2010新版STL修订内容(VC2010)

被vector狠狠地刺痛了


2010新版STL修订内容(VC2010)


揭示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则在首次赋值后不再可指向其它变量(类似:常量指针和指向常量的指针)看了下面代码后,你应该会有一个清晰的认识:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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关键词的含义,你应该知道我们可以在变量申明时不用明确指定类型。编译器会根据表达式的右值推导变量类型;当有如下调用:

1
auto  iter = IntVector.begin();

可以指定是普通还是constant迭代器被返回。因此,为了返回一个constant迭代器(const_iterator),你可以使用:

1
2
auto  iter = IntVector.cbegin(); // cbegin
// The return type is: vector<int>::const_iterator

这样,这个迭代器以只读模式遍历vector,可以写出如下代码:

1
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迭代器。

看以下代码:

1
2
3
4
5
6
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,键不可修改,值可修改。以下是代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
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对象的所有元素。例如:

1
2
3
4
5
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方法都可用。例如:

1
2
3
int * pArray = MyArray.data();
// Or
auto  pArray = MyArray.data();

array::swap(array&) 和swap(array&, array&)

交换两个数组大小和类型相同的array对象。第一个方法是非静态方法,实现将本数组内容与参数中的指定数组交换。第二个方法,携带2个array&参数,互换内容。例如:

1
2
3
4
5
6
7
8
9
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

如果试图交换不同类型数组,编译器会报错:

1
2
3
4
5
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个相同类型的数组对象:

1
2
3
4
5
6
7
8
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个元素的值。

1
2
3
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对象:

1
TwoElement = make_tuple(400, 800);

跟tuple模版类一样,make_tuple 有传入2至10个参数的重载函数版本。make_tuple充分利用C++0x中右值引用的思想,完美的支持STL新特性。它允许复用同一对象而不是调用复制构造函数和赋值操作,以此提升整体性能。

我们可以结合make_tuple和auto关键词来灵活的定义一个tuple:

1
2
auto  Elements = make_tuple(20, 'X' , 3.14159);
// Eq: tuple<int, char, double> Elements(20, 'X', 3.14159);

如何访问tuple中的元素?

对于pair对象,我们都是简单的使用其内部定义的变量first/second来访问元素。但是tuple类中并没有定义这样的变量,我们需要通过辅助函数get来访问这些未命名变量。例如:

1
2
tuple<string, int , char > NameAgeGender( "Gandhi" , 52, 'M' );
int  nAge = get<1>(NameAgeGender);

get<index>()中的可用索引必须在对象初始化的元素范围之内。索引以0为起点。在上述例子中,索引范围为[0,2],get<1>用于访问NameAgeGender对象中的第二个元素,返回一个整型给nAge。

get函数的索引必须是一个常量编译时的整型并在有效范围内。返回类型的推导也是常量时;以下代码编译器会报错:

1
2
int  nAge = get<0>(NameAgeGender);
// ERROR - cannot convert from 'string' to 'int'

这样也有助与发现潜在的代码缺陷:

1
2
char  cGender = get<1>(NameAgeGender); // Leve 4 C4244 warning - 'int' to 'char'
// Should be get<2>

更重要的是,get可以赋值给auto关键词:

1
auto  sName = get<0>(NameAgeGender); // Type deduction - 'string'

对于非const类型tuple对象,返回类型为元组元素的引用,这样,可以修改元素值:

1
2
// Modify Age
get<1>(NameAgeGender) = 79;

也可以将这个返回类型放在另一个引用中,并在之后修改。使用auto关键词也可行:

1
2
auto  & rnAge = get<1>(NameAgeGender) ;
rnAge = 79

需要指出的是,get函数同样适用于array类:

1
2
3
4
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对象来初始化一组变量。看例子:

1
2
3
4
5
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?结果证明不行:

1
2
3
4
5
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++标准函式库建立更为广泛的现成实作品」。

原文:http://www.codeproject.com/KB/stl/stl2010.aspx




你可能感兴趣的:(2010新版STL修订内容(VC2010))