- 函数的定义
- 函数是一块代码,接受0个或者多个参数,做一件事情,并返回0个或者一个值,是用于完成特定任务的独立程序代码单元
- 调用函数
- 函数名(参数值);
- 参数表中的参数为形式参数----也即是参数
- 调用函数时给的值就是实际参数---其实是值
- ()起到了表示函数调用的重要作用,即使没有参数也需要()
- 如果有参数,则需要给出正确的数量和顺序
- 这些值会被按照顺序依次用来初始化函数中的参数
- 函数名(参数值);
- 函数返回
- 函数知道每一次是哪里调用它,然后返回到正确的地方
- 从函数中返回值(这个就类似与汇编语言中的RET---表示函数返回)
- return停止函数的执行,并返回一个值,将这个值交给调用函数的地方
- return;
- return 表达式;
- 返回的值可以赋值给变量、再传递给函数、也可以丢弃
- 没有返回值的函数
- void 函数名(参数表)
- 不能使用带值的return(return 0;或者return 变量;都不可以)
- 可以没有return
- 调用的时候不能做返回值的赋值
- 如果函数有返回值,则必须使用带值的return
- 在C语言中,自定义函数需要写在main函数的上面,因为C的编译器是自上而下分析代码的
- 函数声明
- 如果想要让main函数放在最前面呢?
- 把自定义函数头放在main()之前即可
- 也即函数声明
- 如果想要让main函数放在最前面呢?
- 函数原型(就是用于函数声明的那一句语句)
- 函数头加分号构成了函数原型(一般要用函数原型来声明函数)
- 函数原型的目的
- 告诉编译器这个函数长什么样子
- 名称
- 参数
- 返回类型
- 告诉编译器这个函数长什么样子
- 函数原型的参数可以不写参数名字
- 参数传递(传值)
- 可以传递给函数的值是表达式的结果包括
- 字面量
- 变量
- 函数返回值
- 计算的结果
- 调用函数时给的值与参数的类型可以不严格匹配(这是C语言的一个漏洞)
- 编译器总是会悄悄的将类型转换好,但是很多时候我们也许并不希望这样
- 对于C++/Java在这方面就很严格
- C语言在调用函数时,永远只能传值给函数
- 可以传递给函数的值是表达式的结果包括
如左图中所示:子函数中的a、b发生的交换但是main函数中的a、b并没有发生交换; 因为主函数中的a、b和子函数中的a、b并没有关系 |
- 每一个函数都有自己的变量空间,参数也位于这个独立的空间中,和其他的函数没有关系
- 局部变量(本地变量)
- 函数每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称为局部变量
- 定义在函数内部的变量就是局部变量
- 参数表中的参数也是局部变量
- 变量的生存期和作用域
- 生存期:从变量开始出现,在内存中分配了这个变量的存储空间到系统回收这个空间之间的时间
- 作用域:也就是变量在哪个代码段可以起作用,这个代码段就是作用域
- 生存期和作用域其实就在变量所在的那个块(大括号)内
- 局部变量是定义在块内的
- 可以是定义在函数的块内
- 可以是定义在语句的块内(例如if语句的块内)
- 甚至可以随便拉一对大括号来定义变量
- 程序运行到这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了
- 块的外面定义的变量在块的里面仍然有效
- 块里面定义了和外面同名的变量时会掩盖外面的变量
- 不可以在同一个块里面重复定义
- 局部变量不会被默认初始化
- 参数在进入函数的时候被初始化了
- 函数庶事
- 如果确定函数没有参数----那么使用void f(void)
- void f()表示函数的参数表未知,但是并不表示没有参数,一般情况下不这么写,这就表示没有使用函数原型来声明函数
- 有参数就写清楚
- 没有参数就写void
- 注意int main()
- 逗号运算符和逗号怎么区分?
- 调用函数时圆括号里面的逗号是标点符号,不是运算符---f(a,b)
- 如果再加一层圆括号就表示是逗号运算符---f((a,b))
- 子函数内部不可以再去定义子函数
- 可以放另一个子函数的声明
- 关于main函数
- int main()也是一个函数
- 也可以写成 int main(void)
- return 0是有意义的
- 如果返回0,可以用于表示这个程序正确的执行完了
- 如果返回非0,则表示这个程序在执行过程中出现了错误
- 当main()函数和其他函数放在一起的时候,程序最开始执行的第一条语句就是main()函数的第一条语句
- main()可以被自己或者其他函数调用,但是很少这样用
- 在C标准库中,函数被分为多个系列,每个系列都有各自的头文件。每个头文件中除了其他内容,还包括了本系列所有函数的声明
- 例如:stdio.h头文件包含了标准I/O库函数的声明
- math.h头文件包含了各种数学函数的声明
- 递归
- 函数自己调用自己的过程称为递归
- 递归的几个要点
- 每次函数调用都会有自己的变量
- 每次函数调用都会返回一次,并且程序必须按顺序逐级返回递归
- 递归函数调用之前的语句,均按照被调用函数的顺序执行
- 递归函数调用之后的语句,均按照被调用函数的逆顺序执行
- 递归调用就相当于又从头开始执行函数的代码
- 递归函数必须包含能让递归调用停止的语句
- 例如可以选择函数参数等于某特定值时作为递归的终止条件
- 尾递归
- 最简单的递归形式
- 把递归调用置于函数的末位,即正好在return语句之前
- 每次递归都会创建一组变量,因此递归使用的内存更多,而且每次递归调用都会把创建的一组新变量放在栈中,因此递归调用受限于内存空间
- 因为每次函数调用都会花费一定时间,因此递归的执行速度也较慢
- 递归的优缺点
- 优点:递归为某些编程问题提供了最简单的解决方案
- 缺点:递归算法会快速消耗计算机的内存资源并且递归不便于维护和阅读
- 注意:所有的C函数都是平等的
- 每一个函数都可以被其他函数调用或者调用其他函数
- 多源代码文件的程序
- win系统的IDE中的编译器是面向项目的。
- project描述的是特定程序使用的资源
- 多文件程序需要将源代码文件加入到同一个项目中,确保所有的源代码文件都在项目列表中列出
- 项目一般只管理源代码文件,源代码中的#include管理该源代码使用到的头文件
- 头文件
- 头文件干啥的?
- 在C语言中,C的函数库是按照类型进行分类保存的
- 在编写代码时如果要调用这个函数,则必须要写出函数原型用于函数声明,在C中,这些函数的函数原型按照类别存放在相应的.h头文件中。
- 这样一来每次写程序之前,将可能用到的C库函数的函数原型所在的头文件先include一下,就不必再麻烦的去声明C库函数了
- 我们在编写自己项目时也可以这样自定义
- 把函数原型和已定义的字符常量放在头文件中是一个良好的编程习惯
- 符号常量:
- 一般使用#define 宏 替换体,其中宏就是符号常量
- 我们用符号常量代替替换体,有点类似于用一个记号代替一组数据
- 头文件干啥的?
- 编译的时候就会宏展开,由替换体代替宏
- 预处理器指令从#开始,到后面的换行符为止
- 一般指令仅限1行
- 用'\'反斜杠来延伸到下一行
- 什么是预处理器指令?(有点类似于汇编语言中的伪指令---告诉编译器该怎么汇编)
- #define、 #include、 #ifdef、 #else、 #endif、 #ifndef、 #if、#elif、 #line、 #error、 #pragma类似这种以#开头的指令
- 一个不严谨但好理解的表述
- 预处理器是在程序执行之前查看程序并采取一定动作的一个“器件”
- 预处理器执行预处理指令
- 工作就是将一些文本在执行程序之前转换为另外一些文本
- 宏展开
- 把符号缩写转换为其表示的内容