轉(zhuǎn)自CSDN博客
原文鏈接:http://blog.csdn.net/xinyuwuxian/article/details/9041413#0-qzone-1-46836-d020d2d2a4e8d1a374a433f596ad1440
姓名:呂彬 學號:16130140354
【嵌牛導讀】新手在C語言的學習過程中遇到的最頭疼的知識點應該就是指針了窗怒,指針在C語言中有非常大的用處申窘。下面我就帶著問題來寫下我對于指針的一些理解。
【嵌牛鼻子】
【嵌牛提問】指針是什么?如何使用贷洲?
【嵌牛正文】一、指針的內(nèi)存布局先看下面的例子:int *p;大家都知道這里定義了一個指針p钦椭。但是p 到底是什么東西呢彪腔?還記得第一章里說過,“任何一種數(shù)據(jù)類型我們都可以把它當一個模子”嗎番挺?p玄柏,毫無疑問,是某個模子咔出來的。我們也討論過,任何模子都必須有其特定的大小灾挨,這樣才能用來“咔咔咔”。那咔出p 的這個模子到底是什么樣子呢秒拔?它占多大的空間呢?現(xiàn)在用sizeof 測試一下(32 位系統(tǒng)):sizeof(p)的值為4庵芭。嗯,這說明咔出p 的這個模子大小為4 個byte。顯然曹宴,這個模子不是“int”区转,雖然它大小也為4废离。既然不是“int”那就一定是“int *”了柿扣。好未状,那現(xiàn)在我們可以這理解這個定義:一個“int *”類型的模子在內(nèi)存上咔出了4 個字節(jié)的空間艰垂,然后把這個4 個字節(jié)大小的空間命名為p,同時限定這4 個字節(jié)的空間里面只能存儲某個內(nèi)存地址胰柑,即使你存入別的任何數(shù)據(jù)姐浮,都將被當作地址處理卖鲤,而且這個內(nèi)存地址開始的連續(xù)4 個字節(jié)上只能存儲某個int類型的數(shù)據(jù)。這是一段咬文嚼字的說明,我們還是用圖來解析一下:
如上圖所示,我們把p 稱為指針變量,p 里存儲的內(nèi)存地址處的內(nèi)存稱為p 所指向的內(nèi)存。這里還是多理解一下震肮,比較形象,不錯O(∩_∩)O~指針變量p 里存儲的任何數(shù)據(jù)都將被當作地址來處理躬厌。我們可以簡單的這么理解:一個基本的數(shù)據(jù)類型(包括結(jié)構(gòu)體等自定義類型)加上“*”號就構(gòu)成了一個指針類型的模子鸿捧。這個模子的大小是一定的,與“*”號前面的數(shù)據(jù)類型無關(guān)谍肤『干玻“*”號前面的數(shù)據(jù)類型只是說明指針所指向的內(nèi)存里存儲的數(shù)據(jù)類型俩滥。所以儡率,在32 位系統(tǒng)下,不管什么樣的指針類型棱貌,其大小都為4byte玖媚。可以測試一下sizeof(void *)婚脱。二今魔、“*”與防盜門的鑰匙這里這個“*”號怎么理解呢?舉個例子:當你回到家門口時障贸,你想進屋第一件事就是拿出鑰匙來開鎖袁波。那你想想防盜門的鎖芯是不是很像這個“*”號?你要進屋必須要用鑰匙傍菇,那你去讀寫一塊內(nèi)存是不是也要一把鑰匙呢渊鞋?這個“*”號是不是就是我們最好的鑰匙役首?使用指針的時候盹兢,沒有它窘问,你是不可能讀寫某塊內(nèi)存的。三吏颖、int *p = NULL 和*p = NULL 有什么區(qū)別?很多初學者都無法分清這兩者之間的區(qū)別麦射。我們先看下面的代碼:int *p = NULL;這時候我們可以通過編譯器查看p 的值為0x00000000穆碎。這句代碼的意思是:定義一個指針變量p床蜘,其指向的內(nèi)存里面保存的是int 類型的數(shù)據(jù)护戳;在定義變量p 的同時把p 的值設(shè)置為0x00000000自晰,而不是把*p 的值設(shè)置為0x00000000遣疯。這個過程叫做初始化虐急,是在編譯的時候進行的抗蠢。明白了什么是初始化之后,再看下面的代碼:int *p;*p = NULL;同樣犁钟,我們可以在編譯器上調(diào)試這兩行代碼棱诱。第一行代碼,定義了一個指針變量p涝动,其指向的內(nèi)存里面保存的是int 類型的數(shù)據(jù)迈勋;但是這時候變量p 本身的值是多少不得而知,也就是說現(xiàn)在變量p 保存的有可能是一個非法的地址醋粟。第二行代碼靡菇,給*p 賦值為NULL重归,即給p指向的內(nèi)存賦值為NULL;但是由于p 指向的內(nèi)存可能是非法的厦凤,所以調(diào)試的時候編譯器可能會報告一個內(nèi)存訪問錯誤鼻吮。這樣的話,我們可以把上面的代碼改寫改寫泳唠,使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ū)別了脖母。不過這里還有一個問題需要注意士鸥,也就是這個NULL。初學者往往在這里犯錯誤谆级。注意NULL 就是NULL烤礁,它被宏定義為0:#define NULL 0很多系統(tǒng)下除了有NULL外脚仔,還有NUL(Visual C++ 6.0 上提示說不認識NUL)鲤脏。NUL 是ASCII碼表的第一個字符吕朵,表示的是空字符,其ASCII 碼值為0硫嘶。其值雖然都為0沦疾,但表示的意思完全不一樣第队。同樣哮塞,NULL 和0 表示的意思也完全不一樣。一定不要混淆斥铺。另外還有初學者在使用NULL 的時候誤寫成null 或Null 等彻桃。這些都是不正確的坛善,C 語言對大小寫十分敏感啊晾蜘。當然邻眷,也確實有系統(tǒng)也定義了null,其意思也與NULL 沒有區(qū)別剔交,但是你千萬不用使用null肆饶,這會影響你代碼的移植性。四岖常、如何將數(shù)值存儲到指定的內(nèi)存地址假設(shè)現(xiàn)在需要往內(nèi)存0x12ff7c 地址上存入一個整型數(shù)0x100驯镊。我們怎么才能做到呢?我們知道可以通過一個指針向其指向的內(nèi)存地址寫入數(shù)據(jù)竭鞍,那么這里的內(nèi)存地址0x12ff7c 其本質(zhì)不就是一個指針嘛板惑。所以我們可以用下面的方法:int *p = (int *)0x12ff7c;*p = 0x100;需要注意的是將地址0x12ff7c 賦值給指針變量p 的時候必須強制轉(zhuǎn)換。*p = 0x100;需要注意的是將地址0x12ff7c 賦值給指針變量p 的時候必須強制轉(zhuǎn)換偎快。至于這里為什么選擇內(nèi)存地址0x12ff7c冯乘,而不選擇別的地址,比如0xff00 等晒夹。這僅僅是為了方便VisualC++ 6.0 上測試而已。如果你選擇0xff00喷好,也許在執(zhí)行*p = 0x100;這條語句的時候,編譯器會報告一個內(nèi)存訪問的錯誤些膨,因為地址0xff00 處的內(nèi)存你可能并沒有權(quán)力去訪問。既然這樣洼哎,我們怎么知道一個內(nèi)存地址是可以合法的被訪問呢?也就是說你怎么知道地址0x12ff7c處的內(nèi)存是可以被訪問的呢?其實這很簡單凭涂,我們可以先定義一個變量i比如:int i = 0;變量i 所處的內(nèi)存肯定是可以被訪問的蝙斜。然后在編譯器的watch 窗口上觀察&i 的值不就知道其內(nèi)存地址了么?這里我得到的地址是0x12ff7c稚伍,僅此而已(不同的編譯器可能每次給變量i 分配的內(nèi)存地址不一樣,而剛好Visual C++ 6.0 每次都一樣)困檩。你完全可以給任意一個可以被合法訪問的地址賦值。得到這個地址后再把“int i = 0;”這句代碼刪除糟趾。一切“罪證”銷毀得一干二凈义郑,簡直是做得天衣無縫。除了這樣就沒有別的辦法了嗎劫笙?未必。我們甚至可以直接這么寫代碼:*(int *)0x12ff7c = 0x100;這行代碼其實和上面的兩行代碼沒有本質(zhì)的區(qū)別允华。先將地址0x12ff7c 強制轉(zhuǎn)換汉额,告訴編譯器這個地址上將存儲一個int 類型的數(shù)據(jù)怎茫;然后通過鑰匙“*”向這塊內(nèi)存寫入一個數(shù)據(jù)蜜宪。上面討論了這么多圃验,其實其表達形式并不重要,重要的是這種思維方式。也就是說我們完全有辦法給指定的某個內(nèi)存地址寫入數(shù)據(jù)的麻裁。指針是一個特殊的變量,它里面存儲的數(shù)值被解釋成為內(nèi)存里的一個地址手销。 要搞清一個指針需要搞清指針的四方面的內(nèi)容:指針的類型,指針所指向的 類型姑隅,指針的值或者叫指針所指向的內(nèi)存區(qū),還有指針本身所占據(jù)的內(nèi)存區(qū)鄙陡。讓我們分別說明耙册。先聲明幾個指針放著做例子:例一:(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]怎么樣冷冗?找出指針的類型的方法是不是很簡單?指針所指向的類型當你通過指針來訪問指針所指向的內(nèi)存區(qū)時,指針所指向的類型決定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當做什么來看待泰偿。 從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉调塌,剩下的就是指針所指向的類型。例如:(1)int*ptr;//指針所指向的類型是int(2)char*ptr;//指針所指向的的類型是char(3)int**ptr;//指針所指向的的類型是int*(4)int(*ptr)[3];//指針所指向的的類型是int()[3](5)int*(*ptr)[4];//指針所指向的的類型是int*()[4]在指針的算術(shù)運算中政溃,指針所指向的類型有很大的作用申鱼。指針的類型(即指針本身的類型)和指針所指向的類型是兩個概念。當你對C越來越熟悉時楚殿,你會發(fā)現(xiàn)影涉,把與指針攪和在一起的"類型"這個概念分成"指針的類型"和"指針所指向的類型"兩個概念匣缘,是精通指針的關(guān)鍵點之一肌厨。我看了不少書,發(fā)現(xiàn)有些寫得差的書中,就把指針的這兩個概念攪在一起了,所以看起書來前后矛盾胁镐,越看越糊涂。指針的值,或者叫指針所指向的內(nèi)存區(qū)或地址指針的值是指針本身存儲的數(shù)值空郊,這個值將被編譯器當作一個地址哼审,而不是一個一般的數(shù)值。在32位程序里春霍,所有類型的指針的值都是一個32位整數(shù)衅疙,因為32位程序里內(nèi)存地址全都是32位長离福。 指針所指向的內(nèi)存區(qū)就是從指針的值所代表的那個內(nèi)存地址開始,長度為si zeof(指針所指向的類型)的一片內(nèi)存區(qū)炼蛤。以后妖爷,我們說一個指針的值是XX,就相當于說該指針指向了以XX為首地址的一片內(nèi)存區(qū)域理朋;我們說一個指針指向了某塊內(nèi)存區(qū)域絮识,就相當于說該指針的值是這塊內(nèi)存區(qū)域的首地址彼念。指針所指向的內(nèi)存區(qū)和指針所指向的類型是兩個完全不同的概念。在例一中胧后,指針所指向的類型已經(jīng)有了裆站,但由于指針還未初始化杭棵,所以它所指向的內(nèi)存區(qū)是不存在的滓侍,或者說是無意義的氮兵。以后,每遇到一個指針颠悬,都應該問問:這個指針的類型是什么?指針指向的類型是什么婴氮?該指針指向了哪里迷扇?指針本身所占據(jù)的內(nèi)存區(qū)指針本身占了多大的內(nèi)存?你只要用函數(shù)sizeof(指針的類型)測一下就知道了。在32位平臺里,指針本身占據(jù)了4個字節(jié)的長度送爸。指針本身占據(jù)的內(nèi)存這個概念在判斷一個指針表達式是否是左值時很有用。指針的算術(shù)運算指針可以加上或減去一個整數(shù)板熊。指針的這種運算的意義和通常的數(shù)值的加減運算的意義是不一樣的肪获。例如:例二: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個字節(jié)丹壕。 由于char類型的長度是一個字節(jié)庆械,所以,原來ptr是指向數(shù)組a的第0號單元開始的四個字節(jié)菌赖,此時指向了數(shù)組a中從第4號單元開始的四個字節(jié)缭乘。我們可以用一個指針和一個循環(huán)來遍歷一個數(shù)組,看例子:例三:int array[20];int *ptr=array;for(i=0;i<20;i++){ (*ptr) ++; ptr ++琉用;}這個例子將整型數(shù)組中各個單元的值加1堕绩。由于每次循環(huán)都將指針ptr加1,所以每次循環(huán)都能訪問數(shù)組的下一個單元邑时。再看例子:例四:1逛尚、char a[20];2、int *ptr=a;3刁愿、ptr +=5;在這個例子中绰寞,ptr被加上了5,編譯器是這樣處理的:將指針ptr的值加上5乘sizeof(int)铣口,在32位程序中就是加上了5乘4=20滤钱。由于地址的單位是字節(jié),故現(xiàn)在的ptr所指向的地址比起加5后的ptr所指向的地址來說脑题,向高地址方向移動了20個字節(jié)件缸。在這個例子中,沒加5前的ptr指向數(shù)組a的第0號單元開始的四個字節(jié)叔遂,加5后他炊,ptr已經(jīng)指向了數(shù)組a的合法范圍之外了。雖然這種情況在應用上會出問題已艰,但在語法上卻是可以的痊末。這也體現(xiàn)出了指針的靈活性。如果上例中哩掺,ptr是被減去5凿叠,那么處理過程大同小異,只不過ptr的值是被減去5乘sizeof(int),新的ptr指向的地址將比原來的ptr所指向的地址向低地址方向移動了20個字節(jié)盒件〉疟蹋總結(jié)一下,一個指針ptrold加上一個整數(shù)n后炒刁,結(jié)果是一個新的指針ptrnew恩沽,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同翔始。ptrnew的值將比ptrold的值增加了n乘sizeof(ptrold所指向的類型)個字節(jié)飒筑。就是說,ptrnew所指向的內(nèi)存區(qū)將比ptrold所指向的內(nèi)存區(qū)向高地址方向移動了n乘sizeof(ptrold所指向的類型)個字節(jié)绽昏。 一個指針ptrold減去一個整數(shù)n后协屡,結(jié)果是一個新的指針ptrnew,ptrnew的類型和ptrold的類型相同全谤,ptrnew所指向的類型和ptrold所指向的類型也相同肤晓。ptrnew的值將比ptrold的值減少了n乘sizeof(ptrold所指向的類型)個字節(jié),就是說认然,ptrnew所指向的內(nèi)存區(qū)將比ptrold所指向的內(nèi)存區(qū)向低地址方向移動了n乘sizeof(ptrold所指向的類型)個字節(jié)补憾。運算符&和*這里&是取地址運算符,*是...書上叫做"間接運算符"卷员。&a的運算結(jié)果是一個指針盈匾,指針的類型是a的類型加個*,指針所指向的類型是a的類型毕骡,指針所指向的地址嘛削饵,那就是a的地址。*p的運算結(jié)果就五花八門了未巫×耍總之*p的結(jié)果是p所指向的東西,這個東西有這些特點:它的類型是p指向的類型叙凡,它所占用的地址是p所指向的地址劈伴。例五:int a=12;int b;int *p;int **ptr;p=&a;//&a的結(jié)果是一個指針,類型是int*握爷,指向的類型是int跛璧,指向的地址是a的地址。*p=24;//*p的結(jié)果新啼,在這里它的類型是int啸罢,它所占用的地址是p所指向的地址乘瓤,顯然铅忿,*p就是變量a忧便。ptr=&p;//&p的結(jié)果是個指針拣宏,該指針的類型是p的類型加個*,在這里是int **形纺。該指針所指向的類型是p的類型昭抒,這里是int*。該指針所指向的地址就是指針p自己的地址茶鉴。*ptr=&b;//*ptr是個指針锋玲,&b的結(jié)果也是個指針,且這兩個指針的類型和所指向的類型是一樣的涵叮,所以用&b來給*ptr賦值就是毫無問題的了惭蹂。**ptr=34;//*ptr的結(jié)果是ptr所指向的東西,在這里是一個指針割粮,對這個指針再做一次*運算盾碗,結(jié)果就是一個int類型的變量。指針表達式一個表達式的最后結(jié)果如果是一個指針舀瓢,那么這個表達式就叫指針表式廷雅。 下面是一些指針表達式的例子:例六:int a,b;int array[10];int *pa;pa=&a;//&a是一個指針表達式。int **ptr=&pa;//&pa也是一個指針表達式京髓。*ptr=&b;//*ptr和&b都是指針表達式航缀。pa=array;pa++;//這也是指針表達式。