主頁 > 後端開發 > 《C++ Primer》筆記 第12章 動態記憶體

《C++ Primer》筆記 第12章 動態記憶體

2021-02-10 06:21:05 後端開發

shared_ptr和unique_ptr都支持的操作 解釋
shared_ptr sp或unique_ptr up 空智能指標,可以指向型別為T的物件
p 將p用作一個條件判斷,若p指向一個物件,則為true
*p 解參考p,獲得它指向的物件
p->mem 等價于(*p).mem
p.get() 回傳p中保存的指標,要小心使用,若智能指標釋放了其物件,回傳的指標所指向的物件也就消失了
swap(p, q)或p.swap(q) 交換p和q中的指標
shared_ptr獨有的操作 解釋
make_shared(args) 回傳一個shared_ptr,指向一個動態分配的型別為T的物件,使用args初始化此物件
shared_ptrp(q) p是shared_ptr q的拷貝;此操作會遞增q中的計數器,q中的指標必須能轉換為T*
p = q p和q都是shared_ptr,所保存的指標必須能相互轉換,此操作會遞減p的參考計數,遞增q的參考計數;若p的參考計數變為0,則將其管理的原記憶體釋放
p.unique() 若p.use_count()為1,回傳true;否則回傳false
p.use_count() 回傳與p共享物件的智能指標數量;可能很慢,主要用于除錯
  1. shared_ptr允許多個指標指向同一個物件;unique_ptr則“獨占”所指向的物件,標準庫還定義了一個名為weak_ptr的伴隨類,它是一種弱參考,指向shared_ptr所管理的物件,這三種型別都定義在memory頭檔案中,

  2. make_shared的標準庫函式在動態記憶體中分配一個物件并初始化它,回傳指向此物件的shared_ptr,與智能指標一樣,make_shared也定義在頭檔案memory中,

    // 指向一個值為42的int的shared_ptr
    shared_ptr<int> p3 = make_shared<int>(42);
    // p4指向一個值為“9999999999”的string
    shared_ptr<string> p4 = make_shared<string>(10, '9');
    // p5指向一個值初始化的int,即,值為0
    shared_ptr<int> p5 = make_shared<int>();
    

    我們通常用auto定義一個物件來保存make_shared的結果,這種方式較為簡單,

    // p6指向一個動態分配的空vector<string>
    auto p6 = make_shared<vector<string>>();
    
  3. shared_ptr自動銷毀所管理的物件,shared_ptr還會自動釋放相關聯的記憶體,如果你將shared_ptr存放于一個容器中,而后不再需要全部元素,而只使用其中一部分,要記得用erase洗掉不再需要的那些元素,

  4. 程式使用動態記憶體出于以下三種原因之一:

    • 程式不知道自己需要使用多少物件
    • 程式不知道所需物件的準確型別
    • 程式需要在多個物件間共享資料
  5. 示例:定義StrBlob類

    /* 
    *  功能:我們希望Blob物件的不同拷貝之間共享相同的元素,
    *  即,當我們拷貝一個Blob時,原Blob物件及其拷貝應該參考相同的底層元素,
    */
    
    #include <iostream>
    #include <vector>
    #include <string>
    #include <memory>
    
    using namespace std;
    
    class StrBlob
    {
    public:
        typedef vector<string>::size_type size_type;
        // 建構式
        StrBlob() : data(make_shared<vector<string>>()) {}
        StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        // 添加和洗掉元素
        void push_back(const string &t) { data->push_back(t); }
        void pop_back()
        {
            check(0, "pop_back on empty StrBlob");
            data->pop_back();
        }
        // 元素訪問:非const和const版本
        string &front()
        {
            // 如果vector為空,check會拋出一個例外
            check(0, "front on empty StrBlob");
            return data->front();
        }
        string &back()
        {
            check(0, "back on empty StrBlob");
            return data->back();
        }
        const string &front() const
        {
            check(0, "const front on empty StrBlob");
            return data->front();
        }
        const string &back() const
        {
            check(0, "const back on empty StrBlob");
            return data->back();
        }
    
    private:
        shared_ptr<vector<string>> data;
        // 如果data[i]不合法,拋出一個例外
        void check(size_type i, const string msg) const
        {
            if (i >= data->size()) 
                throw out_of_range(msg);
        }
    };
    
  6. 默認情況下,動態分配的物件是默認初始化的,這意味著內置型別或組合型別的物件的值將是未定義的,而型別別物件將用默認建構式進行初始化,

  7. 我們可以使用傳統的構造方式(使用圓括號),也可以使用串列初始化(使用花括號):

    int *pi = new int(1024); // pi指向的物件的值為1024
    string *ps = new string(10, '9'); // *ps為“9999999999”
    // vector有10個元素,值依次從0到9
    
  8. 也可以對動態分配的物件進行值初始化,只需在型別名之后跟一對空括號即可:

    string *ps1 = new string; // 默認初始化為空string
    string *ps = new string(); // 值初始化為空string
    int *pi1 = new int; // 默認初始化;*pi1的值未定義
    int *pi2 = new int(); // 值初始化為0;*pi2為0
    
  9. 如果我們提供了一個括號包圍的初始化器,就可以使用auto從此初始化器來推斷我們想要分配的物件的型別,但是,由于編譯器要用初始化器的型別來推斷要分配的型別,只有當括號中僅有單一初始化器時才可以使用auto:

    auto p1 = new auto(obj); // p指向一個與obj型別相同的物件
                             // 該物件用obj進行初始化
    auto p2 = new auto{a, b, c}; // 錯誤:括號中只能有單個初始化器
    
  10. 用new分配const物件是合法的:

    // 分配并初始化一個const int
    const int *pci = new const int(1024);
    // 分配并默認初始化一個const的空string
     const string *pcs = new const string;
    
  11. 類似其他任何const物件,一個動態分配的const物件必須進行初始化,對于一個定義了默認建構式的型別別,其const動態物件可以隱式初始化,而其它型別的物件就必須顯式初始化,由于分配的物件是const的,new回傳的指標是一個指向const的指標,

  12. 默認情況下,如果new不能分配所要求的記憶體空間,它會拋出一個型別為bad_alloc的例外,

  13. 定位new運算式允許我們向new傳遞額外的引數,在此例中,我們傳遞給它一個由標準庫定義的名為nothrow的物件,如果將nothrow傳遞給new,我們的意圖是告訴它不能拋出例外,如果這種形式的new不能分配所需記憶體,它會回傳一個空指標,bad_allocnothrow都定義在頭檔案new中,

    // 如果分配失敗,new回傳一個空指標
    int *p1 = new int; // 如果分配失敗,new拋出std::bad_alloc
    int *p2 = new (nothrow) int; // 如果分配失敗,new回傳一個空指標
    
  14. 傳遞給delete的指標必須指向動態分配的記憶體,或者是一個空指標,

  15. 雖然一個const物件的值不能被改變,但它本身是可以被銷毀的:

    const int *pci = new const int(1024);
    delete pci; // 正確:釋放一個const物件
    
  16. 由內置指標(而不是智能指標)管理的動態記憶體在被顯式釋放前一直都會存在,

  17. 空懸指標:指向一塊曾經保存資料物件但現在已經無效的記憶體的指標,

  18. 避免空懸指標問題:在指標即將要離開其作用域之前釋放掉它所關聯的記憶體,如果我們需要保留指標,可以在delete之后將nullptr賦予指標,這樣就清楚地指出指標不指向任何物件,這只是提供了有限的保護

  19. 可以用new回傳的指標來初始化智能指標:

    shared_ptr<double> p1; // shared_ptr可以指向一個double
    shared_ptr<int> p2(new int(42)); // p2指向一個值為42的int
    
  20. 接受指標引數的智能指標建構式是explicit的,因此,我們不能將一個內置指標隱式轉化為一個智能指標,必須使用直接初始化形式來初始化一個智能指標:

    shared_ptr<int> p1 = new int(1024); // 錯誤:必須使用直接初始化形式
    shared_ptr<int> p2(new int(1024)); // 正確;使用了直接初始化形式
    

    一個回傳shared_ptr的函式不能在其回傳陳述句中隱式轉換一個普通指標:

    shared_ptr<int> clone(int p)
    {
    	return new int(p); // 錯誤:隱式轉換為shared_ptr<int>
    }
    

    我們必須將shared_ptr顯式系結到一個想要回傳的指標上:

    shared_ptr<int> clone(int p)
    {
    	// 正確:顯式地用int*創建shared_ptr<int>
    	return shared_ptr<int>(new int(p));
    }
    
  21. 默認情況下,一個用來初始化智能指標的普通指標必須指向動態記憶體,因為智能指標默認使用delete釋放它所關聯的物件,我們可以將智能指標系結到一個指向其他型別的資源的指標上,但是為了這樣做,必須提供自己的操作來替代delete,

定義和改變shared_ptr的其他方法 解釋
shared_ptr p(q) p管理內置指標q所指向的物件;q必須指向new分配的記憶體,且能夠轉換為T*型別
shared_ptr p(u) p從unique_ptr u那里接管了物件的所有權;將u置為空
shared_ptr p(q, d) p接管了內置指標q所指向的物件的所有權,q必須能轉換為T*型別,p將使用可呼叫物件d來代替delete
shared_ptr p(p2, d) p是shared_ptr p2的拷貝,唯一的區別是p將用可呼叫物件d來代替delete
p.reset()或p.reset(q)或p.reset(q, d) 若p是唯一指向其物件的shared_ptrreset會釋放此物件,若傳遞了可選的引數內置指標q,會令p指向q,否則會將p置為空,若還傳遞了引數d,將會呼叫d而不是delete來釋放q
  1. 不要混合使用普通指標和智能指標,shared_ptr可以協調物件的析構,但這僅限于其自身的拷貝(也是shared_ptr)之間,

    // 在函式被呼叫時ptr被創建并初始化
    void process(shared_ptr<int> ptr)
    {
        // 使用ptr
    } // ptr離開作用域,被銷毀
    
    int main()
    {
        shared_ptr<int> p(new int(42)); // 參考計數為1
        process(p); // 拷貝p會遞增它的參考計數;在process中參考計數值為2
        int i = *p; // 正確:參考計數值為1
        
        int *x(new int(1024)); // 危險:x是一個普通指標,不是一個智能指標
        process(x); // 錯誤:不能將int*轉換為一個shared_ptr<int>
        process(shared_ptr<int>(x)); // 合法的,但記憶體會被釋放!
        int j = *x; // 未定義的:x是一個空懸指標!
        
        return 0;
    }
    

    使用一個內置指標來訪問一個智能指標所負責的物件是很危險的,因為我們無法知道物件何時會被銷毀,

  2. get用來將指標的訪問權限傳遞給代碼,你只有在確定代碼不會delete指標的情況下,才能使用get,特別是,永遠不要用get初始化另一個智能指標或者為另一個智能指標賦值,

  3. 函式退出有兩種可能,正常處理結束或者發生了例外,無論哪種情況,區域物件都會被銷毀,所以,如果使用智能指標,即使程式塊過早結束,智能指標類也能確保在記憶體不再需要時將其釋放:

    void f()
    {
    	shared_ptr<int> sp(new int(42)); // 分配一個新物件
    	// 這段代碼拋出一個例外,且在f中未被捕獲
    } // 在函式結束時shared_ptr自動釋放記憶體
    
  4. 如果使用內置指標管理記憶體,且在new之后在對應的delete之前發生了例外,則記憶體不會被釋放,

  5. 智能指標可以提供對動態分配的記憶體安全而又方便的管理,但這建立在正確使用的前提下,為了正確使用智能指標,我們必須堅持一些基本規范:

    • 不使用相同的內置指標值初始化(或reset)多個智能指標,
    • 不delete get()回傳的指標,
    • 不使用get()初始化或reset另一個智能指標,
    • 如果你使用get()回傳的指標,記住當最后一個對應的智能指標銷毀后,你的指標就變為無效了,
    • 如果你使用智能指標管理的資源不是new分配的記憶體,記住傳遞給它一個洗掉器,
  6. 一個unique_ptr“擁有”它所指向的物件,與shared_ptr不同,某個時刻只能有一個unique_ptr指向一個給定物件,當unique_ptr被銷毀時,它所指向的物件也被銷毀,

  7. 與shared_ptr不同,沒有類似make_shared的標準庫函式回傳一個unique_ptr,當我們定義一個unique_ptr時,需要將其系結到一個new回傳的指標上,類似shared_ptr,初始化unique_ptr必須采用直接初始化形式:

    unique_ptr<double> p1; // 可以指向一個double的unique_ptr
    unique_ptr<int> p2(new int(42)); // p2指向一個值為42的int
    

    由于一個unique_ptr擁有它所指向的物件,因此unique_ptr不支持普通的拷貝或賦值操作:

    unique_ptr<string> p1(new string("Stogosaurus"));
    unique_ptr<string> p2(p1); // 錯誤:unique_ptr不支持拷貝
    unique_ptr<string> p3;
    p3 = p2; // 錯誤:unique_ptr不支持賦值
    
unique_ptr操作 解釋
unique_ptr u1或unique_ptr<T, D> u2 空unique_ptr,可以指向型別為T的物件,u1會使用delete來釋放它的指標;u2會使用一個型別為D的可呼叫物件來釋放它的指標
unique_ptr<T, D> u(d) 空unique_ptr,指向型別為T的物件,用型別為D的物件d代替delete
u = nullptr 釋放u指向的物件,將u置為空
u.release() u放棄對指標的控制權,回傳指針,并將u置為空
u.reset()或u.reset(q)或u.reset(nullptr) 釋放u指向的物件,如果提供了內置指標q,令u指向這個物件;否則將u置為空
  1. 雖然我們不能拷貝或賦值unique_ptr,但可以通過呼叫release或reset將指標的所有權從一個(非const)unique_ptr轉移給另一個unique:

    // 將所有權從p1(指向string Stogosaurus)轉移給p2
    unique_ptr<string> p2(p1.release()); // release將p1置為空
    unique_ptr<string> p3(new string("Trex"));
    // 將所有權從p3轉移給p2
    p2.reset(p3.release()); // reset釋放了p2原來指向的記憶體
    
  2. 呼叫release會切斷unique_ptr和它原來管理的物件間的聯系,release回傳的指標通常被用來初始化另一個智能指標或給另一個智能指標賦值,但是,如果我們不用另一個智能指標來保存release回傳的指標,我們的程式就要負責資源的釋放:

    p2.release(); // 錯誤:p2不會釋放記憶體,而且我們丟失了指標
    auto p = p2.release(); // 正確,但我們必須記得delete(p)
    
  3. 不能拷貝unique_ptr的規則有一個例外:我們可以拷貝或賦值一個將要被銷毀的unique_ptr,最常見的例子是從函式回傳一個unique_ptr:

    unique_ptr<int> clone(int p)
    {
    	// 正確:從int*創建一個unique_ptr<int>
    	return unique_ptr<int>(new int(p));
    }
    

    還可以回傳一個區域物件的拷貝

    unique_ptr<int> clone(int p)
    {
    	unique_ptr<int> ret(new int(p));
    	// ...
    	return ret;
    }
    
  4. 多載一個unique_ptr中的洗掉器會影響到unique_ptr型別以及如何構造(或reset)該型別的物件,與多載關聯容器的比較操作類似,我們必須在尖括號中unique_ptr指向型別之后提供洗掉器型別,在創建或reset一個這種unique_ptr型別的物件時,必須提供一個指定型別的可呼叫物件(洗掉器)

    // p指向一個型別為objT的物件,并使用一個型別為delT的物件釋放objT物件
    // 它會呼叫一個名為fcn的delT型別物件
    unique_ptr<objT, delT> p(new objT, fcn);
    
    void f(destination &d /* 其他需要的引數 */)
    {
    	connection c = connect(&d); // 打開連接
    	// 當p被銷毀時,連接將會關閉
    	unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
    	// 使用連接
    	// 當f退出時(即使是由于例外而退出),connection會被正確關閉
    }
    
  5. weak_ptr是一種不控制所指向物件生存期的智能指標,它指向由一個shared_ptr管理的物件,將一個weak_ptr系結到一個shared_ptr不會改變shared_ptr的參考計數,

weak_ptr 解釋
weak_ptr w 空weak_ptr可以指向型別為T的物件
weak_ptr w(sp) 與shared_ptr sp指向相同物件的weak_ptr,T必須能轉換為sp指向的型別
w = p p可以是一個shared_ptr或一個weak_ptr,賦值后w與p共享物件
w.reset() 將w置為空
w.use_count() 與w共享物件的shared_ptr的數量
w.expired() 若w.use_count()為0,回傳true,否則回傳false
w.lock() 如果expired為true,回傳一個空shared_ptr;否則回傳一個指向w的物件的shared_ptr
  1. 由于物件可能不存在,我們不能使用weak_ptr直接訪問物件,而必須呼叫lock,

    auto p = make_shared<int>(42);
    weak_ptr<int> wp(p); // wp弱共享p;p的參考計數未改變
    
    if (shared_ptr<int> np = wp.lock()) // 如果np不為空則條件成立
    {
    	// 在if中,np與p共享物件
    	// ...
    }
    

    只有當lock呼叫回傳true時我們才會進入if陳述句體,在if中,使用np訪問共享物件是安全的,

  2. 示例:StrBlobPtr

    /*
    *  功能:為StrBlob新增一個伴隨指標類
    */
    #include <iostream>
    #include <vector>
    #include <string>
    #include <initializer_list>
    #include <memory>
    #include <stdexcept>
    using namespace std;
    
    // 提前宣告,StrBlob中的友類宣告所需
    class StrBlobPtr;
    
    class StrBlob
    {
        friend class StrBlobPtr;
    public:
        typedef vector<string>::size_type size_type;
        StrBlob();
        StrBlob(initializer_list<string> il);
        StrBlob(vector<string> *p);
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        // 添加和洗掉元素
        void push_back(const string &t) {data->push_back(t);}
        void pop_back();
        // 元素訪問
        string& front();
        const string& front() const;
        string& back();
        const string& back() const ;
    
        // 提供給StrBlobPtr的介面
        StrBlobPtr begin();  // 定義StrBlobPtr后才能定義這兩個函式
        StrBlobPtr end();
        // const版本
        StrBlobPtr begin() const;
        StrBlobPtr end() const;
    private:
        shared_ptr<std::vector<std::string>> data;
        // 如果data[i]不合法,拋出一個例外
        void check(size_type i, const std::string &msg) const;
    };
    
    inline StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }
    inline StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) { }
    inline StrBlob::StrBlob(vector<string> *p): data(p) { }
    
    inline void StrBlob::check(size_type i, const string &msg) const
    {
        if (i >= data->size())
        {
            throw out_of_range(msg);
        }
    }
    
    inline string& StrBlob::front()
    {
        // 如果vector為空,check會拋出一個例外
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    // const版本front
    inline const string& StrBlob::front() const
    {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    inline string& StrBlob::back()
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    // const版本back
    inline const string& StrBlob::back() const
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    inline void StrBlob::pop_back()
    {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    
    // 當試圖訪問一個不存在的元素時,StrBlobPtr拋出一個例外
    class StrBlobPtr
    {
        friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
    public:
        StrBlobPtr(): curr(0) { }
        StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
        StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
    
        string& deref() const;
        string& deref(int off) const;
        StrBlobPtr& incr();     // 前綴遞增
        StrBlobPtr& decr();     // 前綴遞減
    private:
        // 若檢查成功,check回傳一個指向vector的shared_ptr
        shared_ptr<vector<string>> check(size_t, const string&) const;
    
        // 保存一個weak_ptr,意味著底層vector可能會被銷毀
        weak_ptr<vector<string>> wptr;
        size_t curr;    // 在陣列中的當前位置
    };
    
    inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
    {
        auto ret = wptr.lock(); // vector還存在嗎?
        if (!ret)
        {
           throw runtime_error("unbound StrBlobPtr");
        }
        if (i >= ret->size())
        {
           throw out_of_range(msg);
        }
        return ret; // 否則,回傳指向vector的shared_ptr
    }
    
    inline string& StrBlobPtr::deref() const
    {
        auto p = check(curr, "dereference past end");
        return (*p)[curr]; // (*p)是物件所指向的vector
    }
    
    inline string& StrBlobPtr::deref(int off) const
    {
        auto p = check(curr + off, "dereference past end");
        return (*p)[curr + off]; // (*p)是物件所指向的vector
    }
    
    // 前綴遞增:回傳遞增后的物件的參考
    inline StrBlobPtr& StrBlobPtr::incr()
    {
        // 如果curr已經指向容器的尾后位置,就不能遞增它
        check(curr, "increment past end of StrBlobPtr");
        ++curr; // 推進當前位置
        return *this;
    }
    
    // 前綴遞減:回傳遞減后的物件的參考
    inline StrBlobPtr& StrBlobPtr::decr()
    {
        // 如果curr已經為0,遞減它就會產生一個非法下標
        --curr;     // 遞減當前位置
        check(-1, "decrement past begin of StrBlobPtr");
        return *this;
    }
    
    // StrBlob的begin和end成員的定義
    inline StrBlobPtr StrBlob::begin()
    {
        return StrBlobPtr(*this);
    }
    
    inline StrBlobPtr StrBlob::end()
    {
      	auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    // const版本
    inline StrBlobPtr StrBlob::begin() const
    {
    	  return StrBlobPtr(*this);
    }
    
    inline StrBlobPtr StrBlob::end() const
    {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    // StrBlobPtr的比較操作
    inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
    {
        auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
        // 若底層的vector是同一個
        if (l == r)
        {
        	  // 則兩個指標都是空,或者指向相同元素時,它們相等
        	  return (!r || lhs.curr == rhs.curr);
        }
        else
        {
        	  return false; // 若指向不同vector,則不可能相等
        }
    }
    
    inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
    {
    	  return !eq(lhs, rhs);
    }
    
  3. 為了讓new分配一個物件陣列,我們要在型別名之后跟一對方括號,在其中指明要分配的物件的數目,在下例中,new分配要求數量的物件并(假定分配成功后)回傳指向第一個物件的指標:

    // 呼叫get_size確定分配多少個int
    int *pia = new int[get_size()]; // pia指向第一個int
    

    方括號中的大小必須是整型,但不必是常量,

  4. 分配一個陣列會得到一個元素型別的指標,而非一個陣列型別的物件,由于分配的記憶體并不是一個陣列型別,因此不能對動態陣列呼叫begin或end,也不能用范圍for陳述句來處理(所謂的)動態陣列中的元素,要記住我們所說的動態陣列并不是陣列型別,這是很重要的,

  5. 默認情況下,new分配的物件,不管是單個分配的還是陣列中的,都是默認初始化的,可以對陣列中的元素進行值初始化,方法是在大小之后跟一對空括號,我們還可以提供一個元素初始化器的花括號串列,

    int *pia = new int[10]; // 10個未初始化的int
    int *pia2 = new int[10](); // 10個值初始化為0的int
    string *psa = new string[10]; // 10個空string
    string *psa2 = new string[10](); // 10個空string
    
    // 10個int分別用串列中對應的初始化器初始化
    int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    // 10個string,前4個用給定的初始化器初始化,剩余的進行值初始化
    string *ps3 = new string[10]{"a", "an", "the", string(3,'x')};
    
  6. 如果初始化器數目小于元素數目,剩余元素將進行值初始化,如果初始化器數目大于元素數目,則new運算式失敗,不會分配任何記憶體,new會拋出一個型別為bad_array_new_length的例外,類似bad_alloc,此型別定義在頭檔案new中,

  7. 雖然我們用空括號對陣列中元素進行值初始化,但不能在括號中給出初始化器,這意味著不能用auto分配陣列,

  8. 動態分配一個空陣列是合法的,當我們用new分配一個大小為0的陣列時,new回傳一個合法的非空指標,此指標保證與new回傳的其他任何指標都不相同,我們可以像使用尾后迭代器一樣使用這個指標,但此指標不能解參考——畢竟它不指向任何元素,

    char arr[0]; // 錯誤:不能定義長度為0的陣列
    char *cp = new char[0]; // 正確:但cp不能解參考
    
  9. 為了釋放動態陣列,我們使用一種特殊形式的delete——在指標前加上一個空方括號對:

    delete p; // p必須指向一個動態分配的物件或為空
    delete [] pa; // pa必須指向一個動態分配的陣列或為空
    
  10. 陣列中的元素按逆序銷毀,即,最后一個元素首先被銷毀,然后是倒數第二個,以此類推,

  11. 當我們使用一個型別別名來定義一個陣列型別時,在new運算式中不使用[],即使是這樣,在釋放一個陣列指標時也必須使用方括號,

    typedef int arrT[42]; // arrT是42個int的陣列的型別別名
    int *p = new arrT; // 分配一個42個int的陣列;p指向第一個元素
    delete [] p; // 方括號是必需的,因為我們當初分配的是一個陣列
    
  12. 如果我們在delete一個陣列指標時忘記了方括號,或者在delete一個單一物件的指標時使用了方括號,編譯器很可能不會給出警告,我們的程式可能在執行程序中在沒有任何警告的情況下行為例外,

  13. 標準庫提供了一個可以管理new分配的陣列的unique_ptr版本,為了用一個unique_ptr管理動態陣列,我們必須在物件型別后面跟一對空方括號:

    // up指向一個包含10個未初始化int的陣列
    unique_ptr<int[]> up(new int[10]);
    up.release(); // 自動用delete[]銷毀其指標
    

    型別說明符中的方括號(<int[]>)指出up指向一個int陣列而不是一個int,由于up指向一個陣列,當up銷毀它管理的指標時,會自動使用delete[],

指向陣列的unique_ptr 解釋
—— 指向陣列的unique_ptr不支持成員訪問運算子(點和箭頭運算符)
—— 其他unique_ptr操作不變
unique_ptr<T[]> u u可以指向一個動態分配的陣列,陣列元素型別為T
unique_ptr<T[]> u(p) u指向內置指標p所指向的動態分配的陣列,p必須能轉換為型別T*
u[i] 回傳u擁有的陣列中位值i處的物件,u必須指向一個陣列
  1. unique_ptr不同,shared_ptr不直接支持管理動態陣列,如果希望使用shared_ptr管理一個動態陣列,必須提供自己定義的洗掉器:

    // 為了使用shared_ptr,必須提供一個洗掉器
    shared_ptr<int> sp(new int[10], [](int *p){ delete[] p; });
    sp.reset(); // 使用我們提供的lambda釋放陣列,它使用delete[]
    

    如果未提供洗掉器,這段代碼將是未定義的,默認情況下,shared_ptr使用delete銷毀它指向的物件,

  2. shared_ptr未定義下標運算子,而且智能指標型別不支持指標算術運算,因此,為了訪問陣列中的元素,必須用get獲取一個內置指標,然后用它來訪問陣列元素,

    // shared_ptr未定義下標運算子,并且不支持指標的算術運算
    for (size_t i = 0; i != 10; ++i)
    	*(sp.get() + i) = i; // 使用get獲取一個內置指標
    
  3. new有一些靈活性上的局限,其中一方面表現在它將記憶體分配和物件構造組合在了一起,類似的,delete將物件析構和記憶體釋放組合在了一起,更重要的是,那些沒有默認建構式的類就不能動態分配陣列了,

  4. 標準庫allocator類定義在頭檔案memory中,它幫助我們將記憶體分配和物件構造分離開來,

標準庫allocator類及其演算法 解釋
allocator a 定義了一個名為a的allocator物件,它可以為型別為T的物件分配記憶體
a.allocate(n) 分配一段原始的、未構造的記憶體,保存n個型別為T的物件
a.deallocate(p, n) 釋放從T*指標p中地址開始的記憶體,這塊記憶體保存了n個型別為T的物件;p必須是一個先前由allocate回傳的指標,且n必須是p創建時所要求的大小,在呼叫deallocate之前,用戶必須對每個在這塊記憶體中創建的物件呼叫destroy
a.construct(p, args) p必須是一個型別為T*的指標,指向一塊原始記憶體;args被傳遞給型別為T的建構式,用來在p指向的記憶體中構造一個物件
a.destroy(p) p為T*型別的指標,此演算法對p指向的物件執行解構式
allocator<string> alloc; // 可以分配string的allocator物件
auto const p = alloc.allocate(n); // 分配n個未初始化的string

auto q = p; // q指向最后構造的元素之后的位置
alloc.construct(q++); // *q為空字串
alloc.construct(q++, 10, 'c'); // *q為cccccccccc
alloc.construct(q++, "hi"); // *q為hi

cout << *p << endl; // 正確:使用string的輸出運算子
cout << *q << endl; // 災難:q指向未構造的記憶體!

while (q != p)
    alloc.destroy(--q); // 釋放我們真正構造的string

alloc.deallocate(p, n); // 釋放記憶體
  1. 為了使用allocate回傳的記憶體,我們必須用construct構造物件,使用未構造的記憶體,其行為是未定義的,
  2. 我們只能對真正構造了的元素進行destroy操作,
allocator演算法 解釋
—— 這些函式在給定目的位置創建元素,而不是由系統分配記憶體給它們
uninitialized_copy(b, e, b2) 從迭代器b和e指出的輸入范圍中拷貝元素到迭代器b2指定的未構造的原始記憶體中,b2指向的記憶體必須足夠大,能容納輸入序列中元素的拷貝
uninitialized_copy_n(b, n, b2) 從迭代器b指向的元素開始,拷貝n個元素到b2開始的記憶體中
uninitialized_fill(b, e, t) 在迭代器b和e指定的原始記憶體范圍中創建物件,物件的值均為t的拷貝
uninitialized_fill_n(b, n, t) 從迭代器b指向的記憶體地址開始創建n個物件,b必須指向足夠大的未構造的原始記憶體,能夠容納給定數量的物件
  1. 類似copyuninitialized_copy回傳(遞增后的)目的位置迭代器,因此,一次uninitialized_copy呼叫會回傳一個指標,指向最后一個構造的元素之后的位置,

    // 分配比vi中元素所占用空間大一倍的動態記憶體
    auto p = alloc.allocate(vi.size() * 2);
    // 通過拷貝vi中的元素來構造從p開始的元素
    auto q = uninitialized_copy(vi.begin(), vi.end(), p);
    // 將剩余元素初始化為42
    uninitialized_fill_n(q, vi.size(), 42);
    
  2. 如果兩個類概念上“共享”了資料,可以使用shared_ptr來反映資料結構中的這種共享關系,

  3. 當我們設計一個類時,在真正實作成員之前先撰寫程式使用這個類,是一種非常有用的方法,通過這種方法,可以看到類是否具有我們所需要的操作,

  4. 使用標準庫:文本查詢程式

    • my_TextQuery.cpp

    #include "my_TextQuery.h"
    #include "make_plural.h"
    
    #include <cstddef>
    #include <memory>
    #include <sstream>
    #include <string>
    #include <vector>
    #include <map>
    #include <set>
    #include <iostream>
    #include <fstream>
    #include <cctype>
    #include <cstring>
    #include <utility>
    
    using std::size_t;
    using std::shared_ptr;
    using std::istringstream;
    using std::string;
    using std::getline;
    using std::vector;
    using std::map;
    using std::set;
    using std::cerr;
    using std::cout;
    using std::cin;
    using std::ostream;
    using std::endl;
    using std::ifstream;
    using std::ispunct;
    using std::tolower;
    using std::strlen;
    using std::pair;
    
    // read the input file and build the map of lines to line numbers
    TextQuery::TextQuery(ifstream &is): file(new vector<string>)
    {
        string text;
        while (getline(is, text)) {       // for each line in the file
    		file.push_back(text);        // remember this line of text
    		int n = file.size() - 1;     // the current line number
    		istringstream line(text);     // separate the line into words
    		string word;
    		while (line >> word) {        // for each word in that line
                word = cleanup_str(word);
                // if word isn't already in wm, subscripting adds a new entry
                auto &lines = wm[word]; // lines is a shared_ptr
                if (!lines) // that pointer is null the first time we see word
                    lines.reset(new set<line_no>); // allocate a new set
                lines->insert(n);      // insert this line number
    		}
    	}
    }
    
    // not covered in the book -- cleanup_str removes
    // punctuation and converts all text to lowercase so that
    // the queries operate in a case insensitive manner
    string TextQuery::cleanup_str(const string &word)
    {
        string ret;
        for (auto it = word.begin(); it != word.end(); ++it) {
            if (!ispunct(*it))
                ret += tolower(*it);
        }
        return ret;
    }
    
    QueryResult
    TextQuery::query(const string &sought) const
    {
    	// we'll return a pointer to this set if we don't find sought
    	static shared_ptr<set<line_no>> nodata(new set<line_no>);
    
        // use find and not a subscript to avoid adding words to wm!
        auto loc = wm.find(cleanup_str(sought));
    
    	if (loc == wm.end())
    		return QueryResult(sought, nodata, file);  // not found
    	else
    		return QueryResult(sought, loc->second, file);
    }
    
    ostream &print(ostream & os, const QueryResult &qr)
    {
        // if the word was found, print the count and all occurrences
        os << qr.sought << " occurs " << qr.lines->size() << " "
           << make_plural(qr.lines->size(), "time", "s") << endl;
    
        // print each line in which the word appeared
    	for (auto num : *qr.lines) // for every element in the set
    		// don't confound the user with text lines starting at 0
            os << "\t(line " << num + 1 << ") "
    		   << qr.file.begin().deref(num) << endl;
    
    	return os;
    }
    
    // debugging routine, not covered in the book
    void TextQuery::display_map()
    {
        auto iter = wm.cbegin(), iter_end = wm.cend();
    
        // for each word in the map
        for ( ; iter != iter_end; ++iter) {
            cout << "word: " << iter->first << " {";
    
            // fetch location vector as a const reference to avoid copying it
            auto text_locs = iter->second;
            auto loc_iter = text_locs->cbegin(),
                            loc_iter_end = text_locs->cend();
    
            // print all line numbers for this word
            while (loc_iter != loc_iter_end)
            {
                cout << *loc_iter;
    
                if (++loc_iter != loc_iter_end)
                     cout << ", ";
    
             }
    
             cout << "}\n";  // end list of output this word
        }
        cout << endl;  // finished printing entire map
    }
    
    • my_TextQuery.h

    #ifndef TEXTQUERY_H
    #define TEXTQUERY_H
    
    #include <memory>
    #include <string>
    #include <vector>
    #include <map>
    #include <set>
    #include <fstream>
    #include "my_QueryResult.h"
    
    /* this version of the query classes includes two
     * members not covered in the book:
     *   cleanup_str: which removes punctuation and
     *                converst all text to lowercase
     *   display_map: a debugging routine that will print the contents
     *                of the lookup mape
    */
    
    class QueryResult; // declaration needed for return type in the query function
    class TextQuery {
    public:
    	using line_no = std::vector<std::string>::size_type;
    	TextQuery(std::ifstream&);
        QueryResult query(const std::string&) const;
        void display_map();        // debugging aid: print the map
    private:
        StrBlob file; // input file
        // maps each word to the set of the lines in which that word appears
        std::map<std::string,
    	         std::shared_ptr<std::set<line_no>>> wm;
    
    	// canonicalizes text: removes punctuation and makes everything lower case
        static std::string cleanup_str(const std::string&);
    };
    
    #endif
    
    • make_plural.h

    #include <cstddef>
    using std::size_t;
    
    #include <string>
    using std::string;
    
    #include <iostream>
    using std::cout; using std::endl;
    
    #ifndef MAKE_PLURAL_H
    #define MAKE_PLURAL_H
    
    // return the plural version of word if ctr is greater than 1
    inline
    string make_plural(size_t ctr, const string &word, 
                                   const string &ending)
    {
    	return (ctr > 1) ? word + ending : word;
    }
    
    #endif
    
    • my_QueryResult.h

    #ifndef QUERYRESULT_H
    #define QUERYRESULT_H
    
    #include <memory>
    #include <string>
    #include <vector>
    #include <set>
    #include <iostream>
    #include "my_StrBlob.h"
    
    class QueryResult {
    friend std::ostream& print(std::ostream&, const QueryResult&);
    public:
    	typedef std::vector<std::string>::size_type line_no;
    	typedef std::set<line_no>::const_iterator line_it;
    	QueryResult(std::string s,
    	            std::shared_ptr<std::set<line_no>> p,
    	            StrBlob f):
    		sought(s), lines(p), file(f) { }
    	std::set<line_no>::size_type size() const  { return lines->size(); }
    	line_it begin() const { return lines->cbegin(); }
    	line_it end() const   { return lines->cend(); }
    	StrBlob get_file() { return file; }
    private:
    	std::string sought;  // word this query represents
    	std::shared_ptr<std::set<line_no>> lines; // lines it's on
    	StrBlob file;  //input file
    };
    
    std::ostream &print(std::ostream&, const QueryResult&);
    
    #endif
    
    • my_StrBlob.h

    #ifndef MY_STRBLOB_H
    #define MY_STRBLOB_H
    
    #include <vector>
    #include <string>
    #include <initializer_list>
    #include <memory>
    #include <stdexcept>
    using namespace std;
    
    // 提前宣告,StrBlob中的友類宣告所需
    class StrBlobPtr;
    
    class StrBlob
    {
        friend class StrBlobPtr;
    public:
        typedef vector<string>::size_type size_type;
        StrBlob();
        StrBlob(initializer_list<string> il);
        StrBlob(vector<string> *p);
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        // 添加和洗掉元素
        void push_back(const string &t) {data->push_back(t);}
        void pop_back();
        // 元素訪問
        string& front();
        const string& front() const;
        string& back();
        const string& back() const ;
    
        // 提供給StrBlobPtr的介面
        StrBlobPtr begin();  // 定義StrBlobPtr后才能定義這兩個函式
        StrBlobPtr end();
        // const版本
        StrBlobPtr begin() const;
        StrBlobPtr end() const;
    private:
        shared_ptr<std::vector<std::string>> data;
        // 如果data[i]不合法,拋出一個例外
        void check(size_type i, const std::string &msg) const;
    };
    
    inline StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }
    inline StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) { }
    inline StrBlob::StrBlob(vector<string> *p): data(p) { }
    
    inline void StrBlob::check(size_type i, const string &msg) const
    {
        if (i >= data->size())
        {
            throw out_of_range(msg);
        }
    }
    
    inline string& StrBlob::front()
    {
        // 如果vector為空,check會拋出一個例外
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    // const版本front
    inline const string& StrBlob::front() const
    {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    inline string& StrBlob::back()
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    // const版本back
    inline const string& StrBlob::back() const
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    inline void StrBlob::pop_back()
    {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    
    // 當試圖訪問一個不存在的元素時,StrBlobPtr拋出一個例外
    class StrBlobPtr
    {
        friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
    public:
        StrBlobPtr(): curr(0) { }
        StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
        StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
    
        string& deref() const;
        string& deref(int off) const;
        StrBlobPtr& incr();     // 前綴遞增
        StrBlobPtr& decr();     // 前綴遞減
    private:
        // 若檢查成功,check回傳一個指向vector的shared_ptr
        shared_ptr<vector<string>> check(size_t, const string&) const;
    
        // 保存一個weak_ptr,意味著底層vector可能會被銷毀
        weak_ptr<vector<string>> wptr;
        size_t curr;    // 在陣列中的當前位置
    };
    
    inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
    {
        auto ret = wptr.lock(); // vector還存在嗎?
        if (!ret)
        {
           throw runtime_error("unbound StrBlobPtr");
        }
        if (i >= ret->size())
        {
           throw out_of_range(msg);
        }
        return ret; // 否則,回傳指向vector的shared_ptr
    }
    
    inline string& StrBlobPtr::deref() const
    {
        auto p = check(curr, "dereference past end");
        return (*p)[curr]; // (*p)是物件所指向的vector
    }
    
    inline string& StrBlobPtr::deref(int off) const
    {
        auto p = check(curr + off, "dereference past end");
        return (*p)[curr + off]; // (*p)是物件所指向的vector
    }
    
    // 前綴遞增:回傳遞增后的物件的參考
    inline StrBlobPtr& StrBlobPtr::incr()
    {
        // 如果curr已經指向容器的尾后位置,就不能遞增它
        check(curr, "increment past end of StrBlobPtr");
        ++curr; // 推進當前位置
        return *this;
    }
    
    // 前綴遞減:回傳遞減后的物件的參考
    inline StrBlobPtr& StrBlobPtr::decr()
    {
        // 如果curr已經為0,遞減它就會產生一個非法下標
        --curr;     // 遞減當前位置
        check(-1, "decrement past begin of StrBlobPtr");
        return *this;
    }
    
    // StrBlob的begin和end成員的定義
    inline StrBlobPtr StrBlob::begin()
    {
        return StrBlobPtr(*this);
    }
    
    inline StrBlobPtr StrBlob::end()
    {
      	auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    // const版本
    inline StrBlobPtr StrBlob::begin() const
    {
    	  return StrBlobPtr(*this);
    }
    
    inline StrBlobPtr StrBlob::end() const
    {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    // StrBlobPtr的比較操作
    inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
    {
        auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
        // 若底層的vector是同一個
        if (l == r)
        {
        	  // 則兩個指標都是空,或者指向相同元素時,它們相等
        	  return (!r || lhs.curr == rhs.curr);
        }
        else
        {
        	  return false; // 若指向不同vector,則不可能相等
        }
    }
    
    inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
    {
    	  return !eq(lhs, rhs);
    }
    
    #endif
    

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/258309.html

標籤:C++

上一篇:在idea中包下存在的java檔案

下一篇:OpenCL學習(1)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more