H_On个人小站

个人站

这都被你发现了【惊
奖励你一朵小红花~


StandardTemplateLibrary Plus

目录

简介

STL Standard Template Library - 标准模板库又称 C++ 泛型库,它在 std 命名空间中定义了常用的数据结构和算法,大大增加了我们编写算法的效率,使我们在摸索/学习算法或者解决问题时能够通过各种各样的容器来构造简单便利的数据结构。

其实呢~之前再怎么熟练 STL ,练习还是太少了,这个寒假我跟着几位大佬 moyechen、llb、zhouning、(>^ω^<) 学到虚脱很多。虽然大多数时候我都是跟着蹭 (*≧︶≦))( ̄▽ ̄* )ゞ ,不过自己刷水题的过程中对 STL 的掌握也可以说是更上一层楼了。实际上之前我写 STL 介绍文章的时候文件名就是 STL1 ,那么我觉得我是时候可以写 STL2 了。
那么接下来继续由猹来为大家介绍 STL 的其他用法。

getline(cin, string()) 函数

这个函数是包含在 <iostream>std 命名空间中的,用于接收 包含空格的字符串 以回车为接收停止标志。
比如如果你想接受一个包含空格的字符串那么你应该这样写

string s;
getline(cin, s);
cout << s << endl;

这样你会发现只要没有回车,你输入的字符串就算是空格也会一起接收到的,但是用 cin >> s 这个方法来接收字符串的话,只会接收到 第一个空格之前的 字符串。

要注意一点是,这个函数是可以接收 空字符串 的,也就是说,能接收一个回车。什么意思呢~如果你要先输入一个 t ,然后再输入 t 个带空格的字符串,那么你输入 t 这个整数之后肯定要加一些不能接收的字符,比如回车。这时候你就应该酱

int t; cin >> t;
string s;
getline(cin, s);
while (t--) {
        getline(cin, s);
        cout << s << endl;
}

如果你把循环外面那个 getline 删掉的话你会发现你只能输入 t - 1 个字符串。

string 的 .c_str() 方法和 sscanf() 函数

s.c_str() 这个方法是可以对于一个 string 字符串对象返回一个 “ c 语言中的字符指针” ,然而这里了解这个方法也只是为了使用 sscanf() 这个函数而已。

sscanf()

这个函数接收三个(以上)参数

  1. 字符数组的指针
  2. 一个字符串,通常来说是要自己定义直接写的匹配模式串(大概也是接受字符数组指针的)
  3. 之后有若干个以逗号分隔的变量地址。

举个栗子:某种情况下我们需要按照 年-月-日 时:分:秒 这种格式输入 6 个数据,然后进行操作。当然你可以使用传统 <cstdio> 库里的 scanf("%d-%d-%d %d:%d:%d", &y, &m, &d, &H, &M, &S) 函数来实现按格式的输入。但是当我们习惯于断开输入输出流(cin,cout)与传统 c 接收打印函数的连接并坚持使用输入输出流来操作的时候,我们就应该探索更加实用的字符串处理方式。
例:

string s; getline(cin, s);
int y, m, d, H, M, S;
sscanf(s.c_str, "%d-%d-%d %d:%d:%d", &y, &m, &d, &H, &M, &S);

要注意,sscanf后面对应的变量用的也是变量的地址,也就是说要加上取地址符(索引符 - &)。

stringstream 对象

啊~曾经的我一直很关注,也会跟别人说 C++、iostream是输入输出流的意思、istream、ostream . . . 输入输出流在我看来是 C++ 中的非常重要的概念,但是我到此时才真的第一次产生了一点感觉。
stringstream 对象就是可以操作 字符流 的东西,好用程度令我惊叹。对了这个对象定义在 <sstream> 这个库里

//字符串转数字
int string_to_int(string s) {
        stringstream ss;
        ss << s;
        int r;
        ss >> r;
        return r;
}

以上是一个简单的字符流的操作,这是最基础的使用方式,但是这就够我吹一年了好吗!!!你会发现是不是和 cin >> s; cout << s; 这俩玩意有点那个~你品,你细品。你会发现其实就是字符流可以通过将数据放入 的方式来接收和释放,而输入输出的时候跟你的 cin cout 操作方法是完全一样的!

  1. 把字符串输出到字符串流中
  2. 把字符流输入到一个变量(对象)中

这里你可以理解一下 “»” “«” 这两个流操作符,箭头的方向就是数据的流动方向

cin » a 我们用 cin 操作获取键盘输入的字符流,将获取的数据 “流入 变量 a”
cout « a 将数据以 字符流 的形式 “流入 cout” 操作,让屏幕显示 cout 中的字符流

你品 你细品

最后再给你们整几个栗子

//字符串转浮点型数据
double string_to_int(string s) {
        stringstream ss; ss << s;
        double r; ss >> r; return r;
}
//整数转字符串
string string_to_int(int a) {
        stringstream ss; ss << a;
        string r; ss >> r; return r;
}
//浮点数转字符串
string string_to_int(double a) {
        stringstream ss; ss << a;
        string r; ss >> r; return r;
}

cout « fixed « setprecision(2) « double(); 控制输出位数

这是一个小点。就是说 C 语言中的 print() 函数可以通过输出可视化的方式来很方便的控制浮点数的输出位数,而 C++ 中的输出流却由于它的智能,会自动处理掉小数点后没用的 0 ,所以经常会遇到需要控制小数点后的位数,这时候就是用这个方法即可。(这个输出流控制操作包含在函数库 <iomanip> 中)

double a = 3.50;
cout << a << endl;
cout << fixed << setprecision(2) << a << endl;

以上代码输出结果就是

3.5
3.50

记得要在头文件中加上 #include <iomanip>

二元组 pair<T, T> 的使用方法

在上一篇 STL 介绍文章 StandardTemplateLibrary 中有介绍一种容器,map 映照容器。其中 multimap 中有介绍,由于键值可以重复,所以不能用中括号形式索引,因此添加元素只能用 m.insert(pair<T, T>(key, value)) 方法来插入键值对。这里我们已经可以看出来,实际上 map 中的储存结构就是一个 二元组 ,map 只不过是自动按照二元组中的 第一个元素排序 而已。

但其实 pair 二元组 也是 C++ 新添加的一种变量类型,同样也可以当作各种容器的元素,甚至是嵌套的使用都是完全可以的。以下我会用各种例子来向大家介绍~
广搜遍历中储存坐标,可以这样:【”#” 表示墙 “.” 表示路
主函数中传入一个含有初始坐标的二元组

bfs(pair<int, int>(x, y));
void bfs(pair<int, int> a) {
        int c[4][2] = { {0, 1}, {0, -1}, {1, 0}, {-1, 0} };
        queue<pair<int, int>> q;
        q.push(a);
        while (!q.empty()) {
                a = q.front(); q.pop();
                for (int i = 0; i < 4; i++) {
                        int x = a.first + c[i][0];
                        int y = a.second + c[i][1];
                        if (bfs_map[x][y] == '.') q.push(pair<int, int>(x, y));
                }
        }
}

通过上面这个例子可以看到,二元组

  • 可以当一个单独的变量 queue<pair<int, int>> q;;
  • 可以当参数 void bfs(pair<int, int> a) {};
  • 可以当容器中的元素 queue<pair<int, int>> q;;
  • 生成一个二元组的方式 q.push(pair<int, int>(x, y));
  • 访问二元组中的元素也可以通过访问 a.firsta.second 这两个二元组对象的成员变量来获得

同样的,除了 set 集合容器中的二元组,map 映照容器中的值,由于是不可变元素,因此不能更改之外,二元组也可以直接通过 a.first = value 的方式来直接修改二元组里面的值。

最后举几个栗子

map<int, pair<int, string>> m;
//由于 set 是自动排序的,而二元组中有两个元素,与存结构体同理,必须定义一个排序规则
//这里我使其先按第一个元素降序排序,第一个元素相同再按第二个元素升序p排列
struct hon_set_cmp {
        bool operator () (const pair<int, int> &a, const pair<int, int> &b) {
                if (a.first != b.first) return a.first > b.first;
                return a.second < b.second;
        }
};
set<pair<int, int>, hon_set_cmp> s;

容器的遍历方式 - 高级 for 循环

上一章我介绍了两种容器的遍历方式,一种是对于一部分可以通过下标访问的容器,如 vector deque string 等,可以先获取容器容量然后遍历

int l = o.size();
for (int i = 0; i < l; i++) ;

对于所有容器都可以通过迭代器来遍历

Object<T>::iterator p;
for (p = o.begin(); p != o.end(); p++) ;

但是逐渐写的多了就感觉,这两行代码好长啊~尤其是有时候我整一个元素非常复杂【长】的容器,写起迭代器来就很难受,然后如果变量太多太长,比如

multimap<int, pair<int, string>>::iterator p;
for (p = mama.begin(); p != mama.end(); p++) ;

就很难受了
所以我这里介绍一种非常方便的遍历方式:

for (T a : o) ;

这种遍历方式的意思是,我定义一个 与要遍历容器中元素相同 的遍历,然后 冒号 后跟一个容器名,这样 for 循环就能自动的将 o 中的元素依次存入 a 中,然后你就可以自由遍历了。

string s; cin >> s;
for (char c : s) ;

vector<string> v; //假装往 v 中存入一系列字符串
for (string s : v) ;

multimap<int, pair<int, string>> m; //假装往 m 中存入一系列 int - pair<int, string>  的键值对
for (pair<int, pair<int, string>> p : m) ;

如果不是需要修改原数据,或者需要使用遍历的整数 i 做一些其他事情,就是说只需要遍历整个容器的内容,这种 for 循环无疑是最方便的写法了,H_On 猹在此安利。

碎语

  1. map 中的元素默认是按键的值升序配列的,想要降序排列可以仿照上一篇STL介绍中最后那里的方法自己加一个比较结构体,也可以直接这样写 map<int, int, greater<int>> m; ,有没有方便很多呢。 2.string 对象虽然没有像 python 中一样那么方便的切片操作,但是也可以通过这个方法来生成一个字符串切片 s.substr(startPoint, Length) 。这个方法接收一个片段的第一个字符的下标,和需要的字符串的长度(都是整数),最后会返回 s 中 [startPoint, startPoint + Length) 这一部分的字符串(注意是左闭右开哟)

闲言

本篇介绍的一些函数和方法以及操作,确实可以称得上是进阶的部分了,不过也只是介绍了我最近新学到的而且用的特别特别多的一些操作,C++ 一定还有很多其他的方法,但是对于我们来说好不好用,是不是是真的很常用就不一定了,以后如果有新的进步猹还会更新的。

顺便有些东西到底算不算是 STL 的范畴呢,说实话我也不知道,只是真的好用,真的有用,我就一并介绍了,如果有错欢迎指正,期待与各位大佬交流交流。