在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一、格式 1、每行代码不多于80个字符; 2、使用空格,而不是制表符(Tab)来缩进,每次缩进4个字符; 3、指针符号*,引用符号&写在靠近类型的位置; 4、花括号的位置,使用Allman风格,另起一行,代码会更清晰; for (auto i = 0; i < 100; i++) { printf("%d\n", i); } 5、if、for、while等语句就算只有一行,也要强制使用花括号; //永远不要省略花括号 if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) goto fail; //需要写成: if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) { goto fail; } 二、命名约定 1、使用英文单词,不要夹杂拼音; 2、总体上使用驼峰命名法; 3、名字前不要加上类型前缀; bool bEmpty; const char* szName; Array arrTeachers; //不提倡这种做法。变量名字应该关注用途,而不是它的类型。上面名字应该修改为: bool isEmpty; const char* name; Array teachers; 4、类型命名 类型命名采用大写的骆驼命名法,每个单词以大写字母开头,不包含下划线。比如 GameObject
TextureSheet
5、变量命名 5.1、普通变量名字 变量名字采用小写的骆驼命名法。比如: std::string tableName; CCRect shapeBounds; 变量的名字,假如作用域越长,就越要描述详细。作用域越短,适当简短一点。 5.2、类成员变量 成员变量,访问权限只分成两级,private 和 public,不要用 protected。 私有的成员变量,前面加下划线。比如: class Image { public: ..... private: size_t _width; size_t _height; } public 的成员变量,通常会出现在 C 风格的 struct 中,前面不用加下划线。比如: struct Color4f { float red; float green; float blue; float alpha; } 5.3、静态成员 类中尽量不要出现静态变量。类中的静态变量不用加任何前缀。文件中的静态变量统一加s_前缀,并尽可能的详细命名。比如 static ColorTransformStack s_colorTransformStack; // 对 static ColorTransformStack s_stack; // 错(太简略) 5.4、全局变量 不要使用全局变量。真的没有办法,加上前缀 g_,并尽可能的详细命名。比如 Document g_currentDocument; 6、函数命名 变量名称采用小写的驼峰命名法,eg: playMusic; 函数名,整体上,应该是个动词,或者是形容词(返回bool的函数),但不要是名词。 teacherNames(); // 错(这个是总体是名词) getTeacherNames(); // 对 无论是全局函数,静态函数,私有的成员函数,都不强制加前缀。 7、命名空间 命名空间名字,使用小写加下划线的形式; namespace lua_wrapper;
使用小写加下划线,而不要使用骆驼命名法。可以方便跟类型名字区分开来。比如 lua_wrapper::getField(); // getField是命令空间lua_wrapper的函数 LuaWrapper::getField(); // getField是类型LuaWrapper的静态函数 8、宏命名 不建议使用宏,除非真的需要。宏的名字全部大写,中间使用下划线相连。 头文件的防御宏定义 #ifndef __COCOS2D_FLASDK_H__ #define __COCOS2D_FLASDK_H__ .... #endif 9、枚举命名 尽量使用 0x11 风格 enum,例如: enum class ColorType : uint8_t { Black, While, Red, } 枚举里面的数值,全部采用大写的骆驼命名法。使用的时候,就为 ColorType::Black 有些时候,需要使用0x11之前的enum风格,这种情况下,每个枚举值,都需要带上类型信息,用下划线分割。比如 enum HttpResult { HttpResult_OK = 0, HttpResult_Error = 1, HttpResult_Cancel = 2, } 10、纯C风格的接口 假如我们需要结构里面的内存布局精确可控,有可能需要编写一些纯C风格的结构和接口。这个时候,接口前面应该带有模块或者结构的名字,中间用下划线分割。比如 struct HSBColor { float h; float s; float b; }; struct RGBColor { float r; float g; float b; } RGBColor color_hsbToRgb(HSBColor hsb); HSBColor color_rgbToHsb(RGBColor rgb); 这里,color 就是模块的名字。这里的模块,充当 C++ 中命名空间的作用。 11、代码文件、路径命名 代码名跟类名一样,采用大写驼峰命名法; 12、命名避免带有个人标签
三、代码文件 1、#define保护 所有的头文件,都应该使用#define来防止头文件被重复包含。命名的格式为: __<模块>_<文件名>_H__ 很多时候,模块名字都跟命名空间对应。比如 #ifndef __GEO_POINT_H__ #define __GEO_POINT_H__ namespace geo { class Point { ..... }; } #endif 2、#include的顺序 C++代码使用#include来引入其它的模块的头文件。尽可能,按照模块的稳定性顺序来排列#include的顺序。按照稳定性从高到低排列。比如: #include <map> #include <vector> #include <boost/noncopyable.hpp> #include "cocos2d.h" #include "json.h" #include "FlaSDK.h" #include "support/TimeUtils.h" #include "Test.h" 上面例子中。#include的顺序,分别是C++标准库,boost库,第三方库,我们自己写的跟工程无关的库,工程中比较基础的库,应用层面的文件。 但有一个例外,就是 .cpp中,对应的.h文件放在第一位。比如geo模块中的, Point.h 跟 Point.cpp文件,Point.cpp中的包含 #include "geo/Point.h" #include <cmath> 这里,将 #include "geo/Point.h",放到第一位,之后按照上述原则来排列#include顺序。理由下一条规范来描述。 3、尽可能减少对头文件的依赖 代码文件中,每多出现一次#include包含, 就会多一层依赖。比如,有A,B类型,各自有对应的.h文件和.cpp文件。 当A.cpp包含了A.h, A.cpp就依赖了A.h,当A.h被修改的时候,A.cpp就需要重修编译。 若B.cpp 包含了B.h, B.h包含了A.h, 这个时候。B.cpp虽然没有直接包含A.h, 但也间接依赖于A.h。当A.h修改了,B.cpp也需要重修编译。 当在头文件中,出现不必要的包含,就会生成不必要的依赖,引起连锁反应,使得编译时间大大被拉长。 使用前置声明,而不是直接#include,可以显著地减少依赖数量。 具体实践方法见:原文 5、#include中的头文件,尽量使用全路径,或者相对路径 路径的起始点,为工程文件代码文件的根目录。 #include "ui/home/HomeLayer.h" #include "ui/home/HomeCell.h" #include "support/MathUtils.h" 不要直接包含: #include "HomeLayer.h" #include "HomeCell.h" #include "MathUtils.h" 也可以使用相对路径。比如 #include "../MathUtil.h" #include "./home/HomeCell.h" 四、作用域 作用域,表示某段代码或者数据的生效范围。作用域越大,修改代码时候影响区域也就越大,原则上,作用域越小越好。 1、全局变量 禁止使用全局变量。全局变量在项目的任何地方都可以访问。两个看起来没有关系的函数,一旦访问了全局变量,就会产生无形的依赖。使用全局变量,基本上都是怕麻烦,贪图方便。比如: funA -> funB -> funC -> funD 上图表示调用顺序。当funD需要用到funA中的某个数据。正确的方式,是将数据一层层往下传递。但因为这样做,需要修改几个地方,修改的人怕麻烦,直接定义出全局变量。这样做,当然是可以快速fix bug。但funA跟funD就引入无形的依赖,从接口处看不出来。 单件可以看做全局变量的变种。最优先的方式,应该将数据从接口中传递,其次封装单件,再次使用函数操作静态数据,最糟糕就是使用全局变量。 若真需要使用全局变量。变量使用g_开头。 2、类的成员变量 类的成员变量,只能够是private或者public, 不要设置成protected。protected的数据看似安全,实际只是一种错觉。 数据只能通过接口来修改访问,不要直接访问。这样的话,在接口中设置个断点就可以调试知道什么时候数据被修改。另外改变类的内部数据表示,也可以维持接口的不变,而不影响全局。 绝大多数情况,数据都应该设置成私有private, 变量加 _前缀。比如: class Data { private: const uint8_t* _bytes; size_t _size; } 公有的数据,通常出现在C风格的结构中,或者一些数据比较简单,并很常用的类,public数据不要加前缀。 class Point { public: Point(float x_, float y_) : x(x_), y(y_) { } ..... float x; float y; } 注意,我们在构造函数,使用 x_ 的方式表示传入的参数,防止跟 x 来重名。 3、局部变量 局部变量真正需要使用的时候才定义,一行定义一个变量,并且一开始就给它一个合适的初始值。 (在函数最前面定义变量,变量就在整个函数都可见,作用域越大,就越容易被误修改。) 4、命名空间 C++中,尽量不要出现全局函数,应该放入某个命名空间当中。命名空间将全局的作用域细分,可有效防止全局作用域的名字冲突。 比如: namespace json { class Value { .... } } namespace splite { class Value { ... } } 两个命名空间都出现了Value类。外部访问时候,使用 json::Value, splite::Value来区分。 5、文件作用域 详见原文 6、头文件中不要出现using namespace ... 头文件,很可能被多个文件包含。当某个头文件出现了 using namespace ... 的字样,所有包含这个头文件的文件,都简直看到此命令空间的全部内容,就有可能引起冲突。 // Test.h #include <string> using namespace std; class Test { public: Test(const string& name); }; 这个时候,只要包含了Test.h, 就都看到std的所有内容。正确的做法,是头文件中,将命令空间写全。将 string, 写成 std::string, 这里不要偷懒。
五、类 1、让类的接口尽可能小 设计类的接口时,不要想着接口以后可能有用就先加上,而应该想着接口现在没有必要,就直接去掉。这里的接口,你可以当成类的成员函数。添加接口是很容易的,但是修改,去掉接口会会影响较大。 接口小,不单指成员函数的数量少,也指函数的作用域尽可能小。 比如, class Test { public: void funA(); void funB(); void funC(); void funD(); }; 假如,funD 其实是可以使用 funA, funB, funC 来实现的。这个时候,funD,就不应该放到Test里面。可以将funD抽取出来。funD 只是一个封装函数,而不是最核心的。 void Test_funD(Test* test);
编写类的函数时候,一些辅助函数,优先采用 Test_funD 这样的方式,将其放到.cpp中,使用匿名空间保护起来,外界就就不用知道此函数的存在,那些都只是实现细节。 当不能抽取独立于类的辅助函数,先将函数,变成private, 有必要再慢慢将其提出到public。 不要觉得这函数可能有用,一下子就写上一堆共有接口。 再强调一次,如无必要,不要加接口。 从作用域大小,来看
2、声明顺序 类的成员函数或者成员变量,按照使用的重要程度,从高到低来排列。 比如,使用类的时候,用户更关注函数,而不是数据,所以成员函数应该放到成员变量之前。 再比如,使用类的时候,用户更关注共有函数,而不是私有函数,所以public,应该放在private前面。 具体规范
每一块中,按照下面顺序排列
.cpp 文件中,函数的实现尽可能给声明次序一致。 3、继承 优先使用组合,而不是继承。 继承主要用于两种场合:实现继承,子类继承了父类的实现代码。接口继承,子类仅仅继承父类的方法名称。 我们不提倡实现继承,实现继承的代码分散在子类跟父亲当中,理解起来变得很困难。通常实现继承都可以采用组合来替代。 规则:
比如: // swf/Definition.h class Definition { public: virtual ~Definition() {} virtual void parse(const uint8_t* bytes, size_t len) = 0; }; // swf/ShapeDefinition.h class ShapeDefinition : public Definition { public: ShapeDefinition() {} virtual void parse(const uint8_t* bytes, size_t len) override; private: Shape _shape; }; Definition* p = new ShapeDefinition(); .... delete p; 上面的例子,使用父类的指针指向子类,假如父类的析构函数不为virtual, 就只会调用父类的Definition的释放函数,引起子类独有的数据不能释放。所有需要加上virtual。 另外子类覆写的虚函数写上,override的时候,当父类修改了虚函数的名字,就会编译错误。从而防止,父类修改了虚函数接口,而忘记修改子类相应虚函数接口的情况。
六、函数 1、编写短小的函数 函数尽可能的短小,凝聚,功能单一。 只要某段代码,可以用某句话来描述,尽可能将这代码抽取出来,作为独立的函数,就算那代码只有一行。最典型的就是C++中的max, 实现只有一句话。 template <typename T> inline T max(T a, T b) { return a > b ? a : b; }
2、函数的参数尽可能少,原则上不超过5个 3、函数参数顺序 参数顺序按照传入参数,传出参数的顺序排列 4、函数的传出参数,使用指针,而不要使用引用 比如: bool loadFile(const std::string& filePath, ErrorCode* code); // 对 bool loadfile(const std::string& filePath, ErrorCode& code); // 错 因为当使用引用的时候,使用函数的时候会变成 ErrorCode code; if (loadFile(filePath, code)) { ... } 而使用指针,调用的时候,会是 ErrorCode code; if (loadFile(filePath, &code)) { ... } 这样从,&code的方式可以很明显的区分,传入,传出参数。试比较 doFun(arg0, arg1, arg2); // 错 doFun(arg0, &arg1, &arg2); // 对
七、其他 1、const 建议,尽可能多使用const C++中,const是个很重要的关键字,应用了const之后,就不可以随便改变变量的数值了,不小心改变了编译器会报错,就容易找到错误的地方。只要你觉得有不变的地方,就用const来修饰; 2、不要注释代码,代码不使用就直接删掉 要想看之前的代码,可以通过版本控制工具;
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论