13、面向对象进阶:this指针、静态成员、友元函数与友元类、常量成员函数

好,咱们继续往下走。前面几章我们把类和对象的基本概念讲透了,构造函数、析构函数、拷贝控制,这些你都得心里有数。今天聊的这几个话题,说实话,是C++里比较“绕”但又特别实用的东西。我当年刚学的时候,也被this指针搞得晕头转向,后来在项目里踩了几次坑,才算真正吃透。

这一章的核心,说白了就是四个字:权限与控制。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,支持链式调用(虽然这里没展示链式)
  • LibraryBook 的友元类,可以直接访问 Book 的私有成员 titleisbnborrowed

好了,这一章的内容就这些。this 指针让你知道“我是谁”,静态成员让你知道“我们共有多少”,友元让你知道“谁可以破例”,常量成员函数让你知道“哪些操作是安全的”。这四个概念,你在实际项目中几乎天天都会遇到。多写、多练、多踩坑,自然就熟了。


公众号:蓝海资料掘金营,微信deep3321