秒懂C++之set、map的封装

news/2024/9/18 12:05:32/ 标签: c++, 开发语言

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

 

红黑树的泛型编程

改变比较方式:仿函数

迭代器模拟实现

++运算符重载

begin/end

!=/==运算符重载

测试

const

Find

[ ] 运算符重载

全部代码

RBTree.h

Mymap.h

Myset.h

test.cpp


红黑树的泛型编程

既然我们要实现set,map的封装那肯定要用到我们前面所学的红黑树为底层~

不过set是k模型,map是kv模型,难道我们要专门写两份不同模型的红黑树供它们使用吗?

让我们来看看在源码中它们是如何解决这一问题的~

在set源码中底层红黑树也设置了两个参数~不过这两个参数其实都是Key没啥区别

而map源码中底层红黑树第一个参数为key,第二个参数为pair,这就有点奇怪了,红黑树设置kv模型第一个参数为pair就行了,为什么这里有两个参数?

这其实是一种泛型编程的写法,目的就是减少重复冗余代码。 首先红黑树第一个参数统一为key,第二个参数是根据set与map传递的参数而变化~

如果set使用红黑树,那红黑树就是<k,k>模型,value值就是key~

如果是map使用红黑树,那么红黑树就是<k,pair<k,v>>模型,value值就是pair<k,v>~

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){}
};// set<k>->RBTree<K, K>
// map<k,pair<k,v>>->RBTree<K, pair<K, V>>
template<class K, class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:private:Node* _root = nullptr;
};
#pragma once
//Myset.h
#include"RBTree.h"namespace lj
{template<class K>class set{private:RBTree<K, K> _t;};
}
#pragma once
//Mymap.h
#include"RBTree.h"namespace lj
{template<class K,class V>class map{private:RBTree<K,  pair<k,v>> _t;};
}

改变比较方式:仿函数

 以下是源代码中pair的比较方式~但我们不想实现value的比较,我们只想实现key的比较~

其实我们也不用特意去修改运算符>,<,我们修改获取_data数据的方式就行了~

pair小于如果是比较key与value的话,那么我们在获取data的时候只让它拿到key就好了~

定义一个内部类,作用为set参数的data只能获取key,map参数的data只能获取key~

迭代器模拟实现

++运算符重载

当右子树为空时,it++有指向其父节点的,也有指向其爷爷节点的,只能说最终会指向其祖先节点的某一个~那我们要如何表示这个呢?

当右子树为空作为条件时,我们先看向节点15,作为其父节点的左子树访问完后就应该访问父节点了~应该二叉搜索树为中序遍历——左子树,根,右子树

我们再来看向节点6,作为其父节点1的右子树,当把节点6访问完也就意味着包括节点1在内的其所有子树都访问完了,即节点8的左子树访问完了,下一步就该访问节点8~

所以当其右子树为空时,我们就去找哪个祖先节点的孩子节点为左节点,就比如节点15,它是祖先节点的左节点吗?——是的,那么我们的下一个节点就是其父亲。

再看节点6,它是目前父节点的左孩子吗?——不是,那么继续向上遍历,节点1是其父节点8的左孩子吗?——是的,那么我们的下一个节点就是节点1的父亲~

template<class T>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T> Self;Node* _node;RBTreeIterator(Node* node):_node(node){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}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 = parent;parent = cur->_parent;}//找到了,把父亲节点作为下一个节点_node = parent;}return *this;}
};

关于封装迭代器(使得指针++自定义)不熟悉的友友可以去看这篇文章:秒懂C++之List-CSDN博客

begin/end

iterator begin(){// 求最左节点Node* subLeft = _root;//若为空树则返回空节点while (subLeft&&subLeft->_left){subLeft = subLeft->_left;}return iterator(subLeft);}iterator end(){return iterator(nullptr);}

!=/==运算符重载

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

测试

当我们部署好迭代器后,开始来检验一下成果~

const

对const迭代器不了解的友友可以参考这篇文章:秒懂C++之List-CSDN博客

前我们还面临一个问题,key是不支持修改的~而我们现在只能通过降低传参权限去限制~

其实我们也可以封装一个const类型的迭代器~

不过const迭代器了解即可,毕竟正常迭代器就够用了~

ps:map一般不需要const迭代器,因为它需要修改pair里面的value,而set只有key,所以不能被修改~

Find

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

这就是为什么我们红黑树第一个参数统一设置成key的意义,我们用Find函数单纯就是寻找key,如果用第二个参数的话从data里面还得挑出key反而麻烦~

当然啦,如果你就是想在第二个参数的data找也可以~ 

不影响~

[ ] 运算符重载

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

要实现[ ] 运算符重载就必须得对insert的返回值进行修改~

全部代码

RBTree.h

//RBTree.h
#pragma once
#include<vector>
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 RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ptr,Ref> Self;Node* _node;RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}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 = parent;parent = cur->_parent;}//找到了,把父亲节点作为下一个节点_node = parent;}return *this;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};// set->RBTree<K, K>
// map->RBTree<K, pair<K, V>>
template<class K, class T, class whok>
class RBTree
{typedef RBTreeNode<T> Node;public:typedef RBTreeIterator<T, T*, T&> iterator;typedef RBTreeIterator<T, const T*, const T&> const_iterator;const_iterator begin() const{// 求最左节点Node* subLeft = _root;//若为空树则返回空节点while (subLeft && subLeft->_left){subLeft = subLeft->_left;}return const_iterator(subLeft);}iterator begin(){// 求最左节点Node* subLeft = _root;//若为空树则返回空节点while (subLeft&&subLeft->_left){subLeft = subLeft->_left;}return iterator(subLeft);}const_iterator end() const{return const_iterator(nullptr);}iterator end(){return iterator(nullptr);}iterator Find(const T& data){whok kot;Node* cur = _root;while (cur){if (kot(cur->_data) < kot(data)){cur = cur->_right;}else if (kot(cur->_data) > kot(data)){cur = cur->_left;}else{return iterator(cur);}}return end();}pair<iterator, bool>  Insert(const T& data){if (_root == nullptr){_root = new Node(data);//根节点为黑色_root->_col = BLACK;//return true;return make_pair(iterator(_root), true);}whok kot;Node* parent = nullptr;Node* cur = _root;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 false;return make_pair(iterator(cur), false);}}// 准备插入,节点为红色cur = new Node(data);//标记最开始准备插入的cur,避免cur在向上遍历时变色移动,这时候的迭代器虽然还是叫cur,但已经不是原来的节点了Node* newnode = cur;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//若插入成功后其父节点为黑,那么并不会触发连续红色,不需要修改//若插入成功后其父节点为红,触发连续红色,需要修改while (parent && parent->_col == RED){//记录g节点Node* grandfather = parent->_parent;//记录u节点if (grandfather->_left == parent){Node* uncle = grandfather->_right;//开始分类情况//情况一:u节点存在且为红if (uncle && uncle->_col == RED){//p/g/u变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//把g当作cur继续处理cur = grandfather;parent = cur->_parent;}//情况二:u节点不存在或u节点存在且为黑else{//情况二需要按照旋转情况进行分类//右旋情况,以g为旋转点if (cur == parent->_left){//       g//    p     u// c //右旋RotateR(grandfather);//p/g节点变色parent->_col = BLACK;grandfather->_col = RED;}//左右旋,p为旋转点后以g为旋转点else if (cur == parent->_right){//       g//    p     u//		 c //左旋RotateL(parent);//右旋RotateR(grandfather);//cur/g节点变色cur->_col = BLACK;grandfather->_col = RED;}//旋转处理完毕,不用再向上处理,直接退出break;}}else{Node* uncle = grandfather->_left;//开始分类情况//情况一:u节点存在且为红if (uncle && uncle->_col == RED){//p/g/u变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//把g当作cur继续处理cur = grandfather;parent = cur->_parent;}//情况二:u节点不存在或u节点存在且为黑else{//情况二需要按照旋转情况进行分类//左旋情况,以g为旋转点if (cur == parent->_right){//       g//    u     p//			   c//左旋RotateL(grandfather);//p/g节点变色parent->_col = BLACK;grandfather->_col = RED;}//右左旋,p为旋转点后以g为旋转点else if (cur == parent->_left){//       g//    u     p//		 c //右旋RotateR(parent);//左旋RotateL(grandfather);//cur/g节点变色cur->_col = BLACK;grandfather->_col = RED;}//旋转处理完毕,不用再向上处理,直接退出break;}}}//插入调整结束后我们统一让根节点变为黑色_root->_col = BLACK;//插入成功~//return true;//把最开始指向插入节点的迭代器返回return make_pair(iterator(newnode), true);}void RotateL(Node* parent){//先完成左单旋基本规则//移动三个主要节点Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;//再更新_parentif (subRL){//为空就不用给了subRL->_parent = parent;}Node* ppnode = parent->_parent;parent->_parent = subR;//若subR左旋后成为根节点if (parent == _root){_root = subR;subR->_parent = nullptr;}//若subR左旋后不会成为根节点else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){//先完成右单旋基本规则//移动三个主要节点Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;//再更新_parentif (subLR){//为空就不用给了subLR->_parent = parent;}Node* ppnode = parent->_parent;parent->_parent = subL;//若subL右旋后成为根节点if (parent == _root){_root = subL;subL->_parent = nullptr;}//若subL右旋后不会成为根节点else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << endl;_InOrder(root->_right);}void InOrder(){_InOrder(_root);}bool Check(Node* cur,int RedBlackNum,int BlackNum){if (cur == nullptr){if (RedBlackNum != BlackNum){cout << "黑色节点不足" << endl;return false;}cout << BlackNum << endl;return true;}//检查是否存在连续的红色节点if (cur->_col == RED && cur->_parent->_col == RED){cout << cur->_kv.first << "存在连续的红色节点" << endl;return false;}if (cur->_col == BLACK){BlackNum++;}return Check(cur->_left,RedBlackNum, BlackNum)&& Check(cur->_right, RedBlackNum, BlackNum);}bool IsBalance(){	//检查根节点是否为黑色if (_root && _root->_col == RED)return false;//检查路径黑节点int RedBlackNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){RedBlackNum++;}cur = cur->_left;}return Check(_root, RedBlackNum, 0);}private:Node* _root = nullptr;
};

Mymap.h

#pragma once
//Mymap.h
#include"RBTree.h"namespace lj
{template<class K,class V>class map{struct mapk{const K& operator()(const pair<K, V>& kv){//封装一个只获取pair中key值的内部类return kv.first;}};public:typedef typename RBTree< K, pair<const K, V>, mapk>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, mapk>::const_iterator const_iterator;const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}iterator begin(){return _t.begin();}iterator end(){return _t.end();}iterator find(const K& key){return _t.Find(key);}/*bool insert(const pair<K, V>& kv){return _t.Insert(kv);}*/pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}private://使用底层红黑树封装时:RBTree<K,  pair<const K,V>, mapk> _t;};void maptest1(){map<int, int> m;int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){m.insert(make_pair(e, e));}map<int, int>::iterator it = m.begin();while (it != m.end()){cout << it->first << " "<< it->second<<endl;++it;}cout << endl;}void maptest2(){set< int> s;int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){s.insert(e);}set<int>::iterator it = s.begin();while (it != s.end()){//*it += 100;if (it == s.find(1)){cout << "找到了" << endl;cout << *it << endl;break;}else{cout << "找不到" << endl;break;}//cout << *it << endl;++it;}cout << endl;}void maptest3(){string arr[] = { "哈哈", "嘻嘻", "嘿嘿", "呃呃", "呃呃", "嘻嘻","嘿嘿", "嘿嘿", "哈哈", "嘿嘿", "呃呃", "哈哈" };map<string, int> countMap;for (auto& e : arr){/*if (e == "ݮ"){int i = 0;}*/countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
}

Myset.h

#pragma once
//Myset.h
#include"RBTree.h"namespace lj
{template<class K>class set{struct setk{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K,const K, setk>::iterator iterator;typedef typename RBTree<K, const K, setk>::const_iterator const_iterator;iterator begin(){return _t.begin();}const_iterator begin() const{return _t.begin();}iterator end(){return _t.end();}const_iterator end() const{return _t.end();}iterator find(const K& key){return _t.Find(key);}/*bool insert(const K& key){return _t.Insert(key);}*/pair<iterator, bool> insert(const K& key){return _t.Insert(key);}private://使用底层红黑树封装时:RBTree<K, const K, setk> _t;};void settest1(){set< int> s;int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){s.insert(e);}set<int>::iterator it = s.begin();while (it != s.end()){//*it += 100;if (it == s.find(11)){cout << "找到了"<<endl;cout << *it << endl;break;}else{cout << "找不到" << endl;break;}//cout << *it << endl;++it;}cout << endl;}
}

test.cpp

//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include <map>
#include <set>#include "MySet.h"
#include "Mymap.h "int main()
{//lj::maptest1();//lj::maptest2();lj::maptest3();//lj::settest1();return 0;
}


http://www.ppmy.cn/news/1515230.html

相关文章

【论文阅读】DaST: Data-free Substitute Training for Adversarial Attacks(2020)

摘要 Machine learning models&#xff08;机器学习模型&#xff09; are vulnerable&#xff08;容易受到&#xff09; to adversarial examples&#xff08;对抗样本&#xff09;. For the black-box setting&#xff08;对于黑盒设置&#xff09;, current substitute atta…

git远程仓库关联切换迁移

上一篇有关git基础设置文章地址&#xff1a; https://blog.csdn.net/lareinax5/article/details/124271339 关于两个仓库迁移&#xff0c;使用&#xff1a; 一、本地仓库文件关联远程仓库 1.先进入你要上传的文件夹&#xff1a; cd /path/to/your/folder2.初始化仓库、查看…

【动态规划算法题记录】最长/最大 问题汇总 (leetcode)

目录 32. 最长有效括号思路代码 300. 最长递增子序列思路代码 674. 最长连续递增序列思路1&#xff1a;双指针代码1&#xff1a;双指针思路2&#xff1a;dp代码2&#xff1a;dp 718. 最长重复子数组思路1&#xff1a;dp代码1&#xff1a;dp思路2&#xff1a;dp优化代码2&#x…

Git 的基本使用

1.创建 Git 本地仓库 仓库是进⾏版本控制的⼀个⽂件⽬录。我们要想对⽂件进⾏版本控制&#xff0c;就必须先创建⼀个仓库出来&#xff0c;例如下面代码创建了gitcode_linux的文件夹&#xff0c;之后再对其进行初始化。创建⼀个 Git 本地仓库对应的命令为 git init &#xff0c…

23 注意力机制—BERT

目录 BERT 预训练NLP 里的迁移学习BERTBERT动机BERT 预训练 NLP 里的迁移学习 在计算机视觉中比较流行,将 ImageNet 或者更大的数据集上预训练好的模型应用到其他任务中,比如小数据的预测、图片分类或者是目标检测 使用预训练好的模型(例如 word2vec 或语言模型)来抽取词、…

4. Java中的ArrayList和LinkedList有什么区别?各自的优缺点是什么?在什么场景下选择使用它们?

​​​​ArrayList 和 LinkedList 是 Java 中两个常用的 List 接口实现类&#xff0c;它们都可以用来存储有序的元素&#xff0c;但在底层实现和性能特征上有显著的区别。 1. 内部实现 ArrayList&#xff1a; ArrayList 底层基于动态数组实现。它维护一个可调整大小的数组&…

opencv处理图片(实战指南)

一、OpenCV简介 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它拥有众多的图像处理和计算机视觉功能&#xff0c;包括各种常用的图像处理技术&#xff0c;如滤波、边缘检测、特征提取等。OpenCV支持多种编程语…

LLM 压缩之二: ShortGPT

0. 资源链接 论文: https://arxiv.org/pdf/2403.03853 项目代码: 待开源 1. 背景动机 现有的大语言模型 LLM 推理存在以下问题&#xff1a; LLM 模型因为 scale law 极大的提高模型的预测能力&#xff0c;但是同样带来较大的推理延时&#xff1b;对于 LLM 应用部署带来较大…

[数据集][目标检测]电力场景输电线杆塔塔架金属锈蚀腐蚀生锈检测数据集VOC+YOLO格式1344张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1344 标注数量(xml文件个数)&#xff1a;1344 标注数量(txt文件个数)&#xff1a;1344 标注…

【Docker】如何让子用户直接使用docker命令

本文首发于 ❄️慕雪的寒舍 通过用户组&#xff0c;让子用户可以直接使用docker命令。 说明 正常情况下&#xff0c;使用一个子用户来执行docker命令&#xff0c;会出现如下提示 $ docker ps permission denied while trying to connect to the Docker daemon socket at uni…

四十一、【人工智能】【机器学习】- Bayesian Logistic Regression算法模型

系列文章目录 第一章 【机器学习】初识机器学习 第二章 【机器学习】【监督学习】- 逻辑回归算法 (Logistic Regression) 第三章 【机器学习】【监督学习】- 支持向量机 (SVM) 第四章【机器学习】【监督学习】- K-近邻算法 (K-NN) 第五章【机器学习】【监督学习】- 决策树…

海康VisionMaster使用学习笔记11-VisionMaster基本操作

VisionMaster基本操作 VM示例方案 1. 工具拖拽及使用方式 分别从采集和定位栏里拖拽图像源,快速匹配,Blob分析工具 2. 模块连线 依次连线 3.如何订阅 点击快速匹配,可以看到输入源已订阅了图像1的图像,Blob分析类似 4. 方案操作及全局触发 点击快速匹配,创建特征模版,框选…

【2025校招】4399 NLP算法工程师笔试题

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/19 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;2h 本试卷分为单选&#xff0c;自我评价题&#xff0c;编程题 单选和自我评价这里不再介绍&#xff0c;4399的编程题一如既往地抽象&#xff…

Android - lock/unlock bootloader

在执行 adb remount 时高版本经常会提示失败 此时就需要对设备的进行解锁操作。记录两个部分&#xff0c;Google解锁和展锐解锁。 目录 一、Google解锁 二、展锐解锁 三、补充跳过按键检测的方案 一、Google解锁 官网介绍的unlock方法如下&#xff1a;锁定/解锁引导加载程序…

[数据集][目标检测]红外场景下车辆和行人检测数据集VOC+YOLO格式19069张4类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;19069 标注数量(xml文件个数)&#xff1a;19069 标注数量(txt文件个数)&#xff1a;19069 标…

dm 到 dm 的 HS 同步部署

一、数据库部署 完成两节点数据库部署并初始化&#xff0c;配置参数如下&#xff1a; 节点 192.168.2.132 192.168.2.133 数据库版本 DM8 DM8 实例名 DM1 DM2 端口号 5236 5236 数据文件路径 /home/dmdba/dmdata /home/dmdba/dmdata 二、 dmhs 部署 1. 部署…

代码随想录算法训练营第三十九天|198.打家劫舍、

题目链接&#xff1a;198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;因为隔一家才能取&#xff0c;所以当前最大的价值要么是dp[i-2] nums[i] 或者是 dp[i-1] class Solution(object):def rob(self, nums):""":type nums: List[int…

windows核心编程 内核对象,创建进程(CreateProcess),管道(CreatePipe)

windows核心编程 内核对象&#xff0c;创建进程&#xff08;CreateProcess&#xff09;&#xff0c;管道&#xff08;CreatePipe&#xff09; windows核心编程 内核对象&#xff0c;创建进程&#xff08;CreateProcess&#xff09;&#xff0c;管道&#xff08;CreatePipe&…

基于SpringBoot的滴答拍摄影项目

你好&#xff0c;我是计算机专业的学生&#xff0c;专注于摄影项目的开发与实现。如果您对我的项目感兴趣或有任何建议&#xff0c;欢迎随时联系我。 开发语言 Java 数据库 MySQL 技术 SpringBoot框架 工具 MyEclipse、Tomcat服务器 系统展示 首页 管理员功能模块 用…

计算机毕业设计选题推荐-民宿可视化分析-Python爬虫-随机森林算法

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…