# C to C++ ## I/O ```c++= #include <iostream> int main() { // 停止 C++ 的輸入輸出 stream 與 C-style (fprintf) 同步, // 可以加速輸入輸出,缺點是不可以混用兩者。 std::ios_base::sync_with_stdio(0); // std::cin 是有 tie 著 std::cout:這會在每次輸入時 flush 輸出,造成效能負擔。 std::cin.tie(0); } ``` 不用像C那樣printf/scanf,直接cin/cout就好,注意方向不一樣還有cout不會幫你加空格 ```c++= int main(){ int c; cin >> c; //讀一個整數c double d; cin >> d; cout << c << ' ' << d << '\n'; } ``` ## struct 可以告訴程式怎麼樣做兩個struct的比較/加減 ```c++= #include<bits/stdc++.h> using namespace std; struct axis { double x,y; //struct 裡面本來有的東西 bool operator < (const axis &b) const{ //比兩個向量的長度 return x*x+y*y<(b.x*b.x)+(b.y*b.y); } axis operator + (const axis &b) const{ //比兩個向量的長度 axis ret; ret.x=x+b.x; ret.y=y+b.y; return ret; } }; int main(){ axis a={3.5,6.4}; //有點投機取巧的寫法,但很快 axis b={1.6,-2.9}; cout << (a<b) << '\n'; cout << (a+b).x << ' ' << (a+b).y << '\n'; } ``` ## sort 不用像qsort那樣要寫兩行的compare function,直接比較就好。而且保證複雜度nlogn。也可以自訂義比較函數 ```c++= bool cmp(int a,int b){ return a>b; } int main(){ int a[5]={1,5,4,2,3}; sort(a,a+5); sort(a,a+5,cmp)//由大到小 sort(a+1,a+4); //排a[1]~a[3] } ``` 對struct也可以寫函式好好排 ```c++= struct Point { int x,y; }; bool cmp(int a,int b){ if(a.x==b.x) return a.y<b.y;//x一樣就比y return a.x<b.x;//否則比x } int main(){ Point a[5]; //這裡不用再寫struct Point for(int i=0;i<5;i++) cin >> a[i].x >> a[i].y; sort(a,a+5,cmp);//要把比較函式傳進去 for(int i=0;i<5;i++) cout << a[i].x << ' ' << a[i].y << "\n"; } ``` # STL ## pair 把兩個相同型態的變數綁在一起,用first和second表示。定義好的大小關係是先比第一個再比第二個。 ```c++= int main(){ pair<int,int> p1={3,5}; //第一種宣告方式 p2=make_pair(10,2); //第二種 cout << (p1<p2) << '\n'; cout << p1.first << ' ' << p1.second << '\n'; pair<int,double> p3={1,4.3};//也可以放不同型態 }//{1,4,3,5,2} => {(1,1),(4,2),(3,3),(5,4),(2,5)} ``` ## vector 可變長的陣列,不用像C那樣malloc。 ### 宣告 ```c++= int main(){ vector<int> v; //空的vector,要注意這時候不能戳v[0]之類的 vector<int> v(10); //像陣列的int v[10]; vector<int> v(10,1); //宣告長度為10的陣列,並把值都初始化成1 //vector<int> v[10];這個寫法不是宣告一個長度為10的vector,是宣告10個空的vector } ``` ### 插入(push_back) ```c++= int main(){ vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); cout << v.size() << '\n'; //輸出vector的大小 cout << v[0] << ' ' << v[1] << ' ' << v[2] << '\n'; } ``` ### 刪除(pop_back) ```c++= int main(){ vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); v.pop_back(); //括號不需要傳東西進去 cout << v.size() << '\n'; //輸出vector的大小 cout << v[0] << ' ' << v[1] << ' ' << v[2] << '\n'; } ``` ### 輸出vector裡面的東西 ```c++= int main(){ vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); cout << v.size() << '\n'; //輸出vector的大小 for(int i=0;i<v.size();i++) cout << v[i] << ' ';//第一種 cout << '\n'; for(int i:v) cout << i << '\n';//第二種 } ``` ### 其他常用的寫法 ```c++= int main(){ vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); cout << v.size() << '\n'; //輸出vector的大小 cout << v.back() << '\n'; //輸出vector的最後一個東西 cout << v.empty() << '\n'; //vector是不是空的 sort(v.begin(),v.end()); //排序vector裡面的東西 reverse(v.begin(),v.end()); //反轉vector } ``` 在存樹或圖的時候常用(adjacancy list),因為不確定一個點會有幾個child,開vector可以避免開出int a[N][N]大小的陣列。 ```c++= vector<int> v[10]; int main(){ for(int i=0;i<m;i++){ int c,d; cin >> c >> d; v[c].push_back(d); //d在c的adjacancy list裡面 v[d].push_back(c); //c在d的adjacancy list裡面 } } ``` 好用的連結:https://cplusplus.com/reference/vector/vector/ ## string 在C裡面string本身就是一個字元vector,所以使用方式完全跟vector一樣。也可以像讀%s一樣直接cin字串。比較特別的是可以用s.size()在O(1)的時間知道字串長,不用使用笨笨O(N)的strlen。還有字串可以用>,<, ==直接比較,不需要使用strcmp。也可以直接使用字串加法 ```c++= string s1="abbbb"; string s2="ccc"; cout << s1==s2 << '\n'; cout << s1+s2 << '\n'; cout << s1.size() << '\n'; s1.pop_back();//刪除最後一個字元 s1.push_back('a');//加入a ``` ## queue 只有push跟pop,push就是從後面塞東西,pop就是從前面拿東西。沒辦法像vector一樣看queue裡面的第幾個。 ```c++= int main(){ queue<int> q; q.push(3); q.push(5); cout << q.front() << '\n'; //q的前面(第一個要被pop的東西) q.pop();//只會把最前面的東西pop掉,不會回傳被pop的值 //注意在用q.front(),q.pop()之前要先檢查q是不是empty cout << q.front() << '\n'; cout << q.empty() << '\n'; //q是不是空的 cout << q.size() << '\n'; //q的大小 } ``` [queue練習](https://neoj.sprout.tw/problem/37/) [BFS練習](https://zerojudge.tw/ShowProblem?problemid=a982) ## stack 一樣只有push跟pop,都是從stack上面放/拿走東西。注意stack的最上面不是叫front而是top ```c++= int main(){ stack<int> st; st.push(3); st.push(5); cout << st.top() << '\n'; //st的上面(第一個要被pop的東西) st.pop(); //只會把最上面的東西pop掉,不會回傳被pop的值 //注意在用st.top(),st.pop()之前要先檢查st是不是empty cout << st.front() << '\n'; cout << st.empty() << '\n'; //st是不是空的 cout << st.size() << '\n'; //st的大小 } ``` [stack練習1](https://neoj.sprout.tw/problem/36/) [stack練習2](https://zerojudge.tw/ShowProblem?problemid=c123) [stack練習3](https://zerojudge.tw/ShowProblem?problemid=b304) ## priority_queue 可以想像成heap,push就是把東西丟進heap,pop會把最大的東西pop出來。 ### 宣告方式 ```c++= int main(){ priority_queue<int> pq; //max-heap 最上面最大 priority_queue<int, vector<int>, greater<int> > pq2;//min heap 最上面最小 priority_queue<struct, vector<struct>, cmp> //可以自訂比較函數 } ``` ### push/pop ```c++= int main(){ priority_queue<int> pq; pq.push(1); pq.push(3); pq.push(2); cout << pq.top() << '\n';//在這裡也是top pq.pop();//只會把最上面的東西pop掉,不會回傳被pop的值 //注意在用pq.top(),pq.pop()之前要先檢查pq是不是empty cout << pq.empty() << '\n'; //pq是不是空的 cout << pq.size() << '\n'; //pq的大小 } ``` [Priority queue練習1](https://neoj.sprout.tw/problem/59/) [Priority queue練習2](https://zerojudge.tw/ShowProblem?problemid=b151) ## iterator 在進入set跟map之前要先提一個在STL裡面用法跟指標有點像的工具:iterator。因為STL有自己的實作方式,不像陣列是連續的一塊記憶體,因此需要使用比較特別的指標(iterator)才能做到一些原本陣列才能做到的事情 ### 宣告 ```c++= vector<int>::iterator iter=v.begin(); set<double>::iterator iter; ``` ### 常見用法 其中v.begin()指的是vector的頭,v.end()指的是vector的尾。++ 代表的是找vector的下一個元素,就像指標的++那樣。 這裡要注意的是v.begin代表的是vector的第一個element(v[0]),但v.end()代表的是vector最後一個element的後一個(v[n],裡面沒有值)。有點像sort的時候傳的是a和a+n那樣。如果要取到最後一個值可以用v.end()再- - ```c++= for(vector<int>::iterator iter = v.begin(); iter != v.end(); ++iter){ cout << *iter << endl; }//輸出陣列所有元素 vector<int> :: iterator it = v.end(); cout << *prev(it) << '\n'; //vector的最後一個寫法1 it--; cout << *it << '\n'; //vector的最後一個寫法2 ``` 另外還有rbegin和rend,但其實他們可以做到的事begin跟end都做得到,這裡就先不提。 ### auto C++ 11才有的功能,對於有被初始化的變數可以自動判斷他的型別,譬如說上面那個iterator看起來一長串很麻煩,改成這樣也可以: ```c++= for(auto iter = v.begin(); iter != v.end(); ++iter){ cout << *iter << endl; } auto it = v.end(); it--; cout << *it << '\n'; //輸出vector的最後一個 ``` ## set 就像一個集合那樣,可以insert(把東西加進集合裡面)、erase(把東西移出集合)、find(檢查集合裡面有沒有某個東西),複雜度都是 $O(\log N)$ ```c++= set<int> s; s.insert(3); s.insert(2); s.insert(4); s.insert(3);//重複insert不會多一個 s.erase(3); s.erase(s.begin()); //也可以傳入想刪除的iterator,例如這樣是刪掉最小的數 for(auto o:s) cout << *o << ' '; ``` find比較特別,如果有找到會回傳一個iterator指向找到的element,沒有則會回傳s.end()。但find沒辦法知道找到的element是第幾大,這個在lower_bound那段會詳加說明。 ```c++= set<int> s; s.insert(3); s.insert(2); s.insert(4); auto it = s.find(3); if(it == s.end()) cout << "Not Found\n"; else cout << *it; ``` ## multiset 前述set的multi版本,也就是集合裡面可以有多個重複的element。需要注意的是erase(3)在multiset有多個3時會**全部刪掉**,如果只需要erase一個的話可以: ```c++= multiset<int> s; for(int i=0;i<5;i++) s.insert(3); s.erase(s.find(3)); for(int i:s) cout << i << ' ';//輸出四個3 ``` 1=>1 5=>6 "abcde"=>1 ## map map裡面存的是很多對key跟value的對應。實際使用起來感覺會跟陣列很像,但map比較強大的點在於陣列的index只能是非負整數,而且如果你要戳a[N]就要開N的記憶體,map用$O(\log N)$的複雜度解決這些問題。 ```c++= map<int,int> m; m[3]=1; //map可能有insert,但這樣寫方便多了 m[-1]=2; m[1000000000]=9;//這樣也只會佔到O(1)記憶體 m.erase(-1); //把m[-1]設成0不會從map裡面消失,這樣才會 map<string,int> m2; m2["cat"]=1; //有點像hash的感覺 //但需要注意的是如果放了很多長字串進去map記憶體還是O(字串長) ``` ## 遍歷 map裡面存的是pair,在遍歷上會比vector跟set麻煩一點,但概念是一樣的 ```c++= map<int,int> m; m[1]=2; m[-1]=5; m[9]=3; for(pair<int,int> i:m)cout<<i.first<<':'<<i.second<<'\n'; for(auto i:m)cout<<i.first<<':'<<i.second<<'\n'; ``` map可以很方便的實現類似counting sort的演算法,惟map常數較大(同樣時間複雜度比較容易TLE),如果用陣列可以過的題目就不要開map ## lower_bound lower_bound是幫助我們在前述STL裡面$O(\log N)$二分搜的工具,可以幫我們找到指向不小於給定值val最小數的iterator(如果所有數都比val小則回傳v.end())。需要特別注意的是**vector和set/map的lower_bound寫法不可混用**,編譯不會跳錯但會造成複雜度退化成$O(N)$,是很多人常常踩到的bug ### vector 因為vector裡面有第k個element的概念,所以除了可以知道值以外還可以知道他是第幾小的element。前提是vector裡面的東西必須要由小到大排好 ```c++= vector<int> v={1,3,6,9,8}; sort(v.begin(),v.end()); //排好變{1,3,6,8,9} auto it = lower_bound(v.begin(), v.end(), 5);//不小於5最小=6 cout << *it <<'\n'; cout << it - v.begin() <<'\n'; //像指標加減法那樣知道他是第幾個 it = lower_bound(v.begin(), v.end(), 10);//這樣會回傳v.end() cout << *it <<'\n'; cout << it - v.begin() <<'\n'; ``` ### set/map set跟map沒有第k個element的概念,只能經由++(next)或- -(prev)知道他的前一個或後一個。寫法也不太一樣: ```c++= set<int> s={1,3,6,8,9}; auto it = s.lower_bound(6);//不小於6最小=6 cout << *it <<'\n'; cout<< *prev(it) <<'\n'; //小於5的最大整數=3 ``` ```c++= map<int,int> m; m[1]=9;m[-2]=5;m[3]=-7;m[6]=8; auto it = m.lower_bound(2);//對key找,大於等於2最小的數是3 cout << (*it).first << ' ' << (*it).second <<'\n';//先取值才能取first/second cout << (*prev(it)).first << ' ' << (*prev(it)).second <<'\n';//比2小最大 ``` ### upper_bound 原本是**不小於**給定值val,upper_bound是**大於**給定值val。基本上就是大於等於和大於的差別。舉個例子: ```c++= set<int> s={1,3,6,8,9}; auto it = s.lower_bound(6);//不小於6最小=6 cout << *it <<'\n'; it = s.upper_bound(6); //大於6最小=8 cout << *it <<'\n'; ``` ## Reference 參照 ```c++ // 不用再傳 pointer 了! void swap(int& a, int& b) { int t = a; a = b; b = t; } int main() { int a = 3, b = 5; swap(a, b); cout << a << ' ' << b << '\n'; // 5 3 int c = 1; int &d = c; d = 5; cout << c << '\n'; // 5 } ``` 對一個函數傳入大小為 $N$ 的 `vector` 是 $O(N)$ 的。可以用參照來解決 copy 一遍的問題: ```c++ void print(vector<int>& a) { // O(1) function call! } ``` ## Range For 黑魔法。可以這樣對 `vector` 做事: ```cpp= int main() { vector<int> a(5); for (int& i: a) cin >> i; //記得參照,不然輸不進去! for (int i: a) cout << i << '\n'; } ``` 搭配 `auto` 更顯強大: ```cpp= int main() { vector<pair<int, int>> pairs(5); for (auto& [u, v]: pairs) cin >> u >> v; for (auto [u, v]: pairs) cout << u << ' ' << v << '\n'; // 不用再 pairs[i].first 了! } ```