第4章 函数和作用域
函数是编程的基本特征之一。它封装了一些程序代码和数据,实现了更高级的抽象。在C++语言编程中,函数把一个程序分成多个部分来实现数据处理和功能抽象,隐藏了具体实现的细节,使用户将精力集中在函数的功能上,从而增强程序的可读性和便于管理。因此,本章将讲述函数的定义和说明以及函数的调用等重要的基础内容。此外,本章还将介绍有关作用域的概念,讲述变量、对象和函数的作用域及生存期,并简单介绍函数模板的使用。
4.1 函数的定义和说明
在讲述函数的定义和说明之前,先举一个由一个程序中分离出函数的简单例子。
例4.1 求两个浮点数之和的程序
#include <iostream.h>
void main() {
double x, y;
cout << "Input double x and y:";
cin >> x >> y;
double z = x + y;
cout << "sum = " << z << endl;
}
我们可以将上面的程序改写成使用函数的形式如下:
#include <iostream.h>
double sum_double() {
double x, y;
cout << "Input double x and y:";
cin >> x >> y;
double s = x + y;
return s;
}
void main() {
double sum;
sum = sum_double();
cout << "sum = " << sum << endl;
}
这里使用了高级抽象函数 sum_double()
,而读者对这个函数的理解只需知道它是干什么的就够了,而不必去了解它是怎样实现的。因此,可以说标识符 sum_double
是用来对上述的若干条语句序列的抽象,这种抽象被称为函数,而标识符 sum_double
被称为函数名。有了函数这种抽象,就使得用户只关心一个函数是做什么的(即函数的功能),而不必去关心函数内部是如何操作的(即函数实现)。
下面进一步对上述程序中 sum_double()
函数的操作数进行抽象,即实现参数化。用下述程序实现:
#include <iostream.h>
double sum_double(double x, double y) {
return x + y;
}
void main() {
double a, b;
cout << "Input double a and b:";
cin >> a >> b;
double sum = sum_double(a, b);
cout << "sum = " << sum << endl;
}
该程序中,引入函数的目的在于求出两个浮点数的和。因此,函数被执行完成后要返回一个值,来更新主函数中 sum
变量的值。在C++语言中,有的函数也可以不返回值,这种函数定义时要被指定为 void
的类型。
4.1.1 函数的定义格式
前面的例子分析了使用函数的方法。下面给出在C++语言中定义函数的一般格式。
<类型> <函数名>(<参数表>) {
<若干条语句>
}
其中,<类型>
表示该函数的类型,即该函数返回值的类型。它可以是各种数据类型,包括基本数据类型和构造数据类型,也可以是指针和引用类型。如果该函数没有返回值,则其类型为 void
。
<函数名>
与标识符的规定相同,函数名也是一种标识符。函数名最好做到“见名知意”。例如,sum_double
表示浮点数求和的函数名。
<参数表>
由0个、1个或多个参数组成;多个参数之间用逗号分隔。每个参数包括参数名和参数类型。
以上部分称为函数头。用花括号括起来的 <若干条语句>
组成了函数体。无论多少条语句,花括号是不可省的。
例如,下面是一个求两个 int
型数之和的函数:
int sum_int(int x, int y) {
return x + y;
}
定义函数时,<参数表>
中的参数称为形式参数,简称形参。形参在该函数被调用时才被初始化。形参的使用将使被调用函数可以从调用函数那里获取数据。如果被调用函数不需要从调用函数那里获取数据,则该函数没有参数。
示例:空函数
空函数是一种什么都不执行的函数,也有其意义。例如,下面的 nothing()
就是一个空函数:
void nothing() {}
通过上述内容,我们可以了解到如何定义和使用函数以及函数在程序中的重要作用。下一节将继续讲述函数的调用和参数传递方式。
4.1.2 函数的说明方法
在C++语言中,函数的说明是指在函数调用之前对函数进行声明。这种声明方式可以确保在调用函数时,编译器能够识别出函数的定义。如果一个函数定义在前,调用在后,则调用前可以不必说明;但如果一个函数定义在后,调用在前,则调用前必须说明。
函数声明的重要性
函数的说明又称为函数的声明。在C++语言中,函数的说明原则如下:
- 如果被调用函数都在调用函数之前定义,则可以对函数不加说明。这种方法在程序中安排函数的顺序上会花费一些精力,特别是在复杂的调用关系中,需要仔细考虑函数的先后顺序,否则会发生错误。
- 为了避免确定函数定义的顺序,并且为了使程序设计的结构更加清晰,常常将主函数放在程序头,这样就需要对被调用的函数进行说明。
函数声明的格式
说明函数的方法如下:
<类型> <函数名>(<参数表>);
这种说明的格式与定义函数时的函数头相同。包括函数的类型、函数名、函数参数的个数和类型,其中函数参数名可说明,也可不说明。这种说明称为原型说明,它与C语言中的简单说明不同。
例4.2 分析程序输出结果
下面是一个使用原型说明的例子。
#include <iostream.h>
void fun1(), fun2(), fun3();
void main() {
cout << "It is in main." << endl;
fun2();
cout << "It is back in main." << endl;
}
void fun1() {
cout << "It's in fun1." << endl;
fun3();
cout << "It's back in fun1." << endl;
}
void fun2() {
cout << "It's in fun2." << endl;
fun1();
cout << "It's back in fun2." << endl;
}
void fun3() {
cout << "It's in fun3." << endl;
}
该例子主要说明:如果函数定义在后,调用在前,则调用前必须说明。因此,在程序头对要调用的3个函数用函数原型进行了说明,这样后面的函数定义顺序就不必在程序中严格安排。这是一种常用的方法。
无函数原型的函数定义顺序
请读者考虑一下,如果程序开头不用原型来说明任意函数,那么这些函数的定义顺序该如何安排呢?
在没有函数原型的情况下,函数的定义顺序必须严格按照调用顺序进行。例如,如果 fun2
调用 fun1
,则 fun1
必须在 fun2
之前定义。
函数嵌套调用
此例子还将在后面讲述的函数嵌套调用中用来说明调用的具体过程。在上述例子中,fun2
调用了 fun1
,fun1
又调用了 fun3
。通过这种嵌套调用,可以更好地理解函数之间的调用关系和程序的执行流程。