关注

C/C++编程规范

这份规范聚焦工程化C/C++开发,核心目标是提升代码可读性、可维护性,促进团队协作,同时通过标准化注释实现文档自动生成。规范覆盖头文件、类/结构体、代码格式、命名约定、Doxygen注释五大核心维度,兼顾语法规范和工程实践,是团队协作开发的重要参考准则,以下为核心内容的系统整理与解读。

一、头文件规则(工程化开发核心)

头文件是C/C++模块化开发的基础,本规范重点解决多重包含、过度依赖、命名空间冲突三大问题,同时明确头文件与源文件的对应关系。

1.1 #define 防止多重包含(强制要求)

  • 核心要求:所有头文件必须通过#ifndef/#define/#endif实现防止单次编译过程中的多重包含(注:多个.cpp文件包含同一头文件是合法的,此规则仅限制单文件编译期)。
  • 命名格式
    • 业务逻辑类头文件:直接以大写文件名+_H命名,如x_msg_task.h对应X_MSG_TASK_H
    • 类库/公用库头文件:在前面添加项目名称,避免跨项目命名冲突;
  • 关键禁忌头文件中禁止做变量/函数定义,仅允许声明(定义会导致多文件包含时的重定义错误);
  • 示例
    #ifndef X_MSG_TASK_H
    #define X_MSG_TASK_H
    // 仅写声明代码(函数声明、宏定义、结构体/类声明等)
    #endif
    

1.2 减少头文件依赖(性能优化重点)

  • 核心原则:使用前置声明(forward declarations) 尽量减少头文件中#include的数量,降低编译耦合度;
  • 原理:头文件的每一次#include都会引入新的依赖,被依赖头文件的任何修改,都会导致所有包含该头文件的代码重新编译,增加编译成本;
  • 前置声明使用场景
    • 仅使用类/结构体的指针/引用时,无需包含头文件,直接前置声明即可,如class XThread;struct XData;
  • 必须包含头文件的场景
    • 定义类/结构体的实体对象(非指针/引用)、继承该类、调用该类的成员函数时,必须包含对应头文件;
  • 通用准则头文件中尽量不引用其他头文件,所有头文件引用移至.cpp源文件中

1.3 头文件中不导入命名空间(避免冲突)

  • 核心要求:头文件中禁止使用using namespace xxx 导入命名空间;
  • 正确用法:使用命名空间内的标识符时,直接加命名空间前缀,如std::string str;std::vector<int> vec;
  • 命名空间导入位置:仅在cpp源文件中使用using namespace,简化代码;
  • 原因:头文件会被其他未知文件包含,导入命名空间可能导致变量/函数命名冲突,引发不可预期的编译错误。

头文件与源文件的对应关系

  • 通用规则:一个.cpp文件对应一个.h文件
  • 例外场景:
    1. 包含main函数的入口文件,无对应的.h文件;
    2. 仅含纯虚函数的接口类,只有.h文件,无对应的.cpp文件(无实现代码)。

二、类/结构体规则(C++专属,兼顾C结构体)

本部分主要针对C++开发,明确类、结构体、继承、接口的使用规范,同时区分C/C++中结构体的使用场景,核心原则是清晰划分职责,降低设计复杂度

2.1 构造函数的职责

  • 核心要求:构造函数仅做无业务逻辑的初始化(如成员变量赋默认值、指针置空);
  • 业务逻辑初始化:所有涉及业务逻辑的初始化操作,统一放在**Init()方法**中集中实现;
  • 原因:构造函数无法返回错误码,若初始化失败难以处理;Init()方法可通过返回值标识初始化结果,提升代码健壮性。

2.2 结构体(Struct)与类(Class)的区分

C语言场景

struct仅用于封装纯数据,无成员函数,是数据的集合。

C++场景
  • Struct仅当封装纯数据(无成员函数、无业务逻辑)时使用,成员默认访问权限为public
  • Class:除纯数据封装外的所有场景均使用class,成员默认访问权限为private,支持封装、继承、多态;
  • 命名规则:类和结构体的成员变量使用不同的命名规则(详见本规范「变量命名」部分)。

2.3 继承(Inheritance)

  • 核心原则优先使用组合(Composition),而非继承;组合更灵活,降低代码耦合度;
  • 继承的使用要求:若必须使用继承,仅允许public继承,禁止使用protected/private继承;
  • 原因:protected/private继承会导致基类与派生类的耦合度过高,破坏封装性,且代码可读性差。

2.4 多重继承(Multiple Inheritance)

  • 核心原则尽量避免使用多重继承,多重继承易导致菱形继承、对象空间分配混乱等问题;
  • 例外场景:若必须使用,需满足**「一实多虚」** 规则:
    • 一个基类包含实际实现代码
    • 其他基类均为纯虚函数接口类(无成员变量、无实现代码)。

2.5 接口(Interface,C++专属)

  • 接口的定义:满足以下条件的类即为接口,建议以I开头命名(非强制);
    1. 仅包含纯虚函数(=0 和静态函数;
    2. 无任何成员变量;
    3. 无任何函数实现代码。

2.6 类内声明次序(强制规范)

为提升代码可读性,类内成员需按固定次序声明,且访问权限块按以下顺序排列:
public:protected:private:

每个访问权限块内的声明次序
  1. 构造函数;
  2. 析构函数;
  3. 成员函数(含静态成员函数,按功能相关性排列);
  4. 数据成员(含静态数据成员,按类型/用途排列)。

三、代码格式规则(团队协作基础)

统一的代码格式是团队协作的前提,本规范明确缩进、括号的标准格式,无歧义、易阅读。

3.1 缩进:仅使用空格,禁止制表位(Tab)

  • 核心要求全部使用空格实现缩进,禁止使用Tab键
  • 缩进标准每次缩进4个空格
  • 原因:不同编辑器/终端对Tab的显示宽度不同(如2/4/8个空格),会导致代码格式错乱;空格缩进在所有环境下显示一致。

3.2 括号格式:左右括号单独起一行(Allman风格)

  • 核心要求:条件语句(if/else)、循环语句(for/while/do)、函数定义的左右大括号均单独起一行
  • 格式示例
    // 条件语句
    if (true)
    {
        // 代码块
    }
    else
    {
        // 代码块
    }
    // 函数定义
    int Add(int a, int b)
    {
        return a + b;
    }
    // 循环语句
    for (int i = 0; i < 10; i++)
    {
        // 代码块
    }
    
  • 优势:代码层次清晰,大括号的匹配关系一目了然,便于调试和维护。

四、命名约定(核心重点,无歧义、强描述性)

命名是代码可读性的核心,本规范对文件、类/结构体/枚举、变量、函数、宏制定了严格的命名规则,核心原则是:具有描述性,不过度缩写;类型/变量用名词,函数用命令性动词

4.1 通用命名规则(所有命名均需遵守)

  1. 命名需见名知意,具有强描述性,禁止使用无意义的缩写(如a/b/ctmp1/tmp2);
  2. 类型(类/结构体/枚举/typedef)和变量用名词/名词短语命名;
  3. 函数用命令性动词/动词短语命名(如AddGetNameSaveData);
  4. 禁止使用拼音与英文混合命名,统一使用英文

4.2 文件命名

  • 文件名全部小写
  • 单词之间用下划线(_) 分隔;
  • 示例:my_useful.cppx_msg_task.hdata_manager.h

4.3 类型命名(类/结构体/枚举/typedef)

  • 所有类型命名规则统一;
  • 大驼峰命名法:每个单词的首字母大写,单词之间无下划线
  • 示例:MyExcitingClassUserInfoUrlTableErrorsDataBuffer

4.4 变量命名

  • 小蛇形命名法:变量名一律小写,单词之间用下划线(_) 分隔;
  • 类的成员变量:在变量名末尾加下划线(_),区分局部变量和成员变量;
  • 局部变量/全局变量:直接使用小蛇形命名,无后缀;
  • 示例:
    // 局部变量
    int my_exciting_local_variable;
    std::string user_name;
    // 类的成员变量
    class MyClass
    {
    private:
        int num_entries_;
        std::string user_name_;
    };
    

4.5 函数命名

普通函数类的成员存取函数两种场景,分别制定规则。

普通函数(全局函数/类的普通成员函数)
  • 大驼峰命名法:函数名首字母大写,每个单词首字母大写,无下划线
  • 用动词/动词短语命名,明确函数行为;
  • 示例:AddTableEntry()GetUserInfo()SaveFile()InitConfig()
类的成员存取函数(Get/Set函数)
  • 为成员变量xxx_提供的存取函数,统一使用小蛇形命名
  • 获取函数:直接以成员变量名(去掉后缀_)命名,无get前缀,返回对应值;
  • 设置函数:以set_+成员变量名(去掉后缀_)命名,接收参数为待设置值;
  • 示例
    class MyClass
    {
    public:
        // 获取函数:num_entries_ → num_entries()
        int num_entries() const { return num_entries_; }
        // 设置函数:num_entries_ → set_num_entries()
        void set_num_entries(int num_entries) { num_entries_ = num_entries; }
    private:
        int num_entries_;
    };
    

4.6 宏、枚举常量命名

宏定义(#define)
  • 全大写+下划线:所有字符大写,单词之间用下划线分隔;
  • 示例:#define PI_ROUNDED 3.0#define MAX_BUFFER_SIZE 1024#define ERROR_CODE -1
枚举常量(enum内的常量)
  • 全大写+下划线:与宏定义规则一致;
  • 枚举类型本身:遵循「类型命名规则」(大驼峰);
  • 示例
    // 枚举类型:大驼峰
    enum UrlTableErrors
    {
        // 枚举常量:全大写+下划线
        OK = 0,
        ERROR_OUT_OF_MEMORY,
        ERROR_MALFORMED_INPUT,
    };
    

五、Doxygen注释规则(自动生成文档,强制要求)

Doxygen是C/C++最常用的文档生成工具,本规范制定了标准化的Doxygen注释规则,要求对文件、类/结构体、变量/常量、函数进行全面注释,实现注释即文档,核心注释标签为@brief@param@return@file等。

5.1 注释基础格式

使用C++风格的多行注释,以///开头(单行Doxygen注释),或/** ... */(多行Doxygen注释),禁止使用C风格的/* ... */普通注释。

5.2 文件注释(放在文件最开头)

项目注释文件注释两层,项目注释仅在项目入口文件中添加,所有文件均需添加文件注释。

项目注释示例
///////////////////////////////////////////////////////////////////////////
/// @mainpage 项目名称:简易电话簿系统
/// @author 作者姓名
/// @version 版本号:V1.0
/// @date 开发日期:2025年01月01日
/// @brief 项目简介:基于C++实现的简易电话簿系统,支持增删查显等功能
///////////////////////////////////////////////////////////////////////////
文件注释示例
///////////////////////////////////////////////////////////////////////////
/// @file data_manager.h
/// @brief 数据管理模块头文件
/// @details 封装了数据的读取、保存、解析等功能,为上层业务提供数据支撑
/// @author 作者姓名
/// @date 2025年01月01日
///////////////////////////////////////////////////////////////////////////

5.3 类/结构体注释(放在类/结构体定义上方)

  • 核心标签:@brief(简单概述)、@details(详细概述);
  • 注释需明确类/结构体的功能、设计思路、使用场景
  • 示例
    /// @brief 用户信息类
    /// @details 封装了用户的基本信息(姓名、电话、ID),提供信息的存取接口,
    /// 是整个系统的核心数据结构,所有业务模块均依赖此类
    class UserInfo
    {
        // 类内成员
    };
    

5.4 常量/变量注释

行前注释行后注释两种方式,根据变量数量和命名长度选择:

  1. 行前注释:单个变量/少量变量,注释放在变量定义上方,用///开头;
  2. 行后注释:多个变量/命名较短的变量,注释放在变量定义右侧,用///<开头;
  • 示例
    // 行前注释
    /// 系统最大用户数
    #define MAX_USER_NUM 2048
    /// 类的成员变量:用户ID
    int user_id_;
    
    // 行后注释
    int a; ///< 临时变量a
    int b; ///< 临时变量b
    std::string name; ///< 用户名
    

5.5 函数注释(放在函数声明上方,头文件中)

函数注释是Doxygen注释的核心,必须包含功能概述、参数说明、返回值说明,核心标签为@brief@param@return,若函数无返回值则省略@return

核心规则
  1. @brief:简要描述函数的功能/行为,一句话概括;
  2. @param:描述每个参数的含义、取值范围、输入/输出属性,格式为@param 形参名 参数说明
  3. @return:描述返回值的含义、取值范围、错误码标识
  4. 若函数抛出异常,需添加@exception标签,描述异常类型和触发条件。
函数注释示例
/// @brief 添加用户信息到系统
/// @param user 待添加的用户信息对象,输入参数
/// @param err_code 错误码,输出参数:0表示成功,-1表示用户已存在,-2表示系统满
/// @return 成功返回true,失败返回false
bool AddUser(const UserInfo& user, int& err_code);

/// @brief 获取用户的电话信息
/// @param user_id 用户ID,输入参数,必须大于0
/// @return 成功返回用户电话字符串,失败返回空字符串
std::string GetUserPhone(int user_id);

5.6 Doxygen工具配置要求

  1. 项目设置:明确设置项目名称项目语言(C/C++);
  2. 编码方式
    • VS开发的项目:设置INPUT_ENCODING = GBK
    • 跨平台项目:设置INPUT_ENCODING = UTF-8(推荐);
  3. 文件匹配:设置FILE_PATTERNS*.h *.cpp *.c,匹配所有C/C++源文件和头文件;
  4. 递归扫描:开启RECURSIVE = YES,递归扫描项目目录下的所有文件。

六、规范核心总结与落地建议

核心总结

这份规范的核心是**「标准化、工程化、高可读」,所有规则均围绕团队协作长期维护**设计,摒弃个人编码习惯,强调统一化:

  1. 头文件:防重包含、减依赖、禁命名空间,降低编译耦合;
  2. 类/结构体:清晰分工、少继承、避多重继承,降低设计复杂度;
  3. 格式:4空格缩进、Allman风格括号,统一视觉呈现;
  4. 命名:分类型制定规则,见名知意,无歧义;
  5. 注释:Doxygen标准化,注释即文档,提升可维护性。

团队落地建议

  1. 强制执行:将规范纳入团队开发手册,作为代码评审的必查项
  2. 编辑器配置:统一配置编辑器(VS/Clion/Vim),实现自动缩进、空格替换Tab、命名提醒
  3. 工具辅助:使用Lint工具(如Cppcheck、Clang-Tidy)检查代码格式和命名规范,自动发现违规;
  4. 文档生成:将Doxygen文档生成纳入项目构建流程,每次编译自动更新文档;
  5. 新人培训:将本规范作为团队新人的核心培训内容,确保所有成员理解并遵守。

这份规范不仅适用于中小型企业的C/C++开发团队,也可作为个人开发者的工程化编码参考,养成良好的编码习惯,提升代码质量和开发效率。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_42388661/article/details/158428734

文章来源crawl

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--