13、面向对象进阶:this指针、静态成员、友元函数与友元类、常量成员函数
好,咱们继续往下走。前面几章我们把类和对象的基本概念讲透了,构造函数、析构函数、拷贝控制,这些你都得心里有数。今天聊的这几个话题,说实话,是C++里比较“绕”但又特别实用的东西。我当年刚学的时候,也被this指针搞得晕头转向,后来在项目里踩了几次坑,才算真正吃透。
这一章的核心,说白了就是四个字:权限与控制。this指针管的是“对象自己是谁”,静态成员管的是“大家共享什么”,友元管的是“谁能破例访问我”,常量成员函数管的是“哪些操作不会改我”。你把这四条线理清了,面向对象才算真正入了门。
13.1 this指针:每个对象都知道自己是谁
先问个问题:当你调用 obj.func() 时,func 函数怎么知道它操作的是哪个对象?
答案就是 this 指针。每个非静态成员函数内部,都隐藏着一个指向当前对象的指针,名字就叫 this。你写代码时可能没注意过它,但它一直在默默工作。
class Student {
private:
string name;
int age;
public:
// 这里的 this 是隐式存在的
void setName(const string& name) {
// 如果不加 this,name = name 就是自己赋值自己,啥也没干
this->name = name; // 左边是成员变量,右边是参数
}
Student& setAge(int age) {
this->age = age;
return *this; // 返回对象自身,支持链式调用
}
};
关键点:
- this 是一个指针常量(
Student *const this),你不能修改它指向的对象 - 在静态成员函数中,没有 this 指针——因为静态函数不属于某个具体对象
- 链式调用(如
obj.setX(1).setY(2))就是靠返回*this实现的
我的经验: 我早期写代码时,经常在构造函数里写 name = name,结果成员变量永远没被赋值。后来养成习惯:只要参数名和成员变量名相同,一律用 this-> 前缀。这个习惯帮我省了不少调试时间。
13.2 静态成员:属于类,不属于对象
静态成员,说白了就是“大家共用的东西”。你用 static 关键字修饰的成员变量或成员函数,它们不属于某个具体对象,而是属于整个类。
我举个例子:假设你要统计一个类创建了多少个对象。你不可能在每个对象里都放一个计数器——那样每个对象只能看到自己的。你需要一个全局的、共享的计数器。这就是静态成员变量的典型应用。
class GameCharacter {
private:
string name;
static int totalCount; // 声明静态成员变量
public:
GameCharacter(const string& n) : name(n) {
totalCount++; // 每创建一个对象,计数加1
}
~GameCharacter() {
totalCount--; // 对象销毁,计数减1
}
static int getCount() { // 静态成员函数
return totalCount;
}
};
// 静态成员变量必须在类外定义并初始化
int GameCharacter::totalCount = 0;
注意:
- 静态成员变量必须在类外单独定义(分配内存),不能在类内初始化(除了 const static 整型)
- 静态成员函数只能访问静态成员,不能访问非静态成员——因为它没有 this 指针
- 调用静态成员函数可以用
类名::函数名(),也可以用对象调用,但推荐前者
我曾经在一个游戏服务器项目里,用静态成员管理全局的在线玩家数量。每个玩家登录时 totalPlayers++,登出时 totalPlayers--。这个变量被多个线程访问,后来还加了原子操作——那是后话了。但静态成员的设计思路,从那时起就刻在我脑子里了。
13.3 友元函数与友元类:打破封装的“特赦令”
友元,英文叫 friend,意思很直白:我把你当朋友,你可以看我的私房钱(私有成员)。
C++ 的封装性要求外部不能直接访问私有成员,但有时候确实需要例外。比如你要写一个全局的打印函数,或者两个类需要紧密协作(比如迭代器和容器)。这时候友元就派上用场了。
class Account {
private:
double balance;
public:
Account(double b) : balance(b) {}
// 声明一个全局函数为友元
friend void showBalance(const Account& acc);
// 声明另一个类为友元
friend class Auditor;
};
// 友元函数可以访问私有成员
void showBalance(const Account& acc) {
cout << "余额: " << acc.balance << endl; // 直接访问私有成员
}
class Auditor {
public:
void audit(const Account& acc) {
if (acc.balance < 0) {
cout << "警告:账户透支!" << endl;
}
}
};
友元的使用原则:
- 友元关系是单向的:A 声明 B 是朋友,不代表 B 也把 A 当朋友
- 友元不能继承:你朋友的儿子不一定是你的朋友
- 友元不能传递:A 的朋友是 B,B 的朋友是 C,不代表 A 和 C 是朋友
- 友元破坏了封装性,能用成员函数解决的问题,尽量别用友元
我的建议: 友元就像一把双刃剑。我在项目中见过有人滥用友元,把类的私有成员暴露得满世界都是,最后维护起来痛不欲生。我的原则是:能用 public 接口解决的,绝不引入友元。只有像运算符重载(比如 << 和 >>)这种必须用友元的场景,才破例使用。
13.4 常量成员函数:承诺不改,就是不改
常量成员函数,就是在函数声明后面加一个 const 关键字。它向调用者承诺:这个函数不会修改对象的任何成员变量(mutable 修饰的除外)。
class Rectangle {
private:
double width, height;
mutable int cacheArea; // mutable 成员即使在 const 函数中也能修改
bool cacheValid;
public:
Rectangle(double w, double h) : width(w), height(h), cacheValid(false) {}
// 常量成员函数:不修改对象状态
double getArea() const {
if (!cacheValid) {
cacheArea = width * height; // 可以修改 mutable 成员
cacheValid = true;
}
return cacheArea;
}
double getWidth() const { return width; }
double getHeight() const { return height; }
// 非 const 函数:可以修改对象
void setWidth(double w) { width = w; cacheValid = false; }
};
重要规则:
- const 对象只能调用 const 成员函数,不能调用非 const 成员函数
- 非 const 对象可以调用 const 成员函数,也可以调用非 const 成员函数
- const 成员函数内部不能修改非 mutable 的成员变量
- const 成员函数内部不能调用非 const 成员函数(因为那可能修改对象)
我记得有一次,我在一个大型项目中重构代码,发现某个类的 getter 函数全都没加 const。结果 const 引用传参时,编译器报了一堆错。我花了整整一个下午,给几十个函数加上 const 限定符。从那以后,我写 getter 的第一件事就是加 const——这已经成了肌肉记忆。
13.5 综合示例:把这些概念串起来
光说不练假把式。咱们写一个综合例子,把 this 指针、静态成员、友元、常量成员函数都用上。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Library; // 前向声明
class Book {
private:
string title;
string isbn;
bool borrowed;
static int totalBooks; // 静态成员:统计总藏书量
public:
Book(const string& t, const string& i)
: title(t), isbn(i), borrowed(false) {
totalBooks++;
}
~Book() { totalBooks--; }
// 常量成员函数
string getTitle() const { return title; }
string getISBN() const { return isbn; }
bool isBorrowed() const { return borrowed; }
// 返回 *this 实现链式调用
Book& borrow() {
borrowed = true;
return *this;
}
Book& returnBook() {
borrowed = false;
return *this;
}
static int getTotalBooks() { // 静态成员函数
return totalBooks;
}
// 声明 Library 为友元类
friend class Library;
};
int Book::totalBooks = 0; // 静态成员初始化
class Library {
private:
string name;
vector<Book*> books;
public:
Library(const string& n) : name(n) {}
void addBook(Book* book) {
books.push_back(book);
}
// 友元类可以访问 Book 的私有成员
void printBookStatus(const Book& book) const {
cout << "书名: " << book.title
<< ", ISBN: " << book.isbn
<< ", 状态: " << (book.borrowed ? "已借出" : "在馆")
<< endl;
}
void printAllBooks() const {
cout << "=== " << name << " 藏书列表 ===" << endl;
for (const auto& book : books) {
printBookStatus(*book);
}
cout << "总藏书量: " << Book::getTotalBooks() << endl;
}
};
int main() {
Book b1("C++ Primer", "978-7-111-58315-6");
Book b2("Effective Modern C++", "978-7-111-58316-3");
Library lib("社区图书馆");
lib.addBook(&b1);
lib.addBook(&b2);
b1.borrow(); // 借出第一本书
lib.printAllBooks();
return 0;
}
代码要点回顾:
Book::totalBooks是静态成员,所有 Book 对象共享同一个计数器getTitle()等函数加了 const,保证 const 对象也能调用borrow()返回*this,支持链式调用(虽然这里没展示链式)Library是Book的友元类,可以直接访问Book的私有成员title、isbn、borrowed
好了,这一章的内容就这些。this 指针让你知道“我是谁”,静态成员让你知道“我们共有多少”,友元让你知道“谁可以破例”,常量成员函数让你知道“哪些操作是安全的”。这四个概念,你在实际项目中几乎天天都会遇到。多写、多练、多踩坑,自然就熟了。
公众号:蓝海资料掘金营,微信deep3321