文章目录
前言
STL是C++标准库的重要组成部分,不仅是一个可以复用的组件库,而且是一个包罗数据结构与算法的软件框架。
STL组件1:容器
1.1 string
string
是表示字符串的字符串类,该类的接口与常规容器的接口基本相同,在使用string
类时,需要包含#include <string>
- string的六种定义方式:
string str; //""
string str2("123"); // "123"
string str3 = "abc"; // "abc"
string str4("0123456789", 5); //"01234"
string cpy(str3); // "abc"
string str5(str4, 2, 2); //"23"
string str6(10, 'a'); //"aaaaaaaaaa"
- string的三种遍历方式
string str("0123456789");
//1. 迭代器
string::iterator it = str.begin();
while (it != str.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//2. operator[]简写
for (int i = 0; i < str.size(); ++i)
{
cout << str[i] << " ";
}
//3. 范围for遍历: 支持读写, 如果需要修改,则接收类型为引用类型
// 底层通过迭代器实现
for (auto& ch : str)
{
cout << ch << " ";
ch = 'X';
}
cout << endl;
- string接口函数
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符 |
append | 在字符串后追加一个字符串 |
operator+= | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find+npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
函数接口代码: |
string s;
s.push_back('a');//a
s.append(2, 'b');//abb
s.append("cde");//abbcde
string s2;
s2.append(s);//abbcde
string s3;
s3.append(s, 3, 2);//cd
char strArr[] = "1234";
s3.append(strArr, strArr + 2); //cd12
s3.append(s2.begin(), s2.end()); // cd12abbcde
string s4;
//operator+=使用的最多
s4 += '1'; // 1
s4 += "234"; // 1234
s4 += s; //1234abbcde
const char* ptr = s.c_str();data, c_str功能一致,都是返回c风格的字符串,返回字符串的首地址
const char* ptr2 = s.data();
string str = "aaaaaaaaa";
//find如果找不到,返回npos:static const size_t nops = -1 --> string的静态成员
size_t pos = str.find('b'); //正向查找,找到第一个匹配的位置就结束
size_t pos2 = str.rfind('a');//反向查找,找到第一个匹配的位置就结束
//可以用此来查找文件的类型 从后向前找寻找'.'的位置
//substr(pos, len): 如果len大于从pos到结束位置的字符串长度,则把剩余字符串全部截取出来
string file1 = "test.tar.gz.zip";
size_t pos = file1.rfind('.');
string str2 = file1.substr(pos + 1, file1.size() - pos - 1);
string str3 = file1.substr(pos + 1);
pos = string::npos;
函数名称 | 功能说明 |
---|---|
insert | 插入 |
erase | 删除 |
assign | 重新赋值 |
replace | 替换 |
swap | 交换 |
s4.insert(0, s3);
s4.insert(0, s3, 7, 3);
s4.insert(s4.end(), 3, '1');
s4.insert(s4.end(), strArr + 1, strArr + 3);
s4.assign("11111"); // 11111
s4 = "11111";
s4.erase(0, 2); // 111
s4.erase(s4.begin()); //11
s4.erase(s4.end());
s4.erase(s4.begin(), s4.end());
string s = "0123456789";
s.replace(3, 5, "aa"); //012aa89
s.replace(s.begin() + 1, s.end() - 1, "0"); //009
string s2 = "abc";
s2.swap(s); // s2: 009 s: abc
swap(s2, s); //全局string类型的swap函数: 内部调用string的成员函数swap完成交换
reverse(str.begin(),str.end());//逆置
- string关系运算符
string s = "9";
string s2 = "123";
string s3 = "1234";
bool ret = s > s2;
ret = s2 > s3;
ret = s > s3;
string s;
//cin >> s;
getline(cin, s);
cout << s;
getline(cin, s, ',');//遇见第一个逗号停止读取
getline(cin, s, ',');
getline(cin, s, ',');
6. string类的模拟实现
class String
{
public:
string 迭代器: 通过指针实现
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
//第一个元素的位置
return _str;
}
iterator end()
{
//最后一个元素的下一个位置
return _str + _size;
}
const_iterator begin() const
{
//第一个元素的位置
return _str;
}
const_iterator end() const
{
//最后一个元素的下一个位置
return _str + _size;
}
string的基本函数
//构造函数
String(const char* str="")
{
_size = strlen(str);
_str = new char[_size + 1];
strcpy(_str, str);
_capacity = _size;
}
//拷贝构造
String(const String& str)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
//调用构造函数
String tmp(str._str);
Swap(tmp);
}
void Swap(String& str)
{
swap(_str, str._str);
swap(_size, str._size);
swap(_capacity, str._capacity);
}
//赋值运算符
String& operator=(String str)
{
Swap(str);
return *this;
}
//返回C格式字符串
const char* c_str() const
{
return _str;
}
//析构函数
~String()
{
if (_str)
{
delete[] _str;
_size = _capacity = 0;
_str = nullptr;
}
cout << "~String" << endl;
}
//尾插操作
void pushBack(const char& ch)
{
insert(_size, ch);
}
//字符串后追加一个字符
void Append(const char* str)
{
insert(_size, str);
}
//更新容量
void reserve(size_t n)
{
if (n > _capacity)
{
//开空间:+1--->存放\0
char* tmp = new char[n + 1];
//拷贝
strcpy(tmp, _str);
//释放原有空间
delete[] _str;
_str = tmp;
//更新容量
_capacity = n;
}
}
char& operator[](size_t pos)
{
if (pos < _size)
return _str[pos];
}
const char& operator[](size_t pos) const
{
if (pos < _size)
return _str[pos];
}
size_t size() const
{
return _size;
}
String& operator+=(const char& ch)
{
pushBack(ch);
return *this;
}
String& operator+=(const char* str)
{
Append(str);
return *this;
}
//两种插入操作
void insert(size_t pos, const char& ch)
{
if (pos > _size)
return;
//检查容量
if (_size == _capacity)
{
size_t newC = _capacity == 0 ? 15 : 2 * _capacity;
reserve(newC);
}
//移动元素[pos, _size]: 从后向前移动,首先移动最右端的字符,防止覆盖
size_t end = _size + 1;
//end >= pos: 当pos = 0时, 死循环,访问越界
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
//插入
_str[pos] = ch;
++_size;
}
void insert(size_t pos, const char* str)
{
if (pos > _size)
return;
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//移动元素[pos, _size]
size_t end = _size + len;
//size --> size + len, pos ---> pos + len
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
//插入
for (int i = 0; i < len; ++i)
{
_str[i + pos] = str[i];
}
_size += len;
}
void resize(size_t n, const char& ch = '\0')
{
if (n > _capacity)
{
reserve(n);
}
if (n > _size)
{
memset(_str + _size, ch, n - _size);
}
_size = n;
_str[_size] = '\0';
}
void popBack()
{
erase(_size - 1, 1);
}
void erase(size_t pos, size_t len)
{
if (pos < _size)
{
//判断删除的长度是否大于从pos位置开始的剩余字符串的长度
if (pos + len >= _size)
{
_size = pos;
_str[_size] = '\0';
}
else
{
//移动元素[pos + len, size] ---> [pos, size - len]: 从前向后移动
for (int i = pos + len; i <= _size; ++i)
{
_str[pos++] = _str[i];
}
_size -= len;
}
}
}
size_t find(const char* str)
{
char* ptr = strstr(_str, str);
if (ptr)
return ptr - _str;
else
return npos;
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
};
const size_t String::npos = -1;
String operator+(const String& s, const String& str)
{
String ret(s);
ret += str.c_str();
return ret;
}
String operator+(const String& s, const char* str)
{
String ret(s);
ret += str;
return ret;
}
1.2 vector
vector表示可变大小数组的序列容器,它在访问元素时更加高效
1. vector的初始化
vector<int> v;//无参构造
vector<char> v2;//无参构造
vector<string> v3;//无参构造
vector<int> v4(10, 5);//构造并初始化10个5
string s2 = "0123456789";
vector<char> v5(s2.begin(), s2.end());//使用迭代器进行初始化构造
vector<char> v6(v5);//拷贝构造
2. vector的迭代器
string s = "0123456789";
vector<char> v(s.begin(), s.end());
cout << "reverse_iterator" << endl;
vector<char>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
vector<char>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
*it = 'a';
++it;
}
cout << endl;
3. vector的遍历
for (char& ch : v)
{
cout << ch << " ";
ch = 'b';
}
cout << endl;
for (int i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
v[i] = 'c';
}
cout << endl;
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
erase | 删除元素 |
resize | 改变vector的size |
reserve | 改变vector的capacity |
- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的
- resize,如果扩大后不给val的参数,则使用默认值,内置类型为0,自定义类型调用无参构造
- reserve 只能够变大,不能变小
- earse会出现迭代器失效的情况,因此在删除一个位置的元素,一定要返回它的下一个元素的地址
vector<int> v;
size_t sz = v.size();
size_t cap = v.capacity();
v.reserve(100);
cap = v.capacity();
v.reserve(10);
cap = v.capacity();
容量空间 | 接口说明 |
---|---|
find | 查找 |
insert | 在pos之前插入val |
swap | 交换两个vector数据空间 |
erase | 删除元素 |
push_back | 尾插 |
push_pop | 尾删 |
operator[ ] | 像数组般访问 |
emplace | 构造后插入 |
emplace_back | 构造后尾插 |
使用find查找3所在位置的iterator
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
在pos位置之前插入30
v.insert(pos, 30);
vector<int> v(5, 2);
vector<int> v2(5, 1);
swap(v, v2); //内部调用vector的成员函数swap完成
v.swap(v2);
关于匿名对象来收缩空间 vector<int>(v).swap(v); ---其中(v)是用来创建一个匿名对象x,之后调用x和v进行交换以此来缩短v的容量
v.insert(v.begin(),10, 5);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
it = v.erase(it);
}
vector<B> v;
B b(1, 2);
v.insert(v.begin(), b); //插入
v.emplace(v.begin(), 3, 4); // 构造 + 插入
B b2(5, 6);
v.emplace(v.begin(), b2);
v.push_back(b2);
v.emplace_back(b);
v.emplace_back(7, 8); //构造 + 尾插
迭代器失效
迭代器指向的位置,空间被释放或者变成一个不可访问的位置
- 空间发生变为,会导致原来的迭代器失效–>
push_back, insert,reserve,resize,assign
- 位置错位,最主要的是
erase
- 解决的方式:重新获取迭代器,非删除接口
begin,end
;使用earse
时,直接获取其返回值,其返回值指向被删除元素的下一个元素的迭代器(潜在的问题是:如果传如的迭代器为最后一个元素迭代器,其获取的返回值为end迭代器,也是一个不能访问的问题)
vector的分析及模拟实现
template <class T>
class Vector
{
public:
Vector()
:_start(nullptr)
, _finish(nullptr)
, _eos(nullptr)
{}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _eos - _start;
}
void pushBack(const T& val)
{
//1 检查容量
if (_finish == _eos)
{
size_t newC = capacity() == 0 ? 1 : 2 * capacity();
reserve(newC);
}
//2 插入
*_finish = val;
//3 更新size
++_finish;
}
void reserve(size_t n)
{
//只增加容量
if (n > capacity())
{
size_t oldSize = size();
//1. 开空间
T* tmp = new T[n];
//2. 拷贝: 内存拷贝, 浅拷贝
//memcpy(tmp, _start, sizeof(T)* size());
//2. 深拷贝: 调用T类型的赋值运算符
for (int i = 0; i < size(); ++i)
{
tmp[i] = _start[i];
}
//3. 释放原有空间
delete[] _start;
//4. 更新空间指向, 容量
_start = tmp;
_finish = _start + oldSize;
_eos = _start + n;
}
}
//operator[]: 可读可写
T& operator[](size_t pos)
{
if (pos < size())
return _start[pos];
}
//operator[]: 只读
const T& operator[](size_t pos) const
{
if (pos < size())
return _start[pos];
}
//迭代器
//可读可写
typedef T* iterator;
//只读
typedef const T* const_iterator;
iterator begin()
{
//第一个元素的位置
return _start;
}
iterator end()
{
//最后一个元素的下一个位置, 也就是最后一个元素的结束位置
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
//val默认值: 内置类型:0值, 自定义类型:调用无参构造
void resize(size_t n, const T& val = T())
{
if (n > capacity())
reserve(n);
if (n > size())
{
while (_finish != _start + n)
{
*_finish++ = val;
}
}
//更新size
_finish = _start + n;
}
void insert(iterator pos, const T& val)
{
if (pos >= _start && pos <= _finish)
{
//检查容量
if (_finish == _eos)
{
//增容会导致迭代器失效,如果发生了增容,更新迭代器
//保存当前位置与起始位置的偏移量
int len = pos - _start;
size_t newC = capacity() == 0 ? 1 : 2 * capacity();
reserve(newC);
//更新迭代器
pos = _start + len;
}
//移动元素
iterator end = _finish;
while (end > pos)
{
*end = *(end - 1);
--end;
}
//插入元素
*pos = val;
//更新size
++_finish;
}
}
//返回迭代器:执行当前被删除元素的下一个元素位置
iterator erase(iterator pos)
{
if (pos >= _start && pos < _finish)
{
//移动元素
iterator begin = pos + 1;
while (begin != _finish)
{
*(begin - 1) = *begin;
++begin;
}
//更新size
--_finish;
}
return pos;
}
~Vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _eos = nullptr;
}
}
Vector(const Vector<T>& v)
: _start(new T[v.capacity()])
{
//深拷贝
for (int i = 0; i < v.size(); ++i)
{
_start[i] = v[i];
}
_finish = _start + v.size();
_eos = _start + v.capacity();
}
Vector<T>& operator=(Vector<T> v)
{
Swap(v);
return *this;
}
void Swap(Vector<T>& v)
{
swap(_start, v._start);
swap(_finish, v._finish);
swap(_eos, v._eos);
}
private:
T* _start;
T* _finish;
T* _eos; //end of storage
};
template <class T>
void printVector(const Vector<T>& v)
{
cout << "operator[]:" << endl;
for (int i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
//调用const接口,只读
//v[i] = "a";
}
cout << endl;
2.
cout << "迭代器:" << endl;
Vector<T>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
3.
cout << "范围for:" << endl;
for (const T& str : v)
{
cout << str << " ";
}
cout << endl;
}
1.3 list
list是可以在常数范围内在任意位置进行插入和删除的序列式容器,且该容器可以前后双向迭代(叠层是双向链表结构),在任意位置进行插入,移除元素的执行效率更好,但是不支持任意位置的访问
1. list的构造
list<int> lst;
list<char> lst2(5, 'a'); // aaaaa
char str[] = "12345";
list<char> lst3(str, str + 5); // 12345
list<char> lst4(lst2.begin(), lst2.end()); // aaaaa
list<char> copy(lst3); // 12345
2. list的遍历
list<char>::iterator it = lst3.begin();
while (it != lst3.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//正向迭代器
list<int> lst5(3, 1);
list<int>::iterator it2 = lst5.begin();
while (it2 != lst5.end())
{
cout << *it2 << " ";
*it2 = 5;
++it2;
}
cout << endl;
it2 = lst5.begin();
while (it2 != lst5.end())
{
cout << *it2 << " ";
++it2;
}
cout << endl;
for (char ch : lst3)
{
cout << ch << " ";
}
cout << endl;
//反向迭代器
list<char>::reverse_iterator rit = lst3.rbegin();
while (rit != lst3.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
//不可修改迭代器
list<char>::const_iterator cit = lst3.cbegin();
while (cit != lst3.cend())
{
cout << *cit << " ";
//*cit = 'z';
++cit;
}
3. list迭代器
函数声明 | 接口说明 |
---|---|
size | 返回list中有效节点个数 |
empty | 检测list是否为空 |
remove | 删除指定值 |
push_front | 在list首元素前插入 |
push_back | 在list尾部插入 |
pop_front | 删除list第一个元素 |
pop_back | 删除list中最后一个元素 |
insert | 在pos位置插入元素 |
earse | 删除pos位置的元素 |
swap | 交换两个list中的元素 |
merge | 合并 |
sort | 进行排序 |
unique | 删除重复值 |
splice | 拼接 |
clear | 清空list中的有效元素 |
reverse | 逆转元素 |
//构造插入
lst.emplace_back(10);
lst.emplace_front(-1);
lst.emplace_back(a);
template <class T>
void printLst(const list<T>& lst)
{
for (const T& e : lst)
cout << e << " ";
cout << endl;
}
list<int> lst;
//list迭代器在插入元素之后,不会失效
lst.insert(lst.begin(), 1);
list<int> copy(lst);
//删除会导致迭代器失效,调用删除接口之后,需要重新更新迭代器:1. 获取erase返回值, 2. 调用迭代器接口
it = lst.erase(it);
lst.resize(7, 1);
//remove: 删除指定值, 如果有多个,全部删掉,如果没有,不进行删除的操作
lst.remove(10);
//splice: 拼接时,被拼接的元素直接存入第一个lst, 第二个list中不在保留被拼接的元素
printLst(lst);
printLst(lst2);
lst2.splice(lst2.begin(), lst, ++lst.begin(), --lst.end());
//unique: 使用之前需要lst元素有序
lst2.unique();
lst2.sort();
lst2.merge(lst3);
lst2.reverse();
list的模拟实现
//List: 双向带头循环链表
template < class T>
struct ListNode
{
T _value;
ListNode<T>* _next;
ListNode<T>* _prev;
ListNode(const T& val = T())
:_value(val)
, _next(nullptr)
, _prev(nullptr)
{}
};
template <class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
ListIterator(Node* node)
:_node(node)
{}
//解引用: *iterator ---> 获取节点value
Ref operator*()
{
return _node->_value;
}
// 指针->成员
Ptr operator->()
{
return &_node->_value;
}
// ++: 移动到下一个元素的位置
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
Node* _node;
};
template <class T>
class List
{
public:
typedef ListNode<T> Node;
typedef ListIterator<T, T&, T*> iterator;
//不能通过添加const修饰符定义const迭代器
typedef ListIterator<T, const T&, const T*> const_iterator;
iterator begin()
{
//第一个元素的位置
return iterator(_header->_next);
}
iterator end()
{
//最后一个元素的下一个位置
return iterator(_header);
}
const_iterator begin() const
{
//第一个元素的位置
return const_iterator(_header->_next);
}
const_iterator end() const
{
//最后一个元素的下一个位置
return const_iterator(_header);
}
List()
:_header(new Node)
{
//构建循环结构
_header->_next = _header->_prev = _header;
}
void pushBack(const T& val)
{
//Node* cur = new Node(val);
链接
//Node* prev = _header->_prev;
//prev->_next = cur;
//cur->_prev = prev;
//cur->_next = _header;
//_header->_prev = cur;
insert(end(), val);
}
void pushFront(const T& val)
{
insert(begin(), val);
}
void popBack()
{
erase(--end());
}
void popFront()
{
erase(begin());
}
void insert(iterator pos, const T& val)
{
Node* cur = new Node(val);
Node* node = pos._node;
Node* prev = node->_prev;
prev->_next = cur;
cur->_prev = prev;
cur->_next = node;
node->_prev = cur;
}
//删除导致pos迭代器失效
//返回值: 下一个元素的位置
iterator erase(iterator pos)
{
//不能删除头结点(_header)
if (pos != end())
{
Node* node = pos._node;
Node* prev = node->_prev;
Node* next = node->_next;
delete node;
prev->_next = next;
next->_prev = prev;
return iterator(next);
}
return pos;
}
size_t size() const
{
size_t count = 0;
for (const auto& e : *this)
++count;
return count;
}
~List()
{
if (_header)
{
clear();
delete _header;
_header = nullptr;
}
}
void clear()
{
//清空所有非头结点
Node* cur = _header->_next;
while (cur != _header)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
//重新构建循环结构
_header->_next = _header->_prev = _header;
}
List(const List<T>& lst)
:_header(new Node)
{
_header->_next = _header->_prev = _header;
//深拷贝,插入元素
for (const auto& e : lst)
pushBack(e);
}
//现代写法
List<T>& operator=(List<T> lst)
{
swap(_header, lst._header);
return *this;
}
private:
Node* _header;
};
vector和list的对比
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,效率为O(n) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
STL组件2:适配器
适配器是一种设计模式,该种模式是将一个类的接口转换成客户希望的另一个接口(就彷佛我们电脑的变压器一般),在STL中没有将stack和queue
划分在容器行列,而是将其称之为容器适配器,这是因为栈和队列是对其他容器接口进行了包装,默认使用deque
双向队列。
2. 1stack
class stack
{
public:
stack() {}
void push(const T& x) {_c.push_back(x);}
void pop() {_c.pop_back();}
T& top() {return _c.back();}
const T& top()const {return _c.back();}
size_t size()const {return _c.size();}
bool empty()const {return _c.empty();}
private:
std::vector<T> _c;
}
template <class T, class Container = deque<T>>
class Stack
{
public:
void push(const T& val)
{
_c.push_back(val);
}
void pop()
{
_c.pop_back();
}
const T& top()
{
return _c.back();
}
size_t size() const
{
return _c.size();
}
bool empty() const
{
return _c.empty();
}
private:
Container _c;
};
2.2 queue
template<class T>
class queue
{
public:
queue() {}
void push(const T& x) {_c.push_back(x);}
void pop() {_c.pop_front();}
T& back() {return _c.back();}
const T& back()const {return _c.back();}
T& front() {return _c.front();}
const T& front()const {return _c.front();}
size_t size()const {return _c.size();}
bool empty()const {return _c.empty();}
private:
std::list<T> _c;
};
也可以使用list来实现队列,但vector不能够实现队列
//push, pop, front, back, size, empty
template <class T, class Container = deque<T>>
class Queue
{
public:
void push(const T& val)
{
_c.push_back(val);
}
void pop()
{
_c.pop_front();
}
T& front()
{
return _c.front();
}
T& back()
{
return _c.back();
}
size_t size() const
{
return _c.size();
}
bool empty() const
{
return _c.empty();
}
private:
Container _c;
};
2.3 priority_queue
根据严格的弱排序标准,他的第一个元素总是它所包含元素的最大
list也不能够实现优先队列
#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
void TestPriorityQueue()
{
// 默认情况下,创建的是大堆,其底层按照小于号比较
vector<int> v{3,2,7,6,0,4,1,9,8,5};
priority_queue<int> q1;
for (auto& e : v)
q1.push(e);
cout << q1.top() << endl;
// 如果要创建小堆,将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}
以仿函数的形式实现优先队列:
template <class T>
struct Greater
{
bool operator()(const T& val1, const T& val2)
{
return val1 > val2;
}
};
template <class T>
struct Less
{
bool operator()(const T& val1, const T& val2)
{
return val1 < val2;
}
};
class Date
{
public:
Date(int y, int m, int d)
:_y(y), _m(m), _d(d)
{}
bool operator>(const Date& d) const
{
if (_y > d._y)
return true;
else if (_y == d._y)
{
if (_m > d._m)
return true;
else if (_m == d._m)
{
if (_d > d._d)
return true;
}
}
return false;
}
bool operator<(const Date& d) const
{
if (_y < d._y)
return true;
else if (_y == d._y)
{
if (_m < d._m)
return true;
else if (_m == d._m)
{
if (_d < d._d)
return true;
}
}
return false;
}
public:
int _y;
int _m;
int _d;
};
ostream& operator<<(ostream& cout, const Date& d)
{
cout << d._y << "-" << d._m << "-" << d._d << endl;
return cout;
}
template <class T, class Container = vector<T>, class Compare = Less<T>>
class Priority_Queue
{
public:
void push(const T& val)
{
_c.push_back(val);
shiftUp(_c.size() - 1);
}
void pop()
{
swap(_c[0], _c[_c.size() - 1]);
_c.pop_back();
shiftDown(0);
}
T& top()
{
return _c.front();
}
size_t size() const
{
return _c.size();
}
bool empty() const
{
return _c.empty();
}
private:
void shiftDown(int parent)
{
int child = 2 * parent + 1;
while (child < _c.size())
{
//if (child + 1 < _c.size() && _c[child] < _c[child + 1])
//通过仿函数实现比较的逻辑
if (child + 1 < _c.size() && _cmp(_c[child],_c[child + 1]))
++child;
//if (_c[parent] < _c[child])
if (_cmp(_c[parent],_c[child]))
{
swap(_c[parent], _c[child]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
void shiftUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_c[parent] < _c[child])
if (_cmp(_c[parent],_c[child]))
{
swap(_c[parent], _c[child]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
private:
Container _c;
Compare _cmp;
};
2.2 deque
双端队列,是一种双开口的“连续”空间数据结构,可以在头尾两端进行插入和删除操作,且时间复杂度都是O(1)
STL组件3:迭代器
迭代器是一种访问容器元素的通用机制,体现封装的特性,不需要过分的关注容器的实现细节,就可以直接访问元素,用来实现和指针相同的操作,所有支持迭代器的容器其迭代器的使用方式完全相同。
迭代器被分为:
原生迭代器 :string -->char*
和vector--> T*
非原生迭代器:通过封装,实现类似指针的相关操作
正向迭代器:
string str("0123456789");
string::iterator it = str.begin();
while (it != str.end()) {
cout << *it << " ";
//迭代器是个可读可写的接口
*it = 'a';
++it;
}
cout << endl;
}
反向迭代器:
string::reverse_iterator rit = str.rbegin();
while (rit != str.rend()) {
cout << *rit << " ";
*rit = 'a';
++rit;
}
cout << endl;
const迭代器:可读接口不可进行写
string::const_iterator cit = str.cbegin();
while (cit != str.cend()) {
cout << *cit << " ";
++cit;
}
cout << endl;
迭代器接口函数:
string str("0123456789");
int ret =str.size();//返回有效字符的个数
int Maxsize=str.capacity();//可以存放的最大有效字符个数
str.clear();//只清空内容,不改变容量
str.empty();//检查字符串是否为空串
str.resize(20);
//当n > size, 且 n > capacity: 增容(开新的空间 + 拷贝 + 释放原有空间) + 新的位置赋值(如果没有给赋值字符,默认赋值'\0' + 修改size
//当n < size,则只修改size
//当 n > size, 且 n < capacity: 新的位置赋值(如果没有给赋值字符,默认赋值'\0' + 修改size
str.reserve(20);
//reserve调整容量,不修改size和内容
//如减小容量,是按需减小,如要减小的容量小于size,不做任何操作,如大于size,则进行减小操作
- 容量和实际申请出来的空间可能不同,因为字符串的特点是以“\0”结束,需要给其留下一定空间。
- PJ版的string增容过程,如果为空字符串对象,初始容量大小为15,第一次增容扩大二倍,之后以1.5倍增长。
STL组件4:仿函数
可以可以看作是一个函数去使用,本质是调用成员函数“返回值 operator(参数)”
/仿函数类模板
template <class T>
struct Less
{
//重载括号运算符
bool operator()(const T& c1, const T& c2)
{
return c1 < c2;
}
};
template <class T>
struct Greater
{
//重载括号运算符
bool operator()(const T& c1, const T& c2)
{
return c1 > c2;
}
};
Less<C> lc;
C c1(1, 2, 3);
C c2(2, 2, 2);
bool ret = lc.operator()(c1, c2);
ret = lc(c1, c2); // 等价于 lc.operator()(c1, c2)
STL组件5:空间配置器
空间配置器:为各个容器更加高效的管理空间,现阶段要了解的是空间配置器的内部实现原理!
一级空间配置器和二级空间配置器的区别在于以128字节大小为分界线,,一级空间配置器管理大于128字节大块内存,而二级空间配置器管理小于等于128字节的小块内存。
1. 一级空间配置器
一级空间配置器原理非常简单,直接堆malloc与free进行了封装,并且在此基础上增加了空间不足的应对措施!
2. 二级空间配置器
二级空间配置器采用内存池的技术来提高申请空间的速度以及减少额外空间的浪费,采用哈希桶的方式来提高用户获取空间的速度和高效管理。
二级空间配置器申请空间的位置顺序:
- 哈希桶
- 内存池
- 系统
- 更大的哈希桶
- 一级空间配置器
因此一般我们更多的使用二级空间配置器按
STL组件6:算法
#include <algorithm>
以有限的步骤,解决数学或逻辑中的问题
- count与count_if 统计区间中某个元素出现的次数
#include <algorithm>
#include <vector>
bool IsOdd(int i)
{ return ((i % 2) == 1); }
int main()
{
// 统计10在v1中出现的次数
vector<int> v1{ 10, 20, 30, 30, 20, 10, 10, 20 };
cout << count(v1.begin(), v1.end(), 10) << endl;
// 统计v2中有多少个偶数
vector<int> v2{0,1,2,3,4,5,6,7,8,9};
cout << count_if(v2.begin(), v2.end(), IsOdd) << endl;
return 0;
}
-
find,find_if 找元素在区间中出现的第一次位置
-
merge合并两个有序序列为一个有序序列
#include <algorithm>
#include <vector>
#include <list>
int main()
{
vector<int> v{ 2, 6, 5, 8 };
list<int> L{ 9, 3, 0, 5, 7 };
sort(v.begin(), v.end());
L.sort();
vector<int> vRet(v.size() + L.size());
merge(v.begin(), v.end(), L.begin(), L.end(), vRet.begin());
for (auto e : vRet)
cout << e << " ";
cout << endl;
return 0;
}
- partial_sort 找topk
#include <algorithm>
#include <vector>
#include <functional>
int main()
{
// 找该区间中前4个最小的元素, 元素最终存储在v1的前4个位置
vector<int> v1{ 4, 1, 8, 0, 5, 9, 3, 7, 2, 6 };
partial_sort(v1.begin(), v1.begin() + 4, v1.end());
/ 找该区间中前4个最大的元素, 元素最终存储在v1的前4个位置
vector<int> v2{ 4, 1, 8, 0, 5, 9, 3, 7, 2, 6 };
partial_sort(v2.begin(), v2.begin() + 4, v2.end(), greater<int>());
return 0;
}
- partition按照条件堆区间中的元素进行划分
#include <algorithm>
#include <vector>
bool IsOdd(int i)
{ return (i % 2) == 1; }
int main()
{
vector<int> v{0,1,2,3,4,5,6,7,8,9};
// 将区间中元素分割成奇数和偶数两部分
auto div = partition(v.begin(), v.end(), IsOdd);
// 打印[begin, div)的元素
for (auto it = v.begin(); it != div; ++it)
cout << *it <<" ";
cout << endl;
// 打印[div, end)的元素
for (auto it = div; it != v.end(); ++it)
cout << " " << *it;
cout << endl;
return 0;
}
- reverse 元素进行逆置
reverse(v.begin(),v.end());
- sort 排序(重要)
sort(v.begin(), v.end())
STL里sort算法用的是什么排序算法?
毫无疑问是用到了快速排序,但不仅仅只用了快速排序,还结合了插入排序和堆排序。
STL的sort算法,数据量大时采用QuickSort快排算法,分段归并排序。一旦分段后的数据量小于某个门槛(16),为避免QuickSort快排的递归调用带来过大的额外负荷,就改用Insertion Sort插入排序。如果递归层次过深,还会改用HeapSort堆排序。
既然问的是STL的sort算法实现,那么先确认一个问题,哪些STL容器需要用到sort算法?
首先,关系型容器拥有自动排序功能,因为底层采用RB-Tree,所以不需要用到sort算法。
其次,序列式容器中的stack、queue和priority-queue都有特定的出入口,不允许用户对元素排序。
剩下的vector、deque,适用sort算法。
8. unique确保元素唯一性
先对i区间中的元素进行排序,另重复的元素放在相邻的位置,再使用unique将重复的元素覆盖掉,后将后面无效的元素移除。
vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
// 如果想将区间中所有重复性的元素删除掉,可以先对区间中的元素进行排序
for (auto e : v)
cout << e << " ";
cout << endl;
// 先对区间中的元素进行排序,另重复的元素放在相邻位置
sort(v.begin(), v.end());
for (auto e : v)
cout << e << " ";
cout << endl;
// 使用unique将重复的元素覆盖掉
it = unique(v.begin(), v.end());
// 将后面无效的元素移除
v.erase(it, v.end());
for (auto e : v)
cout << e << " ";
cout << endl;
return 0;
}
- next_permutation和pre_permutation(重要)
next_permutation是获取一个排序的下一个排列,可以遍历全排列,prev_permutation刚好相反,
获取一个排列的前一个排列
#include <algorithm>
#include <vector>
#include <functional>
int main()
{
// 因为next_permutation函数是按照大于字典序获取下一个排列组合的
// 因此在排序时必须保证序列是升序的
vector<int> v = {4, 1, 2, 3 };
sort(v.begin(), v.end());
do
{
cout << v[0] << " " << v[1] << " " << v[2] <<" " <<v[3]<< endl;
} while (next_permutation(v.begin(), v.end()));
cout << endl;
// 因为prev_permutation函数是按照小于字典序获取下一个排列组合的
// 因此在排序时必须保证序列是降序的
//sort(v.begin(), v.end());
sort(v.begin(), v.end(), greater<int>());
do
{
cout << v[0] << " " << v[1] << " " << v[2] << " " << v[3] << endl;
} while (prev_permutation(v.begin(), v.end()));
return 0;
}