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主回答的情况下
通过一起学习组队可以互相解决观看视频中自己出现的问题,通过教学相长的方式,将知识可以牢固掌握。
关于文章中的问题欢迎指正,如有其它问题也可以私信,看到会第一时间回复
我们的目标是:学习是我们终身的任务

发布了10 篇原创文章 · 获赞 5 · 访问量 369

猜你喜欢

转载自blog.csdn.net/wanglei19891210/article/details/105223415
今日推荐