以下是我最近幾個星期學習c++11做的一些記錄,包括收集的一些信息,整理的相關概念和寫的一些測試代碼。具體相關代碼我寫了24個cpp文件,托管在來github上面cpp11,記錄一下。
Lambda表達式
構成
為了描述一個lambda,你必須提供:
它的捕捉列表:即(除了形參之外)它可以使用的變量列表(”[&]” 在上面的記錄比較例子中意味著“所有的局部變量都將按照引用的方式進行傳遞”)。如果不需要捕捉任何變量,則使用 [],[=]表示值傳遞。
(可選的)它的所有參數(shù)及其類型(例如: (int a, int b) )。
組織成一個塊的函數(shù)行為(例如:{ return v[a].name < v[b].name; })。
(可選的)使用”返回值類型后置語法“來指明返回類型。但典型情況下,我們僅從return語句中去推斷返回類型,如果沒有返回任何值,則推斷為void。
Lambda表達式與STL算法一起使用;
通過“函數(shù)體”后面的‘()’傳入?yún)?shù)。
int n = [] (int x, int y) { return x + y; }(5, 4);
cout << n << endl;
參考資料
C++11 lambda表達式 - KingsLanding
用作模板參數(shù)的局部類型 | C++11 FAQ 中文版
智能指針
unique_ptr
為動態(tài)申請的內(nèi)存提供異常安全
將動態(tài)申請內(nèi)存的所有權傳遞給某個函數(shù)(不能給復制,只能移動)
從某個函數(shù)返回動態(tài)申請內(nèi)存的所有權
在容器中保存指針
在那些要不是為了避免不安全的異常問題(以及為了保證指針所指向的對象都被正確地刪除釋放),我們不可以使用內(nèi)建指針的情況下,我們可以在容器中保存unique_ptr以代替內(nèi)建指針
shared_ptr與weak_ptr
當 shared_ref_cnt 被減為0時,自動釋放 ptr 指針所指向的對象。當 shared_ref_cnt 與 weak_ref_cnt 都變成0時,才釋放 ptr_manage 對象。
如此以來,只要有相關聯(lián)的 shared_ptr 存在,對象就存在。weak_ptr 不影響對象的生命周期。當用 weak_ptr 訪問對象時,對象有可能已被釋放了,要先 lock()。
weak_ptr可以保存一個“弱引用”,指向一個已經(jīng)用shared_ptr進行管理的對象。為了訪問這個對象,一個weak_ptr可以通過shared_ptr的構造函數(shù)或者是weak_ptr的成員函數(shù)lock()轉化為一個shared_ptr。當最后一個指向這個對象的shared_ptr退出其生命周期并且這個對象被釋放之后,將無法從指向這個對象的weak_ptr獲得一個shared_ptr指針,shared_ptr的構造函數(shù)會拋出異常,而weak_ptr::lock也會返回一個空指針。
參考資料
函數(shù)式編程
algorigthm頭文件中定義了幾個很有用的方法;
相應代碼見: cpp11_21_for_cppFunctionProm.cpp
for_each
對于一些collection(vector,list,set,map)可以對其中每個元素執(zhí)行后面的函數(shù);
auto lambda_echo = [](int i ) { std::cout << i << std::endl; };
std::vector<int> col{20,24,37,42,23,45,37};
for_each(col,lambda_echo);
transform實現(xiàn)map
transform。該算法用于實行容器元素的變換操作。有如下兩個使用原型,一個將迭代器區(qū)間[first,last)中元素,執(zhí)行一元函數(shù)對象op操作,交換后的結果放在[result,result+(last-first))區(qū)間中。另一個將迭代器區(qū)間[first1,last1)的元素i,依次與[first2,first2+(last-first))的元素j,執(zhí)行二元函數(shù)操作binary_op(i,j),交換結果放在[result,result+(last1-first1)
reduce
改變來原始的容器元素
transform實現(xiàn)zip
zip函數(shù)將傳進來的兩個參數(shù)中相應位置上的元素組成一個pair數(shù)組。如果其中一個參數(shù)元素比較長,那么多余的參數(shù)會被刪掉。
find_if實現(xiàn)exists
表示判斷集合中是否有某個元素符合條件;
remove_if和erase實現(xiàn)filter
和map()類似,filter()也接收一個函數(shù)和一個序列。和map()不同的是,filter()把傳入的函數(shù)依次作用于每個元素,然后根據(jù)返回值是True還是False決定保留還是丟棄該元素。
remove_if()并不會實際移除序列[start, end)中的元素; 如果在一個容器上應用remove_if(), 容器的長度并不會改變(remove_if()不可能僅通過迭代器改變?nèi)萜鞯膶傩?, 所有的元素都還在容器里面. 實際做法是, remove_if()將所有應該移除的元素都移動到了容器尾部并返回一個分界的迭代器. 移除的所有元素仍然可以通過返回的迭代器訪問到. 為了實際移除元素, 你必須對容器自行調(diào)用erase()以擦除需要移除的元素.
參考資料
是不是服務端編程剛開始都得從寫業(yè)務開始? - 回答作者: itlr
簡單的程序詮釋C++ STL算法系列之十八:transform
時間工具 chrono
duration
duration 是chrono命名空間下面的一個模板類型,它有一些實例類型如下:
typedef duration<long long, nano> nanoseconds; //納秒
typedef duration<long long, micro> microseconds;//微秒
typedef duration<long long, milli> milliseconds;//毫秒
typedef duration<long long> seconds;
typedef duration<int, ratio<60> > minutes;
typedef duration<int, ratio<3600> > hours;
我們使用的時候可以使用它的實例化類型來創(chuàng)建對象
secons sec{128}
當要取得一個duration實例類型的變量的值的時候,使用count成員函數(shù)
sec.count()
當想要對duration進行單位類型轉換的時候,可以使用duration_cast<duration_type>進行強制類型轉換;
chono::minutes min = duration_cast<chono::minutes>(sec)
time_point
有三種類型 steady_clock(穩(wěn)定常用)
system_clock(直接讀取系統(tǒng)時間,可能被人手動改變)
high_resolution_clock(精度更高,單在vc庫里面就是system_clock())
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
std::cout << "Hello World\n";
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
std::cout << "Printing took "
<< std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count()
<< "us.\n";
參考資料
tuple
可以認為tuple是一個未命名的結構體,該結構體包含了特定的tuple元素類型的數(shù)據(jù)成員。特別需要指出的是,tuple中元素是被緊密地存儲的(位于連續(xù)的內(nèi)存區(qū)域),而不是鏈式結構。元素的類型可以不一樣。tuple元組定義了一個有固定數(shù)目元素的容器,其中的每個元素類型都可以不相同,這與其他容器有著本質(zhì)的區(qū)別.是對pair的泛化。
可以顯式地聲明tuple的元素類型,也可以通過make_tuple()來推斷出元素類型。另外,可以使用get()來通過索引(和C++的數(shù)組索引一樣,從0而不是從1開始)來訪問tuple中的元素。
四個常用函數(shù)
make_tuple
通過make_tuple()來推斷出元素類型,以此來構造一個tuple,也可以顯式定義一個tuple,如果只有兩個元素可以使用make_part進行賦值;tuple是對pair的泛化;
get<index>(tuple)
通過索引來 讀寫 tuple中的元素
tie()
tie函數(shù)用在式子左邊,可以將變量連接到一個給定的tuple上,可以通過tie()函數(shù)的使用方便的對tuple進行“解包”操作。解包時,我們?nèi)绻幌虢饽硞€位置的值時,可以用std::ignore占位符來表示不解某個位置的值。
tie函數(shù)用在式子右邊,它會創(chuàng)建一個元組的左值引用.
tuple_cat()函數(shù)
通過該函數(shù)可以將多個tuple連接起來形成一個tuple
關于tuple的遍歷,元素個數(shù)的獲取,見我的代碼
cpp11_19_tuple.cpp
參考資料
線程
C++11提供了新頭文件 <thread>、<mutex>、<atomic>、<future>等用于支持多線程。
async
async()函數(shù)是一個簡單任務的”啟動”(launcher)函數(shù)。
代碼見:cpp11_14_async.cpp
thread
join()保證了在t1和t2完成后程序才會終結。這里”join”的意思是等待線程返回后再往下執(zhí)行。
通常我們需要在執(zhí)行完一個任務后得到返回的結果。對于那些簡單的對返回值沒有概念的,我建議使用std::future。另一種方法是,我們可以給任務傳遞一個參數(shù),從而這個任務可以把結果存在這個參數(shù)中。
通過this_thrad::get_id()得到線程id發(fā)現(xiàn)main函數(shù)的id是1,其它線程id依次遞增。
使用mutex可以進行關鍵區(qū)的互斥訪問
見代碼:cpp11_20_thread_lambda.cpp
#include<thread>
#include<iostream>
using namespace std;
void f(vector<double>&, double* res); // 將結果存在res中
struct F {
vector<double>& v;
double* res;
F(vector<double>& vv, double* p) :v{vv}, res{p} { }
void operator()(); //將結果存在res中
};
int main()
{
double res1;
double res2;
// f(some_vec,&res1) 在一個單獨的線程中執(zhí)行
std::thread t1{std::bind(f,some_vec,&res1)};
// F(some_vec,&res2)() 在一個單獨的線程中執(zhí)行
std::thread t2{F(some_vec,&res2)};
t1.join();
t2.join();
std::cout << res1 << " " << res2 << ‘\n’;
}
mutex
lock(),調(diào)用線程將鎖住該互斥量。線程調(diào)用該函數(shù)會發(fā)生下面 3 種情況:
- 如果該互斥量當前沒有被鎖住,則調(diào)用線程將該互斥量鎖住,直到調(diào)用 unlock之前,該線程一直擁有該鎖。
- 如果當前互斥量被其他線程鎖住,則當前的調(diào)用線程被阻塞住。
- 如果當前互斥量被當前調(diào)用線程鎖住,則會產(chǎn)生死鎖(deadlock)。
unlock(), 解鎖,釋放對互斥量的所有權。
try_lock(),嘗試鎖住互斥量,如果互斥量被其他線程占有,則當前線程也不會被阻塞。線程調(diào)用該函數(shù)也會出現(xiàn)下面 3 種情況,
- 如果當前互斥量沒有被其他線程占有,則該線程鎖住互斥量,直到該線程調(diào)用 unlock 釋放互斥量。
- 如果當前互斥量被其他線程鎖住,則當前調(diào)用線程返回 false,而并不會被阻塞掉。
- 如果當前互斥量被當前調(diào)用線程鎖住,則會產(chǎn)生死鎖(deadlock)。
condition_variable
condition_variable與mutex的區(qū)別是: mutex 里面包的是關鍵區(qū),互斥訪問的,別人unlock后自動喚醒,lock與unlock是成對存在的。而condition_variable是線程之間同步的順序控制,一個線程wait另一個線程運行完厚notify之后該線程才能繼續(xù)運行。
條件變量用于線程間的同步通信。<condition_variable > 頭文件主要包含了與條件變量相關的類和函數(shù)。與條件變量相關的類包括 std::condition_variable和 std::condition_variable_any,還有枚舉類型std::cv_status另外還包括函數(shù) std::notify_all_at_thread_exit()。
當 std::condition_variable 對象的某個 wait 函數(shù)被調(diào)用的時候,它使用 std::unique_lock(封裝 std::mutex) 來鎖住當前線程。當前線程會一直被阻塞,直到另外一個線程在相同的 std::condition_variable 對象上調(diào)用了 notification 函數(shù)來喚醒當前線程。
基本版:wait、 notify_one、unique_lock
這部分的代碼見 cpp11_22_condVir_proConsu.cpp
unique_lock 對象以獨占所有權的方式( unique owership)管理 mutex 對象的上鎖和解鎖操作,所謂獨占所有權,就是沒有其他的 unique_lock 對象同時擁有某個 mutex 對象的所有權。unique_lock 對象只是簡化了 Mutex 對象的上鎖和解鎖操作,方便線程對互斥量上鎖,即在某個 unique_lock 對象的聲明周期內(nèi),它所管理的鎖對象會一直保持上鎖狀態(tài);而 unique_lock 的生命周期結束之后,它所管理的鎖對象會被解鎖。
wait函數(shù)的一個原型:
void wait (unique_lock<mutex>& lck);
當前線程調(diào)用 wait() 后將被阻塞(此時當前線程應該獲得了鎖(mutex),不妨設獲得鎖 lck),直到另外某個線程調(diào)用 notify_* 喚醒了當前線程。
在線程被阻塞時,該函數(shù)會自動調(diào)用 lck.unlock() 釋放鎖,使得其他被阻塞在鎖競爭上的線程得以繼續(xù)執(zhí)行。另外,一旦當前線程獲得通知(notified,通常是另外某個線程調(diào)用 notify_* 喚醒了當前線程),wait() 函數(shù)也是自動調(diào)用 lck.lock(),使得 lck 的狀態(tài)和 wait 函數(shù)被調(diào)用時相同)。
這句話的意思就是在線程被阻塞的時候,釋放mutex 的lock,在被喚醒的時候重新lock,直到這個lock出了作用域。
std::condition_variable::notify_one();
notify_one喚醒某個等待(wait)線程。如果當前沒有等待線程,則該函數(shù)什么也不做,如果同時存在多個等待線程,則喚醒某個線程是不確定的(unspecified)。
加強版 wait_for、wait_until、notify_all
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
加條件的wait,表示如何這個條件不成立,就wait,成立就直接執(zhí)行。相當于
if (!pred()) wait(lck);
看這個例子:
template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep,Period>& rel_time);
template <class Rep, class Period, class Predicate>
bool wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep, Period>& rel_time,
Predicate pred);
wait_for 可以指定一個時間段,在當前線程收到通知或者指定的時間 rel_time 超時之前,該線程都會處于阻塞狀態(tài)。而一旦超時或者收到了其他線程的通知,wait_for 返回,剩下的處理步驟和 wait() 類似。
可以通過返回值std::cv_status的值來判斷是哪種返回情況;
std::condition_variable::notify_all();
notify_all 喚醒所有的等待(wait)線程。如果當前沒有等待線程,則該函數(shù)什么也不做。
std::cv_status 枚舉類型取值
cv_status::no_timeout 表示 wait_for 或者 wait_until 沒有超時,即在規(guī)定的時間段內(nèi)線程收到了通知。
cv_status::timeout 表示 wait_for 或者 wait_until 超時。
代碼在:cpp11_23_wait_for.cpp
參考資料
【c++11FAQ】std::future和std::promise
正則表達式
auto
C++11中引入的auto主要有兩種用途:自動類型推斷和返回值占位。
自動類型推斷
用處
- 聲明變量是不指明類型(但這時必須初始化)
- 當類型名比較長的時候(比如iterator,函數(shù)指針)
- 當變量的類型依賴于模板參數(shù),需要編譯器來推斷;
注意事項
用auto聲明的變量必須初始化
返回值占位
主要用在模板函數(shù)里,用decltype來推斷類型;
注意在c++11標準里需要在后面寫上實際的類型,或者推斷方式。而c++14則不需要
使用場景
template<typename U, typename V>
auto foo(U u, V v) -> decltype(u*v){
return u*v;
}
與decltype對比
如果你僅僅是想根據(jù)初始化值為一個變量推斷合適的數(shù)據(jù)類型,那么使用auto是一個更加簡單的選擇。當你只有需要推斷某個表達式的數(shù)據(jù)類型,例如某個函數(shù)調(diào)用表達式的計算結果的數(shù)據(jù)類型,而不是某個變量的數(shù)據(jù)類型時,你才真正需要delctype。
參考資料
【c++11 faq】auto – 從初始化中推斷數(shù)據(jù)類型
【c++11 faq】decltype – 推斷表達式的數(shù)據(jù)類型
enum
強類型,有類域
枚舉類(enum)(“強類型枚舉”)是強類型的,并且具有類域:
強類型是指不會隱式轉換為int,有類域是指需要加"類名::"才能訪問它的枚舉值。
使用場景:
Color a6 = Color::blue; // 正確
傳統(tǒng)類型
enum 后面不加class還是表示使用的傳統(tǒng)類型,對于c++11,c++98來說支持它轉換為int,也支持它使用類名找到枚舉值(c++98不允許)
需要注意的是傳統(tǒng)enum不管是c++98還是c++11都不能通過int賦值
可以指定枚舉的底層數(shù)據(jù)類型
可以保證枚舉值所占的字節(jié)大小,枚舉類的底層數(shù)據(jù)類型必須是有符號或無符號的整數(shù)類型(int/short/char等),默認情況下是int。
enum class Color : char { red, blue }; // 緊湊型表示(一個字節(jié))
參考資料
【c++11 cpp11-snippets】enumeration.cpp
類型轉換
關于強制類型轉換的問題,很多書都討論過,寫的最詳細的是C++ 之父的《C++ 的設計和演化》。最好的解決方法就是不要使用C風格的強制類型轉換,而是使用標準C++的類型轉換符:static_cast, dynamic_cast。標準C++中有四個類型轉換符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。下面對它們一一進行介紹。
參考資料
【C++專題】static_cast, dynamic_cast, const_cast探討
繼承構造函數(shù)與父類重載函數(shù)的提升
在C++98標準里,可以將普通的重載函數(shù)從基類“晉級”到派生類里來解決當基類與派生類的同名成員不在同一個作用域內(nèi)時,派生類找不到父類同名成員的問題:
而在c++11中甚至連構造函數(shù)都可以這么做
class A
{
public:
A(int a):_a(a)
{
cout<<"A 的構造函數(shù)\n";
}
void print(double a)
{
cout<<"print in A\n";
}
int _a;
};
class B : public A
{
public:
using A::print;
using A::A;//這句話使得B有了一個B(int)的默認構造函數(shù);而默認構造函數(shù)只有一個;
void print(int a,int b)
{
cout<<"print in B\n";
}
};
int main()
{
B b(1);
//B c;這句話編譯不通過,因為B沒有這樣的默認構造函數(shù);
b.print(1.1);
return 0;
}
參考資料
原生字符串標識R
正常情況下字符串是位于R" "之間的,結束符就是"。可是字符串內(nèi)部可以包含"怎么辦呢?比如R"fewga"rgare",改結束符,變成)",這樣就變成R"(fewga"rgare)",不會混淆了??墒菃栴}又來了,如果字符串內(nèi)包含)"呢?于是又引入了d-char-sequenceopt(可以為空),估且叫它分隔串吧,變成R"--(fewga)"rgare)--"這樣的形式,于是問題解決。由于分隔串是用戶指定的,所以可以使用不固定的結束符,不會混淆(除非有人太二)。字符串內(nèi)有)-",我就用)#",有)#",我就用)**"。
”(…)”分隔法只不過是默認的分隔語法罷了。通過在“(…)”的(…)前后添加顯式的自定義分隔號(譯注:例如下面例子中的三個星號*),我們還可以創(chuàng)造出任何我們想要的分隔語法。
// 字符串為:"quoted string containing the usual terminator (")"
R"***("quoted string containing the usual terminator (")")***"
參考資料
右值引用與移動構造函數(shù)
ove(x) 意味著“你可以把x當做一個右值”。
在C++11的標準庫中,所有的容器都提供了移動構造函數(shù)和移動賦值操作符,那些插入新元素的操作,如insert()和push_back(), 也都有了可以接受右值引用的版本。最終的結果是,在沒有用戶干預的情況下,標準容器和算法的性能都提升了,而這些都應歸功于拷貝操作的減少。
左值(賦值操作符“=”的左側,通常是一個變量)與右值(賦值操作符“=”的右側,通常是一個常數(shù)、表達式、函數(shù)調(diào)用)
參考資料
用戶自定義數(shù)據(jù)后綴
自字義后綴用operator""定義,就是一種特殊的函數(shù)。后綴名必須以下劃線開頭,因為沒有下劃線的后綴是留給std用的。后綴的參數(shù)只能是unsigned long long、long double、const char或者const char + size_t。沒了,它就是這么簡單易上手又很實用的特性。一般來說適合編為后綴的是單位,如kg,km。
C++14預定義了一些標準的字面量,s用于創(chuàng)建std::string,如 "hello"s;h、min、s、ms、us、ns用于創(chuàng)建std::chrono::duration;i、il、if用于創(chuàng)建復數(shù)complex<double>、complex<long double>、complex<float>。
有以下四種數(shù)據(jù)標識的情況,可以被用戶定義后綴來使用用戶自定義數(shù)據(jù)標識:
整型標識:允許傳入一個unsigned long long或者const char*參數(shù)
浮點型標識:允許傳入一個long double或者const char*參數(shù)
字符串標識:允許傳入一組(const char*,size_t)參數(shù)
字符標識:允許傳入一個char參數(shù)。
根據(jù) C++ 11 標準,只有下面這些簽名是合法的:
char const*
unsigned long long
long double
char const*, std::size_t
wchar_t const*, std::size_t
char16_t const*, std::size_t
char32_t const*, std::size_t
上面列出的第一個簽名不要同字符串相混淆,應該被稱為原始字面量 raw literal 操作符。例如:
char const* operator"" _r(char const* s)
{
return s;
}
int main()
{
std::cout << 12_r << '\n';
}
參考資料
隨機數(shù)的產(chǎn)生
#include <random>
#include <iostream>
int main()
{
std::default_random_engine generator;
std::uniform_int_distribution<int> dis(0,100);
for(int i=0;i<5;i++)
{
std::cout<<dis(generator)<<std::endl;
}
return 0;
}
參考資料
其它
委托構造函數(shù)(Delegating constructors)
constexpr 常量表達式
能看懂別人的代碼就行,自己不需要用。因為這個現(xiàn)在看起來沒什么用。
參考資料
序列for循環(huán)語句
C++11中引入了序列for循環(huán)以實現(xiàn)區(qū)間遍歷的簡化。這里所謂的區(qū)間可以是任一種能用迭代器遍歷的區(qū)間,例如STL中由begin()和end()定義的序列。所有的標準容器,例如std::string、 初始化列表、數(shù)組,甚至是istream,只要定義了begin()和end()就行。
這里是一個序列for循環(huán)語句的例子:
void f(const vector& v)
{
for (auto x : v) cout << x << ‘n’;
for (auto& x : v) ++x;
// 使用引用,方便我們修改容器中的數(shù)據(jù)
}
類成員的內(nèi)部初始化
C++11的基本思想是,允許非靜態(tài)(non-static)數(shù)據(jù)成員在其聲明處(在其所屬類內(nèi)部)進行初始化。這樣,在運行時,需要初始值時構造函數(shù)可以使用這個初始值。考慮下面的代碼:
class A {
public:
int a = 7;
};