读书心得(函数设计篇)

2023-12-22 11:31:22

内容取自《高质量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、用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误 情况。

?

文章来源:https://blog.csdn.net/zy1215058242/article/details/135146563
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。