指针常量是指向常量的指针,即 pointer to const,即指针指向的是一个常量,你应该把这个词(指向常量的指针)当做一个整体来理解,而不是分开。(当然也有翻译成指针常量的,但我并不喜欢这种翻译方式)
通常也将指针本身是一个常量称为顶层 const,将指针所指的对象是个常量称为底层 const
关系总结如下
- 常量指针 = * const = 顶层 const
- 指向常量的指针 = const * = 底层 const
它的语法格式是 const 在 * 左边,这一点很重要,因为后面要讲的常量指针是 const 在 * 的右边。下面是它的基本语法格式
const double pi = 3.14; // pi 是一个常量
const double *cptr = π // cptr 是一个指向常量的指针
// 或者,它的另一种写法是交换一下 const 和 double 的位置顺序
double const *cptr = π // cptr 是一个指向常量的指针
它的含义是,不能修改指针指向的内容,比如这里 cptr 指向 pi,不能通过 cptr 修改 pi 的内容。
下面我们举一个例子来进行具体的讲解
#include
using namespace std;
int main()
{
const double pi = 3.14; // pi 是一个常量
const double *cptr = π // cptr 是一个指向常量的指针
cout << *cptr << endl; // 3.14
return 0;
}
这里有一个常量 pi,我用 cptr 这个指向常量的指针来指向它,它的内存示意图应该如下
【注】当我说指针的值(内容)的时候,我说的是 0x456。当我说指针指向的值(内容),我说的是 3.14,下面的语境请随着具体上下文自动脑修。
关于指针常量的两个扩展:
第一个是我们用普通的指针不能指向这个常量
const double pi = 3.14; // pi 是一个常量
double *pnormal = π // 用普通指针指向常量 pi
// error: invalid conversion from ‘const double*’ to ‘double*’
return 0;
答案显然是不行的,报错提示这是一个非法类型转换。
事实上,有这样的一个指向关系,指向常量的指针可以指向常量和非常量,而普通指针只能指向非常量,如下图所示。
对上图,从逻辑上是这么理解的,假设一个普通指针 common_pointer
指向且只能指向非常量 non_const_var
,通过对 common_pointer
的解引用,我们可以修改非常量 non_const_var
的值。但是如果普通指针 common_pointer
指向一个常量 const_var
,理论上来说,可以对普通指针 common_pointer
解引用而修改常量 const_var
的值,但常量又不能修改,所以普通指针只能指向非常量。
对于常量指针的逻辑亦是如此。
那么第二个问题是,cptr 指针指向的内容可以修改吗?显然也是不能修改的,因为它此时指向的是一个常量
const double pi = 3.14; // pi 是一个常量
const double *cptr = π // cptr 是一个指向常量的指针
cout << *cptr << endl; // 3.14
*cptr = 9.8; // error: assignment of read-only location ‘* cptr’
return 0;
可以看到报错的提示是, cptr 具有只读属性。特别的是,即使 cptr 指向的是非常量,它也不能修改这个非常量的内容(如下代码),仅记住一点即可,那就是 cptr 自己本身具有的属性就是只读的。
double pi = 3.14; // pi 是一个变量
const double *cptr = π // cptr 是一个指向常量的指针
cout << *cptr << endl; // 3.14
*cptr = 9.8; // error: assignment of read-only location ‘* cptr’
虽然指针常量不能修改指向地址的值,但是它可以修改指向的地址,如下可以看到指针常量 a 先指向 b 的地址,之后有成功指向 c 的地址
#include
using namespace std;
int main()
{
int b = 10;
int c = 20;
const int* a = &b;
cout << *a << endl; // 10
a = &c;
cout << *a << endl; // 20
}
常量指针寓意着指针本身的内容不能被修改。它的语法是 const 在 * 的右边:char *const p = greeting;
你可以这么理解它的含义,很多时候,我们可以对指针进行递增(减)操作,来移动它在内存中的位置,常量指针就是指不能进行这样的移动。
下面我们用一张图来解释什么叫“指针的值(指针本身的内容)不能被修改”
虽然这幅图已经说明了一切,但我们还是不妨用代码来进一步解释
#include
using namespace std;
int main()
{
double pi = 3.14; // pi 是一个变量
double e = 2.71; // e 是一个变量
double *const cptr = π // cptr 本身的内容不能改变
cout << *cptr << endl; // 3.14
cptr = &e; // error: assignment of read-only variable ‘cptr’
return 0;
}
上面的代码中,cptr 本来是指向 pi,但是当 cptr 转而指向 e 的时候就会报错,因为 cptr 作为指针,它是常量的,它的内容是不可以改变的。
但是 cptr 指向的内容却是可以改变的,这和 pointer to const 不同
double pi = 3.14; // pi 是一个变量
double e = 2.71; // e 是一个变量
double *const cptr = π // cptr 本身的内容不能改变
cout << *cptr << endl; // 3.14
*cptr = e;
cout << *cptr << endl; // 2.71
可以看到在执行完上面的程序后,cptr 指向的值(内容)发生了改变。
也许直接记忆中文的话会很容易绕进去,不如直接记忆它们各自对应的英文会要很多,即指向常量的指针是 pointer to const,常量指针是 const pointer。
对于 pointer to const 来说,显然指针指向的是一个常量(当然它也可以指向一个非常量),对于 const pointer 来说,显然它自己是一个常量,即这个指针是一个常量,它蕴含的意思是这个指针指向的地址永远不可以改变,就好像你永远住在 xx 省 xx 市 xx 路 xx 号一样。
最后还是用一幅图来解释二者的联系与区别
红色区域是不能改变的值。
【注】在这幅图中,如果我们把 pi 改为非常量,即 double pi = 3.14;
的形式,虽然依旧不能通过 cptr 来修改它的值,如 *cptr = 2.71 // error
,但是可以直接修改 pi 的值是没问题的,如 pi = 2.71; // right
。这一点我已经在上面的部分讲过,但在这里还想重复提一下。
#include
using namespace std;
int main()
{
double pi = 3.14;
const double *cptr = π
cout << *cptr << endl; // 3.14
// *cptr = 2.71; error
pi = 2.71;
cout << *cptr << endl; // 2.71
return 0;
}
现在我们思考这样一个问题,存在以下代码,它能否通过编译呢?
int main()
{
char c = 'c';
char* pc;
pc = &c;
const char** pcc = &pc;
return 0;
}
事实是不能的,在 const char** pcc = &pc;
位置会报错 error: invalid conversion from ‘char**’ to ‘const char**’
。原因是,我们的二重指针是 const
的,当 pcc 通过 pc 指向 c 后,逻辑上,我们不希望通过 pcc 来改变 c,但是,pcc 在指向 pc 的时候,pc 不是 const
的,意味着 pc 可以随时改变指针的指向。
上面的代码将 pc 设置为 const
就可以正常运行了
int main()
{
char c = 'c';
const char* pc;
pc = &c;
const char** pcc = &pc;
c = 'a';
cout << c << endl; // a
return 0;
}