面向對象複習提綱(二 繼承的基本知識)

上一篇 :面向對象複習提綱(一 類的基本知識)
鏈接:https://blog.csdn.net/weixin_43732535/article/details/106451560

1.繼承的方式

protected 成員和 private 成員類似,也不能通過對象訪問。但是當存在繼承關係時,protected 和 private 就不一樣了:基類中的 protected 成員可以在派生類中使用,而基類中的 private 成員不能在派生類中使用。

public、protected、private 指定繼承方式
不同的繼承方式會影響基類成員在派生類中的訪問權限。

  1. public繼承方式
    • 基類中所有 public 成員在派生類中爲 public 屬性;
    • 基類中所有 protected成員在派生類中爲 protected 屬性;
    • 基類中所有 private 成員在派生類中不能使用。
  2. protected繼承方式
    • 基類中的所有 public 成員在派生類中爲 protected 屬性;
    • 基類中的所有 protected 成員在派生類中爲 protected 屬性;
    • 基類中的所有 private 成員在派生類中不能使用。
  3. private繼承方式
    • 基類中的所有 public 成員在派生類中均爲 private 屬性;
    • 基類中的所有 protected 成員在派生類中均爲 private 屬性;
    • 基類中的所有 private 成員在派生類中不能使用。

作爲小朋友的我們是不是還很迷茫啊,那就總結一下吧。

總結:基類在派生類中顯示的屬性不會超過繼承方式的屬性,不論什麼方式下繼承基類中所有 private 成員在派生類中不能使用。

舉個栗子

#include<iostream>
using namespace std;

//基類People
class People{
public:
    void setname(char *name);
    void setage(int age);
    void sethobby(char *hobby);
    char *gethobby();
protected:
    char *m_name;
    int m_age;
private:
    char *m_hobby;
};
void People::setname(char *name){ m_name = name; }
void People::setage(int age){ m_age = age; }
void People::sethobby(char *hobby){ m_hobby = hobby; }
char *People::gethobby(){ return m_hobby; }

//派生類Student
class Student: public People{
public:
    void setscore(float score);
protected:
    float m_score;
};
void Student::setscore(float score){ m_score = score; }

//派生類Pupil
class Pupil: public Student{
public:
    void setranking(int ranking);
    void display();
private:
    int m_ranking;
};
void Pupil::setranking(int ranking){ m_ranking = ranking; }
void Pupil::display(){
    cout<<m_name<<"的年齡是"<<m_age<<",考試成績爲"<<m_score<<"分,班級排名第"<<m_ranking<<",TA喜歡"<<gethobby()<<"。"<<endl;
}

int main(){
    Pupil pup;
    pup.setname("小明");
    pup.setage(15);
    pup.setscore(92.5f);
    pup.setranking(4);
    pup.sethobby("乒乓球");
    pup.display();

    return 0;
}

2.改變訪問權限 using

注意:using 只能改變基類中 public 和 protected 成員的訪問權限,不能改變 private 成員的訪問權限,因爲基類中 private 成員在派生類中是不可見的,根本不能使用,所以基類中的 private 成員在派生類中無論如何都不能訪問。

舉個栗子

#include<iostream>
using namespace std;

//基類People
class People{
public:
    void show();
protected:
    char *m_name;
    int m_age;
};
void People::show(){
    cout<<m_name<<"的年齡是"<<m_age<<endl;
}

//派生類Student
class Student: public People{
public:
    void learning();
public:
    using People::m_name;  //將protected改爲public
    using People::m_age;  //將protected改爲public
    float m_score;
private:
    using People::show;  //將public改爲private
};
void Student::learning(){
    cout<<"我是"<<m_name<<",今年"<<m_age<<"歲,這次考了"<<m_score<<"分!"<<endl;
}

int main(){
    Student stu;
    stu.m_name = "小明";
    stu.m_age = 16;
    stu.m_score = 99.5f;
    stu.show();  //compile error
    stu.learning();

    return 0;
}

在這裏插入圖片描述

3.名字遮蔽(隱藏)

如果派生類中的成員(包括成員變量和成員函數)和基類中的成員重名,那麼就會遮蔽從基類繼承過來的成員。所謂遮蔽,就是在派生類中使用該成員(包括在定義派生類時使用,也包括通過派生類對象訪問該成員)時,實際上使用的是派生類新增的成員,而不是從基類繼承來的

舉個栗子

#include<iostream>
using namespace std;

//基類People
class People{
public:
    void show();
protected:
    char *m_name;
    int m_age;
};
void People::show(){
    cout<<"嗨,大家好,我叫"<<m_name<<",今年"<<m_age<<"歲"<<endl;
}

//派生類Student
class Student: public People{
public:
    Student(char *name, int age, float score);
public:
    void show();  //遮蔽基類的show()
private:
    float m_score;
};
Student::Student(char *name, int age, float score){
    m_name = name;
    m_age = age;
    m_score = score;
}
void Student::show(){
    cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<endl;
}

int main(){
    Student stu("小明", 16, 90.5);
    //使用的是派生類新增的成員函數,而不是從基類繼承的
    stu.show();
    //使用的是從基類繼承來的成員函數
    stu.People::show();

    return 0;
}

注意:基類成員函數和派生類成員函數不構成重載

基類成員和派生類成員的名字一樣時會造成遮蔽,這句話對於成員變量很好理解,對於成員函數要引起注意,不管函數的參數如何,只要名字一樣就會造成遮蔽。換句話說,基類成員函數和派生類成員函數不會構成重載,如果派生類有同名函數,那麼就會遮蔽基類中的所有同名函數,不管它們的參數是否一樣。

舉個栗子

#include<iostream>
using namespace std;

//基類Base
class Base{
public:
    void func();
    void func(int);
};
void Base::func(){ cout<<"Base::func()"<<endl; }
void Base::func(int a){ cout<<"Base::func(int)"<<endl; }

//派生類Derived
class Derived: public Base{
public:
    void func(char *);
    void func(bool);
};
void Derived::func(char *str){ cout<<"Derived::func(char *)"<<endl; }
void Derived::func(bool is){ cout<<"Derived::func(bool)"<<endl; }

int main(){
    Derived d;
    d.func("c.biancheng.net");
    d.func(true);
    d.func();  //compile error
    d.func(10);  //compile error
    d.Base::func();
    d.Base::func(100);

    return 0;
}

4.派生類的構造函數

類的構造函數不能被繼承。構造函數不能被繼承是有道理的,因爲即使繼承了,它的名字和派生類的名字也不一樣,不能成爲派生類的構造函數,當然更不能成爲普通的成員函數。
在設計派生類時,對繼承過來的成員變量的初始化工作也要由派生類的構造函數完成,但是大部分基類都有 private 屬性的成員變量,它們在派生類中無法訪問,更不能使用派生類的構造函數來初始化。

那麼該如何進行初始化那?
解決這個問題的思路是:在派生類的構造函數中調用基類的構造函數。

舉個栗子

#include<iostream>
using namespace std;

//基類People
class People{
protected:
    char *m_name;
    int m_age;
public:
    People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}

//派生類Student
class Student: public People{
private:
    float m_score;
public:
    Student(char *name, int age, float score);
    void display();
};
//People(name, age)就是調用基類的構造函數
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
    cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<"。"<<endl;
}

int main(){
    Student stu("小明", 16, 90.5);
    stu.display();

    return 0;
}

在這裏插入圖片描述
基類構造函數不會被繼承,不能當做普通的成員函數來調用。換句話說,只能將基類構造函數的調用放在函數頭部,不能放在函數體中。

5.構造函數的調用順序

基類構造函數總是被優先調用,這說明創建派生類對象時,會先調用基類構造函數,再調用派生類構造函數

A類構造函數 --> B類構造函數 --> C類構造函數

構造函數的調用順序是按照繼承的層次自頂向下、從基類再到派生類的。

還有一點要注意,派生類構造函數中只能調用直接基類的構造函數,不能調用間接基類的。以上面的 A、B、C 類爲例,C 是最終的派生類,B 就是 C 的直接基類,A 就是 C 的間接基類。

基類構造函數調用規則
事實上,通過派生類創建對象時必須要調用基類的構造函數,這是語法規定。換句話說,定義派生類構造函數時最好指明基類構造函數;如果不指明,就調用基類的默認構造函數(不帶參數的構造函數);如果沒有默認構造函數,那麼編譯失敗。

舉個栗子

#include <iostream>
using namespace std;

//基類People
class People{
public:
    People();  //基類默認構造函數
    People(char *name, int age);
protected:
    char *m_name;
    int m_age;
};
People::People(): m_name("xxx"), m_age(0){ }
People::People(char *name, int age): m_name(name), m_age(age){}

//派生類Student
class Student: public People{
public:
    Student();
    Student(char*, int, float);
public:
    void display();
private:
    float m_score;
};
Student::Student(): m_score(0.0){ }  //派生類默認構造函數
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
    cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<"。"<<endl;
}

int main(){
    Student stu1;
    stu1.display();

    Student stu2("小明", 16, 90.5);
    stu2.display();

    return 0;
}

在這裏插入圖片描述

6.派生類的析構函數

和構造函數類似,析構函數也不能被繼承。與構造函數不同的是,在派生類的析構函數中不用顯式地調用基類的析構函數,因爲每個類只有一個析構函數,編譯器知道如何選擇,無需程序員干涉。

另外析構函數的執行順序和構造函數的執行順序也剛好相反:
• 創建派生類對象時,構造函數的執行順序和繼承順序相同,即先執行基類構造函數,再執行派生類構造函數。
• 而銷燬派生類對象時,析構函數的執行順序和繼承順序相反,即先執行派生類析構函數,再執行基類析構函數。

#include <iostream>
using namespace std;

class A{
public:
    A(){cout<<"A constructor"<<endl;}
    ~A(){cout<<"A destructor"<<endl;}
};

class B: public A{
public:
    B(){cout<<"B constructor"<<endl;}
    ~B(){cout<<"B destructor"<<endl;}
};

class C: public B{
public:
    C(){cout<<"C constructor"<<endl;}
    ~C(){cout<<"C destructor"<<endl;}
};

int main(){
    C test;
    return 0;
}

7.類的多繼承

在這裏插入圖片描述

多繼承下的構造函數

多繼承形式下的構造函數和單繼承形式基本相同,只是要在派生類的構造函數中調用多個基類的構造函數。

基類構造函數的調用順序和和它們在派生類構造函數中出現的順序無關,而是和聲明派生類時基類出現的順序相同 。仍然以上面的 A、B、C、D 類爲例,即使將 D 類構造函數寫作下面的形式:
在這裏插入圖片描述

舉個栗子

#include <iostream>
using namespace std;

//基類
class BaseA{
public:
    BaseA(int a, int b);
    ~BaseA();
protected:
    int m_a;
    int m_b;
};
BaseA::BaseA(int a, int b): m_a(a), m_b(b){
    cout<<"BaseA constructor"<<endl;
}
BaseA::~BaseA(){
    cout<<"BaseA destructor"<<endl;
}

//基類
class BaseB{
public:
    BaseB(int c, int d);
    ~BaseB();
protected:
    int m_c;
    int m_d;
};
BaseB::BaseB(int c, int d): m_c(c), m_d(d){
    cout<<"BaseB constructor"<<endl;
}
BaseB::~BaseB(){
    cout<<"BaseB destructor"<<endl;
}

//派生類
class Derived: public BaseA, public BaseB{
public:
    Derived(int a, int b, int c, int d, int e);
    ~Derived();
public:
    void show();
private:
    int m_e;
};
Derived::Derived(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), m_e(e){
    cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
    cout<<"Derived destructor"<<endl;
}
void Derived::show(){
    cout<<m_a<<", "<<m_b<<", "<<m_c<<", "<<m_d<<", "<<m_e<<endl;
}

int main(){
    Derived obj(1, 2, 3, 4, 5);
    obj.show();
    return 0;
}

命名衝突

當兩個或多個基類中有同名的成員時,如果直接訪問該成員,就會產生命名衝突,編譯器不知道使用哪個基類的成員。這個時候需要在成員名字前面加上類名和域解析符::,以顯式地指明到底使用哪個類的成員,消除二義性。

修改上面的代碼,爲 BaseA 和 BaseB 類添加 show() 函數,並將 Derived 類的 show() 函數更名爲 display():

#include <iostream>
using namespace std;

//基類
class BaseA{
public:
    BaseA(int a, int b);
    ~BaseA();
public:
    void show();
protected:
    int m_a;
    int m_b;
};
BaseA::BaseA(int a, int b): m_a(a), m_b(b){
    cout<<"BaseA constructor"<<endl;
}
BaseA::~BaseA(){
    cout<<"BaseA destructor"<<endl;
}
void BaseA::show(){
    cout<<"m_a = "<<m_a<<endl;
    cout<<"m_b = "<<m_b<<endl;
}

//基類
class BaseB{
public:
    BaseB(int c, int d);
    ~BaseB();
    void show();
protected:
    int m_c;
    int m_d;
};
BaseB::BaseB(int c, int d): m_c(c), m_d(d){
    cout<<"BaseB constructor"<<endl;
}
BaseB::~BaseB(){
    cout<<"BaseB destructor"<<endl;
}
void BaseB::show(){
    cout<<"m_c = "<<m_c<<endl;
    cout<<"m_d = "<<m_d<<endl;
}

//派生類
class Derived: public BaseA, public BaseB{
public:
    Derived(int a, int b, int c, int d, int e);
    ~Derived();
public:
    void display();
private:
    int m_e;
};
Derived::Derived(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), m_e(e){
    cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
    cout<<"Derived destructor"<<endl;
}
void Derived::display(){
    BaseA::show();  //調用BaseA類的show()函數
    BaseB::show();  //調用BaseB類的show()函數
    cout<<"m_e = "<<m_e<<endl;
}

int main(){
    Derived obj(1, 2, 3, 4, 5);
    obj.display();
    return 0;
}

在這裏插入圖片描述

舉個栗子

//間接基類A
class A{
protected:
    int m_a;
};

//直接基類B
class B: public A{
protected:
    int m_b;
};

//直接基類C
class C: public A{
protected:
    int m_c;
};

//派生類D
class D: public B, public C{
public:
    void seta(int a){ m_a = a; }  //命名衝突
    void setb(int b){ m_b = b; }  //正確
    void setc(int c){ m_c = c; }  //正確
    void setd(int d){ m_d = d; }  //正確
private:
    int m_d;
};

int main(){
    D d;
    return 0;
}

在這裏插入圖片描述

8.虛繼承(Virtual Inheritance)

爲了解決多繼承時的命名衝突和冗餘數據問題,C++ 提出了虛繼承,使得在派生類中只保留一份間接基類的成員。

//間接基類A
class A{
protected:
    int m_a;
};

//直接基類B
class B: virtual public A{  //虛繼承
protected:
    int m_b;
};

//直接基類C
class C: virtual public A{  //虛繼承
protected:
    int m_c;
};

//派生類D
class D: public B, public C{
public:
    void seta(int a){ m_a = a; }  //正確
    void setb(int b){ m_b = b; }  //正確
    void setc(int c){ m_c = c; }  //正確
    void setd(int d){ m_d = d; }  //正確
private:
    int m_d;
};

int main(){
    D d;
    return 0;
}

這段代碼使用虛繼承重新實現了上圖所示的菱形繼承,這樣在派生類 D 中就只保留了一份成員變量 m_a,直接訪問就不會再有歧義了。

虛基類構造函數

虛繼承時構造函數的執行順序與普通繼承時不同:在最終派生類的構造函數調用列表中,不管各個構造函數出現的順序如何,編譯器總是先調用虛基類的構造函數,再按照出現的順序調用其他的構造函數;而對於普通繼承,就是按照構造函數出現的順序依次調用的。

舉個栗子

#include <iostream>
using namespace std;

//虛基類A
class A{
public:
    A(int a);
protected:
    int m_a;
};
A::A(int a): m_a(a){ }

//直接派生類B
class B: virtual public A{
public:
    B(int a, int b);
public:
    void display();
protected:
    int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
    cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl;
}

//直接派生類C
class C: virtual public A{
public:
    C(int a, int c);
public:
    void display();
protected:
    int m_c;
};
C::C(int a, int c): A(a), m_c(c){ }
void C::display(){
    cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl;
}

//間接派生類D
class D: public B, public C{
public:
    D(int a, int b, int c, int d);
public:
    void display();
private:
    int m_d;
};
D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ }
void D::display(){
    cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
}

int main(){
    B b(10, 20);
    b.display();
   
    C c(30, 40);
    c.display();

    D d(50, 60, 70, 80);
    d.display();
    return 0;
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章