C++學習筆記
C++ Primer Plus中文版第5版(632)
1.char 作為數值類型,則unsigned char表示的數據范圍為0~255 而signed char范圍為-128~127。
2.cin.get(name,size);是不接收換行符,把它留在了輸入流中。cin.get()只接收一個字符,包括換行符。(getline()用法類似)
當get()讀取空行後將設置失效位,接下來的輸入將被阻斷,可用cin.clear()來恢復輸入。
3.枚舉只定義了賦值操作符(在體外只能把enum值賦給enum變量),沒有定義算術運算。
4.靜態,有鏈接性:全局變量(分內外鏈接性,static為內部鏈接)。當file1.cpp中定義了全局變量 int tom;和static int m=0;而在file2.cpp中想用file1.cpp中的
tom的數據,同時想在file2.cpp中定義自己的m.file2.cpp中可以這樣聲明:extern int tom; int m;想要在當前文本中使用另一個
文本的同名變量數據,則必須編譯兩個文本產生後綴為 .o 的文件和在當前文本中用extern聲明想用的變量(一般是不可以初始化,但extern cost 聲名的變量可以初始化),這樣才能成功鏈接使用。
5.用mutable聲明的變量m,即使是const變量也可以修改該變量,如:結構體中定義了nutable變量,再定義一個const的結構體變量node,則node.m是可以被重新修改的。
6.外部定義靜態屬性的const或static變量的鏈接性為 內部的(意為在所有鏈接起的文件都可以使用相同的外部聲明,屬文件私有)。如果希望內部鏈接性為外部的,則
可以使用extern關鍵字來覆蓋默認的內部鏈接性。
7.函數也有鏈接性,默認性況下函數的鏈接性為外部的。可以用關鍵字static將函數的鏈接性設置為內部的(僅在本文件中可見),使之只能在一個文件中使用。必須同時在原型聲明和函數定義中使用該關鍵字。
8.C++有一個“單定義規則”,即對於每個非內聯函數,程序中只能包含一個定義。對於鏈接性為外部的函數來說,這意味著在多文件程序中,只能有一個文件
包含該函數的定義,但使用該函的每個文件都應包含其函數原型。 內聯函數不受這項規則的約束,這允許程序員能夠將內聯函數的定義放在頭文件中。
這樣,包含了頭文件的每個文件都有內聯函數的定義。不過,C++要求同一個函數的所有內聯定義都必須相同。
9.在C++程序的某個文件中調用一個函數,如果該文件中的函數原型指出該函數是靜態的,則只在該文件中查找函數定義。若非靜態的,將在所有的程序文
件中查找,如果找到有兩個定義,編譯器將發出錯誤消息;如在程序文件中沒找到,則將在庫中搜索。
10.布局new 的一種使用:將己分配的內存再分給其他變量。
#include
//使的頭文件
struct node{int a;char b[10];};
node *Node;
int *p;
char buffer[100];
Node=new (buffer)node[2];//將buffer中分配靜態空間給結構node
p=new (buffer)int[20];//將buffer中分配靜態空間給一個包含20個元素的int數組;
/*上述兩個new操作,(1)分配buffer的內存都是從相同的起始地址開始分配,要想Node與p不操作同一內存,
可以這樣:p=new (buffer+2*sizeof(node))int[20]; (2)buffer是靜態內存,所以不能用delete釋放p和Node,
要想釋放,則buffer必須為new一般分配的動態內存。*/
11.名稱空間可以是全局的,也可以位於另一個名稱空間中,但不能位於代碼塊中。所以默認情況下,在名稱空間中聲明的名稱的鏈接性為外部的(除非它引用了常量)。
12.名稱空間定義完後,可以再次添加變量名或定義,如想在己存在的名稱空間jack中再添加變量age:namespace jack{int age;}
(注:先用編譯指令using namespace jack;後向名稱空間jack添加age,那麼age也可以直接使用age變量,而不需聲明。)
當要用名稱空間jack中的age時,可以jack::age=19,或using jack::age; age=19(將age變量添加到它所屬聲明區域中)。
當用using編譯指令:using namespace 名稱空間名。這一次性聲明,但有一缺點:一個程序中使用多個名稱空間,這些名稱空間中
有相同的變量名,那麼會隱藏之前出現相同的名稱。可通過作用域分明。 用using聲明導入變量時,只能存在一個同名變量。
13.(1)名稱空間聲明進行嵌套:一個名稱空間聲明element中可以包含另一個名稱空間的聲明fire。
namespace elements
{ namespace fire
{ int flame;....}
float water;
}
using編譯指令使用內部的名稱用:suing namespace elements::fire;
(2)名稱空間中使用using 編譯指令和suing 聲明,如下所示:
namespace myth
{
using jake::age;
using namespace elements;//using 編譯指令,myth中就有了elements中的名稱
using std::cout; using std::cin;
}
假設要訪問jake::age。由於jake::age現在位於名稱空間myth中,因此可以這樣訪問:myth::age=20則jake::age也是等於20
(3)using 編譯指令具有傳遞性,如:using namespace myth;後using namespace elements也編譯了。
(4)可以給名稱空間創建別名:namespace myth_other_name=myth;
(5)未命名的名稱空間只能在所屬文件中使用,潛在作用域為:從聲明點到該聲明區域末尾(空間中的名稱相當內部鏈接性變量)。
因沒有名稱,所以不能顯示地using 編譯指令和suing聲明。
14.解釋為什新頭文件iostream要與名稱空間std同時使用:因為新頭文件iostream中使用了std名稱空間定義,#include只是將std名稱空間導入到當前文件中。
而舊頭文件iostream.h中沒有使用名稱空間定義。
15.類聲明中默認訪問控制權限為private. 類的構造函數中的參數名不能與成員變量名相同,為避免這種混亂,通常在成員變名中使用m_前綴。
16.為防頭文件被多次包含,頭文件中可以使用:#ifndef MYFILE_H_
#define MYFILE_H_
..........
#endif
17.(1)創建類對象另一種方法(初始化):
Student s=Student("name",20);這樣調用構造函數可能創建一個臨時對象(也可能不會),然後將該臨時對象復到s對象中,後該臨時對象
調用析構函數,釋放臨時內存。(可能立刻冊除,也可能會等一段時間)。
(2)賦值:s=Student("name",20); 賦值語句中使用構造函數總會導致在賦值前創建一個臨時對象。
(3)注意:如果既可通過初始化,也可通過賦值來設置對象的值,應釆用初始化方式。通常這種方式的效率更高。
18.類的const對象:調用函數時只能調用常成員函數,不能調用非 常成員函數。(原因:常對象不能被修改,而非常成員函數可能會修改對象的值,所以不能被調用)
19.接受一個參數的構造函數允許使用賦值句法來將對象初始化一個值:Classname object=value; <==> Classname object(value);
20.用new創建的對象,必須用delete來析構,這樣才會調用類中的析構函數(不是用new創建的對像自動調用析構函數)。一個類只能有一個析構函數,不帶任何參數。
21.警告:要創建類對象數組,則這個類必須有默認構造函數,如果沒有則會出錯。
22.在類中定義一個常量的三種方法:(1)在類中聲明枚舉enum (2)使用static定義常量(變量的值不能變須再加一個const修飾:static const int CONST;),類外初始化:static不必寫出。
(3)用非靜態的const聲明成員變,但這樣必須包含構造函數(要調用默認的則必須有默認構造函數,其他要調用的構造函數同理),
且每個構造函數都必須初始化用const聲明的成員變量,而且必須是這樣的形式:Classname(形參表):CONST(初始化值){....}
23.隨機函數:按一種算法用於一個初始種子值業獲得隨機數。
(1)包含在頭文件cstdlib(以前為stdlib.h) (2)函數srand(seed)是設種子用的;函數rand()取隨機數。如果自己每次取數不設種子,那麼每取出的數將作為下次取數的種子。
seed可以設置成time(0),這樣種子seed就可以隨時不同,那麼取隨機數也就不同,time()函數包含在頭文件time.h中。
24.關鍵字explicit,用來關閉對象自動轉換特性。當類中有一個參數的構造函數時:explicit Students(string name){.....}//這樣在創建對Students對象s後,不能:s=name;
但如果沒有explicit關鍵字時,可行:s=name。強制轉換是可以的,如:s=(Students)name;一般而言:只有一個這樣的成員變量才定義這樣的構造函數。
25.創建轉換函數:例如:可將Students類對象轉成string類對象的Students類成員函數:operator string(){... }//假如將double類型轉成int型通過這類型的方法,是會四捨五入取值
要求:(1)轉換函數必須是類方法 (2)轉換函數不能指定返回類型 (3)轉換函數不能有參數
警告:應小心使用隱式轉換函數。通常,最好先擇僅在被顯式地調用時才會執行的函數。
26.注意:如果一個類中重載了+運算符,第二個是double類型,並在類中又定義了operator double()成員函數,將造成混亂,因為在該類對象加上double類型值時,就有兩種
解釋,一種是調用重載+運算符執行該類加法,而另一種是將該類對象轉換成double類型,再執行double加法。
27.在執行main()之前調用其他函數做法:因為全局變量是在編譯時就創建了,所以可以定義一個全局的類對象,並在類的構造函數中調用一些想在main()之前調用的函數。
28.如果類中有引用變量,則必須包含可調用的構造函數,必須初始化引用成員變量(可對自身進行引用),而且初始化時必須在構造函數打冒號後初始化:Students(int ok):yingyou(ok){...}//yingyou為類的引用成員變量。
29.除虛基類外,類在初始化時,只能將值傳遞給直接基類。
30.記住:如果要在派生類中重新定義基類的方法,通常應將基類方法聲明為虛擬的,這樣,程序將根據所指的對象類型而不是引用或指針的類型來選擇方法版本 為基類聲明一個虛擬析構函數也是一種慣例。
31.用類名調用類的成員情況有二:(1)不須要在派生類中就可以用類名直接調用靜態成員變量 (2)要用類名調用成員函數,只有在派生類成員函數中,父類可以用自己的類名調用自身的成員函數。
32.為何需要 虛析構函數:當一個基類對象指針指向了子類對象,而這時子類對象新增的成員變量是用new分配的內存,但又想通過析構基類對象同時來析構子類新增的成員變量,那麼基類的虛擬類型將發
揮作用,動態聯編性,調用析構函數會從該基類指針對象所指的子類析構函數開始調用直至該基類,這與一般的虛函數用法一樣。如果不是虛析構函數,那麼只調用該基類的析構函數。
33.虛函數的工作原理:通常,編譯器處理虛函數的方法是:給每個類對象添加保存了一個隱藏成員表,即指向函數地址的數組指針。這種數組稱為虛函數表,它存儲了類對象進行聲明的虛函數的地址。
每個類對象都有自己的虛函數表,如果在派生類中有新定義的虛函數,則在創建該對象時也將添加到虛函數表中。
調用虛函數時:程序將查看存儲在對象中的vtbl地址,然後轉向相應的函數地址表。如果使用類聲明中定義的第三個虛函數,則程序將使用該數組中的第三個函數地址,並執行該地址的函數。
總之,使用虛函數時,在內存和執行速度方面有一定的成本,包括:
(1)每個對象都將增大,增大量為存儲地址的空間。(2)對每個類,編譯器都創建一個虛函數地址表(數組)。(3)每次虛函數調用都需要執行一步額外的操作,查地址,有時間開銷。
比較:雖然非虛函數的效率比虛函數稍高,但不具備動態聯編功能。
34.注意事項:(1)基類方法中使用關鍵字virtual 可使該方法在基類以及所有派生類中是虛擬的。
(2)如果使用指向對象的引用或指針來調用虛方法,具有動態聯編性,而不是對象的引用或指針來調用虛方法,則沒有動態聯編性。
(3)如果定義的類將被用作基類,則應將那些要在派生類中重新定義的類方法聲明為虛函數。
35.更多注意:(4)構造函數不能是虛函數 (5)析構函數應當是虛函數,除非類不用做基類。(6)友元函數不能是虛函數,因為友元函數不是類成員
( 7 )經驗規則:第一,如果重新定義繼承的方法,應確保與原來的原型完全相同,但如果返回類型是基類引用或指針,則可以修改為指向派生
類的引用或指針,這種特性被稱為返加回型協變,因為允許返回類型隨類 類型的變化而變化。
第二,如果基類聲明的函數被重載了,則應在派生類中重新定義。如不重載,則派生類對象只能調用最近一次被重載的函數。
36.(1)需要實例化的類,則類中的虛函數必須有定義。而如果該類不實例化,僅僅聲明虛函數的類且虛函數沒有實現,該類編譯是可以通過的!
(2)純虛函數出現在接口類中,並賦值為0,不要為該函數分配函數地址,從而阻止類的實例化!純虛函數是沒有定義的,如果實現了也不是純虛函數啦!
(3)一般的成員函數可以只有聲明,前提是在應用中不能調用該函數,否則會因找不到定義產生連接錯誤!
37.實例化類的虛函數必須有定義,原因如下:有虛函數作為成員函數的類, 它的實例化---對象, 在運行過程分配到的內存不止是它的成員數據, 還有一個指向該類虛函數表(vtable)的指針, 虛函
數表中的每個數據項都是一個虛函數的入口地址; 如果一個對象的虛函數只有聲明而沒有實現, 就會導致這個虛函數表找不到本應作為其數據項之一的虛函數的入口地址, 虛函數表在運行前不能裝載完成, 所以產生連接錯誤!
38.派生類使用基類的友元函數方法:
例如:基類和派生類都重載了輸出運算符,現在使用派生類的對象調用基類的友元輸出運算符:
基類友元函數:friend std::ostream & operator<<(std::ostream &os,const BaseClass bc)
{ os<
子類友元函數調用基類友元函數:friend std::ostream &operator<<(std::ostream &os,const ChildClass cc)
{ os<<(const BaseClass &)cc; os<<新增成員變量; return os;}
39.復制構造函數:類中定義了構造函數,這個構造函數中只有一個參數。而只有在創建對象時才能用=來復值指定的成員變量。
如:BaseClass(Class &k){kk=k;} Class k; BaseClass aa=k;//隱式轉換
但如果在構造函數前加關鍵字:explicit,則不能進行隱式轉換,可以顯示轉換。
40.再次強調:(1)在編寫函數的參數時,應按引用而不是按值來傳遞對象的方式,這樣做可以提高效率。因為按值傳遞時需要調用復制構造函數,然後調用析構函數,
這些調用需要時間。如不修改對象,應將參數聲明為const引用。
(2)函數返回對象和返回引用對象:相同點:都生成臨時拷貝。 不同:返回引用與按引用傳遞對象相似,在調用和被調用函數對同一個對象進行操作。
注意:函數不能返回在函數中創建的臨時對象的引用,因為當函數結束時,臨時對象將消失,因此這種引用將是非法的。
41.const函數(將const放在函數形參表後面),只是不能修改調用該函數的對象(*this). 當函數引用(或指針)返回對象時,函數返回類型必須兼容對象類型。如果對象是const類型,則函數反回類型也必須是const.
42.編譯器生成的成員函數:默認構造函數,復制構造函數,賦值操作符。
43.公有繼承的考慮因素:(1)所屬關系 (2)構造函數,析構函數,賦值操作符函數與友元函數是不能被繼承。
44.警告:如果類有間接虛基類,則除非只需使用該虛基類的默認構造函數,否則必須顯式地調用該虛基類的某個構造函數。
45.調用構造函數的順序:(1)一個派生類沒有直接虛基類:A:先調用基類的構造函數再子類;B:調用基類時,按照派生類聲明繼承基類的順序調用基類的構造函數。
(2)一個派生類有直接虛基類:A:先調用基類的構造函數再子類;B:調用基類時:b1.先調用虛基類後非虛基類,b2.再按聲明的順序被調用。
46.模板:部分具體化:即部分限制模板的通用性。例如:template class Pair {......}; template class Pair{......};
47.template
class beta{
public:
template
U blab(U u,T t);
};
template
template
U beta::blab(U u,T t){ return u+t;}
48.templateclass Thing, typename U, typename V>
class Crab
{ private: ThingS1; ThingS2; .......};
Thing是模板類類型,如果實例化時:Crabnebula; //Stack是模板類,Thing將被替換成Stack ,裡面的成員變量變成StackS1, StackS2 ;
49.模板類的友元函數有三類:A.非模板友元函數(即友元函數是不帶參數或帶參的類型都是要具體化的)
B.約束(bound)模板友元,即友元的類型取決於類被實例化時的類型。
C.非約束(undound)模板友元,即友元的所有具體化都是類的每一個具體化的友元。
50. A類:非模板友元函數:template
class HasFriend
{ friend void report(HasFriend &); };
在定義友元函數時,必須根據模板類實例化對象時傳送的模板類參數類型。具體化定義,如:
要定義一個對象HasFriendobject;則友元函數這樣定義:void report(HasFriend &f){....}
B類:修改前一個上面的范例,使友元函數本身成為模板。使類的每一個具體化都獲得與友元匹配的具體化。包含以下三步:
1.首先,在類定義的前面聲明每個模板函數: template void counts(); template void report(T &);
2.然後在函數中再次將模板聲明為友元。這些語句根據類模板參數的類型聲明具體化:
template
class HasFriend
{ friend void counts();
friend void report<>(HasFriend &);
... };
上述聲明中的<>指出這是模板具體化。對於report(),<>可以為空,這是因為可以從函數參數推斷出模板類型參數(HasFriend)。不過,
也可以使用report >(HasFriend &)。但counts()函數沒有參數,因此必須使用模板參數句法()來指明其具體化。
還需要注意的是,TT是HasFriend類的參數類型。
3.程序必須為友元提供模板定義,例如:
template
void counts() { cout << HasFriend::x << endl;}
template
void report(T & hf) {cout << hf.x << endl;}
調用時,counts()需要提供具體化參數類型。report()只需直接調用,例如:
counts(); //調用HasFriend的友元函數
HasFriend hfdb;
report(hfdb); //調用HasFriend的友元函數
C類: 模板類的非約束模板友元函數
通過在類內部聲明友元函數模板,可以創建非約束友元函數,即每個函數具體化都是每個類具體化的友元:
template
class ManyFriend
{...
template friend void show(C &,D &);
};
在類外定義該友元:
template
void show(C & c,D & d){ ... }
假如創建ManyFriend類對象(hfi1)和ManyFriend類對象(hfi2),傳遞給show(),那麼編譯器將生成如下具體化定義:
void show &,ManyFriend &>
(ManyFriend & c,ManyFriend & d){ ... }
51.嵌套類:被包含在一個類中,訪問權限與一般語法一樣,嵌套類可以訪問包含它的類的成員。
52.終止程序的函數:std::abort()[在頭文件 cstdlib 中],exit(0)。
53.異常極其重要的一點:程序進行堆棧解退以回到能夠捕獲異常的地方時,將釋放堆棧中的自動存儲變量。如果變量是類對象,將為該對象調用析構函數。
54.捕捉異常雖然用引用對象,但還是會創建拷貝,這是件好事,因為拋出異常的函數執行完後,就會釋放拋出的異常對象。
那其中的好處就是:基類引用可以執行派生類對象。與派生類型兼容。(所以通過排列catch塊的順序,讓處理異常更有針對性。)
55.exception類用作其他異常類的基類:
1>logic_error類(繼承自exception類) :通過合理的編程可以避免邏輯錯誤。在頭文件中定義,邏輯異常表明存在可以通過編程修復的問題。下面都是它的派生類:
domain_error:定義域或值域錯誤
invalid_error:傳遞的值不符合要求
length_error:沒有足夠的空間執行所需操作,例如string類的append()方法
out_of_bounds :指示索引錯誤,例如數組下標索引
2>runtime_error類(繼承自exception類):在運行期間發生但難以預計和防范的錯誤。在頭文件中定義,運行時異常表明無法避免的問題,下面派生的類:
range_error:計算的結果不在函數允許的范圍內,但沒有上溢,也沒有下溢
overflow_error:上溢錯誤
underflow_error:下溢錯誤
3>bad_alloc類(繼承自exception類):在頭文件中。當new出現內存分配問題時,C++提供了兩種方式處理:
i>返回一個空指針
ii>引發bad_alloc異常
56.異常何時會迷失方向:
1>意外異常:異常,如果是在帶異常規范的函數中引發的,則必須與規范列表裡的某個異常匹配,若沒有匹配的,則為意外異常,默認情況下,會導致程序異常終止
2>未捕獲異常:異常如果不是在函數中引發的(或者函數沒有異常規范),則它必須被捕獲。如果沒被捕獲(沒有try塊或沒有匹配的的catch塊),則為未捕獲異常。默認情況下,將導致程序異常終止
3>可以修改這種默認設置(存在意外異常或未捕獲異常)
3.1>未捕獲異常:此異常不會導致程序立刻終止(terminate()、set_terminate()都在exception頭文件中聲明)
程序首先調用函數terminate()---->默認情況下,terminate()調用abort()函數,
可以通過set_terminate()函數指定terminate()調用的函數,修改這種行為
若set_terminate()函數調用多次,則terminate()函數將調用最後一次set_terminate()設置的函數
例如:
set_terminate(MyQuit);
void MyQuit(){...}
此時若出現未捕獲異常,程序將調用terminate(),而terminate()將調用MyQuit()
3.2>意外異常:
發生意外異常時,程序將調用unexcepted()函數--->unexpected()將調用terminate()函數--->terminate()在默認情況下調用abort()函數
可以通過set_unexcepted()函數,修改這種默認行為,但unexpected()函數受限更多
i>通過terminate()、abort()、exit()終止程序
ii>引發異常
#引發的異常與原來的異常規范匹配,則可以用預期的異常取代意外異常
#引發的異常與原來的異常不匹配,且異常規范中沒有包括bad_exception類型(繼承自exception類),則程序將調用terminate()
#引發的異常與原來的異常不匹配,且原來的異常規范中包含了bad_exception類型,則不匹配的異常將被bad_exception異常所取代
如果要捕獲所有異常,步驟如下:
#include
using namespace std;
void myUnexception()
{
throw bad_exception();
}
set_unexcepted(myUnexception)
...
function()throw(...,bad_exception);
try{}
catch(bad_exception &be){}
57.動態 判斷對象是什麼類型 的方法,兩個重要的 RTTI 運算符的使用方法,它們是 typeid 和 dynamic_cast。
大多都主張在設計和開發中明智地使用虛擬成員函數,而不用 RTTI 機制。但是,在很多情況下,虛擬函數無法克服本身的局限。每每涉及到處理異類容器和根基類層次(如 MFC)時, 不可避免要對對象類型進行動態判斷,也就是動態類型的偵測。如何確定對象的動態類型呢?答案是使用內建的 RTTI 中的運算符:typeid 和 dynamic_cast。
1> 利用 運算符 typeid 可以獲取與某個對象關聯的運行時類型信息。typeid 有一個參數,傳遞對象或類型名。因此,為了確定 x 的動態類型是不是Y,可以用表達式:
typeid(x) == typeid(Y)實現:#include // typeid 需要的頭文件 例如:typeid(*pfile)==typeid(MediaFile)
存在的問題:如果指針 pfile是MediaFile 的派生類,則判斷為false,類型兼容判斷那麼這類問題就要用到dynamic_cast
2> dynamic_cast用它來確定某個對象是 MediaFile 對象還是它的派生類對象。dynamic_cast 常用於從多態編程基類指針向派生類指針的向下類型轉換。它有兩個參數:一個是類型名
;另一個是多態對象的指針或引用。其功能是在運行時將對象強制轉換為目標類型並返回布爾型結果。也就是說,如果該函數成功地並且是動態的將 *pfile 強制轉換為 MediaFile
,那麼 pfile的動態類型是 MediaFile 或者是它的派生類。否則,pfile 則為其它的類型:
void menu::build(const File * pfile)
{
if (dynamic_cast (pfile))
{
// pfile 是 MediaFile 或者是MediaFile的派生類 LocalizedMedia
add_option("play");
}
else if (dynamic_cast (pfile))
{
// pfile 是 TextFile 是TextFile的派生類
add_option("edit");
}
}
存在的問題:與 typeid 相比,dynamic_cast 用時多些,為了確定是否能完成強制類型轉換,dynamic_cast必須在運行時進行一些轉換細節操作。因此在使用 dynamic_cast 操作時,
應該權衡對性能的影響。指針強制轉化失敗後可以比較指針是否為零,而引用卻沒辦法,所以引用制轉化失敗後拋出異常
58.C++的auto_ptr所做的事情,就是動態分配對象以及當對象不再需要時自動執行清理。auto_ptr在構造時獲取對某個對象的所有權(ownership),在析構時釋放該對象。我們可以這樣使用 auto_ptr來提高代碼安全性:int* p = new int(0); auto_ptr ap(p); 從此我們不必關心應該何時釋放p, 也不用擔心發生異常會有內存洩漏。
這裡我們有幾點要注意:
1) 因為auto_ptr析構的時候肯定會刪除他所擁有的那個對象,所有我們就要注意了,一個蘿卜一個坑,兩個auto_ptr不能同時擁有同一個對象。像這樣:
int* p = new int(0);
auto_ptr ap1(p);
auto_ptr ap2(p);
因為ap1與ap2都認為指針p是歸它管的,在析構時都試圖刪除p, 兩次刪除同一個對象的行為在C++標准中是未定義的。所以我們必須防止這樣使用auto_ptr.
2) 考慮下面這種用法:
int* pa = new int[10];
auto_ptr ap(pa);
因為auto_ptr的析構函數中刪除指針用的是delete,而不是delete [],所以我們不應該用auto_ptr來管理一個數組指針。
3) 構造函數的explicit關鍵詞有效阻止從一個“裸”指針隱式轉換成auto_ptr類型。
4) 因為C++保證刪除一個空指針是安全的, 所以我們沒有必要把析構函數寫成:
~auto_ptr() throw()
{
if(ap) delete ap;
}