揭示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还是普通迭代器; 以下为新增方法:
以上所有方法都在相应的容器类中申明为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标准方法:
以下方法需要重点说明: 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++标准函式库建立更为广泛的现成实作品」。 |
|