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

C++学习:六个月从基础到就业——C++学习之旅:STL迭代器系统

C++学习:六个月从基础到就业——C++学习之旅:STL迭代器系统

本文是我C++学习之旅系列的第二十四篇技术文章,也是第二阶段"C++进阶特性"的第二篇,主要介绍C++ STL迭代器系统。查看完整系列目录了解更多内容。

引言

在上一篇文章中,我们详细探讨了STL容器,了解了各种容器的特性和用法。今天,我们将深入研究STL的另一个核心组件——迭代器系统。迭代器是连接容器和算法的桥梁,它使得算法可以以统一的方式访问不同类型的容器,是STL设计中最为精妙的部分之一。

什么是迭代器?

迭代器是一种行为类似于指针的对象,提供了一种访问容器中元素的方式,而不需要暴露容器的内部实现。通过迭代器,我们可以遍历集合、访问集合中的元素,并且根据需要修改这些元素。

迭代器抽象了底层容器的实现细节,提供了一套统一的接口,使得算法可以工作在各种不同的容器上,而无需知道这些容器的具体实现。

迭代器的分类

STL中的迭代器按照其功能和支持的操作分为五类:

  1. 输入迭代器(Input Iterator):最基础的迭代器,只支持单次遍历,只读访问元素。
  2. 输出迭代器(Output Iterator):只能写入元素,不能读取。
  3. 前向迭代器(Forward Iterator):支持多次遍历,可读写元素,只能向前移动。
  4. 双向迭代器(Bidirectional Iterator):在前向迭代器的基础上增加了向后移动的能力。
  5. 随机访问迭代器(Random Access Iterator):在双向迭代器的基础上增加了随机访问和迭代器算术的能力。

迭代器分类层次

每种更高级别的迭代器都包含了前一级别迭代器的所有功能。不同的容器提供不同类型的迭代器,例如:

  • std::vector 提供随机访问迭代器
  • std::list 提供双向迭代器
  • std::forward_list 提供前向迭代器
  • std::istream_iterator 是输入迭代器
  • std::ostream_iterator 是输出迭代器

C++20新增了**连续迭代器(Contiguous Iterator)**作为随机访问迭代器的子类,对应于元素在内存中连续存储的容器(如std::vector)。

迭代器的基本操作

迭代器提供了一系列基本操作,具体取决于其类别:

所有迭代器共有的操作

*it         // 解引用迭代器
it->member  // 访问元素的成员(相当于(*it).member)
++it        // 前置自增(移动到下一个元素)
it++        // 后置自增
it1 == it2  // 相等比较
it1 != it2  // 不等比较

双向迭代器增加的操作

--it        // 前置自减(移动到前一个元素)
it--        // 后置自减

随机访问迭代器增加的操作

it + n      // 迭代器向前移动n个位置
it - n      // 迭代器向后移动n个位置
it += n     // 迭代器向前移动n个位置并赋值
it -= n     // 迭代器向后移动n个位置并赋值
it1 - it2   // 计算两个迭代器之间的距离
it[n]       // 随机访问(相当于*(it + n))
it1 < it2   // 小于比较
it1 > it2   // 大于比较
it1 <= it2  // 小于等于比较
it1 >= it2  // 大于等于比较

常见STL容器的迭代器类型和用法

让我们看看各种STL容器所提供的迭代器类型以及如何使用它们:

std::vector 的迭代器

std::vector 提供了随机访问迭代器,支持所有迭代器操作。

#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 获取迭代器auto begin = vec.begin();  // 指向第一个元素auto end = vec.end();      // 指向最后一个元素之后的位置// 基本遍历std::cout << "基本遍历: ";for (auto it = begin; it != end; ++it) {std::cout << *it << " ";}std::cout << std::endl;// 随机访问std::cout << "第三个元素: " << *(begin + 2) << std::endl;// 逆向遍历std::cout << "逆向遍历: ";for (auto it = vec.rbegin(); it != vec.rend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 修改元素for (auto it = begin; it != end; ++it) {*it *= 2;}// 迭代器算术运算auto middle = begin + (end - begin) / 2;std::cout << "中间元素: " << *middle << std::endl;return 0;
}

std::list 的迭代器

std::list 提供了双向迭代器,可以向前和向后移动,但不能随机访问。

#include <iostream>
#include <list>int main() {std::list<int> lst = {1, 2, 3, 4, 5};// 正向遍历std::cout << "正向遍历: ";for (auto it = lst.begin(); it != lst.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 反向遍历std::cout << "反向遍历: ";for (auto it = lst.rbegin(); it != lst.rend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 移动迭代器auto it = lst.begin();++it;  // 移动到第二个元素++it;  // 移动到第三个元素std::cout << "第三个元素: " << *it << std::endl;// 不支持随机访问,以下代码无法编译:// auto third = lst.begin() + 2;  // 错误!list迭代器不支持+n操作return 0;
}

std::map 的迭代器

std::map 提供的是双向迭代器,迭代时会按照键的顺序遍历。

#include <iostream>
#include <map>
#include <string>int main() {std::map<std::string, int> scores = {{"Alice", 95},{"Bob", 89},{"Charlie", 92},{"David", 88}};// 正向遍历std::cout << "学生分数:" << std::endl;for (auto it = scores.begin(); it != scores.end(); ++it) {std::cout << it->first << ": " << it->second << std::endl;}// 反向遍历std::cout << "\n按反向字母顺序:" << std::endl;for (auto it = scores.rbegin(); it != scores.rend(); ++it) {std::cout << it->first << ": " << it->second << std::endl;}// 查找并修改auto it = scores.find("Bob");if (it != scores.end()) {it->second = 91;  // 更新Bob的分数std::cout << "\nBob的新分数: " << it->second << std::endl;}return 0;
}

迭代器适配器

STL提供了几种特殊的迭代器适配器,它们构建在其他迭代器之上,提供特殊的功能:

反向迭代器

反向迭代器将遍历方向反转,通过rbegin()rend()获取。

#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};std::cout << "正向遍历: ";for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;std::cout << "反向遍历: ";for (auto it = vec.rbegin(); it != vec.rend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用std::reverse_iterator适配器std::reverse_iterator<std::vector<int>::iterator> rev_it(vec.end());std::cout << "使用反向迭代器适配器: ";for (; rev_it != std::reverse_iterator<std::vector<int>::iterator>(vec.begin()); ++rev_it) {std::cout << *rev_it << " ";}std::cout << std::endl;return 0;
}

插入迭代器

插入迭代器允许算法将元素添加到容器中,而不是覆盖现有元素。STL提供三种插入迭代器:

  1. back_inserter - 在容器尾部插入元素
  2. front_inserter - 在容器头部插入元素
  3. inserter - 在指定位置之前插入元素
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <iterator>int main() {std::vector<int> source = {1, 2, 3, 4, 5};std::vector<int> dest1;std::list<int> dest2;std::vector<int> dest3 = {10, 20, 30};// back_inserter - 在尾部插入std::copy(source.begin(), source.end(), std::back_inserter(dest1));std::cout << "back_inserter结果: ";for (int n : dest1) std::cout << n << " ";std::cout << std::endl;// front_inserter - 在头部插入 (只能用于支持push_front的容器)std::copy(source.begin(), source.end(), std::front_inserter(dest2));std::cout << "front_inserter结果: ";for (int n : dest2) std::cout << n << " ";  // 会是倒序的std::cout << std::endl;// inserter - 在指定位置插入std::copy(source.begin(), source.end(), std::inserter(dest3, dest3.begin() + 1));std::cout << "inserter结果: ";for (int n : dest3) std::cout << n << " ";std::cout << std::endl;return 0;
}

流迭代器

STL还提供了连接I/O流与STL算法的流迭代器:

  1. istream_iterator - 从输入流读取元素
  2. ostream_iterator - 向输出流写入元素
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <sstream>int main() {// 读取输入流的例子std::istringstream iss("1 2 3 4 5");std::vector<int> vec;// 使用istream_iterator从字符串流读取整数std::istream_iterator<int> iss_iter(iss);std::istream_iterator<int> iss_end;  // 默认构造的迭代器表示结束std::copy(iss_iter, iss_end, std::back_inserter(vec));// 使用ostream_iterator输出到控制台std::cout << "从流读取的数字: ";std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, " "));std::cout << std::endl;// 直接使用流迭代器读取和写入std::istringstream input("10 20 30 40 50");std::cout << "数字的平方: ";std::transform(std::istream_iterator<int>(input),std::istream_iterator<int>(),std::ostream_iterator<int>(std::cout, " "),[](int x) { return x * x; });std::cout << std::endl;return 0;
}

迭代器特性(Traits)

迭代器特性是一种用于描述迭代器属性的类模板,它使算法能够决定使用最高效的实现。C++通过std::iterator_traits来访问迭代器的特性。主要的迭代器特性包括:

  1. value_type - 迭代器指向的值的类型
  2. difference_type - 两个迭代器之间距离的类型
  3. pointer - 指向值的指针类型
  4. reference - 引用类型
  5. iterator_category - 迭代器类别
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <type_traits>template <typename Iterator>
void printIteratorCategory() {using Category = typename std::iterator_traits<Iterator>::iterator_category;if (std::is_same<Category, std::input_iterator_tag>::value)std::cout << "输入迭代器" << std::endl;else if (std::is_same<Category, std::output_iterator_tag>::value)std::cout << "输出迭代器" << std::endl;else if (std::is_same<Category, std::forward_iterator_tag>::value)std::cout << "前向迭代器" << std::endl;else if (std::is_same<Category, std::bidirectional_iterator_tag>::value)std::cout << "双向迭代器" << std::endl;else if (std::is_same<Category, std::random_access_iterator_tag>::value)std::cout << "随机访问迭代器" << std::endl;
#if defined(__cpp_lib_concepts) && __cpp_lib_concepts >= 201907Lelse if (std::is_same<Category, std::contiguous_iterator_tag>::value)std::cout << "连续迭代器" << std::endl;
#endifelsestd::cout << "未知迭代器类别" << std::endl;
}int main() {std::cout << "vector迭代器: ";printIteratorCategory<std::vector<int>::iterator>();std::cout << "list迭代器: ";printIteratorCategory<std::list<int>::iterator>();std::cout << "istream_iterator: ";printIteratorCategory<std::istream_iterator<int>>();std::cout << "ostream_iterator: ";printIteratorCategory<std::ostream_iterator<int>>();// 简单的迭代器traits演示using VecIt = std::vector<int>::iterator;std::iterator_traits<VecIt>::value_type val = 10;std::cout << "Vector迭代器value_type: " << val << std::endl;return 0;
}

自定义迭代器

有时候,我们需要为自定义容器实现迭代器。以下是一个简单的自定义迭代器例子,用于遍历一个简单的环形缓冲区:

#include <iostream>
#include <vector>
#include <iterator>// 简单的环形缓冲区
template <typename T, size_t Size>
class CircularBuffer {
private:std::vector<T> buffer;size_t head;  // 开始位置size_t count;  // 元素数量public:CircularBuffer() : buffer(Size), head(0), count(0) {}void push(const T& value) {size_t position = (head + count) % Size;buffer[position] = value;if (count < Size)++count;elsehead = (head + 1) % Size;  // 环形缓冲区满了,覆盖最旧的元素}bool empty() const { return count == 0; }bool full() const { return count == Size; }size_t size() const { return count; }size_t capacity() const { return Size; }// 自定义迭代器class Iterator {private:CircularBuffer* buffer;size_t position;size_t visited;  // 已访问计数public:// 迭代器特性类型定义using iterator_category = std::forward_iterator_tag;using difference_type = std::ptrdiff_t;using value_type = T;using pointer = T*;using reference = T&;Iterator(CircularBuffer* buf, size_t pos, size_t vis): buffer(buf), position(pos), visited(vis) {}reference operator*() const {return buffer->buffer[position];}pointer operator->() const {return &(buffer->buffer[position]);}Iterator& operator++() {if (visited < buffer->count) {position = (position + 1) % Size;++visited;}return *this;}Iterator operator++(int) {Iterator temp = *this;++(*this);return temp;}bool operator==(const Iterator& other) const {return buffer == other.buffer && (position == other.position || visited == buffer->count && other.visited == buffer->count);}bool operator!=(const Iterator& other) const {return !(*this == other);}};// 返回迭代器Iterator begin() {return Iterator(this, head, 0);}Iterator end() {return Iterator(this, (head + count) % Size, count);}
};int main() {CircularBuffer<int, 5> cb;// 添加一些元素for (int i = 1; i <= 7; ++i) {cb.push(i);}std::cout << "环形缓冲区内容: ";for (auto it = cb.begin(); it != cb.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用范围循环std::cout << "使用范围循环: ";for (int val : cb) {std::cout << val << " ";}std::cout << std::endl;// 适用于STL算法std::cout << "使用std::for_each: ";std::for_each(cb.begin(), cb.end(), [](int x) {std::cout << x << " ";});std::cout << std::endl;return 0;
}

迭代器失效问题

使用迭代器时,需要特别注意迭代器失效的问题。当容器被修改时,指向该容器的迭代器可能会失效。不同容器的迭代器失效规则不同:

  1. vector/string

    • 插入时,如果没有内存重新分配,则只有插入点之后的迭代器失效
    • 插入时,如果发生内存重新分配,则所有迭代器都失效
    • 删除时,被删除元素之后的所有迭代器都失效
  2. deque

    • 在两端插入/删除时,所有迭代器都失效,但引用不会失效
    • 在中间插入/删除时,所有迭代器和引用都失效
  3. list/forward_list

    • 只有指向被删除元素的迭代器会失效
    • 插入不会使任何迭代器失效
  4. map/set/multimap/multiset

    • 插入时迭代器不会失效
    • 删除时,只有被删元素的迭代器失效
  5. unordered_容器

    • 插入可能导致全部迭代器失效(如果发生重新哈希)
    • 删除只会使被删元素的迭代器失效

迭代器失效示例

#include <iostream>
#include <vector>
#include <list>void vectorIteratorInvalidation() {std::vector<int> vec = {1, 2, 3, 4, 5};std::cout << "Vector迭代器失效示例:" << std::endl;// 错误示例:在遍历时修改容器std::cout << "错误示例(注释掉以防崩溃):" << std::endl;/*for (auto it = vec.begin(); it != vec.end(); ++it) {if (*it == 3) {vec.push_back(6);  // 可能导致迭代器失效和未定义行为}std::cout << *it << " ";}*/// 正确示例1:先存储元素,结束遍历后再修改std::cout << "正确示例1:" << std::endl;std::vector<int> to_add;for (auto it = vec.begin(); it != vec.end(); ++it) {if (*it == 3) {to_add.push_back(6);}std::cout << *it << " ";}for (int val : to_add) {vec.push_back(val);}std::cout << "\n修改后: ";for (int val : vec) {std::cout << val << " ";}std::cout << std::endl;// 正确示例2:使用索引而非迭代器std::cout << "\n正确示例2:" << std::endl;vec = {1, 2, 3, 4, 5};for (size_t i = 0; i < vec.size(); ++i) {std::cout << vec[i] << " ";if (vec[i] == 3) {vec.erase(vec.begin() + i);  // 删除当前元素--i;  // 调整索引,避免跳过元素}}std::cout << "\n删除后: ";for (int val : vec) {std::cout << val << " ";}std::cout << std::endl;
}void listIteratorInvalidation() {std::list<int> lst = {1, 2, 3, 4, 5};std::cout << "\nList迭代器失效示例:" << std::endl;// List在删除元素时只有被删元素的迭代器失效for (auto it = lst.begin(); it != lst.end(); ) {if (*it == 3) {it = lst.erase(it);  // erase返回下一个元素的迭代器} else {++it;}}std::cout << "删除后: ";for (int val : lst) {std::cout << val << " ";}std::cout << std::endl;
}int main() {vectorIteratorInvalidation();listIteratorInvalidation();return 0;
}

迭代器与算法的结合

STL的迭代器和算法是紧密结合的,通过迭代器,算法可以作用于任何容器。以下是一些常用算法与迭代器的结合使用:

#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <algorithm>
#include <numeric>
#include <iterator>int main() {// 初始化几个容器std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7, 6, 4};std::list<int> lst = {5, 2, 8, 1, 9, 3, 7, 6, 4};std::map<std::string, int> mp = {{"apple", 5},{"banana", 3},{"cherry", 7},{"date", 2}};// 排序std::sort(vec.begin(), vec.end());// list不支持随机访问,不能用std::sort,但有自己的sort方法lst.sort();// 查找auto vecIt = std::find(vec.begin(), vec.end(), 7);auto lstIt = std::find(lst.begin(), lst.end(), 7);auto mpIt = mp.find("cherry");std::cout << "在vector中找到7: " << (vecIt != vec.end() ? "是" : "否") << std::endl;std::cout << "在list中找到7: " << (lstIt != lst.end() ? "是" : "否") << std::endl;std::cout << "在map中找到cherry: " << (mpIt != mp.end() ? "是" : "否") << std::endl;// 计数int count3 = std::count(vec.begin(), vec.end(), 3);std::cout << "vector中3的数量: " << count3 << std::endl;// 求和int sum = std::accumulate(vec.begin(), vec.end(), 0);std::cout << "vector元素总和: " << sum << std::endl;// 转换std::vector<int> doubled;std::transform(vec.begin(), vec.end(), std::back_inserter(doubled),[](int x) { return x * 2; });std::cout << "翻倍后的元素: ";for (int n : doubled) std::cout << n << " ";std::cout << std::endl;// 使用流迭代器输出std::cout << "使用ostream_iterator输出vector: ";std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, " "));std::cout << std::endl;return 0;
}

实际项目中的迭代器最佳实践

避免迭代器失效

  1. 了解每种容器的迭代器失效规则
  2. 在修改容器前保存元素或迭代器
  3. 使用容器方法返回的新迭代器(如erase返回下一个元素的迭代器)

使用更现代的方式

  1. 尽可能使用范围for循环(C++11及以上)
  2. 使用auto避免冗长的迭代器类型声明
  3. 熟悉新标准引入的迭代器工具(如C++17的std::size,C++20的std::ranges

避免无效迭代器解引用

始终检查迭代器是否有效,特别是在可能返回end()的情况下:

auto it = container.find(key);
if (it != container.end()) {// 只有确认迭代器有效时才解引用useValue(*it);
}

使用迭代器适配器简化代码

// 不重新定义临时容器来存储结果
std::transform(input.begin(), input.end(), std::back_inserter(output),someTransformation);// 直接写入流
std::copy(container.begin(), container.end(),std::ostream_iterator<ValueType>(std::cout, ", "));

C++20中的范围(Ranges)

C++20引入了范围库,它建立在迭代器概念之上,但提供了更便捷的接口。范围允许更简洁、更可组合的算法表达:

#include <iostream>
#include <vector>
#include <algorithm>#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
#include <ranges>void rangesExample() {std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 使用管道语法过滤、转换和输出auto result = vec | std::views::filter([](int n) { return n % 2 == 0; })  // 只保留偶数| std::views::transform([](int n) { return n * 2; });   // 将每个数乘以2std::cout << "C++20 Ranges 过滤和转换后: ";for (int n : result) {std::cout << n << " ";}std::cout << std::endl;
}
#else
void rangesExample() {std::cout << "您的编译器不支持C++20 Ranges" << std::endl;
}
#endifint main() {rangesExample();return 0;
}

总结

迭代器是STL设计中最为精妙的部分之一,它实现了容器和算法的解耦,允许算法在不同的容器上以统一的方式运行。掌握迭代器对于高效使用STL至关重要。

本文讨论了迭代器的分类、操作、特性和常见问题,并展示了如何在实际项目中使用迭代器。随着C++20带来的范围库,迭代器的概念进一步演进,变得更易用和更强大。

在下一篇文章中,我们将探讨STL的第三个主要组件:算法库。我们将学习如何利用STL提供的众多算法来处理容器中的数据,从而避免重新发明轮子,编写更高效、更可靠的代码。

参考资源

  • C++ 参考手册
  • 《C++ 标准库》by Nicolai M. Josuttis
  • 《Effective STL》by Scott Meyers
  • 《The C++ Programming Language》by Bjarne Stroustrup

这是我C++学习之旅系列的第二十四篇技术文章。查看完整系列目录了解更多内容。

如有任何问题或建议,欢迎在评论区留言交流!

相关文章:

C++学习:六个月从基础到就业——C++学习之旅:STL迭代器系统

C学习&#xff1a;六个月从基础到就业——C学习之旅&#xff1a;STL迭代器系统 本文是我C学习之旅系列的第二十四篇技术文章&#xff0c;也是第二阶段"C进阶特性"的第二篇&#xff0c;主要介绍C STL迭代器系统。查看完整系列目录了解更多内容。 引言 在上一篇文章中…...

缓存与数据库一致性方案

一、缓存更新策略概述 在现代分布式系统中&#xff0c;缓存作为数据库的前置层&#xff0c;能显著提升系统性能。然而&#xff0c;缓存与数据库之间的数据一致性是一个经典难题。以下是三种常见的缓存更新策略及其优缺点分析。 二、方案对比分析 方案一&#xff1a;直接更新…...

国内ip地址怎么改?详细教程

在中国&#xff0c;更改IP地址需要遵守规则&#xff0c;并确保所有操作合规。在特定情况下&#xff0c;可能需要修改IP地址以满足不同需求或解决特定问题。以下是一些常见且合法的IP地址变更方法及注意事项&#xff1a; 一、理解IP地址 IP地址是设备在网络中的唯一标识&#x…...

通过Quartus II实现Nios II编程

目录 一、认识Nios II二、使用Quartus II 18.0Lite搭建Nios II硬件部分三、软件部分四、运行项目 一、认识Nios II Nios II软核处理器简介 Nios II是Altera公司推出的一款32位RISC嵌入式处理器&#xff0c;专门设计用于在FPGA上运行。作为软核处理器&#xff0c;Nios II可以通…...

拥抱基因体检,迎接精准健康管理新时代

2025年4月20日&#xff0c;由早筛网、细胞科技网联合中国食品药品企业质量安全促进会细胞医药分会、中国抗衰老促进会健康管理工作委员会、中国抗癌协会肿瘤分子医学专业委员会、广东省保健协会&#xff0c;伯温生物冠名支持的《基因体检赋能精准健康管理新时代》圆满召开。 伯…...

QT容器类控件及其属性

Group Box 使用QGroupBox实现一个带有标题的分组框&#xff0c;可以把其它的控件放到里面作为一组 例&#xff1a; 核心属性 属性 说明 title 分组框的标题 alignment 分组框内部内容的对齐方式 flat 是否”扁平模式” checkable 是否可选中 设为true&#xff0c;则…...

云原生--CNCF-3-核心工具介绍(容器和编排、服务网格和通信、监控和日志、运行时和资源管理,安全和存储、CI/CD等)

1、核心工具分类介绍 &#xff08;1&#xff09;、容器编排与管理 1、Docker 它是一款轻量级的容器化技术&#xff0c;可把应用及其依赖打包成独立的容器。借助Docker&#xff0c;开发者能够确保应用在不同环境中保持一致的运行状态。比如在开发环境中创建的容器&#xff0c…...

网络基础知识

文章目录 一、网络架构1. 网络架构图2. 各层级功能3. 机房网络常见问题及解决方案 二、交换技术1. 交换技术基础2. 交换技术分类3. 广播域相关概念4. ARP 协议5. 三层交换机6. VLAN&#xff08;虚拟局域网&#xff09; 三、路由技术1. 路由器端口类型及功能2. 路由器功能3. 路由…...

第3课:运算符与流程控制——JS的“决策者”

一切美好都值得你全力以赴。即使过程艰难&#xff0c;但只要坚持&#xff0c;必有回响。加油&#xff01; 欢迎来到「JavaScript 魔法学院」第 3 课&#xff01;今天我们将化身代码世界的“指挥官”&#xff0c;用运算符计算数据&#xff0c;用流程控制做出决策&#xff01;文…...

VUE3中使用echarts,配置都正确,不出现tooltip

在vue3中使用echarts&#xff0c;出现个别问题&#xff0c;需要注意echars对象的定义&#xff0c;不能使用reactive或者ref定义响应式变量&#xff0c;要使用shallowRef &#xff1b; shallowRef 是 Vue 3 提供的一个 API&#xff0c;它创建一个响应式引用(ref)&#xff0c;但…...

Python实现邮件发送

一、创造灵感 这几天在指导学生做毕业设计&#xff0c;学生做的是跌倒检测。然后我突然想到&#xff0c;如果这个系统只是单纯地从视频流中检测到人的跌倒动作&#xff0c;其实并没有任何的用途和意义。于是&#xff0c;我又想到&#xff0c;如果跌倒的检测结果&#xff0c;能…...

OSPF的优化

OSPF的优化 1、汇总 --- 减少骨干区域LSA的更新量 汇总 --- 区域汇总 域间路由汇总 --- 在ABR设备上&#xff0c;针对3类LSA进行汇总 [r1-ospf-1-area-0.0.0.2]abr-summary 192.168.0.0 255.255.252.0 注&#xff1a;在进行域间路由汇总时&#xff0c;必须是ABR设备自己通过拓…...

IP-guard离线卸载客户端及清除策略说明

1、控制台生成客户端工具 控制台选择“工具-客户端工具-客户端离线辅助工具”&#xff0c;可生成客户端离线卸载工具及 离线策略清除工具&#xff0c;打包生成exe后&#xff0c;可在离线的客户端上运行&#xff0c;实现离线情况下对客户端 进行卸载或者清除策略。 在控制台上“…...

腾讯秋招面试题:bug生命周期中有哪些状态?

在软件测试中,Bug生命周期是质量管控的核心脉络。不同企业的流程或有差异,但核心状态遵循行业通用规范。以下以腾讯TAPD缺陷管理模型为基础,结合互联网大厂主流实践,详解Bug生命周期的关键状态及流转逻辑。 一、基础状态图谱 图表 代码 下载 确认有效 非缺陷/需求不符…...

PyCharm 链接 Podman Desktop 的 podman-machine-default Linux 虚拟环境

#工作记录 PyCharm Community 连接到Podman Desktop 的 podman-machine-default Linux 虚拟环境详细步骤 1. 准备工作 确保我们已在 Windows 系统中正确安装并启动了 Podman Desktop。 我们将通过 Podman Desktop 提供的名为 podman-machine-default 的 Fedora Linux 41 WSL…...

学习海康VisionMaster之卡尺工具

一&#xff1a;进一步学习了 今天学习下VisionMaster中的卡尺工具&#xff1a;主要用于测量物体的宽度、边缘的特征的位置以及图像中边缘对的位置和间距 二&#xff1a;开始学习 1&#xff1a;什么是卡尺工具&#xff1f; 如果我需要检测芯片的每一个PIN的宽度和坐标&#xff…...

私有知识库 Coco AI 实战(二):摄入 MongoDB 数据

在之前的文章中&#xff0c;我们介绍过如何使用《 Logstash 迁移 MongoDB 数据到 Easyseach》&#xff0c;既然 Coco AI 后台数据存储也使用 Easysearch&#xff0c;我们能否直接把 MongoDB 的数据迁移到 Coco AI 的 Easysearch&#xff0c;使用 Coco AI 对数据进行检索呢&…...

【C/C++】插件机制:基于工厂函数的动态插件加载

本文介绍了如何通过 C 的 工厂函数、动态库&#xff08;.so 文件&#xff09;和 dlopen / dlsym 实现插件机制。这个机制允许程序在运行时动态加载和调用插件&#xff0c;而无需在编译时知道插件的具体类型。 一、 动态插件机制 在现代 C 中&#xff0c;插件机制广泛应用于需要…...

硬核科普丨2025年安全、高效网络准入控制系统深度解析

阳途网络准入控制系统&#xff08;Network Access Control&#xff0c;简称NAC&#xff09;是当代网络安全领域的重要工具&#xff0c;有效防止未经授权的访问和数据泄露&#xff0c;保障网络资源的安全性和完整性。本文将深入探讨阳途网络准入控制系统的的重要性和作用。 一、…...

2025知识管理趋势解析:AI、协作与用户体验引领变革

知识管理软件是多元化的商业工具&#xff0c;包括知识库系统、wiki、协作平台、文档管理工具、聊天机器人和帮助系统&#xff0c;针对企业信息爆炸式增长的趋势&#xff0c;这些工具正逐步成为提升知识工作者效率和客户服务水平的关键支撑。 知识成为新资产&#xff1a;知识工…...

杂谈-有感而发

今天又拜读了线性代数的几何意义这本书&#xff0c;感觉确实是很不错的&#xff0c;从几何角度讲解线性代数&#xff0c;而且将线性代数、几何、数学、物理有机结合起来了&#xff0c;不仅仅是可视化&#xff0c;重要的是能便于自己独立思考下去。   从大一开始&#xff0c;就…...

使用nodeJs的express+axios+cors做代理

使用nodeJs的expressaxioscors做代理 前端在请求后端时通常会遇到跨域cors问题&#xff0c;如果只在本地开发可以通过webpack或vite的proxy设置。但如果需要在线上或者其他地方绕过跨域&#xff0c;可以使用代理的方法。 1. 创建文件夹 并创建以下文件 package.json {"…...

SQL进阶知识:四、索引优化

今天介绍下关于索引优化的详细介绍&#xff0c;并结合MySQL数据库提供实际例子。 索引优化是数据库性能优化的关键环节之一&#xff0c;尤其是在处理大量数据时。索引可以加快查询速度&#xff0c;减少数据扫描范围&#xff0c;但不当的索引设计也可能导致性能问题。以下是关于…...

C++初阶-类和对象(下)

目录 1.再探构造函数 2.类型转换 3.友元 4.static成员 5.内部类 6.匿名对象 *7.对象拷贝时的编译器优化&#xff08;非必学&#xff09; 8.总结 1.再探构造函数 &#xff08;1&#xff09;之前我们实现构造函数时&#xff0c;初始化成员变量主要使用函数体内赋值&#…...

RPC通信原理实战

RPC概念及RPC请求响应流程 RPC概念引入 假设有两个模块&#xff0c;用户和订单 在单体项目中&#xff0c;两个模块被打包到一个包&#xff0c;都处在一个tomcat进程中&#xff0c;用户模块调用订单模块属于同一进程内的本地调用 在微服务项目中&#xff0c;用户模块和订单模…...

自动创建 中国古代故事人物一致性图画,看看扣子的空间是否能达到你的满意,自媒体的福音?

欢迎来到涛涛聊AI 看效果&#xff1a; AI绘画最难的人物一致性问题&#xff0c;每次得到的结果都不一致。 官方介绍&#xff1a; 扣子空间是你和 AI Agent 协同办公的最佳场所。 在扣子空间里&#xff0c;精通各项技能的「通用实习生」&#xff0c;各行各业的「领域专家」&…...

【Unity笔记】Unity音效管理:ScriptableObject配置 + 音量控制 + 编辑器预览播放自动化实现

摘要&#xff1a; 本文介绍了如何在 Unity 中构建一个高效的音效管理系统&#xff0c;通过 ScriptableObject 实现音效集中配置&#xff0c;支持为每个音效单独设置音量&#xff0c;并通过自定义 Editor 实现音效的可视化预览播放与下拉选择播放功能&#xff0c;整个系统无场景…...

面向高可靠场景的RISC-V低功耗MCU硬件安全设计

该RISC-V架构的低功耗MCU通过多项硬件级安全技术满足工业控制、汽车电子及物联网等领域对可靠性与安全性的需求。其核心特性包含六个方面&#xff1a; 在数据完整性验证与固件安全升级方面&#xff0c;该MCU通过硬件级校验机制&#xff08;如CRC、哈希算法&#xff09;确保固件…...

OpenVINO教程(五):实现YOLOv11+OpenVINO实时视频目标检测

目录 实现讲解效果展示完整代码 本文作为上篇博客的延续&#xff0c;在之前实现了图片推理的基础上&#xff0c;进一步介绍如何进行视频推理。 实现讲解 首先&#xff0c;我们需要对之前的 predict_and_show_image 函数进行拆分&#xff0c;将图像显示与推理器&#xff08;pre…...

【新能源科学与技术】MATALB/Simulink小白教程(一)实验文档【新能源电力转换与控制仿真】

DP读书&#xff1a;新能源科学与工程——专业课「新能源发电系统」 2025a 版本 MATLAB下面进入正题 仿真一&#xff1a;Buck 电路一、仿真目的二、仿真内容&#xff08;一&#xff09;Buck电路基本构成及工作原理&#xff08;二&#xff09;Buck电路仿真模型及元件连接&#xf…...

软件项目实施全流程及交付物清单

需求分析 -> 概要设计 -> 详细设计 -> 开发 -> 测试 -> 部署 -> 上线运行 一、确认项目目标、范围和团队成员 二、收集和分析客户需求&#xff0c;确定需求规格 三、制定详细的项目计划&#xff0c;包括时间表、资源计划、预算 四、系统架构设计&#xf…...

Docker安装ES :确保 Kibana 正确连接 Elasticsearch

在使用 Docker 部署 ELK&#xff08;Elasticsearch、Logstash、Kibana&#xff09;堆栈时&#xff0c;正确的服务配置和依赖管理至关重要。本文将分享如何优化 Docker Compose 配置&#xff0c;确保 Kibana 能稳定连接到 Elasticsearch&#xff0c;并提供故障排查建议 cd /opt/…...

云贝餐饮 最新 V3 独立连锁版 全开源 多端源码 VUE 可二开

云贝餐饮源码 最新 V3 独立连锁版 全开源 多端源码 VUE 可二开 vue uniapp 独家优化版本 后台管理 Vue 源文件 后台管理文件 yii2 升级包 &#xff08;全开源&#xff09; 收银台 Vue 源文件 装修 Vue 源文件 商家端 uni-app 源文件 用户端 uni-app 源文件 数据库文件 云贝餐饮…...

python异步协程async调用过程图解

1.背景&#xff1a; 项目中有用到协程&#xff0c;但是对于协程&#xff0c;线程&#xff0c;进程的区别还不是特别了解&#xff0c;所以用图示的方式画了出来&#xff0c;用于理清三者的概念。 2.概念理解&#xff1a; 2.1协程&#xff0c;线程&#xff0c;进程包含关系 一…...

【Java学习笔记】选择结构

选择结构 内容结构 一、顺序结构 二、分支控制 &#xff08;1&#xff09;单分支 &#xff08;2&#xff09;双分支 &#xff08;3&#xff09;多分支 &#xff08;4&#xff09;嵌套分支 &#xff08;5&#xff09;switch 分支结构 三、switch和if的比较 一、顺序结构…...

java Nacos

windows下载nacos&#xff0c;版本2.x的&#xff0c;1.0的话会出现“Server check fail, please check server 127.0.0.1 ,port 9848 is available , error {}” 这个鬼问题 下载链接 这里用的是2.0.4版本&#xff0c;启动的话用 startup.cmd -m standalone&#xff0c;单体启…...

c语言之杂识

前言 主要说一下c语言的杂知识 一、预处理指令 1. #include #include指令用于包含其他头文件的内容到当前文件中。这允许您在程序中使用其他模块定义的函数和变量。 示例&#xff1a; #include <stdio.h> // 包含标准输入输出头文件int main() {printf("Hello…...

CSS内边距、外边距、边框

CSS内边距 内边距 &#xff1a;内容区&#xff08;Content&#xff09;与边框&#xff08;Border&#xff09;之间的透明间距&#xff0c;用于增加元素内部的留白。不影响元素内容本身的尺寸&#xff08;如文字大小&#xff09;&#xff0c;但会增大元素的总占用空间。 以下是…...

融山科技前端面经

前端面试问题解答 这次是波珠的第三次面试&#xff0c;持续打怪升级&#xff0c;积累面试中面试官问到的不会的点子。 1.自我介绍 面试官您好&#xff0c;我是来自XX学校XX专业本科的XX名字&#xff0c;然后介绍在学校情况&#xff0c;我就介绍了大一自学前端&#xff0c;然…...

元素滚动和内容垂直居中同时存在,完美的 html 元素垂直居中的方法flex + margin: auto

假设有一个元素 div 作为父容器且宽高固定&#xff0c;里面的内容 content 在父容器中垂直/水平都居中&#xff0c;且当内容很多时&#xff0c;父元素滚动overflow: auto&#xff0c;content的内容要能够完全展示。 我之前在这篇文档提到了两个方法&#xff1a; 使用 flex s…...

STM32F103 单片机(基于 ARM Cortex-M3 内核)的启动过程涉及硬件初始化、固件配置和程序执行流程。

1. 启动模式与地址映射 STM32F103 的启动模式由 BOOT0 和 BOOT1 引脚配置决定&#xff0c;不同的启动模式对应不同的存储器映射&#xff1a; 启动模式 映射地址范围 说明 主 Flash 0x08000000~0x0807FFFF 用户程序存储在 Flash 中&#xff0c;复位后从 Flash 启动&#xff08…...

c++中iota容器和fill的区别

在C 中&#xff0c;std::iota 和 std::fill 都是标准库中的函数&#xff0c;用于对序列进行操作&#xff0c;它们的功能和用法如下&#xff1a; std::iota 功能&#xff1a;std::iota 函数用于将一个连续的递增序列赋值给指定范围的元素。它接受三个参数&#xff0c;第一个参…...

Kafka集群

kafka集群: kafka-server-start.sh;kafka-server-stop.sh cd/opt/software/kafka/ kafka.sh上传:cd /usr/local/bin/ 执行文件变成绿色&#xff1a;chmod 777 kafka.sh Kafka.sh start Xcall.sh jps...

多源数据集成技术分析与应用实践探索

摘要&#xff1a;本文聚焦多源数据集成技术&#xff0c;深入剖析联邦式、基于中间件模型及数据仓库三类主流技术的侧重点与应用场景。通过实际案例阐述多源数据集成应用构建过程&#xff0c;旨在为企业数据整合与共享提供理论指导与实践参考&#xff0c;助力企业提升数据利用效…...

UniOcc:自动驾驶占用预测和预报的统一基准

25年3月来自 UC Riverside、U Wisconsin 和 TAMU 的论文"UniOcc: A Unified Benchmark for Occupancy Forecasting and Prediction in Autonomous Driving"。 UniOcc 是一个全面统一的占用预测基准&#xff08;即基于历史信息预测未来占用&#xff09;和基于摄像头图…...

反向代理和DDNS的区别是什么?

反向代理&#xff08;Reverse Proxy&#xff09;和动态域名解析&#xff08;DDNS&#xff0c;Dynamic Domain Name System&#xff09;是两种不同的网络技术&#xff0c;虽然它们都与外部访问内部服务相关&#xff0c;但解决的问题和应用场景完全不同。具体区别如下&#xff1a…...

markdown展示数学公式

要让Markdown正确展示数学公式&#xff0c;你需要使用支持数学公式渲染的工具&#xff0c;比如在支持LaTeX语法的Markdown编辑器中进行编写。 Markdown本身并不原生支持数学公式的渲染&#xff0c;但许多Markdown解析器&#xff08;例如在GitHub、Jupyter Notebooks、或一些博…...

服务器编译环境配置及数据接收脚本编写(11)

文章目录 一、本章说明二、云端服务器Python编译环境配置三、传感数据解析脚本编写一、本章说明 注:本节为【基于STM的环境监测系统(节点+云服务器存储+QT界面设计)】项目第11篇文章,上面两篇介绍了云服务器数据库的安装与相关设置,本章主要介绍在服务器中安装Python编译…...

硬件基本概念

目录 基本概念 电压 电流 串联、并联 欧姆定律 电路仿真网址&#xff1a; 功率 焦耳定律 AC220V 转 DC5V 基本电子元器件了解 电阻&#xff1a;&#xff08;电阻器&#xff09; 电容 电感 继电器 二极管 三极管 晶振&#xff08;晶体振荡器&#xff09; 光耦…...

Spark与Hadoop之间的联系与区别

联系 生态系统互补&#xff1a; Hadoop 是一个分布式存储和计算平台&#xff0c;主要包括 HDFS&#xff08;分布式文件系统&#xff09;和 MapReduce&#xff08;分布式计算框架&#xff09;。Hadoop 提供了可靠的数据存储和分布式计算的基础。 Spark 是一个高性能的分布式计算…...