0
点赞
收藏
分享

微信扫一扫

【java】异常与错误

f12b11374cba 2024-05-15 阅读 43

vector常见接口使用:

push_back

operator [ ] 

size

resize

reserve

resize 和reserve  :  resize开空间+初始化,可以用[ ]访问修改。reserve是单纯开空间。不能访问[ ],因为[ ]会做 i  < size 的检查

insert

erase

iterator

vector的迭代器是一种支持随机访问的迭代器。随机访问迭代器允许我们迅速访问向量内任何位置的元素,就像使用数组下标一样。这种类型的迭代器提供了以下基本操作:

  • 递增 (++):迭代器可以通过 ++ 操作符进行递增,指向向量中的下一个元素。
  • 递减 (--):迭代器可以通过 -- 操作符进行递减,指向向量中的前一个元素。
  • 解引用 (*):迭代器可以被解引用来访问它指向的元素的值,就像指针一样。
  • 指针算术:迭代器支持加减算术操作。例如,可以执行 iter + 5 来快速移动迭代器到当前位置后的第五个元素。

std::vector 提供了几种类型的迭代器:

  • Iterator:这是最常见的迭代器类型,允许读写向量内的元素。
  • Const_iterator:这种类型的迭代器不允许修改它所指向的元素,只能用来读取元素。常用于不需要更改向量内容的场景。
  • Reverse_iterator:以相反的顺序遍历向量。
  • Const_reverse_iterator:以相反的顺序遍历向量,但不允许修改元素。

向量的迭代器还提供了与容器的关系方面的操作:

  • **begin()/end()**:begin() 返回指向向量第一个元素的迭代器,end() 返回指向向量最后一个元素之后位置的迭代器(也就是说,不是最后一个元素,而是最后一个元素的下一个位置)。
  • **rbegin()/rend()**:与 begin() 和 end() 类似,但返回的是相应的反向迭代器,rbegin() 返回向量末尾的迭代器,rend() 返回向量开头之前位置的迭代器。

使用迭代器时要特别注意迭代器失效的问题。向量在进行插入(insert)或删除(erase)操作后,如果这些操作导致内存重新分配,则从那个点开始到向量末尾的所有迭代器都将失效。必须重新获取迭代器。在编写依赖迭代器的代码时,这是一个重要的安全注意事项。

lsit常见接口使用:

push_back

pop_back

push_front

pop_front

insert

erase

Iterator

下面是自实现的迭代器类

1. 迭代器设计的目的

迭代器的设计目的是提供一种统一的接口来访问容器内的元素,无论容器的内部结构如何。对于链表来说,迭代器隐藏了链表的节点链接逻辑,使得用户可以通过相同的方式来遍历链表、数组等不同的数据结构。

2. 链表迭代器的基本操作

  • 递增(operator++: 迭代器提供了递增操作,使其指向链表中的下一个元素。对于双向链表的迭代器,通常提供了两种递增操作:前缀递增(++iter)和后缀递增(iter++)。前缀形式直接返回递增后的迭代器,后缀形式则返回递增前的拷贝,然后将自身递增。
  • 递减(operator--: 对于双向链表的迭代器,它还支持递减操作,即向前遍历元素。同样地,递减操作也有前缀和后缀两种形式。
  • 解引用(operator* 和 operator->: 解引用操作允许通过迭代器访问当前指向的元素的值。operator* 返回元素的引用,而 operator-> 允许访问元素成员。

3. 链表迭代器的实现

以双向链表为例,迭代器内部通常保存一个指向当前链表节点的指针。通过移动这个指针来实现递增和递减操作,通过解引用这个指针来访问当前节点的值。

迭代器通常区分为常量和非常量两种,非常量迭代器允许修改指向的元素,而常量迭代器不允许。

4. 开闭原则

迭代器的设计遵循了开闭原则,链表的内部实现可以自由改变,只要迭代器接口保持不变,使用迭代器的代码不需要做任何修改就可以继续正常工作。

5. 迭代器失效

需要注意的一个重要概念是迭代器失效。在对链表进行插入、删除等操作时,某些迭代器可能会失效,即它们不再指向原来的元素。比如,删除一个节点后,指向该节点的迭代器将变得无效。正确处理迭代器失效是使用迭代器时的一个重要考虑点。

vector / list 的常见经典问题

vector和list的区别

vector:

优点: 尾插尾删, 下标随机访问,空间连续,CPU高速缓存的命中率更高
缺点:中间的插入和删除需要挪动数据,效率低
           扩容有代价:开新空间+拷贝数据

list:

优点:任意位置O(1)的插入和删除,按需申请和释放
缺点:不支持随机访问(内存碎片问题)

链表的内存碎片问题:

链表和内存碎片化

链表是一种通过节点链接的数据结构,每个节点包含数据和指向下一个(以及可能的上一个)节点的指针。由于链表的节点通常是单独分配的,链表可以引起两种类型的内存碎片:

  1. 外部碎片:链表节点可以在内存的任何位置被分配,这可能导致内存中未被利用的小空闲区域,也就是外部碎片。外部碎片化会随着多次分配和释放增加,最终可能导致内存利用率下降。

  2. 内部碎片:链表节点通常比实际需要存储的数据大(因为还需要存储指针),这可能导致为每个节点分配的内存中有未使用的部分,即内部碎片。

空间配置器和内存碎片化

空间配置器是一个系统级或库级的工具,用来管理内存的分配和释放。它通常会提供一些策略来减少内存分配导致的碎片化。例如:

  1. 内存池:空间配置器可以实现内存池策略,预先分配一大块内存,并从中分配小块内存给用户。当用户请求小块内存时,配置器从池中提取,可以减少外部碎片。

  2. 固定大小的分配器:对于链表这种结构,配置器可能提供了一种固定大小的分配器,专门用来分配节点大小一致的内存块。这样可以减少内部碎片以及释放内存时导致的外部碎片化。

vector的扩容

不同编译器的实现方式不同。
g++在vector的扩容策略是2倍扩容, 在 v s(visual studio)上是1.5倍的扩容。
并且在扩容后的数据移动方式上,C++11之前是拷贝构造,C++11之后就是移动构造了(通过move()变成右值)

举报

相关推荐

0 条评论