4.高級主題
4.1 標(biāo)準(zhǔn)庫特殊設(shè)施
-
tuple類型
希望將一些數(shù)據(jù)合成單一對象前硫,但又不想麻煩地定義一個新數(shù)據(jù)結(jié)構(gòu)來表示這些數(shù)據(jù)時听哭,tuple是非常有用的痊远。
由于tuple定義了<和==運算符 ,可以將tuple序列傳遞給算法刚陡,并且在無序容器將tuple作為關(guān)鍵字類型惩妇。
- 使用tuple返回多個值
-
bitset類型定義和初始化
注意:string的下標(biāo)編號與bitset恰好相反。string下標(biāo)最大用來初始化bitset的低位筐乳。
-
bitset操作
bitset支持位運算符歌殃。
-
正則表達(dá)式組件庫
默認(rèn)情況下趁怔,regex使用的正則表達(dá)式語言是ECMAScript庐舟,[[:alpha:]]匹配任意字母。
注意:一個正則表達(dá)式語法是否正確是在運行時解析的续室。正則表達(dá)式的編譯是一個非常慢的操作勃刨,特別是在使用了擴(kuò)展的正則表達(dá)式語法或是復(fù)雜的正則表達(dá)式時波材。
如果存在錯誤,標(biāo)準(zhǔn)庫會拋出一個類型為regex_error的異常身隐。
使用的RE庫類型必須與輸入序列類型匹配廷区。
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*" ;
regex r(pattern);
smatch results;
strint test_str = "receipt freind theif receive";
if (regex_search(test_str, results, r)) {
cout << results.str() << endl;
}
-
匹配與Regex迭代器類型
下面的end_it是一個空sregex_iterator,起到尾后迭代器的作用贾铝。
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*" ;
regex r(pattern, regex::icase);
for (sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it) {
cout << it->str() << endl;
}
for (sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it) {
auto pos = it->prefix().length();
pos = pos > 40 ? pos - 40 : 0;
cout << it->prefix().str().substr(pos)
<< "\n\t\t>>> " << it->str() << " <<<\n"
<< it->suffix().str().substr(0, 40)
<< endl;
}
- 使用子表達(dá)式
一個子表達(dá)式是模式的一部分隙轻,正則表達(dá)式語法通常用括號表示子表達(dá)式。
匹配對象除了提供匹配整體的相關(guān)信息外忌傻,還提供訪問模式中每個子表達(dá)式的能力大脉。子匹配是按位置來訪問的搞监。第一個子匹配位置是0水孩,表示整個模式對應(yīng)的匹配,隨后是每個子表達(dá)式對應(yīng)的匹配琐驴。
//r有兩個字表達(dá)式俘种,第一個表示文件名,第二個表示擴(kuò)展名
//foo.cpp
//results.str(0)保存foo.cpp
//results.str(1)保存foo
//results.str(2)保存cpp
regex r("([[:alnum:]]+)\\.(cpp|cxx|cc)$", regex::icase);
if (regex_search(filename, results, r)) {
cout << results.str(1) <<endl;
}
ECMAScript正則表達(dá)式語言的一些特性:
反斜線是C++的特殊字幕绝淡,所以需用一個額外的反斜線來告知C++需要一個反斜線而不是一個特殊符號宙刘。
模式的子表達(dá)式分析:
子匹配操作:
-
使用regex_replace
在輸入序列中查找并替換一個正則表達(dá)式。
fmt中用一個符號$跟子表達(dá)式的索引號來表示一個特定的子表達(dá)式牢酵。
匹配和格式化標(biāo)識的類型為match_flag_type悬包,定義在std::regex_constants命名空間里。
默認(rèn)情況下馍乙,regex_replace輸出整個輸入序列布近。未與正則表達(dá)式匹配的部分會原樣輸出:匹配的部分按格式字符串指定的格式輸出垫释。
int main() {
string phone =
"(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{3})";
regex r(phone);
smatch m;
string s;
string fmt = "$2.$5.$7";
while (getline(cin, s)) {
cout << regex_replace(s, r, fmt) << endl;
}
return 0;
}
- rand庫函數(shù)有一些問題:很多程序需要不同范圍的隨機(jī)數(shù),或者需要隨機(jī)浮點數(shù)撑瞧,一些程序需要非均勻分布的數(shù)棵譬。為了解決這些問題而試圖轉(zhuǎn)換rand生成的隨機(jī)數(shù)的范圍、類型或分布時预伺,常常會引入非隨機(jī)性订咸。
-
定義在頭文件random中的隨機(jī)數(shù)庫通過一些協(xié)作類來解決這些問題:隨機(jī)數(shù)引擎類和隨機(jī)數(shù)分布類。
C++程序不應(yīng)該使用庫函數(shù)rand酬诀,而應(yīng)該使用default_random_engine類和恰當(dāng)?shù)姆植碱悓ο蟆?/p>
-
隨機(jī)數(shù)引擎和分布
隨機(jī)數(shù)引擎是函數(shù)對象類脏嚷,調(diào)用運算符不接受參數(shù)并返回一個隨機(jī)unsigned整數(shù)。
標(biāo)準(zhǔn)庫定義了多個隨機(jī)數(shù)引擎類瞒御,區(qū)別在于性能和隨機(jī)性質(zhì)質(zhì)量不同然眼。
在大多數(shù)場合,隨機(jī)數(shù)引擎輸出的原始隨機(jī)數(shù)是不能直接使用的葵腹,正確轉(zhuǎn)換隨機(jī)數(shù)的范圍是極其困難的高每。
分布類型也是函數(shù)對象類,分布類型定義了一個調(diào)用運算符践宴,接受一個隨機(jī)數(shù)引擎作為參數(shù)鲸匿。分布對象使用它的引擎參數(shù)生成隨機(jī)數(shù),將其映射到指定的分布阻肩。
隨機(jī)數(shù)發(fā)生器——是指分布對象和引擎對象的組合带欢。
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e;
for (size_t i = 0; i < 10; ++i) {
cout << u(e) << " ";
}
- 程序每次調(diào)用生成不同隨機(jī)結(jié)果的兩種方法
1)一個給定的隨機(jī)數(shù)發(fā)生器一直會生成相同的隨機(jī)數(shù)序列。一個函數(shù)如果定義了局部的隨機(jī)數(shù)發(fā)生器烤惊,應(yīng)該將其(包括引擎和分布對象)定義為static的乔煞。否則,每次調(diào)用函數(shù)都會生成相同的序列柒室。
或者再調(diào)用隨機(jī)數(shù)發(fā)生器的外面定義渡贾,在里面使用。
2)提供一個種子來達(dá)到這一目的雄右。
種子是一個數(shù)值空骚,引擎可以利用它從序列中一個新位置重新開始生成隨機(jī)數(shù)。
利用時間作為種子擂仍,time返回以秒計的時間囤屹,適用于間隔為秒級或更長的應(yīng)用。
default_random_engine e1(time(0));
-
分布類型所支持的操作
- 生成隨機(jī)實數(shù)
使用uniform_real_distribution - 生成非均勻分布的隨機(jī)數(shù)
正態(tài)分布normal_distribution - bernoulli_distribution類逢渔,非模板
-
IO庫再探——格式控制
標(biāo)準(zhǔn)庫定義了一組操縱符來修改流的格式狀態(tài)肋坚,操縱符返回所處理的流對象。
操縱符用于兩大類輸出控制:
1)控制數(shù)值的輸出形式
2)控制補(bǔ)白的數(shù)量和位置
當(dāng)操作符改變流的格式狀態(tài)時,通常改變后的狀態(tài)對所有后續(xù)IO都生效智厌,因此大多數(shù)操縱符多是設(shè)置/復(fù)原成對的粟判。
-
IO庫再探——未格式化IO
標(biāo)準(zhǔn)庫還提供一組低層操作,支持未格式化IO峦剔。這些操作允許將一個流當(dāng)做一個無解釋的字節(jié)序列來處理档礁。
應(yīng)該在任何后續(xù)未格式化輸入操作之前調(diào)用gcount,peek unget putback會將gcount的返回值置為0.
注意:低層函數(shù)容易出錯吝沫。因此如果可以使用標(biāo)準(zhǔn)庫提供的高層類型操作呻澜,就應(yīng)該使用它們,更加安全惨险。 -
IO庫再探——隨機(jī)訪問
隨機(jī)IO本質(zhì)上依賴于系統(tǒng)羹幸。
由于istream和ostream類型不支持隨機(jī)訪問,所以討論的主要是fstream和sstream類型的隨機(jī)訪問
在一個流中只維護(hù)單一的標(biāo)記——并不存在獨立的讀標(biāo)記和寫標(biāo)記辫愉。因此只要在讀寫操作間切換栅受,必須進(jìn)行seek操作來重定位標(biāo)記。
4.2 用于大型程序的工具
大規(guī)模應(yīng)用程序的特殊要求:
1)在獨立開發(fā)的子系統(tǒng)之間協(xié)同處理錯誤的能力)——異常處理
2)使用各種庫(可能包括獨立開發(fā)的庫)進(jìn)行協(xié)同開發(fā)的能力——命名空間
3)對比較復(fù)雜的應(yīng)用概念建模的能力——多重繼承異常處理
異常處理機(jī)制允許程序中獨立開發(fā)的部分能夠在運行時對出現(xiàn)的問題進(jìn)行通信并做出相應(yīng)的處理恭朗。
異常使得我們能夠?qū)栴}的檢測與解決過程分離開來屏镊。拋出異常
通過拋出一條表達(dá)式來引發(fā)一個異常。被拋出的表達(dá)式的類型以及當(dāng)前的調(diào)用鏈共同決定了哪段處理代碼將用來處理該異常痰腮。被選中的處理代碼是在調(diào)用鏈中與拋出對象類型匹配的最近的處理代碼而芥。
執(zhí)行throw時,程序控制權(quán)轉(zhuǎn)移到catch模塊膀值。表明:
1)沿著調(diào)用鏈的函數(shù)可能會提早退出
2)一旦程序開始執(zhí)行異常處理代碼棍丐,則沿著調(diào)用鏈創(chuàng)建的對象將被銷毀。
查找匹配的catch語句:棧展開沧踏。棧展開過程沿著嵌套函數(shù)的調(diào)用鏈不斷查找歌逢,直到找到了與異常匹配的catch子句為止;或者也可能一直沒有找到匹配的catch翘狱,則退出主函數(shù)后查找過程終止秘案,也會終止當(dāng)前的程序。
找到catch并執(zhí)行完后盒蟆,找到與try關(guān)聯(lián)的最后一個catch子句之后的點踏烙,從這里繼續(xù)執(zhí)行。如果異常發(fā)生在構(gòu)造函數(shù)历等,或者數(shù)組、標(biāo)準(zhǔn)庫容器的元素初始化的過程中辟癌,應(yīng)該確保已構(gòu)造的元素被正確地銷毀寒屯。
在函數(shù)中負(fù)責(zé)釋放資源的代碼可能被跳過(在此delete之前發(fā)生異常)。如果用類來控制資源的分配,就能保證資源能被正確地釋放寡夹。
出于棧展開可能使用析構(gòu)函數(shù)的考慮处面,析構(gòu)函數(shù)不應(yīng)該拋出不能被它自身處理的異常。實際中菩掏,析構(gòu)函數(shù)僅僅是釋放資源魂角,不太可能拋出異常,所有標(biāo)準(zhǔn)庫類型都能確保它們的析構(gòu)函數(shù)不會引發(fā)異常智绸。
一旦在棧展開過程中析構(gòu)函數(shù)拋出了異常野揪,并且析構(gòu)函數(shù)自身沒能捕獲到該異常,則程序?qū)⒈唤K止瞧栗。編譯器使用異常拋出表達(dá)式對異常對象進(jìn)行拷貝初始化斯稳。
異常對象位于由編譯器管理的空間中,確保無論最終調(diào)用的是哪個catch子句都能訪問該空間迹恐。當(dāng)異常處理完挣惰,異常對象被銷毀。
當(dāng)拋出一條表達(dá)式時殴边,該表達(dá)式的靜態(tài)編譯時類型決定了異常對象的類型憎茂。
拋出指針要求在任何對應(yīng)的處理代碼存在的地方,指針?biāo)傅膶ο蠖急仨毚嬖凇?/p>
捕獲異常
聲明的類型決定了處理代碼所能捕獲的異常類型锤岸。這個類型必須是完全類型唇辨。可以是左值引用能耻,但不能使右值引用赏枚。
如果catch接受的異常與某個繼承體系有關(guān),則最好將該catch的參數(shù)定義成引用類型晓猛。異常聲明的靜態(tài)類型將決定catch語句所能執(zhí)行的操作饿幅,如果catch的參數(shù)時基類類型,則catch無法使用派生類特有的任何成員戒职。-
查找匹配的處理代碼
派生類異常的處理代碼出現(xiàn)在基類異常的處理代碼之前栗恩。
只有如下類型轉(zhuǎn)換是允許的,其他的要求異常的類型和catch聲明的類型精確匹配:
-
重新拋出
一個單獨的catch語句不能完整地處理某個異常洪燥】某樱可以通過重新拋出的操作將異常傳遞給另外一個catch語句。
重新拋出是一個空的throw;語句捧韵。
捕獲所有異常的處理代碼
catch(...)捕獲所有的異常市咆,與其他catch一起出現(xiàn),放在最后再来。函數(shù)try語句塊與構(gòu)造函數(shù)
要想處理構(gòu)造函數(shù)初始值拋出的異常蒙兰,必須將構(gòu)造函數(shù)寫成函數(shù)try語句塊的形式磷瘤。
如果在初始化構(gòu)造函數(shù)的參數(shù)發(fā)生了異常,則該異常屬于調(diào)用表達(dá)式的一部分搜变,并將在調(diào)用者所在的上下文處理采缚。不屬于構(gòu)造函數(shù)執(zhí)行的異常。
template <typename T>
Blob<T>::Blob(std::initilalizer_list<T> il) try:
data(std::make_shared<std::vector<T>>(il)) {
//空函數(shù)體
} catch(const std::bad_alloc &e) {
handle_out_of_memory(e);
}
- noexcept異常說明
預(yù)先知道函數(shù)不會拋出異常的益處:
1)有助于簡化調(diào)用該函數(shù)的代碼
2)編譯器能執(zhí)行某些特殊的優(yōu)化操作
noexcept要么出現(xiàn)在該函數(shù)的所有聲明語句和定義語句中挠他,要么一次也不出現(xiàn)扳抽。
一旦一個noexcept函數(shù)拋出了異常,程序就會調(diào)用terminate以確保不在運行時拋出異常的承諾殖侵。 - noexcept運算符
返回一個bool類型的右值常量表達(dá)式贸呢,用于表示給定的表達(dá)式是否會拋出異常。
noexcept(e)
//當(dāng)e調(diào)用的所有函數(shù)都做了不拋出說明
//且e本身不含有任何throw語句
//為true
//否則為false
void f() noexcept(noexcept(g())); //f和g的異常說明一致
//里面的noexcept是運算符
//外面的是異常說明符
異常說明與指針愉耙、虛函數(shù)和拷貝控制
函數(shù)指針及該指針指向的函數(shù)必須具有一致的異常說明贮尉。不拋出的指針只能指向不拋出異常的函數(shù)∑友兀可能拋出異常的指針可以指向任何函數(shù)猜谚。
虛函數(shù)與派生的虛函數(shù)類似。
合成拷貝控制成員時赌渣,如果所有成員和基類的所有操作都承諾不拋出異常魏铅,則合成的成員是noexcept。否則是noexcept(false)坚芜。-
異常類的層次
運行是錯誤表示的是只有在程序運行時才能檢測到的錯誤览芳;
邏輯錯誤一般是可以在程序代碼中發(fā)現(xiàn)的錯誤。
命名空間
多個庫將名字放置在全局名字空間中將引發(fā)命名空間污染鸿竖。
命名空間既可以定義在全局作用域內(nèi)沧竟,也可以定義在其他命名空間中。
命名空間作用域后面無需分號缚忧。
每個命名空間都是一個作用域悟泵。
命名空間可以是不連續(xù)的。
命名空間的組織方式類似于管理自定義類和函數(shù)的方式:
1)命名空間的一部分成員的作用是定義類闪水,以及聲明作為類接口的函數(shù)及對象糕非,則這些成員應(yīng)該置于頭文件中,這些頭文件將被包含在使用了這些成員的文件中
2)命名空間成員的定義部分則置于另外的源文件中
注意:通常不把#include放在命名空間內(nèi)部球榆。模板特例化
模板特例化必須定義在原始模板所屬的命名空間中朽肥。只要在命名空間中聲明了特例化,就可以在命名空間外部定義它持钉。全局命名空間
::member_name表示全局命名空間中的一員衡招。內(nèi)聯(lián)命名空間
內(nèi)聯(lián)命名空間中的名字可以被外層命名空間直接使用。
當(dāng)應(yīng)用程序的代碼在一次發(fā)布和另一次發(fā)布之間發(fā)生了改變時右钾,常常會用到內(nèi)聯(lián)命名空間蚁吝。
代碼可以直接獲得新版本的成員旱爆,如果想使用老版本的成員舀射,必須加上完整的外層命名空間名字窘茁。未命名的命名空間
在里面定義的變量擁有靜態(tài)生命周期。
一個未命名的命名空間可以在文件內(nèi)不連續(xù)脆烟,但不能跨越多個文件山林。每個文件可以定義自己的未命名空間,并且相互無關(guān)聯(lián)邢羔。
未命名空間的名字直接使用驼抹。
未命名空間定義的名字作用域與該命名空間所在的作用域相同。所以在最外層的名字一定要與全局作用域的名字有所區(qū)別拜鹤。
注意:在文件中進(jìn)行靜態(tài)聲明的做法已經(jīng)被C++標(biāo)準(zhǔn)取消了框冀,現(xiàn)在的做法是使用未命名的命名空間。使用命名空間成員
1)using聲明
一條using聲明一次只引入命名空間的一個成員敏簿。有效范圍從using聲明的地方開始明也,到其所在的作用域結(jié)束為止。在此過程惯裕,外層作用域的同名實體將被隱藏温数。
using聲明語句可以出現(xiàn)在全局作用域、局部作用域蜻势、命名空間作用域以及類作用域撑刺。在類作用域中,聲明語句只能指向基類成員握玛。
2)命名空間的別名
可以指向一個嵌套的命名空間够傍。
3)using指示
所有名字都是可見的。using指示可以出現(xiàn)在全局作用域挠铲、局部作用域和命名空間作用域冕屯,不能出現(xiàn)在類的作用域中。
using指示如果不做控制市殷,會重新引入名字沖突問題愕撰。-
using指示與作用域
using指示一般被看作是出現(xiàn)在最近的外層的作用域中。
未加限定的相同名字會產(chǎn)生二義性錯誤醋寝,但是這種沖突是允許的搞挣,使用時只要明確指出名字的版本即可。
注意:避免using指示音羞,頭文件最多只能在它的函數(shù)或命名空間使用using指示或using聲明囱桨。
在命名空間本身的實現(xiàn)文件中可以使用using指示。 類嗅绰、命名空間與作用域
當(dāng)給函數(shù)傳遞一個類類型的對象時舍肠,除了在常規(guī)的作用域查找外還會查找實參所屬的命名空間搀继。這一例外對于傳遞類的引用或指針的調(diào)用同樣有效。
查找規(guī)則的這個例外允許概念上作為類接口一部分的非成員函數(shù)無需單獨地using聲明就能被程序使用翠语。std::move和std::forward
由于右值引用新參可以匹配任何類型叽躯,所以move/forward名字沖突會比其他標(biāo)準(zhǔn)庫函數(shù)的沖突頻繁得多。
沖突很多肌括,move/forward執(zhí)行的是非常特殊的類型操作点骑,所以應(yīng)用程序?qū)iT修改函數(shù)原有的行為的概率非常小。因此建議使用完整版本std::move谍夭。-
友元聲明與實參相關(guān)的查找
當(dāng)類聲明一個友元時黑滴,該友元并沒有使得友元本身可見。
但是涉及到類時紧索,會有例外袁辈。(下面的例子只是為說明問題)
重載與命名空間
命名空間對函數(shù)匹配過程的影響:
1)using聲明或using指示將某些函數(shù)添加到候選的函數(shù)集
using聲明將該函數(shù)的所有版本都引入到當(dāng)前作用域,引入形參完全相同的函數(shù)會報錯珠漂。
using指示引入完全相同形參的函數(shù)不報錯晚缩,但是要區(qū)分版本。
2)對于接受類類型實參的函數(shù)來說甘磨,名字查找將在實參所屬的命名空間中進(jìn)行橡羞。這些命名空間中與被調(diào)用函數(shù)同名的函數(shù)都將被添加到候選集中
-
多重繼承與虛繼承
多重繼承:多個基類相互交織產(chǎn)生的細(xì)節(jié)可能會帶來錯綜復(fù)雜的設(shè)計問題與實現(xiàn)問題。
class Bear: pulic ZooAnimal {};
class Panda: public Bear, pulic Endangered {};
-
假如從多個基類繼承了相同的構(gòu)造函數(shù)(即形參列表完全相同)济舆,則產(chǎn)生錯誤卿泽。此時該類必須為該構(gòu)造函數(shù)定義它自己的版本。
類型轉(zhuǎn)換與多個基類
可以令某個可訪問基類的指針或引用直接指向一個派生類對象滋觉。
編譯器不會在派生類向基類的幾種轉(zhuǎn)換中進(jìn)行比較和選擇签夭,轉(zhuǎn)換到任意一種基類都一樣好。此時如果有不同基類引用或指針為形參的重載函數(shù)椎侠,會產(chǎn)生二義性錯誤第租。
對象、指針和引用的靜態(tài)類型決定了能夠使用哪些成員我纪。多重繼承下的類作用域
在多重繼承的情況下慎宾,查找過程會在所有直接基類中同時進(jìn)行。如果名字在多個基類中都被找到浅悉,不加前綴限定符直接使用該名字將引發(fā)二義性趟据。
避免潛在的二義性最好的方法是在派生類中為該函數(shù)定義一個新版本。-
虛繼承
默認(rèn)情況下术健,派生類中含有繼承鏈上每個類對于的子部分汹碱。某個類在派生過程中出現(xiàn)多次,則派生類將包含該類的多個子對象荞估。這種情況可能有問題咳促。(iostream)
虛繼承機(jī)制可以解決該問題稚新。不論虛基類在繼承體系中出現(xiàn)多少次,在派生類中都只包含唯一一個共性的虛基類子對象跪腹。
class Raccon: public virtual ZooAnimal {};
class Bear: virtual public ZooAnimal {};
-
構(gòu)造函數(shù)與虛繼承
虛基類總是由最底層的派生類初始化褂删。
1)創(chuàng)建Bear或Raccoon對象,此時它們已經(jīng)位于派生的最底層
2)創(chuàng)建Panda對象
如果Panda沒有顯式初始化ZooAnimal基類尺迂,則ZooAnimal的默認(rèn)構(gòu)造函數(shù)將被調(diào)用笤妙。如果沒有默認(rèn)構(gòu)造函數(shù)冒掌,則代碼將發(fā)生錯誤噪裕。
虛基類總是先于非虛基類構(gòu)造,與它們在繼承體系中的次序和位置無關(guān)股毫。
4.3 特殊工具與技術(shù)
- 控制內(nèi)存分配
某些應(yīng)用程序?qū)?nèi)存分配有特殊需求膳音,可以重載new和delete以控制內(nèi)存分配的過程。 - new操作的三個步驟
1)調(diào)用一個operator new或operator new[]的標(biāo)準(zhǔn)庫函數(shù)铃诬,該函數(shù)分配一塊足夠大的祭陷、原始的、未命名的內(nèi)存空間
2)編譯器運行相應(yīng)的構(gòu)造函數(shù)以構(gòu)造這些對象
3)對象被分配了空間并構(gòu)造完成趣席,返回一個指向該對象的指針 - delete操作的兩個步驟
1)對所指對象或數(shù)組的元素執(zhí)行對應(yīng)的析構(gòu)函數(shù)
2)編譯器調(diào)用operator delete或者operator delete[]的標(biāo)準(zhǔn)庫函數(shù)釋放內(nèi)存空間 - 應(yīng)用程序可以在全局作用域中定義operator new和operator delete函數(shù)兵志,也可以定義為成員函數(shù)。根據(jù)作用域查找規(guī)則宣肚,優(yōu)先使用自定義版本想罕。
-
標(biāo)準(zhǔn)庫定義的8個重載版本
- 重載相應(yīng)的運算符時,如delete霉涨,必須使用noexcept異常說明符指定其不拋出異常按价;
自定義版本必須位于全局或類作用域;
當(dāng)定義在類作用域時笙瑟,隱式是靜態(tài)的楼镐。因為用在對象構(gòu)造之前、銷毀之后往枷。不能操縱類的任何數(shù)據(jù)成員框产;
用到自定義的new表達(dá)式時必須使用new的定位形式。
void *operator new(size_t, void*); //不允許重新定義這個版本错洁。
void *operator new(size_t size) {
if (void *mem = malloc(size)) {
return mem;
} else {
throe bad_alloc();
}
}
void operator delete(void *mem) noexcept { free(mem); }
總之:我們不能改變new和delete運算符的基本含義秉宿,只能改變operator new和operator delete改變內(nèi)存分配的方式。new和delete會調(diào)用operator new和operator delete墓臭。
-
定位new表達(dá)式
operator new和operator delete和allocator類的allocate與deallocate成員非常相似蘸鲸,它們負(fù)責(zé)分配或釋放內(nèi)存空間,但是不會構(gòu)造或銷毀對象窿锉。
operator new分配的內(nèi)存空間無法使用construct函數(shù)構(gòu)造對象酌摇,需要使用定位new形式構(gòu)造對象膝舅。
place_address必須是一個指針,initializers提供一個可能為空的以逗號分隔的初始值列表窑多,用于構(gòu)造新分配的對象仍稀。
當(dāng)僅通過一個地址值調(diào)用時,定位new使用void *operator new(size_t, void*)在指定的地址初始化對象以完成整個工作埂息。也即定位new表達(dá)式構(gòu)造對象而不分配內(nèi)存技潘。 construc的指針必須指向同一個allocator對象分配的空間,定位new的指針無須指向operator new分配的內(nèi)存千康,甚至不需要指向動態(tài)內(nèi)存享幽。
需要顯式的析構(gòu)函數(shù)調(diào)用,與destroy類似拾弃。與destory一樣值桩,析構(gòu)函數(shù)會銷毀對象,但不會釋放內(nèi)存豪椿。
string *sp = new string("a value");
sp->~string();
運行時類型識別(run-time type identification, RTTI)
該功能由兩個運算符實現(xiàn):
1)typeid奔坟,用于返回表達(dá)式類型
2)dynamic_cast,用于將基類的指針或引用安全地轉(zhuǎn)換成派生類的指針或引用
當(dāng)將這兩個運算符用于某種類型指針或引用搭盾,并且該類型含有虛函數(shù)時咳秉,運算符將使用指針或引用所綁定對象的動態(tài)類型。這兩個運算符特別適用于以下情況:使用基類對象的指針或引用執(zhí)行某個派生類操作并且該操作不是虛函數(shù)鸯隅。
使用RTTI運算符蘊(yùn)含著更多潛在的風(fēng)險:程序員必須清楚地知道轉(zhuǎn)換的目標(biāo)類型并且檢查類型轉(zhuǎn)換是否被成功執(zhí)行澜建。
在可能的情況下,最好定義虛函數(shù)而非直接接管類型管理的重任滋迈。dynamic_cast運算符
在下面所有形式中霎奢,e類型必須符合以下三種條件的任意一個:
1)e的類型是模板type的公有派生類
2)e的類型是目標(biāo)type的公有基類
3)e是type的類型
指針轉(zhuǎn)換事變,返回0饼灿;引用轉(zhuǎn)換失敗拋出bad_cast異常幕侠。
在條件部分執(zhí)行dynamic_cast操作可以確保類型轉(zhuǎn)換和結(jié)果檢查在同一條表達(dá)式完成。
可以對一個空指針執(zhí)行dynamic_cast碍彭,結(jié)果是所需類型的空指針晤硕。
//type必須是一個類類型,通常該類型應(yīng)該含有虛函數(shù)
dynamic_cast<type*>(e) //e必須是一個有效指針
dynamic_cast<type&>(e)//e必須是一個左值
dynamic_cast<type&&>(e)//e不能是左值
//Base含有虛函數(shù)庇忌,Derived是Base的公有派生類
if (Derived *dp = dynamic_cast<Derived*>(bp)) {
//使用dp指向的Derived對象
} else {
//使用bp指向的Base對象
}
void f(const Base &b) {
try {
const Derived &d = dynamic_cast<const Derived&>(b);
//使用b引用的Derived對象
} catch (bad_cast) { //不存在空引用舞箍,只能處理異常
//處理失敗的情況
}
}
- typeid運算符
typeid(e)
e可以是任意表達(dá)式或類型的名字。操作的結(jié)果是一個常量對象的引用皆疹。該對象的類型是標(biāo)準(zhǔn)庫類型type_info或type_info的公有派生類型疏橄。
1)頂層const被忽略
2)若是引用,則返回該引用所引對象的類型
3)作用于數(shù)組或函數(shù)時,不會執(zhí)行向指針的標(biāo)準(zhǔn)類型轉(zhuǎn)換
4)不含虛函數(shù)的類捎迫,則是靜態(tài)類型晃酒;否則直到運行時才知道結(jié)果
typeid應(yīng)該作用于對象。當(dāng)typeid作用于指針時窄绒,返回的結(jié)果是該指針的靜態(tài)編譯時類型贝次。 -
使用RTTI
一種很容易想到的解決方案是定義一套虛函數(shù),令其在繼承體系的各個層次上分別執(zhí)行相等性判斷彰导。
實際上不可以蛔翅,因為虛函數(shù)的基類版本與派生類版本必須具有相同的形參鳞青,都是基類引用垦江。此時equal只能比較基類的成員义矛,不能比較派生類成員嗦篱。
如果類型相等,則將工作委托給虛函數(shù)equal:
派生類所有函數(shù)的第一件事是將實參的類型轉(zhuǎn)換為派生類监氢,這樣函數(shù)才能返回派生類成員诫隅。
-
type_info類
type_info類一般作為一個基類,應(yīng)提供一個公有的虛析構(gòu)函數(shù)损同。沒有默認(rèn)構(gòu)造函數(shù),拷貝移動構(gòu)造函數(shù)和賦值運算符被定義為刪除鸟款。
創(chuàng)建type_info對象的唯一途徑是使用typeid運算符膏燃。
- 枚舉類型——字面值常量類型
兩種枚舉:限定作用域和不限定作用域。
限定作用域遵循常規(guī)的作用域準(zhǔn)則何什,不會自動轉(zhuǎn)換成整型组哩。
不限定,枚舉成員的作用域與枚舉類型本身的作用域相同处渣,可以自動轉(zhuǎn)換成整型伶贰。
不能直接將整型值傳遞給enum形參,可以將不限定作用域的枚舉類型傳遞給整型形參罐栈。
//限定作用域
enum class open_modes {input, output, append};
//不限定租用
enum color {red, yellow, green};
enum intValues : unsigned long long { //冒號后制定enum使用的類型(大惺蜓谩)
};
- 類成員指針
成員指針是指可以指向類的非靜態(tài)成員的指針。
一般情況荠诬,指針指向一個對象琅翻,但是成員指針指示的是類的成員,而非類的對象柑贞。
聲明時必須加上classname::表示當(dāng)前指針可以指向classname的成員方椎。
為指針賦值時,該指針并沒有指向任何數(shù)據(jù)钧嘶。只有解引用成員指針時才提供對象的信息棠众。
常規(guī)的訪問控制對成員指針同樣有效。
如果希望可以訪問私有數(shù)據(jù)成員有决,可以頂一個函數(shù)闸拿,返回值是指向該成員的指針轿亮。
class Screen {
public:
typedef std::string::size_type pos;
char get_cursor() const {return contents[cursor]; }
char get() const;
char get(pos ht, pos wd) const;
private:
std::string contents;
pos cursor;
pos height, witdth;
};
const string Screen::*pdata;
pdata = &Screen::contents;
//簡單方法是:
auto pdata = &Screen::contents;
Screen myScreen, *pScreen = &myScreen; //這些訪問在類內(nèi)部或友元內(nèi)部
auto s = myScreen.*pdata;
s = pScreen->*pdata;
class Screen { //破壞了封裝性
pulic:
static const std::string Screen::*data() {
return &Screen::contents;
}
}
const string Screen::*pdata = Screen::data();
auto s = myScreen.*pdata;
- 成員函數(shù)指針
1)成員函數(shù)如果有重載,必須顯式地聲明函數(shù)類型以明確指出使用哪個函數(shù)
2)成員函數(shù)和指向該成員函數(shù)指針之間不存在自動轉(zhuǎn)換規(guī)則胸墙。
成員函數(shù)指針可以作為函數(shù)返回類型或形參類型我注。
auto pmf = &Screen::get_cursor;
char (Screen::pmf2)(Screen::pos, Screen::pos) const;
pmf2 = &Screen::get;
Screen myScreen, *pScreen = &myScreen;
char c1 = (pScreen->*pmf)();
char c2 = (myScreen.*pmf2)(0, 0);
using Action = char (Screen::*)(Screen::pos, Screen::pos) const;
Action get = &Screen::get;
Screen& action(Screen&, Action = &Screen::get);
- 成員指針函數(shù)表
class Screen {
public:
Screen& home();
Screen& forward();
Screen& back();
Screen& up();
Screen& down();
using Action = Screen& (Screen::*)();
enum Directions { HOME, FORWARD, BACK, UP, DOWN };
Screen& move(Directions);
private:
static Action Menu[]; //函數(shù)表
};
Screen& Screen::move(Directions cm) {
return (this->*Menu[cm])();
}
Screen::Action Screen::Menu[] = {
&Screen::home,
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
};
//使用方法:
Screen myScreen;
myScreen.move(Screen::HOME);
myScreen.move(Screen::DOWN);
- 將成員函數(shù)用作可調(diào)用對象
成員指針不是一個可調(diào)用對象,不支持函數(shù)調(diào)用運算符迟隅。
1)使用function生成一個可調(diào)用對象
執(zhí)行成員函數(shù)的對象被傳給隱式的this形參但骨。
2)使用mem_fn生成一個可調(diào)用對象——functional頭文件
mem_fn可以根據(jù)成員指針的類型推斷可調(diào)用對象的類型,而無須用戶顯式地指定智袭。
mem_fn生成的可調(diào)用對象可以通過對象調(diào)用奔缠,也可以通過指針調(diào)用。
3)使用bind生成一個可調(diào)用對象
bind必須將函數(shù)中用于表示執(zhí)行對象的隱式形參轉(zhuǎn)換成顯式吼野;
bind生成的可調(diào)用對象的第一個實參可以是指針或引用校哎。
vector<string*> pvec;
function<bool (const string*)> fp = &string::empty;
find_if(pvec.begin(), pvec.end(), fp);
find_if(svec.begin(), svec.end(), mem_fn(&string::empty());
auto f = mem_fn(&string::empty);
f(*svec.begin());
f(&svec[0]);
auto it = find_if(svec.begin(), svec.end(), bind(&string::empty, _1));
auto f = bind(&string::empty, _1); //類的this可以通過對象或者指針來綁定
f(*svec.begin());
f(&svec[0]);
- 嵌套類
嵌套類可以訪問外層類的成員。
外層類的成員可以像使用任何其他類型成員一樣使用嵌套類的名字瞳步。
嵌套類和外層類是相互獨立的闷哆,外層類對象只包含外層類定義的成員,不會有任何嵌套類的成員单起。反之亦然抱怔。
//聲明一個嵌套類
class TextQuery {
pulic:
class QueryResult;
};
//在外層類之外頂一個嵌套類
class TextQuery::QueryResult {
friend std::ostream& print(std::ostream&, const QueryResult&);
pulic:
QueryResult(std::string,
std::shared_ptr<std::set<line_no>>,
std::shared_ptr<std::vector<std::string>>);
};
//定義嵌套類的成員
TextQuery::QueryResult::QueryResult(string s,
shared_ptr<std::set<line_no>> p,
shared_ptr<std::vector<std::string>> f):
sought(s), lines(p), file(f) {}
//嵌套類的靜態(tài)成員定義
int TextQuery::QueryResult::static_mem = 1024;
- union:一種節(jié)省空間的類
union不能繼承自其他類,也不能作為基類使用嘀倒,因此union不能含有虛函數(shù)屈留。
如果提供了初始值,則該初始值別用于初始化第一個成員测蘑。
在匿名union的定義所在的作用域內(nèi)該union的成員都是可以直接訪問的灌危。 -
使用類管理union成員
作為union組成部分的類成員無法自動銷毀,因為析構(gòu)函數(shù)不清楚union存儲的值是什么類型碳胳,所以無法確定應(yīng)該銷毀哪個成員勇蝙。所以要在管理類的析構(gòu)函數(shù)顯式調(diào)用union類的析構(gòu)函數(shù)。
管理需要拷貝控制的聯(lián)合成員:
對于左側(cè)運算對象的union是string時固逗,需要特別處理浅蚪。
Token &Token::operator=(int i) {
if (tok == STR) sval._string();
ival = i;
tok = INT:
return *this;
}
Token &Token::operator=(const std::string &s) {
if (tok == STR) {
sval = s;
} else {
new(&sval) string(s); //利用定位new表達(dá)式
}
tok = STR;
return *this;
}
-
局部類
定義在函數(shù)內(nèi)部的類。和嵌套類不同烫罩,局部類的成員受到嚴(yán)格限制惜傲。
局部類的所有成員(包括函數(shù)在內(nèi))都必須完整定義在類的內(nèi)部。因此局部類不允許聲明靜態(tài)數(shù)據(jù)成員贝攒。
局部類不能使用函數(shù)作用域中的變量盗誊。只能訪問外層作用域中定義的類型名、靜態(tài)變量以及枚舉成員。
常規(guī)的訪問包含規(guī)則對局部類同樣使用哈踱。
嵌套的局部類:可以在局部類的內(nèi)部再嵌套一個類荒适。嵌套類必須定義在與局部類相同的作用域中。局部類內(nèi)的嵌套類也是一個局部類开镣,必須遵循局部類的各種規(guī)定刀诬。
固有的不可移植特性
-
1.位域
類可以將其(非靜態(tài))數(shù)據(jù)成員定義成位域。在一個位域中含有一定數(shù)量的二進(jìn)制位邪财。當(dāng)一個程序需要其他程序或硬件設(shè)備傳遞二進(jìn)制數(shù)據(jù)時陕壹,通常會用到位域。
位域在內(nèi)存中的布局是與機(jī)器相關(guān)的树埠。
通常情況下最好將位域設(shè)為無符號類型糠馆,存儲在帶符號類型中的位域行為將因具體實現(xiàn)而定。
使用位域怎憋,通常使用位運算符操作超過1位的位域:
如果一個類定義了位域成員又碌,則通常會定義一組內(nèi)聯(lián)的成員函數(shù)以檢驗或設(shè)置位域的值:
2.volatile限定符
volatile的確切含義與機(jī)器有關(guān),只能通過閱讀編譯器文檔來理解绊袋。
程序可能包含一個有系統(tǒng)時鐘定時更新的變量毕匀,當(dāng)對象的值可能在程序的控制或檢測之外被改變時,應(yīng)該將對象聲明為volatile愤炸。關(guān)鍵字volatile告訴編譯器不應(yīng)該對這樣的對象進(jìn)行優(yōu)化期揪。
volatile限定符的用法和const很相似。
const和volatile一個重要區(qū)別是不能使用合成的拷貝/移動構(gòu)造函數(shù)及賦值運算符初始化volatile對象或從volatile對象賦值规个。因為合成的成員接受的是(非volatile)常量引用,不能將一個非volatile引用綁定到volatile對象上姓建。-
3.鏈接指示:extern "C"
要想把C++代碼和其他語言編寫的代碼放在一起使用诞仓,要求必須有權(quán)訪問該語言的編譯器,并且這個編譯器與當(dāng)前的C++編譯器是兼容的速兔。
鏈接指示與函數(shù)聲明墅拭、頭文件:
指向extern "C"函數(shù)的指針:
編寫函數(shù)所用的語言是函數(shù)類型的一部分。如下這種賦值嚴(yán)格意義上來說是非法的涣狗,有點編譯器可能會接受這種賦值谍婉。
鏈接指示對整個聲明都有效,包括返回值和形參類型的函數(shù)指針镀钓,如果希望給C++函數(shù)傳入一個指向C函數(shù)的指針穗熬,必須使用類型別名:
使用鏈接指示導(dǎo)出C++函數(shù)到其他語言: