目录
一.概念
const关键字,对变量的类型加以限定,规定此变量一旦创建后其值不能再被修改,但可以参加运算。
注意:
- const对象必须初始化,初始值可以是任意复杂的表达式。
- 默认情况下,const对象仅在文件内有效。如果想要const对象在不同的文件中都能使用,则只需无论是申明还是定义在前面加上extern关键字即可。
1.cpp
extern int j=10; // 定义
2.cpp
extern int j; // 声明,此时j=10
3.常量定义了变量,编译器所作的事情是把本文件中所有用到该变量的地方都替换成对应的值,这意味着,这个变量是不会分配空间的,比如:
const int i=10;
int *p=&i; // err 这是错误的
二.const引用
2.1 定义
把引用绑定到const对象上,就像绑定到其他对象上一样,称之为对常量的引用,常量的引用不能被用作修改它所绑定的对象,即常量引用绑定了的对象将不能再进行修改。
int a=10;
const int &b=a;
b=20;//错误
2.2 常量引用可以不必与其所引用对象的类型一致
如:
int i=10;
const int &r1=i;
const int &r2=20;
const int &r3=r1*2;
以上是正确的,此时,虽然左边是const int 类型的引用,而右边分别是int类型、字面值类型以及表达式,不与左边的常量引用类型相匹配,但是这也是正确的。对于表达式注意,这个表达式必须最终能转换为引用的类型。
如:
int &r4=r1*2;
这个就是错误的,因为左边是int类型的引用,并不是常量引用,所以它必须要与右边的类型相匹配。
2.3 发生细节
如一个常量引用:
double b=3.14;
const int &a=b;
这其中编译器做了一些事情
double b=3.14;
const int &a=(b);
(
//编译器做的事
const int temp=b;
const int &a=temp;
//
)
这其中的temp成为临时量,临时量:编译器创建的一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。
三.指针和const
3.1 指向常量的指针
指向常量的指针不能用于改变其所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针。同时其左右类型不必相同。
const int a = 10;
const int *o = &a;
int b = 10;
const int *c = &b;
指向常量的指针的变量存储的是对象的地址,但是常量const修饰的是这个int型指针,所以对于这个int型指针的赋值操作不能进行。
int b = 10;
const int *c = &b;
b = 20;
cout << *c << endl; // *c=20;
3.2 const指针(常量指针)
把指针本身定为常量。即:
int a=10;
int *const p=&a;
注意:
- 常量指针必须初始化,而且一旦初始化完成,将不可改变
- 常量指针存储的是指向对象的地址,常量const修饰的是这个变量,而这个变量存储的是地址,所以地址是不能改变的,不过也意味着,这个地址指向的对象的值是可以改变的。
3.3 指向常量的指针和指针常量的区别
1.指向常量的指针和指针常量都可以间接的改变所指向对象的值。
int b = 10;
const int *c = &b; // *c=10;
b = 20;
cout << *c<< endl; // *c=20;
int *const d = &b; //*d=20;
b = 30;
cout << *d << endl; // *d=30
2.指向常量的指针可以修改其所指向的对象,常量指针不能指向其所指向的对象。
int b = 10;
const int *c = &b;
int k = 20;
c = &k; // true, 修改c所指向的对象
int *const d = &b;
int h = 40;
d = &h; // err,不能修改d所指向的对象
3.指向常量的指针不能通过指针来修改所指向对象的值,常量指针可以通过指针来修改所指向对象的值。
int b = 10;
const int *c = &b;
*c = 20; // err,指向常量的指针不能通过指针来修改对象的值
cout << *c<< endl;
int *const d = &b;
*d = 20; //true,常量指针可以通过指针来修改对象的值
cout << *d << endl;
综上,指向常量的指针和指针常量之间的区别主要是看const修饰的内容,const修饰的什么内容,那么这个内容就不能修改。
四.顶层const和底层const
4.1 顶层const和底层const的介绍
顶层const:表示指针本身是个常量。
底层const:表示指针所指的对象是个常量。
区别:
- 对于顶层const而言,拷入和拷出并不会改变被拷贝对象的值,所以拷入和拷出的对象是否是常量没有什么关系,也就是说顶层const会在拷贝时被忽略。
- 对于底层const而言,拷入和拷出的对象都必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换,也就是说拷贝时,两边的类型必须相同,即都是底层const。
简而言之:两个变量之间的拷贝,必须要有相同的底层const,而不要求有相同的顶层const。
const int *const i=const int &j;
例如:
const int v2 = 0;
int v1 = v2;
const int *p2 = &v2;
int *p1 = &v1;
int i = 10;
const int *const p3 = &i;
. 判断如下赋值是否合法
r1=v2; // 合法
p1=p2; // 合法
p2= p1; // 合法
p1=p3; //不合法,因为指针在一般情况下也要进行类型匹配,当然对常量的指针和对基类的指针除外。
p2=p3; // 合法
注意:对常量对象取地址是底层const。
const int i = 12;
const int *k = &i; // true,因为i是底层const
int * const k = &i; // err,i是底层const,两边类型不一致
4.2 顶层const在以下情况会被忽略
1.当只有顶层const时,auto一般会忽略掉顶层const,保留底层const,既有顶层又有底层const时则不会进行忽略。
const int i = 12;
auto k = &i; // k为const int *k=&i;
int a=10;
const int *const i = &a;
auto k = &i; // 这里的k就是一个指向常量的常量指针,即const int *const k =&i;
2.只有当变量是顶层const时,它被拷贝时,顶层const才会被忽略。如果是底层const则不会被忽略,导致两边的类型不一致,也就不能进行初始化。
const int ci=42; // 顶层const
int i=ci; // 顶层const直接被忽略
const int *i = &a; //底层const
int *p = i; // err,因为底层const不能被忽略
五.constexpr和常量表达式
5.1 概念
常量表达式:不会改变并且在编译过程就能得到计算结果的表达式。
constexpr关键字:申明为constexpr的变量,编译器会验证变量的值是否是一个常量表达式。
const int max_files=20; // 是常量表达式
const int limit=max_files+1; // 是常量表达式
const int sz=get_size(); // 不是常量表达式,因为get_size()函数要到运行时才会知道结果。
其同样遵从常量定义后不能改变的特性,并且所得到的值通过标识符在代码里进行替换。
那为什么用constexpr关键字不用const关键字呢?
因为:在一个复杂的系统中,很难分辨一个初始值到底是不是常量表达式,所以我们就用constexpr来告诉编译器,我这个是常量表达式,如果是函数也能在大量的代码编写中,很快发现不是常量表达式的错误,因为如果在定义constexpr变量时,使用的是函数,那么这个函数也必须是constexpr函数。
5.2 constexpr变量
申明为constexpr的变量一定是一个常量,并且必须用常量表达式初始化。
constexpr int mf=20;
constexpr int limit=mf+1;
constexpr int sz= size(); //只有当siez是一个constexpr函数时才是正确的声明
5.2.1 字面值类型
常量表达式的值需要在编译时就得到计算,因此必须对声明constexpr时用到的类型有所限制,这些类型比较简单,被称为字面值类型,是指编译时就能得到结果的类型。
常见的字面值类型为:算术类型、引用、指针。
注意:尽管引用和指针可以被定义成constexpr类型,但是,他们的初始值是受到限制的,指针的初始值都必须为nullptr或者0,或者是存储于某个固定地址中的对象,引用也只能绑定在这样的二对象上。因为某些变量在不同的情况下它的地址是会改变的,例如在不同的作用域内,全局变量的地址就是固定的,而局部变量的地址就有可能发生改变,这种局部变量就不能被定义为constexpr类型。
5.3 constexpr函数
constexpr函数:是指能用于常量表达式的函数,被隐式的指定为内联函数。
其定义需遵循如下规则:
- 函数的返回类型及所有形参类型都得是字面值类型
- 函数体中必须有且仅有一条return语句,也可以包含有其他语句,但是这个其他语句不能进行任何操作,你可以进行一些简单的操作,比如空语句,类型别名,using命名空间等。
constexpr int new_sz() {return 42;}
constexpr int sz=new_sz();
constexpr int new_sz()
{
return 10;
}
constexpr int sz=new_sz();
constexpr函数一般和内联函数相同,放在头文件中。
5.4 指针和constexpr
和const不同的是,constexpr修饰的指针,是常量指针,就是它把所定义的对象置为了顶层const。
const int *p=0; //底层const
constexpr int *p=0; //作用于指针本身,相当于顶层const