文章目錄
前言
由於在CSDN上有時無法看到markdown的目錄,有需要額小夥伴可以聯繫我,附送本文的 .md文件,可以再本地typora上更加方便的學習
這篇文章和 彙編入門基礎(點此鏈接) 爲想結合內容,請大家在學習時可以同時參考
1. 內存管理(4張內存圖-非常重要)
1.1 類中的成員變量,不需要主動回收
類中的成員變量,隨着類的對象銷燬,一同銷燬
#include <iostream>
using namespace std;
struct Person {
int m_age;
//用來作一些初始化的工作
Person() {
m_age = 0;
cout << "Person::Person()" << endl;
}
//用來做內存清理的工作
~Person() {
cout << "Person::~Person()" << endl;
}
};
int main() {
{
Person person;
}
return 0;
}
輸出:
Person::Person()
Person::~Person()
結論:成員變量m_age 不需要在析構函數中去進行主動回收( delete… )
1.2 內存泄露
該釋放的內存,並沒有得到釋放
如下代碼:
在類的內部,析構函數中進行銷燬其他對象堆空間的操作
#include <iostream>
using namespace std;
struct Car {
int m_price;
Car() {
cout << "Car::Car()" << endl;
}
~Car() {
cout << "Car::~Car()" << endl;
}
};
struct Person {
int m_age;
Car* m_car;
//用來作一些初始化的工作
Person() {
m_age = 0;
m_car = new Car();
cout << "Person::Person()" << endl;
}
//用來做內存清理的工作
~Person() {
delete m_car;
cout << "Person::~Person()" << endl;
}
};
int main() {
{
Person person;
}
return 0;
}
輸出:
Car::Car()
Person::Person()
Car::~Car()
Person::~Person()
上面代碼分析:
1) 此時 person 對象存儲在棧空間
且佔8個字節(x86環境)前四個是m_age,後四個是 car 指針
隨着函數大括號(“}”)的執行結束,person對象將被回收
爲什麼還要再回收一次 car ?
int main() {
{
Person person;
}
return 0;
}
2)因爲在 person 對象回收後,car 對象的指針被回收,而不是 car 指向的堆空間被回收 只有在 person 的析構函數中進行 delete car 操作,才能回收 car的堆空間
struct Person {
int m_age;
Car* m_car;
//用來作一些初始化的工作
Person() {
m_age = 0;
m_car = new Car();
cout << "Person::Person()" << endl;
}
//用來做內存清理的工作
~Person() {
delete m_car;
cout << "Person::~Person()" << endl;
}
};
1.3 創建對象(棧),對象內部申請棧空間 ☆
對象內部申請的棧空間,隨對象一起回收
1.4 創建對象(棧),對象內部申請堆空間 ☆
對象內部申請的堆空間,由對象內部回收
1.5 創建指針與對象(棧-堆),對象內部申請棧空間 ☆
1.6 創建指針與對象(棧-堆),對象內部申請堆空間 ☆
2. 聲明和實現分離
2.1 整體寫法
# include <iostream>
using namespace std;
// 寫在一起
class Person {
private :
int m_age;
public:
void setAge(int age) {
m_age = age;
}
int getAge() {
return m_age;
}
Person() {
m_age = 0;
}
~Person() {
}
};
int main() {
Person person;
return 0;
}
2.2 聲明和實現分離
# include <iostream>
using namespace std;
//分離寫法
class Person {
private:
int m_age;
public:
void setAge(int age);
int getAge();
Person();
~Person();
};
// 全局函數
void setAge(int age) {
}
// Person 實現
void Person::setAge(int age) {
m_age = age;
}
int Person::getAge() {
return m_age;
}
Person::Person() {
m_age = 0;
}
Person::~Person() {
}
int main() {
Person person;
return 0;
}
注意上面代碼有一個干擾就是
// 全局函數
void setAge(int age) {
}
這個沒在方法前面通過倆個冒號聲明的,爲正常的全局方法(函數)
2.3 文件分離
2.3.1 創建一個類文件
2.3.2 實現分離
2.4 頭文件.h
2.4.1 引用系統頭文件要用<>
2.4.2 引用自己創建頭文件要用 " "
3. 命名空間
訪問權限的改變從彙編層面無法得知
C++的訪問權限完全是由編譯器來識別編譯的
3.1 命名空間的調用
命名空間可以用來避免命名衝突
如下:2個Person 類
#include <iostream>
using namespace std;
namespace WL {
class Person {
int m_age;
int m_money;
};
}
class Person {
int m_height;
};
int main() {
Person person;
cout << sizeof(person) << endl;
WL::Person person2;
cout << sizeof(person2) << endl;
return 0;
}
3.2 命名空間不影響內存佈局
全局函數、變量在該命名空間下,仍爲全局
#include <iostream>
using namespace std;
namespace WL {
// 全局變量
int g_age;
//全局函數
void func() {
}
class Person {
int m_age;
int m_money;
};
}
class Person {
int m_height;
};
int main() {
WL::g_age = 10;
cout << WL::g_age << endl;
return 0;
}
3.3 命名空間作用域
3.4 using namespace std;
std::cout
std::endl
3.5 命名的二義性
同時引入命名空間下,相同變量名稱,容易二義性
#include <iostream>
using namespace std;
namespace WL {
int g_age;
}
namespace LW {
int g_age;
}
int main() {
using namespace WL;
using namespace LW;
WL::g_age = 10;
LW::g_age = 10;
return 0;
}
3.6 C++默認全局命名空間
在命名空間作用域下,訪問無命名空間的全局函數 func()導致二義性
3.6.1 默認全局命名空間 ::
有個默認的全局命名空間,我們創建的命名空間默認都嵌套在它裏面 (::就是默認全局命名空間)
#include <iostream>
using namespace std;
namespace WL {
void func() {
cout << "WL::func()" << endl;
}
}
void func() {
cout << "func()" << endl;
}
int main() {
using namespace WL;
::WL::func();
//默認全局默認空間
::func();
return 0;
}
輸出:
WL::func()
func()
3.7 命名空間的嵌套
#include <iostream>
using namespace std;
namespace WL {
namespace LW {
int g_age;
}
}
int main() {
WL::LW::g_age = 10;
using namespace WL::LW;
g_age = 20;
//指定引用
using WL::LW::g_age;
g_age = 30;
return 0;
}
3.7.1 按需引用
using WL::LW::g_age;
只引用命名空間下的 g_age 屬性
3.8 命名空間的合併
namespace WL {
int g_age;
}
namespace LW {
int g_hright;
}
等價於
namespace WL {
int g_age;
int g_hright;
}
在.h頭文件和.cpp文件 使用命名空間
4. 繼承
繼承:可以讓子類擁有父類的所有成員(變量、函數)
寫法:一個冒號 :
4.1 繼承的本質
將父類的成員變量和函數拿到子類中,且父類在前
struct Person{
int m_age;
void run() { }
};
struct Student:Person{
int m_no;
void study() { }
};
此時就等於在 Student 類中定義
struct Student {
int m_age;
int m_no;
void run() { }
void study() { }
};
如下代碼:
#include <iostream>
using namespace std;
struct Person{
int m_age;
void run() {
cout << "Person::run()" << endl;
}
};
struct Student:Person{
int m_no;
void study() {
cout << "Student::study()" << endl;
}
};
int main() {
Student student;
student.m_age = 20;
student.m_no = 1;
student.run();
student.study();
return 0;
}
Student 是子類(subclass,派生類 )
Person 是父類(superclass,超類 )
4.2 C++中沒有基類
C++中沒有像java、objective-C的基類
Java:java.lang.Object
Objective-C:NSObject
4.3 繼承中對象的內存佈局
4.3.1 結論:父類的成員變量在前,子類的成員變量在後
#include <iostream>
using namespace std;
struct Person{
int m_age;
};
struct Student:Person {
int m_no;
};
struct GoodStudent :Student {
int m_money;
};
int main() {
GoodStudent gs;
gs.m_age = 10;
gs.m_no = 1;
gs.m_money = 600;
return 0;
}
GoodStudent 對象在內存中的分佈,佔12個字節,父類的成員變量在前
cout << sizeof(gs) << endl;
輸出 :12
4.3.2 人爲的繼承導致內存的浪費
使用了繼承,但是創建子類的對象,沒有全部使用繼承的父類的成員變量
gs.m_no = 1;
gs.m_money = 600;
如:gs.m_age 並沒有調用
#include <iostream>
using namespace std;
struct Person{
int m_age;
};
struct Student:Person {
int m_no;
};
struct GoodStudent :Student {
int m_money;
};
int main() {
GoodStudent gs;
gs.m_no = 1;
gs.m_money = 600;
return 0;
}
4.4 父類的構造函數
4.4.1 子類的構造函數默認會調用父類的無參構造函數(且在子類之前)
如下代碼:
#include <iostream>
using namespace std;
struct Person {
int m_age;
Person() {
cout << "Person:;Person()" << endl;
}
Person(int age) {
cout << "Person:;Person(int age)" << endl;
}
};
struct Student : public Person {
Student() {
cout << "Student:;Student()" << endl;
}
};
int main() {
Student student;
return 0;
}
可以看出及時在父類定義多個構造函數,在初始化時,僅調用父類的無參構造函數
通過反彙編看本質
4.4.2 子類的構造函數顯式的調用了父類的有參構造函數
如果子類的構造函數顯式的調用了父類的有參構造函數,就不會再去默認調用父類的無參構造函數
代碼如下:
#include <iostream>
using namespace std;
struct Person {
int m_age;
Person() {
cout << "Person:;Person()" << endl;
}
Person(int age) {
cout << "Person:;Person(int age)" << endl;
}
};
struct Student : public Person {
int m_no;
Student():Person(10) {
cout << "Student:;Student()" << endl;
}
};
int main() {
Student student;
return 0;
}
4.4.3 父類缺少無參構造函數時
父類缺少無參構造函數,子類的構造函數必須顯式調用父類的有參構造函數
4.4.4 父類無構造函數
代碼如下:
#include <iostream>
using namespace std;
struct Person {
int m_age;
};
struct Student : public Person {
int m_no;
Student() {
cout << "Student::Student()" << endl;
}
};
int main() {
Student student;
return 0;
}
輸出:Student::Student()
5. 成員訪問權限
4.1 成員訪問權限、繼承方式有3種
4.1.1 public :公共的、任何地方都可以訪問(struct默認public)
當 public 時
1)子類 Student
2)在棧中創建的對象 person
都可以訪問 Person 類中的 m_age
#include <iostream>
using namespace std;
struct Person {
public:
int m_age;
void run() {
m_age = 10;
}
};
struct Student:Person{
void study() {
m_age = 10;
}
};
int main() {
Person person;
person.m_age = 20;
return 0;
}
4.1.2 protected:子類內部、當前類內部可以訪問
#include <iostream>
using namespace std;
struct Person {
protected:
int m_age;
void run() {
m_age = 10;
}
};
struct Student:Person{
void study() {
m_age = 10;
}
};
int main() {
Person person;
person.m_age = 20; // 報錯
return 0;
}
如上圖代碼編譯時報錯
4.1.3 private:私有的,只有當前類內部可以訪問(class默認)
#include <iostream>
using namespace std;
struct Person {
private:
int m_age;
void run() {
m_age = 10;
}
};
struct Student:Person{
void study() {
m_age = 10;
}
};
int main() {
Person person;
person.m_age = 20;
return 0;
}
如上圖代碼編譯時報錯
4.2 繼承中成員變量訪問權限
4.2.1 子類內部訪問父類成員的權限,是以下2項中權限最小的那個
1) 成員本身的訪問權限
2) 上一級父類的繼承方式
如圖所示:GoodStudent 的訪問 Person 中 m_age 的權限
由 Person本身權限 和 student (相對於GoodStudent 的上一級)繼承方式 的 最小權限
示例1:私有繼承公有成員變量的父類
struct Person {
public:
int m_age;
};
struct Student:private Person{
};
等同於(將父類中的公有成員變量拿過來,在加上私有聲明)
struct Student:private Person{
//相當於
private:
int m_age;
};
上面代碼如圖所示
示例2:私有繼承私有成員變量的父類
struct Person {
private:
int m_age;
};
struct Student:private Person{
};
子類繼承不到任何父類成員變量
示例3:公有繼承私有成員變量的父類
struct Person {
private:
int m_age;
};
struct Student:public Person{
void study() {
m_age=10; // 報錯
}
};
4.2.2 子類一般使用公有繼承 :public superclass
開發中用的最多的繼承方式是public,這樣能保留父類原來的成員訪問權限
#include <iostream>
using namespace std;
struct Person {
public:
int m_age;
};
struct Student:public Person{
void study() {
m_age=10;
}
};
struct GoodStudent :public Student {
void work() {
m_age = 10;
}
};
int main() {
Person person;
person.m_age = 20;
return 0;
}
4.3 訪問權限不影響對象的內存佈局
#include <iostream>
using namespace std;
struct Person {
private:
int m_age;
public:
void setAge(int age) {
m_age = age;
}
int getAge() {
return m_age;
}
};
struct Student:public Person{
};
struct GoodStudent :public Student {
void work() {
setAge(10);
cout << getAge() << endl;
}
};
int main() {
GoodStudent gs;
gs.work();
cout << sizeof(gs) << endl;
return 0;
}
可以看出 GoodStudent 中並沒有定義成員變量,通過繼承的關係。導致 sizeof(gs) 對象 爲4個字節大小
所以訪問權限不影響對象的內存佈局
4.4 class默認爲私有繼承
4.4.1 class聲明的類 繼承都需要聲明 public
但是不影響公有構造函數
如下代碼:
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person::Person()" << endl;
}
~Person() {
cout << "Person::~Person()" << endl;
}
void run() {}
};
class Student: Person {
public:
Student() {
cout << "Student::Student()" << endl;
}
~Student() {
cout << "Student::~Student()" << endl;
}
};
class GoodStudent :Student {
public:
GoodStudent() {
cout << "GoodStudent::GoodStudent()" << endl;
}
~GoodStudent() {
cout << "GoodStudent::~GoodStudent()" << endl;
}
};
int main() {
GoodStudent student;
student.run(); // 報錯
return 0;
}
4.4.2 class和struct 混用
struct 不需要顯示聲明(默認爲public)繼承class或struct
建議還是全部顯示聲明 public 繼承
class Person {
private:
int m_age;
public:
void run() {}
};
struct Student : Person {
};
class GoodStudent : public Student {
};
int main() {
GoodStudent gs;
gs.run();
cout << sizeof(gs) << endl;
/*Person person;
person.m_age = 20;*/
return 0;
}
輸出: 4
6. 初始化列表
6.1 一種便捷的初始化成員變量方式
特點:一種便捷的初始化成員變量方式
只能用在構造函數中
初始化順序只與成員變量的聲明順序有關
6.1.1 一個標準的構造函數,初始化成員變量寫法,如下:
#include <iostream>
using namespace std;
struct Person {
int m_age;
int m_height;
Person(int age,int height) {
m_age = age;
m_height = height;
}
};
int main() {
Person person(18, 180);
return 0;
}
6.1.2 利用初始化列表寫法,如下:
#include <iostream>
using namespace std;
struct Person {
int m_age;
int m_height;
Person(int age, int height) :m_age(age),m_height(height) {}
};
int main() {
Person person(18, 180);
cout << person.m_age << endl;
cout << person.m_height << endl;
return 0;
}
等價關係
6.1.3 正常初始化與利用初始化列表 效率比較
正常初始化 反彙編
mov eax,dword ptr [this]
mov ecx,dword ptr [age]
mov dword ptr [eax],ecx
mov eax,dword ptr [this]
mov ecx,dword ptr [height]
mov dword ptr [eax+4],ecx
初始化列表 反彙編
mov eax,dword ptr [this]
mov ecx,dword ptr [age]
mov dword ptr [eax],ecx
mov eax,dword ptr [this]
mov ecx,dword ptr [height]
mov dword ptr [eax+4],ecx
根據反彙編對比效率是完全一樣的
6.2 利用初始化列表傳參
6.2.1 帶運算的參數
#include <iostream>
using namespace std;
struct Person {
int m_age;
int m_height;
Person(int age, int height) :m_age(age+2),m_height(height) {}
};
int main() {
Person person(18, 180);
cout << person.m_age << endl;
cout << person.m_height << endl;
retrn 0;
}
輸出:20
180
6.2.2 將函數作爲參數
#include <iostream>
using namespace std;
int func() {
return 12;
}
struct Person {
int m_age;
int m_height;
Person(int age, int height) :m_age(func()),m_height(height) {}
};
int main() {
Person person(18, 180);
cout << person.m_age << endl;
cout << person.m_height << endl;
return 0;
}
輸出:12
180
6.2.3 將成員變量作爲參數
6.2.4 顛倒成員變量順序
6.3 成員變量的聲明順序爲初始化列表讀取順序
如下代碼:
#include <iostream>
using namespace std;
int setAge() {
cout << "setAge()" << endl;
return 1;
}
int setHeight() {
cout << "setHeight()" << endl;
return 2;
}
struct Person {
int m_age;
int m_height;
Person(int age, int height) :m_height(setHeight()) ,m_age(setAge()){}
};
int main() {
Person person(18, 180);
cout << person.m_age << endl;
cout << person.m_height << endl;
return 0;
}
總結:在初始化列表時,最好將順序與自定義成員變量順序保持一致
6.4 初始化列表與默認參數配合使用
一個構造函數,起到了三個構造函數的效果(傳統寫法一樣)
#include <iostream>
using namespace std;
struct Person {
int m_age;
int m_height;
Person(int age =0, int height=0) :m_age(age), m_height(height) {}
};
int main() {
Person person1;
Person person2(7);
Person person(18, 180);
return 0;
}
6.4.1 如果函數的聲明和實現是分離的
1)初始化列表智能卸載函數的實現中
2)默認參數只能寫在函數的聲明中
#include <iostream>
using namespace std;
struct Person {
int m_age;
int m_height;
Person(int age = 0, int height = 0);
};
// 語法糖
Person::Person(int age, int height) :m_age(age), m_height(height) {}
int main() {
Person person1;
Person person2(7);
Person person(18, 180);
return 0;
}
6.5 思考
7. 構造函數的互相調用
場景:爲解決在構造函數中多次重複爲成員變量賦值操作
7.1 無參構造函數調用有參構造函數
由上圖可見,輸出的爲亂碼。
Person() {
//創建Person對象
Person(10, 20);
}
這段代碼Person(10, 20);爲創建一個新的Person對象
等價於
Person() {
Person person;
person.m_age = 10;
person.m_height = 20;
}
這個臨時變量person 無法傳給外界person(this)對象
利用匯編剖析
7.2 構造函數調必須在初始化列表中調用構造函數
如下代碼:
#include <iostream>
using namespace std;
struct Person {
int m_age;
int m_height;
Person():Person(10,20){
}
Person(int age, int height) {
m_age = age;
m_height = height;
}
};
int main() {
Person person;
cout << person.m_age << endl;
cout << person.m_height << endl;
return 0;
}
7.3 錯誤寫法
7.4 繼承體系下的構造函數示例
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person(int age) :m_age(age) {}
};
class Student:public Person {
int m_age;
public:
Student(int age,int no) :m_age(age),Person(age) {}
};
int main() {
Student student(2, 23);
return 0;
}
7.5 構造函數 和 析構函數調用順序
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person::Person()" << endl;
}
~Person() {
cout << "Person::~Person()" << endl;
}
};
class Student: Person {
public:
Student() {
cout << "Student::Student()" << endl;
}
~Student() {
cout << "Student::~Student()" << endl;
}
};
class GoodStudent :Student {
public:
GoodStudent() {
cout << "GoodStudent::GoodStudent()" << endl;
}
~GoodStudent() {
cout << "GoodStudent::~GoodStudent()" << endl;
}
};
int main() {
GoodStudent student;
//student.run();
return 0;
}
8. 多態
多態的三要素:
1)子類重寫父類的成員函數(override)(C++父類函數必須是虛函數)
2)父類指針指向子類對象
3)利用父類指針調用(重寫)的成員函數
8.1 父類指針、子類指針
8.1.1 父類指針指向子類對象
父類指針可以指向子類對象,是安全的的,開發中經常用到(繼承方式必須是public)
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
};
class Student:public Person {
public:
int m_score;
};
int main() {
Person* p = new Student();
p->m_age = 9;
return 0;
}
左邊 Person* p 說明指針類型是 person 可訪問的範圍只能是 Person聲明的成員變量(即:m_age)
右邊 new Student() 爲8個字節(m_age,m_score)
p指針訪問時安全,不會超出邊界
繼承方式必須是public
8.1.2 子類指針指向父類對象
直接寫,編譯器報錯
需要“騙”一下編譯器-強制類型轉換
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
};
class Student:public Person {
public:
int m_score;
};
int main() {
Student* p = (Student *)new Person();
p->m_age = 10;
p->m_score = 100;
return 0;
}
p指針訪問時越界,將不可訪問的4個字節m_score 輸入值,這是不安全的
8.2 爲什麼要用多態?
場景:
#include <iostream>
using namespace std;
struct Dog{
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Cat {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct Pig {
void speak() {
cout << "Pig::speak()" << endl;
}
void run() {
cout << "Pig::run()" << endl;
}
};
void liu(Dog*p) {
p->run();
p->run();
}
void liu(Cat* p) {
p->run();
p->run();
}
void liu(Pig* p) {
p->run();
p->run();
}
int main() {
liu(new Dog());
liu(new Cat());
liu(new Pig());
return 0;
}
重複的函數需要多次創建對象去調用
8.3 多態的使用
多態是面向對象非常重要的一個特性
8.3.1 C++默認情況下,編譯器指揮根據指針類型調用對應的函數,不存在多態
#include <iostream>
using namespace std;
struct Animal{
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::speak()" << endl;
}
};
struct Dog:Animal {
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct Pig :Animal {
void speak() {
cout << "Pig::speak()" << endl;
}
void run() {
cout << "Pig::run()" << endl;
}
};
void liu(Animal * p) {
p->run();
p->speak();
}
int main() {
liu(new Dog());
liu(new Cat());
liu(new Pig());
return 0;
}
liu(new Dog());
liu(new Cat());
liu(new Pig());
void liu(Animal * p) 函數只根據 Animal指針,不去參考傳入的對象
通過反彙編可見:
8.3.2 多態是面向對象非常重要的一個特性
1)同一操作作用域不同的對象,可以有不同的解釋,產生不用的執行結果
2)在運行時,可以識別出真正的對象類型,調用對應子類中的函數
8.4 虛函數
8.4.1 C++中的多態通過虛函數(virtual function) 來實現
1)父類沒有使用虛函數的情況下
及時指向子類Cat,指針a調用的也是父類的成員函數
2)只有父類使用 virtual 修飾函數函數時,纔可以訪問指向的對象成員函數
8.4.2 虛函數:被virtual修飾的成員函數
8.4.3 只要在聲明中爲虛函數,子類中重寫的函數也自動變成虛函數(也就是說子類中可以省略virtual關鍵字)
#include <iostream>
using namespace std;
struct Animal{
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::speak()" << endl;
}
};
struct Dog:Animal {
//重寫 override
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct Pig :Animal {
void speak() {
cout << "Pig::speak()" << endl;
}
void run() {
cout << "Pig::run()" << endl;
}
};
void liu(Animal * p) {
p->run();
p->speak();
}
int main() {
liu(new Dog());
liu(new Cat());
liu(new Pig());
return 0;
}
輸出: Dog::run()
Dog::speak()
Cat::run()
Cat::speak()
Pig::run()
Pig::speak()
8.5 虛表
8.5.1 虛函數的實現原理
虛函數的實現原理是虛表,這個虛表裏面存儲着最終需要調用的虛函數地址,這個虛表也叫虛函數表
虛表->存放虛函數地址
虛函數的2個聲明方式:
1.override 父類聲明 virtual 的函數
2.子類中自定義 virtual 的函數
8.5.2 虛表(x86環境)圖
8.5.3 從反彙編剖析虛表
1)x86位下 反彙編 剖析
dog->speak();
x32下指針佔4個字節,所以用dword
ebp-8爲 cat的地址,dword爲4個字節,eax爲cat地址值(cat地址指向的存儲空間)
mov eax,dword ptr [ebp-8]
取eax的地址值給edx,edx地址值則爲虛表的地址
mov edx,dword ptr [eax]
從虛表首地址取4個長度(即:如圖 0x00B814E7),存入eax
mov eax,dword ptr [edx]
調用函數 call cat::speak()
call eax
dog->run();
x32下指針佔4個字節,所以用dword
ebp-8爲 cat的地址,dword爲4個字節,eax爲cat地址值(cat地址指向的存儲空間)
mov eax,dword ptr [ebp-8]
取eax的地址值給edx,edx地址值則爲虛表的地址
mov edx,dword ptr [eax]
從虛表首地址加上4個長度再取4個長度(即:如圖 0x00B814CE),存入eax
mov eax,dword ptr [edx+4]
調用函數 call cat::run()
call eax
2)x64位下 反彙編 剖析
dog->speak();
x64下指針佔8個字節,所以用qword
rbp+8爲 cat的地址,qword爲8個字節,rax爲cat地址值(cat地址指向的存儲空間)
mov rax,qword ptr [rbp+8]
取rax的地址值給rax,rax地址值則爲虛表的地址
mov rax,qword ptr [rax]
從虛表首地址取8個長度
調用函數 call cat::speak()
call qword ptr [rax]
dog->run();
x64下指針佔8個字節,所以用qword
rbp+8爲 cat的地址,qword爲8個字節,rax爲cat地址值(cat地址指向的存儲空間)
mov rax,qword ptr [rbp+8]
取rax的地址值給rax,rax地址值則爲虛表的地址
mov rax,qword ptr [rax]
從虛表首地址加上8個長度再取8個長度
調用函數 call cat::run()
call qword ptr [rax+8]
8.5.4 從內存剖析虛表
初始狀態下
由圖可見:
50 ab db 00 爲 虛表地址
由小端模式,轉化成可查詢地址爲:0x00dbab50
由圖可見:
虛表存儲的地址值
0x00DBAB50 2d 15 db 00 -.?.
0x00DBAB54 23 15 db 00 #.?.
由小端模式,轉化成可查詢地址爲:0x00db152d
由小端模式,轉化成可查詢地址爲:0x00db1523
F11進入查看函數調用結果
8.5.5 相同對象共用同一份虛表
所有cat對象(不管在全局、棧、堆)共用同一份虛表
8.5.6 繼承關係下幾種虛表代碼
1)父類成員函數全部 virtual
存在虛表
虛表內存存放2個函數的地址值:2個都是創建對象重寫父類的函數
#include <iostream>
using namespace std;
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::speak()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
int main() {
Animal* cat = new Cat();
cat->speak();
cat->run();
return 0;
}
輸出:
Cat::speak()
Cat::run()
2)父類成員函數部分 virtual
存在虛表
虛表內存存放1個函數的地址值:子類中重寫被父類聲明 virtual 的函數
#include <iostream>
using namespace std;
struct Animal {
void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::speak()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
int main() {
Animal* cat = new Cat();
cat->speak();
cat->run();
return 0;
}
輸出:
Animal::speak()
Cat::run()
3)子類成員函數部分重寫父類成員函數
存在虛表
虛表內存存放2個函數的地址值:1個子類中重寫被父類聲明 virtual 的函數,1個父類沒被子類重寫的 virtual 函數
#include <iostream>
using namespace std;
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::speak()" << endl;
}
};
struct Cat : Animal {
// 部分重寫
void run() {
cout << "Cat::run()" << endl;
}
};
int main() {
Animal* cat = new Cat();
cat->speak();
cat->run();
return 0;
}
輸出:
Animal::speak()
Cat::run()
反彙編 剖析
內存剖析
4)只有父類本身
存在虛表
虛表內存存放2個函數的地址值:被父類聲明 virtual 的函數
#include <iostream>
using namespace std;
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::speak()" << endl;
}
};
int main() {
Animal* anim = new Animal();
anim->speak();
anim->run();
return 0;
}
輸出:
Animal::speak()
Animal::speak()
5)多重繼承,全部重寫
#include <iostream>
using namespace std;
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::speak()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct WhiteCat :Cat {
void speak() {
cout << "WhiteCat::speak()" << endl;
}
void run() {
cout << "WhiteCat::run()" << endl;
}
};
int main() {
Animal* cat = new WhiteCat();
cat->speak();
cat->run();
return 0;
}
輸出:
WhiteCat::speak()
WhiteCat::run()
6)多重繼承,部分重寫
#include <iostream>
using namespace std;
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::run()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
};
struct WhiteCat :Cat {
};
int main() {
Animal* cat = new WhiteCat ();
cat->speak();
cat->run();
return 0;
}
輸出:
Cat::speak()
Animal::run()
7)多重繼承,父類指針指向子類對象
#include <iostream>
using namespace std;
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::speak()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct WhiteCat :Cat {
void speak() {
cout << "WhiteCat::speak()" << endl;
}
void run() {
cout << "WhiteCat::run()" << endl;
}
};
int main() {
Animal* cat = new WhiteCat();
cat->speak();
cat->run();
Cat * cat1 = new WhiteCat();
cat1->speak();
cat1->run();
return 0;
}
輸出:
WhiteCat::speak()
WhiteCat::run()
WhiteCat::speak()
WhiteCat::run()
8)多重繼承,父類指針指向子類對象,virtual 不在頂級定義
#include <iostream>
using namespace std;
struct Animal {
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::speak()" << endl;
}
};
struct Cat :Animal {
virtual void speak() {
cout << "Cat::speak()" << endl;
}
virtual void run() {
cout << "Cat::run()" << endl;
}
};
struct WhiteCat :Cat {
void speak() {
cout << "WhiteCat::speak()" << endl;
}
void run() {
cout << "WhiteCat::run()" << endl;
}
};
int main() {
Animal* cat = new WhiteCat();
cat->speak();
cat->run();
Cat * cat1 = new WhiteCat();
cat1->speak();
cat1->run();
return 0;
}
輸出:
Animal::speak()
Animal::speak()
WhiteCat::speak()
WhiteCat::run()
可以看出,虛函數是自上向下的
所有父級聲明的 virtual 函數 子類都會成爲 virtual 函數
所有父級聲明的 virtual 函數 父類都不會成爲 virtual 函數
9)多重繼承,父類指針指向子類對象,virtual 不在頂級定義
#include <iostream>
using namespace std;
struct Animal {
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::speak()" << endl;
}
};
struct Cat :Animal {
virtual void speak() {
cout << "Cat::speak()" << endl;
}
virtual void run() {
cout << "Cat::run()" << endl;
}
};
struct WhiteCat :Cat {
void speak() {
cout << "WhiteCat::speak()" << endl;
}
void run() {
cout << "WhiteCat::run()" << endl;
}
};
struct WhiteBlackCat :WhiteCat {
void speak() {
cout << "WhiteBlackCat::speak()" << endl;
}
void run() {
cout << "WhiteBlackCat::run()" << endl;
}
};
int main() {
Animal* cat = new WhiteCat();
cat->speak();
cat->run();
WhiteCat* cat1 = new WhiteBlackCat();
cat1->speak();
cat1->run();
return 0;
}
輸出:
Animal::speak()
Animal::speak()
WhiteBlackCat::speak()
WhiteBlackCat::run()
8.6 調用父類的成員函數實現
類似 Java 中的 super.函數()
#include <iostream>
using namespace std;
struct Animal {
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::speak()" << endl;
}
};
struct Cat :Animal {
void speak() {
Animal::speak();
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
int main() {
Animal* cat = new Cat();
cat->speak();
return 0;
}
輸出:
Cat::speak()
Animal::speak()
8.7 虛析構函數
場景:虛構函數的產生是因爲父類指針無法釋放子類對象
#include <iostream>
using namespace std;
struct Animal {
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::speak()" << endl;
}
~Animal() {
cout << "Animal::~Animal()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
~Cat() {
cout << "Cat::~Cat()" << endl;
}
};
int main() {
Animal* cat = new Cat();
cat->speak();
delete cat;
return 0;
}
輸出:
Animal::speak()
Animal::~Animal()
8.7.1 無虛函數下
delete 父類指針,無法回收子類對象
#include <iostream>
using namespace std;
struct Animal {
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::speak()" << endl;
}
Animal() {
cout << "Animal::Animal()" << endl;
}
~Animal() {
cout << "Animal::~Animal()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
Cat() {
cout << "Cat::Cat()" << endl;
}
~Cat() {
cout << "Cat::~Cat()" << endl;
}
};
int main() {
Animal* cat = new Cat();
cat->speak();
delete cat;
return 0;
}
輸出:
Animal::Animal()
Cat::Cat()
Animal::speak()
Animal::~Animal()
沒有虛表,delete cat 時,會看當前 cat的指針類型,因爲爲Animal類型(無虛表),所以只調用 Animal的虛構函數
8.7.2 將父類析構函數聲明爲虛函數
如果存在父類指針指向子類對象的情況,應該將析構函數聲明爲虛函數(虛析構函數)
delete 父類指針時,纔會調用子類的析構函數,保證析構的完整性
#include <iostream>
using namespace std;
struct Animal {
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::speak()" << endl;
}
Animal() {
cout << "Animal::Animal()" << endl;
}
virtual ~Animal() {
cout << "Animal::~Animal()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
Cat() {
cout << "Cat::Cat()" << endl;
}
~Cat() {
cout << "Cat::~Cat()" << endl;
}
};
int main() {
Animal* cat = new Cat();
cat->speak();
delete cat;
return 0;
}
輸出:
Animal::Animal()
Cat::Cat()
Animal::speak()
Cat::~Cat()
Animal::~Animal()
8.8 純虛函數
簡單說:沒有函數實現的虛函數
純虛函數:沒有函數體且初始化爲0的虛函數
作用:定義接口規範(抽象類、接口)
#include <iostream>
using namespace std;
//Java:抽象類、接口
//OC:協議
struct Animal {
// 純虛函數
virtual void speak() = 0;
virtual void run() = 0;
};
struct Dog :Animal {
//重寫 override
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
int main() {
return 0;
}
8.9 抽象類 (Abstract Class)
抽象類不能被創建對象
不能被new
抽象類的特點:
8.9.1 含有純虛函數的類,不可以實例化(不可以創建對象)
8.9.2 抽象類也可以包含非純虛函數、成員變量
類中只要有一個純虛函數,就是抽象類
#include <iostream>
using namespace std;
//Java:抽象類、接口
//OC:協議
struct Animal {
//可以有成員變量
int m_age;
// 純虛函數
virtual void speak() = 0;
void run() {
}
};
struct Dog :Animal {
//重寫 override
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Cat :Animal {
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
int main() {
Animal* anim = new Cat();
anim->run();
return 0;
}
輸出: Cat::run()
8.9.3 如果父類是抽象類,子類沒有完全實現純虛函數,那麼這個子類依然是抽象類
1)如下圖,有2個抽象類
代碼如下:
#include <iostream>
using namespace std;
struct Animal {
//可以有成員變量
int m_age;
// 純虛函數
virtual void speak() = 0;
virtual void run() = 0;
};
struct Dog :Animal {
void run() {
cout << "Dog::run()" << endl;
}
};
struct Hashiqi :Animal {
void speak() {
cout << "Hashiqi::speak()" << endl;
}
void run() {
cout << "Hashiqi::run()" << endl;
}
};
int main() {
Animal anim; // 編譯報錯
Dog dog; // 編譯報錯
Hashiqi ha;
return 0;
}
2)Hashiqi 不是抽象類
#include <iostream>
using namespace std;
//Java:抽象類、接口
//OC:協議
struct Animal {
//可以有成員變量
int m_age;
// 純虛函數
virtual void speak() = 0;
virtual void run() = 0;
};
struct Dog :Animal {
void run() {
cout << "Dog::run()" << endl;
}
};
struct Hashiqi :Dog {
void speak() {
cout << "Hashiqi::speak()" << endl;
}
};
int main() {
Hashiqi ha;
return 0;
}
主要以b站免費學習資源
打造同進度學習的同學
在沒有up主回答的情況下
通過一起學習組隊可以互相解決觀看視頻中自己出現的問題,通過教學相長的方式,將知識可以牢固掌握。
關於文章中的問題歡迎指正,如有其它問題也可以私信,看到會第一時間回覆
我們的目標是:學習是我們終身的任務