C++11 :新的类功能,lambda,包装器

目录

一.新的类功能

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

1.2成员变量给缺省值

[1.3 defult和delete](#1.3 defult和delete)

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

二.lambda

2.1lambda表达式语法及应用

2.1.1lambda的表达是语法

2.2.2lambda的应用

2.2捕捉列表

三.包装器

3.1function包装器

3.2bind包装器

一.新的类功能

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

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

注:这里用自定义的string可以更加方便我们观察出调用哪一个函数类型,string代码在上一篇文章中已经写过,不知道的可以看上一个文章

默认成员函数 编译器何时自动生成 关键抑制条件(只要满足,不再自动生成)
默认构造函数 用户没有手写任何构造函数 自己实现任意构造(拷贝构造、移动构造、普通构造等)
析构函数 用户没有手写析构函数 显式定义析构函数
拷贝构造函数 用户没有手写拷贝构造 1. 显式定义拷贝构造2. 显式定义移动构造 / 移动赋值(C++11 规则)
拷贝赋值运算符 用户没有手写拷贝赋值 1. 显式定义拷贝赋值2. 显式定义移动构造 / 移动赋值(C++11 规则)
移动构造函数(C++11) 同时满足全部 3 条:① 无自定义移动构造② 无自定义析构③ 无自定义拷贝构造④ 无自定义拷贝赋值 只要定义下面任意一个:析构 / 拷贝构造 / 拷贝赋值 / 移动构造
移动赋值运算符(C++11) 同时满足全部 3 条:① 无自定义移动赋值② 无自定义析构③ 无自定义拷贝构造④ 无自定义拷贝赋值 只要定义下面任意一个:析构 / 拷贝构造 / 拷贝赋值 / 移动赋值

现代 C++ 推荐:能使用 =default / =delete 显式声明,避免编译器隐式行为带来坑。

1.2成员变量给缺省值

1、基础语法(C++11 新增)

只能给非静态成员变量写缺省值,两种写法:

复制代码
struct Node
{
    int _a = 10;         // 等号形式
    double _b{3.14};     // 列表初始化(推荐,防止隐式转换)
    int* _ptr = nullptr;

    static int _c = 1;   // ❌ 错误!静态成员不能这样写(静态只能类外初始化)
};

2、核心优先级规则【面试高频】

构造函数初始化列表 > 类内缺省值 逻辑:

  1. 如果构造函数初始化列表显式列出该成员 → 使用列表的值,类内缺省值直接失效;
  2. 如果初始化列表没有写这个成员 → 自动使用类内缺省值初始化;
  3. 构造函数体内赋值不参与初始化!只是赋值,初始化早已完成。

示例演示:

复制代码
struct Node
{
    int _x = 100;
    Node() : _x(1) {}   // 初始化列表覆盖 → _x = 1
    Node(int a) {}      // 列表没写_x → 使用缺省值 _x = 100
};

3、初始化顺序(重中之重)

成员初始化顺序 只按照类中从上到下声明顺序 和:初始化列表书写顺序、类内缺省值书写顺序无关!

复制代码
struct Test
{
    int a = 1;
    int b = a; // 合法,a先初始化
};

4、和八大默认成员函数的关系

  1. 类内缺省值 ≠ 默认构造函数 类内缺省值只是 "后备初始化方案",不会强制编译器生成默认构造函数 。 只要你手写任意构造函数,编译器不再合成默认构造;就算写了成员缺省值也没用。

    概念 要求 调用方式
    默认构造函数 调用时允许不传参数(无参 / 全部参数带缺省) A obj; 合法
    普通有参构造函数 必须提供实参,无法无参调用 A obj; 报错;A obj(10); 合法
  2. 编译器合成的默认构造 / 默认移动构造 / 默认拷贝构造 在逐个初始化成员时: 如果成员没有在隐式初始化列表中指定,就采用类内缺省值初始化。

  3. ⚠️ 易错区分

    struct listNode
    {
    T _data;
    Node* _next = nullptr;
    Node* _prev = nullptr;
    };

当你创建 listNode 对象: 只要构造函数没有初始化 _next、_prev,自动初始化为 nullptr; 不用在每一个构造函数初始化列表重复写 _next(nullptr),_prev(nullptr) 👉 这就是工程上最常用的好处!减少重复代码。

5、常见限制与坑

  1. 不能初始化引用成员、const 成员 引用 & /const T 成员必须在构造初始化列表显式初始化,类内缺省值无法满足。

    struct A
    {
    const int x = 5; // ❌ 编译报错
    int& r = x; // ❌ 编译报错
    };

  2. 类内缺省值不是赋值!是初始化 区分:

    struct A
    {
    int a = 10; // 初始化
    A(){ a = 20; } // 先初始化为10,随后赋值修改为20(低效,尽量避免)
    };

  3. 模板类(你的 listNode<T>)完全支持类内缺省值,写法一样。

1.3 defult和delete

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

1.4 final和override

1、override

作用

显式声明:这个函数是用来重写父类虚函数的 让编译器帮忙检查语法是否合法;防止写错函数签名导致没有成功重写。

使用位置

只能写在虚成员函数声明末尾

复制代码
class Base
{
public:
    virtual void func()
    {
    }
};

class Derive : public Base
{
public:
    // ✅ 正确,明确重写父类虚函数
    void func() override
    {
    }

    // ❌ 报错:父类没有 void func(int),重写失败,override直接揪出错误
    void func(int) override
    {
    }
};

核心要点

  1. 只能修饰虚函数;普通函数写 override → 编译报错
  2. 不会改变程序行为,仅做编译期检查
  3. 强烈建议:所有重写虚函数都加上 override,规避笔误

参数、返回值、const、引用限定只要和父类虚函数不一致,就无法构成重写,变成隐藏(overload/hide),override 会直接报错提醒。

复制代码
class Base
{
public:
    virtual void func() const {}
};

class Derive : public Base
{
public:
    // ❌ 缺少const,不是重写,override触发编译错误
    void func() override {}
};

2、final

final 有两种用法

用法 1:修饰虚函数

含义:这个虚函数不能被子类继续重写

复制代码
class Base
{
public:
    virtual void func() final
    {
    }
};

class Derive : public Base
{
public:
    void func() override // ❌ 编译报错,func被final禁止重写
    {
    }
};

用法 2:修饰类(写在类名后面)

含义:该类禁止被继承,不能当父类

复制代码
class Base final
{
};

class Derive : public Base // ❌ 报错,Base被final,禁止继承
{
};

注意区分位置

复制代码
void func() final;   // final修饰函数:禁止子类重写
class A final {};    // final修饰类:禁止被继承
关键字 作用对象 核心功能
override 派生类虚函数 告诉编译器:我要重写父类虚函数,帮忙校验签名
final 1. 基类虚函数2. 类 函数:禁止子类重写类:禁止被继承

二.lambda

2.1lambda表达式语法及应用

2.1.1lambda的表达是语法

Lambda = 匿名函数对象

编译器遇到[],自动生成一个重载了operator()的类;创建这个类的临时对象,就是 lambda。

没有原生类型,只能用auto、std::function、模板接收。

  • lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使用层而言没有类型,所以我们一般是用 auto 或者模板参数定义的对象去接收 lambda 对象。
  • lambda 表达式的格式:[capture-list] (parameters)-> return type { function boby }
  • [capture-list]:捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节 7.2 中我们再细讲。捕捉列表为空也不能省略。
  • (parameters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()一起省略
  • ->return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {function boby}:函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。

lambda 能不能递归?

直接不行! lambda 内部不能调用自身,因为定义之前变量还不存在。 两种解决方案:

  • 使用std::function

    function<int(int)> fib = [&](int n){
    if(n<=1) return 1;
    return fib(n-1)+fib(n-2);
    };

2.2.2lambda的应用

常见实战场景

配合算法库sort排序

复制代码
vector<int> v={3,1,2};
sort(v.begin(),v.end(),[](int a,int b){
    return a>b; //降序
});
cpp 复制代码
#include<algorithm>

struct Goods
{
    string _name;  // 名字
    double _price; // 价格
    int _evaluate; // 评价
    // ...

    Goods(const char* str, double price, int evaluate)
        :_name(str)
        , _price(price)
        , _evaluate(evaluate)
    {
    }
};

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

struct Compare2
{
    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(), Compare1());

	// 价格降序
	//sort(v.begin(), v.end(), Compare2());

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

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

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

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

	return 0;
}

2.2捕捉列表

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

三.包装器

3.1function包装器

std::function 通过类型擦除,把不同类型的可调用对象(函数指针、lambda、仿函数、bind 结果)包装成同一个 std::function<R(Args...)> 类型,对外提供统一的 operator() 调用接口。

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

1. 将可调用对象存入容器(vector/map)

lambda、仿函数各自类型独一无二,不能直接放进容器; std::function 是固定模板类型,可以存进 vector、map,实现函数映射表、事件分发。 示例:

复制代码
map<string, function<void()>> opMap;
opMap["add"] = [](){cout << "加法";};
opMap["sub"] = [](){cout << "减法";};

2. 实现 Lambda 递归

lambda 定义前自身变量尚未创建,内部不能直接调用自己。 借助 std::function 中转实现递归:

复制代码
function<int(int)> fib;
fib = [&](int n){
    if(n <= 2) return 1;
    return fib(n-1)+fib(n-2);
};

3.2bind包装器

  • bind 是一个函数模板,它也是一个可调用对象的包装器,可以把他看做一个函数适配器,对接收到的 fn 可调用对象进行处理后返回一个可调用对象。bind 可以用来调整参数个数和参数顺序。bind 也在<functional>这个头文件中。
  • 调用 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>
#include<iostream>
using namespace std;

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()
{
    auto sub1 = bind(Sub, _1, _2);
    cout << sub1(10, 5) << endl;

    // bind 本质返回的一个仿函数对象
    // 调整参数顺序(不常用)
    // _1代表第一个实参
    // _2代表第二个实参
    // ...
    auto sub2 = bind(Sub, _2, _1);
    cout << sub2(10, 5) << endl;

    // 调整参数个数 (常用)
    auto sub3 = bind(Sub, 100, _1);
    cout << sub3(5) << endl;

    auto sub4 = bind(Sub, _1, 100);
    cout << sub4(5) << endl;

    // 分别绑死第123个参数
    auto sub5 = bind(SubX, 100, _1, _2);
    cout << sub5(5, 1) << endl;

    auto sub6 = bind(SubX, _1, 100, _2);
    cout << sub6(5, 1) << endl;

    auto sub7 = bind(SubX, _1, _2, 100);
    cout << sub7(5, 1) << endl;

    // 成员函数对象进行绑死,就不需要每次都传递了
    function<double(Plus&&, double, double)> f6 = &Plus::plusd;
    Plus pd;
    cout << f6(move(pd), 1.1, 1.1) << endl;
    cout << f6(Plus(), 1.1, 1.1) << endl;

    // bind一般用于,绑死一些固定参数
    function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
    cout << f7(1.1, 1.1) << 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)> func3_1_5 = bind(func1, 0.015, _1, 3);
    function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
    function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
    function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);

    cout << func3_1_5(1000000) << endl;
    cout << func5_1_5(1000000) << endl;
    cout << func10_2_5(1000000) << endl;
    cout << func20_3_5(1000000) << endl;

    return 0;
}
  1. std::bind 作用:函数适配器,可以固定参数、调换参数顺序、减少调用时传入参数数量
  2. _1、_2 占位符:std::placeholders 命名空间,代表调用 bind 返回对象时传入的第 1、2 个实参
  3. 绑定普通成员函数 :必须额外传入类对象 / 对象指针(成员函数隐含this)
  4. bind 返回类型极其复杂,通常使用 auto 或者 std::function 接收
  5. 典型业务场景:把一部分参数预先绑定,生成签名更简单的回调函数(示例里的复利计算器)

下面是bind的重要用途

相关推荐
言乐61 小时前
JavaScript概括前端原理
开发语言·前端·javascript·python·ecmascript
liulilittle1 小时前
短期内不存在通用 AI 工作流魔法
大数据·开发语言·c++·人工智能·llm·agent·tools
_wxd6661 小时前
C++STL map与set
数据结构·c++
别动我齐刘海1 小时前
简历技术栈全面复习总结
c++·人工智能·深度学习·学习·tcp/ip·算法·rpc
郝学胜-神的一滴1 小时前
C++ Templates 03:手写一个Stack看透模板核心机制
开发语言·c++·产品运营·软件工程·产品经理
leo在掘金1 小时前
google/ax单日涨1379星:Agent编排运行时到底难在哪?
后端·架构
hetao17338371 小时前
2026-10-03 hetao1733837 的刷题记录
c++·算法
kaixin_learn_qt_ing1 小时前
Qt&C++软件开发工程师
c++
(Charon)1 小时前
【C/C++面试】手写内存池:从空闲链表到内存申请与释放
java·开发语言·windows