近日在写一个统计项目中C/C++文件(后缀名:C/CPP/CC/H/HPP文件)代码行数的小程序。给定包含C/C++代码的目录,统计目录里所有C/C++文件的总代码行数、有效代码行数、注释行数、空白行数。

其中:总代码行数 =(有效代码行数+注释行数+空白行数)

每找到一个目标代码文件,就创建任务投进线程池。线程池的设计基于任务,基于任务相比基于线程的优势,请参考Scott Meyers撰写的Moderm Effective C++一书。

先给出程序运行的效果,见下图:

近5万的代码文件,1183万总代码行数,不到5分钟统计完成,速度还是很快的。有人问了,用5分钟才统计完,怎么也不能说快吧。咱不耍嘴皮子,用结果说话。作为对比的上面那款源码统计专家工具,在对同一个目录里的文件进行分析统计时,半个小时过去还没给出结果。这工具单文件分析都不准,面对5万个文件,结果只怕会错的离谱,运行又慢,我想也没等下去的必要了,果断给×掉了。

需要程序的,可以在文末评论留下邮箱O(∩_∩)O

对代码文件的分析中,空白行好处理,关键在于识别有效代码和注释代码。大的识别原则有:

1.注释符合文法,不能让编译期报错。这是最重要的原则。

2.注释中的空行是空行,原始字符串中的空行不是空行

3.原始字符串里的注释(行注释/块注释)是有效代码,不能当作注释统计

4.注释中的原始字符串是注释,不能当作有效代码统计

为了验证程序逻辑的严密性和准确性,设计了如下的复杂的代码片段:

 1 // idxRawStringStart = curLine.find(R"(R"()");
2 string comment_test = R"({
3 // comment /* with nested comment */
4 // comment
5 "a": "text",
6 /* multi
7 // line-comment-inside-multiline-comment
8 */
9
10 })";
11
12 idxRawStringStart = curLine.find(R"(R"()");
13 idxRawStringStart = curLine.find(R"(R"(\
14 )");
15 /**************************************************************************
16 * 功能描述:
17 **************************************************************************/
18 /* 20 * Since some memmove()'s erroneously fail to handle
21 * overlapping regions, we'll do the shift by hand.
22 */
23 int a = /*b*/ 0;
24 int b = 0 // comment
25 /*a = b*/
26 // int a = b;
27 }
28 /////////////////////////////////////////////////////////////////////////
29 }

这29行代码中,从程序员的角度看,绿色部分是注释共11行,红色部分是代码共16行,line 9是空行但因为包含在原始字符串中,所以是有效代码行。

真正的空行只有 line 11和19,共2行。

对于这么个复杂代码段,市面上某号称源码统计专家的工具给出的结果如下:

可以看到,在面对注释风格多变且复杂的代码文件时,专家工具变“砖”家,结果毫不可信。

而本程序输出结果,完全正确。

上述复杂代码段中,值得关注的是原始字符串的处理,其语法为R"()",小括号内放原始字符串的内容

 std::string comment_test = R"({
// comment /* with nested comment */
"a": ,
// comment
// continued
"b": "text",
/* multi
line
comment
// line-comment-inside-multiline-comment
*/
// and single-line comment
// and single-line comment /* multiline inside single line */
"c": [, , ]
// and single-line comment at end of object
})";

conmment_tes存放的内容是

就是说里面的// 和/* */注释符号不能被认定为注释符号,哪怕它们在其中混用,注释嵌套着注释,不管是多复杂的组合,也是原始字符串的一部分,是有效代码。程序的目的就是利用原始字符串的语法构造规则,找出原始字符串的起始和结束位置。

在识别过程中,本程序做的工作其实是编译期语法/词法分析的一部分功能,这也让我更加了解原始字符串和嵌套注释的语法规则。特别地,发现了vs注释一个有意思的地方。vs注释的快捷键会优先选择/**/风格注释,在/**/不能胜任的地方,会使用//代替。

以前看过一篇文章,建议注释只用//,而不要用/* */,更不要用嵌套的/* */,因为后两种注释加大了文本分析程序解析的难度。 如果注释用//开头,很容易就识别该行代码就是注释了。我在做这个代码行统计小程序时,对这条建议有了更深的体会。用//代替/**/这个建议,应该作为代码规范执行下去。

写程序难免踩坑,在这里记下来,避免后面踩到同样的坑。

1. 在控制台程序输入文件夹路径时,路径如果包含空格,cmd会把空格前后内容当作不同的参数,程序处理参数会错误(路径无效)。把路径用双引号包含起来后就正常了。

2.程序在控制台运行出现中文乱码,工程设置是unicode,程序打印的文件路径含中文,而控制台默认代码页是936,GBK,开发环境和输出环境编码不一致,所以会乱码。

乱码截图一张,图中的问号其实是中文。

为适应控制台的编码,使用setlocale(LC_CTYPE, "")就好了;

3.标准库里的ofstream的<<重载符,对宽字符的处理不好,比如写入std::wstring内容会出错,而使用std::string则不会有问题。但代码路径有中文的情况下,无法输出。解决办法就是把ofstream换成wofstream。同时调用file.imbue(locale("", locale::all ^ locale::numeric));设置中文输出环境。

4.标准库的向量容器push_back(插入元素)非线程安全,在多线程环境下往此容器里写东西时需加锁。当然标准库里的大多数容器操作都是非线程安全的,使用时需谨慎。

统计C/C++代码行数的更多相关文章

  1. VS里统计整个解决方案代码行数的方法

    VS里统计整个解决方案代码行数,在查找里输入正则表达式:b*[^:b#/]+.*$.如下图所示: 结果如下图所示:

  2. Android Stduio统计项目的代码行数

    android studio统计项目的代码行数的步骤如下: 1)按住Ctrl+Shift+A,在弹出的框输入‘find’,然后选择Find in Path.(或者使用快捷键Ctrl+Shift+F) ...

  3. VS 统计整个项目总的代码行数

    vs如何快速统计项目总代码行数呢,如下: vs编辑 | 查找和替换 | 在文件中查找 查找选项选 选择正则表达式 ^b*[^:b#/]+.*$ 设置如下:  结果在查找结果的最后一行,如下 

  4. Xcode 统计项目代码行数及常用快捷键

    1.统计Xcode项目代码行数 1   打开终端. 2  用ls和cd进到你项目的路径. 3   输入下面的指令: grep -r "\n" classes | wc -l (cl ...

  5. Python实现代码行数统计工具

    我们经常想要统计项目的代码行数,但是如果想统计功能比较完善可能就不是那么简单了, 今天我们来看一下如何用python来实现一个代码行统计工具. 思路:首先获取所有文件,然后统计每个文件中代码的行数,最 ...

  6. Git代码行数统计命令

    统计zhangsan在某个时间段内的git新增删除代码行数 git log --author=zhangsan--since=2018-01-01 --until=2019-04-01 --forma ...

  7. Python-统计svn变更代码行数

    1 #!/bin/bash/python 2 # -*-coding:utf-8-*- 3 #svn统计不同url代码行数变更脚本,过滤空行,不过滤注释. 4 import subprocess,os ...

  8. python 脚本(获取指定文件夹、指定文件格式、的代码行数、注释行数)

    1.代码的运行结果: 获取 指定文件夹下.指定文件格式 文件的: 总代码行数.总注释行数(需指定注释格式).总空行数: #coding: utf-8 import os, re # 代码所在目录 FI ...

  9. python3 计算文件夹中所有py文件里面代码行数,注释行数,空行数

    import os,re #代码所在位置 FILE_PATH = './' def analyze_code(codefilesource): ''' 打开一个py文件统计其中的代码行数,包括空格和注 ...

随机推荐

  1. C#设计模式(1)——单例模式(Singleton)

    单例模式即所谓的一个类只能有一个实例, 也就是类只能在内部实例一次,然后提供这一实例,外部无法对此类实例化. 单例模式的特点: 1.只能有一个实例: 2.只能自己创建自己的唯一实例: 3.必须给所有其 ...

  2. django 验证码实现

    django验证码的使用: 验证码的作用:用于人机识别. 验证码 ###验证码: def code_str(request): from PIL import Image from PIL impor ...

  3. SpringBoot 试手(简易的SpringBoot搭建步骤)

    SpringBoot 也算AI吧,它根据您架构中引用的依赖,自动化地按默认方案帮您完成了Spring那些复杂繁琐的配置工作.为了让您不会看低此 AI 水平,还特地喊出了“约定大于配置”的口号.从这个角 ...

  4. 远程桌面 把explorer关掉了

    用Ctrl+Alt+End调出远程桌面的任务管理器.然后,运行explorer.exe即可重启该服务.

  5. Java ArrayList调用构造方法传入"容量size"不生效,如何初始化List容量size

    创建一个ArrayList对象,传入整型参数 @Test public void arrayListConstructor(){ ArrayList<Object> objects = n ...

  6. c# NPOI 导出EXCEL (在下方显示图片)

    需要引入dll文件 也可以在NuGet里面管理(推荐) 比较方便 . using System; using System.Collections.Generic; using System.Linq ...

  7. oracle in和exist的区别 not in 和not exist的区别

    in 是把外表和内表作hash join,而exists是对外表作loop,每次loop再对内表进行查询.一般大家都认为exists比in语句的效率要高,这种说法其实是不准确的,这个是要区分环境的. ...

  8. 【python深入】collections-Counter使用总结

    关于collections的使用,首先介绍:Counter的使用 需要执行:from collections import Counter 在很多使用到dict和次数的场景下,Python中用Coun ...

  9. swift hidesBottomBarWhenPushed 设置界面

    方法一(推荐):一级界面push的时候设置,子页面无需设置 let vc = JYMyCommissionController() vc.hidesBottomBarWhenPushed = true ...

  10. Java高级

    1.GC是什么?为什么要有GC? GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供 ...