C++数组做函数参数详解

程序员通常会想要编写函数来处理数组中的数据。例如,可以编写函数将值放入数组中、在屏幕上显示数组的内容、累计数组的所有元素或计算其平均值。通常,这样的函数将接收一个数组作为参数。

当某个数组的单个元素被传递给一个函数时,它就像任何其他变量一样被处理。例如,下面程序显示了一个循环:
//This program demonstrates that an array element
//can be passed to a function like any other variable.
#include <iostream>
using namespace std;

void showValue(int); // Function prototype

int main()
{
    const int ARRAY_SIZE = 8;
    int collection[ARRAY_SIZE] = {5, 10, 15, 20, 25, 30, 35, 40};
    for (int index = 0; index < ARRAY_SIZE; index++)
        showValue(collection[index]);
    cout << endl;
    return 0;
}
void showValue(int num)
{
    cout << num << " ";
}
程序输出结果:

5 10 15 20 25 30 35 40

此程序中,每次执行循环时都会将 collection 数组的一个元素传递给 showValue 函数。因为 collection 数组的元素是 int 整数,所以每次调用时都会将一个 int 值传递给 showValue 函数。请注意这在 showValue 函数原型和函数头中是如何指定的。实际上,showValue 只知道它正在接收一个 int,至于数据是不是来源于一个数组则无关紧要。

由于 showValue 函数只是显示 num 的内容,并不需要直接处理数组元素本身,所以数组元素可以通过值传递给它。如果函数需要访问原始数组的元素,则它们将需要通过引用进行传递。

如果函数被编写为接收整个数组作为实参,则形参的设置将迥然不同。在下面的函数定义中,形参 num 之后是一组空的方括号。这表明实参将会是整个数组,而不是单个值。
void showValues (int nums[], int size)
{
    for (int index = 0; index < size; index++)
        cout << nums[index] << " ";
    cout << endl;
}
请注意,与包含值的数组一起,数组的大小也被传递给 showValues。这样就可以知道需要处理多少个值。

还要注意在 nums 的方括号内没有大小声明符,这是因为 nums 实际上不是一个数组,它只是一个接收数组地址的特殊变量。当整个数组传递给一个函数时,它不会被通过值传递。

想象一下,如果在每次传递给一个函数时都需要创建一个 10000 元素数组的副本,那该需要消耗掉多少 CPU 时间和内存!所以,被传递的只能是数组的起始内存地址。这与通过引用将变量传递给函数类似,只不过在这种情况下不使用 &。

下面程序演示了函数 showValues 如何接收整个数组的地址,这样它就可以访问和显示其所有元素的内容:
// This program shows how to pass an entire array to a function.
#include <iostream>
using namespace std;

void showValues(int intArray[], int size); // Function prototype
int main()
{
    const int ARRAY_SIZE = 8;
    int collection[ARRAY_SIZE] = {5, 10, 15, 20, 25, 30, 35, 40};
    cout << "The array contains the values\n";
    showValues(collection, ARRAY_SIZE);
    return 0;
}
void showValues (int nums[], int size)
{
    for (int index = 0; index < size; index++)
    cout << nums[index] << " ";
    cout << endl;
}
程序输出结果为:

The array contains the values
5 10 15 20 25 30 35 40

仔细观察第 5 行中的 showValues 原型和第 14 行中的函数头,在这两种情况下,都有一对方括号紧跟第一个参数名称,这让程序知道这个形参将接收一个数组的地址。如果函数原型没有使用形参名称,那么它看起来将像这样:

void showValues(int [],int);

这仍然表示第一个 showValues 形参将接收一个整数数组的地址,第二个形参则接收一个整数值。

再来看下面的语句,在程序的第 11 行如何调用 showValues 函数:

showValues(collection, ARRAY_SIZE);

第一个实参是传递给函数的数组的名称。请记住,在 C++ 中,没有方括号和下标的数组的名称实际上是数组的开始地址。在这个函数调用中,collection 数组的地址被传递给函数。第二个实参是该数组的大小。

在 showValues 函数中,collection 数组的开始地址被复制到 nums 形参变量中。nums 变量然后被用于引用 collection 数组。图 1 说明了 collection 数组和 nums 形参变量之间的关系。当显示 nums [0] 时,实际上屏幕中出现的是 collection [0] 的内容。


图 1 collection 数组和 nums 形参变量之间的关系

注意,虽然 nums 不是一个引用变量,但其作用与引用变量是一样的。

showValues 函数中的 nums 形参变量可以接收任何整数数组的地址,并可以使用它来引用该数组。所以,通过传递数组的名字和它的大小作为实参,即可使用 showValues 函数来显示任何整数数组的内容。

下面的程序使用了该函数来显示两个不同数组的内容。请注意,这两个函数并不一定需要是相同的大小。
// This program demonstrates passing different arrays to a function.
#include <iostream>
using namespace std;

// Declare arrayType to be an alias for an array of ints
typedef int arrayType[];

void showValues(arrayType, int); // Function prototype
int main()
{
    const int SIZE1 =8;
    const int SIZE2 = 5;
    int set1[] = {5, 10, 15, 20, 25, 30, 35, 40};
    int set2[] = {2, 4, 6, 8, 10};
    cout << "Here are the values stored in array set1: ";
    showValues(set1, SIZE1); // Pass set 1 to showValues
   
    cout << "Here are the values stored in array set2: ";
    showValues(set2, SIZE2); // Pass set 2 to showValues return 0;
}
void showValues (arrayType nums, int size)
{
    for (int index = 0; index < size; index++)
        cout << nums [index] << " ";
    cout << endl;
}
程序输出结果:

Here are the values stored in array set1: 5 10 15 20 25 30 35 40
Here are the values stored in array set2: 2 4 6 8 10

此程序中使用了 typedef 声明,它使名称 arrayType 成为一个整数数组的别名,然后在 showValues 原型和函数头中使用此名称,而不是使用 int[] 来指示第一个形参接收 int 数组的起始地址。

另外,程序的第 13 行和第 14 行声明 set1 和 set2 时,并没有使用大小声明符。这里当然也可以使用一个,但是前面已经介绍过,当使用初始化列表时,就可以不需要大小声明符。

当一个引用变量被用作形参时,它将使该函数能够访问原始实参。对引用变量所做的任何更改实际上都是在变量所引用的实参上执行的。数组形参的作用和引用变量非常相似。它们可以让函数直接访问原始数组。对数组形参所做的任何更改实际上都是作用于实参使用的原始数组。

下面程序中的函数 doubleArray 就使用了这个功能来使数组中每个元素的值翻倍:
// each element of an array.
#include <iostream>
using namespace std;

//Declare arrayType to be an alias for an array of ints
typedef int arrayType[];

//Function prototypes
void doubleArray(arrayType, int);
void showValues (arrayType, int);

int main()
{
    const int ARRAY_SIZE = 7;
    arrayType set = {1, 2, 3, 4, 5, 6, 7};
    //Display the original values
    cout << "The arrays values are:\n";
    showValues(set, ARRAY_SIZE);
    // Double the values in the array
    doubleArray(set, ARRAY_SIZE);
    // Display the new values
    cout << "\nAfter calling doubleArray, the values are: \n";
    showValues(set, ARRAY_SIZE);
    cout << endl;
    return 0;
}
void doubleArray(arrayType nums, int size)
{
    for (int index = 0; index < size; index++)
        nums[index] *= 2;
}
void showValues (arrayType nums, int size)
{
    for (int index = 0; index < size; index++)
        cout << nums [index] << " " ;
    cout << endl;
}
程序输出结果:

The arrays values are:
1 2 3 4 5 6 7

After calling doubleArray, the values are:
2 4 6 8 10 12 14

此程序中,set 数组被定义为 arrayType 类型,而不是 int set [] 或 int set[ARRAY_SIZE],尽管它也可以用这两种方法之一来定义。与前面的程序中一样,没有必要指定数组的大小,因为它在创建时使用了初始化列表进行初始化。

还要注意的是,在 typedef 声明、showValues 原型和 showValues 函数头都没有使用 &。记住,当需要将一个数组传递给一个函数时,不要使用 &。

在 C++ 中,当一个常规变量被传递给一个函数,而且有一个 & 符号在其名字前面时,这意味着该函数正在接收一个存储变量的内存地址的引用。但是,数组名称已经是内存地址。也就是说,它保存的不是一个值,而是保存数组在内存中的起始地址。因此,& 符号不应该和数组一起使用。

使用const数组形参

有时候程序员可能会想让函数能够修改作为实参传递给它的数组中的内容,但有些时候则不然。例如,上面程序中需要 doubleArray 函数来改变数组中的值,但并不希望 showValues 函数来改变它们。

要防止函数对传递给它的数组作出无意的修改,可以使用 const 关键字。以下是 showValues 原型和函数头在使用 const 数组形参之后的样子:

void showValues (const arrayType, int)    //函数原型
void showValues (const arrayType nums, int size) //函数头

使用 const 数组形参时,对函数的调用或函数代码中并没有任何变化,只有函数原型和函数头受到影响。当一个数组形参被声明为 const 时,不允许该函数修改数组的内容。如果函数中的语句尝试修改数组,则会在编译时发生错误。作为预防措施,在任何不打算修改其数组实参的函数中始终使用 const 数组形参不失为一个好主意。