Days 9 函数初学

一、为什么要学函数?

刚学 C 语言的时候,所有代码全堆在main里,写重复功能要复制粘贴一大段,代码又乱又难改。函数就是用来解决这个问题的! 函数两大好处:

  1. 代码复用:同一个功能只写一次,想用直接调用,不用反复复制代码;
  2. 模块化拆分:把复杂程序拆成一个个独立小功能,逻辑清晰,找 bug、改代码都很方便。

整个 C 程序必须有且只有一个main函数,程序从main开始执行,其他都是我们自己写的自定义函数。

二、函数完整三要素:定义、调用、声明

1. 函数定义(写函数本体,实现功能)

标准语法:

拆解每一部分:

  1. 返回值类型:函数执行完给外面返回的数据类型;没有返回值写void,不用写return
  2. 函数名:命名规则和变量一样,只能字母、数字、下划线,不能数字开头,尽量见名知意,比如is_leap_year判断闰年;
  3. 形参(形式参数):函数接收外部数据的 “占位符”,只有调用函数时才会分配内存,函数结束直接销毁;
  4. 函数体{}:写具体逻辑;
  5. return:结束函数,把结果传给调用处;void函数可以省略 return。

举个简单例子:判断闰年函数

2. 函数调用(使用写好的函数)

语法:函数名(实参1, 实参2);

  • 实参(实际参数):调用时真实传进去的数据,可以是常量、变量、表达式;
  • 规则:实参的个数、顺序、数据类型必须和形参完全对应;
  • C 语言只有值传递:只是把实参的值拷贝一份给形参,函数内部修改形参,完全不会影响外面的实参。

调用上面闰年函数:

这里main主调函数is_leap_year被调函数

3. 函数声明(函数原型,解决编译报错)

C 语言代码从上往下逐行编译,如果函数定义写在 main 函数下方,编译器读到调用代码时找不到函数,直接报错。 解决办法:在 main 上方提前声明函数,告诉编译器这个函数存在。

声明格式(去掉函数体,末尾加分号):

完整带声明示例:

小总结:

  • 定义在 main 上方:不用声明;
  • 定义在 main 下方:必须提前声明。

三、变量的作用域 & 生命周期(函数配套重点)

1. 局部变量

定义在函数内部、大括号{}里的变量,就是局部变量:

  • 作用域:只能在当前大括号内使用,外面访问不到;
  • 存储位置:栈区;
  • 生命周期:进入代码块分配内存,执行完大括号直接销毁;
  • 默认存储类型auto(可以省略不写);
  • 未初始化:值是随机垃圾值,一定要手动初始化。

2. 全局变量

定义在所有函数外面的变量:

  • 作用域:整个.c文件所有函数都能访问;
  • 存储位置:静态数据区;
  • 生命周期:程序运行全程存在,程序结束才销毁;
  • 未初始化:默认自动赋值 0;
  • 缺点:所有函数都能修改,容易乱改数据,尽量少用。

四、四种变量存储类型(存储位置 / 特性)

1. auto(自动变量,局部默认)

  • 仅局部变量可用;
  • 存栈区;函数结束内存释放;
  • 不初始化 = 随机值。

2. register(寄存器变量)

  • 局部变量专用;
  • 优先存 CPU 寄存器,读取更快;寄存器满了自动降级为 auto 栈变量;
  • 不能取地址。

3. static(静态变量,分两种)

(1)static 修饰局部变量
  • 原本局部变量存栈,static 后放到静态数据区
  • 生命周期:第一次调用函数时初始化一次,之后函数调用不会重新初始化,程序结束才销毁;
  • 作用域不变:还是只能当前函数内使用;
  • 不初始化默认 0;适合计数场景。

示例:统计函数调用次数

2)static 修饰全局变量 / 函数
  • 限制作用域:只能在当前.c文件使用,其他文件无法访问,避免多文件命名冲突。

4. extern(外部声明)

只用来声明变量 / 函数,不分配内存;告诉编译器:这个变量 / 函数定义在别的文件或文件下方。

五、函数传参核心:值传递

C 语言所有函数传参都是单向值传递

  1. 调用函数时,把实参的值复制一份给形参;
  2. 实参、形参是两块独立内存,互不干扰;
  3. 在函数内部修改形参,外部实参完全不会变化。

错误示例(交换两个数字失败):

原因:只是拷贝了 a、b 的值给 x、y,函数内交换的只是副本,本体没变。

六、课堂作业实战(封装函数练习)

作业 1:封装函数,传入年月日,返回当年第几天

思路:

  1. 封装闰年判断函数is_leap_year
  2. 封装get_dayofyear(int y,int m,int d),累加前面月份总天数,加上当日;
  3. 二月天数根据闰年判断是 28/29 天。

作业 2:字符串倒置函数封装(不使用库函数)

七、新手踩坑总结

  1. 函数定义在 main 下方,忘记写函数声明,编译报错;
  2. return 返回值和函数定义的返回类型不一致,会隐式转换丢失数据;
  3. 局部变量不初始化直接使用,读取随机垃圾值;
  4. static 局部变量误以为每次调用都会重新初始化;
  5. 传参误以为修改形参能改变实参,不清楚 C 语言只有值传递;
  6. 函数名和库函数重名(比如 printf),造成程序异常;
  7. void 函数写 return 数字,语法错误。

八、学习感悟

函数是 C 语言从 “简单代码” 进阶 “模块化程序” 的分水岭,学会封装函数之后,写复杂逻辑清晰很多。尤其是 static 静态局部变量、值传递这两个点,一开始很难理解,多写几次计数、交换的练习就能吃透。后续学指针之后还能解决 “值传递无法修改外部变量” 的问题,继续加油!