1. 程式人生 > >《深入應用C++11》第一章

《深入應用C++11》第一章

1,using可重定義一個模板,該模板既不是類模板也不是函式模板,是一種新的模板形式:模板別名

templte <typename T>
using func_t=void (*)(T,T);
//使用func_t模板
func_t<int> xxx_2;

2,函式模板的引數在使用上與其他預設函式略不同,它沒有必須寫在引數表最後的限制。

template <typename R = int , typename U>
R func(U val)
{
val;
}
int main(void)
{
func(123);
return 0;
}

但在呼叫函式模板的時候,若顯示指定了模板的引數,由於引數填充順序是從左到右的,因此下面這樣 呼叫函式模板返回的是long,不是int
func(123); //func的返回值型別是long

3,當預設模板引數和模板引數自動推導同時使用時,若函式模板無法自動推匯出引數型別,則編譯器將使用預設模板引數;否則將使用自動推匯出的引數型別。
優先順序:自動推匯出的引數型別 > 預設模板引數

4,POD型別
粗略上來講,POD是C++為相容C的記憶體佈局而設計的,主要用於修飾使用者自定義型別。
但POD卻遠比這個要複雜。POD(Plain Old Data),從字面意思上來看,是平凡普通的老舊格式的資料,POD是一個型別屬性,既不是關鍵字也不會像”volatile”用來修飾型別資訊。
POD型別,說明該資料是普通的,不會有什麼虛擬函式啊,虛繼承啊,或者內嵌的資料型別很複雜的情況。也就是說,POD型別的C++變數,可以直接用C語言中的struct來解釋
直觀的來說,POD型別中的O代表了與C語言的相容性,可以直接使用memcpy()直接複製而不會出現任何問題。POD這個想法的由來就是要支援兩種最基本的屬性:
(1)支援靜態初始化
(2)編譯C++中的POD型別所得到的記憶體佈局,和C中編譯struct的記憶體佈局相同

具體地,C++11將POD型別劃分為兩個概念:
trivial
standard layout
在C++11中,更多的是用trivial和standard類代替POD型別。

trivial意思是無意義,這個trivial和non-trivial是對類的四種函式來說的:
建構函式(ctor)
複製建構函式(copy)
賦值函式(assignment)
解構函式(dtor)
如果至少滿足下面3條裡的一條:
顯式(explict)定義了這四種函式。
類裡有非靜態非POD的資料成員。
有基類。
那麼上面的四種函式是non-trivial函式

5,在使用初始化列表時,什麼樣的型別C++會認為它是一個聚合體
(1)型別是一個普通陣列
(2)型別是一個類,且

無使用者自定義的建構函式
無私有或者保護的非靜態資料成員
無基類
無虛擬函式
不能有{}和=直接初始化的非靜態資料成員
聚合型別的定義並非遞迴的,當一個類的非靜態成員是非聚合型別時,這個類也有可能是聚合型別,可以列表初始化。

6,std::initializer_list擁有與一個無引數的建構函式;同時它是非常高效的,它只儲存列表中的資料的引用。因此下面的做法是錯誤的:

std::initializer_list<int> func()
{
int a =1,b=2;
return {a,b};
}

這樣雖然可以通過編譯,但是無法得到希望的結果,a,b的生命週期在呼叫後結束,引用的將是不確定的內容。
可以改為:

std::vector<int> func()
{
int a =1,b=2;
return {a,b};
}

7,列表初始化對於型別收窄會報錯

8,可呼叫物件
(1)函式指標
(2)具有operator()成員函式的類物件
(3)一個可被轉為函式指標的類物件
(4)一個類成員指標
可呼叫型別的定義不包括函式型別或者函式引用(只有函式指標),因為函式型別不能直接用來定義物件;而函式引用從某種意義上來說可以看做是一個const的函式指標。

9,std::function是可呼叫物件的包裝器。它是一個類模板,可以容納除了類成員(函式)指標之外的所有可呼叫物件。

10,std::bind的返回型別是一個stl內部定義的仿函式型別,可以直接賦值給一個std::function

auto newCallable=bind(callable.arg_list)
newCallable本身是一個可呼叫物件

例子:

#include <iostream>
#include <functional>

void output(int x,int y){
    std::cout<<x<<" "<<y<<std::endl;
}

int main(int argc, char **argv) {
    std::bind(output,1,2)();// 輸出1 2
    //等價於
    auto f=std::bind(output,std::placeholders::_1,std::placeholders::_2);
    f(1,2); //輸出1 2
    //等價於
    std::bind(output,std::placeholders::_1,std::placeholders::_2)(1,2);// 輸出1 2

    std::bind(output,std::placeholders::_1,2)(1); //輸出 1 2
    std::bind(output,2,std::placeholders::_1)(1); //輸出2 1
    //std::bind(output,2,std::placeholders::_2)(1); //error 呼叫時沒有第二個引數
    std::bind(output,2,std::placeholders::_2)(1,2); //輸出 2 2
    std::bind(output,std::placeholders::_2,std::placeholders::_1)(1,2); //輸出 2 1
}

結果:

1 2
1 2
1 2
1 2
2 1
2 2
2 1

11,關於std::bind的一些用法總結
最近在看《深入應用c++11》的時候遇到了std::bind的一些新用法,之前沒有遇到過,這裡記錄下。通常時候std::bind是與std::function一起結合使用的,std::bind是一個函式模板,而std::function是一個類模板,這個從其原始碼就可看出

/**
 *  @brief Function template for std::bind.
 *  @ingroup binders
 */
template<typename _Func, typename... _BoundArgs>
  inline typename
  _Bind_helper<__is_socketlike<_Func>::value, _Func, _BoundArgs...>::type
  bind(_Func&& __f, _BoundArgs&&... __args)
  {
    typedef _Bind_helper<false, _Func, _BoundArgs...> __helper_type;
    typedef typename __helper_type::__maybe_type __maybe_type;
    typedef typename __helper_type::type __result_type;
    return __result_type(__maybe_type::__do_wrap(std::forward<_Func>(__f)),
             std::forward<_BoundArgs>(__args)...);
  }

template<typename _Signature>
    class function;

std::bind的常見用法
之前的理解僅僅是將bind當做繫結一個全域性函式,現在又遇到了一些其他的用法,直接上程式碼吧

/*
 * main.cpp
 *
 *  Created on: 2017年12月3日
 *      Author: Administrator
 */
#include  <iostream>
#include <functional>

void output(int x, int y)
{
    std::cout << x << " " << y << std::endl;
}
class A
{
public:
    int i_ = 0;

    void output(int x, int y)
    {
        std::cout << x << " " << y << std::endl;
    }

    void operator()() const //無引數 無返回值的仿函式
    {
        std::cout <<"仿函式"<< std::endl;
    }
};

int main(void)
{
    //1,繫結全域性函式
    std::bind(output,1,2); //全域性函式,僅僅是繫結,沒有呼叫
    std::bind(output,1,2)();//繫結並呼叫全域性函式

    //2,繫結成員函式
    A a;
    std::function<void(int, int)> fr =
        std::bind(&A::output, &a /*呼叫者*/, std::placeholders::_1, std::placeholders::_2);
    fr(1, 2);  //輸出 1 2

    //3,繫結成員變數(包括靜態的成員變數) 成員變數這裡function中是int&(void)
    std::function<int&(void)> fr_i = std::bind(&A::i_, &a/*呼叫者*/);  //vs13的bug,繫結成員變數要報錯
    fr_i() = 123;

    std::cout << a.i_ << std::endl;  //輸出 123
    std::cout << fr_i() << std::endl;  //輸出 123

    A a1;
    std::function<int&(void)> fr_i_1 = std::bind(&A::i_, a1/*呼叫者*/); //呼叫者沒有傳指標
    fr_i_1() = 123;

    std::cout << a1.i_ << std::endl;  //輸出 0
    std::cout << fr_i_1() << std::endl;  //輸出 123

    //4,仿函式
    std::function<void(void)> op = std::bind(&A::operator(),a);
    op();

    return 0;
}

結果如下:

1 2
1 2
123
123
0
123
仿函式

12,lambda表示式通過捕獲列表捕獲一定範圍內的變數

[]不捕獲任何變數
[&]捕獲外部作用域中所有變數,並作為引用在函式體內引用
[=]捕獲外部作用域中所有變數,按值捕獲
[=,&foo]按值捕獲外部作用域的所有變數,並按引用捕獲foo變數
[bar] 僅僅只值捕獲bar變數
[this]捕獲當前類中的this指標,讓lambda表示式擁有和當前類成員函式同樣訪問許可權。如果已經使用了&或者=,就預設新增此項。
捕獲this的目的是可以在lambda中使用當前類的成員函式和成員變數

13,lambda無法修改通過賦值方式捕獲的外部變數。
易出錯點:lambda的延時呼叫,注意值捕獲與引用捕獲的時候,延時呼叫的區別

14,lambda的型別在c++11中被稱為“閉包型別”,可認為其是一個仿函式。

    std::function<int(int)> f1=[](int a){return a;};
    std::function<int(int)> f2=std::bind([](int a){return a;},123);
    std::function<int(void)> f3=std::bind([](int a){return a;},123);

這裡函式f2和f3都是繫結的同一個函式,但是f2卻是帶有int引數,f3無引數

15,沒有捕獲變數的lambda表示式可以直接轉換為函式指標,而捕獲變數的lambda表示式則不能轉換為函式指標。

16,按c++的標準,lambda的operator()預設是const,如果是按值捕獲的話,const成員函式是無法修改成員變數的值,而mutable的作用就是取消operator()的const屬性。

17,tuple簡單的建立與取值方式

    //建立方式1
    tuple<const char*,int> tp=make_tuple("123",1);

    //建立方式2
    int x=1;
    int y=2;
    string s="aa";
    auto tp1=std::tie(x,s,y);//tp的型別實際為std::tuple<int&,string&,int&>

    //取值方式1
    const char * data = tp.get<0>(); //獲取第一個值
    int len=tp.get<1>(); //獲取第二個值

    //取值方式2
    int x,y;
    string a;
    std::tie(x,a,y) = tp1;
    std::tie(std::ignore,std::ignore,y) =tp1; //只解第三個值

其中std::tie建立的是一個元組的左值引用