STL工程问题

发布于:2024-07-05 ⋅ 阅读:(16) ⋅ 点赞:(0)

1. vector迭代器失效问题

  • 迭代器失效,其实就是迭代器底层的指针所指向的内存空间被销毁,而访问已经被释放的内存空间会导致程序崩溃

1.1. 情况1:扩容导致迭代器失效

  • 底层空间改变的函数:
    • resize() 改变容器大小
    • reserve() 预留容器存储空间
    • insert() 插入元素
    • assign() 用新元素替换现有元素
    • push_back() \ emplace_back() 尾插元素等。
  • 原因:异地扩容后,原本的内存空间被释放,但迭代器仍指向被释放的内存空间,导致迭代器失效。
  • 解决:若扩容,扩容后对迭代器更新,指向新空间的相对位置。

1.2. 情况2:删除导致迭代器失效

  • 原因:erase() 删除pos位置元素后,pos变成野指针,导致迭代器失效。
  • 解决:使用erase() 的返回迭代器对pos重新赋值,指向删除元素之后位置的迭代器。

1.3. 情况3:尾删导致迭代器失效

  • 原因:erase() 删除pos位置元素后,pos位置之后的元素会往前移动,但如果pos指向最后一个元素,删除后pos迭代器会失效。
  • 解决:if语句判断pos迭代器是否等于超过末尾迭代器end,若不等于时,允许访问pos迭代器。

2. STL怎么做内存管理

  • STL采用两级配置器,当分配的空间大小超过128B时,使用第一级配置器,直接使用malloc()、free()和realloc()等C函数进行内存空间的分配、释放和重新分配;当分配的空间大小小于128B时,为了减少申请小内存造成的内存碎片问题,使用第二级配置器(次级配置器),采用了内存池技术,通过空闲链表管理内存。
  • 第二级配置器具体操作:每次配置一大块内存,并维护16个空闲链表,对应管理大小为8~128B内存块。分配空间时,首先根据所需空间大小调整为8B的倍数,找到对应空闲链表,从链表中取出第一个可用的区块,如果没有可用区块,则调用refill()重新填充空间,新空间取自内存池。释放空间时, 由配置器回收到相应大小的空闲链表中。
  • 内存池技术优劣势:避免外部碎片,不需要频繁从用户态切换到内核态,性能高效。但是,仍然会造成内存浪费,如申请6B内存就必须分配8B,有2B的内部碎片。

3. 如何解决哈希冲突?

  • 哈希表,通过直接计算key的哈希值来访问元素。
  • 哈希表长度使用质数(只能被1和自身整除),可以降低发生冲突的概率,使哈希后的数据更加均匀;但如果使用合数,可能会导致很多数据集中分布到一个点上,造成冲突
  • 解决
    • 开放地址法:寻找其他位置来存放。包括线性探测法、平方探测法、再哈希法。
    • 链表法:直接添加到计算位置的链表中。

4. vector手动释放内存问题

  • 场景:vector包含大量数据时,如vector分配了10000个数据大小的内存,然后使用erase()删除9999个数据,留下一个有效数据,但内存占用仍为10000个数据大小,所有内存空间要等vector析构时才被系统回收,因此有必要主动释放vector内存。
  • 解决:使用 vector< int >(vec).swap(vec) 释放vec中多余空间。也可以,使用vector< int >().swap(vec) 释放整个vecter空间。
  • 采用swap释放内存原理:vector< int >()使用默认构造函数创建临时vector对象;再使用swap() 将临时对象与vec对象交换;最后临时对象被析构,从而释放其占用的内存。

5. 对象池思想

  • 对于需要频繁创建和销毁对象的场景。
  • 对象池思想:首先从对象池中寻找可用的对象并把它激活,若没有就创建对象来使用;当一个对象不使用时,不把它销毁,而是将它设置为不激活状态并放入对象池中。