主頁 > 軟體設計 > 指標詳解

指標詳解

2021-04-14 11:13:21 軟體設計



指標詳解

....
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://www.uj5u.com/ruanti/275840.html

標籤:其他

上一篇:一個神奇的大學科目《軟體工程》,知識點總結+測驗題,包你不掛科

下一篇:Zabbix+ESXI : zabbix監控ESXI主機以及主機上的虛擬機

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more