当前位置: 首页 > article >正文

C++学习(七)(标准库+STL(iotstream公司,日期/时间,器皿,算法,迭代器,多线程))

C++ 标准模板库 (STL)

C++ 标准模板库 (STL) 是头文件的集合,提供了多种数据结构、算法和函数,以简化您的 C++ 编码体验。STL 的主要目的是通过提供一套现成的有用工具来节省时间并提高效率。STL 最常用的功能可以分为三大类:容器、算法和迭代器。

器皿

容器是 C++ 中用于数据存储和作的数据结构。它们分为四种类型:序列容器、关联容器、无序关联容器和容器适配器。

  • 序列容器:这些是按顺序存储元素的线性数据结构。示例包括:

  • std::vector:在运行时增长和收缩的动态数组。
    std::vector<int> my_vector;
  • std::list: 双向链表
    std::list<int> my_list;
  • std::deque允许在两端插入和删除的双端队列。
    std::deque<int> my_deque;
  • Associative Containers:这些容器以具有唯一键的排序方式存储数据。示例包括:

    • std::set:按键排序的唯一元素集合。
      std::set<int> my_set;
    • std::map:按键排序的键值对集合。
      std::map<std::string, int> my_map;
  • 无序关联容器:这些容器使用哈希表以无序方式存储数据。示例包括:

    • std::unordered_set:无特定顺序的唯一元素的集合。
      std::unordered_set<int> my_unordered_set;
      
    • std::unordered_map:无特定顺序的键值对集合。
      std::unordered_map<std::string, int> my_unordered_map;
  • 容器适配器:这些是基于其他现有容器的容器。示例包括:

    • std::stack:基于 deque 或 list 的 LIFO 数据结构。
      std::stack<int> my_stack;
    • std::queue:基于 deque 或 list 的 FIFO 数据结构。
      std::queue<int> my_queue;
    • std::priority_queue:基于 vector 或 deque 的排序队列。
      std::priority_queue<int> my_priority_queue;

算法

STL 提供了几种通用算法,可用于对存储在容器中的数据执行各种作。它们分为五类:非修饰序列算法、修饰序列算法、排序算法、排序范围算法和数值算法。

一些示例包括 、 、 和 。std::find std::replace std::sort std::binary_search

例如,要对向量进行排序,您可以使用以下代码:
 

std::vector<int> my_vec = {4, 2, 5, 1, 3};
std::sort(my_vec.begin(), my_vec.end());

迭代器

迭代器是 STL 中的一个基本概念,因为它们提供了一种访问容器中元素的统一方法。迭代器可以被认为是指针的一种高级形式。

每个容器都有自己的迭代器类型,可用于遍历元素和修改值。最常见的迭代器作是 and,用于分别获取指向容器最后一个元素的第一个和最后一个元素的迭代器。begin()end()

例如,要遍历向量并打印其元素,可以使用以下代码:
 

std::vector<int> my_vec = {1, 2, 3, 4, 5};
for (auto it = my_vec.begin(); it != my_vec.end(); ++it) {
    std::cout << *it << " ";
}

iostream 公司

iostream是 C++ 标准库中的一个标头,它为基本输入和输出 (I/O)作提供功能。I/O 流有助于程序与各种源 (如控制台、文件或其他程序) 之间的通信。

iostream包括以下类:

  • istream:用于来自输入源的输入作。
  • ostream:用于对输出目标进行输出作。
  • iostream:输入和输出作的 和 的组合。istreamostream

这些类继承自基类 和 。iosios_base

此外,还定义了几个对象,这些对象是这些类的实例,并表示标准输入和输出流:iostream

  • cin:要从标准输入中读取的对象,通常对应于键盘。istream
  • cout:要写入标准输出的对象,通常是控制台。ostream
  • cerr:要写入标准错误输出的对象,通常用于显示错误消息。ostream
  • clog:一个对象,类似于 ,但其输出可以缓冲。ostreamcerr

以下是有关用于输入和输出作的一些代码示例:iostream

#include <iostream>

int main() {
    int a;
    std::cout << "Enter a number: ";
    std::cin >> a;
    std::cout << "You entered: " << a << std::endl;
    return 0;
}
#include <iostream>

int main() {
    std::cerr << "An error occurred." << std::endl;
    std::clog << "Logging information." << std::endl;
    return 0;
}

请记住在使用以下功能时包含 Header:iostream

#include <iostream>

iostream 可以理解为 C++ 中程序和外界沟通的“水管系统”。它负责让程序从外部(比如键盘、文件)接收数据,或者向外部(比如屏幕、文件)发送数据。


核心功能 🚰

  1. 输入流 istream
    像一根“吸水的水管”,专门用来读取数据。例如:

    int age;
    std::cin >> age;  // 从键盘(标准输入)读取数据到变量 age
  2. 输出流 ostream
    像一根“喷水的水管”,专门用来输出数据。例如:

    std::cout << "Hello!";  // 把文字输出到屏幕(标准输出)
  3. 双向流 iostream
    既能输入又能输出的“双向水管”(比如文件读写)。


四个常用的“水管开关” 🔘

  1. std::cin
    对应键盘输入,比如用户输入数字、文字:

    std::string name;
    std::cin >> name;  // 用户通过键盘输入名字
  2. std::cout
    对应屏幕输出,用于显示正常信息:

    std::cout << "结果:" << 42;  // 屏幕显示:结果:42
  3. std::cerr
    专门输出错误信息(直接显示,不缓冲),比如:

    if (error) {
        std::cerr << "程序崩溃了!";  // 立即显示错误
    }
  4. std::clog
    输出日志信息(可能先攒着再一起显示),适合记录运行过程:

    std::clog << "开始处理数据...";  // 日志可能稍后显示

为什么要用 iostream? 🤔

  • 简单直观:用 << 和 >> 操作符就能输入输出,类似水流方向。

  • 自动适配:能处理各种类型的数据(整数、字符串、自定义对象)。

  • 错误隔离:用 cerr 单独输出错误,避免和正常信息混在一起。


举个完整例子 🛠️

#include <iostream>  // 引入水管系统的工具箱

int main() {
    int num;
    std::cout << "请输入一个数字:";  // 屏幕提示
    std::cin >> num;                // 从键盘读取数字
    std::cout << "你输入的是:" << num;  // 输出结果
    return 0;
}

总结iostream 是 C++ 的“水管工”,帮你把数据从键盘、文件等地方“吸进来”,或者“喷出去”。最常用的工具是 cin(读)、cout(写)、cerr(报错),记住它们就像不同用途的水龙头!🚰💻

C++ 日期时间

在 C++ 中,您可以使用该库处理日期和时间,该库是标准库 (STL) 的一部分。该库提供了各种数据类型和函数来表示和作持续时间、时间点和时钟。chronochrono

期间

A 表示时间跨度,可以用各种单位表示,例如秒、分钟、小时等。要创建持续时间,请使用 template 类。常见的预定义持续时间类型包括:durationstd::chrono::duration

  • std::chrono::seconds
  • std::chrono::minutes
  • std::chrono::hours

例:

#include <iostream>
#include <chrono>

int main() {
    std::chrono::seconds sec(5);
    std::chrono::minutes min(2);
    std::chrono::hours hr(1);
    return 0;
}

std::chrono::duration 可以理解为一个“时间容器”,专门用来表示一段时间的长度,并且这个容器上贴着“单位标签”(比如秒、分钟、小时),方便你直接按需操作时间。


举个生活例子 🌰

假设你有几个瓶子,分别用来装水:

  • 一个瓶子上贴着“秒”,容量是“10秒”。

  • 另一个贴着“分钟”,容量是“5分钟”。

  • 第三个贴着“小时”,容量是“2小时”。

这些瓶子本质上装的都是时间,但单位不同。std::chrono::duration 的作用就是帮你管理这些不同单位的时间,并且可以自动换算(比如知道“1分钟 = 60秒”)。


怎么用? 🛠️

  1. 直接用预定义的“时间容器”
    C++ 已经帮你准备好了常见单位:

    #include <chrono>  // 必须包含这个头文件
    
    auto sec = std::chrono::seconds(30);  // 30秒的容器
    auto min = std::chrono::minutes(2);   // 2分钟的容器
    auto hour = std::chrono::hours(1);    // 1小时的容器
     
  2. 时间可以互相转换
    编译器知道“1分钟=60秒”,所以可以直接运算:

    auto total = sec + min;  // 30秒 + 2分钟 → 最终自动转成秒(30 + 120 = 150秒)
     
  3. 需要时强制转换单位
    如果要将“分钟”转成“小时”,可以用 duration_cast

    auto time_in_hours = std::chrono::duration_cast<std::chrono::hours>(min); 
    // 2分钟 → 转成小时会是0小时(因为不够1小时)
     

为什么要设计成模板? 🤔

  • 类型安全:避免你意外地把“秒”和“分钟”直接相加却不转换单位。

  • 灵活扩展:你可以自己定义新单位(比如“半秒钟”或“游戏帧数”)。


实际应用场景 🎯

  • 倒计时功能:用 seconds 管理剩余秒数。

  • 性能测试:计算代码运行耗时(用 milliseconds 或 microseconds)。

  • 游戏开发:用 duration 管理技能冷却时间或动画时长。


一个完整例子 🌟

#include <iostream>
#include <chrono>

int main() {
    using namespace std::chrono;  // 简化代码

    seconds timeout = 30s;       // C++14 起支持字面量语法(30秒)
    minutes meeting_time = 2min; // 2分钟

    // 计算总时间(自动转成秒)
    auto total_seconds = timeout + meeting_time;
    std::cout << "总时长:" << total_seconds.count() << "秒"; // 输出:150秒

    // 转成小时(强制转换)
    hours total_hours = duration_cast<hours>(total_seconds);
    std::cout << "或 " << total_hours.count() << "小时";      // 输出:0小时
    return 0;
}
 

总结std::chrono::duration 是 C++ 的时间管家,帮你把“时间长度”和“单位”打包在一起,避免单位混淆,还能自动换算。记住它就像贴了标签的容器,让时间计算既直观又安全!⏱️

时间点

A 表示特定的时间点。它通常是使用 duration 和 clock 的组合创建的。在 C++ 中,该库提供了三种 clock type:time_pointchrono

  • std::chrono::system_clock:表示系统范围的实时挂钟。
  • std::chrono::steady_clock:表示保证永不调整的单调时钟。
  • std::chrono::high_resolution_clock:表示具有最短时钟周期的时钟。

例:

#include <iostream>
#include <chrono>

int main() {
    std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();
    return 0;
}

时钟

时钟提供对当前时间的访问。它由以下元素组成:

  • time_point:一个特定的时间点。
  • duration:两个时间点之间的持续时间。
  • now():返回当前时间点的静态函数。

例:

#include <iostream>
#include <chrono>

int main() {
    // Get the current time_point using system_clock
    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();

    // Get the time_point 1 hour from now
    std::chrono::system_clock::time_point one_hour_from_now = now + std::chrono::hours(1);
    return 0;
}

将时间点转换为日历时间

要将时间点转换为日历表示形式,您可以使用该函数。std::chrono::system_clock::to_time_t

例:

#include <iostream>
#include <chrono>
#include <ctime>

int main() {
    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
    std::time_t now_c = std::chrono::system_clock::to_time_t(now);
    std::cout << "Current time: " << std::ctime(&now_c) << std::endl;
    return 0;
}

这总结了使用该库在 C++ 中处理日期和时间的基本功能。您可以在 C++ 参考中找到更高级的功能,例如强制转换持续时间和时间算术。chrono

C++ 容器

C++ 容器是标准模板库 (STL) 的一部分,它提供用于存储和组织数据的数据结构。有几种类型的容器,每种类型都有自己的特性和使用案例。在这里,我们讨论一些常用的容器:

1. 向量

向量是可以根据需要调整自身大小的动态数组。它们将元素存储在连续的内存位置,允许使用索引进行快速随机访问。


 

#include <iostream>
#include <vector>

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

    vec.push_back(6); // Add an element to the end

    std::cout << "Vector contains:";
    for (int x : vec) {
        std::cout << ' ' << x;
    }
    std::cout << std::endl;
}

2. 列表

列表是一个双向链表,它允许在恒定时间内从任何位置插入或删除元素。它不支持随机访问。对于需要经常在中间插入或删除元素的方案,列表比向量更好。

#include <iostream>
#include <list>

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

    lst.push_back(6); // Add an element to the end
    
    std::cout << "List contains:";
    for (int x : lst) {
        std::cout << ' ' << x;
    }
    std::cout << std::endl;
}

3. 地图

映射是存储键值对的关联容器。它支持根据值的键检索值。默认情况下,键按升序排序。

#include <iostream>
#include <map>

int main() {
    std::map<std::string, int> m;

    m["one"] = 1;
    m["two"] = 2;

    std::cout << "Map contains:" << std::endl;
    for (const auto &pair : m) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
}

std::map 可以理解为一个“智能字典”,它用“键-值对”的方式存储数据,并且自动按“键”的顺序整理内容,让你能快速通过“键”找到对应的“值”。


🌰 举个现实例子

假设你有一个电话簿

  • 键(Key):朋友的名字(比如 "Alice"、"Bob")

  • 值(Value):对应的电话号码(比如 "123456"、"789000")

std::map 的作用就是帮你把名字和号码一一对应存储,并且自动按名字的字母顺序排序,查找时非常高效。


🛠️ 核心特性

  1. 键值对存储
    每个条目都是一个 键(key) + 值(value) 的组合,类似字典的“单词 + 解释”。

    #include <map>
    std::map<std::string, int> ageBook = {
        {"Alice", 25},
        {"Bob", 30}
    };
     
  2. 自动排序
    默认按“键”的升序(从小到大)排列。比如插入 "Charlie" 后,顺序会自动调整为 Alice → Bob → Charlie

  3. 唯一键
    每个“键”必须是唯一的,重复插入相同键会覆盖旧值:

    ageBook["Alice"] = 26;  // 覆盖 Alice 的年龄为 26
     
  4. 快速查找
    基于“键”快速定位数据(底层用红黑树实现,时间复杂度为 O(log n))。


🔍 常用操作

  1. 插入数据

    ageBook.insert({"Charlie", 28});  // 插入新条目
    // 或直接用 [] 运算符:
    ageBook["David"] = 22;
     
  2. 查找数据

    if (ageBook.find("Bob") != ageBook.end()) {
        std::cout << "Bob的年龄是:" << ageBook["Bob"];  // 输出 30
    }
     
  3. 遍历所有条目

    for (const auto& entry : ageBook) {
        std::cout << entry.first << " 年龄:" << entry.second << std::endl;
    }
     

⚠️ 注意事项

  • 键的类型必须支持比较
    如果键是自定义类型(比如类对象),需要定义比较规则(如重载 < 运算符)。

  • 需要唯一键时用 std::map,允许重复键用 std::multimap

    std::multimap<std::string, int> multiAgeBook;  // 允许多个 "Alice" 条目
     
  • 不关心顺序时可用 std::unordered_map
    基于哈希表实现,查找更快(时间复杂度 O(1)),但无序。


🌟 实际应用场景

  • 缓存系统(用键快速检索缓存值)

  • 配置文件(如 参数名 → 参数值

  • 统计词频(单词作为键,出现次数作为值)


总结std::map 是 C++ 的“自动整理字典”,帮你用唯一的键管理数据,且自动排序。适合需要按键快速查找或有序遍历的场景,但需注意键的唯一性和比较规则!📖🔑

4. Unordered_map

与 map 类似,无序 map 存储键值对,但它是使用哈希表实现的。这意味着与 map 相比,unordered_map具有更快的 average-case 性能,因为它不保持排序顺序。但是,最坏情况下的性能可能比 map 差。

#include <iostream>
#include <unordered_map>

int main() {
    std::unordered_map<std::string, int> um;

    um["one"] = 1;
    um["two"] = 2;

    std::cout << "Unordered map contains:" << std::endl;
    for (const auto &pair : um) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
}

std::unordered_map 可以理解为一个“快速但乱放的抽屉柜”,它用键值对存储数据,但不关心顺序,直接通过“键”的哈希值快速定位位置。它的特点是“平均速度快,但极端情况可能翻车”。


🌰 举个现实例子

假设你要管理一个快递仓库

  • 键(Key):快递单号(比如 "SF123"、"JD456")

  • 值(Value):对应的包裹信息(收件人、地址等)

如果用 std::map(有序字典),包裹会按单号顺序整齐排列,找包裹时需要逐步比对单号(类似二分查找)。
如果用 std::unordered_map(无序哈希表),包裹会被直接扔进一个编号的柜子,找包裹时:

  1. 计算单号的哈希值 → 得到柜子编号。

  2. 直接打开对应柜子 → 快速找到包裹(如果柜子里只有1个包裹)。
    但如果多个单号哈希到同一个柜子(哈希冲突),就需要在柜子里翻找所有包裹,效率降低。


🆚 与 std::map 的核心区别

特性std::mapstd::unordered_map
底层实现红黑树(平衡二叉树)哈希表(桶 + 链表/红黑树)
排序按键升序排列完全无序
查找速度(平均)O(log n)(较慢)O(1)(极快)
查找速度(最坏)O(log n)(稳定)O(n)(极慢,所有键哈希冲突)
内存占用较低(树结构紧凑)较高(需预留哈希桶空间)

🛠️ 适用场景

  • 用 std::unordered_map 当
    需要极速查找/插入/删除,且不关心键的顺序
    例子:缓存系统、快速检索用户ID、高频更新的键值存储。

  • 用 std::map 当
    需要按键顺序遍历,或对性能稳定性要求高(避免最坏情况)。
    例子:按时间顺序记录日志、需要范围查询(如找键在A~B之间的条目)。


🔍 工作原理(哈希表)

  1. 哈希函数:将键(如字符串)转换为一个整数(哈希值)。

    // 例如哈希函数可能将 "Alice" → 42,"Bob" → 15
  2. 哈希桶:根据哈希值将键值对分配到不同的“桶”中。

    // 假设桶数量为 10,则哈希值 42 会分配到 42 % 10 = 2 号桶
     
  3. 冲突处理:如果多个键哈希到同一个桶,用链表或红黑树存储(C++11后默认用链表,冲突多时转红黑树)。


⚠️ 注意事项

  • 键必须支持哈希函数
    基本类型(intstring 等)可以直接用,但自定义类型(如类对象)需手动实现哈希函数。

    struct Person {
        std::string name;
        int age;
    };
    
    // 自定义哈希函数
    struct PersonHash {
        size_t operator()(const Person& p) const {
            return std::hash<std::string>()(p.name) ^ std::hash<int>()(p.age);
        }
    };
    
    std::unordered_map<Person, std::string, PersonHash> personMap;
     
  • 哈希函数质量影响性能
    好的哈希函数应尽量均匀分布键,减少冲突。


📝 代码示例

#include <unordered_map>
#include <iostream>

int main() {
    std::unordered_map<std::string, int> ageMap = {
        {"Alice", 25},
        {"Bob", 30}
    };

    // 插入新条目
    ageMap["Charlie"] = 28;

    // 查找(平均 O(1))
    if (ageMap.find("Bob") != ageMap.end()) {
        std::cout << "Bob的年龄:" << ageMap["Bob"] << std::endl; // 输出 30
    }

    // 遍历(顺序不确定!)
    for (const auto& entry : ageMap) {
        std::cout << entry.first << " : " << entry.second << std::endl;
    }

    return 0;
}
 

总结

  • std::unordered_map 是“为速度牺牲顺序”的键值容器,适合高频操作且无需排序的场景。

  • 选择它还是 std::map,取决于你是否需要顺序和性能稳定性。就像选择“乱但快的仓库”还是“整齐但稍慢的书架”!📦🚀

逐句解析 📖

1. for (const auto& entry : ageMap)
  • 作用:告诉编译器你要遍历 ageMap 里的每一个条目。

  • 分解

    • const auto& entry:自动识别条目类型,用“只读引用”的方式访问(避免拷贝,高效安全)。

    • ageMap:你要遍历的容器(比如电话簿)。

2. std::cout << entry.first << " : " << entry.second << std::endl;
  • 作用:打印当前条目的“键”和“值”。

  • 分解

    • entry.first:当前条目的(比如电话簿中的名字)。

    • entry.second:当前条目的(比如对应的电话号码)。

    • std::endl:换行并刷新输出。


🌰 现实类比

假设 ageMap 是一个纸质电话簿

std::map<std::string, int> ageMap = {
    {"Alice", 25}, 
    {"Bob", 30}, 
    {"Charlie", 28}
};
  • 循环过程

    1. 打开电话簿到第一页 → 拿到 Alice 的信息(entry.first = "Alice"entry.second = 25)。

    2. 打印:Alice : 25

    3. 自动翻到下一页 → 拿到 Bob 的信息。

    4. 打印:Bob : 30

    5. 继续翻页,直到最后一页(Charlie)。


🔍 关键语法点

  1. const auto& entry

    • auto:让编译器自动推断 entry 的类型(这里实际是 std::pair<const std::string, int>)。

    • const &:避免拷贝条目,原样只读访问(高效且安全)。

  2. 基于范围的 for 循环

    • 传统循环需要手动控制迭代器,容易出错:

      for (auto it = ageMap.begin(); it != ageMap.end(); ++it) {
          std::cout << it->first << " : " << it->second << std::endl;
      }
       
    • 新语法更简洁,不易出错。


⚠️ 注意事项

  • 条目顺序

    • 如果是 std::map,循环会按键的升序打印(Alice → Bob → Charlie)。

    • 如果是 std::unordered_map,顺序不可预测(可能是 Charlie → Alice → Bob)。

  • 修改条目

    • 如果循环内想修改值(但不能修改键),可以去掉 const 并改用 auto&

      for (auto& entry : ageMap) {
          entry.second += 1;  // 每个人的年龄加1岁
      }
       

📝 完整示例

#include <iostream>
#include <map>

int main() {
    std::map<std::string, int> ageMap = {
        {"Alice", 25}, 
        {"Bob", 30}, 
        {"Charlie", 28}
    };

    // 遍历并打印所有条目
    for (const auto& entry : ageMap) {
        std::cout << entry.first << " 的年龄是 " << entry.second << " 岁" << std::endl;
    }

    return 0;
}
 

输出

Alice 的年龄是 25 岁
Bob 的年龄是 30 岁
Charlie 的年龄是 28 岁
 

总结:这段代码就像让 C++ 帮你“自动翻电话簿”,逐个查看每个名字和对应的年龄,无需手动控制翻页过程!📘🔍

这些只是 C++ 容器的几个示例。还有其他容器类型,例如 、 、 、 、 和 。每个容器都有自己的用例和独特特征。了解这些容器以及何时使用它们可以大大提高您使用 C++ 的效率和有效性。set multiset deque stack queue priority_queue

迭代器

迭代器是 C++ 标准库 () 中的对象,可帮助我们遍历数组、列表和向量等容器。从本质上讲,它们充当容器类和算法之间的桥梁。迭代器的行为类似于指针,但提供了一种更通用和抽象的方式来访问容器中的元素。STL

根据其用例,您会遇到不同类型的迭代器:

  • Input Iterator:用于仅向前读取一次容器中的元素。他们不能修改元素。

例:

std::vector<int> nums = {1, 2, 3, 4};
std::istream_iterator<int> input(std::cin);
std::copy(input, std::istream_iterator<int>(), std::back_inserter(nums));

这段代码的作用是从键盘读取一串整数,动态添加到 nums 向量的末尾。可以想象成一个“自动吸管”把输入的数字源源不断吸到容器里,直到你停止输入。


代码拆解 🛠️

1. 准备容器

std::vector<int> nums = {1, 2, 3, 4};  // 初始有 1,2,3,4
 
  • 就像准备了一个盒子(nums),里面已经放了数字 1,2,3,4。


2. 创建“输入吸管”

std::istream_iterator<int> input(std::cin);  // 吸管对准键盘输入
 
  • istream_iterator 是一个“输入吸管”,专门从 std::cin(键盘)读取整数。

  • 此时“吸管”处于准备状态,等待你输入数字。


3. 启动“自动吸入”程序

std::copy(
    input,                      // 开始位置:吸管准备读取
    std::istream_iterator<int>(),  // 结束位置:吸管停止标志
    std::back_inserter(nums)    // 目的地:把数字塞到 nums 的末尾
);
 
  • std::copy:复制数据的机器。

  • input:开始读取的位置(吸管开始工作)。

  • std::istream_iterator<int>():停止信号(当输入结束时,吸管停止)。

  • std::back_inserter(nums):自动扩展的容器接口(确保 nums 能动态扩容)。


工作流程 🔄

  1. 你输入数字(比如 5 6 7),按回车后:

  2. 吸管 input 开始工作:逐个读取数字 5 → 6 → 7。

  3. 复制到 nums:每读一个数字,就追加到 nums 末尾。

  4. 何时停止?

    • 输入非整数(如字母 a)。

    • 输入结束符(Windows按 Ctrl+Z,Mac/Linux按 Ctrl+D)。

最终 nums 会变成 {1,2,3,4,5,6,7}


类比现实场景 🥤

  • 吸管 (istream_iterator):奶茶店的吸管,专门吸液体(这里吸整数)。

  • 停止标志:吸管遇到冰块(非整数)或杯子空了(输入结束)就停止。

  • 自动扩容容器 (back_inserter):杯子底部有弹性,吸多少液体就膨胀多大空间。


注意事项 ⚠️

  • 输入必须匹配类型:如果输入字母,吸管会立即停止,后续数字不会被读取。

  • 动态扩容:无需提前设置 nums 的大小,back_inserter 会自动调用 push_back


示例运行 🖥️

假设你运行程序后输入:

10 20 30 a

(最后输入字母 a 触发停止)
结果:
nums 变为 {1,2,3,4,10,20,30}

  • Output Iterator:用于在容器中仅向前写入元素一次。它们无法重写元素。 

例:

std::vector<int> nums = {1, 2, 3, 4};
std::ostream_iterator<int> output(std::cout, ", ");
std::copy(nums.begin(), nums.end(), output);

这段代码的作用是将数组中的数字逐个输出到屏幕,并用逗号分隔,就像自动打印列表一样。以下是逐行解释:


代码拆解 🛠️

1. 准备一个数字盒子

std::vector<int> nums = {1, 2, 3, 4};  // 盒子里装了数字 1,2,3,4
 
  • 想象你有一个盒子(nums),里面放了数字 1、2、3、4。


2. 连接一个“自动打印机”

std::ostream_iterator<int> output(std::cout, ", ");  
// 打印机设定:用逗号分隔每个数字
 
  • output 是一个“自动打印机”,功能是:

    • 输出目标std::cout(屏幕)。

    • 分隔符:每次打印完一个数字后,自动加一个 ", "


3. 启动“复制打印”程序

std::copy(nums.begin(), nums.end(), output);  
// 把盒子里的数字逐个塞进打印机
 
  • std::copy 的工作流程:

    1. 从盒子的第一个数字(nums.begin())开始。

    2. 逐个取出数字,直到最后一个(nums.end())。

    3. 每取一个数字,就交给 output 打印机输出。


输出结果 🖨️

执行后屏幕显示:

1, 2, 3, 4, 
 

注意:最后一个数字后面会多出一个逗号(需要额外处理才能去掉)。


现实类比 🧩

  • std::copy 像一个传送带,把盒子里的物品(数字)运送到打印机。

  • ostream_iterator 是打印机,每收到一个物品就打印出来,并自动贴上分隔标签(逗号)。


改进建议 ✨

如果想避免最后的冗余逗号,可以手动处理最后一个元素,或用其他方法(如 std::join in C++23)。不过原代码的核心目的是展示如何用迭代器简化输出。


总结:这段代码用“自动打印机”(ostream_iterator)和传送带(std::copy)将数组内容快速输出到屏幕,是 C++ 中高效输出容器元素的常用技巧!🖨️🚀

 

  • Forward Iterator:类似于输入迭代器,但可用于容器中元素的多个通道。他们不能倒退。

例:

std::forward_list<int> nums = {1, 2, 3, 4};
std::forward_list<int>::iterator itr = nums.begin();
while (itr != nums.end()) {
    std::cout << *itr << " ";
    ++itr;
}

这段代码的作用是遍历并打印单向链表中的所有元素。通俗解释如下:

  1. 创建链表:首先创建了一个名为nums的单向链表,里面存放数字1、2、3、4。可以想象它像一列火车,每节车厢(元素)只连接下一节。

  2. 获取“火车头”:用nums.begin()获取链表的第一个元素的位置(迭代器itr),就像指向火车的第一节车厢。

  3. 遍历车厢

    • 只要itr没有到达“车尾后的空位置”(nums.end()),就做两件事:

      1. 打印当前车厢的货物std::cout << *itr输出当前元素的值。

      2. 移动到下一节车厢++itr让迭代器指向下一个元素。

  4. 结束条件:当处理完最后一节车厢(数字4)后,itr再移动就会到达“空位置”,循环停止。

最终会在屏幕上输出:1 2 3 4。整个过程就像从火车头开始,逐节检查并记录每节车厢的内容,直到所有车厢处理完毕。

反向迭代器:类似于输入迭代器,但可用于容器中元素的多个通道。他们无法前进。

例:

std::list<int> nums = {1, 2, 3, 4};
std::list<int>::reverse_iterator itr = nums.rbegin();
while (itr != nums.rend()) {
    std::cout << *itr << " ";
    ++itr;
}

这段代码的作用是倒序遍历双向链表并逆序打印所有元素。通俗解释如下:


类比理解

想象一个双向链表就像一列可以前后行驶的火车,每节车厢(元素)既能看到前一节,也能看到后一节。这次我们要从最后一节车厢开始倒着走回火车头,并记录每节车厢的货物。


代码分步解释

  1. 创建双向链表

    std::list<int> nums = {1, 2, 3, 4};
     
      
    • 创建了一个双向链表 nums,包含数字 1、2、3、4。它的结构像这样:
      头 ← 1 ↔ 2 ↔ 3 ↔ 4 ← 尾

  2. 获取“倒车起点”

    std::list<int>::reverse_iterator itr = nums.rbegin();
     
      
    • nums.rbegin() 获取链表的反向起始位置(即最后一个元素 4 的位置)。

  3. 倒着遍历车厢

    while (itr != nums.rend()) {
        std::cout << *itr << " ";
        ++itr; // 注意这里是 ++,但实际会向前移动
    }
     
      
    • 循环条件:只要迭代器还没到达“车头前的空位置”(nums.rend())。

    • 关键操作

      1. *itr 读取当前车厢的货物(比如先从 4 开始)。

      2. ++itr:看似“向前走”,但由于是反向迭代器,实际是移动到前一个元素(从 4 移动到 3,再移动到 2,依此类推)。


输出结果

最终会在屏幕上输出:4 3 2 1
(相当于从火车尾倒着走回火车头,依次记录每节车厢的内容)


对比正向迭代器

  • 正向迭代器 (begin() → end()):从头到尾,输出 1 2 3 4

  • **反向迭代器 (rbegin() → rend()):从尾到头,输出 4 3 2 1

反向迭代器的 ++ 操作实际上是“向链表的头部方向移动”,类似于倒车时踩油门反而是在后退。

 

  • 双向迭代器:这些迭代器提供了在容器中向前和向后移动的能力。List 和 set 容器具有双向迭代器。

例:

std::list<int> nums = {1, 2, 3, 4};
std::list<int>::iterator itr;
for (itr = nums.begin(); itr != nums.end(); ++itr) {
    std::cout << *itr << " ";
}
for (--itr; itr != nums.begin(); --itr) {
    std::cout << *itr << " ";
}
  • 随机访问迭代器:这些迭代器提供了访问容器中元素的最灵活方式。它们可以向前、向后移动、直接跳转到其他元素以及访问给定索引处的元素。

例:

std::vector<int> nums = {1, 2, 3, 4};
std::vector<int>::iterator itr;
for (itr = nums.begin(); itr != nums.end(); ++itr) {
    std::cout << *itr << " ";
}
for (itr -= 1; itr != nums.begin() - 1; --itr) {
    std::cout << *itr << " ";
}

在大多数情况下,您需要从关键字和适当的容器方法(如 和 )开始,以便与迭代器一起使用。auto begin() end()

例:

std::vector<int> nums = {1, 2, 3, 4};
for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
    std::cout << *itr << " ";
}

使用算法时,请记住 C++ 标准库提供了各种算法,这些算法已经将迭代器用于搜索、排序和作元素等任务。

STL 算法

C++ 中的标准模板库 (STL) 提供了一组泛型算法,这些算法旨在与各种容器类一起使用。这些算法作为函数实现,可以应用于不同的数据结构,例如数组、向量、列表等。算法的主要头文件是 .<algorithm>

关键概念

排序

排序是指按特定顺序排列元素序列。STL 提供了多种排序算法,例如 、 和 。std::sort std::stable_sort std::partial_sort

std::sort

std::sort用于按非降序(默认情况下)对一系列元素 [first, last] 进行排序。您还可以使用自定义比较函数或 lambda 表达式来更改排序顺序。

例:
 

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

int main() {
    std::vector<int> nums = {10, 9, 8, 7, 6, 5};
    std::sort(nums.begin(), nums.end());

    for (int num : nums) {
        std::cout << num << ' ';
    }
    // Output: 5 6 7 8 9 10
}
  • 范围for循环 (for (int num : nums)) 是C++11引入的语法糖,底层依然通过迭代器实现,但隐藏了复杂的迭代器操作。

搜索

搜索是指查找特定元素是否存在于给定的元素范围内。STL 提供了多种搜索算法,例如 、 和 。std::findstd::binary_searchstd::find_if

std::find

std::find用于查找 [first, last] 范围内给定值首次出现的迭代器。

例:
 

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

int main() {
    std::vector<int> nums = {5, 6, 7, 8, 9, 10};
    auto it = std::find(nums.begin(), nums.end(), 9);

    if (it != nums.end()) {
        std::cout << "Found 9 at position: " << (it - nums.begin());
    } else {
        std::cout << "9 not found";
    }
    // Output: Found 9 at position: 4
}

修改序列

STL 还提供了用于修改序列的算法,例如 、 和 。std::remove std::replace std::unique

std::remove

std::remove用于从给定范围 [first, last) 内的容器中删除值的所有实例。请注意,该函数在删除元素后不会调整容器的大小。

例:

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

int main() {
    std::vector<int> nums = {5, 6, 7, 6, 8, 6, 9, 6, 10};
    nums.erase(std::remove(nums.begin(), nums.end(), 6), nums.end());

    for (int num : nums) {
        std::cout << num << ' ';
    }
    // Output: 5 7 8 9 10
}

我们可以用清理房间的比喻来解释这段代码的功能:


代码作用

删除数组nums中所有数字6,并压缩剩余元素(类似大扫除时把垃圾扔掉,再把有用的物品摆整齐)。


分步解释

  1. 准备房间

    std::vector<int> nums = {5, 6, 7, 6, 8, 6, 9, 6, 10};
     
      
    • 想象你有一个收纳盒,里面杂乱地放着数字:
      📦 [5, 6, 7, 6, 8, 6, 9, 6, 10]

  2. 标记垃圾(数字6)

    std::remove(nums.begin(), nums.end(), 6)
     
      
    • 这个操作像把垃圾推到收纳盒的角落
      📦 [5, 7, 8, 9, 10, ❌6, ❌6, ❌6, ❌6]

    • 注意:此时收纳盒大小没变,只是把垃圾集中到末尾(std::remove返回指向第一个垃圾的位置,图中处)

  3. 彻底扔掉垃圾

    nums.erase(..., nums.end());
     
      
    • 把标记为垃圾的部分整个切掉,收纳盒变小:
      📦 [5, 7, 8, 9, 10]

  4. 展示整理结果

    for (int num : nums) { std::cout << num << ' '; }
     
      
    • 检查收纳盒剩余物品,输出:5 7 8 9 10


关键技术点

  • "标记-清理"策略:先通过std::remove集中垃圾,再用erase批量删除,比逐个删除效率更高。

  • 保持顺序:剩余元素的原始顺序不变(类似整理时不打乱物品摆放顺序)。


类比现实场景

假设你要清理一筐混着苹果和烂苹果的水果:

  1. 先把好苹果推到筐的前半部分,烂苹果挤到后半部(std::remove

  2. 直接把后半筐烂苹果倒掉(erase

总结

C++ 中的 STL 算法为排序、搜索和修改序列等关键作提供了一组有用的函数。这些算法可以与各种容器类一起使用,使其具有高度的通用性,并且是 C++ 编程的重要组成部分。

C++ 中的多线程

多线程是在单个进程或程序中并发执行多个线程。它通过允许并行执行多个任务来提高应用程序的性能和效率。

在 C++ 中,通过 C++11 标准中引入的库提供多线程支持。thread

基本线程创建

要创建新线程,请包含头文件并创建一个将函数作为参数的实例。该函数将在新线程中执行。<thread> std::thread
 

#include <iostream>
#include <thread>

void my_function() {
    std::cout << "This function is executing in a separate thread" << std::endl;
}

int main() {
    std::thread t(my_function);
    t.join(); // waits for the thread to complete
    return 0;
}

带参数的线程

您可以通过将参数作为附加参数提供给构造函数来将参数传递给线程函数。std::thread

#include <iostream>
#include <thread>

void print_sum(int a, int b) {
    std::cout << "The sum is: " << a + b << std::endl;
}

int main() {
    std::thread t(print_sum, 3, 5);
    t.join();
    return 0;
}

互斥锁和锁

当多个线程访问共享资源时,可能会出现数据争用。为避免这种情况,请使用 mutex 和 locks 来同步共享资源访问。

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void print_block(int n, char c) {
    {
        std::unique_lock<std::mutex> locker(mtx);
        for (int i = 0; i < n; ++i) {
            std::cout << c;
        }
        std::cout << std::endl;
    }
}

int main() {
    std::thread t1(print_block, 50, '*');
    std::thread t2(print_block, 50, '$');

    t1.join();
    t2.join();

    return 0;
}

我们可以用银行ATM机排队的比喻来解释这段代码的功能:


代码作用

让两个窗口(线程)有序地打印各自符号,避免打印内容混合(类似ATM机每次只允许一人操作)


分步解释

  1. 准备两个窗口

    td::thread t1(print_block, 50, '*');  // 窗口1要打印50个*
    std::thread t2(print_block, 50, '$');  // 窗口2要打印50个$
     
      
    • 想象银行有两个ATM机(线程),都想同时打印符号

  2. 安装门禁系统(互斥锁)

    std::mutex mtx;  // 相当于ATM机的门禁,一次只放一人进入
     
  3. 取号进门(加锁)

    std::mutex mtx;  // 相当于ATM机的门禁,一次只放一人进入
     
      
    • 当某个线程执行到这里时:

      • 如果门禁卡空闲 → 立即拿到卡进入(开始打印)

      • 如果门禁卡被占用 → 在门口排队等待

  4. 完整打印(独占资源)

    for (int i=0; i<50; ++i) { std::cout << c; }  // 连续打印50次
     
      
    • 进入ATM机的人会连续打印完所有符号后才出来(不会被中途打断)

  5. 自动还卡(解锁)

    }  // unique_lock在这里自动释放锁
     
      
    • 打印完成后,unique_lock会自动归还门禁卡,让另一个窗口的人进入


输出结果示例

*************************
$$$$$$$$$$$$$$$$$$$$$$$$$
 

或者

$$$$$$$$$$$$$$$$$$$$$$$$$
*************************
 

(最终会完整打印两行符号,但先后顺序可能随机,取决于系统调度)


关键特性

  1. 原子性打印:每个线程的50个符号会完整显示,不会出现*$*$交替

  2. 自动管理锁unique_lock在作用域结束后自动解锁,即使发生异常也能保证解锁

  3. 线程安全:避免多个线程同时操作std::cout导致输出混乱


对比不加锁的情况

如果去掉锁相关的代码,输出可能会变成碎片化的混合内容(类似两个ATM用户同时挤进一个机器操作):

*$**$$$*$*$*$$*$*$*...(乱序混合)
 


这个简短的介绍应该可以帮助您开始使用 C++ 中的基本多线程技术。还有很多内容需要学习,例如线程池、条件变量以及用于高级同步和性能调整的原子作。


http://www.kler.cn/a/571684.html

相关文章:

  • 深入理解网络通信中的关键概念:HTTP、TCP与Socket的关系及TCP的可靠性保障
  • Google C++ 开源风格指南
  • 用AI学安卓游戏开发1——控制小球上下左右移动2
  • JavaEE基础之-sessioncookie
  • centos和ubunt下安装redis
  • 论文阅读和代码实现EfficientDet(BiFPN)
  • 基于vue框架的游戏商城系统cq070(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 大模型的实践应用36-基于AI Agent和通义千问大模型,支持多轮问答的智能问数和数据分析的应用场景
  • 如何保证域名网络安全性
  • 甘特图开发代码(测试版)
  • PyCharm 环境配置精髓:打造高效 Python 开发的基石
  • 1.从0搭建前端Vue项目工程
  • 华为最新OD机试真题-服务失效判断-OD统一考试(E卷)
  • hutool Java的工具箱介绍
  • 【氮化镓】基于SiC脉冲I-V系统研究Schottky型p-GaN HEMT正栅极ESD机制
  • Linux:vim快捷键
  • 蓝桥杯备考:动态规划线性dp之传球游戏
  • 结合PyMuPDF+pdfplumber,删除PDF指定文本后面的内容
  • 【漫话机器学习系列】117.马修斯相关系数(Matthews Correlation Coefficient, MCC)
  • 【四.RAG技术与应用】【11.阿里云百炼应用(上):RAG在云端的实践】