函数的概念
模块化编程:
- 用于完成特定任务的程序代码单元
- 那就是把一个小功能封装成一个独立的代码段(函数)
- 封装前和封装后执行的结果一样。
函数的作用: - 增加代码的复用性
- 增加代码的可读性
//输出一维数组中最大值
int getMaxNum(int *a, int length) {
int maxNum = a[0];
for (int i = 0; i < length; ++i) {
if (maxNum < a[i]) {
maxNum = a[i];
}
}
return maxNum;
}
int main(void) {
int a[5] = {1, 2, 7, 9, 3};
//输出一维数组中最大值
// int maxNum = a[0];
// for (int i = 0; i < 5; ++i) {
// if (maxNum < a[i]) {
// maxNum = a[i];
// }
// }
// printf("%d", maxNum);
printf("%d", getMaxNum(a, 5));
return 0;
}
-
无参函数:void fun(void)
参数啥也不写 void fun():表示参数个数不确定
image.png
-
C语言中可以不写返回值类型,不写的话是int类型。
-
函数名字要符合命名规则:
- 首字母:只能是大小写字母、下划线(_),不能用数字做首字母。
- 非首字母:数字、字母、下划线
- 不能用与系统重复的函数名
- 函数名字的长度没有限制
- 主函数main是操作系统调用的
- 函数地址:函数名字就是函数的地址。所以函数调用的时候,本质是:函数地址(参数列表)。
void fun() {
printf("11111");
}
int main(void) {
//打印函数地址
printf("%p",fun);//0x100e25f10
return 0;
}
代码区存代码,方法的名字就是方法的地址,调用方法就是从方法地址的头地址还是调用。fun和&fun都是函数的地址(和int a=12; 不同。a是12,即地址里面的数据,&a是地址 , fun==&fun
void fun() {
printf("11111");
}
int main(void) {
(&fun)();//11111
fun();//11111
return 0;
}
C里面,被调用的函数一定放到前面

函数声明
为了解决函数顺序问题,出现了函数声明。
- 在函数外声明
//函数声明,顺序没要求
void fun(void);
void fun2(void);
int main(void) {
fun();
fun2();
return 0;
}
void fun(void) {
printf("11111");
fun2();
}
void fun2(void) {
printf("2222");
}
2.在函数内声明
int main(void) {
//函数声明
void fun(void);
void fun2(void);
fun();
fun2();
return 0;
}
void fun(void) {
printf("11111");
fun2();
}
void fun2(void) {
printf("2222");
}
注意的是:
-
函数声明管的范围是他下面的范围,因为程序执行顺序是自上而下的。
image.png
- 如果有函数声明,没有写函数定义,调用函数会报错
- 如果有函数声明,没有写函数定义,没有调用函数,语法无错误,但没有意义。
- 函数声明可以重复写多个,但是函数定义只能写一个
//函数声明
void fun(void);
void fun2(void);
int main(void) {
//函数声明
void fun(void);
void fun2(void);
fun();
fun2();
return 0;
}
//函数声明
void fun(void);
void fun2(void);
void fun(void) {
printf("11111");
fun2();
}
void fun2(void) {
printf("2222");
}
函数类型
//函数声明
int fun(void);
int main(void) {
printf("%d",fun());//会把double类型的4.5直接内存阶段性变成4
return 0;
}
int fun(void) {
printf("11111\n");
return 4.9;
}
结果:
11111
4
return
- 用在有返回值的函数中:作用是终止所在函数的执行,并返回指定的数据。
- 可以有多个return,但只执行代码逻辑中但第一个
- 用在无返回值但函数中" return; ":终止函数但执行
- return一次只能返回一个值
- 如果函数有返回值,但是没写return,数值类型会返回0。
//函数声明
int fun(void);
int main(void) {
printf("%d",fun());//会把double类型的4.5直接切整数4
return 0;
}
int fun(void) {
printf("11111\n");
return 1;
printf("2222\n");
return 2;
return 3;
}
结果
11111
1
return一次只能返回一个值,出现return 2, 3; 其实是一个逗号表达式,逗号表达式结果是最右边这个值。
//函数声明
int fun(void);
int main(void) {
int a = fun();
printf("%d", a);//结果是3
return 0;
}
int fun(void) {
return 2, 3;
}
返回多个值但技巧,返回一个堆区内存但地址就可以,也就是malloc、calloc的地址。别的像数组什么的栈区地址是不可以的。
//函数声明
int *fun(void);
int main(void) {
int *p = fun();
printf("%d,%d", *p,p[1]);//结果是4,5
free(p);
return 0;
}
int *fun(void) {
int *p = malloc(8);
*p = 4;
p[1] = 5;
return p;
}
有参数无返回值
声明的两种形式:
- int fun(int a, int b);
- int fun(int , int );
//函数声明
int fun(int a, int b);
int fun2(int, int);
int main(void) {
printf("%d\n", fun(1, 2));//3
printf("%d", fun2(1, 2));//3
}
int fun(int a, int b) {
return a + b;
}
int fun2(int a, int b) {
return a - b;
}
通过函数修改外部变量的值 或者说 把函数内部的值传递到函数外部
答:通过指针。
//函数声明
int fun(int *a);
int main(void) {
int a = 3;
printf("%d\n", a);
fun(&a);
printf("%d\n", a);
}
int fun(int *a) {
*a = 12;
return a;//return 的是a的地址,就是指针a的地址
}
运行结果
3
12
通过函数修改指针的指向
//函数声明
int fun(int *a);
int main(void) {
int a = 3;
int *p = &a;
printf("%p\n", p);//0x7ffee8f1e93c
fun(p);
printf("%p\n", p);//0x7ffee8f1e93c
int fun(int *a) {
a = NULL;
}
上述 fun(p)传的是p的值,即a的地址,所以改变不了指针p的指向,修改成下面的:
//函数声明
int fun(int **a);
int main(void) {
int a = 3;
int *p = &a;
printf("%p\n", p);//0x7ffee8f1e93c
fun(&p);
printf("%p\n", p);//0x0 //看,地址被修改了!!
}
int fun(int **a) {
*a = NULL; //这里得写*a,这样才能修改a的地址的值
}
综上面三个代码所述:你要修改谁,你就传谁的地址,修改的时候记得加* ,例如a = 12和a = NULL
通过函数交换两个变量的值
直接传a,b的值,交换的不是main里面的a,b的值,而是fun里面的地址的值。所以用指针
//函数声明
void fun(int *a, int *b);
int main(void) {
int a = 3;
int b = 5;
printf("%d,%d\n", a, b);
fun(&a, &b);
printf("%d,%d", a, b);
}
void fun(int *a, int *b) {
int c = *a;
*a = *b;
*b = c;
}
运行结果:
3,5
5,3
一维数组做函数参数
- 形参是指针
因为数组的地址是连续的,所以我只有知道数组的首地址,那么就可以遍历数组所有的元素了。
在函数中,我如果把数组首地址和长度传进去,那就可以操作数组了
void fun(int *p, int length)
//函数声明
void fun(int *a, int length);
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; ++i) {
printf("%d,", a[i]);
}
printf("\n");
//&a[0]==&a
fun(&a[0], 5);
for (int i = 0; i < 5; ++i) {
printf("%d,", a[i]);
}
}
void fun(int *p, int length) {
for (int i = 0; i < length; ++i) {
p[i] = i;
}
}
运行结果:
1,2,3,4,5,
0,1,2,3,4,
- 形参是数组
在C中,数组做形参,他的本质是数组。
- void fun(int a[], int length) 可以不写数组长度
- void fun(int a[10000], int length) 这个形参的数组长度多少都没有问题,本质是指针
//函数声明
//void fun(int a[], int length);
void fun(int a[5], int length);
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; ++i) {
printf("%d,", a[i]);
}
printf("\n");
//&a[0]==&a
fun(&a[0], 5);
for (int i = 0; i < 5; ++i) {
printf("%d,", a[i]);
}
}
//void fun(int a[], int length) 可以不写数组长度
//void fun(int a[10000], int length) 这个形参的数组长度多少都没有问题,本质是指针
void fun(int a[5], int length) {
for (int i = 0; i < length; ++i) {
a[i] = i;
}
}
//函数声明
//void fun(int a[], int length);
void fun(int a[10000], int length);
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
fun(&a[0], 5);
}
//void fun(int a[], int length) 可以不写数组长度
//void fun(int a[10000], int length) 这个形参的数组长度多少都没有问题,本质是指针
void fun(int a[10000], int length) {
printf("%d", sizeof(a)); //结果是8,所以a就是个指针。
}
写成数组或者指针没关系,一样,想咋写咋写。
C中,不能把整个数组传到函数中!!!,只能传一个地址
二维数组做函数参数
void fun(int a[2][3], int hang,int lie){}
void fun(int a[][3], int hang,int lie){}
void fun(int (*p)[3], int hang,int lie){}
//函数声明
//void fun(int a[2][3], int hang,int lie);
//void fun(int a[][3], int hang,int lie);
//void fun(int (*p)[3], int hang,int lie)
void fun(int (*p)[3], int hang,int lie);
int main(void) {
int a[2][3] = {{1, 2, 3},
{4, 5, 6}};
//二维数组中,a是一维数组指针。
int (*p)[3] = a;
fun(p,2,3);
}
//void fun(int a[2][3], int hang,int lie) //也可以写成这样,跟下面是一模一样的
//void fun(int a[][3], int hang,int lie) //也可以写成这样,跟下面是一模一样的
void fun(int (*p)[3], int hang,int lie) {
for (int i = 0; i <hang ; ++i) {
for (int j = 0; j <lie ; ++j) {
printf("%d,",p[i][j]); //1,2,3,4,5,6,
}
}
}
函数地址类型
函数地址类型取觉于函数的返回值类型、参数类型、个数
eg:int fun(int a,int b) 这个函数的类似就是int (int a,int b)
//函数声明
int fun(int a);
int main(void) {
//函数指针的变量
//分析:函数的类型是int (int a),因为fun是指针,所以加个*p
int (*p)(int a) =fun;// 一样的意思:int (*p)(int a) =&fun;
//用p调用函数
p(2);
}
int fun(int a) {
printf("我是fun");
return 1;
}
递归函数
函数字节调用自己,本质就是循环。
可循环的三要素:循环控制变量(有初始值)、循环控制变量的变化、循环停止条件。
递归应用之-用递归打印54321
//函数声明
int fun(int a);
int main(void) {
fun(5);
}
//用递归打印54321
int fun(int a) {
if (a > 0){
printf("%d\n", a);
fun(a - 1);
}
return 1;
}
递归的本质:
//函数声明
int fun(int a);
int main(void) {
fun(3);
}
int fun(int a) {
if (a > 0){
printf("前:%d\n", a);
fun(a - 1);
printf("后:%d\n", a);
}
return 1;
}
运行结果
前:3
前:2
前:1
后:1
后:2
后:3
上面看出:递归调用自己之前,值是从外到里调用的,调用完之后,再从最后一个值由外向里执行。
实质是:
int fun(int a) {//3
if (a > 0) {
printf("前:%d\n", a); //打印3
int fun(int a) {//2
if (a > 0) {
printf("前:%d\n", a);//打印2
int fun(int a) {1
if (a > 0) {
printf("前:%d\n", a);//打印1
int fun(int a) {//0 不打印里面的
if (a > 0){
printf("前:%d\n", a);
fun(a - 1);
printf("后:%d\n", a);
}
return 1;
}
printf("后:%d\n", a);//打印1
}
return 1;
}
printf("后:%d\n", a);//打印2
}
return 1;
}
printf("后:%d\n", a);//打印3
}
return 1;
}
递归应用之-斐波拉契数列
规则是 第1,2项都是1,第三项之后是,每一项第值都是前两项第和,即:第n项值=第(n-1)项值+第(n-2)项值
- 求第n项第值:
//函数声明
int fun(int a);
int main(void) {
printf("%d", fun(6));
}
//斐波拉契数列,得到第n项第值
int fun(int n) {
if (n == 1) {
return 1;
} else if (2 == n) {
return 1;
} else {
return fun(n - 1) + fun(n - 2);
}
}
由上看,由通项公式的技巧:前面写前数列起始值,后面是通项公式。

n!
//函数声明
int fun(int a);
int main(void) {
printf("%d", fun(5));
}
//n!
int fun(int n) {
if (n == 1) {
return 1;
} else {
return n * fun(n - 1);
}
}
参数不确定的函数
void fun(int a, ...);
int main(void) {
fun(3,1, 1.3, 4);
}
//参数不确定的情况,第一个参数一定要写,a的意思是后面要传a个参数
void fun(int a, ...) {
va_list b;//定义参数数组
va_start(b, a);//把参数装进数组
int v1 = va_arg(b, int);//取第一个值,因为第一个值是1,int型
float v2 = va_arg(b, double);//取第二个值,因为第一个值是1.3,double型
int v3 = va_arg(b, int);//取第一个值,因为第三个值是4,int型
printf("%d,%lf,%d",v1,v2,v3);
}
[ ]的作用
- 声明变量的时候有[],表示声明的是数组变量
- 函数有[],表示指针
- 地址+[],表示下标运算
网友评论