前言
在 C++ 开发体系中,修饰符(限定符 / 说明符)是一组特殊关键字,作用于变量、指针、函数、类、类成员,用于约束实体的访问权限、生命周期、存储位置、可修改性、编译行为、多态特性。相较于 C 语言,C++ 扩充了大量现代化修饰符,覆盖常量语义、面向对象、编译期计算、多线程、异常规范等场景。
很多开发者只会简单使用const、static,却混淆constexpr与const、分不清override与final、不理解引用限定符、mutable适用边界。修饰符并非语法锦上添花,而是构建安全、规范、高性能代码的基础。合理运用修饰符可以在编译阶段提前捕获错误,辅助编译器优化代码,清晰表达程序设计意图。
本文将按照类别系统讲解全部主流 C++ 修饰符,每个概念配套完整可编译代码示例,剖析底层逻辑、常见陷阱、版本差异。
一、CV 基础类型限定符
CV 限定符名称来源于const与volatile,用来描述对象内存数据的可修改属性,作用于类型本身。包含const、volatile、mutable。
1.1 const
const代表常量只读。被修饰实体禁止被修改,所有修改操作触发编译错误。 应用场景:普通变量、指针、函数参数、返回值、类成员函数。
示例 1:修饰局部变量
#include <iostream>
using namespace std;
int main()
{
const int value = 100;
// value = 200; // 编译失败,const变量不可赋值修改
cout << value << endl;
return 0;
}
示例 2:const 修饰指针(经典难点)
区分const修饰指针指向内容 ,还是修饰指针变量本身:
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int y = 20;
// 情况1:指向const int,不能修改*p,但指针可以换指向
const int* p1 = &x;
// *p1 = 999; // 错误
p1 = &y;
// 情况2:指针本身const,指针无法更改指向,内容可修改
int* const p2 = &x;
*p2 = 200;
// p2 = &y; // 错误
// 情况3:双重const,指针与指向内容都不可修改
const int* const p3 = &x;
// *p3 = 111;
// p3 = &y;
return 0;
}
示例 3:const 成员函数
const 写在成员函数尾部,表示函数内不允许修改实例普通成员变量,只能调用其他 const 成员函数。
#include <iostream>
using namespace std;
class Person
{
private:
int age = 18;
public:
// const成员函数
void show() const
{
cout << age << endl;
// age += 1; // 编译报错,禁止修改成员
}
void setAge(int a)
{
age = a;
}
};
int main()
{
const Person p;
p.show();
// p.setAge(30); // const对象只能调用const成员函数
return 0;
}
1.2 mutable
仅用于类内成员变量 。作用:允许在const成员函数中修改该成员。 适用场景:缓存、统计计数、日志标记等不影响对象核心状态的变量。
#include <iostream>
#include <string>
using namespace std;
class Message
{
private:
string text;
mutable int accessCount = 0; // mutable变量
public:
Message(string s) : text(s) {}
void print() const
{
accessCount++; // const函数内正常修改mutable成员
cout << text << " 访问次数:" << accessCount << endl;
}
};
int main()
{
const Message msg("测试文本");
msg.print();
msg.print();
return 0;
}
注意:mutable 不能修饰全局变量、局部变量、静态变量。
1.3 volatile
告知编译器:变量的值可能被程序外部修改(硬件寄存器、中断、其他线程),禁止编译器优化读写操作,每次访问必须从内存读取,不使用寄存器缓存。 多用于嵌入式底层开发、驱动开发;普通业务代码极少使用。
#include <iostream>
using namespace std;
int main()
{
volatile int reg = 0;
while (reg == 0)
{
// 编译器不会优化while循环,持续读取内存reg
}
cout << "寄存器状态改变" << endl;
return 0;
}
可以组合使用:const volatile int data; 代表数据只读,但内容可能被硬件修改。
二、存储类修饰符
存储类修饰符控制变量 / 函数的内存生命周期、作用域、链接属性、存储区域 。关键字:static、extern、thread_local、auto。
register在 C++17 正式弃用,不再讲解。
2.1 static
static 拥有多重语义,位置不同含义完全不同。
- 局部 static 变量:生命周期全局,仅首次进入作用域初始化一次;
- 全局 static 变量 / 函数:内部链接属性,仅当前 cpp 文件可见;
- 类 static 成员:属于类,不属于任何实例,所有对象共享。
示例 1:局部静态变量
#include <iostream>
using namespace std;
void func()
{
static int num = 0; // 只初始化一次
num++;
cout << num << " ";
}
int main()
{
func();
func();
func();
// 输出:1 2 3
return 0;
}
示例 2:类静态成员
#include <iostream>
using namespace std;
class Student
{
public:
static int total;
int id;
Student()
{
total++;
}
};
// 静态成员类外初始化
int Student::total = 0;
int main()
{
Student s1;
Student s2;
Student s3;
cout << "学生总数:" << Student::total << endl;
return 0;
}
2.2 extern
用于声明,告诉编译器:变量或函数定义在其他翻译单元(其他.cpp 文件),不分配内存。常用于头文件跨文件共享。
// a.cpp
#include <iostream>
int globalVal = 666;
// main.cpp
#include <iostream>
using namespace std;
extern int globalVal; // 声明,不是定义
int main()
{
cout << globalVal << endl;
return 0;
}
2.3 thread_local(C++11)
线程局部存储。每个线程拥有该变量独立副本,线程销毁时变量销毁。多线程开发核心修饰符。
#include <iostream>
#include <thread>
using namespace std;
thread_local int num = 0;
void work()
{
num++;
cout << "线程" << this_thread::get_id() << " num=" << num << endl;
}
int main()
{
thread t1(work);
thread t2(work);
t1.join();
t2.join();
return 0;
}
两个线程各自拥有独立 num,互不干扰。
2.4 auto
C++11 起:类型推导,自动推导变量类型; C++98 旧标准:存储类修饰符(栈局部变量),现已废弃。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
auto a = 10; // int
auto b = 3.14; // double
vector<int> vec{1,2,3};
auto iter = vec.begin();
return 0;
}
三、访问权限修饰符(面向对象专属)
仅存在于类 / 结构体内部:public、protected、private,控制类成员外部访问权限。
#include <iostream>
using namespace std;
class Animal
{
private:
// 仅本类内部访问
string secret = "内部信息";
protected:
// 本类 + 派生类可以访问
int age = 2;
public:
// 任何地方均可访问
string name;
void show()
{
cout << secret << age << name << endl;
}
};
class Dog : public Animal
{
public:
void test()
{
cout << age << endl;
// cout << secret; // private无法访问
}
};
int main()
{
Dog d;
d.name = "旺财";
// d.age = 5; // protected外部不可访问
// d.secret;
return 0;
}
补充:struct 默认成员public,class 默认成员private。
四、面向对象继承与多态修饰符
4.1 virtual
两个用途:虚函数实现多态;虚继承解决菱形继承数据冗余。
示例:虚函数多态
#include <iostream>
using namespace std;
class Base
{
public:
virtual void print()
{
cout << "基类打印" << endl;
}
};
class Derived : public Base
{
public:
void print()
{
cout << "派生类打印" << endl;
}
};
int main()
{
Base* p = new Derived();
p->print(); // 动态绑定,调用派生类函数
delete p;
return 0;
}
4.2 override(C++11)
修饰虚函数,显式声明当前函数重写父类虚函数;编译器校验,签名不匹配直接报错,杜绝隐性 bug。
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func(int x)
{
}
};
class Sub : public Base
{
public:
// override强制检查是否正确重写
void func(int x) override
{
cout << "重写成功" << endl;
}
// void func() override; // 编译报错,签名不一致
};
4.3 final(C++11)
两种用法:
-
修饰类:禁止其他类继承此类;
-
修饰虚函数:禁止子类继续重写该函数。
#include
using namespace std;class Parent
{
public:
virtual void test() final
{
}
};// class Child : public Parent
// {
// public:
// void test(){} // 报错,final函数不能重写
// };final class LockClass{};
// class Test : LockClass{}; // final类禁止继承
五、函数高级修饰符
5.1 inline
建议编译器将函数代码展开嵌入调用处,减少函数调用开销。C++17 支持 inline 变量,允许多文件重复定义。
#include <iostream>
using namespace std;
inline int max(int a, int b)
{
return a > b ? a : b;
}
int main()
{
cout << max(10, 20) << endl;
return 0;
}
inline 只是建议,编译器有权拒绝展开。
5.2 noexcept(C++11)
声明函数不会抛出异常,帮助编译器优化。noexcept(true/false)支持条件判断。
#include <iostream>
using namespace std;
void safeFunc() noexcept
{
cout << "不会抛出异常" << endl;
}
int main()
{
safeFunc();
return 0;
}
5.3 成员函数引用限定符(C++11)
& 仅允许左值对象 调用;&&仅允许右值临时对象调用。
#include <iostream>
using namespace std;
class Demo
{
public:
void func() &
{
cout << "左值调用" << endl;
}
void func() &&
{
cout << "右值调用" << endl;
}
};
int main()
{
Demo d;
d.func(); // 左值版本
Demo{}.func(); // 右值版本
return 0;
}
六、编译期常量系列修饰符(C++11 ~ C++20)
这一组是现代 C++ 核心特性,大量用于元编程、常量计算。
6.1 constexpr(C++11)
修饰变量:编译期常量; 修饰函数:可以在编译期执行。
#include <iostream>
using namespace std;
constexpr int calc(int a, int b)
{
return a + b;
}
int main()
{
constexpr int res = calc(10, 20);
cout << res << endl;
return 0;
}
6.2 consteval(C++20)
强制函数只能在编译期执行,无法运行时调用,强于 constexpr。
consteval int square(int n)
{
return n * n;
}
int main()
{
constexpr int s = square(5);
// int x = 6;
// square(x); // 运行时调用直接报错
return 0;
}
6.3 constinit(C++20)
强制静态存储变量在编译期初始化,防止静态初始化顺序崩溃问题。
#include <iostream>
using namespace std;
constinit static int data = 100;
int main()
{
cout << data << endl;
data = 200; // constinit不代表只读,只是初始化要求
return 0;
}
区分重点:
const:运行 / 编译期只读;constexpr:编译期常量,只读;constinit:仅约束初始化时机,变量运行期依旧可修改。
七、修饰符综合易错总结
const位置决定修饰对象,指针语法是最高频考点;static语义随作用域变化,不要一概而论;mutable只配合 const 成员函数使用;override/final不改变运行逻辑,属于编译期安全检查;constexpr/consteval/constinit三者极易混淆,属于现代 C++ 分水岭;volatile面向底层硬件,上层业务代码不要滥用;thread_local为每个线程创建独立变量实例,和 static 完全不同;- 访问控制修饰符只作用于类成员,不能修饰普通变量。
结语
C++ 修饰符构成了一套完整的约束体系。从基础的只读限定const,到控制生命周期的static、thread_local,面向对象的virtual、final,再到 C++11 之后新增的编译期修饰符constexpr、consteval,每一个关键字都承担明确设计目标。
很多开发者习惯于忽略修饰符,随意编写代码,会埋下数据篡改风险、多态隐性错误、内存生命周期隐患。在工程项目中,遵循最小权限原则:能const就不加可变,能noexcept标记异常,派生虚函数尽可能加上override,静态资源合理使用static。
随着 C++ 标准迭代,修饰符体系持续扩充,掌握各类修饰符的语法规则、适用场景、限制条件,读懂每一段代码背后的设计约束,是从 C++ 入门走向工程级开发必不可少的一步。