1. 程式人生 > >C++繼承相關知識點總結

C++繼承相關知識點總結

1:派生類繼承基類的成員並且可以定義自己的附加成員。每個派生類物件包含兩個部分:從基類繼承的成員和自己定義的成員。

        每個派生類物件都有基類部分,包括基類的private成員。類可以訪問共基類的public 和 protected 成員,就好像那些成員是派生類自己的成員一樣。派生類不能訪問基類的 private 成員。也就是說,雖然派生類繼承了基類的私有成員,但是不可訪問。比如下面的例子:

class father
{
public:
    int publ_i;
    father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {}
    void display_f()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son(int a, int b, int c):father(a, b, c) {};
    void display_s()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]priv_i is " << priv_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
    }
};

       上面的程式碼編譯時會出錯:

test.cpp: In member function ‘void son::display_s()’:
test.cpp:17:6: error: ‘int father::priv_i’ is private
  int priv_i;
      ^
test.cpp:29:32: error: within this context
   cout << "[son]priv_i is " << priv_i << endl;
                                ^

2:類的成員函式中,可以訪問其他同類物件中的私有成員。比如:

class father
{
public:
    int publ_i;
    father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {}

    void display_f(const father& ff) const
    {
        cout << "[father]my publ_i is " << this->publ_i << endl;
        cout << "[father]my priv_i is " << this->priv_i << endl;
        cout << "[father]my prot_i is " << this->prot_i << endl;
        
        cout << "[father]ff.publ_i is " << ff.publ_i << endl;
        cout << "[father]ff.priv_i is " << ff.priv_i << endl;
        cout << "[father]ff.prot_i is " << ff.prot_i << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

int main()
{   
    father ff1(1,2,3);
    father ff2(4,5,6);
    
    ff1.display_f(ff2);
}

         在father::display_f成員函式中,可以訪問物件ff中的所有成員,包括私有成員。這段程式碼的執行結果是:

[father]my publ_i is 1
[father]my priv_i is 2
[father]my prot_i is 3
[father]ff.publ_i is 4
[father]ff.priv_i is 5
[father]ff.prot_i is 6

3:派生類只能通過派生類物件訪問其基類的 protected 成員,派生類對其基類型別物件的 protected 成員沒有特殊訪問許可權。比如:

class father
{
public:
    int publ_i;
    father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {}
    void display_f()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }

private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son(int a, int b, int c):father(a, b, c) {};

    void display_sf(son& ss, father& ff)
    {
        cout << "[son]ss.publ_i is " << ss.publ_i << endl;
        cout << "[son]ss.prot_i is " << ss.prot_i << endl;

        cout << "[son]ff.publ_i is " << ff.publ_i << endl;
        cout << "[son]ff.prot_i is " << ff.prot_i << endl;
    }
};

         上面的程式碼,在編譯時會出錯:

test2.cpp: In member function ‘void son::display_sf(son&, father&)’:
test2.cpp:20:6: error: ‘int father::prot_i’ is protected
  int prot_i;
      ^
test2.cpp:34:38: error: within this context
   cout << "[son]ff.prot_i is " << ff.prot_i << endl;
                                      ^

4:C++ 中的函式呼叫預設不使用動態繫結。要觸發動態繫結,滿足兩個條件:第一,只有指定為虛擬函式的成員函式才能進行動態繫結。成員函式預設為非虛擬函式,對非虛擬函式的呼叫在編譯時確定;第二,必須通過基類型別的引用或指標進行函式呼叫。引用和指標的靜態型別與動態型別可以不同,這是 C++ 用以支援多型性的基石。

        如果呼叫非虛擬函式,則無論實際物件是什麼型別,都執行基類型別所定義的函式。如果呼叫虛擬函式,則直到執行時才能確定呼叫哪個函式,執行的虛擬函式是引用所繫結的或指標所指向的物件所屬型別定義的版本。

        為了指明函式為虛擬函式,在其返回型別前面加上保留字 virtual。除了建構函式之外,任意非 static 成員函式都可以是虛擬函式。保留字只在類內部的成員函式宣告中出現,不能用在類定義體外部出現的函式定義上。

         基類通常應將派生類需要重定義的任意函式定義為虛擬函式。如果派生類沒有重定義某個虛擬函式,則使用基類中定義的版本。

        派生類中虛擬函式的宣告必須與基類中的定義方式完全匹配,但有一個例外:返回對基型別的引用(或指標)的虛擬函式。派生類中的虛擬函式可以返回基類函式所返回型別的派生類的引用(或指標)。

        一旦函式在基類中宣告為虛擬函式,它就一直為虛擬函式,派生類無法改變該函式為虛擬函式這一事實。派生類重定義虛擬函式時,可以使用 virtual 保留字,但不是必須這樣做。

5:即使在派生類中重新定義了虛擬函式,實際上基類中的虛擬函式也被派生類繼承了,只不過它被派生類的虛擬函式覆蓋了而已。如果希望覆蓋虛擬函式機制並強制使用虛擬函式的特定版本,這裡可以使用作用域操作符:

class father
{
public:
    int publ_i;
    father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {}
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }

private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son(int a, int b, int c):father(a, b, c) {};

    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
    }
};

int main()
{
    son ss(4, 5, 6);
    
    father *fp = &ss;
    fp->display();
    
    fp->father::display();
}

         上面的” fp->father::display();”語句,將強制呼叫father中定義的版本,該呼叫將在編譯時確定。上述程式碼的結果如下:

[son]publ_i is 4
[son]prot_i is 6
[father]publ_i is 4
[father]priv_i is 5
[father]prot_i is 6

         或者,直接在派生類中呼叫基類中的函式:

class son: public father
{
public:
    son(int a, int b, int c):father(a, b, c) {};

    void display()
    {
        father::display();
    }
};

int main()
{
    son ss(4, 5, 6);
    
    father *fp = &ss;
    fp->display();
}

         這段程式碼結果為:

[father]publ_i is 4
[father]priv_i is 5
[father]prot_i is 6

6:,虛擬函式也可以有預設實參。通過基類的引用或指標呼叫虛擬函式時,預設實參為在基類虛擬函式宣告中指定的值,如果通過派生類的指標或引用呼叫虛擬函式,則預設實參是在派生類的版本中宣告的值。因此,在同一虛擬函式的基類版本和派生類版本中使用不同的預設實參幾乎一定會引起麻煩。

class father
{
public:
    int publ_i;
    virtual void display(int a = 1, int b = 2, int c = 3)
    {
        cout << "[father]a is " << a << endl;
        cout << "[father]b is " << b << endl;
        cout << "[father]c is " << c << endl;
    }

private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    virtual void display(int a = 4, int b = 5, int c = 6)
    {
        cout << "[son]a is " << a << endl;
        cout << "[son]b is " << b << endl;
        cout << "[son]c is " << c << endl;
    }
};

int main()
{
    son ss;
    
    father *fp = &ss;
    son *sp = &ss;
    fp->display();
    sp->display();
}

         fp->display(),使用指向派生類物件的基類指標呼叫display,此時儘管發生動態繫結,呼叫的是派生類的display函式,但是預設實參卻是基類中的版本;fp->display(),使用指向派生類物件的派生類指標呼叫display,預設實參是派生類的版本。結果如下:

[son]a is 1
[son]b is 2
[son]c is 3
[son]a is 4
[son]b is 5
[son]c is 6

7:如果成員在基類中為 private,則只有基類和基類的友元可以訪問該成員。派生類不能訪問基類的 private 成員,也不能使自己的使用者能夠訪問那些成員。

        如果基類成員為 public 或 protected,則派生列表中使用的訪問標號決定該成員在派生類中的訪問級別:

        • 如果是公用繼承,基類成員保持自己的訪問級別:基類的 public 成員為派生類的 public 成員,基類的 protected 成員為派生類的 protected成員。

        • 如果是受保護繼承,基類的 public 和 protected 成員在派生類中為protected成員。

        • 如果是私有繼承,基類的的所有成員在派生類中為 private 成員。

        比如下面的程式碼:

class father
{
public:
    int publ_i;
    father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {}
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }

    void fun()
    {
        cout << "[father]fun" << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class public_son: public father
{
public:
    public_son(int a, int b, int c):father(a, b, c) {};

    void display()
    {
        cout << "[public_son]publ_i is " << publ_i << endl;
        cout << "[public_son]prot_i is " << prot_i << endl;
    }
};

class grandson1:public public_son
{
public:
    grandson1(int a, int b, int c):public_son(a, b, c) {};
    void display()
    {
        cout << "[grandson1]publ_i is " << publ_i << endl;
        cout << "[grandson1]prot_i is " << prot_i << endl;
    }
};
int main()
{
    public_son ss(1, 2, 3);
    ss.display();
    ss.fun();
}

         在public_son中,father中的public和protected成員在派生類中保持他們的屬性,因此,在grandson1::display中可以訪問publ_i和prot_i,在使用者程式碼中,可以呼叫public_son::display和public_son::fun函式;上面的程式碼結果如下:

[public_son]publ_i is 1
[public_son]prot_i is 3
[father]fun

         再看保護繼承:

class protected_son: protected father
{
public:
    protected_son(int a, int b, int c):father(a, b, c) {};

    void display()
    {
        cout << "[protected_son]publ_i is " << publ_i << endl;
        cout << "[protected_son]prot_i is " << prot_i << endl;
    }
};
class grandson2:public protected_son
{
    public:
    grandson2(int a, int b, int c):protected_son(a, b, c) {};
    void display()
    {
        cout << "[grandson2]publ_i is " << publ_i << endl;
        cout << "[grandson2]prot_i is " << prot_i << endl;
    }
};

         在protected_son中,father中的public和protected成員在派生類中成為了protected成員,因此,在grandson2::display中可以訪問publ_i和prot_i。但是在使用者程式碼中,不可以呼叫protected_son::fun函式:

    protected_son s1(1,2,3);
    s1.fun();

        編譯出錯:

inherit.cpp: In function ‘int main()’:
inherit.cpp:20:7: error: ‘void father::fun()’ is inaccessible
  void fun()
       ^
inherit.cpp:113:9: error: within this context
  s1.fun();
         ^
inherit.cpp:113:9: error: ‘father’ is not an accessible base of ‘protected_son’

         在使用者程式碼中,也不可以呼叫grandson2::fun函式:

    grandson2 s2(4, 5, 6);
    s2.fun();

         編譯同樣出錯:

inherit.cpp: In function ‘int main()’:
inherit.cpp:20:7: error: ‘void father::fun()’ is inaccessible
  void fun()
       ^
inherit.cpp:116:9: error: within this context
  s2.fun();
         ^
inherit.cpp:116:9: error: ‘father’ is not an accessible base of ‘grandson2’

         最後看一下私有繼承:

class private_son: private father
{
public:
    private_son(int a, int b, int c):father(a, b, c) {};

    void display()
    {
        cout << "[private_son]publ_i is " << publ_i << endl;
        cout << "[private_son]prot_i is " << prot_i << endl;
    }
};

class grandson3:public private_son
{
public:
    grandson3(int a, int b, int c):private_son(a, b, c) {};
    void display()
    {
        cout << "[grandson1]publ_i is " << publ_i << endl;
        cout << "[grandson1]prot_i is " << prot_i << endl;
    }
};
int main()
{
    private_son s1(1,2,3);
    s1.display();
    s1.fun();
}

         在private_son中,father中的public和protected成員在派生類中成為了private成員,因此,在grandson3::display中不可以訪問publ_i和prot_i;在使用者程式碼中,也不可以呼叫private_son::fun函式:

inherit.cpp: In member function ‘virtual void grandson3::display()’:
inherit.cpp:11:6: error: ‘int father::publ_i’ is inaccessible
  int publ_i;
      ^
inherit.cpp:103:38: error: within this context
   cout << "[grandson1]publ_i is " << publ_i << endl;
                                      ^
inherit.cpp:28:6: error: ‘int father::prot_i’ is protected
  int prot_i;
      ^
inherit.cpp:104:38: error: within this context
   cout << "[grandson1]prot_i is " << prot_i << endl;
                                      ^
inherit.cpp: In function ‘int main()’:
inherit.cpp:20:7: error: ‘void father::fun()’ is inaccessible
  void fun()
       ^
inherit.cpp:115:9: error: within this context
  s1.fun();
         ^
inherit.cpp:115:9: error: ‘father’ is not an accessible base of ‘private_son’

         總而言之,派生類派生列表中的訪問標號並不影響直接派生類的成員函式,它影響的是直接派生類的使用者程式碼,以及間接派生類的成員函式的訪問許可權。

8:派生類可以使用using宣告,修改繼承成員的訪問級別。(C++ primer第四版 15.2.5中說,派生類不能使訪問級別比基類中原來指定的更嚴格或更寬鬆,這是錯的,實驗證明,對於基類中的public成員或protected成員,派生類可以使用using宣告修改它們的訪問許可權):

class private_son: private father
{
public:
    private_son(int a, int b, int c):father(a, b, c) {};
    using father::prot_i;

    void display()
    {
        cout << "[private_son]publ_i is " << publ_i << endl;
        cout << "[private_son]prot_i is " << prot_i << endl;
    }
};

int main()
{
    private_son ss1(4, 5, 6);
    ss1.prot_i = 10;
    ss1.display();
}

         在private_son類中,按照私有繼承的規則,father::publ_i和father::prot_i在private_son中成為了私有成員。這裡使用using宣告,修改了father::prot_i的訪問屬性為public,因此,可以直接在使用者程式碼中設定ss1.prot_i的值。上面程式碼中結果如下:

[private_son]publ_i is 4
[private_son]prot_i is 10

         注意,基類中的private成員,不能在派生類中任何地方用using宣告。

9:使用 class 保留字定義的派生預設具有private 繼承,而用 struct 保留字定義的類預設具有 public 繼承:

class Base { /* ... */ };
struct D1 : Base { /* ... */ }; // public inheritance by default
class D2 : Base { /* ... */ }; // private inheritance by default

10:友元關係不能繼承。基類的友元對派生類的成員沒有特殊訪問許可權。如果基類被授予友元關係,則只有基類具有特殊訪問許可權,該基類的派生類不能訪問授予友元關係的類。

11:如果基類定義 static 成員,則整個繼承層次中只有一個這樣的成員。無論從基類派生出多少個派生類,每個 static 成員只有一個例項。

12:如果有一個派生型別的物件,則可以使用它的地址對基類型別的指標進行賦值或初始化。同樣,可以使用派生型別的引用或物件初始化基類型別的引用。

        一般可以使用派生型別物件對基類物件進行賦值或初始化。對基類物件進行初始化或賦值,實際上是在呼叫函式:初始化時呼叫建構函式,賦值時呼叫賦值操作符。基類一般(顯式或隱式地)定義自己的複製建構函式和賦值操作符,這些成員接受一個形參,該形參是基類型別的(const)引用。因為存在從派生類引用到基類引用的轉換,所以這些複製控制成員可用於從派生類物件對基類物件進行初始化或賦值。這種情況下,使用派生類物件的基類部分,對基類物件進行初始化或賦值,而派生類本身的部分則被切掉了。

        從基類到派生類的自動轉換是不存在的,需要派生類物件時不能使用基類物件,派生類物件的指標和引用也不能使用基類物件的指標或引用進行賦值。

13:派生類的作用域巢狀在基類作用域中。如果不能在派生類作用域中確定名字,就在外圍基類作用域中查詢該名字的定義。

        名字查詢是在編譯時發生的,因此,即使使用基類型別的引用或指標指向派生類物件時,也不能通過該引用或指標,訪問派生類中存在而基類中不存在的成員:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    void son_func()
    {
        cout << "this is son func" << endl;
    }
};

int main()
{
    father *fp = new son;
    fp->son_func();
}

        上面的程式碼中,即使指標fp指向了一個派生類物件。但是該指標還是一個基類指標,不能呼叫派生類中特有的函式。上述程式碼傳送編譯錯誤:

test.cpp: In function ‘int main()’:
test.cpp:30:6: error: ‘class father’ has no member named ‘son_func’
  fp->son_func();
      ^

14:如果派生類中的某個成員名字與基類中成員名字相同,則儘管派生類繼承了基類的該成員,但是它自身的成員遮蔽掉了基類中的成員。

         特別是對於成員函式來說,即使函式原型不同,基類成員也會被遮蔽:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
    }
    void fun()
    {
        cout << "this is father fun" << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    void fun(int a)
    {
        cout << "this is son fun" << endl;
    }
};

int main()
{
    son ss;
    ss.fun(3);
    ss.fun();
}

         呼叫”ss.fun(3)”時,呼叫的是son類中的版本;而呼叫ss.fun()時,將會發生編譯錯誤,因為編譯器查詢名字fun時,在son中找到該名字之後就不會再繼續在基類中查找了。這個呼叫與son中的fun定義不匹配,因此編譯出錯:

test.cpp: In function ‘int main()’:
test.cpp:35:9: error: no matching function for call to ‘son::fun()’
  ss.fun();
         ^
test.cpp:25:7: note: candidate: void son::fun(int)
  void fun(int a)
       ^
test.cpp:25:7: note:   candidate expects 1 argument, 0 provided

         要想明確呼叫基類中的fun函式,可以採用域操作符:

ss.father::fun();

         從而得到正確的結果:

this is son fun
this is father fun

         因此,如果基類中的成員函式有多個過載版本,則只要派生類中重定義了一個同名函式,則派生類物件只能訪問該重定義版本,因為它遮蔽了所有基類中的同名函式。

        如果派生類需要僅僅重定義一個過載集中某些版本的行為,並且想要繼承其他版本的含義,派生類可以為過載成員使用之前提過的 using 宣告。一個 using 宣告只指定一個名字,不指定形參表,因此,為基類成員函式名稱而作的 using 宣告將該函式的所有過載例項加到派生類的作用域,之後,派生類只需要重定義必須定義的那些函式,對其他版本可以使用繼承的定義:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
    }
    void fun()
    {
        cout << "this is father fun" << endl;
    }
    void fun(int a)
    {
        cout << "this is father fun(int a)" << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    using father:fun;
    void fun(int a)
    {
        cout << "this is son fun(int a)" << endl;
    }
};

int main()
{
    son ss;
    ss.fun(3);
    ss.fun();
}

        結果如下:

this is son fun(int a)
this is father fun

15:虛擬函式在基類和派生類中必須擁有同一原型。如果基類成員與派生類成員接受的實參不同,就沒有辦法通過基類型別的引用或指標呼叫派生類函式。考慮如下(人為的)類集合:

class Base {
public:
virtual int fcn();
};

class D1 : public Base {
public:
// hides fcn in the base; this fcn is not virtual
int fcn(int); // parameter list differs from fcn in Base
// D1 inherits definition of Base::fcn()
};

class D2 : public D1 {
public:
int fcn(int); // nonvirtual function hides D1::fcn(int)
int fcn(); // redefines virtual fcn from Base
};

       D1 中的 fcn 版本沒有重定義 Base 的虛擬函式 fcn,相反,它遮蔽了基類的fcn。結果 D1 有兩個名為 fcn 的函式:從 Base 繼承了一個名為 fcn 的虛擬函式,又定義了自己的名為 fcn 的非虛成員函式,該函式接受一個 int 形參。但是,從 Base 繼承的虛擬函式不能通過 D1 物件(或 D1 的引用或指標)呼叫,因為該函式被 fcn(int) 的定義遮蔽了。

        類 D2 重定義了它繼承的兩個函式,它重定義了 Base 中定義的 fcn 的原始版本並重定義了 D1 中定義的非虛版本。

       考慮下面的程式碼:

Base bobj; D1 d1obj; D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
bp1->fcn(); // ok: virtual call, will call Base::fcnat run time
bp2->fcn(); // ok: virtual call, will call Base::fcnat run time
bp3->fcn(); // ok: virtual call, will call D2::fcnat run time

16:在成員函式形參表後面寫上 = 0 ,可以指定該成員函式為純虛擬函式。含有(或繼承)一個或多個純虛擬函式的類是抽象基類。將函式定義為純虛能夠說明,該函式為後代型別提供了可以覆蓋的介面,但是這個類中的版本決不會呼叫。重要的是,使用者將不能建立抽象基類的物件。試圖建立抽象基類的物件將發生編譯時錯誤:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
    }
    virtual void fun() = 0;
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
    void fun(int a) {}
};

         father類包含純虛擬函式fun(),派生類son繼承了該純虛擬函式,而且重定義了一個原型不同的同名函式fun(int a),因此,son也是個抽象基類。所以,語句”son ss”將會是編譯錯誤:

test.cpp: In function ‘int main()’:
test.cpp:27:6: error: cannot declare variable ‘ss’ to be of abstract type ‘son’
  son ss;
      ^
test.cpp:19:7: note:   because the following virtual functions are pure within ‘son’:
 class son: public father
       ^
test.cpp:12:15: note:   virtual void father::fun()
  virtual void fun() = 0;
               ^