一、函数的概念和分类
1. 函数的概念
就是将一个较大的程序分为一个个程序模块,让每个模块都能实现每个特定的功能
2.函数的分类
(1) 库函数
库函数不能自己定义,可以直接使用,但是在使用库函数的时候,必须包含一定的头文件。库函数不用专门的去记它,我们在学习 c语言的过程中会一个一个的去了解到的,也有专门的网站可以系统的去学习它
(2)自定义函数
在库函数不满足的情况下,我们可以根据自己的需要来自己自定义函数
例题:写一个加法函数,完成2个整型变量的计算
#include<stdio.h>
int Add(int x, int y) {
//被调函数,在这里我们定义了名为Add,函数类型为int,形参也是int类型的,而x,y则是形参便于接收主函数传参过来的数据
//注意:它的类型是int,而不是其他类型,在主函数中传参过来的值也是int型的,
int t = 0;
t = x + y;
return t;
//这里的返回值是t而不是0,是因为我们要把计算完的结果返回到主函数中的c
}
//
int main() {
//主函数
int a, b;
scanf("%d%d", &a, &b);
int c = Add(a, b);
//Add是实际参数,用于传递参数的数据
printf("%d", c);
return 0;
}
二、函数的参数
1.实际参数(实参)
实参就是真实传递给函数的参数。也就是在调用函数的过程中,系统会把实参的值传递给被调用函数的形参
2.形式参数(形参 )
在自定义函数时,函数后面的括号内的值就是形参,这就便于接收主函数传过来的数据
示例:用函数来完成2个实数的平均值.
#include<stdio.h>
float av(float x,float y) {
//被调函数,定义av的函数名,函数类型为float,形参是x,y,形参的类型也是float
float t = 0;
t = (x + y) / 2.0;
return t;
//返回值是t,计算结果要返回到主函数中
}
int main() {
float a, b;
scanf("%f %f", &a, &b);
float c = av(a, b);
//a,b是实际参数,用于与被调函数之间进行数据传递
printf("%.1f", c);
return 0;
}
需要注意一点的是如果调用的是无参函数,括号是不能省略的,如上图主函数中的*(float c=av(a,b))*,a和b可以没有,但是括号不能省略
总结:
- 函数中指定的形参变量,在未使用的时候,没有占据内存空间;而在使用的过程会被临时分配在内存空间中,调用结束了,形参所占的内存空间会得到释放。
- 函数一旦被定义,可以多次调用,但是必须保证实际参数和形式参数的数据类型是一样的。
- 实际参数可以是常量,有确定值的变量或者常量表达式
- 实参对形参的数据传递是“单向传递”,只能由实参传递给形参,而形参不能反向传递给实参
3.return语句
一般形式
return (表达式);
return 数值;
return;
//这个形式适合函数返回类型是(void)的情况
注意事项
- return后面可以是一个数值,或是一个表达式,如果是表达式就先执行表达式,在返回表达式的结果
- 一个函数中可以有多个return语句,执行到那个return语句,哪一个就以作用。
- 在定义函数时应该指定函数的类型,并且与函数类型要一致,如果return的返回值与函数的返回类型不一致,系统会自动将返回的值隐式(自动进行类型转换)转化为函数的返回类型
- 如果函数中存在分支语句,那么要保证每种情况下都要有return返回,否则会出现编译错误
四、嵌套调用和链式访问
1.嵌套调用
一个函数在被调用的过程中可以调用其他函数,这就是函数的嵌套调用
示例:(1)计算某年某月有多少天,用函数来实现
tmp_year
//根据月份确定是否是闰年
days_month
//调用tmp_year确定是否是闰年后,再根据月份来计算这个月的天数
#include<stdio.h>
int is_year(int y) {
//被调用的函数(形参)
if (y % 4 == 0 && y % 100 != 0 || y % 400 == 0)
return 1;
//如果是闰年,则返回1;
else
return 0;
//否则返回0;
}
int days_month(int y, int m) {
//被调用的函数(形参)
int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = days[m];
if (is_year(y) && m == 2)
//判断如果是2月份,那我们就加1
day += 1;
return day;
}
int main() {
主函数
int year = 0;
int month = 0;
scanf("%d %d", &year, &month);
int days = days_month(year, month);
//实际参数
printf("%d\n", days);
return 0;
}
示例:(2)计算1!+ 2!+3 !+……+10!的值并输出,使用函数的嵌套调用实现。
#include<stdio.h>
#define N 10
//宏定义
float fac(int k) {
int i = 0;
float t = 1;
for (i = 2; i <= k; i++)
t *= i;
return t;
}
float sum(int n) {
float fac(int k);
int i=0;
float s = 0;
for (i = 1; i <= n; i++)
s += fac(i);
return s;
}
int main() {
//主函数
float sum(int m);
//声明sum
printf("1!+ 2!+3 !+……+10!\n", sum(N));
return 0;
}
2.链式访问
将一个函数的结果(返回值)作为另一个函数的参数,这种形式将函数串联起来就是函数的链式访问
示例:
#include<stdio.h>
int main() {
printf("%d", printf("%d", printf("%d", 43)));
//首先输出的是43这个值,也是因为它输出有4和3算两个值,然后第二个printf则输出的是2,所以到第一个printf的时候它就是一个值而已
return 0;
}
五、函数的定义和声明
函数的定义
示例:输入两个数,用函数计算它们的平均值。
#include<stdio.h>
float av(float x, float y) {
//函数定义
float t = 0;
t = (x + y) / 2.0;
return t;
}
int main() {
float a, b;
scanf("%f %f", &a, &b);
float c = av(a, b);
//函数的调用
printf("%.1f", c);
return 0;
}
第一个框是函数的定义,第二个是函数的调用
函数的声明
就是告诉编译器函数名是什么,参数是什么,返回的类型是什么。函数的调用一定要满足先声明后使用
示例:(1)通过自定义函数,求任意两个整数的和
被调函数写在主函数的前面,可以不必声明
#include<stdio.h>
int av(int x, int y) {
return x + y;
int main() {
int a, b;
scanf("%d %d", &a, &b);
printf("%d", a,b,sum(a,b));
return 0;
}
示例:(2)输入两个实数,用一个函数求出两数中较小的数
主函数写在被调函数之前,那我们就要进行声明
#include<stdio.h>
int main() {
float x, y, z;
float av(float x, float y);
scanf("%d %d", &x, &y);
z = av(x, y);
printf("%d", z);
return 0;
}
float av(float a, float b) {
float c;
if (a > b)
c = b;
else
c = a;
return c;
}
六、数组作为函数的参数
1.数组元素作为函数参数
这也是一种单向传递(值传送),用法跟普通的变量相同,但数组元素不能作为函数的形参使用
示例:交换数组arr[0]和arr[1]的值
#include<stdio.h>
void av(int x, int y) {
//用的是void类型,不需要返回任何值
int t;
t = x;
x = y;
y = t;
printf("%d %d", x, y);
}
int main() {
int arr[2] = { 1,2 };
av(arr[0],arr[1]);
//这里并没有定义任何的函数类型,所以返回的是void(无类型)
return 0;
}
2.数组名作为函数参数
除了有数组元素作为函数参数外,数组名也可以作为函数参数 ,它不仅可以作为实际参数,也可以作为形式参数。
示例:我们修改上述案例,用数组名作为函数的参数,对temp函数的值进行交换
`#include<stdio.h>
void av(int x[2]) {
//不需要返回的函数类型
int t;
t = x[0];
x[0] = x[1];
//这里用的是数组的下标进行交换
x[1] = t;
printf("%d %d", x[0], x[1]);
}
int main() {
int arr[2] = { 1,2 };
av(arr);
//未定义任何函数类型
return 0;
}`
数组传参需要*注意
*
形式参数如果是数组,数组的大小可以省略不写,但是如果是二维数组,那么行可以省略,但列不能省略;形参操作的数组和实参的数组是同一个数组;数组传参,形参是不会创建新的数组