【C++】7、STL标准库

一、什么是STL标准库

STL(Standard Template Library)是 C++ 标准库中的一个模板库,提供了一组容器、算法和迭代器等组件,旨在为 C++ 程序员提供一组高效、通用的数据结构和算法。

STL 包含了许多常用的容器,如 vector(动态数组)、list(双向链表)、deque(双端队列)、map(关联数组)等等。这些容器都提供了类似于数组的接口,同时具有自动扩容、快速插入和删除元素等特性。

此外,STL 还提供了一组算法,如排序、查找、合并等等,这些算法能够处理各种不同类型的容器,大大减少了程序员的工作量。

最后,STL 还提供了一组迭代器,它们可以在容器中移动并访问元素,迭代器可以使用算法来处理容器中的元素,也可以用于实现自定义的数据结构。

总的来说,STL 是 C++ 程序员必备的工具之一,它可以大大提高程序的开发效率和代码的可读性。

二、C++的核心优势

C++ 是一种高效、通用的编程语言,它在许多方面都有着优秀的表现。以下是 C++ 的一些核心优势:

  1. 高效性:C++ 是一种编译型语言,能够生成高效的本地代码,因此在性能方面表现出色。C++ 也具有底层访问和内存管理的能力,允许程序员直接控制内存的使用,以获得更高的性能。

  2. 面向对象编程(OOP):C++ 是一种面向对象的语言,允许程序员将代码组织成对象,这些对象可以封装数据和行为,并通过继承、多态等特性实现代码复用和灵活性。

  3. 泛型编程:C++ 支持模板(template)编程,它允许程序员编写通用的代码,能够处理各种不同类型的数据,从而提高代码的复用性和灵活性。

  4. 与硬件的接口:C++ 可以直接访问底层硬件,因此在开发嵌入式系统、驱动程序和操作系统等方面非常有用。

  5. 多平台支持:C++ 能够在各种操作系统和硬件平台上运行,因此可以用于开发跨平台的应用程序。

  6. 大量的库和工具支持:C++ 拥有丰富的标准库和第三方库,提供了各种常用的数据结构、算法和网络编程等功能,可以极大地提高开发效率。

综上所述,C++ 是一种高效、灵活、强大的编程语言,适用于各种不同的应用场景,是现代软件开发的重要工具之一。


目光看向上面的第6点,本文讲的STL标准库就是标准库的一种,其核心大致分为三部分,在上面的介绍中也大致提到过,这里重新概括下:

  • 容器(Containers):容器是一种存储数据的对象,它可以用于存储各种类型的数据,并提供了一组常用的方法来访问、插入、删除、搜索等等操作。STL 提供了多种容器,如 vector、list、set、map 等等,每种容器都有不同的特性和适用场景。

  • 算法(Algorithms):算法是一组可重用的操作,它们可以作用于容器中的数据,如排序、查找、复制、移动等等。STL 提供了多种算法,如 sort、find、copy、remove 等等,这些算法能够处理不同类型的容器,大大提高了代码的复用性和可读性。

  • 迭代器(Iterators):迭代器是一种对象,它可以在容器中遍历元素并访问它们。迭代器是算法和容器之间的粘合剂,它们提供了一种通用的方式来遍历各种不同类型的容器。STL 提供了多种迭代器,如随机访问迭代器、双向迭代器、前向迭代器等等,每种迭代器都具有不同的功能和性能特点。

这三个组件相互配合,可以提供一组通用的数据结构和算法,极大地提高了程序员的开发效率和代码的可读性。

三、STL六大组件

可以看看博客:https://blog.51cto.com/u_11767891/5557606?articleABtest=0

这里仍然采用 ChatGPT的解释:

  • 容器(Containers):容器是一种存储数据的对象,它可以用于存储各种类型的数据,并提供了一组常用的方法来访问、插入、删除、搜索等等操作。STL 提供了多种容器,如 vector、list、set、map 等等,每种容器都有不同的特性和适用场景。

  • 算法(Algorithms):算法是一组可重用的操作,它们可以作用于容器中的数据,如排序、查找、复制、移动等等。STL 提供了多种算法,如 sort、find、copy、remove 等等,这些算法能够处理不同类型的容器,大大提高了代码的复用性和可读性。

  • 迭代器(Iterators):迭代器是一种对象,它可以在容器中遍历元素并访问它们。迭代器是算法和容器之间的粘合剂,它们提供了一种通用的方式来遍历各种不同类型的容器。STL 提供了多种迭代器,如随机访问迭代器、双向迭代器、前向迭代器等等,每种迭代器都具有不同的功能和性能特点。

  • 函数对象(Functors):函数对象是一种行为像函数的对象,它们可以像函数一样被调用,但比函数更灵活。STL 中的许多算法都接受函数对象作为参数,从而可以实现不同的行为,如排序时可以根据不同的标准排序。

  • 适配器(Adapters):适配器是一种将一个容器或迭代器接口转换成另一种接口的对象。STL 提供了多种适配器,如迭代器适配器、容器适配器等等,可以将不同类型的容器或迭代器转换成适合特定算法的接口。

  • 分配器(Allocators):分配器是一种可以分配和释放内存的对象,它可以控制容器的内存分配和释放。STL 提供了多种分配器,如 std::allocator、std::vector::allocator_type 等等,可以满足不同场景下的内存管理需求。

四、STL容器的分类

容器是一种存储数据的对象,它可以用于存储各种类型的数据,并提供了一组常用的方法来访问、插入、删除、搜索等等操作。STL 提供了多种容器,如 vector、list、set、map 等等,每种容器都有不同的特性和适用场景。

我们根据其实现的数据结构以及支持的操作不同,将容器划分为三大类,分别是序列式容器,关联式容器和无序容器,三大类下各有细分 ,下面详细解释。

1、序列式容器(Sequence Containers)

序列式容器是一种线性结构,可以按照一定的顺序存储元素,每个元素都有一个唯一的位置,可以通过下标或迭代器来访问。序列式容器包括 vector、deque、list 和 array。

他们的函数几乎都可以通用。

(1)、vector

动态数组,支持快速随机访问,但在插入或删除元素时需要移动其他元素,性能较低。

适用于需要快速随机访问元素的场景,例如在数组中存储数据,并且需要经常对数据进行遍历和排序。但是如果需要在容器中频繁插入和删除元素,会导致内存分配和数据复制的开销,因此不适合在插入和删除元素较多的场景中使用。


例程1:

最基础的vector容器的使用

#include <iostream>
#include <vector> // 动态数组
using namespace std;

// 打印元素数据
void show(vector<int>& num)
{
    cout << num.size() << endl;

    // 提供遍历
    for(int i = 0; i < num.size(); i++)
    {
        cout << num[i] << "\t";
    }
    cout << endl;
}

int main()
{
    // 整形数组
    int arr[5] = {12, 56, 96, 78, 65};

    // 数组容器范围写入
    vector<int> num(arr, arr + 5); // vector 的构造函数,该构造函数接受两个迭代器作为参数,分别指向要复制的元素的起始位置和结束位置。

    // 打印容器元素
    show(num);

    return 0;
}


例程2:

常见的vector函数在vector容器中的使用

#include <iostream>
#include <vector> // 包含 vector 头文件
using namespace std;

// 通过迭代器获取元素并打印
void display(vector<string>& name)
{
    // 定义迭代器对象
    vector<string>::iterator p;

    // 遍历容器并打印元素
    for(p = name.begin(); p != name.end(); p++)
    {
        cout << *p << endl;
    }
    cout << endl;
}

int main()
{
    // 创建一个字符串类型的 vector 容器对象
    vector<string> name;

    // 使用 assign() 方法为容器赋值,赋值为3个初始值为 "jack" 的元素
    name.assign(3, "jack");

    // 定义一个迭代器对象
    vector<string>::iterator it;

    // 在容器的开头插入一个值为 "Tom" 的元素
    it = name.begin();
    name.insert(it, "Tom");

    // 在容器的末尾插入一个值为 "Rose" 的元素
    it = name.end();
    name.insert(it, "Rose");

    // 使用 push_back() 方法将值为 "dz" 的元素插入到容器的末尾
    name.push_back("dz");

    // 调用自定义的 display() 函数,打印容器中的元素
    display(name);

    // 使用迭代器遍历容器,查找元素 "Tom"
    vector<string>::iterator we;
    for(we = name.begin(); we != name.end(); we++)
    {
        if(*we == "Tom")
        {
            cout << "查找到此人\n";
            break;
        }
    }

    cout << endl << endl;

    // 删除容器中索引为 3 的元素(第四个元素)
    name.erase(name.begin() + 3);

    // 再次调用 display() 函数,打印更新后的容器内容
    display(name);

    return 0;
}

这段代码使用了 STL(Standard Template Library)中的 vector 容器来存储字符串数据,并展示了 vector 容器的常用操作方法。

首先,在主函数中创建了一个字符串类型的 vector 容器 name

然后,使用 assign() 方法将容器赋值为3个初始值为 "jack" 的元素。

接下来,定义了一个迭代器对象 it,并使用 begin() 方法获取容器的起始迭代器位置。通过 insert() 方法,在容器的开头插入了一个值为 "Tom" 的元素。

继续使用 end() 方法获取容器的结束迭代器位置,并通过 insert() 方法,在容器的末尾插入了一个值为 "Rose" 的元素。同时,使用 push_back() 方法将值为 "dz" 的元素插入到容器的末尾。

接下来,调用了自定义的函数 display(),将 vector 容器 name 作为参数传递给函数。在函数内部,使用迭代器对象 p 遍历容器中的元素,并通过解引用操作符 * 获取迭代器指向的元素值,然后打印出来。

继续,在一个循环中,使用迭代器对象 we 遍历容器中的元素,判断元素是否为 "Tom",若是,则输出相应的提示信息,并终止循环。

然后,通过调用 erase() 方法,删除容器中索引为 3 的元素(即第四个元素)。

最后,再次调用 display() 函数,打印更新后的容器内容。

整体而言,这段代码展示了 vector 容器的基本操作,包括赋值、插入、删除和遍历等,以及使用迭代器进行元素访问和操作的方法。

(2)、deque

双端队列,支持快速随机访问和首尾插入删除操作,但在中间插入或删除元素时性能较低。

适用于需要快速首尾插入和删除元素的场景,例如在双向队列中存储数据,并且需要经常从队列头或队列尾获取数据。但是在中间插入和删除元素的时候性能较低,因此不适合在频繁插入和删除元素的场景中使用。

#include <iostream>
#include <deque>

using namespace std;

int main() {
    // 创建一个deque对象
    deque<int> dq;

    // 在deque的尾部插入元素
    dq.push_back(10);
    dq.push_back(20);
    dq.push_back(30);

    // 在deque的头部插入元素
    dq.push_front(5);
    dq.push_front(15);
    dq.push_front(25);

    // 获取deque的大小
    cout << "Size of deque: " << dq.size() << endl;

    // 遍历并打印deque中的元素
    cout << "Elements in deque: ";
    for (int i = 0; i < dq.size(); i++) {
        cout << dq[i] << " ";
    }
    cout << endl;

    // 访问deque的头部和尾部元素
    cout << "Front element: " << dq.front() << endl;
    cout << "Back element: " << dq.back() << endl;

    // 删除deque的头部和尾部元素
    dq.pop_front();
    dq.pop_back();

    // 检查deque是否为空
    if (dq.empty()) {
        cout << "Deque is empty" << endl;
    } else {
        cout << "Deque is not empty" << endl;
    }

    return 0;
}

(3)、list

双向链表,支持在任意位置插入和删除元素,但不能快速随机访问,只能通过迭代器遍历元素。

适用于需要频繁插入和删除元素的场景,例如在链表中存储数据,并且需要经常在链表的中间位置插入或删除元素。但是由于不能进行随机访问,只能通过迭代器遍历元素,因此不适合需要频繁访问元素的场景。

#include <iostream>
#include <list>
using namespace std;

// 打印链表
void show(list<int>& data)
{
    // 定义迭代器
    list<int>::iterator it;

    // 循环获取
    for(it = data.begin(); it != data.end(); it++)
    {
        cout << *it << endl;
    }
    cout << endl;
}

// // 此处可以使用范围-based for循环
// void show(list<int>& data)
// {
//     // 使用范围-based for循环遍历元素
//     for (int element : data)
//     {
//         cout << element << endl;
//     }
//     cout << endl;
// }


// 全局函数1,此时a和b只需要指明顺序
bool comp1(int a, int b)
{
    a = 1;
    b = 2;
    return a > b;
}
// 全局函数2,此时a和b只需要指明顺序
bool comp2(int a, int b)
{
    a = 1;
    b = 2;
    return a < b;
}

int main()
{
    // 定义链表容器,此时其实链表的内存已经被划分好了
    list<int> data;

    // 插入数据
    data.assign(1, 100);

    // 头插
    data.push_front(111);
    data.push_front(22);
    data.push_front(66666);
    data.push_front(444);
    // 尾插
    data.push_back(000);
    data.push_back(67);
    data.push_back(77);
    data.push_back(5);

    // 默认的排列
    cout << "默认排序:\n";
    show(data);

    // 删除
    data.remove(0);

    // sort排序
    cout << "==============\n";



    cout << "第一版本从小到大,可以看出删除了0:\n";
    data.sort(); // 第一版本
    show(data);
    cout << "==============\n";



    cout << "第二版本true从小到大:\n";
    data.sort(comp1);
    show(data);
    cout << "==============\n";



    cout << "第二版本false从大到小:\n";
    data.sort(comp2);
    show(data);
    cout << "==============\n";



    cout << "系统默认的仿函数的实现:\n";
    data.sort(less<int>()); // 系统默认的仿函数的实现
    show(data);

    return 0;
}

(4)、array

固定大小的数组,支持快速随机访问,但不能改变大小,需要手动管理数组的生命周期。

适用于需要在编译时确定数组大小并且大小不会改变的场景,例如在编写算法时需要固定大小的数组。

#include <iostream>
#include <array>

using namespace std;

int main()
{
    // 创建array容器
    array<int, 5> arr;

    // 使用fill函数填充数组
    arr.fill(0);

    // 填充有效数据
    for (int i = 0; i < arr.size(); ++i)
    {
        arr.at(i) = i + 1;
    }

    // 使用size函数获取数组大小
    cout << "Array size: " << arr.size() << endl;

    // 使用front和back函数获取数组的首尾元素
    cout << "First Element:" << arr.front() << endl;
    cout << "Tail element:" << arr.back() << endl;

    // 使用data函数获取指向数组内存的指针
    int *ptr = arr.data();
    cout << "The first address of the array:" << ptr << endl;

    // 使用迭代器遍历数组并打印元素
    cout << "Array size: ";
    for (auto it = arr.begin(); it != arr.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;

    return 0;
}

2、关联式容器(Associative Containers)

关联式容器是一种基于某种特定顺序组织元素的容器,可以用于快速查找元素。关联式容器包括 set、map、multiset 和 multimap。

(1)、set

基于红黑树的集合,元素按照一定顺序排列,不允许重复元素。

适用于需要对元素进行排序并且需要快速查找元素的场景。例如在需要对一些数据进行去重、排序并查找元素的场景中,可以使用set或multiset。

(2)、multiset

基于红黑树的多重集合,元素按照一定顺序排列,允许重复元素。

适用于需要对元素进行排序并且需要快速查找元素的场景。例如在需要对一些数据进行去重、排序并查找元素的场景中,可以使用set或multiset。

(3)、map

基于红黑树的映射表,元素按照一定顺序排列,每个元素包含一个键和一个值,不允许重复键。

适用于需要对元素进行排序并且需要根据键值快速查找元素的场景。例如在需要将键值和数据关联起来,并能快速根据键值查找对应数据的场景中,可以使用map或multimap。

(4)、multimap

基于红黑树的多重映射表,元素按照一定顺序排列,每个元素包含一个键和一个值,允许重复键。

适用于需要对元素进行排序并且需要根据键值快速查找元素的场景。例如在需要将键值和数据关联起来,并能快速根据键值查找对应数据的场景中,可以使用map或multimap。

3、无序容器(Unordered Containers)

无序容器是一种基于哈希表的容器,可以用于快速查找和插入元素。无序容器包括 unordered_set、unordered_map、unordered_multiset 和 unordered_multimap。

(1)、unordered_set

基于哈希表的集合,元素不按照顺序排列,不允许重复元素。

适用于需要快速查找元素并且不需要元素按照顺序排列的场景。例如在需要快速判断某个元素是否存在并且不需要对元素进行排序的场景中,可以使用unordered_set或unordered_multiset。

(2)、unorder_map

基于哈希表的映射表,元素不按照顺序排列,每个元素包含一个键和一个值,不允许重复键。

适用于需要快速根据键值查找元素并且不需要元素按照顺序排列的场景。例如在需要将键值和数据关联起来,并能快速根据键值查找对应数据的场景中,可以使用unordered_map或unordered_multimap。

(3)、unorder_multiset

unordered_multiset是一个无序的、允许重复元素的容器。它的底层实现通常采用哈希表,因此插入、删除和查找等操作的平均时间复杂度都是O(1),最坏情况下的时间复杂度是O(n)。

与unordered_set相似,unordered_multiset也有insert、erase、find等操作,其用法也类似。与unordered_set不同的是,unordered_multiset允许相同的元素存在,因此可以通过count来获取某个元素在容器中的出现次数。

unordered_multiset的应用场景和unordered_set类似,主要用于需要快速插入和删除元素,并且不需要按照顺序访问元素的场合。如果需要去重的功能,可以考虑使用set或者unordered_set。需要注意的是,由于unordered_multiset允许重复元素存在,因此在对容器进行遍历或者查找时,需要注意相同元素的顺序问题。

五、vector容器常见函数

size

函数名称size()

功能 获取vector容器中元素的数量
头文件 #include <vector>
原型 size_t size() const;
参数
返回值 一个无符号整数,表示vector容器中元素的数量
功能描述 size函数用于返回vector容器中元素的数量。它返回一个无符号整数,表示当前vector容器中存储的元素个数。

示例:

#include <iostream>
#include <vector>

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

    // 输出: 元素数量: 5
    std::cout << "Number of elements: " << numbers.size() << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的vector容器,并初始化它包含的整数元素。然后,通过调用size函数,我们获取了numbers容器中元素的数量,并将其输出到标准输出流。在此示例中,numbers容器的大小为5,因为它包含了5个元素。

operator

函数名称operator[]

功能 访问vector容器中指定位置的元素
头文件 #include <vector>
原型 T& operator[](size_t pos);
const T& operator[](size_t pos) const;
参数 pos:要访问的元素的位置索引,从0开始
返回值 如果容器不为空,返回指定位置上的元素的引用;如果容器为空,则行为未定义
功能描述 operator[]函数用于访问vector容器中指定位置的元素。通过指定元素的位置索引,可以获取或修改容器中对应位置的元素。使用非常量版本的operator[]可以修改元素的值,而使用常量版本的operator[]只能用于访问元素,不能进行修改。请注意,访问超出vector容器范围的位置会导致未定义行为。因此,在使用operator[]时,应确保位置索引在合法范围内。

示例:

#include <iostream>
#include <vector>

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

    // 访问第三个元素,并修改其值
    numbers[2] = 10;

    // 输出: 修改后的元素: 10
    std::cout << "Modified element: " << numbers[2] << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的vector容器,并初始化它包含的整数元素。然后,我们使用operator[]访问第三个元素(位置索引为2)并将其值修改为10。最后,我们输出修改后的元素值。在此示例中,输出为"修改后的元素: 10",表明成功修改了元素的值。

assign

功能 向vector容器分配新值
头文件 #include <vector>
原型 void assign(InputIterator first, InputIterator last);
参数 first:要复制的起始位置的输入迭代器
last:要复制的结束位置的输入迭代器
返回值
功能描述 assign函数用于向vector容器分配新值。它将指定范围内的元素复制到目标容器中,替换目标容器中的所有元素。被复制的范围由参数firstlast指定,它们分别表示要复制的元素的起始位置和结束位置。要复制的元素包括first位置上的元素,但不包括last位置上的元素。

示例:

#include <iostream>
#include <vector>

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

    // 输出: 1 2 3 4 5
    for (int num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

在这个示例中,我们创建了一个空的numbers向量,并使用assign函数将一组整数({1, 2, 3, 4, 5})分配给该向量。结果是,numbers向量现在包含了这些整数。在此示例中,assign函数使用了初始化列表作为参数,但也可以使用迭代器范围来复制元素。

push_back

函数名称:push_back

功能 向vector容器尾部添加一个元素
头文件 #include <vector>
原型 void push_back(const T& value);
参数 value:要添加到容器的元素的值
返回值
功能描述 push_back函数用于向vector容器的尾部添加一个元素。它接受一个参数 value,表示要添加到容器的元素的值。新元素会被添加到vector容器的末尾,并且容器的大小会相应增加。

示例:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers;

    // 添加元素到容器尾部
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);

    // 输出: 容器中的元素: 10 20 30
    std::cout << "Elements in the container:";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们创建了一个空的numbers向量。然后,使用push_back函数向容器的尾部依次添加了三个整数元素(10、20、30)。最后,我们通过循环遍历容器并输出其中的元素。输出结果为"容器中的元素: 10 20 30",显示成功地将元素添加到了容器的尾部。


仔细看这里的for循环,会发现这里的写法有些奇怪,我们的第一想法就是学习C语言时候见到过的简写模式,但当初见过的那简写是不能简写掉分号的,所以此处显然不是单纯的简写。

实际上,这种for循环叫做范围-based for循环(Range-based for loop),是C++11引入的一种循环语法,用于遍历容器(例如数组、向量、列表等)中的元素。它提供了一种简洁、直观的方式来迭代访问容器中的每个元素,无需使用迭代器或索引。

范围-based for循环的语法格式如下:

for (element_declaration : container) {
    // 循环体中的代码
}

其中,element_declaration 是用于表示当前迭代元素的变量声明,它的类型可以是容器元素类型,也可以使用 auto 关键字进行类型推导。container 是要遍历的容器,可以是任何支持迭代器的容器,如数组、向量、列表等。

在循环的每次迭代中,循环变量会被自动绑定到容器中的下一个元素,并执行循环体中的代码。循环会一直进行,直到遍历完容器中的所有元素。

范围-based for循环的优点包括:

  • 代码简洁:无需手动管理迭代器或索引,更容易阅读和理解。
  • 安全性:自动处理容器边界,避免了索引越界的问题。
  • 自动类型推导:使用 auto 关键字进行类型推导,避免了繁琐的类型声明。

需要注意的是,范围-based for循环在遍历容器时是以只读方式进行的。如果需要修改容器中的元素,可以使用引用或指针的方式进行操作。

范围-based for循环是C++中常用的迭代方式,可以简化容器遍历的代码,提高代码的可读性和编程效率。

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

int main() {

   char arr[] = "helloworld!\n";

   for (auto ch : arr) {
      std::cout << ch << " ";
   }
   std::cout << std::endl;

   return 0;
}

insert

函数名称:insert

功能 向vector容器的指定位置插入一个或多个元素
头文件 #include <vector>
原型 iterator insert(iterator pos, const T& value);
iterator insert(iterator pos, size_type count, const T& value);
template <class InputIterator> void insert(iterator pos, InputIterator first, InputIterator last);
参数 pos:要插入元素的位置的迭代器
value:要插入的元素的值
count:要插入的元素的数量
first:要插入的元素范围的起始位置的迭代器
last:要插入的元素范围的结束位置的迭代器
返回值 一个迭代器,指向插入的第一个元素
功能描述 insert函数用于向vector容器的指定位置插入一个或多个元素。可以通过指定位置的迭代器来确定插入的位置。可以插入单个元素,也可以插入多个相同元素。还可以通过指定迭代器范围来插入其他容器中的元素。插入操作会导致容器的大小增加,并且在插入位置之后的元素将被移动。注意,插入操作可能会导致迭代器失效。

示例:

#include <iostream>
#include <vector>

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

   // 在第二个位置插入元素
   auto it = numbers.insert(numbers.begin() + 1, 10);

   // 输出: 元素: 1 10 2 3
   std::cout << "values: ";
   for (int num : numbers) {
      std::cout << num << " ";
   }
   std::cout << std::endl;

   // 在末尾插入3个元素,值为20
   numbers.insert(numbers.end(), 3, 20);

   std::cout << "values: ";
   for (int num : numbers) {
      std::cout << num << " ";
   }

    return 0;
}

在这个示例中,我们创建了一个名为numbers的vector容器,初始值为 {1, 2, 3}。首先,使用insert函数在第二个位置插入元素 10,返回一个迭代器指向插入的位置。然后,我们通过循环遍历容器,输出插入元素后的结果。接着,我们使用insert函数在容器末尾插入了 3 个值为 20 的元素,并将其进行输出。

注意,在插入操作后,容器的大小增加,并且插入位置之后的元素将被移动。

begin/end

函数名称:begin

功能 获取vector容器的起始位置的迭代器
头文件 #include <vector>
原型 iterator begin() noexcept;
const_iterator begin() const noexcept;
参数
返回值 一个迭代器,指向容器中的起始位置
功能描述 begin函数用于获取vector容器的起始位置的迭代器,即第一个元素的位置。如果容器是常量容器,则返回一个const_iterator类型的迭代器,否则返回一个iterator类型的迭代器。该迭代器可用于遍历vector容器中的所有元素。如果容器为空,则begin函数返回的迭代器没有定义行为。

函数名称:end

功能 获取vector容器的结束位置的迭代器
头文件 #include <vector>
原型 iterator end() noexcept;
const_iterator end() const noexcept;
参数
返回值 一个迭代器,指向容器中的结束位置
功能描述 end函数用于获取vector容器的结束位置的迭代器,即最后一个元素的下一个位置。如果容器是常量容器,则返回一个const_iterator类型的迭代器,否则返回一个iterator类型的迭代器。该迭代器通常用于表示一个越界的位置,表示遍历结束或插入新元素的位置。如果容器为空,则end函数返回的迭代器与begin函数返回的迭代器相同。

示例:

#include <iostream>
#include <vector>

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

    // 使用begin和end函数遍历容器
    std::cout << "Elements in the container: ";
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的vector容器,包含了一些整数元素。然后,我们使用begin函数获取容器的起始位置的迭代器,并使用end函数获取容器的结束位置的迭代器。通过将beginend函数返回的迭代器传递给范围-based for循环或自定义的循环来遍历容器中的所有元素。在此示例中,我们使用自定义的循环并输出容器中的元素值。

erase

函数名称:erase

功能 从vector容器中删除一个或多个元素
头文件 #include <vector>
原型 iterator erase(iterator pos);
iterator erase(iterator first, iterator last);
参数 pos:要删除元素的位置的迭代器
first:要删除元素范围的起始位置的迭代器
last:要删除元素范围的结束位置的迭代器(不包括该位置的元素)
返回值 一个迭代器,指向被删除元素的下一个元素
功能描述 erase函数用于从vector容器中删除一个或多个元素。可以通过指定位置的迭代器来删除单个元素,也可以通过指定迭代器范围来删除一段元素。删除操作会导致容器的大小减小,并且被删除位置之后的元素会向前移动。注意,删除操作可能会导致迭代器失效。删除单个元素时,返回的迭代器指向被删除元素之后的位置。删除范围内的元素时,返回的迭代器指向范围之后的第一个元素。

示例:

#include <iostream>
#include <vector>

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

    // 删除第三个元素
    auto it = numbers.erase(numbers.begin() + 2);

    // 输出: 元素: 1 2 4 5
    std::cout << "values: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的vector容器,包含了一些整数元素。然后,我们使用erase函数删除了第三个元素,即位置索引为2的元素。返回的迭代器 it 指向被删除元素之后的位置。最后,我们通过循环遍历容器并输出删除元素后的结果。输出结果为"元素: 1 2 4 5",表明成功删除了指定位置的元素。

六、deque容器中常见函数

push_back

函数名称:push_back

功能 将元素添加到deque容器的尾部
头文件 #include <deque>
原型 void push_back(const T& value);
参数 value:要添加到容器的元素的值
返回值
功能描述 push_back函数用于将元素添加到deque容器的尾部。它接受一个参数 value,表示要添加到容器的元素的值。新元素会被添加到deque容器的末尾,并且容器的大小会相应增加。

示例:

#include <iostream>
#include <deque>

int main() {
    std::deque<int> numbers;

    // 添加元素到容器尾部
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);

    // 输出: 容器中的元素: 10 20 30
    std::cout << "Elements in the container: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们创建了一个空的numbers双端队列。然后,使用push_back函数向容器的尾部依次添加了三个整数元素(10、20、30)。最后,我们通过循环遍历容器并输出其中的元素。输出结果为"容器中的元素: 10 20 30",显示成功地将元素添加到了容器的尾部。

push_front

函数名称:push_front

功能 将元素添加到deque容器的头部
头文件 #include <deque>
原型 void push_front(const T& value);
参数 value:要添加到容器的元素的值
返回值
功能描述 push_front函数用于将元素添加到deque容器的头部。它接受一个参数 value,表示要添加到容器的元素的值。新元素会被添加到deque容器的开头,并且容器的大小会相应增加。

示例:

#include <iostream>
#include <deque>

int main() {
    std::deque<int> numbers;

    // 在容器头部添加元素
    numbers.push_front(30);
    numbers.push_front(20);
    numbers.push_front(10);

    // 输出:容器中的元素: 10 20 30
    std::cout << "Elements in the container: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们创建了一个空的numbers双端队列。然后,我们使用push_front函数将三个整数元素(10、20、30)依次添加到容器的头部。最后,我们通过循环遍历容器并输出其中的元素。输出结果为"容器中的元素: 10 20 30",表示成功将元素添加到了容器的头部。

size

函数名称:size

功能 返回deque容器中的元素数量
头文件 #include <deque>
原型 size_type size() const noexcept;
参数
返回值 一个size_type类型的值,表示deque容器中的元素数量
功能描述 size函数用于返回deque容器中的元素数量。它不接受任何参数,因此可以直接调用。返回值是一个无符号整数类型(size_type),表示容器中元素的数量。这个函数不会修改容器的内容,因此被声明为const成员函数,并且不会引发异常。

示例:

#include <iostream>
#include <deque>

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

    // 输出:容器中的元素数量为: 5
    std::cout << "The number of elements in the container is:" << numbers.size() << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的deque容器,并初始化了一些整数元素。然后,我们使用size函数获取容器中的元素数量,并将其输出到控制台。输出结果为"容器中的元素数量为: 5",表示deque容器中共有5个元素。

front

函数名称:front

功能 访问deque容器的第一个元素
头文件 #include <deque>
原型 reference front();
const_reference front() const;
参数
返回值 一个引用类型(reference),用于访问deque容器的第一个元素
功能描述 front函数用于访问deque容器的第一个元素。如果容器不为空,可以通过调用front函数来获取对第一个元素的引用。如果容器为空,则行为未定义。如果容器是常量容器,则返回一个const_reference类型的引用,否则返回一个reference类型的引用。可以使用返回的引用来读取或修改第一个元素的值。注意,修改第一个元素可能会影响容器的内容。

示例:

#include <iostream>
#include <deque>

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

    // 输出:容器的第一个元素为: 1
    std::cout << "The first element of the container is: " << numbers.front() << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的deque容器,并初始化了一些整数元素。然后,我们使用front函数访问容器的第一个元素,并将其输出到控制台。输出结果为"容器的第一个元素为: 1",表示成功访问到了容器的第一个元素。

back

函数名称:back

功能 访问deque容器的最后一个元素
头文件 #include <deque>
原型 reference back();
const_reference back() const;
参数
返回值 一个引用类型(reference),用于访问deque容器的最后一个元素
功能描述 back函数用于访问deque容器的最后一个元素。如果容器不为空,可以通过调用back函数来获取对最后一个元素的引用。如果容器为空,则行为未定义。如果容器是常量容器,则返回一个const_reference类型的引用,否则返回一个reference类型的引用。可以使用返回的引用来读取或修改最后一个元素的值。注意,修改最后一个元素可能会影响容器的内容。

示例:

#include <iostream>
#include <deque>

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

    // 输出:容器的最后一个元素为: 5
    std::cout << "The last element of the container is: "  << numbers.back() << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的deque容器,并初始化了一些整数元素。然后,我们使用back函数访问容器的最后一个元素,并将其输出到控制台。输出结果为"容器的最后一个元素为: 5",表示成功访问到了容器的最后一个元素。

pop_front

函数名称:pop_front

功能 从deque容器的头部删除一个元素
头文件 #include <deque>
原型 void pop_front();
参数
返回值
功能描述 pop_front函数用于从deque容器的头部删除一个元素。它不接受任何参数,因此可以直接调用。删除操作会导致容器的大小减小,并且头部之后的元素会向前移动。如果容器为空,则该函数不执行任何操作。

示例:

#include <iostream>
#include <deque>

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

    // 删除容器的第一个元素
    numbers.pop_front();

    // 输出:容器中的元素: 2 3 4 5
    std::cout << "Elements in the container:";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的deque容器,并初始化了一些整数元素。然后,我们使用pop_front函数从容器的头部删除第一个元素。最后,我们通过循环遍历容器并输出其中的元素。输出结果为"容器中的元素: 2 3 4 5",表示成功删除了容器的第一个元素。

pop_back

函数名称:pop_back

功能 从deque容器的尾部删除一个元素
头文件 #include <deque>
原型 void pop_back();
参数
返回值
功能描述 pop_back函数用于从deque容器的尾部删除一个元素。它不接受任何参数,因此可以直接调用。删除操作会导致容器的大小减小。如果容器为空,则该函数不执行任何操作。

示例:

#include <iostream>
#include <deque>

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

    // 删除容器的最后一个元素
    numbers.pop_back();

    // 输出:容器中的元素: 1 2 3 4
    std::cout << "Elements in the container:";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的deque容器,并初始化了一些整数元素。然后,我们使用pop_back函数从容器的尾部删除最后一个元素。最后,我们通过循环遍历容器并输出其中的元素。输出结果为"容器中的元素: 1 2 3 4",表示成功删除了容器的最后一个元素。

empty

函数名称:empty

功能 检查deque容器是否为空
头文件 #include <deque>
原型 bool empty() const noexcept;
参数
返回值 一个bool值,表示容器是否为空
功能描述 empty函数用于检查deque容器是否为空。它不接受任何参数,因此可以直接调用。如果容器为空,则返回true;如果容器不为空,则返回false。这个函数不会修改容器的内容,因此被声明为const成员函数,并且不会引发异常。

示例:

#include <iostream>
#include <deque>

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

    // 检查容器是否为空
    if (numbers.empty()) {
        std::cout << "container is empty" << std::endl;
    } else {
        std::cout << "container isn't empty" << std::endl;
    }

    return 0;
}

在这个示例中,我们创建了一个名为numbers的deque容器,并初始化了一些整数元素。然后,我们使用empty函数检查容器是否为空。根据检查的结果,我们输出相应的信息。如果容器为空,则输出"容器为空";如果容器不为空,则输出"容器不为空"。

七、list容器中常见函数

sort

函数名称:sort

功能 对list容器中的元素进行排序
头文件 #include <list>
原型 void sort();
参数
返回值
功能描述 sort函数用于对list容器中的元素进行排序。它不接受任何参数,因此可以直接调用。该函数会按照默认的升序方式对容器中的元素进行排序。排序操作会改变容器的内容,使得其中的元素按照一定的顺序排列。如果list容器中的元素具有自定义的比较函数,也可以通过传递自定义的比较函数作为参数来进行排序。

示例:

#include <iostream>
#include <list>

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

    // 对容器中的元素进行排序
    numbers.sort();

    // 输出:容器中的元素: 1 2 3 4 5
    std::cout << "容器中的元素: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的list容器,并初始化了一些整数元素。然后,我们使用sort函数对容器中的元素进行排序。最后,我们通过循环遍历容器并输出其中的元素。输出结果为"容器中的元素: 1 2 3 4 5",表示成功对容器中的元素进行了升序排序。

八、array容器中常见函数

at

函数名称:at

功能 访问array容器指定位置的元素
头文件 #include <array>
原型 reference at(size_type pos);
const_reference at(size_type pos) const;
参数 pos:要访问的元素的位置
返回值 一个引用类型(reference),用于访问array容器指定位置的元素
功能描述 at函数用于访问array容器中指定位置的元素。它接受一个参数 pos,表示要访问的元素的位置。位置从0开始,即第一个元素的位置为0,第二个元素的位置为1,依此类推。如果指定的位置超出了容器的范围,at函数会抛出std::out_of_range异常。如果容器是常量容器,则返回一个const_reference类型的引用,否则返回一个reference类型的引用。可以使用返回的引用来读取或修改指定位置的元素的值。

示例:

#include <iostream>
#include <array>

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

    // 输出容器中的第三个元素
    std::cout << "容器中的第三个元素为: " << numbers.at(2) << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为numbers的array容器,并初始化了一些整数元素。然后,我们使用at函数访问容器中的第三个元素,并将其输出到控制台。输出结果为"容器中的第三个元素为: 3",表示成功访问到了指定位置的元素。

九、STL容器中的迭代器

1、什么是STL容器中的迭代器

STL容器中的迭代器是用于访问容器中元素的工具。它可以看作是一种类似于指针的对象,可以指向容器中的一个或多个元素,并可以通过迭代器来遍历或操作容器中的元素。

迭代器的主要作用是提供容器元素的访问接口,它可以用来遍历容器中的元素,获取、修改、删除元素,以及支持一些算法等操作。通过迭代器,可以实现容器中的元素遍历、查找、排序、筛选等各种操作。

迭代器的实现方式与容器的底层实现密切相关。通常情况下,迭代器的实现都依赖于容器内部的数据结构,比如指针、数组下标、链表节点等等。对于不同的容器类型,其迭代器的实现方式也可能不同,但是它们都遵循了一些公共的规则和接口,比如迭代器的操作符(如*、++、--等)和迭代器类别(如输入迭代器、输出迭代器、正向迭代器、双向迭代器、随机访问迭代器等)等。

需要注意的是,在使用迭代器时,需要注意迭代器的有效性,即避免在容器被修改或销毁后,仍然使用已失效的迭代器。此外,还需要注意迭代器的遍历范围和访问权限等问题,以避免出现错误的操作和行为。

总之,STL中的迭代器是非常重要的一部分,它提供了容器元素的访问接口,为算法的实现和容器的操作提供了便利和灵活性。掌握迭代器的使用方法和技巧,对于理解和使用STL容器和算法有着至关重要的作用。

2、迭代器的种类

迭代器类型 对应的命名规则
正向迭代器 iterator
常量正向迭代器 const_iterator
反向迭代器 reverse_iterator
反向迭代器 reverse_iterator
常量反向迭代器 const_reverse_iterator

3、迭代器的使用

例程:

#include <iostream>
#include <vector>

using namespace std;

// 通过迭代器获取元素
void display(vector<string>& name)
{
    // 定义迭代器对象
    vector<string>::iterator p;

    // // 遍历数据
    // for(p = name.begin(); p != name.end(); p++)
    // {
    //     cout << *p << endl;
    // }
    // cout << endl;

    // 使用范围-based for循环遍历元素
    for (const string& element : name)
    {
        cout << element << endl;
    }
    cout << endl;
}

int main()
{
    // 创建对象
    vector<string> name;

    // 容器赋值
    name.assign(3, "jack");

    // 定义一个操作迭代器
    vector<string>::iterator it;

    // 插入元素到开头
    it = name.begin();
    name.insert(it, "Tom");

    // 尾部插入元素1
    it = name.end();
    name.insert(it, "Rose");

    name.push_back("dz");

    // 打印数据
    display(name);

    // 寻找某个元素
    vector<string>::iterator we;
    we = name.begin();
    for(const string& element : name)
    {
        if (*we == "Tom")
        {
            cout << "find this people!\n";
            break;
        }
        
    }
    cout << endl << endl;

    // 删除数据
    name.erase(name.begin() + 3);
    display(name);

    return 0;
}

4、迭代器的函数

distance

函数名称:distance

功能 计算两个迭代器之间的距离
头文件 #include <iterator>
原型 template <class InputIt> typename iterator_traits<InputIt>::difference_type distance(InputIt first, InputIt last);
参数 first:范围的起始迭代器
last:范围的结束迭代器(不包含在范围内)
返回值 两个迭代器之间的距离,以 difference_type 类型表示
功能描述 distance 函数用于计算两个迭代器之间的距离。它接受两个参数,分别是范围的起始迭代器 first 和结束迭代器 last,它们定义了一个范围。函数会返回两个迭代器之间的距离,以 difference_type 类型表示。difference_type 是迭代器类型的内部定义,用于表示两个迭代器之间的距离。请注意,范围是左闭右开的,即不包含 last 迭代器指向的元素。

示例:

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

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

    // 计算容器中元素的个数
    auto count = std::distance(numbers.begin(), numbers.end());

    std::cout << "the number of the vector is: " << count << std::endl;

    return 0;
}

在这个示例中,我们创建了一个名为 numbers 的 vector 容器,并初始化了一些整数元素。然后,我们使用 distance 函数计算容器中元素的个数,即范围的长度。最后,我们将计算得到的个数输出到控制台。输出结果为 "容器中的元素个数为: 5",表示成功计算出了容器中元素的个数。

十、STL中的算法

1、非可变序列算法

不修改序列中的元素,只是根据指定的规则对其进行操作,比如查找、计数、比较、查找极值等,常见的算法有 findcountequalminmax

find

函数名称:find

功能 在指定范围内查找指定值的元素
头文件 #include <algorithm>
原型 ForwardIt find(ForwardIt first, ForwardIt last, const T& value);
参数 first:范围的起始迭代器
last:范围的结束迭代器(不包含在范围内)
value:要查找的值
返回值 指向找到的元素的迭代器,如果未找到,则返回last
功能描述 find函数用于在指定范围内查找指定值的元素。它接受三个参数,分别是范围的起始迭代器 first、范围的结束迭代器 last,以及要查找的值 value。函数会在范围内逐个比较元素的值,直到找到与 value 相等的元素,或者遍历完整个范围。如果找到了匹配的元素,则返回指向该元素的迭代器;如果未找到匹配的元素,则返回 last 迭代器。要注意的是,范围是左闭右开的,即不包含 last 迭代器指向的元素。

示例:

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

using namespace std;

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

    // 在容器中查找值为3的元素
    auto it = find(numbers.begin(), numbers.end(), 3);

    if (it != numbers.end())
        cout << "find 3!\n";
    else
        cout << "not find 3!\n";
    

    return 0;
}

在这个示例中,我们创建了一个名为 numbers 的 vector 容器,并初始化了一些整数元素。然后,我们使用 find 函数在容器中查找值为 3 的元素。如果找到了匹配的元素,则输出 "找到了值为 3 的元素";如果未找到匹配的元素,则输出 "未找到值为 3 的元素"。

sort

算法名称:sort

功能 对指定范围内的元素进行排序
头文件 #include <algorithm>
原型 void sort(ForwardIt first, ForwardIt last, Compare comp);
参数 first:范围的起始迭代器
last:范围的结束迭代器(不包含在范围内)
comp:可调用对象,用于定义排序的比较方式(默认为 less
返回值
功能描述 sort 算法用于对指定范围内的元素进行排序。它接受三个参数,分别是范围的起始迭代器 first、范围的结束迭代器 last,以及一个可调用对象 comp,用于定义排序的比较方式。排序操作会改变范围内元素的顺序,使其按照指定的比较方式进行排序。默认情况下,排序方式是升序排序,使用的比较函数是 less。如果指定的比较方式 comp 返回 true,则表示第一个元素应该排在前面;如果返回 false,则表示第一个元素应该排在后面。可调用对象 comp 可以是函数指针、函数对象或 lambda 表达式。

示例:

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

using namespace std;

// 自定义比较函数对象
struct CustomCompare
{
    bool operator()(int a, int b)const
    {
        return a > b;
    }
};

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

    // 使用默认的升序排序
    sort(numbers.begin(), numbers.end());
    cout << "Ascending sort results: ";
    for(int num : numbers)
    {
        cout << num << " ";
    }
    cout << endl;

    // 使用自定义的降序排序
    sort(numbers.begin(), numbers.end(), CustomCompare());
    cout << "Descending order results: ";
    for(int num : numbers)
    {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

sort 函数的原型有三个参数:void sort(ForwardIt first, ForwardIt last, Compare comp);。其中:

  • firstlast 是指向要排序的范围的起始位置和结束位置的迭代器。
  • comp 是一个可调用对象,用于指定排序的比较方式。它可以是函数指针、函数对象或 lambda 表达式。通过传递不同的 comp,可以实现不同的排序方式。

在第一个调用 sort 函数时,没有提供第三个参数,因此默认使用了 < 运算符进行升序排序。在这种情况下,sort(numbers.begin(), numbers.end()); 的效果等同于 sort(numbers.begin(), numbers.end(), less<int>());

transform

算法名称:transform

功能 对指定范围内的元素进行转换操作
头文件 #include <algorithm>
原型 OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op);

OutputIt transform(InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOperation binary_op);
参数 first1:输入范围的起始迭代器
last1:输入范围的结束迭代器(不包含在范围内)
d_first:输出范围的起始迭代器
unary_op:一元操作函数对象,用于对输入范围中的每个元素进行转换(第一个原型)
first2:第二个输入范围的起始迭代器(第二个原型)
binary_op:二元操作函数对象,用于对两个输入范围中的对应元素进行转换(第二个原型)
返回值 输出范围的结束迭代器
功能描述 transform 算法用于对指定范围内的元素进行转换操作,并将结果存储在另一个范围中。它接受一个或两个范围作为输入,并提供一个操作函数对象来定义元素的转换规则。transform 算法将输入范围中的每个元素应用于操作函数对象,然后将转换后的结果存储到输出范围中。函数对象可以是函数指针、函数对象或 lambda 表达式。第一个原型用于一元转换操作,它将一元操作函数对象 unary_op 应用于输入范围的每个元素。第二个原型用于二元转换操作,它将二元操作函数对象 binary_op 应用于两个输入范围中对应的元素。输出范围的起始迭代器 d_first 指向第一个转换结果的位置。注意,转换操作不会改变原始范围的内容。

示例:

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

using namespace std;

// 自定义一元操作函数对象
struct Square
{
    int operator()(int num)const
    {
        return num * num;
    }
};

int main()
{
    vector<int> numbers = {1, 2, 3, 4, 5};
    vector<int> result(numbers.size());

    // 使用一元操作函数对象对元素进行平方转换
    transform(numbers.begin(), numbers.end(), result.begin(), Square());
    cout << "result: ";
    for(int num : result)
    {
        cout << num << " ";
    }
    cout << endl;
    
    return 0;
}

在这个示例中,我们创建了一个名为 numbers 的 vector 容器,并初始化了一些整数元素。然后,我们创建了另一个 vector 容器 result,用于存储转换后的结果。接下来,我们使用 transform 算法对 numbers 容器中的每个元素进行平方转换,并将转换后的结果存储到 result 容器中。最后,我们通过循环遍历输出转换结果。输出结果为 "转换结果:1 4 9 16 25",表示成功对元素进行了平方转换,并将结果存储在 result 容器中。

2、可变序列的算法

会修改序列中的元素,比如拷贝、替换、删除、旋转、填充等,常见的算法有 copyreplaceremoverotatefill

3、排序算法

对序列进行排序或其他相关操作,比如排序,合并,集合运算,二分查找等,常见的算法有sort,mergeset_unionbinary_search

4、数值运算

适用于数值类型的序列,提供了常见的数学操作,比如求和,积,均值,内积,反转等,常见的算法有 accumulateinner_productadjacent_differencereverse 等。

5、常见的容器和算法

在STL提供能够在各种容器中通用的算法大概有70多种,如插入,删除,查找等,但是容器中的数据是各种类型,所以对于某一种操作而言,只是数据类型不一样而已,但是功能一致。

故而,算法就是函数的模板,算法通过迭代器来操作容器的元素。

在C++中,算法的实现定义在头文件:<algorithm>


std::find 举一个简单的例程,用来在一个整形向量中查找特定的值:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    vector<int> vec{ 2, 4, 6, 8, 10 };

    int value = 6;
    auto it = find(vec.begin(), vec.end(), value); // find函数在aigorithm中

    if(it != vec.end())
    {
        std::cout << "Found " << value << " at index " << std::distance(vec.begin(), it) << std::endl;
    }
    else
    {
        std::cout << "Value " << value << " not found" << std::endl;
    }

    return 0;
}


函数模板的返回值是一个迭代器,指向第一个值为 value 的元素。如果找不到,则返回 last 迭代器。

这个函数模板的实现思路比较简单,它通过迭代遍历区间 [first, last) 中的每一个元素,与 value 进行比较,如果找到了一个值为 value 的元素,则返回其迭代器,否则继续遍历,直到遍历完整个区间或找到第一个值为 value 的元素为止。

6、仿函数

STL 中的仿函数(Functor)是一种行为类似函数的对象,也称为函数对象。它是一种重载了函数调用运算符 () 的类,可以像普通函数一样使用。当使用仿函数时,可以将它看作是一个可调用对象,它也具有某些操作符的行为,可以用于算法中的排序、查找、替换、累加等操作,也可以用于自定义的数据结构中。

当一个类中重载了operator()运算符后,该类就可以被视为一个函数对象或者函数符号,可以像函数一样被调用。因此,类的构造函数也可以被视为一个仿函数,只要在调用时使用了该类对象的operator()运算符重载函数。所以,在实际使用中,无论是定义一个仿函数对象还是直接使用该类的构造函数,都可以视为使用了仿函数。

STL 提供了很多内置的仿函数,包括一元运算符、二元运算符和关系运算符等。例如,std::less 和 std::greater 是两个常用的二元运算符仿函数,它们可以用于比较两个对象的大小关系。另外,STL 还提供了一些适配器,可以用于将一个仿函数转换为另一个仿函数,或者将一个函数指针转换为一个仿函数。适配器包括 std::bind、std::function、std::mem_fn 等。

使用仿函数可以使代码更加灵活和可复用。通过定义自己的仿函数,我们可以实现特定的操作,并将它们传递给算法中使用,从而实现我们自己的定制化需求。此外,使用仿函数还可以避免不必要的函数调用开销,从而提高程序的效率。


格式:

class 类名
{
public:
	返回值类型 operator()(参数列表)
	{
		函数体;
	}
};

例程:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Compare
{
    bool flag;
public:
    Compare(bool flag = true): flag(flag) {}

    // ()重载
    bool operator()(int& a, int& b)
    {
        if(flag)
        {
            return a < b;
        }
        else
        {
            return a > b;
        }
    }

};

int main()
{
    // 缺省数组
    int arr[7] = {3, 6, 2, 55, 32, 1, 9};
    vector<int> L(arr, arr + 7);
    for(int n : L)
    {
        cout << n << "---";
    }
    cout << endl;

    // 仿函数,sort算法
    sort(L.begin(), L.end(), Compare(false));
    for(int n : L)
    {
        cout << n << "---";
    }
    cout << endl;

    // 仿函数,transform算法
    int buf[7] = {0};
    transform(L.begin(), L.end(), buf, [](int x)
    {
        return x + 1;
    });
    for(int n : buf)
    {
        cout << n << "---";
    }
    cout << endl;

    return 0;
}

[](int x){return x + 1;}是一个Lambda表达式,它没有捕获变量,只有一个参数int x,返回值类型为int,函数体为return x+1;,即计算x+1的值并返回。


再看一个例程:

用仿函数完成 5的3次方的计算

即用仿函数实现数学函数:pow(5,3);,数学函数的头文件:<math.h>

#include <iostream>
#include <math.h>
using namespace std;

class MyPow
{
public:
    int operator()(int x, int y) // 这里之前加了引用,但是报错了,问了下ChatGPT,加了引号会将参数声明为字符串类型,导致编译器无法找到匹配的函数,从而报错
    {
        if(0 == y)
        {
            return 1;
        }

        int temp = x;
        for(int i = 1; i < y; i++)
        {
            x *= temp;
        }

        return x;
    }
};

int main()
{
    cout << pow(5, 3) << endl;
    cout << "----------\n";
    MyPow my;
    cout << my(5, 3) << endl;

    return 0;
}

十一、STL的关键字

1、auto关键字

auto关键字用于修饰变量,系统会根据变量的初始值自动判断变量的类型。

使用auto可以简化代码,避免了手动指定类型时可能出现的错误和繁琐。例如:

auto i = 10;  // i自动推断为int类型
auto d = 3.14;  // d自动推断为double类型
auto s = "hello";  // s自动推断为const char *类型

需要注意的是,auto关键字只能用于声明变量,不能用于函数返回类型、函数参数类型、类成员类型等场合。


#include <iostream>
using namespace std;

int main()
{
    auto number = 42; // 自动推导为 int 类型
    auto pi = 3.14; // 自动推导为 double 类型
    auto name = "John"; // 自动推导为 const char* 类型
    auto flag = true; // 自动推导为 bool 类型

    cout << "number: " << number << endl;
    cout << "pi: " << pi << endl;
    cout << "name: " << name << endl;
    cout << "flag: " << flag << endl;

    return 0;
}

2、decltype关键字

decltype 是 C++11 引入的关键字,用于从表达式中推导出类型。它可以用于在编译时获取表达式的类型,并将其作为变量或函数的返回类型。

decltype 的用法如下:

decltype(expression) variable_name;

其中,expression 是一个任意的表达式,variable_name 是要声明的变量名。decltype 关键字将根据表达式的类型推导出变量的类型。

下面是一些使用 decltype 的例子:

int x = 42;
decltype(x) y; // 推导出变量 y 的类型为 int

const float& foo();
decltype(foo()) result; // 推导出 result 的类型为 const float&

struct MyClass
{
    int member;
};

decltype(MyClass::member) data; // 推导出 data 的类型为 int

int arr[] = {1, 2, 3, 4, 5};
decltype(arr) array; // 推导出 array 的类型为 int[5]

decltype 可以非常方便地根据表达式推导出变量的类型,尤其在模板编程、泛型编程和元编程等情况下非常有用。它在标准模板库(STL)中的算法、迭代器和容器等各个组件中广泛使用。


#include <iostream>

using namespace std;

int main()
{
    int x = 42;
    decltype(x) y = 10.01; // 推导出变量 y 的类型为 int

    cout << "x: " << x << endl;
    cout << "y: " << y << endl;

    return 0;
}

3、lambda表达式

回看一下sort算法,他的原型是:void sort(ForwardIt first, ForwardIt last, Compare comp);,其中第三份参数 comp 是一个可调用参数,可以是函数指针、函数对象或 lambda 表达式,但前两者都存在一定的问题。

以下是对使用全局函数和仿函数作为比较对象的一些问题的详细说明:

  1. 全局函数:可以将全局函数作为比较对象传递给 sort 算法,但是存在以下问题:

    • 全局函数需要在命名空间中定义,可能会导致全局命名空间污染,特别是在有多个排序需求时。
    • 全局函数的参数列表是固定的,如果需要比较的对象类型发生变化,需要定义多个全局函数进行重载。
  2. 仿函数:可以定义一个类,重载 operator() 运算符,并将其作为比较对象传递给 sort 算法,但是存在以下问题:

    • 为了实现一个简单的比较操作,需要定义一个完整的类,增加了代码的复杂性。
    • 在代码中需要显式创建并使用该类的对象,增加了代码的冗余。

为了解决上述问题,C++11 引入了 lambda 表达式,它是一种简洁的匿名函数表示方式。使用 lambda 表达式作为 sort 算法的第三个参数具有以下优点:

  • 不需要定义全局函数或仿函数,使代码更加简洁和紧凑。
  • 可以直接在需要的地方内联定义比较函数,不会产生全局命名空间污染。
  • 可以轻松地捕获外部变量,使排序算法更加灵活。

使用 lambda 表达式作为比较对象的示例:

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

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

    // 使用 lambda 表达式作为比较对象进行降序排序
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;
    });

    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

在上述示例中,我们使用 lambda 表达式 [](int a, int b) { return a > b; } 作为比较对象,实现了降序排序。lambda 表达式内联定义了一个匿名函数,可以灵活地进行比较操作,避免了全局函数或仿函数的定义。


#include <iostream>

using namespace std;

int main()
{
    int a = 10, b = 20;

    // lambda形参赋值计算
    auto result = [](int x, int y) -> int { // -> int 是返回类型声明,指定了 lambda 函数的返回类型为整型。
        return x + y;
    };
    int ret = result(a, b);
    cout << ret << endl;

    // lambda捕获列表计算
    auto result2 = [a, &b]() mutable -> int {
        a += 1;
        b = 50;
        return a + b;
    };
    int ret2 = result2();
    cout << ret2 << endl;

    // lambda最简方式
    auto result3 = [=]{
        cout << a << " " << b << endl;
    };
    result3();

    return 0;
}

在上述程序中,我们使用 lambda 表达式展示了三种不同的形式:

  1. 形参赋值计算:使用 [ ] 捕获列表为空,lambda 函数接受两个整数参数,返回它们的和。在 result 中调用 lambda 函数并输出结果。
  2. 捕获列表计算:使用 [a, &b] 捕获列表,其中 a 以传值方式捕获,b 以引用方式捕获。lambda 函数中对 a 进行自增操作并对 b 进行赋值操作,然后返回它们的和。在 result2 中调用 lambda 函数并输出结果。
  3. 最简方式:使用 [=] 捕获列表,表示以传值方式捕获所有外部变量。lambda 函数中输出 ab 的值。在 result3 中调用 lambda 函数。

mutable 是lambda函数的修饰符,默认情况下,lambda表达式总是一个const函数,mutable可以取消常量性,可以在lambda函数修改参数的值。在lambda函数体内部,使用 a += 1; 可以修改 a 的值,因为在捕获列表中 a 是按值捕获的,被视为常量,但使用 mutable 关键字后,可以改变它的值。而 b 是按引用捕获的,所以可以直接修改它的值,无需使用 mutable 修饰符。


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

using namespace std;

bool comp(int a, int b)
{
    return a < b;
}

int main()
{
    // 定义数组
    int arr[5] = {5, 9, 8, 6, 4};

    // 定义容器
    vector<int> v(arr, arr + 5);

    // 打印获取
    for(auto n : v)
    {
        cout << n << " ";
    }
    cout << endl;

    // 排序算法函数
    // sort(v.begin(), v.end(), comp); // 全局函数

    // lambda表达式
    sort(v.begin(), v.end(), [=](int a, int b){
        return a > b;
    });

    // 打印获取
    for(auto n : v)
    {
        cout << n << " ";
    }
    cout << endl;

    return 0;
}

posted @ 2023-07-15 16:09  fhzy  阅读(409)  评论(0)    收藏  举报