前言:
本篇是指针的进阶的第二篇,在前面的文章中,我们已经对指针有了一个基础的了解,这进阶指针一里也学了字符指针,指针数组,数组指针,所以我们这一篇继续深入进阶指针,了解指针和其他知识的联系。
如果还没有看过指针篇的同学可以点下方链接先进行学习:
【C语言】从入门到入土(指针篇)
如果还没有看过指针篇的同学可以点下方链接先进行学习:
【C语言】从入门到入土(进阶之指针第一篇)
C语言指针进阶:
- 一.字符指针
- 二.指针数组
- 三.数组指针
- 1.回顾:
- 四. 数组传参和指针传参
- 1.一维数组传参
- 2.指针数组传参
- 3.二维数组传参
- 4.一级指针 传参
- 5.二级指针传参
- 五. 函数指针
- 六. 函数指针数组
- 七. 指向函数指针数组的指针
- 八. 回调函数
一.字符指针
在上一篇中讲到的哦:
【C语言】从入门到入土(进阶之指针第一篇)
二.指针数组
在上一篇中讲到的哦:
【C语言】从入门到入土(进阶之指针第一篇)
三.数组指针
在上一篇中讲到的哦:
【C语言】从入门到入土(进阶之指针第一篇)
1.回顾:
上一篇我们理解了指针数组和数组指针的概念与区别,在这里我们回顾一下,再进入下面的学习,用代码来表示说明:
int main()
{
//字符数组
char* ch = 'w';
char* p = &ch;
const char* arr = "abcdef";
//指针数组
int* arr2[10];
char* arr3[5];
//先于数组下标结合,再结合指针,指针数组
//数组指针
int (*arr4) [5];
//先与*结合,再结合数组下标,数组指针
return 0;
}
区分数组指针和指针数组,要看变量名先和谁结合,[ ]
的优先级是大于*
的,所以当要结合的时候,如果*和变量名没有括号围起,可能就是指针数组了。
接下来进入下面的进阶指针学习。
四. 数组传参和指针传参
我们在学习c语言的时候,难免会用到函数,也难免会进行数组传参和指针传参,那数组传参和指针传参是怎么传的呢,我们来研究一下:
1.一维数组传参
int main()
{
int arr[10] = { 0 };
test(arr);
return 0;
}
当我们创建一个数组进行函数传参的时候,我们用到的是数组名,所以传过去的是数组首元素的地址,那么接收的时候,我们也只需要接收一个地址。
因为数组的元素是连续存放的,我们可以根据首元素地址找到剩下的元素,所以在一维数组传参的时候,可以有以下版本:
int main()
{
//以下函数是在这个主函数的基础上写的
int arr[10] = { 0 };
test1(arr);
test2(arr);
test3(arr);
test4(arr);
return 0;
}
void test1(int arr[])
{
//因为并不会真实的创建,[]内可以无参数
printf("可以运行 ");
}
void test2(int arr[10])
{
//有参数也可以
printf("可以运行 ");
}
void test3(int arr[100])
{
//参数大小也没有关系
printf("可以运行 ");
}
void test4(int * p)
{
//传过来的是首元素地址,可以直接用指针接收
printf("可以运行 ");
}
2.指针数组传参
而指针数组的传参,其实也是跟一维数组相近,虽然是换了类型但还是数组,所以我们还是可以按照数组的传参一样:
void test1(int * arr[])
{
printf("可以运行 ");
}
void test2(int * arr[10])
{
printf("可以运行 ");
}
void test3(int * arr[100])
{
printf("可以运行 ");
}
int main()
{
int * arr[10] = { 0 };
test1(arr);
test2(arr);
test3(arr);
return 0;
}
而这里唯一一点点特别的就是,指针数组传参传过去的就是指针数组首元素的地址,既然是一级指针的地址,那我们可以用二级指针来接收,所以我们还有这种写法:
void test4(int** p)
{
printf("可以运行 ");
}
int main()
{
int * arr[10] = { 0 };
test4(arr);
return 0;
}
3.二维数组传参
而二维数组的传参,也可以参照部分一维数组的传参,为什么说是部分呢,因为二维数组传参的时候要规定一行有多少个元素,所以列数是一定不可以省略的:
void test1(int arr[5][10])
{
printf("执行成功!");
}
void test2(int arr[][10])
{
printf("执行成功!");
}
void test3(int arr[][])//错误!!!
{
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
printf("执行成功!");
}
int main()
{
int arr[5][10]={0};
test1(arr);
test2(arr);
test3(arr);
return 0;
}
但传参的时候我们注意到一点,传参的时候也是将数组名传过去的,数组名是首元素地址这句话在这里也没错,但首元素可就不是指arr[0][0]
了,二维数组首元素指的是第一行的元素。
其实就是把二维数组看作一维数组,那么划分的时候呢就是要划分每一行作为一个元素,所以我们取数组名的时候才是取出的首元素的就是第一行的元素。
所以我们二维数组传参的时候就可以考虑一个问题,传过去的本质是什么?我们来分析一下:
void fun(int (*p)[5])
{
//首先数组名是首元素地址,是地址,*p
//然后有5个数组元素,加上[5]
//由于[]比*优先级高,所以要(*p)[5]
//最后这个数组是int类型的
//所以是int (*p)[5]
printf("执行成功!");
}
int main()
{
int arr[3][5]={0};
fun(arr);
return 0;
}
总结:二维数组传参可以是二维数组数组接收,也可以是指针数组接收,但不能是二级指针接收,这是两码事。
4.一级指针 传参
一级指针传参,我们先来看,传过去的是指针嘛,我们直接用指针接收就可以了:
void fun(int * p,int sz)
{
//首元素地址传过来指针接收
//其实理论上写数组也可以,如int p[];
//因为数组理论上也是得到地址
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
}
int main()
{
int arr[] = { 1,2,3,4,5 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
fun(p, sz);
return 0;
}
总结一下:
一级指针接收可以接收什么参数:
void test(int* p)
{
printf("执行成功 \n");
}
int main()
{
int a = 10;
int arr[5] = { 1,2,3,4,5 };
int* p = &a;
test(&a);//a的地址
test(p);//指针储存的a的地址
test(arr);//数组名表示首元素地址
test(arr[0]);//首元素地址
test(NULL);//空指针(慎用,因为不知道你需要干什么)
return 0;
}
5.二级指针传参
按照惯例,传啥接啥嘛,二级指针传参,我直接用二级指针接收就可以了,这是比较简单的:
void test(int** p)
{
printf("执行成功!");
}
int main()
{
int a = 10;
int * pa = &a;
int** paa = &pa;
test(paa);
return 0;
}
但是我们同时也要思考,二级指针传过去可以二级指针接收,那二级指针能接收的东西有什么呢:
void test(int** p)
{
printf("执行成功!");
}
int main()
{
int a = 10;
int * pa = &a;
int** paa = &pa;
int * arr[5];
test(paa);//首先这个肯定是没有问题的
test(&pa);//既然paa等于&pa,所以这个也可以
test(arr);//一级指针数组,传过去首元素地址,同样是地址的地址
return 0;
}
五. 函数指针
函数有地址吗,注意:函数也是创建在栈上的,也是需要空间的,所以函数也有地址,也就是也会有函数指针,具体想知道函数的创建与销毁可以看一下这一篇文章:
【C语言】函数栈帧的创建与销毁
那么函数指针是什么样子的呢,我们看一下代码:
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p \n", Add)
printf("%p \n", &Add);
return 0;
}
显然,我们打印的是函数的地址,说明函数确实也是有地址的,也就是函数指针是存在的,同时这里的函数名就不像数组名一样是首元素地址了,函数首元素地址,这听起来也不合适,函数名和取地址函数名是一样的。那如果我创建一个指针去储存函数地址,要用什么样子的指针呢,我们来分析一波。
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*p)(int,int)=&Add
//p是用来存放地址的,p就是函数指针变量
printf("%p \n", &Add);
return 0;
}
①函数指针是指针,我们先写一个*p
②函数指针如果要接收参数,就写上参数
③这里是Add函数,所以就是 *p(int , int)
④然后函数返回是int类型,所以是int (*p)(int,int)
那么指针也是有类型的,比如整形指针,数组指针,所以函数指针也是有类型的,我们继续用类比法,前面的指针都是去掉变量名就是指针的类型,这里也不例外,这里我们创建的函数指针储存Add函数的地址的函数指针,他的类型就是:int (*)(int,int)
。
Q1: 函数指针的用处是在哪呢?
我们来看一下:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*p)(int, int) = &Add;
int ret = Add(2, 3);
printf("%d", ret);
ret = (*p)(3, 4);//调用函数指针进行函数调用
//既然指针储存了函数地址,解引用就可以知道这个函数
//函数指针中已经标明变量了,直接放入参数就可以了
printf("%d", ret);
return 0;
}
Q2:前面上到Add和&Add是一样的,那调用的时候和函数指针的关系是什么?
我们来看一下前面的代码:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pt)(int, int) = Add;
//和加取地址符一个意思
int (*pt)(int, int) = &Add;
int ret = Add(2, 3);
printf("%d", ret);
ret = (*pt)(3, 4);
printf("%d", ret);
return 0;
}
这里我们发现,函数名Add赋给pt之后,Add和pt应该是一样的了,我们可以把Add赋给pt,就说明两者的类型是一样的。如果这句话成立的话,那我们用Add(2,3)
调用函数的时候,是不是也可以理解为pt(2,3)
,也就是去除*
的ret = (*pt)(3, 4);
即为ret = pt(3, 4);
我们尝试一个代码测验一下:
代码中先进行Add
调用函数,正常运行,然后*pt
调用函数把ret
的值覆盖并变成自己调用后的返回值,pt
同样是覆盖并返回自己调用的函数值,说明pt
确实是和Add一样。
其实函数指针的变量名存的就是函数的地址了,但是为了让初学者更容易理解,一般是将指针解引用找到函数的地址,然后调用,实际上这里的解引用就是一个摆设,加多个解引用也没什么事情发生。
学完上面的内容,我们来两道有趣小题:
(*(void (*)())0)();
这个代码是什么意思?void (*signal(int , void(*)(int)))(int);
这个代码如何理解?
首先我们来看第一个,突破口在那里呢,突破口就在这个数字0,数字是变量啊,那前面的一长串又是什么,我们来一步一步分析:
① 首先先将代码拉宽一点看看:
(* ( void (*)() ) 0)();
②这里的0是变量,那前面一个就是一个类型,但是前面并不是int 类型,而是一长串的东西,我们来分析0前面括号内容:
void (*)()
,类型+解引用括号+括号,是不是很像int (*pt) (int,int)
,没错就是函数指针去掉变量名,也就是函数指针的类型。
③所以
void (*)()
是函数指针的类型,将类型为int的0强制类型转换为函数指针类型,而这个类型是void (*)()
无返回无传参。
④然后最前面的
*
登场,解引用函数指针找到函数指针的地址,然后最后面的括号再传参,就是一个0的函数指针解引用又再调用函数。
解析:
1. 代码中把0强制类型转换为类型为void (*)()的一个函数的地址。
2. 解引用0地址,就是去0地址处的这个函数,被调用的函数是无参,返回类型是void
这里一眼看下去确实有点迷糊,但是我们要知道入手点,就能慢慢解开了。理解这个代码的含义,有助于我们理解前面的知识。
然后我们来看第二个,先分块看看:
void ( *signal(int , void(*)(int) ) )(int);
在这里,我们没有看见数字,那我们就从其他地方入手,比如signal,因为其他都是类型和解引用。
①signal后面跟一个括号,里面有两个东西,第一个是int类型,第二个是函数指针类型,但是并不是传参,而是类型说明,signal是一个函数说明。
②然后我们再看,除去
signal(int , void(*)(int) )
,剩下的就是void ( * )(int);
,这不就是一个函数指针类型吗,所以signal函数返回类型是void ( * )(int);
。
解析:
1.signal函数有两个参数,第一个是int类型,第二个是void(*)(int)
函数指针类型
2.signal函数的返回类型依然是void(*)(int)
函数指针类型。
既然我们signal函数的参数是函数指针类型,返回类型也是这个,那我们能不能简化一下呢,有的,那就是typedef
:
typedef void(*pfun_t)(int);
//typedef 重起一个名字
//如typedef int i1就是把int 类型多起一个名字叫i1
//但这里语法规定void(*)(int)这种类型名字要在*的旁边
int main()
{
void (*signal(int, void(*)(int)))(int);
//这里两种形式是相同的意思
pfun_t signal(int, pfun_t);
//将过长的代码简化
return 0;
}
六. 函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组:
int *arr[10];
//数组的每个元素是int*
那个整形指针数组是存放整形指针的数组,那么函数指针数组就是存放函数指针的数组,里面存放着多个函数指针。
我们用代码来演示说明:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int (*pf1)(int, int) = Add;
int (*pf1)(int, int) = Sub;
int (*pf1)(int, int) = Mul;
int (*pf1)(int, int) = Div;
return 0;
}
在这里我们定义了多个函数指针,但是和之前定义整形变量一样,万一不止4个,是10个100个呢,这时候我们就要用到函数指针数组了:
int main()
{
/*int (*pf1)(int, int) = Add;
int (*pf1)(int, int) = Sub;
int (*pf1)(int, int) = Mul;
int (*pf1)(int, int) = Div;*/
//四行代码一行搞定
int (*pfArr[4])(int, int) = {Add,Sub,Mul,Div};
//函数指针数组
return 0;
}
那么函数指针数组可以干什么呢,我们来试着写一个简单的就加减乘除的计算机吧,进入代码世界:
//这里省略了上面加减乘除的函数
void menu()
{
printf("*****************************\n");
printf("****1.add 2.sub************\n");
printf("****3.mul 4.div************\n");
printf("****0.exit ************\n");
printf("*****************************\n");
//设计菜单栏
}
int main()
{
int x = 0;
int y = 0;
int input = 0;
int ret = 0;
menu();
printf("请选择算法:");
scanf("%d", &input);//输入选择加减乘除
do {
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");
}
} while (input);//当遇到0退出程序
return 0;
}
这里就写出来一个简单的加减乘除的计算机,但是我们如果还想计算更多的值呢,有没有更简便更实用的代码,这就用到了函数指针数组:
//这里省略了上面的加减乘除的函数
//省略了上面的菜单
int main()
{
int input = 0;
do {
int ret = 0;
int x = 0;
int y = 0;
menu();
printf("请选择:>");
scanf("%d", &input);
int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
//用函数指针数组存储函数指针
if (input == 0)
{
printf("退出计算机\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
//input直接选择了那种算法
printf("%d\n", ret);
}
else
{
printf("选择错误");
}
} while (input);
return 0;
}
在这里,我们用函数指针数组来存储函数指针,当我们要增加算法也只需要在数组里面添加就可以了,我们在创建数组的时候,用0作为首元素,这样子当我们输入其他的数组的时候,刚好对应上其算法。
然后,除了可以转变为函数指针数组的形式,其实我们也可以通过函数指针去简化第一个代码,也就是有多个case 的代码:
//在这里,我们可以通过观察case里相近的东西
//然后做一个函数去直接调用
//这里省略了加减乘除的函数
int calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
int main()
{
int input = 0;
do {
menu();
printf("请选择算法:");
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");
}
} while (input);
return 0;
}
在之前,我们如果要函数调用去执行一串的代码的时候,可能需要写几个函数,但是现在我们利用函数指针去调用,直接用一个函数就可以解决了,当我们在主函数中调用到calc
函数的时候,由calc
函数再通过函数指针调用到那一个算法函数里,大大减少了 代码的冗余。
七. 指向函数指针数组的指针
我们有指向整形数组的指针,有指向整形指针数组的指针,那也会有指向函数指针数组的指针吧?答案是:有的。
我们继续来一步一步推一下:
int arr[10];
int(*p)[10] = &arr;
//p是指向整形数组的指针
int* arr[10];//整形指针数组
int* (*p)[10] = &arr;//存储整形指针数组的指针
//p是指向整形指针数组的指针
那么函数指针数组的指针,也可以一步一步来得到:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add;//pf是函数指针
int(*pfArr[5])(int , int);//pfArr是一个函数指针数组
int (*(*ppfArr)[5])(int, int);//ppfArr是指向函数指针数组的指针
}
最后一个ppfArr
是指向函数指针数组的指针,如何理解呢,首先ppfArr
先与*
结合,是一个指针。然后这个指针指向数组[5]
,去掉(*ppfArr)[5]
后剩下的就是类型,为int (*)(int, int)
,也就是函数指针类型,所以这ppfArr
是一个指向函数指针数组的指针。
例子:
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[10])(const char*) = &pfunArr;
return 0;
}
八. 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
像我们在上面的calc
函数回调的Add之类的就是回调函数,通常以一种函数指针的形式进行。通过函数指针响应相应的函数,所以我们的函数指针是非常有用的。
在这里学习回调函数之前,我们先学习一个库函数——qsort
。我们之前学习过的排序有冒泡排序,选择排序,插入排序,而这个qsort
则是一个快速排序。我们可以去cplusplus
查看:qsort c++ reference。
qsort
是一个快速排序的库函数,我们先来看看他的参数:
void qsort(void* base,//void *是无具体类型指针
size_t num,
size_t size,
int (*cmp)(const void* e1, const void* e2)//函数指针
);
第一个
void* base
,当我们把一个数组传进去的时候,有不同的类型,如果我们写的是int * base
,那传字符型的时候就会报错。所以我们使用无具体类型指针接收,传任意类型的地址都可以接收,但缺点是因为无具体类型不能进行运算。
第二个是
size_t num
,意义是待排序的元素个数,第三个是size_t size
是一个元素的大小,单位是字节。也就是接收了数组的信息。
而最后一个是
int (*cmp)(const void* e1, const void* e2)
,是一个函数指针。当我们排序的时候不是整形的时候,如结构体的时候,有时候不知道哪两个进行比较,这时候就有const void*e1, const void* e2
,就是将这两个进行比较。cmp
指向的是:排序时,用来指向比较2个元素的函数的指针。
上代码:
#include <stdlib.h>//qsort库函数需引头文件
//测试排序整形数组
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1 };
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]);
}
return 0;
}
在这里,cmp_int
就是回调函数,那函数里面是如何理解的呢,我们来分析一下:首先传进去的两个无类型指针,我们这里想比较的是整形,所以解引用整形指针拿到这个数据。然后就是比较,在这里qsort
函数有以下规定,当第一个元素大于第二个元素,返回值大于0;相等返回0;小于返回值小于0。所以我只需要返回第一个元素减第二个元素就可以了。
我们再来看看用qsort
函数去结构体排序:
struct Stu//创建结构体
{
char name[20];
int age;
};
int cmp_by_name(const void* e1, const void* e2)//用名字比较!
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
//当我们想访问结构体的时候,先把e1,e2强制类型转换为结构体
//然后->访问name,对比两个字符串使用strcmp库函数
//且刚好strcmp的返回值和qsort比较的返回值一样
//直接return 返回即可
}
void test2()
{
struct Stu s[3] = { {"zhangsan",15}, {"lisi",20},{"wangwu",18} };
//创建结构体变量
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_by_name);
}
int main()
{
test2();
return 0;
}
这之前学习库函数的时候,我们理解其他原理之后,也仿制了一个类似的自定义函数,在这里我们学习完qsort
函数,当然要尝试做一个类似的自定义函数:
在之前,我们学习了排序整形数组的冒泡排序,我们可以通过他与qosrt
结合,来制作一个自定义函数,可以容纳多种类型的排序。我们来写一个自定义函数my_qsort
.
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//交换函数
void Swap(char * buff1,char * buff2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buff1;
*buff1 = *buff2;
*buff2 = tmp;
buff1++;
buff2++;
}
}
//自定义qsort
//使用回调函数实现一个通用的冒泡排序函数
void my_qsort(void* base, size_t num, size_t width,
int (*cmp)(const void*e1, const void*e2))
{
int i = 0;
for (i = 0; i < num; i++)
{
int j = 0;
for (j = 0; j < num - 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);
//交换函数
}
}
}
}
void test3()
{
int arr[] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), cmp_int);
//自定义快速排序函数
}
int main()
{
test3();//主函数调用test3()进行测试
return 0;
}
完整代码就在这上面了,我们来解释一下:cmp_int
,int main
以及test3
就不用解释了,这几个就是后面测试的时候使用的几个函数,整体看向my_qsort
和Swap
函数:
①首先我们要包容各种类型比较以及实现
qsort
的功能,我们的参数就应该仿照qsort
,所以我们my_qsort
的参数类型就是qsort
的参数类型。
②然后进入函数内部,我们需要一个一个元素进行对比,所以我们使用冒泡排序的方法进行一个一个的比较。为了仿照
qsort
的返回值判断方法,我们也是当返回值大于0的时候才进行换位。
③那么对比这里就比较难了,首先进行对比我们需要换成一种可以对比的类型,这里我们选择
char
类型,是因为char
是一个字节的类型,当后面的元素进行比较的时候,我们加上width
元素宽度就可以知道后面的元素了。然后对比完第一个和第二个,就要到第二个和第三个比较,所以我们使用j*width
表示现在这一次比较需要比第一次的元素跳过多少个字节。最后我们的对比就是:(char*)base+ j * width
和(char*)base + (j + 1) * width)
的对比了。
④如果对比的返回值大于0,则进入交换函数
Swap
。由于我们不知道我们需要交换的类型是什么,然后我们可以同样用char
来表示,无论那个类型一个一个字节交换之后,整体就相当于交换了。所以我们传进去的参数是两个对比的和元素的宽度,然后循环元素宽度次交换就可以把整体交换完了。
⑤最后就相当于把
qsort
函数的自定义版完成了,然后我们尝试使用一次得到的结果也是成功的,当然我们这里只测试了整形,如果想测试其他的大家可以自己去尝试一下哦。
好啦,本篇的内容就到这里,关于指针进阶的内容也到这里啦,最后也请继续关注我哦。关注一波,互相学习,共同进步。
还有一件事: