day13-函数
1.函数的概念
main scanf printf putchar getchar puts strlen strcpy strcmp strcap srand rand...
实现某一具体功能的代码模块,代码重复使用、简洁优化主程序
2.函数的定义
<数据类型> 函数名(参数列表)
{
代码块;
return 返回值;
}
1)返回类型:char int short float double long... char * 、 int *、 void
函数的返回类型应与返回值的数据类型保持一致,如果函数没有返回值,则为void空类型
2)函数名:标识符
满足命名规则
3)参数列表:参数可以为1个,多个,或者没有,多个参数之间用逗号隔开
4)代码块:功能代码
5)return:代码函数模块的结束,一般后面跟上函数的返回值,也可以不加返回值return;
6)返回值:返回值代表调用的是得到的具体结果(可以先用if判断函数返回值,找出错误信息,确保函数正确再使用)
函数三要素:返回类型、返回值、参数
eg:封装一个函数,该函数实现打印n行的"hello world";
void my_print(int n)
{
for(int i = 0; i < n; i++)
printf("hello world \n");
return ;
}
3.函数的声明
在使用函数之前,对函数进行声明,让编译器找到函数
void my_print(int n);//n可以省略
函数的声明一般放在主函数之上,或者.h文件中,只要是在调用之前声明都可以
函数定义在主函数之上,既定义也声明
4.函数的调用
函数名(实参列表);
my_print(5);
实参列表与参数列表的参数要一一对应(参数个数,参数类型)
5.函数参数的传递
1)值传递
2)地址传递[1] 值传递
eg1:封装一个函数,实现两元素之和
int add(int a, int b)
{
return a+b;
}
int main()
{
int a = 10, b = 20;
add(a,b); //将a变量的值复制给函数中的变量a,将b变量的值复制给函数中的变量b
}
函数add中的变量a,与main函数中的变量a,不是同一片存储空间,所以在add函数内部不会访问到主函数中的变量a
[2] 地址传递
eg2:封装函数,实现两个整型数据的交换
void my_swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
return ;
}
int main()
{
int a = 10, b = 20;
my_swap(&a,&b);
}
地址传递是将变量的地址传入函数内部,函数内部对变量地址进行操作
[3] 全局变量
eg3:1.c文件中
int a = 10, b = 20; //定义两个全局变量a b,在1.c文件中,所有模块都可以访问这两个全局变量
void my_swap()
{
int temp = a; //交换两个全局变量的值
a = b;
b = temp;
return ;
}
int main()
{
my_swap(a,b); //交换两个全局变量的值
}
3)全局变量
1)函数参数与一维数组
[1] 复制传递
void print_arr(int a[], int len)
{
for(int i = 0; i < len; i++){
printf("%d ", a[i]);
}
}
[2] 地址传递
void print_arr(char *a, int len)
{
for(int i = 0; i < len; i++){
printf("%d ", a[i]);
}
}
复制传递与地址传递本质都是操作的指针
[3] 全局数组
eg1:
a、对一维数组进行随机赋值 b、打印一维数组,封装一个函数,查找一维数组中最大的元素,并作为函数的返回值
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//对一维数组随即赋值
void arr_rand(int *a,int len)
{
srand(time(NULL));
for(int i = 0; i < len; i++)
a[i] = rand()%50;
}
//打印一维数组
void arr_print(int *a,int len)
{
for(int i = 0; i < len; i++)
printf("%d ",a[i]);
puts("");
}
//查找一维数组中的最大值
int arr_max(int *a,int len)
{
int max = a[0];
for(int i = 0; i < len ;i++)
{
if(max < a[i])
max = a[i];
}
return max;
}
//一维数组冒泡排序
void arr_sort(int *a,int len)
{
for(int i = 0; i < len-1; i++)
{
for(int j = 0; j < len-i-1; j++){
if(a[j] > a[j+1])
{
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
int main(int argc, char *argv[])
{
int a[32];
arr_rand(a,5);
arr_print(a,5);
printf("max = %d\n",arr_max(a,5));
arr_sort(a,5);
arr_print(a,5);
return 0;
}
eg2:
封装一个函数,统计字符串中小写字符的个数,并将字符串中小写字母转换成大写字母
#include <stdio.h>
int str_len(char *s)
{
int cnt = 0;
int i = 0;
while(*s){
if(*s >= 'a' && *s <= 'z'){
*s-=32;
cnt++;
}
s++;
}
return cnt;
}
int main(int argc, char *argv[])
{
char s[32] = "zcJHJosujcHSJcSLNHc";
printf("len = %d\n",str_len(s));
puts(s);
return 0;
}
2)函数参数与二维数组
eg:对二维数组求和
eg:封装一个函数,求二维数组所有元素之和
int arr_sum(int (*p)[3],int row)
{
int sum = 0;
for(int i = 0; i < row; i++){
for(int j = 0; j < 3; j++)
sum+= p[i][j];
}
return sum;
}
int main(int argc, char *argv[])
{
int a[][3] = {1,2,3,4,5,6};
printf("sum = %d\n",arr_sum(a,2));
return 0;
}
6.指针函数
<数据类型> *函数名(参数列表)
{
代码块;
return 地址值;
}
指针函数的返回值类型为地址值;
不能返回局部变量的地址;
7.函数指针
函数指针用来存放指向函数的入口地址//函数名即为函数的入口地址,结构体变量名不为结构体的地址,应加上&符号
<数据类型> (*函数指针名)(参数列表);
函数名是函数的入口地址
int add(int a, int b)
{
return a+b;
}
int (*pfunc)(int, int) = add;
//定义一个函数指针,指向add函数的入口地址
int (*Pfunc)(int, int) = &add;//同上
Pfunc(10,20);
//指向指向函数add的指针,说明add函数
(*Pfunc)(10,20);//同上
8.函数指针数组
本质是一个数组,满足数组所有属性,该数组中存放多个同类型的函数名(函数入口地址)
<数据类型> (*函数指针数组名[数组元素个数])(参数列表);
//定义一个函数数组指针,保存与数组同类型的函数地址
int (*Pfunc_arr[3])(int, int) = {add,sub,mul};
数组元素的访问:
数组名[下标];
eg:
Pfunc_arr[0];
//表示该数组中的第一个元素,当前第一个元素为add函数名
Pfunc_arr[0](10,20);
//调用add函数
指针函数、函数指针与函数指针数组的区别:
指针函数本质上是一个函数,只是它的返回类型为一个地址值,且不能返回局部变量的地址;
函数指针本质上是一个指针,只是它指向的内容为函数的入口地址
函数指针数组本质上是一个数组,满足数组的所有属性,只是它存放的内容是多个同类型的函数指针(函数入口地址);
eg:
#include <stdio.h>
int add(int, int);
int mul(int, int);
int sub(int, int);
int main(int argc, char *argv[])
{
int (*pfunc)(int, int) = add;
//定义一个函数指针指向add函数:函数指针类型为 int (*) (int,int)
printf("%d\n",pfunc(10,20));
pfunc = mul;//指针指向mul()函数
printf("%d\n",pfunc(10,20));
pfunc = sub;//指针指向sub()函数
printf("%d\n",pfunc(10,20));
int (*pfunc_arr[])(int, int) = {add,sub,mul};
//定义一个函数指针数组存放add、sub、mul函数的入口地址
printf("%d\n",pfunc_arr[0](10,20));
printf("%d\n",pfunc_arr[1](10,20));
printf("%d\n",pfunc_arr[2](10,20));
return 0;
}
int add(int a,int b)
{
return a+b;
}
int mul(int a,int b)
{
return a*b;
}
int sub(int a,int b)
{
return a-b;
}
9.typedef取别名
typedef 对数据类型取别名
eg:
1)typedef unsigned int unit32_t;
//对unsigned int 类型取别名为unit32_t
unit32_t a;
2)typedef int (*p_func)(int, int);
//对int (*)(int, int)函数指针类型取别名为P_func
P_func b;
3)typedef int (*P_arr)[3];
//对int (*)[3]类型的指针数组取别名为P_arr
P_arr c;//注意这里的P_arr是一个数据类型,而不是变量名
10.递归函数
函数递归调用:函数一直在直接或间接的调用函数本身,叫做递归调用,该函数就叫做递归函数
void func(void)
{
func();
}
eg: 有五个人,第5个人的年龄是4个人的岁数+2,第4个人的年龄是第3个人的年龄+2,第3个人的年龄是第2个人的年龄+2,第2个人的年龄是第1个人的年龄+2,第1个人的年龄是10岁,求第五个人的年龄
int func(int n)
{
if(n == 1)
return 10;
else
return func(n-1)+2;
}
练习:求斐波那契数列的第n项
1 1 2 3 5 8 13 21 34 55....
int func2(int n)
{
if(1 == n || 2 == n)
return 1;
else
return func2(n-1)+func2(n-2);
}
int main(int argc, char *argv[])
{
printf("%d\n",func2(12));
return 0;
}
11.回调函数
通过函数指针调用的函数叫做回调函数。当一个函数B中的参数有函数指针,并且通过该函数指针调用到指针指向的函数A,那么函数A为回调函数
void funcA(void)
{
return;
}
void funCB(void (*p) (void))
{
p();
return 0;
}
int main()
{
funcB(funcA);
return 0;
}
eg:通过使用回调函数对数组a进行冒泡排序
#include <stdio.h>
void arr_sort(int*a,int len, void (*p)(int *,int)){
p(a,len);//函数指针
}//函数参数为函数指针类型,指针指向函数minTomax,或maxTomin
void minTomax(int *a,int len){
for(int i = 0; i < len-1; i++)
{
for(int j = 0; j < len-1-i; j++){
if(a[j] > a[j+1]){
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
void maxTomin(int *a,int len){
for(int i = 0; i < len-1; i++)
{
for(int j = 0; j < len-1-i; j++){
if(a[j] < a[j+1]){
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
int main(int argc, char *argv[])
{
int a[] = {12,32,14,1,56,23};
arr_sort(a,6,minTomax);//将函数minTomax入口地址作为参数传入arr_sort
for(int i = 0; i < 6; i++){
printf("%d ",a[i]);
}
puts("");
return 0;
}