☛ 传值方式 ( 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[?] 指到正确的阵列位置。