C++中的类: const, static和友元
const
const成员函数
要点:
- 成员函数都有一个隐含参数
this。 - 对于非
const成员函数,this的类型是A* const: 指针是const(不能改指针本身),但指向的对象可被修改。 - 对于
const成员函数(在函数名后写了const),this的类型是const A* const: 指针不可变且指向const A,表示不能修改对象的非mutable成员。 - 成员函数后加
const表示“承诺不修改对象的可观察状态”。对应的隐含this类型是const A* const(指针不可改且指向const对象)。
有时在查询函数中希望做惰性计算/缓存(例如计算 Fibonacci 并缓存结果),这属于“内部实现细节”,从外部观察对象状态并不改变。
为了在const函数内仍能修改这些内部缓存,使用mutable修饰成员。
1 | class A { |
constant expression
常量表达式是在编译期即可计算出结果的表达式:
把计算前移到编译期程序运行得更快且错误也能够更早被发现。
constexpr:允许在编译期求值的函数或变量(也能在运行期使用)。如果上下文要求常量表达式,编译器会在编译期求值。consteval(C++20):强制立即函数,只能出现在函数声明上,调用时必须在编译期求值;如果调用点不是常量表达式会报错。1
2
3
4
5
6
7
8
9// constexpr:可编译期求值,也可运行期调用
constexpr int sq(int x) { return x * x; }
int a[sq(3)]; // 编译期求值 -> a[9]
int n = 4;
int v = sq(n); // 运行期求值(n 只有运行期才知道)
// consteval (C++20):必须在编译期求值
consteval int two() { return 2; }
int b = two(); // 必须是编译期求值适用场景:数组大小、非类型模板实参、
switch的case标签、查表/位运算等需要编译期常量的地方。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17enum Flags { GOOD=0, FAIL=1, BAD=2, EOF=3 };
int operator|(Flags a, Flags b); // 在这个表达式前面加上constexpr就可以通过
// 或者用 constexpr 辅助函数:
// constexpr int bad_c() { return int(BAD); }
// constexpr int eof_c() { return int(EOF_); }
// constexpr int be_c() { return bad_c() | eof_c(); }
// case be_c(): // 合法
void f(Flags x) {
switch(x) {
case BAD: break;
case EOF: break;
case BAD | EOF: // 可能报错:不是编译期整常量
break;
}
}
static
静态成员是属于类本身的成员(类级别),不是某个对象的成员。
同一类的所有对象共享同一份静态数据(只有一份拷贝)。
当需要不同对象共享一个变量时,不用把它做全局变量(否则缺乏封装并产生命名污染)。
static成员变量
1 | class A { |
- 同一类的所有对象共享同一份静态数据(只有一份拷贝)。
- 声明与定义:
- 在类内声明:
static int shared。 - 在类外定义并初始化(非 inline 情况):
int A::shared = 0。 - C++17起可用
inline初始化:inline static int shared = 0(不需类外定义)。
- 在类内声明:
static成员函数
1 | class A { |
- static函数没有隐含的
this指针,因此不能访问非静态成员(实例成员x)或this。 - 静态函数可以访问和修改静态成员变量(类级别的共享数据)。
- 遵循类访问控制
static成员的使用
因为C++是支持观点”类也是对象”的,所以可以通过A::f()或obj.f()(推荐用第一种方式访问)。这也是拷贝构造函数和移动构造函数的基础。
示例(单例模式的实现):
1 | class singleton { |
友元
因为类的访问控制规定了类的外部不能访问该类的private成员,只能通过该类提供的public方法当作接口来访问私有成员,但是这会降低对私有成员的访问效率并且缺乏灵活性。
为了解决上述的问题提出了友元:
- 分类:
- 友元函数
- 友元类
- 友元类成员函数
- 作用:
- 允许外部函数/类访问本类的
private/protected成员(绕过普通访问控制)。 - 提高设计灵活性,常用于实现高效的操作/接口。
- 在“数据保护”和“访问效率”之间提供一种折中方案:既能保持封装界面,又能避免过多的访问器函数开销。
- 允许外部函数/类访问本类的
1 | void func(); |
- 友元不具传递性:A把f声明为友元并不自动让f成为B的友元;要访问B的私有成员,必须也在B中声明f为友元。
- 友元不继承:派生类不会继承基类给某函数的友元资格。
1 | // 前向声明 |
前向声明尽管在某些条件下是可以省略的,但可能会出现命名污染等问题,所以在使用友元时一定要保证友元在此之前已被恰当声明。
评论





