(五)羽夏看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.结构体重排序 ...
随机推荐
- 构建前端第2篇之--ESLint 配置
张艳涛 写于2021-1-19 报错: http://eslint.org/docs/rules/space-before-function-paren Missing space before fu ...
- WanJetpack项目:用Jetpack实现玩Android,追求最官方的实现方式
项目简介 玩Android demo.用Jetpack MVVM开发架构.单Activity多Fragment项目设计,项目结构清晰,代码简洁优雅,追求最官方的实现方式.用到以下知识点: LiveDa ...
- getRawX、getRawY与getX、getY、getScrollX、getScrollY,getTop,getLeft区别
这篇文章写的不错,Mark一下 http://blog.csdn.net/sinat_29912455/article/details/51073537
- JIPB | 两篇连发:华中农大黄俊斌团队报道二羟基异丁酰化调控稻曲病菌致病新机制
水稻是我国重要的粮食作物,稻曲病是水稻三大病害之一,不仅造成稻米产量损失,更重要的是稻曲球中的稻曲菌素的毒性和致畸作用,给人畜健康带来严重威胁.病原菌对植物的侵袭是由病原菌的毒力和植物免疫系统相互作用 ...
- C++ //虚析构和纯虚析构
1 //虚析构和纯虚析构 2 3 #include <iostream> 4 #include <string> 5 using namespace std; 6 7 clas ...
- java基础技术集合面试【笔记】
java基础技术集合面试[笔记] Hashmap: 基于哈希表的 Map 接口的实现,此实现提供所有可选的映射操作,并允许使用 null 值和 null 键(除了不同步和允许使用 null 之外,Ha ...
- Python - pydantic 入门介绍与 Models 的简单使用
前言 为啥要学这个,因为 FastAPI 是基于它进行开发的,而且是个不错的框架,所以有必要深入学习 前置学习 Python 类型提示:https://www.cnblogs.com/poloyy/p ...
- CentOS8 安装MySQL5.7
CentOS_8 安装MySQL5.7 1.在安装之前,如果你的系统曾经安装过Mariadb,请先卸载:yum remove mariadb*2.安装依赖 yum install -y epel-re ...
- ES6中新增的数组知识记录
JSON数组格式转换 let json = { '0': 'hello', '1': 'I am ', '2': 'michael', length:3 } 这就是一个JSON数组格式,跟普通的JSO ...
- HashTable原理和底层实现
1. 概述 上次讨论了HashMap的结构,原理和实现,本文来对Map家族的另外一个常用集合HashTable进行介绍.HashTable和HashMap两种集合非常相似,经常被各种面试官问到两者的区 ...