1.4 STL C++面试问题
1.4.1 说说STL的基本组成部分
总结
STL 的基本组成部分包括容器、算法、迭代器、函数对象和仿函数和适配器。通过这些组件,STL 提供了高效、灵活和可复用的代码结构,极大地提高了 C++ 的开发效率和程序的可维护性。STL 的设计思想使得算法和数据结构的使用变得更加一致和简单。
C++ 标准模板库(Standard Template Library,STL)是一个功能强大的库,提供了一组常用的数据结构和算法。STL 的基本组成部分主要包括以下几个部分:
1. 容器(Containers)
容器是用于存储对象的集合,它们可以是基本类型的对象,也可以是自定义类型的对象。STL 提供了多种类型的容器,主要分为以下几类:
-
序列容器:按照插入顺序存储元素,提供随机访问。
std::vector:动态数组,支持快速随机访问和末尾插入。std::deque:双端队列,可以在两端高效插入和删除元素。std::list:双向链表,支持在任意位置插入和删除元素,但不支持随机访问。
-
关联容器:基于键值对存储元素,支持高效的查找。
std::set:不允许重复元素的集合,自动排序。std::map:键值对集合,根据键进行排序,允许快速查找。std::multiset:允许重复元素的集合。std::multimap:允许重复键的键值对集合。
-
无序关联容器:基于哈希表实现,提供常数时间复杂度的查找。
std::unordered_set:无序集合,允许重复元素。std::unordered_map:无序键值对集合。
2. 算法(Algorithms)
STL 提供了一系列通用算法,可以对容器中的数据进行操作。这些算法以模板的形式提供,适用于不同类型的容器。常见的算法包括:
- 排序算法:如
std::sort()、std::stable_sort()。 - 查找算法:如
std::find()、std::binary_search()。 - 修改算法:如
std::copy()、std::fill()、std::remove()。 - 集合算法:如
std::set_union()、std::set_intersection()。
3. 迭代器(Iterators)
迭代器是一种通用的访问容器中元素的方式。它们提供了一种统一的接口,可以遍历不同类型的容器。STL 提供了多种类型的迭代器:
- 输入迭代器:只读访问容器的元素。
- 输出迭代器:写入容器的元素。
- 前向迭代器:可读写访问,但只能向前移动。
- 双向迭代器:可读写访问,可以向前和向后移动。
- 随机访问迭代器:支持直接访问任何位置的元素,如指针,允许在容器中任意跳转。
4. 函数对象和仿函数(Function Objects and Functors)
STL 允许使用函数对象作为算法的参数。函数对象是重载了 operator() 的类,使得对象可以像函数一样被调用。STL 的算法可以接受函数指针、函数对象或 lambda 表达式作为参数,以定制算法的行为。
5. 适配器(Adapters)
适配器是对现有容器、迭代器或函数对象的封装,提供了更高层次的功能。常见的适配器包括:
-
容器适配器:
std::stack:基于底层容器实现的栈。std::queue:基于底层容器实现的队列。std::priority_queue:支持优先级的队列。
-
迭代器适配器:
std::reverse_iterator:反向迭代器。std::back_insert_iterator:用于向容器插入元素的迭代器。
1.4.2 说说STL中常见的容器,并介绍一下实现原理。
C++ 标准模板库(STL)提供了多种常见的容器,用于存储和管理数据。下面是一些 STL 中常见的容器及其实现原理的概述。
1. 序列容器
a. std::vector
- 定义:动态数组,可以根据需要自动扩展大小。
- 实现原理:
- 内部使用一个连续的内存块来存储元素。
- 当数组容量不足以容纳新元素时,
std::vector会分配一个更大的内存块(通常是当前大小的 1.5 倍或 2 倍),然后将旧元素复制到新位置。 - 支持随机访问,时间复杂度为 O(1)。
- 插入和删除操作的时间复杂度为 O(n),因为可能需要移动元素。
b. std::deque
- 定义:双端队列,允许在两端高效地插入和删除元素。
- 实现原理:
- 内部使用多个固定大小的数组(块)组成的链表结构。
- 每个块可以动态分配,支持在两端快速插入和删除。
- 随机访问的时间复杂度为 O(1),但由于分散的内存结构,可能会有一定的缓存不命中。
c. std::list
- 定义:双向链表,支持在任意位置高效地插入和删除元素。
- 实现原理:
- 内部实现为双向链表,每个节点包含数据和指向前后节点的指针。
- 插入和删除操作的时间复杂度为 O(1),但不支持随机访问,访问元素的时间复杂度为 O(n)。
2. 关联容器
a. std::set
- 定义:不允许重复元素的集合,自动排序。
- 实现原理:
- 通常使用红黑树(自平衡二叉搜索树)实现。
- 元素按升序排列,插入、删除和查找操作的时间复杂度为 O(log n)。
b. std::map
- 定义:键值对集合,键唯一且按键排序。
- 实现原理:
- 也是基于红黑树实现。
- 支持高效查找、插入和删除操作,时间复杂度为 O(log n)。
c. std::multiset 和 std::multimap
- 定义:允许重复元素的集合和键值对集合。
- 实现原理:
- 同样基于红黑树实现,支持重复元素的插入。
- 查找和删除操作的时间复杂度为 O(log n)。
3. 无序容器
a. std::unordered_set
- 定义:无序集合,允许重复元素。
- 实现原理:
- 基于哈希表实现,使用哈希函数来决定元素的存储位置。
- 查找、插入和删除操作的平均时间复杂度为 O(1)。
b. std::unordered_map
- 定义:无序键值对集合。
- 实现原理:
- 也是基于哈希表实现,键通过哈希函数映射到存储桶。
- 支持高效查找和插入,平均时间复杂度为 O(1)。
4. 适配器
a. std::stack
- 定义:后进先出(LIFO)的数据结构。
- 实现原理:
- 通常基于其他容器(如
std::deque或std::vector)实现。 - 提供
push、pop和top操作。
- 通常基于其他容器(如
b. std::queue
- 定义:先进先出(FIFO)的数据结构。
- 实现原理:
- 也基于其他容器(通常是
std::deque)实现。 - 提供
push、pop和front操作。
- 也基于其他容器(通常是
总结
STL 提供了多种容器,适用于不同的使用场景。每种容器都有其特定的实现原理和性能特点,以满足开发者的需求。选择合适的容器可以提高程序的效率和可维护性。通过 STL,开发者能够轻松地使用和管理各种数据结构,极大地简化了 C++ 编程。
1.4.3 说说STL中 map hashtable deque list 的实现原理
std::map:使用红黑树实现,支持按键排序,操作复杂度为 O(log n)。std::unordered_map:基于哈希表实现,支持快速查找和插入,平均复杂度为 O(1)。std::deque:使用块结构实现的双端队列,支持在两端高效操作,随机访问复杂度为 O(1)。std::list:实现为双向链表,支持高效的插入和删除操作,但不支持随机访问,访问复杂度为 O(n)。
在 C++ 标准模板库(STL)中,map、unordered_map(常称为哈希表)、deque 和 list 是几种常见的容器,每种容器的实现原理都有其特定的结构和特点。以下是对它们实现原理的详细解释。
1. std::map
- 实现原理:
std::map通常使用 红黑树(自平衡二叉搜索树)来实现。- 每个节点存储一个键值对(key-value pair),并通过键的顺序(通常是升序)进行排序。
- 红黑树的特点是每个节点都有一个颜色属性(红色或黑色),并且遵循特定的平衡规则,确保树的高度是对数级别。
- 操作复杂度:
- 查找、插入和删除操作的时间复杂度为 O(log n)。
2. std::unordered_map(哈希表)
- 实现原理:
std::unordered_map基于 哈希表 实现。- 使用一个数组作为基础存储,元素通过哈希函数计算出索引位置,存储在相应的桶中。
- 如果发生哈希冲突(即不同的键映射到同一桶),通常会使用链式法(在同一个桶中使用链表存储冲突的元素)来解决。
- 操作复杂度:
- 查找、插入和删除操作的平均时间复杂度为 O(1),最坏情况下为 O(n)(当所有元素都在同一个桶中)。
3. std::deque
- 实现原理:
std::deque是一种 双端队列,通常实现为多个固定大小的块(chunk)组成的结构。- 每个块可以动态分配,允许在两端高效插入和删除。
- 它维护一个指向这些块的指针数组,支持随机访问和双端操作。
- 操作复杂度:
- 随机访问的时间复杂度为 O(1),在两端插入和删除操作的时间复杂度也为 O(1),但中间插入和删除的复杂度为 O(n)。
4. std::list
- 实现原理:
std::list是一个 双向链表,每个节点包含数据、指向前一个节点和后一个节点的指针。- 这种结构允许在任意位置进行高效的插入和删除操作,因为只需更改指针即可,不需要移动其他元素。
- 链表不支持随机访问,访问元素的时间复杂度为 O(n),需要从头遍历到目标节点。
- 操作复杂度:
- 插入和删除操作的时间复杂度为 O(1),但访问操作的时间复杂度为 O(n)。
通过这些实现原理,STL 提供了高效、灵活的数据结构,能够满足不同的编程需求。选择合适的容器对于程序的性能和可维护性至关重要。
1.4.4 介绍一下STL的空间适配器(allocator)
在 C++ 标准模板库(STL)中,空间适配器(Allocator)是一种用于管理内存分配的机制。它为 STL 容器提供了一种可定制的内存管理方式,使得容器可以在需要时动态地分配和释放内存。以下是对 STL 空间适配器的详细介绍。
1. Allocator 的定义
- Allocator 是一个类模板,负责分配和释放内存。它定义了用于内存管理的一组类型和操作。
- 在 STL 中,默认的分配器是
std::allocator<T>,其中T是容器中存储的元素类型。
2. Allocator 的基本功能
Allocator 提供了以下基本功能:
-
内存分配:
- 使用
allocate(size_t n)函数分配大小为n的元素的内存。 - 返回指向分配内存的指针。
- 使用
-
内存释放:
- 使用
deallocate(T* p, size_t n)函数释放之前分配的内存。 p是指向要释放的内存的指针,n是元素的数量。
- 使用
-
构造和析构:
- 使用
construct(T* p, Args&&... args)在已分配的内存上构造对象。 - 使用
destroy(T* p)在已构造的对象上调用析构函数,释放对象的资源。
- 使用
3. Allocator 的使用
STL 容器在内部使用 Allocator 进行内存管理。用户可以通过模板参数自定义 Allocator,例如:
#include <vector>
#include <memory>int main() {std::vector<int, std::allocator<int>> vec; // 使用默认的 allocatorstd::vector<int, MyAllocator<int>> customVec; // 使用自定义的 allocator
}
4. 自定义 Allocator
用户可以自定义 Allocator,通过继承 std::allocator 或实现自己的分配和释放逻辑。自定义 Allocator 需要遵循 Allocator 的接口要求,提供相应的成员函数。
template <typename T>
class MyAllocator {
public:using value_type = T;MyAllocator() = default;template <typename U>MyAllocator(const MyAllocator<U>&) {}T* allocate(std::size_t n) {// 自定义分配逻辑return static_cast<T*>(::operator new(n * sizeof(T)));}void deallocate(T* p, std::size_t) {// 自定义释放逻辑::operator delete(p);}template <typename U, typename... Args>void construct(U* p, Args&&... args) {new(p) U(std::forward<Args>(args)...); // 在已分配内存上构造对象}template <typename U>void destroy(U* p) {p->~U(); // 调用析构函数}
};
5. Allocator 的优势
- 内存管理的灵活性:Allocator 提供了一种方式,使得容器的内存管理可以被替换和定制。用户可以根据需求选择不同的内存分配策略。
- 性能优化:对于特定应用程序,可以使用自定义 Allocator 来优化内存分配和释放,减少内存碎片,提高性能。
- 可扩展性:通过实现自定义 Allocator,用户可以扩展容器的功能,例如实现自定义的内存池或其他特定的分配逻辑。
6. 总结
STL 的空间适配器(Allocator)是内存管理的重要组成部分,它为 STL 容器提供了灵活、可定制的内存分配和释放机制。通过使用 Allocator,开发者可以在需要时调整内存管理策略,以满足特定的性能和资源需求。了解和使用 Allocator 可以帮助提高 C++ 程序的性能和可维护性。
1.4.6 迭代器什么时候会失效?
在 C++ 标准模板库(STL)中,迭代器是一种用于访问和遍历容器中元素的对象。迭代器提供了一种统一的接口,可以对各种类型的容器进行操作,如数组、链表、集合等。
1. 迭代器的定义
- 迭代器是一种指向容器元素的对象,它能够以类似指针的方式访问容器中的元素。
- 迭代器支持多种操作,如递增、解引用等,以便于在容器中遍历。
2. 迭代器的作用
- 访问容器元素:迭代器可以访问容器中的元素,允许读取和修改这些元素。
- 遍历容器:通过迭代器,可以以统一的方式遍历不同类型的容器。
- 算法支持:STL 的算法(如
std::sort、std::find等)通常以迭代器作为参数,从而使算法能够独立于容器类型地操作数据。 - 更好的抽象:迭代器提供了一种抽象机制,使得开发者可以使用同一套语法访问不同类型的容器,而不需要关心容器的内部实现细节。
3. 迭代器的类型
迭代器根据其功能和特性可以分为几种类型:
- 输入迭代器:支持只读访问容器的元素。
- 输出迭代器:支持写入容器的元素。
- 前向迭代器:可读写访问,但只能向前移动。
- 双向迭代器:可以向前和向后移动,支持读写。
- 随机访问迭代器:支持直接访问容器的任意元素,如指针,允许进行加法和减法操作。
4. 迭代器失效的情况
迭代器失效是指在某些操作后,迭代器不能再安全地使用,通常会导致未定义行为。以下是一些常见的导致迭代器失效的情况:
-
容器元素的插入和删除:
std::vector和std::deque:当在容器中插入或删除元素时,可能会导致其他元素的内存位置改变,导致现有的迭代器失效。一般来说,删除当前迭代器所指向的元素后,指向该元素的迭代器将失效。std::list:在std::list中,由于链表的性质,删除一个元素不会导致其他迭代器失效,除非它们直接指向被删除的元素。
-
容器的重新分配:
std::vector:当std::vector的容量不足以容纳新元素时,可能会发生内存重新分配,导致所有指向该vector中元素的迭代器失效。
-
容器的清空:
- 当容器被清空(例如,调用
clear()方法)时,所有指向该容器的迭代器都将失效。
- 当容器被清空(例如,调用
-
指针或引用:
- 如果迭代器是通过指针或引用访问的,且所指向的对象被删除或作用域结束,迭代器将失效。
5. 总结
- 迭代器是一种用于访问和遍历 STL 容器的对象,具有指向容器元素的能力。
- 迭代器提供了统一的访问接口,支持多种操作,使得容器和算法之间的结合更加灵活。
- 迭代器可能会在插入、删除、清空或容器重新分配时失效,因此在使用迭代器时要格外小心,以避免未定义行为。理解迭代器的生命周期和有效性是高效使用 STL 的关键。
1.4.7 说说迭代器的做用,有指针为何还要迭代器?
迭代器在 C++ 中是用于访问和遍历 STL 容器的一种工具,比指针更通用、灵活。尽管指针可以直接操作数组等连续内存,但迭代器具有更广泛的适用性和功能。
指针与迭代器的区别
| 特性 | 指针 | 迭代器 |
|---|---|---|
| 通用性 | 仅适用于连续内存 | 适用于各种容器(vector、list等) |
| 功能 | 基础读写遍历 | 支持STL算法和多种访问方式 |
| 安全性 | 容易越界 | 限制非法操作 |
| 灵活性 | 支持随机访问 | 支持多种迭代类型 |
迭代器的优势
- 通用性:适配各种容器,包括链表、哈希表等非连续结构。
- 统一接口:标准化接口便于与STL算法配合使用。
- 类型安全:防止非法访问,确保容器的正确操作。
迭代器分类
- 输入/输出迭代器:单向读取/写入。
- 前向/双向迭代器:支持单/双向遍历。
- 随机访问迭代器:支持随机访问(如
vector)。
总结来说,迭代器提供了比指针更通用的容器访问方式,使得代码可移植性更强,适应性更好。
1.4.8 说说STL迭代器是怎么删除元素的
在 C++ 的 STL 中,迭代器通过特定的容器方法来删除元素,但具体实现和操作方式因容器而异。下面是常见容器的元素删除方法及注意事项:
1. vector、deque 等序列容器
在 vector 和 deque 中,删除元素通常使用 erase 方法。删除操作后的迭代器指向已删除元素的下一个位置,因此可以直接利用返回的迭代器继续遍历:
std::vector<int> vec = {1, 2, 3, 4};
auto it = vec.begin();
while (it != vec.end()) {if (*it == 2) {it = vec.erase(it); // 删除元素,并更新迭代器} else {++it;}
}
- 注意:
erase会使其他迭代器失效,因为vector和deque需要移动后续元素以保持连续内存结构。因此,删除操作后应避免使用旧的迭代器。
2. list 和 forward_list
list 和 forward_list 是链表实现,删除元素时不需要移动其他元素,只需修改指针,因此只会使指向已删除元素的迭代器失效:
std::list<int> lst = {1, 2, 3, 4};
auto it = lst.begin();
while (it != lst.end()) {if (*it == 2) {it = lst.erase(it); // 删除后返回下一个有效迭代器} else {++it;}
}
- 优点:由于链表的删除操作不需要移动其他元素,因此性能较高,且不会使其他迭代器失效。
3. map 和 set
在关联容器(如 map 和 set)中,可以使用 erase 方法删除元素。删除后返回的迭代器指向下一个元素:
std::map<int, int> mp = {{1, 10}, {2, 20}, {3, 30}};
auto it = mp.begin();
while (it != mp.end()) {if (it->first == 2) {it = mp.erase(it); // 删除后返回下一个迭代器} else {++it;}
}
- 注意:关联容器的删除操作不会影响其他迭代器,因其内部结构无需移动数据以保持一致性。
4. 使用 remove 和 remove_if
对于序列容器(如 vector、deque),可以结合 std::remove 和 erase 实现元素删除:
std::vector<int> vec = {1, 2, 3, 4};
vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end()); // 删除值为2的元素
- 注意:
remove只移动元素,并不真正删除元素,需要用erase来清理移动后的重复元素。
总结
vector、deque等:使用erase删除元素,但会导致迭代器失效。list、forward_list:使用erase,删除操作效率高,只失效当前迭代器。map、set:erase只失效当前迭代器,不影响其他迭代器。remove/remove_if与erase结合使用:适用于批量删除特定值的情况。
1.4.9 说说STL中resize和reserve的区别
在 C++ 的 STL 中,resize 和 reserve 都用于调整容器的大小或容量,但它们的作用和使用场景不同,具体区别如下:
1. resize
resize 用于改变容器的大小,即元素的个数,适用于vector、deque等支持动态大小的容器。
- 如果
resize的新大小大于当前大小,则会自动扩充新元素并填充默认值(对于内置类型为0,对于类对象则是默认构造的对象)。 - 如果
resize的新大小小于当前大小,则会删除多余的元素。
示例:
std::vector<int> vec = {1, 2, 3};
vec.resize(5); // vec 现在包含 {1, 2, 3, 0, 0}
vec.resize(2); // vec 现在包含 {1, 2}
- 用途:
resize用于调整实际元素数量,当我们希望增加或减少容器的元素数量时使用。 - 影响大小和容量:
resize会改变容器的大小,并在需要时调整容量(扩展存储空间)。
2. reserve
reserve 用于调整容器的容量,即分配内存空间以容纳更多元素,但不改变当前的元素数量。reserve 仅影响 vector 和 string 的容量,不影响大小。
reserve只增加容器的容量,但不会直接创建或删除元素。reserve只在容量不足时有效,若reserve的值小于当前容量,调用无效。
示例:
std::vector<int> vec = {1, 2, 3};
vec.reserve(10); // 预分配存储空间以容纳10个元素,但vec的大小仍为3
- 用途:
reserve主要用于减少内存分配次数,提高性能。当预计容器要存储大量元素时,可以提前分配空间,避免频繁的内存重新分配。 - 影响容量,不影响大小:
reserve只影响容器的容量,增加的只是内存空间,不会创建新的元素,也不会影响现有元素。
总结对比
| 特性 | resize | reserve |
|---|---|---|
| 影响 | 改变容器大小并调整容量 | 仅增加容量,不影响容器大小 |
| 新增元素 | 会根据需要新增元素并填充默认值 | 不新增元素,只增加内存空间 |
| 删除元素 | 会移除超出新大小的元素 | 不会删除任何元素 |
| 应用场景 | 需要改变实际元素数量时 | 预先分配空间以减少内存分配次数 |
resize 用于调整实际元素个数,而 reserve 则是提升性能的优化手段,用于提前分配足够的空间。
1.4.10 说说STL容器动态链接可能产生的问题?
STL 容器动态链接指的是在使用标准模板库(STL)容器时,将这些容器的实现包含在动态链接库(DLL)或共享库(如 .dll、.so 文件)中,并在程序运行时动态加载这些库。通常情况下,STL 的实现是静态链接的,直接包含在可执行文件内,而动态链接可以让程序在多个模块(如主程序和 DLL)之间共享同一份标准库实现。
在 C++ 中,动态链接 STL 容器主要应用在以下场景:
- 模块化程序设计:将 STL 容器的操作或接口放在动态链接库中,便于代码的模块化管理、版本更新和重用。
- 内存管理和节省空间:动态链接库可以让多个程序共享一份标准库代码,减少可执行文件的大小,并节省系统内存。
- 跨模块数据共享:将 STL 容器封装在动态库中后,主程序和库可以共享数据。
STL 容器动态链接可能出现的问题
动态链接 STL 容器可能会带来一些风险和问题,包括内存分配不一致、二进制接口(ABI)不兼容、迭代器失效等问题,主要是因为:
- 不同模块可能使用不同的内存分配器,导致内存管理冲突。
- 编译器和标准库版本不一致可能导致容器结构不同,导致二进制不兼容。
- 容器内部实现依赖于编译器,动态链接时容器的迭代器、指针可能会失效。
STL 容器动态链接的常见实践
- 接口抽象:避免跨模块传递 STL 容器,使用抽象接口来封装容器的内部操作。
- 确保编译环境一致:确保所有模块使用相同的编译器和标准库版本,以避免 ABI 不兼容。
- 使用 PImpl 设计模式:将 STL 容器的实现封装在私有实现类中,以减少跨模块的 STL 容器依赖。
典型示例
假设在一个动态链接库中封装了一个 std::vector<int> 并提供操作函数,主程序可以调用该函数对向量进行操作,但避免直接传递 std::vector<int>。这种封装可以减少跨模块传递 STL 容器的风险。
1.4.11 说说map和unordered_map的区别?底层实现
map 和 unordered_map 是 C++ 标准库中的两种关联容器,用于存储键值对(key-value pair)。尽管它们都提供键到值的映射功能,但在实现和特性上存在显著差异。
1. map 与 unordered_map 的区别
| 特性 | map | unordered_map |
|---|---|---|
| 内部实现 | 基于红黑树(自平衡二叉搜索树) | 基于哈希表 |
| 有序性 | 按键自动排序 | 键的存储顺序不固定 |
| 时间复杂度 | 查找、插入、删除:O(log n) | 查找、插入、删除:平均 O(1),最坏 O(n) |
| 迭代顺序 | 按照键的排序顺序进行遍历 | 无序,按哈希值存储 |
| 内存消耗 | 相对较少,适合需要顺序访问的场景 | 需要额外的哈希表和负载因子管理,内存占用较高 |
| 使用场景 | 需要排序或按顺序访问时 | 注重查找效率,不要求顺序时 |
2. 底层实现
map 的底层实现:红黑树
- 数据结构:
map使用红黑树(Red-Black Tree)实现,这是一种平衡二叉搜索树。红黑树的特性使得插入、删除和查找操作都具有 O(log n) 的时间复杂度。 - 有序性:红黑树确保所有键都按顺序排列,因此
map是有序的。遍历时按照键的升序或降序输出。 - 自动平衡:在插入或删除元素时,红黑树会进行重新平衡,以确保树的高度维持在 log(n) 级别,使操作效率稳定。
unordered_map 的底层实现:哈希表
- 数据结构:
unordered_map基于哈希表实现,存储元素时通过哈希函数将键映射到哈希表中的位置(桶),实现快速查找。 - 无序性:哈希表不维护元素顺序,存储顺序仅与哈希值有关,因此
unordered_map是无序的。 - 哈希冲突:如果两个键的哈希值相同,就会发生哈希冲突。
unordered_map通过链地址法(开放链地址法)解决冲突,即将相同哈希值的元素存储在一个链表或桶中。 - 负载因子和重新哈希:为了维持查找性能,
unordered_map有一个负载因子(默认 1.0)。当元素数量超过负载因子阈值时,unordered_map会扩展容量并重新计算元素的哈希位置。
3. 选择使用场景
map:当需要键值对按顺序存储,或者频繁执行范围查询(如查找所有大于或小于某键的元素)时使用。unordered_map:当更关注查找、插入和删除操作的平均性能且不需要顺序时使用。
1.4.12 说说vector和list的区别,分别适用于什么场景?
vector 和 list 是 C++ STL 中两种常见的序列容器,但它们在底层实现、操作效率和使用场景上存在显著差异。下面是详细的比较:
1. vector 与 list 的区别
| 特性 | vector | list |
|---|---|---|
| 底层实现 | 连续内存块(动态数组) | 双向链表 |
| 随机访问 | 支持 O(1) 时间复杂度的随机访问 | 不支持随机访问,需从头或尾遍历 |
| 插入/删除效率 | 尾部插入和删除为 O(1),中间插入/删除为 O(n) | 任意位置插入和删除为 O(1) |
| 内存占用 | 内存连续分配,节省空间 | 每个元素有额外的指针,内存开销较大 |
| 迭代器失效 | 插入或删除元素可能导致迭代器失效 | 插入、删除不会导致其他迭代器失效 |
| 适用场景 | 需要频繁随机访问、按顺序遍历的场景 | 需要频繁在中间插入或删除元素的场景 |
2. vector 的特点和适用场景
-
特点:
- 采用连续内存存储,可以高效地进行随机访问。
- 尾部操作(如
push_back)效率高,为 O(1) 时间复杂度。 - 进行中间插入或删除时,需要移动大量元素,导致效率较低(O(n))。
-
适用场景:
- 适合需要大量随机访问的情况,如频繁通过索引访问元素。
- 数据量稳定、不需要频繁增减时,如有序数据的缓存、图形点集存储等。
3. list 的特点和适用场景
-
特点:
- 采用双向链表实现,每个节点包含指向前后节点的指针。
- 插入、删除操作效率高,为 O(1) 时间复杂度,特别适合频繁在容器中间进行修改的情况。
- 不支持随机访问,查找效率较低,需要 O(n) 时间遍历链表。
-
适用场景:
- 适合频繁在容器中间插入、删除元素的情况,如事件队列、需要频繁增减的任务列表等。
- 不适合频繁随机访问的场景,除非访问仅限于头尾部的情况(如双端队列实现)。
4. 总结
- 选择
vector:当需要频繁随机访问,且主要操作是尾部插入时。 - 选择
list:当数据量不大、需要在中间频繁插入和删除,并且随机访问要求不高时。
1.4.13 简述vector的实现原理
std::vector 是 C++ STL 中的动态数组容器,其底层实现主要基于连续内存块,提供了动态调整大小和高效的随机访问功能。它的实现原理可以分为以下几个方面:
1. 底层存储结构
vector使用一个连续的内存块来存储元素,这使得它支持 O(1) 时间复杂度的随机访问。- 内存块的大小不固定,当插入元素数量超过当前容量时,
vector会自动重新分配内存,并将现有元素复制到新的更大的内存块中。 vector的底层包含三个指针:begin指向第一个元素,end指向最后一个元素之后的位置,capacity指向当前分配内存的尾部位置。
2. 动态扩容机制
- 每当
vector的元素数量超过当前容量时,就会触发扩容。 - 扩容时,一般会分配一个比当前容量更大的新内存块(通常为当前容量的 2 倍),然后将旧元素复制到新内存块中,释放旧内存。
- 扩容策略为摊还复杂度提供了优化,尽管扩容操作是 O(n),但摊还下来平均插入复杂度为 O(1)。
3. 元素访问
vector支持使用索引访问元素,提供 O(1) 时间复杂度的随机访问。- 通过重载
[]操作符和at()方法,允许使用数组语法访问元素,at()还会进行边界检查,而[]不会。
4. 内存管理
vector使用 RAII(Resource Acquisition Is Initialization)原则来管理内存分配和释放。- 当
vector被销毁时,析构函数会释放底层内存并调用每个元素的析构函数,确保资源不泄漏。
5. 插入与删除操作
- 尾部插入:通过
push_back在尾部插入元素。若未超过容量,则直接插入;若超过容量,则触发扩容。 - 尾部删除:通过
pop_back删除尾部元素,时间复杂度为 O(1)。 - 中间插入或删除:中间位置插入或删除元素会导致大量元素移动,时间复杂度为 O(n)。
6. 迭代器失效
vector在执行插入、删除和扩容操作时,可能会导致原有的迭代器失效。- 尤其在扩容后,所有迭代器都会失效,因为内存位置已发生变化。
- 尾部插入或删除操作一般只会使尾部迭代器失效,而中间插入删除则可能使所有迭代器失效。
总结
std::vector 是基于动态数组的容器,具有快速的随机访问和动态扩容机制。在不需要频繁的中间插入和删除时,vector 是一个高效的选择。
1.4.14 简述STL中的 map 的实现原理
std::map 是 C++ STL 中的一种关联容器,用于存储键值对(key-value pair),且元素按键的升序排列。它的实现基于一种自平衡二叉搜索树,即红黑树(Red-Black Tree)。以下是 std::map 的实现原理:
1. 红黑树的数据结构
std::map采用红黑树作为底层数据结构,这是一种平衡二叉搜索树,具有稳定的 O(log n) 时间复杂度的插入、查找和删除操作。- 红黑树是一种特殊的二叉树,其中每个节点都带有一个颜色(红色或黑色),并满足以下性质:
- 根节点是黑色的。
- 每个叶子节点(
nullptr节点)是黑色的。 - 红色节点的子节点必须是黑色(即不存在连续的红色节点)。
- 从任一节点到其每个叶子节点的路径上包含相同数量的黑色节点。
2. 自动排序
- 红黑树的节点根据键值排序,因此
map中的元素总是按键升序排列。 - 每次插入新元素时,红黑树会根据键值进行位置调整,确保满足排序要求。
3. 插入与删除操作
- 插入:
std::map的插入操作会根据键值在红黑树中找到合适的位置,将新节点插入树中。- 插入后可能破坏红黑树的平衡,红黑树会通过旋转和重新着色来恢复平衡。
- 插入的时间复杂度为 O(log n)。
- 删除:
std::map的删除操作通过定位键值找到目标节点,移除该节点。- 删除同样可能破坏红黑树的平衡,红黑树会进行必要的旋转和重新着色来恢复平衡。
- 删除的时间复杂度为 O(log n)。
4. 查找操作
map支持 O(log n) 的查找效率。查找过程通过键值定位节点,在红黑树中从根节点开始,根据键值大小沿树路径查找目标元素。
5. 迭代器
std::map的迭代器是双向迭代器,支持从键值最小到最大或反向遍历所有元素。- 由于红黑树内部结构的稳定性,
std::map的迭代器在非删除的情况下不会失效。 - 插入元素不会影响其他节点的顺序,但删除节点会影响目标节点和其父节点的迭代器。
6. 节点内存管理
std::map使用动态分配的节点来构建红黑树结构,每个节点包含键值对和两个子节点指针。- 键值对的内存管理采用 RAII 原则,
std::map析构时会递归删除节点,调用键和值的析构函数,避免内存泄漏。
7. 适用场景
std::map适用于需要保持有序性的数据集合,如按键排序、频繁查找、范围查询等场景。- 不适合频繁随机访问的情况,因为其访问效率不如连续内存的容器如
vector。
总结
std::map 基于红黑树实现,提供高效的有序键值存储,插入、删除和查找操作都具有 O(log n) 的复杂度,是管理有序键值对的良好选择。
1.4.15 C++中的vector和list中,如果删除末尾的元素,其指针和迭代器如何变化?若删除的是中间的元素呢?
在 C++ 中,std::vector 和 std::list 是两种常用的序列容器,它们在删除元素时对指针和迭代器的影响是不同的。以下是对这两种容器在删除末尾和中间元素时的行为的详细说明:
1. std::vector
删除末尾元素
- 操作:使用
pop_back()或erase()删除末尾元素。 - 影响:
- 删除末尾元素后,
vector的容量不变,指向末尾元素的迭代器(如end())会失效。 - 其他有效的迭代器(指向未删除元素的迭代器)仍然有效。
- 删除末尾元素后,
删除中间元素
- 操作:使用
erase()删除中间元素。 - 影响:
- 中间元素被删除后,后面的所有元素会被向前移动以填补空缺。
- 这会导致所有指向被删除元素及其后续元素的迭代器失效,包括
begin()到end()范围内的所有迭代器。 - 仅指向删除元素之前的元素的迭代器会保持有效。
2. std::list
删除末尾元素
- 操作:使用
pop_back()或erase()删除末尾元素。 - 影响:
- 删除末尾元素后,
list中的其他元素不需要移动,因为它是基于双向链表的实现。 - 只有指向已删除元素的迭代器会失效,其他指向有效元素的迭代器仍然有效。
- 删除末尾元素后,
删除中间元素
- 操作:使用
erase()删除中间元素。 - 影响:
- 删除中间元素不会影响链表的其他元素,且不需要移动任何元素。
- 只有指向已删除元素的迭代器会失效,其他指向有效元素的迭代器仍然有效。
总结
-
std::vector:- 删除末尾元素:末尾的迭代器失效,其他迭代器仍然有效。
- 删除中间元素:所有指向被删除元素及其后续元素的迭代器失效,指向前面的元素的迭代器仍然有效。
-
std::list:- 删除末尾元素:仅指向已删除元素的迭代器失效,其他迭代器仍然有效。
- 删除中间元素:仅指向已删除元素的迭代器失效,其他迭代器仍然有效。
1.4.16 map 和 set 有什么区别,分别又是怎么实现的?
std::map 和 std::set 是 C++ STL 中的两种关联容器,它们都用于存储有序的数据,但在功能和实现上有一些显著的区别。以下是它们的主要区别以及各自的实现原理。
1. 主要区别
| 特性 | std::map | std::set |
|---|---|---|
| 存储内容 | 存储键值对(key-value pairs)。 | 仅存储唯一的键(keys)。 |
| 访问方式 | 通过键访问值(map[key])。 | 只存储键,不存储值,不能通过键访问。 |
| 重复性 | 允许相同的键(但值可以不同),值会覆盖同键的旧值。 | 不允许重复的键。 |
| 迭代器 | 迭代器返回的是键值对(std::pair)。 | 迭代器返回的是唯一键。 |
| 适用场景 | 需要根据键快速查找、插入和更新值。 | 只需存储唯一值并进行有序访问。 |
2. 实现原理
2.1 std::map 实现
- 底层数据结构:
std::map通常使用 红黑树(Red-Black Tree)作为底层实现。 - 特性:
- 有序性:红黑树确保插入的元素按键的升序排列。
- 复杂度:插入、查找和删除操作的时间复杂度均为 O(log n)。
- 节点结构:
- 每个节点包含一个键和一个与之关联的值。
- 节点之间通过指针连接,形成二叉搜索树结构。
2.2 std::set 实现
- 底层数据结构:
std::set也通常使用 红黑树。 - 特性:
- 有序性:与
map类似,set的元素根据键的升序排列。 - 复杂度:插入、查找和删除操作的时间复杂度同样为 O(log n)。
- 有序性:与
- 节点结构:
- 每个节点仅包含一个键,没有值部分。
- 节点之间通过指针连接,以形成二叉搜索树结构。
3. 使用示例
-
std::map示例:#include <iostream> #include <map>int main() {std::map<int, std::string> myMap;myMap[1] = "One";myMap[2] = "Two";myMap[1] = "Updated One"; // 更新键为1的值for (const auto& pair : myMap) {std::cout << pair.first << ": " << pair.second << std::endl;}return 0; } -
std::set示例:#include <iostream> #include <set>int main() {std::set<int> mySet;mySet.insert(2);mySet.insert(1);mySet.insert(3);mySet.insert(2); // 插入重复元素,set 不会存储重复值for (const auto& value : mySet) {std::cout << value << std::endl;}return 0; }
总结
std::map是键值对的集合,允许根据键快速访问值,并允许重复键(但值会覆盖)。std::set是唯一键的集合,只存储键,确保没有重复值。- 两者通常都使用红黑树作为底层实现,提供高效的查找、插入和删除操作。
1.4.17 说说push_back 和 emplace_back 的区别
在 C++ 中,push_back 和 emplace_back 是 std::vector 和其他 STL 容器常用的方法,用于在容器的末尾添加元素。虽然这两个方法的功能相似,但它们的工作方式和性能上有一些关键的区别。
1. push_back
- 功能:
push_back将一个已有的对象复制或移动到容器的末尾。 - 参数:它接受一个对象的引用(通常是左值或右值),并根据情况执行复制构造或移动构造。
- 性能:
- 当传递左值时,会进行复制构造,可能会带来性能开销。
- 当传递右值时,会调用移动构造(如果有),提高效率。
示例:
#include <iostream>
#include <vector>class MyClass {
public:MyClass(int x) : value(x) {std::cout << "Constructed: " << value << std::endl;}int value;
};int main() {std::vector<MyClass> vec;MyClass obj(10);vec.push_back(obj); // 复制构造vec.push_back(MyClass(20)); // 移动构造(如果有移动构造函数)return 0;
}
2. emplace_back
- 功能:
emplace_back在容器的末尾直接构造对象,而不需要先创建一个对象再复制或移动。 - 参数:它接受构造对象所需的参数,并在容器内部直接调用构造函数。
- 性能:
emplace_back可以避免不必要的复制或移动构造,通常会提供更好的性能。- 适用于那些需要传递多个参数或复杂构造的情况。
示例:
#include <iostream>
#include <vector>class MyClass {
public:MyClass(int x) : value(x) {std::cout << "Constructed: " << value << std::endl;}int value;
};int main() {std::vector<MyClass> vec;vec.emplace_back(10); // 直接构造对象vec.emplace_back(20); // 直接构造对象return 0;
}
3. 主要区别总结
| 特性 | push_back | emplace_back |
|---|---|---|
| 对象创建方式 | 复制或移动已有对象 | 直接在容器内部构造对象 |
| 参数 | 接受对象(左值或右值) | 接受构造对象所需的参数 |
| 性能 | 可能会涉及额外的复制或移动开销 | 避免不必要的复制或移动,通常更高效 |
| 适用场景 | 适合简单的对象添加 | 适合复杂对象或多参数构造 |
总结
- 使用
push_back适合简单的对象添加,但可能会产生额外的复制或移动开销。 emplace_back更适合需要高性能或复杂对象构造的场景,能够提高程序的效率。
1.4.18 STL中vector 与 list 具体是怎么实现的?常见操作的时间复杂度是多少?
在 C++ 标准模板库(STL)中,std::vector 和 std::list 是两种常用的序列容器,它们的实现和性能特征有很大不同。以下是对这两种容器的具体实现原理以及常见操作的时间复杂度分析。
1. std::vector 的实现
实现原理
- 底层数据结构:
std::vector使用动态数组实现。它通过一个指向动态分配内存的指针来存储元素。 - 容量管理:
vector维护一个容量(capacity)和大小(size),当插入新元素导致超出容量时,vector会:- 分配更大的内存(通常是原容量的 1.5 到 2 倍)。
- 将现有元素复制到新内存中。
- 释放旧内存。
- 连续内存:
vector的元素在内存中是连续存储的,这使得可以通过指针或迭代器有效地进行随机访问。
常见操作的时间复杂度
| 操作 | 时间复杂度 |
|---|---|
| 插入(末尾) | O(1) (摊销) |
| 插入(中间) | O(n) |
| 删除(末尾) | O(1) |
| 删除(中间) | O(n) |
| 查找 | O(n) |
| 随机访问 | O(1) |
2. std::list 的实现
实现原理
- 底层数据结构:
std::list使用双向链表实现。每个节点包含指向前一个节点和下一个节点的指针,以及存储的元素。 - 节点分散存储:由于使用链表,
list中的元素在内存中并不是连续存储的。每个节点在堆上独立分配,具有灵活性。 - 插入和删除效率:在
list中,插入和删除操作非常高效,因为只需调整相邻节点的指针,不涉及大量数据移动。
常见操作的时间复杂度
| 操作 | 时间复杂度 |
|---|---|
| 插入(任意位置) | O(1) |
| 删除(任意位置) | O(1) |
| 查找 | O(n) |
| 随机访问 | O(n) |
3. 总结
std::vector是基于动态数组实现的,适合频繁的随机访问和在末尾插入的场景,但在中间插入和删除时效率较低。std::list是基于双向链表实现的,适合频繁的插入和删除操作,但不支持随机访问,查找效率较低。
4. 适用场景
- 使用
std::vector适合需要快速访问元素的情况,特别是频繁在末尾添加元素的场景。 - 使用
std::list适合需要频繁插入和删除操作的场景,但不需要随机访问元素的情况下。
相关文章:
1.4 STL C++面试问题
1.4.1 说说STL的基本组成部分 总结 STL 的基本组成部分包括容器、算法、迭代器、函数对象和仿函数和适配器。通过这些组件,STL 提供了高效、灵活和可复用的代码结构,极大地提高了 C 的开发效率和程序的可维护性。STL 的设计思想使得算法和数据结构的使…...
Bash、sh 和 Shell都弄混了?
在Linux和Unix系统中,Bash、sh 和 Shell 都与命令行解释器相关,但它们各自的含义和作用略有不同。以下是它们之间的关系和区别: Shell Shell 是一个通用术语,指的是操作系统中负责解释和执行用户命令的程序。它是用户与操作系统…...
架构师备考专栏-导航页
简介 架构师备考专栏——软考系统架构师考试的学习宝典,集合了全面覆盖架构师考试大纲的精华文章。每篇文章都为本人手输,并校对数遍后发表,在此我保障每篇文章的质量绝对过关。诚邀对架构师软考感兴趣的朋友们收藏此页面,并根据个人所需高效…...
STM32-Cube定时器TIM
一、内部时钟源 1、创建项目 File → New → STM32 project选择STM32F103C8T6单片机,命名TIM 2、配置单片机 1.打开USART1,方便我们与电脑连接查看数据 开启UART1并开启中断。 2、设置时钟源 开启外部高速晶振 将时钟频率设置为72MHz 设置调试模…...
Webpack 是什么? 解决了什么问题? 核心流程是什么?
在前端开发中,Webpack 无疑是一个举足轻重的工具。它作为一个静态资源打包工具,能够帮助开发者将项目中的各种资源高效整合,以便于在浏览器中加载和执行。本文将深入探讨 Webpack 的核心功能、解决的问题以及 Webpack的核心流程。 Webpack是什…...
Jenkins面试整理-Jenkins 的主要用途是什么?
Jenkins 的主要用途 是在软件开发流程中实现自动化,尤其是在持续集成(CI)和持续交付/部署(CD)中。具体来说,Jenkins 的主要用途包括: 1. 持续集成(CI): ● Jenkins 自动从版本控制系统(如 Git、SVN)中拉取代码,自动化地编译、构建和测试代码。 ● 每当开发人员提…...
Linux下使用C/C++进行UDP网络编程
UDP 是User Datagram Protocol 的简称,中文名是用户数据报协议,是一种无连接、不可靠的协议,同样它也是工作在传顺层。它只是简单地实现从一端主机到另一端主机的数据传输功能,这些数据通过 IP 层发送,在网络中传输&am…...
【JavaEE初阶】网络原理—关于TCP协议值滑动窗口与流量控制,进来看看吧!!!
前言 🌟🌟本期讲解关于TCP协议的重要的机制“连接的建立和断开”~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 …...
无人机避障——使用三维PCD点云生成的2D栅格地图PGM做路径规划
着重介绍通过对三维 PCD 点云进行处理生成 2D 栅格地图 PGM,而后将该 PGM 地图充分运用到无人系统路径规划之中,使得无人机能够依据此规划合理避开飞行路线上可能出现的障碍物。(解决如何使用PGM的问题) Hybrid A*算法 参考博客…...
supermall项目上拉加载bug分析
1.bug分析 bug出现的过程是这样的:better-scroll框架会计算滚动内容的高度(通过BScroll对象的scrollerHeight属性记录滚动内容的高度) 由于内容中的图片资源还未加载成功 就已经完成计算 导致计算结果错误 而计算之后 图片资源随之加载完成 这时候better-scroll框架…...
【linux网络编程】| socket套接字 | 实现UDP协议聊天室
前言:本节内容将带友友们实现一个UDP协议的聊天室。 主要原理是客户端发送数据给服务端。 服务端将数据再转发给所有链接服务端的客户端。 所以, 我们主要就是要实现客户端以及服务端的逻辑代码。 那么, 接下来开始我们的学习吧。 ps:本节内容…...
第二届开放原子大赛-开源工业软件算法集成大赛即将启动!
第二届开放原子大赛-开源工业软件算法集成大赛作为开放原子开源基金会组织举办的开源技术领域专业赛事,聚焦开源底座框架平台建设,通过组件化集成的开发模式,丰富平台功能模块,拓展其应用场景,以此促进工业软件生态的繁…...
PXC集群(Percona XtraDB Cluster )
一、简介 基于Galera Cluster技术的开源分布式数据库解决方案,它允许你在多个MySQL服务器之间创建一个同步的多主复制集群。 特点: * 多主复制架构: PXC集群支持多主复制,每个节点都可以同时读写数据,这使得应用程序可以更灵活地进行负载均衡和高可用性设置。* 同步复制:…...
分布式光伏是什么意思?如何高效管理?
分布式光伏系统是指在用户现场或靠近用电现场配置较小的光伏发电供电系统,以满足特定用户的需求。根据通知,分布式光伏系统主要有以下几类定义: 10kV以下电压等级接入,且单个并网点总装机容量不超过6MW的分布式电源:这…...
提问GPT
1 理解GPT AI模型即采用深度学习技术的人工神经网络。 你不会被AI取代,而是会被熟练运用AI的人取代 1.1 问答或对话是普通用户与这一轮新AI产品的典型交互方式。 GPT生成式预训练转换器 了解当前GPT产品的形式: 通用聊天机器人…...
李飞飞团队新突破:低成本高泛化机器人训练法,零样本迁移成功率90%!
在机器人训练中,如何高效地利用模拟环境一直是研究者们关注的重点问题。 近日,美国斯坦福大学李飞飞教授团队提出了一种突破性的“数字表亲”(digital cousins)概念。这一创新方法既保留了数字孪生的优势,又大大降低了…...
PHP内存马:不死马
内存马概念 内存马是无文件攻击的一种常用手段,利用中间件的进程执行某些恶意代码。首先要讲的是PHP不死马,实质上就是直接用代码弄一个死循环,强占一个 PHP 进程,并不间断的写一个PHP shell,或者执行一段代码。 不死…...
【python】OpenCV—Connected Components
文章目录 1、任务描述2、代码实现3、完整代码4、结果展示5、涉及到的库函数6、参考 1、任务描述 基于 python opencv 的连通分量标记和分析函数,分割车牌中的数字、号码、分隔符 cv2.connectedComponentscv2.connectedComponentsWithStatscv2.connectedComponents…...
【优选算法篇】前缀之序,后缀之章:于数列深处邂逅算法的光与影
文章目录 C 前缀和详解:基础题解与思维分析前言第一章:前缀和基础应用1.1 一维前缀和模板题解法(前缀和)图解分析C代码实现易错点提示代码解读题目解析总结 1.2 二维前缀和模板题解法(二维前缀和)图解分析C…...
win10 更新npm 和 node
win10 更新npm 和 node win10 更新 npm winR 输入cmd,打开命令行,并输入如下 # 查看当前npm版本 npm -v # 清缓存 npm cache clean --force # 强制更新npm,试过npm update -g,没起作用,版本没变化 npm install -g …...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...
