const 在 C/C++ 中的全面用法(C/C++ 差异+核心场景+实战示例)
const 是 C/C++ 中的只读修饰符 ,核心作用是限定变量/对象/函数等不可被修改 ,既能提升代码可读性、避免意外修改,又能让编译器做更多优化(如常量折叠),还能增强类型安全。C 和 C++ 对 const 的支持有核心差异,C++ 在 C 的基础上做了大幅扩展,使其适配面向对象、模板等特性。下面按「基础通用用法」「C 专属特性」「C++ 增强用法」「核心差异」「实战注意事项」展开,覆盖所有高频场景。
一、基础通用用法(C/C++ 均支持,核心共性)
这是 const 最基础的用法,C 和 C++ 规则完全一致,核心是修饰变量为只读,不可直接赋值修改。
1. 修饰普通变量(只读变量)
c
// 定义只读变量,初始化后不可修改
const int a = 10;
// a = 20; // 编译报错:只读变量不可赋值
int b = a; // 合法:可以读取const变量的值
- 关键:
const变量必须在定义时初始化(否则后续无法赋值,变成"只读的未初始化变量",无意义); - 本质:const 普通变量是只读的内存变量(非编译期常量,C 中尤为明显),只是编译器禁止直接修改,底层仍占用内存。
2. 修饰指针(3 种经典场景,C/C++ 完全一致)
指针的核心是「指针本身 」和「指针指向的内容 」,const 修饰不同位置,效果完全不同,这是高频考点,记准就近修饰原则 :const 离谁近,就限定谁不可修改。
c
int x = 10, y = 20;
// 场景1:const 修饰*p → 指针指向的内容不可修改(只读内容)
const int* p = &x;
// *p = 30; // 编译报错:指向的内容只读
p = &y; // 合法:指针本身可以指向其他地址
// 场景2:const 修饰p → 指针本身不可修改(只读指针,地址固定)
int* const p = &x;
*p = 30; // 合法:指向的内容可以修改
// p = &y; // 编译报错:指针本身地址不可变
// 场景3:const 同时修饰*p和p → 内容+指针均不可修改
const int* const p = &x;
// *p = 30; // 报错:内容只读
// p = &y; // 报错:指针地址只读
记忆技巧 :把 * 理解为「指向的内容」,从右往左读:
const int* p→p是指针,指向const int(只读int);int* const p→p是const指针,指向int。
3. 修饰函数参数(C/C++ 通用优化手段)
用于限定函数内不可修改传入的参数,避免意外修改实参(尤其针对指针/引用参数,防止篡改原数据),同时明确函数语义:"此参数仅做读取,不做修改"。
c
// 示例1:修饰普通参数(值传递,意义较小,因为值传递是拷贝,修改不影响实参)
void print(const int num) {
// num = 100; // 报错:不可修改
printf("%d\n", num);
}
// 示例2:修饰指针参数(核心场景,防止修改指向的原数据)
void modify(const int* arr, int len) {
// arr[0] = 10; // 报错:不可修改原数组
for (int i=0; i<len; i++) {
printf("%d ", arr[i]); // 仅读取,合法
}
}
int main() {
int arr[] = {1,2,3};
modify(arr, 3); // 实参不会被篡改,安全
return 0;
}
- 最佳实践:指针/引用参数 尽量加
const(只要函数内不修改),这是工业界代码的通用规范; - 普通值参数加
const意义不大(拷贝开销小,且修改不影响实参),可根据代码规范选择。
二、C 语言中 const 的专属特性(C++ 中已优化/改变)
C 语言对 const 的支持比较"简陋",核心特点是const 变量不是真正的编译期常量,仅为"只读变量",这是和 C++ 最核心的早期差异。
1. const 变量不可作为数组长度(C99 变长数组除外)
C 中 const 变量占用内存,编译器不会将其视为"常量表达式",因此不能用于定义固定长度数组:
c
const int N = 5;
// int arr[N]; // C89 编译报错:N 不是编译期常量(C99 支持变长数组 VLA,可运行但非标准固定数组)
int arr[5]; // 合法:字面量是编译期常量
2. const 变量需用 extern 显式声明才能跨文件使用
C 中 const 变量的默认链接属性是内部链接 (仅当前文件可见),若要在其他文件使用,必须加 extern 显式声明:
c
// file1.c:定义跨文件使用的const变量,必须加extern
extern const int PI = 3.1415;
// file2.c:使用前声明
extern const int PI;
printf("PI = %f\n", PI); // 合法
若不加 extern,C 编译器会为每个文件生成独立的 const 变量,导致链接冲突或值不一致。
3. 无引用特性,const 仅能修饰变量/指针/函数参数
C 语言没有引用(&) 特性,因此 const 无法修饰引用,仅能作用于变量、指针、函数参数,用法远少于 C++。
三、C++ 中 const 的增强用法(C 无此特性,面向对象核心)
C++ 完全兼容 C 中 const 的基础用法,同时针对面向对象、引用、模板、函数 做了大幅扩展,使 const 成为实现代码健壮性的核心关键字,这部分是 C++ 开发的重点。
1. const 修饰引用(常引用,核心场景)
C++ 引入引用 后,const 可修饰引用为常引用(const &),核心作用:
- 绑定只读变量/右值(普通引用只能绑定可修改的左值);
- 避免拷贝大对象(如自定义类、字符串),同时保证原对象不被修改。
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
const int a = 10;
// int& ref = a; // 编译报错:普通引用不能绑定const左值
const int& ref1 = a; // 合法:常引用绑定const左值
const int& ref2 = 20; // 合法:常引用绑定右值(C++ 编译器会生成临时变量)
// 大对象场景:常引用避免拷贝,提升性能
const string& s = "hello world"; // 避免生成临时string拷贝
cout << s << endl;
return 0;
}
- 最佳实践:函数返回大对象/传递大对象 时,优先使用
const &,既避免拷贝开销,又保证原对象安全。
2. const 修饰类的成员函数(常成员函数,面向对象核心)
这是 C++ 面向对象的核心特性 :const 放在类成员函数的参数列表后、函数体前 ,表示该函数是常成员函数,核心规则:
- 常成员函数不能修改类的任何非静态成员变量;
- 常成员函数只能调用类的其他常成员函数/静态成员函数(不能调用普通成员函数,防止间接修改成员);
- 常对象(const 修饰的类对象)只能调用常成员函数(普通对象可调用常/普通成员函数)。
cpp
#include <iostream>
using namespace std;
class Person {
private:
string name;
int age;
static int count; // 静态成员变量
public:
Person(string n, int a) : name(n), age(a) { count++; }
// 常成员函数:不能修改非静态成员,参数列表后加const
string getName() const {
// age = 20; // 编译报错:常成员函数不能修改非静态成员
return name;
}
// 普通成员函数:可修改非静态成员
void setAge(int a) {
age = a;
}
// 静态成员函数:无this指针,可被常/普通对象调用
static int getCount() {
return count;
}
};
int Person::count = 0; // 静态成员变量类外初始化
int main() {
const Person p1("张三", 18); // 常对象
// p1.setAge(20); // 报错:常对象不能调用普通成员函数
cout << p1.getName() << endl; // 合法:调用常成员函数
cout << p1.getCount() << endl; // 合法:调用静态成员函数
Person p2("李四", 20); // 普通对象
p2.setAge(25); // 合法:调用普通成员函数
cout << p2.getName() << endl; // 合法:普通对象可调用常成员函数
return 0;
}
- 核心原理:常成员函数的this 指针 是
const 类名* const类型(指向的对象只读+指针本身只读),因此无法修改成员变量; - 最佳实践:类中仅做读取操作的成员函数 ,必须加
const(如获取属性的 get 方法),这是 C++ 类设计的通用规范。
3. const 修饰类的对象/成员变量
(1)const 修饰类的对象(常对象)
类的常对象所有非静态成员变量均不可修改 ,仅能调用常成员函数/静态成员函数 (如上例中的 p1),本质是限制对象的修改权限。
(2)const 修饰类的成员变量(常成员变量)
类的常成员变量必须在构造函数的「初始化列表」中初始化 (不能在函数体内赋值),且初始化后终身不可修改:
cpp
class Circle {
private:
const double PI; // 常成员变量:圆周率,不可修改
int radius;
public:
// 必须在初始化列表中初始化常成员变量
Circle(int r) : PI(3.1415), radius(r) {}
double getArea() const {
return PI * radius * radius; // 仅读取,合法
}
};
- 注意:常成员变量无法通过普通成员函数修改,即使是类的内部方法也不行。
4. const 修饰函数返回值
C++ 中 const 可修饰函数返回值,根据返回值类型(普通类型/指针/引用)有不同作用,核心是禁止对返回值直接赋值/修改:
cpp
#include <iostream>
using namespace std;
// 场景1:返回普通类型(int),const 意义不大(返回值是拷贝,修改不影响原数据)
const int add(int a, int b) {
return a + b;
}
// 场景2:返回指针,const 限定指向的内容不可修改
const int* getArr() {
static int arr[] = {1,2,3};
return arr;
}
// 场景3:返回引用,const 限定引用的对象不可修改(核心场景,避免原对象被篡改)
const int& getMax(int& a, int& b) {
return a > b ? a : b;
}
int main() {
// add(3,5) = 10; // 报错:const返回值不可赋值
const int* p = getArr();
// *p = 100; // 报错:指向的内容只读
int x = 5, y = 10;
// getMax(x,y) = 20; // 报错:常引用返回值不可修改
cout << getMax(x,y) << endl; // 仅读取,合法
return 0;
}
- 最佳实践:返回指针/引用 时,若不想让调用方修改原对象,必须加
const修饰返回值。
5. const 作为编译期常量(C++ 核心优化)
C++ 中,初始化值为常量表达式的 const 变量 会被编译器视为编译期常量(而非只读内存变量),可用于:
- 定义固定长度数组;
- 作为模板参数;
- 作为枚举常量、类的静态常成员初始化。
cpp
#include <iostream>
#include <vector>
using namespace std;
// const 编译期常量,可用于数组长度
const int N = 5;
int arr[N] = {1,2,3,4,5}; // 合法:C++ 编译期确定长度
// 作为模板参数(模板参数必须是编译期常量)
vector<int> v(N); // 合法:N 是编译期常量
// 类的静态常成员变量:可直接类内初始化(编译期常量)
class Math {
public:
static const double PI; // 声明
static const int MAX = 100; // 类内直接初始化(编译期常量)
};
const double Math::PI = 3.1415; // 类外初始化(若需不同值)
int main() {
cout << Math::MAX << endl; // 100
return 0;
}
这一特性解决了 C 中 const 变量不能作为数组长度的问题,是 C++ 对 const 的关键优化。
6. const 修饰模板参数(C++ 模板特性)
C++ 模板中,const 可修饰模板参数,限定模板实例化后的参数为只读,适配不同的常量类型需求:
cpp
template <const int N>
class Array {
public:
int data[N];
void print() {
for (int i=0; i<N; i++) {
cout << data[i] << " ";
}
}
};
int main() {
Array<5> arr; // 实例化:N=5(编译期常量)
arr.data[0] = 1;
arr.print();
return 0;
}
四、C/C++ 中 const 的核心差异总结(必记)
| 特性/场景 | C 语言 | C++ 语言 |
|---|---|---|
| 常量属性 | 仅为只读变量(占内存) | 初始化值为常量表达式时是编译期常量,否则为只读变量 |
| 数组长度 | 不可用于固定数组长度(C99 VLA 除外) | 可直接用于固定数组长度 |
| 跨文件使用 | 默认内部链接,需加 extern 显式声明 |
全局 const 可直接跨文件使用(编译器自动优化) |
| 引用修饰 | 无引用特性,不可修饰 | 支持常引用(const &),可绑定左值/右值 |
| 类成员函数 | 无类特性,不可修饰 | 支持常成员函数,限制修改成员变量 |
| 类常对象/成员变量 | 无类特性,不可修饰 | 支持修饰常对象、常成员变量(初始化列表初始化) |
| 函数返回值/模板 | 仅支持基础类型,无扩展 | 支持修饰指针/引用返回值、模板参数 |
五、const 实战注意事项(避坑指南)
1. const 不是绝对的"只读"------可通过指针强制修改(不推荐)
C/C++ 中,const 变量的只读性是编译器层面的限制 ,底层仍占用内存,可通过「非 const 指针」强制修改(即const _cast ,C++ 专用),但这是未定义行为,极易导致程序崩溃,严禁在生产代码中使用:
c
// C 语言:强制类型转换修改
const int a = 10;
int* p = (int*)&a;
*p = 20; // 编译器不报错,但运行结果未定义(可能修改成功,也可能触发段错误)
printf("a = %d\n", a); // 结果不可预测
// C++ 语言:const_cast 转换(专用强制转换,仍不推荐)
const int b = 20;
int* q = const_cast<int*>(&b);
*q = 30; // 未定义行为
- 原则:一旦定义 const 变量,就不要尝试修改,违背 const 设计初衷。
2. 函数参数中,指针/引用优先加 const
针对自定义类、数组、字符串等大对象/复杂对象 ,传递参数时优先使用 const 指针/const 引用,既避免拷贝开销,又保证原对象不被修改,这是工业界 C/C++ 代码的通用规范。
3. C++ 类中,get 方法必须加 const,set 方法不加
类的属性访问方法(getXxx)仅做读取操作,必须声明为常成员函数 ;修改方法(setXxx)做写入操作,声明为普通成员函数,这是 C++ 类设计的黄金法则。
4. 全局 const 变量尽量避免重复定义
C 中全局 const 需加 extern 统一定义,C++ 中虽可直接跨文件使用,但仍建议在头文件中声明,源文件中定义,避免多文件包含时的重复定义问题:
cpp
// math.h(头文件)
#pragma once
extern const double PI; // 声明
// math.cpp(源文件)
#include "math.h"
const double PI = 3.1415; // 定义
5. const 与 constexpr 的区别(C++11 及以上)
C++11 引入 constexpr(编译期常量),很多开发者会混淆 const 和 constexpr,核心区别:
const:只读约束,可能是编译期常量(初始化值为常量表达式),也可能是运行时常量(初始化值为变量);constexpr:强制编译期常量,初始化值必须是常量表达式,编译器在编译期就确定其值,可用于更多编译期场景(如 constexpr 函数、类的构造函数)。
cpp
const int a = 10; // 编译期常量
int n = 5;
const int b = n; // 运行时常量(只读变量)
constexpr int c = 20; // 强制编译期常量
// constexpr int d = n; // 编译报错:n 是变量,非常量表达式
- 建议:C++11 及以上版本,明确需要编译期常量 时用
constexpr,仅需要只读约束 时用const。
六、总结(核心要点浓缩)
- 核心作用 :
const是只读修饰符,C/C++ 通用,用于限定变量/对象不可修改,提升代码健壮性和编译器优化能力; - C 语言特性 :const 仅为只读变量 ,不可做数组长度,无引用/类相关用法,跨文件需加
extern; - C++ 增强特性 :兼容 C 基础用法,新增常引用、常成员函数、常对象/常成员变量,const 可作为编译期常量,支持修饰函数返回值/模板参数;
- 关键规则 :
- 指针:
const就近修饰,离谁近限定谁不可修改; - 类:常成员函数不能修改非静态成员,常对象只能调用常成员函数;
- 引用:常引用可绑定左值/右值,是避免大对象拷贝的最佳实践;
- 指针:
- 避坑原则:不通过指针强制修改 const 变量,函数参数中指针/引用优先加 const,C++ 类中 get 方法加 const;
- C++11+ 补充 :
constexpr是强制编译期常量,与const互补,按需选择。
const 是 C/C++ 开发中使用频率最高的关键字之一,掌握其在 C/C++ 中的差异和各场景用法,是写出高质量、健壮性代码的基础,尤其在 C++ 面向对象开发中,const 的正确使用是衡量代码规范的重要标准。