1. 函数的概念
C语言中函数(function)最直观的翻译是"子程序",它是一小段代码,用来完成某个特定的任务的代码。
函数的好处:1.可以拆分任务 2.可以复用,提升开发效率
2. 库函数
C语言并不直接提供库函数,它只是规定了语法规则,让C语言编译器的厂商来根据国标(ANSI C)来实现库函数。
库函数相关头文件 https://zh.cppreference.com/w/c/header 中文版
https://cplusplus.com/reference/clibrary/ 英文版
3. 自定义函数
3.1 函数的语法形式
ret_type 是函数返回类型
fun_name 是函数名
括号中是形参
{}括起来的是函数体
3.2 函数举例
写一个加法函数,完成两个整形变量相加
上面一段代码展示了函数定义和应用的最简单最基本情况,读者可以仿照着敲一下来深度理解函数的定义和应用,以及函数的传参过程。
函数名是自定义的,根据实际情况起名。
参数也是自定义的,需要几个,需要什么类型都视情况而定。
函数是否返回值也跟据实际情况设定,如果不需要返回就用void定义函数返回类型。
4. 形参和实参
4.1实际参数
函数调用时传递给函数的参数
在上面一段代码中,a和b实际给到了函数add()所以它们是实参
4.2 形式参数
在函数中定义的参数
在上面一段代码中,x和y是形式参数,如果只定义了add函数但是不传参调用的话,x和y是不向内存申请空间的,它们只是形式上存在,当传参后形参才被实例化了。
4.3 实参和形参的关系
形参是实参的一份临时拷贝,对形参的修改是不会影响实参的,它们是各自独立的内存空间。
5. return语句
return语句的注意事项:
1.return后面可以跟一个数值,也可以跟一个表达式,表达式计算完后返回结果。
2.return后面可以什么都不跟,代表什么都不返回,函数一般是void型。
这段代码是void型函数提前返回的实例,也就是说即使是void型函数也可以写return只是不返回任何值而已。
3.return的返回类型和函数返回类型不一致,系统会隐式将其改成函数返回类型
4.return执行后,函数会彻底结束,函数后面的代码不再执行
5.如果函数中存在if分支语句,要保证每种if分支都有return返回,否则编译错误
当函数不接受参数时又不写void,就可以随意向函数中传参,只是没有人接收参数,函数也能正常执行。
如果注明void(函数不接收传参),这时再鲁莽的传参,编译器就会提醒和报错
当函数返回类型什么都不写的时后默认返回int形
6. 数组做函数的参数
写一个函数,将一个整形数组中的内容都改为-1,再写一个函数打印整个数组
上面一段代码展示了数组如何在函数中传参,以及数组传参的特性(数组的形参的改变可以影响实参)
注意:
1.实参和形参的名字可以相同
2.数组传参写的是数组名
3.形参数组可以省略元素个数(一维数组)(写上也是摆设),(二维数组可以省略行,不能省略列)
4.数组传参后形参数组和实参数组是同一块地址,改变形参就改变了实参(因为在数组传参的时候传的是首元素地址,形参接收的是地址,在指针中有讲解)
7. 函数的嵌套调用和链式访问
7.1 嵌套调用
写一个程序计算某年某月有多少天
这段代码中展示了在days_of_month函数中嵌套了if_leap_year的实例,因为有闰年的存在,所以二月份的天数并不稳定,因此在判断二月份天数的时候要判断一下是否是闰年,而我把判断闰年的功能用函数封装起来了,这样就形成了函数的嵌套。
7.2 链式访问
将一个函数的返回值作为另一个函数的参数,将两个函数像链条一样串起来
上面这段代码是用上一个printf函数的返回值,作为下一个printf函数的参数,形成了函数的链式访问,这段代码同时也展示了printf函数的一个特性(在代码注释中已经提到),就是printf函数的返回值是在屏幕上打印的总共的字符个数
我可以在前面提到的网址中找到关于printf函数返回值的描述。
8.函数的声明和定义
8.1单个文件
之前的代码中我都是将函数的定义放在主函数的前面,而这次的代码我将函数放在主函数的后面,这样的话就需要在主函数之前声明这个函数,告诉编译器我的程序中有这个函数。
注意:
编译器在编译的时候是从上往下扫描的,如果在调用函数之前没有定义的话会报警告
C语言中要求函数要先声明再使用
函数的定义是一种更强大的声明,所以把函数定义在要使用它之前就不用再特别声明了
8.2 多个文件
在企业中函数的声明和定义是怎么使用的?
.c 是源文件(存放函数的实现)
.h 是头文件(存放函数的声明)
这是主函数源文件
这是函数源文件
这是包含声明函数的头文件
上面的项目中,我将函数存放在别的源文件中,这时我就需要声明这个函数,无论是用一个头文件声明,还是直接在主函数上面声明都行。但是推荐使用头文件声明函数,这样项目看起来会过呢共有调理,而且注意在包含自己创造的头文件的时候要注意用双引号包含
8.3 static 和 extern
static 和 extern 都是C语言中的关键字
static是静态的意思,可以用来:
1.修饰全局变量
2.修饰局部变量
3.修饰函数
extern 是用来声明外部符号的
全局变量:在main()函数外面定义的变量,全局变量的作用域是整个工程(能在自定义的函数中直接使用)
局部变量:在main()函数内部定义的变量,作用域只是自己的大括号内
生命周期:变量从向内存申请空间到销毁的过程
8.3.1 static修饰局部变量
static修饰局部变量本质上影响了变量的存储类型,一个局部变量应该放在栈区,但是被static修饰后就放在了静态区.
这也是为什么上面这段代码a的值继承下来了。但是要注意的是 a 的作用域并没有变还是只能在test()函数中使用,并不能在函数外使用。
8.3.2 extern声明外部符号
我在另外一个源文件中定义了一个整形变量a,用extern声明它我就可以使用了,此时的a相当于一个全局变量。
但是如果把a在外部的源文件中定义成 static int a 就会发现即使声明之后也不能用了,这是因为static在修饰全局变量的时候将它的外部链接属性变成了内部链接属性,也就是变成了只能在变量原本存在的源文件中全局使用了,其他源文件是看不到这个全局变量的,这就是static修饰全局变量的特点。
8.2.2 static修饰函数
与static修饰全局变量相似,在定义函数类型前用static修饰后,函数的外部链接属性也变成了内部链接属性,只能在自己所在源文件中使用。