【C++进阶】匿名函数、STL标准库和正则表达式

1. 匿名函数

1.1 基本语法

[capture list] (parameters) -> return_type {
    
     
    // lambda 函数体 
}
  • capture list:捕获列表,用于捕获外部变量,可以是值传递或引用传递
  • parameters:参数列表,用于指定lambda函数的参数
  • -> return_type:返回类型,用于指定lambda函数的返回值类型,可以省略,由编译器自动推导
  • {}:lambda函数体,包含实现lambda函数的代码块

一个简单的小例子,实现了输出向量中大于规定值的数:

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    
    
    std::vector<int> nums = {
    
    1, 5, 2, 6, 3, 7, 9, 8};
    int threshold = 5;
    std::cout << "Numbers greater than " << threshold << " are: ";
    std::for_each(nums.begin(), nums.end(), [threshold] (int num) {
    
    
        if (num > threshold)
            std::cout << num << " ";
    });
    std::cout << std::endl;

    return 0;
}

stdout:

Numbers greater than 5 are: 6 7 9 8

在这个例子中,lambda函数捕获了外部变量threshold,并在函数体中使用了它。lambda函数的参数为int num,返回类型为void,函数体中使用了if语句判断num是否大于threshold,如果是则输出。

1.2 捕获列表

有时候,需要在匿名函数内使用外部变量,所以用捕获列表来传参

当捕获列表传值时,lambda表达式会复制外部变量的值,这样lambda表达式内部就可以使用这些值,而不会影响外部变量的值。即使,如果外部变量是一个可变对象,lambda表达式内部修改该变量的值也不会影响外部变量的值。

当捕获列表传引用时,lambda表达式会使用外部变量的引用,这意味着lambda表达式内部对外部变量的修改会影响外部变量的值。如果外部变量是一个可变对象,lambda表达式内部修改该变量的值也会影响外部变量的值。

总结一下,使用捕获列表传值会在Lambda表达式内部创建变量的副本,而捕获列表传引用则会使用外部变量的引用。选择使用哪种方式取决于是否需要修改外部变量的值以及是否需要保持外部变量的值不变。

几个特别的用法:

  1. 捕获this指针

lambda表达式可以捕获当前对象的this指针,使其在lambda中可用。这在需要在lambda中访问当前对象的成员函数或成员变量时非常有用。

#include <iostream>

class MyClass
{
    
    
public:
    auto foo()
    {
    
    
        int x = 10;
        auto lambda = [this, x]() {
    
    
            // 在Lambda中访问当前对象的成员函数和成员变量
            bar();
            std::cout << "x = " << x << std::endl;
        };
        return lambda;
    }

private:
    void bar() {
    
     std::cout << "bar" << std::endl; }
};

int main()
{
    
    
    MyClass* mc = new MyClass();
    auto lb = mc->foo();
    lb();

    return 0;
}

stdout:

bar
x = 10
  1. 捕获变量的引用

lambda表达式可以捕获变量的引用,这使得lambda中对该变量的修改会影响到外部变量。

#include <iostream>

int main()
{
    
    
    int x = 10;
    auto lambda = [&x]() {
    
    
        x = 20;
        std::cout << "x = " << x << std::endl;
    };
    lambda();
    std::cout << "x = " << x << std::endl;

    return 0;
}

stdout:

x = 20
x = 20
  1. 捕获变量的移动语义

C++14引入了新的捕获方式,可以将外部变量的移动语义转移给lambda表达式。这使得lambda表达式可以对外部变量进行移动操作,而不是拷贝操作,这对于处理大型对象时非常有用。

#include <iostream>
#include <vector>

int main()
{
    
    
    std::vector<int> v{
    
    1, 2, 3};
    auto lambda = [v = std::move(v)]() {
    
    
        // 对v进行移动操作
        std::cout << "v.size() = " << v.size() << std::endl;
    };
    lambda();

    return 0;
}

stdout:

v.size() = 3
  1. 捕获变量的初始化器列表

C++14还引入了一种新的捕获方式,可以在捕获列表中使用初始化器列表来初始化捕获的变量。

#include <iostream>

int main()
{
    
    
    int x = 10;
    auto lambda = [y = x + 1]() {
    
    
        std::cout << "y = " << y << std::endl;
    };
    lambda();

    return 0;
}

stdout:

y = 11

补充:

  • 如果捕获列表为[&],则表示所有的外部变量都按引用传递给lambda使用
  • 如果捕获列表为[=],则表示所有的外部变量都按值传递给lambda使用
  • 匿名函数构建的时候对于按值传递的捕获列表,会立即将当前可以取到的值拷贝一份作为常数,然后将该常数作为参数传递

1.3 匿名函数的简写

匿名函数由捕获列表、参数列表、返回类型和函数体组成;可以忽略参数列表和返回类型,但不可以忽略捕获列表和函数体,如:

auto lambda = [] {
    
     return 1 + 2; };

1.4 Lambda捕获列表

捕获列表形式 描述
[] 空捕获列表,Lambda不能使用所在函数中的变量
[names] names是一个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量
默认情况下,这些变量会被拷贝,然后按值传递,名字前面如果使用了&,则按引用传递
[&] 隐式捕获列表,lambda体内使用的局部变量都按引用方式传递
[=] 隐式捕获列表,lambda体内使用的局部变量都按值传递
[&, identifier_list] identifier_list是一个逗号分隔的列表,包含0个或多个来自所在函数的变量
这些变量采用值捕获的方式,其他变量则被隐式捕获,采用引用方式传递,identifier_list中的名字前面不能使用&
[=, identifier_list] identifier_list中的变量采用引用方式捕获,而被隐式捕获的变量都采用按值传递的方式捕获
identifier_list中的名字不能包含this,且这些名字面前必须使用&

2. C++11标准库

STL定义了强大的、基于模板的、可复用的组件,实现了许多通用的数据结构及处理这些数据结构的算法。其中包含三个关键组件——容器(container,流行的模板数据结构)、迭代器(iterator)和算法(algorithm)。

组件 描述
容器 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等
迭代器 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集
算法 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作

C++11对STL进行了一些改进和增强,主要包括以下方面:

  1. 新增了一些容器类型,如unordered_map、unordered_set、array等。

  2. 对已有容器类型进行了改进,如vector和string支持移动语义,map和set支持emplace操作等。

  3. 新增了一些算法,如move、forward、unique、minmax等。

  4. 新增了一些迭代器类型,如move_iterator、reverse_iterator等。

  5. 对于多线程编程,新增了一些支持并发操作的容器类型,如concurrent_queue、concurrent_map等。

  6. 新增了一些函数对象,如function、bind等,用于实现函数式编程。

  7. 新增了一些智能指针类型,如unique_ptr、shared_ptr等,用于管理动态内存。

2.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.2 迭代器简介

迭代器在很多方面与指针类似,也是用于指向首类容器中的元素(还有一些其他用途,后面将会提到)。 迭代器存有它们所指的特定容器的状态信息,即迭代器对每种类型的容器都有一个实现。 有些迭代器的操作在不同容器间是统一的。 例如,*运算符间接引用一个迭代器,这样就可以使用它所指向的元素。++运算符使得迭代器指向容器中的下一个元素(和数组中指针递增后指向数组的下一个元素类似)。

STL 首类容器提供了成员函数 begin 和 end。函数 begin 返回一个指向容器中第一个元素的迭代器,函数 end 返回一个指向容器中最后一个元素的下一个元素(这个元素并不存在,常用于判断是否到达了容器的结束位仅)的迭代器。 如果迭代器 i 指向一个特定的元素,那么 ++i 指向这个元素的下一个元素。*i 指代的是i指向的元素。 从函数 end 中返回的迭代器只在相等或不等的比较中使用,来判断这个“移动的迭代器”(在这里指i)是否到达了容器的末端。

使用一个 iterator 对象来指向一个可以修改的容器元素,使用一个 const_iterator 对象来指向一个不能修改的容器元素。

类型 描述
随机访问迭代器(random access) 在双向迭代湍基础上增加了直接访问容器中任意元素的功能, 即可以向前或向后跳转任意个元素
双向迭代器(bidirectional) 在前向迭代器基础上增加了向后移动的功能。支持多遍扫描算法
前向迭代器(forword) 综合输入和输出迭代器的功能,并能保持它们在容器中的位置(作为状态信息),可以使用同一个迭代器两次遍历一个容器(称为多遍扫描算法)
输出迭代器(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 q
p– 后置自减迭代器

随机访问迭代器:

迭代器操作 描述
p+=i 迭代器p前进i个位置
p-=i 迭代器后退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

案例:

  1. 输入迭代器(Input Iterator)

输入迭代器用于读取容器中的元素,但不支持修改

#include <iostream>
#include <iterator>
#include <vector>

int main() {
    
    
    std::vector<int> vec;
    std::istream_iterator<int> input_iter(std::cin);
    std::istream_iterator<int> end_of_input;

    // 从标准输入流中读取整数,并加入到 vector 中
    while (input_iter != end_of_input)
        vec.push_back(*input_iter++);

    // 输出 vector 中的元素
    std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    return 0;
}
  1. 输出迭代器(Output Iterator)

输出迭代器用于向容器中写入元素,但不支持读取

#include <iostream>
#include <iterator>
#include <vector>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 4, 5};
    std::ostream_iterator<int> output_iter(std::cout, " ");

    // 将 vector 中的元素写入到标准输出流中
    std::copy(vec.begin(), vec.end(), output_iter);
    std::cout << std::endl;

    return 0;
}
  1. 前向迭代器(Forward Iterator)

前向迭代器支持读取和修改容器中的元素,但只能向前迭代,不能向后迭代,常见的有 list 和 forward_list

#include <iostream>
#include <iterator>
#include <list>

int main() {
    
    
    std::list<int> lst{
    
    1, 2, 3, 4, 5};

    // 将 list 中的元素逆序输出
    std::list<int>::iterator iter = lst.end();
    while (iter != lst.begin())
        std::cout << *--iter << " ";
    std::cout << std::endl;

    return 0;
}
  1. 双向迭代器(Bidirectional Iterator)

双向迭代器支持读取和修改容器中的元素,可以向前迭代和向后迭代,常见的有 set 和 map

#include <iostream>
#include <iterator>
#include <set>

int main() {
    
    
    std::set<int> s{
    
    3, 1, 4, 1, 5, 9, 2, 6, 5, 3};

    // 输出 set 中的元素
    std::set<int>::iterator iter;
    for (iter = s.begin(); iter != s.end(); ++iter)
        std::cout << *iter << " ";
    std::cout << std::endl;

    return 0;
}
  1. 随机访问迭代器(Random Access Iterator)

随机访问迭代器支持读取和修改容器中的元素,可以随机访问容器中的任意一个元素,支持算术运算符,常见的有 vector 和 deque

#include <iostream>
#include <iterator>
#include <vector>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 4, 5};

    // 将 vector 中的元素逆序输出
    std::vector<int>::iterator iter;
    for (iter = vec.end() - 1; iter >= vec.begin(); --iter)
        std::cout << *iter << " ";
    std::cout << std::endl;

    return 0;
}

2.3 算法简介

STL提供了可以用于多种容器的算法,其中很多算法都是常用的,插入、删除、搜索、排序及其他一些对部分或全部序列容器和关联容器适用的算法。

下面是一些常用的STL算法:

  • sort():对容器中的元素进行排序
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec{
    
    5, 2, 8, 1, 6};
    std::sort(vec.begin(), vec.end());

    for (auto& i : vec)
        std::cout << i << " ";
    std::cout << std::endl;

    return 0;
}
  • find():在容器中查找指定元素,返回指向该元素的迭代器
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 4, 5};
    auto iter = std::find(vec.begin(), vec.end(), 3);

    if (iter != vec.end())
        std::cout << "Found: " << *iter << std::endl;
    else
        std::cout << "Not found." << std::endl;

    return 0;
}
  • accumulate():对容器中的元素进行累加,返回总和
#include <iostream>
#include <vector>
#include <numeric>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 4, 5};
    int sum = std::accumulate(vec.begin(), vec.end(), 0);

    std::cout << "Sum: " << sum << std::endl;

    return 0;
}
  • count():统计容器中某个元素出现的次数
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 2, 4, 2, 5};
    int cnt = std::count(vec.begin(), vec.end(), 2);

    std::cout << "Count: " << cnt << std::endl;

    return 0;
}
  • reverse():对容器中的元素进行反转
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 4, 5};
    std::reverse(vec.begin(), vec.end());

    for (auto& i : vec)
        std::cout << i << " ";
    std::cout << std::endl;

    return 0;
}
  • transform():对容器中的每个元素进行操作,并将结果存储到另一个容器中
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 4, 5};
    std::vector<int> res(vec.size());
    std::transform(vec.begin(), vec.end(), res.begin(), [](int x){
    
     return x * 2; });

    for (auto& i : res)
        std::cout << i << " ";
    std::cout << std::endl;

    return 0;
}
  • unique():去除容器中相邻的重复元素
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 2, 3, 3, 3, 4, 5, 5};
    auto last = std::unique(vec.begin(), vec.end());
    vec.erase(last, vec.end());

    for (auto& i : vec)
        std::cout << i << " ";
    std::cout << std::endl;

    return 0;
}
  • copy():将一个容器中的元素复制到另一个容器中
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 4, 5};
    std::vector<int> res(vec.size());
    std::copy(vec.begin(), vec.end(), res.begin());

    for (auto& i : res)
        std::cout << i << " ";
    std::cout << std::endl;

    return 0;
}
  • binary_search():在有序容器中查找指定元素,返回true或false
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec{
    
    1, 2, 3, 4, 5};
    bool found = std::binary_search(vec.begin(), vec.end(), 3);

    if (found)
        std::cout << "Found." << std::endl;
    else
        std::cout << "Not found." << std::endl;

    return 0;
}
  • fill():将容器中的元素设置为指定的值
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    
    
    std::vector<int> vec(5);
    std::fill(vec.begin(), vec.end(), 3);

    for (auto& i : vec)
        std::cout << i << " ";
    std::cout << std::endl;

    return 0;
}

3. 正则表达式

正则表达式是一种强大的字符串匹配工具,它可以用来查找、替换和提取文本中的模式。C++11开始提供了regex库,可以方便地使用正则表达式。

正则表达式的语法比较复杂,但是可以通过一些基本的元字符和规则来构建。下面是一些常用的元字符和规则:

  • . :匹配任意单个字符
  • [] :匹配括号中的任意一个字符
  • [^] :匹配不在括号中的任意一个字符
  • * :匹配前面的字符出现0次或多次
  • + :匹配前面的字符出现1次或多次
  • ? :匹配前面的字符出现0次或1次
  • {m,n} :匹配前面的字符出现m到n次
  • () :将括号中的内容作为一个整体进行匹配

下面给出一个匹配邮箱地址的正则表达式:

#include <regex>
#include <iostream>

int main() {
    
    
    std::string email = "[email protected]";
    std::regex reg("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
    std::cout << std::regex_match(email, reg) << std::endl; // 输出 1,匹配成功
    
    return 0;
}
  • ^:表示匹配字符串的开头
  • [a-zA-Z0-9._%+-]+:表示匹配一个或多个字母、数字、点、下划线、百分号、加号或减号
  • @:表示匹配一个 @ 符号
  • [a-zA-Z0-9.-]+:表示匹配一个或多个字母、数字、点或减号
  • \\.:表示匹配一个点号,由于点号在正则表达式中有特殊含义,因此需要使用反斜杠进行转义
  • [a-zA-Z]{2,}:表示匹配两个或以上字母
  • $:表示匹配字符串的结尾

再给出一个更复杂的例子:

#include <iostream>
#include <regex>
#include <string>

int main() {
    
    
    std::string str = "Hello, world! My email is [email protected]. My phone number is (123) 456-7890.";
    
    // 匹配邮箱
    std::regex email_regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
    std::smatch email_match;
    if (std::regex_search(str, email_match, email_regex)) {
    
    
        std::cout << "Email found: " << email_match.str() << std::endl;
    } else {
    
    
        std::cout << "Email not found." << std::endl;
    }
    
    // 匹配电话号码
    std::regex phone_regex("\\(\\d{3}\\)\\s\\d{3}-\\d{4}");
    std::smatch phone_match;
    if (std::regex_search(str, phone_match, phone_regex)) {
    
    
        std::cout << "Phone number found: " << phone_match.str() << std::endl;
    } else {
    
    
        std::cout << "Phone number not found." << std::endl;
    }
    
    // 替换字符串
    std::regex replace_regex("world");
    std::string replaced_str = std::regex_replace(str, replace_regex, "C++");
    std::cout << "Replaced string: " << replaced_str << std::endl;
    
    // 切割字符串
    std::regex split_regex("\\s|,");
    std::sregex_token_iterator it(str.begin(), str.end(), split_regex, -1);
    std::sregex_token_iterator end;
    std::cout << "Splitted string: ";
    for (; it != end; ++it) {
    
    
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

大家可以试着去理解一下

一个很好的参考网址,使用时查询即可:https://zh.cppreference.com/w/cpp/regex

猜你喜欢

转载自blog.csdn.net/weixin_52665939/article/details/129868255