文章目录
C/C++ 中 const
关键字的总结
const
关键字在 C 和 C++ 中起着非常重要的作用,用于定义常量、修饰变量、函数参数、返回值、成员函数和类对象等。正确理解和使用 const
可以提高代码的安全性、可读性和效率。本文将详细介绍 const
的使用,包括全局和局部变量、修饰指针和引用、成员函数和数据成员、修饰类对象,以及 const
与宏定义和 static
的区别。
1. 什么是 const
const
是一个限定符,用于限定一个变量或对象的值不可被修改。使用 const
可以防止无意间对数据的修改,提高程序的安全性和可靠性。
- 只读属性:被
const
修饰的变量只能被访问,不能被修改,即只读(readonly)。 - 编译期间检查:
const
在编译期间会告诉编译器该对象不能被修改,如果试图修改,会产生编译错误。
1.1 const
的规则
const
离谁近,谁就不能被修改:在指针和引用的修饰中,const
的位置决定了是修饰指针本身还是指针指向的对象。- 初始化要求:
const
修饰的变量必须在声明时初始化,且初始化后不能再赋值。
1.2 const
的作用
- 定义常量:替代宏定义,提供类型安全的常量。
- 修饰函数参数:防止函数内部修改参数,提高函数的健壮性。
- 修饰函数返回值:防止返回值被修改,增加安全性。
- 修饰成员函数:保证成员函数不修改对象状态,可被
const
对象调用。 - 提高代码可读性:向读者传递变量或函数的使用意图。
1.3 const
的优点
- 类型安全检查:编译器会对
const
进行类型检查,防止类型不匹配的赋值。 - 编译优化:编译器可对
const
进行优化,可能不为其分配内存,直接使用符号表中的值。 - 节省空间:避免不必要的内存分配,减少内存占用。
- 提高效率:减少存储和读内存的操作,提高程序运行效率。
2. const
的使用原理
2.1 基本分类
- 常量变量:
const 类型 变量名;
- 常量引用:
const 类型 & 引用名;
- 常量对象:
const 类名 对象名;
- 常量成员函数:
返回类型 类名::函数名(参数列表) const;
- 常量数组:
const 类型 数组名[大小];
- 常量指针:
- 指向常量的指针:
const 类型 * 指针名;
或类型 const * 指针名;
- 常量指针:
类型 * const 指针名;
- 指向常量的常量指针:
const 类型 * const 指针名;
或类型 const * const 指针名;
- 指向常量的指针:
注意:在某些情况下,const
与类型说明符的位置可以互换,例如:
const int a = 5;
int const a = 5; // 等同于上面
3. const
修饰全局和局部变量
3.1 const
全局变量
- 作用域限制:
const
修饰的全局变量,其作用域仅限于定义它的文件(编译单元),其他文件无法访问,类似于static
全局变量。 - 防止修改:
const
全局变量在初始化后不能被修改,编译器会阻止对其赋值。
示例:
以下是一个完整的示例,演示 const
全局变量的作用域限制 以及解决方法:
文件 1:a.cpp
// a.cpp
#include <iostream>
// 定义 const 全局变量
const int a = 10; // 仅在 a.cpp 内部可见
void printA() {
std::cout << "a (from a.cpp) = " << a << std::endl;
}
文件 2:main.cpp
// main.cpp
#include <iostream>
// extern const int a; // 如果取消注释,将导致链接错误
int main() {
// std::cout << "a (from main.cpp) = " << a << std::endl; // 链接错误:无法访问 a
std::cout << "a is not accessible in main.cpp" << std::endl;
return 0;
}
编译和运行:
g++ a.cpp main.cpp -o main
./main
输出:
a is not accessible in main.cpp
解决方法:显式指定外部链接性
通过 extern
声明和定义全局 const
,将其作用域扩展到多个编译单元。
文件 1:a.h
// a.h
#ifndef A_H
#define A_H
// 声明外部链接的 const 全局变量
extern const int a;
#endif
文件 2:a.cpp
// a.cpp
#include "a.h"
// 定义 const 全局变量
const int a = 10;
文件 3:main.cpp
// main.cpp
#include <iostream>
#include "a.h" // 包含声明
int main() {
std::cout << "a (from main.cpp) = " << a << std::endl;
return 0;
}
编译和运行:
g++ a.cpp main.cpp -o main
./main
输出:
a (from main.cpp) = 10
小结
- 默认情况下,
const
全局变量只能在定义它的编译单元中访问。 - 通过
extern
声明并定义,可以扩展其作用域。 - 推荐使用头文件管理声明,以确保一致性和可维护性。
3.2 const
局部变量
- 防止修改:
const
局部变量在初始化后不能被修改。 - 编译器优化:对于
const
局部变量,编译器可能会进行优化,将其值直接替换到代码中,这被称为常量折叠。
示例:
#include <iostream>
int main() {
const int a = 7;
int *p = (int *)(&a);
*p = 8; // 试图修改常量的值,行为未定义
std::cout << "a = " << a << std::endl;
std::cout << "*p = " << *p << std::endl;
return 0;
}
编译并运行,结果可能是:
a = 7
*p = 8
注意:这种行为是未定义的,不应当修改 const
变量的值。
4. const
修饰指针和引用
4.1 const
修饰指针
指针是一个变量,存储另一个变量的地址。const
修饰指针时,需要区分以下几种情况:
-
指向常量的指针(Pointer to const):
const int *ptr; // 或 int const *ptr;
-
含义:
*ptr
(指针指向的值)不可修改,但ptr
(指针本身的值,即指向的地址)可变。 -
示例:
int a = 10; int b = 20; const int *ptr = &a; // ptr 指向 a // *ptr = 15; // 错误,不能修改指向的值 ptr = &b; // 合法,指针本身可变
-
-
常量指针(Const pointer):
int *const ptr = &a;
-
含义:
*ptr
可修改,但ptr
(指针本身)不可变,必须初始化。 -
示例:
int a = 10; int b = 20; int *const ptr = &a; // ptr 指向 a,且不可更改 *ptr = 15; // 合法,修改指向的值 // ptr = &b; // 错误,不能修改指针本身
-
-
指向常量的常量指针(Const pointer to const):
const int *const ptr = &a;
-
含义:
*ptr
和ptr
都不可修改。 -
示例:
int a = 10; int b = 20; const int *const ptr = &a; // *ptr = 15; // 错误,不能修改指向的值 // ptr = &b; // 错误,不能修改指针本身
-
4.2 const
修饰引用
-
常量引用:
const 类型 &引用名;
-
特点:
- 常引用不能通过引用修改所绑定对象的值。
- 常引用可以绑定到非常量对象、常量对象或临时对象。
-
示例:
int a = 10; const int &ref = a; // ref = 20; // 错误,不能通过常引用修改 a 的值 const int &ref2 = 42; // 合法,绑定到临时对象 std::cout << "ref2 = " << ref2 << std::endl; // 输出 42
5. const
修饰函数参数
5.1 值传递参数
-
对于值传递的参数,
const
修饰符没有实际意义,因为函数内部无法修改实参。 -
示例:
void func(const int x) { // x = 10; // 错误,参数 x 是只读的 }
5.2 指针或引用参数
-
指向常量的指针参数:
void func(const int *ptr) { // *ptr = 10; // 错误,不能修改指向的值 ptr++; // 合法,可以改变指针本身 }
-
常量引用参数:
void func(const int &ref) { // ref = 10; // 错误,不能修改引用的值 }
5.3 使用常量引用避免拷贝
-
对于大型对象,使用常量引用可以避免拷贝,提高效率。
-
示例:
void print(const std::string &str) { std::cout << str << std::endl; }
6. const
修饰函数返回值
6.1 返回常量值
-
对于值传递的返回值,
const
修饰符通常没有意义。 -
示例:
const int func() { return 42; } int main() { int a = func(); // func() = 10; // 错误,不能对函数返回值赋值 return 0; }
6.2 返回指针或引用
-
返回指向常量的指针:
const int *func() { static int value = 10; return &value; } int main() { const int *p = func(); // *p = 20; // 错误,不能修改指向的值 return 0; }
-
返回常量引用:
const int &func() { static int value = 10; return value; } int main() { const int &ref = func(); // ref = 20; // 错误,不能修改引用的值 return 0; }
6.3 返回 const
的意义
- 防止修改返回值:增加安全性,防止调用者修改内部数据。
- 限制链式赋值:在运算符重载中,防止不合理的赋值操作。
示例:
class MyClass {
public:
const MyClass &operator=(const MyClass &other) {
// 赋值操作
return *this;
}
};
int main() {
MyClass a, b, c;
a = b;
// (a = b) = c; // 错误,(a = b) 返回 const,不能赋值
return 0;
}
7. const
修饰成员函数和数据成员
7.1 常量成员函数
-
声明方式:
返回类型 函数名(参数列表) const;
-
特点:
- 常量成员函数不能修改成员变量(除非成员变量是
mutable
修饰)。 - 常量成员函数不能调用非
const
的成员函数。 - 常量成员函数可以被
const
对象和非const
对象调用。
- 常量成员函数不能修改成员变量(除非成员变量是
-
示例:
class MyClass { public: void func() const; // 常量成员函数 void nonConstFunc(); private: int data; }; void MyClass::func() const { // data = 10; // 错误,不能修改成员变量 // nonConstFunc(); // 错误,不能调用非 const 成员函数 } void MyClass::nonConstFunc() { data = 10; // 合法 } int main() { MyClass obj; const MyClass constObj; obj.func(); // 合法 obj.nonConstFunc(); // 合法 constObj.func(); // 合法 // constObj.nonConstFunc(); // 错误,const 对象不能调用非 const 成员函数 return 0; }
7.2 常量成员函数
-
初始化:
const
数据成员必须在构造函数的初始化列表中初始化。 -
示例:
class MyClass { public: MyClass(int val) : data(val) {} void print() const { std::cout << "data = " << data << std::endl; } private: const int data; }; int main() { MyClass obj(10); obj.print(); // 输出 data = 10 return 0; }
-
静态常量成员函数:
- 若为
const static
成员,且为整数或枚举类型,可以在类内初始化。
class MyClass { public: static const int size = 100; void print() const { std::cout << "size = " << size << std::endl; } }; int main() { MyClass obj; obj.print(); // 输出 size = 100 return 0; }
- 若为
8. const
修饰类对象
-
常对象:用
const
修饰的对象,不能调用非const
成员函数。 -
示例:
class MyClass { public: void func(); void func() const; }; void MyClass::func() { std::cout << "非 const 版本的 func()" << std::endl; } void MyClass::func() const { std::cout << "const 版本的 func()" << std::endl; } int main() { MyClass obj; const MyClass constObj; obj.func(); // 输出:非 const 版本的 func() constObj.func(); // 输出:const 版本的 func() return 0; }
9. const
与宏定义的区别
-
类型安全:
const
常量有类型检查,宏定义没有类型。
-
作用域:
const
常量有作用域限制,宏定义在预处理阶段展开,没有作用域。
-
编译器处理:
const
常量由编译器处理,宏定义在预处理阶段替换。
-
调试:
const
常量可以在调试时查看,宏定义不能。
示例:
#include <iostream>
#define PI 3.14159
const double Pi = 3.14159;
int main() {
double r = 1.0;
double area1 = PI * r * r; // 编译时替换
double area2 = Pi * r * r; // 编译器进行类型检查
std::cout << "area1 = " << area1 << std::endl;
std::cout << "area2 = " << area2 << std::endl;
return 0;
}
10. static
与 const
的区别
10.1 static
关键字
-
函数内部的
static
变量:- 变量在函数执行结束后不释放,值保留到下次调用。
示例:
#include <iostream> void func() { static int count = 0; count++; std::cout << "count = " << count << std::endl; } int main() { func(); // 输出 count = 1 func(); // 输出 count = 2 func(); // 输出 count = 3 return 0; }
-
全局的
static
变量和函数:- 只能在当前文件中访问,不能被其他文件访问。
-
类的
static
成员变量:- 属于整个类,共享一份内存。
示例:
class MyClass { public: static int count; MyClass() { count++; } }; int MyClass::count = 0; int main() { MyClass a, b, c; std::cout << "MyClass::count = " << MyClass::count << std::endl; // 输出 3 return 0; }
-
类的
static
成员函数:- 不接受
this
指针,只能访问静态成员。
示例:
class MyClass { public: static void func() { // std::cout << data; // 错误,不能访问非静态成员 std::cout << "Static func" << std::endl; } private: int data; }; int main() { MyClass::func(); // 输出 Static func return 0; }
- 不接受
10.2 const
关键字
-
阻止变量被修改:
const
变量在初始化后不能被修改。
-
修饰指针和引用:
- 控制指针或引用及其指向的对象是否可修改。
-
修饰函数参数:
- 防止函数内部修改参数。
-
修饰成员函数:
-
保证成员函数不修改对象状态。
10.3 二者区别
-
作用不同:
static
主要控制存储区域和可见性。const
主要控制对象的可修改性。
-
使用场景不同:
static
用于管理内存和作用域。const
用于防止数据被修改,提高安全性。
11. const
与 constexpr
的区别(C++11 及以上)
-
const
:- 运行时常量,值在编译期未知,可以在运行时赋值。
-
constexpr
:- 编译时常量,值必须在编译期确定。
- 用于定义在编译期就能确定值的常量,常用于数组大小等需要编译期常量的地方。
示例:
#include <iostream>
const int size1 = 10;
constexpr int size2 = 20;
int main() {
int array1[size1]; // 合法,size1 是 const 整数
int array2[size2]; // 合法,size2 是 constexpr 整数
std::cout << "size1 = " << size1 << std::endl;
std::cout << "size2 = " << size2 << std::endl;
return 0;
}
12. const_cast
的使用
- 作用:用于移除对象的
const
或volatile
属性。 - 限制:不能去除常量对象的常量性,只能用于指针或引用。
示例:
#include <iostream>
void func(const int *ptr) {
int *modifiablePtr = const_cast<int *>(ptr);
*modifiablePtr = 10; // 修改指针指向的值
}
int main() {
const int value = 5;
std::cout << "Before: value = " << value << std::endl;
func(&value);
std::cout << "After: value = " << value << std::endl;
return 0;
}
注意:这种行为是未定义的,修改常量可能会导致不可预料的结果,应谨慎使用。
总结
const
关键字在 C 和 C++ 编程中具有重要意义。通过正确理解和使用 const
,可以编写出更安全、高效、可维护的代码。以下是一些关键要点:
- 防止数据被修改:使用
const
修饰变量、指针、引用、函数参数和返回值,防止意外修改数据。 - 提高代码可读性:明确标识哪些数据是只读的,增强代码的可读性和自解释性。
- 编译器优化:
const
允许编译器进行更多的优化,提高程序性能。 - 与其他关键字配合使用:理解
const
与static
、constexpr
、volatile
等关键字的区别和联系。
建议:在编程实践中,尽可能使用 const
修饰不需要修改的数据,养成良好的编程习惯。