(五)羽夏看C语言——结构体与类
写在前面
由于此系列是本人一个字一个字码出来的,包括示例和实验截图。本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
类与结构体的关系
它们两个的定义我就不在啰嗦了。在C语言中,类和结构体是一个东西,只是用的关键字不一样罢了。不信咱们做一个实验,看看编译会不会报错:
#include <iostream>
struct MyStruct
{
public:
MyStruct();
~MyStruct();
private:
};
class MyClass
{
public:
MyClass();
~MyClass();
private:
};
MyClass::MyClass()
{
}
MyClass::~MyClass()
{
}
MyStruct::MyStruct()
{
}
MyStruct::~MyStruct()
{
}
int main()
{
system("pause");
return 0;
}
结果编译顺利通过。如果还想继续做深入的实验,请自行研究。下面我们来介绍它们的本质。
汇编看类和结构体
类和结构体虽然没有任何区别,但 通常会把只有数据的称之为结构体,还有功能函数的称之为类 。这句话我曾在(二)羽夏看C语言——容器 说明过。在此文章,我一般将用class关键字称之为类,用struct关键字称之为结构体,但脑子里面一定要清楚,C语言中的结构体和类是一个东西。我们将从一下方面对类和结构体进行探讨:
类的实例化
我们将用以下代码进行探讨此问题:
#include <iostream>
class MyClass
{
public:
MyClass();
~MyClass();
int pa = 5;
private:
int a;
};
MyClass::MyClass()
{
a = 10;
}
MyClass::~MyClass()
{
}
int main()
{
MyClass cls;
system("pause");
return 0;
}
以下是反汇编结果,让我们逐个分析类被实例化的过程:

如上图所示,lea ecx,[ebp-10h]就是取该类的指针,即为this。这就是为什么编译器在写类可以用this的原因。下一个call调用即为调用该类的构造函数。

上面的图是call调用后到的第一个代码块,可以说明,当一个类实例化时,会先调用它的构造函数。

根据汇编可知,调用构造函数的时候,先初始化变量,然后继续调用构造函数里面的内容,继而完成整个类的实例化。
类中有静态变量或函数
我们将用以下代码进行实验:
#include <iostream>
using namespace std;
class MyClass
{
public:
int pa = 5;
//static int b;
//void test();
private:
int a = 6;
};
//int MyClass::b = 10;
//void MyClass::test()
//{
// cout << "test" << endl;
//}
int main()
{
MyClass cls;
cout << sizeof(MyClass) << endl;
//int tmp = cls.b;
//cls.test();
system("pause");
return 0;
}
一看就能明白,以上代码使用来查看类大小的,我们可以用这种方式来判断这个东西真正属于不属于类。运行后,结果如下:
8
请按任意键继续. . .
然后,我们把b的声明和初始化以及调用去掉注释,然后再运行一下,发现结果仍和上面的结果一样。我们再看一下它的反汇编,跟到类实例化函数体内:

咦,咋找不到和b相关的任何东西呢,主函数也是没有,在那个b初始化处下断点也下不住。那我们再看看局部变量窗体里看看有没有与b有关的讯息:

遗憾的是,调试器里面的局部变量也不承认有b这个东西。那好,我们唯一能做的是再看一下如何访问这个b的。

我们发现,b被翻译成一个死地址,说明在类里面声明一个静态变量和在类外面声明一个静态变量在汇编层面没有任何区别,只是在C语言层面不同而已。
接下来看一下函数,我们重新把函数取消注释。继续做实验,发现结果还是相同。然后我们看一下反汇编:

可以看到,函数同样被翻译成一个死地址,但在它之前还是将该类的this指针传递给函数。如果将函数前面用static修饰的话,看看反汇编会有什么变化。

可以看到,函数直接被翻译成一个死地址,但不会传递this指针,这和在类外面声明一个函数调用在汇编层面无异。
继承
在类里面十分重要的一个概念就是继承。那么继承在汇编层面到底是什么样子呢?我们用以下代码进行验证:
#include <iostream>
using namespace std;
class MyClass
{
public:
int pa = 5;
MyClass()
{
cout << "MyClass构造函数被调用" << endl;;
}
private:
int a = 6;
};
class MyClassSub :public MyClass
{
public:
int pb = 15;
MyClassSub()
{
cout << "MyClassSub构造函数被调用" << endl;;
}
private:
int b = 16;
};
int main()
{
MyClassSub cls;
//int a = cls.pb;
//a = cls.pa;
system("pause");
return 0;
}
如下是输出结果:
MyClass构造函数被调用
MyClassSub构造函数被调用
请按任意键继续. . .
这个是我们从C语言层面对构造函数调用顺序进行验证,然后我们看一下反汇编:

根据反汇编,我们也同样验证此问题。然后我们再看一下变量会有什么变化,先把被注释掉的恢复进行验证,把代码运行到构造函数刚好结束,然后在内存窗口输入类的地址,可以得到如下结果:

由此可以看出,类的继承是直接是把被继承的类后面贴上子类的。那么,如果子类有的变量父类也有呢?我们把int pb = 15改为int pa = 15,连同下面的代码改动,我们看一下结果。

可以看出,访问pa的时候直接访问子类的,而内存结构根本没有发生任何变化。
我们最后再验证最后一个问题:子类继承默认访问为私有的,如果我们把public删掉后会不会应该继承后的内存结构呢?下一篇将揭晓答案。
虚表
我们从汇编层面观察虚表是什么,将用下面的汇编代码进行实验:
#include <iostream>
using namespace std;
class MyClass
{
public:
int pa = 5;
MyClass()
{
cout << "MyClass构造函数被调用" << endl;;
}
virtual void test();
private:
int a = 6;
};
void MyClass::test()
{
cout << "test" << endl;
}
class MyClassSub :MyClass
{
public:
int pa = 15;
MyClassSub()
{
cout << "MyClassSub构造函数被调用" << endl;;
}
void test();
private:
int b = 16;
};
void MyClassSub::test()
{
cout << "override test" << endl;
}
int main()
{
//请用指针实例化类,如果在堆栈实例化将会调用它的死地址
MyClassSub* cls = new MyClassSub();
cls->test();
system("pause");
return 0;
}
将会得到如下结果:
MyClass构造函数被调用
MyClassSub构造函数被调用
override test
请按任意键继续. . .
在system("pause");这行下断点,然后运行。观察局部变量,看到如下图:

__vfptr就是虚表地址,有几个虚函数就有几个。如果被重写,将会将虚表填充对应的地址。我们看看是如何调用该函数的。

通过汇编得出:通过虚表调用子类的test函数。
拷贝构造函数
在C语言中,每个类都会自带一个拷贝构造函数,我们看看拷贝构造函数为我们做了什么,将用以下代码进行实验:
#include <iostream>
using namespace std;
class MyClass
{
public:
int pa = 5;
MyClass()
{
cout << "MyClass构造函数被调用" << endl;;
}
private:
int a = 6;
};
int main()
{
MyClass cls;
MyClass* ci = new MyClass(cls);
system("pause");
return 0;
}
然后在合适的地方下个断点,看反汇编:

从图中可以看到new和拷贝构造的过程,先调用new函数申请8个字节的内存给类用,然后判断有没有成功,成功后把每个字节对应复制到指定位置。
下一篇
(六)羽夏看C语言——函数
(五)羽夏看C语言——结构体与类的更多相关文章
- (四)羽夏看C语言——循环与跳转
写在前面 由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...
- (二)羽夏看C语言——容器
写在前面 由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...
- (八)羽夏看C语言——C番外篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- (九)羽夏看C语言——C++番外篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- (三)羽夏看C语言——进制
写在前面 由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...
- (一)羽夏看C语言——简述
"羽夏看C语言"介绍什么 本系列从汇编的角度,比较翔实的介绍C语言.C++和C其实是一样的东西,C++的编译器只是更强大,更能帮助我们写代码,例如模板.没有特殊说明,本系列不会 ...
- (六)羽夏看C语言——函数
写在前面 由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...
- (七)羽夏看C语言——模板(C++)
写在前面 由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...
- 失落的C语言结构体封装艺术
Eric S. Raymond <esr@thyrsus.com> 目录 1. 谁该阅读这篇文章 2. 我为什么写这篇文章 3.对齐要求 4.填充 5.结构体对齐及填充 6.结构体重排序 ...
随机推荐
- VUE-router-跳转
跳转的 // 字符串 this.$router.push('/home/first') // 对象 this.$router.push({ path: '/home/first' }) // 命名的路 ...
- 共享内存 & Actor并发模型哪个更快?
HI,前几天被.NET圈纪检委@懒得勤快问到共享内存和Actor并发模型哪个速度更快. 前文传送门: 说实在,我内心10w头羊驼跑过...... 先说结论 首先两者对于并发的风格模型不一样. 共享内存 ...
- MySQL Shell import_table数据导入
目录 1. import_table介绍 2. Load Data 与 import table功能示例 2.1 用Load Data方式导入数据 2.2 用import_table方式导入数据 3. ...
- IIS短文件名漏洞原理与挖掘思路
首先来几个网址先了解一下 https://www.jb51.net/article/166405.htm https://www.freebuf.com/articles/web/172561.htm ...
- WPF上传图片到服务器文件夹
1.前端用ListBox加载显示多张图片 1 <ListBox Name="lbHeadImages" Grid.Row="1" ScrollViewer ...
- Java 14 新功能介绍
不做标题党,认认真真写个文章. 文章已经收录在 Github.com/niumoo/JavaNotes 和未读代码博客,点关注,不迷路. Java 14 早在 2019 年 9 月就已经发布,虽然不是 ...
- 如何高效、快速学习Flutter?如何避坑?(文末送服福利)
稳住,今天是周末,不过我今天要上班....啊..啊... 对于 Flutter 我没有太多的发言权,不过GSY是对 Flutter 充分理解并精深掌握的前辈了,所以转一篇他对 Flutter 的一些解 ...
- [TensorFlow2.0]-学习率 激活函数 损失函数
本人人工智能初学者,现在在学习TensorFlow2.0,对一些学习内容做一下笔记.笔记中,有些内容理解可能较为肤浅.有偏差等,各位在阅读时如有发现问题,请评论或者邮箱(右侧边栏有邮箱地址)提醒. 若 ...
- Linux线程同步之读写锁(rwlock)
读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程.当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步, 和 ...
- MySQL 不完全入门指南
由于 MySQL 的整个体系太过于庞大,文章的篇幅有限,不能够完全的覆盖所有的方面.所以我会尽可能的从更加贴进我们日常使用的方式来进行解释. 小白眼中的 MySQL 首先,对于我们来说,MySQL 是 ...