C语言基础第11天:函数

一、函数概述

函数是实现一定功能的独立代码模块,使用函数需遵循先定义后使用的原则。

使用函数的优势

  1. 可提供功能给他人使用,也可使用他人提供的函数,减少代码量。
  2. 减少重复性代码。
  3. 实现结构化(模块化)程序设计思想,即通过多文件与函数将大型任务划分为相互独立的小型任务模块。

函数的作用

  1. 代码复用:避免重复编写相同功能的代码。
  2. 模块化设计:将复杂程序拆分成多个小功能模块,每个函数负责独立任务,使代码逻辑更清晰。
  3. 便于维护和调试:单个函数功能单一,出现问题易定位和修改,无需改动整个程序。
  4. 提高开发效率:便于多人协同开发,分工明确,编写不同函数后组合成完整程序。

重要说明

C 语言程序必须包含一个 main 函数,可包含零个或多个其他函数。

二、函数分类

按来源分

  1. 库函数:由 C 语言标准库实现并提供,如scanf()printf()fgets()fputs()strlen()等。
  2. 自定义函数:需程序员自行实现,开发中大部分函数为此类。

按参数分

  1. 无参函数:调用时无需传递参数,可有可无返回值,如show_all()
  2. 有参函数:调用时需传递参数,常配套返回值使用,如printf("%d\n", 12)

按返回值分

  1. 有返回值函数:执行后返回一个值,如if (scanf("%d", &num) != 1)
  2. 无返回值函数(void):仅执行操作,不返回值。

从函数调用角度分

  1. 主调函数:主动调用其他函数的函数,main 函数只能作为主调函数。
  2. 被调函数:被其他函数调用的函数。需要注意的是,一个函数可能既是主调函数又是被调函数。

三、函数定义

语法

[返回类型] 函数名([形参列表])  // 函数头|函数首部 { 函数体语句  // 整个{}包裹的内容,{}不能省略 }

{

函数体语句  // 整个{}包裹的内容,{}不能省略 }

函数体语句  // 整个{}包裹的内容,{}不能省略 }

}

函数头各部分说明

  1. 返回类型:函数返回值的类型。
  2. 函数名:遵循标识符命名规则,不能以数字开头,只能包含大小写字母、下划线、数字。建议采用小写 + 下划线,如show_all(),或小驼峰命名法,即第一个单词首字母小写,其他单词首字母大写,如showAll()
  3. 形参列表:用于接收主调函数传递的数据,多个参数用逗号分隔,每个形参都需指明类型。

特殊说明

  1. 形参是主调函数向被调函数传递数据的载体,函数定义时系统不为形参申请内存,调用时才申请,函数返回时自动回收内存。
  2. C 语言中所有参数传递都是值传递,若要修改实参需传递指针,指针传递本质也是值传递。
  3. void类型表示空类型或无类型,无返回值时应明确使用。在 C 语言中,无返回值函数的return语句可省略。
  4. C89 标准允许省略函数返回类型标识符,默认返回 int;但 C99/C11 标准要求必须明确指定返回类型,不再支持默认 int 类型。
  5. 函数中return语句形式可为return(表达式)return 表达式
  6. 形参列表有多个参数时,即使类型相同,也需逐个说明,不能省略类型。若形参列表无参数,可不写或用void标识。
  7. C89 开始支持变长参数,需引入<stdarg.h>,语法为[返回类型] 函数名(参数列表, ...)

四、形参和实参

形参(形式参数)

函数定义时指定的参数,用于接收数据。函数调用时系统为其申请内存,返回时自动回收。

实参(实际参数)

函数调用时主调函数传递给被调函数的具体数据,可是常量、变量、表达式、带有返回值的函数等。

关键特性

  1. 类型多样性:实参类型丰富,可根据需求选用常量、变量、表达式或带返回值的函数等。
  2. 类型转换:实参和形参类型不同时,按赋值规则进行类型转换,可能导致精度丢失。
  3. 单向值传递:C 语言采用单向值传递机制,实参仅将值赋给形参,形参值的改变不影响实参。
  4. 内存独立性:实参和形参在内存中占据不同空间,形参有独立内存地址。

五、函数的返回值

  1. 不需要返回值的函数可以没有return语句,返回类型为void时,return关键字可省略。
  2. 一个函数中可有多条return语句,但同一时刻只有一条被执行。
  3. 返回类型一般应与return语句返回的数据类型一致,不一致时需符合 C 语言隐式转换规则。

六、函数的调用

调用方式

  1. 函数语句:对于无返回值的函数直接调用,如test();对于有返回值的函数,一般在主调函数中接收返回值,如int res = max(2,4)
  2. 函数表达式:如4 + max(2,4)scanf("%d", &num) != 1(c = getchar()) != '\0'
  3. 函数参数:函数可作为实参,如printf("%d", (int)fabs(number)),但作为形参需使用函数指针。

调用条件

  1. 被调用函数必须已定义。
  2. 使用库函数需在文件开头用#include包含对应头文件。
  3. 使用自定义函数且其在主调函数后面时,需在主调函数中对被调函数进行声明。

七、函数的声明

函数调用若出现在定义之前,需进行函数声明。完整的函数使用包括函数声明、函数定义和函数调用。

定义

函数声明保留函数头,便于编译系统检查,可省略形参名称,但不能省略参数类型。同一文件中,函数声明需在所有函数定义最前面,也可抽取到对应的.h文件中。

注意事项

函数定义时参数列表要和声明完全对应,且必须保留形参名称,不能省略参数的数据类型、个数和名称。

声明方式

  1. 函数头加上分号,如int add(int a, int b);
  2. 函数头加上分号,省略形参名称,如int add(int, int);

八、变量和函数底层工作原理(扩展)

变量的底层执行机制

变量本质是内存中的存储空间,底层处理涉及编译期符号解析和运行时内存分配与访问。

  1. 编译阶段:编译器为每个变量创建符号表条目,记录相关信息,全局和静态变量分配到对应段并计算偏移量,局部变量记录在栈帧中的相对位置。
  2. 运行阶段:全局 / 静态变量在程序加载时映射到内存固定位置,局部变量在函数调用时通过栈帧分配地址,动态变量通过系统调用在堆中分配内存。
  3. 访问指令:CPU 通过地址计算得到内存地址,执行加载或存储指令访问变量。

函数的底层执行机制

函数执行是指令流的跳转与栈帧管理,涉及调用、栈帧创建、参数传递和返回值处理。

  1. 编译阶段:编译器将函数体编译为机器指令存储在代码段,记录函数名与起始地址,参数和返回值传递方式由调用约定决定。
  2. 调用步骤:包括参数入栈、保存返回地址、跳转至函数入口、创建栈帧、执行函数体、返回结果、恢复栈帧并返回等环节。

关键底层概念

  1. 内存分段:分为代码段、数据段、BSS 段、栈、堆,分别存储指令、全局变量、未初始化全局变量、局部变量和函数调用信息、动态内存。
  2. 栈帧:每个函数调用对应一个栈帧,包含参数、返回地址、局部变量,由基址指针和栈指针界定。
  3. 地址绑定:变量和函数地址在编译期、加载期或运行期确定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值