轉自CSDN博客
原文鏈接:http://blog.csdn.net/xinyuwuxian/article/details/9041413#0-qzone-1-46836-d020d2d2a4e8d1a374a433f596ad1440
姓名:呂彬 學號:16130140354
【嵌牛導讀】新手在C語言的學習過程中遇到的最頭疼的知識點應該就是指針了,指針在C語言中有非常大的用處。下面我就帶著問題來寫下我對于指針的一些理解。
【嵌牛鼻子】
【嵌牛提問】指針是什么?如何使用?
【嵌牛正文】一、指針的內存布局先看下面的例子:int *p;大家都知道這里定義了一個指針p。但是p 到底是什么東西呢?還記得第一章里說過,“任何一種數據類型我們都可以把它當一個模子”嗎?p,毫無疑問,是某個模子咔出來的。我們也討論過,任何模子都必須有其特定的大小,這樣才能用來“咔咔咔”。那咔出p 的這個模子到底是什么樣子呢?它占多大的空間呢?現在用sizeof 測試一下(32 位系統):sizeof(p)的值為4。嗯,這說明咔出p 的這個模子大小為4 個byte。顯然,這個模子不是“int”,雖然它大小也為4。既然不是“int”那就一定是“int *”了。好,那現在我們可以這理解這個定義:一個“int *”類型的模子在內存上咔出了4 個字節的空間,然后把這個4 個字節大小的空間命名為p,同時限定這4 個字節的空間里面只能存儲某個內存地址,即使你存入別的任何數據,都將被當作地址處理,而且這個內存地址開始的連續4 個字節上只能存儲某個int類型的數據。這是一段咬文嚼字的說明,我們還是用圖來解析一下:
如上圖所示,我們把p 稱為指針變量,p 里存儲的內存地址處的內存稱為p 所指向的內存。這里還是多理解一下,比較形象,不錯O(∩_∩)O~指針變量p 里存儲的任何數據都將被當作地址來處理。我們可以簡單的這么理解:一個基本的數據類型(包括結構體等自定義類型)加上“*”號就構成了一個指針類型的模子。這個模子的大小是一定的,與“*”號前面的數據類型無關。“*”號前面的數據類型只是說明指針所指向的內存里存儲的數據類型。所以,在32 位系統下,不管什么樣的指針類型,其大小都為4byte。可以測試一下sizeof(void *)。二、“*”與防盜門的鑰匙這里這個“*”號怎么理解呢?舉個例子:當你回到家門口時,你想進屋第一件事就是拿出鑰匙來開鎖。那你想想防盜門的鎖芯是不是很像這個“*”號?你要進屋必須要用鑰匙,那你去讀寫一塊內存是不是也要一把鑰匙呢?這個“*”號是不是就是我們最好的鑰匙?使用指針的時候,沒有它,你是不可能讀寫某塊內存的。三、int *p = NULL 和*p = NULL 有什么區別?很多初學者都無法分清這兩者之間的區別。我們先看下面的代碼:int *p = NULL;這時候我們可以通過編譯器查看p 的值為0x00000000。這句代碼的意思是:定義一個指針變量p,其指向的內存里面保存的是int 類型的數據;在定義變量p 的同時把p 的值設置為0x00000000,而不是把*p 的值設置為0x00000000。這個過程叫做初始化,是在編譯的時候進行的。明白了什么是初始化之后,再看下面的代碼:int *p;*p = NULL;同樣,我們可以在編譯器上調試這兩行代碼。第一行代碼,定義了一個指針變量p,其指向的內存里面保存的是int 類型的數據;但是這時候變量p 本身的值是多少不得而知,也就是說現在變量p 保存的有可能是一個非法的地址。第二行代碼,給*p 賦值為NULL,即給p指向的內存賦值為NULL;但是由于p 指向的內存可能是非法的,所以調試的時候編譯器可能會報告一個內存訪問錯誤。這樣的話,我們可以把上面的代碼改寫改寫,使p 指向一塊合法的內存int i = 10;int *p = &i;*p = NULL;在編譯器上調試一下,我們發現p 指向的內存由原來的10 變為0 了;而p 本身的值, 即內存地址并沒有改變。經過上面的分析,相信你已經明白它們之間的區別了。不過這里還有一個問題需要注意,也就是這個NULL。初學者往往在這里犯錯誤。注意NULL 就是NULL,它被宏定義為0:#define NULL 0很多系統下除了有NULL外,還有NUL(Visual C++ 6.0 上提示說不認識NUL)。NUL 是ASCII碼表的第一個字符,表示的是空字符,其ASCII 碼值為0。其值雖然都為0,但表示的意思完全不一樣。同樣,NULL 和0 表示的意思也完全不一樣。一定不要混淆。另外還有初學者在使用NULL 的時候誤寫成null 或Null 等。這些都是不正確的,C 語言對大小寫十分敏感啊。當然,也確實有系統也定義了null,其意思也與NULL 沒有區別,但是你千萬不用使用null,這會影響你代碼的移植性。四、如何將數值存儲到指定的內存地址假設現在需要往內存0x12ff7c 地址上存入一個整型數0x100。我們怎么才能做到呢?我們知道可以通過一個指針向其指向的內存地址寫入數據,那么這里的內存地址0x12ff7c 其本質不就是一個指針嘛。所以我們可以用下面的方法:int *p = (int *)0x12ff7c;*p = 0x100;需要注意的是將地址0x12ff7c 賦值給指針變量p 的時候必須強制轉換。*p = 0x100;需要注意的是將地址0x12ff7c 賦值給指針變量p 的時候必須強制轉換。至于這里為什么選擇內存地址0x12ff7c,而不選擇別的地址,比如0xff00 等。這僅僅是為了方便VisualC++ 6.0 上測試而已。如果你選擇0xff00,也許在執行*p = 0x100;這條語句的時候,編譯器會報告一個內存訪問的錯誤,因為地址0xff00 處的內存你可能并沒有權力去訪問。既然這樣,我們怎么知道一個內存地址是可以合法的被訪問呢?也就是說你怎么知道地址0x12ff7c處的內存是可以被訪問的呢?其實這很簡單,我們可以先定義一個變量i比如:int i = 0;變量i 所處的內存肯定是可以被訪問的。然后在編譯器的watch 窗口上觀察&i 的值不就知道其內存地址了么?這里我得到的地址是0x12ff7c,僅此而已(不同的編譯器可能每次給變量i 分配的內存地址不一樣,而剛好Visual C++ 6.0 每次都一樣)。你完全可以給任意一個可以被合法訪問的地址賦值。得到這個地址后再把“int i = 0;”這句代碼刪除。一切“罪證”銷毀得一干二凈,簡直是做得天衣無縫。除了這樣就沒有別的辦法了嗎?未必。我們甚至可以直接這么寫代碼:*(int *)0x12ff7c = 0x100;這行代碼其實和上面的兩行代碼沒有本質的區別。先將地址0x12ff7c 強制轉換,告訴編譯器這個地址上將存儲一個int 類型的數據;然后通過鑰匙“*”向這塊內存寫入一個數據。上面討論了這么多,其實其表達形式并不重要,重要的是這種思維方式。也就是說我們完全有辦法給指定的某個內存地址寫入數據的。指針是一個特殊的變量,它里面存儲的數值被解釋成為內存里的一個地址。 要搞清一個指針需要搞清指針的四方面的內容:指針的類型,指針所指向的 類型,指針的值或者叫指針所指向的內存區,還有指針本身所占據的內存區。讓我們分別說明。先聲明幾個指針放著做例子:例一:(1)int*ptr;(2)char*ptr;(3)int**ptr;(4)int(*ptr)[3];(5)int*(*ptr)[4];指針的類型從語法的角度看,你只要把指針聲明語句里的指針名字去掉,剩下的部分就是這個指針的類型。這是指針本身所具有的類型。讓我們看看例一中各個指針的類型:(1)int*ptr;//指針的類型是int*(2)char*ptr;//指針的類型是char*(3)int**ptr;//指針的類型是int**(4)int(*ptr)[3];//指針的類型是int(*)[3](5)int*(*ptr)[4];//指針的類型是int*(*)[4]怎么樣?找出指針的類型的方法是不是很簡單?指針所指向的類型當你通過指針來訪問指針所指向的內存區時,指針所指向的類型決定了編譯器將把那片內存區里的內容當做什么來看待。 從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉,剩下的就是指針所指向的類型。例如:(1)int*ptr;//指針所指向的類型是int(2)char*ptr;//指針所指向的的類型是char(3)int**ptr;//指針所指向的的類型是int*(4)int(*ptr)[3];//指針所指向的的類型是int()[3](5)int*(*ptr)[4];//指針所指向的的類型是int*()[4]在指針的算術運算中,指針所指向的類型有很大的作用。指針的類型(即指針本身的類型)和指針所指向的類型是兩個概念。當你對C越來越熟悉時,你會發現,把與指針攪和在一起的"類型"這個概念分成"指針的類型"和"指針所指向的類型"兩個概念,是精通指針的關鍵點之一。我看了不少書,發現有些寫得差的書中,就把指針的這兩個概念攪在一起了,所以看起書來前后矛盾,越看越糊涂。指針的值,或者叫指針所指向的內存區或地址指針的值是指針本身存儲的數值,這個值將被編譯器當作一個地址,而不是一個一般的數值。在32位程序里,所有類型的指針的值都是一個32位整數,因為32位程序里內存地址全都是32位長。 指針所指向的內存區就是從指針的值所代表的那個內存地址開始,長度為si zeof(指針所指向的類型)的一片內存區。以后,我們說一個指針的值是XX,就相當于說該指針指向了以XX為首地址的一片內存區域;我們說一個指針指向了某塊內存區域,就相當于說該指針的值是這塊內存區域的首地址。指針所指向的內存區和指針所指向的類型是兩個完全不同的概念。在例一中,指針所指向的類型已經有了,但由于指針還未初始化,所以它所指向的內存區是不存在的,或者說是無意義的。以后,每遇到一個指針,都應該問問:這個指針的類型是什么?指針指向的類型是什么?該指針指向了哪里?指針本身所占據的內存區指針本身占了多大的內存?你只要用函數sizeof(指針的類型)測一下就知道了。在32位平臺里,指針本身占據了4個字節的長度。指針本身占據的內存這個概念在判斷一個指針表達式是否是左值時很有用。指針的算術運算指針可以加上或減去一個整數。指針的這種運算的意義和通常的數值的加減運算的意義是不一樣的。例如:例二:1、char a[20];2、int *ptr=a;3、ptr ++;在上例中,指針ptr的類型是int*,它指向的類型是int,它被初始化為指向整形變量a。接下來的第3句中,指針ptr被加了1,編譯器是這樣處理的:它把指針ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字節做單位的,故ptr所指向的地址由原來的變量a的地址向高地址方向增加了4個字節。 由于char類型的長度是一個字節,所以,原來ptr是指向數組a的第0號單元開始的四個字節,此時指向了數組a中從第4號單元開始的四個字節。我們可以用一個指針和一個循環來遍歷一個數組,看例子:例三:int array[20];int *ptr=array;for(i=0;i<20;i++){ (*ptr) ++; ptr ++;}這個例子將整型數組中各個單元的值加1。由于每次循環都將指針ptr加1,所以每次循環都能訪問數組的下一個單元。再看例子:例四:1、char a[20];2、int *ptr=a;3、ptr +=5;在這個例子中,ptr被加上了5,編譯器是這樣處理的:將指針ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的單位是字節,故現在的ptr所指向的地址比起加5后的ptr所指向的地址來說,向高地址方向移動了20個字節。在這個例子中,沒加5前的ptr指向數組a的第0號單元開始的四個字節,加5后,ptr已經指向了數組a的合法范圍之外了。雖然這種情況在應用上會出問題,但在語法上卻是可以的。這也體現出了指針的靈活性。如果上例中,ptr是被減去5,那么處理過程大同小異,只不過ptr的值是被減去5乘sizeof(int),新的ptr指向的地址將比原來的ptr所指向的地址向低地址方向移動了20個字節。總結一下,一個指針ptrold加上一個整數n后,結果是一個新的指針ptrnew,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同。ptrnew的值將比ptrold的值增加了n乘sizeof(ptrold所指向的類型)個字節。就是說,ptrnew所指向的內存區將比ptrold所指向的內存區向高地址方向移動了n乘sizeof(ptrold所指向的類型)個字節。 一個指針ptrold減去一個整數n后,結果是一個新的指針ptrnew,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同。ptrnew的值將比ptrold的值減少了n乘sizeof(ptrold所指向的類型)個字節,就是說,ptrnew所指向的內存區將比ptrold所指向的內存區向低地址方向移動了n乘sizeof(ptrold所指向的類型)個字節。運算符&和*這里&是取地址運算符,*是...書上叫做"間接運算符"。&a的運算結果是一個指針,指針的類型是a的類型加個*,指針所指向的類型是a的類型,指針所指向的地址嘛,那就是a的地址。*p的運算結果就五花八門了。總之*p的結果是p所指向的東西,這個東西有這些特點:它的類型是p指向的類型,它所占用的地址是p所指向的地址。例五:int a=12;int b;int *p;int **ptr;p=&a;//&a的結果是一個指針,類型是int*,指向的類型是int,指向的地址是a的地址。*p=24;//*p的結果,在這里它的類型是int,它所占用的地址是p所指向的地址,顯然,*p就是變量a。ptr=&p;//&p的結果是個指針,該指針的類型是p的類型加個*,在這里是int **。該指針所指向的類型是p的類型,這里是int*。該指針所指向的地址就是指針p自己的地址。*ptr=&b;//*ptr是個指針,&b的結果也是個指針,且這兩個指針的類型和所指向的類型是一樣的,所以用&b來給*ptr賦值就是毫無問題的了。**ptr=34;//*ptr的結果是ptr所指向的東西,在這里是一個指針,對這個指針再做一次*運算,結果就是一個int類型的變量。指針表達式一個表達式的最后結果如果是一個指針,那么這個表達式就叫指針表式。 下面是一些指針表達式的例子:例六:int a,b;int array[10];int *pa;pa=&a;//&a是一個指針表達式。int **ptr=&pa;//&pa也是一個指針表達式。*ptr=&b;//*ptr和&b都是指針表達式。pa=array;pa++;//這也是指針表達式。