typedef,auto以及decltype是C++处理类型的几个关键字
1. 类型别名
类型的名字可能非常复杂,所以C++提供了两种创建类型别名的方式,传统的typedef和C++11中引入的using方式,下面是例子:
typedef double my_double;
using mydouble = double;
这两种方式都可以创建别名,别名与类型的名字等价。
2. auto关键字
auto是C++11引入的类型说明符,它可以自动的获得变量的类型 - 显然此时我们必须给变量提供初始值。
auto num = 0; // num自动获取为int类型
auto num; // 错误,因为没有提供初始值
关于auto自动绑定,当涉及到引用的时候会变得有些复杂:
- auto会忽略顶层(top-level)const:
int i = 0;
const int ci = i, &cr = ci; // cr是ci的别名
auto b = ci; // 整数,顶层的const被忽略掉了
auto c = cr; // 整数,同上因为cr是ci别名,ci本身就是个顶层const
auto d = &i; // 整型指针
auto e = &ci; // 整数常量指针 - 对常量对象取地址是一种底层const
(顶层const:指针本身是个常量;底层const:指针指向的对象是一个常量)
- 希望推断出来的auto类型是个顶层const:
const auto f = ci;
- 将引用的类型设为auto:
auto &g = ci; // g是整型常量引用,绑定到ci (即const引用)
auto &h = 42; // 错误,不能将非常量引用绑定到字面值(即&h不是const引用)
const auto &j = 42; // 正确,可以将常量引用绑定到字面值
注意左值引用(left reference)必须被初始化,否则是错误的。引用就是已经存在的对象的别名。
- 放在同一行使用的时候需要保证这一行的类型是一样的:
auto k = ci, &l = i; // k是int(忽略了ci的顶层const),l是int引用所以也是int
auto &m = ci, *p = &ci; // m是int引用,p是int指针,&ci是取地址
auto &n = i, *p2 = &ci; // 不对了,i是int,n是int引用(等价于int),*p2却是个const int
看到这里可能观众对于引用和指针产生了一点疑惑,尤其是 为啥auto &m = ci, *p = &ci; 是合法的,这里说明一点:auto后面的m和*p是并列来看的,m作为int引用,与*p所表示的int是同种类型。这里千万不要认为m和p是同类型,不是的。这里m是int引用,而p是int指针;编译器比较的实际上是m和*p。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int i = 0, &r = i;
auto a = r;
const int ci = i, &cr = ci;
auto b = ci;
auto c = cr;
auto d = &i;
auto e = &ci;
const auto f = ci;
auto &g = ci;
const auto &h = 0;
const auto &j = 0;
auto k = ci, &l = i;
auto &m = ci, *p = &ci;
auto &n = i;
auto *p2 = &ci;
cout << a << b << c << *d << *e << f << g << h << j << k << l << m << n << *p << *p2 << endl;
}
看上面这段代码,m和*p是并列的。指针本身作为一种复合类型,比较复杂;引用也是一种类型,叫做引用类型(听君一席话如听一席话),这里m的类型是const int &, p的类型是const int *。做不到,没办法去定义引用的引用,因为引用本身不是一个对象 - 尝试对引用创建引用只能创建出引用的对象的引用,而不是引用本身的引用。(绕口令,好好寻思寻思)
下面这段小练习挺有意义的:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int i = 0, &r = i;
auto a = r;
const int ci = i, &cr = ci;
auto b = ci;
auto c = cr;
auto d = &i;
auto e = &ci;
const auto f = ci;
auto &g = ci;
const auto &h = 0;
const auto &j = 0;
auto k = ci, &l = i;
auto kk = ci, *ll = &i;
auto &m = ci, *p = &ci;
auto &n = i;
auto *p2 = &ci;
cout << a << b << c << *d << *e << f << g << h << j << k << l << m << n << *p << *p2 << endl;
a = 42; //y int类型可以修改
b = 42; //y 顶层const被忽略,auto判断出来的是int类型,可以修改
c = 42; //n 不能修改const int 引用 y 同上
//d = 42; //?? 可以改但是把值赋给指针类型?N, invalid conversion from ‘int’ to ‘int*’ [-fpermissive]
//e = 42; //同上 N invalid conversion from ‘int’ to ‘const int*’ [-fpermissive]
//g = 42; //n不能修改const int 引用
}
3. decltype类型指示符
C++11 引入的除了auto之外的类型说明符decltype的作用是从表达式的类型推断出变量的类型。它在后面与string相关的地方会有很大用处(避免了无法判断.size()函数返回的是int还是unsigned_int的尴尬 - 实际上是string::size_type类型)。
decltype(f()) sum = x;
编译器不会实际调用f,只把f返回值的类型作为sum的类型。
- 右值*在指针前面叫做解引用符号。
- decltype和auto的不同,decltype不会忽略顶层const
const int ci = 0; &cj = ci;
decltype(ci) x = 0; // x是个const int类型
decltype(cj) y = x; // y是个const int &类型,绑定到x上
decltype(cj) z; // 错误,没有初始化z,z是个引用
- 其实decltype还解答了关于指针解引用之后类型的疑惑,
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // 正确,r + 0表达式类型是int
decltype(*p) c; // 错误,c是int &类型,必须初始化
解引用指针可以得到指针所指的对象,还能给这个对象赋值。因此decltype(*p)的类型是int &而不是int。
- 变量如果加上括号,会被decltype判断成表达式,因为变量可以作为赋值表达式左值,赋值表达式本身是左值的引用,即如果i是int,那么i = x赋值表达式的类型就是int &,所以(i)也被当成了int &。
decltype((i)) d; // 错误,d是int &
decltype(i) e; // 正确,e是int (未初始化)
关于decltype的一个简单应用,书上85页的例子:
string s("some string");
for (decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index) {
s[index] = toupper(s[index]);
}
作者很喜欢这个把字母变成大写的例子,除了这里,迭代器部分也用的这个例子。还有作者很喜欢42这个数字。扯远了。
这里面index的类型就是s.size()的返回类型,因为写string::size_type或者记忆可能有难度,所以用decltype是个非常明智的做法。
类似的,可以用auto获取string::size_type类型:
string s("some string");
auto length = s.size();
今天到这里就可以了。