当前位置:主页 > 查看内容

指针详解

发布时间:2021-07-02 00:00| 位朋友查看

简介:指针详解 . . . . 1. 字符指针 2. 指针数组 3. 数组指针 4. 数组与指针参数 5. 函数指针 6. 函数指针数组 7. 函数指针数组指针 8. 回调函数 9. 模拟qsort 在这篇文章之前我还写了一篇 指针基础 1.字符指针 我们知道 字符指针 指向字符地址,并且非常熟悉他的……


指针详解

....
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的该函数

最后(*(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

其中compare的写法是这样

//如果想要升序排列整型数组,就这样写
 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 );
 }

现在有一个要求,有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;
}
;原文链接:https://blog.csdn.net/m0_51723227/article/details/115669658
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!
上一篇:javaweb之web入门基础 下一篇:没有了

推荐图文


随机推荐