?一.內(nèi)存和地址
我們知道計(jì)算上CPU(中央處理器)在處理數(shù)據(jù)的時(shí)候,是通過(guò)地址總線把需要的數(shù)據(jù)從內(nèi)存中讀取的,后通過(guò)數(shù)據(jù)總線把處理后的數(shù)據(jù)放回內(nèi)存中。如下圖所示:
計(jì)算機(jī)把內(nèi)存劃分為?個(gè)個(gè)的內(nèi)存單元,每個(gè)內(nèi)存單元的大小取1個(gè)字節(jié)( 1個(gè)字節(jié)(Byte)=8個(gè)比特位(bit)),?再對(duì)每個(gè)內(nèi)存單元進(jìn)行編號(hào)處理,這樣就可以高效管理內(nèi)存。
?而在計(jì)算機(jī)中我們把內(nèi)存單元的編號(hào)也稱為地址。C語(yǔ)?中給地址起了新的名字叫:指針。
?
所以通過(guò)以上可總結(jié):內(nèi)存單元的編號(hào) == 地址 == 指針 ? 可見(jiàn)指針就是地址,地址就是指針。指針的作用就是訪問(wèn)內(nèi)存的
二.指針變量和地址
理解了內(nèi)存和地址的關(guān)系,就可以理解,在C語(yǔ)?中創(chuàng)建變量其實(shí)就是向內(nèi)存申請(qǐng)空間,如下:
上述的代碼就是創(chuàng)建了整型變量a,在內(nèi)存中申請(qǐng)4個(gè)字節(jié),?于存放整數(shù)9,其中每個(gè)字節(jié)都有地址。
那我們?nèi)绾文艿玫?a?的地址呢? 這?就得學(xué)習(xí)?個(gè)操作符:&——取地址操作符(單目操作符)
上圖所示,會(huì)打印出:0x006FFD70 (因?yàn)?amp;a取出的是a所占4個(gè)字節(jié)中地址較小的字節(jié)的地址)
?
以上我們通過(guò)取地址操作符(&)拿到的地址是?個(gè)數(shù)值,?如:0x006FFD70,這個(gè)數(shù)值有時(shí)候也是需要 存儲(chǔ)起來(lái),?便后期再使?,那我們把這樣的地址值存放在哪?呢?答:指針變量中。如下:
以上p為指針變量。p左邊寫(xiě)的是 int* , * 是在說(shuō)明p是指針變量,?前?的 int 是在說(shuō)明p指向的是整型(int) 類型的對(duì)象。(指針變量也是?種變量,這種變量就是?來(lái)存放地址的,存放在指針變量中的值都會(huì)理解為地址。 )
我們將地址保存起來(lái),未來(lái)是要使?的,那怎么使?呢?
C語(yǔ)言中,我們只要拿到了地址(指針),就可以通過(guò)地址(指針)找到地址(指針)
指向的對(duì)象,則我們必須學(xué)習(xí)?個(gè)操作符:* ——?解引?操作符
*p 的意思就是通過(guò) p 中存放的地址,找到指向的空間, *p其實(shí)就是a變量了;所以*p = 2,這個(gè)操作符就是把a(bǔ)改成了2。
?這里有同學(xué)肯定在想,如果?的就是把a(bǔ)改成2的話,寫(xiě)成 a = 2; 不就完了嗎? 為啥?要使?指針呢? 其實(shí)這?是把a(bǔ)的修改交給了p來(lái)操作,這樣對(duì)a的修改,就多了?種的途徑,寫(xiě)代碼就會(huì)更加靈活!
指針變量的大小:
由于32位機(jī)器有32根地址總線,每根地址線出來(lái)的電信號(hào)轉(zhuǎn)換成數(shù)字信號(hào)后是1或者0,那我們把32根地址線產(chǎn)?的2進(jìn)制序列當(dāng)做?個(gè)地址,那么?個(gè)地址就是32個(gè)bit位,需要4個(gè)字節(jié)才能存儲(chǔ)
則指針變量的大小就是4個(gè)字節(jié)。如下圖所示:?
同理64位機(jī)器,有64根地址線,?個(gè)地址就是64個(gè)?進(jìn)制位組成的?進(jìn)制序列,存儲(chǔ)起來(lái)就需要
8個(gè)字節(jié)的空間,則指針變量的大小就是8個(gè)字節(jié)。 如下圖所示:?
?由上述總結(jié)可得:指針變量的大小取決于地址的大小
- ? ? ? ? ?32位平臺(tái)下地址是32個(gè)bit位(即4個(gè)字節(jié))
- ? ? ? ? ?64位平臺(tái)下地址是64個(gè)bit位(即8個(gè)字節(jié))
- 指針變量的??和類型是?關(guān)的,只要指針類型的變量,在相同的平臺(tái)下,??都是相同的。
三. 指針變量類型的意義
- 指針的解引用:
以上代碼通過(guò)調(diào)試我們可以看到,其只是將n的第?個(gè)字節(jié)改為0
則可得出結(jié)論:指針的類型決定了,對(duì)指針解引?的時(shí)候有多?的權(quán)限(?次能操作?個(gè)字節(jié))
?如: char* 的指針解引?就只能訪問(wèn)?個(gè)字節(jié),而?int* 的指針的解引?就能訪問(wèn)四個(gè)字節(jié)
- 指針 +- 整數(shù):
觀察以上代碼可總結(jié)出:指針變量類型決定了指針進(jìn)行加1,減1的時(shí)候,能偏移幾個(gè)字節(jié)(一次能走多遠(yuǎn) )
- void* 指針(泛型指針):? 無(wú)具體類型的指針
這種類型的指針可以?來(lái)接受任意類型地址。但是也有局限性, void* 類型的指針不能直接
進(jìn)行指針的 +- 整數(shù)和解引?的運(yùn)算。如下圖:
注:void* 類型的指針不能直接進(jìn)行指針的解引用?和 +- 整數(shù)的運(yùn)算
那么 void* 類型的指針到底有什么?呢?
?般 void* 類型的指針是使?在函數(shù)參數(shù)的部分,?來(lái)接收不同類型數(shù)據(jù)的地址,這樣的設(shè)計(jì)可以
實(shí)現(xiàn)泛型編程的效果。使得?個(gè)函數(shù)來(lái)處理多種類型的數(shù)據(jù)。
四.const修飾指針
-
const用來(lái)修飾變量:?如下圖:
?因?yàn)?/span>加上const 的 m 就是具有常屬性的變量(常屬性:不能被修改的屬性)雖然 m 不能被修改,但是本質(zhì)上還是變量,不是常量??煞Q m 為常變量
注:但在C++ 中,const修飾的變量為常m量
?
那 m?怎么才能改呢?具體如下:
-
const修飾指針變量:如下圖:
上述總結(jié): 當(dāng)const 修飾指針變量放在*左邊時(shí),const限制的是:指針變量指向的內(nèi)容。指針變量指向的內(nèi)容不能通過(guò)指針來(lái)修改,但是可以修改指針變量本身的值(其實(shí)修改的是指針變量的指向)
上述總結(jié): 當(dāng)const 修飾指針變量放在*右邊時(shí),const限制的是:指針變量本身。指針變量不能再指向其他變量,但是可以通過(guò)指針變量,修改指針變量指向的內(nèi)容
?
上述總結(jié): 當(dāng)const 修飾指針變量放在*左邊和右邊時(shí),const限制的是:指針變量本身 和?指針變量指向的內(nèi)容。
?五.指針運(yùn)算
指針的基本運(yùn)算有三種,分別是:
- ? ?指針 +- 整數(shù)
- ? 指針 - 指針
- 指針的關(guān)系運(yùn)算
? ?指針 +- 整數(shù):
例如:指針 +1 ?如下:
? ?
?由以上同理可推出:
type * p
p + 1 ?-- > 跳過(guò)1 * sizeof(type)
p + n-- > 跳過(guò)n * sizeof(type)
?
實(shí)踐:打印數(shù)組中的每個(gè)元素。?如下圖:
由上述總結(jié)得:指針1 +-??整數(shù)? ==? 指針2
指針 - 指針:
注:計(jì)算的前提條件:一定是兩個(gè)指針指向同一塊空間!
由上述代碼得出:指針 - 指針 == 兩個(gè)指針之間的絕對(duì)值的元素個(gè)數(shù)
實(shí)踐:?求字符串的長(zhǎng)度。如下圖:
上述代碼 return??str -? start;? ?就是指針 - 指針 得出并返回字符串的長(zhǎng)度。
指針的關(guān)系運(yùn)算:就是指針和指針比較大小?? 如下圖:
?
上述代碼通過(guò)指針和指針相互比較打印出數(shù)組的每一位。
六. 野指針
概念: 野指針就是指針指向的位置是不可知的(隨機(jī)的、不正確的、沒(méi)有明確限制的)
野指針的成因:
- 指針未初始化:
-
?指針越界訪問(wèn):
- ?指針指向的空間釋放:
如何規(guī)避野指針:
- 指針要初始化:
- 如果明確知道指針指向哪?,就直接初始化一個(gè)明確的值。
- 如果不知道指針應(yīng)該指向哪里,可以給指針賦值NULL。(NULL :是C語(yǔ)?中定義的?個(gè)標(biāo)識(shí)符常量,值是0,0也是地址,這個(gè)地址是?法使?的,讀寫(xiě)該地址會(huì)報(bào)錯(cuò))
舉例:
- 小心指針越界:
?個(gè)程序向內(nèi)存申請(qǐng)了哪些空間,通過(guò)指針也就只能訪問(wèn)哪些空間,不能超出范圍訪問(wèn),超出了就是越界訪問(wèn)。
?
- 指針變量不再使用時(shí),及時(shí)置NULL,指針使?之前檢查有效性:
當(dāng)指針變量指向?塊區(qū)域時(shí),我們可以通過(guò)指針訪問(wèn)該區(qū)域,后期不再使?這個(gè)指針訪問(wèn)空間的
時(shí)候,我們可以把該指針置為NULL。因?yàn)榧s定俗成的?個(gè)規(guī)則就是:只要是NULL指針就不去訪問(wèn),同時(shí)使?指針之前可以判斷指針是否為NULL。
我們可以把野指針想象成野狗,野狗放任不管是?常危險(xiǎn)的,所以我們可以找?棵樹(shù)把野狗拴起來(lái),就相對(duì)安全了,給指針變量及時(shí)賦值為NULL,其實(shí)就類似把野狗栓前來(lái),就是把野指針暫時(shí)管理起來(lái)。不過(guò)野狗即使拴起來(lái)我們也要繞著?,不能去挑逗野狗,有點(diǎn)危險(xiǎn);對(duì)于指針也是,在使?之前,我們也要判斷是否為NULL,看看是不是被拴起來(lái)起來(lái)的野狗,如果是,就不能直接使?,如果不是,我們?cè)偃ナ?。?
- 避免返回局部變量的地址。
七.assert斷言
assert.h 頭?件定義了宏 assert() ,用于在運(yùn)行時(shí)確保程序符合指定條件,如果不符合,就報(bào)錯(cuò)終止運(yùn)行。?這個(gè)宏常常被稱為“斷言”
如下:
assert(p != NULL);
上述代碼檢測(cè):p 是否等于 NULL 。如果確實(shí)不等于 NULL ,程序繼續(xù)運(yùn)?,否則就會(huì)終?運(yùn)?,并且給出報(bào)錯(cuò)信息提示。
assert() 宏接受?個(gè)表達(dá)式作為參數(shù)。如果該表達(dá)式為真(返回值非零), assert() 不會(huì)產(chǎn)?
任何作?,程序繼續(xù)運(yùn)?。如果該表達(dá)式為假(返回值為零), assert() 就會(huì)報(bào)錯(cuò),在標(biāo)準(zhǔn)錯(cuò)誤
流 stderr 中寫(xiě)??條錯(cuò)誤信息,顯?沒(méi)有通過(guò)的表達(dá)式,以及包含這個(gè)表達(dá)式的?件名和?號(hào)。具體如下:
如果該表達(dá)式為假時(shí):
如果該表達(dá)式為真時(shí):
注意:assert 不僅可以斷言指針還可以斷言其他。只要表達(dá)式成立就行!
assert() 的使用的好處:
- 出現(xiàn)錯(cuò)誤時(shí),能?動(dòng)標(biāo)識(shí)?件和出問(wèn)題的?號(hào)
- 無(wú)需更改代碼就能開(kāi)啟或關(guān)閉 assert() 的機(jī)制(需定義?個(gè)宏 NDEBUG 。然后重新編譯程序,編譯器就會(huì)禁用文件中所有的 assert() 語(yǔ)句。如果程序又出現(xiàn)問(wèn)題,可以移除這條 #define NDBUG 指令(或者把它注釋掉),再次編譯,這樣就重啟?了 assert() 語(yǔ)句。)
舉例:
#define NDEBUG? 未注釋時(shí):
從上述代碼可看出:assert()機(jī)制未起作用(為關(guān)閉狀態(tài))
#define NDEBUG? 注釋時(shí):
從上述代碼可看出:assert()機(jī)制起作用了(為開(kāi)啟狀態(tài))
assert() 的缺點(diǎn)是:因?yàn)橐肓祟~外的檢查,增加了程序的運(yùn)行時(shí)間。
注:?般我們可以在 Debug版本中使?#define NDEBUG,在 Release 版本中選擇禁? assert 就行,因?yàn)?/strong>在 VS 這樣的集成開(kāi)發(fā)環(huán)境中,Release 版本中直接就是優(yōu)化掉了。這樣在debug版本寫(xiě)有利于程序員排查問(wèn)題,在 Release 版本不影響??使?時(shí)程序的效率。
八.指針的使用和傳址調(diào)用
-
strlen(求字符串長(zhǎng)度)的模擬實(shí)現(xiàn):
上述代碼,參數(shù) str 接收?個(gè)字符串的起始地址,然后開(kāi)始統(tǒng)計(jì)字符串中 \0 之前的字符個(gè)數(shù),最終返回長(zhǎng)度。 如果要模擬實(shí)現(xiàn)只要從起始地址開(kāi)始向后逐個(gè)字符的遍歷,只要不是 \0 字符,計(jì)數(shù)器就+1,這樣直到 \0 就停止了。
- 傳值調(diào)用和傳址調(diào)用:
學(xué)習(xí)指針的目的是使用指針解決問(wèn)題,那什么問(wèn)題,非指針不可呢?
例如:寫(xiě)?個(gè)函數(shù),實(shí)現(xiàn)交換兩個(gè)整型變量的值?:
錯(cuò)誤示范:
觀察上述代碼我們發(fā)現(xiàn):兩值并沒(méi)產(chǎn)?交換,這是為什么呢?
?看上圖,我們通過(guò)調(diào)試發(fā)現(xiàn):在main函數(shù)內(nèi)部,創(chuàng)建了a和b,a的地址是0x00cffdd0,b的地址是0x00cffdc4,在調(diào)? Swap函數(shù)時(shí),將a和b傳遞給了Swap函數(shù),在Swap函數(shù)內(nèi)部創(chuàng)建了形參x和y接收a和b的值,但是 x的地址是0x00cffcec,y的地址是0x00cffcf0,x和y確實(shí)接收到了a和b的值,但?x 的地址和 a 的地址不?樣,y 的地址和 b 的地址不?樣,相當(dāng)于x和y是獨(dú)立的空間。那么在Swap函數(shù)內(nèi)部交換 x 和 y 的值, 自然不會(huì)影響 a 和 b,當(dāng)Swap函數(shù)調(diào)?結(jié)束后回到main函數(shù),a和b的沒(méi)法交換。Swap函數(shù)在使用的時(shí)候,是把變量本?直接傳遞給了函數(shù),這種調(diào)?函數(shù)的?式就叫傳值調(diào)用。所以Swap是失敗的了……
得出結(jié)論:實(shí)參傳遞給形參的時(shí)候,形參會(huì)單獨(dú)創(chuàng)建?份臨時(shí)空間來(lái)接收實(shí)參,對(duì)形參的修改不影響實(shí)參。
正確示范:
上述代碼就成功的把兩整數(shù) a 和 b 交換了。這?調(diào)?Swap函數(shù)的時(shí)候是將變量的地址傳遞給了函數(shù),這種函數(shù)調(diào)用方式叫:傳址調(diào)?。
總結(jié):傳址調(diào)?,可以讓函數(shù)和主調(diào)函數(shù)之間建?真正的聯(lián)系,在函數(shù)內(nèi)部可以修改主調(diào)函數(shù)中的變量。所以未來(lái)在調(diào)用函數(shù)時(shí),函數(shù)中需要修改主調(diào)函數(shù)中的變量的值,就可以采?傳址調(diào)?。如果函數(shù)中只需要主調(diào)函數(shù)中的變量值來(lái)實(shí)現(xiàn)計(jì)算,就可以采用傳值調(diào)用。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-831568.html
以上就是個(gè)人的理解。由于本人水平有限,如有不足之處,懇請(qǐng)各位老師指出。謝謝!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-831568.html
到了這里,關(guān)于C語(yǔ)言——從頭開(kāi)始——深入理解指針(1)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!