文章目錄
- 一. 建構式
- 二. 解構式
- 三. 拷貝建構式
- 1.淺拷貝
- 2.深拷貝
- 四. 賦值函式
在C++中,對于一個類,C++的編譯器都會為這個類提供四個默認函式,分別是:
A() //默認建構式
~A() //默認解構式
A(const A&) //默認拷貝建構式
A& operator = (const A &) //默認賦值函式,
這四個函式如果我們不自行定義,將由編譯器自動生成這四個預設的函式,下面讓我們來看看這四個函式(重點是后兩個),
一. 建構式
建構式是一種特殊的成員函式,與其他成員函式不同,不需要用戶來呼叫它,而是在建立物件時自動執行,建構式的功能是由用戶定義的,用戶根據初始化的要求設計函式體和函式引數,可以是一個,也可以是多個,可以把建構式理解為多載的一種(函式名相同,不會回傳任何型別,也不可以是void型別,引數型別個數可不同),
class Animal
{
private:
string name;
public:
Animal();//默認建構式
Animal(string n);//也可以自定義建構式
};
Animal::Animal()
{
//什么都不做
}
Animal::Animal(string n)
{
this->name = n;
}
int main()
{
//第一種實體化物件的方法
Animal * a = new Animal(); //將呼叫默認建構式
Animal * b = new Animal("花狗"); //將呼叫自定義的建構式,對name變數初始化,
//第二種實體化物件的方法
Animal c; //將呼叫默認建構式
//注意:對于無參建構式,不可以使用Animal c(),
Animal c("花狗");//將呼叫自定義建構式,對name變數初始化,
return 0;
}
建構式的作用就是對當前類物件起到一個初始化的作用,類物件不像我們基本型別那樣,在很多時候都需要初始化一些成員變數,
可以看到建構式被宣告在public里面,那么可以宣告在private里面嗎?是可以的,只不過不能被外部實體化了,在設計模式中有一種單例模式,就是這樣設計的,有興趣的可以了解一下,
二. 解構式
與建構式相對立的是解構式,這個函式在物件銷毀之前自動呼叫,例如在建構式中,我們為成員變數申請了記憶體,我們就可以在解構式中將申請的記憶體釋放,解構式的寫法是在建構式的基礎上加一個~符號,并且只能有一個解構式,
class Animal
{
private:
string name;
public:
Animal();//默認建構式
~Animal(); //默認解構式
};
三. 拷貝建構式
1.淺拷貝
class Animal
{
private:
string name;
public:
Animal()
{
name = "花狗";
cout << "Animal" << endl;
}
~Animal()
{
cout << "~Animal:" << (int)&name << endl;
}
};
int main()
{
Animal a;
Animal b(a);
return 0;
}
運行結果:

這個例子呼叫的是默認的拷貝建構式(注意看控制臺顯示,呼叫了一次建構式和兩次解構式),可以看出兩個物件的成員變數地址是不一樣的,當成員變數不存在指標型別是,這樣做沒什么問題,當類中有指標變數,自動生成的拷貝函式注定會出錯,往下看,
2.深拷貝
我們將成員變數換成指標變數,繼續實驗,
class Animal
{
private:
//string name;
string * name;
public:
Animal()
{
name = new string("花狗");
cout << "Animal" << endl;
}
~Animal()
{
cout << "~Animal:" << (int)name << endl;
}
};
int main()
{
Animal a;
Animal b(a);
return 0;
}
運行結果:

可以看到兩個物件的指標成員所指的記憶體相同(記憶體里面存著字串:花狗),還記得解構式的作用嗎,在物件銷毀之前自動呼叫,在建構式中,我們為成員變數申請了記憶體,我們就可以在解構式中將申請的記憶體釋放,
現在在解構式中加上對name釋放的代碼:
~Animal()
{
cout << "~Animal:" << (int)name << endl;
delete name;
name = NULL;
}
再運行發現程式崩潰了,呼叫一次建構式,呼叫兩次解構式,兩個物件的指標成員所指記憶體相同,name指標被分配一次記憶體,但是程式結束時該記憶體卻被釋放了兩次,導致程式崩潰

而且發現當重復釋放的兩個指標分別屬于兩個類或者說是兩個變數的時候,會發生崩潰,如果對一個變數多次釋放則不會崩潰,
例如下面的代碼將不會發生奔潰
string * a = new string("花狗");
delete a;
a = NULL;
cout << "第一次完成\n";
delete a;
a = NULL;
cout << "第二次完成\n";
現在我們已經知道對于指標進行淺拷貝會出現的奔潰的問題,那么通過自定義拷貝建構式來解決淺拷貝的問題,
Animal(const Animal & a)
{
//name = s.name;
name = new string(*a.name);
}
之后運行程式不會崩潰,總結起來就是先開辟出和源物件一樣大的記憶體區域,然后將需要拷貝的資料復制到目標拷貝物件,
四. 賦值函式
四個默認函式,當賦值函式最為復雜,
Animal& operator=(const Animal&obj)
{
if(this !=&obj)
{
data=obj.data;
}
return *this;
}
這是它的原型,類似 Animal a(b); Animal a = b; 這樣的寫法會呼叫拷貝建構式,
而賦值函式是在當年物件已經創建之后,對該物件進行賦值的時候呼叫的,Animal a; a = b,
和拷貝建構式一樣,若類中有指標變數,自動生成的賦值函式注定會出錯,老樣子,先申請記憶體,再復制值即可完美解決,
Animal& operator=(const Animal&obj)
{
if(this !=&obj)
{
//默認是 name = obj.name;
name = new string(*obj.name);
}
return *this;
}
還有一個知識點就是運算子多載這一塊,一個自定義型別的物件,如果想要進行預期的加減乘除之類的運算,或者是像內置型別一樣,用cout輸出一個類物件,這些都是需要我們來用代碼告訴機器怎么做,都是需要我們來指定的,
還是拿這個類舉例子,例如運算子+多載
class Animal
{
private:
string * name;
int age;
int num;
public:
Animal()
{
name = new string("花狗");
age = 5;
num = 4;
}
Animal& operator+(const Animal&obj)
{
if(this !=&obj)
{
string * s = name;
name = new string(*name + *obj.name);
delete s;
s == NULL;
this->age+=obj.age;
this->num+=obj.num;
}
return *this;
}
};
int main()
{
Animal a;
Animal b;
a = a+b;
//這樣物件a里面的age成員的值是5,num成員的值是8,而*name的值將是"花狗花狗";
return 0;
{
cout輸出的定義,主要注意的是要用到友元函式,
class Animal
{
//中間代碼略
friend ostream& operator << (ostream& os, Animal& a)
{
os << *a.name << ":" << a.age << ":" << a.num;
return os;
}
};
運行結果:

微信官方交流群:

CSDN認證博客專家
Qt
C
C++
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/262964.html
標籤:其他
