函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:
int* arr[10];//数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。那下面哪一个是函数指针数组呢?
int (*parr1[10])(); int* parr2[10](); int (*)() parr3[10];
答案是:parr1。 parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表。
请看下面的一个简单计算器的实例:
int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 1; int ret = 0; do { printf("========================\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf("========================\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = add(x, y); printf("ret = %d\n", ret); break; case 2: printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = div(x, y); printf("ret = %d\n", ret); break; case 0: printf("退出程序\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
那么用函数指针数组怎么实现呢?请看下面的代码:
int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 1; int ret = 0; //函数指针数组 int(*p[5])(int x, int y) = { NULL, add, sub, mul, div }; //转移表 while (input) { printf("======================\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf("======================\n"); printf("请选择:"); scanf("%d", &input); if ((input <= 4 && input >= 1)) { printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = (*p[input])(x, y); } else printf("输入有误\n"); printf("ret = %d\n", ret); } return 0; }
指向函数指针数组的指针
指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针 。我们来看下面的代码:
int (*pf)(int, int);//函数指针 int (*pfArr[5])(int, int);//函数指针数组 //&pfArr函数指针数组的地址,p就是指向函数指针数组的指针 int (*(*p)[5])(int, int) = &pfArr;
解析:p先和 * 结合说明是一个指针,之后与[]结合,说明是一个数组指针,再与*结合说明用一个指针指向了数组指针,之后又指向了一个函数的地址,该函数有两个int类型参数,返回值是int。
总而言之,指向函数指针数组的指针就是在函数指针数组的基础上,再加一个 * 表示一个指针去指向它。
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
首先演示一下qsort函数的使用:
#include <stdlib.h>//qsort需要引入头文件 int int_cmp(const void * p1, const void * p2) { return (*( int *)p1 - *(int *) p2); } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; qsort(arr, sz, sizeof (int), int_cmp); for (i = 0; i< sz; i++) { printf( "%d ", arr[i]); } printf("\n"); return 0; }
使用回调函数,模拟实现qsort(采用冒泡的方式)
int int_cmp(const void* p1, const void* p2) { return (*(int*)p1 - *(int*)p2); } void swap(void* p1, void* p2, int size) { int i = 0; for (i = 0; i < size; i++) { char tmp = *((char*)p1 + i); *((char*)p1 + i) = *((char*)p2 + i); *((char*)p2 + i) = tmp; } } void bubble(void* base, int count, int size, int(*cmp)(void*, void*)) { int i = 0; int j = 0; for (i = 0; i < count - 1; i++) { for (j = 0; j < count - i - 1; j++) { if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0) { swap((char*)base + j * size, (char*)base + (j + 1) * size, size); } } } } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; //char *arr[] = {"aaaa","dddd","cccc","bbbb"}; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); bubble(arr, sz, sizeof(int), int_cmp); for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
void* 的指针是无具体类型的指针,它可以接收任意类型的地址,这种类型的指针是不能直接进行解引用操作,也不能直接进行指针运算。
测试qsort排序结构体数据
struct Stu { char name[10]; int age; }; //按年龄排序 int cmp_stu_age(const void* p1, const void* p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } //按姓名排序 int cmp_stu_name(const void* p1, const void* p2) { return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); } int main() { struct Stu arr[] = { {"zhangsan", 17}, {"lisi", 18},{"wangwu", 15} }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_age); int i = 0; for (i = 0; i < sz; i++) { printf("%d\n", arr[i].age); } return 0; }
指针和数组经典题目的解析
数组名是数组首元素的地址但有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址。
请看下面的题目:
//一维数组 int a[] = {1,2,3,4}; printf("%d\n",sizeof(a));//4*4=16字节 printf("%d\n",sizeof(a+0));//数组名a是数组首元素地址,a+0还是首地址,地址大小为4/8字节 printf("%d\n",sizeof(*a));//数组名a是数组首元素地址,*a就是首元素,大小为4字节 printf("%d\n",sizeof(a+1));//数组名a是数组首元素地址,a+1就是第二个元素的地址,大小为4/8字节 printf("%d\n",sizeof(a[1]));//数组第二个元素,大小为4字节 printf("%d\n",sizeof(&a));//&a是数组的地址,数组的地址也是地址大小为4/8字节 printf("%d\n",sizeof(*&a));//*和&相互抵消,所以*&a相当于a,所以大小为16个字节 printf("%d\n",sizeof(&a+1));//&a是整个数组的地址,&a+1就是跳过整个数组,但结果任然是一个地址,大小为4/8字节 printf("%d\n",sizeof(&a[0]));//表示首元素地址,大小为4/8个字节 printf("%d\n",sizeof(&a[0]+1));//表示第二个元素的地址,大小为4/8个字节
//字符数组 char arr[] = {'a','b','c','d','e','f'}; printf("%d\n", sizeof(arr));//arr表示整个数组,计算的是整个数组的大小,总共6个字节 printf("%d\n", sizeof(arr+0));//arr表示数组首元素的地址,arr+0还是数组首元素的地址,是地址就是4/8个字节 printf("%d\n", sizeof(*arr));//arr表示数组首元素的地址,*arr就是首元素,大小1个字节 printf("%d\n", sizeof(arr[1]));//arr[1]就是数组第二个元素,大小是1个字节 printf("%d\n", sizeof(&arr));//&arr是数组的地址,但是数组的地址也是地址,是地址就是4/8个字节 printf("%d\n", sizeof(&arr+1));//&arr + 1是跳过整个数组后的地址,是地址就是4/8个字节 printf("%d\n", sizeof(&arr[0]+1));//表示第二个元素的地址,是4/8个字节 printf("%d\n", strlen(arr));//因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找直到找到\0,所以结果就是随机值 printf("%d\n", strlen(arr+0));//arr + 0是首元素的地址,和第一个一样,也是随机值 printf("%d\n", strlen(*arr));//error //strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,'a'的ASCII码值是97,那就是将97作为地址传参,就会从97这个地址开始统计字符串长度,这就非法访问内存了 printf("%d\n", strlen(arr[1]));//error 原因同上 printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,那么传递给strlen函数后,依然是从数组的第一个元素的位置开始往后统计 printf("%d\n", strlen(&arr+1));//原因同上也是随机值 printf("%d\n", strlen(&arr[0]+1));//&arr[0] + 1是第二个元素的地址。结果也是随机值
char arr[] = "abcdef";//等价于[a b c d e f \0] printf("%d\n", sizeof(arr));//7个字节 printf("%d\n", sizeof(arr+0));//arr + 0是首元素的地址,大小1个字节 printf("%d\n", sizeof(*arr));//*arr其实就是首元素,大小1个字节 printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1个字节 printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节 printf("%d\n", sizeof(&arr+1));//&arr + 1是跳过一个数组的地址,结果仍然是地址大小为4/8个字节 printf("%d\n", sizeof(&arr[0]+1));//&arr[0] + 1是第二个元素的地址 大小为4/8个字节 printf("%d\n", strlen(arr));//6 printf("%d\n", strlen(arr+0));//6 printf("%d\n", strlen(*arr));//error printf("%d\n", strlen(arr[1]));//error printf("%d\n", strlen(&arr));//6 printf("%d\n", strlen(&arr+1));//跳过整个数组向后数,后面是未知的所以结果是随机值 printf("%d\n", strlen(&arr[0]+1));//从第二个元素往后数,长度为5
char* p = "abcdef"; printf("%d\n", sizeof(p));//p是一个指针变量大小就是4/8个字节 printf("%d\n", sizeof(p+1));//p+1是'b'的地址,是地址大小就是4/8个字节 printf("%d\n", sizeof(*p));//*p 就是'a',大小就是1个字节 printf("%d\n", sizeof(p[0]));//p[0]--> *(p+0) --> *p 大小1个字节 printf("%d\n", sizeof(&p));//&p --> char** 大小4/8个字节 printf("%d\n", sizeof(&p+1));//直接跳到字符串后面的,实际还是地址,大小4/8个字节 printf("%d\n", sizeof(&p[0]+1));//&p[0] + 1得到是'b'的地址,大小4/8个字节 printf("%d\n", strlen(p));//6 printf("%d\n", strlen(p+1));//5 printf("%d\n", strlen(*p));//error printf("%d\n", strlen(p[0]));//error printf("%d\n", strlen(&p));//随机值 printf("%d\n", strlen(&p+1));//随机值 printf("%d\n", strlen(&p[0]+1));//5
//二维数组 int a[3][4] = {0}; printf("%d\n",sizeof(a));//3*4*4 = 48 printf("%d\n",sizeof(a[0][0]));//4 printf("%d\n",sizeof(a[0]));//a[0]是第一行这个一维数组的数组名,数组名算是单独放在sizeof内部了,计算的是整个数组的大小,大小是16个字节 printf("%d\n",sizeof(a[0]+1));//a[0]作为第一行的数组名,没有单独放在sizeo内部,没有&,a[0]表示数组首元素的地址,也就是a[0][0]的地址,所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节 printf("%d\n",sizeof(*(a[0]+1)));//计算的是第一行第2个元素的大小,为4个字节 printf("%d\n",sizeof(a+1));//a是数组首元素的地址,是第一行的地址,a+1就是第二行的地址,是地址大小就是4/8个字节 (它的类型是int(*)[4]) printf("%d\n",sizeof(*(a+1)));//*(a+1) --> a[1] -> sizeof(*(a+1))->sizeof(a[1]) 计算的是第二行的大小,就是16个字节 printf("%d\n",sizeof(&a[0]+1));//&a[0]是第一行的地址,&a[0]+1 是第二行的地址,是地址大小就是4/8个字节 printf("%d\n",sizeof(*(&a[0]+1)));//计算的是第二行的大小,16个字节 printf("%d\n",sizeof(*a));//a是数组首元素的地址,就是第一行的地址,*a 就是第一行,*a --> *(a+0) --> a[0],大小为16个字节 printf("%d\n",sizeof(a[3]));//第三行的大小,16个字节
总结:
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表示首元素的地址。