C++繼承關係之子類的六個成員函數

默認成員函數
在繼承關係裏面, 在派生類中如果沒有顯示定義下列六個成員函數, 編譯系統則會默認合成這六個默認的成員函數。

上圖:

這篇文章會詳細介紹構造函數,析構函數,拷貝構造函數和賦值運算符的重載。
因爲另外兩個在我們這個階段基本上不用到,所以只要大概知道有這麼個東西就行(說白了就是我也不會。。。)
下面我們先講:

構造函數和析構函數

構造函數和析構函數概念的東西就不用過多解釋了吧,應該都懂。。直接上代碼

代碼塊:

#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)

 

好了,上面大概就是我對這四個默認成員函數的一些瞭解,哪有不對就指出來哦。。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章