首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++进阶:(十四)C++11 深度解析(下):类功能、STL 升级、lambda 与包装器全解析

C++进阶:(十四)C++11 深度解析(下):类功能、STL 升级、lambda 与包装器全解析

作者头像
_OP_CHEN
发布2026-01-14 11:00:44
发布2026-01-14 11:00:44
390
举报
文章被收录于专栏:C++C++

前言

从类的功能增强到 STL 的全面升级,从 lambda 表达式的灵活运用到包装器的统一封装,C++11 彻底改变了 C++ 的编程范式。本文将聚焦 C++11 中类功能扩展、STL 变化、lambda 表达式和包装器四大核心模块,结合实例代码深入剖析,带你全方位掌握这些改变 C++ 编程体验的关键特性。下面就让我们正式开始吧!


一、新的类功能:让类设计更灵活高效

C++11 对类的功能进行了大幅扩展,新增了移动语义相关的默认成员函数,优化了成员变量初始化方式,提供了更精细的默认函数控制手段,同时完善了继承体系中的类型控制。这些特性让类的设计更加灵活,代码编写更加简洁高效。

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

在 C++11 之前,类的默认成员函数有 6 个:构造函数、析构函数、拷贝构造函数、拷贝赋值运算符重载、取地址运算符重载和 const 取地址运算符重载。其中前 4 个是核心,后两个实际使用场景有限。但在处理大量数据拷贝的场景中,传统的拷贝构造和拷贝赋值会带来巨大的性能开销 —— 比如当我们用一个临时对象初始化另一个对象时,拷贝构造会执行深拷贝,重复分配内存并复制数据,而临时对象用完后又会被析构,造成资源的浪费。

C++11 引入了移动语义,新增了两个默认成员函数:移动构造函数移动赋值运算符重载,专门用于处理临时对象的资源转移,避免不必要的拷贝。

1.1.1 默认移动成员函数的生成规则

编译器生成默认移动构造和移动赋值的条件非常明确,这是使用该特性的关键:

  • 若未手动实现移动构造函数,且未实现析构函数、拷贝构造函数、拷贝赋值运算符重载中的任意一个,编译器会自动生成默认移动构造函数。
  • 若未手动实现移动赋值运算符重载,且未实现析构函数、拷贝构造函数、拷贝赋值运算符重载中的任意一个,编译器会自动生成默认移动赋值运算符重载。
  • 默认生成的移动构造和移动赋值,对内置类型成员执行逐字节拷贝(浅拷贝),对自定义类型成员,会调用其移动构造(或移动赋值);若自定义类型成员未实现移动语义,则调用其拷贝构造(或拷贝赋值)。
  • 若手动提供了移动构造或移动赋值,编译器不会自动生成拷贝构造和拷贝赋值,反之亦然。
1.1.2 代码示例:默认移动语义的使用
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

// 自定义字符串类,模拟深拷贝
namespace bit {
class string {
public:
    // 构造函数
    string(const char* str = "") 
        : _size(strlen(str))
        , _capacity(_size)
    {
        _str = new char[_capacity + 1];
        strcpy(_str, str);
        cout << "string(const char* str) -- 构造" << endl;
    }

    // 拷贝构造(深拷贝)
    string(const string& s)
        : _size(s._size)
        , _capacity(s._capacity)
    {
        _str = new char[_capacity + 1];
        strcpy(_str, s._str);
        cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;
    }

    // 拷贝赋值(深拷贝)
    string& operator=(const string& s) {
        cout << "string& operator=(const string& s) -- 拷贝赋值(深拷贝)" << endl;
        if (this != &s) {
            char* tmp = new char[s._capacity + 1];
            strcpy(tmp, s._str);
            delete[] _str;
            _str = tmp;
            _size = s._size;
            _capacity = s._capacity;
        }
        return *this;
    }

    // 析构函数
    ~string() {
        delete[] _str;
        _str = nullptr;
        _size = 0;
        _capacity = 0;
        cout << "~string() -- 析构" << endl;
    }

private:
    char* _str = nullptr;
    size_t _size = 0;
    size_t _capacity = 0;
};
}

// 测试类:未手动实现拷贝相关函数,编译器会生成默认移动语义
class Person {
public:
    Person(const char* name = "", int age = 0)
        : _name(name)
        , _age(age)
    {
        cout << "Person(const char* name, int age) -- 构造" << endl;
    }

    // 未实现析构、拷贝构造、拷贝赋值,编译器会生成默认移动构造和移动赋值
private:
    bit::string _name;
    int _age;
};

int main() {
    Person p1("张三", 20);
    // 移动构造:p2窃取p1的资源(p1变为无效状态)
    Person p2 = move(p1);
    Person p3("李四", 25);
    // 移动赋值:p3窃取p2的资源
    p3 = move(p2);

    return 0;
}
1.1.3 移动语义的核心价值

移动构造和移动赋值的本质是 “资源窃取” 而非 “资源拷贝”。当操作临时对象(右值)时,移动语义会直接接管临时对象的内存资源,避免了深拷贝的开销。例如,在 STL 容器中插入临时对象时,容器会调用元素的移动构造,大幅提升插入效率。

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

C++11 允许在类的成员变量声明时直接指定缺省值,这个缺省值会作为成员变量的默认初始化值,供初始化列表使用。如果在初始化列表中显式初始化了成员变量,则使用显式指定的值;否则使用声明时的缺省值。

1.2.1 代码示例:成员变量缺省值的使用
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class Student {
public:
    // 构造函数:未显式初始化的成员变量使用声明时的缺省值
    Student(const string& name)
        : _name(name)
    {
        // _age默认值为18,_score默认值为0
    }

    // 构造函数:显式初始化部分成员变量
    Student(const string& name, int age)
        : _name(name)
        , _age(age)
    {
        // _score默认值为0
    }

    void Print() {
        cout << "姓名:" << _name << ",年龄:" << _age << ",成绩:" << _score << endl;
    }

private:
    string _name;          // 无缺省值,必须在初始化列表中显式初始化
    int _age = 18;         // 缺省值18
    double _score = 0.0;   // 缺省值0.0
};

int main() {
    Student s1("张三");
    s1.Print();  // 输出:姓名:张三,年龄:18,成绩:0

    Student s2("李四", 20);
    s2.Print();  // 输出:姓名:李四,年龄:20,成绩:0

    return 0;
}
1.2.2 特性优势
  • 简化代码:无需在构造函数中重复初始化多个成员变量,尤其适用于有多个构造函数的场景。
  • 提高可读性:成员变量的默认值直接体现在声明处,一目了然。
  • 兼容初始化列表:缺省值不会覆盖显式初始化的值,灵活性高。

1.3 default 和 delete:精细控制默认函数

C++11 提供了defaultdelete关键字,让开发者能够更精细地控制默认成员函数的生成,解决了 C++98 中默认函数控制不便的问题。

1.3.1 default 关键字:显式要求生成默认函数

当我们手动实现了某些默认函数(如拷贝构造),编译器就不会自动生成移动构造和移动赋值。如果我们需要保留默认的移动语义,可以使用default显式要求编译器生成。

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    Person(const char* name = "", int age = 0)
        : _name(name)
        , _age(age)
    {}

    // 手动实现拷贝构造
    Person(const Person& p)
        : _name(p._name)
        , _age(p._age)
    {
        cout << "Person(const Person& p) -- 拷贝构造" << endl;
    }

    // 显式要求编译器生成默认移动构造
    Person(Person&& p) = default;

    // 显式要求编译器生成默认移动赋值
    Person& operator=(Person&& p) = default;

private:
    string _name;
    int _age;
};

int main() {
    Person p1("张三", 20);
    Person p2 = move(p1);  // 调用默认移动构造
    Person p3("李四", 25);
    p3 = move(p2);         // 调用默认移动赋值

    return 0;
}
1.3.2 delete 关键字:禁止生成默认函数

在 C++98 中,要禁止某个默认函数(如拷贝构造),需要将其声明为 private 且不实现。C++11 中只需在函数声明后加上delete,即可禁止编译器生成该函数的默认版本。

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class NonCopyable {
public:
    NonCopyable() = default;  // 显式生成默认构造
    ~NonCopyable() = default; // 显式生成默认析构

    // 禁止拷贝构造
    NonCopyable(const NonCopyable&) = delete;

    // 禁止拷贝赋值
    NonCopyable& operator=(const NonCopyable&) = delete;
};

int main() {
    NonCopyable nc1;
    // NonCopyable nc2 = nc1;  // 编译报错:拷贝构造已被删除
    // NonCopyable nc3;
    // nc3 = nc1;              // 编译报错:拷贝赋值已被删除

    return 0;
}
1.3.3 应用场景

  • default:适用于需要保留默认语义,但因手动实现其他函数导致编译器不自动生成的场景。
  • delete:适用于单例模式、禁止拷贝的对象(如文件句柄、网络连接)等场景,避免意外拷贝导致的资源问题。

1.4 final 与 override:完善继承体系的类型控制

在继承和多态中,finaloverride关键字为开发者提供了更严格的类型控制,避免了因继承关系复杂导致的错误。

1.4.1 final 关键字:禁止继承或重写
  • 修饰类:表示该类不能被继承。
  • 修饰虚函数:表示该虚函数不能被派生类重写。
代码语言:javascript
复制
#include <iostream>
using namespace std;

// final修饰类:Base类不能被继承
class Base final {
public:
    virtual void Show() {
        cout << "Base::Show()" << endl;
    }
};

// class Derived : public Base {  // 编译报错:Base是final类,不能被继承
// public:
//     void Show() override {
//         cout << "Derived::Show()" << endl;
//     }
// };

class Base2 {
public:
    // final修饰虚函数:Show不能被重写
    virtual void Show() final {
        cout << "Base2::Show()" << endl;
    }
};

class Derived2 : public Base2 {
public:
    // void Show() override {  // 编译报错:Show是final函数,不能被重写
    //     cout << "Derived2::Show()" << endl;
    // }
};

int main() {
    Base b;
    b.Show();

    Base2 b2;
    b2.Show();

    return 0;
}
1.4.2 override 关键字:检查虚函数重写的正确性

override用于派生类的虚函数声明,表示该函数是重写基类的虚函数。编译器会检查基类是否存在对应的虚函数,若不存在或签名不匹配,则编译报错,避免因拼写错误、参数不匹配等导致的重写失败。

代码语言:javascript
复制
#include <iostream>
using namespace std;

class Base {
public:
    virtual void Show(int a) {
        cout << "Base::Show(int a) -- a = " << a << endl;
    }

    virtual void Display() {
        cout << "Base::Display()" << endl;
    }
};

class Derived : public Base {
public:
    // 正确重写:签名与基类一致,加上override进行检查
    void Show(int a) override {
        cout << "Derived::Show(int a) -- a = " << a << endl;
    }

    // void Show(double a) override {  // 编译报错:基类无对应的虚函数
    //     cout << "Derived::Show(double a) -- a = " << a << endl;
    // }

    // void DisPlay() override {  // 编译报错:拼写错误,基类是Display
    //     cout << "Derived::DisPlay()" << endl;
    // }
};

int main() {
    Base* pb = new Derived();
    pb->Show(10);     // 调用派生类重写的Show
    pb->Display();    // 调用基类的Display

    delete pb;
    return 0;
}
1.4.3 特性价值

  • final:防止不必要的继承,避免基类被意外扩展,同时保护核心虚函数的实现不被修改。
  • override:强制编译器检查重写的正确性,减少因人为失误导致的多态失效问题,提高代码的健壮性。

二、STL 中的一些变化:更高效、更易用的标准库

C++11 对 STL 进行了全面升级,不仅新增了实用的容器,还为现有容器添加了大量新接口,优化了容器的性能和使用体验。这些变化让 STL 更加强大,能够满足更多场景的需求。

2.1 新增容器

C++11 新增了 4 个容器:std::arraystd::forward_liststd::unordered_mapstd::unordered_set。其中,std::unordered_mapstd::unordered_set是最常用的,而std::arraystd::forward_list则针对特定场景提供了更高效的选择。

2.1.1 std::array:固定大小的数组容器

std::array是固定大小的数组容器,结合了 C 风格数组的高效性和 STL 容器的易用性。与 C 风格数组相比,std::array提供了迭代器、大小查询、边界检查等安全特性;与std::vector相比,std::array无需动态分配内存,性能更优,但大小固定,不能动态扩展。

代码语言:javascript
复制
#include <iostream>
#include <array>
#include <algorithm>
using namespace std;

int main() {
    // 初始化:指定大小为5,初始值为1,2,3,4,5
    array<int, 5> arr = {1, 2, 3, 4, 5};

    // 访问元素:支持[]和at(),at()会做边界检查
    cout << "arr[2] = " << arr[2] << endl;
    cout << "arr.at(3) = " << arr.at(3) << endl;
    // arr.at(10) = 10;  // 抛出out_of_range异常

    // 大小相关操作
    cout << "数组大小:" << arr.size() << endl;
    cout << "是否为空:" << (arr.empty() ? "是" : "否") << endl;
    cout << "最大容量:" << arr.max_size() << endl;  // 与size()相同,固定大小

    // 遍历:支持范围for、迭代器
    cout << "范围for遍历:";
    for (auto& val : arr) {
        cout << val << " ";
    }
    cout << endl;

    cout << "迭代器遍历:";
    for (auto it = arr.begin(); it != arr.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    // 算法支持
    sort(arr.rbegin(), arr.rend());  // 逆序排序
    cout << "逆序排序后:";
    for (auto& val : arr) {
        cout << val << " ";
    }
    cout << endl;

    return 0;
}
2.1.2 std::forward_list:单向链表

std::forward_list是单向链表容器,仅支持单向遍历,相比std::list(双向链表),它占用内存更少,插入和删除操作更高效(尤其是在链表头部)。适用于只需单向遍历、频繁插入删除的场景。

代码语言:javascript
复制
#include <iostream>
#include <forward_list>
using namespace std;

int main() {
    forward_list<int> flist = {1, 2, 3, 4, 5};

    // 头部插入
    flist.push_front(0);
    // 头部删除
    flist.pop_front();

    // 插入到指定位置(需要通过迭代器)
    auto it = flist.begin();
    advance(it, 2);  // 移动迭代器到第3个元素(索引2)
    flist.insert_after(it, 30);  // 在it之后插入30

    // 遍历
    cout << "forward_list遍历:";
    for (auto& val : flist) {
        cout << val << " ";
    }
    cout << endl;

    // 删除指定位置的元素
    it = flist.begin();
    advance(it, 3);
    flist.erase_after(it);  // 删除it之后的元素

    cout << "删除元素后:";
    for (auto& val : flist) {
        cout << val << " ";
    }
    cout << endl;

    return 0;
}
2.1.3 std::unordered_map/std::unordered_set:哈希容器

std::unordered_mapstd::unordered_set是基于哈希表实现的关联容器,与基于红黑树的std::mapstd::set相比,它们的插入、查找、删除操作的平均时间复杂度为 O (1),在大数据量场景下性能更优。但它们不保证元素的有序性,且占用内存略多。

代码语言:javascript
复制
#include <iostream>
#include <unordered_map>
#include <unordered_set>
using namespace std;

int main() {
    // std::unordered_map示例
    unordered_map<string, int> dict;

    // 插入元素
    dict["apple"] = 10;
    dict["banana"] = 20;
    dict.insert(make_pair("orange", 15));

    // 查找元素
    auto it = dict.find("apple");
    if (it != dict.end()) {
        cout << "apple的价格:" << it->second << endl;
    }

    // 遍历(无序)
    cout << "unordered_map遍历:" << endl;
    for (auto& pair : dict) {
        cout << pair.first << " : " << pair.second << endl;
    }

    // std::unordered_set示例
    unordered_set<int> uset = {1, 2, 3, 4, 5};

    // 插入元素(不允许重复)
    uset.insert(3);  // 插入失败,3已存在

    // 查找元素
    if (uset.count(4)) {
        cout << "4在unordered_set中" << endl;
    }

    // 遍历(无序)
    cout << "unordered_set遍历:";
    for (auto& val : uset) {
        cout << val << " ";
    }
    cout << endl;

    return 0;
}

2.2 现有容器的新接口

C++11 为现有容器(如vectorlistmap等)添加了大量新接口,主要包括移动语义相关接口、initializer_list构造函数、emplace系列接口等,大幅提升了容器的使用效率和灵活性。

2.2.1 移动语义相关接口

所有容器都新增了移动构造函数和移动赋值运算符重载,支持直接用右值(临时对象)初始化或赋值容器,避免了不必要的拷贝。

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
    vector<string> v1 = {"a", "b", "c"};

    // 移动构造:v2窃取v1的资源,v1变为空
    vector<string> v2 = move(v1);
    cout << "v2的大小:" << v2.size() << endl;  // 输出3
    cout << "v1的大小:" << v1.size() << endl;  // 输出0

    vector<string> v3 = {"d", "e"};
    // 移动赋值:v3窃取v2的资源,v2变为空
    v3 = move(v2);
    cout << "v3的大小:" << v3.size() << endl;  // 输出3
    cout << "v2的大小:" << v2.size() << endl;  // 输出0

    return 0;
}

此外,push_backinsert等插入接口也新增了右值引用版本,当插入临时对象时,会调用元素的移动构造,提升插入效率。

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
    vector<string> v;

    string s = "hello";
    v.push_back(s);  // 左值:调用拷贝构造
    v.push_back(move(s));  // 右值:调用移动构造
    v.push_back("world");  // 临时对象:调用移动构造

    for (auto& str : v) {
        cout << str << " ";
    }
    cout << endl;

    return 0;
}
2.2.2 initializer_list 构造函数

所有容器都新增了std::initializer_list类型的构造函数,支持直接用{}初始化容器,语法更简洁。

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;

int main() {
    // vector初始化
    vector<int> v = {1, 2, 3, 4, 5};
    // list初始化
    list<string> lst = {"apple", "banana", "orange"};
    // map初始化
    map<int, string> mp = {{1, "one"}, {2, "two"}, {3, "three"}};

    // 输出vector
    cout << "vector:";
    for (auto& val : v) {
        cout << val << " ";
    }
    cout << endl;

    // 输出list
    cout << "list:";
    for (auto& str : lst) {
        cout << str << " ";
    }
    cout << endl;

    // 输出map
    cout << "map:";
    for (auto& pair : mp) {
        cout << pair.first << ":" << pair.second << " ";
    }
    cout << endl;

    return 0;
}
2.2.3 emplace 系列接口

C++11 新增了emplace_backemplace等接口,它们支持直接在容器中构造元素,无需先创建临时对象,相比push_backinsert更高效。emplace系列接口基于可变参数模板实现,能够接收元素构造所需的参数,直接在容器的内存空间中构造元素。

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Person {
public:
    Person(const string& name, int age)
        : _name(name)
        , _age(age)
    {
        cout << "Person(const string& name, int age) -- 构造" << endl;
    }

    Person(string&& name, int age)
        : _name(move(name))
        , _age(age)
    {
        cout << "Person(string&& name, int age) -- 移动构造" << endl;
    }

private:
    string _name;
    int _age;
};

int main() {
    vector<Person> v;

    // push_back:先创建临时对象,再移动构造到容器中
    cout << "push_back临时对象:" << endl;
    v.push_back(Person("张三", 20));

    // emplace_back:直接在容器中构造对象,无临时对象
    cout << "\nemplace_back直接构造:" << endl;
    v.emplace_back("李四", 25);  // 传递构造参数,直接构造

    return 0;
}

运行结果:

代码语言:javascript
复制
push_back临时对象:
Person(const string& name, int age) -- 构造
Person(string&& name, int age) -- 移动构造

emplace_back直接构造:
Person(const string& name, int age) -- 构造

可以看到,emplace_back直接在容器中构造元素,避免了临时对象的创建和移动,效率更高。

2.3 其他实用变化

  • cbegin/cend 接口:返回const迭代器,确保不能通过迭代器修改容器元素,提高代码的安全性。
  • 范围 for 循环支持:所有容器都支持范围 for 循环,遍历代码更简洁。
  • 移动迭代器std::make_move_iterator可以将普通迭代器转换为移动迭代器,在拷贝容器元素时调用移动构造,提升效率。
代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
    vector<string> v1 = {"a", "b", "c"};
    vector<string> v2;

    // 使用移动迭代器,将v1的元素移动到v2
    v2.insert(v2.begin(), make_move_iterator(v1.begin()), make_move_iterator(v1.end()));

    cout << "v2的元素:";
    for (auto& str : v2) {
        cout << str << " ";
    }
    cout << endl;
    cout << "v1的大小:" << v1.size() << endl;  // v1变为空

    return 0;
}

三、lambda 表达式:简洁灵活的匿名函数

lambda 表达式是 C++11 引入的核心特性之一,它允许在函数内部定义匿名函数对象,无需单独声明函数或仿函数类,让代码更简洁、更具可读性。lambda 表达式广泛应用于算法、线程、回调函数等场景,大幅提升了编程效率。

3.1 lambda 表达式语法

lambda 表达式的核心语法格式如下:

代码语言:javascript
复制
[capture-list] (parameters) -> return-type { function-body }

各部分说明:

  • [capture-list](捕捉列表):用于捕捉函数外部的变量,供 lambda 函数体使用。捕捉列表是 lambda 的标志,不能为空(即使不捕捉任何变量,也需写[])。
  • (parameters)(参数列表):与普通函数的参数列表功能相同,用于接收外部传入的参数。若无需参数,可省略()(连同括号一起省略)。
  • -> return-type(返回值类型):指定 lambda 函数的返回值类型。若函数体中只有一条return语句,或无返回值,可省略该部分,编译器会自动推导返回值类型。
  • {function-body}(函数体):lambda 函数的实现逻辑,可使用参数列表中的参数和捕捉列表中的变量。函数体不能为空(即使无逻辑,也需写{})。
3.1.1 基础示例:lambda 的多种写法
代码语言:javascript
复制
#include <iostream>
using namespace std;

int main() {
    // 1. 完整写法:有参数、有返回值
    auto add = [](int x, int y) -> int {
        return x + y;
    };
    cout << "add(1, 2) = " << add(1, 2) << endl;  // 输出3

    // 2. 省略返回值类型(编译器自动推导)
    auto multiply = [](int x, int y) {
        return x * y;
    };
    cout << "multiply(3, 4) = " << multiply(3, 4) << endl;  // 输出12

    // 3. 无参数、无返回值
    auto printHello = [] {
        cout << "Hello, lambda!" << endl;
    };
    printHello();  // 输出Hello, lambda!

    // 4. 有参数、无返回值
    auto printNum = [](int num) {
        cout << "num = " << num << endl;
    };
    printNum(10);  // 输出num = 10

    return 0;
}

3.2 捕捉列表:灵活获取外部变量

捕捉列表是 lambda 表达式的核心特性之一,用于控制 lambda 函数对外部变量的访问权限。捕捉方式分为值捕捉、引用捕捉、隐式捕捉和混合捕捉,满足不同场景的需求。

3.2.1 捕捉方式详解

  1. 值捕捉([var1, var2, ...]):将外部变量的值拷贝到 lambda 内部,lambda 函数体中不能修改该变量(默认被const修饰)。
  2. 引用捕捉([&var1, &var2, ...]):将外部变量的引用传入 lambda,lambda 函数体中可以修改该变量,修改会影响外部变量。
  3. 隐式值捕捉([=]):自动捕捉 lambda 函数体中使用的所有外部变量,捕捉方式为值捕捉。
  4. 隐式引用捕捉([&]):自动捕捉 lambda 函数体中使用的所有外部变量,捕捉方式为引用捕捉。
  5. 混合捕捉([=, &var] 或 [&, var]):结合隐式捕捉和显式捕捉。[=, &var]表示其他变量隐式值捕捉,var显式引用捕捉;[&, var]表示其他变量隐式引用捕捉,var显式值捕捉。
  6. mutable 关键字:取消值捕捉变量的const限制,允许在 lambda 函数体中修改值捕捉的变量,但修改的是拷贝后的变量,不影响外部变量。使用mutable后,参数列表()不能省略(即使无参数)。
3.2.2 捕捉列表示例
代码语言:javascript
复制
#include <iostream>
using namespace std;

int g_val = 100;  // 全局变量

int main() {
    int a = 10, b = 20, c = 30, d = 40;
    static int s_val = 200;  // 静态局部变量

    // 1. 值捕捉:a、b的值拷贝到lambda内部
    auto func1 = [a, b] {
        // a++;  // 编译报错:值捕捉的变量默认是const的
        cout << "func1: a = " << a << ", b = " << b << endl;
    };
    func1();

    // 2. 引用捕捉:&c、&d是引用,修改会影响外部变量
    auto func2 = [&c, &d] {
        c++;
        d++;
        cout << "func2: c = " << c << ", d = " << d << endl;
    };
    func2();
    cout << "外部:c = " << c << ", d = " << d << endl;  // c=31, d=41

    // 3. 隐式值捕捉:自动捕捉使用的a、b
    auto func3 = [=] {
        cout << "func3: a = " << a << ", b = " << b << endl;
    };
    func3();

    // 4. 隐式引用捕捉:自动捕捉使用的c、d
    auto func4 = [&] {
        c++;
        d++;
        cout << "func4: c = " << c << ", d = " << d << endl;
    };
    func4();
    cout << "外部:c = " << c << ", d = " << d << endl;  // c=32, d=42

    // 5. 混合捕捉:[=, &a] 隐式值捕捉其他变量,显式引用捕捉a
    auto func5 = [=, &a] {
        a++;
        // b++;  // 编译报错:b是值捕捉,不能修改
        cout << "func5: a = " << a << ", b = " << b << endl;
    };
    func5();
    cout << "外部:a = " << a << endl;  // a=11

    // 6. mutable:取消值捕捉变量的const限制
    auto func6 = [a, b]() mutable {
        a++;
        b++;
        cout << "func6: a = " << a << ", b = " << b << endl;  // a=11, b=21
    };
    func6();
    cout << "外部:a = " << a << ", b = " << b << endl;  // a=11, b=20(外部变量未变)

    // 7. 全局变量和静态局部变量无需捕捉,可直接使用
    auto func7 = [] {
        g_val++;
        s_val++;
        cout << "func7: g_val = " << g_val << ", s_val = " << s_val << endl;
    };
    func7();

    return 0;
}
3.2.3 捕捉列表注意事项

  • 捕捉列表只能捕捉 lambda 定义之前的局部变量,不能捕捉定义之后的变量。
  • 全局变量、静态局部变量无需捕捉,可直接在 lambda 函数体中使用。
  • 若 lambda 定义在全局作用域,捕捉列表必须为空(无局部变量可捕捉)。
  • 混合捕捉时,第一个元素必须是=&,且[&]后只能跟值捕捉变量,[=]后只能跟引用捕捉变量。

3.3 lambda 的应用:简化代码的实用场景

lambda 表达式的最大价值在于简化代码,尤其适用于需要临时定义短小函数的场景。以下是几个典型应用场景:

3.3.1 结合 STL 算法使用

STL 算法(如sortfind_iffor_each等)通常需要传入函数指针或仿函数作为参数,使用 lambda 表达式可以直接在算法调用处定义逻辑,无需单独声明函数或仿函数类。

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;

// 商品类
struct Goods {
    string _name;    // 商品名称
    double _price;   // 价格
    int _evaluate;   // 评价数

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

int main() {
    vector<Goods> goods = {
        {"苹果", 2.1, 500},
        {"香蕉", 3.0, 300},
        {"橙子", 2.2, 400},
        {"菠萝", 1.5, 200}
    };

    // 1. 按价格升序排序
    sort(goods.begin(), goods.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price < g2._price;
    });
    cout << "按价格升序排序:" << endl;
    for (auto& g : goods) {
        cout << g._name << " 价格:" << g._price << endl;
    }

    // 2. 按评价数降序排序
    sort(goods.begin(), goods.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate > g2._evaluate;
    });
    cout << "\n按评价数降序排序:" << endl;
    for (auto& g : goods) {
        cout << g._name << " 评价数:" << g._evaluate << endl;
    }

    // 3. 查找价格小于2.5的商品
    auto it = find_if(goods.begin(), goods.end(), [](const Goods& g) {
        return g._price < 2.5;
    });
    if (it != goods.end()) {
        cout << "\n第一个价格小于2.5的商品:" << it->_name << endl;
    }

    // 4. 遍历并修改商品价格(涨价10%)
    for_each(goods.begin(), goods.end(), [](Goods& g) {
        g._price *= 1.1;
    });
    cout << "\n涨价10%后:" << endl;
    for (auto& g : goods) {
        cout << g._name << " 新价格:" << g._price << endl;
    }

    return 0;
}
3.3.2 线程函数定义

在多线程编程中,lambda 表达式可以直接作为线程的执行函数,无需单独定义线程函数,代码更简洁。

代码语言:javascript
复制
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;

int main() {
    int count = 0;

    // 线程1:计数+1,1秒一次
    thread t1([&count] {
        while (count < 5) {
            count++;
            cout << "t1: count = " << count << endl;
            this_thread::sleep_for(chrono::seconds(1));
        }
    });

    // 线程2:计数-1,1秒一次
    thread t2([&count] {
        while (count > 0) {
            count--;
            cout << "t2: count = " << count << endl;
            this_thread::sleep_for(chrono::seconds(1));
        }
    });

    t1.join();
    t2.join();

    return 0;
}
3.3.3 自定义排序规则

在需要自定义排序的场景中,lambda 表达式可以快速定义排序逻辑,相比仿函数更灵活。

代码语言:javascript
复制
#include <iostream>
#include <set>
#include <string>
using namespace std;

int main() {
    // 自定义set的排序规则:按字符串长度降序
    set<string, function<bool(const string&, const string&)>> s(
        [](const string& s1, const string& s2) {
            return s1.size() > s2.size();
        }
    );

    s.insert("apple");
    s.insert("banana");
    s.insert("orange");
    s.insert("pear");

    cout << "按字符串长度降序排列:" << endl;
    for (auto& str : s) {
        cout << str << "(长度:" << str.size() << ")" << endl;
    }

    return 0;
}

3.4 lambda 的原理:底层是仿函数

lambda 表达式的本质是编译器自动生成的一个匿名仿函数类(也叫函数对象)。当我们定义一个 lambda 表达式时,编译器会:

  1. 生成一个唯一的仿函数类(类名由编译器自动生成,如main::lambda_1)。
  2. lambda 的捕捉列表会成为该仿函数类的成员变量。
  3. lambda 的参数列表、返回值类型、函数体会成为该仿函数类的operator()成员函数的参数、返回值类型、函数体。
  4. 当调用 lambda 表达式时,本质是调用该仿函数类的operator()成员函数。
3.4.1 原理验证:lambda 与仿函数的对比
代码语言:javascript
复制
#include <iostream>
using namespace std;

// 仿函数类
class Rate {
public:
    Rate(double rate) : _rate(rate) {}

    double operator()(double money, int year) const {
        return money * _rate * year;
    }

private:
    double _rate;
};

int main() {
    double rate = 0.049;

    // 1. 使用仿函数
    Rate r1(rate);
    double interest1 = r1(10000, 2);
    cout << "仿函数计算利息:" << interest1 << endl;

    // 2. 使用lambda表达式
    auto r2 = [rate](double money, int year) {
        return money * rate * year;
    };
    double interest2 = r2(10000, 2);
    cout << "lambda计算利息:" << interest2 << endl;

    return 0;
}

从功能上看,lambda 表达式r2和仿函数r1完全等价。通过反汇编可以看到,lambda 表达式的底层实现与仿函数一致:

  • 捕捉列表中的rate被作为 lambda 生成的仿函数类的成员变量。
  • lambda 的调用本质是调用仿函数类的operator()
3.4.2 mutable 关键字的底层实现

当 lambda 使用mutable关键字时,本质是取消了仿函数类operator()const修饰,允许修改成员变量(值捕捉的拷贝)。

代码语言:javascript
复制
// lambda表达式
auto func = [a]() mutable { a++; };

// 底层生成的仿函数类(简化)
class LambdaClass {
public:
    LambdaClass(int a) : _a(a) {}

    void operator()() {  // 无const修饰,允许修改_a
        _a++;
    }

private:
    int _a;
};

若不使用mutableoperator()会被const修饰,无法修改成员变量:

代码语言:javascript
复制
class LambdaClass {
public:
    LambdaClass(int a) : _a(a) {}

    void operator()() const {  // const修饰,不能修改_a
        // _a++;  // 编译报错
    }

private:
    int _a;
};

四、包装器:统一可调用对象的接口

C++ 中的可调用对象包括函数指针、仿函数、lambda 表达式、类成员函数等,它们的类型各不相同,使用起来不够统一。于是C++11 引入的包装器(std::functionstd::bind)解决了这一问题,能够将不同类型的可调用对象包装成统一的类型,方便存储、传递和使用。

4.1 std::function:可调用对象的统一包装

std::function是一个类模板,定义在<functional>头文件中,用于包装各种可调用对象,包括函数指针、仿函数、lambda 表达式、类成员函数等。它的核心作用是统一可调用对象的类型,让不同类型的可调用对象可以用相同的方式存储和调用

4.1.1 std::function 的语法
代码语言:javascript
复制
template <class Ret, class... Args>
class function<Ret(Args...)>;

  • Ret:可调用对象的返回值类型。
  • Args...:可调用对象的参数类型列表。
4.1.2 包装各种可调用对象
代码语言:javascript
复制
#include <iostream>
#include <functional>
#include <string>
using namespace std;

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

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

// 3. 类成员函数
class Calculator {
public:
    // 普通成员函数(隐含this指针)
    int Multiply(int a, int b) {
        return a * b;
    }

    // 静态成员函数(无this指针)
    static int Divide(int a, int b) {
        return a / b;
    }
};

int main() {
    // 包装普通函数
    function<int(int, int)> f1 = Add;
    cout << "Add(10, 5) = " << f1(10, 5) << endl;  // 输出15

    // 包装仿函数
    function<int(int, int)> f2 = Subtract();
    cout << "Subtract(10, 5) = " << f2(10, 5) << endl;  // 输出5

    // 包装lambda表达式
    function<int(int, int)> f3 = [](int a, int b) {
        return a % b;
    };
    cout << "Mod(10, 5) = " << f3(10, 5) << endl;  // 输出0

    // 包装静态成员函数
    function<int(int, int)> f4 = &Calculator::Divide;
    cout << "Divide(10, 5) = " << f4(10, 5) << endl;  // 输出2

    // 包装普通成员函数(需绑定对象或对象指针)
    Calculator calc;
    function<int(Calculator&, int, int)> f5 = &Calculator::Multiply;
    cout << "Multiply(10, 5) = " << f5(calc, 10, 5) << endl;  // 输出50

    function<int(Calculator*, int, int)> f6 = &Calculator::Multiply;
    cout << "Multiply(10, 5) = " << f6(&calc, 10, 5) << endl;  // 输出50

    return 0;
}
4.1.3 std::function 的应用场景

(1)存储可调用对象:将不同类型的可调用对象存储在容器中,统一管理。

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <functional>
using namespace std;

int Add(int a, int b) { return a + b; }
int Subtract(int a, int b) { return a - b; }

int main() {
    vector<function<int(int, int)>> funcs;
    funcs.push_back(Add);
    funcs.push_back(Subtract);
    funcs.push_back([](int a, int b) { return a * b; });

    int x = 10, y = 5;
    for (auto& func : funcs) {
        cout << func(x, y) << endl;
    }

    return 0;
}

(2)实现回调函数:将std::function作为函数参数,实现灵活的回调机制。

代码语言:javascript
复制
#include <iostream>
#include <functional>
using namespace std;

// 回调函数类型:void(int)
using Callback = function<void(int)>;

// 模拟异步任务:执行完成后调用回调函数
void AsyncTask(int taskId, Callback callback) {
    cout << "执行任务 " << taskId << endl;
    // 任务执行完成,调用回调
    callback(taskId);
}

int main() {
    // 注册回调函数(lambda表达式)
    AsyncTask(1, [](int taskId) {
        cout << "任务 " << taskId << " 执行完成!" << endl;
    });

    // 注册另一个回调函数
    AsyncTask(2, [](int taskId) {
        cout << "回调:任务 " << taskId << " 处理成功!" << endl;
    });

    return 0;
}

(3)实现策略模式:通过std::function封装不同的策略,动态切换算法。

代码语言:javascript
复制
#include <iostream>
#include <functional>
#include <string>
using namespace std;

class Payment {
public:
    // 构造函数:传入支付策略
    Payment(function<bool(const string&, double)> payStrategy)
        : _payStrategy(payStrategy)
    {}

    // 支付接口
    bool Pay(const string& orderId, double amount) {
        cout << "订单 " << orderId << " 发起支付,金额:" << amount << endl;
        return _payStrategy(orderId, amount);
    }

private:
    function<bool(const string&, double)> _payStrategy;
};

// 支付宝支付策略
bool AlipayPay(const string& orderId, double amount) {
    cout << "支付宝支付 " << amount << " 元成功" << endl;
    return true;
}

// 微信支付策略
bool WechatPay(const string& orderId, double amount) {
    cout << "微信支付 " << amount << " 元成功" << endl;
    return true;
}

int main() {
    // 支付宝支付
    Payment alipayPay(AlipayPay);
    alipayPay.Pay("ORDER001", 99.0);

    // 微信支付
    Payment wechatPay(WechatPay);
    wechatPay.Pay("ORDER002", 199.0);

    // 银联支付(lambda表达式)
    Payment unionPay([](const string& orderId, double amount) {
        cout << "银联支付 " << amount << " 元成功" << endl;
        return true;
    });
    unionPay.Pay("ORDER003", 299.0);

    return 0;
}

4.2 std::bind:可调用对象的适配器

std::bind是一个函数模板,同样定义在<functional>头文件中,用于对可调用对象进行包装和适配,主要功能包括:

  • 调整可调用对象的参数个数(绑定部分参数,固定为某个值)。
  • 调整可调用对象的参数顺序

std::bind的返回值是一个新的可调用对象,可直接调用或用std::function包装。

4.2.1 std::bind 的基本用法
代码语言:javascript
复制
#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;  // 包含占位符 _1, _2, ...

// 普通函数:计算(a - b) * 10
int Sub(int a, int b) {
    return (a - b) * 10;
}

// 三参数函数:计算(a - b - c) * 10
int Sub3(int a, int b, int c) {
    return (a - b - c) * 10;
}

int main() {
    // 1. 绑定部分参数:固定a=100,只保留b作为参数(_1表示第一个参数)
    auto sub1 = bind(Sub, 100, _1);
    cout << "sub1(5) = (100 - 5) * 10 = " << sub1(5) << endl;  // 输出950

    // 2. 调整参数顺序:交换a和b的位置
    auto sub2 = bind(Sub, _2, _1);
    cout << "sub2(10, 5) = (5 - 10) * 10 = " << sub2(10, 5) << endl;  // 输出-50

    // 3. 绑定三参数函数的部分参数:固定b=10,保留a和c(_1和_2)
    auto sub3 = bind(Sub3, _1, 10, _2);
    cout << "sub3(20, 3) = (20 - 10 - 3) * 10 = " << sub3(20, 3) << endl;  // 输出70

    // 4. 绑定所有参数:将可调用对象转为无参函数
    auto sub4 = bind(Sub, 20, 8);
    cout << "sub4() = (20 - 8) * 10 = " << sub4() << endl;  // 输出120

    return 0;
}
4.2.2 绑定类成员函数

类成员函数包含隐含的this指针,使用std::bind绑定的时,需要显式传入对象或对象指针作为第一个参数。

代码语言:javascript
复制
#include <iostream>
#include <functional>
#include <string>
using namespace std;
using namespace placeholders;

class Person {
public:
    Person(const string& name, int age)
        : _name(name)
        , _age(age)
    {}

    void ShowInfo(const string& prefix) {
        cout << prefix << ":姓名:" << _name << ",年龄:" << _age << endl;
    }

private:
    string _name;
    int _age;
};

int main() {
    Person p("张三", 20);

    // 绑定普通成员函数:传入对象引用
    auto show1 = bind(&Person::ShowInfo, ref(p), _1);
    show1("绑定对象引用");  // 输出:绑定对象引用:姓名:张三,年龄:20

    // 绑定普通成员函数:传入对象指针
    auto show2 = bind(&Person::ShowInfo, &p, _1);
    show2("绑定对象指针");  // 输出:绑定对象指针:姓名:张三,年龄:20

    // 绑定并固定prefix参数
    auto show3 = bind(&Person::ShowInfo, &p, "固定前缀");
    show3();  // 输出:固定前缀:姓名:张三,年龄:20

    return 0;
}
4.2.3 std::bind 的应用场景

(1)适配函数参数:当调用的函数参数个数或顺序与现有接口不匹配时,用std::bind进行适配。

代码语言:javascript
复制
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
using namespace std;
using namespace placeholders;

// 现有函数:判断x是否大于y
bool IsGreater(int x, int y) {
    return x > y;
}

int main() {
    vector<int> v = {1, 3, 5, 7, 9, 2, 4, 6, 8};

    // 需求:统计大于5的元素个数
    // count_if需要的是单参数函数:bool(int),用bind适配IsGreater
    int count = count_if(v.begin(), v.end(), bind(IsGreater, _1, 5));
    cout << "大于5的元素个数:" << count << endl;  // 输出4

    return 0;
}

(2)固定默认参数:将函数的某些参数固定为默认值,生成新的函数接口。

代码语言:javascript
复制
#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

// 计算复利:利息 = 本金 * 利率 * 年数
double CompoundInterest(double principal, double rate, int years) {
    double amount = principal;
    for (int i = 0; i < years; ++i) {
        amount *= (1 + rate);
    }
    return amount - principal;
}

int main() {
    // 固定利率为1.5%,生成不同年数的复利计算函数
    auto Interest3Years = bind(CompoundInterest, _1, 0.015, 3);
    auto Interest5Years = bind(CompoundInterest, _1, 0.015, 5);

    // 计算100万本金的复利
    cout << "100万本金,3年复利(1.5%):" << Interest3Years(1000000) << endl;
    cout << "100万本金,5年复利(1.5%):" << Interest5Years(1000000) << endl;

    return 0;
}

(3)结合 std::function 实现灵活调用:将std::bind的结果用std::function包装,方便存储和传递。

代码语言:javascript
复制
#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

int Add(int a, int b) { return a + b; }

int main() {
    function<int(int)> add10 = bind(Add, _1, 10);  // 固定第二个参数为10
    function<int(int)> add20 = bind(Add, 20, _1);  // 固定第一个参数为20

    cout << "add10(5) = " << add10(5) << endl;  // 输出15
    cout << "add20(5) = " << add20(5) << endl;  // 输出25

    return 0;
}

总结

掌握上面这些 C++11 核心特性,能够帮助开发者编写更高效、更简洁、更健壮的 C++ 代码,适应现代 C++ 的编程需求。无论是日常开发还是面试求职,这些特性都是必备的知识技能。希望本文的详细解析能够帮助你全面掌握这些特性,在 C++ 的学习和实践中更上一层楼。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、新的类功能:让类设计更灵活高效
    • 1.1 默认的移动构造和移动赋值
      • 1.1.1 默认移动成员函数的生成规则
      • 1.1.2 代码示例:默认移动语义的使用
      • 1.1.3 移动语义的核心价值
    • 1.2 成员变量声明时给缺省值
      • 1.2.1 代码示例:成员变量缺省值的使用
      • 1.2.2 特性优势
    • 1.3 default 和 delete:精细控制默认函数
      • 1.3.1 default 关键字:显式要求生成默认函数
      • 1.3.2 delete 关键字:禁止生成默认函数
      • 1.3.3 应用场景
    • 1.4 final 与 override:完善继承体系的类型控制
      • 1.4.1 final 关键字:禁止继承或重写
      • 1.4.2 override 关键字:检查虚函数重写的正确性
      • 1.4.3 特性价值
  • 二、STL 中的一些变化:更高效、更易用的标准库
    • 2.1 新增容器
      • 2.1.1 std::array:固定大小的数组容器
      • 2.1.2 std::forward_list:单向链表
      • 2.1.3 std::unordered_map/std::unordered_set:哈希容器
    • 2.2 现有容器的新接口
      • 2.2.1 移动语义相关接口
      • 2.2.2 initializer_list 构造函数
      • 2.2.3 emplace 系列接口
    • 2.3 其他实用变化
  • 三、lambda 表达式:简洁灵活的匿名函数
    • 3.1 lambda 表达式语法
      • 3.1.1 基础示例:lambda 的多种写法
    • 3.2 捕捉列表:灵活获取外部变量
      • 3.2.1 捕捉方式详解
      • 3.2.2 捕捉列表示例
      • 3.2.3 捕捉列表注意事项
    • 3.3 lambda 的应用:简化代码的实用场景
      • 3.3.1 结合 STL 算法使用
      • 3.3.2 线程函数定义
      • 3.3.3 自定义排序规则
    • 3.4 lambda 的原理:底层是仿函数
      • 3.4.1 原理验证:lambda 与仿函数的对比
      • 3.4.2 mutable 关键字的底层实现
  • 四、包装器:统一可调用对象的接口
    • 4.1 std::function:可调用对象的统一包装
      • 4.1.1 std::function 的语法
      • 4.1.2 包装各种可调用对象
      • 4.1.3 std::function 的应用场景
    • 4.2 std::bind:可调用对象的适配器
      • 4.2.1 std::bind 的基本用法
      • 4.2.2 绑定类成员函数
      • 4.2.3 std::bind 的应用场景
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档