C++構(gòu)造函數(shù)一些常見的坑
文章轉(zhuǎn)自微信 公眾號:Coder梁(ID:Coder_LT)
某一天我們接到了一個需求,需要開發(fā)一個類似于STL
中string
的類。
我們很快寫好了代碼:
#include <iostream> #ifndef STRINGBAD_H_ #define STRINGBAD_H_ class StringBad { ? ? private: ? ? ?char *str; ? ? ?int len; ? ? ?static int num_strings; ? ? public: ? ? ?StringBad(const char* s); ? ? ?StringBad(); ? ? ?~StringBad(); ? ? ?friend std::ostream & operator << (std::ostream &os, const StringBad & st); }; #endif
在這個.h文件當(dāng)中,我們定義了一個StringBad
類,這是C++ Primer
當(dāng)中的一個例子。為什么叫StringBad
呢,主要是為了提示,表示這是一個沒有完全開發(fā)好的demo
。
這里有一個小細(xì)節(jié),我們在類當(dāng)中定義的是一個char *也就是字符型指針,而非字符型數(shù)組。這意味著我們在類聲明當(dāng)中沒有為字符串本身分配空間,而是在構(gòu)造函數(shù)當(dāng)中使用new
來完成的,避免了預(yù)先定義字符串的長度。
其次num_strings
是一個靜態(tài)成員,也就是說無論創(chuàng)建了多少對象,它都只會保存一份。類的所有成員共享同一個靜態(tài)變量。
接下來我們來看一下它的實現(xiàn):
#include <cstring> #include "stringbad.h" using std::cout; int StringBad::num_strings = 0; StringBad::StringBad(const char* s) { ? ? len = std::strlen(s); ? ? str = new char[len+1]; ? ? std::strcpy(str, s); ? ? num_strings++; ? ? cout << num_strings << ": \"" << str << "\" object created \n"; } StringBad::StringBad() { ? ? len = 4; ? ? str = new char[4]; ? ? std::strcpy(str, "C++"); ? ? num_strings++; ? ? cout << num_strings << ": \"" << str << "\" object created \n"; } StringBad::~StringBad() { ? ? cout << "\"" << str << "\" object deleted, "; ? ? --num_strings; ? ? cout << num_strings << " left \n"; ? ? delete []str; } std::ostream & operator<<(std::ostream & os, const StringBad &st) { ? ? os << st.str; ? ? return os; }
首先,我們可以注意到第一句就是將num_strings
初始化成了0,我們不能在類聲明中初始化靜態(tài)成員變量。因為聲明只是描述了如何分配內(nèi)存,但并不真的分配內(nèi)存。
所以對于靜態(tài)類成員,我們可以在類聲明之外使用單獨(dú)的語句進(jìn)行初始化。因為靜態(tài)成員變量是單獨(dú)存儲的,并不是對象的一部分。
初始化要在方法文件也就是cpp
文件當(dāng)中,而不是頭文件中。因為頭文件可能會被引入多次,如果在頭文件中初始化將會引起錯誤。當(dāng)然也有一種例外,就是加上了const
關(guān)鍵字。
從邏輯上看,我們這樣實現(xiàn)并沒有任何問題,但是當(dāng)我們執(zhí)行的時候,就會發(fā)現(xiàn)問題很多……
假設(shè)我們現(xiàn)在有一個函數(shù):
void callme(StringBad sb) { ?cout << " ? ?\"" << sb << "\"\n"; }
然后我們這么使用:
int main() { ?StringBad sb("test"); ?callme(sb); ?return 0; }
會得到一個奇怪的結(jié)果:
從屏幕可以看到我們的析構(gòu)函數(shù)執(zhí)行了兩次,一次很好理解應(yīng)該是main函數(shù)退出的時候自動執(zhí)行的,還有一次呢?是什么時候執(zhí)行的?
答案是執(zhí)行callme
函數(shù)的時候執(zhí)行的,因為callme
函數(shù)使用了值傳遞。當(dāng)callme
函數(shù)執(zhí)行結(jié)束時,也會調(diào)用參數(shù)sb的析構(gòu)函數(shù)。
如果我們改成引用傳遞,就一切正常了:
void callme(StringBad &sb) { ?cout << " ? ?\"" << sb << "\"\n"; } int main() { ?StringBad sb("test"); ?callme(sb); ?return 0; }
這還沒完,我們把代碼再改一下,會發(fā)現(xiàn)還有問題:
int main() { ?StringBad sb("test"); ?StringBad sports("Spinach Leaves Bowl for Dollars"); ?StringBad sailor = sports; ?StringBad knot; ?StringBad st = sb; ?return 0; }
執(zhí)行一下,得到:
會發(fā)現(xiàn)又有負(fù)數(shù)出現(xiàn)了,這是為什么呢?
因為我們執(zhí)行了StringBad st = sb
這樣的操作,這個操作并不會調(diào)用我們實現(xiàn)的任何一個構(gòu)造函數(shù)。
它等價于:
StringBad st = StringBad(sb);
對應(yīng)的構(gòu)造函數(shù)原型是:
StringBad(const StringBad&);
當(dāng)我們用一個對象來初始化另外一個對象的時候,編譯器將會自動生成上述的構(gòu)造函數(shù)。這樣的構(gòu)造函數(shù)叫做拷貝構(gòu)造函數(shù),由于我們沒有重載拷貝構(gòu)造函數(shù),因此它不知道要對num_strings
變量做處理,也就導(dǎo)致了不一致的發(fā)生。
到此這篇關(guān)于C++構(gòu)造函數(shù)一些常見的坑的文章就介紹到這了,更多相關(guān)C++構(gòu)造函數(shù)的坑內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言創(chuàng)建動態(tài)dll和調(diào)用dll(visual studio 2013環(huán)境下)
本篇文章主要介紹了C語言創(chuàng)建動態(tài)dll和調(diào)用dll(visual studio 2013環(huán)境下),非常具有實用價值,需要的朋友可以參考下2017-11-11純C語言:遞歸二進(jìn)制轉(zhuǎn)十進(jìn)制源碼分享
這篇文章主要介紹了純C語言:遞歸二進(jìn)制轉(zhuǎn)十進(jìn)制源碼,有需要的朋友可以參考一下2014-01-01C++實現(xiàn)關(guān)機(jī)功能詳細(xì)代碼
大家好,本篇文章主要講的是C++實現(xiàn)關(guān)機(jī)功能詳細(xì)代碼,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01