揭示STL重要更改
预备知识:
l 理解标准C++ 0x 的concepts,例如:auto 关键字,lambda 表达式、右值引用等。
l 熟练使用STL。熟悉2个及以上STL容器的使用。
l 你手上必须有有VC2010的编译器,或者其它支持最新的C++标准和更新Stl的编译器。
这篇文章介绍了新版STL修订内容。这些变化是TR1中最为关注的内容(译注1);以下是STL的新增特性:
l Constant迭代器
l array类
l tuple类
l <algorithm>中新增函数
l 随机生成器类(<random>)
l 对sets及无序sets容器的改进
l 对maps及无序maps的改进
l 正则表达式
l 功能的改进及实用的头文件
l 加强指针管理类
Constant 迭代器
首先说明,constant迭代器并不等于const迭代器。constant迭代器是const_iterator。 将const关键字加在 iterator前时,其所指的变量是无法修改的。而const_iterator则在首次赋值后不再可指向其它变量(类似:常量指针和指向常量的指针)看了下面代码后,你应该会有一个清晰的认识:
using namespace std; vector<int> IntVector; ... // Normal iterator vector<int>::iterator iter_nc = IntVector.begin(); *iter_nc = 100; // Valid iter_nc = IntVector.end(); // Valid // Constant Iterator vector<int>::const_iterator iter_c = IntVector.begin(); *iter_c = 100; // INVALID! iter_c = IntVector.end(); // Valid // The 'const' iterator const vector<int>::iterator iter_const = IntVector.begin(); *iter_const = 100; // Valid iter_const = IntVector.end(); // Invalid (Why? Learn C++!) // The 'const' Constant Iterator const vector<int>::const_iterator iter_const_c = IntVector.begin(); *iter_const_c = 100; // Invalid! iter_const_c = IntVector.end(); // Invalid
新特性
容器中新增函数可以明确返回Constant迭代器。在此之前,我们先了解一下迭代器返回规则:
1. 如果容器是constant,或者类方法前申明const,则将返回const_iterator。
2. 如果左值是const_iterator,普通的iterator将被返回,但会向下转型为const_iterator;
3. 其它情况,将返回普通iterator;
注:大多数迭代器访问方法(eg:begin,end)有相应的重载版本以返回constant 或non-const迭代器 ;
因此,你可明确指明返回的是constant还是普通迭代器;
以下为新增方法:
访问方法 |
含义 |
cbegin |
返回容器第一个元素的const_iterator . |
cend |
返回容器最后一个元素后一位的 const_iterator . |
crbegin |
返回rbegin 的 const_iterator 。即最后一个元素的constant迭代器. |
crend |
返回rend的 const_iterator 。即第一个元素前一位的constant迭代器. |
以上所有方法都在相应的容器类中申明为const。
为什么要引入这些方法?
你当然可以指定你需要那种迭代器,可能你根本就不需要这些新的方法;
加入这些方法的主要原因是因为新修订的auto关键词的引入;如果你已经了解auto关键词的含义,你应该知道我们可以在变量申明时不用明确指定类型。编译器会根据表达式的右值推导变量类型;当有如下调用:
auto iter = IntVector.begin();
可以指定是普通还是constant迭代器被返回。因此,为了返回一个constant迭代器(const_iterator),你可以使用:
auto iter = IntVector.cbegin(); // cbegin // The return type is: vector<int>::const_iterator
这样,这个迭代器以只读模式遍历vector,可以写出如下代码:
for(auto iter = IntVector.cbegin(); iter != IntVector.cend(); ++iter) { }
需要说明的是,constant迭代器可以访问 非const成员方法(我不敢保证,我只是在源码中看到)。因此,最好使用cend来代替end检查有效性。
在sets和maps集合中又是如何?
对于所有的集合类(set,multiset, unordered_set, and unordered_multiset),其迭代器总是constant的。也就是说,当调用方法begin/end,find 以及下标操作符[],都是返回的constant 迭代器。像begin,find这些方法,虽然它们返回的数据类型都是非constant迭代器,但其行为是constant迭代器。
看以下代码:
set<int> is; is.insert(10); *is.begin() = 120; wcout << is.count(10) << ", " << is.count(120);
以上例子首先插入10到set中,然后试图修改第一个元素的值。
因为只有一个元素插入进来,begin将返回指向该元素的迭代器;在vc9及之前的编译器中,可成功修改值为120;
但从vc10开始,第三行将不能编译通过,编译器报错为:
error C3892: 'std::**::begin' : you cannot assign to a variable that is const
对于map,键不可修改,值可修改。以下是代码示例:
map<int, int> imap; imap.insert( make_pair( 1, 1028 )); imap.insert( make_pair( 2, 2048 )); imap.find(1)->second = 1024; // Compiles imap.find(2)->first = 4; // Error imap[2] = 2000; *imap.begin()->first = 10; // Error *imap.begin()->second= 10; // Compiles *imap.cbegin()->second= 10; // Error on VC10, since iterator is constant. NA for VC9
数组类array
array类是STL新增容器类,用于在存储一个固定大小的数组。数组大小与数据元素类型一同由模版参数指出。其指定的大小数值必须是一个常量运行时的(与其它C/c++数组一样)。不像其它容器能够增加或减少容器大小,array支持其它标准方法-如迭代,随机访问,交换数据,赋值等。
头文件:<array>
命名空间:tr1.但由于'using tr1::array' 已被头文件包含。所有,在使用中,申明std就可以了。
示例:
array<int, 64> IntArray;
第一个参数指定模版数据类型,第二个是一个常量编译时的整型。定义之后,IntArray的大小不能再改变。当然,除了int,可以使用其它的基本数据类型。如果要使用自定义类,则要实现复制构造函数,重载赋值操作符,比较操作符。同时,默认构造函数必须为公有。
这个类的引入是为了和其它STL容器进行无缝整合。例如,你使用vector或list,需要调用begin/end方法来访问元素。当你想将其作为普通数组使用时,代码会编译失败。这必须改变。用array类,你可以同时获得STL的灵活性和普通数据的性能。
依据最近的编译器报告(tr1 增刊),可以使用以下方式来初始化array:
array<int, 64> IntArray;
如果忽略1个或多个元素没赋值,它们将被置为0.
如果对array没有做任何初始化操作,其所有元素处于未初始化状态。
array类支持以下STL标准方法:
l at, operator [] - 返回指定位置元素引用.
l back, front - 各自返回第一及最后一个元素位置引用;
l begin, cbegin, rbegin, crbegin -返回第一个元素迭代器;
l end, cend, rend, crend - 返回最后一个元素后一位的迭代器;
l empty - 判断容器是否为空。仅当数组大小为0时返回true;
l size, max_size - 返回array对象大小,该大小在编译期间确定;
以下方法需要重点说明:
array::assign 和 array:fill
这2个方法功能相同,实现将array所有元素赋值为一个给定值。此方法也可用来通过指定值替换array对象的所有元素。例如:
array<int,10> MyArray; MyArray.fill(40); // or 'assign' for_each(MyArray.cbegin(), MyArray.cend(), [](int n) { std::wcout << n << "\t";} );
将10个元素赋值为40,之后输出到控制台。
array::data
此方法返回数组第一个元素地址。与普通数组相比(eg:int_array[N]),此方法类似于表达式&int_array[0] 或者int_array。 此方法是由指针直接操作。const及非const方法都可用。例如:
int* pArray = MyArray.data(); // Or auto pArray = MyArray.data();
array::swap(array&) 和swap(array&, array&)
交换两个数组大小和类型相同的array对象。第一个方法是非静态方法,实现将本数组内容与参数中的指定数组交换。第二个方法,携带2个array&参数,互换内容。例如:
typedef array<int,10> MyArrayType; MyArrayType Array1 = {1,2,4,8,16}; MyArrayType Array2; Array2.assign(64); Array1.swap(Array2); // Or - swap(Array1, Array2); // Array1 - 64, 64...64 // Array2 - 1,2,4,....0
如果试图交换不同类型数组,编译器会报错:
array<int,5> IntArrayOf5 = {1,2,3,4,5}; array<int,4> IntArrayOf4 = {10,20,40}; array<float,4> FloatArrayOf4; IntArrayOf5.swap(IntArrayOf4); // ERROR! swap(IntArrayOf4, FloatArrayOf4); // ERROR!
六种比较操作符
作为全局函数的==, !=, <, >, <=, 及>=可用来比较2个相同类型的数组对象:
typedef array<int,10> MyArrayType; MyArrayType Array1 = {1,2,4,8,16}; MyArrayType Array2; Array2.assign(64); if (Array2 == Array1) wcout << "Same"; else wcout << "Not Same";
结果输出"Not Same" .
元组类tuple
STL程序员都知道pair 结构,它用来包装2个任意类型的元素。除了maps,在其它地方这个结构也非常有用,我们可以用来包装2个元素,而不用定义一个结构体。我们可通过first、 second变量取得这2个元素的值。
pair<string,int> NameAndAge; NameAndAge.first = "Intel"; NameAndAge.second = 40;
程序员经常typedef 它们,这样变量就可以容易的申明并可将pair 传给函数。
但是,如果你需要保证多于2个的元素,该怎么做呢?
通常,你会定义一个结构体,或使用多重pair。这样的格式并不能与STL无缝整合。
tuple 类就是为此而生。这个类允许将2至10个元素包装在一起。
所有元素类型都可不同,如果要加入自定义,其依赖的操作必须事先定义。tuple类通过模版重载具体如何工作,已超出我的理解,这里我只说明它可以完成什么,以及我们可以在哪里使用。
l 头文件: <tuple>
l 命名空间: tr1. 'using tr1::tuple' 已包含在 std .
申明2个元素元组:
tuple<int,int> TwoElements;
在构造函数中初始化:
tuple<int,int> TwoElement (400, 800);
如果初始化,则必须初始化所有元素,以下方式报错:
tuple<int,int> TwoElement (400); // Second initializer missing
这个错误可能不那么明显,但是你应该明白这个原则:必须初始化所有成员。
构造之后的初始化并不简单。在pair中,有make_pair 来辅助pair的初始化。自然的,tuple也有相应的辅助函数make_tuple。如果你已经读过或将要读VC2010并行编程,你会发现C++库中类似的完成类似的功能有其它新的make_函数。(译注2:并行编程地址)
下面例子说明如何初始化tuple对象:
TwoElement = make_tuple(400, 800);
跟tuple模版类一样,make_tuple 有传入2至10个参数的重载函数版本。make_tuple充分利用C++0x中右值引用的思想,完美的支持STL新特性。它允许复用同一对象而不是调用复制构造函数和赋值操作,以此提升整体性能。
我们可以结合make_tuple和auto关键词来灵活的定义一个tuple:
auto Elements = make_tuple(20, 'X', 3.14159); // Eq: tuple<int, char, double> Elements(20, 'X', 3.14159);
如何访问tuple中的元素?
对于pair对象,我们都是简单的使用其内部定义的变量first/second来访问元素。但是tuple类中并没有定义这样的变量,我们需要通过辅助函数get来访问这些未命名变量。例如:
tuple<string, int, char> NameAgeGender("Gandhi", 52, 'M'); int nAge = get<1>(NameAgeGender);
get<index>()中的可用索引必须在对象初始化的元素范围之内。索引以0为起点。在上述例子中,索引范围为[0,2],get<1>用于访问NameAgeGender对象中的第二个元素,返回一个整型给nAge。
get函数的索引必须是一个常量编译时的整型并在有效范围内。返回类型的推导也是常量时;以下代码编译器会报错:
int nAge = get<0>(NameAgeGender); // ERROR - cannot convert from 'string' to 'int'
这样也有助与发现潜在的代码缺陷:
char cGender = get<1>(NameAgeGender); // Leve 4 C4244 warning - 'int' to 'char' // Should be get<2>
更重要的是,get可以赋值给auto关键词:
auto sName = get<0>(NameAgeGender); // Type deduction - 'string'
对于非const类型tuple对象,返回类型为元组元素的引用,这样,可以修改元素值:
// Modify Age get<1>(NameAgeGender) = 79;
也可以将这个返回类型放在另一个引用中,并在之后修改。使用auto关键词也可行:
auto & rnAge = get<1>(NameAgeGender) ; rnAge = 79
需要指出的是,get函数同样适用于array类:
array<char, 5> Vowels = {'A','E','I','o','U'}; char c = get<1>(Vowels); // 'E' get<3>(Vowels) = 'O'; // Modify get<10><Vowels); // ERROR - Out of range!
tie函数
这个函数实现make_tuple函数的逆操作。使用这个函数,你可以用一个tuple对象来初始化一组变量。看例子:
tuple<string, int, char> NameAgeGender("Gandhi", 52, 'M'); string sName; int nAge; char cSex; tie(sName, nAge, cSex) = NameAgeGender;
以上代码将这3个变量对应的设置为值 "Gandhi", 52, and 'M'。我想tie是否可以用于取代make_tuple?结果证明不行:
tuple<string, int, char> NameAgeGender; NameAgeGender = tie("Gates", 46, 'M'); // make_tuple string sName; int nAge; char cSex; make_tuple(sName, nAge, cSex) = NameAgeGender; // NO error. See below.
tie函数返回tuple对象的引用;make_tuple则是创建一个对象,返回的并非引用。上面最后一行试图修改一个临时创建的tuple对象,编译器不报错,却并不能得到正确结果。因此,最好还是按照它们设计的功能来使用这2个函数。
六种关系操作符
与array类一样,tuple类中同样实现了6种操作符的功能。所有的重载版本都要求作关系操作的两个tuple对象必须是同类型的(所包含的元素的对应位置的类型必须相同)。
(未完待续)
【注1】TR1:
C++ Technical Report 1 (TR1)是ISO/IEC TR 19768, C++ Library Extensions(函式库扩充)的一般名称。TR1是一份文件,内容提出了对C++标准函式库的追加项目。这些追加项目包括了正则表达式、智能指针、哈希表、随机数生成器等。TR1自己并非标准,他是一份草稿文件。然而他所提出的项目很有可能成为下次的官方标准。这份文件的目标在于「为扩充的C++标准函式库建立更为广泛的现成实作品」。