- STL六大组件:仿函数、空间配置器、算法、容器、迭代器、配接器。
- 本节内容介绍的是STL六大组件中的容器,容器分为序列式容器和关联式容器,而本节内容主要介绍序列式容器。
一、vector
1.vector的定义及使用
- 定义: vector是可以改变大小数组的顺序容器。
- 使用
#include <iostream>
#include <vector>
using namespace std;
void testVector1()
{
vector<int> vc;
vc.push_back(1); //尾插
vc.push_back(2); //尾插
vc.push_back(3); //尾插
vc.push_back(4); //尾插
size_t n = vc.size(); //计算vector的大小
//for循环遍历vector方法一:
cout << "方法一for循环遍历:" << ' ';
for (int i = 0; i < n; ++i)
{
cout << vc[i] << ' ';
}
cout << endl;
//迭代器遍历vector方法二:
cout << "方法二迭代器遍历:" << ' ';
auto it = vc.begin();
while (it != vc.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
//范围for遍历vector方法三:
cout << "方法三范围for遍历:" << ' ';
for (auto e : vc)
{
cout << e << ' ';
}
cout << endl;
}
void testVector2()
{
//初始化方法一:创建10个5的数组
vector<int> vc(10, 5);
cout << "vc:" << ' ';
for (auto e : vc)
cout << e << ' ';
cout << endl;
//初始化方法二:用迭代器区间初始化
vector<int> v1(vc.begin(), vc.end());
cout << "v1:" << ' ';
for (auto e : v1)
cout << e << ' ';
cout << endl;
vector<int> v2(++v1.begin(), --v1.end());
cout << "v2:" << ' ';
for (auto e : v2)
cout << e << ' ';
cout << endl;
}
void testVector3()
{
vector<int> vc;
vc.push_back(1); //尾插
vc.push_back(2); //尾插
vc.push_back(3); //尾插
vc.push_back(4); //尾插
cout << "正向遍历vector:" << ' ';
for (auto e : vc)
cout << e << ' ';
cout << endl;
//反向遍历vector
cout << "反向遍历vector:" << ' ';
vector<int>::reverse_iterator it = vc.rbegin();
while (it != vc.rend())
{
cout << *it << ' ';
++it;
}
cout << endl;
}
void testVector4()
{
int a[] = { 1, 2, 3, 4 };
//用迭代器初始化,指针是天然的迭代器
vector<int> vc(a, a + sizeof(a) / sizeof(int));
vector<int>::iterator pos = find(vc.begin(), vc.end(), 2);
cout << "2在数组中的位置:" << pos - vc.begin() << endl;
cout << "原数组:" << ' ';
for (auto e : vc)
cout << e << ' ';
cout << endl;
vc.insert(pos, 10);
cout << "在pos位置插入10:" << ' ';
for (auto e : vc)
cout << e << ' ';
cout << endl;
pos = find(vc.begin(), vc.end(), 4);
vc.erase(pos);
cout << "删除pos位置后:" << ' ';
for (auto e : vc)
cout << e << ' ';
cout << endl;
}
int main()
{
cout << "testVector1() : " << endl;
testVector1();
cout << endl;
cout << "testVector2() : " << endl;
testVector2();
cout << endl;
cout << "testVector3() : " << endl;
testVector3();
cout << endl;
cout << "testVector4() : " << endl;
testVector4();
cout << endl;
return 0;
}
2.vector的模拟实现
#include <iostream>
#include <assert.h>
using namespace std;
namespace sheena
{
template<class T>
class my_vector
{
public:
typedef T* iterator;
//构造函数
my_vector()
:_start(nullptr)
,_end(nullptr)
,_end_of_storage(nullptr)
{}
//拷贝构造函数
my_vector(const my_vector<T>& mv)
{
_start = new T[mv.size()];
memcpy(_start, mv._start, sizeof(T) * mv.size());
_end = _start + mv.size();
_end_of_storage = _start + mv.capacity();
}
//析构函数
~my_vector()
{
if (_start)
{
delete[]_start;
_start = _end = _end_of_storage = nullptr;
}
}
size_t size()const
{
return _end - _start;
}
size_t capacity()const
{
return _end_of_storage - _start;
}
T& operator[](size_t pos)
{
return _start[pos];
}
const T& operator[](size_t pos)const
{
return _start[pos];
}
void swap(my_vector<T>& mv)
{
swap(_start, mv._start);
swap(_end, mv._end);
swap(_end_of_storage, mv._end_of_storage);
}
my_vector& operator=(my_vector<T> mv)
{
swap(mv);
return *this;
}
//不仅修改了size,而且还可能修改capacity
my_vector resize(size_t n, const T& val = T())
{
if (n < size())
{
_end = _start + n;
return;
}
if (n > capacity())
_end_of_storage = n;
while (_end != _start + n)
{
*_end = val;
++_end;
}
}
//只修改capacity
void reserve(size_t n)
{
if (n > capacity)
{
size_t sz = size();
T* tmp = new T[n];
for (size_t i = 0; i < sz; ++i)
tmp[i] = _start[i];
delete[]_start;
_start = tmp;
_end = _start + sz;
_end_of_storage = _start + n;
}
}
iterator begin()
{
return _start;
}
iterator end()
{
return _end;
}
void insert(iterator& pos, const T* x)
{
assert(pos < _end && pos >= _start);
if (_end == _end_of_storage)
{
size_t n = pos - _start;
size_t newcapacity = capacity() == 0 ? 2 : 2 * capacity();
reserve(newcapacity);
pos = _start + n;
}
iterator end = _end - 1;
while (end > pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_end;
}
void erase(iterator pos)
{
assert(pos < _end && pos >= _start);
iterator begin = pos + 1;
while (begin < _end)
{
*(begin - 1) = *begin;
++begin;
}
--_end;
}
void push_back(const T& x)
{
insert(end(), x);
}
void pop_back()
{
assert(_end >= _start);
erase(--end());
}
private:
iterator _start;
iterator _end;
iterator _end_of_storage;
};
}
二、list
1.list的定义及使用
- 带头循环链表
#include <iostream>
#include <list>
using namespace std;
//迭代器遍历:
void Print1(list<int> lst)
{
list<int>::iterator it = lst.begin();
while (it != lst.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
}
//范围for遍历:
void Print2(list<int> lst)
{
for (auto e : lst)
cout << e << ' ';
cout << endl;
}
void testList1()
{
list<int> lst;
lst.push_back(1);//尾插
lst.push_back(2);//尾插
lst.push_back(3);//尾插
Print1(lst);
lst.push_front(9);//头插
lst.push_front(8);//头插
lst.push_front(7);//头插
Print1(lst);
lst.pop_back();//尾删
Print2(lst);
lst.pop_front();//头删
Print2(lst);
}
void testList2()
{
list<int> lst(4, 100);//创建4个100的双向链表
cout << "去重之前的lst:" << ' ';
Print1(lst);
lst.unique();//去重
cout << "去重之后的lst:" << ' ';
Print2(lst);
lst.push_back(2);
lst.push_back(9);
lst.push_back(99);
lst.push_front(500);
lst.push_front(22);
lst.push_front(500);
lst.push_back(99);
//lst.unique();
cout << "排序之前的lst:" << ' ';
Print2(lst);
lst.sort();//排序
cout << "排序之后的lst:" << ' ';
Print1(lst);
cout << "去重之后的lst:" << ' ';
lst.unique();//去重前提是必须有序
Print1(lst);
}
//迭代器失效
void testList3()
{
list<int> lst;
lst.push_back(2);
lst.push_back(9);
lst.push_back(99);
lst.push_front(500);
lst.push_front(22);
Print1(lst);
list<int>::iterator it = lst.begin();
while (it != lst.end())
{
//迭代器失效
lst.erase(it);//删除节点
++it;
}
Print2(lst);
}
void testList4()
{
list<int> lst;
lst.push_back(2);
lst.push_back(9);
lst.push_back(99);
lst.push_front(500);
lst.push_front(22);
cout << "删除lst节点之前:" << ' ';
Print1(lst);
list<int>::iterator it = lst.begin();
while (it != lst.end())
lst.erase(it++);//删除节点
cout << "删除lst节点之后:" << ' ';
Print2(lst);
}
int main()
{
cout << "testList1:" << endl;
testList1();
cout << endl;
cout << "testList2:" << endl;
testList2();
cout << endl;
cout << "testList4:" << endl;
testList4();
cout << endl;
return 0;
}
2.list的模拟实现
#include <iostream>
using namespace std;
namespace sheena
{
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)
{}
};
template <class T, class Ref, class Ptr>
struct _list_iterator
{
typedef _list_node<T> node;
typedef _list_iterator<T, Ref, Ptr> Self;
node* _node;
_list_iterator(node* node)
:_node(node)
{}
//内置类型
Ref operator*()
{
return _node->_data;
}
//自定义类型
Ptr operator->()
{
return &_node->_data;
}
//前置++
Self& operator++()
{
_node = _node->_next;
return *this;
}
//后置++
Self operator++()
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
//前置--
Self& operator--()
{
_node = _node->_prev;
return *this;
}
//后置--
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
//it1 != it2
bool operato!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
};
template <class T>
class my_list
{
typedef _list_node<T> node;
public:
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<const T, const T&, const T*> const_iterator;
const_iterator begin()const
{
return const_iterator(_head->_next);
}
const_iterator end()const
{
return const_iterator(_head);
}
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
my_list()//构造函数
{
_head = new node<T>();
_head->_next = _head;
_head->_prev = _head;
}
my_list(const my_list<T>& ls)//拷贝构造函数
{
if (_head != ls._head)
{
_head->_next = _head;
_head->_prev = _head;
auto it = ls.begin();
while (it != ls.end())
{
push_back(*it);
++it;
}
}
}
my_list<T>& operator=(my_list<T> ls)//赋值函数
{
swap(_head, ls._head);
return *this;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
~my_list()//析构函数
{
clear();
delete _head;
_head = nullptr;
}
void insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = pos._node->_prev;
node* newnode = new node(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
}
void push_back(const T& x)
{
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
iterator erase(iterator pos)
{
node* cur = pos->_node;
node* prev = cur->_prev;
node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
cur = nullptr;
return iterator(next);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
private:
node* _head;
};
}
3.vector和list对比
(1)vector底层是动态顺序表,一段连续的空间。而list底层是带头结点的双向循环链表
(2)vector支持随机访问,访问某个元素的效率是O(1)。而list不支持随机访问,放入元素的效率是O(N)。
(3)vector尾插尾删的效率是O(1),任意位置插入和删除的效率低,时间复杂度为O(N),插入时可能需要增容(开辟新空间,拷贝元素,释放旧空,代价大)。而list尾插尾删、头插头删、任意位置的插入、删除效率都是O(1),不支持随机访问。
(4)vector底层是连续的空间,不容易造成内存碎片,空间利用率高,缓存利用率高。list底层是节点的动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低。
(5)vector的迭代器是原生态指针。而list的迭代器是对原生态指针进行的封装。
(6)vector在插入元素时,要给所有迭代器重新赋值,因为插入元素可能会导致重新扩容,导致原来的迭代器失效,删除时,当前迭代器需要重新赋值否则会失效。而list插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不会受影响。
(7)如果需要随机访问时,不关心插入、删除效率时就是用vector。如果有大量插入和删除操作,不需要随机访问时就用list。
三、deque
1.deque的定义及接口
- deque是双端队列,底层是一段假象的连续空间,实际是分段连续的空间,而deque的迭代器维护了假象的连续。(其迭代器包含cur、first、last、node。有start迭代器和finish迭代器)
函数声明 | 接口说明 |
---|---|
size() | 返回deque的有效元素的个数 |
empty() | 返回deque是否为空,若是空,返回true;若不空,返回false |
operator[] | 随机访问deque元素 |
front() | 返回deque首元素的引用 |
back() | 返回deque最后一个元素的引用 |
push_front(val) | deque的头插 |
push_back(val) | deque的尾插 |
insert(pos, val) | 在deque中pos位置插入val |
pop_front() | deque的头删 |
pop_back() | deque的尾删 |
erase(pos) | 删除deque中pos位置的值 |
swap() | 交换deque中的内容 |
clear() | 将deque的元素清空 |
2.接口的使用
#include <iostream>
#include <deque>
using namespace std;
void Print(deque<int> dq)
{
for (auto e : dq)
cout << e << " ";
cout << endl;
}
void Print2(deque<int> dq)
{
deque<int>::iterator it = dq.begin();
while (it != dq.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void Print3(deque<int> dq)
{
deque<int>::reverse_iterator rit = dq.rbegin();
while (rit != dq.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
void testDeque1()
{
cout << "用10个6构造deque:" << " ";
deque<int> dq1(10, 6);
Print2(dq1);
cout << "用数组构造deque:" << " ";
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
deque<int> dq2(arr, arr + sizeof(arr) / sizeof(arr[0]));
Print(dq2);
cout << "用dq2拷贝构造dq3:" << " ";
deque<int> dq3(dq2);
Print(dq3);
cout << "pop_front():" << " ";
dq3.pop_front();
Print(dq3);
cout << "pop_back():" << " ";
dq3.pop_back();
Print(dq3);
cout << "push_back(val):" << " ";
dq3.push_back(50);
Print(dq3);
cout << "push_front(val):" << " ";
dq3.push_front(122);
Print(dq3);
cout << "insert(pos, val):" << " ";
dq3.insert(dq3.begin () + 5, 500);
Print(dq3);
cout << "erase(pos):" << " ";
dq3.erase(dq3.begin() + 3);
Print2(dq3);
cout << "size():" << " ";
cout << dq3.size() << endl;
cout << "front():" << " ";
cout << dq3.front() << endl;
cout << "back():" << " ";
cout << dq3.back() << endl;
cout << "clear()清空后dq3的size是:" << " ";
dq3.clear();
cout << dq3.size() << endl;
}
void testDeque2()
{
int arr[] = {1,2,3,4,5,6,7,8,9 };
deque<int> dq(arr, arr + sizeof(arr) / sizeof(arr[0]));
cout << "operator[]访问:" << " ";
cout << "dq[3] = " << dq[3] << endl;
deque<int> dq2(10, 6);
cout << "swap()之前的dq和dq2:" << endl;
cout << "dq :" << " ";
Print2(dq);
cout << "dq2 :" << " ";
Print(dq2);
cout << "swap()之后的dq和dq2:" << endl;
dq.swap(dq2);
cout << "dq :" << " ";
Print2(dq);
cout << "dq2 :" << " ";
Print(dq2);
cout << "反向打印deque:" << " ";
Print3(dq2);
}
int main()
{
cout << "testDeque1:" << endl;
testDeque1();
cout << endl;
cout << "testDeque2:" << endl;
testDeque2();
cout << endl;
return 0;
}
- swap()是交换两个deque。
- insert(pos, val)和erase(pos)中的pos是迭代器。
3.deque的总结
- 在序列式容器中,如果只是简单的存储元素,使用vector即可,如果对任意位置的插入、删除比较多,使用list即可。而deque的最大的应用就是作为标准库中的stack和queue的底层结构。
四、string
1.string的定义及使用
#include <iostream>
#include <string>
using namespace std;
void testString1()
{
string s1;//无参数初始化
string s2("hello");//带参初始化
cout << "带参初始化s2:" << s2 << endl;
string s3(s2);//拷贝构造
cout << "拷贝构造s3:" << s3 << endl;
cout << "s2的size为: " << s2.size() << endl;
cout << "s2的length为: " << s2.length() << endl;
cout << "s2的capacity为:" << s2.capacity() << endl;
cout << "clear()清空s2之后的size为: " << " ";
s2.clear();
cout << s2.size() << endl;
cout << "clear()清空s2之后的capacity为:" << " ";
cout << s2.capacity() << endl;
}
void testString2()
{
string s = "hello sheena";
cout << "初始状态:" << endl;
cout << "size: " << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << endl;
cout << "resize(n)之后状态(增容):" << endl;
string s1(s);
s1.resize(20);
cout << "s1: " << s1 << endl;
cout << "size: " << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << endl;
string s2(s);
s2.resize(20, 'q');
cout << "resize(n, c)之后状态(增容):" << endl;
cout << "s2: " << s2 << endl;
cout << "size: " << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
cout << endl;
cout << "resize(n)之后状态(缩容):" << endl;
string s3(s);
s3.resize(10);
cout << "s3: " << s3 << endl;
cout << "size: " << s3.size() << endl;
cout << "capacity:" << s3.capacity() << endl;
cout << endl;
}
void Print1(const string s)
{
for (int i = 0; i < s.size(); ++i)
cout << s[i] << " ";
cout << endl;
}
void Print2(const string s)
{
cout << endl;
cout << "遍历字符串方法二顺序遍历:" << " ";
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
cout << "遍历字符串方法二逆序遍历:" << " ";
auto rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
void Print3(const string s)
{
for (auto& e : s)
cout << e << " ";
cout << endl;
}
int main()
{
cout << "testString1:" << endl;
testString1();
string s = "hello sheena!";
cout << "遍历字符串方法一for循环:" << " ";
Print1(s);
cout << endl;
cout << "遍历字符串方法二迭代器: " << " ";
Print2(s);
cout << endl;
cout << "遍历字符串方法三范围for:" << " ";
Print3(s);
cout << endl;
cout << endl;
cout << "testString2:" << endl;
testString2();
return 0;
}
- size()和length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致。一般情况下基本都是用size()。
- clear()知识将string中有效字符清空,不改变底层空间大小。(上面的样例中clear()之后,size变成了0,而capacity依旧是15)
- resize(size_t n)与resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时。resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时。如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。(如上面的样例所示,缩容之后capacity不变,改变的是size)
- reserve(size_t res_arg = 0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserve不会改变容量大小。
2.string的模拟实现
//模拟实现一个简单的string类
#include <iostream>
using namespace std;
namespace sheena
{
class my_string
{
public:
//构造函数
my_string(const char* str = "")//字符串""中有一个'\0'
:_str(new char[strlen(str) + 1])//+1是为了加'\0'
{
if (nullptr == str)
str = "";
strcpy(_str, str);//strcpy可以拷贝'\0'
}
//拷贝构造函数
my_string(const my_string& s)
:_str(nullptr)
{
my_string tmp(s._str);
swap(tmp._str, _str);
}
//赋值函数
my_string& operator=(my_string s)
{
swap(s._str, _str);
return *this;
}
//析构函数
~my_string()
{
if (_str)
{
delete[]_str;
_str = nullptr;
}
}
private:
char* _str;
};
}
3.string和vector对比
(1)string中有’\0’,vector< char >中没有’\0’。
(2)string常要比较大小,vector< char >没必要比较大小。
(3)string重载输入输出,vector< char >用push_back等。
五、resize和reverse的区别
- resize既分配了空间,也创建了对象。reserve只修改capacity大小,不修改size大小,resize既修改了size大小,也可以修改capacity的大小(当resize(n)中的n < capacity时)。
- resever:仅仅改变空间(capacity)(如果已经知道需要多少空间直接开空间),提高效率,只有增容没有缩容(开空间时避免增容)。
- resize:开空间+初始化。不仅改变空间(capacity),而且放了n个值进去(即改变了size),size可以缩小也可以放大。