前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C语言】函数

【C语言】函数

作者头像
TANGLONG
发布于 2024-10-15 11:03:34
发布于 2024-10-15 11:03:34
27200
代码可运行
举报
运行总次数:0
代码可运行

一、函数的概念

    数学中我们其实就⻅过函数的概念,⽐如:⼀次函数 y=kx+b ,k和b都是常数,给⼀个任意的x,就得到⼀个y值。 其实在C语⾔也引⼊函数(function)的概念,有些翻译为:⼦程序,⼦程序这种翻译更加准确⼀些。     C语⾔中的函数就是⼀个完成某项特定的任务的⼀⼩段代码。这段代码是有特殊的写法和调⽤⽅法的。     C语⾔的程序其实是由⽆数个⼩的函数组合⽽成的,也可以说:⼀个⼤的计算任务可以分解成若⼲个较⼩的函数(对应较⼩的任务)完成。同时⼀个函数如果能完成某项特定任务的话,这个函数也是可以复⽤的,提升了开发软件的效率。     在C语⾔中我们⼀般会⻅到两类函数:

  • 库函数
  • 自定义函数

二、库函数

1.标准库和头文件

    C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSI C规定了⼀些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数     我们前⾯内容中学到的 printf 、 scanf 都是库函数,库函数也是函数,不过这些函数已经是现成的,我们只要学会就能直接使⽤了。有了库函数,⼀些常⻅的功能就不需要程序员⾃⼰实现了,⼀定程度提升了效率;同时库函数的质量和执⾏效率上都更有保证     各种编译器的标准库中提供了⼀系列的库函数,这些库函数根据功能的划分,都在不同的头⽂件中进⾏了声明     库函数相关头⽂件:https://zh.c·ppreference.com/w/c/header     有数学相关的,有字符串相关的,有⽇期相关的等,每⼀个头⽂件中都包含了,相关的函数和类型等信息,库函数的学习不⽤着急⼀次性全部学会,慢慢学习,各个击破就⾏

2.库函数使用方法

库函数的学习和查看⼯具很多,⽐如: C/C++官⽅的链接:https://zh.cppreference.com/w/c/header cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/

3.库函数⽂档的⼀般格式

  1. 函数原型
  2. 函数功能介绍
  3. 参数和返回类型说明
  4. 代码举例
  5. 代码输出
  6. 相关知识链接

三、自定义函数

了解了库函数,我们的关注度应该聚焦在⾃定义函数上,⾃定义函数其实更加重要,也能给程序员写代码更多的创造性

1.自定义函数格式:

其实⾃定义函数和库函数是⼀样的,形式如下:

代码语言:javascript
代码运行次数:0
运行
复制
ret type fun_name(形式参数)
{
}

• ret_type 是函数返回类型 • fun_name 是函数名 • 括号中放的是形式参数,简称形参 • {}括起来的是函数体     我们可以把函数想象成⼩型的⼀个加⼯⼚,⼯⼚得输⼊原材料,经过⼯⼚加⼯才能⽣产出产品,那函数也是⼀样的,函数⼀般会输⼊⼀些值(可以是0个,也可以是多个),经过函数内的计算,得出结果

  • ret_type 是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表⽰什么都不返回
  • fun_name 是为了⽅便使⽤函数;就像⼈的名字⼀样,有了名字⽅便称呼,函数有了名字⽅便调⽤,所以函数名尽量要根据函数的功能起的有意义
  • 函数的参数就相当于,⼯⼚中送进去的原材料,函数的参数也可以是 void ,明确表⽰函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数
  • {}括起来的部分被称为函数体,函数体就是完成计算的过程

2.函数举例

    现在我们知道了自定义函数的格式,我们来简单实现一下一个加法函数,要求:给定一个函数Add,其参数为两个整型,返回值为两个整型的和,具体实现如下:

代码语言:javascript
代码运行次数:0
运行
复制
int Add(int x,int y)
{
      int c = 0;
      int c = x + y;
      return c;
}

这样一个简单的自定义加法函数我们就完成了,我们可以试试它能不能正常使用,如图:

可以看到函数成功实现了我们的需求,再次分析一下: (1)函数Add返回值为整型,因为整型相加还是整型 (2)它的形参为两个整型x和y,而在主函数中的a和b则是实参,形参和实参可以使用不同的名字 (3)在函数体中,我们借助另一个参数c存放两个整型的和,最后使用return语句将其返回

四、形参和实参

我们使用上面的例子来讲解形参和实参:

1.实参

    在上图中,在主函数中使用时传递的两个整型为a和b,称为实际参数,简称为实参,实际参数就是真实传递给函数的参数

2.形参

    在函数Add的定义部分有两个整型x和y,它们称为形式参数,简称形参。为什么叫形式参数呢?实际上,如果只是定义了 Add 函数,⽽不去调⽤的话, Add 函数的参数 x和 y 只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化

3.实参和形参的关系

    实参和形参是有关系的,当没有使用函数时,形式参数并不会在内存中开辟空间,只有在使用函数时,将实参传递给函数,那么对应的形参就会接收实参的值,此时形参也会在内存中开辟自己的空间,所以它们的关系可以说是:形参是实参在内存中的临时拷贝

五、return语句

    在函数的设计中,函数中经常会出现return语句,这⾥讲⼀下return语句使⽤的注意事项。

  • return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式的结果,比如我们要简化上面的Add函数就可以使用表达式,如图:

return后面跟了表达式,使得整个函数变得更加简洁了

  • return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况,此时也可以省略return语句
  • return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
  • return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
  • 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。

六、数组做函数参数

    在使⽤函数解决问题的时候,难免会将数组作为参数传递给函数,在函数内部对数组进⾏操作。     ⽐如:写⼀个函数set_arr将⼀个整型数组的内容,全部置为-1,再写⼀个函数print_arr打印数组的内容    现在我们将准备工作做好,创建一个整型数组arr,将函数预先写出,方便了解需求,后面再去写函数的代码,如图:

   这⾥的set_arr函数要能够对数组内容进⾏设置,就得把数组作为参数传递给函数,同时函数内部在设置数组每个元素的时候,也得遍历数组,需要知道数组的元素个数。所以我们需要给set_arr传递2个参数,⼀个是数组,另外⼀个是数组的元素个数。仔细分析print_arr也是⼀样的,只有拿到了数组和元素个数,才能遍历打印数组的每个元素    而给函数传递数组,只需要传递它的名字,如图:

   数组作为参数传递给了set_arr 和 print_arr 函数了,那这两个函数应该如何设计呢?    这⾥我们需要知道数组传参的⼏个重点知识: (1)函数的形式参数要和函数的实参个数匹配 (2)函数的实参是数组,形参也是可以写成数组形式的 (3)形参如果是⼀维数组,数组⼤⼩可以省略不写 (4)形参如果是⼆维数组,⾏可以省略,但是列不能省略 (5)数组传参,形参是不会创建新的数组的 (6)形参操作的数组和实参的数组是同⼀个数组    现在我们开始着手创建这两个函数,如图:

现在我们来看看代码能不能正常跑起来,如图:

可以看到已经实现了我们的需求

七、嵌套调用和链式访问

1.嵌套调用

   嵌套调⽤就是函数之间的互相调⽤,每个函数就像⼀个乐⾼零件,正是因为多个乐⾼的零件互相⽆缝的配合才能搭建出精美的乐⾼玩具,也正是因为函数之间有效的互相调⽤,最后写出来了相对⼤型的程序,可以简单的理解为在一个函数定义中使用了另一个函数,就叫做嵌套调用    假设我们计算某年某⽉有多少天?如果要函数实现,可以设计2个函数,一个函数leap_year用来判断这一年是否是润年,另一个函数get_days用来算出该月的天数

  1. leap_year:我们写代码判断这一年是否是润年,如果是返回1,不是返回0,我们曾经写过如何判断润年,这里直接将它包装为一个函数即可,如:
  1. get_days:我们可以使用一个数组来存放每个月的天数,这里可以用到一个小技巧,由于数组下标从0开始,而月份却是从1开始,我们就可以在0下标处放一个0,随后放1月到12月的天数,如:
代码语言:javascript
代码运行次数:0
运行
复制
int arr[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

这样,arr[month]就可以取出对应月份的天数,当然我们要注意一个特殊的地方,润年2月份是29天,我们就可以使用上面的函数leap_year来判断年份是否为润年,如果是润年,那么就让原本的28天加1,如图:

最后我们看看主函数,以及最后执行的效果:

2.链式访问

   所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问    如以下例子对比:

通过对比可以看出,后面的例子直接将函数strlen作为了函数printf的一个参数,使得代码更加简洁,这就是函数的链式访问

3.有趣的练习:

请思考以下代码的输出结果:

答案:
解析:

   为什么会出现这个结果呢?这时我们就要了解printf的返回值了,printf也有返回值,它返回的是它的屏幕上打印的字符个数,比如:

   此时第一个printf执行,在屏幕上打印了abc并且换行,然后用sz接收它的返回值,可以看到返回了4,这里换行符也算一个字符

   了解清楚这个我们就可以分析上面的题目了: (1)第三个printf打印43,在屏幕上打印2个字符,再返回2 (2)第⼆个printf打印2,在屏幕上打印1个字符,再放回1 (3)第⼀个printf打印1 所以屏幕上最终打印:4321

八、函数的声明和定义

1.单个文件

(1)将函数定义在main函数前,无需声明

如:

如图,函数Add定义在main函数之前,编译器不会有警告,可以顺利完成编译

(2)函数定义在main函数下方:

此时程序还是可以正常运行,但是报了一条警告,说函数Add未定义,这是因为C语⾔编译器对源代码进⾏编译的时候,从第⼀⾏往下扫描的,当遇到Add函数调⽤的时候,并没有发现前⾯有Add的定义,就报出了上述的警告

解决方法:在main函数之前声明一下,只需要声明函数名,函数的返回类型和函数的参数,也就是除了大括号那一部分的内容,如:

这样函数就不会报错了

2.多个文件:

   ⼀般在企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会根据程序的功能,将代码拆分放在多个⽂件中    ⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的实现是放在源⽂件(.c)⽂件中,如:

代码语言:javascript
代码运行次数:0
运行
复制
//add.c文件
 int Add(int x,int y)
 {
      return x+y;//
      //将函数的定义放在.c文件中
}
代码语言:javascript
代码运行次数:0
运行
复制
//add.h
 int Add(int x,int y);
 //函数声明放在.h文件中

随后如果我们想在test.c中使用函数Add,那么只需要包含add.h这个头文件即可,如果包含的头文件是我们自己写的,那么要用引号引起,如:

代码语言:javascript
代码运行次数:0
运行
复制
#include "add.h"

这样代码就可以正常运行了

3.extern和static

(1)变量的生命周期和作用域

   作用域:⼀段程序代码中所⽤到的名字并不总是有效(可⽤)的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域

  1. 局部变量的作⽤域是变量所在的局部范围。
  2. 全局变量的作⽤域是整个⼯程(项⽬)

   ⽣命周期:指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段

  1. 局部变量的⽣命周期是:进⼊作⽤域变量创建,⽣命周期开始,出作⽤域⽣命周期结束
  2. 全局变量的⽣命周期是:整个程序的⽣命周期
(2)extern关键字

extern 是⽤来声明外部符号的,如果⼀个全局的符号在A⽂件中定义的,在B⽂件中想使⽤,就可以使⽤ extern 进⾏声明,然后使⽤,使用方法如下:

在这里插入图片描述
在这里插入图片描述

在上面两段代码中,我们将整型全局变量定义在add.c中,当我们要在另一个文件test.c中使用它时,就要使用extern来声明它,随后可以正常使用,函数也是如此,如图:

我们在另一个文件定义了Add函数,想使用就可以用extern关键字对它进行声明,随后正常使用

(3)static关键字

static会改变所修饰函数或变量的生命周期,而不改变它的作用域,我们下面详细介绍一下

  1. static修饰局部变量: 我们对比以下代码:

它们的运行结果有什么不同呢?这个就要将到static修饰局部变量了 代码1的test函数中的局部变量i是每次进⼊test函数先创建变量(⽣命周期开始)并赋值为0,然后++,再打印,出函数的时候变量⽣命周期将要结束(释放内存) 代码2中,test函数中的i创建好后,出函数的时候是不会销毁的,重新进⼊函数也就不会重新创建变量,直接上次累积的数值继续计算    以上两个代码的运行结果如下:

在这里插入图片描述
在这里插入图片描述

解析:代码1由于是局部变量,每一次调用函数结束就要被销毁,导致每一次进入函数test都从0开始,加1后就是1,循环往复。代码2由于被static修饰,此时变量i的生命周期发生了变化,变成了全局变量的生命周期,但是作用域不变,此时当函数调用结束后i不会被销毁,下一次进入函数就不会被重置为0,也就有了累加的效果 结论:static修饰局部变量改变了变量的⽣命周期,⽣命周期改变的本质是改变了变量的存储类型,本来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的,⽣命周期就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作⽤域不变的

  1. static修饰全局变量    ⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤。本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源⽂件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。    例如我们在另一个文件中创建一个全局变量a,将它赋值为20,下面是正常声明和使用:

可以看到变量a可以正常使用,接下来我们在定义全局变量a时加上static:

可以看到编译器直接报错了,原因就是创建在一个文件下的,并且被static修饰的全局变量,只能在该文件使用,而不能在其它文件使用    使⽤建议:如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤static修饰。

  1. static修饰函数    static修饰函数和修饰全局变量是差不多的,也是将函数变成只能该文件内使用,不能在其它文件使用,例如我们在另一个文件中创建一个函数Add,下面是正常声明和使用:

可以看出只要声明了就可以使用,下面是使用了static修饰函数Add的情况(修饰函数只需要在函数的返回类型前加一个static即可):

   可以看到此时代码出错了,是因为static 修饰函数和 static 修饰全局变量是⼀模⼀样的,⼀个函数在整个⼯程都可以使⽤,被static修饰后,只能在本⽂件内部使⽤,其他⽂件⽆法正常的链接使⽤了    本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部使⽤    使⽤建议:⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
「SF-LC」14 ImpCEvalFun
Step-Indexed Evaluator …Copied from 12-imp.md: Chapter ImpCEvalFun provide some workarounds to make functional evalution works: step-indexed evaluator, i.e. limit the recursion depth. (think about Depth-Limited Search). return option to tell if it’s a norm
零式的天空
2022/03/14
4730
「SF-LC」6 Logic
The equality operator = is also a function that returns a Prop. (property: equality)
零式的天空
2022/03/14
6000
「SF-PLF」5 Smallstep
not just input state get mapped to output state. but also intermediate state (which could be observed by concurrent code!)
零式的天空
2022/03/02
5980
「SF-LC」1 Basics
The .v code is a gorgeous example of literal programming and the compiled .html website is full-fledged. So this note is intended to be NOT self-contained and only focus on things I found essential or interesting. This note is intended to be very personal and potentially mix English with Chinese (You can Lol) So yeah. Don’t expect it to be well organized and well written. I posted it on blog mainly for my own references purpose. The quotes could either come from the book or saying from someone (even including me).
零式的天空
2022/03/14
4060
「SF-LC」13 ImpParser
basically, parser combinator (But 非常麻烦 in Coq)
零式的天空
2022/03/14
3750
「SF-LC」3 List
Pair of Numbers Q: Why name inductive? A: Inductive means building things bottom-up, it doesn’t have
零式的天空
2022/03/14
4290
「SF-LC」4 Poly
Until today, We were living in the monomorphic world of Coq. So if we want a list, we have to define it for each type:
零式的天空
2022/03/14
1.3K0
「SF-PLF」1 Equiv
Some module (e.g.Map) not found either maunally make map.vo or proof general can solve that.
零式的天空
2022/03/02
4960
「SF-LC」15 Extraction
如果不做任何处理的话…生成的 ml 里的 nat 则都会是 Church Numeral…
零式的天空
2022/03/14
5300
「SF-LC」7 Ind Prop
we can write an Inductive definition of the even property!
零式的天空
2022/03/14
6660
「SF-LC」16 Auto
Ltac - automated forward reasoning (hypothesis matching machinery)
零式的天空
2022/03/14
3680
「SF-LC」9 ProofObjects
So the book material is designed to be gradually reveal the facts that
零式的天空
2022/03/14
5600
「SF-LC」10 IndPrinciples
P only need to fullfill l : the_type but not n:nat since we are proving property of the_type.
零式的天空
2022/03/14
7730
「SF-LC」2 Induction
Whether or not it can be just simpl. depending on the definition of orb.
零式的天空
2022/03/14
4330
「SF-PLF」7 Stlc
“Base Types”, only Bool for now. — 基类型 …again, exactly following TAPL.
零式的天空
2022/03/02
3690
「SF-LC」5 Tactics
It also works with conditional hypotheses:
零式的天空
2022/03/14
5350
「SF-LC」8 Maps
From now on, importing from std lib. (but should not notice much difference)
零式的天空
2022/03/14
3460
「SF-PLF」6 Types
The toy lang from SmallStep is too “safe” to demonstrate any runtime (or dynamic) type errors. — 运行时类型错误 So that’s add some operations (common church numeral ones), and bool type.
零式的天空
2022/03/02
4510
「SF-PLF」11. TypeChecking
首先我们需要 check equality for types. 这里非常简单,如果是 SystemF 会麻烦很多,对 ∀ 要做 local nameless 或者 alpha renaming:
零式的天空
2022/03/02
2810
「SF-LC」11 Rel
I have been long confused with Unary Relations vs. Binary Relation on the Same Set (homogeneous relation) I thought they were same…but turns out they are totally different!
零式的天空
2022/03/14
3960
相关推荐
「SF-LC」14 ImpCEvalFun
更多 >
目录
  • 一、函数的概念
  • 二、库函数
    • 1.标准库和头文件
    • 2.库函数使用方法
    • 3.库函数⽂档的⼀般格式
  • 三、自定义函数
    • 1.自定义函数格式:
    • 2.函数举例
  • 四、形参和实参
    • 1.实参
    • 2.形参
    • 3.实参和形参的关系
  • 五、return语句
  • 六、数组做函数参数
  • 七、嵌套调用和链式访问
    • 1.嵌套调用
    • 2.链式访问
    • 3.有趣的练习:
      • 答案:
      • 解析:
  • 八、函数的声明和定义
    • 1.单个文件
      • (1)将函数定义在main函数前,无需声明
      • (2)函数定义在main函数下方:
    • 2.多个文件:
    • 3.extern和static
      • (1)变量的生命周期和作用域
      • (2)extern关键字
      • (3)static关键字
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档