0
点赞
收藏
分享

微信扫一扫

C++ STL总结


文章目录

  • ​​一、vector​​
  • ​​常用函数​​
  • ​​初始化​​
  • ​​插入​​
  • ​​遍历​​
  • ​​删除​​
  • ​​其他函数​​
  • ​​二、Map​​
  • ​​初始化​​
  • ​​插入​​
  • ​​查找​​
  • ​​删除​​
  • ​​遍历​​
  • ​​大小​​
  • ​​判空​​
  • ​​不常用的函数​​
  • ​​三、queue​​
  • ​​常用函数​​
  • ​​初始化​​
  • ​​入队和出队​​
  • ​​返回队首、队尾元素​​
  • ​​遍历​​
  • ​​四、deque​​
  • ​​增加​​
  • ​​删除​​
  • ​​查询搜索​​
  • ​​五、stack​​
  • ​​常用函数​​
  • ​​六、vector、list、deque之间的对比​​
  • ​​七、容器空间适配器​​
  • ​​八、容器适配器​​
  • ​​九、关联容器​​
  • ​​十、常用迭代器​​
  • ​​十一、函数对象​​

一、vector

常用函数
  • begin():返回指向vector首部的迭代器,指向第一个元素
  • end():返回指向vector尾部的迭代器,并不指向最后一个元素
  • rbegin():返回指向vector尾部的迭代器,指向最后一个元素
  • rend():返回指向vector首部的迭代器,并不指向第一个元素
  • pop_back():删除最后一个元素,无返回值
  • push_back():在尾部加入一个元素
  • erase():删除指定位置元素,返回下一个元素的位置(传入迭代器,返回迭代器)
  • erase(begin_pos,end_pos):删除[begin_pos,end_pos)之间的元素,返回下一个元素的位置(传入迭代器,返回迭代器)
  • assign(begin_iter, end_iter):将区间中的数据赋值给向量,左闭右开
  • assign(n, elem):将n个elem的拷贝赋值给向量
  • size():返回向量中元素数量
  • front():返回第一个元素
  • back():返回最后一个元素
  • v1.swap(v2):将v1和v2在内存中的指向互换,也就达到了两向量元素互换的效果
  • empty():判空
  • clear():清除所有数据
  • at(int num):返回第num个数据
  • insert(pos_iter, elem):在pos_iter位置插入一个elem数据
  • insert(pos_iter, n, elem):在pos_iter位置插入n个elem数据
  • reserve(int n):给容器底层预留n个元素空间(vec.size()==0 && vec.capacity()==n)
  • resize(int n):不仅给容器预留n个空间,此时容器内也会有n个元素(vec.size()==n && vec.capacity()==n)

注: 使用insert、erase进行插入和删除元素的时候,一定要用迭代器接收返回值,否则使用后迭代器失效

初始化

<int> v1; // 此时向量中没有元素
vector<int> v1(10); // 向量中10个元素,全为0
vector<int> v1(10,1); // 向量中10个元素,全为1
vector<int> v1(v2); //将v2拷贝给v1,实际上是用v2的size以及数据对v1进行初始化,与capacity无关。
vector<int> v1( v2.begin() , v2.end() );
vector<int> v1 = {1, 2, 3};

插入

insert() 和 push_back()

.insert(v1.begin(), 10);//在首部插入一个10
v1.insert(v1.begin(),3, 9);//在首部插入3个9
v1.push_back(1);//在尾部插入1

遍历

//for...each...
for(int i : v1){
cout<<i<<" ";
}
//索引
for(int i=0; i<v1.size(); i++){
cout<<v1[i]<<" ";
}
//索引
for(int i=0; i<v1.size(); i++){
cout<<v1.at(i)<<" ";
}
//正向迭代器,正向输出
for (vector<int>::iterator it = v2.begin(); it != v2.end(); it++)
cout << *it << endl;
//反向迭代器,反向输出
for (vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++)
cout << *it << endl;

删除

erase() 和 pop_back() 和clear()

.erase(v2.begin());//删除首部元素
v1.erase(v1.begin(), v1.end()-2);//删除区间的元素,其他元素前移,左闭右开
v1.pop_back()//删除尾部元素
v1.clear()//删除所有元素

其他函数

.size();
v1.empty();
v1.assign(v2.begin(), v2.end()-1);//将v2指定区间内的元素赋值给v1
v1.assign(5, 10);//v1存放的元素变成5个10
reverse(v1.begin(), v1.end());//将v1逆置,头文件<algorithm>中的逆置函数

二、Map

有序、红黑树、不可重复

常用函数:

  • begin()返回指向map头部的迭代器
  • end()返回指向map末尾的迭代器
  • rbegin()返回一个指向map尾部的逆向迭代器
  • rend()返回一个指向map头部的逆向迭代器
  • clear()删除所有元素
  • count()根据key返回指定元素出现的次数,返回值只能是0或1
  • empty()判空
  • erase()根据key删除一个元素
  • find()根据返回key返回迭代器,若没找到就返回指向map尾部的迭代器
  • insert()插入元素
  • lower_bound()返回键值 >= 给定值的最小iterator
  • upper_bound()返回键值 > 给定值的最小iterator
  • size()返回map中元素的个数
初始化

map <string, int> mp = {{"a", 1}, {"b", 2}, {"c", 3}};

插入

map <string, int> mp;
mp["a"] = 1;// 同 mp.insert(pair<string, int>("a", 1));
mp["b"] = 2;
mp["c"] = 3;
mp["d"]; // 等价于mp["d"] = string()

map <string, int> mp;
mp.insert(pair<string, int>("a", 1));
mp.insert({"b", 2});
mp.insert(make_pair("c", 3));

查找

find() :根据返回key返回迭代器,若没找到就返回指向map尾部的迭代器
count() :返回键值出现的次数,只返回0或1

map<string, int>::iterator iter = mp.find("b");//根据键值返回迭代器
cout<<iter->first<<":"<<iter->second<<endl;//打印键值对
cout<<mp.count("a")<<endl;//返回1

删除

erase() :根据key删除元素,删除成功返回1,删除失败返回0
clear() : 清除map中的所有元素

mp.erase("a");//返回1
mp.erase("aa");//返回0
mp.clear();//size()变为0

遍历

iter->first : 打印键
iter->second : 打印值

for(map<string, int>::iterator iter = mp.begin(); iter!=mp.end(); iter++){
cout<<iter->first<<":"<<iter->second<<endl;//打印键值对
}

// for_each只允许读,不允许写,使用引用需要加const
for(const pair<string, int>& p : mp){
cout<<p.first<<":"<<p.second<<endl;
}

大小

size() : 返回map中键值对个数
max_size() : 返回map最大容纳键值对的数量

mp.size();//返回键值对个数
mp.max_size();//256204778801521550

判空

mp.empty()

不常用的函数
  • lower_bound() 返回键值 >= 给定值的最小iterator
  • upper_bound() 返回键值 > 给定值的最小iterator

可参考​​这里​​

三、queue

常用函数
  • empty()//判空,返回bool
  • push()// 从队尾入队列,无返回值
  • pop()// 从队首出队列,无返回值
  • front()// 返回队首元素
  • back()// 返回队尾元素
  • size()// 返回队列元素个数
初始化

queue<int> q;//定义一个空队列

入队和出队

q.push(1);//入队列
q.push(2);
q.push(3);
q.pop(); //队首元素出队列

检查 pop 操作是否只是头指针移动,而元素还在

if(*(&(q.front())-1) != 1){
cout<<"已经移除1"<<endl;
}else{
cout<<"只是头指针移动,实际内存中依然存放原队首元素:";
cout<<*(&(q.front())-1)<<endl;
}

运行结果:

C++ STL总结_c++

返回队首、队尾元素

q.front();//返回队首元素
q.back();//返回队尾元素

遍历

由于STL没有提供遍历队列的操作,只能先找到队首元素的地址,再挨个往后访问

for(int i=0; i<q.size(); i++){
cout<<*(&(q.front())+i)<<" ";
}

四、deque

deque是双端队列容器,底层数据是动态扩容的二维数组。一维数组从2开始,以2倍的方式进行扩容,每次扩容后原来第二维的数组从新的第一维数组的下标​​oldsize/2​​开始存放,上下留出相同的空间,方便deque首尾入队。

由于是双端队列,所以最开始的时候,​​first​​​ 和 ​​last​​其实是指向相同地方的

C++ STL总结_数据_02


扩容后:

C++ STL总结_c++_03

增加
  • push_back(num)
  • push_front(num)
  • insert(iter, num)
删除
  • pop_back(num)
  • pop_front(num)
  • erase(iter)
查询搜索

连续insert和erase要考虑迭代器失效的问题

list的底层数据结构是一个双向循环链表,操作和deque一模一样

五、stack

常用函数
  • 入栈:如s.push(x);
  • 出栈:如 s.pop(),注:出栈操作只是删除栈顶的元素,并不返回该元素。
  • 访问栈顶:如s.top();
  • 判断栈空:如s.empty(),返回bool值
  • 访问栈中的元素个数,如s.size()

六、vector、list、deque之间的对比

​vector​​​和​​deque​​之间的区别?

  1. 底层数据结构:动态扩容的一维数组和动态扩容的二维数组
  2. 前中后删除元素的时间复杂度
  3. 内存使用效率:vector需要的内存空间完全连续,deque只要求部分连续即可
  4. 中间插入和删除的时候,vector由于内存完全连续,移动元素方便。而deque内存不完全连续,元素移动不太方便

​vector​​​和​​list​​​之间的区别?
根据底层数据结构(动态扩容的一维数组 和 双向循环链表)对比访问,增删的效率即可

七、容器空间适配器

容器的空间配置器allocator是将内存开辟、内存释放、对象构造、对象析构四个操作分开,而不是像​​new​​​一样把内存开辟和对象构造同时做,像​​delete​​一样把内存释放和对象析构同时做。

// 定义容器的空间配置器
struct Allocator {
// 1. 开辟空间
T* allocator(size_t size) {
return (T*)malloc(sizeof(T) * size);
}
// 2. 释放空间
void deallocator(void* p) {
free(p);
}
// 3. 构造对象
void construct(T* p, const T& val) {
// 定位new,在指定内存上构造一个值为val的对象,调用拷贝构造
new (p) T(val);
}
// 4. 负责对象析构
void destroy(T* p) {
p->~T();
}
};

说一下STL容器的swap:

  1. 若两个容器使用的空间配置器相同,即内存管理方式一样,那么两个容器交换资源就仅仅就是交换一下成员变量(也可以理解为交换指针的指向),没有开辟空间,数据拷贝等操作,效率较高。
  2. 若两个容器使用的空间配置器不同,即内存管理方式不一样,此时交换资源就需要开辟空间,拷贝数据,效率低,这在实际使用中属于少数情况

八、容器适配器

什么是容器适配器?

  1. 适配器底层没有实现自己的数据结构,它是另外一个容器的封装,它的方法全部依赖底层的容器实现,就是一种代理
  2. 容器适配器没有实现自己的迭代器

​stack​​​的底层就是封装了​​deque​​​,也即​​stack​​就是容器适配器

template<typename T, typename Container=deque<T>>

class Stack {
public:
void push(const T& val) {
con.push_back(val);
}

void pop() {
con.pop_back();
}

T top() const {
return con.back();
};

private:
Container con;
};

常见的容器适配器有:stack(deque)、queue(deque)、priority_queue(vector、大顶堆)

面试官:stack和queue为什么依赖deque,而不依赖vector?

  1. vector的内存利用效率太低;vector:0->1->2->4->8,而deque是4096/sizeof(T),deque无需很多的内存扩容的操作
  2. 对于queue来说,需要进行头删尾插的操作;vector只在尾部操作快,头部操作很慢。而deque则两头操作都是O(1)
  3. vector需要大片的连续内存,而deque只要求部分内存连续即可。当存储数据较多是,deque的内存效率更高。

面试官:priority_queue为什么依赖vector,而不依赖deque?

由于priority_queue底层是一个大顶堆,所有节点通过索引访问,这需要连续内存,即依赖vector

九、关联容器

C++ STL总结_c++_04

C++ STL总结_迭代器_05

无序关联容器(底层哈希表)

#include<iostream>
#include<unordered_map>
#include<unordered_set>

using namespace std;

int main() {
unordered_set<int> set1;
// 1. 插入
for (int i = 0; i < 20; i++) {
set1.insert(rand() % 20);
}
// cout << set1.size() << endl; // < 20
// cout << set1.count(5) << endl; // 0或1

// 2. 遍历,迭代器可访问所有容器
for (auto iter = set1.begin(); iter != set1.end(); iter++) {
cout << *iter << " ";
}
cout << endl;
// for_each,底层就是用迭代器
for (int v : set1) {
cout << v << " ";
}
cout << endl;

// 3. 删除 erase(iter), erase(key)
for (auto iter = set1.begin(); iter != set1.end(); ) {
if (*iter == 10) {
// erase后iter失效,删除后从当前位置开始比较,不用++
iter = set1.erase(iter);
}
else {
iter++;
}
}

// 4. 查找find(key),找到返回对应位置iter,否则返回end()
auto iter = set1.find(20);
if (iter != set1.end()) {
set1.erase(iter);
}

return 0;
}

有序关联容器(底层红黑树)

set

有序、不可重复

#include<iostream>
#include<set> // set multiset 红黑树

using namespace std;

class Student {
public:
Student(int id, string name) :_id(id), _name(name) {};
bool operator<(const Student& stu) const{
return _id < stu._id;
}
private:
int _id;
string _name;
friend ostream& operator<<(ostream& out, const Student& stu);
};

ostream& operator<<(ostream& out, const Student& stu) {
out << stu._id << " " << stu._name << endl;
return out;
}

int main() {
set<Student> set1;
set1.insert(Student(10, "张三"));
set1.insert(Student(30, "李二"));
set1.insert(Student(20, "王五"));
for (const Student& stu : set1) {
cout << stu;
}
return 0;
}

map

有序、不可重复

#include<iostream>
#include<map> // map multimap 红黑树

using namespace std;

class Student {
public:
// 对于存放于map的元素,记得给默认构造函数(mp[key]也可能插入元素)
Student(int id=0, string name=string()) :_id(id), _name(name) {};
private:
int _id;
string _name;
friend ostream& operator<<(ostream& out, const Student& stu);
};

ostream& operator<<(ostream& out, const Student& stu) {
out << stu._id << " " << stu._name << endl;
return out;
}

int main() {
map<int, Student> mp;
mp.insert(make_pair(10, Student(10, "张三")));
mp.insert(pair<int, Student>(30, Student(30, "李二")));
mp.insert({ 20, Student(20, "王五") });
for (auto iter = mp.begin(); iter != mp.end(); iter++) {
cout << iter->first << " " << iter->second;
}
return 0;
}

十、常用迭代器

<int> vec;
vector<int>::const_iterator it1 = vec.begin(); // 可读不可写,子类
vector<int>::iterator it1 = vec.begin(); // 可读可写,派生类

vector<int>::const_reverse_iterator r_beg = vec.rbegin();
vector<int>::reverse_iterator r_beg = vec.rbegin(); // 返回最后一个元素的反向迭代器表示

vector<int>::const_reverse_iterator r_end = vec.rend();
vector<int>::reverse_iterator r_end = vec.rend(); // 返回首元素前驱的反向迭代器表示

十一、函数对象

C语言中的函数指针实现函数调用,有函数开销

#include<iostream>

using namespace std;

template<typename T>
bool my_greater(const T& a, const T& b) {
return a > b;
}

template<typename T>
bool my_less(const T& a, const T& b) {
return a < b;
}

template<typename T, typename Compare>
bool compare(const T& a, const T& b, Compare comp) {
// 函数指针调用,有函数开销,无法内联
// 编译过程中,看到函数指针comp,无法知道调用的是哪个函数,所以无法写死,只能动态地从函数地址取出函数
return comp(a, b);
}
int main() {
cout << compare(10, 20, my_greater<int>);
return 0;
}

把有​​operator()​​运算符重载函数的对象,称作函数对象,也叫仿函数

可以确定调用函数, 完全可以内联,省去函数调用开销

#include<iostream>

using namespace std;

template<typename T>
class my_greater {
public:
bool operator()(const T& a, const T& b) {
return a > b;
}
};

template<typename T>
class my_less {
public:
bool operator()(const T& a, const T& b) {
return a < b;
}
};

template<typename T, typename Compare>
bool compare(const T& a, const T& b, Compare comp) {
//可以确定调用函数, 完全可以内联,省去函数调用开销
return comp(a, b);
}

int main() {
cout << compare(10,20, my_greater<int>());
return 0;
}

使用函数对象改变priority_queue的排序方式

int main() {
// 降序,默认less
priority_queue<int> q1;
// 升序
// using MinQueue = priority_queue<int, vector<int>, greater<int>>;
priority_queue<int, vector<int>, greater<int>> q2;

// 升序,默认less
set <int> s1;
// 降序
set<int, greater<int>> s2;
return 0;
}

#include<iostream>
#include<vector>
#include<functional>
#include<algorithm>

using namespace std;

int main()
{
/*
find_if需要一元函数对象
找第一个大于100的数字
greater:a>b less:a<b
*/
vector<int> vec = { 5,432,5,7,6586,7876,234,14,1 };
// 找到返回对应迭代器,找不到返回end
// auto iter = find_if(vec.begin(), vec.end(), bind1st(less<int>(), 100));
auto iter = find_if(vec.begin(), vec.end(),
[](int val)->bool {return val > 100; } // lambda表达式
);
vec.insert(iter, 100);
for (int i : vec) {
cout << i << " ";
}
cout << endl;

for_each(vec.begin(), vec.end(),
[](int val)->void {
if (val % 2 == 0) {
cout << val << " ";
}
}
);
return 0;
}


举报

相关推荐

0 条评论