vector和list跟我们学习数据结构中的顺序表和链表是相似的,这两个容器的接口和string类的也是有相似性的。在使用时,可以说使用方法基本一模一样。下面逐一介绍。
Vector篇
1.vector的使用
1.1构造函数
构造函数声明 | 功能说明 |
vector() | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
vector(const vector& x) | 拷贝构造 |
vector(InputIterator first, InputIterator last) | 使用迭代器进行初始化构造 |
代码演示:
void TestVector()
{
vector<int> v1; //无参构造
vector<int> v2(3, 0); //用3个0去初始化
vector<int> v3(v2); //拷贝构造
vector<int> v4(v3.begin(), v3.end()); //迭代器区间构造
}
1.2容量操作
函数名称 | 功能说明 |
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize | 改变vector的size |
reserve | 改变vector的capacity |
对于上述的函数说明和string是一样的,下面这篇文章中有介绍
String类 简单介绍_三分芝士的博客-CSDN博客
1.3迭代器
iterator | 功能说明 |
begin + end | 正向迭代器,begin是第一个数据的位置,end是最后一个元素的下一个位置 |
rbegin + rend | 反向迭代器 |
代码演示:
void TestVector()
{
vector<int> v1(10, 1);
vector<int>::iterator vt = v1.begin();
while (vt != v1.end())
{
cout << *vt << " ";
++vt;
}
cout << endl;
}
1.4修改操作
函数名称 | 功能说明 |
push_back | 尾插 |
pop_back | 尾删 |
insert | 在pos之前插入val |
erase | 删除pos位置的数据 |
swap | 交换两个vector的数据空间 |
operator[] | 下标访问 |
代码演示:
void TestVector()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4); // 1234
v1.pop_back();
v1.pop_back(); // 12
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << ' '; //1 2
}
cout << endl;
}
2.Vector模拟
2.1迭代器失效
(1)其实欲要模拟vector,给先要了解迭代器失效问题,在这里的由于我们的迭代器看作是原生指针T*,那么它失效则是原有的空间被销毁,而使用了这块被释放的空间,造成的后果就是程序崩溃。原有的空间为什么会销毁?在我们插入数据的过程中,如果原空间的大小已经不够存放数据,此时需要扩容,而vector的扩容大都是异地扩容,我们先来看看下面的插入代码:
void insert(iterator pos, const T& x)
{
assert(pos < _finish);
assert(pos >= _start);
if (_finish == _endofstorage)
{
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
iterator end = _finish;
while (end > pos)
{
*end = *(end - 1);
--end;
}
*pos = x;
++_finish;
}
上述代码中由于扩容是异地扩容,会改变原指向的空间,此时pos 并不在这个空间范围内, 此时就会造成迭代器失效,因此可以先将我们的pos位置记下,利用元素之间个数记录, 后在将新空间的start + len 赋值给len
修改后的代码:
void insert(iterator pos, const T& x)
{
assert(pos < _finish);
assert(pos >= _start);
if (_finish == _endofstorage)
{
size_t len = pos - _start;
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
pos = _start + len;
}
iterator end = _finish;
while (end > pos)
{
*end = *(end - 1);
--end;
}
*pos = x;
++_finish;
}
注意对于vector,可能其类型是string,或者其他类型,此时扩容就不能再像原来那样直接memcpy,memcpy是根据内存拷贝,但是vector<string>有两层需要拷贝,代码应是:
void reserve(size_t n)
{
if (n > capacity())
{
size_t Oldsize = size();
iterator tmp = new T[n];
if (_start)
{
//用memcpy会发生浅拷贝的问题
//memcpy(tmp, _start, sizeof(T) * Oldsize);
for (size_t i = 0; i < oldSize; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = tmp + Oldsize;
_endofstorage = _start + n;
}
}
构造函数
//无参构造
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
//这个地方用引用是为了减少拷贝,节省时间, 因为当val不是一个内置类型,而是一个自定义类型的时候
//此时拷贝的代价就会很大,因此用引用节省拷贝的开销
vector(int n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
//迭代器区间构造
template<class Inputiterator>
vector(Inputiterator first, Inputiterator last)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
拷贝构造,赋值重载
//传统写法1
vector(const vector<T>& v)
{
_start = new T[v.size()];
memcpy(_start, v._start, sizeof(T) * v.size());
_finish = _start + v.size();
_endofstorage = _start + v.size();
}
传统写法2
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(v.capacity());
for (const auto& e : v)
{
push_back(e);
}
}
//现代写法
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
vector<T>& operator=(vector<T> v)
{
swao(v);
return *this;
}
析构函数
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
List篇
list的底层实际是双向带头循环链表,可以在常数范围内任意位置进行插入和删除的序列式容器。与其他容器相比list的最大缺陷是不支持任意位置的随机访问。
- List的使用
1.1构造函数
构造函数 | 功能说明 |
list (size_type n, const value_type& val = value_type()) | 构造n个val的list |
list() | 构造空的list |
list(const list& x) | 拷贝构造函数 |
list(InputIterator first, InputIterator last) | 用迭代器区间构造 |
代码演示:
void TestList()
{
list<int> s1; //空构造
list<int> s2(6, 1); //1 1 1 1 1 1
list<int> s3(s2.begin(), s2.end()); //1 1 1 1 1 1
list<int> s4(s3); // 1 1 1 1 1 1
}
1.2容量操作
函数声明 | 功能说明 |
empty | 检测list是否为空 |
size | 返回list中结点的有效个数 |
void TestList()
{
list<int> s(6, 1);
if (s.empty())
{
cout << "此list为空" << endl;
}
else
{
cout << s.size() << endl; //6
}
}
1.3迭代器(重点)
函数声明 | 功能说明 |
begin + end | 迭代器正向访问 |
rbegin + rend | 迭代器反向访问 |
list的迭代器就不能是原生指针了,根据list的特性,它的物理空间不连续,结点与结点之间的物理地址也是不定的,此时如果对它++, --的话,会造成访问错误,所以我们要去封装一个迭代器来完成迭代器的++,--以及*等操作。
template <class T>
struct ListIterator
{
typedef ListNode<T> node;
node* _pnode;
ListIterator(node* p)
:_pnode(p)
{
}
T& operator*()
{
return _pnode->_data;
}
//前置++, --
node& operator++()
{
_pnode = _pnode->_next;
return *this;
}
node& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
node* _pnode;
};
我们发现这种写法对于const版本不行,如果我们直接在这个类中加上一个const的版本的话。
如:
T& operator*()
{
return _pnode->_data;
}
//此时的const迭代器可以解引用,但是这个迭代器本身不能++
//因为const的修饰的是指针本身,而不是对象
const T& operator*() const
{
return _pnode->_data;
}
解决办法:
可以单独实现一个类,将上面的代码拷贝一个类出来,唯一区别就是加上了const
用多个模板参数来解决(最实用)
//此时const成员就传const,非const成员就传非const
template <class T, class Ref>
struct ListIterator
{
typedef ListNode<T> node;
typedef ListIterator<T, Ref> Self;
node* _pnode;
ListIterator(node* p)
:_pnode(p)
{
}
Ref operator*()
{
return _pnode->_data;
}
//前置++, --
Self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
Self& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
};
1.4修改操作
函数说明 | 功能说明 |
push_front | 在头部插入 |
push_back | 在尾部插入 |
pop_front | 在头部删除 |
pop_back | 在尾部删除 |
insert | 在pos前位置插入 |
erase | 删除pos位置上的数据 |
swap | 交换两个list的元素 |
clear | 清空有效数据 |
List模拟
整体代码
namespace gwf
{
template <class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& x)
: _next(nullptr)
, _prev(nullptr)
, _data(x)
{
}
};
template <class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> node;
typedef ListIterator<T, Ref, Ptr> Self;
node* _pnode;
ListIterator(node* p)
:_pnode(p)
{
}
Ref operator*()
{
return _pnode->_data;
}
//ptr 是指向此结构体的指针
Ptr operator->()
{
return &_pnode->_data;
}
//前置++, --
Self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
Self& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
//后置++, --
Self& operator++(int)
{
Self tmp(*this);
_pnode = _pnode->_next;
return tmp;
}
Self& operator--(int)
{
Self tmp(*this);
_pnode = _pnode->_prev;
return tmp;
}
bool operator!=(const Self& it)
{
return _pnode != it._pnode;
}
bool operator==(const Self& it)
{
return _pnode == it._pnode;
}
};
template <class T>
class list
{
typedef ListNode<T> node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
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 emptyInitialize()
{
_head = new node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
emptyInitialize();
}
//这个地方提供两个是因为 当你使用list<int> lt1(4, 3);
//此时编译器无法识别你是迭代器的区间类型还是什么size_t类型
//因此提供一个int的类型可以快速识别
list(int n, const T& val = T())
{
emptyInitialize();
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
list(size_t n, const T& val = T())
{
emptyInitialize();
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
emptyInitialize();
while (first != last)
{
push_back(*first);
++first;
}
}
传统写法
***********************************************************************************
拷贝构造 lt2(lt1)
//list(const list<T>& lt)
//{
// emptyInitialize();
// for (auto& e : lt)
// {
// push_back(e);
// }
//}
赋值 lt2 = lt1
//list<T>& operator=(const list<T>& lt)
//{
// 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);
}
list(const list<T>& lt)
{
emptyInitialize();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
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;
//++_size;
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
void pop_back()
{
erase(--end());
}
iterator insert(iterator pos, const T& x)
{
node* newnode = new node(x);
node* cur = pos._pnode;
node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
return iterator(newnode);
}
iterator erase(iterator pos)
{
assert(pos != end());
node* prev = pos._pnode->_prev;
node* next = pos._pnode->_next;
prev->_next = next;
next->_prev = prev;
delete pos._pnode;
--_size;
return iterator(next);
}
size_t size() const
{
return _size;
}
bool empty() const
{
return _head->_next == _head->_prev;
}
private:
node* _head;
size_t _size;
};
}