1 引例
首先看这样一个程序,思考一下它的输出是什么:
#include <stdio.h>
void test1(int a[], int *p)
{
p = &a[2];
}
void test2()
{
int a[] = { 1,2,3 };
int b = 5;
int* p = &b;
test1(a, p);
printf("%d ", *p);
}
int main(void)
{
test2();
}
你可能会以为,程序的输出是3,因为a[2] = 3。然而,事实是它会输出 5。在test1()
中,我们传递的是指针变量,为什么还会出现这样的结果呢?
2 分析
2.1 为什么会这样
其实,在C语言中,实际参数是通过值传递的。也就是说,形参是实参的拷贝值。
函数调用时,会在栈中开辟空间,创建一个变量(即形参)存放实参的值,你可以认为,这两个变量只是值相同,其他毫无关联。就像int j = k = 1;
一样,j
和k
只是都是值为1。
我们可以把程序稍作改动,来验证上面的说法。
void test1(int a[], int *p)
{
printf("%p\n", &p); /*二*/
printf("%d\n", *p);
p = &a[2];
}
void test2()
{
int a[] = { 1,2,3 };
int b = 5;
int* p = &b;
printf("%p\n", &p); /*一*/
printf("%d\n", *p);
test1(a, p);
printf("%d ", *p);
}
int main(void)
{
test2();
}
运行程序,结果为:
004FFA48
5
004FF974
5
5
可以看出,在一
处,p的地址为004FFA48
,而在二
处,p的地址为004FF974
,这便说明了,这两个p
不是同一个p
。用图示的方法可能更好理解:
进入test2()
后,我们手动定义了变量p,并使它指向了b,调用test1()
时,程序自动定义了变量P'
,也使它指向了b。
在test1()
中,我们改变了`P’的指向。
test1()
执行结束以后,P'
被释放。而P
的指向一直没有改变。
2.2 怎么改
知道原因以后,又该怎么更改程序,达到我们想要的效果呢?这里,我们用到了双重指针。直接看程序:
void test1(int a[], int **p)
{
printf("%p\n", &p); /*二*/
printf("%p\n", &*p); /*三*/
*p = &a[2]; /*四 要是想修改b的值,只需要把这里改为 **p = a[2];*/
}
void test2()
{
int a[] = { 1,2,3 };
int b = 5;
int* p = &b;
printf("%p\n", &p); /*一*/
printf("%d\n", *p);
test1(a, &p);
printf("%d ", *p);
}
int main(void)
{
test2();
}
运行程序:
0095FB84
5
0095FAB0
0095FB84
3
从运行结果可以看出,三
处和一
处的输出是一样的,所以可以达到想要的效果。同样的,用图示的方法来理解:
进入test2()
,定义P
指向b
,运行test1()
,创建临时变量P'
指向P
,可以理解为int **P' = &P
.test1()
中,*p = &a[2];
中,*p
即是*p'
,也就是P
的值,所以*p = &a[2];
等价于P = %a[2];
test1()
执行结束以后,P'
被释放,*P
变为了3.
3 为什么会出错
为什么会“理所当然”地犯下引例中的错误?源头可能是来自数组做形参时的引导。再来看一个例子:
#include <stdio.h>
void test3(int a[])
{
a[2] = 5;
}
void test4()
{
int a[] = { 1,2,3 };
test3(a);
printf("%d\n", a[2]);
}
int main()
{
test4();
}
没有那么多套路,输出结果确实是 5。数组名也是指针,为什么它就可以呢?它不是值传递吗?
答案是,它也是值传递,不过,这里的情况和前面的例子稍有不同。这里再写一个例子,读者可以结合下面的例子,按照前面的创建P'
和画图的方法,自行思考原因。
#include <stdio.h>
void test4()
{
int a[] = { 1,2,3 };
int* b = a;
b[2] = 5;
printf("%d\n", a[2]);
}
int main()
{
test4();
}