C++ 函数


通过使用函数(functions)我们可以把我们的程序以更模块化的形式组织起来,从而利用C++所能提供的所有结构化编程的潜力。

一个函数(function)是一个可以从程序其它地方调用执行的语句块。以下是它的格式:

type name ( argument1, argument2, ...) statement

这里:

下面看一下第一个函数的例子:

// function example
#include <iostream.h>
int addition (int a, int b)
{
int r;
r=a+b;
return (r);
}

int main ()
{
int z;
z = addition (5,3);
cout << "The result is " << z;
return 0;
}

输出结果:

The result is 8

记得在我们教程开始时说过:一个C++程序总是从main函数开始执行。 因此我们从那里开始。

我们可以看到 main 函数以定义一个整型变量z 开始。紧跟着我们看到调用addition 函数。我们可以看到函数调用的写法和上面函数定义本身十分相似:

参数有明显的对应关系。在main 函数中我们调用addition 函数,并传入两个数值: 5 和3 , 它们对应函数addition 中定义的参数int a 和int b。

当函数在main 中被调用时,程序执行的控制权从main转移到函数addition。调用传递的两个参数的数值 (5 和3) 被复制到函数的本地变量(local variables) int a 和int b 中。

函数addition 中定义了新的变量(int r;),通过表达式r=a+b;, 它把a 加b 的结果赋给r 。因为传过来的参数a 和b 的值分别为5 和3 ,所以结果是8。

下面一行代码:

return (r);

结束函数addition,并把控制权交还给调用它的函数(main) ,从调用addition的地方开始继续向下执行。另外,return 在调用的时候后面跟着变量r (return (r);), 它当时的值为8, 这个值被称为函数的返回值。

函数返回的数值就是函数的计算结果,因此, z 将存储函数addition (5, 3)返回的数值, 即8。用另一种方式解释,你也可以想象成调用函数(addition (5,3)) 被替换成了它的返回值 (8)。

接下来main中的下一行代码是:

cout << "The result is " << z;

它把结果打印在屏幕上。

变量的范围(Scope of variables)

你必须考虑到变量的范围只是在定义该变量的函数或指令块内有效,而不能在它的函数或指令块之外使用。 例如,在上面的例子里就不可能在main 中直接使用变量a, b 或 r ,因为它们是函数addition的本地变量(local variable)。在函数addition中也不可能直接使用变量z,因为它是main的本地变量。

因此,本地变量 (local variables)的范围是局限于声明它的嵌套范围之内的。尽管如此,你还可以定义全局变量(global variables),它们可以在代码的任何位置被访问,不管在函数以内还是以外。要定义全局变量,你必须在所有函数或代码块之外定义它们,也就是说,直接在程序体中声明它们。

这里是另一个关于函数的例子:

// function example
#include <iostream.h>
int subtraction (int a, int b)
{
int r;
r=a-b;
return (r);
}

int main ()
{
int x=5, y=3, z;
z = subtraction (7,2);
cout << "The first result is " << z << '\n';
cout << "The second result is " << subtraction (7,2) << '\n';
cout << "The third result is " << subtraction (x,y) << '\n';
z= 4 + subtraction (x,y);
cout << "The fourth result is " << z << '\n';
return 0;
}

输出结果:

The first result is 5
The second result is 5
The third result is 2
The fourth result is 6

在这个例子中,我们定义了函数subtraction。这个函数的功能是计算传入的两个参数的差值并将结果返回。

在 main 函数中,函数subtraction被调用了多次。我们用了几种不同的调用方法,因此你可以看到在不同的情况下函数如何被调用。

为了更好的理解这些例子,你需要考虑到被调用的函数其实完全可以由它所返回的值来代替。例如在上面例子中第一种情况下 (这种调用你应该已经知道了,因为我们在前面的例子中已经用过这种形式的调用):

z = subtraction (7,2);
cout << "The first result is " << z;

如果我们把函数调用用它的结果(也就是5)替换,我们将得到:

z = 5;
cout << "The first result is " << z;

同样的

cout << "The second result is " << subtraction (7,2);

与前面的调用有同样的结果,但在这里我们把对函数subtraction 的调用直接用作cout的参数。这可以简单想象成我们写的是:

cout << "The second result is " << 5;

因为5 是subtraction (7,2)的结果。

cout << "The third result is " << subtraction (x,y);

中,与前面的调用唯一的不同之处是这里调用subtraction 时的参数使用的是变量而不是常量。这样用时毫无问题的。在这个例子里,传入函数subtraction 的参数值是变量x 和y中存储的数值,即分别为5 和3,结果为2。

第四种调用也是一样的。只要知道除了

z = 4 + subtraction (x,y);

我们也可以写成:

z = subtraction (x,y) + 4;

它们的结果是完全一样的。注意在整个表达式的结尾写上分号semicolon sign (;)。它并不需要总是跟在函数调用的后面,因为你可以有一次把它们想象成函数被它的结果所替代:

z = 4 + 2;
z = 2 + 4;


没有返回值类型的函数,使用void.

如果你记得函数声明的格式:

type name ( argument1, argument2 ...) statement

就会知道函数声明必须以一个数据类型(type)开头,它是函数由return 语句所返回数据类型。但是如果我们并不打算返回任何数据那该怎么办呢?

假设我们要写一个函数,它的功能是打印在屏幕上打印一些信息。我们不需要它返回任何值,而且我们也不需要它接受任何参数。C语言为这些情况设计了void 类型。让我们看一下下面的例子:

// void 函数示例
#include <iostream>
using namespace std;

void printmessage ()
{
cout << "I'm a function!";
}

int main ()
{
printmessage ();
return 0;
}

输出结果:

I'm a function!

void还可以被用在函数参数位置,表示我们明确希望这个函数在被调用时需要任何参数。例如上面的函数printmessage也可以写为以下形式:

void printmessage (void)
{
cout << "I'm a function!";
}

虽然在C++ 中void可以被省略,我们还是建议写出void,以便明确指出函数不需要参数。

你必须时刻知道的是调用一个函数时要写出它的名字并把参数写在后面的括号内。但如果函数不需要参数,后面的括号并不能省略。因此调用函数 printmessage 的格式是

printmessage();

函数名称后面的括号就明确表示了它是一个函数调用,而不是一个变量名称或其它什么语句。以下调用函数的方式就不对:

printmessage;


参数按数值传递和按地址传递(Arguments passed by value and by reference)

到目前为止,我们看到的所有函数中,传递到函数中的参数全部是按数值传递的(by value)。也就是说,当我们调用一个带有参数的函数时,我们传递到函数中的是变量的数值而不是变量本身。 例如,假设我们用下面的代码调用我们的第一个函数addition :

int x=5, y=3, z;
z = addition ( x , y );

在这个例子里我们调用函数addition 同时将xy的值传给它,即分别为53,而不是两个变量:

这样,当函数addition被调用时,它的变量ab的值分别变为53,但在函数addition内对变量a 或b 所做的任何修改不会影响变量他外面的变量x 和 y 的值,因为变量xy并没有把它们自己传递给函数,而只是传递了他们的数值。

但在某些情况下你可能需要在一个函数内控制一个函数以外的变量。要实现这种操作,我们必须使用按地址传递的参数(arguments passed by reference),就象下面例子中的函数duplicate

// passing parameters by reference
#include <iostream.h>

void duplicate (int& a, int& b, int& c)
{
a*=2;
b*=2;
c*=2;
}

int main ()
{
int x=1, y=3, z=7;
duplicate (x, y, z);
cout << "x=" << x << ", y=" << y << ", z=" << z;
return 0;
}

输出结果:

x=2, y=6, z=14

第一个应该注意的事项是在函数duplicate的声明(declaration)中,每一个变量的类型后面跟了一个地址符ampersand sign (&),它的作用是指明变量是按地址传递的(by reference),而不是像通常一样按数值传递的(by value)。

当按地址传递(pass by reference)一个变量的时候,我们是在传递这个变量本身,我们在函数中对变量所做的任何修改将会影响到函数外面被传递的变量。

用另一种方式来说,我们已经把变量a, b,c和调用函数时使用的参数(x, yz)联系起来了,因此如果我们在函数内对a 进行操作,函数外面的x 值也会改变。同样,任何对b 的改变也会影响y,对c 的改变也会影响z>

这就是为什么上面的程序中,主程序main中的三个变量x, yz在调用函数duplicate 后打印结果显示他们的值增加了一倍。

如果在声明下面的函数:

void duplicate (int& a, int& b, int& c)

时,我们是按这样声明的:

void duplicate (int a, int b, int c)

也就是不写地址符 ampersand (&),我们也就没有将参数的地址传递给函数,而是传递了它们的值,因此,屏幕上显示的输出结果x, y ,z 的值将不会改变,仍是1,3,7

这种用地址符 ampersand (&)来声明按地址"by reference"传递参数的方式只是在C++中适用。在C 语言中,我们必须用指针(pointers)来做相同的操作。

按地址传递(Passing by reference)是一个使函数返回多个值的有效方法。例如,下面是一个函数,它可以返回第一个输入参数的前一个和后一个数值。

// more than one returning value
#include <iostream.h>
void prevnext (int x, int& prev, int& next)
{
prev = x-1;
next = x+1;
}

int main ()
{
int x=100, y, z;
prevnext (x, y, z);
cout << "Previous=" << y << ", Next=" << z;
return 0;
}

输出结果:

Previous=99, Next=101


参数的默认值(Default values in arguments)

当声明一个函数的时候我们可以给每一个参数指定一个默认值。如果当函数被调用时没有给出该参数的值,那么这个默认值将被使用。指定参数默认值只需要在函数声明时把一个数值赋给参数。如果函数被调用时没有数值传递给该参数,那么默认值将被使用。但如果有指定的数值传递给参数,那么默认值将被指定的数值取代。例如:

// default values in functions
#include <iostream.h>
int divide (int a, int b=2) {
int r;
r=a/b;
return (r);
}

int main () {
cout << divide (12);
cout << endl;
cout << divide (20,4);
return 0;
}

输出结果:

6
5

我们可以看到在程序中有两次调用函数divide。第一次调用:

divide (12)

只有一个参数被指明,但函数divide允许有两个参数。因此函数divide 假设第二个参数的值为2,因为我们已经定义了它为该参数缺省的默认值(注意函数声明中的int b=2)。因此这次函数调用的结果是 6 (12/2)。

在第二次调用中:

divide (20,4)

这里有两个参数,所以默认值 (int b=2) 被传入的参数值4所取代,使得最后结果为 5 (20/4).


函数重载(Overloaded functions)

两个不同的函数可以用同样的名字,只要它们的参量(arguments)的原型(prototype)不同,也就是说你可以把同一个名字给多个函数,如果它们用不同数量的参数,或不同类型的参数。例如:

// overloaded function
#include <iostream.h>

int divide (int a, int b) {
return (a/b);
}

float divide (float a, float b) {
return (a/b);
}

int main () {
int x=5,y=2;
float n=5.0,m=2.0;
cout << divide (x,y);
cout << "\n";
cout << divide (n,m);
cout << "\n";
return 0;
}

输出结果:

2
2.5

在这个例子里,我们用同一个名字定义了两个不同函数,当它们其中一个接受两个整型(int)参数,另一个则接受两个浮点型(float)参数。编译器 (compiler)通过检查传入的参数的类型来确定是哪一个函数被调用。如果调用传入的是两个整数参数,那么是原型定义中有两个整型(int)参量的函数被调用,如果传入的是两个浮点数,那么是原型定义中有两个浮点型(float)参量的函数被调用。

为了简单起见,这里我们用的两个函数的代码相同,但这并不是必须的。你可以让两个函数用同一个名字同时完成完全不同的操作。


Inline 函数(inline functions)

inline 指令可以被放在函数声明之前,要求该函数必须在被调用的地方以代码形式被编译。这相当于一个宏定义(macro)。它的好处只对短小的函数有效,这种情况下因为避免了调用函数的一些常规操作的时间(overhead),如参数堆栈操作的时间,所以编译结果的运行代码会更快一些。

它的声明形式是:

inline type name ( arguments ... ) { instructions ... }

它的调用和其他的函数调用一样。调用函数的时候并不需要写关键字inline ,只有在函数声明前需要写。


递归(Recursivity)

递归(recursivity)指函数将被自己调用的特点。它对排序(sorting)和阶乘(factorial)运算很有用。例如要获得一个数字n的阶乘,它的数学公式是:

n! = n * (n-1) * (n-2) * (n-3) ... * 1

更具体一些,5! (factorial of 5) 是:

5! = 5 * 4 * 3 * 2 * 1 = 120

而用一个递归函数来实现这个运算将如以下代码:

// factorial calculator
#include <iostream.h>

long factorial (long a){
if (a > 1) return (a * factorial (a-1));
else return (1);
}

int main () {
long l;
cout << "Type a number: ";
cin >> l;
cout << "!" << l << " = " << factorial (l);
return 0;
}

输出结果:

Type a number: 9
!9 = 362880

注意我们在函数factorial中是怎样调用它自己的,但只是在参数值大于1的时候才做调用,因为否则函数会进入死循环(an infinite recursive loop),当参数到达0的时候,函数不继续用负数乘下去(最终可能导致运行时的堆栈溢出错误(stack overflow error)。

这个函数有一定的局限性,为简单起见,函数设计中使用的数据类型为长整型(long)。在实际的标准系统中,长整型long无法存储12!以上的阶乘值。


函数的声明(Declaring functions)

到目前为止,我们定义的所有函数都是在它们第一次被调用(通常是在main中)之前,而把main 函数放在最后。如果重复以上几个例子,但把main 函数放在其它被它调用的函数之前,你就会遇到编译错误。原因是在调用一个函数之前,函数必须已经被定义了,就像我们前面例子中所做的。

但实际上还有一种方法来避免在main 或其它函数之前写出所有被他们调用的函数的代码,那就是在使用前先声明函数的原型定义。声明函数就是对函数在的完整定义之前做一个短小重要的声明,以便让编译器知道函数的参数和返回值类型。

它的形式是:

type name ( argument_type1, argument_type2, ...);

它与一个函数的头定义(header definition)一样,除了:

例如:

// 声明函数原型
#include <iostream.h>

void odd (int a);
void even (int a);

int main () {
int i;
do {
cout << "Type a number: (0 to exit)";
cin >> i;
odd (i);
} while (i!=0);
return 0;
}

void odd (int a) {
if ((a%2)!=0) cout << "Number is odd.\n";
else even (a);
}

void even (int a) {
if ((a%2)==0) cout << "Number is even.\n";
else odd (a);
}

输出结果:

Type a number (0 to exit): 9
Number is odd.
Type a number (0 to exit): 6
Number is even.
Type a number (0 to exit): 1030
Number is even.
Type a number (0 to exit): 0
Number is even.

这个例子的确不是很有效率,我相信现在你已经可以只用一半行数的代码来完成同样的功能。但这个例子显示了函数原型(prototyping functions)是怎样工作的。并且在这个具体的例子中,两个函数中至少有一个是必须定义原型的。

这里我们首先看到的是函数odd 和even的原型:

void odd (int a);
void even (int a);

这样使得这两个函数可以在它们被完整定义之前就被使用,例如在main中被调用,这样main就可以被放在逻辑上更合理的位置:即程序代码的开头部分。

尽管如此,这个程序需要至少一个函数原型定义的特殊原因是因为在odd 函数里需要调用even 函数,而在even 函数里也同样需要调用odd函数。如果两个函数任何一个都没被提前定义原型的话,就会出现编译错误,因为或者odd 在even 函数中是不可见的(因为它还没有被定义),或者even 函数在odd函数中是不可见的。

很多程序员建议给所有的函数定义原型。这也是我的建议,特别是在有很多函数或函数很长的情况下。把所有函数的原型定义放在一个地方,可以使我们在决定怎样调用这些函数的时候轻松一些,同时也有助于生成头文件。

粤ICP备11097351号-1