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

【STL】:list的模拟实现

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关list的模拟实现,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 基本构造

2. 正向迭代器

2.1 非const迭代器

2.2 const迭代器

2.3 正向迭代器的改进优化

3. 修改相关接口

3.1 insert、erase

3.2 尾插、头插、尾删、头删、清理

4. 拷贝构造、赋值重载、析构

5. 完整代码


1. 基本构造

list的底层其实是一个带头双向循环的链表,所以在模拟实现之前可以看一下库里面怎么实现的:

namespace ywh
{//链表结构template<class T>struct list_node{T _data;                 //节点中的数据list_node<T>* _prev;    //指向前一个节点的指针list_node<T>* _next;    //指向后一个节点的指针//构造list_node(const T& x = T()):_data(x), _prev(nullptr), _next(nullptr){}};//list结构template<class T>class list{public:typedef list_node<T> Node;public://空初始化void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _next;}//构造list(){empty_init();}private:Node* _head;  //链表的头节点size_t _size; //节点个数};
}

2. 正向迭代器

在使用list的阶段的迭代器使用方法和之前的string、vector一样,但是了解过链表的结构都知道,链表要访问下一个节点就是使用节点中的next指针指向下一个节点,需要访问里面的数据需要找到里面的data,++和解引用并不能访问链表的节点,那么迭代器的使用在底层肯定是封装加运算符重载。

在实现迭代器的时候也需要采用封装和运算符重载,运算符重载需要用到++、--、!=、==、*、->等运算符。迭代器分为const迭代器和非const迭代器,首先先来看看非const迭代器:

2.1 非const迭代器

迭代器的实现一般使用struct来进行封装:

namespace ljm
{//链表结构template<class T>struct list_node{T _data;                 //节点中的数据list_node<T>* _prev;    //指向前一个节点的指针list_node<T>* _next;    //指向后一个节点的指针//构造list_node(const T& x = T()):_data(x), _prev(nullptr), _next(nullptr){}};//非const正向迭代器template<class T>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T> self;Node* _node;//迭代器构造__list_iterator(Node* node):_node(node){}//前置//operator++self& operator++(){return _node->_next;}//operator--self& operator--(){return _node->_prev;}//后置self operator++(int){self* tmp(_node);_node = _node->_next;return tmp;}//operator--self operator--(int){self* tmp(_node);_node = _node->_prev;return tmp;}//operator*T& operator*(){return _node->_data;}//operator->T* operator->(){return &_node->_data;}//operator!=bool operator!=(const self& s){return _node != s._node;}//operator==bool operator==(const self& s){return _node == s._node;}};//list结构template<class T>class list{public:typedef list_node<T> Node;public://空初始化void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;}//构造list(){empty_init();}///正向迭代器iterator begin(){return iterator(_head->_next); //使用匿名对象进行构造}iterator end(){return iterator(_head);}private:Node* _head;  //链表的头节点size_t _size; //节点个数};
}

2.2 const迭代器

迭代器中涉及到修改的就是*和->,const迭代器的作用是指向的内容不能修改,本身不能修改,所以需要重新进行封装:

namespace ljm
{//链表结构template<class T>struct list_node{T _data;                 //节点中的数据list_node<T>* _prev;    //指向前一个节点的指针list_node<T>* _next;    //指向后一个节点的指针//构造list_node(const T& x = T()):_data(x), _prev(nullptr), _next(nullptr){}};//非const正向迭代器//...//const正向迭代器template<class T>struct __list_const_iterator{typedef list_node<T> Node;typedef __list_const_iterator<T> self;Node* _node;//迭代器构造__list_const_iterator(Node* node):_node(node){}//前置//operator++self& operator++(){_node = _node->_next;return *this;}//operator--self& operator--(){_node = _node->_prev;return *this;}//后置self operator++(int){self* tmp(_node);_node = _node->_next;return tmp;}//operator--self operator--(int){self* tmp(_node);_node = _node->_prev;return tmp;}//operator*const T& operator*(){return _node->_data;}//operator->const T* operator->(){return &_node->_data;}//operator!=bool operator!=(const self& s){return _node != s._node;}//operator==bool operator==(const self& s){return _node == s._node;}};//list结构template<class T>class list{public:typedef list_node<T> Node;typedef __list_iterator<T> iterator;typedef __list_const_iterator<T> const_iterator;public:基本构造/////空初始化void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;}//构造list(){empty_init();}///正向迭代器iterator begin(){return iterator(_head->_next); //使用匿名对象进行构造}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}private:Node* _head;  //链表的头节点size_t _size; //节点个数};
}

2.3 正向迭代器的改进优化

上面的这两种写法不难看出有很多代码都是重复出现的,使得代码非常冗余,那么怎么将这些冗余的代码进行合并呢?

我们可以看一下库里面如何实现:

采用泛型编程,使用模板,将这一转化的工作交给编译器,而我们只需传递const参数和非const参数即可:

namespace ywh
{//链表结构template<class T>struct list_node{T _data;                 //节点中的数据list_node<T>* _prev;    //指向前一个节点的指针list_node<T>* _next;    //指向后一个节点的指针//构造list_node(const T& x = T()):_data(x), _prev(nullptr), _next(nullptr){}};//非const正向迭代器//   类型模板参数   传递引用      传递指针template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;//迭代器构造__list_iterator(Node* node):_node(node){}//前置//operator++self& operator++(){_node = _node->_next;return *this;}//operator--self& operator--(){_node = _node->_prev;return *this;}//后置self operator++(int){self* tmp(_node);_node = _node->_next;return tmp;}//operator--self operator--(int){self* tmp(_node);_node = _node->_prev;return tmp;}//operator*Ref operator*(){return _node->_data;}//operator->Ptr operator->(){return &_node->_data;}//operator!=bool operator!=(const self& s){return _node != s._node;}//operator==bool operator==(const self& s){return _node == s._node;}};//list结构template<class T>class list{public:typedef list_node<T> Node;typedef __list_iterator<T, T&, T*> iterator;   //非const迭代器typedef __list_iterator<T, const T&, const T*> const_iterator;  //const迭代器public:基本构造/////空初始化void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;}//构造list(){empty_init();}///正向迭代器iterator begin(){return iterator(_head->_next); //使用匿名对象进行构造}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}private:Node* _head;  //链表的头节点size_t _size; //节点个数};
}

3. 修改相关接口

3.1 insert、erase

在pos位置进行插入删除比较简单,需要进行链接新节点,改变节点中指针的指向即可,库里面对于插入和删除都带有返回值,那么我们在实现的时候也加上返回值:

///修改相关接口//在pos位置插入节点iterator insert(iterator pos, const T& x){//创建新新节点Node* newnode = new Node(x);//链接节点Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;//更新节点个数++_size;//返回新节点的位置return iterator(newnode);}//删掉pos位置的节点iterator erase(iterator pos){//保存相对位置Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;//链接节点delete cur;prev->_next = next;next->_prev = prev;//更新节点个数--_size;//返回pos的下一个位置return iterator(next);}

在进行insert之后可以看到迭代器是不会失效的,但是在erase之后pos位置会被释放,因此erase之后迭代器会失效,在使用前先更新迭代器。

3.2 尾插、头插、尾删、头删、清理

当实现了insert和erase之后,实现这些接口直接复用即可:

        //尾插void push_back(const T& x){insert(end(), x);}//头插void push_front(const T& x){insert(begin(), x);}//尾删void pop_back(){erase(--end());}//头删void pop_front(){erase(begin());}//清理void clear(){iterator it = begin();while (it != end()){it = erase(it);}}

4. 拷贝构造、赋值重载、析构

在这里我们都是采用现代写法:

 拷贝构造直接使用迭代器依次遍历进行尾插。

//拷贝构造list(const list<T>& lt){//先创建空节点empty_init();//依次尾插即可for (auto e : lt){push_back(e);}}//operator=void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt){swap(lt);return *this;}//析构~list(){clear();delete _head;_head = nullptr;_size = 0;}

5. 完整代码

 头文件:List.h

#pragma oncenamespace ywh
{//链表结构template<class T>struct list_node{T _data;                 //节点中的数据list_node<T>* _prev;    //指向前一个节点的指针list_node<T>* _next;    //指向后一个节点的指针//构造list_node(const T& x = T()):_data(x), _prev(nullptr), _next(nullptr){}};//非const正向迭代器//   类型模板参数   传递引用      传递指针template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;//迭代器构造__list_iterator(Node* node):_node(node){}//前置//operator++self& operator++(){_node = _node->_next;return *this;}//operator--self& operator--(){_node = _node->_prev;return *this;}//后置self operator++(int){self* tmp(_node);_node = _node->_next;return tmp;}//operator--self operator--(int){self* tmp(_node);_node = _node->_prev;return tmp;}//operator*Ref operator*(){return _node->_data;}//operator->Ptr operator->(){return &_node->_data;}//operator!=bool operator!=(const self& s){return _node != s._node;}//operator==bool operator==(const self& s){return _node == s._node;}};//list结构template<class T>class list{public:typedef list_node<T> Node;typedef __list_iterator<T, T&, T*> iterator;   //非const迭代器typedef __list_iterator<T, const T&, const T*> const_iterator;  //const迭代器public:基本构造/////空初始化void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;}//构造list(){empty_init();}//拷贝构造list(const list<T>& lt){//先创建空节点empty_init();//依次尾插即可for (auto e : lt){push_back(e);}}//operator=void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt){swap(lt);return *this;}//析构~list(){clear();delete _head;_head = nullptr;_size = 0;}///正向迭代器iterator begin(){return iterator(_head->_next); //使用匿名对象进行构造}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}///修改相关接口//在pos位置插入节点iterator insert(iterator pos, const T& x){//创建新新节点Node* newnode = new Node(x);//链接节点Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;//更新节点个数++_size;//返回新节点的位置return iterator(newnode);}//删掉pos位置的节点iterator erase(iterator pos){//保存相对位置Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;//链接节点delete cur;prev->_next = next;next->_prev = prev;//更新节点个数--_size;//返回pos的下一个位置return iterator(next);}//尾插void push_back(const T& x){insert(end(), x);}//头插void push_front(const T& x){insert(begin(), x);}//尾删void pop_back(){erase(--end());}//头删void pop_front(){erase(begin());}//清理void clear(){iterator it = begin();while (it != end()){it = erase(it);}}//节点个数size_t size(){return _size;}private:Node* _head;  //链表的头节点size_t _size; //节点个数};
}

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!  

相关文章:

【STL】:list的模拟实现

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关list的模拟实现&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据…...

第七章 图【数据结构与算法】【精致版】

第七章 图【数据结构与算法】【精致版】 前言版权第七章 图7.1 应用实例7.2图的基本概念7.3图的存储结构7.3.1邻接矩阵**1-邻接矩阵.c****2-邻接矩阵plus.c** 7.3.2 邻接表**3-邻接表.c** **4-邻接表plus.c** 7.3.3 十字链表7.3.4多重链表 7.4图的遍历7.4.1深度优先搜索遍历**5…...

模型蒸馏学习

知识蒸馏&#xff1a;获取学生网络和教师网络指定蒸馏位点的输出特征并计算蒸馏 loss 的过程 知乎-mmrazor-模型蒸馏 知识蒸馏算法往往分为 reponse-based基于响应、feature-based基于特征 和 relation-based基于关系三类。 也可为 data-free KD、online KD、self KD&#xff…...

总结Kibana DevTools如何操作elasticsearch的常用语句

一、操作es的工具 ElasticSearch HeadKibana DevToolsElasticHQ 本文主要是总结Kibana DevTools操作es的语句。 二、搜索文档 1、根据ID查询单个记录 GET /course_idx/_doc/course:202、term 匹配"name"字段的值为"6789999"的文档 类似于sql语句中的等…...

【QT】QT自定义C++类

在使用Qt的ui设计时&#xff0c;Qt为我们提供了标准的类&#xff0c;但是在很多复杂工程中&#xff0c;标准的类并不能满足所有的需求&#xff0c;这时就需要我们自定义C类。 下面以自定义的QPushButton作一个很简单的例子。 先新建默认Qt Widgets Application项目 一、自定义…...

【多媒体文件格式】AVI、WAV、RIFF

AVI、RIFF AVI&#xff1a;Audio/Video Interleaved&#xff08;音频视频交织/交错&#xff09;&#xff0c;用于采集、编辑、播放的RIFF文件。由Microsoft公司1992年11月推出&#xff0c;是Microsoft公司开发的一种符合RIFF文件规范的数字音频与视频文件格式&#xff0c;原先…...

AI创作系统ChatGPT商业运营系统源码+支持GPT4/支持ai绘画

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…...

JWT简介 JWT结构 JWT示例 前端添加JWT令牌功能 后端程序

目录 1. JWT简述 1.1 什么是JWT 1.2 为什么使用JWT 1.3 JWT结构 1.4 验证过程 2. JWT示例 2.1 后台程序 2.2 前台加入jwt令牌功能 1. JWT简述 1.1 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7…...

Rust核心功能之一(所有权)

目录 1、什么是所有权&#xff1f; 1.1 所有权规则 1.2 变量作用域 1.3 String 类型 1.4 内存与分配 变量与数据交互的方式&#xff08;一&#xff09;&#xff1a;移动 变量与数据交互的方式&#xff08;二&#xff09;&#xff1a;克隆 只在栈上的数据&#xff1a;拷贝…...

跨域(CORS)和JWT 详解

跨域 (CORS) 概念 同源策略 (Same-Origin Policy) 同源策略是一项浏览器安全特性&#xff0c;它限制了一个网页中的脚本如何与另一个来源&#xff08;域名、协议、端口&#xff09;的资源进行交互。这对于防止跨站点请求伪造和数据泄露非常重要。 为什么需要跨域? 跨域问题通…...

前端框架Vue学习 ——(二)Vue常用指令

文章目录 常用指令 常用指令 指令: HTML 标签上带有 “v-” 前缀的特殊属性&#xff0c;不同指令具有不同含义。例如: v-if, v-for… 常用指令&#xff1a; v-bind&#xff1a;为 HTML 标签绑定属性值&#xff0c;如设置 href&#xff0c;css 样式等 <a v-bind:href"…...

Linux 指令心法(十四)`flash_erase` 擦除Flash存储器

文章目录 flash_erase 作用flash_erase的主要特点和使用场景flash_erase命令应用方法flash_erase命令可以解决哪些问题?flash_erase命令使用时注意事项 flash_erase 作用 这是一个用于擦除Flash存储器的命令。它可以擦除指定的Flash块或扇区&#xff0c;以便在写入新数据之前…...

GoLong的学习之路(二十一)进阶,语法之并发(go最重要的特点)(协程的主要用法)

并发编程在当前软件领域是一个非常重要的概念&#xff0c;随着CPU等硬件的发展&#xff0c;我们无一例外的想让我们的程序运行的快一点、再快一点。Go语言在语言层面天生支持并发&#xff0c;充分利用现代CPU的多核优势&#xff0c;这也是Go语言能够大范围流行的一个很重要的原…...

加快网站收录 3小时百度收录新站方法

加快网站收录 3小时百度收录新站方法 3小时百度收录新站方法说起来大家可能不相信&#xff0c;但这确实是真实的(该方法是通过技术提交&#xff0c;让百度快速抓取收录您的网站&#xff0c;不管你网站有没有备案&#xff0c;都能在短时间内被收录&#xff0c;要是你的网站迟迟不…...

GPT实战系列-ChatGLM3本地部署CUDA11+1080Ti+显卡24G实战方案

目录 一、ChatGLM3 模型 二、资源需求 三、部署安装 配置环境 安装过程 低成本配置部署方案 四、启动 ChatGLM3 五、功能测试 新鲜出炉&#xff0c;国产 GPT 版本迭代更新啦~清华团队刚刚发布ChatGLM3&#xff0c;恰逢云栖大会前百川也发布Baichuan2-192K&#xff0c;一…...

图片怎么转换成pdf?

图片怎么转换成pdf&#xff1f;图片可以转换成PDF格式文档吗&#xff1f;当然是可以的呀&#xff0c;当图片转换成PDF文件类型时&#xff0c;我们就会发现图片更加方便的打开分享和传播&#xff0c;而且还可以更加安全的保证我们的图片所有性。我们知道PDF文档是可以加密的&…...

【源码】医学影像PACS实现三维影像后处理等功能

医学影像诊断技术近年来取得了快速发展&#xff0c;包括高性能的影像检查设备的临床应用和数字信息技术的图像显示、存储、传输、处理、识别&#xff0c;这些技术使得计算机辅助检测和诊断成为可能&#xff0c;同时人工智能影像诊断也进入了人们的视野。这些技术进步提高了疾病…...

DOCTYPE是什么,有何作用、 使用方式、渲染模式、严格模式和怪异模式的区别?

前言 持续学习总结输出中&#xff0c;今天分享的是DOCTYPE是什么&#xff0c;有何作用、 使用方式、渲染模式、严格模式和怪异模式的区别。 DOCTYPE是什么&#xff0c;有何作用&#xff1f; DOCTYPE是HTML5的文档声明&#xff0c;通过它可以告诉浏览器&#xff0c;使用那个H…...

Go语言实现HTTP正向代理

文章目录 前言实现思路代码实现 前言 正向代理&#xff08;Forward Proxy&#xff09;是一种代理服务器的部署方式&#xff0c;它位于客户端和目标服务器之间&#xff0c;代表客户端向目标服务器发送请求。正向代理可以用来隐藏客户端的真实身份&#xff0c;以及在不同网络环境…...

第11章_数据处理之增删改

文章目录 1 插入数据1.1 实际问题1.2 方式 1&#xff1a;VALUES的方式添加1.3 方式2&#xff1a;将查询结果插入到表中演示代码 2 更新数据演示代码 3 删除数据演示代码 4 MySQL8新特性&#xff1a;计算列演示代码 5 综合案例课后练习 1 插入数据 1.1 实际问题 解决方式&#…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...

Linux基础开发工具——vim工具

文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)

注&#xff1a;文章末尾网盘链接中自取成品使用演示视频、项目源码、项目文档 所用硬件&#xff1a;STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …...