Java PriorityQueue

一般情况下, 我们使用队列是为了能够建造队列的先进先出 (First-In-First-Out) 模式的, 达到一种资源的公平分配, 先到达的任务 (元素) 先处理, 但有时需要在队列中基于优先级处理对象。
存入队列中的任务 (元素) 具有优先级, 需要根据优先级修复里面的数据。而在 JDK 1.5 引入的 PriorityQueue, 就具备了这种功能。

1 PriorityQueue 的实现数据结构

PriorityQueue 通过二叉小顶堆实现, 具备下面的特点:

  1. 二叉堆就是完全二叉树, 左边最多比右边深 1 层, 不能是右边比左边深, 这是和平衡二叉树不同的地方。在树中的数据变动了 (新增/删除等), 具备自行调整的特性。
  2. 小顶堆: 根节点最小, 父结点的键值总是小于或等于任何一个子节点的键值 (大顶堆, 则是相反的)。

基于上面的 2 个特性, 可以推导出几个特点 (假设当前有一个节点 Node, 从上往下, 从左往右计算, 他是 index 个)

  1. Node 节点的左子节点的位置为 2 * index + 1
  2. Node 节点的右子节点的位置为 2 * index + 2
  3. Node 节点的父级节点的位置为 (index - 1) / 2

每个节点可以推导出其相关的父子级节点, 所以二叉小顶堆可以通过数组优雅的实现。

如图:
Alt '数组实现 PriorityQueue'

2 PriorityQueue 中的几比较重要的属性

public class PriorityQueue<E> {transient Object[] queue; private int size = 0;private final Comparator<? super E> comparator;transient int modCount = 0;
}

2.1 queue

PriorityQueue 中数据存储的地方, 可以看到是通过数组实现, 通过数组实现了二叉小顶堆的结构。

2.2 size

当前 PriorityQueue 中存储的数据量

2.3 comparator

用户自定义的比较器, PriorityQueue 就是借助这个比较器, 对存入的数据进行比较, 决定优先级的。
如果用户没有定义这个比较器的话, 那么需要保证存入 PriorityQueue 中的数据是可以比较的, 即实现了 Comparator 接口。
而当用户既定义了比较器, 同时数据实现了 Comparator 接口, 优先使用比较器进行比较。

2.4 modCount

当前的 PriorityQueue 变更了多少次。

3 PriorityQueue 的构造方法

public class PriorityQueue<E> {// 构造函数 1: 无参构造函数public PriorityQueue() {// 省略    }// 构造函数 2: 指定容量的构造函数public PriorityQueue(int initialCapacity) {// 省略}// 构造函数 3: 指定容量比较器的构造函数public PriorityQueue(Comparator<? super E> comparator) {// 省略}// 构造函数 4: 指定了初始容量和比较器的构造函数public PriorityQueue(int initialCapacity,  Comparator<? super E> comparator) {// 省略}// 构造函数 5: 指定一个 PriorityQueue 的构造函数public PriorityQueue(PriorityQueue<? extends E> c) {// 省略this.comparator = (Comparator<? super E>) c.comparator();initFromPriorityQueue(c);}// 构造函数 6: 指定一个  SortedSet 的构造函数public PriorityQueue(SortedSet<? extends E> c) {this.comparator = (Comparator<? super E>) c.comparator();initElementsFromCollection(c);}// 构造函数 7: 给定一个 Collection 的构造函数public PriorityQueue(Collection<? extends E> c) {// 省略}
}

总共提供了 7 个构造函数, 看起来很多, 实际后面很多都是相同的, 所以需要深入理解的就 1 个, 指定 Collection 的构造函数。

3.1 无参构造函数

public PriorityQueue() {// 内部调用到自身 2 个参数的构造函数// 同时提供默认值, 初始容量的默认值为 DEFAULT_INITIAL_CAPACITY = 11, Comparator 比较器为nullthis(DEFAULT_INITIAL_CAPACITY, null);
}

3.2 指定容量的构造函数

public PriorityQueue(int initialCapacity) {// 同样是内部调用到自身的 2 个参数的构造函数// 初始容量默认值为用户定义的值, Comparator 比较器则为 nullthis(initialCapacity, null);
}

3.3 指定容量比较器的构造函数

public PriorityQueue(Comparator<? super E> comparator) {// 同样是内部调用到了自身的 2 个参数的构造函数// 初始容量默认为 11, 比较器为用户自定义的this(DEFAULT_INITIAL_CAPACITY, comparator);
}

3.4 指定了初始容量和比较器的构造函数

public PriorityQueue(int initialCapacity,  Comparator<? super E> comparator) {// 初始容量必须大于 1 if (initialCapacity < 1)throw new IllegalArgumentException();// 声明数组this.queue = new Object[initialCapacity];// 将用户声明的比较器赋值给自身的比较器属性this.comparator = comparator;
}

3.5 指定一个 PriorityQueue 的构造函数

public PriorityQueue(PriorityQueue<? extends E> c) {// 获取队列里面的比较器this.comparator = (Comparator<? super E>) c.comparator();// 将队列里面的数据读取到自身, 下面集合构造函数分析initFromPriorityQueue(c);
}

3.6 指定一个 SortedSet 的构造函数

public PriorityQueue(SortedSet<? extends E> c) {// 获取 SortedSet 里面的比较器this.comparator = (Comparator<? super E>) c.comparator();// 从集合里面读取数据到自身, 同样下面的集合构造函数有分析initElementsFromCollection(c);
}

3.7 给定一个 Collection 的构造函数

public PriorityQueue(Collection<? extends E> c) {if (c instanceof SortedSet<?>) {// SortedSet 内部也是有序的, 实现逻辑也是和 PriorityQueue 一样, 所以特殊处理SortedSet<? extends E> ss = (SortedSet<? extends E>) c;// 获取 SortedSet 中的比较器this.comparator = (Comparator<? super E>) ss.comparator();// 从 Collection 中获取数据赋值到当前的 PriorityQueueinitElementsFromCollection(ss);} else if (c instanceof PriorityQueue<?>) {// PriorityQueue 转为 PriorityQueue, 逻辑简单很多, 所以特殊处理PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;// 获取 PriorityQueue 的比较器this.comparator = (Comparator<? super E>) pq.comparator();// 获取数据initFromPriorityQueue(pq);} else {// 设置当前的 PriorityQueue 的比较器为 nullthis.comparator = null;// 获取数据initFromCollection(c);}
}/*** 场景一: 从有序的结合中获取数据 */
private void initElementsFromCollection(Collection<? extends E> c) {// 调用这个方法的调用方, Colleciont 是已经有序的// 转换数组Object[] a = c.toArray();// 不是 ArrayList 类型, 通过 Arrays.copyOf 做一层转换if (c.getClass() != ArrayList.class)a = Arrays.copyOf(a, a.length, Object[].class);int len = a.length;// 只有 1 个元素 或者 自定义的比较器不为 nullif (len == 1 || this.comparator != null)// 不能为 nullfor (int i = 0; i < len; i++)if (a[i] == null)throw new NullPointerException();// 赋值给自身的 queue 数组      this.queue = a;// 当前的个数等于数组的长度this.size = a.length;
}/*** 场景二: 从 PriorityQueue 中获取数据*/
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {// 是 PriorityQueue 直接赋值if (c.getClass() == PriorityQueue.class) {this.queue = c.toArray();this.size = c.size();} else {initFromCollection(c);}
}/*** 场景三: 从普通的集合中获取数据*/
private void initFromCollection(Collection<? extends E> c) {// 调用这个方法的调用方, Collection 不一定是有序的, 所以为了满足二叉小顶堆的特点, 需要进行堆化调整// 把 Collection c 中的数据赋值给当前的 QueueinitElementsFromCollection(c);// 堆化, 数组调整, 使其满足二叉小顶堆的特点heapify();
}private void heapify() {// 对于一个杂乱无章的数组, 进行调整的话, 如果直接从第一个元素开始调整, 那么会频繁的调整// 通过分析可以知道, 二叉树没有叶子节点占整棵树节点的一半, 所有的叶子节点暂时不看他们的父级节点, 可以看做是已经满足二叉树的节点// 这一部分看做是满足条件的话, 那么就可以从最后一个有叶子节点的开始往前进行调整, 这样的话, 可以只对数组中的一半进行调整// 从数组的中间开始调整for (int i = (size >>> 1) - 1; i >= 0; i--)// 下移操作, 后面讲解siftDown(i, (E) queue[i]);
}

4 PriorityQueue 的操作方法

4.1 添加数据

二叉小顶堆的特点

  1. 父结点的键值总是小于或等于任何一个子节点的键值
  2. 于数组实现的二叉堆, 对于数组中任意位置的 n 上元素, 其左孩子在 2n+1 位置上, 右孩子 2(n+1) 位置, 它的父亲则在 n-1/2 上, 根节点在 0 位置

为了维护这个特点, 二叉堆在添加元素的时候, 需要一个 “上移” 的动作, “上移” 的过程, 如图:

Alt 'BinaryHeapShiftUp'

  1. 将元素 2 添加在最后一个位置
  2. 由于 2 比其父亲 6 要小, 所以将元素 2 上移, 交换 2 和 6 的位置
  3. 然后由于 2 比 5 小, 继续将 2 上移, 交换 2 和 5 的位置
  4. 此时 2 大于其父亲 (根节点) 1, 结束

上移的过程总结

  1. 先把需要新增的节点放到末尾的位置
  2. 和其父级进行比较

2.1 如果没有父节点了, 结束, 当前的位置就是新增节点的位置
2.1 如果比父级的值大, 结束, 这个位置就是新增的节点的所在位置
2.2 如果比父级的值小, 和父级交互值, 然后回到第二步, 继续进行比较

代码实现:

public class PriorityQueue<E> {public boolean add(E e) {// 默认添加到尾部, 调用自身的 offer 方法return offer(e);}public boolean offer(E e) {// 不支持 nullif (e == null)throw new NullPointerException();// 修改次数 +1            modCount++;// 当前数据的个数int i = size;// 数据的个数大于当前数组的长度if (i >= queue.length)// 扩容grow(i + 1);// 已存储数据个数 + 1            size = i + 1;// 当前数组中的没有数据if (i == 0)// 直接把数组的第一位设置为添加的数据queue[0] = e;else// 添加数据到数组的 i 位置并进行上移操作siftUp(i, e);return true;}// 元素上移private void siftUp(int k, E x) {if (comparator != null)// 有比较器的使用比较器的上移方法siftUpUsingComparator(k, x);else// 没有比较器使用, 没有比较器的的上移方法siftUpComparable(k, x);}// 使用用户自定义的比较器进行比较, 然后完成元素上移操作private void siftUpUsingComparator(int k, E x) {while (k > 0) {// 获取插入位置的父级节点的位置int parent = (k - 1) >>> 1;// 获取父级节点的值Object e = queue[parent];// 使用比较器进行比较当前的值和父级的值, // 当前的值 >= 父级的值, 不需要上移了, 此时的 k 位置就是数据存储的位置if (comparator.compare(x, (E) e) >= 0)break;// 当前的位置设置为父级的值                queue[k] = e;// 将父级的位置赋给当前的 k, 标识下次计划插入的位置, 也就是上移操作k = parent;}// 数组当前的 k 位置等于需要插入的数据 xqueue[k] = x;}// 使用元素的比较器进行比较, 然后完成元素上移操作private void siftUpComparable(int k, E x) {// 没有比较器, 需要当前的数据是 Comparable 的实现类, 即数据类型是可比较的Comparable<? super E> key = (Comparable<? super E>) x;// 大体的流程和上面的一样while (k > 0) {int parent = (k - 1) >>> 1;Object e = queue[parent];if (key.compareTo((E) e) >= 0)break;queue[k] = e;k = parent;}queue[k] = key;}// 数组扩容private void grow(int minCapacity) {// 当前数组的容量int oldCapacity = queue.length;// 当前数组的容量 < 64, 则新的数组容量 = 旧数组容量 * 2 + 2, 否则等于新的数组容量 = 旧的容量 * 1.5int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1));// 新的容量比最大值 (int 的最大值 - 8) 大if (newCapacity - MAX_ARRAY_SIZE > 0)// 控制新的最大值不大于 int 的最大值newCapacity = hugeCapacity(minCapacity);// 声明新的数组, 同时将旧的数组的数据迁移到新的数组queue = Arrays.copyOf(queue, newCapacity);            }private static int hugeCapacity(int minCapacity) {// 小于 0, 抛异常if (minCapacity < 0) throw new OutOfMemoryError();// 入参的容量大于 int 的最大值 - 8 的话, 返回 int 的最大值, 否则返回 int 的最大值 - 8return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;}
}

4.2 删除数据

对于 PriorityQueue 删除数据都是删除根元素, 也就是最小的元素。
删除了根元素, 就要找一个替代者移动到根位置, 相对于被删除的元素来说就是 “下移”, 如图:
Alt 'BinaryHeapShiftDown'

  1. 将找出队尾的元素 8, 并将它在队尾位置上删除
  2. 此时队尾元素 8 比根元素 1 的最小孩子 3 要大, 所以将元素 1 下移, 交换 1 和 3 的位置
  3. 然后此时队尾元素 8 比元素 1 的最小孩子 4 要大, 继续将 1 下移, 交换 1 和 4 的位置
  4. 然后此时根元素 8 比元素 1 的最小孩子 9 要小, 不需要下移, 直接将根元素 8 赋值给此时元素 1 的位置, 1 被覆盖则相当于删除

下移的过程总结

  1. 获取末尾节点的值, 然后进行删除
  2. 找到需要删除位置的左右节点, 找到 2 个节点值比较小的节点

2.1 如果没有左右节点, 把末尾节点的值放到这个位置, 下移过程结束
2.2 如果只有左节点, 较小值等于左节点的值

  1. 用末尾的值和找到的较小值比较

3.1 如果末尾的值比较小值小, 那么需要删除位置放入末尾节点的值, 下移结束
3.2 如果末尾的值比较小值大, 那么把较小值的放到需要删除的位置, 需要删除的位置替换为较小值所在的位置, 然后回到第二步, 继续进行比较

如果删除的位置刚好是根节点, 上面的下移基本完成了。

如果删除的位置是中间的位置, 那么还需要在进行一次是否上移的判断:
如果末尾的值直接就是放入第一次需要删除的位置, 没有任何的比较替换操作, 这是放入的位置的值可能比他的父级小, 所以还需要进行一次上移的判断,
如果不是直接放入到第一次需要删除的位置, 就不需要进行上移的判断

代码实现:

public class PriorityQueue<E> {public E remove() {E x = poll();if (x != null)return x;else// 为 null 抛出异常throw new NoSuchElementException();}public E poll() {// 存储的数据个数为 0, 返回 nullif (size == 0)return null;// 新的元素个数 = 旧的元素个数 - 1    int s = --size;// 修改次数 + 1modCount++;// 获取根节点E result = (E) queue[0];// 获取最后一个节点E x = (E) queue[s];// 设置最后一个节点为 nullqueue[s] = null;// 新的元素个数不等于 0, 进行下移if (s != 0)siftDown(0, x);  return result;}private void siftDown(int k, E x) {// 将元素 x 放入到 k 的位置, 然后进行下移if (comparator != null)siftDownUsingComparator(k, x);elsesiftDownComparable(k, x);}// 使用用户自定义的比较器进行比较, 然后完成元素下移操作private void siftDownUsingComparator(int k, E x) {// 通过 size/2 找到第一个没有叶子节点的元素, 这个位置后面的节点都是没有叶子节点, 可以不用处理了int half = size >>> 1;while (k < half) {// 获取指定位置的左节点int child = (k << 1) + 1;// c 存储的是左右节点中较小的值Object c = queue[child];// 右节点的位置int right = child + 1;// 不是数组的尾部, 左节点比右节点的值大, 那么较小值 c = 右节点的值if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)// 需要替换的值为右节点的值c = queue[child = right];// 需要替换的值比左右节点中的较小值还要小, 不需要继续处理了if (comparator.compare(x, (E) c) <= 0)break;// 将 x 理论存储的位置从 k 下移到左右节点中较小的节点的位置 child, 进入下层循环queue[k] = c;k = child;}// 将 x 值放到 k 位置queue[k] = x;}// 使用元素的的比较器进行比较, 然后完成元素上移操作private void siftDownComparable(int k, E x) {// 和上面的逻辑差不多Comparable<? super E> key = (Comparable<? super E>)x;int half = size >>> 1;   while (k < half) {int child = (k << 1) + 1; Object c = queue[child];int right = child + 1;if (right < size &&  ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)c = queue[child = right];if (key.compareTo((E) c) <= 0)break;queue[k] = c;k = child;}queue[k] = key;}
}

队列基本的操作只有入队和出队, 几乎没有查询的操作, 所以 PriorityQueue 的代码分析就到这里。

5 使用场景

ProfityQueue 在一些调度和算法中使用比较多

  1. 带有优先级任务的调度, 比如什么加急审批
  2. 负载均衡, 可以根据流量等因素进行优先级的调度
  3. 一些搜索算法

6 参考

PriorityQueue源码分析

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

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

相关文章

Java 泛型知识总结

1、基础知识 泛型是 JDK5 引入的参数化类型特性&#xff0c;所谓参数化类型就是把类型当成参数一样传递&#xff0c;它使得一个类型在定义类、接口和方法时成为一个参数&#xff0c;类似于方法声明中的形式参数&#xff0c;不同之处在于形式参数的输入是值&#xff0c;而类型形…

微机原理_5

一、单项选择题(本大题共15小题,每小题3分,共45分。在每小题给出的四个备选项中,选出一个正确的答案,请将选定的答案填涂在答题纸的相应位置上。) 8086微处理器CLK引脚输入时钟信号是由(提供。 A. 8284 B. 8288 C.8287 D. 8289 2.下面4个寄存器中,不能作为间接寻址的寄存器是(…

Java + openCV更换证件照背景色

最近在小红书上看到很多更换证件照背景色的需求&#xff0c;联想到以前自己也更换过证件照背景色而且还是付费的&#xff0c;碰巧最近在看一本书《JavaOpenCV高效入门》&#xff0c;于是查找资料&#xff0c;找到了通过技术解决这个需求的办法。 先看效果图&#xff08;图片来自…

62 权限提升-烂土豆dll劫持引号路径服务权限

目录 演示案例:Win2012-烂士豆配合令牌窃取提权-Web权限Win2012-DLL劫持提权应用配合MSF-Web权限Win2012-不安全的服务权限配合MSF-本地权限Win2012-不带引号服务路径配合MSF-Web&#xff0c;本地权限补充说明: dll劫持提权及AlwaysInstallElevated等说明关于Windows相关知识点…

yo!这里是异常相关介绍

目录 前言 异常的概念 异常的抛出与捕获 捕获过程 重新抛出 规范 异常体系 自定义 标准库 异常的优缺点 后记 前言 对于程序运行时发生的错误&#xff0c;比如内存错误、除0错误等类型&#xff0c;你会如何处理&#xff1f;是使用assert终止程序或是使用exit返回错误…

Linux系统---僵尸进程、孤儿进程

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 键盘敲烂&#xff0c;年薪百万&#xff01; 有了上一篇博客的学习&#xff0c;我们已经简单了解了进程的基础知识&#xff0c;今天我们再来学习两个特殊的进程&#xff0c;僵尸进程和孤儿进程。 …

7000字详解 动态代理(JDK动态代理 CGLIB动态代理)与静态代理

代理模式 1. 代理模式 概念2. 静态代理3. 动态代理3.1.JDK动态代理3.2.CGLIB动态代理3.3. JDK动态代理和CGLIB动态代理区别 4.静态代理和动态代理区别5.篇末 1. 代理模式 概念 代理模式是一种设计模式。 使用代理对象来替代真实对象&#xff0c;用代理对象去访问目标对象。这样…

虚拟化逻辑架构: LBR 网桥基础管理

目录 一、理论 1.Linux Bridge 二、实验 1.LBR 网桥管理 三、问题 1.Linux虚拟交换机如何增删 一、理论 1.Linux Bridge Linux Bridge&#xff08;网桥&#xff09;是用纯软件实现的虚拟交换机&#xff0c;有着和物理交换机相同的功能&#xff0c;例如二层交换&#…

百面深度学习-自然语言处理

自然语言处理 神经机器翻译模型经历了哪些主要的结构变化&#xff1f;分别解决了哪些问题&#xff1f; 神经机器翻译&#xff08;Neural Machine Translation, NMT&#xff09;是一种使用深度学习技术来实现自动翻译的方法。自从提出以来&#xff0c;NMT模型经历了几个重要的…

HTTP协议发展

HTTP 1.0 -> HTTP 1.1 -> HTTP 2.0 -> HTTP 3.0 (QUIC) 每一代HTTP解决了什么问题&#xff1f; 下图说明了主要功能。 HTTP 1.0 于 1996 年最终确定并完整记录。对同一服务器的每个请求都需要单独的 TCP 连接。 HTTP 1.1 于 1997 年发布。TCP 连接可以保持打开状态…

openGauss学习笔记-132 openGauss 数据库运维-查看openGauss状态

文章目录 openGauss学习笔记-132 openGauss 数据库运维-查看openGauss状态132.1 背景信息132.2 前提条件132.3 操作步骤132.4 参数说明132.5 示例 openGauss学习笔记-132 openGauss 数据库运维-查看openGauss状态 132.1 背景信息 openGauss支持查看整个openGauss的状态&#…

如何在Linux系统安装Nginx并启动

Nginx的介绍 Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。其特点是占有内存少&#xff0c;并发能力强&#xff0c;事实上nginx的并发能力在同类型的网页服务器中表现较好。官网&#xff1a;nginx newsNginx的下载 前往…

docker基础学习笔记

文章目录 Docker简介Linux下安装DockerDocker常用命令Docker网络Docker存储docker-composedockerfile制作镜像私有仓库镜像导入导出参考 Docker简介 定义&#xff1a;Docker是一个开源的应用容器引擎优势&#xff1a; 一键部署&#xff0c;开箱即用&#xff1a;容器使用基于im…

Qt5.15.2静态编译 VS2017 with static OpenSSL

几年前编译过一次Qt静态库:VS2015编译Qt5.7.0生成支持XP的静态库,再次编译,毫无压力。 一.环境 系统:Windows 10 专业版 64位 编译器:visual studio 2017 第三方工具:perl,ruby和python python用最新的3.x.x版本也是可以的 这三个工具都需要添加到环境变量,安装时勾选…

057-第三代软件开发-文件监视器

第三代软件开发-文件监视器 文章目录 第三代软件开发-文件监视器项目介绍文件监视器实现原理关于 QFileSystemWatcher实现代码 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&…

人工智能时代的内容写作

内容不再只是王道&#xff0c;正如俗话所说&#xff1a;它是一种流动的货币&#xff0c;推动了巨大的在线信息和影响力经济。 每个品牌都是一个故事&#xff0c;通过其服务和商品讲述自己。尽管如此&#xff0c;大多数客户还是会通过您的在线内容最了解您。 但随着我们进入人…

每日一题:LeetCode-LCR 143.子结构判断

每日一题系列&#xff08;day 05&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

汇编:关于栈的知识

1.入栈和出栈指令 2. SS与SP 3. 入栈与出栈 3.1 执行push ax ↑↑ 3.2 执行pop ax ↓↓ 3.3 栈顶超界的问题 4. 寄存器赋值 基于8086CPU编程时&#xff0c;可以将一段内存当作栈来使用。一个栈段最大可以设为64KB&#xff08;0-FFFFH&#xff09;。 1.入栈和出栈指令…

C语言——函数

导读 &#xff1a; 这篇文章主要讲解一下C语言函数的一些基本知识。 前言&#xff1a;函数的概念 C语言中的函数又常常被称为子程序&#xff0c;是用来完成某项特定的工作的一段代码。就像我们生活中的模块化建造技术&#xff0c;类比模块化建房子的过程&#xff1a;整个程序…

高校大学校园后勤移动报修系统 微信小程序uniapp+vue

本文主要是针对线下校园后勤移动报修传统管理方式中管理不便与效率低的缺点&#xff0c;将电子商务和计算机技术结合起来&#xff0c;开发出管理便捷&#xff0c;效率高的基于app的大学校园后勤移动报修app。该系统、操作简单、界面友好、易于管理和维护&#xff1b;而且对后勤…