函数的写法:
写一个函数,首先是函数的接口。包括几个要素,是否static,返回值,函数名,输入参数,输出参数。
Static,以前已经说过其用法了。
返回值,原则上说,有返回值,则调用该函数的时候就应该检查返回值。如果不检查,还不如先写成void类型,到需要检查返回值的时候,再写成带返回类型的。
返回值,很多人喜欢返回整数类型,然后再定义:
#define OK (0)
#define NOK (1)
我一般习惯将返回值定义成枚举类型,见前文。
这也是我的一个偏好,很喜欢枚举类型,讨厌直接返回数字1,2,3,4,5之类的。返回OK、NOK之类的宏都好一些,但我认为枚举类型更紧凑。
函数名,一般采用驼峰法命名,采用动宾结构,例如GetParam,DetectDevice之类,模块级的接口函数一般加前缀,如playInterface1,也有人喜欢写成PLAY_Interface1,我更喜欢前面的写法,紧凑简单。
函数带参数,本来是天经地义的。但看到不少这样的函数,
Init()、GetParam(),不带参数,然后在函数中直接访问全局变量。//L
函数带几个参数呢,少点好。超过5个以后,就用结构体封装起来,然后传结构体指针进去。
函数参数的排列,排整齐,不要参差不齐。
强烈推荐使用const修饰函数参数,防止参数被意外改变。
被const修饰,也暗示了该参数只能是个输入参数,而不可能成为输出。输出通常是传指针进来,再改变指针所指向的值。
另外,const *,* const ,*const *,请搞明白。
定义好了函数接口,下面是函数实现了。
一般你会怎么做呢,拿起键盘就狂敲?
我一般一下想不清该怎么实现,一般就会这样写。
//参数检查
//步骤一,获取配置
//步骤二,获取输入参数
//步骤三,计算
…
//最后一步,输出结果
反正就是先写伪码。伪码写完,再翻译成代码语言,
//参数检查
…
//步骤一,获取配置
GetConfig(…);
//步骤二,获取输入参数
GetInputParam(…);
//步骤三,计算
Caculate(&stConfig, &stParam, &stOutput);
…
//最后一步,输出结果
…
写完后,就把伪码擦掉,如果觉得擦掉会影响可读性,那就改善该段代码的可读性,如果还是觉得擦不掉,就留下吧,留下来的,就是注释。
在上面的过程中,又产生了GetConfig等几个函数,这几个函数出现的时候,就马上去定义他们。
把这个函数写完后,再到GetConfig中去,如法炮制,写伪码,根据伪码,造个函数出来,定义新函数,
然后擦掉伪码,擦不掉的就成为注释。不断这样做,一直到各个函数都变成功能很单一的小函数的时候,再去实现这些函数。
多小算小函数呢,原则上,不要超过两屏,有的公司要求不超过200行,有的极端的人要求10行内,我个人建议60行,因为我一屏通常有30行代码,两屏在我接受的范围内。200行的函数,上下翻动,找东西,都不方便。
函数中必然会定义许多变量,这些变量你一般怎么排列呢?
我看到很多代码中变量排列相当随意,非常杂乱,很多没有初始化。
我一般在做函数的功能实现时,用到一个变量,再去定义他。定义变量时,大体遵循这样的原则,指针类型先定义,结构体先定义,多字节类型先于单字节类型。先用到的变量后定义,后用到的类型先定义。
坚持这样的原则,可以使变量定义不再杂乱。当然,这是个人习惯,你也可以用其他的方式,但务必保持一致,使代码整洁。
函数实现的其他规则:
嵌套不超过三层;
单行不超过80个字符,不允许代码写到屏幕外面去;
只做一件事情;
有些人可能会说,我的函数功能太复杂了,200行实现不了啊。实际上,我真的见过1700++行的怪物函数,不过真的,这么长是人搞出来的,本来完全可以写很短的。
这个怪物函数中,一个if else就用了50多个ifelse,然后每个else里面写一段功能实现。我简直晕死了。
这个函数,一种方法是使用switch结构,但这还是不够好,毕竟50多个case还是很要命的,更好的方法是直接使用函数指针,将每个类型映射为一个函数指针,这个用一张表就可以实现了。然后,通过函数指针调用,实现功能。
后来,通过仔细观察,我发现这些else之后的功能实现,还能采用更好的方式。因为每个else后的功能都是相似的,所以可以写几个函数来实现所有这些功能。如果采用这样的写法,这个大函数是可以通过一系列小函数来实现的。
总之,只要肯分解,函数要不了那么长。