定义
函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main()
函数就是功能,每一个函数用来实现一个特定的功能,函数的名字应反映其代表的功能
定义函数应包括:
- 指定函数的名字,以便以后按名调用
- 指定函数的类型,即函数返回值的类型
- 指定函数的参数的名字和类型,以便在调用函数时向它们传递数据(对无参函数不需要)
- 指定函数应当完成什么操作,也就是函数时做什么,即函数的功能
定义函数的方法
定义无参函数:
类型名 函数名()
{
函数体
}
或
类型名 函数名(void)
{
函数体
}
void表示空,即函数没有参数。函数体包括声明部分和语句部分
定义有参函数:
类型名 函数名(形式参数表列)
{
函数体
}
函数体包括声明部分和语句部分
定义空函数:
类型名 函数名()
{ }
函数体是空的。调用此函数时,什么工作也不做,没有任何实际作用
调用函数
一般形式:
函数名(实参表列)
- 函数调用语句
把函数调用单独作为一个语句
例如:printf_star( );——不要求函数带回值,只要求函数完成一定的操作
- 函数表达式
函数调用出现在另一个表达式,是赋值表达式中的一部分,此时要求函数带回一个确定的值以参加表达式的运算
例如:c=2*max(a,b);
- 函数参数
函数调用作为另一个函数调用时的实参
例如:m=max(a,max(b,c));
只有作为函数调用语句才需要有分号。若作为函数表达式或函数参数,函数调用本身是不必有分号的
printf(“%d”,max(a,b));
数据传递
在定义函数时函数名后面括号中的变量名称为“形式参数”或“虚拟参数”
形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”
实际参数可以是:常量、变量和表达式
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参
在调用函数过程中,系统会把实参的值传递给被调用函数的形参。该值在函数调用期间有效,可以参加该函数中的运算。
例如:
#include<stdio.h>
int max(int x, int y)//定义max函数,有两个参数
{
int z;//定义临时变量
z = x > y ? x : y;//把x和y中的大值赋给z
return z;//把z返回main主函数
}
int main()
{
int max(int x, int y);//对max函数的声明
int a, b, c;
printf("请输入两个整数:");//提示输入数据
scanf("%d,%d", &a, &b);//输入两个整数
c = max(a, b);//调用max函数,有两个实参,最大值赋给c
printf("最大数为:%d\n", c);//输出c
return 0;
}
结果:
请输入两个整数:12,11
最大数为:12
函数调用过程
- 形参在未出现函数调用时,并不占内存中的存储单元,发生函数调用时,函数的形参才能分配内存单元
- 实参的值传递给对应形象
- 若形参已有值,可以利用形参进行有关的运算
- 通过return语句将函数值带回到主调函数。执行return语句就把这个函数返回值带回主调函数main
返回值的类型与函数类型一致
若不要返回值,则函数类型应定义为void类型
- 调用结束,形参自动销毁,实参单元仍保留并维持原值
若在执行一个被调用函数时,形参的值发生改变,不改变主调函数的实参的值
只能由实参传给形参,实参和形参在内存中占有不同的存储单元,实参无法得到形参的值
传值调用:对形参的修改不会影响实参
传址调用:
把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
返回值
- 函数的返回值是通过函数中的return语句获得
- 函数值的类型
- 指定的函数类型一般应该和return语句中的表达式类型一致
函数类型决定返回值的类型
- 对于不带返回值的函数,应当用void类型
函数声明和原型
函数声明:已定义的函数首行,在加一个分号
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数
声明决定不了。 - 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的声明一般要放在头文件中的
函数原型:函数首部
函数声明的一般形式:
- 函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2,……,参数类型n 参数名n);
- 函数类型 函数名(参数类型1,参数类型2,……,参数类型n);
函数的嵌套调用
实例:
#include <stdio.h>
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i = 0;
for(i=0; i<3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
结果:
hehe
hehe
hehe
函数的递归调用
递归:程序调用自身的编程技巧
递归调用:在调用函数的过程中又出现直接或间接地调用该函数本身
两个必要条件:
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。
例如:
#include <stdio.h>
void print(int n)
{
if(n>9)
{
print(n/10);
}
printf("%d ", n%10);
}
int main()
{
int num = 1234;
print(num);
return 0;
}
结果:
1 2 3 4
栈溢出:系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况
栈溢出的解决方法:
- 将递归改写成非递归。
- 使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代
nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问