目录
[1. const (常量)](#1. const (常量))
[1.1 修饰变量和指针](#1.1 修饰变量和指针)
[1.2 修饰函数参数和返回值](#1.2 修饰函数参数和返回值)
[1.3 修饰类成员函数(C++ 特有)](#1.3 修饰类成员函数(C++ 特有))
[1.4 代码示例:](#1.4 代码示例:)
[2. static (静态)](#2. static (静态))
[2.1 修饰局部变量(改变存储期)](#2.1 修饰局部变量(改变存储期))
[2.2 修饰全局变量和函数(改变链接性)](#2.2 修饰全局变量和函数(改变链接性))
[2.3 修饰类成员变量(C++ 特有)](#2.3 修饰类成员变量(C++ 特有))
[2.4 修饰类成员函数(C++ 特有)](#2.4 修饰类成员函数(C++ 特有))
[2.5 代码示例:](#2.5 代码示例:)
[3. volatile (易失性)](#3. volatile (易失性))
[3.1 阻止编译器优化](#3.1 阻止编译器优化)
[3.2 常见应用场景:](#3.2 常见应用场景:)
[3.3 与 const 的结合](#3.3 与 const 的结合)
[4. explicit (显式)](#4. explicit (显式))
[4.1 修饰单参数构造函数](#4.1 修饰单参数构造函数)
[4.2 修饰转换函数(C++11/14)](#4.2 修饰转换函数(C++11/14))
1. const (常量)
定义: 用于声明常量或指定不允许修改的数据。它强制执行"常量正确性"(Const Correctness),是 C++ 中提高代码健壮性和可读性的核心机制。
核心用法与考点:
1.1 修饰变量和指针
理解 const
靠近谁,谁就是常量。
声明方式 | 含义 | 可修改性 |
---|---|---|
const int a = 10; |
常量整数 | a 的值不可改 |
const int* p; |
指向常量的指针 | *p (值)不可改,p (地址)可改 |
int* const p; |
常量指针 | *p (值)可改,p (地址)不可改 |
const int* const p; |
指向常量的常量指针 | *p 和 p 都不可改 |
1.2 修饰函数参数和返回值
-
修饰参数: 使用
const
引用 (const T&
) 传递对象,可以避免不必要的拷贝(效率高),同时保证函数内不会修改原始对象(安全性高)。 -
修饰返回值: 限制调用者不能修改返回的对象。
1.3 修饰类成员函数(C++ 特有)
在成员函数末尾加上 const
,表示该函数是一个 常成员函数。
-
常成员函数不能修改类的任何非
static
成员变量(除非该成员被声明为mutable
)。 -
常对象只能调用常成员函数。
1.4 代码示例:
cpp
#include <iostream>
#include <vector>
//-------------------------------------------
// 1.1 const 修饰变量 / 指针
//-------------------------------------------
void demo_pointer() {
int x = 5, y = 10;
const int a = 42; // a 只读
// a = 43; // ❌ 编译错误:a 是常量
const int* p1 = &x; // *p1 只读,p1 可改
// *p1 = 100; // ❌ 不能改指向的值
p1 = &y; // ✅ 可以改指向
int* const p2 = &x; // p2 只读,*p2 可改
*p2 = 100; // ✅ 可以改值
// p2 = &y; // ❌ 不能改指针本身
const int* const p3 = &x; // 全只读
// *p3 = 200; p3 = &y; // ❌ 都动不了
}
//-------------------------------------------
// 1.2 const 修饰函数参数 & 返回值
//-------------------------------------------
// 参数:大对象只读传参,零拷贝又安全
void printVec(const std::vector<int>& v) { // v 只能读
// v.push_back(9); // ❌ 编译错误:v 是 const
for (int n : v) std::cout << n << ' ';
std::cout << '\n';
}
// 返回值:阻止调用者改临时量
const int getMax(const int& a, const int& b) {
return (a > b ? a : b);
}
//-------------------------------------------
// 1.3 const 成员函数
//-------------------------------------------
class Counter {
int value_ = 0;
mutable int access_ = 0; // mutable 允许在 const 函数里改
public:
int get() const { // ① 常成员函数
++access_; // ✅ mutable 成员可改
return value_;
}
void inc() { ++value_; } // 普通函数可改成员
// 重载:const / 非 const 版本
int& data() { return value_; } // 非 const 对象调用
const int& data() const { return value_; } // const 对象调用
};
int main() {
demo_pointer();
std::vector<int> nums{1, 2, 3};
printVec(nums); // 1 2 3
int m = getMax(3, 7);
std::cout << "max=" << m << '\n'; // max=7
// getMax(3,7) = 99; // ❌ 返回的是 const int,不能当左值
Counter c1;
c1.inc();
std::cout << "c1=" << c1.get() << '\n'; // 1
const Counter c2; // ② 常对象
// c2.inc(); // ❌ 只能调 const 成员函数
std::cout << "c2=" << c2.get() << '\n'; // 0
// c2.data() = 5; // ❌ 返回 const int&,不能赋值
}
4 句话再总结
-
const
靠谁,谁只读;靠*
左边还是右边,决定"值"还是"指针"只读。 -
const T&
传参 = 零拷贝 + 防篡改,是 C++ 最常用签名。 -
成员函数末尾加
const
= 隐式 this 指针前加 const,内部不能改普通成员。 -
常对象只能调常成员函数;需要统计/缓存时用
mutable
打洞。
2. static (静态)
定义: static
关键字改变了变量或函数的存储期(Storage Duration)、作用域(Scope)和链接性(Linkage)。
核心用法与考点:
2.1 修饰局部变量(改变存储期)
-
作用: 局部变量的生存期从函数调用结束延伸到整个程序运行期间。
-
特点: 只初始化一次。
2.2 修饰全局变量和函数(改变链接性)
-
作用: 将全局变量或函数的外部链接性(External Linkage)改为 内部链接性(Internal Linkage)。
-
特点: 它们只能在当前文件(编译单元)内访问和使用,对其他文件隐藏。这有助于避免命名冲突。
2.3 修饰类成员变量(C++ 特有)
-
作用: 静态成员变量 属于整个类,而不是任何特定的对象。
-
特点: 整个类只有一个副本,所有对象共享。必须在类外进行定义和初始化。
2.4 修饰类成员函数(C++ 特有)
-
作用: 静态成员函数 属于整个类,不接收隐式的
this
指针。 -
特点: 静态成员函数只能直接访问类的静态成员(变量和函数),不能访问非静态成员。
2.5 代码示例:
cpp
//============ file: main.cpp ============
#include <iostream>
//----------------------------------------
// 2.2 全局 static:内部链接,别的文件看不到
//----------------------------------------
static int g_hidden = 999; // 只在 main.cpp 有效
static void hiddenFunc() { // 同上,链接器对外"隐藏"
std::cout << "hiddenFunc: " << g_hidden << '\n';
}
//----------------------------------------
// 2.1 局部 static:存储期变成"整个程序运行期"
//----------------------------------------
void visitCount() {
static int count = 0; // ① 只有第一次进来才初始化
++count;
std::cout << "第 " << count << " 次调用 visitCount\n";
}
//----------------------------------------
// 2.3 + 2.4 类静态成员
//----------------------------------------
class Logger {
public:
// 非静态成员:每个对象各一份
std::string name_;
// 静态数据成员:整个类只有一份,必须类外再定义一次
static int instanceCount; // ② 只是"声明",不是"定义"
// 静态成员函数:没有 this,只能访问静态成员
static void printCount() {
std::cout << "当前实例总数 = " << instanceCount << '\n';
// std::cout << name_; // ❌ 编译错误:没有 this,不能访问非静态成员
}
// 构造函数:每创建一个对象就 ++
explicit Logger(const std::string& n) : name_(n) {
++instanceCount;
}
// 析构函数:对象销毁就 --
~Logger() { --instanceCount; }
};
// ② 类静态数据成员的"真正定义"放在类外(只能有一次)
int Logger::instanceCount = 0;
//============ main 函数:演示全部效果 ============
int main() {
std::cout << "----- 局部 static -----\n";
visitCount(); // 第 1 次
visitCount(); // 第 2 次
visitCount(); // 第 3 次
std::cout << "\n----- 全局 static -----\n";
hiddenFunc(); // 能调到,因为同文件
std::cout << "\n----- 类 static -----\n";
Logger::printCount(); // 0 ,还没有对象
{
Logger a("A");
Logger::printCount(); // 1
Logger b("B");
Logger::printCount(); // 2
} // a、b 析构
Logger::printCount(); // 0 再次回到 0
// 不需要对象,直接类名调用静态函数
Logger::printCount();
}
4 句话再总结
-
局部
static
让变量跨越函数调用继续存活。 -
全局
static
让名字跨不出当前源文件。 -
类静态数据成员只有一份 ,类外必须再定义一次。
-
类静态成员函数没有 this ,只能玩静态成员,调用时可以不要对象。
3. volatile (易失性)
定义: 用于告诉编译器,变量的值可能会在程序控制流之外被意外地修改(即"易失")。
核心用法与考点:
3.1 阻止编译器优化
当编译器看到一个变量的值在两次使用之间没有被程序修改时,它可能会进行优化,将该值缓存在 CPU 寄存器中,而不是每次都从内存中重新读取。
- 用途:
volatile
关键字指示编译器,每次访问该变量时都必须从 内存 中重新读取它的值,而不是使用寄存器中的缓存值。
3.2 常见应用场景:
-
并行设备(硬件): 访问内存映射的 I/O 寄存器,这些寄存器的值可能被外部硬件改变。
-
中断服务程序(ISR): 全局变量被 ISR 和主程序同时访问。
-
多线程环境(并发): 多个线程共享的全局变量(注意:
volatile
本身不能解决竞态条件,仍需要互斥锁等同步机制,但它是第一道防线)。
示例:
// 假设 status 寄存器的值可能被硬件修改
volatile int status_register;
void wait_for_hardware() {
// 如果没有 volatile,编译器可能优化为只读一次 status_register
while (status_register == 0) {
// 等待硬件设置 status_register 为非零值
}
}
3.3 与 const
的结合
volatile const int READ_ONLY_REG;
:表示这是一个不能被程序修改的常量(const
),但它可能会被外部硬件修改(volatile
)。
4. explicit (显式)
定义: (C++ 特有) 用于修饰类的构造函数或转换函数,以禁止(抑制)编译器进行隐式的类型转换。
核心用法与考点:
4.1 修饰单参数构造函数
-
问题: 如果一个类有一个只接受一个参数的构造函数,C++ 允许编译器使用这个构造函数将参数类型隐式转换为类类型。
-
解决方案: 使用
explicit
关键字可以避免这种不期望的隐式转换。
示例:
cpp
class Data {
public:
// 没有 explicit:允许隐式转换
Data(int x) { /* ... */ }
// 使用 explicit:禁止隐式转换
// explicit Data(int y) { /* ... */ }
};
void process(Data d) { /* ... */ }
// 如果构造函数没有 explicit:
Data d1 = 10; // 允许:隐式调用 Data(10)
process(20); // 允许:隐式调用 Data(20)
// 如果构造函数使用了 explicit:
// Data d1 = 10; // 错误:禁止隐式转换
Data d2(30); // 允许:显式调用
process(Data(40)); // 允许:显式构造临时对象
4.2 修饰转换函数(C++11/14)
explicit
也可以修饰自定义类型转换操作符(如 operator bool()
),防止对象被隐式转换为目标类型。
总结: explicit
关键字提高了代码的清晰度和安全性,防止在不经意间发生类型转换,从而避免潜在的 bug。