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年的迭代更新,就讓書本上的一些知識被淘汰了,所以查找最近的資料是很必要的技能。最好的方式是直接去官網看官方文檔或者直接看源碼,你可能會說,官方文檔都是英文,我英語不好看不懂,源碼我也看不懂。我想說的是,筆者英文也不好,但還是會偶爾去官方文檔看看瞅瞅的(谷歌翻譯是個好東西),或者去查中文翻譯(雖然翻譯也會有些延遲),但最重要的是學習,跟緊潮流,跟緊時代。

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