快速学C语言—— 第 7 章:函数
第 7 章:函数
在现实生活中,复杂的任务通常会被分解成多个小步骤,由不同的人或部门协作完成。
编程也是如此,当程序变得复杂时,我们需要将其分解成更小、更易管理的模块。
在 C 语言中,函数就是实现这种模块化编程的基本单元。函数是一段完成特定任务的独立代码块,可以被多次调用,大大提高了代码的复用性和可维护性,同时让代码结构更清晰、便于团队协作。
7.1 什么是函数?
函数可以看作是一个 “黑盒子”:你给它一些输入(参数),它执行特定的任务,然后返回一个结果(或无返回)。
你不需要知道函数内部是如何实现的,只需要知道它的 “输入要求”(参数类型、数量)和 “输出结果”(返回值类型、功能),即可直接调用。
函数的核心优势:
- 代码复用:一次定义,可在程序多个地方调用,避免重复编写相同代码。
- 模块化编程:将复杂问题分解为多个简单任务,每个函数负责一个功能,逻辑更清晰。
- 易维护性:修改函数内部实现时,只要不改变 “输入输出规则”,所有调用它的代码都无需修改。
- 团队协作:不同程序员可分别实现不同函数,无需关注他人的代码细节,提高开发效率。
7.2 函数的定义
一个完整的函数定义包含 “函数头” 和 “函数体” 两部分,语法格式如下:
返回类型 函数名(参数列表){// 函数体:实现具体功能的代码块(可包含变量定义、执行语句等)return返回值;// 与返回类型匹配,无返回值时可省略或写 return;}各部分详细说明:
返回类型:
- 表示函数执行完毕后返回值的类型,可是
int、float、char等基本类型,或结构体、指针等复杂类型。 - 若函数不返回任何值,返回类型必须写
void(空类型)。
- 表示函数执行完毕后返回值的类型,可是
函数名:
- 函数的唯一标识符,遵循变量命名规则(字母 / 下划线开头,仅含字母、数字、下划线)。
- 命名需有描述性,见名知义(如
calculateCircleArea表示 “计算圆面积”)。
参数列表:
- 函数的 “输入”,由多个参数组成(用逗号分隔),每个参数需指定 “类型 + 名称”(如
int a, int b)。 - 无参数时,可写
void或留空(推荐写void,明确表示无参数)。 - 参数列表中的参数称为 “形式参数”(简称形参),仅在函数内部有效,用于接收外部传入的值。
- 函数的 “输入”,由多个参数组成(用逗号分隔),每个参数需指定 “类型 + 名称”(如
函数体:
- 用花括号
{}包裹的代码块,包含实现函数功能的所有语句(变量定义、循环、条件判断等)。 - 函数体内部定义的变量是局部变量,仅在函数内有效。
- 用花括号
return 语句:
- 用于返回函数结果,返回值的类型必须与 “返回类型” 一致(或可隐式转换)。
void类型函数中,return;可省略(函数执行到花括号结尾时自动返回)。
示例:实现两个整数相加的函数
#include<stdio.h>// 函数定义:计算两个整数的和(返回类型int,形参a和b为int类型)intadd(inta,intb){intsum=a+b;// 函数体内定义局部变量sumreturnsum;// 返回sum的值(与返回类型int匹配)}intmain(){intresult=add(5,3);// 调用add函数,传入实际值5和3printf("5 + 3 = %d\n",result);// 输出:5 + 3 = 8return0;}7.3 函数的声明与调用
函数的使用分为 “声明” 和 “调用” 两步:声明告诉编译器函数的 “规则”,调用则是执行函数的功能。
7.3.1 函数声明(函数原型)
函数声明的作用是告诉编译器函数的存在,明确其返回类型、函数名和参数类型,无需提供函数体。
语法格式:返回类型 函数名(参数类型1, 参数类型2, ...);(参数名可省略,仅需保留类型)
核心说明:
- C 语言要求 “先声明,后使用”:若函数定义在调用位置之后(如 main 函数之后),必须先声明,否则编译器会报错。
- 声明与定义的 “规则必须一致”:返回类型、函数名、参数数量和类型必须完全匹配(参数名可不同)。
- 声明可多次,但定义只能一次。
7.3.2 函数调用
函数调用是执行函数功能的过程,语法格式:函数名(实际参数列表);
核心说明:
- 实际参数(简称实参):传入函数的具体值(可是常量、变量、表达式),需与形参的数量、类型一一对应。
- 函数调用的结果:若函数有返回值,可将结果赋值给变量,或直接作为表达式的一部分(如
printf("%d", add(2,3)))。 void类型函数的调用:直接写函数名(实参);,无需接收返回值。
示例:声明与调用的完整用法
#include<stdio.h>// 函数声明(参数名可省略,仅保留类型)intmultiply(int,int);// 等价于 int multiply(int x, int y);voidprintMessage(char[]);intmain(){// 函数调用:实参4和5传入形参x和yintproduct=multiply(4,5);printf("4 * 5 = %d\n",product);// 输出:4 * 5 = 20// 调用void类型函数printMessage("Hello, Functions!");// 输出:Hello, Functions!return0;}// 函数定义(实现具体功能)intmultiply(intx,inty){returnx*y;// 返回乘积}voidprintMessage(charmessage[]){printf("%s\n",message);}⚠️我本人习惯使用的写法:将函数声明放在 main 函数之前,函数定义放在 main 函数之后,使代码结构更清晰(避免 main 函数过长)。
7.4 参数传递:值传递(默认方式)
C 语言中,函数参数传递的默认方式是值传递:函数接收的是实参的 “副本”(拷贝值),而非实参本身。
核心特点:修改形参的值不会影响实参,实参的原始值保持不变。
示例:交换两个变量的值(值传递无法实现)
#include<stdio.h>// 值传递示例:形参a和b是实参x和y的副本voidswap(inta,intb){inttemp=a;a=b;b=temp;printf("函数内部:a = %d, b = %d\n",a,b);// 内部交换成功}intmain(){intx=5,y=10;printf("交换前:x = %d, y = %d\n",x,y);// 输出:交换前:x = 5, y = 10swap(x,y);// 传入x和y的副本,实参本身未变printf("交换后:x = %d, y = %d\n",x,y);// 输出:交换后:x = 5, y = 10(实参未交换)return0;}运行结果:
交换前:x=5, y=10函数内部:a=10, b=5交换后:x=5, y=10-------------------------------- Process exited after0.05426seconds withreturnvalue0请按任意键继续...原理说明:
- 调用
swap(x, y)时,编译器会创建 x 和 y 的副本,赋值给形参 a 和 b。 - 函数内部仅修改 a 和 b(副本),x 和 y(原始变量)的内存地址与 a 和 b 无关,因此值不变。
- 若需在函数内修改实参的值,需使用指针传递(后续章节详细讲解)。
7.5 递归函数(⚠️了解即可)
递归函数是直接或间接调用自身的函数,核心思想是 “将大问题分解为与原问题相似的小问题”,适用于阶乘、斐波那契数列、树遍历等场景。
递归的两个核心要素:
- 终止条件:递归必须有明确的结束条件(否则会无限递归,导致栈溢出)。
- 递归调用:函数调用自身时,传入的参数需逐渐靠近终止条件。
示例 1:计算 n 的阶乘(n! = n × (n-1) × … × 1)
#include<stdio.h>longlongfactorial(intn){// 终止条件:0! = 1,1! = 1if(n==0||n==1){return1;}// 递归调用:n! = n × (n-1)!returnn*factorial(n-1);}intmain(){intnum;printf("请输入一个正整数:");scanf("%d",&num);if(num<0){printf("负数没有阶乘!\n");}else{printf("%d的阶乘是:%lld\n",num,factorial(num));}return0;}示例 2:计算斐波那契数列第 n 项(前两项为 0、1,后续每项 = 前两项之和)
#include<stdio.h>intfibonacci(intn){// 终止条件:第0项=0,第1项=1if(n==0)return0;if(n==1)return1;// 递归调用:fib(n) = fib(n-1) + fib(n-2)returnfibonacci(n-1)+fibonacci(n-2);}intmain(){intnum;printf("请输入一个正整数:");scanf("%d",&num);printf("斐波那契数列前%d项:",num);for(inti=0;i<num;i++){printf("%d ",fibonacci(i));}printf("\n");return0;}递归的优缺点:
- 优点:代码简洁,符合问题的自然逻辑。
- 缺点:多次递归调用会占用栈空间,可能导致栈溢出;重复计算较多(如斐波那契数列),效率较低,大规模问题建议用循环实现。
7.6 变量的作用域与生命周期
变量的 “作用域” 指变量能被访问的代码范围,“生命周期” 指变量从创建到销毁的时间段,二者与变量的定义位置密切相关。
7.6.1 局部变量
- 定义位置:函数内部或代码块(如
if、for内部)。 - 作用域:仅在定义它的函数 / 代码块内有效,外部无法访问。
- 生命周期:进入函数 / 代码块时创建,退出时自动销毁(内存释放)。
- 特点:未初始化时,值为随机 “垃圾值”;不同函数中的局部变量可重名(互不影响)。
7.6.2 全局变量
- 定义位置:所有函数外部(通常在程序开头)。
- 作用域:整个程序(所有函数都可访问、修改)。
- 生命周期:程序启动时创建,程序结束时销毁。
- 特点:未初始化时,默认值为 0;全局变量会占用静态内存,不推荐滥用(易导致命名冲突、代码耦合度高)。
示例:局部变量与全局变量的对比
#include<stdio.h>// 全局变量:所有函数可访问,默认值为0intglobal_count=0;voidincrement(){// 局部变量:仅increment函数内有效,每次调用时重新初始化intlocal_count=0;local_count++;// 局部变量自增global_count++;// 全局变量自增printf("局部变量:%d,全局变量:%d\n",local_count,global_count);}intmain(){increment();// 输出:局部变量:1,全局变量:1(local_count重新初始化)increment();// 输出:局部变量:1,全局变量:2(local_count重新初始化)increment();// 输出:局部变量:1,全局变量:3(local_count重新初始化)return0;}7.7 函数应用实例
实例 1:计算圆的面积
#include<stdio.h>// 函数声明:接收圆的半径,返回面积(double类型)doublecircleArea(doubleradius);intmain(){doubler,area;printf("请输入圆的半径:");scanf("%lf",&r);area=circleArea(r);// 调用函数,接收返回值printf("半径为%.2f的圆面积是:%.2f\n",r,area);return0;}// 函数定义:计算圆面积doublecircleArea(doubleradius){constdoublePI=3.14159;// 局部常量,仅函数内有效returnPI*radius*radius;}运行结果:
请输入圆的半径:3.5 半径为3.50的圆面积是:38.48 -------------------------------- Process exited after6.234seconds withreturnvalue0请按任意键继续...实例 2:求三个整数的最大值
#include<stdio.h>// 函数声明:接收三个int参数,返回最大值intmaxOfThree(inta,intb,intc);intmain(){intx,y,z,max;printf("请输入三个整数:");scanf("%d %d %d",&x,&y,&z);max=maxOfThree(x,y,z);printf("最大值是:%d\n",max);return0;}// 函数定义:比较三个数的最大值intmaxOfThree(inta,intb,intc){intmax=a;// 假设a是最大值if(b>max){max=b;}if(c>max){max=c;}returnmax;}运行结果:
请输入三个整数:153最大值是:5 -------------------------------- Process exited after5.436seconds withreturnvalue0请按任意键继续...7.8 函数设计的最佳实践
编写高质量函数的核心原则,帮助你写出规范、易维护、高复用的代码:
- 单一职责:每个函数只完成一个明确的任务(如
calculateArea仅计算面积,不负责输入输出)。 - 描述性命名:函数名和参数名需见名知义,避免无意义命名(如
func1、x)。 - 合理参数:参数数量不宜过多(建议≤5 个),过多会增加调用难度;参数类型需明确,避免使用无意义的
void*(通用指针)。 - 明确返回值:返回值类型需与函数功能匹配(如计算和返回
int,计算面积返回double);void函数避免使用return返回值。 - 添加注释:为函数添加注释,说明功能、参数含义、返回值、异常情况(如
// 参数radius:圆的半径,需≥0)。 - 避免副作用:函数应尽量通过 “参数输入” 和 “返回值输出” 实现功能,避免修改全局变量(副作用),降低代码耦合度。
- 错误处理:考虑异常情况(如除数为 0、参数非法),通过返回特定值或提示信息处理(如
return -1表示参数错误)。
笔记:
- 函数的核心结构:返回类型 + 函数名 + 参数列表 + 函数体 + return 语句,缺一不可(void 函数可省略 return)。
- 函数声明与定义的区别:声明仅告诉编译器 “函数规则”,定义实现具体功能;必须先声明(或定义)再调用。
- 参数传递默认是值传递:形参是实参的副本,修改形参不影响实参;需修改实参时需用指针传递。
- 变量作用域:局部变量(函数 / 代码块内有效)、全局变量(整个程序有效);局部变量未初始化是垃圾值,全局变量默认值为 0。
- 递归函数需满足两个条件:明确的终止条件 + 递归调用靠近终止条件,否则会无限递归。
- 函数设计原则:单一职责、见名知义、参数精简、避免副作用,提高代码复用性和可维护性。
