C语言——函数

一,函数的概念

函数这个概念相信大家最早接触,是在数学当中,如一次函数:y=kx+b,其中k与b为常量,给出任意x的值就能够知道y的值。

在C语言当中也引入了函数(function)的概念,当然另一种翻译——子程序,会帮助我们有更好的理解。

C语言中的函数就是一个完成某项特定任务的一小串代码。这串代码是有特殊的写法和调用方法的。

C语言的程序其实是由无数个小的函数组合而成的,也可以说:一个大的计算任务可以分解成若干个较小的函数完成。同时一个函数如果能完成某项特定任务的话,这个函数也是可以复用的,这就大大提升了软件的开发效率。

在C语言中我们一般会遇见两种函数:

•  库函数
•  自定义函数

二,库函数

1.标准库和头文件

C语言标准中规定了C语言的各种语法规则,但C语言本身并不提供库函数;C语言的国际标准ANSI规定了一些常用的函数标准,被称为标准库;不同的编译器厂商又根据ANSI提供的标准库给出了一系列函数的实现,这些函数就被成为库函数。

在有了库函数之后,一些常见的功能便不用程序员自己进行实现,函数是现成的,只需要学会就能直接进行使用,不仅质量和执行效率都更有保证,且在一定程度上大幅提升了开发效率。

各种编译器的标准库中提供了一系列的库函数,这些库函数根据功能的划分,都在头文件中进行了声明。

库函数相关头文件: https://zh.cppreference.com/w/c/header

2.库函数的使用方法

库函数的学习和查看有相当多的网站与工具,可以自行在百度上进行查找学习;如:

C/C++官方链接:https://zh.cppreference.com/w/c/header

cplusplus.comhttps://legacy.cplusplus.com/reference/clibrary/

举例:sqrt

i.功能

Compute square root 计算平方根
Returns the square  root of  x.(返回平方根)

ii.头文件包含

库函数是在标准库中对应的头文件中声明的,所以库函数的使用,务必包含对应的头文件。
如sqrt()对应的头文件为<math.h>,我们就得在程序中包含该头文件。

iii.实践

iiii.库函数文档的一般格式

三,自定义函数

 C语言中,除了常用到的,我们还可以为了方便自己程序的书写而自己创造函数,也就是自定义函数,这也就为我们写代码带来了更多的创造性。

数的语法形式函

•  ret_type是函数的返回类型
•  fun_name是自定义函数名
()中放的是形式参数
•  {}括起来的是函数体  
我们可以用一张图来简单体现函数在程序中的作用
•  ret_type是用来表示函数计算结果的类型,有时函数类型可以是void,表示什么都不用返回
•  fun_name是为了方便使用函数;调用函数时都会用到函数名,所以函数名尽量根据函数功能起名,使函数名有意义化
•   函数的参数就是需要使用该函数进行计算的数据,函数的参数也可以是void,表示函数没有参数。如果有参数,需要交代清楚参数的类型和名字,以及个数
•  {}括起来的被称为函数体,也就是函数计算的过程
例如:
我们根据要完成的功能,给函数取名:Add,函数Add需要接收2个整型类型的参数,函数计算的结果也是整型。
于是我们根据以上分析完成了代码的书写
熟悉后Add函数我们也可以简化书写
注意:函数的参数部分需要交代清楚参数个数,每个参数参数类型以及参数名。
根据以上例子,我们可以在之后设计函数时灵活变化函数名,返回类型,参数个数等来灵活设计函数。
四,形参和实参
以上讲到了函数的参数,而参数在函数使用过程中又可以分为形参和实参。
这里我们用上面那串加法代码来对形参与实参进行剖析
实参:
上述代码第9行与第10行定义了a,b这两个变量,第3—6行定义了函数,在第11行使用该函数时传给了函数参数a和b,此时的a和b就是 实际参数,简称 实参
形参:
在第3行创建函数时我们定义了x与y两个变量,而在下面第11调用函数的过程中,传递的a,变量就由x和y来进行接收,此时x=a,y=b,程序在函数中进行运行时的参数就变成了a和b,所以这里的x和y只是形式上存在的参数,函数调用时会被实际参数所取代,所以我们称x,y这样的参数为 形式参数,简称 形参

四,return语句

相信大家对return定不会感到陌生,都知道它在返回值时会使用到,但却没有细微的去了解过return语句的注意点,接下来就让我来为大家介绍一下

•  return后面可以是一个数值,也可以是一个表达式,如果后面是表达式,则会执行该表达式,并返回表达式执行的结果。

•  return后面也可以什么都没有,直接写return;这种情况常用于函数类型是viod的情况。

•  当return返回的值和函数的返回类型不一致时,系统会自动将return返回的值隐式转换成函数的返回类型
•  return语句执行后,函数就彻底返回,后边的代码不再执行
•  如果函数中存在分支结构,如if语句,要保证每种情况下都有return返回,否则将存在编译错误

五,数组做函数参数

我们在明白函数是如何传递参数后,我们又会遇到新的问题,那就是在传递参数时碰到将数组作为参数传递给函数的情况,在函数内部对数组进行操作。

接下来,我们用代码大致来实现一下该操作。

先写出主函数表达一下我们大概的构思

这里我们所需要的set_arr函数要能够对数组进行设置,所以就得把数组作为参数传递给函数,同时在函数内部设置数组每个元素的时候,也得遍历数组,需要知道数组的元素个数,所以我们需要给set_arr函数传递两个参数,一个是数组,另一个则是数组的元素个数。

此外再对printf_arr函数进行分析,发现创建该函数需要满足的条件同set_arr函数相似,只有拿到了数组和数组的元素个数,才能遍历打印打印数组的每个元素。

知道了传递的参数后我们便可以此设计函数了。

在设计之前,我们先了解一下数组传参时的几个重要知识点

•   函数的形式参数要和函数的实参个数匹配
•   函数的实参是数组,形参也是可以写成数组形式的
•   形参如果是⼀维数组,数组⼤⼩可以省略不写
•   形参如果是⼆维数组,⾏可以省略,但是列不能省略
•  数组传参,形参是不会创建新的数组的
•   形参操作的数组和实参的数组是同⼀个数组

于是我们依照此处知识点写上我们上方所需要的函数

整个代码:

打印结果:

六,嵌套调用和链式访问

1.嵌套调用

嵌套调用就是各个函数之间的互相联系与利用,也就是一个函数可以写入另一个函数当中,也正是函数之间的有效调用,才使得相对较大型的程序得以较为简单的开发出来

假设:我们需要写一个程序来计算某年某月有多少天

我们先对题目进行分析:

1.我们需要定义两个变量来实现输入年份和月份

2.每年的每个月有多少天是固定的,但闰年的2月份是个例外

3.我们设想写一个函数来判断该年份是否为闰年,再写一个函数来判断该输入的年月对应的输出天数

4.我们定义一个数组来装下12个月份的各月份的天数,以月份为下标的方式来实现查找功能

首先我们根据构思写上主函数

然后我们根据逻辑关系写上is_leap_year函数来判断输入年份是否为闰年

随后继续根据逻辑顺序写上Day函数来帮我们计算天数

至此,我们整个题目要求的代码便大功告成了,让我们来瞅瞅吧

再让我们来看看打印结果

这一串代码不仅完成了一个独立的功能,而且还反应不少函数的调用:

•  main函数中调用了printf,scanf,Day函数
•   Day函数中调用了is_leap_year函数
由此可以见得,稍微大点的函数都会涉及到嵌套调用,但是函数是不能嵌套定义的。

2.链式访问

链式访问就是将一个函数的返回值作为另一个函数的参数,像链条一样将函数串起来,就是函数的来链式访问。

如:

如果我们要用链式访问打印上述代码的话,我们便只需要将strlen的返回值作为printf函数的参数便可以了

明白了上述知识点后,再让我们来试着解读一下这串代码

很显然,这串代码将printf函数的返回值作为了printf函数的参数,所以在我们解读这串代码前得先明白printf函数的返回值是什么

原来,printf函数的返回值就是打印在屏幕上的字符个数

由此我们便可以试着解读这串代码了

1.最内层的printf函数在屏幕上打印了43,含有两个数字,所以最内层的printf函数返回值为2;

2.第二个printf函数将最内层printf函数的返回值“2”作为参数,故在屏幕上打印数字2这一个字符,所以第二个printf函数的返回值为1;

3.第一个printf函数将第二个printf函数的返回值1作为参数,在屏幕上打印数字1;

4.在清楚每个printf函数打印的值后,根据程序运行顺序分析出首先打印43,其次打印2,最后打印1。于是我们便得到了这串代码的打印结果4321。

七,函数的声明和定义

1.单个文件

函数的调用满足先声明,后使用;函数的定义也是一种特殊的声明,所以函数定义放在调用之前也是可行的。

具体什么意思?让我们通过以下例子来进行理解

该情况为函数声明置于函数调用前

而当函数调用置于函数声明后时,编译器就会报出这样的警告

这是因为程序是逐行向下运行的,当运行到函数调用那行时,前方并没有对该函数进行声明,所以才会报出警告;解决问题则需再前方对该函数进行声明即可。

如:is_leap_year(int year),像这样交代清楚函数类型,函数名,函数的参数即可。

2.多个文件

当面对大型程序时,往往牵扯的代码量较大,这时候我们便会把一个程序拆分为多个文件进行设计;而这个时候,函数的声明,类型的声明我们就会放在.h为后缀的头文件中,函数的实现放在.c为后缀的源文件中。

我们以加法函数和运行举例:

在完成加法函数板块后我们又该如何使用呢?

原来我们只需要像使用库函数一样,包含该函数的头文件即可。

3.static和extern

i.作用域和生命周期

通常来说,一段程序代码中所用到的文字并不总是有效的,而限定这个名字可用性的代码范围就是这个名字的作用域如:
•   全局变量的作用域是整个程序
•   局部变量的作用域是该变量所在的范围
生命周期指的是从变量创建(申请内存空间)到变量销毁(收回内存)的时间段。如:
•   全局变量的生命周期是:整个程序的生命周期
•   局部变量的声明周期是:进入作作用域变量创建,出作用域变量销毁的时间段

ii.extern

C语言中extern用来声明外部符号

iii.static

static是静态的意思,有以下几点作用:

•   修饰全局变量

•   修饰局部变量

•   修饰函数

static修饰局部变量

对比两串代码,我们发现在static修饰局部变量t后打印结果发生了明显的改变,由原来的五次循环,每次循环值不变变为了五次循环,每次循环值+1

这是因为:

代码1的test函数中的局部变量i是每次进⼊test函数先创建变量(⽣命周期开始)并赋值为0,然后
++,再打印,出函数的时候变量⽣命周期将要结束(释放内存)。
代码2中,我们从输出结果来看,i的值有累加的效果,其实 test函数中的i创建好后,出函数的时候是不会销毁的,重新进⼊函数也就不会重新创建变量,直接上次累积的数值继续计算。
从而我们得出 结论:
static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本来⼀个局部变量是存储在内存的栈区的,但是被static 修饰后存储到了静态区。存储在静态区的变就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作用域不变的。所以我们在以后写代码的过程中,如果希望一个变量出了函数后,能够保留值而不被销毁,便使用static进行修饰。
static修饰全局变量

extern用来声明外部符号,如果文件A定义了符号,文件B想要使用,便可以使用extern来进行声明;但当不同文件的全局变量被static修饰后,即使使用extern进行声明,也不能正常使用。

得出结论

⼀个全局变量被static修饰后,使得这个全局变量只能在本源⽂件内使用,不能在其它源文件内使用。本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使用;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用了,其它源文件,即使声明了,也无法正常使用。
static修饰函数

由此看来,static无论是修饰全局变量还是函数,在不同文件中被修饰后都无法使用extern声明使用。

所以当存在一个函数只想在所在的源文件内部使用,不想被其它源文件使用,就可以使用static修饰。

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
sscanf函数是C语言中一个非常常用的函数,它可以将一个字符串按照指定的格式转换成相应的数据类型。在嵌入式开发中,sscanf函数也是非常常见的,因为很多时候需要从串口或者其他外部设备中读取数据,并将其转换成相应的数据类型进行处理。下面是一些sscanf函数的使用技巧: 1. 使用sscanf函数时一定要注意格式字符串的正确性。格式字符串中的占位符必须与待转换的数据类型相对应,否则会发生未知错误。 2. 如果待转换的字符串中包含多个数据,可以使用多个占位符进行转换。例如,如果待转换的字符串为"1,2,3",可以使用" %d,%d,%d"的格式字符串进行转换。 3. 可以使用sscanf函数的返回值来判断转换是否成功。如果返回值等于待转换字符串的长度,则说明转换成功,否则转换失败。 4. 如果待转换的字符串中包含浮点数,可以使用"%f"或者"%lf"的格式字符串进行转换。 5. 如果待转换的字符串中包含十六进制数,可以使用"%x"的格式字符串进行转换。 6. 如果待转换的字符串中包含字符或字符串,可以使用"%c"或者"%s"的格式字符串进行转换。 7. 如果待转换的字符串中包含指针类型的数据,可以使用"%p"的格式字符串进行转换。 总之,在使用sscanf函数时一定要注意格式字符串的正确性,否则很容易出现转换错误的情况。同时,还应该注意sscanf函数返回值的判断,以确保转换的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小六学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值