C++/Qt 代码规范指南

这是一篇规范指南,前些天看《C++开发规范》有感,说了很多关于C++/Qt的代码规范,读完提升了很多。要是所有人都能完全按照这个指南进行开发,那就再也不会有屎山代码了!

1. 命名约定

  • 清晰性: 名称应清晰地表达其目的或内容。避免缩写,除非是广泛接受且不会引起歧义的(如 num, str, buf)。
  • 一致性: 在整个项目中保持命名风格一致。

1.1 类、结构体、枚举、命名空间

  • 规则: 使用 PascalCase(大驼峰命名法)。

  • 示例:

    cpp 复制代码
    class MyWidget;
    struct UserSettings;
    enum class LogLevel { Debug, Info, Warning, Error };
    namespace NetworkUtils;

1.2 函数和方法

  • 规则: 使用 camelCase(小驼峰命名法)。

  • 示例:

    cpp 复制代码
    void calculateTotalPrice();
    QString getDisplayName() const;
    bool isValidInput();

1.3 变量(局部变量、成员变量、全局变量)

  • 规则: 使用 snake_case(蛇形命名法)。

  • 成员变量: 建议为成员变量添加前缀 m_ 以区别于局部变量。

  • 常量: 使用 k 前缀或全大写 SNAKE_CASE(视项目约定而定)。

  • 示例:

    cpp 复制代码
    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、内联函数或模板。如果必须使用,确保名称独特且不易冲突。

  • 示例:

    cpp 复制代码
    #ifdef Q_OS_WIN
    #define PLATFORM_SPECIFIC_SETTING 1
    #endif

2. 格式与布局

  • 缩进: 使用 4 个空格 进行缩进。不要使用制表符 (Tab)。配置编辑器以确保按 Tab 键时插入空格。

  • 大括号 {}

    • K&R / Stroustrup 风格: 左大括号 { 放在语句的同一行末尾,右大括号 } 单独一行并与语句开始处对齐。这是 Qt 项目中常见的风格。

    • 示例:

      cpp 复制代码
      if (condition) {
          // ...
      } else {
          // ...
      }
      
      for (int i = 0; i < count; ++i) {
          // ...
      }
      
      void myFunction() {
          // ...
      }
  • 行长度: 建议每行不超过 80 或 120 个字符(团队内部统一)。如果超出,应在逻辑清晰的位置(如逗号后、运算符前)进行换行,新行缩进一级。

  • 空格:

    • 在二元运算符(如 =, +, -, *, /, %, <<, >>, <, >, <=, >=, ==, !=, &&, ||, &, |, ^)前后添加空格。

    • 在逗号 , 和分号 ; 后添加空格。

    • 在控制流语句(if, for, while, switch)和左括号 ( 之间添加空格。

    • 函数调用和函数声明中,函数名与左括号 ( 之间不加空格。

    • 不要在 ., ->, :: 运算符前后加空格。

    • 不要在单目运算符(如 !, ~, ++, --, & (取址), * (解引用))和其操作数之间加空格。

    • 不要在 [], () 内部紧贴括号的地方加多余空格。

    • 示例:

      cpp 复制代码
      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):

      cpp 复制代码
      #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):

      cpp 复制代码
      // 包含对应的头文件
      #include "myclass.h"
      // 其他必要的头文件
      #include <QDebug>
      // 实现
      namespace MyNamespace {
      MyClass::MyClass(QWidget *parent) : QWidget(parent) {
          // 构造函数实现
      }
      MyClass::~MyClass() {
          // 析构函数实现 (如果需要清理资源)
      }
      // ... 其他成员函数的实现
      } // namespace MyNamespace

3. 内存管理

  • 所有权: 明确对象的所有权关系(谁创建,谁负责销毁)。

  • Qt 父对象机制: 充分利用 Qt 的父子对象机制。当创建一个 QObject 派生类对象(如 QWidget)时,如果指定了父对象,父对象销毁时会自动销毁其所有子对象。这是管理 GUI 对象生命周期的首选方式。

    cpp 复制代码
    QPushButton *button = new QPushButton("Click Me", this); // 'this' 是父窗口
    // 当父窗口销毁时,button 也会被自动销毁
  • 智能指针:

    • 对于 QObject 派生类对象,或者所有权需要在不同作用域间转移的对象,优先使用智能指针std::unique_ptr, std::shared_ptr)来管理内存,避免手动 new/delete
    • QPointer 当持有指向 QObject 派生类对象的指针时,如果该对象可能被 Qt 的父子机制在其他地方销毁,应使用 QPointer<T>。它是一个弱指针,当目标对象被销毁时,它会自动设置为 nullptr,防止悬空指针。
    cpp 复制代码
    QPointer<QPushButton> weakButton = button; // button 是 QPushButton*
    // ... 其他地方可能删除了 button 指向的对象
    if (weakButton) { // 安全检查,避免访问已销毁对象
        weakButton->setText("Safe");
    }
  • 避免手动 delete 除非有明确的理由(如性能关键路径,且对象生命周期非常清晰),否则尽量避免手动调用 delete。优先依赖父对象机制或智能指针。

  • 资源管理: 使用 RAII (Resource Acquisition Is Initialization) 原则管理文件句柄、网络连接、锁等资源。利用构造函数获取资源,析构函数释放资源。

4. Qt 特有特性规范

  • QObjectQ_OBJECT 宏:

    • 所有派生自 QObject 的类都必须 在类定义的 private 区域包含 Q_OBJECT 宏(通常紧跟在类名和基类列表之后)。
    • 这启用了信号槽机制、运行时类型信息 (qobject_cast)、属性系统等。
  • 信号 (signals):

    • signals: 区域声明信号。

    • 信号只需声明,不要实现。

    • 信号名通常使用动词的一般现在时或描述状态变化的短语。

    • 信号参数应尽量少且简单。避免传递复杂或需要手动管理内存的类型(如原始指针)。优先使用值类型或 Qt 的隐式共享类(如 QString, QImage)。

    • 示例:

      cpp 复制代码
      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 文件),但这不是强制的。

    • 示例:

      cpp 复制代码
      public slots:
          void handleButtonClick(); // 通用命名
          void on_pushButton_clicked(); // UI 自动连接命名
  • 信号槽连接 (connect):

    • 优先使用 Qt5 的新式连接语法 (基于函数指针): 它提供编译时类型检查,更安全。

    • 示例:

      cpp 复制代码
      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)。

    • 示例:

      cpp 复制代码
      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 变量。
    cpp 复制代码
    int calculate() const; // const 成员函数
    void processData(const QString &data); // const 引用参数
    const double kFactor = 1.234; // const 常量
  • 初始化:

    • 使用初始化列表初始化成员变量,尤其是在基类有非默认构造函数或成员变量是引用/常量时。
    • 避免在构造函数体内进行赋值初始化。
    cpp 复制代码
    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: 移除 constvolatile 属性(谨慎使用)。
      • reinterpret_cast: 低级的、与实现相关的重新解释(高度危险,尽量避免)。
    • qobject_cast 用于在 QObject 继承树中进行安全的向下转型。比 dynamic_cast 更快,因为它利用 Qt 的元对象系统。
    cpp 复制代码
    QWidget *widget = ...;
    QPushButton *button = qobject_cast<QPushButton*>(widget);
    if (button) {
        // 转换成功
    }
  • 错误处理:

    • 异常: 在 Qt 中,异常的使用存在一些争议。Qt 本身在错误情况下(如内存分配失败)通常不抛出异常。团队需要统一策略:是使用异常还是错误码/返回值。
    • 断言: 使用 Q_ASSERT, Q_ASSERT_X 在调试版本中检查程序内部逻辑不变量的成立。它们只在 Debug 构建中有效,在 Release 构建中会被移除。
    cpp 复制代码
    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 表示空指针,而不是 NULL0

  • 范围 for 循环: 优先使用基于范围的 for 循环遍历容器。

    cpp 复制代码
    QList<QString> list;
    for (const QString &str : list) {
        // ...
    }
  • auto 谨慎使用 auto。在类型明显且冗长(如迭代器类型)或模板代码中可以提高可读性。避免在类型不明显或对理解代码至关重要的地方使用 auto

    cpp 复制代码
    auto button = new QPushButton(this); // 类型明显 (QPushButton*)
    for (auto it = list.begin(); it != list.end(); ++it) { // 迭代器类型可能冗长
    }

6. 文档与注释

  • API 文档: 使用 Doxygen 风格的注释为公共 API(类、方法、属性、枚举等)添加文档。

    cpp 复制代码
    /**
     * @brief 计算两个数的和。
     * @param a 第一个加数。
     * @param b 第二个加数。
     * @return 两个加数的和。
     */
    int add(int a, int b);
  • 代码注释: 在代码内部,对复杂的逻辑、算法、非显而易见的决策、重要的注意事项或 TODO/FIXME 标记添加注释。避免注释那些从代码本身就能清晰理解的内容。

  • TODO/FIXME 使用一致的标记来标注需要后续完善或修复的地方。

    cpp 复制代码
    // TODO: Optimize this algorithm for large datasets.
    // FIXME: This workaround might break on Windows.

7. 工具

  • 代码格式化工具: 使用 clang-format 等工具自动格式化代码,确保团队风格一致。可以配置 .clang-format 文件定义规则。
  • 静态分析工具: 使用 Clang-TidyCppcheck 或 Qt Creator 内置的分析工具来检查潜在问题(如未定义行为、内存泄漏隐患、编码规范违反等)。
  • 版本控制: 使用 Git 等版本控制系统管理代码。遵循良好的分支策略(如 Git Flow)和提交信息规范(清晰描述修改内容)。
相关推荐
li星野2 小时前
QT模拟题:QT项目实践与架构设计(120分钟)
开发语言·qt
大鹏说大话2 小时前
Java 锁膨胀机制深度解析:从偏向锁到重量级锁的进化之路
开发语言·c#
IT猿手2 小时前
基于 ZOH 离散化与增量 PID 的四旋翼无人机轨迹跟踪控制研究,MATLAB代码
开发语言·算法·matlab·无人机·动态路径规划·openclaw
IT猿手2 小时前
基于控制障碍函数(Control Barrier Function, CBF)的无人机编队三维动态避障路径规划,MATLAB代码
开发语言·matlab·无人机·动态路径规划·无人机编队
huaweichenai2 小时前
java的时间操作介绍
java·开发语言
就不掉头发2 小时前
C++右值与右值引用
开发语言·c++
IT猿手3 小时前
基于 CBF 的多无人机编队动态避障路径规划研究,无人机及障碍物数量可以自定义修改,MATLAB代码
开发语言·matlab·无人机·动态路径规划
炸膛坦客3 小时前
单片机/C/C++八股:(十六)C 中 malloc/free 和 C++ 中 new/delete 有什么区别?
c语言·开发语言·c++
@insist1233 小时前
软件设计师-组网技术基础:网络设备、传输介质与局域网核心协议
开发语言·网络·软考·软件设计师·软件水平考试