• STL学习笔记(一) 容器


    0.前言
    随机访问迭代器: vector、string、deque
    STL的一个革命性的方面就是它的计算复杂性保证

    条款01:慎重选择容器类型

    c++提供的容器:
    标准STL序列容器:vector、string、deque、list
    标准STL关联容器:set、multiset、map、multimap
    非标准STL容器:stack、queue、hash_set、hash_map

    STL容器的一个分类方法:
    连续内存容器:逻辑相邻的元素物理地址也相邻:vector、string、deque
    基于节点的容器:逻辑相邻的元素的物理地址一般不相邻:list、set、map

    条款02:不要试图编写独立于容器类型的代码

    STL以泛化原则为基础:
    数组被泛化为"以其包含的对象的类型为参数"的容器
    指针被泛化为"以其指向的对象的类型为参数"的迭代器
    函数被泛化为"以其使用的迭代器的类型为参数"的算法

    条款03:确保容器中的对象拷贝正确而高效

    当通过 insert、push_back 向容器中加入对象时,存入容器的是你所指定对象的拷贝
    当通过 front、back 从容器中取出一个对象时,你所得到的是容器中所保存对象的拷贝

    使拷贝动作高效、正确,并防止派生类对象剥离问题的一个简单办法就是:使容器包含指针而不是对象(智能指针)

    void func() {
        typedef std::tr1::shared_ptr<Widget> SPW;
        std::vector<SPW> vpw;
        for (int i = 0; i < 10; ++i)
            vpw.push_back(SPW(new Widget));    //无内存泄露且异常安全
    }
    条款04:调用 empty 而不是 检查 size() 是否为0

    if (c.size() == 0) 本质上与 if (c.empty()) 等价
    我们应该总是调用 empty(),因为 empty() 操作对所有的标准STL容器都是O(1),size() 对某一些list容器是O(n) (不同平台list实现略有不同)

    条款05:区间成员函数优先于与之对应的单元素成员函数

    区间成员函数:使用两个迭代器参数来确定该成员操作所执行的区间

    std::vector<int> vec1;
    std::vector<int> vec2;

    现在让vec1的整体与vec2 的后半部分相同:

    (1)区间成员assign版本:

    vec1.assign(vec2.begin() + vec2.size() / 2 , v2.end());

    //注:assign() 对所有的标准序列容器 vector、string、deque、list都适用,当需要完全替换一个容器的内容时,应该优先使用assign()

    (2)单元素循环版本:

    vec1.clear();
    for (auto it = vec2.begin() + vec2.size() / 2; it != vec2.end(); ++it)
        vec1.push_back(*it);

    (3)区间成员insert版本:

    vec1.clear();
    vec1.insert(vec1.end(), vec2.begin() + vec2.size() / 2 , vec2.end());

    假定把一个int数组拷贝到一个vector里面:

    方法一:区间成员函数

    int data[numValues];
    vector<int> vec;
    vec.insert(vec.begin(), data, data + numValues);

    方法二:单元素循环

    vector<int>::iterator it = vec.begin();
    for (int i = 0; i < numValues; ++i) {
        it = vec.insert(it, data[i]);
        ++it;
    }

    注:任何push和insert操作都可能导致迭代器失效,当元素插入到vector、deque、string时,必须确保迭代器在每次循环后都得到更新

    a.方法一之调用一次insert函数,方法二调用了numValues次insert函数
    b.假设vec插入前已有n个元素,则方法二总共会有 n*numValues 次元素移动,如果元素为自定义类型,则会调用 n*numValues次赋值操作
    方法一中,c++标准要求区间insert函数把现有容器中的元素直接移动到他们的最终位置,总共会有 n 次元素移动
    c.方法二使用区间insert可以在元素插入之前计算需要多少新内存,从而只进行一次 vector的内存扩充操作
    方法一单元素循环插入numValues次最多可导致log(numValues)次新内存分配(涉及到新内存分配、元素拷贝到新内存、旧内存回收)


    区间函数:
    (1)区间创建:所有标准容器提供如下形式的构造函数

    container::container(InputIterator begin, InputIterator end);

    (2)区间插入:所有标准序列容器提供如下形式的 insert

    container::insert(iterator pos, InputIterator begin, InputIterator end);//元素在pos之前插入

    所有标准关联容器提供如下形式的insert:

    container::insert(InputIterator begin, InputIterator end);//关联容器使用比较函数来决定元素该插入何处

    (3)区间删除:所有标准容器提供如下形式的 erase

    container::erase(iterator begin, iterator end);

    (4)区间赋值:所有标准序列容器都提供如下形式的 assign

    container::assign(InputIterator begin, InputIterator end);

    总结:应该优先使用区间成员函数而不是单元素循环版本,因为使用区间可以预先知道操作结果,从而一次性移动到最终位置

    条款06:当心c++编译器最烦人的分析机制

    当区间创建函数 container::container(InputIterator begin, InputIterator end) 使用的迭代器是 istream_iterator时可能遇到c++最烦人的分析机制

    条款07:容器中包含 new 创建的指针,必须在对象析构之前 delete 掉(注:采用 shared_ptr,避免人肉 delete)
    条款08:采用shared_ptr智能指针,禁止使用auto_ptr
    条款09:慎重选择删除元素的方法


    假如有以下标准STL容器: container<int> c; 现在要删除其中所有值为2012的元素
    如果是连续内存容器(vector、string、deque):

    c.erase(remove(c.begin(), c.end(), 2012), c.end()); //erase-remove是删除特定值的最好办法

    如果是list容器:

    c.remove(c.begin(), c.end(), 2012);//remove是list容器删除特定值的最好办法

    如果是标准关联容器(set、map):

    c.erase(2012);//erase是set、map容器删除特定值的最好办法(无remove成员函数)

    对于 vector、string、deque、list 删除满足条件的特定值时,采用 remove_if


    而对于关联容器,比如set容器:写一个循环遍历容器中的元素

    std::set<int> c;
    for (auto it = c.begin(); it != c.end(); ++it) {
        if (func(*it)) c.erase(it);//错误!! 如果erase(*it)之后,位于it之后的所有迭代器全部失效,而循环变量it不能被递增
    }

    正确写法:

    for (auto it = c.begin(); it != c.end(); ) {
        if (func(*it)) {
            c.erase(it++);
        } else {
            ++it;
        }
    }
    条款10:了解分配子(allocator)的约定和限制
    条款11:理解自定义分配子的合理用法
    条款12:切勿对STL容器的线程安全性有不切实际的依赖
    vector<int> vec;                                            //第1行
    vector<int>::iterator it(find(vec.begin(), vec.end(), 10));  //第2行
    if (it != vec.end()) *it = 0;                                //第3行

    在多线程环境中,可能在第1行刚刚完成之后,另一个不同的线程会更改vec(扩充重分配内存),这样导致 it 迭代器失效,对it解引用错误
    此时应该对这3行代码块进行手工同步控制,引入互斥锁(以对象管理锁资源)

  • 相关阅读:
    [Selenium+Java] Upload Selenium Script to GitHub
    [Selenium+Java] Apache ANT with Selenium: Complete Tutorial
    [Selenium+Java] Selenium Core Extensions (User-Extensions.js)
    序列流、对象操作流、打印流、标准输入输出流、随机访问流、数据输入输出流、Properties(二十二)
    FileReader、 FileWriter、readLine()和newLine()、LineNumberReader(二十一)
    图片加密解密小知识
    道德经--老子
    IO、FileInputStream、(二十)
    异常、Throwable、finally、File类(十九)
    Collections工具类、Map集合、HashMap、Hashtable(十八)
  • 原文地址:https://www.cnblogs.com/wwwjieo0/p/3443947.html
Copyright © 2020-2023  润新知