1.從概念上區分:
復制構造函數是構造函數,而賦值操作符屬於操作符重載范疇,它通常是類的成員函數
2.從原型上來區分:
復制構造函數原型ClassType(const ClassType &);無返回值
賦值操作符原型ClassType& operator=(const ClassType &);返回值為ClassType的引用,便於連續賦值操作
3.從使用的場合來區分:
復制構造函數用於產生對象,它用於以下幾個地方:函數參數為類的值類型時、函數返回值為類類型時以及初始化語句,例如(示例了初始化語句,函數參數與函數返回值為類的值類型時較簡單,這裡沒給出示例)
ClassType a; //
ClassType b(a); //調用復制構造函數
ClassType c = a; //調用復制構造函數
而賦值操作符要求‘=’的左右對象均已存在,它的作用就是把‘=’右邊的對象的值賦給左邊的對象
ClassType e;
Class Type f;
f = e; //調用賦值操作符
復制構造函數是去完成對未初始化的存儲區的初始化,而賦值操作符則是處理一個已經存在的對象。對一個對象賦值,當它一次出現時,它將調用復制構造函數,以後每次出現,都調用賦值操作符。
構造函數、析構函數、賦值函數是每個類最基本的的函數。每個類只有一個析構函數和一個賦值函數。但是有很多構造函數(一個為復制構造函數,其他為普通構造函數。對於一個類A,如果不編寫上述四個函數,c++編譯器將自動為A產生四個默認的函數,即:
既然能自動生成函數,為什麼還需要自定義?原因之一是“默認的復制構造函數”和"默認的賦值函數“均采用”位拷貝“而非”值拷貝“
位拷貝 v.s. 值拷貝
為便於說明,以自定義String類為例,先定義類,而不去實現
#include <iostream>
using namespace std;
class String
{
public:
String(void);
String(const String &other);
~String(void);
String & operator =(const String &other);
private:
char *m_data;
int val;
};
位拷貝拷貝的是地址,而值拷貝拷貝的是內容。
如果定義兩個String對象a, b。當利用位拷貝時,a=b,其中的a.val=b.val;但是a.m_data=b.m_data就錯了:a.m_data和b.m_data指向同一個區域。這樣出現問題:
因此
當類中還有指針變量時,復制構造函數和賦值函數就隱含了錯誤。此時需要自己定義。
結論
注意
因此此時如果寫String s是錯誤的,因為定義了其他構造函數,就不會自動生成無參默認構造函數。
復制構造函數 v.s. 賦值函數
#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:
String(const char *str);
String(const String &other);
String & operator=(const String &other);
~String(void);
private:
char *m_data;
};
String::String(const char *str)
{
cout << "自定義構造函數" << endl;
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(const String &other)
{
cout << "自定義拷貝構造函數" << endl;
int length = strlen(other.m_data);
m_data = new char[length + 1];
strcpy(m_data, other.m_data);
}
String & String::operator=(const String &other)
{
cout << "自定義賦值函數" << endl;
if (this == &other) // 在賦值函數中一定要記得比較
{
return *this;
}
else
{
delete [] m_data;
int length = strlen(other.m_data);
m_data = new char[length + 1];
strcpy(m_data, other.m_data);
return *this;
}
}
String::~String(void)
{
cout << "自定義析構函數" << endl;
delete [] m_data;
}
int main()
{
cout << "a(\"abc\")" << endl;
String a("abc");
cout << "b(\"cde\")" << endl;
String b("cde");
cout << " d = a" << endl;
String d = a;
cout << "c(b)" << endl;
String c(b);
cout << "c = a" << endl;
c = a;
cout << endl;
執行結果
說明幾點
1. 賦值函數中,上來比較 this == &other 是很必要的,因為防止自復制,這是很危險的,因為下面有delete []m_data,如果提前把m_data給釋放了,指針已成野指針,再賦值就錯了
2. 賦值函數中,接著要釋放掉m_data,否則就沒機會了(下邊又有新指向了)
3. 拷貝構造函數是對象被創建時調用,賦值函數只能被已經存在了的對象調用
注意:String a("hello"); String b("world"); 調用自定義構造函數
String c=a;調用拷貝構造函數,因為c一開始不存在,最好寫成String c(a);