本節(jié)來解決如何給一個指針變量初始化。即怎樣使一個指針變量指向另一個變量椎麦。
前面章節(jié)中的某些程序?qū)嶋H上已經(jīng)使用了,即可以用賦值語句使一個指針變量得到另一個變量的地址材彪,從而使它指向該變量观挎。比如:
inti,*j;
j=&i;
這樣就將變量 i 的地址放到了指針變量 j 中,通過 i 的地址段化,j 就能找到 i 中的數(shù)據(jù)嘁捷,所以 j 就“指向”了變量 i。其中 & 是“取地址運算符”显熏,與 scanf 中的 & 是一樣的概念雄嚣;* 為“指針運算符”,功能是取其內(nèi)部所存變量地址所指向變量中的內(nèi)容喘蟆。因為 j 是定義成指針型變量缓升,所以 j 中只能存放變量的地址,所以變量i前一定要加 &蕴轨。需要注意的是港谊,指針變量中只能存放地址,不要將一個整數(shù)或任何其他非地址類型的數(shù)據(jù)賦給一個指針變量橙弱。
此外歧寺,還有兩點需要注意:
j 不是 i,i 也不是 j棘脐。修改j的值不會影響i的值斜筐,修改 i 的值也不會影響 j 的值。j 是變量 i 的地址蛀缝,而 i 是變量 i 里面的數(shù)據(jù)奴艾。一個是“內(nèi)存單元的地址”,另一個是“內(nèi)存單元的內(nèi)容”内斯。
定義指針變量時的“*j”和程序中用到的“*j”含義不同蕴潦。定義指針變量時的“*j”只是一個聲明,此時的“*”僅表示該變量是一個指針變量俘闯,并沒有其他含義潭苞。而且此時該指針變量并未指向任何一個變量,至于具體指向哪個變量要在程序中指定真朗,即給指針變量初始化此疹。而當(dāng)指定 j 指向變量 i 之后,*j 就完全等同于 i 了,可以相互替換蝗碎。
下面給大家寫一個程序:
# include<stdio.h>
intmain(void)
{
inti=3,*j;//*j表示定義了一個指針變量j
j=&i;
printf("*j = %d\n",*j);//此時*j完全等同于i
printf("j = %d\n",j);//j里面存儲的是變量i的地址
return0;
}
輸出結(jié)果是:
*j = 3
j = 1245052
下面再將上面這個程序修改一下:
# include<stdio.h>
intmain(void)
{
inti=3;
int*j=&i;//*j表示定義了一個指針變量j, 并將變量i的地址賦給它
printf("*j = %d\n",*j);//此時*j完全等同于i
printf("j = %d\n",j);//j里面存儲的是變量i的地址
return0;
}
輸出結(jié)果是:
*j = 3
j = 1245052
這個程序與第一個程序有什么不同湖笨?同樣是將變量 i 的地址賦給指針變量 j,第一個程序是“j=&i蹦骑;”慈省,而第二個程序是“*j=&i;”眠菇。原因是边败,前者是定義指針變量后對它初始化,即先定義后初始化捎废;而后者是定義指針變量時對它進行初始化笑窜,即定義時初始化。通過這個對比我們可以更鮮明地看出定義指針變量時的“*j”和程序中用到的“*j”含義的不同登疗。
那么指針變量和指針變量之間可不可以相互賦值呢排截?我們看看下面這個程序:
# include<stdio.h>
intmain(void)
{
int*i,*j;
intk=3;
i=&k;
j=i;//直接指針變量名之間進行賦值
printf("*j = %d\n",*j);//此時*j完全等同于k
printf("j = %d\n",j);// j里面存儲的是變量k的地址
return0;
}
輸出結(jié)果是:
*j = 3
j = 1245044
可見,可以直接將一個指針變量賦給另一個指針變量辐益,只要將指針變量名賦給另一個指針變量名即可断傲。但是需要注意的是:
這兩個指針變量的基類型一定要相同。
在賦值之前荷腊,賦值運算符“=”右邊的指針變量必須是已經(jīng)初始化過的艳悔。也就是說,切忌將一個沒有初始化的指針變量賦給另一個指針變量女仰。這是非常嚴(yán)重的語法錯誤猜年。
同樣,也可以在定義指針變量時就給它賦初值:
# include<stdio.h>
intmain(void)
{
intk=3;
int*i=&k;
int*j=i;
printf("*j = %d\n",*j);//此時*j完全等同于k
printf("j = %d\n",j);//j里面存儲的是變量k的地址
return0;
}
輸出結(jié)果是:
*j = 3
j = 1245048
注意疾忍,“int*j=i乔外;”千萬不要寫成“int*j=*i;”一罩。因為此時 *i 不是定義指針變量 i杨幼,而是完全等同于變量 k。所以 int 型變量不能賦給 int* 型的變量聂渊。
指針常見錯誤
1) 引用未初始化的指針變量
試圖引用未初始化的指針變量是初學(xué)者最容易犯的錯誤差购。未初始化的指針變量就是“野”指針,它指向的是無效的地址汉嗽。
有些書上說:“如果指針變量不初始化欲逃,那么它可能指向內(nèi)存中的任何一個存儲單元,這樣就會很危險饼暑。如果正好指向存儲著重要數(shù)據(jù)的內(nèi)存單元稳析,而且又不小心向這個內(nèi)存單元中寫入了數(shù)據(jù)洗做,把原來的重要數(shù)據(jù)給覆蓋了,這樣就會導(dǎo)致系統(tǒng)崩潰彰居〕现剑”這種說法是不正確的!如果真是這樣的話就是編譯器的一個嚴(yán)重的 BUG陈惰!
編譯器的設(shè)計人員是不會允許這么大的 BUG 存在的畦徘。那么如果指針變量未初始化,編譯器的設(shè)計人員是如何處理這個問題的呢奴潘?肯定不可能讓它亂指旧烧。以VC++6.0這個編譯器為例影钉,如果指針變量未初始化画髓,那么編譯器會讓它指向一個固定的、不用的地址平委。下面來寫一個程序:
# include<stdio.h>
intmain(void)
{
int*p,*q;
printf("p = %#X\n",p);
printf("q = %#X\n",q);
return0;
}
輸出結(jié)果是:
p = 0XCCCCCCCC
q = 0XCCCCCCCC
可見奈虾,在 VC++6.0 中只要指針變量未初始化,那么編譯器就讓它指向 0XCCCCCCCC 這個內(nèi)存單元廉赔。而且這個內(nèi)存單元是程序所不能訪問的肉微,訪問就會觸發(fā)異常,所以也不怕往里面寫東西蜡塌。
而如果在 VS 2008 這個編譯器中碉纳,程序雖然能編譯通過,但是在運行的時候直接出錯馏艾,它并不會像 VC++6.0 那樣還能輸出所指向的內(nèi)存單元的地址劳曹。
下面來看一個程序:
# include<stdio.h>
intmain(void)
{
inti=3,*j;
*j=i;
return0;
}
程序中,j 是 int* 型的指針變量琅摩。j 中存放的應(yīng)該是內(nèi)存空間的地址铁孵,然后“變量 i 賦給 *j”表示將變量i中的值放到該地址所指向的內(nèi)存空間中。但是現(xiàn)在 j 中并沒有存放一個地址房资,程序中并沒有給它初始化蜕劝,那么它指向的就是 0XCCCCCCCC 這個內(nèi)存單元。這個內(nèi)存單元是不允許訪問的轰异,即不允許往里面寫數(shù)據(jù)岖沛。而把 i 賦給 *j 就是試圖往這個內(nèi)存空間中寫數(shù)據(jù),程序執(zhí)行時就會出錯搭独。但這種錯誤在編譯的時候并不會報錯婴削,只有在執(zhí)行的時候才會出錯,即傳說中的“段錯誤”戳稽。所以馆蠕,一定要確保指針變量在引用之前已經(jīng)被初始化為指向有效的地址期升。
在實際編程中,這種錯誤常見的另一個地方是用 scanf 給指針變量所指向的內(nèi)存單元賦值互躬。我們看看下面這個程序:
# include<stdio.h>
intmain(void)
{
int*i;
scanf("%d",i);
return0;
}
該程序試圖給指針變量 i 所指向的內(nèi)存單元賦值播赁。但現(xiàn)在指針變量 i 并沒有初始化,所以程序執(zhí)行時出錯吼渡。所以同樣容为,在使用 scanf 時必須要先給指針變量 i 初始化。比如像下面這樣寫:
# include<stdio.h>
intmain(void)
{
int*i,j;
i=&j;//先給指針變量i初始化
scanf("%d",i);//i本身就是地址, 所以不用加&
printf("%d\n",*i);
return0;
}
輸出結(jié)果是:
10
10
能不能使用 scanf 給指針變量初始化寺酪?指針變量里面存放的是地址坎背,而內(nèi)存中有數(shù)不清的單元,每個單元都有一個地址寄雀,你知道每個單元的地址嗎得滤?你知道哪些地址是空閑可用的,而哪些地址正存儲著重要數(shù)據(jù)不能用嗎盒犹?不知道的話怎么用scanf給它初始化呢懂更?萬一隨便寫一個地址正好是存儲著非常重要的數(shù)據(jù)的內(nèi)存單元地址,那系統(tǒng)就真的崩潰了急膀!
隨著大家編程能力的不斷提高沮协,慢慢地就會發(fā)現(xiàn),其實編程最重要卓嫂、最核心的就是如何處理內(nèi)存的問題慷暂,如何與內(nèi)存打交道。
2) 往一個存放NULL地址的指針變量里面寫入數(shù)據(jù)
這也是編程中最容易犯的錯誤晨雳,不僅是初學(xué)編程的行瑞,即使是有一些經(jīng)驗的程序員也會不小心犯這個錯誤。我們把前面的程序改一下:
# include<stdio.h>
intmain(void)
{
inti=3;
int*j=NULL;
*j=i;
return0;
}
之前是沒有給指針變量j初始化悍募,現(xiàn)在初始化了蘑辑,但是將它初始化為指向 NULL。NULL 也是一個指針變量坠宴。NULL 指向的是內(nèi)存中地址為 0 的內(nèi)存空間洋魂。以 32 位操作系統(tǒng)為例,內(nèi)存單元地址的范圍為 0x00000000~0xffff ffff喜鼓。其中 0x00000000 就是 NULL 所指向的內(nèi)存單元的地址副砍。但是在操作系統(tǒng)中,該內(nèi)存單元是不可用的庄岖。凡是試圖往該內(nèi)存單元中寫入數(shù)據(jù)的操作都會被視為非法操作豁翎,從而導(dǎo)致程序錯誤。同樣隅忿,這種錯誤在編譯的時候也不會報錯心剥,只有在執(zhí)行的時候才會出錯邦尊。這種錯誤也屬于“段錯誤”。
然而雖然這么寫是錯誤的优烧,但是將一個指針變量初始化為指向 NULL蝉揍,這在實際編程中是經(jīng)常使用的。就跟前面講普通變量在定義時給它初始化為 0 一樣畦娄,指針變量如果在定義時不知道指向哪里就將其初始化為指向 NULL又沾。只是此時要注意的是,在該指針變量指向有效地址之前不要往該地址中寫入數(shù)據(jù)熙卡。也就是說杖刷,該指針變量還要二次賦值。
既然不能往里面寫數(shù)據(jù)驳癌,而且還容易犯錯滑燃,為什么還要這樣給它初始化呢?直接同前面定義普通變量時一樣喂柒,在定義時也不初始化不瓶,等到后面知道該給它賦什么值時再給它賦值不行嗎禾嫉?可以灾杰!但還是建議大家將它初始化為 NULL,就同前面將普通變量在定義時初始化為 0 一樣熙参。這是很好的一種編程習(xí)慣艳吠。
最后關(guān)于 NULL 再補充一點,NULL 是定義在 stdio.h 頭文件中的符號常量孽椰,它表示的值是 0昭娩。