StandardTemplateLibrary Plus - STL 進階

歡迎收藏個人博客:StandardTemplateLibrary Plus
對於 STL 完全不理解的可以先看看基礎篇:StandardTemplateLibrary

簡介

STL Standard Template Library - 標準模板庫又稱 C++ 泛型庫,它在 std 命名空間中定義了常用的數據結構和算法,大大增加了我們編寫算法的效率,使我們在摸索/學習算法或者解決問題時能夠通過各種各樣的容器來構造簡單便利的數據結構。

其實呢~之前再怎麼熟練 STL ,練習還是太少了,這個寒假我跟着幾位大佬 moyechen、llb、zhouning、(>ω<) 學到虛脫很多。雖然大多數時候我都是跟着蹭 (*≧︶≦))( ̄▽ ̄* )ゞ ,不過自己刷水題的過程中對 STL 的掌握也可以說是更上一層樓了。實際上之前我寫 STL 介紹文章的時候文件名就是 STL1 ,那麼我覺得我是時候可以寫 STL2 了。

那麼接下來繼續由猹來爲大家介紹 STL 的其他用法。

getline(cin, string()) 函數

這個函數是包含在 庫 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()

這個函數接收三個(以上)參數,第一個是字符數組的指針,第二個參數是一個字符串,通常來說是要自己定義直接寫的匹配模式串(大概也是接受字符數組指針的),之後有若干個以逗號分隔的變量地址。

舉個栗子:某種情況下我們需要按照 年-月-日 時:分:秒 這種格式輸入 6 個數據,然後進行操作。當然你可以使用傳統 庫裏的 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) << doubnle(); 控制輸出位數

這是一個小點。就是說 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 的範疇呢,說實話我也不知道,只是真的好用,真的有用,我就一併介紹了,如果有錯歡迎指正,期待與各位大佬交流交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章