编程基本功-细节定成败

引言

看武侠都知道,即便你的武艺再高,如果内力跟不上,那在对手面前也只是花拳绣腿。编程也一样,即便你写的逻辑再深、业务再复杂,如果你的代码没有人能看懂(过段时间自己也懵逼了),下次再解决类似问题又得花费很长时间来处理。由此可说明你没有一套好的内功心法,无法积攒自己的内功。

小测试

让我们先来做个测试。看你心中的高手(或者自己)符合以下几条。

  1. 真正的程序员没有进度表,只有讨好领导的马屁精才有进度表,真正的程序员会让领导提心吊胆。
  2. 真正的程序员不写使用说明书,用户应当自己去猜想程序的功能。
  3. 真正的程序员几乎不写代码的注释,如果注释很难写,它理所当然也很难读。
  4. 真正的程序员不画流程图,原始人和文盲才会干这事。
  5. 真正的程序员不看参考手册,新手和胆小鬼才会看。
  6. 真正的程序员不写文档也不需要文档,只有看不懂程序的笨蛋才用文档。
  7. 真正的程序员认为自己比用户更明白用户需要什么。
  8. 真正的程序员不接受团队开发的理念,除非他自己是头头。
  9. 真正的程序员的程序不会在第一次就正确运行,但是他们愿意守着机器进行若干个30小时的调试改错。
  10. 真正的程序员不会在上午 9:00 到下午 5:00 之间工作,如果你看到他在上午9:00 工作,这表明他从昨晚一直干到现在。
    ...

是否心中很信仰这样的程序员?的确,这样‘真正的’程序员无拘无束、行云流水,我一开始是很向往的。在拜读林锐的《高质量C++、C编程指南》之后,逐渐发现,若照此修炼,就只能花拳绣腿。因此练好内功,才能刚!

内功修炼法精简版(正文)

1.程序版式

【规则1】一行代码只做一件事,如只定义一个变量,或只写一条语句。便于阅读,便于注释。

int width;   //宽度  
int height;  //高度
int depth;   //深度

【建议】尽可能在定义变量的同时初始化该变量(就近原则)

int width = 10;   //宽度  
int height = 10;  //高度
int depth = 10;   //深度

【规则2】if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论 执行语句有多少都要加{}。

if (width < height) 
{
   dosomething();
}

【规则3】空格问题
1.关键字之后要留空格。象 const、virtual、inline、case 等关键字之后 至少要留一个空格。if、for、while 等关键字之后应留一个 空格再跟左括号‘(’,以突出关键字。

  1. ‘ ,’之后要留空格,如 Function(x, y, z)。如果‘;’不是一行的结束
    符号,其后要留空格,如 for (initialization; condition; update)。
  2. 赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=”“>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。
    ...
    详见如下代码
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;

【规则4】内容对齐

void Function(int x) 
{
    ... // program code 
}

if (condition) 
{
    ... // program code 
}
else 
{
    ... // program code 
}

【规则5】长行拆分:长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以 便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。

if ((very_longer_variable1 >= very_longer_variable12)
   && (very_longer_variable3 <= very_longer_variable14) 
   && (very_longer_variable5 <= very_longer_variable16))
{
    dosomething();
}

virtual CMatrix CMultiplyMatrix (CMatrix leftMatrix, 
                                 CMatrix rightMatrix);
                                 
for (very_longer_initialization; 
     very_longer_condition;
     very_longer_update)
{
    dosomething();
}                               

【规则6】注释
1、注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主, 注释太多了会让人眼花缭乱。注释的花样要少。
2、如果代码本来就是清楚的,则不必加注释。否则多此一举,令人厌烦。例如 i++; //i 加 1,多余的注释
3、当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。

/*
* 函数介绍:
* 输入参数:
* 输出参数:
* 返回值 :
*/
void Function(float x, float y, float z)
{
    ... 
}

if (...) 
{
    ...
    while (...) 
    {
        ...
    } // end of while 
    ...
} // end of if
2.命名

【规则1】变量和参数用小写字母开头的单词组合而成。 例如:

BOOL flag;
int drawMode;

【规则2】常量全用大写的字母,用下划线分割单词。 例如:

const int MAX = 100;
const int MAX_LENGTH = 1000;

【规则3】静态变量加前缀 s_(表示 static)。 全局变量,则使全局变量加前缀 g_(表示 global)。
例如:

void Init(...) 
{
    static int s_initValue; // 静态变量
    ... 
}

int g_howManyPeople;  // 全局变量 
int g_howMuchMoney;   // 全局变量
3.表达式和基本语句


【规则1】不要有多用途的复合表达式。
例如:
d = (a = b + c) + r ;

该表达式既求 a 值又求 d 值。应该拆分为两个独立的语句:

a = b + c; 
d = a + r;

【规则2】不可将布尔变量直接与 TRUE、FALSE 或者 1、0 进行比较。
假设布尔变量名字为 flag,它与零值比较的标准 if 语句如下:

if (flag)    //表示flag为真
if (!flag)   //表示 flag 为假

其它的用法都属于不良风格,例如:

if (flag == TRUE) 
if (flag == 1 )
if (flag == FALSE) 
if (flag == 0)

【规则3】应当将整型变量用“==”或“!=”直接与0比较。
假设整型变量的名字为 value,它与零值比较的标准 if 语句如下:

if (value == 0)
if (value != 0)

不可模仿布尔变量的风格而写成

if (value)   // 会让人误解 value 是布尔变量 
if (!value)

【规则4】应当将指针变量用“==”或“!=”与NULL比较。
指针变量的零值是“空”(记为 NULL)。尽管 NULL 的值与 0 相同,但是两者意义不 同。假设指针变量的名字为 p,它与零值比较的标准 if 语句如下:

if (p == NULL)   // p 与 NULL 显式比较,强调 p 是指针变量
if (p != NULL)

有时候我们可能会看到 if (NULL == p) 这样古怪的格式。不是程序写错了,是程 序员为了防止将 if (p == NULL) 误写成 if (p = NULL),而有意把 p 和 NULL 颠倒。编 译器认为 if (p = NULL) 是合法的,但是会指出 if (NULL = p)是错误的,因为 NULL 不能被赋值。
比如OC的懒加载方法中:

if (nil == object)
{
    object = [NSObject alloc] init];
    ...
} 

【规则5】循环语句效率
1.在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的 循环放在最外层,以减少 CPU 跨切循环层的次数。

// 低效率
for (row=0; row<100; row++) 
{
    for ( col=0; col<5; col++ ) 
    {
        sum = sum + a[row][col]; 
    }
} 
// 高效率
for (col=0; col<5; col++ ) 
{
    for (row=0; row<100; row++) 
    {
        sum = sum + a[row][col]; 
    }
}

2.如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。

// 效率低但代码简洁 若N较小适用,执行效率劣势不大
for (i=0; i<N; i++) 
{
    if (condition) 
        DoSomething();
    else 
        DoOtherthing();
}

// 效率高但代码冗余 若N较大适用,执行效率较高
if (condition) 
{
    for (i=0; i<N; i++) DoSomething();
} 
else 
{
    for (i=0; i<N; i++) DoOtherthing();
}

3.建议for语句的循环控制变量的取值采用“半开半闭区间”写法。
半开半闭区间写法:for (int x=0; x<N; x++)
闭区间写法: for (int x=0; x<=N-1; x++)

4.常量

const 与 #define 的比较

【规则1】能用const的地方尽量用const来的代替#define,由于宏常量没有数据类型,const定义的常量编译器可以进行数据类型的安全检查。

【规则2】把公开的常量放在声明文件中,不公开的常量放在实现文件中。
【规则3】如果某一常量与其它常量密切相关,应在定义中包含这种关系。例如:

const float RADIUS = 100;
const float DIAMETER = RADIUS * 2;
5.内存管理

【一】杜绝“野指针”
“野指针”不是 NULL 指针,是指向“垃圾”内存的指针。人们一般不会错用 NULL 指针,因为用 if 语句很容易判断。但是“野指针”是很危险的,if 语句对它不起作用。
成因:
1.指针未初始化。指针刚创建时不会自动置为NULL,需要置为NULL或指向合法内存。

char *p = NULL;
char *str = (char *) malloc(100);

2.指针p被free或者delete之后,没有置为NULL,会让人误以为p是个合法指针。错误案例如下:

char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的内存被释放,但是 p 所指的地址仍然不变 
...
if(p != NULL) // 没有起到防错作用
{
    strcpy(p, “world”); // 出错 
}

3.指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:

class A
{ 
public:
    void Func(void){ cout << “Func of class A” << endl; } 
};
    
void Test(void) 
{
    A *p; 
        {
            A a;
            p = &a;    // 注意 a 的生命期 
        }
    p->Func();     // p 是“野指针” 
}

【二】malloc/free 的使用要点
函数 malloc 的原型如下:

void * malloc(size_t size);

用 malloc 申请一块长度为 length 的整数类型的内存,程序如下:

int *p = (int *) malloc(sizeof(int) * length);

【注1】malloc 返回值的类型是 void *,所以调用malloc要进行显示转换一下类型。
【注2】malloc 本身不识别申请的内存类型,只关心内存的总字节数。由于不同位数的操作系统数据的字节又不一样,所以需要使用sizeof来计算所占字节数。

函数 free 的原型如下:

void free( void * memblock );

为什么 free 函数不象 malloc 函数那样复杂呢?这是因为指针 p 的类型以及它所指 的内存的容量事先都是知道的,语句 free(p)能正确地释放内存。如果 p 是 NULL 指针, 那么 free 对 p 无论操作多少次都不会出问题。如果 p 不是 NULL 指针,那么 free 对 p 连续操作两次就会导致程序运行错误。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 1 文件结构 每个C++/C程序通常分为两个文件。一个文件用于保存程序的声明(declaration),称为头文件...
    Mr希灵阅读 2,860评论 0 13
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,674评论 0 38
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • 30天推番挑战 1、你的入坑作:《魔法咪路咪路》 这真的是暴露年龄系列……小的时候在星空台看过,恩……这也是暴露年...
    三日昉主阅读 4,995评论 0 0
  • 孔爷爷说,有朋自远方来,不亦乐乎。想想以后漫长的日子里,三两知己,几杯清茶,闲聊些许,岂不快哉? 1你好,日记 以...
    舟木木阅读 257评论 2 1