1. 函数的概念“函数”是从英文 function 翻译过来的其实function 在英文中的意思既是“函数”也是“功能”。从本质意义上说函数就是用来完成一定的功能的。函数就是功能。每一个函数用来实现一个特定的功能。函数的名字应反映其代表的功能。在设计一个较大的程序时往往把它分为若干个程序模块每一个模块包括一个或多个函数每个函数实现一个特定的功能。一个 C 程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。2. 函数的分类2.1 库函数和自定义函数① 库函数它是由系统提供的用户不必自己定义可直接使用它们。应该说明不同的 C 语言编译系统提供的库函数的数量和功能会有一些不同当然许多基本的函数是共同的。库函数相关头文件https://zh.cppreference.com/c/headerC/C 官方链接https://zh.cppreference.com/c/headercplusplus.comhttps://legacy.cplusplus.com/reference/clibrary/② 库函数文档格式库函数文档的一般格式函数原型、函数功能介绍、参数和返回类型说明、代码举例、代码输出、相关知识链接③ 库函数举例#includestdio.h#includemath.hintmain(){doubleparam36.0,result0.0;resultsqrt(param);printf(sart(%f)%f\n,param,result);return0;}④ 用户自己定义函数。它是以解决用户专门需要的函数。#includestdio.hintgetsum(intx,inty){returnxy;}intmain(){inta0,b0;scanf(%d %d,a,b);printf(%d\n,getsum(a,b));return0;}2.2 无参函数和有参函数① 无参函数。在调用无参函数时主调函数不向被调用函数传递数据。无参函数一般用来执行指定的一组操作。无参函数可以带回或不带回函数值但一般以不带回函数值的居多。② 有参函数。在调用函数时主调函数在调用被调用函数时通过参数向被调用函数传递数据一般情况下执行被调用函数时会得到一个函数值被主函数调用。3. 函数调用时的数据传递3.1 形式参数和实际参数在调用有参函数时主调函数和被调用函数之间有数据传递关系。在定义函数时函数名后面括号中的变量名称为“形式参数”简称“形参”或“虚拟参数”。在主调函数中调用一个函数时函数名后面括号中的参数称为“实际参数”简称“实参”。实际参数可以是常量、变量或表达式。3.2 实参和形参间的数据传递在调用函数过程中系统会把实参的值传递给被调用函数的形参。或者说形参从实参得到一个值。该值在函数调用期间有效可以参加该函数中的运算。在调用函数过程中发生的实参与形参间的数据传递称为“虚实结合”。形参是实参的一份临时拷贝。实参向形参的数据传递是“值传递”单向传递只能由实参给形参而不能由形参给实参。实参和形参在内存中占用不同的存储单元实参无法得到形参的值。4. return 语句函数的返回值是通过return语句获得的。①return后边可以是一个数值也可以是一个表达式如果是表达式则先执行表达式再返回表达式的结果。②return后边可以什么都没有直接写return;这种写法适合函数返回类型是void的情况。③return语句执行后函数就彻底返回后边的代码不再执行。④return返回的值和函数返回类型不一致系统会自动将返回的值隐式转换为函数的返回类型。⑤ 如果函数中存在if等分支语句则要保证每种情况下都有return返回否则会出现编译错误。⑥ 函数的返回类型如果不写编译器默认的返回类型是int。⑦ 函数写了返回类型但是函数中没有使用return返回值那么函数的返回值是未知的。5. 数组做函数参数在使用函数解决问题的时候难免会将数组作为参数传递给函数在函数内部对数组进行操作。在进行数组传参时需要掌握几个关键点① 函数调用时实参的个数必须与形参的个数保持一致。② 当函数的实参是数组时形参也可以写成数组的形式。如果传递的是一维数组形参中的数组大小可以省略如果传递的是二维数组形参中的行数可以省略但列数必须明确写出。③数组作为参数传递时形参并不会重新创建一个新的数组而是直接操作实参数组对应的内存空间。因此在函数内部对形参数组进行修改实际上也会影响到外部传入的原数组。6. 函数的嵌套调用与链式访问6.1 嵌套调用C 语言的函数定义是互相平行、独立的也就是说在定义函数时一个函数内不能再定义另一个函数即不能嵌套定义但可以嵌套调用函数即在调用一个函数的过程中又调用另一个函数。假设计算某年某月有多少天如果要函数实现可以设计 2 个函数•is_leap_year(): 根据年份确定是否是闰年•get_days_of_month(): 调用is_leap_year()确定是否是闰年后再根据月计算这个月的天数#includestdio.hintis_leep_year(inty){if(((y%40)(y%100!0))||(y%4000)){return1;}else{return0;}}intget_days_of_month(inty,intm){intdays[]{0,31,28,31,30,31,30,31,31,30,31,30,31};intdaydays[m];if(is_leep_year(y)m2){day1;}returnday;}intmain(){intyear0;intmonth0;scanf(%d %d,year,month);intdayget_days_of_month(year,month);printf(%d\n,day);return0;}6.2 链式访问函数的链式访问指的是把一个函数的返回值直接作为另一个函数的参数继续使用也就是多个函数一层一层地嵌套调用。#includestdio.hintmain(){printf(%d\n,strlen(hello));return0;}#includestdio.hintmain(){printf(%d ~,printf(%d,printf(%d ,2026)));return0;}最内层先打印2026 并返回5中间打印5并返回1最外层打印1 ~所以结果是2026 51 ~7. 函数的声明和定义在一个函数中调用另一个函数即被调用函数需要具备如下条件① 首先被调用的函数必须是已经定义的函数是库函数或用户自己定义的函数。但仅有这一条件还不够。② 如果使用库函数应该在本文件开头用#include指令将调用有关库函数时所需用到的信息包含到本文件中来。③在同一个文件中如果使用用户自己定义的函数而该函数的位置在调用它的函数的后面应该在主调函数中对被调用的函数作声明。函数的定义和声明不是同一回事。①函数的定义是指对函数功能的确立包括指定函数名、函数值类型、形参及其类型以及函数体等它是一个完整的、独立的函数单位。②函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统以便在调用该函数时系统按此进行对照检查例如函数名是否正确实参与形参的类型和个数是否一致它不包含函数体。在 C 语言程序的编译过程中编译器通常按照源文件中代码出现的先后顺序进行语法分析和语义检查。当程序执行到函数调用语句时如果该函数在调用位置之前尚未被定义或声明编译器便无法获知该函数的返回类型、函数名称以及参数类型等必要信息因此可能产生函数未声明或隐式声明相关的编译警告。针对这一问题通常需要在函数调用之前对目标函数进行函数声明即给出函数原型。函数声明的作用是提前告知编译器该函数的基本接口信息包括函数名、返回值类型以及形参类型。通过这种方式编译器能够在正式遇到函数定义之前正确识别函数调用从而保证程序的编译过程更加规范、可靠。8. C语言中 static 与 extern 关键字8.1 作用域与生命周期的基本概念在讨论static和extern之前需要首先明确两个基本概念作用域和生命周期。作用域是指程序中某个标识符可以被访问和使用的代码范围。对于局部变量而言其作用域通常局限于所在的函数体或代码块内部而对于普通全局变量而言其作用域可以扩展到整个工程只要在其他文件中进行了适当声明就可以被访问。生命周期是指变量从创建、占用存储空间到被销毁、释放存储空间所经历的时间范围。普通局部变量一般在进入其所在作用域时创建在离开作用域时销毁全局变量则在程序开始运行前完成存储分配并一直存在到整个程序结束。局部变量和全局变量在作用域与生命周期上具有明显区别这是理解 static 修饰效果的前提。8.2 static 修饰局部变量当static修饰局部变量时该变量仍然只能在其所在函数或代码块内部被访问即作用域不发生改变但是它的生命周期会被延长为整个程序的生命周期。也就是说普通局部变量在函数调用结束后会被销毁而静态局部变量在函数调用结束后不会销毁其值会被保留下来下一次进入函数时可以继续使用。在上述代码中i是test函数内部的局部变量但由于它被static修饰因此不会在每次函数调用结束后销毁。第一次调用test函数时i的值由0增加为1第二次调用时i并不会重新初始化为0而是在上一次结果的基础上继续增加。因此多次调用函数后变量值呈现出连续累加的效果。本质上看static修饰局部变量改变的是变量的存储类型。普通局部变量通常存储在栈区而静态局部变量存储在静态存储区因此其生命周期与全局变量类似直到程序结束才被销毁。但需要注意的是它仍然是局部变量不能在函数外部直接访问。对此总结为static修饰局部变量改变了变量的生命周期但不改变其作用域。8.3 static 修饰全局变量普通全局变量默认具有外部链接属性这意味着它不仅可以在定义它的源文件中使用也可以通过extern声明后在其他源文件中使用。extern的主要作用是声明外部符号。外部符号通常是指在其他源文件中定义、但希望在当前源文件中使用的全局变量或函数。这里的extern int g_val; 并不是重新定义一个变量而是告诉编译器g_val这个变量已经在其他地方定义当前文件只是引用它。这样编译器在编译当前文件时可以识别该变量链接器在链接阶段会去其他目标文件中寻找其真正定义。static修饰全局变量的核心作用是将全局变量的外部链接属性改为内部链接属性使该变量只能在当前源文件内部使用。这有助于降低不同源文件之间的耦合程度避免全局变量被其他文件随意访问或修改从而增强程序模块的封装性和安全性。被static修饰的全局变量只能在本源文件内部使用其他源文件即使进行声明也无法正常使用。8.4 static 修饰函数在 C 语言中普通函数默认也具有外部链接属性。这意味着一个源文件中定义的函数只要在其他源文件中进行了函数声明就可以被调用。static修饰函数的本质是将函数的链接属性由外部链接改为内部链接使该函数成为当前源文件内部的私有函数。在工程开发中如果某个函数只服务于当前模块不希望被其他模块直接调用就可以使用static对其进行修饰。这样既可以避免命名冲突也可以增强模块边界的清晰性。被static修饰后的函数只能在其所在源文件内部使用其他文件无法正常链接调用。8.5 static 与 extern 的总结从作用方向上看static和extern体现了两种相反的程序组织思想。static倾向于限制符号的可见性使变量或函数仅在当前源文件内部有效而extern倾向于扩大符号的可访问范围使当前文件能够引用其他文件中定义的外部符号。从工程实践角度看二者通常配合模块化编程使用。对于需要提供给外部模块使用的函数或变量可以在头文件中进行声明并在源文件中定义对于只在当前源文件内部使用的辅助函数或全局变量则应使用static修饰避免其暴露到整个工程中。这样既能保证模块之间的必要通信又能减少不必要的外部依赖。关键字修饰对象主要作用static局部变量延长生命周期作用域不变static全局变量限制变量只能在当前源文件内部使用static函数限制函数只能在当前源文件内部调用extern全局变量/函数声明外部符号用于跨文件访问