C++中STL的基本使用
string 字符串
初始化
string str1="hello";
string str2("hello");
char str3[10]="hello";
char str4[10]="hello";
//string类型可以用等号判断值相等,char数组不能
cout<<(str1==str2)<<endl; //1
cout<<(str3==str4)<<endl; //0
cout<<(str1==str3)<<endl; //1
cout<<(str1==str4)<<endl; //1
字符串常量不能相加。
cout<<"a"+"b";
//[Error] invalid operands of types 'const char [2]' and 'const char [2]' to binary 'operator+'
访问与遍历
下标访问
cout<<str1[1]<<endl; //e
访问最后一位
cout<<str1.back()<<endl; //o
下标遍历
for(int i=0;i<str1.size();i++)
{
cout<<str1[i]<<' ';
}
//h e l l o
迭代器遍历
for(string::iterator it=str1.begin();it!=str1.end();it++)
{
cout<<*it<<' ';
}
//h e l l o
可以使用auto关键字简化编码,省略迭代器的类型。
for(auto it=str1.begin();it!=str1.end()-2;it++)
{
//用法和指针一样
cout<<*(it+2)<<' ';
}
//l l o
使用auto关键字
for(auto ch:str1)
{
cout<<ch<<' ';
}
//h e l l o
获取长度:length()和size()
length()
是沿用C语言的习惯,size()
是考虑到兼容STL容器,二者作用一致。
cout<<str1.size()<<endl; //5
cout<<str1.length()<<endl; //5
这两个函数的返回值是无符号整数,直接和负数比较可能导致潜在问题。
cout<<(str1.size()>-1)<<endl; //0
//转成int类型就可以比了
int s1=str1.size();
cout<<(s1>-1)<<endl; //1
清空、判断空串:clear()和empty()
cout<<"*"<<str1<<' '<<str1.empty()<<endl; //*hello 0
str1.clear();
cout<<"*"<<str1<<' '<<str1.empty()<<endl; //* 1
插入:insert()
str.insert(pos,n,ch)
:向str
中插入n
个字符ch
,第一个字符的下标是pos
。
str1.insert(pos,str2)
:向str
中插入字符串str2
,str2
的首位在str1
中下标是pos
。
cout<<str2<<endl; //hello
str2.insert(str2.size(),"world");
str2.insert(5,1,' ');
cout<<str2<<endl; //hello world
替换:replace()
str.replace(pos,n1,n2,ch)
:将str
从下标pos
开始连续的n1
个字符替换成n2
个ch
。
str1.replace(pos,n,str2)
:将str1
从下标pos
开始连续的n
个字符替换成str2
。
cout<<str2<<endl; //hello world
str2.replace(5,1,3,'0');
cout<<str2<<endl; //hello000world
str2.replace(5,3,"1111");
cout<<str2<<endl; //hello1111world
删除:erase()
str.erase(pos,n)
:将str
从下标pos
开始的n
个字符删除。
cout<<str2<<endl; //hello1111world
str2.erase(5,4);
cout<<str2<<endl; //helloworld
切片:substr()
str.substr(pos)
:取str
从下标pos
开始到结尾的字符串。
str.substr(pos,n)
:取str
从下标pos
开始的n
个字符。
cout<<str2.substr(2)<<endl; //lloworld
cout<<str2.substr(2,6)<<endl; //llowor
查找:find()
str.find(sub)
:在str
中查找sub
第一次出现的位置。
str.find(sub,pos)
:在str
中从下标pos
开始查找sub
第一次出现的位置。
cout<<str2<<endl; //helloworld
cout<<str2.find("world")<<endl; //5 (5是'w'的在str2中的下标)
//没找到会返回string::npos,在int类型下是-1
cout<<str2.find("abc")<<' '<<string::npos<<endl; //18446744073709551615 18446744073709551615
int idx=str2.find("abc");
cout<<idx<<endl; //-1
//指定查找起点
cout<<str2.find("o")<<endl; //4
cout<<str2.find("o",5)<<endl; //6
应用:查找所有的字母'l'
cout<<str2<<endl; //helloworld
for(int idx=str2.find('l');idx!=-1;idx=str2.find('l',idx+1))
{
printf("str2[%d]=%s\n",idx,"l");
}
/*
str2[2]=l
str2[3]=l
str2[8]=l
*/
类型转换
cout<<stoi("234")+1<<endl; //235
cout<<stoi("-234.8")+1<<endl; //-233
cout<<stoi("00012")<<endl; //12
应用:删去字符串前导0
手写时需要注意一个细节:如果字符串是全0
,则需要至少保留一个0
。
cout<<to_string(stoi("-00001"))<<endl; //-1
cout<<to_string(stoi("00000"))<<endl; //0
序列式容器:vector 动态数组
//需要引入vector
#include <vector>
...
初始化、访问、遍历
vector<int> v1;
vector<string> v2={"one","two","three"};
//也可以是vector<string> v2{"one","two","three"};
//也可以是vector<string> v2({"one","two","three"});
vector<int> v3(20); //创建20个元素,它们的默认初始值都为0
vector<int> v4(20,-1); //创建20个元素,它们的初始值都为-1
cout<<v2[1]<<' '<<v3[1]<<' '<<v4[1]<<endl; //two 0 -1
cout<<v2.back()<<endl; //three
也可以与string
一样使用下标、迭代器和auto
关键字访问。
尾部添加元素
push_back()
和C++11新增加的emplace_back()
作用相同,但是底层实现的机制不同,emplace_back()
效率更高。
v1.push_back(123);
v1.emplace_back(456);
cout<<v1[0]<<' '<<v1[1]<<endl; //123 456
插入元素
insert(it,x)
:在迭代器it
指定的位置之前插入新元素x
,并返回新插入元素位置的迭代器。
insert(it,n,x)
:在迭代器it
指定的位置之前插入n
个新元素x
,并返回第一个新插入元素位置的迭代器。
emplace(it,...args)
:和insert(it,x)
用法类似,...args
是与新插入元素的构造函数相对应的多个参数。同样效率更高,不过emplace()
每次只能插入一个元素。
v1={0,1,2,3,4,5,6};
auto it=v1.insert(v1.begin()+2,99);
for(auto v:v1) cout<<v<<' '; //0 1 99 2 3 4 5 6
cout<<endl<<*it<<endl; //99
it=v1.insert(v1.begin()+4,5,-1);
for(auto v:v1) cout<<v<<' '; //0 1 99 2 -1 -1 -1 -1 -1 3 4 5 6
cout<<endl<<*it<<endl; //-1
v1.emplace(v1.begin(),5);
cout<<v1[0]<<' '<<*it<<endl; //5 5
关于内存管理和shrink_to_fit
vector可以看作是一个动态数组,会动态的申请内存空间。size
是容器的实际大小,capacity
是大小的上限。当vector
满载需要进行扩容时,会完全弃用旧空间,申请更大的新空间,再将元素复制到新空间中。
vector<int> v5;
for(int i=0;i<6;i++)
{
v5.push_back(i);
cout<<v5.size()<<' '<<v5.capacity()<<endl;
}
/*
1 1
2 2
3 4
4 4
5 8
6 8
*/
因此,此前保存的迭代器可能在重新分配后失效!
it=v5.begin(); //假设我们保存了容器首部的迭代器
cout<<*it<<endl; //v[0]=0
cout<<v5.size()<<' '<<v5.capacity()<<endl; //6 8
v5.insert(it,3,0); //原来size=6,capacity=8,插入3个会引起扩容
cout<<v5.size()<<' '<<v5.capacity()<<endl; //9 12
cout<<*it<<endl; //14032400 失效了
//使用shrink_to_fit释放没有使用的内存(同样会完全重新分配内存)
v5.shrink_to_fit();
cout<<v5.size()<<' '<<v5.capacity()<<endl; //9 9
删除元素与清空、判空
pop_back()
:删除vector
容器中最后一个元素。该容器的大小size
会减1
,但容量capacity
不变。
erase(it)
:删除vector
容器中迭代器it
指定的元素,并返回指向被删除元素下一个位置元素的迭代器。size
会减1
,但capacity
不变。
erase(it1,it2)
:删除vector
容器中位于迭代器[it1,it2)
指定区域内的所有元素,并返回指向被删除区域下一个位置元素的迭代器。size
会减小,但capacity
不变。
clear()
:删除vector
容器中所有的元素。size
变为0
,但capacity
不变。
v1={0,1,2,3,4,5,6,7,8,9,10};
v1.pop_back();
cout<<v1.size()<<' '<<v1.capacity()<<endl; //10 14
it=v1.erase(v1.begin()+5);
for(auto v:v1) cout<<v<<' '; //0 1 2 3 4 6 7 8 9
cout<<endl<<*it<<endl; //6
cout<<v1.size()<<' '<<v1.capacity()<<endl; //9 14
it=v1.erase(v1.begin()+3,v1.begin()+6);
for(auto v:v1) cout<<v<<' '; //0 1 2 7 8 9
cout<<endl<<*it<<endl; //7
cout<<v1.size()<<' '<<v1.capacity()<<endl; //6 14
同样可以用empty()
判空。
v1.clear();
cout<<v1.empty()<<endl; //1
cout<<v1.size()<<' '<<v1.capacity()<<endl; //0 14
排序
严格来说不属于C++ STL的范畴,需要用到sort()
函数。
//需要引入algorithm
#include <algorithm>
...
v1={1,4,5,2,6,8};
sort(v1.begin(),v1.end());
for(auto v:v1) cout<<v<<' '; //1 2 4 5 6 8
关于元组
//需要引入tuple
#include <tuple>
...
pair 二元组
pair
将两个数据组合,它是由结构体实现,元素分别为first
和second
。
定义和初始化:
pair<int,int> p1;
cout<<p1.first<<' '<<p1.second<<endl; //0 0
pair<int,double> p2(1,3.14);
//也可以是pair<int,double> p2{1,3.14};
//也可以是pair<int,double> p2={1,3.14};
//也可以是pair<int,double> p2({1,3.14});
cout<<p2.first<<' '<<p2.second<<endl; //1 3.14
进行比较时,先比较pair.first
元素的大小,如果相等则继续比较pair.second
元素的大小:
pair<int,double> p3(1,3.14);
pair<int,double> p4(1,2);
cout<<(p2==p3)<<endl; //1
cout<<(p3==p4)<<endl; //0
pair<int,int> p5(1,1);
pair<int,int> p6(1,2);
pair<int,int> p7(2,0);
cout<<(p7>p6)<<endl; //1
cout<<(p6>p5)<<endl; //1
C++中可以对元组进行解构赋值:
string name;
double value;
pair<string,double> p8("pi",3.14);
tie(name,value)=p8;
cout<<name<<' '<<value<<endl; //pi 3.14
应用:让函数返回多个值
//输入(x,y),输出(y,x)
pair<int,int> swapxy(pair<int,int> p)
{
return make_pair(p.second,p.first);
}
//make_pair可以直接生成一个pair常量值
p1=swapxy(make_pair(2,7));
cout<<p1.first<<' '<<p1.second<<endl; //7 2
tuple 多元组
tuple
的用法类似,但不限制元素数量。
tuple<int,int,int,double> t1(1,2,3,9.99);
取第i
个元素:
//取第i个元素使用get<i>(t)
cout<<get<0>(t1)<<' '<<get<3>(t1)<<endl; //1 9.99
上述方法的i
必须传入一个常量!
获取元组的大小:
//获取tuple大小
cout<<tuple_size<tuple<int,int,int>>::value<<endl; //3
//decltype(t1)实际是取了t1的类型
cout<<tuple_size<decltype(t1)>::value<<endl; //4
解构的操作是一样的:
int a,b,c;
double d;
tie(a,b,c,d)=t1;
cout<<a<<' '<<b<<' '<<c<<' '<<d<<endl; //1 2 3 9.99
关联式容器:map 映射
序列式容器vector
储存的元素都是“值”的概念,关联式容器储存的元素是“键值对”的概念。如果已知目标元素的键值,则直接通过键就可以找到目标元素,不需要遍历整个容器。
关联式容器存储的元素,默认会根据键值做升序排序。STL标准库在实现时底层选用的数据结构是红黑树
。
初始化、访问、遍历
map
中的元素默认按照键的顺序进行从小到大排序
map<string,double> m1;
map<string,double> m2={
{"one",1},
{"two",2},
{"three",3},
};
//也可以是map<string,double> m2{{"one",1},{"two",2},{"three",3}};
//也可以是map<string,double> m2({{"one",1},{"two",2},{"three",3}});
m1["a"]=6;
m1["b"]=5;
m1["c"]=4;
m1["d"]=3; //这里相当于创建了m1["d"]
m1["e"]=2;
m1["d"]=1; //这里相当于修改
//实际上只要使用[key]取值,并且传入的key是新键,就会创建一个新键值对,值为类型默认值
cout<<m1["c"]<<endl; //4
for(auto it1=m1.begin();it1!=m1.end();it1++)
{
cout<<it1->first<<' '<<it1->second<<endl;
}
/*
a 6
b 5
c 4
d 1
e 2
*/
插入元素
insert(pair)
:插入一个键值对pair=(key,value)
。
如果成功,返回(新插入的val的迭代器,true)
;
如果失败,说明已经有键为key
的键值对,返回(此元素的迭代器,false)
。
emplace(...args)
:传入键值对的构造参数即可。同样效率更高,不过emplace()
每次只能插入一个元素。
auto it1=m1.begin();
pair<decltype(m1)::iterator,bool> res;
res=m1.insert(make_pair("f",99));
it1=res.first;
cout<<it1->first<<' '<<it1->second; //f 99
cout<<' '<<res.second<<endl; //1
res=m1.insert({"f",88});
it1=res.first;
cout<<it1->first<<' '<<it1->second; //f 99
cout<<' '<<res.second<<endl; //0
res=m1.emplace("g",55);
it1=res.first;
cout<<it1->first<<' '<<it1->second; //g 55
cout<<' '<<res.second<<endl; //1
删除元素与清空、判空
erase(key)
:删除key
键对应的键值对。
clear()
:清空map
。
cout<<m1.size()<<endl; //7
m1.erase("g");
cout<<m1.size()<<endl; //6
cout<<m1.empty()<<endl; //0
m1.clear();
cout<<m1.size()<<endl; //0
cout<<m1.empty()<<endl; //1
查找
find(key)
:查找键为key
的元素,返回该元素的迭代器,找不到则返回迭代器end()
。
m1["a"]=100;
m1["b"]=200;
m1["c"]=300;
it1=m1.find("a");
if(it1!=m1.end())
{
cout<<it1->first<<' '<<it1->second<<endl;
}
else
{
cout<<"not found."<<endl;
}//a 100
it1=m1.find("d");
if(it1!=m1.end())
{
cout<<it1->first<<' '<<it1->second<<endl;
}
else
{
cout<<"not found."<<endl;
}//not found.
二分查找
lower_bound(key)
返回指向第一个键值大于等于key
的位置的迭代器,找不到则返回迭代器end()
。
upper_bound(key)
返回指向第一个键值大于key
的位置的迭代,找不到则返回迭代器end()
。
m1.clear();
m1["a"]=1;
m1["b"]=2;
m1["c"]=3;
m1["x"]=4;
m1["y"]=5;
it1=m1.lower_bound("b");
cout<<it1->first<<' '<<it1->second<<endl;//b 2
it1=m1.upper_bound("b");
cout<<it1->first<<' '<<it1->second<<endl;//c 3
it1=m1.lower_bound("j");
cout<<it1->first<<' '<<it1->second<<endl;//x 4
it1=m1.upper_bound("j");
cout<<it1->first<<' '<<it1->second<<endl;//x 4
it1=m1.upper_bound("y");
cout<<(it1==m1.end())<<endl;//1
对于结构体类型
如果key
是结构体类型,需要定义比较函数。
struct POINT{
double x,y;
//假如想按照平方距离从大到小排序
//默认的排序方式是从小到大,把小于号重载成"大于"的含义相当于打破这种默认
//或者理解为,排序方式是从小到大,那么我们就要把期望的结果定义为"小"的
friend bool operator <(POINT a,POINT b)
{
double da=a.x*a.x+a.y*a.y;
double db=b.x*b.x+b.y*b.y;
//我们期望"平方距离大"的元素在这种含义下是"小的"
//因此返回(da>db)的值,也就是这个条件为1的时候,我们认为前者是"小的"
return da>db;
}
};
struct POINTDETAIL{
string name; //点的名字
vector<int> v; //假设每个点还对应了一些需要用vector储存的信息
};
map<POINT,POINTDETAIL> m3;
//不同的插入方式
m3[{3,4}]={"A点",{1,2,3}};
m3.insert(
make_pair(
(POINT){4,5},
(POINTDETAIL){"B点",{4,5,6}}
)
);
m3.insert(
{ {1,5},{"C点",{7,8,9,10}} }
);
m3.emplace(
(POINT){4,4},
(POINTDETAIL){"D点",{2,3,5,7}}
);
for(auto it1=m3.begin();it1!=m3.end();it1++)
{
//it1指向一个键值对(key,val)
//it1->first是键key,key是一个POINT类型,具有x,y属性
//it1->second是值val,val是一个POINTDETAIL类型,具有name属性和v向量
cout<<it1->second.name<<": "<<it1->first.x<<' '<<it1->first.y;
cout<<" v=[";
for(auto v:it1->second.v) cout<<v<<',';
cout<<"\b]"<<endl;
}
/*
B点: 4 5 v=[4,5,6]
D点: 4 4 v=[2,3,5,7]
C点: 1 5 v=[7,8,9,10]
A点: 3 4 v=[1,2,3]
*/
m3[{4,3}]={"E点",{5,6,7}};//E点
cout<<m3[{3,4}].name<<endl;
//因为定义的是平方和作为判断依据,{3,4}和{4,3}被视为同一个键而发生了重新赋值
multimap
multimap
允许键值重复。
multimap<string,int> mm1;
//multimap没有重载[]方法
mm1.emplace("a",3);
mm1.emplace("a",1);
mm1.emplace("a",2);
mm1.emplace("b",4);
mm1.emplace("b",5);
find(key)
返回找到的第一个值。
auto it2=mm1.find("a");
if(it2!=mm1.end())
{
cout<<it2->first<<' '<<it2->second<<endl;
}
else
{
cout<<"not found."<<endl;
}//a 3
count(key)
返回键的数量(这个普通的map
也可以用)。
cout<<mm1.count("a")<<endl; //3
erase(key)
会擦除key
对应的所有值。
mm1.erase("b");
it2=mm1.find("b");
if(it2!=mm1.end())
{
cout<<it2->first<<' '<<it2->second<<endl;
}
else
{
cout<<"not found."<<endl;
}//not found.
关联式容器:set 集合
使用set
容器存储的各个键值对,要求键key
和值value
必须相等。实际上这可以看作,set
容器只需要一个key
信息,即可成功将元素存储起来。同样的,set
也会对元素进行默认从小到大的排序。
初始化、访问、遍历
set<string> s1={"a","world","b","hello","a"};
//也可以是set<string> s1{"a","world","b","hello","a"};
//也可以是set<string> s1({"a","world","b","hello","a"});
for(auto it=s1.begin();it!=s1.end();it++)
{
cout<<*it<<endl;
}
/*
a
b
hello
world
*/
插入元素
insert(x)
:插入一个元素x
。
如果成功,返回(新插入的x的迭代器,true)
;
如果失败,说明已经有元素x
,返回(x的迭代器,false)
。
emplace(...args)
:传入键值对的构造参数即可。同样效率更高,不过emplace()
每次只能插入一个元素。
auto it1=s1.begin();
pair<decltype(s1)::iterator,bool> res;
res=s1.insert("c");
it1=res.first;
cout<<*it1<<' '<<res.second<<endl; //c 1
res=s1.emplace("c");
it1=res.first;
cout<<*it1<<' '<<res.second<<endl; //c 0
删除元素与清空、判空
erase(x)
:删除元素x
。
clear()
:清空set
。
cout<<s1.size()<<endl; //5
s1.erase("hello");
cout<<s1.size()<<endl; //4
cout<<s1.empty()<<endl; //0
s1.clear();
cout<<s1.size()<<endl; //0
cout<<s1.empty()<<endl; //1
查找
find(x)
:查找元素x
,返回该元素的迭代器,找不到则返回迭代器end()
。
s1.insert("a");
s1.insert("b");
s1.insert("c");
it1=s1.find("a");
if(it1!=s1.end())
{
cout<<*it1<<endl;
}
else
{
cout<<"not found."<<endl;
}//a
it1=s1.find("d");
if(it1!=s1.end())
{
cout<<*it1<<endl;
}
else
{
cout<<"not found."<<endl;
}//not found.
二分查找
用法与map
一致,见map
。
应用:数组去重
这个方法去重顺便还排了序,注意效率!
vector<int> arr1={2,1,3,2,5,1,4,2,3,6,2,1,5,2,3,6,1};
for(auto v:arr1) cout<<v<<' '; //2 1 3 2 5 1 4 2 3 6 2 1 5 2 3 6 1
cout<<endl;
set<int> s_arr1(arr1.begin(),arr1.end());
vector<int> arr2(s_arr1.begin(),s_arr1.end());
for(auto v:arr2) cout<<v<<' '; //1 2 3 4 5 6
cout<<endl;
multiset
multiset
允许元素重复。
multiset<int> ms1;
ms1.emplace(1);
ms1.emplace(2);
ms1.emplace(2);
ms1.emplace(2);
ms1.emplace(3);
find()
、count()
、erase()
与multimap
类似。
cout<<(ms1.find(1)==ms1.end())<<endl; //0
cout<<(ms1.find(5)==ms1.end())<<endl; //1
cout<<ms1.count(2)<<endl; //3
cout<<ms1.size()<<endl; //5
ms1.erase(2);
cout<<ms1.size()<<endl; //2
无序容器
无序容器也使用“键值对”的方式存储数据。由于不需要维持有序,无序容器的底层实现采用的是哈希表
。
无序容器包括无序映射unordered_map
和无序集合unordered_set
等。它们用法与map
和set
类似,但是不会维持键值有序。
如果使用自定义结构体作为键值,需要提供一个哈希函数。C++标准库提供了hash
模板类,可以用于自定义类型的哈希函数。本文不对无序容器进行展开介绍。
stack 栈
//需要引入stack
#include <stack>
...
size()
:获取元素数量。
empty()
:判空。
push(x)
:压入一个元素x
。
top()
:返回栈顶元素。
pop()
:弹出栈顶元素。
stack<int> s;
s.push(1);
s.push(2);
s.push(3);
while(!s.empty())
{
int x=s.top();
s.pop();
cout<<x<<endl;
}
/*
3
2
1
*/
queue 队列
//需要引入queue
#include <queue>
...
size()
:获取元素数量。
empty()
:判空。
push(x)
:压入一个元素x
。
front()
:返回队首元素。
back()
:返回队尾元素。
pop()
:弹出队首元素。
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
while(!q.empty())
{
int x=q.front();
q.pop();
cout<<x<<endl;
}
/*
1
2
3
*/
priority_queue 优先队列
优先队列是一种特殊的队列,它的出队顺序是按照元素的优先级来的。优先队列默认是大根堆
。
//需要引入queue
#include <queue>
...
size()
:获取元素数量。
empty()
:判空。
push(x)
:压入一个元素x
。
top()
:返回堆顶元素。
pop()
:弹出堆顶元素。
priority_queue<int> q1;
q1.push(7);
q1.push(2);
q1.push(5);
q1.push(6);
q1.push(4);
q1.push(1);
q1.push(3);
while(!q1.empty())
{
int x=q1.top();
q1.pop();
cout<<x<<endl;
}
/*
7
6
5
4
3
2
1
*/
对于结构体类型
struct DATE {
int y,m,d;
//默认的优先队列是大根堆,把小于号重载成"大于"的含义相当于打破这种默认,变成小根堆
friend bool operator <(DATE a,DATE b)
{
if(a.y!=b.y) return a.y>b.y;
if(a.m!=b.m) return a.m>b.m;
return a.d>b.d;
}
};
priority_queue<DATE> q2;
q2.push({2028,12,23});
q2.push({2028,11,28});
q2.push({2035,1,1});
q2.push({2029,1,1});
q2.push({2028,12,25});
while(!q2.empty())
{
DATE date=q2.top();
q2.pop();
cout<<date.y<<'-'<<date.m<<'-'<<date.d<<endl;
}
/*
2028-11-28
2028-12-23
2028-12-25
2029-1-1
2035-1-1
*/