首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

《小弟我的第一本C++书》节选:9.1.3 容器的使用说明书

2014-01-05 
《我的第一本C++书》节选:9.1.3 容器的使用说明书http://www.chenlq.net9.1.3 容器的使用说明书容器虽好,也

《我的第一本C++书》节选:9.1.3 容器的使用说明书
http://www.chenlq.net
9.1.3 容器的使用说明书

容器虽好,也得小心轻放、注意容器的各种使用规则。这里有一份容器的使用说明书,只有按照说明书上的使用规则,才能将容器用得恰到好处,发挥它应有的作用。这份容器使用说明书是这样写的:

    容器可以用来存放对象或者对象的指针。一般来说,容器不太关心里面存放的是什么东西,既可以在容器中存放普通的数据,也可以存放类的实例对象,或者指向这些实例对象的指针。在创建容器时,可以通过容器模板类的参数指定这个容器将要存放的内容。例如:

    // 用于存放整型数的vector容器
    vector<int> vecnSalary;
    // 用于存放字符串的list容器
    list<string> listName;
    // 用于存放Employee*指针的map容器
    map<int, Employee*> mapEmployee;

    一 般来说,既可以在容器中存放对象,也可以存放这些对象的指针,那么到底该如何选择呢?如果使用的是基于连续内存的容器,例如vector容器等,当在这些 容器中插入或者删除元素时,往往会引起内存的重新分配或者内存的复制移动。在这种情况下,为了提高内存操作的性能,我们优先选择保存对象的指针,因为指针 的体积通常比对象的更小。对基于节点内存的容器,当进行数据元素的操作时则很少有内存的复制移动,所以在这种容器中保存对象或者指向对象的指针并无性能上 的差别。但是从方便使用的角度考虑,可以优先选择保存对象。如果需要保存一些机器资源(例如,文件句柄、命名管道、套接字或者其他类似的资源),那么通常 选择保存指向这些对象的指针。
    小心清理容器中保存的对象指针。如果容器中保存的是对象,那么在容器析构的时候容器会自动清理这 些对象。但是,如果容器中保存的是对象的指针,那么这些指针的清理工作就是程序员的责任了。我们应该在容器使用完毕后,注意清理其中保存的指针,释放这些 指针所指向的对象。例如:

    // 创建一个存放Employee*指针的vector容器
    vector<Employee*> vecEmployee;
    // 对容器进行操作…

    // 在容器使用完毕后,清空容器中保存的指针,
    // 释放这些指针所指向的对象
    for( auto it = vecEmployee.begin();
    it != vecEmployee.end(); ++it )
    {
    // 判断指针是否为NULL,
    // 如果不为NULL,则释放指针指向的对象
    if( NULL != (*it) )
    delete (*it); // 释放指针指向的对象

    (*it) = NULL; // 将指针设置为NULL,防止误用
    }
    // 清空整个容器
    vecEmployee.clear();

    这里通过循环遍历容器中保存的每个指针,释放这些指针所指向的对象,将整个容器清空,最终完成容器的手动清理工作。
    为 容器中的对象实现拷贝构造函数和赋值操作符。如果需要将某个对象保存到容器中,实际上STL会重新生成一个此对象的拷贝,然后将这个拷贝保存在容器中,源 对象将不再使用。所以,为了保证将对象装入容器时对象能够被正确地拷贝,需要实现这个对象的类的拷贝构造函数和赋值操作符。例如:

    // 将会保存到容器中的员工类
    class Employee
    {
    public:
    // 实现默认构造函数
    Employee()
    {}
    // 实现带参数的构造函数
    Employee(string strName)
    :m_strName(strName)
    {}
    // 实现拷贝构造函数
    Employee(const Employee& rSource )
    {
    // 利用赋值操作符实现对象的拷贝
    *this = rSource;
    };
    // 实现赋值操作符,这里对类的每个属性都进行了合理的初始化
    Employee& operator = (const Employee& rSource )
    {
    m_nSalary = rSource.m_nSalary;
    m_strName = rSource.m_strName;

    return *this;
    };
    // 类的属性
    private:
    // 使用类成员声明初始化
    int m_nSalary = 1000;
    string m_strName = “Jiawei”;
    };

    当 然,很多时候无须自己实现类的拷贝构造函数,编译器会自动创建一个拷贝构造函数,以拷贝内存的方式完成类对象的拷贝。但是,这里要特别注意的是,内存拷贝 并不能保证类对象被正确地拷贝,特别是当类中有指针作为成员属性的时候,需要谨慎地实现自己的拷贝构造函数,以保证类对象在装入容器时能够正确地拷贝。
    使 用迭代器删除容器中的数据元素需谨慎。当使用迭代器删除容器中的数据元素时,因为元素的删除,容器中的元素位置将发生变化,所以迭代器所代表的当前位置也 会发生变化,这一点需要特别注意。例如,:我们想要删除一个容器中大于1 000的数。,按照思维习惯,我们会遍历容器中的数据元素,遇到符合条件的元素就进行删除,实现如下:

    // 循环遍历删除容器中的元素
    for( auto it = vecSalary.begin();
    it != vecSalary.end(); ++it )
    {
    // 遇到符合条件的元素就进行删除
    if( *it > 1000 )
    vecSalary.erase(it);
    }

    可 是,当实际运行这段代码的时候,发现其运行结果并非想象的那样。这是因为在删除过程中,在删除某个元素后,vector容器后面的元素会自动向前移动一个 位置,以保持vector容器内存的连续性,这时的迭代器实际上指向的是被删除元素后的第一个元素。当进入下次循环的时候,迭代器向后移动一个位置,实际 上指向的是被删除元素后的第二个元素,中间跳过了一个元素,这就很可能造成漏掉某些元素的检查而导致删除不完全。所以,需要在每次删除动作发生后,重新设 置当前迭代器的值,将它指向正确的容器位置:。
     
    // 循环遍历删除容器中的元素
    for( auto it = vecSalary.begin();
    it != vecSalary.end(); )
    {
    // 遇到符合条件的元素就进行删除
    if( *it > 1000 )
    it = vecSalary.erase( it ); // 删除完成后,将迭代器重新指向正确的位置
    else
    ++it; // 如果不删除当前元素,则将迭代器指向元素的下一个位置
    }

熟读容器的使用说明书,了解容器的各种使用规则和注意事项,可以让容器使用起来更加舒心、更加得心应手。


[解决办法]
换本好书,这本书跟谭浩强的书差不了多少

热点排行