C++入门与STL

C++具有比C更加庞大的函数库和更快捷的处理方式,但在算法竞赛中我们一般用不到C++中一些复杂的东西,相比之下,我们用的更多的是C++与C语言中重叠的部分,以及少量的标准模板库(Standard Template Library)俗称STL

新的改变

首先我们要将文件的后缀由.c改为.cpp。然后平时使用的头文件<stdio.h><ctype.h>等均改为< cstdio> < cctype>等,同时还有一些新的头文件的加入。
对于算法竞赛来说,c++还有一个万能头文件#include<bits/stdc++.h>

基本框架

#include <cstdio>
#include <iostream>//包含了c++中的一些输入输出流;
using namespace std;//改进输入输出的方式;
int main() {
  // do something...
  return 0;
}
**基本框架与C比起来十分相似,事实上,C++中的大多数都是与C语言相同的;**

新的输入输出方式

#include <iostream>
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)//加快输入输出的速度
int main() {
   
  int x, y;                          // 声明变量
  std::cin >> x >> y;                // 读入 x 和 y
  std::cout << y << std::endl << x;  // 输出 y,换行,再输出 x
  return 0;                          // 结束主函数
}
如上图,std::cin>><<是c++中崭新的输入输出方式,它可以避免使用%d等占位符,当然,原先的C中scanf与printf的方式依然可以使用。
这种新增的输入输出方式最大的弊端就是会耗费一定的时间(除非特别要求,否则绝对不多)。
而上面提到的using namespace std;在此声明后,输入输出会变得更加简洁;

```cpp
using namespace std;
int main()
{
   
int x,y;
cin>>x>>y;
cout<<x<<y;
return 0;
}```

引用

*在C++中新增了引用的方法,主要运用在函数传参方面,与C语言的指针有些类似,但C++也有指针(几乎与C相同),引用只不过是新增的方式,使程序写起来更加方便,具体代码如下:

#include<iostream>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
void swap2(int& a, int& b) {
   
int t = a; a = b; b = t;
}
int main() {
   
int a = 3, b = 4;
swap2(a, b);
cout << a << " " << b << "\n";
return 0;
}

**只需在传参时函数上加上&符,就能做到传入实参,在函数中改变a,b的值也会影响原来的值;它可以在一定程度上代替C中的指针。
而之所以函数名采用swap2而不用swap,是因为swap已经包含在c++的库中,是c++自带的交换函数,举个例子:
**`

#include<bits/stdc++.h>
using namespace std;

int main() {
   
int a = 3, b = 4;
swap(a, b);
cout << a << " " << b << "\n";
string x="hfx";
string y="sllh";
swap(x,y);
cout<<x<<" "<<y<<endl;//endl用在这里指另起一行类似于“\n”.
return 0;
}`

当中用的string,我们在下面进行介绍;

迭代器

迭代器(iterator)是一种可以遍历容器元素的数据类型,是针对于stl容器的一种变量,与C语言中的指针类似(当然C++也有指针)。
迭代器的模板如下

std::vector<int> ::iterator it;     //it能读写vector<int>的元素
std::vector<int>::const_iterator it;//it只能读vector<int>的元素,不可以修改vector<int>中的元素
//如果有using namespace std,std::可省略;
//vector<int >可根据不同需要更改容器名和类型名;

就像在c语言中用指针遍历数组一样,迭代器也是这样遍历容器.。

for( it = vector.begin(); it != vector.end(); it++ )
	cout<<*it<<endl;
//当然还可以反向迭代;
 for( std::vector<int>::reverse_iterator it = v.rbegin(); it!=v.rend();it++ )
    cout<<*it<<endl;
//reverse_iterator就是反向迭代器,这里的++就相当于往前走;

这里看不懂很正常,只需知道有这个东西,在后面看到相关在回来看看.。(注意,并不是所有的STL容器都有迭代器)!

字符串string

C语言中的字符串就是字符数组,在C++中依然有字符数组这种形式。字符数组的通常用来输入字符串,对于平时操作比较麻烦,比如:

char zfc1[10]="114",zfc2[10]="514";
strcat(zfc1,zfc2);//链接两个字符串;

几乎对字符串的操作,不是循环就是函数,而在C++中新增了一种字符串方式string,就带来了很多方便的操作;同时它需要头文件< string>(用万能头的时候就不用加了)。

输入

首先我们先来介绍一下字符串在C++中的输入;除了原本的scanf(“%s”,zfc1)外,我们还可以用cin<<zfc1;但是,它的缺陷还与C语言一样,遇到空字符就停下了。例如输入eiyou niganman;实际上保存的只有eiyou;在c++中又提供了两种读字符串的方式getline()和get();而getline对于输入字符数组char[]与字符串类型string又有所不同,在此,我们只介绍string中的;
getline();
模板;getling(cin,字符串名);与gets()的效果差不多,自动将‘\n’转换为’\0’,即不保存回车;string str; getline(cin,str);

String类

与日常声明变量一样,在定义的字符串名称前加string

#include<iostream>
#include<string>
using namespace std;
int main()
{
   
	char charr1[100] = "hi ";
	char charr2[100] = "csdn";
	char charr3[100];
	string str1 = "hello ";
	string str2 = "world";
	string str;
	/*赋值操作*/
	*charr3 = charr1;***//错误,根据数组学习,数组之间不可直接赋值**
	strcpy(charr3,charr1);//正确,赋值charr1给charr3
	str = str1;//正确,string是一个类,str为对象,对象之间可以赋值
	/*拼接操作*/
	str = str1 + str2;//此时str为 "hello world"
	/*附加操作*/
	strcat(charr1,"xiao mei");//charr1此时为"hi xiao mei"
	str1 += str2;//此时str1为"hello world"
}

看了上面的代码,大家可能对string有了一定的了解,相当与直接声明了一个字符串,无需声明大小,系统自动截取。之后从上面的代码我们可以看出来,无论是赋值还是拼接,都比字符数组要方便的多;
下面我们再来介绍一些特性:
1.string(size_type n,char c) :创建一个包含 n 个元素的 string 对象,其中每个元素都被初始化为字符 c

string str(10, 'a');

2.获取string对象的长度,C语言中使用strlen()来获取字符串长度,C++中使用str.size()或str.length().

string str("hello!");//这也是一种string初始化的方式
int len1 = str.size();
int len2 = str.length();

3.string.append() 函数,在string对象后添加一个string对象或c风格字符串。

string str("hello");
string str2("world");
str.append(str2);
str.append("abcd");

4.string.push_back() 函数来在一个 string 对象后面附加一个字符

string str("hello");
char ch = 'a';
str.push_back(ch);

5.对于string对象的比较,可以直接使用关系运算符。

string str1("abcd");
string str2("abcd");
if(str1 == str2)
	break;

6.访问 string 字符串的元素

string str("hello");
cout << str[2] << endl;
cout << str.at(2) << endl;

7.string中的find()函数
(1)string的find()函数用于找出字母在字符串中的位置。

find(str,position);//str是要找的字符串;
//position:字符串中的某个位置,表示从从这个位置开始的字符串中找指定元素(不填第二/个参数,默认从字符串的开头进行查找)

返回值为目标字符的位置(第一个字符位置为0),当没有找到目标字符时返回npos(就是返回npos,例如if(str.find(x)!=steing::npos)就是没找到的意思)
(2) 延伸用法
找到目标字符第一次出现和最后一次出现的位置

string s = "hello world!";
cout <<s.find_first_of("l") << endl;//第一次出现的位置
cout << s.find_last_of("l") << endl;//最后一次出现的位置

反向查找

string s = "hello world!";
cout << s.rfind("l") << endl;//即从后往前第一次出现"l"的位置

最后,如果要清空string中的字符串可以用strName.clear()函数;
这里只是对string做了一个简单的介绍,都是平时在做题中经常用到的,如果想了解的更仔细的话可以自行查询,这里不再过多赘述了。

结构体的运用

C++中的结构体与C中略有不同,在这我们只说一小部分经常用到的内容。结构体(struct),可以看做是一系列称为成员元素的组合体。可以看做是自定义的数据类型。在 C++ 中 struct 被扩展为类似 class 的类说明符。class是与结构体相似的一个类。在工程中,一般用struct定义“纯数据”的类型,只包含较少的辅助成员函数,而用class定义“拥有复杂行为”的类型。为了简单起见,我们在这里只谈论struct;
C++中的结构体除了可以拥有成员变量(用a.x的方式访问)之外,还可以
拥有成员函数(用a.add(1,2)的方式访问)。

#include<iostream>
using namespace std;
struct Point {
   
int x, y;
Point(int x=0, int y=0):x(x),y(y) {
   }
};
int main() {
   
Point a, b(1,2);
a.x = 3;
cout << a+b << "\n";
return 0;
}

声明Pointa,b(1,2)时,分别调用了Point( )和Point(1,2)。注意这个构造函数的两个参数后面都有“=0”字样,其中0为默认值。也就是说,如果没有指明这两个参数的值,就按0处理,因此Point( )相当于Point(0,0)。“:x(x),y(y)”则是一个简单的写法,表示“把成员变量x初始化为参数x,成员变量y初始化为参数y”。也可以写成:
Point(intx=0,inty=0){this->x=x;this->y=y;}
这里的“this”是指向当前对象的指针。this->x的意思是“当前对象的成员变量x”,即
(*this).x。
此次仅简单介绍可以在结构体中定义函数,对于C++中的结构体还有更多 的妙用,再次不做无关题目的赘述。

内存分配new delete

在C语言中,我们用malloc和free进行内存的动态分配和释放。在C++中,新增了new与delete这种分配与释放的方式。

new

new用于内存的分配,与malloc相似。
1.T *p = new T;
其中,T为任意类型名,比如int、char等等,p为类型T的指针。上面的代码会在堆上动态的分配出大小为sizeof(T)的内存空间,并且将该空间的起始地址赋值给p。
2.T *p = new T[N];
其中,T 是任意类型名,p 是类型为 T* 的指针,N 代表“元素个数”,可以是任何值为正整数的表达式,表达式中可以包含变量、函数调用等。这样的语句动态分配出 N × sizeof(T) 个字节的内存空间,这片空间的起始地址被赋值给 p。

Delete

delete用于内存的释放,与free相似。

delete p;//对应new的第一种
delete[] p;//对应第二种

注意:1.分配过的内存一定要释放;
2.new分配到不能分配时返回的不是NULL。

不定长数组vector

vector为可变长数组(动态数组),定义的vector数组可以随时添加数值和删除元素。

注意:在局部区域中(比如局部函数里面)开vector数组,是在堆空间里面开的。

在局部区域开数组是在栈空间开的,而栈空间比较小,如果开了非常长的数组就会发生爆栈。``

故局部区域不可以开大长度数组,但是可以开大长度vector。使用时需要头文件< vector>;
1.定义

vector<int>a;//定义一个int类型的不定长数组a,int可换为任意类型`
vector<int>b(n,1);//定义一个int类型长度为n的数组,里面的值都为1;
vector<int>c[6];//定义一个行数为六的二维数组;
vector<vector<int>>m;//定义一个行列都不定长的二维数组;
vector<int>s(a);//定义一个数组s与a大小和值都相同;

vector也可以想平常数组一样赋初值;

vector<int>c={
   1,2,3,4,5};

注:不定长数组并不是指它的空间无限大,而是通过插入数值来扩大空间。如果初始没赋大小,那它就是0;在有初始空间情况下可以cin输入,如果超出空间就要用到c.push_back()等函数向内填值;
2.相关函数
d在这里插入图片描述pug:这是我见过的最好一张图了,描述的很详细了,我感觉自己很难描述成这样。。。

平常访问值的时候依然可以像数组一样访问如:a[i];这里我只说这一种方式,对于未接触过c++的人来说就这样访问就可以了,很方便;(C++中常见的访问方式是迭代器访问,有兴趣可自行学习)
其实这跟平常数组也差不多,但是省了很多事,十分方便;

栈stack

栈是STL中实现的一个后进先出的容器,称作:stack。使用时需要头文件;它就像一个试管,你把一颗颗珠子放进去。要拿出来是只能先拿最上面的,也就是最后放进去的。栈就是这样一种容器。

stack<int>a;//栈的定义

之后不管是放入还是取出,都需要用到函数。
在这里插入图片描述
话不多说,直接上代码。

int main() {
   
    int a,b;
    stack<int>n;
    for (int i = 0; i < 6; ++i) {
   
        cin>>a;
        n.push(a);//在顶部添加一个元素;
    }
    cout<<n.size()<<endl;
    for (int i = 0;; ++i) {
   
        if(n.empty())break;
        cout<<n.top()<<" ";
        n.pop();//删除栈顶元素;
    }
    puts("");
    b=(int)n.size();//这里用类型转换的原因是size返回的是unsigned类型,所以转化一下。
    cout<<b;
    return 0;
}

对于栈的讲解只有这么多,是不是很简单。。。

队列

STL的队列容器主要分为三种,queue,deque,priority_queue

queue

queue队列与栈不同,秉持一个先进先出的原则。就像一个漏斗,你先放进去的,肯定先出来。学了这么多,大家应该不难看出STL容器的声明方式十分类似,下一步我们先看函数,然后直接上代码。
在这里插入图片描述注意:是从尾部添加元素,可以返回尾部和头部的元素,但只能从头部往下走(就是每次只能删除头部元素)。

int main(){
   
    queue<int>a;
    int n,c;
    cin>>n;
    for (int i = 0; i < n; ++i) {
   
        cin>>c;
        a.push(c);
    }//向容器中放入元素
    cout<<a.size()<<endl;//输出容器大小
    for (int i = 0; i <n ; ++i) {
   
        cout<<a.front()<<" "<<a.back()<<endl;
        a.pop();//删除第一个
    }//无清空函数;

    return 0;
}

deque

deque被称做双端队列,与queue不同的是,它既可以从头部插入(或删除)元素,也可以从尾部插入(或删除)元素。比起queue,我感觉它更像vector,区别在于它不能从中间插入元素,也不能用下标访问。
在这里插入图片描述其中包括begin(),end()函数返回首地址和尾地址的下一位!!!
注意:其中iterator是迭代器部分的东西,我们在前面也提到过。由于没有学习C++,从C语言 的角度来说,就像C中的指针,指向的迭代器就像地址一样。这里是说erase()中放的是地址,只要注意这一点也可以灵活运用了。
注:deque可以使用sort排序默认由小到大;

int main(){
   
    deque<int >s;
    deque<int>::iterator it;//可以不用理解(迭代器的声明,与指针类似)
    int n,x;
    cin>>n;
    for (int i = 0; i <n ; ++i) {
   
        cin>>x;
        s.push_back(x);
        s.push_front(x);
    }//我们在这里从前后同时插入元素
    it=s.begin();//可以不用理解
    s.erase(it+2,s.end()-3);//可以不用理解
    cout<<s.size()<<endl;
    sort(s.begin(),s.end());//排序
    for (int i = 0;i<n ; ++i) {
   
        cout<<s.front()<<" "<<s.back()<<endl;
        s.pop_back();
        s.pop_front();//删除最后/最前的元素;
    }
    s.clear();
    return 0;
}

priority_queue

priority_queue被称作优先队列,如果按我的理解来的话,这个东西就是自动将queue排了个序,每次输出最大或最小的那个。这是一种“堆”的容器,你可以把它理解为金字塔,每次只拿塔尖的东西(奇妙比喻)。
我们先看一下相关函数,可以先浏览一下,之后对照下面的代码理解。
在这里插入图片描述每次只能取塔尖哦!

int main(){
   
//标准的定义方式:priority_queue<类型,容器类型,greater<>或less<>>a;
    priority_queue<int >a;//默认大根堆(less<>就是越来越小,每次输出最大的),默认容器是vector
    priority_queue<int,vector<int>, greater<>>b;//小根堆
    int n,x;
    cin>>n;
    for (int i = 0; i < n; ++i) {
   
        cin>>x;
        a.push(x);//放入元素
    }
    cout<<a.size()<<endl;
    for (int i = 0; i < n; ++i) {
   
        cout<<a.top()<<" ";//每次输出的就是优先级最高的
        a.pop();//删除优先级最高的
    }
    return 0;
}

看完代码,你是不是觉得更加无法理解了,因为这个东西定义太复杂了。你先要记住,这玩意是你可以指定它的顺序和内置容器(一般是vector和deque,不能是list,初学用vector即可,模板第三行代码已给出),不用过多的理解,因为它就是这样定义的。如果你明白了定义,那么后面的就是一些简单 的应用,很容易掌握。
注:优先级可以通过重载自定义,需要一定的C++基础,而且一般情况下这两种就够用了,在这里就不过多赘述。
然而到这还没完,因为优先队列里还可以放入pair(你就理解为是一个地方放了两个值)。就像一个结构体数组,有两个值。

 priority_queue<pair<int ,int>>q;//这样定义,pair的类型可以自定义(但必须可比较大小)。
 //它内部排序就是先比前一个,如果相等再比后一个。

具体应用我们来看下。

int main(){
   
    priority_queue<pair<int ,int>>q;
    q.emplace(1,2);//插入值,这个值的前半部分是1,后半部分是2;
    q.emplace(3,4);
    cout<<q.top().first<<" "<<q.top().second;//由于是两个值,输出是就像结构体输出一样,前面的叫first,后面叫second
    return 0;
}

由于是一次进入两个值,所以我们这里用到emplace函数(不用去理解,用法就是代码里的。。)。也有其它的插入方式。

	q.push({
   7, 8});
    q.push({
   7, 9});
    q.push(make_pair(8, 7));//用法如图所示

这里你学习了很多新的函数,不用去担心,因为它就是换汤不还药,用法很朴实无华,就像你在C中有%d,%c一样,不同类型对应输入方式不同,记住就好了。

映射map

map是一种对应关系,每一个键对应一个值。

模板:map<t,t>;//t是类型
map<string,string> mp1;
map<string,int> mp2;
map<int,node> mp3;//node是结构体类型

看上面的说明,我们就大概了解了map的定义;<a,b>中的a就相当与数组arr[i]中[]的i;而b就相当与arr[i]对应的值。
那么map有什么用呢,它可以让字符对应数值mp["ikun"]=666;
而且我们知道,平时开数组时,我们能开的范围是有限的。但用map就可以让键值达到long long范围;

#define ll long long
map<ll,ll>map1;
ll arr[x];//x过大会导致运行错误;

下面我们就看看它相关的函数;
在这里插入图片描述
我们直接上代码;

#include <bits/stdc++.h>
#define ll long long
using namespace std;
map<ll,ll>c ;//<键,值>
map<string,ll>s;
int main(){
   
    int n,y;
    cin>>n;
    s["nikezhenliua"]=5;
    cout<<s["nikezhenliua"];
    for (auto i = 0; i < n; ++i) {
   
        cin>>y;
        c.insert(pair<ll,ll>(i,y));//构造键值对,使用pair
        //也可以c[i]=y;
    }
    cout<<c.size()<<endl;
    if(c.count(3))cout<<c.find(3)->second<<endl;//查找与打印;
    for ( map<ll,ll>::iterator its=c.begin();its!=c.end();its++) {
   
        cout<<its->first<<" "<<its->second<<endl;
    }//遍历输出
    cout<<c.lower_bound(4)->second<<endl;//大于等于键值
    cout<<c.upper_bound(4)->second<<endl;//大于该键值的第一个
    c.clear();

    return 0;
}

这里面有很多与之前都相似,值得注意的是,lower_bound()中放的是键值,就是前一个值;
map还有很多进阶的用法,例如二维map,一键对应二值等,有些因为太过深入,有些是因为不常用,这里就不过多阐述了。

set

set容器中的元素不会重复,当插入集合中已有的元素时,并不会插入进去,而且set容器里的元素自动从小到大排序。也就是说,set中的东西有且只有一个;
像以往的定义一样,set的定义方式没有什么特别。

set<int> s;//<>中可以是任意类型;

下面我们来看相关函数:
在这里插入图片描述
在这里插入图片描述
下面我们来看相关代码;

#include <bits/stdc++.h>
using namespace std;
#define ll long long

int main(){
   
    set<ll>shit;//默认从小到大
    set<ll,greater<> >ui;//从大到小;
    ll n,x;
    cin>>n;
    for (int i = 0; i < n; ++i) {
   
        cin>>x;
        shit.insert(x);//插入一个元素 ,像这种自动排序的容器就不用push了;
    }
    shit.erase(shit.begin());
    shit.erase(1);//erase中既可以写元素值,也可以放迭代器。
    if(!shit.empty())
        cout<<shit.size()<<endl;
    cout<<shit.count(3)<<endl;//返回3的个数,由于set不允许重复存在,所以为一;
    auto kun=shit.find(3);
    cout<<*kun<<endl;
    for (auto &it:shit) {
   //相当与定义一个迭代器it遍历shit
        cout<<it<<" ";
    }
    shit.clear();
    return 0;
}

除此之外,set还可以通过重载或函数的方式进行更复杂的排序,在此我们只做最基础的介绍

二进制容器bitset

这个容器是专门存放二进制的,对于一些复杂的二进制运算有良好的效果,它的声明和之前不太一样。

bitset<n>bit1;//n是常数或者常量表达式,表示长度为n,bit1是名字,默认初始值是0;
bitset<n>bit2(s)//s可以是一个数(自动转为二进制,前面补零),或者一个字符串(由0和1组成);

bitset声明的容器可以用下标访问,并且可以直接进行位运算。

bitset<8>bit2(14);
cout<<(bit2>>1)<<endl;
cout<<(~bit2)<<endl;
cout<<bit2[3]<<endl;
//输出为00000111
//11110001
//1

之后我们来看一下相关函数;
在这里插入图片描述
虽然看起来很多,但是都是熟悉的使用方式。注意,.size()返回的就是你初始时n的值;
我们来看代码;

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
int main(){
   //可通过下标访问
    string s;
    cin>>s;
    int x;
    const int n=10;
    bitset<n>bit1(s);
    if(bit1.any())//是否含有1;
        cout<<"1的个数为"<<bit1.count()<<endl;
    cout<<"二进制位的个数为"<<bit1.size()<<endl;

    cin>>x;

    if(bit1.test(x))
        cout<<x<<"的位置是1"<<endl;
    else bit1.set(x);
    cout<<bit1<<endl;
    cout<<bit1.to_ulong()<<endl;
    bit1.flip();
    cout<<bit1<<endl;
    cout<<bit1.to_ulong()<<endl;

    return 0;
}

下面是输出。

10101110
1的个数为5
二进制位的个数为10
6
0011101110
238
1100010001
785

其实就是相当与一个bool的数组,可以位运算,很容易理解。

STL容器其实不止这些,还有更多的容器需要大家自己去拓展,而且有很多的stl容器在功能方面有诸多重复,所以这里所介绍的都是常用的容器,只供初步的学习。
而学习的路是很漫长的,我认为不论哪一本算法书或着那一篇文章都不能让你一遍看懂,想要真正掌握一门知识还需要自己动手去写出来,每一个代码都自己去写一遍,我认为这便是最快的理解与进步方式!!!

到此为止,我们对STL 的初步介绍就完成了,其中肯定有很多不完备的地方,希望与大家共同交流与进步。
本文参考的资料有:《算法竞赛入门经典第二版》《C++ primer plus》以及众多csdn的文章
本文仅供交流学习使用