当前位置: 首页 > news >正文

用函数实现模块化程序设计

1. 函数的概念

“函数”是从英文 function 翻译过来的,其实,function 在英文中的意思既是“函数”,也是“功能”。从本质意义上说,函数就是用来完成一定的功能的。函数就是功能。每一个函数用来实现一个特定的功能。函数的名字应反映其代表的功能。

在设计一个较大的程序时,往往把它分为若干个程序模块,每一个模块包括一个或多个函数,每个函数实现一个特定的功能。一个 C 程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。

2. 函数的分类

2.1 库函数和自定义函数

① 库函数
它是由系统提供的,用户不必自己定义,可直接使用它们。应该说明,不同的 C 语言编译系统提供的库函数的数量和功能会有一些不同,当然许多基本的函数是共同的。
库函数相关头文件:https://zh.cppreference.com/c/header
C/C++ 官方链接https://zh.cppreference.com/c/header
cplusplus.comhttps://legacy.cplusplus.com/reference/clibrary/

② 库函数文档格式


库函数文档的一般格式:
函数原型、函数功能介绍、参数和返回类型说明、代码举例、代码输出、相关知识链接

③ 库函数举例

#include<stdio.h>#include<math.h>intmain(){doubleparam=36.0,result=0.0;result=sqrt(param);printf("sart(%f)=%f\n",param,result);return0;}

④ 用户自己定义函数。它是以解决用户专门需要的函数。

#include<stdio.h>intgetsum(intx,inty){returnx+y;}intmain(){inta=0,b=0;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()确定是否是闰年后,再根据月计算这个月的天数

#include<stdio.h>intis_leep_year(inty){if(((y%4==0)&&(y%100!=0))||(y%400==0)){return1;}else{return0;}}intget_days_of_month(inty,intm){intdays[]={0,31,28,31,30,31,30,31,31,30,31,30,31};intday=days[m];if(is_leep_year(y)&&m==2){day+=1;}returnday;}intmain(){intyear=0;intmonth=0;scanf("%d %d",&year,&month);intday=get_days_of_month(year,month);printf("%d\n",day);return0;}

6.2 链式访问

函数的链式访问指的是:把一个函数的返回值,直接作为另一个函数的参数继续使用,也就是多个函数一层一层地嵌套调用。

#include<stdio.h>intmain(){printf("%d\n",strlen("hello"));return0;}

#include<stdio.h>intmain(){printf("%d ~",printf("%d",printf("%d ",2026)));return0;}

最内层先打印2026 并返回5;中间打印5并返回1;最外层打印1 ~,所以结果是2026 51 ~

7. 函数的声明和定义

在一个函数中调用另一个函数(即被调用函数)需要具备如下条件:
① 首先被调用的函数必须是已经定义的函数(是库函数或用户自己定义的函数)。但仅有这一条件还不够。
② 如果使用库函数,应该在本文件开头用#include指令将调用有关库函数时所需用到的信息包含到本文件中来。
在同一个文件中,如果使用用户自己定义的函数,而该函数的位置在调用它的函数的后面,应该在主调函数中对被调用的函数作声明。

函数的定义和声明不是同一回事。
函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。
函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如,函数名是否正确,实参与形
参的类型和个数是否一致),它不包含函数体。

在 C 语言程序的编译过程中,编译器通常按照源文件中代码出现的先后顺序进行语法分析和语义检查。当程序执行到函数调用语句时,如果该函数在调用位置之前尚未被定义或声明,编译器便无法获知该函数的返回类型、函数名称以及参数类型等必要信息,因此可能产生函数未声明或隐式声明相关的编译警告。针对这一问题,通常需要在函数调用之前对目标函数进行函数声明,即给出函数原型。函数声明的作用是提前告知编译器该函数的基本接口信息,包括函数名、返回值类型以及形参类型。通过这种方式,编译器能够在正式遇到函数定义之前正确识别函数调用,从而保证程序的编译过程更加规范、可靠。

8. C语言中 static 与 extern 关键字

8.1 作用域与生命周期的基本概念

在讨论staticextern之前,需要首先明确两个基本概念:作用域和生命周期。

作用域,是指程序中某个标识符可以被访问和使用的代码范围。对于局部变量而言,其作用域通常局限于所在的函数体或代码块内部;而对于普通全局变量而言,其作用域可以扩展到整个工程,只要在其他文件中进行了适当声明,就可以被访问。

生命周期,是指变量从创建、占用存储空间,到被销毁、释放存储空间所经历的时间范围。普通局部变量一般在进入其所在作用域时创建,在离开作用域时销毁;全局变量则在程序开始运行前完成存储分配,并一直存在到整个程序结束。局部变量和全局变量在作用域与生命周期上具有明显区别,这是理解 static 修饰效果的前提。

8.2 static 修饰局部变量

static修饰局部变量时,该变量仍然只能在其所在函数或代码块内部被访问,即作用域不发生改变;但是它的生命周期会被延长为整个程序的生命周期。也就是说,普通局部变量在函数调用结束后会被销毁,而静态局部变量在函数调用结束后不会销毁,其值会被保留下来,下一次进入函数时可以继续使用。


在上述代码中,itest函数内部的局部变量,但由于它被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 的总结

从作用方向上看,staticextern体现了两种相反的程序组织思想。static倾向于限制符号的可见性,使变量或函数仅在当前源文件内部有效;而extern倾向于扩大符号的可访问范围,使当前文件能够引用其他文件中定义的外部符号。

从工程实践角度看,二者通常配合模块化编程使用。对于需要提供给外部模块使用的函数或变量,可以在头文件中进行声明,并在源文件中定义;对于只在当前源文件内部使用的辅助函数或全局变量,则应使用static修饰,避免其暴露到整个工程中。这样既能保证模块之间的必要通信,又能减少不必要的外部依赖。

关键字修饰对象主要作用
static局部变量延长生命周期,作用域不变
static全局变量限制变量只能在当前源文件内部使用
static函数限制函数只能在当前源文件内部调用
extern全局变量/函数声明外部符号,用于跨文件访问
http://www.jsqmd.com/news/874398/

相关文章:

  • 深入理解 Eino 的向量体系:从 Embedding 到向量数据库
  • BIND DNS漏洞CVE-2025-13878:EDNS选项解析致堆越界崩溃分析
  • 龙芯电脑装系统,选UOS、Loongnix还是等Debian?给3A4000/3A5000用户的保姆级选择指南
  • 超详细图解Attention机制:从原理到Self-Attention、多头注意力全覆盖
  • 工具变量评估与合成:从核心原理到机器学习实践
  • Windows 11上WSL安装后报getpwuid错误的完整排查手册:从Docker冲突到用户权限
  • 手机抓包配置全指南:从连不上到解密HTTPS
  • 从需求到交付:深度拆解企业级软件定制开发的标准化流程
  • 为什么你的渐变总像PPT?揭秘Midjourney v6.2中未公开的--color-bleed机制与渐变锚点定位技术
  • 别再到处找激活工具了!手把手教你用vlmcsd在Windows上自建KMS服务器(附防火墙配置)
  • 保姆级教程:用Arbe或大陆4D毫米波雷达点云数据,手把手实现Freespace检测(附Python伪代码)
  • 神经纹理:让3D世界“活”起来的AI魔法,一篇讲透!
  • Zookeeper集群启动失败?从myid配置到防火墙,保姆级排错指南来了
  • 语义优先架构:从VLM实验看90%功能漂移与具身AI新范式
  • 河北亮泽管道设备有限公司:2026年至今河北弹簧支吊架领域的优选实力服务商 - 2026年企业推荐榜
  • 无框架手写实现Function Calling:原理拆解+纯Python手写实现
  • Claude API文档版本管理生死线:v2.1→v3.0迁移实录,12个breaking change的文档同步策略
  • 别再乱格式化!一文搞懂NTFS、exFAT等磁盘格式区别与DiskGenius格式化实操
  • Super IO Blender插件:终极批量导入导出指南,工作效率提升300%
  • 储能 PACK 与 BMS:怎么识别有真实出货的系统集成厂,避开组装贴牌
  • 从纸质报表到Excel:PaddleOCR+Python自动化识别复杂表格(附完整代码)
  • 2026郑州柔性腻子优质品牌推荐指南:河南金刚沙腻子、河南防水抗裂砂浆、河南防水砂浆、郑州儿童房腻子、郑州内墙漆腻子选择指南 - 优质品牌商家
  • 别再死记ResNet结构了!用Python手搓一个ResUnet,从代码里真正搞懂残差连接
  • 觅健AI病程管理系统入选2026中国医疗健康产业最具创新力产品技术50强
  • P2WPKH:比特币的「见证革命」与比特鹰的技术解析
  • 照亮虚拟世界:神经渲染中的神经光照技术全解析
  • 【Lovable高阶开发者私藏技巧】:绕过平台限制实现自定义CSS/JS注入与第三方SDK深度对接
  • 2026徐闻装修公司推荐:徐闻别墅装修/徐闻办公楼装修/徐闻商铺装修/徐闻奶茶店装修/徐闻精装修/徐闻装修公司/选择指南 - 优质品牌商家
  • 计算机视觉与贝叶斯优化驱动的粉末饮料智能制备系统
  • 《论三生原理》对《周易》《道德经》的一次根本性重写?