C++
之STL
容器篇
vector
向量,就是可变长的数组,可以在末尾插入元素。拥有数组的所有特性。完全可以代替数组。
- 能像数组一样随机访问
- 可以在末尾插入元素
push_back()
- 使用
insert()
,在某个元素位置前插入新的元素。给出一个迭代器和插入的值,在该迭代器前插入 begin(),end()
方法,返回vector
首尾的迭代器。迭代器被设计得像一个指针,取元素就使用*
,end()
指向的元素不属于容器- 给出一个迭代器定义的例子:
vector<int>::iterator ite
erase(),clear()
,后者删除全部元素,前者删除一个或者一段元素。- 迭代器给出的区间都是左开右闭,因为右边的迭代器是无法被访问到的
size()
可以知道向量的大小,empty()
就是问容器是否为空,空就返回1,非空就返回0 reverse()
反转序列,给出首尾迭代器,将范围内的元素序列反转sort()
升序排列,和上一个算法一样,给出首尾迭代器
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
bool cmp(const int& a, const int& b);
int main()
{
// 容器的基本使用
vector<int> v1; //大小不确定,但是初始是3,貌似
vector<int> v2(10); //大小为10的容器,下标0~9,值被初始化为0
vector<double> v3(10,8.6);//大小为10,初值为8.6
v.push_back(2);
v.push_back(2);
// 迭代器的遍历
vector<double> v4(3);
v4[1] = 1;
v4[2] = 2;
v4[3] = 3;
vector<double>::iterator ite;
for(ite = v4.begin(); ite != v.end(); ite++)
cout << *ite << " ";
cout << endl;
// 元素的删除
v4.erase(v4.begin());
for(ite = v4.begin(); ite != v4.end(); ite++)
cout << *ite << " ";
cout <<endl;
// reverse反转序列
vector<int> v(10);
for(int i = 0; i < 10; i++)
v[i] = i;
reverse(v.begin(),v.end());
vector<int>::iterator ite;
for(ite = v.begin(); ite != v.end(); ite++)
cout << *ite << " ";
cout << endl;
// sort从小到大排列
vector<int> v(10);
vector<int>::iterator ite;
for(int i = 0; i < 10; i++)
v[i] = 9 - i;
for(ite = v.begin(); ite != v.end(); ite++)
cout << *ite << " ";
cout << endl;
sort(v.begin(),v.end());
for(ite = v.begin(); ite != v.end(); ite++)
cout << *ite << " ";
cout << endl;
}
// 自己设计一个比较函数,首先,明确问题就是
// 返回值肯定是bool,传入参数的类型就是要比较的类型
// 这里设计的是从大到小排序,所以问题就是 a 大于 b吗?
// 正常的就是直接 运算 a > b,这个问题就是 a大于b吗
// 唯一注意就是 a == b的时候,等于的时候,就随便谁大都可以
bool cmp(const int& a, const int& b)
{
if( a == b) return a > b;
else return a > b;
}
string
这个东西是STL
提供的字符串,拥有添加,删除,替换,查找和比较的方法。
头文件就是#include<string>
- 对象的创建
- 对象的赋值,一般使用C语言的字符指针
// scanf输入给字符数组,然后再赋值给string
char ss[5000];
scanf("%s",ss);
string s;
s = ss;
- 在string对象的尾部添加字符使用
+
或者append()
,后者括号中的格式为char*
- string插入字符串
insert()
,给出一个迭代器和要插入的值,这里的值是char
- string对象的一个元素就是一个
char
,所以上一点插入的值就是char
类型 - 删除部分string对象,
erase()
函数,使用迭代器。 - 字符串的搜索
find()
compare()
成员函数,按字典序来比较reverse()
函数将迭代器选择的范围的元素反转序列- string对象可以作为vector的元素
- 如果我们要将一个数的每个位都求出来,就要取余,但是这样要花很多时间,直接将数字作为字符串处理就更好
- 用
printf
输出stirng
要使用c_str()
这个函数 sscanf()
可以将字符串中的子串提取出来,记住提取的格式要和源字符串契合- 数值和
string
的转化,不能直接赋值,要自己写函数,C语言也有自己的方法。C++有专门的对象ostringstream
,istringstream
。
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
// 数值转化为string
string convertToString(double x)
{
ostringstream o;
if(o << x)
return o.str();
return "conversioin error!";
}
// string转化为数值
double convertFromString(const string& s)
{
istringstream i(s);
double x;
if( i >> x)
return x;
return 0.0;
}
int main()
{
// 对象的创建,以及赋值,上面还有一种更常用的方法,但是需要scanf
string s;
cout << s.length() << endl;
s = "hello,world!";
cout << s << endl;
s[0] = 'l';
cout << s << endl;
// append() 其实 + 更方便
string s;
s = "hello,world!";
s.append("a");
// insert()
string s;
s = "hello,world!";
string::iterator ite;
ite = s.begin();
s.insert(ite, '1');
cout << s << endl;
// 删除元素:赋空或者erase()
string s;
s = "123456789";
string::iterator ite;
ite = s.begin();
s.erase(ite, ite+4);
cout << s <<endl;
// 元素替换,replace()方法是先删后添加,不需要使用迭代器,给出起始位置和字符串的长度,然后替代的值
string s;
s = "abc456789";
s.replace(0,3,"123");
cout << s << endl;
// 字符串的find,找出符合的第一个字符串,返回字符串的第一个元素的下标
// 找字符用'',找字符串用""
string s;
s = "cat dog cat";
cout << s.find('c') << endl; // 返回 0
cout << s.find("cat") << endl; // 返回 0
cout << s.find("dong") << endl; // 返回 4
cout << s.find("isa") << endl; // 返回 18446744073709551615
// string对象的比较,是按字典序来比较的。
string s;
s = "cat dog cat";
cout << s.compare("cat") << endl; // >0
cout << s.compare("cat dog cat") << endl; // ==0
cout << s.compare("dog") << endl; // <0
// reverse(),将迭代器选择范围内的序列反转,记得#include<algorithm>
string s;
s = "hello,world!";
reverse(s.begin(),s.end());
cout << s << endl;
// vector<string>
vector<string> v;
v.push_back("jack");
v.push_back("mike");
v.push_back("tom");
cout << v[0] <<endl; // jack
cout << v[1] <<endl; // mike
cout << v[2] <<endl; // tom
cout << v[0][0] <<endl; // j
cout << v[0].length() <<endl; // 4
// 将读入的数作为字符串处理,求出各个位数之和
string s;
int sum = 0;
s = "1234059";
for(int i = 0; i<s.length(); i++)
{
if(s[i] == '0') sum+=0;
else if(s[i] == '1') sum+=1;
else if(s[i] == '2') sum+=2;
else if(s[i] == '3') sum+=3;
else if(s[i] == '4') sum+=4;
else if(s[i] == '5') sum+=5;
else if(s[i] == '6') sum+=6;
else if(s[i] == '7') sum+=7;
else if(s[i] == '8') sum+=8;
else if(s[i] == '9') sum+=9;
}
cout << sum << endl;
// int sscanf(const char* str, const char* format, ...)
// 从字符串读取格式化输入
// str事C字符串,是数据源,format也是C字符串,是一个格式
// sscanf可以把字符串按自己需要的方式提取出子串,甚至是数字
string a,b,c;
char sa[100], sb[100], sc[100];
sscanf("abc 123 pc","%s %s %s",sa,sb,sc);
a = sa;
b = sb;
c = sc;
cout << a << " " << b << " " << c <<endl;
int x,y,z;
sscanf("1,23$45","%d,%d$%d",&x,&y,&z);
cout << x << " " << y << " " << z <<endl;
// 如果直接将数值给string得到的是ASCII码
int num = 100;
string b;
b = num;
cout << b << endl;
// c方法,将数值转化为string
char data[10];
string a;
sprintf(data, "%d", 1984);
a = data;
cout << a << endl;
// c++,数值转string
string cc = convertToString(num);
cout << cc << endl;
// c++,string转数值
string dd = "2020";
int p = convertFromString(dd);
cout << p + 2 << endl;
}
set
set
集合容器实现了红黑树的平衡二叉检索树的数据结构,不会重复插入相同键值的元素,直接忽略。
检索时采取中序遍历的方式检索效率高于vector
,deque
,list
等容器。
采用中序遍历算法可以将键值由小到大遍历出来,所以平衡二叉检索数在插入元素时,就会自动按键值由由小到大排列。(键就是值)
构造set
的目的就是为了快速检索。
- 创建
insert()
插入,遍历输出是中序遍历,从小到大输出,使用迭代器 - 反向遍历有反向遍历的迭代器
reverse_iterator
- 元素的删除:删除的对象是 迭代器指向的对象,等于某个键值的对象,一个区间上的元素和清空合集。使用
erase()
,全部删除使用clear()
- 元素的检索
find()
,查找到元素的键值,然后返回一个迭代器,没有找到就返回end()
指向的位置 - 自定义比较函数,使用
insert()
将元素插入到集合中去的时候,如果没有自定义比较函数,那么会使用默认的从小到大的比较函数。自己写比较函数可以根据元素是不是结构体分类
#include<set>
#include<iostream>
using namespace std;
struct myComp
{
bool operator() (const int& a, const int& b)
{
if( a == b) return a > b;
else return a > b;
// return ( a == b) ? a > b : a > b; 这样貌似慢了0.1s
}
};
struct Foo
{
string name;
double score;
bool operator < (const Foo& a) const
{
// 类的内部,所以省略的一个参数,比较大小,现在的问题是 this->score < a.score吗 ?
// 从小到大排序,排序的关键就是看return里的符号,< 就是从小到大排序
if(score == a.score) return score < a.score;
else return score < a.score;
}
};
int main()
{
// 创建set对象
set<int> s;
// 元素的插入以及中序遍历
// 插入默认是从小到大插入,也可以自己写比较规则函数
s.insert(8);
s.insert(1);
s.insert(12);
s.insert(6);
s.insert(8);
set<int>::iterator ite;
for(ite = s.begin();ite!=s.end();ite++)
cout << *ite << " ";
cout << endl;
//使用专门的反向迭代器反向遍历set
set<int>::reverse_iterator rit;
for(rit = s.rbegin();rit!=s.rend();rit++)
cout << *rit << " ";
cout << endl;
// 使用erase()删除指定的键值
s.erase(6);
for(rit = s.rbegin();rit!=s.rend();rit++)
cout << *rit << " ";
cout << endl;
s.clear();
cout << s.size() << endl;
//使用find()寻找键值,返回迭代器
finder = s.find(20);
if(finder != s.end())
cout << "find it!" << endl;
else
cout << "not find!" << endl;
// 自定义比较函数
// 在自定义比较函数的时候没有返回值,结果是元素全部插入进去,而且是按插入顺序存储
// 使用自定义比较函数在定义set的时候,类型中要加入自定义的结构体,相应的迭代器的类型也要改变
// 1、元素不是结构体
set<int,myComp> s;
s.insert(8);
s.insert(1);
s.insert(12);
s.insert(6);
s.insert(8);
set<int,myComp>::iterator ite;
for(ite = s.begin();ite!=s.end();ite++)
cout << *ite << " ";
cout << endl;
// 2、元素是结构体,将比较函数放在了结构体内部
set<Foo> s;
Foo foo;
foo.name = "jacky";
foo.score = 99;
s.insert(foo);
foo.name = "mary";
foo.score = 88;
s.insert(foo);
foo.name = "dyc";
foo.score = 100;
s.insert(foo);
set<Foo>::iterator ite;
for(ite = s.begin();ite!=s.end();ite++)
cout << (*ite).name << " " << (*ite).score << endl;;
}
multiset
和set
一样,就是可以插入重复元素。
所以,它在插入元素,删除元素,查找元素有所不同。
- 在
insert()
元素后会排序,然后遍历输出的时候,对于字符串,数字排在了字母前面,可能是按ascii来排的。 erase()
函数删除元素的时候,会把和键值匹配的所有元素全部删除,并且返回删除元素的个数。find()
寻找元素,返回找到第一个元素的迭代器,没有找到,返回end()
#include<set>
#include<iostream>
#include<string>
using namespace std;
int main()
{
// multiset集合的创建
multiset<string> ms;
ms.insert("abc");
ms.insert("123");
ms.insert("111");
ms.insert("aaa");
ms.insert("123");
multiset<string>::iterator ite;
for(ite = ms.begin();ite!=ms.end();++ite)
cout << *ite << " ";
cout <<endl;
// multiset元素的删除,erase(),并且返回删除元素的个数
int delete_num = ms.erase("123");
cout << "delete_num: " << delete_num << endl;
for(ite = ms.begin();ite!=ms.end();++ite)
cout << *ite << " ";
cout <<endl;
// find()的使用
multiset<string>::iterator finder;
finder = ms.find("abc");
if(finder!=ms.end())
cout << "find it!" << endl;
else
cout << "not find!" << endl;
}
map
map
就是键值对组成的红黑树,不允许重复的键插入。比较函数只对元素的键值进行比较。
- 创建数据结构的基础方法还是一样的,就是取元素的时候,键是
first
,值是second
。 - 删除元素就是
erase()
根据键值删除,clear()
清空数据。 - 反向遍历
reverse_iterator
,rbegin()
,rend()
。 find
进行搜索,找到了就返回迭代器,没有就返回end()
。- 自定义比较函数,和
set
一样。根据元素是否是结构体来写。如果不是结构体,就写一个结构体,然后内部就重载一下operator()
,如果是一个结构体,就重载成员函数operator<
等。 - 自定义比较函数,比较的是键,所以传入的参数类型就是键的类型。
map
实现数字分离,主要就是先对一个map
预设好数字,或者字符。- 字符转数字就是
map<char,int> cim;
,数字转字符就是map<int,char> icm;
#include<map>
#include<iostream>
#include<string>
using namespace std;
int main()
{
// map的建立
map<string,float> m;
m["jack"] = 99;
m["mary"] = 100;
m["dyc"] = 78;
map<string,float>::iterator ite;
for(ite = m.begin();ite != m.end();++ite)
cout << ite->first << " " << ite->second << endl;
//cout << (*ite).first << " " << (*ite).second << endl;
// map元素的删除
m.erase("dyc");
// 数字地图 nm
// 实现了 字符'1'到'9' 于数字 1到9的映射
map<char,int> cim;
for(int i = 0; i<10; i++)
{
cim['0'+i] = i;
}
string sa;
sa = "6543";
int sum = 0;
for(int i = 0;i < s.length();++i)
sum += cim[sa[i]];
// 将数字映射字符的写法。
map<int,char> icm;
for(int i = 0; i<10; ++i)
icm[i] = '0' +i;
int n = 7;
string s = "The number is ";
cout << s + icm[n] << endl;
}
multimap
就是可以插入重复的键
insert()
插入元素,不能直接m[key] = value
,使用pair<>()
创建插入的临时对象erase()
会把所有键值一样的元素全部删除,并且返回删除的数量。find()
找到第一个匹配的键值,如果没有找到就返回end()
#include<map>
#include<iostream>
using namespace std;
int main()
{
// 元素的插入与遍历
// insert()与pair<>()
multimap<char,int> mm;
mm.insert(pair<char,int>('a',12));
mm.insert(pair<char,int>('b',13));
mm.insert(pair<char,int>('a',50));
multimap<char,int>::iterator ite;
for(ite = mm.begin();ite!=mm.end();++ite)
cout << ite->first << " " << ite->second << endl;
}
deuqe
双端队列容器deque
和vector
一样都是顺序表存储结构
- 考虑到容器元素的内存分配策略和操作的性能的时候,
deque
比vector
更有优势。 - 创建元素和
vector
一样。 - 插入元素,从尾部插入
push_back()
,会不断扩张队列 - 插入元素,从首部插入
push_front
,不会扩张队列,会挤掉已有的元素。 - 插入元素,从中间插入
insert()
,不会扩张队列,使用迭代器,会挤掉已有的元素 - 遍历元素
.size()
查看元素的个数。 - 遍历元素,迭代器
deque<type>::iterator
- 反向遍历元素,使用反向遍历的迭代器
deque<type>::iterator
- 删除元素,可以从前
pop_front()
,中erase()
,后pop_back()
删除,或者清空容器clear()
。
#include<deque>
#include<iostream>
using namespace std;
int main()
{
// 创建容器,从尾部插入元素
deque<int> di;
di.push_back(1);
di.push_back(2);
di.push_back(3);
cout << di[0] << " " << di[1] << " " << di[2] << endl;
// 输出 1,2,3
// 从首部插入元素
di.push_front(10);
di.push_front(20);
di.push_front(30);
di.push_front(40);
cout << di[0] << " " << di[1] << " " << di[2] << endl;
// 输出 40 30 20
// 从中间插入元素,用迭代器 , 输出 40 88 20
di.insert(di.begin()+1,88);
cout << di[0] << " " << di[1] << " " << di[2] << endl;
// pop_front() erase() pop_back() clear()
di.pop_front();
di.erase(di.begin());
di.pop_back();
di.clear();
}
list
双向循环链表
元素的插入,删除,查找是非常快速的。list
每个结点都有三部分,前驱指针,数据,后继指针
list
的迭代器只能++,--
,而不能+2
。因为他们是存储在不连续的内存中。
- 容器的创建以及元素的插入
push_back(),push_front()
和遍历 - 元素删除,
remove()
根据元素的值删除。值相同的元素全部删除 - 元素删除,
pop_front(),pop_back()
, - 元素删除,
erase()
使用迭代器删除,但是注意迭代器不能使用+n
的格式 - 元素删除,
clear()
全部删除 - 元素查询,这里使用的是
#include<algorithm>
中的find()
。找到了就是返回迭代器,没有就是返回`end()
- 元素排序,是
list
自带的sort()
,从小到大。 - 剔除重复元素,
unique()
,将值相同的元素只留一个
#include<list>
#include<iostream>
using namespace std;
int main()
{
// 容器的创建,元素的插入
list<int> l;
l.push_back(5);
l.push_back(6);
l.push_front(4);
list<int>::iterator ite;
for(ite = l.begin();ite!=l.end();++ite)
cout << *ite << endl;
// 反向遍历
list<int>::reverse_iterator rite;
for(rite = l.rbegin();rite!=l.rend();++rite)
cout << *rite << endl;
// remove()删除元素
l.remove(8);
// 自带的sort()
l.sort()
// unique()
l.unique();
}
bitset
bitset()
容器的元素的大小为一个bit
。值为0/1。
- 容器的大小在创建的时候就要给出,而且不能扩张。
set()
,所有元素全部为1,reset(pos)
,pos
位变成0- 输出方式
- 直接
cout
- 或者
for(int i = b.size()-1;i>=0;i--)
#include<bitset>
#include<iostream>
using namespace std;
int main()
{
bitset<10> b;
b[1] = 1;
b[6] = 1;
b[9] = 1; // 9是最高位
for(int i = b.size()-1;i>=0;--i)
cout << b[i];
cout << endl;
// set(pos, value) value默认是1
b.set();
for(int i=b.size()-1;i>=0;i--)
cout << b[i];
cout << endl;
// reset(pos) 设置为0
b.reset(0);
// for循环输出
for(int i=b.size()-1;i>=0;i--)
cout << b[i];
cout << endl;
// 直接cout
cout << b <<endl;
}
stack
堆,后进先出的一个线性表,插入和删除元素都只能在栈顶操作,插入元素Push
,删除元素Pop
- 入栈,出栈
push()
,`pop()
- 栈顶元素访问
top()
- 判空
empty()
- 栈内元素
size()
#include<stack>
#include<iostream>
using namespace std;
int main()
{
stack<int> s;
s.push(1);
s.push(2);
s.push(3);
s.push(4);
cout << s.top() << endl;
cout << s.empty() << endl;
while(!s.empty())
{
cout << s.top() <<endl;
s.pop();
}
}
queue
队列容器是一个先进先出的线性表,插入只能在队尾,删除只能在队首
- 入队
push
,出队pop()
- 查看队首
front()
,查看队尾back()
- 判空
empty()
,队列元素数量size()
#include<queue>
#include<iostream>
using namespace std;
int main()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
while(!q.empty())
{
cout << q.front() << endl;
q.pop();
}
cout << q.size();
}
priority_queue
优先队列,保持了队列的基本属性,元素从队尾进入,从队首出队。但是它有一个特性,就是队列中最大的数总是位于队首,就相当于给元素排了个序。可以自己重载operator <
来定义比较规则。
- 入队
push()
,出队pop()
- 读取队首元素
top()
- 判空
empty()
,读取元素数量`size()
- 自己写的结构体重载
operator<
可以定义优先级 - 直接重载
operator()
#include<queue>
#include<iostream>
using namespace std;
// priority_queue默认就是从大到小排序
// 所以,函数内部用<就是从大到小
struct Info
{
string name;
float score;
bool operator < (const Info& f) const
{
if(score != f.score) return score > f.score;
else return score > f.score;
}
};
// > 就是从小到大
// < 就是从大到小
struct myComp
{
bool operator() (const int& a, const int& b)
{
if( a != b) return a > b;
else return a > b;
}
};
int main()
{
priority_queue<int> pq;
pq.push(1);
pq.push(2);
pq.push(3);
pq.push(9);
cout << pq.size() << endl;
while(!pq.empty())
{
cout << pq.top() << endl;
pq.pop();
}
// 结构体存入priority_queue
// 重载<
priority_queue<Info> pq;
Info i1,i2,i3;
i1.name = "dyc";
i1.score = 100;
i2.name = "zyx";
i2.score = 120;
i3.name = "lzy";
i3.score = 150;
pq.push(i1);
pq.push(i2);
pq.push(i3);
cout << pq.size() << endl;
while(!pq.empty())
{
cout << (pq.top()).name << " " << (pq.top()).score << endl;
pq.pop();
}
// 定义:priority_queue<Type, Container, Functional>
// Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque // 等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式。
// 当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型。
// 这里要使用自己的比较函数,所以3个全写
priority_queue<int,vector<int>,myComp> pq;
pq.push(1);
pq.push(9);
pq.push(2);
pq.push(30);
while(!pq.empty())
{
cout << pq.top() << endl;
pq.pop();
}
}