资讯动态

栈与队列:从LIFO/FIFO原理到消息队列工程实践

发布时间:2026/10/27 10:54:03 来源:尧图企业网站定制
简介面向数据结构初学者与算法练习者的铁路车厢调度编程题解包围绕“丁”字型双轨调度场景要求借助栈与队列思想把任意入站序列调整为 1n 顺序出站。压缩包共 9 个文件约 128KB以 C 源码和头文件为核心配合 Dev-C 工程、Makefile 配置、目标文件及可执行程序便于直接编译运行、调试与对照实现。资源已吸引 1939 人学习下载适合课程实验、课后作业和栈队应用练习。包内给出完整调度程序和链栈、链队列等数据结构定义读者可据此梳理入站、辅助轨暂存、出站判定与序列合法性检查思路也能参考工程组织方式完成自己的实验报告与代码改造。1. 列车进站那一刻栈和队列到底在管什么坐过老式火车的人大概见过两种站台。一种是尽头式铁轨铺到站台尽头就断了车开进去只能原路倒出来另一种是穿堂式车从这头进、那头出谁先到谁先走。前者是栈后者是队列。这两个词听着像计算机系的黑话说的却是一件极小的事一批数据排在那里你只允许从固定的一两个口子取放取放的顺序就被结构本身定死了。搞懂这一点函数为什么能一层层返回来、打印任务为什么不会乱序、消息中间件为什么能扛住洪峰就都能顺下去。栈和队列是数据结构里最基础的两块砖也是实际写代码时最容易踩坑的地方。2. 栈为什么后进先出不是反直觉而是最贴合现场的规则2.1 一个只能单边进出的死胡同就是栈的原型把镜头拉近到那条尽头式铁轨上。列车一列接一列往里开第一节顶到最里头的车挡停下第二节挂在它后面第三节再挂上去最后一节停在最外头。这时候如果有人要开走最里面那列车怎么办没别的办法只能先把外面挡路的车一列列倒出去让出通道最里面那列才动得了。进去的顺序是 A、B、C出来的顺序必然是 C、B、A。这个「后进去的先出来」不是谁故意设计的怪规矩而是「只有一个口、另一头封死」这个物理条件推出来的唯一结果。栈就是这个死胡同。它是一张线性表但被加了一条限制只允许在一端插入和删除。允许操作的这一端叫栈顶封死不动的那一端叫栈底。往栈顶放元素叫压栈或入栈英文 push从栈顶取元素叫弹栈或出栈英文 pop只看栈顶是谁但不取走叫取栈顶英文 peek 或 top。三个动作合起来构成栈的全部对外接口。英文里把这条规则缩写成 LIFOLast In First Out后进先出。日常里更熟悉的版本是一摞盘子。洗好的盘子一只只摞上去用的时候从最上面拿。你不可能从中间抽一只出来而不把上面的先搬走也不会有人从最底下开始用。这个动作太自然自然到没人觉得它是一种「数据结构」。但恰恰是这种自然说明后进先出不是反直觉而是符合直觉的当你要处理的东西天然堆叠在一起最近放上去的那一个本来就应该最先被拿到。所以判断一个场景适不适合用栈不需要背定义只需要问一句这批东西是不是从同一个口放进去、又从同一个口取出来如果是顺序自动就是后进先出。栈的价值不在于它「怪」而在于它用一条极简的限制换来了一个极其明确的顺序保证。限制越少的结构越灵活但也越容易用错限制越多的结构越死板但用对了地方就几乎不会出错。栈是后者的典型。2.2 push 和 pop 背后栈顶、栈底、上溢与下溢栈的操作少得可怜但每一个都有明确的边界条件。压栈、弹栈、取栈顶、判空、求元素个数这五个动作的时间复杂度全是 O(1)。原因很简单所有操作只碰栈顶那一个位置不需要移动其他元素也不需要遍历。这正是栈在性能上讨人喜欢的地方——无论里面存了一百个还是一百万个元素压和弹都是一步到位。代价是两条必须处理的异常。栈满了还继续压叫上溢栈空了还继续弹叫下溢。这两个词在工程里出现频率极高因为几乎所有跟栈有关的崩溃追到根上都是这两个之一。写代码时把这两个判断漏掉程序在测试环境跑一万次都没事上线遇到边界数据就翻车这种血泪经验在栈这种结构上格外常见。实现一个栈有两条路。顺序栈用数组内存连续缓存友好实现最简单缺点是容量固定装满了要么报错要么扩容拷贝。链栈用链表按需增长理论上不设上限缺点是每个节点多一个指针开销内存不连续遍历时缓存命中率差一些。选哪个看的是「容量能不能预估」和「对内存布局敏不敏感」这两件事。class ArrayStack: def __init__(self, capacity8): # capacity 是栈能容纳的元素上限数组按这个上限一次性开好 self._data [None] * capacity self._capacity capacity self._top -1 # 栈顶指针-1 表示当前是空栈 def push(self, item): if self._top 1 self._capacity: raise OverflowError(栈满不能再压入) # 上溢 self._top 1 self._data[self._top] item def pop(self): if self._top -1: raise IndexError(栈空没有元素可弹出) # 下溢 value self._data[self._top] self._top - 1 return value def peek(self): if self._top -1: raise IndexError(栈空) return self._data[self._top]这段代码里最值得看的是_top这个变量的约定它指向当前栈顶元素的下标空栈时取 -1。压栈时先判满、再自增、再写入弹栈时先判空、再取值、再自减。顺序一旦写反就会出现「先写后判」把已有元素覆盖掉的低级错误。capacity是容量上限决定了数组开多大也决定了上溢什么时候发生。这里有个初学者常踩的坑栈顶指针的约定有两种流派一种指向栈顶元素本身一种指向下一个可写入的空位。两种写法都对但判空和判满的条件完全不同。混着用代码看起来没毛病跑起来元素总是差一个。写之前先定死一种约定注释写清楚比事后调试省事得多。2.3 函数调用栈程序里最沉默的黑匣子栈在真实系统里最重量级的应用是函数调用。每次调用一个函数运行环境会在调用栈上压入一个栈帧里面放这次调用的参数、局部变量和返回地址。函数执行完栈帧弹出控制权顺着返回地址回到调用它的那一行。嵌套调用就是连续压栈最内层的函数在栈顶最外层的入口在栈底。这解释了一个很多人似懂非懂的现象局部变量为什么在函数返回后就没了因为它的内存就在栈帧里栈帧一弹那块空间就被回收了。也解释了递归为什么危险递归是自己调自己每深入一层就压一个栈帧终止条件写错或者层数太深栈帧只增不减最终把调用栈的空间耗尽程序直接崩掉。这个现象的名字就叫栈溢出。调试的时候报错信息里那一长串从内到外的函数名就是调用栈的快照。读它的正确姿势是从下往上看最下面是程序入口越往上越是靠近出事地点的调用。很多人习惯从上往下读读半天不知道自己在哪一层其实顺序反了。栈在这里扮演的是黑匣子的角色——平时你完全感知不到它的存在出了问题它却记录了完整的来路。栈还是天然的回溯结构。走迷宫每到一个岔口就把当前位置压栈选一条路往前走走进死胡同就弹栈回到上一个岔口换一条。这个过程不需要记住所有走过的路只需要记住「最近的那个岔口」而后进先出恰好保证了最近的那个总在栈顶。深度优先搜索用的就是这个思路一条路走到黑走不通再退退的顺序和进的顺序正好相反。3. 队列为什么先来后到是最省心的公平3.1 穿堂式车站与售票窗口两个口一条队换一种站台。铁轨从这一头穿到那一头列车从东边进站停靠再从西边开走。先进站的车先出站后进站的车排在后面。没有谁被堵死也没有谁需要倒车。这就是队列的物理原型两个口一端只进一端只出。队列是一张线性表但限制条件换成了只允许在一端插入在另一端删除。插入的那一端叫队尾删除的那一端叫队头。往队尾放元素叫入队从队头取元素叫出队。规则缩写成 FIFOFirst In First Out先进先出。生活里最标准的例子是售票窗口前的那条队后来的人站到队尾窗口每处理完一个人队头的人往前走一步谁也不会因为站得久就被插到后面去。队列通常维护两个指针。队头指针指向下一个要出队的元素队尾指针指向下一个可以写入的位置。入队动队尾出队动队头两边互不干扰所以入队和出队都是 O(1)。这一点和栈一样区别只在于栈的两个动作都发生在同一端队列的两个动作分居两端。这个「两个口」的物理差异决定了后面所有的行为差异。栈的顺序是被封死的另一头逼出来的队列的顺序是被两个口之间的单向流动定下来的。判断场景该用谁问的就是那句话这批数据是从一个口进出还是从一头进、另一头出需要原路返回的用栈需要排队往前走的用队列。队列保证的是顺序公平不是优先级公平。先到的先被处理仅此而已。如果任务有轻重缓急先来后到反而会误事——急诊病人不该排在普通门诊后面。这时候要的不是队列而是优先队列那是另一套规则后面单独说。3.2 循环队列数组实现里最容易翻车的地方用数组实现队列第一个冒出来的笨办法是每次出队把后面所有元素整体往前挪一格。逻辑上完全正确性能上完全不能接受因为出队从 O(1) 退化成了 O(n)。队列里存十万条任务每取一条就搬十万次这种写法在生产环境里是灾难。正常的做法是不挪元素只挪指针。队头指针往后走队尾指针往后走前面的空位就空着。问题随之而来队尾指针走到数组末尾就没地方去了可数组前面明明还空着一大片。数组没满但按指针的位置看已经不能入队了。这个现象叫假溢出是循环队列这一节的核心。解法是把数组看成一个环指针走到末尾就绕回开头用取模运算实现。队尾前进一格写成(rear 1) % capacity队头同理。这样一来空出来的位置能被重新利用空间利用率回到满格。绕回开头又带来一个新麻烦队头指针和队尾指针相等的时候到底是空还是满两种状态长得一模一样。业界有三种成熟解法。第一种是牺牲一个存储单元人为规定队列最多存 capacity - 1 个元素这样满的条件是(rear 1) % capacity front空的条件是front rear两者不再冲突。第二种是额外维护一个元素计数器空看计数为 0满看计数等于容量。第三种是加一个标志位记录最后一次操作是入队还是出队。class CircularQueue: def __init__(self, capacity8): # 多开一个单元用来区分「空」和「满」这两种指针相等的情况 self._data [None] * (capacity 1) self._cap capacity 1 self._front 0 # 队头下标指向当前队头元素 self._rear 0 # 队尾下标指向下一个可写入的空位 def enqueue(self, item): if (self._rear 1) % self._cap self._front: raise OverflowError(队满) # 牺牲单元法判满 self._data[self._rear] item self._rear (self._rear 1) % self._cap def dequeue(self): if self._front self._rear: raise IndexError(队空) # 指针相等即空 value self._data[self._front] self._front (self._front 1) % self._cap return value参数上要留意用户传入的capacity是「希望队列最多存多少个元素」实际数组开的是capacity 1多出来的那一格永远不存数据专门用来让判满条件和判空条件错开。所以这个队列实际能存的上限是capacity个元素而不是capacity 1。把这两个数字搞混是循环队列最经典的翻车点——测试时存八个没事存第九个报满回头查半天以为是取模写错了。取模公式本身也容易写反。判满用的是「队尾再走一格会不会撞上队头」判空用的是「队头和队尾是不是已经重合」。两句话一字之差条件完全相反。写的时候先在纸上画一个长度 5 的环手动走一遍入队出队比盯着屏幕改半天有效。循环队列的意义在于用固定空间换稳定性能。空间不增长操作恒定 O(1)没有内存分配抖动。嵌入式设备、网络收包缓冲、日志采集管道里这类结构随处可见。3.3 消息队列和缓冲区队列在工程里的真身队列在工程里最广为人知的应用是消息队列。生产方把消息放进队尾消费方从队头取走处理两边互不等待。这个模型解决的是速度不匹配的问题生产方可能一瞬间涌出十万条请求消费方一秒钟只能处理一千条。如果没有队列在中间缓冲生产方要么被拖慢要么直接把消费方压垮。有了队列洪峰先堆在里面后端按自己的能力匀速消费这就是常说的削峰填谷。队列在这里还顺手做了一件事解耦。生产方不需要知道消费方是谁、在哪、用的是什么技术它只管往队列里放。消费方也不需要知道消息从哪来它只管从队列里取。两边通过队列这个中间层间接通信任何一方换实现、加机器、临时下线另一方都不受直接影响。缓冲区是同一套逻辑的另一个名字。键盘敲击的字符先进入输入缓冲程序有空了再读网卡收到的数据包先进入接收缓冲协议栈按自己的节奏处理写日志时先攒在内存队列里攒够一批再落盘。这些场景的共同点是上游的产出速度和下游的消费速度对不上中间需要一个能暂存、能保序的地方。队列正好满足这两条。队列也有变种。双端队列允许两端都能入队和出队比普通队列灵活。优先队列不按到达时间出队按优先级出队名字里带队列规则其实完全不同。阻塞队列在队空时让取操作等待、队满时让放操作等待是并发编程里的常用件。这些变种各有各的适用面但底层的「一头进另一头出」的思路没有变。有一个参数需要认真对待队列长度。很多人下意识觉得队列越长越安全其实不是。队列越长消息在里面的等待时间越久端到端的延迟越大而且队列里的消息最终还是要被处理积压只是把问题往后推不是消灭问题。队列满了之后只有两条路要么丢数据要么阻塞生产方两条都需要提前想清楚。这个长度定多少取决于能容忍多大延迟、能承受多少丢失是个业务问题不是技术问题。4. 易混概念盘点栈、队列、双端队列、优先队列到底差在哪4.1 易混点一栈和队列的差别不是「顺序不同」最容易混的地方是把栈和队列的差别记成「一个后进先出、一个先进先出」然后背下来就完事。这样记的结果是真到选型的时候还是不知道用哪个因为两条规则看起来都像是一种偏好好像随便选一个都能跑。为什么会混因为定义确实是在描述顺序顺序又是最显眼的特征。但顺序是结果不是原因。真正的原因在物理结构上栈只有一个口队列有两个口。一个口意味着你放进去的东西必然挡在之前所有东西的前面取的时候只能先取它后进先出是被逼出来的。两个口意味着进入和离开走的是不同通道谁也不挡谁先进先出是自然流动的结果。怎么区分不要问「我想要什么顺序」要问「这批数据的进出通道长什么样」。需要原路返回、需要回溯、需要最近优先通道就是一个口用栈。需要排队推进、需要保序、需要一头进另一头出通道就是两个口用队列。顺序是选完之后自动得到的不是选之前需要纠结的。4.2 易混点二优先队列名字里有队列规则却不是先来后到优先队列听起来像是「更高级的队列」很多人默认它还是先进先出只是性能更好或者功能更多。实际完全不是。优先队列出队的依据是优先级不是到达时间。谁优先级高谁先走哪怕它是最后一秒才进来的。为什么会混名字带队列对外接口也像——放进去、取出来两个动作。再加上大多数教材把它排在队列后面讲容易让人觉得是同一个东西的升级版。怎么区分看「谁先出」这个问题的答案。普通队列的答案是「来得最早的」优先队列的答案是「优先级最高的」。进来的顺序对优先队列毫无影响。实现上也不一样普通队列用数组或链表就能做优先队列通常要用堆。适用场景也分得很开打印任务排队用普通队列急诊分诊、紧急工单插队、最短路径算法里「先扩展代价最小的节点」用优先队列。一句话概括队列是排队优先队列是理直气壮地插队而且插队规则是提前定好的。4.3 易混点三双端队列不是队列的加强版而是栈和队列的共同母集双端队列两端都能入队、两端都能出队限制比普通队列少得多。很多人把它当成「队列多开了一个口」觉得只是方便了一点。实际上它的地位更高栈和队列都是它的特例。为什么会混因为名字里带队列又是在队列之后学的很容易被归为同一类东西的不同版本。怎么区分看限制条件。只允许一端进出就是栈只允许一端进、另一端出就是队列两端都自由就是双端队列。限制越少能力越强但「顺序保证」也越弱。用双端队列的时候你得自己想清楚每一步该从哪端操作结构本身不再替你兜底。需要同时从两端操作的场景才值得用它比如滑动窗口求最大值、判断回文串普通排队和回溯用不上。记住这条包含关系双端队列是母集栈和队列是它的两个受限子集。选型时先想清楚需要几条通道、通道之间是否连通答案自然就出来了。4.4 易混点四「栈」是抽象规则还是内存里那块区域听到「栈溢出」「栈内存」「调用栈」不少人会愣一下这跟数据结构课上讲的栈是一回事吗同一个词被用在两个层面容易让人以为是两个毫不相干的概念撞了名字。为什么会混因为抽象规则和具体内存布局确实不在一个讨论层级上。一个是逻辑模型一个是物理实现。怎么区分数据结构里的栈是规则后进先出只在一端操作。调用栈是这套规则在内存里的一个具体落地——函数调用确实按后进先出的顺序压入栈帧、弹出栈帧规则完全一致。它们不是同名巧合是同一个思想在两个层面的体现。真正需要小心的是「堆」这个词数据结构里的堆是优先队列的实现基础内存管理里的堆是另一块区域这两个才是真的容易混跟栈没有关系。看到「栈」先看上下文。在讲算法和抽象的时候说的是规则在讲程序运行、内存布局、报错信息的时候说的是那块具体区域。分清楚了很多术语就不再打架。4.5 易混点五循环队列的「假溢出」不是真的溢出数组实现的队列里队尾指针走到数组末尾前面却还空着一片这时候看起来「装不下了」其实元素个数远没到容量上限。这个现象叫假溢出。为什么会混因为从指针的位置看确实到头了直觉上就该报满。很多人第一次遇到直接去扩容数组扩完发现还是报满因为问题根本不在容量上。怎么区分判断队列满不满看的是有效元素个数不是队尾指针的绝对位置。取模之后队尾会绕回开头前面的空位可以被重新利用所以指针到末尾不等于队列满。真正的溢出是元素个数达到了容量上限那时候无论怎么绕都没有空位了。写循环队列时把判满条件写对假溢出就不会发生判满条件写错真溢出和假溢出会混在一起排查起来格外费劲。5. 同一批数据换种结构就是另一种解法几个能对照的场景5.1 括号匹配与表达式求值栈在「配对」上的天然优势括号匹配是栈最经典的教学例子也是最经得起推敲的一个。给你一串字符里面有圆括号、方括号、花括号判断它们是否成对闭合、嵌套是否正确。人的眼睛扫一遍大概能看出来但要让程序判断就得有个结构记住「哪些左括号还没等到右括号」。为什么是栈因为括号的闭合规则天然是后进先出最近打开的那个括号必须最先闭合。{[()]}是合法的{[(])}不合法原因就是方括号还没闭合圆括号就先关了违背了「最近打开的最近关闭」。这个规则和栈的后进先出一模一样。def brackets_match(text): pairs {): (, ]: [, }: {} stack [] for ch in text: if ch in ([{: stack.append(ch) # 左括号一律压栈等待配对 elif ch in pairs: if not stack: # 栈空却遇到右括号多了一个右括号 return False if stack.pop() ! pairs[ch]: # 弹出的必须是同类型的左括号 return False return not stack # 全部扫完栈必须为空否则说明还有左括号没闭合stack里存的是尚未闭合的左括号pairs把每种右括号映射到它对应的左括号循环结束后用not stack判断是否「不多不少正好配完」。时间复杂度 O(n)每个字符最多进出栈一次空间复杂度 O(n)最坏情况全是左括号。这个思路可以直接搬去检查标签闭合、代码块嵌套、配置文件里的层级结构规则完全一样。表达式求值是另一个例子。人写的中缀表达式3 4 * 2计算机不好直接算因为要处理优先级和括号。常规做法是先转成后缀表达式再用一个栈求值遇到数字就压栈遇到运算符就弹出两个数算完再压回去。转换过程本身也要用栈运算符入栈前要把优先级不低于它的先弹出来。这两步的核心动作都是「最近的先处理」所以都落在栈上。5.2 撤销与重做两个栈配合出的后悔药文本编辑器的撤销和重做是栈在交互软件里最贴近用户的落地。只用一个栈能做到撤销但做不到重做因为撤销掉的操作已经被弹出丢弃了想恢复回来没有地方找。要支持重做就得用两个栈。规则很清楚用户执行一个新操作把它压进撤销栈同时清空重做栈用户点撤销从撤销栈弹出一个操作执行它的逆操作再把它压进重做栈用户点重做从重做栈弹出一个操作重新执行再压回撤销栈。两个栈像两个候车区操作在中间来回搬。为什么必须清空重做栈这是很多人第一次实现时踩的坑。假设你做了 A、B、C 三步撤销两次现在撤销栈里剩 A重做栈里有 C、B。这时候如果你执行一个新操作 D历史就分叉了原来的重做路径 B、C 是在「A 之后」的假设下成立的现在变成「A、D 之后」B 和 C 已经接不上了。不清空的话用户点重做会得到一个逻辑上不存在的状态。这个细节说明一件事结构本身很简单难的是想清楚状态之间的转换规则。栈只负责保存顺序什么时候清空、什么时候搬运是设计者要定的业务规则。规则定错结构再好也救不回来。5.3 广度优先搜索与任务调度队列保证的层序与公平同样是遍历一张图用栈和用队列会得到两种完全不同的结果。用栈是深度优先从起点出发选中一个邻居就一头扎进去走到没路了再退回来换下一个。用队列是广度优先从起点出发先把所有直接邻居都放进队列然后依次取出来每取出一个就把它的邻居追加到队尾于是一层一层往外扩。差别来自取用顺序。栈取的是最近放进去的所以总是沿着一条路往深里走队列取的是最早放进去的所以总是先把同一层的处理完再往下。同一个图、同一份数据换个结构就是另一种解法这个对照很能说明「结构决定行为」这句话。任务调度是队列的另一个主场。最朴素的策略叫先来先服务谁先提交谁先执行用队列实现规则简单到不会出错。打印队列、批处理作业、请求排队用的都是这一套。它的优点是可预测每个任务的等待时间大致等于前面所有任务的耗时之和不会有任务被无限期跳过。缺点也很明显一个耗时长的大任务堵在队头后面的短任务全得跟着等哪怕它们几毫秒就能跑完。这个现象在调度里有个专门的名字叫护航效应。解决办法有几个方向给任务分配时间片轮流执行或者按优先级排序改用优先队列或者按预估耗时排短作业优先。每一种都是在「公平」和「效率」之间重新画一条线没有免费的午餐。选结构的时候先想清楚要的是哪一种顺序保证。要层序推进队列要一条路走到底栈要轻重缓急优先队列。想清楚了代码几乎是顺着写下来的。6. 什么时候该用栈或队列什么时候别硬套6.1 一张判断表选型这件事落到最后就是几个问题的答案。你需要的保证该选谁原因最近处理的要最先回溯栈单口进出后进先出天然匹配先到的必须先处理队列双口单向流动顺序即到达顺序按层推进、逐层扩散队列先入队的先扩展天然分层嵌套结构的配对与闭合栈最近打开的最先关闭任务有轻重缓急优先队列按优先级出队与到达时间无关两端都要操作双端队列限制最少但顺序保证也最弱6.2 三个「别硬套」的信号第一种信号需要随机访问中间的元素。栈和队列都只开放端点想取中间那一条只能把上面的全弹出来。这种需求该用数组或哈希表硬套栈队列只会让代码绕成一团。第二种信号任务有优先级。用普通队列处理带优先级的任务等于假装优先级不存在紧急任务会被普通任务堵在后面。这种场景该换优先队列而不是把队列长度调大或者加几个线程。第三种信号只顾着用结构没想过队列会满。队列不是无限的长度是必须提前定的参数。定太短洪峰一来就丢数据定太长延迟高到用户能感知。这个数字背后是业务对延迟和丢失的容忍度不是拍脑袋能定的。6.3 一个具体的收尾举个很小的例子。某次做一个日志收集的小模块一开始图省事所有日志先进一个无界队列后台线程慢慢消费。测试环境跑得很欢上了真实流量之后内存一路涨因为生产速度长期高于消费速度队列只进不出。后来改成有界队列加满了就丢弃低级别日志内存稳住了关键日志也没丢。这个翻车点的教训不在于队列本身而在于「无界」这两个字。栈和队列都是很老的结构老到让人觉得没什么可想的但真正出事的地方往往不是结构选错而是边界没定、满了怎么办没想。选结构只是第一步容量、满了之后的行为、超时怎么处理这些才是让结构在真实系统里站得住的东西。从那以后我每次设计一条数据流都先问自己两句这批东西是从一个口进出还是两个口是最近的要先处理还是先到的要先处理问完这两个问题选栈还是选队列基本就定了剩下的只是实现细节。希望帮到你。这份资料沿着列车进站这条线把两种结构从站台类比、基本操作、数组与链表实现一路串到括号匹配、撤销重做、消息缓冲几个真实场景想顺着走一遍的话可以直接获取。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑