《C语言深度刨析》整理--函数和文件

 

进行到这里,就比较轻松愉快了,最后的内容也较少较简单

       规范的养成是一个长期的过程,关键是要有意识,开始的时候,代码写前写中写后,可以参照比较好的代码规范对比修正一下;

一、简要介绍几种较好的编码规范

       1、每一个函数都必须有注释,即使函数短到可能只有几行

       2、每个函数定义结束之后以及每个文件结束之后都要加一个或若干个空行

       3、在一个函数体内,变量定义与函数语句之间要加空行

       4、逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔

       5、修改别人代码的时候不要轻易删除别人的代码,应该将别人的代码注释掉

      6、用缩行显示程序结构,使排版整齐,缩进量统一使用4个字符(不使用TAB缩进)

            不同的编辑器tab缩进的数目不一定相同,不同编辑器打开会导致乱作一团

       7、代码行最大长度宜控制在80 个字符以内,较长的语句、表达式等要分成多行书写

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

             比如:        

view plain
print
?

  1. if ((very_longer_variable1 >= very_longer_variable12)  
  2.     && (very_longer_variable3 <= very_longer_variable14)  
  3.     && (very_longer_variable5 <= very_longer_variable16))  
  4. {  
  5.         …..;  
  6. }  

         9、如果函数中的参数较长,则要进行适当的划分

          比如:

view plain
print
?

  1. void function(float very_longer_var1,  
  2.               float very_longer_var2,  
  3.               float very_longer_var3)  

         10、如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级(不同运算符之间的运算,多加括号,避免引起歧义)

           比如:    

view plain
print
?

  1. leap_year = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);  

           11. 不要编写太复杂的复合表达式
            比如:      

view plain
print
?

  1. i = a >= b && c < d && c + f  <=  g + h;  

            12、不要有多用途的复合表达式

            比如:

            d = (a = b + c) + r;
           该表达式既求a 值又求d 值。应该拆分为两个独立的语句:
           a = b + c;
           d = a + r;

           13、参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用void 填充

           比如:

           void set_value (int, int);
           float get_value ();

           比较好的写法:

           void set_value (int number, int count);
           float get_value (void);

二、函数设计的技巧原则:

      1、原则上尽量少使用全局变量,因为全局变量的生命周期太长,容易出错,也会长时间占用空间

      2、参数命名要恰当,顺序要合理

          比如,字符串拷贝函数:

          void str_copy (char *str1, char *str2);      命名不合理,可读性差,不知道两个字符串的含义

         void str_copy (char *strSource, char *strDestination); 命名合理,但参数的顺序位置有误,一般参数拷贝函数源字符串在后面,目的字符串在前面

      3、如果参数是指针,且仅作输入参数用,则应在类型前加const,以防止该指针在函数体内被意外修改。
           例如:
           void str_copy (char *strDestination,const char *strSource);

      4、不要省略返回值的类型,如果函数没有返回值,那么应声明为void 类型。如果没有返回值,编译器则默认为函数的返回值是int类型的

      5、在函数体的“入口处”,对参数的有效性进行检查。尤其是指针参数,尽量使用assert宏做入口校验,而不使用if语句校验

      6、return 语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时被自动销毁。例如:
            char * Func(void)
           {
                  char str[30];
                  …
                 return str;
           }
         str 属于局部变量,位于栈内存中,在Func 结束的时候被释放,所以返回str 将导致错误

       7、相同的输入应当产生相同的输出。尽量避免函数带有“记忆”功能。在C 语言中,函数的static局部变量是函数的“记忆”存储器。建议尽量少用static 局部变量,除非

            必需。

        8、避免函数有太多的参数,参数个数尽量控制在4个或4个以内。如果参数太多,在使用时容易将参数类型或顺序搞错

        9、 函数名与返回值类型在语义上不可冲突

           违反这条规则的典型代表就是C语言标准库函数getchar;

          char c;
          c = getchar();
          if(EOF == c)
         {
               …
          }
        按照getchar 名字的意思,应该将变量c 定义为char 类型。但是很不幸,getchar 函数的返回值却是int 类型,其原型为:
        int getchar(void);
       由于c 是char 类型的,取值范围是[-128,127],如果宏EOF 的值在char 的取值范围之外,EOF 的值将无法全部保存到c 内,会发生截断,将EOF 值的低8 位保存到c 里。这样if 语句有可能总是失败。这种潜在的危险,如果不是犯过一次错,肯怕很难发现。    

三、递归函数

       采用递归函数可以使代码简洁,递归函数的典型代表之一快排算法的实现,下面是递归的一个示例

       1.  用递归函数实现c库函数strlen 函数(不调用库函数,也不使用任何全局或局部变量编写int  getStrlen (char *strDest);)    

view plain
print
?

  1. #include “stdio.h”  
  2. #include “assert.h”  
  3.   
  4. //求字符串长度函数  
  5. int getStrlen(char *strDest)  
  6. {  
  7.     assert(NULL != strDest);  
  8.       
  9.     if(‘\0’ == *strDest)  
  10.     return 0;  
  11.     else  
  12.     return (1 + getStrlen(++strDest));  
  13. }  
  14.   
  15. int main()  
  16. {  
  17.     char strTest[100] = {0};  
  18.     int strLength = 0;  
  19.   
  20.     while(scanf(“%s”, strTest) != EOF)  
  21.     {  
  22.     strLength = getStrlen(strTest);  
  23.     printf(“the length of the string is %d\n”, strLength);  
  24.     }  
  25.   
  26.     return 0;  
  27. }  

           上面的getStrlen 函数还可以改为简洁的 条件表达式的形式(不过从规范的角度来说,还是if….else….语句可读性较强)          

view plain
print
?

  1. //求字符串长度函数  
  2. int getStrlen(char *strDest)  
  3. {  
  4.  assert(NULL != strDest);  
  5.   
  6.  return (‘\0’ == *strDest)? 0: (1 + getStrlen(++strDest));  
  7. }  

解析:

递归注意事项:递归函数虽然代码简洁,但是递归函数的的效率较低,而且递归的层次太深容易导致栈溢出

递归函数考虑的两个方面

 1. 递归函数的递归中止条件一定要准确(否则,递归将无线循环)

 2. 递归函数的递归层次不能太深
         

四、文件结构

      简单介绍几种文件命名规范

      1. 每个头文件和源文件的头部必须包含文件头部说明和修改记录

      2.各个源文件必须有一个头文件说明,头文件中要包含一个防止头文件重复包含

         #ifndef _FILENAME_H
         #define _FILENAME_H

         ………………………
         #endif

      3. 需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部

     4. 文件标识符分为两部分,即文件名前缀和后缀。文件名前缀的最前面要使用范围限定符—模块名(文件名)缩写

     5. 采用小写字母命名文件,避免使用一些比较通俗的文件名,如:public.c 等

点赞