C++ decltype 与 auto关键字
顶层const 与 底层const
指针本身是不是常量 和 指针所指的是不是一个常量 是2个问题。
顶层const 表示指针本身是常量。
底层const 表示指针所指的对象是个常量。
更一般的是顶层const 表示任意对象是常量,底层const则是与指针和引用有关(使得被修饰的变量本身无法改变的const是顶层const,其他的通过指针或引用等间接途径来限制目标内容不可变的const是底层const)
int i = 0;
int *const p1 = &i; // 不能改变p1的值(即p1只能指向i),这是一个顶层const
const int ci = 42; // 不能改变ci的值,这是一个顶层const
const int *p2 = &ci; // 不能改变ci的值,这是一个底层const
const int* const p3 = p2; // 前一个是底层const,后一个是顶层const
const int &r = ci; // 这是一个底层const
当执行对象拷贝的操作时,底层const 和 顶层const区别明显。顶层const无所谓,但当对象是底层const时,拷入可拷出的对象必须具有相同的底层const资格。
auto 关键字
编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚地知道表达式的类型,为了解决这个问题,C++11 引入auto类型说明符,用它能让编译器替我们去分析表达式所属的类型。auto让编译器通过初始值来推算变量的类型。显然auto定义的变量必须有初始值。
编译器推断出来的auto类型有时和初始值的类型并不完全一样,编译器会适当的改变结果类型使其更符合初始化规则。
-
数组
int arr[3] = {1, 2, 3}; auto i = arr // auto推断出的i是整型指针 decltype(arr) arr2 // decltype 推断出的是和 arr 一样的数组类型
-
引用类型
使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值,此时编译器以引用对象的类型作为auto的类型:int i = 0 int &r = i; auto a = r; // a是一个整数
-
auto 一般会忽略掉顶层const,同时底层const则会保留下来。
const int ci = i; // 顶层const const int &cr = ci; // 底层const auto b = ci; // b 是一个整数(ci 的顶层const特性被忽略掉了) auto c = cr; // c 是一个整数 (cr 是引用) auto d = &i; // d是int* auto e = &ci; // e是const int* auto &g = ci; // const int&
decltype 关键字
有时会遇到这种情况:希望从表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量。为了满足这一要求,C++11标准引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
decltype(f()) sum = x; // sum的类型就是函数f的返回类型。
decltype处理顶层const 和 引用的方式 与 auto 有些许不同,如果decltype使用表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)。
const int ci = 0;
const int &cj = ci;
decltype(ci) x = 0; // x 的类型是 const int
decltype(cj) y = x; // y 的类型是const int&,y 绑定到变量x
decltype(cj) z; // 错误:z 是一个引用,必须初始化
如果decltype使用的是表达式,则decltype返回表达式结果对应的类型。
int i = 42;
int *p = &i;
int &r = i;
decltype(r) j; // j 是引用
decltype(r+0) b; // b是int
decltype(*p) c; // c是int&,这里必须要初始化
r 是一个引用,则j也必须是引用。
r+0 是一个算术表达式,返回的是一个右值,所以b是int。
*p,解引用表达式返回的是左值,所以c是引用,而不是int。
另外,变量名加上一对括号,则得到的类型也会与不加括号不同,如果 decltype 使用的是一个不加括号的变 量,则得到的结果就是该变量的类型。如果给变量加上了一层或多层括号,编译器就会把它当成一个表达式,变量是一种可以赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型。
int i = 10;
decltype(i) e; // e是一个未初始化的int
decltype((i)) d; // d是一个int&,这里必须要初始化
常量表达式 与 constexpr 关键字
常量表达式是指在编译期间就能计算得到值得表达式。
字面值是常量表达式。
用常量表达式初始化的const的对象也是常量表达式。
一个对象是不是常量表达式,是由它的数据类型和初始值共同决定的,例如:
const int a = 5; // 常量表达式
const int b = a+3; // 常量表达式
int c = 30; // 非常量表达式,虽然初始值30是字面值,但是赋值操作需要在运行时才能进行
const int sz = get_size(); // 非常量表达式,初始值不是常量,需要在运行时确定
为了让编译器帮助验证常量表达式,C++11中引入了 constexpr关键字。如果想让一个表达式成为常量表达式,就使用 constexpr进行声明,当编译器发现声明的表达式并不是常量表达式时,就会报错,这就减少了出错的概率。
#include <iostream>
int func() { return 1; }
int main()
{
constexpr int i = func(); // error,编译器不能计算得到Func的返回值。
}
constexpr 变量可以由特殊的constexpr函数初始化,C++11中对constexpr函数的形式有严格的要求:其函数体只能含有一条return 语句,并且return 语句只能含有字面值类型或其他的constexpr函数。
#include <iostream>
#include <string>
int F1() { return 1; }
constexpr int F2(int n) { return 5; } // OK
constexpr int F3(int n) { const expr int i = 5; return 5; } //
constexpr int F4(int n) { return F1(); } // 错误,返回非字面值类型
参考资料
1. Stanley B. Lippman, Barbara E. Moo. C++ primer[M]. 电子工业出版社,2013
2. https://zhuanlan.zhihu.com/p/39307289