C++|养成良好的代码规范(那些年我踩过的坑)

成败在于细节,代码是否能运行成功也在于此,切不可忽略规范。
在 C++ 开发过程中,良好的代码规范是提高代码可读性、可维护性和团队协作效率的关键。以下是一些常见的问题和解决方案,帮助你避免那些常见的“坑”。

1. 命名规范

1.1 头文件命名

  • 全小写:头文件名应全部小写,可以包含下划线(_),但避免以数字开头。
  • 扩展名:通常使用 .h.hpp
  • 示例my_useful_class.h

1.2 类命名

  • 大驼峰命名法:类名通常使用大驼峰命名法(PascalCase),即每个单词的首字母大写。
  • 避免以下划线或数字开头:类名不应以下划线或数字开头。
  • 示例MyUsefulClass

1.3 变量命名

  • 小写和下划线:变量名通常使用小写字母,并用下划线分隔单词。
  • 类成员变量:类的成员变量通常以下划线结尾,如 my_useful_variable_
  • 示例int my_useful_variable;

1.4 函数命名

  • 小驼峰命名法:函数名通常使用小驼峰命名法(camelCase),即第一个单词小写,后续单词首字母大写。
  • 动词或动宾词组:函数名应使用动词或动宾词组,如 doSomething()
  • 示例void doSomething();

1.5 常量命名

  • 前缀 k:常量通常以 k 开头,如 kMyConstant
  • 示例const int kMyConstant = 42;

2. 作用域范围

2.1 全局作用域

  • 定义:全局变量和函数在整个程序中可见。
  • 示例
    int globalVar = 10;
    void globalFunc() {
        std::cout << globalVar << std::endl;
    }
    

2.2 局部作用域

  • 定义:局部变量只在代码块(如函数、if 语句、循环等)内可见。
  • 示例
    void localFunc() {
        int localVar = 20;
        std::cout << localVar << std::endl;
    }
    

2.3 命名空间作用域

  • 定义:命名空间中的变量和函数在命名空间范围内可见。
  • 示例
    namespace MyNamespace {
        int namespaceVar = 30;
        void namespaceFunc() {
            std::cout << namespaceVar << std::endl;
        }
    }
    

2.4 类作用域

  • 定义:类成员(包括变量和函数)在类内部可见,并可通过对象或指针访问。
  • 示例
    class MyClass {
    public:
        int classVar = 40;
        void classFunc() {
            std::cout << classVar << std::endl;
        }
    };
    

3. 命名空间

3.1 定义命名空间

  • 语法:使用 namespace 关键字定义命名空间。
  • 示例
    namespace MyNamespace {
        int myVar = 50;
        void myFunc() {
            std::cout << myVar << std::endl;
        }
    }
    

3.2 使用命名空间

  • 完全限定名:通过命名空间名访问成员。
    MyNamespace::myFunc();
    
  • using 声明:将单个标识符引入当前作用域。
    using MyNamespace::myFunc;
    myFunc();
    
  • using 指令:将命名空间中的所有内容引入当前作用域。
    using namespace MyNamespace;
    myFunc();
    

4. 宏重定义

4.1 避免宏重定义

  • 问题:宏重定义可能导致意外的行为和难以调试的错误。
  • 解决方案:使用 #ifndef 检查是否已定义宏。
    #ifndef MY_MACRO
    #define MY_MACRO
    // 宏定义内容
    #endif
    

5. 避免头文件重复包含

5.1 使用头文件保护

  • 问题:头文件重复包含可能导致编译错误。
  • 解决方案:使用头文件保护(#ifndef#define#endif)。
    // my_header.h
    #ifndef MY_HEADER_H
    #define MY_HEADER_H
    
    // 头文件内容
    
    #endif // MY_HEADER_H
    

6. 菱形继承

6.1 问题

  • 定义:菱形继承是指一个类继承了两个派生自同一个基类的子类。
  • 问题:可能导致基类被多次继承,导致对象大小增加和访问基类成员时的歧义。

6.2 解决方案

  • 虚继承:使用虚继承解决菱形继承问题。
    class Base {};
    class Derived1 : virtual public Base {};
    class Derived2 : virtual public Base {};
    class Final : public Derived1, public Derived2 {};
    

7. 枚举值定义

7.1 使用 enum class 定义枚举值

  • 问题:传统的 enum 可能导致枚举值之间的冲突和隐式类型转换。
  • 解决方案:使用 enum class 定义枚举值,这样可以避免枚举值之间的冲突,并且 enum class 不会隐式转换为整数类型。
    enum class Status {
        Active,
        Inactive
    };
    
    Status state = Status::Active;
    

8. 避免第三方库定义的宏冲突

8.1 问题

  • 问题:第三方库(如 glog)和系统库(如 Windows SDK)可能定义了同名的宏,导致冲突。
  • 解决方案
    • 使用命名空间:将第三方库的宏定义放在命名空间中,避免全局污染。
    • 条件编译:使用条件编译指令来避免冲突。
      #ifdef ERROR
      #undef ERROR
      #endif
      #include <windows.h>
      #include <glog/logging.h>
      
    • 宏重定义:在包含第三方库之前,重新定义冲突的宏。
      #define ERROR GLOG_ERROR
      #include <glog/logging.h>
      #undef ERROR
      #include <windows.h>
      

通过遵循这些规范和解决方案,可以有效避免 C++ 开发中常见的问题,提高代码的可读性和可维护性。

当然可以,以下是一些补充的常见问题及解决方案:

9. 避免全局变量的滥用

9.1 问题

  • 问题:全局变量在程序的任何地方都可以被访问和修改,这可能导致难以追踪的错误和代码难以维护。
  • 示例
    int globalVar = 10; // 全局变量
    void func() {
        globalVar = 20; // 修改全局变量
    }
    

9.2 解决方案

  • 使用局部变量:尽量使用局部变量,减少全局变量的使用。
  • 使用类成员变量:将变量封装在类中,通过成员函数访问和修改。
  • 示例
    class MyClass {
    private:
        int localVar = 10; // 类成员变量
    public:
        void setVar(int value) {
            localVar = value;
        }
        int getVar() const {
            return localVar;
        }
    };
    

10. 避免使用裸指针

10.1 问题

  • 问题:裸指针容易导致内存泄漏、野指针和悬挂指针等问题。
  • 示例
    int* ptr = new int(10);
    delete ptr;
    

10.2 解决方案

  • 使用智能指针:使用 std::unique_ptrstd::shared_ptr 来管理动态内存。
  • 示例
    #include <memory>
    std::unique_ptr<int> ptr = std::make_unique<int>(10);
    

11. 避免使用魔法数字

11.1 问题

  • 问题:直接在代码中使用未定义的数字(魔法数字)会导致代码难以理解和维护。
  • 示例
    int result = calculate(42); // 42 是什么?
    

11.2 解决方案

  • 使用常量或枚举:将魔法数字定义为常量或枚举值,增加代码的可读性。
  • 示例
    const int kMagicNumber = 42;
    int result = calculate(kMagicNumber);
    

12. 避免过度使用 using namespace

12.1 问题

  • 问题:在全局作用域中使用 using namespace 可能导致命名冲突。
  • 示例
    using namespace std;
    

12.2 解决方案

  • 使用 using 声明:仅在需要时引入特定的标识符。
  • 示例
    using std::vector;
    vector<int> myVector;
    

13. 避免使用 std::endl

13.1 问题

  • 问题std::endl 会刷新缓冲区,可能导致性能问题。
  • 示例
    std::cout << "Hello" << std::endl;
    

13.2 解决方案

  • 使用换行符:使用换行符 \n,并在需要时手动刷新缓冲区。
  • 示例
    std::cout << "Hello\n";
    std::cout.flush();
    

14. 避免在头文件中定义函数体

14.1 问题

  • 问题:在头文件中定义函数体可能导致编译时间增加和符号重复定义错误。
  • 示例
    // my_header.h
    int add(int a, int b) {
        return a + b;
    }
    

14.2 解决方案

  • 将函数定义放在源文件中:将函数定义放在 .cpp 文件中,只在头文件中声明函数。
  • 示例
    // my_header.h
    int add(int a, int b);
    
    // my_source.cpp
    #include "my_header.h"
    int add(int a, int b) {
        return a + b;
    }
    

15. 避免使用 std::vector<bool>

15.1 问题

  • 问题std::vector<bool> 是一个特化模板,其行为与普通 std::vector 不同,可能导致意外行为。
  • 示例
    std::vector<bool> vec;
    

15.2 解决方案

  • 使用 std::vector<int> 或其他类型:如果需要布尔值集合,可以使用 std::vector<int> 或其他类型。
  • 示例
    std::vector<int> vec;
    

16. 避免使用 std::auto_ptr

16.1 问题

  • 问题std::auto_ptr 已被弃用,其行为可能导致意外的拷贝语义问题。
  • 示例
    std::auto_ptr<int> ptr(new int(10));
    

16.2 解决方案

  • 使用智能指针:使用 std::unique_ptrstd::shared_ptr 来管理动态内存。
  • 示例
    std::unique_ptr<int> ptr = std::make_unique<int>(10);
    

17. 避免使用 std::bind

17.1 问题

  • 问题std::bind 的语法较为复杂,且在某些情况下可能导致性能问题。
  • 示例
    auto func = std::bind(myFunction, _1, _2);
    

17.2 解决方案

  • 使用 Lambda 表达式:Lambda 表达式通常更简洁、更易读。
  • 示例
    auto func = [](int a, int b) { return myFunction(a, b); };
    

18. 避免使用 std::function 存储 Lambda 表达式

18.1 问题

  • 问题std::function 存储 Lambda 表达式可能导致不必要的性能开销。
  • 示例
    std::function<int(int, int)> func = [](int a, int b) { return a + b; };
    

18.2 解决方案

  • 直接使用 Lambda 表达式:直接使用 Lambda 表达式,避免不必要的类型转换。
  • 示例
    auto func = [](int a, int b) { return a + b; };
    

19. 避免使用 std::stringstream

19.1 问题

  • 问题std::stringstream 的性能较差,且在某些情况下可能导致内存泄漏。
  • 示例
    std::stringstream ss;
    ss << "Hello" << 42;
    std::string result = ss.str();
    

19.2 解决方案

  • 使用 std::ostringstreamstd::istringstream:根据需要选择合适的流类型。
  • 示例
    std::ostringstream oss;
    oss << "Hello" << 42;
    std::string result = oss.str();
    

20. 避免使用 std::exception 的派生类

20.1 问题

  • 问题std::exception 的派生类可能导致异常处理的复杂性增加。
  • 示例
    class MyException : public std::exception {
    public:
        const char* what() const noexcept override {
            return "MyException";
        }
    };
    

20.2 解决方案

  • 使用标准异常类:尽量使用标准异常类,如 std::runtime_errorstd::logic_error
  • 示例
    throw std::runtime_error("MyException");
    

21. 避免使用 std::setstd::map 的默认比较函数

21.1 问题

  • 问题:默认的比较函数可能导致性能问题,尤其是在自定义类型中。
  • 示例
    std::set<MyClass> mySet;
    

21.2 解决方案

  • 自定义比较函数:为自定义类型提供自定义比较函数。
  • 示例
      struct MyClassCompare {
          bool operator()(const MyClass& a, const MyClass& b) const {
              return a.id < b.id;
          }
      };
      
      std::set<MyClass, MyClassCompare> mySet;
    

都看到这里的不点个赞再走?欢迎评论区讨论,大家都遇到过哪些由于不规范导致的BUG哈哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奇树谦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值