const限定符
cv type包括:const
和volatile
(或者还有mutable)限定符。
const用于定义无法被改变值的常量。
- 可执行不改变内容的其他变量操作,如初始化其他变量、算数运算等
- 默认状态下仅在单个文件内有效,解决方式:
- 在不同文件分别创建const,本质上是不同的同名变量
- 使用extern在不同文件进行声明,结果是同一变量
const
和#define
的区别:#define
后的内容在编译前会被全文替换,而const
则只会在编译时进行取值,且#define
无数据类型和存储空间,const
常量有数据类型且被分配存储空间。
const引用
引用const常量,只能使用对常量的引用,如const int&
。因为引用本身并非一个对象。
又称常量引用。仅在常量引用中存在以下情况,引用的类型可与被引对象不一致:
-
初始化常量引用时允许任意表达式,只要该表达式能转为被引用类型。
int i = 42; double j = 4.2; const int& r1 = i; //可行 const int& r2 = 42; //仅在const引用中可行 const int& r3 = r1 * 2; //仅在const引用中可行 int& r4 = r1 * 2; //错误 const int& r5 = j; //正确,但值有损失
原因:在编译时,引用绑定了一个临时量对象:
double j = 4.2; const int& rj = j; //-------等价于------- double j = 4.2; const int temp = 4.2; const int& rj = temp;
通过const引用非const的变量,不能通过const的引用改变其值:
int i = 42; int& r1 = i; const int& r2 = i; r1 = 4; r2 = 3; //报错
指向const的指针、const指针
指向常量的指针 pointer to const
存放常量的地址,只能使用指向常量的指针如const int*
指向常量的指针指向一个非常量对象时,二者类型可不一致,参见对常量的引用。
常量指针 const pointer
本质上就是将指针本身定义为常量。
常量指针必须初始化,之后它存放的地址就不可改变。
int a = 0; //变量
const int b = 1; //常量
int* const pc_a = &a; //指向变量的常量指针
const int* cp_a = &a; //正确,但指向的可以不是常量
int* p_b = &b; //错误,不存在指向常量的普通指针
int* const pc_b = &b; //错误,没有指向常量的const标识
const int* const cpc_b = &b; //指向常量的常量指针
int** pcp_a = &cpc_b; //错误,没有指向常量的const标识
const int** cp_cpc_b = &cpc_b; //错误,不存在指向常量指针的普通指针
const int* const* cpc_cpc_b = &cpc_b; //指向常量指针的普通指针
const int* const** cpc_cpc_cpc_b = &cpc_cpc_b //上式取地址
//......
对于const指针的取地址操作
对于指向常量的指针,类型为:const int*
,取地址后为const int**
,因此需要按此顺序进行下一层指针的定义。
下一层指针类型为const int* const*
,则取地址后为const int* const**
,即为再下层的定义方式,以此类推……
顶层和底层const
指针本身是否为常量和是否指向常量相互独立。按此可分为:
- 顶层top-level const:指针本身是个常量。定义指针时,(相对)右边的const表示顶层const。
-
底层low-level const:指向对象是个常量。定义指针时,(相对)左边的const表示底层const。
声明引用的const都是底层const。
template<typename T> using Const = const T;
template<typename T> using Ptr = T*;
const int *** const shit = nullptr;
//等价于:
Const<Ptr<Ptr<Ptr<Const<int>>>>> shit = nullptr;
const int ** const ** const * const A
(仅作理解用)
可以理解为
const <- (* <- (*const <- (* <- (*const <- (*const <int>))))) A
读法从右往左:
const (int) pointer A which points to (a const pointer points to (a pointer points to (a const pointer points to (a pointer points to (a const))))).
-
*
一定在int之后。const
修饰左边的星号,左边没有星号则是底层const - 判断时以
*
为分界符,内部多个const
进行合并,可和int
互换:-
const int const*
等价于const int*
-
- 对于
const int **** const **** const
:- 上一层有
最左const
,则下一层必须有最左const
。当前顶层以外的所有的底层均不能更改,无论是否最终指向一个常量。 -
int
右无const
时,随着取地址,int
后*
+1 - 有
最右const
,取地址时,*
只能在该const
后+1。 - 以上任意层的最右边都可以添加
顶层const
,表明指针自身是个const。 - 简而言之,左边必须保持不变,右边随着指针层数增加,一层一层加
const
和*
- 上一层有
constexpr和常量表达式const expression
常量表达式const expression:不会改变并且在编译过程就能得到计算结果的表达式,如字面值常量等。用常量表达式初始化的const对象也是常量表达式。
const int a = 0 // 是const expression
const int b = a+1 // 是const expression
const int c = d() // 不是const expression
int e = 0 // 不是const expression
constexpr
https://en.cppreference.com/w/cpp/language/constexpr
当难以分辨一个初始值是否是常量表达式时,(since C++11)可以声明变量为constexpr类型以便由编译器验证变量的值是否为常量表达式。
声明为constexpr的变量一定是一个常量,且必须用常量表达式初始化。
const修饰的是类型,constexpr修饰的是用来算出值的那段代码。
constexpr指针
constexpr指针定义无论顺序如何,仅修饰指针本身而不是所指对象,且初值必须是nullptr或者0或固定地址中的对象。如:
const int* p = nullptr; //指向常量的指针
constexpr int* pp = nullptr; //常量指针
int* const ppp = nullptr; //同上
但是,函数体外定义的变量由于生命周期长,可以用于初始化constexpr指针。因此,将constexpr指向常量时的操作如下:
#include <iostream>
const int a = 1;
constexpr int b = 0;
int main(){
constexpr const int*pa = &a; //constexpr中指向常量的常量指针
constexpr const int*pb = &b; //合理
std::cout<<*pa<<*pb;
return 0;
}