指向常量的指针

指向常量的指针,即 pointer to const,即指针指向的是一个常量,你应该把这个词(指向常量的指针)当做一个整体来理解,而不是分开。(当然也有翻译成指针常量的,但我并不喜欢这种翻译方式)

通常也将指针本身是一个常量称为顶层 const,将指针所指的对象是个常量称为底层 const

它的语法格式是 const 在 * 左边,这一点很重要,因为后面要讲的常量指针是 const 在 * 的右边。下面是它的基本语法格式

const double pi = 3.14;   // pi 是一个常量
const double *cptr = π // cptr 是一个指向常量的指针

// 或者,它的另一种写法是交换一下 const 和 double 的位置顺序

double const *cptr = π // cptr 是一个指向常量的指针

下面我们举一个例子来进行具体的讲解

#include <iostream>
using namespace std;

int main()
{
const double pi = 3.14; // pi 是一个常量
const double *cptr = &pi; // cptr 是一个指向常量的指针
cout << *cptr << endl; // 3.14
return 0;
}

这里有一个常量 pi,我用 cptr 这个指向常量的指针来指向它,它的内存示意图应该如下:

【注】当我说指针的值(内容)的时候,我说的是 0x456。当我说指针指向的值(内容),我说的是 3.14,下面的语境请随着具体上下文自动脑修。
关于指针常量的两个扩展:
第一个是我们用普通的指针不能指向这个常量

  const double pi = 3.14; // pi 是一个常量
double *pnormal = &pi; // 用普通指针指向常量 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 = &pi; // 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 = &pi; // cptr 是一个指向常量的指针
cout << *cptr << endl; // 3.14
*cptr = 9.8; // error: assignment of read-only location ‘* cptr’

虽然指针常量不能修改指向地址的值,但是它可以修改指向的地址,如下可以看到指针常量 a 先指向 b 的地址,之后有成功指向 c 的地址

   #include <iostream>
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 <iostream>
using namespace std;

int main()
{
double pi = 3.14; // pi 是一个变量
double e = 2.71; // e 是一个变量
double *const cptr = &pi; // 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 = &pi; // 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 <iostream>
using namespace std;

int main()
{
double pi = 3.14;
const double *cptr = &pi;
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;
}