![]() ![]() |
|
C++编程指南学习(六) | |
作者:佚名 文章来源:不详 点击数 更新时间:2008/4/18 14:38:28 文章录入:杜斌 责任编辑:杜斌 | |
|
|
第6章 函数设计
2 【建议6-2-1】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。 例如字符串拷贝函数strcpy的原型: char *strcpy(char *strDest,const char *strSrc); strcpy函数将strSrc拷贝至输出参数strDest中,同时函数的返回值又是strDest。这样做并非多此一举,可以获得如下灵活性: char str[20]; int length = strlen( strcpy(str, “Hello World”) ); 2 【建议6-2-2】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。 例如: class String {… // 赋值函数 String & operate=(const String &other); // 相加函数,如果没有friend修饰则只许有一个右侧参数 friend String operate+( const String &s1, const String &s2); private: char *m_data; } String的赋值函数operate = 的实现如下: String & String::operate=(const String &other) { if (this == &other) return *this; delete m_data; m_data = new char[strlen(other.data)+1]; strcpy(m_data, other.data); return *this; // 返回的是 *this的引用,无需拷贝过程 } 对于赋值函数,应当用“引用传递”的方式返回String对象。如果用“值传递”的方式,虽然功能仍然正确,但由于return语句要把 *this拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,降低了赋值函数的效率。例如: String a,b,c; … a = b; // 如果用“值传递”,将产生一次 *this 拷贝 a = b = c; // 如果用“值传递”,将产生两次 *this 拷贝 String的相加函数operate + 的实现如下: String operate+(const String &s1, const String &s2) { String temp; delete temp.data; // temp.data是仅含‘\0’的字符串 temp.data = new char[strlen(s1.data) + strlen(s2.data) +1]; strcpy(temp.data, s1.data); strcat(temp.data, s2.data); return temp; } 对于相加函数,应当用“值传递”的方式返回String对象。如果改用“引用传递”,那么函数返回值是一个指向局部对象temp的“引用”。由于temp在函数结束时被自动销毁,将导致返回的“引用”无效。例如: c = a + b; 此时 a + b 并不返回期望值,c什么也得不到,流下了隐患。 6.3 函数内部实现的规则 不同功能的函数其内部实现各不相同,看起来似乎无法就“内部实现”达成一致的观点。但根据经验,我们可以在函数体的“入口处”和“出口处”从严把关,从而提高函数的质量。 l 【规则6-3-1】在函数体的“入口处”,对参数的有效性进行检查。 很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert)来防止此类错误。详见6.5节“使用断言”。 l 【规则6-3-2】在函数体的“出口处”,对return语句的正确性和效率进行检查。 如果函数有返回值,那么函数的“出口处”是return语句。我们不要轻视return语句。如果return语句写得不好,函数要么出错,要么效率低下。 注意事项如下: (1)return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。例如 char * Func(void) { char str[] = “hello world”; // str的内存位于栈上 … return str; // 将导致错误 } (2)要搞清楚返回的究竟是“值”、“指针”还是“引用”。 (3)如果函数返回值是一个对象,要考虑return语句的效率。例如 return String(s1 + s2); 这是临时对象的语法,表示“创建一个临时对象并返回它”。不要以为它与“先创建一个局部对象temp并返回它的结果”是等价的,如 String temp(s1 + s2); return temp; 实质不然,上述代码将发生三件事。首先,temp对象被创建,同时完成初始化;然后拷贝构造函数把temp拷贝到保存返回值的外部存储单元中;最后,temp在函数结束时被销毁(调用析构函数)。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。 类似地,我们不要将 return int(x + y); // 创建一个临时变量并返回它 写成 int temp = x + y; return temp; 由于内部数据类型如int,float,double的变量不存在构造函数与析构函数,虽然该“临时变量的语法”不会提高多少效率,但是程序更加简洁易读。 6.4 其它建议 2 【建议6-4-1】函数的功能要单一,不要设计多用途的函数。 2 【建议6-4-2】函数体的规模要小,尽量控制在50行代码之内。 2 【建议6-4-3】尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。 带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态”。这样的函数既不易理解又不利于测试和维护。在C/C++语言中,函数的static局部变量是函数的“记忆”存储器。建议尽量少用static局部变量,除非必需。 2 【建议6-4-4】不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。 2 【建议6-4-5】用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。 6.5 使用断言 程序一般分为Debug版本和Release版本,Debug版本用于内部调试,Release版本发行给用户使用。 断言assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。示例6-5是一个内存复制函数。在运行过程中,如果assert的参数为假,那么程序就会中止(一般地还会出现提示对话,说明在什么地方引发了assert)。 void *memcpy(void *pvTo, const void *pvFrom, size_t size) { |
|
![]() ![]() |