目录
一、数组参数、指针参数
在写代码时难免要把数组或者指针传给函数,那函数的参数该如何设计呢?
一维数组传参
void test(int arr[])
void test(int arr[10])
void test(int* arr)
void test2(int* arr[20])
void test2(int** arr)
int main()
{
int arr1[10] = { 0 };
int* arr2[20] = { 0 };
test(arr1);
test2(arr2);
return 0;
}
test对应的三种方式,都对。数组大小可以省略,传过去的是数组名,首元素地址,所以可用一个指针接收。test2里,第二个方法,arr2中元素类型就是int*类型,所以用一个指针接收,这个指针就是一个二级指针。
可传指针,可传数组。
二维数组传参
void test(int arr[3][5])
void test(itn arr[][5])
//void test(int arr[3][])
//void test(int arr[][])
//void test(int* arr)
//void test(int** arr)
void test(int(*arr)[5])
int main()
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
看//后的,二维数组传参,形参的设计只能省略第一个[]的数字,对于一个二维数组,不能不知道一行多少元素;针对一级指针,二级指针,所对应的对象都不是二维数组,要用到之前所写的数组指针,也就是(int (*arr)[5])
一级指针传参
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d", *(p + i));
}
}
int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
print(p, sz);
return 0;
}
这是一个很简单的一级指针传参,现在加点思考:当一个函数的参数部分是一级指针的时候,函数能接收什么参数?
void test1(int* p1)
{}
int main()
{
int a = 10;
int* p1 = &a;
test1(&a);
test1(p1);
return 0;
}
a的地址放到了p1里,所以传&a和p1都可。
二级指针传参
void test1(int** p)
{}
int main()
{
int a = 10;
int* p = &a;
int** pp = &p;
test1(pp);
test1(&p);
return 0;
}
两者也都可以。二级指针和一级指针地址都可,还有其他的传参,int *arr[],传过去数组名,也就是首元素地址,类型为int*。三种方式,二级指针本身,一级指针地址,存放一级指针的数组。
二、函数指针
指向函数的指针。数组指针是指向数组的指针。
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
printf("%p\n", Add);
printf("%p\n", &Add);
return 0;
}
&Add和Add是一样的,这里可以联想一下arr,不过没有函数首元素一说。&函数名、函数名都是函数的地址。要存放Add的地址,这里可以继续联想到arr,int (*p)[10] = &arr,int*类型的指针变量p,指向10个元素的arr,那么Add也一样,int(*pa)(int, int) = Add。那么利用这个指针使用函数:
printf("%d\n", (*pa)(2, 3))
这样就可以计算2+3的值。
所以函数指针就是一个存放函数地址的指针。不同函数的地址定义的函数指针也不相同。
void Print(const char* str)
{
printf("%s\n", str);
}
int main()
{
void (*p)(const char*) = Print;
(*p)("hello world");
return 0;
}
p存放Print,前面是函数的返回类型,后面括号是函数的参数类型。然后解引用p使用函数,输入要传入的参数。
这就是函数指针的内容。接下来看两个代码
( * ( void (*) () ) 0 ) ()
void (*signal (int, void (*) (int) ) ) (int)
先看这个void (*p) (char*) = Print
去掉p就是这个函数的类型,也就是void (*) (char*)。p必须和*连在一起。理解了这个,在看上面两个。
( * ( void (*) () ) 0 ) (),单独看void (*) (),是一个函数的类型,函数无参,给这部分上括号放在0前面也就是强制类型转换,把0做成一个函数名,也就是这个函数的地址,再在前面放上*,解引用,就开始调用这个函数了,最后的 (),调用函数,无参。所以这是一个调用函数过程,函数类型是void (*) (),函数名是0。
void (*signal (int, void (*) (int) ) ) (int) 再看这个,如果把signal (int, void (*) (int) )看成p,那么整体就是void (*p) (int),这是一个函数指针,指针变量是p,而这个p又是一个函数指针,名字为signal,函数参数是一个整型int,和一个函数指针void (*) (int)。
我们可以简化它。
typdef这个关键字可重命名,不过面对函数指针,重命名的名字也得放在*后面
typedef void (*pfun_t) (int)
或者pfun_t signal(int, pfun_t)
对于这个函数的描述:
signal是一个函数声明。
signal函数的参数有2个,第一个是int, 第二个是函数指针,该函数指针指向的函数的参数的int,返回类型是void。
signal函数的返回类型也是一个函数指针,该函数指针指向的函数的参数是int, 返回类型是void。
关于函数指针再写一个东西
int main()
{
int (*p)(int, int) = Add;
printf("%d\n", p(2, 3));
printf("%d\n", Add(2, 3));
printf("%d\n", (*p)(2, 3));
return 0;
}
三种方法都可。*p解引用找到函数;Add的地址给了p后,p就相当于Add,所以前两个都可。无论是**p,***p,结果都一样,所以这个*更像是一个摆设,可加可不加。
三、函数指针数组
int main()
{
int* arr[5];
int(*pa)(int, int) = Add;
int(*parr[4])(int, int) = { Add, Sub, Mul, Div };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", parr[i](2, 3));
}
return 0;
}
int(*parr[4])(int, int) = {Add, Sub, Mul, Div} 就是一个函数指针数组,存放函数指针的数组,每个函数类型相同。
用途:转移表
计算器
void menu()
{
printf("*********************************\n");
printf("********* 1.Add 2.Sub *********\n");
printf("********* 3.Mul 4.Div *********\n");
printf("************ 0.Exit *************\n");
}
int main()
{
int x = 0, y = 0;
do
{
menu();
printf("请选择: ");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", Add(x, y));
break;
case 2:
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", Sub(x, y));
break;
case 3:
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", Mul(x, y));
break;
case 4:
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", Div(x, y));
break;
case 0:
printf("退出\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
四个函数分别是加减乘除,定义函数就不写了,非常简单。也可以实现更多的功能,只要在case里面涉及到的函数里面加即可,但是如果一直加好多功能,switch语句将会非常臃肿。这里就可以用到函数指针数组。
int main()
{
int x = 0, y = 0, input = 0;
int (*pfarr[5])(int, int) = { 0, Add, Sub, Mul, Div };
do
{
menu();
printf("请选择: ");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入操作数: ");
scanf("%d%d", &x, &y);
int ret = pfarr[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
printf("退出\n");
else
printf("选择错误\n");
} while (input);
return 0;
}
如果相加函数,往数组里加入函数名,menu里面做一下改动即可。可声明pfarr[],元素个数有实际决定,这样以后也不需要改。这就是函数指针数组的一个用途。
四、回调函数
回调函数就是通过函数指针调用的函数。通过这个函数来解决之前的一个问题。
void menu()
{
printf("*********************************\n");
printf("********* 1.Add 2.Sub *********\n");
printf("********* 3.Mul 4.Div *********\n");
printf("************ 0.Exit *************\n");
}
int main()
{
int x = 0, y = 0;
do
{
menu();
printf("请选择: ");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", Add(x, y));
break;
case 2:
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", Sub(x, y));
break;
case 3:
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", Mul(x, y));
break;
case 4:
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", Div(x, y));
break;
case 0:
printf("退出\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
程序很冗杂。从case1到case4,其中的不同就是函数不同。
void Calc(int(*pf)(int, int))
{
int x = 0;
int y = 0;
printf("请输入两个操作数: ");
scanf("%d%d", &x, &y);
printf("%d\n", pf(x, y));
}
int main()
{
int x = 0, y = 0, input = 0;
int (*pfarr[5])(int, int) = { 0, Add, Sub, Mul, Div };
do
{
menu();
printf("请选择: ");
scanf("%d", &input);
switch (input)
{
case 1:
Calc(Add);
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0:
printf("退出\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
每次调用时,指针变量pf接收函数的地址,通过地址调用函数。这时候被调用的函数就是回调函数。
五、指向函数指针数组的指针
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
int(*pfArr[4])(int, int);
int(*(*ppfArr)[4])(int, int) = &pfArr;
return 0;
}
arr一个数组可以取出地址,放入到一个数组指针里,同样,一个函数也可以放到一个函数指针数组,而对于一个函数指针数组,也可以有一个指针指向它。int(*(*pfArr)[4])(int, int) = &pfArr,pfArr是一个函数指针数组,指向的函数类型为int,参数类型为int,现在把pfArr的地址取出来,虽然他是函数指针数组,但也是一个数组,ppfArr指向这个数组,数组元素个数有为4,每个元素类型就是int(*)(int, int)。ppfArr是一个指向函数指针数组的指针,本质上是一个数组指针,pfArr是一个函数指针数组,pf是一个函数指针。
关于回调函数还需要再写一点,放在下一篇笔记里。
结束。