首頁體育 > 正文

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

2021-10-25由 新聞修煉者 發表于 體育

本文將介紹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個特徵,不管如何進行型別轉換, 多少級指標, 是否包含函式指標等, 看到指標 就思考上面幾點點, 尤其是最後兩點。 練習多了之後就會發現指標本身不是很難, 難的是怎麼判斷資料的型別。

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

頂部