第 13.5 節

繼承

0瀏覽次數0訪問次數--跳出率--平均停留

繼承

繼承是面向對象三大特性之一

有些類與類之間存在特殊的關係,例如下圖中:

1544861202252

我們發現,定義這些類時,下級別的成員除了擁有上一級的共性,還有自己的特性。

這個時候我們就可以考慮利用繼承的技術,減少重複代碼

繼承的基本語法

例如我們看到很多網站中,都有公共的頭部,公共的底部,甚至公共的左側列表,只有中心內容不同

接下來我們分別利用普通寫法和繼承的寫法來實現網頁中的內容,看一下繼承存在的意義以及好處

普通實現:

//Java页面
class Java 
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java,Python,C++...(公共分类列表)" << endl;
    }
    void content()
    {
        cout << "JAVA学科视频" << endl;
    }
};
//Python页面
class Python
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java,Python,C++...(公共分类列表)" << endl;
    }
    void content()
    {
        cout << "Python学科视频" << endl;
    }
};
//C++页面
class CPP 
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java,Python,C++...(公共分类列表)" << endl;
    }
    void content()
    {
        cout << "C++学科视频" << endl;
    }
};

void test01()
{
    //Java页面
    cout << "Java下载视频页面如下: " << endl;
    Java ja;
    ja.header();
    ja.footer();
    ja.left();
    ja.content();
    cout << "--------------------" << endl;

    //Python页面
    cout << "Python下载视频页面如下: " << endl;
    Python py;
    py.header();
    py.footer();
    py.left();
    py.content();
    cout << "--------------------" << endl;

    //C++页面
    cout << "C++下载视频页面如下: " << endl;
    CPP cp;
    cp.header();
    cp.footer();
    cp.left();
    cp.content();

}

int main() {

    test01();


    return 0;
}

運行/觀察結果: 運行後會分別打印 Java、Python、C++ 三個下載頁面的公共頭部、公共底部、公共分類和各自內容。

繼承實現:

//公共页面
class BasePage
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }

    void footer()
    {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java,Python,C++...(公共分类列表)" << endl;
    }

};

//Java页面
class Java : public BasePage
{
public:
    void content()
    {
        cout << "JAVA学科视频" << endl;
    }
};
//Python页面
class Python : public BasePage
{
public:
    void content()
    {
        cout << "Python学科视频" << endl;
    }
};
//C++页面
class CPP : public BasePage
{
public:
    void content()
    {
        cout << "C++学科视频" << endl;
    }
};

void test01()
{
    //Java页面
    cout << "Java下载视频页面如下: " << endl;
    Java ja;
    ja.header();
    ja.footer();
    ja.left();
    ja.content();
    cout << "--------------------" << endl;

    //Python页面
    cout << "Python下载视频页面如下: " << endl;
    Python py;
    py.header();
    py.footer();
    py.left();
    py.content();
    cout << "--------------------" << endl;

    //C++页面
    cout << "C++下载视频页面如下: " << endl;
    CPP cp;
    cp.header();
    cp.footer();
    cp.left();
    cp.content();

}

int main() {

    test01();


    return 0;
}

運行/觀察結果: 運行後輸出與上一版頁面示例相同,但公共部分改由基類複用,重點觀察繼承帶來的重複代碼減少。

總結:

繼承的好處:==可以減少重複的代碼==

class A : public B;

A 類稱爲子類 或 派生類

B 類稱爲父類 或 基類

派生類中的成員,包含兩大部分

一類是從基類繼承過來的,一類是自己增加的成員。

從基類繼承過過來的表現其共性,而新增的成員體現了其個性。

繼承方式

繼承的語法:class 子类 : 继承方式 父类

繼承方式一共有三種:

  • 公共繼承
  • 保護繼承
  • 私有繼承

img

示例:

class Base1
{
public: 
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

//公共继承
class Son1 :public Base1
{
public:
    void func()
    {
        m_A; //可访问 public权限
        m_B; //可访问 protected权限
        //m_C; //不可访问
    }
};

void myClass()
{
    Son1 s1;
    s1.m_A; //其他类只能访问到公共权限
}

//保护继承
class Base2
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};
class Son2:protected Base2
{
public:
    void func()
    {
        m_A; //可访问 protected权限
        m_B; //可访问 protected权限
        //m_C; //不可访问
    }
};
void myClass2()
{
    Son2 s;
    //s.m_A; //不可访问
}

//私有继承
class Base3
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};
class Son3:private Base3
{
public:
    void func()
    {
        m_A; //可访问 private权限
        m_B; //可访问 private权限
        //m_C; //不可访问
    }
};
class GrandSon3 :public Son3
{
public:
    void func()
    {
        //Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
        //m_A;
        //m_B;
        //m_C;
    }
};

運行/觀察結果: 這段偏語法定義,通常需要配合調用代碼一起編譯,重點看定義方式和使用位置。

繼承中的對象模型

**問題:**從父類繼承過來的成員,哪些屬於子類對象中?

示例:

class Base
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};

//公共继承
class Son :public Base
{
public:
    int m_D;
};

void test01()
{
    cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {

    test01();


    return 0;
}

運行/觀察結果: 運行後會按輸出語句打印對應內容,變量值可結合初始化、賦值和函數調用順序推導。

利用工具查看:

1545881904150

打開工具窗口後,定位到當前CPP文件的盤符

然後輸入: cl /d1 reportSingleClassLayout查看的類名 所屬文件名

效果如下圖:

1545882158050

結論: 父類中私有成員也是被子類繼承下去了,只是由編譯器給隱藏後訪問不到

繼承中構造和析構順序

子類繼承父類後,當創建子類對象,也會調用父類的構造函數

問題:父類和子類的構造和析構順序是誰先誰後?

示例:

class Base 
{
public:
    Base()
    {
        cout << "Base构造函数!" << endl;
    }
    ~Base()
    {
        cout << "Base析构函数!" << endl;
    }
};

class Son : public Base
{
public:
    Son()
    {
        cout << "Son构造函数!" << endl;
    }
    ~Son()
    {
        cout << "Son析构函数!" << endl;
    }

};

void test01()
{
    //继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
    Son s;
}

int main() {

    test01();


    return 0;
}

運行/觀察結果: 運行後會按輸出語句打印對應內容,變量值可結合初始化、賦值和函數調用順序推導。

總結:繼承中 先調用父類構造函數,再調用子類構造函數,析構順序與構造相反

繼承同名成員處理方式

問題:當子類與父類出現同名的成員,如何通過子類對象,訪問到子類或父類中同名的數據呢?

  • 訪問子類同名成員 直接訪問即可
  • 訪問父類同名成員 需要加作用域

示例:

class Base {
public:
    Base()
    {
        m_A = 100;
    }

    void func()
    {
        cout << "Base - func()调用" << endl;
    }

    void func(int a)
    {
        cout << "Base - func(int a)调用" << endl;
    }

public:
    int m_A;
};

class Son : public Base {
public:
    Son()
    {
        m_A = 200;
    }

    //当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
    //如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
    void func()
    {
        cout << "Son - func()调用" << endl;
    }
public:
    int m_A;
};

void test01()
{
    Son s;

    cout << "Son下的m_A = " << s.m_A << endl;
    cout << "Base下的m_A = " << s.Base::m_A << endl;

    s.func();
    s.Base::func();
    s.Base::func(10);

}
int main() {

    test01();

    return EXIT_SUCCESS;
}

運行/觀察結果: 運行後會按輸出語句打印對應內容,變量值可結合初始化、賦值和函數調用順序推導。

總結:

  1. 子類對象可以直接訪問到子類中同名成員
  2. 子類對象加作用域可以訪問到父類同名成員
  3. 當子類與父類擁有同名的成員函數,子類會隱藏父類中同名成員函數,加作用域可以訪問到父類中同名函數

繼承同名靜態成員處理方式

問題:繼承中同名的靜態成員在子類對象上如何進行訪問?

靜態成員和非靜態成員出現同名,處理方式一致

  • 訪問子類同名成員 直接訪問即可
  • 訪問父類同名成員 需要加作用域

示例:

class Base {
public:
    static void func()
    {
        cout << "Base - static void func()" << endl;
    }
    static void func(int a)
    {
        cout << "Base - static void func(int a)" << endl;
    }

    static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
public:
    static void func()
    {
        cout << "Son - static void func()" << endl;
    }
    static int m_A;
};

int Son::m_A = 200;

//同名成员属性
void test01()
{
    //通过对象访问
    cout << "通过对象访问: " << endl;
    Son s;
    cout << "Son  下 m_A = " << s.m_A << endl;
    cout << "Base 下 m_A = " << s.Base::m_A << endl;

    //通过类名访问
    cout << "通过类名访问: " << endl;
    cout << "Son  下 m_A = " << Son::m_A << endl;
    cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}

//同名成员函数
void test02()
{
    //通过对象访问
    cout << "通过对象访问: " << endl;
    Son s;
    s.func();
    s.Base::func();

    cout << "通过类名访问: " << endl;
    Son::func();
    Son::Base::func();
    //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
    Son::Base::func(100);
}
int main() {

    //test01();
    test02();


    return 0;
}

運行/觀察結果: 運行後會按輸出語句打印對應內容,變量值可結合初始化、賦值和函數調用順序推導。

總結:同名靜態成員處理方式和非靜態處理方式一樣,只不過有兩種訪問的方式(通過對象 和 通過類名)

多繼承語法

C++允許一個類繼承多個類

語法: class 子类 :继承方式 父类1 , 继承方式 父类2...

多繼承可能會引發父類中有同名成員出現,需要加作用域區分

C++實際開發中不建議用多繼承

示例:

class Base1 {
public:
    Base1()
    {
        m_A = 100;
    }
public:
    int m_A;
};

class Base2 {
public:
    Base2()
    {
        m_A = 200;  //开始是m_B 不会出问题,但是改为mA就会出现不明确
    }
public:
    int m_A;
};

//语法:class 子类:继承方式 父类1 ,继承方式 父类2 
class Son : public Base2, public Base1 
{
public:
    Son()
    {
        m_C = 300;
        m_D = 400;
    }
public:
    int m_C;
    int m_D;
};

//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
    Son s;
    cout << "sizeof Son = " << sizeof(s) << endl;
    cout << s.Base1::m_A << endl;
    cout << s.Base2::m_A << endl;
}

int main() {

    test01();


    return 0;
}

運行/觀察結果: 運行後會按輸出語句打印對應內容,變量值可結合初始化、賦值和函數調用順序推導。

總結: 多繼承中如果父類中出現了同名情況,子類使用時候要加作用域

菱形繼承

菱形繼承概念:

​ 兩個派生類繼承同一個基類

​ 又有某個類同時繼承者兩個派生類

​ 這種繼承被稱爲菱形繼承,或者鑽石繼承

典型的菱形繼承案例:

IMG_256

菱形繼承問題:

  1. 羊繼承了動物的數據,駝同樣繼承了動物的數據,當草泥馬使用數據時,就會產生二義性。
    
  2. 草泥馬繼承自動物的數據繼承了兩份,其實我們應該清楚,這份數據我們只需要一份就可以。

示例:

class Animal
{
public:
    int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

void test01()
{
    SheepTuo st;
    st.Sheep::m_Age = 100;
    st.Tuo::m_Age = 200;

    cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
    cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
    cout << "st.m_Age = " << st.m_Age << endl;
}

int main() {

    test01();


    return 0;
}

運行/觀察結果: 運行後會按輸出語句打印對應內容,變量值可結合初始化、賦值和函數調用順序推導。

總結:

  • 菱形繼承帶來的主要問題是子類繼承兩份相同的數據,導致資源浪費以及毫無意義
  • 利用虛繼承可以解決菱形繼承問題
音乐页