【C++】C++11(下)

本文是小编巩固自身而作,如有错误,欢迎指出!

目录

一、可变参数模版

(1)基本语法

(2)参数包的展开

二、emplace接口

三、lambda

(1)lambda的基本语法

(2)lambda的示例

[示例 1:基本用法,无捕获,无参数](#示例 1:基本用法,无捕获,无参数)

[示例 2:捕获外部变量](#示例 2:捕获外部变量)

[示例 3:作为函数参数(最常见场景)](#示例 3:作为函数参数(最常见场景))

[示例 4:在类成员函数中捕获 this](#示例 4:在类成员函数中捕获 this)

四、包装器

(1)function

基本语法:

使用示例:

[示例 1:包装普通函数](#示例 1:包装普通函数)

[示例 2:包装 Lambda 表达式](#示例 2:包装 Lambda 表达式)

[示例 3:包装成员函数](#示例 3:包装成员函数)

(2)bind

核心用途

基本语法

占位符

使用示例

[示例 1:固定参数](#示例 1:固定参数)

[示例 2:调整参数顺序](#示例 2:调整参数顺序)

[示例 3:绑定成员函数](#示例 3:绑定成员函数)


一、可变参数模版

什么是可变参数模版?

可变就是可以不同。可变参数,即:参数类型可变,参数个数可变。

(1)基本语法

一个可变参数模版的定义看起来就是像这样的

cpp 复制代码
template <typename... Args>
void my_function(Args... args) {
    // ...
}

我们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或 typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板 ⼀样,每个参数实例化时遵循引⽤折叠规则。

我们可以像下列一样调用它

cpp 复制代码
my_function();                  // 0 个参数
my_function(10);                // 1 个参数,类型 int
my_function(10, "hello");       // 2 个参数,类型 int 和 const char*
my_function(3.14, 'a', true);   // 3 个参数,类型 double, char, bool

(2)参数包的展开

仅仅定义一个可变参数模板是不够的,你需要一种方式来 "解包" 这些参数,对它们中的每一个进行操作。这个过程称为参数包展开

想要将一个包展开,可以像展开打印数组一样去打印吗?还是直接打印?很明显他们的底层逻辑是不一样的如果像下面一样打印,那就大错特错了

错误演示:

cpp 复制代码
template<class ...Args>
void Print(Args...args)
{
	cout << args... << endl;
}

那么要正确的展开包要怎么做呢?

cpp 复制代码
#include <iostream>

// 1. 递归终止函数(处理 0 个参数的情况)
void print() {
    std::cout << "--------------------------" << std::endl;
}

// 2. 可变参数递归函数
template <typename T, typename... Args>
void print(T first_arg, Args... remaining_args) {
    // 打印第一个参数
    std::cout << first_arg << std::endl;
    
    // 递归调用,将剩余的参数包传递下去
    // 每次递归,参数包都会"剥离"掉第一个参数
    print(remaining_args...);
}

int main() {
    print(10, 3.14, "Hello", 'A');
    return 0;
}

工作原理:

  1. print(10, 3.14, "Hello", 'A') 调用 print<int, double, const char*, char>(10, 3.14, "Hello", 'A')
  2. 打印 10,然后递归调用 print(3.14, "Hello", 'A')
  3. print(3.14, "Hello", 'A') 调用 print<double, const char*, char>(3.14, "Hello", 'A')
  4. 打印 3.14,然后递归调用 print("Hello", 'A')
  5. 这个过程继续,直到最后调用 print(),此时匹配到递归终止函数,打印分隔符,递归结束。

二、emplace接口

emplace 是 C++11 引入的一组容器成员函数,它们提供了一种更高效、更灵活的方式来在容器中构造元素,而不是先创建对象再拷贝或移动到容器中。

简单来说,empalce接口,更像是我们之前学习的push_back和insert的进阶版,像我们常规的push_back还需要现在main函数栈帧上创建一个对象然后进行尾插,而empacle可以直接不用创建新对象,直接在尾部将其插入。

容器会在其内部的存储空间中直接构造该对象,从而避免了中间对象的创建和拷贝 / 移动操作。

cpp 复制代码
#include <map>
#include <string>
#include <iostream>

struct Value {
    std::string data;
    Value(std::string d) : data(std::move(d)) {
        std::cout << "Value Constructor: " << data << std::endl;
    }
    Value(const Value& other) : data(other.data) {
        std::cout << "Value Copy Constructor: " << data << std::endl;
    }
};

int main() {
    std::map<int, Value> my_map;

    std::cout << "--- Using emplace on map ---" << std::endl;
    // emplace 会将参数完美转发给 std::pair 的构造函数
    // std::pair 的构造函数在这里会用 10 作为 key,并用 "apple" 构造 Value 对象
    my_map.emplace(10, "apple"); 

    std::cout << "\n--- Using insert on map (for comparison) ---" << std::endl;
    // insert 需要一个已经构造好的 pair 对象
    my_map.insert(std::make_pair(20, Value("banana")));

    return 0;
}

emplace_back 的优势:

  1. 性能提升:避免了临时对象的创建和销毁,以及一次拷贝或移动操作。对于构造成本高的对象,这能带来显著的性能提升。
  2. 代码简洁 :当你需要插入一个新构造的对象时,emplace_back 的写法更直接。

三、lambda

Lambda 表达式是 C++11 引入的一项革命性特性,它允许你在需要一个函数的地方就地定义一个匿名函数。

(1)lambda的基本语法

cpp 复制代码
[capture-clause] (parameters) mutable exception-specification -> return-type { body }
  1. [capture-clause] (捕获子句):

    • 这是 Lambda 表达式与普通函数最核心的区别。它定义了 Lambda 如何从其封闭作用域(即定义它的函数或代码块)中捕获变量,以便在 Lambda 体内部使用。
    • [] 表示不捕获任何变量。
    • [=] 表示按值捕获所有在 Lambda 体中使用的外部变量。
    • [&] 表示按引用捕获所有在 Lambda 体中使用的外部变量。
    • [var] 表示按值捕获特定变量 var
    • [&var] 表示按引用捕获特定变量 var
    • [this] (在类成员函数中) 表示捕获当前对象的指针,允许访问类的成员。
    • 可以组合使用,例如 [=, &var] (大部分按值,var 按引用) 或 [&, var] (大部分按引用,var 按值)。
  2. (parameters) (参数列表):

    • 与普通函数的参数列表类似。如果没有参数,可以省略括号 ()
    • 支持自动类型推导(在 C++14 及以后),例如 (auto x, auto y)
  3. mutable (可选):

    • 默认情况下,按值捕获的变量在 Lambda 体内部是 const 的,不能被修改。
    • 如果使用了 mutable 关键字,Lambda 体就可以修改按值捕获的变量(注意:这只修改 Lambda 内部的副本,不影响外部变量)。
  4. exception-specification (可选):

    • 例如 noexcept,用于指定 Lambda 是否会抛出异常。
  5. -> return-type (返回类型 trailing-return-type) (可选):

    • 如果 Lambda 体只包含一个 return 语句,编译器可以自动推导返回类型,此时可以省略返回类型。
    • 否则,必须显式指定返回类型。
  6. { body } (函数体):

    • 包含 Lambda 表达式的执行代码,与普通函数体相同。

(2)lambda的示例

示例 1:基本用法,无捕获,无参数

cpp 复制代码
#include <iostream>

int main() {
    // 定义并立即调用一个 Lambda
    [] {
        std::cout << "Hello, World!" << std::endl;
    }(); // 注意末尾的 ()

    return 0;
}

示例 2:捕获外部变量

cpp 复制代码
#include <iostream>

int main() {
    int x = 10;
    int y = 20;

    // 按值捕获 x 和 y
    auto add = [x, y]() {
        return x + y;
    };
    std::cout << "Sum: " << add() << std::endl; // 输出 30

    // 按引用捕获 x
    auto increment = [&x]() {
        x++;
    };
    increment();
    std::cout << "x after increment: " << x << std::endl; // 输出 11

    // 按值捕获 y,并尝试修改 (需要 mutable)
    auto try_modify_y = [y]() mutable {
        y = 100; // 只修改副本
        std::cout << "y inside lambda: " << y << std::endl; // 输出 100
    };
    try_modify_y();
    std::cout << "y outside lambda: " << y << std::endl; // 仍然是 20

    return 0;
}

示例 3:作为函数参数(最常见场景)

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6};

    // 使用 Lambda 作为 std::sort 的比较函数
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b; // 降序排序
    });

    std::cout << "Sorted in descending order: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

   
}

示例 4:在类成员函数中捕获 this

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

class Greeter {
private:
    std::string message;
public:
    Greeter(std::string msg) : message(std::move(msg)) {}

    void greet() {
        // Lambda 捕获 this 指针,从而可以访问 message
        auto print_message = [this]() {
            std::cout << this->message << std::endl;
            // 或者直接 std::cout << message << std::endl;
        };
        print_message();
    }
};

int main() {
    Greeter greeter("Hello from Greeter class!");
    greeter.greet(); // 输出 "Hello from Greeter class!"
    return 0;
}

Lambda 表达式的优点:

  1. 代码简洁:在需要一个小函数的地方,可以直接定义,避免了编写独立的命名函数。
  2. 就地定义:逻辑与使用地点紧密结合,提高了代码的可读性和维护性。
  3. 捕获机制灵活:可以方便地访问外部作用域的变量。
  4. 非常适合与 STL 算法配合 :极大地简化了使用 std::sort, std::find_if, std::transform 等算法的代码。

四、包装器

(1)function

std::function 是一个通用的多态函数包装器。它可以存储、复制和调用任何可调用对象(Callable Object),例如:

  • 普通函数
  • Lambda 表达式
  • 函数指针
  • 成员函数指针
  • 函数对象(Functor,即重载了 operator() 的类对象)

基本语法:

cpp 复制代码
#include <functional>

std::function<返回类型(参数类型1, 参数类型2, ...)> func;

使用示例:

示例 1:包装普通函数
cpp 复制代码
#include <iostream>
#include <functional>

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

int main() {
    // 包装普通函数
    std::function<int(int, int)> func = add;
    
    // 调用
    std::cout << func(3, 5) << std::endl; // 输出 8
    
    return 0;
}
示例 2:包装 Lambda 表达式
cpp 复制代码
#include <iostream>
#include <functional>

int main() {
    // 包装 Lambda(捕获外部变量)
    int x = 10;
    std::function<int(int)> func = [x](int y) {
        return x + y;
    };
    
    std::cout << func(5) << std::endl; // 输出 15
    
    return 0;
}
示例 3:包装成员函数
cpp 复制代码
#include <iostream>
#include <functional>
#include <string>

class Person {
public:
    void say_hello(const std::string& name) {
        std::cout << "Hello, " << name << "!" << std::endl;
    }
};

int main() {
    Person person;
    // 包装成员函数(需要绑定对象实例)
    std::function<void(const std::string&)> func = std::bind(&Person::say_hello, &person, std::placeholders::_1);
    
    func("Alice"); // 输出 "Hello, Alice!"
    
    return 0;
}

(2)bind

std::bind 是一个参数绑定器,用于将函数的部分参数提前绑定到具体的值或占位符,生成一个新的可调用对象。

核心用途

  • 固定参数:将函数的某些参数固定为特定值,减少调用时需要传递的参数数量。
  • 调整参数顺序:通过占位符重新排列函数参数的顺序。
  • 绑定成员函数:将成员函数与对象实例绑定,生成一个无成员函数指针。

基本语法

cpp 复制代码
#include <functional>

auto bound_func = std::bind(函数对象, 绑定参数...);

占位符

  • std::placeholders::_1, _2, ..., _N:表示绑定后的函数被调用时,这些位置的参数将被传递给原始函数。

使用示例

示例 1:固定参数
cpp 复制代码
#include <iostream>
#include <functional>

int add(int a, int b, int c) {
    return a + b + c;
}

int main() {
    // 绑定前两个参数为 10 和 20,第三个参数用 _1 占位
    auto bound_add = std::bind(add, 10, 20, std::placeholders::_1);
    
    // 调用时只需要传递第三个参数
    std::cout << bound_add(30) << std::endl; // 10 + 20 + 30 = 60
    
    return 0;
}
示例 2:调整参数顺序
cpp 复制代码
#include <iostream>
#include <functional>

void print(int a, int b) {
    std::cout << "a: " << a << ", b: " << b << std::endl;
}

int main() {
    // 交换参数顺序(_1 对应原函数的第二个参数,_2 对应原函数的第一个参数)
    auto reversed_print = std::bind(print, std::placeholders::_2, std::placeholders::_1);
    
    reversed_print(10, 20); // 输出 "a: 20, b: 10"
    
    return 0;
}
示例 3:绑定成员函数
cpp 复制代码
#include <iostream>
#include <functional>
#include <string>

class Person {
public:
    void say_hello(const std::string& name) {
        std::cout << "Hello, " << name << "!" << std::endl;
    }
};

int main() {
    Person person;
    // 绑定成员函数和对象实例,参数用 _1 占位
    auto bound_say_hello = std::bind(&Person::say_hello, &person, std::placeholders::_1);
    
    bound_say_hello("Bob"); // 输出 "Hello, Bob!"
    
    return 0;
}

本次分享就到这里结束了,后续会继续更新,感谢阅读!

相关推荐
青衫码上行24 分钟前
【Java Web学习 | 第15篇】jQuery(万字长文警告)
java·开发语言·前端·学习·jquery
胡萝卜3.06 小时前
掌握C++ map:高效键值对操作指南
开发语言·数据结构·c++·人工智能·map
电子_咸鱼6 小时前
【STL string 全解析:接口详解、测试实战与模拟实现】
开发语言·c++·vscode·python·算法·leetcode
沐知全栈开发7 小时前
ionic 选项卡栏操作详解
开发语言
曹牧7 小时前
C#中,#region和#endregion
开发语言·c#
顾安r7 小时前
11.22 脚本打包APP 排错指南
linux·服务器·开发语言·前端·flask
蒙小萌19938 小时前
Swift UIKit MVVM + RxSwift Development Rules
开发语言·prompt·swift·rxswift
Z***25808 小时前
Java爬虫框架
java·开发语言·爬虫
hateregiste8 小时前
C语言中如何优雅、准确、高效地设计和处理输入输出
c语言·开发语言·scanf·输入输出