繼承(二)


1. 父子間的同名沖突

同名變量沖突

  • 子類可以定義父類中的同名成員變量
  • 父類中的同名成員變量被隱藏,但仍然存在於子類中
  • 父類中的同名成員變量需要通過作用域分辨符(::)訪問
Child c;
c.mi = 100;           //訪問子類中的mi
c.Parent::mi = 1000;  //訪問父類中的mi
#include <iostream>
#include <string>

using namespace std;

namespace A
{
int g_i = 0;
}

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;

    c.mi = 100;
    c.Parent::mi = 1000;

    cout << endl;
    cout << "&c.mi = " << &c.mi << endl;
    cout << "c.mi = " << c.mi << endl;
    cout << endl;

    cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
    cout << "c.Parent::mi = " << c.Parent::mi << endl;

    return 0;
}

同名函數沖突

  • 子類中的成員函數將隱藏父類的同名成員函數(只需要同名即可,對參數列表和返回值類型無要求
  • 使用作用域分辨符訪問父類中的同名成員函數
  • 子類可以定義與父類完全相同的成員函數(包括函數名,參數列表、返回值類型)
  • 子類無法重載父類中的成員函數
#include <iostream>
#include <string>

using namespace std;

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);
    }
};

int main()
{
    Child c;

    c.mi = 100;
    c.Parent::mi = 1000;

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

    c.add(1);
    c.add(2, 3);
    c.add(4, 5, 6);

    c.Parent::add(1);
    c.Parent::add(2, 3);

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

    return 0;
}

2. 同名沖突引發的問題

父子間的賦值兼容

父子間的賦值兼容,指的是子類對象可以當作父類對象使用,具體表現在兩個方面

  • 子類對象可以直接初始化父類對象,或者給父類對象賦值
  • 父類指針(引用)可以直接指向(引用)子類對象
  • 實際上僅僅指向(引用)了子類對象中的父類部分
  • 通過父類指針(引用),只能訪問父類中的成員變量和成員函數
  • 子類對象本身不受影響,依然可以訪問自身的成員
#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;

    Parent(int i = 0)
    {
        mi = i;
    }

    void add(int i)
    {
        mi += i;
    }

    void add(int a, int b)
    {
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mv;

    Child(int v = 0)
    {
        mv = v;
    }

    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;

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

    cout << "p.mi = " << p.mi << endl;
    cout << "c.mv = " << c.mv << endl;
    cout << "rp.mi = " << rp.mi << endl;
    cout << "pp->mi = " << pp->mi << endl;
    cout << endl;

    /*編譯不過,只能訪問父類中的成員*/
    // pp->mv = 1000;
    // pp->add(1, 10, 100);

    /*父類指針或引用指向子類對象,對子類對象本身沒有影響*/
    c.mv = 1000;
    c.add(1, 1, 1);

    cout << "p.mi = " << p.mi << endl;
    cout << "c.mv = " << c.mv << endl;
    cout << "rp.mi = " << rp.mi << endl;
    cout << "pp->mi = " << pp->mi << endl;

    return 0;
}

函數重寫

  • 子類可以重定義父類中已經存在的成員函數
  • 這種重定義發生在繼承中,叫做函數重寫
  • 函數重寫是同名覆蓋的一種特殊情況
class Parent
{
public:
    void print()
    {
        cout << "I'm Parent." << endl;
    }
};

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

當函數重寫遇上賦值兼容

當函數重寫遇上賦值兼容時會發生什么,下面的示例代碼展示了這個問題。

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    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)
{
    p->print();
}

int main()
{
    Parent p;
    Child c;

    how_to_print(&p);  // Expected: I'm Parent
    how_to_print(&c);  // Expected: I'm Child

    return 0;
}

第34-35行注釋部分展示了我們期望的結果,但實際運行結果和期望不符,原因在於

  • 編譯期間,編譯器只能根據指針(引用)的類型判斷所指向(引用)的對象
  • 根據賦值兼容,編譯器認為父類指針(引用)指向(引用)的是父類對象
  • 因此,編譯結果只可能是調用父類中定義的同名函數

編譯器的處理方法是合理的,但不是我們期望的,這就是同名沖突引發的問題,要想解決這個問題,需要用到多態的知識。

3. 繼承中的強制類型轉換

我們之前講過了C++的四種強制類型轉換,那么在繼承中如何正確地使用強制類型轉換呢?

  • dynamic_cast是與繼承相關的類型轉換關鍵字
  • dynamic_cast要求相關的類中必須有虛函數
  • dynamic_cast用於有直接或者間接繼承關系的類指針(引用)之間
  • 指針:轉換成功,得到目標類型的指針;轉換失敗,得到一個空指針
  • 引用:轉換成功,得到目標類型的引用;轉換失敗,得到一個異常操作信息
  • 編譯器會檢查dynamic_cast的使用是否正確
  • 類型轉換的結果只可能在運行階段才能得到
/*
** 繼承中的dynamic_cast關鍵字
*/

#include <iostream>
#include <string>

using namespace std;

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

    /*
     * dynamic_cast要求類中必須有虛函數,因此將父類的析構函數定義為虛函數
    */
    virtual ~Base()
    {
        cout << "Base::~Base()" << endl;
    }
};

class Derived : public Base
{

};

int main()
{
    Base *p0 = new Derived;
    Base *p1 = new Base;
    Base &p2 = *p1;

    Derived *pd0 = dynamic_cast<Derived *>(p0); //dynamic_cast轉換指針成功,得到目標類型指針
    Derived *pd1 = dynamic_cast<Derived *>(p1); //不能用子類指針指向一個父類對象,dynamic_cast轉換指針失敗,得到的pd1為NULL

    cout << "pd0 = " << pd0 << endl;
    cout << "pd1 = " << pd1 << endl;

    Derived &pd2 = dynamic_cast<Derived &>(p2); //dynamic_cast轉換引用失敗,運行時拋出異常

    delete p0;
    delete p1;

    return 0;
}


免責聲明!

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



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