☛ 传值方式 ( Pass by value )

C++ 预设的参数传递方法是传值 ( Pass by value ),也就是在函式呼叫时,将实际参数的值 copy 给形式参数,如此可保证原实际参数的内容不会被函式所更动:

int funct(int a, int b)
{
    a=100;
    b=200;
    return 0;
}

void main()
{
     int RetVal;
     int i=5,j=6;

     RetVal=funct(i, j);      //将 i,j 的值 copy 到 funct() 中的 a,b
     cout<< i << j;           //i 仍为 5, j 仍为 6
     ....
}

由于形式参数所占的空间是在呼叫函式时由堆叠中配置的,所以在函式结束后,这些形式参数便不复存在了。当下次再呼叫函式时,也许所配置到的又是另一块堆叠中的空间。

☛ 传址方式 ( Pass by address )

除了阵列外,其他型别均可作为传值的参数。当参数的 size 很大时,传值的方式无论在 copy 参数的时间或堆叠的占用上都会造成相当大的负担,这时候就应该以传址方式 ( Pass by address ) 来传递参数:

struct St
{
       char a[200];
       int  i[200];
}
int funct(St *);

void main()
{
     St s;
     ......
     funct(&s);        //将结构体 s 的位址以传值方式传入
     ....
}

funct(St *st)          //st 须宣告为指位器型别
{
     st->a[0]='a';     //取用结构体内的成员
     ....
}

事实上,传址方式就是传值方式的一种,只不过所传的是资料的位址而非内容;然而,使用传址方式却可以在函式中经由指位算符 ( * ) 而更改到实际参数的内容。在许多状况下会需要这样的功能:

void swap(int *, int *);
void main()
{
     int i=1,j=5;
     swap(&i, &j);          //互换内容
     cout << i <<", "<< j;  //印出 5, 1
}

void swap(int *a, int *b)
{
     int t;
     t=*a;                  //经由 *a, *b 来更改 i, j 的内容
     *a=*b;
     *b=t;
}

如果使用传址呼叫,但又不希望实际参数的内容在函式执行期间遭到有意或无意的更改,则可在参数型别的前面加上 const:

funct(const St *st);

则在 funct 中,任何企图更改 st 所指变数的内容之动作均会造成 Compile-Time error。C++ 最大的好处之一,就是会尽量为我们找出各种可能潜伏的 Bug,以增加程式的强固性。

函式的传回值也可以是一个地址,这原理就和参数的传址呼叫是一样的。一个常用的函式库字串拷贝函式 strcpy() 就是最好的例子,其在 string.h 中的宣告如下:

char *strcpy(char *dest, const char *src);

这个函式会把 src 字串的内容 copy 到 dest 字元阵列中,最后并传回 dest 字串的位址,在第二个参数前加上 const 表示 src 的值不可被 strcpy() 所更改。下面是使用范例:

#include <iostream.h>
#include <string.h>

void main()
{
     char d[40], *s="Flag Publishing Corp.";
     cout << strcpy(d, s);
}

执行结果:

Flag Publishing Corp.

虽然也可以用 cout << d; 来印出结果,但经由传回值的方式则可使程式较为简洁,同时也容易串接于运算式中:

cout << strcpy(d, strcpy(e, s));   //s → e → d → cout

☛ 传参考方式 ( Pass by reference )

传址方式虽然好用,但在很多状况下使用起来却很不自然,每次呼叫时必须用 & 来取址,在函式中又得用 * 来依址取值,所以很容易因为疏忽而造成 bug。

至于传参考的方式 ( Pass by reference ) 则可以让实际参数和形式参数成为同一个变数,只不过所使用的名称和地点不同而已。也就是说,形式参数会成为实际参数的别名:

void main()
{
     int i=2;
     .....
     funct(i);      //i 和 a 代表同一个变数
     .....
}

void funct(int& a)  //a 须宣告为参考型别
{
     a=5;
}

这样的用法就自然多了,以前面的 swap() 为例:

void swap(int&, int& );

void main()
{
     int i=1, j=5;
     swap(i,j);
     .....
}

void swap(int& a, int& b)  //a 是 i 的别名,b 是 j 的别名
{
     int t;
     t=a;                  //经由 a,b 来更改 i,j 的内容
     a=b;
     b=t;
}

当参数的 size 很大时,传参考也是一个很好的办法,同时我们也可以用 const 来防止资料无意间被更改:

class BigData
{
      ......
}
funct(const BigData& bd);

void main()
{
     BigData a;
     ....
     funct(a);
}

由于函式的传回值只能传回一个资料,当我们希望传回多个资料,或是资料的 size 很大时,则可用传址或传参考的方式来达成。这两种方式中又以传参考较为自然,但它有个缺点就是容易造成假象,使人误以为是传值呼叫 ( 因为它们在呼叫函式时的写法是一样的 ),结果参数的内容被更改了而不自知;而传址呼叫的参数由于必须以 & 取址后再传,所以不易造成这种困扰。

☛ 传递多维阵列的参数

阵列本身是不能作为参数来传递的,但是它的位址值却可以:

int a[20];
funct(a);       //亦可写成 funct(&a[0]);    ← 实际参数
....

funct(int *a)   //亦可写成 funct(int a[]);  ← 形式参数
{               //或      funct(int a[20]);
     ......
}

注意,即使在形式参数中指明了阵列的大小 ( 如 int a[20] ),C++ 还是不会为我们检查阵列范围的,所以一般都是自行设法来防范超过阵列范围的存取:

funct(int a[], int size)  //连阵列大小一起传入
{
     ......
}

至于多维阵列则比较麻烦一点,因为除了第一维度外,我们必须指明其余的每一个维度之大小:

funct(int a[][15]);     //或写成 funct(int (*a)[15])
{
      cout << a[1][2];  //印出自 a 算起第 1*15+2 个元素
      a++;              //a 往后移了 15 个整数
}

void main()
{
     int b[3][15];
     funct(b);
}

上例中,a 是一个指向二维阵列的指位器,当我们用 a 来对阵列做运算时,就必须知道该阵列第二维度的大小,如此才能让 a[?] 指到正确的阵列位置。