写在前面

  由于此系列是本人一个字一个字码出来的,包括示例和实验截图。本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

类与结构体的关系

  它们两个的定义我就不在啰嗦了。在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语言——结构体与类的更多相关文章

  1. (四)羽夏看C语言——循环与跳转

    写在前面   由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...

  2. (二)羽夏看C语言——容器

    写在前面   由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...

  3. (八)羽夏看C语言——C番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...

  4. (九)羽夏看C语言——C++番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...

  5. (三)羽夏看C语言——进制

    写在前面   由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...

  6. (一)羽夏看C语言——简述

    "羽夏看C语言"介绍什么   本系列从汇编的角度,比较翔实的介绍C语言.C++和C其实是一样的东西,C++的编译器只是更强大,更能帮助我们写代码,例如模板.没有特殊说明,本系列不会 ...

  7. (六)羽夏看C语言——函数

    写在前面   由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...

  8. (七)羽夏看C语言——模板(C++)

    写在前面   由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...

  9. 失落的C语言结构体封装艺术

    Eric S. Raymond <esr@thyrsus.com> 目录 1. 谁该阅读这篇文章 2. 我为什么写这篇文章 3.对齐要求 4.填充 5.结构体对齐及填充 6.结构体重排序 ...

随机推荐

  1. Scrapy+splash报错 Connection was refused by other side

    报错信息如下: Traceback (most recent call last):   File "/usr/local/lib/python3.7/site-packages/scrap ...

  2. nuxt服务部署到云上全程记录

    首先,在使用脚手架nuxt-app中创建项目时,箭头选用不起作用,这是因为git bash在windows中交互问题,临时的解决办法是换用cmd 登录云服务器后,首先安装nodejs yum inst ...

  3. YOLO-V4 实现口罩识别(附加数据、数据批量处理程序)

    一.YOLO-v4概念 如果想要了解和认识yolo-v4的基本概念,首先要提的就是它的基础版本yolo-v1,对于yolo来说,最经典的算是yolo-v3.如果想要了解它的由来和历史的话,可以自行搜索 ...

  4. (数据科学学习手札126)Python中JSON结构数据的高效增删改操作

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在上一期文章中我们一起学习了在Python ...

  5. expect命令和here document免交互

    目录 一.Here Document免交互 1.1 概述 1.2 语法格式 1.3 简单案例 1.4 支持变量替换 1.5 多行注释 1.6 完成自动划分磁盘免交互 二.Expect进行免交互 2.1 ...

  6. 基于CAS实现SSO单点登录

    1. 概述 1.1. 什么是SSO? 单点登录( Single Sign-On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要 登录一 ...

  7. 字符串连接 strcat

    1 //字符串连接 strcat 2 //将一个字符串连接到另一个字符串的末尾,组合成一个新字符串 3 4 #include<stdio.h> 5 #include<stdlib.h ...

  8. C# 10 完整特性介绍

    前言 开头防杠:.NET 的基础库.语言.运行时团队从来都是相互独立各自更新的,.NET 6 在基础库.运行时上同样做了非常多的改进,不过本文仅仅介绍语言部分. 距离上次介绍 C# 10 的特性已经有 ...

  9. 【Java】@Scheduled常用的注解的使用

    @Scheduled注解的使用 cron cron这个参数必须要接受一个cron表达式 cron表达式是个啥呢,Cron表达式是一个具有时间含义的字符串,字符串以5个空格隔开,分为6个域,格式为 X ...

  10. spring boot中连接数据库报错500(mybatis)

    spring boot中连接数据库报错500(mybatis) pom.xml中的依赖 <!-- 集成mybatis--> <dependency> <groupId&g ...