this 是"当前对象的地址",一个藏在每个普通成员函数里的隐形参数。你写 p.show() 时,编译器实际做的事是 show(&p)——把 p 的地址偷偷传了进去。所以成员函数里的 x 其实就是 this->x,它才知道该读"谁的 x"。三条要点:① this 是指针,不是对象;② 它不能改指向(this = ... 不允许);③ 静态成员函数没有 this(10.4.3 那条限制的根源就在这)。
本节回答的是 10.1.1 埋到现在的一个问题:"成员函数里为什么可以直接写 x,不用写对象名?" 以及 10.4.3 那个限制("静态成员函数不能访问普通成员")背后的原因。用到的旧知识:7.4 指针(this 就是指针)、7.4.5 指针常量(this 属于"指针本身不能改"那一类)、7.8.1 引用("当前对象"也可以用引用来表达:*this)。
一、问题从哪来
class Point {
private:
int x, y;
public:
void set(int a, int b) { x = a; y = b; }
void show() { cout << "(" << x << ", " << y << ")" << endl; }
};
int main() {
Point p1, p2;
p1.set(1, 2);
p2.set(3, 4);
p1.show(); // (1, 2)
p2.show(); // (3, 4)
}
关键问题:p1.show() 和 p2.show() 调的是同一个函数(同一份代码,同一个地址)——那它怎么知道这次该打印 (1,2) 还是 (3,4)?
同一个函数,凭什么给出两种结果?
二、答案:偷偷多传了一个参数
编译器在编译成员函数时,悄悄给每个普通成员函数加了一个"看不见的第一个参数"——就是 this。
你写的:
void show() { cout << x << endl; }
p.show();
编译器心里的版本(概念上等价):
void show(Point *const this) { cout << this->x << endl; }
show(&p);
所以:
| 你看到的 | 实际发生的 |
|---|---|
p.show() |
show(&p) —— 把 p 的地址传进去 |
函数里的 x |
this->x —— "当前对象"的 x |
函数里的 set(a, b) |
this->set(a, b) |
| 静态成员函数没有这个参数 | 所以它不知道自己属于谁 → 不能碰普通成员(10.4.3) |
一句话理解:
this就是"我这次是在给谁办事"。每次调用成员函数,"谁"都不一样,this指着不同的对象——同一个函数因此能服务所有对象。
三、this 的三个性质
| 性质 | 说明 | 违反会怎样 |
|---|---|---|
| ① 它是一个指针 | 类型是 类名 *(如 Point *),所以用 -> 访问 |
写成 this.x 会报错(this 是指针,得写 this->x) |
| ② 不能改指向 | 它的类型严格说是 Point * const this——是指针常量(7.4.5 的"锁指针") |
this = &other; 编译不过:"这一刻我在给谁办事"不能中途换人 |
| ③ 不是对象 | 它只存地址,不占对象的内存(sizeof(Point) 里不含 this) |
别以为"每个对象里存了个 this" |
关于 ③ 的补充:this 不是成员变量,它只是参数。所以:
cout << sizeof(Point) << endl; // 8(两个 int),跟 this 无关
在 const 成员函数里,this 会变成"指向常量的指针":
| 函数 | this 的类型 |
含义 |
|---|---|---|
void show() |
Point * const |
可以改自己的数据 |
void show() const |
const Point * const |
不能改自己的数据(10.4.1 那个承诺的实现方式) |
看到没有——const 成员函数的"承诺",就是靠把 this 变成超窄的类型的实现的。
四、this 的三种实用写法
写法 1:解决"参数名和成员名同名"
这是最常见的用途:
class Point {
private:
int x, y;
public:
void set(int x, int y) { // 参数也叫 x、y(很自然)
this->x = x; // 左边是成员,右边是参数
this->y = y;
}
};
this->x = x; 读作:"把这个对象的 x,设成传进来的那个 x"。
不加 this-> 会怎样:x = x; —— 自己赋值给自己(参数覆盖了参数的视野,成员根本没被碰到)。这个 bug 编译器不报错(3.1.4 讲过的"赋值有值"陷阱的变体),调完发现数据没变,特别容易懵。
写法 2:返回"自己",实现链式调用
class Point {
private:
int x, y;
public:
Point& moveX(int dx) { // 返回"自己的引用"
x += dx;
return *this; // *this 就是"我自己"(7.8.1:*p 是对象本身)
}
Point& moveY(int dy) {
y += dy;
return *this;
}
};
Point p;
p.moveX(3).moveX(2).moveY(5); // 链式调用:一路点下去
为什么能连点:p.moveX(3) 返回的是 p 自己的引用,所以后面还能继续 .moveX(2)。这就是 cout << a << b << c; 那种写法的原理(<< 也返回了流自己的引用)。
注意返回类型是
Point&(引用),不是Point(值):返回值会复制一个新对象(10.2.2),链式调用改的就不是原件了。这里必须返回引用。
写法 3:判断"是不是同一个对象"
bool isSame(const Point &other) const {
return this == &other; // 比地址:同一个对象才有同一个地址
}
比的是地址(7.4.3 的思路),而不是比内容。"是不是同一个东西"和"长得像不像"是两件事。
五、动手 Demo:亲眼看见 this
#include <iostream>
using namespace std;
class Item {
private:
int value;
char name[20];
public:
// ① 参数与成员同名:必须用 this->
void set(int value) {
this->value = value; // 不加 this-> 就是自赋值(没效果)
}
void setName(const char *name) {
strcpy(this->name, name); // 同理
}
// ② 打印 this,看它是不是"对象的地址"
void whereAmI() const {
cout << " this = " << this << endl;
}
// ③ 返回自己,实现链式调用
Item& add(int v) {
value += v;
return *this; // *this = 我自己
}
int get() const { return value; }
// ④ 判断是不是同一个对象
bool isSame(const Item &other) const {
return this == &other;
}
};
int main() {
Item a, b;
a.set(10);
b.set(20);
cout << "对象 a 的地址 = " << &a << endl;
a.whereAmI(); // this 应该等于 &a
cout << "对象 b 的地址 = " << &b << endl;
b.whereAmI(); // this 应该等于 &b
cout << "--- 链式调用 ---" << endl;
a.add(5).add(5).add(10); // 一路点下去
cout << "a 的值 = " << a.get() << "(应为 30)" << endl;
cout << "--- 是不是同一个对象 ---" << endl;
cout << "a.isSame(a) ? " << (a.isSame(a) ? "是" : "否") << endl; // 是
cout << "a.isSame(b) ? " << (a.isSame(b) ? "是" : "否") << endl; // 否
cout << "sizeof(Item) = " << sizeof(Item) << "(不含 this)" << endl;
return 0;
}
看到什么算成功(地址各机器不同,关键是相等关系):
对象 a 的地址 = 0x61ff00
this = 0x61ff00 ← 与 &a 相同
对象 b 的地址 = 0x61fef0
this = 0x61fef0 ← 与 &b 相同
--- 链式调用 ---
a 的值 = 30(应为 30)
--- 是不是同一个对象 ---
a.isSame(a) ? 是
a.isSame(b) ? 否
sizeof(Item) = 24(不含 this)
读结果四句话:
this打印出来的地址,和对应对象的&完全一样——this就是"当前对象的地址",实锤。a.add(5).add(5).add(10)一次连点三下,最后a的值是 30——链式调用靠return *this;实现。a.isSame(b)是"否"——虽然两个对象长得一样(都是Item),但地址不同,不是同一个对象。sizeof(Item) = 24(int4 +char[20]20 = 24,正好对齐)——this不占对象空间。
30 秒实验:把 void set(int value) 里的 this->value = value; 改成 value = value;(去掉 this->),重新运行。a 的值 会变成 0(或垃圾值)——因为那句变成了"参数给自己赋值",成员 value 压根没动。 编译器不报任何错。这个静悄悄的 bug,就是 this-> 存在的意义。
六、常见误区
- "
this是对象"——不是,是指针。所以要用this->x,不能写this.x。"当前对象"本身要写*this。 - "每个对象内部存了一份
this"——不是。this是函数调用时传进来的参数,不占对象的内存(sizeof里没有它)。 - "
this可以被赋值、可以指向别的对象"——不行。它是指针常量(Point * const),this = ...编译不过。"这次在给谁办事"是定死的。 - "静态成员函数里也能用
this"——不能,它压根没有this。这就是 10.4.3 那条"静态成员函数不能访问普通成员"的根本原因。报错invalid use of 'this' in static member function。 - "链式调用返回
Point和返回Point&差不多"——差很多。返回值会复制一个临时对象(10.2.2),你改的是那个临时副本,链式调用就白点了。要链式调用就必须返回引用(Point&),且返回*this。
自测三问(点开对答案)
1. `p.show()` 在编译器眼里是什么样子?`this` 是什么?——大致等价于 **`show(&p)`**:编译器给成员函数**偷偷加了一个"当前对象地址"的参数**,就是 **`this`**。所以函数里的 `x` 其实是 `this->x`。 2. `this` 能改指向吗?它占对象的内存吗?——**不能改指向**(类型是 `T * const`,指针常量,7.4.5 的"锁指针");**不占对象内存**(它是参数,不是成员)。 3. 什么时候必须显式写 `this->`?举两个例子。——① **参数名和成员名同名**时(`this->x = x;`,否则变成自己给自己赋值,还静悄悄不报错);② **返回自身做链式调用**(`return *this;`)时;③ 判"是不是同一个对象"(`this == &other`)。- ☐ 10.4.5 嵌套类(下一节)
相关笔记
- 静态类成员 —— "静态成员函数没有
this"的根源 - 嵌套类 —— 下一节:类里定义的类
- 指向常量的指针与指针常量 ——
this属于"锁指针"那一类 - 访问类成员 ——
const成员函数承诺与this类型的关系 - 引用概述 ——
*this是"我自己",返回引用才不复制 - 🔷 C++基础 · 索引 —— 全课路线图