動態內存、智能指針(shared_ptr、unique_ptr、weak_ptr)、動態數組

文章目錄

  • 三種對象的分類
  • 三種內存的區別
  • 動態內存
    • 概念
    • 智能指針允許的操作
    • 智能指針的使用規范
  • new
    • 概念
    • 內存耗盡/定位new
    • 初始化
      • 默認初始化
      • 直接初始化
      • 值初始化
  • delete
    • 概念
    • 手動釋放動態對象
    • 空懸指針
  • shared_ptr類
    • 格式
    • 獨有的操作
    • make_shared函數
    • shared_ptr的計數器
    • 通過new用普通指針初始化shared_ptr
  • unique_ptr
    • 概念、初始化、特性
    • 支持的操作
  • weak_ptr
    • 概念
  • 關于普通指針和智能指針
    • 不能使用內置指針來訪問shared_ptr所指向的內存
    • get()函數
    • reset函數
    • 處理異常
  • 動態數組
    • 概念
    • new分配對象數組
      • 兩種方法
      • new的返回值
    • 初始化
    • 動態分配一個空數組是合法的
    • 釋放動態數組
    • 動態數組和unique_ptr
    • 動態數組和shared_ptr
  • allocator類
    • new的局限性(使用allocator的原因)
    • 概念即創建銷毀操作
      • construct
      • destroy
      • deallocate
    • 兩個伴隨算法
  • 使用了動態生存期的資源的類
    • 實例


三種對象的分類

三種對象:

  • 全局對象在程序啟動時分配,在程序結束時銷毀。
  • 局部自動對象,當我們進入其定義所在的程序塊時被創建,在離開塊時銷毀。
  • 局部static對象在第一次使用前分配,在程序結束時銷毀。

三種內存的區別

  1. 靜態存儲區:主要存放static靜態變量、全局變量、常量。這些數據內存在編譯的時候就已經為他們分配好了內存,生命周期是整個程序從運行到結束。
  2. 棧區:存放局部變量。在執行函數的時候(包括main這樣的函數),函數內的局部變量的存儲單元會在棧上創建,函數執行完自動釋放,生命周期是從該函數的開始執行到結束。線性結構。
  3. 堆區:程序員自己申請的任意大小的內存。一直存在直到被釋放。鏈表結構。

前兩種內存中的將對象由編譯器自動創建和銷毀。堆也被稱作自由空間,被用來存儲動態分配的對象(程序運行時分配的對象),動態對象的生存期由程序來控制——當動態對象不被使用時,必須顯式地消滅他們。

動態內存

概念

為什么要使用動態內存:

  1. 程序不知道自己需要使用多少對象
  2. 程序不知道所需對象的準確類型
  3. 程序需要在多個對象間共享數據

動態內存的分配與釋放通過一對運算符來完成:

  • new:在動態內存中為對象分配空間并返回一個指向該對象的指針,可以選擇對對象進行初始化;
  • delete:接受一個動態對象的指針,銷毀該對象,并釋放與之關聯的內存。

使用動態內存時容易出現的問題:

  • 忘記釋放內存,產生的內存泄漏。這種內存永遠不可能被歸還給自由空間了。查找本錯誤是非常困難的,通常應用程序運行很長時間之后,真正耗盡內存時,才能檢測到這種錯誤。
  • 在尚有指針引用內存的情況下釋放內存,產生引用非法內存的指針
  • 釋放一個已經被delete的內存,產生double free的問題。出現此操作時,自由空間就可能被破壞。

為了避免上述問題,c++11提供了兩種智能指針(smart pointer)類型來管理動態對象,兩種指針的區別在于管理底層指針的方式:

  • shared_ptr:允許多個指針指向同一個對象
  • unique_ptr:獨占所指向的對象

除此之外,標準庫還定義了一個名為weak_ptr的伴隨類,他是一種弱引用,指向shared_ptr所管理的對象。這三種類型都定義在memory頭文件中。

智能指針允許的操作

在這里插入圖片描述


智能指針的使用規范

  • 不使用相同的內置指針值初始化(或reset)多個智能指針。
  • 不delete get()返回的指針。
  • 不使用get()初始化或reset另一個智能指針。
  • 如果你使用get()返回的指針,記住當最后一個對應的智能指針銷毀后,你的指針就變為無效了。
  • 如果你使用智能指針管理的資源不是new分配的內存,記住傳遞給它一個刪除器。


new

概念

在自由空間分配的內存是無名的,因此new無法為其分配的對象命名,而是返回一個指向該對象的指針:

int *pi = new int; // pi指向一個動態分配的、未初始化的無名對象

當然用new分配const的對象也是合法的,但是一個動態分配的const對象必須進行初始化:

  • 定義了默認構造函數的類類型,const動態對象可以隱式初始化
  • 其他類型必須顯式初始化
  • 由于分配的對象是const的,因此new返回的指針是一個指向const的指針
    在這里插入圖片描述

內存耗盡/定位new

值得一提的是,如果一個程序用光了它所有可用的內存,new表達式就會失敗。 默認情況下。如果new不能分配所要求的內存空間,就會拋出一個bad_alloc的異常。可以改變使用new的方式來阻止它拋出異常:
在這里插入圖片描述
我們稱上面形式的new未定位new(placement new),定位new表達式允許我們向new傳遞額外的參數。上例中,我們傳遞給它一個由標準庫定義的名為nothrow的對象,將nothrow傳遞給new,意圖是告訴它不能拋出異常。

bad_alloc和northrow都定義在頭文件new中。


初始化

默認初始化

默認情況下,動態分配的對象是默認初始化的,這意味著內置類型或組合類型的對象的值將是未定義的,而類類型對象將用默認構造函數進行初始化:
在這里插入圖片描述

直接初始化

為了避免未定義行為,最好使用直接初始化的方式來初始化一個動態分配的對象:

  • 可以用圓括號
  • 可以用列表初始化

在這里插入圖片描述

值初始化

也可以使用值初始化,只需要直接在類型名之后跟一對空括號即可:
在這里插入圖片描述
值得一提的是:

  • 對于定義了自己的構造函數的類類型來說,要求值初始化是沒有意義的——不管采用什么形式,對象都會通過默認構造函數來初始化。
  • 對于內置類型,兩種形式的差別就很大了;值初始化的內置類型對象有著良好定義的值,而默認初始化的對象的值則是未定義的。類似的,對于類中那些依賴于編譯器合成的默認構造函數的內置類型成員,如果它們未在類內被初始化,那么它們的值也是未定義的。


delete

概念

為了防止內存耗盡,在動態內存使用完畢后,必須通過delete表達式將動態內存歸還給系統。

默認情況下shared_ptr和unique_ptr都使用delete釋放指向的對象,但也都允許重載默認的刪除器(delete)。

delete執行兩個動作:

  • 銷毀給定的指針指向的對象
  • 釋放對應的內存

delete表達式接受一個指針,指向我們想要釋放的對象,該指針必須指向動態分配的內存,或者是一個空指針。

通常情況下,

  • 編譯器不能分辨一個指針指向靜態還是動態分配的對象
  • 編譯器不能分辨一個指針所指向的內存是否已經被釋放

對于上述兩種情況,大多數編譯器會編譯通過,盡管他們是錯誤的。

因此,釋放一塊非new分配的內存或者將相同的指針值多次釋放,其行為是未定義的:

在這里插入圖片描述

另外,const對象的值雖然不能夠被改變,但是其本身可以被銷毀:

const int *pci = new const int(1024);
delete pci; // 正確:釋放一個const對象

手動釋放動態對象

智能指針可以在計數值為0時自動釋放動態對象,而delete是一種手動釋放動態對象的方式,這就要求程序員不能忘記delete這一步驟。

與類類型不同,內置類型的對象被銷毀時什么也不會發生。 特別是,當一個指針離開其作用域時,它所指向的對象什么也不會發生。如果這個指針指向的是動態內存,那么內存將不會被自動釋放。

舉個例子:

foo *factory(T arg){return new Foo(arg); // 調用factory的對象負責釋放動態內存
}void use_factory(T arg){Foo *p = factory(arg);
} // p離開了它的作用域,但實際所指向的內存沒有被釋放

本例中,一旦use_factory返回,程序就沒有辦法釋放這塊內存了。修正這個錯誤的唯一方法是在use_factory中記得釋放內存:

void use_factory(T arg){Foo *p = factory(arg);delete p;
}

空懸指針

執行delete p;后,p并不指向空指針,相反的,p的值(指向的地址)不變,但不能再使用p處理該地址的內容(指針失效),也不能重復delete p。 此時p就變成了空懸指針(dangling pointer),即指向一塊曾經保存數據對象但現在已經無效的內存的指針。

不能重復delete p

在這里插入圖片描述

但是可以重復 delete 空指針:

在這里插入圖片描述

避免空懸指針有兩種方法:

  1. 在指針即將要離開其作用域之前釋放掉它所關聯的內存。這樣,在指針關聯的內存被釋放掉后,就沒有機會繼續使用指針了。
  2. 也可以在delete之后將nullptr賦予指針,這樣就清楚地指出指針不指向任何對象。

但重置指針地方法仍然不是完美的,動態內存的一個基本問題是可能有多個指針指向相同的內存。在delete內存之后重置指針的方法只對這個指針有效,對其他任何仍指向(已釋放的)內存的指針是沒有作用的,然而在實際中,查找只想相同內存地所有指針也是異常困難的:
在這里插入圖片描述



shared_ptr類

格式

shared_ptr<類型>

默認初始化的智能指針中保存著一個空指針。


獨有的操作

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
關于上面兩表的具體操作將在下面的普通指針和智能指針中指出。


make_shared函數

shared_ptr可以協調對象的析構,但這僅限于其自身的拷貝(也是shared_ptr)之間。因此最安全的分配和使用動態內存的方法是調用一個名為make_shared的標準庫函數,而不是new。這樣,我們就能在分配對象的同時就將shared_ptr與之綁定,從而避免了無意中將同一塊內存綁定到多個獨立創建的shared_ptr上。

make_shared函數定義在頭文件memory中。

功能:在動態內存中分配一個對象并初始化它,返回此對象的shared_ptr。

實例:
在這里插入圖片描述
調用make_shared<T>時傳遞的參數必須與T的某個構造函數相匹配,換言之,調用make_shared的行為的底層操作其實是調用對應類型的構造函數。

當然,用auto定義一個對象來保存make_shared的結果也是可以的:

auto p = make_shared<vector<string>>();

shared_ptr的計數器

因為shared_ptr允許多個指針指向同一個對象。因此每個shared_ptr都有一個關聯的計數器,通常稱其為引用計數(reference count),用來記錄有多少個其他shared_ptr指向相同的對象。

  • 用一個shared_ptr初始化另一個shared_ptr
  • shared_ptr作為參數傳遞給一個函數
  • shared_ptr作為函數的返回值

時,shared_ptr所關聯的計數器就會遞增。

  • 給shared_ptr賦予一個新值(舊值計數器遞減,新值計數器遞增)
  • shared_ptr被銷毀(例如一個局部的shared_ptr離開其作用域)

時,shared_ptr所關聯的計數器就會遞減。

一旦一個shared_ptr的計數器變為0,它就會自動釋放自己所管理的對象。

由于在最后一個shared_ptr銷毀前內存都不會釋放, 保證shared_ptr在無用之后不再保留就非常重要了。如果你忘記了銷毀程序不再需要的shared_ptr,程序仍會正確執行,但會浪費內存。

share_ptr在無用之后仍然保留的一種可能情況是,你將shared_ptr存放在一個容器中,隨后重排了容器,從而不再需要某些元素。在這種情況下,你應該確保用erase刪除那些不再需要的shared_ptr元素。


通過new用普通指針初始化shared_ptr

可以使用new返回的指針來初始化智能指針。接受指針參數的智能指針構造函數是explicit的。因此,我們不能進行內置指針到智能指針間的隱式轉換,必須使用直接初始化形式來初始化一個智能指針:
在這里插入圖片描述
p1的初始化隱式地要求編譯器將一個new返回的int*隱式轉換成一個shared_ptr,這是不被允許的。

同樣的,一個返回shared_ptr的函數不能在其返回語句中隱式轉換一個普通指針:
在這里插入圖片描述
必須將shared_ptr顯式綁定到一個想要返回的指針上:
在這里插入圖片描述



unique_ptr

概念、初始化、特性

某個時刻只能有一個unique_ptr指向一個給定對象。unique_ptr被銷毀時,它所指向的對象也被銷毀。

unique_ptr沒有類似make_shared的標準庫函數。定義一個unique_ptr時,需要將其綁定到一個new返回的指針上,且必須采用直接初始化形式:

unique_ptr<double> pb;
unique_ptr<int> pi(new int(2));

根據“獨占”的特性,unique_ptr不支持普通的拷貝或賦值操作:
在這里插入圖片描述

不能拷貝unique_ptr的規則有個例外:可以拷貝或賦值一個將要被銷毀的unique_ptr。

常見的例子是從函數返回一個unique_ptr:
在這里插入圖片描述

或者返回一個局部對象的拷貝:
在這里插入圖片描述


支持的操作

在這里插入圖片描述
可以通過release或reset起到類似拷貝或賦值的作用:
在這里插入圖片描述

release會切斷unique_ptr和它原來管理的對象間的聯系,返回的指針常被用來初始化另一個智能指針或給另一個智能指針賦值。但是,如果不用另一個智能指針來保存release返回的指針,就要記得手動釋放資源:
在這里插入圖片描述



weak_ptr

概念

weak_ptr是一種不控制所指向對象生存期的智能指針,它指向一個由shared_ptr管理的對象。

具有以下特點:

  • 將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數
  • 引用計數歸零時,即使仍有weak_ptr指向對象,對象還是會被釋放

由于對象可能不存在,我們不能使用weak_ptr直接訪問對象,必須調用lock。因此可以這樣使用:
在這里插入圖片描述
在這里插入圖片描述



關于普通指針和智能指針

不能使用內置指針來訪問shared_ptr所指向的內存

當將一個shared_ptr綁定到一個普通指針時,我們就將內存的管理責任交給了這個shared_ptr,不應該再使用內置指針來訪問shared_ptr所指向的內存了。

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

舉例:
在這里插入圖片描述
對于上面的函數,以智能指針作為參數以傳值方式傳遞是安全的,當process結束時,ptr的引用計數為1,因此雖然局部ptr被銷毀,但是ptr指向的內存不會被釋放:
在這里插入圖片描述
但同時也可以傳遞給process一個用內置指針顯式構造的臨時shared_ptr。但是這樣做的風險是很大的:
在這里插入圖片描述
process(x);結束時,臨時對象被銷毀,其引用計數為0,指向的內存會被釋放,此時x變成了空懸指針。


get()函數

get函數返回一個內置指針,指向智能指針管理的對象。

函數是為了這種情況設計的:我們需要向不能使用智能指針的代碼傳遞一個內置指針。

使用get返回的指針的代碼不能delete此指針。

雖然編譯器不會給出錯誤信息,但是將另一個智能指針也綁定到get返回的指針上是錯誤的:
在這里插入圖片描述
上述代碼中,shared_ptr<int>(q);將另一個指針綁定到get返回的指針上,會導致程序塊結束時p指向的內存被釋放,p變成空懸指針。


reset函數

reset將一個新的指針賦予shared_ptr:

shared_ptr<int> p = new int(1024); // error:不能將一個普通指針賦予shared_ptr
p.reset(new int(1024)); // 正確:p指向一個新對象

與賦值類似,reset會更新引用計數,在需要的時候,可以釋放p指向的對象。

reset成員經常與unique一起使用,來控制多個shared_ptr共享的對象。在改變底層對象之前,我們檢查自己是否是當前對象僅有的用戶。如果不是,在改變之前要制作一份新的拷貝:

  if(!p.unique()){p.reset(new string(*p)); // 不是舊對象僅有的指針,分配新拷貝}*p += newVal; // 是舊對象僅有的指針,直接改變對象的值,因為不會再有別的指針訪問舊對象

處理異常

當處理異常時,經常會使程序塊過早結束,也就是如果使用普通指針管理內存,可能在遇到detele之前推出程序塊:

void f()
{int *ip = new int(2);// throw一個異常且在f中未被捕獲delete ip; //沒能正常退出因此無法調用本句釋放內存
}

如果ip是shared_ptr類型則不會出現內存泄漏的情況,在程序塊結束時,自動釋放內存。



動態數組

概念

動態數組并不是數組類型。

new和delete運算符一次分配/釋放一個對象,但某些應用需要一次為很多對象分配內存的功能。

  • 使用容器的類可以使用默認版本的拷貝、賦值和析構操作。
  • 分配動態數組的類必須定義自己版本的操作,在拷貝、復制以及銷毀對象時管理所關聯的內存。

new分配對象數組

兩種方法

方法一:在類型名之后跟一對方括號,在其中指明要分配的對象的數目,方括號中的大小必須是整形,但不必是常量。

例如:

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

方法二:也可以用一個表示數組類型的類型別名來分配一個數組,這樣,new表達式中就不需要方括號了:

typedef int arrT[10]; // arrT表示10個int的數組類型
int *p = new arrT; // 分配一個10個int的數組;p指向第一個int

但編譯器在執行這個表達式時還是會用new[]:

int *p = new int[42];

new的返回值

當用new分配一個數組時,我們并未得到一個數組類型對象,而是得到一個數組元素類型的指針。因此:

  • 不能對動態數組調用begin或end。這些函數使用數組維度來返回指向首元素和尾后元素的指針。
  • 不能用范圍for語句來處理動態數組中的元素

初始化

默認情況下,new分配的對象,不管是單個的還是數組中的,都是默認初始化的。也可以通過一對空括號進行值初始化:
在這里插入圖片描述

以及提供一個元素初始化器的花括號列表:
在這里插入圖片描述

  • 初始化器數目小于元素數目,剩余元素進行值初始化。
  • 初始化器數目大于元素數目,new表達式失敗,不會分配任何內存。

new表達式失敗時會拋出一個類型為bad_array_new_length的異常。類似bad_alloc,定義在頭文件new中。

值得一提的是: 雖然我們用空括號對數組中元素進行值初始化,但不能在括號中給出初始化器,這意味著不能用auto分配數組。 因為auto是編譯器根據初始化值來判斷類型的,使用auto就必須有初始值,沒有初始值(這里是初始化器)auto自然也就不可以用了。

動態分配一個空數組是合法的

雖然我們不能創建一個大小為0的靜態數組對象,但當n等于0時,調用new[n]是合法的:
在這里插入圖片描述
在這里插入圖片描述
當我們用new分配一個大小為0的數組時,new返回一個合法的非空指針。此指針保證與new返回的其他任何指針都不相同。對于零長度的數組來說,此指針就像尾后指針一樣,我們可以像使用尾后迭代器一樣使用這個指針。可以用此指針進行比較操作,但此指針不能解引用——畢竟它不指向任何元素。


釋放動態數組

為了釋放動態數組,可以使用一種特殊的delete——在職陣前加上一個方括號對。
在這里插入圖片描述
第二條語句銷毀pa指向的數組中的元素,并釋放對應的內存。數組中的元素按逆序銷毀,即,最后一個元素首先被銷毀,然后是倒數第二個,依此類推。

當我們釋放一個指向數組的指針時,空方括號對是必需的:它指示編譯器此指針指向一個對象數組的第一個元素。如果我們在delete一個指向數組的指針時忽略了方括號(或者在delete一個指向單一對象的指針時使用了方括號),其行為是未定義的。


動態數組和unique_ptr

在這里插入圖片描述

  • 當一個unique_ptr指向一個數組時,我們不能使用點和箭頭成員運算符。畢竟unique_ptr指向的是一個數組而不是單個對象,因此這些運算符是無意義的。
  • 我們可以使用下標運算符來訪問數組中的元素

實例:
在這里插入圖片描述
類型說明符中的方括號(<int[]>)指出up指向一個int數組而不是一個int。由于up指向一個數組,當up銷毀它管理的指針時,會自動使用delete[]。


動態數組和shared_ptr

shared_ptr不直接支持管理動態數組。如果希望用shared_ptr管理,必須提供自己的刪除器:
在這里插入圖片描述
shared_ptr不直接支持動態數組管理這一特性會影響我們如何訪問數組中的元素:
在這里插入圖片描述
shared_ptr未定義下標運算符,而且智能指針類型不支持指針算術運算。 因此,為了訪問數組中的元素,必須用get獲取一個內置指針,然后用它來訪問數組元素。



allocator類

new的局限性(使用allocator的原因)

  • new將內存分配和對象構造組合在了一起。
  • delete將對象析構和內存釋放組合在了一起

上述特性在靈活性上是有一定局限性的。這樣在分配單個對象時當然是好的,可以明確知道對象應該有什么值。但是分配大塊內存時,我們希望將內存分配和對象構造分離。這意味著我們可以先分配大塊內存,只有在真正需要時才執行對性的創建操作。

實例:
在這里插入圖片描述

有如下問題:

  • 我們可能不需要n個string,可能只用到了少量的string。因此我們可能創建了一些永遠也用不到的對象。
  • 對于確實需要使用的對象,每個都被賦值了兩次:第一次是在默認初始化時,第二次是在賦值時。
  • 沒有默認構造函數的類就不能動態分配數組了。

概念即創建銷毀操作

  • allocator定義在頭文件memory中。

在這里插入圖片描述

  • allocator分配的內存是未構造的。還未構造對象的情況下就是用原始內存是錯誤的。

construct

構造對象是通過construct完成的:

  • construct成員函數接受一個指針和零個或多個額外參數, 在給定位置構造一個元素。
  • 額外參數用來初始化構造的對象。類似make_shared的參數,這些額外參數必須是與構造的對象的類型相匹配的合法的初始化器:
allocator<string> alloc;
auto const p = alloc.allocate(20);
auto q = p; 
// q指向最后構造的元素之后的位置
// p指向分配的內存的首地址
alloc.construct(q++, 5, 'x'); // *p為xxxxx	
cout << *p << endl; // 正確:使用string的輸出運算符
cout << *q << endl; // 災難:q指向未構造的內存

為了理解上面的代碼,用下面的代碼查看一下構造對象前后p和q分別指向的地址:
在這里插入圖片描述
可以看到,p一直指向分配的內存的首地址,q指向最后構造的元素之后的地址,因此 *p 可以訪問已經構造的對象;而 *p 訪問的是未構造對象的原始內存,這種行為是錯誤的。


destroy

用完對象后,必須對每個構造的元素調用destory來銷毀它們。

destory接受一個指針,對指向的對象執行析構函數:

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

不妨來查看一下執行上述代碼之后的地址指向情況即內存分配的對象的值:
在這里插入圖片描述
可以看到,執行完while之后,q指向的地址已經和p一樣了,而再訪問p中的對象——執行*p也無法輸出”xxxxx“了。

但是在atom里面嘗試運行的時候發現和預期的不一樣。。。。destroy之后解引用p仍然能得到”xxxxx“:
在這里插入圖片描述
(吐槽:同一段代碼在不同的編譯器上得到的結果不同,猜測可能是底層的編譯環境不同導致的。 emmmmm……還是更傾向于相信vs的運行結果,如果有大佬看到這個問題知道原因的話,請不吝賜教,孩子實在不知道為什么會這樣。)

  1. 我們只能對真正構造了的元素進行destory操作。
  2. 一旦元素被銷毀后,可以重新使用這部分內存來保存其他的string,也可將內存歸還給系統。

deallocate

釋放內存通過deallocate來完成:

alloc.deallocate(p, 20);
  • 傳遞給deallocate的指針不能為空,必須指向由allocate分配的內存。
  • 傳遞給deallocate的大小參數必須與調用allocate分配內存時提供的大小參數具有一樣的值。

兩個伴隨算法

  • 用來初始化內存中創建的對象

在這里插入圖片描述
實例:
在這里插入圖片描述

  • uninitialized_copy返回遞增后的目的位置迭代器,指向最后一個構造元素之后的位置。


使用了動態生存期的資源的類

大多數類中,分配的資源都與對應對象生存期一致。 例如:每個vector(對象)“擁有”其自己的元素(分配的資源)。當我們拷貝一個vector時,原vector和副本vector中的元素是相互分離的。

某些類分配的資源具有與原對象相獨立的生存期(可能一個資源被兩個對象共同引用)。 換言之,如果兩個對象共享底層的數據,當某個對象被銷毀時,我們不能單方面地銷毀底層數據。

實例

構建一個類A,用share_ptr管理vector<string>

#pragma once
#include <vector>
#include <string>
#include <memory>using namespace std;class A{
public:A(): vs(make_shared<vector<string>>()){} // 分配一個空的vectorA(initializer_list<string> il): vs(make_shared<vector<string>>(il)){}// 接受一個初始化器的花括號列表,將il當作make_shared的參數初始化vs// 通過調用底層vector的成員函數來完成size、empty、push_back、pop_backvector<string>::size_type size() const{return vs->size();}bool empty() const{return vs->empty();}void push_back(const string& s){vs->push_back(s);}// pop_back、front、back操作需要先檢查操作對象是否為空void pop_back(){check(0, "pop_back on empty A");vs->pop_back();}string& front(){check(0, "front on empty A");return vs->front();}string& back(){check(0, "back on empty A");return vs->back();}// 針對const的A對象的front和back函數重載const string& front() const;const string& back() const;
private:shared_ptr<vector<string>> vs;// check函數提供判空功能,如果操作對象為空拋出一個異常void check(vector<string>::size_type si, const string &s) const{if(si >= vs->size()){throw out_of_range(s);}}
};const string& A::front() const{check(0, "front on empty A");return vs->front();
}
const string& A::back() const{check(0, "back on empty A");return vs->back();
}

用一個簡單的A的使用程序。測試類的正確性:

#include <iostream>using namespace std;#include "my_A.h"int main(int argc, char const *argv[]) {A a1;{A a2 = {"a", "an", "the"};a1 = a2;a2.push_back("about");cout << a2.size() << endl;}cout << a1.size() << endl;cout << a1.front() << " " << a1.back() << endl;const A a3 = a1;cout << a3.front() << " " << a3.back() << endl;return 0;
}

輸出結果:
在這里插入圖片描述



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/443807.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443807.shtml
英文地址,請注明出處:http://en.pswp.cn/news/443807.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

動態數組的簡單應用

文章目錄連接兩個字符串字面常量題目注意代碼輸出結果處理輸入的變長字符串題目注意代碼連接兩個字符串字面常量 題目 連接兩個字符串字面常量&#xff0c;將結果保存在一個動態分配的char數組中。重寫&#xff0c;連接兩個標準庫string對象。 注意 使用頭文件cstring的str…

二分查找算法實現

文章目錄思路代碼以二分區間作為while判定條件以給定值作為while判定條件主函數思路 while判定條件的選擇&#xff0c;注意最外層的return的內容就好。下面提供了兩個代碼版本。計算 mid 時需要防止溢出&#xff08;對應類型【如本例中的int】可能存不下&#xff09;&#xff…

Windows下Spring3.x計劃任務實現定時備份MySql數據庫

今天在空閑之余查了一下關于MySql數據庫備份的方案&#xff0c;最后結合自己的項目情況寫了一個關于Spring計劃任務的例子&#xff0c;目前我這個版本是在Windwos下測試成功&#xff0c;希望對大家有所幫助&#xff0c;不足之處還請大家多多包含&#xff0c;有什么建議盡管提出…

根據中序、前序遍歷重建二叉樹

文章目錄題目遞歸思路細節易錯代碼復雜度分析迭代思路細節易錯代碼復雜度分析題目 輸入某二叉樹的前序遍歷和中序遍歷的結果&#xff0c;請重建該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。 例如&#xff0c;給出 前序遍歷 preorder [3,9,20,15,7] 中…

深搜+剪枝

文章目錄題目思路注意代碼復雜度分析題目 給定一個 m x n 二維字符網格 board 和一個字符串單詞 word 。如果 word 存在于網格中&#xff0c;返回 true &#xff1b;否則&#xff0c;返回 false 。 單詞必須按照字母順序&#xff0c;通過相鄰的單元格內的字母構成&#xff0c…

搜索+回溯問題(DFS\BFS詳解)

文章目錄題目思路DFS思路代碼復雜度分析BFS思路代碼復雜度分析題目 地上有一個m行n列的方格&#xff0c;從坐標 [0,0] 到坐標 [m-1,n-1] 。一個機器人從坐標 [0, 0] 的格子開始移動&#xff0c;它每次可以向左、右、上、下移動一格&#xff08;不能移動到方格外&#xff09;&am…

剪繩子(動規、數論、貪心)

文章目錄題目數論思路代碼復雜度分析動規一思路代碼動規二思路代碼對最終結果取模1e97思路代碼題目 給你一根長度為 n 的繩子&#xff0c;請把繩子剪成整數長度的 m 段&#xff08;m、n都是整數&#xff0c;n>1并且m>1&#xff09;&#xff0c;每段繩子的長度記為 k[0],…

快速冪實現pow函數(從二分和二進制兩種角度理解快速冪)

文章目錄迭代實現快速冪思路int的取值范圍快速冪從二進制的角度來理解從二分法的角度來理解代碼復雜度分析進階——超級次方思路倒序快速冪正序快速冪代碼復雜度分析迭代實現快速冪 實現 pow(x, n) &#xff0c;即計算 x 的 n 次冪函數&#xff08;即&#xff0c;xn&#xff0…

備份MySQL數據庫的命令

備份MySQL數據庫的命令 mysqldump -hhostname -uusername -ppassword databasename > backupfile.sql 備份MySQL數據庫為帶刪除表的格式 備份MySQL數據庫為帶刪除表的格式&#xff0c;能夠讓該備份覆蓋已有數據庫而不需要手動刪除原有數據庫。 mysqldump -–add-drop-…

n位數的全排列(需要考慮大數的情況)

文章目錄題目思路代碼題目 輸入數字 n&#xff0c;按順序打印出從 1 到最大的 n 位十進制數。比如輸入 3&#xff0c;則打印出 1、2、3 一直到最大的 3 位數 999。 示例 1: 輸入: n 1 輸出: [1,2,3,4,5,6,7,8,9] 說明&#xff1a; 用返回一個整數列表來代替打印 n 為正整數 …

正則表達式匹配(動規)

文章目錄題目思路轉移方程特征再探 i 和 j代碼題目 請實現一個函數用來匹配包含 . 和 * 的正則表達式。模式中的字符 . 表示任意一個字符&#xff0c;而 * 表示它前面的字符可以出現任意次&#xff08;含0次&#xff09;。在本題中&#xff0c;匹配是指字符串的所有字符匹配整…

在循環遞增一次的數組中插入元素

文章目錄題目思路如何建立左右區間&#xff1f;如何查找最高點&#xff1f;那我們怎么判斷 num 到底處于什么樣的位置呢&#xff1f;如何確定插入位置&#xff1f;插入元素代碼題目 給一個只循環遞增一次的數組 res&#xff0c;res 滿足首元素大于等于尾元素&#xff0c;形如&…

Unable to find 'struts.multipart.saveDir' property setting.

Unable to find struts.multipart.saveDir property setting. 今天在做工作的時候遇到了這個問題&#xff0c;后來經過百度&#xff0c;終于知道了原因&#xff0c;現在記錄下來&#xff0c;以備以后查看。 1.struts.multipart.saveDir沒有配置 2.struts.multipart.saveDir用…

表示數值的字符串(有限狀態自動機與搜索)

文章目錄題目思路一代碼一思路二代碼二題目 思路一 考察有限狀態自動機&#xff08;參考jyd&#xff09;&#xff1a; 字符類型&#xff1a; 空格 「 」、數字「 0—9 」 、正負號 「 」 、小數點 「 . 」 、冪符號 「 eE 」 。 狀態定義&#xff1a; 按照字符串從左到右的…

樹的子結構

文章目錄題目深搜深搜代碼廣搜廣搜代碼題目 輸入兩棵二叉樹A和B&#xff0c;判斷B是不是A的子結構。(約定空樹不是任意一個樹的子結構) B是A的子結構&#xff0c; 即 A中有出現和B相同的結構和節點值。 例如: 給定的樹 A: 給定的樹 B&#xff1a; 返回 true&#xff0c;因為…

寫題過程中碰見的小問題

文章目錄和--vector二維vector的初始化數組中最大的數max_element()數組所有元素之和accumulate()vector數組去重對pair類型的vector排序對元素都為正整數的vector利用sort默認的升序排列進行降序排列一維數組轉二維數組size_t和int如何不用臨時變量交換兩個數?將類函數的形參…

LeetCode——二叉樹序列化與反序列化

文章目錄題目思路問題一問題二代碼實現題目 請實現兩個函數&#xff0c;分別用來序列化和反序列化二叉樹。 設計一個算法來實現二叉樹的序列化與反序列化。不限定序列 / 反序列化算法執行邏輯&#xff0c;你只需要保證一個二叉樹可以被序列化為一個字符串并且將這個字符串反序…

jsp中生成的驗證碼和存在session里面的驗證碼不一致的處理

今天在調試項目的時候發現&#xff0c;在提交表單的時候的驗證碼有問題&#xff0c;問題是這樣的&#xff1a;就是通過debug模式查看得知&#xff1a;jsp頁面生成的驗證碼和表單輸入的頁面輸入的一樣&#xff0c;但是到后臺執行的時候&#xff0c;你會發現他們是不一樣的&#…

求1~n這n個整數十進制表示中1出現的次數

文章目錄題目思路代碼復雜度分析題目 輸入一個整數 n &#xff0c;求1&#xff5e;n這n個整數的十進制表示中1出現的次數。 例如&#xff0c;輸入12&#xff0c;那么1&#xff5e;12這些整數中包含1 的數字有1、10、11和12。可得1一共出現了5次。 思路 將個位、十位……每位…

求數字序列中的第n位對應的數字

文章目錄題目思路代碼復雜度分析致謝題目 數字以0123456789101112131415…的格式序列化到一個字符序列中。在這個序列中&#xff0c;第5位&#xff08;從下標0開始計數&#xff09;是5&#xff0c;第13位是1&#xff0c;第19位是4&#xff0c;等等。 請寫一個函數&#xff0c…