永州市网站建设_网站建设公司_Bootstrap_seo优化
2025/12/23 8:45:15 网站建设 项目流程

复习函数的基本知识

来复习一下介绍过的有关函数的知识。要使用C++函数,必须完成如下工作:

  • 提供函数定义;
  • 提供函数原型;
  • 调用函数。

库函数是已经定义和编译好的函数,同时可以使用标准库头文件提供其原型,因此只需正确地调用这
种函数即可。本书前面的示例已经多次这样做了。例如,标准C 库中有一个strlen( )函数,可用来确定字
符串的长度。相关的标准头文件cstring 包含了strlen( )和其他一些与字符串相关的函数的原型。这些预备
工作使程序员能够在程序中随意使用strlen( )函数。

然而,创建自己的函数时,必须自行处理这3 个方面—定义、提供原型和调用。程序清单7.1 用一
个简短的示例演示了这3 个步骤。

#include <iostream> void simple(); int main() { using namespace std; cout << "main() will call the simple() function:\n"; simple(); return 0; } void simple() { using namespace std; cout << "I'm but a simple function.\n"; }

下面是该程序的输出:

main() will call the simple() function: I'm but a simple function.

执行函数simple()时,将暂停执行main( )中的代码;等函数simple()执行完毕后,继续执行main()中的
代码。在每个函数定义中,都使用了一条using 编译指令,因为每个函数都使用了cout。另一种方法是,
在函数定义之前放置一条using 编译指令或在函数中使用std::cout。

下面详细介绍这3 个步骤。

定义函数

可以将函数分成两类:没有返回值的函数和有返回值的函数。没有返回值的函数被称为void 函数,其
通用格式如下:

void functionName(parameterList) { statements(s); return; //optional }

其中,parameterList 指定了传递给函数的参数类型和数量,本章后面将更详细地介绍该列表。可选的
返回语句标记了函数的结尾;否则,函数将在右花括号处结束。void 函数相当于Pascal 中的过程、FORTRAN
中的子程序和现代BASIC 中的子程序过程。通常,可以用void 函数来执行某种操作。例如,将Cheers!打
印指定次数(n)的函数如下:

void cheers(int n) //no return value { for(int i=0;i<n;i++) std::cout<<"Cheers!"; std::cout<<std::endl; }

参数列表int n 意味着调用函数cheers( )时,应将一个int 值作为参数传递给它。
有返回值的函数将生成一个值,并将它返回给调用函数。换句话来说,如果函数返回9.0 的平方根(sqrt
(9.0)),则该函数调用的值为3.0。这种函数的类型被声明为返回值的类型,其通用格式如下:

typeName functionName(parameterList) { statements; return value; //value is type cast to type typeName }

对于有返回值的函数,必须使用返回语句,以便将值返回给调用函数。值本身可以是常量、变量,也
可以是表达式,只是其结果的类型必须为typeName 类型或可以被转换为typeName(例如,如果声明的返
回类型为double,而函数返回一个int 表达式,则该int 值将被强制转换为double 类型)。然后,函数将最
终的值返回给调用函数。C++对于返回值的类型有一定的限制:不能是数组,但可以是其他任何类型—整
数、浮点数、指针,甚至可以是结构和对象!(有趣的是,虽然C++函数不能直接返回数组,但可以将数
组作为结构或对象组成部分来返回。)

作为一名程序员,并不需要知道函数是如何返回值的,但是对这个问题有所了解将有助于澄清概念。(另
外,还有助于与朋友和家人交换意见。)通常,函数通过将返回值复制到指定的CPU 寄存器或内存单元中来
将其返回。随后,调用程序将查看该内存单元。返回函数和调用函数必须就该内存单元中存储的数据的类型
达成一致。函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据(参见
图7.1)。在原型中提供与定义中相同的信息似乎有些多余,但这样做确实有道理。要让信差从办公室的办公
桌上取走一些物品,则向信差和办公室中的同事交代自己的意图,将提高信差顺利完成这项工作的概率。

函数在执行返回语句后结束。如果函数包含多条返回语句(例如,它们位于不同的if else 选项中),则
函数在执行遇到的第一条返回语句后结束。例如,在下面的例子中,else 并不是必需的,但可帮助马虎的
读者理解程序员的意图:

int bigger(int a,int b) { if(a>b) return a; //if a>b ,function terminates here else return b; //oterwise,function terminates here }

如果函数包含多条返回语句,通常认为它会令人迷惑,有些编译器将针对这一点发出警告。然而,这
里的代码很简单,很容易理解。

有返回值的函数与Pascal、FORTRAN 和BASIC 中的函数相似,它们向调用程序返回一个值,然后调
用程序可以将其赋给变量、显示或将其用于别的用途。下面是一个简单的例子,函数返回double 值的立方:

double cube(double x) //x times x times x { return x*x*x; //a type double value }

例如,函数调用cube(1, 2)将返回1.728。请注意,上述返回语句使用了一个表达式,函数将计算该表
达式的值(这里为1.728),并将其返回。

函数原型和函数调用

至此,读者已熟悉了函数调用,但对函数原型可能不太熟悉,因为它经常隐藏在include 文件中。程序
清单7.2 在一个程序中使用了函数cheer( )和cube( )。请留意其中的函数原型。

#include <iostream> void cheers(int); //prototype :no return value double cube(double x); //prototype:returns a double int main() { using namespace std; cheers(5); //function call cout << "Give me a number:"; double side; cin >> side; double volume = cube(side); //function call cout<< "A " << side << " -foot cube has a volume of " << volume << " cubic feet.\n"; cheers(cube(2)); //prototype protection at work return 0; } void cheers(int n) { using namespace std; for (int i = 0; i < n; i++) cout << "Cheers! "; cout << endl; } double cube(double x) { return x * x * x; }

运行结果

Cheers! Cheers! Cheers! Cheers! Cheers! Give me a number:5 A 5 -foot cube has a volume of 125 cubic feet. Cheers! Cheers! Cheers! Cheers! Cheers! Cheers! Cheers! Cheers!

main( )使用函数名和参数(后面跟一个分号)来调用void 类型的函数:cheers(5);,这是一个函数调
用语句。但由于cube( )有返回值,因此main( )可以将其用在赋值语句中:

double volume=cube(side);

但正如前面指出的,读者应将重点放在原型上。那么,应了解有关原型的哪些内容呢?首先,需要知
道C++要求提供原型的原因。其次,由于C++要求提供原型,因此还应知道正确的语法。最后,应当感谢
原型所做的一切。下面依次介绍这几点,将程序清单7.2 作为讨论的基础。

为什么需要原型

原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型(如果有的话)以及参数的类型
和数量告诉编译器。例如,请看原型将如何影响程序清单7.2 中下述函数调用:

double volume=cube(side);

首先,原型告诉编译器,cube( )有一个double 参数。如果程序没有提供这样的参数,原型将让编译器
能够捕获这种错误。其次,cube( )函数完成计算后,将把返回值放置在指定的位置—可能是CPU 寄存器,
也可能是内存中。然后调用函数(这里为main( ))将从这个位置取得返回值。由于原型指出了cube( )的类
型为double,因此编译器知道应检索多少个字节以及如何解释它们。如果没有这些信息,编译器将只能进
行猜测,而编译器是不会这样做的。

读者可能还会问,为何编译器需要原型,难道它就不能在文件中进一步查找,以了解函数是如何定义
的吗?这种方法的一个问题是效率不高。编译器在搜索文件的剩余部分时将必须停止对main( )的编译。一
个更严重的问题是,函数甚至可能并不在文件中。C++允许将一个程序放在多个文件中,单独编译这些文
件,然后再将它们组合起来。在这种情况下,编译器在编译main( )时,可能无权访问函数代码。如果函数
位于库中,情况也将如此。避免使用函数原型的唯一方法是,在首次使用函数之前定义它,但这并不总是
可行的。另外,C++的编程风格是将main( )放在最前面,因为它通常提供了程序的整体结构。

原型的语法

函数原型是一条语句,因此必须以分号结束。获得原型最简单的方法是,复制函数定义中的函数头,
并添加分号。对于cube( ),程序清单7.2 中的程序正是这样做的:

double cube(double x); //add ;to header to get prototype

然而,函数原型不要求提供变量名,有类型列表就足够了。对于cheer( )的原型,该程序只提供了参数
类型:

void cheers(int); //okay to drop variable names in prototype

通常,在原型的参数列表中,可以包括变量名,也可以不包括。原型中的变量名相当于占位符,因此
不必与函数定义中的变量名相同。

原型的功能

正如您看到的,原型可以帮助编译器完成许多工作;但它对程序员有什么帮助呢?它们可以极大地降低程序出错的几率。具体来说,原型确保以下几点:

  • 编译器正确处理函数返回值;
  • 编译器检查使用的参数数目是否正确;
  • 编译器检查使用的参数类型是否正确。如果不正确,则转换为正确的类型(如果可能的话)。
    前面已经讨论了如何正确处理返回值。下面来看一看参数数目不对时将发生的情况。例如,假设进行
    了如下调用:
double z=cube();

如果没有函数原型,编译器将允许它通过。当函数被调用时,它将找到cube( )调用存放值的位置,并
使用这里的值。这正是ANSIC 从C++借鉴原型之前,C 语言的工作方式。由于对于ANSI C 来说,原型是
可选的,因此有些C 语言程序正是这样工作的。但在C++中,原型不是可选的,因此可以确保不会发生这
类错误。

接下来,假设提供了一个参数,但其类型不正确。在C 语言中,这将造成奇怪的错误。例如,如果函
数需要一个int 值(假设占16 位),而程序员传递了一个double 值(假设占64 位),则函数将只检查64 位
中的前16 位,并试图将它们解释为一个int 值。但C++自动将传递的值转换为原型中指定的类型,条件是
两者都是算术类型。例如,程序清单7.2 将能够应付下述语句中两次出现的类型不匹配的情况:

cheers(cube(2));

首先,程序将int 的值2 传递给cube( ),而后者期望的是double 类型。编译器注意到,cube( )原型指
定了一个double 类型参数,因此将2 转换为2.0—一个double 值。接下来,cube( )返回一个double 值(8.0),
这个值被用作cheer( )的参数。编译器将再一次检查原型,并发现cheer( )要求一个int 参数,因此它将返回
值转换为整数8。通常,原型自动将被传递的参数强制转换为期望的类型。(但第8 章将介绍的函数重载可
能导致二义性,因此不允许某些自动强制类型转换。)

自动类型转换并不能避免所有可能的错误。例如,如果将8.33E27 传递给期望一个int 值的函数,则这
样大的值将不能被正确转换为int 值。当较大的类型被自动转换为较小的类型时,有些编译器将发出警告,
指出这可能会丢失数据。

仅当有意义时,原型化才会导致类型转换。例如,原型不会将整数转换为结构或指针。
在编译阶段进行的原型化被称为静态类型检查(static type checking)。可以看出,静态类型检查可捕获
许多在运行阶段非常难以捕获的错误。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询