1、什么是函数
首先,什么是函数?函数(function)是完成特定任务的独立程序代码。单元语法规则定义了函数的结构和使用方式。虽然C中的函数和其他语言中的函数、子程序、过程作用相同,但是细节上略有不同。
为什么使用函数?
首先,使用函数可以省去编写重复代码的苦差,当程序需要多次实现同种功能的时候,只需要编写一个合适的函数,就可以省去很多的时间和代码篇幅;其次,即使程序只完成某项任务一次,也值得使用函数。因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。
2、函数的分类
C语言中函数分为库函数和自定义函数。
2.1库函数
为什么有库函数?
1、在学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想 把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格 式打印到屏幕上(printf)。
2、在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
3.、在编程时我们也计算,总是会计算n的k次方这样的运算(pow)。
像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。
学习库函数我们就非常需要一些辅助网站或者工具,例如:
- MSDN(Microsoft Developer Network)
- www.cplusplus.com
- http://en.cppreference.com(英文版)
- http://zh.cppreference.com(中文版)
这里附上第一个工具的下载链接:链接:https://pan.baidu.com/s/1HMd4INww7KHBvcSjMSZ6Og?pwd=1234
提取码:1234
C语言常用的库函数有:IO函数,字符串操作函数,字符操作函数,内存操作函数,时间/日期函数,数学函数,其他库函数。使用库函数一定要包含#include对应的头文件,一些不常用的函数我们不知道其对应的头文件,这时候就需要通过工具来查看其对应的头文件。
2.2自定义函数
有的时候程序员需要实现的功能是非常复杂的,库函数也没有提供实现相应功能的函数,这个时候就需要就需要我们程序员来进行编写自定义函数,
自定义函数和库函数一样,有函数名,返回值类型和函数参数,但不一样的是自定义函数中,以上这些都是我们程序员设计,这就使得自定义函数实现的功能比库函数实现功能要复杂且精巧。
函数的组成语法:
ret_type fun_name(para1, * )
{
statement;//语句项
}
名词解释:
ret_tyoe:返回类型
fun_name:函数名
paral:函数参数
这里先编写一下几个简单的函数来深刻认识一下自定义函数,例如找最大值函数和交换变量函数:
void swap(int x, int y)
{
printf("x = %d,y = %d\n", x, y);
int temp = 0;
temp = x;
x = y;
y = temp;
printf("x = %d,y = %d\n", x, y);
}
int main(void)
{
int x = 10, y = 20;
int m_max = max(x, y);
printf("x和y里的最大值为:%d\n", m_max);
swap(x, y);
return 0;
}
3、函数的参数
3.1实际参数(实参)
真实传给函数的参数叫实参,实参可以是:常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行该函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
3.2形式参数(形参)
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内 存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
形参实例化之后其实相当于实参的一份临时拷贝
通过代码我们将更为深刻地认识实参和形参这两个概念:
4、函数的调用
4.1传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
4.2传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
当我们不需要修改实参的时候,应该使用传值调用,防止在函数内修改我们不需要修改的实参;当我们需要修改实参的时候,应该使用传址调用,函数内部也能修改函数外部的变量。
5、函数的嵌套调用和链式访问
5.1嵌套调用
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
怎么理解这句话,在大型程序设计的时候,一个功能里面经常需要使用其他功能函数,C语言是允许自定义函数互相调用的。用一个简单的例子来检验一下C语言是否支持:
函数可以嵌套调用,但是不能嵌套定义。
5.2链式访问
把一个函数的返回值作为另外一个函数的参数。
scanf和printf函数就可以进行链式访问。详情可查阅这篇博客:https://blog.csdn.net/sakura0908/article/details/130298477?spm=1001.2014.3001.5501
6、函数的声明和定义
6.1 函数声明
函数声明语法:
ret_type fun_name(para1, * );
1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数 声明决定不了。
2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
3. 函数的声明一般要放在头文件中的。
声明函数时必须声明函数的类型,带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应该声明为void。如果没有声明函数的类型,旧版本的C编译器会假定函数的类型是int。这一管理源于C的早期,那时的函数绝大多数都是int类型,然而C99标准不再支持int类型函数的这种假定设置。
6.2 函数定义
函数定义语法:
ret_type fun_name(para1, * )
{
//函数功能实现
statement;//语句项
}
函数的定义是指函数的具体实现,交待函数的功能实现。
函数声明和函数定义常用于多文件编写的程序中,函数声明放在自定义的的头文件.h中,而函数定义放在一个对应的.c文件中,在主函数的.c文件中需要包含自定义的头文件之后才能使用自定义的函数。
7、函数递归
程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接 调用自身的 一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。 递归的主要思考方式在于:把大事化小
递归的两个必要条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续
- 每次递归调用之后越来越接近这个限制条件
在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出) 这样的信息。 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
学习函数递归,就不得不说函数求阶乘这一经典案例,数学中,阶乘的定义是:一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。
那如何解决上述的问题:
1. 将递归改写成非递归。
2. 使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代 nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问