構造函數、析構函數與賦值函數是每個類最基本的函數。它們太普通以致讓人容易麻痺大意,其實這些貌似簡單的函數就象沒有頂蓋的下水道那樣危險。
每個類只有一個析構函數和一個賦值函數,但可以有多個構造函數(包含一個拷貝構造函數,其它的稱為普通構造函數)。
對於任意一個類A,如果不想編寫上述函數,C++編譯器將自動為A 產生四個缺省的函數,例如:
A(void); // 缺省的無參數構造函數
A(const A &a); // 缺省的拷貝構造函數
~A(void); // 缺省的析構函數
A & operate =(const A &a); // 缺省的賦值函數
這不禁讓人疑惑,既然能自動生成函數,為什麼還要程序員編寫?原因如下:
<1>如果使用“缺省的無參數構造函數”和“缺省的析構函數”,等於放棄了自主“初始化”和“清除”的機會,C++發明人Stroustrup 的好心好意白費了。
<2>“缺省的拷貝構造函數”和“缺省的賦值函數”均采用“位拷貝”而非“值拷貝”的方式來實現,倘若類中含有指針變量,這兩個函數注定將出錯。
C++ 默認構造函數 :
1、每個類必須有一個構造函數,否則沒法創建對象;
2、若 程序員沒有提供任何構造函數,則C++提供一個默認的構造函數,該默認構造函數是無參構造函數,它僅負責創建對象,不做任何初始化的工作;
3、只要programer 定義了一個構造函數(不管是無參還是有參構造),C++就不再提供默認的默認構造函數。即如果為類定義了一個帶參的構造函數,還想要無參構造函數,就必須自己定義;
4、與變量定義類似,在用默認構造函數創建對象時,如果創建的是全局對象或靜態對象,則對象的位模式全為0,否則,對象值是隨機的。
C++默認拷貝構造函數:
1、默認的拷貝構造函數執行的順序與其他用戶定義的構造函數相同,執行先父類後子類的構造.
2、拷貝構造函數對類中每一個數據成員執行成員拷貝(memberwise Copy)的動作.
3、如果數據成員為某一個類的實例,那麼調用此類的拷貝構造函數.
4、如果數據成員是一個數組,對數組的每一個執行按位拷貝.
5、如果數據成員是一個數量,如int,double,那麼調用系統內建的賦值運算符對其進行賦值.
請看下面代碼:
//============================================================================
// Name : main.cpp
// Author : ShiGuang
// Version :
// Copyright : [email protected]
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student()
{
cout << "構造函數1" << endl;
}
Student(int k)
{
cout << "構造函數2" << endl;
i = k;
}
Student(Student const &m)
{
cout << "拷貝構造函數" << endl;
i = m.i * (-1);
}
void p()
{
cout << i << endl;
}
~Student()
{
cout << "析構函數" << endl;
}
protected:
int i;
};
int main(int argc, char **argv)
{
Student s(9818);
// 調用構造函數2
s.p();
Student t(s);
// 調用拷貝構造函數
t.p();
Student k = s;
// 調用拷貝構造函數
k.p();
Student *p = new Student(s);
// 調用拷貝構造函數
p->p();
Student m;
// 調用構造函數1
m = s;// 賦值運算
m.p();
return 0;
}
運行結果:
構造函數2
9818
拷貝構造函數
-9818
拷貝構造函數
-9818
拷貝構造函數
-9818
構造函數1
9818
析構函數
析構函數
析構函數
析構函數
下面我們來討論一下關於淺拷貝和深拷貝的問題。
深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生復制過程的時候(復制指針所指向的值),這個過程就可以叫做深拷貝,反之對象存在資源但復制過程並未復制資源(只復制了指針所指的地址)的情況視為淺拷貝。
很多人會問到,既然系統會自動提供一個默認的拷貝構造函數來處理復制,那麼我們沒有必要去自定義拷貝構造函數呀,對,就普通情況而言這的確是沒有必要的,但在某些狀況下,類體內的成員是需要開辟動態堆內存的,如果我們不自定義拷貝構造函數而讓系統自己處理,那麼就會導致堆內存的所屬權產生混亂。試想一下,已經開辟的一端堆地址原來是屬於對象a的,由於復制過程發生,b對象取得是a已經開辟的堆地址,一旦程序產生析構,釋放堆的時候,計算機不清楚這段地址是真正屬於誰的,當連續發生兩次析構的時候就出現了運行錯誤。
為了更詳細的說明問題,請看如下的代碼。
//============================================================================
// Name : main.cpp
// Author : ShiGuang
// Version :
// Copyright : [email protected]
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <string>
using namespace std;
class aa
{
public:
aa()
{
cout << "調用構造函數" << endl;
f = new char[10];
}
~aa()
{
cout << "調用析構函數" << endl;
delete[] f;
}
char * f;
};
int main(int argc, char **argv)
{
aa p;
printf("p.f=%p\n",p.f);
strcpy(p.f, "Computer");
cout << p.f << endl;
aa q(p);// 調用默認的拷貝構造函數
printf("q.f=%p\n",q.f);
cout << q.f << endl;
strcpy(p.f, "Software");
cout << p.f << endl;
cout << q.f << endl;
strcpy(q.f, "Software");
cout << p.f << endl;
cout << q.f << endl;
return 0;
}
運行結果:
調用構造函數
p.f=003F1048
Computer
q.f=003F1048
Computer
Software
Software
Software
Software
調用析構函數
調用析構函數
通過上面的例子,我們能清楚的看到,第二個對象調用拷貝構造函數,q.f獲得的地址值與p.f相同,即指向了同一片內存區域。程序結束時,會兩次調用析構函數,即同一個指針執行兩遍delete操作,會發生什麼呢?這可能是一場災難,可能會破壞該堆及自由內存表。
那我們該如何避免呢?這裡我們就需要使用深拷貝。
//============================================================================
// Name : main.cpp
// Author : ShiGuang
// Version :
// Copyright : [email protected]
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <string>
using namespace std;
class aa
{
public:
aa()
{
cout << "調用構造函數" << endl;
f = new char[10];
}
aa(aa const & s)
{
cout << "調用拷貝構造函數" << endl;
f = new char[10];
strcpy(f, s.f);
}
~aa()
{
cout << "調用析構函數" << endl;
delete[] f;
}
char * f;
};
int main(int argc, char **argv)
{
aa p;
printf("p.f=%p\n", p.f);
strcpy(p.f, "Computer");
cout << p.f << endl;
aa q(p);// 調用用戶的拷貝構造函數
printf("q.f=%p\n", q.f);
cout << q.f << endl;
strcpy(p.f, "Software");
cout << p.f << endl;
cout << q.f << endl;
strcpy(q.f, "Software");
cout << p.f << endl;
cout << q.f << endl;
return 0;
}
運行結果:
調用構造函數
p.f=00351048
Computer
調用拷貝構造函數
q.f=00351060
Computer
Software
Computer
Software
Software
調用析構函數
調用析構函數
現在我們可以看到,p.f和q.f分別指向不同的內存區域。
作者 sg131971的學習筆記