C++——C++11(3)

C++------C++11(3)

我们今天接着来了解一下C++11一些新的特性,如果还没有看过上两次的小伙伴可以点击这里:

https://blog.csdn.net/qq_67693066/article/details/136577386
https://blog.csdn.net/qq_67693066/article/details/136658325

lambda表达式(匿名的仿函数对象)

后期,C++受到了其他语言的影响,风格开始发生了变化,这个lambda表达式就是受了其他语言的影响:

在C++中,lambda表达式(也称为匿名函数)提供了一种方便的方式来定义和使用内联的小函数对象。Lambda表达式特别适用于需要临时函数对象的情况,例如在算法调用(如std::sort、std::find_if等)中作为参数传递。

C++中的lambda表达式的基本语法如下:

cpp 复制代码
[capture](parameters) -> return_type { body_of_lambda }

capture:捕获子句 ,用于捕获外部作用域的变量,以在lambda函数体内使用。可以是值捕获(通过=)或引用捕获(通过&)。也可以显式列出要捕获的变量。
parameters :lambda函数的参数列表,与常规函数参数列表类似。
return_type :返回类型,通常可以省略,编译器会自动推导返回类型。
body_of_lambda :lambda函数的主体,包含要执行的代码。
注意
在lambda函数定义中,参数列表和返回值类型都是可选部分 ,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

比如我可以用lambda表达式写一个简单的判断偶数的式子:

cpp 复制代码
int main()
{
    vector<int> v1 = {1,2,3,4,5,6,7,8,9,10};

    auto s1 = [v1](int n){ //捕捉v1,参数为n
        if( n % 2 == 0)
        {
            cout<<n<<"是偶数"<<endl; //body_of_lambda
        }
    };

    for(auto e : v1)
    {
        s1(e); //调用s1
    }
}

我们可以看看s1的类型:

cpp 复制代码
cout << typeid(s1).name() << endl;

这里class <lambda_741bc9d219826239a4505f712752848a>后面的这串字符串是uuid(通用唯一识别码)然后编译器会生成这么一个类型的仿函数:

我们打个断点,进入到反汇编:

我们可以通过lambda表达式实现自定义排序:

之前在C++98中如果我们要自定义排序,通常要写一个仿函数:

cpp 复制代码
struct Goods
{
    string _name;  // 名字
    double _price; // 价格
    int _evaluate; // 评价
    Goods(const char* str, double price, int evaluate)
        :_name(str)
        , _price(price)
        , _evaluate(evaluate)
    {}
};

struct ComparePriceLess
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price < gr._price;

    }
};


struct ComparePriceGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price > gr._price;
    }
};


int main() {
    vector <Goods> v = {{"苹果", 2.1, 5},
                        {"香蕉", 3,4},
                        {"橙子", 2.2,3},
                        {"菠萝", 1.5, 4}};
    sort(v.begin(), v.end(), ComparePriceGreater()); //使用ComparePriceGreater()匿名对象


    for(auto e: v)
    {
        cout<<e._name << ": " <<e._price <<endl;
    }

    sort(v.begin(), v.end(), ComparePriceLess());
    cout << endl;

    for (auto e : v)
    {
        cout << e._name << ": " << e._price << endl;
    }
}

现在我们有lambda匿名仿函数对象,我们就可以不用写这么多的仿函数了:

cpp 复制代码
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)
        {return g1._price > g2._price; });


    for (auto e : v)
    {
        cout << e._name << ": " << e._price << endl;
     }

    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)
        {return g1._price < g2._price; }); 
    

    cout << endl;


    for (auto e : v)
    {
        cout << e._name << ": " << e._price << endl;
    }
}

一些注意点

既然我们可以通过lambda实现一些仿函数的功能,那么我们可以用这个实现一个简单的交换两个变量值的功能:

cpp 复制代码
int main()
{
    int x = 10;
    int y = 19;

    cout << x << " " << y << endl;



    //使用lambda表达式
    auto s1 = [x, y]() {
        int temp;
        temp = x;
        x = y;
        y = temp;
    };

但是这样会报错:

那是因为:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性 。使用该修饰符时,参数列表不可省略(即使参数为空)。

这下就可以了:

cpp 复制代码
int main()
{
    int x = 10;
    int y = 19;

    cout << x << " " << y << endl;



    //使用lambda表达式
    auto s1 = [x, y]() mutable{
        int temp;
        temp = x;
        x = y;
        y = temp;
    };

    cout << endl;

    s1();

    cout << x << " " << y << endl;
}

这里我们的s1没有传参,是因为捕获列表[],捕捉到了上面的x,y{}函数体直接使用的是x和y:

我们来运行一下:
发现这里并没有实现交换,估计多半是传的临时对象,我们可以传引用:

不仅这样,lambda的捕捉列表给了几种方式:

lambda捕捉列表

[var] :表示值传递方式捕捉变量var
[=] :表示值传递方式捕获所有父作用域中的变量(包括this)
[&var] :表示引用传递捕捉变量var
[&] :表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针

我们来看看[=]和[&],[this]的玩法:

[=]

[=]:表示值传递方式捕获所有父作用域中的变量(包括this)

cpp 复制代码
int main()
{
    int x = 10;
    int y = 90;

    //lambda表达式 x属于外部变量
    auto lambda = [=]()mutable {
        x += 90;
        y += 90;
        cout << x << " " << y << endl;
    };

    lambda();
    cout << endl;

    //查看数值是否被修改
    cout << x << " " << y << endl;
}

发现原本的数值没有改变。

[&]

[=]:表示值传递方式捕获所有父作用域中的变量(包括this)

cpp 复制代码
int main()
{
    int x = 10;
    int y = 90;

    //lambda表达式 x属于外部变量
    auto lambda = [&]() {
        x += 90;
        y += 90;
        cout << x << " " << y << endl;
    };

    lambda();
    cout << endl;

    //查看数值是否被修改
    cout << x << " " << y << endl;
}

[this]

[this]:表示值传递方式捕捉当前的this指针

cpp 复制代码
class MyClass {
public:
    int value;

    MyClass(int v) : value(v) {}

    void printValuePlusTen() {
        // 使用 [this] 捕获当前对象的 this 指针  
        auto lambda = [this]() {
            std::cout << this->value + 10 << std::endl;
        };

        lambda(); // 输出 value 的值加 10  
    }
};

int main() 
{
    MyClass obj(5);
    obj.printValuePlusTen(); // 输出: 15  
    cout << obj.value << endl; //输出5
    return 0;
}

需要注意的是,由于this指针是通过值传递的,因此lambda内部对this指针的拷贝进行操作时,不会影响到原始的this指针或对象本身。

除了这样,还可以混合使用:

语法上捕捉列表可由多个捕捉项组成,并以逗号分割。

比如:[=, &a, &b] :以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

lambda的赋值

注意一下,就算两个lambda函数的捕获列表,参数,函数体都是一样的,这两个lambda本质上类型不一样,无法互相赋值:

cpp 复制代码
int main()
{
    int x = 10;
    int y = 19;

    cout << x << " " << y << endl;

    //使用lambda表达式
    auto s1 = [&x, &y](){
        int temp;
        temp = x;
        x = y;
        y = temp;
    };

    auto s2 = [&x, &y]() {
        int temp;
        temp = x;
        x = y;
        y = temp;
    };

    s1 = s2;

}

会报错:

function包装器

现在我们学习了lambda,现在我们调用一个函数有多种方法了:
函数指针,仿函数,lambda,这有时候方法多了,用法就会混,有没有什么方法可以将他们统一起来呢?

C++11中引入了function

function 是 C++ 标准库 头文件中定义的一个模板类,它包装了任何可调用的目标(函数、lambda 表达式、函数对象等),允许你以一种统一的方式来存储和调用它们。std::function 的一个关键特性是其类型擦除(type erasure)能力,这意味着你可以使用同一个 std::function 对象来存储不同类型的可调用对象,只要它们具有兼容的调用签名。

举个例子:

cpp 复制代码
//函数指针
void Swap_fuc(int& r1, int& r2)
{
    int temp = r1;
    r1 = r2;
    r2 = temp;
}

//仿函数
struct Swap
{
    void operator()(int& r1,int& r2)
    {
        int temp = r1;
        r1 = r2;
        r2 = temp;
    }
};

int main()
{
    //lambda表达式
    auto s1 = [](int& r1, int& r2) {
        int temp = r1;
        r1 = r2;
        r2 = temp;
    };
}

function可以将这三个很好的结合起来:

cpp 复制代码
#include<functional>
//函数指针
void Swap_fuc(int& r1, int& r2)
{
    int temp = r1;
    r1 = r2;
    r2 = temp;
}

//仿函数
struct Swap
{
    void operator()(int& r1,int& r2)
    {
        int temp = r1;
        r1 = r2;
        r2 = temp;
    }
};

int main()
{
    //lambda表达式
    auto s1 = [](int& r1, int& r2) {
        int temp = r1;
        r1 = r2;
        r2 = temp;
    };

    function<void(int&, int&)> funtion1;


    //接收函数指针
    int r1 = 10;
    int r2 = 90;

    cout << "Swap_fuc交换之前的值:" << endl;
    cout << r1 << " " << r2 << endl;
    funtion1 = Swap_fuc;
    funtion1(r1,r2);

    cout << "交换之后的值:" << endl;
    cout << r1 << " " << r2 << endl;
    cout << endl;

    //仿函数
    cout << "Swap()交换之前的值:" << endl;
    cout << r1 << " " << r2 << endl;
    funtion1 = Swap();
    funtion1(r1, r2);
    cout << "交换之后的值:" << endl;
    cout << r1 << " " << r2 << endl;
    cout << endl;

    //lambda
    cout << "lambda交换之前的值:" << endl;
    cout << r1 << " " << r2 << endl;
    funtion1 = s1;
    funtion1(r1, r2);
    cout << "交换之后的值:" << endl;
    cout << r1 << " " << r2 << endl;
    cout << endl;

}

function成员函数的包装

这里注意一下function对成员函数的包装要加上**&**:

cpp 复制代码
class MyClass
{
public:
    void func_1(int& r1)
    {
        cout << "func_1:" << r1 << endl;
    }

    void func_2(int& r2)
    {
        cout << "func_2:" << r2 << endl;
    }
};

int main()
{
    function<void(int&)> s = &MyClass::func_1;
}

但是,会报错:
这个错误信息好像说,类型好像不一样,好像有一个this指针,我们得加上:
这个时候,调用这个函数就会稍微复杂一点:

如果嫌弃这样还是过于麻烦,我们可以传对象:
这样还是太过于复杂,有没有什么办法呢,有的,我们有bind

bind绑定参数

std::bind是C++标准库中的一个功能,它用于将可调用对象(如函数、函数对象或lambda表达式)与参数绑定在一起,生成一个新的可调用对象。对于成员函数,std::bind特别有用,因为它允许你指定一个对象实例来调用该成员函数。

举一个简单的例子:

cpp 复制代码
void Swap(int& r1, int& r2)
{
    int temp = r1;
    r1 = r2;
    r2 = temp;
}

int main()
{
    //function<void(MyClass,int&)> s = &MyClass::func_1;
    //int u = 90;
    //s(MyClass(), u); //传一个匿名对象

    auto NewSwap = bind(&Swap, 20, placeholders::_1);
    int x = 89;
    NewSwap(x);

    cout << x << endl;
}

这里我指定函数是Swap绑定了第一个参数为20,之后placeholders::_1(_2, _3)表示要传的第一个参数 ,之后会用新绑定的NewSwap这个对象来调用:

我们可以利用这一点来简化代码:

cpp 复制代码
class MyClass
{
public:
    void func_1(int& r1)
    {
        cout << "func_1:" << r1 << endl;
    }

    void func_2(int& r2)
    {
        cout << "func_2:" << r2 << endl;
    }
};
int main()
{
    //function<void(MyClass,int&)> s = &MyClass::func_1;
    //int u = 90;
    //s(MyClass(), u); //传一个匿名对象

    //auto NewSwap = bind(&Swap, 20, placeholders::_1);
    //int x = 89;
    //NewSwap(x);

    //cout << x << endl;

    function<void(int&)> s = bind(&MyClass::func_1,
        MyClass(), placeholders::_1);
    int x = 89;
    s(x); 
}
相关推荐
獨枭1 小时前
CMake 构建项目并整理头文件和库文件
c++·github·cmake
西猫雷婶2 小时前
python学opencv|读取图像(十九)使用cv2.rectangle()绘制矩形
开发语言·python·opencv
liuxin334455662 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
码农W2 小时前
QT--静态插件、动态插件
开发语言·qt
ke_wu2 小时前
结构型设计模式
开发语言·设计模式·组合模式·简单工厂模式·工厂方法模式·抽象工厂模式·装饰器模式
code04号3 小时前
python脚本:批量提取excel数据
开发语言·python·excel
小王爱吃月亮糖3 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript
hakesashou3 小时前
python如何打乱list
开发语言·python
网络风云3 小时前
【魅力golang】之-反射
开发语言·后端·golang
Want5954 小时前
Java圣诞树
开发语言·python·信息可视化