一、匿名函数lambda
重点: 怎么传递参数。 传引用还是传
1. 匿名函数的基本语法
[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {// 函数体
}
语法规则:lambda表达式可以看成是一般函数的函数名被略去,返回值使用了一个 -> 的形式表示。唯 一与普通函数不同的是增加了“捕获列表”。
//[捕获列表](参数列表)->返回类型{函数体}int main(){auto Add = [](int a, int b)->int {return a + b;};std::cout << Add(1, 2) << std::endl; return 0;}
一般情况下,编译器可以自动推断出lambda表达式的返回类型,所以我们可以不指定返回类型,即:
//[捕获列表](参数列表){函数体}int main(){auto Add = [](int a, int b) {return a + b;};std::cout << Add(1, 2) << std::endl; return 0;//输出3}
但是如果函数体内有多个return语句时,编译器无法自动推断出返回类型,此时必须指定返回类型。
2. 捕获列表
有时候,需要在匿名函数内使用外部变量,所以用捕获列表来传递参数。根据传递参数的行为,捕获列 表可分为以下几种:
2.1 值捕获
void test3(){cout << "test3" << endl;int c = 12;int d = 30;auto Add = [c, d](int a, int b)->int {cout << "d = " << d << endl;return c;};d = 20;std::cout << Add(1, 2) << std::endl;}
代码行为分析
在
test3
函数中:
- 首先定义了两个变量
c = 12
和d = 30
。- 创建 lambda 表达式
Add
,通过值捕获([c, d]
)将c
和d
拷贝到 lambda 内部。- 修改外部变量
d
的值为 20(此时 lambda 已经创建完成)。- 调用
Add(1, 2)
,观察 lambda 内部的d
和返回的c
。关键现象
运行代码时,输出结果会是
test3 d = 30 // lambda 内部的 d 是创建时的 30(拷贝值) 12 // 返回的 c 是创建时的 12(拷贝值)
现象的本质:值捕获的时机
- 创建时拷贝:当 lambda 表达式
Add
被定义(创建)时,会立即将当前的c
(12)和d
(30)的值拷贝到 lambda 的闭包(closure)中。此时,lambda 内部的c
和d
是独立于外部变量的拷贝。- 调用时不更新:后续即使外部变量
d
被修改为 20(d = 20;
),lambda 内部的d
仍然保留创建时的拷贝值(30)。因为 lambda 内部的d
是独立的拷贝,与外部变量不再关联。
2.2 引用捕获
与引用传参类似,引用捕获保存的是引用,值会发生变化。 如果Add中加入一句:c = a;
void test5(){cout << "test5" << endl;int c = 12;int d = 30;auto Add = [&c, &d](int a, int b)->int {c = a; // 编译对的cout << "d = " << d << endl;return c;};d = 20;std::cout << Add(1, 2) << std::endl;}
关键现象与结果
运行这段代码时,输出结果会是:
test5 d = 20 // lambda 内部输出的 d 是外部修改后的 20 1 // 返回的 c 是 lambda 内部修改后的 1(即 a=1)
现象的本质:引用捕获的 “共享性”
引用捕获的核心是:lambda 内部的
c
和d
是外部变量的引用(别名),它们与外部变量共享同一内存地址。因此:
外部修改会影响 lambda 内部:
当外部执行d = 20;
时,lambda 内部的d
会同步变为 20(因为它们指向同一块内存)。lambda 内部修改会影响外部变量:
lambda 内部执行c = a;
(即c = 1
)时,外部的c
也会被修改为 1(因为c
是引用)
2.3 隐式捕获
手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获 列表中写一个 & 或 = 向编译器声明采用引用捕获或者值捕获。
示例 1:隐式值捕获(
[=]
)当捕获列表为
[=]
时,编译器会自动值捕获所有被 lambda 内部使用的外部变量(未被使用的变量不会被捕获)。代码实现
#include <iostream> using namespace std;void test_lambda_auto_capture() {int a = 10; // 外部变量 aint b = 20; // 外部变量 bint c = 30; // 外部变量 c(未被 lambda 使用)// 隐式值捕获:[=] 表示值捕获所有被使用的外部变量(此处 a 和 b 被使用)auto lambda_by_value = [=](int x) {cout << "Lambda 内部(值捕获): " << endl;cout << "a(拷贝值): " << a << "(外部 a 原值:10)" << endl; // 使用 a 的拷贝cout << "b(拷贝值): " << b << "(外部 b 原值:20)" << endl; // 使用 b 的拷贝return a + b + x;};// 外部修改变量值a = 100;b = 200;// 调用 lambdaint result = lambda_by_value(5);cout << "计算结果(值捕获): " << result << endl; // 结果 = 10 + 20 + 5 = 35 }int main() {test_lambda_auto_capture();return 0; }
输出结果
Lambda 内部(值捕获): a(拷贝值): 10(外部 a 原值:10) b(拷贝值): 20(外部 b 原值:20) 计算结果(值捕获): 35
关键说明
[=]
表示值捕获所有被 lambda 内部使用的外部变量(此例中a
和b
被使用,c
未被使用,因此不捕获)。- 捕获发生在 lambda 创建时(即
lambda_by_value
定义时),因此即使后续外部修改a=100
、b=200
,lambda 内部仍使用创建时的拷贝值(a=10
、b=20
)。示例 2:隐式引用捕获(
[&]
)当捕获列表为
[&]
时,编译器会自动引用捕获所有被 lambda 内部使用的外部变量(未被使用的变量不会被捕获)。代码实现
#include <iostream> using namespace std;void test_lambda_auto_capture_ref() {int a = 10; // 外部变量 aint b = 20; // 外部变量 bint c = 30; // 外部变量 c(未被 lambda 使用)// 隐式引用捕获:[&] 表示引用捕获所有被使用的外部变量(此处 a 和 b 被使用)auto lambda_by_ref = [&](int x) {cout << "Lambda 内部(引用捕获): " << endl;cout << "a(引用值): " << a << "(外部 a 当前值:?)" << endl; // 引用外部 acout << "b(引用值): " << b << "(外部 b 当前值:?)" << endl; // 引用外部 ba = 1000; // 直接修改外部 a 的值return a + b + x;};// 外部修改变量值(在 lambda 创建后、调用前)a = 100;b = 200;// 调用 lambdaint result = lambda_by_ref(5);cout << "计算结果(引用捕获): " << result << endl; // 结果 = 1000 + 200 + 5 = 1205cout << "外部 a 被修改后的值: " << a << endl; // 输出 1000(lambda 内部修改了外部 a) }int main() {test_lambda_auto_capture_ref();return 0; }
输出结果
Lambda 内部(引用捕获): a(引用值): 100(外部 a 当前值:100) b(引用值): 200(外部 b 当前值:200) 计算结果(引用捕获): 1205 外部 a 被修改后的值: 1000
关键说明
[&]
表示引用捕获所有被 lambda 内部使用的外部变量(此例中a
和b
被使用,c
未被使用,因此不捕获)。- 引用捕获的是外部变量的内存地址,因此 lambda 内部的
a
和b
与外部变量共享同一值。外部修改a=100
、b=200
会直接反映到 lambda 内部;lambda 内部修改a=1000
也会直接修改外部的a
。示例 3:混合隐式与显式捕获
可以混合使用隐式捕获(
&
或=
)和显式捕获(手动指定变量),但需注意规则:
- 显式捕获的变量必须与隐式捕获的类型不同(例如
[=, &a]
合法,但[=, a]
非法)。- 显式捕获的变量会覆盖隐式捕获的规则(例如
[=, &a]
表示:值捕获其他变量,引用捕获a
)。代码实现
#include <iostream> using namespace std;void test_mixed_capture() {int a = 10;int b = 20;int c = 30;// 混合捕获:[=, &a] 表示值捕获其他变量,引用捕获 aauto lambda_mixed = [=, &a](int x) {a = 1000; // 引用捕获,修改外部 areturn a + b + c + x; // b、c 是值捕获(创建时的拷贝)};// 外部修改变量a = 100; // 会影响 lambda 内部的 a(引用捕获)b = 200; // 不影响 lambda 内部的 b(值捕获的是创建时的 20)c = 300; // 不影响 lambda 内部的 c(值捕获的是创建时的 30)int result = lambda_mixed(5);cout << "混合捕获结果: " << result << endl; // 1000(a 引用值) + 20(b 拷贝值) + 30(c 拷贝值) + 5 = 1055cout << "外部 a 被修改后的值: " << a << endl; // 输出 1000(lambda 内部修改了外部 a) }int main() {test_mixed_capture();return 0; }
输出结果
混合捕获结果: 1055 外部 a 被修改后的值: 1000
总结
捕获方式 行为说明 适用场景 隐式值捕获 [=]
编译器自动值捕获所有被使用的外部变量(创建时拷贝,后续外部修改不影响内部) 需要隔离外部变量修改,或变量拷贝成本低(如基本类型) 隐式引用捕获 [&]
编译器自动引用捕获所有被使用的外部变量(与外部共享内存,外部修改直接影响内部) 需要实时同步外部变量的修改,或变量拷贝成本高(如大对象) 混合捕获 [=, &a]
部分变量值捕获,部分变量引用捕获(平衡隔离性与实时性) 需要对个别变量特殊处理(如大对象引用捕获,小对象值捕获)
2.4 空捕获列表
捕获列表'[]'中为空,表示Lambda不能使用所在函数中的变量.
示例 1:空捕获列表无法访问局部变量(编译错误)
当捕获列表为空
[]
时,Lambda 表达式不能直接使用所在函数的局部变量(因为未捕获任何变量)。错误代码示例
#include <iostream> using namespace std;void test_empty_capture_error() {int a = 10; // 局部变量 a// 空捕获列表 []:尝试访问局部变量 a(未捕获)auto lambda = [](int x) {return a + x; // 编译错误:'a' 在此作用域中未声明};cout << lambda(5) << endl; }int main() {test_empty_capture_error();return 0; }
错误原因
Lambda 表达式
[](int x) { ... }
的捕获列表为空([]
),因此无法访问所在函数test_empty_capture_error
中的局部变量a
。编译器会报错:'a' 在此作用域中未声明
。示例 2:空捕获列表的合法使用(仅用参数)
空捕获列表的 Lambda 可以正常使用参数(因为参数是 Lambda 自身的输入,不依赖外部变量)。
正确代码示例
#include <iostream> using namespace std;void test_empty_capture_valid() {// 空捕获列表 []:仅使用参数,无需外部变量auto add = [](int x, int y) {return x + y; // 合法:仅依赖参数};int result = add(3, 5); // 调用时传入参数cout << "3 + 5 = " << result << endl; // 输出 8 }int main() {test_empty_capture_valid();return 0; }
输出结果
3 + 5 = 8
关键说明
空捕获列表
[]
的 Lambda 虽然不能访问外部局部变量,但可以通过参数传递获取所需数据。此例中add
仅依赖输入的x
和y
,因此无需捕获外部变量。
2.5 表达式捕获
上面提到的值捕获、引用捕获都是已经在外层作用域声明的变量,因此这些捕获方式捕获的均为左值, 而不能捕获右值。
C++14之后支持捕获右值,允许捕获的成员用任意的表达式进行初始化,被声明的捕获变量类型会根据 表达式进行判断,判断方式与使用 auto 本质上是相同的:
示例背景:捕获不可拷贝的右值(如
std::unique_ptr
)
std::unique_ptr
是仅移动语义的智能指针(不可拷贝)。在 C++11 中,若想在 Lambda 中使用unique_ptr
,只能通过引用捕获(但需保证外部变量生命周期);而 C++14 可通过表达式捕获直接移动unique_ptr
到 Lambda 内部,确保所有权转移的安全性。代码示例:表达式捕获
std::unique_ptr
#include <iostream> #include <memory> // 包含 unique_ptr// 辅助类:用于演示对象的生命周期 class Data { public:int value;Data(int v) : value(v) { std::cout << "Data 构造,value = " << value << std::endl; }~Data() { std::cout << "Data 析构,value = " << value << std::endl; }void print() const { std::cout << "Data 打印,value = " << value << std::endl; } };void test_expression_capture() {// 创建一个 unique_ptr(右值,不可拷贝)auto ptr = std::make_unique<Data>(100); // ptr 指向 Data(100)// C++14 表达式捕获:将 ptr 移动到 Lambda 内部的 new_ptr 变量auto lambda = [new_ptr = std::move(ptr)]() { // 表达式捕获:new_ptr = std::move(ptr)if (new_ptr) {new_ptr->print(); // 访问 Lambda 内部的 new_ptr}};// 原 ptr 已被移动,现在为空(验证所有权转移)std::cout << "原 ptr 是否为空:" << (ptr == nullptr ? "是" : "否") << std::endl;// 调用 Lambda,访问内部的 new_ptrlambda(); }int main() {test_expression_capture();return 0; }
输出结果
Data 构造,value = 100 // Data(100) 被创建 原 ptr 是否为空:是 // ptr 已被移动,变为空 Data 打印,value = 100 // Lambda 内部的 new_ptr 调用 print() Data 析构,value = 100 // Lambda 生命周期结束,new_ptr 析构 Data(100)
关键行为分析
传统捕获的局限性(C++11):
若尝试用值捕获[ptr]
,会因unique_ptr
不可拷贝而编译失败;若用引用捕获[&ptr]
,则 Lambda 依赖外部ptr
的生命周期(若ptr
在 Lambda 调用前被销毁,会导致悬垂引用)。表达式捕获的优势(C++14):
通过[new_ptr = std::move(ptr)]
,将ptr
的所有权移动到 Lambda 内部的new_ptr
变量中。此时:
- 外部
ptr
变为空(所有权转移);- Lambda 内部的
new_ptr
独立拥有Data
对象,生命周期与 Lambda 绑定(Lambda 销毁时new_ptr
自动析构)。示例 2:捕获右值临时对象(如字符串)
表达式捕获还可直接用 ** 临时对象(右值)** 初始化捕获变量,避免额外拷贝。
代码示例
#include <iostream> #include <string>void test_rvalue_capture() {// C++14 表达式捕获:用临时字符串(右值)初始化捕获变量auto lambda = [str = std::string("Hello, C++14!")]() { // 表达式:std::string("...") 是右值std::cout << "Lambda 内部字符串: " << str << std::endl;};lambda(); // 输出 Lambda 内部的 str }int main() {test_rvalue_capture();return 0; }
输出结果
Lambda 内部字符串: Hello, C++14!
关键说明
- 此处
std::string("Hello, C++14!")
是临时对象(右值),传统值捕获无法直接捕获(因为需要先有一个左值变量)。- 表达式捕获允许直接用右值初始化
str
,str
的类型由表达式推导为std::string
,生命周期与 Lambda 绑定。示例 3:捕获表达式计算结果
表达式捕获的 “表达式” 可以是任意合法表达式(如计算、函数调用等),捕获变量的类型由表达式自动推导(类似
auto
)。代码示例
#include <iostream>void test_expression_result() {int a = 10, b = 20;// 表达式捕获:计算 a+b 的结果,并用结果初始化 sum 变量auto lambda = [sum = a + b]() { // sum 的类型是 int(a+b 的结果类型)std::cout << "a + b 的和(捕获时计算): " << sum << std::endl;};a = 100; // 外部修改 a,但不影响 sum(sum 是 a+b 的捕获时结果)lambda(); }int main() {test_expression_result();return 0; }
输出结果
a + b 的和(捕获时计算): 30
关键说明
sum
的值是 Lambda 创建时a + b
的计算结果(30),后续修改a
不影响sum
(因为sum
是独立变量)。- 若用传统值捕获
[a, b]
,则需在 Lambda 内部重新计算a + b
;而表达式捕获直接存储结果,避免重复计算。总结
表达式捕获(C++14 引入)的核心优势:
特性 传统捕获(C++11) 表达式捕获(C++14) 捕获类型 仅左值(已存在的变量) 任意表达式(左值、右值、临时对象、计算结果等) 变量初始化 依赖外部变量 用任意表达式初始化新变量 处理移动语义 不支持(需引用捕获) 支持(直接移动右值到 Lambda 内部) 变量类型推导 显式(与外部变量一致) 自动推导(类似 auto
)
2.6 泛型lambda
在C++14之前,lambda表示的形参只能指定具体的类型,没法泛型化。从 C++14 开始, Lambda 函数 的形式参数可以使用 auto关键字来产生意义上的泛型:
//泛型 Lambda C++14void test10(){cout << "test10" << endl;auto add = [](auto x, auto y) {return x+y;};std::cout << add(1, 2) << std::endl;std::cout << add(1.1, 1.2) << std::endl;
}
2.7 可变lambda
采用值捕获的方式,lambda不能修改其值,如果想要修改,使用mutable修饰 采用引用捕获的方式,lambda可以直接修改其值:
一、值捕获:必须用
mutable
才能修改内部拷贝值值捕获的 Lambda 中,捕获的变量是外部变量的拷贝,存储在 Lambda 的闭包类中。默认情况下,Lambda 的调用运算符
operator()
是const
成员函数,无法修改拷贝值。需通过mutable
关键字取消const
限定,才能修改。示例 1:值捕获未用
mutable
(修改报错)#include <iostream> using namespace std;void test_value_capture_no_mut() {int a = 10;auto lambda = [a]() { // 值捕获 a(拷贝值 10)a = 20; // 编译错误!Lambda 的 operator() 是 const,无法修改拷贝值cout << a << endl;}; }
错误原因:
Lambda 生成的闭包类中,
operator()
默认为const
,不允许修改成员变量(值捕获的a
是类的成员)。示例 2:值捕获使用
mutable
(允许修改拷贝值)#include <iostream> using namespace std;void test_value_capture_mut() {int a = 10;auto lambda = [a]() mutable { // 添加 mutable,operator() 变为非 consta = 20; // 合法:修改 Lambda 内部的拷贝值(不影响外部 a)cout << "Lambda 内部 a: " << a << endl;};lambda(); // 输出:Lambda 内部 a: 20cout << "外部 a: " << a << endl; // 输出:外部 a: 10(未被修改) }
关键说明:
mutable
的作用是让 Lambda 的operator()
变为非const
,允许修改值捕获的拷贝值。- 外部变量
a
仍为 10,因为 Lambda 修改的是内部拷贝(与外部变量无关)。二、引用捕获:无需
mutable
即可修改外部变量引用捕获的 Lambda 中,捕获的是外部变量的引用(别名),直接操作外部变量。由于修改的是外部变量(而非 Lambda 自身的状态),无需
mutable
。示例 3:引用捕获直接修改外部变量(无需
mutable
)#include <iostream> using namespace std;void test_ref_capture() {int b = 20;auto lambda = [&b]() { // 引用捕获 b(关联外部变量)b = 30; // 合法:直接修改外部变量 bcout << "Lambda 内部 b: " << b << endl;};lambda(); // 输出:Lambda 内部 b: 30cout << "外部 b: " << b << endl; // 输出:外部 b: 30(已被修改) }
关键说明:
- 引用捕获的
b
是外部变量的引用,Lambda 内部修改b
即直接修改外部变量。- 无需
mutable
,因为 Lambda 的operator()
即使是const
,也可以通过引用修改外部变量(引用本身是 Lambda 的成员,但修改的是外部对象,而非 Lambda 自身的状态)。
2.8 精确推导 decltype
一、Lambda 默认的返回类型推断规则
在 C++11 及以上版本中,Lambda 表达式的返回类型通常由编译器自动推断,规则如下:
- 单 return 语句:返回类型由 return 表达式的类型推导。
例如:auto add = [](int a, int b) { return a + b; };
,返回类型为int
。- 多 return 语句:所有 return 表达式的类型必须完全一致,否则编译器无法推断(报错)。
例如:auto func = []() { if (true) return 1; else return 2.5; };
会编译失败(int
和double
类型不一致)。- 无 return 或返回 void:返回类型为
void
(如仅执行打印操作的 Lambda)。二、
decltype(auto)
:精确推导返回类型C++14 引入了
decltype(auto)
作为返回类型说明符,其作用是:让编译器根据 return 表达式的类型,推导返回类型,且完全保留其引用和 cv 限定符(而普通auto
会退化掉引用和 cv 限定)。这一特性在 Lambda 中同样适用,尤其当需要返回一个引用或保持类型精确性时。
三、具体示例对比
通过以下例子,对比普通
auto
推断和decltype(auto)
推断的差异:示例 1:返回引用时的类型保留
#include <iostream> #include <type_traits>int main() {int x = 10;// 普通 auto 推断返回类型(丢失引用)auto lambda1 = [&x]() { return x; }; // 返回类型为 int(x的拷贝)static_assert(std::is_same_v<decltype(lambda1()), int>); // 断言成立// decltype(auto) 推断返回类型(保留引用)decltype(auto) lambda2 = [&x]() -> decltype(auto) { return x; }; // 返回类型为 int&static_assert(std::is_same_v<decltype(lambda2()), int&>); // 断言成立lambda2() = 20; // 通过引用修改 xstd::cout << x << std::endl; // 输出 20return 0; }
关键点:
lambda1
使用auto
推断返回类型,x
是int&
,但return x
返回的是int
(值拷贝),因此返回类型为int
。lambda2
使用decltype(auto)
推断返回类型,return x
的表达式类型是int&
,因此返回类型为int&
(引用),可直接修改原变量x
。示例 2:返回 cv 限定类型
#include <iostream> #include <type_traits>int main() {const int y = 20;// 普通 auto 推断(丢失 const 限定)auto lambda3 = [&y]() { return y; }; // 返回类型为 int(const 被剥离)static_assert(std::is_same_v<decltype(lambda3()), int>); // 断言成立// decltype(auto) 推断(保留 const 限定)decltype(auto) lambda4 = [&y]() -> decltype(auto) { return y; }; // 返回类型为 const int&static_assert(std::is_same_v<decltype(lambda4()), const int&>); // 断言成立// lambda4() = 30; // 编译错误:不能修改 const 引用return 0; }
关键点:
lambda3
的返回类型是int
(const int
的值拷贝,丢失const
限定)。lambda4
的返回类型是const int&
(保留原变量的const
限定),避免意外修改。四、
decltype(auto)
的使用场景在 Lambda 中使用
decltype(auto)
主要用于以下场景:
- 返回引用类型:需要直接操作原对象(如示例 1 中的
lambda2
)。- 保留 cv 限定符:避免因类型退化导致的错误(如示例 2 中的
lambda4
)。- 泛型编程:当 Lambda 的返回类型依赖模板参数或复杂表达式时,
decltype(auto)
能精确推导类型。
二、C++11标准库(STL)
STL定义了强大的、基于模板的、可复用的组件,实现了许多通用的数据结构及处理这些数据结构的算 法。其中包含三个关键组件——容器(container,流行的模板数据结构)、迭代器(iterator)和算法 (algorithm)。
组件 | 描述 |
---|---|
容器 | 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。 |
迭代器 | 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。 |
算法 | 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。 |
1. 容器简洁
STL容器,可将其分为四类:序列容器、有序关联容器、无序关联容器、容器适配器 序列容器:
标准库容器类
标准库容器类 | 描述 |
---|---|
array | 固定大小,直接访问任意元素 |
deque | 从前部或后部进行快速插入和删除操作,直接访问任何元素 |
forward_list | 单链表,在任意位置快速插入和删除 |
list | 双向链表,在任意位置进行快速插入和删除操作 |
vector | 从后部进行快速插入和删除操作,直接访问任何元素 |
有序关联容器(键按顺序保存):
标准库容器类 | 描述 |
---|---|
set | 快速查找,无重复元素 |
multiset | 快速查找,可有重复元素 |
map | 一对一映射,无重复元素,基于键快速查找 |
multimap | 一对一映射,可有重复元素,基于键快速查找 |
无序关联容器:
标准库容器类 | 描述 |
---|---|
unordered_set | 快速查找,无重复元素 |
unordered_multiset | 快速查找,可有重复元素 |
unordered_map | 一对一映射,无重复元素,基于键快速查找 |
unordered_multimap | 一对一映射,可有重复元素,基于键快速查找 |
容器适配器:
标准库容器类 | 描述 |
---|---|
stack | 后进先出(LIFO) |
queue | 先进先出(FIFO) |
priority_queue | 优先级最高的元素先出 |
序列容器描述了线性的数据结构(也就是说,其中的元素在概念上” 排成一行"), 例如数组、向量和 链 表。
关联容器描述非线性的容器,它们通常可以快速锁定其中的元素。这种容器可以存储值的集合或 者键 值对。
栈和队列都是在序列容器的基础上加以约束条件得到的,因此STL把stack和queue作为容器适配器来实 现,这样就可以使程序以一种约束方式来处理线性容器。类型string支持的功能跟线性容器一样, 但是 它只能存储字符数据。
2. 迭代器简介
迭代器在很多方面与指针类似,也是用于指向首类容器中的元素(还有一些其他用途,后面将会提 到)。 迭代器存有它们所指的特定容器的状态信息,即迭代器对每种类型的容器都有一个实现。 有些迭 代器的操作在不同容器间是统一的。 例如,*运算符间接引用一个迭代器,这样就可以使用它所指向的 元素。++运算符使得迭代器指向容器中的下一个元素(和数组中指针递增后指向数组的下一个元素类 似)。
STL 首类容器提供了成员函数 begin 和 end。函数 begin 返回一个指向容器中第一个元素的迭代器,函 数 end 返回一个指向容器中最后一个元素的下一个元素(这个元素并不存在,常用于判断是否到达了容 器的结束位仅)的迭代器。 如果迭代器 i 指向一个特定的元素,那么 ++i 指向这个元素的下一个元素。* i 指代的是i指向的元素。 从函数 end 中返回的迭代器只在相等或不等的比较中使用,来判断这个“移动 的迭代器” (在这里指i)是否到达了容器的末端。
使用一个 iterator 对象来指向一个可以修改的容器元素,使用一个 const_iterator 对象来指向一个不能 修改 的容器元素。
类型 | 描述 |
---|---|
随机访问迭代器 (random access) | 在双向迭代器基础上增加了直接访问容器中任意元素的功能,即可以向前或向后跳转任意个元素 |
双向迭代器 (bidirectional) | 在前向迭代器基础上增加了向后移动的功能,支持多遍扫描算法 |
前向迭代器 (forward) | 综合输入和输出迭代器的功能,并能保持它们在容器中的位置(作为状态信息),可使用同一个迭代器两次遍历一个容器(多遍扫描算法) |
输出迭代器 (output) | 用于将元素写入容器,每次只能向前移动一个元素,只支持一遍扫描算法,不能用相同输出迭代器两次遍历序列容器 |
输入迭代器 (input) | 用于从容器读取元素,每次只能向前移动一个元素,只支持一遍扫描算法,不能用相同输入迭代器两次遍历序列容器 |
每种容器所支持的迭代器类型决定了这种容器是否可以在指定的 STL 算 法中使用。 支持随机访问迭代 器的容器可用于所有的 STL 算法(除了那些需要改变容器大小的算法,这样的算法不能在数组和 array 对象中使用)。 指向 数组的指针可以代替迭代器用于几乎所有的 STL 算法中,包括那些要求随机访问 迭代器的算法。 下表显示了每种 STL 容器所支持的迭代器类型。 注意, vector 、 deque 、 list 、 set 、 multiset 、 map 、 multimap以及 string 和数组都可以使用迭代器遍历。
容器 | 支持的迭代器类型 | 容器 | 支持的迭代器类型 |
---|---|---|---|
vector | 随机访问迭代器 | set | 双向迭代器 |
array | 随机访问迭代器 | multiset | 双向迭代器 |
deque | 随机访问迭代器 | map | 双向迭代器 |
list | 双向迭代器 | multimap | 双向迭代器 |
forward_list | 前向迭代器 | unordered_set | 双向迭代器 |
stack | 不支持迭代器 | unordered_multiset | 双向迭代器 |
queue | 不支持迭代器 | unordered_map | 双向迭代器 |
priority_queue | 不支持迭代器 | unordered_multimap | 双向迭代器 |
下表显示了在 STL容器的类定义中出现的几种预定义的迭代器 typedef。不是每种 typedef 都出现在每 个容器中。 我们使用常量版本的迭代器来访问只读容器或不应该被更改的非只读容器,使用反向迭代器 来以相反的方向访问容器。
为迭代器预先定义的 typedef | ++ 的方向 | 读写能力 |
---|---|---|
iterator | 向前 | 读 / 写 |
const_iterator | 向前 | 读 |
reverse_iterator | 向后 | 读 / 写 |
const_reverse_iterator | 向后 | 读 |
下表显示了可作用在每种迭代器上的操作。 除了给出的对于所有迭代器都有的运算符,迭代器还必须提 供默认构造函数、拷贝构造函数和拷贝赋值操作符。 前向迭代器支持++ 和所有的输入和输出迭代器的 功能。 双向迭代器支持–操作和前向迭代器的功能。 随机访问迭代器支持所有在表中给出的操作。 另 外, 对于输入迭代器和输出迭代器,不能在保存迭代器之后再使用保存的值。
迭代器操作 | 描述 |
---|---|
适用所有迭代器的操作 | |
++p | 前置自增迭代器 |
p++ | 后置自增迭代器 |
p = p1 | 将一个迭代器赋值给另一个迭代器 |
输入迭代器 | |
*p | 间接引用一个迭代器 |
p->m | 使用迭代器读取元素 m |
p == p1 | 比较两个迭代器是否相等 |
p != p1 | 比较两个迭代器是否不相等 |
输出迭代器 | |
*p | 间接引用一个迭代器(用于写入) |
p = p1 | 把一个迭代器赋值给另一个迭代器 |
前向迭代器 | 前向迭代器提供了输入和输出迭代器的所有功能 |
双向迭代器 | |
--p | 前置自减迭代器 |
p-- | 后置自减迭代器 |
随机访问迭代器 | |
p + i | 迭代器 p 前进 i 个位置 |
p - i | 迭代器 p 后退 i 个位置 |
p += i | 在迭代器 p 的位置上前进 i 个位置 |
p -= i | 在迭代器 p 的位置上后退 i 个位置 |
p - p1 | 表达式的值是一个整数,代表同一个容器中两个元素间的距离 |
p[i] | 返回与迭代器 p 的位置相距 i 的元素 |
p < p1 | 若迭代器 p 小于 p1 (即容器中 p 在 p1 前)则返回 true ,否则返回 false |
p <= p1 | 若迭代器 p 小于或等于 p1 (即容器中 p 在 p1 前或位置相同)则返回 true ,否则返回 false |
p > p1 | 若迭代器 p 大于 p1 (即容器中 p 在 p1 后)则返回 true ,否则返回 false |
p >= p1 | 若迭代器 p 大于或等于 p1 (即容器中 p 在 p1 后或位置相同)则返回 true ,否则返回 false |
三、 正则表达式
一、常用类与函数
std::regex
:存储正则表达式模式。std::smatch
:保存针对std::string
的匹配结果。regex_match
:判断整个字符串是否完全匹配正则表达式。regex_search
:在字符串中搜索匹配的子串。regex_replace
:将匹配的子串替换为指定内容。二、核心语法
- 元字符:
.
:匹配除换行符外的任意单个字符。*
:匹配前一元素零次或多次。+
:匹配前一元素一次或多次。?
:匹配前一元素零次或一次。^
:匹配字符串开头,$
:匹配结尾。()
:分组提取,|
:逻辑或(如a|b
匹配a
或b
)。- 字符类:
\d
:匹配数字(等价于[0-9]
)。\w
:匹配单词字符(字母、数字、下划线,等价于[a-zA-Z0-9_]
)。\s
:匹配空白字符(空格、制表符等)。\D
、\W
、\S
:分别匹配非数字、非单词、非空白字符。三、示例代码
示例 1:完全匹配(判断是否为数字字符串)
#include <iostream> #include <regex> #include <string>int main() {std::string text = "12345";std::regex pattern("\\d+"); // \\d 转义为 \d,+表示多次匹配if (std::regex_match(text, pattern)) {std::cout << "完全匹配,是数字字符串" << std::endl;} else {std::cout << "不匹配" << std::endl;}return 0; }
示例 2:搜索子串(查找 “apple” 或 “banana”)
#include <iostream> #include <regex> #include <string>int main() {std::string text = "I have an apple and a banana.";std::regex pattern("apple|banana"); // 匹配 apple 或 bananastd::smatch result;if (std::regex_search(text, result, pattern)) {std::cout << "找到匹配子串:" << result.str() << std::endl; // 输出第一个匹配的 “apple”}return 0; }
示例 3:替换子串(将数字替换为 “NUM”)
#include <iostream> #include <regex> #include <string>int main() {std::string text = "There are 3 apples and 5 bananas.";std::regex pattern("\\d+");std::string replaced = std::regex_replace(text, pattern, "NUM");std::cout << "替换后:" << replaced << std::endl; // 输出:There are NUM apples and NUM bananas.return 0; }
示例 4:简单邮箱格式验证
#include <iostream> #include <regex> #include <string>int main() {std::string email = "user@example.com";// 简化的邮箱正则(实际场景需更严谨)std::regex pattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");if (std::regex_match(email, pattern)) {std::cout << "邮箱格式正确" << std::endl;} else {std::cout << "邮箱格式错误" << std::endl;}return 0; }
0voice · GitHub