当前位置: 首页 > news >正文

C++第五节 - this指针、构造函数、析构函数

一、类对象的存储方式

只保存成员变量,成员函数存放在公共的代码段

 注意点:

#include<iostream>
using namespace std;
class A
{
public:void PrintA(){cout << _a << endl;}
private:char _a;
};
int main()
{class A a;cout << sizeof(a) << endl;cout << sizeof(A) << endl;return 0;
}

sizeof求大小的时候输入对象名和类型名的效果是一样的!

int main()
{class A a;//cout << sizeof(a) << endl;//cout << sizeof(A) << endl;a._a = 1;a.PrintA();return 0;
}

a._a是在对象里面找空间,将其值覆为1;

a.PrintfA()不是在对象中寻找!(函数存放在公共代码段中)

剩下的成员变量是按照C语言中的结构体的内存对齐来存储的!

下面为两个class内存对齐的例子:

  • 第一个class,存放char的时候,对其数为1,因此挨着int存放;
  • 第二个class,存放int的时候,其对其数为4,因此要在对其数的整数倍处存放;
  • 整个结构体的大小为对其数的整数倍;

为什么要进行内存对齐?

对于不同的硬件来说,访问内存并不是说想访问那个字节就访问哪个字节,具体的访问数量跟硬件的设置有关系;

假设硬件CPU一次读取四个字节

  • 访问_ch的时候访问到了4个字节;但是此时可以直接抛弃下面的3个字节,仅仅实现读取一次就完成!
  • 访问_a的时候直接访问4个字节一次读取即可完成;

如果没有内存对齐:

此时读取a需要读取两次!每次只能读取一部分!

怎么修改对其数呢?

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认

调整对其数后,会造成访问效率下降,但是能节省空间;

注意点:空类和仅有成员函数

class A2 {
public:void f2() {}
};
// 类中什么都没有---空类
class A3
{};int main()
{class A2 a2;class A3 a3;cout << sizeof(a2) << endl;cout << sizeof(a3) << endl;return 0;
}

没有成员变量的类对象,需要1byte,是为了占位,表示对象存在!这个字节不存储有效数据!

不给1个byte的内存,此时如果对对象取地址,无法取地址!

二、this指针

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

其实,方框类似于编译器自己做的处理,会将对象的地址传过去,然后通过地址调用不同对象所对应的成员函数;(自己不能显示的去写)

  • this不能在形参和实参显示传递,但是可以在函数内部显示使用!
  • this的实际类型为Date* const this,const修饰的是this指针,因此this指针不能被修改,但是this指向的内容可以被修改!(例如this = nullptr是错误的!)

问题1:this指针存放在哪里?(对象里面,堆,栈,静态区,常量区)

this是形参,因此this指针跟普通的参数一样存放在函数调用的栈帧里面!(函数调用结束后,this指针销毁!)

如果this指针存放在对象里面,那么空类得大小就不会为1!

lea指令的作用是将取d1的地址放到ecx中! 

问题2:

空指针访问是运行的问题,而不是编译的错误!(因此排除A);

  • p调用Print,不会发生解引用,因为Printf的地址不在对象中,p会作为实参传递给this指针;
  • this是空的,但是函数内部没有堆this进行解引用操作,因此不会发生报错!

调用Print()函数是直接调用,没有发生解引用! 

正确答案为: C

问题3:

  • p调用Print,不会发生解引用,因为Printf的地址不在对象中,p会作为实参传递给this指针;
  • this是空的,但是函数内部访问了_a,this->_a,本质上是通过指针进行解引用(汇编指令是解引用)操作找到_a,因此会报错!

正确答案为: B 

访问类的成员函数不能使用::限定符,因为使用.传递的时候,编译器会自动传入this指针,但是使用::的时候不知道需要传入什么值!

如果自己传递的话就不满足this指针不能显示传递的条件!

  1. 点运算符(.

    • 点运算符用于访问对象的实例成员。它表示你在访问某个特定对象的属性或方法。例如,如果你有一个类 Data 的实例 data,你可以通过 data.a 来访问 a 这个成员变量。
  2. 双冒号运算符(::

    • 双冒号运算符通常用于访问类的静态成员或命名空间中的成员。在一些语言(如 C++ 和 PHP)中,ClassName::member 用于访问类的静态属性或方法,而不是实例的属性。

因此,Data::a 通常表示你在尝试访问 Data 类的静态成员 a,而 Data.a 则表示你在访问 Data 类的实例 data 的成员变量 a。如果 a 是一个实例变量,你必须先创建 Data 类的一个实例,然后通过该实例来访问 a

总结:this指针的特性

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

三、使用C和C++实现栈

        C++实现栈本质上和C没有大的区别,但是C++都被封装到一起,使用起来更方便!不需要自己传入指针参数!

        即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。

  • 当我们自己在写代码的时候,经常会忘记Init或者Destory,如果不进行Destory就会造成内存泄露!
  • 有的地方写起来很繁琐!例如下面的,我们还不能直接对Stack进行销毁,还需要先保存值,然后将stack销毁,再返回ret;

引入:构造函数主要完成初始化工作;(不是创建对象,对象已经在函数栈帧自动创建了!会自动调用函数,函数的功能不是创建对象,而是做初始化工作!)

           析构函数主要完成清理工作;(不是创建对象!会自动调用函数,函数的功能不是销毁对象,而是做清理工作!)

四、构造函数

        构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

  1. 函数名与类名相同。
  2. 无返回值。(也不需要写Void)
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。

如果把构造函数设置为private,语法上没有问题,但是直接运行会报错!

当我们不写构造函数的时候,编译器会默认生成构造函数,内置类型不做处理,自定义类型会去调用他的默认构造!

例如VS2013

此时会发现自定义类型_st被初始化,但是内置类型没有被初始化!

但是VS2019会自动对两种变量都进行处理:

结论:自定义类型一定会被初始化,但是内置类型可能不会被初始化,具体结果取决于编译器!

且如果没有自定义类型,内置类型不会被初始化!

  1. 一般情况下,如果有内置类型,就需要自己写构造函数!
  2. 全部都是自定义类型的成员,可以考虑让编译器自己生成!

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值。(这里不是初始化,这里只是声明,这里给的是默认的缺省值,给编译器生成默认构造函数用)

没有给默认值:

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;
_second = 0;}
private:int _hour;int _minute;int _second;
};

给定默认值: 

class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;Date d(2020,2,2);  // 修改默认值return 0;
}

构造函数的调用

普通函数是对象+成员函数;

构造函数是类+对象(参数列表);

且构造函数的调用不能采用以下方式:

Data d1();

没有参数的时候不能这样调用!

这样子写的话会跟函数声明有冲突,编译器不好识别!

可能会把d1当作函数名,返回类型为Data!

注意点:无参和全缺省函数构成函数重载,但是在调用的时候存在歧义!因此也不能同时存在!

        默认构造函数:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
        注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。(即不传参就可以调用的就是默认构造函数!)

        这三个构造函数有且只能存在1个!

  •         class类中默认成员函数有6个!即我们不写但是编译器会自己生成!
  •         但是默认构造函数不一定是编译器自己写的!

五、析构函数

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。(默认生成的对内置类型不做修改,对自定义类型做处理)
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。

  • 内置类型/基本类型,语言本身定义的基本类型int/char/double/指针等;
  • 自定义、用struct/class等等定义的类型;

 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 讯方·智汇云校北京校区
  • Result 和 自定义异常 在前后端交互中的作用
  • 数据结构之算法的分析和应用
  • 多速率信号处理
  • langgraph tool如何获取上下文和RunableConfig
  • C语言从头学58——学习头文件math.h(一)
  • 基于深度学习的蛋白质结构预测
  • 【spring】 Jackson :@JsonIgnore 注解
  • 校园二手数码交易系统小程序的设计
  • nnunetv2系列:torch转onnx
  • AI学习指南深度学习篇-带动量的随机梯度下降法Python实践
  • 技术美术一百问(01)
  • 基于CNN-BiGUR的恶意域名检测方法
  • IDC基础学习笔记
  • Pycharm Remote Development 报错解决
  • @jsonView过滤属性
  • css属性的继承、初识值、计算值、当前值、应用值
  • flask接收请求并推入栈
  • Javascripit类型转换比较那点事儿,双等号(==)
  • JS笔记四:作用域、变量(函数)提升
  • LeetCode18.四数之和 JavaScript
  • Linux下的乱码问题
  • markdown编辑器简评
  • mysql 数据库四种事务隔离级别
  • Object.assign方法不能实现深复制
  • PHP的Ev教程三(Periodic watcher)
  • Python学习之路13-记分
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • vue自定义指令实现v-tap插件
  • 编写高质量JavaScript代码之并发
  • 如何优雅地使用 Sublime Text
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 微信小程序实战练习(仿五洲到家微信版)
  • 正则与JS中的正则
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • #微信小程序:微信小程序常见的配置传旨
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (函数)颠倒字符串顺序(C语言)
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (转载)利用webkit抓取动态网页和链接
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • ../depcomp: line 571: exec: g++: not found
  • .bat批处理出现中文乱码的情况
  • .describe() python_Python-Win32com-Excel
  • .gitignore文件设置了忽略但不生效
  • .net 4.0发布后不能正常显示图片问题
  • .NET Core 项目指定SDK版本
  • .Net CoreRabbitMQ消息存储可靠机制
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .net 获取url的方法
  • .net 受管制代码