目录
1.字符指针
2. 指针数组
3. 数组指针
3.1 数组指针的定义
3.2 &数组名VS数组名
4. 函数指针
代码1( * ( void (*)( ) )0 )( );
代码2void (*signal( int , void(*)(int) ) )(int);
代码2的简化
5. 函数指针数组
6. 回调函数
qsort函数
qsort函数讲解:
qsort函数运用:
用冒泡排序实现qsort函数
1.函数参数:
2.比较方式:
3.交换逻辑
7.代码仓库
指针的概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:int main() { char ch = 'w'; char *pc = &ch; *pc = 'w'; return 0; }
int main() { const char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗? printf("%s\n", pstr); return 0; }
代码 const char* pstr = "hello world.";特别容易让同学以为是把字符串 hello world 放到字符指针 pstr 里了,但是/本质是把字符串 hello world. 首字符的地址放到了pstr中
指针数组是一个存放指针的数组。
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。int (*p)[10]
解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
注意:[ ]的优先级要高于*号的,所以必须加上( )来保证p先和*结合
对于下面的数组:
int arr[10];
arr 和 &arr 分别是什么?我们知道
arr是数组名,数组名表示数组首元素的地址。
&arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
&arr 的类型是: int(*)[10] ,是一种数组指针类型
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr[5]; 为数组,每个元素为int类型
int *parr1[10]; 为指针数组,每个指针指向的元素为int类型
int (*parr2)[10]; 为数组指针,指针指向的数组每个元素为int类型
int (*parr3[5])[3]; 为数组指针数组,parr3数组有5个元素,每个元素为int(*)[3]的数组指针图解int (*parr3[5])[3]
首先看一段代码:
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void *pfun2();
pfun1可以存放。
pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
阅读两段有趣的代码:
//代码1 (*(void (*)())0)(); //代码2 void (*signal(int , void(*)(int)))(int);
代码1
( * ( void (*)( ) )0 )( );该代码是一次函数的调用,调用0地址处的函数。
首先代码中将0强制类型转换为类型是 void (*)( ) 的函数指针,
然后去调用0地址处的函数
代码2
void (*signal( int , void(*)(int) ) )(int);该代码是函数signal的一次声明。
signal函数的参数有两个,第一个是 int 类型,第二个是函数指针类型。该指针指向的函数参数为 int 类型,函数返回类型为 void。
signal函数的返回类型为函数指针,该指针指向的函数参数为 int 类型,函数返回类型为 void。
代码2的简化
我们可以使用 typedef 进行类型简化,将 void(*)(int) 简化为 pf_t 。但 pf_t 必须写进(*)里面
typedef void(* pf_t )(int);
所以代码2可简化为:pf_t signal(int, pf_t);
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组
int (* parr1[10] )( );
parr1 先和 [ ] 结合,说明 parr1是数组,数组的内容是 int (*)( ) 类型的函数指针。
函数指针数组的用途:转移表
例子:(计算器)以下为常规写法
#define _CRT_SECURE_NO_WARNINGS 1 #include
void menu() { printf("**************************\n"); printf("**********1,add***********\n"); printf("**********2,sub***********\n"); printf("**********3,mul***********\n"); printf("**********4,div***********\n"); printf("**********0,exit**********\n"); printf("**************************\n"); } int Addnum(int x, int y) { return x + y; } int Subnum(int x, int y) { return x - y; } int Mulnum(int x, int y) { return x*y; } int Divnum(int x, int y) { return x / y; } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); printf("请输入选项\n"); scanf("%d", &input); switch (input) { case 0: printf("退出程序\n"); break; case 1: printf("请输入两个操作数\n"); scanf("%d %d", &x, &y); ret = Addnum(x,y); printf("%d\n", ret); break; case 2: printf("请输入两个操作数\n"); scanf("%d %d", &x, &y); ret = Subnum(x, y); printf("%d\n", ret); break; case 3: printf("请输入两个操作数\n"); scanf("%d %d", &x, &y); ret = Mulnum(x, y); printf("%d\n", ret); break; case 4: printf("请输入两个操作数\n"); scanf("%d %d", &x, &y); ret = Divnum(x, y); printf("%d\n", ret); break; default: printf("输入错误,请重新选择\n"); break; } } while (input); return 0; } 通过观察,我们得知这种常规写法会过于冗余、复杂。
以下是我们通过函数指针数组修改得到的代码,
#define _CRT_SECURE_NO_WARNINGS 1 #include
void menu() { printf("**************************\n"); printf("**********1,add***********\n"); printf("**********2,sub***********\n"); printf("**********3,mul***********\n"); printf("**********4,div***********\n"); printf("**********0,exit**********\n"); printf("**************************\n"); } int Addnum(int x, int y) { return x + y; } int Subnum(int x, int y) { return x - y; } int Mulnum(int x, int y) { return x*y; } int Divnum(int x, int y) { return x / y; } int(*pf[5])(int, int) = {0, Addnum, Subnum, Mulnum, Divnum}; int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); printf("请输入选项\n"); scanf("%d", &input); if (input == 0) { printf("退出程序\n"); break; } else if (input >= 1 && input <= 4) { printf("请输入两个操作数\n"); scanf("%d %d", &x, &y); ret = pf[input](x, y); printf("%d\n", ret); } else { printf("输入错误,请重新选择\n"); } } while (input); return 0; } 我们通过创建 int(*pf[5])(int, int) = {0, Addnum, Subnum, Mulnum, Divnum}; 进行函数的调用大大简化了代码。
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,
当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
qsort函数讲解:
特点:1. 采用快速排序的方法
2. 适合任意类型数据的排序
.
qsort函数参数
void qsort(
void* base, //排序的数组第一个元素地址
size_t num, //元素个数
size_t size, //一个元素的大小
int (*cmp)(const void* e1, const void* e2)
//函数指针类型,指向的函数能比较base中元素,
void*可接收任意类型元素地址,但无法对其进行解引用操作
);
qsort函数运用:
//void*可接收任意类型元素地址,但无法对其进行解引用操作 int cmp_t(void* e1, void* e2) { //使用前按照比较的内容进行“强制类型转换”,再解引用操作 return *(int*)e1 - *(int*)e2; } int main() { int arr[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, 10, sizeof(arr[0]), cmp_t); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
实现效果:
完结
要使用冒泡排序思想实现qsort函数效果,就要使冒泡排序的”主体框架“和”相邻比较“思想不变,进而实现此函数。
为了使bubble_sort函数能像qsort函数一样接收任意元素类型,我们就要对其”函数参数“、”比较方式“、”交换逻辑“进行轻微改动。
1.函数参数:
为达到和qsort函数一致的效果,bubble_sort函数参数设置应和qsort函数一致。
重新修改后的bubble_sort函数参数:
void bubble_sort( void *base,//首元素地址 int num,//元素个数 int width,//单个元素字节大小 int(__cdecl *compare)(void* e1, void* e2) //函数指针类型,指向的函数能比较base中元素 )
2.比较方式:
比较方式仍遵循冒泡排序思想,故而其“主体框架”不变,“交换逻辑”发生变化。
void bubble_sort(void *base, int num, int width, int(__cdecl *compare)(void* e1, void* e2)) { int i = 0; //比较趟数 for (i = 0; i < num - 1; i++) { int j = 0; //相邻两数比较,每次比完少一个数 for (j = 0; j < num - 1 - i; j++) { //函数的新比较逻辑 } } }
3.交换逻辑
为使bubble_sort函数像qsort函数一样适用于任意类型,就要进行“交换逻辑”的变更。
当元素都是int类型时,只需设置同为int类型的tmp变量交换即可。但面对其他类型时,此方法就不可使用,我们则可进行单一字节的交换。
图解:
当两个整形变量进行交换时,先创建一个字节,然后交换四次即可完成。
修改后:
//由于前一步已经进行char*的强制类型转换,所以直接用char*类型接收 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 num, int width, int(*compare)(void* e1, void* e2)) { int i = 0; //比较趟数 for (i = 0; i < num - 1; i++) { int j = 0; //相邻两数比较,每次比完少一个数 for (j = 0; j < num - 1 - i; j++) { //函数的新比较逻辑 //先对base强制类型转换为char*类型 //然后可依照不同类型元素加不同的width进行“不同类型的相邻元素”比较 if (compare((char*)base+j*width,(char*)base+(j+1)*width)>0) { //创建swap函数进行“单一字节交换” swap((char*)base + j*width, (char*)base + (j + 1)*width, width); } } } }
图解swap函数:
实现效果:
以上为用冒泡排序实现qsort函数的详解。
https://gitee.com/jimmywang16/learn_1.git