【c++】类和对象(二)this指针

Alt

🔥个人主页:Quitecoder

🔥专栏:c++笔记仓

Alt

朋友们大家好,本节内容来到类和对象第二篇,本篇文章会带领大家了解this指针

目录

  • 1.this指针
    • 1.1this指针的引出
    • 1.2this指针的特性
    • 1.3思考题
    • 1.4C语言和C++实现Stack的对比

1.this指针

1.1this指针的引出

首先我们定义一个日期类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 d1, d2;
	d1.Init(2005, 6, 23);
	d2.Init(2024, 3, 25);
	d1.Print();
	d2.Print();

	return 0;
}

我们来思考这么一个问题:

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

在这里插入图片描述
首先思考,这里打印函数,访问的变量是哪里的?

void Print()
	{
		cout << _year << "-" << _month << "-" << _day <<endl;
	}

这里访问的是private声明下的吗?

private:
	int _year;
	int _month;
	int _day;

并不是,因为这里只是声明,并没有开辟空间,真正访问的是实例化的d1,d2

private部分声明的变量_year_month_day等,在类中只是进行了声明,实际上并没有为它们分配内存空间。**内存空间是在创建类的实例(也就是对象)**时为这些成员变量分配的。每个对象都有自己独立的一套成员变量,占用各自的内存空间

因此,当成员函数Print()通过this指针(隐式指向当前对象)访问这些成员变量时,它实际上访问的是调用这个成员函数的那个==特定对象(实例)==的成员变量。每个对象的_year_month_day都存储在各自独立的内存区域中,这些内存区域是在对象被创建时随对象一起分配的

那么我d1,d2如何找到这两个函数呢?

这里就与隐含的this指针有关了

this指针是面向对象编程语言中的一个特殊指针,它指向调用成员函数的那个对象。通过this指针,成员函数可以访问调用它的那个对象的成员变量和成员函数this指针是隐式传递给成员函数的,是成员函数的一个隐含参数

在这里插入图片描述
可以理解为,编译器处理后处理为上述的样子,调用的地方,编译器也会处理:

在这里插入图片描述
它会把调用对象当做形参进行传递

在这里插入图片描述
这里我们也能知道,为什么d1访问能打印d1,d2访问能打印d2

这个东西我们并不陌生,在前面数据结构中我们也有学过:
在这里插入图片描述

1.2this指针的特性

  1. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
  2. this指针的类型:类类型* const,(Date* const this)即成员函数中,不能给this指针赋值,但是this指向的内容可以被改变
    在这里插入图片描述

特点:

  1. 在形参和实参的位置,我们不能显示写出来
    在这里插入图片描述
  2. 在函数内部可以使用

在这里插入图片描述

1.3思考题

一,this指针是存在哪里的?

在这里插入图片描述
不同的数据是存储在不同的区域的,思考一下this指针是存在哪个区域的呢?

const int i = 0;
int j = 1;
cout << &i << endl;
cout << &j << endl;

c++中,const定义的变量是存储在栈中的,我们可以打印它们的地址:

在这里插入图片描述
发现是相邻的

const int i = 0;
int j = 1;
const char* p = "abcdefg";
cout << &i << endl;
cout << &j << endl;
cout << &p << endl;
cout << (void*)p << endl;

在这里插入图片描述
在C++中,变量和数据的存储位置分为几个区域,主要包括栈(Stack)、堆(Heap)、全局/静态存储区(Global/Static Area)和常量区(Constant Pool)。具体到您提供的代码示例中的变量,它们的存储位置如下:

  1. const int i = 0;

    • i是一个常量整型变量。在C++中,const修饰的局部变量默认存储在栈上,但是编译器优化可能会将其存储在程序的只读数据段中(常量区),尤其是当它被视为编译时常量时。然而,取地址操作&i表明i必须在内存中有实际的存储位置,所以它很可能位于栈上,除非进行了特殊的优化
  2. int j = 1;

    • j是一个非const局部变量,存储在栈上。栈用于存储局部变量和函数调用的上下文
  3. const char* p = "abcdefg";

    • 这里p是一个指针,指向一个字符串常量。字符串常量"abcdefg"存储在常量区(也称为字符串字面量区或只读数据段),这是因为字符串字面量在程序的整个生命周期内都不应被修改。而指针p本身(即存储字符串地址的变量)作为局部变量,存储在栈上
  • i(取决于编译器优化)和j存储在栈上。
  • 字符串常量"abcdefg"存储在常量区。
  • 指针p(存储字符串常量的地址)存储在栈上。

在上述的讲解后,我们能够推出this指针的存储位置:this是一个形参,它指向调用该成员函数的对象,this指针在成员函数调用时需要被快速访问并用于访问对象的成员,所以我们推测它存储在栈上

为了提高访问速度,某些编译器可能选择将this指针存储在某个寄存器中,尤其是在成员函数调用时。这实际上可以减少内存访问次数,从而提高程序的执行效率,寄存器是CPU内部的极小量存储器,具有非常高的数据访问速度
在这里插入图片描述

二,判断下面程序的运行结果(this能否是空指针?)

class A
{
public:
	void PrintA()
	{
		cout << "PrintA()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}

在这里插入图片描述
我们发现它是可以正常运行的,我们接下来简单分析一下

尽管p被初始化为nullptr,指向A类型对象的指针p是空的,但PrintA()函数只是打印一条消息,没有访问任何对象的成员变量。这种特殊情况下,代码可运行,主要是因为成员函数的调用并没有实际依赖于this指针指向的对象实例的状态

因为PrintA()不访问对象的任何成员变量,所以这个调用在技术上不需要访问通过this指针指示的内存地址。因此,对于这种不访问任何成员变量的成员函数,通过nullptr调用可能不会导致运行时错误

简单来说,

void PrintA()
	{
		cout << "PrintA()" << endl;
	}

这串代码传递空指针并没有任何影响

接下来看下面的代码:

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}

在这里插入图片描述
这串代码运行异常,因为这里访问a是通过this->_a来实现的

1.4C语言和C++实现Stack的对比

c语言实现

void StackInit(Stack* ps)
{
	assert(ps);
	ps->array = (DataType*)malloc(sizeof(DataType) * 3);
	if (NULL == ps->array)
	{
		assert(0);
		return;
	}
	ps->capacity = 3;
	ps->size = 0;
}
void StackDestroy(Stack* ps)
{
	assert(ps);
	if (ps->array)
	{
		free(ps->array);
		ps->array = NULL;
		ps->capacity = 0;
		ps->size = 0;
	}
}
void CheckCapacity(Stack* ps)
{
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity * 2;
		DataType* temp = (DataType*)realloc(ps->array,
			newcapacity * sizeof(DataType));
		if (temp == NULL)
		{
			perror("realloc申请空间失败!!!");
			return;
		}
		ps->array = temp;
		ps->capacity = newcapacity;
	}
}
void StackPush(Stack* ps, DataType data)
{
	assert(ps);
	CheckCapacity(ps);
	ps->array[ps->size] = data;
	ps->size++;
}
int StackEmpty(Stack* ps)
{
	assert(ps);
	return 0 == ps->size;
}
void StackPop(Stack* ps)
{
	if (StackEmpty(ps))
		return;
	ps->size--;
}
DataType StackTop(Stack* ps)
{
	assert(!StackEmpty(ps));
	return ps->array[ps->size - 1];
}
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->size;
}
int main()
{
	Stack s;
	StackInit(&s);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);
	StackPush(&s, 4);
	printf("%d\n", StackTop(&s));
	printf("%d\n", StackSize(&s));
	StackPop(&s);
	StackPop(&s);
	printf("%d\n", StackTop(&s));
	printf("%d\n", StackSize(&s));
	StackDestroy(&s);
	return 0;
}

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

  • 每个函数的第一个参数都是Stack*
  • 函数中必须要对第一个参数检测,因为该参数可能会为NULL
  • 函数中都是通过Stack*参数操作栈的
  • 调用时必须传递Stack结构体变量的地址

结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据的方式是分离开的

c++实现

typedef struct Stack
{
	DataType* array;
	int capacity;
	int size;
}Stack;
typedef int DataType;
class Stack
{
public:
	void Init()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 3);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = 3;
		_size = 0;
	}
	void Push(DataType data)
	{
		CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	void Pop()
	{
		if (Empty())
			return;
		_size--;
	}
	DataType Top() { return _array[_size - 1]; }
	int Empty() { return 0 == _size; }
	int Size() { return _size; }
	void Destroy()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	void CheckCapacity()
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity * 2;
			DataType* temp = (DataType*)realloc(_array, newcapacity *
				sizeof(DataType));
			if (temp == NULL)
			{
				perror("realloc申请空间失败!!!");
				return;
			}
			_array = temp;
			_capacity = newcapacity;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack s;
	s.Init();
	s.Push(1);
	s.Push(2);
	s.Push(3);
	s.Push(4);

	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Pop();
	s.Pop();
	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Destroy();
	return 0;
}

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

感谢大家阅读!!!后续给大家带来析构函数和构造函数有关内容!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt|多线程串口通信

前篇:Qt|串口通信之同步数据收一包发一包数据 文章目录 创建工程添加串口通信类添加线程类主函数运行结果需求:串口下方的一些耗时操作并不想阻塞主进程的推进; 环境:windows10+VS2017+Qt5.14.2; 写在最前: 串口不支持跨线程操作,需要写信号槽形式传递;选择COM口下发指…

Allegro之轻松绕等长

如大家所见&#xff0c;这个世界通信的速率越来越快&#xff0c;生活的节奏也在飞驰&#xff0c;如果工作还是慢条斯理&#xff0c;你将是下一个淘汰的人。 高速PCB设计避免不了的要绕等长&#xff0c;然而有时候一个单板需要绕等长的总线很多&#xff0c;一个一个的绕下去&…

独立游戏《星尘异变》UE5 C++程序开发日志3——UEC++特供的数据类型

本篇日志将介绍FString&#xff0c;FText、FName的用法和相互转换&#xff0c;以及容器TMap&#xff0c;TArray的增删查改 一、字符串相关数据类型&#xff1a;FString、FText、FName FString是最接近std::string的类型&#xff0c;字符串本身可以看做一个存储char型的动态数…

数据容器-dict以及总结-Python

师从黑马程序员 字典的定义 同样使用{},不过存储的元素是以个个的&#xff1a;键值对&#xff0c;如下语法&#xff1a; #定义字典 my_dict1{"王力宏":99,"周杰伦":88,"林俊杰":77} #定义空字典 my_dict2{} my_dict3dict() print(f"字典1…

HarborCDN技术分析

一、介绍 简要介绍 ​​Harbor​​ 是由VMware公司开源的企业级的Docker Registry管理项目&#xff0c;它包括权限管理(RBAC)、LDAP、日志审核、管理界面、自我注册、镜像复制和中文支持等功能。Harbor 的所有组件都在 Dcoker 中部署&#xff0c;所以 Harbor 可使用 Docker C…

直击GDC 2024 (二):网易智企首次揭秘篮球游戏AI智能体!

近几日&#xff0c;世界级游戏开发者盛会 GDC&#xff08;Game Developers Conference&#xff09;2024 正在美国举行。全球游戏行业的精英都汇聚于旧金山&#xff0c;共同探索前沿的游戏开发技术、洞察行业最新趋势&#xff0c;并互相交流对未来发展的深刻见解。 &#xff08;…

校园跑腿小程序源码系统多校园版 跑腿达人入驻接单 带完整的安装代码包以及系统部署教程

在数字化时代的浪潮中&#xff0c;校园生活的便捷性和高效性成为了广大师生的共同追求。为了满足这一需求&#xff0c;罗峰给大家分享一款适用于多校园的跑腿小程序源码系统——校园跑腿小程序源码系统多校园版。该系统不仅提供了完整的安装代码包&#xff0c;还附带了详尽的系…

你以为的富贵包其实是脂肪瘤 整形外科医生为你科普脂肪瘤

脖子后面长的包都是富贵包吗&#xff1f;西安国际医学中心医院整形外科门诊主任冯登超曾接诊过一位七旬老人&#xff0c;他的颈部后方有一个直径约13厘米的巨大肿块&#xff0c;核磁共振检查提示右侧劲后皮下软组织内巨大囊性肿块。“他们以为是富贵包&#xff0c;实际上是脂肪…

C语言:给结构体取别名的4种方法

0 前言 在进行嵌入式开发的过程中&#xff0c;我们经常会见到typedef这个关键字&#xff0c;这个关键字的作用是给现有的类型取别名&#xff0c;在实际使用过程中往往是将一个复杂的类型名取一个简单的名字&#xff0c;便于我们的使用。就像我们给很熟的人取外号一样&#xff…

【大模型基础】什么是KV Cache?

哪里存在KV Cache&#xff1f; KV cache发生在多个token生成的步骤中&#xff0c;并且只发生在decoder中&#xff08;例如&#xff0c;decoder-only模型&#xff0c;如 GPT&#xff0c;或在encoder-decoder模型&#xff0c;如T5的decoder部分&#xff09;&#xff0c;BERT这样…

详解Python面向对象编程(一)

类和对象 面向过程——怎么做&#xff1f; &#xff08;1&#xff09;把完成某一需求的所有步骤、从头到尾&#xff0c;逐步实现 &#xff08;2&#xff09;根据开发需求&#xff0c;将某些功能独立的代码块封装成一个又一个的函数 &#xff08;3&#xff09;最后完成的代码&a…

3.3 数据定义 数据库与系统概论

目录 3.3.1 模式的定义与删除 1. 定义模式 2. 删除模式 CASCADE&#xff08;级联&#xff09; RESTRICT&#xff08;限制&#xff09; 3.3.2 基本表的定义、删除与修改 表的定义 2.数据类型 3. 模式与表 4. 修改基本表 5. 删除基本表 3.3.3 索引的建立与删除 1. …

开发者的瑞士军刀:DevToys

DevToys&#xff1a; 一站式开发者工具箱&#xff0c;打造高效创意编程体验&#xff0c;让代码生活更加得心应手&#xff01;—— 精选真开源&#xff0c;释放新价值。 概览 不知道大家是否在windows系统中使用过PowerToys&#xff1f;这是微软研发的一项免费实用的系统工具套…

2024-简单点-pandas

pandas pandas to numpy 尽量不用.values提取数据 numexpr 和 bottleneck加速 布尔操作 describe 自定义describe .pipe df.apply 行或者列级别函数级别应用

pod name 传到容器内部环境变量

背景: 部署skywalking需要管理k8s的pod&#xff0c;需要一个参数-Dskywalking.agent.instance_name 要将podname传递给这个参数 -Dskywalking.agent.instance_name{PODNAME} 通过configmap和secret都无法传递。 在容器里通过这两个命令都可以获取到podname 但在env里这样设置或…

模仿羊羊~消消乐

慎玩&#xff01;随机生成、不保证能消完哦&#xff01; 游戏试玩&#xff1a; 链接: https://pan.baidu.com/s/1IwtOd__8Ca0bSouMP8kEzw 提取码: 6yhd

docker配置镜像加速后容器和镜像消失

一、问题描述 根据阿里云给docker配置镜像加速器 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://gt6j98xi.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl rest…

力扣面试150 Pow(x, n) 快速幂 负指数

Problem: 50. Pow(x, n) 解题方法 &#x1f468;‍&#x1f3eb; 参考题解 复杂度 时间复杂度: O ( l o g 2 n ) O(log_{2}n) O(log2​n) 空间复杂度: O ( 1 ) O(1) O(1) Code class Solution {public double myPow(double x, int n){if (x 0.0f)return 0.0d;long b…

最长上升子序列问题

题目&#xff1a;1014. 登山 思路 First 这题也可以看作最长上升子序列&#xff0c;只不过这个序列有三种情况。 先上升&#xff0c;后下降先上升&#xff0c;不下降不上升&#xff0c;直接下降 其实这三种情况我们可以归纳为一种情况&#xff1a;先上升后下降。先上升不下…

C++之多态(二)

一.抽象类 纯虚函数&#xff1a;在虚函数后面加上0就是纯虚函数 作用&#xff1a;纯虚函数规范了派生类必须重写&#xff0c;另外纯虚函数更体现出了接口继承 抽象类定义&#xff1a; 包含纯虚函数的类叫做抽象类&#xff08;也叫接口类&#xff09;&#xff0c;抽象类不能…