几个方便编程的C++特性
前言:
??????? C++11的自動化特性給編程提供了不少方便,同時也給調試增加了很多負擔,至于取舍看程序員的風格和側重而定。
auto:自動類型推斷
在C++11之前,auto關鍵字用來指定存儲期。在新標準中,它的功能變為類型推斷。auto現在成了一個類型的占位符,通知編譯器去根據初始化 代碼推斷所聲明變量的真實類型。各種作用域內聲明變量都可以用到它。例如,名空間中,程序塊中,或是for循環的初始化語句中。
auto i = 42; // i is an int auto l = 42LL; // l is an long long auto p = new foo(); // p is a foo*
使用auto通常意味著更短的代碼(除非你所用類型是int,它會比auto少一個字母)。試想一下當你遍歷STL容器時需要聲明的那些迭代器(iterator)。現在不需要去聲明那些typedef就可以得到簡潔的代碼了。
std::map> map; for(auto it = begin(map); it != end(map); ++it) { }
需要注意的是,auto不能用來聲明函數的返回值。但如果函數有一個尾隨的返回類型時,auto是可以出現在函數聲明中返回值位置。這種情況下,auto 并不是告訴編譯器去推斷返回類型,而是指引編譯器去函數的末端尋找返回值類型。在下面這個例子中,函數的返回值類型就是operator+操作符作用在 T1、T2類型變量上的返回值類型。
template auto compose(T1 t1, T2 t2) -> decltype(t1 + t2) { return t1+t2; } auto v = compose(2, 3.14); // v's type is double
?????? 注:最主要的是 在小范圍迭代時 不用刻意去注意其類型,節省了編程時間....
nullptr 空指針?
以前都是用0來表示空指針的,但由于0可以被隱式類型轉換為整形,這就會存在一些問題。關鍵字nullptr是std::nullptr_t類型的 值,用來指代空指針。nullptr和任何指針類型以及類成員指針類型的空值之間可以發生隱式類型轉換,同樣也可以隱式轉換為bool型(取值為 false)。但是不存在到整形的隱式類型轉換。
void foo(int* p) {} void bar(std::shared_ptr p) {} int* p1 = NULL; int* p2 = nullptr; if(p1 == p2) { } foo(nullptr); bar(nullptr); bool f = nullptr; int i = nullptr; // error: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
為了向前兼容,0仍然是個合法的空指針值。
基于范圍的for循環:
???????? C++使用了800行STL代碼就是為了讓一個遍歷語句更像python這種動態語言.
為了在遍歷容器時支持”foreach”用法,C++11擴展了for語句的語法。用這個新的寫法,可以遍歷C類型的數組、初始化列表以及任何重載了非成員的begin()和end()函數的類型。(這是向動態語言看齊啊...)
如果你只是想對集合或數組的每個元素做一些操作,而不關心下標、迭代器位置或者元素個數,那么這種foreach的for循環將會非常有用。
std::map> map; std::vector v; v.push_back(1);v.push_back(2); v.push_back(3); map["one"] = v;for(const auto& kvp : map) {std::cout << kvp.first << std::endl;for(auto v : kvp.second) {std::cout << v << std::endl;} }int arr[] = {1,2,3,4,5}; for(int& e : arr) {e = e*e; } //C++ 11 CPP 11 featuresSmart Pointers 智能指針,有計數的指針.
已經有成千上萬的文章討論這個問題了,所以我只想說:現在能使用的,帶引用計數,并且能自動釋放內存的智能指針包括以下幾種:
unique_ptr: 如果內存資源的所有權不需要共享,就應當使用這個(它沒有拷貝構造函數),但是它可以轉讓給另一個unique_ptr(存在move構造函數)。
shared_ptr: 如果內存資源需要共享,那么使用這個(所以叫這個名字)。
weak_ptr: 持有被shared_ptr所管理對象的引用,但是不會改變引用計數值。它被用來打破依賴循環(想象在一個tree結構中,父節點通過一個共享所有權的引 用(chared_ptr)引用子節點,同時子節點又必須持有父節點的引用。如果這第二個引用也共享所有權,就會導致一個循環,最終兩個節點內存都無法釋 放)。
另一方面,auto_ptr已經被廢棄,不會再使用了。
什么時候使用unique_ptr,什么時候使用shared_ptr取決于對所有權的需求,我建議閱讀以下的討 論:http://stackoverflow.com/questions/15648844/using-smart-pointers-for- class-members
以下第一個例子使用了unique_ptr。如果你想把對象所有權轉移給另一個unique_ptr,需要使用std::move(我會在最后幾段討論這個函數)。在所有權轉移后,交出所有權的智能指針將為空,get()函數將返回nullptr。
void foo(int* p) { std::cout << *p << std::endl; } std::unique_ptr p1(new int(42)); std::unique_ptr p2 = std::move(p1); // transfer ownership if(p1) foo(p1.get()); (*p2)++; if(p2) foo(p2.get());
第二個例子展示了shared_ptr。用法相似,但語義不同,此時所有權是共享的。
void foo(int* p) { } void bar(std::shared_ptr p) { ++(*p); } std::shared_ptr p1(new int(42)); std::shared_ptr p2 = p1; bar(p1); foo(p2.get());
第一個聲明和以下這行是等價的:
auto p3 = std::make_shared(42);
make_shared是一個非成員函數,使用它的好處是可以一次性分配共享對象和智能指針自身的內存。而顯示地使用 shared_ptr構造函數來構造則至少需要兩次內存分配。除了會產生額外的開銷,還可能會導致內存泄漏。在下面這個例子中,如果seed()拋出一個 錯誤就會產生內存泄漏。
void foo(std::shared_ptr p, int init) { *p = init; } foo(std::shared_ptr(new int(42)), seed());
如果使用make_shared就不會有這個問題了。第三個例子展示了weak_ptr。注意,你必須調用lock()來獲得被引用對象的shared_ptr,通過它才能訪問這個對象。
auto p = std::make_shared(42); std::weak_ptr wp = p; { auto sp = wp.lock(); std::cout << *sp << std::endl; } p.reset(); if(wp.expired()) std::cout << "expired" << std::endl;
如果你試圖鎖定(lock)一個過期(指被弱引用對象已經被釋放)的weak_ptr,那你將獲得一個空的shared_ptr.
Lambdas (省去了 寫大量的函數對象)
?????? 思想:函數就近使用原則.
匿名函數(也叫lambda)已經加入到C++中,并很快異軍突起。這個從函數式編程中借來的強大特性,使很多其他特性以及類庫得以實現。你可以在 任何使用函數對象或者函子(functor)或std::function的地方使用lambda。你可以從這里 (http://msdn.microsoft.com/en-us/library/dd293603.aspx)找到語法說明。
std::vector v; v.push_back(1); v.push_back(2); v.push_back(3); std::for_each(std::begin(v), std::end(v), [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1; }; auto pos = std::find_if(std::begin(v), std::end(v), is_odd); if(pos != std::end(v)) std::cout << *pos << std::endl;更復雜的是遞歸lambda。考慮一個實現Fibonacci函數的lambda。如果你試圖用auto來聲明,就會得到一個編譯錯誤。
Move semantics (Move語義)
??????? 移動語義避免了移動原始數據,只是修改了記錄...
這是C++11中所涵蓋的另一個重要話題。就這個話題可以寫出一系列文章,僅用一個段落來說明顯然是不夠的。因此在這里我不會過多的深入細節,如果你還不是很熟悉這個話題,我鼓勵你去地資料。
C++11加入了右值引用(rvalue reference)的概念(用&&標識),用來區分對左值和右值的引用。左值就是一個有名字的對象,而右值則是一個無名對象(臨時對 象)。move語義允許修改右值(以前右值被看作是不可修改的,等同于const T&類型)。
C++的class或者struct以前都有一些隱含的成員函數:默認構造函數(僅當沒有顯示定義任何其他構造函數時才存在),拷貝構造函數,析構 函數還有拷貝賦值操作符。拷貝構造函數和拷貝賦值操作符提供bit-wise的拷貝(淺拷貝),也就是逐個bit拷貝對象。也就是說,如果你有一個類包含 指向其他對象的指針,拷貝時只會拷貝指針的值而不會管指向的對象。在某些情況下這種做法是沒問題的,但在很多情況下,實際上你需要的是深拷貝,也就是說你 希望拷貝指針所指向的對象。而不是拷貝指針的值。這種情況下,你需要顯示地提供拷貝構造函數與拷貝賦值操作符來進行深拷貝。
如果你用來初始化或拷貝的源對象是個右值(臨時對象)會怎么樣呢?你仍然需要拷貝它的值,但隨后很快右值就會被釋放。這意味著產生了額外的操作開銷,包括原本并不需要的空間分配以及內存拷貝。
現在說說move constructor和move assignment operator。這兩個函數接收T&&類型的參數,也就是一個右值。在這種情況下,它們可以修改右值對象,例如“偷走”它們內部指針所 指向的對象。舉個例子,一個容器的實現(例如vector或者queue)可能包含一個指向元素數組的指針。當用一個臨時對象初始化一個對象時,我們不需 要分配另一個數組,從臨時對象中把值復制過來,然后在臨時對象析構時釋放它的內存。我們只需要將指向數組內存的指針值復制過來,由此節約了一次內存分配, 一次元數組的復制以及后來的內存釋放。
總結
以上是生活随笔為你收集整理的几个方便编程的C++特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高位放量下跌是什么意思
- 下一篇: 持仓限额 什么是持仓的限额