跳到主要内容
C++ const 关键字详解:变量、指针与函数用法 | 极客日志
C++ 算法
C++ const 关键字详解:变量、指针与函数用法 C++ const 关键字用于声明不可修改的实体,在编译时提供语义约束。它可以修饰变量、指针、引用、函数参数及返回值。在类中,const 成员函数承诺不修改对象状态,配合 mutable 处理特殊情况。constexpr 则进一步要求编译期求值。正确使用 const 能提升代码安全性与可读性,避免意外修改数据。
GRACE Grace 发布于 2026/3/23 更新于 2026/4/24 1 浏览C++ const 关键字详解
在编写代码时,我们经常需要保证某些数据不被意外修改。C++ 提供了 const 关键字来实现这一目的。不要小看它,它在变量、指针、函数参数、返回值以及类成员等地方都有广泛应用。
修饰变量
const 是常量限定符,用来限定特定变量,通知编译器该变量不可修改。
修饰常量及数组
const int maxUsers = 100 ;
const int arr[] = { 1 , 2 , 3 };
使用 const 定义时必须初始化(除非用 extern 声明),一旦初始化,其值在生命周期内不可修改。
与宏常量的比较
与 C 语言不同,C++ 中的 const 变量通常具有类型安全,而宏常量只是文本替换。
宏常量是用 #define 在预处理阶段定义的:
#define MAX_USERS 100
它只是单纯的文本替换,编译器根本不知道有个叫 MAX_USERS 的东西。从定义点开始到文件结束,它会污染所有后续代码。
想象一下计算圆面积的函数,用 const 和宏分别定义圆周率:
const double PI = 3.14159 ;
double area (double r) { return PI * r * r; }
#define PI 3.14159
double area (double r) { return PI * r * r; }
看起来差不多,但坑往往藏在细节里。假如你在另一个文件里不小心写了个变量叫 PI:
int PI = 42 ;
用 版本完全没问题, 作为常量有自己的作用域。用宏版本则会编译错误,因为 是全局的,编译器会把 里的 也替换成 ,变成 。
const
PI
#define PI
int PI = 42;
PI
3.14159
int 3.14159 = 42;
这就是宏的'名称污染'问题。因此定义常量时,首选 const。宏的唯一用武之地是那些必须发生在预处理阶段的事情,比如条件编译或头文件保护。
const 与指针 const 与指针的组合是 C++ 里最让人头疼的部分之一。这俩凑一块儿,就像两个性格迥异的室友:一个总想改变(指针),一个坚决不变(const)。
当它们在一起后,会出现三种组合:指向常量的指针、常量指针、指向常量的常量指针。
指向常量的指针 p 是一个指针,它指向一个 const int。你可以改变 p 本身,让它指向别处;但你不能通过 p 来修改它当前指向的那个整数。
int a = 10 , b = 20 ;
const int * p = &a;
*p = 30 ;
p = &b;
记住,const 在 * 的左边,但不能改变指向的值。
常量指针 p 是一个常量指针,也就是说指针本身是只读的。一旦它指向某个变量,就不能再指向别处。但是,你可以通过它修改它指向的那个变量(只要变量本身不是 const)。
int a = 10 , b = 20 ;
int * const p = &a;
*p = 30 ;
p = &b;
指向常量的常量指针 合体版!p 本身是常量,指向的也是常量。所以既不能改 p 的指向,也不能通过 p 修改指向的值。
int a = 10 , b = 20 ;
const int * const p = &a;
*p = 30 ;
p = &b;
三种指针总结 类型 声明形式 指针本身可改? 指向的值可改? 指向常量的指针 const int* / int const*可改 不可改 常量指针 int* const不可改 可改 指向常量的常量指针 const int* const不可改 不可改
const 与函数参数 const 与函数参数就像给函数的输入加一道'安检门',不同的参数传递方式对应不同的安检级别。
值传递 void func (const int x) {
}
参数 x 是通过值传递的,函数内操作的是实参的副本。加上 const 意味着这个副本在函数内是只读的。其实对于值传递,加不加 const 对外部调用者来说没有区别,它的作用主要是对内:告诉函数的读者(以及编译器)这个参数在函数内不应该被修改,起到自我约束的作用。
不过,因为值传递本身就会拷贝,如果对象很大,拷贝开销会很高,所以通常只适用于基本类型或小型对象。
指针传递 指针传递时,const 可以修饰指针本身,也可以修饰指针指向的数据。
void func (const int * p) {
}
假如你想通过指针传入一个大型数组或对象,并且承诺不会修改它。这样调用者可以放心地把数据交给你,即使数据本身是 const 的也能传进来。这么做避免了拷贝,同时保证了数据只读。
void func (int * const p) {
}
你希望这个指针在函数内始终指向同一个对象。但这种情况很少单独使用,因为通常我们更关心数据是否被修改。
void func (const int * const p) {
}
引用传递 void func (const MyClass& obj) {
}
obj 是传入对象的别名,加上 const 表示这个别名是只读的。
避免拷贝 :对于大型对象(如 std::string、vector),直接传值会拷贝整个对象,开销巨大。传引用则没有拷贝。
保护数据 :const 保证了函数内不会修改传入的对象。
接受临时对象 :常引用可以绑定到临时对象(右值),比如 func(getString()) 是合法的,而普通引用不行。
这就像你进图书馆看书,图书馆给你一张'只读阅览证'——你可以随便看(引用),但不能在书上写写画画(const),而且这张证不占用你书包空间(无拷贝)。
三种传递方式总结 传递方式 语法 适用场景 优势 值传递 void f(const int x)基本类型小数据 调用者无需担心数据被修改 指针传递 void f(const int* p)只读大数组/对象,允许空 避免拷贝,保护指向内容 引用传递 void f(const T& ref)大对象只读,首选方式 避免拷贝和指针语法,保护对象
const 与函数返回值 这是 const 在函数出口设置的'关卡',告诉调用者:'给你这个返回值,但有些事你不能做!'
返回 const 值 const int getAge () { return 10 ; }
函数返回的是一个 const 限定的对象(通常是副本)。对基本类型而言,返回 const 值意义不大,因为右值本来就不能被修改。但对类类型可防止意外赋值:
const std::string getName () const ;
比如 getName().append("suffix") 会被编译器阻止,因为 append 是非 const 成员函数。这在某些场景下能避免无意义的修改。
返回 const 指针 const int * getData () { return nullptr ; }
返回一个指针,指向的数据是 const 的。调用者不能通过这个指针修改指向的数据,但可以修改指针本身。
返回 const 引用 const std::string& getName () { return "xingxing" ; }
返回一个 const 左值引用,指向某个对象。调用者可以通过这个引用读取对象,但不能修改它。就像你家墙上开了一扇玻璃窗,你可以透过窗户看到屋里的东西(读取数据),但你不能伸手进去改(不能修改)。
类中的 const 走进类的内部,看看 const 在类中担任哪些角色。这里涉及四个角色:const 成员函数、mutable 关键字、const 对象、const 静态成员。
const 成员函数 在类的成员函数后面加 const,就是向编译器和调用者承诺:'这个函数不会修改对象的状态(非静态成员变量)。'
class Student {
public :
Student (std::string name = nullptr ) : _name(name), _age(10 ) {}
std::string getName () const {
return _name;
}
void setName (const std::string& n) {
_name = n;
}
int getAge () {
return _age;
}
private :
std::string _name;
int _age;
};
我们在 getName() 成员函数后面加上 const,如果在 getName 里面调用非 const 成员函数:
std::string getName () const {
int a = getAge ();
return _name;
}
编译器就会报错。因为 const 向编译器和调用者承诺了不会修改对象状态。
const 对象 假设你有一个 const 对象(比如 const Student s;),它只能调用 const 成员函数。如果 getName 没加 const,s.getName() 就会编译错误。
const Student alice ("Alice" ) ;
alice.getName ();
alice.setName ("Bob" );
此方法常用于表示不可变的数据实体,比如配置文件、常量配置等。
mutable 关键字 有时候在逻辑上不应该修改对象,可技术上却不得不改一些'内部状态'。比如你有一个互斥锁,需要在 const 成员函数里加锁解锁,这当然会修改锁的状态,但逻辑上并不影响对象的数据。
这时候 const 的严格性就成了障碍。那么我们就可以用 mutable 给 const 开个'后门'。
class ThreadSafeCounter {
public :
int get () const {
std::lock_guard<std::mutex> lock (m) ;
return value;
}
private :
mutable std::mutex m;
int value = 0 ;
};
这里的 m 是 mutable 的,因为加锁操作改变了它的状态,但这并不影响 value 的读取。逻辑上 get() 仍是只读操作。
const 静态成员 最常见的组合是 const static 成员。这表示一个属于类的常量,所有对象都能访问,且不能修改。
class MathConstants {
public :
static const double PI;
static const int MAX = 100 ;
};
const double MathConstants::PI = 3.14159 ;
我们可以看到整型(或枚举类型)的 const 静态成员可以在类内直接初始化,但是非整形的还需在类外定义。不过在 C++17 后引入了 inline static,就不存在以上问题了。
class MathConstants {
public :
inline static const double PI = 3.14159 ;
};
const 与 constexpr 通过以上内容我们知道 const 的核心是'承诺不变'。你可以用它修饰变量,告诉编译器:'这个值我不会改,你别让我改它。'但这个值到底是在编译时确定还是运行时确定,const 并不关心。
而 constexpr 是 C++11 引入的,它的核心是'编译时可知'。它强制要求在编译期就能算出值(或至少在编译期可求值)。
变量初始化的时机不同 const int a = 42 ;
const int b = rand ();
constexpr int c = 42 ;
constexpr int d = rand ();
const 变量:可以在运行时初始化,之后不能修改。
constexpr 变量:必须在编译期初始化,且初始化表达式必须是常量表达式。
constexpr 变量本身也是 const 的,所以 constexpr int x = 42; 等价于 const int x = 42;,但反之不成立。
函数是否可以在编译期调用 const 成员函数:承诺不修改对象状态,与编译期求值无关。
constexpr 函数:如果传入的参数是常量表达式,那么该函数可以在编译期求值;如果传入运行时的值,它也可以像普通函数一样在运行时调用。
constexpr int square (int x) { return x * x; }
int arr[square (5 )];
int y = 10 ;
int z = square (y);
修饰指针的含义不同 int x = 10 ;
const int * p1 = &x;
int * const p2 = &x;
constexpr int * p3 = &x;
constexpr 指针:C++11 起,constexpr 指针必须初始化为 nullptr、0,或者静态存储期对象的地址(如全局变量、静态变量),因为它们的地址在编译期是已知的。局部变量的地址在运行时才确定,不能用于初始化 constexpr 指针。
static int slocal = 42 ;
constexpr int * p = &slocal;
int local = 10 ;
constexpr int * q = &local;
constexpr 指针本身是常量指针(即不能改指向),而且指向的地址必须在编译期确定。
const constexpr int * p = &slocal;
*p = 100 ;
总结 在 C++ 中,const 关键字用于声明一个不可修改的实体,它在编译时提供语义约束并由编译器强制执行。
const 可以修饰基本类型变量、指针、引用(常引用)、函数参数(值传递、指针传递、引用传递)以及函数返回值。在类中,const 成员函数承诺不修改非 mutable 成员变量,使 const 对象能够调用这些函数。
mutable 成员则允许在 const 成员函数中修改不影响对象逻辑状态的内部数据。static const 成员定义类级别的常量。而 constexpr(C++11 起)进一步要求编译期求值,可用于变量、函数和构造函数,实现真正的编译时常量,与 const 互补。
正确使用 const 能提升代码的安全性、可读性,并辅助编译器优化。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online