C++中繼承與多態(tài)

父子間的同名沖突

首先來看一段代碼:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;
};

class Child : public Parent
{
public:
    int mi;
};

int main()
{
    Child c;
    
    //這里的mi是Parent中的還是Child中的呢?
    c.mi = 100;
    
    return 0;
}

編譯通過,說明子類可以定義和父類相同的同名成員。

  • 子類可以定義父類中的同名成員
  • 子類中的成員將隱藏父類中的同名成員
  • 父類中的同名成員依然存在于子類中
  • 通過作用域分辨符( : : )訪問父類中的同名成員
  • 訪問父類中的同名成員
Child c;

//子類中的mi
c.mi = 100;

//父類中的mi
c.Parent::mi = 1000;

再來看一個例子:

#include <iostream>
#include <string>

using namespace std;

//定義一個命名空間A
namespace A
{
    int g_i = 0;
}
//定義一個命名空間B
namespace B
{
    int g_i = 1;
}

class Parent
{
public:
    int mi;
    
    Parent()
    {
        cout << "Parent() : " << "&mi = " << &mi << endl;
    }
};

class Child : public Parent
{
public:
    int mi;
    
    Child()
    {
        cout << "Child() : " << "&mi = " << &mi << endl;
    }
};

int main()
{
    Child c;
    
    //向子類的mi成員賦值100
    c.mi = 100;    
    //通過作用域向父類的mi成員賦值1000
    c.Parent::mi = 1000;
    
    //打印子類中mi的地址
    cout << "&c.mi = " << &c.mi << endl;
    //打印子類中mi的內(nèi)容
    cout << "c.mi = " << c.mi << endl;
    
    //打印父類中mi的地址
    cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
    //打印父類中mi的內(nèi)容
    cout << "c.Parent::mi = " << c.Parent::mi << endl;
    
    return 0;
}

輸出結(jié)果為:

Parent() : &mi = 0x7fff57f57a90
Child() : &mi = 0x7fff57f57a94
&c.mi = 0x7fff57f57a94
c.mi = 100
&c.Parent::mi = 0x7fff57f57a90
c.Parent::mi = 1000
  • 類中的成員函數(shù)可以進行重載
    • 重載函數(shù)的本質(zhì)為多個不同的函數(shù)
    • 函數(shù)名和參數(shù)列表是唯一的標(biāo)識
    • 函數(shù)重載必須發(fā)生在同一個作用域中
    • 所以父子之間的同名成員不構(gòu)成重載

比如像這樣:

class Parent
{
public:
    int mi;
    
    void add(int v)
    {
        mi += v;
    }
    
    void add(int a, int b)
    {
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mi;
    
    void add(int v)
    {
        mi += v;
    }
    
    void add(int a, int b)
    {
        mi += (a + b);
    }
    
    void add(int x, int y, int z)
    {
        mi += (x + y + z);
    }
};

代碼 Parent類 和 Child類 中有同名的函數(shù)add ,但是兩個類之間不構(gòu)成重載,只有Parent類中多個add函數(shù)構(gòu)成重載。

  • 子類中的函數(shù)將隱藏父類的同名函數(shù)
  • 子類無法重載父類中的成員函數(shù)
  • 使用作用域分辨符訪問父類中的同名函數(shù)
  • 子類可以定義父類中完全相同的成員函數(shù)

父子間的賦值兼容

  • 子類對象可以當(dāng)作父類對象使用(兼容性)
    • 子類對象可以直接賦值給父類對象
    • 子類對象可以直接初始化父類對象
    • 父類指針可以直接指向子類對象
    • 父類引用可以直接引用子類對象

舉個例子:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;
    
    void add(int i)
    {
        mi += i;
    }
    
    void add(int a, int b)
    {
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mv;
    
    void add(int x, int y, int z)
    {
        mv += (x + y + z);
    }
};

int main()
{
    Parent p;
    Child c;
    //子類對象可以直接賦值給父類對象
    p = c;
    //子類對象可以直接初始化父類對象
    Parent p1(c);
    //父類引用可以直接引用子類對象
    Parent& rp = c;
    //父類指針可以直接指向子類對象
    Parent* pp = &c;

    return 0;
}

在main函數(shù)中進行上述幾條的操作都沒有出現(xiàn)編譯出錯?,F(xiàn)在進行這樣操作:

rp.mi = 100;
rp.add(5);        
rp.add(10, 10);  

發(fā)現(xiàn)可以編譯通過,并沒有出現(xiàn)同名覆蓋的問題。但是如果這樣操作:

pp->mv = 1000;
pp->add(1, 10, 100);

運行以后就會報錯,報錯信息如下:

48-1.cpp:51:10: error: no member named 'mv' in 'Parent'
     pp->mv = 1000;
     ~~  ^
48-1.cpp:52:10: error: no matching member function for call to 'add'
     pp->add(1, 10, 100);
     ~~~~^~~
48-1.cpp:16:10: note: candidate function not viable: requires 2 arguments, but 3
      were provided
    void add(int a, int b)
         ^
48-1.cpp:11:10: note: candidate function not viable: requires single argument
      'i', but 3 arguments were provided
    void add(int i)
         ^
2 errors generated.

信息提示沒有找到帶有3個參數(shù)的add函數(shù)。為什么呢?

  • 當(dāng)使用父類指針(引用)指向子類對象時
    • 子類對象退化為父類對象
    • 只能訪問父類中定義的成員
    • 可以直接訪問被子類覆蓋的同名成員

特殊的同名函數(shù)

  • 子類中可以沖定義父類中已經(jīng)存在的成員函數(shù)
  • 這種沖定義發(fā)生在繼承中,叫做函數(shù)重寫
  • 函數(shù)重寫是同名覆蓋的一種特殊情況

例如:

class Parent
{
    public:
        void print()
        {
            cout << "I'm Parent." << endl;
        }
};

//函數(shù)重寫

class Child : public Parent
{
    public:
        void print()
        {
            cout << "I'm Child" << endl;
        }
};

假如函數(shù)重寫和賦值兼容同時出現(xiàn)呢? 就像這樣:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;
    
    void add(int i)
    {
        mi += i;
    }
    
    void add(int a, int b)
    {
        mi += (a + b);
    }
    
    void print()
    {
        cout << "I'm Parent." << endl;
    }
};

class Child : public Parent
{
public:
    int mv;
    
    void add(int x, int y, int z)
    {
        mv += (x + y + z);
    }
    
    void print()
    {
        cout << "I'm Child." << endl;
    }
};

void how_to_print(Parent* p)
{
    p->print();
}

int main()
{
    Parent p;
    Child c;
    
    how_to_print(&p);    // Expected to print: I'm Parent.
    how_to_print(&c);    // Expected to print: I'm Child.
    
    return 0;
}

預(yù)期輸出是 I'm Parent.I'm Child. 。但是實際輸出:

I'm Parent.
I'm Parent.
  • 問題分析
    • 編譯期間,編譯器只能根據(jù)指針的類型判斷所指向的對象
    • 根據(jù)賦值兼容,編譯器認(rèn)為父類指針指向的是父類對象
    • 因此,編譯結(jié)果只可能是調(diào)用父類中定義的同名函數(shù)

在編譯 void how_to_print(Parent* p) 這個函數(shù)時,編譯器不可能知道指針p究竟指向了什么,但是編譯器沒有理由報錯。于是,編譯器認(rèn)為最安全的做法是調(diào)用父類的print函數(shù),因為父類和子類肯定都有相同的print函數(shù)。

多態(tài)的概念和意義

  • 函數(shù)重寫回顧
    • 父類中被重寫的函數(shù)依然會繼承給子類
    • 子類中重寫的函數(shù)將覆蓋父類中的函數(shù)
    • 通過作用域分辨符( : : )可以訪問到父類中的函數(shù)

就像這樣:

Child c;
Parent* p = &c;

c.Parent::print();  //從父類中繼承
c.print();          //從子類中重寫

p->print();         //父類中定義

雖然程序邏輯是這樣,但并不是我們所期望的。面向?qū)ο笾衅谕男袨椋?/p>

  • 根據(jù) 實際的對象類型 判斷如何調(diào)用重寫函數(shù)
  • 父類指針(引用) 指向
    • 父類對象 則調(diào)用 父類 中定義的函數(shù)
    • 子類對象 則調(diào)用 子類 中定義的重寫函數(shù)

這里就引出了面向?qū)ο笾械?多態(tài) 的概念:

  • 根據(jù)實際的 對象類型決定函數(shù)調(diào)用 的具體目標(biāo)
  • 同樣的 調(diào)用語句 在實際運行時有 多種不同的表現(xiàn)形態(tài)

例如:

p->print();

p指向父類對象時,會執(zhí)行

void print()
{
    cout << "I'm Parent" << end;
}

p指向子類對象時,會執(zhí)行

void print()
{
    cout << "I'm Child" << endl;
}
  • C++語言直接支持多態(tài)的概念
    • 通過使用 virtual 關(guān)鍵字對多態(tài)進行支持
    • virtual 聲明的函數(shù)被重寫后具有多態(tài)特性
    • virtual 聲明的函數(shù)叫做虛函數(shù)

舉個例子:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    //用 virtual 關(guān)鍵字修飾,則具有多態(tài)特性
    virtual void print()
    {
        cout << "I'm Parent." << endl;
    }
};

class Child : public Parent
{
public:
    void print()
    {
        cout << "I'm Child." << endl;
    }
};

void how_to_print(Parent* p)
{
    // 展現(xiàn)多態(tài)的行為
    p->print();     
}

int main()
{
    Parent p;
    Child c;
    
    how_to_print(&p);    // Expected to print: I'm Parent.
    how_to_print(&c);    // Expected to print: I'm Child.
    
    return 0;
}

執(zhí)行結(jié)果為:

I'm Parent.
I'm Child.
  • 多態(tài)的意義
    • 在程序運行過程中展現(xiàn)出動態(tài)的特性
    • 函數(shù)重寫必須多態(tài)實現(xiàn),否則沒有意義
    • 多態(tài)是面向?qū)ο蠼M件化程序設(shè)計的基礎(chǔ)特性

靜態(tài)聯(lián)編和動態(tài)聯(lián)編

  • 理論中的概念
    • 靜態(tài)聯(lián)編
      • 在程序的編譯期間就能確定具體的函數(shù)調(diào)用。 如:函數(shù)重載
    • 動態(tài)聯(lián)編
      • 在程序?qū)嶋H運行后才能確定具體的函數(shù)調(diào)用。如:函數(shù)重寫

舉個例子:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    //函數(shù)重載  并且用 virtual 關(guān)鍵字修飾
    virtual void func()
    {
        cout << "void func()" << endl;
    }
    //函數(shù)重載  并且用 virtual 關(guān)鍵字修飾
    virtual void func(int i)
    {
        cout << "void func(int i) : " << i << endl;
    }
    //函數(shù)重載  并且用 virtual 關(guān)鍵字修飾
    virtual void func(int i, int j)
    {
        cout << "void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl;
    }
};

class Child : public Parent
{
public:
    //函數(shù)重載
    void func(int i, int j)
    {
        cout << "void func(int i, int j) : " << i + j << endl;
    }
    //函數(shù)重載
    void func(int i, int j, int k)
    {
        cout << "void func(int i, int j, int k) : " << i + j + k << endl;
    }
};

void run(Parent* p)
{
    p->func(1, 2);     // 展現(xiàn)多態(tài)的特性
                       // 動態(tài)聯(lián)編
}

int main()
{
    Parent p;
    
    p.func();         // 靜態(tài)聯(lián)編
    p.func(1);        // 靜態(tài)聯(lián)編
    p.func(1, 2);     // 靜態(tài)聯(lián)編
    
    cout << endl;
    
    Child c;
    
    c.func(1, 2);     // 靜態(tài)聯(lián)編
    
    cout << endl;
    
    run(&p);
    run(&c);
    
    return 0;
}

運行結(jié)果為:

void func()
void func(int i) : 1
void func(int i, int j) : (1, 2)

void func(int i, int j) : 3

void func(int i, int j) : (1, 2)
void func(int i, int j) : 3

小結(jié)

  • 子類可以定義父類的 同名成員 ,定義時子類中的成員將 隱藏 父類中的 同名成員
  • 子類和父類 中的函數(shù) 不能構(gòu)成重載關(guān)系
  • 使用 作用域分辨符 可以訪問父類中的 同名成員
  • 子類對象 可以當(dāng)做 父類對象 使用
  • 父類指針 可以正確的指向 子類對象
  • 父類引用 可以正確的代表 子類對象
  • 子類中可以重寫父類中的 成員函數(shù)
  • 函數(shù)重寫只可能發(fā)生在 父類與子類 之間
  • 多態(tài)是根據(jù) 實際對象的類型 確定調(diào)用的 具體函數(shù)
  • virtual關(guān)鍵字 是C++中支持 多態(tài) 的唯一方式
  • 被重寫的 虛函數(shù) 可表現(xiàn)出多態(tài)的特性
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容