C/C++编码规范

C/C++编码规范

写出漂亮易读的程序是很重要的,不仅方便自己查看,也方便别人修改。于是我参考了网上别人的优秀博文(文章后面会贴出出处),对原内容进行了些修改,并整理成此文章。以下的编码规则适用于C++游戏服务端,当然,其它语言也可以参考该规则。

一、文档的结构

  1. 头文件用来声明,cpp文件用来定义。声明和定义分开,可以让代码更清晰、方便阅读,同时,如果在某些场合,代码不能公开,只向用户提供头文件和二进制的库即可。通常这样的情况下,头文件和源文件是在不同目录的, 按照实际意义区分目录,一个目录内超过10个文件,要考虑是否拆分。
  2. #include<file>引用标准库头文件(编译器从标准库目录开始搜索),#include"file.h"引用其他文件(编译器从工作目录开始搜索)
  3. 尽量保持头文件中不包含头文件
  4. 为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预处理块。这个宏定义必须讲究,如果重复了,可能会出现莫名其妙的问题。个人习惯是工程名+目录+文件名。

例如:

#ifndef _GateApp_Role_Player_H_  
#define _GateApp_Role_Player_H_  
//代码实现在这里。。。  
#endif  

二、命名规范

命名是非常重要的,对于所有类型的命名都有效的规范:

  1. 名称表述清楚,使用统一的英文词汇。
  2. 不使用英文简写,除非是一些固有的缩略语。
  3. 单词命名优先表达清楚意思,其次才考虑缩短长度,不过也不要太长。
  4. 对于无法用英文表述的命名,使用拼音。每个词看做一个单词,中间不用大写

例如:

    1. 修仙副本 xiuxianCopy
    2. 炫阳套装 xuanyangSuit
  1. 因为汉字太多的话,大写太多会很乱。
  2. 使用驼峰命名法,严禁全字母大写并且没有”_”的命名。
  3. 所有函数、变量、类等,都必须要加注释,即使一看就懂的也翻译一下中文。
  4. 命名要一致,不同的功能,对于相同的概念,要用相同的名称。
  5. 使用反义词去命名互斥意义的变量或函数
  6. 尽量不要出现标识符完全相同的局部变量和全局变量
  7. 同一个项目的代码下,即使在不同工程内,也不要出现完全相同的文件名称,这是VS的一个bug,调试起来会在同名文件间跳来跳去。

游戏代码的命名是个蛋疼的事情,作为游戏程序员,碰到的很多名词是无法翻译的。例如诛邪、神兽、涅槃、貔貅、修为、境界、法力,很多很多,有些时候你可能翻译了一个单词出来,过几天一个新的功能,你会发现意义好像差不多的。这个时候我们经常会用拼音,通俗易懂啊。

还有个更蛋疼的事情,游戏的功能是多变的,策划的需求一天一个样,例如某个货币叫做荣誉,做好了功能,过几天这个荣誉要改名为修为,另外的地方突然有个货币叫做荣誉。这个时候你不得不跟着把名称也改掉。否则过一个星期,没几个人看得懂,而且跟客户端,PHP后台,运营等交流也很蛋疼,大家无法同步。所以,命名的时候不一定要按他们给出来的来命名,最好用一些跟中性的而又不违反语义的单词,另外自己弄一张汉语-英文-功能对照表。还有文档的功能名称通常跟上线后的功能名称完全不同的。

     变量命名规范

  1. 变量的名字推荐使用名词或者形容词+名词
  2. 变量名前不使用任何表示类型的前缀。
  3. 类内部数据成员,以前缀“_”开头
  4. 变量首字母使用小写
  5. 函数内局部变量,在函数长度不大和表达清晰的情况下可以适当减少变量名。
  6. 变量名应该反映的是实际使用意义
  7. 一般情况下code是编码,属于偏向配置,id是身份证,属于具体对象,不要乱用。

    函数命名规范

  1. 函数的名字推荐使用名动词或者动词+名词
  2. 函数名必须直观,并且能正确表达其内在功能。
  3. 对于函数参数中的引用和指针类型视情况以const修饰。
  4. 对于不修改数据成员的类成员函数,以const修饰
  5. 函数名以小写开头
  6. 返回值为布尔型的一些检测函数,正确的要返回true,错误的返回false,不要调转含义。

   类和结构名命名规范

  1. 类名以C开头结构以S开头,都以大写开头。
  2. 接口类的命名沿用上面规则,并以前缀“I”开头,例如:IInterfaceMgr,IModule。

   常量与宏命名规范

  1. 尽量不使用宏定义;
  2. 对于常数定义,尽量采用const修饰或enum定义;
  3. 对于简单的操作,采用inline函数方式定义;
  4. 常量名第一个字母大写

   文件命名规范

  1. 目录名每个单词首字母大写,不使用任何分隔符;

   typedef类型命名规范

  1. typedef std::vector<int> SeqInt;  //vector的全部都在前面加Seq
  2. typedef ::std::map<int, int> DictIntInt;//map在cdl中加前缀Dict,在代码中用Map
  3. 必须以大写开头。
  4. 命名翻译的是内部的组成,例如SeqInt,一看就是vector<int>。不能改为PlayerId这种具有确切意义的命名,缺乏通用性。具体含义应该在变量名上反映出来。

二、程序风格

平时用得比较多的是Visual Studio,所以下面的风格也是这个软件的

   空格放置

  1. 关键字之后要留空格。

例如 const 、virtual 、inline 、case  等关键字之后至少要留一个空格,否则无法辨析关键字。

例如 if 、for 、while 等关键字之后应留一个空格再跟 ( ‘,以突出关键字。  

  1. 函数名之后不要留空格,紧跟'(',以与关键字区别。
  2. '('向后紧跟,右括号、逗号、分号向前紧跟,紧跟处不留空格。’’之后要留空格如Function(x,  y,  z) 。
  3. 如果‘; ’不是一行的结束符号,其后要留空格,如 for (int i = 0;  i < 10; i++) 
  4. 除“[]”、“.”、“->”、“::”外,双目操作符, 如“= ”、“+= ”      “>= ”、“<= ”、“+ ”、“* ”、“% ”、“&& ”、“||”、“<< ”,  “^ ”等两侧各留一个空格。  
  5. 单目操作符如“!”、“~ ”、“++ ”、“-- ”、“& ”(地址运算符)与操作数之间不留空格。  
  6. 对于表达式比较长的 for 语句和 if 语句,为了紧凑起见可以适当地去掉一些空格。

如 for (i=0; i<10; i++)和 if ((a<=b) && (c<=d)) ,或者太长的话可以适当换行。

  1. 当一个函数的返回值是指针变量或引用变量时,类型与操作符(“*”或“&”)之间不留空格,操作符之后留一个空格

例如:


//  良好的风格                              // 不良的风格

void Func1(int x, int y, int z);           void Func1 (int x,int y,int z);     
 
 if (year >= 2000)                         if(year>=2000)    
 
 if ((a>=b) && (c<=d))                     if(a>=b&&c<=d) 
 
 for (i=0; i<10; i++)                      for(i=0;i<10;i++) 或 for (i = 0; I < 10; i ++)
 
 x = a < b ? a : b;                        x=a<b?a:b;  
 
 int *x = &y;                              int * x = & y;     

 array[5] = 0;                             array [ 5 ] = 0;  

 a.Function();                             a . Function();  

 b->Function();                            b -> Function();  

int& Func1(int& x, int* y);           	   int &Func1 (int &x,int *y);  

   布局规范

  1. 程序体建议以TAB缩进(4个空格),少使用空格缩进。
  2. 尽可能在定义变量的同时初始化该变量(就近原则),放久了很容易忘记。
  3. 类的public函数写在前面,private数据写在后面。要注意这个类本身的含义。
  4. 每一个函数(或类方法)之间至少保留一个空行
  5. 一行代码只做一件事情,只定义一个变量,只写一条语句。这样的代码容易阅读,方便于写注释。
  6. 在一个函数体内,逻揖上密切相关的语句之间不加空行,具有语义转换时,必须保留一个空行
  7. 复合语句if 、for 、while 、do  等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加 {}。这样可以防止书写失误。
  8. 程序的分界符‘ {’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。 这个是C++的风格,java的风格是{紧跟出现前的代码 
  9. { }之内的代码块在‘{’右边一个制表符处左对齐出现嵌套的{ },则使用缩进(按Tab键)对齐
  10. 多次使用的代码,尽量抽出来作为一个函数。
  11. 代码行不要过长代码行最大长度宜控制在 70 至 80 个字符以内。否则要拉来拉去很麻烦
  12. 长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。表达式内尽量不要套多层表达式。
  13. 判断一个指针是否为空时,使用“!ptr”的形式判断。
  14. 对于数据成员都公开的类,使用struct关键字来定义,否则使用class关键字。
  15. 对于计数器或迭代器,可使用前++(--)或后++ (--)时,一律使用前++(--)。

不良风格的代码:

int attack, defense, life; // 攻击、防御、生命,同时声明多个变量,没有初始化
int hurt= attack - defense;   life -= hurt; //执行多条语句 

if ((crit >= 100) && (block <= 100) && (wreck <= 100)){
    onDeath1(); 
}else{
    onDeath2(); 
}
for (SeqCPlayer::iter iter = player.begin();iter != player.end();iter++){
    check();
}
virtual CPoint changeToNewPoint(CPoint attackPoint,CPoint defensePoint);

良好风格的代码:

int attack = 0; // 攻击 
int defense = 0; // 防御 --跟上下联系紧密,不加空行
int life = 0; // 生命    
 
int hurt= attack - defense;   
life -= hurt;
 
if ((crit >= 100)
&& (block <= 100)
&& (wreck <= 100))
{
    onDeath1(); 
}
else
{
    onDeath2(); 
}                                                  

for (SeqCPlayer::iter iter = player.begin();
    iter != player.end();
    ++iter)
{
    check();
}

virtual CPoint changeToNewPoint(CPoint attackPoint,
                                CPoint defensePoint);

   注释

  1. 程序块的注释常采用“/*…*/”,行注释一般采用“ //…”。
  2. 注释是对代码的“提示”,而不是文档。注释风格统一。
  3. 边写代码边注释,改代码后要同时修改注释
  4. 注释应当准确、易懂,防止注释有二义性。 错误的注释有害。
  5. 尽量避免在注释中使用缩写,特别是不常用缩写。
  6. 注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。在VS中,在上方注释后,下面所有代码的提示都受到影响的,所以优先放后方
  7. 当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。
  8. 对于思路复杂的代码,可以把思路写在注释里面,否则别人看起来很蛋疼。另外一些设计思想也应该写下来。

对于一些业务多变,而且往往不合常理的需求,一定要多写注释。例如游戏代码中,很多策划特意要求做的特殊处理,有的是为了坑玩家,更多的是为了体验。这东西看代码是看不到任何思路的。当你发现某个地方明明可以用很简洁的代码写,却硬是要用一些很傻比的方法的时候,很可能不是那个人没经验或者偷懒,而是策划要求造成的。所以像游戏这种系统,一定要多注释,但改的时候一定要跟着改,否则,-_-!

四、从习惯开始优化程序的性能

不要总是想着用什么高级方法,不要为了用而用。很多时候,先写出来,后面再优化就好了。

  常量

  1. 尽量使用常量来表示程序中经常出现的数字或字符串
const float PI = 3.14159; // C++ 语言的 const 常量
  1. 需要对外公开的常量放在头文件,内部使用的放在源文件。把常量放在同一个文件中,一旦多了之后改动一下,多个地方要编译。

  表达式和基本语句

  1. 如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免 使用默认的优先级。
  2. 不要编写太复杂的复合表达式。
i = a >= b && c < d && c + f <= g + h ; // 复合表达式过于复杂
  1. 不要有多用途的复合表达式。
d = (a = b + c) + r ;
  1. 不要用数学表达式的方法来写程序中的表达式
if (a < b < c) // a < b < c 是数学表达式而不是程序表达式
//并不表示 
if (( a < b ) && ( b < c ))
//而成了
if ( (a <b) < c))
  1. 不可将布尔变量直接与 true、false 、 1、0 进行比较
bool isPlayer  = ture;
if (flag) // 表示 flag 为真 
if (!flag) // 表示 flag 为假 
//其它的用法都属于不良风格,例如:
if (flag == TRUE) 
if (flag == 1 ) 
if (flag == FALSE) 
if (flag == 0)
  1. 应当将整型变量用“==”或“!=”直接与 0 比较。而且最好把0写在左边,其他数字也是
int life = 0;
if ( 0 == life) 
if ( 0 != life )
if ( life == 0 )//容易写成 if ( life = 0 ),而且不会报错
//不可模仿布尔变量的风格而写成 
if (value) // 会让人误解 value 是布尔变量 
if (!value)
  1. 不可将浮点变量用“==”或“!=”与任何数字比较。 因为float和double都有精度限制
double x = 0;
if ( 0.0 == x )//隐含错误的比较
if ((x >= -EPSINON) && (x <= EPSINON)) //其中 EPSINON 是允许的误差(即精度)。
  1. 指针变量与Null比较,最好不直接写。
//推荐:
if (p == NULL) // p 与 NULL 显式比较,强调 p 是指针变量 i
if (p != NULL) 
//不推荐
if (p == 0) // 容易让人误解 p 是整型变量 
if (p != 0) 
if (p) 
if (!p)

  循环语句的使用

  1. 少用慎用goto。
  2. 嵌套循环不要太多,不方便阅读
  3. 尽量长循环放里面,短循环放外面有些时候比较难把短循环抽出去的话,就没必要太刻意了。
  4. 不可在 for 循环体内修改循环变量,防止 for 循环失去控制。尤其注意erase的用法
typedef std::vector<CPoint> SeqCPoint;
typedef std::map<int, CPoint> MapCPoint;
SeqCPoint points;
MapCPoint pointMap;

for (SeqCPoint::iter iter = points.begin();
    iter != points.end();
    )
{
    if(xxx)
    {
        points.erase(iter);//erase后,指针会跳到下一个
    }
    else
    {
        iter ++;
    }
}
 
for (MapCPoint::iter iter = pointMap.begin();
    iter != pointMap.end();
    iter ++;)
{
    if(xxx)
    {
        pointMap.erase(iter);//erase不需要特殊处理
    } 
}

}构造、析构、赋值函数

  1. 赋值函数只是位拷贝而不是值拷贝,如果其中有指针就很可能出问题。而且会造成内存泄漏。
  2. 构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表),初始化表在函数参数表之后,在函数体{}之前。
  3. 如果类存在构造关系,必须在初始化表里面初始化。
  4. 带有const的属性必须在初始化表里面初始化。
  5. 一个类初始化的时候,如果属性不是基础类型,放在初始化表来初始化,效率更高。

  内存管理

  1. 内存没分配成功,通常用指向这个内存的指针检测是否为空。

内存分配不成功,后面的处理已经不重要了。整个系统内存都爆了,宕机什么的是必然的了。然后运营会收到各种投诉,查错,赔偿,维护一大堆事情陆续有来。

  1. 内存后,要养成初始化的习惯。
    不要以为检测一个数字为负数就能确定还没初始化,如果碰上重复使用的那块内存,真的不知道是什么。
  2. 动态内存的申请与释放必须配对,防止内存泄漏。 
  3. 用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产 生“野指针”。
  4. 指针消亡,并不代表指向的内存被释放,内存被释放,并不代表指针为null。
  5. 指针声明的时候,要么指向具体内存,要么设置为null,否则是随机值,可能if(p == Null)不起作用的。
  6. new一个对象的代价大,还是初始化后重用的大呢?初始化可能更加占消耗

  函数

  1. 函数声明是,参数要写完整,命名要恰当。
  2. 参数最好加上&,可以大量节省对象构造和析构的时间。
  3. 只做传值的参数最好加上const
  4. 不要使用太多参数,参数太多,可以弄一个结构包起来,让程序清晰。
  5. 尽量不要使用类型和数量不确定参数
  6. 默认参数要慎用,而且不要太多。
  7. 函数返回和函数名字在语义上要一致
  8. 函数功能要单一,不要设计多用途的函数
  9. 函数体规模要小,50行内,超过了最好拆分一下。
  10. 在函数体内,容易出现return或者throw的分支尽量写在前面,可以省掉剩下的判断
  11. 如果返回值是一个对象,要考虑效率

例如:

//效率高,直接在函数外部构造了
return String(s1 + s2); 

//效率低,需要经过构造,传递,析构
String temp(s1 + s2);
return temp;

//当然,这个对基础类型没什么作用
return 1 + 1;
int temp = 1 + 1;
return temp;

  其他建议

  1. 不要一味提高效率,应该更多考虑程序的可读性,可维护性。如果你也是做游戏的话,很可能你的游戏根本没几个人玩,永远碰不到这个瓶颈,哈哈哈哈哈!
  2. 优化程序的时候,要充分考虑游系统的特性,找出瓶颈所在。例如一个MMO游戏,在初期推广的时候,很多新玩家,经常在某个时段某个功能很多人同时玩,这个要注意。到了后期的功能,可能会很少人接触,但是在后来往往有很多跨服功能,几乎所有玩家在同一个服玩,这些都是要考虑的问题。
  3. 最好的优化应该是从业务级别开始。很多东西计算量固定在那,怎么减都减不了的。需要考虑从业务级别入手。
  4. 先优化数据结构,再优化算法
  5. 有时候时间效率和空间效率对立,要做出折冲的方案现在内存很便宜,硬盘更便宜。但是系统启动读数据,可能会很慢很慢。
  6. 避免编写技巧性很高代码。
  7. 当心变量发生上溢或下溢,数组的下标越界。
  8. 尽量使用标准库函数,不要“发明”已经存在的库函数。
  9. 尽量不要使用与具体硬件或软件环境关系密切的变量。

参考:小规则让你写出漂亮又高效的程序_肥宝的实验室-CSDN博客

个人公众号:拾一札记

  • 7
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值