指针详解



指针详解

....
1.字符指针2.指针数组3.数组指针4.数组与指针参数
5.函数指针6.函数指针数组7.函数指针数组指针8.回调函数
9.模拟qsort

在这篇文章之前我还写了一篇指针基础


1.字符指针

我们知道 字符指针指向字符地址,并且非常熟悉他的用法,比如下面:

#include <stdio.h>
int main()
{
 char arr[] = "abcdef";
 char* p = arr;
 printf("%s\n", arr);
 printf("%s\n", p);
 return 0;
}

输出结果是:

abcdef

abcdef

那如果我们这样写呢???

#include <stdio.h>
int main()
{
    char* p = "abcdef"; // 这是一个常量字符串
    printf("%s\n", p);
    printf("%c\n", *p);
    return 0;
}

运行结果是:

abcdef
a

这个代码特别让人容易误会是把字符串 abcdef放到字符指针p里面,实际是把该字符串的首字符地址放到了指针p里面,

并且强调 !!! ,这样写就代表着 abcdef是一个常量字符串,不可修改.

比如我们再写一个语句 *p = ‘w’;

就会发生段错误,因为不可修改

image-20210406141044588

因此,如果我们进行第二种写法,最好规范书写,就是加上const,以便我们理解

#include <stdio.h>
int main()
{
 const char* p = "abcdef";
 printf("%s\n", p);
 printf("%c\n", *p);
 return 0;
}
下面有两道面试题,请写出答案:

image-20210406141921853

答案:

第一道 哈哈

第二道 呵呵

解析:

第一道: 因为arr1arr2是两个数组,他们所占据的空间不同.而数组名是数组首元素地址,因此arr1与arr2的地址又不同,

所以 arr1 != arr2,所以回打印 哈哈

image-20210406142529866

第二道: 因为这种写法代表着就是abcdef是常量字符串,而p1与p2的值都是abcdef,所以计算机为了节约空间,就不再开辟多的空间,只开辟一块

image-20210406142801660

所以 p1 == p2,打印 呵呵

同理,我们为了规范书写,还是在char*前面加上const

2.指针数组

指针数组:重点是后面,数组,所以指针数组是数组,即数组里面的元素类型是指针.

我们知道 一个数组的构成包括是三个部分 : 元素类型 数组符号[] 数组名(可以省略)

比如 **int arr [3] 意思是一个数组,他的名字是arr,该数组含有3个元素,每个数组类型是int **

char arr[4] 意思是一个数组,他的名字是arr,该数组含有4个元素,每个数组类型是char

那么,指针数组该怎么写呢??我们一步一步的来.

  • 第一步,数组符号 []

  • 第二步,写上数组名 arr

  • 第三步,写上数组类型 (指针) int* char*…等

先在我们要求写一个数组名是pp,含有6个元素的整型指针数组.

int* pp[6];

运用:

低级运用

#include <stdio.h>
int main()
{
 int a = 10;
 int b = 20;
 int c = 30;
 int* all[3] = {&a,&b,&c};
 for(int i = 0;i<3;i++)
 {
     printf("%d\n",*all[i]);
 }
 return 0;
}

运算结果:

10

20

30

高级运用

#include <stdio.h>
int main()
{
 int arr1[] = {1,2,3};
 int arr2[] = {4,5,6};
 int arr3[] = {7,8,9};
 int* all[3] = {arr1,arr2,arr3};
 int i = 0;
 for(i = 0;i<3;i++)
 {
     int j = 0;
     for(j = 0;j<3;j++)
     {
         printf("%d ", *(all[i]+j));//利用了指针加减整数
//还可以这样写printf("%d ", all[i][j]);
     }
     printf("\n");
 }
 return 0;
}

运行结果:

1 2 3
4 5 6
7 8 9

注释里面这样写 all[i][j]的原因是all[i]等于数组名,数组名再跟上数组符号[],就等于新数组

3.数组指针

我们在2里面说了指针数组,现在我们讨论数组指针,注意,重点是指针,所以数组指针是 指针,指向的是数组,存放的是数组地址

我们知道指针的写法是 指针类型加上变量名.

比如 char p,称为字符指针 int p称为整型指针**

那么数组指针的类型怎么写呢???,假设有个整型数组叫arr

是这样吗? int *p = &arr 错,这是整型指针,差数组符号

是这样吗? int[] *p = &arr 错,int[]这种形式代表着int是一个数组名,而我们不能用数据类型做变量名

是这样吗? int *p[] = &arr 错,[]的结合性比*高,即p是数组,不是指针,即*p[]代表着在解引用一个数组p[]

综合上述,我们把(*p)括起来,这样就代表p是指针了.

所以,整型数组指针这样写 int (*p)[] = &arr ,意思是,指针p指向数组arr,arr的类型是int

现在我们进行剖析 p是指针,那么剩下的就是 类型 即 int(* )[]

有人会问,为什么不这样写? int(* )[] p,这是c的规定,*与p必须在一起

出题,这里有一个数组char arr[10] = "abcdefabc";请写出数组指针存放arr

第一步,写出指针 (*p)

第二步,写出指向的类型 [10]

第三部,写出指向的数组的类型 char

所以就是 char (*p) [10] = &arr;

出题,这里有个整型指针数组 int* arr[3] = {&a,&b,&c};请写出数组指针存放arr

按照上面的步骤就是下面答案

int* (*p) [10] = &arr; 意思是,指针p,指向数组arr,arr的元素类型是int*

低级运用:

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9};
    int (*pa)[10] = &arr; //一定是存放数组地址哦,而不是arr首元素地址哦
    for(int i = 0;i<10;i++)
    {
        printf("%d ",(*pa)[i]); //把(*pa)括起来是因为[]的结合性比*高,保证pa是指针
 //因为   pa等于  &arr
 //所以  *pa等于arr
 //     *pa+1 等于 arr+1
        //所以,我们还可以这样写 printf("%d ",*(*pa + 1));   利用指针加减整数访问
    }
    return 0;
}

结果 1 2 3 4 5 6 7 8 9 10

但是就是一个一维数组而已,我么完全不用这样写,太累了,所以,数组指针我么是用于二维数组

高级运用:

#include <stdio.h>
void test(int(*p)[5],int a,int b)//因为arr是第一行的数组地址,就相当于是一个一维数组,所以用数组指针接收
{
    int i = 0,j = 0;
    for (i = 0;i<a;i++)
    {
        for(j = 0;j<b;j++)
        {
            printf("%d ",*(*(p+i)+j));//因为p是第一行的数组地址,即整个地址,所以*(p)就是 arr,这里感觉矛盾的请看最下面注释
            //所以*(p+i)就等于 &arr+i,即地址跳到下一行,*(*(p+i) +j)相当于解引用每一行中的每一列的某个值
            //不懂的其实这样写更好理解 (*(p + i))[j]   因为*(p+i)是某一行的数组名
        }
        printf("\n");
    }
}

int main()
{
    int arr[5][5] = {
        {1,2,3,4,5},
        {2,3,4,5,6},
        {3,4,5,6,7},
        {4,5,6,7,8},
        {5,6,7,8,9}
    };
    test(arr,5,5);//二维数组的数组名是首元素地址,但是二维数组的首元素是整个第一行,所以二维arr相当于就是一维数组&arr
    return 0;
}

结果:

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9

文章目录前三个例子的总结:

int arr[5]; 这是一个数组,数组名是arr,有五个元素,每个元素类型是int,叫做 整型数组

int *arr[10];这是一个数组([]的结合性比*高),数组名是arr,有10个元素,每个元素的类型是int*,所以叫做 指针数组

int (*arr2)[10]; 这是一个指针,指向一个数组名是arr2的数组,该数组有10个元素,每个元素类型是int 所以叫做 数组指针

int(*arr3[10])[5] 这是一个数组([]的结合性比*高),数组名叫做arr3,有10个元素,每个元素类型是 数组指针,元素类型指向的是一个整形数组,所以叫做 数组指针数组

④有点不好理解,我画图分层写出来

image-20210409202907053

我们能看见arr3由 三部分组成,数组名,数组标志[],元素类型

我在画细节图给大家理解

image-20210409204147192

现在我出一个题,请写出用什么存储

    int arr1[3] = { 1, 2, 3 };
    int arr2[3] = { 4, 5, 6 };
    int arr3[3] = { 7, 8, 9 };
    
    ?????????? = { &arr1, &arr2, &arr3 };

答案: int(*parr[3])[3]

解析: &arr是 数组地址,数组地址用指 数组指针指向,一个指针指向一个数组地址

有三个所以需要三个数组指针

所以用数组存储

就是int(*parr[3])[3] 而这也呼吁了上面的

4.数组与指针传参

一维数组传参

#include <stdio.h>
void test(int arr[]);// 1
void test(int arr[10]);//2
void test(int* arr);//3
void test2(int* arr[20]);//4
void test2(int** arr);//5
int main()
{
    int arr1[10] = {0};   //整形数组
    int* arr2[20] = {0};  //数组指针
    test(arr1);
    test2(arr2);
    return 0;
}

/*
以上传参均正确.
arr1是整形数组,他的每一个元素是int
所以1和2种接参法正确,表示接受的是int数组  
因为arr1是一个地址,所以可以用指针接受,3也是正确的

arr2是指针数组,他的每一个元素都是指针int*
所以用4接受方法,表示接受一个数组指针, 正确
因为arr2是首元素地址,而首元素就是一个指针,所以要用二级指针接受,也是正确
*/

二维数组传参

#include <stdio.h>
void test(int arr[][]);//1
void test(int arr[3][5]);//2
void test(int* arr)//3
void test(int(*p)[5]);//4
int main()
{
    int arr[3][5] = {0};
    test(arr);
 	return 0;   
}

/*
解释:
1种   错误;   二维数组[][]里面的数字不能省略
2种   正确;   传的arr,arr的类型是int, 所以2的int arr[3][5]正确,表示接受二维整型数组
3种   错误;   二维数组的数组名是首元素地址,但是二维数组的首元素是  第一行  相当于一维数组&arr

所以,如果想要用指针接参,我们应该怎么弄??     答案是  数组指针;
数组指针:指向----->数组地址   数组地址:   一维:&arr  二维:arr
*/
针对二维数组传参中那个数组指针案例,我写了一个代码加以理解,二维数组的数组名和一维数组的数组名区别
#include <stdio.h>
void test(int(*p)[3])    /*一维数组名接收*/
{
    for (int j = 0; j < 3; j++)
    {
        printf("%d ", (*p)[j]);
    }
    printf("\n");
}

void test1(int(*p)[5])/*二维数组名接收*/
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 5; j++)
        {
            printf("%d ", (*(p + i))[j]);
        }
        printf("\n");
    }
}
int main()
{
    int arr1[3] = { 1, 2, 3 };
    //一个数组
    
    
    int arr[3][5] = { {1,2,3,4,5} ,{2,3,4,5,6}, {3,4,5,6,7} };
    test(&arr1);//传的数组地址;
    test1(arr);//传的是一维的数组地址(二维数组本质上是多个一维数组连接在一起储存)
    return 0;
}

/*运行结果:
1 2 3
1 2 3 4 5 
2 3 4 5 6
3 4 5 6 7
*/

解释:

一维数组: &arr这个代表数组地址,不是首元素地址,而数组地址一般用数组指针接收,因为数组指针指向数组

p是指针,p等于&arr, 所以*p就等于arr, arr[] 就等于 (*p)[]

二维数组:arr可以理解为就等于&arr,只是没有&. 就相当于 p = &arr,只是写的时候没有&

所以 *p就是二维数组中真正的首元素地址,即第一个元素地址.在上面程序中i是行,所以*(p+i) 就是下一行中的第一个元素地址.

所以*(p+i) + j就是第i行j列的某一个元素地址,所以*(*(p+i) + j)就是i行j列某一个具体元素 也可以写成(*(p+i))[j]

一级指针传参与二级指针传参

看看下面两边的代码

image-20210410234504423

可以很明显的看到,一级指针,我传的是一级指针,我函数就用一级指针接收

二级指针,我传的是二级指针,我就用二级指针接收

总结:看完上面的知识,我们思考一个问题,那就是:

如果一个函数的参数是 一级指针,那么他可以接收什么?

如果一个函数的参数是 二级指针,那么他可以接收什么?

答案1:

如果一个函数的参数是一级指针,那么他可以接收 普通数组,变量地址,和一级指针

如果一个函数的参数是二级指针,那么他可以接收 二级指针,一级指针地址,和 一级指针数组 (int arr[10])*

5.函数指针

函数指针:重点是指针,指向一个函数;

那么函数指针我们该怎么写呢??? 我们回顾一下在最开始写数组指针的时候是怎么写的.

先确定是一个指针(用括号括起来) 然后指向的东西是…(数组符号[]),再给数组写上元素类型

所以函数指针同样. 假设有一个函数是int add(int x, int y),那函数指针就是 int (*p)(int ,int )

例子:

#include <stdio.h>
int add(int x,int y)
{
    return x+y;
}

int main()
{
    printf("%p\n", &add);
    int a,b;
    a = 4;b = 3;
    int (*p)(int ,int);
    printf("%d", (*p)(a,b));
    return 0;
}
/*答案:
00F012BC
7
*/

下面有三个有趣的题:

void *p ();

void (*p) (); 这两个表达式的意思一样吗? 不一样. 上面是函数的声明,下面是函数指针

(*(void(*)())0)();请问这个是啥???

答案: 这是一个函数调用,并且该函数的地址在0X00000000处.

解析: 首先看0左边括号里面的东西------void(*)(),这是什么?? ----函数指针类型 在他的外面又添加了一层(),然后在紧挨着0左边

说明(void(*)())这是一个强制类型转换,即把0转换为某一个函数的地址,
地址在0处. 所以*(void(*)())0这里解释为 取出一个函数在0处的地址.

最后(*(void(*)())) () 这就是再调用该函数. 之所以在解引用外面加一个(),是因为()的结合性比*高

void (*signal(int,void(*)(int)))(int); 请问这个是啥??

答案: 这是一个返回类型为函数指针的函数

解析: 首先我们可以把他们拆开成signal(int,void(*)(int))void (*)(int) 清晰的看到 前者是一个函数

他的参数是一个整型和函数指针 后者是一个类型,函数指针类型. 所以这是一个返回类型为函数指针的函数.

第三个例子,我为了大家更好理解我这样写.
typedef void (*)(int) a;
a  signal(int,a);
这样好理解了吗??  好理解!!!!
但是这是有错误的写法哦~~~~~~~~~~,我只是为了大家好理解才这样写的
应该是这样写:
typedef void (* a)(int);
a  signal(int,a);
即名字必须挨着*!!!!!!

这样是不是解非常好理解了呢??? 那么有人会问,既然a signal(int,a);可以这样写 那么原来为什么不这样写??? 看下面

void (*)(int) signal(int,void(*)(int)) 这个其实就和上面一样,即名字必须挨着*,所以要揉在一起.

6.函数指针数组

顾名思义:重点在最后面!!!**这是一个数组,**只是他的存储对象是 函数指针

为什么会有他呢??我们看看下面的例子:

#include <stdio.h>
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 a,b;
    a = 4;b = 3;
    /*现在我们需要调用上面的所有函数,如果一个一个的写就太麻烦了.因此现在就需要一个数组存储所有函数*/
    return 0;
}

函数指针数组的写法::

我们知道一个数组由三个类型组成,分别是: 存储类型 数组名 数组符号[] 比如int num[]

所以按照上面的定义我么可以知道这样写

int(*)(int,int) num[4] 是不是这样??? 对!!! 了90% 不要忘记我上面所说的*必须和名字结合

所以应该是这样

int (*num[4])(int ,int)

问题: 现在有一个函数 char* my_strcpy(char* dest, const char* src);

第一,请写一个函数指针指向 my_strcpy 第二,请写一个函数指针数组,可以存放四个函数该地址

第一: char* (*p)(char*, const char*)

第二: char* (*num[4])(char*, const char*)

下面,我们来编写一个小程序 (计算器),需要用到上面的东西

该计算器的作用是实现 基本加减乘除

1实现+ 2实现- 3实现* 4实现 0退出 其余数字报错,提醒重按.

按照上面的逻辑,我们可以很快想到用 do while循环 和 switch;

                            /*我们首先搭建结构*/
//结构搭建
#include <stdio.h>
int main()
{
 	int input = 0;
    int x,y;
    do
    {
        remind();//提醒按键菜单
        printf("请根据上面的提醒,按下命令数字1或2或3或4:\n");
        scanf("%d", &input);
        switch (input)
        {
            case 1:
                printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
        		scanf("%d%d", &x,&y);
        		printf("输入完毕,结果是:\n");
                printf("%d\n",add(x,y));
                break;
            case 2:
                printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
        		scanf("%d%d", &x,&y);
        		printf("输入完毕,结果是:\n");
                printf("%d\n",sub(x,y));
                break;
            case 3:
                printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
        		scanf("%d%d", &x,&y);
        		printf("输入完毕,结果是:\n");
                printf("%d\n",mul(x,y));
                break;
            case 4:
                printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
        		scanf("%d%d", &x,&y);
        		printf("输入完毕,结果是:\n");
                printf("%d", div(x,y));
                break;
            case 0:
                printf("成功退出计算器\n\n\n");
                break;
            default:
                printf("对不起,你输入的命令有误,请重新输入!\n");
                break;
        }
    }while(input);
    return 0;
}
上面的结构已经搭建好了,现在我么开始写函数 加减乘除的功能以及remind的功能
#include <stdio.h>
void remind()
{
    printf("**********************************************\n");
    printf("*********按1加   按2减    按3乘    按4除*********\n");
    printf("**************  其他操作报告提示  ***************\n");
}

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;
}
然后我么总体合并,就可以实现了…
#include <stdio.h>
void remind()
{
    printf("**********************************************\n");
    printf("****按1加   按2减    按3乘    按4除   按0退出*****\n");
    printf("**************  其他操作报告提示  ***************\n");
}

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 input = 0;
    int x,y;
    do
    {
        remind();//提醒按键菜单
        printf("请根据上面的提醒,按下命令数字1或2或3或4或0:\n");
        scanf("%d", &input);
        switch (input)
        {
            case 1:
                printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
        		scanf("%d%d", &x,&y);
        		printf("输入完毕,结果是:\n");
                printf("%d\n",add(x,y));
                break;
            case 2:
                printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
        		scanf("%d%d", &x,&y);
        		printf("输入完毕,结果是:\n");
                printf("%d\n",sub(x,y));
                break;
            case 3:
                printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
        		scanf("%d%d", &x,&y);
        		printf("输入完毕,结果是:\n");
                printf("%d\n",mul(x,y));
                break;
            case 4:
                printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
        		scanf("%d%d", &x,&y);
        		printf("输入完毕,结果是:\n");
                printf("%d", div(x,y));
                break;
            case 0:
                printf("您成功退出计算器\n\n\n");
                break;
            default:
                printf("对不起,你输入的命令有误,请重新输入!\n");
                break;
                
        }
    }while(input);
    return 0;
}
现在我们看到,基本已经实现了计算器的功能,但是如果我们还有继续多的函数,比如幂次方 开放 对数,难道也要像上面一样,一一列举出来吗?这样是不是会显得十分繁琐,并且代码冗余???所以这就需要我们的 函数指针数组 了,然后根据索引进行取功能

代码如下:

#include <stdio.h>
int main()
{
 	int input = 0;
    int x,y;
    int(*num[5])(int,int) = {0,add,sub,mul,div};
    do
    {
        remind();//提醒按键菜单
        printf("请根据上面的提醒,按下命令数字1或2或3或4或0:\n");
        scanf("%d", &input);
        if(input>=1 && input <= 4)
        {         
            printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
       		scanf("%d%d", &x,&y);
       		printf("输入完毕,结果是:\n\n");
            printf("%d\n",num[input](x,y));
        }
        else if(input == 0)
            printf("成功退出\n");
        else
            printf("对不起,你输入的命令有误,请重新输入!\n");
    }while(input);
    return 0;
}
是不是发现代码量 极度减少???,这就是函数指针数组的好处.

我们首先把加减乘除函数封装在数组里面,然后利用下标进行访问

7.函数指针数组指针

指向函数指针数组的指针是一个 指针 ,指针指向一个 数组 ,数组的元素都是 函数指针 ;

那么怎么定义呢? 我们同样可以回顾一下最开始我们是怎么定义 数组指针 —>> 函数指针----->>>数组指针数组等等的

现在我们需要定义函数指针数组指针, 所以需要明确 本体(指针) 指向类型(数组) 所指向的数组存的什么(函数指针)

#include <stdio.h>
int add();

int main()
{
 /*第一步,首先写出函数指针*/
 int(* func_point)() = &add; //这是一个函数指针

 /*第二步,写出一个数组,用来存放函数指针 (所以想想怎么写这个数组)*/
 int (* )() num[] = {func_point,func_point,func_point};  //这样写对吗?对!!!!了90%,因为基本符合数组的规范,
 //但是我前面一直强调一个事情,*必须怎么样???*需要挨着名字.所以下面才是真正的写法

 int (*num[])() = {func_point,func_point,func_point};

 /*第三部,写出一个指针,用来存放函数指针数组*/

 //第一步,先写出指针
 (*point)
 //第二步,写出指针指向的数组
 (*point)[]
 //第三部, 给所指向的数组添加数组类型
 int (*)() (*point)[];// 成功了吗??对!!!!了90%,但是还是不要忘记我们所说的,*必须挨着名字,所以:
 int (*(*point)[])();  //大工告成!!!!!!!!!!!!
	return 0;   
}

所以,我们函数指针数组指针一般这样写 int (*(*point)[])();

8.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一

个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该

函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或

条件进行响应。

简而言之,就是一个函数的参数接收的是一个函数地址.被接收的函数,这时候就称为 回调函数

#include <stdio.h>
    int x = 2;
    int y = 3;

int add(int x,int y)
{
    return x+y;
}

int many( int(*func)(int,int), int b )
{
    return func(x,y) + b;
}

int main()
{
	printf("%d", many(add,6));
    return 0;
}

/*
运行结果是:
11
*/

这时候,add就是回调函数

9.模拟qsort函数

现在我们开始使用回调函数以及指针来模拟qsort,并且qsort可以排序一切但是在模拟之前,我们首先需要知道qsort函数是怎么使用的,现在我们来看官方文档.

image-20210412165113384

官方的 qsort 是像上面一样声明的,我现在一一解释什么意思:

void* base 待排序元素的首元素地址,即数组首元素地址,即数组名

size_t num 数组元素数量

size_t width 数组单个元素的内存大小

int(_cdecl *compare)(const void* eleml,const void* elem2) 接收一个比较函数,返回 正数 负数0

其中compar参数是qsort函数排序的核心内容,它指向一个比较两个元素的函数,注意两个形参必须是const void *型,同时在调用compar 函数(compar实质为函数指针,这里称它所指向的函数也为compar)时,传入的实参也必须转换成const void *型。在compar函数内部会将const void *型转换成实际类型,见下文。

int compar(const void *p1, const void *p2);

如果compar返回值小于0(< 0),那么p1所指向元素会被排在p2所指向元素的前面

如果compar返回值等于0(= 0),那么p1所指向元素与p2所指向元素的顺序不确定

如果compar返回值大于0(> 0),那么p1所指向元素会被排在p2所指向元素的后面

因此,如果想让qsort()进行从小到大(升序)排序,那么一个通用的compar函数可以写成这样:

//方法一
 int compare (const void * a, const void * b)
 {
   if ( *(MyType*)a <  *(MyType*)b ) return -1;
   if ( *(MyType*)a == *(MyType*)b ) return 0;
   if ( *(MyType*)a >  *(MyType*)b ) return 1;
 }

注意:你要将MyType换成实际数组元素的类型。

或者,如下所示。

//方法二
//升序排序
 int compare (const void * a, const void * b)
 {
	 return ( *(int*)a - *(int*)b );
 }
//降序排列
 int compare (const void * a, const void * b)
 {
	 return ( *(int*)b - *(int*)a );
 }

如果变量 a 指向一个较小的负整型数,b指向一个较大的正整型数,(*(int*)a - *(int*)b)表达式会计算出一个正数,这是错误的。

因此,我们不能用减法来比较int的大小,这会产生溢出。取而代之地,我们可以使用大于、小于运算符来比较,如方法一所示。

现在有一个要求,有5个人,每个人有详细的 名字 年龄 分数,请按照要求输出分数从高到低的每一个人的名字
#include <stdio.h>
#include <stdlib.h>
struct info
{
    char name[20];
    int age;
    int grade;
};
int compare(const void* a, const void* b)
{
    return ((struct info*)a)->grade - ((struct info*)b)->grade;
}

int main()
{
    struct info information[5] = {
              {"夏敏",18,65},
              {"李华",16,71},
              {"杜美丽",17,85},
              {"刘安",17,69},
              {"李平",18,90}
    };

    qsort(information, sizeof(information) / sizeof(information[0]), sizeof(information[0]), compare);
    for (int i = 0; i < 5; i++)
    {
        printf("%s\n", information[i].name);
    }
    return 0;
}

/*
运行结果:

李平
杜美丽
李华
刘安
夏敏
*/

现在我们可以开始模拟qsort函数了,因为我们知道了他的机制.现在我们开始写my_sqort();

我们知道qsort的主要作用就是排序,所以我们自己设计的qsort的核心程序就是排序,我们为了简单就选择通过冒泡思想来解决

#include <stdio.h>
struct info
{
    char name[20];
    int age;
    int grade;
};

/*因为指针就收的地址是第一个字节,所以需要挨个交换*/
void swap(char* a,char*b,int width)
{
    for(int i = 0;i<width;i++)
    {
        char tmp = *b;
        *b = *a;
        *a = tmp;
        a++;
        b++;
    }
}
/*第一步,首先按照标准qsort的写法,我们直接模拟一个与其一样的函数声明*/
void my_sort(void* base,int number,int size,int(*compare)(const void*,const void*))
{
    /*第二步,写好冒泡排序的框架*/
    int i = 0,j = 0;
    for(i = 0;i<number-1;i++)
    {
        for(j = 0;j<number-1-i;j++)
        {
/*第三步,我们需要通过调用compare知道他的返回值是大于0.还是小于0;如果大于0,我们就需要升序,反之,降序.*/
//那么,当有人在qsort外面写compare时候,他是知道自己需要排序什么类型的,但是我们模拟qsort的时候,我们是不知道的,所以我们需要实现某种方式来保证我们能够知道: 想要使用qsort排序的人的排序数组类型
//现在我们在my_qsort内部只知道 4 个参数 base   number    size    与compare
//那么怎么来利用这4个值确定我们一定可以知道待排序类型呢??   那就是base与size,base是指针,首元素地址,size是一个元素的大小.
//那么 (char*)base就一定是第一个元素的地址
//    (char*)base + size就一定是第二个元素地址
//所以,(char*)base + j*size就是前一个元素地址,
//    (char*)base + (j+1)*size就是后一个元素地址.
      //所以,我们可以开始自己使用compare
      		if(compare((char*)base + j*size,(char*)base + (j+1)*size)>0)
      		{
      			//如果返回值大于0,说明前面的值比后面大,所以我们需要交换前后两个值
                swap((char*)base + j*size, (char*)base + (j+1)*size, size);
      		}
        }
    }
}


int main()
{
    struct info information[5] = {
              {"夏敏",18,65},
              {"李华",16,71},
              {"杜美丽",17,85},
              {"刘安",17,69},
              {"李平",18,90}
    };
    my_qsort(information, sizeof(information) / sizeof(information[0]), sizeof(information[0]), compare);
    for (int i = 0; i < 5; i++)
    {
        printf("%s\n", information[i].name);
    }
    return 0;
}
  • 41
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

捕获一只小肚皮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值