大家好!今天我们要一起探索C语言中最有趣、最强大的部分——函数。别被这个名字吓到,它其实就像你生活中的小工具,比如榨汁机、微波炉,帮你把复杂的任务变得简单!
🎯 第一部分:函数是什么?为什么要用它?
想象一下这个场景:
你想喝一杯苹果汁,可以:
- 没有函数:每次想喝都去买苹果、洗苹果、切苹果、榨汁、清洗机器……
- 有函数:买一台榨汁机(函数),以后想喝时,只要放入苹果(输入),按下按钮(调用),就得到果汁(输出)!
函数就是C语言中的“榨汁机”——把一段常用的代码打包起来,起个名字,以后想用时直接“调用”就行!
函数的三大好处:
- 代码复用:写一次,用无数次
- 逻辑清晰:大程序拆成小模块,容易理解
- 便于维护:修改只需改一处
🧩 6.1 函数的定义——创造你的第一个“小工具”
6.1.1 定义函数:就像写家电说明书
// 语法模板:返回值类型 函数名(参数列表){// 函数体——这里写这个函数要做什么return返回值;// 如果有返回值的话}// 实际例子:创建一个“加法器”函数intadd(inta,intb){// 函数名叫add,需要两个整数参数intresult=a+b;// 函数体:计算a+breturnresult;// 返回计算结果}翻译成大白话:
· int add:我要做一个叫“add”的工具,它会给我一个整数结果
· (int a, int b):使用前需要给我两个整数,我管它们叫a和b
· { … }:大括号里是这个工具的工作步骤
· return result:工作完成后,我把结果交出来
6.1.2 函数的参数:给函数的“食材”
参数就是调用函数时传给它的数据,就像给榨汁机放入水果:
// 无参数的函数:像自动售货机,按按钮就出固定商品voidsayHello(){printf("Hello, World!\n");}// 一个参数的函数:像单口味榨汁机voidprintNumber(intnum){printf("数字是:%d\n",num);}// 多个参数的函数:像多功能料理机intmultiply(intx,inty,intz){returnx*y*z;}6.1.3 函数的返回值:函数给你的“成果”
// 有返回值的函数:会给你一个结果floatcalculateBMI(floatweight,floatheight){floatbmi=weight/(height*height);returnbmi;// 把计算结果返回给调用者}// 无返回值的函数:只做事,不交结果voidprintStars(intcount){for(inti=0;i<count;i++){printf("*");}printf("\n");// 这里没有return语句,或者用 return;}返回值类型小贴士:
· int、float、char等:返回对应类型的数据
· void:不返回任何值(像只执行任务不产出的工具)
📞 6.2 函数的调用——使用你创造的“工具”
6.2.1 函数调用:按下“开始按钮”
定义了函数后,怎么使用它呢?超简单!
#include<stdio.h>// 1. 先定义一个函数(创造工具)intadd(inta,intb){returna+b;}intmain(){// 2. 在main函数中调用它(使用工具)intsum1=add(3,5);// 传入3和5,得到8printf("3+5=%d\n",sum1);intsum2=add(10,20);// 再次使用,传入10和20printf("10+20=%d\n",sum2);// 也可以直接使用返回值printf("100+200=%d\n",add(100,200));return0;}调用过程就像这样:
main函数:嘿,add函数!帮我算一下3+5! add函数:好的,收到3和5,正在计算... 结果是8,返回给你! main函数:谢谢!我收到了8。6.2.2 函数原型:先声明,后使用
有时候函数定义在main后面,需要先“打个招呼”:
#include<stdio.h>// 函数原型声明:告诉编译器“后面会有这个函数”intadd(inta,intb);// 注意这里有个分号!intmain(){printf("5+7=%d\n",add(5,7));// 现在可以调用了return0;}// 函数的实际定义(实现)intadd(inta,intb){returna+b;}为什么需要原型声明?
想象你要去朋友家,先打电话说一声(原型声明),到了就能直接进门。如果不打电话直接去(不声明),可能会吃闭门羹(编译错误)。
6.2.3 函数的嵌套调用和递归调用
嵌套调用:工具里使用工具
#include<stdio.h>// 工具1:判断是否为正数intisPositive(intnum){if(num>0)return1;elsereturn0;}// 工具2:计算绝对值intabsolute(intnum){if(isPositive(num)){// 嵌套调用:在absolute里调用isPositivereturnnum;}else{return-num;}}intmain(){printf("-5的绝对值是:%d\n",absolute(-5));return0;}递归调用:镜子中的镜子
递归就是函数调用自己,就像俄罗斯套娃:
#include<stdio.h>// 计算n的阶乘:n! = n × (n-1) × (n-2) × ... × 1intfactorial(intn){if(n<=1){return1;// 终止条件:最小套娃}else{returnn*factorial(n-1);// 调用自己!}}intmain(){printf("5的阶乘是:%d\n",factorial(5));return0;}递归执行过程(计算factorial(3)):
factorial(3) → 3 × factorial(2) → 2 × factorial(1) → 1 (遇到终止条件) → 2 × 1 = 2 → 3 × 2 = 6 结果:6⚠️ 递归注意事项:
- 必须有终止条件(否则无限循环!)
- 每次调用问题规模要减小
- 递归层数不能太深(可能栈溢出)
📊 6.3 数组作为函数参数——批量处理数据
6.3.1 数组元素作为参数:一次送一个
voidprintNumber(intnum){printf("%d ",num);}intmain(){intscores[5]={85,90,78,92,88};// 把数组的每个元素单独传给函数for(inti=0;i<5;i++){printNumber(scores[i]);}return0;}6.3.2 数组名作为参数:把整个数组送过去
// 参数写成 int arr[] 或 int *arrvoidprintArray(intarr[],intsize){for(inti=0;i<size;i++){printf("%d ",arr[i]);}printf("\n");}intsumArray(intarr[],intsize){inttotal=0;for(inti=0;i<size;i++){total+=arr[i];}returntotal;}intmain(){intnumbers[5]={1,2,3,4,5};printArray(numbers,5);// 输出整个数组printf("总和:%d\n",sumArray(numbers,5));return0;}重要理解:
当数组作为参数时,传递的是数组首地址,不是整个数组的拷贝。所以:
· 节省内存
· 函数内修改数组会影响原数组
voidchangeFirstElement(intarr[]){arr[0]=999;// 会修改原数组!}intmain(){intmyArray[3]={1,2,3};changeFirstElement(myArray);printf("%d\n",myArray[0]);// 输出999,不是1!return0;}6.3.3 多维数组作为参数
// 二维数组作为参数,必须指定列数voidprintMatrix(intmatrix[][3],introws){for(inti=0;i<rows;i++){for(intj=0;j<3;j++){printf("%d ",matrix[i][j]);}printf("\n");}}intmain(){inttable[2][3]={{1,2,3},{4,5,6}};printMatrix(table,2);return0;}🏠 6.4 变量的作用域——谁在哪能被看见
6.4.1 局部变量:房间里的私人物品
voidfunctionA(){intx=10;// x只在functionA内有效printf("A中的x:%d\n",x);}voidfunctionB(){intx=20;// 这是另一个x,和functionA的x无关printf("B中的x:%d\n",x);}intmain(){intx=5;// main函数自己的xprintf("main中的x:%d\n",x);functionA();functionB();// 这里只能访问main的x,不能访问functionA或functionB的xreturn0;}局部变量特点:
· 在函数内部定义
· 只在定义它的函数内有效
· 不同函数可以有同名局部变量(互不影响)
· 函数执行结束,局部变量被销毁
6.4.2 全局变量:客厅里的公共物品
#include<stdio.h>intglobalCount=0;// 全局变量,在所有函数外定义voidincrement(){globalCount++;// 任何函数都可以修改全局变量printf("计数:%d\n",globalCount);}voidreset(){globalCount=0;// 另一个函数也能访问printf("已重置\n");}intmain(){increment();// 计数:1increment();// 计数:2reset();// 已重置increment();// 计数:1return0;}全局变量特点:
· 在所有函数外部定义
· 从定义位置开始到程序结束都有效
· 任何函数都可以访问和修改
· 未初始化时自动设为0
⚠️ 全局变量使用建议:
少用!因为它们破坏了函数的独立性,让程序难以理解和维护。
🗃️ 6.5 变量的存储类别——数据怎么保存
6.5.1-2 自动变量(auto):默认的存储方式
voiddemo(){autointx=5;// auto通常省略,因为默认就是autointy=10;// 等价于 auto int y = 10;}自动变量特点:
· 进入函数时创建
· 离开函数时销毁
· 每次进入函数都重新初始化
6.5.3 静态局部变量(static):有记忆的变量
#include<stdio.h>voidcounter(){staticintcount=0;// static局部变量count++;printf("这是第%d次调用\n",count);}intmain(){counter();// 第1次调用counter();// 第2次调用(count保持为1,不会重新初始化为0)counter();// 第3次调用return0;}静态局部变量特点:
· 只初始化一次(第一次进入函数时)
· 函数结束后不销毁,保留值
· 只能在定义它的函数内访问
6.5.4 寄存器变量(register):建议放CPU寄存器
voidcalculate(){registerinti;// 建议编译器把i放在寄存器中for(i=0;i<10000;i++){// 频繁使用的变量}}注意:register只是建议,编译器不一定采纳。
6.5.5 外部变量(extern):使用其他文件的全局变量
file1.c
intsharedValue=100;// 定义全局变量file2.c
#include<stdio.h>externintsharedValue;// 声明外部变量(在别的文件中定义的)voidprintValue(){printf("共享的值:%d\n",sharedValue);}🎪 6.6 函数应用实例
实例1:计算器程序
#include<stdio.h>// 函数声明floatadd(floata,floatb);floatsubtract(floata,floatb);floatmultiply(floata,floatb);floatdivide(floata,floatb);voidprintMenu();// 全局变量记录操作次数staticintoperationCount=0;intmain(){floatnum1,num2,result;intchoice;do{printMenu();printf("请选择操作(1-5):");scanf("%d",&choice);if(choice>=1&&choice<=4){printf("输入两个数字:");scanf("%f %f",&num1,&num2);switch(choice){case1:result=add(num1,num2);break;case2:result=subtract(num1,num2);break;case3:result=multiply(num1,num2);break;case4:if(num2!=0){result=divide(num1,num2);}else{printf("错误:除数不能为0!\n");continue;}break;}operationCount++;printf("结果:%.2f\n",result);printf("已执行%d次运算\n\n",operationCount);}}while(choice!=5);printf("感谢使用!\n");return0;}// 函数定义voidprintMenu(){printf("=== 简易计算器 ===\n");printf("1. 加法\n");printf("2. 减法\n");printf("3. 乘法\n");printf("4. 除法\n");printf("5. 退出\n");}floatadd(floata,floatb){returna+b;}floatsubtract(floata,floatb){returna-b;}floatmultiply(floata,floatb){returna*b;}floatdivide(floata,floatb){returna/b;}实例2:学生成绩管理系统(简化版)
#include<stdio.h>#defineMAX_STUDENTS5// 使用全局数组存储成绩floatscores[MAX_STUDENTS];// 函数声明voidinputScores();floatcalculateAverage();intfindHighest();voidprintAllScores();intmain(){printf("=== 学生成绩管理系统 ===\n");inputScores();printf("\n所有成绩:\n");printAllScores();printf("平均分:%.2f\n",calculateAverage());printf("最高分是第%d个学生:%.2f\n",findHighest()+1,scores[findHighest()]);return0;}voidinputScores(){printf("请输入%d个学生的成绩:\n",MAX_STUDENTS);for(inti=0;i<MAX_STUDENTS;i++){printf("学生%d:",i+1);scanf("%f",&scores[i]);}}floatcalculateAverage(){floatsum=0;for(inti=0;i<MAX_STUDENTS;i++){sum+=scores[i];}returnsum/MAX_STUDENTS;}intfindHighest(){inthighestIndex=0;for(inti=1;i<MAX_STUDENTS;i++){if(scores[i]>scores[highestIndex]){highestIndex=i;}}returnhighestIndex;}voidprintAllScores(){for(inti=0;i<MAX_STUDENTS;i++){printf("学生%d:%.2f\n",i+1,scores[i]);}}📚 本章小结
函数就像编程世界的"乐高积木":
- 定义函数 → 创造新的积木块
- 调用函数 → 使用积木搭建
- 参数和返回值 → 积木的接口和产出
- 变量作用域 → 积木的内部和外部空间
- 存储类别 → 积木的不同保存方式
核心要点口诀:
· 函数定义三部曲:返回值、函数名、参数列表
· 先声明后使用:原型声明是礼貌
· 数组传地址:效率高,但小心修改原数据
· 局部变量最安全:尽量用局部,少用全局
· 递归要有终止:否则无限循环程序崩
编程思维提升:
学会了函数,你就掌握了模块化编程的核心思想:把复杂问题分解成小问题,每个小问题用一个函数解决,最后组合起来解决大问题。
💪 动手挑战
试着完成以下练习,巩固所学:
- 基础题:写一个函数,判断一个数是否是素数
- 进阶题:用递归函数计算斐波那契数列第n项
- 综合题:设计一个简单的通讯录管理系统,包含添加、删除、查找功能(每个功能一个函数)
记住:多看不如多写,多写不如多调试!遇到问题时,用printf打印中间结果,观察程序如何一步步执行。
祝你学习愉快,早日成为函数使用高手!🚀