條款11: 為需要動態分配內存的類聲明一個拷貝構造函數和一個賦值操作符
這個條款的原因在哪裡呢?
就是如果你創建一個類,什麼都不做,那麼類會給你創建一個默認構造函數,默認析構函數,默認拷貝函數和默認賦值函數。
所以出問題就出在默認上面去了,尤其是默認拷貝函數和默認賦值函數出的問題最多。
默認拷貝函數會怎麼做呢,對於a=b,它會將b中的成員逐位拷貝給另一個a,如果通過指針動態分配內存,則僅僅將指針的值賦給a。
這會導致至少兩個問題:
第一,b曾指向的內存永遠不會被刪除,因而會永遠丟失。這是產生內存洩漏的典型例子。第二,現在a和b包含的指針指向同一個字符串,那麼只要其中一個離開了它的生存空間,其析構函數就會刪除掉另一個指針還指向的那塊內存。
看下面代碼:
[cpp]
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
class MyString
{
public:
MyString(const char* value);
~MyString();
friend ostream& operator << (ostream& os,MyString &c);
private:
char *data;
};
MyString::MyString(const char* value)
{
if(value){
data = new char[strlen(value)+1];
strcpy(data,value);
}
else{
data = new char[1];
data = '\0';
}
}
ostream& operator << (ostream& os,MyString &c)
{
os<<c.data;
return os;
}
MyString::~MyString()
{
delete []data;
}
int main()
{
MyString a("hello");
{
MyString b("world");
b = a;
}
MyString c = a;
cout<<c<<endl;
}
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
class MyString
{
public:
MyString(const char* value);
~MyString();
friend ostream& operator << (ostream& os,MyString &c);
private:
char *data;
};
MyString::MyString(const char* value)
{
if(value){
data = new char[strlen(value)+1];
strcpy(data,value);
}
else{
data = new char[1];
data = '\0';
}
}
ostream& operator << (ostream& os,MyString &c)
{
os<<c.data;
return os;
}
MyString::~MyString()
{
delete []data;
}
int main()
{
MyString a("hello");
{
MyString b("world");
b = a;
}
MyString c = a;
cout<<c<<endl;
}
看到輸出的結果是這樣的
▒▒#a▒▒#a
Aborted (core dumped)
可以看到c得不到正確的值,同時析構函數調用的時候出現崩潰的問題。
所以,如果你會調用拷貝構造函數和賦值函數,那麼一定要顯示的聲明他們,否則將他們定義為私有的。
那麼下面就看看如何顯示的聲明他們,如下
[cpp]
MyString::MyString()
{
data = NULL;
}
MyString::MyString(MyString &myString)
{
if (this == &myString)
{
return;
}
else
{
if(myString.data != NULL)
{
data = new char[strlen(myString.data)];
strcpy(data,myString.data);
}
else
{
data = new char[1];
data = '\0';
}
}
}
MyString & MyString::operator= (const MyString &myString)
{
if (this == &myString)
{
return *this;
}
else
{
delete []data;
if(myString.data != NULL)
{
data = new char[strlen(myString.data)];
strcpy(data,myString.data);
}
else
{
data = new char[1];
data = '\0';
}
}
return *this;
}
MyString::MyString()
{
data = NULL;
}
MyString::MyString(MyString &myString)
{
if (this == &myString)
{
return;
}
else
{
if(myString.data != NULL)
{
data = new char[strlen(myString.data)];
strcpy(data,myString.data);
}
else
{
data = new char[1];
data = '\0';
}
}
}
MyString & MyString::operator= (const MyString &myString)
{
if (this == &myString)
{
return *this;
}
else
{
delete []data;
if(myString.data != NULL)
{
data = new char[strlen(myString.data)];
strcpy(data,myString.data);
}
else
{
data = new char[1];
data = '\0';
}
}
return *this;
}
這裡需要注意幾點:
1 MyString c = a; 實際上調用的是MyString::MyString(MyString &myString), 而不是重載 =號的函數。
你可以用vs或者gdb來單步調試。原因在於,這種形式是初始化,而不是賦值。
2 下面兩個才是賦值
MyString b("world");
MyString c;
b = a;
c = a;
這個時候,你需要先刪除之前的data數據,考慮到有的時候參數為0,所以就定義一個參數為0的構造函數,或者將上面的構造函數寫成MyString::MyString(const char* value = NULL)的缺省參數的形式。
3 拷貝構造函數的時候,不需要刪除data,因為拷貝構造函數的時候,this->data肯定沒有申請空間,刪除會引起錯誤。
4 上面代碼其實有二個錯誤,就是應該申請的空間為strlen(data)+1,最後一位'\0';
我在cygwin 下面運行是沒問題的,估計是僥幸,但是 vs裡面運行就會崩潰。
另外,大家可以練習怎麼寫這個函數,面試經常會問到的。真正的理解,需要一個過程,主要很多細節:要判斷是否相等,要注意什麼時候可以刪除data,什麼時候不可以, 要注意new [] 和delete[] 的對應。
條款12: 盡量使用初始化而不要在構造函數裡賦值
書上介紹的很清楚了,我這裡主要總結一下、
初始化的好處:
1 const 和引用必須使用初始化,而不能用賦值。
2 效率高。
初始化的流程,就一步,將類成員初始化。
而構造函數內賦值則有兩步:第一,調用默認構造函數,第二,調用賦值構造函數。
類的構造函數的本質上是函數,函數就有形參和實參。
例如
[cpp]
class A
{
pulic:
A(B &bInput);
private:
B b;
}
A::A(B &bInput)
{
b = bInput;
}
class A
{
pulic:
A(B &bInput);
private:
B b;
}
A::A(B &bInput)
{
b = bInput;
}
它的流程是什麼呢?先調用B()生成b,然後在調用operator = 賦值函數,效率當然無法保證了。這也是第一條的原因,如果是 const或者引用則等於是聲明了一下,而沒有初始化,那麼編譯的時候肯定報錯。
條款13: 初始化列表中成員列出的順序和它們在類中聲明的順序相同
舉個簡單例子:
[cpp]
#include <iostream>
using namespace std;
class A
{
public:
A(int value);
void print();
private:
int i;
int j;
};
A::A(int value):j(value),i(j)
{
}
void A::print()
{
cout<<i<<" "<<j<<endl;
}
int main()
{
A a(10);
a.print();
}
#include <iostream>
using namespace std;
class A
{
public:
A(int value);
void print();
private:
int i;
int j;
};
A::A(int value):j(value),i(j)
{
}
void A::print()
{
cout<<i<<" "<<j<<endl;
}
int main()
{
A a(10);
a.print();
}
這個輸出的結果是什麼?
有些人以為是10 10
我電腦上的結果是
2281060 10
第一個數是隨機數,因為執行的順序是j(i) 然後才是j(10)
條款14: 確定基類有虛析構函數
我之前有篇文章專門將這個,大家可以看看
http://blog.csdn.net/mlkiller/article/details/8884321
這裡就不展開了。
條款15: 讓operator=返回*this的引用
剛才在寫前面的例子,重載符號<<和=的時候,我還想返回值怎麼去寫。
<<符號是和=都是二元操作符,但是後面跟的參數不一樣,<<跟了兩個參數,流對象和操作對象,它最後返回流對象。
而=只有自己,它的返回值呢應該是這個對象本身,我就在糾結&引用怎麼返回,它和this指針之間什麼關系。
等知道答案的時候,還是有些疑惑*this和&引用相等麼?看個例子
[cpp]
int p = 1;
int *q = &p;
int &t = p;
int &s = *q;
int p = 1;
int *q = &p;
int &t = p;
int &s = *q;
你看到引用本身怎麼用指針初始化。
關於文中返回const類型,我沒有搞得太清楚,時間比較晚了,以後弄明白在寫進來。