今天看阿秀的校招笔记指针和引用的区别那块,遇到一段代码,对其输出不太理解:
void test(int *p)
{
int a=1;
p=&a;
cout<<p<<" "<<*p<<endl;
}
int main(void)
{
int *p=NULL;
test(p);
if(p==NULL)
cout<<"指针p为NULL"<<endl;
return 0;
}
//运行结果为:
//0x22ff44 1
//指针p为NULL
一开始我很疑惑,为什么会输出“指针p为NULL”呢,难道前面test§;没有修改p的值吗。
直接说结果吧,其实实参p确实没有被修改,传入被调函数的是变量p的拷贝,记为_p,而前面被调函数test的返回值是void类型,没有将更改后的变量传回主调函数。
这涉及到几个基础知识点:
1.形参和实参的定义与区别
实参(argument):
全称为"实际参数",是在调用时传递给函数的参数, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值比如p = NULL, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。
形参(parameter):
全称为"形式参数" ,由于它不是实际存在变量,所以又称虚拟变量。是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数.在调用函数时,实参将赋值给形参。因而,必须注意实参的个数,类型应与形参一一对应。
需要注意的是:函数调用中发生的数据传送是单向的, 即只能把实参的值传送给形参,而不能把形参的值 反向 地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
2.局部变量与全局变量
p定义在main函数里面,main 函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和全局变量一样长而已。全局变量一定是定义在函数外部的。既然它是局部变量,test函数肯定无法使用 p的真身。那函数调用是怎么处理的?
其实就是对实参做一份拷贝并传递给被调用的函数。即对 p 做一份拷贝,为_p。那传递到函数内部的就是_p 而并非 p 本身。
3.被调函数传递参数时发生了什么
如果我们想使用更改过后的变量p呢?即不会输出指针p为NULL了。
先复习以下知识点:
在调用函数时,如果需要从被调函数返回一个值供主调函数使用,那么返回值类型必须定义成非 void 型。此时被调函数中必须包含 return语句,而且 return 后面必须要有返回值,否则就是语法错误。——谭浩强《C程序设计第四版》
所以说,我们需要把test函数改成非void类型(因为void没有返回值,可以理解为有个隐式的return),并且return p。
int* test(int *p) //p是指针类型,所以是int*
{
int a=1;
p=&a;
cout<<p<<" "<<*p<<endl;
return p;
}
//后面main函数不变
被调函数运行结束后才会返回主调函数,并且被调函数运行结束后系统为被调函数中的局部变量分配的内存空间就会被释放。也就是说,return返回的那个值在被调函数运行一结束就被释放掉了,那么它是怎么返回给主调函数的呢?
事实上在执行 return 语句时系统是在内部自动创建了一个临时变量,然后将 return 要返回的那个值赋给这个临时变量。所以当被调函数运行结束后 return后面的返回值真的就被释放掉了,最后是通过这个临时变量将值返回给主调函数的。而且定义函数时指定的返回值类型实际上指定的就是这个临时变量的类型。这也是为什么当 return 语句中表达式的类型和函数返回值类型不一致时,将 return 的类型转换成函数返回值类型的原因。return语句实际上就是将其后的值赋给临时变量,所以它要以临时变量的类型为准,即函数返回值的类型。
其实还有种办法!来自《C语言深度剖析这本书》
我们把p的地址作为实参,即调用test(&p),这样就把p的地址传过去了(注意p本身就是个地址,即地址的地址,即二级指针),那么用*(&p)就能访问p并且修改p!
void test(int**p)//注意形参变化
{
int a=1;
*p=&a;
}
int main(void)
{
int *p=NULL;
test(&p);//注意参数是&p,不是p了
if(p==NULL)
cout<<"指针p为NULL"<<endl;
return 0;
}
当然,还是第一种方法更直观一点,相比于二级指针,还是return p更常见,不过需要注意此时的函数返回类型不能是void了。