💡 本篇的内容来自C++ Primer中的2.4节
基本内容
定义与初始化
We can make a variable unchangeable by defining the variable’s type as const.
- 因为const变量,不能改变,因此必须初始化。
const int i = get_size(); // 正确,runtime初始化
const int j = 42; // 正确,compile time初始化
const int k; // 错误,没有初始化
const变量可以接受那些无法改变变量本身数据的操作,例如赋值和copy等。
const int ci = i;
int j = ci; // 正确,j初始化后,与ci无关。
特性:const Objects Are Local to a File
- const variables are defined as local to the file.
- When we define a const with the same name in multiple files, it is as if we had written definitions for separate variables in each file.
对于const变量,规定其只在本文件中起作用。
为什么这样规定?这要提到compiler。对于
const int bufSize = 512
这种使用编译时常量初始化const变量时, 在编译时,编译器会将所有提到的变量进行替换,为了完成替换,编译器必须知道初始化值,当程序分成多个文件时,为了支持编译器的这种操作,需要在所有使用该变量的多个文件中定义同名的const变量,这是我们不愿见到的,因此定义const变量只在本文件生效。
当在一个文件中定义了const变量,但是想在另一个文件中使用,需要使用extern
关键字。
// file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_1.h
extern const int bufSize; // same bufSize as defined in file_1.cc
这样可以实现const变量的共享。
References to const
我想,有时候学技术,还是要看英文原版的说明,用词更准确和直接。
澄清概念
这里首先明确 references to const 指的是:
- bind a reference to an object of a const type.
- the object is a reference that refers to a const type;
但是,这种对于const的引用,不能通过引用改变其值。
const int ci = 1024; // ok: both reference and underlying object are const
const int &r1 = ci;
r1 = 42; // error: r1 is a reference to const
int &r2 = ci; // error: nonconst reference to a const object
我们在之前讲引用时,提到引用必须初始化,且在整个生命周期中,不能改变其绑定的变量。因此,这里需要澄清一下references to const 中易于混淆的概念:
- 有人认为:references to const即为 const references,这会造成误解,其实本质不同;
- 由于引用本身不能更换引用的对象,因此 in some sense all references are const,或者说,技术上,不存在const references;
- 我们一直提的const,不能改变,其实指的是reference to const中,不能通过该引用改变其绑定的值;
初始化
在引用中,引用本身的类型必须与绑定的变量的类型匹配,但是加入了const后,就出现了例外。
const对reference初始化的影响
reference必须初始化,且终生不能改变绑定对象;而且其引用的对象类型必须与自己相匹配;且初始化时使用的表达式只能是objects, 不能是 literal 或者运算表达式。
但是const的使用改变了这些规则。
we can initialize a reference to const from any expression that can be converted to the type of the reference.
const int i = 42;
const int &r1 = i;
const int &r2 = 42; // ok: r1 is a reference to const
const int &r3 = r1 * 2; // ok: r3 is a reference to const
int &r4 = r * 2; //error: r4 is a plain, nonconst reference
对于类型不匹配的也是可以的,其中存在隐式类型转换。
double dval = 3.14;
const int &i = dval;
cout << i << endl; // 输出3
这里可以这样理解,编译器会生成一个临时变量 const int temp = dval, 然后通过i 引用该temp,所以i 并没有直接引用dval。
references to const可以绑定非常量对象
我们提到references to const不能改变绑定对象的值,但是并不意味着绑定对象只能是const变量,只要保证无法通过references to const改变其值即可,因此绑定对象可以为非常量。
int i = 42;
int &r1 = i; // ok
const int &r2 = i; // ok
r1 = 0; // ok, i可以改变
r2 = 0; // error, i不能通过r2改变
所谓,功成不必在我。
Pointers and const
pointer to const
使用指针指向一个const变量,是可行的,但是前提是该pointer是一个pointer to const。
const double pi = 3.14;
double *ptr = π // error: ptr is a plain pointer
const double *cptr = π // ok, pointer to const
*cptr = 42; // error: cannot assign to *cptr
注意事项:
- pointer to const: 指向的对象不一定是const,这个有一点离奇,但是就是这么回事,其核心要强调的点是:通过该pointer to const不能改变指向对象的值,但是该对象的值可以通过别的方式改变;
double dval = 3.14; cptr = &dval; *cptr = 1.23; // error ptr = &dval; *ptr = 1.23; // ok
const pointer
- 与reference不同,pointer是一个object, 因此其本身也可以是const的。
- const pointer指的是该pointer本身不能改变,即不能指向其他的对象;
- 同一般的reference一样,也必须进行初始化;
- 最容易的理解方式:还是从右往左读;
int errNumb = 0; int *const curErr = &errNumb; // const pointer const int *ptr = &errNUmb; // pointer to const const double pi = 3.14159; const double *const pip = &pi // const pointer and pointer to const
- 对于const pointer,与pointer to const一样,对于所指向的对象是否为const,没有限制;
Top-level const
这里只是一个名词的解释:
- top-level const: indicate that the pointer itself is a const;
- low-level const: indicate that a pointer can point to a const object;
这两个名词,从英文来看很好理解,有什么用呢?语法规定:
- Top-level const can appear in any object type;
- Low-level const appears in the base type of compound types such as pointers or references; 因为只有指针和引用可以指向别的对象。
什么意思?
- 在const对象和非const之间可以随便赋值,拷贝对象,不限制一定是const;
- 但是,在pointer to const对象中,如果进行赋值和拷贝,两者必须都是pointer to const. 这样保证,即便发生了复制,也不能通过新的pointer改变指向的对象的值。
int i = 10;
const int *p1 = &i;
int *p2 = p1; // error
const int *p3 = p1; // ok
Constant Expressions
A constant expression is an expression whose value cannot change and that can be evaluated at compile time.
const
const修饰的一定是constant expression吗?不一定,因为这些值不一定是compile time时确定的, 即:const并不会区分runtime 和compile time变量。
const int sz = get_size(); // sz is not a constant expression
constexpr
编译时检查
由于const与constant expression, 即不是充分条件,也不是必要条件,但是会让人混淆。在C++11中,constexpr被提出,让compiler确认,通过其修饰的一定是compile time常量,即constant expression, 因此其一定必须初始化。
constexpr int mf = 20; // 20 is a constant expression constexpr
int limit = mf + 1; // mf + 1 is a constant expression constexpr
int sz = size(); // ok only if size is a constexpr function
因此,最好使用constexpr代替const,使得能够在compile time时进行检查。
数组的维度,也是在compile time检查的,可以结合进行验证。
literal types
由于constexpr变量在编译时验证,因此其应用的类型也受到限制,只能是literal types:
- arithmetic, reference, and pointer types are literal types.
- the library IO and string types are not literal types.
constexpr string i = "hello, world"; // error, c++20可以
pointers
when we define a pointer in a constexpr declaration, the constexpr specifier applies to the pointer, not the type to which the pointer points.
constexpr产生的是 top-level const on the objects it defines.
const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int
constexpr const int *p = &i; // p is a constant pointer to the const int i
这语法,C++ NB。