第五章 函数
本文章内容参考中国铁道出版社《C程序设计(第三版)》
用于记录学习中的一些重要点
欢迎交流学习,有错误请多多包涵,尽管指出
简单函数调用执行过程梗概
first
给形式参数分配内存空间
second
传实型参数(有必要要先计算其表达式)
C语言规定实参到形参为单向传递(若要调整实参变量,涉及到后面的指针传递)
即对形参的改变不会影响实参
对于实参表达式,一般按照从右至左的顺序求值
(总之没事不要瞎折腾那些带副作用的奇怪东西)
third
给函数局部变量分配内存空间
forth
执行函数体
fifth
执行完函数后释放函数调用的内存空间
sixth
(如果有返回值)返回值到函数调用处
p.s.函数说明
此外还有函数声明,常用于调用 执行处后 或 其他程序文件中 定义的函数
值得注意的是,函数不能在别的函数中被定义,但可以在别的函数中被声明
实例
#include <stdio.h>
int main()
{
double a, b ;
double min ( double , double ) ;//函数声明
printf("Input a,b :") ;
scanf("%lf%lf" , &a , &b ) ;
printf("MIN(%f,%f) = %f\n" , a , b , min(a, b) ) ;
return 0 ;
}
double min ( double x ,double y )
{
return x < y ? x : y ;
}
函数递归
主要分为:
直接递归调用
不断调用这个函数本身
间接递归调用
两个函数相互交替调用
主要思想
对一个问题进行分解,新问题规模小于原问题,但是解决方法与原问题相同(在这里体现为同一个函数)
可以称之为"递"
最终得到一个最小的规模的解已知的问题
一般称为"递归边界"
最后不断返回
称之为"归"
递归与递推
递归主体是选择结构
递推主题是循环结构
递归分解问题规模直至最基本情况为止,之后通过回溯获得指定规模的解
递推通过不断循环渐进的到达终止条件就结束,没有回溯的过程
存储类别 & 作用域
既然已经讲到了函数的使用,那么我们应该从现在开始了解一些基础的存储相关的知识,以便于理解编写函数时一些需要注意的点
标识符是指常量、变量、语句标号以及用户自定义函数的名称
程序中标识符的进一步的属性:存储类别 、 存储期 、 作用域
1. 存储类别
存储类别用于确定以此标识符明明的对象(例如变量)的 存储期 作用域 连接 etc属性
其中:
存储期 指对象存在的期限
作用域 指该标识符可以在程序中被引用的区域
连接 指该程序由多个源文件组成时,仅标识符存在的源文件可见 还是 适当声明后其他源文件也可见
对于以下四种存储类别关键词:
auto register 用于声明具有自动存储期的变量
extern static 用于声明具有静态存储期的变量和函数 注:这个学问挺深!需多加理解!
注:只有 全局变量和函数名 static声明的局部变量 两种标识符具有静态存储期
- 自动的auto
所有局部变量默认的存储类型,一般省略
局部变量指函数定义中声明的变量,仅定义函数中可见
其可以分为 函数定义 or 函数形参 or 复合语句 中的局部变量 - 寄存器的register
表示建议 (因为实际上不一定能够) 让编译系统将改变量装载到计算机的某个高速硬件寄存器中
适用于 具有自动存储期的 整形or指针类型的 变量
通常用于频繁使用的计数器等整形变量,以优化对内存的使用 - 外部的extern
全局变量 and 函数名 默认情况为extern存储类型
全局变量指在函数定义外声明的变量,在整个程序声明周期存在,在程序开始执行时一次性分配和初始化
使用extern(即默认)的 全局变量 or 函数 可以被其他程序源文件使用
使用方法见后续拓展 - 静态的static
-
修饰局部变量时:
具有静态存储期
具体来说是第一次运行到此时创建标识定义,然后在整个程序生命周期存在,退出函数时并不同普通局部变量一样经过析构函数销毁
与全局变量不同的是只能被定义它的函数使用
示例void foo1() { static int a = 10 ; printf("a = %d\n", a ) ; a -- ; return ; } int main() { printf("请输入输出次数 : n = ") ; int n ; scanf("%d" , &n ) ; for ( int i = 1 ; i <= n ; i ++ ) foo1() ; return 0; } /*请输入输出次数 : n = 5 *a = 10 *a = 9 *a = 8 *a = 7 *a = 6 */
-
修饰全局变量时:
表示该全局变量仅当前源程序文件中可见 -
修饰函数时:
表示该函数具有静态存储期的特性,旨在当前源文件的函数中使用,不可在别的源文件中使用
-
拓展:调用不同源文件中的函数(注意:再次修正时发现此处存在较大的执行性差异,请甄别来看)
在后续学习中,可能会遇见代码量很大的情况,这时我们如果可以将不同作用的函数放在不同的文件中,显然可以大大优化代码的结构
于是我们需要学习一下如何调用不同源文件中的函数
以下介绍一种基础方式即原理,其他的方式可以类推
首先我们有两个源文件:
-
函数定义源文件(function.h)
实际命名成.c后缀也行
实际上,一般会用function.c
的文件中对函数进行定义,而在function.h
的文件中进行声明#include <stdio.h> int add ( int a , int b) { return a + b ; } int a = 10 , b = 20 ;
-
负责调用的主文件(main.c)
值得注意的是:头文件处调用要用 “” 而非 <>
另外从各种层面来讲,最好放在同一个文件目录之中,不确定在不同文件目录是是否可以这样实现调用
后期修订: 对于上面一行斜体所说的,在学习文件处理技术之后,实际上""
中可以是文件的绝对地址(目前我还没明白相对地址怎么用),没有地址默认在同一级文件夹中寻找;相应的,<>
则是默认在系统提供的文件中寻找这个头文件库#include "function.h" int main() { printf("a + b = %d" , add(a , b )) ; return 0; }
存储类别与变量的可见性和存在性
存储类别 | 变量种类 | 可见性 | 存在性 | 默认初值 |
---|---|---|---|---|
auto | 局部 形参 | 定义范围内 | 离开定义范围清除 | 不确定 |
register | 局部 形参 | 定义范围内 | 离开定义范围清除 | 不确定 |
extern | 全局 | 本文件 其他文件 | 整个程序生命周期 | 0 |
static | 局部 | 定义函数中 | 离开定义范围仍保留 | 0 |
static | 全局 | 本文件 | 整个程序声明周期 | 0 |
2. 作用域规则
标志符的作用域:标识符可以被使用的程序段落
标号标识符(?)的作用域是声明标号的函数,该函数内可使用这个标号
此处未能很好理解书本意思,个人理解就是函数内部声明的变量等的作用域就是这个函数
函数外声明的标识符,若为 全局变量 or 函数原型说明 ,其作用域从声明处开始至源程序文件结束
函数 or 复合语句 中声明的标志符,例如
函数形参 作用域为定义形参的函数
函数局部变量 作用域为定义所在的程序块 (也就是从定义处开始到该块的右花括号)
值得注意的是:嵌套语句中,内部和外部标识符同名时,优先可见内部该标识符,外部标识符就等于是不可见了!