const在C中就早已出现,该限定符可以将一个变量限制为常量。在C++中,const有了更多的含义。
举个栗子
const int ci = 1024; const int &r1 = ci; //没有问题,引用和变量都是const常量,可以进行绑定 r1 = 42; //很明显的错误,不能对一个常量赋值 int &r2 = ci; //错误,r2是一个非常量引用,不能对一个常量进行绑定与指针相似,非常量引用不能与和常量进行绑定,这样会导致不安全行为的出现(通过引用修改一个常量)。在使用时,可以将const理解为变量的基本类型之一,非同类且不能进行类型转换的变量自然不能进行赋值运算。
但是也有例外:
int i = 42; const int &r1 = i; //允许将const int &绑定到一个普通int对象上 const int &r2 = 42; //正确,r2是一个常量引用 cosnt int &r3 = r1 *2; //正确,r3是一个常量引用 int &r4 = r1 *2; //错误,r4是一个普通的非常量引用引用在初始化时,如果其被const修饰,则其可以被任何可以转换成其本身类型的表达式初始化,如上面的r1和r3,都将引用绑定到了一个非常量表达式上。 但是实际上,这种引用并没有绑定到为其赋值初始化的变量上,而是与一个叫“临时量”的东西绑定在一起,具体如下:
const int temp = r1 * 2; const int &r3 = temp;这样就好理解很多了,编译器先计算r1*2的值,并将其保存在内存中的一个位置中,一般情况下,该位置的具体信息应该是隐式不可见的,之后,再将该临时量与定义的引用绑定在一起。至此,引用绑定完成,却没有与任何可见的量绑定在一起,这也是危险的行为之一。
与C相同,const和指针在一起时,只有两种形式:常量指针和指向常量的指针,具体形式如下:
const double pi = 3.14; const double *cptr = π //这是指向常量的指针 double *const lcptr = π //这是常量指针常量指针是指指针本身是常量,除了初始化外不能被再次赋值。 指向常量的指针如字面意思,指针指向的对象是常量,不可进行修改。
为了区分描述常量指针和指向常量的指针,将const的描述分为顶层(top-level-const)和底层(low-level-const)。
顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等底层const则与指针和引用等复合类型的基本类型部分有关。举个栗子:
int i = 0; int *const p1 = &i; //不能改变p1的值,只是一个顶层const const int ci = 42; //不能改ci的值,这是一个顶层const const int *p2 = &ci; //不能改变ci的值,允许改变p2的值,这是一个底层const const int *const p3 = p2; //靠右的const是顶层const,靠左的const是底层const const int &r = ci; //用于声明引用的const都是底层const在const与非const对象或具有不同const资格的对象间拷贝数据时,有一些限制:
当执行对象拷贝操作时,拷入和拷出的对象必须具有相同的const资格,或者两个对象的数据类型必须能够互相转换。
int *p = p3; //错误,p3包含底层const的定义,而p没有 p2 = p3; //正确,p2和p3都是底层const p2 = &i; //正确,int*能转换成const int* int &r = ci; //错误,普通的int&不能绑定到int常量上 const int &r2 = i; //正确,const int&可以绑定到一个普通int上对被const修饰的变量执行拷贝操作前,弄清楚其究竟是底层还是顶层const更加安全。
constexpr是C++11新规定的修饰符,被该修饰修饰的变量将在程序编译时由编译器检查该变量是否是一个常量,如果不是,则编译器将报错。同样,constexpr也适用于函数,该类函数应该足够简单易使其在编译时即可获得结果(如依赖于机器的一些适应性函数)。
constexpr int mf = 20; //20是常量表达式 constexpr int limit = mf + 1; //mf+1是常量表达式 constexpr int sz = size(); //只有当size是一个constexpr函数时才是一条正确的声明语句一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr。
