顾名思义,
const就是constant的缩写,在C++中表示其所修饰的程序语言的元素是不可以改变的。它可以修饰的程序语言的元素有数据类型(内建的和用户自定义的)、函数、函数返回值、函数参数和类,而用const修饰的数据类型一般被称为常量。
相同数据类型的常量和非常量间是不可以随意的进行相互转换的: 常量可以向下兼容非常量,但是常量不能直接转换为非常量。这个问题主要是出现在调用函数时的参数传递和指针的地址获取上。由上面说的转换规则可知,声明、定义函数时,最好使用
const来修饰函数的参数,这样一来可以增大函数的适用范围。而指针常量则是一个比较特别的存在,有三种常量类型:指向地址不可变、指向内容不可变、二者皆不可变,形式如下:
const int *pi;
//指向内容不变
int *
const pi;
//指向地址不变
const int *
const pi;
//二者皆不变
int
const *
const pi;
//二者皆不变
同样,由转换规则可知,我们可以用一个常量指针指向一个非常量的地址,但是不可以用一个非常量指针指向一个常量的地址。
在C语言中,
const的含义较为简单,它表示所修饰的普通变量在进行初始化后就不可以被修改。C语言中允许对const性质的变量先声明,而后定义,而且常量链接进行的是外部链接,这与C++中大有不同。
在C++中,
const则被赋予了更多的意义:
首先,
C++中的const常量是不允许单独声明的,除非使用extern修饰或是在类中。也就是说大多数情况下,C++中的const只有定义;
其次,
C++中的常量链接时进行的是内部链接,只是文件内部可见的。这个特性在进行大型工程编写时要特别注意;
再次,
C++中的const拥有const fold的特性,这也是C++中针对const引入的最重要的特性。const fold就是将常量写入编译器的符号表,而不给常量分配内存,在编译时展开常量即可。我们知道,在C和C++中数组长度是在编译时确定的,而一般的变量则是在编译之后分配内存并赋值的,这也是为什么C和C++中不能用变量定义数组长度的原因。C语言中的常量和普通变量是一样的,只是赋值之后就不可改变罢了。所以C语言中同样不能使用常量定义数组的长度,而只能通过诸如:
#define array_length 100
之类的宏定义来定义数组的长度以便于代码阅读、数组长度修改和管理。但是宏定义有个致命的缺陷——不能进行类型检查,这将会导致一些难以发觉的错误。而
C++由于引入了const fold这一特性,多数情况下常量值是在编译时可见,也就是说,C++中的常量可以用来定义数组的长度,起到了替代宏定义的作用,增强了代码的安全性和可控性。
值得注意的是
const fold这一特性是一个编译器尽可能实现的性质,而不是一个必须实现的性质。若是程序需要获得某个常量的地址,比如说进行地址或引用的传递,那么编译器就会为常量分配地址,但这并不就说明该常量在编译时就不可见,不能定义数组长度。只有那些只有在程序运行时才能获值的常量才在编译时不可见,比如说:
const int i = scanf(“%d”,&i);
在
C++中比较特别的常量是类的成员常量和被const修饰的成员函数。成员常量的特性反倒比较贴近于C语言中对常量的定义。就是说,类中的常量和普通变量一样,只是初始化后不能改变罢了,在编译时不可见,不能用来定义数组的长度。如果非要想使用类中的常量来定义数组长度,则需再加上static修饰常量(注意,可以在类的声明中赋值的数据类型只有integral types,若不是,则可将赋值操作放到类的定义文件中)。特别要注意的是,类中的常量除了用static修饰的以外不能直接赋初值,能且只能在构造函数的初始化列表中初始化,之后则不可改变。而被const修饰的成员函数则意味着不能改变除了用mutable修饰的成员变量以外的任何成员变量。
在这里,需要引入两个概念来解释
mutable的由来。一直以来,人们对所谓的const member function有两种看法:bitwise constness和logical constness。我将之译为物理不变性和逻辑不变性。物理不变性指的是类构造出的对象所占据的内存空间中所有的数据在
const member function中都是不改变的。这是一种物理意义上严格的不变。可是,这并不符合人们看问题的逻辑。例如
class conot {
…
char &operator[](std::size_t position)
{ return pch[position] }
private:
char *pch; }
因为
pch所指向的字符串存储空间不在对象占据的内存空间中,所以我们可以通过操作符[]来改变pch指向的字符串。而这样一来,人们通常会认为该对象已经被改变了。于是,逻辑不变性在此基础上进行了修正,它指的是在用户无法察觉的情况下,对象的内容可以被改变。在上面的代码中,用户是无法看到、使用pch的,所以可以用mutable修饰pch,于是不仅pch指向的内容可以改变,pch的值也可以被改变。
从上面的内容可以看出,编译器强制执行的是物理不变性,而我们可以在C++中通过mutable实现逻辑不变性。所以,实际上我们接触的是逻辑不变性。