传值参数
传值参数的特点及传值过程
先看下面的一个实例:
int result(int a,int b,int c){
return a+b+c+(a+b+c)/3;
}
在以上程序中,a,b和c都是函数result的形式参数(formal parameter),类型均为整型,如果用以下语句调用此函数:
result = result(5,y,z);
那么,在调用语句中,5,y和z分别是对应于a,b和c的实际参数(actual parameter),当调用语句被执行时,a被赋值为5,而b和c分别被赋值为y和z,如果y或者z不是整型时,先进性强制类型转换将其转为int类型,然后再将其赋值给b和c。
总结(重点)
在程序执行时,与函数形式参数对应的实际参数的值将在函数执行之前被复制给形式参数,这个过程是由该形式参数所属数据类型的复制构造函数(copy constructor)完成的。如果实际参数类型与形式参数不符合时,会先进行类型转换,再进行复制,如果转换失败,直接报错。
当函数运行结束时,形式参数所属数据类型的析构函数(destructor)负责释放该形式参数。当一个函数返回时,形式参数的值不会被复制到对应的实际参数中,因此函数调用不会修改实际参数的值。
模板函数
以上的函数只适用于int类型的数据,而我们希望其适用于float、double等类型时,需要再写如下的函数:
float result(float a,float b,float c){
return a+b+c+(a+b+c)/3;
}
可以看到,上面的函数只是形式参数以及函数返回值的数据类型不同,对于这种情况,就可以使用模板函数,编写一段通用的代码,将参数的数据类型作为一个变量,它的值由编译器来确定,具体的代码如下:
template<class T>
T result(T a,T b,T c){
return a+b+c+(a+b+c)/3;
}
然后,在调用result函数时,编译器会根据传入的参数类型来确定T的具体类型,如给a传入的是double类型,则T将被实例化为double类型。
引用参数
上面程序中形式参数的用法会增加程序的运行开销,这主要是由于在函数调用时,构造函数会复制传给形式参数相应的实际参数的值,在函数内部使用,然后在函数执行结束时调用析构函数销毁这些函数内部使用过的值,如果需要多次调用该函数或者该函数中拥有大量的参数传递时,自然会在调用析构函数和构造函数对参数的赋值和销毁方面造成巨大的开销。
而在下面的代码中:
template<class T>
T result(T& a,T& b,T& c){
return a+b+c+(a+b+c)/3;
}
a,b,c是引用参数(references parameter),如果用result(x,y,z)
来调用该函数,x,y,z的值不会被复制给a,b,c,而是直接用的x,y,z本来的值,也就是说,这种方法不会产生复制过程,会改变外部变量的实际的值。
常量引用参数
常量引用参数是指函数不得修改引用参数,使用关键字const来定义常量引用参数,具体的语法为:const 数据类型 参数名
,如const int &b
返回值
函数的返回值类型有具体的值、引用以及常量引用;上面的函数都是返回一个具体的值,在这种情况下,被返回的对象均被复制到调用(返回)环境中;因为函数所计算出的表达式的结果被存储在一个局部临时变量中,当函数返回时,这个临时变量(以及所有其他的临时变量和传值形式参数)所占用的空间将被释放,其值当然也不再有效。为了避免丢失这个值,在释放临时变量以及传值形式参数的空间之前,必须把这个值复制到调用该函数的环境中去。
如果需要返回一个引用,可以在函数返回类型中加“&”符号,语法为返回类型& 函数名(){}
;如
T& X(int n, T& s){
return s;
}
在上面的函数中,这种返回形式不会把s的值复制到返回环境中。当函数X返回时,传值形式参数i以及局部变量所占用的所有空间都将被释放,由于s是一个实际参数的引用,因此,他不会受影响。和常量引用相比,除了返回的结果是一个不变的对象之外,返回一个常量引用与返回一个引用是相同的。
int& add(int a,int &b){
return a+b;
}//这个函数会报错。因为需要返回一个引用,而a是传值形式参数
附: 参考《数据结构算法与应用——C++语言描述》