23.C++- 繼承的多種方式、顯示調用父類構造函數、父子之間的同名函數、virtual虛函數


 上章鏈接: 22.C++- 繼承與組合,protected訪問級別 


 

繼承方式

繼承方式位於定義子類的”:”后面,比如:

class Line : public Object             //繼承方式是public
{

};

繼承方式默認為private

在C++中,繼承方式共有3種:

public繼承

-指父類的成員(變量和函數)訪問級別,在子類中保持不變

private繼承

-指父類的成員,在子類中變為private私有成員.

-也就是說子類無法訪問父類的所有成員

protected繼承

-指父類的public成員 ,在子類中變為protected保護成員,其它成員級別保持不變

如下圖所示:

 

 

注意: protected繼承只針對子類有效

比如當父類是protected繼承時,則子類的子類就無法訪問父類的所有成員

一般而言,C++項目只用到public繼承

 

顯示調用父類構造函數

  • 當我們創建子類對象時,編譯器會默認調用父類無參構造函數
  • 若有子類對象,也會默認調用子類對象的無參構造函數。

比如以下代碼:

class  StrA
{
public:
          StrA()
          {
             cout<<"StrA()"<<endl;
          }
          StrA(string s)
          {
             cout<<"StrA(string s):"<<s<<endl;
          } 
};

class  StrB : public StrA
{
public:
          StrB(string s)
          {
             cout<<"StrB(int i):"<<s<<endl;
          }
};

int main()
{
       StrB b("123");
       return 0;
}

 編譯運行:

StrA()                    //父類無參構造函數
StrB(int i):123

 

也可以通過子類構造函數的初始化列表來顯示調用

接下來,修改上面子類的StrB(string s)函數,通過初始化列表調用StrA(string s)父類構造函數

改為:

StrB(string s): StrA(s)
{
  cout<<"StrB(int i):"<<s<<endl;
}

運行打印:

StrA(string s):123
StrB(int i):123

 

 

父子間的同名成員和同名函數

  • 子類可以定義父類中的同名成員和同名函數
  • 子類中的成員變量和函數將會隱藏父類的同名成員變量和函數
  • 父類中的同名成員變量和函數依然存在子類中
  • 通過作用域分辨符(::)才可以訪問父類中的同名成員變量和函數

比如:

class Parent{

public:
       int mval;
       Parent()
       {
              mval=1000;
       }
void add(int i) { mval+=i; } }; class Child : public Parent { public: int mval; Child() { mval=100; } void add(int i,int j) { mval+=i+j; } };

 

在main()函數執行:

       Child c;

       //c. add(10);        //該行會報錯,由於子類有add函數,所以編譯器會默認在子類里尋找add(int i);

       c.Parent::add(10);   //該行正確,執行父類的成員函數

       c.add(2,3);

       cout<<"Child.mval="<<c.mval<<endl;

       cout<<"Parent.mval="<<c.Parent::mval<<endl;

 

打印:

Child.mval=105
Parent.mval=1010

從打印結果看到,父類和子類之間的作用域是不同的, 所以執行父類的同名成員變量和函數需要作用域分辨符(::)才行

 

父子間的兼容

以上示例的Parent父類Child子類為例

  • 子類對象可以直接賦值給父類對象使用,比如: Parent p; Child c;   p=c;
  • 子類對象可以初始化父類對象,比如: Parent p1(c);
  • 父類引用可以直接引用子類對象,比如: Parent& p2 =c;    //p2是c對象的別名
  • 父類指針可以直接指向子類對象,比如: Parent* p3=&c;

其實是編譯器是將子類對象退化為了父類對象, 從而能通過子類來賦值初始化父類

所以上述的父類對象(包括指針/引用)也只能訪問父類中定義的成員.

 

如果父類對象想訪問子類的成員,只能通過強制轉換,將父類對象轉為子類類型

示例1,通過C方式轉換:

Child c;
Parent* p3=&c;
Child *c2 = (Child*)p3;         

 

示例2,通過static_cast轉換:

Child c;
Parent* p3=&c;
Child *c2 = (static_cast*)<Child*>(p3);

 

 

虛函數

實現多態性,通過指向子類的父類指針或引用,可以訪問子類中同名覆蓋成員函數

首先參考下面,沒有虛函數的示例:

class Parent
{
    int i; 
public:  
         void example()
        {
            cout<<"class Parent"<<endl;
        }

}; 
 
class Child : public Parent  
{
    int j; 
public:
        void example()
        {
            cout<<"class Child"<<endl;
        }     
};


void print(Parent* p)
{
     p->example();
}
int main()
{
    Parent t; 
    Child c;
    
    print(&t);
    print(&c);     
    
    cout<<"SIZEOF Parent:"<<sizeof(t)<<endl;
    cout<<"SIZEOF Child:"<<sizeof(c)<<endl; 
}

運行打印:

class Parent
class Parent
SIZEOF Parent:4
SIZEOF Child:8

從結果看出,即使example函數指針p指向了Child c,也只能調用父類的example(),無法實現多態性.

 

所以C++引入了虛函數概念,根據指針指向的對象類型,執行不同類的同名覆蓋成員函數,實現不同的形態

定義: 在父類成員函數的返回值前面,通過virtual關鍵字聲明,這樣便能訪問子類中的同名成員函數了

接下來將上個示例的父類成員函數example()改寫為虛函數:

virtual void print()        //將父類的成員函數定為虛函數
{
cout<<"class Parent"<<endl;
}        

 

運行打印:

class Parent
class Child
SIZEOF Parent:8
SIZEOF Child:12

可以發現,父類和子類的長度都增加了4字節,這4個字節就是用來指向“虛函數表”的指針,編譯器便會更據這個指針來執行不同類的虛函數,實現多態性.

 

虛析構函數

-在使用基類指針指向派生類對象時用到

-通過基類析構函數可以刪除派生類對象 

示例

#include <iostream>

using namespace std;

class Base
{
public:
     Base()
    {
        cout << "Base()" << endl;
    }

     virtual ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
public:
     Derived()
    {
        cout << "Derived()" << endl;
    }

     ~Derived()
    {
        cout << "~Derived()" << endl;
    }
};

int main()
{
    Base* p = new Derived(); 
    // ...
    delete p;

    return 0;
}

運行打印:

Base()
Derived()
~Derived()
~Base()

可以發現,由於基類的析構函數是虛函數,所以我們delete基類指針時,派生類也跟着調用了析構函數,從而避免了內存泄漏,也能滿足使用dynamic_cast強制轉換

一般而言,虛構造函數只有在繼承下才會被使用,單個類是不會使用虛構函數的,因為虛函數表會產生額外的空間

注意:構造函數不能成為虛函數,因為虛函數表是在構造函數執行后才會進行初始化


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM