【数据结构】-- 栈和队列

🐇
🔥博客主页: 云曦
📋系列专栏:数据结构💨吾生也有涯,而知也无涯 💛 感谢大家👍点赞 😋关注📝评论
文章目录
- 前言
- 一、栈
- 📙1.1 栈的概念及结构
- 📙1.2 栈的实现
- 📜1.2.1 栈的结构
- 📜1.2.2 初始化栈
- 📜1.2.3 入栈
- 📜1.2.4 出栈
- 📜1.2.5 获取栈顶元素
- 📜1.2.6 获取栈中有效元素个数
- 📜1.2.7 检测栈是否为空
- 📜1.2.8 销毁栈
- 📜1.2.9 栈所有接口的测试
- 二、队列
- 📙2.1 队列的概念及结构
- 📙2.2 队列的实现
- 📜2.2.1 队列的结构
- 📜2.2.2 初始化队列
- 📜2.2.3 队尾入队列
- 📜2.2.4 检测队列是否为空
- 📜2.2.5 队头出队列
- 📜2.2.6 获取队列头部元素
- 📜2.2.7 获取队列尾部元素
- 📜2.2.8 获取队列中有效元素个数
- 📜2.2.9 销毁队列
- 📜2.2.10 队列所有接口的测试
- 完整代码
- 📙**Stack.h**
- 📙**Stack.c**
- 📙**Queue.h**
- 📙**Queue.c**
- 📙**test.c**
前言
在上期中我们把双向链表学完了,这期我们要开始新的一章 “栈和队列” 的讲解,栈和队列基本都是拿我们之前所学的顺序表或链表来实现,可谓是新瓶装旧酒,有了对顺序表和链表的认识,实现栈和队列并不是难事!
一、栈
📙1.1 栈的概念及结构
概念:栈也是一种特殊的线性表,其次栈只允许在固定一端插入删除,进行数据插入或删除操作的一端叫作栈顶,另一端为栈底。 栈中的数据元素遵守后进先出LIFO的原则。
- 压栈:栈插入数据的操作叫作进栈/入栈/压栈,且入数据是在栈顶。
- 出栈:栈删除数据的操作叫作出栈,出数据也在栈顶。
结构:
注意:栈如果用链表实现的话要考虑找前一个节点的问题,因为栈的插入删除都是在栈顶,所以实现栈时推荐用顺序表来实现,因为顺序表在尾部插入数据的代价比较小。
📙1.2 栈的实现
📜1.2.1 栈的结构
既然用顺序表实现那么栈的结构就是顺序表的结构
typedef int STDataType;typedef struct Stack
{int* arr;int top;//栈顶int capacity;//容量
}ST;
📜1.2.2 初始化栈
因为在栈的结构里,插入数据只能在栈顶插入,所有只需要实现一个插入的接口即可,把malloc改到插入接口(push)里即可.
void STInit(ST* ps)
{assert(ps);ps->arr = NULL;ps->capacity = 0;ps->size = 0;
}
📜1.2.3 入栈
void STPush(ST* ps, STDataType x)
{assert(ps);//检测容量if (ps->top == ps->capacity){//使用三目操作符来解决扩容问题int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;STDataType* tmp = (STDataType*)realloc(ps->arr, sizeof(STDataType)*newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}ps->arr = tmp;ps->capacity = newcapacity;}//入栈ps->arr[ps->top] = x;ps->top++;
}
需要注意的是:realloc有一个特性,如果传入的参数是NULL,则会像malloc一样开辟一个空间出来,相当于调用了malloc。
📜1.2.4 出栈
出栈很简单让栈顶减1即可。
void STPop(ST* ps)
{assert(ps);//栈为空就不能再删了assert(ps->top > 0);ps->top--;
}
需要注意的是:插入的指针有可能为空或者栈有可能为空,两个情况都要检查一下
📜1.2.5 获取栈顶元素
top是有效数据个数后一个的位置,减1就为栈顶的元素
STDataType STTop(const ST* ps)
{assert(ps);return ps->arr[ps->top-1];
}
📜1.2.6 获取栈中有效元素个数
top就是有效数据的个数,直接返回top即可
int STSize(const ST* ps)
{assert(ps);return ps->top;
}
📜1.2.7 检测栈是否为空
返回top是否等于0,等于返回真,不等于返回假
bool STEmpty(const ST* ps)
{assert(ps);return ps->top == 0;
}
📜1.2.8 销毁栈
注意:这里的销毁指得是把这块空间还给操作系统。
void STDestroy(ST* ps)
{assert(ps);free(ps->arr);ps->arr = NULL;ps->capacity = 0;ps->top = 0;
}
📜1.2.9 栈所有接口的测试
void TestStack()
{ST s;STInit(&s);STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);STPush(&s, 5);printf("栈的有效元素个数%d\n", STSize(&s));while (!STEmpty(&s)){printf("%d ", STTop(&s));STPop(&s);}//STPop(&s);printf("\n栈的有效元素个数%d\n", STSize(&s));STDestroy(&s);}

当栈为空还在删除时:
二、队列
📙2.1 队列的概念及结构
概念:队列也是一种只允许在一端插入数据,在另外一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO。
- 入队列:进行插入操作的一端称为队尾。
- 出队列:进行删除操作的一端称为队头。
结构:
注意:队列也能用顺序表和链表实现,但使用链表的结构实现更优一些,因为如果是顺序表的话,头删要挪移数据才能完成,效率比链表低,所以实现队列推荐用链表来实现。
📙2.2 队列的实现
📜2.2.1 队列的结构
既然使用队列实现,那么结构自然是链表的形式。
typedef int QueDataType;typedef struct QueueNode
{QueDataType data;struct QueueNode* next;
}QNode;typedef struct Queue
{QNode* phead;QNode* tail;int size;
}Que;
因为是单链表,出队列是头删,入队列是尾插,导致需要一个头指针和尾指针,且单链表计算长度要遍历一遍数组,这样下来:
- 要传头和尾的参数
- 计算链表长度要遍历链表,复杂度为O(N)
- 不如直接把头尾指针和定义一个存储链表元素个数的变量整合为一个结构体这样就方便多了
📜2.2.2 初始化队列
void QueueInit(Que* pq)
{pq->phead = NULL;pq->tail = NULL;pq->size = 0;
}
只有一个插入的接口,那么就直接在插入接口获取节点就行。
📜2.2.3 队尾入队列
首先获取并初始化节点,然后就是尾插,尾插分为两种情况:
- 链表为空,特殊处理。
- 链表不为空,正常尾插。
void QueuePush(Que* pq, QueDataType x)
{//获取并初始化节点QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("newnode fail");exit(-1);}newnode->data = x;newnode->next = NULL;//入队列if (pq->phead == NULL){//链表为空pq->phead = newnode;pq->tail = newnode;}else{//链表不为空pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}
📜2.2.4 检测队列是否为空
直接返回pq->phead == NULL,等于返回真,不等于返回假
bool QueueEmpty(Que* pq)
{assert(pq);return pq->phead == NULL;
}
📜2.2.5 队头出队列
单链表头删很简单,但要注意:链表为空时,不能再删了,否则会出问题
void QueuePop(Que* pq)
{assert(pq);//检测链表是否为空,为空就不能再删了assert(!QueueEmpty(pq));//出队列QNode* Next = pq->phead->next;free(pq->phead);pq->phead = Next;pq->size--;
}
📜2.2.6 获取队列头部元素
phead指向的就是头节点,返回头节点的元素即可,但要注意:链表有可能为空,所以要检测一下
QueDataType QueueFront(Que* pq)
{assert(pq);//检测链表是否为空assert(!QueueEmpty(pq));return pq->phead->data;
}
📜2.2.7 获取队列尾部元素
tail指向的就是尾节点,直接返回尾节点的元素即可,但要注意:链表有可能为空,所以要检测一下
QueDataType QueueBack(Que* pq)
{assert(pq);//检测链表是否为空assert(!QueueEmpty(pq));return pq->tail->data;
}
📜2.2.8 获取队列中有效元素个数
size存储的就是队列的有效元素个数,返回size即可。
int QueueSize(Que* pq)
{assert(pq);return pq->size;
}
📜2.2.9 销毁队列
- 这里的销毁指的也是把这片空间还给操作系统
- 遍历一遍队列,记录下一个节点的位置,释放掉当前节点,最后将头尾指针置空,size置为0即可。
void QueueDestroy(Que* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* Next = cur->next;free(cur);cur = Next;}pq->phead = NULL;pq->tail = NULL;pq->size = 0;
}
📜2.2.10 队列所有接口的测试
TestQueue()
{Que obj;QueueInit(&obj);QueuePush(&obj, 1);QueuePush(&obj, 2);QueuePush(&obj, 3);QueuePush(&obj, 4);QueuePush(&obj, 5);printf("队尾元素:%d\n", QueueBack(&obj));printf("队列的有效数据个数:%d\n", QueueSize(&obj));while (!QueueEmpty(&obj)){printf("%d ", QueueFront(&obj));QueuePop(&obj);}//QueuePop(&obj);printf("\n队列的有效数据个数:%d\n", QueueSize(&obj));QueueDestroy(&obj);
}

当队列为空还在删时:
完整代码
*
📙Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//栈
typedef int STDataType;typedef struct Stack
{int* arr;int top;//栈顶int capacity;//容量
}ST;//初始化栈
void STInit(ST* ps);
//入栈
void STPush(ST* ps, STDataType x);
//出栈
void STPop(ST* ps);
//获取栈顶元素
STDataType STTop(const ST* ps);
//获取栈中有效元素的个数
int STSize(const ST* ps);
//检测栈是否为空
bool STEmpty(const ST* ps);
//销毁栈
void STDestroy(ST* ps);
📙Stack.c
#include "Stack.h"void STInit(ST* ps)
{assert(ps);ps->arr = NULL;ps->capacity = 0;ps->top = 0;
}void STPush(ST* ps, STDataType x)
{assert(ps);//检测容量if (ps->top == ps->capacity){//使用三目操作符来解决扩容问题int newcapacity = ps->capacit==0 ? 4 : ps->capacity*2;STDataType* tmp = (STDataType*)realloc(ps->arr, sizeof(STDataType)*newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}ps->arr = tmp;ps->capacity = newcapacity;}//入栈ps->arr[ps->top] = x;ps->top++;
}void STPop(ST* ps)
{assert(ps);//栈为空就不能再删了assert(ps->top > 0);ps->top--;
}STDataType STTop(const ST* ps)
{assert(ps);return ps->arr[ps->top-1];
}int STSize(const ST* ps)
{assert(ps);return ps->top;
}bool STEmpty(const ST* ps)
{assert(ps);return ps->top == 0;
}void STDestroy(ST* ps)
{assert(ps);free(ps->arr);ps->arr = NULL;ps->capacity = 0;ps->top = 0;
}
*
📙Queue.h
#pragma once#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//队列
typedef int QueDataType;typedef struct QueueNode
{QueDataType data;struct QueueNode* next;
}QNode;typedef struct Queue
{QNode* phead;QNode* tail;int size;
}Que;//初始化队列
void QueueInit(Que* pq);
//入队列
void QueuePush(Que* pq, QueDataType x);
//检测队列是否为空
bool QueueEmpty(Que* pq);
//出队列
void QueuePop(Que* pq);
//获取队列头部元素
QueDataType QueueFront(Que* pq);
//获取队列尾部元素
QueDataType QueueBack(Que* pq);
//获取队列中有效元素的个数
int QueueSize(Que* pq);
//销毁队列
void QueueDestroy(Que* pq);
📙Queue.c
#include"Queue.h"void QueueInit(Que* pq)
{assert(pq);pq->phead = NULL;pq->tail = NULL;pq->size = 0;
}void QueuePush(Que* pq, QueDataType x)
{assert(pq);//获取并初始化节点QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("newnode fail");exit(-1);}newnode->data = x;newnode->next = NULL;//入队列if (pq->phead == NULL){//链表为空pq->phead = newnode;pq->tail = newnode;}else{//链表不为空pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}bool QueueEmpty(Que* pq)
{assert(pq);return pq->phead == NULL;
}void QueuePop(Que* pq)
{assert(pq);//检测链表是否为空,为空就不能再删了assert(!QueueEmpty(pq));//出队列QNode* Next = pq->phead->next;free(pq->phead);pq->phead = Next;pq->size--;
}QueDataType QueueFront(Que* pq)
{assert(pq);//检测链表是否为空assert(!QueueEmpty(pq));return pq->phead->data;
}QueDataType QueueBack(Que* pq)
{assert(pq);//检测链表是否为空assert(!QueueEmpty(pq));return pq->tail->data;
}int QueueSize(Que* pq)
{assert(pq);return pq->size;
}void QueueDestroy(Que* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* Next = cur->next;free(cur);cur = Next;}pq->phead = NULL;pq->tail = NULL;pq->size = 0;
}
*
📙test.c
#include"Stack.h"
#include"Queue.h"void TestStack()
{ST s;STInit(&s);STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);STPush(&s, 5);printf("栈的有效元素个数%d\n", STSize(&s));while (!STEmpty(&s)){printf("%d ", STTop(&s));STPop(&s);}//STPop(&s);printf("\n栈的有效元素个数%d\n", STSize(&s));STDestroy(&s);}TestQueue()
{Que obj;QueueInit(&obj);QueuePush(&obj, 1);QueuePush(&obj, 2);QueuePush(&obj, 3);QueuePush(&obj, 4);QueuePush(&obj, 5);printf("队尾元素:%d\n", QueueBack(&obj));printf("队列的有效数据个数:%d\n", QueueSize(&obj));while (!QueueEmpty(&obj)){printf("%d ", QueueFront(&obj));QueuePop(&obj);}//QueuePop(&obj);printf("\n队列的有效数据个数:%d\n", QueueSize(&obj));QueueDestroy(&obj);
}int main()
{TestStack();TestQueue();return 0;
}
相关文章:
【数据结构】-- 栈和队列
🐇 🔥博客主页: 云曦 📋系列专栏:数据结构 💨吾生也有涯,而知也无涯 💛 感谢大家👍点赞 😋关注📝评论 文章目录 前言一、栈📙1.1 栈…...
使用SpringAop切面编程通过Spel表达式实现Controller权限控制
目录 参考一、概念SpEL表达式 二、开发引入包定义注解定义切面定义用户上下文 三、测试新建Service在方法上注解新建Service在类上注解运行 参考 SpringBoot:SpEL让复杂权限控制变得很简单 一、概念 对于在Springboot中,利用自定义注解切面来实现接口…...
Flutter:简单搞一个内容高亮
内容高亮并不陌生,特别是在搜索内容页面,可以说四处可见,就拿掘金这个应用而言,针对某一个关键字,我们搜索之后,与关键字相同的内容,则会高亮展示,如下图所示: 如上的效果…...
2023/08/10
文章目录 一、计算属性传参二、小程序、h5跳转其他平台授权三、封装popup弹窗四、实现保存海报五、下载图片和复制分享链接 一、计算属性传参 计算属性的值往往通过一个回调函数返回,但是这个回调函数是无法传递参数的,要想实现计算属性传参可以通过闭包…...
LeetCode 1289. 下降路径最小和 II:通俗易懂地讲解O(n^2) + O(1)的做法
【LetMeFly】1289.下降路径最小和 II:通俗易懂地讲解O(n^2) O(1)的做法 力扣题目链接:https://leetcode.cn/problems/minimum-falling-path-sum-ii/ 给你一个 n x n 整数矩阵 arr ,请你返回 非零偏移下降路径 数字和的最小值。 非零偏移下…...
Coin Change
一、题目 Suppose there are 5 types of coins: 50-cent, 25-cent, 10-cent, 5-cent, and 1-cent. We want to make changes with these coins for a given amount of money. For example, if we have 11 cents, then we can make changes with one 10-cent coin and one 1-c…...
2023 8 -14链表OJ
💕人面只今何处去,桃花依旧笑春风💕 作者:Mylvzi 文章主要内容:详解链表OJ题 题目一:环形链表(判断链表是否带环) 题目描述: 画图分析: 代码实现&#x…...
大数据必回之LSM树
LSM树(Log-Structured-Merge-Tree)并不像B、红黑树一样是一颗严格的树状数据结构,它其实是一种存储结构,像HBase、RocksDB这些NoSQL存储都是采用LSM树。它是一种分层、有序、面向磁盘的数据结构,核心思想是顺序写性能远…...
Vue中的Object.defineProperty详解
Vue中的Object.defineProperty是一个比较重要的方法,它是可以定义对象中属性的一个方法,相比于在对象中直接定义的对象,它更具有灵活性。 直接定义对象中的属性是这样的: let person {name:张三,address:广东,age:12,} 而Object.…...
MySQL高阶知识点(一)一条SQL【更新】语句是如何执行的
一条SQL【更新】语句是如何执行的 首先,可以确定的说,【查询】语句的那一套流程,【更新】语句也是同样会走一遍,与查询流程不一样的是, 更新语句涉及到【事务】,就必须保证事务的四大特性:ACID&…...
threejs实现模型gltf的动画效果
确保加载模型后模型有animations属性。加载完模型后,在模型中定义mixer的变量值。 // 4、加入加载器 const loader new GLTFLoader(); loader.load("./model/gltf/RobotExpressive/RobotExpressive.glb", function (gltf) {// 赋值动画给mixermixer ne…...
Harmony创建项目ohpm报错
Harmony创建FA模型的项目时报如下错: The registry is empty - edit .ohpmrc file or use "ohpm config set registry your_registry" command to set registry.解决方法: File -> Settings -> Build,Execution,Deployment -> Ohpm …...
44 | 酒店预订及取消的数据分析
1.背景介绍 数据集来自Kaggle网站上公开的Hotel booking demand项目 该数据集包含了一家城市酒店和一家度假酒店的预订信息,包括预订时间、入住时间、成人、儿童或婴儿数量、可用停车位数量等信息。 数据集容量约为12万32 本次数据分析主要包含如下内容: 总览数据,完成对…...
物联网和不断发展的ITSM
物联网将改变社会,整个技术行业关于对机器连接都通过嵌入式传感器、软件和收集和交换数据的电子设备每天都在更新中。Gartner 预测,全球将有4亿台互联设备投入使用。 无论企业采用物联网的速度如何,连接设备都将成为新常态,IT服务…...
加了ComponentScan,但是feign接口无法注入的原因
正文 正确的注入 如果发现无法注入:看看启动类Application是否有加入注解:EnableFeignClients(AppConstant.BASE_PACKAGES) 注意:EnableFeignClients和ComponentScan是两个独立的扫描,所以,如果只配置了ComponentSca…...
C#Winform中DataGridView控件显示行号实例
本文演示C#Winform中如何给DataGridView控件显示行号。 首先创建winform项目,添加DataGridView控件,给控件添加两列。 修改CS代码: using System.Windows.Forms;namespace DataGridviewDemo {public partial class Form1 : Form{public Form1(){InitializeComponent();//添…...
Stable Diffusion WebUI安装和使用教程(Windows)
目录 下载Stable Diffusion WebUI运行安装程序,双击webui.bat界面启动插件安装(github)模型下载(有些需要魔法)安装过程遇到的大坑总结参考的博客 整个过程坑巨多,我花了一个晚上的时间才全部搞定,本教程针对有编程基础…...
LeetCode 35题:搜索插入位置
题目 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], target 5 输出: 2示例 2:…...
Linux系统中常见的几种软件包管理器
软件包管理器 DPKGAPT(APT-GET)RPMYUMDNF Linux软件包管理工具是一组命令的集合,其作用是在操作系统中提供安装、更新、删除及卸载软件的方法,同时提供对系统中所有软件状态信息的查询。不同的Linux发行版会有不同的包管理器&…...
python异步IO完全指南
原地址:https://flyingbyte.cc/post/async_io/ python异步IO完全指南 做为一种并行编程的範式,异步IO在Python中非常受重视,从Python3.4到3.7快速演进。 我们已经有多线程,多进程,并发(concurrency&#x…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...



