面試C語(yǔ)言深度解析_第1頁(yè)
面試C語(yǔ)言深度解析_第2頁(yè)
面試C語(yǔ)言深度解析_第3頁(yè)
面試C語(yǔ)言深度解析_第4頁(yè)
面試C語(yǔ)言深度解析_第5頁(yè)
已閱讀5頁(yè),還剩14頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

目錄作者自序 2第一章:概念定義篇 21.1、局部變量能否和全局變量重名? 21.2、堆棧溢出一般是什么原因?qū)е碌模?31.3、? 5第二章:關(guān)鍵字篇 52.1、如何引用一個(gè)已經(jīng)定義過(guò)的全局變量? 52.2、單片機(jī)程序中經(jīng)常使用到死循環(huán),如何使用C語(yǔ)言寫(xiě)出一個(gè)死循環(huán)? 72.3、do···while和while···do有什么區(qū)別? 82.4、C語(yǔ)言中static有什么用? 8第三章操作符 103.1、請(qǐng)寫(xiě)出下列代碼的輸出內(nèi)容 103.2、寫(xiě)出floatx與“零值”比較的if語(yǔ)句. 122、? 15附錄1、C語(yǔ)言運(yùn)算符表 16作者自序現(xiàn)在再寫(xiě)C語(yǔ)言的書(shū)似乎有點(diǎn)不合時(shí)宜。原因很簡(jiǎn)單,C語(yǔ)言的書(shū)實(shí)在是不少了,而且其中不乏經(jīng)典優(yōu)秀之作。如國(guó)外的《C程序設(shè)計(jì)語(yǔ)言》、《C和指針》、《C缺陷與陷阱》,國(guó)內(nèi)林銳的《高質(zhì)量C/C++編程指南》等。加之C語(yǔ)言本身這些年也沒(méi)有發(fā)生值得討論的大的更改,因此曾經(jīng)的經(jīng)典書(shū)籍并沒(méi)有被歲月磨去使用價(jià)值。我們可以輕松得出結(jié)論:根本沒(méi)有必要再去寫(xiě)一本系統(tǒng)介紹、循序漸進(jìn)的學(xué)習(xí)C語(yǔ)言的教材。那么本書(shū)從何立意?為什么大家需要這樣一本C語(yǔ)言書(shū)?或者說(shuō),大家到底還需要一本什么樣的C語(yǔ)言書(shū)呢?我將會(huì)在本書(shū)中為大家揭開(kāi)答案。首先,從組織形式來(lái)看。本書(shū)沒(méi)有像傳統(tǒng)的C語(yǔ)言教材一樣,以知識(shí)點(diǎn)為線索來(lái)構(gòu)架整書(shū)。而是另辟蹊徑,以專題的形式來(lái)平面化組織。各專題之間是彼此獨(dú)立、平行存在的。因此本書(shū)并不是一本系統(tǒng)學(xué)習(xí)C語(yǔ)言的初級(jí)教材,它并不適合沒(méi)有接觸過(guò)編程語(yǔ)言的新人初學(xué)C語(yǔ)言,而是一本以專題為單位深入挖掘、有理有據(jù)、條分縷析,試圖讓大家對(duì)C語(yǔ)言有更深理解和感悟的提高型專作。其次,從選題角度來(lái)看。本書(shū)中所選的題目,都是軟件研發(fā)企業(yè)面試筆試題中出現(xiàn)率最高的題目集合。因?yàn)槁殬I(yè)的原因,筆者需要幫助大量應(yīng)聘者指導(dǎo)面試筆試,從而需要接觸和研究各家軟件類企業(yè)的技術(shù)題目。毋庸置疑,C語(yǔ)言在各種技術(shù)面試中的比重是最大的(因?yàn)镃語(yǔ)言的普及度高,最能考核面試者的基本素質(zhì),而且C語(yǔ)言在實(shí)際開(kāi)發(fā)中應(yīng)用很廣)。就深圳地區(qū)來(lái)講,無(wú)論是華為、騰訊、聯(lián)想這樣的知名巨頭,還是遍布在深圳各地、為數(shù)眾多的中小型公司,其招聘中單論技術(shù)環(huán)節(jié),C語(yǔ)言都是其中的重頭戲。有意思的是,經(jīng)常出現(xiàn)的題目也是非常固定的。所以,面試中的技術(shù)面,可以說(shuō)是一場(chǎng)開(kāi)卷考試??上У氖?,即使是這樣的一場(chǎng)開(kāi)卷考試,仍然有眾多考生“屢試不爽”,技術(shù)面總是不得要領(lǐng),或者總是被歸入“初級(jí)程序員”一列。本書(shū)參考眾多國(guó)內(nèi)外知名企業(yè)如華為、騰訊等的面試題,收錄其中有代表性的典型題目,進(jìn)行題目解答與深入分析。其中的解答部分以被面試者的角度回答問(wèn)題,可以作為大家面試中被問(wèn)到相同題目時(shí)的可選標(biāo)準(zhǔn)答案來(lái)參考。即回答了“是什么樣”的問(wèn)題;而深度解析部分(也是我重點(diǎn)要推薦的)則以該題目為引子,深入探討了C語(yǔ)言中的相關(guān)知識(shí)點(diǎn)。并且會(huì)結(jié)合筆者對(duì)C語(yǔ)言的理解和講述經(jīng)驗(yàn),試圖帶領(lǐng)大家探討“為什么是這樣?怎么來(lái)的?”的問(wèn)題。再次,從表述風(fēng)格來(lái)看。本書(shū)貫徹以下一些基本理念,并以之為指導(dǎo)思想:深入淺出。深入指的是深入到技術(shù)細(xì)節(jié),甚至是技術(shù)背后的機(jī)制和原理。淺出指的是以舉例、圖解、類比比喻等手法使大家便于理解。深入進(jìn)去,才能學(xué)到東西;淺出介紹,才容易使人接受。羅嗦(貌似這是個(gè)貶義詞······)。相信大家都有過(guò)這種體驗(yàn):有些時(shí)候看書(shū)或者聽(tīng)課時(shí),費(fèi)盡九牛二虎之力,看過(guò)好幾個(gè)人的文章,聽(tīng)過(guò)好幾個(gè)老師的課才明白到底怎么回事。我認(rèn)為主要原因就在于:這些文章為了避免羅嗦,追求簡(jiǎn)潔的語(yǔ)言藝術(shù),沒(méi)有在關(guān)鍵問(wèn)題上做詳細(xì)(甚至是反復(fù)的、多種角度的)的說(shuō)明,剛撓到癢處就“點(diǎn)到為止”了,造成信息傳遞的不完全,讓讀書(shū)的人“自己猜、自己悟”。本人堅(jiān)信:語(yǔ)言的主要目的就是信息傳遞,至少傳道授業(yè)解惑時(shí)是這樣。語(yǔ)言有無(wú)美感是其次,關(guān)鍵是講的人要講清楚,聽(tīng)的人要聽(tīng)明白了。我從來(lái)不以羅嗦為恥。幽默。以前說(shuō):興趣是最好的老師?,F(xiàn)在又說(shuō):愉悅的用戶體驗(yàn)是成功的關(guān)鍵。再好的書(shū),不想看、看不下去也是白搭。筆者在實(shí)際授課中就以生動(dòng)幽默、輕松愉悅的課堂氛圍為學(xué)生所喜愛(ài),在本書(shū)中我仍舊堅(jiān)持發(fā)揚(yáng)這種風(fēng)格,努力讓大家的學(xué)習(xí)少點(diǎn)枯燥與晦澀,多些歡樂(lè)與輕松。最后,隨書(shū)提供了大量測(cè)試用例。這些測(cè)試用例都是精心設(shè)計(jì)編寫(xiě),有些用來(lái)輔助文章描述方便大家理解,有些用來(lái)作為佐證證實(shí)書(shū)中所說(shuō),還有些用來(lái)驗(yàn)證自己的猜測(cè)、也就是通過(guò)代碼示例的運(yùn)行結(jié)果來(lái)學(xué)習(xí)。編程總的來(lái)說(shuō)是一門(mén)技能,光有知識(shí)是不夠的,還要通過(guò)練習(xí)將之轉(zhuǎn)化為能力。所以,實(shí)際編寫(xiě)代碼、調(diào)試代碼的能力是程序員的核心技能,必須努力訓(xùn)練培養(yǎng)。通過(guò)代碼調(diào)試來(lái)驗(yàn)證自己對(duì)問(wèn)題的理解和猜測(cè)是一種很好的學(xué)習(xí)手段,是大家建立自己判斷力、靠自己的大腦來(lái)認(rèn)識(shí)世界的一個(gè)有效途徑。別人的書(shū)永遠(yuǎn)只是個(gè)引子,別人總結(jié)的話永遠(yuǎn)只是他的理解。授人以魚(yú),不如授之以漁。本書(shū)中我始終試圖引導(dǎo)大家自己分析、理解問(wèn)題。所有的未知都是由已知加上符合邏輯、有道理的推論發(fā)展而來(lái)的,我試圖帶領(lǐng)大家從源頭開(kāi)始經(jīng)歷這些推論發(fā)展過(guò)程,從而建立大家的自我學(xué)習(xí)、分析、驗(yàn)證能力。常言常大哥說(shuō)過(guò):盡信書(shū)不如無(wú)書(shū)。知識(shí)的世界博大精深,我個(gè)人也只是探索其中的一葉孤舟。限于水平和認(rèn)識(shí),疏漏與不足在所難免。請(qǐng)大家討論與指正。最后,希望大家從本書(shū)中有所得、有所悟。 朱有鵬2014032第一章:概念定義篇1.1、局部變量能否和全局變量重名?解答:能。在局部變量的作用域內(nèi),局部變量會(huì)屏蔽掉同名的全局變量。此時(shí)若需要使用全局變量,需要使用”::”符號(hào)(linux中必須使用g++編譯,使用gcc時(shí)并不識(shí)別域操作符::)。深度解析:C語(yǔ)言中有“作用域”的概念。譬如全局變量作用域?yàn)檎麄€(gè)文件(準(zhǔn)確的說(shuō)是定義該全局變量的文件中該變量定義/聲明之后的部分。只不過(guò)一般情況下全局變量都在文件頭部定義,因此說(shuō)全局變量為文件作用域。),局部變量為代碼塊作用域。所謂代碼塊作用域,代碼塊是指用一對(duì)大括號(hào){}括起來(lái)的部分(譬如一個(gè)函數(shù)的函數(shù)體,for循環(huán)的循環(huán)體等)。也就是說(shuō)局部變量的作用域其實(shí)是定義這個(gè)局部變量的代碼塊中該變量定義體之后的部分。這樣看來(lái),至少得到以下兩個(gè)結(jié)論:變量的作用域是有大有小的變量的作用域是有重疊部分的。譬如在一個(gè)函數(shù)內(nèi),該函數(shù)的局部變量和整個(gè)文件的全局變量都覆蓋這個(gè)作用域,這就是作用域的重疊。重疊作用域中,如果全局變量名和局部變量名不同不會(huì)造成困擾。因?yàn)槲覀兛梢院苋菀椎耐ㄟ^(guò)變量名來(lái)區(qū)分兩個(gè)變量(我們班有個(gè)叫旺財(cái)?shù)?,別人班有個(gè)叫富貴的,我叫旺財(cái)你肯定知道我要找的是誰(shuí)吧?。5窃趦蓚€(gè)變量名相同時(shí)要怎么辦呢(我們班有個(gè)旺財(cái),隔壁班也有個(gè)旺財(cái),我在走廊里喊一聲旺財(cái),你覺(jué)得我在叫誰(shuí)···)?這種情況邏輯學(xué)上叫二義性(ambiguity)。即有兩種可能的解釋,卻沒(méi)有任何區(qū)分的方法。怎么辦呢?人為規(guī)定嘛。C語(yǔ)言規(guī)定:在變量作用域重疊時(shí),作用域?yàn)樾》秶淖兞扛采w大范圍的變量。譬如函數(shù)內(nèi)有個(gè)局部變量var,文件內(nèi)有個(gè)全局變量var。則在該函數(shù)內(nèi)部(準(zhǔn)確的說(shuō)是函數(shù)內(nèi)部var局部變量定義體之后的部分),你使用var訪問(wèn)的是var局部變量,此處全局變量var被掩蔽(要想在此處訪問(wèn)全局變量var,對(duì)于C++可以使用::符號(hào),而C語(yǔ)言中則沒(méi)有域操作符::)。1.2、堆棧溢出一般是什么原因?qū)е碌??解答:堆棧溢出一般都是由堆棧越界訪問(wèn)導(dǎo)致的。例如函數(shù)內(nèi)局部變量數(shù)組越界訪問(wèn),或者函數(shù)內(nèi)局部變量使用過(guò)多,超出了操作系統(tǒng)為該進(jìn)程分配的棧的大小也會(huì)導(dǎo)致堆棧溢出。深度解析:首先要區(qū)分清楚堆、棧、堆棧這幾個(gè)名詞。堆(heap)和棧(stack)是兩種不同的內(nèi)存管理機(jī)制。堆被稱為動(dòng)態(tài)內(nèi)存,由堆管理器(系統(tǒng)里的大人物,山高皇帝遠(yuǎn)不用去管它)管理,程序中可以使用malloc函數(shù)來(lái)(向堆管理器)申請(qǐng)分配堆內(nèi)存,使用完后使用free函數(shù)釋放(給堆管理器回收)。堆內(nèi)存的特點(diǎn)是:在程序運(yùn)行過(guò)程中才申請(qǐng)分配,在程序運(yùn)行中即釋放(因此稱為動(dòng)態(tài)內(nèi)存分配技術(shù))。棧是C語(yǔ)言使用的一種內(nèi)存自動(dòng)分配技術(shù)(注意是自動(dòng),不是動(dòng)態(tài),這是兩個(gè)概念),自動(dòng)指的是棧內(nèi)存操作不用C程序員干預(yù),而是自動(dòng)分配自動(dòng)回收的。C語(yǔ)言中局部變量就分配在棧上,進(jìn)入函數(shù)時(shí)局部變量需要的內(nèi)存自動(dòng)分配,函數(shù)結(jié)束退出時(shí)局部變量對(duì)應(yīng)的內(nèi)存自動(dòng)釋放,整個(gè)過(guò)程中程序員不需要人為干預(yù)。堆棧這個(gè)詞純粹是用來(lái)坑人的。堆就是堆(heap),棧就是棧(stack),根本沒(méi)有另外一種內(nèi)存管理機(jī)制叫堆棧。大多數(shù)時(shí)候有人說(shuō)起堆棧,其實(shí)他想說(shuō)的是棧,以前早些的時(shí)候,這方面的命名并不是特別準(zhǔn)確。(別人說(shuō)堆棧的時(shí)候,大家知道他其實(shí)想說(shuō)的是棧就行了,自己就不要再用這個(gè)不準(zhǔn)確的詞了)。既然堆和棧都是用來(lái)管理內(nèi)存的機(jī)制,使用時(shí)就有一定的規(guī)則。無(wú)視規(guī)則的錯(cuò)誤使用(C語(yǔ)言設(shè)計(jì)時(shí)賦予了程序員很大的自由度,所以有些錯(cuò)誤語(yǔ)言本身是不會(huì)檢查的,全憑程序員自己把握。)就可以導(dǎo)致一些內(nèi)存錯(cuò)誤,如內(nèi)存泄漏、溢出錯(cuò)誤等。內(nèi)存泄漏主要發(fā)生在堆內(nèi)存使用中。譬如我們使用malloc申請(qǐng)了內(nèi)存,使用過(guò)后并未釋放而丟棄了指向該內(nèi)存的指針(這個(gè)指針是這段內(nèi)存的唯一記錄,程序中釋放該段內(nèi)存都靠這個(gè)指針了),那么這段堆內(nèi)存就泄漏掉了(堆管理器以為程序還在使用,所以不會(huì)將這段內(nèi)存再次分配給別的程序)。必須等到這個(gè)程序徹底退出后,系統(tǒng)回收該程序所使用的所有資源(申請(qǐng)的內(nèi)存,使用的文件描述符等)時(shí)這些泄漏的內(nèi)存才重新回到堆管理器的懷抱。內(nèi)存溢出在堆和棧中都有可能發(fā)生。參見(jiàn)章節(jié)示例1_2_stack_overflow.c中的8個(gè)示例函數(shù),其中前三個(gè)函數(shù)與堆溢出有關(guān),后五個(gè)函數(shù)與棧溢出有關(guān)。<堆溢出>函數(shù)heap_overflow中使用malloc申請(qǐng)了16字節(jié)動(dòng)態(tài)內(nèi)存,然后嘗試去讀寫(xiě)這16個(gè)內(nèi)存之中的第n個(gè)。三個(gè)測(cè)試分別給n賦值9,99和9999999,得到的結(jié)果很有意思(見(jiàn)程序后面的注釋,大家也可以自己編譯運(yùn)行測(cè)試),現(xiàn)在我們來(lái)探討其中的原理。n等于9的時(shí)候沒(méi)什么好說(shuō)的,本該正確運(yùn)行,這個(gè)相信大家沒(méi)有異議。n等于99的時(shí)候······竟然也可以正確運(yùn)行,這個(gè)相信很多人就有點(diǎn)想不通了。我們申請(qǐng)的空間只有16字節(jié)啊,怎么竟然還可以訪問(wèn)第99個(gè)字節(jié)空間呢(這就是所謂的堆溢出訪問(wèn))?這時(shí)候?qū)嶋H已經(jīng)堆溢出了,但是為什么結(jié)果沒(méi)有出錯(cuò)呢?原因在操作系統(tǒng)的內(nèi)存分配策略中。譬如linux中內(nèi)存是按照頁(yè)(Page,一般是4K字節(jié)一個(gè)頁(yè))來(lái)管理的,操作系統(tǒng)給進(jìn)程分配內(nèi)存本質(zhì)上都是以頁(yè)為單位進(jìn)行的。也就是說(shuō)你雖然只要求了16個(gè)字節(jié),但是實(shí)際分配給你這個(gè)進(jìn)程的可能是一個(gè)頁(yè)(4K字節(jié))。這個(gè)頁(yè)中只有這16個(gè)字節(jié)是你自己的“合法財(cái)產(chǎn)”,其他部分你不該去訪問(wèn)(一訪問(wèn)就堆越界)。但是因?yàn)椴僮飨到y(tǒng)對(duì)內(nèi)存的訪問(wèn)權(quán)限管理是以頁(yè)為單位的,因此本頁(yè)內(nèi)16字節(jié)之外的內(nèi)存你(非法)訪問(wèn)時(shí)系統(tǒng)仍然不會(huì)報(bào)錯(cuò),并且確實(shí)能夠達(dá)成目的(示例中n等于99時(shí)讀寫(xiě)仍然正確)。那是不是說(shuō)堆越界是無(wú)害的,完全不用擔(dān)心呢?顯然不是。因?yàn)槎言浇缱畲蟮膫Σ皇菍?duì)自己,而是對(duì)“別人”。因?yàn)槌四闵暾?qǐng)的16字節(jié)外本頁(yè)面內(nèi)其他內(nèi)存可能會(huì)被堆管理器分配給其他變量,你越界訪問(wèn)時(shí)意味著你可能踐踏了其他變量的有效區(qū)域(譬如我們給第99個(gè)字節(jié)賦值為g時(shí),很可能把別處動(dòng)態(tài)分配的一個(gè)變量的一部分給無(wú)意識(shí)的修改了)。因此其他變量會(huì)“莫名其妙”的出錯(cuò),而且最可怕的是這種出錯(cuò)編譯器無(wú)法幫你發(fā)現(xiàn),大多數(shù)時(shí)候隱藏的很深,極難發(fā)現(xiàn),往往令調(diào)試者抓狂、痛不欲生。因此訪問(wèn)堆內(nèi)存時(shí)應(yīng)該極為小心,一定要檢驗(yàn)訪問(wèn)范圍,謹(jǐn)防堆訪問(wèn)越界。最后一個(gè)示例中n等于9999999,這是我隨便寫(xiě)的一個(gè)很大的數(shù),執(zhí)行結(jié)果為:段錯(cuò)誤(Segmentationfault)。熟悉C語(yǔ)言的同學(xué)都知道,一般段錯(cuò)誤都是因?yàn)槌绦蛟L問(wèn)了不該訪問(wèn)的區(qū)域(譬如試圖寫(xiě)代碼段),這里也不例外。什么原因?考慮下上文中提到的以頁(yè)為單位的內(nèi)存管理策略。給你分配了一個(gè)頁(yè)(一般是4KB),你訪問(wèn)時(shí)索引值太大已經(jīng)超出了這個(gè)頁(yè)(跑到下個(gè)頁(yè)甚至更后面的頁(yè)面去了),那邊的內(nèi)存頁(yè)面根本不歸你使用,你試圖讀寫(xiě)的時(shí)候操作系統(tǒng)的內(nèi)存管理部分就會(huì)一巴掌把你扇回來(lái),給你個(gè)Segmentationfault。那個(gè)數(shù)字式我隨便寫(xiě)的,你也可以自己試試先給個(gè)小數(shù)字,然后逐漸加大,總會(huì)有個(gè)臨界點(diǎn),過(guò)了那個(gè)點(diǎn)就開(kāi)始段錯(cuò)誤了。<棧溢出>func1到func5這五個(gè)示例用來(lái)演示棧溢出。func1是典型的數(shù)組越界造成的棧溢出,壓棧越界導(dǎo)致沖毀了函數(shù)調(diào)用堆棧結(jié)構(gòu),致使整個(gè)程序崩潰。由此可見(jiàn),在C語(yǔ)言中數(shù)組訪問(wèn)時(shí)一定要小心檢查,保證不越界。C語(yǔ)言為了追求最高的效率,并未提供任何數(shù)組訪問(wèn)動(dòng)態(tài)檢查(實(shí)際上也沒(méi)有提供編譯時(shí)數(shù)組訪問(wèn)是否越界的靜態(tài)檢查,其原因是C語(yǔ)言愿意相信程序員,而將檢查的重任交給了程序員自己······果然是權(quán)力越大、責(zé)任越大啊?。?,因此“保衛(wèi)世界和平的重任就靠你了”。func2和func3是一對(duì)對(duì)比測(cè)試。其中調(diào)用了一個(gè)遞歸函數(shù)factorial,該函數(shù)用來(lái)求一個(gè)正整數(shù)n的階乘。func2中n等于10,計(jì)算結(jié)果為3628800,是正確的(大家可以用計(jì)算器自己驗(yàn)證)。func3中n等于10000000,運(yùn)行結(jié)果為段錯(cuò)誤(其實(shí)即使不段錯(cuò)誤,factorial函數(shù)本身也無(wú)法計(jì)算很大數(shù)字的階乘,原因在于函數(shù)中使用unsignedint類型來(lái)存階乘值,這個(gè)類型的取值范圍非常有限,n稍微大一點(diǎn)就會(huì)溢出。但溢出只會(huì)導(dǎo)致計(jì)算結(jié)果不對(duì),不會(huì)造成段錯(cuò)誤的)。怎么會(huì)段錯(cuò)誤呢?因?yàn)檫f歸次數(shù)太多,棧終于被撐爆了。遞歸函數(shù)運(yùn)行時(shí),實(shí)際上相當(dāng)于不停在執(zhí)行子函數(shù)調(diào)用,因此棧一直在分配而沒(méi)有釋放。若在棧使用完之前遞歸仍然沒(méi)有結(jié)束返回(此時(shí)會(huì)逐層釋放棧)就會(huì)發(fā)生段錯(cuò)誤。這是棧溢出的另一個(gè)典型情況,請(qǐng)大家以后使用遞歸算法解決問(wèn)題時(shí)注意這個(gè)限制。func4和func5是一對(duì)對(duì)比測(cè)試。其中均定義了一個(gè)局部變量數(shù)組a,不同的是a的大小。func4中數(shù)組大小為1M(注意a的類型是int,因此這里單位是4字節(jié)),運(yùn)行成功。而func5中數(shù)組大小為4M,運(yùn)行時(shí)則發(fā)生段錯(cuò)誤。相信有了上面上面的講解,大家能夠很容易想明白,局部變量分配太多把棧用完了,所以就段錯(cuò)誤了,就這么簡(jiǎn)單。以上,通過(guò)5個(gè)示例程序?yàn)榇蠹已菔玖藯R绯龅娜N情況。一般來(lái)說(shuō),第一種情況是明顯的錯(cuò)誤,且每次執(zhí)行都確定會(huì)發(fā)生錯(cuò)誤。而后兩種錯(cuò)誤則稍微復(fù)雜一些,原因在于這兩種錯(cuò)誤都依賴于棧的大小。而棧的大小在操作系統(tǒng)中不是固定的,是可以人為設(shè)置的(譬如linux中使用ulimit–s來(lái)查看和設(shè)置用戶進(jìn)程棧大?。?。這就會(huì)帶來(lái)一些很“神奇”的bug,如程序在你的計(jì)算機(jī)中運(yùn)行良好,調(diào)試通過(guò)。結(jié)果發(fā)給客戶,10個(gè)客戶中8個(gè)運(yùn)行良好,另外兩個(gè)會(huì)報(bào)錯(cuò)、死機(jī)······這時(shí)候只要重新設(shè)置一個(gè)更大的用戶棧容量就可以解決問(wèn)題。所以大家在寫(xiě)代碼時(shí)一定要注意,考慮到你的代碼有可能潛在的問(wèn)題。這樣一旦問(wèn)題暴露即可迅速定位,并最快的找到解決方案。不過(guò)更高級(jí)的做法是:在寫(xiě)代碼時(shí)盡量減少可能存在的問(wèn)題,讓你的程序盡量更加健壯(robust)。1.3、?解答:能。深度解析:第二章:關(guān)鍵字篇2.1、如何引用一個(gè)已經(jīng)定義過(guò)的全局變量?解答:如果要引用的全局變量在同一文件內(nèi)定義,則可以直接引用;如果要引用的全局變量在另外的C文件中定義,則有兩種引用方式。第一種是使用#include包含聲明了該全局變量的頭文件,第二種是使用extern關(guān)鍵字在本文件中再次聲明該全局變量。深度解析:C語(yǔ)言中有定義和聲明,需搞清楚兩者的聯(lián)系和區(qū)別。變量定義的本質(zhì)是新建一個(gè)變量(或者向系統(tǒng)申請(qǐng)一個(gè)變量),系統(tǒng)需要為新建立的變量分配內(nèi)存空間。因此變量的定義意味著變量的生成;而聲明卻不產(chǎn)生新的變量,只是告訴編譯器有這么一個(gè)變量存在(也許是在別的地方定義的)。變量的定義好理解,因此要搞清楚兩者的區(qū)別關(guān)鍵在于理解聲明。首先要明白為什么需要聲明(我相信沒(méi)有人想問(wèn)為什么需要定義吧···)。這是個(gè)很深刻的問(wèn)題,要理解這個(gè)問(wèn)題需要從編譯連接系統(tǒng)的工作原理入手。C語(yǔ)言的編譯系統(tǒng)工作方式是:第一階段:?jiǎn)蝹€(gè).c源文件先獨(dú)立分開(kāi)編譯(生成對(duì)應(yīng)的.o目標(biāo)文件),由編譯器完成,此階段發(fā)現(xiàn)的錯(cuò)誤稱為編譯錯(cuò)誤。如語(yǔ)法錯(cuò)誤、變量未定義等都是編譯錯(cuò)誤。第二階段:所有的.o目標(biāo)文件連接生成可執(zhí)行程序,由連接器完成。此階段發(fā)現(xiàn)的錯(cuò)誤成為連接錯(cuò)誤。如某個(gè)符號(hào)未定義,多重定義等。(注:實(shí)際的過(guò)程可能更復(fù)雜,譬如要考慮預(yù)處理器、匯編器的工作,連接階段要考慮預(yù)編譯庫(kù)等等。這里為了簡(jiǎn)單起見(jiàn),大家先不管這些細(xì)節(jié)了。)編譯時(shí),變量必須先經(jīng)過(guò)聲明才能使用。沒(méi)有聲明就直接使用的變量會(huì)被判編譯錯(cuò)誤(原因是編譯器需要變量或者函數(shù)的類型原型聲明以判斷類型不匹配錯(cuò)誤)。也就是說(shuō)編譯階段編譯器只看聲明而不要求定義。連接時(shí),同名變量必須有且僅有一次定義。變量只有聲明而沒(méi)有定義,或者多處定義同名變量都會(huì)報(bào)連接錯(cuò)誤。因?yàn)橥兞浚ㄒ簿褪峭粋€(gè)變量)只能分配一處內(nèi)存空間,因此只能定義一次。好了,廢了半天口舌,背景基本交代清楚了?,F(xiàn)在我們假設(shè)這樣一種情況:由兩個(gè)文件a.c和b.c組成的工程,在a.c中定義了全局變量var,同時(shí)在a.c和b.c中都使用到該變量。這種情況實(shí)際上很常見(jiàn),我相信大家都遇到過(guò)。此時(shí),a.c中引用var很容易。只要將var定義在a.c的最前面部分,即可保證文件中任何函數(shù)都可以無(wú)障礙訪問(wèn)var,保證編譯和連接階段都不會(huì)報(bào)錯(cuò)。但是b.c呢?我們?cè)赽.c中如何使用var呢?下面來(lái)逐步分析一下:<思路1>:在b.c中再次定義var。這種方法可以嗎?如果可以那我們?cè)赽.c中定義的var和a.c中的是同一個(gè)嗎?根據(jù)C語(yǔ)言的規(guī)定,變量定義意味著創(chuàng)造一個(gè)新的變量,因此這兩個(gè)var雖然同名,但肯定是兩個(gè)不同的變量。因此不能滿足我們的需要。思路1失敗。<思路2>:在頭文件a.h中聲明var,然后在b.c中#include<a.h>。這種方法行嗎?分析編譯過(guò)程看看。a.c編譯肯定不會(huì)有錯(cuò),b.c編譯時(shí)因?yàn)榘薬.h中var的聲明,因此編譯器得到了var的變量原型(即編譯器知道有一個(gè)變量名叫var,并且知道這個(gè)var的類型信息等,但編譯器不知道這個(gè)var在哪里定義的。不過(guò)編譯器根本不需要知道這個(gè)變量到底在哪里定義的,交給連接器同志去處理這些吧!),因此只要在b.c中使用該var的地方符合a.h中var的聲明,b.c即可順利通過(guò)編譯。至此兩個(gè)源文件編譯通過(guò),進(jìn)入第二階段連接。連接器在連接時(shí)會(huì)為b.o中引用var的部分尋找var的定義體。此時(shí)連接器很輕松的發(fā)現(xiàn)a.o中即有一個(gè)名為var的全局變量的定義體,因此連接器確定了a.o和b.o中的var為同一個(gè)變量。至此問(wèn)題圓滿解決。思路2成功。<思路3>:使用extern關(guān)鍵字。a.c中情況不變,仍然定義全局變量var(例如,intvar;),b.c中使用extern關(guān)鍵字聲明變量var(externintvar;)。注意沒(méi)有使用頭文件中的聲明,這樣可以嗎?同樣的,先分析a.c和b.c各自的編譯過(guò)程,再分析連接過(guò)程。編譯時(shí)a.c當(dāng)然沒(méi)問(wèn)題,b.c中因?yàn)橄仁褂胑xternintvar;對(duì)var進(jìn)行了聲明(聲明的意義和思路2中相同)因此編譯階段也沒(méi)問(wèn)題。連接階段和思路2相同,因此連接成功。至此,思路3成功。通過(guò)以上分析,我們知道引用包含聲明的頭文件或者extern聲明的方式都可以使一個(gè)源文件引用其他源文件內(nèi)定義的變量。下面來(lái)探討一些更細(xì)節(jié)更有趣的問(wèn)題。<問(wèn)題1>:變量能否定義在頭文件內(nèi)?如果可以,那是定義在頭文件中好還是定義在源文件中好?實(shí)際測(cè)試證明,變量是可以定義在頭文件內(nèi)的,測(cè)試示例見(jiàn)章節(jié)示例test2。那么是不是說(shuō)變量定義放在源文件與頭文件是隨意的,沒(méi)有任何區(qū)別呢?請(qǐng)?jiān)倏凑鹿?jié)示例test3。此示例中我們?cè)赼.h中定義了全局變量var1和var2,然后在a.c和b.c中都include了該頭文件,結(jié)果鏈接時(shí)報(bào)錯(cuò),提示var2重復(fù)定義了。test3很好的演示了變量定義在頭文件中的壞處。當(dāng)該頭文件被多個(gè)源文件引用時(shí)即會(huì)導(dǎo)致該變量被重復(fù)定義,造成鏈接時(shí)錯(cuò)誤。所以,變量還是定義在源文件中的好。<問(wèn)題2>:工程中包含兩個(gè)源文件a.c和b.c,a.c中有一行intvar,并且在其后的函數(shù)中引用了該var;b.c中也有一行intvar并且在其后的函數(shù)中也使用了var;該工程能否成功編譯連接。如果不能,請(qǐng)指出編譯還是連接錯(cuò)誤,哪個(gè)文件會(huì)報(bào)錯(cuò)?在test4中,我們用一個(gè)實(shí)例說(shuō)明了以上的定義是允許的,而且a.c和b.c中定義的同名變量var實(shí)際上是同一個(gè)變量。怎么回事?為什么沒(méi)有重復(fù)定義呢?大家回憶下test1中對(duì)變量var的定義和聲明,可以發(fā)現(xiàn)其實(shí)變量的定義和聲明形式上是相同的,有時(shí)編譯器會(huì)將它看作定義,有時(shí)會(huì)將它看作聲明,有時(shí)候又是定義加聲明,編譯器會(huì)很智能的處理這個(gè)問(wèn)題(當(dāng)然了如果使用了賦值運(yùn)算符=在定義的同時(shí)初始化,那這個(gè)表達(dá)式就一定是定義而肯定不是聲明了)。譬如test4的示例中,a.c和b.c中都有intvar;的表達(dá)式,所以編譯器會(huì)自動(dòng)將其中一個(gè)當(dāng)作定義,而另一個(gè)當(dāng)作聲明。因此,就算你再多加幾十個(gè)源文件都使用這個(gè)同名變量,還是只會(huì)有一個(gè)是定義,其余全是聲明,不會(huì)報(bào)錯(cuò)。那如果我們?cè)诙x變量的同時(shí)給其賦初值呢?如test5中的樣子。會(huì)發(fā)現(xiàn)出現(xiàn)鏈接錯(cuò)誤,var重復(fù)定義了。原因很簡(jiǎn)單,因?yàn)槲覀儗?duì)var的兩個(gè)定義表達(dá)式都賦初值了,所以編譯器不得不把兩個(gè)表達(dá)式都當(dāng)成定義,所以連接時(shí)會(huì)重復(fù)定義。2.2、單片機(jī)程序中經(jīng)常使用到死循環(huán),如何使用C語(yǔ)言寫(xiě)出一個(gè)死循環(huán)?解答:C語(yǔ)言實(shí)現(xiàn)死循環(huán)有很多種方式,但是最常用的就是for(;;);和while(1);此外,還可以使用goto語(yǔ)句實(shí)現(xiàn)類似匯編風(fēng)格的死循環(huán)。深度解析:這個(gè)題目比較簡(jiǎn)單,但是很實(shí)用。注意for循環(huán)中兩個(gè)分號(hào)分開(kāi)的三個(gè)部分其實(shí)都是可以省略的,所以當(dāng)你看到for循環(huán)的某個(gè)部分缺失時(shí)不用驚訝。goto關(guān)鍵字一向被定義為不建議使用,主要原因是goto無(wú)條件到處跳來(lái)跳去的超級(jí)靈活特性導(dǎo)致它破壞了代碼的結(jié)構(gòu)性,使得代碼靈活到難以讀懂的程度了,這顯然是大家不愿意看到的。但是goto也是有其存在的價(jià)值的(星爺語(yǔ)錄:就算是一張衛(wèi)生紙,一條破內(nèi)褲都有它的用處),最適合使用goto的一場(chǎng)場(chǎng)景就是在多重循環(huán)體的內(nèi)部,此時(shí)使用goto可以一步直接跳出所有的循環(huán)層次(以前使用break則只能逐層跳出,麻煩;現(xiàn)在用goto,一跳頂過(guò)去五跳,實(shí)惠···)。熟悉匯編的同學(xué)應(yīng)該能感到,goto實(shí)際上是匯編中跳轉(zhuǎn)指令的簡(jiǎn)單封裝(譬如MCS51中的jmp指令,ARM匯編中的b指令)。而for和while、dowhile循環(huán)則是匯編中這些跳轉(zhuǎn)指令的復(fù)雜封裝,for循環(huán)等在匯編層次也是通過(guò)跳轉(zhuǎn)指令實(shí)現(xiàn)的。之所以大家覺(jué)得C語(yǔ)言編程比匯編容易一些,就是因?yàn)镃編譯器幫大家做了一些基礎(chǔ)性的封裝,形成了C語(yǔ)言這種更符合人類思維方式的結(jié)構(gòu)化編程語(yǔ)言。而直接使用匯編語(yǔ)言,則需要程序員“委屈自己”,去適應(yīng)機(jī)器的思維方式,按照機(jī)器所能理解的方式去“講話”。2.3、do···while和while···do有什么區(qū)別?解答:do···while循環(huán)的循環(huán)體至少會(huì)執(zhí)行一次,而while···do循環(huán)則有可能一次都不被執(zhí)行。深度解析:do{循環(huán)體}while(條件);循環(huán)先執(zhí)行循環(huán)體,然后根據(jù)條件判斷的真假?zèng)Q定是否繼續(xù)下一次循環(huán);而while(條件)do{循環(huán)體}循環(huán)則先判斷條件的真假,然后決定是否執(zhí)行循環(huán)體。執(zhí)行完后再次判斷條件,直到條件為假則終止循環(huán)。請(qǐng)注意dowhile循環(huán)中,while后面的()后面是有分號(hào)的,而whiledo循環(huán)do后面的()后是沒(méi)有分號(hào)的。循環(huán)體中要提前終止或跳出循環(huán),可以使用continue和break(當(dāng)然了goto也可以,但是除了一次跳出多重循環(huán)的情況外,其他情況最好不使用goto)。兩者的區(qū)別在于:continue用于終結(jié)本輪循環(huán)中continue語(yǔ)句之后的部分,因此continue之后的語(yǔ)句就不用執(zhí)行了,直接進(jìn)入到下一輪循環(huán);而break用于終結(jié)本層循環(huán),直接跳到本層循環(huán)之外的一層。簡(jiǎn)單來(lái)說(shuō),continue最溫和,break要狠一些,不過(guò)最狠的還是goto。2.4、C語(yǔ)言中static有什么用?解答:static可以用來(lái)修飾局部變量,全局變量、函數(shù)。static修飾局部變量,則該變量成為靜態(tài)局部變量。靜態(tài)局部變量只在第一次訪問(wèn)時(shí)初始化一次,以后訪問(wèn)時(shí)它的值保持和上一次操作結(jié)束時(shí)一樣。static修飾全局變量(函數(shù)),則該變量(函數(shù))成為靜態(tài)全局變量(函數(shù))。靜態(tài)全局變量(函數(shù))只能在該文件范圍內(nèi)使用,不能被其他文件所使用。這是因?yàn)殪o態(tài)全局變量(函數(shù))的連接屬性為內(nèi)連接,這樣可以解決不同源文件內(nèi)全局變量(函數(shù))名稱重名沖突問(wèn)題。深度解析:C語(yǔ)言中有存儲(chǔ)類、作用域、生命周期和連接屬性等四個(gè)概念。存儲(chǔ)類用來(lái)描述變量、函數(shù)等在內(nèi)存中分配相關(guān)的屬性(比如全局變量分配在數(shù)據(jù)區(qū),而普通局部變量分配在棧上,靜態(tài)局部變量分配在數(shù)據(jù)區(qū)),作用域描述變量可以被引用的范圍(譬如全局變量有文件作用域,而局部變量只有代碼塊作用域),生命周期表述一個(gè)變量何時(shí)產(chǎn)生何時(shí)消亡(如局部變量在進(jìn)入代碼塊時(shí)產(chǎn)生,代碼塊執(zhí)行結(jié)束時(shí)消亡),連接屬性描述一個(gè)變量或函數(shù)在連接階段可以被連接的范圍(C語(yǔ)言中全局變量和函數(shù)默認(rèn)為外連接,因此同一工程中不同源文件之中的全局變量或函數(shù)不能重名,否則會(huì)引起鏈接時(shí)錯(cuò)誤。用static修飾全局變量或函數(shù)形成靜態(tài)全局變量或函數(shù),則將其連接屬性修改為內(nèi)連接,此時(shí)該全局變量或函數(shù)名稱只在本文件內(nèi)有效,因此可以避免連接時(shí)重名問(wèn)題)。這四個(gè)屬性分別從四個(gè)角度去描述一個(gè)符號(hào)(變量或函數(shù)都是符號(hào))所處的狀態(tài),并且四個(gè)屬性之間互相影響,互為因果。這里我不打算用枯燥晦澀的概念來(lái)跟大家布道(這個(gè)世界永遠(yuǎn)不缺專家教授,這樣的書(shū)到處都能找到)。按照我一貫的風(fēng)格,我會(huì)講一些示例,我們要講的道理就在這些示例中,大家在心里默默體會(huì)吸收即可。示例1:全局變量可以在文件內(nèi)(有些書(shū)也寫(xiě)作模塊內(nèi),這里的模塊其實(shí)就是指這個(gè)全局變量所在的.c源文件,因此我喜歡稱文件內(nèi)。概念不重要,大家能明白我想說(shuō)的是什么才是關(guān)鍵。)任何地方被訪問(wèn),而局部變量只能在定義它的代碼塊內(nèi)被訪問(wèn)。造成這個(gè)事實(shí)的主要原因就是:全局變量的作用域?yàn)槲募饔糜颍植孔兞康淖饔糜驗(yàn)榇a塊作用域;全局變量在main函數(shù)開(kāi)始之前產(chǎn)生(全局變量的內(nèi)存空間由加載器分配并初始化,因此在程序進(jìn)入main,開(kāi)始執(zhí)行我們自己寫(xiě)的代碼之前,全局變量就已經(jīng)就位了。),在整個(gè)程序結(jié)束時(shí)死亡(跟隨程序一起釋放)。而局部變量在代碼執(zhí)行進(jìn)入代碼塊時(shí)產(chǎn)生,在程序執(zhí)行退出代碼塊時(shí)死亡。造成這個(gè)事實(shí)的主要原因是:全局變量的存儲(chǔ)類為全局?jǐn)?shù)據(jù)區(qū)(有時(shí)稱為數(shù)據(jù)區(qū),數(shù)據(jù)段,.data段),而局部變量的存儲(chǔ)類為棧(stack)。兩者存儲(chǔ)類的不同導(dǎo)致了不同的生命周期。示例2:普通局部變量每次進(jìn)入代碼塊時(shí)都會(huì)重新分配(如果定義同時(shí)有初始化式,還要再重新初始化一次),然后在代碼塊中被使用,最后在代碼塊退出時(shí)掛掉(被釋放)。而靜態(tài)局部變量只在第一次使用時(shí)初始化一次,以后使用時(shí)其值保持上次使用完時(shí)的值。也就是說(shuō)可以認(rèn)為靜態(tài)局部變量第一次訪問(wèn)時(shí)被分配并初始化(實(shí)際上分配時(shí)間和全局變量一樣),之后一直存在直到程序結(jié)束時(shí)與程序一起死亡。從以上描述很容易看出,靜態(tài)局部變量在表現(xiàn)上與全局變量幾乎一模一樣。都是都是程序開(kāi)始時(shí)產(chǎn)生、程序結(jié)束時(shí)死亡;都是初始化一次,以后每次訪問(wèn)時(shí)保持上次操作后的值。唯一的不同在于作用域(全局變量文件內(nèi)各處隨便訪問(wèn),靜態(tài)局部變量只在定義它的代碼塊內(nèi)可以訪問(wèn),代碼塊外“看不見(jiàn)”它)。為什么會(huì)這樣?答案在于static修飾局部變量,改變了該變量的存儲(chǔ)類(所以內(nèi)存分配區(qū)域改變了,生命周期跟著改變了,表現(xiàn)形態(tài)也跟著改變了),由棧改變?yōu)槿謹(jǐn)?shù)據(jù)區(qū)了。而static對(duì)其作用域?qū)傩院翢o(wú)影響,它仍然是代碼塊作用域,因此仍然只能在代碼塊內(nèi)訪問(wèn),不能在外部訪問(wèn)。示例3:普通全局變量(函數(shù))不能和工程內(nèi)其他源文件中定義的全局變量(函數(shù))重名,而靜態(tài)全局變量(函數(shù))卻可以;普通全局變量(函數(shù))可以被工程內(nèi)其他源文件中的函數(shù)所引用(#include聲明頭文件或extern聲明,詳見(jiàn)第2章第1節(jié)),而靜態(tài)全局變量(函數(shù))不能被其他源文件中的函數(shù)引用。造成這一事實(shí)的主要原因是:C語(yǔ)言默認(rèn)全局變量和函數(shù)鏈接屬性為外連接(這個(gè)外字,可以理解為源文件外、工程內(nèi)),而static修飾全局變量和函數(shù),將其連接屬性修改為內(nèi)連接(文件內(nèi))。而存儲(chǔ)類和生命周期等其他屬性則不受影響。以上三個(gè)示例中,我們以局部變量、全局變量、函數(shù)、靜態(tài)局部變量、靜態(tài)全局變量、函數(shù)等C語(yǔ)言中最常用的一些變量類型為例,給大家演示了四種屬性在C語(yǔ)言中的意義。想明白這些示例,大家就可以不用去管那些復(fù)雜的概念而直接明白個(gè)中道理,從而更好的使用C語(yǔ)言為我們提供的這些基礎(chǔ)設(shè)施,搭建自己的代碼大廈。不妨再多說(shuō)點(diǎn)廢話。大家有沒(méi)發(fā)現(xiàn):static這個(gè)關(guān)鍵字修飾局部變量和全局變量時(shí)意義相差極大(一個(gè)改變了存儲(chǔ)類,一個(gè)改變了鏈接類型)。而且static這個(gè)英文單詞的本意為靜態(tài)的、靜止的,這個(gè)和靜態(tài)局部變量還有點(diǎn)意義關(guān)聯(lián)(放在全局?jǐn)?shù)據(jù)區(qū)是比放在棧中要“靜”一些吧),但是和改變連接屬性由外連接到內(nèi)連接,這個(gè)確實(shí)很難找到意義關(guān)聯(lián)。為什么?好像之前有在某本書(shū)上看過(guò)(也可能是自己腦補(bǔ),不過(guò)即使是在書(shū)上看的,也有可能是那本書(shū)作者腦補(bǔ)···我一向堅(jiān)持盡信書(shū)不如無(wú)書(shū),不管從哪里來(lái),大家只管自己品鑒有無(wú)道理),static關(guān)鍵字剛開(kāi)始在C語(yǔ)言中本來(lái)只有修飾局部變量的用法(很合理),后來(lái)因?yàn)镃語(yǔ)言工程變大,文件變多,默認(rèn)外連接下文件間函數(shù)、變量重名問(wèn)題太麻煩(這個(gè)問(wèn)題后續(xù)的語(yǔ)言都是通過(guò)命名空間來(lái)解決的,如C++,Java,C#等。),于是乎需要一個(gè)新的關(guān)鍵字來(lái)將根本不需要在別的文件中引用的函數(shù)(內(nèi)部函數(shù))限制為內(nèi)連接(這樣只有極少數(shù)需要在外部訪問(wèn)的函數(shù)或變量才外連接,減少了重名風(fēng)險(xiǎn))。本來(lái)你新建個(gè)關(guān)鍵字就完了(譬如internal,private這些都挺貼切的),但是當(dāng)時(shí)大家都認(rèn)為C語(yǔ)言的關(guān)鍵字已經(jīng)挺多了,不想再隨便添加(C++的關(guān)鍵字就比C語(yǔ)言多了好多),于是乎可憐的static被看中了(因?yàn)閟tatic本來(lái)的含義只能修飾局部變量,根本沒(méi)有修飾全局變量和函數(shù)的用法),從此后編譯器賦予了static可以修飾全局變量和函數(shù)的能力,并且有了另外的含義(今天看來(lái),這或許不是一個(gè)好的解決方案,至少新建個(gè)關(guān)鍵字都比這好用點(diǎn),或者引入命名空間的概念從根本上解決問(wèn)題。但是···玩C的都是奔著大神、高手、geek去的,這點(diǎn)檻又算得了什么,讓暴風(fēng)雨來(lái)得更猛烈些吧?。。。?。第三章操作符3.1、請(qǐng)寫(xiě)出下列代碼的輸出內(nèi)容intmain(void){ inta,b,c,d; a=10; b=a++; c=++a; d=10*a++; printf("b,c,d:%d,%d,%d",b,c,d);}解答:輸出為:b,c,d:10,12,120。深度解析:此類題目考察的是++符號(hào)的前綴(pre-fix)和后綴(post-fix)用法,最好的分析方法是逐行分析,把當(dāng)前一句代碼執(zhí)行過(guò)后各個(gè)變量的值寫(xiě)出來(lái),一直到最后一行。下面是我逐行分析的結(jié)果:1 inta,b,c,d; //a=?,b=?,c=?,d=?2 a=10; //a=10,b=?,c=?,d=?3 b=a++; //a=11,b=10,c=?,d=?4 c=++a; //a=12,b=10,c=12,d=?5 d=10*a++; //a=13,b=10,c=12,d=120第1行為局部變量定義。注意此處定義的局部變量并沒(méi)有在定義的同時(shí)賦初值,因此這個(gè)變量的值此時(shí)為隨機(jī)值。注釋中值為?的地方即表示這些變量的值此時(shí)為不確定。第2行為普通的賦值操作,執(zhí)行完成后變量a就有了確定的值。第3行就有點(diǎn)意思了。首先大家來(lái)考慮一個(gè)問(wèn)題:b=a++中a先與左邊的賦值運(yùn)算符=結(jié)合,還是先與右邊的++符號(hào)結(jié)合?要回答這個(gè)問(wèn)題,依據(jù)便是附錄1中列出的《C語(yǔ)言運(yùn)算符表》。查表可知++優(yōu)先級(jí)為2級(jí),而=優(yōu)先級(jí)為14級(jí),所以++的優(yōu)先級(jí)遠(yuǎn)遠(yuǎn)高于=,因此a應(yīng)該先與++結(jié)合,然后整體再賦值給b,即相當(dāng)于b=(a++);好的,那a++先運(yùn)算,怎么運(yùn)算呢?++運(yùn)算符規(guī)定:當(dāng)++被后綴使用時(shí),先返回操作數(shù)本身,然后操作數(shù)自加1。因此賦值給b的值是自加1之前的a,a是在返回原來(lái)的a給b賦值之后才自加的。請(qǐng)注意造成這種結(jié)果的原因,不是=比++先執(zhí)行(++比=優(yōu)先級(jí)高,因此是++先執(zhí)行的),而是因?yàn)?+后綴式用法本身的特性決定的。很多人總是把這里跟優(yōu)先級(jí)高低混起來(lái),造成理解上的矛盾。a的值在第2行執(zhí)行后為10,因此第三行中賦值給b的值是10,因此第三行執(zhí)行完成后b=10,而a的值賦值后再自加1,因此執(zhí)行完后a的值是11。第4行的解析方法和第3行相同。理解了后綴式++的分析前綴式也就不難理解了。同樣的++先執(zhí)行,再執(zhí)行賦值操作。前綴式++運(yùn)算規(guī)則為:先對(duì)操作數(shù)進(jìn)行自加1,然后再返回自加后的值作為表達(dá)式返回值。第3行完成時(shí)a=11,此處先自加則a=12,整個(gè)表達(dá)式++a的值也為運(yùn)算后的12,賦值給c后,c的值也為12。第4行結(jié)束。第5行,首先要清楚中間的*是乘法符號(hào),而不是指針的解引用符。在C語(yǔ)言中這兩個(gè)符號(hào)都是*,但是使用場(chǎng)合不同。簡(jiǎn)單來(lái)說(shuō),*后面的操作數(shù)如果是指針那*就是指針解引用,而*后面如果是數(shù)值類型(int,float等)則*為乘法運(yùn)算符。接下來(lái)按部就班,查《C語(yǔ)言運(yùn)算符表》可知,++運(yùn)算符比乘法運(yùn)算符優(yōu)先級(jí)高(乘法為3級(jí),而++為2級(jí)。有意思的是,*作為指針解引用時(shí)優(yōu)先級(jí)也為2級(jí),跟++相同。這個(gè)設(shè)置會(huì)帶來(lái)一些很有意思的特性,特別是在strcpy等字符串庫(kù)函數(shù)源碼分析中會(huì)用到,此處暫且按下不表。),因此第5行相當(dāng)于d=(10*(a++));接下來(lái)就好分析了,根據(jù)前文對(duì)++后綴的分析可知,a應(yīng)該以原值參與運(yùn)算,因此d=10*12=120,運(yùn)算后a再自加,因此運(yùn)算完后a=13。++符號(hào)(--與之類似)的問(wèn)題是C/C++工程師筆試題目的??停浅>哂写硇?,因此值得花時(shí)間深入搞明白。以上之外,還有幾點(diǎn)需要說(shuō)明一下:貪心法。貪心法用來(lái)分析諸如c=a+++++b;這樣的表達(dá)式,對(duì)于這樣的一串+相連的表達(dá)式應(yīng)該怎么去區(qū)分呢?首先應(yīng)該對(duì)該連續(xù)表達(dá)式進(jìn)行“斷句”處理。所謂斷句,就是將其分為幾部分。那怎么分呢?用貪心法。貪心法:一個(gè)表達(dá)式中,從左到右,每部分(在保持自身為合法表達(dá)式的前提下)總是想得到盡可能多的字符。譬如a+++++b中,首先應(yīng)該斷句出的是a++,因?yàn)閺淖筮呴_(kāi)始有意義的符號(hào)只有a、a++(a+、a+++以及以后的都不是C語(yǔ)言規(guī)定的有效符號(hào)了),而a++比a長(zhǎng),所以“貪心”的編譯器總是從a++處斷句的。再下來(lái)呢?因?yàn)?a++)++是非法的(參見(jiàn)3_1_operator++.c中l(wèi)ine11),所以應(yīng)該從第3個(gè)+處斷句,最后剩下的++b大家應(yīng)該一眼就看出它是一個(gè)有意義的表達(dá)式了。因此整句話斷句為:c=a+++++b;它相當(dāng)于是c=(a++)+(++b);實(shí)踐中++的使用。實(shí)際上大家驗(yàn)證,c=a+++++b;這樣的表達(dá)式編譯是通不過(guò)的,也就是說(shuō)C語(yǔ)言的編譯器不接受這樣的書(shū)寫(xiě)。所以上面的題目純屬“理論探索”,實(shí)際開(kāi)發(fā)中是用不上的。但是斷句后的書(shū)寫(xiě)編譯是可以通過(guò)的(具體參看章節(jié)示例代碼3_1_operator++.c)。在這里要強(qiáng)調(diào)的是:實(shí)際編碼中不鼓勵(lì)(甚至是深惡痛絕)這種編碼風(fēng)格,這種表達(dá)式是違背代碼的可讀性原則的。不使用這種風(fēng)格,我們同樣可以寫(xiě)出效率高、可讀性強(qiáng)、干凈漂亮的代碼。真理就是:代碼的復(fù)雜度應(yīng)該是由代碼所解決的問(wèn)題本身的復(fù)雜度決定的,而不是由編碼人員的編程風(fēng)格決定的。譬如使用鏈表存儲(chǔ)及操作一些學(xué)生信息,要比使用結(jié)構(gòu)體數(shù)組來(lái)操作要復(fù)雜。這是由鏈表本身操作難度比數(shù)組要高決定的(當(dāng)然復(fù)雜的自然有它的好處,不然誰(shuí)不愿意簡(jiǎn)單活著)。如果一個(gè)程序員寫(xiě)的數(shù)組操作讀起來(lái)比別人寫(xiě)的鏈表操作還要難讀懂,那你覺(jué)得這個(gè)程序員的水平···是低呢?還是很低呢?如果這樣一個(gè)人寫(xiě)的代碼交給你來(lái)維護(hù),你是不是有種想罵娘的沖動(dòng)了···++跟在指針后面。簡(jiǎn)單變量(如int)的++比較簡(jiǎn)單,a++相當(dāng)于是a+=1。這個(gè)是確定的,你完全不用擔(dān)心哪一天它突然變成了a+=2或者a+=4了。++要是跟在指針后面(或者是前面)就不這么簡(jiǎn)單了。為此我們特意準(zhǔn)備了一個(gè)示例程序,代碼及運(yùn)行結(jié)果見(jiàn)3_2_pointer++.c。從示例中可以看出,指針p進(jìn)行++操作時(shí),它的值實(shí)際增長(zhǎng)多少不是固定的,而是跟指針類型有關(guān)。具體來(lái)說(shuō):int型的指針(int*p)在p++時(shí),指針值實(shí)際增加4;short型指針(short*p)在p++時(shí)指針值實(shí)際增加2;char型指針(char*p)在p++時(shí)指針值實(shí)際增加1。為什么這樣設(shè)計(jì)呢?從示例中可以看出,這樣的設(shè)計(jì)正好保證了指針++之后指向了數(shù)組中下一個(gè)元素(請(qǐng)注意,不是下一個(gè)字節(jié),而是下一個(gè)元素),所以這樣的設(shè)計(jì)最實(shí)用。本來(lái)嘛,指針就是用來(lái)操作地址的。而地址并不都是單字節(jié)間隔的,譬如整形數(shù)據(jù)中,4個(gè)字節(jié)才是一個(gè)地址單元,此時(shí)指針加1指向的位置是一個(gè)整型數(shù)的中部,直接訪問(wèn)是一般是沒(méi)有意義的。可以結(jié)合圖3_2_1來(lái)理解。結(jié)合示例程序3_2_pointer++.c很容易看出,圖中的0xbfac13xx是指針p的值,也就是一系列的內(nèi)存地址,而12、34等等是內(nèi)存單元中的值。圖中用綠色和紅色邊框分別示出了p++(注意p的類型為int*p)和p=(int*)((char*)p+1)兩種不同的操作下,操作前后p所指向內(nèi)存單元的不同。結(jié)合該內(nèi)存分布圖,我相信大家應(yīng)該很容易理解實(shí)驗(yàn)中那個(gè)奇怪的數(shù)字(570425344)是怎么來(lái)的了。3.2、寫(xiě)出floatx與“零值”比較的if語(yǔ)句.解答:if(x>=-0.000001&&x<=0.000001)或#defineEPSINON0.000001if(x>=-EPSINON&&x<=EPSINON)深度解析:C語(yǔ)言中的基本數(shù)據(jù)類型分?jǐn)?shù)值類型與字符類型。字符類型即char,在所有機(jī)器中char型均占1個(gè)字節(jié),字符類型一般用來(lái)存儲(chǔ)一個(gè)ASCII碼字符,因此稱為字符型;數(shù)值類型用來(lái)存儲(chǔ)數(shù)值(即所謂數(shù)字),按照數(shù)值本身的特點(diǎn)又分為整形和浮點(diǎn)型(有些書(shū)成為實(shí)型···我估計(jì)是從數(shù)學(xué)中的實(shí)數(shù)概念叫出來(lái)的。本來(lái)嘛,整數(shù)和非整數(shù)加在一起組成實(shí)數(shù),浮點(diǎn)數(shù)就是用來(lái)描述小數(shù)的,而整數(shù)也可以看作是小數(shù)部分為0的小數(shù),因此浮點(diǎn)數(shù)就對(duì)應(yīng)數(shù)學(xué)中的實(shí)數(shù),所以可以稱為實(shí)型。),整形一般有short、int、long三種,浮點(diǎn)型一般有float和double兩種。先看整形。首先要明白的是,在不同的CPU架構(gòu)下,三種整形變量各自的定義(即其所占有的內(nèi)存大小,所表示的數(shù)值范圍)是不同的。因此,在講這些數(shù)據(jù)類型前,一定要搞清楚自己所針對(duì)的CPU平臺(tái)(再次強(qiáng)調(diào),在未特別說(shuō)明時(shí),本書(shū)均以32位ARM體系為目標(biāo)平臺(tái))。在32位體系中,short(也寫(xiě)作shortint,短整形)占2字節(jié),int(整形)占4字節(jié),long(也寫(xiě)作longint,長(zhǎng)整形)占4字節(jié)。其有符號(hào)和無(wú)符號(hào)的情況下各自表示的數(shù)范圍是多少大家可以自己計(jì)算下(其實(shí)關(guān)鍵的不是那個(gè)數(shù)字,而是要理解其分析方法)。有些人可能覺(jué)得longint有點(diǎn)名不符實(shí):人家int就有4字節(jié)你也4字節(jié)憑什么叫l(wèi)ong?還有些人在書(shū)上看到過(guò)“short和int占用內(nèi)存是相同的”諸如此類的話,加起來(lái)的效果就是搞得人無(wú)所適從、暈頭轉(zhuǎn)向。其實(shí)造成這些困擾的主要原因就是因?yàn)榇蠹以谥v這些話時(shí)沒(méi)搞清楚平臺(tái)。就拿流傳最廣的IntelX86平臺(tái)來(lái)講吧,最開(kāi)始的X86(80386以前)是16位的CPU,那時(shí)候short和int均都是16位(2字節(jié))??墒呛髞?lái)的80386就變成了32位CPU了,那時(shí)候short仍然是16位,而int則變成了32位。還是那句話,在講這些整型類型前先搞清楚自己針對(duì)的平臺(tái),這樣就不會(huì)混亂了。(有些書(shū)和網(wǎng)絡(luò)資料在講到這些問(wèn)題時(shí),直接以自己的平臺(tái)為例得出了結(jié)論,而沒(méi)有指明是適用于特定平臺(tái)的,造成了大家的困擾。)其實(shí)從上面X86平臺(tái)16位到32位的遷移過(guò)程可以看出,C語(yǔ)言原生類型short、int、long這些是非常不靠譜的(一樣的代碼,今天還是2字節(jié),明天就變成4字節(jié)了,讓人情何以堪······)。所以很多專業(yè)的項(xiàng)目中一般都不推薦使用C語(yǔ)言原生類型,而都是使用typedef關(guān)鍵字,以原生類型來(lái)定義(準(zhǔn)確的說(shuō)叫重命名)一些新的類型以供使用,例如:typedefunsignedintu32;typedefsignedints32;typedefunsignedshortu16;typedefunsignedlongu32;typedefvolatileunsignedintvu32;以上的類型重定義簡(jiǎn)潔明了,可以使代碼更加快捷清爽;而有些重命名的類型則暗示了它的用途,如linux中的size_t;另外有些軟件項(xiàng)目甚至?xí)谥孛麜r(shí)添加上自己的項(xiàng)目名稱,以表示這是項(xiàng)目專用的類型定義。這樣使用typedef重命名的專用類型,在CPU平臺(tái)遷移后,只需要相應(yīng)修改這些typedef語(yǔ)句即可,而不需要修改整個(gè)代碼。與編程中使用宏定義有異曲同工之妙,這就是可移植性。再看浮點(diǎn)型,有單精度(float,注意C語(yǔ)言中沒(méi)有Single關(guān)鍵字,單精度是float)和雙精度(double)兩種。其差別在于:float占用的內(nèi)存要少于double(具體是幾個(gè)字節(jié)也是平臺(tái)相關(guān)的,暫不細(xì)究),相應(yīng)的float所表示的數(shù)范圍大小以及精度(精度指的是小數(shù)點(diǎn)后精確到第幾位)也小于double。關(guān)于浮點(diǎn)型,要注意以下幾點(diǎn):浮點(diǎn)型都是有符號(hào)的,不存在無(wú)符號(hào)浮點(diǎn)型。所以千萬(wàn)不要寫(xiě)出unsignedfloat或unsigneddouble(這個(gè)錯(cuò)誤倒是不太用擔(dān)心,因?yàn)榫幾g根本通不過(guò),編譯器會(huì)幫你找到這個(gè)錯(cuò)誤并提示你修正的。)。浮點(diǎn)型在內(nèi)存中的存儲(chǔ)方式和整形不同。整形是直接以二進(jìn)制方式存儲(chǔ)在內(nèi)存中,因此譬如對(duì)于145這個(gè)數(shù)字來(lái)說(shuō),其以char型或者short型或者int型在內(nèi)存中存在時(shí),內(nèi)存映像是相同的;而浮點(diǎn)數(shù)則是以指數(shù)形式表示,即以有效數(shù)字和指令冪的方式來(lái)描述的(在內(nèi)存中有一些位負(fù)責(zé)存儲(chǔ)有效數(shù)字,另一些位負(fù)責(zé)存儲(chǔ)指數(shù)值。這些位越多則表示的數(shù)范圍越大,精度越高。)在32位系統(tǒng)中float占4字節(jié),而double占8字節(jié)。因此double的數(shù)值范圍和精度要高于float。浮點(diǎn)數(shù)對(duì)應(yīng)數(shù)學(xué)中的小數(shù)。因此浮點(diǎn)型與0值比較時(shí)可以使用大于號(hào),也可以使用小于號(hào),但是不能使用等于號(hào)。這是因?yàn)樵跀?shù)學(xué)運(yùn)算當(dāng)中,真正的零值是很難得到的。而在

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論