本文是小编巩固自身而作,如有错误,欢迎指出!
目录
[示例 1:基本用法,无捕获,无参数](#示例 1:基本用法,无捕获,无参数)
[示例 2:捕获外部变量](#示例 2:捕获外部变量)
[示例 3:作为函数参数(最常见场景)](#示例 3:作为函数参数(最常见场景))
[示例 4:在类成员函数中捕获 this](#示例 4:在类成员函数中捕获 this)
[示例 1:包装普通函数](#示例 1:包装普通函数)
[示例 2:包装 Lambda 表达式](#示例 2:包装 Lambda 表达式)
[示例 3:包装成员函数](#示例 3:包装成员函数)
[示例 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;
}
工作原理:
print(10, 3.14, "Hello", 'A')调用print<int, double, const char*, char>(10, 3.14, "Hello", 'A')。- 打印
10,然后递归调用print(3.14, "Hello", 'A')。 print(3.14, "Hello", 'A')调用print<double, const char*, char>(3.14, "Hello", 'A')。- 打印
3.14,然后递归调用print("Hello", 'A')。 - 这个过程继续,直到最后调用
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 的优势:
- 性能提升:避免了临时对象的创建和销毁,以及一次拷贝或移动操作。对于构造成本高的对象,这能带来显著的性能提升。
- 代码简洁 :当你需要插入一个新构造的对象时,
emplace_back的写法更直接。

三、lambda
Lambda 表达式是 C++11 引入的一项革命性特性,它允许你在需要一个函数的地方就地定义一个匿名函数。
(1)lambda的基本语法
cpp
[capture-clause] (parameters) mutable exception-specification -> return-type { body }
[capture-clause](捕获子句):
- 这是 Lambda 表达式与普通函数最核心的区别。它定义了 Lambda 如何从其封闭作用域(即定义它的函数或代码块)中捕获变量,以便在 Lambda 体内部使用。
[]表示不捕获任何变量。[=]表示按值捕获所有在 Lambda 体中使用的外部变量。[&]表示按引用捕获所有在 Lambda 体中使用的外部变量。[var]表示按值捕获特定变量var。[&var]表示按引用捕获特定变量var。[this](在类成员函数中) 表示捕获当前对象的指针,允许访问类的成员。- 可以组合使用,例如
[=, &var](大部分按值,var按引用) 或[&, var](大部分按引用,var按值)。
(parameters)(参数列表):
- 与普通函数的参数列表类似。如果没有参数,可以省略括号
()。- 支持自动类型推导(在 C++14 及以后),例如
(auto x, auto y)。
mutable(可选):
- 默认情况下,按值捕获的变量在 Lambda 体内部是
const的,不能被修改。- 如果使用了
mutable关键字,Lambda 体就可以修改按值捕获的变量(注意:这只修改 Lambda 内部的副本,不影响外部变量)。
exception-specification(可选):
- 例如
noexcept,用于指定 Lambda 是否会抛出异常。
-> return-type(返回类型 trailing-return-type) (可选):
- 如果 Lambda 体只包含一个
return语句,编译器可以自动推导返回类型,此时可以省略返回类型。- 否则,必须显式指定返回类型。
{ 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 表达式的优点:
- 代码简洁:在需要一个小函数的地方,可以直接定义,避免了编写独立的命名函数。
- 就地定义:逻辑与使用地点紧密结合,提高了代码的可读性和维护性。
- 捕获机制灵活:可以方便地访问外部作用域的变量。
- 非常适合与 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;
}
本次分享就到这里结束了,后续会继续更新,感谢阅读!