C從零開始教材_第1頁
C從零開始教材_第2頁
C從零開始教材_第3頁
C從零開始教材_第4頁
已閱讀5頁,還剩71頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

C++從零開始何謂編程引言曾經有些人問我問題,問得都是ー些很基礎的問題,但這些人卻己經能使用VC編一個對話框界面來進行必耍的操作或者是文檔/視界面來實時接收端口數據并動態顯示曲線(還使用了多線程技術),卻連那些基礎的問題都不淸楚,并且最嚴重的后果就是導致編寫出拙劣的代碼(雖然是多線程,但真不敢恭維),不清楚類的含義,混雜使用各種可用的技術來達到目的(連用異常代替選擇語句都弄出來了),代碼邏輯混亂,感覺就和金山快譯的翻譯效果ー樣。我認為任何事情,基礎都是最重要的,并且在做完我自定的最后ー個項目后我就不再做編程的工作,守著這些經驗也沒什么意義,在此就用本系列說說我對編程的理解,幫助對電腦編程感興趣的人快速入門(不過也許并不會想象地那么快)。由于我從沒正經看完過一本C++的書(都是零碎偶爾翻翻的),并且本系列并不是教條式地將那些該死的名詞及其解釋羅列一遍,而是希望讀者能夠理解編程,而不是學會ー門語言(即不止會英翻漢,還會漢翻英)。整個系列全用我自己的理解來寫的,并無參考其他教材(在ー些基礎概念上還是參考了MSDN),所以本系列中的內容可能有和經典教材不一致的地方,但它們的本質應該還是ー樣的,只是角度不同而ー。本系列不會仔細講解C++的每個關鍵字(有些并不重要),畢竟H的不是C++語言參考,而是編程入門。如果本系列文章中有未提及的內容,還請參考MSDN中的C++語言參考(看完本系列文章后應該可能力做這件事了),而本系列給出的內容均是以VC編譯器為基礎,基于32位Windows操作系統的。下面羅列一下各文章的標題和主要內容,紅色修飾的文章標題表示我認為的重點。C++從零開始()——何謂編程(說明編程的真正含義及兩個重要卻容易被忽略的基礎概念)C++從零開始(二)——何謂表達式(說明各操作符的用處,但不是全部,剩余的會在其它文章提到)C++從零開始(三)——何謂變量(說明電腦的工作方式,闡述內存、地址等極其重要的概念)C++從零開始(四)——賦值操作符(《C++從零開始(二)》的延續,并為指針的解釋打一點基礎)C++從零開始(五)一?何謂指針(闡述指針、數組等重要的概念)C++從零開始(六)——何謂語句(講解C++提供的各個語句,說明各自存在的理由)C++從零開始(七)ーー何謂函數(說明函數及其存在的理由)C++從零開始(ハ)-C++樣例ー(給出ー兩個簡單算法,ー步步說明如何從算法編寫出C++代碼)C++從零開始(九)——何謂結構(簡要說明結構、枚舉等及其存在的理由)C++從零開始(十)——何謂類(說明類及其存在的理由,以及聲明、定義、頭文件等概念)C++從零開始(十一)ーー類的相關知識(說明派生、繼承、名字空間、操作符重載等)C++從零開始(十二)——何謂面向對象編程思想(闡述何謂編程思想,軍點講述面向對象編程思想)何謂程序程序,即過程的順序,準確地說應該是順序排列的多個過程,其是方法的描述。比如吃菜,先用筷子夾起菜,再用筷子將菜送入嘴中,最后咀嚼并吞下。其中的夾、送、咀嚼和吞下就被稱作命令,而菜則是資源,其狀態(如形狀、位置等)隨著命令的執行而不斷發生變化。上面就是吃菜這個方法的描述,也就是吃菜的程序。任何方法都是為了改變某些資源的狀態而存在,因此任何方法的描述,也就是程序,也都一定有命令這個東西以及其所作用的資源。命令是山程序的執行者來實現的,比如上面的吃菜,其中的夾、送等都是山吃菜的人來實現的,而資源則一定是執行者可以改變的東西,而命令只是告訴執行者如何改變而已。電腦程序和上面一樣,是方法的描述,而這些方法就是人期望電腦能做的事(注意不是電腦要做的事,這經常一直混淆著許多人),當人需要做這些事時,人再給出某些資源以期電腦能對其做正確的改變。如計算圓周率的程序,其只是方法的描述,本身是不能發生任何效用的,直到它被執行,人為給定它ー塊內存(關于內存,請參考《C++從零開始(三)》),告訴它計算結果的精度及計算結果的存放位置后,其オ改變人為給定的這塊內存的狀態以表現出計算結果。

因此,對于電腦程序,命令就是CPU的指令,而執行者也就由于是CPU的指令而必須是CPU了,而最后的資源則就是CPU可以改變其狀態的內存(當然不止,如端口等,不過?般應用程序都大量使用內存罷了)。所以,電腦程序就是電腦如何改變給定資源(一般是內存,也可以是其他硬件資源)的描述,注意是描述,本身沒有任何意義,除非被執行。何謂編程編程就是編寫程序,即制訂方法。為什么要有方法?方法是為了說明。而之所以耍有說明就有很多原因了,但電腦編程的根本原因是因為語言不同,且不僅不同,連概念都不相通。人類的語言五花八門,但都可以通過翻譯得到正解,因為人類生存在同一個四維物理空間中,具有相同或類似的感知。而電腦程序執行時的CPU所能感受到的空間和物理空間嚴重不同,所以是不可能將電腦程序翻譯成人類語言的描述的。這很重要,其導致了大部分程序員編寫出的拙劣代碼,因為人想的和電腦想的沒有共性,所以他們在編寫程序時就隨機地無H的地編寫,進而導致了拙劣卻可以執行的代碼。電腦的語言就是CPU的指令,因為CPU就這ー個感知途徑(準確地說還有內存定位、中斷響應等感知途徑),不像人類還能有肢體語言,所以電腦編程就是將人類語言書寫的方法翻譯成相應的電腦語言,是?個翻譯過程。這完全不同于一般的翻譯,由于前面的紅字,所以是不可能翻譯的。既然不可能翻譯,那電腦編程到底是干甚?考慮?個木匠,我是客人。我對木匠說我要一把搖椅,躺著很舒服的那種。然后木匠開始刨木頭,按照一個特殊的曲線制作搖椅下面的曲木以保證我搖的時候重心始終不變以感覺很舒服。這里我編了個簡單的程序,只有?條指令——做一把搖著很舒服的搖椅。而木匠則將我的程序翻譯成了刨木頭、設計特定的曲木等ー系列我看不懂的程序。之所以會這樣,在這里就是因為我生活的空間和木工(是木工エ藝,不是木匠)沒有共性。這里木匠就相當于電腦程序員兼CPU(因為最后由木匠來制作搖椅),而木匠的手藝就是CPU的指令定義,而木匠就將我的程序翻譯成r木工的一些規程,由木匠通過其手藝來實現這些規程,也就是執行程序。上面由于我生活的空間和木工(指木工エ藝,不是工人)沒有共性,所以是不可能翻譯的,但上面翻譯成功了,實際是沒有翻譯的。在木工眼中,那個搖椅只是ー些直木和曲木的拼接而已,因為木工空間中根本沒有搖椅的概念,只是我要把那堆木頭當作搖椅,進而使用。如果我把那堆木頭當作兇器,則它就是兇器,不是什么搖椅了。“廢話加荒謬加放屁!”,也許你會這么大叫,但電腦編程就是這么一回事。CPU只能感知指令和改變內存的狀態(不考慮其他的硬件資源及響應),如果我們編寫了?個計算圓周率的程序,給出了…塊內存,并執行,完成后就看見電腦的屏幕顯示正確的結果。但一定注意,這里電腦實際只是將一些內存的數值復制、加減、乘除而已,電腦并不知道那是圓周率,而如果執行程序的人不把它說成是圓周率那么那個結果也就不是圓周率了,可能是一個隨機數或其他什么的,只是運氣極好地和圓周率驚人地相似。上面的東西我將其稱為語義,即語言的意義,其不僅僅可應用在電腦編程方面,實際上許多技術,如機械、電子、數學等都有自己的語言,而那些設計師則負責將客戶的簡單程序翻譯成相應語言描述的程序。作為ー個程序員是極其有必要了解到語義的重要性的(我在我的另一篇文章《語義的需要》中對代碼級的語義做過較詳細的闡述,有興趣可以參考之),在后續的文章中我還將提到語義以及其對編程的影響,如果你還沒有理解編程是什么意思,隨著后續文章的閱讀應該能夠越來越明了。電腦編程的基礎知識——編譯器和連接器我從沒見過(不過應該有)任何一本C++教材有講過何謂編譯器(Compiler)及連接器(Linker)(倒是在很老的C教材中見過),現在都通過一個類似VC這樣的編程環境隱藏了大量東西,將這些封裝起來。在此,對它們的理解是非常重要的,木系列后面將大量運用到這兩個詞匯,其決定了能否理解如聲明、定義、外部變量、頭文件等非常重要的關鍵。前面已經說明了電腦編程就是ー個“翻譯”過程,要把用戶的程序翻譯成CPU指令,其實也就是機器代碼。所謂的機器代碼就是用CPU指令書寫的程序,被稱作低級語言。而程序員的工作就是編寫出機器代碼。由于機器代碼完全是ー些數字組成(CPU感知的一切都是數字,即使是指令,也只是1代表加法、2代表減法這ー類的數字和工作的

映射),人要記住1是代表加法、2是代表減法將比較困難,并且還要記住第3塊內存中放的是圓周率,而第4塊內存中放的是有效位數。所以發明了匯編語言,用ー些符號表示加法而不再用1了,如用ADD表示加法等。由于使用了匯編語言,人更容易記住了,但是電腦無法理解(其只知道1是加法,不知道ADD是加法,因為電腦只能看見數字),所以必須有個東西將匯編代碼翻譯成機器代碼,也就是所謂的編譯器。即編譯器是將一種語言翻譯成另一種語言的程序。即使使用了匯編語言,但由于其幾乎只是將CPU指令中的數字映射成符號以幫助記憶而已,還是使用的電腦的思考方式進行思考的,不夠接近人類的思考習慣,故而出現了紛繁復雜的各種電腦編程語言,如:PASCAL.BASIC.C等,其被稱作高級語言,因為比較接近人的思考模式(尤其C++的類的概念的推出),而匯編語言則被稱作低級語言(C曾被稱作高級的低級語言),因為它們不是很符合人類的思考模式,人類書寫起來比較困難。由于CPU同樣不認識這些PASCAL.BASIC等語言定義的符號,所以也同樣必須有一個編譯器把這些語言編寫的代碼轉成機器代碼。對于這里將要講到的C++語言,則是C++語言編譯器(以后的編譯器均指C++語言編譯器)。因此,這里所謂的編譯器就是將我們書寫的C++源代碼轉換成機器代碼。由于編譯器執行ー個轉換過程,所以其可以對我們編寫的代碼進行ー些優化,也就是說其相當于是ー個CPU指令程序員,將我們提供的程序翻譯成機器代碼,不過它的工作要簡單ー些了,因為從人類的思考方式轉成電腦的思考方式這ー過程已經由程序員完成了,而編譯器只是進行翻譯罷了(最多進行一些優化)。還有一種編譯器被稱作翻譯器(Translator),其和編譯器的區別就是其是動態的而編譯器是靜態的。如前面的BASIC的編譯器在早期版本就被稱為翻譯器,因為其是在運行時期即時進行翻譯工作的,而不像編譯器一次性將所有代碼翻成機器代碼。對于這里的“動態”、“靜態”和“運行時期”等名詞,不用刻意去理解它,隨著后續文章的閱讀就會了解了。編譯器把編譯后(即翻譯好的)的代碼以一定格式(對于VC,就是COFF通用對象文件格式,擴展名為.obj)存放在文件中,然后再由連接器將編譯好的機器代碼按一定格式(在Windows操作系統下就是PortableExecutableFileFormat——PE文件格式)存儲在文件中,以便以后操作系統執行程序時能按照那個格式找到應該執行的第一條指令或其他東西,如資源等。至于為什么中間還要加一個連接器以及其它細節,在后續文章中將會進ー步說明。也許你還不能了解到上面兩個概念的重要性,但在后續的文章中,你將會發現它們是如此的重要以至于完全有必要在這嘮叨一番。C++從零開始(二)——何謂表達式本篇是此系列的開頭,在學英語時,第一時間學的是字母,其是英語的基礎。同樣,在C++中,所有的代碼都是通過標識符(Identifier).表達式(Expression)和語句(Statement)及ー些必要的符號(如大括號等)組成,在此先說明何謂標識符。標識符標識符是一個字母序列,由大小寫英文字母.下劃線及數字組成,用于標識。標識就是標出并識別,也就是名字。其可以作為后面將提到的變量或者函數或者類等的名字,也就是說用來標識某個特定的變量或者函數或者類等C++中的元素。比如:abc就是ー個合法的標識符,即abc可以作為變量.函數等元素的名字,但并不代表abc就是某個變量或函數的名字,而所謂的合法就是任何ー個標識符都必須不能以數字開頭,只能包括大小寫英文字母.下劃線及數字,不能有其它符號,如,!’等,并且不能與C++關鍵字相同。也就是我們在給一個變量或函數起名字的時候,必須將起的名字看作是ー個標識符,并進而必須滿足上面提出的要求。如12ab_C就不是ー個合法的標識符,因此我們不能給某個變量或函數起12abエ這樣的名字;ab」2c就是合法的標識符,因此可以被用作變量或函數的名字。前面提到關鍵字,在后續的語句及一些聲明修飾符的介紹中將發現,C++提供了一些特殊的標識符作為語句的名字,用以標識某ー特定語句,如if、while等;或者提供ー些修飾符用以修飾變量.函數等元素以實現語義或給編譯器及連接器提供ー些特定信息以進行優化、查錯等操作,如extern,static等。因此在命名變量或函數或其他元素時,不能使用if、extern等這種C++關鍵字作為名字,否則將導致編譯器無法確認是ー個變量(或函數或其它C++元素)還是一條語句,進而無法編譯。如果要讓某個標識符是特定變量或函數或類的名字,就需要使用聲明,在后續的文章中再具體說明。數字C++作為電腦編程語言,電腦是處理數字的,因此C++中的基礎東西就是數字。C++中提供兩種數字:整型數和浮點數,也就是整數和小數。但由于電腦實際并不是想象中的數字化的(詳情參見《C++從零開始(三)》中的類型ー節),所以整型數又分成了有符號和無符號整型數,而浮點數則由精度的區別而分成單精度和雙精度浮點數,同樣的整型數也根據長度分成長整型和短整型。要在C++代碼中表示一個數字,直接書寫數字即可,如:123、34.23、ー34.34等。由于電腦并非以數字為基礎而導致了前面數字的分類,為了在代碼中表現出來,C++提供了一系列的后綴進行表示,如下:u或U表示數字是無符號整型數,如:123u,但并不說明是長整型還是短整型1或L表示數字是長整型數,如:1231;而123ul就是無符號長整型數:而34.41就是長雙精度浮點數,等效于雙精度浮點數i64或164表示數字是長長整型數,其是為64位操作系統定義的,長度比長整型數長。如:43i64f或F表示數字是單精度浮點數,如:12.3fe或E表示數字的次哥,如:34.4e-2就是0.344;0.2544e3f表示一個單精度浮點數,值為254.4當什么后綴都沒寫時,則根據有無小數點及位數來決定其具體類型,如:123表示的是有符號整型數,而12341434則是有符號長整型數:而34.43表示雙精度浮點數。為什么要搞這么多事出來,還分什么有符號無符號之類的?這全是因為電腦并非基于數字的,而是基于狀態的,詳情在下篇中將詳細說明。作為科學計算,可能經常會碰到使用非十進制數字,如16進制、8進制等,C++也為此提供了一些前綴以進行支持。在數字前面加ヒOx或0X表示這個數字是16進制表示的,如:0xF3Fa、OxllcF。而在前面加一個〇則表示這個數字是用8進制表示的,如:0347,變為十進制數就為231。但16進制和8進制都不能用于表示浮點數,只能表示整型數,即0x34.343是錯誤的。字符串C++除了提供數字這種最基礎的表示方式外,還提供了字符及字符串。這完全只是出于方便編寫程序面提供的,C++作為電腦語言,根本沒有提供字符串的必要性。不過由于人對電腦的基本要求就是顯示結果,而字符和字符串都由于是人易讀的符號面被用于顯示結果,所以C++專門提供了對字符串的支持。前面說過,電腦只認識數字,而字符就是文字符號,是ー種圖形符號。為了使電腦能夠處理符號,必須通過某種方式將符號變成數字,在電腦中這通過在符號和數字之間建立一個映射來實現,也就是ー個表格。表格有兩列,一列就是我們欲顯示的圖形符號,而另一列就是ー個數字,通過這么ー張表就可以在圖形符號和數字之間建立映射。現在已經定義出ー標準表,稱為ASCII碼表,幾乎所有的電腦硬件都支持這個轉換表以將數字變成符號進面顯示計算結果。有了上面的表,當想說明結果為“A”時,就查ASCII碼表,得到“A”這個圖形符號對應的數字是65,然后就告訴電腦輸出序號為65的字符,最后屏幕上顯示“A”。這明顯地繁雜得異常,為此C++就提供了字符和字符串。當我們想得到某ー個圖形符號的ASCII碼表的序號時,只需通過單引號將那個字符括起來即可,如:’A',其效果和65是ー樣的。當要使用不止ー個字符時,則用雙引號將多個字符括起來,也就是所謂的字符串了,如:"ABC"。因此字符串就是多個字符連起來面已。但根據前面的說明易發現,字符串也需要映射成數字,但它的映射就不像字符那么簡單可以通過查表就搞定的,對于此,將在后續文章中對數組作過介紹后再說明。操作符電腦的基本是數字,那么電腦的所有操作都是改變數字,因此很正常地C++提供了操作數字的ー些基本操作,稱作操作符(Operator),如:+-*/等。任何操作符都要返回一個數字,稱為操作符的返回值,因此操作符就是操作數字并返回數字的符號。作為一般性地分類,按操作符同時作用的數字個數分為一元、二元和三元操作符。一元操作符有:+其后接數字,原封不動地返回后接的數字。如:+4.4f的返回值是4.4;+-9.3f的返回值是ー9.3。完全是出于語義的需要,如表示此數為正數。其后接數字,將后接的數字的符號取反。如:-34.4f的返回值是ー34.4:-(-54)的返回值是54〇用于表示負數。!其后接數字,邏輯取反后接的數字。邏輯值就是“真”或“假”,為了用數字表示邏輯值,在C++中規定,非零值即為邏輯真,而零則為邏輯假。因此3、43.4、‘A’都表示邏輯真,而。則表示邏輯假。邏輯值被應用于后續的判斷及循環語句中。而邏輯取反就是先判斷“!”后面接的數字是邏輯真還是邏輯假,然后再將相應值取反。如:!5的返回值是〇,因為先由5非零而知是邏輯真,然后取反得邏輯假,故最后返回〇。!!345.4的返回值是1,先因345.4非零得邏輯真,取反后得邏輯假,再取反得邏輯真。雖然只要非零就是邏輯真,但作為編譯器返回的邏輯真,其一律使用1來代表邏輯真。、其后接數字,取反后接的數字。取反是邏輯中定義的操作,不能應用于數字。為了對數字應用取反操作,電腦中將數字用二進制表示,然后對數字的每一位進行取反操作(因為二進制數的每一位都只能為1或〇,正好符合邏輯的真和假)。如?123的返回值就為ー124。先將123轉成二進制數01111011,然后各位取反得10000100,最后得-124。這里的問題就是為什么是8位而不是!6位二進制數。因為123小于!28,被定位為char類型,故為8位(關于char是什么將下篇介紹)。如果是?123ul,則返回值為4294967172。為什么要有數字取反這個操作?因為CPU提供了這樣的指令。并且其還有著很不錯且很重要的應用,后面將介紹。關于其他的一元操作符將在后續文章中陸續提到(但不一定全部提到)。二元操作符有:+*/%其前后各接ー數字,返回兩數字之和、差、積、商、余數。如: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其前后各接ー邏輯值,返回兩邏輯值之“與”運算邏輯值和“或”運算邏輯值。如:'A'&&34.3f的返回值是邏輯真,為1;34&&0的返回值是邏輯假,為〇。0|I'B’的返回值是邏輯真,為1;0||0的返回值是邏輯假,為〇。&I"其前后各接ー數字,返回兩數字之“與”運算、“或”運算、“異或”運算值。如前面所說,先將兩側的數字轉成二進制數,然后對各位進行與、或、異或操作。如:4&6的返回值是4,4轉為00000100,6轉為00000110各位相與得,00000100,為4。4|6的返回值是6,4轉為00000100,6轉為00000110各位相或得,00000110,為6。4~6的返回值是2,4轉為00000100,6轉為00000110各位相異或得,00000010,為2。>=<=!=其前后各接ー數字,根據兩數字是否大于、小于、等于、大于等于、小于等于及不等于而返回相應的邏輯值。如:34>34的返回值是〇,為邏輯假:32<345的返回值為1,為邏輯真。23>=23和23>=14的返回值都是1,為邏輯真;54く=4的返回值為〇,為邏輯假。56==6的返回值是0,為邏輯假:45==45的返回值是1,為邏輯真。5!=5的返回值是〇,為邏輯假;5!=35的返回值是真,為邏輯真。??其前后各接ー數字,將左側數字右移或左移右側數字指定的位數。與前面的?、&、丨等操作ー樣,之所以要提供左移、右移操作主要是因為CPU提供了這些指令,主要用于編ー些基于二進制數的算法。くく將左側的數字轉成二進制數,然后將各位向左移動右側數值的位數,如;4,轉為00000100,左移2位,則變成00010000,得16o>>與くくー樣,只不過是向右移動罷了。如;6,轉為00000110,右移1位,變成00000011,得3。如果移2位,則有一位超出,將截斷,則6>>2的返回值就是00000001,為1。左移和右移有什么用?用于一些基于二進制數的算法,不過還可以順便作為ー個簡單的優化手段。考慮十進制數3524,我們將它左移2位,變成352400,比原數擴大了100倍,準確的說應該是擴大了10的2次方倍。如果將3524右移2位,變成35,相當于原數除以100的商。同樣,前面4>>2,等效于4/4的商;32>>3相當于32/8,即相當于32除以2的3次方的商。而4<<2等效于4*4,相當于4乘以2的2次方。因此左移和右移相當于乘法和除法,只不過只能是乘或除相應進制數的次方罷了,但它的運行速度卻遠遠高于乘法和除法,因此說它是ー種簡單的優化手段。,其前后各接ー數字,簡單的返回其右側的數字。如:34.45f,54的返回值是54;-324,4545f的返回值是4545f。那它到底有什么用?用于將多個數字整和成一個數字,在《C++從零開始(四)》中將進ー步說明。關于其他的二元操作符將在后續文章中陸續提到(但不一定全部提到)。三元操作符只有一個,為?:,其格式為:く數字1>?く數字2>:く數字3>。它的返回值為:如果〈數字1>是邏輯真,返回〈數字2〉,否則返回〈數字3>。如;34?4:2的返回值就是4,因為34非零,為邏輯真,返回4。而〇?4:2的返回值就是2,因為〇為邏輯假,返回2。表達式你應該發現前面的荒謬之處了——12>435返回值為0,那為什么不直接寫0還吃飽了撐了寫個12>435在那?這就是表達式的意義了。前面說“>”的前后各接ー數字,但是操作符是操作數字并返回數字的符號,因為它返回數字,因此可以放在上面說的任何ー個要求接數字的地方,也就形成了所謂的表達式。如:23*54/45>34的返回值就是0.因為23*54的返回值為1242;然后又將1242作為“/”的左接數字,得到新的返回值27.6;最后將27.6作為“ゾ的左接數字進而得到返回值〇,為邏輯假。因此表達式就是由一系列返回數字的東西和操作符組合而成的一段代碼,其由于是由操作符組成的,故一定返回值。而前面說的“返回數字的東西”則可以是另一個表達式,或者ー個變量,或者ー個具有返回值的函數,或者具有數字類型操作符重載的類的對象等,反正只要是能返回一個數字的東西。如果對于何謂變量、函數、類等這些名詞感到陌生,不需要去管它們,在后繼的文章中將會ーー說明。因此34也是一個表達式,其返回值為34,只不過是沒有操作符的表達式罷了(在后面將會了解到34其實是ー種操作符)。故表達式的概念其實是很廣的,只要有返回值的東西就可以稱為表達式。由于表達式里有很多操作符,執行操作符的順序依賴丁?操作符的優先級,就和數學中的ー樣,?、/的優先級大于+、而+、一又大于〉、〈等邏輯操作符。不用去刻意記住操作符的優先級,當不能確定操作符的執行順序時,可以使用小括號來進行指定。如:((1+2)*3)+3)/4的返回值為3,而1+2*3+3/4的返回值為7。注意3/4為〇,因為3/4的商是〇。當希望進行浮點數除法或乘法時,只需讓操作數中的某ー個為浮點數即可,如:3/4.0的返回值為0.75。&|~~等的應用前面提過邏輯操作符“&&”、“丨丨”、“『’等,作為表示邏輯,其被C++提供一點都不值得驚奇。但是為什么要有ー個將數字轉成二進制數,然后對二進制數的各位進行邏輯操作的這么ー類操作符呢?首先是CPU提供了相應的指令,并且其還有著下面這個非常有意義的應用。考慮ー十字路口,每個路口有三盞紅綠燈,分別指明能否左轉、ん轉及直行。共有12盞,現在要為它編寫ー個控制程序,不管這程序的功能怎樣,首先需要將紅綠燈的狀態轉化為數字,因為電腦只知道數字。所以用3個數字分別表示某路口的三盞紅綠燈,因此每個紅綠燈的狀態由一個數字來表示,假設紅燈為0,綠燈為1(不考慮黃燈或其他情況)。后來忽然發現,其實也可以用一個數字表示一個路U的三盞紅綠燈狀態,如用110表示左轉綠燈、直行綠燈而右轉紅燈。上.面的110是ー個十進制數字,它的每位實際都可以為〇、9十個數字,但是這里只應用到了兩個:〇和1,感覺很浪費。故選擇二進制數來表示,還是110,但是是二進制數了,轉成十進制數為6,即使當為111時轉成十進制數也只是7,比前面的110這個十進制數小多了,節約了……??什么??我們在紙上寫數字235425234一定比寫134這個數字要更多地占用紙張(假設字都一樣大)。因此記錄ー個大的數比記錄ー個小的數要花費更多的資源。簡直荒謬!不管是100還是1000,都只是?個數字,為什么記錄大的數字就更費資源?因為電腦并不是數字計算機,而是電子計算機,它是基于狀態而不是基于數字的,這在下篇會詳細說明。電腦必須使用某種表示方式來代表一個數字,而那個表示方式和二進制很像,但并不是二進制數,故出現記錄大的數較小的數更耗資源,這也就是為什么上面整型數要分什么長整型短整型的原因了。下面繼續上面的思考。使用了110這個二進制數來表示三盞紅綠燈的狀態,那么現在要知道110這個數字代表左轉紅綠燈的什么狀態。以數字的第三位表示左轉,不過電腦并不知道這個,因此如下:110&100。這個表達式的返回值是100,非零,邏輯真。假設某路口的狀態為010,則同樣的010&100,返回值為〇,邏輯假。因此使用“&”操作符可以將二進制數中的某一位或幾位的狀態提取出來。所以我們要了解ー個數字代表的紅綠燈狀態中的左轉紅綠燈是否綠燈時,只需讓它和100相與即可。現在要保持其他紅綠燈的狀態不變,僅僅使左轉紅綠燈為綠燈,圳當前狀態為010,為了使左轉紅綠燈為綠燈,值應該為110,這可以通過0101100做到。如果當前狀態是001,則0011100為101,正確——直行和右轉的紅綠燈狀態均沒有發生變化。因此使用“丨”操作符可以給ー個二進制數中的某一位或幾位設置狀態,但只能設置為1,如果想設置為〇,如101,要關掉ん轉的綠燈,則101&100,返冋值為001。上面一直提到的路口紅綠燈的狀態實際編寫時可以使用ー個變量來表示,而上面的100也可以用一?個標識符來表示,如state&TS_LEFT,就可以表示檢查變キtstate所表示的狀態中的左轉紅綠燈的狀態。上面的這種方法被大量地運用,如創建一個窗口,ー個窗口可能有二三十個風格,則通過上面的方法,就可以只用ー個32位長的:進制數字就表示了窗口的風格,而不用去弄二三十個數字來分別代表每種風格是否具有。C++從零開始(三)——何謂變量本篇說明內容是C++中的關鍵,基本大部分人對于這些內容都是昏的,但這些內容又是編程的基礎中的基礎,必須詳細說明。數字表示數學中,數只有數值大小的不同,絕不會有數值占用空間的區別,即數學中的數是邏輯上的ー個概念,但電腦不是。考慮算盤,每個算盤上有很多列算子,每列都分成上下兩排算子。上排算子有2個,每個代表5,下排算子有4個,每個代表1(這并不重要)。因此算盤上的每列共有6個算子,每列共可以表示〇到14這15個數字(因為上排算子的可能狀態有0到2個算子有效,而下排算子則可能有0到4個算子有效,故為3X5=15種組合方式)。上面的重點就是算盤的每列并沒有表示〇到14這!5個數字,而是每列有!5種狀態,因此被人利用來表示數字而已(這很重要)。由于算盤的每列有15個狀態,因此用兩列算子就可以有15X15=225個狀態,因此可以表示〇到224〇阿拉伯數字的每一位有0到9這!0個圖形符號,用兩個阿拉伯數字圖形符號時就能有10X10=100個狀態,因此可以表示〇到99這!00個數。這里的算盤其實就是一個基于15進制的記數器(可以通過維持一列算子的狀態來記錄一位數字),它的一列算子就相當于一位阿拉伯數字,每列有!5種狀態,故能表示從0到14這!5個數字,超出14后就必須通過進位來要求另一列算子的加入以表示數字。電腦與此ー樣,其并不是數字計算機,而是電子計算機,電腦中通過ー根線的電位高低來表示數字。ー-根線中的電位規定只有兩種狀態——高電位和低電位,因此電腦的數字表示形式是二進制的。和上面的算盤ー樣,ー根電線只有兩個狀態,當要表示超出1的數字時,就必須進位來要求另一根線的加入以表示數字。所謂的32位電腦就是提供了32根線(被稱作數據總線)來表示數據,因此就有2的32次方那么多種狀態。而16根線就能表示2的16次方那么多種狀態。所以,電腦并不是基于二進制數,而是基于狀態的變化,只不過這個狀態可以使用二進制數表示出來而已。即電腦并不認識二進制數,這是下面“類型”ー節的基礎。內存內存就是電腦中能記錄數字的硬件,但其存儲速度很快(與硬盤等低速存儲設備比較),又不能較長時間保存數據,所以經常被用做草稿紙,記錄一些臨時信息。前面已經說過,32位計算機的數字是通過32根線上的電位狀態的組合來表示的,因此內存能記錄數字,也就是能維持32根線上各自的電位狀態(就好象算盤的算子撥動后就不會改變位置,除非再次撥動它)。不過依舊考慮上面的算盤,假如一個算盤上有15列算子,則ー個算盤能表示15的15次方個狀態,是很大的數字,但經常實際是不會用到變化那么大的數字的,因此讓ー個算盤只有兩列算子,則只能表示225個狀態,當數字超出時就使用另ー個或多個算盤來一起表示。上面不管是2列算子還是15列算子,都是算盤的粒度,粒度分得過大造成不必要的浪費(很多列算子都不使用),太小又很麻煩(需要多個算盤)。電腦與此ー樣。2的32次方可表示的數字很大,-?般都不會用到,如果直接以32位存儲在內存中勢必造成相當大的資源浪費。于是如上,規定內存的粒度為8位二進制數,稱為ー個內存單元,而其大小稱為ー個字節(Byte)〇就是說,內存存儲數字,至少都會記錄8根線上的電位狀態,也就是2的8次方共256種狀態。所以如果ー個32位的二進制數要存儲在內存中,就需要占據4個內存單元,也就是4個字節的內存空間。我們在紙上寫字,是通過肉眼判斷出字在紙上的相對橫坐標和縱坐標以查找到要看的字或要寫字的位置。同樣,山于內存就相當于草稿紙,因此也需要某種定位方式來定位,在電腦中,就是通過一個數字來定位的。這就和旅館的房間號ー樣,內存單元就相當于房間(假定每個房間只能住一個人),而前面說的那個數字就相當于房間號。為了向某塊內存中寫入數據(就是使用某塊內存來記錄數據總線上的電位狀態),就必須知道這塊內存對應的數字,而這個數字就被稱為地址。而通過給定的地址找到對應的內存單元就稱為尋址。因此地址就是ー個數字,用以唯一標識某一特定內存單元。此數字一般是32位長的二進制數,也就可以表示4G個狀態,也就是說一般的32位電腦都具有4G的內存空間尋址能力,即電腦最多裝4G的內存,如果電腦有超過4G的內存,此時就需要增加地址的長度,如用40位長的二進制數來表示。類型在本系列最開頭時已經說明了何渭編程,而剛オ更進ー步說明了電腦其實連數字都不認識,只是狀態的記錄,而所謂的加法也只是人為設計那個加法器以使得兩個狀態經過加法器的處理而生成的狀態正好和數學上的加法的結果ー樣而已。這一切的一切都只說明一點:電腦所做的工作是什么,全視使用的人以為是什么。因此為了利用電腦那很快的“計算”能力(實際是狀態的變換能力),人為規定了如何解釋那些狀態。為了方便其間,對于前面提出的電位的狀態,我們使用1位二進制數來表示,則上面提出的狀態就可以使用ー個二進制數來表示,而所謂的“如何解釋那些狀態”就變成了如何解釋ー個二進制數。C++是高級語言,為了幫助解釋那些二進制數,提供了類型這個概念。類型就是人為制訂的如何解釋內存中的ニ進制數的協議。C++提供了下面的ー些標準類型定義。signedchar表示所指向的內存中的數字使用補碼形式,表示的數字為ー128至U+127,長度為1個字節unsignedchar表示所指向的內存中的數字使用原碼形式,表示的數字為0到255,長度為1個字節signedshort表示所指向的內存中的數字使用補碼形式,表示的數字為-32768至リ+32767,長度為2個字節unsignedshort表示所指向的內存中的數字使用原碼形式,表示的數字為〇到65535,長度為2個字節signedlong表示所指向的內存中的數字使用補碼形式,表示的數字為ー2147483648到+2147483647,長度為4個字節unsignedlong表示所指向的內存中的數字使用原碼形式,表示的數字為〇到4294967295,長度為4個字節signedint表示所指向的內存中的數字使用補碼形式,表示的數字則視編譯器。如果編譯器編譯時被指明編譯為在16位操作系統上運行,則等同于signedshort;如果是編譯為32位的,則等同于signedlong;如果是編譯為在64位操作系統上運行,則為8個字節長,而范圍則如上一樣可以自行推算出來。unsignedint表示所指向的內存中的數字使用原碼形式,其余和signedintー樣,表示的是無符號數。bool表示所指向的內存中的數字為邏輯值,取值為false或true。長度為1個字節。float表示所指向的內存按IEEE標準進行解釋,為real*4,占用4字節內存空間,等同于上篇中提到的單精度浮點數。double表示所指向的內存按IEEE標準進行解釋,為real*8,可表示數的精度較float高,占用8字節內存空間,等同于上篇提到的雙精度浮點數。longdouble表示所指向的內存按IEEE標準進行解釋,為real*10,可表示數的精度較double高,但在為32位Windows操作系統編寫程序時,仍占用8字節內存空間,等效于double,只是如果CPU支持此類浮點類型則還是可以進行這個精度的計算。標準類型不止上面的幾個,后面還會陸續提到。上面的長度為2個字節也就是將兩個連續的內存單元中的數字取出并合并在一起以表示一個數字,這和前面說的ー個算盤表示不了的數字,就進位以加入另一個算盤幫助表示是同樣的道理。上面的signed關鍵字是可以去掉的,即char等同于signedchar,用以簡化代碼的編寫。但也僅限于signed,如果是unsignedchar,則在使用時依舊必須是unsignedchar〇現在應該已經了解上篇中為什么數字還要分什么有符號無符號、長整型短整型之類的了,面上面的short、char等也都只是長度不同,這就由程序員自己根據可能出現的數字變化幅度來進行選用了。類型只是對內存中的數字的解釋,但上面的類型看起來相對簡單了點,且語義并不是很強,即沒有什么特殊意思。為此,C++提供了自定義類型,也就是后繼文章中將要說明的結構、類等。變量在本系列的第一篇中已經說過,電腦編程的絕大部分工作就是操作內存,而上面說了,為了操作內存,需要使用地址來標識要操作的內存塊的首地址(上面的long表示連續的4個字節內存,其第一個內存單元的地址稱作這連續4個字節內存塊的首地址)。為此我們在編寫程序時必須記下地址。做5+2/3-5*2的計算,先計算出2/3的值,寫在草稿紙上,接著算出5*2的值,又寫在草稿紙上。為了接下來的加法和減法運算,必須能夠知道草稿紙上的兩個數字哪個是2/3的值哪個是5*2的值。人就是通過記憶那兩個數在紙上的位置來記憶的,而電腦就是通過地址來標識的。但電腦只會做加減乘除,不會去主動記那些2/3、5*2的中間值的位置,也就是地址。因此程序員必須完成這個工作,將那兩個地址記下來。問題就是這里只有兩個值,也許好記ー些,但如果多了,人是很難記住哪個地址對應哪個值的,但人對符號比對數字要敏感得多,即人很容易記下ー個名字而不是一個數字。為此,程序員就自己寫了一個表,表有兩列,一列是''2/3的值”,一列是對應的地址。如果式子稍微復雜點,那么那個表可能就有個二三十行,而每寫一行代碼就要去翻查相應的地址,如果來個幾萬行代碼那是人都不能忍受。C++作為高級語言,很正常地提供了上面問題的解決之道,就是由編譯器來幫程序員維護那個表,要查的時候是編譯器去查,這也就是變量的功能。變量是一個映射元素。上面提到的表由編譯器維護,而表中的每一行都是這個表的ー個元素(也稱記錄)。表有三列:變量名、對應地址和相應類型。變量名是ー個標識符,因此其命名規則完全按照上一篇所說的來。當要對某塊內存寫入數據時,程序員使用相應的變量名進行內存的標識,而表中的對應地址就記錄了這個地址,進而將程序員給出的變量名,ー個標識符,映射成一個地址,因此變量是ー個映射元素。而相應類型則告訴編譯器應該如何解釋此地址所指向的內存,是2個連續字節還是4個?是原碼記錄還是補碼?而變量所對應的地址所標識的內存的內容叫做此變量的值。有如下的變量解釋:“可變的量,其相當于一個盒子,數字就裝在盒子里,而變量名就寫在盒子外面,這樣電腦就知道我們要處理哪ー個盒子,且不同的盒子裝不同的東西,裝字符串的盒子就不能裝數字。”上面就是我第一次學習編程時,書上寫的(是BASIC語言)。對于初學者也許很容易理解,也不能說錯,但是造成的誤解將導致以后的程序編寫地千瘡百孔。上面的解釋隱含了一個意思——變量是ー塊內存。這是嚴重錯誤的!如果變量是一塊內存,那么C++中著名的引用類型將被棄置荒野。變量實際并不是一塊內存,只是ー個映射元素,這是致關重要的。內存的種類前面已經說了內存是什么及其用處,但內存是不能隨便使用的,因為操作系統自己也要使用內存,而且現在的操作系統正常情況下都是多任務操作系統,即可同時執行多個程序,即使只有一個CPU。因此如果不對內存訪問加以節制,可能會破壞另一個程序的運作。比如我在紙上寫了2/3的值,而你未經我同意且未通知我就將那個值擦掉,并寫上5*2的值,結果我后面的所有計算也就出錯了。因此為了使用ー塊內存,需要向操作系統申請,由操作系統統ー管理所有程序使用的內存。所以為了記錄ー個long類型的數字,先向操作系統申請ー塊連續的4字節長的內存空間,然后操作系統就會在內存中查看,看是否還有連續的4個字節長的內存,如果找到,則返回此4字節內存的首地址,然后編譯器編譯的指令將其記錄在前面提到的變量表中,最后就可以用它記錄ー些臨時計算結果了。上面的過程稱為要求操作系統分配ー塊內存。這看起來很不錯,但是如果只為了4個字節就要求操作系統搜索一下內存狀況,那么如果需要100個臨時數據,就要求操作系統分配內存100次,很明顯地效率低下(無謂的99次査看內存狀況)。因此C++發現了這個問題,并且操作系統也提出了相應的解決方法,最后提出了如下的解決之道。棧(Stack)任何程序執行前,預先分配ー固定長度的內存空間,這塊內存空間被稱作棧(這種說法并不準確,但由于實際涉及到線程,在此為了不將問題復雜化オ這樣說明),也被叫做堆棧。那么在要求一個4字節內存時,實際是在這個已分配好的內存空間中獲取內存,即內存的維護工作由程序員自己來做,即程序員自己判斷可以使用哪些內存,而不是操作系統,直到已分配的內存用完。很明顯,上面的工作是由編譯器來做的,不用程序員操心,因此就程序員的角度來看什么事情都沒發生,還是需要像原來那樣向操作系統申請內存,然后再使用。但工作只是從操作系統變到程序自己而已,要維護內存,依然要耗費CPU的時間,不過要簡單多了,因為不用標記ー塊內存是否有人使用,而專門記錄一個地址。此地址以上的內存空間就是有人正在使用的,而此地址以下的內存空間就是無人使用的。之所以是以下的空間為無人使用而不是以上,是當此地址減小到。時就可以知道堆棧溢出了(如果你已經有些基礎,請不要把〇認為是虛擬內存地址,關于虛擬內存將會在《C++從零開始(十八)》中進行說明,這里如此解釋只是為了方便理解)。而且CPU還專門對此法提供了支持,給出了兩條指令,轉成匯編語言就是push和pop,表示壓棧和出棧,分別減小和增大那個地址。而最重要的好處就是由于程序ー開始執行時就已經分配了一大塊連續內存,用ー個變量記錄這塊連續內存的首地址,然后程序中所有用到的,程序員以為是向操作系統分配的內存都可以通過那個首地址加上相應偏移來得到正確位置,而這很明顯地由編譯器做了。因此實際上等同于在編譯時期(即編譯器編譯程序的時候)就已經分配了內存(注意,實際編譯時期是不能分配內存的,因為分配內存是指程序運行時向操作系統申請內存,而這里由于使用堆棧,則編譯器將生成一些指令,以使得程序ー開始就向操作系統申請內存,如果失敗則立刻退出,而如果不退出就表示那些內存已經分配到了,進而代碼中使用首地址加偏移來使用內存也就是有效的),但壞處也就是只能在編譯時期分配內存。堆(Heap)上面的工作是編譯器做的,即程序員并不參與堆棧的維護。但上面已經說了,堆棧相當于在編譯時期分配內存,因此一旦計算好某塊內存的偏移,則這塊內存就只能那么大,不能變化了(如果變化會導致其他內存塊的偏移錯誤)。比如要求客戶輸入定單數據,可能有10份定單,也可能有100份定單,如果ー開始就定好了內存大小,則可能造成不必要的浪費,又或者內存不夠。為了解決上面的問題,C++提供了另ー個途徑,即允許程序員有兩種向操作系統申請內存的方式。前ー種就是在棧上分配,申請的內存大小固定不變。后一種是在堆上分配,申請的內存大小可以在運行的時候變化,不是同定不變的。那么什么叫堆?在Windows操作系統下,由操作系統分配的內存就叫做堆,而棧可以認為是在程序開始時就分配的堆(這并不準確,但為了不復雜化問題,故如此說明)。因此在堆上就可以分配大小變化的內存塊,因為是運行時期即時分配的內存,而不是編譯時期已計算好大小的內存塊。變量的定義上面說了那么多,你可能看得很暈,畢竟連ー個實例都沒有,全是文字,下面就來幫助加深對上面的理解。定義一個變量,就是向上面說的山編譯器維護的變量表中添加元素,其語法如下:longa;先寫變量的類型,然后一個或多個空格或制表符(\t)或其它間隔符,接著變量的名字,最后用分號結束。要同時定義多個變量,則各變量間使用逗號隔開,如下:longa,b,c;unsignedshorte,a_34c;上面是兩條變量定義語句,各語句間用分號隔開,而各同類型變量間用逗號隔開。而前面的式子5+2/3-5*2,則如下書寫。longa=2/3,b=5*2;longc=5+a-b;可以不用再去記那煩人的地址了,只需記著a、b這種簡單的標識符。當然,上面的式子不一定非要那么寫,也可以寫成:longc=5+2/3-5*2i面那些a、b等中間變量編譯器會自動生成并使用(實際中編譯器由于優化的原因將直接計算出結果,而不會生成實際的計算代碼)。下面就是問題的關鍵,定義變量就是添加一個映射。前面已經說了,這個映射是將變量名和一個地址關聯,因此在定義一個變量時,編譯器為了能將變量名和某個地址對應起來,幫程序員在前面提到的棧上分配了一塊內存,大小就視這個變量類型的大小。如上面的a、b、c的大小都是4個字節,而e、a_34c的大小都是2個字節。假設編譯器分配的棧在ー開始時的地址是100〇,并假設變量a所對應的地址是1000-56,則b所對應的地址就是1000-60,而c所對應的就是1000-64,e對應的是1000-66,a_34c是1000-68。如果這時b突然不想是4字節了,面希望是8字節,則后續的c、e、a_34c都將由于還是原來的偏移位置而使用了錯誤的內存,這也就是為什么棧上分配的內存必須是固定大小。考慮前面說的紅色文字:“變量實際并不是ー塊內存,只是ー個映射元素”。可是只要定義ー個變量,就會相應地得到一塊內存,為什么不說變量就是ー塊內存?上面定義變量時之所以會分配ー塊內存是因為變量是一個映射元素,需要一個對應地址,因此オ在棧上分配了一塊內存,并將其地址記錄到變量表中。但是變量是可以有別名的,即另ー個名字。這個說法是不準確的,應該是變量所對應的內存塊有另ー個名字,而不止是這個變量的名字。為什么要有別名?這是語義的需要,表示既是什么又是什么。比如ー塊內存,里面記錄了老板的信息,因此起名為Boss,但是老板又是另一家公司的行政經理,故變量名應該為Manager,而在程序中有段代碼是老板的公司相關的,面另一段是老板所在公司相關的,在這兩段程序中都要使用到老板的信息,那到底是使用Boss還是Manager?其實使用什么都不會對最終生成的機器代碼產生什么影響,但此處出于語義的需要就應該使用別名,以期從代碼上表現出所編寫程序的意思。在C++中,為了支持變量別名,提供了引用變量這個概念。要定義ー個引用變量,在定義變量時,在變量名的前面加一個,如下書寫:longa;long&al=a,&a2=a,&a3=a2;上面的al、a2、a3都是a所對應的內存塊的別名。這里在定義變量a時就在棧上分配了一塊4字節內存,而在定義al時卻沒有分配任何內存,直接將變量a所映射的地址作為變量al的映射地址,進而形成對定義a時所分配的內存的別名。因此上面的Boss和Manager,應該如F(其中Person是一個結構或類或其他什么自定義類型,這將在后繼的文章中陸續說明):PersonBoss;Person&Manager=Boss;由于變量一旦定義就不能改變(指前面說的變量表里的內容,不是變量的值),直到其被刪除,所以上面在定義引用變量的時候必須給出欲別名的變量以初始化前面的變量表,否則編譯器編譯時將報錯。現在應該就更能理解前面關于變量的紅字的意思了。并不是每個變量定義時都會分配內存空間的。而關于如何在堆上分配內存,將在介紹完指針后予以說明,并進而說明上ー?篇遺留下來的關于字符串的問題。C++從零開始(四)——賦值操作符本篇是《C++從零開始(二)》的延續,說明《C++從零開始(二)》中遺留下來的關于表達式的內容,并為下篇指針的運用做一點鋪墊。雖然上篇已經說明了變量是什么,但對于變量最關鍵的東西卻由于篇幅限制面沒有說明,下面先說明如何訪問內存。賦值語句前面已經說明,要訪問內存,就需要相應的地址以表明訪問哪塊內存,面變量是ー個映射,因此變量名就相當于ー個地址。對于內存的操作,在一般情況下就只有讀取內存中的數值和將數值寫入內存(不考慮分配和釋放內存),在C++中,為了將一數值寫入某變量對應的地址所標識的內存中(出于簡便,以后稱變量a對應的地址為變量a的地址,而直接稱變量a的地址所標識的內存為變量a),只需先書寫變量名,后接“=",再接欲寫入的數字(關于數字,請參考《C++從零開始(二)》)以及分號。如下:a=10.Of;b=34;由于接的是數字,因此就可以接表達式并由編譯器生成計算相應表達式所需的代碼,也就可如下:c=a/b*120.4f;上句編譯器將會生成進行除法和乘法計算的CPU指令,在計算完畢后(也就是求得表達式a/b*120.4f的值了后),也會同時生成將計算結果放到變量c中去的CPU指令,這就是語句的基本作用(對于語句,在《C++從零開始(六)》中會詳細說明)。上面在書寫賦值語句時,應該確保此語句之前已經將使用到的變量定義過,這樣編譯器才能在生成賦值用的CPU指令時查找到相應變量的地址,進面完成CPU指令的生成。如上面的a和b,就需要在書寫上面語句前先書寫類似下面的變量定義:floata;longb;直接書寫變量名也是一條語句,其導致編譯器生成一條讀取相應變量的內容的語句。即可以如ド書寫:上面將生成一條讀取內存的語句,即使從內存中讀出來的數字沒有任何應用(當然,如果編譯器開了優化選項,則上面的語句將不會生成任何代碼)。從這一點以及上面的c=a/b*120.4f;語句中,都可以看出一點——變量是可以返回數字的。而變量返回的數字就是按照變量的類型來解釋變量對應內存中的內容所得到的數字。這句話也許不是那么容易理解,在看過后面的類型轉換ー節后應該就可以理解了。因此為了將數據寫入ー塊內存,使用賦值語句(即等號);要讀取ー塊內存,書寫標識內存的變量名。所以就可以這樣書寫:a=a+3;假設a原來的值為1,則上面的賦值語句將a的值取出來,加上3,得到結果4,將4再寫入a中去。由于C++使用“ソ’來代表賦值語句,很容易使人和數學中的等號混淆起來,這點應注意。而如上的floata;語句,當還未對變量進行任何賦值操作時,a的值是什么?上帝才知道。當時的a的內容是什么(對于VC編譯器,在開啟了調試選項時,將會用OxCCCCCCCC填充這些未初始化內存),就用IEEE的real*4格式來解釋它并得到相應的ー個數字,也就是a的值。因此應在變量定義的時候就進行賦值(但是會有性能上的影響,不過很小),以初始化變量而防止出現莫名其妙的值,如:floata=O.Of;,賦值操作符上面的a=a+3;的意思就是讓a的值增加3。在C++中,對于這種情況給出了一種簡寫方案,即前面的語句可以寫成:a+=3;。應當注意這兩條語句從邏輯上講都是使變量a的值增3,但是它們實際是有區別的,后者可以被編譯成優化的代碼,因為其意思是使某ー塊內存的值增加一定數量,而前者是將一個數字寫入到某塊內存中。所以如果可能,應盡量使用后者,即a+=3;。這種語句可以讓編譯器進行一定的優化(但由于現在的編譯器都非常智能,能夠發現a=a+3;是對ー塊內存的增值操作而不是ー塊內存的賦值操作,因此上面兩條語句實際上可以認為完全相同,僅僅只具有簡寫的功能了)。對于上面的情況,也可以應用在減法、乘法等二元非邏輯操作符(不是邏輯值操作符,即不能a&&=3:)上,如:a*=3;a-=4;a|=34;a?=3;等。除了上面的簡寫外,C++還提供了一種簡寫方式,即a++;,其邏輯上等同于a+=1;?同上,在電腦編程中,加一和減ー是經常用到的,因此CPU專門提供了兩條指令來進行加一和減ー操作(轉成匯編語言就是Inc和Dec),但速度比直接通過加法或減法指令來執行要快得多。為此C++中也就提供了“++”和“一”操作符來對應Inc和Dec。所以a++;雖然邏輯上和a=a+1;等效,實際由于編譯器可能做出的優化處理而不同,但還是如上,由于編譯器的智能化,其是有可能看出a=a+1;可以編譯成Inc指令進而即使沒有使用a++;卻也依然可以得到優化的代碼,這樣a++;將只剩下簡寫的意義而已。應當注意一點,a=3;這句語句也將返回一個數字,也就是在a被賦完值后a的值。由于其可以返回數字,按照《C++從零開始(二)》中所說,“=”就屬于操作符,也就可以如下書寫:c=4+(a=3);之所以打括號是因為"="的優先級較“+”低,而更常見和正常的應用是:c=a=3;應該注意上面并不是將c和a賦值為3,而是在a被賦值為3后再將a賦值給c,雖然最后結果和c、a都賦值為3是ー樣的,但不應該這樣理解。由于a++;表示的就是a+=1;就是a=a+1;,因此a++;也將返回一個數字。也由于這個原因,C++又提供了另ー個簡寫方式,++a;。假設a為1,則a++;將先返回a的值,1,然后再將a的值加一;而++a;先將a的值加一,再返回a的值,2。而aー和一a也是如此,只不過是減ー罷了。上面的變量a按照最上面的變量定義,是float類型的變量,對它使用++操作符并不能得到預想的優化,因為float類型是浮點類型,其是使用!EEE的real*4格式來表示數字的,而不是二進制原碼或補碼,而前面提到的Inc和Dec指令都是出于二進制的表示優點來進行快速增一和減ー,所以如果對浮點類型的變量運用“++”操作符,將完全只是簡寫,沒有任何的優化效果(當然,如果CPU提供了新的指令集,如MMX等,以對real*4格式進行快速增一和減ー操作,且編譯器支持相應指令集,則還是可以產生優化效果的)。賦值操作符的返冋值在進ー步了解++a和a++的區別前,先來了解何謂操作符的計算(Evaluate)?操作符就是將給定的數字做ー些處理,然后返回一個數字。而操作符的計算也就是執行操作符的處理,并返回值。前面已經知道,操作符是個符號,其ー側或兩側都可以接數字,也就是再接其他操作符,面又由于賦值操作符也屬于一種操作符,因此操作符的執行順序變得相當重要。對于a+b+c,將先執行a+b,再執行(a+b)+c的操作。你可能覺得沒什么,那么如下,假設a之前為1:c=(a*=2)+(a+=3);上句執行后a為5。而c=(a+=3)+(a*=2);執行后,a就是8了。那么c呢?結果可能會大大的出乎你的意料。前者的c為10,而后者的c為16。上面其實是ー個障眼法,其中的“+”沒有任何意義,即之所以會從左向右執行并不是因為"+”的緣故,而是因為(a*=2)和(a+=3)的優先級相同,而按照“()”的計算順序,是從左向右來計算的。但為什么c的值不是預想的2+5和4+8呢?因為賦值操作符的返回值的關系。賦值操作符返回的數字不是變量的值,而是變量對應的地址。這很重要。前面說過,光寫ー個變量名就會返回相應變量的值,那是因為變量是ー個映射,變量名就等同于ー個地址。C++中將數字看作一個很特殊的操作符,即任何ー個數字都是一個操作符。而地址就和長整型、単精度浮點數這類ー樣,是數字的一種類型。當ー個數字是地址類型時,作為操作符,其沒有要操作的數字,僅僅返回將此數字看作地址而標識的內存中的內容(用這個地址的類型來解釋)。地址可以通過多種途徑得到,如上面光寫ー個變量名就可以得到其對應的地址,而得到的地址的類型也就是相應的變量的類型。如果這句話不能理解,在看過下面的類型轉換一節后應該就能了解了。所以前面的c=(a+=3)+(a*=2);,由于“()”的參與改變了優先級面先執行了兩個賦值操作符,然后兩個賦值操作符都返回a的地址,然后計算‘'+”的值,分別計算兩邊的數字——a的地址(a的地址也是ー個操作符),也就是已經執行過兩次賦值操作的a的值,得8,故最后的c為16。而另ー個也由于同樣的原因使得c為10。現在考慮操作符的計算順序。當同時出現了幾個優先級相同的操作符時,不同的操作符具有不同的計算順序。前面的“()”以及、"ボ’等這類二元操作符的計算順序都是從左向右計算,而“!”、負號“-”等前面介紹過的一元操作符都是從右向左計算的,如:!-!!a;,假設a為3。先計算從左朝右數第三個“!”的值,導致計算a的地址的值,得3;然后邏輯取反得。,接著再計算第二個“!”的值,邏輯取反后得1,再計算負號“-”的值,得T,最后計算第一個“げ的值,得〇。賦值操作符都是從右向左計算的,除了后綴“++”和后綴“一”(即上面的a++和a-),因此上面的c=a=3;,因為兩個“=”優先級相同,從右向左計算,先計算a=3的值,返回a對應的地址,然后計算返回的地址而得到值3,再計算c=(a=3),將3寫入c。面不是從左向右計算,即先計算c=a,返回c的地址,然后再計算第二個“=”,將3寫入c,這樣a就沒有被賦值而出現問題。又:a=1;c=2;c*=a+=4;由于“*=”和“+=”的優先級相同,從右向左計算先計算a+=4,得a為5,然后返回a的地址,再計算a的地址得a的值5,計算“*=”以使得c的值為10。因此按照前面所說,++a將返回a的地址,而a++也因為是賦值操作符而必須返回一個地址,但很明顯地不能是a的地址了,因此編譯器將編寫代碼以從棧中分配ー塊和a同樣大小的內存,并將a的值復制到這塊臨時內存中,然后返回這塊臨時內存的地址。由于這塊臨時內存是因為編譯器的需要而分配的,與程序員完全沒有關系,因此程序員是不應該也不能寫這塊臨時內存的(因為編譯器負責編譯代碼,如果程序員欲訪問這塊內存,編譯器將報錯),但可以讀取它的值,這也是返回地址的主要目的。所以如下的語句沒有問題:(++a)=a+=34;但(a++)=a+=34;就會在編譯時報錯,因為a++返回的地址所標識的內存只能由編譯器負責處理,程序員只能獲得其值而已。a++的意思是先返回a的值,也就是上面說的臨時內存的地址,然后再將變量的值加一。如果同時出現多個a++,那么每個a++都需要分配ー塊臨時內存(注意前面c=(a+=3)+(a*=2);的說明),那么將有點糟糕,面且a++的意思是先返回a的值,那么到底是什么時候的a的值呢?在VC中,當表達式中出現后綴“++”或后綴“一”時,只分配一塊臨時內存,然后所有的后綴“++”或后綴“一”都返回這個臨時內存的地址,然后在所有的可以計算的其他操作符的值計算完畢后,再將對應變量的值寫入到臨時內存中,計算表達式的值,最后將對應變量的值加ー或減ー。因此:a=1;c=(a++)+(a++);執行后,c的值為2,而a的值為3?而如下:a=1;b=1;c=(++a)+(a++)+(b*=a++)+(a*=2)+(a*=a++);執行時,先分配臨時內存,然后由于5個“()”,其計算順序是從左向右,計算++a的值,返回增ー后的a的地址,a的值為2

計算a++的值,返冋臨時內存的地址,a的值仍為2計算b*=a++中的a++,返回臨時內存的地址,a的值仍為2計算b*=a++中的,將a的值寫入臨時內存,計算得b的值為2,返回b的地址計算a*=2的值,返回a的地址,a的值為4計算a*=a++中的a++,返回臨時內存的地址,a的值仍為4計算a*=a++中的“*=”,將a的值寫入臨時內存,返回a的地址,a的值為16計算剩下的,為了進行計算,將a的值寫入臨時內存,得值16+16+2+16+16為66,寫入c中計算三個a++欠下的加ー,a最后變為19。上面說了那么多,無非只是想告誡你——在表達式中運用賦值操作符是不被推崇的。因為其不符合平常的數學表達式的習慣,目.計算順序很容易搞混。如果有多個‘'++”操作符,最好還是將表達式分開,否則很容易導致錯誤的計算順序而計算錯誤。并且導致計算順序混亂的還不止上面的a++就完了,為了讓你更加地重視前面的紅字,下面將介紹更令人火大的東西,如果你已經同意上面的紅字,則ド面這ー節完全可以跳過,其對編程來講可以認為根本沒有任何意義(要不是為了寫這篇文章,我都不知道它的存在)。序列點(SequencePoint)和附加效果(SideEffect)在計算c=a++時,當c的值計算(Evaluate)出來時,a的值也增加了?,a的值加一一就是計算前面表達式的附加效果。有什么問題?它可能影響表達式的計算結果。對于a=0;b=1;(a*=2)&&(b+=2);,由于兩個“()”優先級相同,從左向右計算,計算“*=”而返回a的地址,再計算“+=”而返回b的地址,最后山于a的值為〇而返回邏輯假。很正常,但效率低了點。如果''&&"左邊的數字已經是〇了,則不再需要計算右邊的式子。同樣,如果“|ド左邊的數字已經非零了,也不需要再計算右邊的數ア。因為“&&”和“丨ド都是數學上的,數學上不管先計算加號左邊的值還是右邊的值,結果都不會改變,因此“&&”和“丨丨”オ會做剛オ的解釋。這也是C++保證的,既滿足數學的定義,乂能提供優化的途徑(“&&”和“|ド右邊的數字不用計算了)。因此上面的式子就會被解釋成——如果a在自乘了2后的值為0,則b就不用再自增2了。這很明顯地違背了我們的初衷,認為b無論如何都會被自增2的。但是C++卻這樣保證,不僅僅是因為數學的定義,還由于代碼生成的優化。但是按照操作符的優先級進行計算,上面的b+=2依舊會被執行的(這也正是我們會書寫上面代碼的原因)。為了實現當a為0時b+=2不會被計算,C++提出了序列點的概念。序列點是?些特殊位置,由C++強行定義(C++并未給岀序列點的定義,因此不同的編譯器可能給出不同的序列點定義,VC是按照C語言定義的序列點)。當在進行操作符的計算時,如果遇到序列點,則序列點處的值必須被優先計算,以保證?些特殊用途,如上面的保證當a為O時不計算b+=2,并且序列點相關的操作符(如前面的“&&”和“Iド)也將被計算完畢,然后オ恢復正常的計算。“&&”的左邊數字的計算就是ー個序列點,而“"的左邊數字的計算也是。C++定義了多個序列點,包括條件語句、函數參數等條件下的表達式計算,在此,不需要具體了解有哪些序列點,只需要知道山于序列點的存在而可能導致賦值操作符的計算出乎意料。F面就來分析ー個例子:a=0;b=1;(a*=2)&&(b+=++a);按照優先級的順序,編譯器發現要先計算a*=2,再計算++a,接著“+=”,最后計算“&&”。然后編譯器發現這個計算過程中,出現了“&&”左邊的數字這個序列點,其要保證被優先計算,這樣就有可能不用計算b+=++a了。所以編譯器先計算“&&”的數字,通過上面的計算過程,編譯器發現就要計算a*=2才能得到“&&”左邊的數字,因此將先計算a*=2,返回a的地址,然后計算“&&”左邊的數字,得a的值為0.因此就不計算b+=++a了。而不是最開始想象的由于優先級的關系先將a加一后再進行a的計算,以返回1。所以上面計算完畢后,a為0,b為!,返回0,表示邏輯假。因此序列點的出現是為了保證一些特殊規則的出現,如上面的“&&”和“|"。再考慮",”操作符,其操作是計算兩邊的值,然后返回右邊的數字,即:a,b+3將返回b+3的值,但是a依舊會被計算。山于“,”的優先級是最低的(但高于前面提到的“數字”操作符),因此如果a=3,4;,那么a將為3而不是4,因為先計算“=",返回a的地址后再計算“,”。又:

a=l:b=0;b=(a+=2)+((a*=2,b=a-l)&&(c=a));由于“&&”左邊數字是ー個序列點,因此先計算a*=2,b的值,但根據、”的返回值定義,其只返回右邊的數字,因此不計算a*=2而直接計算b=a-1得0,“&&”就返回了,但是a*=2就沒有被計算而導致a的值依I口為!,這違背了“,”的定義。為了消除這一點(當然可能還有其他應用、”的情況),C++也將ヾ”的左邊數字定為了序列點,即一定會優先執行ヾ”左邊的數字以保證ヾ”的定義——計算兩邊的數字。所以上面就由丁ヾ”左邊數字這個序列點而導致a*=2被優先執行,并導致b為1,因此由于“&&”是序列點且其左邊數字非零而必須計算完右邊數字后オ恢復正常優先級,而計算c=a,得2,最后オ恢復正常優先級順序,執行a+=2和“+”。結果就a為4,c為2,b為5。所以前面的a=3,4;其實就應該是編譯器先發現ヾ”這個序列點,而發現要計算“,”左邊的值,必須先計算出a=3,因此オ先計算a=3以至于感覺序列點好像沒有發生作用。下面的式子請自行分析,執行后a為4,但如果將其中的ヾ”換成“&&”,a為2。a=l;b=(a*=2)+((a*=3),(a-=2));如果上面你看得很暈,沒關系,因為上面的內容根本可以認為毫無意義,寫在這里也只是為了進ー步向你證明,在表達式中運

溫馨提示

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

評論

0/150

提交評論