学嵌入式和C语言编程 | 学习日记Day10:函数、函数的调用,值传递、递归、一维整型数组做函数参数

前言

今天学习 C 语言函数,函数是实现代码模块化最重要的工具。使用函数可以降低程序耦合度,减少重复代码,提高代码复用能力。在嵌入式项目开发中,我们会把传感器读取、外设驱动、数据处理全部封装成函数,方便调用与维护。本次学习内容包含函数定义、调用、声明、值传递、递归调用,以及数组作为函数参数的传参规则。

一、函数基础

函数优点:降低程序的耦合性(关联度),减少重复代码,让程序模块化,增强代码复用性。

1. 函数定义

格式:

c
返回值类型 函数名(形参表)
{
函数体;
}

  1. 返回值:可以是 int、char 等基础数据类型,也可以是构造数据类型。用来返回函数最终运算结果。void空类型:函数执行完毕,不需要返回任何结果时使用 void 作为返回值。
  2. 函数名:标识符,遵守标识符命名规则,尽量见名知意。习惯以 “动词”+“宾语” 命名,例如get_max_val();获取最大值。
  3. 形参表:函数执行过程中需要用到的条件。
    格式:数据类型 形参名, 数据类型 形参名, 数据类型 形参名 …

注意:当函数有明确返回值类型,必须写return;如果不写 return,系统返回内存里随机垃圾值。
如果返回值为 void,可以不写 return,也可以写return;

2. 函数调用

格式:

c
函数名(实参表);

实参:传递给函数的实际条件,可以是常量、变量、表达式。

实参要求:

  1. 实参的个数和形参的个数必须一一对应。
  2. 实参和形参的数据类型要一致或者相互兼容。

概念区分:

  • 主调函数:发起调用的函数
  • 被调函数:被别人调用的函数

函数执行规则:

  1. 函数不被调用则不会执行,调用几次就执行几次。
  2. 实参和形参名字可以相同,也可以不同。

3. 函数的声明

出现场景:被调函数写在主调函数后面,编译器报implicit declaration隐式声明警告

两种声明方式:

  1. 在主调函数前面进行函数声明:返回值类型 函数名(形参表);
  2. 多文件开发,其他文件想要调用本文件函数,也需要做函数声明。

4. 函数调用过程(栈空间工作机制)

  1. 保护现场:函数被调用时,把调用位置的下一条指令地址保存到栈空间,栈遵循先进后出
  2. 恢复现场:被调函数执行结束,从栈取出保存的地址,CPU 回到调用处,继续执行后续代码。

二、函数的传参 —— 值传递

值传递:实参向形参做拷贝,实参、形参拥有各自独立内存空间。

值传递核心特点:

  1. 单向传递,只把实参拷贝给形参。
  2. 在被调函数内部修改形参,不会影响主调函数里面原来的实参变量

项目

值传递说明

内存空间

实参、形参各自独立内存

数据流向

只能实参 → 形参,单向拷贝

修改效果

被调函数修改形参,主调函数变量不受影响

三、函数的递归调用

  1. 定义:函数直接或者间接调用自身。
  2. 本质:属于循环的一种实现方式。
  3. 重点规则:递归必须设置递归出口,没有出口就会变成死循环,造成栈溢出。
  4. 缺点:递归调用系统资源开销大于普通循环,消耗栈内存,嵌入式开发不建议深度递归。

简单递归示例逻辑,求累加:

Plain Text
main() → get_sum(5) → get_sum(4) → get_sum(3) → get_sum(2) → get_sum(1)递归出口

get_sum(5) → get_sum(4)+1 → get_sum(3)+1 → get_sum(2)+1 → get_sum(1)+1→return 1;

四、一维数组作为函数参数

数组名本质是数组首元素的地址。

1. 传参书写格式

函数定义:

c
返回值类型 函数名(int 数组名[], int len)
{
//函数内部数组使用方式和外部完全一样
}

函数调用:

c
函数名(数组名,数组长度);

2. 数组做函数参数四条注意点

  1. 形参书写格式:int a[],方括号内常量可以写,也可以省略。
  2. 数组名传参,传递的不是全部数组拷贝,传递的是数组首元素地址,形参本质接收一个起始地址。
  3. 只传数组名无法得到数组长度,必须额外把数组长度 len 当做参数传入函数
  4. 数组作为函数参数传递给被调函数后,函数内部的使用方法和外部的使用方法一样,除了sizeof(数组名);

值传递 vs 数组传参对比

传参方式

传递内容

修改是否影响外部原始数据

普通变量值传递

变量值拷贝

不影响外部变量

数组名作为参数

数组首元素地址

函数内修改数组,外部数组同步改变

今日学习总结

  1. 函数三要素:返回值、函数名、形参列表;void 代表无返回值;有返回值的函数必须使用 return。
  2. 实参与形参个数、类型需要匹配;值传递是单向拷贝,修改形参不会改变主调函数变量。
  3. 函数写在调用之后,必须提前做函数声明,消除隐式声明警告。
  4. 函数调用依靠栈空间完成保护现场、恢复现场,栈遵循先进后出。
  5. 递归就是函数调用自己,一定要写递归出口,递归栈开销大,嵌入式尽量少用深度递归。
  6. 数组名做函数参数传递的是首元素地址,不是完整数组拷贝,必须额外传入数组长度;函数内部修改数组,外部原始数组会被同步修改。
  7. 工程实践:嵌入式开发中,传感器处理、外设驱动全部封装为函数,提升代码可读性、复用性。