9、面向对象基础(下):this指针、静态成员(static)、友元函数与友元类、常量成员函数
好,咱们接着往下聊。上一章我们把构造函数、析构函数、拷贝构造这些“对象的生老病死”讲清楚了。这一章,咱们要深入对象的“内部运作机制”和“特殊权限”。
说白了,就是四个东西:this指针、静态成员、友元、常量成员函数。这四个概念,你在实际项目中几乎天天都会碰到。我当年刚入行时,就在静态成员初始化上栽过跟头,编译报错找了半天……嗯,咱们一个一个来。
9.1 this指针:对象自己的“身份证”
每个对象都有一个隐藏的指针,叫 this。它指向对象自己。你写的每个成员函数,其实都偷偷带着这个参数。
核心理解: this 就是“当前对象的地址”。在成员函数内部,你可以用 this 来访问自己的成员变量或成员函数。
举个例子:
class Student {
public:
void setAge(int age) {
// 这里的 this->age 就是成员变量 age
// 右边的 age 是参数
this->age = age;
}
private:
int age;
};
你想想看,如果参数名和成员变量名一样,不加 this-> 就分不清了。我个人习惯是,成员变量加个前缀比如 m_age,这样就不用每次都写 this 了。但有些团队规范要求必须用 this,这都行,看你们项目风格。
小技巧: 链式调用(return *this)是 this 指针的经典用法。比如 Qt 的 QTextCursor 就大量用了这个模式。
class Builder {
public:
Builder& setX(int x) { this->x = x; return *this; }
Builder& setY(int y) { this->y = y; return *this; }
private:
int x, y;
};
// 用法:builder.setX(10).setY(20);
9.2 静态成员:属于类,不属于对象
静态成员(static)是很多新手容易搞混的点。说白了,静态成员变量是所有对象共享的,而不是每个对象各有一份。
我举个例子你就明白了:
class Game {
public:
Game() { playerCount++; }
~Game() { playerCount--; }
static int getPlayerCount() { return playerCount; }
private:
static int playerCount; // 声明
};
// 必须在类外定义并初始化
int Game::playerCount = 0;
这里 playerCount 是所有玩家对象共用的计数器。你创建一个新玩家,它就加1;销毁一个,它就减1。用静态成员函数 getPlayerCount() 来获取当前在线人数。
注意: 静态成员变量必须在类外单独定义!这是 C++ 的语法要求。我见过不少新手在类内部直接初始化静态成员,结果编译报错“undefined reference”。
静态成员函数有几个特点:
- 没有 this 指针(因为它不属于某个具体对象)
- 只能访问静态成员变量和静态成员函数
- 可以通过类名直接调用:
Game::getPlayerCount()
我在项目中常用静态成员来做“单例模式”或者“全局计数器”。比如日志系统,整个程序只需要一个日志实例,用静态成员来管理就很合适。
9.3 友元函数与友元类:打破封装的特权
C++ 的封装性很好,private 成员外面不能直接访问。但有时候,你就是需要让某个外部函数或类访问你的私有成员。这时候就用 友元。
友元分两种:友元函数 和 友元类。
9.3.1 友元函数
友元函数不是类的成员函数,但它可以访问类的私有成员。声明时用 friend 关键字:
class Matrix {
friend Matrix operator+(const Matrix& a, const Matrix& b);
public:
Matrix(int rows, int cols) : data(rows, std::vector<int>(cols)) {}
private:
std::vector<std::vector<int>> data;
};
Matrix operator+(const Matrix& a, const Matrix& b) {
// 这里可以直接访问 a.data 和 b.data
Matrix result(a.data.size(), a.data[0].size());
// ... 计算逻辑
return result;
}
为什么要用友元?因为运算符重载时,左操作数可能不是当前类的对象。比如 cout << obj,你不能把 operator<< 写成成员函数,因为左操作数是 ostream。这时候友元就派上用场了。
9.3.2 友元类
友元类更“狠”——整个类都是友元,它的所有成员函数都能访问你的私有成员。
class Engine {
friend class Mechanic; // Mechanic 可以访问 Engine 的私有成员
private:
int horsepower;
void start() { /* ... */ }
};
class Mechanic {
public:
void tune(Engine& e) {
e.horsepower += 50; // 直接访问私有成员
e.start(); // 直接调用私有函数
}
};
我曾经踩过的坑: 友元关系不能传递!A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元。而且友元关系是单向的,不能反过来。设计时要谨慎,别滥用友元,否则封装性就形同虚设了。
9.4 常量成员函数:承诺不改数据
常量成员函数,就是在函数声明后面加个 const。它告诉编译器:这个函数不会修改对象的任何成员变量。
class Student {
public:
int getAge() const { // 常量成员函数
return age;
}
void setAge(int a) { // 非常量成员函数
age = a;
}
private:
int age;
};
为什么需要这个?因为 const 对象只能调用 const 成员函数。如果你有一个常量引用或常量指针指向对象,你只能调用那些承诺不改数据的函数。
void printStudent(const Student& s) {
// s 是 const 引用
std::cout << s.getAge(); // 正确:getAge 是 const
// s.setAge(20); // 错误:setAge 不是 const
}
这里有个细节:常量成员函数内部不能修改成员变量(除非成员变量加了 mutable 关键字)。mutable 是个特例,用于那些“逻辑上不影响对象状态”的成员,比如缓存、计数器等。
我的建议: 只要函数不修改成员变量,就加上 const。这不仅是语法要求,更是一种“契约”——告诉调用者“你放心用,我不会改你的数据”。在 Qt 中,几乎所有 getter 函数都是 const 的。
9.5 知识体系总览
这四个概念其实围绕着一个核心问题:对象如何管理自己、如何与外界交互。我画了一张图帮你理清关系:
9.6 综合示例:一个简单的游戏角色类
咱们把今天学的四个概念串起来,写一个完整的例子:
#include <iostream>
#include <string>
class Character {
friend std::ostream& operator<<(std::ostream& os, const Character& c);
public:
Character(const std::string& name) : name(name) {
totalCharacters++;
}
~Character() {
totalCharacters--;
}
// 常量成员函数
std::string getName() const { return name; }
int getHealth() const { return health; }
// 非常量成员函数
void takeDamage(int dmg) {
health -= dmg;
if (health < 0) health = 0;
}
// 静态成员函数
static int getTotalCharacters() {
return totalCharacters;
}
// 链式调用示例(this指针)
Character& setHealth(int h) {
this->health = h;
return *this;
}
private:
std::string name;
int health = 100;
static int totalCharacters; // 静态成员变量声明
};
// 静态成员变量定义
int Character::totalCharacters = 0;
// 友元函数实现
std::ostream& operator<<(std::ostream& os, const Character& c) {
os << "Character: " << c.name
<< ", Health: " << c.health
<< ", Total: " << Character::getTotalCharacters();
return os;
}
int main() {
Character hero("Hero");
Character monster("Monster");
hero.setHealth(80).takeDamage(10); // 链式调用
std::cout << hero << std::endl; // 友元函数输出
std::cout << monster << std::endl;
std::cout << "Total characters: "
<< Character::getTotalCharacters() << std::endl;
return 0;
}
这个例子把 this 指针(链式调用)、静态成员(计数器)、友元函数(operator<<)、常量成员函数(getter)都用上了。你可以在自己的编译器上跑跑看。
总结一下:
- this指针:对象自己的地址,用于区分成员和参数、实现链式调用
- 静态成员:属于类而非对象,所有实例共享,类外定义
- 友元:授权外部访问私有成员,谨慎使用,别破坏封装
- 常量成员函数:承诺不改数据,const 对象只能调用 const 函数
嗯,这一章的内容就到这儿。这四个概念你掌握好了,面向对象的基础就算打扎实了。下一章咱们要聊继承和多态,那才是 C++ 面向对象的精髓所在。
公众号:蓝海资料掘金营,微信deep3321