14、this指针与静态成员:this指针、静态成员变量、静态成员函数
好,咱们今天聊点实在的。this指针和静态成员,这两个东西在C++里属于「看着简单,用起来容易翻车」的知识点。我刚开始学的时候,觉得this指针不就是个指向自己的指针嘛,有啥好讲的?结果后来在项目里调试一个诡异的崩溃,查了两天才发现是this指针被篡改了……嗯,从那以后我再也不敢小看它了。
14.1 this指针:每个对象都知道自己是谁
先问一个问题:当你在一个成员函数里访问成员变量时,编译器怎么知道你要操作的是哪个对象的变量?
说白了,每个对象调用自己的成员函数时,编译器都会偷偷把对象的地址传进去。这个地址,就是this指针。
class Student {
public:
void setName(const std::string& name) {
// 这里的this指向调用该函数的对象
this->name = name;
}
Student* getThis() {
return this; // 返回当前对象的地址
}
private:
std::string name;
};
int main() {
Student stu1, stu2;
stu1.setName("张三"); // 此时this指向stu1
stu2.setName("李四"); // 此时this指向stu2
return 0;
}
你看,同样的setName函数,因为this指针不同,操作的就是不同的对象。编译器在背后帮我们做了这件事,但我们得知道它的存在。
核心要点:
- this指针是隐含在非静态成员函数里的一个指针
- 它指向调用该函数的对象本身
- 类型是:ClassName * const this(不能修改this的指向)
14.2 this指针的典型用法
我在项目中遇到过一种情况:当函数参数名和成员变量名冲突时,this指针就派上用场了。
class Person {
public:
// 参数名和成员变量名相同,用this区分
void setAge(int age) {
this->age = age; // 左边的age是成员变量,右边的age是参数
}
private:
int age;
};
还有一种常见用法——链式调用。你想想看,如果每个设置函数都返回this指针,是不是就能连续调用了?
class Config {
public:
Config& setWidth(int w) {
width = w;
return *this; // 返回当前对象的引用
}
Config& setHeight(int h) {
height = h;
return *this; // 返回当前对象的引用
}
private:
int width;
int height;
};
// 链式调用
Config cfg;
cfg.setWidth(100).setHeight(200); // 一行搞定两个设置
我个人习惯在需要返回对象自身时,返回引用而不是指针。这样用起来更自然,也符合运算符重载的惯例。
注意:静态成员函数里没有this指针!因为静态函数不属于某个具体对象,它是属于整个类的。你在静态函数里写this->xxx,编译器直接报错。
14.3 静态成员变量:属于类,不属于对象
普通成员变量,每个对象都有一份自己的拷贝。但静态成员变量不一样——它是所有对象共享的。
我举个例子你就明白了。假设你要统计一个类创建了多少个对象:
class Counter {
public:
Counter() {
count++; // 每创建一个对象,计数加1
}
~Counter() {
count--; // 每销毁一个对象,计数减1
}
static int getCount() {
return count;
}
private:
static int count; // 声明静态成员变量
};
// 静态成员变量必须在类外定义并初始化
int Counter::count = 0;
int main() {
std::cout << Counter::getCount() << std::endl; // 输出0
Counter c1;
Counter c2;
std::cout << Counter::getCount() << std::endl; // 输出2
{
Counter c3;
std::cout << Counter::getCount() << std::endl; // 输出3
} // c3被销毁
std::cout << Counter::getCount() << std::endl; // 输出2
return 0;
}
看到没?count变量不属于任何一个对象,它是整个类的。所有对象共享这一个变量。
小技巧:静态成员变量在类内只是声明,必须在类外定义。我建议在.cpp文件里定义,不要在头文件里定义,否则多个cpp文件包含同一个头文件会导致链接错误。
14.4 静态成员函数:没有this,但有全局视野
静态成员函数和静态成员变量是配套的。它只能访问静态成员,不能访问非静态成员。为什么?因为它没有this指针,不知道你要操作哪个对象的成员。
class MathUtils {
public:
static double pi() {
return 3.1415926535;
}
static double circleArea(double radius) {
return pi() * radius * radius; // 可以调用其他静态函数
}
// 错误!静态函数不能访问非静态成员
// double getRadius() { return radius; } // 编译错误
private:
double radius; // 非静态成员
};
int main() {
// 静态函数可以直接通过类名调用
std::cout << "圆周率: " << MathUtils::pi() << std::endl;
std::cout << "面积: " << MathUtils::circleArea(5.0) << std::endl;
return 0;
}
我曾经在项目里用静态函数实现了一个简单的工厂模式。静态函数负责创建对象,并记录创建的总数。这样所有创建操作都集中在一个地方,方便管理。
14.5 知识体系总览
下面这张图把本章的核心知识点串起来了,你看一眼就能明白整体结构:
14.6 避坑指南
讲几个我踩过的坑,你遇到了能少走弯路:
- 静态成员变量初始化顺序:我曾经在多个cpp文件里定义了静态成员变量,结果初始化顺序不确定,导致一个静态变量在初始化时依赖另一个还没初始化的静态变量,程序直接崩溃。解决方案是尽量用函数内的局部静态变量(第一次调用时初始化),或者把初始化逻辑放到一个专门的初始化函数里。
- this指针被误用:在构造函数里调用虚函数时,this指针指向的是正在构造的对象,虚函数可能不会按你期望的方式调用。因为此时派生类还没构造完成。我建议构造函数里不要调用虚函数。
- 静态成员在多线程环境下的问题:所有线程共享同一个静态变量,如果不加锁,数据竞争是必然的。我遇到过线上服务因为静态计数器没加锁,导致统计结果偶尔少几个的情况。
14.7 总结
this指针和静态成员,说白了就是「个体」和「集体」的关系。this指针让你在个体内部知道自己是谁,静态成员让你在集体层面共享数据和行为。
记住几个关键点:
- this指针只在非静态成员函数里有效
- 静态成员变量必须在类外定义
- 静态成员函数没有this,只能访问静态成员
- 静态成员通过类名::成员名的方式访问
嗯,这些概念刚开始可能觉得抽象,但你写几个小例子跑一跑,很快就明白了。我在项目里几乎每天都会用到它们,尤其是静态成员变量做全局计数器、静态函数做工具类,都是非常实用的场景。
个人建议:如果你刚开始学,可以写一个简单的日志类,用静态成员变量记录日志条数,用静态成员函数输出统计信息。这个小练习能帮你把今天讲的内容串起来。
公众号:蓝海资料掘金营,微信deep3321