【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.const
和constexpr
的区别
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定义在函数体外,有固定地址
}