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

下載本文檔

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