標籤:

C 語言指針怎麼理解?

剛接觸C語言,沒有基礎


對於程序員來說內存可以簡化成這樣一種東西:

你可以把它想像成一條無限長的紙帶。紙帶上邊有一個個的小格子,每個小格子正好是一位元組,裡邊能夠存放一個數字。計算機的工作就是對這些小格子里的數字做處理。雖然你在電腦上能夠看視頻、聽音樂,但這些東西本質上都是存在內存這條紙帶上的數字。

對於紙帶上的每個小格子來說能夠採取的操作只有兩種「讀取」和「寫入」。每次執行操作時都必須要指明對於哪一個格子進行操作,為了方便起見人們就給這些格子編了號。如上圖所示,第一個就是0號,第二個是1號以此類推(前邊的0x代表是16進位的意思)。而這些編號就是我們所說的指針的內容,指針變數裡邊所記載的就是這些編號。但為什麼我們平時看到的指針值都是類似於0xa2cf23c3d這種呢?這是因為,內存非常之大,作為編號的數字也非常大。所以看是來就像是神秘代碼一樣。

舉個例子:

unsigned char a = 1; // unsigned char 類型佔一個位元組
unsigned char* b = a; // a 代表的就是得到變數a所存儲的那個小格子的編號。
// 賦值給變數b後,b格子里存的就是a格子的編號。
printf("%d", *b); //輸出1,*b代表的是看看b格子里存儲的編號所代表的格子里存的數是什麼。

上邊代碼中的內容變成紙帶就是:

ok,如果你覺得這個太簡單,我們現在可以深入到一些更加複雜的概念——指針的指針。例如下邊的代碼:

unsigned char a = 1;
unsigned char* b = a;
unsigned char** c = b;

上文中我們提到指針就是格子的編號,那麼指針的指針是什麼呢?就是指針變數所在格子的編號。如圖:

至於什麼指針的指針的指針,以此類推。有 時候人們會把這種關係視作是一種指向關係。所以當人們說一個指針指向某個變數時,他們指的是這個指針變數的值是某個變數的格子編號。

但是這裡有個問題,前文說到 ,每個格子只有一個位元組。有些類型的變數需要多個位元組來存儲,比如int型就需要4個位元組(不同平台長度不一致,這裡假定是4個)。如果我有一個int型的變數a存在0x01號地址,我必須有方法告訴程序後邊的三個格子也是這個變數值的一部分。

為了解決這個問題人們引入了指針類型的概念,對於int型的指針他表示的是從當前格子開始共4個格子都是該變數的值,而對於unsigned char型則只表示當前的格子。例如:

unsigned int c = 257;
unsigned int* a = c;
unsigned char* b = (unsigned char*)c; //必須強制轉換一下

printf("%d", *a); // 輸出 257
printf("%d", *b); // 輸出 1

如圖:

有的同學可能會奇怪為什麼*b的值是1。要解決這個問題我們先要了解一個概念,大小端。

計算機使用二進位來表示數字。對於所有unsigned的整數類型變數來說,二進位的值本身就代表了他自己的數值,257轉換成二進位是多少?「100000001」。8位是一個位元組,int型就是4個位元組32位。補齊前邊的0後257可以寫作「00000000 00000000 00000001 00000001」。8個位8個位的轉換成10進位,上邊的數字就是「0 0 1 1」。

OK,現在給你編號為1-4的四個格子和四個數字,讓你按順序存進去,請問有幾種方法?

答案是2種,一種是1號放0,2號放0, 3號放1, 4號放1。另外一種是,4號放0,3號放0, 2號放1, 1號放1。我們把第一種方式叫小端,第二種叫大端(這裡寫反了,應該是第一種叫做大端,謝謝 @浩子 提醒)。由於沒有什麼人來規定怎麼在內存里存多個位元組的數字,所以人們就隨便來了。碰巧在我的機器上,內存里是大端存儲的。所以就變成了上圖中所示的樣子。

此時計算機看到*a,於是找到了編號0x00的格子,因為是int型的指針,於是順手把後邊3個也讀了出來,得到「1 1 0 0」。因為我的機器是大端,所以調整成人類的書寫格式(低位在最右邊)就是「0 0 1 1」,然後「00000000 00000000 00000001 00000001」,再然後「257」。

之後計算機又看了 *b,因為是unsigned char型的指針所以只拿0x00格子里的內容。於是就是1了。

ok,如果你還有興趣看下去的話,我們來討論一下指針運算的問題。

假設有一個指針a,那麼a+1代表什麼呢?就存儲a的格子編號,加上a的類型所佔的位元組數所得到的的新地址。如圖:

減法也是一個意思。兩個指針相減就是他們之間差了幾個格子。比如0x04-0x00 ==&>4之類的。課本上一般會說兩個指針相加沒有意義,或者指針不能相加。現在知道為什麼了吧,因為0x00+0x04之類的運算是沒有實際含義的,算倒是可以算。

你知道為什麼C語言中的數組下表從0開始嗎?因為arr[n]的形式事實上等價於*(arr+n)。數組名其實就是數組的首地址,也就是數組的第一個格子的地址。作為數組中的第一個元素當然得加0啦。

關於數組我多說兩句,數組就是一段連續的小格子。數組名代表的就是第一個小格所在編號。因此數組名就是指針,只不過這個指針變數的值是無法改變的。對於二維數組int [3][4] a來說,唯一的區別就是指針+1後移動過的格子數不同。感興趣的同學可以自己查查為什麼。

至於什麼指針數組、數組的指針之類的等等概念,都是基於上述簡單概念的組合,以此類推就可以了。

ok,理解到這一步其實基本上就算是可以了。不過為了稍微嚴謹一點我再多說幾句。

實際中的計算機可沒有這麼簡單。這個紙帶不一定是一條,有可能一條是內存,一條是硬碟上的虛擬內存。紙帶還會被分成多個區域,像什麼堆棧之類的,並不是每個地址都可以訪問,紙帶也不一定是連續的。但是你在寫程序的時候操作系統幫你把內存抽象成了一條連續的紙帶。很多時候我們並不關心具體的值是什麼。此外有的時候這種格子編號,不一定都指的是內存中的小格子。也有可能是某個硬體設備之類的。不過這就是另外一個話題了。

PS:上文中格子編號一律可以替換為內存地址。


內存需要被定位到,也就是 refer to。內存的定位精確到 byte。

內存地址,就是對內存空間的編址,是在該內存空間中的 index。

指針變數的值,是內存地址。

在編程中,除了單片機,嵌入式,現在在 PC 上基本都是「邏輯線性內存空間」。

在早期,比如說 16 bit,內存的位元組數可能超過 16 bit 表示的範圍,這樣,內部整數是無法涵蓋所有內存空間的,也就是說,數據匯流排的寬度(例如16根),小於地址匯流排的寬度(例如20根),所以引入了 段:段內偏移 這樣的邏輯地址形式,本質上,也就是說,一個數字不夠覆蓋,則用二個數字拼湊成完整的地址線(這部分是由硬體設計實現完成,程序員只需了解之,使用之)。 在這種情況下,不帶上段,就是近指針。(如果你用過 16 bit 目標平台的 tc2.0 寫程序)。

指針在 32 位系統下是 32 位,在 64 位系統下是 64 位,這一點,當你把程序面向 64 位系統編譯時,就是需要額外要注意之處。

但是指針 refer 的數據,則大小是可變的,從最小 1 bytes 到很多 bytes,是靈活的。指針尺寸則是相對固定的。

由於 64 bit 可表示範圍相當的大,如同 unicode 統一了全球字元編碼一樣,在 64 bit 下,指針和 64 位無符號整數可以無縫銜接,也完全不需要考慮區分遠近了。

在 32 位系統下,由於進程虛擬空間也是給你 4GB(32 根地址線),所以你會發現在這個空間里,指針和 32 位整數也是無縫銜接,不分遠近的。


針就是一種用來存儲地址(地址指的就是數據在電腦的存儲位置)的數據類型;就如int是存整數的,float,double用來存浮點數一樣;

在c語言中,變數的聲明格式為 數據類型|數據名|數據

指針也一樣

數據類型:根據要取地址的數值的數據的類型,指針有相應的數據類型;

如若要被取地址的是int類型那麼

指針就是 int* float 就是float*(類推)

數據名 遵守變數的命名規則即可;

數據:就如前文講的指針的數據是地址,即數據在電腦中的存儲位置,將電腦比作一棟樓,數據比作樓中的房客的話,地址很好理解,最重要的是通過一個數據的地址可以訪問到這個數據.在c語言中地址用16進位表示.

:

{

&>_&< : 那麼如何取得一個數據的地址呢?

^_^ : 用這個符號,如 int i=9; int* i_p=i;是一個一元運算符。他的作用就是取地址。

&>_&<:那麼到底是怎麼取地址的呢?電腦在讀到時候發生了什麼?

^_^:每一個變數標識符在編譯期間,編譯器會為它們創建一個符號表,其中存放著變數標識符相應的各種屬性,如類型.地址標識等,從這個符號表裡找

&>_&<:那有這麼多變數唉....每一個變數都要創建符號表不是很耗內存?電腦又是怎麼從這麼多變數中找到某個變數的?符號表中這麼多數據每個又是存在那的?

=_=||:...........這個嘛.....你猜啊...在c中當定義一個變數並對其取地址的時候電腦發生了什麼?

}

指針取地址之後我們稱為指針指向某個物體;通過對一個指針*可以獲得指針指向的數據的值。

(int i=9;int* i_p=i;這樣i_p就指向i);之後就可以通過*來獲取到i的值;

(printf("%d
%d",*i_p,i))會輸出兩個一模一樣的值,不,該說是輸出同一個值兩次.仔細分析之.int i;聲明變數int整數,分配地址.賦值9,將值9丟到地址中;int* i_p=i;同樣聲明變數int指針,分配地址.賦值i的地址,將i地址丟到i_p的地址中.*i_p 就會得到i的值;

i_p:i的地址值;

*i_p:i的值;

i_p:指針的指針值;

規則;

賦值:

求值:

求指針地址

給指針加上一個整數;(數組有意義)

求差值(數組有意義)

數組與指針;

int num[2]={1,2};

數組名的值也就是數組首個元素的地址值;

即printf("%p
%p",num,num[0]);輸出是相同的

為什麼呢?

噹噹...這就是指針存在的意義啊.

指針還有一個特性,即指針加上一個數相當於指針的數值+指針所指數據類型的位元組數;

int num[2] = { 1, 2 };

printf("%p
%p
%d
%d",num,num+1,*num,*(num+1));

顯示為:00D8FD10

00D8FD14

1

2

計算機將數組存儲在相鄰的內存中,通過指針的這個特性,就可以通過指針的加減來獲取數組的值;

比較複雜的是多維數組.但是原理還是同上的;

二維數組

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

數組名是數組首個元素的地址值;

num=num[0];

但是num[0]同樣也是一個數組

num[0]=num[0][0];

另外二維數組指針的定義與普通指針不同

為什麼呢?

因為數據類型不同,而數據類不同就不能正確的使用指針的特性.普通的指針 數據類型為int*每次指針加上一個數就相當於加4.但這樣是沒有辦法通過一個指針獲取整個數組所有值的.所以正確的格式是int(*n_p)[3]=num;根據運算符先括弧,所以是一個指向每個元素有三個數的數數組的,知道int,知道三個數,那麼當n_p+1時加的就是12了,*(n_p+1)就能獲取到num的第二個三元素數組的首地址.

對於二維數組來說使用指針獲得數組中值的方法是-—num[n][m]=*((n_p+n)+m);

這樣就可以使用指針表示變數和數組的值;

而且其是直接訪問地址,在使用是直接傳遞一個地址值通過加減指針使用.


相當於windows桌面上常見的快捷方式。

快捷方式可以指向某個遊戲,這是普通指針。

快捷方式不介意它指向的是什麼,也可以指向另一個快捷方式,這就是指向指針的指針。

也可以指向某個文件夾,這是指向數組的指針。

某天你不想下床,告訴室友「打開桌面上叫」音樂「的快捷方式,播放第3首歌」,這就是在指針的基礎上加了一個偏移量。

快捷方式可以指向一個文件夾,進而可以進一步指向某一類里的具體某一個,比如「遊戲」文件夾里存了很多個遊戲的快捷方式,你用一個指向「遊戲」文件夾的快捷方式就很容易調用它們。這就是 指向指針數組的指針。


題主問的C語言指針該怎麼學這個問題,真的是有1000個人能給出1000個答案。我覺得,在這裡我可以給出我自己的一個答案。

對於大部分C語言指針使用的場景而言,我認為可以從一個叫做「兩己三他」的維度來理解指針。所謂的「兩己三他」,展開來說,就是「己址,己值,他址,他值,他型」。詳細論述,建議題主看看我的這篇文章:從5個維度來看C語言指針(指針就是個紙老虎)

大概來說:

己址:就是自己的地址的意思。就是指針變數也有自己的地址;

己值:就是自己的數據值的意思。就是指針變數也有自己的數據值;

他址:就是他人的地址的意思。指針變數的己值,其實就是他人的地址;

他值;就是他人的數據值的意思。指針變數的己值是他人的地址,那麼就可以通過指針變數獲取到他人的數據值;

他型:就是他人的類型。你以為聲明指針變數時的類型,是指指針嗎?你錯啦!

我這裡舉一個例子,大概解釋下「兩己三他」的意思。

#include &

int main(void)
{
int *pInt = NULL;
int para = 1;
pInt = para;

printf("%x
", pInt);
printf("%x
", pInt + 1);
printf("%d
", *pInt);
printf("%d
", *pInt + 1);

return 0;
}

程序也很簡單,裡面有一些關於指針常見的代碼寫法。

pInt = para;這條語句的本質,其實就將變數para的地址給了指針pInt的己值。

printf("%x
", pInt);:這條語句的本質,其實就是在輸出指針變數pInt的己值,也就是自己的數據值;

printf("%x
", pInt + 1);這條語句的本質,其實就是在用指針變數pInt的己值,加上了一個數字1。根據我那篇文章的敘述,這個1,是根據指針變數pInt的他型來決定的。這裡,pInt的他型是int,那麼,這個1,其實是 1 * sizeof(int)個位元組 = 1 * 4個位元組 = 4個位元組。

printf("%d
", *pInt);這條語句的本質,其實輸出在用指針變數pInt的他值。哪個「他」?當然是變數para了。

printf("%d
", *pInt + 1);這條語句呢,本質也是在用指針變數pInt的他值做計算,啥計算,就是加了個1唄。指針變數pInt的他值是1,再加1,就是2咯!

所以,你看指針難嗎?不難,在「兩己三他」5個維度面前,真的是個紙老虎!

希望我的文章能幫助到題主理解指針!


指針:

我給小紅一個紅蘋果。

我給小明說,別人問你時,你看小紅有什麼,你就說什麼。

我問:小紅,你有什麼呀?

小紅:一個紅蘋果。

我問:小明,你看的那個人有什麼呀?

小明看看小紅,說:一個紅蘋果。

我把小紅的蘋果換成黃蘋果。

我問:小紅,你有什麼呀?

小紅:一個黃蘋果。

我問:小明,你看的那個人有什麼呀?

小明看看小紅,說:一個黃蘋果。

指針操作:

我讓小剛坐在小紅的隔壁。我給小紅一個紅蘋果,給小剛一個黃蘋果。

我給小明說,你看小紅那一隊。別人問你時,你看隊首那個人有什麼,你就說什麼。

我問:小明,你看的那個人有什麼呀?

小明看看小紅,說:一個紅蘋果。

我說:小明,現在你看那個小隊下一個人。小明,你看的那個人有什麼呀?

小明看看小剛,說:一個黃蘋果。

Malloc:

我找了個空盤子放了一個紅蘋果。

我給小明說:別人問你時,你看那個盤子里有什麼,你就說什麼。

我問:小明,你看的地方有什麼呀?

小明看看盤子:一個紅蘋果。

我把盤子里的紅蘋果換成黃蘋果。

我問:小明,你有什麼呀?

小明看看盤子:一個黃蘋果。

我又拿了個空盤子,盤子里放了一個青蘋果。

我說:小明,現在你看這個新盤子。

我問:小明,你有什麼呀?

小明看看我新拿的盤子:一個青蘋果。

鏈表:

我讓小紅、小明和小剛三人依次坐好。我分別給他們一個紅蘋果、一個黃蘋果、一個青蘋果。

我給他們說:你們記住你們下一個人是誰。

我給小亮說,別人問你時,你看小紅有什麼,你就說什麼。

我問:小亮,你看的那個人有什麼呀?

小亮:一個紅蘋果。

我說:小亮,現在你看這個人後面那個人有什麼,你就說什麼。

小亮:小紅,你的後面是誰?

小紅看看身後,說:我的後面是小明。

我問:小亮,你看的那個人有什麼呀?

小亮看看小明,說:一個黃蘋果。

我說:小亮,現在你看這個人後面那個人有什麼,你就說什麼。

小亮:小明,你的後面是誰?

小明看看身後,說:我的後面是小剛。

我問:小亮,你看的那個人有什麼呀?

小亮看看小剛,說:一個青蘋果。

我說:小亮,現在你看小剛後面那個人有什麼,你就說什麼。

小亮:小剛,你的後面是誰?

小剛看看身後,說:我身後沒有人。

堆(雖然這裡類型變了):

我分別給小紅、小明和小剛一粒芝麻、一個蘋果和一個西瓜。

我給他們說:等會你們依次到操場上去排隊。從排在隊首的人開始,如果你手上的東西比他的大,就站到他的左手邊;如果你手上的東西比他的小,就站到他的右手邊。如果那個位置有人了,你就和那個人繼續比。

我說:你們去排隊吧。

小紅拿著芝麻來到操場中間。

小明拿著蘋果走向小紅。小明的蘋果比芝麻大,小明站到小紅的左手邊。

小剛拿著西瓜走向小紅。小剛的西瓜比芝麻大,小剛來到小紅的左手邊,看到小明站在這裡。小剛的西瓜比蘋果大,小剛站到小明的左手邊。

Struct:

我做了一個果籃。

果籃裏有一個蘋果和一個字條,字條上面寫了一個數字。

我讓小明拿著這個果籃。

我對小明說:這個數字代表了樓下那個儲物櫃的號碼。每個儲物櫃裏都有一個和這個一樣的果籃,每個果籃裏都有一個蘋果和另一個寫了儲物櫃號碼的字條。

我說:小明,你去拿下一個果籃。

小明照著字條上的號碼,找到了下一個櫃子裏的果籃。

我說:小明,你再去拿下一個果籃。

小明照著第二張字條上的號碼,找到了第三個櫃子裏的果籃。

傳指針代替引用傳值:

小剛負責削蘋果。但大家只能打電話跟他聯繫。

小明看著自己的蘋果給小剛打電話說:幫我削這個蘋果。

小剛不知道小明要削的是他自己的蘋果,於是拿起身邊一個蘋果,削好了。

但是小剛不知道削好的蘋果怎麼給小明,只好放回身邊說:我削好了。

小明看自己的蘋果沒削好,只好說:錯啦錯啦。重新來。

小明把自己的蘋果放到 10 號儲物櫃裏,給小剛打電話說:幫我削第 10 號櫃子裏的蘋果。

小剛接到電話,取出櫃子裏的蘋果,削好,然後放回原處,給小明說:我削好了。

小明來到 10 號櫃,拿到了削好的蘋果。


指針是可以存放地址的變數。變數就是對應了一段內存空間,內存空間可以存放數據,並且數據可以通過賦值修改。地址就是內存的編號,可以用過運算符獲得地址對應內存的數據。


相當於手機里的號碼本,通過這個號碼,你就能到這個人。但是記錯號碼很後果嚴重。


內存就是一個大的一維數組,指針就是數組中元素的下標。


可以這樣理解:

指針就是下標。指針的指針就是下標的下標。理解的就此打住,不理解往下看。

假設一個進程的地址空間為:

m=(m_0,m_1,...,m_p,...,m_{2^{32}-1})

其中m_{p}in {0,1,?,255}.

顯然它是一個2^{32}
行向量

那麼p就是所謂的指針了。

乃至p可以是m的函數值。

註:p一般不能為0。

現在你應該知道,指向指針的指針是什麼意思了。

意思就是:表達式 m_{m_{p}}中的p


IP 位置啊

網址就是指針

IP 就是指針所指的那個位置

多重指針就是跳轉

有 offset 的多重指針

就是??

好幾重的短網址吧


* 得到該地址上的值

變數 地址

得到變數的地址

那麼我們來看一道題

有以下程序:

#include &

main()

{ int n,*p=NULL;

 *p=n;

 printf("Input n:"); scanf("%d",p); printf("output n:"); printf("%d
",p);

}

該程序試圖通過指針p為變數n讀入數據並輸出,但程序有多處錯誤,以下語句正確的是(  )

A) int n,*p=NULL;

B) *p=n;

C) scanf("%d",p)

D) printf("%d
",p);

那麼

A正確

B錯誤將地址賦給一個空地址

C錯誤p本身就是地址,不需要得到地址的地址

D錯誤列印出來的應該是地址上的值而不是地址


我把計算機內部分為兩個大小相同、緊挨著的立方體、注音:兩個;一個裝地址、另一個裝內容、每一個地址對應一個內容;

重點就是對地址和內容的理解;什麼是地址?其實很簡單、和現實一樣;例如你去約炮、炮友告訴你她住在XX小區A棟501號、這就是地址;然後你順著這個地址找到了她、然後搞起了炮....現在我們把它和指針聯繫起來、當指針變數找到了地址後、就可以找到裡面的內容;例子中炮友就是內容、你順著這個地址找到了這個炮友;

聲明 int a=1,*p; p=a; 現在指針變數得到了a的地址、便可以得到此地址對應的內容了、通過*p即可得到a的內容為1;


指針是一個變數,這個變數的特殊之處在於,它是專門用來存儲其他變數在內存中地址的。

申明一個指針變數要給出它所指向的那個變數的類型,像下面這樣:

int* pointsToInt = 0x558

上面代碼的意思是,我申明了一個叫pointsToInt的指針變數,它指向的變數類型是一個整型,這個整型的變數存在內存地址為0x558的地方。內存中的地址一般用十六進位的形式來表示。

那如何知道一個變數存在內存中的哪個位置呢?用這個操作符:

float Pi = 3.14;
Pi;
&>&>&> 0x5434

返回的結果就是變數Pi在內存中存儲的位置。當然這個地址是我隨機編的,你在電腦上看到的結果跟我的這個結果肯定不一樣。

假設我現在有一個指針變數,我如何知道它指向的變數值是多少呢?答案是用*這個操作符:

int age = 20;
int* pointer = age

*age
&>&>&> 20

上面的*age返回的值會是變數age的值。

值得一提的是,當你增加或者一個單位的pointer,pointer的改變不是一個byte大小,而是pointer指向類型的大小,也就是說現在的指針指向了另一個變數的值。

# include &

int main(){
int* pointer = new int [2];

*pointer = 1;
*(pointer+1) =2;

std::cout &<&< *pointer &<&< std::endl; std::cout &<&< *(++pointer) &<&< std::endl; std::cout &<&< pointer &<&< std::endl; std::cout &<&< ++pointer &<&< std::endl; return 0; }

這裡返回的結果是:

&>&>&> 1
&>&>&> 2
&>&>&> 0x7f9ea0c002e4
&>&>&> 0x7f9ea0c002e8

注意到pointer 和++pointer的值差了4byte,因為int的大小是4byte。

使用pointer要注意:

  1. 初始化pointer,可以用new或者NULL,否則pointer是指向內存中所有可能的值。
  2. 使用new獲得內存分配地址之後,要記得用delete將內存內容釋放。


俄羅斯套娃:

最裡面那層:變數的內容

第二層:變數

第三層:指針

第四層:指針的指針

第五層:指針的指針的指針


先買一本計算機體系結構, 知道什麼叫做 ram 和 cpu , 知道 地址匯流排和數據匯流排什麼意思。 理解透徹 cpu 是怎麼去訪問內存的過程。 剛學編程,別去管什麼 實模式和保護模式,就考慮最簡單的情況

然後,就一切開朗了。


"數據在內存里的存儲地址"


我把指針理解為數據內容和結構的一種抽象。指針的值是程序虛擬地址空間的門牌號,也就是內存地址。通過地址可以訪問數據內容。而指針還有一個類型的屬性,到底是一個指向何種數據的指針,決定了指針操作的行為,反映了數據結構。


推薦閱讀:

同樣的數組參數,用sizeof求數組長度為何會產生不同的結果?
國外有什麼優秀的c語言入門教學視頻?
僅用C語言可以構造出Python中Dict那種數據結構嗎?
用c語言怎麼實現把一個文件中所有的字元串進行篩選,重複的字元串只留下一個?

TAG:C編程語言 |