第三层:C++对象模型和this指针

news2025/1/11 8:38:41

文章目录

  • 前情回顾
  • C++对象模型和this指针
    • 类成员变量和类成员函数的储存
    • this指针
      • this指针概念
      • this指针用途
        • 用途1解释
        • 用途2解释
    • 空指针调用成员函数
    • const修饰的成员变量
      • 常函数内可以被修改的值
  • 突破!步入第四层
  • 本章知识点(图片形式)

🎉welcome🎉
✒️博主介绍:一名大一的智能制造专业学生,在学习C/C++的路上会越走越远,后面不定期更新有关C/C++语法,数据结构,算法,Linux,ue5使用,制作游戏的心得,和大家一起共同成长。
✈️C++专栏:C++爬塔日记
😘博客制作不易,👍点赞+⭐收藏+➕关注

前情回顾

上回说到,在进入第二层之后,我见识到了对象的初始化和清理,封装的力量甚至只是为了给它打基础,但是有惊无险,我也成功掌握,进入到了第三层…

C++对象模型和this指针

进入到第三层,映入眼帘的是一块巨大的石碑,石碑上密密麻麻的都是字和代码,正当我还是迷惑这层的力量是什么的时候,那道声音的主人仿佛看出了我的迷惑,解释随之飘来:“这层的力量是一种隐藏在编译器内的神秘力量,对象模型和this指针,它们两个相辅相成,祝你好运,闯关者…"

类成员变量和类成员函数的储存

在C++中,类内的成员变量和成员函数是分开储存的,只有类内的非静态成员变量才属于这个类的对象,如下图所示。

在这里插入图片描述
那如果类内什么属性都没有,这个类实体化的对象会占用内存空间吗?

#include<iostream>
using namespace std;

class A
{

};
void test1()
{
	A a;
	cout << "对象a的大小是: " << sizeof(a) << endl;
}

int main()
{
	test1();
	return 0;
}

在这里插入图片描述
这是为什么?类内明明没有成员变量,为什么会占用大小?新机子蛙一直摸你肚子!对于C++的编译器而言,每一个对象都应该有一个独一无二的地址,当类内没有非静态成员变量时,创建一个对象,如果不给它一片空间,在遇到这个对象的时候,编译器不知道它开辟的内存空间在哪里,如果这种空对象多,那在未来里面加入非静态成员变量的时候,可能就会产生几个空对象指向同一片空间,出现错误。
当拥有一个非静态成员变量的时候,对象的大小是多少,是数据类型所占字节+1吗?还是会产生对齐,是数据类型的整数倍呢?

#include<iostream>
using namespace std;

class A
{
	int a;
};
void test1()
{
	A a;
	cout << "对象a的大小是: " << sizeof(a) << endl;
}

int main()
{
	test1();
	return 0;
}

在这里插入图片描述
看到了,是数据类型的大小,当里面不是空的时候,编译器就不会给对象这一个字节的大小来确定对象在内存空间中的位置,直接用类内非静态成员变量的位置,也变相的说明了,非静态成员变量是属于类实例化出来的对象的。
那上面说静态成员变量不属于对象,那现在也可以去验证一下。

#include<iostream>
using namespace std;

class A
{
	static int a;
};
void test1()
{
	A a;
	cout << "对象a的大小是: " << sizeof(a) << endl;
}

int main()
{
	test1();
	return 0;
}

在这里插入图片描述
又变成了1,说明我们的静态成员变量是不属于对象的。
那非静态成语函数呢?

#include<iostream>
using namespace std;

class A
{
public:
	void a()
	{

	}
};
void test1()
{
	A a;
	cout << "对象a的大小是: " << sizeof(a) << endl;
}

int main()
{
	test1();
	return 0;
}

在这里插入图片描述
根据这些可以得知,只有非静态成员变量属于类实例化的对象,类内属性和非静态成员函数是分开存放的,静态成员函数也与非静态成员函数一样,不属于类实例化的对象。

this指针

this指针概念

通过上面得知,在C++中,成员变量和成员函数是分开存放的,每一个非静态成员函数都会产生一份函数实例,也就是说,多个同类型的对象在调用函数的会共用同一块空间,那么问题就来了,这同一个函数,是通过上面来区分是哪个对象来调用函数的?这里就C++就提供了一个特殊对象指针——this指针,this指针,它指向被调用的成员函数所属的对象,它隐藏在每一个非静态成员函数内,不需要程序员去定义,可以直接使用,假设现在有三个对象:p1、p2、p3,其中p1要调用函数,其底层就如下图所示:
在这里插入图片描述
就如同这样p1不能直接指向,先指向this指针,这个时候this指针代表了p1,指向要调用的函数。

this指针用途

  1. 当成员函数的形参和成员变量名同名时,可以用this指针来区分
  2. 在非静态成员函数中返回对象本身是,可以使用return *this

用途1解释

当成员函数的形参和成员变量同名时,这个时候如果要对成员变量在成员函数内进行一个赋值的操作时会失败的,可以验证一下:

#include<iostream>
using namespace std;

class A
{
public:
	void a(int b)
	{
		b = b;
	}
	int b;
};

void test1()
{
	A a;
	a.a(10);
	cout << a.b << endl;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
可以看到,属性内b根本没有改变,这是因为,在编译器严重,它认为函数内的b都是形参,解决方法很简单,在右侧的b前面加上this指针,让this指针指向对象a中成员变量b的那块空间,进行改变。

#include<iostream>
using namespace std;

class A
{
public:
	void a(int b)
	{
		this->b = b;
	}
	int b;
};

void test1()
{
	A a;
	a.a(10);
	cout << a.b << endl;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述

用途2解释

当对一个对象进行函数调用时,这个时候想多调用几次,除了使用循环,还有没有别的方法?试试下面这种,看看行不行:

  • 对象名.调用函数.调用函数.调用函数…

尝试使用一下:

#include<iostream>
using namespace std;

class A
{
public:
	void a(int b)
	{
		this->b += b;
	}
	int b;
};

void test1()
{
	A a;
	a.b=0;
	a.a(10).a(10).a(10);
	cout << a.b << endl;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
可以看到报错,报错为表达式必须具有类类型,但它具有类型“void",这是这么回事呢?我们看代码
在这里插入图片描述
成员函数的返回值为void,所以类型不兼容,那把返回类型换成类名呢?

#include<iostream>
using namespace std;

class A
{
public:
	A a(int b)
	{
		this->b += b;
		return *this;
	}
	int b;
};

void test1()
{
	A a;
	a.b=0;
	a.a(10).a(10).a(10);
	cout << a.b << endl;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述

确实不会报错了,但是调用了三次成员函数,为什么结果是这样?而不是30?为什么要返回* this,而不是this?首先,为什么是* this而不是this是因为,this是指针,它指向的是地址,也就是对象a的那块空间,*解引用找到的就是对象a;结果第一次是10,因为返回的类型是类名,就会调用拷贝构造函数,将数据重新放在一份新的空间,而不是原空间,所以这里要返回的值应该是引用:

#include<iostream>
using namespace std;

class A
{
public:
	A& a(int b)
	{
		this->b += b;
		return *this;
	}
	int b;
};

void test1()
{
	A a;
	a.b = 0;
	int b = 10;
	a.a(b).a(b).a(b);
	cout << a.b << endl;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述

空指针调用成员函数

在创建对象的时候,这个时候创建的对象是一个对象指针,并且对这个指针置空,那这个时候让它去访问成员函数,会发生什么?

#include<iostream>
using namespace std;

class A
{
public:
	void a()
	{
		cout << "调用函数a" << endl;
	}
	void b()
	{
		c = 10;
	}
	int c;
};
void test1()
{
	A* p = NULL;
	p->a();
	p->b();
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
在调用时可以发现,为什么只调用了a函数,b函数为什么不能调用?调试看一下:
在这里插入图片描述
看到,是在对非静态成员变量c赋值时产生的错误,错误为this为空指针,那这个给时候不难看出,对于空指针只能调用部分函数,这部分函数为不访问类内成员变量的函数,而访问类内成员变量的函数,则不能访问,是因为this为空指针,没有确定的对象,所以访问不到类内的成员变量。那这个时候就可以优化一下函数,用if或者assert来判断一下this指针是否为空,可以大大提高函数的健壮性。

const修饰的成员变量

当一个程序员不想让木某个函数体内部修改成员的值时,可以在函数后面跟一个const,该函数内部就不能修改成员变量了:

返回类型 函数(参数) const
{
实现功能
}

在this指针中提到,不管写不写this指针,函数内都会调用this指针,this指针属于指针常量,不可以改变指向,但可以改变指针的指向的值,所以this指针的本质就是:

  • 类名 * const this;

那这个时候程序员不仅仅让this指针不能改变指向,也不能改变指向的值时,就可以想上面一样,加const,函数本身也就变成常函数了,这个时候,函数内部就不能改变成员变量了,如果在对象前加一个const,那对象就变成常对象了,也不能修改,同时,常对象只能调用常函数。

常函数内可以被修改的值

那有没有什么方法让常函数内也可以被修改?这里就有一种特殊变量,可以在常函数内进行修改,只需要在成员变量前加上关键字mutable,就可以在常函数内进行修改了。

突破!步入第四层

“轰隆”,一声巨响,面前的巨大石碑轰然倒下,通往下一层的楼梯也出现在眼前…

本章知识点(图片形式)

在这里插入图片描述

😘预知后事如何,关注新专栏,和我一起征服C++这座巨塔
🚀专栏:C++爬塔日记
🙉都看到这里了,留下你们的👍点赞+⭐收藏+📋评论吧🙉

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Matlab中算法结合Simulink求解直流微电网中功率

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

初识 jQuery(JavaScript 框架)

初识 jQuery&#xff08;JavaScript 框架&#xff09;参考描述jQuery使用 jQuery 的开发优势&#xff08;部分&#xff09;获取jQuery 语法基础语法入口函数$()jQuery 与 $参数DOM 与 jQuery模板获取DOM 对象jQuery 对象转换DOM 对象转换为 jQuery 对象$()jQuery 对象转换为 DO…

Linux系统管理中Nginx和python的安装以及python虚拟环境软件的安装与使用(四)

1、Nginx的安装和配置&#xff1a; 说明&#xff1a;Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器&#xff1b;同时也是一个IMAP、POP3、SMTP代理服务器&#xff1b;Nginx可以作为一个HTTP服务器进行网站的发布处理&#xff0c;另外Nginx可以作为反向代理进…

C++:list结构算法

List 1.元素在逻辑上具有线性次序&#xff0c;物理地址不做限制。 2.哨兵节点&#xff0c;header和trailer&#xff0c;封装后外部不可见。 3.重载操作符[]&#xff0c;实现下标和位置转换。 4.有序查找无序查找 5.前插入算法&#xff0c;首先创建新节点 然后使new成为this节点…

设计模式之代理模式(静态动态)代理

前言&#xff1a;二十三种设计模式中的一种&#xff0c;属于结构型模式。它的作用就是通过提供一个代理类&#xff0c;让我们在调用目标方法的时候&#xff0c;不再是直接对目标方法进行调用&#xff0c;而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥…

PHP设计模式

目录 一、使用设计模式目的 二、设计模式的七大原则 三、创建型模式&#xff08;构建型模式&#xff09; 1、单例模式 代码实例 2、工厂模式 2.1、工厂模式——简单工厂模式 简单工厂模式的代码实例 2.2、工厂模式——工厂方法模式 工厂方法模式的代码实例 2.3、工厂…

java开发环境配置及问题排查

Java程序必须运行在JVM之上&#xff0c;所以&#xff0c;我们第一件事情就是安装JDK。 JDK(Java Development Kit)&#xff0c;是Java开发工具包&#xff0c;它提供了Java的开发环境(提供了编译器javac等工具&#xff0c;用于将java文件编译为class文件)和运行环境(提 供了JVM…

Java内存模型和线程安全

Java内存模型和线程安全Java内存模型引言volatile关键字synchronized关键字Java线程Java线程安全synchronized锁优化锁优化技巧列举自旋锁锁消除锁粗化具体实现轻量级锁偏向锁Java内存模型 引言 对于多核处理器而言,每个核都会有自己单独的高速缓存,又因为这多个处理器共享同一…

JavaWeb-会话技术

JavaWeb-会话技术 1&#xff0c;会话跟踪技术的概述 对于会话跟踪这四个词&#xff0c;我们需要拆开来进行解释&#xff0c;首先要理解什么是会话&#xff0c;然后再去理解什么是会话跟踪: 会话:用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&#xff…

反射机制.

文章目录概述两个疑问关于java.lang.Class的理解获取Class实例的方式哪些类型可以有Class对象了解类的加载器掌握加载配置文件的另一种方式创建运行时类的对象体会动态性获取运行时类的完整结构调用运行时类的制定结构每日一考动态代理概述 1、反射是动态语言的关键 2、动态语…

使用Docker打包镜像并发布

1、docker介绍 Docker 是一个开源的应用容器引擎&#xff0c;以镜像的形式进行发布。docker的图标是一个大鲸鱼驮着许多集装箱在海上航行。大鲸鱼就是docker&#xff0c;集装箱就是一个个容器。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口&#xff0c;每个容器都…

高级Spring之BeanFactory 与 ApplicationContext 的区别

ApplicationContext接口 SpringBootApplication public class A01 {private static final Logger log LoggerFactory.getLogger(A01.class);public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {//启动SpringBoot程序…

Python Scipy 显著性检验

Scipy 显著性检验显著性检验&#xff08;significance test&#xff09;就是事先对总体&#xff08;随机变量&#xff09;的参数或总体分布形式做出一个假设&#xff0c;然后利用样本信息来判断这个假设&#xff08;备择假设&#xff09;是否合理&#xff0c;即判断总体的真实情…

Linux基本功系列之userdel命令实战

春节马上要到了&#xff0c;大街上到处都是张灯结彩&#xff0c;喜气洋洋的景象&#xff0c;你们那里也开始了吗&#xff1f; 文章目录一. userdel命令概述二. 语法格式及常用选项语法格式及常用参数三. 参考案例3.1 删除用户但不删除家目录等相关的文件3.2 把用户彻底删除3.3 …

【Linux05-进程控制】进程创建、进程等待、进程退出、进程程序替换(附简易shell实现)

前言 本期分享进程控制的内容。 博主水平有限&#xff0c;不足之处望请斧正&#xff01; 进程的控制主要分为四点&#xff1a; 进程创建进程退出进程等待进程程序替换 进程创建 怎么创建 通过fork创建。 #fork 是什么 创建子进程的函数。&#xff08;使用已经介绍过&am…

Python基础学习 -- 概念

一、变量python的变量定义起来比较随意&#xff0c;不用定义数据类型a123b"123"系统会自动识别a为数值&#xff0c;b为字符串二、关键字定义变量名字的时候&#xff0c;要避开下面的关键字&#xff0c;但是可以通过大小写区分&#xff0c;as123;#错误定义As123;print…

阿里云服务器ECS

云服务器 ECS云服务器ECS&#xff08;Elastic Compute Service&#xff09;是一种简单高效、处理能力可弹性伸缩的计算服务。帮助您构建更稳定、安全的应用&#xff0c;提升运维效率&#xff0c;降低IT成本&#xff0c;使您更专注于核心业务创新。为什么选择云服务器ECS选择云服…

音频如何分割成两段音频?这些实用方法值得收藏

有些时候&#xff0c;我们从网上下载的音频素材可能会出现体积较大、播放时间长等情况&#xff0c;而我们却只需要其中的一小段。这个时候我们就需要借助一些音频分割软件来将重要的音频片段提取出来&#xff0c;从而有助于缩小音频文件的占比以及存储。那么如何如何分割音频呢…

JVM进修之路(一)程序计数器与虚拟机栈

JVM 定义&#xff1a;JVM:Java Virtual Machine&#xff0c;也就是Java运行时所需要的环境&#xff08;Java二进制字节码运行时所需要的环境&#xff09; 好处&#xff1a; 1.java代码一次编写&#xff0c;跨平台运行 2.自动内存管理&#xff0c;垃圾回收 3.数组下标越界检查 4…

千锋Node.js学习笔记

千锋Node.js学习笔记 文章目录千锋Node.js学习笔记写在前面1. 认识Node.js2. NVM3. NPM4. NRM5. NPX6. 模块/包与CommonJS7. 常用内置模块1. url2. querystring3. http4. 跨域jsonpcorsmiddleware&#xff08;http-proxy-middleware&#xff09;5. 爬虫6. events7. File System…