|
Chapter 8 函数,第二部分:引用,重载和默认参数
本章将继续探讨函数。这一章重点讨论C++中与函数相关的3个重要主题:引用、函数重载和默认参数。这3个特征极大地扩展了函数的功能。你将会看到,引用实际上是隐式指针。函数重载允许一个函数可以有两个或更多的实现,而每个实现可以执行独立的任务,函数重载也是C++支持多态的一种形式。默认参数是指在调用函数时,如果没有为函数的形式参数指定实际参数值,那么形式参数将使用预先设置的默认参数。
由于引用经常被用来声明函数的形式参数(这也是引用最主要的用途),本章将首先简单地讨论在调用函数时如何将参数传递给函数。
8.1 两种参数传递的方法
要理解引用的本质,你就必须首先理解参数传递的原理。通常,在计算机语言中可以通过两种方法将参数传递给子例程。第一种方法是值调用。这种方法是指将实际参数的值复制到子例程的形式参数中。因此,子程序对形式参数的改变将不会影响用来调用函数的实际参数。
引用调用是第二种传递参数的方法。这种方法将实际参数的地址而不是值复制到形式参数中。在子例程中,这个地址可以用来访问实际参数。这意味着改变形式参数的值将会影响用来调用子例程的实际参数。
8.1.1 C++如何传递参数
在C++中,默认情况下使用值调用方法来传递参数。这意味着,通常函数中的代码将不能改变用来调用函数的实际参数。到目前为止,本书中的所有程序使用的都是值调用方法。考虑下面这个程序:
#include <iostream>
using namespace std;
int sqr_it(int x);
int main()
{
int t=10;
cout << sqr_it(t) << ' ' << t;
return 0;
}
int sqr_it(int x)
{
x = x*x;
return x;
}
在上面的程序中,传递给函数sqr_it()的参数值是10,因此10就被复制到形式参数x中。当程序执行赋值运算x=x*x时,被修改的是局部变量x。而用来调用函数sqr_it()的实际参数t则仍然保持为10,它并没有被函数中的运算改变。因此,程序的输出将是100和10。
记住:在默认情况下,实际参数值的副本将被传递给函数。函数中所进行的运算将不会影响用来调用函数的实际参数。
8.1.2 用指针实现引用调用
尽管C++默认的参数传递约定是值调用,但我们可以将实际参数的地址(例如,指向实际参数的指针)传递给函数来手工实现引用调用。这样,函数中的代码就可以改变在函数外部的实际参数。在前面的章节中讨论指针传递时,你已经看到了类似的示例。当然,这需要将函数的形式参数声明为指针类型。
下面的函数swap()通过传递指针来手工实现引用调用,在该函数中交换了由实际参数指向的两个变量的值。
void swap(int *x, int *y)
{
int temp;
temp = *x; //保存在地址x上的值
*x = *y; // 将y地址中的值放入地址x中
*y = temp; //将x地址中的值放入地址y中
}
*x和*y分别是由指针x和y指向的变量,x和y是调用函数的实际参数的地址。接下来,该函数交换了调用函数的变量的值。
#include <iostream>
using namespace std;
//将函数swap()声明为使用指针作为参数
void swap(int *x, int *y);
int main()
{
int i, j;
i = 10;
j = 20;
cout << "Initial values of i and j: ";
cout << i << ' ' << j << '\n';
swap(&j, &i); //用i和j的地址调用swap()函数
cout << "Swapped values of i and j: ";
cout << i << ' ' << j << '\n';
return 0;
}
//交换实际参数中的值
void swap(int *x, int *y)
{
int temp;
temp = *x; //保存地址x中的值
*x = *y; // 将y地址中的值放入地址x中
*y = temp; //将x地址中的值放入地址y中
}
程序输出如下:
Initial values of i and j: 10 20
Swapped values of i and j: 20 10
在上面的程序中,变量i中的值是10,变量j中的值是20。然后用i和j的地址来调用函数swap()。一元运算符&被用来获得变量的地址。因此,传递给函数swap()的是i和j的地址,而不是i和j的值。当swap()返回时,变量i和j中的值将被交换。
由于函数swap()的形式参数被声明为两个指针,所以你必须记住在调用swap()时,传递给函数的参数是你想交换的变量的地址。下面的程序给出了正确使用函数swap()的方法:
8.2 引用参数
通过指针运算符可以手工实现引用调用,但这种方法很笨拙。首先,它强制你通过指针来完成所有的运算。其次,在调用函数的时候,它要求你记住传递给函数的是实际参数的地址,而不是实际参数的值。所幸的是,在C++中,可以告诉编译器对函数的一个或几个参数自动使用引用调用而不是值调用。你可以通过引用参数来实现这一点。当你使用引用参数时,实际参数的地址(而不是值)被自动传递给了函数。在函数中,对引用参数的操作将自动地被转换到实际参数上,因此也就没有必要使用指针运算符。
在函数的声明中,可以通过在参数名前加&将参数声明为一个引用参数。在引用参数上所进行的操作将会影响到调用函数的实际参数,而不是引用参数本身。
为了更好地理解引用参数,我们首先来看一个简单的示例。在下面的程序中,函数f()带有一个int类型的引用参数:
// 使用引用参数。
#include <iostream>
using namespace std;
void f(int &i);
int main()
{
int val = 1;
cout << "Old value for val: " << val << '\n';
f(val); //传递val的地址给函数f()
cout << "New value for val: " << val << '\n';
return 0;
}
void f(int &i)
{
i = 10; //这条语句将修改调用函数的实际参数的值
}
程序输出如下:
Old value for val: 1
New value for val: 10
要特别注意函数f()的定义,如下所示:
void f(int &i)
{
i = 10; //这条语句将修改调用函数的实际参数的值
}
注意形式参数i的声明: i的前面有一个&,这表示它是一个引用参数(这个声明同样也要用在函数的原型中)。在函数中,下面这条语句
i = 10;
并没有将值10赋给变量i。相反,这条语句将值10赋给了被i引用的变量(在本例中是val)。注意,这条语句并没有使用指针运算符*。当你使用引用参数时,C++编译器会自动知道它是一个地址(例如是一个指针)。实际上,使用*将会产生编译错误。
由于i被声明为一个引用参数,编译器将自动把调用函数的实际参数的地址传递给函数f()。因此,在main()中,下面的语句
f(val); //传递val的地址给函数f()
将传递变量val的地址(不是它的值)给函数f(),并且不需要在val前面加上&(这样做是错误的)。由于f()以引用的形式接收了val的地址,所以它可以用来修改val的值。
为了说明引用参数在实际中的应用与它带来的好处,下面的程序使用引用参数重新改写了函数swap(),注意在程序中swap()是如何被声明和调用的。
#include <iostream>
using namespace std;
//使用引用参数来声明函数swap()。
void swap(int &x, int &y);
int main()
{
int i, j;
i = 10;
j = 20;
cout << "Initial values of i and j: ";
cout << i << ' ' << j << '\n';
swap(j, i);
cout << "Swapped values of i and j: ";
cout << i << ' ' << j << '\n';
return 0;
}
/* 这里的 swap()函数被定义为使用引用调用而不是值调用。因此,函数可以交换用来调用函数的实际参数的值。*/
void swap(int &x, int &y)
{
int temp;
temp = x; //保存在地址x上的值
x = y; // 将y地址中的值放入地址x中
y = temp; //将x地址中的值放入地址y中
}
再次说明,通过将x和y作为引用参数,在交换值的时候就可以不使用运算符*。前面解释过,如果使用*将会产生一个错误。
记住,编译器将自动生成实际参数的地址来调用swap(),并自动指向x和y所引用的变量。
让我们来复习一下。当你创建一个引用参数时,参数将自动引用(也即隐式地指向)用来调用函数的实际参数。而且对实际参数不需要使用运算符&。同时,在函数中,引用参数可以被直接使用,不需要使用运算符*,使用*反而不正确。所有涉及引用参数的操作将自动作用于调用函数的实际参数。
记住:当你给引用参数赋值时,实际上是将这个值赋给了引用参数所指向的变量,而该变量也就是用来调用函数的实际参数。
|
|