【C++篇】从零实现 `list` 容器:细粒度剖析与代码实现

文章目录

  • 从零实现 `list` 容器:细粒度剖析与代码实现
      • 前言
      • 1. `list` 的核心数据结构
        • 节点结构分析
      • 2 迭代器设计与实现
        • 2.1 为什么 `list` 需要迭代器?
        • 2.2 实现一个简单的迭代器
        • 2.3 测试简单迭代器
          • 解释:
        • 2.4 增加后向移动和 `->` 运算符
          • 关键点:
          • 实现代码:
        • 2.5 测试前后移动和 `->` 运算符
          • 目的:
          • 测试代码:
          • 输出:
        • 2.6 为什么不能简单使用 `const` 修饰?
          • 问题解释:
          • 直接使用 `const` 修饰的限制:
          • 错误示例:直接使用 `const` 修饰
          • 错误代码:
          • 错误分析:
        • 2.6 正确解决方案:使用模板参数区分 `const` 和 `non-const`
          • 为什么需要模板参数?
          • 使用模板参数的好处:
          • 正确的模板泛化代码实现:
          • 关键点解释:
        • 2.7 测试泛化后的迭代器
          • 测试场景:
          • 测试代码:
          • 输出:
        • 2.8 迭代器设计分析
      • 3. `list` 容器的基本操作
        • 3.1 构造函数
        • 构造函数分析
      • 4. 插入与删除操作
        • 4.1 插入操作
        • 插入操作分析
        • 4.2 删除操作
        • 删除操作分析
      • 5. 反向迭代器
      • 6. 迭代器失效问题
      • 结论

从零实现 list 容器:细粒度剖析与代码实现

接上篇【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器

💬 欢迎讨论:学习过程中有问题吗?随时在评论区与我交流。你们的互动是我创作的动力!

👍 支持我:如果你觉得这篇文章对你有帮助,请点赞、收藏并分享给更多朋友吧!
🚀 一起成长:欢迎分享给更多对计算机视觉和图像处理感兴趣的小伙伴,让我们共同进步!

本文详细介绍如何从零开始实现一个 C++ list 容器,帮助读者深入理解 list 的底层实现,包括核心数据结构、迭代器的设计、以及常见的插入、删除等操作。从初学者到进阶开发者都能从中受益。

前言

在 C++ 标准模板库 (STL) 中,list 是一种双向链表容器,适合频繁插入和删除操作。它与 vector 的主要区别在于不支持随机访问,且插入、删除时无需移动其他元素。这使得 list 在某些场景下具有独特优势,例如大量元素的动态操作。

为了更好地理解 list 的工作原理,我们将在本篇博客中模拟实现一个简单版的 list,同时分析每个步骤背后的原理及其易错点。


1. list 的核心数据结构

我们首先要理解 list 的底层是由双向链表实现的。双向链表中的每个节点不仅包含数据,还包含两个指针,分别指向前一个节点和后一个节点。以下是节点结构的定义:

namespace W {// 定义链表节点template<class T>struct ListNode {T _val;               // 节点存储的值ListNode* _prev;      // 指向前一个节点ListNode* _next;      // 指向后一个节点ListNode(const T& val = T()) : _val(val), _prev(nullptr), _next(nullptr) {}};
}
节点结构分析
  1. _val:存储节点的值。
  2. _prev_next:分别指向前后节点,便于在链表中进行前后遍历和插入、删除操作。

2 迭代器设计与实现

2.1 为什么 list 需要迭代器?

在 C++ 中,vector 是一种动态数组,元素在内存中是连续存储的,因此我们可以使用下标快速访问元素。例如,vec[0] 就可以直接访问到 vector 的第一个元素。这是因为 vector 中的每个元素都可以通过下标计算出准确的内存地址。

然而,list 的底层是链表结构,链表节点在内存中不是连续存放的。因此,链表不能像数组那样通过下标随机访问元素。每个节点都通过指针链接到前一个节点(_prev)和后一个节点(_next)。这意味着,如果你想访问链表中的某个节点,必须从链表的起始位置开始,一个节点一个节点地遍历。这时我们就需要迭代器来方便地遍历链表。

迭代器的作用类似于一个指针,它指向链表中的某个节点,并允许我们通过类似指针的方式访问和操作链表节点。与普通指针不同,迭代器提供了更高级的功能,并且能够保持更好的接口一致性,因此它成为了 STL 容器中访问元素的核心工具。


2.2 实现一个简单的迭代器

为了实现最基本的链表迭代器,首先我们需要定义链表节点的结构。

namespace W {// 链表节点定义template<class T>struct ListNode {T _val;               // 节点中存储的值ListNode* _prev;      // 指向前一个节点ListNode* _next;      // 指向后一个节点ListNode(const T& val = T()) : _val(val), _prev(nullptr), _next(nullptr) {}};
}

我们可以看到,ListNode 是一个模板结构,它包含:

  • _val:存储链表节点的值。
  • _prev:指向链表中前一个节点的指针。
  • _next:指向链表中下一个节点的指针。

接下来,我们定义一个最基本的迭代器 ListIterator。它内部保存了一个指向 ListNode 的指针 _node,并且支持以下基本操作:

  1. 解引用:通过 *it 访问节点中的值。
  2. 前向移动:通过 ++it 访问链表中的下一个节点。
  3. 比较:通过 it != end() 判断两个迭代器是否相等。
namespace W {template<class T>class ListIterator {typedef ListNode<T> Node;  // 使用 Node 表示链表节点类型public:// 构造函数,接受一个指向链表节点的指针ListIterator(Node* node = nullptr) : _node(node) {}// 解引用操作,返回节点的值T& operator*() { return _node->_val; }// 前向移动操作,指向下一个节点ListIterator& operator++() {_node = _node->_next;  // 将当前节点移动到下一个节点return *this;  // 返回自身以支持链式调用}// 比较操作,判断两个迭代器是否相等bool operator!=(const ListIterator& other) const { return _node != other._node; }private:Node* _node;  // 迭代器指向的链表节点};
}
2.3 测试简单迭代器

为了测试这个简单的迭代器,我们先创建几个链表节点,并将它们相互连接,形成一个链表。接着使用迭代器遍历链表,输出每个节点的值。

#include <iostream>int main() {// 创建三个节点,分别存储值 1、2、3W::ListNode<int> node1(1);      W::ListNode<int> node2(2);      W::ListNode<int> node3(3);      // 链接节点形成链表node1._next = &node2;  // node1 的下一个节点是 node2node2._prev = &node1;  // node2 的前一个节点是 node1node2._next = &node3;  // node2 的下一个节点是 node3node3._prev = &node2;  // node3 的前一个节点是 node2// 创建迭代器,指向第一个节点W::ListIterator<int> it(&node1);// 使用迭代器遍历链表并输出每个节点的值while (it != nullptr) {std::cout << *it << std::endl;  // 输出当前节点的值++it;  // 前向移动到下一个节点}return 0;
}

输出:

1
2
3
解释:
  • it 初始指向第一个节点 node1
  • 每次 *it 解引用获取当前节点的值,++it 将迭代器移动到链表中的下一个节点,直到链表结束。

2.4 增加后向移动和 -> 运算符

我们之前实现的迭代器只能向前移动。然而,list双向链表,因此我们还需要增加后向移动 -- 的功能,以便迭代器可以从链表的末尾向前遍历。同时,为了让迭代器像指针一样工作,我们还要重载 -> 运算符,以支持通过 -> 访问节点的成员变量。

关键点:
  1. _val 是基本数据类型(如 int)时,可以直接通过 *it 来获取节点的值,而不需要使用 *(it->)。虽然 *(it->) 语法上是正确的,但显得繁琐且不必要。

    为什么 *(it->) 是正确的?
    因为 it-> 是在调用 operator->(),返回 _val 的指针,然后 *(it->) 解引用该指针。语法上是没有问题的,但通常我们直接使用 *it 更简洁。

  2. _val 是自定义类型时,可以使用 it->x 直接访问自定义类型的成员变量 x。编译器会将 it->x 优化为 it.operator->()->x,让访问更加方便。

实现代码:
namespace W {template<class T>class ListIterator {typedef ListNode<T> Node;  // 使用 Node 表示链表节点类型public:ListIterator(Node* node = nullptr) : _node(node) {}// 解引用操作,返回节点的值T& operator*() { return _node->_val; }// 指针操作,返回节点的值的指针T* operator->() { return &(_node->_val); }// 前向移动,指向下一个节点ListIterator& operator++() {_node = _node->_next;return *this;}// 后向移动,指向前一个节点ListIterator& operator--() {_node = _node->_prev;return *this;}// 比较操作,判断两个迭代器是否相等bool operator!=(const ListIterator& other) const { return _node != other._node; }private:Node* _node;  // 迭代器指向的链表节点};
}

2.5 测试前后移动和 -> 运算符
目的:

通过一个测试程序验证迭代器的前后移动功能,并使用 -> 运算符访问节点的值。我们将测试基本数据类型和自定义类型的情况,展示如何在不同数据类型下使用迭代器。

测试代码:
  1. 对于 int 类型,可以直接使用 *it 访问节点的值,而不需要使用 *(it->)。虽然 *(it->) 语法上是正确的,但没有必要,因为直接 *it 就能得到节点的值。

  2. 对于自定义类型 CustomType,可以通过 it->x 来访问自定义类型 CustomType 中的成员变量 x

#include <iostream>struct CustomType {int x;
};int main() {// 创建三个节点,分别存储值 1、2、3W::ListNode<int> node1(1);      W::ListNode<int> node2(2);      W::ListNode<int> node3(3);      // 链接节点形成链表node1._next = &node2;node2._prev = &node1;node2._next = &node3;node3._prev = &node2;// 创建迭代器,初始指向第二个节点W::ListIterator<int> it(&node2);// 对于 int 类型,直接使用 *it 访问节点的值std::cout << *it << std::endl;  // 输出 2// 使用 it-> 访问 CustomType 的成员变量W::ListNode<CustomType> customNode1({1});W::ListNode<CustomType> customNode2({2});customNode1._next = &customNode2;customNode2._prev = &customNode1;W::ListIterator<CustomType> customIt(&customNode1);// 访问自定义类型 CustomType 的成员变量 xstd::cout << customIt->x << std::endl;  // 输出 1return 0;
}
输出:
2
1

2.6 为什么不能简单使用 const 修饰?
问题解释:

vector 中,const_iterator 可以简单地通过 const 修饰实现,因为 vector连续内存存储的结构。const 只需要防止修改元素的值即可。但在 list 中,情况要复杂得多。list双向链表,迭代器不仅需要访问节点的值,还需要操作链表的前驱和后继节点(prevnext)。简单使用 const 修饰的迭代器无法完全满足 list 的需求。

直接使用 const 修饰的限制:
  • const 修饰的迭代器会使得一些必要的操作(如前向或后向移动)无法进行。
  • 例如:直接对 const 迭代器执行 ++-- 操作,会导致编译错误,因为这些操作需要修改迭代器的内部状态(指针),但 const 修饰符禁止任何修改。

错误示例:直接使用 const 修饰

为了更清楚地说明问题,以下是一个错误示例,展示了为什么简单使用 const 修饰符会导致问题。

错误代码:
#include <iostream>template<class T>
struct ListNode {T _val;ListNode* _prev;ListNode* _next;ListNode(T val) : _val(val), _prev(nullptr), _next(nullptr) {}
};template<class T>
class ListIterator {typedef ListNode<T> Node;public:ListIterator(Node* node = nullptr) : _node(node) {}// 解引用操作,返回节点的值T& operator*() { return _node->_val; }// 前向移动ListIterator& operator++() {_node = _node->_next;return *this;}// 后向移动ListIterator& operator--() {_node = _node->_prev;return *this;}private:Node* _node;
};int main() {// 创建三个节点,分别存储值 1、2、3ListNode<int> node1(1), node2(2), node3(3);// 链接节点形成链表node1._next = &node2;node2._prev = &node1;node2._next = &node3;node3._prev = &node2;// 尝试创建一个常量迭代器const ListIterator<int> constIt(&node1);// 错误1:前向移动时,编译器报错,因为 ++ 操作符不能对 const 迭代器操作++constIt;  // 编译错误// 错误2:解引用操作也无法进行修改*constIt = 5;  // 编译错误
}
错误分析:
  1. ++constIt 无法使用:由于 const 修饰,迭代器不能修改其指向的节点,因此 ++ 操作无法进行,因为它需要修改迭代器的内部状态。

  2. *constIt = 5 无法修改值:同样,由于 const 修饰符,迭代器不能修改节点的值,因此编译器会报错。

总结:为什么不能简单使用 const 修饰?

  • 限制过多:简单使用 const 修饰符会导致一些必要的操作无法进行,例如前向和后向移动操作 ++--,因为 const 禁止对迭代器内部状态的修改。
  • 需要灵活区分:我们需要通过模板参数 RefPtr 来灵活区分哪些操作需要保持常量,哪些操作允许修改。这使得迭代器可以在常量和非常量链表中都能正确工作。
  • 代码复用:使用模板参数使得我们只需要一套迭代器代码就可以处理 constnon-const 的情况,提高了代码的简洁性和复用性。

2.6 正确解决方案:使用模板参数区分 constnon-const
为什么需要模板参数?

为了应对上面提到的问题,我们可以通过模板参数来区分 constnon-const 的情况。模板参数 RefPtr 可以控制迭代器的行为,使得它在常量链表和非常量链表中都能够正常工作:

  • Ref:控制解引用 * 返回的是非常量引用 T& 还是常量引用 const T&
  • Ptr:控制通过 -> 操作符返回的是非常量指针 T* 还是常量指针 const T*
使用模板参数的好处:
  • 灵活性:可以根据需要处理 constnon-const 的迭代器场景。
  • 保证安全性:对于常量链表,保证不能修改元素的值;而对于非常量链表,可以进行修改操作。
  • 代码复用:通过模板参数,只需要编写一套迭代器代码,既可以用于常量链表,也可以用于非常量链表。

正确的模板泛化代码实现:

通过模板参数 RefPtr,我们可以实现支持 constnon-const 两种迭代器的代码。

namespace W {template<class T, class Ref, class Ptr>class ListIterator {typedef ListNode<T> Node;  // 使用 Node 表示链表节点类型public:ListIterator(Node* node = nullptr) : _node(node) {}// 解引用操作,返回节点的值Ref operator*() const { return _node->_val; }// 指针操作,返回节点的值的指针Ptr operator->() const { return &_node->_val; }// 前向移动,指向下一个节点ListIterator& operator++() {_node = _node->_next;return *this;}// 后向移动,指向前一个节点ListIterator& operator--() {_node = _node->_prev;return *this;}// 比较操作,判断两个迭代器是否相等bool operator!=(const ListIterator& other) const { return _node != other._node; }private:Node* _node;  // 迭代器指向的链表节点};
}
关键点解释:
  1. 模板参数 RefPtr:这两个参数分别用于控制 operator*operator-> 的返回值类型:

    • Ref 用于控制解引用操作的返回类型,可以是 T&const T&
    • Ptr 用于控制 -> 操作符的返回类型,可以是 T*const T*
  2. 前向与后向移动:我们定义了 operator++operator--,这些操作修改了迭代器的内部状态,允许它前向或后向遍历链表节点。


2.7 测试泛化后的迭代器
测试场景:
  1. 对非常量链表:我们测试 int 类型节点,通过 *it 解引用获取节点值。
  2. 对自定义类型 CustomType 的链表:通过 it->x 来访问自定义类型 CustomType 的成员变量 x
  3. 对常量链表:我们测试 const 迭代器,确保无法通过迭代器修改链表节点的值。
测试代码:
#include <iostream>struct CustomType {int x;
};int main() {// 创建三个 int 类型的节点,分别存储值 1、2、3W::ListNode<int> node1(1);      W::ListNode<int> node2(2);      W::ListNode<int> node3(3);      // 链接节点形成链表node1._next = &node2;node2._prev = &node1;node2._next = &node3;node3._prev = &node2;// 创建一个非常量迭代器W::ListIterator<int, int&, int*> it(&node1);std::cout << *it << std::endl;  // 输出 1++it;  // 前向移动std::cout << *it << std::endl;  // 输出 2// 创建自定义类型 CustomType 的链表节点W::ListNode<CustomType> customNode1({1});W::ListNode<CustomType> customNode2({2});customNode1._next = &customNode2;customNode2._prev = &customNode1;// 创建自定义类型的迭代器W::ListIterator<CustomType, CustomType&, CustomType*> customIt(&customNode1);std::cout << customIt->x << std::endl;  // 输出 1// 创建一个常量链表const W::ListNode<int> constNode1(1);const W::ListNode<int> constNode2(2);constNode1._next = &constNode2;// 创建一个常量迭代器W::ListIterator<int, const int&, const int*> constIt(&constNode1);std::cout << *constIt << std::endl;  // 输出 1// 常量迭代器不允许修改值// *constIt = 5;  // 错误:无法修改常量链表节点的值return 0;
}
输出:
1
2
1
1

2.8 迭代器设计分析

通过以上步骤,我们设计了一个功能完整的 list 迭代器,支持以下功能:

  • 指针操作*->,可以访问节点的值和自定义类型的成员变量。
  • 前向与后向移动++--,可以在链表中双向遍历。
  • 支持 const 和非 const:通过模板参数 RefPtr,迭代器能够根据链表是否为常量链表返回不同类型的值或指针,确保常量链表不能被修改。
  • 代码复用:同一套代码可以处理常量链表和非常量链表,极大地提高了代码的灵活性和复用性。

3. list 容器的基本操作

现在我们有了节点和迭代器,接下来实现 list 的核心操作,包括构造、插入、删除和访问元素等。

3.1 构造函数

我们将实现多种构造函数,允许用户创建空链表、指定大小的链表,以及从迭代器区间构造链表。

namespace W {template<class T>class list {typedef ListNode<T> Node;public:typedef ListIterator<T, T&, T*> iterator;// 默认构造函数list() { CreateHead(); }// 指定大小的构造函数list(size_t n, const T& val = T()) {CreateHead();for (size_t i = 0; i < n; ++i)push_back(val);}// 迭代器区间构造函数template<class Iterator>list(Iterator first, Iterator last) {CreateHead();while (first != last) {push_back(*first);++first;}}// 析构函数~list() {clear();delete _head;}// 头节点初始化void CreateHead() {_head = new Node();_head->_next = _head;_head->_prev = _head;}// 清空链表void clear() {Node* cur = _head->_next;while (cur != _head) {Node* next = cur->_next;delete cur;cur = next;}_head->_next = _head;_head->_prev = _head;}private:Node* _head;  // 指向头节点的指针};
}
构造函数分析
  • 默认构造函数:创建一个空链表,并初始化头节点。
  • 指定大小构造函数:使用 push_back 插入 n 个值为 val 的节点。
  • 区间构造函数:从迭代器区间 [first, last) 中构造链表。

4. 插入与删除操作

list 容器的优势在于高效的插入与删除操作。我们将在指定位置插入节点,或删除指定节点,插入和删除时间复杂度均为 O(1)。

4.1 插入操作
namespace W {template<class T>class list {typedef ListNode<T> Node;typedef ListIterator<T, T&, T*> iterator;public:// 在指定位置前插入新节点iterator insert(iterator pos, const T& val) {Node* newNode = new Node(val);Node* cur = pos._node;newNode->_next = cur;newNode->_prev = cur->_prev;cur->_prev->_next = newNode;cur->_prev = newNode;return iterator(newNode);}// 在链表末尾插入新节点void push_back(const T& val) { insert(end(), val); }// 在链表头部插入新节点void push_front(const T& val) { insert(begin(), val); }};
}
插入操作分析
  • 插入效率:在链表中插入时,仅需调整前后节点的指针,不涉及元素移动,因此效率为 O(1)。
  • 头尾插入:通过 push_backpush_front,实现头部和尾部插入。
4.2 删除操作
namespace W {template<class T>class list {typedef ListNode<T> Node;typedef ListIterator<T, T&, T*> iterator;public:// 删除指定位置的节点iterator erase(iterator pos) {Node* cur = pos._node;Node* nextNode = cur->_next;cur->_prev->_next = cur->_next;cur->_next->_prev = cur->_prev;delete cur;return iterator(nextNode);}// 删除链表头部节点void pop_front() { erase(begin()); }// 删除链表尾部节点void pop_back() { erase(--end()); }};
}
删除操作分析
  • 删除效率:与插入类似,删除操作仅涉及指针调整,不需要移动元素,效率为 O(1)。
  • 头尾删除:通过 pop_frontpop_back 实现头部和尾部删除操作。

5. 反向迭代器

在双向链表中,反向迭代器可以通过包装普通迭代器实现。反向迭代器的 ++ 相当于正向迭代器的

--,反之亦然。

namespace W {template<class Iterator>class ReverseListIterator {Iterator _it;public:ReverseListIterator(Iterator it) : _it(it) {}auto operator*() { Iterator temp = _it; --temp; return *temp; }auto operator->() { return &(operator*()); }ReverseListIterator& operator++() { --_it; return *this; }ReverseListIterator operator++(int) { ReverseListIterator temp = *this; --_it; return temp; }ReverseListIterator& operator--() { ++_it; return *this; }ReverseListIterator operator--(int) { ReverseListIterator temp = *this; ++_it; return temp; }bool operator==(const ReverseListIterator& other) const { return _it == other._it; }bool operator!=(const ReverseListIterator& other) const { return !(*this == other); }};
}

6. 迭代器失效问题

当我们删除一个节点时,指向该节点的迭代器会失效。如果继续使用该迭代器,会导致未定义行为。因此,在删除操作后,我们需要使用 erase 返回的迭代器。

void TestIteratorInvalidation() {W::list<int> lst = {1, 2, 3, 4, 5};auto it = lst.begin();while (it != lst.end()) {it = lst.erase(it);  // 正确:使用 erase 返回的新迭代器}
}

结论

通过本次模拟实现,我们深入剖析了 C++ list 的核心功能,从双向链表的数据结构,到迭代器的设计,再到插入和删除操作的高效实现。希望通过这篇文章,大家对 list 有了更为深入的理解,并能在实际开发中灵活运用。

以上就是关于【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/54877.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC5-域对象共享数据

目录 使用ServletAPI向request域对象共享数据 使用ModelAndView向request域对象共享数据 使用Model向request域对象共享数据 使用map向request域对象共享数据 使用ModelMap向request域对象共享数据 Model、ModelMap、Map的关系 向session域共享数据 向application域共享…

asp.net core grpc快速入门

环境 .net 8 vs2022 创建 gRPC 服务器 一定要勾选Https 安装Nuget包 <PackageReference Include"Google.Protobuf" Version"3.28.2" /> <PackageReference Include"Grpc.AspNetCore" Version"2.66.0" /> <PackageR…

Python | Leetcode Python题解之第441题排列硬币

题目&#xff1a; 题解&#xff1a; class Solution:def arrangeCoins(self, n: int) -> int:left, right 1, nwhile left < right:mid (left right 1) // 2if mid * (mid 1) < 2 * n:left midelse:right mid - 1return left

Junit 5 - 理解Mockito,提高UT 覆盖率

前言 当我是1个3年初级程序员时&#xff0c; 我被面试者问到1个问题&#xff1a; 如何保证你的开发任务交付质量 当我是1个7年开发组长时&#xff0c; 我被面试者问到另1个问题&#xff1a;如何保证你的团队的代码质量&#xff0c; 减少rework。 又若干年后&#xff0c; 我才…

Mysql调优之索引优化(四)

一、mysql索引结构B树原理 B树开始就是n树&#xff0c;不是二叉树 B树的非叶子结点存储了数据&#xff0c;导致层级会很深&#xff0c;每一层又有数据又有索引。 B树只有叶子结点存储数据&#xff0c;其余都是存储索引&#xff0c;增加了每层存取索引的数量&#xff08;3层结构…

Comfyui 学习笔记1

如果图像输出被裁剪&#xff0c;则需要使用PrepImageForClipVision&#xff0c;来设置图像距离上边沿的位置. 决定绘画的作用区域&#xff0c;后面的KSample只作用到 mask标记的范围。 图像位置偏移了&#xff0c;可以考虑通过Image crop 裁剪 IPAdapter face 提取时&…

OceanBase 3.X 高可用 (一)

OceanBase 3.X 高可用&#xff08;一&#xff09; 一、分布式核心 OceanBase 3.x 采用的是paxos 协议&#xff0c;与raft协议相比。其复杂程度高&#xff0c;实现技术难度大。 Paxos 协议允许事务日志乱序发送&#xff0c;顺序提交。raft允许事务顺序发送&#xff0c;顺序提…

【java】前端RSA加密后端解密

目录 1. 说明2. 前端示例3. 后端示例3.1 pom依赖3.2 后端结构图3.3 DecryptHttpInputMessage3.4 ApiCryptoProperties3.5 TestController3.6 ApiCryptoUtil3.7 ApiDecryptParamResolver3.8 ApiDecryptRequestBodyAdvice3.9 ApiDecryptRsa3.10 ApiCryptoProperties3.11 KeyPair3…

Android界面控件概述

节选自《Android应用开发项目式教程》&#xff0c;机械工业出版社&#xff0c;2024年7月出版 做最简单的安卓入门教程&#xff0c;手把手视频、代码、答疑全配齐 控件是Android界面的重要组成单元&#xff0c;Android应用主要通过控件与用户交互&#xff0c;Android提供了非常…

raise Exception(“IPAdapter model not found.“)

IPAdapter模型文件太多了&#xff0c;而节点IPAdapter Unified Loader是通过函数&#xff08;get_ipadapter_file与get_clipvision_file&#xff09;预设来加载模型文件&#xff0c;当发生错误“IPAdapter model not found.“时并不指明模型文件名&#xff0c;导致想要有针对性…

在MacOS上安装MongoDB数据库

一、安装方法 1.1 安装包安装 首先&#xff0c;打开MongoDB 官网下载安装包&#xff0c;下载链接&#xff1a;https://www.mongodb.com/try/download/community。 根据自己的系统环境自行选择下载的版本。将下载好的 MongoDB 安装包解压缩&#xff0c;并将文件夹名改为 mon…

理解:基础地理实体相关概述

理解&#xff1a;基础地理实体相关概述 地理实体 geo-entity 现实世界中占据一定且连续空间位置和范围、单独具有同一属 性或完整功能的地理对象。 基础地理实体 fundamental geo-entity 通过基础测绘采集和表达的地理实体&#xff0c;是其他地理实体和相关 信息的定位框架与…

卡通角色检测系统源码分享

卡通角色检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

Keepalived+MySQL 高可用集群

基础架构如下 准备干净的实验环境 [rootmysql1 ~]# systemctl stop firewalld [rootmysql1 ~]# cat /etc/sysconfig/selinux |grep "SELINUXdisabled" SELINUXdisabled [rootmysql1 ~]# setenforce 0 setenforce: SELinux is disabled [rootmysql1 ~…

IoT网关的主要功能有哪些?天拓四方

在数字化浪潮席卷全球的今天&#xff0c;物联网&#xff08;IoT&#xff09;技术凭借其独特的优势&#xff0c;逐渐在各个领域展现出强大的生命力。而IoT网关&#xff0c;作为连接物理世界与数字世界的桥梁&#xff0c;其在物联网体系中的作用愈发凸显。 一、数据聚合与预处理…

acw(树的重心)

给定一颗树&#xff0c;树中包含 n&#x1d45b; 个结点&#xff08;编号 1∼n1∼&#x1d45b;&#xff09;和 n−1&#x1d45b;−1 条无向边。 请你找到树的重心&#xff0c;并输出将重心删除后&#xff0c;剩余各个连通块中点数的最大值。 重心定义&#xff1a;重心是指树…

《论文阅读》 用于产生移情反应的迭代联想记忆模型 ACL2024

《论文阅读》 用于产生移情反应的迭代联想记忆模型 ACL2024 前言简介任务定义模型架构Encoding Dialogue InformationCapturing Associated InformationPredicting Emotion and Generating Response损失函数问题前言 亲身阅读感受分享,细节画图解释,再也不用担心看不懂论文啦…

C++杂项

作业&#xff1a; 将之前实现的顺序表、栈、队列都更改成模板类 顺序表 #include <iostream>using namespace std;template<typename T>class SeqList { private:T *ptr;int size; //总长度int len 0; //当前顺序表实际长度public://初始…

误差不到1毫米的WGS84与CGCS2000坐标转换工具

我们在《WGS84与CGCS2000坐标的精密转换方法》一文中为你分享了一个WGS84与CGCS2000坐标的精密转换纯理论性的方法。 现在&#xff0c;再为你分享一个据说是误差不到1毫米的WGS84与CGCS2000坐标的转换工具&#xff0c;请从文末查看该工具的领取方法。 WGS84与CGCS2000坐标转换…

人工智能有助于解决 IT/OT 集成安全挑战

思科的一项研究表明&#xff0c;信息技术 (IT) 和运营技术 (OT) 融合所带来的安全问题可以通过人工智能 (AI) 解决&#xff0c;尽管该技术也可能被恶意行为者利用。 该报告由思科和 Sapio Research 联合发布&#xff0c;对 17 个国家的 1,000 名行业专业人士进行了调查&#x…