每天學(xué)一點(diǎn)
2.2 編程語言
第一種類型:對(duì)c++概念的理解程度
典型問題:對(duì)c++關(guān)鍵字的理解
1.在c++中,有哪四種與類型轉(zhuǎn)換相關(guān)的關(guān)鍵字谆构,這幾個(gè)關(guān)鍵字有什么特點(diǎn)裸扶,應(yīng)該在什么場合下使用
參考鏈接 http://www.reibang.com/p/e6a1ed13f14f https://blog.csdn.net/Bob__yuan/article/details/88044361
答:static_cast,reinterpret_cast搬素,dynamic_cast和const_cast呵晨。****const_dynamic和reinterpret_cast用于給const變量賦值和非相關(guān)類型之間轉(zhuǎn)換,盡量不要用熬尺。static_cast常用于基本數(shù)據(jù)類型之間的轉(zhuǎn)換摸屠。static_cast和dynamic_cast都可以用于類之間指針和引用的轉(zhuǎn)換。但用dynamic_cast進(jìn)行轉(zhuǎn)換要求基類必須是多態(tài)的(有虛函數(shù))粱哼,static_cast沒有這個(gè)限制季二,但是由于其沒有類型檢查,在進(jìn)行基類指針向派生類指針轉(zhuǎn)換時(shí),存在風(fēng)險(xiǎn)胯舷。四種轉(zhuǎn)換類型中刻蚯,除dynamic_cast是在運(yùn)行時(shí)進(jìn)行類型轉(zhuǎn)換,其余都是編譯時(shí)進(jìn)行桑嘶。
可以參考https://blog.csdn.net/Bob__yuan/article/details/88044361最后一段代碼加深理解
使用方法:type B = static_cast(type) (A)
a. static_cast 用于非多態(tài)類型的轉(zhuǎn)換(靜態(tài)轉(zhuǎn)換炊汹,類中有虛函數(shù),這個(gè)類就是多態(tài)的)逃顶。四種方法中最常見的方法讨便。只能用于相關(guān)類型之間的轉(zhuǎn)換,例如int和char以政,不能用于不相關(guān)類型之間的轉(zhuǎn)換器钟,例如int和int*,因?yàn)樗麄z一個(gè)表示數(shù)據(jù)妙蔗,一個(gè)表示地址傲霸。此關(guān)鍵字沒有運(yùn)行時(shí)類型檢查來保證轉(zhuǎn)換的安全性
注意點(diǎn):1)不能在沒派生關(guān)系的兩個(gè)類類型之間進(jìn)行轉(zhuǎn)換 2)不能去除掉原有類型的類型修飾符,例如const眉反,volatile昙啄,__unaligned 3)轉(zhuǎn)換對(duì)象時(shí)由于沒有動(dòng)態(tài)類型檢查,所以由基類對(duì)象轉(zhuǎn)換成派生類對(duì)象的時(shí)候存在安全隱患
主要用法:1)用于類層析結(jié)構(gòu)中基類和派生類之間指針和引用的轉(zhuǎn)換寸五,進(jìn)行上行轉(zhuǎn)換梳凛,將派生類指針轉(zhuǎn)換為基類指針是安全的,進(jìn)行下行轉(zhuǎn)換梳杏,將基類指針或引用轉(zhuǎn)換為派生類韧拒,由于沒有動(dòng)態(tài)類型檢查,是不安全的 2)用于基本數(shù)據(jù)類型的轉(zhuǎn)換(最常用)十性,例如int轉(zhuǎn)換為char叛溢,安全性也由開發(fā)人員來保證 3)將空指針轉(zhuǎn)換為目標(biāo)類型的空指針 4)將任何類型的表達(dá)式轉(zhuǎn)化為void類型
int i = 10; double d2 = static_cast<double>(i);
b. reinterpret_cast reinterpret的含義是重新解釋,可將一種類型轉(zhuǎn)換成另一種不相關(guān)類型劲适,需要謹(jǐn)慎使用
主要用法:1)改變指針或引用類型 2)將指針轉(zhuǎn)化為足夠長度的整數(shù) 3)將整型轉(zhuǎn)化為指針或引用類型
int i = 10; int* p2 = reinterpret_cast(i);
還可以進(jìn)行函數(shù)轉(zhuǎn)換
void Fun(int s) { cout << s;}
typedef void(*FUNC)();
FUNC pf = reinterpret_cast<FUNC>(Fun); pf();
這段代碼最終會(huì)輸出一個(gè)隨機(jī)值楷掉。因?yàn)橛脩粼谕獠课磦鲄ⅲ窃摵瘮?shù)在調(diào)用時(shí)會(huì)創(chuàng)建形參霞势,該形參未初始化烹植,自然是隨機(jī)值
c. const_cast 刪除變量的const屬性,方便再次賦值 愕贡。該轉(zhuǎn)換在編譯時(shí)完成草雕,用于解除const,volatile修飾符固以,只能轉(zhuǎn)換指針或者引用墩虹。沒什么實(shí)際用途,容易踩坑,盡量不用败晴。
const int i = 10; int *p = const_cast<int*>(&i);
d. dynamic_cast 以上三種類型轉(zhuǎn)換都是編譯時(shí)完成,dynamic_cast是運(yùn)行時(shí)完成栽渴,并進(jìn)行類型檢查尖坤。用法dynamic_cast<type-id> (expression)
注意點(diǎn):1)運(yùn)行時(shí)完成,且有類型檢查 2)不能用于內(nèi)置基本數(shù)據(jù)類型之間的強(qiáng)制轉(zhuǎn)換 3)type-id必須是指針或者引用闲擦,轉(zhuǎn)換成功的話返回指向類的指針或者引用慢味,失敗返回nullptr 4)在類間進(jìn)行上行轉(zhuǎn)換(子類指針轉(zhuǎn)化為父類指針),效果同static_cast,進(jìn)行下行轉(zhuǎn)換(父類指針轉(zhuǎn)換為子類指針墅冷,由于有類型檢查功能纯路,更加安全)5)使用dynamic_cast進(jìn)行轉(zhuǎn)換,基類一定要有虛函數(shù)寞忿。這是由于運(yùn)行是類型檢查需要的類型信息是存在虛函數(shù)表中的驰唬,只有定義了虛函數(shù)才有虛函數(shù)表(即要求基類是多態(tài)的)
2.sizeof問題
面試題:定義一個(gè)空的類型,里面沒有任何成員變量和成員函數(shù)腔彰,對(duì)該類型進(jìn)行sizeof叫编,結(jié)果是多少
答:是1(或是由編譯器決定)
原因:空類型的實(shí)例不包含任何信息,本來sizeof應(yīng)該是0霹抛。但是由于我們在聲明該類型的實(shí)例時(shí)搓逾,他必須在內(nèi)存中占有一定的空間,否則無法使用杯拐。因此編譯器會(huì)給其分配一定的內(nèi)存霞篡,具體多少,由編譯器決定端逼。
面試題:如果在該類型中添加一個(gè)構(gòu)造或者析構(gòu)函數(shù)朗兵,再對(duì)該類型進(jìn)行sizeof,結(jié)果是多少
答:不變
原因:調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)只需要知道函數(shù)的地址即可顶滩,而函數(shù)地址只有類型有關(guān)矛市,與類的實(shí)例無關(guān),編譯器不會(huì)在實(shí)例中添加任何額外信息
面試題:如果將析構(gòu)函數(shù)定義為虛函數(shù)呢
答:一個(gè)指針的大小诲祸。32位編譯器上市4浊吏,64位編譯器上是8
原因:c++編譯器一旦發(fā)現(xiàn)一個(gè)類中有虛函數(shù),就會(huì)對(duì)該類型生成虛函數(shù)表救氯,并在該類型的實(shí)例中添加一個(gè)指向虛函數(shù)表的指針找田。
【C++拾遺】 C++虛函數(shù) 參考鏈接 https://blog.csdn.net/xiejingfa/article/details/50454819
在c++中存在靜態(tài)聯(lián)編和動(dòng)態(tài)聯(lián)編的區(qū)別。靜態(tài)聯(lián)編是指編譯器在編譯階段就實(shí)現(xiàn)了聯(lián)編或者綁定着憨。比如函數(shù)重載墩衙,c++編譯器在編譯階段根據(jù)給函數(shù)傳遞的參數(shù)個(gè)數(shù)和參數(shù)類型就判斷具體使用哪一個(gè)函數(shù),實(shí)現(xiàn)綁定,這種綁定方式就叫做靜態(tài)聯(lián)編漆改。而動(dòng)態(tài)聯(lián)編心铃,指在程序運(yùn)行時(shí)完成的聯(lián)編。
c++通過虛函數(shù)來支持動(dòng)態(tài)聯(lián)編挫剑,并實(shí)現(xiàn)多態(tài)機(jī)制去扣。在c++中,多態(tài)就是利用基類指針指向子類實(shí)例樊破,然后通過基類指針調(diào)用子類(虛)函數(shù)愉棱,實(shí)現(xiàn)“一個(gè)接口,多種形態(tài)”的效果哲戚。c++中用基類指針和虛函數(shù)實(shí)現(xiàn)動(dòng)態(tài)多態(tài)奔滑,虛函數(shù)在基類中聲明一次即可(virtual),在派生類中顺少,virtual關(guān)鍵字可以省略朋其。
假設(shè)類base2繼承類base1,base1中有虛函數(shù)func1和非虛函數(shù)func2脆炎,定義
base2 b2令宿; base1* b1 = &b2;b1->func1();
(base2中的func1腕窥,因?yàn)檫@是個(gè)虛函數(shù)粒没,具體調(diào)用哪個(gè)版本取決于指針?biāo)赶虻膶?duì)象類型) b1->func2();(bass1中func2,對(duì)于非虛函數(shù)簇爆,具體調(diào)用哪個(gè)版本由指針類型本身決定癞松,與指針指向?qū)ο鬅o關(guān))注意:類中非虛函數(shù)可以在繼承來中被重新定義,但是絕大多數(shù)情況下不應(yīng)該這么做
第二種類型:分析代碼運(yùn)行結(jié)果
面試題:
**class A {** **private: int value; ** **public: ** **A(int n) { value = n; } ** **A(A other) {value = other.value;}** **};** **int _tmain() { A a = 10; A b = a; b.print();}**
問該程序是否能正常執(zhí)行
答:不能入蛆,編譯出錯(cuò)
原因:在上述代碼中响蓉,A(A other)這個(gè)復(fù)制構(gòu)造函數(shù)是一個(gè)傳值函數(shù)。在調(diào)用傳值函數(shù)時(shí)哨毁,需要調(diào)用復(fù)制構(gòu)造函數(shù)將實(shí)參復(fù)制給形參枫甲。這樣會(huì)造成復(fù)制構(gòu)造函數(shù)無休止的遞歸,最終造成棧溢出扼褪。因此想幻,c++中不允許復(fù)制構(gòu)造函數(shù)為傳值函數(shù),會(huì)編譯出錯(cuò)话浇。應(yīng)將其修改為常量引用A(const A& other)
第三種類型:要求寫代碼定義一個(gè)類型或者實(shí)現(xiàn)類型中的成員函數(shù)
典型問題:圍繞構(gòu)造函數(shù)脏毯,析構(gòu)函數(shù)和運(yùn)算符重載
記錄基本書籍
《effective c++》 c++經(jīng)常出現(xiàn)的問題及其解決技巧
《Inside c++ object model》了解c++對(duì)象的內(nèi)部,包括前面sizeof問題
面試題1:賦值運(yùn)算符函數(shù)
如下為類型CMyString的聲明幔崖,請為改類型添加賦值運(yùn)算符函數(shù)
class CMyString { public: CMyString(char* pData = NULL); CMyString(const CMyString& str); ~CMyString(void); private: char* m_pData; };
參考鏈接: https://www.cnblogs.com/CJT-blog/p/10458442.html
[https://github.com/zhedahht/CodingInterviewChinese2/blob/master/01_AssignmentOperator/AssignmentOperator.cpp]
劍指offer源碼
(https://github.com/zhedahht/CodingInterviewChinese2/blob/master/01_AssignmentOperator/AssignmentOperator.cpp)答:
#ifndef UNTITLED_CMYSTRING_H #define UNTITLED_CMYSTRING_H # include <string> // 劍指offer第一題食店,寫一個(gè)賦值函數(shù) // 注意點(diǎn) 1.返回值要是實(shí)例引用渣淤,為了可以連續(xù)賦值 2.傳遞參數(shù)為常量引用,防止在函數(shù)內(nèi)被更改 // 3.判斷傳入變量不為當(dāng)前對(duì)象本身吉嫩,避免釋放掉自己本身 4.注意釋放空間 class CMyString { public: CMyString(const char* pData = NULL); CMyString(const CMyString& str); ~CMyString(void) { delete [] m_pData; } CMyString& operator=(const CMyString &str ); void print() { cout << m_pData; } private: char* m_pData; }; CMyString::CMyString(const char* pData) // 構(gòu)造函數(shù) { if (pData == NULL) { m_pData = NULL; return; } m_pData = new char[strlen(pData) + 1]; strcpy(m_pData, pData); } CMyString::CMyString(const CMyString& str) // 復(fù)制構(gòu)造函數(shù)价认,注意這里要是引用,不然會(huì)是淺拷貝 { if (str.m_pData != NULL) { m_pData = new char[strlen(str.m_pData) + 1]; strcpy(m_pData, str.m_pData); } else { m_pData = NULL; } } CMyString& CMyString::operator=(const CMyString &str ) { if (this == &str || str.m_pData == NULL) // 這里一定要判斷傳入?yún)?shù)與實(shí)例是否為同一個(gè)自娩,否則后面釋放內(nèi)存用踩,會(huì)把傳入?yún)?shù)也釋放了 { return *this; } /* 這個(gè)方法是比較常見的寫法,但是有點(diǎn)問題椒功,如果new不成功,返回的是一個(gè)m_pdata為空的實(shí)例 * 后續(xù)調(diào)用容易出問題 * 這里最好升級(jí)成如果new不成功智什,則返回原來的實(shí)例动漾,不進(jìn)行delete的 if (m_pData != NULL) { delete [] m_pData; } m_pData = new char[strlen(str.m_pData) + 1]; strcpy(m_pData, str.m_pData); return *this; */ // 定義局部實(shí)例strTemp,接著將其m_pData與當(dāng)前實(shí)例做交換荠锭。由于局部實(shí)例在函數(shù)結(jié)束后會(huì)釋放旱眯,自動(dòng)調(diào)用析構(gòu)函數(shù),因此當(dāng)前實(shí)例m_pData內(nèi)存就會(huì)被釋放 // 如果new空間不夠证九,則賦值構(gòu)造函數(shù)跑出諸如alloc之類的異常删豺,原來的實(shí)例狀態(tài)尚未被修改 CMyString strTemp(str); char *pTemp = strTemp.m_pData; strTemp.m_pData = m_pData; m_pData = pTemp; return *this; } #endif //UNTITLED_CMYSTRING_H // main.cpp中測試用例 // test operator = 測試用例 // 1.正常賦值 cout << "test1 begin" << endl; char* test = "hello world"; CMyString str1(test); CMyString str2; str2 = str1; cout << "str2: "; str2.print(); cout << endl; cout << "test1 end" << endl; // 2.賦值給自己 cout << "test2" << endl; str1 = str1; str1.print(); cout << endl; cout << "test2 end" << endl; // 3.連續(xù)賦值 cout << "test3" << endl; CMyString str3; CMyString str4; str4 = str3 = str1; str4.print(); cout << endl; cout << "test3 end" << endl;
注意考察點(diǎn) 1)是否把返回值的類型聲明為該類型的引用,并在函數(shù)結(jié)束前返回實(shí)例本身引用(即*this)愧怜。若返回值為void呀页,將不能連續(xù)賦值,不符合運(yùn)算符規(guī)則 2)是否將傳入?yún)?shù)的類型聲明為常量引用拥坛。傳入?yún)?shù)用引用代替實(shí)例蓬蝶,節(jié)約一次形參到實(shí)參調(diào)用復(fù)制構(gòu)造函數(shù)的過程,const關(guān)鍵字猜惋,可以防止賦值函數(shù)內(nèi)部更改傳入實(shí)例狀態(tài) 3)是否釋放實(shí)例自身已有內(nèi)存 4)是否判斷傳入?yún)?shù)與當(dāng)前實(shí)例是否為同一實(shí)例,防止delete掉需要賦值的內(nèi)容
答:1)經(jīng)典解法
CMyString&(注意點(diǎn)1) CMyString::operator = (const CMyString &str(注意點(diǎn)2))
{
步驟1:if (this == &str) return *this; (注意點(diǎn)4) //同一實(shí)例丸氛,不需要進(jìn)行賦值操作
步驟2:delete []m_pData; m_pData = NULL; (注意點(diǎn)3)
步驟3:m_pData = new char[strlen(str.m_pData) + 1];
步驟4:strcp(m_pData, str.m_pData);
步驟5:return *this;
}
2)升級(jí),考慮異常安全性
注意升級(jí)點(diǎn):考慮如果運(yùn)行步驟3的時(shí)候由于運(yùn)行內(nèi)存不足著摔,導(dǎo)致new空間失敗缓窜。由于原始m_pData空間已在步驟2中被清空,m_pData將是一個(gè)空指針谍咆,容易導(dǎo)致程序崩潰禾锤。因此,升級(jí)程序摹察,考慮在new失敗情況下时肿,不更改當(dāng)前實(shí)例的方法。 一種方法:先new再delete港粱,若是new不成功螃成,則保持原實(shí)例不變 另一種方法:創(chuàng)建一個(gè)臨時(shí)實(shí)例旦签,再交換臨時(shí)實(shí)例和原來的實(shí)例。在構(gòu)造函數(shù)中用new分配內(nèi)存寸宏,在析構(gòu)函數(shù)中釋放內(nèi)存宁炫。
CMyString&(注意點(diǎn)1) CMyString::operator = (const CMyString &str(注意點(diǎn)2))
{
if (this != &str) (注意點(diǎn)4)
{ CMyString strTemp(str); char* pTemp = strTemp.m_pData; strTemp.m_pData = m_pData; m_pData = pTemp;(運(yùn)用臨時(shí)變量氮凝,當(dāng)new不成功時(shí)羔巢,可以返回原始實(shí)例) }
return *this;
}
【c++拾遺】memcpy和strcpy
參考鏈接:https://www.cnblogs.com/stoneJin/archive/2011/09/16/2179248.html1)strcpy只能復(fù)制字符串,memcpy可以復(fù)制任何內(nèi)容罩阵,例如字符數(shù)組竿秆,結(jié)構(gòu)體等
2)復(fù)制方法不同,strcpy不需要指定長度稿壁,他遇到被復(fù)制字符串的結(jié)束符"\0"才會(huì)結(jié)束幽钢,因此容易溢出。memcpy由第三個(gè)參數(shù)決定復(fù)制長度
3)用途不同傅是。一般復(fù)制字符串用strcpy匪燕,其余用memcpy
典型問題:設(shè)計(jì)模式
面試題2:實(shí)現(xiàn)singleton(單例)模式(設(shè)計(jì)一個(gè)類,我們只能生成該類的一個(gè)實(shí)例)
// // Created by Xue,Lin on 2020/6/9. // // 餓漢模式 #ifndef UNTITLED_SINGLETON_HUNGERY_H #define UNTITLED_SINGLETON_HUNGERY_H class SingletonHungry{ private: static SingletonHungry* single; SingletonHungry() { cout << "constructor called!" << endl; }; SingletonHungry(SingletonHungry&) {}; SingletonHungry& operator=(const SingletonHungry&) {}; ~SingletonHungry() { cout << "destructor called!" << endl; } public: static SingletonHungry* GetSingleton(); }; // 重點(diǎn)在這里喧笔,在類定義的時(shí)候就已經(jīng)被實(shí)例化了 SingletonHungry *SingletonHungry::single = new SingletonHungry(); SingletonHungry *SingletonHungry::GetSingleton() { return single; } #endif >//UNTITLED_SINGLETON_HUNGERY_H // main文件中 SingletonHungry* single1 = SingletonHungry::GetSingleton(); SingletonHungry* single2 = >SingletonHungry::GetSingleton(); // 運(yùn)行結(jié)果帽驯,調(diào)用一次構(gòu)造函數(shù),不調(diào)用析構(gòu)函數(shù)书闸。注意main文件里什么都不寫尼变,其實(shí)構(gòu)造函數(shù)也會(huì)被調(diào)用,就是這樣 // SingletonHungry* single1 = SingletonHungry::GetSingleton(); // SingletonHungry* single2 = SingletonHungry::GetSingleton();
#ifndef UNTITLED_SINGLETON_LAZY_H #define UNTITLED_SINGLETON_LAZY_H // 懶漢模式 #include <mutex> class SingleLazy{ private: static SingleLazy* single; SingleLazy() {cout << "constructor called!"; }; SingleLazy(SingleLazy&); SingleLazy&operator=(const SingleLazy&); ~SingleLazy() {cout << "destructor called!"; }; static std::mutex my_mutex; public: static SingleLazy* GetSingleton() { if (single == NULL) { //這個(gè)鎖的保護(hù)范圍就是這個(gè)大括號(hào)中的內(nèi)容 std::lock_guard<std::mutex> ml(my_mutex); if (single == NULL) { single = new SingleLazy(); } } return single; } }; std::mutex SingleLazy::my_mutex; SingleLazy* SingleLazy::single = NULL; #endif //UNTITLED_SINGLETON_LAZY_H
書中進(jìn)階思路:定義構(gòu)造函數(shù)為private(只適用于單線程浆劲,多線程扔會(huì)創(chuàng)建多個(gè)) --- 在創(chuàng)建實(shí)例之前加同步鎖(lock(syncobj){if(instance == null) instance = new singleton;} 每次想要獲得實(shí)例享甸,還需要進(jìn)行同步鎖,非常耗時(shí)) --- 再多加一層判斷梳侨,先判斷實(shí)例是否存在蛉威,不存在再加鎖(if(instance == null) {lock(syncobj){if(instance == null) instance = new singleton;}},代碼復(fù)雜走哺,容易出錯(cuò))--- 強(qiáng)烈推薦的算法1:利用靜態(tài)構(gòu)造函數(shù)初始化靜態(tài)變量蚯嫌,利用靜態(tài)函數(shù)只被調(diào)用一次的特性(只要調(diào)用類就會(huì)被創(chuàng)建,浪費(fèi)內(nèi)存丙躏,這個(gè)應(yīng)該是對(duì)應(yīng)下面餓漢模式择示,在調(diào)用頻繁的時(shí)候更有優(yōu)勢,相當(dāng)于空間換時(shí)間) --- 強(qiáng)烈推薦算法2:定義一個(gè)私有類型晒旅,只有在調(diào)用靜態(tài)構(gòu)造函數(shù)創(chuàng)建實(shí)例時(shí)栅盲,該類型才會(huì)被調(diào)用(這個(gè)對(duì)應(yīng)下面的懶漢模式,在調(diào)用不頻繁時(shí)更有優(yōu)勢废恋,用時(shí)間換空間)谈秫。
書中代碼用的是c#扒寄,參考網(wǎng)上資料研究c++版本
https://www.cnblogs.com/sunchaothu/p/10389842.html, http://www.reibang.com/p/69eef7651667
https://www.cnblogs.com/xuelisheng/p/9744301.html
單例是設(shè)計(jì)模式的一種,其特點(diǎn)是只提供唯一一個(gè)類的實(shí)例拟烫,具有全局變量的特點(diǎn)该编,在任何位置都可以通過接口獲得那個(gè)唯一的實(shí)例。
具體應(yīng)用場景 1)設(shè)備管理器硕淑,系統(tǒng)中可能有多個(gè)設(shè)備课竣,但是只有一個(gè)設(shè)備管理器身弊,用于管理設(shè)備驅(qū)動(dòng) 2)數(shù)據(jù)池什乙,用于緩存數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)惜颇,在一處寫八秃,多處讀取,或者在多處寫盗誊,多處讀取
基礎(chǔ)要點(diǎn) 1)全局只有一個(gè)實(shí)例洒疚,static特性鸠姨,同時(shí)禁止用戶自己聲明并定義實(shí)例(將構(gòu)造函數(shù)定義為private) 2)線程安全 3)禁止賦值和拷貝 4)用戶通過接口獲取實(shí)例:使用static類成員函數(shù)
c++實(shí)現(xiàn)單例模式的幾種方法:
餓漢模式:在單例類定義時(shí)就進(jìn)行實(shí)例化寂拆。用空間來換取時(shí)間奢米。本身就是線程安全的抓韩。
懶漢模式:顧名思義纠永,就是不到萬不得已不會(huì)去實(shí)例化類,也就是第一次用到類實(shí)例的時(shí)候才會(huì)去實(shí)例化谒拴。在訪問量較小時(shí)使用尝江,用時(shí)間來換取空間。
餓漢模式代碼
class Singelton{
private:
Singelton(){
}
static Singelton *single;
public:
static Singelton *GetSingelton();
};
// 餓漢模式的關(guān)鍵:初始化(實(shí)例化)
Singelton *Singelton::single = new Singelton();
Singelton *Singelton::GetSingelton(){
return single;
}
簡單懶漢模式代碼英上,這個(gè)單例模式只適用于單線程炭序,當(dāng)多線程時(shí),其余線程可能判斷當(dāng)前對(duì)象是null創(chuàng)建新對(duì)象苍日,因此是線程不安全的(解決方法:加鎖)惭聂。同時(shí),只有new對(duì)象相恃,沒有delete對(duì)象辜纲,容易造成內(nèi)存泄漏(解決方法:使用共享指針)
class Singleton{
private:
Singleton(){};
static Singelton *single;
public:
static Singleton *GetSingleton();
};
Singleton *Singleton::single = nullpter; (與餓漢模式的區(qū)別點(diǎn))
Singleton *Singleton::GetSingleton() {
if (single == nullptr)
{
single = new Singleton();
}
return single;
}
雙檢鎖加只能指針懶漢模式,解決線程不安全和內(nèi)存泄漏問題拦耐。shared ptr在析構(gòu)的時(shí)候耕腾,其對(duì)應(yīng)的new出來的對(duì)象也會(huì)被delete掉,會(huì)調(diào)用析構(gòu)函數(shù)杀糯。但是其實(shí)關(guān)于內(nèi)存泄漏的問題存在爭議扫俺,有說法,https://blog.csdn.net/stpeace/article/details/68953096?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
單例伴隨著進(jìn)程的生命周期固翰, 常駐內(nèi)存狼纬, 不需要程序員來釋放(實(shí)際上羹呵, 人為釋放是有風(fēng)險(xiǎn)的)。 如果進(jìn)程終結(jié)畸颅, 對(duì)應(yīng)的堆內(nèi)存自動(dòng)被回收担巩, 不會(huì)泄露。
class Singleton{
private:
Singleton(){};
Singleton(Singleton&){};
Singleton& operator=(const Singleton&){};
// static Singelton *single;
static Ptr m_instance_ptr; // 實(shí)例改為智能指針
static std::mutex m_mutex; // 加鎖
public:
typedef std::shared_ptr<Singleton> Ptr;
static Ptr GetSingleton();
};
Singleton::Ptr Singleton::m_instance_ptr = nullpter;
std::mutex Singleton::m_mutex;
Ptr Singleton::GetSingleton() {
if (m_instance_ptr == nullptr){ // 如果判斷為空没炒,再加鎖判斷涛癌,不然加鎖判斷比較耗時(shí)
std::lock_guard<std::mutex> lk(m_mutex);
if(m_instance_ptr == nullpter)
{
m_instance_ptr = std::shared_ptr<Singleton>(new Singleton);
}
return m_instance_ptr;
}
}
這種模式仍存在不足1)使用智能指針要求用戶也要用只能指針。同時(shí)用鎖也有開銷送火。更嚴(yán)重的是拳话,在某些平臺(tái),雙檢鎖會(huì)失效种吸。具體解決不研究了弃衍,參考https://www.cnblogs.com/sunchaothu/p/10389842.html
【c++拾遺】c++內(nèi)存區(qū) 參考鏈接 https://blog.csdn.net/aheadkeeper/article/details/105038740
在c++中,內(nèi)存分為5個(gè)區(qū)坚俗,分別為堆镜盯,棧,自由存儲(chǔ)區(qū)猖败,全局/靜態(tài)存儲(chǔ)區(qū)和常量存儲(chǔ)區(qū)
堆:由new分配的內(nèi)存塊速缆,他們的釋放編譯器不管,由程序來控制恩闻,一般一個(gè)new對(duì)應(yīng)一個(gè)delete艺糜。如果程序員沒有釋放掉,在程序結(jié)束幢尚,操作系統(tǒng)會(huì)自動(dòng)回收破停。
棧:就是編譯器在需要的時(shí)候分配,在不需要的時(shí)候自動(dòng)清理的空間尉剩。里面變量包括局部變量真慢,函數(shù)參數(shù)等。
自由存儲(chǔ)區(qū): 由malloc分配的內(nèi)存塊理茎,與堆相似黑界,不過是用free來進(jìn)行釋放
全局/靜態(tài)存儲(chǔ)區(qū):全局變量和靜態(tài)變量都被分配在同一塊內(nèi)存中
常量存儲(chǔ)區(qū):存放常量
int\* p = new int[5];
中即用到了堆又用到了棧。程序會(huì)先確定在堆中分配內(nèi)存的大小功蜓,調(diào)用operatenew來分配一塊內(nèi)存區(qū)园爷。然后返回內(nèi)存區(qū)首地址,為指針值式撼,存在棧中童社。釋放內(nèi)存時(shí)要注意告訴編譯器這是一塊數(shù)組delete []p
【c++拾遺】 static關(guān)鍵字(修飾類,方法著隆,變量扰楼,靜態(tài)塊)參考鏈接: https://www.cnblogs.com/XuChengNotes/p/10403523.html
c++中static用法分為面向過程的static用法(面向函數(shù)和變量)和面向?qū)ο蟮膕tatic用法(面向類)
面向過程的static用法 1)靜態(tài)全局變量 2)靜態(tài)局部變量 3)靜態(tài)函數(shù)
靜態(tài)變量都在全局?jǐn)?shù)據(jù)分配區(qū)呀癣,包括靜態(tài)全局變量和靜態(tài)局部變量。把全局變量改為靜態(tài)全局變量是改變其作用域弦赖,限制其使用范圍只在本文件中项栏。將局部變量改為全局變量是改變其存儲(chǔ)方式,進(jìn)而改變其生存期蹬竖。
1)靜態(tài)全局變量
在全局變量之前沼沈,加上static,這個(gè)變量就被定義為靜態(tài)全局變量币厕。具有以下特點(diǎn) a.該變量在全局?jǐn)?shù)據(jù)區(qū)分配內(nèi)存 b.未經(jīng)初始化的靜態(tài)全局變量會(huì)被程序自動(dòng)初始化成0 c.靜態(tài)全局變量在聲明他的整個(gè)文件是可見的列另,在文件外是不可見的
靜態(tài)全局變量與普通全局變量:
共同點(diǎn):都存在全局變量區(qū),都是靜態(tài)存儲(chǔ)旦装,就是編譯的時(shí)候就分配好空間页衙。
不同點(diǎn):動(dòng)態(tài)全局變量的作用域?yàn)檎麄€(gè)源程序,當(dāng)一個(gè)源程序有多個(gè)源文件阴绢,動(dòng)態(tài)全局變量在各個(gè)文件中都有效店乐。但是靜態(tài)全局變量只在自己定義的文件中有效。靜態(tài)函數(shù)也是為了限制其作用域呻袭,只在本文件中可見眨八,其余文件中可以定義同名函數(shù),不會(huì)發(fā)生沖突棒妨。
2)靜態(tài)局部變量
通常情況下踪古,在函數(shù)體內(nèi)定義一個(gè)變量含长,每當(dāng)程序運(yùn)行到該語句就會(huì)給局部變量分配棧內(nèi)存券腔。但隨著程序退出函數(shù)體,棧內(nèi)存會(huì)被釋放拘泞,局部變量也會(huì)因此失效纷纫。但是有時(shí)候,我們需要在兩次調(diào)用之間對(duì)變量值進(jìn)行保存陪腌,用靜態(tài)局部變量就可以實(shí)現(xiàn)這個(gè)功能辱魁。
特點(diǎn):a. 在全局?jǐn)?shù)據(jù)區(qū)分配空間 b. 在程序首次執(zhí)行到聲明處時(shí)被初始化,之后不會(huì)再進(jìn)行初始化 c.如果沒有顯式初始化诗鸭,會(huì)被程序自動(dòng)初始化成0 d.靜態(tài)局部變量作用域?yàn)榫植孔饔糜蛉敬亍5谴鎯?chǔ)是在全局存儲(chǔ)區(qū),知道程序運(yùn)行結(jié)束强岸。
3)靜態(tài)函數(shù)
聲明只在本文件中可見锻弓,不會(huì)被其余文件使用。在其余文件中定義同名函數(shù)蝌箍,不會(huì)發(fā)生沖突青灼。面向?qū)ο蟮膕tatic方法 1)靜態(tài)數(shù)據(jù)成員 2)靜態(tài)成員函數(shù) 3)靜態(tài)類
1)靜態(tài)數(shù)據(jù)成員
對(duì)于非靜態(tài)暴心,每個(gè)類都有自己的拷貝。為靜態(tài)數(shù)據(jù)成員杂拨,無論這個(gè)類的對(duì)象被定義了多少個(gè)专普,靜態(tài)數(shù)據(jù)成員在程序中只有一份拷貝,由所有成員共享弹沽。
靜態(tài)數(shù)據(jù)成員初始化形式與其余數(shù)據(jù)成員不同檀夹,<數(shù)據(jù)類型><類名>::<靜態(tài)數(shù)據(jù)成員名>=<值>。若靜態(tài)數(shù)據(jù)成員為public屬性策橘,其訪問格式<類對(duì)象名>.<靜態(tài)數(shù)據(jù)成員名> 或 <類類型名>::<靜態(tài)數(shù)據(jù)成員名>
2)靜態(tài)成員函數(shù)
與靜態(tài)數(shù)據(jù)成員一樣击胜,靜態(tài)成員函數(shù)不是具體為某一個(gè)類的對(duì)象服務(wù)。普通成員函數(shù)一般隱藏一個(gè)this指針役纹,指向?qū)ο蟊旧砼妓ぃ庆o態(tài)成員函數(shù)不與任何對(duì)象聯(lián)系,因此其不具有this指針促脉。因此辰斋,靜態(tài)成員函數(shù)無法訪問非靜態(tài)數(shù)據(jù)成員和其余非靜態(tài)成員函數(shù)。但是非靜態(tài)成員函數(shù)可以訪問靜態(tài)成員函數(shù)和成員變量瘸味。
3)靜態(tài)類
靜態(tài)類只能包含靜態(tài)成員類型宫仗,不能進(jìn)行實(shí)例化,不能被繼承旁仿,不能在外部進(jìn)行new操作藕夫。相當(dāng)于一個(gè)sealed abstract類。
注意事項(xiàng):
1)靜態(tài)成員函數(shù)只能訪問靜態(tài)數(shù)據(jù)成員和其余靜態(tài)成員函數(shù)枯冈。靜態(tài)成員函數(shù)不能被定義為虛函數(shù)
2)靜態(tài)數(shù)據(jù)成員是靜態(tài)存儲(chǔ)的毅贮,因此必須進(jìn)行初始化,且初始化的方式與普通數(shù)據(jù)成員不同
靜態(tài)數(shù)據(jù)成員的初始化需要在類外進(jìn)行尘奏,前面不加static滩褥。且不能在頭文件中進(jìn)行,避免頭文件被多次引用炫加,造成多次定義瑰煎。
【c++拾遺】智能指針 參考鏈接 https://blog.csdn.net/k346k346/article/details/81478223
auto_ptr, unique_ptr, shared_ptr, weak_ptr。其中auto_ptr已被廢棄俗孝,使用unique_ptr替代酒甸。c++中,new在動(dòng)態(tài)內(nèi)存管理中為對(duì)象分配空間赋铝,并返回一個(gè)指向該對(duì)象的指針插勤,可以對(duì)對(duì)象進(jìn)行初始化。delete接受一個(gè)動(dòng)態(tài)對(duì)象指針,銷毀該對(duì)象饮六,并釋放與之關(guān)聯(lián)的內(nèi)存其垄。但是動(dòng)態(tài)內(nèi)存使用,有時(shí)候容易忘記釋放卤橄,導(dǎo)致內(nèi)存泄漏绿满,有時(shí)候在還在使用的時(shí)候就釋放了,導(dǎo)致引用非法內(nèi)存指針窟扑。為了更容易管理動(dòng)態(tài)內(nèi)存喇颁,c++提供智能指針管理動(dòng)態(tài)對(duì)象。智能指針可以自動(dòng)釋放其所指向的內(nèi)存空間嚎货。
1)unique是c++ 11引入橘霎,替代不安全的auto。unique是定義在頭文件<memory>中的智能指針殖属。他持有對(duì)對(duì)象的獨(dú)有權(quán) --- 兩個(gè)unique不能指向同一個(gè)對(duì)象姐叁。他無法復(fù)制到其余的unique,也無法通過傳值傳遞到函數(shù)洗显。
2)shared是一個(gè)標(biāo)準(zhǔn)的共享所有權(quán)智能指針外潜,允許多個(gè)指針指向同一個(gè)對(duì)象,通用定義在頭文件<memory>中挠唆。
3)weak被設(shè)計(jì)為和shared共同工作处窥,用于觀測shared的使用情況,可以用來判斷管理的shared資源是否被釋放玄组,解決shared循環(huán)問題滔驾。智能指針的選用指南:
1)如果程序要使用多個(gè)指向同一個(gè)對(duì)象的指針,用shared俄讹。具體情況包括
a. 將參數(shù)作為參數(shù)或函數(shù)返回值進(jìn)行傳遞
b. 兩個(gè)對(duì)象都包含指向第三個(gè)對(duì)象的指針
c. stl容器包含指針(不是很明白)
2)如果程序不需要多個(gè)指向同一個(gè)對(duì)象的指針哆致,用unique。如果函數(shù)使用new分配內(nèi)存颅悉,并返回該內(nèi)存的指針沽瞭,將其返回類型聲明為unique是不錯(cuò)的選擇迁匠,用該指針負(fù)責(zé)delete剩瓶。
【c++拾遺】看到的兩種寫單例互斥的鎖模式
參考鏈接:https://blog.csdn.net/xy_cpp/article/details/819105131)pthread_mutex_t
互斥鎖,主要有pthread_mutex_init城丧,pthread_mutex_destory延曙,pthread_mutex_lock,pthread_mutex_unlock幾個(gè)函數(shù)完成鎖的初始換亡哄,鎖的銷毀枝缔,上鎖和釋放鎖操作。
使用方法:
pthread_mutex_t mutex ;(定義)
pthread_mutex_init(&mutex,NULL);(初始化)
pthread_mutex_lock(&mutex); (上鎖)
pthread_mutex_unlock(&mutex); (釋放鎖)
pthread_mutex_destroy(&mutex); (銷毀鎖)
2)lock_guard
mutex互斥量是一個(gè)類,這個(gè)類有一個(gè)lock()方法和一個(gè)unlock()方法愿卸。如果第一次運(yùn)行了lock方法而沒有運(yùn)行unlock灵临,第二次運(yùn)行l(wèi)ock的時(shí)候會(huì)卡在這里,只有當(dāng)運(yùn)行了unlock方法后趴荸,第二次lock方法才會(huì)通過儒溉。運(yùn)用這種鎖的機(jī)制可以保障兩端代碼獨(dú)立運(yùn)行。lock和unlock必須成對(duì)出現(xiàn)发钝,不能多寫也不能少寫顿涣。
std::mutex my_mutex_1;
my_mutex_1.lock();
要保護(hù)的代碼塊
my_mutex_1.unlock();lock_quard()是一個(gè)類模板,代替lock和unlock酝豪。將需要保護(hù)的代碼用一個(gè)大括號(hào)括起來涛碑,然后在大括號(hào)的第一句定義
std::mutex my_mutex_1;
{std::lock_guard<std::mutex> my_lock_guard(my_mutex_1)
要保護(hù)的代碼塊}