C语言中传值与传指针的介绍与区别
作者:守望 时间:2023-08-01 15:26:51
前言
我们可能听过C语言中的传值和传指针,在其他语言中,也有传引用一说,那么他们到底有什么区别呢?如果你还不能准确地分辨,就该好好了解一下了。
传值
我们在初学C语言的时候就被老师教过,下面的方式是无法交换a和b的值的:
#include<stdio.h>
void swap(int a,int b)
{
int temp = a;
a = b;
b = temp;
printf("swap a = %d,b = %d\n",a,b);
}
int main(void)
{
int a = 10;
int b = 20;
printf("before swap:a = %d,b = %d\n",a,b);
swap(a,b);
printf("after swap:a = %d,b = %d\n",a,b);
return 0;
}
运行结果如下:
before swap:a = 10,b = 20
internal swap a = 20,b = 10
after swap:a = 10,b = 20
可以看到,a和b的值最终并没有被交换。开始时a,b的值为10,20,而最终还是同样的值。
为什么呢?因为函数参数在传递的时候,都是传原数据的副本,也就是说,swap内部使用的a和b只是最初始a和b的一个副本而已,所以无论在swap函数内部对a和b做任何改变,都不会影响初始的a和b的值。
正因如此,我们常常被告知,不要把直接把结构体直接作为参数,这样效率会很低。由于结构体本身占用字节数较大,如果直接作为参数,那么将会产生一个较大的”副本“,如此一来,效率也就很低了。
我们再结合下面的图来理解:
首先图中方框中的上部分a和b代表了main函数中的a和b,即原始数据,而方框中的下部分a和b代表了函数的参数a和b,即原始数据的“副本”。(后面的图都是如此,上部分代表原始值,下部分代表函数参数值)。
调用swap函数前后的情形如下:
由于在swap中永远只是对a和b的副本进行操作,因此完全不影响原始的a和b的值。最终也不可能达到交换a和b的值的目的。
传指针
那么为解决上面的问题,我们知道,需要传指针。其代码如下:
#include<stdio.h>
void swap(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
printf("swap a = %d,b = %d\n",*a,*b);
}
int main(void)
{
int a = 10;
int b = 20;
printf("before swap:a = %d,b = %d\n",a,b);
swap(&a,&b);
printf("after swap:a = %d,b = %d\n",a,b);
return 0;
}
运行结果:
before swap:a = 10,b = 20
swap a = 20,b = 10
after swap:a = 20,b = 10
可以看到在这种情况下,a,b的值才是真正交换了。
为什么又有传值,又有传指针
看到这里,不知道你是否会疑惑,为什么给函数传递参数的时候,一会是传值,一会是传指针呢?为什么传指针就能改变参数的值呢?实际上,C语言里,参数传递都是值传递!也就是说,你认为的传指针也是传值,只不过它的值是指针类型罢了。
我们再通过图来理解前面为什么传指针就可以交换a,b的值:
从图中可以看出,虽然传递给函数的是指向a和b的指针的副本,但是它的副本同样也是指向a和b,因此虽然不能改变指针的指向,但是能改变参数a和b指向的内容,即改变原始a和b的值。
再看传指针
如果是为指针p申请一段内存,下面的代码能达到目的吗?
#include<stdio.h>
#include<stdlib.h>
void getMemery(int *p)
{
/*申请1024个int大小*/
p = malloc(sizeof(int)*1024);
if(NULL == p)
{
printf("malloc failed\n");
p = NULL;
}
}
int main(void)
{
int *p = NULL;
getMemery(p);
printf("address of p is %p\n",p);
return 0;
}
通过前面的内容分析,肯定是达不到预期效果的。
运行结果:
address of p is (nil)
这是为什么呢?我们还是利用前面所知来分析,由于传递给getMemory函数的参数都是一个副本,因此函数内的p也是外部p的一个副本,因此即便在函数内部,将p指向了一块新申请的内存,仍然不会改变外面p的值,即p还是指向NULL。
如何修改呢?我们需要传入p的地址,即指向int类型指针的指针。
#include<stdio.h>
#include<stdlib.h>
void getMemery(int **p)
{
/*申请1024个int大小*/
*p = malloc(sizeof(int)*1024);
if(NULL == *p)
{
printf("malloc failed\n");
*p = NULL;
}
}
int main(void)
{
int *p = NULL;
getMemery(&p);
printf("address of p is %p\n",p);
free(p);
p = NULL;
return 0;
}
运行结果如下:
address of p is 0x144f010
从运行结果可以看到,p的值被改变了。
可配合下面的图进行理解:
总结
本文总结如下:
函数的形参都是原数据的“副本”,因此在函数内无法改变原数据
函数中参数都是传值,传指针本质上也是传值
如果想要改变入参内容,则需要传该入参的地址(指针和引用都是类似的作用),通过解引用修改其指向的内容
以上结论不限于C语言
思考
如何实现不借助第三个变量,交换两个整数的值?
结合本文,理解C++/Java中所谓的传引用
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。
本文作者: 守望
本文链接: https://www.yanbinghu.com/2019/06/20/53981.html
版权声明: 本文为原创文章,版权归守望所有,并采用 CC BY-NC-SA 3.0 许可协议。转载请联系本人!
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
详解Java中Iterable与Iterator用法
mybatis水平分表实现动态表名的项目实例
![](https://img.aspxhome.com/file/2023/2/59772_0s.png)
Struts2下拉框实例解析
![](https://img.aspxhome.com/file/2023/9/61399_0s.jpg)
如何使用ByteArrayOutputStream下载文件
一文详解gRPC快速整合SpringCloud
![](https://img.aspxhome.com/file/2023/4/70504_0s.jpg)
Android Handler消息机制分析
![](https://img.aspxhome.com/file/2023/8/87268_0s.jpg)
C#利用异或算法实现加密解密
![](https://img.aspxhome.com/file/2023/0/116460_0s.png)
JAVA格式化时间日期的简单实例
java安全编码指南之:Mutability可变性详解
![](https://img.aspxhome.com/file/2023/3/59073_0s.jpg)
Android Handler移除Message详解及实例代码
Java swing读取txt文件实现学生考试系统
![](https://img.aspxhome.com/file/2023/6/79906_0s.jpg)
C#生成带二维码的专属微信公众号推广海报实例代码
![](https://img.aspxhome.com/file/2023/7/79087_0s.jpg)
C#如何动态创建lambda表达式
ijkPlayer播放器的全自动编译脚本及最终编译包
C#进程监控方法实例分析
C/C++与Java各数据类型所占字节数的详细比较
MybatisPlus使用Wrapper实现条件查询功能
![](https://img.aspxhome.com/file/2023/2/79322_0s.png)
android recyclerview模拟聊天界面
![](https://img.aspxhome.com/file/2023/3/88723_0s.jpg)
在Java的Struts中判断是否调用AJAX及用拦截 器对其优化
![](https://img.aspxhome.com/file/2023/0/59980_0s.png)