C++第五弹---类与对象(二)

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

类与对象

1、类对象模型

1.1、如何计算类对象的大小

1.2、类对象的存储方式猜测

1.3、结构体内存对齐规则

2、this指针

2.1、this指针的引出

2.2、this指针的特性

2.3、C语言和C++实现Stack的对比

总结


1、类对象模型


1.1、如何计算类对象的大小

class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
char _a;
};

 问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算
一个类的大小?


1.2、类对象的存储方式猜测


1、对象中包含类的各个成员

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一
个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么
如何解决呢?
2、代码只保存一份,在对象中保存存放代码的地址
3、只保存成员变量,成员函数存放在公共的代码段

问题:对于上述三种存储方式,那计算机到底是按照那种方式来存储的?
我们再通过对下面的不同对象分别获取大小来分析看下

// 类中既有成员变量,又有成员函数
class A1 {
public:
void f1(){}
private:
int _a;//只计算成员变量的大小,为4字节
};
// 类中仅有成员函数
class A2 {
public:
void f2() {}
//只计算成员变量的大小,但是没有变量则算空类,空类规定为1字节
//为什么空类为1字节呢?因为实例化对象的标志是开辟的内存空间,竟然开辟了空间那么就不会是0,
//因此规定设为1字节
};
// 类中什么都没有---空类
class A3//空类为1字节
{};

 sizeof(A1) : __4____ sizeof(A2) : ___1___ sizeof(A3) : ___1___
结论:

一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。


1.3、结构体内存对齐规则

1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


【面试题】
1. 结构体怎么对齐? 为什么要进行内存对齐?

平台移植型好
不是所有的硬件平台都能访问任意地址上的数据;某些硬件平台只能只在某些地址访问某些特定类型的数据,否则抛出硬件异常,及遇到未对齐的边界直接就不进行读取数据了。

cpu处理效率高

从上图可以看出,对应两种存储方式,若CPU的读取粒度为4字节,

那么对于一个int 类型,若是按照内存对齐来存储,处理器只需要访存一次就可以读取完4个字节
若没有按照内存对其来读取,如上图所示,就需要访问内存两次才能读取出一个完整的int 类型变量具体过程为,第一次拿出 4个字节,丢弃掉第一个字节,第二次拿出4个字节,丢弃最后的三个字节,然后拼凑出一个完整的 int 类型的数据。


结论:结构体内存对齐是拿空间换取时间的做法。提高效率。

2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?

结构体的对齐是有规定的,不可任意对齐,但有一条指令可以修改默认对齐数(#pragma pack(对齐数))结构体进行规定对齐是跟底层的硬件有关系的,每种硬件对于数据的读取是不同的,为了提高硬件读取数据的效率,所以在代码层进行了结构体内存对齐。

比如:假设一台机器有32根地址线,即该机器每次能够读取32位的数据,也就是4byte的数据。例如将以下代码在该机器下进行读取:

 若内存对齐的方式进行读取

char _a变量总共读取一次(先读取四个字节,只取到第一个字节的数据即可),int _b总共读取一次。当读取完char _a 之后接着读取int _b 的时候恰好读取四个字节的内容,而这四个字节的内容恰好是int _b 的内容,刚好读取完成!

若不用内存对齐的方式进行存储:

char _a 读取一次,而 int _b 却要读取两次,读取一两个效率没啥影响,但是每种程序中读取的数据可能成千上万,效率就会有所降低! 

总结:

不能进行任意对齐,也不可不进行对齐,因为可能会降低效率! 

3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景。

大小端是数据的两种存储方式,因为市面上电脑硬件的制造不同,不同的硬件对于数据的处理方式不同,常用的市面上的硬件机器存储方式大致有两种:大端存储和小端存储!

概念:

大端存储:数据的低位字节放到内存的高地址处,高位字节放到内存的低地址处,是大端存储

小端存储:数据的低位字节放到内存的低地址处,高位字节放到内存的高地址处,是小端存储

测试一台机器是大端还是小端:

方式一:

利用联合体的特性,可以得出机器是大端还是小端。因为联合体共用同一块空间,所以我们给两个成员 一个char 和一个int  给int成员数字1,随后通过char成员去读取数据,访问成员的时候是从低地址到高地址开始访问的!若取到的char结果是1,是小端存储。若是0则是大端存储!

方式二:

创建一个int类型大小变量,将它的地址强转成char*类型,然后解引用得到的结果为1则为小端存储。否则为大端存储。

代码:

#include <iostream>
using namespace std;union S
{char a;int b;
};int main()
{S s1;//给整型变量赋值1s1.b = 1;//通过char进行访问cout << (int)s1.a << endl;return 0;
}

结论:

通过联合体共用同一块空间的特性或者整型存储来判断大小端!

2、this指针


2.1、this指针的引出


我们先来定义一个日期类 Date

class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
private:
//此处为声明
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
//Date::_year++;//此处为声明,因此不能修改值
Date d1, d2;//实例化对象
d1._year++;//此处已经定义过了,因此可以进行修改
d1.Init(2022,1,11);//初始化函数 可以使用缺省参数
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}

对于上述类,有这样的一个问题:
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函
数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。


2.2、this指针的特性

1. this指针的类型:类 类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用。
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递。

代码:

#include <iostream>
using namespace std;//定义一个日期类
class Date
{
public://初始化void Init(int year = 1, int month = 1, int day = 1){//编译器会自动将对象的地址传进来 用this指针形参接收//可在函数内部显式的写this指针进行访问this->_year = year;this->_month = month;this->_day = day;}//打印函数void Print(){//切记this指针是不能被修改的因为是用const修饰的参数// this = nullptr;  会报错error:不可修改的左值//编译器会自动将对象的地址传进来 用this指针形参接收//可在函数内部显式的写this指针进行访问cout << this->_year << " - ";cout << this->_month << " - ";cout << this->_day << endl;}
private:int _year;int _month;int _day;
};int main()
{//类的实例化对象 Date d1;//调用Init方法,//调用的时候编译器会自动的将对象的地址传进去// 且用this指针接收地址d1.Init(2023, 4, 30);//打印//调用的时候编译器会自动的将对象的地址传进去// 且用this指针接收地址d1.Print();//但我们不能显式的去传递对象的地址//会报错 error:函数调用中的参数太多//因为编译器已经默认传了对象的地址了//我们在传递就会出现错误//d1.Print(&d1);return 0;
}


图片:

总结:

对象在调用成员函数的时候,编译器会自动将对象的地址传给this指针,用户不可在进行显式传对象地址。在成员函数内部可以显式的通过this指针去访问成员变量,this指针默认是用const修饰的不可在成员函数内部修改this指针的值。this指针本质是一个形参,是存储在栈区上的!

【面试题】
1. this指针存在哪里?

this指针本质是成员函数的形参,只是被编译器隐式的传递操作了。因为是函数的形参,是存在栈区上的,不存在对象中!
2. this指针可以为空吗?

this指针本质是用const修饰的,所以我们不能在成员函数内部将this指针置为空。而对象实例化之后必然是有地址的,空对象也是占用1个字节的空间。但是外部给this指针传一个空指针进来 this指针是可能为空的,但为空之后this指针是没有啥意义的,空指针是不能进行访问成员的,很危险!

即:this指针可能为nullptr

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}

上面定义了一个A类 的指针p并给其赋予空值,通过指针去调用成员函数Print,此时在传的时候给this指针形参传过去的是p指针的值 也就是nullptr,而在成员函数内部并没有通过this指针去访问成员(进行解引用操作),this指针啥都没做,也没有用到this指针。即程序是可以正常运行的!

#include<iostream>
using namespace std;
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}

上面定义了一个A类 的指针p并给其赋予空值,通过指针去调用成员函数Print,此时在传的时候给this指针形参传过去的是p指针的值 也就是nullptr,而在成员函数内部通过this指针去访问成员_a(进行解引用操作),而此时的this指针是一个nullptr,对空指针解引用是不可行的。上述语法是没有问题的,编译时候是不会报错的,但访问是有问题的。即程序会出现运行崩溃!

2.3、C语言和C++实现Stack的对比


1. C语言实现

typedef int DataType;
typedef struct Stack
{
DataType* array;
int capacity;
int size;
}Stack;
void StackInit(Stack* ps)
{//初始化栈
}
void StackDestroy(Stack* ps)
{//销毁栈
}
void CheckCapacity(Stack* ps)
{//检查容量
}
void StackPush(Stack* ps, DataType data)
{//入栈
}
int StackEmpty(Stack* ps)
{//判断栈是否为空
}
void StackPop(Stack* ps)
{//出栈
}
DataType StackTop(Stack* ps)
{//获取栈顶数据
}
int StackSize(Stack* ps)
{//计算栈有效数据个数
}

可以看到,在用C语言实现时,Stack相关操作函数有以下共性:

每个函数的第一个参数都是Stack*。
函数中必须要对第一个参数检测,因为该参数可能会为NULL。
函数中都是通过Stack*参数操作栈的。
调用时必须传递Stack结构体变量的地址。
结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据
的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出
错。


2. C++实现
 

typedef int DataType;
class Stack
{
public://公共权限
void Init()
{//初始化栈
}
void Push(DataType data)
{//入栈
}
void Pop()
{//出栈
}
void Destroy()
{//销毁栈
}
}
private://私有权限
void CheckCapacity()
{//检查容量
}
private://私有权限
//成员变量
DataType* _array;
int _capacity;
int _size;
};

C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在
类外可以被调用
,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。
而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack *
参数是编译器维护的,C语言中需用用户自己维护。

总结


本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/276955.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot275毕业就业信息管理系统的设计与实现

毕业就业信息管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装毕业就业信息管理系统软件…

【C语言进阶篇】数据在内存中的存储

目录 1.大小端字节序和字节序判断 2.浮点数在内存中的存储与读取 2.1 浮点数在内存中的存储 2.2 浮点数在内存中的读取 在前面的学习中&#xff0c;我们知道内存被划分为一个个小的内存单元&#xff0c;数据就是存储在这些内存单元中的。那么&#xff0c;具体是如何存储的&am…

Unity中的网格创建和曲线变形

Unity中的网格创建和曲线变形 3D贝塞尔曲线变形贝塞尔曲线基础线性公式二次方公式三次方公式 Unity 实现3D贝塞尔曲线变形准备工作脚本概述变量定义 变量解析函数解析 获取所有子节点GetAllChildren 获取所有子节点UpdateBezierBend 控制点更新CalculateBezier Bezier 曲线公式…

pytorch升级打怪(三)

数据集合数据加载器 简介加载数据集迭代和可视化数据集为您的文件创建自定义数据集__init____len____getitem__ 准备您的数据以使用DataLoaders进行训练通过DataLoader进行遍载 简介 处理数据样本的代码可能会变得混乱且难以维护&#xff1b;理想情况下&#xff0c;我们希望我…

三分钟快速理解Yarn的工作流程

知识讲解之前&#xff0c;我们先来听一段小故事(不要带入现实) 健鑫集团是一家公司&#xff0c;主要业务就是承接其他公司外包出去的项目&#xff0c;这地方是怎么运行的呢&#xff1f; 外部想和集团达成合作&#xff0c;草拟个合同直接和 Boss谈&#xff0c;Boss 来者不拒&am…

智慧城市:提升城市治理能力的关键

目录 一、智慧城市的概念及特点 二、智慧城市在提升城市治理能力中的应用实践 1、智慧交通&#xff1a;提高交通治理效率 2、智慧政务&#xff1a;提升政府服务水平 3、智慧环保&#xff1a;加强环境监测与治理 4、智慧安防&#xff1a;提高城市安全水平 三、智慧城市在…

css 各种方位计算 - client系列 offset系列 scroll系列 x/y 系列

offset系列 HTMLElement.offsetTop - Web API 接口参考 | MDN 一文读懂offsetHeight/offsetLeft/offsetTop/offsetWidth/offsetParent_heightoffset-CSDN博客 client系列 搞清clientHeight、offsetHeight、scrollHeight、offsetTop、scrollTop-CSDN博客 scroll系列 秒懂scr…

Python控制摄像头并获取数据文件

一、引言 摄像头作为计算机视觉领域的核心设备之一&#xff0c;广泛应用于视频监控、图像采集和数据处理等领域。通过Python编程语言&#xff0c;我们可以实现对摄像头的精确控制&#xff0c;包括摄像头的开启、关闭、参数设置以及数据获取等功能。 目录 一、引言 二、摄像头…

ElementUI Message 消息提示,多个显示被覆盖的问题

现象截图&#xff1a; 代码&#xff1a;主要是在this.$message 方法外层加上 setTimeout 方法 <script> export default {name: "HelloWorld",props: {msg: String,},methods: {showMessage() {for (let i 0; i < 10; i) {setTimeout(() > {this.$mess…

上海亚商投顾:沪指震荡调整 飞行汽车概念股持续爆发

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日震荡调整&#xff0c;深成指走势稍强&#xff0c;创业板指一度涨超1%&#xff0c;黄白二线走势分化&a…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:ColumnSplit)

将子组件纵向布局&#xff0c;并在每个子组件之间插入一根横向的分割线。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 可以包含子组件。 ColumnSplit通过分割线限制子组件的高度。初始…

微信小程序--开启下拉刷新页面

1、下拉刷新获取数据enablePullDownRefresh 开启下拉刷新&#xff1a; enablePullDownRefreshbooleanfalse是否开启当前页面下拉刷新 案例&#xff1a; 下拉刷新&#xff0c;获取新的列表数据,其实就是进行一次新的网络请求&#xff1a; 第一步&#xff1a;在.json文件中开…

【论文阅读】ACM MM 2023 PatchBackdoor:不修改模型的深度神经网络后门攻击

文章目录 一.论文信息二.论文内容1.摘要2.引言3.作者贡献4.主要图表5.结论 一.论文信息 论文题目&#xff1a; PatchBackdoor: Backdoor Attack against Deep Neural Networks without Model Modification&#xff08;PatchBackdoor:不修改模型的深度神经网络后门攻击&#xf…

DDR协议基础进阶(三)——(基本功能、初始化、MR寄存器)

DDR协议基础进阶&#xff08;三&#xff09;——&#xff08;基本功能、初始化、MR寄存器&#xff09; 一、DDR基本功能 DDR基本功能主要包括&#xff1a; 8-bit prefetch预取——8-bit&#xff0c;是指8位数据&#xff0c;即8倍芯片位宽的数据。由于DDR内部数据传输是32bit…

大型政企迈向数智化深水区,如何保障安全用云?

文&#xff5c;白 鸽 编&#xff5c;王一粟 在这个千行万业加速智能升级的时代&#xff0c;我们应该如何保证数字安全&#xff1f; 一方面&#xff0c;AI技术快速发展给当下带来一系列新型挑战&#xff1a;自动化攻击、深度伪造、隐私侵犯、数据安全等等。另一方面&#…

群晖NAS使用Docker安装WPS Office并结合内网穿透实现公网远程办公

文章目录 推荐1. 拉取WPS Office镜像2. 运行WPS Office镜像容器3. 本地访问WPS Office4. 群晖安装Cpolar5. 配置WPS Office远程地址6. 远程访问WPS Office小结 7. 固定公网地址 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff…

【C++庖丁解牛】vector容器的简易模拟实现(C++实现)(最后附源码)

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 前言vector容器代码实现内…

stm32-定时器输入捕获

目录 一、输入捕获简介 二、输入捕获框图 1.定时器总框图 2.输入捕获框图 3.主从触发模式 三、固件库实现 1.定时器测量PWM频率 2.PWMI模式 一、输入捕获简介 二、输入捕获框图 1.定时器总框图 上图可知&#xff0c;四个输入捕获和输出比较共用4个CCR寄存器&#x…

lftp服务与http服务(包含scp服务)详解

目录 前言: 1.lftp服务 1.1lftp服务的介绍以及应用场景 1.2安装lftp服务 1.2进行配置 1.3实际操作 2.http服务 2.1http服务介绍以及应用场景 2.1安装httpd服务 2.2进行配置 2.3实际操作 3.scp服务 3.1scp服务的介绍以及应用场景 致谢: 前言: 在当今互联网…

Seata 2.x 系列【12】高可用集群部署

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Seata 版本 2.0.0 本系列Spring Boot 版本 3.2.0 本系列Spring Cloud 版本 2023.0.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-seata-demo 文章目录 1. 概述2. 搭建演…