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 知识体系总览

这四个概念其实围绕着一个核心问题:对象如何管理自己、如何与外界交互。我画了一张图帮你理清关系:

面向对象基础(下)核心概念 this 指针 指向当前对象 静态成员 属于类,共享数据 友元 打破封装特权 常量成员函数 承诺不修改数据 核心关系 • this指针:每个成员函数都隐含的参数,指向调用对象自身 • 静态成员:所有对象共享,通过类名直接访问,没有this指针 • 友元:授权外部函数/类访问私有成员,但破坏封装性,慎用 • 常量成员函数:const对象只能调用const函数,mutable例外

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