⚠️
本文之前有个图画错了,经 C 友指正,我重新完善了本文,感谢每一位参与讨论的 C 友。
本文在修改的过程中重点参考了《Difference between constant pointer, pointers to constant, and constant pointers to constants》这篇文章,也特别推荐给大家

指针常量

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

通常也将指针本身是一个常量称为顶层 const,将指针所指的对象是个常量称为底层 const
关系总结如下

  1. 常量指针 = * const = 顶层 const
  2. 指向常量的指针 = 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 <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
}

在上图中,指向常量的指针还可以指向一个非常量,而当指向常量的指针指向非常量的时候,依旧,不能通过该指针修改这个非常量,如下。

int main()
{
    int nonconst  = 100;
    int nonconst2 = 300;
    const int* pointer2const = &nonconst;
    
    cout << *pointer2const << endl; // 100
    
    // *pointer2const = 200; // error
    
    pointer2const = &nonconst2; // 可以指向其他非常量
    cout << *pointer2const << endl; // 200
 
    return 0;
}

常量指针

常量指针寓意着指针本身的内容不能被修改,即该指针被固定指向一个内存空间。它的语法是 const 在 * 的右边:char *const p = greeting;

在这里插入图片描述

你可以这么理解它的含义,很多时候,我们可以对指针进行递增(减)操作,来移动它在内存中的位置,常量指针就是指不能进行这样的移动。

虽然这幅图已经说明了一切,但我们还是不妨用代码来进一步解释

int main()
{
    int a = 100;
    int b = 400;
    int* const ptr = &a;
    
    cout << *ptr << endl;
    cout << ptr << endl; // 假设 ptr 指向 a 的地址为 0x123
    
    *ptr = 200; // 可以改变 0x123 中存的值,这里改为 200
    
    cout << *ptr << endl; // 200
    
    a = 300; // 可以改变 0x123 中存的值,这里改为 300
    
    cout << *ptr << endl; // 300
    
    // ptr = &b; // error,但不能再让 ptr 指向其他的地址了
 
    return 0;
}

上面的代码中,常量指针 ptr 首先指向了 a 的地址,就好像 ptr 永远定居在了 a 这栋房子里。但是你可以改变 a 房子里的装修,比如,我先将 a 地址通过 *ptr 装修成了 200,又通过 a 将房子里装修成了 300。

但当我要将 ptr 迁居到 b 地址时,编译器告诉我,你已经离不开 a 了。

拓展

现在我们思考这样一个问题,存在以下代码,它能否通过编译呢?

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;
}

参考

  1. 《Primer C++》
  2. 《C++ 面向对象高效编程》
  3. 简述指针常量与常量指针的区别
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐