C語言指針小結(jié)

轉(zhuǎn)自CSDN博客

原文鏈接:http://blog.csdn.net/xinyuwuxian/article/details/9041413#0-qzone-1-46836-d020d2d2a4e8d1a374a433f596ad1440

姓名:呂彬 學(xué)號:16130140354

【嵌牛導(dǎo)讀】新手在C語言的學(xué)習(xí)過程中遇到的最頭疼的知識點(diǎn)應(yīng)該就是指針了,指針在C語言中有非常大的用處。下面我就帶著問題來寫下我對于指針的一些理解。

【嵌牛鼻子】

【嵌牛提問】指針是什么?如何使用?

【嵌牛正文】一、指針的內(nèi)存布局先看下面的例子:int *p;大家都知道這里定義了一個(gè)指針p。但是p 到底是什么東西呢?還記得第一章里說過,“任何一種數(shù)據(jù)類型我們都可以把它當(dāng)一個(gè)模子”嗎?p,毫無疑問,是某個(gè)模子咔出來的。我們也討論過,任何模子都必須有其特定的大小,這樣才能用來“咔咔咔”。那咔出p 的這個(gè)模子到底是什么樣子呢?它占多大的空間呢?現(xiàn)在用sizeof 測試一下(32 位系統(tǒng)):sizeof(p)的值為4。嗯,這說明咔出p 的這個(gè)模子大小為4 個(gè)byte。顯然,這個(gè)模子不是“int”,雖然它大小也為4。既然不是“int”那就一定是“int *”了。好,那現(xiàn)在我們可以這理解這個(gè)定義:一個(gè)“int *”類型的模子在內(nèi)存上咔出了4 個(gè)字節(jié)的空間,然后把這個(gè)4 個(gè)字節(jié)大小的空間命名為p,同時(shí)限定這4 個(gè)字節(jié)的空間里面只能存儲某個(gè)內(nèi)存地址,即使你存入別的任何數(shù)據(jù),都將被當(dāng)作地址處理,而且這個(gè)內(nèi)存地址開始的連續(xù)4 個(gè)字節(jié)上只能存儲某個(gè)int類型的數(shù)據(jù)。這是一段咬文嚼字的說明,我們還是用圖來解析一下:

圖片發(fā)自簡書App


如上圖所示,我們把p 稱為指針變量,p 里存儲的內(nèi)存地址處的內(nèi)存稱為p 所指向的內(nèi)存。這里還是多理解一下,比較形象,不錯(cuò)O(∩_∩)O~指針變量p 里存儲的任何數(shù)據(jù)都將被當(dāng)作地址來處理。我們可以簡單的這么理解:一個(gè)基本的數(shù)據(jù)類型(包括結(jié)構(gòu)體等自定義類型)加上“*”號就構(gòu)成了一個(gè)指針類型的模子。這個(gè)模子的大小是一定的,與“*”號前面的數(shù)據(jù)類型無關(guān)?!?”號前面的數(shù)據(jù)類型只是說明指針?biāo)赶虻膬?nèi)存里存儲的數(shù)據(jù)類型。所以,在32 位系統(tǒng)下,不管什么樣的指針類型,其大小都為4byte??梢詼y試一下sizeof(void *)。二、“*”與防盜門的鑰匙這里這個(gè)“*”號怎么理解呢?舉個(gè)例子:當(dāng)你回到家門口時(shí),你想進(jìn)屋第一件事就是拿出鑰匙來開鎖。那你想想防盜門的鎖芯是不是很像這個(gè)“*”號?你要進(jìn)屋必須要用鑰匙,那你去讀寫一塊內(nèi)存是不是也要一把鑰匙呢?這個(gè)“*”號是不是就是我們最好的鑰匙?使用指針的時(shí)候,沒有它,你是不可能讀寫某塊內(nèi)存的。三、int *p = NULL 和*p = NULL 有什么區(qū)別?很多初學(xué)者都無法分清這兩者之間的區(qū)別。我們先看下面的代碼:int *p = NULL;這時(shí)候我們可以通過編譯器查看p 的值為0x00000000。這句代碼的意思是:定義一個(gè)指針變量p,其指向的內(nèi)存里面保存的是int 類型的數(shù)據(jù);在定義變量p 的同時(shí)把p 的值設(shè)置為0x00000000,而不是把*p 的值設(shè)置為0x00000000。這個(gè)過程叫做初始化,是在編譯的時(shí)候進(jìn)行的。明白了什么是初始化之后,再看下面的代碼:int *p;*p = NULL;同樣,我們可以在編譯器上調(diào)試這兩行代碼。第一行代碼,定義了一個(gè)指針變量p,其指向的內(nèi)存里面保存的是int 類型的數(shù)據(jù);但是這時(shí)候變量p 本身的值是多少不得而知,也就是說現(xiàn)在變量p 保存的有可能是一個(gè)非法的地址。第二行代碼,給*p 賦值為NULL,即給p指向的內(nèi)存賦值為NULL;但是由于p 指向的內(nèi)存可能是非法的,所以調(diào)試的時(shí)候編譯器可能會(huì)報(bào)告一個(gè)內(nèi)存訪問錯(cuò)誤。這樣的話,我們可以把上面的代碼改寫改寫,使p 指向一塊合法的內(nèi)存int i = 10;int *p = &i;*p = NULL;在編譯器上調(diào)試一下,我們發(fā)現(xiàn)p 指向的內(nèi)存由原來的10 變?yōu)? 了;而p 本身的值, 即內(nèi)存地址并沒有改變。經(jīng)過上面的分析,相信你已經(jīng)明白它們之間的區(qū)別了。不過這里還有一個(gè)問題需要注意,也就是這個(gè)NULL。初學(xué)者往往在這里犯錯(cuò)誤。注意NULL 就是NULL,它被宏定義為0:#define NULL 0很多系統(tǒng)下除了有NULL外,還有NUL(Visual C++ 6.0 上提示說不認(rèn)識NUL)。NUL 是ASCII碼表的第一個(gè)字符,表示的是空字符,其ASCII 碼值為0。其值雖然都為0,但表示的意思完全不一樣。同樣,NULL 和0 表示的意思也完全不一樣。一定不要混淆。另外還有初學(xué)者在使用NULL 的時(shí)候誤寫成null 或Null 等。這些都是不正確的,C 語言對大小寫十分敏感啊。當(dāng)然,也確實(shí)有系統(tǒng)也定義了null,其意思也與NULL 沒有區(qū)別,但是你千萬不用使用null,這會(huì)影響你代碼的移植性。四、如何將數(shù)值存儲到指定的內(nèi)存地址假設(shè)現(xiàn)在需要往內(nèi)存0x12ff7c 地址上存入一個(gè)整型數(shù)0x100。我們怎么才能做到呢?我們知道可以通過一個(gè)指針向其指向的內(nèi)存地址寫入數(shù)據(jù),那么這里的內(nèi)存地址0x12ff7c 其本質(zhì)不就是一個(gè)指針嘛。所以我們可以用下面的方法:int *p = (int *)0x12ff7c;*p = 0x100;需要注意的是將地址0x12ff7c 賦值給指針變量p 的時(shí)候必須強(qiáng)制轉(zhuǎn)換。*p = 0x100;需要注意的是將地址0x12ff7c 賦值給指針變量p 的時(shí)候必須強(qiáng)制轉(zhuǎn)換。至于這里為什么選擇內(nèi)存地址0x12ff7c,而不選擇別的地址,比如0xff00 等。這僅僅是為了方便VisualC++ 6.0 上測試而已。如果你選擇0xff00,也許在執(zhí)行*p = 0x100;這條語句的時(shí)候,編譯器會(huì)報(bào)告一個(gè)內(nèi)存訪問的錯(cuò)誤,因?yàn)榈刂?xff00 處的內(nèi)存你可能并沒有權(quán)力去訪問。既然這樣,我們怎么知道一個(gè)內(nèi)存地址是可以合法的被訪問呢?也就是說你怎么知道地址0x12ff7c處的內(nèi)存是可以被訪問的呢?其實(shí)這很簡單,我們可以先定義一個(gè)變量i比如:int i = 0;變量i 所處的內(nèi)存肯定是可以被訪問的。然后在編譯器的watch 窗口上觀察&i 的值不就知道其內(nèi)存地址了么?這里我得到的地址是0x12ff7c,僅此而已(不同的編譯器可能每次給變量i 分配的內(nèi)存地址不一樣,而剛好Visual C++ 6.0 每次都一樣)。你完全可以給任意一個(gè)可以被合法訪問的地址賦值。得到這個(gè)地址后再把“int i = 0;”這句代碼刪除。一切“罪證”銷毀得一干二凈,簡直是做得天衣無縫。除了這樣就沒有別的辦法了嗎?未必。我們甚至可以直接這么寫代碼:*(int *)0x12ff7c = 0x100;這行代碼其實(shí)和上面的兩行代碼沒有本質(zhì)的區(qū)別。先將地址0x12ff7c 強(qiáng)制轉(zhuǎn)換,告訴編譯器這個(gè)地址上將存儲一個(gè)int 類型的數(shù)據(jù);然后通過鑰匙“*”向這塊內(nèi)存寫入一個(gè)數(shù)據(jù)。上面討論了這么多,其實(shí)其表達(dá)形式并不重要,重要的是這種思維方式。也就是說我們完全有辦法給指定的某個(gè)內(nèi)存地址寫入數(shù)據(jù)的。指針是一個(gè)特殊的變量,它里面存儲的數(shù)值被解釋成為內(nèi)存里的一個(gè)地址。 要搞清一個(gè)指針需要搞清指針的四方面的內(nèi)容:指針的類型,指針?biāo)赶虻?類型,指針的值或者叫指針?biāo)赶虻膬?nèi)存區(qū),還有指針本身所占據(jù)的內(nèi)存區(qū)。讓我們分別說明。先聲明幾個(gè)指針放著做例子:例一:(1)int*ptr;(2)char*ptr;(3)int**ptr;(4)int(*ptr)[3];(5)int*(*ptr)[4];指針的類型從語法的角度看,你只要把指針聲明語句里的指針名字去掉,剩下的部分就是這個(gè)指針的類型。這是指針本身所具有的類型。讓我們看看例一中各個(gè)指針的類型:(1)int*ptr;//指針的類型是int*(2)char*ptr;//指針的類型是char*(3)int**ptr;//指針的類型是int**(4)int(*ptr)[3];//指針的類型是int(*)[3](5)int*(*ptr)[4];//指針的類型是int*(*)[4]怎么樣?找出指針的類型的方法是不是很簡單?指針?biāo)赶虻念愋彤?dāng)你通過指針來訪問指針?biāo)赶虻膬?nèi)存區(qū)時(shí),指針?biāo)赶虻念愋蜎Q定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當(dāng)做什么來看待。 從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉,剩下的就是指針?biāo)赶虻念愋?。例如?1)int*ptr;//指針?biāo)赶虻念愋褪莍nt(2)char*ptr;//指針?biāo)赶虻牡念愋褪莄har(3)int**ptr;//指針?biāo)赶虻牡念愋褪莍nt*(4)int(*ptr)[3];//指針?biāo)赶虻牡念愋褪莍nt()[3](5)int*(*ptr)[4];//指針?biāo)赶虻牡念愋褪莍nt*()[4]在指針的算術(shù)運(yùn)算中,指針?biāo)赶虻念愋陀泻艽蟮淖饔?。指針的類?即指針本身的類型)和指針?biāo)赶虻念愋褪莾蓚€(gè)概念。當(dāng)你對C越來越熟悉時(shí),你會(huì)發(fā)現(xiàn),把與指針攪和在一起的"類型"這個(gè)概念分成"指針的類型"和"指針?biāo)赶虻念愋?兩個(gè)概念,是精通指針的關(guān)鍵點(diǎn)之一。我看了不少書,發(fā)現(xiàn)有些寫得差的書中,就把指針的這兩個(gè)概念攪在一起了,所以看起書來前后矛盾,越看越糊涂。指針的值,或者叫指針?biāo)赶虻膬?nèi)存區(qū)或地址指針的值是指針本身存儲的數(shù)值,這個(gè)值將被編譯器當(dāng)作一個(gè)地址,而不是一個(gè)一般的數(shù)值。在32位程序里,所有類型的指針的值都是一個(gè)32位整數(shù),因?yàn)?2位程序里內(nèi)存地址全都是32位長。 指針?biāo)赶虻膬?nèi)存區(qū)就是從指針的值所代表的那個(gè)內(nèi)存地址開始,長度為si zeof(指針?biāo)赶虻念愋?的一片內(nèi)存區(qū)。以后,我們說一個(gè)指針的值是XX,就相當(dāng)于說該指針指向了以XX為首地址的一片內(nèi)存區(qū)域;我們說一個(gè)指針指向了某塊內(nèi)存區(qū)域,就相當(dāng)于說該指針的值是這塊內(nèi)存區(qū)域的首地址。指針?biāo)赶虻膬?nèi)存區(qū)和指針?biāo)赶虻念愋褪莾蓚€(gè)完全不同的概念。在例一中,指針?biāo)赶虻念愋鸵呀?jīng)有了,但由于指針還未初始化,所以它所指向的內(nèi)存區(qū)是不存在的,或者說是無意義的。以后,每遇到一個(gè)指針,都應(yīng)該問問:這個(gè)指針的類型是什么?指針指向的類型是什么?該指針指向了哪里?指針本身所占據(jù)的內(nèi)存區(qū)指針本身占了多大的內(nèi)存?你只要用函數(shù)sizeof(指針的類型)測一下就知道了。在32位平臺里,指針本身占據(jù)了4個(gè)字節(jié)的長度。指針本身占據(jù)的內(nèi)存這個(gè)概念在判斷一個(gè)指針表達(dá)式是否是左值時(shí)很有用。指針的算術(shù)運(yùn)算指針可以加上或減去一個(gè)整數(shù)。指針的這種運(yùn)算的意義和通常的數(shù)值的加減運(yùn)算的意義是不一樣的。例如:例二:1、char a[20];2、int *ptr=a;3、ptr ++;在上例中,指針ptr的類型是int*,它指向的類型是int,它被初始化為指向整形變量a。接下來的第3句中,指針ptr被加了1,編譯器是這樣處理的:它把指針ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字節(jié)做單位的,故ptr所指向的地址由原來的變量a的地址向高地址方向增加了4個(gè)字節(jié)。 由于char類型的長度是一個(gè)字節(jié),所以,原來ptr是指向數(shù)組a的第0號單元開始的四個(gè)字節(jié),此時(shí)指向了數(shù)組a中從第4號單元開始的四個(gè)字節(jié)。我們可以用一個(gè)指針和一個(gè)循環(huán)來遍歷一個(gè)數(shù)組,看例子:例三:int array[20];int *ptr=array;for(i=0;i<20;i++){ (*ptr) ++; ptr ++;}這個(gè)例子將整型數(shù)組中各個(gè)單元的值加1。由于每次循環(huán)都將指針ptr加1,所以每次循環(huán)都能訪問數(shù)組的下一個(gè)單元。再看例子:例四:1、char a[20];2、int *ptr=a;3、ptr +=5;在這個(gè)例子中,ptr被加上了5,編譯器是這樣處理的:將指針ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的單位是字節(jié),故現(xiàn)在的ptr所指向的地址比起加5后的ptr所指向的地址來說,向高地址方向移動(dòng)了20個(gè)字節(jié)。在這個(gè)例子中,沒加5前的ptr指向數(shù)組a的第0號單元開始的四個(gè)字節(jié),加5后,ptr已經(jīng)指向了數(shù)組a的合法范圍之外了。雖然這種情況在應(yīng)用上會(huì)出問題,但在語法上卻是可以的。這也體現(xiàn)出了指針的靈活性。如果上例中,ptr是被減去5,那么處理過程大同小異,只不過ptr的值是被減去5乘sizeof(int),新的ptr指向的地址將比原來的ptr所指向的地址向低地址方向移動(dòng)了20個(gè)字節(jié)。總結(jié)一下,一個(gè)指針ptrold加上一個(gè)整數(shù)n后,結(jié)果是一個(gè)新的指針ptrnew,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同。ptrnew的值將比ptrold的值增加了n乘sizeof(ptrold所指向的類型)個(gè)字節(jié)。就是說,ptrnew所指向的內(nèi)存區(qū)將比ptrold所指向的內(nèi)存區(qū)向高地址方向移動(dòng)了n乘sizeof(ptrold所指向的類型)個(gè)字節(jié)。 一個(gè)指針ptrold減去一個(gè)整數(shù)n后,結(jié)果是一個(gè)新的指針ptrnew,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同。ptrnew的值將比ptrold的值減少了n乘sizeof(ptrold所指向的類型)個(gè)字節(jié),就是說,ptrnew所指向的內(nèi)存區(qū)將比ptrold所指向的內(nèi)存區(qū)向低地址方向移動(dòng)了n乘sizeof(ptrold所指向的類型)個(gè)字節(jié)。運(yùn)算符&和*這里&是取地址運(yùn)算符,*是...書上叫做"間接運(yùn)算符"。&a的運(yùn)算結(jié)果是一個(gè)指針,指針的類型是a的類型加個(gè)*,指針?biāo)赶虻念愋褪莂的類型,指針?biāo)赶虻牡刂仿?,那就是a的地址。*p的運(yùn)算結(jié)果就五花八門了??傊?p的結(jié)果是p所指向的東西,這個(gè)東西有這些特點(diǎn):它的類型是p指向的類型,它所占用的地址是p所指向的地址。例五:int a=12;int b;int *p;int **ptr;p=&a;//&a的結(jié)果是一個(gè)指針,類型是int*,指向的類型是int,指向的地址是a的地址。*p=24;//*p的結(jié)果,在這里它的類型是int,它所占用的地址是p所指向的地址,顯然,*p就是變量a。ptr=&p;//&p的結(jié)果是個(gè)指針,該指針的類型是p的類型加個(gè)*,在這里是int **。該指針?biāo)赶虻念愋褪莗的類型,這里是int*。該指針?biāo)赶虻牡刂肪褪侵羔榩自己的地址。*ptr=&b;//*ptr是個(gè)指針,&b的結(jié)果也是個(gè)指針,且這兩個(gè)指針的類型和所指向的類型是一樣的,所以用&b來給*ptr賦值就是毫無問題的了。**ptr=34;//*ptr的結(jié)果是ptr所指向的東西,在這里是一個(gè)指針,對這個(gè)指針再做一次*運(yùn)算,結(jié)果就是一個(gè)int類型的變量。指針表達(dá)式一個(gè)表達(dá)式的最后結(jié)果如果是一個(gè)指針,那么這個(gè)表達(dá)式就叫指針表式。 下面是一些指針表達(dá)式的例子:例六:int a,b;int array[10];int *pa;pa=&a;//&a是一個(gè)指針表達(dá)式。int **ptr=&pa;//&pa也是一個(gè)指針表達(dá)式。*ptr=&b;//*ptr和&b都是指針表達(dá)式。pa=array;pa++;//這也是指針表達(dá)式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • void* 類型指針:通用變體類型指針;可以不經(jīng)轉(zhuǎn)換,賦給其他指針,函數(shù)指針除外;malloc返回的就是void*...
    冰吉凌閱讀 3,527評論 0 18
  • 指針是C語言中廣泛使用的一種數(shù)據(jù)類型。 運(yùn)用指針編程是C語言最主要的風(fēng)格之一。利用指針變量可以表示各種數(shù)據(jù)結(jié)構(gòu); ...
    朱森閱讀 3,615評論 3 44
  • 1. 指針數(shù)組是一個(gè)數(shù)組,它的元素是一個(gè)指針。 2. 數(shù)組指針是一個(gè)指針,它指向數(shù)組的首地址。 3. 指針函數(shù)是一...
    guanjianhe閱讀 181評論 0 0
  • 整理自計(jì)蒜客-CS 112: C++ 程序設(shè)計(jì) 指針是什么 指針是一個(gè)變量,其儲存的是值的地址,而不是值本身。指針...
    埠默笙聲閱讀 794評論 0 3
  • 1 初識工傷已經(jīng)好多年了,讀大學(xué)的時(shí)候人力資源課程無法涉及到這么詳細(xì)的法律,當(dāng)時(shí)只是提了下《勞動(dòng)法》。剛?cè)肼毮菚?huì),...
    冬冬Steven閱讀 195評論 0 1

友情鏈接更多精彩內(nèi)容