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()
魔法方法¶
构造、删除¶
方法名 |
功能 |
|---|---|
|
申请内存空间,生成类实例。 |
|
在对象被生成之后调用,分配类属性。 |
|
对象被销毁时调用。 |
访问类属性¶
方法名 |
功能 |
|---|---|
|
当访问该类不存在的属性时,会调用此方法。 |
|
在设置类的属性时,会调用此方法。 |
|
优先级大于 |
序列化相关¶
方法名 |
功能 |
|---|---|
|
当使用 |
|
当使用 |
|
保存该函数的返回值,传递给 |
|
同上,但是优先级更高。 |
类容器¶
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++¶
C++11的新特性列表¶
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> 中。
基本用法¶
初始化:该结构可以代表多种类型,初始化列表中有且仅能有其声明的某一项
std::variant:必须声明模板类型。
提取内容
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);
}
类¶
面向对象的基础。
成员变量和成员方法的关键字¶
关键字 |
功能 |
|---|---|
|
常量 |
|
方法返回一个常量 |
|
该方法不修改成员变量的值 |
|
属于类的变量,位于静态存储区,不实例化也可以访问 |
|
属于类的方法,没有实例化也可以调用,没有指向实例的 |
|
虚函数,当虚函数后面紧接着 =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。
结合
using和typedef可以用于类型定义:
vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++)
{
//...
}
重用匿名类型
struct
{
int d ;
doubel b;
}a;
decltype(a) b; // 定义了一个上面匿名的结构体
auto¶
Note
auto 关键字不允许没有初始值的声明,比如:
int x;
auto y = x; // ok
auto z; // error
用来自动推断类型,从而节省很多代码。
std::vector<int> v;
std::vector<int>::iterator i1 = v.begin();
auto i2 = v.begin();
配合 lambda 表达式类使用,每个 lambda 表达式的类型都是独一无二的,这个类型只有编译器知道,因此创建时需要使用
auto关键字。
auto closure = [](const int&, const int&) {}
虽然也可以使用 function 来实现,但这远没有 auto 来的简单直观。
模板函数返回值
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对象需要遵守以下两条原则:
转换后的
std::function对象的参数能转换为可调用实体的参数。可调用实体的返回值能转换为
std::function对象的返回值。
使用 std::bind 进行绑定¶
std:bind 可以返回一个 std::function 对象,主要有两个用途:
添加默认值
绑定类成员函数
绑定普通函数¶
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
}
总结¶
有两种值类型,左值和右值。
有三种引用类型,左值引用、右值引用和通用引用。左值引用只能绑定左值,右值引用只能绑定右值,通用引用由初始化时绑定的值的类型确定。
左值和右值是独立于他们的类型的,右值引用类型可能是左值可能是右值。
引用折叠规则:所有的右值引用叠加到右值引用上仍然是一个右值引用;其他引用折叠都为左值引用。
移动语义可以减少无谓的内存拷贝,要想实现移动语义,需要实现移动构造函数和移动赋值函数。
std::move()将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,这个函数本身并没有对这个左值什么特殊操作。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;
}
Note
参考自 泛化之美–C++11可变模版参数的妙用。