首先对于C语言来说参数的传递只有两种方式:
刚好今天无意中看到一道有关C语言值传递的面试题,感觉非常具有代表性,背后涉及的知识也非常多,所以这里就拿出来分析一下,顺便讲讲C语言的值传递。(高手请直接略过本文)
void swap(int *x, int *y)
{
int *tmp;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 1, b = 2;
int *p1 = &a;
int *p2= &b;
swap(p1, p2);
return 0;
}
问题是:main函数执行完毕后,a和b的值有没有被交换? 答案:没有
注意:如果指针或者地址作为实参传入函数,指针指向的内容或者地址存放的数据是可以改变的,但是指针的值和地址本身是不会改变的。 根据以上规则,我们可以得出两个结论:1.swap函数传进去的是a和b的地址。2.swap函数的形参int *x和int *y是p1和p2的一份拷贝。下面是这6个变量在内存中(地址是假设的)的示意图:
上图左边是内存示意图,表明该块内存中存放的数据,右边是内存的地址,其中p1指向a,p2指向b。
我们还可以在程序中添加如下打印(对上述6个变量全部取地址),看下运行结果:
void swap(int *x, int *y)
{
int* tmp;
printf("x地址=%p y地址=%p\n", &x, &y);
tmp = x;
x = y;
y = tmp;
//printf("x=%d y=%d\n", *x, *y);
}
int main()
{
int a = 1, b = 2;
int *p1 = &a;
int *p2= &b;
printf("a地址=%p b地址=%p\n", &a, &b);
printf("p1地址=%p p2地址=%p\n", &p1, &p2);
swap(&a, &b);
printf("ab交换后: ");
printf("a=%d b=%d\n", *p1, *p2);
return 0;
}
运行结果如下图所示,这6个变量地址均不相同,且a,b没有交换。
传参过程相当于执行int *x=p1;int *y=p2;形参x,y是实参p1和p2的一份拷贝,它们在内存中也有自己的地址。
接着在swap函数中加上打印,看下swap函数到底交换了什么。
void swap(int *x, int *y)
{
int* tmp;
printf("交换前:\r\n");
printf("\r\n");
printf("x地址=%p y地址=%p\n", &x, &y);
printf("x=%p y=%p\n", x, y);
printf("\r\n");
tmp = x;
x = y;
y = tmp;
printf("交换后:\r\n");
printf("\r\n");
printf("x地址=%p y地址=%p\n", &x, &y);
printf("x=%p y=%p\n", x, y);
printf("\r\n");
}
swap执行时,x,y本身的地址没有改变,只是x和y进行了一次交换,且a,b本身也没有受到任何影响(p1,p2也不受影响)。swap函数执行结束时,形参x,y内存被自动释放,对实参依旧没有产生任何影响。
1.如何修改程序实现上述交换?
a.直接修改源数据 由于传进来的是a和b的地址,所以可以直接修改地址上的内容,实现交换ab。程序修改如下:
void swap(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
这种方式并没有修改传进来的参数,因为a,b的地址并没有改变,只是通过传进来的参数间接的修改了地址上的内容,所以仍然满足前面说的形参不能改变实参本身。
b.二级指针修改源数据 这种方法和一级指针类似,都是通过地址间接修改ab的值,无非就是多一级指针。程序如下:
void swap(int **x, int **y)
{
int tmp;
printf("swap交换前:\r\n");
printf("\r\n");
printf("x地址=%p y地址=%p\n", &x, &y);
printf("x=%p y=%p\n", x, y);
printf("\r\n");
tmp = **x;
**x = **y;
**y = tmp;
printf("swap交换后:\r\n");
printf("\r\n");
printf("x地址=%p y地址=%p\n", &x, &y);
printf("x=%p y=%p\n", x, y);
printf("\r\n");
}
int main()
{
int a = 1, b = 2;
int *p1 = &a;
int *p2= &b;
printf("ab交换前:\r\n");
printf("a地址=%p b地址=%p\n", &a, &b);
printf("p1地址=%p p2地址=%p\n", &p1, &p2);
printf("p1=%p p2=%p\n", p1, p2);
printf("========================\r\n");
swap(&p1, &p2);
printf("========================\r\n");
printf("ab交换后:\r\n");
printf("p1地址=%p p2地址=%p\n", &p1, &p2);
printf("p1=%p p2=%p\n", p1, p2);
printf("a=%d b=%d\n", a, b);
return 0;
}
程序执行结果如下,p1和p2的地址以及p1,p2的值都没有改变,但是ab的值已经交换。
2.如何修改传入指针的值?
使用二级指针 下面先用示意图说明下这个问题。假设内存中有如下变量指针p=0x300,变量x地址为0x300,变量y的地址为0x304。现将p作为参数传入某函数,要求在该函数內修改p的值,使p=0x304?
显然对于这个问题,如果函数形参是一级指针,也就是传入的值为0x300,那我们是无论如何也不能将0x300改为0x304的(因为形参是无法改变传入的值的),但是如果传入的是p的地址(0x0004),那么我们就可以修改0x0004这个地址上的内容,也就是修改了p的值。 这里或许有同学会问,既然二级指针改变了p的值,那么形参不还是改变了实参?其实不是这样的,首先我们需要了解传入的是什么值,发生改变的又是什么值,这里传入的是p的地址(0x0004),发生改变的是p(由0x300变为0x400),因此,形参并未改变实参的值。 下面再举个指针传参的面试题说明下这个问题:
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
int main()
{
char *str=NULL;
GetMemory(str);
strcpy_s(str,6,"Hello");
printf(str);
return 0;
}
问题是:上述程序运行会有什么问题? 答案:程序崩溃,一级指针传参无法改变指针的值,所以str的值一直是NULL。 所以对于上述程序,如果要程序正常运行,可以使用二级指针,修改后程序如下:
void GetMemory(char **p)
{
*p = (char *)malloc(100);
}
int main()
{
char *str=NULL;
printf("申请之前:str=%p\r\n",str);
GetMemory(&str);
printf("申请之后:str=%p\r\n",str);
strcpy_s(str,6,"Hello");
printf(str);
free(str);//动态申请的内存一定要手动释放
return 0;
}
我们或许会遇到下面这种数组作为形参的情况,这是一种特殊情况,按照之前的分析,数组作为形参传递时,函数内部会定义一个与之类型相同的数组接收传入的数组变量,但实际上不是这样。
void test(char str[5])
{
....
}
下面进行举例说明:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void test(char str[5])
{
printf("str = %d\n",sizeof(str));
printf("str addr = %x\n",str);
printf("str:%d-%d-%d-%d-%d\r\n",str[0],str[1],str[2],str[3],str[4]);
str[1] = 66;
}
int main(void)
{
char buf[]={1,2,3,4,5};
printf("buf addr = %x\n",buf);
test(buf);
printf("buf:%d-%d-%d-%d-%d\r\n",buf[0],buf[1],buf[2],buf[3],buf[4]);
return 0;
}
运行结果如下:
通过打印str和buf的值,发现buf和str的值是一样的,而且str占用的空间是4个字节,由此可见:对于数组作为形参的这种情况,实参传递的是数组的首地址,形参数组接收这个首地址,实参和形参数组占用同一块内存。
void test(char str[5]);
//这个表达式中的5实际上没有任何意义,因为形参只接收数组首地址,
//并不清楚实参数组长度
//一般再加个额外的参数,指示数组长度,防止数组越界情况
void test(char str[5],int n);//n表示数组长度
简单总结下:C语言参数传递的本质都是值传递;值传递意味着只能由实参传给形参,形参是实参在内存中的一份拷贝,函数运行结束时被销毁,形参无法改变实参。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有