const 限定符的核心价值就是防止误操作改变了不须改变的变量的值
const 对象一旦创建后就不能发生任何改变,因此必须在定义的同时进行初始化(字面值、任意表达式均可)
const 对象的文件间共享:
【声明与定义的设置是为了在不同文件间使用同一个变量,当然只在该变量确实会改变的情况下才有意义
而如果要在不用文件间分享确定不会改变的const 对象,则无须在各文件间传递const 的值
因此语言默认不同文件内的const 对象即使名字相同且定义于函数体之外,它们也是各自文件内的局部变量(定义在函数体外的非常量变量是全局变量)
而如果const 对象的值为不确定的表达式时,在文件间分享const 对象就有了现实意义
因此语言规定,对const 对象的文件间共享,必须在声明和定义之前都必须加extern 关键字(必须在函数体之外,见2.2.2)】
{在本节中,有许多种匹配,在实际使用这些内容时,一定要注意类型与类型是否能够合法的匹配}
【可以把引用绑定在const 对象上,称为对常量的引用。
const int a = 0;
const int &s = a;//const 不能丢,丢的话非常量引用与常量对象的类型不能匹配
s++;//错误,常量引用对象不支持会改变对象的运算
但是,我们不能用该引用来修改它所绑定的对象。】
【2.3.1 提到大多数情况下引用必须与对象类型匹配,但有两种例外。
常量引用就是一种例外:在初始化时允许常量引用绑定非常量的同类型对象、不同基本类型的对象、字面值、甚至一般表达式,只要前述诸种能被转换成引用的类型。
当常量引用绑定到另外一种基本类型的对象时,实际上编辑器创建了一个与引用同类型的临时变量(常量),将该不同类型变量先赋值给该常量临时变量,再让常量引用绑定到这个临时量上。】
【上述情况的原因如下:
常量引用实际上是种自我限制,它仅仅限制了自己不能发生任何改变,对于对象本身进行的变化则不加限定,所以能绑定到非常量的对象上。
int i = 0;
const int &s = i;
i++;
int a = s;//可以把常量引用赋值给普通的整形变量,因为赋值过程不会导致常量s 发生改变
cout << a;//输出结果是1,可见i 自己进行的变化也传递到了常量引用s ,因此常量引用并不是严格意义上的不发生任何改变
非常量引用通常用来通过引用改变绑定的对象,无论是考虑到从不同类型对象可进行的操作不同,还是考虑到不同类型的绑定会使引用绑定到一个临时量,都不能实现通过引用改变对象的目的,因此规定将不同基本类型的对象赋值给非常量引用的行为非法。】
【指针和const 有交集时,有两个截然不同的概念:
指向常量对象的非常量指针、指向非常量对象的常量指针(当然也有两者都是常量或两者都是非常量)。
const 出现的不同位置决定了到底指向对象和指针变量自身谁是常量。
int a;//普通整形a
const int b;//常量对象b
int *const a1 = &a;//指向普通对象a 的常量指针a1
const int *b1 = &b;//指向常量对象b 的非常量指针b1
const int *const b2 = &b;//指向常量对象b 的常量指针b2
】
【指向常量的指针与常量引用相似,也是种自我限制,不能通过指向常量的指针改变其所指的对象,存放常量对象的地址时只能用指向常量对象的指针
常量指针,必须在定义的同时初始化,定义之后指针指向的位置就不能发生变化了,但是可以通过此指针改变其指向的对象。】
【顶层const 指对象本身是一个常量。
底层const 指对象所关联的对象是一个常量。】
【在执行拷贝时,作为赋值的右操作符,顶层const 不会对操作有任何影响,因为拷贝操作不改变被拷贝对象的值。
但是当底层const 对象作为赋值操作的右操作符,拷入和拷出的对象必须有相同的底层const 资格;或者能够相互转换,一般来说非常量可以转化成常量,反之则不行。
int i = 0;
const int *p = &i;//合法,因为指向常量的指针本质上是自我限制,只是限制了通过p 修改i 的行为,但i 还是可以不通过p 自行发生变化的
const int l = 0;
int *const q = &l;//不合法,因为这样意味着常量指针q 指向了一个正常int 类型,而正常int 能进行的操作,对于常量l 来说很有可能是不合法的
】
【常量引用和指向常量的指针可以一同理解,两者都可以实际上关联一个非常量,只是从这两者的角度看来,他们关联的是常量,这两者本身不能去改变他们关联的对象。】
【常量表达式指值不会发生改变且在编译过程就能得到计算结果的表达式,即编译期常量,如字面值、用常量表达式初始化的const 对象。
c++语言中有几种情况下是要用到常量表达式的?
一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr 类型。】
【一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定。
其数据类型必须是个常量。
初始值必须不依赖于程序运行时的输入,反例如下:
const int sz = get_size();
虽然sz 是个常量,但是其初始值必须到程序运行时才能获取到。】
【在一个复杂系统中,很难(几乎不能)分辨一个初始值到底是不是常量表达式。
c++ 新标准规定,允许将变量声明为constexpr 类型以便由编译器来验证变量的值是否是一个常量表达式(constexpr 的意义之一)。
声明为constexpr 的变量一定是一个常量,而且必须使用常量表达式初始化。
以下代码包含一些编译知识:
constexpr int mf = 20;//20是常量表达式
constexpr int limit = mf + 1;//mf + 1 是常量表达式,mf 作为常量分配空间的同时也要完成初始化,所以在编译时就能确定mf 的值并将之后的mf 都替换为该值
constexpr int sz = size();//仅当size 是一个constexpr 函数时,才是一条正确的声明语句
int i = 0;//变量i 在编译时分配存储空间,在程序运行至该定义语句时完成初始化
constexpr int a = i;//不合法,因为变量i 不像mf 一样也在编译时初始化,自然a 也不能在编译时就知道其值
新标准允许定义一种特殊的constexpr 函数(6.5.2),这种函数应该足够简单以便编译时就可以计算其结果,这样就能用constexpr 函数去初始化constexpr 变量了。】
【常量表达式要求在编译时就得到计算,因此声明成constexpr 的变量类型必须有所限制。
这些类型一般足够简单、值显而易见且容易得到(因为是作为表达式来用的,必须要有易得到的值),称为“字面值类型”。
算术类型、引用和指针都属于字面值类型,其它一些字面值类型将在7.5.6 和19.3 介绍。】
【指针和引用都能定义成constexpr 类型,但必须被严格的限制。
constexpr 指针的初始值必须是nullptr 或者0,或者是指向存储于某个固定地址中的对象(变量地址不能根据程序运行的状态而发生变化)。
6.1.1 节中将介绍变量地址的固定与否与定义该变量位置的关系以及其他规定。】
【与const 不同的是,使用constexpr 来声明指针类型的时候,constexpr 限定符仅对指针有效,与指针所指的对象无关。
const int *p = nullptr;//p 是一个指向整形常量的指针
constexpr int *q = nullptr;//q 是一个指向整形的常量指针
constexpr int i = 0;//i 是一个常量表达式,同时也是一个整形常量
constexpr const int *r = &i;//r 是一个指向整形常量的常量指针
】