最近在刷剑指Offer过程中,关于C++的函数传参,有很多误区,查了很多资料,把个人的理解简单的说一下,如果存在问题,欢迎大家一起讨论!
值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,
不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
指针传递:
形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
引用传递:
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
接下来我们以交换两个整数值来做具体的说明:
值传递:
#include<stdio.h>
#include<iostream.h>
void change0(int a1,int a2)
{
//这个函数不能实现目的
cout<<"change0 执行前:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*&a1="<<*&a1<<",*&a2="<<*&a2<<endl;
// cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;//因为是值传递,不是地址,所以这样没有意义
cout<<"a1="<<a1<<",a2="<<a2<<endl;
//函数实现
int temp=a1;
cout<<"&temp="<<&temp<<endl;
cout<<"*&temp="<<*&temp<<endl;
a1=a2;
a2=temp;
cout<<"change0 执行后:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*&a1="<<*&a1<<",*&a2="<<*&a2<<endl;
// cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
}
从下图我们可以看出,值传递是对实参中数值的拷贝,形参a1的地址和实参m的地址并不相同,而是从内存中开辟了一个新的空间,存储实参中存储的值,对形参数据的更改,不能改变交换实参中的数据。
指针传递:
void change1(int *a1,int *a2)
{
//能实现目的
cout<<"change1 执行前:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"*(&a1)="<<*(&a1)<<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;
}
下图为运行结果,从图中我们可以看出,指针传递的本质是值传递,传递的是指向m,n的指针(实参是m,n的地址,或者指针变量p1,p2中存储的指针值)
值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变)
下图是对指针传值过程的图解,这种改变是接改变m和n 的值,而pm和pn的指向未改变(与change4正好相反)。从图示中能清楚的看出change1中实现的操作。图中每个圆圈或方块代表一个变量,上方为自己实际的内存地址,其中的内容为他的值。从图中可以看出,change1执行前,生成了两个实参的副本(左侧黄色部分),注意副本的内存地址和实参的不一样的(不然怎么叫重新生成的),他们的值为各自对应实参的值。change1实际上的操作为将变量m和n中的值进行互换,而两个实参的副本还是指向原来的位置(即值不变)。chang1 执行后如图底部所示,指针pm和pn指向的位置不变,但该位置中存储的内容发生了变化。
我觉得这篇文章很不错,推荐给大家:
图解C++各种传参方式
void change2(int *a1,int *a2)
{
//这个函数不能实现目的 只改变了副本的的位置
//能实现目的
cout<<"change2 执行前:"<<endl;
cout<<"形参a1的地址&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"形参a1指向的值*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"*(&a1)="<<*(&a1)<<endl;
cout<<"形参a1存储的地址a1="<<a1<<",a2="<<a2<<endl;
int *temp=a1;
a1=a2;
a2=temp;
cout<<"change2 执行后:"<<endl;
cout<<"形参a1的地址&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"形参a1指向的值*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"*(&a1)="<<*(&a1)<<endl;
cout<<"形参a1存储的地址a1="<<a1<<",a2="<<a2<<endl;
}
实验结果如下,两个形参指向的位置发生了交换,但对应实参指向的变量没有发生改变,变量中存储的数据也没有发生改变,所以不会交换原数据的值。
对应过程图解如下,change2不能实现目的的原因和change0的原因相同,都只是改变了副本的值,而真正的实参没进行任何操作。所以仍各自保持原来的值不变。
引用传递:
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;
}
引用传递,即直接对传入参数进行操作,而不是对生成的副本进行操作。这样在change3中对参数的任何操作都能直接对参数产生影响,如图所示,当change3执行完毕后,变量m和n的值已经改变了。
双重指针:改变实参指针的指向
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;
}
想要交换两个数据,可以交换变量m,n中的内容,也可以交换两个实参指针中的内容。
change4是用双重指针进行传参,他是通过改变指针pm和pn的指向来实现的,而实际上m和n的值都未改变。但从图中可以看出,change4执行后,ppm和ppn的指向位置未改变,而指向位置的内容却发生了改变,即执行前后ppm始终指向pm,ppn始终指向pn,只不过是执行后pm和pn的指向发了改变。
主函数:
void main()
{
int m=5;
int n=10;
int* p1=&m;
int** pp1=&p1;
int* p2=&n;
int** pp2=&p2;
cout<<"主函数交换之前"<<"m="<<m<<",n="<<n<<endl;
cout<<"主函数交换之前"<<"&m="<<&m<<",&n="<<&n<<endl;
cout<<"主函数交换之前"<<"实参&p1="<<&p1<<",实参&p2="<<&p2<<endl;
change4(pp1,pp2);
cout<<"主函数交换之前"<<"m="<<m<<",n="<<n<<endl;
cout<<"主函数中的结果"<<"&m="<<&m<<",&n="<<&n<<endl;
cout<<"主函数交换之前"<<"实参&p1="<<&p1<<",实参&p2="<<&p2<<endl;
}
最后,总结一下指针和引用的相同点和不同点:
★相同点:
●都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
★不同点:
●指针是一个实体,而引用仅是个别名;
●引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
●引用没有const,指针有const,const的指针不可变;(具体指没有int& const a这种形式,而const int& a是有 的, 前者指引用本身即别名不可以改变,这是当然的,所以不需要这种形式,后者指引用所指的值不可以改变)
●引用不能为空,指针可以为空;
●“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
●指针和引用的自增(++)运算意义不一样;
●引用是类型安全的,而指针不是 (引用比指针多了类型检查)
关于引用和指针的异同,这篇文章写得非常好,大家可参考一下!