既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
一、前言
在C语言中指针与函数的结合是一件很麻烦的事情,用起来总是丈二和尚,摸不着头脑,特别是在参数传递,申请空间等,把我折磨的是一头雾水。也许大家对于这些知识都是一知半解(除过一些大佬O),没有真正的去搞透彻,一问我都会(这就是我),但是真正引用的时候就会出现很多问题,而且百度的时候又看不懂,所以为了帮助大家解决这个问题(实际上是自己不会),在这里进行一个全面的介绍(形参和实参与传值和传址)。
二、函数的形参和实参
🍑实参
知识点1:实参(实际参数)
🔑在调用有参函数时,函数名后面括号中的参数被称为“实参”,通俗的来讲,就是我们真实传递给函数的参数。
🔑实参可以表示为:常量、变量、表达式、函数等。
🔑无论实参是何种类型的量,在进行函数调用时,他们必须有确定的值,以便于把这些值传递给形参。
💦代码举例解析
#include <stdio.h> #include <math.h> #include <stdlib.h> #include <string.h> int add(int x, int y) //加法函数 { int sum = 0; sum = x + y; return sum; } int main() { int a = 0, b = 0; printf("请输入a,b的值:\n"); scanf("%d %d", &a, &b); // 真实传递给函数的参数就是实参 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; }
💦结果输出
🍎形参
知识点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)
中的a
和b
叫做实参(实际参数)**而当我们进到**
Swap1
函数内部的时候,Swap1(int x, int y)
中的x
和y
叫做形参(形式参数)**所以我们在调用**
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函数
**,这种方式叫:传址调用
💦概念理解
想必大家看了我上面的讲解,对传值与传址调用有了一定的了解,下面我将给大家更加详细的总结。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
);
printf(“交换后: a=%d b=%d\n”, a, b);
return 0;
}**🔑**当我去调用**`Swap1函数`**的时候,我把**`a、b`**本身的变量传到**`Swap1函数`**,这种方式叫:**传值调用** **🔑**当我去调用**`Swap2函数`**的时候,我把**`a、b`**的**地址**传到**`Swap2函数`**,这种方式叫:**传址调用**
💦概念理解
想必大家看了我上面的讲解,对传值与传址调用有了一定的了解,下面我将给大家更加详细的总结。
[外链图片转存中…(img-RgUjqLhr-1715320809200)]
[外链图片转存中…(img-dS7hDXFz-1715320809200)]
[外链图片转存中…(img-qHaOrS4f-1715320809200)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新