C++挖掘程序本質(第二章C++面向對象-中)李明傑-M了個J 配套教材

文章目錄

前言

由於在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主回答的情況下
通過一起學習組隊可以互相解決觀看視頻中自己出現的問題,通過教學相長的方式,將知識可以牢固掌握。
關於文章中的問題歡迎指正,如有其它問題也可以私信,看到會第一時間回覆
我們的目標是:學習是我們終身的任務

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