概述
C/C++语言参数传递可以分为三种:值传递,地址传递和引用。(引用属于C++)
这三者的区别我们经常使用经典的swap()函数来说明。
一,值传递
void swap_value(int a,int b)
{
int temp;
temp=a;
a=b;
b=temp;
}
值传递是单向传递,如a=5,x++=a之后a的值仍为5一样,x所作的操作并不会影响a,只是复制了一份副本过去。
PS: 当参数为数组名时,实际上是地址传递而不是值传递,因为数组名可以视为数组所在内存空间首地址。
二,地址传递
void swap_pointer(int *a,int *b)
{
int temp=*a; //需要关注的是改变指针所指变量的值,而不是交换指针值本身
*a=*b; // 是用int temp 而不是int *temp
*b=temp;
}
地址传递是指函数传入的参数是地址,这个地址通常是指向原变量的指针,如
x2=1,y2=100;
int *xx=&x2,*yy=&y2;
swap_pointer(xx,yy);
其中,xx,yy为指向x2和y2的指针,即常量值1,100存储空间的地址。这样,在swap_pointer()中,通过地址将原存储空间内的值交换,即实现了变量值的交换。这里需要注意的两点是:
① swap_pointer()函数体中,切记,要实现的是相应地址(存储空间)内对应的值的交换—定义int temp,然后
在temp,*a,*b之间作交换(此时*a,*b可以看作a,b自身,对他们的操作直接就是对a,b的操作,会影响其
值。),而不是交换指针值本身,如一个非常常见的错误写法:
void swap_pointer(int *a,int *b)
{
int *temp;
temp=a;
a=b;
b=temp;
}
int *xx=&x2,*yy=&y2;
swap_pointer(xx,yy);
指针a,b的“值”得到互换,而指针的值即所指向变量的物理地址,所以a,b所指向的变量得到交换。但是!!地址传递的本质其实是值传递,由swap_value知道,传入的指针变量xx,yy系统会使用局部变量a,b来“存放”他们,在函数体中操作的只是xx,yy的一个副本。即在函数体中对指针本身的改变,并不会影响原指针变量。所以a,b的值交换了,但是xx,yy的值并没有交换,这和swap_value()其实犯了同样的错误。
另外,还有一种易错的写法:
void swap_pointer(int *a,int *b)
{
int *temp;
*temp=*a;
*a=*b;
*b=*temp;
}
乍一看上去没什么错误,但是int *temp; 声明之后并没有初始化,之后的*temp=*a;可以理解为用“*”取出temp的值,这就存在一个潜在的问题,没有初始化意味着在*temp之前temp的值(即所指地址)在系统工作区,则*temp操作必然会报错,虽然此处只是赋值——用*a的值“覆盖”*temp的值。
除了标准交换方法swap_pointer()外,也可以针对声明未初始化的解决:
void swap_pointer(int *a,int *b)
{
int x;
int *temp=&x; //声明一个int变量x,取其地址来进行temp的初始化
*temp=a; //相较于标准解法,多用了储存空间及时间
a=b;
b=*temp;
}
②实际调用swap_pointer()时,除了如上使用指向原变量的指针传参外,也可以直接使用
swap_pointer(&x2,&y2)的形式,二者实测效果相同。但是,要注意的是,这种方式和
int *xx=&x2,*yy=&y2;
swap_pointer(xx,yy);
都不能避免①中描述的错误:
void swap_pointer(int *a,int *b)
{
int *temp;
temp=a;
a=b;
b=temp;
}
不能将swap_pointer(&x2,&y2)的x2,y2“代入”函数体内观察,因为系统在实际执行函数过程中,是将(&x2,&y2)视为实参的,还是要传入形参(int *a,int *b)中,这涉及了一个知识点——地址传递/指针传递的本质还是值传递,只是此时传递的值是指针变量的“值”即指针变量所指变量的地址,这样在函数体内通过“由地址访问内存”的机制(*p),达到了改变变量真正的值的目的。所以清楚了地址传递的实质,再看swap_pointer(xx,yy),同样是值传递,实参*xx,*yy将值传递给形参*a,*b。
所以可以这么说,地址传递实现的关键点不在于函数的参数可以是指针变量,而在于C语言提供了“通过地址直接访问(读写)内存空间”的机制,即支持“*p”操作。另外,由此也可知,地址传递在函数体内操作的是*p,对p的操作是无意义的,就比如对a的操作其实并不会改变xx的值,因为这也是值传递,所以①中的错误:
void swap_pointer(int *a,int *b)
{
int *temp;
temp=a;
a=b;
b=temp;
}
int *xx=&x2,*yy=&y2;
swap_pointer(xx,yy);
要想在自定义函数内实现对指针变量xx的改变,必须再封装一层,比如指针的指针int **Ptrx=&xx作为参数传入。
Exercise:
#include<stdio.h>
void main()
{
long x=666;
long* p=&x;
//printf()输出格式中,%p是以16进制输出地址,即指针变量的值(所指向变量的地址),或者指针变量本身的地址
printf("%dn",*p);
//p指向的地址的内容, 也就是x的地址的内容,也就是666
printf("%pn",p);
//p指向的地址,也就是x的地址
printf("%pn",&p);
//p本身的地址
}
PS:%p必然可以正确输出p或者&p,因为系统的“地址值”是固定的,这与p所指向的变量类型,长度没有关系。
三,引用传递
void swap_reference(int &a,int &b)
{
int temp; //①引用即别名
temp=a; //②注意引用实参形参的写法
a=b;
b=temp;
}
简单理解:引用即给原来变量起的别名
注意形参实参的写法,不要弄错:
形参:void swap_reference(int &a,int &b)
实参:void swap_reference(x3,y3)
为验证三种参数传递方式的区别,在VC++6.0上实测,程序如下:
#include<stdio.h>
{
int temp;
temp=a;
a=b;
b=temp;
}
{
int temp=*a; //需要关注的是改变指针所指变量的值,而不是交换指针值本身
*a=*b; // 是用int temp 而不是int *temp
*b=temp;
}
{
int temp; //①引用即别名
temp=a; //②注意引用实参形参的写法
a=b;
b=temp;
}
int main()
{
int x1,x2,x3,y1,y2,y3;
x1=x2=x3=1;
y1=y2=y3=100;
swap_value(x1,y1);
printf("x1=%d y1=%dn",x1,y1);
int *xx=&x2,*yy=&y2;
swap_pointer(xx,yy);
printf("x2=%d y2=%dn",x2,y2);
swap_reference(x3,y3);
printf("x3=%d y3=%dn",x3,y3);
return 0;
}
运行结果:
注意,引用是CPP的,所以文件后缀为.cpp编译无错,若为.c则void swap_reference()那里会报错。
PS:指针和引用在格式上区别
指针,定义函数时为void swap_pointer(int *a,int *b),使用函数时swap_pointer(&a,&b)(方式之一);
引用,定义函数时为void swap_reference(int &a,int &b),使用函数时swap_reference(a,b)
引用别人的理解:
“从概念上讲。指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。
而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。(用常量指针来理解)
在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的:
指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(注:这就是②中xx,yy并没有改变的根本原因)
而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。”
其实,从①中错误实例去理解指针传递的本质——和swap_value没有区别,从swap_reference的实参形式swap_reference(x,y)去理解引用的“常量性”——只有这一种表示方法。
上面说的是“机制”,具体涉及到“使用规则”:
① 需不需要初始化
指针可以视为独立的变量——可以存放对应类型变量地址的变量,其位数由机器的地址位数决定(因为其存地址)。所以指针变量可以不初始化,或者不指向任何变量,即空指针,eg:
int* p; //合法,虽然可能会warning
int* p=null; //正确
PS:这代表p不指向任何变量,但是p本身有自己的值,且一般不为0,若用printf(%p)输出p可以看出并不是0x0。
而引用具有依附性,不存在不是某变量引用的引用变量,eg:
int& r; //错误,未初始化
int& r=null; //错误,赋值的类型(需要为int变量)和值的大小均错误
int x=100;
int& r=x; //正确
② 引用的依附性具有“唯一性”
即引用变量要初始化,而且只能初始化一次,可以用常量指针来理解。而指针变量具有独立性,不仅可以不初始化,还能根据需要指向不同的变量。
综上,其实可以看出引用时面向需求的,如在swap_reference()中进行参数传递,可以兼顾函数的封装性(不然直接写交换语句而不用使用函数了)和改变实参的要求,引用变量具有唯一性,初始化后就不能改变。在需要时创建,使用后基本就“废弃”。
而指针可以充当更为灵活和关键的角色,如在链表中,指针可以说充当了“灵魂”的角色,根据需要进行改变,指向不同的节点。毫无疑问,指针同样可以面向需求来使用,需要时创建,唯一指定,使用后废弃,但是其本身可以起到更大的作用。
关于引用的优点,总的就是引用的依附性,这使相比于指针,使用引用变量不用检查其非空性,即出现了引用变量则其必然是已经指向具体变量的(这在声明引用时由编译器保证的),提高了效率。另一方面,在参数传递时使用引用而不是指针,可以避免使用匿名对象的情况(Java中)。
参考资料:https://blog.csdn.net/u011447369/article/details/49448001
声明:除已注明的引用外,文章系本人原创,引用转载请注明出处,如有必要请联系作者。
最后
以上就是秀丽含羞草为你收集整理的由经典swap( )谈起——参数传递的三种类型的全部内容,希望文章能够帮你解决由经典swap( )谈起——参数传递的三种类型所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复