C++ Primer筆記
ch2 變量和基本類型
聲明
extern int i;
extern int i = 3.14;//定義
左值引用(綁定零一變量初始值,別名)
不能定義引用的引用;引用必須被初始化;類型嚴格匹配;不能綁定字面值/計算結果;無法二次綁定
int i=4;
int &r=i;
指針
本身是對象,允許賦值和拷貝;無需定義時賦初值;類型嚴格匹配
int *ip1, *ip2;
int ival = 42;
int *p = &ival;
*p = 0;
cout << *p;
int *p2 = p;
int &r2 = *p;
指針值:1.指向一個對象;2.指向緊令對象的下一個位置;3.nullptr;4.無效指針
不能把int變量直接賦值給指針
有時候無法清楚賦值語句改變了指針的值還是指針所指對象的值:賦值永遠改變等號左邊的對象
指針比較:(==)相等時意味着地址值相等(都為空/指向同一對象/指向同一對象的下一地址)
**表示指向指針的指針
int ival = 1024;
int *pi = &ival;
int **ppi = π//ppi指向pi的地址
int *pi2 = pi; //pi2和pi指向同一對象
指向指針的引用
int i = 42;
int *p;
int *&r = p;//r是p的引用
r = &i;//r引用了p,此處讓p指向i
*r = 0;
理解r的類型是什么,最簡單的是從右向左讀r的定義,&r的&表明r是一個引用,*說明r引用的是一個指針,int指出r引用的是一個int指針
const
必須初始化;
默認狀態下,const對象僅在文件內有效,共享必須添加extern
//文件間共享const對象
extern const int bufSize = fcn();//定義初始化常量,且能被其他文件訪問
extern const int bufSize;
const引用
初始化常量引用時允許用任意表達式
int i = 42;
const int &r1=i;
const int &r2=42;
const int &r3=r1*2;
double dval = 3.14;
const int &ri = dval;//浮點數生成臨時常量再綁定
對const的引用可能會引用一個並非const的對象
指向常量的指針:不能通過該指針改變對象的值。
double dval = 3.14;
const double *cptr = &dval;
const指針(必須初始化,值不能改變)
//從右向左確定含義
int errNumb = 0;
int *const curErr = &errNumb;//curErr一直指向errNumb
const double pi = 3.14;
const double *const pip = π//pip是一個指向常量對象的常量指針
- 頂層const:指針本身是常量
- 底層const:指針所指對象是常量
int i = 0;
int *const pi = &i;//頂層const
const int ci = 42;//頂層const
const int *p2 = &ci;//底層const
const int *const p3 = p2;//靠右是頂層,靠左是底層
const int &r = ci;//用於聲明的都是底層const
//拷貝時,頂層const不受影響;底層const必須相同
i = ci;
p2 = p3;
int *p = p3;//wrong,p3有底層,p沒有
p2 = p3;//都是底層
p2 = &i;//int*能轉換為const int*
int &r = ci;//wrong,int&不能綁定到int常量上
const int &r2 = i;//const int&可以綁定到int
常量表達式:constexpr
ch3 字符串、向量和數組
頭文件不應包含using聲明
string
-
定義和初始化
string s1 = "hiya";//默認初始化使用= string s2("hiya");//直接初始化 string s3(10,'c');//直接初始化
-
getline(cin, line),使用endl結束當前行並刷新緩沖區
-
empty和size操作
size返回值:string::size_type。返回的是一個無符號整數,不要使用int了 -
比較(長度/大小寫敏感)
-
賦值/對象相加/字面值與string相加(保證每個加法運算對象之一是string)
-
處理字符:range for語句
decltype(s.size()) punct_cnt = 0;//使用s.size()返回值類型聲明punct_cnt for(auto &c : s){...}//加引用改變字符
-
下標(隨機訪問)
vector(容器/類模板)
-
定義和初始化
vector<T> v1//默認初始化 vector<T> v2(v1); vector<T> v2 = v1; vector<T> v4(n);//值初始化 vector<T> v5{a,b,c,...};//列表初始化 vector<T> v6={a,b,c,...};
-
操作
v.push_back()//添加 v.empty() v.size()//vector<int>::size_type v1 = v2;//拷貝 v1 = {a,b,c}//列表拷貝 v1 == v2;//數量相同且元素值相同
不能用下標添加元素
迭代器
*iter//返回iter所指元素的引用
iter->data//解引用獲取data的成員,等價於(*iter).data
++iter
--iter
iter1 == iter2
auto e = v.end();//因為end返回的迭代器不指示某個元素,不能對其遞增或解引用的操作
迭代器運算
數組
//初始化
const unsigned sz = 3;
int a[] = {0,1,2};
int a[5] = {0,1,2};
string a[] = {"hi","bye"}
int a2[]=a;//wrong,不允許拷貝賦值
a2 = a;
int *ptrs[10];//ptrs含有10個整形指針
int &refs[10];//wrong
int (*Parray)[10] = &arr;//Parray指向一個含有10個整數的數組
int (&arrRef)[10] = arr;arrRef引用一個含喲10個整數的數組
//數組的名稱由內向外讀
int * (&array)[10] = ptrs://array是數組的引用,該數組包含10個指針
訪問數組元素(size_t,無符號)
指針和數組
string nums[] = {"one", "two", "three"};
string *p = nums;//等價於p2 = &nums[0]
auto ia(nums);//ia是一個string指針,指向nums的第一個元素
指針也是迭代器
int ia[] = {0,1,2};
int *beg = begin(ia);
int *last = end(ia);
尾后指針不能解引用和遞增操作
C風格字符串
strlen(p)
strcmp(p1, p2)
strcat(p1, p2)//p2附加到p1之后
strcpy(p1, p2)//p2拷貝給p1
混用string和C風格字符串
char *str = s;//wrong
const char *str = s.c_str();
使用數組初始化vector對象
int int_arr[] = {0,1,2,3,4,5};
vector<int> ivec(begin(int_arr), end(int_arr));
vector<int> subVec(int_arr+1, int_arr+4);
//盡量使用vector和迭代器;盡量使用string
多維數組
int ia[2][3] = {
{1,2,3},
{2,3,4}
};
//下標引用
ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1];//row綁定到ia的第二個4元素數組上
size_t cnt = 0;
for(auto &row : ia)//不加引用則無法編譯
for(auto &col : row){
col = cnt++;
}
//指針和多維數組
int ia[3][4];
int (*p)[4] = ia;
p = &ia[2];//p指向ia的尾元素
ch4 表達式
重載運算符(運算對象的類型和返回值的類型)
- (對象被用作)左值:用的是對象的身份(內存地址)
- 賦值運算符運算左值對象,返回左值
- 取地址符作用於左值,返回指向該運算的指針(右值)
- 解引用/下標/迭代器/string和vector的下標運算符,結果為左值
- 內置類型和迭代器的遞增遞減運算符,作用於左值,返回左值
- (對象被用作)右值:用的是對象的值(內容)
處理復合表達式
- 拿不准的時候最好用括號來限制
- 如果改變了某個運算對象的值,在表達式的其他地方不要再使用這個運算對象(*++iter例外)
溢出和其他算術運算符異常(“環繞”)
//(-m)/n和m/(-n)等於-(m/n),m%(-n)等於m%n,(-m)%n等於-(m%n)
//除法運算中,兩個運算對象的符號相同則商為正,否則為負
//取余運算中,m和n是整數且n非0,則(m/n)*n+m%n的結果與m相同
-21 % -8 = -5 -21 / -8 = 2
21 % -5 = 1 21 / -5 = -4
進行比較運算時除非比較的對象是布爾類型,否則不要使用true和false作為運算對象。
賦值運算滿足右結合律
除非必須,否則不使用遞增遞減運算符的后置版本(相對復雜的迭代器類型,額外工作消耗巨大)
一條語句中混用解引用和遞增運算符(*iter++|后置遞增運算符返回初始的未加1的值)
while(beg != s.end() && !isspace(*beg))
*beg = toupper(*beg++);//wrong
*beg = toupper(*beg);
*(beg+1) = toupper(*beg);
條件運算符(?)右結合律,優先級比<<低
sizeof(所得的值是size_t類型)
//sizeof運算能夠返回整個數組的大小
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr[sz];
類型轉換
- 隱式轉換
- 比int小的整數型首先提升為較大的整數類型(算術轉換/數組->指針/指針轉換)
- 條件中,非布爾轉換為布爾
- 初始化過程中,初始值轉換成變量類型;賦值語句中,右側運算對象轉換成左側類型
- 算術運算/關系運算的運算對象多種類型,需要轉換成同一種類型
- 函數調用時
- 顯示轉換
-
static_cast:最廣泛,除了底層const
-
const_cast:之惡能改變運算對象的底層const,常量對象轉換為非常量對象
const char *pc; char *p = const_cast<char*>(pc); char *q = static_cast<char*>(pc);//wrong static_cast<string>(pc); const_cast<string>(pc);//wrong
-
reinterpret_cast:為運算對象的位模式提供較低層次上的重新解釋。
-
dynamic_cast:支持運算時類型識別
盡量避免強制類型轉換
-
ch5 語句
懸垂else:else就近匹配
switch語句內部的變量定義
case true:
string file_name;//wrong,隱式初始化
int ival = 0;//wrong,顯式初始化
int jval;//true
break;
case false:
jval = next_num();
if(file_name.empty()){}
范圍for:不能通過范圍for增加vector對象元素,因為預存end(),一旦再序列中添加/刪除元素,end函數的值就可能變得無效了。
跳轉語句
- break:作用范圍僅限於最近循環/switch
- continue:中斷迭代,繼續執行循環
- goto:無條件跳轉到同一函數內的另一條語句
不要在程序中使用goto,會使得程序難理解難修改
try-catch語句塊
-
throw表達式
Sales_item item1, item2; cin >> item1 >> item2; if(item1.isbn()!=item2.isbn()) throw runtime_error("Data must refer to same ISBN!"); //拋出runtime_error異常,用string對象初始化 cout << item1+item2<<endl;
-
try-catch()
while(cin >> item1 >> item2){ try{ // }catch (runtime_error err){ cout << err.what() << endl; } }
函數在尋找處理代碼的過程中退出:編寫異常安全代碼十分困難
-
標准異常
- exception:定義最通用的異常類,報告異常的發生
- stdexcept:定義幾種常見的異常類
- new定義bad_alloc異常類型
- type_info定義bad_cast異常類型
ch6 函數
函數:返回類型,函數名字,0個或多個形參組成的列表,函數體
局部對象
- 名字的作用域是程序文本的一部分,名字在其中可見;
- 對象的生命周期是程序執行過程中該對象存在的一段時間。
- static局部靜態對象,第一次經過對象定義語句后初始化,直到程序結束才終止
變量和函數在頭文件中聲明(不包含函數體),在源文件中定義
分離式編譯:多文件組成的程序是如何編譯並執行的
參數傳遞
-
傳值參數(不影響初始值)
//指針形參,執行指針拷貝時,拷貝的時指針的值;拷貝后,兩個指針不同指向同一對象 void reset(int *ip) { *ip = 0;//改變指針ip所指對象的值 ip = 0;//改變ip的局部拷貝,實參未被改變 } int i = 0; reset(&i); //建議使用引用類型的形參替代指針
-
傳引用
void reset(int &i){ i = 0; } int j = 42; reset(j); //使用引用避免拷貝,若無須修改引用形參的值,聲明為常量引用
const形參和實參
const int ci = 42;//wrong,const頂層 int i = ci;//實參初始化形參和拷貝時,忽略頂層const int *const p = &i;//wrong,const頂層 *p = 0; void fcn(const int i)//不能向i寫值 void fcn(int i)//wrong,重復定義
指針/引用形參與const
int i = 42; const int *cp = &i;//cp don't change i const int &r = i;//r don't change i const int &r2 = 42; int *p = cp;//wrong,type don't match int &r3 = r;//wrong,type don't match int &r4 = 42;//wrong,字面值不能初始化非常量引用 const int ci = i; string::size_type ctr = 0; reset(&i); reset(&ci);//wrong,不能用指向const int對象指針初始化int* reset(i); reset(ci);//wrong,普通引用不能綁定在const對象上 reset(42);//wrong,普通引用不能綁定在字面值 rest(ctr);//wrong,type don't match find_char("hello world!", 'o', ctr);
盡量使用常量引用
數組形參print(const int*) print(const int[]) print(const int[10]) int i = 0, j[2]={0,1}; print(&i); print(j);//j轉換為int*指向j[0]
管理指針形參三種常用的技術
-
標記指定數組長度
void print(const char *cp){ if(cp) while(*cp) cout << *cp++ << endl; }
-
標准庫規范
void print(const int *beg, const int *end) { while(beg != end) cout << *beg++ << endl; }
-
顯示傳遞數組大小形參
void print(const int ia[], size_t size) { for(size_t i = 0; i < size; ++i) cout << ia[i] << endl; }
數組引用形參
void print(int (&arr)[10]){} f(int &arrp[10])//wrong,將arr聲明成了引用的數組 f(int (&arr)[10])//true,arr是包含10個整數的數組引用 void print(int (*matrix)[10], int rowSize){} void print(int matrix[][10], int rowSize){}//兩者等價
含有可變形參的函數:initializer_list形參(用於與C函數交互的接口)
void error_msg(initializer_list<string> il) { for(auto beg = il.begin();beg!=il.end();++beg) cout << *beg << endl; }
-
return 語句:不要返回局部對象的引用或指針
-
引用返回左值
char &get_val(string &str, string::size_type ix) return str[ix]; int main() { string s("a value"); get_val(s,0) = 'A'; cout << s << endl; }
-
列表初始化返回值
-
返回數組指針
//聲明一個返回數組指針的函數 int arr[10];//10個整數的數組 int *p1[10];//10個整數指針 int (*p2)[10] = &arr;//p2指向arr int (*func(int i))[10];//func的調用進行解引用后得到一個10的數組 //尾置返回類型 auto func(int i)->int (*)[10]; //使用decltype decltype(odd) *arrPtr(int i) { return (i % 2) ? &odd : &even; }
函數重載(函數名字相同形參列表不同)
-
重載和const形參:頂層const不影響傳入函數的對象,一個擁有頂層const的形參無法與一個沒有頂層const的形參區分開來
-
最好是重載那些確實非常相似的操作
-
const_cast和重載
string &shorterString(string &s1, string &s2) { auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2)); return const_cast<string&>(r); }
特殊用途語言特性
-
默認實參
typedef string::size_type sz; string screen(sz ht = 24, sz wid = 80, char backgrnd = ' '); //通常,應該在頭文件函數聲明中指定默認實參
-
內聯函數和constexpr函數(定義在頭文件中)
//避免函數調用的開銷,在編譯時展開 //能用於常量表達式的函數 constexpr size_t scale(size_t cnt) { return new_sz() * cnt; } int arr[scale(2)]; int i =2; int a2[scale(i)];//wrong,scale(i)不是常量表達
-
調試幫助
-
assert預處理宏assert(word.size() > threshold)
-
NDEBUG預處理變量
void print(const int ia[], size_t size) { #ifndef NDEBUG cerr << __func__<< "" << endl; #endif } __FILE__存放文件名 __LINE__存放當前行號 __TIME__存放文件編譯時間 __DATE__存放文件編譯日期
-
函數匹配
函數指針:指向是函數而非對象。函數的類型由返回類型和形參類型共同決定bool lengthCompare(const string &, const string &); bool (*pf)(const string &, const string &);//not initialize pf = lengthCompare; bool b1 = pf("hello world", "goodbye"); bool b2 = (*pf)("hello world", "goodbye");//equal bool b3 = lengthCompare("hello world", "goodbye");//equal //重載函數的指針 void ff(int*); void ff(unsigned int); void (*pf1)(unsigned int) = ff; //函數指針形參 void useBigger(const string &s1, const string &s2, bool pf(const string&, const string&)); useBigger(s1, s2, lengthCompare); //函數 typedef bool Func(const string&, const string&); typedef decltype(lengthCompare) Func2;//equal //指向函數的指針 typedef bool (*FuncP)(const string&, const string&); typedef decltype(lengthComapre) *FuncP;//equal //返回指向函數的指針 //auto和decltype用於函數指針
聲明getFcn唯一需要注意的地方是,牢記當我們將decltype作用於某個函數時,它返回函數類型而非指針類型。顯示加上*表明需要返回指針,而非函數本身.
-
ch7 類
定義在類內部的函數時隱式的inline函數
成員函數通過一個名為this常量指針的額外的隱式參數來訪問調用它的那個對象。
const放在成員函數參數表后,表明this時一個指向常量的指針。
//定義read和print函數
istream &read(istream &is, Sailes_data &item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
osteam &print(ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price() << endl;
return os;
}
構造函數
- 默認構造函數:無需任何實參
- 構造函數初始值列表
- 拷貝/賦值/析構
訪問控制和封裝(public|private;class|struct)
- 確保用戶代碼不會無意間破壞封裝對象的狀態。
- 被封裝的類的具體實現細節可以隨時改變,而無需調整用戶級別的代碼
友元函數(friend函數聲明|類定義開始/結束位置集中聲明友元)
重載成員函數
可變數據成員:mutable
//返回*this的成員函數
inline Screen &set(char);//若返回類型不是引用,則返回值時*this的副本
myScreen.display(cout).set('*');//wrong,display返回常量引用*this,無法set一個常量對象
//基於const的重載
Screen &display(std::ostream &os){return *this;}
const Screen &display(std::ostream &os) const {return *this;}
myScreen.set('&').display(cout);//調用非常量版本
blank.display(cout);//常量版本
建議:對於公共代碼使用私有功能屬性
類之間的友元關系:friend聲明,友元類的成員函數可以訪問此類非公有成員
建議:使用構造函數初始值
委托構造函數
class Sales_data{
public:
Sales_data(std::string s, unsigned cnt, double price):
bookNo(s), units_sold(cnt), revenue(cnt*price){}
//委托delegate
Sales_data():Sales_data("",0,0){}
Sales_data(std::string s):Sales_data(s, 0, 0){}
Sales_data(std::istream &is): Sales_data(){ read(is, *this);}
};
隱式類類型轉換:通過將構造函數聲明為explicit加以阻止,只能用於直接初始化,且對一個實參的構造函數有效
類的靜態成員(static關鍵字):存在於任何對象之外,成員函數不包含this指針
double r = Account::rate();
Account ac1;
Account *ac2 = &ac1;
r = ac1.rate();
r = ac2>rate();//通過對象/指針訪問
靜態成員的類內初始化,使用constexpr,但通常情況下依然在類外定義該成員。
ch8 IO庫
IO類
- iostream定義了用於讀寫流的基本類型
- fstream定義了讀寫命名文件的類型
- sstream定義了讀寫內存string對象的類型
標准庫能使我們忽略不同類型的流之間的差異:繼承機制實現的
IO對象無拷貝或賦值:不能將形參或返回類型設置為流類型,通常以引用方式傳遞返回流,不能使const的
緩沖刷新的原因:
- 程序正常結束,main函數的return操作的一部分,緩沖刷新被執行
- 緩沖區滿時,刷新
- 使用endl顯示刷新
- 每個書除操作之后,用unitbuf設置流的內部狀態,清空緩沖區。默認情況下,cerr時設置unitbuf的
- 輸出流關聯到另一個流時。如cin和cerr都關聯到cout,讀寫cin和cerr會導致cout的緩沖區被刷新
使用flush和ends刷新緩沖區(輸出一個空字符)
警告:如果程序崩潰,書除緩沖區不會被刷新
文件輸入輸出
ifstream in(ifile);
ofstream out;
out.open(ifile+".copy");
in.close();
in.open(ifile+"2");
當一個fstream對象被銷毀時,close會自動被調用,自動構造和析構
在每次打開文件時,都要設置文件模式,可能時顯示地設置,也可能是隱式地設置。當程序未指定模式,使用默認值。
string流:某些工作對整行文本進行處理,而其他工作是處理行內地某個單詞,用istringstream
string line, word;
vector<PersonInfo> people;
while(getline(cin,line)){
PersonInfo info;
istringstream record(line);
record >> info.name;
while(record >> word)
info.phones.push_back(word);
people.push_back(info);
}
ostringstream:逐步構造輸出,最后一起打印
ch9 順序容器
- vector:可變大小數組,支持快速隨機訪問,在尾部之外地位置插入或刪除可能很慢
- deque:雙端隊列,支持快速隨機訪問,在頭尾位置插入/刪除速度很快
- list:雙向鏈表,只支持雙向順序訪問,list任何位置插入/刪除速度很快
- forward_list:單向鏈表,只支持單項順序訪問,鏈表任何位置進行插入/刪除速度很快
- array:固定數組大小,支持快速隨機訪問,不能添加/刪除元素
- string:與vector相似地容器,專門保存字符,隨機訪問很快,尾部插入/刪除快
選擇哪種順序容器
- 通常選擇vector
- 程序中有很多小的元素,且空間額外開銷很重要,不要使用list和forward_list
- 程序要求隨機訪問元素,使用vector和deque
- 要求中間插入/刪除元素,使用list和forward_list
- 要求頭尾插入/刪除元素,中間不插入/刪除,使用deque
- 只在讀取輸入時在中間插入元素,則
-
- 首先,確定是否真的需要在容器中間插入元素,借助vector和sort
- 必須中間插入元素,輸入階段使用list,結束后拷貝到vector
容器操作:P330
迭代器范圍由一對迭代器表示,begin和end,[begin,end),帶r的版本返回反向迭代器,以c為開頭的版本返回const迭代器
容器定義和初始化
C c;//默認構造函數
C c1(c2);//拷貝
C c1 = c2;
C c{a,b,c};//列表初始化
C c={a,b,c};
C c(b,e)//c初始化為b和e指定范圍內元素的拷貝。
C seq(n);//seq包含n個元素,構造函數時explicit的
C seq(n,t);//seq包含n個初始值為t的元素
容器賦值運算
c1 = c2;//c1替換為c2的拷貝
c = {a,b,c};//c1元素替換為c2的拷貝
swap(c1,c2);
c1.swap(c2);//swap比從c2到c1拷貝快得多
seq.assign(b,e)//seq元素替換為b,e表示范圍內的元素
seq.assign(il)
seq.assign(n,t)
//assign可以將char*值賦予list
list<string> name;
vector<const char*> oldstyle;
names = oldstyle;//wrong,類型不匹配
names.assign(oldstylee.cbegin(),oldstyle.cend());
關鍵概念:容器元素時拷貝
push_front:list,forward_list,deque支持,常數級
vector,deque,list,string支持insert,比較耗時
slist.insert(iter, "Hello!");
svec.insert(svec.end(),10,"Anna");
slist.insert(slist.begin(),v.end()-2,v.end());
slist.insert(slist.end(),{"these","words"});
slist.insert(slist.begin(),slist.begin(),slist.end());//wrong,迭代器表示拷貝范圍,不能指向與目的位置相同的容器
iter = lst.insert(iter,word);//等同於push_front()
emplace_frontpush_front,emplaceinsert,emplace_back~push_back,用於容器中直接構造元素
at和下標操作只適用於string、vector、deque、array
back不適用於forward_list
如果容器為空,front和back時未定義的
c.pop_back()//刪除c中尾元素,c為空,行為未定義,返回void
c.pop_front()//刪除c中首元素,c為空,行為未定義,返回void
c.erase(p)
c.erase(b,e)//返回一個指向最后一個被刪元素之后元素的迭代器,若e為尾后迭代器,返回尾后迭代器
c.clear()
//刪除deque除首尾之外的任何元素都會使任何迭代器、指針、引用失效
forward_list特殊版本的添加/刪除操作
vector是如何增長的
- size:容器已經保存的元素數量
- capacity:不分配新的內存空間最多可以保存多少元素
不同的分配策略遵循一個原則:確保用push_back添加元素的操作由高效率
額外的string操作
string s(cp,n)//cp指向的數組前n個字符
string s(s2,pos2)//s2從pos2開始的字符的拷貝
string s(s2,pos2,len2)//s2從pos2開始len2字符的拷貝
string s2 = s.substr(0,5)
string s2 = s.substr(6)
s.append(args);//args追加到s,返回s的引用
s.replace(range,args);//將range字符替換成args
//搜索操作
string name("AnnaBelle");
auto pos1=name.find("Anna");
string numbers("0123456789"),name("r2d2");
auto pos = name.find_first_of(numbers);
string dept("03714p3");
auto pos = dept.find_first_not_of(numbers);
//compare函數
數值轉換
int i = 42;
string s = to_string(i);
double d = stod(s);
容器適配器:stack,queue,priority_queue
本質上,一個適配器是一種機制。每個適配器都在其底層順序接口容器上定義了一個新的接口。
ch10 泛型算法
算法:實現了一些經典算法的公共接口
泛型:它們可以用於不同類型的元素和多種容器類型(包括標准庫vector和list以及內置的數組類型)
算法遍歷由兩個迭代器指定的一個元素范圍進行操作
迭代器令算法不依賴於容器,但算法依賴於元素類型的操作
關鍵:算法不會執行容器的操作,只會運行於迭代器之上,算法永遠不會改變底層容器的大小
-
只讀算法
find //求和 int sum = accumulate(vec.cbegin(), vec.cend(), 0); string sum = accumulate(v.cbegin(), v.cend(), string(""));//去掉string會導致編譯錯誤,不能直接傳遞字面值 //比較兩個序列 equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());//假定第二個序列至少與第一個序列一樣長
-
寫容器元素算法
fill(vec.begin(), vec.end(), 0);//每個元素重置為0,寫操作安全 //算法不檢查寫操作 fill_n(vec.begin(), vec.size(), 0); vector<intj> vec; fill_n(vec.begin(), 10, 0);//結果未定義wrong
迭代器參數:兩個序列的元素可以來自不同的容器。
插入迭代器:back_insertervector<int> vec; fill_n(back_inserter(vec), 10, 0);
copy算法
int a1[] = {0,1,2,3,4}; int a2[sizeof(a1)/sizeof(*a1)]; auto ret = copy(begin(a1), end(a1), a2); replace(ilst.begin(), ilst.end(), 0, 42); replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0 ,42);//ivec包含ilst的一份拷貝
-
重排容器元素算法
void elimDupe(vector<string> &words){ sort(words.begin(), words.end()); auto end_unique = unique(words.begin(), words.end())//返回指向不重復區域之后一個位置的迭代器 words.erase(end_unique, words.end()); }
定制操作
-
向算法傳遞函數
bool isShorter(const string &s1, const string &s2){ return s1.size() < s2.size(); } sort(words.begin(), words.end(), isShorter); elimDups(words); stable_sort(words.begin(), words.end(), isShorter); for(const auto &s:words)//無須拷貝字符串 cout << s << " " ; cout << endl;
-
lambda表達式(一個返回類型,一個參數列表,一個函數體)
void biggies(vector<string> &words, vector<string>::size_type sz) { elimDups(words); stable_sort(words.begin(), words.end(), isShorter); } //lambda [capture list](parameter list) -> return type { function body } auto f = []{return 42; } [](const string &s1, const string &s2){ return s1.size() < s2.size(); } [sz](const string &a){ return a.size() >= sz; } auto wc = find_if(words.begin(), words.end(), [sz](const string &a) {return a.size() >= sz;}) for_each(wc, words.end(), [](const string &s){cout << s << " ";}); cout << endl;
lambda捕獲和返回
- 值捕獲
- 引用捕獲
- 隱式捕獲
建議:盡量保持lambda的變量捕獲簡單化
可變lambda:加上mutable
指定lambda返回類型:transform
-
參數綁定bind
auto check6 = bind(check_size(), _1, 6); sort(words.begin(), words.end(), bind(isShorter, _2, _1));//重排參數順序 for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));//綁定引用參數
再探迭代器
- insert iterator:插入元素,it = t, *it, ++it, it++
back_inserter:創建一個使用push_back的迭代器
front_inserter:使用push_front的迭代器 - iostream迭代器
istream_iterator
ostream_iterator - 反向迭代器
迭代器類別
- 輸入迭代器:==,!=;++;*;->
- 輸出迭代器:++;*
- 前向迭代器:只能沿一個方向移動,replace|forward_list
- 雙向迭代器:--,reverse
- 隨機訪問迭代器:<,<=,>,>=;+,+=,-=;-;下標運算符
特定容器算法
lst.merge(lst2)
lst.merge(lst2, comp)
list.remove(val)
lst.remove_if(pred)
list.reverse()
lst.sort()
lst.sort(comp)
lst.unique()
lst.unique(pred)
lst.splice(args)
//鏈表特有的操作會改變容器,如remove,unique,merge,splice
ch11 關聯容器
關聯容器中的元素是按關鍵字保存和訪問的
主要是map和set
map,set,multimap,multiset,unordered_map,unordered_set,unordered_multimap,unordered_multiset
pair標准庫類型
有序容器:使用比較運算符組織元素
關聯容器操作
//map的value_type是一個pair,可以改變值,但不能改變關鍵字成員的值
//set的迭代器是const的
auto map_it = word_count.cbegin();
while(map_it != word_count.cend())
{
cout << map_it->first << map_it->second << endl;
++map_it;
}
//添加元素
set.insert(ivec.cbegin(), ivec.cend());
set.insert({1,2,3});
word_count.insert({word,1});
word_count.insert(make_pair(word,1));
word_count.insert(pair<string,size_t>(word,1));
word_count.insert(map<string,size_t>::value_type(word,1));
//insert操作
c.insert(v);
c.emplace(args)
c.insert(b,e)
c.insert(il)
c.insert(p,v)
c.emplace(p,args)
//multimap
multimap<string,string> authors;
authors.insrt({"Barth,John","Factor"});
//刪除元素
c.erase(k)
c.erase(p)
c.erase(b,e)
//訪問元素
c.find(k)
c.count(k)
c.lower_bound(k)
c.upper_bound(k)
c.equal_range(k)
string search_item("Alain de Botton");
auto entries = authors.count(search_item);
auto iter = authors.find(search_item);
while(entries)
{
cout << iter->second << endl;
++iter;
entries--;
}
for(auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item);
beg != end; ++beg)
{
cout << beg->second << endl;
}
//equal_range
for(auto pos = authors.equal_range(search_item); pos.first != pos.end; ++pos.first)
{
cout << pos.first->second << endl;
}
對map使用find代替下標操作
無序容器:使用哈希函數和關鍵字類型的==運算符,存儲上組織為一組桶,哈希函數將元素映射到桶
size_t hasher(const Sales_data &sd)
{
return hash<string>()(sd.isbn());
}
bool eqOp(const Sales_data &lhs, cont Sales_data &rhs){
return lhs.isbn() == rhs.isbn();
}
ch12 動態內存
靜態內存:局部static對象、類static數據成員、定義在函數之外的變量。
棧內存:保存定義在函數內的非static對象
內存池(堆):存儲動態分配的對象,且顯式銷毀
-
shared_ptr類(模板)
默認初始化的智能指針中保存着一個空指針if(p1 && p1->empty()) *p1 = "hi"; //shared_ptr和unique_ptr都支持 shared_ptr<T> sp unique_ptr<T> sp p *p p->mem p.get()//p中保存的指針 swap(p,q)//交換指針 p.swap(q) //shared_ptr獨有 make_shared<T>(args) shared_ptr<T>p(q) p = q p.unique() p.use_count()//用於調試
-
make_shared
最為安全 auto p6 = make_shared<vector>(); -
拷貝賦值
shared_ptr都有一個引用計數。auto r = make_shared<int>(42); r = q;//遞增q指向對象的引用計數,遞減r的引用計數,r原來指向的對象自動銷毀
-
自動釋放相關聯的內存
shared_ptr<Foo> factory(T arg) { rdturn make_shared<Foo>(arg); } shared_ptr<Foo> use_factory(T arg) { shared_ptr<Foo> p = factory(arg); return p;//返回p時,引用計數遞增 }
-
使用了動態生存期的資源的類
-
程序不知道自己需要使用多少對象(容器類)
-
程序不知道所需對象的准確類型(ch15)
-
程序需要在多個對象中共享數據
Blob<string> b1; { Blob<string> b2 = {"a","aa","aaa"}; b1 = b2;//b1和b2共享底層數據,b2被銷毀,但b2中的元素不能銷毀 }
-
-
StrBlob
-
直接管理內存
int *p1 = new int;//分配失敗,拋出std::bad_alloc string *ps = new string(10,'9'); auto p1 = new auto(obj); const int *pci = new const int(1025); delete p; int i, *pi1 = &i, *pi2 = nullptr; double *pd = new double(33), *pd2 = pd; delete i;//false delete pi1;//undefined delete pd;//true delete pd2;//undifined delete pi2;//true
由內置指針管理的動態內存在顯式釋放前都會存在
new和delete管理內存存在三個問題:- 忘記delete
- 使用已經釋放掉的內存
- 同一塊內存釋放兩次
-
shared_ptr和new結合使用
//接受指針參數的智能指針構造函數是explicit shared_ptr<int> p1 = new int(1025);//wrong shared_ptr<int> p2(new int(1025));true shared_ptr<int> clone(int p){ return shared_ptr<int>(new int (p)); } //定義和改變shared_ptr shared_ptr<T> p(q) shared_ptr<T> p(u) shared_ptr<T> p(q,d) shared_ptr<T> p(p2,d) p.rerset() p.reset(q) p.reset(q,d)
不要混合使用智能指針和普通指針
不要使用get初始化另一個智能指針或為智能指針賦值shared_ptr<int> p(new int(42)); int *q = p.get(); { shared_ptr<int>(q); } imt foo = *p;//未定義,p指向的內存已釋放
reset更新引用計數,經常與unique一起使用
if(!p.unique())
p.reset(new string(*p));
*p += newVal; -
智能指針與異常
智能指針陷阱:- 不使用相同的內置指針值初始化(或reset)多個智能指針
- 不delete get()返回的指針
- 不適用get()初始化或reset另一個智能指針
- 使用get()返回的指針,最后一個對應的智能指針銷毀后,指針變為無效
- 使用智能指針,傳遞一個刪除器
-
-
unique_ptr
unique_ptr不支持拷貝和賦值//unique_ptr支持的操作 unique_ptr<T> u1 unique_ptr<T,D> u2 unique_ptr<T,D> u(d) u = nullptr u.release() u.reset() u.reset(q) u.reset(nullptr)
拷貝或賦值一個將要被銷毀的unique_ptr參數
unique_ptr<int> clone(int p){ return unique_ptr<int>(new int(p)); } unique_ptr<int> clone(int p){ unique_ptr<int> ret(new int(p)); //... return ret; } //傳遞刪除器 unique_ptr<objT, delT> p(new objT, fcn); void f(destination &d) { connection c = connect(&d); unique_ptr<connection, decltype(end_connection)*> p(&c,end_connection); //使用連接,且f退出時,connection會被關閉 }
-
weak_ptr
不控制所指向對象生存期的智能指針,指向一個shared_ptr管理的對象,不會改變shared_ptr的引用計數。weak_ptr<T> w weak_ptr<T> w(sp) w = p w.reset() w.use_count() w.expired() w.lock() auto p = make_shared<int>(42); weak_ptr<int> wp(p); if(shared_ptr<int> np = wp.lock()) //...
大多數應用應該使用標准庫容器而不是動態分配的數組,使用容器更不容易出線內存管理錯誤且可能有更好的性能。
-
new和數組
int *p = new int(get_size());
-
allocator類
allocator<T> a a.allocate(n) a.deallocate(p,n) a.construct(p,args) a.destroy(p) uninitialized_copy(b,e,b2) uninitialized_copy_n(b,n,b2) uninitialized_fill(b,e,t) uninitialized_fill_n(b,n,t)
ch13 拷貝控制
-
拷貝構造函數
class Foo { public: Foo(); Foo(const Foo&); }
合成~:
-
類類型成員:使用其拷貝構造函數
-
內置類型:直接拷貝
-
對於數組,如果不是類類型,則逐元素拷貝;否則按照元素拷貝構造函數拷貝
class Sales_data{
public:
Sales_data(const Sales_data&);
private:
std::string bookNo;
int units_sold = 0;
double revenue = 0.0;
}
Sales_data::Sales_data(const Sales_data &orig):
bookNo(orig.bookNo),
units_sold(orig.units_sold),
revenue(orig.revenue)
{}
//拷貝初始化
string dots(10, '.');//直接初始化
string s(dots);//直接初始化
string s2 = dots;//拷貝初始化
string nullbook = "9-99-99999-9";//拷貝初始化
string nines = string(100, '9');//拷貝初始化
拷貝發生在:
-
=定義變量
-
將一個對象從實參傳遞給非引用類型的形參
-
從一個返回類型為非引用類型的函數返回一個對象
-
花括號初始化一個數組或一個struct
當傳遞一個實參或從函數返回一個值時,不能隱式使用一個explicit構造函數。vector
v1(10);//true
vectorv2 = 10;//wrong,構造函數explicit
void f(vector);
f(10);//wrong,不能用explicit構造函數拷貝一個實參
f(vector(10));//true
編譯器可以繞過拷貝構造函數
-
-
拷貝賦值運算符
重載運算符:本質上是函數,由operator后接運算符符號。class Foo{ public: Foo& operator=(const Foo&); }
合成拷貝賦值運算符
Sales_data& Sales_data::operator=(const Sales_data &rhs){ bookNo = rhs.bookNo; units_sold = rhs.units_sold; revenue = rhs.revenue; return *this; }
-
析構函數
析構函數不接受參數,不能重載
析構函數被調用:- 變量離開作用域被銷毀
- 當以額對象被銷毀時,其成員被銷毀
- 容器被銷毀時,其元素被銷毀
- 動態分配的對象,指向它的指針用delete被銷毀
- 臨時對象,創建它的表達式結束時被銷毀
當指向一個對象的引用或指針離開作用域時,析構函數不會執行
析構函數體不直接銷毀成員,而是在析構函數體之后隱含的析構階段被銷毀
-
三/五原則
如果一個類需要析構函數,那么必然需要拷貝構造和拷貝賦值函數class HasPtr{ public: HasPtr(const std::string &s = std::string()): ps(new std::string(s)),i(0){} ~HasPtr(){delete ps;} //wrong,HasPtr需要一個拷貝構造和拷貝賦值 } //賦值 HasPtr f(HasPtr hp) { HasPtr ret = hp; return ret; } //hp和ret都被銷毀,導致ps被析構兩次 HasPtr p("some values"); f(p);//f結束時,p.ps被析構 HasPtr q(p);//q和p都指向無效內存
拷貝構造和拷貝賦值是共生的
-
使用=defalut
我們只能對具有合成版本的成員函數使用=defalut(默認構造,拷貝控制成員) -
阻止拷貝
=delete通知編譯器,我們不希望定義這些成員。我們可以對任何函數指定,主要用途是禁止拷貝控制成員。
析構函數不能=delete -
合成拷貝控制成員可能是刪除的
- 類的某個成員的析構函數是刪除的或private
- 拷貝構造函數是刪除的或private
- 拷貝賦值運算符是刪除的或private
- 類有一個引用成員,沒有類內初始化器,或有一個const成員,沒有類內初始化器,則默認構造函數是刪除的
類的行為像一個值,意味着它應該有自己的狀態,副本與原對象完全獨立。如標准庫容器和string類
類的行為像一個指針,共享狀態。副本和原對象使用相同的底層數據。如shared_ptr
IO和unique_ptr不允許拷貝和賦值,既不是指針也不是值
-
類值
class HasPtr{ public: HasPtr(const std::string &s = std::string()): ps(new std::string(s)),i(0){} HasPtr(const HasPtr &p): ps(new std::string(*p.ps)), i(0){} HasPtr& operator=(const HasPtr &); ~HasPtr(){delete ps;} private: std::string *ps; int i; } HasPtr& HasPtr::operator=(const HasPtr &rhs){ auto newp = new string(*rhs.ps); delete ps; ps = newp; i = rhs.i; return *this; } //note //如果一個對象賦予它自身,賦值運算符必須正常工作 //大多數賦值運算符組合了析構函數和拷貝構造函數的工作
-
類指針
class HasPtr{ public: HasPtr(const std::string &s = std::string()): ps(new std::string(s)),i(0),use(new std::size_t(1)){} HasPtr(const HasPtr &p): ps(new std::string(*p.ps)), i(p.i), use(p.use{++*use;} HasPtr& operator=(const HasPtr &); ~HasPtr(); private: std::string *ps; int i; } HasPtr::~HasPtr() { if(--*use == 0) { delete ps; delete use; } } HasPtr& HasPtr::operator=(const HasPtr &rhs) { ++*rhs.use; if(--*use == 0) { delete ps; delete use; } ps = rhs.ps; i = rhs.i; use = rhs.use; return *this; }
swap
拷貝控制示例
動態內存管理類
對象移動
//右值引用
int i = 42;
int &r = i;//true
int &&rr = i;//wrong
int &r2 = i*42;//wrong
const int &r3 = i*42;//true
int &&rr2 = i*42;//true
右值引用:所引用的對象即將銷毀;對象沒有其他用戶
顯示將左值轉換成右值引用:int &&rr3 = std::move(rr1);
StrVec::StrVec(StrVec &&s) noexcept//承諾不拋出異常
: elements(s.elements), first_free(s.first_free),cap(s.cap)
{
s.elements = s.first_free = s.cap = nullptr;
}
StrVec &StrVec::operator=(const StrVec &&rhs) noexcept
{
if(this != &rhs)
{
free();
elements = rhs.elements;
first_free = rhs.first_free;
cap = rhs.cap;
rhs.elements = rhs.first_free=rhs.cap = nullptr;
}
return *this;
}
ch14 重載運算與類型轉換
-
基本概念
一元運算符有一個,二元運算符有兩個參數。operator()含有默認實參- 賦值=、下標[]、調用()和成員訪問箭頭->必須是成員
- 復合賦值運算符一般來說是成員
- 遞增、遞減和解引用運算符,通常應該是成員
- 對稱性的運算符,如算術、相等、關系、位運算等,則為普通的非成員函數
-
輸入和輸出運算符
ostream &operator<<(ostream &os, const Sales_data &item) { os << item.isbn(); return os; } //輸入輸出運算符必須是非成員函數 istream &operator>>(istream &is, Sales_data &item) { double price; is >> item.bookNo >> item.units_sold >> price; if(is) item.revenue = item.units_sold * price; else item = Sales_data(); return is; }
-
算術和關系運算符
定義成非成員函數-
相等運算符
bool operator==(const Sales_data &lhs, const Sales_data &rhs) { return lhs.isbn() == rhs.isbn() && lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue; } bool operator!=(const Sales_data &lhs, const Sales_data &rhs) { return !(lhs == rhs); }
- 如果一個類含有判斷兩個對象是否相等的操作,則它應該把函數定義成operator==而非一個普通的命名函數
- 若定義了operator==,則應該能夠判定一組給定對象中是否含有重復數據
- 同時應該定義!=
- ==和!=應該把工作托管給其中一個
-
關系運算符
定義operator<比較有用- 定義順序關系,使得與關聯容器中對關鍵字的要求一致
- 類同時有運算符,定義一種關系使得與保持一致
如果存在唯一一種邏輯可靠的<定義,則應該考慮定義<,若同時包含,當且僅當<與產生的結果一致才定義
-
-
賦值運算符
復合賦值運算符Sales_data& Sales_data::operator+=(const Sales_data &rhs) { units_sold += rhs.units_sold; revenue += rhs.revenue; return *this; }
-
下標運算符(成員函數)
class StrVec{ public: std::string& operator[](std::size_t n) { return elements[n]; } const std::string& operator[](std::size_t n)const { return elements[n]; } private: std::string *elements; }
-
遞增/遞減運算符
//前置 class StrBlobPtr{ public: StrBlobPtr& operator++(); StrBlobPtr& operator--(); }; StrBlobPtr& StrBlobPtr::operator++() { check(curr,"increment past end of StrBlobPtr"); ++curr; return *this; } StrBlobPtr& StrBlobPtr::operator--() { --curr; check(curr,"increment past begin of StrBlobPtr"); return *this; } //后置 class StrBlobPtr{ public: StrBlobPtr& operator++(int); StrBlobPtr& operator--(int); }; StrBlobPtr& StrBlobPtr::operator++(int) { StrBlobPtr ret = *this; ++*this; return ret; } StrBlobPtr& StrBlobPtr::operator--() { StrBlobPtr ret = *this; --*this; return ret; }
-
成員訪問運算符
class StrBlobPtr{ public: std::string& operator*()const { auto p = check(curr, "dereference past end"); return (*p)[curr]; } std::string* operator->()const { return & this->operator*(); } }
-
函數調用運算符
-
重載、類型轉換與運算符
ch15 面向對象程序設計
OOP(object-oriented programming)核心思想是數據抽象、繼承和動態綁定。
- 抽象:接口和實現分離
- 繼承:定義相似的類型並對相似關系建模
- 動態綁定:一定程度上忽略類型的卻別,以統一的方式使用他們的對象
使用基類的引用/指針調用一個虛函數將發生動態綁定
OOP的核心思想是多態。引用/指針的靜態類型和動態類型不同正是C++支持多態的根本所在
基類與派生類
class Quote{
public:
Quote() = default;
Quote(const std::string &book, double sales_price):
bookNo(book),price(sales_price){}
std::string isbn()const {return bookNo;}
virtual double net_price(std::size_t n)const
{
return n * price;
}
virtual ~Quote() = default;
private:
std::string bookNo;
protected:
double price = 0.0;
};
class Bulk_quote : public Quote{
public:
Bulk_quote() = default;
Bulk_quote(const string&, double, std::size_t, double);
double net_price(std::size_t)const override;
private:
std::size_t min_qty = 0;
double discount = 0.0;
}
//派生類到基類的隱式轉換
Quote item;
Bulk_quote bulk;
Quote *p = &item;
p = &bulk;
Quote &r = bulk;
Bulk_quote(const std::string book, double p,std::size_t qty, double disc):
Quote(book,p),min_qty(qty),discount(disc){}//首先初始化基類,然后按照聲明順序初始化
繼承與靜態成員:只存在該成員唯一定義
防止繼承發生:final
- 虛函數
對虛函數的調用直到運行時才被解析。
通過作用域運算符可以強行調用基類虛函數 - 抽象基類
含有純虛函數=0的類是抽象基類。不能直接創建一個抽象基類對象。 - 訪問控制和繼承
protected:- 和私有成員類似,受保護的成員對於類的用戶是不可訪問的
- 與公有成員類似,受保護的成員對於派生類的成員和友元是可訪問的
- 繼承中的類作用域
派生類成員隱藏同名基類成員
p->mem()- 首先確定p的靜態類型。
- p的靜態類型對應的類內趙mem,如果找不到,則在直接基類內查找直至繼承鏈的頂端,還是找不到編譯器報錯。
- 找到了mem,進行類型檢查
- 若mem是虛函數,通過引用/指針調用,則在運行時確定到底運行虛函數的哪個版本
- 反之,若是通過對象或不是虛函數,則產生一個常規函數調用
- 構造函數與拷貝控制
虛析構函數 = default,阻止合成移動操作
合成拷貝控制與繼承
派生類的拷貝控制成員
繼承的構造函數 - 容器與繼承
ch16 模板與泛型編程
-
定義模板
template <typename T> int compare(const T &v1, const T &v2) { if(v1 < v2) return -1; if(v2 < v1) return 1; return 0; } template <typename T> T foo(T* p) { T tmp = *p; return tmp; } template <typename T, class U> calc(const T&, const U&);
非類型模板參數
inline和constexpr的函數模板template <typename T> int compare(const T &v1, const T &v2) { if(less<T>()(v1,v2)) return -1; if(less<T>()(v1,v2)) return 1; return 0; }
類模板
-
默認情況下,對於一個實例化了的類模板,其成員只有在使用時才被實例化
-
類模板作用域內,可以直接使用模板名而不必指定模板實參
-
類模板與友元:一對一(BlobPtr
可以訪問Blob ,而不能訪問Blob ),通用/一對多,模板自己的類型成為友元 -
類模板的static:每個實例都有唯一一個static對象
模板參數 -
當我們希望通知編譯器一個名字表示類型時,必須使用typename,而不能使用class
-
默認模板實參
template <typename T, typename F = less<T>> int compare(const T &v1, cosnt T &v2,F f = F()) { if(f(v1,v2)) return -1; if(f(v2,v1)) return 1; return 0; }
成員模板(不能是虛函數)
-
template <typename T> class Blob{ template <typename It> Blob(It b, It e); }; template <typename T> template <typename T> Blob<T>::Blob(It b, It e):data(std::make_shared_ptr<std::vector<T>>(b,e)){}
控制實例化
-
多文件中實例化相同模板的開銷非常嚴重,可以通過顯式實例化避免開銷。
extern template class Blob<string>; template int compare(const int&, const int&);
-
在一個類模板的實例化定義中,所用類型必須能夠用於模板的所有成員函數
效率和靈活性 -
運行時綁定刪除器:shared_ptr
-
編譯時綁定刪除器:unique_ptr
-
-
模板實參推斷(函數實參->模板實參)
調用中應用於函數模板-
const轉換,將一個非const對象的引用傳遞給const引用形參
-
數組或函數指針轉換
-
顯式實參
auto val3 = sum<long,long>(i,lng); template <typename T1,typename T2,typename T3> T3 alternative_sum(T2,T1); auto val2 = alternative_sum(long long,int,int)(i,lng);
-
尾置返回類型與類型轉換
//允許我們在參數列表之后聲明返回類型 template <typename It> auto fcn(It beg, It end)->decltype(*beg) { return *beg; }
-
std::move實現
-
轉發:如果一個函數參數是指向模板類型參數的右值引用,它對應的實參的const屬性和左值/右值屬性將得到保持
-
使用std::forward保持類型
-
-
重載與模板
正確定義一組重載的函數模板需要對類型間的關系和模板函數允許的有限的實參類型轉換有深刻的理解。 -
可變參數模板
-
模板參數包:0個/多個模板參數
//Args是模板參數包,rest是函數參數包 template <typename T, typename... Args> void foo(const T &t, const Args&... rest); //sizeof...運算符返回包中元素
-
函數參數包:0個/多個函數參數
-
包擴展
-
轉發參數包
-
-
模板特例化(本質是實例化一個模板)
template<> int compare(const char* const &p1, const char* const &p2) return strcmp(p1,p2); namespace std{ template<> struct hash<Sales_data> { typedef size_t result_type; typedef Sales_data argument_type; size_t operator()(const Sales_data& s)const; }; size_t hash<Sales_data>::operator()(const Sales_data &s)const { return hash<string>()(s.bookNo) ^ hash<unsigned>()(s.units_sold) ^ hash<double>()(s.revenue); } }
部分特例化類模板,而不能部分特例化函數模板
特例化成員而不是類