C++ const
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。
const作用
可以定义常量
1
const int a = 100;
类型检查
const
常量与#define
宏定义常量的区别:const
常量具有类型,编译器可以进行安全检查;#define
宏定义没有数据类型,只能简单的字符串替换,不能安全检查防止修改,起保护作用,增加程序健壮性
1 2 3
void f(const int i) { i++; // error! }
可以节省空间,避免不必要的内存分配
const
定义常量从汇编的角度看,只是给出了对应的内存地址,而不是像#define
一样给出的立即数,所以const
定义的常量在程序运行过程中只有一份拷贝,而#define
定义的常量在内存中有若干个拷贝
const对象默认为文件局部变量
非const的全局变量默认为extern。要使const全局变量能够在其他文件中访问,必须在文件中显式地指定它为extern。
未被const修饰的变量不需要extern
显式声明!而const
常量需要显式声明extern
,并且需要做初始化!因为常量在定义后就不能被修改,所以定义时必须初始化。
定义常量
1
2
3
4
5
6
const int b = 10;
b = 9; // error:assignment of read-only variable 'b'
const string s = "helloworld";
const int i, j = 0; // error:uninitialized const 'i'
指针与const
指针与const
主要有四种情况:
1
2
3
4
const char * a; // a是一个指针,指向const char
char const * a; // a是一个指针,指向const char
char * const a; // a是一个const指针,指向char
const char * const a; // a是一个const指针,指向const char
实际行为差异:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// char 类型
// 1. 指向常量的指针
const char * p1 = "hello";
char str[] = "world";
p1 = str; // ✓ 可以改变指针指向
// *p1 = 'H'; // ✗ 不能修改指向的内容
// 2. 常指针
char * const p2 = str;
*p2 = 'W'; // ✓ 可以修改指向的内容
// p2 = "new"; // ✗ 不能改变指针指向
// 3. 指向常量的常指针
const char * const p3 = "test";
// p3 = str; // ✗ 不能改变指针指向
// *p3 = 'T'; // ✗ 不能修改指向的内容
// int 类型
int a = 10, b = 20, c = 30;
// 1. 指向const int的指针
const int *p1 = &a;
int const *p1_same = &a; // 与上面等价
p1 = &b; // 合法
// *p1 = c; // 非法
// 2. const指针指向int
int *const p3 = &a;
*p3 = c; // 合法
// p3 = &b; // 非法
// 3. const指针指向const int
const int * const p4 = &a;
// &p4 = c; // 非法
// p4 = &a; // 非法
助记口诀:
const
在*
左边:指向的内容不能改,指针本身可以修改const
在*
右边:指针本身不能改,指针指向的内容可以改const
在两边:都不能改
const
在*
左边 → 地址上的内容只读,但地址可变const
在*
右边 → 地址固定,但内容可修改
指向常量的指针
1
2
const int *ptr;
*ptr = 10; // error!
ptr
是一个指向int
类型const
对象的指针,const
定义的是int
类型,也就是ptr
所指向的对象类型而不是ptr
本身,所以ptr
可以不用赋初始值。但是不能通过ptr
去修改所指对象的值
- **不能使用
void*
指针保存const
对象的地址,必须使用const void*
类型的指针来保存const
对象的地址 - 允许把非
cosnt
对象的地址赋值给指向const
对象的指针,这样只能通过指针对对象进行读取而不能修改,保护了原来的变量,常用于函数中不修改原始数据
常指针
const
指针必须进行初始化,且const
指针的值不能修改
常指针是指类型 * const
这种形式,即指针本身是常量的指针
常见应用场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass {
private:
int * const data_ptr; // 常指针成员变量
public:
// 构造函数中初始化常指针
MyClass(int* ptr) : data_ptr(ptr) {
// data_ptr在对象生命周期内始终指向同一地址
}
void setValue(int val) {
*data_ptr = val; // 可以修改指向的值
}
// data_ptr不能重新指向其他地址
};
指向常量的常指针
1
2
const int p = 3;
const int * const ptr = &p;
ptr
是一个const
指针,然后指向了一个int
类型的cosnt
对象
函数与const
const修饰函数返回值
const int func1();
这个本身无意义,因为参数返回本身就是赋值给其他的变量
const int * func2();
指针指向的内容不变
int * const func2();
指针本身内容不可变
const修饰函数参数
传递过来的参数及指针本身在函数内不可变,无意义
1 2
void func(const int var); // 传递过来的参数不可变 void func(int *const var); // 指针本身不可变
表明参数在函数体内不能被修改,单此处没有任何意义,
var
本身就是形参,在函数内部会改变,包括传入的形参是指针也是一样参数指针所指向内容为常量不可变
1
void StringCopy(char *dst, const char *src);
其中
str
是输入参数,dst
是输出参数。给src
加长const
修饰后,如果函数内的语句试图改动src
的内容,编译器会报错例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
// 指向const对象的指针参数 void print_array(const int* arr, int size) { for(int i = 0; i < size; i++) { cout << arr[i] << " "; // ✓ 可以读取 // arr[i] = 0; // ✗ 不能修改数组元素 } } // const指针参数(较少使用) void func2(int* const ptr) { *ptr = 100; // ✓ 可以修改指向的值 // ptr = NULL; // ✗ 不能修改指针本身 }
参数为引用,为了增加效率同时防止修改
1
void func(const A &a)
A
是一个非内部数据类型,如果使用void func(A a)
的声明函数方式效率慢(函数体内将产生A
类型的临时对象用于复制参数a
),为了提高效率可以将函数声明改为void func(A &a)
,因为引用传递仅借用以下参数的别名,不需要产生临时对象此外,为了解决对原始
a
对象可能被修改的问题,声明时加上cosnt
修饰例如:
1 2 3 4 5 6 7 8
void process_data(const string& str) { // 避免拷贝,提高效率 cout << str.length() << endl; // ✓ 可以调用const成员函数 // str += "test"; // ✗ 不能修改 } void modify_data(string& str) { // 非const引用,可以修改 str += " modified"; // ✓ 可以修改 }
对于非内部数据类型的输入参数,应该将“值传递”的方式改为
const
引用传递,目的提高效率 对于内部数据类型的输入参数,应该继续使用“值传递”的方式,效率一样但函数的可理解性强内部数据类型:
char
,short
,int
,long
,long long
,char
,float
,double
,bool
,void
类与const
在一个类中,任何不会修改数据成员的函数都应该声明为const
类型
使用const
关键字进行说明的成员函数,被成为常成员函数。
常成员函数特点
- 不能修改成员变量
- 不能调用非常成员函数
- 常对象只能调用常成员函数