Python+PyQt5設(shè)計(jì)實(shí)現(xiàn)一個C++源代碼行數(shù)統(tǒng)計(jì)工具
引言
在軟件開發(fā)過程中,代碼行數(shù)(Lines of Code, LOC)是一個基礎(chǔ)但重要的軟件度量指標(biāo)。雖然它不能完全代表代碼質(zhì)量或開發(fā)效率,但在項(xiàng)目規(guī)模評估、進(jìn)度跟蹤和復(fù)雜度分析中仍然具有參考價(jià)值。對于C++項(xiàng)目而言,由于頭文件(.h)和實(shí)現(xiàn)文件(.cpp)的分離,需要一個專門的工具來準(zhǔn)確統(tǒng)計(jì)源代碼規(guī)模。
本文介紹一個基于PyQt5的圖形化C++源代碼行數(shù)統(tǒng)計(jì)工具,該工具能夠遞歸遍歷指定目錄,統(tǒng)計(jì)所有C++源文件的行數(shù),并提供詳細(xì)的分類統(tǒng)計(jì)結(jié)果。

程序設(shè)計(jì)架構(gòu)
整體架構(gòu)設(shè)計(jì)
該工具采用經(jīng)典的Model-View-Controller(MVC)架構(gòu)模式,但在PyQt5的上下文中有所調(diào)整:
- View層:由PyQt5的各類窗口部件組成,負(fù)責(zé)用戶交互和結(jié)果展示
- Controller層:處理用戶事件,協(xié)調(diào)View和Model之間的數(shù)據(jù)流
- Model層:文件統(tǒng)計(jì)線程,負(fù)責(zé)實(shí)際的文件遍歷和行數(shù)統(tǒng)計(jì)
多線程設(shè)計(jì)
考慮到大型項(xiàng)目可能包含數(shù)千個源文件,統(tǒng)計(jì)過程可能耗時(shí)較長,因此采用多線程設(shè)計(jì)至關(guān)重要。主線程負(fù)責(zé)UI渲染和事件處理,工作線程負(fù)責(zé)文件統(tǒng)計(jì),通過PyQt5的信號-槽機(jī)制實(shí)現(xiàn)線程間通信。

其中Ttotal?是總耗時(shí), Ttraverse?是文件遍歷時(shí)間, Tread?是文件讀取時(shí)間,Tcount?是行數(shù)統(tǒng)計(jì)時(shí)間。
核心模塊實(shí)現(xiàn)
主窗口類設(shè)計(jì)
import sys
import os
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QTextEdit, QFileDialog,
QWidget, QProgressBar, QMessageBox, QSplitter)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QFont
class CodeLineCounter(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
self.selected_directory = ""
self.count_thread = None
def init_ui(self):
self.setWindowTitle('C++源代碼行數(shù)統(tǒng)計(jì)工具')
self.setGeometry(100, 100, 900, 700)
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# 創(chuàng)建界面組件
self.create_control_panel(main_layout)
self.create_progress_bar(main_layout)
self.create_result_display(main_layout)
def create_control_panel(self, parent_layout):
control_layout = QHBoxLayout()
self.select_btn = QPushButton('選擇目錄')
self.select_btn.clicked.connect(self.select_directory)
self.count_btn = QPushButton('開始統(tǒng)計(jì)')
self.count_btn.clicked.connect(self.start_counting)
self.count_btn.setEnabled(False)
self.path_label = QLabel('未選擇目錄')
self.path_label.setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; }")
control_layout.addWidget(self.select_btn)
control_layout.addWidget(self.count_btn)
control_layout.addWidget(self.path_label, 1)
parent_layout.addLayout(control_layout)
def create_progress_bar(self, parent_layout):
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
parent_layout.addWidget(self.progress_bar)
def create_result_display(self, parent_layout):
self.result_label = QLabel('請選擇包含C++源代碼的目錄')
self.result_label.setAlignment(Qt.AlignCenter)
self.result_label.setStyleSheet("QLabel { font-size: 14px; padding: 10px; }")
parent_layout.addWidget(self.result_label)
splitter = QSplitter(Qt.Vertical)
self.file_list = QTextEdit()
self.file_list.setReadOnly(True)
self.file_list.setPlaceholderText('文件統(tǒng)計(jì)結(jié)果將顯示在這里...')
self.file_list.setFont(QFont("Consolas", 9))
self.detail_text = QTextEdit()
self.detail_text.setReadOnly(True)
self.detail_text.setPlaceholderText('詳細(xì)統(tǒng)計(jì)信息將顯示在這里...')
self.detail_text.setFont(QFont("Consolas", 9))
splitter.addWidget(self.file_list)
splitter.addWidget(self.detail_text)
splitter.setSizes([400, 200])
parent_layout.addWidget(splitter, 1)
文件統(tǒng)計(jì)線程類
class FileCounterThread(QThread):
progress_updated = pyqtSignal(int)
file_counted = pyqtSignal(str, int)
finished_counting = pyqtSignal(dict, int)
def __init__(self, directory):
super().__init__()
self.directory = directory
self.file_extensions = ['.h', '.cpp', '.hpp', '.cc', '.cxx', '.c', '.hh']
def run(self):
file_results = {}
total_lines = 0
file_count = 0
cpp_files = self.find_cpp_files()
total_files = len(cpp_files)
for i, file_path in enumerate(cpp_files):
line_count = self.count_file_lines(file_path)
if line_count >= 0:
file_results[file_path] = line_count
total_lines += line_count
self.file_counted.emit(file_path, line_count)
progress = int((i + 1) / total_files * 100)
self.progress_updated.emit(progress)
self.finished_counting.emit(file_results, total_lines)
def find_cpp_files(self):
cpp_files = []
for root, dirs, files in os.walk(self.directory):
for file in files:
if any(file.lower().endswith(ext) for ext in self.file_extensions):
cpp_files.append(os.path.join(root, file))
return cpp_files
def count_file_lines(self, file_path):
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
return len(lines)
except Exception as e:
print(f"無法讀取文件 {file_path}: {e}")
return -1
事件處理邏輯
class CodeLineCounter(QMainWindow):
# ... 前面的代碼 ...
def select_directory(self):
directory = QFileDialog.getExistingDirectory(
self,
'選擇包含C++源代碼的目錄',
'',
QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks
)
if directory:
self.selected_directory = directory
self.path_label.setText(directory)
self.count_btn.setEnabled(True)
self.result_label.setText('目錄已選擇,點(diǎn)擊"開始統(tǒng)計(jì)"按鈕進(jìn)行統(tǒng)計(jì)')
def start_counting(self):
if not self.selected_directory:
QMessageBox.warning(self, '警告', '請先選擇目錄!')
return
self.reset_ui_for_counting()
self.start_counting_thread()
def reset_ui_for_counting(self):
self.file_list.clear()
self.detail_text.clear()
self.progress_bar.setVisible(True)
self.progress_bar.setValue(0)
self.count_btn.setEnabled(False)
self.select_btn.setEnabled(False)
self.result_label.setText('正在統(tǒng)計(jì)中...')
def start_counting_thread(self):
self.count_thread = FileCounterThread(self.selected_directory)
self.count_thread.file_counted.connect(self.on_file_counted)
self.count_thread.progress_updated.connect(self.progress_bar.setValue)
self.count_thread.finished_counting.connect(self.on_counting_finished)
self.count_thread.start()
def on_file_counted(self, file_path, line_count):
rel_path = os.path.relpath(file_path, self.selected_directory)
self.file_list.append(f"{rel_path}: {line_count} 行")
def on_counting_finished(self, file_results, total_lines):
self.progress_bar.setVisible(False)
self.count_btn.setEnabled(True)
self.select_btn.setEnabled(True)
self.display_final_results(file_results, total_lines)
def display_final_results(self, file_results, total_lines):
file_types = self.analyze_file_types(file_results)
detail_text = self.generate_summary_text(file_results, total_lines, file_types)
detail_text += self.generate_file_type_statistics(file_types)
detail_text += self.generate_top_files_list(file_results)
self.detail_text.setText(detail_text)
self.result_label.setText(f'統(tǒng)計(jì)完成!共 {len(file_results)} 個文件,總行數(shù): {total_lines:,}')
def analyze_file_types(self, file_results):
file_types = {}
for file_path in file_results.keys():
ext = os.path.splitext(file_path)[1].lower()
file_types[ext] = file_types.get(ext, 0) + 1
return file_types
def generate_summary_text(self, file_results, total_lines, file_types):
text = f"統(tǒng)計(jì)完成!\n"
text += f"=" * 50 + "\n"
text += f"目錄: {self.selected_directory}\n"
text += f"總文件數(shù): {len(file_results)}\n"
text += f"總行數(shù): {total_lines:,}\n\n"
return text
def generate_file_type_statistics(self, file_types):
text = "文件類型統(tǒng)計(jì):\n"
for ext, count in sorted(file_types.items()):
text += f" {ext}: {count} 個文件\n"
return text
def generate_top_files_list(self, file_results):
if not file_results:
return ""
sorted_files = sorted(file_results.items(), key=lambda x: x[1], reverse=True)
text = f"\n行數(shù)最多的文件 (前10個):\n"
for i, (file_path, line_count) in enumerate(sorted_files[:10]):
rel_path = os.path.relpath(file_path, self.selected_directory)
text += f" {i+1}. {rel_path}: {line_count:,} 行\(zhòng)n"
return text
算法分析與優(yōu)化
文件遍歷算法
文件遍歷采用深度優(yōu)先搜索(DFS)策略,使用os.walk()函數(shù)遞歸遍歷目錄樹。該算法的時(shí)間復(fù)雜度為O(n),其中 n是目錄中的文件和子目錄總數(shù)。

其中d是目錄樹的最大深度,bi?是第 i 層的分支因子。

行數(shù)統(tǒng)計(jì)算法
行數(shù)統(tǒng)計(jì)采用簡單的逐行讀取方法,對于每個文件:
- 打開文件并設(shè)置適當(dāng)?shù)木幋a處理
- 讀取所有行到內(nèi)存中
- 統(tǒng)計(jì)行數(shù)
這種方法的時(shí)間復(fù)雜度為O(m),其中 m 是文件中的行數(shù)。
內(nèi)存優(yōu)化策略
對于極大的源文件,我們采用流式讀取而非一次性加載到內(nèi)存:
def count_file_lines_streaming(file_path):
try:
line_count = 0
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
line_count += 1
return line_count
except Exception as e:
print(f"無法讀取文件 {file_path}: {e}")
return -1
完整可運(yùn)行代碼
import sys
import os
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QTextEdit, QFileDialog,
QWidget, QProgressBar, QMessageBox, QSplitter)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QFont
class FileCounterThread(QThread):
progress_updated = pyqtSignal(int)
file_counted = pyqtSignal(str, int)
finished_counting = pyqtSignal(dict, int)
def __init__(self, directory):
super().__init__()
self.directory = directory
self.file_extensions = ['.h', '.cpp', '.hpp', '.cc', '.cxx', '.c', '.hh']
def run(self):
file_results = {}
total_lines = 0
cpp_files = self.find_cpp_files()
total_files = len(cpp_files)
for i, file_path in enumerate(cpp_files):
line_count = self.count_file_lines(file_path)
if line_count >= 0:
file_results[file_path] = line_count
total_lines += line_count
self.file_counted.emit(file_path, line_count)
progress = int((i + 1) / total_files * 100)
self.progress_updated.emit(progress)
self.finished_counting.emit(file_results, total_lines)
def find_cpp_files(self):
cpp_files = []
for root, dirs, files in os.walk(self.directory):
for file in files:
if any(file.lower().endswith(ext) for ext in self.file_extensions):
cpp_files.append(os.path.join(root, file))
return cpp_files
def count_file_lines(self, file_path):
try:
line_count = 0
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
line_count += 1
return line_count
except Exception as e:
print(f"無法讀取文件 {file_path}: {e}")
return -1
class CodeLineCounter(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
self.selected_directory = ""
self.count_thread = None
def init_ui(self):
self.setWindowTitle('C++源代碼行數(shù)統(tǒng)計(jì)工具')
self.setGeometry(100, 100, 900, 700)
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
control_layout = QHBoxLayout()
self.select_btn = QPushButton('選擇目錄')
self.select_btn.clicked.connect(self.select_directory)
self.count_btn = QPushButton('開始統(tǒng)計(jì)')
self.count_btn.clicked.connect(self.start_counting)
self.count_btn.setEnabled(False)
self.path_label = QLabel('未選擇目錄')
self.path_label.setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; }")
control_layout.addWidget(self.select_btn)
control_layout.addWidget(self.count_btn)
control_layout.addWidget(self.path_label, 1)
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
self.result_label = QLabel('請選擇包含C++源代碼的目錄')
self.result_label.setAlignment(Qt.AlignCenter)
self.result_label.setStyleSheet("QLabel { font-size: 14px; padding: 10px; }")
splitter = QSplitter(Qt.Vertical)
self.file_list = QTextEdit()
self.file_list.setReadOnly(True)
self.file_list.setPlaceholderText('文件統(tǒng)計(jì)結(jié)果將顯示在這里...')
self.file_list.setFont(QFont("Consolas", 9))
self.detail_text = QTextEdit()
self.detail_text.setReadOnly(True)
self.detail_text.setPlaceholderText('詳細(xì)統(tǒng)計(jì)信息將顯示在這里...')
self.detail_text.setFont(QFont("Consolas", 9))
splitter.addWidget(self.file_list)
splitter.addWidget(self.detail_text)
splitter.setSizes([400, 200])
main_layout.addLayout(control_layout)
main_layout.addWidget(self.progress_bar)
main_layout.addWidget(self.result_label)
main_layout.addWidget(splitter, 1)
def select_directory(self):
directory = QFileDialog.getExistingDirectory(
self,
'選擇包含C++源代碼的目錄',
'',
QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks
)
if directory:
self.selected_directory = directory
self.path_label.setText(directory)
self.count_btn.setEnabled(True)
self.result_label.setText('目錄已選擇,點(diǎn)擊"開始統(tǒng)計(jì)"按鈕進(jìn)行統(tǒng)計(jì)')
def start_counting(self):
if not self.selected_directory:
QMessageBox.warning(self, '警告', '請先選擇目錄!')
return
self.file_list.clear()
self.detail_text.clear()
self.progress_bar.setVisible(True)
self.progress_bar.setValue(0)
self.count_btn.setEnabled(False)
self.select_btn.setEnabled(False)
self.result_label.setText('正在統(tǒng)計(jì)中...')
self.count_thread = FileCounterThread(self.selected_directory)
self.count_thread.file_counted.connect(self.on_file_counted)
self.count_thread.progress_updated.connect(self.progress_bar.setValue)
self.count_thread.finished_counting.connect(self.on_counting_finished)
self.count_thread.start()
def on_file_counted(self, file_path, line_count):
rel_path = os.path.relpath(file_path, self.selected_directory)
self.file_list.append(f"{rel_path}: {line_count} 行")
def on_counting_finished(self, file_results, total_lines):
self.progress_bar.setVisible(False)
self.count_btn.setEnabled(True)
self.select_btn.setEnabled(True)
file_types = {}
for file_path, line_count in file_results.items():
ext = os.path.splitext(file_path)[1].lower()
file_types[ext] = file_types.get(ext, 0) + 1
detail_text = f"統(tǒng)計(jì)完成!\n"
detail_text += f"=" * 50 + "\n"
detail_text += f"目錄: {self.selected_directory}\n"
detail_text += f"總文件數(shù): {len(file_results)}\n"
detail_text += f"總行數(shù): {total_lines:,}\n\n"
detail_text += "文件類型統(tǒng)計(jì):\n"
for ext, count in sorted(file_types.items()):
detail_text += f" {ext}: {count} 個文件\n"
if file_results:
sorted_files = sorted(file_results.items(), key=lambda x: x[1], reverse=True)
detail_text += f"\n行數(shù)最多的文件 (前10個):\n"
for i, (file_path, line_count) in enumerate(sorted_files[:10]):
rel_path = os.path.relpath(file_path, self.selected_directory)
detail_text += f" {i+1}. {rel_path}: {line_count:,} 行\(zhòng)n"
self.detail_text.setText(detail_text)
self.result_label.setText(f'統(tǒng)計(jì)完成!共 {len(file_results)} 個文件,總行數(shù): {total_lines:,}')
def main():
app = QApplication(sys.argv)
app.setApplicationName('C++代碼行數(shù)統(tǒng)計(jì)工具')
window = CodeLineCounter()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
使用說明與功能特點(diǎn)
安裝依賴
pip install PyQt5
運(yùn)行程序
python cpp_line_counter.py
主要功能特點(diǎn)
- 多格式支持:支持.h, .cpp, .hpp, .cc, .cxx, .c, .hh等多種C++文件格式
- 遞歸統(tǒng)計(jì):自動遞歸遍歷所有子目錄
- 實(shí)時(shí)顯示:統(tǒng)計(jì)過程中實(shí)時(shí)顯示每個文件的結(jié)果
- 進(jìn)度反饋:進(jìn)度條顯示整體統(tǒng)計(jì)進(jìn)度
- 詳細(xì)報(bào)告:提供文件類型統(tǒng)計(jì)和最大文件排名
- 錯誤容錯:自動處理無法讀取的文件,繼續(xù)統(tǒng)計(jì)其他文件
- 編碼兼容:支持UTF-8編碼,兼容各種編碼格式的源文件
性能分析與優(yōu)化建議
性能瓶頸分析
在實(shí)際使用中,主要性能瓶頸可能出現(xiàn)在:
- I/O操作:大量小文件的讀取操作
- 內(nèi)存使用:極大文件的處理
- UI更新:頻繁的界面刷新
優(yōu)化策略
- 批量處理:將小文件分組批量讀取
- 異步I/O:使用異步文件讀取提高并發(fā)性
- 采樣統(tǒng)計(jì):對于極大文件,可以采用采樣統(tǒng)計(jì)方法
- 緩存機(jī)制:緩存已統(tǒng)計(jì)文件的結(jié)果
擴(kuò)展功能建議
- 代碼復(fù)雜度分析:集成圈復(fù)雜度、函數(shù)長度等指標(biāo)
- 注釋率統(tǒng)計(jì):區(qū)分代碼行和注釋行
- 歷史對比:支持不同版本間的代碼變化統(tǒng)計(jì)
- 導(dǎo)出功能:支持將統(tǒng)計(jì)結(jié)果導(dǎo)出為CSV或Excel格式
結(jié)論
本文介紹的C++源代碼行數(shù)統(tǒng)計(jì)工具基于PyQt5框架,采用了多線程架構(gòu)和模塊化設(shè)計(jì),具有良好的用戶體驗(yàn)和可擴(kuò)展性。通過數(shù)學(xué)建模和算法分析,我們深入探討了工具的性能特征和優(yōu)化方向。
該工具不僅滿足了基本的代碼行數(shù)統(tǒng)計(jì)需求,還為后續(xù)的功能擴(kuò)展奠定了堅(jiān)實(shí)基礎(chǔ)。在實(shí)際軟件開發(fā)過程中,這樣的工具能夠幫助開發(fā)團(tuán)隊(duì)更好地理解項(xiàng)目規(guī)模,進(jìn)行有效的項(xiàng)目管理和技術(shù)決策。
代碼行數(shù)雖然只是一個基礎(chǔ)度量指標(biāo),但在恰當(dāng)?shù)恼Z境下,結(jié)合其他質(zhì)量指標(biāo),仍然能夠?yàn)檐浖こ虒?shí)踐提供有價(jià)值的參考信息。
到此這篇關(guān)于Python+PyQt5設(shè)計(jì)實(shí)現(xiàn)一個C++源代碼行數(shù)統(tǒng)計(jì)工具的文章就介紹到這了,更多相關(guān)Python統(tǒng)計(jì)C++代碼行數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
全面掌握Python?JSON庫函數(shù)與方法學(xué)會JSON數(shù)據(jù)處理
Python提供了內(nèi)置的JSON庫,允許在Python中解析和序列化JSON數(shù)據(jù),本文將深入研究Python中JSON庫的各種函數(shù)和方法,為你提供豐富的示例代碼來幫助掌握J(rèn)SON處理的方方面面2024-01-01
對Python 網(wǎng)絡(luò)設(shè)備巡檢腳本的實(shí)例講解
下面小編就為大家分享一篇對Python 網(wǎng)絡(luò)設(shè)備巡檢腳本的實(shí)例講解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-04-04
python數(shù)學(xué)建模之Matplotlib?實(shí)現(xiàn)圖片繪制
這篇文章主要介紹了python數(shù)學(xué)建模之Matplotlib?實(shí)現(xiàn)圖片繪制,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07
Mac 安裝 Python3.10 和 配置環(huán)境的詳細(xì)教程
這篇文章主要介紹了Mac 安裝 Python3.10 和 配置環(huán)境,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
Keras中的兩種模型:Sequential和Model用法
這篇文章主要介紹了Keras中的兩種模型:Sequential和Model用法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06
使用Django2快速開發(fā)Web項(xiàng)目的詳細(xì)步驟
這篇文章主要介紹了使用Django2快速開發(fā)Web項(xiàng)目的詳細(xì)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
numpy中數(shù)組拼接、數(shù)組合并方法總結(jié)(append(),?concatenate,?hstack,?vstack
numpy庫是一個高效處理多維數(shù)組的工具,可以在進(jìn)行邊寫的數(shù)組計(jì)算上進(jìn)行一系列的操作,下面這篇文章主要給大家介紹了關(guān)于numpy中數(shù)組拼接、數(shù)組合并方法(append(),?concatenate,?hstack,?vstack,?column_stack,?row_stack,?np.r_,?np.c_等)的相關(guān)資料,需要的朋友可以參考下2022-08-08

