最值得收藏的 C語言 " 指針 " 解析文章!通俗易懂易上手,超讚!

本文將介紹C語言的指針相關知識.

指針是什麼?

指針和其他的int, float等類似, 是一種類型. 有類型就有相應類型的變量和常量. 本文主要討論變量的情況.

指針變量就是一種變量, 和其他種類的變量類似, 但指針和其他變量又有區別.

首先C語言作為一種類型語言, 每個變量都會有幾個屬性.

● 變量名稱.

● 變量類型.

● 變量的值.

例如int a = 3, 變量名稱就是a, 變量類型是int, 變量的值是3, 如果不提供初始值, 那麼變量的值可能是一個隨機值.

也就是說, 任何時候看到一個變量, 就會有這3個屬性.

對於指針變量, 可以認為有4個屬性.

● 指針變量的名稱.

● 指針變量的類型, 即指針類型.

● 指針變量的值, 即一個地址.

● 指針變量的值所指向的內存裡的數據類型. 本文稱做"指向類型".

可以看到指針變量的關鍵在於指針所指向的內存裡面數據的類型.

例如int a = 3; int *b = &a;, 指針變量名稱是b, 指針變量類型是指針, 變量b的值是變量a的內存地址. 變量b所指向的內存的數據類型是int. 指針變量多了一個"變量b所指向的內存的數據類型是int”, 本文將指針變量所指向的內存的數據類型稱做指向類型.

任何時候看到一個指針就需要關注4點內容: 名稱, 指針類型, 指針值, 指向類型. 搞清楚這幾個內容, 就可以弄明白指針怎麼回事, 當然還要記憶 一些例外的情形.


類型

對於C語言來說, 搞清楚變量的類型相當重要, 涉及到指針的時候就更加重要. 看到一個指針變量後需要理解其指向類型.

例如char * const * (*next)(), next是一個指針, 那麼其指向類型是什麼? 這個聲明/定義比較複雜, 日常編程可能就會碰到比較 複雜的情況, 所以要搞清楚指針首先要懂得怎麼看一個聲明/定義的變量的類型.

如果看到一個變量的聲明或者定義, 那麼就需要弄明白變量的類型. 在<

>這本書中有一部分內容專門講解怎麼分析 一個變量的類型, 值得參考.

理解類型的規則:

1、從變量名稱開始讀取, 然後依照優先級按順序處理.

2、優先級從高到低 a. 括號內優先級高. b. 後綴操作符, ()表示一個函數, []表示一個數組. c. 前綴操作符,*表示"指向...的指針"

3、如果const, volatile後面為類型(int, long等), 那麼作用於類型, 其他情況下作用於const, volatile左邊的指針*.

char * const * (*next)()

按照上面的規則來理解next的類型

1、括號內的優先級最高, 即首先看(*next)

2、next左邊為*, 因此next是一個指針類型

3、然後後綴()的優先級更高, 因此next是一個指針, 指向一個函數.

4、接著是const右邊的*, 表示next是一個指針, 指向一個函數, 該函數返回值類型為一個指針.

5、char * const看作一個整體為指向字符的常量指針.

整個來說: next是一個指針, 指向一個函數, 函數的返回值也是一個指針, 指向一個類型為char的常量指針.


類型有什麼用?

C語言為類型語言, 即每個變量都有類型. 類型在變量的賦值, 函數傳參, 編譯檢查等等方面都會用到.

類型可以確定數據的大小和操作.

例如int a = 3, 那麼在內存中會存儲一個數據3, 那麼對於int類型具體來說.

1、

這個數據3會佔用4字節(常見32位機器與64位機器上int類型佔用4字節). 實際上是有4字節的內存, 內容是0×00000003. 因此int類型就規定了佔用的內存大小.

2、對於int類型就可以進行+,-,*,/等操作, 但是不能進行取指針值(*a)的操作. 能夠進行什麼操作, 也是由類型規定的.

那麼對於指針來說, 其指向類型就非常重要, 指向類型就規定了指針的值所指向的內存的數據是什麼類型, 也就是佔用多大內存, 可以進行什麼操作.


sizeof

只要類型確定, 那麼便可以用sizeof計算類型佔用的內存大小, 這個是編譯階段便可以確定的.

對於指針類型來說, 所有指針類型佔用的內存大小基本都是一樣的, 例如在32bit的機器上佔用4字節, 在64bit的機器上佔用8字節.

下面代碼的變量a和變量b都是指針類型, 但是指向類型不同. 因此sizeof(a)和sizeof(b)的值相等, 但是sizeof(*a)和sizeof(*b)不相等.

int*a;

double*b;

sizeof(a) ==sizeof(b);

sizeof(*a) !=sizeof(*b);


指針類型的操作

可以對指針變量進行+操作.

double a[3] = {1,2,3};

double *b = a;

printf("b: %p, content: %f\\n", b, *b);

printf("b+1: %p, content: %f\\n", b+1, *(b+1));

int c[3] = {1,2,3};

int*d = d;

printf("d: %p, content: %d\\n", d, *d);

printf("d+1: %p, content: %d\\n", d+1, *(d+1));

運行結果:

b:0x7fff5f9ec7e0,content:1.000000

b+1:0x7fff5f9ec7e8,content:2.000000

d:0x7fff5f9ec7d0,content:1

d+1:0x7fff5f9ec7d4,content:2

可以看到b+1的值比b要大8. d+1的值比b要大4. b+1實際上是指向a[1]的內存地址. d+1是指向c[1]的內存地址.

有如下公式成立, 指針做加法後的指針變量值和指向類型佔用的內存大小相關.

指針變量 + 數字 = 指針變量值 + 數字 * sizeof(指向類型)

可以看到指向類型除了告訴你指針指向的內存裡面的數據類型, 在指針變量的相關運算上也是有用的.


數組類型

數組與指針有一定的相似, 同時又很不一樣.

數組與指針的關鍵區別在於數組名是一個常量(和const常量不同). const常量表示變量的內容不會變化, 實際上還是一個變量. 這裡所說的數組名為一個常量, 可以理解數組名稱是一個內存地址值, 例如0×7fff5f9ec7d4.

以下面的例子來說, a本身不會佔用內存, 佔用內存的是a[0], a[1], a[2], 實際上a所表示的這塊內存才是數組變量.

int a[10] = {0};

int*b = a;

int(*d)[10]= &a;

int c;

c = a[1];

c = b[1];

那麼a[1]和b[1]的區別就在於數組是一個常量, 而不是變量(變量本身需要佔用內存).

執行c = a[1]是直接從a表示的內存地址偏移4字節的內存中取數據. 僅包含一次內存讀操作.

執行c = b[1]是首先從內存中取出變量b的值, 然後將變量b的值偏移4字節, 然後從這個地址的內存中取數據. 包含2次內存讀操作. 第一次是讀取變量b的值.

數組的其他幾個需要注意的地方:

1、數組名稱相當於地址常量, 那麼這個地址指向一段內存, 因此這個地址本身會有指向類型, 其指向類型就是數組的元素類型. 例如int a[10], 那麼a的指向類型就是int, 因此a+1結果實際上指向a[1].

2、sizeof(a)是計算整個數組的類型.sizeof(*a)是計算其指向類型的大小.

3、

可以對數組名進行&a操作(取地址), 實際上&a的指針值和a的指針值一樣, 而且也是個地址常量, 但是&a的指向類型 是int [10], 即指向一個包含10個int元素的數組, 所以sizeof(*&a), 計算&a的指向類型的佔用內存大小就是40.

4、數組作為函數參數傳遞後, 在函數內使用等價於指針. 因為函數傳參是進行值傳遞, 相當於有一個指針變量記錄數組的地址值.

可以看到有的時候a看作一個數組(例如sizeof(a)是計算數組的內存佔用), 有時候a看作一個地址常量(例如計算sizeof(*a)和a+1的時候). 還有的時候完全是比較特殊的使用(例如&a得到的指向類型為int [10]的地址常量).


函數指針

函數名本身也是一個地址常量, 其指向類型為一個函數. 實際指向的是函數在內存中的指令集合的起始位置.

int foo(int a)

{

return a;

}

int(*p_foo)(int a) = foo;

printf("%d, %d, %d\\n",sizeof(foo),sizeof(*foo),sizeof(&foo));

printf("%d, %d\\n",sizeof(p_foo),sizeof(*p_foo));

輸出值如下:

1, 1, 8

8, 1

1、對函數名本身計算類型佔用內存大小, 其值為1, 對於函數名的指向類型計算內存佔用大小其值也為1.

2、foo, *foo, &foo的類型相同, 但是sizeof(&foo)結果為8.

3、函數指針可以進行多次解引用,*****p_foo == *p_foo = p_foo.

4、函數指針可以進行調用,p_foo(3);

以上幾點可以認為是函數的特殊情形, 直接記憶.

可以將函數的指令看作是一個unsigned char []的數組. 這樣函數名就好像是一個數組名一樣, 都是地址常量, 其指向類型為unsigned char類型. 但是函數指令的數組的長度是未知的, 因此編譯器默認輸出sizeof(foo)為1, sizeof(*foo)相當於是sizeof(unsigned char)為1.


強制類型轉換

很多時候涉及到指針和強制類型轉換就會感覺比較麻煩, 實際上只要抓住類型這個關鍵點也可以很簡單.

強制類型轉換的關鍵是一段內存, 這段內存裡面的數據你把它當作什麼類型來看待.

double a =23.456;

int*b = (int*) &a;

那麼變量a有一段內存(8個字節), 裡面存儲了23.456(按標準浮點格式存儲). 然後指針b指向這段內存, 而且指針b的指向類型是int, 因此指針b認為這段內存裡面存儲的是一個int類型的數據.


最後

每次看到指針的時候, 記住4個特徵,不管如何進行類型轉換, 多少級指針, 是否包含函數指針等, 看到指針 就思考上面幾點點, 尤其是最後兩點. 練習多了之後就會發現指針本身不是很難, 難的是怎麼判斷數據的類型。

對於熱愛編程的人來說,物以類聚,人以群分!筆者有一個編程零基礎入門學習交流俱樂部(群)私信我【編程學習】進入,有一群一起學習一起解答的小夥伴很重要,還有學習視頻文件,歡迎初學者和正在進階中的小夥伴們!


分享到:


相關文章: