【C++11深度解析(2)】从新增类功能到智能指针的现代 C++ 核心新特性

目录

引言

[一. 新的类功能](#一. 新的类功能)

[1.1 默认的移动构造和移动赋值](#1.1 默认的移动构造和移动赋值)

[1.2 成员变量声明时给缺省值](#1.2 成员变量声明时给缺省值)

[1.3 default与delete](#1.3 default与delete)

[1.4 final与override](#1.4 final与override)

[1.5 委托构造函数](#1.5 委托构造函数)

[1.6 继承构造函数](#1.6 继承构造函数)

[二. STL中的一些变化](#二. STL中的一些变化)

[三. lambda](#三. lambda)

[3.1 lambda表达式语法](#3.1 lambda表达式语法)

[3.2 捕捉列表](#3.2 捕捉列表)

[3.3 lambda的应用](#3.3 lambda的应用)

[3.4 lambda的原理](#3.4 lambda的原理)

[四. 包装器](#四. 包装器)

[4.1 function](#4.1 function)

[4.2 bind](#4.2 bind)

总结


引言

C++11 是现代 C++ 的奠基版本,它不仅补齐了长期存在的语言缺陷,还为泛型编程、对象语义管理、高性能编程等领域提供了全新的抽象方式。无论你是进行底层系统开发、构建高性能服务器,还是实现通用库组件,C++11 都是绕不开的一道坎。


一. 新的类功能

1.1 默认的移动构造和移动赋值

  • 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重 载/const 取地址重载,最后重要的是前4个,后两个用处不大,默认成员函数就是我们不写编译器 会生成一个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载
  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一 个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意 一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值重载函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

cpp 复制代码
class Person
{
public:
    Person(const char* name = "张三", int age = 19)
        :_name(name)
        , _age(age)
    {
    }

    /*Person(const Person& p)
    :_name(p._name)
    ,_age(p._age)
    {}*/

    /*Person& operator=(const Person& p)
    {
        if(this != &p)
        {
            _name = p._name;
            _age = p._age;
        }
            return *this;
    }*/

    /*~Person()
    {}*/

private:
    bit::string _name;
    int _age;
};
int main()
{
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    Person s4("xxx", 1);
    s4 = std::move(s2);

    return 0;
}

1.2 成员变量声明时给缺省值

在最开始类和对象中已经奖结果讲解过,如果遗忘,点击复习【C++】类和对象


1.3 default与delete

  • C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因 这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default关键字显示指定移动构造生成。
  • 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已, 这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

cpp 复制代码
class Person
{
public:
    Person(const char* name = "张三", int age = 19)
        :_name(name)
        , _age(age)
    {
    }

    // default:解决 "自定义某个函数后,默认函数消失" 的问题
    // 注意:在VS中这三个如果要生成就要一起生成
    //Person(const Person& p) = default;
    //Person(Person&& p) = default;
    //Person& operator=(const Person& p) = default;

    // 一些类不期望被拷贝
    // C++11
    //Person(const Person& p) = delete;

    ~Person()
    {}
private:
    // C++98:只声明(声明为私有),不实现
    //Person(const Person& p);

    bit::string _name;
    int _age;
};
int main()
{
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    Person s4("xxx", 1);
    s4 = std::move(s2);

    return 0;
}

1.4 final与override

在之前已经讲解过,如果遗忘,点击复习【C++】多态


1.5 委托构造函数

  • C++中的委托构造函数(Delegating Constructor)是C++11引入的特性,允许一个构造函数调用同类中的其他构造函数,从而减少代码重复并提高可维护性。
  • 被委托的构造函数必须初始化所有成员变量,因为委托构造函数后不能再重复初始化。

cpp 复制代码
class Example {
public:
    Example(int a, int b)
        :_x(a)
        , _y(b)
    {
        cout << "目标构造函数\n";
    }

    Example(int a)
        : Example(a, 0)
    {
        cout << "委托构造函数\n";
    }

    int _x;
    int _y;
};

class Time {
public:
    // 所有参数都会走初始化列表,虽然这里没有写second,但是second也会走这里的初始化列表初始化
    // 初始化所有成员
    Time(int h, int m)
        :_hour(h)
        , _minute(m)
    {}

    // error C3511: "Time": 对委托构造函数的调用应仅为成员初始值设定项
    // error C2437 : "_second": 已初始化
    Time(int h, int m, int s)
        :Time(h, m)
       // , _second(s)
    {}

private:
    int _hour;
    int _minute;
    int _second = 0;
};

int main()
{
    Example(1, 2);
    Example(1);

    return 0;
}

1.6 继承构造函数

  • 继承构造函数是C++11引入的一项特性,它允许派生类直接继承基类的构造函数,而不需要手动重新定义它们。这一特性显著简化了派生类的编写,特别是在基类有多个构造函数的情况下。
  • 派生类继承基类的普通构造函数,特殊的拷贝构造函数/移动构造函数不继承。
  • 继承构造函数中派生类自己的成员变量如果有缺省值会使用缺省值初始化,如果没有缺省值那么跟之前类似,内置类型成员不确定,自定义类型成员使用默认构造初始化。

cpp 复制代码
class Base {
public:
    Base(int x, double d)
        :_x(x)
        , _d(d)
    {}

    Base(int x)
        :_x(x)
    {}

    Base(double d)
        :_x(d)
    {}

protected:
    int _x = 0;
    double _d = 0;
};

// 传统的派生类实现构造
//class Derived : public Base {
//public:
//    Derived(int x) : Base(x) {}
//    Derived(double d) : Base(d) {}
//    Derived(int x, double d) : Base(x, d) {}
//};

// C++11继承基类的所有构造函数
class Derived : public Base {
public:
    // 继承构造
    // 继承了父类的所有构造,可以理解为编译器帮我们生成了上面的三个构造
    using Base::Base;

    // 这样的话,自己的成员就无法初始化了,因为无法传参,所以只能用缺省值
    // 所以如果有自己的成员,最好自己写构造
    /*protected:
        int _i = 0;
        string _s;*/

    // 派生类有自己的成员时,不适合用继承构造
    // 综上:继承构造适用于派生类没有成员,或者是有成员,但是不用显示给值,只用缺省值即可
};

int main()
{
    Derived d1(1);
    Derived d2(1.1);
    Derived d3(2, 2.2);

    return 0;
}

二. STL中的一些变化

  • C++11新增容器中,最有用的是unordered_map和unordered_set。这 两个前面已经进行了非常详细的讲解。
  • STL中容器的新接口也不少,最重要的就是右值引用和移动语义相关的push/insert/emplace系列 接口和移动构造和移动赋值,还有initializer_list版本的构造等,这些前面都讲过了,还有一些无关 痛痒的如cbegin/cend等需要时查查文档即可。
  • 范围for,之前已经讲解过,这里不再讲解

如有遗忘,可点击复习【C++】unorderedset/map


三. lambda

3.1 lambda表达式语法

  • lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。 lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收 lambda 对象。
  • lambda表达式的格式: [capture-list] (parameters)-> return type { function boby }
  • capture-list\] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据\[\]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,捕捉列表为空也不能省略。

  • ->return type :返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此 部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以 使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。

cpp 复制代码
// 尾置返回类型lambda和普通函数都有
// 尾置返回类型,auto用于占位;这样如果返回类比较长,更容易看出看出函数名
std::map<std::string, std::pair<std::string, std::string>>::iterator func();
auto func()->std::map<std::string, std::pair<std::string, std::string>>::iterator;

int main()
{
    // 一个简单的lambda表达式
    // 匿名函数对象
    // 可以像函数一样使用,底层是一个仿函数
    //auto add1 = [](int x, int y)->int {return x + y; };
    auto add1 = [](int x, int y) {return x + y; }; // 返回类型可以省略,编译器会自动推导;但是写出来更清晰
    cout << add1(1, 2) << endl; 
    //cout << [](int x, int y)->int {return x + y; }(1, 2) << endl; // 不会这样用

    // 1、捕捉为空也不能省略
    // 2、参数为空可以省略
    // 3、返回类型可以省略,可以通过返回对象自动推导
    // 4、函数体不能省略
    auto func1 = []
        {
            cout << "hello bit" << endl;
            return 0;
        };
    func1();

    int a = 0, b = 1;
    auto swap1 = [](int& x, int& y)
        {
            int tmp = x;
            x = y;
            y = tmp;
        };
    swap1(a, b);
    cout << a << ":" << b << endl;

    return 0;
}

3.2 捕捉列表

  • lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉
  • 第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x, y, &z] 表示x和y值捕捉,z引用捕捉。
  • 第二种捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表写一个=表示隐式值捕捉,在捕捉列表 写一个&表示隐式引用捕捉,这样我们 lambda 表达式中用了那些变量,编译器就会自动捕捉那些 变量。
  • 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。[=, &x]表示其他变量隐式值捕捉, x引用捕捉;[&, x, y]表示其他变量引用捕捉,x和y值捕捉。当使用混合捕捉时,第一个元素必须是 &或=,并且&混合捕捉时,后面的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必 须是引用捕捉。
  • lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不**能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使用。**这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
  • 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改, mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。

cpp 复制代码
int x = 0;
// lambda写在全局,捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func1 = []()
    {
        x++;
    };

// 在一个类的成员函数中写lambda
class A
{
public:
    void func()
    {
        int x = 0, y = 1;

        // 隐式传值捕捉,默认除了捕捉x和y,也把this也捕捉过来了(注:只有用了才会捕捉,这里的意思是this也能捕捉)
        // 可以捕捉_a1和_a2,虽然不属于当前的局部域,可以理解为把this捕捉了,因为_a1和_a2是通过this访问的
        auto f1 = [=]
            {
                _a1++; // 可以修改
                return x + y + _a1 + _a2;
            };
        cout << f1() << endl;

        // 隐式引用捕捉同上
        auto f2 = [&]
            {
                x++; 
                _a1++; // 可以修改
                return x + y + _a1 + _a2;
            };
        cout << f2() << endl;

        // 捕捉this本质是可以访问成员变量
        //auto f3 = [x] // 如果显示传值捕捉只捕捉x,则不能访问_a1和_a2,因为没有this
        //auto f3 = [x, _a1] // err:这样也不行
        auto f3 = [x, this]
            {
                _a1++; // 可以修改
                return x + _a1 + _a2;
            };

        cout << f3() << endl;
    }

private:
    int _a1 = 0;
    int _a2 = 1;
};

int main()
{

    // 只能用当前lambda局部域捕捉的对象和全局对象
    // 捕获列表的意义,本质更方便的使用当前局部域的对象
    int a = 0, b = 1, c = 2, d = 3;
    auto func1 = [a, &b] // 这里不是取地址,而是引用捕捉
        {
            // 值捕捉的变量不能修改,引用捕捉的变量可以修改
            //a++; // 这里的a是外面a的拷贝,默认是const不能修改
            b++; // 引用捕捉,这里对b的修改就是对外面b的修改
            int ret = a + b;
            x++;
            return ret;
        };

    // 传值捕捉加mutable就可以修改(一般不会加)
    //auto func1 = [a, &b] () mutable // 用mutable必须加参数列表
    //    {
    //        // 值捕捉的变量不能修改,引用捕捉的变量可以修改
    //        a++; // 这里的a是外面a的拷贝,加mutable就可以修改,注意:传值捕捉里面改变不影响外面
    //        b++; // 引用捕捉,这里对b的修改就是对外面b的修改
    //        int ret = a + b;
    //        x++;
    //        return ret;
    //    };
    cout << func1() << endl;

    // 隐式值捕捉
    // 用了哪些变量就捕捉哪些变量(所有都能用)
    auto func2 = [=]
        {
            int ret = a + b + c + d;
            return ret;
        };
    cout << func2() << endl;

    // 隐式引用捕捉
    // 用了哪些变量就捕捉哪些变量(所有都能用)
    auto func3 = [&]
        {
            a++;
            c++;
            d++; 
        };
    func3();
    cout << a << " " << b << " " << c << " " << d << endl;

    // 混合捕捉1
    // a和b值捕捉,其他都是引用捕捉
    //auto func4 = [=, a, b] err
    //auto func4 = [a, &, b] err
    auto func4 = [&, a, b]
        {
            // 值捕捉不能修改
            //a++;
            //b++;
            c++;
            d++;
            return a + b + c + d;
        };
    func4();
    cout << a << " " << b << " " << c << " " << d << endl;

    // 混合捕捉2
    // a和b引用捕捉,其他都是值捕捉
    auto func5 = [=, &a, &b]
        {
            a++;
            b++;
            /*c++;
            d++;*/
            return a + b + c + d;
        };
    func5();
    cout << a << " " << b << " " << c << " " << d << endl;

    // 局部的静态和全局变量不能捕捉,也不需要捕捉
    // 可以直接使用,因为他们的作用域/生命周期是全局的
    static int m = 0;
    auto func6 = []
        {
            int ret = x + m;
            return ret;
        };

    // 传值捕捉本质是一种拷贝,并且被const修饰了
    // mutable相当于去掉const属性,可以修改了
    // 但是修改了不会影响外面被捕捉的值,因为是一种拷贝
    auto func7 = [=]()mutable
        {
            a++;
            b++;
            c++; 
            d++;
            return a + b + c + d;
        };
    cout << func7() << endl;
    cout << a << " " << b << " " << c << " " << d << endl;

    // 虽然我们可以自己写仿函数,但是用lambda更方便,编译器可以帮助我们生成,与范围for类似

    return 0;
}

3.3 lambda的应用

  • 在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。
  • lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等, lambda 的应用还是很广泛的。

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 } };
    // 类似这样的场景,我们实现仿函数对象或者函数指针支持商品中
    // 不同项的比较,相对还是比较麻烦的,那么这里lambda就很好用了
    sort(v.begin(), v.end(), ComparePriceLess());
    sort(v.begin(), v.end(), ComparePriceGreater());
    
    // 使用lambda;lambda是一个对象
    // 传给auto
    /*auto priceLess = [](const Goods& gl, const Goods& gr)
    {
        return gl._price < gr._price;
    };

    sort(v.begin(), v.end(), priceLess);*/
 
    // 直接传对象,更方便(传给模板)
    sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
            return gl._price < gr._price;
        });

    sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
            return gl._price > gr._price;
        });

    sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
            return gl._evaluate < gr._evaluate;
        });

    sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
            return gl._evaluate > gr._evaluate;
        });

    return 0;
}

3.4 lambda的原理

  • lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了一个 lambda 以后,编译器会生成一个对应的仿函数的类。
  • 仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返 回类型/函数体就是仿函数operator()的参数/返回类型/函数体,lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。
  • 上面的原理,我们可以透过汇编层了解一下,下面第二段汇编层代码印证了上面的原理。

cpp 复制代码
 // lambda的原理
class lambda8
{
public:
    lambda8(int a_, int& b_)
        :a(a_)
        , b(b_)
        {
        }

    int operator()(int x)
    {
        ++b;
        return a + b + x;
    }
private:
    const int a;
    int& b;
};

auto func8 = [a, &b](int x)
{
    ++b;
    return a + b + x;
};
// 等价于:
//lambda8 func8(a, b);
//func8(1);

通过汇编代码观察:

cpp 复制代码
r1(10000, 2);
00D8297D push 2
00D8297F sub esp,8
00D82982 movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D8298A movsd mmword ptr [esp],xmm0
00D8298F lea ecx,[r1]
00D82992 call Rate::operator() (0D81212h)
// 汇编层可以看到r2 lambda对象调用本质还是调用operator(),类型是lambda_1,这个类型名
// 的规则是编译器自己定制的,保证不同的lambda不冲突
r2(10000, 2);
00D82999 push 2
00D8299B sub esp,8
00D8299E movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D829A6 movsd mmword ptr [esp],xmm0
00D829AB lea ecx,[r2]
00D829AE call `main'::`2'::<lambda_1>::operator() (0D824C0h)

四. 包装器

4.1 function

std::function 是一个"万能容器":它可以存储任何"可调用对象"(函数、Lambda、函数对象、成员函数等)。

cpp 复制代码
template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
  • std::function 是一个类模板,也是一个包装器。 std::function 的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调用对象被称为 std::function 的目标。若 std::function 不含目标,则称它为空。调用空 std::function 的目标导致抛出 std::bad_function_call 异常。
  • 以上是 function 的原型,他被定义头文件中。std::function - cppreference.com 是function的官方文件链接。
  • 函数指针、仿函数、 lambda 等可调用对象的类型各不相同, std::function 的优势就是统 一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型,下面的第二个代 码样例展示了 std::function 作为map的参数,实现字符串和可调用对象的映射表功能。

cpp 复制代码
int f(int a, int b)
{
    return a + b;
}
struct Functor
{
public:
    int operator() (int a, int b)
    {
        return a + b;
    }
};
class Plus
{
public:
    Plus(int n = 10)
        :_n(n)
    {
    }
    static int plusi(int a, int b)
    {
        return a + b;
    }
    double plusd(double a, double b)
    {
        return (a + b) * _n;
    }
private:
    int _n;
};

int main()
{
    // 包装各种可调用对象
    // 类型擦除
    // 包装函数指针,仿函数,lambda
    function<int(int, int)> f1 = f;
    function<int(int, int)> f2 = Functor();
    function<int(int, int)> f3 = [](int a, int b) {return a + b; };
    cout << f1(1, 1) << endl;
    cout << f2(1, 1) << endl;
    cout << f3(1, 1) << endl;

	vector<function<int(int, int)>> v;
	v.push_back(f);
	v.push_back(Functor());
	v.push_back([](int a, int b) {return a + b; });

	for (auto& f : v)
	{
		cout << f(1, 1) << endl;
	}

    // 包装成员函数的指针
    // 静态成员
    //function<int(int, int)> f4 = Plus::plusi;
    function<int(int, int)> f4 = &Plus::plusi; // 取地址最好还是加上
    cout << f4(1, 1) << endl;  // 调用方面
    // 非静态成员(注意:还有一个this指针)
    function<double(Plus*, double, double)> f5 = &Plus::plusd;
    Plus ps;
    cout << f5(&ps, 1.1, 1.1) << endl; // 调用

    // 其他写法
    function<double(Plus, double, double)> f6 = &Plus::plusd;
    cout << f6(ps, 1.1, 1.1) << endl;

    function<double(Plus, double, double)> f7 = &Plus::plusd;
    cout << f7(Plus(), 1.1, 1.1) << endl;

    function<double(Plus&&, double, double)> f8 = &Plus::plusd;
    cout << f8(Plus(), 1.1, 1.1) << endl; // 右值引用,这里可以传匿名对象

    // 为什么上面即可以传对象的指针也可以传对象?
    // 成员函数的指针
    auto pf1 = &Plus::plusd; 
    //cout << pf1(&ps, 1.1, 1.1); // 不能这样调用:因为不能显示的传递this指针
	cout << (ps.*pf1)(1.1, 1.1) << endl;
    // 对象的指针
    Plus* ptr = &ps; 
	cout << (ptr->*pf1)(1.1, 1.1) << endl;

    return 0;
}

cpp 复制代码
#include <iostream>
#include <functional>

// 1. 普通函数
int add(int a, int b) { return a + b; }

// 2. 仿函数 (Functor)
struct Subtract {
    int operator()(int a, int b) { return a - b; }
};

// 3. 类成员函数
class Calculator {
public:
    int multiply(int a, int b) { return a * b; }
    static int divide(int a, int b) { return a / b; }
};

int main() {
    // 声明一个能接受两个 int 并返回 int 的 function
    std::function<int(int, int)> op;

    // A. 包装普通函数
    op = add;
    std::cout << "Add: " << op(10, 5) << std::endl; // 输出 15

    // B. 包装 Lambda 表达式
    op = [](int a, int b) { return a % b; };
    std::cout << "Mod: " << op(10, 3) << std::endl; // 输出 1

    // C. 包装仿函数
    op = Subtract();
    std::cout << "Sub: " << op(10, 5) << std::endl; // 输出 5

    // D. 包装类静态成员函数 (和普通函数一样)
    op = Calculator::divide;
    std::cout << "Div: " << op(10, 5) << std::endl; // 输出 2

    // E. 包装类非静态成员函数 (注意!)
    // 非静态成员函数需要对象实例(隐式的 this 指针)
    // std::function 的签名需要变更为:<int(Calculator*, int, int)> 或者配合 bind 使用
    
    return 0;
}

4.2 bind

std::bind 是一个"适配器":它可以把一个函数预先固定某些参数,或者改变参数的顺序,生成一个新的可调用对象。

cpp 复制代码
simple(1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
  • bind 是一个函数模板,它也是一个可调用对象的包装器,可以把他看做一个函数适配器,对接收的fn可调用对象进行处理后返回一个可调用对象。 bind 可以用来调整参数个数和参数顺序。 bind 也在这个头文件中。
  • 调用bind的一般形式: auto newCallable = bind(callable,arg_list); 其中 newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的 参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
  • arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是占位符,表示 newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象 中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。_1/_2/_3....这些占位符放到placeholders的一个命名空间中。

cpp 复制代码
#include<functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

int Sub(int a, int b)
{
    return (a - b) * 10;
}

int SubX(int a, int b, int c)
{
    return (a - b - c) * 10;
}

class Plus
{
public:
    static int plusi(int a, int b)
    {
        return a + b;
    }
    double plusd(double a, double b)
    {
        return a + b;
    }
};

int main()
{
    // bind 本质返回的一个仿函数对象
    // 调整参数顺序(不常用)
    // _1代表第一个实参
    // _2代表第二个实参
    auto f1 = bind(Sub, _1, _2);
    auto f2 = bind(Sub, _2, _1);

    cout << f1(10, 5) << endl; // 50
    cout << f2(10, 5) << endl; // -50

    // 调整参数个数
    // 编译器会根据占位符(_1,_2)来调整Sub的顺序或个数
    auto f3 = bind(SubX, 10, _2, _1); // 绑死第一个参数
    cout << f2(15, 5) << endl;
    // 底层operator(),调用SubX,第一个参数10,_1表示第一个实参,_2表示第二个实参

    auto f4 = bind(SubX, _1, 10, _2); // 绑死第二个参数
    cout << f4(15, 5) << endl;
    // 底层operator(),调用SubX,参数分别是15,10,5

    auto f5 = bind(SubX, _1, _2, 10); // 绑死第三个参数
    cout << f5(15, 5) << endl;
    // 底层operator(),调用SubX,参数分别是15,5,10

    // bind作用
    function<double(Plus, double, double)> f7 = &Plus::plusd;
    // 如果要进行多次调用,每次都穿Plus(),比较麻烦;其他参数是变化的
    // 绑定通常是这个参数在这一组参数中是固定的,把它绑死
    // 绑定返回的是一个可调用对象
    cout << f7(Plus(), 1.1, 1.1) << endl;
    cout << f7(Plus(), 1.1, 2.2) << endl;
    cout << f7(Plus(), 1.1, 3.3) << endl;

    function<double(double, double)> f8 = bind(&Plus::plusd, Plus(), _1, _2);
    cout << f8(1.1, 1.1) << endl;
    cout << f8(1.1, 2.2) << endl;
    cout << f8(1.1, 3.3) << endl << endl;

    // 计算复利的lambda
    auto func1 = [](double rate, double money, int year)->double 
        {
            double ret = money;
            for (int i = 0; i < year; i++)
            {
                ret += ret * rate;
            }
            return ret - money;
        };

    function<double(double)> func_r1_5_y3 = bind(func1, 0.015, _1, 3);
	function<double(double)> func_r1_5_y5 = bind(func1, 0.015, _1, 5);
	function<double(double)> func_r1_5_y20 = bind(func1, 0.015, _1, 20);

    cout << func_r1_5_y3(100000) << endl;
	cout << func_r1_5_y5(100000) << endl;
	cout << func_r1_5_y20(100000) << endl;

	function<double(double)> func_r10_y3 = bind(func1, 0.1, _1, 3);
	function<double(double)> func_r10_y5 = bind(func1, 0.1, _1, 5);
	function<double(double)> func_r10_y20 = bind(func1, 0.1, _1, 20);
	cout << func_r10_y3(100000) << endl;
	cout << func_r10_y5(100000) << endl;
	cout << func_r10_y20(100000) << endl;

    return 0;
}

总结

如有不足或改进之处,欢迎大家在评论区积极讨论,后续我也会持续更新C++相关的知识。文章制作不易,如果文章对你有帮助,就点赞收藏关注支持一下作者吧,让我们一起努力,共同进步!

相关推荐
21997 小时前
消息中间件2025技术全景与选型指南
中间件·开源·rabbitmq
Chase_______7 小时前
【JAVA基础指南(一)】快速掌握基础语法
java·开发语言
沧澜sincerely7 小时前
蓝桥杯103 日期问题
c++·蓝桥杯
小白学大数据7 小时前
Python 爬虫如何分析并模拟 JS 动态请求
开发语言·javascript·爬虫·python
秦少游在淮海7 小时前
网络缓冲区 · 通过读写偏移量维护数据区间的高效“零拷贝” Buffer 设计
linux·开发语言·网络·tcp协议·muduo·网络缓冲区
qs70167 小时前
c直接调用FFmpeg命令无法执行问题
c语言·开发语言·ffmpeg
zoujiahui_20187 小时前
python中模型加速训练accelerate包的用法
开发语言·python
码界奇点7 小时前
基于Golang的分布式综合资产管理系统设计与实现
开发语言·分布式·golang·毕业设计·go语言·源代码管理
xiaolongmeiya7 小时前
P3810 【模板】三维偏序 / 陌上花开 cdq分治+树状数组
c++·算法