读书心得(函数设计篇)

内容取自《高质量C/C++编程》

前言:本章内容我读的也有些吃力,里面涉及了不少C++的知识,我对本章内容做了删减,欠缺之处望海涵。

        函数是 C++/C 程序的基本功能单元,每一个函数都是一个子程序,其重要性不言而喻。函数设计的细微缺点很容 易导致该函数被错用,所以光使函数的功能正确是不够的。本章重点论述函数的接口设 计和内部实现的一些规则。
         函数接口的两个要素是参数和返回值。C 语言中,函数的参数和返回值的传递方式有两种:值传递(pass by value)和指针传递(pass by pointer)。C++ 语言中多了引用传递(pass by reference)。由于引用传递的性质像指针传递,而使用方式却像值传递,初学者常常迷惑不解,容易引起混乱, 后续我会在为大家讲解指针时进行补充,这里不做过多阐述。

一、 参数的规则

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

void SetValue(int width, int height);         // 良好的风格
void SetValue(int, int);                               // 不良的风格
float GetValue(void);                                 // 良好的风格
float GetValue();                                         // 不良的风格

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

         例如:编写字符串拷贝函数 StringCopy,它有两个参数。如果把参数名字起为 str1 和
str2,例如:void StringCopy(char *str1, char *str2);
那么我们很难搞清楚究竟是把 str1 拷贝到 str2 中,还是刚好倒过来。可以把参数名字起得更有意义,如叫 strSource 和 strDestination。这样从名字上就可以看出应该把 strSource 拷贝到 strDestination。还有一个问题,这两个参数哪一个该在前哪一个该在后?参数的顺序要遵循程序员的习惯。一般地,应将目的参数放在前面,源参数放在后面。如果将函数声明为: void StringCopy(char *strSource, char *strDestination);
别人在使用时可能会不假思索地写成如下形式:
char str[20];
StringCopy(str, “Hello World”); // 参数顺序颠倒

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

void StringCopy(char *strDestination,const char *strSource);

4、如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。

建议1:应避免函数有太多的参数,参数个数尽量控制在 5 个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。

建议2:尽量不要使用类型和数目不确定的参数。C 标准库函数 printf 是采用不确定参数的典型代表,其原型为: int printf(const chat *format[, argument]…);     这种风格的函数在编译时丧失了严格的类型安全检查。

二、返回值的规则

1、不要省略返回值的类型。

         C 语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为 void 类型。
        C++语言有很严格的类型安全检查,不允许上述情况发生。由于 C++程序可以调用C 函数,为了避免混乱,规定任何 C++/ C 函数都必须有类型。如果函数没有返回值,那么应声明为 void 类型。

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

违反这条规则的典型代表是 C 标准库函数 getchar。
例如:
char c;
c = getchar();
if (c == EOF)
按照 getchar 名字的意思,将变量 c 声明为 char 类型是很自然的事情。但不幸的是getchar 的确不是 char 类型,而是 int 类型,其原型为: int getchar(void);
        由于 c 是 char 类型,取值范围是[-128,127],如果宏 EOF 的值在 char 的取值范围 之外,那么 if 语句将总是失败,这种“危险”人们一般哪里料得到!导致本例错误的责任并不在用户,是函数 getchar 误导了使用者。

3、不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用 return 语句返回。

        回顾上例,C 标准库函数的设计者为什么要将 getchar 声明为令人迷糊的 int 类型呢?他会那么傻吗?

        在正常情况下,getchar 的确返回单个字符。但如果 getchar 碰到文件结束标志或发生读错误,它必须返回一个标志 EOF。为了区别于正常的字符,只好将 EOF 定义为负数(通常为负1)。因此函数 getchar 就成了 int 类型。

        其实在实际工作中,人们经常会碰到上述令人为难的问题。为了避免出现误解,我们应
该将正常值和错误标志分开。即:正常值用输出参数获得,而错误标志用 return 语句返回。

建议1有时候函数原本不需要返回值,但为了增加灵活性,如支持链式表达,可以附加返回值。

 例如字符串拷贝函数 strcpy 的原型: char *strcpy(char *strDest,const char *strSrc);

        strcpy 函数将 strSrc 拷贝至输出参数 strDest 中,同时函数的返回值又是 strDest。   这样做并非多此一举,可以获得如下灵活性:
char str[20];
int length = strlen( strcpy(str, “Hello World”) );

三、函数内部实现的规则

         不同功能的函数其内部实现各不相同,看起来似乎无法就“内部实现”达成一致的观点。但根据经验,我们可以在函数体的“入口处”和“出口处”从严把关,从而提高函数的质量。

 1、在函数体的“入口处”,对参数的有效性进行检查。

        很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert 来防止此类错误。(后续会讲解断言相关的知识点)

2、在函数体的“出口处”,对 return 语句的正确性和效率进行检查。如果函数有返回值,那么函数的“出口处”是 return 语句。我们不要轻视 return 语句。如果 return 语句写得不好,函数要么出错,要么效率低下。

四、其他建议

1、函数的功能要单一,不要设计多用途的函数。

2、函数体的规模要小,尽量控制在 50 行代码之内。

3、尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。 带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某 种“记忆状态”。这样的函数既不易理解又不利于测试和维护。在 C/C++ 语言中,函数 static 局部变量是函数的“记忆”存储器。建议尽量少用 static 局部变量,除非必需。
4、不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内 的变量的有效性,例如:全局变量、文件句柄等。
5、用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误 情况。

 

你可能感兴趣的:(c语言,c++)