第二次作业

这个作业属于哪个课程 软件基础课程班级
这个作业的目标 实现英文文本计数程序,通过版本迭代、单元测试和性能分析,练习从需求明确到验证交付的开发过程。
姓名-学号 徐壮-2024329301136

从文本计数到代码行分析:一次 C++ 命令行程序的开发记录

项目仓库:text-counter。单纯关于项目具体可以看README,写的很详细了。

这次作业要实现一个命令行文本计数程序,统计英文 txt 文件中的字符、单词和句子,并尝试增加代码行分类。最终使用 C++17、CMake 和 GoogleTest 完成实现,主要在 VS Code 中操作,性能分析使用 Visual Studio 自带的 CPU 使用率工具。

项目使用了 AI 辅助。代码、测试和文档的编写均有 AI 参与,我负责确认需求和技术方案、审核材料、运行程序、截图及提交。

1793330578389

下面结合实际结果,记录这次作业中几个值得展开的问题。

一、写计数程序之前,先确定“怎么算”

“统计单词数”看起来很明确,落实到具体文本时却会出现分歧:don't 算一个还是两个?well-known 怎么处理?文件最后没有句号,要不要算一句?如果这些规则不先说明,即使程序运行成功,也很难判断输出是否正确。

本项目采用以下口径:

输入情况 处理方式
空格、标点和换行 都计入字符数
Windows 的 CRLF 与 LF 换行 都按一个换行字符计数
文件开头的 UTF-8 BOM 不计入字符数
don't、well-known 各算一个单词
a--b、abc123def 各算两个单词,纯数字不计为单词
Really?! 算一句,连续结束标点不增加空句
文件末尾有文字但没有结束标点 仍计一句

句子统计按 . ! ? 划分,没有加入英文缩写和网址的语义识别。例如 Mr. 中的句点也按结束标点处理。这项规则在文档和测试中保持一致,方便使用者判断程序是否适合自己的文本。

用于核对的样例只有两行:

Hello, world!
How are you? I am fine.

第二行后保留一个换行。两行正文分别是 13、23 个字符,加上两个换行,结果为 38 个字符;单词是 8 个,句子是 3 个。样例足够短,可以人工复核,再与程序结果比较。

v02-文本统计

二、三个版本分别解决了什么

v0.1:先让项目能够构建和运行

第一个版本只有程序入口和构建配置,运行后输出“空项目已建立”。这个阶段的作用是确认源文件能够经过编译和链接生成 wc.exe,不包含统计功能。

最初电脑上有 VS Code,但没有把编译器和 CMake 配置到同一个可用环境中。检查后使用了已有的小熊猫 GCC 工具链,并补齐 CMake。构建时又遇到旧版 MinGW Make 无法正确处理中文路径的问题,最终通过构建期间临时映射英文盘符解决,文件仍保存在原目录。

这一过程把编辑器、构建工具和编译器的分工区分开了:VS Code 用来编辑和操作项目,CMake 根据配置生成构建规则,编译器负责把 C++ 源码转换为可执行程序。

v01-空项目

v0.2:从程序入口扩展到可测试的统计功能

第二个版本加入文件读取、字符统计、单词统计和句子统计,支持如下调用:

wc.exe -c file.txt
wc.exe -w file.txt
wc.exe -s file.txt
wc.exe -a file.txt

实现中没有把全部逻辑放进 main()。命令行入口负责解析选项、打开文件和输出结果,统计模块接收输入流并返回统计结果。这样测试时可以直接提供一小段字符串,不必每个用例都创建文件或启动程序。

三个基础指标在一次扫描中完成。字符经过换行统一处理后计数;单词统计保存是否处于单词内部的状态;句子统计保存当前片段是否出现过字母或数字,基础统计不需要把整篇文本装入内存。

v0.3:在保留基础功能的同时增加行分类

第三个版本增加 -l 选项,输出代码行、空行和注释行。这里最容易出错的地方,是把“出现注释符号”直接等同于“这一行是注释”。

例如:

const char* url = "http://example.com";
int value = 1; // 一行代码后面带注释
/* 块注释
   延续到下一行 */

第一行字符串里的 // 不应改变行分类;第二行虽然有注释,仍然属于代码行;块注释则需要把状态保留到下一行。因此,行分类使用状态机,分别处理普通代码、单行注释、块注释、字符串和字符字面量等情况。

测试还覆盖了原始字符串与 1'000 这样的数字分隔符,避免把数字中的撇号误判为字符字面量的开始。

分类规则是:有代码就算代码行,只有注释及空白就算注释行,只含空白就算空行。三类互斥。样例文件共 11 行,结果为 5 个代码行、2 个空行、4 个注释行。

v03-代码行

各阶段都保留了对应代码与提交,并通过 v0.1、v0.2、v0.3 标签定位。

三、测试不能只验证样例文件

样例输出正确,只能说明这一份输入通过了。实际测试还需要覆盖空文件、不同换行、无末尾换行、连续标点、长文本、中文路径和错误参数。

最终测试分为两层:

层次 数量 检查内容
GoogleTest 单元测试 57 个 24 个文本统计用例、4 个边界用例、29 个代码行分类用例
命令行集成测试 18 个场景 实际启动程序,检查选项、输出、退出码及文件路径

在 CTest 中,整组命令行集成测试算作一项,因此完整结果是 58 项 CTest 通过,单独运行 GoogleTest 则显示 57 个测试通过。两张截图上的数量不同,原因在这里。

v03-测试

测试过程中还出现过预期字符数手算错误的情况。这提醒我,测试失败后需要同时核对实现和预期值,不能直接把程序输出填回测试。对于短文本,应逐字确认字符和换行;对于大量重复文本,可以用“重复行数 × 每行计数”独立计算预期结果。

最终的 Debug、Release 测试均通过。提交前还把准备上传的文件复制到一个没有构建缓存的目录,重新配置、编译并运行测试,确认项目不依赖原来的 build 目录。

四、性能测试分别回答两个问题

文件变大后,需要运行多久

耗时测试使用 Release 构建,生成约 1MiB、10MiB、50MiB 的英文文件。每个文件先预热一次,再测量五次,取中位数;计时范围包含读取和统计,不包含文件打开与控制台输出。

2026 年 10 月 30 日的复测结果如下:

目标规模 实际字符数 单词数 句子数 耗时中位数(毫秒)
1MiB 1,048,582 216,948 72,316 4.847
10MiB 10,485,762 2,169,468 723,156 50.845
50MiB 52,428,810 10,847,340 3,615,780 260.156

环境为 Windows x64、Intel Core i9-13980HX、GCC 11.4.0。测试文件按完整行生成,所以实际大小略大于目标值。从 10MiB 到 50MiB,数据量约增加到 5 倍,耗时约增加到 5.12 倍,与基础统计单次扫描的实现相符。这些结果受机器负载和文件缓存影响,不是所有电脑上的固定耗时。

v03-运行耗时

CPU 采样主要集中在哪里

为了完成 IDE 性能分析,使用 Visual Studio 自带的 CPU 使用率工具分析 wc_profile。它持续处理固定的内存文本,每轮校验计数结果,便于观察统计路径的 CPU 开销。

截图中,textcount::count 的总 CPU 采样占比为 99.73%,自 CPU 占比为 8.87%。两者的区别是:前者包含下级函数调用,后者只计算函数自身,主要外部调用热点位于 msvcp140.dll。

v03-IDE性能分析

这里的百分比是采样占比,不是整机 CPU 利用率,也不是毫秒耗时。文件耗时测试与 CPU 分析采用的工作负载不同,分别记录才能避免混淆。

五、这次作业留下的改进方向

这次作业中,我对方案和文档的多次修改主要围绕同一件事:别人拿到项目后,能否知道它怎么算、怎样运行,以及结果依据是什么。代码、测试、截图和文档需要互相对应。后续也需要继续熟悉状态机和构建脚本,能够独立解释关键分支,并在增加规则时自己补上测试。

AI 使用说明

本项目的方案讨论、代码与测试编写、问题排查和文档整理均使用了 AI 辅助。技术栈和需求由我确认,运行截图和仓库提交由我操作,文档经过人工审核与修改。

仓库内的 AI 使用记录。其在v03的docs里。
168C476CA6292AD2E88F128193FF4501

posted @ 2026-10-30 12:11  xxzzxx  阅读(12)  评论(0)    收藏  举报