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