const限定符
- const对象一旦创建后其数值就不会被再次改变,因此const对象必须初始化。
- const对象只在文件中有效
- 在不同的文件中使用不同的const来定义不同的常量,那么每个文件定义的变量只会在自己所属的文件中有效。如果想让多个文件共享同一个const变量,那么使用关键字extern即可
const的引用
把引用绑定到const对象上,就像绑定到其他对象上一样,称之为对于常量的引用。和普通信用不同,对于常量的引用不能被用于修改它所绑定的对象。
const int ci = 1024;
const int &r1 = ci;//正确,引用及其对应的对象都是常量
r1 = 42; //错误,r1是对于常量的引用
int &r2 = ci; //错误,试图让一个非常量去引用一个常量对象
- 因为不允许直接为ci赋值,当然也不可以通过引用去改变ci,因此,对于r2的初始化是错误的,假设初始化合法,就可以通过r2来改变他引用的对象的数值,这显然是不正确的。
初始化和对const的引用
-
引用的类型必须和其所引用对象的类型是一致的,但是有两个例外。1,初始化常量引用时候允许用任意表达式来作为初始化的数值,只要该表达式结果可以转化为引用的类型即可。尤其,允许一个常量引用绑定非常量的对象、字面值甚至是一个一般表达式。
int i = 42;const int &r1 = i; //允许将const int& 绑定到一个普通int对象上const int &r2 = 42;//r2是一个常量的引用const int &r3 = r1 * 2;//r3是一个常量的引用int &r4 = r1 * 2; //错误,r4是一个普通的非常量的引用
对const引用可能引用一个并非const的对象
-
常量的引用仅仅对于可以引用可以参与的操作进行了限定,对于引用的对象的本身是不是一个常量未做限定。因为对象也可能是一个非常量,所以可以通过其他途径来改变它的值。
int i = 42;int &r1 = i; //引用r1绑定对象iconst int &r2 = i;//r2也绑定对象i,但是不允许通过r2来修改i的数值r1 = 0; //r1并非常量,i的数值修改为0r2 = 0; //错误,r2是一个常量的引用,因此不可以修改引用的元素的数值
指针和const
- 与引用一样,可以另指针指向常量或者非常量。类似于常量的引用,指向常量的指针不能用于改变其所指对象的数值
- 要想存放常量对象的地址,只能指向常量的指针。
const double pi = 3.141592653; //pi是个常量,它的值不能改变double *ptr = π //错误,ptr是一个普通的指针const double *cptr = π //正确,cptr可以指向一个双精度的常量*cptr = 43;//错误,不能给*cptr赋值
- 指针的类型必须和所指对象类型一致,但是有两个例外:1,允许令一个指向常量的指针指向一个非常量的对象。
double dval = 3.14;//dval是一个双精度的浮点数,它的数值可以改变
cptr = &dval; //正确,但是不能通过cptr来改变dval的数值
const指针
-
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定位常量。常量指针必须初始化,而且一旦初始化,它的值(存放在指针中的那个地址)就不可以再改变了。
-
把*放在const关键字之前用来说明指针是一个常量,即不变的是指指针本身的数值而不是指向的那个值
int errNum = 0;int *const curErr = &errNum; //curErr一直指向errNumconst double pi = 3.14158;const double *const pip = π //pip是一个指向常量对象的常量指针
- 遵循从右往左读的思想
- 离curErr最近的符号是const,意味着curErr本身是一个常量对象,对象的类型由声明符的其余部分决定。声明符的下一个符号是*,意思是curErr是一个常量指针。同理,pip是一个常量指针,指向的对象是一个双精度浮点类型的常量。
- 指针本身是一个常量并不意味着不能通过指针修改其所指向的数值,能否这样做完全依赖于所指向的对象的类型。例如,如果pip是一个指向常量的常量指针,不论是pip所指的对象值还是pip自己存储的那个地址都不能改变。如果,curErr指向的是一个一般的非常量整数,那么完全可以用curErr来修改errNum的数值。
顶层const
- 指针本身是一个对象,它又可以指向另外一个对象,因此,指针本身是不是常量以及指针所指的是不是一个常量,是两个相互独立的问题。用名词顶层const表示指针本身是一个常量;而使用名词底层const来表示指针所指的对象是一个常量。
- 顶层的const可以表示任意的对象是常量,这一点适用于任何数据类型,如算数类型、类、指针等。底层const则与指针和引用等符合类型的基本类型部分相关。
- 比较特殊的是,指针类型既可以是顶层const也可以是底层const,这一点和其他类型相比区别比较明显。
int i = 0;int *const p1 = &i; //不可以改变p1的数值,这是一个顶层的constconst int ci = 42; //不可以改变ci的数值,这是一个顶层的constconst int *p2 = &ci; //可以改变p2的数值,这是一个底层的constconst int *const p3 = p2; //靠右边的是顶层const,靠左边的是底层的constconst int &r = ci; //用于声明的const都是底层const
- 底层const限制不可以忽视。执行对象的拷贝操作的时候,拷入和烤出的对象具有相同的底层const资格,或者两个对象的数据类型之间能够相互转化,一般来说非常量可以转化为常量,反之不可以。
constexper和常量表达式
- 常量表达式是指不会改变并且在编译的过程中能得到编译结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
- 一个对象或者表达式是不是常量表达式是由它的数据类型和初始值共同决定的。
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不是常量表达式
- staff_size的初始值是一个字面值常量,但是由于他的数据类型只是一个普通的int而不是const int,所以他不属于常量表达式。
- sz本身是一个常量,但是他的具体值直到运行的时候才可以获取到,因此也不是常量表达式。
constexpr变量
- C++11允许将变量声明为constexpr类型,从而使得编译器来验证变量的数值是否是一个常量的表达式。
- 声明为constexpr的变量一定是一个常量,而且需要用常量表达式来初始化。
constexpr int mf = 20; //20是常量表达式constexpr int limit = mf + 1 ;// limit是常量表达式constexpr int sz = size();// 只有当size是一个constexpr函数的时候,才是一条正确的声明语句
字面值类型
- 常量表达式的值需要在编译的时候就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也比较明显,将其称之为字面值类型。
- 算数类型、引用和指针都是属于字面值类型。自定义的类型、IO库、string则不属于字面值类型,即不可以定义为constexpr。
- 指针和引用可以被定义为constexpr类型,但是他们的初始值会受到严格的限制,constexpr指针的初始值必须是nullptr或者是0,或者是存储于某个固定地址中的对象。
- 先前指出函数体内部定义的变量一般来说不会存在到固定的地址中,因此constexpr指针不可以指向这种变量。相反,因为存储于函数体外的对象其固定的地址不变,因此可以用于初始化constexpr指针。
- 其中,允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定的地址,因此也可以用于对于constexpr指针的初始化。
指针和constexpr
- 在constexpr声明中定义了一个指针,限定符号constexpr仅仅对于指针有效,而对于指针所指向的对象本身无效。
const int *p = nullptr;//p是一个指向整型常量的指针constexpr int *q = nullptr;//q是一个指向整数的常量指针
- 与其他常量指针相类似,constexpr指针既可以指向一个常量指针,也可以指向一个非常量
const int *np = nullptr;//np是一个指向整数的常量指针,其值为空
int j = 0;
constexpr int i = 42;//i的类型是整型常量
//i和j必须定义在函数体之外constexpr const int *p = &i;//p是常量指针,指向整型常量iconstexpr int *p1 = &j;