因为一直对这几种函数参数的传递方式理解的不是很透彻,花了一段时间仔细捋清了他们之间的区别。这个问题也是编程初级阶段会经常遇到的问题,也是有可能在面试中遇到的基本问题,在此进行了简单的总结一下,一是加深自己的理解,二是希望帮助遇到同样问题的同学,希望能帮你们快速透彻的理解他们。
主要以实现交换两个整形值(老生常谈的话题了)为载体进行透彻的说明,我们的主要方法就是利用最基本的输入输出功能来看函数执行前和执行后,参数地址和值的变化来看函数“做了什么事情”。
针对每一个不同的函数,主要从展现了如下几个方面:(1)函数开始执行前的参数的状态,包括参数的地址和值(2)函数实现了什么样的操作(3)函数执行后的参数的状态。最后我们简单的总结了这5个函数为什么有的能实现目的;而有的不能实现目的,进一步解释了这些不能实现目的函数他们到底做了哪些事情(通过前后参数状态的变化来反映)。
在main函数中,我们首先显示出实参的地址,当将实参传入到函数中,我们显示被调函数中变量的地址。只要被调函数中变量地址和实参的地址一样,我们认为是直接操作变量而不是操作变量的”副本“;如果被调函数中变量的地址与实参地址不同,则认为是对实参进行了一次拷贝,即新建了一个实参的”副本“,这个”副本“的值和实参值一样,被调函数在后续的操作中都是对这个”副本“进行操作的,而副本的改变和原实参无任何关系。当被调函数结束后,该副本做为局部变量而结束生命周期。
首先看一下5个常见交换值的函数(有的能实现,有的不能实现交换目的)。依据上述原理,如果交换函数中是对真正实参(数据或指针)的操作,则认为能实现交换目的;而如果只是对实参副本进行交换操作,则认为不能达到交换目的。
本文未从理论上进行过多的论述,需要的同学可以自行搜索一下相关资料。
<span style="font-family:SimSun;">//交换两个数字的值,由于函数中较多的使用了cout来显示相关的值,所以使原本简单的函数看起来很长</span><span style="font-family:SimHei;">。</span> void change0(int a1,int a2) { //这个函数不能实现目的 cout<<"change0 执行前:"<<endl; cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl; cout<<"a1="<<a1<<",a2="<<a2<<endl; int temp=a1; a1=a2; a2=temp; cout<<"change0 执行后:"<<endl; cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl; cout<<"a1="<<a1<<",a2="<<a2<<endl; }
void change1(int *a1,int *a2)
{
//能实现目的
cout<<"change1 执行前:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
int temp;
temp=*a1;
*a1=*a2;
*a2=temp;
cout<<"change1 执行后:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
}
void change2(int *a1,int *a2)
{
//这个函数不能实现目的
cout<<"change2 执行前:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
int *temp=a1;
a1=a2;
a2=temp;
cout<<"change2 执行后:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
}
void change3(int &a,int &b)
{
cout<<"change3 执行前:"<<endl;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"&a="<<&a<<",&b="<<&b<<endl;
int temp=a;
a=b;
b=temp;
cout<<"change3 执行后:"<<endl;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"&a="<<&a<<",&b="<<&b<<endl;
}
void change4(int **a,int **b)
{
cout<<"change4 执行前:"<<endl;
cout<<"&a="<<&a<<",&b="<<&b<<endl;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"*a="<<*a<<",*b="<<*b<<endl;
cout<<"**a="<<**a<<",**b="<<**b<<endl;
int **temp = new int *;
*temp = *a;
*a = *b;
*b = *temp;
cout<<endl;
cout<<"change4 执行后:"<<endl;
cout<<"&a="<<&a<<",&b="<<&b<<endl;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"*a="<<*a<<",*b="<<*b<<endl;
cout<<"**a="<<**a<<",**b="<<**b<<endl;
}
change0不能实现目的。简单的说就是值传参只是对实参的副本进行了操作,并不能对实参本身进行操作。这点会通过下面的例子体现出来。
change1能实现目的,但要注意,这种改变是接改变m和n 的值,而pm和pn的指向未改变(与change4正好相反)。从图示中能清楚的看出change1中实现的操作。图中每个圆圈或方块代表一个变量,上方为自己实际的内存地址,其中的内容为他的值。从图中可以看出,change1执行前,生成了两个实参的副本(左侧黄色部分),注意副本的内存地址和实参的不一样的(不然怎么叫重新生成的),他们的值为各自对应实参的值。change1实际上的操作为将变量m和n中的值进行互换,而两个实参的副本还是指向原来的位置(即值不变)。chang1 执行后如图底部所示,指针pm和pn指向的位置不变,但该位置中存储的内容发生了变化。
change2不能实现交换的目的,从图中我们可以看书,change2中完成的操作只是将两个实参副本的值互换了,即只把这俩副本指向的位置改变了(当然,在change2执行结束后他们将消亡),而我们的目的是想改变pm和pn的指向,或互换m和n的值。change2不能实现目的的原因和change0的原因相同,都只是改变了副本的值,而真正的实参没进行任何操作。所以仍各自保持原来的值不变。
change3能实现,他是通过引用的方式实现传递参数的。即直接对传入参数进行操作,而不是对生成的副本进行操作。这样在change3中对参数的任何操作都能直接对参数产生影响,如图所示,当change3执行完毕后,变量m和n的值已经改变了。
change4是用双重指针进行传参,他是通过改变指针pm和pn的指向来实现的,而实际上m和n的值都未改变。但从图中可以看出,change4执行后,ppm和ppn的指向位置未改变,而指向位置的内容却发生了改变,即执行前后ppm始终指向pm,ppn始终指向pn,只不过是执行后pm和pn的指向发了改变。
总结:
利用判断被调函数是对实参操作还是对实参副本操作来判断一个交换函数是否真能实现交换操作;
利用显示变量的内存地址来判断是“实参”还是“实参副本”。