C++/Qt 代码规范指南

发布时间:2026/5/21 22:06:31

C++/Qt 代码规范指南 这是一篇规范指南前些天看《C开发规范》有感说了很多关于C/Qt的代码规范读完提升了很多。要是所有人都能完全按照这个指南进行开发那就再也不会有屎山代码了1. 命名约定清晰性名称应清晰地表达其目的或内容。避免缩写除非是广泛接受且不会引起歧义的如num,str,buf。一致性在整个项目中保持命名风格一致。1.1 类、结构体、枚举、命名空间规则使用PascalCase大驼峰命名法。示例class MyWidget; struct UserSettings; enum class LogLevel { Debug, Info, Warning, Error }; namespace NetworkUtils;1.2 函数和方法规则使用camelCase小驼峰命名法。示例void calculateTotalPrice(); QString getDisplayName() const; bool isValidInput();1.3 变量局部变量、成员变量、全局变量规则使用snake_case蛇形命名法。成员变量建议为成员变量添加前缀m_以区别于局部变量。常量使用k前缀或全大写SNAKE_CASE视项目约定而定。示例int item_count; QString m_user_name; // 成员变量 double total_amount; static constexpr int kMaxRetries 5; // 常量 const double PI 3.1415926535; // 或 PI 全大写1.4 宏规则使用全大写SNAKE_CASE。建议在 Qt 中应尽量避免使用宏优先使用constexpr、内联函数或模板。如果必须使用确保名称独特且不易冲突。示例#ifdef Q_OS_WIN #define PLATFORM_SPECIFIC_SETTING 1 #endif2. 格式与布局缩进使用4 个空格进行缩进。不要使用制表符 (Tab)。配置编辑器以确保按 Tab 键时插入空格。大括号{}KR / Stroustrup 风格左大括号{放在语句的同一行末尾右大括号}单独一行并与语句开始处对齐。这是 Qt 项目中常见的风格。示例if (condition) { // ... } else { // ... } for (int i 0; i count; i) { // ... } void myFunction() { // ... }行长度建议每行不超过80 或 120个字符团队内部统一。如果超出应在逻辑清晰的位置如逗号后、运算符前进行换行新行缩进一级。空格在二元运算符如,,-,*,/,%,,,,,,,,!,,||,,|,^前后添加空格。在逗号,和分号;后添加空格。在控制流语句if,for,while,switch和左括号(之间添加空格。函数调用和函数声明中函数名与左括号(之间不加空格。不要在.,-,::运算符前后加空格。不要在单目运算符如!,~,,--,(取址),*(解引用)和其操作数之间加空格。不要在[],()内部紧贴括号的地方加多余空格。示例int result a b * (c - d); // 运算符空格 if (flag) { ... } // if 后空格 myFunction(arg1, arg2); // 函数名后无空格逗号后有空格 object-method(); // - 前后无空格 int *ptr var; // * 是单目运算符无空格 array[5] value; // [] 内无多余空格空行使用空行将逻辑相关的代码块分开提高可读性。在函数定义之间、类定义内部不同部分如 public, private, signals, slots之间、长的函数内部逻辑段落之间添加空行。避免连续多个空行。文件组织头文件 (.h, .hpp)#pragma once // 或 #ifndef ... #define ... #endif // 必要的头文件包含 (先系统头文件后第三方库头文件再项目头文件) #include vector #include QWidget #include utils.h // 前向声明 (如果需要) class MyOtherClass; // 类/命名空间声明 namespace MyNamespace { class MyClass : public QWidget { Q_OBJECT // Qt 元对象系统宏 public: explicit MyClass(QWidget *parent nullptr); ~MyClass() override; // ... 其他公共成员 signals: // ... 信号 public slots: // ... 公共槽 private slots: // ... 私有槽 private: // ... 私有成员变量和方法 }; } // namespace MyNamespace源文件 (.cpp)// 包含对应的头文件 #include myclass.h // 其他必要的头文件 #include QDebug // 实现 namespace MyNamespace { MyClass::MyClass(QWidget *parent) : QWidget(parent) { // 构造函数实现 } MyClass::~MyClass() { // 析构函数实现 (如果需要清理资源) } // ... 其他成员函数的实现 } // namespace MyNamespace3. 内存管理所有权明确对象的所有权关系谁创建谁负责销毁。Qt 父对象机制充分利用 Qt 的父子对象机制。当创建一个QObject派生类对象如QWidget时如果指定了父对象父对象销毁时会自动销毁其所有子对象。这是管理 GUI 对象生命周期的首选方式。QPushButton *button new QPushButton(Click Me, this); // this 是父窗口 // 当父窗口销毁时button 也会被自动销毁智能指针对于非QObject派生类对象或者所有权需要在不同作用域间转移的对象优先使用智能指针std::unique_ptr,std::shared_ptr来管理内存避免手动new/delete。QPointer当持有指向QObject派生类对象的指针时如果该对象可能被 Qt 的父子机制在其他地方销毁应使用QPointerT。它是一个弱指针当目标对象被销毁时它会自动设置为nullptr防止悬空指针。QPointerQPushButton weakButton button; // button 是 QPushButton* // ... 其他地方可能删除了 button 指向的对象 if (weakButton) { // 安全检查避免访问已销毁对象 weakButton-setText(Safe); }避免手动delete除非有明确的理由如性能关键路径且对象生命周期非常清晰否则尽量避免手动调用delete。优先依赖父对象机制或智能指针。资源管理使用 RAII (Resource Acquisition Is Initialization) 原则管理文件句柄、网络连接、锁等资源。利用构造函数获取资源析构函数释放资源。4. Qt 特有特性规范QObject和Q_OBJECT宏所有派生自QObject的类都必须在类定义的private区域包含Q_OBJECT宏通常紧跟在类名和基类列表之后。这启用了信号槽机制、运行时类型信息 (qobject_cast)、属性系统等。信号 (signals)在signals:区域声明信号。信号只需声明不要实现。信号名通常使用动词的一般现在时或描述状态变化的短语。信号参数应尽量少且简单。避免传递复杂或需要手动管理内存的类型如原始指针。优先使用值类型或 Qt 的隐式共享类如QString,QImage。示例signals: void buttonClicked(); void progressChanged(int percent); void dataReady(const QByteArray data); // 使用 const 引用槽 (slots)在public slots:,protected slots:,private slots:区域声明槽函数。槽函数是普通的成员函数需要实现。槽函数命名应清晰反映其功能。可以使用on_ObjectName_signalName的命名约定来自动连接如果使用 Qt Designer 的 UI 文件但这不是强制的。示例public slots: void handleButtonClick(); // 通用命名 void on_pushButton_clicked(); // UI 自动连接命名信号槽连接 (connect)优先使用 Qt5 的新式连接语法 (基于函数指针)它提供编译时类型检查更安全。示例connect(sender, SenderClass::signalName, receiver, ReceiverClass::slotName); connect(button, QPushButton::clicked, this, MyWidget::handleButtonClick);避免使用基于字符串的旧式SIGNAL()和SLOT()宏除非连接QObject动态属性或需要运行时灵活性这种情况较少。属性系统 (Q_PROPERTY)使用Q_PROPERTY宏声明类的属性使其可被元对象系统访问如用于 QML。定义相应的读取函数READ getter、写入函数WRITE setter和通知信号NOTIFY signal。示例Q_PROPERTY(QString userName READ userName WRITE setUserName NOTIFY userNameChanged)事件处理 (event,eventFilter)重写QObject::event(QEvent *)或特定的事件处理器如mousePressEvent,keyPressEvent来处理事件。使用事件过滤器 (installEventFilter,eventFilter) 时注意过滤器的安装和移除时机。在事件处理器中如果需要处理特定事件后还要让事件继续传播调用基类的实现如QWidget::mousePressEvent(event)。线程 (QThread)QObject的线程亲和性每个QObject实例都与一个线程通常是创建它的线程相关联。其子对象也属于同一线程。不要在非创建线程中访问该对象的成员信号槽跨线程调用是安全的。QThread使用模式Worker-Object 模式 (推荐)创建一个工作对象派生自QObject将其moveToThread到一个QThread实例中。在该对象中使用信号槽进行线程间通信。QThread的事件循环负责执行该对象槽函数的调用。避免直接继承QThread并重写run()方法除非有非常特殊的低级需求。线程安全使用互斥锁 (QMutex,QMutexLocker)、读写锁 (QReadWriteLock)、信号量 (QSemaphore) 或原子操作来保护多线程共享数据。尽量减少共享数据。5. 其他 C 最佳实践const正确性将不会修改成员变量的成员函数声明为const。使用const引用传递不希望被修改的大型对象或参数。尽可能使用const变量。int calculate() const; // const 成员函数 void processData(const QString data); // const 引用参数 const double kFactor 1.234; // const 常量初始化使用初始化列表初始化成员变量尤其是在基类有非默认构造函数或成员变量是引用/常量时。避免在构造函数体内进行赋值初始化。MyClass::MyClass(int value, const QString name) : BaseClass(value), // 基类初始化 m_name(name), // 成员变量初始化 m_counter(0) { // 构造函数体 }类型转换避免 C 风格转换如(int) someFloat。优先使用 C 风格转换static_cast: 用于明确定义的、相对安全的转换如数值类型转换、基类指针到派生类指针当你知道安全时。dynamic_cast: 用于在继承层次中进行安全的向下转型需要运行时类型信息 RTTI。适用于QObject派生类使用qobject_cast更高效。const_cast: 移除const或volatile属性谨慎使用。reinterpret_cast: 低级的、与实现相关的重新解释高度危险尽量避免。qobject_cast用于在QObject继承树中进行安全的向下转型。比dynamic_cast更快因为它利用 Qt 的元对象系统。QWidget *widget ...; QPushButton *button qobject_castQPushButton*(widget); if (button) { // 转换成功 }错误处理异常在 Qt 中异常的使用存在一些争议。Qt 本身在错误情况下如内存分配失败通常不抛出异常。团队需要统一策略是使用异常还是错误码/返回值。断言使用Q_ASSERT,Q_ASSERT_X在调试版本中检查程序内部逻辑不变量的成立。它们只在Debug构建中有效在Release构建中会被移除。void MyClass::setValue(int v) { Q_ASSERT_X(v 0 v 100, MyClass::setValue, Value out of range [0,100]); m_value v; }nullptr使用nullptr表示空指针而不是NULL或0。范围for循环优先使用基于范围的for循环遍历容器。QListQString list; for (const QString str : list) { // ... }auto谨慎使用auto。在类型明显且冗长如迭代器类型或模板代码中可以提高可读性。避免在类型不明显或对理解代码至关重要的地方使用auto。auto button new QPushButton(this); // 类型明显 (QPushButton*) for (auto it list.begin(); it ! list.end(); it) { // 迭代器类型可能冗长 }6. 文档与注释API 文档使用 Doxygen 风格的注释为公共 API类、方法、属性、枚举等添加文档。/** * brief 计算两个数的和。 * param a 第一个加数。 * param b 第二个加数。 * return 两个加数的和。 */ int add(int a, int b);代码注释在代码内部对复杂的逻辑、算法、非显而易见的决策、重要的注意事项或TODO/FIXME标记添加注释。避免注释那些从代码本身就能清晰理解的内容。TODO/FIXME使用一致的标记来标注需要后续完善或修复的地方。// TODO: Optimize this algorithm for large datasets. // FIXME: This workaround might break on Windows.7. 工具代码格式化工具使用clang-format等工具自动格式化代码确保团队风格一致。可以配置.clang-format文件定义规则。静态分析工具使用Clang-Tidy、Cppcheck或 Qt Creator 内置的分析工具来检查潜在问题如未定义行为、内存泄漏隐患、编码规范违反等。版本控制使用 Git 等版本控制系统管理代码。遵循良好的分支策略如 Git Flow和提交信息规范清晰描述修改内容。

相关新闻