C语言深度解析:函数的形参和实参与传值和传址(多角度分析,让你有一种相见恨晚的感觉!!(1)

// 真实传递给函数的参数就是实参
int sum1 = add(1, 2); // 实参可以是常量
printf(“输出1 + 2 的和:”);
printf(“%d\n”, sum1);

int sum2 = add(a, b); // 实参可以是变量
printf(“输出a + b 的和:”);
printf(“%d\n”, sum2);

int sum3 = add(a + b, a - b); //实际参数可以是表达式
printf(“输出(a+b)与(a-b)的和:”);
printf(“%d\n”, sum3);

int sum4 = add(add(a, b), 6); //实际参数可以是函数
printf(“输出函数(2+3)+5的和:”);
printf(“%d\n”, sum4);
return 0;
}


**💦结果输出**


![](https://img-blog.csdnimg.cn/cd3d8e94e58a485589a4cf8acaa80551.png)



🍎形参

知识点2:形参(形式参数)

🔑形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的和过程中才实例化(分配内存单元),所以叫形式参数。

🔑形参出现在函数定义中,在整个函数体内都可以使用,离开函数则不能使用

🔑形参没有确定的值

⭐通俗的来讲:形参定义时,编译系统并不为其分配存储空间,也没有初始值,只是在函数调用时,临时分配存储空间,接受来自实参的值。函数调用结束,内存空间释放,值消失。

💦代码举例解析:还是用上面的代码测试

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
int subtraction(int x, int y) // 注意:此时在subtraction函数里的 x,y就是形式参数
{
	return x - y;
}
int main()
{
	int a, b;
	printf("请输出a,b的值:\n");
	scanf("%d %d", &a, &b);
	printf("请输出a-b的值:");
	int sum = 0;
	sum = subtraction(a, b);
	printf("%d\n", sum);
	return 0;
}

🔑注意:

形参只有在函数中有效

▶****形参在函数调用之前还未创建

▶****函数调用过程中,形参才被实例化

▶****函数调用结束,形参生命周期结束,形参会被立刻销毁

💦结果输出

🍇形参与实参之间的区别

知识点3:形参与实参之间的区别

🍐形参与实参之间的联系(重点!!!!)

知识点4:形参与实参之间的联系

💦在讲到形参与实参的联系,这里就牵扯出了函数调用的两大类(不同的类别,形参与实参之间的联系就不同)

🔑传值调用

✨****官方理解: 函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参

✨****通俗的理解:形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参)

🔑传址调用

✨****官方理解

▶ 传值调用是把函数外部创建的内存地址传递给函数参数的一种调用函数的方式

▶ 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量

✨****通俗的理解:形参为指向实参地址的指针,当对形参进行指向操作时,就相当于实参本身进行的操作。

💦讲到这里大家肯定会有疑问,那么什么时候用传值调用,什么时候用传址调用,怎么用呢?

接下来我将回答大家依次解答。

三、函数的传值和传址(超重点!!!)

🍒例题分析

**在理解传值调用传址调用的之前我们先来看一道题目,**从题目中区分他们之间的联系

**例题:**写一个函数可以交换两个整型变量的内容

例如:

交换前:20 30
交换后:30 20

代码展示:用函数的方式书写:

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
//交换函数  
void Swap1(int x, int y)  // 其中 x ,y 是形参
{
	int z = 0;
	z = x;
	x = y;
	y = z;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	//交换两个变量
	printf("交换前: a=%d b=%d\n", a, b);

	Swap1(a, b);        // 其中 a , b 是实参

	printf("交换后: a=%d b=%d\n", a, b);

	return 0;
}

💦看看运行结果:

20 30   // 输入 a b 的值

交换前:a=20 b=30
交换后:a=20 b=30

啊,看到这个结果,大家应该会大吃一惊吧,我们不是已经通过函数进行数值交换了吗?

怎么会出现的这样的结果呢?

不急,等我给大家娓娓道来哦。

🍍错误分析

分析:

主函数里面的**Swap1(a, b)中的ab叫做实参(实际参数)**

而当我们进到**Swap1函数内部的时候,Swap1(int x, int y)中的xy叫做形参(形式参数)**

所以我们在调用**Swap1函数**的时候,是不是把实际参数传给了形式参数

🔑这个时候结论是这样子的:

▶  当函数调用的时候,实参传给形参,形参其实是实参的一份临时拷贝

🔑此时大家又要问了,那什么是临时拷贝呢?

▶  形参有自己的独立空间,我们的操作只是把实参的内容拷贝一份,放到形参里面去了

🔑 所以我们想要的答案出现了

▶ 我们将实参的值拷贝给了形参

▶ 但是在 swap1() 函数中 形参的值发生了改变(进行了数值互换),可是实参的值却没有发生改变。

▶ 在swap1() 函数结束时,也无法将转换的值返回给实参 因为void():无返回值函数

▶ 根据形参的特性:形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

▶ 所以最终导致,实参并没有发生变化。

💦于此同时我将上面的代码进行改写,这个大家就能更好的理解,为什么会出错。

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
void Swap1(int x, int y)
{
	int z = 0;
	z = x;
	x = y;
	y = z;
	printf("inside:形参为x,y\nx=%d,地址为%d\ny=%d,地址为%d\n\n", x, &x, y, &y);
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);

	//交换两个变量
	printf("before:实参为a,b\na=%d,地址为%d\nb=%d,地址为%d\n\n", a, &a, b, &b);

	Swap1(a, b);

	printf("after:实参为a,b\na=%d,地址为%d\nb=%d,地址为%d\n\n", a, &a, b, &b);

	return 0;
}

💦查看输出结果

20 30
before:实参为a,b
a=20,地址为-401606620
b=30,地址为-401606588

inside:形参为x,y
x=30,地址为-401606672
y=20,地址为-401606664

after:实参为a,b
a=20,地址为-401606620
b=30,地址为-401606588

🔑 通过结果中的展示,发现从形参x和y的地址来看,传递的是实参a和b的拷贝。对拷贝的修改不会改变实参的值

最终的结论:

🔑****当函数调用的时候,实参传给形参,形参其实是实参的一份临时拷贝

注意:此时大家也会发现上面的代码实参a ,b传输的是 数值 **,**这种传递就叫做 值传递

🥝 错误修改

修改:

很明显,刚刚那个代码是错误的,那么如何修改呢?

在回想一下我们写过的函数:

写了一个**Swap1函数,把a、b的值传给了x、y以后,x、y的空间和a、b**的空间是独立的空间。


所以对
a、b
的修改是不会影响
x、y
的,它们之间是
没有建立连接的

那么我们怎么让它们之间建立联系呢?

**🔑 答案是 :指针

💦利用指针进行代码改写**

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
void Swap2(int *pa, int *pb)   // 其中 *pa *pb 是形参
{
	int z = 0;
	z = *pa;
	*pa = *pb;
	*pb = z;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	//交换两个变量
	printf("交换前: a=%d b=%d\n", a, b);

	Swap2(&a, &b);     // 此时传出过去的实参是 a,b数值的地址

	printf("交换后: a=%d b=%d\n", a, b);

	return 0;
}

💦察看运行结果

20 30

交换前:a=20 b=30
交换后:a=30 b=20

💦代码分析

我们先创建了 a , b变量,a , b变量里面放入 20、30。

我们将 a 的地址取出来(实参),放到 pa指针变量(形参)里面去。

同样我们将 b 的地址取出来(实参),放到 pb指针变量(形参)里面去。

此时地址 pa 就能找到 a, 地址 pb 就能找到 b。

那么 *pa = a  , *pb = b  ,在定义一个z变量,用于交换 *pa 和*pb。

💦代码图解

✨得出结论**:形参为指向实参地址的指针,当对形参进行指向操作时,就相当于实参本身进行的操作。**

注意:此时大家也会发现上面的代码实参a ,b传输的是 地址**,**这种传递就叫做 址传递。

🍋 深入理解 值传递与址传递

我们结合连两个代码一起来看看。

#include <stdio.h>
#include <stdlib.h>
void Swap1(int x, int y)
{
	int z = 0;
	z = x;
	x = y;
	y = z;
}

void Swap2(int *pa, int *pb)
{
	int z = 0;
	z = *pa;
	*pa = *pb;
	*pb = z;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);

	//交换两个变量
	printf("交换前: a=%d b=%d\n", a, b);

	Swap1(a, b);

	Swap2(&a, &b);

	printf("交换后: a=%d b=%d\n", a, b);

	return 0;
}

🔑当我去调用**Swap1函数的时候,我把a、b本身的变量传到Swap1函数**,这种方式叫:传值调用

🔑当我去调用**Swap2函数的时候,我把a、b地址传到Swap2函数**,这种方式叫:传址调用

💦概念理解

想必大家看了我上面的讲解,对传值与传址调用有了一定的了解,下面我将给大家更加详细的总结。

❓如何在函数中区分 什么是传值调用,什么是传址调用呢?(估计细心的大家已经发现了)

🔑  当在主函数中,调用其他函数,实参为 数值 的时候 此时的函数调用被称为:传值调用

例如:

int main()
{
   int a , b;
   scanf("%d %d",&a,&b); 
   add(a,b);  //  传值调用
   
   int *x = &a;
   int *y = &b;
   add(x,y);  // 传值调用
}

注意:在代码中add(x,y),使用实参 x,y 是值传递**,传递的****是指针的值,而不是地址。**

🔑****当在主函数中,调用其他函数,实参为 地址 的时候 此时的函数调用被称为:传值调用

如何判断是地址呢?

在实参里面有取地址符(&)存在的时候,就证明是地址

例如:

int main()
{
   int a , b;
   scanf("%d %d",&a,&b); 
   add(&a,&b);  //  传址调用
   
   int *x = &a;
   int *y = &b;
   add(&x,&y);  // 传址调用
}

❓在我们自己写代码的时候,何时使用传值调用,何时使用传址调用呢?

🔑 当要使用的函数,是对 实参本身 进行改变时--------使用传址调用

注意:传址调用,大多数 无返回值 用 void();

// .写一个函数,每调用一次这个函数,就会将 num 的值增加1。
#include <stdio.h>
#include <stdlib.h>
void Add(int* p) {
	(*p)++;
}
int main() {
	int num = 0;
	Add(&num);//1
	printf("%d\n", num);
	Add(&num);//2
	printf("%d\n", num);
	Add(&num);//3
	printf("%d\n", num);
	Add(&num);//4
	printf("%d\n", num);
 
	return 0;
}

🔑 当要使用的函数,只是对实参的值做 加减乘除之类 的操作时---------使用传值调用

注意:传值调用,大多数需要加上返回值 return ;

#include <stdio.h>
int add(int n, int m)
{
	return n + m;
}
int main()
{
	int a = 10;
	int b = 20;
	int sum = add(a, b);
	printf("sum=%d\n", sum);  // sum = 30
	return 0;
}

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

return 0;
}


**🔑 当要使用的函数,只是对实参的值做 加减乘除之类 的操作时---------使用传值调用**


**注意:传值调用,大多数需要加上返回值 return ;**



#include <stdio.h>
int add(int n, int m)
{
return n + m;
}
int main()
{
int a = 10;
int b = 20;
int sum = add(a, b);
printf(“sum=%d\n”, sum); // sum = 30
return 0;
}




[外链图片转存中…(img-lRh4rpbD-1714400138223)]
[外链图片转存中…(img-35kpYv2u-1714400138223)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值