目錄
對象的初始化和清理也是兩個非常重要的安全問題,一個對象或者變量沒有初始時,對其使用後果是未知,同樣的使用完一個變量,沒有及時清理,也會造成一定的安全問題。c++爲了給我們提供這種問題的解決方案,構造函數和析構函數,這兩個函數將會被編譯器自動調用,完成對象初始化和對象清理工作。
一、構造函數和析構函數
構造函數主要作用在於創建對象時爲對象的成員屬性賦值,構造函數由編譯器自動調用,無須手動調用。
析構函數主要用於對象銷燬前系統自動調用,執行一些清理工作。
構造函數語法:
|
析構函數語法:
|
二、 構造函數的分類及調用
- 按參數類型:無參構造函數、有參構造函數
- 按類型分類:普通構造函數、拷貝構造函數(複製構造函數)
例子:創立一個Person類
1、構造函數的分類
class Person
{
public:
Person()
{
cout << "Person的默認構造函數調用" << endl;
}
Person(int a)
{
cout << "Person的有參構造函數調用" << endl;
}
//拷貝構造 //值傳遞的本質 就是調用 拷貝構造函數
Person(const Person &p)
{
cout << "Person的拷貝構造函數調用" << endl;
m_Age = p.m_Age;
}
~Person()
{
cout << "Person的析構函數調用" << endl;
}
int m_Age;
};
2、構造函數的調用
void test02()
{
//1 括號法
Person p1(10); //有參構造函數調用
p1.m_Age = 18;
Person p2(p1); //利用括號法 調用拷貝構造函數
cout << "p2的年齡爲: " << p2.m_Age << endl;
//注意事項1 : 不要利用括號法 調用默認構造函數 Person p(); 原因將代碼看成 函數的聲明,不會認爲是在創建對象
Person p();
void func();
//2 顯示法
Person p3 = Person(10); //有參構造調用
//顯示法 調用 拷貝構造函數
Person p4 = Person(p3);
Person(10); //單獨寫 Person(10); 稱爲 匿名對象 特點:當本行執行完畢,立即釋放
cout << "aaaaaaaaa" << endl;
//注意事項2 : 不要利用拷貝構造函數 初始化匿名對象
Person(p4); // 當寫成Person(p4); 編譯器會認爲寫了 Person p4 如果已經有p4就是重定義 匿名對象放到右值沒問題
//3 隱式轉換法 可讀性低
Person p5 = 10; //編譯器隱式將代碼轉爲 Person p5 = Person(10);
//利用隱式轉換法 調用拷貝構造函數
Person p6 = p5; // 隱式轉爲 Person p6 = Person(p5);
}
b爲A的實例化對象,A a = A(b) 和 A(b)的區別? 當A(b) 有變量來接的時候,那麼編譯器認爲是一個匿名對象,當沒有變量來接的時候,編譯器認爲A(b) 等價於 A b. |
三、拷貝構造函數的調用時機
值傳遞的本質:就是調用 拷貝構造函數
3種情況可能用到:
- 對象以值傳遞的方式傳給函數參數
- 函數局部對象以值傳遞的方式從函數返回(vs debug模式下調用一次拷貝構造,qt不調用任何構造)
- 用一個對象初始化另一個對象
用代碼解釋上面3中情況:
class Person
{
public:
Person()
{
cout << "Person的默認構造函數調用" << endl;
}
Person(int a)
{
cout << "Person的有參構造函數調用" << endl;
}
//拷貝構造 //值傳遞的本質 就是調用 拷貝構造函數
Person(const Person &p)
{
cout << "Person的拷貝構造函數調用" << endl;
m_Age = p.m_Age;
}
~Person()
{
cout << "Person的析構函數調用" << endl;
}
int m_Age;
};
//1、用已經創建好的對象 初始化新的對象
void test01()
{
Person p1;
p1.m_Age = 10;
Person p2(p1);//拷貝構造函數調用
cout << "p2的年齡爲: " << p2.m_Age << endl;
}
//2、值傳遞的方式 給函數參數傳值
void doWork( Person p)
{
}
void test02()
{
Person p;
doWork(p);
}
//3、以值的方式返回局部對象
Person doWork2()
{
Person p;
return p;
}
void test03()
{
Person p = doWork2();
}
四、構造函數調用規則
- 默認情況下,c++編譯器至少爲我們寫的類增加3個函數:
- 默認構造函數(無參,函數體爲空)
- 默認析構函數(無參,函數體爲空)
- 默認拷貝構造函數,對類中非靜態成員屬性簡單值拷貝
- 如果用戶定義拷貝構造函數,c++不會再提供任何默認構造函數
- 如果用戶定義了普通構造(非拷貝),c++不在提供默認無參構造,但是會提供默認拷貝構造
五、深拷貝和淺拷貝
當類中有指針,並且此指針有動態分配空間,析構函數做了釋放處理,往往需要自定義拷貝構造函數,自行給指針動態分配空間,深拷貝。
如果是深拷貝, 堆區的數據可能會被釋放多次。
拷貝構造函數例子:
class Person{
public:
Person(char* name,int age){
pName = (char*)malloc(strlen(name) + 1);
strcpy(pName,name);
mAge = age;
}
//增加拷貝構造函數
Person(const Person& person){
pName = (char*)malloc(strlen(person.pName) + 1);
strcpy(pName, person.pName);
mAge = person.mAge;
}
~Person(){
if (pName != NULL){
free(pName);
}
}
private:
char* pName;
int mAge;
};
void test(){
Person p1("Edward",30);
//用對象p1初始化對象p2,調用c++提供的默認拷貝構造函數
Person p2 = p1;
}
六、初始化列表
注意:初始化成員列表(參數列表)只能在構造函數使用。
例子:
class Person{
public:
#if 0
//傳統方式初始化
Person(int a,int b,int c){
mA = a;
mB = b;
mC = c;
}
#endif
//初始化列表方式初始化
Person(int a, int b, int c):mA(a),mB(b),mC(c){}
void PrintPerson(){
cout << "mA:" << mA << endl;
cout << "mB:" << mB << endl;
cout << "mC:" << mC << endl;
}
private:
int mA;
int mB;
int mC;
};
七、explicit關鍵字
[explicit注意]
|
class MyString
{
public:
explicit MyString(int len)
{
cout << "MyString有參構造函數(int )調用" << endl;
}
MyString(char * str)
{
cout << "MyString有參構造函數(char * )調用" << endl;
}
};
void test01()
{
MyString str = "abcde";
MyString str2("abcde");
MyString str3 = MyString("abcde");
//MyString str4 = 10; // 有人會認爲是 字符串是 "10" 也有人會認爲字符串長度爲10
//爲了防止這種寫法,可以用關鍵字 explicit
MyString str5(10);
MyString str6 = MyString(10);