一.什么是函数?
维基百科中对函数的定义:子程序
在计算机科学中,子程序,是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。
简单来说:函数是组织好的,可重复使用的,用来实现单一或者相关联功能的代码段。
C 语言中的函数往往是独立地实现了某项功能。一个程序由多个函数组成,可以理解为「一个程序由多个小的功能叠加而成」。
二.C语言中函数的分类
- 库函数
- 自定义函数
1.库函数
为什么会有库函数?
- 学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。
- 在编程的过程中频繁的做一些字符串的拷贝工作(strcpy)。
- 在编程中总是会计算n的k次方这样的运算(pow)。
像上面描述的基础功能,在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。
C 语言在发布时已经为我们封装好了很多函数,它们被分门别类地放到了不同的头文件中,使用函数时引入对应的头文件即可。这些函数都是专家编写的,执行效率极高,并且考虑到了各种边界情况。
C 语言自带的函数称为库函数。库是编程中的一个基本概念,可以简单地认为它是一系列函数的集合,在磁盘上往往是一个文件夹。C 语言自带的库称为标准库,其他公司或个人开发的库称为第三方库。
简单的总结,C语言常用的库函数都有:
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
注:库函数不需要全部记忆,只需要基本了解并学会查询库函数。
除了库函数,我们还可以编写自己的函数,拓展程序的功能。自己编写的函数称为自定义函数。自定义函数和库函数在编写和使用方式上完全相同。
2.自定义函数
自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。
函数是一段可以重复使用的代码,用来独立地完成某个功能,它可以接收用户传递的数据,也可以不接收。接收用户数据的函数在定义时要指明参数,不接收用户数据的不需要指明,根据这一点可以将函数分为有参函数和无参函数。
1.无参函数的定义
ret_type fun_Name()
{
//body
}
- ret_type 是返回值类型,它可以是 C 语言中的任意数据类型,例如 int、float、char 等。
- fun_Name 是函数名,它是标识符的一种,命名规则和标识符相同。函数名后面的括号( )不能少。
- body 是函数体,它是函数需要执行的代码,是函数的主体部分。即使只有一个语句,函数体也要由{ }包围。
- 如果有返回值,在函数体中使用 return 语句返回。return 出来的数据的类型要和 ret_type 一样。
例如,定义一个函数,计算从 1 加到 100 的结果:
int sum()
{
int i, sum = 0;
for (i = 1; i <= 100; i++)
{
sum += i;
}
return sum;
}
累加结果保存在变量 sum 中,最后通过 return 语句返回。sum 是 int 型,返回值也是 int 类型,它们一一对应。return 是 C 语言中的一个关键字,只能用在函数中,用来返回处理结果。
完整代码为:
#include <stdio.h>
int sum()
{
int i, sum = 0;
for (i = 1; i <= 100; i++)
{
sum += i;
}
return sum;
}
int main()
{
int a = sum();
printf("The sum is %d\n", a);
return 0;
}
运行结果:
The sum is 5050
函数不能嵌套定义,main 也是一个函数定义,所以要将 sum 放在 main 外面。函数必须先定义后使用,所以 sum要放在 main 前面。
无返回值函数
有的函数不需要返回值,或者返回值类型不确定(很少见),那么可以用 void 表示,例如:
void hello()
{
printf("Hello,world \n");
//没有返回值就不需要 return 语句
}
void 是 C 语言中的一个关键字,表示“空类型”或“无类型”,绝大部分情况下也就意味着没有 return 语句。
2.有参函数的定义
如果函数需要接收用户传递的数据,那么定义时就要带上参数。如下所示:
ret_type func_Name( dataType1 param1, dataType2 param2 ... )
{
//body
}
dataType1 param1, dataType2 param2 ...
是参数列表。函数可以只有一个参数,也可以有多个,多个参数之间由,
分隔。参数本质上也是变量,定义时要指明类型和名称。与无参函数的定义相比,有参函数的定义仅仅是多了一个参数列表。
数据通过参数传递到函数内部进行处理,处理完成以后再通过返回值告知函数外部。
更改无参函数的例子,计算从 m 加到 n 的结果:
int sum(int m, int n)
{
int i, sum = 0;
for (i = m; i <= n; i++)
{
sum += i;
}
return sum;
}
参数列表中给出的参数可以在函数体中使用,使用方式和普通变量一样。
调用 sum() 函数时,需要给它传递两份数据,一份传递给 m,一份传递给 n。你可以直接传递整数,例如:
int xhz= sum(1, 100); //1 传递给 m,100 传递给 n
也可以传递变量:
int ct = 4;
int rl = 86;
int xhz = sum(ct, rl); //begin 传递给 m,end 传递给 n
也可以整数和变量一起传递:
int num = 33;
int xhz = sum(num, 80); //num 传递给 m,80 传递给 n
函数定义时给出的参数称为形式参数,简称形参;函数调用时给出的参数(也就是传递的数据)称为实际参数,简称实参。函数调用时,将实参的值传递给形参,相当于一次赋值操作。后面将说明实参和形参。
原则上讲,实参的类型和数目要与形参保持一致。如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型,例如将 int 类型的实参传递给 float 类型的形参就会发生自动类型转换。
将上面代码补全:
#include<stdio.h>
int sum(int m, int n)
{
int i, sum = 0;
for (i = m; i <= n; i++)
{
sum += i;
}
return sum;
}
int main()
{
int ct = 4;
int rl = 86;
int xhz = sum(ct, rl);
printf("The sum from %d to %d is %d\n", ct, rl, xhz);
return 0;
}
运行结果:
The sum from 5 to 86 is 3731
定义 sum() 时,参数 m、n 的值都是未知的;调用 sum() 时,将 ct、rl 的值分别传递给 m、n,这和给变量赋值的过程是一样的,它等价于:
m = ct;
n = rl;
三.函数的形式参数和实际参数
1.形参(形式参数)
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据。
2.实参(实际参数)
真实传给函数的参数,叫实参。
实参可以是:常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
3.形参和实参的区别和联系
- 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
- 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
- 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
- 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。
- 形参和实参虽然可以同名,但它们之间是相互独立的,互不影响,因为实参在函数外部有效,而形参在函数内部有效。
请看下面的例子:
#include <stdio.h>
//计算从m加到n的值
int sum(int m, int n)
{
int i;
for (i = m + 1; i <= n; ++i)
{
m += i;
}
return m;
}
int main()
{
int a, b, total;
printf("Input two numbers: ");
scanf("%d %d", &a, &b);
total = sum(a, b);
printf("a=%d, b=%d\n", a, b);
printf("total=%d\n", total);
return 0;
}
运行结果:
Input two numbers: 1 100↙
a=1, b=100
total=5050
在这段代码中,函数定义处的 m、n 是形参,函数调用处的 a、b 是实参。通过 scanf() 可以读取用户输入的数据,并赋值给 a、b,在调用 sum() 函数时,这份数据会传递给形参 m、n。
从运行情况看,输入 a 值为 1,即实参 a 的值为 1,把这个值传递给函数 sum() 后,形参 m 的初始值也为 1,在函数执行过程中,形参 m 的值变为 5050。函数运行结束后,输出实参 a 的值仍为 1,可见实参的值不会随形参的变化而变化。
以上调用 sum() 时是将变量作为函数实参,除此以外,你也可以将常量、表达式、函数返回值作为实参,如下所示:
total = sum(10, 98); //将常量作为实参
total = sum(a + 10, b - 3); //将表达式作为实参
total = sum(pow(2, 2), abs(-100)); //将函数返回值作为实参
更改上面的代码,让实参和形参同名:
#include <stdio.h>
//计算从m加到n的值
int sum(int m, int n)
{
int i;
for (i = m + 1; i <= n; ++i)
{
m += i;
}
return m;
}
int main()
{
int m, n, total;
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
total = sum(m, n);
printf("m=%d, n=%d\n", m, n);
printf("total=%d\n", total);
return 0;
}
运行结果:
Input two numbers: 1 100
m=1, n=100
total=5050
调用 sum() 函数后,函数内部的形参 m 的值已经发生了变化,而函数外部的实参 m 的值依然保持不变,可见它们是相互独立的两个变量,除了传递参数的一瞬间,其它时候是没有瓜葛的。
四. 函数返回值(return )
函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。
return 语句的一般形式为:
return 表达式;
或者:
return (表达式);
有没有( )都是正确的,为了简明,一般也不写( )。例如:
return max;
return a+b;
return (100+200);
对 C 语言返回值的说明:
-
没有返回值的函数为空类型,用 void 表示。例如:
void func() { printf("jntm\n"); }
一旦函数的返回值类型被定义为 void,就不能再接收它的值了。例如,下面的语句是错误的:
int a = func();
为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为 void 类型。
-
return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值。例如:
//返回两个整数中较大的一个 int max(int a, int b) { if (a > b) { return a; } else { return b; } }
如果 a>b 成立,就执行 return a,return b 不会执行;如果不成立,就执行 return b,return a 不会执行。
-
函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用。例如:
//返回两个整数中较大的一个 int max(int a, int b) { return (a>b) ? a : b; printf("Function is performed\n"); }
第 4 行代码就是多余的,永远没有执行的机会。
五.函数的调用
所谓函数调用(Function Call),就是使用已经定义好的函数。函数调用的一般形式为:
fun_Name(param1, param2, param3 ...);
functionName 是函数名称,param1, param2, param3 …是实参列表。实参可以是常数、变量、表达式等,多个实参用逗号,分隔。
函数调用的方式有多种,例如:
//函数作为表达式中的一项出现在表达式中
z = max(x, y);
m = n + max(x, y);
//函数作为一个单独的语句
printf("%d", a);
scanf("%d", &b);
//函数作为调用另一个函数时的实参
printf( "%d", max(x, y) );
total( max(x, y), min(m, n) );
函数的嵌套调用
函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。
请看下面的例子:
#include <stdio.h>
void new()
{
printf("hehe\n");
}
void line()
{
int i = 0;
for(i=0; i<3; i++)
{
new();
}
}
int main()
{
line();
return 0;
}
运行结果:
hehe
hehe
hehe
【示例】计算 sum = 1! + 2! + 3! + … + (n-1)! + n!
#include <stdio.h>
//求阶乘
long factorial(int n)
{
int i;
long result=1;
for(i=1; i<=n; i++)
{
result *= i;
}
return result;
}
// 求累加的和
long sum(long n)
{
int i;
long result = 0;
for(i=1; i<=n; i++)
{
//在定义过程中出现嵌套调用
result += factorial(i);
}
return result;
}
int main()
{
printf("1!+2!+...+9!+10! = %ld\n", sum(10)); //在调用过程中出现嵌套调用
return 0;
}
运行结果:
1!+2!+...+9!+10! = 4037913
sum() 的定义中出现了对 factorial() 的调用,printf() 的调用过程中出现了对 sum() 的调用,而 printf() 又被 main()调用,它们整体调用关系为:main() --> printf() --> sum() --> factorial()
如果一个函数 A() 在定义或调用过程中出现了对另外一个函数 B() 的调用,那么我们就称 A() 为主调函数或主函数,称 B() 为被调函数。
当主调函数遇到被调函数时,主调函数会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回主调函数,主调函数根据刚才的状态继续往下执行。
一个 C 语言程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条。这个链条的起点是 main(),终点也是 main()。当 main() 调用完了所有的函数,它会返回一个值(例如 return 0;)来结束自己的生命,从而结束整个程序。
函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码,当遇到函数调用时,CPU 首先要记录下当前代码块中下一条代码的地址(假设地址为 0X1000),然后跳转到另外一个代码块,执行完毕后再回来继续执行 0X1000 处的代码。整个过程相当于 CPU 开了一个小差,暂时放下手中的工作去做点别的事情,做完了再继续刚才的工作。
从上面的分析可以推断出,在所有函数之外进行加减乘除运算、使用 if…else 语句、调用一个函数等都是没有意义的,这些代码位于整个函数调用链条之外,永远都不会被执行到。
六.函数的声明和函数原型
函数声明:
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
- 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的声明一般要放在头文件中的。
函数声明的格式非常简单,相当于去掉函数定义中的函数体,并在最后加上分号;,如下所示:
dataType fun_Name( dataType1 param1, dataType2 param2 ... );
也可以不写形参,只写数据类型:
dataType fun_Name( dataType1, dataType2 ... );
函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,称为函数原型。函数原型的作用是告诉编译器与该函数有关的信息,让编译器知道函数的存在,以及存在的形式,即使函数暂时没有定义,编译器也知道如何使用它。
【实例 1】定义一个函数 sum(),计算从 m 加到 n 的和,并将 sum() 的定义放到 main() 后面。
#include <stdio.h>
//函数声明
int sum(int m, int n); //也可以写作int sum(int, int);
int main()
{
int begin = 5, end = 86;
int result = sum(begin, end);
printf("The sum from %d to %d is %d\n", begin, end, result);
return 0;
}
//函数定义
int sum(int m, int n)
{
int i, sum=0;
for(i=m; i<=n; i++)
{
sum+=i;
}
return sum;
}
我们在 main() 函数中调用了 sum() 函数,编译器在它前面虽然没有发现函数定义,但是发现了函数声明,这样编译器就知道函数怎么使用了,至于函数体到底是什么,暂时可以不用操心,后续再把函数体补上就行
【实例 2】定义两个函数,计算 1! + 2! + 3! + … + (n-1)! + n!的和。
#include <stdio.h>
// 函数声明部分
long factorial(int n); //也可以写作 long factorial(int);
long sum(long n); //也可以写作 long sum(long);
int main()
{
printf("1!+2!+...+9!+10! = %ld\n", sum(10));
return 0;
}
//函数定义部分
//求阶乘
long factorial(int n)
{
int i;
long result=1;
for(i=1; i<=n; i++)
{
result *= i;
}
return result;
}
// 求累加的和
long sum(long n)
{
int i;
long result = 0;
for(i=1; i<=n; i++)
{
result += factorial(i);
}
return result;
}
运行结果:
1!+2!+...+9!+10! = 4037913