1,列表初始化
一般用" { } "进行初始化的就称作列表初始化,在C++98中,一般数组和结构体就可以用列表初始化。
cpp
struct test
{
int x;
int y;
};
test t1={2,3};
在C++11中试图支持"一切"列表初始化。
1.1,内置类型
cpp
int x={2};
double y={3.14};
1.2,自定义类型
自定义类型的本质就是类型转换,中间一般有临时对象进行拷贝构造,但最后编译器会优化成直接构造。
cpp
class Date
{
public:
Date(int x,int y,int z)
:_x(x),_y(y),_z(z)
{}
private:
int _x;
int _y;
int _z;
};
Date d1={2,3,4}; //此时会直接调用构造
const Date& d2={3,4,5}; //此时假设有拷贝构造,则d2引用的是{3,4,5}的临时对象。
1.3,省略"="
在C++11中,初始化时可以省略" = "
cpp
int x{1};
double y{3.23};
Date d{2,3,4};
test t1{3,4};
且需要注意的是,以下形式的初始化:
cpp
vector<int> v1={1,2,3,4}; //不是列表初始化,属于构造加拷贝构造
//C++11会将该{}传给initializer list的类去构造,再拷贝构造;
vector<int> v2({2,3,4}); //只有构造
STL容器支持任意多个值构成的" { } "进行初始化,就是通过std::initializer_list的构造函数支持的。这个类的底层就是开辟一个数组将数据拷贝。
2,可变参数模板
2.1基本语法及原理
**C++支持可变参数模板,也就是支持可变数量参数的函数模板和类模板,**可变数量的参数被称作参数包,参数包有模板参数包,函数参数包两种。
模板参数包:表示0或多个模板参数。
函数参数包:表示0或多个函数参数。
参数包的表现形式为:
cpp
template<class...Args>
void func(Args...args)
{}
template<class...Args>
void func(Args&...args)
{}
template<class...Args>
void func(Args&&...args)
{}
其中 class... 表示参数包,args表示参数包参数,且同样支持左值右值引用,也遵循同样的规则。
可变参数模板的原理本质就是:一个模板实例化出多个不同参数个数的函数。
cpp
template<class...Args>
void func(Args...args)
{
cout<<sizeof...(args)<<endl;
}
int main()
{
double i=3.2;
func(); //包含0个参数
func(1); //包含1个参数
func(1,string("1231")); //包含2个参数
func(1,1.1,string("fffff"),i); //包含4个参数
return 0;
}
使用如上述,且可以使用 sizeof...() 去计算参数包的个数,且由上述可知,可变参数不仅支持数量改变,类型也可以改变。
实例化如下:
cpp
//实例化出的对象也会根据引用折叠规则,分别示例化出以下
void func();
void func(int&& args1);
void func(int&& args1,string&& args2);
void func(int&& args1,double&& args2,string&& args3,double& args4);
//更加底层的模板类似如下:其实并没有可变模板
//底层都是实例化出一个具体的模板,而此举也是为了让泛型编程更加灵活。
void func();
template<class T1>
void func(T1&& args1);
template<class T1,class T2>
void func(T1&& args1,T2&& args2);
template<class T1,class T2,class T3,class T4>
void func(T1&& args1,T2&& args2,T3&& args3,T4& args4);
2.2包扩展
包扩展:就是解析参数包的内容。
对于一个参数包,不仅可以计算他的参数个数,还可以解析他的内容,也就是将它分解为构成的元素,再进行获取应用。
扩展的过程:类似与递归的思想,在编译时"递归"包扩展,通过递归扩展成不同参数的函数。
模式一:
通过递归思想,每次获取一个参数,再递归下去。其底层实现也是实现了不同参数数量的具体函数。
cpp
template<class...Args>
void Print(Args&&...args) //主调用
{
show(args);
}
void show()
{
cout<<endl; //参数数量为0时匹配
}
template<class T,class...Args>
void show(T&& x,Args&&...args) //第一个参数会给x,剩下的给args递归
{
cout<<x<<" ";
show(args...);
}
int main()
{
Print(1,string("azx"),a,12.2);
return 0;
}
模式二:
该方法比较抽象,其思想却是一致的,但这一步更像是一个把参数包循环塞到GetArg中。
cpp
template<class...Args>
void Print(Args&&...args)
{
Argu(GetArg(args)...); //主要步骤
}
template<class...Args>
void Argu(Args...args)
{}
template<class T>
const T& GetArg(const T& x)
{
cout<<x;
return x;
}
int main()
{
Print(1,sting("aaa"),1.2);
return 0;
}
//改模式比较抽象,其主要步骤相当于:
void Print(int&& x,string&& y,double&& z)
{
Argu(GetArg(x),GetArg(y),GetArg(z));
}
2.3,emplace系列接口
STL容器基本新增了emplace系列接口,其作用也是插入数据,功能上类似push和insert系列。不同的是emplace系列接口均为模板可变参数,且部分场景下会更加高效。
emplace_back和push_back的区别:
1--若传左值都走拷贝构造。
2--若传右值都走移动构造
3--若直接传参(const char*等),**push会隐式类型转换+构造临时对象+(右值)移动构造,emplace是直接构造(深拷贝)且直接在容器内存中构造。**在此效率会高。
3,lambda
3.1lambda语法
lambda表达式本质是一个匿名函数对象,与普通函数不同的是他可以定义在函数内部。且其表达式在语法使用层没有类型而言,一般用auto或者模板参数定义的对象来接收lambda对象。
格式:
cpp
[capture-list] (parameters)-> return type {function body}
//[捕捉列表](参数列表)->返回值类型 {函数体}
捕捉列表:用于编译器识别lambda表达式,能够捕捉上下文中变量供lambda函数使用,捕捉列表可以为空但不能省略。
参数列表:与普通函数参数列表功能类似,若不需要参数则该整体都可以省略。
返回值类型:若该函数的返回值明确或没有,则可以省略,编译器会自推导该返回值类型,这里标注是用追踪返回类型形式声明函数的返回值类型。
函数体:和普通函数类似,在函数体内除了可以使用其参数外还可以使用捕捉列表的变量。
简单例子:
cpp
auto add1=[](int x1,int y1)->int{return x1+y1;};
auto add2=[](int x2,int y2){return x2+y2;};
cout<<add1(1,2)<<endl;
auto func=[]
{
return 0;
}; //lambda表达式则也就和一个式子相同需要" ; "结尾
3.2捕捉列表-捕捉方法
上述的示例中,对于捕捉列表的演示都是"空",对于捕捉列表的定义是"能够捕捉上下文",而一些情况是可以不捕捉的,下面来细说一下捕捉列表的规则。
lambda表达式默认只能用lambda表达式和参数列表中的变量(就类似正常函数),但捕捉列表增加了使用外部变量的可能,若想用外部的只要进行捕捉就可以使用。(全局变量可以直接使用)
捕捉方式:传值/传引用捕捉,隐式捕捉,混合捕捉。
传值和传引用和普通函数使用一致,传值不会影响外部的变量。
cpp
void test()
{
int x=1;
string y("111");
vector<int> z(10,0);
auto fun1=[x,&y,&z](int a,int b) //传值传引用捕捉,x为传值捕捉,y,z为传引用捕捉
{ //使用和正常参数无差,不过是作用域外的变量
y+="222";
z[2]=2;
return a+b+x;
}
// 隐式捕捉 编译器自动捕捉
auto fun2=[=](int a,int b) //传"="是隐式传值捕捉
{
return x+a+b;
}
auto fun2=[&](int a,int b) //传"&"是隐式传值捕捉
{
z[2]=2;
return x+a+b;
}
//混合捕捉
auto fun2=[&,x](int a,int b) //此处说明x是传值捕捉,不可使用引用
{
z[2]=2;
return x+a+b;
}
auto fun2=[=,&z](int a,int b) //传"="是隐式传值捕捉
{
z[2]=2;
return x+a+b;
}
}
3.3lambda原理
lambda的底层是由编译器自动生成的一个对应的仿函数对象,其编译后在汇编语言看不到这种结构,只能看到一个对应的仿函数的类。
其仿函数的类名是编译器按一定规则生成的(为了确保不重复),lambda的参数,返回类型,函数体就是对应仿函数operator()的参数,返回类型,函数体。
而捕捉列表的本质是:生成的仿函数的类的成员变量,也就相当于其类构造函数的实参。
4,包装器 --function,bind
包装器(Wrapper)是一种设计模式或编程技术,用于在不修改原有代码的基础上,对已有功能进行封装、增强或适配。其核心目的是将一个接口或组件"包裹"起来,使其能够以更统一、安全或兼容的方式被调用。C++官方有function和bind两种包装器。
4.1,function -<functional>
std::function是一个类模板也是一个包装器。也就是利用类模板来实现"包装"的效果。他的实例对象可以包装存储其他可以调用的对象(包括函数指针,仿函数,lambda表达式等)。这种存储的可调用对象称之为function的目标。若function不含目标,则称之为空,调用空function的目标会导致std::bad_function_call抛异常。

使用样例如下:
cpp
#include<functional>
struct functest1
{
int operator()(int a,int b)
{
return a+b;
}
};
int functest2(int a,int b)
{
return a+b;
}
class func3
{
public:
func3(int n=10)
:_n(n)
{}
static int funci(int a,int b)
{
return a+b;
}
double funcd(double a,double b)
{
return (a+b)*_n;
}
private:
int _n;
};
int main()
{
//包装各种可调用对象
std::function<int(int,int)> f1=functest1(); //结构体
std::function<int(int,int)> f2=functest2; //函数指针
std::function<int(int,int)> f3=[](int a,int b) {return a+b;}; //lambda表达式
// <返回类型(参数列表)>
int a=f1(3,4);
//包装静态成员函数
//包装成员函数需要指定类域
std::function<int(int,int)> f4=&func3::funci;
int c=f4(3,4);
//包装普通成员函数
//普通成员函数的参数中有一个隐含的this指针参数,所以包装时需要包含
func3 fun;
std::function<double(func3,double,double)> f5=&func3::funcd;
double d1=f5(fun,2.2,2.1);
std::function<double(func3*,double,double)> f6=&func3::funcd;
double d2=f6(&fun,2.2,2.1);
std::function<double(func3&&,double,double)> f7=&func3::funcd;
double d3=f7(move(fun),2.2,2.1);
double d4=f7(func3(),2.2,2.1);
return 0;
}
4.2,bind --<functional>
std::bind是一个函数模板,也是一个可调用对象的包装器。可看作是一个函数适配器,对接收的对象处理后返回一个可调用对象,可用来调整参数个数和参数顺序。

一般形式如下:
cpp
auto newCallable =bind(callable,arg_list);
// 可调用对象(仿函数) 可调用对象 参数列表
当调用newCallable时会调用callable且将参数列表传过去。
参数列表可能包含"_n"的参数,这是占位符,表示newCallable的参数。"_1"表示newCallable的第一个参数,以此类推。
_1,_2......这些占位符都在placeholders的一个命名空间中。
一般使用中,newCallable有多少个参数就需要多少个占位符。
cpp
#include<functional>
//占位符使用
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int functest1(int a,int b)
{
return a+b;
}
int functest2(int a,int b,int c)
{
return a+b+c;
}
class func3
{
public:
func3(int n=10)
:_n(n)
{}
static int funci(int a,int b)
{
return a+b;
}
double funcd(double a,double b)
{
return (a+b)*_n;
}
private:
int _n;
};
int main()
{
auto fun1=bind(functest1,_1,_2);
int t1=fun1(1,2);
auto fun2=bind(functest2,_1,_2,_3);
int t2=fun2(1,2,3);
auto fun3=bind(functest1,10,_1);
int t3=fun3(1); //此处第一个参数被固定,只有第二个参数需要传
auto fun4=bind(functest2,_1,_2,10);
int t4=fun4(1,2);
auto fun5=bind(functest2,10,_1,_2);
int t5=fun5(1,2);
auto fun6=bind(functest2,10,_2,10);
int t6=fun6(1,2);
//绑定成员函数
function<double(func3,double,double)> f1=&func3::funcd;
func3 fun;
f1(fun,1.2,1.2); //这样每次都需要传类指针,则可以进行绑定
function<double(double,double)> f2=bind(&func3::funcd,fun,_1,_2);
f2(1.2,1.2);
return 0;
}
由上可以看到,function和bind可以配合使用,便利程序的书写和操作,让代码简便且灵活。
