C++11

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的构造函数支持的。这个类的底层就是开辟一个数组将数据拷贝。

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可以配合使用,便利程序的书写和操作,让代码简便且灵活。

相关推荐
6Hzlia1 小时前
【Classic 150 刷题计划】 LeetCode 26. 删除有序数组中的重复项 | C++ 快慢双指针经典模板
c++·算法·leetcode
夜不会漫长2 小时前
C++:类和对象(2)
java·javascript·c++
ch.ju2 小时前
Java—idea知识:如何创建新项目
java·开发语言·intellij-idea
无小道2 小时前
C++ 中 `explicit` 关键字详解:彻底理解隐式类型转换
c++·explicit
Evand J2 小时前
【MATLAB例程,图像滤波降噪12】加权中值滑动窗口滤波(WMF)图像降噪,包含滤波质量评价。附代码下载链接
开发语言·图像处理·人工智能·计算机视觉·matlab·滑动窗口·滤波
蒸蒸yyyyzwd2 小时前
cpp 选手秋招学习笔记 day34
c++·八股
Tairitsu_H2 小时前
[C++] C++11类和STL的新特性
开发语言·c++·stl·c++11
承渊政道2 小时前
Python IDLE鸿蒙PC适配全记录:从Tkinter桌面程序到ArkUI原生开发闭环
开发语言·python·harmonyos·鸿蒙系统·桌面程序
Lucis__3 小时前
基于责任链模式的消息队列—异步处理流水线的最佳实践
linux·c++·消息队列·责任链模式·ipc