文章目錄
零、前言
筆者再一次的參加了字節的面試,倒在了二面,自己的技術棧是真的太垃圾了,二面的HR給了我很多的建議和指導,希望自己三年後可以去字節上班哦~~
HR給目前的我推薦了一本神一樣的書籍《C++ Primer》,才知道自己的大學是有多麼的垃圾呀,本文的基礎來源於個人看書之後的總結,大家也一起學一下,順便,祝可愛的豬崽有一份很好的工作~
一、C++庫引用(Import C++ Library)
衆所周知,常用的庫引用如下:
#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
有些太多,所以有了一個新奇的方法:
#include <bits/stdc++.h>
一行代碼解決一系列代碼,方便!
其中 bits/stdc++.h 源碼如下:
/** @file stdc++.h
* This is an implementation file for a precompiled header.
*/
// 17.4.1.2 Headers
#ifndef _GLIBCXX_NO_ASSERT
#include <cassert>
#endif
#include <cctype>
#include <cerrno>
#include <cfloat>
#include <ciso646>
#include <climits>
#include <clocale>
#include <cmath>
#include <csetjmp>
#include <csignal>
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#if __cplusplus >= 201103L
#include <ccomplex>
#include <cfenv>
#include <cinttypes>
#include <cstdalign>
#include <cstdbool>
#include <cstdint>
#include <ctgmath>
#include <cwchar>
#include <cwctype>
#endif
// C++
#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>
#if __cplusplus >= 201103L
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <forward_list>
#include <future>
#include <initializer_list>
#include <mutex>
#include <random>
#include <ratio>
#include <regex>
#include <scoped_allocator>
#include <system_error>
#include <thread>
#include <tuple>
#include <typeindex>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#endif
不過 VS 會報錯,網上解決辦法一大堆,這裏就不過多贅述了。
二、STL(Standard Template Library)
- STL(Standard Template Library),中文名 標準模板庫,是一個高效的C++程序庫,包含很多常用的 基本數據結構 和 基本算法,爲C++程序員們提供了一個可擴展的應用框架,高度體現了軟件的可複用性。
- 從邏輯層次來看,在STL中體現了 泛型編程思想(generic programming)。
- 從實現層次看,整個STL是以一種 類型參數化(type parameterized) 的方式實現的。
STL有六大組件(容器(containers)、迭代器(iterators)、空間配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函數(functors))。
但主要是容器、迭代器和算法三個部分~~
容器(Containers):用來管理某類對象的集合。對最常用的數據結構提供了支持,每一種容器都有其優點和缺點,爲了應付程序中的不同需求。
迭代器(Iterators):用來在一個對象集合的元素上進行遍歷動作。這個對象集合或許是個容器,或許是容器的一部分,每種容器都提供了了解該種容器內部結構的迭代器。
算法(Algorithms):用來處理對象集合中的元素。通過所有容器的迭代器提供一致的接口,可以多次複用算法於任意容器之上。
STL 的基本觀念就是將數據和操作分離。
數據由 容器 進行管理;
操作由 算法進行;
而 迭代器 在兩者之間充當粘合劑,使任何 算法 都可以和任何 容器 交互運作。
三、容器(Containers)
一個容器就是一些特定類型對象的集合,是用來管理某類對象的,從C++11標準以來,C++中STL定義的幾種容器的效率非常高,優化的非常好,完全沒有必要自己去定義類似的數據結構,瞭解使用它們,可以滿足90%的日常編程需要!本文是 基於C++11標準,基於《C++primer》參考 完成的。
常用的STL基本容器類型分爲四類:
順序容器(Sequence containers),爲程序員提供了控制元素存儲和訪問順序的能力。順序性容器中的每個元素均有固定的位置,取決於插入時機和地點,和元素值無關,除非用刪除或插入的操作改變這個位置。
關聯容器(Associative containers),支持高效的關鍵字查找和訪問操作。關聯容器中各元素間沒有嚴格的物理順序,取決於特定的排序準則以及元素值,和插入次序無關,元素是有序的集合。默認情況下,標準庫使用關鍵字類型的 < 運算符來進行比較操作。
無序容器(Unordered associative container),使用 哈希函數 和關鍵字類型的 == 運算符組織元素。在關鍵字類型的元素沒有明顯的序關係的情況下,無序容器是非常有用的。在某些應用中,維護元素的序代價非常高昂, 此時無序容器也很有用。使用無序容器通常更爲簡單(通常也會有更好的性能) 。
容器適配器(Adaptor),是標準庫中的一個通用概念,容器、迭代器和函數都有適配器。本質上,一個適配器是一種機制,能使某種事物的行爲看起來像另外一種事物一樣的一種機制。適配器是容器的接口,它本身不能直接保存元素,它保存元素的機制是調用另一種順序容器去實現,即可以把適配器看作"它保存一個容器,這個容器再保存所有元素"。
其中,STL 提供的 最常用的:
四個 順序容器:
向量(vector);
雙端隊列(deque);
列表(list);
字符串(string);
四個 關聯容器:
集合(set);多重集合(multiset);
映射(map);多重映射(multimap);
三種 適配器:
棧(stack);
隊列(queue);
優先級隊列(priority_queue);
四種 無序容器:
unordered_map;
unordered_multimap;
unordered_set;
unordered_multiset;
容器類自動申請和釋放內存,因此無需new和delete操作。
四、順序容器(Sequence containers)
4.1)常用操作(共同點)
1_添加元素
2_訪問元素
3_刪除元素
4_改變容器大小
5_容器操作可能使迭代器失效
向容器中添加或刪除元素可能會使指向容器元素的指針、引用或迭代器失效。失效的指針、引用或迭代器不再表示任何元素,使用它們是一種嚴重的程序設計錯誤。
向容器中添加元素後:
如果容器是 vector 或 string 類型,且存儲空間被重新分配,則指向容器的迭代器、指針和引用都會失效。如果存儲空間未重新分配,指向插入位置之前元素的迭代器、指針和引用仍然有效,但指向插入位置之後元素的迭代器、指針和引用都會失效。
如果容器是 deque 類型,添加到除首尾之外的任何位置都會使迭代器、指針和引用失效。如果添加到首尾位置,則迭代器會失效,而指針和引用不會失效。
如果容器是 list 或 forward_list 類型,指向容器的迭代器、指針和引用仍然有效。
從容器中刪除元素後,指向被刪除元素的迭代器、指針和引用失效:
如果容器是 list 或 forward_list 類型,指向容器其他位置的迭代器、指針和引用仍然有效。
如果容器是 deque 類型,刪除除首尾之外的任何元素都會使迭代器、指針和引用失效。如果刪除尾元素,則尾後迭代器失效,其他迭代器、指針和引用不受影響。如果刪除首元素,這些也不會受影響。
如果容器是 vector 或 string 類型,指向刪除位置之前元素的迭代器、指針和引用仍然有效。但尾後迭代器總會失效。
4.2)向量(vector)
vector(向量):事實上和數組差不多,但比數組更優越,一般來說數組不能動態拓展,因此在程序運行的時候不是浪費內存,就是造成越界,而 vector 正好彌補了這個缺陷,它的特徵是相當於可變大小的數組(動態數組)。由於元素是連續存儲的,隨機訪問快,在末端插入和刪除快,但在中間插入和刪除慢。
優缺點:
優點:支持隨機訪問,即 [] 操作和 .at(),查詢效率高。
缺點:當向頭部或中部,插入或刪除元素,插入效率低。
需要導入頭文件 #include 。
1_定義和初始化:
2_簡單vector操作
3_關鍵概念: vector對象能高效增長
C++標準要求 vector 應該能在運行時高效快速地添加元素,因此定義 vector 對象的大小沒有必要,事實上性能可能更差,只有一種例外情況,就是所有元素的值都一樣!一旦元素的值有所不同,更有效的辦法是先定義一個空的 vector 對象,再在運行時向其中添加具體值。
開始的時候創建空的 vector 對象,在運行時再動態添加元素,這一做法與C語言及其他大多數語言中內置數組類型的用法不同,特別是如果用慣了C或者Java,可以預計在創建 vector 對象時順便指定其容量是最好的,然而事實上,通常的情況是恰恰相反。
4_實例
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec1;
for (int i = 0; i<9; i++)
ivec1.push_back(i);
cout << ivec1.size() << endl;
for (int i = 0; i<ivec1.size(); i++)
cout << ivec1[i] << " ";
cout << endl;
system("pause");
return 0;
}
4.3)雙端隊列(deque)
deque(雙端隊列):是一個更爲複雜的數據結構,最大任務就是在這些分段的連續空間上,維護其整體連續的假象,並提供隨機存取的接口。與 vector 類似,隨機訪問快,不過是在兩端插入和刪除快,但在中間插入和刪除慢。
優缺點:
優點:支持隨機訪問,即 [] 操作和 .at(),查詢效率高;當向兩端,插入或刪除元素,插入效率高。
缺點:當向中部,插入或刪除元素,插入效率低。
需要導入頭文件 #include
1_實例
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int> ideq1;
for (int i = 0; i<9; i++)
ideq1.push_back(i);
cout << ideq1.size() << endl;
for (int i = 0; i<ideq1.size(); i++)
cout << ideq1[i] << " ";
cout << endl;
system("pause");
return 0;
}
4.4)列表(list)
list(列表):由 deque 實現而成,元素也存放在堆中。設計目的是令容器任何位置的添加和刪除操作都很快速,作爲代價不支持元素的隨機訪問——爲了訪問一個元素,只能遍歷整個容器。
優缺點:
優點:內存不連續,動態操作,可在任意位置插入或刪除且效率高。
缺點:不支持隨機訪問。
需要導入頭文件 #include
1_實例
#include <iostream>
#include <list>
using namespace std;
int main(int argc, char* argv[])
{
list<char> ilist;
for (char c = 'a'; c <= 'z'; ++c)
ilist.push_back(c);
cout << ilist.size() << endl;
while (!ilist.empty())
{
cout << ilist.front() << " ";
ilist.pop_front();
}
system("pause");
return 0;
}
4.5)字符串(string)
string(字符串):表示可變長的字符序列,事實上和 vector 差不多。由於元素是連續存儲的,隨機訪問快,在末端插入和刪除快,但在中間插入和刪除慢。
優缺點:
優點:支持隨機訪問,即 [] 操作和 .at(),查詢效率高。
缺點:當向頭部或中部,插入或刪除元素,插入效率低。
需要導入頭文件 #include 。
1_定義和初始化:
2_簡單vector操作
3_處理字符
4_額外操作
5_實例
#include <iostream>
#include <string>
using namespace std;
int main()
{
string ivec1;
for (int i = 0; i<9; i++)
ivec1.push_back('i');
cout << ivec1.size() << endl;
for (int i = 0; i<ivec1.size(); i++)
cout << ivec1[i] << " ";
cout << endl;
system("pause");
return 0;
}
4.6)容器選擇
容器選擇原則:
除非有合適的理由選擇其他容器,否則應該使用 vector。
如果程序有很多小的元素,且空間的額外開銷很重要,則不要使用 list 或 forward_list。
如果程序要求隨機訪問容器元素,則應該使用 vector 或 deque。
如果程序需要在容器頭尾位置插入/刪除元素,但不會在中間位置操作,則應該使用 deque。
如果程序只有在讀取輸入時才需要在容器中間位置插入元素,之後需要隨機訪問元素。則:
先確定是否真的需要在容器中間位置插入元素。當處理輸入數據時,可以先向 vector 追加數據,再調用標準庫的 sort 函數重排元素,從而避免在中間位置添加元素。
如果必須在中間位置插入元素,可以在輸入階段使用 list。輸入完成後將 list 中的內容拷貝到 vector 中。
不確定應該使用哪種容器時,可以先只使用 vector 和 list 的公共操作:使用迭代器,不使用下標操作,避免隨機訪問。這樣在必要時選擇 vector 或 list 都很方便。
五、關聯容器(Associative containers)
5.1)常用操作(共同點)
1_類型別名
2_添加元素
3_刪除元素
4_訪問元素
5.2)集合(set)和多重集合(multiset)
set(集合):由紅黑樹實現,其中每個元素只包含一個關鍵字並依據其值自動排序,支持高效的關鍵字查詢操作,每個元素值只能出現一次,不允許重複。插入和刪除效率比用其他序列容器高,因爲對於關聯容器來說,不需要做內存拷貝和內存移動。
multiset(多重集合):唯一的區別是插入的元素可以相同。
優缺點:
優點:關鍵字查詢高效,且元素唯一,以及能自動排序。
缺點:每次插入值的時候,都需要調整紅黑樹,效率有一定影響。
需要導入頭文件 #include 。
1_實例
#include <iostream>
#include <set>
using namespace std;
int main(int argc, char* argv[])
{
set<int> iset;
iset.insert(3);
iset.insert(1);
iset.insert(2);
iset.insert(1);
set<int>::iterator it;
cout << iset.size() << endl;
for (it = iset.begin(); it != iset.end(); it++)
{
cout << *it << " ";
}
cout << endl;
system("pause");
return 0;
}
5.3)映射(map)和多重映射(multimap)
map(映射):由紅黑樹實現,其中每個元素都是一些 鍵值對(key-value):關鍵字起索引作用,值表示與索引相關聯的數據。每個元素有一個鍵,是排序準則的基礎。每一個鍵只能出現一次,不允許重複。插入和刪除效率比用其他序列容器高,因爲對於關聯容器來說,不需要做內存拷貝和內存移動。
multimap(多重映射):唯一的區別是插入的元素(值)可以相同,即同一個鍵可以對應多個值。
優缺點:
優點:關鍵字查詢高效,且元素唯一,以及能自動排序。把一個值映射成另一個值,可以創建字典。
缺點:每次插入值的時候,都需要調整紅黑樹,效率有一定影響。
需要導入頭文件 #include
1_實例
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main(int argc, char* argv[])
{
map<int, string> imap;
imap.insert({ 3, "張三" });
imap.insert({ 4, "李四" });
imap.insert({ 1, "王二麻子" });
imap.insert({ 2, "小淘氣" });
map<int, string>::iterator it;
for (it = imap.begin(); it != imap.end(); it++)
{
printf("學號:%d 姓名:%s\n", (*it).first, (*it).second.c_str());
}
system("pause");
return 0;
}
六、適配器(Adaptor)
6.1)常用操作(共同點)
6.2)棧(stack)
stack(棧):LIFO(後進先出)。
需要導入頭文件 #include 。
1_基本操作
2_實例
《C++ Primer》習題參考答案:第9章 - 順序容器 最後一題。6.3)隊列(queue)和優先級隊列(priority_queue)
queue(隊列):FIFO(先進先出),即普通的緩衝區(buffer)。
priority_queue(優先級隊列):基於程序員提供的排序準則定義不同的優先權。
需要導入頭文件 #include 。
1_基本操作
七、無序容器(Unordered associative container)
7.1)unordered_map/unordered_multimap
7.2)unordered_set/unordered_multiset
無序容器:在關鍵字類型的元素沒有明顯的序關係的情況下,無序容器是非常有用的。在某些應用中,維護元素的序代價非常高昂, 此時無序容器也很有用。事實上使用無序容器通常更爲簡單(通常也會有更好的性能) 。如果關鍵字類型固有就是無序的,或者性能測試發現問題可以用哈希技術解決,就可以使用無序容器。
1_常用操作(共同點)
通常可以用一個無序容器替換對應的有序容器,反之亦然。但是輸出(通常)會不同。
八、sizeof、size和capacity
sizeof 操作符統計的是容器 vector 的大小;
size() 操作符統計的是容器(vector)內元素的大小;
capacity 操作符統計的是容器(vector)能存儲的容量大小。
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char* argv[])
{
vector<int> ivec;
cout << sizeof(ivec) << endl;
cout << ivec.size() << endl;
cout << ivec.capacity() << endl;
for (int i = 0; i<10; i++)
ivec.push_back(1);
cout << sizeof(ivec) << endl;
cout << ivec.size() << endl;
cout << ivec.capacity() << endl;
system("pause");
return 0;
}
另外:
reserve() 操作符統計的是指定容器內能存儲元素的數量;
resize() 操作符統計的是重新指定容器內有效元素的數量;