4.3 函数的参数
4.3.1 函数参数的求值顺序
当一个函数带有多个参数时,C++语言没有规定在函数调用时实参的求值顺序。编译器根据对代码进行优化的需要自行规定对实参的求值顺序,有的编译器规定从左至右,有的编译器规定从右至左。这种对求值顺序的不同规定,对一般参数来说没有影响,但是如果实参表达式中带有副作用的运算符,就有可能由于求值顺序不同而产生二义性。
例4.7 由于使用对参数求值顺序不同的编译器而产生的二义性:
#include <iostream.h>
int add_int(int x, int y) {
return x + y;
}
void main() {
int x = 4, y = 6;
int z = add_int(++x, x + y);
cout << z << endl;
}
该程序中,调用表达式:
z = add_int(++x, x + y);
其中,实参是两个表达式 ++x
和 x + y
。如果编译器对实参求值的顺序是从左至右,则两个实参值分别为5和11。如果编译器对实参求值的顺序是从右至左,则两个实参值分别为5和10。由于实参值可能不同,调用 add_int()
函数后,返回值也不同,于是便产生了这个程序在不同编译器下输出不同结果的二义性。
克服这种二义性的方法是改变 add_int()
函数的两个实参的写法,尽量避免二义性的出现。在 main()
函数中可改写如下:
int x = 4, y = 6;
int t = ++x;
int z = add_int(t, x + y);
cout << z << endl;
这样,可以避免二义性的出现。对于函数 add_int()
的两个实参表达式,无论怎样的计算顺序,结果都是相同的。
4.3.2 设置函数参数的默认值
在函数声明或定义时可以给一个或多个参数指定默认值,但是要求在一个指定了默认值的参数的右边,不能出现没有指定默认值的参数。例如:
int add_int(int x, int y = 10);
在上述对函数 add_int()
的声明中,对该函数的最右边的一个参数指定了默认值。在函数调用时,编译器按从左至右的顺序将实参与形参结合。当实参的数目不足时,编译器将按同样的顺序使用声明或定义中的默认值来补足所缺少的实参。例如,下列函数调用表达式:
add_int(15)
与下列调用表达式:
add_int(15, 10)
是等价的。
在给某个参数指定默认值时,不仅可以是一个数值,而且还可以是一个表达式。
例4.8 设置默认的参数值的函数:
#include <iostream.h>
void fun(int a = 1, int b = 3, int c = 5) {
cout << "a=" << a << ", b=" << b << ", c=" << c << endl;
}
void main() {
fun();
fun(7);
fun(7, 9);
fun(7, 9, 11);
cout << "OK!" << endl;
}
执行该程序,输出如下结果:
a=1, b=3, c=5
a=7, b=3, c=5
a=7, b=9, c=5
a=7, b=9, c=11
OK!
该程序中在定义函数 fun()
时设置了参数的默认值,在调用函数时有多种情况,有的实参数目不足,有的实参数目与形参相等。这样可以根据不同情况说明默认值的使用。
下面再举一个将参数默认值设置在函数声明中的例子:
#include <iostream.h>
int m = 8;
int add_int(int x, int y = 7, int z = m);
void main() {
int a = 5, b = 15, c = 20;
int s = add_int(a, b);
cout << s << endl;
}
int add_int(int x, int y, int z) {
return x + y + z;
}
该程序中,在声明函数 add_int()
时,给函数参数设置了默认值,而其中一个参数的值被设置为一个已知变量(m
)的值。
通常,当一个函数有声明又有定义时,参数的默认值要在声明时设置。
4.3.3 使用数组作为函数参数
数组作为函数参数的传递可以分为如下三种情况,这三种情况的效果相同,只是调用机制不同:
1. 形参和实参都用数组
调用函数的实参用数组名,形参也用数组名。这种情况下,实参和形参共享内存中的同一个数组,因此在被调用函数中改变了形参数组的值,也会影响到调用函数中的数组。
例4.10 分析下列程序的输出结果:
#include <iostream.h>
int a[8] = {1, 3, 5, 7, 9, 11, 13, 0};
void fun(int b[], int n) {
for(int i = 0; i < n - 1; i++)
b[7] += b[i];
}
void main() {
int m = 8;
fun(a, m);
cout << a[7] << endl;
}
该程序中,定义了一个外部的一维数组 a
并给它的前7个元素赋了值。在定义的函数 fun()
的形参 b
是数组,没有给出数组大小。在被调用函数 fun()
中,改变了形参数组 b[7]
的值,在 main()
函数中,数组 a[7]
的值也会被改变,因为它们共用一个数组。
2. 形参和实参都用指向数组的指针
在C++语言中,数组名被规定为一个指向该数组首元素的指针,因为它的值是该数组首元素的地址值,因此数组名是一个常量指针。在实际应用中,形参和实参中一个用指针,另一个用数组也是可以的。在使用指针时可以用数组名,也可以用另外定义的指向数组元素的指针。
例4.11 分析下列程序的输出结果:
#include <iostream.h>
int a[8] = {1, 3, 5, 7, 9, 11, 13, 0};
void fun(int *pa, int n) {
for(int i = 0; i < n - 1; i++)
*(pa + 7) += *(pa + i);
}
void main() {
int m = 8;
fun(a, m);
cout << a[7] << endl;
}
该程序中,定义的函数 fun()
的形参 pa
是一个指向 int
型变量的指针,本程序让它指向数组 a
的首元素。因此,在 fun(a, m)
调用表达式中,a
是数组名,其值是数组 a
的首元素的地址。通过指针 pa
可以在被调用函数中改变数组中的元素。
请读者在本例程序中进行如下修改,再观察输出结果: 在 main()
中,增加一条声明语句:
int *p = a;
并将调用表达式 fun(a, m);
改为:
fun(p, m);
3. 实参用数组名,形参用引用名
为实现这种情况,可以将数组类型定义为一个类型别名,如下所示:
typedef int array[8];
然后,使用 array
来定义数组和引用。
例4.12 分析下列程序的输出结果:
#include <iostream.h>
typedef int array[8];
int a[8] = {1, 3, 5, 7, 9, 11, 13, 0};
void fun(array &b, int n) {
for(int i = 0; i < n - 1; i++)
b[7] += b[i];
}
void main() {
int m = 8;
fun(a, m);
cout << a[7] << endl;
}
程序在 fun()
函数中,使用了引用作为形参。调用时所对应的实参应该是一个数组名,这里的引用是给数组起个别名。在 fun()
函数中对数组 b
的操作,就相当于对被它引用的数组 a
的操作。在C++语言中,常用这种调用方式。