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

【c++面向对象编程】第6篇:this指针:对象如何知道自己在调用谁?

目录

一、一个看起来“理所当然”的问题

二、this是什么?长什么样?

基本特征

成员函数中的等效写法

三、this的三个典型使用场景

场景1:区分参数和成员变量(最常见)

场景2:链式调用(返回*this)

场景3:在成员函数里传递当前对象给其他函数

四、const成员函数中的this

为什么要有const成员函数?

五、一个完整的例子:理解this的全貌

六、几个容易被问到的细节

1. this可以被赋值吗?

2. this在静态成员函数中存在吗?

3. delete this 合法吗?

4. 空指针调用成员函数会发生什么?

七、这一篇的收获


一、一个看起来“理所当然”的问题

先看一个简单的类:

cpp

class Student { private: string name; int age; public: void setName(string n) { name = n; // 这里的name是谁的name? } };

问题来了:setName函数里只有一个参数n,没有传入任何“对象”的信息。但当我们调用s.setName("张三")时,它怎么知道要把"张三"赋给sname,而不是别的对象的?

你可能会说:“这还用问?就是s的啊!”

对,但编译器也需要知道这个规则。C++的实现方式就是:每个成员函数都隐藏了一个指向调用对象的指针,这个指针叫this

上面的代码,编译器实际处理成类似这样:

cpp

void setName(Student* this, string n) { this->name = n; }

调用s.setName("张三")时,编译器翻译成:

cpp

setName(&s, "张三");

这就是this的本质——它是对象的地址,指向“正在调用成员函数的那个对象”。


二、this是什么?长什么样?

基本特征

cpp

class Demo { public: void print() { cout << "我的地址是:" << this << endl; } }; int main() { Demo d1, d2; d1.print(); // 输出d1的地址 d2.print(); // 输出d2的地址 }

核心要点

  • this是一个指针,指向当前对象

  • 它的类型是ClassName* const(指向类的常量指针)

  • 你可以在成员函数里直接使用this

  • this是编译器自动传给成员函数的,你不能手动传

成员函数中的等效写法

cpp

class Student { private: string name; public: void setName(string n) { this->name = n; // 显式使用this(通常省略) } string getName() { return this->name; // 等价于 return name; } };

this->namename在这里完全等价。大多数时候我们省略this->,但有几个场景必须显式写出来。


三、this的三个典型使用场景

场景1:区分参数和成员变量(最常见)

cpp

class Student { private: string name; public: // 参数名也叫name,和成员变量冲突了 void setName(string name) { name = name; // ❌ 这是自己赋值给自己,成员变量没改! } };

上面的错误太经典了:name = name,左右都是参数name,成员变量纹丝不动。

解决方案:用this区分

cpp

void setName(string name) { this->name = name; // ✅ this->name是成员,右边的name是参数 }

很多编码规范建议:要么参数名加前缀(如_namename_),要么显式用this。我推荐后者,因为它一目了然。

场景2:链式调用(返回*this)

想实现这样的调用:

cpp

Student s; s.setName("张三").setAge(18).setScore(95);

关键是把setXxx函数的返回值设成当前对象的引用,然后return *this

cpp

class Student { private: string name; int age; double score; public: Student& setName(string name) { this->name = name; return *this; // 返回当前对象 } Student& setAge(int age) { this->age = age; return *this; } Student& setScore(double score) { this->score = score; return *this; } void print() { cout << name << ", " << age << ", " << score << endl; } }; int main() { Student s; s.setName("张三").setAge(20).setScore(95.5); s.print(); // 张三, 20, 95.5 }

原理:s.setName("张三")返回s本身(引用),然后继续调用.setAge(20),以此类推。

这种风格在C++标准库里很常见(如cout << a << b << c),也叫流式接口

场景3:在成员函数里传递当前对象给其他函数

有时候需要把当前对象传给另一个函数:

cpp

class Student; class School { public: void registerStudent(Student* s); }; class Student { public: void enroll(School& school) { school.registerStudent(this); // 把当前对象传进去 } };

this就是“我自己”的指针,传出去让其他代码能够访问当前对象。


四、const成员函数中的this

你肯定见过这种写法:

cpp

class Student { private: string name; public: string getName() const { // 注意这个const return name; } };

函数后面的const是什么意思?它修饰的是this指针。

没有const的成员函数this的类型是Student* const(指向非常量的常量指针)

有const的成员函数this的类型是const Student* const(指向常量的常量指针)

也就是说,在const成员函数内部:

  • 不能修改任何成员变量

  • 只能调用其他const成员函数

  • 成员变量即便不是const,也不能被修改

cpp

class Demo { private: int x; public: void modify() const { x = 100; // ❌ 错误!const成员函数不能修改成员 } int getX() const { return x; // ✅ 读取没问题 } };

为什么要有const成员函数?

  1. 常对象只能调用const成员函数

cpp

const Demo d; // 常对象 d.getX(); // ✅ 可以(getX是const) d.modify(); // ❌ 错误(modify不是const)
  1. 表达“只读”的语义:如果函数不会改变对象,就应该标记为const,这是一种契约和文档。

  2. 能被常对象调用也能被非常对象调用:const成员函数是“更友好”的接口。


五、一个完整的例子:理解this的全貌

cpp

#include <iostream> #include <string> using namespace std; class Counter { private: int value; public: Counter() : value(0) {} // 返回引用,实现链式调用 Counter& increment() { value++; cout << "increment: this = " << this << ", value = " << value << endl; return *this; } // 带参数的设置,演示区分同名 Counter& setValue(int value) { this->value = value; // 必须用this return *this; } // const成员函数:只能读取,不能修改 int getValue() const { // value = 100; // 这行放开会编译错误 return value; } // 非const成员函数:可以修改 void reset() { value = 0; } void print() const { cout << "Counter@" << this << " = " << value << endl; } }; int main() { Counter c1, c2; cout << "c1的地址:" << &c1 << endl; cout << "c2的地址:" << &c2 << endl; cout << endl; c1.increment().increment().setValue(10); c1.print(); c2.setValue(99).increment(); c2.print(); cout << "\n--- const对象测试 ---" << endl; const Counter c3; // 常对象 cout << c3.getValue() << endl; // ✅ getValue是const // c3.reset(); // ❌ reset不是const,编译错误 c3.print(); // ✅ print是const return 0; }

输出示例(地址每次运行不同):

text

c1的地址:0x7ffc9e8a2a10 c2的地址:0x7ffc9e8a2a20 increment: this = 0x7ffc9e8a2a10, value = 1 increment: this = 0x7ffc9e8a2a10, value = 2 Counter@0x7ffc9e8a2a10 = 10 Counter@0x7ffc9e8a2a20 = 100 --- const对象测试 --- 0 Counter@0x7ffc9e8a2a30 = 0

注意:每个对象的this就是它自己的地址,和&c1相同。


六、几个容易被问到的细节

1. this可以被赋值吗?

cpp

void func() { this = nullptr; // ❌ 错误!this是const指针,不能修改 }

this的类型是ClassName* const,指针本身是常量,不能指向别处。

2. this在静态成员函数中存在吗?

cpp

class Demo { public: static void func() { cout << this << endl; // ❌ 错误!静态函数没有this } };

静态成员函数属于类,不属于对象,没有this指针。

3. delete this 合法吗?

cpp

class Demo { public: void suicide() { delete this; // 能编译,但极其危险 } };

技术上可以,但你必须确保:

  • 这个对象是用new分配的

  • 析构后不能再访问任何成员

  • 外部不能再delete这个对象

强烈不推荐,属于奇技淫巧,几乎永远有更好的设计。

4. 空指针调用成员函数会发生什么?

cpp

Student* p = nullptr; p->setName("张三"); // 未定义行为,大概率崩溃

除非调用的函数内部没有访问任何成员变量(包括通过this隐式访问),但这种写法极度危险,不要尝试。


七、这一篇的收获

你现在应该完全理解:

  • this是成员函数隐藏的参数,指向调用对象

  • 类型是ClassName* const,不能修改指向

  • 用来区分同名的参数和成员变量

  • 返回*this可以实现链式调用

  • const成员函数中的thisconst ClassName* const,不能修改成员

💡 小作业:写一个StringBuilder类,支持链式调用。要求:

  • append(const string& s)追加字符串,返回自身引用

  • toString()返回最终字符串

使用示例:

cpp

StringBuilder sb; sb.append("Hello").append(" ").append("World"); cout << sb.toString(); // Hello World

下一篇预告:第7篇《static成员:属于类而不是对象的变量和函数》——有些东西应该是全类共享的,比如对象数量、配置参数。static成员变量和成员函数就是为这个设计的,它们不依附于任何具体对象。

http://www.jsqmd.com/news/800099/

相关文章:

  • 如何用Rye与Docker打造无缝Python容器开发环境:完整实践指南
  • 明日方舟基建自动化管理:智能助手让你彻底解放双手
  • 3分钟搭建免费B站视频解析服务:PHP开源工具完全指南
  • 苹果app上架4.3a问题如何解决? 3天极速解决方案,请查收
  • GoCraft存储系统:BoltDB实现游戏数据的持久化
  • 从阿里天池金融风控赛看实战:用XGBoost搞定贷款违约预测的完整流程与避坑指南
  • TQVaultAE终极指南:告别泰坦之旅背包烦恼,开启无限仓库新时代
  • 不止于安装:在CentOS7上为MongoDB配置生产级安全与自启动
  • Tessera:内核级异构GPU分解技术解析与应用
  • 24小时近45亿美元!国产大模型融资狂欢,印奇与杨植麟分道扬镳谁能笑到最后?
  • 自托管AI原生项目管理平台Kanbu:无缝集成MCP与OpenClaw,构建人机协作工作流
  • React Native与Godot引擎融合:JSI桥接实现高性能3D混合应用开发
  • KuboardSpray资源包完全解析:自制离线安装包的完整教程
  • 图腾柱PFC电流尖峰问题分析与改进控制策略
  • AJV $data引用:10个终极动态验证规则实现指南 [特殊字符]
  • Python Redis 缓存策略实战:提升应用性能的最佳实践
  • 语音指令分类模型训练(基于CNN方法)
  • 深入学习 Helm:K8s 的包管理器,管理复杂应用的终极指南
  • Cadence Allegro 17.4保姆级教程:PCB丝印位号重排与反标回原理图完整避坑指南
  • DeepSeek表格制作
  • Tera持久化缓存机制:如何实现毫秒级数据访问
  • 终极穿越机飞控解决方案:Betaflight如何重塑你的飞行体验
  • Kimi融资超376亿商业化成熟,DeepSeek拟募资500亿估值超515亿美元,谁能笑到最后?
  • 2026注塑厂家推荐:电子零配件加工厂+机加工镭雕厂家+钣金加工厂推荐 - 栗子测评
  • 手把手复刻1889年Kallitype专利工艺:用Midjourney生成符合John Spence历史级密度曲线的负片(含Log-C转Kallitype Density Table)
  • 构建智能代码筛选框架:从AST解析到规则引擎的工程实践
  • Windows实时语音转文字终极指南:TMSpeech让离线字幕生成如此简单
  • Python与WebAssembly:在浏览器中运行高性能Python代码实战指南
  • 如何高效进行后端开发中的数据库设计与优化
  • 51单片机项目实战:用LCD12864自制一个温湿度计(带中文界面和自定义图标)