第七章 指针、数组与引用
7.5 指针与const
C++提供了两种与“常量”有关的概念:
- constexpr:编译时求值(见2.2.3节和10.4节)。
- const:在当前作用域内,值不发生改变(见2.2.3节)。
基本上,constexpr的作用是指示或确保在编译时求值,而const的主要任务是规定接口的不可修改性。本节主要关注第二点:接口说明。
很多对象的值一旦初始化就不会再改动:
- 使用符号化常量的代码比直接使用字面值常量的代码更易维护。
- 我们经常通过指针读取数据,但是很少通过指针写入数据。
- 绝大多数函数的参数只负责读取数据,很少写入数据。
为了表达一经初始化就不可修改的特性,我们可以在对象的定义中加上const关键字。例如:
const int model = 90; //model是一个const
const int v[] = {1,2,3,4}; //v[i]是一个const
const int x; //错误:缺少初始化器
因为我们无法给const对象赋值,所以它必须初始化。
一旦我们把某物声明成const,就确保它的值在其作用域内不会发生改变:
void f()
{
model = 200; //错误
v[2] = 3; //错误
}
使用const会改变一种类型。所谓改变不是说改变了常量的分配方式,而是限制了它的使用方式。例如:
void g(const X* p)
{
//此处无权修改*p
}
void h()
{
X val; //此处可以修改val的值
g(&val);
//...
}
一个指针牵扯到两个对象:指针本身以及指针所指的对象。在指针的声明语句中“前置”const关键字将令所指的对象而非指针本身成为常量。要想令指针本身成为常量,应该用const代替普通的。例如:
void f1(char* p)
{
char s[] = “Gorm”;
const char* pc = s; //指向常量的指针
pc[3] = ‘g’; //错误:pc指向常量
pc = p; //OK
char *const cp = s; //常量指针
cp[3] = ‘a’; //OK
cp = p; //错误:cp是一个常量
const char *const cpc = s; //指向常量的常量指针
cpc[3] = ‘a’; //错误:cpc指向常量
cpc = p; //错误:cpc本身是一个常量
}
声明运算符const的作用是令指针本身成为常量。不存在形如const的声明运算符,相反,出现在*前面的const是基本类型的一部分。例如:
char *const cp; //指向char的常量指针
char const* pc; //指向常量const的指针
const char* pc2; //指向常量char的指针
要想理解上述声明的含义,一个小技巧是按照从右向左的顺序读。例如,“cp是指向char的const指针”,而“pc2是指向char const的指针”。
对于同一个对象来说,通过一个指针访问它时是常量并不妨碍在其他情况下它是个变量。这一点在涉及函数的实参时特别有用。我们可以把指针类型的实参声明成const,这样就能阻止函数修改该指针所指的对象了。例如:
const char* strchr(const char* p, char c); //找到在字符串p中字符c第一次出现的位置
char* strchr(char* p, char c); //找到在字符串p中字符c第一次出现的位置
第一个函数的参数是常量字符串,函数无权修改其中的元素;它的返回值是指向const的指针,也不允许修改其所指的对象,第二个函数没有这些限制。
C++允许把非const变量的地址赋给指向常量的指针,这不会造成什么不可接受的后果。相反,常量的地址不能被赋给某个不受限的指针,因为如果这样的话,用户有可能通过该指针修改对象的值,这显然是不被允许的。例如:
void f4()
{
int a = 1;
const int c = 2;
const int* p1 = &c; //OK
const int* p2 = &a; //OK
int* p3 = &c; //错误:用const int*初始化int*
*p3 = 7; //试图改变c的值
}
C++允许通过显式类型转换的方式(见16.2.9节和11.5节)显式地移除掉对于指针指向常量的限制,但是一般情况下不要这么做。