《C++ Primer (5th Edition)》筆記-Part II . The C++ Library

注:本文以《C++ Primer(英文版)》(5th Edition)爲參考。

總共由四部分組成:

《C++ Primer (5th Edition)》筆記-Part I. The Basics 
《C++ Primer (5th Edition)》筆記-Part II. The C++ Library
《C++ Primer (5th Edition)》筆記-Part III. Tools For Class Authors
《C++ Primer (5th Edition)》筆記-Part IV. Advanced Topics


Part II. The C++ Library

Chapter 8. The IO Library

8.1 針對wchar_t,定義了以w開頭的IO類,如wistream、wifstream、wistream、wcin等等。

8.2 IO 對象不可賦值,不可拷貝。一般也不定義其const reference。

8.3 Stream的condition state保存在iostate裏面,由4各狀態(constexpr pattern)組成:badbit:不可恢復的系統級錯誤;failbit:可恢復的錯誤,如讀數字時卻讀到字符;eofbit:到達文件末尾,會設置eofbit和failbit;goodbit:前三種bit都沒有被設置。相應的函數的有bad(),fail(),eof(),good()。其中,不管badbit還是failbit被設置,fail都會返回true。所以判斷Stream狀態一般用good()和fail()。Stream做爲condition的時候,就是調用的 !fail()。

8.4 stream buffer的刷新:endl,ends(插入NULL),flush;unitbuf(每寫出一次就刷新),nounitbuf(正常狀態,有系統負責刷新)。

8.5 流之間的綁定(tie),通常input stream應該綁定到output stream,這樣在嘗試讀入前,先寫出。默認,cin和cerr是tied to cout的。綁定是通過成員函數 tie(),參數可以爲空,這是僅返回當前綁定的stream指針(可能爲空指針);參數還可以是Stream指針(可以爲nullptr),那麼將會綁定到參數Stream上,函數返回綁定前已經綁定的stream的指針。

8.6 在C++11中,文件流打開文件的所用的文件路徑可以爲string類型,在之前,只允許使用C串類型。

8.7 如果文件打開失敗,failbit會被設置;文件open之後,close之前,1)此文件不能再次open,否則會失敗;2)此fstream不應該再與其他的file關聯,否則失敗。

8.8 文件打開模式:in,out,app,ate,trunc,binary,注意各自的使用限制(P319)。每次調用open,模式都是被重新設置。

8.9 stringstream的成員函數str(),參數可以爲空,此時返回stream所持有的string;參數也可以爲一個string,此時stream持有參數字符串,返回爲void。

Chapter 9. Sequential Containers

9.1 由於move語義的引入(在後續章節中會有說明),C++ 11的容器要比老版本的容器快很多。

9.2 順序容器有一下6種:vector,deque,list,forward_list,array,string。其中forward_list和array是C++11新引入的兩種順序容器。注意在不同的情況下容器的選擇(P327)。

9.3 C++11中二維vector中的兩個>可以不用加空格,即vector<vector<int>>v是合法的, list::size。

9.4 array不能用iterator範圍做構造函數的參數,不用initializer list賦值(但可以用來初始化),可以用相同size的array來賦值(與built-in數組的區別),沒有assign、resize等函數。forward_list沒有size()成員函數,沒有reverse_iterator、rbegin()等逆向iterator。關聯容器的iterator沒有大小比較的操作符。

9.5 由於move語義的關係,swap兩個容器,要比逐個元素的swap快的多;swap兩個容器,容器中的元素並沒有swapped,只是一些內部數據結構被swaapped,就是說,swap前的元素的iterator、reference、pointer並沒有失效,只是換了一個容器而已。swap(c1,c2),一般的容器都是直接swap的,但對於array,是逐個元素的swap。最好養成使用非成員swap函數的習慣。

9.6 list::sizie()在C++11標準中要求時間複雜度爲Constant,forward_list沒有size()成員函數,沒有back,push_back,emplace_back等在尾部操作的函數。vector、string沒有push_front、emplace_front等在首部操作的函數。在C++11標準下,添加了emplace相關的函數;insert成員函數也添加更多的重載函數,返回類型也從void變爲iterator。 forward_list有特殊的begin、intsert、emplace,erase函數: before_begin,insert_after,emplace_after,erase_after。

9.7 容器的下標是unsigned類型,若爲負值,則自動轉爲unsigned類型。容器的下標操作符是不檢查下標的合法性的,另外,若容器爲空,調用front()、back()將導致undefined的行爲。容器的at成員函數是有檢查下標的合法性的,若越界,將會拋出out_of_range異常。

9.8  插入和刪除操作之後對於已有的iterator、pointer、reference的影響:對list和forward_list沒有影響,對deque和vector、string有一定的影響(P353)。

9.9 C++ 11爲vector、deque、string引入了shrink_to_fit函數,shrink_to_fit只是請求,不是命令。

9.10 C++標準並未vector的allocation策略,但是,it must not allocate new memory until it is forced to do so。

9.11 string的replace成員函數與std::replace的功能不一樣。

9.12 C++11標準,引入了string與數字相互轉換的函數:std::to_string(val); std::stoi、stol、stoul、stoll、stoull(s,p,b);stof、stod、stold(s, p)。

9.13 容器適配器:stack、queue、priority_queue。默認,前兩者使用deque容器,後者使用vector容器。容器適配器除了用自身初始化外,還可以用container_type類型的容器來初始化。

9.14 適配器都有的函數:push, emplace, pop。特殊的函數:stack:top; queue:front、back;priority_queue:top。

Chapter 10 Generic Algorithm

10.1 C++的library提供了100+中算法。

10.2 T accumulate (InputIterator first, InputIterator last, T init);
bool equal ( InputIterator1 first1, InputIterator1 last1, InputIterator2 first2 );
void fill (ForwardIterator first, ForwardIterator last, const T& val);
OutputIterator fill_n (OutputIterator first, Size n, const T& val);注意越界問題。

10.3 back_inserter,賦值時,可以不解引用。

vector<int> v;
auto it = back_inserter(v);
*it = 42;
it = 43;
10.4 OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result);
void replace (ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value);
OutputIterator replace_copy (InputIterator first, InputIterator last, OutputIterator result, const T& old_value, const T& new_value);

10.5 去重複:ForwardIterator unique (ForwardIterator first, ForwardIterator last);會把相鄰的重複元素移到末尾,返回值即爲末尾區域的第一個iterator。由於僅去掉相鄰元素的重複,所以一般使用前先排序。去重複之後,再根據返回值調用容器的刪除函數。

10.6 predicate。算法庫使用的predicate只有兩種:unary predicates和binary predicates。sort,stable_sort。

10.7 一般算法接受的callable object(即能夠對其使用調用操作符的對象),C++裏的callable object包括:函數、函數指針、重載了調用操作符的類和Lambda表達式。Lambda表達式定義如下:

[capture list](parameter list) -> return type{ function body }
其中參數列表和返回類型兩部分均可省略。capture list只保存local nonstatic variables,因爲,lambdas能夠直接訪問local statics和在函數外定義的variables。

10.8 當我們定義一個lambda時,編譯器就爲我們生成了一個相應的new (unnamed) class type。與形參不一樣的地方的時,在lambda被創建的時候,capture list中的值就被拷貝了,而不是在調用的時候;若capture by reference,就不存在這種問題。

int val = 34;
auto f = [val]{return val;};
auto f2 = [val]{return val;};
decltype(f) f3 = f;			//沒有默認構造函數,必須賦值
auto rf = [&val]{return val;};
cout << (typeid(f) == typeid(f2))<< endl;//false
cout << (typeid(f) == typeid(f3))<< endl;//true
cout << f() << " " << rf() << endl;	//34, 34
val = 0;
cout << f() << " " << rf() << endl;	//34, 0

10.9 implicit capture。當capture list爲[=]或者[&]時,編譯器會以capture by value(或者capture by reference)的方式,自動推導出capture list。

10.10 當mix implicit and explicit captures時,capture list的第一項必須是一個&或者=,而explicitly captured variables必須使用另一種capture方式。

int val1 = 34, val2 = 0;
auto f = [&, val2]{return val1 + val2;};	//ok
auto f2 = [&, &val2]{return val1 + val2;};	//error!

10.11當capture by value,拷貝得到的變量是不可修改的,若想修改,需要在parameter list後加上關鍵字“mutable”,注意,此時parameter list將不可省略。當capture by reference時,變量能否修改,取決於其所引用到的原始類型是const還是nonconst。

10.12 省略返回類型時,若函數體沒有return語句,就默認返回爲void;若函數體內有一條返回語句,編譯器自行推斷返回類型(即使:return a?b:c, b跟c的類型不同,也可以);若函數體內不止一條return語句,那麼編譯器無法推導,必須顯示寫明返回類型。

10.13 在以前的C++函數bind主要用4個函數:bind1st, bind2end, not1, not2。在C++11中,統一都使用一個bind函數:bind()。

auto newCallable = bind(callable, arg_list)
其中arg_list能夠包括placeholders:std::placeholders::_1、_2、 _3、 ... 。若callable的參數中有引用類型時,要使用ref或者cref函數,將相應參數封裝成引用對象後,再傳給bind函數。

10.14 除了容器常規的iterator之外,還有幾種特殊的iterator:Insert iterators, Stream Iterators, Reverse iterators, Move iterators。

10.15 主要有三種Inserter :back_inserter, front_inserter, inserter;當*it = t 時,分別調用push_back(t), push_front(t), insert(t, p)成員函數,並implicitly ++it,所以vector、string、forward_list不能定義front_inserter,forward_list不能定義inserter;另外顯式執行*it,++it,  it++,do nothing to it,總是返回it。

10.16 istream_iterator使用>>從Stream中讀取。默認初始化的istream_iterator可以作爲off-the-end值。*in返回從stream讀到的值,++in,in++則從stream中用>>讀取下一個值。

istream_iterator<int> in(cin), eof;
while(in != eof)
	vec.push_back(*in++);
10.17 istream_iterator使用Lazy Evaluation。即不保證理解從流中讀取,通常delay reading the stream until we use the iterator。

10.18 ostream_iterator構造函數可以指定一個C串指針,每次輸出之後,都緊接着輸出該C串。out = val;使用<<向流中寫入val。*out,++out,out++都do nothing to out,返回out。

10.19 reverse_iterator。很巧妙的排序sort(vec.rbegin(), vec.rend())。reverse_iterator的base()成員函數返回該iterator右邊的正常iterator。

10.20 iterator categories:
Input iterator: read,but not write; single pass; increment only.
Output iterator: write, but not read; single pass; increment only.
Forward iterator: read and write; multi-pass; increment only.
Bidirectional iterator: read and write; multi-pass; increment and decrement,如reverse算法,不能用在forward_list上。
Random-access iterator: read and write; multi-pass; full iterator arithmetic。如sort算法,不能用在list和forward_list上。

10.21 list和forward_list特有的成員函數:merge,remove,remove_if,reverse,sort,unique。list.splice(args), forward_list.splice_after(args)。

Chapter 11 Associative Containers

11.1 Associative Container類型主要有map, multimap, unordered_map, unordered_multimap, set, multiset, unordered_set, unordered_multiset。

11.2 C++11下,Associative container可以用list initialize the elements。對於ordered container,默認使用對key type使用<操作符進行比較;當然我們也可以自己定義自己的比較操作符,但是必須滿足“Strict weak ordering”,注意,嚴格弱排序跟升序、降序沒有直接關係。只是表述了一種不對稱、具有傳遞性的關係。以set的構造函數爲例:

template < class T,                 // set::key_type/value_type
	class Compare = less<T>,        // set::key_compare/value_compare
	class Alloc = allocator<T> >    // set::allocator_type
	>set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());

這裏的Compare可以是函數指針、lambda expression、function object。但是不能爲函數類型。

A ai;
set<int, A> s1(ai);			//Function object
set<int, A> s2(A());			//error: 這種寫法不正確,不知道爲什麼。
set<int, decltype(cmp)*> s3(cmp);	//Function pointer
set<int, decltype(f)> s4(f); 		//Lambda expression

11.3 map的value_type是pair類型,pair的默認構造函數會做value initialization,pair有按字典序的比較操作符,能夠使用list initialization。

11.4 關聯容器的幾個別名,key_type,mapped_type(只有map大類的容器纔有),value_type(Set類的value_type與key_type一致,Map類的value_type類型爲pair<const key_type, mapped_type>)。

11.5 容器的keys是const的,不可更改(可以刪除、添加),所以set::iterator和set::const_iterator都是隻讀的。由於關聯容器的特性,一般不是使用通用算法對關聯容器進行處理;實踐中,關聯容器,往往爲通用算法的原序列或目標序列。

11.6 添加元素使用insert、emplace函數。當插入一個元素insert(v),emplace(args),通常返回一個pair<iterator, bool>,表示key所在元素的iterator和是否插入成功,但對於multimap和multiset返回的只是一個指向新插入元素的iterator。當插入多個元素insert(b,e),insert(initilal_list),返回爲void。另外,插入一個元素時,可以額外提供一個hint iterator:insert(p,v),emplace(p,args);如果給的p比較合適的話,效率較高。

11.7刪除元素使用erase函數。erase(key),返回刪掉得元素個數;erase(p),返回p的下一個iterator;erase(b, e),返回e。

11.8 map(unordered_map)的下標操作符很方便,但是multimap和unordered_multimap沒有下標操作符。與c[key]不同,c.at(key)會檢查c中是否有key,若沒有則會拋出out_of_range異常。

11.9 對於multi-關聯容器,常用的函數有c.fine(k), c.count(k),
c.lower_bound(k); //Return an iterator to the first element with key not less than k;
c.upper_bound(k);//Return an iterator to the firrst element with key greater than k;
c.equal_range(k);//Return a pair of iterators。

11.10 unordered關聯容器不再使用比較操作符來組織元素,而是使用hash function和key_type的==操作符。

11.11  unordered container are organized as a collection of buckets。容器具有相同hash值的elements都放在同一個bucket中,其實就是連地址法(解決衝突)。因此,容器的性能主要取決於hash函數的質量和bucket的number和size。bucket管理的有關函數如下:


11.12 unordered container對key_type的要求:要有hash函數,有==操作符。library已經對built-in類型、指針、string、智能指針、vector<bool>等,定義了hash模板函數的特化版本,所以對這些類型不需要再指定hash函數。hash函數的返回類型爲size_t。

size_t hasher(const Sales_data &sd) { 
	return hash<string>()(sd.isbn());
}
bool eqOp(const Sales_data &lhs, const Sales_data &rhs){
	return lhs.isbn() == rhs.isbn();
}
using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>;
// arguments are the bucket size and pointers to the hash function and equality operator 
SD_multiset bookstore(42, hasher, eqOp);

Chapter 12. Dynamic Memory

12.1 C++11引入了智能指針:shared_ptr, unique_ptr。另外還定義了shared_ptr所管理對象的弱引用:weak_ptr。均定義在memory頭文件下。

12.2 最安全的allocae和use動態內存的方法是使用庫函數make_shared。

12.3 注意:使用new動態申請內存時,加空括號和不加括號的區別。不加括號則是default intialized,built-in or compound type have undefined value, class對象則被默認構造函數初始化;加上括號,則是value initialized(P459)。auto p = new auto(obj)是沒問題的,根據obj推導類型;但auto p = new auto{a, b, c}就無法推出類型。另外動態申請的const 對象必須被初始化:const int *pci = new const int(1024); const string *pcs = new const string;

12.4 當new無法申請到空間時,默認拋出bad_alloc異常,我們使用placement new的形式阻止其拋出異常,而是返回空指針。

int *p1 = new int; // if allocation fails, new throws std::bad_alloc
int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer
12.5 傳給delete的指針必須是使用new申請得到的或者是null pointer。delete非new出來的內存,或者同一指針刪除多次(即刪除dangling pointer),是undefined。

12.6 shard_ptr可以用pointer、shared_ptr、unique_ptr(必須爲右值引用)來初始化,可以使用pointer通過reset成員函數,重置shared_ptr所管理的動態空間(可能會需要刪除以前的空間)。不論初始化、還是重置,都可以指定一個callable object來代替默認的delete來free 動態空間。

void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other parameters */){
	connection c = connect(&d);
	shared_ptr<connection> p(&c, end_connection);
	// use the connection
	// when f exits, even if by an exception, the connection will be properly closed
}

12.7  不要使用shared_ptr的get函數得到的指針去初始化或者賦值給另一個shared_ptr。

12.8 unique_ptr不能用另一個unique_ptr變量來賦值,但是可以用另一個unique_ptr的右值引用(在函數返回處),或者用nullptr來賦值。u=nullptr, u.reset(), u.reset(nullptr);都會刪除u所管理的空間,並makes u null。u.release()不會刪除,而是返回空間指針,u.reset(q),刪除u管理的空間,並接管指針q所指空間。所以,通常用release和reset來傳遞指針。

12.9 unique_ptr與shared_ptr另外一個不同地方是:unique_ptr的deleter是類型的一部分,變量一經定義,deleter將無法更改,而shared_ptr的deleter僅僅是一個參數,隨時可以修改。這也說明,在內部上,shared_ptr是通過多態特性來調用deleter的。在Part III中的16.16小項中有具體分析。

12.10 weak_ptr 並不控制他所指對象的生命週期,而是有shared_ptr負責管理的。所以weak_ptr不能用指針來初始化或賦值,只能有weak_ptr或者shared_ptr來賦值,即weak_ptr通常是和shared_ptr捆綁在一起的。

12.11 由於weak_ptr所指對象可能一經不存在,所以我們不能通過weak_ptr來訪問對象。

12.12 weak_ptr的常用函數:w.reset(),makes w null;w.use_count()返回和其共享的shared_ptr的個數;w.expired(),return w.use_count()==0;w.lock(),如果expired是true,返回一個null shared_ptr,否則,返回w所指對象的shared_ptr。

12.13 數組類型也可以定義別名,使用別名進行動態申請內存時,雖然new後面沒有看到[],但是編譯器任然通過new[]來申請內存。

typedef int arrT[42]; // arrT names the type array of 42 ints
int *p = new arrT; // allocates an array of 42 ints; p points to the first
12.14 動態申請數組空間,仍然可以再後面加括號,來進行value initialization;C++11標準中,也可以使用list initialization初始化。

int *p = new int[10]();
12.15 動態開闢空數組是合法的,並可以保證,其所返回的地址不會與其他new出來的地址一樣,但是不能對此地址解引用。

12.16 釋放應該與申請保持一致,即,用new得到的空間要用delete來釋放,用new []得到的空間,應該用delete[]來釋放,否則將會是undefined。

12.17 unique_ptr有針對數組的版本。數組版本特有下標操作符u[i],就相當於get()[i]。shared_ptr沒有提供最數組的直接支持,但是我們可以重載他的deleter,來管理數組,只是沒有下標操作符罷了。

unique_ptr<int[]> up(new int[10]);
up.reset(); // automatically uses delete[] to destroy its pointer
//這裏,在Primer中用的是up.release(),個人認爲作者是真是意圖是用reset()的。

// to use a shared_ptr we must supply a deleter
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; }); 
sp.reset(); // uses the lambda we supplied that uses delete[] to free the array
12.18 使用allocator類能去掉construction和allocation之間的耦合,在某些場合下,可以提高效率。allocator的管理函數如下,其中construct在C++11標準中有做變動。


12.19 destory(p)只能用來析構已經constructed的元素。deallocate(p,n),這裏p不能是nullptr,另外這裏n也要和allocate時的n保持一致。

12.20 allocator的算法:uninitialized_copy(b, e, b2), uninitialized_copy_n(b, n, b2), uninitialized_fill(b, e, t),uninitialized_fill_n(b, n, t)。其中uninitialized_copy_n是C++11新增加的函數。除了uninitialized_fill返回void外,其餘三個函數都返回iterator。

注意,在上一版的C++ primer,即第四版中,將uninitialized_fill_n形式寫爲:uninitialized_fill_n(b, e, t, n)應該是不正確的。



注:本文以《C++ Primer(英文版)》(5th Edition)爲參考。

總共由四部分組成:

《C++ Primer (5th Edition)》筆記-Part I. The Basics 
《C++ Primer (5th Edition)》筆記-Part II. The C++ Library
《C++ Primer (5th Edition)》筆記-Part III. Tools For Class Authors
《C++ Primer (5th Edition)》筆記-Part IV. Advanced Topics



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