auto decltype

有时候程序中会出现难以拼写的复杂类型,或者是有时候程序不知道所需要的变量的类型是什么,C++提供了类型别名、auto类型说明符和decltype类型指示符以处理相应的情况。

类型别名

类型别名(type alias)是一个名字,是某种类型的同义词,它让复杂的类型名字变得简单易用。
有两种方法可以用于定义类型别名

  • 传统方法,使用关键字typedef
  • 新标准,使用别名声明,using
    类型别名和类型的名字等价。但是如果某个类型别名指代的是复合类型或常量,可能会产生意想不到的结果。
    1
    2
    3
    4
    5
    6
    7
    typedef char *pstring;  
    char ps[] = "hello";
    const pstring cstr = ps; // cstr是指向char的常量指针
    const char *p = ps; // p是指向常量字符的指针
    cstr[0] = 'J';
    cout << ps << endl; // output Jello
    p[0] = 'K'; // error! cannot change its content

auto类型说明符

有时候需要把一个较为复杂的表达式或者类型难以拼写的变量赋值给一个变量,C++11引入了auto类型说明符,可以让编译器分析表达式的值所属的类型,来推算变量的类型,显然,auto定义的变量必须有初始值。

1
auto item = val1 + val2; // item初始化为val1和val2相加的结果,根据结果可以推断出item的类型

使用auto也能在一条语句中声明多个变量,但语句中所有变量的初始基本数据类型必须一样,即使是可以相互转换的类型也不行

1
2
3
4
5
6
7
8
auto i = 0, *p = &i;     // 正确,i是int类型,p是指向int的指针
auto sz = 0, pi = 3.14; // 错误,sz和pi的类型不一致
float x = 2.1;
auto a = x, b = 3.14; // 错误,x是float类型,而b是double类型
Base1 base1; // Base1是TopLevel的父类
TopLevel t;
Base1 * pb =&t; // 正确,基类类型的指针可以指向子类类型对象
auto u = base1, v = t; // 错误,u和v类型不一致

复合类型、常量和auto

编译器推断出来的auto类型有时和初始值的类型并不完全一样,编译器会适当改变结果类型使其更加符合初始化规则。
使用引用其实是使用引用的对象,因此引用作为初始值时,参与初始化的是引用对象的值,编译器以此为auto的类型。

1
2
int i = 0, &r = i;
auto a = r; // a是int类型

其次,auto一般会忽略掉顶层const,底层const则会保留。

1
2
3
4
5
const int ci = i, &cr = ci; // ci是const int类型,cr是const int&类型
auto b = ci; // b为int,忽略了ci的顶层const
auto c = cr; // c为int,cr是ci的别名,忽略了ci的顶层const
auto d = &i; // d为int*
auto e = &ci; // e为const int*,对常量对象取地址是一种底层const

如果希望推断出的auto类型是一个顶层const,需要明确指出(如上面例子中的cr):

1
const auto f = ci;  // ci的推演类型是int,f是const int

可以将引用的类型设为auto,初始值中的顶层const仍然保留。

1
2
3
auto & g = ci; // g是一个整型常量的引用
auto &h = 42; // 错误,非常量引用无法绑定到字面值
const auto &j = 42; // 常量引用可以绑定到字面值

一条语句中定义多个变量时,符合&和*只从属于某个声明符,而非数据类型的一部分。

1
2
3
auto k = ci, &l = i;        // k是int,l是int &
auto &m = ci, *p = &ci; // m是const int &,p是const int *
auto &n = i, *p2 = &cr; // 错误,n是int&,p2是const int *

多维数组循环遍历中的auto

c++11新增了范围for语句,因此可以使用其遍历二维数组:

1
2
3
4
5
6
7
int ia[][4] = {{1,2,3,4}, {5,6,7,8}};
for (const auto& row : ia)
{
for (auto c : row)
cout << c << ' ';
cout << endl;
}

除了最内层的循环外,外层循环的控制变量必须是引用类型,否则auto推断类型会把数组转为指针。

decltype类型指示符

有时候希望从一个表达式的类型推断出所需要定义的变量的类型,但又不想用该表达式的值初始化变量。c++11引入了类型说明符decltype
它的作用是选择并返回操作数的数据类型。编译器并不计算表达式的值。

1
2
3
4
5
6
7
8

int f() { return 1;}
void g() {}
// ……
decltype(f()) i;
decltype(f())* pi = &i;
*pi = 3;
cout << i << endl; // 3

decltype处理顶层const和引用的方式与auto不同,如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用)。

1
2
3
4
const int ci = 0, &cj = ci;
decltype(ci) x=0; // const int
decltype(cj) y = x; // const int &
decltype(cj) z; // const int &,必须初始化

其次,如果decltype使用的表达式不是一个变量,则返回该表达式结果对应的类型,如果表达式结果可赋值,则得到引用类型。

1
2
3
4
5
6
7
8
9
10
int i = 42, *p = &i, &r=i;
decltype(r+0)b; // int
decltype(*p) c; // int &,必须初始化
decltype(*p+0) d; // int
int i = 9;
const int * p = &i;
decltype(*p) u= 9; // const int &,必须初始化
decltype(p) v; // const int *
const int * const q = &i;
decltype(q) w= NULL; // const int * const

如果表达式的结果是左值类型,则返回一个引用,注意还有不可修改的左值——数组:

1
2
3
4
5
6
7
8
9
10
int ia[][4] = {{1,2,3,4}, {5,6,7,8}};
int ib[] = {1,2,3,4};
decltype(ia[1]) x= ib; // x是int(&)[4] ia[1]是一个不可修改的左值
decltype(ib) y; // int[4]
for (decltype(ia[0]) row : ia) // int (&)[4]
{
for (auto c : row)
cout << c << ' ';
cout << endl;
}

对于变量,如果加上了括号,则被当作表达式,而变量是可赋值的特殊表达式,因此会返回引用类型。

1
2
decltype(((i))) d; // int &
decltype((*p + 0)) e; // int,*p+0是一个临时量无法赋值

另外与auto不同的地方是,当数组作为一个auto变量的初始值时,推断得到的类型是指针,而decltype关键字不会发生上述转换:

1
2
3
4
5
int ia[] = {1,2,3,4};
auto ia2(ia); // ia2 是int*,等同于auto ia2(&ia[0])
decltype(ia) ia3 = {0,1,2,3}; // ia3是int[4]
ia3[1] = 9;
return 0;