函数指针
首先看一段代码:
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); //函数名 就是函数地址 printf("%p\n", &test); //&函数名 也是函数地址 return 0; }
运行结果
那么如何将test()函数指针保存起来呢?
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); //函数指针类型 void *pfun2(); //函数,函数的返回值是void*
函数指针类型
指针都是有类型的
整型指针 int*
数组指针 int (*)[]
函数指针 返回值 (*)(参数....)
#include<stdio.h> int Add(int x, int y) { return x + y; } int main() { int (*pf)(int, int) = Add; int sum = (*pf)(3, 5); //对函数指针解引用 printf("sum = %d", sum); sum = pf(3, 5); //因为 Add和&Add相同,即Add等价于 pf printf("sum = %d", sum); return 0; }
有趣的代码
//代码1 (*(void (*)())0)(); //void (*)()为函数指针 //(void (*)())0 将0强制类型抓换成函数指针的地址 //(*(void (*)())0)() *地址,调用0地址处的这个函数 //函数的返回值空,参数为空
//代码2 void (*signal(int , void(*)(int)))(int); //void(*)(int) 函数指针,返回值void,参数int //void (*signal(int , void(*)(int)))(int) // signal是函数名 //返回值是void(*)(int) // 参数int 和函数指针 void(*)(int) //这是一个函数的声明
当我们看到代码2很难看懂这个代码!
可以简化吗?
void (*signal(int , void(*)(int)))(int); //既然这个函数的返回值类型是 void(*)(int) //那我们可以写成 // void(*)(int) signal(int , void(*)(int)); //但是这样会语法错误 error
函数指针类型重命名
简化
typedef void(*ptr_t) (int); //正确的类型重命名 ptr_t signal(int, ptr_t); //简化
上面的代码出自《C陷阱和缺陷》
有兴趣的伙伴可以尝试阅读!
函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组
比如:
int* arr[10]; //整型指针数组
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10]])(); // int *parr2[10](); int (*)() parr3[10];
答案是:parr1 ·parr1· 先和[] 结合,说明parr1是数组,数组的内容是什么呢? 是int (*)()类型的函数指针。
函数指针数组的用途:
转移表
例子:(计算器)
#include <stdio.h> 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 (*pf[4])(int, int) = { add,sub,mul,div }; int sz = sizeof(pf) / sizeof(pf[1]); int i = 0; for (i = 0; i < 4; i++) { //实现5和3的加减乘除 printf("%d\n", pf[i](5,3)); } return 0; }
//计算器优化 #include <stdio.h> void menu() { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf("*************************\n"); printf("请选择:"); } 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; } void Calc(int (*pf)(int ,int)) { int x, y; printf("请输入两个操作数\n"); scanf("%d%d", &x, &y); printf("%d\n", pf(x, y)); } int main() { int x, y; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 do { menu();//菜单 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; }
指向函数指针数组的指针
看到这个指针是不是感觉头都大了,没事我们慢慢分析!
int* arr[3]; //整型指针数组 int*(*parr)[3]=&arr; //整型指针数组的地址放入parr //parr就是指向整型指针数组的指针 //而我们只需要将整型指针换成函数指针就ok了 int(*pf)(int,int);//函数指针 int(*pfarr[3])(int,int);//函数指针数组 int(*(*ppfarr)[3])(int,int)=&pfarr; //&pfarr //ppfarr是指针,指针指向一个数组,数组的类型为函数指针 //元素个数为3,函数的返回值为int,参数为int
博主就不带大家套娃了,就学到这里了!
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
刚刚的计算器优化后就是回调函数,依赖于函数指针。
我们把加减乘除函数的函数指针,传参的形式,传给另一方调用,回调该加减乘除的函数,所以为回调函数!
qsort函数的使用:
这是MSDN中对qsort函数的解释!
我给大家介绍一下!
qsort
作用
Performs a quick sort.
qsort是快速排序函数库<stdlib.h> and <search.h>中,我们可以通过qsort函数实现任意类型数据的排序!
函数声明
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
返回值
void
参数
void *base
待排序的内容的首地址
size_t num
待排序元素的个数
size_t width
待排序元素的大小
int (__cdecl *compare )(const void *elem1, const void *elem2 )
自己定义一个排序的方式,例如一个结构体数组,结构体元素,按照结构体里的什么类型的数据的类型排序,所以需要自己定义一个campare排序方式!
然后将函数指针传参到qsot中!
//qsort函数使用实例 #include<stdio.h> struct peo { char name[10]; char numb[12]; }; //自己定义一个排序的方式 int compare_char(const void* e1, const void* e2) { return strcmp(((struct peo*)e1)->numb, ((struct peo*)e2)->numb); //按照peo中numb排序 } int main() { //创建一个结构体数组 struct peo peo1[3] = { {"张三","15779770910"},{"李四","17370126640"},{"王五","12669781134"} }; int sz = sizeof(peo1) / sizeof(peo1[1]); qsort(peo1, sz, sizeof(peo1[1]), compare_char); for (int i = 0; i < sz; i++) { printf("%s\t%s\n",peo1[i].name,peo1[i].numb); } return 0; }
看完qsort函数使用演示,是不是学会了。
my_qsort函数模拟实现
模仿qsort的功能实现一个通用的冒泡排序!
我们通过qsort函数的使用,我们尝试一下模拟实现qosrt函数。
//模拟实现qsort //模仿qsort的功能实现一个通用的冒泡排序 //void qsort(void* base, size_t num, size_t width, // int(__cdecl* compare)(const void* elem1, const void* elem2)); #include<stdio.h> typedef struct student { char name[5]; int score; }Student; int compare_score(const void*e1,const void* e2) { return ((Student*)e1)->score - ((Student*)e2)->score; } int compare_name(const void* e1, const void* e2) { return strcmp(((Student*)e1)->name, ((Student*)e2)->name); } void swap(void* e1, void* e2, size_t width) { //将元素一个一个字节交换 int i = 0; for (i = 0; i < width; i++) { char tmp; tmp = *((char*)e1+i); *((char*)e1+i) = *((char*)e2+i); *((char*)e2+i) = tmp; } } void my_qsort(void* base, size_t num, size_t width, int(*compare)(const void* e1, const void* e2)) { int i = 0, j = 0,flag = 1; //趟数 for (i = 0; i < num-1; i++) { //每趟比较次数 for (j = 0; j < num - i - 1; j++) { //比较 //因为我们无法得知实参的类型,无法得知指针加减的步长 //所以我们强制转换成char*类型指针,通过width元素大小进行访问 if (compare((char*)base + width * j, (char*)base + (j + 1) * width)>0) { //交换 swap((char*)base + j * width, (char*)base + (j + 1) * width, width); flag = 0; } } if (flag == 1) break; } } int main() { Student student[5] = { {"李四",65},{"张三",78},{"王五",56},{"老王",100},{"小李",98} }; my_qsort(student, sizeof(student) / sizeof(student[1]),sizeof(student[0]),compare_score); int i = 0; for (i = 0; i < 5; i++) { printf("%s %d\n", student[i].name, student[i].score); } return 0; }
指针和数组面试题解
题目
//一维数组 int a[] = {1,2,3,4}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a+0)); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a+1)); printf("%d\n",sizeof(a[1])); printf("%d\n",sizeof(&a)); printf("%d\n",sizeof(*&a)); printf("%d\n",sizeof(&a+1)); printf("%d\n",sizeof(&a[0])); printf("%d\n",sizeof(&a[0]+1));
解析
//一维数组 int a[] = {1,2,3,4}; printf("%d\n",sizeof(a));// 16 //sizeof数组名就是计算整个数组空间大小 printf("%d\n",sizeof(a+0));// 4/8 //a+0说明数组名a表示首元素地址,加0后还是首元素地址, //地址就是指针,而指针大小统一为4/8 printf("%d\n",sizeof(*a));//4 //解引用数组名,该处的数组名表示首元素地址, //*后就是第一个元素 int型所以为4 printf("%d\n",sizeof(a+1));// 4/8 //解析同a+0,a+1代表第二个元素的地址,指针大小4/8 printf("%d\n",sizeof(a[1])); // 4 //a[1]为int 故为4 printf("%d\n",sizeof(&a)); // 4/8 //&数组名,得到了整个数组的地址,而指针大小4/8 printf("%d\n",sizeof(*&a));// 16 //&a得到整个数组地址,而*&a得到整个数组 16 printf("%d\n",sizeof(&a+1)); // 4/8 //&数组名,得到整个数组的地址,&a+1还是数组地址, //而指针大小4/8 printf("%d\n",sizeof(&a[0]));// 4/8 //&a[0]得到第一个元素的地址,指针4/8 printf("%d\n",sizeof(&a[0]+1)); // 4/8 //&a[0]得到第一个元素的地址, //&a[0]+1第二个元素地址,而指针大小4/8
运行结果
题目
//字符数组 char arr[] = {'a','b','c','d','e','f'}; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(arr+0)); printf("%d\n", sizeof(*arr)); printf("%d\n", sizeof(arr[1])); printf("%d\n", sizeof(&arr)); printf("%d\n", sizeof(&arr+1)); printf("%d\n", sizeof(&arr[0]+1)); printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr+0)); printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1])); printf("%d\n", strlen(&arr)); printf("%d\n", strlen(&arr+1)); printf("%d\n", strlen(&arr[0]+1)); char arr[] = "abcdef"; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(arr+0)); printf("%d\n", sizeof(*arr)); printf("%d\n", sizeof(arr[1])); printf("%d\n", sizeof(&arr)); printf("%d\n", sizeof(&arr+1)); printf("%d\n", sizeof(&arr[0]+1)); printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr+0)); printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1])); printf("%d\n", strlen(&arr)); printf("%d\n", strlen(&arr+1)); printf("%d\n", strlen(&arr[0]+1)); char *p = "abcdef"; printf("%d\n", sizeof(p)); printf("%d\n", sizeof(p+1)); printf("%d\n", sizeof(*p)); printf("%d\n", sizeof(p[0])); printf("%d\n", sizeof(&p)); printf("%d\n", sizeof(&p+1)); printf("%d\n", sizeof(&p[0]+1)); printf("%d\n", strlen(p)); printf("%d\n", strlen(p+1)); printf("%d\n", strlen(*p)); printf("%d\n", strlen(p[0])); printf("%d\n", strlen(&p)); printf("%d\n", strlen(&p+1)); printf("%d\n", strlen(&p[0]+1));
解析
//字符数组 char arr[] = {'a','b','c','d','e','f'}; printf("%d\n", sizeof(arr));// 6 //sizeof数组名,就是整个数组 数组大小为6 printf("%d\n", sizeof(arr+0));// 4/8 //arr+0,数组名就是首元素地址,arr+0也是首元素地址 //指针大小4/8 printf("%d\n", sizeof(*arr));// 1 //解引用数组名,此时的数组名代表首元素地址, //解引用后得到第一个元素,元素大小1 printf("%d\n", sizeof(arr[1]));// 1 //arr[1]表示第一个元素,元素大小1 printf("%d\n", sizeof(&arr));// 4/8 //&数组名,代表数组地址,指针大小4/8 printf("%d\n", sizeof(&arr+1)); //&arr 数组地址,&arr+1也是数组地址 //而指针变量大小4/8 printf("%d\n", sizeof(&arr[0]+1));// 4/8 //&arr[0]第一个元素地址,&arr[0]+1第二个元素地址 printf("%d\n", strlen(arr));//随机值 //stlen函数遇到'\0'停止计数 printf("%d\n", strlen(arr+0));// 随机值 //同上 printf("%d\n", strlen(*arr));// 报错 // stlen函数参数接收的是指针,*arr是第一个元素 printf("%d\n", strlen(arr[1]));//报错 //同上 printf("%d\n", strlen(&arr));// 随机值 //&arr数组的地址,遇到'\0'停止计数 printf("%d\n", strlen(&arr+1));// 随机值—6 //&arr+1 整个数组的地址加一跳过,数组大小 //而数组大小为6个字节,所以长度为随机值-6 printf("%d\n", strlen(&arr[0]+1));//随机值-1 //&arr[0]+1得到数组第二个元素地址,从第一个元素计数 //计数结果为随机值,故从第二个为随机值-1
char arr[] = "abcdef"; //字符串数组,数组大小要加上'\0' printf("%d\n", sizeof(arr)); // 7 //sizeof arr arr表示整个数组 printf("%d\n", sizeof(arr+0));// 4/8 //首元素地址加0还是首元素地址,指针大小为4/8 printf("%d\n", sizeof(*arr));// 1 //*arr得到第一个元素,元素大小为 1bit printf("%d\n", sizeof(arr[1]));// 1 //同上 printf("%d\n", sizeof(&arr)); // 4/8 //&arr整个数组的地址,指针大小4/8 printf("%d\n", sizeof(&arr+1));// 4/8 //&arr+1数组指针,指针大小4/8 printf("%d\n", sizeof(&arr[0]+1));// 4/8 //&arr[0]第一个元素地址,&arr[0]+1第二个元素地址 //而指针大小4/8 printf("%d\n", strlen(arr));// 6 //arr数组存到是字符串,字符串中含有'\0' printf("%d\n", strlen(arr+0));// 6 // 同上 printf("%d\n", strlen(*arr));// 报错 //*arr得到首元素,而strlen函数参数为指针类型 printf("%d\n", strlen(arr[1]));// 报错 //同上 printf("%d\n", strlen(&arr));// 6 // &arr得到整个数组地址 printf("%d\n", strlen(&arr+1));// 随机值 //&arr+1跳过整个数组 printf("%d\n", strlen(&arr[0]+1));// 5 //&arr[0]+1得到第二个元素地址
char *p = "abcdef"; //const修饰的字符常量 printf("%d\n", sizeof(p));// 4/8 //p是字符指针,指针大小4/8 printf("%d\n", sizeof(p+1));// 4/8 //p+1字符指针,指针大小4/8 printf("%d\n", sizeof(*p));// 1 //*p得到第一个元素,元素类型为char printf("%d\n", sizeof(p[0]));// 1 //同上 printf("%d\n", sizeof(&p));// 4/8 //&p为字符指针,指针大小4/8 printf("%d\n", sizeof(&p+1));//4/8 //&p+1为第二个字符地址,字符指针,指针大小4/8 printf("%d\n", sizeof(&p[0]+1));// 4/8 //同上 printf("%d\n", strlen(p));// 随机值 //p指针,所指空间,'\0'位置未知 printf("%d\n", strlen(p+1));// 随机值-1 //p+1第二个字符地址 printf("%d\n", strlen(*p));// 报错 // *p是字符,strlen参数为指针类型 printf("%d\n", strlen(p[0]));// 报错 //同上 printf("%d\n", strlen(&p));// 另一随机值 // &p是二级指针, printf("%d\n", strlen(&p+1));// 又另一随机值 //&p二级指针 ,&p+1后还是二级指针 printf("%d\n", strlen(&p[0]+1));// 随机值—1 //&p[0]+1第二个字符地址
//二维数组 int a[3][4] = { 0 }; printf("%d\n", sizeof(a));//48 //sizeof数组名.为整个数组 printf("%d\n", sizeof(a[0][0]));//4 //a[0][0]为第一元素 printf("%d\n", sizeof(a[0]));//16 //a[0]得到二维数组第一行 一行有4个元素 printf("%d\n", sizeof(a[0] + 1));//4/8 //a[0]第一行地址,a[0]+1第二行地址 //第二行地址,数组地址,指针大小4/8 printf("%d\n", sizeof(*(a[0] + 1)));//4 //a[0]首元素地址,即第一行第一个元素地址, //加一得到第二个元素地址,解引用得到第二个元素 printf("%d\n", sizeof(a + 1));//4/8 //a为第一行地址,a+1第二行地址 printf("%d\n", sizeof(*(a + 1)));//16 //a+1第二行地址,解引用后得到第二行 printf("%d\n", sizeof(&a[0] + 1));//4/8 //&a[0]+1第二行地址 printf("%d\n", sizeof(*(&a[0] + 1)));//16 //&a[0]+1第二行地址 解引用得到第二行 printf("%d\n", sizeof(*a));//16 //a第一行地址,解引用后得到第一行 printf("%d\n", sizeof(a[3]));//16 //数组越界,无第四行,但是根据二维数组前面行的大小 //sizeof认为a[3]相同大小
总结:
数组名的意义:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。