Python实现一个代码行数统计工具(以C/C++为例)

article/2025/10/25 15:59:40

  前几天在网上看到一个有意思的题,题目是设计一个代码行数统计工具。这类工具我经常会用到,但是具体是如何实现的呢?这个问题我还从未思考过,于是便试着做出这种工具。

题目描述是这样的:

题目要求:
  请设计一个命令行程序:使用多线程,统计C\C++程序语言程序源代码行数;源代码是可以编译通过的合法的代码,统计其物理行数、其中的空行行数、其中含有的有效代码行数、其中含有的注释行数。

冲突处理:
  在多行注释内的空行不算作注释行;一行中同时有代码和注释,需要同时算作有效代码行和注释行。

提示
  注意换行符和续行符、注意字符和字符串内部的处理和转义符、不要使用正则表达式、要求自己独立完成答题;如果代码框架能更容易的扩展到支持多种语言的源代码行数统计,将获得更高的评价。

提交要求:

  • 命令行程序的输入:从命令行给程序输入一个文件夹的路径作为参数;程序统计其中(包含子文件夹)源代码文件的行数信息;(以参数方式传递路径,如启动命令counter.exe c:\src,不要在程序执行过程中手动输入任何内容,程序完成要自动退出)。

  • 命令行程序的输出:输出结果到标准输出,每个代码文件输出一行结果,格式为 file:src.cpp total:123 empty:123 effective:123 comment:123 依次为文件名file(带工程目录内的相对路径)、物理行数total、空行行数empty、 有效代码行数effective、注释行数comment,每个数据以 key:value 的形式输出, key为以上使用的单词,请勿使用其他单词命名,数据间以空格分隔。

  • 请附上最终汇总统计结果,并以以下格式展示:
    ----------------------------------------------------------
    - Files Lines Code Comments Blanks
    ----------------------------------------------------------
    -
    10 100 100 39 40
    ----------------------------------------------------------
    -

  首先,这道题需要我们统计的是一个文件夹及其子文件夹中的所有源代码文件行数,所以第一步我们应该搜索符合条件的文件。

先以Windows环境下,以C/C++ 为例,我们需要搜索的文件扩展名为:.c、.h、.cpp、.hpp。利用递归实现对文件的搜索,代码如下:

def get_filelist(path, Filelist):extendList = ['.c', '.h', '.cpp', '.hpp']newPath = pathif os.path.isfile(path) and os.path.splitext(path)[1] in extendList:#文件扩展名属于列表中其中一种时,文件路径添加到filelist中Filelist.append(path)elif os.path.isdir(path):#路径为目录时,遍历目录下的所有文件和目录for s in os.listdir(path):newPath=os.path.join(path, s)Counter.get_filelist(newPath, Filelist)return Filelist

  完成对文件的搜索后,下一步就是读取文件,然后逐行分析。
   1. 首先是对物理行数total的统计,物理行数也就是文件所有内容的总行数,不受续行符的影响。统计方法是读取文件后,获取readlines()方法返回元素个数。

# 打开文件并获取所有行
fp = open(filename, encoding = 'gbk', errors = 'ignore')
lines = fp.readlines()total = len(lines) # 总行数

   需要注意的是,如果文件最后一行内容为空行,是不会存在列表中的。这是因为最后一行出现空行的原因是倒数第二行的末尾出现了换行符,而最后一行实际上什么都没有,所以不会存在列表中。这种情况需要另外进行判断。这一行空行是否要加入计数中也需要根据需求来,例如比较常用的代码行统计工具cloc工具是不计最后一行空行的,而vscode 用于统计代码行的扩展 VS Code Counter是计了最后一行空行的。如果要记录最后一行空行,则在所有行遍历结束后加上

if(lines[-1][-1] == '\n'):total += 1 # 总行数+1empty += 1 # 空白行+1

  对物理行的计数记录下来之后,我们开始进行每行遍历,每一行在遍历开始前,需要去除行两边的空白符,还需要查看最后一个字符是否为续行符,若为续行符,则将本行内容拼接到下一行前面,然后跳过本行遍历进入下一行,再继续判断末尾是否有续行符。(这一步造成了空间在一定程度上的浪费,建议优化一下,笔者就懒得写了)

for line in lines:line = temp + lineline = line.strip("\r\t ") # 去除两端空白if line[-1] == "\\": # 检查末尾是否有续行符,若有续行符,则保存当前line值,准备与下一行进行拼接temp += line[:-1]continueelse:temp = ""

   2.对空白行blank的计数就比较简单了,遍历到的行如果只有一个字符’\n’则空白行数加一。

lineLen = len(line)if lineLen == 1 and line == '\n':#空行,空行数量+1empty += 1continue

   3. 对有效代码行code行的计数,可以判断遇到的符号是不是注释符或当前状态是否在注释状态中,如果不是则为有效代码行,我们用"is_effective_code"来记录该行是否为有效代码行。
   4. 对注释行comments的计数,需要特别注意的是题目中提到若同一行中有有效代码和注释,则此时代码行数和注释行数都要加一,这一点与cloc和vs code couter 都不同。我的思路是可以判断是否遇到了注释符,如果遇到的符号为’//‘则进入行注释状态,直到行字符遍历完毕,注释行加一。如果遇到了 ‘/*’ 则进入块注释状态,此时保留块注释状态去遍历接下来的每一行,每遍历完一行则注释行加一,直到遇到块注释结束符’*/'退出块注释模式。退出时,此行遍历不一定结束,程序仍需要进行后续字符的遍历,所以别忘了退出时要将退出行也加上。
   对于注释行的判断,还有需要注意的地方,若注释符出现在字符串中的话,是不能够起到注释效果的,所以需要先考虑是否进入了字符串模式。(但是cloc和vs code couter都是将双引号中间的注释符当做是正常注释状态了,这部分有待讨论)

3和4 我们会遇到好几种状态,为保证代码的可读性,我们定义一组枚举变量表示目前处于哪种状态下。

# 将可能遇到的情况枚举
# Init:表示初始状态
# Common:表示普通状态 
# CharString:表示字符串状态 
# LineComment:表示行注释状态 
# BlockComments:表示块注释状态 
Status = Enum('Status','Init Common CharString LineComment BlockComments')

综上,3.&4.的实现代码如下

skipStep = 0 # 需要跳过的字符数,用于跳过一些符号,例如遇到//时进入行注释状态,跳过到//后面第一个
is_effective_code = False # 有效代码行标识for i in range(lineLen):if skipStep != 0:skipStep -= 1continueif row_cur_status == Status.Common:# 普通状态下if line[i] == '"' or line[i] =="'":row_cur_status = Status.CharString # 切换到字符串状态CharStringStart = line[i] # 记录字符串开始时的标识符,用于判断后续退出位置continue# 检查是否进入行注释状态if i + 1 < lineLen and line[i:i + 2] == '//':row_cur_status = Status.LineComment # 切换到行注释状态skipStep = 1continue# 检查是否进入块注释状态if i + 1 < lineLen and line[i:i + 2] == '/*':row_cur_status = Status.BlockComments # 切换到块注释状态skipStep = 1continueif line[i] == '\n':continueif line[i] == ' ':continueelse:is_effective_code = True # 代码行有效continueelif row_cur_status == Status.CharString:#字符串状态下if line[i] == CharStringStart:row_cur_status = Status.Common # 字符串结束,切换回普通状态 is_effective_code = True # 字符串也属于有效代码continueelse:continueelif row_cur_status == Status.BlockComments:# 块注释状态下# 检查是否退出块注释状态if i + 1 < lineLen and line[i:i + 2] == '*/':# 退出块注释,注释行加上块注释的最后一行,切换回普通状态comment_numbers += 1row_cur_status = Status.CommonskipStep = 1continueelse:continue# 单行遍历结束后,以当前状态记录行数
# 代码行有效,有效代码行数+1
if is_effective_code == True:codes_numbers += 1# 当前状态为块注释或行注释状态下,注释代码行数+1
if row_cur_status in (Status.BlockComments, Status.LineComment):comment_numbers += 1# 当前状态不为块注释时,进入下一行前,初始化当前状态
if row_cur_status != Status.BlockComments:row_cur_status = Status.Common

最后我们加上一点点细节处理和输出,在main函数里加上一点点多线程,整合后的完整版如下:

from queue import Empty
import sys
import os
from enum import Enum
import time
from unittest.mock import patch
import threadingclass Counter:Line_numbers = 0Code = 0total_comment_numbers = 0Blanks = 0def get_filelist(path, Filelist):extendList = ['.c', '.h', '.cpp', '.hpp']newPath = pathif os.path.isfile(path) and os.path.splitext(path)[1] in extendList:#文件扩展名属于列表中其中一种时,文件路径添加到filelist中Filelist.append(path)elif os.path.isdir(path):#路径为目录时,遍历目录下的所有文件和目录for s in os.listdir(path):newPath=os.path.join(path, s)Counter.get_filelist(newPath, Filelist)return Filelistdef CodeCounter(filename, path):codes_numbers = 0empty = 0comment_numbers = 0# 打开文件并获取所有行fp = open(filename, encoding = 'gbk', errors = 'ignore')lines = fp.readlines()row_cur_status = Status.Common # 设置初始状态为Commontemp = ""for line in lines:line = temp + lineline = line.strip("\r\t ") # 去除两端空白if line[-1] == "\\": # 检查末尾是否有续行符,若有续行符,则保存当前line值,准备与下一行进行拼接temp += line[:-1]continueelse:temp = ""lineLen = len(line)if lineLen == 1 and line == '\n':#空行,空行数量+1empty += 1continueskipStep = 0 # 需要跳过的字符数,用于跳过一些符号,例如遇到//时进入行注释状态,跳过到//后面第一个字符is_effective_code = False # 有效代码行标识for i in range(lineLen):if skipStep != 0:skipStep -= 1continueif row_cur_status == Status.Common:# 普通状态下if line[i] == '"' or line[i] =="'":row_cur_status = Status.CharString # 切换到字符串状态CharStringStart = line[i] # 记录字符串开始时的标识符,用于判断后续退出位置continue# 检查是否进入行注释状态if i + 1 < lineLen and line[i:i + 2] == '//':row_cur_status = Status.LineComment # 切换到行注释状态skipStep = 1continue# 检查是否进入块注释状态if i + 1 < lineLen and line[i:i + 2] == '/*':row_cur_status = Status.BlockComments # 切换到块注释状态skipStep = 1continueif line[i] == '\n':continueif line[i] == ' ':continueelse:is_effective_code = True # 代码行有效continueelif row_cur_status == Status.CharString:#字符串状态下if line[i] == CharStringStart:row_cur_status = Status.Common # 字符串结束,切换回普通状态 is_effective_code = True # 字符串也属于有效代码continueelse:continueelif row_cur_status == Status.BlockComments:# 块注释状态下# 检查是否退出块注释状态if i + 1 < lineLen and line[i:i + 2] == '*/':# 退出块注释,注释行加上块注释的最后一行,切换回普通状态comment_numbers += 1row_cur_status = Status.CommonskipStep = 1continueelse:continue# 单行遍历结束后,以当前状态记录行数# 代码行有效,有效代码行数+1if is_effective_code == True:codes_numbers += 1# 当前状态为块注释或行注释状态下,注释代码行数+1if row_cur_status in (Status.BlockComments, Status.LineComment):comment_numbers += 1# 当前状态不为块注释时,进入下一行前,初始化当前状态if row_cur_status != Status.BlockComments:row_cur_status = Status.Commontotal = len(lines)        if(lines[-1][-1] == '\n'):total += 1empty += 1fp.close()print("file:{0} total:{1} empty:{2} effective:{3} comment:{4}".format(filename.replace(path + "\\", ""), total, empty, codes_numbers, comment_numbers))Counter.Line_numbers += totalCounter.Blanks += emptyCounter.Code += codes_numbersCounter.total_comment_numbers += comment_numbersif __name__ == "__main__":path = os.path.abspath(sys.argv[1]) #获取命令行输入的文件夹绝对路径# path = r"C:\Users\Undefined\Desktop\test\Osiris"list = Counter.get_filelist(path, [])threads = []# 将可能遇到的情况枚举# Common:表示普通状态 # CharString:表示字符串状态 # LineComment:表示行注释状态 # BlockComments:表示块注释状态 Status = Enum('Status','Init Common CharString LineComment BlockComments')for file in  list:t = threading.Thread(target=Counter.CodeCounter,args=(file, path))threads.append(t)for thr in threads:thr.start()for the in threads:    thr.join()time.sleep(0.1)print("-"*56)print("- {0:<10} {1:<10} {2:<10} {3:<10} {4:<10}".format("Files", "Lines", "Code", "Comments", "Blanks"))print("-"*56)print("  {0:<10} {1:<10} {2:<10} {3:<10} {4:<10}".format(len(list), Counter.Line_numbers, Counter.Code, Counter.total_comment_numbers, Counter.Blanks))print("-"*56)

最后奉上部分结果截图
在这里插入图片描述

那么问题来了,题目还有一个要求是尽可能容易扩展到其他语言的代码行,这时候我们可以把不同语言的续行符、行注释符、块注释符等符号放入列表或者字典中,每次判断该语言有哪些相对应的符号即可。我按照自己的想法做了一个,代码太多我怕太乱了,放到了另一篇文章中,详细代码见我的另一篇文章。
Python实现一个代码行数统计工具(易拓展到其他语言版)

第一次写比较长的文章,表达的可能不是很清晰,文章若有任何不明白的地方可以私聊我🙆‍♂️
若文中有错误,还请大佬在评论区指正,我会好好学习和改进,谢谢大佬们🙇


http://chatgpt.dhexx.cn/article/MNlEfxjr.shtml

相关文章

统计项目代码行数工具cloc

Ubuntu用户 使用cloc在ubuntu内统计代码行数 安装cloc工具 sudo apt-get install cloc进入需要统计的目录内&#xff0c;然后执行 cloc .然后就会显示文件目录中的文件数(files)、空白行数(blank)、注释行数(comment)和代码行数(code)。 Windows 用户 也是使用cloc工具 …

Win10 代码行数统计工具CLOC的安装和使用

简介 CLOC(Count Lines of Code)&#xff0c;是一个可以统计多种编程语言中空行、评论行和物理行的工具。这个工具还是蛮实用的&#xff0c;可以帮我们快速了解一个项目中代码的信息。 注&#xff1a;底下这个命令可以实现统计代码行数的功能&#xff0c;只是不排除空行和注释…

代码行数统计小工具

一、先下载好SourceCounter小工具。解压&#xff0c;然后直接打开文件夹中的SourceCounter.exe。如果没有找到此工具的下载链接&#xff0c;点这里下载 二、选择代码类型&#xff0c;勾选上所有类型 三、双击点开后&#xff0c;选择文件夹&#xff0c;就可以直接统计出字…

在项目开发中统计代码行数的6种方式

文章目录 一、使用find和wc命令统计代码行数进行参数的过滤筛选命令参数简要说明 二、PowerShell工具统计代码行数条件过滤输出所有文件的行数PowerShell相关命令的简要说明 三、git命令git ls-filesgit log 四、代码编辑器插件五、jscpd六、自己实现一个注释和空行忽略目录和文…

chatgpt赋能python:Python代码行数统计-统计Python代码行数的常用工具与使用方法

Python代码行数统计 - 统计Python代码行数的常用工具与使用方法 Python编程语言是当今最流行的编程语言之一&#xff0c;在数据科学、人工智能、Web应用程序等许多领域都得到了广泛应用。当我们开发Python项目时&#xff0c;我们经常需要统计代码行数以管理代码库并监视进度。…

局域网电脑使用同一台鼠标键盘控制

问题又来了,我现在有两个电脑&#xff0c;局域网相连&#xff0c;但是我只有一套键盘鼠标啊&#xff0c;办公特别不方便&#xff0c;现在有工具可以让我们达到这个目的。 微软推出的 Mouse without Borders (无界鼠标)&#xff0c;这是一个免费的工具大家可以放心。下载地址为…

用一套键鼠控制两台主机

自己最近做一个项目涉及到以下场景&#xff1a;需要在一台window10系统的电脑上看一些文档&#xff0c;同时在一台装Ubuntu1804的电脑上跑一些代码。因此需要同时用两套鼠标键盘&#xff0c;切换起来十分麻烦&#xff0c;而且经常会拿错。因此参考博客解决了用一套键鼠控制两台…

Synergy两台电脑使用同一个鼠标和键盘

###分享一款共享鼠标和键盘的软件&#xff0c;即两台电脑使用同一个鼠标和键盘&#xff01; Synergy是一款跨平台的键盘鼠标共享软件&#xff0c;日前我们提供了Synergy 和Synergy 64位的Win版本、Synergy Mac版&#xff0c;Synergy 能够让使用者仅用一套键盘鼠标&#xff0c;就…

如何把笔记本做台式机的副屏(一套键鼠控制两台电脑)

通过一套键鼠控制两台电脑 前提安装所需的软件一、 简介二、 安装2.1 小技巧 前提 两台电脑在同一个局域网内&#xff0c;并且均为windows操作系统 例如两台电脑链接的同一个WIFI&#xff0c;或者笔记本无线连接路由&#xff0c;台式机插网线链接路由 只有在同一个局域网内才能…

用一个键盘和鼠标控制两台双显示器计算机的最佳方法是什么?

If you have two awesome dual-monitor computers and want to easily switch back and forth between them using a single keyboard and mouse, then what is the best way to go about it? Today’s SuperUser Q&A post provides some great suggestions for a reader’…

怎么用一个计算机控制两个屏幕,一台电脑带2个显示器,这样操作就对了,双屏显示操作更方便...

为了提高操作电脑的工作效率,许多用户决定将两台显示器连接到台式电脑或笔记本电脑,在两个显示器上工作,可以扩展桌面空间,在需要的时候还可以将图像从其中一个屏幕复制到另一个屏幕上。你可以将两台显示器连接到几乎任何台式计算机或笔记本电脑,在Windows 7和Windows 10操…

python两台电脑文件传输_两台电脑怎么互相连接传送文件?

相信许多购买了新电脑的朋友最头疼的事儿就是如何把旧电脑里的资料拷贝到新电脑里。因为,文件太大用移动硬盘实在麻烦,网络社交工具在线传送速度太慢,网线对拷既要求交叉线又搞不懂IP设置,难!难!难! 那么,有没有一种简单又粗暴的方式可以实现电脑数据对拷呢? 绿联USB …

图文教程使用一套键鼠控制两台电脑

现在天气太热了,我一直呆在实验室,实验室原本有一台电脑,配置不太好,但是毕竟是台式机,硬盘快的一笔,CPU也是I5的,所以我想利用一下那台机器,和我的笔记本优势互补。 第一步:首先你要有起码2台电脑。 都装上windows系统,不同版本没有关系,我是win7和win8 第二步:连…

【两台电脑之间实现鼠标键盘共享】

文章目录 前言一、实现前提二、步骤1.下载与安装软件2.两台电脑的配对&#xff08;姑且这么叫&#xff09; 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 在工作的过程中有遇到需要使用两台电脑&#xff0c;但是两台电脑有这个各自的鼠标键盘&#xf…

计算机安装两个键盘会怎样,外设门诊:一个键盘能否连接两个接收器?

1一个键盘能否连接两个接收器 [中关村在线键鼠频道原创] 用户及玩家在日常使用键鼠外设产品时,经常会出现这样或那样的问题。小到驱动下载安装,大到更换线材、MOD改造等等,如果您想得到及时丰富的解答,请到电竞游戏装备区论坛》发帖,说出您心底的疑惑,中关村在线编辑及网…

2台电脑共享一套键盘鼠标

###分享一款共享鼠标和键盘的软件&#xff0c;即两台电脑使用同一个鼠标和键盘&#xff01;Synergy是一款跨平台的键盘鼠标共享软件&#xff0c;日前我们提供了Synergy 和Synergy 64位的Win版本、Synergy Mac版&#xff0c;Synergy 能够让使用者仅用一套键盘鼠标&#xff0c;就…

计算机主机如何控制所有的电脑,教你如何让一个鼠标和控制两台电脑

1简介 synergy是个开源的软件&#xff0c;有三个版本 mac / linux / windows&#xff0c;要想实现共享鼠标键盘&#xff0c;必须在所有机器上都安装这个软件&#xff0c;并进行相应的配置&#xff0c;有一台唯一的主机作为服务器端&#xff0c;其他主机作为客户端&#xff0c;要…

两台计算机共享鼠标,总算发现什么是双模键盘(两台电脑共用一套鼠标键盘)

这次的键盘评测比较特殊&#xff0c;是在家里玩成的&#xff0c;键盘性价比颇高&#xff0c;从购买到使用也就一周多的时间&#xff0c;因为最近评测写得比较多&#xff0c;主要是打字用&#xff0c;所以我就选择的青轴&#xff0c;当然这个价格只能买国产轴了。(至于售价我会在…

【高效办公】一个鼠标键盘控制两台电脑-synergy

1.发现Synergy 今天我在苏宁易购买的机械师创物者笔记本电脑到啦&#xff0c;这是为了替换我大学4年用的thinkpad&#xff0c;所以这下我就有了两台笔记本电脑。这旧电脑虽然卡&#xff0c;但它仍然有使用的价值&#xff0c;比如申请专利得用2003版的office&#xff0c;比如运…

synergy一套键鼠控制两台电脑

1. 目的 使用一套键鼠来控制两台主机&#xff0c;win10和ubuntu18。 Synergy是一个键盘和鼠标共享工具&#xff0c;可以跨终端进行使用。 该方案的实现必须使两台设备在同一局域网下&#xff0c;两种设备安装的Synergy版本最好一致&#xff0c;版本不一致为测试&#xff0c;可…