C++的常用规范(一)

1、命名规范

2、代码风格

3、注释

4、头文件

5、作用域

6、全局类型

7、函数

8、优先使用C++的特性


1、命名规范

类型 命名风格

类类型、结构体类型、枚举类型、联合体类型等类型定义,作用域名称

大驼峰,如class UrlTableTester、struct UrlTableProperties、union Packet、enum UrlTableErrors、namespace HelloDemoSpace{    }

函数(全局函数、作用域函数、成员函数)

大驼峰,如:GetInputValue(){ }
全局变量,类静态变量,局部变量,函数参数,类、结构体和联合体中的成员变量 小驼峰,全局变量应该加g_前缀,如int g_activeConnectCount
宏、常量、枚举值,goto标签 全大写,下划线分割,如#define PI 3.14
文件 文件名和类名保持一致,使用大驼峰或者下划线小写风格,如DatabaseConnection.cpp 或者 database_connection.cpp

2、代码风格

  • 行宽不超过 120 个字符
  • 使用空格进行缩进,每次缩进4个空格
  • 函数声明和定义的返回类型和函数名在同一行,函数参数列表超出行宽时要换行并合理对齐,函数调用入参列表应放在一行,超出行宽换行时,保持参数进行合理对齐
ReturnType FunctionName(ArgType paramName1, ArgType paramName2) // Good:全在同一行
{}
ReturnType VeryVeryVeryLongFunctionName(ArgType paramName1, // 行宽不满足所有参数,进行换行
                                        ArgType paramName2, // Good:和上一行参数对齐
                                        ArgType paramName3){}
ReturnType LongFunctionName(ArgType paramName1, ArgType paramName2, // 行宽限制,进行换行
    ArgType paramName3, ArgType paramName4, ArgType paramName5) // Good: 换行后 4 空格缩进
{}
ReturnType ReallyReallyReallyReallyLongFunctionName( // 行宽不满足第1个参数,直接换行
    ArgType paramName1, ArgType paramName2, ArgType paramName3) // Good: 换行后 4 空格缩进
{}
  •  表达式换行要保持换行的一致性,运算符放行末
if (currentValue > threshold && // Good:换行后,逻辑操作符放在行尾
    someConditionsion) {
    DoSomething();
    ...
}

int result = reallyReallyLongVariableName1 + // Good
             reallyReallyLongVariableName2;

int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
    longVaribleName4 + longVaribleName5 + longVaribleName6; // Good: 4空格缩进

int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
          longVaribleName4 + longVaribleName5 + longVaribleName6; // Good: 保持对齐

3、注释

  • 在 C++ 代码中,使用/* */ 和// 都是可以的。
// 这是单行注释
DoSomething();
// 这是多行注释
// 第二行
DoSomething();

/* 这是单行注释 */
DoSomething();
/*
* 另一种方式的多行注释
* 第二行
*/
DoSomething();
  • 代码注释放于对应代码的上方或右边
  • 注释符与注释内容间要有1空格;右置注释与前面代码至少1空格
  • 不用的代码段直接删除,不要注释掉

4、头文件

  • 每一个.cpp文件应有一个对应的.h文件,用于声明需要对外公开的类与接口
  • 禁止头文件循环依赖、禁止包含用不到的头文件
  • 头文件应当自包含,任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,给这个头文件的用户增添不必要的负担。
  • 头文件必须编写#define 保护,防止重复包含
#ifndef VOS_INCLUDE_TIMER_TIMER_H
#define VOS_INCLUDE_TIMER_TIMER_H
...
#endif
  •  禁止通过声明的方式引用外部函数接口、变量,只能通过包含头文件的方式使用其他模块或文件提供的接口
  • 禁止在extern "C"中包含头文件
  • 头文件包含顺序:首先是.cpp相应的.h文件,其它头文件按照稳定度排序
#include "Foo/Foo.h"

#include <cstdlib>
#include <string>
#include <linux/list.h>
#include <linux/time.h>

#include "platform/Base.h"
#include "platform/Framework.h"

#include "project/public/Log.h"

5、作用域

  • 对于cpp文件中不需要导出的变量,常量或者函数,请使用匿名namespace封装,不推荐使用static,原因如下:

1. static在C++中已经赋予了太多的含义,静态函数成员变量,静态成员函数,静态全局变量,静态函数局部变
量,每一种都有特殊的处理。
2. static只能保证变量,常量和函数的文件作用域,但是namespace还可以封装类型等。
3. 统一namespace来处理C++的作用域,而不需要同时使用static和namespace来管理。
4. static修饰的函数不能用来实例化模板,而匿名namespace可以。

扫描二维码关注公众号,回复: 13048351 查看本文章
// Foo.cpp
namespace {
    const int MAX_COUNT = 20;
    void InternalFun(){};
}

void Foo::Fun()
{
    int i = MAX_COUNT;
    InternalFun();
}
  •  不要在头文件中或者#include之前使用using导入命名空间
  •  命名空间的三种使用方法
1、使用声明
#include <iostream>
using namespace std;    //直接using会把整个空间里的东西全部引入,
cout << "Hello World" << endl;

2、使用指示  
#include <iostream>
using std::cin;
using std::cout;
using std::endl;

int x = 1;
cin >> x;
cout << x << endl;

3、直接使用命名空间下的元素
#include <iostream>
std::cout << "Hello World." << std::endl;  

6、全局类型

  • 优先使用命名空间来管理全局函数,如果和某个class有直接关系的,可以使用静态成员函数
  • 优先使用命名空间来管理全局常量,如果和某个class有直接关系的,可以使用静态成员常量
  • 尽量避免使用全局变量,考虑使用单例模式,实现单例模式以后,实现了全局唯一一个实例,和全局变量同样的效果,并且单实例提供了更好的封装性
class Counter {
public:
    static Counter& GetInstance()
    {
        static Counter counter;
        return counter;
    } // 单实例实现简单举例
    void Increase()
    {
        value++;
    }
    void Print() const
    {
        std::cout << value << std::endl;
    }
private:
    Counter() : value(0) {}
private:
    int value;
};

// a.cpp
Counter::GetInstance().Increase();

// b.cpp
Counter::GetInstance().Increase();

// c.cpp
Counter::GetInstance().Print();

7、函数

  •  函数行数不超过50行

  • 内联函数不超过10行,虚函数、递归函数不能被用来做内联函数,因为虚函数是在运行时间确定而内联函数在编译时刻的机制,虚函数是内联,会产生多个函数拷贝,在某些情况下会付出空间代价

  • 函数参数使用引用取代指针,引用比指针更安全,因为它一定非空,且一定不会再指向其他目标

  • 使用强类型参数,避免使用void*

  • 函数的参数个数不超过5个,如果超过可以考虑看能否拆分函数、看能否将相关参数合在一起,定义结构体

  • 只有参数类型和个数不同,但是作用域和函数名相同的函数为重载,返回值不同的不是重载,编译期间就会报错。

  • 如果子类具有父类相同的函数名(即使参数不同),就会覆盖父类中的函数,不是重载。

8、优先使用C++的特性

  • 使用string/string_view而不是char *
  • 使用vector/array而不是原生数据
  • 使用namespace而不是static
  • 使用引用、智能指针而不是普通指针,优先使用unique_ptr 而不是shared_ptr

1. shared_ptr 引用计数的原子操作存在可测量的开销,大量使用shared_ptr 影响性能。
2. 共享所有权在某些情况(如循环依赖)可能导致对象永远得不到释放。
3. 相比于谨慎设计所有权,共享所有权是一种诱人的替代方案,但它可能使系统变得混乱。

  •  使用std::make_unique 而不是new 创建unique_ptr、使用std::make_shared 而不是new 创建shared_ptr
// 不好:两次出现 MyClass,重复导致不一致风险
std::unique_ptr<MyClass> ptr(new MyClass(0, 1));

// 好:只出现一次 MyClass,不存在不一致的可能
auto ptr = std::make_unique<MyClass>(0, 1);
  • 使用nullptr ,而不是NULL 或 0
  • 使用iostream而不是printf、scanf
  • 使用new、delete而不是malloc、free
  • 使用const常量、inline函数、函数模板而不是宏
  • 使用static_cast代替C的强制类型转换
  • 使用函数对象(仿函数),而不是函数指针
  • 使用using 而非typedef
typedef Type Alias; // Type 在前,还是 Alias 在前
using Alias = Type; // 符合'赋值'的用法,容易理解,不易出错
  • 对于不需要修改的指针和引用类型的形参,使用const修饰
  • 不会修改成员变量的成员函数,使用const修饰
  • 初始化后不会再修改的成员变量定义为const
  • 避免使用宏,如果要使用,用模板函数和内联函数代替
  • 合理使用auto

auto 可以避免编写冗长、重复的类型名,也可以保证定义变量时初始化。
auto 类型推导规则复杂,需要仔细理解。
如果能够使代码更清晰,继续使用明确的类型,且只在局部变量使用auto 。

  • 禁止用memcpy_s和memset_s初始化非POD对象,POD对象包括:int, char, float, double, enumeration, void等
  • 对聚合类型的class,使用直接的内存拷贝和比较,破坏了信息隐蔽和数据保护的作用,不提倡使用memcpy_s和menset_s。
  • 一组相关的整型常量应定义为枚举
// 好的例子:
enum Week {
    SUNDAY,
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY
};
enum Color {
    RED,
    BLACK,
    BLUE
};

void ColorizeCalendar(Week today, Color color);
ColorizeCalendar(BLUE, SUNDAY); // 编译报错,参数类型错误
// 不好的例子:
const int SUNDAY = 0;
const int MONDAY = 1;
const int BLACK = 0;
const int BLUE = 1;
bool ColorizeCalendar(int today, int color);
ColorizeCalendar(BLUE, SUNDAY); // 不会报错

猜你喜欢

转载自blog.csdn.net/MOU_IT/article/details/106924192