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

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

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

  • 相关阅读:
    JavaScript教程——JavaScript 的基本语法(标识符)
    ECMAScript 6 入门——ES6 声明变量的六种方法
    JavaScript教程——数据类型概述
    对称机密算法与非对称机密算法
    C语言提高 (2) 第二天 用指针对字符串进行操作
    C语言提高 (1) 第一天 数据类型本质与内存四区
    关于内存地址和内存空间的理解(转)
    为什么对数组处理的函数需要传递数组长度
    Unicode编码,解释UCS、UTF、BMP、BOM等名词
    上海
  • 原文地址:https://www.cnblogs.com/me115/p/1864344.html
Copyright © 2020-2023  润新知