C++ 11 常用特性的使用經驗總結(一)
C++11已經出來很久了,網上也早有很多優秀的C++11新特性的總結文章,在編寫本文之前,筆者在工作和學習中學到的關於C++11方面的知識,也得益於很多其他網友的總結。本文是在學習的基礎上,加上在日常工作中的使用C++11的一些總結、經驗和感悟,整理出來,分享給大家,希望對各位讀者有幫助,文章中的總結可能存在很多不完整或有錯誤的地方,也希望讀者指出。
1、關鍵字及新語法
C++11相比C++98增加了許多關鍵字及新的語法特性,很多人覺得這些語法可有可無,沒有新特性也可以用傳統C++去實現。
也許吧,但個人對待新技術總是抱著渴望而熱衷的態度對待,也許正如很多人所想,用傳統語法也可以實現,但新技術可以讓你的設計更完美。這就如同在原來的維度裡,你要完成一件事情,需要很多個複雜的步驟,但在新語法特性裡,你可以從另外的維度,很乾脆,直接就把原來很複雜的問題用很簡單的方法解決了,我想著就是新的技術帶來的一些程式設計體驗上非常好的感覺。大家也不要覺得程式碼寫得越複雜就先顯得越牛B,有時候在理解的基礎上,儘量選擇“站在巨人的肩膀上”,可能你會站得更高,也看得更遠。
本章重點總結一些常用c++11新語法特點。後續會在本人理解的基礎上,會繼續在本部落格內更新或增加新的小章節。
1.1、auto 關鍵字及用法
A、auto 關鍵字能做什麼?
auto並沒有讓C++成為弱型別語言,也沒有弱化變數什麼,只是使用auto的時候,編譯器根據上下文情況,確定auto變數的真正型別。
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html auto AddTest(int a, int b) { return a + b; } int main() { auto index = 10; auto str = "abc"; auto ret = AddTest(1,2); std::cout "index:" std::endl; std::cout "str:" std::endl; std::cout "res:" std::endl; }
是的,你沒看錯,程式碼也沒錯,auto在C++14中可以作為函式的返回值,因此auto AddTest(int a, int b)的定義是沒問題的。
執行結果:

B、auto 不能做什麼?
auto作為函式返回值時,只能用於定義函式,不能用於宣告函式。
//Test.h 示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html #pragma once class Test { public: auto TestWork(int a ,int b); };
如下函式中,在引用標頭檔案的呼叫TestWork函式是,編譯無法通過。
但如果把實現寫在標頭檔案中,可以編譯通過,因為編譯器可以根據函式實現的返回值確定auto的真實型別。如果讀者用過inline類成員函式,這個應該很容易明白,此特性與inline類成員函式類似。
//Test.h 示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html #pragma once class Test { public: auto TestWork(int a, int b) { return a + b; } };
1.2、nullptr 關鍵字及用法
為什麼需要nullptr ? NULL 有什麼毛病?
我們通過下面一個小小的例子來發現NULL的一點問題:
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
class Test { public: void TestWork(int index) { std::cout "TestWork 1" std::endl; } void TestWork(int * index) { std::cout "TestWork 2" std::endl; } }; int main() { Test test; test.TestWork(NULL); test.TestWork(nullptr); }
執行結果:

NULL在c++裡表示空指標,看到問題了吧,我們呼叫test.TestWork(NULL),其實期望是呼叫的是void TestWork(int * index),但結果呼叫了void TestWork(int index)。但使用nullptr的時候,我們能呼叫到正確的函式。
1.3、for 迴圈語法
習慣C#或java的同事之前使用C++的時候曾吐槽C++ for迴圈沒有想C#那樣foreach的用法,是的,在C++11之前,標準C++是無法做到的。熟悉boost庫讀者可能知道boost裡面有foreach的巨集定義BOOST_FOREACH,但個人覺得看起並不是那麼美觀。
OK,我們直接以簡單示例看看用法吧。
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html int main() { int numbers[] = { 1,2,3,4,5 }; std::cout "numbers:" std::endl; for (auto number : numbers) { std::cout std::endl; } }
以上用法不僅僅侷限於資料,STL容器都同樣適用。
2、STL 容器
C++11在STL容器方面也有所增加,給人的感覺就是越來越完整,越來越豐富的感覺,可以讓我們在不同場景下能選擇跟具合適的容器,提高我們的效率。
本章節總結C++11新增的一些容器,以及對其實現做一些簡單的解釋。
2.1、std::array
個人覺得std::array跟陣列並沒有太大區別,對於多維資料使用std::array,個人反而有些不是很習慣吧。
std::array相對於陣列,增加了迭代器等函式(介面定義可參考C++官方文件)。
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html #include int main() { std::arrayint, 4> arrayDemo = { 1,2,3,4 }; std::cout "arrayDemo:" std::endl; for (auto itor : arrayDemo) { std::cout std::endl; } int arrayDemoSize = sizeof(arrayDemo); std::cout "arrayDemo size:" std::endl; return 0; }
執行結果:

打印出來的size和直接使用陣列定義結果是一樣的。
2.2、std::forward_list
std::forward_list為從++新增的線性表,與list區別在於它是單向連結串列。我們在學習資料結構的時候都知道,連結串列在對資料進行插入和刪除是比順序儲存的線性表有優勢,因此在插入和刪除操作頻繁的應用場景中,使用list和forward_list比使用array、vector和deque效率要高很多。
#include int main() { std::forward_listint> numbers = {1,2,3,4,5,4,4}; std::cout "numbers:" std::endl; for (auto number : numbers) { std::cout std::endl; } numbers.remove(4); std::cout "numbers after remove:" std::endl; for (auto number : numbers) { std::cout std::endl; } return 0; }
執行結果:

2.3、std::unordered_map
std::unordered_map與std::map用法基本差不多,但STL在內部實現上有很大不同,std::map使用的資料結構為二叉樹,而std::unordered_map內部是雜湊表的實現方式,雜湊map理論上查詢效率為O(1)。但在儲存效率上,雜湊map需要增加雜湊表的記憶體開銷。
下面程式碼為C++官網例項原始碼例項:
//webset address: http://www.cplusplus.com/reference/unordered_map/unordered_map/bucket_count/ #include #include string> #include int main() { std::unordered_mapstring, std::string> mymap = { { "house","maison" }, { "apple","pomme" }, { "tree","arbre" }, { "book","livre" }, { "door","porte" }, { "grapefruit","pamplemousse" } }; unsigned n = mymap.bucket_count(); std::cout "mymap has " " buckets.n"; for (unsigned i = 0; ii) { std::cout "bucket #" " contains: "; for (auto it = mymap.begin(i); it != mymap.end(i); ++it) std::cout "[" first ":" second "] "; std::cout "n"; } return 0; }
執行結果:

執行結果與官網給出的結果不一樣。實驗證明,不同編譯器編譯出來的結果不一樣,如下為linux下gcc 4.6.3版本編譯出來的結果。或許是因為使用的雜湊演算法不一樣,個人沒有深究此問題。

2.4、std::unordered_set
std::unordered_set的資料儲存結構也是雜湊表的方式結構,除此之外,std::unordered_set在插入時不會自動排序,這都是std::set表現不同的地方。
我們來測試一下下面的程式碼:
#include #include string> #include #include set> int main() { std::unordered_setint> unorder_set; unorder_set.insert(7); unorder_set.insert(5); unorder_set.insert(3); unorder_set.insert(4); unorder_set.insert(6); std::cout "unorder_set:" std::endl; for (auto itor : unorder_set) { std::cout std::endl; } std::setint> set; set.insert(7); set.insert(5); set.insert(3); set.insert(4); set.insert(6); std::cout "set:" std::endl; for (auto itor : set) { std::cout std::endl; } }
執行結果:

3、多執行緒
在C++11以前,C++的多執行緒程式設計均需依賴系統或第三方介面實現,一定程度上影響了程式碼的移植性。C++11中,引入了boost庫中的多執行緒部分內容,形成C++標準,形成標準後的boost多執行緒程式設計部分介面基本沒有變化,這樣方便了以前使用boost介面開發的使用者切換使用C++標準介面,把容易把boost介面升級為C++介面。
我們通過如下幾部分介紹C++11多執行緒方面的介面及使用方法。
3.1、std::thread
std::thread為C++11的執行緒類,使用方法和boost介面一樣,非常方便,同時,C++11的std::thread解決了boost::thread中構成引數限制的問題,我想著都是得益於C++11的可變引數的設計風格。
我們通過如下程式碼熟悉下std::thread使用風格。
#include void threadfun1() { std::cout "threadfun1 - 1rn" std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout "threadfun1 - 2" std::endl; } void threadfun2(int iParam, std::string sParam) { std::cout "threadfun2 - 1" std::endl; std::this_thread::sleep_for(std::chrono::seconds(5)); std::cout "threadfun2 - 2" std::endl; } int main() { std::thread t1(threadfun1); std::thread t2(threadfun2, 10, "abc"); t1.join(); std::cout "join" std::endl; t2.detach(); std::cout "detach" std::endl; }
執行結果:

有以上輸出結果可以得知,t1.join()會等待t1執行緒退出後才繼續往下執行,t2.detach()並不會並不會把,detach字元輸出後,主函式退出,threadfun2還未執行完成,但是在主執行緒退出後,t2的執行緒也被已經被強退出。
3.2、std::atomic
std::atomic為C++11分裝的原子資料型別。
什麼是原子資料型別?
從功能上看,簡單地說,原子資料型別不會發生資料競爭,能直接用在多執行緒中而不必我們使用者對其進行新增互斥資源鎖的型別。從實現上,大家可以理解為這些原子型別內部自己加了鎖。
我們下面通過一個測試例子說明原子型別std::atomic_int的特點。
下面例子中,我們使用10個執行緒,把std::atomic_int型別的變數iCount從100減到1。
#include #include #include std::atomic_bool bIsReady = false; std::atomic_int iCount = 100; void threadfun1() { if (!bIsReady) { std::this_thread::yield(); } while (iCount > 0) { printf("iCount:%drn", iCount--); } } int main() { std::atomic_bool b; std::list lstThread; for (int i = 0; i 10; ++i) { lstThread.push_back(std::thread(threadfun1)); } for (auto& th : lstThread) { th.join(); } }
執行結果:

注:螢幕太短的原因,上面結果沒有截完屏
從上面的結果可以看到,iCount的最小結果都是1,單可能不是最後一次列印,沒有小於等於0的情況,大家可以程式碼複製下來多執行幾遍對比看看。
3.3、std::condition_variable
C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一樣,可以讓執行緒休眠,直到別喚醒,現在在從新執行。執行緒等待在多執行緒程式設計中使用非常頻繁,經常需要等待一些非同步執行的條件的返回結果。
OK,在此不多解釋,下面我們通過C++11官網的例子看看。
// webset address: http://www.cplusplus.com/reference/condition_variable/condition_variable/%20condition_variable // condition_variable example #include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock #include <condition_variable> // std::condition_variable std::mutex mtx; std::condition_variable cv; bool ready = false; void print_id(int id) { std::unique_lock<std::mutex> lck(mtx); while (!ready) cv.wait(lck); // ... std::cout << "thread " << id << '\n'; } void go() { std::unique_lock<std::mutex> lck(mtx); ready = true; cv.notify_all(); } int main() { std::thread threads[10]; // spawn 10 threads: for (int i = 0; i<10; ++i) threads[i] = std::thread(print_id, i); std::cout << "10 threads ready to race...\n"; go(); // go! for (auto& th : threads) th.join(); return 0; }
執行結果:

上面的程式碼,在14行中呼叫cv.wait(lck)的時候,執行緒將進入休眠,在呼叫33行的go函式之前,10個執行緒都處於休眠狀態,當22行的cv.notify_all()執行後,14行的休眠將結束,繼續往下執行,最終輸出如上結果。
在接觸第二部分之前不妨看一下筆者介紹的程式設計學習群,本人從事線上教育多年,將自己的資料整合建了一個QQ群,對於有興趣一起交流學習c/c++的初學者可以加群:941636044,裡面有大神會給予解答,也會有許多的資源可以供大家學習分享,歡迎大家前來一起學習進步!