笨辦法學(xué)C 練習(xí)15:指針施逾,可怕的指針

練習(xí)15:指針敷矫,可怕的指針

原文:Exercise 15: Pointers Dreaded Pointers

譯者:飛龍

指針是C中的一個著名的謎之特性,我會試著通過教授你一些用于處理它們的詞匯汉额,使之去神秘化曹仗。指針實際上并不復(fù)雜,只不過它們經(jīng)常以一些奇怪的方式被濫用蠕搜,這樣使它們變得難以使用怎茫。如果你避免這些愚蠢的方法來使用指針,你會發(fā)現(xiàn)它們難以置信的簡單妓灌。

要想以一種我們可以談?wù)摰姆绞絹碇v解指針轨蛤,我會編寫一個無意義的程序蜜宪,它以三種方式打印了一組人的年齡:

#include <stdio.h>

int main(int argc, char *argv[])
{
    // create two arrays we care about
    int ages[] = {23, 43, 12, 89, 2};
    char *names[] = {
        "Alan", "Frank",
        "Mary", "John", "Lisa"
    };

    // safely get the size of ages
    int count = sizeof(ages) / sizeof(int);
    int i = 0;

    // first way using indexing
    for(i = 0; i < count; i++) {
        printf("%s has %d years alive.\n",
                names[i], ages[i]);
    }

    printf("---\n");

    // setup the pointers to the start of the arrays
    int *cur_age = ages;
    char **cur_name = names;

    // second way using pointers
    for(i = 0; i < count; i++) {
        printf("%s is %d years old.\n",
                *(cur_name+i), *(cur_age+i));
    }

    printf("---\n");

    // third way, pointers are just arrays
    for(i = 0; i < count; i++) {
        printf("%s is %d years old again.\n",
                cur_name[i], cur_age[i]);
    }

    printf("---\n");

    // fourth way with pointers in a stupid complex way
    for(cur_name = names, cur_age = ages;
            (cur_age - ages) < count;
            cur_name++, cur_age++)
    {
        printf("%s lived %d years so far.\n",
                *cur_name, *cur_age);
    }

    return 0;
}

在解釋指針如何工作之前,讓我們逐行分解這個程序祥山,這樣你可以對發(fā)生了什么有所了解圃验。當你瀏覽這個詳細說明時,試著自己在紙上回答問題枪蘑,之后看看你猜測的結(jié)果符合我對指針的描述损谦。

ex15.c:6-10

創(chuàng)建了兩個數(shù)組,ages儲存了一些int數(shù)據(jù)岳颇,names儲存了一個字符串數(shù)組照捡。

ex15.c:12-13

為之后的for循環(huán)創(chuàng)建了一些變量。

ex15.c:16-19

你知道這只是遍歷了兩個數(shù)組话侧,并且打印出每個人的年齡栗精。它使用了i來對數(shù)組索引。

ex15.c:24

創(chuàng)建了一個指向ages的指針瞻鹏。注意int *創(chuàng)建“指向整數(shù)的指針”的指針類型的用法悲立。它很像char *,意義是“指向字符的指針”新博,而且字符串是字符的數(shù)組薪夕。是不是很相似呢?

ex15.c:25

創(chuàng)建了指向names的指針赫悄。char *已經(jīng)是“指向char的指針”了规哲,所以它只是個字符串败去。你需要兩個層級,因為names是二維的,也就是說你需要char **作為“指向‘指向字符的指針’的指針”秃症。把它學(xué)會陨献,并且自己解釋它寸士。

ex15.c:28-31

遍歷agesnames谜诫,但是使用“指針加偏移i”。*(cur_name+i)name[i]是一樣的痪蝇,你應(yīng)該把它讀作“‘cur_name指針加i’的值”鄙陡。

ex15.c:35-39

這里展示了訪問數(shù)組元素的語法和指針是相同的。

ex15.c:44-50

另一個十分愚蠢的循環(huán)和其它兩個循環(huán)做著相同的事情躏啰,但是它用了各種指針算術(shù)運算來代替:

ex15.c:44

通過將cur_namecur_age置為namesage數(shù)組的起始位置來初始化for循環(huán)柔吼。

ex15.c:45

for循環(huán)的測試部分比較cur_age指針和ages起始位置的距離,為什么可以這樣寫呢丙唧?

ex15.c:46

for循環(huán)的增加部分增加了cur_namecur_age的值愈魏,這樣它們可以只想namesages的下一個元素。

ex15.c:48-49

cur_namecur_age的值現(xiàn)在指向了相應(yīng)數(shù)組中的一個元素,我們我可以通過*cur_name*cur_age來打印它們培漏,這里的意思是“cur_namecur_age指向的值”溪厘。

這個看似簡單的程序卻包含了大量的信息,其目的是在我向你講解之前嘗試讓你自己弄清楚指針牌柄。直到你寫下你認為指針做了什么之前畸悬,不要往下閱讀。

你會看到什么

在你運行這個程序之后珊佣,嘗試根據(jù)打印出的每一行追溯到代碼中產(chǎn)生它們的那一行蹋宦。在必要情況下,修改printf調(diào)用來確認你得到了正確的行號:

$ make ex15
cc -Wall -g    ex15.c   -o ex15
$ ./ex15
Alan has 23 years alive.
Frank has 43 years alive.
Mary has 12 years alive.
John has 89 years alive.
Lisa has 2 years alive.
---
Alan is 23 years old.
Frank is 43 years old.
Mary is 12 years old.
John is 89 years old.
Lisa is 2 years old.
---
Alan is 23 years old again.
Frank is 43 years old again.
Mary is 12 years old again.
John is 89 years old again.
Lisa is 2 years old again.
---
Alan lived 23 years so far.
Frank lived 43 years so far.
Mary lived 12 years so far.
John lived 89 years so far.
Lisa lived 2 years so far.
$

解釋指針

當你寫下一些類似ages[i]的東西時咒锻,你實際上在用i中的數(shù)字來索引ages冷冗。如果i的值為0,那么就等同于寫下ages[0]惑艇。我們把i叫做下標蒿辙,因為它是ages中的一個位置。它也能稱為地址滨巴,這是“我想要ages位于地址i處的整數(shù)”中的說法思灌。

如果i是個下標,那么ages又是什么恭取?對C來說ages是在計算機中那些整數(shù)的起始位置泰偿。當然它也是個地址,C編譯器會把任何你鍵入ages的地方替換為數(shù)組中第一個整數(shù)的地址蜈垮。另一個理解它的辦法就是把ages當作“數(shù)組內(nèi)部第一個整數(shù)的地址”甜奄,但是它是整個計算機中的地址,而不是像i一樣的ages中的地址窃款。ages數(shù)組的名字在計算機中實際上是個地址。

這就產(chǎn)生了一種特定的實現(xiàn):C把你的計算機看成一個龐大的字節(jié)數(shù)組牍氛。顯然這樣不會有什么用處晨继,于是C就在它的基礎(chǔ)上構(gòu)建出類型和大小的概念。你已經(jīng)在前面的練習(xí)中看到了它是如何工作的搬俊,但現(xiàn)在你可以開始了解C對你的數(shù)組做了下面一些事情:

  • 在你的計算機中開辟一塊內(nèi)存紊扬。
  • ages這個名字“指向”它的起始位置。
  • 通過選取ages作為基址唉擂,并且獲取位置為i的元素餐屎,來對內(nèi)存塊進行索引。
  • ages+i處的元素轉(zhuǎn)換成大小正確的有效的int玩祟,這樣就返回了你想要的結(jié)果:下標i處的int腹缩。

如果你可以選取ages作為基址,之后加上比如i的另一個地址,你是否就能隨時構(gòu)造出指向這一地址的指針呢藏鹊?是的润讥,這種東西就叫做指針。這也是cur_agecur_name所做的事情盘寡,它們是指向計算機中這一位置的變量楚殿,agesnames就處于這一位置。之后竿痰,示例程序移動它們脆粥,或者做了一些算數(shù)運算,來從內(nèi)存中獲取值影涉。在其中一個實例中变隔,只是簡單地將cur_age加上i,這樣等同于array[i]常潮。在最后一個for循環(huán)中弟胀,這兩個指針在沒有i輔助的情況下自己移動,被當做數(shù)組基址和整數(shù)偏移合并到一起的組合喊式。

指針僅僅是指向計算機中的某個地址孵户,并帶有類型限定符,所以你可以通過它得到正確大小的數(shù)據(jù)岔留。它類似于將agesi組合為一個數(shù)據(jù)類型的東西夏哭。C了解指針指向什么地方,所指向的數(shù)據(jù)類型献联,這些類型的大小竖配,以及如何為你獲取數(shù)據(jù)。你可以像i一樣增加它們里逆,減少它們进胯,對他們做加減運算。然而它們也像是ages原押,你可以通過它獲取值胁镐,放入新的值,或執(zhí)行全部的數(shù)組操作诸衔。

指針的用途就是讓你手動對內(nèi)存塊進行索引盯漂,一些情況下數(shù)組并不能做到。絕大多數(shù)情況中笨农,你可能打算使用數(shù)組就缆,但是一些處理原始內(nèi)存塊的情況,是指針的用武之地谒亦。指針向你提供了原始的竭宰、直接的內(nèi)存塊訪問途徑空郊,讓你能夠處理它們。

在這一階段需要掌握的最后一件事羞延,就是你可以對數(shù)組和指針操作混用它們絕大多數(shù)的語法渣淳。你可以對一個指針使用數(shù)組的語法來訪問指向的東西,也可以對數(shù)組的名字做指針的算數(shù)運算伴箩。

實用的指針用法

你可以用指針做下面四個最基本的操作:

  • 向OS申請一塊內(nèi)存入愧,并且用指針處理它。這包括字符串嗤谚,和一些你從來沒見過的東西棺蛛,比如結(jié)構(gòu)體。
  • 通過指針向函數(shù)傳遞大塊的內(nèi)存(比如很大的結(jié)構(gòu)體)巩步,這樣不必把全部數(shù)據(jù)都傳遞進去旁赊。
  • 獲取函數(shù)的地址用于動態(tài)調(diào)用。
  • 對一塊內(nèi)存做復(fù)雜的搜索椅野,比如终畅,轉(zhuǎn)換網(wǎng)絡(luò)套接字中的字節(jié),或者解析文件竟闪。

對于你看到的其它所有情況离福,實際上應(yīng)當使用數(shù)組。在早期炼蛤,由于編譯器不擅長優(yōu)化數(shù)組妖爷,人們使用指針來加速它們的程序。然而理朋,現(xiàn)在訪問數(shù)組和指針的語法都會翻譯成相同的機器碼絮识,并且表現(xiàn)一致。由此嗽上,你應(yīng)該每次盡可能使用數(shù)組次舌,并且按需將指針用作提升性能的手段。

指針詞庫

現(xiàn)在我打算向你提供一個詞庫兽愤,用于讀寫指針彼念。當你遇到復(fù)雜的指針語句時,試著參考它并且逐字拆分語句(或者不要使用這個語句烹看,因為有可能并不好):

type *ptr

type類型的指針,名為ptr洛史。

*ptr

ptr所指向位置的值惯殊。

*(ptr + i)

ptr所指向位置加上i)的值。

譯者注:以字節(jié)為單位的話也殖,應(yīng)該是ptr所指向的位置再加上sizeof(type) * i土思。

&thing

thing的地址务热。

type *ptr = &thing

名為ptrtype類型的指針己儒,值設(shè)置為thing的地址崎岂。

ptr++

自增ptr指向的位置。

我們將會使用這份簡單的詞庫來拆解這本書中所有的指針用例闪湾。

指針并不是數(shù)組

無論怎么樣冲甘,你都不應(yīng)該把指針和數(shù)組混為一談。它們并不是相同的東西途样,即使C讓你以一些相同的方法來使用它們江醇。例如,如果你訪問上面代碼中的sizeof(cur_age)何暇,你會得到指針的大小陶夜,而不是它指向數(shù)組的大小。如果你想得到整個數(shù)組的大小裆站,你應(yīng)該使用數(shù)組的名稱age条辟,就行第12行那樣。

譯者注宏胯,除了sizeof羽嫡、&操作和聲明之外,數(shù)組名稱都會被編譯器推導(dǎo)為指向其首個元素的指針胳嘲。對于這些情況厂僧,不要用“是”這個詞,而是要用“推導(dǎo)”了牛。

如何使它崩潰

你可以通過將指針指向錯誤的位置來使程序崩潰:

  • 試著將cur_age指向names颜屠。可以需要C風(fēng)格轉(zhuǎn)換來強制執(zhí)行鹰祸,試著查閱相關(guān)資料把它弄明白甫窟。
  • 在最后的for循環(huán)中,用一些古怪的方式使計算發(fā)生錯誤蛙婴。
  • 試著重寫循環(huán)粗井,讓它們從數(shù)組的最后一個元素開始遍歷到首個元素。這比看上去要困難街图。

附加題

  • 使用訪問指針的方式重寫所有使用數(shù)組的地方浇衬。
  • 使用訪問數(shù)組的方式重寫所有使用指針的地方。
  • 在其它程序中使用指針來代替數(shù)組訪問餐济。
  • 使用指針來處理命令行參數(shù)耘擂,就像處理names那樣。
  • 將獲取值和獲取地址組合到一起絮姆。
  • 在程序末尾添加一個for循環(huán)醉冤,打印出這些指針所指向的地址秩霍。你需要在printf中使用%p
  • 對于每一種打印數(shù)組的方法蚁阳,使用函數(shù)來重寫程序铃绒。試著向函數(shù)傳遞指針來處理數(shù)據(jù)。記住你可以聲明接受指針的函數(shù)螺捐,但是可以像數(shù)組那樣用它颠悬。
  • for循環(huán)改為while循環(huán),并且觀察對于每種指針用法哪種循環(huán)更方便归粉。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椿疗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子糠悼,更是在濱河造成了極大的恐慌届榄,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倔喂,死亡現(xiàn)場離奇詭異铝条,居然都是意外死亡,警方通過查閱死者的電腦和手機席噩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門班缰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悼枢,你說我怎么就攤上這事埠忘。” “怎么了馒索?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵莹妒,是天一觀的道長。 經(jīng)常有香客問我绰上,道長旨怠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任蜈块,我火速辦了婚禮鉴腻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘百揭。我一直安慰自己爽哎,他們只是感情好,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布器一。 她就那樣靜靜地躺著课锌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盹舞。 梳的紋絲不亂的頭發(fā)上产镐,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機與錄音踢步,去河邊找鬼癣亚。 笑死,一個胖子當著我的面吹牛获印,可吹牛的內(nèi)容都是我干的述雾。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼兼丰,長吁一口氣:“原來是場噩夢啊……” “哼玻孟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳍征,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤黍翎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后艳丛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匣掸,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年氮双,在試婚紗的時候發(fā)現(xiàn)自己被綠了碰酝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡戴差,死狀恐怖送爸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情暖释,我是刑警寧澤袭厂,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站饭入,受9級特大地震影響嵌器,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谐丢,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一爽航、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乾忱,春花似錦讥珍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蹄葱,卻和暖如春氏义,著一層夾襖步出監(jiān)牢的瞬間锄列,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工惯悠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留邻邮,地道東北人。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓克婶,卻偏偏與公主長得像筒严,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子情萤,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容

  • 指針是C語言中廣泛使用的一種數(shù)據(jù)類型鸭蛙。 運用指針編程是C語言最主要的風(fēng)格之一。利用指針變量可以表示各種數(shù)據(jù)結(jié)構(gòu)筋岛; ...
    朱森閱讀 3,430評論 3 44
  • 重新系統(tǒng)學(xué)習(xí)下C++娶视;但是還是少了好多知識點;socket睁宰;unix歇万;stl;boost等勋陪; C++ 教程 | 菜...
    kakukeme閱讀 19,833評論 0 50
  • C語言指針的總結(jié) 1. 變量 不同類型的變量在內(nèi)存中占據(jù)不同的字節(jié)空間贪磺。 內(nèi)存中存儲數(shù)據(jù)的最小基本單位是字節(jié),每一...
    xx_cc閱讀 3,708評論 11 39
  • 《岡仁波齊》是由張楊執(zhí)導(dǎo)的電影诅愚,由尼瑪扎堆寒锚、楊培、斯朗卓嘎等主演违孝。 該片主要講述了尼瑪扎堆等十一個藏...
    時空說閱讀 204評論 0 0
  • 當?shù)谝豢|陽光劃過樹梢刹前,鳥兒在枝頭鳴叫,我便肩負著喜悅的心情雌桑,將要去邂逅一片未知的天地喇喉,一個斑斕的巴蜀寶地。 收拾好...
    挪威的森林126閱讀 272評論 0 0