【C++基础】第十课:const限定符

const变量,const引用,const指针,顶层const,底层const,常量表达式,constexpr

Posted by x-jeff on August 17, 2019

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.定义一个const变量

有时我们希望定义这样一种变量,它的值不能被改变,这时可以用关键字const对变量的类型加以限定:

1
2
3
const int bufSize=512;//输入缓冲区大小
bufSize=512;//错误:试图向const对象写值
const int k;//错误:k是一个未经初始化的常量

❗️因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化。

const对象可以完成大部分非const对象所能完成的操作,主要的限制就是只能在const类型的对象上执行不改变其内容的操作。例如:

1
2
float i=2.15;
const int ci=i;

2.const变量的声明

⚠️默认状态下,const对象仅在文件内有效。

但是某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。即只在一个文件中定义const,而在其他多个文件中声明并使用它。解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了:

1
2
3
4
//file_1.cpp定义并初始化了一个常量,该常量能被其他其他文件访问
extern const int bufSize=fcn();
//file_1.h
extern const int bufSize;//与file_1.cpp中定义的bufSize是同一个

⚠️如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。

3.const的引用

可以把引用绑定到const对象上,就像绑定到其他对象上一样,称之为对常量的引用

1
2
3
4
const int ci=1024;
const int &r1=ci;
r1=42;//error:r1是对常量的引用
int &r2=ci;//error:试图让一个非常量引用指向一个常量对象

❗️因为不允许直接为ci赋值,当然也不能通过引用去改变ci

⚠️正常来说,引用的类型必须与其所引用对象的类型一致,但是有两个例外。

本文介绍第一个例外的情况:在初始化常量引用时允许任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是一个一般表达式:

1
2
3
4
5
6
7
8
9
int i=42;
const int &r1=i;//允许将const int&绑定到一个普通int对象上
const int &r2=42;//正确
const int &r3=r1*2;//正确
int &r4=r1*2;//错误:r4是一个普通的非常量引用

double dval=3.14;
const int &ri=dval;//ri=3;dval=3.14
dval=5.14;//此时:ri=3;dval=5.14

4.指针和const

与引用一样,也可以令指针指向常量或非常量。

1
2
3
4
const double pi=3.14;//pi是个常量,它的值不能改变
double *ptr=π//error:ptr是一个普通指针
const double *cptr=π//正确
*cptr=42;//错误:不能给*cptr赋值

⚠️正常来说,指针的类型必须与其所指对象的类型一致,但是有两个例外。

本文介绍第一种例外:允许令一个指向常量的指针指向一个非常量对象:

1
2
double dval=3.14;
cptr=&dval;//指针的值改变了

这里需要注意,const double *cptr只限制了不能改变指针指向的那个值,但是我们却可以改变指针本身。这种情况可用顶层const和底层const来解释。

5.顶层const和底层const

顶层const(top-level const)表示指针本身是个常量。

底层const(low-level const)表示指针所指的对象是一个常量。

更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算数类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层const也可以是底层const,这一点和其他类型区别明显:

1
2
3
4
5
6
int i=0;
int *const p1=&i;//不能改变p1的值,这是一个顶层const
const int ci=42;//不能改变ci的值,这是一个顶层const
const int *p2=&ci;//允许改变p2的值,这是一个底层const
const int *const p3=p2;//靠右的const是顶层const,靠左的是底层const
const int &r=ci;//用于声明引用的const都是底层const

6.constexpr和常量表达式

常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。

一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定:

1
2
3
4
const int max_files=20;//max_files是常量表达式
const int limit=max_files+1;//limit是常量表达式
int staff_size=27;//staff_size不是常量表达式
const int sz=get_size();//sz不是常量表达式

注意:尽管sz本身是一个常量,但它的具体值直到运行时才能获取到,所以不是常量表达式。

在一个复杂系统中,很难(几乎肯定不能)分辨一个初始值到底是不是常量表达式。因此,⚠️一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr类型。

6.1.constconstexpr的区别

const是表明这个值是constant的,但是不必在编译期确定,然而数组的大小是需要在编译期确定的,如:

1
2
3
int i;
const int size=1;
int arr[size];//error:因为编译期size的值并不确定,因此不能拿来初始化数组大小

然而对于constexpr,则表明这个值不仅是constant的,而且是必须在编译期确定的:

1
2
int i;//
constexpr int size=i;//error:因为i的值不确定,所以在编译期无法确定size的值(即使i被初始化或者赋值之后也不可以,因为i的值存在变动的可能性,在编译期并无法确定)

6.2.字面值类型

常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为“字面值类型”(literal type)。

  • 算术类型、引用和指针都属于字面值类型。
  • 自定义类、IO库、string类型则不属于字面值类型,也就不能被定义成constexpr

尽管指针和引用都能定义成constexpr,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。

⚠️函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。

⚠️此外,允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址。因此,constexpr引用能绑定到这样的变量上,constexpr指针也能指向这样的变量。

6.3.指针和constexpr

❗️必须明确一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。

1
constexpr int *q=nullptr;//constexpr把它所定义的对象置为了顶层const
1
2
3
4
5
6
int j=0;
constexpr int i=42;
int main(){
	constexpr const int *p=&i;//i定义在函数体外,有固定地址,如果是定义在函数体内就不行
	constexpr int *p1=&j;//j定义在函数体外,有固定地址
}

7.参考资料

  1. c++ const和constexpr的区别(百度知道)