繼承有三種繼承方式,public繼承、protected繼承、private繼承
基類成員的訪問權限在派生類裏必須是 <= 繼承方式的
繼承方式 | 基類的public成員 | 基類的protected成員 | 基類的private成員 |
public繼承 | 仍是public成員 | 仍是protected成員 | 被派生類繼承,但是不可見 |
protected繼承 | 變爲protected成員 | 變爲protected成員 | 被派生類繼承,但是不可見 |
private繼承 | 變爲private成員 | 變爲private成員 | 被派生類繼承,但是不可見 |
派生類會繼承基類的數據和基類的作用域,所以在派生類可以寫同名的成員變量和同名的成員方法
派生類會繼承基類構造函數、析構函數以外的所有方法
派生類可以訪問從基類繼承來的public和protected的成員
派生類對象的構造方式
- 先構造基類成員對象
- 在構造基類自己
- 構造派生類的成員對象
- 構造派生類自己
- 析構則反之
繼承中重載、隱藏、重寫
- 重載需要在同一個作用域下,函數名相同參數列表不同叫做函數重載
- 重寫/覆蓋 在基類中有虛函數,當有指針指向對象的時候,編譯時期不知道調用哪個類的方法,在運行的時候通過對象的虛函數指針訪問虛函數表就可以知道訪問哪個類裏的方法, 如圖二
- 隱藏是在不同的作用域下同名的成員方法/變量,派生類的成員方法/變量 把基類的成員方法隱藏了。如果要調用基類的成員方法需要加上基類的作用域 Base::
基類和派生類對象之間的賦值:
- 基類對象不可以賦值給派生類對象(想當於不能把人(基類)賦值給學生(派生類),可以把學生賦值給人)
- 派生類對象可以賦給基類對象
- 基類的指針(or 引用)指向派生類的對象,可以指向派生類對象但是不可以訪問派生類獨有的變量和方法,只可以訪問基類的成員方法和變量,因爲指針的類型是基類,指針解引用只能訪問派生類從基類繼承而來的方法和變量
- 派生類指針(or 引用)不可以引用基類對象(把基類對象強轉之後是可以引用的,用指針訪問的時候可能會出現問題)
成爲虛函數的兩大條件: 能取地址,還得有對象
虛函數表的地址在哪裏?? 虛函數表的產生是在編譯期就形成了,程序運行,表放在只讀數據段裏邊(生命週期是從程序開始到程序結束,在全局靜態區域)
一個類型對應一張表,相同類型定義的對象,不同的虛函數指針指向的同一張虛函數表 如圖一
圖一
圖二
動多態的代碼
/****************動多態的代碼示範****************/
#if 1
#include <iostream>
#include <typeinfo>
using namespace std;
class Base
{
public:
//提供了構造函數,編譯器就不會提供默認的構造函數了
Base(int a):ma(a){ cout << "Base()" << endl; }
~Base(){ cout << "~Base()" << endl; }
virtual void show(){cout << "Base::show()" << endl;} //基類中加上虛函數
virtual void show(int i){cout << "Base::show(int)" << endl;}
protected:
int ma;
};
class Derive : public Base
{
public:
//在初始化列表沒有調用基類的構造函數則編譯器會自動調用默認的構造函數,如果編譯器沒有提供基類的構造函數則會報錯
Derive(int data):Base(data), mb(data){ cout << "Derive()" << endl; }
~Derive(){cout << "~Derive()" << endl;}
void show(){cout << "Derive::show()" << endl;} //虛函數
private:
int mb;
};
int main()
{
Derive derive(10);
Base *p = &derive;
p -> show(); //沒有虛函數的時候 call Base::show =====> 編譯時的綁定
/*
mov eax, dword ptr[p]
mov ecx, dword ptr[eax]
call ecx ===============> 運行時的綁定 運行時的多態
*/
cout<< sizeof(Base) <<endl;
cout<< sizeof(Derive) <<endl;
cout<< typeid(p).name() <<endl;
cout<< typeid(*p).name() <<endl; //訪問的是對象RTTI類型信息
return 0;
}
#endif
派生類構造順序的代碼示範
/****************派生類對象的構造方式****************/
#if 1
#include <iostream>
#include <typeinfo>
using namespace std;
class Base
{
public:
//提供了構造函數,編譯器就不會提供默認的構造函數了
Base(int a):ma(a){
cout << "Base()" << endl;
}
~Base(){
cout << "~Base()" << endl;
}
protected:
int ma;
};
class Derive : public Base
{
public:
//在初始化列表沒有調用基類的構造函數則編譯器會自動調用默認的構造函數,如果編譯器沒有提供基類的構造函數則會報錯
Derive(int data):Base(data), mb(data){
cout << "Derive()" << endl;
}
~Derive(){cout << "~Derive()" << endl;}
private:
int mb;
};
int main()
{
//派生類對象的構造方式
/*先構造基類成員對象,在構造基類自己,構造派生類的成員對象,
構造派生類自己,析構則反之
(根據初始化列表的理解,調用構造函數先執行初始化列表再執行函數體)*/
Derive derive(10);
return 0;
}
#endif
堅持✊