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