与 STL 相关的一些知识

本文深入探讨了STL中的vector、map、hash_map容器及其算法优化,详细分析了内存结构、操作开销及迭代器使用注意事项,并对比了三种容器的适用场景。同时介绍了STL算法库中的迭代器类型与函数对象的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STL 速查手册: http://download.youkuaiyun.com/detail/whd0810/4817808


vector

内存结构如图 1:

图 1

1、调用 push_back 时要考虑扩容、内存移动的开销,例如:


    std::vector<int> ivec;
    for (int i = 0; i < 10; ++ i)
    {   
        std::cout << "size: " << ivec.size() << ", capacity: " << ivec.capacity() << std::endl;
        ivec.push_back(i);
    }   


初始时 vector 申请 capacity (0) 大小的内存块,当内存用完后再一次调用 push_back 则会进行一次扩容(1.5 倍或者 2 倍),然后把旧数据移动到新的内存中。capacity 的变化序列为: 1 2 4 8 16,内存总共移动了 4 次,移动的元素个数为 15 个,这是一个巨大的开销。

如果大概知道元素的个数,可以调用 reserve 预留空间避免内存移动开销,改动后如下:


   std::vector<int> ivec;
    ivec.reserve(16);
    ...


初始的 vector 调用 [] 操作符来插入元素会引起数组越界(此时 capacity 的大小为 0),即使调用 reserve 预留了足够的空间也不应使用,因为 [] 不会修改vector内部的 finish 指针这会引起各种问题,增加新元素建议使用: push_back。


2、调用 erase 时要注意迭代器的失效问题

erase 删除元素后把后面的元素往前移动,此时后面的迭代器都会指向不正确的值,为安全起见应该不再使用与此容器相关的迭代器。在 for 循环中删除元素可用 erase 的返回值指向下一个元素的迭代器的特点进行删除,如下:

   for (std::vector<int>::iterator iter = ivec.begin(); iter != ivec.end(); )
    {
        iter = ((val == *iter)? ivec.erase(iter): ++ iter);
    } 

也可以使用: erase-remove 批量删除元素,remove 算法把容器中要删除的值移动到末尾返回第一个要删除元素的迭代器,代码如下:

    ivec.erase(std::remove(ivec.begin(), ivec.end(), val), ivec.end());

remove 操作前后的内存结构如图 2:


图 2


map (multimap)

1、使用 [] 操作符时要注意的事项, operator [] 的具体实现如下:


 _Tp& operator[](const key_type& __k) {
    iterator __i = lower_bound(__k);
    // __i->first is greater than or equivalent to __k.
    if (__i == end() || key_comp()(__k, (*__i).first))
      __i = insert(__i, value_type(__k, _Tp()));
    return (*__i).second;
  }


大概意思是:不存在则插入新的元素 (__k, _Tp()) 并返回新插入元素值的引用,存在则返回旧值的引用。

这会引起以下三个问题:
a) 查找不存在的元素则会新增一个新的元素 (__k, _Tp()),频繁查找会增加 map 的容量
b) 新增元素先调用一次元素的默认构造函数,然后再调用一次赋值函数,多了一次默认构造函数的调用
c) 如果已经存在相应的 key 值,则新值覆盖旧值且不报错


2、调用 erase 时要注意迭代器的失效问题

map 使用红黑树来实现,以节点指针为操作单位,键值对包含在节点指针里,所以 插入、删除操作都不会影响到其它迭代器也不会引起内存的移动。但删除时本身会失效所以在 for 里循环删除时要注意,代码如下:


   for (std::map<int, int>::iterator iter = imap.begin(); iter != imap.end(); )
    {
        if (val == iter->first)
        {
            imap.erase(iter ++);
        }   
        else
        {
            ++ iter;
        }   
    }

hash_map


hash_map 使用链表数组 + hash 的方式来实现,内存结构如图 3(大小: 5,元素个数: 3):



图 3

注意事项跟 map 相同

vector、 map、 hash_map 比较



插入

查找

删除

vector

O(1) 有可能会涉及内存移动

O(n)

O(n)

map

O(logn) 不涉及内存移动

O(logn)

O(logn)

hash_map

O(1) 有可能会涉及重组

O(1)

O(1)


三种容器的适用情况:
vector: 元素比较少,遍历居多,增删改查不频繁
map: 需要维护元素有序,元素较多,增删改查频繁
hash_map: 不需要维护元素有序,元素较多,增删改查频繁


迭代器


stl 里有5种迭代器,它们的继承关系如图 4:


图 4

Input Iterator: 只读迭代器,支持: 右*, ++
Output Iterator: 只写迭代器,支持: 左*, ++
Forward Iterator: 单向移动可读写迭代器,支持: *, ++
Bidirectional Iterator: 双向移动可读写迭代器,支持: *, ++,  --
Random Access Iterator: 随机迭代器,支持: *, ++, --, + n, - n
对于非随机迭代器,要加减n,最好调用:advance函数


例如:

vector: Random Access Iterator

map: Bidirectional Iterator

hash_map: Forward Iterator


stl 算法都是以它所需要的最低程度的迭代器类型,比如 find,声明如下:

template <class _InputIter, class _Tp>
inline _InputIter find(_InputIter __first, _InputIter __last,
                       const _Tp& __val);

当然,用 Forward, Bidirectional, Random 迭代器来调用都可以。但对于 nth_element 则只能使用Random 迭代器,否则编译出错。

template <class _RandomAccessIter>
inline void nth_element(_RandomAccessIter __first, _RandomAccessIter __nth,
                        _RandomAccessIter __last);

函数对象


函数对象在 stl 算法库中广泛应用的一种轻量级的对象。定义:具有函数特质的对象,只需要重载 () 即可,例如:

template <class _Tp>
struct greater : public binary_function<_Tp,_Tp,bool>
{
  bool operator()(const _Tp& __x, const _Tp& __y) const { return __x > __y; }
};

stl 使用的两种函数对象类型是:unary function (只带一个参数), binary function (带两个参数)。常用的函数对象为:

1、带有一个参数的比较函数对象,例如: find_if 的第三个参数

template <class _InputIter, class _Predicate>
inline _InputIter find_if(_InputIter __first, _InputIter __last,
                          _Predicate __pred);
struct Equal
{
    Equal(int i) : __val(i) {}
    bool operator () (int __x) const { return __x == __val; }
    int __val;
};  
find_if(ivec.begin(), ivec.end(), Equal(3));

2、带有两个参数的比较函数对象,例如: sort 的第三个参数

template <class _RandomAccessIter, class _Compare>
inline void sort(_RandomAccessIter __first, _RandomAccessIter __last,
                 _Compare __comp);
struct Less
{
    bool operator () (int __x, int __y) const { return __x < __y; }
};  
sort(ivec.begin(), ivec.end(), Less());


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值