读书频道 > 网站 > 网页设计 > 深入应用C++11:代码优化与工程级应用
1.1.1 auto类型推导
15-07-07    下载编辑
收藏    我要投稿   
在StackOverflow的最近一次世界性调查中,C++11在所有的编程语言中排名第二, C++11受到程序员的追捧是毫不意外的,因为它就像C++之父Bjarne Stroustrup说的:它看起来就像一门新的语言。C++11新增加了相当多的立即去当当网订购

1.1 类型推导

C++11引入了auto和decltype关键字实现类型推导,通过这两个关键字不仅能方便地获取复杂的类型,而且还能简化书写,提高编码效率。

1.1.1 auto类型推导

1.auto关键字的新意义

用过C#的读者可能知道,从Visual C# 3.0开始,在方法范围中声明的变量可以具有隐式类型var。例如,下面这样的写法(C#代码):

var i = 10;   // 隐式(implicitly)类型定义
int i = 10;   // 显式(explicitly)类型定义

其中,隐式的类型定义也是强类型定义,前一行的隐式类型定义写法和后一行的显式写法是等价的。

不同于Python等动态类型语言的运行时变量类型推导,隐式类型定义的类型推导发生在编译期。它的作用是让编译器自动推断出这个变量的类型,而不需要显式指定类型。

现在,C++11中也拥有了类似的功能:auto类型推导。其写法与上述C#代码等价:

auto i = 10;

是不是和C#的隐式类型定义很像呢?

下面看下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是const 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”,编译器将会报错:

const auto *v = &x, u = 6.0;

error: inconsistent deduction for 'const auto': 'int' and then

'double'

最后y、r、s的推导过程比较简单,就不展开讲解了。读者可自行在支持C++11的编译器上实验。

由上面的例子可以看出来,auto并不能代表一个实际的类型声明(如s的编译错误),只是一个类型声明的“占位符”。

使用auto声明的变量必须马上初始化,以让编译器推断出它的实际类型,并在编译时将auto占位符替换为真正的类型。

细心的读者可能会发现,auto关键字其实并不是一个全新的关键字。在旧标准中,它代表“具有自动存储期的局部变量”,不过其实它在这方面的作用不大,比如:

auto int i = 0;  // C++98/03,可以默认写成int i = 0;
static int j = 0;

上述代码中的auto int是旧标准中auto的使用方法。与之相对的是下面的static int,它代表了静态类型的定义方法。

实际上,我们很少有机会这样直接使用auto,因为非static的局部变量默认就是“具有自动存储期的”。

考虑到auto在C++中使用的较少,在C++11标准中,auto关键字不再表示存储类型指示符(storage-class-specif?iers,如上文提到的static,以及register、mutable等),而是改成了一个类型指示符(type-specif?ier),用来提示编译器对此类型的变量做类型的自动推导。

2.?auto的推导规则

从上一节的示例中可以看到auto的一些使用方法。它可以同指针、引用结合起来使用,还可以带上cv限定符(cv-qualif?ier,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; // e -> const int&
auto& h = g;  // f -> 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,和下面的模板参数自动推导出来的类型是一致的:

template <typename T> void func(T   x) {} // T   -> auto
template <typename T> void func(T * x) {} // T * -> auto *
template <typename T> void func(T & x) {} // T & -> auto &

template <typename T> void func(const T   x) {} // const T   ->
const auto
template <typename T> void func(const T * x) {} // const T * ->
const auto *
template <typename T> void func(const T & x) {} // const T & ->
const auto &

注意:auto是不能用于函数参数的。上面的示例代码只是单纯比较函数模板参数推导和auto推导规则的相似处。

因此,在熟悉auto推导规则时,可以借助函数模板的参数自动推导规则来帮助和加强理解。

3.auto的限制

上一节提到了auto是不能用于函数参数的。那么除了这个之外,还有哪些限制呢?

请看下面的示例,如代码清单1-1所示。

代码清单1-1 auto使用受限的示例

void func(auto a = 1) {}    // error: auto不能用于函数参数

struct Foo
{
   auto var1_ = 0;    // error: auto不能用于非静态成员变量
   static const auto var2_ = 0;   // OK: var2_ -> static const int
};

template <typename T>
struct Bar {};

int main(void)
{
   int arr[10] = {0};
   auto aa     = arr;    // OK: aa -> int *
   auto rr[10] = arr;    // error: auto无法定义数组
   Bar<int> bar;
   Bar<auto> bb = bar;    // error: auto无法推导出模板参数

   return 0;
}

在Foo中,auto仅能用于推导static const的整型或者枚举成员(因为其他静态类型在C++标准中无法就地初始化),虽然C++11中可以接受非静态成员变量的就地初始化,但却不支持auto类型非静态成员变量的初始化。

在main函数中,auto定义的数组rr和Bar<auto>bb都是无法通过编译的。

main函数中的aa不会被推导为int[10],而是被推导为int*。这个结果可以通过上一节中auto与函数模板参数自动推导的对比来理解。

4.?什么时候用auto

前面说了这么多,最重要的是,应该在什么时候使用auto呢?

在C++11之前,定义了一个stl容器以后,在遍历的时候常常会写这样的代码:

#include <map>

int main(void)
{
   std::map<double, double> resultMap;

   // ...

   std::map<double,double>::iterator it = resultMap.begin();
   for(; it != resultMap.end(); ++it)
   {
   // do something
   }

   return 0;
}

观察上面的迭代器(iterator)变量it的定义过程,总感觉有点憋屈。其实通过resultMap.begin(),已经能够知道it的具体类型了,却非要书写上长长的类型定义才能通过编译。

来看看使用了auto以后的写法:

#include <map>

int main(void)
{
   std::map<double, double> resultMap;

   // ...

   for(auto it = resultMap.begin(); it != resultMap.end(); ++it)
   {
   // do something
   }

   return 0;
}

再次观察it的定义过程,是不是感到清爽了很多?

再看一个例子,在一个unordered_multimap中查找一个范围,代码如下:

#include <map>

int main(void)
{
   std::unordered_multimap<int, int>resultMap;

   // ...

   std::pair<std::unordered_multimap<int,int>::iterator,
std::unordered_multimap<int, int>::iterator>
   range = resultMap.equal_range(key);

   return 0;
}

这个equal_range返回的类型声明显得烦琐而冗长,而且实际上并不关心这里的具体类型(大概知道是一个std::pair就够了)。这时,通过auto就能极大的简化书写,省去推导具体类型的过程:

#include <map>

int main(void)
{
   std::unordered_multimap<int, int> map;

   // ...

   auto range = map.equal_range(key);

   return 0;
}

另外,在很多情况下我们是无法知道变量应该被定义成什么类型的,比如,如代码清单1-2所示的例子。

代码清单1-2 auto简化函数定义的示例

class Foo
{
public:
   static int get(void)
   {
      return 0;
   }
};

class Bar
{
public:
   static const char* get(void)
   {
      return "0";
   }
};

template <class A>
void func(void)
{
   auto val = A::get();

   // ...
}

int main(void)
{
   func<Foo>();
   func<Bar>();
   return 0;
}

在这个例子里,我们希望定义一个泛型函数func,对所有具有静态get方法的类型A,在得到get的结果后做统一的后续处理。若不使用auto,就不得不对func再增加一个模板参数,并在外部调用时手动指定get的返回值类型。

上面给出的各种示例仅仅只是实际应用中很少的一部分,但也足以说明auto关键字的各种常规使用方法。更多的适用场景,希望读者能够在实际的编程中亲身体验。

auto是一个很强大的工具,但任何工具都有它的两面性。不加选择地随意使用auto,会带来代码可读性和维护性的严重下降。因此,在使用auto的时候,一定要权衡好它带来的“价值”和相应的“损失”。

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.3 功能
下一篇:1.5 小结
相关文章
图文推荐
JavaScript网页动画设
1.9 响应式
1.8 登陆页式
1.7 主题式
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站