🌈个人主页 :@ꪔ小林Y
✨个人专栏 :《C++小白闯关日记》,《C语言小白闯关日记》,《数据结构入门------从原理到实战》,《拾光Linux》
🍀代码信条 :每一行代码都是成长的脚印👣,每一次调试成功都是对坚持的回应
目录
- vector
-
- 1.vector的使用
- 2.vector迭代器失效问题
- 3.例题:杨辉三角
- 4.vector模拟实现
-
- [4.1 reserve 的两种实现方式](#4.1 reserve 的两种实现方式)
- [4.2 完整实现(vector.h)](#4.2 完整实现(vector.h))
- [4.3 测试代码(test.cpp)](#4.3 测试代码(test.cpp))
- List
- list和vector的区别
vector
1.vector的使用
vector 是 C++ 标准模板库(STL)中的动态数组容器,它能够自动管理内存,支持随机访问,并在尾部高效地插入和删除元素。其底层结构由三个指针控制内存块:

_start:指向内存块的起始位置。_finish:指向当前有效数据的末尾。_end_of_storage:指向已开辟内存块的末尾。
size() 返回 _finish - _start,capacity() 返回 _end_of_storage - _start。
下面通过几个测试函数来演示 vector 的常见用法:
cpp
#include<iostream>
#include<vector>
using namespace std;
void Print(const vector<int>& v)
{
// 方式一:下标访问
for (size_t i = 0;i < v.size();i++)
{
cout << v[i] << " ";
}
cout << endl;
// 方式二:范围 for(C++11)
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
// 方式三:迭代器访问
vector<int>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void test_vector1()
{
vector<int> v1; // 默认构造,空 vector
vector<int> v2(10,1); // 构造 10 个值为 1 的元素
vector<int> v3(v2.begin(),v2.end()); // 用迭代器区间构造
string s1("XXXXXX");
vector<int> v4(s1.begin(), s1.end()); // 用 string 的迭代器构造(char 隐式转 int)
vector<int> v5(v3); // 拷贝构造
vector<int> v6 = {1,2,3,4,5}; // 用初始化列表构造(C++11)
vector<int> v7 = {1,2,3,4,5,1,1,1,1};
Print(v2);
Print(v4);
Print(v6);
auto il = { 10,20,30,1,2,3 }; // initializer_list 对象
for (auto e : il)
{
cout << e << " ";
}
cout << endl;
}
void test_vector2()
{
vector<int> v1;
v1.reserve(100); // 提前扩容到 100,避免多次扩容带来的开销
size_t old = v1.capacity();
cout << v1.capacity() << endl;
size_t begin = clock();
for (size_t i = 0;i < 100;i++)
{
v1.push_back(i);
/*if (old != v1.capacity())
{
cout << v1.capacity() << endl;
old = v1.capacity();
}*/
}
size_t end = clock();
cout << end - begin << endl; // 统计插入耗时
vector<int> v2;
v2.resize(100, 1); // 调整大小为 100,新元素用 1 填充
Print(v2);
}
void test_vector3()
{
vector<int> v1 = { 1,2,3,4,5 };
v1.push_back(6); // 尾插
Print(v1);
// 头部插入
v1.insert(v1.begin(),0);
Print(v1);
// 指定位置插入
v1.insert(v1.begin()+3, 0);
Print(v1);
// 删除指定位置元素
v1.erase(v1.begin()+3);
Print(v1);
}
struct AA
{
int _a1 = 1;
int _a2 = 1;
AA(int a1 = 1, int a2 = 1)
:_a1(a1)
, _a2(a2)
{ }
};
void test_vector4()
{
AA aa1 = { 0,0 };
vector<AA> v = { aa1,{1,1},{2,2},{3,3} };//隐式类型转换
auto it = v.begin();
while (it != v.end())
{
cout << it->_a1 << ":" << it->_a2 << endl;
++it;
}
cout << endl;
v.push_back(aa1);
v.emplace_back(aa1);
//差异
v.emplace_back(1,1);//可以传AA对象,也可以传构造AA对象的参数
v.push_back({ 2,2 });//只能传AA对象
}
int main()
{
test_vector1();
return 0;
}
要点总结:
reserve只扩容不改变size,resize会改变size并可能扩容。emplace_back支持直接传入构造参数,在底层直接构造对象,理论上比push_back更高效(省去一次移动/拷贝)。- 用
string的迭代器构造vector<int>时,char会被隐式转换为int。
2.vector迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
对于vector可能会导致其迭代器失效的操作有:
- 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、 assign、push_back等。
- 指定位置元素的删除操作--erase
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理
论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end
的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素
时,vs就认为该位置迭代器失效了。- 注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。
补充说明 :erase 返回的是被删除元素的下一个位置的迭代器,因此正确的删除写法是 it = v.erase(it);,这样即使删除后迭代器失效,也能通过返回值重新获得有效迭代器。此外,insert 也会导致插入位置之后的所有迭代器失效(因为可能发生扩容导致空间重新分配),因此使用 insert 后应使用其返回值更新迭代器。
3.例题:杨辉三角
题目要求生成杨辉三角的前 numRows 行,每一行的首尾元素为 1,中间元素等于上一行相邻两元素之和。这里用 vector<vector<int>> 来存储,外层 vector 的每个元素又是一个 vector,表示一行。

cpp
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv;
// 先确定行数,每一行初始化为空 vector
vv.resize(numRows,vector<int>());
// 再确定每一行的列数,并将所有元素初始化为 1
for(size_t i=0;i<numRows;++i)
{
vv[i].resize(i+1,1);
}
// 从第 3 行开始(下标 2),中间元素等于上一行相邻两元素之和
for(size_t i=2;i<vv.size();++i)
{
for(size_t j=1;j<vv[i].size()-1;++j)
{
vv[i][j]=vv[i-1][j]+vv[i-1][j-1];
}
}
return vv;
}
};
思路解析:
- 先
resize外层 vector,确定行数。 - 再对每一行
resize,确定列数并全部初始化为 1(首尾自然为 1)。 - 从第 3 行开始,用上一行的数据填充中间元素。
4.vector模拟实现
4.1 reserve 的两种实现方式
方式一:使用 memcpy(浅拷贝,有缺陷)

cpp
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
memcpy(tmp, _start, sizeof(T) * sz);
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}

缺陷分析 :memcpy 是逐字节拷贝,属于浅拷贝。对于 int 等内置类型没有问题,但如果 T 是 string 等自定义类型,memcpy 只会拷贝指针(即 string 内部的 _str 指针),导致新旧两个 string 指向同一块堆内存。当旧空间被 delete[] 时,string 的析构函数会释放这块内存,新空间中的 string 就变成了野指针,程序崩溃。
方式二:使用赋值或 swap(深拷贝,正确)
赋值更靠谱,对内置类型可以,对自定义类型也可以

cpp
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
//tmp[i] = _start[i]; // 如果是string,调用string的赋值深拷贝
std::swap(tmp[i], _start[i]); // 如果是string,调用string的交换,交换资源指向
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
说明 :tmp[i] = _start[i] 会调用 string 的赋值运算符,进行深拷贝;而 std::swap(tmp[i], _start[i]) 则交换两个 string 的内部指针,旧空间中的 string 在 delete[] 时析构,释放的是原本属于 tmp 的空间(即新空间中的旧数据),而新空间中的 string 拿到了原本属于 _start 的数据。两种方式都能正确完成深拷贝,但 swap 方式效率更高。

4.2 完整实现(vector.h)
cpp
//vector.h
#include<assert.h>
namespace Lin
{
template<class T>
class vector
{
public:
//typedef T* iteator;
using iterator = T*;
using const_iterator = const T*;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
vector()//会走初始化列表
{}
vector(initializer_list<T> il)
{
reserve(il.size());
for (const auto& e : il)
{
push_back(e);
}
}
//析构实现,防止内存泄漏
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
//传统写法:开空间,拷贝数据都得自己来
//v2(v1),拷贝构造
/*vector(const vector<T>& v)
{
reserve(v.capacity());
for (const auto& e : v)
{
push_back(e);
}
}*/
//v0=v1=v3
//v1=v3
//vector<T>& operator=(const vector<T>& v)
//{
// if (this != &v)
// {
// clear();
// reserve(v.capacity());//若v1空间比v3大,则不用扩容
// for (const auto& e : v)
// {
// push_back(e);
// }
// }
// return *this;
//}
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0;i < n;i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0;i < n;i++)
{
push_back(val);
}
}
//函数模板,迭代器不一定是vector迭代器,也可以是其他容器的迭代器
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
//reserve(last-first);
while (first != last)
{
push_back(*first);
++first;
}
}
//现代写法:开空间,拷贝数据借助其他对象
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
//v1=v3
vector<T>& operator=( vector<T> tmp)
{
swap(tmp);
return *this;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
void clear()
{
_finish = _start;
}
bool empty() const//判空
{
return _start == _finish;
}
//判断空间是否足够,不够则扩容
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
//tmp[i] = _start[i]; // 如果是string,调用string的赋值深拷贝
std::swap(tmp[i], _start[i]); // 如果是string,调用string的交换,交换资源指向
}
//memcpy(tmp, _start, sizeof(T) * sz);
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
//保留前几个数据
void resize(size_t n, T val = T())
{
if (n < size())
{
//删除数据
_finish = _start + n;
}
else
{
//空间不够就扩容,插入数据
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
}
size_t capacity() const
{
return _end_of_storage-_start;
}
size_t size() const
{
return _finish - _start;
}
T& operator[](size_t i)//针对普通对象,可读可写
{
assert(i < size());
return _start[i];
}
const T& operator[](size_t i)const//重载,仅读
{
assert(i < size());
return _start[i];
}
//插入数据
void push_back(const T& x)
{
//满了需要扩容
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
//删除数据
void pop_back()
{
assert(!empty());
--_finish;
}
//pos位置插入值
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)//如果满了就扩容
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
//挪动数据
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
//删除pos位置的值
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
private:
iterator _start=nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}
设计要点:
- 拷贝构造(现代写法) :先构造一个临时对象
tmp,再调用swap交换三个指针,代码简洁且异常安全。 - 赋值运算符 :参数直接传值
vector<T> tmp,利用拷贝构造生成副本,再swap,同样简洁安全。 insert返回迭代器 :因为插入可能触发扩容,导致原pos失效,所以先记录偏移量len,扩容后重新计算pos,最后返回pos供外部更新。erase返回迭代器:返回被删除位置的下一个迭代器,方便外部在循环中安全删除。
4.3 测试代码(test.cpp)
cpp
//test.cpp
#include<iostream>
using namespace std;
#include"vector.h"
namespace Lin
{
void Print(const vector<int>& v)
{
/*for (auto e : v)
{
cout << e << " ";
}
cout << endl;*/
for (size_t i = 0;i < v.size();i++)
{
//v[0]++;不能这样写
cout << v[i] << " ";
}
cout << endl;
}
void test_vector1()
{
Lin::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
Print(v);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
void test_vector2()
{
Lin::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
Print(v);
v.insert(v.begin(), 0);
Print(v);
auto it = v.begin() + 3;//对于vector而言,insert以后,it失效,不能使用(第一个迭代器失效)
v.insert(it, 30);
Print(v);
}
void test_vector3()
{
Lin::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
Print(v);
v.erase(v.begin());
Print(v);
auto it = v.begin() + 2;
//对于vector而言,erase以后, it失效,不能访问,访问结果未定义
v.erase(it);
Print(v);
}
void test_vector4()
{
Lin::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.resize(3);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.resize(20);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
void test_vector5()
{
Lin::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
Lin::vector<int> v2(v1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//bit::vector<int> v3 ({ 10,20,30,40 });
Lin::vector<int> v3 = { 10,20,30,40 };//经典浅拷贝
v1 = v3;//经典浅拷贝
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
Lin::vector<int> v4 = { 10,1 };
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
void test_vector6()
{
Lin::vector<string> v1;
v1.push_back("111111111111");
v1.push_back("111111111111");
v1.push_back("111111111111");
v1.push_back("111111111111");
v1.push_back("111111111111");
for (auto& e : v1)
{
cout << e << " ";
}
cout << endl;
}
}
int main()
{
Lin::test_vector6();
//C++内置类型定义
//C++98
int i = 0;
int j = int();
int k = int(1);
//C++11扩展
int y = {};
int t = { 1 };
int z{};
return 0;
}
测试说明:
test_vector1:验证push_back、Print和范围 for。test_vector2:验证insert后原迭代器失效,需重新获取。test_vector3:验证erase后迭代器失效,访问结果未定义。test_vector4:验证resize缩小和扩大。test_vector5:验证拷贝构造和赋值运算符(现代写法)。test_vector6:验证vector<string>的深拷贝,若reserve使用memcpy此处会崩溃。
List
本文基于 C++ 标准库中的
std::list,手把手带你模拟实现一个带头结点的双向循环链表 ,并深入理解其迭代器、插入删除等核心机制。建议先掌握 C++ 类与模板的基础知识再阅读本文。
list模拟实现
std::list 的底层结构是带头结点的双向循环链表 。在动手实现之前,我们先定义链表节点结构。节点中除了存储数据的 _data 成员外,还需要 _next 与 _prev 两个指针分别指向后继节点和前驱节点。
cpp
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T& x = T())
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{
}
};
说明:节点构造函数中
_data(x)使用了拷贝构造 ,因此T类型需要支持拷贝构造与默认构造(或传入显式初值)。
构造实现
下面给出 list 类的主体框架,包含默认构造、迭代器区间构造、initializer_list 构造、拷贝构造、赋值运算符重载、析构、clear、size 等基础接口。其中 empty_init 用于创建哨兵位头节点,使链表在空状态下也满足 _head->_next == _head 且 _head->_prev == _head。
cpp
template<class T>
class list
{
using Node = list_node<T>;
public:
using iterator = list_iterator<T, T&>;
using const_iterator = list_iterator<T, const T&>;
// using iterator = list_iterator<T>;
// using const_iterator = list_const_iterator<T>;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
void empty_init()//哨兵位
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
list(initializer_list<T> il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
list(size_t n, T val = T())//n个val的构造
{
empty_init();
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
list(int n, T val = T())
{
empty_init();
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
~list()//析构
{
clear();
delete _head;
_head = nullptr;
_size = 0;
}
// 传统写法
// lt2(lt1),构造
list(const list<T>& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
// lt1 = lt3;
list<T>& operator=(const list<T>& lt)
{
if (this != <)
{
clear();
for (auto& e : lt)
{
push_back(e);
}
}
return *this;
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
size_t size() const
{
return _size;
}
private:
Node* _head;
size_t _size = 0;
};
说明:
begin()返回第一个有效节点的迭代器,end()返回哨兵位头节点的迭代器,因此end()指向的位置是最后一个有效节点的下一个位置 ,符合左闭右开区间[begin, end)的语义。- 同时提供
iterator与const_iterator两个类型别名,二者通过模板参数Ref区分,const_iterator解引用后返回const T&,从而禁止通过 const 迭代器修改元素。- 构造函数同时提供
size_t与int两个重载,是为了避免list<int> lt(5, 1)这类调用与迭代器区间构造产生二义性。clear()中通过erase逐个删除节点,erase返回下一个有效位置的迭代器,从而保证循环安全推进。
list迭代器实现
由于 list 的底层是链表,节点在内存中不连续 ,因此不能像 vector 那样直接使用原生指针作为迭代器。我们需要对节点指针进行封装,重载 operator*、operator++、operator--、operator==、operator!= 等运算符,使其行为与指针一致。大家可暂时将迭代器理解成一个指针,该指针指向 list 中的某个节点。大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

cpp
//迭代器实现
template<class T, class Ref>
struct list_iterator
{
using Self = list_iterator<T, Ref>;
using Node = list_node<T>;
Node* _node;
list_iterator(Node* node)
:_node(node)
{
}
// *it = 1
Ref operator*()
{
return _node->_data;
}
// ++it,迭代器++
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
// --it
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
//判断两个迭代器相不相等
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
说明:
- 模板参数
Ref用于区分普通迭代器与 const 迭代器:当Ref = T&时,operator*返回可修改的引用;当Ref = const T&时,返回只读引用,从而禁止通过 const 迭代器修改元素。operator++与operator--都提供了前置与后置两个版本,后置版本通过返回临时对象来区分,符合 C++ 迭代器规范。- 迭代器内部只保存一个
_node指针,比较两个迭代器是否相等,本质是比较它们指向的节点地址是否相同。
erase实现
erase 用于删除 pos 位置的节点。删除后,pos 迭代器指向的节点已被释放,继续使用该迭代器属于未定义行为 ,即所谓的「迭代器失效」。因此 erase 需要返回被删除节点的下一个有效位置的迭代器,方便调用方安全地继续遍历。
cpp
iterator erase(iterator pos)//迭代器失效
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
// 将前驱与后继直接相连,跳过当前节点
prev->_next = next;
next->_prev = prev;
delete cur;
--_size;
// 返回下一个有效位置的迭代器
return next;
}
说明:删除节点后,
pos本身已失效,但next指向的节点仍然有效,因此返回next可让clear()等场景安全地继续循环。
insert实现
insert 用于在 pos 位置之前插入一个新节点。与 vector 不同,list 的插入不需要搬移元素 ,只需修改相邻节点的指针即可,因此时间复杂度为 O(1)。
cpp
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
push_back模拟实现

cpp
//尾插
void push_back(const T& x)
{
Node* newnode = new Node(x);
Node* tail = _head->_prev;
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
实现insert和erase之后直接一复用即可
cpp
void push_back(const T& x)//尾插
{
insert(end(), x);
}
void push_front(const T& x)//头插
{
insert(begin(), x);
}
void pop_back()//尾删
{
erase(--end());
}
void pop_front()//头删
{
erase(begin());
}
测试
cpp
#include<iostream>
using namespace std;
#include"List.h"
void test_list1()
{
bit::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
bit::list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//支持迭代器就支持范围for
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list2()
{
bit::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_front(-1);
lt.push_front(-2);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_back();
lt.pop_back();
lt.pop_front();
lt.pop_front();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
cout << lt.size() << endl;
}
void Print(const bit::list<int>& lt)
{
bit::list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
//*it = 1;//const迭代器的特点就是不能给常量赋值
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list3()
{
bit::list<int> lt1 = { 1,2,3,4,5,6 };
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
bit::list<int> lt2(lt1);
for (auto e : lt2)
{
cout << e << " ";
}
cout << endl;
bit::list<int> lt3 = { 10,20,30 };
lt1 = lt3;
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
Print(lt1);
}
int main()
{
test_list3();
return 0;
}
list和vector的区别
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:
| vector | list | |
|---|---|---|
| 底层访问 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
| 插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
| 迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
| 迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
🎊到这儿,《C++ STL 容器深度剖析:vector 与 list 的模拟实现及对比》的内容就结束了。如果文中有表述不准的地方,或是你有更清晰的理解思路,强烈欢迎在评论区留言交流------ 技术路上多碰撞,才能更快进步
觉得内容对你有帮助的话,别忘了点赞❤️➕收藏🌟,方便后续回顾复习;想跟着一起系统学习数据结构的朋友,也可以点击关注,下一期我们会聚焦更进一步的学习。不见不散✌️



