1. C++基礎(chǔ)
- 大多數(shù)編程語(yǔ)言通過兩種方式來(lái)進(jìn)一步補(bǔ)充其基本特征
1)賦予程序員自定義數(shù)據(jù)類型的權(quán)利,從而實(shí)現(xiàn)對(duì)語(yǔ)言的擴(kuò)展
2)將一些有用的功能封裝成庫(kù)函數(shù)提供給程序員
1.1 變量和基本類型
-
算術(shù)類型
-
類型的轉(zhuǎn)換
一個(gè)算術(shù)表達(dá)式既有無(wú)符號(hào)數(shù)又有int值時(shí),int值就會(huì)轉(zhuǎn)換成無(wú)符號(hào)數(shù)(負(fù)數(shù)總是加上模)
-
指定字面值的類型
變量提供了一個(gè)具名的、可供程序操作的存儲(chǔ)空間。數(shù)據(jù)類型決定著變量所占內(nèi)存空間的大小和布局方式、該空間能存儲(chǔ)的值的范圍,以及變量能參與的運(yùn)算。
在c++中,初始化和賦值時(shí)兩個(gè)完全不同的操作。
初始化不是賦值,初始化的含義是創(chuàng)建變量時(shí)賦予其一個(gè)初始值,而賦值的含義是把對(duì)象的當(dāng)前值擦除,而以一個(gè)新值替代。在C++ 11中,用花括號(hào)來(lái)初始化變量得到了全面應(yīng)用。這種初始化的形式被稱為列表初始化。當(dāng)用于內(nèi)置類型的變量時(shí),列表初始化有一個(gè)重要特點(diǎn):如果使用列表初始化且初始值存在丟失信息的風(fēng)險(xiǎn),則編譯器將報(bào)錯(cuò)。
定義于函數(shù)體內(nèi)的內(nèi)置類型的對(duì)象如果沒有初始化,則其值未定義。類的對(duì)象如果沒有顯示地初始化,則其值由類確定。
-
命名規(guī)范
在變量使用的附近定義變量。這樣更容易找到變量的定義,并且付給它一個(gè)比較合理的初始值。
左值引用——為對(duì)象起了另外一個(gè)名字,引用必須初始化。引用本身不是一個(gè)對(duì)象,因此一旦定義了引用,就無(wú)法令其再綁定到另外的對(duì)象。
一般初始化變量時(shí),初始值會(huì)被拷貝到新建的對(duì)象中,然而定義引用時(shí),程序把引用和它的初值綁定在一起,而不是將初值拷貝給引用。
引用只能綁定在對(duì)象上,而不能與字面值或某個(gè)表達(dá)式的計(jì)算結(jié)果綁定在一起。指針本身是一個(gè)對(duì)象;指針無(wú)需在定義時(shí)賦值。
在新標(biāo)準(zhǔn)下,C++程序最好使用nullptr(字面值)來(lái)對(duì)空指針進(jìn)行賦值,同時(shí)盡量避免使用NULL(預(yù)處理變量)。
建議初始化所有的指針,并且在可能的情況下,盡量等定義了對(duì)象之后再定義指向它的指針。如果實(shí)在不清楚指針應(yīng)該指向何處,就把它初始化為nullptr。
引用本身不是一個(gè)對(duì)象,因此不能定義指向引用的指針。但指針使對(duì)象,所以存在對(duì)指針的引用。
int i = 43;
int *p;
int *&r = p; // r是對(duì)指針p的引用,從右往左進(jìn)行分析
r = &i;
*r = 0;
- const 引用
當(dāng)一個(gè)常量引用被綁定到另外一種類型時(shí),會(huì)綁定到一個(gè)臨時(shí)量對(duì)象。C++將這種行為歸為非法。
int i = 42;
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1 * 2;
錯(cuò)誤 int &r4 = r1 * 2;
- const引用可能引用一個(gè)并非const的對(duì)象,此時(shí)允許通過其他途徑改變其值。
int i = 42;
int &r1 = i;
const int &r2 = i;
r1 = 0;
錯(cuò)誤r2 = 0;
- 編譯器在編譯過程中,將用到const變量的地方替換成對(duì)應(yīng)的值,默認(rèn)情況下,const對(duì)象被設(shè)定為僅在文件內(nèi)有效。如果想在多個(gè)文件之間共享const對(duì)象,必須在變量定義之前添加extern關(guān)鍵字。
- 頂層const表示指針本身是個(gè)常量,底層const表示指針?biāo)傅膶?duì)象是一個(gè)常量。
頂層const 可以表示任意的對(duì)象時(shí)常量,這一點(diǎn)對(duì)任何數(shù)據(jù)類型都適用,如算術(shù)類型、類、指針等。底層const則與指針和引用等符合類型的基本類型部分有關(guān)。
int i = 0;
int *const p1 = &i; // 不能改變p1,頂層
const in ci = 42; // 不能改變c1,頂層
const int *p2 = &ci; //允許改變p2,底層const
const int *const p3 = p2; // 頂層和底層
const int &r = ci; // 用于聲明引用的const都是底層const
- 常量表達(dá)式是指值不會(huì)改變且在編譯過程中能得到計(jì)算結(jié)果的表達(dá)式。
C++ 11規(guī)定,允許將變量聲明為constexpr類型以便由編譯器來(lái)驗(yàn)證變量的值是否是一個(gè)常量表達(dá)式。聲明為cosntexpr的變量一定是一個(gè)常量,而且必須用常量表達(dá)式初始化。
一個(gè)constexpr指針的初始值必須是nullptr,或者是存儲(chǔ)于某個(gè)固定地址中對(duì)象。
函數(shù)體內(nèi)定義的自動(dòng)變量并非固定地址,不能用constexpr指針。定義于函數(shù)體外的對(duì)象其地址不變,可以用來(lái)初始化constexpr。 - constexptr把它所定義的對(duì)象置為了頂層const。
const int *p = nullptr; // p是一個(gè)指向常量的指針
constexpr int *q = nullptr; // q是一個(gè)常量指針
- 兩種方法定義類型別名
1)typedef
2)using SI = Sales_item; // SI是Sales_item的同義詞
typedef char *pstring;
const pstring cstr = 0; // cstr是指向char的常量指針
const pstring *ps; // ps是一個(gè)指針,它的對(duì)象時(shí)指向char的常量指針
錯(cuò)誤的理解 const char *cstr = 0; // 是對(duì)const pstring cstr的錯(cuò)誤理解,typedef定義后,就變成了不可分割的整體
- auto讓編譯器分析表達(dá)式所屬的類型,使用auto也能在一條語(yǔ)句中聲明多個(gè)變量。因?yàn)橐粭l聲明語(yǔ)句只能有一個(gè)基本數(shù)據(jù)類型,所以該語(yǔ)句中的所有變量的初始基本數(shù)據(jù)類型都必須一樣。
1)使用引用其實(shí)使用的是引用的對(duì)象,真正參與初始化的是引用對(duì)象的值,因此編譯器以引用對(duì)象的類型作為auto的類型
2)auto會(huì)忽略掉頂層const。同時(shí)底層const則會(huì)保留下來(lái)。
3)如果希望推斷出的auto類型是一個(gè)頂層const,需要明確指出
4)設(shè)置一個(gè)類型為auto的引用時(shí),初始值中的頂層常量屬性仍然保留
int i = 0, &r = i;
auto a = r; //a是一個(gè)整數(shù)
const int ci = i, &cr = ci;
auto b = ci; // b是一個(gè)整數(shù),ci的頂層const被忽略
auto c = cr; // c是一個(gè)整數(shù)
auto d = &i; // d的是一個(gè)整型指針
auto e = &ci; //ci是一個(gè)指向整型常量的指針
const auto f = ci; //f是const int
auto &g = ci; //g是一個(gè)整型常量引用,綁定到ci
錯(cuò)誤 auto &h = 42;
const auto &j = 42; //可以為常量引用綁定字面值
- decltype類型指示符
選擇并返回操作數(shù)的數(shù)據(jù)類型,在此過程中,編譯器分析表達(dá)式并得到它的類型,卻并不計(jì)算表達(dá)式的值。
decltype與auto有些許不同
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x 是const int
decltype(cj) y = x; // y 是const int &, y綁定到x
- 如果表達(dá)式的內(nèi)容十解引用操作,則decltype將得到引用類型。
變量如果加上括號(hào),會(huì)得到引用類型。
decltype((variable)) 結(jié)果永遠(yuǎn)是引用,而decltype(variable)結(jié)果只有當(dāng)variable本身是一個(gè)引用時(shí)才是引用。
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // 結(jié)果是int
錯(cuò)誤 decltype(*p) c; // c是int &,必須初始化
錯(cuò)誤 decltype((i)) d; // d是int&,必須初始化
decltype(i) e; //e是int
1.2 字符串、向量和數(shù)組
- 作用域操作符::,編譯器從操作符左側(cè)名字的作用域中尋找右側(cè)那個(gè)名字。
- 頭文件一般不應(yīng)該使用using
-
string初始化
1)拷貝初始化
使用=,執(zhí)行的是拷貝初始化,編譯器將等號(hào)右側(cè)的初始值拷貝到新創(chuàng)建的對(duì)象中去。
2)直接初始化
不是用等號(hào) -
string的操作
- string::size_type是無(wú)符號(hào)類型的值。注意不要在表達(dá)式中混用帶符號(hào)樹和無(wú)符號(hào)數(shù)。
- 標(biāo)準(zhǔn)庫(kù)允許把字符字面值和字符串字面值轉(zhuǎn)換成string對(duì)象,所以在需要string對(duì)象的地方可以使用這兩種字面值來(lái)代替。當(dāng)把string對(duì)象和字符字面值及字符串字面值混在一條語(yǔ)句中使用時(shí),必須確保每個(gè)加號(hào)運(yùn)算符(+)的兩側(cè)的運(yùn)算對(duì)象至少有一個(gè)是string。
- 為了與C兼容,C++中字符串字面值并不是標(biāo)準(zhǔn)庫(kù)類型string對(duì)象。字符串字面值與string是不同類型。
-
處理string對(duì)象中的字符
range for可以處理string中的每個(gè)字符。
-
vector——類模板
模板本身不是類或函數(shù),相反可以將模板看作為編譯器生成類或函數(shù)編寫的一份說(shuō)明。編譯器根據(jù)模板創(chuàng)建類或函數(shù)的過程稱為實(shí)例化。
老式的聲明vector<vector<int> >,需要添加一個(gè)空格,C++11不需要。
-
圓括號(hào)和花括號(hào)的區(qū)別,花括號(hào)是列表初始化:
- 范圍for語(yǔ)句體內(nèi)不應(yīng)改變其所遍歷序列的大小。
-
vector操作
-
迭代器
有效迭代器指向某個(gè)元素或者指向容器中尾元素的下一位置,其他情況都屬于無(wú)效。
- 標(biāo)準(zhǔn)庫(kù)容器的迭代器都定義了==和!=,大多數(shù)沒有定義<。
- 使迭代器失效的操作
1)不能在范圍for循環(huán)中向vector對(duì)象添加元素
2)任何一種可能改變vector對(duì)象容量的操作,都會(huì)使vector對(duì)象迭代器失效 -
迭代器運(yùn)算
距離的類型名是difference_type的帶符號(hào)整型數(shù)。
- 數(shù)組 —— 跟C語(yǔ)言一樣
size_t在cstddef頭文件里面進(jìn)行了定義。
指針也是迭代器 - 在iterator頭文件中定義了begin和end函數(shù),end函數(shù)返回尾元素下一位置的指針。尾后指針不能執(zhí)行解引用和遞增操作。
兩個(gè)指針相減的結(jié)果類型是ptrdiff_t。
int *pbeg = begin(arr), *pend = end(arr);
while (pbeg != pend && *pbeg >= 0) {
++pbeg;
}
盡管C++支持C風(fēng)格字符串,但在C++最好還是不要使用它們。因?yàn)镃風(fēng)格字符串使用不方便,且極易引發(fā)程序漏洞。
現(xiàn)代C++程序應(yīng)該盡量使用vector和迭代器,避免使用內(nèi)置數(shù)組和指針;應(yīng)該盡量使用string,避免使用C風(fēng)格的基于數(shù)組的字符串。多維數(shù)組
int ia[3][4];
for (auto p = begin(ia); p != end(ia); ++p) {
for (auto q = begin(*p); q != end(*p); ++q) {
cout << *q << ' ';
}
cout << endl;
}
1.3 表達(dá)式
- 重載運(yùn)算符時(shí),其包括運(yùn)算對(duì)象的類型和返回值的類型,都是由該運(yùn)算符定義的,但是運(yùn)算對(duì)象的個(gè)數(shù)、運(yùn)算符的優(yōu)先級(jí)和結(jié)合律都是無(wú)法改變的。
- 左值和右值:當(dāng)一個(gè)對(duì)象被用作右值時(shí),用的是對(duì)象的值(內(nèi)容);當(dāng)對(duì)象被用作左值時(shí),用的是對(duì)象的身份(在內(nèi)存中的位置)。
如果表達(dá)式的求值結(jié)果是左值,decltype作用于該表達(dá)式(不是變量)得到一個(gè)引用類型,例如p是int 類型,decltype(p)是int &。 - 只有四種運(yùn)算符明確規(guī)定了運(yùn)算對(duì)象的求值順序:&& || ?: ,四種運(yùn)算符
- 求值順序、優(yōu)先級(jí)、結(jié)合律
1)當(dāng)拿不準(zhǔn)時(shí)候,最好用括號(hào)來(lái)強(qiáng)制讓表達(dá)式的組合關(guān)系符合程序邏輯的要求
2)如果改變了某個(gè)運(yùn)算符對(duì)象的值,在表達(dá)式的其他地方不要使用這個(gè)運(yùn)算對(duì)象。例外:當(dāng)改變運(yùn)算符的子表達(dá)式就是另外一個(gè)子表達(dá)式的運(yùn)算對(duì)象時(shí)。例如:*++iter - c++新標(biāo)準(zhǔn)規(guī)定商一律向0取整(即直接切除小數(shù)部分)
- 除非必要,否則不用遞增遞減運(yùn)算符的后置版本
- 在大多數(shù)用到數(shù)組的表達(dá)式中,數(shù)組自動(dòng)轉(zhuǎn)換成指向數(shù)組首元素的指針;數(shù)組用作decltype、&、sizeof、typeid的運(yùn)算對(duì)象時(shí)除外。
- 四種類型的顯式轉(zhuǎn)換
cast-name<type>(expression)
1)static_cast
任何具有明確定義的類型轉(zhuǎn)換,只要不包含底層const,都可以使用static_cast。
2)dynamic_cast
支持運(yùn)行時(shí)類型識(shí)別
3)const_cast
只能改變運(yùn)算對(duì)象底層const
4)reinterpret_cast
為運(yùn)算對(duì)象的位模式提供較低層次上的重新解釋。
const char *pc;
char *p = const_cast<char *>(pc); // 正確,但是通過p寫值是未定義的行為
- 避免強(qiáng)制類型轉(zhuǎn)換,尤其是reinterpret_cast
在有重載函數(shù)的上下文中使用const_cast無(wú)可厚非
-
運(yùn)算符優(yōu)先級(jí)表
1.4 語(yǔ)句
- break語(yǔ)句負(fù)責(zé)終止離它最近的while、do while、for或switch語(yǔ)句,并從這些語(yǔ)句之后的第一條語(yǔ)句開始繼續(xù)執(zhí)行。
continue終止最近的循環(huán)中的當(dāng)前迭代并立即開始下一次迭代。 - 異常處理機(jī)制為程序中異常檢測(cè)和異常處理兩部分的協(xié)作提供支持。
1)throw表達(dá)式,異常檢測(cè)部分使用throw表達(dá)式來(lái)表示它遇到了無(wú)法處理的問題。
2)try語(yǔ)句塊,異常處理部分使用try語(yǔ)句塊處理異常。try語(yǔ)句塊中代碼拋出的異常通常會(huì)被某個(gè)catch子句處理。
3)一套異常類,用于在throw表達(dá)式和相關(guān)的catch子句之間傳遞異常的具體信息。 - 當(dāng)異常被拋出時(shí),首先搜索拋出該異常的函數(shù)。如果沒有找到匹配的catch子句,終止該函數(shù),并在調(diào)用該函數(shù)的函數(shù)中繼續(xù)尋找。如果還是沒有找到匹配的catch子句,這個(gè)新的函數(shù)也被終止,繼續(xù)搜索調(diào)用它的函數(shù)。以此類推。沿著程序的執(zhí)行路徑逐層回退,直到找到適當(dāng)類型的catch子句為止。如果最終沒有找到任何匹配的catch子句,程序轉(zhuǎn)到terminate的標(biāo)準(zhǔn)庫(kù)函數(shù)。
-
異常類分別定義在4個(gè)頭文件中
1)exception頭文件定義最通用的異常類。只報(bào)告異常的發(fā)生,不提供任何額外的信息
2)stdexcept頭文件定義了幾種常用的異常類
3)new頭文件定義了bad_alloc異常類型
4)type_info定義了bad_cast異常類型 - 只能以默認(rèn)初始化的方式初始化exception bad_alloc bad_cast對(duì)象,不允許為這些對(duì)象提供初始值。
其他異常類型的行為恰好相反:應(yīng)該使用string對(duì)象或C風(fēng)格字符串初始化這些類型的對(duì)象,不允許使用默認(rèn)初始化方式。
異常類型之定義了一個(gè)what成員函數(shù),返回const char*,提供異常的一些文本信息。
1.5 函數(shù)
- 在C++中,名字有作用域,對(duì)象有生命周期。
- 函數(shù)的形參盡量使用常量引用,如果將其定義為普通引用,就不能將const 對(duì)象、字面值或者需要類型類型轉(zhuǎn)換的對(duì)象傳遞給普通的引用形參。
-
編寫能處理不同數(shù)量實(shí)參的參數(shù),C++ 11新標(biāo)準(zhǔn)提供了兩種主要的方法
1)如果所有的實(shí)參類型相同,可以傳遞一個(gè)名為intializer_list的標(biāo)準(zhǔn)庫(kù)類型
initializer_list對(duì)象中的元素用于是常量值,無(wú)法改變。
2)如果實(shí)參類型不同,可以編寫可變參數(shù)模板
void error_msg(initializer_list<string> il) {
for (auto beg = il.begin(); beg != il.end(); ++beg) {
cout << *beg << " ";
}
cout << endl;
}
- vargargs省略符形參應(yīng)該僅僅用于C和C++通用的類型。特別注意,大多數(shù)類型的對(duì)象在傳遞給省略符形參時(shí)都無(wú)法正確拷貝。
- 返回一個(gè)值的方式和初始化一個(gè)變量或形參的方式完全一樣:返回的值用于初始化調(diào)用點(diǎn)的一個(gè)臨時(shí)量,該臨時(shí)量就是函數(shù)調(diào)用的結(jié)果。
- 返回引用的函數(shù)得到左值,其他返回類型得到右值。
- C++11規(guī)定,函數(shù)可以返回花括號(hào)包圍的值的列表。類似于其他返回結(jié)果,此處的列表也用來(lái)對(duì)表示函數(shù)返回的臨時(shí)量進(jìn)行初始化。
vector<string> process() {
if (expected.empty()) {
return {};
} else if (expected == actual ) {
return {"functionX", "okay"};
} else {
return {"functionX", expected, actual};
}
}
- C++ 11可以使用尾置返回類型來(lái)簡(jiǎn)化復(fù)雜返回類型的函數(shù)聲明
auto func(int i) -> int (*) [10]; // func接受一個(gè)int類型的實(shí)參,返回一個(gè)指針,該指針指向還有10個(gè)整數(shù)的數(shù)組
- 重載函數(shù):同一作用域內(nèi)的幾個(gè)函數(shù)名字相同但形參列表不同。編譯器會(huì)根據(jù)傳遞的實(shí)參類型推斷想要的是哪個(gè)函數(shù)。
不允許兩個(gè)函數(shù)除了返回類型外其他所有要素都相同。 - 頂層const 不影響傳入函數(shù)的對(duì)象,所以無(wú)法和沒有頂層const的形參區(qū)分開來(lái);
底層const可以重載,此時(shí)形參是某種類型的指針或引用,當(dāng)傳遞一個(gè)非常量對(duì)象或指向非常量對(duì)象的指針時(shí),編譯器會(huì)優(yōu)先選用非常量版本的函數(shù)。
頂層const 可以表示任意的對(duì)象時(shí)常量,這一點(diǎn)對(duì)任何數(shù)據(jù)類型都適用,如算術(shù)類型、類、指針等。底層const則與指針和引用等符合類型的基本類型部分有關(guān)。
Record lookup(Phone);
Record lookup(const Phone); // 重復(fù)聲明
Record lookup(Phone*);
Record lookup(Phone* const); // 重復(fù)聲明
Record lookup(Account &);
Record lookup(const Account &); // 新函數(shù)
Record lookup(Account*);
Record lookup(const Account *); // 新函數(shù)
- const_cast和重載:將const轉(zhuǎn)變?yōu)槠胀ǖ臒o(wú)const
const string &shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
string &shorterString(string &s1, string &s2) {
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const_string&>(s2));
return const_cast<string&>(r);
}
- 函數(shù)匹配(重載確定):
1)編譯器找到一個(gè)與實(shí)參最佳匹配的函數(shù),并生成調(diào)用該函數(shù)的代碼
2)找不到任何一個(gè)函數(shù)與調(diào)用的實(shí)參匹配,此時(shí)編譯器發(fā)出無(wú)匹配的錯(cuò)誤信息
3)有多于一個(gè)函數(shù)可以匹配,但是每一個(gè)都不是明顯的最佳選擇。此時(shí)也將發(fā)生錯(cuò)誤,稱為二義性調(diào)用。 - 若在內(nèi)層作用域中聲明重載函數(shù),它將隱藏外層作用域中聲明的同名實(shí)體。編譯器首先在當(dāng)前作用域?qū)ふ?,如果找到相?yīng)的函數(shù),編譯器就會(huì)忽略掉外層作用域中的同名實(shí)體,剩下的工作就是檢查函數(shù)調(diào)用是否有效。
void print(const string&);
void print(double);
void fooBar(int ival) {
void print(int); //隱藏了外層作用域的兩個(gè)print
print("value: "); //錯(cuò)誤
print(ival); //正確
print(3.14); //調(diào)用print(int)
}
正確做法:
void print(const string&);
void print(double);
void print(int);
void fooBar(int ival) {
print("value: "); //print(const string&)
print(ival); //print(int)
print(3.14); //print(double)
}
- 默認(rèn)實(shí)參
一旦某個(gè)形參被賦予了默認(rèn)值,它后面的所有形參都必須有默認(rèn)值。當(dāng)設(shè)計(jì)含有默認(rèn)實(shí)參的函數(shù)時(shí),其中一項(xiàng)任務(wù)是合理設(shè)置形參的順序,盡量讓不怎么使用默認(rèn)值的形參出現(xiàn)在前面,而讓那些經(jīng)常使用默認(rèn)值的形參出現(xiàn)在后面。 - 內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)說(shuō)明只是向編譯器發(fā)出的一個(gè)請(qǐng)求,編譯器可以選擇忽略這個(gè)請(qǐng)求。一般來(lái)說(shuō),內(nèi)聯(lián)機(jī)制用于優(yōu)化規(guī)模較小、流程直接、頻繁調(diào)用的函數(shù)。 - constexpr函數(shù)
能用于常量表達(dá)式的函數(shù),要遵循幾項(xiàng)約定:函數(shù)的返回類型及所有形參的類型都得是字面值類型,而且函數(shù)體中必須有且只有一條return語(yǔ)句。
為了能在編譯過程中隨時(shí)展開,constexpr函數(shù)被隱式地指定為內(nèi)聯(lián)函數(shù)。
constexpr函數(shù)不一定返回常量表達(dá)式
constexpr int new_sz() { return 42;}
constexpr size_t scale(size_t cnt) {
return new_sz() * cnt;
}
int arr[scale(2)]; // 正確
int i = 2;
int a2[scale(i)]; // 錯(cuò)誤:scale(i)不是常量表達(dá)式
- 調(diào)試幫助
程序可以包含一些用于調(diào)試的代碼,但是這些代碼只是在開發(fā)程序時(shí)使用。當(dāng)應(yīng)用程序編寫完成準(zhǔn)備發(fā)布時(shí),要先屏蔽掉調(diào)試代碼。
1)assert預(yù)處理宏(cassert頭文件)
assert(expr); 如果表達(dá)式為假(即0),assert輸出信息并終止程序的執(zhí)行。如果為真,則什么也不做。
預(yù)處理名字由預(yù)處理器而非編譯器管理。
2)NDEBUG預(yù)處理變量
assert的行為依賴于一個(gè)名為NDEBUG的預(yù)處理變量的狀態(tài)。如果定義了NDEBUG,則assert什么也不做。默認(rèn)狀態(tài)下沒有定義NDEBUG,此時(shí)assert將執(zhí)行運(yùn)行時(shí)檢查。
可以使用編譯命令選項(xiàng)-D來(lái)定義NDEBUG。
可以將assert當(dāng)成調(diào)試程序的一種輔助手段,但是不能用它替代真正的運(yùn)行時(shí)邏輯檢查,也不能替代程序本身應(yīng)該包含的錯(cuò)誤檢查。
預(yù)處理器定義對(duì)于程序調(diào)試很有用的名字:
__func__ 函數(shù)名字
__FILE__ 文件名
__LINE__行號(hào)
__TIME__編譯時(shí)間
__DATE__日期
- 函數(shù)匹配
1)函數(shù)匹配的第一步是選定本次調(diào)用對(duì)應(yīng)的重載函數(shù)集,集合中的函數(shù)稱為候選函數(shù)。候選函數(shù)具備兩個(gè)特征:一是與被調(diào)用的函數(shù)同名,二是其聲明在調(diào)用點(diǎn)可見。
2)第二步考察本次調(diào)用提供的實(shí)參,然后從候選參數(shù)中選出能被這組實(shí)參調(diào)用的函數(shù),這些新選出的函數(shù)稱為可行函數(shù)。可行函數(shù)也有兩個(gè)特征:一是其形參數(shù)量與本次調(diào)用提供的實(shí)參數(shù)量相等,二是每個(gè)實(shí)參的類型與對(duì)應(yīng)的形參類型相同,或者能轉(zhuǎn)換成形參的類型。
如果沒有找到可行函數(shù),編譯器將報(bào)告無(wú)匹配函數(shù)的錯(cuò)誤。
3)第三步是從可行函數(shù)選擇與本次調(diào)用最匹配的函數(shù)。實(shí)參類型與形參類型越接近,它們匹配得越好。
如果沒有最匹配的函數(shù),則該調(diào)用錯(cuò)誤。
調(diào)用重載函數(shù)時(shí)應(yīng)盡量避免強(qiáng)制類型轉(zhuǎn)換。如果在實(shí)際應(yīng)用中確實(shí)需要強(qiáng)制類型轉(zhuǎn)換,則說(shuō)明我們?cè)O(shè)計(jì)的形參集合不合理。 -
為了確定最佳匹配,編譯器將實(shí)參類型到形參類型的轉(zhuǎn)換劃分成幾個(gè)等級(jí),具體排序如下所示:
void ff(int);
void ff(short);
ff('a'); // char提升成int;調(diào)用f(int)
void manip(long);
void manip(float);
manip(3.14); // 錯(cuò)誤:二義性調(diào)用
Record lookup(Account &);
Record lookup(const Account&);
const Account a;
Account b;
lookup(a); // 調(diào)用lookup(const Account &)
lookup(b); // 調(diào)用lookup(Account &)
- 函數(shù)指針
函數(shù)的類型由它的返回類型和形參類型共同決定,與函數(shù)名無(wú)關(guān)。
1)函數(shù)或者函數(shù)指針都可以作為形參,函數(shù)類型實(shí)際上被當(dāng)成了指針使用
2)作為返回類型時(shí),編譯器不會(huì)自動(dòng)將函數(shù)返回類型當(dāng)成對(duì)應(yīng)的指針類型處理。
using F = int(int*, int); // F是函數(shù)類型,不是指針
using PF = int(*)(int*, int); //PF是指針類型
PF f1(int); // 正確
F f1(int); // 錯(cuò)誤,不能返回函數(shù)類型
F *f1(int); //正確
上面等價(jià)于下面的直接聲明:
int (*f1(int))(int*, int);
auto f1(int) -> int (*) (int*, int);
- decltype作用于某個(gè)函數(shù)時(shí),它返回函數(shù)類型而非指針類型。
string:size_type sumLength(const string&, const string&);
string:size_type largerLength(const string&, const string&);
//下面的函數(shù)返回上面兩個(gè)函數(shù)的指針的一個(gè)
decltype(sumLength) *getFcn(const string&); // decltype返回的是函數(shù)類型
1.6 類
- 類的基本思想是數(shù)據(jù)抽象和封裝。數(shù)據(jù)抽象是一種依賴于接口和實(shí)現(xiàn)分離的編程技術(shù)。封裝實(shí)現(xiàn)了類的接口和實(shí)現(xiàn)的分離。
- 成員函數(shù)通過一個(gè)名為this的額外的隱式參數(shù)來(lái)訪問調(diào)用它的那個(gè)對(duì)象。當(dāng)我們調(diào)用一個(gè)成員函數(shù)時(shí),用請(qǐng)求該函數(shù)的對(duì)象地址初始化this。
- 把const關(guān)鍵字放在成員函數(shù)的參數(shù)列表之后,表示this是一個(gè)指向常量的指針。默認(rèn)情況下,this的類型是指向類類型非常量版本的常量指針。因此常量成員函數(shù)不能改變調(diào)用它的對(duì)象的內(nèi)容。
std::string isbn() const {} // 將this聲明成 const Sales_data *const,指向常量對(duì)象
- 編譯器分兩步處理:首先編譯成員的聲明,然后才輪到成員函數(shù)體。因此,成員函數(shù)體可以隨意使用類中的其他成員而無(wú)須在意這些成員出現(xiàn)的次序。
- 函數(shù)如果定義在類的內(nèi)部,默認(rèn)是內(nèi)聯(lián)的,如果定義在類的外部,默認(rèn)是不內(nèi)聯(lián)的。
- 一般來(lái)說(shuō),當(dāng)定義的函數(shù)類似于某個(gè)運(yùn)算符時(shí),應(yīng)該令該函數(shù)的行為盡量模仿這個(gè)運(yùn)算符。內(nèi)置的賦值運(yùn)算符把它的左側(cè)運(yùn)算對(duì)象當(dāng)成左值返回,因此為了保持一致,combine必須返回引用類型。
Sales_data& Sales_data::combine(const Sales_data &rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
- 一般來(lái)說(shuō),如果非成員函數(shù)是類接口的組成部分,則這些函數(shù)的聲明應(yīng)該與類在同一個(gè)頭文件內(nèi)。
IO類型屬于不能被拷貝的類型,因此只能通過引用來(lái)傳遞。讀取和寫入的操作會(huì)改變流的內(nèi)容,所以是普通引用。
istream &read(istream &is, Sales_data &item) {
double price = 0;
...
return is;
}
- 如果類沒有顯式地定義構(gòu)造函數(shù),那么編譯器就會(huì)隱式地定義一個(gè)默認(rèn)構(gòu)造函數(shù)——合成的默認(rèn)構(gòu)造函數(shù)。
1)如果存在類內(nèi)初始值,用它來(lái)初始化成員
2)否則,默認(rèn)初始化該成員。 - 某些類不能依賴合成的默認(rèn)構(gòu)造函數(shù)
1)沒有聲明任何構(gòu)造函數(shù)是才自動(dòng)生成
2)合成的默認(rèn)構(gòu)造函數(shù)可能執(zhí)行錯(cuò)誤的操作,利于默認(rèn)初始化的值是未定義
3)不能為某些類合成默認(rèn)的構(gòu)造函數(shù)。類中含有一個(gè)其他類類型的成員,且這個(gè)成員的類型沒有默認(rèn)構(gòu)造函數(shù)。 - 定義默認(rèn)構(gòu)造函數(shù)
1)不接受任何實(shí)參,所以是一個(gè)默認(rèn)構(gòu)造函數(shù)
2)C++11中,如果需要默認(rèn)行為,可以在參數(shù)列表后加上= default
Sales_data() = default;
- 盡管編譯器能夠合成拷貝、賦值和銷毀操作,但是對(duì)于某些類來(lái)說(shuō)合成版本無(wú)法正常工作。特別是,當(dāng)類需要分配類對(duì)象之外的資源時(shí)。
- 友元
類可以允許其他類或者函數(shù)訪問它的非公有成員,方法是令其他類或函數(shù)稱為它的友元。
友元聲明只能出現(xiàn)在類定義的內(nèi)部,即以friend關(guān)鍵字開頭的函數(shù)聲明。
- 可變數(shù)據(jù)成員mutable
不會(huì)是const,即使它是const對(duì)象的成員。一個(gè)const成員函數(shù)可以改變可變成員的值。
- 類內(nèi)初始值必須使用=的初始化形式或者花括號(hào)括起來(lái)的直接初始化形式。
- 一個(gè)常量成員函數(shù)如果以引用的形式返回*this,那么它的返回類型將是常量引用。
- 通過區(qū)分成員函數(shù)是否是const,可以對(duì)其進(jìn)行重載。
- 在實(shí)踐中,設(shè)計(jì)良好的C++代碼常常包含大量的小函數(shù)。
- 不完全類型:聲明之后定義之前都是不完全類型
不完全類型只能在非常有限的情景下使用:可以定義指向這種類型的指針或引用,也可以聲明(但是不能定義)以不完全類型作為參數(shù)或者返回類型的函數(shù)。
- 友元關(guān)系不存在傳遞性。
- 要想令某個(gè)成員函數(shù)作為友元,必須仔細(xì)組織程序的結(jié)構(gòu)以滿足聲明和定義的彼此依賴關(guān)系
1)首先定義Window_mgr類,其中聲明clear函數(shù),但是不能定義它。在clear使用Screen的成員之前必須先聲明Screen
2)接下來(lái)定義Screen,包括對(duì)于clear的友元聲明
3)最后定義clear,此時(shí)才可以使用Screen的成員
class Screen{
friend void Window_mgr::clear(ScreenIndex);
};
- 對(duì)于定義在類內(nèi)部的成員函數(shù),解析其中名字的方式:
1)首先編譯成員的聲明
2)直到類全部可見后才編譯函數(shù)體 - 成員函數(shù)中使用的名字按照如下方式解析:
1)首先,在成員函數(shù)內(nèi)查找該名字的聲明。只有在函數(shù)使用之前出現(xiàn)的聲明才被考慮
2)如果在成員函數(shù)內(nèi)沒有找到,則在類內(nèi)繼續(xù)查找,類的所有成員都可以被考慮
3)如果類內(nèi)沒有找到該名字的聲明,在成員函數(shù)定義之前的作用域內(nèi)繼續(xù)查找
- 如果成員是const、引用,或者屬于某種未提供默認(rèn)構(gòu)造函數(shù)的類類型,我們必須通過構(gòu)造函數(shù)初始值列表為這些成員提供初值。
建議使用構(gòu)造函數(shù)初始值,而非賦值。 - 最好令構(gòu)造函數(shù)初始值的順序與成員聲明的順序保持一致。
- 委托構(gòu)造函數(shù),一個(gè)委托構(gòu)造函數(shù)使用它所屬類的其他構(gòu)造函數(shù)執(zhí)行自己的初始化過程,或者說(shuō)它把自己的一些或者全部職責(zé)委托給了其他構(gòu)造函數(shù)。
class Sales_data {
public:
Sales_data(std::string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt *price) {}
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);}
};
- 在實(shí)際中,如果定義了其他構(gòu)造函數(shù),那么最好也提供一個(gè)默認(rèn)構(gòu)造函數(shù)。
- 轉(zhuǎn)換構(gòu)造函數(shù):通過一個(gè)實(shí)參調(diào)用的構(gòu)造函數(shù)定義了一條從構(gòu)造函數(shù)的參數(shù)類型向類類型隱式轉(zhuǎn)換的規(guī)則。
編譯器只會(huì)自動(dòng)執(zhí)行一步類型轉(zhuǎn)換。
explicit抑制構(gòu)造函數(shù)定義的隱式轉(zhuǎn)換,并且只能在類內(nèi)聲明構(gòu)造函數(shù)時(shí)使用explicit,在外部定義時(shí)不應(yīng)重復(fù)。
- 聚合類——struct,所有成員都是可以直接訪問的
-
字面值常量類
- 類的靜態(tài)成員:需要一些成員與類本身直接相關(guān),而不是與類的各個(gè)對(duì)象保持聯(lián)系。
類的靜態(tài)成員存在于任何對(duì)象之外,對(duì)象中不包含任何與靜態(tài)數(shù)據(jù)成員有關(guān)的數(shù)據(jù)。
靜態(tài)成員函數(shù)不包含this指針,不能聲明成const。
static只出現(xiàn)在類內(nèi)部的聲明語(yǔ)句中。
要想確保對(duì)象只定義次,最好的辦法是把靜態(tài)數(shù)據(jù)成員的定義與其他非內(nèi)聯(lián)函數(shù)的定義放在同一文件中。
即使一個(gè)常量靜態(tài)數(shù)據(jù)成員在類內(nèi)部被初始化了,通常情況下也應(yīng)該在類的外部定義一下該成員。
靜態(tài)成員可以是不完全類型;靜態(tài)成員可以作為默認(rèn)實(shí)參,非靜態(tài)成員不可以。



















