欢迎光临
我们一直在努力

类的高级话题——static、const、友元与嵌套类

类的高级话题——static、const、友元与嵌套类

本篇文章是《C++ 类与对象完全指南:从入门到工程实践》系列的第八篇。

前面七篇我们走过了类的诞生、核心语法、生命周期、拷贝控制、移动语义和内存模型。今天我们要补齐类语法中最后几块拼图:static、const、friend 和嵌套类。这些关键字看似零散,但它们都有一个共同的主题——控制访问权限与作用域。理解了它们,你才算真正掌握了类的完整语法。

先来看一段代码,测试一下你对 static 和 const 的理解:

class Counter {
public:
Counter() { ++count; }
~Counter() { count; }
static int getCount() { return count; }
int getValue() const { return value; } // const 成员函数
private:
int value = 0;
static int count; // 静态成员变量
};
int Counter::count = 0;

int main() {
Counter c1, c2;
cout << Counter::getCount() << endl; // 2(不依赖对象)
cout << c1.getCount() << endl; // 2(也可以用对象调用)
const Counter c3;
// c3.getValue() 能调用吗?c3.getCount() 能调用吗?
}

const 对象能调用哪些成员函数?static 成员函数能不能访问普通成员变量?这些问题看似细节,却在日常开发中频繁出现。

一、static 成员——属于类,而非对象

普通的成员变量和成员函数是“属于对象”的——每个对象都有自己的一份数据。而 static 成员是“属于类”的——所有对象共享同一份数据。

1. static 成员变量

class Student {
public:
Student(const string& name) : _name(name) {
++_totalCount; // 每创建一个学生,计数加一
}
~Student() {
_totalCount; // 销毁时计数减一
}
static int GetTotal() { return _totalCount; }

private:
string _name;
static int _totalCount; // 声明:所有 Student 对象共享
};
int Student::_totalCount = 0; // 定义:必须在类外分配存储空间

三大关键特性:

特性说明
存储位置 静态存储区(数据段),不占用对象内存
共享性 所有对象共享同一份,任何一个对象修改,其他对象都能看到
生命周期 从程序启动到程序结束,与对象无关

必须在类外定义: 类内只是声明,不会分配内存。类外定义时不需要再加 static 关键字(加上也不报错,但语义上多余)。

int Student::_totalCount = 0; // 正确
// static int Student::_totalCount = 0; // 也合法,但没必要

访问方式: 可以通过类名访问(推荐),也可以通过对象访问。

cout << Student::GetTotal() << endl; // ✅ 推荐:不依赖对象
Student s1("张三");
cout << s1.GetTotal() << endl; // ✅ 可以,但不推荐——看起来像依赖对象

2. static 成员函数

static 成员函数没有 this 指针。 这是理解它所有限制的根源。

特性说明
没有 this 指针 因此不能访问非静态成员变量和非静态成员函数
只能访问 static 成员 因为它属于类,不属于任何对象
不能用 const 修饰 const 修饰的是 this 指向的对象,而它没有 this
不能用 virtual 修饰 虚函数依赖 this 指针找虚表

class Demo {
public:
static void StaticFunc() {
// _value = 10; // ❌ 错误:没有 this,无法访问非静态成员
// NonStaticFunc(); // ❌ 错误:同上
_staticValue = 10; // ✅ 正确:可以访问静态成员
}
void NonStaticFunc() {
_staticValue = 10; // ✅ 正确:非静态函数可以访问静态成员
_value = 10; // ✅ 正确
}
private:
int _value;
static int _staticValue;
};

3. 应用场景:单例模式

static 成员最经典的应用就是单例模式(Singleton)——保证一个类只有一个实例。

class Singleton {
public:
// 全局访问点:静态成员函数
static Singleton& GetInstance() {
static Singleton instance; // 局部静态对象:C++11 保证线程安全
return instance;
}

void DoSomething() { cout << "干活" << endl; }

// 禁用拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

private:
// 私有构造:禁止外部创建对象
Singleton() { cout << "Singleton 构造" << endl; }
~Singleton() { cout << "Singleton 析构" << endl; }
};

int main() {
Singleton& s1 = Singleton::GetInstance();
Singleton& s2 = Singleton::GetInstance();
s1.DoSomething();
// Singleton s3; // ❌ 编译错误:构造函数是私有的
// 且 s1 和 s2 是同一个对象
}

为什么单例的构造函数要私有? 因为要阻止外部用 Singleton s; 创建对象,强制用户通过 GetInstance() 获取唯一实例。而 GetInstance() 必须是 static,否则用户得先有对象才能调用它——鸡生蛋问题。

单例的进阶:饿汉 vs 懒汉

模式实现特点
饿汉模式 程序启动时就创建实例 简单、线程安全,但如果没人用会浪费
懒汉模式 第一次调用时才创建(如上面代码) 延迟加载,C++11 后局部静态变量天然线程安全

二、const 成员——承诺不修改

const 在类中有两种用法:const 成员变量和 const 成员函数。后者更重要,也更常用。

1. const 成员变量

const 成员变量必须在初始化列表中初始化(因为构造函数体内的赋值语句无法修改 const 变量)。

class Circle {
public:
Circle(double r) : _radius(r), _PI(3.14159) {} // ✅ 初始化列表
private:
double _radius;
const double _PI; // const 成员:初始化后不能修改
};

2. const 成员函数——只读接口

在成员函数后面加 const,表示这个函数不会修改对象的任何非静态成员变量。

class Date {
public:
int GetYear() const { // const 成员函数:不修改对象
return _year;
}
void SetYear(int year) { // 非 const 成员函数:可以修改对象
_year = year;
}
private:
int _year;
};

核心规则:const 对象只能调用 const 成员函数。

const Date d1; // const 对象
d1.GetYear(); // ✅ 可以调用 const 成员函数
// d1.SetYear(2026); // ❌ 编译错误:const 对象不能调用非 const 成员函数

Date d2; // 普通对象
d2.GetYear(); // ✅ 可以调用 const 成员函数
d2.SetYear(2026); // ✅ 可以调用非 const 成员函数

为什么? 因为 SetYear 的隐藏参数是 Date* const this,而 d1 是 const Date,const Date* 不能传给 Date*。而 GetYear 的隐藏参数是 const Date* const this,可以接收 const Date 的地址。

3. 权限放大与缩小——指针和引用的 const 传递

void Func1(const Date& d) {
d.GetYear(); // ✅ const 引用调用 const 成员
// d.SetYear(); // ❌ 不能调用非 const 成员
}

void Func2(Date& d) {
d.GetYear(); // ✅ 非 const 引用可以调用 const 成员
d.SetYear(1); // ✅ 也可以调用非 const 成员
}

int main() {
const Date d1;
Func1(d1); // ✅ const 对象只能传给 const 引用/指针
// Func2(d1); // ❌ 错误:const 对象不能传给非 const 引用

Date d2;
Func1(d2); // ✅ 非 const 对象可以传给 const 引用(权限缩小,安全)
Func2(d2); // ✅
}

口诀: const 权限只能缩小或平移,不能放大。非 const 对象转 const 是安全的,反过来是危险的。

4. mutable —— const 中的“例外”

mutable 修饰的成员变量可以在 const 成员函数中被修改。它用于那些“逻辑上不影响对象状态”的成员。

class Cache {
public:
int GetData() const {
++_accessCount; // ✅ mutable 成员可以在 const 函数中修改
return _data;
}
private:
int _data = 42;
mutable int _accessCount = 0; // 访问计数:逻辑上不影响对象状态
};

典型应用场景: 缓存、访问计数、互斥锁(mutable std::mutex)。

5. const 成员函数的重载

const 和非 const 成员函数可以构成重载,编译器根据对象的 const 性选择。

class String {
public:
char& operator[](size_t pos) {
cout << "非 const 版本" << endl;
return _str[pos];
}
const char& operator[](size_t pos) const {
cout << "const 版本" << endl;
return _str[pos];
}
private:
char* _str;
};

int main() {
String s1;
s1[0] = 'a'; // 调用非 const 版本

const String s2;
cout << s2[0]; // 调用 const 版本
}

三、友元——突破封装的“特权通道”

封装是好的,但有时候外部的某个函数或类确实需要访问私有成员。为此,C++ 提供了 friend 关键字。

友元的本质: 声明某个函数或类是“自己人”,允许它访问私有成员。友元是单向的、不能继承、不能传递的。

1. 友元函数

class Date {
public:
Date(int year, int month, int day)
: _year(year), _month(month), _day(day) {}

// 声明友元函数:它不是成员函数,但能访问私有成员
friend ostream& operator<<(ostream& out, const Date& d);

private:
int _year, _month, _day;
};

// 友元函数的定义(注意:没有 Date:: 前缀)
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << "-" << d._month << "-" << d._day; // ✅ 可以访问私有成员
return out;
}

int main() {
Date d(2026, 8, 30);
cout << d << endl; // 2026-8-30
}

为什么 operator<< 必须作为友元? 因为它的第一个参数是 ostream&,不是 Date 对象。如果作为成员函数,调用会变成 d << cout,非常别扭。所以只能作为全局函数,但需要访问私有成员 → 只能声明为友元。

2. 友元类

整个类都可以成为另一个类的友元。

class A {
friend class B; // B 是 A 的友元,B 的所有成员函数都能访问 A 的私有成员
private:
int _secret = 42;
};

class B {
public:
void Peek(A& a) {
cout << a._secret << endl; // ✅ B 是友元,可以访问
}
};

注意: 友元关系是单向的。B 是 A 的友元,不代表 A 是 B 的友元。同时,友元关系不能被继承。

3. 友元的优缺点与使用建议

优点缺点
提供受控的访问通道 破坏封装性,增加耦合
简化某些运算符重载 难以维护,修改时需同步更新
提高效率(免去 Getter 调用) 滥用会削弱类的设计价值

使用建议:

  • 优先考虑用 public 成员函数替代友元。
  • 只在实现运算符重载(如 <<)或紧耦合的类对(如容器与迭代器)中使用友元。
  • 不要为了图方便而滥用友元。

四、嵌套类——类中的类

嵌套类(Nested Class) 是定义在另一个类内部的类。

class Outer {
public:
class Inner { // 嵌套类
public:
void DoSomething() { cout << "Inner::DoSomething" << endl; }
};

Inner CreateInner() { return Inner(); }
};

int main() {
Outer::Inner inner; // 访问方式:外部类名::内部类名
inner.DoSomething();

Outer outer;
auto inner2 = outer.CreateInner();
}

嵌套类的核心特性:

特性说明
独立作用域 嵌套类有自己的作用域,与外部类互相独立
访问权限 嵌套类不能直接访问外部类的非静态成员(没有隐式外部对象)
外部访问内部 外部类不能直接访问嵌套类的私有成员(除非声明友元)
名字查找 嵌套类可以访问外部类的 static 成员和类型定义

class Outer {
private:
int _value = 10;
static int _staticValue;
public:
class Inner {
public:
void Test(Outer& o) {
// _value = 1; // ❌ 错误:不能直接访问外部类的非静态成员
o._value = 1; // ✅ 正确:需要显式传对象
_staticValue = 1; // ✅ 正确:静态成员可以直接访问
}
};
};

典型应用:迭代器

STL 中的 list 和 vector 的迭代器,就是用嵌套类实现的。

template <typename T>
class MyVector {
public:
class Iterator { // 嵌套类:迭代器
public:
T& operator*() { return *_ptr; }
Iterator& operator++() { ++_ptr; return *this; }
private:
T* _ptr;
};

Iterator Begin() { return Iterator{_data}; }
Iterator End() { return Iterator{_data + _size}; }
private:
T* _data;
size_t _size;
};

五、综合案例:一个线程安全的计数器

把本篇文章的知识点综合一下,设计一个线程安全的引用计数器:

#include <mutex>

class RefCounter {
public:
RefCounter() : _count(0) {}

void AddRef() const {
std::lock_guard<std::mutex> lock(_mutex); // ✅ mutable 成员
++_count;
}

void Release() const {
std::lock_guard<std::mutex> lock(_mutex);
_count;
}

int GetCount() const { // ✅ const 成员函数
std::lock_guard<std::mutex> lock(_mutex);
return _count;
}

// 禁用拷贝(避免 mutex 被拷贝)
RefCounter(const RefCounter&) = delete;
RefCounter& operator=(const RefCounter&) = delete;

private:
mutable std::mutex _mutex; // ✅ mutable:锁本身不是"逻辑状态"
mutable int _count; // ✅ mutable:引用计数可在 const 函数中修改
};

这个类综合运用了:

  • const 成员函数:GetCount 不修改对象的逻辑状态。
  • mutable:_mutex 和 _count 需要在 const 函数中被修改。
  • 删除拷贝控制:std::mutex 不可拷贝,因此禁用拷贝构造和赋值。

六、总结

概念核心要点
static 成员变量 属于类,所有对象共享;类外定义;不占对象内存
static 成员函数 没有 this 指针,不能访问非静态成员;不能用 const/virtual
const 成员变量 必须在初始化列表中初始化
const 成员函数 承诺不修改对象;const 对象只能调用 const 成员函数
mutable 突破 const 限制,允许在 const 函数中修改成员
friend 友元 单向、不可继承、不可传递;建议少用
嵌套类 独立作用域;不自动访问外部类非静态成员;常用于迭代器

核心思想回顾: static、const、friend、嵌套类——这四个特性看似零散,实则都在回答同一个问题:谁有权访问什么? static 回答“谁能共享数据”,const 回答“谁能修改数据”,friend 回答“谁能突破边界”,嵌套类回答“谁的作用域在哪里”。理解了访问控制的边界,你就理解了 C++ 封装设计的全部精髓。

下篇预告

至此,我们已经完整走过了 C++ 类与对象的全部核心语法——从类的诞生到 this 指针,从构造析构到拷贝控制,从移动语义到内存模型,从 static/const 到友元嵌套类。

但语法只是工具,如何设计出优秀的类才是工程实践的核心。最后一篇,我们将进入《设计类的最佳实践(工程篇)》,结合《Effective C++》的经典条款和真实工程场景,讨论如何设计一个易用、不易误用、可维护的类。

思考题:

  • 为什么 static 成员函数不能是 virtual 的?(提示:想想虚函数依赖什么来找到正确的函数版本)

  • 如果一个 const 成员函数内部需要调用一个非 const 成员函数,该怎么办?除了 mutable,还有没有别的方法?(提示:const_cast)

  • 单例模式中,GetInstance() 返回的是引用而不是指针,这样设计有什么好处?如果返回指针,会带来哪些问题?

  • 欢迎在评论区留下你的思考。下一篇,也是本系列的收官之作,我们将从"会写类"走向"设计好类"——不见不散!

    赞(0)
    未经允许不得转载:171主机测评 » 类的高级话题——static、const、友元与嵌套类
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址