C++基础进阶
C++基礎(chǔ)與進(jìn)階
局部變量及初始化、auto、頭文件防衛(wèi)式聲明、引用、常量;
auto:變量的自動(dòng)類(lèi)型判斷
auto可以在聲明變量的時(shí)候根據(jù)變了初始值自動(dòng)為此選擇匹配的類(lèi)型(聲明時(shí)賦初值)
auto自動(dòng)類(lèi)型判斷發(fā)生在編譯期間,所以使用auto不會(huì)造成程序效率的降低
頭文件的防衛(wèi)式聲明
#ifndef 頭文件名_H#define 頭文件名_H#endif防止頭文件互相include時(shí)引發(fā)重定義。
引用:為變量起另一個(gè)名字,一般用&表示,起完別名后,這個(gè)別名我們看作和變量為同一個(gè)變量
&不是取地址運(yùn)算符,只是起標(biāo)識(shí)作用,定義引用別不額外占用內(nèi)存
定義引用的時(shí)候必須初始化(初始化為綁定變量或?qū)ο?#xff0c;不能綁定常量)
注意區(qū)分指針
函數(shù)的形參是引用時(shí),函數(shù)中對(duì)參數(shù)的值進(jìn)行變化會(huì)對(duì)外界數(shù)值產(chǎn)生變化。
常量:不變的量(const)。
constexper:C++11引入,也是常量的概念,在編譯時(shí)求值,能夠提升性能。
代碼中出現(xiàn)的常量盡量不要改變其值。
范圍for語(yǔ)句、動(dòng)態(tài)內(nèi)存分配、nullptr
C++11對(duì)for語(yǔ)句進(jìn)行擴(kuò)展
int v[]{12,13,14,16,18}; for(auto &x:v)//將數(shù)組v中每個(gè)元素依次放入x中并打印x值。加引用提高效率 {cout << x << endl; } for (auto x : {12,13,15,17}) {cout << x << endl; }動(dòng)態(tài)內(nèi)存分配
供程序使用的存儲(chǔ)空間,有程序區(qū),靜態(tài)存儲(chǔ)區(qū),動(dòng)態(tài)存儲(chǔ)區(qū)。
C++中將內(nèi)存分為5個(gè)區(qū)
- 棧:一般函數(shù)內(nèi)的局部變量都會(huì)放在這里由編譯器自動(dòng)分配和釋放。
- 堆:程序員malloc/new分配,用free/delete來(lái)釋放。忘記釋放后,系統(tǒng)會(huì)在程序結(jié)束時(shí)回收。
- 全局/靜態(tài)存儲(chǔ)區(qū):放全局變量和靜態(tài)變量static。程序結(jié)束時(shí)系統(tǒng)釋放。
- 程序代碼區(qū)
堆和棧不同的用途和區(qū)別
- 棧:空間有限。分配速度快,程序員也控制不了。
- 堆:只要不超出實(shí)際擁有的物理內(nèi)存,也在操作系統(tǒng)允許你能夠分配的最大內(nèi)存大小之內(nèi)都可以分配。分配速度慢,但靈活,可隨時(shí)用new/malloc來(lái)分配,使用delete/free釋放。
C語(yǔ)言中使用malloc和free來(lái)動(dòng)態(tài)分配和釋放內(nèi)存,C++中使用new/delete
void *malloc(int NumBytes)分配成功返回被分配的內(nèi)存指針,失敗返回NULL。
void free(void *FirstByte)不用時(shí)使用free釋放。
int *p = NULL; p = (int*)malloc(sizeof(int));//在堆中分配四個(gè)字節(jié) if(p != NULL) {*p = 5;cout << *p << endl;free(p); } *q++//*與++優(yōu)先級(jí)相同,右結(jié)合性,等價(jià)于*(q++)C++中new和delete時(shí)運(yùn)算符,不是函數(shù)。
new/delete和malloc/free干了同樣分配和釋放內(nèi)存的事。但new時(shí)會(huì)調(diào)用構(gòu)造函數(shù),delete時(shí)會(huì)調(diào)用析構(gòu)函數(shù)(本質(zhì)區(qū)別)。
new的三種使用方式:
-
指針變量名 = new 類(lèi)型標(biāo)識(shí)符
int *myint = new int; if(myint) {*myint = 8;cout << *myint << endl;delete myint; } -
指針變量名 = new 類(lèi)型標(biāo)識(shí)符(初始值)
int *myint = new int(18); if(myint) {cout << *myint << endl;delete myint; } -
指針變量名 = new 類(lèi)型標(biāo)識(shí)符[內(nèi)存單元個(gè)數(shù)]
int *pa = new int[100]; if(pa) {int *q =pa;*q++ = 12;*q++ = 18;cout << *pa << endl;//12cout << *(pa+1) << endl; //18delete[] pa;//不同之處,[]之中不寫(xiě)大小。 }
注意配對(duì)使用,且free/delete不要重復(fù)調(diào)用。
nullptr代表的也是空指針
只能給指針賦值。NULL既能給指針賦值也能給整型等類(lèi)型變量賦值。
實(shí)際效果與NULL相同,引入nullptr為了避免與整數(shù)與指針之間發(fā)生混淆。
nullptr與NULL不是一個(gè)類(lèi)型。
結(jié)構(gòu)、權(quán)限修飾符、類(lèi)簡(jiǎn)介
結(jié)構(gòu)回顧
struct student {//成員變量int number;char name[100]; }int main() {student student1;student1.number = 1001;strcpy_s(student1.name,sizeof(student1.name),"zhangsan"); }用指向結(jié)構(gòu)體的指針做函數(shù)參數(shù):
void func(student *ptmpstu) {ptmpstu->number = 2000;strcpy_s(ptmpstu->name,sizeof(ptmpstu->name),"who");return; }引用做函數(shù)參數(shù):
void func(student &tmpstu) {tmpstu.number = 2000;strcpy_s(tmpstu.name,sizeof(tmpstu.name),"who");return; }C++中的結(jié)構(gòu)即類(lèi)既有成員變量也有成員函數(shù)(也叫方法)。
權(quán)限修飾符:public,private,protected
- public:能被外界訪問(wèn)
- private:只能被內(nèi)部定義的函數(shù)和成員才能訪問(wèn)
- protected:繼承的時(shí)候,繼承的類(lèi)可以訪問(wèn)。
類(lèi)簡(jiǎn)介:略
類(lèi)的書(shū)寫(xiě)規(guī)范:類(lèi)的定義寫(xiě)在頭文件中,實(shí)現(xiàn)寫(xiě)在.cpp文件中。
函數(shù)新特性、內(nèi)聯(lián)函數(shù)、const詳解
函數(shù)定義中,形參如果在函數(shù)體內(nèi)用不到的話,則可以不給形參的名字,只給其類(lèi)型。
函數(shù)聲明時(shí),可以只有形參的類(lèi)型。
void fun(int,int); void fun(int a,int) {return; }C++11引入后置返回類(lèi)型
auto func(int a,int b)->void; auto func(int a,int b)->void {return; }前面放auto,表示函數(shù)返回類(lèi)型放在參數(shù)列表之后,而放在參數(shù)列表之后的返回類(lèi)型時(shí)通過(guò)->開(kāi)始的。
內(nèi)聯(lián)函數(shù)
函數(shù)定義前加上inline,普通函數(shù)變?yōu)閮?nèi)聯(lián)函數(shù)
inline void function1(int a,int b) {return 1; }函數(shù)體很簡(jiǎn)單,但調(diào)用頻繁,導(dǎo)致開(kāi)銷(xiāo)很大。內(nèi)聯(lián)函數(shù)有以下功能:
- inline影響編譯器:編譯階段堆inline這種函數(shù)進(jìn)行處理,系統(tǒng)嘗試將調(diào)用該函數(shù)的動(dòng)作替換為函數(shù)本體。通過(guò)這種方式提升性能。
- inline為開(kāi)發(fā)者對(duì)于編譯器的建議,編譯器可以選擇去做和不去做,這取決于編譯器的診斷功能。
- 內(nèi)聯(lián)函數(shù)的定義就要放在頭文件中。這樣需要用到這個(gè)內(nèi)聯(lián)函數(shù)的.cpp文件都能通過(guò)#include把這個(gè)內(nèi)聯(lián)函數(shù)的源代碼#include進(jìn)來(lái),以便找到該函數(shù)的本體源代碼并嘗試將該函數(shù)的調(diào)用替換為函數(shù)體內(nèi)的語(yǔ)句。
優(yōu)缺點(diǎn):
- 代碼膨脹問(wèn)題,所以?xún)?nèi)聯(lián)代碼函數(shù)函數(shù)體盡量小。
- 各種編譯器堆inline的處理各不相同。inline函數(shù)盡量簡(jiǎn)單,代碼盡可能少。循環(huán),分支,遞歸調(diào)用盡量不要出現(xiàn)在inline函數(shù)中。否則的話,編譯器很可能會(huì)因?yàn)槟銓?xiě)這些代碼的原因拒絕讓這個(gè)函數(shù)成為一個(gè)inline函數(shù)。
constexpr函數(shù)可以堪稱(chēng)時(shí)更嚴(yán)格的一種內(nèi)聯(lián)函數(shù)。
注意:向系統(tǒng)已回收的地址中寫(xiě)入數(shù)據(jù)會(huì)引發(fā)巨大隱患。
若函數(shù)不調(diào)用,可以只有聲明部分,沒(méi)有定義部分。
- const char*:表示該指針指向的內(nèi)容不能通過(guò)該指針來(lái)修改,指向的地址可以改變
- char* const:定義時(shí)必須初始化,該指針指向了一個(gè)東西后,就不能再改變指向?qū)ο?。但可以改變指向?nèi)存中的內(nèi)容。
- char const*:等價(jià)于const char*
- const char * const或char const * const:地址與內(nèi)容都不能變。
另外的對(duì)于引用:
- const int &i =j;i的內(nèi)容不能通過(guò)i來(lái)改變。
- 函數(shù)中形參如果是引用,可以改變實(shí)參的值,如果不想改變,則可以在前面加上const,引用可以減少值傳遞,從而減少賦值操作,提升代碼性能
- 加const會(huì)使得實(shí)參類(lèi)型更靈活。
string類(lèi)型簡(jiǎn)介
- string s1;
- string s2 = " dsada";
- string s3(“dsadad”);
- string s4(num,“a”);表示創(chuàng)建num個(gè)a。不推薦,因?yàn)闀?huì)創(chuàng)建臨時(shí)對(duì)象。
- empty():判斷是否為空,為空是返回true
- size()/length():返回字符串長(zhǎng)度,無(wú)符號(hào)整型
- c_str():返回一個(gè)字符串的內(nèi)容常量指針。此函數(shù)的引入是為了與C語(yǔ)言兼容。
vector類(lèi)型簡(jiǎn)介
vector的初始化
-
空vector
vector<string>vstr; -
元素拷貝的方式
vector<string>str1(str2); vector<string>str1 = str2;//兩種方式等價(jià) -
C++11中,用列表初始化
vector<string>str1 = {"aaaaa","bbbbb","ccccc"} -
創(chuàng)建指定數(shù)量的元素
vector<string>str1(10,"hello");//創(chuàng)建10個(gè)元素,都是"hello" vector<string>str2(20);//表示創(chuàng)建20個(gè)string元素 -
多種初始化方式
一般來(lái)說(shuō)()中表示元素個(gè)數(shù),{}表示元素內(nèi)容,但不絕對(duì)。
vector<string>str1{10};//10個(gè)元素,每個(gè)元素為“” vector<string>str2{10,"hello"};//10個(gè)元素,每個(gè)都為hello vector<int>str1(10,1)//10個(gè)元素,每個(gè)元素的值都是1.要想正常的通過(guò){}來(lái)初始化,需要{}中的內(nèi)容與vector類(lèi)型相同。
vector對(duì)象上的操作。
最常用的是創(chuàng)建空的vector,再往里面加元素
- empty():判斷是否為空。
- push_back(value):末尾增加一個(gè)元素。
- size():返回容器中元素的個(gè)數(shù)。
- clear():清空所有元素。
- []:可用下標(biāo)。
- =:賦值
- ==,!=
范圍for
for(auto &x:strvector) {//..... }范圍for不要插入和刪除容器中的東西。
迭代器精彩演繹、失效分析及彌補(bǔ)、實(shí)戰(zhàn)
迭代器簡(jiǎn)介
迭代器是一種遍歷容器內(nèi)元素的數(shù)據(jù)類(lèi)型,這種數(shù)據(jù)類(lèi)型有點(diǎn)像指針,理解為迭代器指向容器中的某個(gè)元素。
減少下標(biāo)的使用,迭代器的方式更通用
通過(guò)迭代器可以讀和修改容器中的元素值。
容器的迭代器類(lèi)型
vector<int> vec = {100,200,300}; vector<int>::iterator iter;//迭代器類(lèi)型與容器保持一致迭代器begin()/end()操作,反向迭代器rbegin()/rend()
begin()/end()返回迭代類(lèi)型。
iter = vec.begin();//如果vec中有元素,則begin()返回的是指向第一個(gè)元素的迭代器 iter = vec.end();//注意,end()返回的迭代器并不指向末端元素,而是末端后的一個(gè)元素。rbegin()/rend():反向迭代器,從后往前遍歷。
成對(duì)使用
vector<int>::reverse_iterator riter;//注意初始化不同 cout<<*riter;迭代器運(yùn)算符
- *iter:返回迭代器iter所指向的引用。必須保證迭代器指向的是有效的元素,不能指向end();(不是有效元素)
- ++iter/iter++:如果已經(jīng)指向end(),則不能再使用。
- –iter/iter–:同理指向begin()不行。
- ==,!=
- 迭代器相減:表示兩個(gè)迭代器的距離。
const_iterator
表示迭代器指向的值不能改變。只能從容器中讀元素,像常量指針。
vector<int>::const_iterator citer; cout << *citer;//可以 *citer = 9;//不可以,會(huì)報(bào)錯(cuò)使用場(chǎng)景
const vector<int> vec;//常量容器- cbegin()/cend();C++11引入:返回的都是常量迭代器。
迭代器失效問(wèn)題
范圍for與迭代器等價(jià)。所以,不要在使用迭代器的過(guò)程中,不要改變?nèi)萜魅萘俊?/p>
-
災(zāi)難程序1:
vector<int>vecvalue{1,2,3,4,5}; auto beg = vecvalue.begin(); auto end = vecvalue.end(); while(beg != end){cout<<*beg<<endl;vecvalue.insert(beg,80);//導(dǎo)致迭代器失效,程序出錯(cuò)。break;//如果立即break是最明智的做法。++beg; } -
災(zāi)難程序2
vector<int>vecvalue{1,2,3,4,5}; for(auto iter =vecvalue.begin();iter!=vecvalue.end();iter++ ){vecvalue.erase(iter);//刪除當(dāng)前元素,返回下一個(gè)元素的位置 }正確寫(xiě)法:
vector<int>::iterator iter = vecvalue.begin(); while(iter!=vecvalue.end()){iter=vecvalue.erase(iter); }或者:
while(!vecvalue.empty()) {vecvalue.erase(vecvalue.begin()); }
范例演示
-
遍歷一下string:
string str = "I Love China!"; for(auto iter = str.begin();iter!=str.end();iter++){*iter = toupper(*iter);//該函數(shù)是將小寫(xiě)字母變成大寫(xiě)。 } cout<< str;
類(lèi)型轉(zhuǎn)換:static_cast、reinterpret_cast
隱式類(lèi)型轉(zhuǎn)換:系統(tǒng)自動(dòng)進(jìn)行,不需要程序開(kāi)發(fā)人員介入。
int m = 3 + 45.6;顯式類(lèi)型轉(zhuǎn)換(強(qiáng)制類(lèi)型轉(zhuǎn)換)
int k = 5 % (int)3.2;//C語(yǔ)言風(fēng)格的強(qiáng)制類(lèi)型轉(zhuǎn)換 int k = 5 % int(3.2);//函數(shù)風(fēng)格的強(qiáng)制類(lèi)型轉(zhuǎn)換(也是C語(yǔ)言風(fēng)格)C++中的強(qiáng)制類(lèi)型轉(zhuǎn)換(4種)
- static_cast:靜態(tài)轉(zhuǎn)換,就理解為正常轉(zhuǎn)換,編譯的時(shí)候進(jìn)行類(lèi)型轉(zhuǎn)換的檢查(代碼中需要保證代碼的安全性和正確性,跟C語(yǔ)言中的強(qiáng)制類(lèi)型轉(zhuǎn)換差不多)可用于相關(guān)類(lèi)型的轉(zhuǎn)換,比如整型和實(shí)型的轉(zhuǎn)換;子類(lèi)轉(zhuǎn)成父類(lèi)也可以使用;void*(無(wú)類(lèi)型指針,可以指向任何類(lèi)型的指針)與其他類(lèi)型指針之間的轉(zhuǎn)換。一般不能用于指針類(lèi)型的轉(zhuǎn)換
- dynami_cast:主要應(yīng)用于運(yùn)行時(shí)類(lèi)型識(shí)別和檢查。主要用來(lái)父類(lèi)型和子類(lèi)型之間轉(zhuǎn)換用的(父類(lèi)型指針指向子類(lèi)型對(duì)象,然后用dynamic_cast把父類(lèi)型指針轉(zhuǎn)子類(lèi)型指針)
- const_cast:去除指針或者引用的const屬性。該轉(zhuǎn)換能夠?qū)onst性質(zhì)轉(zhuǎn)換掉。編譯時(shí)進(jìn)行。(去掉const類(lèi)型不要進(jìn)行寫(xiě)值行為,屬于未定義行為,容易出現(xiàn)奇怪的結(jié)果)
- reinterpret_cast:重新解釋。將操作數(shù)內(nèi)容解釋為另一種不同的類(lèi)型(處理無(wú)關(guān)類(lèi)型的轉(zhuǎn)換)。一般用于:將一個(gè)整型(地址)轉(zhuǎn)換成指針,一種類(lèi)型指針轉(zhuǎn)換成另一種類(lèi)型指針,按照轉(zhuǎn)換后的內(nèi)容重新解釋內(nèi)存中的內(nèi)容;將指針類(lèi)型轉(zhuǎn)換成一個(gè)整型。被認(rèn)為是危險(xiǎn)的類(lèi)型轉(zhuǎn)換.
這四個(gè)強(qiáng)制類(lèi)型轉(zhuǎn)換都被稱(chēng)呼為“命名的強(qiáng)制類(lèi)型轉(zhuǎn)換”(因?yàn)樗麄兠恳粋€(gè)都有一個(gè)名字并且名字各不相同)
<type>(express);//轉(zhuǎn)換的格式強(qiáng)制類(lèi)型轉(zhuǎn)換能夠抑制編譯器報(bào)錯(cuò),不建議使用。主要需要認(rèn)識(shí)。reinterpret_cast危險(xiǎn)。使用const_cast意味著設(shè)計(jì)缺陷。
若實(shí)在需要類(lèi)型轉(zhuǎn)換,不再使用C語(yǔ)言風(fēng)格轉(zhuǎn)換。
構(gòu)造函數(shù)詳解, explicit,初始化列表
構(gòu)造函數(shù):在類(lèi)中,有一種特殊的成員和函數(shù),它的名字與類(lèi)名相同,我們?cè)趧?chuàng)建類(lèi)的對(duì)象的時(shí)候,這個(gè)特殊的成員函數(shù)就會(huì)被系統(tǒng)自動(dòng)調(diào)用,這個(gè)成員函數(shù)叫做構(gòu)造函數(shù)。因?yàn)闃?gòu)造函數(shù)能夠被系統(tǒng)自動(dòng)調(diào)用,所以我們介意簡(jiǎn)單的理解成:構(gòu)造函數(shù)的目的就是初始化類(lèi)對(duì)象的數(shù)據(jù)成員。
- 構(gòu)造函數(shù)沒(méi)有任何的返回值。
- 不可以手動(dòng)調(diào)用構(gòu)造函數(shù),否則編譯出錯(cuò)
- 正常情況下,構(gòu)造函數(shù)應(yīng)該被聲明為public,因?yàn)槲覀儎?chuàng)建一個(gè)對(duì)象時(shí)系統(tǒng)要替我們調(diào)用構(gòu)造函數(shù),這說(shuō)明構(gòu)造函數(shù)應(yīng)該是public。
- 構(gòu)造函數(shù)中如果有多個(gè)參數(shù),則創(chuàng)建對(duì)象的時(shí)候需要帶上這些參數(shù)。
-
構(gòu)造函數(shù)的形參缺省值只能放在聲明中。
-
在具有多個(gè)參數(shù)的函數(shù)中,默認(rèn)參數(shù)都必須出現(xiàn)在不默認(rèn)參數(shù)的右邊,一旦某個(gè)參數(shù)開(kāi)始制定默認(rèn)值,他右邊的所有參數(shù)必須制定默認(rèn)值
Time(int temHour, int temfen, int temmiao = 15); Time(int temHour, int temfen);//這種情況會(huì)報(bào)錯(cuò)。
隱式轉(zhuǎn)換和explicit
Time myTime = (12,13,14); Time mytime1 = 14;//存在臨時(shí)對(duì)象隱式轉(zhuǎn)換- 把一個(gè)數(shù)字轉(zhuǎn)換成了一個(gè)Time類(lèi)型的對(duì)象
- 在構(gòu)造函數(shù)聲明中,添加explicit,強(qiáng)制要求系統(tǒng)不能做隱式轉(zhuǎn)換。
- 對(duì)于單參數(shù)的構(gòu)造函數(shù),一般都聲明為explicit,除非有特別原因。
構(gòu)造函數(shù)的初始化列表
Time(int temHour, int temfen, int temmiao = 15) :Hour(temHour), fen(temfen), miao(temmiao){};這樣的寫(xiě)法:
- 專(zhuān)業(yè)
- 函數(shù)體中加賦值,初始化列表叫初始化。效率更高。
- 初始化列表使用形參初始化,不要用數(shù)據(jù)成員進(jìn)行賦值。
inline,const,mutable,this,static
在頭文件中,一般來(lái)說(shuō)只寫(xiě)成員函數(shù)的聲明,但是如果寫(xiě)了成員函數(shù)的實(shí)現(xiàn),那么系統(tǒng)會(huì)認(rèn)為這個(gè)函數(shù)是內(nèi)聯(lián)函數(shù)(inline),即在函數(shù)調(diào)用時(shí)用函數(shù)體替換。
- 如果一個(gè)數(shù)據(jù)成員聲明為const,則無(wú)法改變其值,其初始化可以選擇類(lèi)內(nèi)初始化,也可以在構(gòu)造函數(shù)的初始化列表進(jìn)行初始化。其余函數(shù)無(wú)法改變其值。
- 另外,如果一個(gè)類(lèi)的對(duì)象被聲明為const,則該對(duì)象無(wú)法調(diào)用函數(shù)聲明中末尾沒(méi)有加const的函數(shù)。
- 成員函數(shù)聲明為const表示該函數(shù)不會(huì)修改該類(lèi)的所有數(shù)據(jù)成員,該函數(shù)能被該類(lèi)的所有對(duì)象調(diào)用。
如果說(shuō)要在聲明const的成員函數(shù)中修改一個(gè)數(shù)據(jù)成員的值,那么可以在該數(shù)據(jù)成員的前面加上mutable,此時(shí),該數(shù)據(jù)成員變?yōu)榭尚薷摹?/li>
類(lèi)內(nèi)初始化、默認(rèn)構(gòu)造函數(shù)、=default;
類(lèi)相關(guān)非成員函數(shù)
可以將類(lèi)相關(guān)非成員函數(shù)的聲明放在類(lèi)定義之后,實(shí)現(xiàn)放在相關(guān)的.cpp文件中
Time.h中: class Time{..... }; void ouput_some(Time&time);Time.cpp中: void ouput_some(Time&time){...... }類(lèi)內(nèi)初始化
在C++11里,我們可以為類(lèi)內(nèi)的成員變量提供一個(gè)初始值,那么我們?cè)趧?chuàng)建對(duì)象的時(shí)候,這個(gè)初始化值就用來(lái)初始化該成員變量。
class Time{ public:int Hour = 0;//類(lèi)內(nèi)初始化 };const成員變量初始,在構(gòu)造函數(shù)的初始化列表中進(jìn)行,不可以通過(guò)賦值來(lái)初始化,或者類(lèi)內(nèi)初始化。
默認(rèn)構(gòu)造函數(shù)
沒(méi)有參數(shù)的構(gòu)造函數(shù),我們稱(chēng)為默認(rèn)構(gòu)造函數(shù)
沒(méi)有構(gòu)造函數(shù),這些類(lèi)對(duì)象如何初始化呢?這叫“默認(rèn)初始化”,也就是說(shuō),這個(gè)類(lèi)通過(guò)一個(gè)特殊的構(gòu)造函數(shù)執(zhí)行默認(rèn)的初始化過(guò)程。那么這個(gè)特殊的構(gòu)造函數(shù)就叫做“默認(rèn)構(gòu)造函數(shù)”,也叫做無(wú)參數(shù)的構(gòu)造函數(shù)。在類(lèi)定義中,沒(méi)有構(gòu)造函數(shù)的情況下,編譯器會(huì)為我們隱式的定義一個(gè)默認(rèn)的構(gòu)造函數(shù),成為“合成的默認(rèn)構(gòu)造函數(shù)”
一旦我們自己寫(xiě)了一個(gè)構(gòu)造函數(shù),不管這個(gè)構(gòu)造函數(shù)帶幾個(gè)參數(shù),編譯器就都不會(huì)為我們創(chuàng)建默認(rèn)的構(gòu)造函數(shù)。
=default;,=delete;
C++11標(biāo)準(zhǔn)引入
當(dāng)我們寫(xiě)了一個(gè)帶參數(shù)的構(gòu)造函數(shù),則進(jìn)行不帶參數(shù)的初始化時(shí)失敗
explicit Time() = default;//帶參數(shù)和普通函數(shù)都不能用,只能默認(rèn)構(gòu)造函數(shù)能行 Time() = delete;//讓程序員顯示的禁用某個(gè)函數(shù)拷貝構(gòu)造函數(shù)
默認(rèn)情況下,類(lèi)對(duì)象的拷貝是每個(gè)成員逐個(gè)拷貝
如果一個(gè)類(lèi)的構(gòu)造函數(shù)的第一個(gè)參數(shù)是所屬的類(lèi)類(lèi)型的引用。如果還有其他額外參數(shù),那么這些額外的參數(shù)還都有默認(rèn)值,則這個(gè)構(gòu)造函數(shù)就叫做拷貝構(gòu)造函數(shù)。
拷貝構(gòu)造函數(shù)會(huì)在一定的時(shí)機(jī)被系統(tǒng)自動(dòng)調(diào)用。
成員變量逐個(gè)拷貝的功能因?yàn)槲覀冏约憾x的拷貝構(gòu)造函數(shù)的存在而丟失了作用。
或者說(shuō)我們自己的拷貝構(gòu)造函數(shù)取代了系統(tǒng)默認(rèn)的每個(gè)成員變量逐個(gè)拷貝的這種行為
其他調(diào)用拷貝構(gòu)造函數(shù)的情況:
-
將一個(gè)對(duì)象作為實(shí)參傳遞給一個(gè)非引用類(lèi)型的形參
-
函數(shù)的返回值返回了一個(gè)類(lèi)的臨時(shí)對(duì)象
重載運(yùn)算符、拷貝復(fù)制運(yùn)算符、析構(gòu)函數(shù)
重載運(yùn)算符
- 重載運(yùn)算符,本質(zhì)上是一個(gè)函數(shù)。整個(gè)函數(shù)的正式名字:operator關(guān)鍵字 接 運(yùn)算符
- 既然重載運(yùn)算符本質(zhì)上是一個(gè)函數(shù),那么會(huì)有返回類(lèi)型和參數(shù)列表
- 有一些運(yùn)算符,如果我們不自己寫(xiě)該運(yùn)算符的重載,那么系統(tǒng)會(huì)自動(dòng)生成一個(gè)。比如賦值運(yùn)算符的重載。
拷貝賦值運(yùn)算符
我們可以自己沖在賦值運(yùn)算符,如果我們自己不重載,編譯器也會(huì)為我們生成一個(gè)
但編譯器生成的賦值運(yùn)算符重載比較粗糙。一般就是將非static成員賦值給賦值運(yùn)算符左側(cè)的對(duì)象的對(duì)應(yīng)成員中去。如果這個(gè)成員是個(gè)類(lèi)對(duì)象的話,可能還會(huì)調(diào)用這個(gè)類(lèi)拷貝賦值運(yùn)算符
所以為了精確控制類(lèi)的賦值動(dòng)作,我們往往會(huì)自己來(lái)重載賦值運(yùn)算符
重載賦值運(yùn)算符:有返回類(lèi)型和參數(shù)裂變,這里的參數(shù)就表示運(yùn)算符的運(yùn)算對(duì)象。
析構(gòu)函數(shù)
相對(duì)于構(gòu)造函數(shù),對(duì)象在銷(xiāo)毀的時(shí)候,會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)
如果我們自己不寫(xiě)自己的析構(gòu)函數(shù),編譯器也會(huì)生成一個(gè)默認(rèn)的析構(gòu)函數(shù)。默認(rèn)析構(gòu)函數(shù)的函數(shù)體為空{(diào)},表示默認(rèn)的析構(gòu)函數(shù)沒(méi)干什么有意義的事情
構(gòu)造函數(shù)里new,析構(gòu)函數(shù)中需要自己寫(xiě)delete。
析構(gòu)函數(shù)無(wú)參,無(wú)返回值。不能重載
構(gòu)造函數(shù)的成員初始化:干了兩個(gè)事,函數(shù)體之前和函數(shù)體之中。
析構(gòu)函數(shù)的成員銷(xiāo)毀:干了兩個(gè)事,函數(shù)體,函數(shù)體之后。
先定義的先初始化,后銷(xiāo)毀
析構(gòu)函數(shù)中內(nèi)存釋放只是new出來(lái)的對(duì)象,其他的等析構(gòu)函數(shù)執(zhí)行完畢后自動(dòng)銷(xiāo)毀。
Time *p = new Time; Time *p1 = new Time();派生類(lèi)、調(diào)用順序、訪問(wèn)等級(jí)、函數(shù)遮蔽
派生類(lèi)
類(lèi)之間的層次關(guān)系,繼承,是面向?qū)ο蟪绦蛟O(shè)計(jì)的核心思想之一。
繼承,首先需要等一一個(gè)公用的成員變量,成員函數(shù)。
通過(guò)繼承父類(lèi)來(lái)構(gòu)建新的類(lèi):子類(lèi);所以,寫(xiě)代碼時(shí),我們只需要寫(xiě)和子類(lèi)相關(guān)的一些內(nèi)容即可。
子類(lèi)會(huì)比父類(lèi)更加龐大
派生類(lèi)對(duì)象定義時(shí)調(diào)用構(gòu)造函數(shù)的順序
先調(diào)用父類(lèi)的構(gòu)造函數(shù),再調(diào)用子類(lèi)的構(gòu)造函數(shù)
public, protected,private
三種訪問(wèn)權(quán)限
- public:可以被任意實(shí)體所訪問(wèn)
- protected:只允許本類(lèi)或者子類(lèi)的成員函數(shù)來(lái)訪問(wèn)
- private:只允許本類(lèi)的成員函數(shù)訪問(wèn)
| public | public | public |
| protected | public | protected |
| private | public | 子類(lèi)無(wú)權(quán)訪問(wèn) |
| public | protected | protected |
| protected | protected | protected |
| private | protected | 子類(lèi)無(wú)權(quán)訪問(wèn) |
| public | private | private |
| protected | private | private |
| private | private | 子類(lèi)無(wú)權(quán)訪問(wèn) |
總結(jié):
- 子類(lèi)public繼承父類(lèi)不給便父類(lèi)的訪問(wèn)權(quán)限
- protected繼承將父類(lèi)中public成員變?yōu)樽宇?lèi)的protected成員;
- private繼承使得父類(lèi)所有成員在子類(lèi)中的訪問(wèn)權(quán)限變?yōu)閜rivate;
- 父類(lèi)中的private成員不受繼承方式的影響,子類(lèi)永遠(yuǎn)無(wú)權(quán)限訪問(wèn)
- 對(duì)于父類(lèi)來(lái)講,尤其時(shí)父類(lèi)的成員函數(shù),如果你不想讓外邊訪問(wèn),就設(shè)置為private;如果你想讓自己的子類(lèi)能夠訪問(wèn),就設(shè)置為protected,如果你想公開(kāi),就設(shè)置為public;
函數(shù)遮蔽
子類(lèi)重寫(xiě)父類(lèi)中的函數(shù),會(huì)使得子類(lèi)對(duì)象無(wú)法再調(diào)用父類(lèi)中的同名函數(shù)。
子類(lèi)中如果有一個(gè)同名函數(shù),那么父類(lèi)中,不管有幾個(gè)同名函數(shù),不管有幾個(gè)同名函數(shù),子類(lèi)中都無(wú)法訪問(wèn)。
能調(diào)用的方式:
-
在子類(lèi)的成員函數(shù)中使用:父類(lèi)名::函數(shù)名
-
using:C++11中讓父類(lèi)同名函數(shù)在子類(lèi)中可見(jiàn)。
using 父類(lèi)名::函數(shù)名//不能指定形參,只能指定函數(shù)名。 -
using 引入的主要目的是用來(lái)實(shí)現(xiàn)在子類(lèi)對(duì)象中調(diào)用父類(lèi)的重載版本。
基類(lèi)指針、純虛函數(shù)、多態(tài)性、虛析構(gòu)
基類(lèi)指針、派生類(lèi)指針
Human *phuman = new Human(); Man *pman = new Man; Human *phuman = new Man;//父類(lèi)指針能夠指向子類(lèi)對(duì)象,但是不能調(diào)用子類(lèi)的成員函數(shù)問(wèn)題:既然父類(lèi)指針沒(méi)有辦法調(diào)用子類(lèi)的成員函數(shù),那么為什么還讓父類(lèi)指針new一個(gè)子類(lèi)對(duì)象呢。
虛函數(shù)
通過(guò)一個(gè)父類(lèi)指針調(diào)用父類(lèi)、子類(lèi)中的同名同參的函數(shù),對(duì)這個(gè)函數(shù)的要求為,這個(gè)函數(shù)在聲明前必須加virtual,聲明為虛函數(shù),子類(lèi)也可以加(推薦加)
一旦一個(gè)函數(shù)在基類(lèi)中被聲明為虛函數(shù),那么在子類(lèi)中都是虛函數(shù)。
Human* phuman = new Man; phuman->eat(); phuman = new Woman; phuman->eat(); phuman = new Human; phuman->eat();//new 誰(shuí)調(diào)用誰(shuí)的函數(shù)指向子類(lèi)的父類(lèi)指針調(diào)用父類(lèi)的虛函數(shù)
Human* phuman = new Man; phuman->Human::eat();//能調(diào)用Human父類(lèi)的eat()函數(shù)-
override
為了避免你在子類(lèi)中寫(xiě)錯(cuò)虛函數(shù),在C++11中,可以在函數(shù)聲明這里增加一個(gè)override關(guān)鍵字。用在子類(lèi)中,虛函數(shù)專(zhuān)用。
override就是用來(lái)說(shuō)明派生類(lèi)中的虛函數(shù),你用了這個(gè)關(guān)鍵字后,編譯器就會(huì)認(rèn)為你這個(gè)函數(shù)是覆蓋了父類(lèi)中的同名函數(shù),只有虛函數(shù)才存在子類(lèi)可以覆蓋父類(lèi)中同名函數(shù)的問(wèn)題。那么編譯器就會(huì)在父類(lèi)中找同名同參的虛函數(shù),如果寫(xiě)錯(cuò)函數(shù)名,能夠幫助糾錯(cuò)。
-
final
final也是虛函數(shù)專(zhuān)用,是用在“父類(lèi)”,如果我們?cè)诟割?lèi)的函數(shù)聲明中加了final,那么人格嘗試覆蓋該函數(shù)的操作都將引發(fā)錯(cuò)誤。
virtual void eat()final;
調(diào)用虛函數(shù)執(zhí)行的是“動(dòng)態(tài)綁定”。表示的就是我們子啊程序運(yùn)行的時(shí)候才能知道調(diào)用了那個(gè)子類(lèi)的虛函數(shù)。
動(dòng)態(tài)的綁定到Many上去,取決于呢哇的是Man還是Woman
多態(tài)性
多態(tài)性只是針對(duì)于虛函數(shù)來(lái)說(shuō)的;
隨著虛函數(shù)的提出,面向?qū)ο缶幊汤镞呌幸粋€(gè)概念“多態(tài)性”就浮出水面
多態(tài)性體現(xiàn)在具有繼承關(guān)系的父類(lèi)和子類(lèi)之間,子類(lèi)重新定義(重寫(xiě))父類(lèi)的成員函數(shù)eat(),同時(shí)父類(lèi)把這個(gè)eat()函數(shù)聲明成了virtual虛函數(shù),只有到了程序運(yùn)行時(shí)期,找到動(dòng)態(tài)綁定到父類(lèi)指針上的對(duì)象,這個(gè)對(duì)象它有可能是某個(gè)子類(lèi)對(duì)象,也可能是父類(lèi)對(duì)象。
然后系統(tǒng)內(nèi)部實(shí)際上是要查一個(gè)虛函數(shù)表,找到函數(shù)eat()的入口地址,從而調(diào)用父類(lèi)或者子類(lèi)的eat()函數(shù)
純虛函數(shù)
是在基類(lèi)中聲明的函數(shù) ,但是他在基類(lèi)中沒(méi)有定義,但是要求任何派生類(lèi)都要定義該函數(shù)自己的實(shí)現(xiàn)方式
- 基類(lèi)中實(shí)現(xiàn)純虛函數(shù)的方法是在函數(shù)后面加一個(gè)=0;
- 一旦一個(gè)類(lèi)中有純虛函數(shù)了,那么就不能生成這個(gè)類(lèi)的對(duì)象了。
- 子類(lèi)中必須要實(shí)現(xiàn)該基類(lèi)中定義的純虛函數(shù)。
基類(lèi)的析構(gòu)甘薯一般寫(xiě)成虛函數(shù)(虛析構(gòu)函數(shù))
用基類(lèi)指針new子類(lèi)對(duì)象,在delete的時(shí)候系統(tǒng)不會(huì)調(diào)用派生類(lèi)的析構(gòu)函數(shù)。存在內(nèi)存泄漏。
解決方法:
? 把父類(lèi)的析構(gòu)函數(shù)寫(xiě)成虛函數(shù)。
結(jié)論
在public繼承中,基類(lèi)對(duì)派生類(lèi)及其對(duì)象的操作,只能影響到那些從基類(lèi)繼承下來(lái)的成員,如果想要用基類(lèi)對(duì)非繼承成員進(jìn)行操作,則要把基類(lèi)的這個(gè)函數(shù)定義為虛函數(shù),析構(gòu)函數(shù)自然也應(yīng)該如此。另外就是基類(lèi)中析構(gòu)函數(shù)的虛屬性也會(huì)被繼承給自雷,這樣的話子類(lèi)中的析構(gòu)函數(shù)也就自然而然的成為了虛函數(shù),雖然名字和基類(lèi)的析構(gòu)函數(shù)不相同
delete phuman的時(shí)候,肯定是要調(diào)用父類(lèi)的析構(gòu)函數(shù),但在父類(lèi)析構(gòu)函數(shù)中它要是調(diào)用子類(lèi)的析構(gòu)函數(shù),那么Human這個(gè)類(lèi)中的析構(gòu)函數(shù)就要聲明為virtual的,也就是說(shuō)C++中為了獲得運(yùn)行時(shí)的多態(tài)行為,所調(diào)用的成員函數(shù)必須得是virtual的。
所以
- 一個(gè)類(lèi)要想做基類(lèi),我們務(wù)必要把這個(gè)類(lèi)的析構(gòu)函數(shù)寫(xiě)成virtual析構(gòu)函數(shù)
- 只要基類(lèi)的析構(gòu)函數(shù)是虛函數(shù),就能保證我們delete基類(lèi)指針的時(shí)候能夠運(yùn)行正確的析構(gòu)函數(shù)版本。
- 普通類(lèi)我們可以不寫(xiě)虛析構(gòu)函數(shù),但如果是基類(lèi),就必須寫(xiě)一個(gè)析構(gòu)函數(shù),而且這個(gè)析構(gòu)函數(shù)還必須是虛析構(gòu)函數(shù)
- 虛函數(shù)是會(huì)怎家內(nèi)存開(kāi)銷(xiāo)的,類(lèi)里面定義虛函數(shù),編譯器就會(huì)給這個(gè)類(lèi)增加虛函數(shù)表,在這個(gè)表里存放虛函數(shù)指針。
友元函數(shù)、友元類(lèi)、友元成員函數(shù)
友元函數(shù)
讓一個(gè)函數(shù)稱(chēng)為一個(gè)類(lèi)的友元函數(shù),那么這個(gè)函數(shù)就能夠訪問(wèn)類(lèi)的所有成員,包括private,protected
友元函數(shù)的聲明是放在類(lèi)中,友元函數(shù)不屬于類(lèi)成員,所以友元函數(shù)聲明不受public,protected,private限制
友元類(lèi)
類(lèi)可以把其他的類(lèi)定義為友元類(lèi),如果你是我的友元類(lèi),那么你就可以在你的成員函數(shù)中訪問(wèn)我的所有數(shù)據(jù)成員
class A{ private:int data;friend class B;//友元類(lèi)的聲明,如果此處報(bào)錯(cuò),可以在class A前添加一個(gè)類(lèi)B的聲明。 }class B { public:void callCAF(int x, A &a){a.data = x;//正常情況下不行,因?yàn)閐ata是私有} }注意:每個(gè)類(lèi)都負(fù)責(zé)控制自己的友元類(lèi)和友元函數(shù)
- 友元關(guān)系不能被繼承
- 友元關(guān)系是單向,即類(lèi)C是類(lèi)A的朋友,但A不一定是C的朋友
- 友元關(guān)系沒(méi)有傳遞性,比如類(lèi)B是類(lèi)A的友元類(lèi),類(lèi)C是類(lèi)B的友元類(lèi),者并不代表類(lèi)C是類(lèi)的友元類(lèi)
友元成員函數(shù)
讓一個(gè)類(lèi)中的某些函數(shù)可以訪問(wèn)該類(lèi)的所有成員。
寫(xiě)這種友元成員函數(shù),必須注意代碼組織結(jié)構(gòu),因?yàn)檫@里有聲明和定義的組織結(jié)構(gòu)在里邊。
一個(gè)類(lèi)中只有public的函數(shù)才能稱(chēng)為其他類(lèi)的友元成員函數(shù)
friend void B::func();//該類(lèi)前面必須include "B.h"總結(jié)
- 允許在特定情況下某些非成員函數(shù)訪問(wèn)類(lèi)的protected,private的成員,從而提出友元概念,使訪問(wèn)protected,private成員更靈活。
- 然而破壞了類(lèi)的封裝性,降低了類(lèi)的可靠性和可維護(hù)性。
RTTI、dynamic_cast、typeid、虛函數(shù)表
RTTI是什么
(Run Time Type Identification)運(yùn)行時(shí)類(lèi)型識(shí)別;通過(guò)運(yùn)行時(shí)類(lèi)型識(shí)別,程序能夠使用基類(lèi)的指針或者引用來(lái)檢查這些指針或者引用所指的對(duì)象的實(shí)際派生類(lèi)型。
Human*phuman = new Man; Human &q = *phuman;//*phuman表示指針phuman所指向的對(duì)象RTTI我們可以把這個(gè)稱(chēng)呼看成是一種系統(tǒng)提供給我們的一種能力,或者一種功能。這種功能或者能力是通過(guò)兩個(gè)運(yùn)算符來(lái)體現(xiàn):
- dynamic_cast運(yùn)算符:能夠基類(lèi)的指針或者引用安全的轉(zhuǎn)換為派生類(lèi)的指針或者引用;
- typeid運(yùn)算符:返回指針或者引用所指向?qū)ο蟮膶?shí)際類(lèi)型
要想讓RTTI的兩個(gè)運(yùn)算符能夠正常工作,那么基類(lèi)中必須至少要有一個(gè)虛函數(shù),不然這兩個(gè)運(yùn)算符工作的結(jié)構(gòu)就可能跟我們預(yù)期不符。因?yàn)橹挥刑摵瘮?shù)的存在,這兩個(gè)運(yùn)算符才會(huì)使用指針或者引用所綁定的對(duì)象的動(dòng)態(tài)類(lèi)型(你new的類(lèi)型);
dynamic_cast:
Human *phuman = new Man; Man *p = (man*)(phuman); Woman *p1 = (Woman*)(phuman);//不安全,因?yàn)閜human指向的是mandynamic_cast如果能夠轉(zhuǎn)換成功,說(shuō)明這個(gè)指針實(shí)際上是要轉(zhuǎn)換到的那個(gè)理性。這個(gè)運(yùn)算符能夠做安全檢查
Human *phuman = new Man; Man *pman = dynamic_cast<Man*>(phuman); if(pman != nullptr) {cout << "phuman實(shí)際是一個(gè)Man類(lèi)型" << endl; } else {cout << "phuman不是一個(gè)Man類(lèi)型" << endl; }對(duì)于引用,如果用dynamic_cast轉(zhuǎn)換失敗,則系統(tǒng)會(huì)拋出一個(gè)std::bad_cast異常,用try{}catch{}處理。
Human *phuman = new Man; Human &q = *phuman; try{Man & menbm = dynamic_cast<Man&>(q);cout << "phuman實(shí)際是一個(gè)Man類(lèi)型" << endl;//這里調(diào)用Man類(lèi)型的成員函數(shù)都是安全的。 } catch(std::bad_cast){cout << "phuman實(shí)際不是一個(gè)Man類(lèi)型" << endl; }typeid運(yùn)算符
- typeid(類(lèi)型)
- typeid(表達(dá)式)
可以拿到對(duì)象類(lèi)型信息;typeid就會(huì)返回一個(gè)常量對(duì)象的引用,這個(gè)常量對(duì)象是一個(gè)標(biāo)準(zhǔn)庫(kù)類(lèi)型typeid_info(類(lèi)/類(lèi)類(lèi)型)。
Human *phuman = new Man; Human &q = *phuman; cout << typeid(*phuman).name() << endl;//Class Mantypeid的主要用途是為了比較兩個(gè)指針是否指向同一種類(lèi)型的對(duì)象;
-
兩個(gè)指針定義的類(lèi)型相同(Human)
Human *phuman = new Man; Human *phuman2 = new Woman; if(typeid(phuman) == typeid(phuman2)) {cout << "phuman和phuman2是同一種指針類(lèi)型[看指針定義]" <<endl; }不管兩個(gè)指針new的是啥,typeid都相等,只看前面定義的
-
比較對(duì)象時(shí),看的是new出來(lái)的是那個(gè)對(duì)象或者該指針指向的是哪個(gè)對(duì)象,和定義該指針時(shí)定義的類(lèi)型沒(méi)關(guān)系。
Human *phuman = new Man; Man *phuman2 = new Man; Human *phuman3 = phuman2; if(typeid(*phuman) == typeid(*phuman2))//不要掉* {cout << "phuman和phuman2指向的類(lèi)型相同" << endl; } if(typeid(*phuman) == typeid(Man))//直接可以接類(lèi)型名 {}強(qiáng)調(diào):基類(lèi)必須有虛函數(shù)。不然條件不成立。如果不含有虛函數(shù),則typeid()返回的是表達(dá)式的靜態(tài)類(lèi)型,即定義時(shí)的類(lèi)型。
type_info類(lèi)
typeid就會(huì)返回一個(gè)常量對(duì)象的引用,這個(gè)常量對(duì)象時(shí)一個(gè)標(biāo)準(zhǔn)庫(kù)類(lèi)型type_info(類(lèi)/類(lèi)類(lèi)型)
-
.name():名字,c風(fēng)格的字符串
const typeid_info &tp = typeid(*phuman); cout << tp.name() << endl; -
== ,!=
RTTI與虛函數(shù)表
C++中,如果類(lèi)里含有虛函數(shù)。編譯器就會(huì)對(duì)該類(lèi)產(chǎn)生一個(gè)虛函數(shù)表。虛函數(shù)表里有很多項(xiàng),每一項(xiàng)都是一個(gè)指針。每個(gè)指針指向的是這個(gè)類(lèi)里的各個(gè)虛函數(shù)的入口地址。虛函數(shù)表項(xiàng)里,第一表項(xiàng)很特殊,它指向的不是虛函數(shù)的入口地址,它指向的不是虛函數(shù)的入口地址,它指向的實(shí)際上是這個(gè)類(lèi)所關(guān)聯(lián)的type_info對(duì)象
基類(lèi)與派生類(lèi)關(guān)系的詳細(xì)再探討
派生類(lèi)對(duì)象模型簡(jiǎn)述‘
子類(lèi)對(duì)象包含多個(gè)組成部分(也就是多個(gè)子對(duì)象):
- 一個(gè)是含有派生類(lèi)自己定義的成員變量、成員函數(shù)的子對(duì)象。
- 一個(gè)是該派生類(lèi)所繼承的基類(lèi)的子對(duì)象,這個(gè)子對(duì)象中華包含的是基類(lèi)中定義的成員變量、成員函數(shù)(派生類(lèi)對(duì)象含有基類(lèi)對(duì)應(yīng)的組成部分)
基類(lèi)指針可以new派生類(lèi)對(duì)象,因?yàn)榕缮?lèi)對(duì)象含有基類(lèi)部分,所以我們是可以把派生類(lèi)對(duì)象當(dāng)成基類(lèi)對(duì)象使用的
Human *phuman = new Man編譯器幫助我們做了隱式類(lèi)型轉(zhuǎn)換,這種轉(zhuǎn)換的好處就是有些需要基類(lèi)引用的地方你可以用這個(gè)派生類(lèi)對(duì)象的引用來(lái)代替,如果有些需要基類(lèi)指針的地方,也可以用派生類(lèi)對(duì)象的指針來(lái)代替。(不太理解!)
派生類(lèi)構(gòu)造函數(shù)
派生類(lèi)實(shí)際是使用基類(lèi)的構(gòu)造函數(shù)來(lái)初始化它的基類(lèi)部分,基類(lèi)控制基類(lèi)的成員初始化,派生類(lèi)控制派生類(lèi)成員的初始化。
傳遞參數(shù)給基類(lèi)構(gòu)造函數(shù)問(wèn)題:通過(guò)派生類(lèi)的初始化列表
class A { public:A(int i):m_value(i){}virtual ~A(){} private:int m_value; }; class B : public A { public: B(int i,int j, int k):A(i),m_valueB(k){}//子類(lèi)初始化列表為父類(lèi)構(gòu)造函數(shù)傳遞參數(shù)。 private:int m_valueB; };先執(zhí)行基類(lèi)的構(gòu)造函數(shù),再執(zhí)行派生類(lèi)的構(gòu)造函數(shù),釋放時(shí)先執(zhí)行派生類(lèi)的析構(gòu)函數(shù),再執(zhí)行基類(lèi)的析構(gòu)函數(shù)。
既當(dāng)父類(lèi)又當(dāng)子類(lèi)
class gra{...}; class fa :public gra {...}; class son :public fa {...};繼承關(guān)系一直傳遞,構(gòu)成了一種繼承鏈,最終結(jié)果就是派生類(lèi)son包含它的直接基類(lèi)的成員以及每個(gè)間接基類(lèi)的成員。
不想當(dāng)基類(lèi)的類(lèi)
class A final { public:A(int i):m_value(i){}virtual ~A(){} private:int m_value; }; class B final : public A //此時(shí)此處報(bào)錯(cuò).兩種final放的位置 { public: B(int i,int j, int k):A(i),m_valueB(k){}//子類(lèi)初始化列表為父類(lèi)構(gòu)造函數(shù)傳遞參數(shù)。 private:int m_valueB; };final:加再類(lèi)名后面,此時(shí)這個(gè)類(lèi)不能作基類(lèi)(C++11)
靜態(tài)類(lèi)型與動(dòng)態(tài)類(lèi)型
靜態(tài)類(lèi)型:變量聲明時(shí)候的類(lèi)型。靜態(tài)類(lèi)型編譯的時(shí)候時(shí)已知的。
動(dòng)態(tài)類(lèi)型:指的是這個(gè)指針/引用所代表的(所表達(dá)的)內(nèi)存中的對(duì)象的類(lèi)型。
動(dòng)態(tài)類(lèi)型是再運(yùn)行時(shí)才能知道的。
動(dòng)態(tài)類(lèi)型和靜態(tài)類(lèi)型只有基類(lèi)指針/引用才存在不一致的情況。
如果不是基類(lèi)的指針/引用,那么靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型永遠(yuǎn)是一致的。
Human *phuman = new Man()//基類(lèi)指針指向一個(gè)派生類(lèi)對(duì)象 HUman &q = *phuman;//基類(lèi)引用綁定到派生類(lèi)對(duì)象派生類(lèi)向基類(lèi)的隱式類(lèi)型轉(zhuǎn)換
Human *phuman = new Man()//基類(lèi)指針指向一個(gè)派生類(lèi)對(duì)象 HUman &q = *phuman;//基類(lèi)引用綁定到派生類(lèi)對(duì)象編譯器隱式的做了派生類(lèi)對(duì)基類(lèi)的轉(zhuǎn)換
這種轉(zhuǎn)換之所以能夠成功,是因?yàn)槊總€(gè)派生類(lèi)都包含一個(gè)基類(lèi)對(duì)象部分,所以基類(lèi)的引用或者指針是可以綁到基類(lèi)對(duì)象這部分
基類(lèi)對(duì)象能獨(dú)立存在,也能作為派生類(lèi)對(duì)象的一部分存在
并不存在從基類(lèi)到派生類(lèi)的自動(dòng)類(lèi)型轉(zhuǎn)換。
Man man; Human *phuman = &man;//可以 Man *pman = phuman;//不可以,編譯器通過(guò)靜態(tài)類(lèi)型推斷轉(zhuǎn)換合法性。發(fā)現(xiàn)基類(lèi)不能轉(zhuǎn)換為派生類(lèi)如果基類(lèi)中有虛函數(shù)的話,可以通過(guò)dynamic_cast可以轉(zhuǎn)換。
Man *pman = dynamic_cast<Man*>(phuman);父類(lèi)與子類(lèi)之間的拷貝與賦值
Man man; Human human(man);//用派生類(lèi)對(duì)象來(lái)定義并初始化基類(lèi)對(duì)象。這個(gè)會(huì)導(dǎo)致基類(lèi)的拷貝構(gòu)造函數(shù)的執(zhí)行用派生類(lèi)給基類(lèi)對(duì)象初始化或者賦值時(shí),只有該派生類(lèi)對(duì)象的基類(lèi)部分會(huì)被拷貝和賦值。
左值、右值、左值引用、右值引用、move
左值和右值
int i = 10;//對(duì)象:一塊內(nèi)存區(qū)域左值:能用在賦值語(yǔ)句等號(hào)左側(cè)的東西,它能夠代表一個(gè)地址
右值:不能作左值的值就是右值;右值不能出現(xiàn)再賦值語(yǔ)句中等號(hào)的左側(cè)。
結(jié)論:C++中的一條表示式,要么就是右值,要么就是左值,不可能兩者都不是。
左值有的時(shí)候能夠被當(dāng)作右值使用、
i = i+1;//i 是個(gè)左值,不是個(gè)右值,雖然它出現(xiàn)再了等號(hào)右邊i用字啊等號(hào)右邊的時(shí)候,我們說(shuō)i有一種右值屬性(不是右值)。
i出現(xiàn)再等號(hào)左邊,用的時(shí)i代表的內(nèi)存中的地址,我們說(shuō)i有一種左值屬性。
左值引用和右值引用
左值引用綁定到一個(gè)左值
右值引用綁定到一個(gè)右值
i++是右值,++i是左值
引用的分類(lèi):
-
左值引用:
int &i = var; -
const 引用:不改變值的引用
-
右值引用:
int &&i = 100;
右值引用引入的目的:
- C++11引入,&&代表一種新的數(shù)據(jù)類(lèi)型,引入新數(shù)據(jù)類(lèi)型肯定有目的
- 提高程序運(yùn)行效率 。把拷貝對(duì)象變成移動(dòng)對(duì)象來(lái)提高程序運(yùn)行效率
- 移動(dòng)對(duì)象如何發(fā)生.&&(應(yīng)付移動(dòng)構(gòu)造函數(shù),應(yīng)付移動(dòng)賦值運(yùn)算符的)
std::move函數(shù)
- C++11標(biāo)準(zhǔn)庫(kù)里的新函數(shù)
- std::move:把一個(gè)左值強(qiáng)制轉(zhuǎn)換成一個(gè)右值。帶來(lái)結(jié)果就是一個(gè)右值可以綁上去了。不存在移動(dòng)的操作。
再調(diào)用std::move后,不該再對(duì)參數(shù)使用。
左值右值總結(jié)說(shuō)明
臨時(shí)對(duì)象深入探討、解析、提高性能手段
對(duì)象移動(dòng)、移動(dòng)構(gòu)造函數(shù)、移動(dòng)賦值運(yùn)算符
對(duì)象移動(dòng)的概念
C++11提出,將一個(gè)對(duì)象的地址移動(dòng)給另一個(gè)對(duì)象,所有者變更。
移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符概念
引入的目的在于提高效率
- A移動(dòng)B那么A對(duì)象就不能再使用了
- 并不是把內(nèi)存中的數(shù)據(jù)從一個(gè)地址移動(dòng)到另外一個(gè)地址。只是內(nèi)存所有者變更
如果有其它參數(shù)必須要有默認(rèn)值
移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符應(yīng)該完成的功能
- 完成必要的內(nèi)存移動(dòng),站短原對(duì)象和內(nèi)存的關(guān)系
- 確保移動(dòng)后原對(duì)象處于一種“即使被銷(xiāo)毀也沒(méi)有問(wèn)題”的一種狀態(tài)。
移動(dòng)構(gòu)造函數(shù)的演示
class B{ public:int m_bm;B():b_m(100){cout<<"類(lèi)B的構(gòu)造函數(shù)"<<endl;}B(const B&tmp):m_bm(tmp.m_bm){cout << "類(lèi)B的拷貝構(gòu)造函數(shù)"<<endl;}virtual ~B(){cout << "類(lèi)B的析構(gòu)函數(shù)" << endl;} };class A{ public:A():m_pb(new B()){cout<<"類(lèi)A的構(gòu)造函數(shù)"<<endl;}A(const A&tmpa):m_pb(new B(*(tmp.m_pb))){cout << " 類(lèi)A的拷貝構(gòu)造函數(shù)" <<ednl;}//再返回臨時(shí)對(duì)象時(shí),不再調(diào)用拷貝構(gòu)造函數(shù),而是調(diào)用移動(dòng)構(gòu)造函數(shù),提高了系統(tǒng)效率//noexcept:通知標(biāo)準(zhǔn)庫(kù)移動(dòng)構(gòu)造函數(shù)不拋出任何異常(提高編譯器工作效率) A(A&&tmpa)noexcept:m_pb(tmpa.m_pb)//移動(dòng)接管{tmpa.m_pb = nullptr;//斷開(kāi)cout << " 類(lèi)A的移動(dòng)構(gòu)造函數(shù)" <<ednl;}A &operator=(const A&src){if(this == &src)return *this;delete m_pb;m_pb = new B(*(src.m_pb));cout << "類(lèi)A的拷貝賦值運(yùn)算符" <<endl;return *this;}A &operator=(A&&src)noexcept{if(this == &src)return *this;delete m_pb;m_pb = src.m_pb;src.m_pb = nullptr;cout << "類(lèi)A的移動(dòng)賦值運(yùn)算符" <<endl;}virtual ~A(){delete m_pb;cout << "類(lèi)A的析構(gòu)函數(shù)" << endl;}private:B *m_pb; };static A getA()//靜態(tài)函數(shù),只能再本文件中使用。 {A a;return a; } A a = getA();//1個(gè)構(gòu)造函數(shù),一個(gè)移動(dòng)構(gòu)造函數(shù),一個(gè)析構(gòu)函數(shù) A a1(a);//1個(gè)拷貝構(gòu)造函數(shù) A a2(std::move(a));//移動(dòng)構(gòu)造函數(shù) A &&a2(std::move(a));//沒(méi)有建立新對(duì)象,就只是一個(gè)右值引用移動(dòng)賦值運(yùn)算符
A &operator=(A&&src)noexcept{if(this == &src)return *this;delete m_pb;m_pb = src.m_pb;src.m_pb = nullptr;cout << "類(lèi)A的移動(dòng)賦值運(yùn)算符" <<endl;} a1 = std::move(a2);//這樣就會(huì)調(diào)用移動(dòng)賦值運(yùn)算符合成的移動(dòng)操作
某些條件下,編譯器能合成移動(dòng)構(gòu)造函數(shù),移動(dòng)賦值運(yùn)算符
- 有自己的拷貝構(gòu)造函數(shù),自己的拷貝賦值運(yùn)算符,或者自己的析構(gòu),那么編譯器就不會(huì)為它合成移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符。所以有一些類(lèi)時(shí)沒(méi)有移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符的。
- 如果沒(méi)有提供移動(dòng)構(gòu)造函數(shù),編譯器會(huì)調(diào)用自己寫(xiě)的拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符來(lái)代替
- 只有一個(gè)類(lèi)沒(méi)定義任何自己版本的拷貝構(gòu)造成員(沒(méi)有拷貝構(gòu)造函數(shù)也沒(méi)有拷貝賦值運(yùn)算符),且類(lèi)的每個(gè)非靜態(tài)成員都可以移動(dòng)式,編譯器才會(huì)合成移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符
什么叫成員可以移動(dòng)
- 內(nèi)置類(lèi)型可以移動(dòng)
- 類(lèi)類(lèi)型的成員,則這個(gè)類(lèi)要有對(duì)應(yīng)的移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符
總結(jié)
- 盡量給類(lèi)增加移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符
- noexcept
- 該給nullptr的就要給nullptr,讓被移動(dòng)對(duì)象隨時(shí)處于一種能夠被析構(gòu)的狀態(tài)
- 沒(méi)有移動(dòng),就會(huì)調(diào)用拷貝代替。
繼承的構(gòu)造函數(shù)、多重繼承、虛繼承
繼承的構(gòu)造函數(shù)
一個(gè)類(lèi)只繼承其直接基類(lèi)(父類(lèi))的構(gòu)造函數(shù)。默認(rèn)、拷貝、移動(dòng)構(gòu)造函數(shù)是不能被繼承的
using A::A;//繼承A的構(gòu)造函數(shù)(寫(xiě)在B類(lèi)中)//編譯器遇到這條道嗎的時(shí)候,會(huì)把基類(lèi)的每個(gè)構(gòu)造函數(shù),都生成一個(gè)與之對(duì)應(yīng)的派生類(lèi)構(gòu)造函數(shù)編譯器生成的構(gòu)造函數(shù)函數(shù)體為空。
B(int i,int j,int k):A(i,j,k){};如果基類(lèi)A的構(gòu)造函數(shù)有默認(rèn)參數(shù)的話,那么編譯器遇到這種using A::A的時(shí)候,就會(huì)幫咱們?cè)谂缮?lèi)中構(gòu)造出多個(gè)構(gòu)造函數(shù)來(lái)。
- 第一個(gè)構(gòu)造函數(shù)是帶有所有參數(shù)的構(gòu)造函數(shù)
- 其余的構(gòu)造函數(shù),每個(gè)分別省略掉一個(gè)默認(rèn)參數(shù)
如果基類(lèi)含有多個(gè)構(gòu)造函數(shù),則多數(shù)情況下,派生類(lèi)會(huì)繼承所有這些構(gòu)造函數(shù),但如下例外:
- 如果你在派生類(lèi)中定義的構(gòu)造函數(shù)與基類(lèi)構(gòu)造函數(shù)有相同的參數(shù)列表,那么從基類(lèi)中繼承來(lái)的構(gòu)造函數(shù)會(huì)被你在派生類(lèi)中的定義覆蓋掉
- 默認(rèn)、拷貝、移動(dòng)構(gòu)造函數(shù)不會(huì)被繼承
如果類(lèi)B只含有using A::A從A類(lèi)繼承來(lái)的構(gòu)造桉樹(shù)的話,那么編譯器是會(huì)給它合成默認(rèn)的構(gòu)造函數(shù)的。
多重繼承
- 如果從多個(gè)父類(lèi)繼承成子類(lèi)
-
靜態(tài)成員變量,屬于類(lèi),而不屬于類(lèi)對(duì)象,公有繼承子類(lèi)也可見(jiàn)。
-
派生類(lèi)構(gòu)造函數(shù)與析構(gòu)函數(shù)
構(gòu)造一個(gè)派生類(lèi)對(duì)象,也將同時(shí)構(gòu)造并初始化所有的基類(lèi)子對(duì)象
派生類(lèi)的構(gòu)造函數(shù)初始化列表只初始化它的直接基類(lèi),就會(huì)讓所有類(lèi)得到初始化
派生類(lèi)構(gòu)造函數(shù)初始化列表將實(shí)參分別傳遞給每個(gè)直接基類(lèi),基類(lèi)的構(gòu)造順序跟“派生列表”中基類(lèi)的出現(xiàn)順序保持一致。先構(gòu)造的后釋放。
顯示的初始化基類(lèi)和隱式的初始化基類(lèi):子類(lèi)的初始化列表中,如果使用基類(lèi)的默認(rèn)構(gòu)造函數(shù),則可以不顯式的寫(xiě)出來(lái),系統(tǒng)會(huì)調(diào)用父類(lèi)的默認(rèn)構(gòu)造函數(shù)初始化父類(lèi)。
-
從多個(gè)父類(lèi)繼承構(gòu)造函數(shù)
如果一個(gè)類(lèi)從它的基類(lèi)中繼承了相同的構(gòu)造函數(shù),這個(gè)類(lèi)必須為該構(gòu)造函數(shù)定義它自己的版本。
類(lèi)型轉(zhuǎn)換
基類(lèi)指針可以指向一個(gè)派生類(lèi)對(duì)象:編譯器幫助我們隱式執(zhí)行這種派生類(lèi)到基類(lèi)的轉(zhuǎn)換。轉(zhuǎn)換成功的原因是每個(gè)派生類(lèi)對(duì)象都包含基類(lèi)的部分。所以基類(lèi)的引用或指針都可以綁定到基類(lèi)對(duì)象這部分上類(lèi)
虛基類(lèi)、虛繼承(虛派生)
派生列表中同一個(gè)基類(lèi)只能出現(xiàn)一次,但如下兩種情況例外
- 派生類(lèi)可以通過(guò)它的直接基類(lèi)分別繼承同一個(gè)間接基類(lèi)
- 直接繼承某個(gè)基類(lèi),然后通過(guò)另一個(gè)基類(lèi)間接繼承,然后通過(guò)另一個(gè)基類(lèi)間接繼承該類(lèi)。
虛基類(lèi):無(wú)論這個(gè)類(lèi)在繼承體系中出現(xiàn)多少次,派生類(lèi)中都指揮包含唯一一個(gè)共享的虛基類(lèi)(Grand)子內(nèi)容。
如果不用虛基類(lèi)則會(huì)出現(xiàn)訪問(wèn)不明確。
虛繼承只對(duì)C類(lèi)有意義,對(duì)于C的父類(lèi)A,A2沒(méi)有意義,A,A2從Gand虛繼承,只影響到從A,A2這些類(lèi)中進(jìn)一步派生出來(lái)的類(lèi)C。而對(duì)A,A2本身沒(méi)有影響
每個(gè)Grand的子類(lèi)都要虛繼承Gand類(lèi),那么才能保證Grand的孫子類(lèi) 能夠虛繼承Grand
只要子類(lèi)中都加virtual繼承,那么Grand就自然稱(chēng)為了虛基類(lèi),virtual表達(dá)了一種意愿:表示后續(xù)從我(類(lèi)A,A2)派生的類(lèi)中應(yīng)該共享虛基類(lèi)的同一份實(shí)例。
class A:virtual public Gand{}虛基類(lèi)時(shí),孫子初始化爺爺。
說(shuō)明:
- 現(xiàn)在時(shí)C類(lèi)初始化Gand,如果C類(lèi)還有孩子,則讓用C類(lèi)的孩子初始化,所以,Grand時(shí)最底層的類(lèi)初始化。
- 初始化順序:先初始化虛基類(lèi)部分,然后按照派生列表中出現(xiàn)順序來(lái)初始化其他類(lèi)
- 多個(gè)虛基類(lèi)的初始化順序:按照派生列表中的直接基類(lèi)來(lái)往回追隨,看是否這些直接基類(lèi)含有虛基類(lèi)
總結(jié)
小心虛繼承,不提倡多重繼承使用。
簡(jiǎn)單,不容易出現(xiàn)二義性,是在必要的情況下才進(jìn)行虛繼承
類(lèi)型轉(zhuǎn)換構(gòu)造函數(shù)、運(yùn)算符、類(lèi)成員指針
2.2 類(lèi)對(duì)象轉(zhuǎn)換為函數(shù)指針
typedef void(*tfpoint)(int);//定義一個(gè)函數(shù)指針類(lèi)型 using tfpoint = void(*)(int);? 兩種方式等價(jià)
class TestInt { public:using tfpoint = void(*)(int); public:static void mysfunc(int v1){cout << v1 << endl;}operator tfpoint(){return mysfunc;//返回函數(shù)地址指針}};int main(){TestInt test;test(123);//這樣就可以調(diào)用mysfunc函數(shù),首先調(diào)用類(lèi)型轉(zhuǎn)換運(yùn)算符,類(lèi)型轉(zhuǎn)換運(yùn)算符返回一個(gè)函數(shù)指針,通過(guò)函數(shù)指針調(diào)用函數(shù)。test.operator TestInt::tfpoint()(123);//等價(jià)于這種,顯式調(diào)用 }類(lèi)型轉(zhuǎn)換的二義性問(wèn)題
operator int(){}; operator double(){};一個(gè)類(lèi)中定義兩種會(huì)產(chǎn)生二義性問(wèn)題。
在一個(gè)類(lèi)中盡量只出現(xiàn)一個(gè)類(lèi)型轉(zhuǎn)換運(yùn)算符。
class CT1 { public:CT1(int ct1){}//類(lèi)型轉(zhuǎn)換構(gòu)造函數(shù) }; class CT2 { public:CT2(int ct2){}//類(lèi)型轉(zhuǎn)換構(gòu)造函數(shù) }; void testfunc(const CT1&t1)(){}; void testfunc(const CT2&t2)(){}; int main(){testfunc(123);//存在二義性問(wèn)題,因?yàn)?23可以轉(zhuǎn)換為CT1也可以轉(zhuǎn)換為CT2,編譯器不知道調(diào)用哪一個(gè)函數(shù)。//可修改為testfunc(CT1(123));//明確調(diào)用的是void testfunc(const CT1&); }類(lèi)成員函數(shù)指針:是個(gè)指針,指向類(lèi)成員函數(shù)
4.1. 對(duì)于普通函數(shù)
格式:類(lèi)名::*函數(shù)指針變量名。 來(lái)聲明普通成員函數(shù)指針
? &類(lèi)名::成員函數(shù)名。 來(lái)獲取類(lèi)成員函數(shù)地址,這是真正的內(nèi)存地址
class CT { public:CT(int ct){}//類(lèi)型轉(zhuǎn)換構(gòu)造函數(shù)void ptfunc(int tmpvalue){cout<<"ptfun普通成員函數(shù)被調(diào)用,value = "<<tmpvalue<<endl;}virtual void virtualfunc(int tmpvalue){cout<<"virtualfunc虛成員函數(shù)被調(diào)用,value = "<<tmpvalue<<endl;}static void staticfunc(int tmpvalue){cout<<"staticfunc靜態(tài)成員函數(shù)被調(diào)用,value = "<<tmpvalue<<endl;} }; int main(){void(CT::*myfpoint)(int);//一個(gè)類(lèi)成員函數(shù)指針變量的定義,變量名為myfpointmypoint = &CT::ptfunc;CT adc;(abc.*myfpoint)(100);CT *pt = &abc;(abc->*myfpoint)(100); }注意:成員函數(shù)是屬于類(lèi)的,不屬于類(lèi)對(duì)象,只要有類(lèi)在就有成員函數(shù)地址在。但是入藥使用這個(gè)成員函數(shù)指針,就必須把它綁定到一個(gè)類(lèi)對(duì)象上才能調(diào)用。
使用格式 :類(lèi)對(duì)象名.*函數(shù)指針變量名
如果是個(gè)對(duì)象指針,則調(diào)用格式:指針名->*函數(shù)指針變量名
4.2 對(duì)于虛函數(shù)
void(CT::*myfpointvirtu)(int) = &CT::virtualfunc;也必須綁定到類(lèi)對(duì)象上才能使用。
4.3 對(duì)于靜態(tài)成員函數(shù)
使用格式:*函數(shù)指針變量名 聲明靜態(tài)成員函數(shù)指針,使用&類(lèi)名::成員函數(shù)名。獲取類(lèi)成員函數(shù)地址
void(*myfpointstatic)(int) = &CT::staticfunc; myfpointstatic(100);//可以直接調(diào)用。類(lèi)成員變量指針
5.1 對(duì)于普通成員變量
int CT::*mp = &CT::m_a;//定義了個(gè)類(lèi)成員變量指針并不是真正意義上的指針,它不指向內(nèi)存中某個(gè)地址,而是該成員變量,與該類(lèi)對(duì)象指針的偏移量
當(dāng)類(lèi)中有虛函數(shù),則編譯器會(huì)給該類(lèi)生成虛函數(shù)表,當(dāng)生成對(duì)象時(shí),如果這個(gè)類(lèi)中有虛函數(shù)表,則對(duì)象中,就會(huì)有一個(gè)指向這個(gè)虛函數(shù)表的指針,這個(gè)指針占有4個(gè)字節(jié)。所以第一個(gè)成員變量的地址是0x00000004(偏移四個(gè)字節(jié))
CT ctestmp; ctestmp.*mp = 189;//通過(guò)類(lèi)成員變量指針來(lái)修改變量值5.2 對(duì)于靜態(tài)成員變量
靜態(tài)成員變量屬于類(lèi),其地址不是偏移量,是一個(gè)真正意義上的地址。
int *stcp = &CT::m_stca;//m_stca是一個(gè)CT類(lèi)的靜態(tài)成員變量 *stcp = 535;//等價(jià)于CT::m_stca= 535;總結(jié)
C++對(duì)象模型(高級(jí)的知識(shí))
模版概念、函數(shù)模版定義、調(diào)用
概述
vector:vector類(lèi)模版
- 所謂泛型編程,是以獨(dú)立與任何特定類(lèi)型的方式編寫(xiě)代碼。使用泛型編程時(shí),我們需要提供具體程序?qū)嵗僮鞯念?lèi)習(xí)慣或者值
- 模版是泛型編程的基礎(chǔ)。模版是創(chuàng)建類(lèi)或者函數(shù)的藍(lán)圖或者共識(shí)。我們給這些藍(lán)圖或者公式提供足夠的信息,讓這些藍(lán)圖或者公式真正的轉(zhuǎn)變?yōu)榫唧w的類(lèi)或者函數(shù),這種轉(zhuǎn)變發(fā)生在編譯時(shí)。
- 模版支持將類(lèi)型作為參數(shù)的程序設(shè)計(jì)方式,從而實(shí)現(xiàn)了對(duì)泛型程序設(shè)計(jì)的直接支持,也就是說(shuō),C++模板機(jī)制允許在定義類(lèi)、函數(shù)時(shí)將類(lèi)型作為參數(shù)
模板一般分為函數(shù)模板和類(lèi)模板。
函數(shù)模版的定義
template<typename T> T funcadd(T i1,T i2) {T addhe = i1 + i2;return addhe; }-
模版定義是用template關(guān)鍵開(kāi)頭的,后邊跟<>,<>里邊叫模板參數(shù)列表(模板實(shí)參),如果模板列表里有多個(gè)參數(shù),則用逗號(hào)隔開(kāi)。<typename T,typename Q>;<>里必須至少得有一個(gè)模板參數(shù),模板參數(shù)前面有個(gè)typename/class關(guān)鍵字,表示這是類(lèi)型名。模板參數(shù)列表里表示在函數(shù)定義中用到的“類(lèi)型”或者“值”,也和函數(shù)參數(shù)列表類(lèi)似。
-
我們用的時(shí)候,有的時(shí)候需要指定模板實(shí)參給他,指定的時(shí)候我們要用<>把模板實(shí)參包起來(lái)。有的時(shí)候又不需要我們制定模板實(shí)參給他,系統(tǒng)自己能夠根據(jù)一些信息推斷出來(lái)。
-
funcadd這個(gè)函數(shù)聲明了一個(gè)名字為T(mén)的類(lèi)型參數(shù)。編譯器在編譯時(shí)會(huì)根據(jù)針對(duì)funcadd調(diào)用時(shí)確定。
函數(shù)模版的使用
函數(shù)模版調(diào)用和函數(shù)調(diào)用區(qū)別不大,調(diào)用的時(shí)候,編譯器會(huì)根據(jù)你調(diào)用這個(gè)函數(shù)模版時(shí)的實(shí)參去推斷模版參數(shù)列表里的參數(shù)(形參)的類(lèi)型。推斷的依據(jù)是調(diào)用這個(gè)函數(shù)的時(shí)候函數(shù)的實(shí)參來(lái)推斷的。
有的時(shí)候,光憑借函數(shù)實(shí)參推斷不出來(lái)模板參數(shù),這個(gè)時(shí)候我們就的用<>來(lái)主動(dòng)提供模板參數(shù)了;
funcadd(4,5);//判斷出來(lái)為int funcadd(4.8,5.9);//判斷為double型 funcadd(4,5.9);//報(bào)錯(cuò),無(wú)法推斷類(lèi)型 funcadd(4.0,5.9)//可以,為double。非類(lèi)型模板參數(shù)
- 在模板參數(shù)列表里,還可以定義非類(lèi)型參數(shù),非類(lèi)型參數(shù)代表的是一個(gè)值
- 不能用typename/class修飾,用傳統(tǒng)類(lèi)型修飾
當(dāng)模板被實(shí)例化時(shí),這種非類(lèi)型模板參數(shù)的值或者是用戶(hù)提供的,或者是編譯器推斷的,都有可能
因?yàn)閷?shí)例化這些模板是編譯器在編譯時(shí)進(jìn)行的,因此必須時(shí)常量表達(dá)式。
template<int a,int b> int funcaddv2(){int addhe = a+b;return addhe; } int main(){funcaddv2<3,4>();//顯示指定模板參數(shù),編譯時(shí)確定,不能給變量,必須時(shí)常量。 } template<typename T,int a,int b> int funcaddv2(T c) {int addhe = (int)c +a +b;return addhe; } int result = funcaddv2<int,11,12>(13); int result2 = funcaddv2<double,11,12>(13);//13會(huì)被轉(zhuǎn)為double。即以<>中提供的類(lèi)型為準(zhǔn)。 template<unsigned L1,unsigned L2> int charscomp(const char(&p1)[L1],const char(&p2)[L2]) {return strcmp(p1,p2); } int main(){int resullt3 = charscomp("test2","test");//不用提供非類(lèi)型模板參數(shù),系統(tǒng)會(huì)自動(dòng)推斷。 }模板函數(shù)可以時(shí)inline的,inline的位置放在模板參數(shù)列表之后。
函數(shù)模板可以放在頭文件中,多個(gè).cpp文件包含也不會(huì)出現(xiàn)重復(fù)定義。
類(lèi)模板概念、類(lèi)模板定義、使用
概述
用類(lèi)模板實(shí)例化一個(gè)特定的類(lèi)。
編譯器不能為類(lèi)模板推斷模板參數(shù)類(lèi)型,所以為了使用類(lèi)模板,我們必須在類(lèi)模板名后邊用<>來(lái)提供額外的信息,這些信息其實(shí)就是對(duì)應(yīng)著模板參數(shù)列表中的參數(shù)
類(lèi)模板定義
template<typename 形參名1,typename 形參名2,.......typename 形參名n> class 類(lèi)名 {}類(lèi)模板的成員函數(shù)
類(lèi)模板成員函數(shù),可以寫(xiě)在類(lèi)模板定義中,那么這種寫(xiě)在類(lèi)模板定義中的成員函數(shù)會(huì)被隱式聲明為inline函數(shù)
類(lèi)模板一旦被實(shí)例化后,那么這個(gè)模板的每個(gè)實(shí)例都會(huì)有自己版本的成員函數(shù)
所以,類(lèi)模板的成員函數(shù)具有和這個(gè)類(lèi)模板相同的模板參數(shù)(這句話的核心意思:類(lèi)模板的成員函數(shù)是有模板參數(shù)的)
如果要把類(lèi)模板成員函數(shù)的定義(函數(shù)體)寫(xiě)在類(lèi)模板定義的外邊,那么這個(gè)成員函數(shù)的模板參數(shù)就體現(xiàn)出來(lái)了。
非類(lèi)型的模板參數(shù)
template<typename T,int size = 10>//可以跟非類(lèi)型模板參數(shù) class myarray { public:void func(); }; template<typename T,int size = 10> void myarray<T,size>::func(){}- 浮點(diǎn)型不能作非類(lèi)型模板參數(shù),double,float
- 類(lèi)類(lèi)型也不能作為非類(lèi)型模板參數(shù)
模板類(lèi)的定義和實(shí)現(xiàn)都放在頭文件中。
用typename的場(chǎng)合、默認(rèn)模板參數(shù)、趣味寫(xiě)法分析
typename的使用場(chǎng)合
-
模板定義里,表明氣候的模板參數(shù)時(shí)類(lèi)型參數(shù)
typename后邊跟的是一個(gè)類(lèi)型,在函數(shù)模板和類(lèi)模板中體現(xiàn)
template
可以寫(xiě)為class,與類(lèi)定義的class不是一個(gè)意思。
-
使用類(lèi)的類(lèi)型成員,用typename來(lái)標(biāo)識(shí)這是一個(gè)類(lèi)型
::作用域運(yùn)算符
typename的必須性:訪問(wèn)類(lèi)型成員(用typedef定義的),顯式的告訴編譯器這是個(gè)類(lèi)型,而不是靜態(tài)成員。不能換成class
template<typename T> typename T::size_type getlength(const T&c){if(c.empty())return 0;return c.size(); } int main(){string mytest = "dasdad";string::size_type;//為了保證代碼的可移植性,string中有一個(gè)size_type類(lèi)型,類(lèi)似于unsigned int.string::size_type size = getlength(mytest); }函數(shù)指針做其他函數(shù)的參數(shù)
typedef int (*Funtype)(int ,int);//頭文件中。定義函數(shù)指針類(lèi)型 int mf(int tmp1, int tmp2){return tmp1+tmp2; } void testfunc(int i, int j, Funtype funcpoint){//通過(guò)函數(shù)指針調(diào)用函數(shù)int result = funcpoint(i,j);cout << result << endl; }函數(shù)模板趣味用法舉例
template<typename T, typename F> void testfunc(const T &i, const T&j, F Funtype funcpoint){int result = funcpoint(i,j);cout << result << endl; }可調(diào)用對(duì)象所代表的類(lèi)
class tc{ public:tc(){cout << "構(gòu)造函數(shù)執(zhí)行"<<endl;}tc(const tc &c){cout << "拷貝構(gòu)造函數(shù)執(zhí)行"<<endl;}//重載圓括號(hào)int operator()(int i, int j){return i+j;} } 使得testfunc可以支持函數(shù)指針,也可以支持可調(diào)用對(duì)象的圓括號(hào) testfunc(3,4,tc());//不會(huì)調(diào)用拷貝構(gòu)造函數(shù),tc()直接生成臨時(shí)對(duì)象。所以只調(diào)用構(gòu)造和函數(shù)。默認(rèn)模板參數(shù)
-
類(lèi)模板,類(lèi)模板后邊必須用<>來(lái)提供額外信息,可以有缺省值,但不能省<>,里面可以為空。
-
函數(shù)模板:老標(biāo)準(zhǔn)只能為類(lèi)模板提供默認(rèn)的參數(shù),C++11可以為函數(shù)模板提供默認(rèn)的參數(shù)
template<typename T, typename F = tc > void testfunc(const T &i, const T&j, F Funtype funcpoint = F()){int result = funcpoint(i,j);cout << result << endl; } int main(){testfunc(3,4);//同時(shí)給函數(shù)缺省值。 }
成員函數(shù)模板,顯示實(shí)例化,聲明
普通類(lèi)的成員函數(shù)模板
不管是普通類(lèi),還是類(lèi)模板,他的成員函數(shù)可以是一個(gè)函數(shù)模板,成為“成員函數(shù)模板”。不可以是虛函數(shù),否則報(bào)錯(cuò)。
類(lèi)模板的成員函數(shù)模板
template<typename C> class A { public:template<typename T2>A(T2 v1,T2 v2)//構(gòu)造函數(shù)引入新類(lèi)型T2,與類(lèi)的模板C沒(méi)關(guān)系{}template<typename T>void myft(T tmpt){} }; template<typename C> template<typename T2> A<C>::A(T2 v1, T2 v2){//..... }類(lèi)模板的模板參數(shù)必須用<>指定,成員函數(shù)模板(函數(shù)模板)的參數(shù)可以推斷
類(lèi)模板的成員函數(shù)只有為程序所用才會(huì)被實(shí)例化,也包括類(lèi)模板的普通成員函數(shù)、成員函數(shù)模板。若從未使用,則不會(huì)實(shí)例化。
模板顯式實(shí)例化,模板聲明
為了防止再多個(gè).cpp文件中都實(shí)例相同的類(lèi)模板,所以C++11提出了“顯式實(shí)例化”;通過(guò)該方法來(lái)生成多個(gè)
template A<float>;這種實(shí)例化定義只需要在一個(gè).cpp文件就可以
在其他.cpp文件中“顯式實(shí)例化”手段中的實(shí)例化聲明。
extern template A<float>;這玩意叫聲明,其他用到A的.cpp文件開(kāi)頭都要寫(xiě)這個(gè)。
extern 作用:不會(huì)在本文件中生成一個(gè)extern后邊所表示的模板的實(shí)例化版本代碼,告訴編譯器已存在。
template void myfunc123(int v1,int v2);func123 是函數(shù)模板,顯式實(shí)例還。
顯式實(shí)例化后任然可以用其他類(lèi)的模板版本。
模板的實(shí)例化定義只有一個(gè),實(shí)例化聲明有多個(gè)。
總結(jié)
類(lèi)模板的顯式實(shí)例化會(huì)實(shí)例化所有成員函數(shù),普通的使用模板實(shí)例化時(shí)只有在使用到了類(lèi)模板的成員函數(shù)才會(huì)實(shí)例化成員函數(shù)。
using定義模板別名,顯式指定模板參數(shù)
using定義模板別名
typedef一般用來(lái)定義類(lèi)型別名
typedef unsigned int uint_t;//相當(dāng)于給unsigned int類(lèi)型其了一個(gè)別名uint_t typedef map<string,int> map_s_i; map_s_i mymap;//map_s_i等價(jià)于map<string,int>需求:希望模板類(lèi)的類(lèi)型不固定,又想定義新的別名
template<typename st> struct map_s {typedef map<string,st> type; };map_s<int>::type map1;以任意類(lèi)型為value為了實(shí)現(xiàn)這種比較通用的string類(lèi)型為key。以任意類(lèi)型value的map容器,我們不得不自己寫(xiě)一個(gè)類(lèi)來(lái)達(dá)到這個(gè)目的,實(shí)現(xiàn)手段比較猥瑣。
C++11:
template<typename T> using str_map_t = map<string,T>;str_map_t<int>map2;using 用來(lái)給一個(gè)類(lèi)型模板起名字(別名)用的,包含了typedef的所有功能。
typedef unsigned int uint_t; using uint_t = unsingned int;typedef map<string,int>map_s_i; using map_s_i = map<string,int>using定義類(lèi)型的定義方法感覺(jué)像賦值。
tenplate<typename T> using Mypoint = int(*)(T,T);//函數(shù)指針using中使用這種模板,既不是類(lèi)模板,也不是函數(shù)模板,可以看作是一種新的模板類(lèi)型
顯式指定模板參數(shù)
tmplate <typename T1,typename T2,typename T3> T1 sum(T1 i, T3 j) {T1 result = i+ j;return result; }auto result = sum<double,double,double>(2000000000,2000000000); cout<< result <<endl; //int 最多能保存21億多。會(huì)溢出 //手動(dòng)指定類(lèi)型優(yōu)先。模板全特化、偏特化(局部特化)
特化<->泛化:模板
特化:對(duì)特殊的類(lèi)型進(jìn)行特殊的對(duì)待,給他寫(xiě)適合他的專(zhuān)用代碼
類(lèi)模板特化
類(lèi)模板全特化
-
常規(guī)全特化
必須先有泛化版本才能存在特化版本。只要涉及特化,一定先存在泛化;
template<typename T, typename U> struct TC {void functest(){cout<<"泛化版本"<<endl;}};當(dāng)T和U都為int類(lèi)型是,我們希望做一個(gè)特化版本。
全特化:所有類(lèi)型模板參數(shù)(T,U)都用具體的類(lèi)型代表。
template<>//所有類(lèi)型都用具體類(lèi)型代表 struct TC<int, int> {void functest(){cout<<"特化版本"<<endl;} };特化版本的代碼編譯器會(huì)優(yōu)先選擇。
作用:為了解決類(lèi)模板無(wú)法適配某些類(lèi)型的情況。
-
特化成員函數(shù)而不是模板
在類(lèi)外:
template<> void TC<double,int>::functest(){cout<<"double,int 的特化成員函數(shù)"<<endl; }
類(lèi)模板偏特化(局部特化)
模板參數(shù)數(shù)量上
泛化模板:
template<typename T, typename U,typename W> struct TC {void functest(){cout<<"泛化版本"<<endl;}};特化版本(確定兩個(gè)模板參數(shù))
template<typename U> struct TC<int, U, double> {void functest(){cout<<"偏特化int, U, double版本"<<endl;}};模板參數(shù)范圍上
int 變?yōu)?const int,單位變小。
T變?yōu)門(mén)*,范圍縮小。
? T&,T&&,都是范圍縮小。
特化就是范圍變小。
泛化版本:
template<typename T> struct TC {void functest(){cout<<"泛化版本"<<endl;}};特化版本:
template<typename T> struct TC<const T>//const 的特化版本 {void functest(){cout<<"const特化版本"<<endl;}}; template<typename T>//注意這里面不變 struct TC<T*>//const 的特化版本 {void functest(){cout<<"T*特化版本"<<endl;}};函數(shù)模板特化
函數(shù)模板全特化:
泛化版本:
template<typename T,typename U> void tfunc(T&tmprv,U&tmprv2){cout << "tfunc泛化版本" << endl;cout << tmprv << endl;cout << tmprv2 << endl; }全特化版本:
template<> void tfunc(int &tmprv, double &tmprv2) {cout << "tfunc特化版本" << endl;cout << tmprv << endl;cout << tmprv2 << endl; }全特化函數(shù)模板實(shí)際上等價(jià)于實(shí)例化一個(gè)函數(shù)模板,并不是等價(jià)于一個(gè)函數(shù)重載
如果既存在重載函數(shù)也存在全特化函數(shù)版本,優(yōu)先調(diào)用重載函數(shù)。
編譯器選擇最最合適:普通優(yōu)先,特化版本,泛化版本。
如果傳遞一個(gè)字符串給函數(shù)模板,函數(shù)模板的特化版本中,如果有數(shù)組類(lèi)型模板參數(shù),指針類(lèi)型模板參數(shù),編譯器會(huì)認(rèn)為數(shù)組類(lèi)型模板參數(shù)比指針類(lèi)型模板參數(shù)更合適。所以編譯器會(huì)為你選擇數(shù)組類(lèi)型的模板參數(shù)的特化。
函數(shù)模板偏特化:
函數(shù)模板不能偏特化。
模板特化版本 放置位置建議。
模板定義、實(shí)現(xiàn)都放在一個(gè).h文件中,模板的特化版本和模板的泛化版本都應(yīng)該放在一個(gè).h文件中,前面放泛化版本,后面放特化版本。
可變參模板
可變參函數(shù)模板
C++11中引入(variadic Templates):允許模板中有0個(gè)到任意個(gè)模板參數(shù),在語(yǔ)法上也和傳統(tǒng)模板不一樣,多了一個(gè)…
-
范例:
template<typename... T> void myfunct1(T... args) {cout << sizeof...(args) <<endl;cout << sizeof...(T) <<endl; }
我們吧這個(gè)args稱(chēng)為一包或者一堆參數(shù),而且這些參數(shù)的類(lèi)型可以各不相同
我們理解T這種類(lèi)型的時(shí)候,不能把他理解成一個(gè)類(lèi)型,要理解成0到多個(gè)類(lèi)型,自然,對(duì)應(yīng)的參數(shù)args也應(yīng)該是多個(gè)不同類(lèi)型的參數(shù),這一包參數(shù)中可以容納0到多個(gè)模板參數(shù),而且這些模板參數(shù)可以為任意的類(lèi)型;
T后邊帶了…所以,我們稱(chēng)呼T為可變參類(lèi)型;這個(gè)東西看起來(lái)是一個(gè)類(lèi)型名,實(shí)際上里邊包含的是0到多個(gè)不同的類(lèi)型(一包類(lèi)型);
args:可變形參,既然T代表的是一包類(lèi)型,那顯然args代表的就是一包形參。
sizeof…():可變參的數(shù)量
在具體函數(shù)形參中,&的位置,出現(xiàn)在了類(lèi)名的后邊
-
范例2
template<typename T,typename...U> void myfunct2(const T&firstarg,const U&...otherargs){}
參數(shù)包的展開(kāi):展開(kāi)套路固定,一般都是用遞歸函數(shù)的方式來(lái)展開(kāi)參數(shù)
要求在代碼的編寫(xiě)中,又一個(gè)參數(shù)包展開(kāi)函數(shù),和一個(gè)同名的遞歸終止函數(shù),通過(guò)這兩個(gè)函數(shù)把參數(shù)包展開(kāi)。
一個(gè)參數(shù)+一包參數(shù)這種可變參函數(shù)模板寫(xiě)法最適合參數(shù)包展開(kāi)
void myfunct2() {cout << "執(zhí)行了遞歸終止函數(shù)" <<endl;//遞歸終止函數(shù) }template<typename T,typename...U> void myfunct2(const T&firstarg,const U&...otherargs){cout<<"受到的參數(shù)值為:"<<firstarg <<endl;if(sizeof...(otherargs)!=0)myfunct2(otherargs...); }可變參類(lèi)模板
允許模板定義中含有0到任意個(gè)模板參數(shù)
- 通過(guò)遞歸程序方式展開(kāi)參數(shù)包
可變參模板續(xù)、模板模板參數(shù)
可變參類(lèi)模板
-
通過(guò)遞歸組合方式展開(kāi)參數(shù)包
class A{}; class B{A m_i; }一個(gè)類(lèi)包含另一個(gè)類(lèi),稱(chēng)為組合關(guān)系。
直接內(nèi)存管理(new/delete)、創(chuàng)建新工程觀察內(nèi)存泄露
臨時(shí)對(duì)象:作用域結(jié)束生命周期結(jié)束
局部靜態(tài)對(duì)象:程序結(jié)束被釋放
直接內(nèi)存管理(new/delete)
new分配我們稱(chēng)為動(dòng)態(tài)分配(分配在堆上)
如何初始化:
int *pointi = new int;//初值未定義 string *mystr = new string;//調(diào)用構(gòu)造函數(shù)初始化為空 int *pointi = new int(100);//圓括號(hào)初始化 string *mystr2 = new string(5,'a');//五個(gè)a的字符串 vector<int>*pointv = new vector<int>{1,2,3,4,5};值初始化:用()來(lái)初始化
int *pointi = new int;//初始化的值未定 int *pointi = new int();//初始化的值為0是否是“值初始化”效果一樣,都是調(diào)用A的構(gòu)造函數(shù)。(只有int類(lèi)型有區(qū)別)
new對(duì)象的時(shí)候,能夠進(jìn)行“值初始化”就進(jìn)行一下為好,防止值沒(méi)有初始化。
C++11中,auto可以與new配合使用:
string *mystr = new string(5,'a'); auto mystr2 = new auto(mystr);//指向同一段內(nèi)存 //string **mystr3 = new string*(mystr);const也可以動(dòng)態(tài)分配:
const int *pointci = new const int(200);new和delete說(shuō)明:
- 成對(duì)使用,delete的作用是回收用new分配的內(nèi)存(釋放內(nèi)存);不是new出來(lái)的內(nèi)存,是不能用delete釋放
- delete一塊內(nèi)存,只能delete一次,不能delete多次。delete后,這塊內(nèi)存就不能使用了.空指針可以多次刪除,但無(wú)意義
- delete一個(gè)指針后,可以將其置空(nullpt)
c++11開(kāi)始出現(xiàn)了“只能指針”,new后忘記了delete,只能指針能夠幫助delete
MFC應(yīng)用程序能夠在一定程度上(程序退出的時(shí)候),能幫助我們發(fā)現(xiàn)內(nèi)存泄露。
new、delete探秘,智能指針概述、shared_ptr基礎(chǔ)
new/delete探秘
-
new/delete是什么
new/delete是關(guān)鍵字/運(yùn)算符,不是函數(shù)
malloc,free(主要用于C語(yǔ)言中),而new/delete用于C++編程,都用于動(dòng)態(tài)的在堆中分配/釋放內(nèi)存
new/delete比malloc/free干的事情更多:new會(huì)調(diào)用構(gòu)造函數(shù),delete會(huì)調(diào)用析構(gòu)函數(shù)
new/delete具備對(duì)堆上所分配內(nèi)存進(jìn)行初始化/釋放的能力,這些能力是malloc/free不具備的。
-
operator new()和operator delete()
這兩個(gè)是函數(shù)。new干了兩件事:分配內(nèi)存,調(diào)用構(gòu)造函數(shù)初始化內(nèi)存。通過(guò)operate new()分配內(nèi)存。
delete也干兩件事:調(diào)用析構(gòu)函數(shù),釋放內(nèi)存。
-
基本new如何記錄分配的內(nèi)存大小供delete使用
不同的編譯器new內(nèi)部有不同的實(shí)現(xiàn)方式
int *p = new int;//new會(huì)記錄分配出多少字節(jié) -
申請(qǐng)和釋放一個(gè)數(shù)組
類(lèi)對(duì)象有地址,空的類(lèi)類(lèi)對(duì)象占的是1個(gè)字節(jié)的地方。成員函數(shù)不占內(nèi)存,數(shù)據(jù)成員占
動(dòng)態(tài)給類(lèi)類(lèi)型A分配內(nèi)存對(duì)象數(shù)組是多出來(lái)4個(gè)字節(jié),而給內(nèi)置類(lèi)型分配內(nèi)存對(duì)象數(shù)組時(shí),沒(méi)有多出來(lái)。
多出來(lái)的4個(gè)字節(jié)用來(lái)記錄new時(shí)的數(shù)組有多少個(gè)。但是內(nèi)置類(lèi)型不需要記錄
A *pA = new A[2](); delete []A; -
為什么new/delete,new[]/delete[]要配對(duì)使用
內(nèi)置類(lèi)型例如int不需要調(diào)用析構(gòu)函數(shù),所以new[]并沒(méi)有多分配出4個(gè)字節(jié)。
結(jié)論:如果一個(gè)對(duì)象,使用new[]來(lái)分配內(nèi)存,卻用單獨(dú)的delete(而不是delete[])來(lái)釋放函數(shù),該對(duì)象的類(lèi)型是內(nèi)置類(lèi)型或者是無(wú)自定義的析構(gòu)函數(shù)的類(lèi)類(lèi)型
智能指針總述
裸指針:直接用new返回的指針,這種指針強(qiáng)大,靈活,但是開(kāi)發(fā)者全程負(fù)責(zé)維護(hù)
智能指針:理解為對(duì)“裸指針”進(jìn)行包裝,能夠自動(dòng)釋放所指向的對(duì)象內(nèi)存,不用擔(dān)心new出來(lái)的內(nèi)存忘記釋放
優(yōu)先選擇智能指針
C++標(biāo)準(zhǔn)庫(kù)有四種智能指針:std::
- auto_ptr:被unique_ptr完全替代,C++98(棄用)
- unique_ptr:獨(dú)占式的指針;同一個(gè)時(shí)間內(nèi)只有一個(gè)指針能夠指向該對(duì)象,不過(guò)該對(duì)象的所有權(quán)可以移交出去。
- shared_ptr:共享式指針,多個(gè)指針指向同一個(gè)對(duì)象,最后一個(gè)指針被銷(xiāo)毀時(shí),這個(gè)對(duì)象會(huì)被釋放
- weak_ptr:輔助share_ptr工作的;
智能指針幫助我們動(dòng)態(tài)分配對(duì)象的生命周期的管理,有效防止內(nèi)存泄露
shared_ptr基礎(chǔ)
共享所有權(quán),不會(huì)被一個(gè)shared_ptr擁有,而是被多個(gè)shared_ptr之間相互協(xié)作;有額外開(kāi)銷(xiāo)
工作原理:引用計(jì)數(shù),每個(gè)shared_ptr的拷貝都指向相同的內(nèi)存。所以,只有最后一個(gè)指向該內(nèi)存(對(duì)象)的shared_ptr指針不需要再指向該對(duì)象時(shí),才去去析構(gòu)所指向的對(duì)象
最后一個(gè)指向該內(nèi)存對(duì)象的shared_ptr在什么情況下會(huì)釋放該對(duì)象呢(shared_ptr所指向的對(duì)象)?
- 這個(gè)shared_ptr被析構(gòu)的時(shí)候
- 這個(gè)shared_ptr指向其他的對(duì)象時(shí)
垃圾回收機(jī)制,不再擔(dān)心對(duì)象合適delete
類(lèi)模板里,用到<>,格式:<>里,就是指針可以指向的類(lèi)型,后邊在跟智能指針名
格式:shared_ptr<指向的類(lèi)型>智能指針名;
shared_ptr<int>pi(new int(100));//pi指向一個(gè)值為100的int型數(shù)據(jù) shared_ptr<int>pi = new int(100);//報(bào)錯(cuò),智能指針式explicit,必須顯式初始化。make_shared函數(shù):標(biāo)準(zhǔn)庫(kù)里的函數(shù)模板,安全高效的分配和使用shared_ptr;
它能夠在動(dòng)態(tài)內(nèi)存(堆)中分配并初始化一個(gè)對(duì)象,然后返回指向次對(duì)象的shared_ptr;
shared_ptr<int>p2 = make_shared<int>(100); auto p2 = make_shared<int>(100);shared_ptr常用操作、計(jì)數(shù)、自定義刪除器等等
Shared_ptr引用計(jì)數(shù)的增加和減少
共享式,引用計(jì)數(shù),每個(gè)shared_ptr的拷貝對(duì)象都指向相同的內(nèi)存(對(duì)象),只有最后一個(gè)指向該對(duì)象的shared_ptr指針不需要再指向該對(duì)象的時(shí)候,才會(huì)去析構(gòu)所指向的對(duì)象
每個(gè)shared_ptr都會(huì)記錄有多少個(gè)其他的shared_ptr指向相同的對(duì)象;
auto p6 = make_shared<int>(100);auto p7(p6);在如下情況下,所有指向這個(gè)對(duì)象的shared_ptr引用計(jì)數(shù)都會(huì)增加1;
- 像上邊這樣,用p6來(lái)初始化p7這個(gè)智能指針;
- 把智能指針當(dāng)作是參往函數(shù)里傳(值傳遞會(huì),引用不會(huì))
- 作為函數(shù)的返回值(如果沒(méi)有變量接,會(huì)增加后又減少)
引用計(jì)數(shù)的減少:
- 給shared_ptr賦予新值,讓該shared_ptr指向一個(gè)新對(duì)象
- 局部的shared_ptr離開(kāi)其作用域。
- 當(dāng)一個(gè)shared_ptr引用計(jì)數(shù)從1變?yōu)?,則它會(huì)自動(dòng)釋放自己所管理(指向)的對(duì)象。
shared_ptr常用操作
-
use_count():返回多少個(gè)智能指針指向某個(gè)對(duì)象,主要用于調(diào)試目的
-
unique():是否該智能指針獨(dú)占某個(gè)指向的對(duì)象,也就是若只有一個(gè)智能指針指向某個(gè)對(duì)象,則返回true,否則返回false。不能為空,而且只有它一個(gè)指向該對(duì)象
-
reset():不帶參數(shù)時(shí),若pi時(shí)唯一指向該對(duì)象的指針,那么釋放pi所指向的對(duì)象,并將pi置空;若不是唯一指向該對(duì)象的指針,那么不釋放所指向的對(duì)象,但指向該對(duì)象的引用計(jì)數(shù)會(huì)減少1,同時(shí)將pi置空。
帶參數(shù)時(shí)(一般時(shí)一個(gè)new出來(lái)的指針)
若pi時(shí)唯一指向該對(duì)象的指針,則釋放pi指向的對(duì)象,讓pi指向新對(duì)象。
若pi不是唯一指向該對(duì)象的指針,則不釋放所指向的對(duì)象,但指向該對(duì)象的引用計(jì)數(shù)會(huì)減少1,同時(shí)將pi指向新對(duì)象。
-
*解引用:獲得p指向的對(duì)象
-
get():返回p中保存的指針(裸指針),小心使用,如果智能指針釋放了所指向的對(duì)象,那么這個(gè)返回的裸指針也就變得無(wú)效了??紤]到有些函數(shù)的參數(shù)需要的是一個(gè)內(nèi)置裸指針而不是智能指針(千萬(wàn)不能delete)。
-
swap():交換兩個(gè)智能指針?biāo)赶虻膶?duì)象
-
=nullptr:將所指向的對(duì)象引用計(jì)數(shù)減1,若引用計(jì)數(shù)為0,則釋放智能指針?biāo)赶虻膶?duì)象。將智能指針置空
-
智能指針名作為判斷條件:是否是空指針
-
指定刪除器以及數(shù)組問(wèn)題:一定時(shí)機(jī)幫我們刪除所指向的對(duì)象;delete:將delete運(yùn)算符號(hào)作為默認(rèn)的資源析構(gòu)方式。
我們可以指定自己的刪除器取代系統(tǒng)提供的默認(rèn)刪除器,當(dāng)智能指針需要?jiǎng)h除所指向的對(duì)象是,編譯器就會(huì)調(diào)用我們自己定義的刪除器
shared_ptr指定刪除器方法比較簡(jiǎn)單,一般只需要在參數(shù)中添加具體的刪除器函數(shù)名即可;
void myDelete(int *p)//定義的刪除器 {delete p; }shared_ptr<int> p(new int(123456), myDelete); shared_ptr<int> p2(p);刪除器可以是一個(gè)lambda表達(dá)式
shared_ptr<int> p1(new int(123456),[](int *p){delete p; });有些情況,默認(rèn)的刪除器處理不了(用shared_ptr管理動(dòng)態(tài)數(shù)組),需要自己指定刪除器。
shared_ptr<int> p1(new int[10](),[](int *p){delete []p; });
可用default_delete來(lái)做刪除器,default_delete是標(biāo)準(zhǔn)庫(kù)里的類(lèi)模板
shared_ptr<A> pA(new A[10],std::default_delete<A[]>());定義數(shù)組的時(shí)候我們?cè)诩饫ㄌ?hào)中加[]
shared_ptr<A[]> pA(new A[10]);就算兩個(gè)shared_ptr指定了不同的刪除器,只要他們所指向的對(duì)象類(lèi)型相同,那么這兩個(gè) shared_ptr也屬于同一個(gè)類(lèi)型。
類(lèi)型相同,就代表可以放到元素類(lèi)型為該對(duì)象類(lèi)型的容器中來(lái)。
make_shared是提倡的生成shared_ptr的方法,但無(wú)法指定刪除器。
weak_ptr概述、weak_ptr常用操作、尺寸
weak_ptr概述
用來(lái)輔助shared_ptr進(jìn)行工作
強(qiáng)指的是shared_ptr,弱指的就是weak_ptr;
weak_ptr:也是個(gè)類(lèi)模板,也是個(gè)智能指針。這個(gè)智能指針指向一個(gè)有shared_ptr管理的對(duì)象。但是weak_ptr這種指針不控制變量聲明周期。因此,將weak_ptr綁定到shared_ptr上并不會(huì)改變shared_ptr的引用計(jì)數(shù)
當(dāng)shared_ptr需要釋放所指定對(duì)象的時(shí)候照常釋放,不管是否有weak_ptr指向該對(duì)象。
作用:理解成監(jiān)視shared_ptr(強(qiáng)引用)的生命周期用的。是一種對(duì)shared_ptr的助手。weak_ptr能夠監(jiān)視到它所指向的對(duì)象是否存在
auto pi = make_shared<int>(100); weak_ptr<int>piw(pi);lock():檢查weak_ptr所指向的對(duì)象是否存在,如果存在,那么這個(gè)lock就能返回一個(gè)指向該對(duì)象的shared_ptr(指向?qū)ο蟮膹?qiáng)引用計(jì)數(shù)就會(huì)加一)如果對(duì)象不存在,lock就會(huì)返回一個(gè)空的shared_ptr;
weak_ptr常用操作
- use_count():獲取與該弱指針共享對(duì)象的其他shared_ptr的數(shù)量,或者說(shuō)獲得當(dāng)前所觀測(cè)資源的強(qiáng)引用計(jì)數(shù)
- expired():是否過(guò)期的意思。若該指針的use_count()為0(表示該弱指針?biāo)赶虻膶?duì)象已經(jīng)不存在 ),則返回true
- reset():將該弱引用指針設(shè)置為空,不影響指向該對(duì)象的強(qiáng)引用數(shù)量,但指向該引用的弱引用數(shù)量減少。
- lock():檢查weak_ptr所指向的對(duì)象是否存在,如果存在,那么這個(gè)lock就能返回一個(gè)指向該對(duì)象的shared_ptr(指向?qū)ο蟮膹?qiáng)引用計(jì)數(shù)就會(huì)加一)如果對(duì)象不存在,lock就會(huì)返回一個(gè)空的shared_ptr;
weak_ptrpw尺寸問(wèn)題
和shared_ptr尺寸一樣大,是裸指針的2倍。8字節(jié),普通裸指針4字節(jié)。所以一個(gè)shared_ptr包含兩個(gè)裸指針,一個(gè)指向?qū)ο?#xff0c;一個(gè)指向控制塊的指針,控制塊有:
- 引用計(jì)數(shù)
- 弱引用計(jì)數(shù)
- 其他數(shù)據(jù)(比如自定義刪除器)
shared_ptr使用場(chǎng)景,陷阱、性能分析、使用建議
多線程程序設(shè)計(jì)
Linux使用g++ 編譯多線程代碼時(shí),需要用到以下語(yǔ)句
g++ -std=c++11 -o client2 client_lan_tcp2.cpp -pthread
并發(fā)基礎(chǔ)概念及實(shí)現(xiàn)、進(jìn)程、線程基本概念和綜述
并發(fā)、進(jìn)程、線程的基本概念和綜述
并發(fā):兩個(gè)或者多個(gè)任務(wù)同時(shí)發(fā)生進(jìn)行。
單核cpu某一個(gè)時(shí)刻只能執(zhí)行一個(gè)任務(wù):由操作系統(tǒng)調(diào)度,每秒鐘進(jìn)行多次所謂的任務(wù)切換(不是真正的并發(fā)),上下文切換,有時(shí)間開(kāi)銷(xiāo)。
可執(zhí)行程序
磁盤(pán)上的一個(gè)文件,windows下,一個(gè)可擴(kuò)展名為.exe的文件
進(jìn)程
程序的運(yùn)行過(guò)程
線程
每個(gè)進(jìn)程都有一個(gè)主線程,線程用來(lái)執(zhí)行代碼,我們剋寫(xiě)代碼來(lái)創(chuàng)建其他線程,甚至去不同的地方
每創(chuàng)建一個(gè)新線程,我就可以在同一個(gè)時(shí)刻,多干一個(gè)不同的事
線程并不是越多越好,每個(gè)線程,都需要一個(gè)獨(dú)立的堆棧空間(1M),線程之間的切換要保存很多中間狀態(tài);切換會(huì)消耗程序運(yùn)行時(shí)間。線程創(chuàng)建時(shí)間最大不建議超過(guò)200-300個(gè)。一個(gè)進(jìn)程中最少有一個(gè)線程,即主線程。
并發(fā)的實(shí)現(xiàn)方法
- 通過(guò)多個(gè)進(jìn)程實(shí)現(xiàn)并發(fā)
- 在單獨(dú)的進(jìn)程中,創(chuàng)建多個(gè)線程來(lái)實(shí)現(xiàn)并發(fā);自己寫(xiě)代碼來(lái)創(chuàng)建出了主線程之外的其他線程。
多進(jìn)程并發(fā)
進(jìn)程之間的通信(同一個(gè)電腦上:管道,文件,消息隊(duì)列,共享內(nèi)存);
不同電腦上:socket通信技術(shù)
多線程并發(fā)
單個(gè)進(jìn)程中創(chuàng)建了多個(gè)線程,一個(gè)進(jìn)程中的所有線程共享地址空間(共享內(nèi)存);
全局變量,指針,引用都可以在線程之間傳遞,所以:使用線程開(kāi)銷(xiāo)遠(yuǎn)遠(yuǎn)小于多進(jìn)程。
共享內(nèi)存帶來(lái)新問(wèn)題,數(shù)據(jù)一致性問(wèn)題;
多進(jìn)程并發(fā)和多線程并發(fā)雖然可以混合使用,但建議優(yōu)先考慮多線程技術(shù)手段而不是多進(jìn)程。
和進(jìn)程相比,線程如下優(yōu)點(diǎn):
- 線程啟動(dòng)速度更快,更輕量級(jí)
- 系統(tǒng)資源開(kāi)銷(xiāo)更少,執(zhí)行速度更快,比如共享內(nèi)存這種通信方式比任何其他的通信方式都快;
缺點(diǎn):使用有一定難度,要小心處理數(shù)據(jù)的一致性問(wèn)題
C++11新標(biāo)準(zhǔn)線程庫(kù)
以往:Windows:CreateThread(),_beginthred(),_beginthredexe()創(chuàng)建線程
Linux:pthread_creat()
C++語(yǔ)言本身增加對(duì)多線程的支持,以為著可移植性。
線程啟動(dòng)、結(jié)束、創(chuàng)建多線程多法、join、detach
范例演示線程運(yùn)行的開(kāi)始和結(jié)束
程序運(yùn)行起來(lái),生成一個(gè)進(jìn)程,該進(jìn)程所屬的主線程開(kāi)始自動(dòng)運(yùn)行;主線程結(jié)束,所有子線程結(jié)束,程序結(jié)束,所以保證主線程的運(yùn)行,子線程才能正常運(yùn)行。
thread:標(biāo)準(zhǔn)庫(kù)里的類(lèi)
void myprint(){//初始函數(shù)cout<<"我的線程執(zhí)行"<<endl; } void myprint1(){//初始函數(shù)cout<<"第二程執(zhí)行"<<endl; } void myprint3(){//初始函數(shù)cout<<"第三線程執(zhí)行"<<endl; } int main(){thread obj(myprint);//創(chuàng)建了線程,線程執(zhí)行起點(diǎn)為myprint,開(kāi)始執(zhí)行thread obj2(myprint1);thread obj3(myprint3);obj.join();//阻塞主線程,等待子線程執(zhí)行完畢后執(zhí)行下面的語(yǔ)句。obj2.join();obj3.join();//上面三個(gè)函數(shù)的執(zhí)行順序不確定,下面的執(zhí)行順序正常:thread obj(myprint);obj.join();thread obj2(myprint1);obj2.join();thread obj3(myprint3);obj3.join(); }通常情況,如果主線程執(zhí)行完畢了,但子線程沒(méi)執(zhí)行完畢,這種程序員事不合格的,寫(xiě)出的程序也是不穩(wěn)定的。一個(gè)書(shū)寫(xiě)良好的程序,應(yīng)該是主線程等待子線程結(jié)束再退出。
detach():傳統(tǒng)多線程程序主線程要等待子線程執(zhí)行完畢,然后自己再退出,但是detach()可以讓子線程和主線程分離,主線程可以先執(zhí)行完畢。(我們創(chuàng)建了很多子線程,讓主線程逐個(gè)等待子線程結(jié)束,這種編程方法不太好,所以引入了detach())。
一旦detach()后,與主線程相關(guān)聯(lián)的thread對(duì)象就會(huì)失去與這個(gè)主線程的關(guān)聯(lián),此時(shí)這個(gè)子線程就會(huì)駐留在后臺(tái)運(yùn)行,這個(gè)子線程就相當(dāng)于被C++運(yùn)行時(shí)庫(kù)接管,當(dāng)這個(gè)子線執(zhí)行完成后,由運(yùn)行時(shí)庫(kù)負(fù)責(zé)清理該線程相關(guān)的資源(守護(hù)線程)。detach()會(huì)使得子線程失去控制,推薦使用join(),使用了detach()后不能再join()
joinable():判斷是否可以成功使用join()或者detach()的,返回true(可以join或者detach)或者false(不能)。
其他創(chuàng)建線程的手法
可調(diào)用對(duì)象;
類(lèi):
class A { public:int m_i;A(int &i):m_i(i){}void operator()()//不能帶參數(shù){cout<<"我的線程開(kāi)始執(zhí)行";} }; int main(){int b=6;A a(b);thread obj(a);//調(diào)用拷貝構(gòu)造函數(shù),把a(bǔ)拷貝至線程中。obj.detach();//主線程結(jié)束,a被銷(xiāo)毀,b也被銷(xiāo)毀。a是被復(fù)制到線程中去的,所以沒(méi)問(wèn)題,但是b是引用過(guò)去的,會(huì)出現(xiàn)無(wú)法預(yù)料的結(jié)果 }lambda表達(dá)式:
int main(){auto mylambdathread = []{cout<<"線程開(kāi)始執(zhí)行";};thread obj(mylambdathread);obj.join(); }線程傳參詳解,detach()大坑,成員函數(shù)做線程函數(shù)
傳遞臨時(shí)對(duì)象作為線程參數(shù)
要避免的陷阱
void myprint(const int &i, char *pmybuf){cout << i << endl;cout << pmybuf << endl;return; } int main(){int mvar = 1;int &mvary = mvar;char mybuf[] = "this is a test!";thread obj(myprint,mvar,mybuf);obj.detach();cout<< "主線程結(jié)束" << endl;return 0; }detach子線程傳遞指針是絕對(duì)有問(wèn)題的
void myprint(const int &i, const string &pmybuf){cout << i << endl;cout << pmybuf << endl;return; } int main(){int mvar = 1;int &mvary = mvar;char mybuf[] = "this is a test!";//mybuf隱式類(lèi)型轉(zhuǎn)換為string,但是這個(gè)可能會(huì)在主線程結(jié)束后才開(kāi)始轉(zhuǎn)換。thread obj(myprint,mvar,mybuf);obj.detach();cout<< "主線程結(jié)束" << endl;return 0; }穩(wěn)定版本:
void myprint(const int &i, const string &pmybuf){cout << i << endl;cout << pmybuf << endl;return; } int main(){int mvar = 1;int &mvary = mvar;char mybuf[] = "this is a test!";thread obj(myprint,mvar,string(mybuf));obj.detach();cout<< "主線程結(jié)束" << endl;return 0; }發(fā)現(xiàn)的問(wèn)題:
class A { public:int m_i;A(int i):m_i(i){cout<<"類(lèi)型轉(zhuǎn)換構(gòu)造函數(shù)的執(zhí)行"<<endl;cout << this << endl;}A(const A& a):m_i(a.m_i){cout<<"拷貝構(gòu)造函數(shù)的執(zhí)行"<<endl;cout<<this<<endl;}~A(){cout<<"析構(gòu)函數(shù)的執(zhí)行"<<endl;cout<< this <<endl;}};void func(const A a){cout << &a << endl;return; } int main(){int var = 12;A a = A(var);thread obj(func,a);obj.join();return 0; }輸出結(jié)果:
類(lèi)型轉(zhuǎn)換構(gòu)造函數(shù)的執(zhí)行 0x7ff7bfeff320 拷貝構(gòu)造函數(shù)的執(zhí)行 0x7ff7bfeff278 拷貝構(gòu)造函數(shù)的執(zhí)行 0x10074b100 析構(gòu)函數(shù)的執(zhí)行 0x7ff7bfeff278 拷貝構(gòu)造函數(shù)的執(zhí)行 0x700001958f28 0x700001958f28 析構(gòu)函數(shù)的執(zhí)行 0x700001958f28 析構(gòu)函數(shù)的執(zhí)行 0x10074b100上面結(jié)果跟視頻內(nèi)容不符,有兩次拷貝構(gòu)造函數(shù)執(zhí)行,可能C++做了更新,待驗(yàn)證;
只要利用臨時(shí)一線構(gòu)造的A作為參數(shù)傳遞給線程,那么一定能夠在主線程執(zhí)行完畢之前構(gòu)造出來(lái)。
- 若傳遞int這種簡(jiǎn)單類(lèi)型參數(shù),建議都是值傳遞,不要用引用。放置節(jié)外生枝。
- 如果傳遞對(duì)象,避免隱式類(lèi)型轉(zhuǎn)換。全部都在創(chuàng)建線程這一剛就構(gòu)建出臨時(shí)對(duì)象來(lái),然后再函數(shù)參數(shù)里,用引用來(lái)接,否則系統(tǒng)還會(huì)再構(gòu)造出一次臨時(shí)對(duì)象來(lái)。隱式類(lèi)型轉(zhuǎn)換是在子線程執(zhí)行的,容易出現(xiàn)主線程結(jié)束,子線程才開(kāi)始轉(zhuǎn)換。
臨時(shí)對(duì)象作為線程參數(shù)繼續(xù)講
線程id概念:id是個(gè)數(shù)字,每個(gè)線程(不管是主線程還是子線程)實(shí)際上都對(duì)應(yīng)著一個(gè)數(shù)字,而且每個(gè)線程對(duì)應(yīng)的這個(gè)數(shù)字都不相同。
線程id可以用C++標(biāo)準(zhǔn)庫(kù)的李的函數(shù)來(lái)獲取。std::this_thread::get_id()來(lái)獲取
傳遞類(lèi)對(duì)象、智能指針作為線程參數(shù)
std::ref:可以把真實(shí)的對(duì)象調(diào)入子線程。
成員函數(shù)做
A myobj(10)。
thread my obj(&A::thread_work,myobj,15);
其中thread_work是A類(lèi)的成員函數(shù),后面兩是參數(shù)。
創(chuàng)建多個(gè)線程、數(shù)據(jù)共享問(wèn)題分析、案例代碼
創(chuàng)建和等待多個(gè)線程
void func(int i){cout <<"線程"<< i << endl;return; } int main(){vector<thread> th;for(int i = 0;i < 10;i++){th.push_back(thread(func,i));}for(auto iter = th.begin();iter!=th.end();iter++){iter->join();}cout<<"主線程結(jié)束"<<endl;return 0; }用vector進(jìn)行管理方便。
數(shù)據(jù)共享問(wèn)題
只讀的數(shù)據(jù):是安全穩(wěn)定的,不需要特別什么處理手段。直接讀就可以
有讀有寫(xiě):2個(gè)線程寫(xiě),8個(gè)線程讀,如果代碼沒(méi)有特別處理,那程序肯定崩潰。
最簡(jiǎn)單的不崩潰處理,讀的時(shí)候不能寫(xiě),寫(xiě)的時(shí)候不能讀。兩個(gè)線程不能同時(shí)寫(xiě)
共享數(shù)據(jù)的保護(hù)案例代碼
網(wǎng)絡(luò)游戲服務(wù)器。兩個(gè)自己創(chuàng)建的線程,一個(gè)線程手機(jī)玩家命令,并把命令寫(xiě)到一個(gè)隊(duì)列中,另一個(gè)線程,從隊(duì)列中 取出玩家發(fā)送來(lái)的命令,解析,然后執(zhí)行玩家需要的動(dòng)作。
list,vector.list在頻繁的按順序插入和刪除數(shù)據(jù)時(shí)效率高
class A { public:void inMsgRecvQueue(){for (int i = 0; i < 100000; ++i) {cout << "inMsgRecvQueue()插入" << i << endl;msgRecvQueue.push_back(i);}}void outMsgQueue() {for (int i = 0; i < 100000; ++i) {if (!msgRecvQueue.empty()) {int command = msgRecvQueue.front();//返回第一個(gè)元素,但不檢查元素是否存在msgRecvQueue.pop_front();//移除第一個(gè)元素但不返回cout << "讀取操作" << command << endl;}else {cout << "消息隊(duì)列為空" << endl;}}} private:list<int>msgRecvQueue; }; int main() {A myobj;thread recive(&A::inMsgRecvQueue,&myobj);//第二個(gè)參數(shù)是引用才能保證線程里用的同一個(gè)對(duì)象thread solution(&A::outMsgQueue, &myobj);recive.join();solution.join();}該程序會(huì)崩潰,因?yàn)閷?duì)于同一個(gè)數(shù)據(jù),又在寫(xiě)又在讀;
互斥量概念、用法、死鎖演示及解決詳解
互斥量(mutex)的基本概念
保護(hù)共享數(shù)據(jù),操作時(shí),某個(gè)線程用代碼把共享數(shù)據(jù)鎖住、操作數(shù)據(jù)、解鎖,其他想操作共享數(shù)據(jù)的現(xiàn)場(chǎng)必須等待解鎖,鎖定住,操作,解鎖。
互斥量就是一個(gè)類(lèi)對(duì)象,理解成一把鎖,多個(gè)線程嘗試用lock()成員函數(shù)來(lái)加鎖這把鎖頭,只有一個(gè)線程能鎖定成功(成功的標(biāo)志時(shí)locke()返回true)
如果沒(méi)有鎖成功就卡在這,直到鎖成功。
互斥量使用要小心,保護(hù)數(shù)據(jù)不多也不少,少了沒(méi)達(dá)到效果
lock()和unlock要成對(duì)使用,有l(wèi)ock()必然有unlock()每調(diào)用一次lock(),必然應(yīng)該調(diào)用一次unlock();
不應(yīng)該也不允許調(diào)用1次lock()卻調(diào)用兩次unlock().非對(duì)稱(chēng)數(shù)量調(diào)用不穩(wěn)定。
class A { public:void inMsgRecvQueue(){for (int i = 0; i < 100000; ++i) {my_mutex.lock();//上鎖cout << "inMsgRecvQueue()插入" << i << endl;msgRecvQueue.push_back(i);my_mutex.unlock();//解鎖}}void outMsgQueue() {for (int i = 0; i < 100000; ++i) {my_mutex.lock();//保護(hù)數(shù)據(jù),上鎖if (!msgRecvQueue.empty()) {int command = msgRecvQueue.front();//返回第一個(gè)元素,但不檢查元素是否存在msgRecvQueue.pop_front();//移除第一個(gè)元素但不返回cout << "讀取操作" << command << endl;my_mutex.unlock();//解鎖}else {cout << "消息隊(duì)列為空" << endl;my_mutex.unlock();//解鎖}}} private:list<int>msgRecvQueue;mutex my_mutex;//創(chuàng)建一個(gè)互斥量 }; int main() {A myobj;thread recive(&A::inMsgRecvQueue,&myobj);//第二個(gè)參數(shù)是引用才能保證線程里用的同一個(gè)對(duì)象thread solution(&A::outMsgQueue, &myobj);recive.join();solution.join();}為了防止忘記unlock(),引入了一個(gè)叫std::lock_guard的類(lèi)模板:系統(tǒng)幫忙unlock
std::lock_guard類(lèi)模板,直接取代lock和unlock。
lock_guardsbguard(my_mutex);構(gòu)造函數(shù)中有l(wèi)ock,析構(gòu)函數(shù)有unlock
可以加{}提前析構(gòu)。
死鎖
比如有兩把鎖(死鎖的前提條件是由至少兩個(gè)鎖頭也就是兩個(gè)互斥量才能產(chǎn)生);金鎖,銀鎖兩個(gè)線程A,B
- 線程A執(zhí)行的時(shí)候,這個(gè)線程先鎖金鎖,把金鎖lock成功了,然后去lock銀鎖線程
- B執(zhí)行,先鎖銀鎖,成功了,然后去鎖金鎖。。
此時(shí)死鎖發(fā)生,兩個(gè)線程永遠(yuǎn)卡在這里
死鎖的一般解決方案:鎖的順序保持一致。
std::lock()函數(shù)模板:用來(lái)處理多個(gè)互斥量時(shí)。
能力:一次鎖住兩個(gè)或者兩個(gè)以上的互斥量(至少兩個(gè))
它不存在這種因?yàn)槎鄠€(gè)線程中因?yàn)殒i的順序問(wèn)題導(dǎo)致死鎖的風(fēng)險(xiǎn);如果互斥量中有一個(gè)沒(méi)鎖住,它就在那里等著,等所有互斥量都鎖住,它才能往下走
要么兩個(gè)互斥量都鎖住,要么兩個(gè)互斥量都沒(méi)鎖住,一個(gè)沒(méi)鎖住,就把另一個(gè)放開(kāi),然后等待,一段時(shí)間后再?lài)L試。
std::adopt_lock():結(jié)構(gòu)體對(duì)象,起一個(gè)標(biāo)記作用,作用就是表示這個(gè)互斥量已經(jīng)lock過(guò)了,不需要在lock_guard中再lock了。
std:lock(my_mutex1,my_mutex2);
lock_guardsbguard(my_mutex1, adopt_lock);
lock_guardsbguard(my_mutex2, adopt_lock);
condition_variable、wait、notify_one、notify_sll
條件變量std::condition_variable、wait()、notify_one()
線程A:等待一個(gè)條件滿足
線程B:專(zhuān)門(mén)往消息隊(duì)列扔數(shù)據(jù)
std::condition_variable實(shí)際上是一個(gè)類(lèi),是一個(gè)和條件相關(guān)的類(lèi),這個(gè)類(lèi)是需要和互斥量配合使用的
std::unique_lock<std::mutex>sbguard(my_mutex); std::condition_variable my_cond; my_cond.wait(sbguard,[this]{if(!msRecvQueue.empty())return true;return false; });如果第二個(gè)參數(shù)lambda表達(dá)式返回值時(shí)false,那么wait()將解鎖互斥量,并阻塞到本行,阻塞待其他某個(gè)線程調(diào)用notify_one()成員函數(shù)為止。如果返回值為true,那直接往下走。如果沒(méi)有第二個(gè)參數(shù),那么就跟第二個(gè)參數(shù)lambda表達(dá)式返回false一樣。
當(dāng)其他線程用notify_one()將wait()喚醒的時(shí)候,會(huì)嘗試再去拿互斥量鎖,當(dāng)拿到鎖時(shí),繼續(xù)判斷l(xiāng)ambda表達(dá)式的內(nèi)容,若表達(dá)式返回false,則解鎖,繼續(xù)睡眠,阻塞,等待喚醒,如果返回true,繼續(xù)向下執(zhí)行。如果wait()沒(méi)有第二個(gè)參數(shù),則被喚醒,拿到鎖后就向下走。所以調(diào)用notif_one的進(jìn)程,需要及時(shí)將鎖解開(kāi)。
notify_one:通知一個(gè)線程。
notif_all():通知所有有wait的線程
async、future、packaged_task、promise
std::async、std::future創(chuàng)建后臺(tái)任務(wù)并返回值
Std::async是一個(gè)函數(shù)模板,用來(lái)啟動(dòng)一個(gè)異步任務(wù),啟動(dòng)起來(lái)一個(gè)異步任務(wù)以后,返回一個(gè)std::future。就是自動(dòng)創(chuàng)建一個(gè)線程并開(kāi)始執(zhí)行對(duì)應(yīng)的線程入口函數(shù),future對(duì)象里邊就含有線程入口函數(shù)所返回的結(jié)果(線程返回的結(jié)果),我們可以通過(guò)future的成員函數(shù)get()來(lái)獲取結(jié)果。std::future提供了一種訪問(wèn)異步操作結(jié)果的機(jī)制,這個(gè)結(jié)果可能沒(méi)有辦法馬上拿到,在線程執(zhí)行完畢的時(shí)候,就能夠拿到結(jié)果了。包含頭文件#include
int mythread(){//一些代碼...return 6; } =================================================== std::future<int> result = std::async(mythread); cout << result.get() << endl;//這一句會(huì)阻塞至線程結(jié)束future還有一個(gè)wait()函數(shù)只是等待線程結(jié)束,不會(huì)返回值。
get 函數(shù)只能調(diào)用一次,不能調(diào)用多次。
程序若不使用get,整個(gè)程序也會(huì)等待線程結(jié)束后才退出。
我們通過(guò)額外向std::async()傳遞一個(gè)參數(shù),給參數(shù)類(lèi)型時(shí)std::lunnch類(lèi)型(枚舉類(lèi)型),來(lái)達(dá)到一些特殊的目的;
-
std::launch::deferred:表示線程入口函數(shù)調(diào)用被延遲到std::future的wait()或者get()函數(shù)調(diào)用才執(zhí)行。若果wait()/get()沒(méi)有被調(diào)用,線程不會(huì)被創(chuàng)建。實(shí)際上,延遲調(diào)用沒(méi)有創(chuàng)建新線程,是在主線程中調(diào)用的線程入口函數(shù)。
-
std::launch::async:在調(diào)用async函數(shù)的時(shí)候就開(kāi)始創(chuàng)建線程。
std::future<int> result = std::async(std::launch::deferred,mythread); std::future<int> result = std::async(std::launch::async,mythread);//不需要執(zhí)行g(shù)et,創(chuàng)建即立刻執(zhí)行,并且創(chuàng)建了新的線程。實(shí)際上,async()函數(shù)默認(rèn)的就是此標(biāo)記
std::packaged_task:打包任務(wù),把任務(wù)包裝起來(lái)
是個(gè)類(lèi)模板,他的模板參數(shù)是各種可調(diào)用對(duì)象,通過(guò)std::packaged_task把各種可調(diào)用對(duì)象包裝起來(lái),方便將來(lái)作為線程入口函數(shù)的參數(shù)
std::packaged_task<int(int)>mypt(mythread); std::thread t1(std::ref(mypt),1); t1.join(); std::future<int>result = mypt.get_future(); cout<<result.get()<<endl;總結(jié)
- 上一篇: 谷歌云计算技术基础架构,谷歌卷积神经网络
- 下一篇: Visual Studio C++/C