vector常见接口使用:
push_back
void push_back (const value_type& val); void push_back (value_type&& val);
push_back有两种重载方式
一、 参数是一个vector 存储类型的元素
二、 参数是一个vector 存储类型的右值元素,是右值类型的重载版本,用于向容器移动右值(临时对象),而不是复制
operator [ ]
reference operator[] (size_type n); const_reference operator[] (size_type n) const;
operator [ ] 是vector重载了 [ ] 让它可以像数组一样使用,参数是下标,返回值是该下标的元素的引用,支持了const和非const版本
size
size_type size() const noexcept;
作用是返回当前vector的元素个数
noexcept 关键字是 指定这个函数不会抛异常。暗示了调用
size
函数是安全的,因为它不会导致程序因为异常而突然终止。
resize
void resize (size_type n); void resize (size_type n, const value_type& val);
void resize (size_type n);
这个版本的
resize
函数将容器的大小调整为n
元素。如果当前大小大于n
,则会移除多余的元素。如果当前大小小于n
,则会添加元素直到容器的大小为n
。新添加的元素将会进行默认初始化。
void resize (size_type n, const value_type& val);
这个版本的
resize
函数也是将容器的大小调整为n
元素,但是它允许你指定新增元素的初始值val
。如果当前大小大于n
,多余的元素会被移除;如果当前大小小于n
,则会添加值为val
的元素到容器的末尾直到其大小为n
。
reserve
void reserve (size_type n);
目的是提前为容器分配足够的内存空间,以存储至少
n
个元素。这是一种优化内存管理和提高性能的方法。
resize 和reserve : resize开空间+初始化,可以用[ ]访问修改。reserve是单纯开空间。不能访问[ ],因为[ ]会做 i < size 的检查
insert
| single element (1) |
iterator insert (const_iterator position, const value_type& val);
|
| fill (2) |iterator insert (const_iterator position, size_type n, const value_type& val);
|
| range (3) |template <class InputIterator> iterator insert (const_iterator position, InputIterator first, InputIterator last);
|
| move (4) |iterator insert (const_iterator position, value_type&& val);
|
initializer list (5) iterator insert (const_iterator position, initializer_list<value_type> il);
单元素版 (
single element
):
iterator insert (const_iterator position, const value_type& val);
这个函数在**position
** 指示的位置之前插入一个新元素val
,并返回指向新插入元素的迭代器。填充版 (
fill
):
iterator insert (const_iterator position, size_type n, const value_type& val);
这个函数在position
指示的位置之前插入n
个新元素,每个新元素的值都是**val
**,并返回指向第一个新插入元素的迭代器。范围版 (
range
):
template <class InputIterator>
iterator insert (const_iterator position, InputIterator first, InputIterator last);
这个函数在position
指示的位置之前插入由范围 **[first, last)
**指定的元素,返回指向第一个新插入元素的迭代器。- 例: {1,2,3 }插入{4,5,6}
position是2。
结果:{1,4,5,6,2,3}移动版 (
move
):
iterator insert (const_iterator position, value_type&& val);
这个函数使用右值引用将一个新元素val
移动到 **position
**指示的位置之前,并返回一个迭代器,该迭代器指向被移动的元素。初始值列表版 (
initializer list
):
iterator insert (const_iterator position, initializer_list<value_type> il);
这个函数在position
指示的位置之前插入由initializer_list il
指定的元素,并返回指向第一个新插入元素的迭代器。
erase
iterator erase (const_iterator position); iterator erase (const_iterator first, const_iterator last);
- 单迭代器版本:
这个函数删除位于position
的单个元素,并返回指向被删除元素后一个元素的迭代器。如果删除的元素是std::vector
的最后一个元素,则返回的迭代器等同于end()
。- 迭代器范围版本:
这个函数删除闭区间[first, last)
内所有的元素,就是说,从first
到last
之间的元素会被删除,但不包括last
指向的元素。并返回指向原来last
所在位置的新元素的迭代器。如果last
等同于end()
,返回的迭代器也将是end()
。
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
void push_back (const value_type& val); void push_back (value_type&& val);
void push_back (const value_type& val);
这个版本的push_back
接受一个对常量引用 (const value_type&
) 参数。这意味着你可以传递一个对现有对象的引用,函数将复制这个对象并在列表的末尾添加一个新元素,这个新元素的内容就是传入对象的副本。这个过程通常涉及到调用对象的拷贝构造函数。void push_back (value_type&& val);
第二个版本是 C++11 新增的,接受一个右值引用 (value_type&&
)。这允许使用移动语义来传递临时对象或者可以被"移动"的对象作为参数,这样可以避免不必要的拷贝,提高效率。如果对象支持移动语义,push_back
将利用移动构造函数直接在列表的末尾构建新元素,通常而言,这比拷贝构造过程更高效。
pop_back
void pop_back();
其作用是移除容器末尾的元素。当调用
pop_back()
时,会发生以下几件事情:
- 它会销毁列表末尾的最后一个元素,即调用该元素的析构函数。
- 容器的大小会减少1。
- 如果容器为空(没有元素可以弹出),调用
pop_back()
可能导致未定义的行为,因为试图删除不存在的元素。因此,在调用pop_back()
之前,检查容器是否为空通常是一个好习惯。
push_front
void push_front (const value_type& val); void push_front (value_type&& val);
void push_front (const value_type& val);
这个版本的push_front
接受一个对常量引用 (const value_type&
) 参数。这会将一个元素的副本添加到容器的开始处。这个操作通常会调用元素的拷贝构造函数创建一个新的实例。void push_front (value_type&& val);
这个版本是带有 C++11 右值引用 (value_type&&
) 参数的push_front
。它允许通过移动语义来插入元素,可以避免不必要的对象复制,提高效率。它将直接在容器起始位置构造新元素,通常使用移动构造函数。
pop_front
void pop_front();
移除容器开始处的第一个元素。使用
pop_front()
函数时,将会发生以下情况:
- 它将销毁容器开始处的第一个元素,即调用该元素的析构函数。
- 容器的大小将减少一个元素。
- 如果容器是空的(已经没有元素可以删除了),调用
pop_front()
将导致未定义的行为,因为它会尝试去删除一个不存在的元素。在调用pop_front()
之前,最好检查一下容器是否为空。
insert
| single element (1) |
iterator insert (const_iterator position, const value_type& val);
|
| fill (2) |iterator insert (const_iterator position, size_type n, const value_type& val);
|
| range (3) |template <class InputIterator> iterator insert (const_iterator position, InputIterator first, InputIterator last);
|
| move (4) |iterator insert (const_iterator position, value_type&& val);
|
initializer list (5) iterator insert (const_iterator position, initializer_list<value_type> il);
单个元素(
single element
)的插入
iterator insert (const_iterator position, const value_type& val);
在迭代器
position
指定的位置之前插入一个由val
指定的元素,并返回指向新插入元素的迭代器。填充(
fill
)
iterator insert (const_iterator position, size_type n, const value_type& val);
在迭代器
position
指定的位置之前插入n
个由val
指定的元素,并返回指向第一个被插入的元素的迭代器;如果n
是0,则不会进行插入操作,返回position
。范围(
range
)在迭代器
position
指定的位置之前插入由范围[first, last)
指定的所有元素,并返回指向第一个被插入的元素的迭代器。移动(
move
)
iterator insert (const_iterator position, value_type&& val);
使用右值引用。在迭代器
position
指定的位置之前插入一个val
元素(使用移动语义),并返回指向新插入元素的迭代器。初始化列表(
initializer list
)
iterator insert (const_iterator position, initializer_list<value_type> il);
在迭代器
position
指定的位置之前插入一个由初始化列表il
指定的所有元素,并返回指向第一个被插入元素的迭代器。
erase
iterator erase (const_iterator position); iterator erase (const_iterator first, const_iterator last);
单个元素的删除:
iterator erase (const_iterator position);
这个函数从容器中删除位于position
处的元素,并返回指向被删除元素之后元素的迭代器。如果被删除的元素是容器中的最后一个元素,则返回容器的end()
。范围内元素的删除:
iterator erase (const_iterator first, const_iterator last);
这个函数删除范围[first, last)
内的所有元素,并返回指向最后被删除元素之后元素的迭代器。如果last
等于容器的end()
,那么返回的迭代器也将是end()
。
Iterator
下面是自实现的迭代器类
template<class T, class Ref, class Ptr>
class ListIterator
{
typedef ListNode<T>* PNode;
typedef ListIterator<T, Ref, Ptr> Self;
public:
ListIterator(PNode pNode = nullptr)
:_pNode(pNode)
{}
ListIterator(const Self& l)
{
_pNode = l._pNode;
}
Ref operator*()
{
return _pNode->_val;
}
Ptr operator->()
{
return &_pNode->_val;
}
Self& operator++()
{
_pNode = _pNode->_pNext;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_pNode = _pNode->_pNext;
return tmp;
}
Self& operator--()
{
_pNode = _pNode->_pPre;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
_pNode = _pNode->_pPre;
return tmp;
}
bool operator!=(const Self& l)
{
return _pNode != l._pNode;
}
bool operator==(const Self& l)
{
return _pNode == l._pNode;
}
public:
PNode _pNode;
};
1. 迭代器设计的目的
迭代器的设计目的是提供一种统一的接口来访问容器内的元素,无论容器的内部结构如何。对于链表来说,迭代器隐藏了链表的节点链接逻辑,使得用户可以通过相同的方式来遍历链表、数组等不同的数据结构。
2. 链表迭代器的基本操作
- 递增(
operator++
) : 迭代器提供了递增操作,使其指向链表中的下一个元素。对于双向链表的迭代器,通常提供了两种递增操作:前缀递增(++iter
)和后缀递增(iter++
)。前缀形式直接返回递增后的迭代器,后缀形式则返回递增前的拷贝,然后将自身递增。 - 递减(
operator--
): 对于双向链表的迭代器,它还支持递减操作,即向前遍历元素。同样地,递减操作也有前缀和后缀两种形式。 - 解引用(
operator*
和operator->
) : 解引用操作允许通过迭代器访问当前指向的元素的值。operator*
返回元素的引用,而operator->
允许访问元素成员。
3. 链表迭代器的实现
以双向链表为例,迭代器内部通常保存一个指向当前链表节点的指针。通过移动这个指针来实现递增和递减操作,通过解引用这个指针来访问当前节点的值。
迭代器通常区分为常量和非常量两种,非常量迭代器允许修改指向的元素,而常量迭代器不允许。
4. 开闭原则
迭代器的设计遵循了开闭原则,链表的内部实现可以自由改变,只要迭代器接口保持不变,使用迭代器的代码不需要做任何修改就可以继续正常工作。
5. 迭代器失效
需要注意的一个重要概念是迭代器失效。在对链表进行插入、删除等操作时,某些迭代器可能会失效,即它们不再指向原来的元素。比如,删除一个节点后,指向该节点的迭代器将变得无效。正确处理迭代器失效是使用迭代器时的一个重要考虑点。
vector / list 的常见经典问题
vector和list的区别
vector:
优点: 尾插尾删, 下标随机访问,空间连续,CPU高速缓存的命中率更高
缺点:中间的插入和删除需要挪动数据,效率低
扩容有代价:开新空间+拷贝数据
list:
优点:任意位置O(1)的插入和删除,按需申请和释放
缺点:不支持随机访问(内存碎片问题)
链表的内存碎片问题:
链表和内存碎片化
链表是一种通过节点链接的数据结构,每个节点包含数据和指向下一个(以及可能的上一个)节点的指针。由于链表的节点通常是单独分配的,链表可以引起两种类型的内存碎片:
-
外部碎片:链表节点可以在内存的任何位置被分配,这可能导致内存中未被利用的小空闲区域,也就是外部碎片。外部碎片化会随着多次分配和释放增加,最终可能导致内存利用率下降。
-
内部碎片:链表节点通常比实际需要存储的数据大(因为还需要存储指针),这可能导致为每个节点分配的内存中有未使用的部分,即内部碎片。
空间配置器和内存碎片化
空间配置器是一个系统级或库级的工具,用来管理内存的分配和释放。它通常会提供一些策略来减少内存分配导致的碎片化。例如:
-
内存池:空间配置器可以实现内存池策略,预先分配一大块内存,并从中分配小块内存给用户。当用户请求小块内存时,配置器从池中提取,可以减少外部碎片。
-
固定大小的分配器:对于链表这种结构,配置器可能提供了一种固定大小的分配器,专门用来分配节点大小一致的内存块。这样可以减少内部碎片以及释放内存时导致的外部碎片化。
vector的扩容
不同编译器的实现方式不同。
g++在vector的扩容策略是2倍扩容, 在 v s(visual studio)上是1.5倍的扩容。
并且在扩容后的数据移动方式上,C++11之前是拷贝构造,C++11之后就是移动构造了(通过move()变成右值)