0%

重学C/C++中的const

使用C/C++语言的同学应该对const都比较了解,但对于初学者来说,const确是一个难肯的骨头,理解起来困难重重。今天我就带你重新学习一下C/C++的中的const

const 与 #define 的区别

C/C++中定义常量通常使用const关键字,当然你也可以使有宏#define来定义。这两种方式定义常量如下所示:

  • const 定义常量
    1
    const int PI = 3.14;
  • 宏定义常量
    1
    #define PI = 3.14

这两种定义常量的方式有什么区别呢?

要回答这个问题,我们需要了解一点编译原理的知识。编译器在编译我们写好C/C++程序时,其编译器过程为:预编译->编译->链接C/C++中宏的替换就是在预编译阶段完成的,也就是说在预编译阶段将C/C++中的所有用到的宏都用宏定义中的值替换掉。

const 定义的常量则与宏定义的常量不同,它是在编译阶段进行检测,而且还可以对其类型进行检测。因此我们可以总结出使用const定义的常量与宏定义的常量有如下区别:

  • 宏是在预编译时进行宏展开,而const是在编译时检测,所以两者操作的时期不同
  • 由于宏在预编译时被操作,所以无法进行类型检测;而const则可以进行类型检测
  • 因在编译阶段可以形成符号表,所以const定义的常量可以通过调试器进行调试;而宏在展开后就消失了,所以无法通过调试器进行调试

以上就是const的最主要的区别。所以一般情况下我们都建议使用const来定义常量。

const常量与const常量指针

上面我们已经列举过const如何定义常量,这里就不再赘述了。现在咱们来看一下const常量指针,它该如何定义呢?

1
const int * ptr; //const常量指针

上面就是const常量指针的定义,也挺好理解的对吧。我们将const常量和const常量指针放在一起看一下:

1
2
const int i;    //const常量
const int* ptr; //const常量指针

将他们放在一起比较着看,你就更容易理解const常量指针了,无非就是将const常量中的类型变成指针而已。

const常量指针的作用

那么常量指针的作用是什么呢?,我们来看个例子你就清楚了。

1
2
3
4
5
6
7
8
const int i = 100;     // 定义一个常量
i = 100; // 不允许修改常量

int *p = &i; // 老的编译器是被允许的,这实际存在安全问题
// 为了解决这个问题,新的编译器报错,非常量指针不允许指向常量地址

const int * ptr = &i; // 常量指针指向常量地址
*ptr = 100; // 不允许修改常量的内容

按照常量的定义,常量定义好后其内容就不允许再修改了,因此对于上面代码中的前两行相信你不会有什么异义。

但在较老的编译器上,存在一个漏洞,它允许你用普通指针指向常量地址。这样你就可以通过该指针修改常量的内容了,这是非常大的安全漏洞。为了消除这个安全隐患,在新的编译器上已经不允许普通指针指向const常量了。

为什么在老编译器上指针可以指向常量地址并修改其内容呢?究其原因是因为const定义的常量实际是在内存的可读写空间,只是由于编译器限制你才不能修改它。而老的编译器却没有这方面的限制,所以才会出现通过普通指针修改常量的可能。

我们再来看代码的最后两行。使用const常量指针指向常量地址,此时你无论用新编译器还是老编译器,都无法通过该指针修改常量的内容。所以代码的最后一行当你修改常量内容时就会报错。

通过上面的讲解,你应该对常量指针的概念比较清楚了。常量指针不能修改常量内容,但能不能让常量指针指向另外一个常量的地址呢?比如下面这样:

1
2
3
4
5
const int i = 100;
const int n = 1000;

const int * ptr = &i; //常量指针先指向 i 常量的地址
ptr = &n; //又修改为指向 n 常量的地址

这样做当然是可以的,因为常量指针限制的是不能修改常量内容,但并没有限制它指向哪个常量。

const变形

上面我们已经清楚了const常量指针是什么,它起了什么作用。但它还有一点你不知道,就是它会变型。我们来看一个例子:

1
2
const int * ptr;
int const * ptr;

上面这两行代码很像是不是?第二行代码将 const 放到了 int 类型之后,它表达的是什么意思呢?其实两行表示的是同一个意思,都是常量指针。只是有的人喜欢将const写在最前面,有的人喜欢将const 写在类型后面罢了。这里有一个记忆的小巧门,我们只要记住const是在 * 左边它就表示的是常量指针就OK了,

指针常量

上面的内容清楚之后,我们再来一个复杂的指针常量。看到这个词相信很多同学立马晕了,上面是常量指针,这又来个指针常量是这是说绕口令吗?先别急,我们先来看个例子:

1
int * const ptr;

上面这行代码是不是与前面的很相似?一模一样? 如果你这样认为说明你没有仔细观察。之前的常量指针const是在*号左边,这次的const跑到*号右边了。

它表示的是什么意思呢?

前面我已经说了,对于常量指针来说,你是不能修改它所指向的内容的,因为内容是常量,但它可以让它指向不同的常量地址。新需求来了,有没有可能让指针指向一个地址就不动了呢?或者换个思考的角度,既然const可以定义常量,能不能定义一个指针常量呢?这就是指针常量的由来

C/C++编译器的作者考虑的一下这个需求,觉得这个需求是合理的,决定实现它。但怎么才能表示指针常量呢?于是就有了 const int * const ptr 这个写法,const 放在*后面表达对ptr的限制。

了解了指针变量的由来,下面我们来看一下它的用法:

1
2
3
4
5
6
7
int i = 100;
int n = 1000;

int * const ptr = &i; // 正确,初始指向某个变量
ptr = &n; // 错误,ptr是常量,不能再发生变化

*ptr = 20; // 正确,因为我们没有对指针指向的内容做限制

上面代码中定义了两个变量 i 和 n,ptr是针指常量,因此它只能在初始化时指向某个变量的地址,之后它就不能更改变指向其它地址了,因为它是常量但需要注意的是ptr指向的内容是可以被修改的

指向常量的指针常量

看这个标题就觉得好复杂啊!没错我们又要升级难度了。先看个例子

1
2
const int * const ptr1;
int const * const ptr2;

天呐,一条语句中出列了两个const,如果我们没有基础的话,这两行代码简直无法理解。不过,有了上面的基础我们再来看这两句还是能猜出它要干什么的对吧?

这两条语句的含义是一样的,表示的是ptr1/ptr2指向的地址不能再改变,而且它指向的地址里的内容也不能再改变。

小结

在本文中我向你详细介绍了C/C++中的const的含义和用法,总结一下包括以下几种:

  • 定义常量, 内容不能改变,const int a;
  • 定义常量指针,指向的内容不能改变,const int * ptr;int const * ptr;
  • 定义指针常量,指针不能改变,但指向的内容可以改变。int * const ptr
  • 定义指向常量的针指常量,指针不能改变,内容也不能改变。const int * const ptrint const * const ptr

参考

C++高阶知识:深入分析移动构造函数及其原理
细说智能指针
聊聊C++中的类型转换

欢迎关注我的其它发布渠道