C語言基礎知識:最核心的—指針!知識總結(第二部分)

c語言編程 發佈 2022-09-24T10:36:12.032627+00:00

指針是C語言最重要也是最難理解的部分,它在我們平時的工作中無處不在。一個指針,它指向的可以是一個結構體類型,這稱為結構體指針。

指針是C語言最重要也是最難理解的部分,它在我們平時的工作中無處不在。

今天我們繼續來看看指針的剩下的知識總結吧!上一批的話可以在主頁看到哦~

5 指針與結構體

一個指針,它指向的可以是一個結構體類型,這稱為結構體指針。而一個結構體,它的成員中也可以有指針成員。

struct{
    char *name;  //姓名
    int num;  //學號
    int age;  //年齡
    char group;  //所在小組
    float score;  //成績
} stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;

上面的代碼中,定義了一個結構體變量stu1。這個變量中有一個指針變量name。還定義了一個結構體指針pstu。

我們想要通過結構體指針訪問結構體成員一般有以下兩種方式:

(*pstu).name;
pstu->name;


6 指針與const:常量指針與指針常量

初學者常常對這兩個概念搞錯。首先,我認為需要理解這裡說的常量是什麼意思。常量就是只可讀不可修改的。那常量指針和指針常量到底哪個是只可讀不可修改的呢?是指針還是指針指向的內容?這裡有一個方法,能讓你迅速明白哪個是不可修改的。就是在聲明時,以星號(*)為界,分成兩部分,星號左邊的和星號右邊的。const在哪邊,那個就是只可讀不可修改的。以下面這個代碼為例,我們來分析一下:以星號(*)為界,星號左邊是char,沒有const關鍵詞,所以它指向的內容不是常量。然後,我們看星號的右邊是const ptr,所以我們可以說ptr是一個常量。所以,這行代碼聲明了一個是常量的指針但是指向的內容不是常量。即這個是一個指針常量。

char* const ptr = "just a string";

類似的,我們也可以分析下面的代碼:

// Neither the data nor the pointer are const
//
char* ptr = "just a string";

// Constant data, non-constant pointer
//
const char* ptr = "just a string";

// Constant pointer, non-Constant data
//
char* const ptr = "just a string";

// Constant pointer, constant data
//
const char* const ptr = "just a string";

6.1 指針常量(Constant Pointers)

指針常量(Constant Pointers): 它的本質是一個常量,只不過這個常量是指針。由於指針是只可讀不可修改的,所以這個指針不能指向別的地址了,但是該地址里的內容還是可以改變的。指針常量的聲明格式如下:

<type of pointer> * const <name of pointer>
例如: int * const ptr;

我們來看下序:

#include<stdio.h>
int main(void)
{
    int var1 = 0, var2 = 0;
    int *const ptr = &var1;
    ptr = &var2;
    printf("%d\n", *ptr);

    return 0;
}

上面這段程序中:

我們首先定義了兩個變量var1,var2;

然後,定義了一個指針常量ptr,並且指向了var1

接著,試圖讓ptr指向var2

最後,列印出指針ptr指向的地址的內容

讓我們來運行一下這個程序:

main.c: In function 'main':
main.c:6:9: error: assignment of read-only variable 'ptr'
     ptr = &var2;
         ^

我們看到這個程序編譯報錯了:試圖對只讀(read-only)變量ptr進行賦值。所以,一旦我們定義了指針常量,那這個指針就不能指向其他變量了。

但是我們還是可以修改指向的地址里的內容的:

#include<stdio.h>
int main(void)
{
    int var1 = 0;
    int *const ptr = &var1;
    *ptr = 10; // OK
    printf("%d\n", *ptr); // 10
    return 0;
}

6.2 常量指針(Pointer to Constants)

常量指針(Pointer to Constants):它的本質是一個指針,只不過它指向的值是常量(只可讀,不可修改)。由於指向的是一個只可讀不修改的值,所以指針不能通過它存儲的地址間接修改這個地址的值,但是這個指針可以指向別的變量。

常量指針的聲明格式如下:

const <type of pointer>* <name of pointer>
例如: const int* ptr;

還是有一段程序:

#include<stdio.h>

int main(void)
{
    int var1 = 0;
    const int* ptr = &var1;
    *ptr = 1;
    printf("%d\n", *ptr);

    return 0;
}

我們還是來分析一下這個程序:

我們定義了一個變量 var1,並且初始化為0

然後我們定義了一個指針常量ptr,並且將它指向了var1

接著,試圖通過指針ptr來改變var1的值

最後,列印出ptr指向的地址的內容。

我們進行編譯:

main.c: In function 'main':
main.c:7:10: error: assignment of read-only location '*ptr'
     *ptr = 1;
          ^

編譯報錯也很明顯: *ptr是一個只讀的。所以不能通過ptr來修改var1的值。

但是,我們可以將ptr指向其他的變量:

#include<stdio.h>

int main(void)
{
    int var1 = 0;
    const int* ptr = &var1;
    printf("%d\n", *ptr); // 0
    int var2 = 20;
    ptr = &var2; // OK
    printf("%d\n", *ptr); // 20
    return 0;
}

6.3 指向常量的常量指針

理解了上面兩種類型的話,理解這個就很容易了。指向常量的常量指針是指這個指針既不能指向其他的地址也不能通過地址修改內容。

它的聲明格式如下:

const <type of pointer>* const <name of pointer>
例如: const int* const ptr;

同樣,下面一段程序,我想你一定知道哪裡編譯錯誤了。

#include<stdio.h>

int main(void)
{
    int var1 = 0,var2 = 0;
    const int* const ptr = &var1;
    *ptr = 1;
    ptr = &var2;
    printf("%d\n", *ptr);

    return 0;
}

編譯結果:

main.c: In function 'main':
main.c:7:10: error: assignment of read-only location '*ptr'
     *ptr = 1;
          ^
main.c:8:9: error: assignment of read-only variable 'ptr'
     ptr = &var2;
         ^


7 指針與函數

7.1 函數指針

指針與函數相結合有兩種情況:指針函數、函數指針。

指針函數,它的本質是一個函數,它的返回值是一個指針。

int * func(int x, int y);

函數名本身就是一個指針(地址),這個地址就是函數的入口地址。

#include <stdio.h>
int sum(int a, int b)
{
        return a + b;
}
 
int main()
{
        printf("%p\n", sum);
        return 0;
}

輸出:

0000000000401550

而函數指針,它的本質是一個指針。只不過它存的地址恰好是一個函數的地址罷了。

函數指針變量定義的格式一般是:

返回值 (*變量名)(參數列表)

比如:

#include <stdio.h>
int sum(int a, int b)
{
    return a + b;
}
 
int main()
{
    printf("%p\n", sum);
    int (*psum)(int, int);  // 函數指針變量,參數名可以省略
    psum = sum;
    printf("%p\n", psum);
    return 0;
}

輸出:

0000000000401550
0000000000401550

可以發現,兩者地址相等。

函數指針類型的定義:

typedef 返回值 (* 類型名)(參數列表);複製代碼

比如:

typedef int(*PSUM)(int, int);
PSUM pSum2 = sum;
PSUM pSum3 = sum;

這樣的好處就是,首先通過typedef定義一個函數指針類型PSUM,定義完後,PSUM就相當於一種新的類型,可以用此類型去定義其他函數指針變量,就不用每次都使用int(*pSum)(int, int);來定義一個函數指針變量。

#include <stdio.h>
int sum(int a, int b)
{
    return a + b;
}
int func2(int a, int b)
{
    return a - b;
}
typedef int (*PFUNC) (int, int);
int main()
{
    int (*psum)(int, int);
    psum = sum;
    printf("psum(4, 5):%d\n", psum(4, 5));
    PFUNC p2 = func2;
    printf("p2(5, 2):%d\n", p2(5, 2));
    p2 = sum;
    printf("p2(5, 2):%d\n", p2(5, 2));
    return 0;
}

輸出:

psum(4, 5):9
p2(5, 2):3
p2(5, 2):7


7.2 回調函數

說到函數指針,那還有一個概念不得不提——回調函數。因為在實際的項目代碼中實在是太常見了。

回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。

那為什麼要使用回調函數呢?或者說使用回調函數有什麼好處呢?回調函數允許用戶把需要調用的方法的指針作為參數傳遞給一個函數,以便該函數在處理相似的事情時,可以靈活的使用不同的方法。

怎麼使用回調函數:

#include <stdio.h>
int Callback_1(int a)   ///< 回調函數1
{
    printf("Hello, this is Callback_1: a = %d \n", a);
    return 0;
}

int Callback_2(int b)  ///< 回調函數2
{
    printf("Hello, this is Callback_2: b = %d \n", b);
    return 0;
}

int Callback_3(int c)   ///< 回調函數3
{
    printf("Hello, this is Callback_3: c = %d \n", c);
    return 0;
}

int Handle(int x, int (*Callback)(int)) // 注意這裡用到的函數指針定義
{
    Callback(x);
}

int main()
{
    Handle(4, Callback_1);
    Handle(5, Callback_2);
    Handle(6, Callback_3);
    return 0;
}

如上述代碼:可以看到,Handle()函數裡面的參數是一個指針,在main()函數裡調用Handle()函數的時候,給它傳入了函數Callback_1()/Callback_2()/Callback_3()的函數名,這時候的函數名就是對應函數的指針,也就是說,回調函數其實就是函數指針的一種用法。


8 二維指針

二維指針,或者二級指針。就是指向指針的指針。比如:

#include<stdio.h>
int main()
{
    int a = 10;
    int *pa = &a;
    int **ppa = &pa;
    printf("%p, %p, %p, %p, %p", a, pa, *pa, ppa, *ppa);
    return 0;
}

輸出如下:

000000000000000A, 000000000061FE14, 000000000000000A, 000000000061FE08, 000000000061FE14

從輸出結果也可以看到,pa存的內容*pa= 000000000000000A,剛好與a的地址相同。而ppa存的內容*ppa= 000000000061FE14也剛好等於pa的地址。它們之間的內存關係可以用如下的圖表示:


8.1 命令行參數

處理命令行參數是指向指針的指針的一個用武之地。

一般main函數具有兩個形參。第一個通常稱為argc,它表示命令行參數的數目。第2個通常稱為argv,它指向一組參數值。由於參數的數目並沒有內在的限制,所以argv指向這組參數值(從本質上來說是一個數組)的第一個元素。這些元素的每個都是指向一個參數文本的指針。如果程序需要訪問命令行參數,main函數在聲明時就要加上這些參數。

int main(int argc, char **argv)

舉例:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("argc: %d\n", argc);

    // 列印參數,直到遇到NULL指針。程序名被跳過
    while (*++argv != NULL) {
        printf("%s\n", *argv);
    }
    return 0;
}

在windows上執行: \test2.exe hello world

輸出:

argc: 3
hello
world

注意,如果命令行中傳遞的一個參數包括空格,就需要用 ""將參數括起來,比如:

.\test2.exe "hello word"

則上面的代碼將輸出:

argc: 2
hello word


9 結束語

本文關於指針的講解就結束了。我相信你一定對指針有更深入的了解。

對啦對啦!另外的話為了幫助大家,輕鬆,高效學習C語言/C++,我給大家分享我收集的資源,從最零基礎開始的教程到C語言項目案例,幫助大家在學習C語言的道路上披荊斬棘!可以來我粉絲群領取哦~

編程學習書籍分享:

編程學習視頻分享:

整理分享(多年學習的源碼、項目實戰視頻、項目筆記,基礎入門教程)最重要的是你可以在群裡面交流提問編程問題哦!

對於C/C++感興趣可以關注小編在後台私信我:【編程交流】一起來學習哦!可以領取一些C/C++的項目學習視頻資料哦!已經設置好了關鍵詞自動回復,自動領取就好了!

關鍵字: