STL 注意事项

STL中的容器的存储分为顺序存储(如vector、deque)与链式存储(如list、map、set)。
对于map与list这样的链式存储结构。我们一般可以有两种方法
1. 使用erase(iter++),因为iter2 = iter++是iter先移到指向下一个节点,而iter2还是指向当前的节点。
for(;iter!=mapStudent.end();)
{
         if((iter->second)>=aa)
         {
                 //满足删除条件,删除当前结点,并指向下面一个结点
                           mapStudent.erase(iter++);
         }
         else
         {
         //条件不满足,指向下面一个结点
         iter++;
         }
}
这种删除方式也是STL源码一书中推荐的方式,分析 mapStudent.erase(iter++)语句,map中在删除iter的时候,先将iter做缓存,然后执行iter++使之指向下一个结点,再进入erase函数体中执行删除操作,删除时使用的iter就是缓存下来的iter(也就是当前iter(做了加操作之后的iter)所指向结点的上一个结点)。
       根据以上分析,可以看出mapStudent.erase(iter++)和map Student.erase(iter); iter++;这个执行序列是不相同的。前者在erase执行前进行了加操作,在iter被删除(失效)前进行了加操作,是安全的;后者是在erase执行后才进行加操作,而此时iter已经被删除(当前的迭代器已经失效了),对一个已经失效的迭代器进行加操作,行为是不可预期的,这种写法势必会导致 map操作的失败并引起进程的异常。

2. erase的返回值会指向下一个节点
       for(TStrMapIter iter= strmap.begin(); iter!= strmap.end();)  
       {  
             if ("somevalue" == iter->second )  
             {  
                 iter = strmap.erase(iter);  
             }  
             else  
             {  
                 ++iter;  
             }  
       }  

但对于顺序存储第一种用法却是错误的。因为顺序存储的容器一旦erase时,会涉及到数据移动,iterator所指的位置还是那个位置,但元素却移动了,iter++之后已不再你想要的元素位置了。iter = strmap.erase(iter)与iter++对于顺序容器有何不同?

源码剖析

以STL list为例:

iterator的相关操作
_Self& operator++()
{
       this->_M_incr();
       return *this;
}

_Self operator++(int)
     _Self __tmp = *this;
       this->_M_incr();
       return __tmp;                             //后缀++按照语意返回了++前的iterator,
}

void _M_incr() { _M_node = _M_node->_M_next; }       //++的操作对于list结构来说,就是使iterator的_M_node指向下一个结点

iterator erase(iterator __position)
   _List_node_base* __next_node = __position._M_node->_M_next;
       _List_node_base* __prev_node = __position._M_node->_M_prev;
       _Node* __n = (_Node*) __position._M_node;
       __prev_node->_M_next = __next_node;
       __next_node->_M_prev = __prev_node;   //上面的代码把删除结点__position的前后结点串起来,而移除_positoin
       _STLP_STD::_Destroy(&__n->_M_data); //call T::~T()
       this->_M_node.deallocate(__n, 1);                       //释放结点内存
       return iterator((_Node*)__next_node);
}

分析代码我们可以看出,erase会deallocate__position的_M_node, 在__position上再进行++是错误的。
所以不能在m_list.erase(iter)后,进行iter++.

哪为什么m_list.erase(iter++)可以呢?为什么不能用m_list.erase(++iter)?
参照operator++的代码我们可以找到答案,iter++返回了++之前的iter值,erase使用这个值能正确进行__position的前后结点的串接及删除正确的结点,而++iter返回的是++之后的iter,所以m_list.erase(++iter)串接不正确,iter->_M_node也是失效的.

对于非结点类,如数组类的容器vector,string,deque,如果erase会返回下个有效的iterator,可以这样处理:
for(vector<int>::iterator iter = m_vector.begin(); iter != m_vector.end();)
{
       if(需要删除)
       {
               iter=m_vector.erase(iter);
       }
       else
               ++iter;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C++编程中,有一些实用的注意事项可以帮助你编写更高效、可维护和安全的代码。以下是几个关键点: 1. **类型转换**:使用`static_cast`, `dynamic_cast`, `const_cast`, 和 `reinterpret_cast`时要小心,确保类型转换的正确性和意图。滥用类型转换可能导致运行时错误或性能损失。 2. **内存管理**:手动管理内存(使用new, delete,指针等)时要避免内存泄漏和悬挂指针。使用智能指针(如`shared_ptr`, `unique_ptr`, 或`std::optional`)可以简化内存处理。 3. **异常处理**:尽量遵循"异常不应被忽视"的原则,用`try-catch`块捕获并处理可能的异常。同时,避免在finally块中释放资源,因为这可能导致资源在异常发生后未得到正确的清理。 4. **命名规范**:遵循一致的命名约定,比如成员变量使用小驼峰式(`camelCase`),函数和类使用大驼峰式(`PascalCase`)。 5. **模板编程**:C++模板可以生成灵活的代码,但过度使用可能导致代码复杂度增加。确保模板参数的数量适中,并为模板提供明确的文档。 6. **RAII(Resource Acquisition Is Initialization)**:利用RAII原则,确保在对象生命周期结束时自动释放资源,例如文件、锁或动态分配的内存。 7. **范围-based for循环**:对于迭代容器,使用范围-based for循环比传统的迭代器更简洁易读。 8. **STL**:充分利用标准模板库(STL),它提供了高效的数据结构和算法,可以提高代码质量。 9. **避免全局变量**:尽可能减少全局变量,它们增加了耦合度和测试的复杂性。 10. **代码复用**:合理地设计类和模块,以促进代码的复用和模块化。 相关问题: 1. 何为RAII原则? 2. 使用范围-based for循环有何优点? 3. C++中为什么要避免全局变量?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值