【轉】C++11常用特性的使用經驗總結 C++11常用特性的使用經驗總結

C++11常用特性的使用經驗總結

概述及目錄

  C++11已經出來很久了,網上也早有很多優秀的C++11新特性的總結文章,在編寫本博客之前,博主在工作和學習中學到的關於C++11方面的知識,也得益於很多其他網友的總結。本博客文章是在學習的基礎上,加上博主在日常工作中的使用C++11的一些總結、經驗和感悟,整理出來,分享給大家,希望對各位讀者有幫助,文章中的總結可能存在很多不完整或有錯誤的地方,也希望讀者指出。大家可以根據如下目錄跳到自己需要的章節。

  1、關鍵字及新語法

    1.1、auto關鍵字及用法

    1.2、nullptr關鍵字及用法

    1.3、for循環語法

  2、STL容器

    2.1、std::array

    2.2、std::forward_list

    2.3、std::unordered_map

    2.4、std::unordered_set

  3、多線程

    3.1、std::thread

    3.2、st::atomic

    3.3、std::condition_variable

  4、智能指針內存管理

    4.1、std::shared_ptr

    4.2、std::weak_ptr

  5、其他

    5.1、std::function、std::bind封裝可執行對象

    5.2、lamda表達式

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:" << index << std::endl; std::cout << "str:" << str << std::endl; std::cout << "res:" << ret << 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 << number << 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 <array>
int main()
{
    std::array<int, 4> arrayDemo = { 1,2,3,4 };
    std::cout << "arrayDemo:" << std::endl;
    for (auto itor : arrayDemo)
    {
        std::cout << itor << std::endl;
    }
    int arrayDemoSize = sizeof(arrayDemo);
    std::cout << "arrayDemo size:" << arrayDemoSize << std::endl;
    return 0;
}
複製代碼

  運行結果:

     

  打印出來的size和直接使用數組定義結果是一樣的。

 

2.2、std::forward_list

  std::forward_list爲從++新增的線性表,與list區別在於它是單向鏈表。我們在學習數據結構的時候都知道,鏈表在對數據進行插入和刪除是比順序存儲的線性表有優勢,因此在插入和刪除操作頻繁的應用場景中,使用list和forward_list比使用array、vector和deque效率要高很多。

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <forward_list>
int main()
{
    std::forward_list<int> numbers = {1,2,3,4,5,4,4};
    std::cout << "numbers:" << std::endl;
    for (auto number : numbers)
    {
        std::cout << number << std::endl;
    }
    numbers.remove(4);
    std::cout << "numbers after remove:" << std::endl;
    for (auto number : numbers)
    {
        std::cout << number << 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 <iostream>
#include <string>
#include <unordered_map>
int main()
{
    std::unordered_map<std::string, std::string> mymap =
    {
        { "house","maison" },
        { "apple","pomme" },
        { "tree","arbre" },
        { "book","livre" },
        { "door","porte" },
        { "grapefruit","pamplemousse" }
    };
    unsigned n = mymap.bucket_count();
    std::cout << "mymap has " << n << " buckets.\n";
    for (unsigned i = 0; i<n; ++i) 
    {
        std::cout << "bucket #" << i << " contains: ";
        for (auto it = mymap.begin(i); it != mymap.end(i); ++it)
            std::cout << "[" << it->first << ":" << it->second << "] ";
        std::cout << "\n";
    }
    return 0;
}
複製代碼

  運行結果:

    

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

  

2.4、std::unordered_set

  std::unordered_set的數據存儲結構也是哈希表的方式結構,除此之外,std::unordered_set在插入時不會自動排序,這都是std::set表現不同的地方。

  我們來測試一下下面的代碼:  

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <iostream> #include <string> #include <unordered_set> #include <set> int main() { std::unordered_set<int> 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 << itor << std::endl; } std::set<int> 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 << itor << 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使用風格。

複製代碼
//示例代碼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; }
複製代碼

  運行結果:

    

  有以上輸出結果可以得知,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。

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <thread> #include <atomic> #include <stdio.h> std::atomic_bool bIsReady = false; std::atomic_int iCount = 100; void threadfun1() { if (!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(); } }
複製代碼

  運行結果:

  

   注:屏幕太短的原因,上面結果沒有截完屏

  從上面的結果可以看到,iCount的最小結果都是1,單可能不是最後一次打印,沒有小於等於0的情況,大家可以代碼複製下來多運行幾遍對比看看。

 

3.3、std::condition_variable

  C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一樣,可以讓線程休眠,直到別喚醒,現在在從新執行。線程等待在多線程編程中使用非常頻繁,經常需要等待一些異步執行的條件的返回結果。

  OK,在此不多解釋,下面我們通過C++11官網的例子看看。

複製代碼
 1 // webset address: http://www.cplusplus.com/reference/condition_variable/condition_variable/%20condition_variable
 2 // condition_variable example
 3 #include <iostream>           // std::cout
 4 #include <thread>             // std::thread
 5 #include <mutex>              // std::mutex, std::unique_lock
 6 #include <condition_variable> // std::condition_variable
 7 
 8 std::mutex mtx;
 9 std::condition_variable cv;
10 bool ready = false;
11 
12 void print_id(int id) {
13     std::unique_lock<std::mutex> lck(mtx);
14     while (!ready) cv.wait(lck);
15     // ...
16     std::cout << "thread " << id << '\n';
17 }
18 
19 void go() {
20     std::unique_lock<std::mutex> lck(mtx);
21     ready = true;
22     cv.notify_all();
23 }
24 
25 int main()
26 {
27     std::thread threads[10];
28     // spawn 10 threads:
29     for (int i = 0; i<10; ++i)
30         threads[i] = std::thread(print_id, i);
31 
32     std::cout << "10 threads ready to race...\n";
33     go();                       // go!
34 
35     for (auto& th : threads) th.join();
36 
37     return 0;
38 }
複製代碼

  運行結果:

   

  上面的代碼,在14行中調用cv.wait(lck)的時候,線程將進入休眠,在調用33行的go函數之前,10個線程都處於休眠狀態,當22行的cv.notify_all()運行後,14行的休眠將結束,繼續往下運行,最終輸出如上結果。

 

4、智能指針內存管理

  在內存管理方面,C++11的std::auto_ptr基礎上,移植了boost庫中的智能指針的部分實現,如std::shared_ptr、std::weak_ptr等,當然,想boost::thread一樣,C++11也修復了boost::make_shared中構造參數的限制問題。把智能指針添加爲標準,個人覺得真的非常方便,畢竟在C++中,智能指針在編程設計中使用的還是非常廣泛。

  什麼是智能指針?網上已經有很多解釋,個人覺得“智能指針”這個名詞似乎起得過於“霸氣”,很多初學者看到這個名詞就覺得似乎很難。

  簡單地說,智能指針只是用對象去管理一個資源指針,同時用一個計數器計算當前指針引用對象的個數,當管理指針的對象增加或減少時,計數器也相應加1或減1,當最後一個指針管理對象銷燬時,計數器爲1,此時在銷燬指針管理對象的同時,也把指針管理對象所管理的指針進行delete操作。

  如下圖所示,簡單話了一下指針、智能指針對象和計數器之間的關係:

  

  

  下面的小章節中,我們分別介紹常用的兩個智能指針std::shared_ptr、std::weak_ptr的用法。

4.1、std::shared_ptr

  std::shared_ptr包裝了new操作符動態分別的內存,可以自由拷貝複製,基本上是使用最多的一個智能指針類型。

  我們通過下面例子來了解下std::shared_ptr的用法:

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <memory> class Test { public: Test() { std::cout << "Test()" << std::endl; } ~Test() { std::cout << "~Test()" << std::endl; } }; int main() { std::shared_ptr<Test> p1 = std::make_shared<Test>(); std::cout << "1 ref:" << p1.use_count() << std::endl; { std::shared_ptr<Test> p2 = p1; std::cout << "2 ref:" << p1.use_count() << std::endl; } std::cout << "3 ref:" << p1.use_count() << std::endl; return 0; }
複製代碼

  運行結果:

  

  從上面代碼的運行結果,需要讀者瞭解的是:

  1、std::make_shared封裝了new方法,boost::make_shared之前的原則是既然釋放資源delete由智能指針負責,那麼應該把new封裝起來,否則會讓人覺得自己調用了new,但沒有調用delete,似乎與誰申請,誰釋放的原則不符。C++也沿用了這一做法。

  2、隨着引用對象的增加std::shared_ptr<Test> p2 = p1,指針的引用計數有1變爲2,當p2退出作用域後,p1的引用計數變回1,當main函數退出後,p1離開main函數的作用域,此時p1被銷燬,當p1銷燬時,檢測到引用計數已經爲1,就會在p1的析構函數中調用delete之前std::make_shared創建的指針。

4.2、std::weak_ptr

  std::weak_ptr網上很多人說其實是爲了解決std::shared_ptr在相互引用的情況下出現的問題而存在的,C++官網對這個只能指針的解釋也不多,那就先甭管那麼多了,讓我們暫時完全接受這個觀點。

  std::weak_ptr有什麼特點呢?與std::shared_ptr最大的差別是在賦值是,不會引起智能指針計數增加。

  我們下面將繼續如下兩點:

  1、std::shared_ptr相互引用會有什麼後果;

  2、std::weak_ptr如何解決第一點的問題。

  A、std::shared_ptr相互引用的問題示例:

   

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <memory>
class TestB;
class TestA
{
public:
    TestA()
    {
        std::cout << "TestA()" << std::endl;
    }
    void ReferTestB(std::shared_ptr<TestB> test_ptr)
    {
        m_TestB_Ptr = test_ptr;
    }
    ~TestA()
    {
        std::cout << "~TestA()" << std::endl;
    }
private:
    std::shared_ptr<TestB> m_TestB_Ptr; //TestB的智能指針
}; 

class TestB
{
public:
    TestB()
    {
        std::cout << "TestB()" << std::endl;
    }

    void ReferTestB(std::shared_ptr<TestA> test_ptr)
    {
        m_TestA_Ptr = test_ptr;
    }
    ~TestB()
    {
        std::cout << "~TestB()" << std::endl;
    }
    std::shared_ptr<TestA> m_TestA_Ptr; //TestA的智能指針
};


int main()
{
    std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
    std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();
    ptr_a->ReferTestB(ptr_b);
    ptr_b->ReferTestB(ptr_a);
    return 0;
}
複製代碼

  運行結果:

    

  大家可以看到,上面代碼中,我們創建了一個TestA和一個TestB的對象,但在整個main函數都運行完後,都沒看到兩個對象被析構,這是什麼問題呢?

  原來,智能指針ptr_a中引用了ptr_b,同樣ptr_b中也引用了ptr_a,在main函數退出前,ptr_a和ptr_b的引用計數均爲2,退出main函數後,引用計數均變爲1,也就是相互引用。

  這等效於說:

    ptr_a對ptr_b說,哎,我說ptr_b,我現在的條件是,你先釋放我,我才能釋放你,這是天生的,造物者決定的,改不了。

    ptr_b也對ptr_a說,我的條件也是一樣,你先釋放我,我才能釋放你,怎麼辦?

  是吧,大家都沒錯,相互引用導致的問題就是釋放條件的衝突,最終也可能導致內存泄漏。

 

  B、std::weak_ptr如何解決相互引用的問題

  我們在上面的代碼基礎上使用std::weak_ptr進行修改:

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <memory> class TestB; class TestA { public: TestA() { std::cout << "TestA()" << std::endl; } void ReferTestB(std::shared_ptr<TestB> test_ptr) { m_TestB_Ptr = test_ptr; } void TestWork() { std::cout << "~TestA::TestWork()" << std::endl; } ~TestA() { std::cout << "~TestA()" << std::endl; } private: std::weak_ptr<TestB> m_TestB_Ptr; }; class TestB { public: TestB() { std::cout << "TestB()" << std::endl; } void ReferTestB(std::shared_ptr<TestA> test_ptr) { m_TestA_Ptr = test_ptr; } void TestWork() { std::cout << "~TestB::TestWork()" << std::endl; } ~TestB() { std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock(); tmp->TestWork(); std::cout << "2 ref a:" << tmp.use_count() << std::endl; std::cout << "~TestB()" << std::endl; } std::weak_ptr<TestA> m_TestA_Ptr; }; int main() { std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>(); std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>(); ptr_a->ReferTestB(ptr_b); ptr_b->ReferTestB(ptr_a); std::cout << "1 ref a:" << ptr_a.use_count() << std::endl; std::cout << "1 ref b:" << ptr_a.use_count() << std::endl; return 0; }
複製代碼

  運行結果:

      

  由以上代碼運行結果我們可以看到:

  1、所有的對象最後都能正常釋放,不會存在上一個例子中的內存沒有釋放的問題。

  2、ptr_a 和ptr_b在main函數中退出前,引用計數均爲1,也就是說,在TestA和TestB中對std::weak_ptr的相互引用,不會導致計數的增加。在TestB析構函數中,調用std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock(),把std::weak_ptr類型轉換成std::shared_ptr類型,然後對TestA對象進行調用。

  

5、其他

  本章節介紹的內容如果按照分類來看,也屬於以上語法類別,但感覺還是單獨拿出來總結好些。

  下面小節主要介紹std::function、std::bind和lamda表達式的一些特點和用法,希望對讀者能有所幫助。

5.1、std::function、std::bind封裝可執行對象

  std::bind和std::function也是從boost中移植進來的C++新標準,這兩個語法使得封裝可執行對象變得簡單而易用。此外,std::bind和std::function也可以結合我們一下所說的lamda表達式一起使用,使得可執行對象的寫法更加“花俏”。

  我們下面通過實例一步步瞭解std::function和std::bind的用法:

  Test.h文件

複製代碼
//Test.h 示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
class Test { public: void Add() { } };
複製代碼

  main.cpp文件

複製代碼
//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; }
複製代碼

  解釋:

    上面代碼中,我們實現了一個add函數和一個Test類,Test類裏面有一個Test函數也有一個函數Add。

 

  OK,我們現在來考慮一下這個問題,假如我們的需求是讓Test裏面的Add由外部實現,如main.cpp裏面的add函數,有什麼方法呢?

  沒錯,我們可以用函數指針。

  我們修改Test.h

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
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的調用

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
....
....
Test test; test.Add(add, 1, 2);
....
複製代碼

  運行結果:

    

 

  到現在爲止,完美了嗎?如果你是Test.h的提供者,你覺得有什麼問題?

  我們把問題升級,假如add實現是在另外一個類內部,如下代碼:

複製代碼
//示例代碼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類內部,那你的Test類沒轍了,因爲Test裏的Test函數只接受函數指針。你可能說,這個不是我的問題啊,我是接口的定義者,使用者應該遵循我的規則。但如果現在我是客戶,我們談一筆生意,就是我要購買使用你的Test類,前提是需要支持我傳入函數指針,也能傳入對象函數,你做不做這筆生意?

  是的,你可以選擇不做這筆生意。我們現在再假設你已經好幾個月沒喫肉了(別跟我說你是素食主義者),身邊的蒼蠅肉、蚊子肉啊都不被你喫光了,好不容易等到有機會喫肉,那有什麼辦法呢?

  這個時候std::function和std::bind就幫上忙了。

  我們繼續修改代碼:

  Test.h

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
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封裝的可執行對象返回值和兩個參數均爲int類型。

  main.cpp

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
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第一個參數爲對象函數指針,表示函數相對於類的首地址的偏移量;

    testAdd爲對象指針;

    std::placeholders::_1和std::placeholders::_2爲參數佔位符,表示std::bind封裝的可執行對象可以接受兩個參數。

  

  運行結果:

  

  是的,得出這個結果,你就可以等着喫肉了,我們的Test函數在函數指針和類對象函數中都兩種情況下都完美運行。

  

5.2、lamda表達式

  在衆多的C++11新特性中,個人覺得lamda表達式不僅僅是一個語法新特性,對於沒有用過java或C#lamda表達式讀者,C++11的lamda表達式在一定程度上還衝擊着你對傳統C++編程的思維和想法。

  我們先從一個簡單的例子來看看lamda表達式:

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
1 int main() 2 { 3 auto add= [](int a, int b)->int{ 4 return a + b; 5 }; 6 int ret = add(1,2); 7 std::cout << "ret:" << ret << std::endl; 8 return 0; 9 }
複製代碼

  解釋:

    第3至5行爲lamda表達式的定義部分

    []:中括號用於控制main函數與內,lamda表達式之前的變量在lamda表達式中的訪問形式;

    (int a,int b):爲函數的形參

    ->int:lamda表達式函數的返回值定義

    {}:大括號內爲lamda表達式的函數體。

  運行結果:

  

  我使用lamda表達式修改5.1中的例子看看:

  main.cpp

複製代碼
//示例代碼1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
..... 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); test.Add([](int a, int b)->int { std::cout << "lamda add fun" << std::endl; return a + b; },1,2); return 0; }
複製代碼

  運行結果:

    

 

原文地址:https://www.cnblogs.com/feng-sc/p/5710724.html

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