c++11的新特性
1.1 lambda表達(dá)式
[capture list] (params list) mutable exception-> return type { function body };
[capture list] (params list) -> return type {function body}; //1
[capture list] (params list) {function body}; //2
[capture list] {function body}; //3
例子1:
vector<int> v({1,5,2,7,8});
sort(v.begin(), v.end(), [] (const int &a, const int &b) { return a < b; });
for(int i = 0; i < v.size(); ++i)
cout<<v[i]<<endl;
輸出:
1
2
5
7
8
例子2:
// 值捕獲
int main()
{
int a = 123;
auto f = [a] { cout << a << endl; };
a = 321;
f(); // 輸出:123
}
// 引用捕獲
int main()
{
int a = 123;
auto f = [&a] { cout << a << endl; };
a = 321;
f(); // 輸出:321
}
// 隱式值捕獲
int main()
{
int a = 123;
auto f = [=] { cout << a << endl; }; // 值捕獲
f(); // 輸出:123
}
// 隱式引用捕獲
int main()
{
int a = 123;
auto f = [&] { cout << a << endl; }; // 引用捕獲
a = 321;
f(); // 輸出:321
}
C++11中的Lambda表達(dá)式捕獲外部變量主要有以下形式:
捕獲形式 | 說明 |
---|---|
[] | 不捕獲任何外部變量 |
[變量名, …] | 默認(rèn)以值得形式捕獲指定的多個外部變量(用逗號分隔)拼卵,如果引用捕獲酣溃,需要顯示聲明(使用&說明符) |
[this] | 以值的形式捕獲this指針 |
[=] | 以值的形式捕獲所有外部變量 |
[&] | 以引用形式捕獲所有外部變量 |
[=, &x] | 變量x以引用形式捕獲面睛,其余變量以傳值形式捕獲 |
[&, x] | 變量x以值的形式捕獲昵时,其余變量以引用形式捕獲 |
1.2 自動類型推導(dǎo)和 decltype
自動類型推導(dǎo):
auto x = 0; //0 是 int 類型,所以 x 也是 int 類型
auto c = 'a'; //char
auto d = 0.5; //double
auto national_debt = 14400000000000LL;//long long
vector<int> vi;
auto ci=vi.begin();
auto作為函數(shù)返回值時湾蔓,只能用于定義函數(shù)瘫析,不能用于聲明函數(shù)。
#pragma once
class test
{
public:
auto testWork(int a, int b); // 聲明函數(shù)(錯誤)
// 在引用頭文件的調(diào)用testWork函數(shù)是默责,編譯無法通過贬循。
}
class test
{
public:
auto testWork(int a, int b) // 定義函數(shù)(正確)
{
return a+b;
}
// 但如果把實現(xiàn)寫在頭文件中,可以編譯通過傻丝,
// 因為編譯器可以根據(jù)函數(shù)實現(xiàn)的返回值確定auto的真實類型甘有。
}
C++11 也提供了從對象或表達(dá)式中“俘獲”類型的機制,
新的操作符 decltype 可以從一個表達(dá)式中“俘獲”其結(jié)果的類型并“返回”:
decltype使用:
const vector<int> vi;
typedef decltype (vi.begin()) CIT;
CIT another_const_iterator;
const int ci = 0, &cj = ci;
decltype(ci) x = 0;
decltype(cj) y = x;
decltype(cj) z; //報錯葡缰,因為cj是一個引用亏掀,因此作為引用的 z 必須要進(jìn)行初始化
int i = 0;
decltype((i)) a; //報錯,因為a類型為 int&泛释,必須進(jìn)行初始化
decltype(i) b; //正確
需要注意的是滤愕,decltype((variable))的結(jié)果永遠(yuǎn)是引用,而decltype(variable)結(jié)果只有當(dāng)variable本身就是一個引用時才是引用怜校,其實就是根據(jù)它的類型決定间影;
1.3 deleted 函數(shù)和 defaulted 函數(shù)
=default; 指示編譯器生成該函數(shù)的默認(rèn)實現(xiàn)。這有兩個好處:一是讓程序員輕松了茄茁,少敲鍵盤魂贬,二是有更好的性能巩割。
與 defaulted 函數(shù)相對的就是 deleted 函數(shù), 實現(xiàn) non copy-able 防止對象拷貝,要想禁止拷貝付燥,用 =deleted 聲明一下兩個關(guān)鍵的成員函數(shù)就可以了:
int func()=delete;
//防止對象拷貝的實現(xiàn)
struct NoCopy
{
NoCopy & operator =(const NoCopy &) = delete;
NoCopy(const NoCopy &) = delete;
};
NoCopy a;
NoCopy b(a); //編譯錯誤宣谈,拷貝構(gòu)造函數(shù)是 deleted 函數(shù)
1.4 nullptr
nullptr 是一個新的 C++ 關(guān)鍵字,它是空指針常量键科,它是用來替代高風(fēng)險的 NULL 宏和 0 字面量的闻丑。
nullptr 是強類型的,所有跟指針有關(guān)的地方都可以用 nullptr,包括函數(shù)指針和成員指針:
void f(int); //#1
void f(char *);//#2
//C++03
f(0); //調(diào)用的是哪個 f?
//C++11
f(nullptr) //毫無疑問勋颖,調(diào)用的是 #2
const char *pc=str.c_str(); //data pointers
if (pc != nullptr)
cout << pc << endl;
int (A::*pmf)()=nullptr; //指向成員函數(shù)的指針
void (*pmf)()=nullptr; //指向函數(shù)的指針
1.5 右值引用
左值右值的區(qū)別:
左值:在賦值號左邊嗦嗡,可以被賦值的值,可以取地址饭玲;
右值:在賦值號右邊侥祭,取出值賦給其他變量的值;
左值引用:type & 引用名 = 左值表達(dá)式
右值引用:type && 引用名 = 右值表達(dá)式
有一個可以區(qū)分左值和右值的便捷方法:
看能不能對表達(dá)式取地址咱枉,如果能卑硫,則為左值,否則為右值蚕断。
int main() {
int i = 1; //i為常規(guī)左值
int &r = i; //正確:r綁定到i上,r是一個引用
int &&rr = i; //錯誤:不能將一個右值引用綁定到左值i上
int &r2 = i * 1; //錯誤:等號右邊是一個右值入挣,但左值引用只能綁定到左值上
int &&rr2 = i * 1; //正確:右值引用綁定到右值上
const int &r3 = i * 1; //正確:可以將一個const的左值引用綁定到右值上
return 0;
}
關(guān)于右值引用的兩個應(yīng)用:
1.移動構(gòu)造
2.移動賦值
MyString(const MyString& str) // 拷貝構(gòu)造函數(shù)
MyString(MyString&& str) // 移動構(gòu)造函數(shù)
MyString& operator=(const MyString& str) // 拷貝賦值函數(shù) =號重載
MyString& operator=(MyString&& str) // 移動賦值函數(shù) =號重載
// 我們可以銷毀一個move后源對象亿乳,也可以賦予它新值,但不能使用一個move后源對象的值
// 移動構(gòu)造函數(shù)径筏,參數(shù) "arg.member" 是左值
A(A&& arg) : member(std::move(arg.member))
{
}
// 移動賦值函數(shù)
A& operator=(A&& other) {
member = std::move(other.member);
return *this;
}
移動構(gòu)造函數(shù)與拷貝構(gòu)造函數(shù)的區(qū)別是葛假,拷貝構(gòu)造的參數(shù)是const MyString& str,是常量左值引用滋恬,而移動構(gòu)造的參數(shù)是MyString&& str聊训,是右值引用,而MyString("hello")是個臨時對象恢氯,是個右值带斑,優(yōu)先進(jìn)入移動構(gòu)造函數(shù)而不是拷貝構(gòu)造函數(shù)。而移動構(gòu)造函數(shù)與拷貝構(gòu)造不同勋拟,它并不是重新分配一塊新的空間勋磕,將要拷貝的對象復(fù)制過來,而是"偷"了過來敢靡,將自己的指針指向別人的資源挂滓,然后將別人的指針修改為nullptr,這一步很重要啸胧,如果不將別人的指針修改為空赶站,那么臨時對象析構(gòu)的時候就會釋放掉這個資源幔虏,"偷"也白偷了。
對于一個左值贝椿,肯定是調(diào)用拷貝構(gòu)造函數(shù)了想括,但是有些左值是局部變量,生命周期也很短团秽,能不能也移動而不是拷貝呢主胧?C++11為了解決這個問題,提供了std::move()方法來將左值轉(zhuǎn)換為右值习勤,從而方便應(yīng)用移動語義踪栋。我覺得它其實就是告訴編譯器,雖然我是一個左值图毕,但是不要對我用拷貝構(gòu)造函數(shù)夷都,而是用移動構(gòu)造函數(shù)吧。予颤。囤官。
vector<MyString> vecStr2;
vecStr2.reserve(1000); //先分配好1000個空間
for(int i=0;i<1000;i++){
MyString tmp("hello");
vecStr2.push_back(std::move(tmp)); //調(diào)用的是移動構(gòu)造函數(shù)
}
1.6 智能指針
shared_ptr
weak_ptr
unique_ptr
1.7 多線程
在C++11以前,C++的多線程編程均需依賴系統(tǒng)或第三方接口實現(xiàn)蛤虐,一定程度上影響了代碼的移植性党饮。C++11中,引入了boost庫中的多線程部分內(nèi)容驳庭,形成C++標(biāo)準(zhǔn)刑顺,形成標(biāo)準(zhǔn)后的boost多線程編程部分接口基本沒有變化,這樣方便了以前使用boost接口開發(fā)的使用者切換使用C++標(biāo)準(zhǔn)接口饲常,把容易把boost接口升級為C++接口蹲堂。我們通過如下幾部分介紹C++11多線程方面的接口及使用方法。
1.7.1 std::thread
std::thread為C++11的線程類贝淤,使用方法和boost接口一樣柒竞,非常方便,同時播聪,C++11的std::thread解決了boost::thread中構(gòu)成參數(shù)限制的問題朽基,我想著都是得益于C++11的可變參數(shù)的設(shè)計風(fēng)格。我們通過如下代碼熟悉下std::thread使用風(fēng)格犬耻。
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <thread>
void threadfun1()
{
std::cout << "threadfun1 - 1\r\n" << 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;
}
輸出:
threadfun1 - 1
threadfun2 - 1
threadfun1 - 2
join
detach
有以上輸出結(jié)果可以得知踩晶,t1.join()會等待t1線程退出后才繼續(xù)往下執(zhí)行(當(dāng)thread::join()函數(shù)被調(diào)用后,調(diào)用它的線程會被block枕磁,直到線程的執(zhí)行被完成渡蜻。);t2.detach()此時 子線程和main thread 完全分離,兩個線程自顧自的運行茸苇,main thread可以不等子線程運行完排苍,就提前結(jié)束。detach字符輸出后学密,主函數(shù)退出淘衙,threadfun2還未執(zhí)行完成,但是在主線程退出后腻暮,t2的線程也被已經(jīng)被強退出彤守。
1.7.2 std::atomic
std::atomic為C++11分裝的原子數(shù)據(jù)類型。
- 什么是原子數(shù)據(jù)類型哭靖?
從功能上看具垫,簡單地說,原子數(shù)據(jù)類型不會發(fā)生數(shù)據(jù)競爭试幽,能直接用在多線程中而不必我們用戶對其進(jìn)行添加互斥資源鎖的類型筝蚕。從實現(xiàn)上,大家可以理解為這些原子類型內(nèi)部自己加了鎖铺坞。
下面例子中起宽,我們使用10個線程,把std::atomic_int類型的變量iCount從100減到1济榨。
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <thread>
#include <atomic>
#include <stdio.h>
std::atomic_bool bIsReady = false; // std::atomicM<bool>
std::atomic_int iCount = 100;
void threadfun1()
{
while(!bIsReady) {
std::this_thread::yield();
}
while (iCount > 0)
{
printf("iCount:%d\r\n", iCount--);
}
}
int main()
{
std::atomic_bool b;
std::list<std::thread> lstThread;
for (int i = 0; i < 10; ++i)
{
lstThread.push_back(std::thread(threadfun1));
}
for (auto& th : lstThread)
{
th.join();
}
}
1.7.3 std::condition_variable
C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一樣坯沪,可以讓線程休眠,直到別喚醒擒滑,現(xiàn)在在從新執(zhí)行屏箍。線程等待在多線程編程中使用非常頻繁,經(jīng)常需要等待一些異步執(zhí)行的條件的返回結(jié)果橘忱。
// 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行中調(diào)用cv.wait(lck)的時候卸奉,線程將進(jìn)入休眠钝诚,在調(diào)用33行的go函數(shù)之前,10個線程都處于休眠狀態(tài)榄棵,當(dāng)22行的cv.notify_all()運行后凝颇,14行的休眠將結(jié)束,繼續(xù)往下運行疹鳄,最終輸出如上結(jié)果拧略。
1.8 std::function、std::bind封裝可執(zhí)行對象
std::bind和std::function也是從boost中移植進(jìn)來的C++新標(biāo)準(zhǔn)瘪弓,這兩個語法使得封裝可執(zhí)行對象變得簡單而易用垫蛆。此外,std::bind和std::function也可以結(jié)合我們一下所說的lamda表達(dá)式一起使用,使得可執(zhí)行對象的寫法更加“花俏”袱饭。
我們下面通過實例一步步了解std::function和std::bind的用法:
Test.h
//Test.h 示例代碼
class Test
{
public:
void Add()
{
}
};
//main.cpp 示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <functional>
#include <iostream>
#include "Test.h"
int add(int a,int b)
{
return a + b;
}
int main()
{
Test test;
test.Add();
return 0;
}
假如我們的需求是讓Test里面的Add由外部實現(xiàn)川无,如main.cpp里面的add函數(shù),有什么方法呢虑乖?
//修改Test.h
class Test
{
public:
typedef int(*FunType)(int, int);
void Add(FunType fun,int a,int b)
{
int sum = fun(a, b);
std::cout << "sum:" << sum << std::endl;
}
};
//main.cpp
int add(int a,int b)
{
return a + b;
}
....
....
Test test;
test.Add(add, 1, 2);
....
到現(xiàn)在為止懦趋,完美了嗎?如果你是Test.h的提供者疹味,你覺得有什么問題仅叫?我們把問題升級,假如add實現(xiàn)是在另外一個類內(nèi)部糙捺,如下代碼:
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
class TestAdd
{
public:
int Add(int a,int b)
{
return a + b;
}
};
int main()
{
Test test;
//test.Add(add, 1, 2);
return 0;
}
假如add方法在TestAdd類內(nèi)部诫咱,那你的Test類沒轍了,因為Test里的Test函數(shù)只接受函數(shù)指針继找。
//繼續(xù)修改Test.h
class Test
{
public:
void Add(std::function<int(int, int)> fun, int a, int b)
{
int sum = fun(a, b);
std::cout << "sum:" << sum << std::endl;
}
};
// Test類中std::function<int(int,int)>表示std::function封裝的可執(zhí)行對象返回值和兩個參數(shù)均為int類型遂跟。
main.cpp
int add(int a,int b)
{
std::cout << "add" << std::endl;
return a + b;
}
class TestAdd
{
public:
int Add(int a,int b)
{
std::cout << "TestAdd::Add" << std::endl;
return a + b;
}
};
int main()
{
Test test;
test.Add(add, 1, 2);
TestAdd testAdd;
test.Add(std::bind(&TestAdd::Add, testAdd, std::placeholders::_1, std::placeholders::_2), 1, 2);
return 0;
}
解釋:
std::bind第一個參數(shù)為對象函數(shù)指針,表示函數(shù)相對于類的首地址的偏移量婴渡;
testAdd為對象引用幻锁;
std::placeholders::_1和std::placeholders::_2為參數(shù)占位符,表示std::bind封裝的可執(zhí)行對象可以接受兩個參數(shù)边臼。
- 保存普通函數(shù)
void printA(int a)
{
cout << a << endl;
}
std::function<void(int a)> func;
func = printA;
func(2); //2
- 保存lambda表達(dá)式
std::function<void()> func_1 = [](){cout << "hello world" << endl;};
func_1(); //hello world
- 保存成員函數(shù)
class Foo{
Foo(int num) : num_(num){}
void print_add(int i) const {cout << num_ + i << endl;}
int num_;
};
//保存成員函數(shù)
std::function<void(const Foo&,int)> f_add_display = &Foo::print_add;
Foo foo(2);
f_add_display(foo,1);
關(guān)于bind的用法:
#include <iostream>
#include <functional>
using namespace std;
class A
{
public:
void fun_3(int k,int m)
{
cout<<"print: k="<<k<<",m="<<m<<endl;
}
};
void fun_1(int x,int y,int z)
{
cout<<"print: x=" <<x<<",y="<< y << ",z=" <<z<<endl;
}
void fun_2(int &a,int &b)
{
a++;
b++;
cout<<"print: a=" <<a<<",b="<<b<<endl;
}
int main(int argc, char * argv[])
{
//f1的類型為 function<void(int, int, int)>
auto f1 = std::bind(fun_1,1,2,3); //表示綁定函數(shù) fun 的第一哄尔,二,三個參數(shù)值為: 1 2 3
f1(); //print: x=1,y=2,z=3
auto f2 = std::bind(fun_1, placeholders::_1,placeholders::_2,3);
//表示綁定函數(shù) fun 的第三個參數(shù)為 3柠并,而fun 的第一岭接,二個參數(shù)分別由調(diào)用 f2 的第一,二個參數(shù)指定
f2(1,2);//print: x=1,y=2,z=3
auto f3 = std::bind(fun_1,placeholders::_2,placeholders::_1,3);
//表示綁定函數(shù) fun 的第三個參數(shù)為 3臼予,而fun 的第一鸣戴,二個參數(shù)分別由調(diào)用 f3 的第二,一個參數(shù)指定
//注意: f2 和 f3 的區(qū)別粘拾。
f3(1,2);//print: x=2,y=1,z=3
int m = 2;
int n = 3;
auto f4 = std::bind(fun_2, placeholders::_1, n); //表示綁定fun_2的第一個參數(shù)為n, fun_2的第二個參數(shù)由調(diào)用f4的第一個參數(shù)(_1)指定窄锅。
f4(m); //print: m=3,n=4
cout<<"m="<<m<<endl;//m=3 說明:bind對于不事先綁定的參數(shù),通過std::placeholders傳遞的參數(shù)是通過引用傳遞的,如m
cout<<"n="<<n<<endl;//n=3 說明:bind對于預(yù)先綁定的函數(shù)參數(shù)是通過值傳遞的缰雇,如n
A a;
//f5的類型為 function<void(int, int)>
auto f5 = std::bind(&A::fun_3, a,placeholders::_1,placeholders::_2); //使用auto關(guān)鍵字
f5(10,20);//調(diào)用a.fun_3(10,20),print: k=10,m=20
std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2);
fc(10,20); //調(diào)用a.fun_3(10,20) print: k=10,m=20
return 0;
}