C++ STL標準模板庫入門學習與應用

C++ 標準模板庫的核心包括以下三個組件:

  1. 容器(Containers)
    • deque、list、vector、map等
  2. 算法(Algorithms)
    • 算法作用于容器。它們提供了執行各種操作的方式,包括對容器內容執行初始化、排序、搜索和轉換等操作
  3. 迭代器(iterators)
    • 迭代器用于遍歷對象集合的元素。這些集合可能是容器,也可能是容器的子集

C++ 11 新特新

T&& 右值引用 std::move

右值引用出現之前我們只能用const引用來關聯臨時對象(右值)所以我們不能修臨時對象的內容,右值引用的出現就讓我們可以取得臨時對象的控制權,終于可以修改臨時對象了!

int main()
{
    int i = 42;
    int &r = i; // ok: r refers to i
    int &&rr = i;   // error: cannot bind an rvalue reference to an lvalue
    int &r2 = i * 42;   // error: i * 42 is an rvalue
    const int &r3 = i * 42; // ok: we can bind a reference to  const  to an rvalue
    int &&rr2 = i * 42;
    int &&rr3 = rr2;   // error: the expression rr2 is an lvalue!
    return 0;
}

即凡是可以 vartype varname; 這樣定義出來的變量(variable)其自身都是左值。

std::move相關。 右值引用因為綁定對象即將被銷毀,意味著沒有人會繼續訪問他們,所以就可以把他們(的資源)steal(偷)過來。 雖然不能將右值引用綁在左值上,但通過利用utility頭文件新增的函數模板move,它返回傳入對象的右值引用,可以達到 steal的效果。

    int &&rr3 = std::move(rr2); // ok

再提醒:一旦使用了move,編譯器就默認傳入對象已經不打算使用了,是可以被銷毀的,move之后該對象的值已經不確定,不要再訪問。還有由于對象偷取與復制的差別巨大,不注意會產生非常難定位的bug,所以所有使用move的地方一定要使用全稱std::move,給大家以提醒。(其實c++11在algorithm頭文件也新增了一個move,參數與意義都與此截然不同)。

#include <iostream>
using namespace std;

class HugeMem{
    public:
        HugeMem(int size): sz(size > 0 ? size : 1) {
            c = new int[sz];
        }
        ~HugeMem() { cout<<"HugeMem 析構\n";delete [] c; }
        HugeMem(HugeMem && hm): sz(hm.sz), c(hm.c) {
            cout<<"HugeMem move 構造\n";
            hm.c = nullptr;
        }
        int * c;
        int sz;
};
class Moveable{
    public:
        Moveable():i(new int(3)), h(1024) {}
        ~Moveable() { cout<<"Moveable 析構\n";delete i; }
        Moveable(Moveable && m):
            i(m.i), h(move(m.h)) {      // 強制轉為右值,以調用移動構造函數
                m.i = nullptr;
            }
        int* i;
        HugeMem h;
};

Moveable GetTemp() {
    //Moveable tmp = Moveable();
    Moveable tmp;
    cout << hex << "Huge Mem from " << __func__
        << " @" << tmp.h.c << endl; // Huge Mem from GetTemp @0x603030
    return tmp;
}

int main() {
    Moveable a(GetTemp());
    cout << hex << "Huge Mem from " << __func__
        << " @" << a.h.c << endl;   // Huge Mem from main @0x603030
}

早在C++11之前編譯器就把優化幾乎做到了極致——局部變量返回到函數外部并賦值給外部變量這個過程基本上不存在任何多余的臨時變量構造和析構,這比move機制更加高效。顯式指定move以后,return std::move(localvar)這里會強行從localvar移動構造一個臨時變量temp,然后return temp(temp這里會有RVO優化)。

auto for循環

需要注意的是,auto不能用來聲明函數的返回值。但如果函數有一個尾隨的返回類型時,auto是可以出現在函數聲明中返回值位置。這種情況下,auto并不是告訴編譯器去推斷返回類型,而是指引編譯器去函數的末端尋找返回值類型。在下面這個例子中,函數的返回值類型就是operator+操作符作用在T1、T2類型變量上的返回值類型。

template <typename T1, typename T2>
auto compose(T1 t1, T2 t2) -> **decltype**(t1 + t2)
{
   return t1+t2;
}
auto v = compose(2, 3.14); // v's type is double

auto與for配合使用

std::map<std::string, std::vector<int>> map;
std::vector<int> 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;
}

std::lambda

“Lambda 表達式”(lambda expression)是一個匿名函數,Lambda表達式基于數學中的λ演算得名,直接對應于其中的lambda抽象(lambda abstraction),是一個匿名函數,即沒有函數名的函數。

C++11 的 lambda 表達式規范如下:

  • [ capture ] ( params ) mutable exception attribute -> ret { body } (1)
  • [ capture ] ( params ) -> ret { body } (2)
  • [ capture ] ( params ) { body } (3)
  • [ capture ] { body } (4)

其中

(1) 是完整的 lambda 表達式形式, (2) const 類型的 lambda 表達式,該類型的表達式不能改捕獲("capture")列表中的值。 (3)省略了返回值類型的 lambda 表達式,但是該 lambda 表達式的返回類型可以按照下列規則推演出來: 如果 lambda 代碼塊中包含了 return 語句,則該 lambda 表達式的返回類型由 return 語句的返回類型確定。 如果沒有 return 語句,則類似 void f(...) 函數。 省略了參數列表,類似于無參函數 f()。

[] // 不引用外部變量 [x, &y] // x引用方式 ,y 傳值 [&] // 任何使用的外部變量都是引用方式。 [=] // 任何被使用到的外部都是傳值方式。 [&, x] // 除x傳值以外其他的都以引用方式。 [=, &z] // 除z引用以外其他的都是以傳值方式使用。

int main()
{
    std::vector<int> c { 1,2,3,4,5,6,7 };
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; } ), c.end());

    std::cout << "c: ";
    for (auto i: c) {
        std::cout << i << ' ';
    }
    std::cout << '\n';

    // 可以用auto 接收一個lambda 表達式。
    auto func1 = [](int i) { return i+4; };
    std::cout << "func1: " << func1(6) << '\n';

    // std::function 也可以接收lambda 表達式。

    std::function<int(int)> func2 = [](int i) { return i+4; };
    std::cout << "func2: " << func2(6) << '\n';

    std::function<int()> func3 = [x]{return x;};
    std::cout << "func3: " << func3() << '\n';


    std::vector<int> someList = {1,2,3};  //這里是c++11
    int total = 0;
    double sum = 0.0f;
    std::for_each(someList.begin(), someList.end(), [&total](int x) { total += x; });
    std::cout << total << '\n';
    std::for_each(someList.begin(), someList.end(), [&](int x){ total += x; sum += x;});
    std::cout << total << '\n';
    std::cout << sum << '\n';

    //再寫一種簡單的lambda
    [](){std::cout<<"就地展開的lambda\n";}();

}

bind

std::bind是STL實現函數組合概念的重要手段,std::bind綁定普通函數(函數指針)、lambda表達式、成員函數、成員變量、模板函數等

#include <iostream>
#include <functional>

void f(int n1, int n2, int n3, const int& n4, int n5)
{
        std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}

int g(int n1)
{
        return n1;
}

struct Foo {
    void print_sum(int n1, int n2)
    {
        std::cout << n1+n2 << '\n';
    }
    static void static_func(std::function<int(int)> f,int n)
    {
        std::cout<<"call static_func\n";
        std::cout<<"f(n):\t"<<f(n)<<"\n";
    }
    int data = 10; //c++11 支持聲明是就初始化值
};

int main()
{
    using namespace std::placeholders;

    // std::cref(n) 表示要把n以引用的方式傳入  
    int n = 7;
    auto f1 = std::bind(f, _2, _1, 42, std::cref(n), n);
    n = 10;
    f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused


    // 綁定一個子表達式,用_3替換了 其他位置的變量
    // std::bind(g, _3) 在這里已經表示int
    auto f2 = std::bind(f, _4, std::bind(g, _4), _4, 4, 5);
    f2(10, 11, 12 ,13);

    // 綁定成員函數
    Foo foo;
    auto f3 = std::bind(&Foo::print_sum, foo, 95, _1);
    f3(5);

    // 綁定成員變量
    auto f4 = std::bind(&Foo::data, _1);
    std::cout << f4(foo) << '\n';


    // 綁定靜態成員函數
    auto f5 = std::bind(&Foo::static_func,g,_1);

    f5(3);
}

std::function

通過std::function對C++中各種可調用實體(普通函數、Lambda表達式、函數指針、以及其它函數對象等)的封裝,形成一個新的可調用的std::function對象;讓我們不再糾結那么多的可調用實體。

轉換后的std::function對象的參數能轉換為可調用實體的參數; 可調用實體的返回值能轉換為std::function對象的返回值。 std::function對象最大的用處就是在實現函數回調(實際工作中就是用到了這一點),使用者需要注意,它不能被用來檢查相等或者不相等,但是可以與NULL或者nullptr進行比較。

#include <functional>
#include <iostream>
using namespace std;

std::function< int(int)> Functional;

// 普通函數
int TestFunc(int a)
{
return a;
}

// Lambda表達式
auto lambda = [](int a)->int{ return a; };

// 仿函數(functor)
class Functor
{
public:
int operator()(int a)
{
return a;
}
};

// 1.類成員函數
// 2.類靜態函數
class TestClass
{
public:
int ClassMember(int a) { return a; }
static int StaticMember(int a) { return a; }
};

int main()
{
// 普通函數
Functional = TestFunc;
int result = Functional(10);
cout << "普通函數:"<< result << endl;

// Lambda表達式
Functional = lambda;
result = Functional(20);
cout << "Lambda表達式:"<< result << endl;

// 仿函數
Functor testFunctor;
Functional = testFunctor;
result = Functional(30);
cout << "仿函數:"<< result << endl;

// 類成員函數
TestClass testObj;
Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
result = Functional(40);
cout << "類成員函數:"<< result << endl;

// 類靜態函數
Functional = TestClass::StaticMember;
result = Functional(50);
cout << "類靜態函數:"<< result << endl;

return 0;
}

initializer_list

過往,我們這樣給vector賦值:

std::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

需要感謝的是,C++11讓你更方便。

std::vector v = { 1, 2, 3, 4 };

這就是所謂的initializer list。更進一步,有一個關鍵字叫initializer list

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <typeinfo>

class MyNumber
{
public:
    MyNumber(const std::initializer_list<int> &v) {
        for (auto itm : v) {
            mVec.push_back(itm);
        }
    }

    void print() {
        for (auto itm : mVec) {
            std::cout << itm << " ";
        }
    }
private:
    std::vector<int> mVec;
};

class Test {
public:
    void show()
    {
        for(auto kv : nameToBirthday)
        {
            std::cout<<"key:\t"<<kv.first<<"\tvalue:\t"<<kv.second<<"\n";
        }
    }
private:
    static std::map<std::string, std::string> nameToBirthday;
};
std::map<std::string,std::string> Test::nameToBirthday  = {
    {"lisi", "18841011"},
    {"zhangsan", "18850123"},
    {"wangwu", "18870908"},
    {"zhaoliu", "18810316"}
};

class CompareClass
{
public:
    CompareClass (int,int)
    {std::cout<<"call old const\n";}
    CompareClass (std::initializer_list <int> )
    {std::cout<<"call initializer_list const\n";}
};


int main()
{
    MyNumber m = { 1, 2, 3, 4 };
    m.print();  // 1 2 3 4

    Test t;
    t.show();

    std::map<int,int> ii_map = {{1,1},{2,2}};

    CompareClass foo {10,20};  // calls initializer_list ctor
    CompareClass bar (10,20);  // calls first constructor


    for(auto kv : ii_map)
    {
        std::cout<<"key:\t"<<typeid(kv.first).name()<<"\n";
    }
    return 0;
}

注意

本文內容轉自博客:http://blog.csdn.net/tangliguantou/article/details/50549751

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容