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

C++效率掌握之STL库:map set底层剖析及迭代器万字详解

文章目录

  • 1.map、set的基本结构
  • 2.map、set模拟实现
    • 2.1 初步定义
    • 2.2 仿函数实现
    • 2.3 Find功能实现
    • 2.4 迭代器初步功能实现
      • 2.4.1 ++运算符重载
      • 2.4.2 --运算符重载
      • 2.4.3 *运算符重载
      • 2.4.4 ->运算符重载
      • 2.4.5 !=运算符重载
      • 2.4.6 begin()
      • 2.4.7 end()
    • 2.5 迭代器进阶功能实现
      • 2.5.1 set:const迭代器及insert的实现
      • 2.5.2 map:const迭代器及insert、[ ]运算符重载的实现
  • 3.代码展示
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

mapset 的封装可以说是很天才的底层结构了,本篇将对其结构进行详细的解析,虽然会很复杂且难以理解,但是学完成就感满满,而且对底层理解和面试很有帮助

1.map、set的基本结构

在这里插入图片描述

通过查看官方文档,截取部分关键代码,我们可以发现 set 虽然事 k-k 类型,mapk-v 类型,但是实际上这两个类共用一个红黑树,准确来说是共用同一个模板类型,set<K,K>map<K,pair<K,V>>,下面会进行详细解析

  • size_type node_count:用于记录红黑树节点数量,跟踪树的大小
  • link_type header:是指向红黑树头节点的指针
  • Value value_field:存储节点的值

那么下面我们将自己实现简单的 setmap 类:

2.map、set模拟实现

2.1 初步定义

template<class K>
class set
{
private:RBTree<K, K> _t;
};template<class K, class V>
class map
{
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

平常我们认为键值对指的就是 KV,但是在库里不是这样的,库里的 K 表示键值对的类型,V 表示插入红黑树的键值对,只不过对于 set 来说,KV 是一样的

在这里插入图片描述

在红黑树中,定义的模板参数 T,而不是原先的 pair,这里的 T 表示插入的数据 _data 的类型,这种定义方法能够共同使用同一参数模板,避免额外的代码编写

2.2 仿函数实现

template<class K>
class set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
private:RBTree<K, K, SetKeyOfT> _t;
};template<class K, class V>
class map
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

我们知道 setmap 是通过比较 key,在红黑树中来插入的,但是由于上述的定义,如果每次对于 map 都频繁取出 first 就太麻烦了,因此就定义了仿函数

🚩为什么使用仿函数而不是普通函数呢?

红黑树中只要涉及到数据 _data 的地方,就需要使用到仿函数提取 key,使用普通函数消耗太大,而仿函数带有 inline 的性质,降低消耗。同时官方文档中还对比较进行了实现,即 Compare,模板要求参数必须是一个类型,而普通函数无法作为类型传递

🚩为什么要自己定义仿函数,pair自带的仿函数不行吗?

在这里插入图片描述
虽然 pair 确实有自己的仿函数比较,但是他是比较完 first 后不行,会接着比较 second,这不符合我们的设计思路


在这里插入图片描述

截取了部分 insert 中的代码,利用仿函数确实是能够简单的实现键值 first 的提取,我们再对整体的调用思路进行整理

在这里插入图片描述

其实仿函数主要是为了 map 而设计的,为的就是提取 firstset 为了保持设计模式的一致,因而也设计了相同的仿函数,这样就不用关心是否需要调用这一点了,保持一致性

这里我们不对 Compare 进行实现,有兴趣的可以自己去看底层代码

🔥值得注意的是: 仿函数内不实现比较功能是因为,比较功能是一个外层调用功能,如果放在内部就不能操作者自行去调用了,况且 Compare 也是以仿函数的形式实现的,两个仿函数嵌套过于复杂,不好使用

2.3 Find功能实现

Node* Find(const K& key)
{Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;
}

2.4 迭代器初步功能实现

在这里插入图片描述

类似的迭代器分析我们在 list 部分有做过解析,确实大体上是相像的,但是结构并不一样,这里的树形结构需要以中序遍历:左-根-右的方式遍历

template<class T>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T> Self;Node* _node;__TreeIterator(Node* node):_node(node){}
};

库里的迭代器模式并不能满足我们的设计需要,所以这里自己构建一个 __TreeIterator

2.4.1 ++运算符重载

Self& operator++()
{if (_node->_right){// 右树的最左节点(最小节点)Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{Node* cur = _node;Node* parent = cur->_parent;// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点while (parent){if (cur == parent->_left){break;}else{cur = cur->_parent;parent = parent->_parent;}}_node = parent;}return *this;
}

中序遍历的方式是 左-根-右,因此可以总结为两种情况来遍历:

  • 当前节点有右子树

处理方式: 找到右子树的最左节点(即右子树中的最小值)

原因: 在中序遍历中,当前节点的下一个节点是其右子树的最左节点

  • 当前节点没有右子树

处理方式: 向上回溯,直到找到某个祖先节点,使得当前节点位于该祖先的左子树中

原因: 在中序遍历中,若无右子树,则下一个节点是第一个满足 “当前节点是其左子节点” 的祖先

🔥值得注意的是: 当前节点没有右子树的情况,是 左-根-右 的最后一步,无论是在根的左边还是右边,最终都会回到根节点,所以直接 _node = parent 即可

2.4.2 --运算符重载

Self& operator--()
{if (_node->_left){Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 孩子是父亲的右的那个节点Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;
}

operator-- 的思路和 operator++ 是一样的,反过来遍历就行了

2.4.3 *运算符重载

T& operator*()
{return _node->_data;
}

2.4.4 ->运算符重载

T* operator->()
{return &_node->_data;
}

这里再提醒一下重载 -> 是因为用 * 的代码不够简洁,具体分析参考 list 部分的解析

传送门:C++效率掌握之STL库:list底层剖析及迭代器万字详解

2.4.5 !=运算符重载

bool operator!=(const Self& s) const
{return _node != s._node;
}

_node:当前迭代器指向的节点
s._node:另一个迭代器(作为参数传入)指向的节点

2.4.6 begin()

//RBTree.h
iterator begin()
{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return iterator(leftMin);
}//Set.h Map.h
iterator begin()
{return _t.begin();
}

2.4.7 end()

//RBTree.h
iterator end()
{return iterator(nullptr);
}//Set.h Map.h
iterator end()
{return _t.end();
}

现在已经可以基本实现遍历的功能了

2.5 迭代器进阶功能实现

2.5.1 set:const迭代器及insert的实现

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;const_iterator begin() const
{return _t.begin();
}const_iterator end() const
{return _t.end();
}

由于 set 规定 key 是不可以被修改的,因此 iteratorconst_iterator 本质上其实都是const_iterator

🔥值得注意的是: begin()end()const 迭代器函数被 const 修饰是为了满足常量容器对象或非常量容器对象都能调用


insert 的错误代码:

pair<iterator, bool> insert(const K& key)
{return _t.Insert(key);
}

这里是返回红黑树的插入,红黑树的插入详见下面的代码展示

从之前的学习我们知道 insert 返回的是 pair<iterator, bool>,那么是不是直接返回insert的结果就好了呢?看似确实是没问题,但是这里理了个巨大的坑,我们实际分析一波:

  • _t.Insert(key) 返回的是 RBTree::iterator,是一个普通迭代器
  • pair<iterator, bool> insert(const K& key) 返回的是 set::iterator,是一个 const 迭代器

insert 的正确代码:

// iterator RBTree::const_iterator
pair<iterator, bool> insert(const K& key)
{// pair<RBTree::iterator, bool>pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);
}

正确的做法是先将 insert 返回的普通迭代器由变量 ret 存储,然后再用一个匿名对象进行构造,将 ret 的普通迭代器构造成 const 迭代器返回即可,下面将进行详细的构造原理解释:

在这里插入图片描述

回看官方文档发现 iteratorconst_iterator 都是被单独拿出来实例化的,并没有受到 RefPtr 的影响,那么此时就分为两种情况:

  • 普通迭代器的拷贝构造

__rb_tree_iterator 是普通迭代器时,iterator 就是自身类型,此时构造函数等价于:

__rb_tree_iterator(const __rb_tree_iterator<Value, Value&, Value*>& it): node(it.node) {}

这是一个标准的拷贝构造函数,用于创建一个新的普通迭代器,指向相同的节点

  • const迭代器的构造

__rb_tree_iteratorconst 迭代器时, iterator 指的是普通迭代器类型,此时构造函数等价于:

__rb_tree_iterator(const __rb_tree_iterator<Value, Value&, Value*>& it): node(it.node) {}

这变成了一个构造函数,允许从普通迭代器创建 const 迭代器

所以可以理解为单独拿出来实例化是为了不让 RefPtr 影响参数,而外面的类型就会受 RefPtr 影响,这样就能保证外面的类型是 const 迭代器,里面的参数是普通迭代器,成功构造出一个支持普通迭代器构造 const 迭代器的构造函数

在这里插入图片描述

那再转到实际代码上,ret.first 的类型是 typename RBTree<K, K, SetKeyOfT>::iterator ,返回值 pair 的第一个元素类型是 set 类中定义的 iterator,实际上是 typename RBTree<K, K, SetKeyOfT>::const_iterator

ret.first 会调用自定义的迭代器类型的构造函数 __TreeIterator(const Iterator& it) 进行单参数转换,变成 const_iterator

2.5.2 map:const迭代器及insert、[ ]运算符重载的实现

typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin()
{return _t.begin();
}iterator end()
{return _t.end();
}const_iterator begin() const
{return _t.begin();
}const_iterator end() const
{return _t.end();
}

对于 map 来说,key 是不允许改变的,value 是可以改变的,但是如果像 set 那样写的话 keyvalue 都不能修改了,所以直接在 pairkeyconst ,控制 value 即可


insert 代码:

pair<iterator, bool> insert(const pair<K, V>& kv)
{return _t.Insert(kv);
}

map 就没有像 set 那么麻烦了,红黑树和 `map 的迭代器是一致的


[ ]运算符重载 代码:

V& operator[](const K& key)
{pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;
}

之前详细解释过,可以看之前的博客

传送门:C++漫溯键值的长河:map && set

3.代码展示

🚩MySet.h

#pragma once
#include"RBTree.h"namespace bit
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}// iterator RBTree::const_iteratorpair<iterator, bool> insert(const K& key){// pair<RBTree::iterator, bool>pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);}private:RBTree<K, K, SetKeyOfT> _t;};
}

🚩MyMap.h

#pragma once
#include"RBTree.h"namespace bit
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};
}

🚩RBTree.h

#pragma once
#include<iostream>
using namespace std;enum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};template<class T, class Ptr, class Ref>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T, Ptr, Ref> Self;typedef __TreeIterator<T, T*, T&> Iterator;__TreeIterator(const Iterator& it):_node(it._node){}Node* _node;__TreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node != s._node;}Self& operator--(){if (_node->_left){Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 孩子是父亲的右的那个节点Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator++(){if (_node->_right){// 右树的最左节点(最小节点)Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{Node* cur = _node;Node* parent = cur->_parent;// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点while (parent && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}
};// set->RBTree<K, K, SetKeyOfT> _t;
// map->RBTree<K, pair<K, V>, MapKeyOfT> _t;
template<class K, class T, class KeyOfT>
struct RBTree
{typedef RBTreeNode<T> Node;
public:// 同一个类模板,传的不同的参数实例化出的不同类型typedef __TreeIterator<T, T*, T&> iterator;typedef __TreeIterator<T, const T*, const T&> const_iterator;iterator begin(){Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return iterator(leftMin);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return const_iterator(leftMin);}const_iterator end() const{return const_iterator(nullptr);}Node* Find(const K& key){Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}cur = new Node(data);cur->_col = RED;Node* newnode = cur;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;// u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else // u不存在 或 存在且为黑{if (cur == parent->_left){//     g//   p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p//		cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else // parent == grandfather->_right{Node* uncle = grandfather->_left;// u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){// g//	  p//       cRotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{// g//	  p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}void RotateL(Node* parent){++_rotateCount;Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void RotateR(Node* parent){++_rotateCount;Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright)curright->_parent = parent;Node* ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}// 17:20继续bool CheckColour(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark)return false;return true;}if (root->_col == BLACK){++blacknum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << "出现连续红色节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark)&& CheckColour(root->_right, blacknum, benchmark);}bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;if (root->_col != BLACK){return false;}// 基准值int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}return CheckColour(root, 0, benchmark);}int Height(){return Height(_root);}int Height(Node* root){if (root == nullptr)return 0;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}private:Node* _root = nullptr;public:int _rotateCount = 0;
};

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

相关文章:

C++效率掌握之STL库:map set底层剖析及迭代器万字详解

文章目录 1.map、set的基本结构2.map、set模拟实现2.1 初步定义2.2 仿函数实现2.3 Find功能实现2.4 迭代器初步功能实现2.4.1 运算符重载2.4.2 --运算符重载2.4.3 *运算符重载2.4.4 ->运算符重载2.4.5 !运算符重载2.4.6 begin()2.4.7 end() 2.5 迭代器进阶功能实现2.5.1 set…...

使用 Docker Desktop 安装 Neo4j 知识图谱

一、简介 Neo4j是一个高性能的&#xff0c;基于java开发的&#xff0c;NOSQL图形数据库&#xff0c;它将结构化数据存储在网络上而不是表中&#xff1b;它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎。 Neo4j分为企业版和社区版&#xff0c;企业版可以创…...

从构想到交付:专业级软开发流程详解

目录 ​​一、软件开发生命周期&#xff08;SDLC&#xff09;标准化流程​​ 1. 需求工程阶段&#xff08;Requirement Engineering&#xff09; 2. 系统设计阶段&#xff08;System Design&#xff09; 3. 开发阶段&#xff08;Implementation&#xff09; 4. 测试阶段&a…...

时源芯微| KY键盘接口静电浪涌防护方案

KY键盘接口静电浪涌防护方案通过集成ESD保护元件、电阻和连接键&#xff0c;形成了一道有效的防护屏障。当键盘接口受到静电放电或其他浪涌冲击时&#xff0c;该方案能够迅速将过电压和过电流引导至地&#xff0c;从而保护后续电路免受损害。 ESD保护元件是方案中的核心部分&a…...

数据库故障排查指南:从理论到实践的深度解析

数据库作为现代信息系统的核心组件&#xff0c;承载着数据存储、查询和事务处理等关键任务。然而&#xff0c;数据库系统在运行过程中可能遭遇各种故障&#xff0c;从硬件故障到软件配置问题&#xff0c;从性能瓶颈到安全漏洞&#xff0c;这些问题都可能影响业务的连续性和数据…...

电脑开机提示按f1原因分析及解决方法(6种解决方法)

经常有网友问到一个问题,我电脑开机后提示按f1怎么解决?不管理是台式电脑,还是笔记本,都有可能会遇到开机需要按F1,才能进入系统的问题,引起这个问题的原因比较多,今天小编在这里给大家列举了比较常见的几种电脑开机提示按f1的解决方法。 电脑开机提示按f1原因分析及解决…...

常用的Java工具库

1. Collections 首先是 java.util 包下的 Collections 类。这个类主要用于操作集合&#xff0c;我个人非常喜欢使用它。以下是一些常用功能&#xff1a; 1.1 排序 在工作中&#xff0c;经常需要对集合进行排序。让我们看看如何使用 Collections 工具实现升序和降序排列&…...

NC65开发环境(eclipse启动)在企业报表中的报表数据中心里计算某张报表时,一直计算不出数据的解决办法。

NC65开发环境&#xff08;eclipse启动&#xff09;在企业报表中的报表数据中心里计算某张报表时&#xff0c;一直计算不出数据的解决办法。 如下图&#xff0c;在报表数据中心&#xff0c;针对现金内部往来明细表计算5月的数据&#xff0c;然后报表下面一张显示计算&#xff0c…...

React 第三十九节 React Router 中的 unstable_usePrompt Hook的详细用法及案例

React Router 中的 unstable_usePrompt 是一个用于在用户尝试离开当前页面时触发确认提示的自定义钩子&#xff0c;常用于防止用户误操作导致数据丢失&#xff08;例如未保存的表单&#xff09;。 一、unstable_usePrompt用途 防止意外离开页面&#xff1a;当用户在当前页面有…...

《P4391 [BalticOI 2009] Radio Transmission 无线传输 题解》

题目描述 给你一个字符串 s1​&#xff0c;它是由某个字符串 s2​ 不断自我连接形成的&#xff08;保证至少重复 2 次&#xff09;。但是字符串 s2​ 是不确定的&#xff0c;现在只想知道它的最短长度是多少。 输入格式 第一行一个整数 L&#xff0c;表示给出字符串的长度。…...

使用ECS搭建云上博客wordpress(ALMP)

一、需求分析与技术选型 1. 架构组成及含义 本文使用ECS云服务器&#xff0c;采用ALMP架构搭建wordpress。组件具体的含义如下表&#xff1a; 组件作用WordPress中的功能体现Linux操作系统基础&#xff0c;提供稳定运行环境支持PHP运行和服务器管理ApacheWeb服务器&#xff…...

Scratch游戏 | 企鹅大乱斗

有没有过无聊到抓狂的时刻&#xff1f;试试这款 企鹅大乱斗 吧&#xff01;超简单的玩法&#xff0c;让你瞬间告别无聊&#xff01; &#x1f3ae; 玩法超简单 等待屏幕出现 ”Go!” 疯狂点击&#xff0c;疯狂拍打企鹅&#xff01; &#x1f4a5; 游戏特色 解压神器&#x…...

深入理解SpringBoot中的SpringCache缓存技术

深入理解SpringBoot中的SpringCache缓存技术 引言 在现代应用开发中&#xff0c;缓存技术是提升系统性能的重要手段之一。SpringBoot提供了SpringCache作为缓存抽象层&#xff0c;简化了缓存的使用和管理。本文将深入探讨SpringCache的核心技术点及其在实际业务中的应用场景。…...

URP相机如何将场景渲染定帧模糊绘制

1&#xff09;URP相机如何将场景渲染定帧模糊绘制 2&#xff09;为什么Virtual Machine会随着游戏时间变大 3&#xff09;出海项目&#xff0c;打包时需要勾选ARMv7吗 4&#xff09;Unity是手动还是自动调用GC.Collect 这是第431篇UWA技术知识分享的推送&#xff0c;精选了UWA社…...

嵌入式中深入理解C语言中的指针:类型、区别及应用

在嵌入式开发中,C语言是一种基础且极为重要的编程语言,其中指针作为一个非常强大且灵活的工具,广泛应用于内存管理、动态数据结构的实现以及函数参数的传递等方面。然而,尽管指针的使用极为常见,很多开发者在掌握其基本使用后,往往对指针的深入理解还不够。本文将深入分析…...

.NET程序启动就报错,如何截获初期化时的问题json

一&#xff1a;背景 1. 讲故事 前几天训练营里的一位朋友在复习课件的时候&#xff0c;程序一跑就报错&#xff0c;截图如下&#xff1a; 从给出的错误信息看大概是因为json格式无效导致的&#xff0c;在早期的训练营里曾经也有一例这样的报错&#xff0c;最后定位下来是公司…...

WeakAuras Lua Script ICC (BarneyICC)

WeakAuras Lua Script ICC &#xff08;BarneyICC&#xff09; https://wago.io/BarneyICC/69 全量英文字符串&#xff1a; !WA:2!S33c4TXX5bQv0kobjnnMowYw2YAnDKmPnjnb4ljzl7sqcscl(YaG6HvCbxaSG7AcU76Dxis6uLlHNBIAtBtRCVM00Rnj8Y1M426ZH9XDxstsRDR)UMVCTt0DTzVhTjNASIDAU…...

Sunsetting 创建 React App

&#x1f916; 作者简介&#xff1a;水煮白菜王&#xff0c;一位前端劝退师 &#x1f47b; &#x1f440; 文章专栏&#xff1a; 前端专栏 &#xff0c;记录一下平时在博客写作中&#xff0c;总结出的一些开发技巧和知识归纳总结✍。 感谢支持&#x1f495;&#x1f495;&#…...

Python笔记:c++内嵌python,c++主窗口如何传递给脚本中的QDialog,使用的是pybind11

1. 问题描述 用的是python 3.8.20, qt版本使用的是5.15.2, PySide的版本是5.15.2, pybind11的版本为2.13.6 网上说在python脚本中直接用PySide2自带的QWinWidget&#xff0c;如from PySide2.QtWinExtras import QWinWidget&#xff0c;但我用的版本中说没有QWinWidget&#x…...

环境配置与MySQL简介

目录 1 环境配置 2 MySQL简介 1 环境配置 本专栏使用CentOS7进行讲解。首先我们查看系统中是否已经安装了MySQL&#xff0c;可以使用rpm -qa 命令查看系统安装包/压缩包 列表 这只是看我们是否下载过对应安装包&#xff0c;不一定就安装了。如果我们需要重新下载&#xff0c;…...

Unity3D游戏内存管理优化指南

前言 Unity3D 的内存管理机制较为复杂&#xff0c;开发者需要理解其内存分布以避免内存泄漏和性能问题。以下是 Unity3D 游戏内存分布的核心概览&#xff0c;结合托管堆、本地堆、资源内存等关键模块&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&#xff0c;大家…...

深度解析 Sora:从技术原理到多场景实战的 AI 视频生成指南【附学习资料包下载】

一、技术架构与核心能力解析 1.1 时空建模体系的创新突破 Sora 在视频生成领域的核心优势源于其独特的时空建模架构。区别于传统将视频拆解为单帧处理的模式,Sora 采用时空 Patch 嵌入技术,将连续视频序列分割为 32x32 像素的时空块(每个块包含相邻 3 帧画面),通过线性投…...

Maven构建流程详解:如何正确管理微服务间的依赖关系-当依赖的模块更新后,我应该如何重新构建主项目

文章目录 一、前言二、Maven 常用命令一览三、典型场景说明四、正确的构建顺序正确做法是&#xff1a; 五、为什么不能只在 A 里执行 clean install&#xff1f;六、进阶推荐&#xff1a;使用多模块项目&#xff08;Multi-module Project&#xff09;七、总结 一、前言 在现代…...

zookeeper本地部署

下载源码本地运行 zookeeper下载地址 更改配置 运行命令 如果本地启动zookeeper时出现了端口被占用的情况&#xff0c;在 conf 下的 zoo.cfg 文件中加入 admin.serverPort“端口号”...

精益数据分析(59/126):移情阶段的深度博弈——如何避开客户访谈的认知陷阱

精益数据分析&#xff08;59/126&#xff09;&#xff1a;移情阶段的深度博弈——如何避开客户访谈的认知陷阱 在创业的移情阶段&#xff0c;客户访谈是挖掘真实需求的核心手段&#xff0c;但人类认知偏差往往导致数据失真。今天&#xff0c;我们结合《精益数据分析》的方法论…...

一文理解扩散模型(生成式AI模型)(2)

第二期内容主要是扩散模型的架构&#xff0c;其中包括用于扩散模型的U-Net架构和用于扩散模型的transformer架构。(transformer架构非常重要) 扩散模型需要训练一个神经网络来学习加噪数据的分数函数&#xff0c;或者学习加在数据上的噪声(这对应上文所展示的扩散模型的两种训…...

【Java面试题】——this 和 super 的区别

&#x1f381;个人主页&#xff1a;User_芊芊君子 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 &#x1f50d;系列专栏&#xff1a;【Java】内容概括 【前言】 在Java的世界里&#xff0c;this和 super是两个非常重要且容易混淆的关键字。无论是在日常…...

数据结构基础排序算法

选择排序 选择排序的基本思路&#xff1a;从待排序元素中选取最大&#xff08;或最小&#xff09;的一个元素加入到已完成排序的末尾。 #include <stdio.h>#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0])) #define SWAP(arr, i, j ) { \ int tmp arr[i]; …...

数据结构中的高级排序算法

希尔排序 你可以将希尔排序理解成——先通过几次分组的、较小的组间插入排序将原数组变得有序&#xff0c;最后再进行一次序列基本有序的完整插入排序。 #include <stdio.h>#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0]))void print_arr(int arr[], int len) {for…...

家庭宽带的内网穿透实践

家庭宽带的内网穿透实践 龙生龙&#xff0c;凤生凤&#xff0c;老鼠的儿子会打洞。我们今天来学习 “打洞” &#xff01; 背景 众所周知&#xff0c;当前运营商在IPv4环境下面&#xff0c;由于地址资源不够&#xff0c;启用了大内网策略。导致家庭宽带到路由器这一层都分配了…...

LabVIEW在电子电工教学中的应用

在电子电工教学领域&#xff0c;传统教学模式面临诸多挑战&#xff0c;如实验设备数量有限、实验过程存在安全隐患、教学内容更新滞后等。LabVIEW 作为一款功能强大的图形化编程软件&#xff0c;为解决这些问题提供了创新思路&#xff0c;在电子电工教学的多个关键环节发挥着重…...

算法每日刷题 Day6 5.14:leetcode数组1道题,用时30min,明天按灵茶山艾府题单开刷,感觉数组不应该单算

14. 977.有序数组的平方(简单&#xff0c;学习&#xff0c;双指针) 977. 有序数组的平方 - 力扣&#xff08;LeetCode&#xff09; 思想 法一: 1.平方赋值到另一个数组sort排序 法二: 1.寻找负数和非负数的分界线(学习代码如何写&#xff1f;)&#xff0c;[0,neg]负数,[neg1…...

JS逆向实战四:某查查请求头逆向解密

声明&#xff1a;本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;…...

QT之QComboBox组件

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 1.引言2.初见QComboBox3.核心功能和常用方法1. 添加和删除选项2. 获取和设置当前值3. 可编辑模式4. 数据绑定 4.信号与槽5.应用场景6.使用示例7.总结 1.引言 在记事本项目中&#xff0c;不同的编码设…...

数值积分知识

数值积分 对于增加插值节点序列&#xff1a; { x i } i 0 n \left\{x_i\right\}_{i0}^{n} {xi​}i0n​&#xff0c;由插值定理给出&#xff1a; f ( x ) ∑ i 0 n y i l i ( x ) f ( n 1 ) ( ξ ) ( n 1 ) ! ∏ i 0 n ( x − x i ) f(x)\sum_{i0}^{n}y_i l_i(x)\frac{f…...

代码随想录训练营第二十三天| 572.另一颗树的子树 104.二叉树的最大深度 559.N叉树的最大深度 111.二叉树的最小深度

572.另一颗树的子树&#xff1a; 状态&#xff1a;已做出 思路&#xff1a; 这道题目当时第一时间不是想到利用100.相同的树思路来解决&#xff0c;而是先想到了使用kmp&#xff0c;不过这个题目官方题解确实是有kmp解法的&#xff0c;我使用的暴力解法&#xff0c;kmp的大致思…...

力扣-105.从前序与中序遍历序列构造二叉树

题目描述 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 class Solution { public:TreeNode* buildTree(vector<int>& preorder, vecto…...

【Linux网络】————详解TCP三次握手四次挥手

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;Linux 创作时间 &#xff1a;2025年5月14日 一、TCP三次握手四次挥手介绍 TCP使用三次握手来进行建立连接&#xff0c;四次挥手来终止连接&#xff0c;为何连接还要这么麻烦呢&#xff0c;那是因为这样可以确保建立…...

LLM(大语言模型)部署加速方法——PagedAttention

一、vLLM 用于大模型并行推理加速 存在什么问题&#xff1f; vLLM 用于大模型并行推理加速&#xff0c;其中核心改进是PagedAttention算法&#xff0c;在 vLLM 中&#xff0c;我们发现 LLM 服务的性能受到内存的瓶颈。在自回归解码过程中&#xff0c;LLM 的所有输入标记都会生…...

附加:TCP如何保障数据传输

附加&#xff1a;TCP如何保障数据传输 LS-NET-012-TCP的交互过程详解 TCP 如何保障数据传输 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是互联网核心协议之一&#xff0c;负责在IP网络上提供可靠的、面向连接的数据传输服务。它位于T…...

【python机器学习】Day 25 异常处理

知识点&#xff1a; 异常处理机制debug过程中的各类报错try-except机制try-except-else-finally机制 在即将进入深度学习专题学习前&#xff0c;我们最后差缺补漏&#xff0c;把一些常见且重要的知识点给他们补上&#xff0c;加深对代码和流程的理解。 借助ai写代码的时候&…...

idea springboot 配置文件 中文显示

这里一定要注意编码。如果使用的是中文&#xff0c;则有可能出现乱码&#xff0c; 请单击IDEA菜单栏中的“File→→Settings→Editor→File Encodings”命令&#xff0c; 然后将 Properties Files(*.properties)下的“Default encoding for properties files"设置为UTF-8,…...

day20-线性表(链表II)

一、调试器 1.1 gdb&#xff08;调试器&#xff09; 在程序指定位置停顿 1.1.1 一般调试 gcc直接编译生成的是发布版&#xff08;Release&#xff09; gcc -g //-g调式版本&#xff0c;&#xff08;体积大&#xff0c;内部有源码&#xff09;&#xff08;DeBug&#…...

深入剖析某App视频详情逆向:聚焦sig3参数攻克

深入剖析某手App视频详情逆向&#xff1a;聚焦sig3参数攻克 一、引言 在当今互联网信息爆炸的时代&#xff0c;短视频平台如某手&#xff0c;已成为人们获取信息、娱乐消遣的重要渠道。对于技术爱好者和研究人员而言&#xff0c;深入探索其内部机制&#xff0c;特别是视频详情…...

数据结构与算法-双向链表专题

目录 一. 双向链表的结构 二.双向链表的使用 2.1 创建节点 2.2 初始化 2.3 打印 2.4 尾插 2.5 头插 2.6 尾删 2.7 头删 2.8 在指定位置pos之后插入数据 2.9 查找数据 2.10 删除pos位置的节点 2.11 销毁链表 一. 双向链表的结构 在List.h的头文件中对链表的结构进行创建 #prag…...

为什么要选择七彩喜数字康养平台?加盟后有何优势?

一&#xff0e;七彩喜数字康养平台 1.技术领先性 七彩喜依托“端-网-云-脑”四层技术架构&#xff0c;整合毫米波雷达、AI算法引擎、区块链等前沿技术&#xff0c;解决传统养老的隐私泄露、设备孤岛等痛点。 比如非接触式健康监测系统通过毫米波雷达实现跌倒检测准确率&#…...

vscode调试c/c++

1. 调试配置选择 调试 C 程序&#xff1a;选择 "Debug C Program"&#xff08;调用 gcc 编译&#xff09;。 调试 C 程序&#xff1a;选择 "Debug C Program"&#xff08;调用 g 编译&#xff09;。 2. 调试步骤 打开代码文件&#xff1a;确保当前编辑器…...

进阶数据结构: AVL树

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…...

C# 调试技巧——日志记录,NuGet内断点

在C#中&#xff0c;Debug.WriteLine()、Trace.WriteLine() 和 Console.WriteLine() 都用于输出信息&#xff0c;但它们的用途和适用场景有显著区别。以下是它们的核心差异总结&#xff1a; Debug.WriteLine()主要适用于控制台程序&#xff0c;输出到控制台Trace.WriteLine() …...

模糊数学方法之模糊贴近度

模糊数学方法之模糊贴近度 一、概述 二、代码实现&#xff08;内含注释&#xff09; #程序文件ex14_3.py # 本段带代码主要是用于判断b是属于a中的哪个种类的 # 通过计算贴近度的形式来实现的 import numpy as np a np.array([[0.4,0.3,0.5,0.3],[0.3,0.3,0.4,0.4],[0.2,0.3…...