C語言指針,從入門到放肆

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

 

說到指針,估計仍是有不少小夥伴都仍是雲裏霧裏的,有點「知其然,而不知其因此然」。可是,不得不說,學了指針,C語言才能算是入門了。指針是C語言的「精華」,能夠說,對對指針的掌握程度,「直接決定」了你C語言的編程能力。程序員


在講指針以前,咱們先來了解下變量在「內存」中是如何存放的。編程

在程序中定義一個變量,那麼在程序編譯的過程當中,系統會根據你定義變量的類型來分配「相應尺寸」的內存空間。那麼若是要使用這個變量,只須要用變量名去訪問便可。數組

經過變量名來訪問變量,是一種「相對安全」的方式。由於只有你定義了它,你纔可以訪問相應的變量。這就是對內存的基本認知。可是,若是光知道這一點的話,其實你仍是不知道內存是如何存放變量的,由於底層是如何工做的,你依舊不清楚。安全

那麼若是要繼續深究的話,你就須要把變量在內存中真正的樣子是什麼搞清楚。內存的最小索引單元是1字節,那麼你其實能夠把內存比做一個超級大的「字符型數組」。在上一節咱們講過,數組是有下標的,咱們是經過數組名和下標來訪問數組中的元素。那麼內存也是同樣,只不過咱們給它起了個新名字:地址。每一個地址能夠存放「1字節」的數據,因此若是咱們須要定義一個整型變量,就須要佔據4個內存單元。網絡

那麼,看到這裏你可能就明白了:其實在程序運行的過程當中,徹底不須要變量名的參與。變量名只是方便咱們進行代碼的編寫和閱讀,只有程序員和編譯器知道這個東西的存在。而編譯器還知道具體的變量名對應的「內存地址」,這個是咱們不知道的,所以編譯器就像一個橋樑。當讀取某一個變量的時候,編譯器就會找到變量名所對應的地址,讀取對應的值。ide

初識指針和指針變量

那麼咱們如今就來切入正題,指針是個什麼東西呢?函數

所謂指針,就是內存地址(下文簡稱地址)。C語言中設立了專門的「指針變量」來存儲指針,和「普通變量」不同的是,指針變量存儲的是「地址」。測試

定義指針

指針變量也有類型,實際上取決於地址指向的值的類型。那麼如何定義指針變量呢:url

很簡單:類型名* 指針變量名操作系統

char* pa;//定義一個字符變量的指針,名稱爲pa
int* pb;//定義一個整型變量的指針,名稱爲pb
float* pc;//定義一個浮點型變量的指針,名稱爲pc

注意,指針變量必定要和指向的變量的類型同樣,否則類型不一樣可能在內存中所佔的位置不一樣,若是定義錯了就可能致使出錯。

取地址運算符和取值運算符

獲取某個變量的地址,使用取地址運算符&,如:

char* pa = &a;
int* pb = &f;

若是反過來,你要訪問指針變量指向的數據,那麼你就要使用取值運算符*,如:

printf("%c, %d\n", *pa, *pb);

這裏你可能發現,定義指針的時候也使用了*,這裏屬於符號的「重用」,也就是說這種符號在不一樣的地方就有不一樣的用意:在定義的時候表示「定義一個指針變量」,在其餘的時候則用來「獲取指針變量指向的變量的值」。

直接經過變量名來訪問變量的值稱之爲直接訪問,經過指針這樣的形式訪問稱之爲間接訪問,所以取值運算符有時候也成爲「間接運算符」。

好比:

//Example 01
//代碼來源於網絡,非我的原創
#include 
int main(void)
{
    char a = 'f';
    int f = 123;
    char* pa = &a;
    int* pf = &f;
    
    printf("a = %c\n", *pa);
    printf("f = %d\n", *pf);
    
    *pa = 'c';
    *pf += 1;
    
    printf("now, a = %c\n", *pa);
    printf("now, f = %d\n", *pf);
    
    printf("sizeof pa = %d\n", sizeof(pa));
    printf("sizeof pf = %d\n", sizeof(pf));
    
    printf("the addr of a is: %p\n", pa);
    printf("the addr of f is: %p\n", pf);
    
    return 0;
}

程序實現以下:

//Consequence 01
a = f
f = 123
now, a = c
now, f = 124
sizeof pa = 4
sizeof pf = 4
the addr of a is: 00EFF97F
the addr of f is: 00EFF970

避免訪問未初始化的指針

void f()
{
    int* a;
    *a = 10;
}

像這樣的代碼是十分危險的。由於指針a到底指向哪裏,咱們不知道。就和訪問未初始化的普通變量同樣,會返回一個「隨機值」。可是若是是在指針裏面,那麼就有可能覆蓋到「其餘的內存區域」,甚至多是系統正在使用的「關鍵區域」,十分危險。不過這種狀況,系統通常會駁回程序的運行,此時程序會被「停止」並「報錯」。要是萬一中獎的話,覆蓋到一個合法的地址,那麼接下來的賦值就會致使一些有用的數據被「莫名其妙地修改」,這樣的bug是十分很差排查的,所以使用指針的時候必定要注意初始化。

指針和數組

有些讀者可能會有些奇怪,指針和數組又有什麼關係?這倆貨明明八竿子打不着井水不犯河水。彆着急,接着往下看,你的觀點有可能會改變。

數組的地址

咱們剛剛說了,指針實際上就是變量在「內存中的地址」,那麼若是有細心的小夥伴就可能會想到,像數組這樣的一大摞變量的集合,它的地址是啥呢?

咱們知道,從標準輸入流中讀取一個值到變量中,用的是scanf函數,通常貌似在後面都要加上&,這個其實就是咱們剛剛說的「取地址運算符」。若是你存儲的位置是指針變量的話,那就不須要。

//Example 02
int main(void)
{
    int a;
    int* p = &a;
    
    printf("請輸入一個整數:");
    scanf("%d", &a);//此處須要&
    printf("a = %d\n", a);
    
    printf("請再輸入一個整數:");
    scanf("%d", p);//此處不須要&
    printf("a = %d\n", a);
    
    return 0;
}

程序運行以下:

//Consequence 02
請輸入一個整數:1
a = 1
請再輸入一個整數:2
a = 2

在普通變量讀取的時候,程序須要知道這個變量在內存中的地址,所以須要&來取地址完成這個任務。而對於指針變量來講,自己就是「另一個」普通變量的「地址信息」,所以直接給出指針的值就能夠了。

試想一下,咱們在使用scanf函數的時候,是否是也有不須要使用&的時候?就是在讀取「字符串」的時候:

//Example 03
#include 
int main(void)
{
    char url[100];
    url[99] = '\0';
    printf("請輸入TechZone的域名:");
    scanf("%s", url);//此處也不用&
    printf("你輸入的域名是:%s\n", url);
    return 0;
}

程序執行以下:

//Consequence 03
請輸入TechZone的域名:www.techzone.ltd
你輸入的域名是:www.techzone.ltd

所以很好推理:數組名其實就是一個「地址信息」,實際上就是數組「第一個元素的地址」。我們試試把第一個元素的地址和數組的地址作個對比就知道了:

//Example 03 V2
#include 
int main(void)
{
    char url[100];
    printf("請輸入TechZone的域名:");
    url[99] = '\0';
    scanf("%s", url);
    printf("你輸入的域名是:%s\n", url);

    printf("url的地址爲:%p\n", url);
    printf("url[0]的地址爲:%p\n", &url[0]);

    if (url == &url[0])
    {
        printf("二者一致!");
    }
    else
    {
        printf("二者不一致!");
    }
    return 0;
}

程序運行結果爲:

//Comsequense 03 V2
請輸入TechZone的域名:www.techzone.ltd
你輸入的域名是:www.techzone.ltd
url的地址爲:0063F804
url[0]的地址爲:0063F804
二者一致!

這麼看,應該是實錘了。那麼數組後面的元素也就是依次日後放置,有興趣的也能夠本身寫代碼嘗試把它們輸出看看。

指向數組的指針

剛剛咱們驗證了數組的地址就是數組第一個元素的地址。那麼指向數組的指針天然也就有兩種定義的方法:

...
char* p;
//方法1
p = a;
//方法2
p = &a[0];

指針的運算

當指針指向數組元素的時候,能夠對指針變量進行「加減」運算,+n表示指向p指針所指向的元素的「下n個元素」,-n表示指向p指針所指向的元素的「上n個元素」。並非將地址加1。

如:

//Example 04
#include 
int main(void)
{
    int a[] = { 1,2,3,4,5 };
    int* p = a;
    printf("*p = %d, *(p+1) = %d, *(p+2) = %d\n", *p, *(p + 1), *(p + 2));
    printf("*p -> %p, *(p+1) -> %p, *(p+2) -> %p\n", p, p + 1, p + 2);
    return 0;
}

執行結果以下:

//Consequence 04
*p = 1, *(p+1) = 2, *(p+2) = 3
*p -> 00AFF838, *(p+1) -> 00AFF83C, *(p+2) -> 00AFF840

有的小夥伴可能會想,編譯器是怎麼知道訪問下一個元素而不是地址直接加1呢?

其實就在咱們定義指針變量的時候,就已經告訴編譯器了。若是咱們定義的是整型數組的指針,那麼指針加1,實際上就是加上一個sizeof(int)的距離。相對於標準的下標訪問,使用指針來間接訪問數組元素的方法叫作指針法。

其實使用指針法來訪問數組的元素,不必定須要定義一個指向數組的單獨的指針變量,由於數組名自身就是指向數組「第一個元素」的指針,所以指針法能夠直接做用於數組名:

...
printf("p -> %p, p+1 -> %p, p+2 -> %p\n", a, a+1, a+2);
printf("a = %d, a+1 = %d, a+2 = %d", *a, *(a+1), *(a+2));
...

執行結果以下:

p -> 00AFF838, p+1 -> 00AFF83C, p+2 -> 00AFF840
b = 1, b+1 = 2, b+2 = 3

如今你是否是感受,數組和指針有點像了呢?不過筆者先提醒,數組和指針雖然很是像,可是絕對「不是」一種東西。

甚至你還能夠直接用指針來定義字符串,而後用下標法來讀取每個元素:

//Example 05
//代碼來源於網絡
#include 
#include 
int main(void)
{
    char* str = "I love TechZone!";
    int i, length;
    
    length = strlen(str);
    
    for (i = 0; i < length, i++)
    {
        printf("%c", str[i]);
    }
    printf("\n");
    
    return 0;
}

程序運行以下:

//Consequence 05
I love TechZone!

在剛剛的代碼裏面,咱們定義了一個「字符指針」變量,而且初始化成指向一個字符串。後來的操做,不只在它身上可使用「字符串處理函數」,還能夠用「下標法」訪問字符串中的每個字符。

固然,循環部分這樣寫也是沒毛病的:

...
for (i = 0, i < length, i++)
{
    printf("%c", *(str + i));
}

這就至關於利用了指針法來讀取。

指針和數組的區別

剛剛說了許多指針和數組相互替換的例子,可能有的小夥伴又開始說:「這倆貨不就是一個東西嗎?」

隨着你對指針和數組愈來愈瞭解,你會發現,C語言的創始人不會這麼無聊去建立兩種同樣的東西,還叫上不一樣的名字。指針和數組終究是「不同」的。

好比筆者以前看過的一個例子:

//Example 06
//代碼來源於網絡
#include 
int main(void)
{
    char str[] = "I love TechZone!";
    int count = 0;
    
    while (*str++ != '\0')
    {
        count++;
    }
    printf("總共有%d個字符。\n", count);
    
    return 0;
}

當編譯器報錯的時候,你可能會開始懷疑你學了假的C語言語法:

//Error in Example 06
錯誤(活動) E0137 表達式必須是可修改的左值
錯誤 C2105 「++」須要左值

咱們知道,*str++ != ‘\0’是一個複合表達式,那麼就要遵循「運算符優先級」來看。具體能夠回顧《C語言運算符優先級及ASCII對照表》。

str++比*str的優先級「更高」,可是自增運算符要在「下一條語句」的時候才能生效。因此這個語句的理解就是,先取出str所指向的值,判斷是否爲\0,如果,則跳出循環,而後str指向下一個字符的位置。

看上去貌似沒啥毛病,可是,看看編譯器告訴咱們的東西:表達式必須是可修改的左值

++的操做對象是str,那麼str究竟是不是「左值」呢?

若是是左值的話,那麼就必須知足左值的條件。

  1. 擁有用於識別和定位一個存儲位置的標識符
  2. 存儲值可修改

第一點,數組名str是能夠知足的,由於數組名實際上就是定位數組第一個元素的位置。可是第二點就不知足了,數組名其實是一個地址,地址是「不能夠」修改的,它是一個常量。若是非要利用上面的思路來實現的話,能夠將代碼改爲這樣:

//Example 06 V2
//代碼來源於網絡
#include 
int main(void)
{
    char str[] = "I love TechZone!";
    char* target = str;
    int count = 0;
    
    while (*target++ != '\0')
    {
        count++;
    }
    printf("總共有%d個字符。\n", count);
    
    return 0;
}

這樣就能夠正常執行了:

//Consequence 06 V2
總共有16個字符。

這樣咱們就能夠得出:數組名只是一個「地址」,而指針是一個「左值」。

指針數組?數組指針?

看下面的例子,你能分辨出哪一個是指針數組,哪一個是數組指針嗎?

int* p1[5];
int(*p2)[5];

單個的咱們均可以判斷,可是組合起來就有些難度了。

答案:

int* p1[5];//指針數組
int(*p2)[5];//數組指針

咱們挨個來分析。

指針數組

數組下標[]的優先級是最高的,所以p1是一個有5個元素的「數組」。那麼這個數組的類型是什麼呢?答案就是int*,是「指向整型變量的指針」。所以這是一個「指針數組」。

那麼這樣的數組應該怎麼樣去初始化呢?

你能夠定義5個變量,而後挨個取地址來初始化。

不過這樣太繁瑣了,可是,並非說指針數組就沒什麼用。

好比:

//Example 07
#include 
int main(void)
{
    char* p1[5] = {
        "人生苦短,我用Python。",
        "PHP是世界上最好的語言!",
        "One more thing...",
        "一個好的程序員應該是那種過單行線都要往兩邊看的人。",
        "C語言很容易讓你犯錯誤;C++看起來好一些,但當你用它時,你會發現會死的更慘。"
    };
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("%s\n", p1[i]);
    }
    return 0;
}

結果以下:

//Consequence 07
人生苦短,我用Python。
PHP是世界上最好的語言!
One more thing...
一個好的程序員應該是那種過單行線都要往兩邊看的人。
C語言很容易讓你犯錯誤;C++看起來好一些,但當你用它時,你會發現會死的更慘。

這樣是否是比二維數組來的更加直接更加通俗呢?

數組指針

()和[]在優先級裏面屬於「同級」,那麼就按照「前後順序」進行。

int(*p2)將p2定義爲「指針」, 後面跟隨着一個5個元素的「數組」,p2就指向這個數組。所以,數組指針是一個「指針」,它指向的是一個數組。

可是,若是想對數組指針初始化的時候,千萬要當心,好比:

//Example 08
#include 
int main(void)
{
    int(*p2)[5] = {1, 2, 3, 4, 5};
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("%d\n", *(p2 + i));
    }
    return 0;
}

Visual Studio 2019報出如下的錯誤:

//Error and Warning in Example 08
錯誤(活動) E0146 初始值設定項值太多
錯誤 C2440 「初始化」: 沒法從「initializer list」轉換爲「int (*)[5]」
警告 C4477 「printf」: 格式字符串「%d」須要類型「int」的參數,但可變參數 1 擁有了類型「int *」

這實際上是一個很是典型的錯誤使用指針的案例,編譯器提示說這裏有一個「整數」賦值給「指針變量」的問題,由於p2歸根結底仍是指針,因此應該給它傳遞一個「地址」才行,更改一下:

//Example 08 V2
#include 
int main(void)
{
    int temp[5] = {1, 2, 3, 4, 5};
    int(*p2)[5] = temp;
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("%d\n", *(p2 + i));
    }
    return 0;
}//Error and Warning in Example 08 V2
錯誤(活動) E0144 "int *" 類型的值不能用於初始化 "int (*)[5]" 類型的實體
錯誤 C2440 「初始化」: 沒法從「int [5]」轉換爲「int (*)[5]」
警告 C4477 「printf」: 格式字符串「%d」須要類型「int」的參數,但可變參數 1 擁有了類型「int *」

但是怎麼仍是有問題呢?

咱們回顧一下,指針是如何指向數組的。

int temp[5] = {1, 2, 3, 4, 5};
int* p = temp;

咱們本來覺得,指針p是指向數組的指針,可是實際上「並非」。仔細想一想就會發現,這個指針其實是指向的數組的「第一個元素」,而不是指向數組。由於數組裏面的元素在內存中都是挨着個兒存放的,所以只須要知道第一個元素的地址,就能夠訪問到後面的全部元素。

可是,這麼來看的話,指針p指向的就是一個「整型變量」的指針,並非指向「數組」的指針。而剛剛咱們用的數組指針,纔是指向數組的指針。所以,應該將「數組的地址」傳遞給數組指針,而不是將第一個元素的地址傳入,儘管它們值相同,可是「含義」確實不同:

//Example 08 V3
//Example 08 V2
#include 
int main(void)
{
    int temp[5] = {1, 2, 3, 4, 5};
    int(*p2)[5] = &temp;//此處取地址
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("%d\n", *(*p2 + i));
    }
    return 0;
}

程序運行以下:

//Consequence 08
1
2
3
4
5

指針和二維數組

在上一節《C語言之數組》咱們講過「二維數組」的概念,而且咱們也知道,C語言的二維數組其實在內存中也是「線性存放」的。

假設咱們定義了:int array[4][5]

array

array做爲數組的名稱,顯然應該表示的是數組的「首地址」。因爲二維數組實際上就是一維數組的「線性拓展」,所以array應該就是指的指向包含5個元素的數組的指針。

若是你用sizeof()去測試array和array+1的話,就能夠測試出來這樣的結論。

*(array+1)

首先從剛剛的問題咱們能夠得出,array+1一樣也是指的指向包含5個元素的數組的指針,所以*(array+1)就是至關於array[1],而這恰好至關於array[1][0]的數組名。所以*(array+1)就是指第二行子數組的第一個元素的地址。

*(*(array+1)+2)

有了剛剛的結論,咱們就不難推理出,這個實際上就是array[1][2]。是否是感受很是簡單呢?

總結一下,就是下面的這些結論,記住就好,理解那固然更好:

*(array + i) == array[i]
*(*(array + i) + j) == array[i][j]
*(*(*(array + i) + j) + k) == array[i][j][k]
...

數組指針和二維數組

咱們在上一節裏面講過,在初始化二維數組的時候是能夠偷懶的:

int array[][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

剛剛咱們又說過,定義一個數組指針是這樣的:

int(*p)[3];

那麼組合起來是什麼意思呢?

int(*p)[3] = array;

經過剛剛的說明,咱們能夠知道,array是指向一個3個元素的數組的「指針」,因此這裏徹底能夠將array的值賦值給p。

其實C語言的指針很是靈活,一樣的代碼用不一樣的角度去解讀,就能夠有不一樣的應用。

那麼如何使用指針來訪問二維數組呢?沒錯,就是使用「數組指針」:

//Example 09
#include 
int main(void)
{
    int array[3][4] = {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11}
    };
    int(*p)[4];
    int i, j;
    p = array;
    for (i = 0, i < 3, i++)
    {
        for (j = 0, j < 4, j++)
        {
            printf("%2d ", *(*(p+i) + j)); 
        }
        printf("\n");
    }
    return 0;
}

運行結果:

//Consequence 09
0 1 2 3
4 5 6 7
8 9 10 11

void指針

void其實是無類型的意思。若是你嘗試用它來定義一個變量,編譯器確定會「報錯」,由於不一樣類型所佔用的內存有可能「不同」。可是若是定義的是一個指針,那就沒問題。void類型中指針能夠指向「任何一個類型」的數據,也就是說,任何類型的指針均可以賦值給void指針。

將任何類型的指針轉換爲void是沒有問題的。可是若是你要反過來,那就須要「強制類型轉換」。此外,不要對void指針「直接解引用」,由於編譯器其實並不知道void指針會存放什麼樣的類型。

//Example 10
#include 
int main(void)
{
    int num = 1024;
    int* pi = #
    char* ps = "TechZone";
    void* pv;
    
    pv = pi;
    printf("pi:%p,pv:%p\n", pi, pv);
    printf("*pv:%d\n", *pv);
    
    pv = ps;
    printf("ps:%p,pv:%p\n", ps, pv);
    printf("*pv:%s\n", *pv);
}

這樣會報錯:

//Error in Example 10
錯誤 C2100 非法的間接尋址
錯誤 C2100 非法的間接尋址

若是必定要這麼作,那麼能夠用「強制類型轉換」:

//Example 10 V2
#include 
int main(void)
{
    int num = 1024;
    int* pi = #
    char* ps = "TechZone";
    void* pv;

    pv = pi;
    printf("pi:%p,pv:%p\n", pi, pv);
    printf("*pv:%d\n", *(int*)pv);

    pv = ps;
    printf("ps:%p,pv:%p\n", ps, pv);
    printf("*pv:%s\n", pv);
}

固然,使用void指針必定要當心,因爲void指針幾乎能夠「通吃」全部類型,因此間接使得不一樣類型的指針轉換變得合法,若是代碼中存在不合理的轉換,編譯器也不會報錯。

所以,void指針能不用則不用,後面講函數的時候,還能夠解鎖更多新的玩法。

NULL指針

在C語言中,若是一個指針不指向任何數據,那麼就稱之爲「空指針」,用「NULL」來表示。NULL實際上是一個宏定義:

#define NULL ((void *)0)

在大部分的操做系統中,地址0一般是一個「不被使用」的地址,因此若是一個指針指向NULL,就意味着不指向任何東西。爲何一個指針要指向NULL呢?

其實這反而是一種比較指的推薦的「編程風格」——當你暫時還不知道該指向哪兒的時候,就讓它指向NULL,之後不會有太多的麻煩,好比:

//Example 11
#include 
int main(void)
{
    int* p1;
    int* p2 = NULL;
    printf("%d\n", *p1);
    printf("%d\n", *p2);
    return 0;
}

第一個指針未被初始化。在有的編譯器裏面,這樣未初始化的變量就會被賦予「隨機值」。這樣指針被稱爲「迷途指針」,「野指針」或者「懸空指針」。若是後面的代碼對這類指針解引用,而這個地址又恰好是合法的話,那麼就會產生莫名其妙的結果,甚至致使程序的崩潰。所以養成良好的習慣,在暫時不清楚的狀況下使用NULL,能夠節省大量的後期調試的時間。

指向指針的指針

開始套娃了。其實只要你理解了指針的概念,也就沒什麼大不了的。

//Example 12
#include 
int main(void)
{
    int num = 1;
    int* p = #
    int** pp = &p;
    
    printf("num: %d\n", num);
    printf("*p: %d\n", *p);
    printf("**p: %d\n", **pp);
    printf("&p: %p, pp: %p\n", &p, pp);
    printf("&num: %p, p: %p, *pp: %p\n", &num, p, *pp);
    return 0;
}

程序結果以下:

//Consequence 12
num: 1
*p: 1
**p: 1
&p: 004FF960, pp: 004FF960
&num: 004FF96C, p: 004FF96C, *pp: 004FF96C

固然你也能夠無限地套娃,一直指下去。不過這樣會讓代碼可讀性變得「不好」,過段時間可能你本身都看不懂你寫的代碼了。

指針數組和指向指針的指針

那麼,指向指針的指針有什麼用呢?

它可不是爲了去創造混亂代碼,在一個經典的實例裏面,就能夠體會到它的用處:

char* Books[] = {
    "《C專家編程》",
    "《C和指針》",
    "《C的陷阱與缺陷》",
    "《C Primer Plus》",
    "《Python基礎教程(第三版)》"
};

而後咱們須要將這些書進行分類。咱們發現,其中有一本是寫Python的,其餘都是C語言的。這時候指向指針的指針就派上用場了。首先,咱們剛剛定義了一個指針數組,也就是說,裏面的全部元素的類型「都是指針」,而數組名卻又能夠用指針的形式來「訪問」,所以就可使用「指向指針的指針」來指向指針數組:

...
char** Python;
char** CLang[4];

Python = &Books[5];
CLang[0] = &Books[0];
CLang[1] = &Books[1];
CLang[2] = &Books[2];
CLang[3] = &Books[3];
...

由於字符串的取地址值實際上就是其「首地址」,也就是一個「指向字符指針的指針」,因此能夠這樣賦值。

這樣,咱們就利用指向指針的指針完成了對書籍的分類,這樣既避免了浪費多餘的內存,並且當其中的書名要修改,只須要改一次便可,代碼的靈活性和安全性都獲得了提高。

常量和指針

常量,在咱們目前的認知裏面,應該是這樣的:

520, 'a'

或者是這樣的:

#define MAX 1000
#define B 'b'

常量和變量最大的區別,就是前者「不可以被修改」,後者能夠。那麼在C語言中,能夠將變量變成像具備常量同樣的特性,利用const便可。

const int max = 1000;
const char a = 'a';

在const關鍵字的做用下,變量就會「失去」原本具備的可修改的特性,變成「只讀」的屬性。

指向常量的指針

強大的指針固然也是能夠指向被const修飾過的變量,但這就意味着「不能經過」指針來修改它所引用的值。總結一下,就是如下4點:

  1. 指針能夠修改成指向不一樣的變量
  2. 指針能夠修改成指向不一樣的常量
  3. 能夠經過解引用來讀取指針指向的數據
  4. 不能夠經過解引用來修改指針指向的數據

常量指針

指向很是量的常量指針

指針自己做爲一種「變量」,也是能夠修改的。所以,指針也是能夠被const修飾的,只不過位置稍稍「發生了點變化」:

...
int* const p = #
...

這樣的指針有以下的特性:

  1. 指針自身不可以被修改
  2. 指針指向的值能夠被修改

指向常量的常量指針

在定義普通變量的時候也用const修飾,就獲得了這樣的指針。不過因爲限制太多,通常不多用到:

...
int num = 100;
const int cnum = 200;
const int* const p = &cnum;
...

 

免責聲明:本文系網絡轉載,版權歸原做者全部。若有問題,請聯繫咱們,謝謝!

 

推薦閱讀
  • https://blog.51cto.com/u_14938734/2980417
相關文章
相關標籤/搜索