值传递、指针传递、引用传递的区别
c语言的规则很简单:“所有的参数都是传值调用”。在这句话的基础上,我们来分析值传递、指针传递、引用传递的区别。
一、值传递
值传递,这与C函数的性质有关。C函数的所有参数均以“传值调用”方式进行传递,这意味着函数值将获得参数值的一份拷贝,函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传给他的参数。
我们先来看实现函数swap1:
void swap1(int a,int b)
{
printf("\n\n传值的示例函数swap1\n");
printf("形参a的地址:%p\n",&a);
printf("形参b的地址:%p\n",&b);
int temp;
temp = a;
a = b;
b = temp;
printf("形参a的值:%d ",a);
printf("形参b的值:%d ",b);
}
main函数如下:
int a,b;
a = 5;
b = 6;
printf("实参a的值:%d ",a);
printf("实参a的地址:%p\n",&a);
printf("实参b的值:%d ",b);
printf("实参b的地址:%p\n",&b);
swap1(a,b);
printf("\n\n传值交换后a,b的值:\n");
printf("实参a的值:%d \n",a);
printf("实参b的值:%d \n",b);
执行结果如下:
实参a的地址为:0018FF44,实参b的地址为0018FF40
栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。因此,参数a,b是存放在栈区的。在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。因此,我们会看到a,b的地址是呈减少的趋势。
因此,按照“值传递”的思想,形参是实参的拷贝,程序会开辟一块新的栈区为形参。它们进行交换操作是在这块新的栈区里面,并不影响实参的那一块内存。
执行完swap1函数,形参的内存中a,b的值发生了变化,但并不影响实参的的值。
在函数调用时,参数是从右到左读的,所以b的地址比a的地址高
二、指针传递
我们来看实现函数swap2:
void swap2(int *a,int *b)
{
printf("\n\n传指针的示例函数swap2\n");
printf("形参a的地址:%p\n",&a);
printf("形参b的地址:%p\n",&b);
int temp;
temp = *a;
*a = *b;
*b = temp;
printf("形参a的值:%p ",a);
printf("形参b的值:%p ",b);
}
main函数调用:
swap2(&a,&b);
printf("\n\n传指针交换后a,b的值:\n");
printf("实参a的值:%d \n",a);
printf("实参b的值:%d \n",b);
运行结果:
因此我们可以看到,形参的值实际是实参的地址,因为c语言是值传递,main函数中传入的是&a,&b即a,b的地址,所以形参中存储的也是a和b的地址。
然后执行:
temp = *a;
*a = *b;
*b = temp;
*a是什么意思?*a是地址为a的内存中所存储的函数值。所以上面三条语句的意思就是地址为a的内存中所存储的函数值与地址为b的内存中所存储的函数值进行交换。如下图:
因此,他修改的是实参的内容。执行完后实参的内容应该是这样的:
所以,这个时候a的值为6,b的值为5。
三、引用传递
我们来看实现函数swap3:
void swap3(int &a,int &b)
{
printf("\n\n传引用的示例函数swap3\n");
printf("形参a的地址:%p\n",&a);
printf("形参b的地址:%p\n",&b);
int temp;
temp = a;
a = b;
b = temp;
printf("形参a的值:%d ",a);
printf("形参b的值:%d ",b);
}
main函数调用:
swap3(a,b);
printf("\n\n传引用交换后a,b的值:\n");
printf("实参a的值:%d \n",a);
printf("实参b的值:%d \n",b);
执行结果截图:
我们可以看到形参的地址与实参的地址相同,调出汇编代码:
71: swap3(a,b);
0040D9B8 lea eax,[ebp-8]
0040D9BB push eax
0040D9BC lea ecx,[ebp-4]
0040D9BF push ecx
0040D9C0 call @ILT+15(swap3) (00401014)
0040D9C5 add esp,8
lea eax,[ebp-8]是将b的偏移地址送入eax,
这个是swap2的汇编代码,我们可以看到二者是一样的。
66: swap2(&a,&b);
0040D979 lea edx,[ebp-8]
0040D97C push edx
0040D97D lea eax,[ebp-4]
0040D980 push eax
0040D981 call @ILT+10(swap2) (0040100f)
0040D986 add esp,8
所以,对于这里的swap3和前面的swap2来讲,堆栈中的函数参数存放的都是地址,在函数中操作的方式是一致的。但是,对swap3来说这个地址是主调函数通过将实参变量的偏移地址压栈而传递进来的–这是引用传递;而对swap2来说,这个地址是主调函数通过将实参变量的值压栈而传递进来的–这是值传递,只不过由于这个实参变量是指针变量所以其值是地址而已。
这里的关键点在于,同样是地址,一个是引用传递中的变量地址,一个是值传递中的指针变量的值。
四、总结
其实指针传递和值传递本质都是值传递,值传递是传输了要传递的变量的一个副本。在C++中有引用传递,就是将要传递的变量的地址传到了被调用函数中,如果在被调用函数中改变,那么就会在调用函数中改变。
Reference:
2.《C和指针》第七章函数。