15.5 再次泛化vector
显然,通过15.3~15.4节的例子我们发现,标准库vector包含一个iterator成员类型,以及begin()和end()成员函数(与std::list类似)。然而,我们并没有在第14章中为vector类提供这些成员。那么,对于不同类型的容器而言,它们究竟采用了什么方法,以使它们或多或少地能够在15.3节所介绍的STL泛型编程风格中相互替换使用?首先,我们将简要介绍一种解决方案(简单起见,我们忽略了分配器),然后再对解决方案进行解释:
using声明为一个类型创建别名,即对于我们的vector,iterator是我们用作迭代器类型T*的一个同义词,它的另一个名字。现在,对于vector对象v,我们可以编写如下代码:
以及
通过别名的方式,我们事实上不需要知道iterator和size_type的实际类型。特别地,由于使用了iteartor和size_type,上述代码也可以用于那些size_type不是unsigned long类型(在很多嵌入式系统中,size_type为其他类型)并且iterator为类而不是简单指针(这种情况在C++实现中很普遍)的vector。
标准库以相似的方式定义了list和其他标准容器。例如:
这样,我们编写代码时就不必操心使用的是list还是vector。所有标准库算法中都使用了上述这些容器的成员类型名,例如iterator和size_type,所以,算法的实现不依赖于容器的实现或者具体操作的是什么容器(参见第16章)。
还有一种方法可以替代对容器C使用C::iterator——Iterator<C>,这也是我们通常更倾向使用的方法。通过一个简单的模板别名即可实现这种用法:
出于语言技术层面的原因,我们需要为C::iterator加上typename前缀,以表明iterator是一个类型,这也是我们更倾向于使用Iterator<C>的原因之一。类似地,我们可以定义
这样,我们就可以在代码中使用Value_type<C>了。这些类型别名不包含在标准库中,但你可以在std_lib_facilities.h中找到它们。
using声明是C++11的新特性,与C和C++中为人熟知的typedef(见附录A.16)相似,可以看作后者的泛化。
15.5.1 遍历容器
使用size(),我们可以从头到尾遍历一个vector。例如:
这段代码不适用于链表,因为list不提供下标操作。但是,我们可以用一个简单的范围for循环(见4.6.1节)来遍历标准库vector和list。例如:
这段代码既适用于标准库容器,也适用于“我们的”vector和list。它是如何办到的?“窍门”在于范围for循环是基于begin()和end()函数的,前者返回指向我们的vector的首元素的迭代器,后者返回指向尾元素之后位置的迭代器。范围for循环其实不过是使用迭代器遍历序列的循环的一种“语法糖衣”而已。如果我们为自己的vector和list定义了begin()和end(),就“偶然地”提供了范围-for所需要的东西。
15.5.2 auto
当我们不得不编写循环遍历一个通用结构时,命名迭代器是很令人厌烦的事情。考虑下面代码:
这里最恼人的地方是编译器显然已经知道了list的iterator类型和vector的size_type。我们为什么还必须告诉编译器它已经知道的事情呢?这样做徒增我们当中不擅打字的人的烦恼,并增加出错的机会。幸运的是,我们无须这样做:可以将变量声明为auto的,表示使用iterator类型作为变量的类型:
这里,p是一个vector<T>::iterator,q是一个list<T>::iterator。我们几乎可以在任何包含初始化器的定义中使用auto。例如:
注意,字符串字面值常量的类型为const char*,因此对字符串字面值常量使用auto可能导致令人不快的意外:
当我们确切知道想要的类型时,通常指明类型和使用auto一样容易。
auto的常见用途是在范围for循环中指明循环变量。考虑下面代码:
在这段代码中,我们使用auto的原因是给出容器cont的元素类型不是那么容易。我们使用const的原因是并不写入容器元素,而我们使用&(引用)的原因是以免元素过大拷贝代价太高。