Python

上下文管理

with 关键字

紧跟在with后面的语句会被求值, 其返回对象的 __enter__ 方法被调用,这个方法的返回值将被赋值给 as 关键字后面的变量, 当with后面的代码块全部被执行完之后,又将调用前面返回对象的 __exit__ 方法。

使用 with 关键字,并配合自定义的 __enter____exit__ 函数,可以巧妙地实现上下文管理。

contextlib

编写 __enter____exit__ 仍然很繁琐,因此Python的标准库contextlib提供了更简单的写法, 比如:

from contextlib import contextmanager

class Query(object):

    def __init__(self, name):
        self.name = name

    def query(self):
        print('Query info about %s...' % self.name)

@contextmanager
def create_query(name):
    print('Begin')
    q = Query(name)
    yield q
    print('End')

@contextmanager 这个decorator接受一个generator,用 yield 语句把 with ... as var 把变量输出出去,然后,with 语句就可以正常地工作了:

with create_query('Bob') as q:
    q.query()

魔法方法

构造、删除

方法名

功能

__new__

申请内存空间,生成类实例。

__init__

在对象被生成之后调用,分配类属性。

__del__

对象被销毁时调用。

访问类属性

方法名

功能

__getattr__

当访问该类不存在的属性时,会调用此方法。

__setattr__

在设置类的属性时,会调用此方法。

__getattribute__

优先级大于 __getattr__,并且不论被访问的属性是否存在,都会进入此方法。

序列化相关

方法名

功能

__getstate__

当使用 deepcopy 或者 pickle.dump 此类时调用,输出需要保存的属性。

__setstate__

当使用 deepcopy 或者 pickle.load 此类时调用,加载需要保存的属性。

__getnewargs__

保存该函数的返回值,传递给 __new__ 函数。

__getnewargs_ex__

同上,但是优先级更高。

类容器

len()               object.__len__(self)
self[key]           object.__getitem__(self, key)
self[key] = value   object.__setitem__(self, key, value)
del[key]            object.__delitem__(self, key)
iter()              object.__iter__(self)
reversed()          object.__reversed__(self)
in操作              object.__contains__(self, item)
字典key不存在       object.__missing__(self, key)

一元操作符

-           object.__neg__(self)
+           object.__pos__(self)
abs()       object.__abs__(self)
~           object.__invert__(self)
complex()   object.__complex__(self)
int()       object.__int__(self)
long()      object.__long__(self)
float()     object.__float__(self)
oct()       object.__oct__(self)
hex()       object.__hex__(self)
round()     object.__round__(self, n)
floor()     object__floor__(self)
ceil()      object.__ceil__(self)
trunc()     object.__trunc__(self)

二元操作符

+   object.__add__(self, other)
-   object.__sub__(self, other)
*   object.__mul__(self, other)
//  object.__floordiv__(self, other)
/   object.__div__(self, other)
%   object.__mod__(self, other)
**  object.__pow__(self, other[, modulo])
<<  object.__lshift__(self, other)
>>  object.__rshift__(self, other)
&   object.__and__(self, other)
^   object.__xor__(self, other)
|   object.__or__(self, other)
+=  object.__iadd__(self, other)
-=  object.__isub__(self, other)
*=  object.__imul__(self, other)
/=  object.__idiv__(self, other)
//= object.__ifloordiv__(self, other)
%=  object.__imod__(self, other)
**= object.__ipow__(self, other[, modulo])
<<= object.__ilshift__(self, other)
>>= object.__irshift__(self, other)
&=  object.__iand__(self, other)
^=  object.__ixor__(self, other)
|=  object.__ior__(self, other)
<   object.__lt__(self, other)
<=  object.__le__(self, other)
==  object.__eq__(self, other)
!=  object.__ne__(self, other)
>=  object.__ge__(self, other)
>   object.__gt__(self, other)

C++

std::tuple

std::tuple 定义在头文件 <tuple> 中。

基本用法

初始化:该结构可以组合多种类型,初始化列表中必须包含其声明的所有项。

  • std::tuple :必须声明模板类型。

  • std::make_tuple :可以自动推导返回类型。

提取内容:

  • std::get :提取单个内容,需要索引,返回该对象的引用,因此可以修改。

  • std::tie :提取所有内容,不需要的项目可以使用 std:ignore 来代替。

  • []:使用方括号提取所有内容。

例子:

#include <iostream>  // std::cout
#include <tuple>  // std::tuple, std::get, std::tie, std::ignore

template<typename T>
void print(T last) {
    std::cout << last << std::endl;
}

template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << '\t';
    print(args...);
}

int main () {
    std::tuple<int,char> foo = {10,'x'};
    auto bar = std::make_tuple("test", 3.1, 14, 'y');

    // access element
    std::get<0>(foo) = 100;
    print(std::get<0>(foo));

    // unpack using std::tie
    int myint; char mychar;
    std::tie(std::ignore, std::ignore, myint, mychar) = bar;
    print(myint, mychar);

    // unpack using [] since c++17
    auto [a, b, c, d] = bar;
    print(a, b, c, d);

    return 0;
}

std::apply

<tuple> 中提供了一个类似于 std::invoke 的函数 std::apply ,区别在于后者可以把 std::tuple 类型的变量展开。

int f(int, int);
int g(tuple<int, int>);

std::tuple<int, int> tup(1, 2);

std::invoke(f, 1, 2); // calls f(1, 2)
std::invoke(g, tup);  // calls g(tup)
std::apply(f, tup);   // also calls f(1, 2)
// std::invoke(f, tup);  // 错误,参数不匹配

variant

std::variant 定义在头文件 <variant> 中。

基本用法

  1. 初始化:该结构可以代表多种类型,初始化列表中有且仅能有其声明的某一项

  • std::variant :必须声明模板类型。

  1. 提取内容

  • std::get :需要类型或者索引,当此处索取的类型和变量实际拥有的类型不一致时报错。

Warning

std::get 是一个常量表达式,无法在运行时确定要获取哪个值。

#include <variant>
#include <string>

int main()
{
    std::variant<int, float> v, w;
    v = 12; // v contains int
    int i = std::get<int>(v);
    w = std::get<int>(v);
    w = std::get<0>(v); // same effect as the previous line
    w = v; // same effect as the previous line

    //  std::get<double>(v); // error: no double in [int, float]
    //  std::get<3>(v);      // error: valid index values are 0 and 1

    try {
    std::get<float>(w); // w contains int, not float: will throw
    }
    catch (const std::bad_variant_access&) {}
}

Warning

std::variant 在初始化时可以完成隐式的类型转换,但是可能会出现两者皆可的情况:

std::variant<std::string, void const*> y("abc")

动态类型

在Python代码中,函数的返回类型可以在运行时再决定,而C++不支持这样,C++的所有类型都已经在编译时确定。

std::variant 可以解决这一问题,但其并没有改变C++这门语言的特性,因为 std::variant 本身的类型就是确定的。

#include <iostream>
#include <variant>

std::variant<bool, int, std::string>
foo(const std::string & s) {
    std::variant<bool, int, std::string> v;
    if (s == "bool") {
        v = false;
    } else if (s == "int") {
        v = 0;
    } else {
        v = "other";
    }

    return v;
}

std::visit

以下是猜测。。。

std::invoke 没办法处理 std::variant 这种输入,原因是不知道这个 std::variant 当前持有的类型。

std::visit 可以让 std::variant 作为参数传入时,坍缩成其当前持有的类型。

#include <iostream>
#include <functional>
#include <variant>
#include <string>
#include <vector>

using var_t = std::variant<int, long, double, std::string>;

int main() {
    std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
    for(auto& v: vec)
        // arg 的类型依次是 int, long, double, std::string
        std::visit([](auto&& arg){std::cout << arg << std::endl;}, v);

        // 编译时报错,arg 每次都是 variant 类型,而它没有定义 << 操作符
        // std::visit([](auto&& arg){std::cout << arg << std::endl;}, v);
}

面向对象的基础。

成员变量和成员方法的关键字

关键字

功能

const 在变量前

常量

const 在方法前

方法返回一个常量

const 在方法后

该方法不修改成员变量的值

static 在变量前

属于类的变量,位于静态存储区,不实例化也可以访问

static 在方法前

属于类的方法,没有实例化也可以调用,没有指向实例的 this 指针

virtual 在方法前

虚函数,当虚函数后面紧接着 =0 则表示为纯虚函数,子类必须实现

初始化列表

C++11允许构造函数和其他函数把初始化列表当做参数,举例如下:

class CompareClass
{
CompareClass (int,int);
CompareClass (initializer_list<int>);
};

int main()
{
    myclass foo {10,20};  // calls initializer_list ctor
    myclass bar (10,20);  // calls first ctor
}

C++11新增关键字

noexcept

放在函数声明的后面,表示这个函数不会抛出异常,从而令编译器可以做更多优化。 在 C++11 之前,这个声明由 throw() 来实现。

void swap(Type& x, Type& y) throw()   // C++11 之前
{
    x.swap(y);
}
void swap(Type& x, Type& y) noexcept  // C++11 以后
{
    x.swap(y);
}

decltype

这个关键字被用来在编译期间推断变量类型,当然也可以为我们编写代码提供方便。

int x = 4;
decltype(x) y;  // 推断结果为 int,a 的类型为 int。
  1. 结合 usingtypedef 可以用于类型定义:

vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++)
{
    //...
}
  1. 重用匿名类型

struct
{
    int d ;
    doubel b;
}a;

decltype(a) b;  // 定义了一个上面匿名的结构体

auto

Note

auto 关键字不允许没有初始值的声明,比如:

int x;
auto y = x;  // ok
auto z;      // error
  1. 用来自动推断类型,从而节省很多代码。

std::vector<int> v;
std::vector<int>::iterator i1 = v.begin();
auto i2 = v.begin();
  1. 配合 lambda 表达式类使用,每个 lambda 表达式的类型都是独一无二的,这个类型只有编译器知道,因此创建时需要使用 auto 关键字。

auto closure = [](const int&, const int&) {}

虽然也可以使用 function 来实现,但这远没有 auto 来的简单直观。

  1. 模板函数返回值

template<class T, class U>
auto mul(T x, U y) -> decltype(x * y)
{
    return x * y;
}

Note

在 C++14 之后,可以省去后面的 decltype(x * y)

default和delete

  • default

创建类时,如果自己提供了任何形式的构造函数,那么编译器将不会产生默认构造函数。 default 关键字则告诉编译器产生一个默认构造函数,如:

class A
{
public:
    A(int a){}
    A() = default;
};
  • delete

禁用被 delete 修饰的函数签名。

class A
{
public:
    A(int a){};
    A(double) = delete;         // conversion disabled
    A& operator=(const A&) = delete;  // assignment operator disabled
};
int main{
    A a(10);     // OK
    A b(3.14);   // Error: conversion from double to int disabled
    a = b;       // Error: assignment operator disabled
}

override和final

如果派生类在虚函数声明时使用了 override 描述符,那么该函数必须重载其基类中的同名虚函数,否则代码将无法通过编译。 要求函数名相同,参数一致,且基类中的该函数是虚函数。

final 禁用类的继承和函数的重写。

class Super final
{
//......
};
class Super
{
public:
    Supe();
    virtual void SomeMethod() final;
};

Lambda表达式

“Lambda 表达式”(lambda expression)是指匿名函数,基本语法如下:

[capture list] (parameter list) ->return type { function body }

  • [] // 沒有定义任何变量。使用未定义变量会导致错误。

  • [x, &y] // x 以传值的方式传入,y 以参考方式传入。

  • [&] // 任何被使用到的外部变量皆隐式地以參考方式加以引用。

  • [=] // 任何被使用到的外部变量皆隐式地以传值方式加以引用。

  • [&, x] // x 显式地以传值方式加以引用。其余变量以参考方式加以引用。

  • [=, &z] // z 显式地以参考方式加以引用。其余变量以传值方式加以引用。

class CTest
{
public:
CTest() : m_nData(20) { NULL; }
void TestLambda()
{
    vector<int> vctTemp;
    vctTemp.push_back(1);
    vctTemp.push_back(2);

// 无函数对象参数,输出:1 2
{
    for_each(vctTemp.begin(), vctTemp.end(), [](int v){ cout << v << endl; });
}

// 以值方式传递作用域内所有可见的局部变量(包括this),输出:11 12
{
    int a = 10;
    for_each(vctTemp.begin(), vctTemp.end(), [=](int v){ cout << v+a << endl; });
}

// 以引用方式传递作用域内所有可见的局部变量(包括this),输出:11 13 12
{
    int a = 10;
    for_each(vctTemp.begin(), vctTemp.end(), [&](int v)mutable{ cout << v+a << endl; a++; });
    cout << a << endl;
}

// 以值方式传递局部变量a,输出:11 13 10
{
    int a = 10;
    for_each(vctTemp.begin(), vctTemp.end(), [a](int v)mutable{ cout << v+a << endl; a++; });
    cout << a << endl;
}

// 以引用方式传递局部变量a,输出:11 13 12
{
    int a = 10;
    for_each(vctTemp.begin(), vctTemp.end(), [&a](int v){ cout << v+a << endl; a++; });
    cout << a << endl;
}

// 传递this,输出:21 22
{
    for_each(vctTemp.begin(), vctTemp.end(), [this](int v){ cout << v+m_nData << endl; });
}

// 除b按引用传递外,其他均按值传递,输出:11 12 17
{
    int a = 10;
    int b = 15;
    for_each(vctTemp.begin(), vctTemp.end(), [=, &b](int v){ cout << v+a << endl; b++; });
    cout << b << endl;
}
// 操作符重载函数参数按引用传递,输出:2 3
{
    for_each(vctTemp.begin(), vctTemp.end(), [](int &v){ v++; });
    for_each(vctTemp.begin(), vctTemp.end(), [](int v){ cout << v << endl; });
}
// 空的Lambda表达式
{
    [](){}();    []{}();
}
}
private:  int m_nData;
};

std::function

相比于函数指针, std::function 提供了更加简便的使用方式,同时支持使用Lambda表达式来保存运行时状态,功能更加强大。

基础用法

std::function 的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。

通常 std::function 是一个函数对象类,它包装其它任意的函数对象,被包装的函数对象具有类型为T1, …,TN的N个参数,并且返回一个可转换到R类型的值。

#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;
}

Warning

关于可调用实体转换为std::function对象需要遵守以下两条原则:

  1. 转换后的 std::function 对象的参数能转换为可调用实体的参数。

  2. 可调用实体的返回值能转换为 std::function 对象的返回值。

使用 std::bind 进行绑定

std:bind 可以返回一个 std::function 对象,主要有两个用途:

  1. 添加默认值

  2. 绑定类成员函数

绑定普通函数

int add (int x, int y) { return x + y; }
int main() {
    auto fn = std::bind(add, std::placeholders::_1, 2);
    std::cout << fn(2, 3) << std::endl;
}

绑定类成员函数

struct Foo {
    void print_sum(int x, int y) {
        std::cout << x + y << std::endl;
    }
};

int main()  {
    Foo foo;
    auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1);
    f(5);  // 100
}

移动语义和完美转发

左值VS右值

C++中所有的值都必然属于左值、右值二者之一。 左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象。 所有的具名变量或者对象都是左值,而右值不具名。 很难得到左值和右值的真正定义,但是有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值。

左值引用VS右值引用

在C++98中,右值不能被引用。

int a = 10; int& refA = a; // refA是a的别名, 修改refA就是修改a, a是左值,refA是左值引用
int& b = 1; //编译错误! 1是右值,不能够使用左值引用

C++11中引入了右值引用(rvalue reference)的概念,使用的符号是 &&

int&& a = 1;  // 实质上就是将不具名(匿名)变量取了个别名
int b = 1;
int&& c = b;  // 编译错误!不能将一个左值复制给一个右值引用
int getTemp()
{
    return int(0);
}
int&& a = getTemp();  // getTemp()的返回值是右值(临时变量)

Note

这里a的类型是右值引用类型(int&&),但是如果从左值和右值的角度区分它,它实际上是个左值。因为可以对它取地址,而且它还有名字,是一个已经命名的右值。

所以,左值引用只能绑定左值,右值引用只能绑定右值,如果绑定的不对,编译就会失败。

但是,常量左值引用却是个例外,它可以绑定非常量左值、常量左值、右值,而且在绑定右值的时候,常量左值引用还可以像右值引用一样将右值的生命期延长。

const int& a = 1;  // 常量左值引用绑定右值,不会报错

int getTemp()
{
    return int(0);
}
const int& a = getTemp();  // 不会报错

移动语义

移动语义狭义上是指移动构造函数和移动赋值函数。

基于“右值是指表达式结束时就不再存在的临时对象”这个特点,可以创建移动构造函数来充分利用该右值(临时变量)的内容,以达到优化性能的目标。

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

class MyString
{
public:
    static size_t CCtor;  // 统计调用拷贝构造函数的次数
    static size_t MCtor;  // 统计调用移动构造函数的次数
public:
    // 构造函数
    MyString(const char* cstr=0){
        if (cstr) {
            m_data = new char[strlen(cstr)+1];
            strcpy(m_data, cstr);
        }
        else {
            m_data = new char[1];
            *m_data = '\0';
        }
    }

    // 拷贝构造函数
    MyString(const MyString& str) {
        CCtor++;
        m_data = new char[ strlen(str.m_data) + 1 ];
        strcpy(m_data, str.m_data);
    }

    // 移动构造函数
    MyString(MyString&& str) noexcept
        :m_data(str.m_data) {  // 直接把参数的资源抢过来,避免复制。
        MCtor++;
        str.m_data = nullptr;  // 不再指向之前的资源
    }

    ~MyString() {
        delete[] m_data;
    }

    char* get_c_str() const { return m_data; }
private:
    char* m_data;
};

size_t MyString::CCtor = 0;
size_t MyString::MCtor = 0;

int main()
{
    vector<MyString> vecStr;
    vecStr.reserve(1000);  // 先分配好1000个空间
    for(int i=0;i<1000;i++){
        MyString tmp("hello");
        vecStr.push_back(tmp);  // 调用的是拷贝构造函数
    }
    cout << "CCtor = " << MyString::CCtor << endl;
    cout << "MCtor = " << MyString::MCtor << endl;
    cout << endl;

    MyString::CCtor = 0;
    MyString::MCtor = 0;
    vector<MyString> vecStr2;
    vecStr2.reserve(1000);  // 先分配好1000个空间
    for(int i=0;i<1000;i++){
        vecStr2.push_back(MyString("hello");  // 传入右值,调用移动构造函数
    }
    cout << "CCtor = " << MyString::CCtor << endl;
    cout << "MCtor = " << MyString::MCtor << endl;
}

/* 运行结果
CCtor = 1000
MCtor = 0

CCtor = 0
MCtor = 1000
*/

从上面的例子可以看到,C++程序在执行时可以分辨参数是左值还是右值,再决定调用哪个构造函数。 而移动构造函数接受一个右值作为参数,可以直接把参数的资源给抢过来,从而避免复制,毕竟临时对象的资源不好好利用也是浪费。

同理,也可以重载 = 操作符并接受右值引用类型,来构造移动赋值函数。

std::move

基于上面的例子, 对于一个左值,肯定是调用拷贝构造函数,但是有些左值是局部变量,生命周期也很短,能否也调用移动构造函数?

C++11为了解决这个问题,提供了 std::move() 方法来将左值转换为右值,从而方便应用移动语义。 它其实就是告诉编译器,虽然我是一个左值,但是不要对我用拷贝构造函数,而是用移动构造函数。

for(int i=0;i<1000;i++){
    MyString tmp("hello");
    vecStr2.push_back(std::move(tmp)); // 移动语义,调用的是移动构造函数
}

通用引用(universal references)

当右值引用和模板结合的时候, T&& 并不一定表示右值引用,它可能是个左值引用又可能是个右值引用。例如

template<typename T>
void f( T&& param){

}
f(10);  // 10是右值
int x = 10;
f(x);  // x是左值

Note

在一些复杂情况下,需要通过“引用折叠”规则来判断到底是左值引用还是右值引用。

  • 所有的右值引用叠加到右值引用上仍然使一个右值引用。

  • 所有的其他引用类型之间的叠加都将变成左值引用。

Warning

通用引用仅仅发生在 T&& 下,任何一点附加条件都会使之失效,比如:

template<typename T>
void f(const T&& param);  // 右值引用

template<typename T>
void f(std::vector<T>&& param);  // 右值引用

总结通用引用:传递左值进去,就是左值引用;传递右值进去,就是右值引用。如它的名字,这种类型确实很”通用”,下面要讲的完美转发,就利用了这个特性。

完美转发

所谓转发,就是通过一个函数将参数继续转交给另一个函数进行处理,原参数可能是左值引用类型,可能是右值引用,如果还能继续保持参数的原有特征,那么它就是完美的。

void process(int& i){
    cout << "process(int&):" << i << endl;
}
void process(int&& i){
    cout << "process(int&&):" << i << endl;
}

void myforward(int&& i){
    cout << "myforward(int&&):" << i << endl;
    process(i);
}

int main()
{
    int a = 0;
    process(a);  // a是左值 process(int&):0
    process(1);  // 1是右值 process(int&&):1
    process(move(a));  // 移动语义,将a由左值改为右值 process(int&&):0
    myforward(2);
    /*
    右值在函数内部转交给process,然而 ``i `` 虽然是右值引用类型,但其本身是个左值
    process(int&):2
    */
    myforward(move(a));  // 同上,在转发的时候右值变成了左值  process(int&):0
}

上面的例子就是不完美转发,没有保持调用时期望的右值特性。解决这个问题需要用到c++11提供的 std::forward() 模板函数:

void myforward(int&& i){
    cout << "myforward(int&&):" << i << endl;
    process(std::forward<int>(i));
}

myforward(2);  // process(int&&):2

经过上面的修改可以转发右值,但是由于 myforward 函数本身不接受左值,这个转发仍然不完美。 要实现真正的完美转发,还需要用到前面提到的通用引用。

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

void RunCode(int &&m) {
    cout << "rvalue ref" << endl;
}
void RunCode(int &m) {
    cout << "lvalue ref" << endl;
}
void RunCode(const int &&m) {
    cout << "const rvalue ref" << endl;
}
void RunCode(const int &m) {
    cout << "const lvalue ref" << endl;
}

template<typename T>
void perfectForward(T && t) {
    RunCode(forward<T> (t));
}

int main()
{
    int a = 0;
    int b = 0;
    const int c = 0;
    const int d = 0;

    perfectForward(a); // lvalue ref
    perfectForward(move(b)); // rvalue ref
    perfectForward(c); // const lvalue ref
    perfectForward(move(d)); // const rvalue ref
}

总结

  1. 有两种值类型,左值和右值。

  2. 有三种引用类型,左值引用、右值引用和通用引用。左值引用只能绑定左值,右值引用只能绑定右值,通用引用由初始化时绑定的值的类型确定。

  3. 左值和右值是独立于他们的类型的,右值引用类型可能是左值可能是右值。

  4. 引用折叠规则:所有的右值引用叠加到右值引用上仍然是一个右值引用;其他引用折叠都为左值引用。

  5. 移动语义可以减少无谓的内存拷贝,要想实现移动语义,需要实现移动构造函数和移动赋值函数。

  6. std::move() 将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,这个函数本身并没有对这个左值什么特殊操作。

  7. std::forward() 和通用引用共同实现完美转发。

变参模块

变参模板(variadic templates)是C++11新增的最强大的特性之一。

一个简单的可变模版参数函数:

template <class... T>
void f(T... args)
{
    cout << sizeof...(args) << endl;  // 打印变参的个数
}

f();              // 0
f(1, 2);          // 2
f(1, 2.5, "");    // 3

递归函数方式展开参数包

通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数。

以求和为例:

template<typename T>
T sum(T t)
{
    return t;
}
template<typename T, typename ... Types>
T sum (T first, Types ... rest)  // 把第一个参数提取出来
{
    return first + sum(rest...);
}
int main(){
    sum(1,2,3,4);  // 10
    return 0;
}