C++構造函數一些常見的坑
文章轉自微信 公眾號:Coder梁(ID:Coder_LT)
某一天我們接到瞭一個需求,需要開發一個類似於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文件當中,我們定義瞭一個StringBad
類,這是C++ Primer
當中的一個例子。為什麼叫StringBad
呢,主要是為瞭提示,表示這是一個沒有完全開發好的demo
。
這裡有一個小細節,我們在類當中定義的是一個char *也就是字符型指針,而非字符型數組。這意味著我們在類聲明當中沒有為字符串本身分配空間,而是在構造函數當中使用new
來完成的,避免瞭預先定義字符串的長度。
其次num_strings
是一個靜態成員,也就是說無論創建瞭多少對象,它都隻會保存一份。類的所有成員共享同一個靜態變量。
接下來我們來看一下它的實現:
#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,我們不能在類聲明中初始化靜態成員變量。因為聲明隻是描述瞭如何分配內存,但並不真的分配內存。
所以對於靜態類成員,我們可以在類聲明之外使用單獨的語句進行初始化。因為靜態成員變量是單獨存儲的,並不是對象的一部分。
初始化要在方法文件也就是cpp
文件當中,而不是頭文件中。因為頭文件可能會被引入多次,如果在頭文件中初始化將會引起錯誤。當然也有一種例外,就是加上瞭const
關鍵字。
從邏輯上看,我們這樣實現並沒有任何問題,但是當我們執行的時候,就會發現問題很多……
假設我們現在有一個函數:
void callme(StringBad sb) { cout << " \"" << sb << "\"\n"; }
然後我們這麼使用:
int main() { StringBad sb("test"); callme(sb); return 0; }
會得到一個奇怪的結果:
從屏幕可以看到我們的析構函數執行瞭兩次,一次很好理解應該是main函數退出的時候自動執行的,還有一次呢?是什麼時候執行的?
答案是執行callme
函數的時候執行的,因為callme
函數使用瞭值傳遞。當callme
函數執行結束時,也會調用參數sb的析構函數。
如果我們改成引用傳遞,就一切正常瞭:
void callme(StringBad &sb) { cout << " \"" << sb << "\"\n"; } int main() { StringBad sb("test"); callme(sb); return 0; }
這還沒完,我們把代碼再改一下,會發現還有問題:
int main() { StringBad sb("test"); StringBad sports("Spinach Leaves Bowl for Dollars"); StringBad sailor = sports; StringBad knot; StringBad st = sb; return 0; }
執行一下,得到:
會發現又有負數出現瞭,這是為什麼呢?
因為我們執行瞭StringBad st = sb
這樣的操作,這個操作並不會調用我們實現的任何一個構造函數。
它等價於:
StringBad st = StringBad(sb);
對應的構造函數原型是:
StringBad(const StringBad&);
當我們用一個對象來初始化另外一個對象的時候,編譯器將會自動生成上述的構造函數。這樣的構造函數叫做拷貝構造函數,由於我們沒有重載拷貝構造函數,因此它不知道要對num_strings
變量做處理,也就導致瞭不一致的發生。
到此這篇關於C++構造函數一些常見的坑的文章就介紹到這瞭,更多相關C++構造函數的坑內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!