auto关键字的新意义
下面看下auto的一些基本用法:
auto x = 5; //OK,x是int类型
auto pi = new auto(1); //OK,pi被推导为int *
const auto* v = &x, u = 6; //OK,v是const int* 类型,u是cosnt int 类型
static auto y = 0.0; //OK,y是double类型
auto int r; //error,auto不在表示存储类型指示符
auto s; //error,auto无法推导出s的类型
在上面的代码实例中:字面量5是一个const int类型,变量x将被推导为int类型(const 被丢弃),并被初始化为5,pi的推导说明auto还可以用于new操作符。在上面例子中,new操作符后面的auto(1)被推导为int (1),因此pi的类型是int *;接着,由&x的类型是int *,推导出const auto* 中的auto应该是int,于是v被推导出为const int*,而u则被推导为const int。
v和u的推导需要注意两点:
虽然经过前面const auto* v = &x的推导,auto的类型可以确定为int了,但是u仍然必须要在后面写“=6”,否则编译器不予通过。
u的初始化 不能使编译器推导产生二义性。例如,把u初始化改成“u = 6.0”,编译器将会报错。
auto.cpp:20:8: error: inconsistent deduction for ‘auto’: ‘const int’ and then ‘const double’ const auto* v = &x, u = 6.0;
auto并不能代表一个实际的类型声明,只是一个类型声明的“占位符”,所以auto s会报错。
使用auto声明的变量必须马上初始化,以让编译器推断出它的实际类型,并在编译时将auto占位符替换为真正的类型。
auto的推导规则
aut可以同指针,引用结合起来使用,还可以带上cv限定符(const和volatile限定符的统称)。
int x = 0;
auto * a = &x; //a -> int*,auto被推导为int
auto b = &x; // b-> int*,auto被推导为int*
auto &c = x; //c -> int&,auto被推导为int
auto d = c; //d -> int,auto被推导为int
const auto e = x; // e -> const int
auto f =e; //f -> int
const auto& g = x; //g -> const int&
auto& h = g; //h -> const int&
从上面的例子可以看出:
a和c的推导结果是很显然的,auto在编译时被替换为int,因此a和c分别被推导为int* 和int &。
b的推导结果说明,其实auto不声明为指针,也可以推导出指针类型。
d的推导结果说明当表达式是一个引用类型时,auto会把引用类型抛弃,直接推导成原始类型int。
e的推导结果说明,const auto会在编译时被替换为const int。
f的推导结果说明,当表达式带有const(实际上volatile也会得到同样的结果)属性时,auto会把const属性抛弃掉,推导成non-const类型int。
g,h的推导说明,当auto和引用(换成指针在这里也将得到同样的结果)结合时,auto的推导将保留表达式的const属性。
通过上面的一系列实例,可以得到下面这两条规则:
1、当不声明为指针或引用时,auto的推导结果和初始化表达式抛弃引用和cv限定符后类型一致。
2、当声明为指针或引用时,auto的推导结果将保持初始化表达式的cv属性。
auto的限制
auto 不能用于函数参数
void func(auto a = 1){} //error,auto不能用于函数参数
auto不能用于非静态成员变量
struct Foo
{
auto var1 = 0; //error:auto不能用于非静态成员变量
static const auto var2 = 0; //OK:var2 -> static const int
};
auto 无法定义数组
auto rr[10] = {1, 2,3}; //error:auto无法定义数组
Bar<auto> bar; //error:auto无法推导出模板参数
什么时候使用auto
C++11之前,定义了一个stl容器后,在遍历的时候常常这样写代码:
map<int, int> m_map;
map<int, int>::iterator it = m_map.begin();
for(; it != m_map.end(); it++)
{
//do something
}
观察上面的迭代器变量it的定义过程,要写很大一串。
使用auto以后的语法:
map<int, int> m_map;
for(auto it = m_map.begin(); it != m_map.end(); it++)
{
// do something
}
再次观察it的定义过程,代码量少了
另外,在很多情况下我们是无法知道变量应该被定义成什么类型,请看下面的代码清单:
#include <iostream>
#include <map>
using namespace std;
class Foo
{
public:
static int get(void)
{
printf("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
};
class Bar
{
public:
static const char* get(void)
{
printf("%s %s %d\n", __FILE__, __func__, __LINE__);
return "0";
}
};
template<class A>
void func(void)
{
auto val = A::get();
}
int main()
{
func<Foo>();
func<Bar>();
return 0;
}
在这个例子里,我们希望定义一个泛型函数func,对所有具有静态get方法的类型A,在得到get的结果后能统一的后续处理。若不适用auto,就不得不对func再增加一个模板参数,并在外部调用时手动指定get的返回类型。