【零基礎(chǔ)學(xué)C語言】知識總結(jié)十:指針及其相關(guān)知識(一)
指針也就是內(nèi)存地址,指針變量是用來存放內(nèi)存地址的變量,不同類型的指針變量所占用的存儲單元長度是相同的,而存放數(shù)據(jù)的變量因數(shù)據(jù)的類型不同,所占用的存儲空間長度也不同。有了指針以后,不僅可以對數(shù)據(jù)本身,也可以對存儲數(shù)據(jù)的變量地址進行操作。
計算機中所有的數(shù)據(jù)都必須放在內(nèi)存中,不同類型的數(shù)據(jù)占用的字節(jié)數(shù)不一樣,例如 int 占用 4 個字節(jié),char 占用 1 個字節(jié)。為了正確地訪問這些數(shù)據(jù),必須為每個字節(jié)都編上號碼,就像門牌號、身份證號一樣,每個字節(jié)的編號是唯一的,根據(jù)編號可以準(zhǔn)確地找到某個字節(jié)。

在計算機中, 所有的數(shù)據(jù)都是存放在存儲器中的, 不同的數(shù)據(jù)類型占有的內(nèi)存空間的大小各不相同。內(nèi)存是以字節(jié)為單位的連續(xù)編址空間, 每一個字節(jié)單元對應(yīng)著一個唯一的編號, 這個編號被稱為內(nèi)存單元的地址。比如: int類型占兩個字節(jié), char類型占1個字節(jié)等。內(nèi)存為變量分配存儲空間的首個字節(jié)單元的地址, 稱之為該變量的地址。地址用來標(biāo)識每一個存儲單元, 方便用戶對存儲單元中的數(shù)據(jù)進行正確的訪問。在高級語言中地址形象地稱為指針,? CPU 訪問內(nèi)存時需要的是地址,而不是變量名和函數(shù)名!變量名和函數(shù)名只是地址的一種助記符,當(dāng)源文件被編譯和鏈接成可執(zhí)行程序后,它們都會被替換成地址。編譯和鏈接過程的一項重要任務(wù)就是找到這些名稱所對應(yīng)的地址。
需要注意的是 變量名和函數(shù)名為我們提供了方便,讓我們在編寫代碼的過程中可以使用易于閱讀和理解的英文字符串,不用直接面對二進制地址,那場景簡直讓人崩潰。雖然變量名、函數(shù)名、字符串名和數(shù)組名在本質(zhì)上是一樣的,它們都是地址的助記符,但在編寫代碼的過程中,我們認(rèn)為變量名表示的是數(shù)據(jù)本身,而函數(shù)名、字符串名和數(shù)組名表示的是代碼塊或數(shù)據(jù)塊的首地址。
指針相對于一個內(nèi)存單元來說,指的是單元的地址,該單元的內(nèi)容里面存放的是數(shù)據(jù)。在C語言中,允許用指針變量來存放指針,因此,一個指針變量的值就是某個內(nèi)存單元的地址或稱為某內(nèi)存單元的指針。

指針的定義與使用
變量的指針與指針變量:
在C語言中,允許用一個變量來存放指針,這種變量稱為指針變量。指針變量的值就是某份數(shù)據(jù)的地址,這樣的一份數(shù)據(jù)可以是數(shù)組、字符串、函數(shù),也可以是另外的一個普通變量或指針變量
變量的指針就是變量的存儲地址,指針變量就是存儲指針的變量。指針變量是存放一個變量地址的變量,不同于其他類型變量,它是專門用來存放內(nèi)存地址的,也稱為地址變量。定義指針變量的一般形式為:類型說明符 * 變量名 *
類型說明符表示指針變量所指向變量的數(shù)據(jù)類型;* 表示這是一個指針變量;變量名表示定義的指針變量名,其值是一個地址,例如:char * p1;表示p1是一個指針變量,它的值是某個字符變量的地址
? 指針變量的使用:
? ? ? 取地址運算符&:單目運算符&是用來取操作對象的地址。
例:&i 為取變量 i 的地址。對于常量表達(dá)式、寄存器變量不能取地址(因為它們存儲在存儲器中,沒有地址)。
指針運算符 * (間接尋址符):與&為逆運算,作用是通過操作對象的地址,獲取存儲的內(nèi)容。? ?
例:x = &i? ? ? x 為 i 的地址,*x 則為通過 i 的地址,獲取 i 的內(nèi)容。
和普通變量一樣,指針變量也可以被多次寫入,只要你想,隨時都能夠改變指針變量的值:
? * 是一個特殊符號,表明一個變量是指針變量,定義 p1、p2 時必須帶 *。而給 p1、p2 賦值時,因為已經(jīng)知道了它是一個指針變量,就沒必要多此一舉再帶上 *,后邊可以像使用普通變量一樣來使用指針變量。也就是說,定義指針變量時必須帶 *,給指針變量賦值時不能帶 *。
指針變量存儲了數(shù)據(jù)的地址,通過指針變量能夠獲得該地址上的數(shù)據(jù):
可以用指針來改變被指向那個變量的值 如:
也就是說,定義指針變量時的*和使用指針變量時的*意義完全不同。以下面的語句為例:
其他一些騷操作:
關(guān)于“&”和“*”
“&”和“ * ”都是右結(jié)合的。假設(shè)有變量 x = 10,則*&x 的含義是,先獲取變量 x 的地址,再獲取地址中的內(nèi)容。因為“&”和“ * ”互為逆運算,所以 x = *&x。
假設(shè)有一個 int 類型的變量 a,? pa 是指向a的指針,那么*&a和&*pa分別是什么意思呢?
*&a可以理解為*(&a),&a表示取變量 a 的地址(等價于 pa),*(&a)表示取這個地址上的數(shù)據(jù)(等價? ? ? ? ? ? ? * pa),繞來繞去,又回到了原點,*&a仍然等價于 a。
&*pa可以理解為&(*pa),*pa表示取得 pa 指向的數(shù)據(jù)(等價于 a),&(*pa)表示數(shù)據(jù)的地址(等價于 &a),所以&*pa等價于 pa。
野指針與空指針
空指針是說,這個指針沒有指向一塊有意義的內(nèi)存,比如說:char* k;? 這里這個k就叫空指針.我們并未讓它指向任意點.
又或者char* k = NULL;? 這里這個k也叫空指針,因為它指向NULL 也就是0,注意是整數(shù)0,不是'\0'
一個空指針我們也無法對它進行取內(nèi)容操作,空指針只有在真正指向了一塊有意義的內(nèi)存后,我們才能對它取內(nèi)容.也就是說要這樣? k = "hello world!";? 這時k就不是空指針了.
對于空指針值,一般的文檔中傾向于用 NULL 表示,而沒有直接說成 0。但是我們應(yīng)該清楚:對于指針類型來說,返回 NULL 和 返回 0 是完全等價的,因為 NULL 和 0 都表示 “null pointer”(空指針)。一句話, 空指針是什么,就是一個被賦值為0的指針,在沒有被具體初始化之前,其值為0.(百度解釋)
如 :
注意:void* 這不叫空指針,這叫無確切類型指針.這個指針指向一塊內(nèi)存,卻沒有告訴程序該用何種方式來解釋這片內(nèi)存.所以這種類型的指針不能直接進行取內(nèi)容的操作.必須先轉(zhuǎn)成別的類型的指針才可以把內(nèi)容解釋出來.
還有'\0',這也不是空指針?biāo)傅膬?nèi)容. '\0'是表示一個字符串的結(jié)尾而已,并不是NULL的意思
void*因為是表示不知道要指向什么東西的指針,計算時于char相同(但不相通)
? ? ? ? 野指針不同于空指針,空指針是指一個指針的值為null,而野指針的值并不為null,野指針會指向一段實際的內(nèi)存,只是它指向哪里我們并不知情,或者是它所指向的內(nèi)存空間已經(jīng)被釋放,所以在實際使用的過程中,我們并不能通過指針判空去識別一個指針是否為野指針。避免野指針只能靠我們自己養(yǎng)成良好的編程習(xí)慣
? ? ? 野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)指針變量在定義時如果未初始化,其值是隨機的,指針變量的值是別的變量的地址,意味著指針指向了一個地址是不確定的變量,此時去解引用就是去訪問了一個不確定的地址,所以結(jié)果是不可知的。(百度解釋)
下面說說哪些情況下會產(chǎn)生野指針,以及怎樣避免。
1、指針變量的值未被初始化: 聲明一個指針的時候,沒有顯示的對其進行初始化,那么該指針?biāo)赶虻牡刂房臻g是亂指一氣的。如果指針聲明在全局?jǐn)?shù)據(jù)區(qū),那么未初始化的指針缺省為空,如果指針聲明在棧區(qū),那么該指針會隨意指向一個地址空間。所以良好的編程習(xí)慣就是在聲明指針的時候就對其進行初始化,如果暫時不知道該初始化成什么值,就先把指針置空。
2、指針?biāo)赶虻牡刂房臻g已經(jīng)被free或delete:在堆上malloc或者new出來的地址空間,如果已經(jīng)free或delete,那么此時堆上的內(nèi)存已經(jīng)被釋放,但是指向該內(nèi)存的指針如果沒有人為的修改過,那么指針還會繼續(xù)指向這段堆上已經(jīng)被釋放的內(nèi)存,這時還通過該指針去訪問堆上的內(nèi)存,就會造成不可預(yù)知的結(jié)果,給程序帶來隱患,所以良好的編程習(xí)慣是:內(nèi)存被free或delete后,指向該內(nèi)存的指針馬上置空。
3、指針操作超越了作用域,如果在一個程序塊中讓一個指針指向那個塊中的某個變量,但是那個變量只是在塊中有效,出了那個程序塊,此變量就無效了,此時指向它的指針也就變成了野指針
所以 使用指針時應(yīng)當(dāng)注意”規(guī)避“:初始化時置 NULL,釋放時置 NULL
3、指針的運算
C 指針是一個用數(shù)值表示的地址。因此,您可以對指針執(zhí)行算術(shù)運算??梢詫χ羔樳M行四種算術(shù)運算:++、--、+、-。遞增遞減加減,兩個指針相減
常見的指針運算:
*(++p): 先移動指針,取下一個單元的值
*(p++): 先取出當(dāng)前單元的值,再移動指針
( * p)++ : 先取出當(dāng)前單元的值,再使當(dāng)前單元的值加1? (指針不移動)
++( * p) : 先使當(dāng)前單元的值加1,再取出當(dāng)前單元的值? (指針不移動)
指針的類型轉(zhuǎn)換:
int *p=&i;
void *q=(void * )p;
這并沒有改變p所指向的變量的類型,而是讓后人用不同的眼光通過p看它所指的變量
意思是:這里的p我不再當(dāng)你是int了,認(rèn)為你就是個void類型的
注意 由于優(yōu)先級的問題? *p++和 * (p++)是等價的
取地址符 &補充:
獲得變量的地址,它的操作必須是變量,
int i,printf("%x",&i); 取得i的地址并輸出。
int i,printf("%p",&i); 取得i的地址并輸出。
地址的大小是否于int相同取決于編譯器
注意? 使用指針的時候的類型,無論指向什么類型,所有的指針的大小都是一樣的,因為都是地址,但是指向不同類型的指針是不能相互賦值的,這是為了避免用錯指針。

作者:Mr_Li_
對啦對啦!另外的話為了幫助大家,輕松,高效學(xué)習(xí)C語言/C++,我給大家分享我收集的資源,從最零基礎(chǔ)開始的教程到C語言項目案例,幫助大家在學(xué)習(xí)C語言的道路上披荊斬棘!可以來我粉絲群領(lǐng)取哦~

編程學(xué)習(xí)書籍分享:

