STL 指的就是C++的 标准模板库(Stand Template Library),所谓模板,是指不必预先制定类型的函数或类,STL 为用户提供了多种名为容器( Container ) 的类包括 动态数组 、 表 、 栈 、 队列等数据结构,可以说它是我们平时刷题的利器,本篇针对于算法刷题,总结自己常用到的一些简单的使用教程以及和 使用C的不同之处,由C到C++的一个完整的过渡。主要是针对学完C想使用C++的小伙伴~省的去看一堆厚厚的,而且都写的都是关于C++特性的东西,顺便自己做一个总结。
由C使用到C++我们需要了解,C与C++有什么区别, C++中有什么好用的特性,下面一一具体说到
从使用C到C++的这个过渡,那么我们就从程序的最起始的部分开始,看看C和C++区别在那里 以一个简短的a+b问题为例:
#include
int main(){
int a, b;
while(scanf("%d%d", &a, &b) == 2) printf("%d\n", a + b);
return 0;
}
??? 这么一看? 貌似没什么区别呀? 注意头文件,之前在C中使用的头文件是
stdio.h
,而现在换成了stdio
,这就是使用C++的区别之一,实际上stdio.h是依旧存在,但是在C++中推荐是用的是cstdio
,同理,string.h
就变成了cstring
、math.h
变成了cmath
、ctype.h
变成了cctype
, 所用以后使用原来C的头文件的换成一下的写法:
#include
#include
#include
#include
在C++简化了输入输出的形式 ,不用去考虑输入输出的信息的类型 ,我把刚刚 的那个程序用C++的格式写一下看看区别
#include
using namespace std;
int main(){
int a, b;
while(cin >> a >> b) cout << a + b << endl;
return 0;
}
这样一看,代码清爽了许多,但是可能也有不熟悉C++的小伙伴不太清楚,
#include
,using namespace std
是个啥?iostream
用中文解释就是输入输出流(output input stream) 对应了C中的stdio.h
标准输入输出(Standard input output) ,其实他们就是同样的意思,用来做输入输出的,using namespace std
留到一会再详细解释 ~
scanf
、printf
在stdio.h
中,cin
、cout
在iostream
中,cin
和cout
简化了scanf
、printf
输入输出方式,cin >> a
对应了scanf("%d", &a)
,cout << a
对应了printf("%d", a)
, 而且使用cin
、cout
不用像在C中那样去写出对应输入输出%d
、%f
、%c
这样的数据的类型。输出时的
endl
表示此行结束(end of line),其实就是换行和C中\n
效果一样 为了简洁一点出现变量时使用cout << a << endl
,字符串就是使用cout << " hello\n"
注意 :
cin
、cout
的速度是比scanf
、printf
慢的,具体想了解的话可以看看这道题就能体现的出来 ,在某些题上的特定情况会出现超时的现象- 使用C++以后,并不是使用
cin
、cout
一直都很好,这个要根据具体的情况决定,如果有的题目需要输出带几位特定小数的的情况,此时cout
貌似就没有那么好用啦~例如某个数字前特定的有几个零时printf("%04d",15)
, 或者输出有多个空格等其他特殊格式时,也使用printf
其实翻译过来就是使用名称空间
std
,因为在C++中都会出现cin
、cout
这样的语句 输入,输出的本身的写法的是std::cin
、std::cout
如果的函数有多个输入输出的话这样写就非常繁琐,所有我们会加上using namespace std
来去简化写法,当然啦~ 你不加上那句话可以的,那天它就是这个样子的~
#include
int main(){
int a, b;
while(std::cin >> a >> b) std::cout << a + b << std::endl;
return 0;
}
C++中有出现了一个C中没有的变量
bool
它叫做布尔,其实就是和java
中 的boolean
是一个东西啦~ 它只有两个值——true
、flase
,不同的就是bool
也可以用数字来表示,非零的数字表示true
,零表示false
。
string的出现可以说是大大简化了输出字符的过程,在C中只有
char
类型的数据,如果你想使用输出string这样的格式,那么你就是得使用指针(char *)或者二维数组表示,非常麻烦, 我们看看在C++中使用的吧~
string的赋值其实和其他的变量赋值没有什么区别,但是它也可是使用构造的方式给他赋值(虽然这样的方法用的少)
string s ("hello"), s1 = " hello~~";
cout << s << s1;
//直接使用 + 就可以拼接字符串非常简洁
string s1 = "hello", s2 = "world";
string s = s1 + s2;
cout << s;
//通过size() 和length()都能获取长度
string s1 = "hello", s2 = "world";
string s = s1 + s2;
cout << s.size() << s.length();
一般情况下:
/**
string的比较是使用 >, <, <=, >=, ==, != 这样的符号来表示的
string通过字典的顺序从前到后比较,遇到不同的字符比较字典序,
字典序靠前的的字符小,靠后的就是字符大(理解为按照ASCII),出现不同时通过本次的不同比 较出当前字符串的大小
**/
string s1 = "ab", s2 = "aca";
if(s1 > s2) cout << s1;
else cout << s2; //明显的输出的就是aca
出现字符一致长度不同时
string s1 = "aa", s2 = "aaa";
if(s1 > s2) cout << s1;
else cout << s2; //输出aaa
出现大小写时
//出现大小写通过ASCII比较,A的ACSII码为65 a为97
string s1 = "Acz", s2 = "acZ";
if(s1 > s2) cout << s1;
else cout << s2; //输出acZ
string字符串的比较用的还是比较多的,例如如果出现学生的成绩相同, 通过他们的名称自典顺序比较~~,以后刷题上会遇到类似的题目。
在处理 字符串的时候常常会用到字符串的子串,下面看看字符串的子串是怎么使用的吧
截取子串的一般是有两种用法
string t, s = "hello!";
//截取字符串的下标为0开始,2个字符
t = s.substr(0,2);
cout << t << endl; // he
//下标为1到字符串尾
t = s.sbustr(1);
cout << t << endl; // ello!
C++中大小写通过
tolower
,toupper
方式转化大小写,目前提供一种基本的方式,其他方法后续提到。
#include
#include
using namespace std;
int main(){
string s = "hello";
for(int i = 0; i < s.size(); i++){
//转化为大写 对于单个字符
s[i] = toupper(s[i]);
//转化为小写
s[i] = tolower(s[i]);
}
cout << s;
return 0;
}
使用输入时大概三种情况
使用cin
/*对于不同的情况输入字符,使用`cin`的方式中间出现空格就会分隔 ,即空格为分隔符,前后表示 s1, s2 */
string s1, s2;
cin >> s1 >> s2;
cout << s1 << s2;
使用getline
/*对于某种特殊的情况你也可能使用读入一整行的字符,这个方法通`getline`实现*/
string s;
getline(cin, s);
cout << s;
同时使用时
注意:同时使用
cin
和getline
时刚刚接触容易出现错误,使用cin
后通过回车读入字符,在getline
中第4行相当于无效了,通过添加getchar()
出去回车,这个地方不细心的话就常常出现错误,尤其实在作题的时候,可以把getchar()
去掉看看输出是什么结果
string s1, s2, s3;
cin >> s1 >> s2;
getchar();
getline(cin,s3);
cout << s1 << s2 << s3;
string的输出主要两种方式
使用cout
string s = "hello";
cout << s;
使用printf
/*使用printf ? C中并没有string这个类型啊 那么他输出
的类型是什么呢? 猜测按照之前的方式就应该是%吧*/
string s = "hello";
printf("%s\n", s);
//按照这样的输出方式,其实是错的,不知道的这个细节的话可以亲自试试
//正确做法是
string s = "hello";
printf("%s\n", s.c_str());
/*可能会有疑问为甚么 要用这么繁琐的方式呢?本来不就有
简单的方法吗?原因和使用scanf一样 主要还是因为遇到特
殊格式时使用,比如多种同时输出多种格式的数据时添加空格
使用cout在一行中写的太长~ ,其次是速度快
*/
在C++中添加了一个引用型的数据类型符号表示为
&
, 你能你觉得这为什么和取地址的符号一样?其实引用是在变量前加上&
符号,传出参数就像这样int &a
,不知道它怎么使用也没关系我们从C中的swap的那个示例说起
使用C交换两个变量的值(有误)
#include
void swap1(int a, int b){
int t = a; a = b; b = t;
}
int main(){
int a = 6, b = 10;
swap1(a, b);
printf("%d %d", a , b);
//毫无疑问 ,输出为 6 , 10 我就不多解释啦~
return 0;
}
使用C交换两个变量的值
其实真正想要使用函数的方式交换了两个数的值,是要使用指针的,而使用指针的这种凡是刚刚接触还是不好理解的~
#include
//这里理解为声明
void swap1(int *a, int *b){
//这里的*a可不是函数参数中的那个*a哦~,此处*a意思时解引用
int t = *a; *a = *b; *b = t;
}
int main(){
int a = 6, b = 10;
swap1(&a, &b);
printf("%d %d", a , b);
//输出为10 6
return 0;
}
使用C++的引用方式
刚刚我们使用指针的方式完成了交换,但是多少还是有些麻烦的,接下来看看使用引用的方式怎么实现值的交换
#include
#include
using namespace std;
void swap1(int &a, int &b){
int t = a; a = b; b = t;
}
int main(){
int a = 6, b = 10;
swap1(a, b);
printf("%d %d", a , b);// 10 6
return 0;
}
通过C++中使用引用的方法,在参数名前添加
&
,表示这个参数按照传引用(call by reference)的方式, 而不是C中(call by value),其实类似于指针的功能,意思就是按照值传递他就是单向的的传递,指针双向传递,而引用代替了C中的指针,通过传引用的凡是改变了实参的值
C++中省略了结构的声明的写法,当结构题定义完,直接写名字,不用去写
struct
啦~ ,如果在C中想不写struct
,你还用要用到typedef
#include
#include
using namespace std;
struct stu{
string name;
int score;
};
int main(){
stu sarr[5]; //C++中的声明方式
struct sarrt[5]; //C中的声明方式
return 0;
}
本篇首先引出基本vector,set,map,stack,queue这些作题中常常用到的集合及一些简单的使用,后续会在第二篇中总结全面点的使用方式,以及C++ 11中的特性。
vector的理解
vector意思就是矢量,其实vector就是动态数组,它的长度时不确定的,可以不用特意声明vector的长度,相比与 C中的数组我们必须要对它声明长度使用vector的时候需要引用头文件
#include
,using namepace std
vector主要的成员函数
函数名 | 功能 | 复杂度 |
---|---|---|
size() | 返回向量的个数 | O ( 1 ) {O(1)} O(1) |
push_back(n) | 在向量的末尾添加n | O ( 1 ) {O(1)} O(1) |
begin() | 返回指向向量开头的迭代器 | O ( 1 ) {O(1)} O(1) |
end() | 返回指向向量末尾的的后一个迭代器 | O ( 1 ) {O(1)} O(1) |
insert(p, x) | 在向量P的位置插入x | O ( n ) {O(n)} O(n) |
erase§ | 删除向量中位置p的元素 | O ( n ) {O(n)} O(n) |
clear() | 删除向量中所有的元素 | O ( n ) {O(n)} O(n) |
pop_back() | 删除向量的最后一个元素 | O ( 1 ) {O(1)} O(1) |
resize() | 给向量分配大小 | O ( 1 ) {O(1)} O(1) |
具体使用方法
声明
#include
#include
#include
using namespace std;
int main(){
//定义一个不定长度vector v1
vector<int> v1;
//定义v1并指定v2大小为5 ,没有被赋值的元素默认为0
vector<int> v2(5);
//定义v3的长度为3,默认值全部为 4
vector<int> v3(3, 4);
vector<int> v4;
//将不定长的vector设置长度为6,默认初始化的值为0
v4.resize(6);
//这个要和v2区分一下 意思就是每个v5[i]都是一个vector
//理解成二维数组
vector<int> v5[2];
return 0;
}
基本函数使用
#include
#include
#include
using namespace std;
void dispaly(vector<int> v){
//遍历vector
for(int i = 0; i < v.size(); i++){
cout << v[i] << " ";
}
cout << endl;
}
int main(){
//声明一个vector v1 没有分配大小
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
dispaly(v1); // 1 2
//使用数组的形式
v1[1] = 3;
dispaly(v1); // 1 3
v1.insert(v1.begin() + 1, 9);
//尾部插入2 个 1
//v1.insert(v1.end(), 2, 1);
dispaly(v1);// 1 9 3
v1.erase(v1.begin() + 1);
dispaly(v1); //1 3
cout << v1.size() << endl; // 2
v1.resize(6);//没有被赋值的元素默认为0
cout << v1.size() << endl; // 6
dispaly(v1);//1 3 0 0 0 0
return 0;
}
遍历
#include
#include
#include
using namespace std;
int main(){
vector< int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//1. 通过数组的形式遍历
for(int i = 0; i < v.size(); i++){
cout << v[i];
}
//2.通过迭代器的方式遍历后两种使用的少
for(vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout << *it;
}
//3.简化迭代器的写法C++11中的特性 编译器根据初始值的类型直接判断变量的类型
for(auto it = v.begin(); it != v.end(); it++){
cout << *it;
}
//4. 使用ForEach
for(int vt : v){
cout << vt;
}
//使用引用的方式可以修改它的值
for(int &vt : v){
vt *= 2;
}
//5.同样的使用ForEach,C++11中的新特性
for(auto it : v){
cout << it;
}
return 0;
}
注意:如果之前没有了解C++的小伙伴看到第二种遍历的方法时,可能不知道
vector
那个是什么东西,什么意思,::iterator iterator
的意思就是迭代器,它就类似于指针,理解为指针就OK啦~不太清楚的就看看接下来时怎么写的 ,还有不小心遍历扯的有点多了刚刚接触的话以看遍历就这么多,那不是心态炸了其实用的最多就是第一个遍历方法,第二个在vector中不用就是为了后面的内容铺垫,后面方法也不用重点看知道有就行,第二篇我们重点谈这个内容,目前先理解 这个iterator
, 用C写出遍历的意思如下
#include
//1. 用指针求和
int getsum1(int* begin, int* end){
int len = end - begin, sum = 0;
for(int i = 0; i < len; i++){
sum += begin[i];
}
return sum;
}
//2. 用指针求和
int getsum2(int* begin, int* end){
int sum = 0;
for(int *it = begin; it != end; it++){
sum += *it;
}
return sum;
}
// 这个就和那个iterator十分的类似,理解为指针就是这个意思
void disply(int* begin, int* end){
for(int *it = begin; it != end; it++){
printf("%d ", *it);
}
}
int main(){
int a[5] = {1, 2, 3, 4, 5};
printf("%d\n", getsum1(a, a + 5));
printf("%d\n", getsum2(a, a + 5));
disply(a, a + 5);
return 0;
}
set的理解
set 是根据元素值进行排序的集合,所插入的元素在集合中唯一,不存在重复元素
set的常用函数
函数名 | 功能 | 复杂度 |
---|---|---|
size() | 返回set中元素的个数 | O ( 1 ) {O(1)} O(1) |
clear() | 清空set | O ( n ) {O(n)} O(n) |
end() | 返回指向末尾的迭代器 | O ( 1 ) {O(1)} O(1) |
begin() | 返回指向开头的迭代器 | O ( 1 ) {O(1)} O(1) |
erase(key) | 删除key | O ( l o g n ) {O(log_{n})} O(logn) |
find(key) | 搜索key返回指向key的迭代器 | O ( l o g n ) {O(log_{n})} O(logn) |
insert(key) | 向set中插入key | O ( l o g n ) {O(log_{n})} O(logn) |
使用方法
#include
#include
#include
using namespace std;
int main(){
set<int> s;
//插入
s.insert(1);
s.insert(2);
s.insert(3);
//查找元素4
if(s.find(4) == s.end()){
cout << "Not Exist!";
}
//查找 1 这个元素并输出, (s.find(1) != s.end())表示可以找到1这个元素
cout << (s.find(1) != s.end()) << end;
//删除2
s.erase(2);
//set集合的遍历,类比vector,看成指针,不理解可以看看我之前用C的那个遍历
for(set<int>::iterator it = s.begin(); it != s.end(); it++){
cout << *it;
}
//C++11新特性
for(auto it = s.begin(); it != s.end(); it++){
cout << *it;
}
//清空s中的元素
s.clear();
return 0;
}
map理解
map就是从键(key)到值(value)的映射, map 集合以键与值的组合为元素,每个元素拥有 1 个键和 1 个值,集合以值作为排序标 准。集合中各元素的键唯一,不存在重复。map很好用,他就像增强版的数组一样,例如我这么定义
map
(string 是key, int是value),赋值的时候就可以这样m m["student"] = score
,其他类型同理~
map的常用函数
函数名 | 功能 | 复杂度 |
---|---|---|
size() | 返回map中元素的个数 | O ( 1 ) {O(1)} O(1) |
clear() | 清空map | O ( n ) {O(n)} O(n) |
end() | 返回指向末尾的迭代器 | O ( 1 ) {O(1)} O(1) |
begin() | 返回指向开头的迭代器 | O ( 1 ) {O(1)} O(1) |
erase(key) | 删除key | O ( l o g n ) {O(log_{n})} O(logn) |
find(key) | 搜索key返回指向key的迭代器 | O ( l o g n ) {O(log_{n})} O(logn) |
insert(key,value) | 向map中插入(key,value) | O ( l o g n ) {O(log_{n})} O(logn) |
map的基本使用
#include
#include
#include
using namespace std;
int main(){
//定义
map<string,int> m;
//添加元素
m["steves"] = 85;
m["alan"] = 95;
//插入元素
m.insert(pair<string, int>("emma", 90));
//删除
m.erase("emma");
//查找
pair<string, int> t = *m.find("alan");
cout<< t.first << " " << t.second << endl;
//遍历
for(map<string, int>::iterator it = m.begin(); it != m.end(); it++){
cout << it->first << " " << it->second << endl;
}
for(auto it = m.begin(); it != m.end(); it++){
cout << it->first << " " << it->second << endl;
}
for(auto mt : m){
cout << mt.first << " " << mt.second << endl;
}
return 0;
}
stack理解
stack 简单来说就是一个先进后出的数据结构 ,c实现栈的话就要手写,C++中引入
#include
即可
stack中常用的方法
函数名 | 功能 | 复杂度 |
---|---|---|
size() | 返回栈的大小 | O ( 1 ) {O(1)} O(1) |
top() | 返回栈顶元素 | O ( 1 ) {O(1)} O(1) |
pop() | 栈顶元素弹栈 | O ( 1 ) {O(1)} O(1) |
push(x) | x入栈 | O ( 1 ) {O(1)} O(1) |
empty() | 判空 | O ( 1 ) {O(1)} O(1) |
使用方法
stack的使用的还是非常广泛的,比如经典的图的深度遍历,我用一个简单10 转 2概括一下stack使用
#include
#include
#include
using namespace std;
int main(){
//声明
stack<int> s;
//压栈
s.push(1);
//栈的长度
cout << s.size() << endl;
//弹栈
s.pop();
int n = 105;
while(n > 0){
s.push(n % 2);
n /= 2;
}
// 判空
while(!s.empty()){
//输出栈顶元素
cout << s.top();
s.pop();
}
return 0;
}
队列的理解
队列就是一种先进先出的数据结构,C中也需要手写,C++中引入
#include
即可
队列的常用方法
函数名 | 功能 | 复杂度 |
---|---|---|
size() | 返回对列的大小 | O ( 1 ) {O(1)} O(1) |
front() | 返回队头元素 | O ( 1 ) {O(1)} O(1) |
pop() | 取出队头元素 | O ( 1 ) {O(1)} O(1) |
push(x) | x入队 | O ( 1 ) {O(1)} O(1) |
empty() | 判空 | O ( 1 ) {O(1)} O(1) |
back() | 对位元素 | O ( 1 ) {O(1)} O(1) |
queue中的使用
同样的queue的使用也是非常广泛的,比如图的广度遍历有兴趣可以看看,我在这里就简单演示一下啦
#include
#include
#include
using namespace std;
int main(){
queue<int> q;
for(int i = 0; i < 10; i++){
//入队
q.push(i);
}
cout << q.size() << endl;
for(int i = 0; i < 10; i++){
//队头 队尾
cout << q.front() << " " <<q.back() << endl;
q.pop();
}
if(q.empty()) cout << "Not exist any elements\n";
return 0;
}
本篇主要写了由C到C++的过渡之间存在的差异,以及刷题中常常用到STL集合的基本使用,不是很全面。第二篇将对vector、set、 map的使用做个补充,以及其他类型做个全面补充例如 list unorderedmap,STL其他实用的库 例如algorithm等,还有C++11中好用的特性做一个全面的补充。