9.0.0 函数
9.1.0 函数的基本使用
9.1.1 函数的定义
可以使用函数来实现代码的重用。
- 首先将那段想要被重用的代码塞到函数中,只要塞一次。
- 什么时候想要执行这个函数中的代码,就只要调用这个函数就可以了
- 只需要塞一次,可以多次调用。
- 函数的定义:
- 位置:应该定义在main函数的外面,也就是写在源文件下
- 语法:
返回值类型 函数名称(【参数列表】)
{
写上那段需要被重用的代码(函数体);
}
9.1.2 函数的调用
函数的调用
- 当执行代码时,CPU只会执行main函数中的代码。
- 如果我们想要执行自定义函数中的代码,只要在想要执行的地方调用这个函数就可以了。
- 调用函数的语法:
函数名();
- 当CPU执行的代码时在调用函数的时候,CPU会跳转到这个函数的内部去执行这个函数内部的代码,执行完毕后,再返回来继续往下执行。
9.1.3 注意事项
- 定义函数的位置一定要在main函数之外;
- 目前为止,返回值的类型直接写void;
- 函数名称:
- 命名要符合标示符的命名规则和规范;
- 函数的命名:
- 函数的名称一定要有意义
- 一个函数代表一个相对独立的功能,所以函数的名称最好采用动词,一看就知道在做什么事情
- 定义函数的函数名后面的小括号,必不可少
- 定义函数的函数名后面的小括号后面没有分号
- 定义一个函数,如果这个函数不被调用,那么这个函数内的代码永远不会被执行
- 写在大括号内的代码称之为函数体,定义函数名称的那句代码叫做函数头
- 调用函数的时候的小括号也是必不可少的
9.1.4 什么时候需要定义函数
- 当有一段代码需要重用的时候,这个时候就可以使用函数,将那段代码封装在这个函数中,什么时候想要执行这段代码,什么时候就调用这个函数。
- 在写代码的时候,如果发现一段代码是在完成一个相对独立的功能,我们也可以将这段代码封装在1个函数中,因为有可能将来还会用到这段代码。
9.1.5 函数的调试
- 在调试的时候,如果高亮选中的代码是在调用一个函数,这个时候按下step over只会将这个函数中的代码一下执行完毕
- 如果希望能看到CPU执行函数中每一句代码的过程,需要按下step in
9.2.0 全局变量与局部变量
9.2.1 什么叫全局变量和局部变量
- 局部变量
定义在函数内部的变量,叫做局部变量。
- 全局变量
定义在函数外部的变量,叫做全局变量。直接定义在源文件下面。
9.2.2 全局变量和局部变量的异同点
- 相同点:都是变量,都是在内存中开辟的空间来储存数据。
- 不同点:
- 声明的位置不同:局部变量在函数内部,而全局变量声明在函数的外面;
- 作用域不同:局部变量只能在函数内部访问,而全局变量从定义这个变量开始后面所有的函数都可以访问这个全局变量。一般情况下,全局变量都是定义在最顶上的,即#include下面。
- 默认值不同(不为其赋值的话):
- 局部变量的默认值是一个垃圾值
- 全局变量的默认值是0,char变量的默认值是\0,\0代表1个不可见的字符,这个字符的ASCII码就是0
- 创建和回收的时间不同:
- 局部变量:CPU在执行声明变量的时候才会在内存中声明,当作用域结束以后就会自动回收
- 全局变量:程序一启动就在内存中创建变量,程序结束的时候才会回收变量
9.2.3 全局变量的特点
- 程序一启动就会创建,直到程序结束的时候才回收,所以无论在哪一个函数中访问全局变量,访问的都是同一个变量,具备共享性。将数据声明为全局变量,所有的函数都可以访问,都可以共享这个数据。
- 什么时候我们需要把1个数据声明为全局变量呢?
如果这个数据想要被所有的函数访问,那么就把这个数据声明为全局变量
注意:函数如果不被调用,那么函数内部的代码和变量是不会被执行和调用的。
- 可以定义一个局部变量的名字和全局变量是同样的名字。
这个时候,在函数内部从定义同名局部变量开始后面访问的变量都是这个局部变量。
但虽然可以这么做,但是不建议这样操作。
9.3.0 函数的进阶使用
9.3.1 带参数的函数
一个函数就是一个相对独立的功能,而我们的程序实际上就是由一个一个的小功能组合起来的。
我们的需求:
函数的内部有一个数据,这个数据的取值是多少,函数内部不确定,调用者在调用这个函数的时候,才将这个数据给它。
实现的功能就是调用者在调用函数的时候,将自己的数据传递到函数的内部让函数使用。这个时候我们就需要用到带有参数的函数。
9.3.2 函数的参数
void test(int num)
- 声明在函数名称后面的小括号中的变量,就叫做这个函数的参数。
- 在小括号中声明参数的时候,其实就是声明一个变量,后面不需要加分号。
- 参数的本质就是定义在函数内部的一个局部变量。
- 所以在函数内部不能再定义一个局部变量的名称和参数的名称一样
- 所以在函数的内部可以直接访问参数的值
- 在调用一个带有参数的函数的时候,就必须要为这个函数的参数赋值。
函数名(要赋值给参数的数据);
test(10);
- 形参和实参:
- 形参:声明在函数名的小括号里面的参数叫做形式参数,简称形参。
- 实参:在调用函数的时候,需要为形参赋值,这个值就叫做实际参数,简称实参。
9.3.3 函数的调用执行步骤
- 先加载被调用的函数
- 声明函数的参数
- 将实参的值赋值给形参
- 执行函数体
- 函数体执行完毕后,返回继续往下执行
9.3.4 函数的参数的作用
可以将调用者的数据传递到函数的内部中去使用。
在声明参数的时候,如果不指定参数的类型,直接写参数的名称是允许的。这个参数的默认类型就是int类型。
9.3.5 带多个参数的函数
声明多个参数的方式:在小括号中声明多个变量就可以了,每个变量声明用逗号隔开。
调用的时候,将要赋值个形参的实参,依次的写在小括号中,用逗号隔开就可以了。
void test(int num1, int num2, int num3)
{
函数体;
}
调用时:
test(20,30,40);
9.3.6 return关键字
return关键字用在函数体之中。
在函数体中如果遇到了return关键字,就会立即结束这个函数的执行。
- 函数的结束有两种方式:
- 自然结束,当函数执行完毕之后,就会结束这个函数;
- return结束,可以使用return来提前结束一个函数。
当我们的函数需要提前结束的时候,就可以使用return关键字。
9.3.7 带返回值的函数
当函数执行完成之后,会有一个结果。而这个结果,函数应该把它返回给调用者。
- 函数如何将数据返回给调用者:
- 先确定要返回给调用者的数据的类型;
- 修改函数的返回值类型为要返回的数据的类型;
- 在函数结束之前,使用return关键字将数据返回
例子:
int test(int num1, int num2)
{
int num3 = num1 + num2;
return num3;
}
- 调用者如何拿到函数的返回数据:
声明一个和函数返回值类型相同的变量来接收这个返回值就可以了。
例子:
int num = test(10,20);
- C语言中使用0表示假,使用非0表示真。所以,如果一个函数的返回数据是真或者假的时候,返回值应该写int类型的。
- return后面的数据可以是数据,也可以写一个表达式,如果是表达式返回的就是这个表达式的结果。
- 注意:
- 函数的返回值类型代表函数执行完成以后,有一个这个类型的数据要返回给调用者。
- void代表函数执行完毕之后,没有任何数据返回给调用者。
- 一旦函数指定了返回值类型,那么就必须在函数结束之前使用return返回响应类型的数据。
- 如果函数有返回值,那么调用者可以选择接收或者不接收。
- 一旦你的函数定义了返回值,那么就必须要保证函数体的每一个分支结束前都有返回值。
- 有关return关键字:
- 在无返回值的函数中,只能直接使用return代表直接结束函数,不能后面跟数据;
- 在有返回值的函数中,必须在函数结束前使用return返回一个数据。这个时候return代表立即结束函数,并返回指定的数据。
9.3.8 函数的声明
- 一般来说,函数的定义都放在main函数的前面,以免在运行编译的时候CPU没有发现相关的函数而报错。但是有一个方法是可以把函数的定义放在main函数的后面的,这就是函数的声明。
- 创建一个函数的完整步骤,应该分为两步:
- 函数的声明
直接写上函数头后面加一个分号就可以了。
声明的作用是告诉编译器,这个函数是有的,只不过这个函数体在后面或者其他的地方。
例如:void test();
- 函数的定义
和之前函数的定义是一样的。
- 注意:
- 如果函数写了声明,那么这个函数的定义就可以放在调用函数的后面了;
- 函数的声明最晚应该放在调用函数的那段代码的前面。一般情况下函数的声明都是统一放在最顶部的;
- 如果被调用的函数在主调函数之前,那么被调用函数的声明可以省略,可以直接定义;但是如果被调函数的定义在主调函数的后面,那就必须先声明;
- 声明函数的函数头必须和定义函数的函数头完全一致;
- 声明函数的参数的名称可以不写;
例如:
int test(int,int);可以不写参数名
- 如果函数的返回值是int类型的,可以不写这个函数的声明。
- 一般做法:
- main函数无论如何排在第一位;
- 无论什么情况,函数都要写声明和定义。声明写在最顶部#include下面,定义写在main函数的后面;
- 无论如何,都建议声明的函数头和定义的函数头都保持一致
9.4.0 多文件开发
9.4.1 预处理代码
C语言的代码主要分为两类:
- C代码。我们之前学习的代码都叫做C代码
- 预处理代码。在编译之前执行的代码,以#开头的代码就叫做预处理代码。
- 预处理指令的分类:
- 文件包含指令:#include
- 宏定义:#define
- 条件编译指令:#if
- 预处理指令的特点:
- 都是以#开头
- 预处理指令后面没有分号
- 在编译的时候,检查语法之前执行
9.4.2 文件包含指令
#include
- 作用:可以将指定的文件的内容拷贝到写指令的地方。
- 语法:
#include “文件路径”
#include <文件路径>
- 作用:
#include “/users/alanchou/Desktop/1.txt”
在编译之前,就会执行预处理代码。
这个时候,#include指令的作用,将后面这个txt文件的内容拷贝到写include指令的地方。
然后再检查语法,生成.o目标文件。
就相当于,写指令的这个地方实际上写的文件中的内容。
- 注意:
- 被包含的文件中的代码必须要符合C语法规范,否则再编译检查语法的时候就会报错。
- 如果被包含的文件不存在,也会报错。
- 一般情况下,被包含的文件不应该随便放置,而是放在源文件目录下,和当前.c文件同目录下。
- 如果我们将被包含的文件放在和源文件同一个目录下,写这个文件的路径的时候就不用写全路径了,而是写一个相对路径:和当前源文件的路径相同的部分删除掉就可以了。
- 绝对路径:路径从根目录开始。例如:/users/alanchou/desktop/1.txt
- 相对路径:相对于当前这个文件夹的路径。和当前文件路径相同的部分删除,这样的路径就叫做相对路径。
- 文件路径用双引号和尖括号的异同点:
- 都是将指定的文件的内容包含到写指令的地方
- 不同点在于寻找指定文件的方式不一样:
- 如果文件路径用双引号引起来:#include “1.txt”
- 先去当前源文件所在目录中查找这个文件,如果有,直接包含;
- 如果没有,就去系统自带的编译器目录中查找,如果有直接包含,如果没有就报错。
- 如果文件路径用尖括号括起来:#include <1.txt>
- 直接去编译器目录中查找,如果有就包含,如果没有就报错。
- 如果文件路径用双引号引起来:#include “1.txt”
- 系统自带的编译器目录
- C语言有一个标准的函数库:系统自带了很多的函数.
苹果已经把开发中常用的那些功能写成了函数集成在了C语言当中。它将其功能类似的那些函数声明放在了一个.h的头文件中,再将所有的头文件放在系统的编译器目录中。
例如:stdio.h stdlib.h等
stdio.h 这个头文件中存储的函数声明都是和输入输出相关的函数。
stdlib.h 这个头文件存储的是一些核心函数。
如果我们要调用C语言函数库中的函数:
- 先将这个函数的声明所在的头文件包含进来;
- 然后调用这个函数
- 使用建议:
- 我们自定义的被包含文件,不要乱放,放在源文件同一个目录下;
- 如果包含的文件是我们自定义的文件,那么使用双引号“”;
- 如果包含的文件是系统自带的文件,那么使用尖括号< >.
9.4.3 多文件开发
一个C程序其实就是一个一个的函数组成的。当我们的程序很庞大的时候,那么程序中的函数就会很多。如果将这些所有的函数都写在main.c中,那么就会:
- 混乱,不方便管理
- 不利于团队开发
每一个人负责一个模块的开发。
程序其实都是一个一个的模块组成的,每个模块就是一个小功能。
- 所以如何分模块来开发呢?
- 写模块的人要提供两个文件:
.h文件,head头文件,专门用来声明函数
.c文件,专门用来写函数的定义
- 如果有人想要使用这个模块中的函数,只需要包含这个模块的头文件即可。
#include “test.h”
- .h和.c的文件都是写模块的人来负责,要调用的人只需要在代码中包含那个头文件即可。
- 如果需要修改函数、新增和删除函数的时候,要连.h文件一起修改
- 总结:
- 当我们的程序中的函数过多的时候,就要考虑模块开发
- 一个模块至少要包含两个文件,一个.h头文件,一个.c函数定义文件。谁要调用这个模块中的函数,只需要在代码中包含这个模块的头文件即可。