复习
在上一篇博客中我简单的介绍了一些,这里我们来复习一下
字符指针
#include<stdio.h> int main() { char* pc = "abcdef"; printf("%p\n", pc); printf("%c", *pc); return 0; }
pc存放的是字符a的地址,不是字符串
前面我们还学习到了const char * pa 和char *coust pa
数组指针
简单的讲就是指向数组的指针
#include<stdio.h> int main() { int arr[10] = { 0 }; int(*parr)[10] = &arr; printf("%p\n", parr); printf("%p", parr + 1); return 0; }
可以计算出两个地址相差40,也就是40个字节
这里我们又会引出一个知识点,就是数组名的理解,只有&arr 和sizeof(arr)中的arr是整个数组,不是数组首元素地址,其他的数组名都是首元素地址
如果是二维数组传参的话,传递的是第一行的地址
指针数组
简单的说还是数组,只不过元素的类型是指针类型
#include<stdio.h> int main() { int a = 0; int b = 0; int c = 0; int* arr[] = { &a, &b,&c }; printf("%p", arr[0]); return 0; }
数组传参和指针传参
数组传参
1. 传递的是数组首元素的地址
2. 一维数组传参,传递的是第一个元素的地址
3. 二维数组传参,传递的是第一行的地址
4. 数组在传参的时候,形参可以写成数组或者指针
5. 如果是二维数组传参,形参为指针要使用数组指针
函数指针
顾名思义就是指向函数的指针
#include<stdio.h> int Add(int a, int b) { return a + b; } int main() { int a = 4; int b = 5; int (*pA)(int, int) = Add; printf("%d", pA(a, b)); return 0; }
在这里&Add和Add都是传递函数的地址过去,还有一点就是(*pA)(a, b)和(pA)(a, b)是一样的,
函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,可以理解是存放地址的数组。那函数指针数组就是存放函数指针的数组,类型就是函数指针类型,那我就以上面的为例
#include<stdio.h> int Add(int a, int b) { return a + b; } int main() { int a = 4; int b = 5; int (*parr[])(int, int) = { Add }; printf("%d", parr[0](a, b)); return 0; }
看看是一样的效果
那我们来写一个计算器
#include<stdio.h> void menu() { printf("*************************************\n"); printf("***** 0.exit 1.add *****\n"); printf("***** 2.sub 3.mul ******\n"); printf("***** 4.div ******\n"); printf("*************************************\n"); } 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 catf(int(*pa)(int, int)) { printf("输入两个整数:>"); int num1 = 0; int num2 = 0; scanf("%d %d", &num1, &num2); return pa(num1, num2); } int main() { while (1) { menu(); int input = 0; printf("请选择:>"); scanf("%d", &input); int (*arr[])(int, int) = { NULL,add, sub, mul, div }; if (!input) { printf("结束"); break; } else if (input > 4) { printf("输入错误\n"); continue; } printf("%d\n", catf(arr[input])); } return 0; }
这里我运用了函数指针数组(转移表),可以节约很多的代码量,简单的说就是很好用
指向函数指针数组的指针(可以理解为数组的指针,只是类型变了)
前面我们学过了数组指针,这里的意思也是一样的只是叫法不一样,那这个类型就是函数指针类型
,函数指针类型是这样的 int (* )(), 那指向函数指针数组的指针的写法就是 int (* (*parr)[num])(),感觉很别扭
少说废话,直接演示
#include<stdio.h> int main() { int a = 5; int b = 2; int* arr[] = { &a, &b }; int* (*parr)[2] = &arr; printf("%d", *(*parr[0])); return 0; }
这是整形指针数组,指向整形指针数组的指针
同理函数指针数组的指针如下
#include<stdio.h> int add(int a, int b) { return a + b; } int main() { int (*arr[])(int, int) = { add }; int (*(*parr)[1])(int, int) = &arr; printf("%d", (*parr[0])(5, 6)); return 0; }
这里我只使用了一个函数,小可爱可以看看思路,和数组指针的写法是一样,只是这个类型不一样而已
回调函数
这个知识点主要涉及到函数指针的,比较重要,我们来学习
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
我们可以简单理解为回调函数的地址作为参数传递给另一个函数,也就是说回调函数通过通过传递本身地址给另外一个函数
上面我们写的计算器的代码就是使用到了回调函数
这里我简单的画了一下,回调函数就是add()函数,直接调用就是通过函数名直接调用
如果这里还不明白那就举一个函数qsort(),这是一个排序函数,底层是快速排序
#include<stdio.h> #include<stdlib.h> void print_arr(int* p, int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); } printf("\n"); } int cmp(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } int main() { int arr[] = { 9,8,7,4,5,6,1,2,3 }; int sz = (sizeof arr / sizeof(int)); print_arr(arr, sz); qsort(arr, sz, sizeof(arr[0]), cmp); print_arr(arr, sz); return 0; }
可能一些小可爱不懂这个函数,那我就来介绍一下这个函数
void* :我们可以理解为一个垃圾桶,可以接收任何指针类型的数据,比如可以接收 int* 、char* 等类型的数据
base:就是我们要从哪里开始排序的第一个元素地址(待排序数据的起始地址)
size_t: 在一些编译器中 是unsigned int,而在一些编译器中就不是,这里就是unsigned int
num:我们要排序的元素个数
width:我们排序的元素中一个元素的大小,单位是字节
compare:是一个函数地址
我们可以想想前面我们的冒泡排序算法
#include<stdio.h> #include<stdlib.h> int main() { int arr[] = { 1,5,4,8,26,8,5,2,15,9 }; int sz = sizeof arr / sizeof(arr[0]); int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = 0; j < sz - i - 1; j++) { if (arr[j] > arr[j + 1]) { arr[j] = arr[j] ^ arr[j + 1]; arr[j + 1] = arr[j] ^ arr[j + 1]; arr[j] = arr[j] ^ arr[j + 1]; } } } return 0; }
主要核心就是两两相邻的元素进行比较,我们要理清排几次,每次怎么比较,冒泡排序有一些缺点,就是有点死板,只能排序某一类型,而qsort()可以排序任何类型,主要是在于compare函数的写法;这个函数是用来比较两个元素的,这两个元素可能是整形、字符、结构体等类型,使用的方法也会不同,但最终要返回 >0 <0 ==0的结果,简单的说就是我们要比较啥类型的这个函数就要写成该类型的比较方法
废话少说,直接演示
int ( *compare )(const void *e1, const void *e2 )
这是qsort函数里 的参数,我来解释一下,
可能有一些小可爱不明白void*,那我来解释一下
平时我们写一个int*类型的指针,我们加1会跳过4个字节,char* 类型加1跳过一个字节,如果我们在写比较两个元素比较的方法,不指明类型就会不懂加1跳过几个字节,所以规定了void *类型 不能解引用,也不能加减操作
#include<stdio.h> #include<stdlib.h> struct test { char arr[20]; int age; }; int cmp2(const void* e1, const void* e2) { return ((struct test*)e1)->age - ((struct test*)e2)->age; } int main() { struct test arr[] = { {"ksdads", 15},{"ksdajk", 18} ,{"ksdadgjhjs", 10} }; qsort(arr, sizeof arr / sizeof arr[0], sizeof(arr[0]), cmp2); return 0; }
这是结构体比较年龄,如果要比较名字的话
int cmp3(const void* e1, const void* e2) { return strcmp(((struct test*)e1)->arr, ((struct test*)e2)->arr); }
那我们可不可以写一个以冒泡排序为逻辑的qsort函数呢?
我们来试试看
#include<stdio.h> #include <stdlib.h> #include<string.h> void print(int* arr, int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } // 比较两个元素 int cmp(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } //比较字符 int cmp1(const void* e1, const void* e2) { return *(char*)e1 - *(char*)e2; } //两个元素进行交换 void exchange(char* e1, char* e2, size_t byt) { int i = 0; for (i = 0; i < byt; i++) { char a = *(e1 + i); *(e1 + i) = *(e2 + i); *(e2 + i) = a; } } void my_qsort(void* base, size_t sz, size_t byt, int (*conpi)(const void*, const void*)) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (conpi((char*)base + j * byt, (char*)base + (j + 1) * byt) > 0) { exchange((char*)base + j * byt, (char*)base + (j + 1) * byt, byt); } } } } void test(void *arr, int sz, size_t by) { print(arr, sz); my_qsort(arr, sz, by, cmp); print(arr,sz); } int main() { int arr[] = { 8,3,1,5,2,3,6,9,7,4,8,9,2,4}; char arr1[] = "dsfdfgfdfgfdf"; // 整形 //test(arr, sizeof arr/ sizeof(arr[0]), sizeof (arr[0])); // 字符串 printf("%s\n", arr1); my_qsort(arr1, sizeof arr1 / sizeof(arr1[0]), sizeof(arr1[0]), cmp1); printf("%s\n", arr1); return 0; }
可能有一些小可爱不明白这里,那我来讲讲,因为,我们要构造一个qsort函数,那我们就该要传入对应的base(起始地址)、unm(长度)、wigth(一个元素的字节长度) 和函数cmp(用于比较两个的函数),
我们不知道要传入啥数据类型,我就用void*进行接收,使用上面这个方法是为了让对于的存储的内存进行交换,转换成char*是为了更好的计算
这幅图是我以两个元素进行交换的情况一个元素的内存可以拆分成内存单元(一个字节),转换成char*,加1,我们访问一个字节,这样就可以解决类型不同,访问的字节也不同的问题了
总结
到这里我就介绍结束了,有不懂的小可爱可以私聊我