c++中STL的简单使用

本博客适合于有C语言基础并正在学习数据结构的同学
本博客参考了柳婼学姐的《从放弃C语⾔到使⽤C++刷算法的简明教程》还加入的一些自己的理解。
看过本博客的同学在学习数据结构与算法这门课时会更方便。

PS:本来学c++的时候就想随手写一些STL的个人理解,结果一直咕咕咕,最近突然想起来,就全部总结出来。

学c++的好处

c++有许多别人写好的容器,比如堆栈,队列,优先队列;算法函数,比如最著名的sort函数,O(nlogn)的时间复杂度,比手写排序函数要好,还稳定,还有二分函数等等。另外c++向下兼容c,可以说c++是c的儿子,但这个儿子很出色,比他老爹(C)好用多了。

关于头文件

c++的头文件没有想c一样的.h扩展后缀,一般情况下C语言里面的头文件去掉.h然后再前面加个c就可以在c++里继续适用c语言的头文件中的函数了。
PS:有些学过一点的同学经常用万能头#include<bits/stdc++.h>,在这里还是建议不要这么用,如果你是打比赛或者仅仅个人用什么的无可厚非,但如果在公司或者上班后就会知道,这个万能头会严重拖慢编译速度,因为其链接的所有c++的头文件,造成编译过程中的链接变得非常慢,所以强烈不推荐适用!!!

命名空间using namespace std的解释

这句话是使用std这个名称空间,主要是为了解决命名冲突问题,c++的一些常用函数什么的都在其中统一命名,一般来说写c++代码都是需要这句话的,当然如果不想加,在某些c++特有命名前就要加一些代码,如下

std:cin>>n;

比较麻烦,所以不如直接在include下加上去,一般是这样

#include<iostream>
using namespace std;

cin和cout

cin、cout和scanf、printf一一对应,都在iostream这个头文件里,但用法不一样,下面分别解释一下

cin
cin语法如下

cin>>变量名;

cin比scanf要简介很多,不用去记那些%d,%f什么的,但有几个缺点

  1. cin比scanf慢好多,毕竟简单了就意味着内部实现要复杂很多
  2. cin在读char类型是会自动忽略不可见字符(空格、回车、tab等),所以如果遇到对字符细处理的话,要么用string,要么老老实实用scanf("%c")

cout
cout语法如下

cout<<变量名;

同cin一样,cout要比printf简介许多,但同时也有cin的一些缺点:慢、char类型的处理不是特别好(c++在处理字符主要是string,后面会讲),同时,想输出回车有两种方法

cout<<endl;
cout<<'\n';

平时学习用endl无可厚非,但如果是打acm比赛,或者pat考试的时候,最好还是用第二种,因为endl很慢,似乎是要进行输出流的同步,所以很慢。
另外,endl仅适用于cout,cin不能用!cin不能用!cin不能用!有见人用过,所以特别强调。

文件读写

cin和cout也是可以进行文件读写的,有两种用法,具体用法如下

ifstream in("要读取的文件名.后缀");
ofstream out("要写入的文件名.后缀");

ifstream和ofstream在这里都是一种变量类型,所以in和out在这里仅仅是个名字。
新建过后,就可以用了,具体用法和cin、cout一样,同时和cin、cout互不影响

直接修改输入输出流位置,语法如下

freopen("要读取的文件名.后缀", "r", stdin);
freopen("要写入的文件名.后缀", "w", stdout);

在代码中加上上述两端代码,就可以直接用cin、cout文件了,但正常的从黑框框读写就无法进行了。
更多用法详见博客

有关cin、cout慢的解决办法
在代码中加上一句ios::sync_with_stdio(false);
这句代码的意思是关闭输入输出同步流,其速度要比scanf还要快,但必须注意,加上这句代码后,就只能适用cin、cout了,不可cin和scanf混用,混用会出现意想不到的错误。

c++特有的bool变量

bool变量只有两个值,false和true,以前c里面都是int的0和1,这样虽然也能用,但用一个8字节大小的去储存仅有两个值的值,有些浪费空间,所以就直接搞了一个bool出来。c++的bool会把所有非零值解释为true,零值解释为false,所以直接对其赋值也是可以的

STL的一些容器

c++自己构造了一些容器,主要用的是面向对象的方法,在这里提前普及一点有关面向对象的语法。
相信大家一定早有耳闻面向对象需要new对象,但在本文章里是不需要new的,需要new的是需要自己分配空间和释放空间的(有点指针的感觉)。在者理仅仅介绍定义变量后适用的语法。具体如下
首先明确两个概念,struct和class,两个有点类似,又有点不一样,如果你自己创建一个结构,因为结构中的变量用的是.这个操作符,其实struct中也可以定义函数(严格来说应该叫方法),class和struct这一点一样,可以定义值和方法,但class比较高级,class可以继承,这是面向对象的知识,等以后学到就明白了。
另外在面向对象的思维里,class里定义的值是不可以暴漏的,这是很危险的事情,所以在面向对象的思维的,对象内的值的增删查改只能通过方法,也就是操作符.来调用方法。语法如下

类名.类方法(参数);

知道这一点就可以愉快的使用STL里的一些容器了。

c++里的string

在处理字符串的时候,c语言是建一个字符串数组,要考虑其大小,对数组的操作也非常不方便,所以c++特别构建了string容器(在头文件string里),这是一个可变字符串,在定义的时候不需要定义其大小,string会根据传入的字符自己分配大小,非常方便。语法如下

string 变量名;

赋值
在c里面一个字符是'字符'这样的,而在c++里多了字符串"字符串"用字符串直接赋值,当然也可以当作字符串数组直接使用,赋值修改查询都没问题。
读写
读入string仅能使用cin和cout,其他如scanf等都不可使用。
连接
这是string最nb 的地方,字符串拼接可以直接用符号+,语法如下

连接后字符串 = 待连接字符串a + 待连接字符串b;
s = s1 + s2;

方法
常用方法

s.size();
s.length();
/*两个都是求字符长度,没有本质区别
只是前一个代替c中char的求长度函数
后一个是STL本身的惯例size函数*/

s.find(s1);//查找子串

s.substr(num1,num2);
//截取字符串从num1到num2(省略即为到末尾)的子串,有返回值
ans = s.substr(num1,num2);

还有很多,不在一一赘述,有兴趣可以去看官方文档或者直接去看string的源码。

c++里的vector

之前c语言用int num[];定义数组,缺点是不能随意改变长度,c++里有了vector(在头文件vector里),翻译为矢量,但还是习惯叫其动态数组或者不定长数组更合适。语法如下

vector<数据类型> 名称(长度,初值);

PS:括号及其内容可省略。

查值及改值
vector可以像数组那样直接用[]进行查改操作。
常用方法
下面将以一个名为v的vector介绍一下vector的常用用法

v.size();//返回求长度
v.push_back(元素名);//在末尾添加元素,必须是定义的元素,否则会报错
v.begin();//返回第一个元素的迭代器
v.end();//返回最后一个元素后面一个元素的迭代器

同string,vector还有很多方法,有兴趣的可以去看官方文档或者直接看vector源码
有关迭代器的知识会在介绍完容器后统一讲解,可以暂时先理解为指针。
强调:end方法返回的是最后一个元素后面一个元素的迭代器
PS:这里可能有同学会问,为什么end方法返回的是尾元素后面一个元素的迭代器,这里会在讲解sort函数的时候详细展开。

c++里的set

set是一个集合(在头文件set里),一个set没有相同的元素,并且set会按照元素进行从小到大的排序(如果是自定义的数据结构,则需要重载运算符),其底层实现是一颗红黑树(有兴趣的小伙伴可以去找找什么是红黑树),语法如下

set<数据类型> 名称;

下面将以一个名为s的集合介绍一下set的常用用法

s.insert(元素);//向集合s里插入一个元素
s.erase(元素);//删除集合s中的元素
s.size();//返回集合s的大小
s.begin();//返回集合中第一个元素的迭代器
s.end();//返回集合中最后一个元素后面一个元素的迭代器
s.find(元素);//返回集合s中的元素的迭代器,如果没有则返回的是s.end()

同vector,set还有很多方法,有兴趣的可以去看官方文档或者直接看set源码

c++里的map

map是指映射,在c的时候学了数组,那是数字对数字的影射,但某些时候,也需要在其他数据结构间进行映射,所以就有了map(在头文件map里),可以实现不同数据结构之间的映射,并对所有键值按照从小到大排序,其底层实现是哈希,哈希在理论上有着O(1)的复杂度,算是查找结构的非常快的一种了,其具体实现会在数据结构课上详解说明,在此不在赘述,下面介绍用法,首先是语法

map<key数据结构,value数据结构> 名称;

其用法和数据基本相似,且数组下边不再仅是数字,数组下标取决于key数据结构,下面以map<string,int> m;举例说明

m["hello"]=2;
m["world"]=3;

接下来介绍几个常用方法

m.begin();//数据m的第一个数据的迭代器
m.end();//m的最后一个数据的下一个数据的迭代器
m.size();//m的数量
map<string,int>::iterator p;//迭代器的定义
p->first;//该迭代器位置的key键值
p->second;//该迭代器位置的value键值

其实map的方法用的倒不是很多,只要是用其O(logn)时间的查删改,O(logn)的这几个操作比便利快很多,在默写数据量比较大的情况或者数据不是数字是特殊数据的情况下,map的用处就特别明显了。

同set,map还有很多方法,有兴趣的可以去看官方文档或者直接看map源码

c++里的stack

stack就是数据结构最基础的堆栈,c++内部实现了这个数据结构,在写某些题时可以不在考虑堆栈的实现,而直接使用,其在stack头文件中,语法如下

stack<数据类型> 名称;

下面以stack<int> s;介绍其常用方法

s.push(num);//将元素num压入堆栈中
s.pop();//将堆栈中的第一个元素pop,不返回该元素
s.top();//返回堆栈中的第一个元素但不pop
s.size();//返回堆栈大小
s.empty();//判断堆栈是否为空,为空则返回true,否则返回false

常用用法基本上就这么几个,还有几个高级用法不在赘述,想了解的可以去看官方文档或者直接看stack源码

c++里的queue

queue也是数据结构课上比较基础的数据结构,其在queue头文件中,语法如下

queue<数据类型> 名称;

下面以queue<int> q;介绍其常用方法

q.push(num);//将元素num压入队列中
q.pop();//将队列的第一个元素pop掉
q.front();//返回队列中的第一个元素
q.back();//返回队列中的最后一个元素
q.size();//返回队列的大小
q.empty();//判断队列是否为空,为空则返回true,否则返回false

常用用法基本上就这么几个,还有几个高级用法不在赘述,想了解的可以去看官方文档或者直接看queue源码
PS:c++有关队列的还有双端队列(deque),有兴趣的可以去了解一下。

c++里的unordered_map和unordered_set

unordered_map在头文件unordered_map里,unordered_set在头文件unordered_set里。
unordered_map和unordered_set与map和set的区别是,后者会按照键值对其进行从小到达的排序,而前者省去了这个过程,前者的速度会快于后者,所以在打acm的时候,如果出现使用后者超时是,就可以将后者改为前者而缩短代码运行时间。
两者用法基本一样。

c++里的bitset

bitset用来处理一些二进制问题,在pat和平时上课不这么用,但在acm的里会有处理二进制的问题,用bitset会方便很多,其头文件是bitset,语法如下

bitset<位数> 名称(十进制大小);

括号中不仅可以传数字,还可以传字符串。
下面以bitset<10> b(100);来介绍其常用方法

b.any();//b中是否存在1的二进制位
b.none();//b中不存在1吗?
b.count();//b中1的二进制为的个数
b.size();//b的二进制为的个数
b.test(num);//下标为num的位置是否为1
b.set(num);//把b的下标num处置为1
b.reset();//所有位归零
b.reset(num);//下标num处置0
b.flip();//b按位取反

bitset很快,其原因似乎是因为其内部是个cache?(我了解的不多,因为确实不常用),想了解更多的可以去看官方文档或者直接看bitset源码

c++里的迭代器

迭代器的英文是iterator,所以其在c++中的定义也离不开iterator,下面以vector<int>的迭代器简述一下其语法

vector<int>::ioerator p;

这样我们就定义了一个名为p的迭代器,其实迭代器和指针有点像,但有和指针不是那么一样,所以可以把迭代器理解为进阶的指针。笔者感觉,迭代器的主要用处就是为一些STL的容器提供了另一种遍历方式。最实用的就是map容器,在未知map键值的情况下想遍历,map的方法里并未提供,但我们可以用迭代器遍历。下面以map<string,int> m;为例,示范一下遍历语法

map<string,int> m;
m["a"]=1;
m["b"]=2;
m["c"]=3;
map<string,int>::iterator p;
for(p = m.begin() ; p != m.end() ; p++)
	cout<<p->first<<" "<<p->second;
for(auto p = m.begin();p != m.end();p++)
	cout<<p->first<<" "<<p->second;

这样输出的就是map中所有的键值和value值
PS:auto:声明。其可以让编译器根据初始值类型直接推断变量类型,用在迭代器中,可以代替一大长串的迭代器类型声明。

c++中的sort

sort函数是c++中笔者认为最好的函数了,我们平时好多题目或者大作业都会有排序的情况,手写快排,基本上得调试好久,冒泡或者选择又太慢,所以sort内置写好了快排(O(logn)),还是比你手写的快排好上挺多得快排。笔者入坑c++完全是因为sort得方便。下面介绍sort得用法


sort(待排序容器头元素位置,待排序容器尾元素后一个位置,排序规则函数名);

PS:这里对应上面end方法为什么对应容器尾元素后一个元素的迭代器
先说简单得把,比如我有一个数组num,我要对其第0个到第9个数字进行排序,那么我可以这样写

sort(num,num+10);
sort(num,num[10]);
sort(num,num+10,greater<int>() );
sort(num,num+10,less<int>() );

其中第一个和第二个一样,都会把num中得第0个到第9个数字进行排序,顺序是从小到大(默认)。而如果第三个参数传入的话,会按照第三个参数的规则进行排序,上面第三个传入的是greater<int>(),会把数字按照从大到小排序,第四个传入less<int>();会按照从小到大排序。
当然,也可以自己写排序规则,比如

bool cmp(int a,int b){
	return a>b;
}

如果sort(num,num+10,cmp);这样,就会按照从大到小的顺序排序。
当然,如果有双关键字或者多关键字排序,也可以,比如

struct node{
	int num;
	string s;
}

bool cmp(node a,node b){
	if(a.num!=b.num)
		return a.num>b.num;
	else
		return a.s>b.s;
}

这样排序后,就会按照num先排序,num相同的情况下,再按照s排序

c++里的重载运算符

重载运算符比较常用的地方是复数运算,矩阵运算和图论上向量的运算等一些结构上的运算,比如图中的坐标,对每个运算去写一个函数,每次调用时都用函数,写起来复杂还不容易懂,这里如果对坐标结构来说用±*/符号就好理解一些,所以就有了重载运算符,下面是重载运算符语法:

返回类型 operator重载符号(const 类型& 参数1名,const 类型& 参数2名){
	运算规则;
}

下面以向量来举例

struct point{
	int x,y;
}

point operator+(const point& a,const point& b){
	point ans;
	ans.x=a.x+b.x;
	ans.y=a.y+b.y;
	return ans;
}

int operator*(const point& a,const point& b){
	return a.x*b.x+a.y*b.y;
}

int main(){
	point p1,p2;
	p1.x=1;
	p1.y=2;
	p2.x=2;
	p2.y=1;
	point p_plus;
	p_plus=p1+p2;
	int num;
	num=p1*p2;
}

定义了一个point的结构体,重载+,*运算符,两个向量相加,返回的应该是个point的类型,其x值应为+号前后的x值相加,其y值应为+号前后的y值相加。同理,两个向量相乘就如上述代码所示。

同理,在sort内,可以不传比较函数的参数,而重载运算符<达到比较的目的。

c++里的 lower_bound

lower_bound有一个兄弟upper_bound,但后者不常用,lower_bound的功能是返回非递减序列内的第一个大于等于传入元素的位置,后者是返回大于。两者都用的是二分的方法,时间复杂度O(logn),在线性查找方面速度很快,但传入的序列必须为有序序列(从小到大)。语法如下

lower_bound(容器.begin(),容器.end(),带查找元素);

PS:如果是自己构建的结构体,就要重载<运算符以确保其比较时的正确性。
一般来说lower_bound都要配合者sort使用。

c++里的priority_queue

priority_queue是优先队列,特别好用,其复杂度只有O(logn),并且可以快速得到最大元素,其内部实现为大小顶堆,其在queue的头文件中,语法如下

priority_queue<参数类型> q;
priority_queue<参数类型,容器<类型>,less|greater<类型> > q;

第一个是比较简单的,创建后是大顶堆,而如果想要创建小顶堆的话,需要按照第二种定义队列,容器一般为vector,less和greater决定其是大还是小,类型如果是自定义的话则需要重载判断运算符。
常用方法

q.empty();//如果队列为为空,返回真
q.pop();//pop出第一个元素
q.push(元素);//压入一个元素
q.size();//返回队列大小
q.top();//返回队列中第一个元素

结语

写了好久终于写完了,可能还有没写上去的待补充。
在结尾说几句话,编程是一门实践性很强的课程,它和理论课程不一样,背背知识点就行了,还需要亲自上手把代码敲出来,理解每种数据结构的抽象含义,思考每种数据结构的实现方法,手敲出每种数据结构,才是学数据结构的正确方法,如果一味的搬代码,Ctrl+c&Ctrl+v(虽然码农确实是代码的搬运工),但基础知识你都搬运,以后工作后怎么期望你能写出足够好的代码呢?
另外作为计算机类专业的学生,自学能力以及查资料的能力是很重要的,课本上的知识固然重要,但课本毕竟是几年前编的,一本书从创作直到送到你手里做较次啊起码要经过5年,这5年的迭代更新,就让书本上的一些知识被淘汰了,所以查找最近的资料是很必要的技能。最好的方式是直接去官网看官方文档或者直接看源码,你可能会说,官方文档都是英文,我英语不好看不懂,源码我也看不懂。我想说的是,笔者英文也不好,但还是会偶尔去官方文档看看瞅瞅的(谷歌翻译是个好东西),或者去查中文翻译(虽然翻译也会有些延迟),但最重要的是学习,跟紧潮流,跟紧时代。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值