赞
踩
C++ 是一门十分复杂并且威力强大的语言,使用这门语言的时候我们应该有所节制,绝对的自由意味着混乱。
我十分清楚每个人对怎么编写代码都有自己的偏好。这里定下的规范,某些地方可能会跟个人原来熟悉的习惯相违背,并引起不满。但多人协作的时候,需要有一定规范。定下一些规范,当大家面对某些情况,有所分歧的时候,容易达成共识。另外通过一定规范,加强代码的一致性,从团队中某人的代码切换到另一个人的代码,会更为自然,让别人可以读懂你的代码是很重要的。
通常面对某种情况,会有两种或更多种做法,我们就挑选其中一种,共同遵守。这并不表示另一种做法就是错的,只是仅仅不同。
这里规范是死的,现实是多变的,当你觉得某些规范,对你需要解决问题反而有很大限制,可以违反,但要有理由,而不仅仅是借口。那到底是否在寻找借口,并没有很明确的判断标准。这就如同不能规定少于多少根头发为秃头,但当我们看到某个人的时候,自然能够判断他是否是秃头。同样,当我们碰到具体情况的时候,自然能够判断是否在寻找借口。
本规范编写过程中,大量参考了《Google C++ 编程规范》,Google那份规范十分好,建议大家对比着看。
------------------------------------------
从前的电脑终端,每行只可以显示 80 个字符。现在有更大更宽的显示屏,很多人会认为这条规则已经没有必要。但我们有充分的理由:
规则总会有例外。比如当你有些代码行,是 82 个字符,假如我们强制规定少于80字符,人为将一行容易读的代码拆分成两行代码,就太不人性化了。我们可以适当超过这个限制。
代码编辑器,基本都可以设置将Tab转为空格,请打开这个设置。
制表符在每个软件中的显示,都会有所不同。有些软件中每个Tab缩进8个字符,有些软件每个Tab缩进4个字符,随着个人的设置不同而不同。只使用空格来缩进,保证团队中每个人,看同一份代码,格式不会乱掉。
- CCNode* p = CCNode::create(); // (1)
- CCNode *p = CCNode::create(); // (2)
也就说,上面两种写法。写成第(1)种。
我知道这个规定有很大的争议。指针符号到底靠近类型,还是靠近变量,这争论一直没有停过。其实两种写法都没有什么大问题,关键是统一。经考虑,感觉第1种写法更统一更合理。理由:
CCNode* _a;
CCNode _b;
int _c;
当星号靠近类型而不是变量。_a, _b, _c 等变量会很自然对齐。
而当星号靠近变量,如果不手动多按空格微调,会写成。
CCNode *_a;
CCNode _b;
int _c;
const char* getTableName();
static_cast<CCLayer*>(node);
反对第一种写法的理由通常是:
int* a, b, c;
上面写法本身就有问题。应该每行定义一个变量, 并初始化。
int* a = nullptr;
int* b = nullptr;
int* c = nullptr;
这个有点道理。但我们也不能十分依赖工具。可以使用clang_format等美化工具去辅助调整代码。
采用Allman风格,if, for, while,namespace, 命名空间等等的花括号,另起一行。例子
- for (auto i = 0; i < 100; i++)
- {
- printf("%d\n", i);
- }
这条规定,很可能又引起争议。很多人采用 K&R 风格,将上面代码写成
- for (auto i = 0; i < 100; i++) {
- printf("%d\n", i);
- }
K&R风格在书籍印刷上会节省纸张。但在实际的代码中显得过于密集。Allman风格会更加清晰易读。当然,这理由带有很多主观因素。
永远不要省略花括号,不要写成:
- if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
- goto fail;
需要写成:
- if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
- {
- goto fail;
- }
省略花括号,以后修改代码,或者代码合并的时候,容易直接多写一行。如
- if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
- goto fail;
- goto fail;
就会引起错误。
------------------------------------------
这条规则强制执行,不能有例外。
单词与单词之间,使用大小写相隔的方式分开,中间不包含下划线。比如
- TimerManager // (1)
- playMusic // (2)
其中(1)为大写的骆驼命名法,(2)为小写的骆驼命名法。
不要使用
- timer_manager
- play_music
这种小写加下划线的方式在 boost 库,C++ 标准库中,用得很普遍。
接下来分别描述具体的命名方式。
有些代码库,会在变量名字前面加上类型前缀。比如 b表示 bool, i 表示 int , arr 表示数组, sz 表示字符串等等。他们会命名为
- bool bEmpty;
- const char* szName;
- Array arrTeachers;
我们不提倡这种做法。变量名字应该关注用途,而不是它的类型。上面名字应该修改为
- bool isEmpty;
- const char* name;
- Array teachers;
注意,我们将 bool 类型添加上is。isEmpty, isOK, isDoorOpened,等等,读起来就是一个询问句。
类型命名采用大写的骆驼命名法,每个单词以大写字母开头,不包含下划线。比如
- GameObject
- TextureSheet
类型的名字,应该带有描述性,是名词,而不要是动词。尽量避开Data, Info, Manager 这类的比较模糊的字眼。(但我知道有时也真的避免不了,看着办。)
所有的类型,class, struct, typedef, enum, 都使用相同的约定。例如
- class UrlTable
- struct UrlTableProperties
- typedef hash_map<UrlTableProperties*, std::string> PropertiesMap;
- enum UrlTableError
变量名字采用小写的骆驼命名法。比如
- std::string tableName;
- CCRect shapeBounds;
变量的名字,假如作用域越长,就越要描述详细。作用域越短,适当简短一点。比如
- for (auto& name : _studentNames)
- {
- std::cout << name << std::endl;
- }
-
- for (size_t i = 0; i < arraySize; i++)
- {
- array[i] = 1.0;
- }
名字清晰,并且尽可能简短。
成员变量,访问权限只分成两级,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;
- }
类中尽量不要出现静态变量。类中的静态变量不用加任何前缀。文件中的静态变量统一加s_前缀,并尽可能的详细命名。比如
- static ColorTransformStack s_colorTransformStack; // 对
- static ColorTransformStack s_stack; // 错(太简略)
不要使用全局变量。真的没有办法,加上前缀 g_,并尽可能的详细命名。比如
Document g_currentDocument;
变量名字采用小写的骆驼命名法。比如
- playMusic
- getSize
- isEmpty
函数名字。整体上,应该是个动词,或者是形容词(返回bool的函数),但不要是名词。
- teacherNames(); // 错(这个是总体是名词)
- getTeacherNames(); // 对
无论是全局函数,静态函数,私有的成员函数,都不强制加前缀。但有时静态函数,可以适当加s_前缀。
类的成员函数,假如类名已经出现了某种信息,就不用重复写了。比如
- class UserQueue
- {
- public:
- size_t getQueueSize(); // 错(类名已经为Queue了,
- // 这里再命名为getQueueSize就无意义)
- size_t getSize(); // 对
- }
命令空间的名字,使用小写加下划线的形式,比如
namespace lua_wrapper;
使用小写加下划线,而不要使用骆驼命名法。可以方便跟类型名字区分开来。比如
- lua_wrapper::getField(); // getField是命令空间lua_wrapper的函数
- LuaWrapper::getField(); // getField是类型LuaWrapper的静态函数
不建议使用宏,但真的需要使用。宏的名字,全部大写,中间加下划线相连接。这样可以让宏更显眼一些。比如
- #define PI_ROUNDED 3.0
- CLOVER_TEST
- MAX
- MIN
头文件出现的防御宏定义,也全部大写,比如:
- #ifndef __COCOS2D_FLASDK_H__
- #define __COCOS2D_FLASDK_H__
-
- ....
-
- #endif
不要写成这样:
- #ifndef __cocos2d_flashsdk_h__
- #define __cocos2d_flashsdk_h__
-
- ....
-
- #endif
尽量使用 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,
- }
假如我们需要结构里面的内存布局精确可控,有可能需要编写一些纯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++ 中命名空间的作用。
- struct Path
- {
- ....
- }
-
- Path* Path_new();
- void Path_destrory(Path* path);
- void Path_moveTo(Path* path, float x, float y);
- void Path_lineTo(Path* path, float x, float y);
这里,接口中Path出现的是类的名字。
代码文件的名字,应该反应出此代码单元的作用。
比如 Point.h, Point.cpp,实现了class Point;
当 class Point,的名字修改成,Point2d, 代码文件名字,就应该修改成 Point2d.h, Point2d.cpp。代码文件名字,跟类型名字一样,采用大写的骆驼命名法。
路径名字,对于于模块的名字。跟上一章的命名规范一样,采用小写加下划线的形式。比如
- ui/home/HomeLayer.h
- ui/battle/BattleCell.h
- support/geo/Point.h
- support/easy_lua/Call.h
路径以及代码文件名,不能出现空格,中文,不能夹着拼音。假如随着代码的修改,引起模块名,类型名字的变化,应该同时修改文件名跟路径名。
比如,不要将某个模块名字为
- HJCPoint
- hjc/Label.h
hjc为团队某人名字的缩写。
项目归全体成员所有,任何人都有权利跟义务整理修改工程代码。当某样东西打上个人标记,就倾向将其作为私有。其他人就会觉得那代码乱不关自己事情,自己就不情愿别人来动自己东西。
当然了,文件开始注释可以出现创建者的名字,信息。只是类型,模块,函数名字,等容易在工程中散开的东西不提倡。个人项目可以忽略这条。
再强调一下,任何人都有权利跟义务整理修改他人代码,只要你觉得你修改得合理,但不要自作聪明。我知道有些程序员,会觉得他人修改自己代码,就是入侵自己领土。
有些时候,我们需要自己写的库跟C++的标准库结合。这时候可以采用跟C++标准库相类似的风格。比如
- class MyArray
- {
- public:
- typedef const char* const_iteator;
- ...
-
- const char* begin() const;
- const char* rbegin() const;
- }
----------------
所有的头文件,都应该使用#define来防止头文件被重复包含。命名的格式为
__<模块>_<文件名>_H__
很多时候,模块名字都跟命名空间对应。比如
- #ifndef __GEO_POINT_H__
- #define __GEO_POINT_H__
-
- namespace geo
- {
- class Point
- {
- .....
- };
- }
-
- #endif
并且,#define宏,的名字全部都为大写。不要出现大小写混杂的形式。
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顺序。理由下一条规范来描述。
代码文件中,每出现一次#include包含, 就会多一层依赖。比如,有A,B类型,各自有对应的.h文件和.cpp文件。
当A.cpp包含了A.h, A.cpp就依赖了A.h,我们表示为
A.cpp -> A.h
这样,当A.h被修改的时候,A.cpp就需要重修编译。 假设
- B.cpp -> B.h
- B.h -> A.h
这表示,B.cpp 包含了B.h, B.h包含了A.h, 这个时候。B.cpp虽然没有直接包含A.h, 但也间接依赖于A.h。当A.h修改了,B.cpp也需要重修编译。
当在头文件中,出现不必要的包含,就会生成不必要的依赖,引起连锁反应,使得编译时间大大被拉长。
使用前置声明,而不是直接#include,可以显著地减少依赖数量。实践方法:
比如写类A,有文件 A.h, 和A.cpp 那么在A.cpp中,将A.h的包含写在第一位。在A.cpp中写成
- // 前面没有别的头文件包含
- #include "A.h"
- #include <string>
- #include .......
.... 包含其它头文件
之后可以尝试在 A.h 中去掉多余的头文件。当A.cpp可以顺利编译通过的时候,A.h包含的头文件就是过多或者刚刚好的。而不会是包含不够的。
首先,只在头文件中使用引用或者指针,而不是使用值的,可以前置声明。而不是直接包含它的头文件。 比如
- class Test : public Base
- {
- public:
- void funA(const A& a);
- void funB(const B* b);
- void funC(const space::C& c);
-
- private:
- D _d;
- };
这里,我牵涉到几个其它类,Base, A, B, space::C(C 在命名空间space里面), D。Base和D需要知道值,A, B, space::C只是引用和指针。所以Base, C的头文件需要包含。A, B,space::C只需要前置声明。
- #include "Base.h"
- #include "D.h"
-
- namespace space
- {
- class C;
- }
-
- class A;
- class B;
- class Test : public Base
- {
- public:
- void funA(const A& a);
- void funB(const B* b);
- void funC(const space::C& c);
-
- private:
- D _d;
- };
注意命名空间里面的写法。
就是类里面包含实现类的指针。在cpp里面实现。
简单说,就是不要将所有东西都塞在一起。这样的代码组积相对清晰。头文件包含也相对较少。但现实中,或多或少会违反。
比如,工程用到一些常量字符串(或者消息定义,或者enum值,有多个变种)。一个似乎清晰的结构,是将字符串都放到同一个头文件中。不过这样一来,这个字符串文件,就几乎会被所有项目文件包含。当以后新加一个字符串时候,就算只加一行,工程几乎被全部编译。
更好的做法,是按照字符串的用途来分拆开。
又比如,有些支持库。有时贪图方便,不注意的,就会写一个 GlobalUtils.h 之类的头文件,包含所有支持库,因为这样可以不关心到底应该包含哪个,反正包含GlobalUtils.h就行,这样多省事。不过这样一来,需要加一个支持的函数,比如就只是角度转弧度的小函数,也会发生连锁编译。
更好的做法,是根据需要来包含必要的文件。就算你麻烦一点,写10行#include的代码,都比之后修改一行代码,就编译上10多分钟要好。
减少编译时间,这点很重要。再啰嗦一下
路径的起始点,为工程文件代码文件的根目录。
比如
- #include "ui/home/HomeLayer.h"
- #include "ui/home/HomeCell.h"
- #include "support/MathUtils.h"
不要直接包含
- #include "HomeLayer.h"
- #include "HomeCell.h"
- #include "MathUtils.h"
这样可以防止头文件重名,比如一个第三方库文件有可能就叫 MathUtils.h。
并且移植到其它平台,配置起来会更容易。比如上述例子,在安卓平台上,就需要配置包含路径
- <Project_Root>/ui/home/
- <Project_Root>/support/
也可以使用相对路径。比如
- #include "../MathUtil.h"
- #include "./home/HomeCell.h"
这样做,还有个好处。就是只用一个简单脚本,或者一些简单工具。就可以分析出头文件的包含关系图,然后就很容易看出循环依赖。
--------------------
作用域,表示某段代码或者数据的生效范围。作用域越大,修改代码时候影响区域也就越大,原则上,作用域越小越好。
禁止使用全局变量。全局变量在项目的任何地方都可以访问。两个看起来没有关系的函数,一旦访问了全局变量,就会产生无形的依赖。使用全局变量,基本上都是怕麻烦,贪图方便。比如
funA -> funB -> funC -> funD
上图表示调用顺序。当funD需要用到funA中的某个数据。正确的方式,是将数据一层层往下传递。但因为这样做,需要修改几个地方,修改的人怕麻烦,直接定义出全局变量。这样做,当然是可以快速fix bug。但funA跟funD就引入无形的依赖,从接口处看不出来。
单件可以看做全局变量的变种。最优先的方式,应该将数据从接口中传递,其次封装单件,再次使用函数操作静态数据,最糟糕就是使用全局变量。
若真需要使用全局变量。变量使用g_开头。
类的成员变量,只能够是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 来重名。
局部变量尽可能使它的作用范围最小。换句话说,就是需要使用的时候才定义,而不要在函数开始就全部定义。
从前C语言有个约束,需要将用到的全部变量都定义在函数最前面。之后这个习惯也被传到C++的代码当中。但这种习惯是很不好的。
我们的结论是,局部变量真正需要使用的时候才定义,一行定义一个变量,并且一开始就给它一个合适的初始值。
- int i;
- i = f(); // 错,初始化和定义分离
- int j = g(); // 对,定义时候给出始值
C++中,尽量不要出现全局函数,应该放入某个命名空间当中。命名空间将全局的作用域细分,可有效防止全局作用域的名字冲突。
比如
- namespace json
- {
- class Value
- {
- ....
- }
- }
-
- namespace splite
- {
- class Value
- {
- ...
- }
- }
两个命名空间都出现了Value类。外部访问时候,使用 json::Value, splite::Value来区分。
假如,某个函数,或者类型,只在某个.cpp中使用,请将函数或者类放入匿名命名空间。来防止文件中的函数导出。比如
- // fileA.cpp
- namespace
- {
- void doSomething()
- {
- ....
- }
- }
上述例子,doSomething这个函数,放入了匿名空间。因此,此函数限制在fileA.cpp中使用。另外的文件定义相同名字的函数,也不会造成冲突。
另外传统C的做法,是在 doSomething 前面加 static, 比如
- // fileB.cpp
- static void doSomething()
- {
- ...
- }
doSomething也限制到文件fileB.cpp中。
同理,只在文件中出现的类型,也放到匿名空间中。比如
- // sqlite/Value.cpp
- namespace sqlite
- {
- namespace
- {
- class Record
- {
- ....
- }
- }
- }
上述例子,匿名空间嵌套到sqlite空间中。这样Record这个结构只可以在sqlite/Value.cpp中使用,就算是同属于空间sqlite的文件,也不知道 Record 的存在。
头文件,很可能被多个文件包含。当某个头文件出现了 using namespace ... 的字样,所有包含这个头文件的文件,都简直看到此命令空间的全部内容,就有可能引起冲突。比如
- // Test.h
- #include <string>
- using namespace std;
-
- class Test
- {
- public:
- Test(const string& name);
- };
这个时候,只要包含了Test.h, 就都看到std的所有内容。正确的做法,是头文件中,将命令空间写全。将 string, 写成 std::string, 这里不要偷懒。
----------------
面向对象编程中,类是基本的代码单元。本节列举了在写一个类的时候,需要注意的事情。
设计类的接口时,不要想着接口以后可能有用就先加上,而应该想着接口现在没有必要,就直接去掉。这里的接口,你可以当成类的成员函数。添加接口是很容易的,但是修改,去掉接口会会影响较大。
接口小,不单指成员函数的数量少,也指函数的作用域尽可能小。
比如,
- 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。 不要觉得这函数可能有用,一下子就写上一堆共有接口。
再强调一次,如无必要,不要加接口。
从作用域大小,来看
类的成员函数或者成员变量,按照使用的重要程度,从高到低来排列。
比如,使用类的时候,用户更关注函数,而不是数据,所以成员函数应该放到成员变量之前。 再比如,使用类的时候,用户更关注共有函数,而不是私有函数,所以public,应该放在private前面。
具体规范
每一块中,按照下面顺序排列
.cpp 文件中,函数的实现尽可能给声明次序一致。
优先使用组合,而不是继承。
继承主要用于两种场合:实现继承,子类继承了父类的实现代码。接口继承,子类仅仅继承父类的方法名称。
我们不提倡实现继承,实现继承的代码分散在子类跟父亲当中,理解起来变得很困难。通常实现继承都可以采用组合来替代。
规则:
比如
- // 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的时候,当父类修改了虚函数的名字,就会编译错误。从而防止,父类修改了虚函数接口,而忘记修改子类相应虚函数接口的情况。
--------------------
函数尽可能的短小,凝聚,功能单一。
只要某段代码,可以用某句话来描述,尽可能将这代码抽取出来,作为独立的函数,就算那代码只有一行。最典型的就是C++中的max, 实现只有一句话。
- template <typename T>
- inline T max(T a, T b)
- {
- return a > b ? a : b;
- }
人脑短时记忆的数字是很有限的,大约可以记忆7个数字。有些人多些,有些人少些。我们这里取最少值,就是5个参数。
参数的个数,太多,就很容易混乱,记不住参数的意义。
同时参数的个数太多,很可能是因为这个函数做的事情有点多了。
可以通过很多手段来减少参数的个数。比如将函数分解,分解成多个短小的函数。或者将几个经常一起的参数,封装成一个类或者结构。比如,设计一个绘画贝塞尔曲线的接口
- void drawQuadBeizer(float startX, float startY,
- float controlX, float controlY,
- float endX, float endY);
这样的接口,就不够
- void drawQuadBeizer(const Point& start,
- const Point& control,
- const Point& end);
简洁易用。
当然,每个规则都会有例外。比如设置一个矩阵的数值,二维矩阵本来就需要6个数字来表示,设置接口自然需要6个参数。
参数顺序,按照传入参数,传出参数,的顺序排列。不要使用可传入可传出的参数。
- bool loadFile(const std::string& filePath, ErrorCode* code); // 对
- bool loadFile(ErrorCode* code, const std::string& filePath); // 错
保持统一的顺序,使得他人容易记忆。
比如
- 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); // 对
我们经常会通过查看现有的代码来了解如何使用函数的接口。缺省参数使得某些参数难以从调用方就完全清楚,需要去查看函数的接口,也就是完全了解某个接口,需要查看两个地方。
另外,缺省参数那个数值,其实是实现的一部分,写在头文件是不适当的。
缺省参数,其实可以通过将一个函数拆分成两个函数。实现放到.cpp中。
-------------------
我们建议,尽可能的多使用const。
C++中,const是个很重要的关键字,应用了const之后,就不可以随便改变变量的数值了,不小心改变了编译器会报错,就容易找到错误的地方。只要你觉得有不变的地方,就用const来修饰吧。比如:
const的位置:
- const int* name; // 对(这样写,可读性更好)
- int const* name; // 错
有些人不习惯使用版本控制工具,某段代码不再使用了,他们会注释掉代码,而不是直接删除掉。他们的理由是,这段代码现在没有用,可能以后会有用,我注释了,以后真的再用的时候,就不用再写了。
不要这样做。
注释掉的代码,放在源文件里面,会将正常的代码搞混乱。有个破窗理论,说假如一个窗户破了,不去管它,路人就会倾向敲烂其它的窗户。同样,假如你看到代码某个地方乱了,会觉得再搞的更乱也没有关系,就会越来越乱。
而在现代的版本控制工具下,只要写好提交记录,找回从前的代码是很容易的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。