C*指针进阶
Posted 三分苦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C*指针进阶相关的知识,希望对你有一定的参考价值。
目录
1、函数指针
- 简要概念:既然数组指针是指向数组的指针,那么函数指针就是指向函数的指针,用来存放函数地址的一个指针。
- 我们先写一个加法函数,并且打印出其地址看看。
#include<stdio.h> int Add(int x, int y) return x + y; int main() printf("%p\\n", &Add); printf("%p\\n", Add); //&函数名 和 函数名 都是函数的地址 return 0;
不难发现,&函数名 和 函数名 都是函数的地址
- 当我们拿到函数地址了,该怎么存起来呢?先回顾下数组地址的存法:
int arr[10] = 0 ; int (*p)[10] = &arr;
- 仿照数组指针的写法,写一下函数指针:
#include<stdio.h> int Add(int x, int y) return x + y; int main() int (*pa)(int, int) = Add; printf("%d\\n", (*pa)(2, 3)); // 5 return 0;
- 不同函数的地址存起来定义的指针也不相同
#include<stdio.h> void Print(char* str) printf("%s\\n", str); int main() void(*p)(char*) = Print; (*p)("hello bit"); p("hello bit"); return 0;
- 下面解释下为什么在存的时候*p要加括号。
void test() printf("hehe\\n"); //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void *pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:
pfun1可以存放。这里涉及到优先级的问题,因为在不加括号的情况下,pfun1会先和右边的括号也就是函数相结合,后和*结合,那就不是指针了,继而存不了地址,所以要在*pfun1先整个用()括起来,确保pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
- 注意:
#include<stdio.h> int Add(int x, int y) return x + y; int main() int (*pa)(int, int) = Add; printf("%d\\n", (*pa)(2, 3)); // 5 printf("%d\\n", (**pa)(2, 3)); // 5 printf("%d\\n", (***pa)(2, 3)); // 5 printf("%d\\n", (pa)(2, 3)); // 5 printf("%d\\n", Add(2, 3)); // 5 return 0;
如果pa是个函数指针,我去调用这个函数的时候,我可以解引用,也可以不解引用,*号可以看成一个摆设
1.2、两段有趣代码
//代码1 (*(void (*)())0)(); //代码2 void (*signal(int , void(*)(int)))(int);
- 解释代码1:
(*(void (*)())0)();
void (*)() -> 函数指针类型. 0前面有个括号,(void (*)())0,把0进行强制类型转换,此时0就是一个函数地址,此时再加个*进行解引用,再加一个括号去调用函数,不需要传参,因为指向的函数是无参的 总结:把0进行强制转换成函数指针类型,该指针指向的函数是无参,返回类型是void,当0变成一个函数地址时,对它进行解引用操作,去调用以0为地址的该函数。
- 解释代码2:
void (*signal(int, void(*)(int)))(int);
- 可以将上述代码用typedef关键字进行简化:
typedef void(*pfun_t)(int);//就是说将void(*)(int)整个换为*pfun_t
- 但是万万不可写成:
typedef void(*)(int) pfun_t; // err
- 此时上述代码就可优化为:
pfun_t signal(int, pfun_t);
- 总结:
- signal是一个函数声明,
- signal函数的参数有2个,第一个是int,第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void
- signal函数的返回类型也是一个函数指针,该函数指针指向的函数的参数是int,返回类型是void
2、函数指针数组
- 前面我们学习到了指针数组
#include<stdio.h> int Add(int x, int y) int Sub(int x, int y) int Mul(int x, int y) int Div(int x, int y) int main() // 指针数组 int* arr[5]; //数组的每个元素是int* int(*pa)(int, int) = Add;// 存了Add,也可以存Sub,Mul,Div return 0;
这里我们创建了4个函数Add、Sub、Mul、Div,然后创建了函数指针来存放Add函数的地址,如果想要存其它的,只需要将Add换成其它的就可,但若都想存起来,则要创建4个不同的函数指针,继而有4个不同的变量,但duck不必这么麻烦,只需要用到函数指针数组即可:
//需要一个数组,这个数组可以存放4个函数的地址 - 函数指针的数组 int(*parr[4])(int, int) = Add,Sub,Mul,Div ;//函数指针数组
- 重新回顾下函数指针数组定义过程:
int *arr[10]; int (*parr1[10])();
parr1 先和 [ ] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
- 练习:
char* my_strcpy(char* dest, const char* src);
- 1.写一个函数指针 pf ,能够存放my_strcpy
char* (*pf)(char*, const char*);
- 2.写一个函数指针数组 pfArr ,能够存放4个my_strcpy函数的地址
char* (*pfArr[4])(char*, const char*);
2.2、用途(转移表)->计算器
- 先写一个不用函数指针数组版本的简易计算器:
#include<stdio.h> void menu() printf("****************************************\\n"); printf("***** 1. add 2. sub *****\\n"); printf("***** 3. mull 4. div *****\\n"); printf("***** 0. exit *****\\n"); printf("****************************************\\n"); int Add(int x, int y) return (x + y); int Sub(int x, int y) return (x - y); int Mull(int x, int y) return (x * y); int Div(int x, int y) return (x / y); int main() int input = 0; int x = 0; int y = 0; do menu(); printf("请选择\\n"); scanf("%d", &input); switch (input) case 1: printf("请输入两个操作数:>\\n"); scanf("%d %d", &x, &y); printf("%d\\n", Add(x, y)); break; case 2: printf("请输入两个操作数:>\\n"); scanf("%d %d", &x, &y); printf("%d\\n", Sub(x, y)); break; case 3: printf("请输入两个操作数:>\\n"); scanf("%d %d", &x, &y); printf("%d\\n", Mull(x, y)); break; case 4: printf("请输入两个操作数:>\\n"); 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;
- 上述代码过于繁琐,可以使用函数指针数组进行简化
- 函数指针数组的用途->转移表
int main() int input = 0; int x = 0; int y = 0; // pfArr是一个函数指针数组,用途->转移表 int(*pfArr[])(int, int) = 0 ,Add,Sub,Mull,Div ; do menu(); printf("请选择\\n"); scanf("%d", &input); if (input >= 1 && input <= 4) printf("请输入操作数\\n"); 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;
主体定义函数的内容跟上份代码一样,这里就先省略,直接写出主要使用函数指针数组的内容。使用函数指针数组就可以不需要再使用这么多次的switch case语句了。避免了代码的过多重复性
- 注意:
- 在上上份的switch case法计算器中,有一段代码出现多次:
printf("请输入两个操作数:>\\n"); scanf("%d %d", &x, &y);
- 解决方法如下:需要用到回调函数,这里简单使用下,后续会详细介绍。
#include<stdio.h> void menu() printf("***** 1. add 2. sub *****\\n"); int Add(int x, int y) return (x + y); int Sub(int x, int y) return (x - y); 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 input = 0; do menu(); printf("请选择\\n"); scanf("%d", &input); switch (input) case 1: Calc(Add); break; case 2: Calc(Sub); break; default: printf("选择错误\\n"); break; while (input); return 0;
- 这里简单拿Add和Sub这两个函数举例子,后续会详细介绍回调函数。
3、指向函数指针数组的指针
- 代码解释:
#include<stdio.h> int Add(int x, int y) return x + y; int main() int arr[10] = 0 ; //数组指针p int(*p)[10] = &arr; //函数指针pf int(*pf)(int, int); //函数指针数组pfArr int(*pfArr[4])(int, int); //函数指针数组指针ppfArr int(*(*ppfArr)[4])(int, int) = &pfArr; //ppfArr首先和*结合说明是指针,指针指向的是数组,数组有4个元素,每个元素的类型是函数指针 /*正解: ppfArr是一个数组指针,指针指向的数组有4个元素 指向的数组的每个元素的类型是一个函数指针 int(*)(int,int)*/ return 0;
4、回调函数
- 概念:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
4.1、回顾冒泡排序
void bubble_sort(int arr[], int sz) int i = 0; for (i = 0; i < sz - 1; i++) int j = 0; for (j = 0; j < sz - 1 - i; j++) if(arr[j]>arr[j+1]) int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; #include<stdio.h> int main() int arr[10] = 9,8,7,6,5,4,3,2,1,0 ; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); int i = 0; for (i = 0; i < sz; i++) printf("%d ", arr[i]); //0 1 2 3 4 5 6 7 8 9 return 0;
- 我们都知道冒泡排序是专门用来排序整形数据的,那么浮点型数据和结构体该如何排序呢?
#include<stdio.h> struct stu char name[20]; int age; ; int main() struct stu s[3] = "zhangsan",20,"lisi",30,"wangwu",10 ; float f[] = 9.0,8.0,7.0,6.0,5.0,4.0 ; return 0;
- 此时就引出万能函数qsort
4.2、使用qsort函数
- qsort - 库函数 - 排序
- 算法思想: 快速排序 quick sort
- 格式:
void qsort( void* base, ---> 目标数组 arr size_t num, ---> 待排序元素的个数 n size_t width,---> 元素的字节大小 int(* cmp)(const void* elem1, const void* elem2) ---> 函数比较 cmp_int );
- 讲解下void*
void*类型的指针 可以接收任意类型的地址,且不报警告
- 正文:
#include<stdio.h> #include<stdlib.h> #include<string.h> int cmp_int(const void* e1, const void* e2) //比较整形值 return *(int*)e1 - *(int*)e2; void test1() int arr[10] = 9,8,7,6,5,4,3,2,1,0 ; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); int i = 0; for (i = 0; i < sz; i++) printf("%d ", arr[i]); int cmp_float(const void* e1, const void* e2) // 比较浮点数值 /*if (*(float*)e1 == *(float*)e2) return 0; else if (*(float*)e1 > *(float*)e2) return 1; else return -1;*/ // 或者int强转 return (int)(*(float*)e1 - *(float*)e2); void test2() float f[] = 9.0,8.0,7.0,6.0,5.0,4.0 ; int sz = sizeof(f) / sizeof(f[0]); qsort(f, sz, sizeof(f[0]), cmp_float); int j = 0; for (j = 0; j < sz; j++) printf("%.1f ", f[j]); struct stu char name[20]; int age; ; int cmp_stu_by_age(const void* e1, const void* e2) // 比较年龄 return ((struct stu*)e1)->age - ((struct stu*)e2)->age; void test3() struct stu s[3] = "zhangsan",20,"lisi",30,"wangwu",10 ; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_age); int cmp_stu_by_name(const void* e1, const void* e2) // 比较名字就是比较字符串 // 字符串比较不能直接用><=来比较,应该用strcmp函数 return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name); void test4() struct stu s[3] = "zhangsan",20,"lisi",30,"wangwu",10 ; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); int main() //test1(); //排序整形数组 //test2(); //排序浮点数 //test3(); //排序结构体 通过年龄比较 test4(); //排序结构体 通过名字比较 return 0;
4.3、使用回调函数,模拟实现qsort(采用冒泡的方式)
#include<stdio.h> struct stu char name[20]; int age; ; void Swap(char* buf1, char* buf2, int width) int i = 0; for (i = 0; i < width; i++) char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; void bubble_sort(void* base, int sz, int width, int(*cmp)(void* e1, void* e2)) int i = 0; for (i = 0; i < sz - 1; i++) int j = 0; for (j = 0; j < sz - 1 - i; j++) //两个元素比较 if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) //交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); int cmp_int(const void* e1, const void* e2) //比较整形值 return *(int*)e1 - *(int*)e2; int cmp_stu_by_age(const void* e1, const void* e2) // 比较年龄 return ((struct stu*)e1)->age - ((struct stu*)e2)->age; int cmp_stu_by_name(const void* e1, const void* e2) // 比较名字就是比较字符串 // 字符串比较不能直接用><=来比较,应该用strcmp函数 return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name); void test5() int arr[10] = 9,8,7,6,5,4,3,2,1,0 ; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); void test6() struct stu s[3] = "zhangsan",20,"lisi",30,"wangwu",10 ; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age); void test7() struct stu s[3] = "zhangsan",20,"lisi",30,"wangwu",10 ; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name); int main() //test5(); //排序整形数组 //test6(); //排序结构体 通过年龄比较 test7(); //排序结构体 通过名字比较 return 0;
以上是关于C*指针进阶的主要内容,如果未能解决你的问题,请参考以下文章