類的構造,拷貝構造,析構,賦值(string類)
建構函式初始化表的使用規則:
如果類存在繼承關係,派生類必須在起初始化表裡呼叫基類的建構函式
類的const常量只能在初始列表裡被初始化,因為他不能在函式體內用賦值的方式初始化
類的資料成員的初始化可以採用初始化表或函式體內賦值兩種方式,,這兩種方式的 效率不完全相同
非內部資料型別的成員物件應當採用第一種方式初始化,以獲取更高的效率。例如
class A
{…A(void);// 無引數建構函式
A(const A &other);// 拷貝建構函式
A & operate =( const A&other); // 賦值函式
};
class B
{public:
B(const A &a); // B
private:Am_a;// 成員物件
};
示例 9-2(a)中,類 B 的建構函式在其初始化表裡呼叫了類 A 的拷貝建構函式,從而 將成員物件 m_a 初始化。
示例 9-2 (b)中,類 B 的建構函式在函式體內用賦值的方式將成員物件 m_a 初始化。 我們看到的只是一條賦值語句,但實際上 B 的建構函式幹了兩件事:先暗地裡建立 m_a 物件(呼叫了 A 的無引數建構函式),再呼叫類 A 的賦值函式,將引數 a 賦給m_a。
對於內部資料型別的資料成員而言,兩種初始化方式的效率幾乎沒有區別,但後者 的程式版式似乎更清晰些。若類 F 的宣告如下:
class F
{
public:
F(int x, int y);//
private:i
nt m_x, m_y;
int m_i, m_j;
};
示例 9-2(c)中 F 的建構函式採用了第一種初始化方式,示例 9-2(d)中 F 的構造函 數採用了第二種初始化方式。
2.程式碼展示:
class String { public: String(const char *str = NULL); // 普通建構函式 String(const String &other); // 拷貝建構函式 ~ String(void); // 解構函式 String & operator = (const String &other); // 賦值函式 private: char *m_data; // 用於儲存字串 }; // String 的普通建構函式 String::String(const char *str) { if (str == NULL) { m_data = new char[1]; *m_data = '\0'; } else { int length = strlen(str); m_data = new char[length + 1]; strcpy(m_data, str); } } // String 的解構函式 String::~String(void) { delete[]m_data;//由於由於 m_data 是內部資料型別,也可以寫成 delete m_data; } // 拷貝建構函式 String::String(const String&other) { //允許操作other的私有成員m_data; int length = strlen(other.m_data); m_data = new char[length + 1]; strcpy(m_data, other.m_data); cout << "kaobeigouzao" << endl; } //賦值函式 String&String::operator = (const String &other) { //檢查自賦值 if (this == &other) return*this; //釋放原有的記憶體資源 delete[]m_data; //分配新的記憶體資源,並複製內容 int length = strlen(other.m_data); m_data = new char[length + 1]; strcpy(m_data, other.m_data); //返回本物件的引用 cout << "fuzhi" << endl; return *this; } void main() { String a;//構造 char *str = "fdsgfd"; String b(a);//拷貝構造 String c;//構造 c = a;//賦值 }
類 String 拷貝建構函式與普通建構函式(參見 9.4 節)的區別是:在函式入口處無需與 NULL 進行比較,這是因為“引用”不可能是 NULL,而“指標”可以為 NULL。類 String 的賦值函式比建構函式複雜得多,分四步實現:
(1)第一步,檢查自賦值。你可能會認為多此一舉,難道有人會愚蠢到寫出 a = a 這樣的自賦值語句!的確不會。但是間接的自賦值仍有可能出現,例如
也許有人會說:“即使出現自賦值,我也可以不理睬,大不了化點時間讓物件複製自己而已,反正不會出錯!
他真的說錯了。看看第二步的 delete,自殺後還能複製自己嗎?所以,如果發現自 賦值,應該馬上終止函式。注意不要將檢查自賦值的 if 語句 if(this == &other) 錯寫成為if( *this == other)
(2)第二步,用 delete 釋放原有的記憶體資源。如果現在不釋放,以後就沒機會了,將造成記憶體洩露。
(3)第三步,分配新的記憶體資源,並複製字串。注意函式 strlen 返回的是有效字串長度,不包含結束符‘\0’。函式 strcpy 則連‘\0’一起復制。
(4)第四步,返回本物件的引用,目的是為了實現象 a = b = c 這樣的鏈式表達。注意不要將 return *this 錯寫成 return this 。那麼能否寫成 return other 呢?效果 不是一樣嗎? 不可以!因為我們不知道引數other 的生命期。有可能 other 是個臨時物件,在賦 值結束後它馬上消失,那麼 return other 返回的將是垃圾。
3. 如何在派生類中實現類的基本函式
基類的建構函式、解構函式、賦值函式都不能被派生類繼承。如果類之間存在繼承關係,在編寫上述基本函式時應注意以下事項:
1.派生類的建構函式應在其初始化表裡呼叫基類的建構函式。
2.基類與派生類的解構函式應該為虛(即加 virtual 關鍵字)。例如
class Base
{
public:
virtual ~Base()
{
cout << "~base" << endl;
}
};
class Derived :public Base
{
public:
virtual~Derived()
{
cout << "~Derived" << endl;
}
};
void main()
{
Base*Pb = new Derived;
delete Pb;
}
3.在編寫派生類的賦值函式時,注意不要忘記對基類的資料成員重新賦值。例如:
class Base
{
public:
Base&operator = (const Base&other);// 類 Base 的賦值函式
private:
int m_i, m_j, m_k;
};
class Derived :public Base
{
public:
Derived &operator=(const Derived&other);
private:
int m_x, m_y, m_z;
};
Derived&Derived::operator=(const Derived&other)
{
//(1)檢查自賦值
if(this == &other) return *this;
//(2)對基類的資料成員重新賦值
Base::operator =(other); // 因為不能直接操作私有資料成員
//(3)對派生類的資料成員賦值
m_x = other.m_x; m_y = other.m_y; m_z = other.m_z;
//(4)返回本物件的引用
return *this;
}
參考資料:《高質量c++程式設計指南》