简介:本资源是一套面向LabVIEW初学者与工程实践者的完整学习案例集,涵盖数据采集、仪器控制、人机界面设计及Web发布等典型应用场景,适用于高校实验教学、自动化项目开发与NI平台技能提升。压缩包共167个文件,主体为154个可直接运行的VI源代码(含拨号盘、指示表等自定义控件CTL),辅以6个HTML网页(LVweb系列)用于Web发布演示,另有位图图像、RTM配置文件等支撑素材,整体容量达412.53MB,结构清晰、模块独立,便于按功能分类学习与复用。目前已有366人下载学习,案例覆盖从基础控件使用到Web交互集成的完整技术链,提供即开即用的工程化参考模板,尤其适合缺乏真实项目经验的学习者快速建立LabVIEW开发逻辑与调试思路。 以前在工控论坛上下载过一个“labview例子源代码全集”压缩包,里面上百个vi,当时我以为拿到手就能起飞,结果光是把主程序跑起来就折腾了大半个下午。后来才慢慢明白,LabVIEW和C语言不一样,代码是图形化的,你不把数据流捋一遍,光看文件名根本猜不出它想干什么。所以这篇专门聊聊LabVIEW例子源代码到底怎么用:从版本判断、目录结构、程序框图拆解,到秒表小程序、串口通信、数据采集这些高频例程,再到把示例代码改造成能上线的工程,以及运行过程中经常翻车的细节。内容偏实操,适合刚接触LabVIEW的工程师,也适合那些手里存着不少例程但始终没法用到项目里的朋友。
1. 拿到例子源代码后,别双击就跑:先看版本、目录和顶层VI
一个合格的例程包,打开之前的检查比打开本身更重要。很多例程跑不起来的真实原因不是代码写错,而是版本不匹配、文件路径变了、依赖的子VI丢了。这一节说清楚拿到源码后第一步该干什么。
1.1 版本兼容性是第一个门槛
LabVIEW每代版本的vi文件后缀都是.vi,看起来一样,但内部格式不通用。高版本LabVIEW能打开低版本vi,并且会弹一个升级提示;反过来低版本打开高版本就会直接报错,提示VI是在更高版本中创建的。热词里出现“labview安装错误”“labview安装路径”,很多情况下都是因为装了新版本卸载不干净,或者同时装了多个版本导致组件错乱。
我的本机长期同时存在两个LabVIEW版本,一个老版本用于处理旧例程,一个新版本用于开发当前项目。注意不要装在同一路径,否则部分共用组件会被覆盖,最好先装旧版再装新版。我更推荐用虚拟机装一个旧版LabVIEW,比如2015或2018,专门用来开那些老例程,这样不会影响主机的开发环境。
拿到例程后,怎么快速判断它是什么版本?方法很简单,打开LabVIEW后选择File -> Open,如果弹出来的对话框里有“Save in a newer version”之类的提示,说明当前LabVIEW比例程版本新,可以直接转换;如果直接报错说版本太新,那就得找更高版本的LabVIEW来打开。
1.2 先摸清目录结构,再找顶层VI
一个完整的例程包通常包含多个vi,其中有一个入口,也就是顶层VI,一般带有Main、Example、Demo这类名字,或者放在根目录。除入口外还有一堆子VI,可能放在subvi文件夹或子目录里,也可能整包打包进llb。很多人习惯把所有vi全部拖进LabVIEW打开,结果是满屏窗口,谁也看不懂。
正确做法是先看包里的readme或者说明文档,没有文档的话就打开根目录下的主vi。判断顶层VI的另一个技巧是看项目视图或llb的排列顺序,一般排在最前面的就是入口。打开入口VI后,先用Ctrl+E在前面板和程序框图之间切换,大致的代码结构立刻就能看出来。
在这里额外提一个习惯:把例程复制到自己的项目目录后再打开,不要在压缩包内直接打开。因为LabVIEW运行子VI时会按绝对路径找依赖,压缩包内的路径经常出问题,会导致子VI找不到或加载失败。
1.3 在前面板上看输入输出,再进框图拆数据流
例程源码最大的价值在框图,但一进框图看到上百个节点也很容易懵。我的顺序是:先看前面板,搞清楚这个程序要接收什么输入、输出什么结果。比如秒表小程序,前面板上一定有开始、停止、复位按钮和一个时间显示控件;串口通信例程一定有一个串口号下拉框和接收区显示框。这些控件对应的就是框图里的数据源头和最终去向。
看完前面板再进框图,先找数据流主干:输入控件接到了哪个函数,函数输出又接到了哪个显示控件,中间有没有循环、事件结构、条件结构。这样拆出来的代码,逻辑主线是清晰的。不要一上来就去抠某个子VI的内部实现,那是后面的事。
LabVIEW图形化代码和文本代码不同,它没有从上到下的固定阅读顺序,而是数据流驱动。哪个节点有输入数据,哪个节点就开始执行。所以看懂一个VI的关键是找到数据流从哪来、到哪去,而不是按行读。
1.4 用高亮执行和探针把代码“跑在脑子里”
LabVIEW工具栏上有个灯泡图标,叫高亮执行(Execution Highlighting),打开后重新运行程序,框图上就会出现一个个小气泡沿连线流动,表示数据正在传递。这是理解例程逻辑的最好工具。配合探针使用效果更佳:在任意一条连线上右键,选择Probe,就能实时看到该线上流动的数据值。
我的习惯是给关键节点设置探针,再打开高亮执行,比如看串口读出来的字符串、看时间函数的差值、看数组的大小。这样例程的实际运行过程就完全暴露在眼前。不过要注意,高亮执行会让程序慢很多,循环次数多的例程不要长时间开着,看到关键逻辑就可以关掉。
顺带提一个例程调试中很常用的小技巧:循环里如果有“等待(ms)”节点,把它改成“等待下一个整数倍毫秒”节点,可以让循环节拍更稳定,很多时间相关的例程里都有这个细节。
2. 从秒表小程序到串口通信:四条高频例程源码的拆解思路
这一节直接拆解几个LabVIEW里最常见的例程类型。秒表、串口、Modbus、UDP,这些是学习阶段的必修课,也是很多上位机项目的底层通信模板。理解了它们的代码结构,再看其他例程就会轻松很多。
2.1 秒表小程序:事件循环和时间定标
秒表小程序是很多人的第一个完整LabVIEW例程,看起来简单,但能把事件结构、时间函数、循环条件全串起来。框图核心由三部分组成:一个While循环、一个事件结构、一组时间函数。
具体逻辑是这样:前面板有开始、停止、复位三个按钮和一个数字显示框。点击“开始”按钮时,用Tick Count(ms)节点读取当前系统时间,存为起点T0;在While循环里,不断用当前的Tick Count值减去T0,得到一个毫秒数,再换算成秒、分显示出来。点击“停止”时读取当前时间T1,保存累计时长;点击“复位”时清零所有状态。
这里有个很容易忽略的点:为什么不直接在While循环里加一个“等待(ms)”节点来计时?因为等待节点的计时精度低,而且它只是让循环按固定周期运行,并不代表精确的运行时长。Tick Count读的是系统时钟,精度高得多,适合做秒表这类时间统计。
另一个关键设计是用事件结构还是轮询方式。传统例程喜欢用轮询,也就是循环里不断检查按钮状态,这会导致CPU占用高、响应滞后。工程上推荐用事件结构,only在按钮被点击时触发一段代码,循环空闲时不做无意义操作。秒表例程虽然简单,但把“事件驱动”这个思想体现得很完整。
2.2 串口通信例程:VISA函数链就是一套标准流程
串口通信是LabVIEW上位机最常用的功能。例程里的函数链一般长这样:VISA资源名称配置串口参数,VISA Write发送数据,VISA Read读取数据,VISA Close关闭串口。
拆解这段代码时,重点看两个细节。第一,VISA Configure Serial Port节点上有波特率、数据位、停止位、校验位四个参数,这些必须和设备端完全一致,否则收发全是乱码。第二,VISA Read节点需要指定读取字节数,很多新手不知道设备会返回多长数据,随便填个数字,结果不是读不完整就是卡住。
常见的工程做法是:在读取之前,先用VISA属性节点查询当前串口缓冲区里有多少字节(Number of Bytes at Serial Port),再把该值传给VISA Read去读。这样无论设备返回多长数据都能完整读出来。例程里如果没写这一步,说明只是个演示demo,拿到项目里得自己补上。
还有一个必须注意的细节:串口打开后一定要关闭,否则程序退出后串口会被一直占用,导致下次运行报“串口被占用”错误。很多例程为了省事不写VISA Close,这属于教学简化,实际项目必须做。
2.3 Modbus RTU通信例程:帧结构比库API更重要
热词里“labview modbus rtu”和“labview与汇川plc通讯”经常一起出现。Modbus RTU是工业控制领域最常见的一种协议,底层走的就是串口,所以串口通信例程看懂后,Modbus就是在其上多加了一层报文解析。
Modbus RTU例程常见实现有两种:一种是直接用NI或第三方Modbus库,调用几个API就能读写寄存器,优点是代码简单;另一种是通过VISA底层自己拼帧,使用CRC校验,优点是灵活、不依赖库,但代码量大很多。我建议初学者至少看懂第二种方式的例程,因为只有理解了帧结构,出了问题才知道怎么排查。
一个典型的Modbus RTU读保持寄存器请求帧是:设备地址、功能码03、起始寄存器地址高字节、起始寄存器地址低字节、寄存器数量高字节、寄存器数量低字节、CRC校验低字节、CRC校验高字节。比如读地址为01的设备,起始地址0000,读2个寄存器,报文就是01 03 00 00 00 02 C4 0B。例程里的CRC子VI负责生成最后的校验字节,这个模块不建议自己重写,拿现成验证过的即可。
帧结构可以直接用下面这个表来对照解析:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 从站地址 | 1 | 目标设备地址,例如01 |
| 功能码 | 1 | 03表示读保持寄存器,06表示写单个寄存器 |
| 起始地址 | 2 | 要读取的寄存器起始地址,高字节在前 |
| 寄存器数量 | 2 | 要读取的寄存器个数,高字节在前 |
| CRC校验 | 2 | 对整个报文做CRC16校验,低字节在前 |
看例程时多关注寄存器地址换算。PLC和仪表的寄存器地址经常是十进制,而报文里是十六进制,这中间差了地址偏移,搞错一个字节,设备就不响应。
2.4 UDP通信例程:无连接但要走对数据格式
“labview udp通信”例程一般包含UDP Open、UDP Write、UDP Read、UDP Close这几个节点。UDP和TCP最大的区别是无连接,发送端只管把数据包丢出去,不管对端是否收到。好处是速度快、开销小,坏处是丢包了不会自动重传。
例程拆解重点有三处。第一,端口号必须双方约好,发送端发到指定IP和端口,接收端在同一个端口上监听。第二,报文字节序要约定好,尤其传输数值时,必须明确是大端还是小端,否则接收端解析出的数字会颠倒。第三,UDP Read节点的max size参数要设置得比实际报文大,否则太长报文会被截断。
如果要在项目里做数据可靠性要求高的UDP传输,建议在例程基础上加一层简单的协议:报文头带序号,接收端发现序号不连续就请求重发。这个思路很多源码例程不会写,但实际调试时非常实用。
3. 从例程到工程:数据采集、压力曲线与状态机改造
很多例程的问题是“能跑通,但不适合工程用”。比如数据采集例程通常只做一次性采集,把数据画到图表上就完事;真正的压力曲线采集需要连续循环、实时存储、异常处理。这一节讲怎么把示例代码改造成工程项目可用的结构。
3.1 数据采集例程不能止步于“一次性采集”
热词“labview数据采集”和“labview压力曲线采集”是高频需求。例程里的DAQmx函数链通常是:DAQmx Create Virtual Channel创建通道,DAQmx Timing设置采样率,DAQmx Start启动任务,DAQmx Read读取数据,DAQmx Clear清除任务。这套流程本身没问题,但教学例程往往只做一次读取。
压力曲线采集的真实需求更接近连续采集:硬件端按固定采样率不断产生数据,软件端在循环里定期读取一批样本,同时把数据写入文件,并往前端界面推送实时曲线。这时代码结构就变成:循环里反复调用DAQmx Read,每读一次就更新波形图表,同时写入TDMS文件。
采样率的设置要结合信号带宽来定,不是越高越好。根据采样定理,采样率至少是信号最高频率的两倍,工程上通常取5到10倍。比如压力信号的最高频率分量估计在100Hz以内,那采样率设在1kHz左右就够。采样率设太高,缓冲区压力大,数据落盘也跟不上,反而容易出问题。
3.2 波形图表与波形图的选择:很多人搞反了
LabVIEW里有两个名字很像的控件:波形图表(Waveform Chart)和波形图(Waveform Graph)。例程里混用它们的情况非常多,但两者的行为逻辑完全不同。
波形图表是滚动显示,新数据来了就自动往右推进,老数据滚出画面,适合实时趋势监控,比如压力实时显示;波形图是一次性把所有点画完,适合查看一段完整的历史波形,比如分析一次实验记录。拆解数据采集例程时,第一件事就是确认代码里用的是Chart还是Graph,如果需求是历史波形回放,却用了Chart,数据会被不断刷新覆盖,根本看不到完整曲线。
如果需要在同一个界面里同时显示实时曲线和历史曲线,建议把实时部分用Chart,历史分析部分用Graph,两个控件分开,数据分别从采集循环和历史文件读取。
3.3 用生产者-消费者和队列改造例程
绝大多数教学例程的结构是“一个While循环搞定所有事”:采集数据、刷新界面、处理按钮、写文件全放在一个循环里。这种结构在简单演示时没问题,但一旦采集任务变重或者界面操作变多,程序就会卡顿,按钮点了半天没反应。
工程上的标准解法是生产者-消费者结构,用队列把界面事件和数据采集解耦。具体操作步骤:
- 在程序开始处,用Queue Operations里的Obtain Queue函数创建一个队列,队列元素类型定义为枚举或变体。
- 生产者循环负责响应界面事件。比如“开始采集”按钮触发时,向队列中放入一条“开始采集”消息;“停止采集”按钮触发时,放入“停止采集”消息。
- 消费者循环负责处理消息。从队列中用Dequeue Element取出消息,根据消息内容执行相应的采集或停止动作。
- 程序停止前,先向队列中放入一条“退出”消息,消费者循环处理完这条消息后再退出,避免数据丢失。
- 退出前用Release Queue释放队列。
改造完成后,界面操作和采集任务就不会互相阻塞。点击按钮,事件结构立刻响应;采集循环按自己的节奏跑,互不干扰。这是LabVIEW进阶过程中最重要的思维转变,也是很多优秀源码例程的核心设计。
3.4 错误链:例程最容易忽略的接线
看一批例程下来会发现,很多教学源码为了画面简洁,完全省略了错误簇的接线。这在演示时没问题,但在工程项目里就是埋雷。错误簇(Error Cluster)是LabVIEW里专门传递错误信息的数据类型,包含status、code、source三个元素。
工程代码的正确做法是:几乎所有函数的错误输出都要接到下一个函数的错误输入,形成一条从头到尾的错误链。这样任何一个环节出错,错误信息会沿着链条往后传,最终集中处理。在错误链末端接一个Simple Error Handler或者自定义的日志子VI,就可以把错误码、错误来源、发生时间记录下来。
我的习惯是错误链末端接一个写日志的VI,把错误信息同时写入本地文件和前面板提示区,而不是只弹一个对话框。这样程序跑一夜,第二天早上翻日志就能看到哪一步出了问题,不需要人一直守着界面。
4. 例程运行时的常见翻车点:安装、TDMS、DBC与退出逻辑
这一节直接对应热词里那些特别具体的问题。很多例程本身没写错,但运行环境不对,或者文件格式理解有偏差,导致代码跑不起来。这些坑几乎每个LabVIEW工程师都会遇到。
4.1 LabVIEW安装错误和安装路径问题
热词里“labview安装错误”和“labview安装路径”出现频率很高。这类问题大多跟安装方式有关,我总结了几条实际经验:
- 安装LabVIEW时,安装路径不要出现中文和特殊字符,建议保持默认路径。很多第三方驱动和工具包对路径敏感,中文路径会导致vi加载异常。
- 安装前关闭杀毒软件和防护软件,否则部分驱动文件会被隔离,安装过程看起来成功,运行例程时却说找不到驱动。
- 卸载LabVIEW时不要手动删文件,要用NI官方提供的卸载工具或者NI Package Manager清理干净。残留文件会导致新版安装时冲突。
- 如果设备采集卡是NI的,注意LabVIEW和NI-DAQmx驱动的版本匹配,比如热词里提到的“labview daq软件驱动下载2020”,装完LabVIEW后需要单独安装对应版本的NI-DAQmx。
- 多个大版本共存在我看来是常态,但隔离是最省事的方案。我在虚拟机里装了老版本,主机装新版,两边不打架。
4.2 TDMS写入:Write to Measurement File Express VI的坑
“labview write to measurement file express vi tdms格式 channel写入抬头”这个热搜词很具体,明显是很多人踩了同一个坑。Write to Measurement File是一个Express VI,它会弹一个配置框,可以选择文件格式、文件路径、保存方式等。
用Express VI的优点是配置方便,缺点是不够灵活。例程里用Express VI没问题,但工程里我建议直接使用底层的TDMS文件VI函数,包括TDMS Open、TDMS Write、TDMS Close。原因是底层函数可以逐条控制通道名、数据属性、写入时机,而Express VI把这些都封装起来了,改起来很别扭。
“channel写入抬头”指的是把通道名称作为TDMS文件的属性写进去,这样用NI PicoScope或Excel插件打开TDMS文件时,能直接看到通道名和单位,而不是一堆没有标签的数据列。具体实现方式是在写入数据前,用TDMS Set Properties函数设置属性,比如将“通道名称”设为字符串“pressure_1”,将“单位”设为“MPa”。这些属性会随着文件一起保存,后续分析时非常重要。
另外一个容易踩的坑是写入频率。如果每个采到的点都立刻写入TDMS,文件会变得巨大且磁盘IO压力大。正确做法是攒一批数据再写入,比如每读1000个点写一次,配合TDMS Flush函数控制缓冲,性能提升很明显。
4.3 DBC文件解析:别上来就自己写解析器
热词“labview dbc文件解析”是做汽车总线或CAN通信的人经常搜的。DBC文件本质上是CAN总线数据库的文本描述,包含报文ID、信号名、起始位、长度、缩放因子、偏移量、取值范围等信息。
例程里出现的“DBC解析”,很多只是把DBC文件里的信号列表读出来,显示到Tree控件里,并不代表完整协议栈。如果你想在LabVIEW里做CAN报文收发和分析,有两条路:一是用NI-XNET库,直接导入DBC文件后,它会自动生成信号级别的读写接口,不需要关心位拼接;二是自己按文本规则解析DBC,适用于只有少量信号且不想依赖额外模块的场景。
自己做解析时,最容易错的地方是字节序和起始位。CAN信号有Motorola格式和Intel格式两种字节序,Intel格式低字节在前,Motorola格式高字节在前,换算错一个bit,解析出的数值就完全不对。所以看例程源码时,不要只看它读出了字符串,要确认它有没有针对两种字节序分别处理。
4.4 退出主VI时同时退出子VI
热词“labview 退出主vi时同时退出子vi”也是老生常谈。不少程序用Open VI Reference或Call by Reference的方式调用子VI,主界面关掉了,后台却还有vi在运行,任务管理器里的进程一直不退出,下一次运行还会报端口被占用等错误。
解决办法是在主VI的退出逻辑里,显式关闭所有打开的子VI引用。具体做法:
- 在主VI开始时,把所有打开的子VI引用存到一个数组里。
- 在主VI退出分支里,用一个For循环遍历这个数组。
- 对每个VI引用,使用Invoke Node调用Front Panel.Close方法,关闭子VI前面板。
- 再使用Invoke Node调用VI.Run方法,并将“wait until done”设为False,等效于点击子VI的停止按钮。
- 最后调用Close Reference节点,释放引用。
如果子VI里也有循环,最好在子VI内部设计一个“停止”标志,主VI在退出前通过属性节点或本地变量把该标志写为True,让子VI正常结束循环。只关闭前面板而不停止内部循环,程序依然会在后台运行。所以顺序是先让循环退出,再关前面板,最后释放引用。
4.5 LabVIEW源代码加密:给VI加密码不如从流程上防泄露
热词“源代码加密”背后是很多人担心自己交付的源码被白嫖。LabVIEW本身提供了一种保护方式:在File -> VI Properties -> Protection中设置密码,或者勾选“Execute only”(运行但不显示框图)。这个措施能挡住一些小白,但破解难度不高,根本谈不上真正安全。
工程上更靠谱的做法是:
- 项目最后编译时使用Build Specification打包成exe,只发布可执行程序,不发布vi源码。
- 对需要分发的llb,可以在Build Specifications里勾选“Remove diagram”,这样生成的llb只保留运行时信息,不包含框图。
- 核心算法如果特别值钱,用DLL封装,把算法逻辑用C/C++写好,LabVIEW只负责调用。
- 真正的防泄露要靠流程:权限管理、密钥管理、通信加密,而不是单纯给vi加个密码。
有一点要提醒:给vi设置了密码之后,一旦忘记密码,恢复非常麻烦,需要联系NI官方走流程。所以设密码前务必做好备份,最好把所有源码的密码统一记录在项目文档里,放到受控的配置管理系统中。
5. 从“例程合集”到“自己的源码库”:组织、注释与版本管理
既然手里有了一套又一套的LabVIEW例子源代码,最后一步就是把这些资源变成自己的东西。很多人下载了几十个G的例程,用的时候却找不到,找到了又看不懂,核心原因是没有组织好。这个章节分享一下我积累源码库的习惯。
5.1 下载的例程合集怎么整理
我下载了新的例程包后,会先做一次“准入检查”,检查不通过的直接不纳入正式库。我的目录结构是这样的:
- 根目录按功能分:界面框架、通信协议、数据采集、文件IO、信号处理、报表生成、数据库。
- 每个例程包单独一个子目录,目录名包含“功能描述_版本_收集日期”三段信息。
- 每个子目录里放一个README.txt,记录例程功能、适用LabVIEW版本、依赖的模块、是否已在某个项目中验证过。
- 单独建一个“已通过验证”目录,把跑通并入库的例程复制一份放到这里。
实际项目开发时,我只会从“已通过验证”目录里找代码,避免把网上找到的没验证例程直接用进生产程序。这一步能省下大量排错时间。另外要注意版权问题,从网上下载的例程,尤其是付费转卖的,不能直接复制到商业项目里,也别随手转卖。热词里的“labview上传资料赚钱的网站”就涉及这类风险,谨慎处理。
5.2 注释与VI属性:写给自己三个月后看
LabVIEW的注释体系和文本语言不一样,很多人看例程觉得晦涩,就是因为作者没写注释。自己写库的时候,必须建立一套统一的注释规范。
我常用的注释载体有三个:
- 自由标签(Free Label):放在框图空白处,说明一段逻辑的意图。比如“这里将采集到的原始数据转换成工程单位,因为传感器输出的是毫伏”。
- VI属性描述:在File -> VI Properties -> Documentation里填写描述和帮助信息。鼠标悬停在VI图标上时,这些描述就会显示出来。
- 连线板说明:给每个输入输出端口写上下文帮助,调用子VI时直接看图标就知道每个端口该接什么。
写注释的重点不是解释“做了什么”,而是解释“为什么这么做”。比如“重试三次再报错”和“因为PLC断电恢复需要大约5秒,这里重试三次”,后者对后人帮助大得多。实际上,这些注释也是给自己看的,三个月后再打开自己写的源码,如果没有注释,一样会懵。
5.3 版本管理与可持续积累
vi是二进制文件,Git虽然能跟踪二进制文件的变化,但没法像文本代码那样做diff对比,所以很多团队不太用Git管理LabVIEW源码。我的经验是,Git仍然值得用,主要是为了记录“哪些文件在何时被修改了”,以及提供版本回退的能力。
具体做法:
- 用Git管理整个LabVIEW项目目录,忽略掉编译生成的build目录和临时文件。
- 对关键时间节点,比如一次成功联调后,打一个tag,方便后续回退。
- 定期把已验证的vi打包成llb,压缩包命名带日期。这个压缩包就是离线快照。
- 需要对比两个vi差异时,用LabVIEW自带的Tools -> Compare -> Compare VIs工具,它可以高亮框图上的差异,比Git的二进制diff直观得多。
坚持一段时间后,库里的例程会越来越多。我的个人体会是,例程源码全集的价值不在于“跑通”,而在于把它拆明白之后,消化成自己的模块。遇到类似场景,能从自己的库里抽出验证过的代码块,比重新翻一个陌生例程高效得多。
我自己每次拿到一个新例程,会强迫自己把数据流完整复述一遍,再动手改代码。复述不出来,说明还没真正看懂。这个过程虽然慢,但积累下来的东西,都是你自己的。
本文还有配套的精品资源,点击获取