大家好,這篇文章分享了C程式設計(譚浩強)第五版第六章課後題答案,所有程式已經測試能夠正常運行,如果小伙伴發現有錯誤的的地方,歡迎留言告訴我,我會及時改正!感謝大家的觀看!!! ...
這一部分內容可以直接看《C++ primer》第十五章,這裡講的基本上都是重覆的。第十五章的最後一個小節還有一個綜合性的代碼案例,包含操作符重載、繼承、多態等等。第十五章的筆記可以看我的另一篇隨筆第十五章 面向對象程式設計
繼承的基本意義
繼承的本質(好處):
-
代碼的復用;
-
在基類中給所有派生類提供統一的虛函數介面,讓派生類進行重寫,然後就能使用多態了。
類和類之間的關係:
- 組合 一部分的關係
- 繼承 一種的關係
總結:1.外部只能訪問對象public的成員,protected和private成員無法直接訪問;2、在集成結構中,派生類從基類可以繼承過來private的成員,但是派生類缺無法直接訪問;3、protected和private的區別?在基類中定義的成員,想被派生類訪問,但是不想被外部訪問,那麼就在基類中把相關成員定義為protected的;如果派生類和外部都不打算訪問,那麼在基類中就把相關成員定義為private的。
預設的繼承方式是什麼?要看派生類使用class定義的還是struct定義的。如果是class定義的派生類,預設繼承方式是private的;如果是struct定義的派生類,預設繼承方式是public的。
派生類的構造過程
派生類怎麼初始化從基類繼承來的成員變數呢?
- 派生類從基類可以繼承來所有的成員(變數和方法),但是不包含構造函數和析構函數
- 通過調用基類相應的構造函數來初始化
重載、覆蓋、隱藏
重載關係:
- 一組函數要重載必須處在同一個作用域當中,且函數名字相同,參數列表不同
在基類和派生類中的函數不能被重載,因為作用域不同。如果在基類中定義了重載函數,在派生類中可以直接調用,但是如果派生類中定義了與基類同名的函數,在調用派生類的時候只會產生派生類中的重載,不會與基類中的同名函數發生重載
隱藏關係:
- 在繼承結構中,派生類的同名成員會把基類的同名成員隱藏起來,
基類和派生類的 類型轉化
只能訪問藍色部分的記憶體,後面紅色部分的記憶體是不存在的,訪問基類指針會報記憶體非法訪問
在繼承結構中,進行上下的類型轉換,預設只支持從下到上的類型轉換。
覆蓋關係:
基類和派生類的方法、返回值、函數名以及參數列表都相同,而且基類的方法是虛函數,那麼派生類的方法就自動處理成虛函數,他們之間稱為覆蓋關係。
虛函數、靜態綁定和動態綁定
虛函數virtual。RTTI(run-time type information)運行時的類型信息。
總結1.如果類裡面定義了虛函數,編譯階段編譯器給這個類類型產生一個唯一的vftable虛函數表,虛函數表中主要存儲的內容就是RTTI指針和虛函數的地址。當程式運行時,每一張虛函數表都會載入到記憶體的.rodata區。
總結2.一個類里定義了虛函數,那麼這個類定義的對象運行時,記憶體中的開始部分會多存儲一個vfptr虛函數指針,指向相應類型的虛函數表vftable。一個的類型定義的n個對象,他們的vfptr指向的都是同一張虛函數表。
在這個例子里對象的大小是8個位元組
總結3.一個類裡面虛函數的個數不影響對象記憶體大小(Vfptr),影響的是虛函數表的大小
class Base{
public:
Base(int data=10): ma(data){}
//虛函數 virtual
virtual void show(){ cout<<"Base::show()"<<endl;}
virtual void show(int)(cout<<"Base::show(int)"<<endl;)
protected:
int ma;
};
總結4.如果派生類中的方法和基類繼承來的某個方法,返回值、函數名、參數列表都相同,而且基類的方法是virtual虛函數,那麼派生類的這個方法會自動處理成虛函數。
派生類中的虛函數表,如果派生類中有與基類中重覆的函數,會在派生類的虛函數表中覆蓋原來基類中的方法地址。
類中存在虛函數就會發生動態綁定。
靜態綁定在彙編代碼中會call具體的方法地址,而動態綁定call一個寄存器,寄存器會在運行時找到虛函數表中相應的方法的地址。
虛析構函數
問題1:那些函數不能實現成虛函數?
-
虛函數依賴:
- 虛函數能產生地址,存儲在vftable中
- 對象必須存在(vfptr->vftable->虛函數地址)對象存在才有vfptr,才能找到vftable,才有虛函數地址
-
構造函數不能成為虛函數。構造函數中調用虛函數,不會發生動態綁定。構造函數調用的任何函數都是靜態綁定的
-
靜態成員方法也不能實現成虛函數
問題2:虛析構函數
- 析構函數調用的時候對象是存在的。
- 基類的析構函數是虛析構函數,派生類的析構函數會自動定義為虛析構函數
什麼時候必須把基類的析構函數必須實現成虛函數?
基類的指針(引用)指向堆上new出來的派生類對象的時候,delete pb(基類的指針),他調用析構函數的時候,必鬚髮生動態綁定,否則會導致派生類的析構函數無法調用
為什麼要使用虛析構函數?
解釋這個問題使用的代碼:
//
// Created by 26685 on 2022-05-17 19:41.
// Description:ClassDerive.h 學習繼承和多態
//
#ifndef C___CLASSDERIVE_H
#define C___CLASSDERIVE_H
#include <iostream>
using namespace std;
class Base{
public:
Base(int data=10):ma(data){
cout<<"Base()"<<endl;
}
~Base(){
cout<<"~Base()"<<endl;
}
virtual void show(){
cout<<"Base::show()"<<endl;
}
private:
int ma;
};
class Derive:public Base{
public:
Derive(int data=10): Base(data),mb(data),ptr(new int(data)){
cout<<"Derive()"<<endl;
}
~Derive(){
cout<<"~Derive()"<<endl;
}
void show() override{
cout<<"Derive::show()"<<endl;
}
private:
int mb;
int* ptr;//會指向額外的空間,必須由析構函數釋放
};
#endif //C___CLASSDERIVE_H
如果在堆上開闢記憶體存放派生類會發生不調用派生類的析構函數的情況,代碼如下:
int main(){
Base *pb=new Derive;
pb->show();
delete pb;
return 0;
}
輸出的結果只有基類的析構函數:
這裡的pb是Base類型,會去Base類中找析構函數,此時的綁定就是靜態綁定。
基類的析構函數如果是虛函數,派生類中的析構函數會自動生成為虛析構函數。所以要將析構函數定義為虛析構函數。
virtual Base::~Base(){
cout<<"~Base()"<<endl;
}
Derive::~Derive(){
cout<<"~Derive()"<<endl;
delete ptr;
}
此時就會正確的析構:
再談虛函數和動態綁定
問題:是不是虛函數的調用一定就是動態綁定? 答:不是的
在類的構造函數當中調用虛函數是靜態綁定。
用對象本身調用虛函數是靜態綁定
動態綁定發生在指針調用虛函數的情況下:
理解多態是什麼
面試中常見的問題:如何解釋多態
動態的多態:
靜態的多態:
函數的重載
理解抽象類
擁有純虛函數的類叫做抽象類,抽象類不能再實例化對象,但是可以定義指針和引用變數
virtual void 函數名()=0;
定義純虛函數
一般把什麼類設計為抽象類?
基類