超詳細的繼承和派生講解

1、繼承中的構造和析構的順序

在這裏插入圖片描述
#千鋒教育#
學習必須如蜜蜂一樣,採過許多花,這才能釀出蜜來。

class Base
{
public:
    Base()
    {
        cout<<"父類的無參構造函數"<<endl;
    }
    ~Base()
    {
        cout<<"父類中的析構函數"<<endl;
    }
};
class Son:public Base
{
public:
    Son()
    {
        cout<<"子類的無參構造"<<endl;
    }
    ~Son()
    {
        cout<<"子類中的析構函數"<<endl;
    }
};
void test01()
{
    Son ob1;
}

運行結果:
在這裏插入圖片描述

總結:

構造順序: 父類(基類)構造 ------> 子類(派生類)構造
析構順序:子類(派生類)析構------> 父類 (基類) 析構
在這裏插入圖片描述
在這裏插入圖片描述

2、子類中 有父類、對象成員 構造和析構的順序

父類的構造和析構 對象成員的構造和析構 子類自身的構造和析構

class Other
{
public:
    Other()
    {
        cout<<"對象成員的構造函數"<<endl;
    }
    ~Other()
    {
        cout<<"對象成員的析構函數"<<endl;
    }
};
class Base
{
public:
    Base()
    {
        cout<<"父類的無參構造函數"<<endl;
    }
    ~Base()
    {
        cout<<"父類中的析構函數"<<endl;
    }
};
class Son:public Base
{
public:
    Son()
    {
        cout<<"子類的無參構造"<<endl;
    }
    ~Son()
    {
        cout<<"子類中的析構函數"<<endl;
    }

    Other ob;//對象成員

};
void test01()
{
    Son ob1;
}

運行結果:
在這裏插入圖片描述

總結:(重要)

在這裏插入圖片描述

3、詳解 子類中的構造

1、子類會默認調用  父類的 無參構造
2、子類 必須顯示 使用初始化列表  調用 父類的有參構造

調用形式:父類名稱。

Son(int a,int b):Base(a),b(b)
{
    //this->b = b;
}
class Base
{
private:
    int a;
public:

    Base()
    {
        cout<<"父類的無參構造函數"<<endl;
    }
    Base(int a)
    {
        this->a = a;
        cout<<"父類的有參構造函數"<<endl;
    }
    ~Base()
    {
        cout<<"父類中的析構函數"<<endl;
    }
};
class Son:public Base
{
private:
    int b;
public:
    Son()
    {
        cout<<"子類的無參構造"<<endl;
    }
    Son(int b)
    {
        this->b = b;
        cout<<"子類的有參構造函數int"<<endl;
    }

    //子類必須用 初始化列表 顯示的調用父類的有參構造
    //父類名稱(參數)
    Son(int a,int b):Base(a)//顯示的調用父類的有參構造
    {
        this->b = b;
        cout<<"子類的有參構造函數 int int"<<endl;
    }
    ~Son()
    {
        cout<<"子類中的析構函數"<<endl;
    }
};
void test01()
{
    //子類 默認 會調用 父類的無參構造
    //Son ob1(10);

    //子類必須用 初始化列表 顯示的調用父類的有參構造
    //父類名稱+()
    Son ob2(10,20);

}

運行結果:
在這裏插入圖片描述

案例提高:

如果父類有參構造:

 Base(int a, int data)
{
    this->a = a;
    this->data = data;
    cout<<"父類的有參構造函數"<<endl;
}

子類想調用 父類有參構造:

//子類必須用 初始化列表 顯示的調用父類的有參構造
//父類名稱(參數)
Son(int a,int b, int c):Base(a,c),b(b)//顯示的調用父類的有參構造
{
    //this->b = b;
    cout<<"子類的有參構造函數 int int"<<endl;
}

4、父類和子類的同名 成員變量 處理

4.1、當 父類和子類 成員變量同名時 在子類就近原則 選擇本作用域的子類成員

4.2、如果在子類中 必須使用父類中的同名成員 必須加上父類的作用域。

class Base
{
    //父類的私有數據 一旦涉及繼承 在子類中不可見
public:
    int num;
public:
    Base(int num)
    {
        this->num = num;
        cout<<"Base有參構造int"<<endl;
    }
    ~Base()
    {
        cout<<"析構函數"<<endl;
    }
};

class Son:public Base
{
private:
    int num;
public:
    Son(int num1,int num2):Base(num1)
    {
        this->num = num2;
        cout<<"有參構造int int"<<endl;
    }

    ~Son()
    {
        cout<<"析構函數"<<endl;
    }
    void showNum(void)
    {
        //如果在子類中 必須使用父類中的同名成員  必須加上父類的作用域
        cout<<"父類中的num = "<<Base::num<<endl;

        //當 父類和子類 成員變量同名時  在子類就近原則 選擇本作用域的子類成員
        cout<<"子類中的num = "<<num<<endl;
    }
};

void test01()
{
    Son ob1(10,20);
    ob1.showNum();
}

運行結果:
在這裏插入圖片描述

4.3、子類可以藉助 父類的公有方法 間接的操作 父類的私有數據(不可見的數據)

class Base
{

private:
    int num;//父類的私有數據 一旦涉及繼承 在子類中不可見
public:
    Base(int num)
    {
        this->num = num;
        cout<<"Base有參構造int"<<endl;
    }
    ~Base()
    {
        cout<<"析構函數"<<endl;
    }
    int getNum(void)
    {
        return num;
    }
};

class Son:public Base
{
private:
    int num;
public:
    Son(int num1,int num2):Base(num1)
    {
        this->num = num2;
        cout<<"有參構造int int"<<endl;
    }

    ~Son()
    {
        cout<<"析構函數"<<endl;
    }
    void showNum(void)
    {
        //如果在子類中 必須使用父類中的同名成員  必須加上父類的作用域
        cout<<"父類中的num = "<<getNum()<<endl;

        //當 父類和子類 成員變量同名時  在子類就近原則 選擇本作用域的子類成員
        cout<<"子類中的num = "<<num<<endl;
    }
};

void test01()
{
    Son ob1(10,20);
    ob1.showNum();
}

運行結果:
在這裏插入圖片描述

5、父類和子類的同名 成員函數 處理

案例:1子類繼承父類所有成員函數 和成員變量

class Base
{
public:
    void func(void)
    {
        cout<<"父類中的void func"<<endl;
    }
    void func(int a)
    {
        cout<<"父類中的int func a = "<<a<<endl;
    }
};

class Son:public Base
{
public:
    
};

void test01()
{
    //爲啥構造和析構除外?父類的構造和析構 只有父類自己知道該怎麼做(構造和析構 系統自動調用)
    //子類會繼承父類所有成員函數(構造和析構函數除外) 和成員變量
    Son ob1;
    ob1.func();//訪問的是父類的void func(void)
    ob1.func(10);//訪問的是父類的func(int a)
}

案例:2子類和父類 同名成員函數

class Base
{
public:
    void func(void)
    {
        cout<<"父類中的void func"<<endl;
    }
    void func(int a)
    {
        cout<<"父類中的int func a = "<<a<<endl;
    }
};

class Son:public Base
{
public:
    //一旦子類 實現了 父類的同名成員函數 將屏蔽所有父類同名成員函數
    void func(void)
    {
        cout<<"子類中voidfunc"<<endl;
    }
};

void test01()
{
    //爲啥構造和析構除外?父類的構造和析構 只有父類自己知道該怎麼做(構造和析構 系統自動調用)
    //子類會繼承父類所有成員函數(構造和析構函數除外) 和成員變量
    Son ob1;
    ob1.func();
    //ob1.func(10);//err //一旦子類 實現了 父類的同名成員函數 將屏蔽所有父類同名成員函數 

    //如果用戶 必須要調用父類 的同名成員函數 必須加作用域
    ob1.Base::func();//調用父類的void func
    ob1.Base::func(10);//調用父類的int func
}
int main(int argc, char *argv[])
{
    test01();
    return 0;
}

運行結果:
在這裏插入圖片描述

6、繼承中的靜態成員特性(瞭解)

class Base
{
public:
    //靜態成員屬於類 而不屬於對象
    static int num;
    static int data;

    static void showData(void);

};
int Base::num  = 100;
int Base::data = 200;

class Son:public Base
{
public:
    static int data;//父和子類 靜態成員 同名
    static void showData(void);
};
int Son::data = 300;

void test01()
{
    //從Base類中訪問
    cout<<Base::num<<endl;

    // Son 也擁有了靜態成員num
    cout<<Son::num<<endl;

    //父和子類 靜態成員 同名 在子類中 訪問子類中的成員
    cout<<Son::data<<endl;//200

    //父和子類 靜態成員 同名 訪問父類中的成員 必須加 Base::
    cout<<Son::Base::data<<endl;//200


    //父和子類 同名靜態成員函數 子類默認訪問子類的靜態成員函數
    Son::showData();
    //父和子類 同名靜態成員函數 子類訪問父類的靜態成員函數 必須加 Base::
    Son::Base::showData();
}

運行結果:
在這裏插入圖片描述

7、多繼承(瞭解)

7.1、多繼承的格式:

class 子類: 繼承方式1 父類名1,繼承方式2 父類名2,繼承方式3 父類名3,....
{

};
//表示子類 是由 父類名1,父類名2,父類名3...共同派生出來
class Base1
{
public:
    int a;
};
class Base2
{
public:
    int b;
};

class Son:public Base1,public Base2
{
    //Son類 擁有了a  b
};
int main(int argc, char *argv[])
{
    Son ob;
    ob.a = 100;
    ob.b = 200;
    return 0;
}

7.2、多繼承容易產生二義性: (解決辦法1 使用作用域)

class Base1
{
public:
    int a;
};
class Base2
{
public:
    int a;
};

class Son:public Base1,public Base2
{

};
int main(int argc, char *argv[])
{
    Son ob;
    //ob.a = 100;//err Base1 和 Base2中都有a成員同名
    //解決辦法:加作用域
    ob.Base1::a = 100;
    ob.Base2::a = 200;
    return 0;
}

7.3、菱形繼承(具有公共祖先 的多繼承)

class Animal
{
public:
    int data;
};

class Sheep:public Animal
{
public:
};
class Tuo:public Animal
{
public:
};

class SheepTuo:public Sheep,public Tuo
{
public:
};
int main(int argc, char *argv[])
{
    SheepTuo st;
    //SheepTuo 從Sheep中繼承data  從Tuo繼承data 就產生二義性
    //st.data = 200;//err
    //第一中方式:加作用域解決
    st.Sheep::data = 200;
    st.Tuo::data = 300;

    return 0;
}

8、普通繼承:vs studio分析

class Animal
{
public:
    int data;
};

在這裏插入圖片描述

class Sheep:public Animal
{
public:
};

在這裏插入圖片描述

class Tuo:public Animal
{
public:
};

在這裏插入圖片描述

class SheepTuo:public Sheep,public Tuo
{
public:
};

在這裏插入圖片描述

9、虛繼承(瞭解) vs studio分析

virtual修飾繼承方式
//繼承的動作 虛繼承
//父類:虛基類
class 子類:virtual public 父類
{
};

虛繼承:

class Animal
{
public:
    int data;
};

在這裏插入圖片描述

class Sheep:virtual public Animal
{
public:
};

在這裏插入圖片描述
vbptr(虛基類指針) 其中v是virtual 虛 b是base 基類 prt指針
(vbptr指向虛基類表)
vbtable(虛基類表 ) 保存了當前的虛指針相對於虛基類的首地址的偏移量

class Tuo:virtual public Animal
{
public:
};

在這裏插入圖片描述
總結:之所以 產生 vbptr和vbtable 目的 保證 不管多少個繼承 虛基類的數據只有一份。

class SheepTuo:public Sheep,public Tuo
{
public:
};

在這裏插入圖片描述
在這裏插入圖片描述

案例1:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<string.h>
using namespace std;

class Animal
{
public:
    int data;
};

class Sheep :virtual public Animal
{
public:
};
class Tuo :virtual public Animal
{
public:
};

class SheepTuo :public Sheep, public Tuo
{
public:
};
int main(int argc, char* argv[])
{
    SheepTuo st;
    st.data = 200;
    
    //通過Sheep的vbptr 尋找vbptr距離虛基類首地址的偏移量 
    //&st == vbptr
    //*(int *)&st sheep 的虛基類表的起始位置
    int off_set = (int)*((int*)(*(int*)&st) + 1);
    cout << off_set << endl;

    //通過sheep的vbptr 和 off_set定位虛基類的首地址
    cout << ((Animal*)((char*)&st + off_set))->data << endl;

    return 0;
}

注意:vsstudio中運行
在這裏插入圖片描述
注意: 虛繼承只能解決具備公共祖先的多繼承所帶來的二義性問題,不能解決沒有公共祖先的多繼承的。
虛繼承:不管繼承多少次 虛基類 只有一份數據。

各位看官,感覺我寫的怎麼樣呢, 大家有沒有看懂呢, 歡迎評論
如果對大家有幫助,不要忘記點個贊哦.

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