- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
2.4 函数
由于函数用于创建 C++程序的模块,对 C++的 OOP 定义至关重要,因此必须熟悉它。函数的某些方面属于高级主题,将在第 7 章和第 8 章重点讨论函数。然而,现在了解函数的一些基本特征,将使得在以后的函数学习中更加得心应手。本章剩余的内容将介绍函数的一些基本知识。
C++函数分两种:有返回值的和没有返回值的。在标准 C++函数库中可以找到这两类函数的例子,您也可以自己创建这两种类型的函数。下面首先来看一个有返回值的库函数,然后介绍如何编写简单的函数。
2.4.1 使用有返回值的函数
有返回值的函数将生成一个值,而这个值可赋给变量或在其他表达式中使用。例如,标准 C/C++库包含一个名为 sqrt( ) 的函数,它返回平方根。假设要计算 6.25 的平方根,并将这个值赋给变量 x,则可以在程序中使用下面的语句:
表达式 sqrt(6.25) 将调用 sqrt( ) 函数。表达式 sqrt(6.25) 被称为函数调用,被调用的函数叫做被调用函数(called function),包含函数调用的函数叫做调用函数(calling function,参见图 2.6)。
圆括号中的值(这里为 6.25)是发送给函数的信息,这被称为传递给函数。以这种方式发送给函数的值叫做参数。(参见图 2.7。)函数 sqrt( ) 得到的结果为 2.5,并将这个值发送给调用函数;发送回去的值叫做函数的返回值(return value)。可以这么认为,函数执行完毕后,语句中的函数调用部分将被替换为返回的值。因此,这个例子将返回值赋给变量 x。简而言之,参数是发送给函数的信息,返回值是从函数中发送回去的值。
图 2.6 调用函数
图 2.7 函数调用的句法
情况基本上就是这样,只是在使用函数之前,C++编译器必须知道函数的参数类型和返回值类型。也就是说,函数是返回整数、字符、小数、有罪裁决还是别的什么东西?如果缺少这些信息,编译器将不知道如何解释返回值。C++提供这种信息的方式是使用函数原型语句。
注意:
C++程序应当为程序中使用的每个函数提供原型。
函数原型之于函数就像变量声明之于变量—指出涉及的类型。例如,C++库将 sqrt( ) 函数定义成将一个(可能)带小数部分的数字(如 6.25)作为参数,并返回一个相同类型的数字。有些语言将这种数字称为实数,但是 C++将这种类型称为 double(将在第 3 章介绍)。sqrt( ) 的函数原型像这样:
第一个 double 意味着 sqrt( ) 将返回一个 double 值。括号中的 double 意味着 sqrt( ) 需要一个 double 参数。因此该原型对 sqrt( ) 的描述和下面代码中使用的函数相同:
原型结尾的分号表明它是一条语句,这使得它是一个原型,而不是函数头。如果省略分号,编译器将把这行代码解释为一个函数头,并要求接着提供定义该函数的函数体。
在程序中使用 sqrt( ) 时,也必须提供原型。可以用两种方法来实现:
- 在源代码文件中输入函数原型;
- 包含头文件 cmath(老系统为 math.h),其中定义了原型。
第二种方法更好,因为头文件更有可能使原型正确。对于 C++库中的每个函数,都在一个或多个头文件中提供了其原型。请通过手册或在线帮助查看函数描述来确定应使用哪个头文件。例如,sqrt( ) 函数的说明将指出,应使用 cmath 头文件。(同样,可能必须使用老式的头文件 math.h,它可用于 C 和 C++程序中。)
不要混淆函数原型和函数定义。可以看出,原型只描述函数接口。也就是说,它描述的是发送给函数的信息和返回的信息。而定义中包含了函数的代码,如计算平方根的代码。C 和 C++将库函数的这两项特性(原型和定义)分开了。库文件中包含了函数的编译代码,而头文件中则包含了原型。
应在首次使用函数之前提供其原型。通常的做法是把原型放到 main( ) 函数定义的前面。程序清单 2.4 演示了库函数 sqrt( ) 的用法,它通过包含 cmath 文件来提供该函数的原型:
程序清单 2.4 sqrt.cpp
注意:
如果使用的是老式编译器,则必须在程序清单 2.4 中使用#include <math.h>,而不是#include<cmath>。
使用库函数
C++库函数存储在库文件中。编译器编译程序时,它必须在库文件搜索您使用的函数。至于自动搜索哪些库文件,将因编译器而异。如果运行程序清单 2.4 时,将得到一条消息,指出_sqrt 是一个没有定义的外部函数(似乎应当避免),则很可能是由于编译器不能自动搜索数学库(编译器倾向于给函数名添加下划线前缀—提示它们对程序具有最后的发言权)。如果在 UNIX 实现中遇到这样的消息,可能需要在命令行结尾使用-lm 选项:
在 Linux 系统中,有些版本的 Gnu 编译器与此类似:
只包含 cmath 头文件可以提供原型,但不一定会导致编译器搜索正确的库文件。
下面是该程序的运行情况:
由于 sqrt( ) 处理的是 double 值,因此这里将变量声明为这种类型。声明 double 变量的句法与声明 int 变量相同:
double 类型使得变量 area 和 side 能够存储带小数的值,如 1 536.0 和 39.191 8。将看起来是整数(如 1536)的值赋给 double 变量时,将以实数形式存储它,其中的小数部分为.0。在第 3 章将指出,double 类型覆盖的范围要比 int 类型大得多。
C++允许在程序的任何地方声明新变量,因此 sqrt.cpp 在要使用 side 时才声明它。C++还允许在创建变量时对它进行赋值,因此也可以这样做:
这个过程叫做初始化(initialization),将在第 3 章更详细地介绍。
cin 知道如何将输入流中的信息转换为 double 类型,cout 知道如何将 double 类型插入到输出流中。前面讲过,这些对象都很智能化。
2.4.2 函数变体
有些函数需要多项信息。这些函数使用多个参数,参数间用逗号分开。例如,数学函数 pow( ) 接受两个参数,返回值为以第一个参数为底,第二个参数为指数的幂。该函数的原型如下:
要计算 5 的 8 次方,可以这样使用该函数:
另外一些函数不接受任何参数。例如,有一个 C 库(与 cstdlib 或 stdlib.h 头文件相关的库)包含一个 rand( ) 函数,该函数不接受任何参数,并返回一个随机整数。该函数的原型如下:
关键字 void 明确指出,该函数不接受任何参数。如果省略 void,让括号为空,则 C++将其解释为一个不接受任何参数的隐式声明。可以这样使用该函数:
注意,与其他一些计算机语言不同,在 C++中,函数调用中必须包括括号,即使没有参数。
还有一些函数没有返回值。例如,假设编写了一个函数,它按美元、美分格式显示数字。当向它传递参数 23.5 时,它将在屏幕上显示$23.50。由于这个函数把值发送给屏幕,而不是调用程序,因此不需要返回值。可以在原型中使用关键字 void 来指定返回类型,以指出函数没有返回值:
由于它不返回值,因此不能将该函数调用放在赋值语句或其他表达式中。相反,应使用一条纯粹的函数调用语句:
在有些语言中,有返回值的函数被称为函数(function);没有返回值的函数被称为过程(procedure)或子程序(subroutine)。但 C++与 C 一样,这两种变体都被称为函数。
2.4.3 用户定义的函数
标准 C 库提供了 140 多个预定义的函数。如果其中的函数能满足要求,则应使用它们。但用户经常需要编写自己的函数,尤其是在设计类的时候。无论如何,设计自己的函数很有意思,下面来介绍这一过程。前面已经使用过好几个用户定义的函数,它们都叫 main( )。每个 C++程序都必须有一个 main( ) 函数,用户必须对它进行定义。假设需要添加另一个用户定义的函数。和库函数一样,也可以通过函数名来调用用户定义的函数。对于库函数,在使用之前必须提供其原型,通常把原型放到 main( ) 定义之前。但现在您必须提供新函数的源代码。最简单的方法是,将代码放在 main( ) 的后面。程序清单 2.5 演示了这些元素。
程序清单 2.5 ourfunc.cpp
main( ) 函数两次调用 simon( ) 函数,一次的参数为 3,另一次的参数为变量 count。在这两次调用之间,用户输入一个整数,用来设置 count 的值。这个例子没有在 cout 提示消息中使用换行符。这样将导致用户输入与提示出现在同一行中。下面是运行情况:
1.函数格式
在程序清单 2.5 中,simon( ) 函数的定义与 main( ) 的定义采用的格式相同。首先,有一个函数头;然后是花括号中的函数体。可以把函数的格式统一为如下的情形:
注意,定义 simon( ) 的源代码位于 main( ) 的后面。和 C 一样(但不同于 Pascal),C++不允许将函数定义嵌套在另一个函数定义中。每个函数定义都是独立的,所有函数的创建都是平等的(参见图 2.8)。
2.函数头
在程序清单 2.5 中,simon( ) 函数的函数头如下:
开头的 void 表明 simon( ) 没有返回值,因此调用 simon( ) 不会生成可在 main( ) 中将其赋给变量的数字。因此,第一个函数调用方式如下:
由于 simon( ) 没有返回值,因此不能这样使用它:
图 2.8 函数定义在文件中依次出现
括号中的 int n 表明,使用 simon( ) 时,应提供一个 int 参数。n 是一个新的变量,函数调用时传递的值将被赋给它。因此,下面的函数调用将 3 赋给 simon( ) 函数头中定义的变量 n:
当函数体中的 cout 语句使用 n 时,将使用函数调用时传递的值。这就是为什么 simon(3)在输出中显示 3 的原因所在。在示例运行中,函数调用 simon(count) 导致函数显示 512,因为这正是赋给 count 的值。简而言之,simon( ) 的函数头表明,该函数接受一个 int 参数,不返回任何值。
下面复习一下 main( ) 的函数头:
开头的 int 表明,main( ) 返回一个整数值;空括号(其中可以包含 void)表明,main( ) 没有参数。对于有返回值的函数,应使用关键字 return 来提供返回值,并结束函数。这就是为什么要在 main( ) 结尾使用下述语句的原因:
这在逻辑上是一致的:main( ) 返回一个 int 值,而程序员要求它返回整数 0。但可能会产生疑问,将这个值返回到哪里了呢?毕竟,程序中没有哪个地方可以看出对 main( ) 的调用:
答案是,可以将计算机操作系统(如 UNIX 或 Windows)看作调用程序。因此,main( ) 的返回值并不是返回给程序的其他部分,而是返回给操作系统。很多操作系统都可以使用程序的返回值。例如,UNIX 外壳脚本和 Windows 命令行批处理文件都被设计成运行程序,并测试它们的返回值(通常叫做退出值)。通常的约定是,退出值为 0 则意味着程序运行成功,为非零则意味着存在问题。因此,如果 C++程序无法打开文件,可以将它设计为返回一个非零值。然后,便可以设计一个外壳脚本或批处理文件来运行该程序,如果该程序发出指示失败的消息,则采取其他措施。
关键字
关键字是计算机语言中的词汇。本章使用了 4 个 C++关键字:int、void、return 和 double。由于这些关键字都是 C++专用的,因此不能用作他用。也就是说,不能将 return 用作变量名,也不能把 double 用作函数名。不过可以把它们用作名称的一部分,如 painter(其中包含 int)或 return_aces。附录 B 提供了 C++关键字的完整列表。另外,main 不是关键字,由于它不是语言的组成部分。然而,它是一个必不可少的函数的名称。可以把 main 用作变量名(在一些很神秘的以致于无法在这里介绍的情况中,将 main 用作变量名会引发错误,由于它在任何情况下都是容易混淆的,因此最好不要这样做)。同样,其他函数名和对象名也都不能是关键字。然而,在程序中将同一个名称(比如 cout)用作对象名和变量名会把编译器搞糊涂。也就是说,在不使用 cout 对象进行输出的函数中,可以将 cout 用作变量名,但不能在同一个函数中同时将 cout 用作对象名和变量名。
2.4.4 用户定义的有返回值的函数
我们再深入一步,编写一个使用返回语句的函数。main( ) 函数已经揭示了有返回值的函数的格式:在函数头中指出返回类型,在函数体结尾处使用 return。可以用这种形式为在英国观光的人解决重量的问题。在英国,很多浴室都以英石(stone)为单位,不像美国以磅或公斤为单位。一英石等于 14 磅,程序清单 2.6 使用一个函数来完成这样的转换。
程序清单 2.6 convert.cpp
下面是该程序的运行情况:
在 main( ) 中,程序使用 cin 来给整型变量 stone 提供一个值。这个值被作为参数传递给 stonetolb( ) 函数,在该函数中,这个值被赋给变量 sts。然后,stonetolb( ) 用关键字 return 将 14*sts 返回给 main( )。这表明 return 后面并非一定得跟一个简单的数字。这里通过使用较为复杂的表达式,避免了创建一个新变量,将结果赋给该变量,然后将它返回。程序将计算表达式的值(这里为 210),并将其返回。如果返回表达式的值很麻烦,可以采取更复杂的方式:
这两个版本返回的结果相同,但第二个版本更容易理解和修改,因为它将计算和返回分开了。
通常,在可以使用一个简单常量的地方,都可以使用一个返回值类型与该常量相同的函数。例如,stonetolb( ) 返回一个 int 值,这意味着可以以下面的方式使用该函数:
在上述任何一种情况下,程序都将计算返回值,然后在语句中使用这个值。
这些例子表明,函数原型描述了函数接口,即函数如何与程序的其他部分交互。参数列表指出了何种信息将被传递给函数,函数类型指出了返回值的类型。程序员有时将函数比作一个由出入它们的信息所指定的黑盒子(black boxes)(电工用语)。函数原型将这种观点诠释得淋漓尽致(参见图 2.9)。
图 2.9 函数原型和作为黑盒的函数
函数 stonetolb( ) 短小、简单,但包含了全部的函数特性:
- 有函数头和函数体;
- 接受一个参数;
- 返回一个值;
- 需要一个原型。
可以把 stonetolb( ) 看作函数设计的标准格式。第 7 章和第 8 章将更详细地介绍函数。而本章的内容让读者能够很好地了解函数的工作方式及其如何与 C++匹配。
2.4.5 在多函数程序中使用 using 编译指令
在程序清单 2.5 中,两个函数中都包含下面一条 using 编译指令:
这是因为每个函数都使用了 cout,因此需要能够访问位于名称空间 std 中的 cout 定义。
在程序清单 2.5 中,可以采用另一种方法让两个函数都能够访问名称空间 std,即将编译指令放在函数的外面,且位于两个函数的前面:
当前通行的理念是,只让需要访问名称空间 std 的函数访问它是更好的选择。例如,在程序清单 2.6 中,只有 main( ) 函数使用 cout,因此没有必要让函数 stonetolb( ) 能够访问名称空间 std。因此编译指令 using 被放在函数 main( ) 中,使得只有该函数能够访问名称空间 std。
总之,让程序能够访问名称空间 std 的方法有多种,下面是其中的 4 种。
- 将 using namespace std;放在函数定义之前,让文件中所有的函数都能够使用名称空间 std 中所有的元素。
- 将 using namespace std;放在特定的函数定义中,让该函数能够使用名称空间 std 中的所有元素。
- 在特定的函数中使用类似 using std::cout;这样的编译指令,而不是 using namespace std;,让该函数能够使用指定的元素,如 cout。
- 完全不使用编译指令 using,而在需要使用名称空间 std 中的元素时,使用前缀 std::,如下所示:
命名约定
C++程序员给函数、类和变量命名时,可以有很多种选择。程序员对风格的观点五花八门,这些看法有时就像公共论坛上的圣战。就函数名称而言,程序员有以下选择:
Myfunction( ) myfunction( ) myFunction( ) my_function( ) my_funct( )
选择取决于开发团体、使用的技术或库以及程序员个人的品位和喜好。因此凡是符合第 3 章将介绍的 C++规则的风格都是正确的,都可以根据个人的判断而使用。
撇开语言是否允许不谈,个人的命名风格也是值得注意的—它有助于保持一致性和精确性。精确、让人一目了然的个人命名约定是良好的软件工程的标志,它在整个编程生涯中都会起到很好的作用。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论