C語(yǔ)言數(shù)組與指針的不同之處

數(shù)組并非指針

C編程新手最常聽(tīng)到的說(shuō)法之一就是“數(shù)組和指針是相同的”碧信。不幸的是耕蝉,這是一種非常危險(xiǎn)的說(shuō)法铐刘,并不完全正確歧杏。
ANSI C標(biāo)準(zhǔn)6.5.4.2節(jié)建議:

注意以下聲明的區(qū)別:
extern int *x;
extern int y[];
第一條語(yǔ)句聲明x是個(gè)int型的指針镰惦;第二條語(yǔ)句聲明y是個(gè)int型數(shù)組,長(zhǎng)度尚未確定(不完整的類(lèi)型)犬绒,其存儲(chǔ)在別處定義旺入。

標(biāo)準(zhǔn)并沒(méi)有做更細(xì)的規(guī)定。許多C語(yǔ)言書(shū)籍對(duì)數(shù)組與指針何時(shí)相同凯力、何時(shí)不同含糊其辭茵瘾,對(duì)于這個(gè)應(yīng)該重點(diǎn)闡述的話題只是一帶而過(guò)。先看一個(gè)例子:

文件1:
int mango[100];

文件2:
extern int *mango;

這里咐鹤,文件1定義了數(shù)組mango拗秘,但文件2聲明它為指針。這有什么錯(cuò)誤嗎祈惶?無(wú)論如何雕旨,“每個(gè)人都知道”在C語(yǔ)言中扮匠,數(shù)組和指針?lè)浅O嗨啤?wèn)題在于“每個(gè)人”這種說(shuō)法是錯(cuò)誤的凡涩!這相當(dāng)于把整數(shù)和浮點(diǎn)數(shù)混為一談:

文件1:
int guava;

文件2:
extern float guava;

上面這個(gè)int和float的例子非常明顯棒搜,類(lèi)型不匹配,沒(méi)人會(huì)指望這樣的代碼能夠運(yùn)行活箕。但是為什么人們會(huì)認(rèn)為指針和數(shù)組始終應(yīng)該是可以互換的呢力麸?

答案是對(duì)數(shù)組的引用總是可以寫(xiě)成對(duì)指針的引用,而且確實(shí)存在一種指針和數(shù)組的定義完全相同的上下文環(huán)境育韩。

不幸的是克蚂,這只是數(shù)組的一種極為普通的用法,并非所有情況下都是如此筋讨。但是陨舱,人們卻自然而然的歸納并假定在所有的情況下數(shù)組和指針都是等同的,包括上面完全錯(cuò)誤的“數(shù)組定義等同于指針的外部聲明”這種情況版仔。

聲明與定義

在搞清這個(gè)問(wèn)題之前游盲,需要在頭腦里重新整理一些基本的C語(yǔ)言術(shù)語(yǔ)。記住蛮粮,C語(yǔ)言中的對(duì)象必須有且只有一個(gè)定義益缎,但它可以有多個(gè)extern聲明。這里所說(shuō)的對(duì)象跟C++中的對(duì)象并無(wú)關(guān)系然想,這里的對(duì)象只是跟鏈接器有關(guān)的東西莺奔,比如函數(shù)和變量。

定義是一種特殊的聲明变泄,它創(chuàng)建了一個(gè)對(duì)象令哟;聲明簡(jiǎn)單的說(shuō)明了在其他地方創(chuàng)建的對(duì)象的名字,它允許你使用這個(gè)名字妨蛹。

定義 只能出現(xiàn)在一個(gè)地方 確定對(duì)象的類(lèi)型并分配內(nèi)存屏富,用于創(chuàng)建新的對(duì)象。例如:int my_array[100];
聲明 可以多次出現(xiàn) 描述對(duì)象的類(lèi)型蛙卤,用于指代其他地方定義的對(duì)象(例如在其他文件里)例:extern int my_array[];

區(qū)分定義和聲明:
1.聲明相當(dāng)于普通的聲明:它所說(shuō)明的并非自身狠半,而是描述其他地方的創(chuàng)建的對(duì)象。
2.定義相當(dāng)于特殊的聲明:它為對(duì)象分配內(nèi)存颤难。

extern 對(duì)象聲明告訴編譯器對(duì)象的類(lèi)型和名字神年,對(duì)象的內(nèi)存分配則在別處進(jìn)行。由于并未在聲明中為數(shù)組分配內(nèi)存行嗤,所以并不需要提供關(guān)于數(shù)組長(zhǎng)度的信息已日。對(duì)于多維數(shù)組,需要提供除最左邊一維之外其他維的長(zhǎng)度——這就給編譯器足夠的信息產(chǎn)生相應(yīng)的代碼栅屏。

對(duì)數(shù)組和對(duì)指針的引用的不同之處

首先需要注意的是“地址y”和“地址y的內(nèi)容”之間的區(qū)別飘千。這是一個(gè)相當(dāng)微妙之處堂鲜,因?yàn)樵诖蠖鄶?shù)編程語(yǔ)言中我們用同一個(gè)符號(hào)來(lái)表示這兩樣?xùn)|西,由編譯器根據(jù)上下文環(huán)境判斷它的具體含義占婉。

以一個(gè)簡(jiǎn)單的賦值為例:

x = y;
x = y
在這個(gè)上下文環(huán)境里泡嘴,符號(hào)x的含義是x所代表的地址甫恩。 在這個(gè)上下文里逆济,符號(hào)y的含義是y所代表的地址的內(nèi)容。
這被稱為左值 這被稱為右值
左值在編譯時(shí)可知磺箕,左值表示存儲(chǔ)結(jié)果的地方奖慌。 右值直到運(yùn)行時(shí)才知。如無(wú)特別說(shuō)明松靡,右值表示“y的內(nèi)容”简僧。

c語(yǔ)言引入“可修改的左值”這個(gè)術(shù)語(yǔ)。
它表示左值允許出現(xiàn)在賦值語(yǔ)句的左邊雕欺,這個(gè)奇怪的術(shù)語(yǔ)是為了與數(shù)組名區(qū)分岛马。
數(shù)組名也用于確定對(duì)象在內(nèi)存中的位置,也是左值屠列,但它不能作為賦值的對(duì)象啦逆。因此,數(shù)組名是個(gè)左值但不是可修改的左值笛洛。
標(biāo)準(zhǔn)規(guī)定賦值符必須用可修改的左值作為它左邊一側(cè)的操作數(shù)夏志。
用通俗的話說(shuō),只能給可以修改的東西賦值苛让。

編譯器為每個(gè)變量分配一個(gè)地址(左值)沟蔑,這個(gè)地址在編譯時(shí)可知,而且該變量在運(yùn)行時(shí)一直保存于這個(gè)地址狱杰。存儲(chǔ)于變量中的值(右值)只有在運(yùn)行時(shí)才可知瘦材。如果需要用到變量中存儲(chǔ)的值,編譯器就發(fā)出指令從指定地址讀入變量值并將它存于寄存器中仿畸。

這里的關(guān)鍵之處在于每個(gè)符號(hào)的地址在編譯時(shí)可知宇色。所以,如果編譯器需要一個(gè)地址(可能還需要加上偏移量)來(lái)執(zhí)行某種操作颁湖,它就可以直接進(jìn)行操作宣蠕,并不需要增加指令首先取得具體的地址。相反甥捺,對(duì)于指針抢蚀,必須首先在運(yùn)行時(shí)取得它的當(dāng)前值,然后才能對(duì)它進(jìn)行解除引用操作(作為以后進(jìn)行查找的步驟之一镰禾。)

1.對(duì)數(shù)組進(jìn)行下標(biāo)引用的步驟:

char a[9] = "abcdefgh";
...
c = a[i];

首先皿曲,編譯器符號(hào)表有數(shù)組a的地址唱逢,假設(shè)為9980;
運(yùn)行時(shí)步驟1:取i的值屋休,將它與9980相加(獲得a[i]對(duì)應(yīng)的偏移地址)
運(yùn)行時(shí)步驟2:取地址(9980+i)的內(nèi)容坞古。

這就是為什么extern char a[]與extern char a[100]等價(jià)的原因。這兩個(gè)聲明都提示a是一個(gè)數(shù)組劫樟,也就是一個(gè)內(nèi)存地址痪枫,數(shù)組內(nèi)的字符可以從這個(gè)地址找到。編譯器并不需要知道數(shù)組總共有多長(zhǎng)叠艳,因?yàn)閿?shù)組長(zhǎng)度只用于表示偏離起始地址的最大偏移量奶陈。從數(shù)組提取一個(gè)字符,只要簡(jiǎn)單的用符號(hào)表里a的地址加上下標(biāo)的偏移量附较,所需要的字符就位于這個(gè)地址中吃粒。

如果聲明extern char *p,它將告訴編譯器p是一個(gè)指針拒课,它指向的對(duì)象是一個(gè)字符徐勃。為了取得這個(gè)字符,必須得到地址p的內(nèi)容早像,把內(nèi)容再作為字符的地址僻肖,并從這個(gè)地址中取得這個(gè)字符。指針的訪問(wèn)要靈活的多扎酷,但需要增加一次額外的提取檐涝。

2.對(duì)指針的引用的步驟:

char *p;
...
c = *p;

首先,編譯器有符號(hào)p的地址法挨,假設(shè)為4624谁榜;
運(yùn)行時(shí)步驟1:取地址4624的內(nèi)容,為5081凡纳;
運(yùn)行時(shí)步驟2:取地址5081的內(nèi)容窃植。

3.對(duì)指針進(jìn)行下標(biāo)引用的步驟:

char *p = "abcdefgh";
...
c = p[i];

首先,編譯器符號(hào)表有一個(gè)p荐糜,假設(shè)地址為4624巷怜;
運(yùn)行時(shí)步驟1:取地址4624的內(nèi)容,為5081暴氏;
運(yùn)行時(shí)步驟2:取得i的值延塑,并將它與5081相加;
運(yùn)行時(shí)步驟3:取地址(5081+i)的內(nèi)容答渔。

對(duì)照1关带、3的訪問(wèn)方式:

char a[] = "abcdefgh"; ... a[3];
char *p = "abcdefgh"; ... p[3];

在這兩種情況下,都可以取得字符‘d’沼撕,但兩者的途徑非常不一樣宋雏。

定義為指針芜飘,但以數(shù)組方式引用,編譯器將會(huì):
a.取得符號(hào)表中p的地址磨总,提取存儲(chǔ)于此處的指針嗦明。
b.把下標(biāo)所表示的偏移量與指針的值相加,產(chǎn)生一個(gè)地址蚪燕。
c.訪問(wèn)上面這個(gè)地址娶牌,取得字符。

編譯器已被告知p是一個(gè)指向字符的指針邻薯。p[i]表示”從p所指的地址開(kāi)始裙戏,前進(jìn)i步,每步都是一個(gè)字符(即每個(gè)元素的長(zhǎng)度為一個(gè)字節(jié))≌痃裕“如果是其他類(lèi)型的指針(如int或double)邪乍,其步長(zhǎng)(每步的字節(jié)數(shù))也各不相同。

既然把p聲明成指針雾袱,那么不管p原先定義為指針還是數(shù)組,都會(huì)按照上面所示的三個(gè)步驟進(jìn)行操作,但是只有當(dāng)p原來(lái)定義為指針時(shí)這個(gè)方法才是正確的寿羞。

定義為數(shù)組,但被聲明為指針的問(wèn)題

假設(shè)p原先的定義是char p[10]赂蠢,p在外部文件被聲明為extern char *p绪穆。
當(dāng)用p[i]這種形式提取這個(gè)聲明的指針p的內(nèi)容時(shí),實(shí)際上得到的是一個(gè)字符虱岂。但按照上面的方法玖院,編譯器卻把取到的字符當(dāng)成是一個(gè)地址,把ACSII字符解釋為地址顯然是牛頭不對(duì)馬嘴第岖,它很可能會(huì)污染程序地址空間的內(nèi)容难菌,并出現(xiàn)莫名其妙的錯(cuò)誤。

指針的外部聲明與數(shù)組定義不匹配的問(wèn)題很容易修正蔑滓,只要修改聲明郊酒,使之與定義相匹配即可,如下所示:

文件1:
int mango[100];

文件2:
extern int mango[];

mango數(shù)組的定義分配了100個(gè)int的空間键袱。
而指針定義 int *raisin燎窘;則申請(qǐng)一個(gè)地址容納該指針。
指針的名字是raisin蹄咖,它可以指向任何一個(gè)int變量(或int數(shù)組)褐健。
指針變量raisin本身始終位于同一個(gè)地址,但它的內(nèi)容在任何時(shí)候都可以不同比藻,指向不同地址的int變量铝量。這些不同的int變量可以有不同的值倘屹。
mango數(shù)組的地址并不能改變,在不同的時(shí)候它的內(nèi)容可以不同慢叨,但它總是表示100個(gè)連續(xù)的內(nèi)存空間纽匙。

數(shù)組和指針的其他區(qū)別

比較數(shù)組和指針的另外一個(gè)方法就是比對(duì)兩者的特點(diǎn)。

指針 數(shù)組
保存數(shù)據(jù)地址 保存數(shù)據(jù)
間接訪問(wèn)數(shù)據(jù)拍谐,首先取得指針的內(nèi)容烛缔,把它作為地址,然后從這個(gè)地址提取數(shù)據(jù)轩拨。如果指針有一個(gè)下標(biāo)[i]践瓷,就把指針的內(nèi)容加上i作為地址,從中提取數(shù)據(jù) 直接訪問(wèn)數(shù)據(jù)亡蓉,a[i]只是簡(jiǎn)單的以a+i為地址取得數(shù)據(jù)晕翠。
通常用于動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu) 通常用于存儲(chǔ)固定數(shù)目且數(shù)據(jù)類(lèi)型相同的元素
相關(guān)的函數(shù)為malloc()、free() 隱式分配和刪除
通常指向匿名數(shù)據(jù) 自身即為數(shù)據(jù)名

數(shù)組和指針都可以在它們的定義中用字符串常量進(jìn)行初始化砍濒。盡管看上去一樣淋肾,底層的機(jī)制卻不相同。
定義指針時(shí)爸邢,編譯器并不為指針?biāo)赶虻膶?duì)象分配空間樊卓,它只分配指針本身的空間,除非在定義時(shí)同時(shí)賦給指針一個(gè)字符串常量進(jìn)行初始化杠河。例如碌尔,下面的定義創(chuàng)建了一個(gè)字符串常量(為其分配了內(nèi)存):

char *p = "breadfruit";

注意只有對(duì)字符串常量才是如此。不能指望為浮點(diǎn)數(shù)之類(lèi)的常量分配空間券敌,如:

float *pip = 3.141唾戚;//錯(cuò)誤!無(wú)法通過(guò)編譯陪白。

在ANSI C中颈走,初始化指針時(shí)所創(chuàng)建的字符串常量被定義為只讀。如果試圖通過(guò)指針修改這個(gè)字符串的值咱士,程序就會(huì)出現(xiàn)未定義的行為立由。在有些編譯器中,字符串常量被存放在只允許讀取的文本段中序厉,以防止它被修改锐膜。

數(shù)組也可以用字符串常量進(jìn)行初始化:

char a[] = "gooseberry";

與指針不同,由字符串常量初始化的數(shù)組是可以修改的弛房。其中的單個(gè)字符在以后可以改變道盏,比如下面的語(yǔ)句:

strncpy(a, "black", 5);

就將數(shù)組的值修改為"blackberry"。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市荷逞,隨后出現(xiàn)的幾起案子媒咳,更是在濱河造成了極大的恐慌,老刑警劉巖种远,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涩澡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坠敷,警方通過(guò)查閱死者的電腦和手機(jī)妙同,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)膝迎,“玉大人粥帚,你說(shuō)我怎么就攤上這事∠薮危” “怎么了芒涡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掂恕。 經(jīng)常有香客問(wèn)我拖陆,道長(zhǎng)弛槐,這世上最難降的妖魔是什么懊亡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮乎串,結(jié)果婚禮上店枣,老公的妹妹穿的比我還像新娘。我一直安慰自己叹誉,他們只是感情好鸯两,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著长豁,像睡著了一般钧唐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匠襟,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天钝侠,我揣著相機(jī)與錄音,去河邊找鬼酸舍。 笑死帅韧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的啃勉。 我是一名探鬼主播忽舟,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了叮阅?” 一聲冷哼從身側(cè)響起刁品,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浩姥,沒(méi)想到半個(gè)月后哑诊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡及刻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年镀裤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缴饭。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暑劝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颗搂,到底是詐尸還是另有隱情担猛,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布丢氢,位于F島的核電站傅联,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疚察。R本人自食惡果不足惜蒸走,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望貌嫡。 院中可真熱鬧比驻,春花似錦、人聲如沸岛抄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)夫椭。三九已至掸掸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹭秋,已是汗流浹背扰付。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留感凤,地道東北人悯周。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像陪竿,于是被迫代替她去往敵國(guó)和親禽翼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屠橄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349