天天看點

C語言從指針入門到指針進階一、​​​​​​​了解指針二、二級指針三、字元指針四、野指針五、指針數組六、數組指針七、函數指針八、函數指針數組九、指向函數指針數組的指針十、函數的回調

目錄

一、​​​​​​​了解指針

二、二級指針

三、字元指針

四、野指針

1.未初始化

2.指針越界通路

3.動态記憶體釋放

4.規避野指針

五、指針數組

六、數組指針

七、函數指針

八、函數指針數組

九、指向函數指針數組的指針

十、函數的回調

C語言之是以難,就是因為有指針這個東西,那麼首先我們需要了解指針,這裡基本概念我會簡單略過,主要講解指針的各種運用。

一、​​​​​​​了解指針

概念:指針=位址(本篇指針和位址會換着使用,但請讀者了解這兩個是一個東西),而我們平常習慣說的指針,實質上是指針變量,這兩個是不同的概念,千萬别搞混了!

我們用指針來指向位址,例如:

#include<stdio.h>
int main()
{
    int a=10;
    int* p=&a;
    return 0;
}
           

這裡我要提一下,*号告訴p是指針,p前面的int是告訴我們p指向的對象的類型是int類型

我們調試便可以知道p和a的位址是相同的

C語言從指針入門到指針進階一、​​​​​​​了解指針二、二級指針三、字元指針四、野指針五、指針數組六、數組指針七、函數指針八、函數指針數組九、指向函數指針數組的指針十、函數的回調

細心的小夥伴肯定發現p本身也是有位址的,是以有了二級指針。

二、二級指針

#include<stdio.h>
int main()
{
    int a=10;
    int* p=&a;
    int** pp=&p;
    return 0;
}
           

有了指針以後,我們可以用*号去解引用,可以進行修改等一系列操作,例如:

C語言從指針入門到指針進階一、​​​​​​​了解指針二、二級指針三、字元指針四、野指針五、指針數組六、數組指針七、函數指針八、函數指針數組九、指向函數指針數組的指針十、函數的回調

  那麼我們要如何了解這裡的操作呢?請看下面的圖例

C語言從指針入門到指針進階一、​​​​​​​了解指針二、二級指針三、字元指針四、野指針五、指針數組六、數組指針七、函數指針八、函數指針數組九、指向函數指針數組的指針十、函數的回調

那麼我們為什麼要加*号呢,我的了解是a的類型是int,p的類型是int *,我們想要類型比對,就需要在加一個*号,這樣我們才能去使用a儲存的值,而不是位址。

三、字元指針

概念:指向字元的指針
#include<stdio.h>
int main()
{
    //這裡儲存的并不是整個字元常量,而是字元常量的首位址,即w的位址
    char* c="work hard";
    printf("%s",c);
    return 0;
}
           

四、野指針

什麼叫野指針?即:指針指向的空間是未知的。

導緻這個問題有幾個原因,我們來一一分析。

1.未初始化

int main()
{
    int *p;//p指向一個随機的位置
    *p=20;
    printf("%d",*p);
    return 0;
}
           

我們調試的時候便可以看到問題:

C語言從指針入門到指針進階一、​​​​​​​了解指針二、二級指針三、字元指針四、野指針五、指針數組六、數組指針七、函數指針八、函數指針數組九、指向函數指針數組的指針十、函數的回調

2.指針越界通路

int main()
{
    int arr[5]={1,2,3,4,5};
    int* p=arr;
    //數組最大是5,我們卻通路了後面的元素,不僅指針不能這麼用,數組也不能這麼用
    for(int i=0;i<9;i++)
    printf("%d",*(p+i));
    return 0;
}
           

3.動态記憶體釋放

int main()
{
    int* a=(int*)malloc(sizeof(int));
    free(a);
    //free隻是釋放這個空間,單這個空間還可以被使用,為了不被使用,我們需要置空
    a=NULL;
    return 0;
}
           

4.規避野指針

    (1) 指針初始化

    (2)不越界通路

    (3)指針指向的空間要釋放

    (4)避免傳回局部變量的位址(因為局部變量出了作用域會被銷毀)

    (5)指針使用前檢查有效性

五、指針數組

概念:本質上是數組,裡面存放了指針(位址),定義方法為int* p[]
C語言從指針入門到指針進階一、​​​​​​​了解指針二、二級指針三、字元指針四、野指針五、指針數組六、數組指針七、函數指針八、函數指針數組九、指向函數指針數組的指針十、函數的回調

p是一個數組,數組裡存放了3個元素,每個元素的類型是int*

這裡p[3]存放的是數組名,而數組名就是數組首元素的位址,這個用法是幫助我們了解指針數組,實際上指針數組還有一個常用的方法:

#include<stdio.h>
int main()
{
    int i=0;
    char* p[3]={"I","love","China"};
    for(i=0;i<3;i++)
    printf("%s ",*(p+i));
    return 0;
}
           
輸出結果為:I love China

注意!很多同學在這裡認為指針數組儲存的是一整個字元串,其實不是,這裡儲存的是常量字元串的首位址。

六、數組指針

概念:指向數組的指針,存放的是數組位址(重點是數組位址),本質上是一個指針,那麼這裡我們這樣定義int* p[]是對的嗎?答案是:完全錯誤!因為p首先和方括号結合,最後就成了數組了,這裡我們需要加括号來指名他是數組指針,例如:int (*p)[],這樣p就會首先和*号結合,牢牢記住!

p的類型是int(*)[],數組指針的基本用法為:

#include<stdio.h>
int main()
{
    int i=0;
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int (*p)[10]=&arr;
    for(i=0;i<10;i++)
    {
        //*p=arr,因為p相當于&arr,*p就等于*&arr,這樣就抵消了,相當于獲得了數組名
        printf("%d ",(*p)[i]);
    }
    return 0;
}
           
輸出結果為:1 2 3 4 5 6 7 8 9 10

這裡一定要注意我的注釋,非常重要,這裡注意是&arr,指針數組用的是arr,這兩個的差別是arr是數組首元素的位址,而&arr是整個數組的位址,實際上我們不會這麼用,我隻是想用這個方法告訴你數組指針的基本概念,我們一般這樣用:

#include<stdio.h>
void print(int (*arr)[4],int row,int col)
{
    int i=0;
    int j=0;
    for(i=0;i<row;i++)
    {
        for(j=0;j<col;j++)
        //printf("%d ",arr[i][j]);
        printf("%d ",*(*(arr+i)+j));
        printf("\n");
    }
}
int main()
{
    int arr[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}};
    print(arr,3,4);
    return 0;
}
           

輸出結果為:1 2 3 4

                    2 3 4 5

                    3 4 5 6

二維數組的數組名是首元素的位址,而二維數組的首元素就是第一行的位址!是以arr+i就是第i行的位址*(arr+i)相當于拿到了第i行,這又相當于數組名,因為*(p+i)==p[i],在二維數組中,這就是第i行的數組名,數組名又相當于數組首元素的位址,是以我們可以寫成*(*(arr+i)+j)

七、函數指針

函數指針:指向函數的指針變量,存放函數位址

下面我們來看一個圖:

C語言從指針入門到指針進階一、​​​​​​​了解指針二、二級指針三、字元指針四、野指針五、指針數組六、數組指針七、函數指針八、函數指針數組九、指向函數指針數組的指針十、函數的回調

我們發現&Add和Add的位址一樣的, 那麼我們可以用指針儲存起來,void (*pf)(),以下是代碼

#include<stdio.h>
int Add(int x,int y)
{
    return x+y;
}
int main()
{
    //這兩個都可以用,因為他們指向的位址是一樣的
    //數組&和沒有&是有差別的,但是函數沒有,因為函數沒有首元素取位址的說法
    //int (*pf)(int x,int y)=&Add;
    int (*pf)(int x,int y)=Add;
    //這裡可以沒有*号的原因是Add=pf
    int ret=pf(2,3);
    //int ret=(*pf)(2,3);
    printf("%d \n",ret);
    return 0;
}
           

八、函數指針數組

概念:把函數位址存放到數組中成為函數指針數組,它的本質是數組,是以我們定義為void(*pf[])()
C語言從指針入門到指針進階一、​​​​​​​了解指針二、二級指針三、字元指針四、野指針五、指針數組六、數組指針七、函數指針八、函數指針數組九、指向函數指針數組的指針十、函數的回調

在我們看函數指針數組實際運用之前,我們先做一個加減乘除的電腦,代碼如下:

#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;
}
void menu()
{
        printf("**** 0.exit 1.add ****\n");
 	    printf("**** 2.sub  3.mul ****\n");
 	    printf("****    4.div    ****\n");
}
int main()
{
    int input=0;
    int a=0;
    int b=0;
    int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
    do
    {
        menu();
        printf("請輸入->\n");
        scanf("%d",&input);
        switch (input)
        {
        case 0:
            printf("exit\n");
            break;
        case 1:
            printf("請輸入兩個數字\n");
            scanf("%d %d",&a,&b);
            printf("%d\n",Add(a,b));
            break;
        case 2:
            printf("請輸入兩個數字\n");
            scanf("%d %d",&a,&b);
            printf("%d\n",Sub(a,b));
            break;
        case 3:
            printf("請輸入兩個數字\n");
            scanf("%d %d",&a,&b);
            printf("%d\n",Mul(a,b));
            break;
        case 4:
            printf("請輸入兩個數字\n");
            scanf("%d %d",&a,&b);
            printf("%d\n",Div(a,b));
            break;
        default:
            break;
        }
    }while(input);
    return 0;
}
           

這段代碼備援度很高對吧?這時函數指針數組的優勢就展現出來了,我們可以用函數把main函數的代碼改為:

int main()
{
    int input=0;
    int a=0;
    int b=0;
    int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
    do
    {
        menu();
        printf("請輸入->\n");
        scanf("%d",&input);
        if(input==0)
        {
            printf("exit\n");
        }
        else if(input>=1&&input<=4)
        {
            printf("輸入兩個數字\n");
            scanf("%d %d",&a,&b);
            int ret=pfArr[input](a,b);
            printf("%d\n",ret);
        }
        else
        {
            printf("重新輸入\n");
        }
    }
    while(input);
    return 0;
}
           

九、指向函數指針數組的指針

概念:簡單說就是存放函數指針數組的位址,重心在于指針!
int main()
{
    int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
    //重心在于指針,是以我們需要先寫成這樣(*ppfArr)
    int (*(*ppfArr)[5])(int x,int y)={&pfArr};
}
           

十、函數的回調

概念:函數的(指針)位址作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們稱為函數的回調。

可能看這個文字描述會讓我們雲裡霧裡的,是以我們直接用代碼去解釋,就用之前的電腦的代碼:

//既然我們傳過來了函數的位址,那麼我們裡面的參數就可以用函數指針來接受
void calc(int (*pf)(int x,int y))
{
    printf("請輸入兩個數字->");
    int a=0;
    int b=0;
    scanf("%d %d",&a,&b);
    int ret=pf(a,b);
    printf("%d\n",ret);
}
int main()
{
    int input=0;
    do
    {
        menu();
        printf("請輸入->\n");
        scanf("%d",&input);
        switch (input)
        {
        case 0:
            printf("exit");
            break;
        case 1:
            calc(Add);
            break;
        case 2:
            calc(Sub);
            break;
        case 3:
            calc(Mul);
            break;
        case 4:
            calc(Div);
            break;
        default:
            break;
        }
    }
    while(input);
    return 0;
}
           

這裡我們在回過頭來想想是不是很好了解了?我們把函數的位址作為參數,把這個參數給另一個函數,這個指針被用來調用了我們定義的加減乘除函數。

指針的所有用法基本都在這裡介紹了,如果有錯誤希望指正!

繼續閱讀