十七、函数
函数 : C语句称为函数语言,实现特定功能的代码块。
函数优点 : 使用函数可以使程序更加简洁,提高运行速度,
当重复执行同一块代码的时候,选择封装函数。
函数有很多,举几个例子:
1.IO函数:scanf\printfgetcahr\putchar\gets\puts
2.字符串函数:strlen\strcpy\strcat\strcmp
3.数学函数:pow sqrt
4.主函数:main
一个程序只有一个主函数,主函数可以调用任何函数
但是任何函数不可以调用主函数。
17.1 函数如何定义
格式:
存储类型 数据类型 函数名(参数列表) //函数头
{
函数体;
}
解析:
1.存储类型 : auto\static\extern\register\const\volatile
2.数据类型 : 基本类型、构造类型、指针类型、空类型
3.函数名 : 满足命名规范
如 : Max max_fun
4.() : 不可以省略,函数的标志
5.参数列表 : 可有可无,如果没有void ,有参数且多个中间使用逗号隔开
6.{} : 函数不可以省略
17.2 函数的分类
-
库函数:系统自带的函数【strlen…】
-
自定义函数:程序员手动封装的函数
1. 有/无参数
2. 有/无返回值
void Sum(void); 无参无返回值函数
void Sum(int a,int b); 有参无返回值函数
int Sum(void); 无参有返回值函数
int Sum(int a,int b); 有参有返回值函数
17.3 无参函数
17.3.1 无返回值
定义格式: eg:
void 函数名(void) void Sum(void)
{ {
函数体; int a=1,b=2;printf("%d",a+b);
} }
调用格式:
函数名(); Sum();
17.3.2 有返回值
定义格式: eg:
int 函数名(void) int Sum(void)
{ {
函数体; int a=1,b=2;printf("%d",a+b);
return num; return a+b;
} }
调用格式:
函数名(); Sum();
或 : int sum = Sum();
17.4 有参函数
17.4.1 无返回值
定义格式: eg:
void 函数名(参数列表) void Sum(int a, int b)
{ {
函数体; printf("%d",a+b);
} }
调用格式:
函数名(); Sum(num1, num2);
17.4.2 有返回值
定义格式: eg:
int 函数名(参数列表) int Sum(int a, int b)
{ {
函数体; printf("%d",a+b);
return num; return a+b;
} }
调用格式:
函数名(); Sum(num1, num2);
或 : int sum = Sum(num1, num2);
普通变量做参数
实参 : 变量 常量 表达式
形参 : 变量
实参和形参的特点:
1.个数一致
2.类型建议一致,如果不一致以形参为主,发生自动或强制转换
3.变量可以不一致
4.形参实参需要一一对应
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void Sum(int m,int n);
//有参函数:被调函数需要使用主调函数的局部变量
#if 0
实参:变量 常量 表达式
形参:变量
实参和形参的特点:
1.个数一致
2.类型建议一致,如果不一致以形参为主,发生自动或强制转换
3.变量可以不一致
#endif
int main(int argc, const char *argv[])//主调函数
{
float a=1.8999,b=2.2222;//局部变量,只能main函数使用
Sum(a,b);//实际参数/实参:只写变量名
return 0;
}
void Sum(int m,int n)//被调函数 形式参数/形参:需要添加数据类型
{
printf("m+n=%d\n",m+n);
}
数组做参数
数组做参数值传递数组名,数组名表示数组的首地址
1.一维数组做参数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//形参数组的数组长度需要>=实际参数数组的个数
//省略形参数组长度,默认是实际实参数组的个数
void Output(int len,int arr[])//形参arr看似数组,实则是指针
{
printf("arr=%ld\n",sizeof(arr));
for(int i=0;i<len;i++)
{
printf("%d\t",arr[i]);
}
}
int main(int argc, const char *argv[])
{
int arr[]={11,22,33,44};//局部
//在自定义函数实现输出:有参无返函数
//参数:数组\长度
int len=sizeof(arr)/sizeof(arr[0]);//局部
printf("arr=%ld\n",sizeof(arr));
Output(len,arr);//参数arr,数组名表示数组的首地址
return 0;
}
2.二维数组做参数
#include <stdlib.h>
void Output(int row,int arr[][row],int line);
int main(int argc, const char *argv[])
{
int arr[][4]={11,22,33,44,55,66};//局部
//在自定义函数中实现输出
//参数:数组,行、列
int line=sizeof(arr)/sizeof(arr[0]);
int row=sizeof(arr[0])/sizeof(arr[0][0]);
Output(row,arr,line);
return 0;
}
void Output(int row,int arr[][row],int line)
{
for(int i=0;i<line;i++)
{
for(int j=0;j<row;j++)
{
printf("%4d",arr[i][j]);
}
printf("\n");
}
}
3.字符数组做参数
void Ouput(char str[])
{
printf("%s\n",str);
printf("sizeof(str)=%ld\n",sizeof(str));//8在自定义函数计算指针的大小
printf("strlen(str)=%ld\n",strlen(str));//5 在自定义函数中可以计算字符串长度
}
int main(int argc, const char *argv[])
{
char str[]="hello";
printf("sizeof(str)=%ld\n",sizeof(str));//6
printf("strlen(str)=%ld\n",strlen(str));//5
//参数:str,
Ouput(str);
return 0;
}
4.二维字符数组做参数
void Output(char str[][5],int line)
{
for(int i=0;i<line;i++)
{
printf("%s\n",str[i]);
}
}
int main(int argc, const char *argv[])
{
char str[][5]={"asdf","!@","1234"};
//自定义函数输出
//参数:数组,行
int line=sizeof(str)/sizeof(str[0]);
// printf("line=%d\n",line);
Output(str,line);
return 0;
}
17.4.3 return
return的作用:
返回值,结束函数
定义格式:
格式1: return(表达式);
格式2:return 表达式;
1.表达式:变量,常量,表达式
return 1; return b; return a+b;
2.return 一个函数可以有多个return,但是只执行一个,原因结束
3.return的位置可以任意,一般在最后
4.当函数的返回值类型和return的数据类型不一致时和函数类型保持一致,发生自动或强制转换。
5,return只能返回一个值或者地址
17.5 自定义函数的位置
17.5.1 定义在主函数的上面
注意:
自定义函数在主函数上面时,
本着 先声明后使用的原则,
如果顺序在前的函数使用了顺序在后的函数,
会报未声明的错误,因此需要理清楚调用逻辑
17.5.2 定义在主函数的下面
注意:
自定义函数在主函数下面时,
本着 先声明后使用的原则,
需要先在上面写出声明,
再在主函数下面写出完整的函数功能。
tips:
声明时需要在这一句声明之后写个 ; 表示这局声明结束了
可以在声明中的参数列表 略写 变量名,但是变量类型必须写
函数功能实现时,参数列表必须完整:变量类型 变量名,
要注意实现时 别在 参数列表后的括号之后写 ;
17.6 多文件编译
目前写的代码,代码量最多只是到了一百多行,等以后开发时,代码量会特别多,因此要学会多文件编译,将代码分到不同的文件中
17.6.1 函数声明在自定义头文件
进入底行模式 :vsp 文件名
如果文件存在,则在同一目录下打开该文件
如果文件不存在,则在同一路径创建该文件,并打开
可以将自定义的函数声明都放在自己定义的头文件中,然后把头文件引入到主函数所在的文件,如上图所示:#include "head.h"
可以将自定义的函数放在另一个 .c 文件中,要注意这个 .c 文件中别写 main 函数
将存放自定义函数的文件引入到 头文件 中,即在头文件中引入这个 .c 文件,如:#include" my_fun.c"
还需要将存放自定义函数的文件引入到 主函数所在的文件中,方式如上。
如果存放自定义函数的文件没有引入到主函数所在的文件,那么编译时需要在 要编译的文件前 加上存放自定义函数的文件全名
如:gcc myfun.c main.c
17.7 内存空间的分区、变量的作用域及生命周期
- 系统性学过组成原理的同学都应该知道,存储器的级别分为很多层次
最顶级的是 寄存器 断电数据易失 读写最快
往下是 高速缓冲存储器 有多级(一般三级或两级)也被称为缓存、cache,断电数据易失 速度次之
然后是 主存 也就是我们常说的内存条,我们的所有程序都要取到这里面进行运行,断电数据易失 速度再次
最后是 辅存 这个是各种的磁盘、硬盘之类的存储器,断电数据不易失
17.7.1 内存(主存)空间的分区
一般内核区与用户区按1:3分配
17.7.2 全局变量
全局变量:变量定义在函数外面
1.全局变量没有写存储类型,默认是外部变量extern
2.全局变量内存空间在静态区
3.作用域:从定义开始到整个文件结束
4.生命周期:从地址申请到释放 即【整个文件】
17.7.3 局部变量
局部变量:定义在函数内部的变量,参数列表
1.局部变量省略存储类型默认是auto
2.局部变量的内存空间在栈区,由计算机自动分配自动回收
3.作用域:从定义开始,到本函数有效
3.生命周期:从地址申请开始,到本函数结束后释放
17.7.4 全局变量和局部变量结合使用
1.全局变量和局部变量重名,优先选用局部变量,局部屏蔽全局---》就近原则
int a=100,b=2;
void Sum(void)
{
int a=1,b=4;
printf("%d+%d=%d\n",a,b,a+b);
}
int main(int argc, const char *argv[])
{
Sum();
return 0;
}
2.局部变量和局部变量重名
int a=100,b=2;
void Mul(void)
{
printf("%d-%d=%d\n",a,b,a-b);//3
}
void Sum(void)
{
int a=1,b=4;
printf("%d+%d=%d\n",a,b,a+b);//5
}
int main(int argc, const char *argv[])
{
Sum();
Mul();
return 0;
}
3.局部和局部重名
15 int main(int argc, const char *argv[])
16 {
17 // Sum();
18 // Mul();
19 int i=0;//19--28
20 for(int i=1;i<=4;i++) //20--24 5
21 {
22 int i=10;//22--24
23 printf("i=%d\n",i);//10 10 10 10
24 }
25
26 printf("i=%d\n",i);//
27 return 0;
28 }
在哪都是就近原则,因为在编译器里面就是这么设置的,数据需要压进栈里,先声明的会扔到下面,后面声明的就在上面(先进后出、后进先出),取用时先从上面取,所以就是就近原则
17.8 递归
递归:(recursion)是指一种通过重复将问题分解为同类的子问题而解决问题的方法。本质是循环。
1)递归运行速度慢,但是多用于问题规模较大的情况
2) 循环运行速度快
- 直接递归:函数自身调用自身
- 间接递归:多个函数之间相互递归调用
- 死递归: 等价于死循环
17.8.1递归练习
1.递归计算1-n的和
2.递归计算阶乘n!
5!=12345=54321
边界:n==1
递归公式: n*rec(n-1)
for(int i=5;i>=1;i–)
3.递归实现斐波那契第n项
1 2 3 4 5 6… 项数
1 1 2 3 5 8… 值
n-2 n-1 n
递归出口:n1 || n2 返回1
递归公式:return rec(n-1)+rec(n-2)
小作业 1
1,
在主函数定义一维数组并输入
自定义有参函数实现冒泡升序排序
自定义有参函数实现简单选择降序排序
自定义函数实现输出
2,
在主函数定义二维数组并循环输入
自定义函数实现计算最大值和最小值
3,
在主函数定义两个字符串并输入
自定义函数实现字符串拷贝
4,
在主函数定义两个字符串并输入
自定义函数实现字符串比较
下面是我写的:代码在图后面
1.
2.
3.
4.
代码:
1.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void bubble_up(int *, int);
void jdxz_down(int *, int);
void print_arr(int *, int);
void swap_num(int *, int *);
int main(int argc, const char *argv[])
{
int a[8] = {0};
for (int i = 0; i < 8; i++)
scanf("%d", a + i);
int len = sizeof(a) / sizeof(a[0]);
bubble_up(a, len);
print_arr(a, len);
jdxz_down(a, len);
print_arr(a, len);
return 0;
}
void bubble_up(int *a, int n)
{
for (int i = 0; i < n; i++)
for (int j = 0; j < n - i - 1; j++)
if (a[j] > a[j + 1])
swap_num(a + j, a + j + 1);
}
void jdxz_down(int *a, int n)
{
int max;
for (int i = 0; i < n - 1; i++)
{
max = i;
for (int j = i + 1; j < n; j++)
if (a[max] < a[j])
max = j;
if (i != max)
swap_num(a + i, a + max);
}
}
void print_arr(int *a, int n)
{
for (int i = 0; i < n; i++)
printf("%d ", *(a + i));
putchar(10);
}
void swap_num(int *a, int *b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int get_max(int line, int row, int (*a)[row]);
int get_min(int line, int row, int (*a)[row]);
void Outout(int line, int row, int (*a)[row]);
int main(int argc, const char *argv[])
{
int a[3][4];
int line = sizeof(a) / sizeof(a[0]);
int row = sizeof(a[0]) / sizeof(a[0][0]);
for (int i = 0; i < line; i++)
{
for (int j = 0; j < row; j++)
{
scanf("%d", &a[i][j]);
}
}
Outout(line,row,a);
get_max(line,row,a);
get_min(line,row,a);
return 0;
}
int get_max(int line, int row, int (*a)[row])
{
int max, x = 0, y = 0;
for (int i = 0; i < line; i++)
{
for (int j = 0; j < row; j++)
{
if (i == 0 && j == 0)
max = a[i][j];
else if (max < a[i][j])
{
max = a[i][j];
x = i;
y = j;
}
}
}
printf("最大值为:%d\n", max);
printf("在第%d行,第%d列.\n",x+1, y+1);
}
int get_min(int line, int row, int (*a)[row])
{
int min, x = 0, y = 0;
for (int i = 0; i < line; i++)
{
for (int j = 0; j < row; j++)
{
if (i == 0 && j == 0)
min = a[i][j];
else if (min > a[i][j])
{
min = a[i][j];
x = i;
y = j;
}
}
}
printf("最小值为:%d\n", min);
printf("在第%d行,第%d列.\n", x+1, y+1);
}
void Outout(int line, int row, int (*a)[row])
{
for (int i = 0; i < line; i++)
{
for (int j = 0; j < row; j++)
{
printf("a[%d][%d]=%d\t", i, j, a[i][j]);
}
putchar(10);
}
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *mystrcpy(char *dest, const char *src);
int main(int argc, const char *argv[])
{
char s1[128] = {0};
char s2[128] = {0};
printf("请输入字符串s1:\n");
fgets(s1, 128, stdin);
printf("请输入字符串s2:\n");
fgets(s2, 128, stdin);
mystrcpy(s1, s2);
puts(s1);
return 0;
}
char *mystrcpy(char *dest, const char *src)
{
char *p = dest;
int i = 0;
while (*(src + i))
{
*dest = *(src + i);
dest++;
i++;
}
*dest = *(src + i);
return p;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int mystrcmp(const char *src1, const char *src2);
void print_result(int a);
int main(int argc, const char *argv[])
{
char s1[128] = {0};
char s2[128] = {0};
printf("请输入字符串s1:\n");
fgets(s1, 128, stdin);
printf("请输入字符串s2:\n");
fgets(s2, 128, stdin);
print_result(mystrcmp(s1, s2));
return 0;
}
int mystrcmp(const char *src1, const char *src2)
{
int i = 0;
while (*(src1 + i) && *(src2 + i) && *(src1 + i) == *(src2 + i))
{
i++;
}
return *(src1 + i) - *(src2 + i);
}
void print_result(int a)
{
if (a > 0)
printf("第一个字符串大\n");
else if (a < 0)
printf("第二个字符串大\n");
else
printf("俩字符串一样大\n");
}
小作业 2
递归计算各个位数字之和
下面是我写的:(图在前,代码在后)
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
递归求各个位数之和
*/
int rec(int);
int main(int argc, const char *argv[])
{
int a;
scanf("%d",&a);
printf("%d\n", rec(a));
return 0;
}
int rec(int n)
{
if (n / 10 <= 0)
{
return n;
}
else
{
return rec(n / 10) + (n % 10);
}
}