简介:面向Ubuntu 20.04用户的OpenCV 3.3.1编译资源包,专为需要在较新系统上复现旧版OpenCV的开发者与计算机视觉学习者准备。该资源针对OpenCV 3.3.1与新版FFmpeg接口不匹配导致的三个典型编译错误(CODEC_FLAG_GLOBAL_HEADER未声明、AVFMT_RAWPICTURE未声明、PyString_AsString类型转换失败)做了修复,适配Ubuntu 20.04默认工具链,拿到后可直接进行源码编译与二次开发。整个zip压缩包共5630个文件,除cpp源码与h/hpp头文件外,还包含大量png/jpg图像资源、构建脚本、markdown说明文档以及java、python接口示例文件,压缩后约85.64MB,目录结构完整,便于按模块检索与定制。目前已有1780人学习下载。打包内容既涵盖核心库与contrib扩展模块,也提供测试用例和demo工程,适合深入理解图像处理、特征提取、视频分析等API的调用方式;其中针对FFmpeg兼容性问题的修复思路,对排查Linux下音视频接口编译报错同样具有参考价值。
1. 为什么是 opencv-3.3.1:Ubuntu 20.04 编译老版本的第一道坎
如果你维护过 2017 年前后的人工智能项目,大概率经历过这种场景:代码栈固定在 linux 环境下,里面写着 cv2.xfeatures2d.SIFT_create(),依赖的还是 OpenCV 3.3.1 的模块结构,而 Ubuntu 20.04 的 apt 仓库里只有 OpenCV 4.2,pip 上的 opencv-python 也都是 4.x,把老代码直接跑起来全是接口报错。唯一的出路就是源码编译,但 20.04 默认的 GCC 9.3、FFmpeg 4.2、Python 3.8,和当年围绕 GCC 5、FFmpeg 3.x、Python 2.7 构建的 3.3.1 之间,刚好存在三类编译断裂。这份资源不是干净的官方源码,而是已经处理完 videoio 与 python 模块这三类错误的修改版,解压后可以直接进入 CMake 阶段。适合被迫维护老模型的算法工程师,也适合想把老代码当参考、却不想在编译环境上耗一天的新手。
2. 编译前准备:依赖库、Python 版本与工具链检查
OpenCV 3.3.1 的官方源码默认假设编译环境是 2017 年的主流配置。Ubuntu 20.04 比它晚了两个大版本,工具链变化集中在三块:编译器从 GCC 6 跳到 GCC 9.3,FFmpeg 从 3.x 跳到 4.2.x,默认 Python 从 3.5 跳到 3.8。这三处变化恰好全部落在 OpenCV 编译链路的敏感位置上——videoio 模块在编译期直接绑定 FFmpeg API,python 模块在编译期直接绑定 Python C API,highgui 则依赖 GTK 的检测结果。所以编译前花十分钟把环境看清楚,比遇到报错再回头排查要省事得多。
2.1 Ubuntu 20.04 的默认工具链与 3.3.1 的兼容水位
先看一组系统默认值。Ubuntu 20.04 自带 gcc/g++ 9.3.0,CMake 3.16.3,Python 3.8.x,FFmpeg 4.2.x(对应 libavcodec 58),GTK 有 2 和 3 两套可选。而 OpenCV 3.3.1 发布时对应的主流组合是 GCC 5/6、CMake 3.8、Python 2.7 或 3.5、FFmpeg 3.x。编译器层面 GCC 9 对 C++11 的默认特性检查更严格,隐式类型转换会被直接判错;FFmpeg 4.x 是主版本升级,删除了一批 3.x 时代的老 API;Python 3.8 的 C API 对 Python 2 时代的函数名做了清除或重定义。
为什么不能直接用 apt 装?Ubuntu 20.04 官方源里的 libopencv-dev 是 4.2 版本,4.x 把 xfeatures2d、SIFT 这类经典特征算法挪进了 opencv_contrib,并且部分算法还改了命名空间,老代码在 cv::xfeatures2d 路径下根本找不到符号。pip 的 opencv-python 同样只有 4.x。所以对于代码栈固定在 3.3.1 的项目,源码编译是唯一可控的路径。判断是否需要这份修改版资源,不用看报错,直接看系统里的 libavcodec 版本:版本号以 58 开头,第三章里的两个 FFmpeg 错误就必然出现;如果系统里根本没装 FFmpeg 开发库,错误路径又完全不同。
2.2 安装基础依赖:build-essential、CMake 与 GTK2
在 Ubuntu 20.04 上编译 OpenCV 3.3.1,依赖安装可以一次到位:
sudo apt update sudo apt install build-essential cmake git pkg-config \ libgtk2.0-dev libavcodec-dev libavformat-dev libswscale-dev \ libv4l-dev libtbb-dev libjpeg-dev libpng-dev libtiff-dev \ python3-dev python3-numpy python3-distutils这里有两个容易被忽略的点。第一个是 libgtk2.0-dev 而不是 libgtk-3-dev:OpenCV 3.3.1 的 highgui 对 GTK2 的适配是完整且有测试覆盖的,对 GTK3 的适配在 2017 年只停留在“能检测到但链接期容易翻车”的状态,选 GTK2 能少一类 undefined reference。第二个是 python3-distutils:Ubuntu 20.04 的 Python 3.8 把 distutils 单独拆出去了,不装的话,make install 阶段跑到 python 模块时会直接抛出 ModuleNotFoundError: No module named 'distutils.util'。这两项都属于装了不亏、漏了必炸的依赖。
python3-numpy 也不能省,OpenCV 的 python 绑定在编译期要引用 numpy 的头文件做数组类型检查,缺失会在 CMake 阶段直接报 Python numpy 未找到。v4l 库对应的是 V4L2 视频采集接口,如果你后续要接 USB 摄像头或者用 VideoCapture 读设备节点,这一项是硬依赖,否则编译出来只有文件读写没有设备读写,排错的时候会非常迷惑。
2.3 检查环境版本,确认需要打补丁
依赖装完后,把下面的命令逐条跑一遍,把输出记下来:
gcc --version | head -1 cmake --version | head -1 python3 --version pkg-config --modversion libavcodec pkg-config --modversion gtk+-2.0我一般关注三个关键判断:python3 是否 3.8 及以上,libavcodec 是否 58.x 开头,gtk+-2.0 是否能被 pkg-config 查到。这三个条件同时满足,说明当前环境正好落在 OpenCV 3.3.1 的兼容水位之外,第三章的源码修改是不可跳过的。反过来,如果 libavcodec 是 54/55/56 这种 FFmpeg 2.x/3.x 老版本,那源码不需要动,直接跳到第四章的 CMake 配置即可。这个检查把“要不要打补丁”从玄学变成了确定判断,也避免拿着旧源码在配置阶段反复绕圈。
3. 修复三类编译错误:FFmpeg API 更名与 Python 2 接口残留
这一章是资源的核心价值。OpenCV 3.3.1 在 Ubuntu 20.04 上编译,最典型的三类报错分别来自 FFmpeg 的两个 API 变更和 python 绑定层的一个类型转换问题。下面逐个拆开,说明报错为什么会冒出来,以及这份资源里的修改手法。
3.1 CODEC_FLAG_GLOBAL_HEADER:FFmpeg 4.x 的宏更名
先看第一个报错:
error: ‘CODEC_FLAG_GLOBAL_HEADER’ was not declared in this scope这个错误出现在 modules/videoio 底下的 cap_ffmpeg 实现文件里。OpenCV 3.3.1 写视频输出时,需要设置编码器的 header 标志,用的是 FFmpeg 3.x 时代的旧名字 CODEC_FLAG_GLOBAL_HEADER。FFmpeg 4.0 把所有 AVCodecContext 相关的标志宏从 CODEC_FLAG_* 统一改名为 AV_CODEC_FLAG_*,旧名字在头文件里被删除,编译时 preprocessor 找不到这个宏,直接在作用域检查阶段报错。
修复方式是把旧宏替换成新宏。这份资源里的处理是搜索 videoio 模块下的全部 cap_ffmpeg 相关文件,避免遗漏同族旧宏。如果你拿到的是未修改的官方源码,可以手动执行:
grep -rn "CODEC_FLAG_GLOBAL_HEADER" modules/videoio/src/ sed -i 's/CODEC_FLAG_GLOBAL_HEADER/AV_CODEC_FLAG_GLOBAL_HEADER/g' \ modules/videoio/src/cap_ffmpeg.cpp modules/videoio/src/cap_ffmpeg_impl.hpp这里有个容易出错的地方:有些人图省事在 OpenCV 根目录做全局 sed,结果把文档和 contrib 里的引用也一并替换,反而引入新的不一致。我一般会把替换范围局限在 videoio/src 目录,替换完再 grep 一遍确认没有残留。另外,CODEC_FLAG_LOOP_FILTER 这类同族旧宏也可能在同一个文件里出现,可以一起检查:
grep -rn "CODEC_FLAG_" modules/videoio/src/输出里所有 CODEC_FLAG_ 开头的旧宏都按 AV_CODEC_FLAG_ 前缀规则处理,避免编译到 70% 再抛出第二个同名错误。还有一点值得提醒:不要用 -fpermissive 把错误降级成警告跳过。那个开关只会掩盖掉类型检查,FFmpeg 4.x 头文件里根本没有这个宏,所谓警告背后实际是符号缺失,运行时必然在 avcodec 初始化阶段崩溃。
3.2 AVFMT_RAWPICTURE:被移除的输出格式标志
第二个报错紧跟着第一个出现:
error: ‘AVFMT_RAWPICTURE’ was not declared in this scopeAVFMT_RAWPICTURE 原本是 AVOutputFormat 结构上的一个 flag,用来告诉 muxer 输入是原始图像帧。FFmpeg 4.0 在重构 libavformat 时把这个 flag 从公共头文件里摘掉了,因为 muxer 的行为不再依赖这个标志位。OpenCV 3.3.1 的写视频分支还在设置它,于是编译器报未声明。
修复方式比第一个更值得讲究。直接删掉该行在 FFmpeg 4.x 下没有任何问题,可一旦将来要回到 FFmpeg 3.x 环境,写视频逻辑会缺少原本依赖的行为。推荐的写法是用条件编译把设置语句包起来:
#ifdef AVFMT_RAWPICTURE oc->oformat->flags |= AVFMT_RAWPICTURE; #endif逻辑很直接:宏存在时保持旧行为,宏被删除时跳过设置,前后两个 FFmpeg 主版本都能编译。这份资源采用的就是这个手法,而不是无脑删行。自己改的时候,注意同文件里可能不止一处引用 AVFMT_RAWPICTURE,先把所有引用位置用 grep 找全,再统一包 ifdef,避免改了一半编译到后半段又报同样的错误。
3.3 PyString_AsString:Python 2 时代绑定接口的残留
第三个错误来自 python 绑定层,报错信息带着具体行号:
error: invalid conversion from ‘const char*’ to ‘char*’ [-fpermissive] 856 | char* str = PyString_AsString(obj);这个错误的根因和前两个不同,不是接口被删除,而是接口的返回类型在 Python 3 下发生了改变。PyString_AsString 是 Python 2 的字符串 C API,在 Python 3.8 的头文件里被映射到 PyUnicode 或 PyBytes 的对应函数,有些分支返回 const char*,而 OpenCV 3.3.1 源码里声明的局部变量是 char*。C++ 对 const char* 到 char* 的隐式转换在 GCC 9 下直接报错。
正确的修复是把字符串获取逻辑按 Python 版本分开处理。在实际打补丁时,做法类似下面这样:
#if PY_MAJOR_VERSION >= 3 const char* str = nullptr; if (PyUnicode_Check(obj)) { str = PyUnicode_AsUTF8(obj); } else if (PyBytes_Check(obj)) { str = PyBytes_AsString(obj); } #else char* str = PyString_AsString(obj); #endif注意两点。第一,str 的声明类型要改成 const char*,因为 PyUnicode_AsUTF8 返回 const char*,不匹配下一行又会报同样的 -fpermissive 错误;第二,PyString_AsString 在同文件里经常不是孤例,PyString_Size、PyString_FromString 也属于同一批 Python 2 接口,要一并搜出来处理:
grep -rn "PyString_" modules/python/src2/另外,OpenCV 3.3.1 的 python 绑定不是直接手写的,而是通过 gen2.py 模板生成 cv2.cpp。直接改生成文件是临时方案,重新跑生成流程时修改会丢;这份资源是在生成器层面处理的,所以不会出现“改了 cv2.cpp 但重新生成后又恢复原样”的情况。把三个错误的对应关系整理成表:
| 报错原文 | 根因 | 修复手法 |
|---|---|---|
| CODEC_FLAG_GLOBAL_HEADER was not declared | FFmpeg 4.0 宏更名 | CODEC_FLAG_ 前缀改为 AV_CODEC_FLAG_ |
| AVFMT_RAWPICTURE was not declared | FFmpeg 4.0 移除 flag | 用 #ifdef AVFMT_RAWPICTURE 条件编译包裹 |
| invalid conversion from const char* to char* | Python 3 字符串 C API 返回类型改变 | 按 PY_MAJOR_VERSION 分支,改用 PyUnicode_AsUTF8 |
4. CMake 配置与编译落地:参数组合、并发度与安装路径
源码准备好之后,CMake 参数是决定成败的下一步。OpenCV 3.3.1 的 CMake 检测能力比 4.x 弱,很多参数需要显式写出来,否则它会按自己的默认值走,用户对编译产物没有掌控感。这一节给出的配置是 Ubuntu 20.04 上反复验证过的组合,兼顾功能完整性和编译速度。
4.1 生成构建脚本:显式指定 Python 与模块开关
在 opencv-3.3.1 根目录下执行:
cd opencv-3.3.1 mkdir -p build && cd build cmake -D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D BUILD_opencv_python2=OFF \ -D BUILD_opencv_python3=ON \ -D PYTHON3_EXECUTABLE=$(which python3) \ -D PYTHON3_INCLUDE_DIR=/usr/include/python3.8 \ -D PYTHON3_PACKAGES_PATH=$(python3 -c "import site; print(site.getsitepackages()[0])") \ -D PYTHON3_NUMPY_INCLUDE_DIRS=$(python3 -c "import numpy; print(numpy.get_include())") \ -D WITH_FFMPEG=ON \ -D WITH_V4L=ON \ -D WITH_GTK=ON \ -D WITH_TBB=ON \ -D WITH_CUDA=OFF \ -D WITH_OPENCL=ON \ -D BUILD_EXAMPLES=ON \ -D INSTALL_PYTHON_EXAMPLES=ON ..逐个说参数的含义。CMAKE_BUILD_TYPE 用 RELEASE,OpenCV 3.3.1 在 Release 下会开 -O3,图像算法吃优化,Debug 版性能差到没法用。BUILD_opencv_python2=OFF 是为了避免系统里没有 Python 2 时给出误导性警告;BUILD_opencv_python3=ON 配合下面三个 PYTHON3_ 参数,把 Python 3.8 的头文件、numpy 头文件和包安装路径全部钉死。这里的常见坑是:不显式指定 PYTHON3_PACKAGES_PATH 时,CMake 偶尔会指向一个不存在的目录,导致 import cv2 时模块找不到。
WITH_FFMPEG=ON 确保 videoio 走 FFmpeg 后端,这是读 mp4 的基本前提;WITH_GTK=ON 配合第二章装的 libgtk2.0-dev,保证 imshow 可以工作。WITH_CUDA=OFF 是这类老版本编译的一致选择,OpenCV 3.3.1 对 Ubuntu 20.04 的驱动和 CUDA 版本没有适配,打开反而要处理 GPU 架构检测失败的问题,纯 CPU 编译对大多数老项目已经足够。WITH_TBB=ON 启用线程构建块做并行加速,对多核机器上的图像处理有明显收益,前提是已经装了 libtbb-dev。WITH_OPENCL=ON 利用 CPU 端的 OpenCL 运行时做部分算子加速,没有独显也不会报错,OpenCL 检测失败时 CMake 会自动降级。
cmake 命令末尾的 .. 指向源码根目录。执行完先看输出末尾的配置摘要,重点确认三行:FFMPEG 是 YES、GTK 是 YES、Python 3 是 YES。如果 FFMPEG 显示 NO,说明 pkg-config 没找到 libavcodec,回去检查 libavcodec-dev 是否安装。
4.2 编译:并发数按内存挑,不按核心数挑
配置通过后开始编译:
make -j4-j 参数不建议直接写 nproc。OpenCV 3.3.1 的编译单元非常吃内存,尤其是 opencv_core 和 opencv_imgproc,单个编译进程吃 1.5GB 到 2GB 很正常。8 核机器用 -j8 在 16GB 内存上问题不大,8GB 内存用 -j8 大概率在编译中期出现 cc1plus 进程被 kill 的报错。我一般的习惯:内存小于等于 8GB 用 -j2,16GB 用 -j4,32GB 以上才放开到 -j8。这条经验比只看核心数靠谱。
编译中断也没关系,make 支持增量编译。中断后重新执行相同命令即可续编,不需要重新 cmake。如果编译过程中再次出现第三章修过的错误名,先停掉 make,回源码目录执行一次 grep,确认修改落在正确文件上。cap_ffmpeg.cpp 和 cap_ffmpeg_impl.hpp 这两个文件经常被弄混,只改其中一个并不能让 videoio 编过。如果是没见过的新错误,先看报错文件路径和行号,多半是依赖版本问题,不要盲目加 -fpermissive 掩盖。
4.3 安装与软链配置
编译完成后安装:
sudo make install sudo ldconfigmake install 会把头文件放到 /usr/local/include/opencv2,库文件放到 /usr/local/lib,python 模块按 PYTHON3_PACKAGES_PATH 指定的目录放,pkg-config 文件 opencv.pc 放到 /usr/local/lib/pkgconfig。ldconfig 必须执行,否则动态库缓存里没有 /usr/local/lib,python 加载 cv2 时会报找不到 libopencv_core.so.3.3。
通常还需要把 pkg-config 路径导出,方便后续编译 C++ 工程时能找到 opencv.pc:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig pkg-config --modversion opencv第二条命令输出 3.3.1 说明安装链路正常。这里值得提醒:OpenCV 3.3.1 与系统里其他 OpenCV 版本不会冲突,因为库名带版本后缀;但如果系统里还有一份 apt 安装的 opencv,pkg-config 结果可能被抢先截获,出现版本对不上的假象,排错时要留意。
5. 避坑清单:从 configure 到 import cv2 的五条实战记录
写配置、改源码、make install,每一步都有对应的坑。下面五条是 Ubuntu 20.04 上编译 opencv-3.3.1 期间实际踩过的,按出现阶段排列,每条按现象、原因、解决来写。
5.1 ippicv 下载卡死
现象:cmake 执行到 3rdparty 检测时长时间停在 Downloading ippicv_xxxx 不动,或者直接报网络 timeout,整个配置流程中断。
原因:OpenCV 3.3.1 的 CMake 在检测 IPP 加速库时会从 github 下载 ippicv 压缩包。这个下载源在国内网络环境下成功率很低,而 CMake 没有合理的重试机制,卡住就是一直卡住。
解决:手动下载对应的 ippicv tarball,放进 build/3rdparty/ippicv/downloads/ 目录,文件名要和 CMake 脚本校验的 checksum 对应。更省事的做法是给 cmake 命令追加 -DIPPICV_DOWNLOAD_URL=file:///path/to/ippicv.tgz,指定本地源,让配置阶段直接读文件。这一步只花一分钟,但能避免在 configure 阶段耗掉半天。注意这个坑和源码修改无关,是 OpenCV 3.3.1 的共性问题。
5.2 GTK 版本误选导致链接期错误
现象:CMake 配置输出里 GTK 显示 YES,编译也全部通过,但写代码调用 cv::imshow 时出现 undefined reference,或者样例程序链接失败。
原因:系统里同时存在 GTK2 和 GTK3 时,OpenCV 3.3.1 的 CMake 优先走到 GTK3 分支。那个版本的 highgui 对 GTK3 的适配不完整,部分函数在链接期缺符号。Ubuntu 20.04 默认同时提供两套库,很容易踩中。
解决:第二章只装 libgtk2.0-dev,不装 libgtk-3-dev。已经装了的话,检查 CMake 配置摘要里 GTK 指向的版本号,如果显示 3.x,把 CMakeCache.txt 里相关 GTK 路径清掉重配,或者干净环境下重新跑 cmake。GTK 选错不会影响编译期,但会让 imshow 系列功能在运行期瘫痪,排查起来比编译错误更隐蔽。
5.3 distutils 模块缺失
现象:make install 执行到 python 模块安装阶段,抛出 ModuleNotFoundError: No module named 'distutils.util',前面的 C++ 库都装好了,唯独 python 接口没装上。
原因:Ubuntu 20.04 的 Python 3.8 把 distutils 从基础安装里去掉了,OpenCV 3.3.1 的 python 安装脚本依赖它解析路径并执行 setup 逻辑。
解决:sudo apt install python3-distutils 装完后重新执行 sudo make install。不需要重新编译,安装阶段会再次调用 python 脚本,这次就能通过。如果你是在 venv 虚拟环境里做 python 绑定,还要确认 venv 内也具备 distutils,否则同样会报。
5.4 import cv2 报动态库找不到
现象:编译、安装都成功,python3 里执行 import cv2 却抛 ImportError: libopencv_core.so.3.3: cannot open shared object file。
原因:/usr/local/lib 里的动态库没有进入 ldconfig 缓存。ldconfig 只有在系统启动或手动执行时才会刷新,安装后忘了执行是最常见的原因。
解决:sudo ldconfig 刷新一次。如果系统里 /usr/local/lib 本身没有被 ldconfig 配置,需要手动写入:
echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/opencv.conf sudo ldconfig之后重新打开 python3,import cv2 前先确认 sys.path 里没有指向其他版本 cv2 的路径。如果有多个 python 环境混用,优先检查 PYTHON3_PACKAGES_PATH 是否真的存在于当前 python3 的 site-packages 搜索范围内。
5.5 同一个错误在 make 后期再次出现
现象:make 编译到 60% 左右又报出 CODEC_FLAG_GLOBAL_HEADER 未声明,和第三章修掉的错误完全一样。
原因:修改只作用到了 cap_ffmpeg_impl.hpp,而实际编译单元最终 include 的是 cap_ffmpeg.cpp,或者另一个文件里还有残留引用。OpenCV 3.3.1 的 videoio 模块里这两个文件双向包含,很容易改漏。
解决:不要只 grep 单个文件名,直接全目录搜索:
grep -rn "CODEC_FLAG_GLOBAL_HEADER" modules/videoio/src/返回值应该为空。还有内容就继续替换,替换完再 grep 一次确认干净,然后重新 make。这个习惯能省下半小时的无效重编译时间。
6. 验证安装:一张图、一段视频与一份构建信息
安装完成不算结束,还要证明修过的三个错误确实让功能恢复正常。我习惯用一个最小脚本把核心链路全部过一遍。
import cv2 print("OpenCV version:", cv2.__version__) info = cv2.getBuildInformation() for key in ["FFMPEG", "V4L/V4L2", "GTK", "Python 3"]: for line in info.split("\n"): if key in line: print(line.strip()) break img = cv2.imread("test.jpg") if img is None: raise SystemExit("test.jpg read failed") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray, 50, 150) cv2.imwrite("edges.jpg", edges) cap = cv2.VideoCapture("test.mp4") if not cap.isOpened(): raise SystemExit("test.mp4 open failed") ok, frame = cap.read() print("video frame:", frame.shape if ok else "read failed") cap.release()脚本里每一段都有对应目的。cv2.getBuildInformation 里检查 FFMPEG、GTK、Python 3 三行的值,确认核心模块编进了运行版本;imread 和 imwrite 验证 imgcodecs 对 jpg 的编解码;Canny 验证 imgproc 的算法链路;VideoCapture 读 mp4 验证 videoio 的 FFmpeg 后端。只要视频能读到 frame,第三章那两个 FFmpeg 错误就说明改到位了。如果 VideoCapture 报 open failed,优先查构建信息里 FFMPEG 那行是不是 NO,而不是怀疑代码。
脚本跑完,我会顺手复制官方 samples 里的一个 C++ 例子编译一遍,确认 pkg-config 链路也对得上:
cd opencv-3.3.1/samples/cpp g++ -o example example.cpp $(pkg-config --cflags --libs opencv) ./example从那以后,每次 Ubuntu 版本升级后重装这个环境,我都强制把上面这个脚本完整走一遍再继续其他工作。它能在两分钟内把最容易翻车的四个模块全部验完,也能在项目交接时给对方一个明确的验收标准。编译环境这种事,动手前多确认一遍版本,动手后多跑一遍验证,就能把不确定性压到最低。希望帮到你。
本文还有配套的精品资源,点击获取