默認成員函數
在繼承關係裏面, 在派生類中如果沒有顯示定義下列六個成員函數, 編譯系統則會默認合成這六個默認的成員函數。
上圖:
這篇文章會詳細介紹構造函數,析構函數,拷貝構造函數和賦值運算符的重載。
因爲另外兩個在我們這個階段基本上不用到,所以只要大概知道有這麼個東西就行(說白了就是我也不會。。。)
下面我們先講:
構造函數和析構函數
構造函數和析構函數概念的東西就不用過多解釋了吧,應該都懂。。直接上代碼
代碼塊:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdio.h>
using namespace std;
class base
{
public:
base()
{
cout << "base()" << endl;
}
~base()
{
cout << "~base()" << endl;
}
int pub;
protected:
int pro;
private:
int pri;
};
class Derive:public base
{
public:
Derive()
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
int _pub;
protected:
int _pro;
private:
int _pri;
};
void Test()//析構函數只在函數體結束時候調用,所以在main函數裏聲明不好看到析構函數
{
Derive d;
}
int main()
{
Test();
system("pause");
return 0;
}
運行結果:
分析:我們創建了一個派生類Derived對象d,從運行結果可以看出,創建對象過程是:先調用基類的構造函數,再調用子類的構造函數;而析構對象時,是先調用子類的析構函數,再調用基類的析構函數。
But...
實際上是,先調用子類的構造函數,子類又繼承了父類,所以父類的構造函數是在子類的初始化列表裏面被調用的。
跳到轉匯編:
截圖一:
截圖二:
截圖三:
進行彙編單步調試之後,圖一:光標先移動到子類構造函數中去,在構造函數還沒進入cout("derive()"),圖二:先call了"00C53765 call base::base (0C514BFh)", 圖三:然後跳轉進 base的構造函數中去
所以說base()這一動作是在子類析構函數的初始化列表上完成的。
總結:
1.因爲是在棧上創建的對象,所以符合棧後進先出的特性。
2.多繼承同理,只是兩個父類對象,按照先聲明的先構造。
拷貝構造函數:(單參)
注意區分:構造函數是創建新的對象並初始化。
拷貝構造函數是利用已有對象來初始化這個新對象。
調用拷貝構造函數的三種情況:
1.使用一個已有對象來初始化這個準備創建的對象。
2.函數(形)參數是類的對象,調用函數生成類的臨時對象。
3.函數返回值是對象的時候,函數執行完返回時初始化一個無名對象。
參數:類 類型對象的引用 const base& 爲什麼是引用呢。?
原因是如果傳值的話,這個是默認的成員函數,所以每次創建一個臨時對象的時候就會調用這個函數,然後又要創建新的 臨時對象,所以咯,陷入了死循環。。
代碼塊:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdio.h>
using namespace std;
class base
{
public:
base(int a=1,int b=2,int c=3)//構造函數
: pub(a)
, pro(b)
, pri(c)
{
cout << "base()" << endl;
}
base(base& b1)//拷貝構造函數
{
pub = b1.pub;
pro = b1.pro;
pri = b1.pri;
cout << "copy constructor"<<endl;
}
~base()//析構函數
{
cout << "~base()" << endl;
}
void dis()
{
cout << "dis()" << endl;
cout << " pub=" << pub;
cout << " pro=" << pro;
cout << " pri=" << pri<<endl;;
}
//對象做參數,返回值------結合截圖分析效果會更佳哦
base func(base b3)//第三個拷貝構造出一個臨時變量
{
int x;
int y;
int z;
cout << "func()" << endl;//輸出
x = b3.pub + 10;
y = b3.pro + 10;
z = b3.pri + 10;
base b4(x, y, z);//構造函數
return b4;//函數執行完返回一個base類對象,第四個拷貝構造初始化一個無名對象
}//函數體結束,依次析構在函數體裏創建的三個對象
int pub;
protected:
int pro;
private:
int pri;
};
int main()
{
base b;//構造函數
b.dis();//函數調用
base p(b);//第一個拷貝構造函數
base p1 = b;//第二個拷貝構造函數
p1 = p.func(p);//進來說
p1.dis();
system("pause");
return 0;
}
運行結果:
萬一程序沒有顯式定義拷貝構造函數,編譯器將會自動生成一個。
如果想在派生類中構造基類對象,那麼不僅僅可以用構造函數,也可以用拷貝構造函數;
最後一個主題:
賦值運算符重載
類似於拷貝構造函數,拷貝構造函數使用一個已有對象來初始化這個準備創建的對象。(有新對象生成)
賦值運算符的重載是對一個已存在的對象進行拷貝賦值。(沒有新對象生成)
代碼塊:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdio.h>
using namespace std;
class base
{
public:
base(int a=1,int b=2,int c=3)
: pub(a)
, pro(b)
, pri(c)
{
cout << "base()" << endl<<endl;
}
/*base(base& b1)//拷貝構造函數已被註釋
{
pub = b1.pub;
pro = b1.pro;
pri = b1.pri;
cout << "copy constructor"<<endl;
}*/
base& operator= (const base& b1)//賦值運算符重載函數,時刻記得類的成員函數裏面第一個參數總是this指針哦
{
if (this != &b1)//先確認不是自己給自己賦值
{
cout << "base& operator=(const base& b1)" << endl<<endl;
pub = b1.pub;
pro = b1.pro;
pri = b1.pri;
}
return *this;//因爲this是指針,所以要解引用
}
~base()
{
cout << "~base()" << endl;
}
void dis()
{
cout << "dis()" << endl;
cout << " pub=" << pub;
cout << " pro=" << pro;
cout << " pri=" << pri << endl<<endl;;
}
int pub;
protected:
int pro;
private:
int pri;
};
int main()
{
base b(10,20,30);//構造函數,給出參數的話,則使用參數(10,20,30)
b.dis();//函數調用
base p;//構造函數,沒有給出參數,則使用默認缺省參數(1,2,3)
p.dis();//函數調用
p = b;//賦值運算符重載
p.dis();//重載後再次函數調用
system("pause");
return 0;
}
運行結果:
考慮一下..要是子類給父類進行運算符重載呢。?
代碼塊:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdio.h>
using namespace std;
class base//父類
{
public:
base(int a=1,int b=2,int c=3)//初始化列表------全缺省
: pub(a)
, pro(b)
, pri(c)
{
cout << "base()" << endl<<endl;
}
/*base(base& b1)//已被註釋的拷貝構造函數
{
pub = b1.pub;
pro = b1.pro;
pri = b1.pri;
cout << "copy constructor"<<endl;
}*/
base& operator= (const base& b1)//正在使用的賦值運算符重載函數
{
if (this != &b1)
{
cout << "base& operator=(const base& b1)" << endl<<endl;
pub = b1.pub;
pro = b1.pro;
pri = b1.pri;
}
return *this;
}
~base()
{
cout << "~base()" << endl;
}
void dis()
{
cout << "dis()" << endl;
cout << " pub=" << pub;
cout << " pro=" << pro;
cout << " pri=" << pri << endl<<endl;;
}
int pub;
protected:
int pro;
private:
int pri;
};
class Derive:public base//子類,公有繼承
{
public:
Derive(int a=4,int b=5,int c=6)//同上,初始化列表全缺省
: _pub(a)
, _pro(b)
, _pri(c)
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void dis()
{
cout << "dis()" << endl;
cout << " pub=" << _pub;
cout << " pro=" << _pro;
cout << " pri=" << _pri << endl << endl;;
}
int _pub;
protected:
int _pro;
private:
int _pri;
};
int main()
{
base b(10,20,30);//構造函數
b.dis();//函數調用
Derive d;//構造函數,現在初始化列表中構造父類,再構造子類
cout << endl << "***************************記住這個d.pub=" << d.pub << "*********************"<< endl << endl;
d.dis();//函數調用
b = d;//賦值運算符重載,這裏面進行了一個切片動作,也記住這個切片
b.operator=(d);//與上一行等價
//d.operator=((Derive*)b);//父類不能給子類賦值·error:不存在從base到Derive*的適當轉換函數
//也就是說如果派生類包含了轉換構造函數,即對基類對象轉換爲派生類對象進行了定義,則可以將基類對象賦給派生對象。
//d = (Derive*)b;與上一行等價
b.dis();//重載後再次函數調用
system("pause");
return 0;
}
運行結果:
大家可能會對這個 最後重載之後再次函數調用 b.dis();這塊有所疑問,爲什麼全局都沒有輸出1,2,3 最後卻來個這。?
分析:
首先看賦值運算符重載函數 base& operator= (const base& b1)
注意這裏其實是有兩個參數:隱含的this指針(10,20,30)和b1(1,2,3)
注意這裏的形參b1是base類的,而實參d則是Derive類的,說明發生了切片。
所以原先這個d裏面包含的是他本身的成員(4,5,6)和父類的(1,2,3)
切記不是父類對象的(10,20,30),在那裏我特意輸出pub的值就是讓你知道,這裏是父類的(1,2,3)....
進行切片之後呢就把除父類之外的子類部分全部切掉,只保留下來父類的成員(1,2,3)...
所以咯,這裏就輸出了(1,2,3)而不是容易誤導我們的(10,20,30)
好了,上面大概就是我對這四個默認成員函數的一些瞭解,哪有不對就指出來哦。。