1. 程式人生 > >C++解析(22):父子間的衝突

C++解析(22):父子間的衝突

0.目錄

1.同名覆蓋

2.賦值相容

3.函式重寫遇上賦值相容

4.小結

1.同名覆蓋

子類中是否可以定義父類中的同名成員?如果可以,如何區分?如果不可以,為什麼?

父子間的衝突:

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

訪問父類中的同名成員:

示例:

#include <iostream>

using namespace std;

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 << "&c.mi = " << &c.mi << endl;
    cout << "c.mi = " << c.mi << endl;
    
    cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
    cout << "c.Parent::mi = " << c.Parent::mi << endl;
    
    return 0;
}

執行結果為:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out 
Parent() : &mi = 0x7ffe251260c0
Child() : &mi = 0x7ffe251260c4
&c.mi = 0x7ffe251260c4
c.mi = 100
&c.Parent::mi = 0x7ffe251260c0
c.Parent::mi = 1000

類中的成員函式可以進行過載:

  1. 過載函式的本質為多個不同的函式
  2. 函式名引數列表是唯一的標識
  3. 函式過載必須發生在同一個作用域中

子類中定義的函式是否能過載父類中的同名函式?

示例:

#include <iostream>

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);
    
    cout << "c.mi = " << c.mi << endl;
    
    cout << "c.Parent::mi = " << c.Parent::mi << endl;
    
    return 0;
}

執行結果為:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out 
c.mi = 100
c.Parent::mi = 1000
c.mi = 121
c.Parent::mi = 1000

父子間的衝突:

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

示例:

#include <iostream>

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 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.Parent::add(1);
    c.Parent::add(2, 3);
    c.add(4, 5, 6);
    
    cout << "c.mi = " << c.mi << endl;
    
    cout << "c.Parent::mi = " << c.Parent::mi << endl;
    
    return 0;
}

執行結果為:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out 
c.mi = 100
c.Parent::mi = 1000
c.mi = 115
c.Parent::mi = 1006

2.賦值相容

子類物件可以當作父類物件使用(相容性):

  • 子類物件可以直接賦值給父類物件
  • 子類物件可以直接初始化父類物件
  • 父類指標可以直接指向子類物件
  • 父類引用可以直接引用子類物件

示例——能編譯通過的賦值相容:

#include <iostream>

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; // 第1種相容性
    
    Parent p1(c); // 第2種相容性
    Parent& rp = c; // 第3種相容性
    Parent* pp = &c; // 第4種相容性
    
    rp.mi = 100;
    rp.add(5);      // 沒有發生同名覆蓋?
    rp.add(10, 10); // 沒有發生同名覆蓋?
    
    return 0;
}

當使用父類指標(引用)指向子類物件時:

  • 子類物件退化為父類物件
  • 只能訪問父類中定義的成員
  • 可以直接訪問被子類覆蓋的同名成員

3.函式重寫遇上賦值相容

特殊的同名函式:

  • 子類中可以重定義父類中已經存在的成員函式
  • 這種重定義發生在繼承中,叫做函式重寫
  • 函式重寫是同名覆蓋的一種特殊情況

函式重寫遇上賦值相容會發生什麼?

示例——函式重寫遇上賦值相容:

#include <iostream>

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

執行結果為:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out 
I'm Parent.
I'm Parent.

問題分析:

  • 編譯期間,編譯器只能根據指標的型別判斷所指向的物件
  • 根據賦值相容,編譯器認為父類指標指向的是父類物件
  • 因此,編譯結果只可能是呼叫父類中定義的同名函式


在編譯這個函式的時候,編譯器不可能知道指標p究竟指向了什麼。但是編譯器沒有理由報錯。於是,編譯器認為最安全的做法是呼叫父類的print函式,因為父類和子類肯定都有相同的print函式。

4.小結

  • 子類可以定義父類中的同名成員
  • 子類中的成員將隱藏父類中的同名成員
  • 子類和父類中的函式不能構成過載關係
  • 子類可以定義父類中完全相同的成員函式
  • 使用作用域分辨符訪問父類中的同名成員
  • 子類物件可以當作父類物件使用(賦值相容)
  • 父類指標可以正確的指向子類物件
  • 父類引用可以正確的代表子類物件
  • 子類中可以重寫父類中的成員函式