C++奇迹之旅:隐含的this指针

请添加图片描述

文章目录

  • 📝this指针
  • 🌠 this指针的引出
    • 🌉 this指针的特性
    • 🌉this指针存在哪里
  • 🌠思考
  • 🌠C语言和C++实现Stack的对比
    • 🌉 C语言实现
    • 🌉 C++实现
  • 🚩总结


📝this指针

C++编程中,有一个特殊的指针叫做this指针,它在类的成员函数中扮演着重要的角色。本文将从一个简单的例子开始,逐步探讨this指针的概念、作用和用法。

🌠 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 d1;
	date d2;
	d1.init(2006, 6, 27);
	d2.init(2024, 4, 15);

	d1.print();
	d2.print();

	return 0;
}

对于上述类,有这样的一个问题:
Date类中有 InitPrint 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init
数时,两个Dated1d2调用的是同一个函数,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
在这里插入图片描述
我们先看一个打印函数print(),里面访问的成员变量是哪里的,是private声明下的吗?

void Print()
 {
	 cout <<_year<< "-" <<_month << "-"<< _day <<endl;
 }
 
 private:
 	int _year;     // 年
 	int _month;    // 月
 	int _day;      // 日
};

其实不是,这里只是声明,没有分配空间,访问的是类对象d1,d2各自的成员变量

这里的 _year_month_day 并不是 Print() 函数中访问的成员变量。这些成员变量是在 private 访问说明符下声明的,但是并没有分配空间。实际上,在 Print() 函数中访问的是类对象 d1d2 各自的成员变量。当我们调用 d1.Print()d2.Print() 时,Print() 函数会分别访问 d1d2 对象的 _year_month_day 成员变量。

这里我们是怎么知道d1和d2对象怎么特定访问到各自的成员变量呢?我们在上节第三种猜想可以知道对象的存储方式:只保存成员变量,成员函数存放在公共的代码段
在这里插入图片描述
类的成员函数代码只保存一份,存放在程序的公共代码段中。每个类对象中只保存成员变量的实际数据。对象中不保存任何指向成员函数的指针。
当通过对象调用成员函数时,编译器会根据成员函数的名称和类型,找到对应的函数代码地址,并传入对象自身的this指针,来完成函数的调用
总结来说:当成员函数print()通过this指针,访问这些成员变量时,本质上来说,是访问调用该对象(d1)下的成员函数(print()),里面的函数的访问的成员变量对应是对象(d1)的成员变量。

那类对象d1,d2是怎么找到两个函数的,this 指针是一个指向当前对象的指针。当我们调用一个类的成员函数时,编译器会自动将当前对象的地址传递给 this 指针。通过 this 指针,我们可以访问当前对象的成员变量和成员函数。这在需要区分同名的成员变量和形参时很有用。
在这里插入图片描述
经过编译器处理,他会自动加上this指针,这就是说为什么叫隐含式this,而在调用的时候编译器是这这样的:

d1.print(&d1);
	void print(date* this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
		//cout << d1->_year << "-" << d1->_month << "-" << d1->_day << endl;
	}
	
d2.print(&d2);
	void print(date* this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
		//cout << d2->_year << "-" << d2->_month << "-" << d2->_day << endl;
	}

首先会把调用的对象当做形参进行传递,这就是为什么每个类对象能够稳定的访问自己的类成员变量,这里不就同我们C语言数据传递地址使用指针访问变量嘛
在这里插入图片描述
在这里插入图片描述
总结来说:C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

🌉 this指针的特性

  1. this指针的类型:类型 const,即成员函数中,不能给this指针赋值*。函数的本质原型为void print(date* const this),我们在学指针时【C语言】深入解开指针(二)知道const 后的内容能不能改变,取决于const的位置,因此这里的const限制的this指针,但this指针的内容可以修改。
    在这里插入图片描述
  2. this指针是成员函数第一个隐含的指针,一般情况是由编译器ecx寄存器自动传递,不需要用户传递
    在这里插入图片描述
    注意点:
  3. 实参和形参的位置不能显示写,编译器自己加,但是在类里面可以用
    无论是哪一形参或者实参的哪一个位置显示写了,编译器都会发生报错。
    在这里插入图片描述
    在这里插入图片描述
  4. 只能在“成员函数”的内部使用
    在这里插入图片描述

🌉this指针存在哪里

假设我们有一个简单的 C++ 类 MyClass,它包含一个成员变量 x 和一个成员函数 printX()

class MyClass 
{
public:
    void setX(int value) 
    {
        x = value;
    }

    void printX() 
    {
        std::cout << "x = " << x << std::endl;
    }

private:
    int x;
};

当我们创建一个 MyClass 对象并调用 printX() 函数时,内存中的情况如下:

  1. 在堆内存区域,我们创建了一个 MyClass 对象,其中包含成员变量 x

  2. 当我们调用 printX() 函数时,函数会在栈内存区域分配一块空间,用于存储函数的局部变量和参数。

  3. 在这个函数栈空间中,编译器会自动添加一个隐式的 this 指针参数。这个 this 指针指向当前正在执行 printX() 函数的 MyClass 对象在堆内存中的地址。

示意图如下:

在这里插入图片描述

printX() 函数内部,我们可以使用 this 指针来访问和操作当前对象的成员变量 x。例如:

void printX() 
{
    std::cout << "x = " << this->x << std::endl;
}

这里的 this->x 就是通过 this 指针访问当前对象在堆内存中的成员变量 x

printX() 函数执行完毕并返回时,函数栈空间会被释放,包括 this 指针在内的所有局部变量。但是,对象在堆内存中的成员变量 x 仍然存在,直到对象被销毁。

总结:this指针实际上是存储在栈空间上的。当一个对象调用一个成员函数时,该对象的地址会被传递到栈中,并作为参数传递给成员函数。这个参数即是this指针所指向的对象的地址。因此,this指针所存储的地址也就位于栈上。

每个函数调用都会在栈上分配一段内存空间,用于存储函数的参数、局部变量和其他相关信息。在成员函数被调用时,this指针会作为一个隐含参数传递到函数的参数列表中,并在栈上存储对象的地址。通过this指针,成员函数可以在堆内存中准确地定位和访问对象的成员变量和成员函数

🌠思考

  1. 下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{

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

答案是:正常运行
首选我们知道指针p为nullptr,意思是指向A类型的对象的指针p为空,print()只做了一件事,那就是打印,没有访问对象里的成员变量,Print() 函数只是简单地打印一个字符串,Print() 函数调用并不会尝试访问任何无效的内存地址。
在这里插入图片描述
小结:成员函数的调用如果没有依赖于this指针指向对象实例化的某个成员变量的有效内存地址时,也就是不访问该对象的成员变量的的成员函数,通过nullptr调用不会发生运行时的错误,Print() 函数只是简单地打印一个字符串。那如果我们看看底层汇编:
在这里插入图片描述
好的,让我来解释一下这段汇编代码:

 1. `00B925E5 mov dword ptr [p], 0`
 2. 将指针 `p` 初始化为 `nullptr`(0)。它将 `p` 所指向的内存地址设置为 03. `00B925EC mov ecx, dword ptr [p]`
 4. 将 `p` 的值(也就是 0)加载到 `ecx` 寄存器中。在 x86 架构上,`ecx` 寄存器通常用作函数调用的第一个参数。

 5. `00B925EF call A::Print (0B91442h)`
 6. 调用了 `A::Print()` 函数。由于 `p` 的值为 0(即 `nullptr`),所以这实际上是通过一个空指针来调用成员函数。

可以看出,在VS汇编上,它就把指针存到寄存器中,然后call函数的地址,然后在函数内部,如果用这个指针去访问成员变量,就会发生运行崩溃。

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

答案是:运行崩溃
分析:如前面所说那样,this->_a,等同于空指针访问成员变量,访问内存,会发生运行时的崩溃。
在这里插入图片描述

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

🌉 C语言实现

typedef int DataType;
typedef struct Stack
{
    DataType* array;
    int capacity;
    int size;
}Stack;
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++实现

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


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语言中需用用户自己维护。


🚩总结

请添加图片描述

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

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

相关文章

Vue(二)

文章目录 1.条件渲染1.关于js中的false的判定2.基本介绍3.v-if1.需求分析2.代码实例 4.v-show实现5.v-if与v-show比较6.课后练习 2.列表渲染1.代码实例2.课后练习 3.组件化编程1.基本介绍2.实现方式一_普通方式2.实现方式二_全局组件方式3.实现方式三_局部组件方式 4.生命周期和…

拥抱企业消费新纪元,胜意科技2024代理人大会圆满落幕

因信赖相聚&#xff0c;为共赢而来。近日&#xff0c;由胜意科技主办的“做好生意&#xff0c;拥抱胜意——2024代理人大会”在武汉成功召开&#xff0c;吸引了全国各地百余家TMC生态合作伙伴齐聚一堂&#xff0c;共同探讨数字化浪潮下的差旅管理实践&#xff0c;激发增长新智慧…

十大排序——9.桶排序

这篇文章我们来介绍一下桶排序 目录 1.介绍 2.代码实现 3.总结与思考 1.介绍 桶排序和计数排序一样&#xff0c;都不是基于比较进行排序的。 下面通过一个例子来理解一下桶排序吧。 首先&#xff0c;给你一个无序数组[ 20,18,28,66,25,31,67,30 ]&#xff0c;然后&#…

【GD32】_时钟架构及系统时钟频率配置

文章目录 一、有关时钟源二、系统时钟架构三、时钟树分析四、修改参数步骤1、设置外部晶振2、选择外部时钟源。3、 设置系统主频率大小4、修改PLL分频倍频系数 学习系统时钟架构和时钟树&#xff0c;验证及学习笔记如下&#xff0c;如有错误&#xff0c;欢迎指正。主要记录了总…

【电控笔记2.2】电流回路+延迟效应

延迟效应的来源以及影响 数字控制系统的delay: 5.4节有介绍T0=0.5TS 低通滤波器的时间常数? 滤波器的传递函数与性能参数

C语言入门第四天(数组)

一、C语言数组的基本语法 1.数组的定义 数组是 C 语言中的一种数据结构&#xff0c;用于存储一组具有相同数据类型的数据。数组中的每个元素可以通过一个索引&#xff08;下标&#xff09;来访问&#xff0c;索引从 0 开始&#xff0c;最大值为数组长度减 1。 2.定义语法格式 …

Linux进阶篇:文件传输工具curl命令详解

文件传输工具Linux curl命令详解 一 curl命令介绍 在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具&#xff0c;可以说是一款很强大的http命令行工具。它支持文件的上传和下载&#xff0c;是综合传输工具&#xff0c;但按传统&#xff0c;习惯称url为下载工具。…

leetcode-反转链表

206. 反转链表 题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3…

OpenHarmony南向开发实例:【游戏手柄】

介绍 基于TS扩展的声明式开发范式编程语言&#xff0c;以及OpenHarmony的分布式能力实现的一个手柄游戏。 完成本篇Codelab需要两台开发板&#xff0c;一台开发板作为游戏端&#xff0c;一台开发板作为手柄端&#xff0c;实现如下功能&#xff1a; 游戏端呈现飞机移动、发射…

jenkins构建微信小程序并展示二维码

测试小程序的过程中&#xff0c;很多都是在回头和前端开发说一句&#xff0c;兄弟帮我打一个测试版本的测试码&#xff0c;开发有时间的情况下还好&#xff0c;就直接协助了&#xff0c;但是很多时候他们只修复了其中几个bug&#xff0c;其他需要修复的bug代码正在编写&#xf…

Unity 左右折叠显示与隐藏UI的简单实现

要实现一个简单的UI左右折叠显示与隐藏&#xff0c;可以结合遮罩&#xff0c;通过代码控制UI区块的宽度和位移来实现。 具体可以按以下步骤实现&#xff1a; 1、新建一个Image组件&#xff0c;并添加精灵&#xff0c;调整大小后&#xff0c;复制一份作为该UI的父物体&#xf…

rhce.定时任务和延迟任务项目

一 . 在系统中设定延迟任务要求如下&#xff1a; 在系统中建立 easylee 用户&#xff0c;设定其密码为 easylee 延迟任务由 root 用户建立 要求在 5 小时后备份系统中的用户信息文件到/backup中 确保延迟任务是使用非交互模式建立 确保系统中只有 root 用户和easylee用户可以…

当当图书网数据采集分析:10万条数据的深入洞察

基于搜索结果&#xff0c;我将为您提供一个关于当当图书网数据采集的文章框架&#xff0c;假设我们已经有了10万条数据的采集结果。请注意&#xff0c;由于没有具体的数据文件&#xff0c;以下内容将是一个示例性的框架&#xff0c;您可以根据实际采集到的数据进行填充和调整。…

AI人工智能老师大模型讲师叶梓 OneLLM:开创性的多模态大型语言模型技术

在人工智能领域&#xff0c;多模态大型语言模型&#xff08;MLLM&#xff09;的研究一直是一个热门话题。近期&#xff0c;一种名为OneLLM的创新技术引起了业界的广泛关注。OneLLM通过其独特的统一框架&#xff0c;实现了多种不同模态与自然语言的高效对齐&#xff0c;为多模态…

什么是NAT!

一、NAT&#xff08; network address translation&#xff09; 网络地址翻译 为什么会出现这个技术&#xff0c;目的就是用来解决ipv4 地址不够用的情况&#xff0c;因为在互联网最开始的时候&#xff0c;有一个概念是拥有合法IP地址&#xff0c;每个主机连接到互联网必须要…

Big Data and Cognitive Computing (IF=3.7) 计算机/大数据/人工智能期刊投稿

Special Issue: Artificial Cognitive Systems for Computer Vision 欢迎计算机/大数据/人工智能/计算机视觉相关工作的投稿&#xff01; 影响因子3.7&#xff0c;截止时间2024年12月31日 投稿咨询&#xff1a;lqyan18fudan.edu.cn 投稿网址&#xff1a;https://www.mdpi.com/j…

RK3568笔记二十二:基于TACO的垃圾检测和识别

若该文为原创文章&#xff0c;转载请注明原文出处。 基于TACO数据集&#xff0c;使用YOLOv8分割模型进行垃圾检测和识别&#xff0c;并在ATK-RK3568上部署运行。 一、环境 1、测试训练环境&#xff1a;AutoDL. 2、平台&#xff1a;rk3568 3、开发板: ATK-RK3568正点原子板子…

Ubuntu Vs code配置ROS开发环境

文章目录 1.开发环境2.集成开发环境搭建2.1 安装Ros2.2 安装 Vs code2.3 安装vs code 插件 3.Vs code 配置ROS3.1 创建ROS工作空间3.2 从文件夹启动Vs code3.3 使用Vscode 编译ROS 空间3.4 使用Vs code 创建功能包 4.编写简单Demo实例4.1编写代码4.2编译与执行 1.开发环境 系统…

(文章复现)分布式电源选址定容的多目标优化算法

参考文献&#xff1a; [1]夏澍,周明,李庚银.分布式电源选址定容的多目标优化算法[J].电网技术,2011,35(09):115-121. [2] Ye Tian, Ran Cheng, Xingyi Zhang, and Yaochu Jin, “PlatEMO: A MATLAB platform for evolutionary multi-objective optimization [educational for…

毕设论文的分类号与UDC查询

对于毕业论文分类号与UDC&#xff0c;可以根据个人研究领域查询。 中图分类号查询链接&#xff1a; 中图分类号查询 | 中国图书馆分类法 | 中图法 | 中图分类号 (clcindex.com)https://www.clcindex.com/category/ UDC查询链接: UDC Summaryhttps://udcsummary.info/php/ind…
最新文章