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 许可协议。转载请联系本人!
猜你喜欢
- 有时候我们从数据库中查询出来数据之后,需要按照DataTable的某列进行分组,可以使用下面的方法实现,代码如下:using System;
- 1、ThreadLocal 使用原理前文我们讲过ThreadLocal的主要用途是实现线程间变量的隔离,表面上他们使用的是同一个Thread
- 1、很多资料说,添加以下代码,可以隐藏地址栏,但我试了很多次,貌似不成功啊。<meta name="apple-mobile
- Java中的static关键字可以用于修饰变量、方法、代码块和类,还可以与import关键字联合使用,使用的方式不同赋予了static关键字
- Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。
- 详解Java对象的强、软、弱和虚引用+ReferenceQueue一、强引用(StrongReference)强引用是使用最普遍的引用。如果
- 详解Java中HashSet和TreeSet的区别1. HashSetHashSet有以下特点:不能保证元素的排列顺序,顺序有可能发生变化不
- springboot项目启动,初始化方法加载参数今天我看到项目中用到了 @PostConstruct 这个注解,之前没看到过,特地查了一下,
- 前言:根据ThreadPoolExecutor的构造方法,JDK提供了很多工厂方法来创建各种用途的线程池.1 newFixedThreadP
- 修订功能可以跟踪文档所有的修改,了解修改的过程,这对于团队协同文档编辑、审阅是非常有用的一个功能。将工作簿发送给他人审阅时,我们可以开启修订
- 本文将介绍如何在spring boot中集成ehcache作为hibernate的二级缓存。各个框架版本如下spring boot:1.4.
- 方法一:<uses-permission android:name="android.permission.WAKE_LOC
- 本文实例讲述了java使用归并删除法删除二叉树中节点的方法。分享给大家供大家参考。具体分析如下:实现的思想很简单:first:找到要删除的节
- 对谷歌地图操作使用的是WebBrowser控件,通过对javascript的操作来实现对谷歌地图的各种操作,所以首先要创建一个html文件,
- spring针对Bean之间的循环依赖,有自己的处理方案。关键点就是 * 缓存。当然这种方案不能解决所有的问题,他只能解决Bean单例模式下非
- 最近研究C#相关的ORC技术,图像识别一般C和C++这种底层语言做的比较多,C#主要是依托一些封装好的组件进行调用,这里介绍三种身份证识别的
- 定义工具类-创建对应的日志对象定义枚举类-存储定义的日志文件名称logback.xml里配置对应的日志名称和日志等级1、工具类 Logger
- 抛出问题:Long a = 4l;Long b = 4l;a == b //trueLong a = 128l;Long b = 128l;
- 本文实例讲述了C#获取任务栏显示进程的方法。分享给大家供大家参考。具体实现方法如下:private static void getWindo
- 本文实例分析了C#反射内存的处理。分享给大家供大家参考。具体分析如下:这段时间由于公司的项目的要求,我利用c#的反射的机制做了一个客户端框架