news 2026/10/1 4:55:31

软件工程实战复习:从需求到测试的全链路建模

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
软件工程实战复习:从需求到测试的全链路建模

1. 这不是“划重点”,而是软件工程知识体系的实战重建

“软件工程期末复习(详细)”——看到这个标题,我第一反应不是翻笔记,而是下意识摸了摸自己去年带过的三届本科生的期末卷子。不是因为怀念讲台,而是因为太清楚:90%的学生拿到这六个字,第一件事是打开百度文库搜“重点总结”,第二件事是复制粘贴到Word里调个字号,第三件事是考前两小时狂背“软件生命周期有哪几个阶段”。结果呢?卷子发下来,UML图连线连错对象,需求规格说明书写成用户操作手册,测试用例设计漏掉边界值,连“耦合”和“内聚”都分不清哪个是模块内部、哪个是模块之间。这不是记性问题,是知识没被真正“组装”过。

我带的班里有个学生,考前一周来找我,说“老师,我看了五份复习资料,越看越乱”。我让他当场画一个银行转账系统的用例图,他卡在“ATM机算不算Actor”上纠结了三分钟;我让他写一段符合开闭原则的Java代码,他交上来的是if-else堆砌的“万能开关”。那一刻我就知道,所谓“详细复习”,从来不是信息量的堆砌,而是把散落的螺丝钉、垫片、轴承,按真实工程逻辑拧进一台能运转的发动机里。这篇内容,就是帮你完成这场“组装”。它不承诺“三天速成”,但保证你合上书后,能独立画出一个电商订单模块的类图+序列图+状态图组合,能对着需求文档反向推导出哪些接口必须定义、哪些异常必须捕获、哪些日志必须埋点。核心关键词就三个:软件生命周期、质量保障、工程实践——它们不是PPT里的圆圈箭头,而是你写每一行代码时背后站着的隐形检查员。适合谁?适合那些不想靠押题过线、想真正搞懂“为什么这么设计”的人,也适合刚实习回来发现学校教的和公司用的像两个世界、急需补上认知断层的准毕业生。

2. 内容整体设计与思路拆解:从“背概念”到“建模型”的底层逻辑

2.1 为什么拒绝传统复习路径?——知识碎片化的致命陷阱

传统复习法本质是“概念搬运工”:把教材目录抄成提纲,把定义摘成卡片,把案例缩成梗概。这在软件工程领域尤其危险。举个最典型的例子:“瀑布模型”。教材定义是“需求→设计→实现→测试→维护”的线性流程。学生背得滚瓜烂熟,但一遇到实际题目就露馅——比如问“如果测试阶段发现需求理解错误,瀑布模型如何应对?”标准答案是“返工回需求阶段”,可现实里,返工成本是多少?需求文档版本怎么管理?变更请求单谁审批?这些教材绝口不提。结果就是,学生脑子里只有五个孤立的圆圈,没有连接圆圈的管道压力、阀门开关和流量计读数。

我拆解过近五年本校软件工程期末卷,高频失分点根本不在冷门概念,而在跨阶段关联能力缺失。比如一道15分大题:“为图书馆管理系统设计数据库ER图,并说明如何在编码阶段通过ORM框架映射该模型”。学生要么ER图画得完美但ORM部分空着,要么ORM写了一堆注解却把借阅关系实体化错了。问题出在哪?出在把“分析”“设计”“实现”当成三个割裂的考场模块,而忘了它们本是一条流水线上的同一块钢板。

2.2 我的设计主线:以“一个可运行的最小系统”为锚点

所以我的复习框架彻底抛弃章节顺序,改用真实项目驱动的逆向推演法。核心锚点是一个极简但完整的系统:学生成绩录入与查询Web应用(Spring Boot + MySQL + Thymeleaf)。为什么选它?

  • 规模可控:功能仅含登录、成绩录入(课程/学生/分数)、按学生查成绩、按课程查平均分,无复杂权限或并发;
  • 覆盖全链路:需求(教师要快速录分)、分析(用例图/活动图)、设计(类图/数据库ER图)、实现(Controller/Service/DAO分层)、测试(JUnit+Mockito单元测试)、部署(jar包启动);
  • 暴露典型矛盾:比如“录入成绩时需校验分数0-100”,这既是需求约束,也是设计时的输入验证逻辑,更是编码时的if判断,还是测试用例的边界值(-1,0,100,101)。

整个复习过程,就是带着这个小系统,一层层剥开软件工程的洋葱:

  1. 先跑通它:用现成代码(我会提供精简版)部署起来,点开浏览器看它怎么工作;
  2. 再拆解它:针对每个功能点,倒推“如果没有XX工程活动,这里会出什么问题”;
  3. 最后重建它:删掉部分代码或设计图,让你亲手补全,比如只给数据库表结构,让你画类图并写出Service层接口。

这种设计不是炫技,而是模拟企业真实场景——新人入职接到的永远不是“从零开始”,而是“在现有系统上加一个按钮”。你的复习成果,必须能直接迁移到实习代码审查中。

2.3 工具链选择:为什么用PlantUML而非Visio?为什么坚持手写SQL?

工具选择背后全是工程权衡:

  • UML图绘制:弃用Visio/StarUML,强推PlantUML。原因很实在:Visio画的图是图片,无法版本控制;而PlantUML是纯文本代码(如@startuml class Student { +String name +int id } @enduml),能直接放进Git仓库,和代码一起提交。去年我让学生用Visio交作业,有三人因文件损坏重画三小时;用PlantUML的,一个git diff就能看出类图修改了哪些字段。
  • 数据库设计:禁用Navicat等图形化建表工具,要求手写CREATE TABLE语句。不是复古,是逼你直面约束——score DECIMAL(3,1) NOT NULL CHECK(score BETWEEN 0 AND 100)这一行,比勾选“非空”“检查约束” checkbox 让你更懂数据完整性。我见过太多学生ER图里标着“分数0-100”,建表时却写score INT,结果插入99.5报错还懵圈。
  • 测试编写:不用Postman测接口,强制用JUnit5+Mockito写单元测试。因为期末考常考“设计测试用例”,而Postman只能测“能不能通”,JUnit才能考“是否覆盖了所有分支”。比如calculateGrade()方法有A/B/C/D/F五档,考试若问“至少需要几个测试用例”,答“5个”是错的——边界值测试要求对每个档位的上下界都覆盖,实际需10+个用例。

这些选择,表面是工具偏好,实则是把“工程思维”刻进肌肉记忆:可追溯、可验证、可协作,才是软件工程的底色。

3. 核心细节解析与实操要点:把教科书定义焊进代码里

3.1 需求工程:从“用户说要什么”到“系统必须防什么”

需求不是用户嘴里的“我要一个查成绩的功能”,而是藏在对话缝隙里的风险预判。以成绩查询为例,学生可能说:“老师,我想查自己各科分数。” 但作为工程师,你必须追问:

  • 数据时效性:查询结果是实时数据库值,还是缓存?若缓存,失效策略是什么?(考题常设陷阱:“缓存未更新导致查到旧成绩,属于什么质量属性缺陷?” 答:时效性(Timeliness),属ISO/IEC 25010质量模型中的“时间特性”)
  • 数据安全性:学生A能查到学生B的成绩吗?这直接对应访问控制需求,需在用例图中明确Actor(Student)与Use Case(ViewOwnGrades)的关联,而非笼统写“ViewGrades”。

实操要点:

  1. 用例图必须标注扩展关系:基础用例“ViewGrades”应被“ExportToExcel”扩展,而非并列。因为导出是可选行为,且依赖查询结果。考试若给一张没标< >的图,大概率是扣分点。
  2. 需求规格说明书(SRS)的致命细节:教材常忽略“非功能需求”的写法。正确示范:

    性能需求:支持100并发用户查询,平均响应时间≤2秒(95%分位)
    安全需求:密码传输必须使用HTTPS,明文密码禁止出现在日志中
    约束:必须兼容Chrome/Firefox最新两个版本
    错误示范:“系统要快”“系统要安全”——这种描述在工程中等于没说。

提示:期末考常考“指出SRS中的错误”。记住铁律:所有需求必须可验证。例如“界面美观”不可验证,“按钮尺寸≥44px以满足移动端触控”可验证。

3.2 软件设计:类图不是画框框,是定义责任契约

类图常被当成美术作业,其实它是模块间法律合同。以成绩录入功能为例,学生类(Student)、课程类(Course)、成绩类(Score)的关系,绝不是简单画个连线:

  • 关联方向:Score类必须持有Student和Course的引用(private Student student; private Course course;),而非反过来。因为成绩的存在依赖于学生和课程,这是单向依赖,画反了意味着设计倒置。
  • 多重性标注:一个Score实例对应1个Student(1),但一个Student可对应多个Score(*)。类图中必须标1和*,否则无法推导出数据库外键(Score表中student_id字段为NOT NULL)。

实操避坑:

  • 继承滥用是高频雷区:学生(Student)和教师(Teacher)都属“人员”,但若建Person基类并让二者继承,会引发“菱形继承”难题——当需要添加“职称”字段时,是加在Person里(教师有职称,学生没有)还是分别加?正确解法是组合优于继承:建立Role类,Student与Role关联,Role包含职称属性。考试若给一个含Person→Student/Teacher继承的类图,十有八九考你“指出设计缺陷”。
  • 接口与抽象类的选择:定义成绩计算规则时,用interface GradeCalculator而非abstract class。因为Java中类只能单继承,但可实现多接口;未来若需同时支持“百分制转等级制”和“GPA转换”,接口能灵活组合,抽象类会锁死继承链。

注意:类图中所有属性/方法必须有可见性标识(+公有/-私有/#受保护)。考试若出现无标识的类图,直接判定不规范。

3.3 质量保障:测试不是找Bug,是证明“没Bug”的证据链

测试章节最容易陷入“背方法论”陷阱。记住:黑盒/白盒测试的本质差异,在于你能否看到代码内部逻辑。

  • 黑盒测试(如等价类划分):面对成绩录入页面,你不知道后台怎么校验分数,只根据输入范围划分:有效等价类(0-100)、无效等价类(负数、>100、非数字)。设计用例时,必须覆盖每个等价类的边界值(-1,0,100,101)和典型值(50,99)。
  • 白盒测试(如分支覆盖):当你看到if (score < 0 || score > 100) throw new IllegalArgumentException();这行代码,就知道必须设计两个用例:score=-5(触发异常分支)和score=85(走正常分支)。若只测85,覆盖率仅为50%。

实操关键参数计算:

  • 圈复杂度(Cyclomatic Complexity)是考试必考点。公式:V(G) = E - N + 2P(E=边数,N=节点数,P=连通分量数),但更实用的是判定节点法:V(G) = 判定节点数 + 1。例如一个含3个if语句的方法,圈复杂度=3+1=4,意味着至少需4个测试用例才能达到100%分支覆盖。我让学生统计自己写的Service方法圈复杂度,超6的必须重构——因为人类大脑难以可靠维护高复杂度逻辑。

实操心得:别迷信“100%覆盖率”。我见过覆盖率95%的代码,漏测了空指针(传入null参数)。真正重要的是关键路径覆盖:所有if/else、循环、异常抛出点,必须有对应用例。覆盖率只是副产品。

4. 实操过程与核心环节实现:用代码还原工程决策现场

4.1 从需求到数据库:手写DDL的每一步都在回答“为什么”

我们以成绩系统数据库设计为例,完整走一遍工程决策链:
第一步:ER图核心实体识别

  • 实体:Student(学号、姓名)、Course(课程号、课程名)、Score(成绩ID、分数)
  • 关系:Student与Score是“一对多”(一个学生多门成绩),Course与Score也是“一对多”(一门课多个学生成绩)
  • 关键洞察:Score是联系实体(Associative Entity),因为它有独立属性(分数),不能简化为Student-Course的直接多对多关系。

第二步:转换为关系模式(手写SQL)

-- 学生表:主键学号,姓名非空 CREATE TABLE student ( student_id VARCHAR(10) PRIMARY KEY, name VARCHAR(50) NOT NULL ); -- 课程表:主键课程号,名称非空 CREATE TABLE course ( course_id VARCHAR(10) PRIMARY KEY, course_name VARCHAR(100) NOT NULL ); -- 成绩表:复合主键(student_id, course_id),外键关联,分数带约束 CREATE TABLE score ( student_id VARCHAR(10) NOT NULL, course_id VARCHAR(10) NOT NULL, score DECIMAL(3,1) NOT NULL CHECK(score BETWEEN 0 AND 100), PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE RESTRICT );

为什么这样写?

  • ON DELETE CASCADE:删学生时自动删其所有成绩,避免孤儿记录;
  • ON DELETE RESTRICT:删课程时若存在成绩记录则禁止删除,防止数据不一致;
  • CHECK(score BETWEEN 0 AND 100):数据库层硬约束,比代码层校验更可靠(万一Service层漏了校验呢?)。

第三步:反向生成类图
从上述SQL,你能直接推出:

  • Score类必须有studentId、courseId、score三个属性;
  • Score类与Student类是聚合关系(空心菱形+实线),因为Score依赖Student存在,但Student可独立存在;
  • Score类中studentId字段类型必须与Student.student_id一致(VARCHAR(10)),否则ORM映射失败。

这就是“详细复习”的真意:每一个SQL关键字,都在回答一个工程问题。

4.2 从设计到代码:Spring Boot分层架构的职责切分

以成绩查询接口为例,展示三层如何协作并体现工程原则:
Controller层(处理HTTP协议)

@RestController @RequestMapping("/api/grades") public class GradeController { @Autowired private GradeService gradeService; // GET /api/grades/student/{id} → 返回JSON @GetMapping("/student/{id}") public ResponseEntity<List<GradeDTO>> getGradesByStudent(@PathVariable String id) { try { List<GradeDTO> grades = gradeService.findByStudentId(id); return ResponseEntity.ok(grades); } catch (StudentNotFoundException e) { return ResponseEntity.notFound().build(); // HTTP 404 } } }

关键设计点:

  • Controller只做三件事:接收请求、调用Service、返回HTTP响应;
  • 异常转换:StudentNotFoundException是业务异常,Controller将其转为HTTP 404,不暴露技术细节给前端;

Service层(核心业务逻辑)

@Service public class GradeServiceImpl implements GradeService { @Autowired private ScoreRepository scoreRepository; // DAO层 @Override @Transactional // 保证数据库操作原子性 public List<GradeDTO> findByStudentId(String studentId) { // 1. 校验学生是否存在(防御性编程) if (!studentExists(studentId)) { throw new StudentNotFoundException("Student not found: " + studentId); } // 2. 查询成绩并转换为DTO(避免暴露Entity给前端) return scoreRepository.findByStudentId(studentId).stream() .map(this::convertToDTO) .collect(Collectors.toList()); } }

关键设计点:

  • @Transactional:确保查询操作在同一个数据库事务中,避免脏读;
  • DTO(Data Transfer Object)模式:返回GradeDTO而非Score实体,防止前端意外修改Score的studentId等关键字段;

DAO层(数据访问)

@Repository public interface ScoreRepository extends JpaRepository<Score, Long> { // Spring Data JPA自动生成SQL:SELECT * FROM score WHERE student_id = ? List<Score> findByStudentId(String studentId); }

为什么不用原生SQL?

  • JpaRepository已封装CRUD,减少样板代码;
  • 但考试若考“手写JDBC”,必须写出PreparedStatement防SQL注入("SELECT * FROM score WHERE student_id = ?"),而非字符串拼接("SELECT * FROM score WHERE student_id = '" + id + "'")。

实操心得:我在代码审查中发现,80%的线上Bug源于Controller层过度承担——比如在Controller里写数据库查询逻辑。记住:Controller是交通警察,只管放行/拦截;Service是调度中心,决定怎么走;DAO是运输车队,负责具体搬运。

4.3 从代码到测试:JUnit5的精准打击式用例设计

针对GradeServiceImpl.findByStudentId()方法,设计高价值测试用例:

@SpringBootTest class GradeServiceImplTest { @Autowired private GradeService gradeService; @Test void shouldReturnGradesWhenStudentExists() { // 给定:数据库中存在学号为"S001"的学生及2门成绩 // 当:调用findByStudentId("S001") List<GradeDTO> result = gradeService.findByStudentId("S001"); // 那么:返回2条成绩,且课程名正确 assertThat(result).hasSize(2); assertThat(result.get(0).getCourseName()).isEqualTo("Math"); } @Test void shouldThrowExceptionWhenStudentNotFound() { // 当:查询不存在的学号 // 那么:抛出StudentNotFoundException assertThatThrownBy(() -> gradeService.findByStudentId("INVALID")) .isInstanceOf(StudentNotFoundException.class) .hasMessage("Student not found: INVALID"); } }

为什么这两个用例足够?

  • 第一个覆盖主成功路径(Happy Path);
  • 第二个覆盖关键异常路径(Sad Path);
  • 不需要测“空字符串”“null”等边界,因为Controller层已用@PathVariable String id声明,Spring会自动校验非空(若需校验空字符串,应加@NotBlank注解)。

考试常考“补充测试用例”,核心逻辑是:覆盖所有公开方法的输入域和异常域。看到一个方法,先问:

  • 正常输入有哪些?(如合法学号)
  • 异常输入有哪些?(如不存在学号、数据库连接失败)
  • 每种情况对应的HTTP状态码是什么?(404/500)

5. 常见问题与排查技巧实录:那些教材不会写的血泪教训

5.1 UML图绘制高频失分点与救急方案

问题现象根本原因救急方案考试应对
用例图中Actor与Use Case连线无箭头未理解Actor是主动发起者,Use Case是被动响应者PlantUML中强制用-->表示方向:Student --> (View Grades)若考题给图找错,直接指出“缺少方向性箭头,无法区分主被动关系”
类图中继承关系用实线+空心三角,而非虚线+空心三角混淆了继承(inheritance)与实现(implementation)PlantUML中继承用`<--:Student <
序列图生命线(Lifeline)未标注激活期(Activation Bar)忽略了“对象何时在执行操作”这一时间维度PlantUML中用activate/deactivate:Student -> :Controller: login(); activate Controller大题若要求画序列图,生命线必须有激活条,否则扣30%分

实操心得:我让学生用PlantUML画图时,强制要求每张图下方写一行注释,说明“这张图要解决什么工程问题”。比如序列图注释:“验证登录流程中,密码加密是否在Controller层完成(是)还是Service层完成(否)”。这能瞬间提升答题精准度。

5.2 数据库设计经典误区与修正

误区1:“成绩表不需要主键,用student_id+course_id当联合主键就行”

  • 错因:联合主键在ORM中易引发问题。例如Hibernate中,若Score实体用@EmbeddedId,则save()时需手动构造ScoreId对象,极易出错;而用代理主键(@Id private Long id),ORM自动处理。
  • 修正:成绩表必须有代理主键id BIGINT PRIMARY KEY AUTO_INCREMENT,student_id+course_id设为唯一索引(UNIQUE INDEX)。

误区2:“外键约束无所谓,代码里校验就行”

  • 错因:代码校验可被绕过(如直接SQL插入),而数据库约束是最后一道防线。曾有学生删库跑路(误操作),因无外键约束,student表删了但score表残留大量孤儿记录,恢复时花8小时人工清洗。
  • 修正:所有关联字段必须加外键,且明确ON DELETE行为(CASCADE/RESTRICT/SET NULL)。

误区3:“VARCHAR长度随便写,反正够用”

  • 错因:MySQL中VARCHAR(255)和VARCHAR(100)存储空间相同(都用1字节存长度),但VARCHAR(1000)需2字节存长度,且影响索引效率。学号若固定10位,必须写VARCHAR(10),而非VARCHAR(255)。
  • 修正:字段长度严格按业务需求定,宁小勿大。考试若给建表语句找错,“name VARCHAR(255)”常是扣分点(应为VARCHAR(50))。

5.3 测试用例设计失分重灾区与破局点

失分点1:“测试用例只覆盖正常流程,漏掉异常流”

  • 真实案例:学生写calculateGrade(85)返回"B",却没写calculateGrade(-5)应抛异常。结果考试考“设计测试用例验证异常处理”,全军覆没。
  • 破局点:用错误推测法(Error Guessing)补充用例。问自己:哪些输入会让代码崩溃?(负数、null、超长字符串、数据库连接超时)

失分点2:“测试用例命名模糊,如test1()、test2()”

  • 后果:考场上看到@Test void test1(),完全猜不出测什么,浪费3分钟。
  • 破局点:强制用Given-When-Then命名法:shouldReturnBGradeWhenScoreIs85()、shouldThrowExceptionWhenScoreIsNegative()。名字即文档,阅卷老师扫一眼就知道你懂。

失分点3:“集成测试混入单元测试,导致用例不稳定”

  • 典型错误:在JUnit中直接new GradeServiceImpl(),但Service依赖ScoreRepository,未Mock导致测试连真实数据库。
  • 修正:用@MockBean替代@Mock,确保Spring容器中替换的是真实Bean:
    @SpringBootTest class GradeServiceTest { @MockBean // 注意是MockBean,不是Mock private ScoreRepository scoreRepository; }

最后分享一个小技巧:考前72小时,别再刷题,做三件事:

  1. 把自己画的类图/ER图/序列图,用PlantUML代码重写一遍(强化语法肌肉记忆);
  2. 手写一份《常见错误自查清单》,包括“外键是否加了”“主键是否唯一”“测试用例是否覆盖异常”;
  3. 对着成绩系统源码,口头复述每一层的职责(Controller管啥?Service管啥?DAO管啥?),直到能脱口而出。
    这些动作看似简单,但能把你从“知道”推向“掌握”,而期末考,考的就是这个临界点。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 4:55:22

AI日报系统:日粒度热词捕获与结构化摘要工程实践

1. 项目概述&#xff1a;这不是一份“新闻简报”&#xff0c;而是一套可复用的AI日更内容生产系统你点开这个标题&#xff0c;第一反应可能是——又一个信息过载时代的碎片化产物&#xff1f;但作为连续三年每天产出结构化AI领域动态内容的从业者&#xff0c;我得说清楚&#x…

作者头像 李华
网站建设 2026/10/1 4:54:48

AIGC图片编辑实战指南:局部重绘、ControlNet与批量工作流全解析

AIGC系列的图片编辑篇拖到现在才写&#xff0c;不是没东西写&#xff0c;是内容多到不知道从哪下刀。上一篇聊完文生图的基本功&#xff0c;这次把图片编辑这条线彻底捋一遍&#xff1a;局部重绘、画布扩展、ControlNet精准控制、老照片修复、AI写真、批量工作流&#xff0c;每…

作者头像 李华
网站建设 2026/10/1 4:54:44

如何从GitHub Trending挖掘优质开源项目?关注这三个新方向

今天早上照例把 GitHub 日榜趋势速报刷了一遍&#xff0c;今天的列表比上周有意思不少。Trending 页面本身没什么魔法&#xff0c;它只是把 Star 增长最快的仓库按天排列&#xff0c;但看久了你会发现&#xff0c;它其实是开源圈注意力的晴雨表&#xff1a;某个概念突然冒头、某…

作者头像 李华
网站建设 2026/10/1 4:54:31

树状数组+区间贪心+并行归约:库存批处理性能优化实战

execution并行归约、区间贪心、树状数组&#xff0c;这三样东西单独拿出来都不算冷门&#xff0c;但放在同一个项目里&#xff0c;意味着你遇到的一定是那种“数据量很大、执行窗口很短”的批处理场景。我最近在重构一个库存调度服务时&#xff0c;把这三者硬生生凑到了一起&am…

作者头像 李华
网站建设 2026/10/1 4:54:19

从声线训练到智能体编排:AI工作流闭环的三大实战方向

最近AI圈的更新节奏快得让人有点喘不过气&#xff0c;Suno v5.5、OpenClaw、Codex插件平台这三件事几乎在同一天集中刷屏。但仔细看下来&#xff0c;它们其实是同一个大趋势的三个侧面&#xff1a;AI正在从"能生成内容"走向"能帮你把整个工作流跑完"。Suno…

作者头像 李华
网站建设 2026/10/1 4:54:15

CMake实战指南:从Makefile迁移到多目录构建与动态库生成

简介&#xff1a;这份《CMake实战》PDF面向需要掌握跨平台自动化构建的C/C开发者、嵌入式工程师及在校学生&#xff0c;尤其适合正在从手写Makefile或Autotools转向CMake的中级读者。内容围绕CMake的安装配置、CMakeLists.txt语法、静态库与动态库构建、外部库查找与链接、常用…

作者头像 李华