C和C++中有很多函数的参数类型使用了const这个关键字。例如C语言string.h头文件中的strcpy字符串复制函数,它的原型是
char * strcpy (char * Dest, const char * Source)
其中的const表示strcpy函数保证不修改Source指向的内存中的数据,而未加const的Dest指向的内容却可以被修改。
再如stdlib.h中的qsort快速排序函数原型如下:
void qsort (void * Base, size_t NumOfElements, size_t SizeOfElements, int (*PtFuncCompare)(const void *, const void *))
其中最后一个参数是一个函数指针,qsort需要你提供一个比较函数,这个比较函数以两个指针为参数,const要求你不能通过指针修改其指向的数据。
然而指针类型的参数加了const的函数就真的能保证它不会修改数据吗?
我们测试一下直接修改指向的数据会怎么样:
#include<stdio.h> void change_const(const char *s){ *s='B'; } int main(){ char s[]="Hello"; change_const(s); printf("%s",s); return 0; }
编译不通过!错误在第四行:[Error] assignment of read-only location ‘* s’
*s的类型是const char,所以对它赋值必然编译不通过。
再试试用函数修改s指向的数据:
#include<stdio.h> #include<string.h> void change_const(const char *s){ strcpy(s,"Hi"); } int main(){ char s[]="Hello"; change_const(s); printf("%s",s); return 0; }
还是编译不通过!错误在第五行:[Error] invalid conversion from ‘const char*’ to ‘char*’
strcpy的第一个参数类型是char *,但是你试图传入一个const char *类型的值,编译器告诉你不能这样类型转换。
别忘了C/C++有强制类型转换。如果编译器不让转换,我可以强制把const char *转换成char *类型!
#include<stdio.h> #include<string.h> void change_const(const char *s){ ((char*)s)[0]='B'; } int main(){ char s[]="Hello"; change_const(s); printf("%s",s); return 0; }
编译通过,程序输出Bello,说明const指针指向的数据被成功修改了。
再试试strcpy:
#include<stdio.h> #include<string.h> void change_const(const char *s){ strcpy((char*)s,"Hi"); } int main(){ char s[]="Hello"; change_const(s); printf("%s",s); return 0; }
编译通过,程序输出Hi。
这个测试说明C/C++的const是不可信的,通过强制类型转换可以让const失效。
然而我并不推荐以这种方式写代码,这样的代码是不规范的。我做这个测试是为了说明const只是编译时的限制罢了,运行时并不能保证加了const的变量不被修改。如果有一个第三方提供的库,我们不能绝对信任参数中加了const的函数不会修改我们的数据。
注:const int * a 和 int * const b 是不同的。前者要求不能通过a修改指向的内容,但是指针a可以被修改,使它指向别的地方;后者本身不能被修改,但是可以通过这个指针修改其指向的内容。*a=1无法编译通过,但是a=&x可以;b=&x不能编译通过,但是*b=1可以。
个人认为const主要是提供编译器的hint来做优化用的,而不是真正起protect作用
今天有人问到 mutable 的作用,又刚好看到 @雷锋 的回复…还是多说一点吧。
C++ 中 const 是 bitwise constness,要求该片内存是不可改的。但是凡事都有个但是,这个要求在面向对象范式中是不合逻辑的,因为有时候我们不关心这片内存是否被修改,而是关心逻辑上这个对象在操作后和原来的对象是否等价,即 logical constness。所以 C++ 有 const_cast 这种东西,而且存在非常合理的使用场景。比如说可能有一个字符串类 string,通常情况下插入是用链表加速,在最终使用时重新整合成一整块字符串。很明显,此处我们不关心里面到底发生了什么,只关心最终 .c_str() 能拿到一个可用的 const char *,而且这在 const string 上也应该可以工作,因为我不去修改它,只是从里面拿数据而已。(这实际上就是某些 C++ 标准库实现的做法)
现在 C++11+ 有了 mutable 和 constexpr,就可以大大消除 const_cast 的使用场景了。比方说上面的字符串类里面可以把内部的链表、缓冲区改成 mutable。真正的常量用 constexpr 而不必担心被人乱改。
>如果有一个第三方提供的库,我们不能绝对信任参数中加了const的函数不会修改我们的数据。
const参数被修改了只能说明那个库有bug…如果你调用库的时候都是假定库很buggy的话,那你什么库都不能用了(包括标准库)
嗯,是的。这篇文章主要是想说明语法的“常量”只是编译时检查,并不一定有运行时的保护
如果这么说的话,那什么都不可相信了。
const的原意不仅仅是编译的时候不让修改,而且常量是要放在常量储存区的,想修改,那块内存区域是不让修改的。
不过真正编译器的具体实现如何就不好说了。
赞!