目录
1.return后面可以是一个数值,也可以是一个表达式,也可以什么都没有。
2.若return返回的值,与函数返回类型不一致,系统则隐式转换return返回的值为函数的返回类型。
4.如果函数中存在 if 等分支语句,则需要保证每种情况下都有return返回,否则导致编译错误。
4.数组传参,形参不会创建新的数组。形参操作的数组和实参的数组是同一个数组。
一、函数的概念
我们在生活中见过函数的概念,如一元一次函数y = kx+b。
C语言也引入函数的概念,有些翻译为“子程序”。
C语言的程序其实是由无数个小的函数组合而成。一个函数如果能够完成某项特定任务的话,这个函数可以复用的,能够提升开发软件的效率。
简而言之,C语言的函数就是一个完成某项特殊任务的一小段代码。
在C语言中,我们一般会见到两类函数,一种是库函数,一种是自定义函数。
二、库函数
(一)标准库和头文件
C语言标准中规定了C语言的各种语法规则,但不提供库函数。
C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库。不同的编译器厂商根据ANSI提供的C语言标准给出一系列函数的实现,这些函数就叫做库函数。
printf、scanf等都是库函数,库函数也是函数。这些函数是现成的,我们可以直接使用,在一定程度上提升了效率。
C/C++官方链接C 标准库头文件 - cppreference.com
(二)库函数的使用方法
(三)使用
(四)库函数文档的一般格式
1.函数原型
2.函数功能介绍
3.参数和返回类型说明
4.代码举例
5.代码输出
6.相关知识链接
三、自定义函数
(一)语法形式
- ret_type 是函数返回类型,有时候可以使用void来表示什么都不返回
- fun_name 是函数名
- 括号中放的是形式参数
- {}括起来的是函数体
(二)使用
写⼀个加法函数,完成2个整型变量的加法操作。
根据实际需要来设计函数,函数名、参数、返回类型都是可以灵活变化的,上面只是一个例子。
四、形参和实参
(一)概念理解
使用中的函数参数分为两类:实际参数和形式参数,分别简称实参和形参。
以上面的例题为例子:第一个{}中的a和b 是形式参数;第二个{}中调用Add函数时传递给Add函数的a和b,为实际参数。
只有在Add函数被调用的过程中,为了存放实参传递过来的值,形式参数才向内存申请空间。
(二)形参和实参的关系
1.代码运行时的过程关系
调用函数:需要写参数,但形参的名字可以命名。以上面的题为例子:
当代码读到 int c = Add_(a , b ),便开始调用Add函数,向空间申请内存,将a 和 b 的值拷贝一份,存放在Add_( )函数中。代码执行后,返回 x + y 的值。
2.在空间地址上的的关系
值得一提的是, a 、 b 的地址与 x 、y 的地址,不一样。它们各自有自己的空间,自己的地址。
因此可以理解为,形式参数 x 和 y ,只是 a 和 b 的一份临时拷贝;就连地址,也只是暂时存在的。
五、return语句
(一)注意事项
1.return后面可以是一个数值,也可以是一个表达式,也可以什么都没有。
若是表达式,则先执行表达式,再返回表达式的结果。
若是什么都没有,则适合函数返回类型是void的情况。
2.若return返回的值,与函数返回类型不一致,系统则隐式转换return返回的值为函数的返回类型。
3.return语句执行后,函数彻底返回,程序不再执行。
4.如果函数中存在 if 等分支语句,则需要保证每种情况下都有return返回,否则导致编译错误。
六、数组做函数参数
(一)数组传参的注意事项
1.函数的形参和实参个数要匹配
2.形参如果是一堆数组,则数组的大小可以省略不写
3.形参若是二维数组,行能省略,但列不行
4.数组传参,形参不会创建新的数组。形参操作的数组和实参的数组是同一个数组。
七、嵌套调用
以解决“某一年某月有多少天”的问题来实践嵌套调用。
八、链式访问
链式访问,就是将一个函数的返回值作为另一个函数的参数,像链条一样把函数串联起来。
(一)举一个简单的例子:
如果将 strlen 的返回值作为 printf 的返回值,又会如何呢?
而这,就是一个链式访问的例子了:
(二)再看一个例子:
会打印出什么呢?
其实,这个例子的难点在于 printf 的返回值。
printf 函数返回的是打印在屏幕上的字符的个数。
在上面的代码中,第一个prinf打印的是第二个printf的返回值,第二个printf打印的是第三个printf的返回值。
第三个printf打印43,屏幕上的字符数量为2,返回2;
第二个printf打印2,屏幕上的字符数量为1,返回1;
第一个printf打印1
屏幕上最终打印:4321
九、函数的声明和定义
(一)在使用函数时,要先声明后调用。
上面的 int is_leap_year(int y) 就是函数的声明。如果将 int main() 与 int is_leap_year(int y) 调换位置,编译器可能会发出警告。因为编译器对代码进行编译的时候,是自上到下进行的。
(二)函数可以嵌套调用,但不能嵌套定义。
(三)多个文件
代码较多时,我们根据程序的功能,将代码拆分放在多个文件当中。
一般情况下,函数的声明、类型的声明放在头文件(.h), 函数的实现放在源文件(.c)当中。
但是也可以拆分放在三个文件当中,这样我们写代码就更加方便了。
(四)static 和extern
static 和extern 都是C语言的关键字。
在了解它们之前,我们先来了解一下作用域和生命周期。
作用域:
程序设计概念。通常来说,一段程序代码中的所用到的名字并不总是有效(可用)的。而限定这个名字的可用性的代码范围,就是这个名字的作用域。
- 局部变量的作用域就是变量所在的局部范围
- 全局变量的作用域就是整个工程(项目)
生命周期:
是指变量创建(申请内存)到变量销毁(收回内存)的一个时间段。
- 局部变量的生命周期:进入作用域变量创建,生命周期开始,出作用域生命周期结束
- 全局变量的生命周期:整个程序的生命周期
1.extern
exturn 是用来声明外部符号的。
如果⼀个全局的符号在A文件中定义,在B文件中想使用,就可以使用extern 进行声明。然后就可以使用了。
2.static
static是静态的意思,可以用来:
- 修饰全局变量
- 修饰局部变量
- 修饰函数
(1)修饰局部变量
图一 图二
对比图一和图二的代码运=运行效果,来理解static在局部变量中的作用
图一:test函数中的局部变量 i ,是每次进⼊test函数先创建变量(生命周期开始)并赋值为0,然后 ++,再打印,出函数的时候变量生命将要结束(释放内存)。
图二:从输出结果来看,i 的值有累加的效果。用static 修饰局部变量 i ,在i创建好后,出函数的时候是不会销毁的,重新进入函数也不会重新创建变量,直接从上次累积的数值继续计算。
结论:static改变了变量的生命周期。因此,未来⼀个变量出了函数后,如果我们还想保留值,等下次进入函数继续使用,就可以使用static 修饰。
那么,static 究竟是如何改变变量的生命周期的呢?
其实,生命周期的改变,本质上是变量的存储类型的改变。一个局部变量最初是存储在栈区的,经过static修饰之后,便存储到了静态区。一旦存储在静态区,变量的生命周期就和全局变量的生命周期一样了:只有程序结束,变量才被销毁,内存才收回。
但值得注意的是,即使变量被存储到了静态区,它的作用域仍不变。
(2)修饰全局变量
add.c add.c
text.c text.c
代码一 代码二
运行结果:代码一正常,代码二出现链接性错误。
结论:一个全局变量被static修饰后,只能在本源文件中中使用,不能在其他源文件中使用。
本质原因:
全局变量被默认为具有外部链接属性,想在外部的文件中使用,只需要适当的声明即可。
但是全局变量被static修饰之后,外部链接属性就变成了内部链接属性,就只能在自己所在的源文件内使用了。在其他源文件声明,也无法使用。
值得一提的是,全局变量具有的外部链接属性具有一定的不安全性,只要声明就能够使用。
因此,一个全局变量,只想在所在的源文件内使用,不想被其他文件发现,可以用static修饰。
(3)修饰函数
add.c add.c
text.c text.c
代码一 代码二
运行结果:代码一正常,代码二出现链接性错误。
结论: static 修饰函数和 static 修饰全局变量是一样的,⼀个函数在整个工程都可以使用, 被static修饰后,只能在本文件内部使用,其他文件无法正常的链接使用。
本质原因:
函数默认具有有外部链接属性,使得函数在整个工程中只要被适当的声明就能够被使用。
但被static修饰后,函数就具有了内部链接属性,使得函数只能在所在的源文件内部使用。
因此,一个函数只想在所在的源文件内使用,不想被其他源文件使用时,可以用static来修饰。
到这里就结束了,如有后续会继续补充。