简要回答
static ------ 控制生命周期和可见性。 它让局部变量在函数调用之间持久存在,让全局变量/函数的作用域限制在当前文件,让类成员被所有对象共享而非各自持有。核心思想:"存储不随栈帧消亡,名字不向外部暴露"。
const ------ 承诺"不修改"。 它把变量标记为只读,把指针/引用标记为不可通过它来修改所指对象,把成员函数标记为不会修改对象状态。核心思想:"编译器帮你守住承诺,一旦违背立刻报错"。
详细回答
static 关键字详解
1. 局部静态变量
在函数内用 static 声明的变量,生命周期延长到整个程序运行期间,但作用域仍限于函数内部。它只在第一次执行到声明语句时初始化一次,后续调用保留上次的值。
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 程序启动 │ ──→ │ 首次调用 │ ──→ │ 后续调用 │ ──→ │ 程序结束 │
│ 分配静态 │ │ 执行初始化│ │ 保留上次 │ │ 析构释放 │
│ 存储区 │ │(仅一次) │ │ 的值 │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
2. 全局静态变量 / 函数(文件作用域)
在文件作用域加 static,会限制该符号的链接属性为内部链接(internal linkage) ,即其他 .cpp 文件无法通过 extern 访问它------实现"文件级私有"。
3. 类的静态成员变量
属于类本身而非某个对象。所有对象共享同一份拷贝,存储在全局/静态区。必须在类外单独定义 (C++17 起可用 inline static 类内定义)。
4. 类的静态成员函数
没有 this 指针,因此不能访问非静态成员 。可直接通过 类名::函数名() 调用,常用于工厂方法和单例模式。
const 关键字详解
1. 修饰普通变量
变量被标记为只读,编译器会阻止任何对它的赋值操作。const int x = 10; 之后 x = 20; 会编译报错。
2. 修饰指针(顶层 vs 底层)
这是面试重灾区!口诀:const 在 * 左边修饰值,在 * 右边修饰指针。
┌─────────────────────┬──────────────────────────────────────┐
│ 写法 │ 含义 │
├─────────────────────┼──────────────────────────────────────┤
│ const int* p │ 底层 const → 指向的「值」不可改 │
│ │ 指针可以指向别处 │
├─────────────────────┼──────────────────────────────────────┤
│ int* const p │ 顶层 const → 「指针」本身不可改 │
│ │ 指向的值可以修改 │
├─────────────────────┼──────────────────────────────────────┤
│ const int* const p │ 双重 const → 值和指针「都不可改」 │
└─────────────────────┴──────────────────────────────────────┘
3. 修饰引用
const int& ref = x; 表示不能通过 ref 修改 x。常用于函数参数传递------既避免拷贝开销,又保证不会意外修改原对象。
4. 修饰成员函数
在成员函数签名末尾加 const,承诺该函数不修改对象的任何非 mutable 成员。const 对象只能调用 const 成员函数。
核心差异对比表
┌────────────────┬─────────────────────────┬─────────────────────────┐
│ 维度 │ static │ const │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 核心语义 │ 控制生命周期和可见性 │ 控制可修改性(只读) │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 修饰局部变量 │ 延长生命周期至程序结束 │ 值不可更改 │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 修饰全局变量 │ 限制为文件内部链接 │ 默认内部链接(C++) + 只读 │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 修饰类成员变量 │ 所有对象共享一份 │ 必须在初始化列表中初始化 │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 修饰成员函数 │ 无 this 指针 │ 不修改成员 │
│ │ 可用类名直接调用 │ const 对象可调用 │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 能否同时使用 │ ✅ 可以!static const int X = 42; │
└────────────────┴─────────────────────────┴─────────────────────────┘
📊 变量存储位置对比
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 栈区 STACK │ │ 全局/静态区 DATA │ │ 只读区 RODATA │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ int x = 5; │ │ static int cnt; │ │ "hello" │
│ → 普通局部变量 │ │ → 静态局部变量 │ │ → 字符串字面量 │
│ │ │ │ │ │
│ const int y=10; │ │ static int g; │ │ constexpr int N │
│ → const局部变量 │ │ → 静态全局变量 │ │ → 编译期常量 │
│ │ │ │ │ │
│ │ │ Cls::s_val │ │ │
│ │ │ → 类静态成员 │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
💻 Part 03 · 代码示例
示例一:static 关键字综合示例
#include <iostream>
using namespace std;
// ① 文件作用域的 static:仅本文件可见
static int filePrivate = 42;
// ② 类中的 static 成员
class Counter {
public:
static int count; // 声明(类内)
Counter() { ++count; }
~Counter() { --count; }
// ④ 静态成员函数:无 this,不能访问非静态成员
static int getCount() { return count; }
};
int Counter::count = 0; // 定义(类外,必须!)
// ③ 局部 static 变量
void callMe() {
static int times = 0; // 仅初始化一次
cout << "called " << ++times << " times\n";
}
int main() {
callMe(); // called 1 times
callMe(); // called 2 times
callMe(); // called 3 times
{ Counter a, b, c; } // 三个对象创建后立即析构
cout << "alive: " << Counter::getCount() << "\n";
return 0;
}
输出:
called 1 times
called 2 times
called 3 times
alive: 0
示例二:const 关键字综合示例
#include <iostream>
#include <string>
using namespace std;
class Student {
string name_;
mutable int accessCount_ = 0; // mutable 允许在 const 函数中修改
public:
Student(const string& n) : name_(n) {}
// ④ const 成员函数:承诺不修改对象
string getName() const {
++accessCount_; // OK,mutable 允许
return name_;
}
void setName(const string& n) { name_ = n; }
};
int main() {
// ① const 变量
const int MAX = 100;
// MAX = 200; // ❌ 编译错误
// ② const 与指针
int a = 10, b = 20;
const int* p1 = &a; // 指向 const → 不能改值
// *p1 = 30; // ❌ 编译错误
p1 = &b; // ✅ 可以改指向
int* const p2 = &a; // const 指针 → 不能改指向
*p2 = 30; // ✅ 可以改值
// p2 = &b; // ❌ 编译错误
// ③ const 对象
const Student s("Alice");
cout << s.getName(); // ✅ getName 是 const 函数
// s.setName("Bob"); // ❌ const 对象不能调非 const 函数
return 0;
}
💡 面试技巧: 回答 const 指针问题时,画一张表格比口述更清晰。面试官会非常欣赏你能快速画出
const int*vsint* const的对比。
🎯 Part 04 · 内容拓展 · 面试追问
面试官经常会在基础问题后继续追问。以下是最常见的追问方向与参考回答:
Q1:const 和 #define 有什么区别?
#define 是预处理阶段的文本替换,没有类型检查,不占内存,不受作用域限制,调试时看不到符号名。const 是编译阶段处理,有类型安全、有作用域、可以调试,并且编译器可能会做常量折叠优化。
结论:C++ 中应优先使用 const(或 constexpr)而非 #define。
Q2:const 和 constexpr 有什么区别?
const 表示"运行时不可修改",初始值可以在运行时才确定。constexpr(C++11)更强,表示"编译期常量",值必须在编译时就能确定。例如 constexpr int N = 1 + 2; 编译时就算好了 3,可以用于数组大小、模板参数等。
const → 运行时只读 → 值可以运行时确定
constexpr → 编译期常量 → 值必须编译时确定(更强的保证)
Q3:static 局部变量是线程安全的吗?
C++11 标准保证:局部 static 变量的初始化是线程安全的("magic statics")。编译器会在底层加锁保证只有一个线程执行初始化。但初始化之后的读写操作并不是线程安全的,仍需手动加锁或使用原子操作。
这也是单例模式使用 static 局部变量实现(Meyer's Singleton)的理论基础:
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 保证线程安全初始化
return instance;
}
private:
Singleton() = default;
};
Q4:mutable 关键字的作用是什么?
mutable 允许一个成员变量即使在 const 成员函数中也可以被修改。典型场景:缓存计算结果、访问计数器、互斥锁(mutable std::mutex mtx_;)。它不破坏逻辑上的 const 语义------对象的"可观察状态"没有变化,只是内部实现细节改变了。
Q5:能不能用 const_cast 去掉 const?有什么风险?
const_cast 可以移除或添加 const 限定。但如果原始对象本身就是 const 的(比如 const int x = 10;),去掉 const 后修改它是未定义行为(UB)。
合法场景:对象本身不是 const 的,但通过 const 引用/指针传递进来,确信可以安全修改时才使用。
面试建议:强调"尽量不用"。
Q6:static 变量的初始化顺序有什么问题?
不同编译单元(.cpp 文件)中的全局/静态变量,初始化顺序是未定义的 ,称为 "Static Initialization Order Fiasco"。如果 A 文件的静态变量依赖 B 文件的静态变量,可能在 B 还没初始化时就被使用。
解决方案: 用函数内的局部 static 变量替代(Construct On First Use Idiom),保证首次访问时才初始化。
// ❌ 危险:初始化顺序不确定
// file_a.cpp
extern int b_val;
int a_val = b_val + 1; // b_val 可能还没初始化!
// ✅ 安全:首次使用时才初始化
int& getA() {
static int a_val = getB() + 1;
return a_val;
}
Q7:C 语言的 const 和 C++ 的 const 有什么不同?
C 中的 const 变量默认是外部链接的,不能用于数组大小(非 VLA 编译器),本质上是"只读变量"。C++ 中的 const 默认是内部链接的,编译器会尝试将其当作编译期常量处理,可以用于数组大小。所以 C++ 的 const 更接近"常量"的语义。
C 中的 const → 只读变量(外部链接,不能做数组大小)
C++ 中的 const → 编译期常量(内部链接,可做数组大小)
⚠️ 面试加分项: 如果你能自然地把
static与单例模式、const与constexpr联系起来,会显示出你对 C++ 有更深层的理解,而不仅仅是背诵概念。