



版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
C++從零開始何謂編程引言曾經(jīng)有些人問我問題,問得都是ー些很基礎(chǔ)的問題,但這些人卻己經(jīng)能使用VC編一個(gè)對話框界面來進(jìn)行必耍的操作或者是文檔/視界面來實(shí)時(shí)接收端口數(shù)據(jù)并動態(tài)顯示曲線(還使用了多線程技術(shù)),卻連那些基礎(chǔ)的問題都不淸楚,并且最嚴(yán)重的后果就是導(dǎo)致編寫出拙劣的代碼(雖然是多線程,但真不敢恭維),不清楚類的含義,混雜使用各種可用的技術(shù)來達(dá)到目的(連用異常代替選擇語句都弄出來了),代碼邏輯混亂,感覺就和金山快譯的翻譯效果ー樣。我認(rèn)為任何事情,基礎(chǔ)都是最重要的,并且在做完我自定的最后ー個(gè)項(xiàng)目后我就不再做編程的工作,守著這些經(jīng)驗(yàn)也沒什么意義,在此就用本系列說說我對編程的理解,幫助對電腦編程感興趣的人快速入門(不過也許并不會想象地那么快)。由于我從沒正經(jīng)看完過一本C++的書(都是零碎偶爾翻翻的),并且本系列并不是教條式地將那些該死的名詞及其解釋羅列一遍,而是希望讀者能夠理解編程,而不是學(xué)會ー門語言(即不止會英翻漢,還會漢翻英)。整個(gè)系列全用我自己的理解來寫的,并無參考其他教材(在ー些基礎(chǔ)概念上還是參考了MSDN),所以本系列中的內(nèi)容可能有和經(jīng)典教材不一致的地方,但它們的本質(zhì)應(yīng)該還是ー樣的,只是角度不同而ー。本系列不會仔細(xì)講解C++的每個(gè)關(guān)鍵字(有些并不重要),畢竟H的不是C++語言參考,而是編程入門。如果本系列文章中有未提及的內(nèi)容,還請參考MSDN中的C++語言參考(看完本系列文章后應(yīng)該可能力做這件事了),而本系列給出的內(nèi)容均是以VC編譯器為基礎(chǔ),基于32位Windows操作系統(tǒng)的。下面羅列一下各文章的標(biāo)題和主要內(nèi)容,紅色修飾的文章標(biāo)題表示我認(rèn)為的重點(diǎn)。C++從零開始()——何謂編程(說明編程的真正含義及兩個(gè)重要卻容易被忽略的基礎(chǔ)概念)C++從零開始(二)——何謂表達(dá)式(說明各操作符的用處,但不是全部,剩余的會在其它文章提到)C++從零開始(三)——何謂變量(說明電腦的工作方式,闡述內(nèi)存、地址等極其重要的概念)C++從零開始(四)——賦值操作符(《C++從零開始(二)》的延續(xù),并為指針的解釋打一點(diǎn)基礎(chǔ))C++從零開始(五)一?何謂指針(闡述指針、數(shù)組等重要的概念)C++從零開始(六)——何謂語句(講解C++提供的各個(gè)語句,說明各自存在的理由)C++從零開始(七)ーー何謂函數(shù)(說明函數(shù)及其存在的理由)C++從零開始(ハ)-C++樣例ー(給出ー兩個(gè)簡單算法,ー步步說明如何從算法編寫出C++代碼)C++從零開始(九)——何謂結(jié)構(gòu)(簡要說明結(jié)構(gòu)、枚舉等及其存在的理由)C++從零開始(十)——何謂類(說明類及其存在的理由,以及聲明、定義、頭文件等概念)C++從零開始(十一)ーー類的相關(guān)知識(說明派生、繼承、名字空間、操作符重載等)C++從零開始(十二)——何謂面向?qū)ο缶幊趟枷耄U述何謂編程思想,軍點(diǎn)講述面向?qū)ο缶幊趟枷耄┖沃^程序程序,即過程的順序,準(zhǔn)確地說應(yīng)該是順序排列的多個(gè)過程,其是方法的描述。比如吃菜,先用筷子夾起菜,再用筷子將菜送入嘴中,最后咀嚼并吞下。其中的夾、送、咀嚼和吞下就被稱作命令,而菜則是資源,其狀態(tài)(如形狀、位置等)隨著命令的執(zhí)行而不斷發(fā)生變化。上面就是吃菜這個(gè)方法的描述,也就是吃菜的程序。任何方法都是為了改變某些資源的狀態(tài)而存在,因此任何方法的描述,也就是程序,也都一定有命令這個(gè)東西以及其所作用的資源。命令是山程序的執(zhí)行者來實(shí)現(xiàn)的,比如上面的吃菜,其中的夾、送等都是山吃菜的人來實(shí)現(xiàn)的,而資源則一定是執(zhí)行者可以改變的東西,而命令只是告訴執(zhí)行者如何改變而已。電腦程序和上面一樣,是方法的描述,而這些方法就是人期望電腦能做的事(注意不是電腦要做的事,這經(jīng)常一直混淆著許多人),當(dāng)人需要做這些事時(shí),人再給出某些資源以期電腦能對其做正確的改變。如計(jì)算圓周率的程序,其只是方法的描述,本身是不能發(fā)生任何效用的,直到它被執(zhí)行,人為給定它ー塊內(nèi)存(關(guān)于內(nèi)存,請參考《C++從零開始(三)》),告訴它計(jì)算結(jié)果的精度及計(jì)算結(jié)果的存放位置后,其オ改變?nèi)藶榻o定的這塊內(nèi)存的狀態(tài)以表現(xiàn)出計(jì)算結(jié)果。
因此,對于電腦程序,命令就是CPU的指令,而執(zhí)行者也就由于是CPU的指令而必須是CPU了,而最后的資源則就是CPU可以改變其狀態(tài)的內(nèi)存(當(dāng)然不止,如端口等,不過?般應(yīng)用程序都大量使用內(nèi)存罷了)。所以,電腦程序就是電腦如何改變給定資源(一般是內(nèi)存,也可以是其他硬件資源)的描述,注意是描述,本身沒有任何意義,除非被執(zhí)行。何謂編程編程就是編寫程序,即制訂方法。為什么要有方法?方法是為了說明。而之所以耍有說明就有很多原因了,但電腦編程的根本原因是因?yàn)檎Z言不同,且不僅不同,連概念都不相通。人類的語言五花八門,但都可以通過翻譯得到正解,因?yàn)槿祟惿嬖谕粋€(gè)四維物理空間中,具有相同或類似的感知。而電腦程序執(zhí)行時(shí)的CPU所能感受到的空間和物理空間嚴(yán)重不同,所以是不可能將電腦程序翻譯成人類語言的描述的。這很重要,其導(dǎo)致了大部分程序員編寫出的拙劣代碼,因?yàn)槿讼氲暮碗娔X想的沒有共性,所以他們在編寫程序時(shí)就隨機(jī)地?zé)oH的地編寫,進(jìn)而導(dǎo)致了拙劣卻可以執(zhí)行的代碼。電腦的語言就是CPU的指令,因?yàn)镃PU就這ー個(gè)感知途徑(準(zhǔn)確地說還有內(nèi)存定位、中斷響應(yīng)等感知途徑),不像人類還能有肢體語言,所以電腦編程就是將人類語言書寫的方法翻譯成相應(yīng)的電腦語言,是?個(gè)翻譯過程。這完全不同于一般的翻譯,由于前面的紅字,所以是不可能翻譯的。既然不可能翻譯,那電腦編程到底是干甚?考慮?個(gè)木匠,我是客人。我對木匠說我要一把搖椅,躺著很舒服的那種。然后木匠開始刨木頭,按照一個(gè)特殊的曲線制作搖椅下面的曲木以保證我搖的時(shí)候重心始終不變以感覺很舒服。這里我編了個(gè)簡單的程序,只有?條指令——做一把搖著很舒服的搖椅。而木匠則將我的程序翻譯成了刨木頭、設(shè)計(jì)特定的曲木等ー系列我看不懂的程序。之所以會這樣,在這里就是因?yàn)槲疑畹目臻g和木工(是木工エ藝,不是木匠)沒有共性。這里木匠就相當(dāng)于電腦程序員兼CPU(因?yàn)樽詈笥赡窘硜碇谱鲹u椅),而木匠的手藝就是CPU的指令定義,而木匠就將我的程序翻譯成r木工的一些規(guī)程,由木匠通過其手藝來實(shí)現(xiàn)這些規(guī)程,也就是執(zhí)行程序。上面由于我生活的空間和木工(指木工エ藝,不是工人)沒有共性,所以是不可能翻譯的,但上面翻譯成功了,實(shí)際是沒有翻譯的。在木工眼中,那個(gè)搖椅只是ー些直木和曲木的拼接而已,因?yàn)槟竟た臻g中根本沒有搖椅的概念,只是我要把那堆木頭當(dāng)作搖椅,進(jìn)而使用。如果我把那堆木頭當(dāng)作兇器,則它就是兇器,不是什么搖椅了。“廢話加荒謬加放屁!”,也許你會這么大叫,但電腦編程就是這么一回事。CPU只能感知指令和改變內(nèi)存的狀態(tài)(不考慮其他的硬件資源及響應(yīng)),如果我們編寫了?個(gè)計(jì)算圓周率的程序,給出了…塊內(nèi)存,并執(zhí)行,完成后就看見電腦的屏幕顯示正確的結(jié)果。但一定注意,這里電腦實(shí)際只是將一些內(nèi)存的數(shù)值復(fù)制、加減、乘除而已,電腦并不知道那是圓周率,而如果執(zhí)行程序的人不把它說成是圓周率那么那個(gè)結(jié)果也就不是圓周率了,可能是一個(gè)隨機(jī)數(shù)或其他什么的,只是運(yùn)氣極好地和圓周率驚人地相似。上面的東西我將其稱為語義,即語言的意義,其不僅僅可應(yīng)用在電腦編程方面,實(shí)際上許多技術(shù),如機(jī)械、電子、數(shù)學(xué)等都有自己的語言,而那些設(shè)計(jì)師則負(fù)責(zé)將客戶的簡單程序翻譯成相應(yīng)語言描述的程序。作為ー個(gè)程序員是極其有必要了解到語義的重要性的(我在我的另一篇文章《語義的需要》中對代碼級的語義做過較詳細(xì)的闡述,有興趣可以參考之),在后續(xù)的文章中我還將提到語義以及其對編程的影響,如果你還沒有理解編程是什么意思,隨著后續(xù)文章的閱讀應(yīng)該能夠越來越明了。電腦編程的基礎(chǔ)知識——編譯器和連接器我從沒見過(不過應(yīng)該有)任何一本C++教材有講過何謂編譯器(Compiler)及連接器(Linker)(倒是在很老的C教材中見過),現(xiàn)在都通過一個(gè)類似VC這樣的編程環(huán)境隱藏了大量東西,將這些封裝起來。在此,對它們的理解是非常重要的,木系列后面將大量運(yùn)用到這兩個(gè)詞匯,其決定了能否理解如聲明、定義、外部變量、頭文件等非常重要的關(guān)鍵。前面已經(jīng)說明了電腦編程就是ー個(gè)“翻譯”過程,要把用戶的程序翻譯成CPU指令,其實(shí)也就是機(jī)器代碼。所謂的機(jī)器代碼就是用CPU指令書寫的程序,被稱作低級語言。而程序員的工作就是編寫出機(jī)器代碼。由于機(jī)器代碼完全是ー些數(shù)字組成(CPU感知的一切都是數(shù)字,即使是指令,也只是1代表加法、2代表減法這ー類的數(shù)字和工作的
映射),人要記住1是代表加法、2是代表減法將比較困難,并且還要記住第3塊內(nèi)存中放的是圓周率,而第4塊內(nèi)存中放的是有效位數(shù)。所以發(fā)明了匯編語言,用ー些符號表示加法而不再用1了,如用ADD表示加法等。由于使用了匯編語言,人更容易記住了,但是電腦無法理解(其只知道1是加法,不知道ADD是加法,因?yàn)殡娔X只能看見數(shù)字),所以必須有個(gè)東西將匯編代碼翻譯成機(jī)器代碼,也就是所謂的編譯器。即編譯器是將一種語言翻譯成另一種語言的程序。即使使用了匯編語言,但由于其幾乎只是將CPU指令中的數(shù)字映射成符號以幫助記憶而已,還是使用的電腦的思考方式進(jìn)行思考的,不夠接近人類的思考習(xí)慣,故而出現(xiàn)了紛繁復(fù)雜的各種電腦編程語言,如:PASCAL.BASIC.C等,其被稱作高級語言,因?yàn)楸容^接近人的思考模式(尤其C++的類的概念的推出),而匯編語言則被稱作低級語言(C曾被稱作高級的低級語言),因?yàn)樗鼈儾皇呛芊先祟惖乃伎寄J?,人類書寫起來比較困難。由于CPU同樣不認(rèn)識這些PASCAL.BASIC等語言定義的符號,所以也同樣必須有一個(gè)編譯器把這些語言編寫的代碼轉(zhuǎn)成機(jī)器代碼。對于這里將要講到的C++語言,則是C++語言編譯器(以后的編譯器均指C++語言編譯器)。因此,這里所謂的編譯器就是將我們書寫的C++源代碼轉(zhuǎn)換成機(jī)器代碼。由于編譯器執(zhí)行ー個(gè)轉(zhuǎn)換過程,所以其可以對我們編寫的代碼進(jìn)行ー些優(yōu)化,也就是說其相當(dāng)于是ー個(gè)CPU指令程序員,將我們提供的程序翻譯成機(jī)器代碼,不過它的工作要簡單ー些了,因?yàn)閺娜祟惖乃伎挤绞睫D(zhuǎn)成電腦的思考方式這ー過程已經(jīng)由程序員完成了,而編譯器只是進(jìn)行翻譯罷了(最多進(jìn)行一些優(yōu)化)。還有一種編譯器被稱作翻譯器(Translator),其和編譯器的區(qū)別就是其是動態(tài)的而編譯器是靜態(tài)的。如前面的BASIC的編譯器在早期版本就被稱為翻譯器,因?yàn)槠涫窃谶\(yùn)行時(shí)期即時(shí)進(jìn)行翻譯工作的,而不像編譯器一次性將所有代碼翻成機(jī)器代碼。對于這里的“動態(tài)”、“靜態(tài)”和“運(yùn)行時(shí)期”等名詞,不用刻意去理解它,隨著后續(xù)文章的閱讀就會了解了。編譯器把編譯后(即翻譯好的)的代碼以一定格式(對于VC,就是COFF通用對象文件格式,擴(kuò)展名為.obj)存放在文件中,然后再由連接器將編譯好的機(jī)器代碼按一定格式(在Windows操作系統(tǒng)下就是PortableExecutableFileFormat——PE文件格式)存儲在文件中,以便以后操作系統(tǒng)執(zhí)行程序時(shí)能按照那個(gè)格式找到應(yīng)該執(zhí)行的第一條指令或其他東西,如資源等。至于為什么中間還要加一個(gè)連接器以及其它細(xì)節(jié),在后續(xù)文章中將會進(jìn)ー步說明。也許你還不能了解到上面兩個(gè)概念的重要性,但在后續(xù)的文章中,你將會發(fā)現(xiàn)它們是如此的重要以至于完全有必要在這嘮叨一番。C++從零開始(二)——何謂表達(dá)式本篇是此系列的開頭,在學(xué)英語時(shí),第一時(shí)間學(xué)的是字母,其是英語的基礎(chǔ)。同樣,在C++中,所有的代碼都是通過標(biāo)識符(Identifier).表達(dá)式(Expression)和語句(Statement)及ー些必要的符號(如大括號等)組成,在此先說明何謂標(biāo)識符。標(biāo)識符標(biāo)識符是一個(gè)字母序列,由大小寫英文字母.下劃線及數(shù)字組成,用于標(biāo)識。標(biāo)識就是標(biāo)出并識別,也就是名字。其可以作為后面將提到的變量或者函數(shù)或者類等的名字,也就是說用來標(biāo)識某個(gè)特定的變量或者函數(shù)或者類等C++中的元素。比如:abc就是ー個(gè)合法的標(biāo)識符,即abc可以作為變量.函數(shù)等元素的名字,但并不代表abc就是某個(gè)變量或函數(shù)的名字,而所謂的合法就是任何ー個(gè)標(biāo)識符都必須不能以數(shù)字開頭,只能包括大小寫英文字母.下劃線及數(shù)字,不能有其它符號,如,!’等,并且不能與C++關(guān)鍵字相同。也就是我們在給一個(gè)變量或函數(shù)起名字的時(shí)候,必須將起的名字看作是ー個(gè)標(biāo)識符,并進(jìn)而必須滿足上面提出的要求。如12ab_C就不是ー個(gè)合法的標(biāo)識符,因此我們不能給某個(gè)變量或函數(shù)起12abエ這樣的名字;ab」2c就是合法的標(biāo)識符,因此可以被用作變量或函數(shù)的名字。前面提到關(guān)鍵字,在后續(xù)的語句及一些聲明修飾符的介紹中將發(fā)現(xiàn),C++提供了一些特殊的標(biāo)識符作為語句的名字,用以標(biāo)識某ー特定語句,如if、while等;或者提供ー些修飾符用以修飾變量.函數(shù)等元素以實(shí)現(xiàn)語義或給編譯器及連接器提供ー些特定信息以進(jìn)行優(yōu)化、查錯(cuò)等操作,如extern,static等。因此在命名變量或函數(shù)或其他元素時(shí),不能使用if、extern等這種C++關(guān)鍵字作為名字,否則將導(dǎo)致編譯器無法確認(rèn)是ー個(gè)變量(或函數(shù)或其它C++元素)還是一條語句,進(jìn)而無法編譯。如果要讓某個(gè)標(biāo)識符是特定變量或函數(shù)或類的名字,就需要使用聲明,在后續(xù)的文章中再具體說明。數(shù)字C++作為電腦編程語言,電腦是處理數(shù)字的,因此C++中的基礎(chǔ)東西就是數(shù)字。C++中提供兩種數(shù)字:整型數(shù)和浮點(diǎn)數(shù),也就是整數(shù)和小數(shù)。但由于電腦實(shí)際并不是想象中的數(shù)字化的(詳情參見《C++從零開始(三)》中的類型ー節(jié)),所以整型數(shù)又分成了有符號和無符號整型數(shù),而浮點(diǎn)數(shù)則由精度的區(qū)別而分成單精度和雙精度浮點(diǎn)數(shù),同樣的整型數(shù)也根據(jù)長度分成長整型和短整型。要在C++代碼中表示一個(gè)數(shù)字,直接書寫數(shù)字即可,如:123、34.23、ー34.34等。由于電腦并非以數(shù)字為基礎(chǔ)而導(dǎo)致了前面數(shù)字的分類,為了在代碼中表現(xiàn)出來,C++提供了一系列的后綴進(jìn)行表示,如下:u或U表示數(shù)字是無符號整型數(shù),如:123u,但并不說明是長整型還是短整型1或L表示數(shù)字是長整型數(shù),如:1231;而123ul就是無符號長整型數(shù):而34.41就是長雙精度浮點(diǎn)數(shù),等效于雙精度浮點(diǎn)數(shù)i64或164表示數(shù)字是長長整型數(shù),其是為64位操作系統(tǒng)定義的,長度比長整型數(shù)長。如:43i64f或F表示數(shù)字是單精度浮點(diǎn)數(shù),如:12.3fe或E表示數(shù)字的次哥,如:34.4e-2就是0.344;0.2544e3f表示一個(gè)單精度浮點(diǎn)數(shù),值為254.4當(dāng)什么后綴都沒寫時(shí),則根據(jù)有無小數(shù)點(diǎn)及位數(shù)來決定其具體類型,如:123表示的是有符號整型數(shù),而12341434則是有符號長整型數(shù):而34.43表示雙精度浮點(diǎn)數(shù)。為什么要搞這么多事出來,還分什么有符號無符號之類的?這全是因?yàn)殡娔X并非基于數(shù)字的,而是基于狀態(tài)的,詳情在下篇中將詳細(xì)說明。作為科學(xué)計(jì)算,可能經(jīng)常會碰到使用非十進(jìn)制數(shù)字,如16進(jìn)制、8進(jìn)制等,C++也為此提供了一些前綴以進(jìn)行支持。在數(shù)字前面加ヒOx或0X表示這個(gè)數(shù)字是16進(jìn)制表示的,如:0xF3Fa、OxllcF。而在前面加一個(gè)〇則表示這個(gè)數(shù)字是用8進(jìn)制表示的,如:0347,變?yōu)槭M(jìn)制數(shù)就為231。但16進(jìn)制和8進(jìn)制都不能用于表示浮點(diǎn)數(shù),只能表示整型數(shù),即0x34.343是錯(cuò)誤的。字符串C++除了提供數(shù)字這種最基礎(chǔ)的表示方式外,還提供了字符及字符串。這完全只是出于方便編寫程序面提供的,C++作為電腦語言,根本沒有提供字符串的必要性。不過由于人對電腦的基本要求就是顯示結(jié)果,而字符和字符串都由于是人易讀的符號面被用于顯示結(jié)果,所以C++專門提供了對字符串的支持。前面說過,電腦只認(rèn)識數(shù)字,而字符就是文字符號,是ー種圖形符號。為了使電腦能夠處理符號,必須通過某種方式將符號變成數(shù)字,在電腦中這通過在符號和數(shù)字之間建立一個(gè)映射來實(shí)現(xiàn),也就是ー個(gè)表格。表格有兩列,一列就是我們欲顯示的圖形符號,而另一列就是ー個(gè)數(shù)字,通過這么ー張表就可以在圖形符號和數(shù)字之間建立映射?,F(xiàn)在已經(jīng)定義出ー標(biāo)準(zhǔn)表,稱為ASCII碼表,幾乎所有的電腦硬件都支持這個(gè)轉(zhuǎn)換表以將數(shù)字變成符號進(jìn)面顯示計(jì)算結(jié)果。有了上面的表,當(dāng)想說明結(jié)果為“A”時(shí),就查ASCII碼表,得到“A”這個(gè)圖形符號對應(yīng)的數(shù)字是65,然后就告訴電腦輸出序號為65的字符,最后屏幕上顯示“A”。這明顯地繁雜得異常,為此C++就提供了字符和字符串。當(dāng)我們想得到某ー個(gè)圖形符號的ASCII碼表的序號時(shí),只需通過單引號將那個(gè)字符括起來即可,如:’A',其效果和65是ー樣的。當(dāng)要使用不止ー個(gè)字符時(shí),則用雙引號將多個(gè)字符括起來,也就是所謂的字符串了,如:"ABC"。因此字符串就是多個(gè)字符連起來面已。但根據(jù)前面的說明易發(fā)現(xiàn),字符串也需要映射成數(shù)字,但它的映射就不像字符那么簡單可以通過查表就搞定的,對于此,將在后續(xù)文章中對數(shù)組作過介紹后再說明。操作符電腦的基本是數(shù)字,那么電腦的所有操作都是改變數(shù)字,因此很正常地C++提供了操作數(shù)字的ー些基本操作,稱作操作符(Operator),如:+-*/等。任何操作符都要返回一個(gè)數(shù)字,稱為操作符的返回值,因此操作符就是操作數(shù)字并返回?cái)?shù)字的符號。作為一般性地分類,按操作符同時(shí)作用的數(shù)字個(gè)數(shù)分為一元、二元和三元操作符。一元操作符有:+其后接數(shù)字,原封不動地返回后接的數(shù)字。如:+4.4f的返回值是4.4;+-9.3f的返回值是ー9.3。完全是出于語義的需要,如表示此數(shù)為正數(shù)。其后接數(shù)字,將后接的數(shù)字的符號取反。如:-34.4f的返回值是ー34.4:-(-54)的返回值是54〇用于表示負(fù)數(shù)。!其后接數(shù)字,邏輯取反后接的數(shù)字。邏輯值就是“真”或“假”,為了用數(shù)字表示邏輯值,在C++中規(guī)定,非零值即為邏輯真,而零則為邏輯假。因此3、43.4、‘A’都表示邏輯真,而。則表示邏輯假。邏輯值被應(yīng)用于后續(xù)的判斷及循環(huán)語句中。而邏輯取反就是先判斷“!”后面接的數(shù)字是邏輯真還是邏輯假,然后再將相應(yīng)值取反。如:!5的返回值是〇,因?yàn)橄扔?非零而知是邏輯真,然后取反得邏輯假,故最后返回〇。!!345.4的返回值是1,先因345.4非零得邏輯真,取反后得邏輯假,再取反得邏輯真。雖然只要非零就是邏輯真,但作為編譯器返回的邏輯真,其一律使用1來代表邏輯真。、其后接數(shù)字,取反后接的數(shù)字。取反是邏輯中定義的操作,不能應(yīng)用于數(shù)字。為了對數(shù)字應(yīng)用取反操作,電腦中將數(shù)字用二進(jìn)制表示,然后對數(shù)字的每一位進(jìn)行取反操作(因?yàn)槎M(jìn)制數(shù)的每一位都只能為1或〇,正好符合邏輯的真和假)。如?123的返回值就為ー124。先將123轉(zhuǎn)成二進(jìn)制數(shù)01111011,然后各位取反得10000100,最后得-124。這里的問題就是為什么是8位而不是!6位二進(jìn)制數(shù)。因?yàn)?23小于!28,被定位為char類型,故為8位(關(guān)于char是什么將下篇介紹)。如果是?123ul,則返回值為4294967172。為什么要有數(shù)字取反這個(gè)操作?因?yàn)镃PU提供了這樣的指令。并且其還有著很不錯(cuò)且很重要的應(yīng)用,后面將介紹。關(guān)于其他的一元操作符將在后續(xù)文章中陸續(xù)提到(但不一定全部提到)。二元操作符有:+*/%其前后各接ー數(shù)字,返回兩數(shù)字之和、差、積、商、余數(shù)。如:34+4.4f的返回值是38.4;3+-9.3f的返回值是-6.3。34-4的返回值是30;5-234的返回值是ー229。3*2的返回值是6;10/3的返回值是3,10%3的返回值是1;20%7的返回值是6。&&II其前后各接ー邏輯值,返回兩邏輯值之“與”運(yùn)算邏輯值和“或”運(yùn)算邏輯值。如:'A'&&34.3f的返回值是邏輯真,為1;34&&0的返回值是邏輯假,為〇。0|I'B’的返回值是邏輯真,為1;0||0的返回值是邏輯假,為〇。&I"其前后各接ー數(shù)字,返回兩數(shù)字之“與”運(yùn)算、“或”運(yùn)算、“異或”運(yùn)算值。如前面所說,先將兩側(cè)的數(shù)字轉(zhuǎn)成二進(jìn)制數(shù),然后對各位進(jìn)行與、或、異或操作。如:4&6的返回值是4,4轉(zhuǎn)為00000100,6轉(zhuǎn)為00000110各位相與得,00000100,為4。4|6的返回值是6,4轉(zhuǎn)為00000100,6轉(zhuǎn)為00000110各位相或得,00000110,為6。4~6的返回值是2,4轉(zhuǎn)為00000100,6轉(zhuǎn)為00000110各位相異或得,00000010,為2。>=<=!=其前后各接ー數(shù)字,根據(jù)兩數(shù)字是否大于、小于、等于、大于等于、小于等于及不等于而返回相應(yīng)的邏輯值。如:34>34的返回值是〇,為邏輯假:32<345的返回值為1,為邏輯真。23>=23和23>=14的返回值都是1,為邏輯真;54く=4的返回值為〇,為邏輯假。56==6的返回值是0,為邏輯假:45==45的返回值是1,為邏輯真。5!=5的返回值是〇,為邏輯假;5!=35的返回值是真,為邏輯真。??其前后各接ー數(shù)字,將左側(cè)數(shù)字右移或左移右側(cè)數(shù)字指定的位數(shù)。與前面的?、&、丨等操作ー樣,之所以要提供左移、右移操作主要是因?yàn)镃PU提供了這些指令,主要用于編ー些基于二進(jìn)制數(shù)的算法。くく將左側(cè)的數(shù)字轉(zhuǎn)成二進(jìn)制數(shù),然后將各位向左移動右側(cè)數(shù)值的位數(shù),如;4,轉(zhuǎn)為00000100,左移2位,則變成00010000,得16o>>與くくー樣,只不過是向右移動罷了。如;6,轉(zhuǎn)為00000110,右移1位,變成00000011,得3。如果移2位,則有一位超出,將截?cái)?,則6>>2的返回值就是00000001,為1。左移和右移有什么用?用于一些基于二進(jìn)制數(shù)的算法,不過還可以順便作為ー個(gè)簡單的優(yōu)化手段??紤]十進(jìn)制數(shù)3524,我們將它左移2位,變成352400,比原數(shù)擴(kuò)大了100倍,準(zhǔn)確的說應(yīng)該是擴(kuò)大了10的2次方倍。如果將3524右移2位,變成35,相當(dāng)于原數(shù)除以100的商。同樣,前面4>>2,等效于4/4的商;32>>3相當(dāng)于32/8,即相當(dāng)于32除以2的3次方的商。而4<<2等效于4*4,相當(dāng)于4乘以2的2次方。因此左移和右移相當(dāng)于乘法和除法,只不過只能是乘或除相應(yīng)進(jìn)制數(shù)的次方罷了,但它的運(yùn)行速度卻遠(yuǎn)遠(yuǎn)高于乘法和除法,因此說它是ー種簡單的優(yōu)化手段。,其前后各接ー數(shù)字,簡單的返回其右側(cè)的數(shù)字。如:34.45f,54的返回值是54;-324,4545f的返回值是4545f。那它到底有什么用?用于將多個(gè)數(shù)字整和成一個(gè)數(shù)字,在《C++從零開始(四)》中將進(jìn)ー步說明。關(guān)于其他的二元操作符將在后續(xù)文章中陸續(xù)提到(但不一定全部提到)。三元操作符只有一個(gè),為?:,其格式為:く數(shù)字1>?く數(shù)字2>:く數(shù)字3>。它的返回值為:如果〈數(shù)字1>是邏輯真,返回〈數(shù)字2〉,否則返回〈數(shù)字3>。如;34?4:2的返回值就是4,因?yàn)?4非零,為邏輯真,返回4。而〇?4:2的返回值就是2,因?yàn)椹枮檫壿嫾?,返?。表達(dá)式你應(yīng)該發(fā)現(xiàn)前面的荒謬之處了——12>435返回值為0,那為什么不直接寫0還吃飽了撐了寫個(gè)12>435在那?這就是表達(dá)式的意義了。前面說“>”的前后各接ー數(shù)字,但是操作符是操作數(shù)字并返回?cái)?shù)字的符號,因?yàn)樗祷財(cái)?shù)字,因此可以放在上面說的任何ー個(gè)要求接數(shù)字的地方,也就形成了所謂的表達(dá)式。如:23*54/45>34的返回值就是0.因?yàn)?3*54的返回值為1242;然后又將1242作為“/”的左接數(shù)字,得到新的返回值27.6;最后將27.6作為“ゾ的左接數(shù)字進(jìn)而得到返回值〇,為邏輯假。因此表達(dá)式就是由一系列返回?cái)?shù)字的東西和操作符組合而成的一段代碼,其由于是由操作符組成的,故一定返回值。而前面說的“返回?cái)?shù)字的東西”則可以是另一個(gè)表達(dá)式,或者ー個(gè)變量,或者ー個(gè)具有返回值的函數(shù),或者具有數(shù)字類型操作符重載的類的對象等,反正只要是能返回一個(gè)數(shù)字的東西。如果對于何謂變量、函數(shù)、類等這些名詞感到陌生,不需要去管它們,在后繼的文章中將會ーー說明。因此34也是一個(gè)表達(dá)式,其返回值為34,只不過是沒有操作符的表達(dá)式罷了(在后面將會了解到34其實(shí)是ー種操作符)。故表達(dá)式的概念其實(shí)是很廣的,只要有返回值的東西就可以稱為表達(dá)式。由于表達(dá)式里有很多操作符,執(zhí)行操作符的順序依賴丁?操作符的優(yōu)先級,就和數(shù)學(xué)中的ー樣,?、/的優(yōu)先級大于+、而+、一又大于〉、〈等邏輯操作符。不用去刻意記住操作符的優(yōu)先級,當(dāng)不能確定操作符的執(zhí)行順序時(shí),可以使用小括號來進(jìn)行指定。如:((1+2)*3)+3)/4的返回值為3,而1+2*3+3/4的返回值為7。注意3/4為〇,因?yàn)?/4的商是〇。當(dāng)希望進(jìn)行浮點(diǎn)數(shù)除法或乘法時(shí),只需讓操作數(shù)中的某ー個(gè)為浮點(diǎn)數(shù)即可,如:3/4.0的返回值為0.75。&|~~等的應(yīng)用前面提過邏輯操作符“&&”、“丨丨”、“『’等,作為表示邏輯,其被C++提供一點(diǎn)都不值得驚奇。但是為什么要有ー個(gè)將數(shù)字轉(zhuǎn)成二進(jìn)制數(shù),然后對二進(jìn)制數(shù)的各位進(jìn)行邏輯操作的這么ー類操作符呢?首先是CPU提供了相應(yīng)的指令,并且其還有著下面這個(gè)非常有意義的應(yīng)用??紤]ー十字路口,每個(gè)路口有三盞紅綠燈,分別指明能否左轉(zhuǎn)、ん轉(zhuǎn)及直行。共有12盞,現(xiàn)在要為它編寫ー個(gè)控制程序,不管這程序的功能怎樣,首先需要將紅綠燈的狀態(tài)轉(zhuǎn)化為數(shù)字,因?yàn)殡娔X只知道數(shù)字。所以用3個(gè)數(shù)字分別表示某路口的三盞紅綠燈,因此每個(gè)紅綠燈的狀態(tài)由一個(gè)數(shù)字來表示,假設(shè)紅燈為0,綠燈為1(不考慮黃燈或其他情況)。后來忽然發(fā)現(xiàn),其實(shí)也可以用一個(gè)數(shù)字表示一個(gè)路U的三盞紅綠燈狀態(tài),如用110表示左轉(zhuǎn)綠燈、直行綠燈而右轉(zhuǎn)紅燈。上.面的110是ー個(gè)十進(jìn)制數(shù)字,它的每位實(shí)際都可以為〇、9十個(gè)數(shù)字,但是這里只應(yīng)用到了兩個(gè):〇和1,感覺很浪費(fèi)。故選擇二進(jìn)制數(shù)來表示,還是110,但是是二進(jìn)制數(shù)了,轉(zhuǎn)成十進(jìn)制數(shù)為6,即使當(dāng)為111時(shí)轉(zhuǎn)成十進(jìn)制數(shù)也只是7,比前面的110這個(gè)十進(jìn)制數(shù)小多了,節(jié)約了……??什么??我們在紙上寫數(shù)字235425234一定比寫134這個(gè)數(shù)字要更多地占用紙張(假設(shè)字都一樣大)。因此記錄ー個(gè)大的數(shù)比記錄ー個(gè)小的數(shù)要花費(fèi)更多的資源。簡直荒謬!不管是100還是1000,都只是?個(gè)數(shù)字,為什么記錄大的數(shù)字就更費(fèi)資源?因?yàn)殡娔X并不是數(shù)字計(jì)算機(jī),而是電子計(jì)算機(jī),它是基于狀態(tài)而不是基于數(shù)字的,這在下篇會詳細(xì)說明。電腦必須使用某種表示方式來代表一個(gè)數(shù)字,而那個(gè)表示方式和二進(jìn)制很像,但并不是二進(jìn)制數(shù),故出現(xiàn)記錄大的數(shù)較小的數(shù)更耗資源,這也就是為什么上面整型數(shù)要分什么長整型短整型的原因了。下面繼續(xù)上面的思考。使用了110這個(gè)二進(jìn)制數(shù)來表示三盞紅綠燈的狀態(tài),那么現(xiàn)在要知道110這個(gè)數(shù)字代表左轉(zhuǎn)紅綠燈的什么狀態(tài)。以數(shù)字的第三位表示左轉(zhuǎn),不過電腦并不知道這個(gè),因此如下:110&100。這個(gè)表達(dá)式的返回值是100,非零,邏輯真。假設(shè)某路口的狀態(tài)為010,則同樣的010&100,返回值為〇,邏輯假。因此使用“&”操作符可以將二進(jìn)制數(shù)中的某一位或幾位的狀態(tài)提取出來。所以我們要了解ー個(gè)數(shù)字代表的紅綠燈狀態(tài)中的左轉(zhuǎn)紅綠燈是否綠燈時(shí),只需讓它和100相與即可?,F(xiàn)在要保持其他紅綠燈的狀態(tài)不變,僅僅使左轉(zhuǎn)紅綠燈為綠燈,圳當(dāng)前狀態(tài)為010,為了使左轉(zhuǎn)紅綠燈為綠燈,值應(yīng)該為110,這可以通過0101100做到。如果當(dāng)前狀態(tài)是001,則0011100為101,正確——直行和右轉(zhuǎn)的紅綠燈狀態(tài)均沒有發(fā)生變化。因此使用“丨”操作符可以給ー個(gè)二進(jìn)制數(shù)中的某一位或幾位設(shè)置狀態(tài),但只能設(shè)置為1,如果想設(shè)置為〇,如101,要關(guān)掉ん轉(zhuǎn)的綠燈,則101&100,返冋值為001。上面一直提到的路口紅綠燈的狀態(tài)實(shí)際編寫時(shí)可以使用ー個(gè)變量來表示,而上面的100也可以用一?個(gè)標(biāo)識符來表示,如state&TS_LEFT,就可以表示檢查變キtstate所表示的狀態(tài)中的左轉(zhuǎn)紅綠燈的狀態(tài)。上面的這種方法被大量地運(yùn)用,如創(chuàng)建一個(gè)窗口,ー個(gè)窗口可能有二三十個(gè)風(fēng)格,則通過上面的方法,就可以只用ー個(gè)32位長的:進(jìn)制數(shù)字就表示了窗口的風(fēng)格,而不用去弄二三十個(gè)數(shù)字來分別代表每種風(fēng)格是否具有。C++從零開始(三)——何謂變量本篇說明內(nèi)容是C++中的關(guān)鍵,基本大部分人對于這些內(nèi)容都是昏的,但這些內(nèi)容又是編程的基礎(chǔ)中的基礎(chǔ),必須詳細(xì)說明。數(shù)字表示數(shù)學(xué)中,數(shù)只有數(shù)值大小的不同,絕不會有數(shù)值占用空間的區(qū)別,即數(shù)學(xué)中的數(shù)是邏輯上的ー個(gè)概念,但電腦不是??紤]算盤,每個(gè)算盤上有很多列算子,每列都分成上下兩排算子。上排算子有2個(gè),每個(gè)代表5,下排算子有4個(gè),每個(gè)代表1(這并不重要)。因此算盤上的每列共有6個(gè)算子,每列共可以表示〇到14這15個(gè)數(shù)字(因?yàn)樯吓潘阕拥目赡軤顟B(tài)有0到2個(gè)算子有效,而下排算子則可能有0到4個(gè)算子有效,故為3X5=15種組合方式)。上面的重點(diǎn)就是算盤的每列并沒有表示〇到14這!5個(gè)數(shù)字,而是每列有!5種狀態(tài),因此被人利用來表示數(shù)字而已(這很重要)。由于算盤的每列有15個(gè)狀態(tài),因此用兩列算子就可以有15X15=225個(gè)狀態(tài),因此可以表示〇到224〇阿拉伯?dāng)?shù)字的每一位有0到9這!0個(gè)圖形符號,用兩個(gè)阿拉伯?dāng)?shù)字圖形符號時(shí)就能有10X10=100個(gè)狀態(tài),因此可以表示〇到99這!00個(gè)數(shù)。這里的算盤其實(shí)就是一個(gè)基于15進(jìn)制的記數(shù)器(可以通過維持一列算子的狀態(tài)來記錄一位數(shù)字),它的一列算子就相當(dāng)于一位阿拉伯?dāng)?shù)字,每列有!5種狀態(tài),故能表示從0到14這!5個(gè)數(shù)字,超出14后就必須通過進(jìn)位來要求另一列算子的加入以表示數(shù)字。電腦與此ー樣,其并不是數(shù)字計(jì)算機(jī),而是電子計(jì)算機(jī),電腦中通過ー根線的電位高低來表示數(shù)字。ー-根線中的電位規(guī)定只有兩種狀態(tài)——高電位和低電位,因此電腦的數(shù)字表示形式是二進(jìn)制的。和上面的算盤ー樣,ー根電線只有兩個(gè)狀態(tài),當(dāng)要表示超出1的數(shù)字時(shí),就必須進(jìn)位來要求另一根線的加入以表示數(shù)字。所謂的32位電腦就是提供了32根線(被稱作數(shù)據(jù)總線)來表示數(shù)據(jù),因此就有2的32次方那么多種狀態(tài)。而16根線就能表示2的16次方那么多種狀態(tài)。所以,電腦并不是基于二進(jìn)制數(shù),而是基于狀態(tài)的變化,只不過這個(gè)狀態(tài)可以使用二進(jìn)制數(shù)表示出來而已。即電腦并不認(rèn)識二進(jìn)制數(shù),這是下面“類型”ー節(jié)的基礎(chǔ)。內(nèi)存內(nèi)存就是電腦中能記錄數(shù)字的硬件,但其存儲速度很快(與硬盤等低速存儲設(shè)備比較),又不能較長時(shí)間保存數(shù)據(jù),所以經(jīng)常被用做草稿紙,記錄一些臨時(shí)信息。前面已經(jīng)說過,32位計(jì)算機(jī)的數(shù)字是通過32根線上的電位狀態(tài)的組合來表示的,因此內(nèi)存能記錄數(shù)字,也就是能維持32根線上各自的電位狀態(tài)(就好象算盤的算子撥動后就不會改變位置,除非再次撥動它)。不過依舊考慮上面的算盤,假如一個(gè)算盤上有15列算子,則ー個(gè)算盤能表示15的15次方個(gè)狀態(tài),是很大的數(shù)字,但經(jīng)常實(shí)際是不會用到變化那么大的數(shù)字的,因此讓ー個(gè)算盤只有兩列算子,則只能表示225個(gè)狀態(tài),當(dāng)數(shù)字超出時(shí)就使用另ー個(gè)或多個(gè)算盤來一起表示。上面不管是2列算子還是15列算子,都是算盤的粒度,粒度分得過大造成不必要的浪費(fèi)(很多列算子都不使用),太小又很麻煩(需要多個(gè)算盤)。電腦與此ー樣。2的32次方可表示的數(shù)字很大,-?般都不會用到,如果直接以32位存儲在內(nèi)存中勢必造成相當(dāng)大的資源浪費(fèi)。于是如上,規(guī)定內(nèi)存的粒度為8位二進(jìn)制數(shù),稱為ー個(gè)內(nèi)存單元,而其大小稱為ー個(gè)字節(jié)(Byte)〇就是說,內(nèi)存存儲數(shù)字,至少都會記錄8根線上的電位狀態(tài),也就是2的8次方共256種狀態(tài)。所以如果ー個(gè)32位的二進(jìn)制數(shù)要存儲在內(nèi)存中,就需要占據(jù)4個(gè)內(nèi)存單元,也就是4個(gè)字節(jié)的內(nèi)存空間。我們在紙上寫字,是通過肉眼判斷出字在紙上的相對橫坐標(biāo)和縱坐標(biāo)以查找到要看的字或要寫字的位置。同樣,山于內(nèi)存就相當(dāng)于草稿紙,因此也需要某種定位方式來定位,在電腦中,就是通過一個(gè)數(shù)字來定位的。這就和旅館的房間號ー樣,內(nèi)存單元就相當(dāng)于房間(假定每個(gè)房間只能住一個(gè)人),而前面說的那個(gè)數(shù)字就相當(dāng)于房間號。為了向某塊內(nèi)存中寫入數(shù)據(jù)(就是使用某塊內(nèi)存來記錄數(shù)據(jù)總線上的電位狀態(tài)),就必須知道這塊內(nèi)存對應(yīng)的數(shù)字,而這個(gè)數(shù)字就被稱為地址。而通過給定的地址找到對應(yīng)的內(nèi)存單元就稱為尋址。因此地址就是ー個(gè)數(shù)字,用以唯一標(biāo)識某一特定內(nèi)存單元。此數(shù)字一般是32位長的二進(jìn)制數(shù),也就可以表示4G個(gè)狀態(tài),也就是說一般的32位電腦都具有4G的內(nèi)存空間尋址能力,即電腦最多裝4G的內(nèi)存,如果電腦有超過4G的內(nèi)存,此時(shí)就需要增加地址的長度,如用40位長的二進(jìn)制數(shù)來表示。類型在本系列最開頭時(shí)已經(jīng)說明了何渭編程,而剛オ更進(jìn)ー步說明了電腦其實(shí)連數(shù)字都不認(rèn)識,只是狀態(tài)的記錄,而所謂的加法也只是人為設(shè)計(jì)那個(gè)加法器以使得兩個(gè)狀態(tài)經(jīng)過加法器的處理而生成的狀態(tài)正好和數(shù)學(xué)上的加法的結(jié)果ー樣而已。這一切的一切都只說明一點(diǎn):電腦所做的工作是什么,全視使用的人以為是什么。因此為了利用電腦那很快的“計(jì)算”能力(實(shí)際是狀態(tài)的變換能力),人為規(guī)定了如何解釋那些狀態(tài)。為了方便其間,對于前面提出的電位的狀態(tài),我們使用1位二進(jìn)制數(shù)來表示,則上面提出的狀態(tài)就可以使用ー個(gè)二進(jìn)制數(shù)來表示,而所謂的“如何解釋那些狀態(tài)”就變成了如何解釋ー個(gè)二進(jìn)制數(shù)。C++是高級語言,為了幫助解釋那些二進(jìn)制數(shù),提供了類型這個(gè)概念。類型就是人為制訂的如何解釋內(nèi)存中的ニ進(jìn)制數(shù)的協(xié)議。C++提供了下面的ー些標(biāo)準(zhǔn)類型定義。signedchar表示所指向的內(nèi)存中的數(shù)字使用補(bǔ)碼形式,表示的數(shù)字為ー128至U+127,長度為1個(gè)字節(jié)unsignedchar表示所指向的內(nèi)存中的數(shù)字使用原碼形式,表示的數(shù)字為0到255,長度為1個(gè)字節(jié)signedshort表示所指向的內(nèi)存中的數(shù)字使用補(bǔ)碼形式,表示的數(shù)字為-32768至リ+32767,長度為2個(gè)字節(jié)unsignedshort表示所指向的內(nèi)存中的數(shù)字使用原碼形式,表示的數(shù)字為〇到65535,長度為2個(gè)字節(jié)signedlong表示所指向的內(nèi)存中的數(shù)字使用補(bǔ)碼形式,表示的數(shù)字為ー2147483648到+2147483647,長度為4個(gè)字節(jié)unsignedlong表示所指向的內(nèi)存中的數(shù)字使用原碼形式,表示的數(shù)字為〇到4294967295,長度為4個(gè)字節(jié)signedint表示所指向的內(nèi)存中的數(shù)字使用補(bǔ)碼形式,表示的數(shù)字則視編譯器。如果編譯器編譯時(shí)被指明編譯為在16位操作系統(tǒng)上運(yùn)行,則等同于signedshort;如果是編譯為32位的,則等同于signedlong;如果是編譯為在64位操作系統(tǒng)上運(yùn)行,則為8個(gè)字節(jié)長,而范圍則如上一樣可以自行推算出來。unsignedint表示所指向的內(nèi)存中的數(shù)字使用原碼形式,其余和signedintー樣,表示的是無符號數(shù)。bool表示所指向的內(nèi)存中的數(shù)字為邏輯值,取值為false或true。長度為1個(gè)字節(jié)。float表示所指向的內(nèi)存按IEEE標(biāo)準(zhǔn)進(jìn)行解釋,為real*4,占用4字節(jié)內(nèi)存空間,等同于上篇中提到的單精度浮點(diǎn)數(shù)。double表示所指向的內(nèi)存按IEEE標(biāo)準(zhǔn)進(jìn)行解釋,為real*8,可表示數(shù)的精度較float高,占用8字節(jié)內(nèi)存空間,等同于上篇提到的雙精度浮點(diǎn)數(shù)。longdouble表示所指向的內(nèi)存按IEEE標(biāo)準(zhǔn)進(jìn)行解釋,為real*10,可表示數(shù)的精度較double高,但在為32位Windows操作系統(tǒng)編寫程序時(shí),仍占用8字節(jié)內(nèi)存空間,等效于double,只是如果CPU支持此類浮點(diǎn)類型則還是可以進(jìn)行這個(gè)精度的計(jì)算。標(biāo)準(zhǔn)類型不止上面的幾個(gè),后面還會陸續(xù)提到。上面的長度為2個(gè)字節(jié)也就是將兩個(gè)連續(xù)的內(nèi)存單元中的數(shù)字取出并合并在一起以表示一個(gè)數(shù)字,這和前面說的ー個(gè)算盤表示不了的數(shù)字,就進(jìn)位以加入另一個(gè)算盤幫助表示是同樣的道理。上面的signed關(guān)鍵字是可以去掉的,即char等同于signedchar,用以簡化代碼的編寫。但也僅限于signed,如果是unsignedchar,則在使用時(shí)依舊必須是unsignedchar〇現(xiàn)在應(yīng)該已經(jīng)了解上篇中為什么數(shù)字還要分什么有符號無符號、長整型短整型之類的了,面上面的short、char等也都只是長度不同,這就由程序員自己根據(jù)可能出現(xiàn)的數(shù)字變化幅度來進(jìn)行選用了。類型只是對內(nèi)存中的數(shù)字的解釋,但上面的類型看起來相對簡單了點(diǎn),且語義并不是很強(qiáng),即沒有什么特殊意思。為此,C++提供了自定義類型,也就是后繼文章中將要說明的結(jié)構(gòu)、類等。變量在本系列的第一篇中已經(jīng)說過,電腦編程的絕大部分工作就是操作內(nèi)存,而上面說了,為了操作內(nèi)存,需要使用地址來標(biāo)識要操作的內(nèi)存塊的首地址(上面的long表示連續(xù)的4個(gè)字節(jié)內(nèi)存,其第一個(gè)內(nèi)存單元的地址稱作這連續(xù)4個(gè)字節(jié)內(nèi)存塊的首地址)。為此我們在編寫程序時(shí)必須記下地址。做5+2/3-5*2的計(jì)算,先計(jì)算出2/3的值,寫在草稿紙上,接著算出5*2的值,又寫在草稿紙上。為了接下來的加法和減法運(yùn)算,必須能夠知道草稿紙上的兩個(gè)數(shù)字哪個(gè)是2/3的值哪個(gè)是5*2的值。人就是通過記憶那兩個(gè)數(shù)在紙上的位置來記憶的,而電腦就是通過地址來標(biāo)識的。但電腦只會做加減乘除,不會去主動記那些2/3、5*2的中間值的位置,也就是地址。因此程序員必須完成這個(gè)工作,將那兩個(gè)地址記下來。問題就是這里只有兩個(gè)值,也許好記ー些,但如果多了,人是很難記住哪個(gè)地址對應(yīng)哪個(gè)值的,但人對符號比對數(shù)字要敏感得多,即人很容易記下ー個(gè)名字而不是一個(gè)數(shù)字。為此,程序員就自己寫了一個(gè)表,表有兩列,一列是''2/3的值”,一列是對應(yīng)的地址。如果式子稍微復(fù)雜點(diǎn),那么那個(gè)表可能就有個(gè)二三十行,而每寫一行代碼就要去翻查相應(yīng)的地址,如果來個(gè)幾萬行代碼那是人都不能忍受。C++作為高級語言,很正常地提供了上面問題的解決之道,就是由編譯器來幫程序員維護(hù)那個(gè)表,要查的時(shí)候是編譯器去查,這也就是變量的功能。變量是一個(gè)映射元素。上面提到的表由編譯器維護(hù),而表中的每一行都是這個(gè)表的ー個(gè)元素(也稱記錄)。表有三列:變量名、對應(yīng)地址和相應(yīng)類型。變量名是ー個(gè)標(biāo)識符,因此其命名規(guī)則完全按照上一篇所說的來。當(dāng)要對某塊內(nèi)存寫入數(shù)據(jù)時(shí),程序員使用相應(yīng)的變量名進(jìn)行內(nèi)存的標(biāo)識,而表中的對應(yīng)地址就記錄了這個(gè)地址,進(jìn)而將程序員給出的變量名,ー個(gè)標(biāo)識符,映射成一個(gè)地址,因此變量是ー個(gè)映射元素。而相應(yīng)類型則告訴編譯器應(yīng)該如何解釋此地址所指向的內(nèi)存,是2個(gè)連續(xù)字節(jié)還是4個(gè)?是原碼記錄還是補(bǔ)碼?而變量所對應(yīng)的地址所標(biāo)識的內(nèi)存的內(nèi)容叫做此變量的值。有如下的變量解釋:“可變的量,其相當(dāng)于一個(gè)盒子,數(shù)字就裝在盒子里,而變量名就寫在盒子外面,這樣電腦就知道我們要處理哪ー個(gè)盒子,且不同的盒子裝不同的東西,裝字符串的盒子就不能裝數(shù)字?!鄙厦婢褪俏业谝淮螌W(xué)習(xí)編程時(shí),書上寫的(是BASIC語言)。對于初學(xué)者也許很容易理解,也不能說錯(cuò),但是造成的誤解將導(dǎo)致以后的程序編寫地千瘡百孔。上面的解釋隱含了一個(gè)意思——變量是ー塊內(nèi)存。這是嚴(yán)重錯(cuò)誤的!如果變量是一塊內(nèi)存,那么C++中著名的引用類型將被棄置荒野。變量實(shí)際并不是一塊內(nèi)存,只是ー個(gè)映射元素,這是致關(guān)重要的。內(nèi)存的種類前面已經(jīng)說了內(nèi)存是什么及其用處,但內(nèi)存是不能隨便使用的,因?yàn)椴僮飨到y(tǒng)自己也要使用內(nèi)存,而且現(xiàn)在的操作系統(tǒng)正常情況下都是多任務(wù)操作系統(tǒng),即可同時(shí)執(zhí)行多個(gè)程序,即使只有一個(gè)CPU。因此如果不對內(nèi)存訪問加以節(jié)制,可能會破壞另一個(gè)程序的運(yùn)作。比如我在紙上寫了2/3的值,而你未經(jīng)我同意且未通知我就將那個(gè)值擦掉,并寫上5*2的值,結(jié)果我后面的所有計(jì)算也就出錯(cuò)了。因此為了使用ー塊內(nèi)存,需要向操作系統(tǒng)申請,由操作系統(tǒng)統(tǒng)ー管理所有程序使用的內(nèi)存。所以為了記錄ー個(gè)long類型的數(shù)字,先向操作系統(tǒng)申請ー塊連續(xù)的4字節(jié)長的內(nèi)存空間,然后操作系統(tǒng)就會在內(nèi)存中查看,看是否還有連續(xù)的4個(gè)字節(jié)長的內(nèi)存,如果找到,則返回此4字節(jié)內(nèi)存的首地址,然后編譯器編譯的指令將其記錄在前面提到的變量表中,最后就可以用它記錄ー些臨時(shí)計(jì)算結(jié)果了。上面的過程稱為要求操作系統(tǒng)分配ー塊內(nèi)存。這看起來很不錯(cuò),但是如果只為了4個(gè)字節(jié)就要求操作系統(tǒng)搜索一下內(nèi)存狀況,那么如果需要100個(gè)臨時(shí)數(shù)據(jù),就要求操作系統(tǒng)分配內(nèi)存100次,很明顯地效率低下(無謂的99次査看內(nèi)存狀況)。因此C++發(fā)現(xiàn)了這個(gè)問題,并且操作系統(tǒng)也提出了相應(yīng)的解決方法,最后提出了如下的解決之道。棧(Stack)任何程序執(zhí)行前,預(yù)先分配ー固定長度的內(nèi)存空間,這塊內(nèi)存空間被稱作棧(這種說法并不準(zhǔn)確,但由于實(shí)際涉及到線程,在此為了不將問題復(fù)雜化オ這樣說明),也被叫做堆棧。那么在要求一個(gè)4字節(jié)內(nèi)存時(shí),實(shí)際是在這個(gè)已分配好的內(nèi)存空間中獲取內(nèi)存,即內(nèi)存的維護(hù)工作由程序員自己來做,即程序員自己判斷可以使用哪些內(nèi)存,而不是操作系統(tǒng),直到已分配的內(nèi)存用完。很明顯,上面的工作是由編譯器來做的,不用程序員操心,因此就程序員的角度來看什么事情都沒發(fā)生,還是需要像原來那樣向操作系統(tǒng)申請內(nèi)存,然后再使用。但工作只是從操作系統(tǒng)變到程序自己而已,要維護(hù)內(nèi)存,依然要耗費(fèi)CPU的時(shí)間,不過要簡單多了,因?yàn)椴挥脴?biāo)記ー塊內(nèi)存是否有人使用,而專門記錄一個(gè)地址。此地址以上的內(nèi)存空間就是有人正在使用的,而此地址以下的內(nèi)存空間就是無人使用的。之所以是以下的空間為無人使用而不是以上,是當(dāng)此地址減小到。時(shí)就可以知道堆棧溢出了(如果你已經(jīng)有些基礎(chǔ),請不要把〇認(rèn)為是虛擬內(nèi)存地址,關(guān)于虛擬內(nèi)存將會在《C++從零開始(十八)》中進(jìn)行說明,這里如此解釋只是為了方便理解)。而且CPU還專門對此法提供了支持,給出了兩條指令,轉(zhuǎn)成匯編語言就是push和pop,表示壓棧和出棧,分別減小和增大那個(gè)地址。而最重要的好處就是由于程序ー開始執(zhí)行時(shí)就已經(jīng)分配了一大塊連續(xù)內(nèi)存,用ー個(gè)變量記錄這塊連續(xù)內(nèi)存的首地址,然后程序中所有用到的,程序員以為是向操作系統(tǒng)分配的內(nèi)存都可以通過那個(gè)首地址加上相應(yīng)偏移來得到正確位置,而這很明顯地由編譯器做了。因此實(shí)際上等同于在編譯時(shí)期(即編譯器編譯程序的時(shí)候)就已經(jīng)分配了內(nèi)存(注意,實(shí)際編譯時(shí)期是不能分配內(nèi)存的,因?yàn)榉峙鋬?nèi)存是指程序運(yùn)行時(shí)向操作系統(tǒng)申請內(nèi)存,而這里由于使用堆棧,則編譯器將生成一些指令,以使得程序ー開始就向操作系統(tǒng)申請內(nèi)存,如果失敗則立刻退出,而如果不退出就表示那些內(nèi)存已經(jīng)分配到了,進(jìn)而代碼中使用首地址加偏移來使用內(nèi)存也就是有效的),但壞處也就是只能在編譯時(shí)期分配內(nèi)存。堆(Heap)上面的工作是編譯器做的,即程序員并不參與堆棧的維護(hù)。但上面已經(jīng)說了,堆棧相當(dāng)于在編譯時(shí)期分配內(nèi)存,因此一旦計(jì)算好某塊內(nèi)存的偏移,則這塊內(nèi)存就只能那么大,不能變化了(如果變化會導(dǎo)致其他內(nèi)存塊的偏移錯(cuò)誤)。比如要求客戶輸入定單數(shù)據(jù),可能有10份定單,也可能有100份定單,如果ー開始就定好了內(nèi)存大小,則可能造成不必要的浪費(fèi),又或者內(nèi)存不夠。為了解決上面的問題,C++提供了另ー個(gè)途徑,即允許程序員有兩種向操作系統(tǒng)申請內(nèi)存的方式。前ー種就是在棧上分配,申請的內(nèi)存大小固定不變。后一種是在堆上分配,申請的內(nèi)存大小可以在運(yùn)行的時(shí)候變化,不是同定不變的。那么什么叫堆?在Windows操作系統(tǒng)下,由操作系統(tǒng)分配的內(nèi)存就叫做堆,而??梢哉J(rèn)為是在程序開始時(shí)就分配的堆(這并不準(zhǔn)確,但為了不復(fù)雜化問題,故如此說明)。因此在堆上就可以分配大小變化的內(nèi)存塊,因?yàn)槭沁\(yùn)行時(shí)期即時(shí)分配的內(nèi)存,而不是編譯時(shí)期已計(jì)算好大小的內(nèi)存塊。變量的定義上面說了那么多,你可能看得很暈,畢竟連ー個(gè)實(shí)例都沒有,全是文字,下面就來幫助加深對上面的理解。定義一個(gè)變量,就是向上面說的山編譯器維護(hù)的變量表中添加元素,其語法如下:longa;先寫變量的類型,然后一個(gè)或多個(gè)空格或制表符(\t)或其它間隔符,接著變量的名字,最后用分號結(jié)束。要同時(shí)定義多個(gè)變量,則各變量間使用逗號隔開,如下:longa,b,c;unsignedshorte,a_34c;上面是兩條變量定義語句,各語句間用分號隔開,而各同類型變量間用逗號隔開。而前面的式子5+2/3-5*2,則如下書寫。longa=2/3,b=5*2;longc=5+a-b;可以不用再去記那煩人的地址了,只需記著a、b這種簡單的標(biāo)識符。當(dāng)然,上面的式子不一定非要那么寫,也可以寫成:longc=5+2/3-5*2i面那些a、b等中間變量編譯器會自動生成并使用(實(shí)際中編譯器由于優(yōu)化的原因?qū)⒅苯佑?jì)算出結(jié)果,而不會生成實(shí)際的計(jì)算代碼)。下面就是問題的關(guān)鍵,定義變量就是添加一個(gè)映射。前面已經(jīng)說了,這個(gè)映射是將變量名和一個(gè)地址關(guān)聯(lián),因此在定義一個(gè)變量時(shí),編譯器為了能將變量名和某個(gè)地址對應(yīng)起來,幫程序員在前面提到的棧上分配了一塊內(nèi)存,大小就視這個(gè)變量類型的大小。如上面的a、b、c的大小都是4個(gè)字節(jié),而e、a_34c的大小都是2個(gè)字節(jié)。假設(shè)編譯器分配的棧在ー開始時(shí)的地址是100〇,并假設(shè)變量a所對應(yīng)的地址是1000-56,則b所對應(yīng)的地址就是1000-60,而c所對應(yīng)的就是1000-64,e對應(yīng)的是1000-66,a_34c是1000-68。如果這時(shí)b突然不想是4字節(jié)了,面希望是8字節(jié),則后續(xù)的c、e、a_34c都將由于還是原來的偏移位置而使用了錯(cuò)誤的內(nèi)存,這也就是為什么棧上分配的內(nèi)存必須是固定大小??紤]前面說的紅色文字:“變量實(shí)際并不是ー塊內(nèi)存,只是ー個(gè)映射元素”??墒侵灰xー個(gè)變量,就會相應(yīng)地得到一塊內(nèi)存,為什么不說變量就是ー塊內(nèi)存?上面定義變量時(shí)之所以會分配ー塊內(nèi)存是因?yàn)樽兞渴且粋€(gè)映射元素,需要一個(gè)對應(yīng)地址,因此オ在棧上分配了一塊內(nèi)存,并將其地址記錄到變量表中。但是變量是可以有別名的,即另ー個(gè)名字。這個(gè)說法是不準(zhǔn)確的,應(yīng)該是變量所對應(yīng)的內(nèi)存塊有另ー個(gè)名字,而不止是這個(gè)變量的名字。為什么要有別名?這是語義的需要,表示既是什么又是什么。比如ー塊內(nèi)存,里面記錄了老板的信息,因此起名為Boss,但是老板又是另一家公司的行政經(jīng)理,故變量名應(yīng)該為Manager,而在程序中有段代碼是老板的公司相關(guān)的,面另一段是老板所在公司相關(guān)的,在這兩段程序中都要使用到老板的信息,那到底是使用Boss還是Manager?其實(shí)使用什么都不會對最終生成的機(jī)器代碼產(chǎn)生什么影響,但此處出于語義的需要就應(yīng)該使用別名,以期從代碼上表現(xiàn)出所編寫程序的意思。在C++中,為了支持變量別名,提供了引用變量這個(gè)概念。要定義ー個(gè)引用變量,在定義變量時(shí),在變量名的前面加一個(gè),如下書寫:longa;long&al=a,&a2=a,&a3=a2;上面的al、a2、a3都是a所對應(yīng)的內(nèi)存塊的別名。這里在定義變量a時(shí)就在棧上分配了一塊4字節(jié)內(nèi)存,而在定義al時(shí)卻沒有分配任何內(nèi)存,直接將變量a所映射的地址作為變量al的映射地址,進(jìn)而形成對定義a時(shí)所分配的內(nèi)存的別名。因此上面的Boss和Manager,應(yīng)該如F(其中Person是一個(gè)結(jié)構(gòu)或類或其他什么自定義類型,這將在后繼的文章中陸續(xù)說明):PersonBoss;Person&Manager=Boss;由于變量一旦定義就不能改變(指前面說的變量表里的內(nèi)容,不是變量的值),直到其被刪除,所以上面在定義引用變量的時(shí)候必須給出欲別名的變量以初始化前面的變量表,否則編譯器編譯時(shí)將報(bào)錯(cuò)?,F(xiàn)在應(yīng)該就更能理解前面關(guān)于變量的紅字的意思了。并不是每個(gè)變量定義時(shí)都會分配內(nèi)存空間的。而關(guān)于如何在堆上分配內(nèi)存,將在介紹完指針后予以說明,并進(jìn)而說明上ー?篇遺留下來的關(guān)于字符串的問題。C++從零開始(四)——賦值操作符本篇是《C++從零開始(二)》的延續(xù),說明《C++從零開始(二)》中遺留下來的關(guān)于表達(dá)式的內(nèi)容,并為下篇指針的運(yùn)用做一點(diǎn)鋪墊。雖然上篇已經(jīng)說明了變量是什么,但對于變量最關(guān)鍵的東西卻由于篇幅限制面沒有說明,下面先說明如何訪問內(nèi)存。賦值語句前面已經(jīng)說明,要訪問內(nèi)存,就需要相應(yīng)的地址以表明訪問哪塊內(nèi)存,面變量是ー個(gè)映射,因此變量名就相當(dāng)于ー個(gè)地址。對于內(nèi)存的操作,在一般情況下就只有讀取內(nèi)存中的數(shù)值和將數(shù)值寫入內(nèi)存(不考慮分配和釋放內(nèi)存),在C++中,為了將一數(shù)值寫入某變量對應(yīng)的地址所標(biāo)識的內(nèi)存中(出于簡便,以后稱變量a對應(yīng)的地址為變量a的地址,而直接稱變量a的地址所標(biāo)識的內(nèi)存為變量a),只需先書寫變量名,后接“=",再接欲寫入的數(shù)字(關(guān)于數(shù)字,請參考《C++從零開始(二)》)以及分號。如下:a=10.Of;b=34;由于接的是數(shù)字,因此就可以接表達(dá)式并由編譯器生成計(jì)算相應(yīng)表達(dá)式所需的代碼,也就可如下:c=a/b*120.4f;上句編譯器將會生成進(jìn)行除法和乘法計(jì)算的CPU指令,在計(jì)算完畢后(也就是求得表達(dá)式a/b*120.4f的值了后),也會同時(shí)生成將計(jì)算結(jié)果放到變量c中去的CPU指令,這就是語句的基本作用(對于語句,在《C++從零開始(六)》中會詳細(xì)說明)。上面在書寫賦值語句時(shí),應(yīng)該確保此語句之前已經(jīng)將使用到的變量定義過,這樣編譯器才能在生成賦值用的CPU指令時(shí)查找到相應(yīng)變量的地址,進(jìn)面完成CPU指令的生成。如上面的a和b,就需要在書寫上面語句前先書寫類似下面的變量定義:floata;longb;直接書寫變量名也是一條語句,其導(dǎo)致編譯器生成一條讀取相應(yīng)變量的內(nèi)容的語句。即可以如ド書寫:上面將生成一條讀取內(nèi)存的語句,即使從內(nèi)存中讀出來的數(shù)字沒有任何應(yīng)用(當(dāng)然,如果編譯器開了優(yōu)化選項(xiàng),則上面的語句將不會生成任何代碼)。從這一點(diǎn)以及上面的c=a/b*120.4f;語句中,都可以看出一點(diǎn)——變量是可以返回?cái)?shù)字的。而變量返回的數(shù)字就是按照變量的類型來解釋變量對應(yīng)內(nèi)存中的內(nèi)容所得到的數(shù)字。這句話也許不是那么容易理解,在看過后面的類型轉(zhuǎn)換ー節(jié)后應(yīng)該就可以理解了。因此為了將數(shù)據(jù)寫入ー塊內(nèi)存,使用賦值語句(即等號);要讀取ー塊內(nèi)存,書寫標(biāo)識內(nèi)存的變量名。所以就可以這樣書寫:a=a+3;假設(shè)a原來的值為1,則上面的賦值語句將a的值取出來,加上3,得到結(jié)果4,將4再寫入a中去。由于C++使用“ソ’來代表賦值語句,很容易使人和數(shù)學(xué)中的等號混淆起來,這點(diǎn)應(yīng)注意。而如上的floata;語句,當(dāng)還未對變量進(jìn)行任何賦值操作時(shí),a的值是什么?上帝才知道。當(dāng)時(shí)的a的內(nèi)容是什么(對于VC編譯器,在開啟了調(diào)試選項(xiàng)時(shí),將會用OxCCCCCCCC填充這些未初始化內(nèi)存),就用IEEE的real*4格式來解釋它并得到相應(yīng)的ー個(gè)數(shù)字,也就是a的值。因此應(yīng)在變量定義的時(shí)候就進(jìn)行賦值(但是會有性能上的影響,不過很?。?以初始化變量而防止出現(xiàn)莫名其妙的值,如:floata=O.Of;,賦值操作符上面的a=a+3;的意思就是讓a的值增加3。在C++中,對于這種情況給出了一種簡寫方案,即前面的語句可以寫成:a+=3;。應(yīng)當(dāng)注意這兩條語句從邏輯上講都是使變量a的值增3,但是它們實(shí)際是有區(qū)別的,后者可以被編譯成優(yōu)化的代碼,因?yàn)槠湟馑际鞘鼓畅`塊內(nèi)存的值增加一定數(shù)量,而前者是將一個(gè)數(shù)字寫入到某塊內(nèi)存中。所以如果可能,應(yīng)盡量使用后者,即a+=3;。這種語句可以讓編譯器進(jìn)行一定的優(yōu)化(但由于現(xiàn)在的編譯器都非常智能,能夠發(fā)現(xiàn)a=a+3;是對ー塊內(nèi)存的增值操作而不是ー塊內(nèi)存的賦值操作,因此上面兩條語句實(shí)際上可以認(rèn)為完全相同,僅僅只具有簡寫的功能了)。對于上面的情況,也可以應(yīng)用在減法、乘法等二元非邏輯操作符(不是邏輯值操作符,即不能a&&=3:)上,如:a*=3;a-=4;a|=34;a?=3;等。除了上面的簡寫外,C++還提供了一種簡寫方式,即a++;,其邏輯上等同于a+=1;?同上,在電腦編程中,加一和減ー是經(jīng)常用到的,因此CPU專門提供了兩條指令來進(jìn)行加一和減ー操作(轉(zhuǎn)成匯編語言就是Inc和Dec),但速度比直接通過加法或減法指令來執(zhí)行要快得多。為此C++中也就提供了“++”和“一”操作符來對應(yīng)Inc和Dec。所以a++;雖然邏輯上和a=a+1;等效,實(shí)際由于編譯器可能做出的優(yōu)化處理而不同,但還是如上,由于編譯器的智能化,其是有可能看出a=a+1;可以編譯成Inc指令進(jìn)而即使沒有使用a++;卻也依然可以得到優(yōu)化的代碼,這樣a++;將只剩下簡寫的意義而已。應(yīng)當(dāng)注意一點(diǎn),a=3;這句語句也將返回一個(gè)數(shù)字,也就是在a被賦完值后a的值。由于其可以返回?cái)?shù)字,按照《C++從零開始(二)》中所說,“=”就屬于操作符,也就可以如下書寫:c=4+(a=3);之所以打括號是因?yàn)?="的優(yōu)先級較“+”低,而更常見和正常的應(yīng)用是:c=a=3;應(yīng)該注意上面并不是將c和a賦值為3,而是在a被賦值為3后再將a賦值給c,雖然最后結(jié)果和c、a都賦值為3是ー樣的,但不應(yīng)該這樣理解。由于a++;表示的就是a+=1;就是a=a+1;,因此a++;也將返回一個(gè)數(shù)字。也由于這個(gè)原因,C++又提供了另ー個(gè)簡寫方式,++a;。假設(shè)a為1,則a++;將先返回a的值,1,然后再將a的值加一;而++a;先將a的值加一,再返回a的值,2。而aー和一a也是如此,只不過是減ー罷了。上面的變量a按照最上面的變量定義,是float類型的變量,對它使用++操作符并不能得到預(yù)想的優(yōu)化,因?yàn)閒loat類型是浮點(diǎn)類型,其是使用!EEE的real*4格式來表示數(shù)字的,而不是二進(jìn)制原碼或補(bǔ)碼,而前面提到的Inc和Dec指令都是出于二進(jìn)制的表示優(yōu)點(diǎn)來進(jìn)行快速增一和減ー,所以如果對浮點(diǎn)類型的變量運(yùn)用“++”操作符,將完全只是簡寫,沒有任何的優(yōu)化效果(當(dāng)然,如果CPU提供了新的指令集,如MMX等,以對real*4格式進(jìn)行快速增一和減ー操作,且編譯器支持相應(yīng)指令集,則還是可以產(chǎn)生優(yōu)化效果的)。賦值操作符的返冋值在進(jìn)ー步了解++a和a++的區(qū)別前,先來了解何謂操作符的計(jì)算(Evaluate)?操作符就是將給定的數(shù)字做ー些處理,然后返回一個(gè)數(shù)字。而操作符的計(jì)算也就是執(zhí)行操作符的處理,并返回值。前面已經(jīng)知道,操作符是個(gè)符號,其ー側(cè)或兩側(cè)都可以接數(shù)字,也就是再接其他操作符,面又由于賦值操作符也屬于一種操作符,因此操作符的執(zhí)行順序變得相當(dāng)重要。對于a+b+c,將先執(zhí)行a+b,再執(zhí)行(a+b)+c的操作。你可能覺得沒什么,那么如下,假設(shè)a之前為1:c=(a*=2)+(a+=3);上句執(zhí)行后a為5。而c=(a+=3)+(a*=2);執(zhí)行后,a就是8了。那么c呢?結(jié)果可能會大大的出乎你的意料。前者的c為10,而后者的c為16。上面其實(shí)是ー個(gè)障眼法,其中的“+”沒有任何意義,即之所以會從左向右執(zhí)行并不是因?yàn)?+”的緣故,而是因?yàn)?a*=2)和(a+=3)的優(yōu)先級相同,而按照“()”的計(jì)算順序,是從左向右來計(jì)算的。但為什么c的值不是預(yù)想的2+5和4+8呢?因?yàn)橘x值操作符的返回值的關(guān)系。賦值操作符返回的數(shù)字不是變量的值,而是變量對應(yīng)的地址。這很重要。前面說過,光寫ー個(gè)變量名就會返回相應(yīng)變量的值,那是因?yàn)樽兞渴签`個(gè)映射,變量名就等同于ー個(gè)地址。C++中將數(shù)字看作一個(gè)很特殊的操作符,即任何ー個(gè)數(shù)字都是一個(gè)操作符。而地址就和長整型、単精度浮點(diǎn)數(shù)這類ー樣,是數(shù)字的一種類型。當(dāng)ー個(gè)數(shù)字是地址類型時(shí),作為操作符,其沒有要操作的數(shù)字,僅僅返回將此數(shù)字看作地址而標(biāo)識的內(nèi)存中的內(nèi)容(用這個(gè)地址的類型來解釋)。地址可以通過多種途徑得到,如上面光寫ー個(gè)變量名就可以得到其對應(yīng)的地址,而得到的地址的類型也就是相應(yīng)的變量的類型。如果這句話不能理解,在看過下面的類型轉(zhuǎn)換一節(jié)后應(yīng)該就能了解了。所以前面的c=(a+=3)+(a*=2);,由于“()”的參與改變了優(yōu)先級面先執(zhí)行了兩個(gè)賦值操作符,然后兩個(gè)賦值操作符都返回a的地址,然后計(jì)算‘'+”的值,分別計(jì)算兩邊的數(shù)字——a的地址(a的地址也是ー個(gè)操作符),也就是已經(jīng)執(zhí)行過兩次賦值操作的a的值,得8,故最后的c為16。而另ー個(gè)也由于同樣的原因使得c為10。現(xiàn)在考慮操作符的計(jì)算順序。當(dāng)同時(shí)出現(xiàn)了幾個(gè)優(yōu)先級相同的操作符時(shí),不同的操作符具有不同的計(jì)算順序。前面的“()”以及、"ボ’等這類二元操作符的計(jì)算順序都是從左向右計(jì)算,而“!”、負(fù)號“-”等前面介紹過的一元操作符都是從右向左計(jì)算的,如:!-!!a;,假設(shè)a為3。先計(jì)算從左朝右數(shù)第三個(gè)“!”的值,導(dǎo)致計(jì)算a的地址的值,得3;然后邏輯取反得。,接著再計(jì)算第二個(gè)“!”的值,邏輯取反后得1,再計(jì)算負(fù)號“-”的值,得T,最后計(jì)算第一個(gè)“げ的值,得〇。賦值操作符都是從右向左計(jì)算的,除了后綴“++”和后綴“一”(即上面的a++和a-),因此上面的c=a=3;,因?yàn)閮蓚€(gè)“=”優(yōu)先級相同,從右向左計(jì)算,先計(jì)算a=3的值,返回a對應(yīng)的地址,然后計(jì)算返回的地址而得到值3,再計(jì)算c=(a=3),將3寫入c。面不是從左向右計(jì)算,即先計(jì)算c=a,返回c的地址,然后再計(jì)算第二個(gè)“=”,將3寫入c,這樣a就沒有被賦值而出現(xiàn)問題。又:a=1;c=2;c*=a+=4;由于“*=”和“+=”的優(yōu)先級相同,從右向左計(jì)算先計(jì)算a+=4,得a為5,然后返回a的地址,再計(jì)算a的地址得a的值5,計(jì)算“*=”以使得c的值為10。因此按照前面所說,++a將返回a的地址,而a++也因?yàn)槭琴x值操作符而必須返回一個(gè)地址,但很明顯地不能是a的地址了,因此編譯器將編寫代碼以從棧中分配ー塊和a同樣大小的內(nèi)存,并將a的值復(fù)制到這塊臨時(shí)內(nèi)存中,然后返回這塊臨時(shí)內(nèi)存的地址。由于這塊臨時(shí)內(nèi)存是因?yàn)榫幾g器的需要而分配的,與程序員完全沒有關(guān)系,因此程序員是不應(yīng)該也不能寫這塊臨時(shí)內(nèi)存的(因?yàn)榫幾g器負(fù)責(zé)編譯代碼,如果程序員欲訪問這塊內(nèi)存,編譯器將報(bào)錯(cuò)),但可以讀取它的值,這也是返回地址的主要目的。所以如下的語句沒有問題:(++a)=a+=34;但(a++)=a+=34;就會在編譯時(shí)報(bào)錯(cuò),因?yàn)閍++返回的地址所標(biāo)識的內(nèi)存只能由編譯器負(fù)責(zé)處理,程序員只能獲得其值而已。a++的意思是先返回a的值,也就是上面說的臨時(shí)內(nèi)存的地址,然后再將變量的值加一。如果同時(shí)出現(xiàn)多個(gè)a++,那么每個(gè)a++都需要分配ー塊臨時(shí)內(nèi)存(注意前面c=(a+=3)+(a*=2);的說明),那么將有點(diǎn)糟糕,面且a++的意思是先返回a的值,那么到底是什么時(shí)候的a的值呢?在VC中,當(dāng)表達(dá)式中出現(xiàn)后綴“++”或后綴“一”時(shí),只分配一塊臨時(shí)內(nèi)存,然后所有的后綴“++”或后綴“一”都返回這個(gè)臨時(shí)內(nèi)存的地址,然后在所有的可以計(jì)算的其他操作符的值計(jì)算完畢后,再將對應(yīng)變量的值寫入到臨時(shí)內(nèi)存中,計(jì)算表達(dá)式的值,最后將對應(yīng)變量的值加ー或減ー。因此:a=1;c=(a++)+(a++);執(zhí)行后,c的值為2,而a的值為3?而如下:a=1;b=1;c=(++a)+(a++)+(b*=a++)+(a*=2)+(a*=a++);執(zhí)行時(shí),先分配臨時(shí)內(nèi)存,然后由于5個(gè)“()”,其計(jì)算順序是從左向右,計(jì)算++a的值,返回增ー后的a的地址,a的值為2
計(jì)算a++的值,返冋臨時(shí)內(nèi)存的地址,a的值仍為2計(jì)算b*=a++中的a++,返回臨時(shí)內(nèi)存的地址,a的值仍為2計(jì)算b*=a++中的,將a的值寫入臨時(shí)內(nèi)存,計(jì)算得b的值為2,返回b的地址計(jì)算a*=2的值,返回a的地址,a的值為4計(jì)算a*=a++中的a++,返回臨時(shí)內(nèi)存的地址,a的值仍為4計(jì)算a*=a++中的“*=”,將a的值寫入臨時(shí)內(nèi)存,返回a的地址,a的值為16計(jì)算剩下的,為了進(jìn)行計(jì)算,將a的值寫入臨時(shí)內(nèi)存,得值16+16+2+16+16為66,寫入c中計(jì)算三個(gè)a++欠下的加ー,a最后變?yōu)?9。上面說了那么多,無非只是想告誡你——在表達(dá)式中運(yùn)用賦值操作符是不被推崇的。因?yàn)槠洳环掀匠5臄?shù)學(xué)表達(dá)式的習(xí)慣,目.計(jì)算順序很容易搞混。如果有多個(gè)‘'++”操作符,最好還是將表達(dá)式分開,否則很容易導(dǎo)致錯(cuò)誤的計(jì)算順序而計(jì)算錯(cuò)誤。并且導(dǎo)致計(jì)算順序混亂的還不止上面的a++就完了,為了讓你更加地重視前面的紅字,下面將介紹更令人火大的東西,如果你已經(jīng)同意上面的紅字,則ド面這ー節(jié)完全可以跳過,其對編程來講可以認(rèn)為根本沒有任何意義(要不是為了寫這篇文章,我都不知道它的存在)。序列點(diǎn)(SequencePoint)和附加效果(SideEffect)在計(jì)算c=a++時(shí),當(dāng)c的值計(jì)算(Evaluate)出來時(shí),a的值也增加了?,a的值加一一就是計(jì)算前面表達(dá)式的附加效果。有什么問題?它可能影響表達(dá)式的計(jì)算結(jié)果。對于a=0;b=1;(a*=2)&&(b+=2);,由于兩個(gè)“()”優(yōu)先級相同,從左向右計(jì)算,計(jì)算“*=”而返回a的地址,再計(jì)算“+=”而返回b的地址,最后山于a的值為〇而返回邏輯假。很正常,但效率低了點(diǎn)。如果''&&"左邊的數(shù)字已經(jīng)是〇了,則不再需要計(jì)算右邊的式子。同樣,如果“|ド左邊的數(shù)字已經(jīng)非零了,也不需要再計(jì)算右邊的數(shù)ア。因?yàn)椤?&”和“丨ド都是數(shù)學(xué)上的,數(shù)學(xué)上不管先計(jì)算加號左邊的值還是右邊的值,結(jié)果都不會改變,因此“&&”和“丨丨”オ會做剛オ的解釋。這也是C++保證的,既滿足數(shù)學(xué)的定義,乂能提供優(yōu)化的途徑(“&&”和“|ド右邊的數(shù)字不用計(jì)算了)。因此上面的式子就會被解釋成——如果a在自乘了2后的值為0,則b就不用再自增2了。這很明顯地違背了我們的初衷,認(rèn)為b無論如何都會被自增2的。但是C++卻這樣保證,不僅僅是因?yàn)閿?shù)學(xué)的定義,還由于代碼生成的優(yōu)化。但是按照操作符的優(yōu)先級進(jìn)行計(jì)算,上面的b+=2依舊會被執(zhí)行的(這也正是我們會書寫上面代碼的原因)。為了實(shí)現(xiàn)當(dāng)a為0時(shí)b+=2不會被計(jì)算,C++提出了序列點(diǎn)的概念。序列點(diǎn)是?些特殊位置,由C++強(qiáng)行定義(C++并未給岀序列點(diǎn)的定義,因此不同的編譯器可能給出不同的序列點(diǎn)定義,VC是按照C語言定義的序列點(diǎn))。當(dāng)在進(jìn)行操作符的計(jì)算時(shí),如果遇到序列點(diǎn),則序列點(diǎn)處的值必須被優(yōu)先計(jì)算,以保證?些特殊用途,如上面的保證當(dāng)a為O時(shí)不計(jì)算b+=2,并且序列點(diǎn)相關(guān)的操作符(如前面的“&&”和“Iド)也將被計(jì)算完畢,然后オ恢復(fù)正常的計(jì)算?!?&”的左邊數(shù)字的計(jì)算就是ー個(gè)序列點(diǎn),而“"的左邊數(shù)字的計(jì)算也是。C++定義了多個(gè)序列點(diǎn),包括條件語句、函數(shù)參數(shù)等條件下的表達(dá)式計(jì)算,在此,不需要具體了解有哪些序列點(diǎn),只需要知道山于序列點(diǎn)的存在而可能導(dǎo)致賦值操作符的計(jì)算出乎意料。F面就來分析ー個(gè)例子:a=0;b=1;(a*=2)&&(b+=++a);按照優(yōu)先級的順序,編譯器發(fā)現(xiàn)要先計(jì)算a*=2,再計(jì)算++a,接著“+=”,最后計(jì)算“&&”。然后編譯器發(fā)現(xiàn)這個(gè)計(jì)算過程中,出現(xiàn)了“&&”左邊的數(shù)字這個(gè)序列點(diǎn),其要保證被優(yōu)先計(jì)算,這樣就有可能不用計(jì)算b+=++a了。所以編譯器先計(jì)算“&&”的數(shù)字,通過上面的計(jì)算過程,編譯器發(fā)現(xiàn)就要計(jì)算a*=2才能得到“&&”左邊的數(shù)字,因此將先計(jì)算a*=2,返回a的地址,然后計(jì)算“&&”左邊的數(shù)字,得a的值為0.因此就不計(jì)算b+=++a了。而不是最開始想象的由于優(yōu)先級的關(guān)系先將a加一后再進(jìn)行a的計(jì)算,以返回1。所以上面計(jì)算完畢后,a為0,b為!,返回0,表示邏輯假。因此序列點(diǎn)的出現(xiàn)是為了保證一些特殊規(guī)則的出現(xiàn),如上面的“&&”和“|"。再考慮",”操作符,其操作是計(jì)算兩邊的值,然后返回右邊的數(shù)字,即:a,b+3將返回b+3的值,但是a依舊會被計(jì)算。山于“,”的優(yōu)先級是最低的(但高于前面提到的“數(shù)字”操作符),因此如果a=3,4;,那么a將為3而不是4,因?yàn)橄扔?jì)算“=",返回a的地址后再計(jì)算“,”。又:
a=l:b=0;b=(a+=2)+((a*=2,b=a-l)&&(c=a));由于“&&”左邊數(shù)字是ー個(gè)序列點(diǎn),因此先計(jì)算a*=2,b的值,但根據(jù)、”的返回值定義,其只返回右邊的數(shù)字,因此不計(jì)算a*=2而直接計(jì)算b=a-1得0,“&&”就返回了,但是a*=2就沒有被計(jì)算而導(dǎo)致a的值依I口為!,這違背了“,”的定義。為了消除這一點(diǎn)(當(dāng)然可能還有其他應(yīng)用、”的情況),C++也將ヾ”的左邊數(shù)字定為了序列點(diǎn),即一定會優(yōu)先執(zhí)行ヾ”左邊的數(shù)字以保證ヾ”的定義——計(jì)算兩邊的數(shù)字。所以上面就由丁ヾ”左邊數(shù)字這個(gè)序列點(diǎn)而導(dǎo)致a*=2被優(yōu)先執(zhí)行,并導(dǎo)致b為1,因此由于“&&”是序列點(diǎn)且其左邊數(shù)字非零而必須計(jì)算完右邊數(shù)字后オ恢復(fù)正常優(yōu)先級,而計(jì)算c=a,得2,最后オ恢復(fù)正常優(yōu)先級順序,執(zhí)行a+=2和“+”。結(jié)果就a為4,c為2,b為5。所以前面的a=3,4;其實(shí)就應(yīng)該是編譯器先發(fā)現(xiàn)ヾ”這個(gè)序列點(diǎn),而發(fā)現(xiàn)要計(jì)算“,”左邊的值,必須先計(jì)算出a=3,因此オ先計(jì)算a=3以至于感覺序列點(diǎn)好像沒有發(fā)生作用。下面的式子請自行分析,執(zhí)行后a為4,但如果將其中的ヾ”換成“&&”,a為2。a=l;b=(a*=2)+((a*=3),(a-=2));如果上面你看得很暈,沒關(guān)系,因?yàn)樯厦娴膬?nèi)容根本可以認(rèn)為毫無意義,寫在這里也只是為了進(jìn)ー步向你證明,在表達(dá)式中運(yùn)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025-2030旅游輔助行業(yè)市場現(xiàn)狀供需分析及重點(diǎn)企業(yè)投資評估規(guī)劃分析研究報(bào)告
- 2025-2030批發(fā)零售行業(yè)并購重組機(jī)會及投融資戰(zhàn)略研究咨詢報(bào)告
- 2025-2030當(dāng)歸市場市場現(xiàn)狀供需分析及投資評估規(guī)劃分析研究報(bào)告
- 農(nóng)產(chǎn)品溯源與綠色大米采購專項(xiàng)合同
- 生物醫(yī)藥項(xiàng)目研發(fā)誠意金繳納及知識產(chǎn)權(quán)保護(hù)協(xié)議
- 飲品店委托經(jīng)營管理公司運(yùn)營與品牌連鎖合同
- 高速高精度車床進(jìn)口采購及調(diào)試合同
- 彩票銷售店員工招聘、培訓(xùn)與權(quán)益保障合同
- 餐廳租賃合同書及食品安全責(zé)任承諾
- 環(huán)保產(chǎn)業(yè)園廠房租賃及環(huán)境治理承包合同
- MT 754-1997小型煤礦地面用抽出式軸流通風(fēng)機(jī)技術(shù)條件
- GB/T 3863-2008工業(yè)氧
- GB/T 18391.1-2002信息技術(shù)數(shù)據(jù)元的規(guī)范與標(biāo)準(zhǔn)化第1部分:數(shù)據(jù)元的規(guī)范與標(biāo)準(zhǔn)化框架
- 護(hù)理科研選題與論文寫作
- 2023年河北泓杉供水有限責(zé)任公司招聘筆試模擬試題及答案解析
- 施工現(xiàn)場臨電講解課件
- 淘寶網(wǎng)-信息披露申請表
- 小微型客車租賃經(jīng)營備案表
- 教育培訓(xùn)機(jī)構(gòu)辦學(xué)許可證申請書(樣本)
- 尾礦壩施工方案
- 瓷磚業(yè)務(wù)員提成方案
評論
0/150
提交評論