![linux-C編程一站式學(xué)習(xí)_第1頁](http://file4.renrendoc.com/view2/M03/31/2C/wKhkFmaBtFmAQUu0AABC6wEQDxI081.jpg)
![linux-C編程一站式學(xué)習(xí)_第2頁](http://file4.renrendoc.com/view2/M03/31/2C/wKhkFmaBtFmAQUu0AABC6wEQDxI0812.jpg)
![linux-C編程一站式學(xué)習(xí)_第3頁](http://file4.renrendoc.com/view2/M03/31/2C/wKhkFmaBtFmAQUu0AABC6wEQDxI0813.jpg)
![linux-C編程一站式學(xué)習(xí)_第4頁](http://file4.renrendoc.com/view2/M03/31/2C/wKhkFmaBtFmAQUu0AABC6wEQDxI0814.jpg)
![linux-C編程一站式學(xué)習(xí)_第5頁](http://file4.renrendoc.com/view2/M03/31/2C/wKhkFmaBtFmAQUu0AABC6wEQDxI0815.jpg)
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
linux-C編程一站式學(xué)習(xí)目錄1.程序的基本概念1.程序和編程語言2.自然語言和形式語言3.程序的調(diào)試4.第一個程序2.常量、變量和表達式1.繼續(xù)HelloWorld2.常量3.變量4.賦值5.表達式6.字符類型與字符編碼3.簡單函數(shù)1.數(shù)學(xué)函數(shù)2.自定義函數(shù)3.形參和實參4.全局變量、局部變量和作用域4.分支語句1.if語句2.if/else語句3.布爾代數(shù)4.switch語句5.深入理解函數(shù)1.return語句2.增量式開發(fā)3.遞歸6.循環(huán)語句1.while語句2.do/while語句3.for語句4.break和continue語句5.嵌套循環(huán)6.goto語句和標號7.結(jié)構(gòu)體1.復(fù)合類型與結(jié)構(gòu)體2.數(shù)據(jù)抽象3.數(shù)據(jù)類型標志4.嵌套結(jié)構(gòu)體8.數(shù)組1.數(shù)組的基本概念2.數(shù)組應(yīng)用實例:統(tǒng)計隨機數(shù)3.數(shù)組應(yīng)用實例:直方圖4.字符串5.多維數(shù)組9.編碼風(fēng)格1.縮進和空白2.注釋3.標識符命名4.函數(shù)5.indent工具10.gdb1.單步執(zhí)行和跟蹤函數(shù)調(diào)用2.斷點3.觀察點4.段錯誤11.排序與查找1.算法的概念2.插入排序3.算法的時間復(fù)雜度分析4.歸并排序5.線性查找6.折半查找12.棧與隊列1.數(shù)據(jù)結(jié)構(gòu)的概念2.堆棧3.深度優(yōu)先搜索4.隊列與廣度優(yōu)先搜索5.環(huán)形隊列13.本階段總結(jié)II.C語言本質(zhì)14.計算機中數(shù)的表示1.為什么計算機用二進制計數(shù)2.不同進制之間的換算3.整數(shù)的加減運算3.1.SignandMagnitude表示法3.2.1'sComplement表示法3.3.2'sComplement表示法3.4.有符號數(shù)和無符號數(shù)4.浮點數(shù)15.數(shù)據(jù)類型詳解1.整型2.浮點型3.類型轉(zhuǎn)換3.1.IntegerPromotion3.2.UsualArithmeticConversion3.3.由賦值產(chǎn)生的類型轉(zhuǎn)換3.4.強制類型轉(zhuǎn)換3.5.編譯器如何處理類型轉(zhuǎn)換16.運算符詳解1.位運算1.1.按位與、或、異或、取反運算1.2.移位運算1.3.掩碼1.4.異或運算的一些特性2.其它運算符2.1.復(fù)合賦值運算符2.2.條件運算符2.3.逗號運算符2.4.sizeof運算符與typedef類型聲明3.SideEffect與SequencePoint4.運算符總結(jié)17.計算機體系結(jié)構(gòu)基礎(chǔ)1.內(nèi)存與地址2.CPU3.設(shè)備4.MMU5.MemoryHierarchy18.x86匯編程序基礎(chǔ)1.最簡單的匯編程序2.x86的寄存器3.第二個匯編程序4.尋址方式5.ELF文件5.1.目標文件5.2.可執(zhí)行文件19.匯編與C之間的關(guān)系1.函數(shù)調(diào)用2.main函數(shù)和啟動例程3.變量的存儲布局4.結(jié)構(gòu)體和聯(lián)合體5.C內(nèi)聯(lián)匯編6.volatile限定符20.鏈接詳解1.多目標文件的鏈接2.定義和聲明2.1.extern和static關(guān)鍵字2.2.頭文件2.3.定義和聲明的詳細規(guī)則3.靜態(tài)庫4.共享庫4.1.編譯、鏈接、運行4.2.動態(tài)鏈接的過程4.3.共享庫的命名慣例5.虛擬內(nèi)存管理21.預(yù)處理1.預(yù)處理的步驟2.宏定義2.1.函數(shù)式宏定義2.2.內(nèi)聯(lián)函數(shù)2.3.#、##運算符和可變參數(shù)2.4.宏展開的步驟3.條件預(yù)處理指示4.其它預(yù)處理特性22.Makefile基礎(chǔ)1.基本規(guī)則2.隱含規(guī)則和模式規(guī)則3.變量4.自動處理頭文件的依賴關(guān)系5.常用的make命令行選項23.指針1.指針的基本概念2.指針類型的參數(shù)和返回值3.指針與數(shù)組4.指針與const限定符5.指針與結(jié)構(gòu)體6.指向指針的指針與指針數(shù)組7.指向數(shù)組的指針與多維數(shù)組8.函數(shù)類型和函數(shù)指針類型9.不完全類型和復(fù)雜聲明24.函數(shù)接口1.本章的預(yù)備知識1.1.strcpy與strncpy1.2.malloc與free2.傳入?yún)?shù)與傳出參數(shù)3.兩層指針的參數(shù)4.返回值是指針的情況5.回調(diào)函數(shù)6.可變參數(shù)25.C標準庫1.字符串操作函數(shù)1.1.初始化字符串1.2.取字符串的長度1.3.拷貝字符串1.4.連接字符串1.5.比較字符串1.6.搜索字符串1.7.分割字符串2.標準I/O庫函數(shù)2.1.文件的基本概念2.2.fopen/fclose2.3.stdin/stdout/stderr2.4.errno與perror函數(shù)2.5.以字節(jié)為單位的I/O函數(shù)2.6.操作讀寫位置的函數(shù)2.7.以字符串為單位的I/O函數(shù)2.8.以記錄為單位的I/O函數(shù)2.9.格式化I/O函數(shù)2.10.C標準庫的I/O緩沖區(qū)2.11.本節(jié)綜合練習(xí)3.數(shù)值字符串轉(zhuǎn)換函數(shù)4.分配內(nèi)存的函數(shù)26.鏈表、二叉樹和哈希表1.鏈表1.1.單鏈表1.2.雙向鏈表1.3.靜態(tài)鏈表1.4.本節(jié)綜合練習(xí)2.二叉樹2.1.二叉樹的基本概念2.2.排序二叉樹3.哈希表27.本階段總結(jié)III.Linux系統(tǒng)編程28.文件與I/O1.匯編程序的Helloworld2.C標準I/O庫函數(shù)與UnbufferedI/O函數(shù)3.open/close4.read/write5.lseek6.fcntl7.ioctl8.mmap29.文件系統(tǒng)1.引言2.ext2文件系統(tǒng)2.1.總體存儲布局2.2.實例剖析2.3.數(shù)據(jù)塊尋址2.4.文件和目錄操作的系統(tǒng)函數(shù)3.VFS3.1.內(nèi)核數(shù)據(jù)結(jié)構(gòu)3.2.dup和dup2函數(shù)30.進程1.引言2.環(huán)境變量3.進程控制3.1.fork函數(shù)3.2.exec函數(shù)3.3.wait和waitpid函數(shù)4.進程間通信4.1.管道4.2.其它IPC機制5.練習(xí):實現(xiàn)簡單的Shell31.Shell腳本1.Shell的歷史2.Shell如何執(zhí)行命令2.1.執(zhí)行交互式命令2.2.執(zhí)行腳本3.Shell的基本語法3.1.變量3.2.文件名代換(Globbing):*?[]3.3.命令代換:`或$()3.4.算術(shù)代換:$(())3.5.轉(zhuǎn)義字符\3.6.單引號3.7.雙引號4.bash啟動腳本4.1.作為交互登錄Shell啟動,或者使用--login參數(shù)啟動4.2.以交互非登錄Shell啟動4.3.非交互啟動4.4.以sh命令啟動5.Shell腳本語法[5.1.條件測試:test5.2.if/then/elif/else/fi5.3.case/esac5.4.for/do/done5.5.while/do/done5.6.位置參數(shù)和特殊變量5.7.函數(shù)6.Shell腳本的調(diào)試方法32.正則表達式1.引言2.基本語法3.sed4.awk5.練習(xí):在C語言中使用正則表達式33.信號1.信號的基本概念2.產(chǎn)生信號2.1.通過終端按鍵產(chǎn)生信號2.2.調(diào)用系統(tǒng)函數(shù)向進程發(fā)信號2.3.由軟件條件產(chǎn)生信號3.阻塞信號3.1.信號在內(nèi)核中的表示3.2.信號集操作函數(shù)3.3.sigprocmask3.4.sigpending4.捕捉信號4.1.內(nèi)核如何實現(xiàn)信號的捕捉4.2.sigaction4.3.pause4.4.可重入函數(shù)4.5.sig_atomic_t類型與volatile限定符4.6.競態(tài)條件與sigsuspend函數(shù)4.7.關(guān)于SIGCHLD信號34.終端、作業(yè)控制與守護進程1.終端1.1.終端的基本概念1.2.終端登錄過程1.3.網(wǎng)絡(luò)登錄過程2.作業(yè)控制2.1.Session與進程組2.2.與作業(yè)控制有關(guān)的信號3.守護進程35.線程1.線程的概念2.線程控制2.1.創(chuàng)建線程2.2.終止線程3.線程間同步3.1.mutex3.2.ConditionVariable3.3.Semaphore3.4.其它線程間同步機制4.編程練習(xí)36.TCP/IP協(xié)議基礎(chǔ)1.TCP/IP協(xié)議棧與數(shù)據(jù)包封裝2.以太網(wǎng)(RFC894)幀格式3.ARP數(shù)據(jù)報格式4.IP數(shù)據(jù)報格式5.IP地址與路由6.UDP段格式7.TCP協(xié)議7.1.段格式7.2.通訊時序7.3.流量控制37.socket編程1.預(yù)備知識1.1.網(wǎng)絡(luò)字節(jié)序1.2.socket地址的數(shù)據(jù)類型及相關(guān)函數(shù)2.基于TCP協(xié)議的網(wǎng)絡(luò)程序2.1.最簡單的TCP網(wǎng)絡(luò)程序2.2.錯誤處理與讀寫控制2.3.把client改為交互式輸入2.4.使用fork并發(fā)處理多個client的請求2.5.setsockopt2.6.使用select3.基于UDP協(xié)議的網(wǎng)絡(luò)程序4.UNIXDomainSocketIPC5.練習(xí):實現(xiàn)簡單的Web服務(wù)器5.1.基本HTTP協(xié)議5.2.執(zhí)行CGI程序附錄字符編碼1.ASCII碼2.Unicode和UTF-83.在LinuxC編程中使用Unicode和UTF-8索引注:原文檔電子版(非掃描),需要的請下載本文檔后留言謝謝。第1章程序的基本概念目錄1.程序和編程語言2.自然語言和形式語言3.程序的調(diào)試4.第一個程序1.程序和編程語言程序(Program)告訴計算機應(yīng)如何完成一個計算任務(wù),這里的計算可以是數(shù)學(xué)運算,比如解方程,也可以是符號運算,比如查找和替換文檔中的某個單詞。從根本上說,計算機是由數(shù)字電路組成的運算機器,只能對數(shù)字做運算,程序之所以能做符號運算,是因為符號在計算機內(nèi)部也是用數(shù)字表示的。此外,程序還可以處理聲音和圖像,聲音和圖像在計算機內(nèi)部必然也是用數(shù)字表示的,這些數(shù)字經(jīng)過專門的硬件設(shè)備轉(zhuǎn)換成人可以聽到、看到的聲音和圖像。程序由一系列指令(Instruction)組成,指令是指示計算機做某種運算的命令,通常包括以下幾類:輸入(Input)從鍵盤、文件或者其它設(shè)備獲取數(shù)據(jù)。輸出(Output)把數(shù)據(jù)顯示到屏幕,或者存入一個文件,或者發(fā)送到其它設(shè)備?;具\算執(zhí)行最基本的數(shù)學(xué)運算(加減乘除)和數(shù)據(jù)存取。測試和分支測試某個條件,然后根據(jù)不同的測試結(jié)果執(zhí)行不同的后續(xù)指令。循環(huán)重復(fù)執(zhí)行一系列操作。對于程序來說,有上面這幾類指令就足夠了。你曾用過的任何一個程序,不管它有多么復(fù)雜,都是由這幾類指令組成的。程序是那么的復(fù)雜,而編寫程序可以用的指令卻只有這么簡單的幾種,這中間巨大的落差就要由程序員去填了,所以編寫程序理應(yīng)是一件相當復(fù)雜的工作。編寫程序可以說就是這樣一個過程:把復(fù)雜的任務(wù)分解成子任務(wù),把子任務(wù)再分解成更簡單的任務(wù),層層分解,直到最后簡單得可以用以上指令來完成。編程語言(ProgrammingLanguage)分為低級語言(Low-levelLanguage)和高級語言(High-levelLanguage)。機器語言(MachineLanguage)和匯編語言(AssemblyLanguage)屬于低級語言,直接用計算機指令編寫程序。而C、C++、Java、Python等屬于高級語言,用語句(Statement)編寫程序,語句是計算機指令的抽象表示。舉個例子,同樣一個語句用C語言、匯編語言和機器語言分別表示如下:表1.1.一個語句的三種表示編程語言表示形式C語言a=b+1;匯編語言mov0x804a01c,%eaxadd$0x1,%eaxmov%eax,0x804a018機器語言a11ca0040883c001a318a00408計算機只能對數(shù)字做運算,符號、聲音、圖像在計算機內(nèi)部都要用數(shù)字表示,指令也不例外,上表中的機器語言完全由十六進制數(shù)字組成。最早的程序員都是直接用機器語言編程,但是很麻煩,需要查大量的表格來確定每個數(shù)字表示什么意思,編寫出來的程序很不直觀,而且容易出錯,于是有了匯編語言,把機器語言中一組一組的數(shù)字用助記符(Mnemonic)表示,直接用這些助記符寫出匯編程序,然后讓匯編器(Assembler)去查表把助記符替換成數(shù)字,也就把匯編語言翻譯成了機器語言。從上面的例子可以看出,匯編語言和機器語言的指令是一一對應(yīng)的,匯編語言有三條指令,機器語言也有三條指令,匯編器就是做一個簡單的替換工作,例如在第一條指令中,把movl?,%eax這種格式的指令替換成機器碼a1?,?表示一個地址,在匯編指令中是0x804a01c,轉(zhuǎn)換成機器碼之后是1ca00408(這是指令中的十六進制數(shù)的小端表示,小端表示將在第5.1節(jié)“目標文件”介紹)。從上面的例子還可以看出,C語言的語句和低級語言的指令之間不是簡單的一一對應(yīng)關(guān)系,一條a=b+1;語句要翻譯成三條匯編或機器指令,這個過程稱為編譯(Compile),由編譯器(Compiler)來完成,顯然編譯器的功能比匯編器要復(fù)雜得多。用C語言編寫的程序必須經(jīng)過編譯轉(zhuǎn)成機器指令才能被計算機執(zhí)行,編譯需要花一些時間,這是用高級語言編程的一個缺點,然而更多的是優(yōu)點。首先,用C語言編程更容易,寫出來的代碼更緊湊,可讀性更強,出了錯也更容易改正。其次,C語言是可移植的(Portable)或者稱為平臺無關(guān)的(PlatformIndependent)。平臺這個詞有很多種解釋,可以指計算機體系結(jié)構(gòu)(Architecture),也可以指操作系統(tǒng)(OperatingSystem),也可以指開發(fā)平臺(編譯器、鏈接器等)。不同的計算機體系結(jié)構(gòu)有不同的指令集(InstructionSet),可以識別的機器指令格式是不同的,直接用某種體系結(jié)構(gòu)的匯編或機器指令寫出來的程序只能在這種體系結(jié)構(gòu)的計算機上運行,然而各種體系結(jié)構(gòu)的計算機都有各自的C編譯器,可以把C程序編譯成各種不同體系結(jié)構(gòu)的機器指令,這意味著用C語言寫的程序只需稍加修改甚至不用修改就可以在各種不同的計算機上編譯運行。各種高級語言都具有C語言的這些優(yōu)點,所以絕大部分程序是用高級語言編寫的,只有和硬件關(guān)系密切的少數(shù)程序(例如驅(qū)動程序)才會用到低級語言。還要注意一點,即使在相同的體系結(jié)構(gòu)和操作系統(tǒng)下,用不同的C編譯器(或者同一個C編譯器的不同版本)編譯同一個程序得到的結(jié)果也有可能不同,C語言有些語法特性在C標準中并沒有明確規(guī)定,各編譯器有不同的實現(xiàn),編譯出來的指令的行為特性也會不同,應(yīng)該盡量避免使用不可移植的語法特性。總結(jié)一下編譯執(zhí)行的過程,首先你用文本編輯器寫一個C程序,然后保存成一個文件,例如program.c(通常C程序的文件名后綴是.c),這稱為源代碼(SourceCode)或源文件,然后運行編譯器對它進行編譯,編譯的過程并不執(zhí)行程序,而是把源代碼全部翻譯成機器指令,再加上一些描述信息,生成一個新的文件,例如a.out,這稱為可執(zhí)行文件,可執(zhí)行文件可以被操作系統(tǒng)加載運行,計算機執(zhí)行該文件中由編譯器生成的指令,如下圖所示:圖1.1.編譯執(zhí)行的過程有些高級語言以解釋(Interpret)的方式執(zhí)行,解釋執(zhí)行過程和C語言的編譯執(zhí)行過程很不一樣。例如編寫一個Shell腳本script.sh,內(nèi)容如下:#!/bin/sh
VAR=1
VAR=$(($VAR+1))
echo$VAR
定義Shell變量VAR的初始值是1,然后自增1,然后打印VAR的值。用Shell程序/bin/sh解釋執(zhí)行這個腳本,結(jié)果如下:$/bin/shscript.sh
2
這里的/bin/sh稱為解釋器(Interpreter),它把腳本中的每一行當作一條命令解釋執(zhí)行,而不需要先生成包含機器指令的可執(zhí)行文件再執(zhí)行。如果把腳本中的這三行當作三條命令直接敲到Shell提示符下,也能得到同樣的結(jié)果:$VAR=1
$VAR=$(($VAR+1))
$echo$VAR
2
圖1.2.解釋執(zhí)行的過程編程語言仍在發(fā)展演化。以上介紹的機器語言稱為第一代語言(1GL,1stGenerationProgrammingLanguage),匯編語言稱為第二代語言(2GL,2ndGenerationProgrammingLanguage),C、C++、Java、Python等可以稱為第三代語言(3GL,3rdGenerationProgrammingLanguage)。目前已經(jīng)有了4GL(4thGenerationProgrammingLanguage)和5GL(5thGenerationProgrammingLanguage)的概念。3GL的編程語言雖然是用語句編程而不直接用指令編程,但語句也分為輸入、輸出、基本運算、測試分支和循環(huán)等幾種,和指令有直接的對應(yīng)關(guān)系。而4GL以后的編程語言更多是描述要做什么(Declarative)而不描述具體一步一步怎么做(Imperative),具體一步一步怎么做完全由編譯器或解釋器決定,例如SQL語言(SQL,StructuredQueryLanguage,結(jié)構(gòu)化查詢語言)就是這樣的例子。習(xí)題1、解釋執(zhí)行的語言相比編譯執(zhí)行的語言有什么優(yōu)缺點?這是我們的第一個思考題。本書的思考題通常要求讀者系統(tǒng)地總結(jié)當前小節(jié)的知識,結(jié)合以前的知識,并經(jīng)過一定的推理,然后作答。本書強調(diào)的是基本概念,讀者應(yīng)該抓住概念的定義和概念之間的關(guān)系來總結(jié),比如本節(jié)介紹了很多概念:程序由語句或指令組成,計算機只能執(zhí)行低級語言中的指令(匯編語言的指令要先轉(zhuǎn)成機器碼才能執(zhí)行),高級語言要執(zhí)行就必須先翻譯成低級語言,翻譯的方法有兩種--編譯和解釋,雖然有這樣的不便,但高級語言有一個好處是平臺無關(guān)性。什么是平臺?一種平臺,就是一種體系結(jié)構(gòu),就是一種指令集,就是一種機器語言,這些都可看作是一一對應(yīng)的,上文并沒有用“一一對應(yīng)”這個詞,但讀者應(yīng)該能推理出這個結(jié)論,而高級語言和它們不是一一對應(yīng)的,因此高級語言是平臺無關(guān)的,概念之間像這樣的數(shù)量對應(yīng)關(guān)系尤其重要。那么編譯和解釋的過程有哪些不同?主要的不同在于什么時候翻譯和什么時候執(zhí)行?,F(xiàn)在回答這個思考題,根據(jù)編譯和解釋的不同原理,你能否在執(zhí)行效率和平臺無關(guān)性等方面做一下比較?希望讀者掌握以概念為中心的閱讀思考習(xí)慣,每讀一節(jié)就總結(jié)一套概念之間的關(guān)系圖畫在書上空白處。如果讀到后面某一節(jié)看到一個講過的概念,但是記不清在哪一節(jié)講過了,沒關(guān)系,書后的索引可以幫你找到它是在哪一節(jié)定義的。2.自然語言和形式語言自然語言(NaturalLanguage)就是人類講的語言,比如漢語、英語和法語。這類語言不是人為設(shè)計(雖然有人試圖強加一些規(guī)則)而是自然進化的。形式語言(FormalLanguage)是為了特定應(yīng)用而人為設(shè)計的語言。例如數(shù)學(xué)家用的數(shù)字和運算符號、化學(xué)家用的分子式等。編程語言也是一種形式語言,是專門設(shè)計用來表達計算過程的形式語言。形式語言有嚴格的語法(Syntax)規(guī)則,例如,3+3=6是一個語法正確的數(shù)學(xué)等式,而3=+6$則不是,H2O是一個正確的分子式,而2Zz則不是。語法規(guī)則是由符號(Token)和結(jié)構(gòu)(Structure)的規(guī)則所組成的。Token的概念相當于自然語言中的單詞和標點、數(shù)學(xué)式中的數(shù)和運算符、化學(xué)分子式中的元素名和數(shù)字,例如3=+6$的問題之一在于$不是一個合法的數(shù)也不是一個事先定義好的運算符,而2Zz的問題之一在于沒有一種元素的縮寫是Zz。結(jié)構(gòu)是指Token的排列方式,3=+6$還有一個結(jié)構(gòu)上的錯誤,雖然加號和等號都是合法的運算符,但是不能在等號之后緊跟加號,而2Zz的另一個問題在于分子式中必須把下標寫在化學(xué)元素名稱之后而不是前面。關(guān)于Token的規(guī)則稱為詞法(Lexical)規(guī)則,而關(guān)于結(jié)構(gòu)的規(guī)則稱為語法(Grammar)規(guī)則[\h1]。當閱讀一個自然語言的句子或者一種形式語言的語句時,你不僅要搞清楚每個詞(Token)是什么意思,而且必須搞清楚整個句子的結(jié)構(gòu)是什么樣的(在自然語言中你只是沒有意識到,但確實這樣做了,尤其是在讀外語時你肯定也意識到了)。這個分析句子結(jié)構(gòu)的過程稱為解析(Parse)。例如,當你聽到“Theothershoefell.”這個句子時,你理解theothershoe是主語而fell是謂語動詞,一旦解析完成,你就搞懂了句子的意思,如果知道shoe是什么東西,fall意味著什么,這句話是在什么上下文(Context)中說的,你還能理解這個句子主要暗示的內(nèi)容,這些都屬于語義(Semantic)的范疇。雖然形式語言和自然語言有很多共同之處,包括Token、結(jié)構(gòu)和語義,但是也有很多不一樣的地方。歧義性(Ambiguity)自然語言充滿歧義,人們通過上下文的線索和自己的常識來解決這個問題。形式語言的設(shè)計要求是清晰的、毫無歧義的,這意味著每個語句都必須有確切的含義而不管上下文如何。冗余性(Redundancy)為了消除歧義減少誤解,自然語言引入了相當多的冗余。結(jié)果是自然語言經(jīng)常說得啰里啰嗦,而形式語言則更加緊湊,極少有冗余。與字面意思的一致性自然語言充斥著成語和隱喻(Metaphor),我在某種場合下說“Theothershoefell”,可能并不是說誰的鞋掉了。而形式語言中字面(Literal)意思基本上就是真實意思,也會有一些例外,例如下一章要講的C語言轉(zhuǎn)義序列,但即使有例外也會明確規(guī)定哪些字面意思不是真實意思,它們所表示的真實意思又是什么。說自然語言長大的人(實際上沒有人例外),往往有一個適應(yīng)形式語言的困難過程。某種意義上,形式語言和自然語言之間的不同正像詩歌和說明文的區(qū)別,當然,前者之間的區(qū)別比后者更明顯:詩歌詞語的發(fā)音和意思一樣重要,全詩作為一個整體創(chuàng)造出一種效果或者表達一種感情。歧義和非字面意思不僅是常見的而且是刻意使用的。說明文詞語的字面意思顯得更重要,并且結(jié)構(gòu)能傳達更多的信息。詩歌只能看一個整體,而說明文更適合逐字句分析,但仍然充滿歧義。程序計算機程序是毫無歧義的,字面和本意高度一致,能夠完全通過對Token和結(jié)構(gòu)的分析加以理解?,F(xiàn)在給出一些關(guān)于閱讀程序(包括其它形式語言)的建議。首先請記住形式語言遠比自然語言緊湊,所以要多花點時間來讀。其次,結(jié)構(gòu)很重要,從上到下從左到右讀往往不是一個好辦法,而應(yīng)該學(xué)會在大腦里解析:識別Token,分解結(jié)構(gòu)。最后,請記住細節(jié)的影響,諸如拼寫錯誤和標點錯誤這些在自然語言中可以忽略的小毛病會把形式語言搞得面目全非。[\h1]很不幸,Syntax和Grammar通常都翻譯成“語法”,這讓初學(xué)者非?;靵y,Syntax的含義其實包含了Lexical和Grammar的規(guī)則,還包含一部分語義的規(guī)則,例如在C程序中變量應(yīng)先聲明后使用。即使在英文的文獻中Syntax和Grammar也?;煊?,在有些文獻中Syntax的含義不包括Lexical規(guī)則,只要注意上下文就不會誤解。另外,本書在翻譯容易引起混淆的時候通常直接用英文名稱,例如Token沒有十分好的翻譯,直接用英文名稱。3.程序的調(diào)試編程是一件復(fù)雜的工作,因為是人做的事情,所以難免經(jīng)常出錯。據(jù)說有這樣一個典故:早期的計算機體積都很大,有一次一臺計算機不能正常工作,工程師們找了半天原因最后發(fā)現(xiàn)是一只臭蟲鉆進計算機中造成的。從此以后,程序中的錯誤被叫做臭蟲(Bug),而找到這些Bug并加以糾正的過程就叫做調(diào)試(Debug)。有時候調(diào)試是一件非常復(fù)雜的工作,要求程序員概念明確、邏輯清晰、性格沉穩(wěn),還需要一點運氣。調(diào)試的技能我們在后續(xù)的學(xué)習(xí)中慢慢培養(yǎng),但首先我們要區(qū)分清楚程序中的Bug分為哪幾類。編譯時錯誤編譯器只能翻譯語法正確的程序,否則將導(dǎo)致編譯失敗,無法生成可執(zhí)行文件。對于自然語言來說,一點語法錯誤不是很嚴重的問題,因為我們?nèi)匀豢梢宰x懂句子。而編譯器就沒那么寬容了,只要有哪怕一個很小的語法錯誤,編譯器就會輸出一條錯誤提示信息然后罷工,你就得不到你想要的結(jié)果。雖然大部分情況下編譯器給出的錯誤提示信息就是你出錯的代碼行,但也有個別時候編譯器給出的錯誤提示信息幫助不大,甚至?xí)`導(dǎo)你。在開始學(xué)習(xí)編程的前幾個星期,你可能會花大量的時間來糾正語法錯誤。等到有了一些經(jīng)驗之后,還是會犯這樣的錯誤,不過會少得多,而且你能更快地發(fā)現(xiàn)錯誤原因。等到經(jīng)驗更豐富之后你就會覺得,語法錯誤是最簡單最低級的錯誤,編譯器的錯誤提示也就那么幾種,即使錯誤提示是有誤導(dǎo)的也能夠立刻找出真正的錯誤原因是什么。相比下面兩種錯誤,語法錯誤解決起來要容易得多。運行時錯誤編譯器檢查不出這類錯誤,仍然可以生成可執(zhí)行文件,但在運行時會出錯而導(dǎo)致程序崩潰。對于我們接下來的幾章將編寫的簡單程序來說,運行時錯誤很少見,到了后面的章節(jié)你會遇到越來越多的運行時錯誤。讀者在以后的學(xué)習(xí)中要時刻注意區(qū)分編譯時和運行時(Run-time)這兩個概念,不僅在調(diào)試時需要區(qū)分這兩個概念,在學(xué)習(xí)C語言的很多語法時都需要區(qū)分這兩個概念,有些事情在編譯時做,有些事情則在運行時做。邏輯錯誤和語義錯誤第三類錯誤是邏輯錯誤和語義錯誤。如果程序里有邏輯錯誤,編譯和運行都會很順利,看上去也不產(chǎn)生任何錯誤信息,但是程序沒有干它該干的事情,而是干了別的事情。當然不管怎么樣,計算機只會按你寫的程序去做,問題在于你寫的程序不是你真正想要的,這意味著程序的意思(即語義)是錯的。找到邏輯錯誤在哪需要十分清醒的頭腦,要通過觀察程序的輸出回過頭來判斷它到底在做什么。通過本書你將掌握的最重要的技巧之一就是調(diào)試。調(diào)試的過程可能會讓你感到一些沮喪,但調(diào)試也是編程中最需要動腦的、最有挑戰(zhàn)和樂趣的部分。從某種角度看調(diào)試就像偵探工作,根據(jù)掌握的線索來推斷是什么原因和過程導(dǎo)致了你所看到的結(jié)果。調(diào)試也像是一門實驗科學(xué),每次想到哪里可能有錯,就修改程序然后再試一次。如果假設(shè)是對的,就能得到預(yù)期的正確結(jié)果,就可以接著調(diào)試下一個Bug,一步一步逼近正確的程序;如果假設(shè)錯誤,只好另外再找思路再做假設(shè)?!爱斈惆巡豢赡艿娜刻蕹?,剩下的——即使看起來再怎么不可能——就一定是事實?!保词鼓銢]看過福爾摩斯也該看過柯南吧)。也有一種觀點認為,編程和調(diào)試是一回事,編程的過程就是逐步調(diào)試直到獲得期望的結(jié)果為止。你應(yīng)該總是從一個能正確運行的小規(guī)模程序開始,每做一步小的改動就立刻進行調(diào)試,這樣的好處是總有一個正確的程序做參考:如果正確就繼續(xù)編程,如果不正確,那么一定是剛才的小改動出了問題。例如,Linux操作系統(tǒng)包含了成千上萬行代碼,但它也不是一開始就規(guī)劃好了內(nèi)存管理、設(shè)備管理、文件系統(tǒng)、網(wǎng)絡(luò)等等大的模塊,一開始它僅僅是LinusTorvalds用來琢磨Intel80386芯片而寫的小程序。據(jù)LarryGreenfield說,“Linus的早期工程之一是編寫一個交替打印AAAA和BBBB的程序,這玩意兒后來進化成了Linux?!保ㄒ訲heLinuxUser'sGuideBeta1版)在后面的章節(jié)中會給出更多關(guān)于調(diào)試和編程實踐的建議。4.第一個程序通常一本教編程的書中第一個例子都是打印“Hello,World.”,這個傳統(tǒng)源自[K&R],用C語言寫這個程序可以這樣寫:例1.1.HelloWorld#include<stdio.h>
/*main:generatesomesimpleoutput*/
intmain(void)
{
printf("Hello,world.\n");
return0;
}
將這個程序保存成main.c,然后編譯執(zhí)行:$gccmain.c
$./a.out
Hello,world.
gcc是Linux平臺的C編譯器,編譯后在當前目錄下生成可執(zhí)行文件a.out,直接在命令行輸入這個可執(zhí)行文件的路徑就可以執(zhí)行它。如果不想把文件名叫a.out,可以用gcc的-o參數(shù)自己指定文件名:$gccmain.c-omain
$./main
Hello,world.
雖然這只是一個很小的程序,但我們目前暫時還不具備相關(guān)的知識來完全理解這個程序,比如程序的第一行,還有程序主體的intmain(void){...return0;}結(jié)構(gòu),這些部分我們暫時不詳細解釋,讀者現(xiàn)在只需要把它們看成是每個程序按慣例必須要寫的部分(Boilerplate)。但要注意main是一個特殊的名字,C程序總是從main里面的第一條語句開始執(zhí)行的,在這個程序中是指printf這條語句。第3行的/*...*/結(jié)構(gòu)是一個注釋(Comment),其中可以寫一些描述性的話,解釋這段程序在做什么。注釋只是寫給程序員看的,編譯器會忽略從/*到*/的所有字符,所以寫注釋沒有語法規(guī)則,愛怎么寫就怎么寫,并且不管寫多少都不會被編譯進可執(zhí)行文件中。printf語句的作用是把消息打印到屏幕。注意語句的末尾以;號(Semicolon)結(jié)束,下一條語句return0;也是如此。C語言用{}括號(Brace或CurlyBrace)把語法結(jié)構(gòu)分成組,在上面的程序中printf和return語句套在main的{}括號中,表示它們屬于main的定義之中。我們看到這兩句相比main那一行都縮進(Indent)了一些,在代碼中可以用若干個空格(Blank)和Tab字符來縮進,縮進不是必須的,但這樣使我們更容易看出這兩行是屬于main的定義之中的,要寫出漂亮的程序必須有整齊的縮進,第1節(jié)“縮進和空白”將介紹推薦的縮進寫法。正如前面所說,編譯器對于語法錯誤是毫不留情的,如果你的程序有一點拼寫錯誤,例如第一行寫成了stdoi.h,在編譯時會得到錯誤提示:$gccmain.c
main.c:1:19:error:stdoi.h:Nosuchfileordirectory
...
這個錯誤提示非常緊湊,初學(xué)者往往不容易看明白出了什么錯誤,即使知道這個錯誤提示說的是第1行有錯誤,很多初學(xué)者對照著書看好幾遍也看不出自己這一行哪里有錯誤,因為他們對符號和拼寫不敏感(尤其是英文較差的初學(xué)者),他們還不知道這些符號是什么意思又如何能記住正確的拼寫?對于初學(xué)者來說,最想看到的錯誤提示其實是這樣的:“在main.c程序第1行的第19列,您試圖包含一個叫做stdoi.h的文件,可惜我沒有找到這個文件,但我卻找到了一個叫做stdio.h的文件,我猜這個才是您想要的,對嗎?”可惜沒有任何編譯器會友善到這個程度,大多數(shù)時候你所得到的錯誤提示并不能直接指出誰是犯人,而只是一個線索,你需要根據(jù)這個線索做一些偵探和推理。有些時候編譯器的提示信息不是error而是warning,例如把上例中的printf("Hello,world.\n");改成printf(1);然后編譯運行:$gccmain.c
main.c:Infunction‘main’:
main.c:7:warning:passingargument1of‘printf’makespointerfromintegerwithoutacast
$./a.out
Segmentationfault
這個警告信息是說類型不匹配,但勉強還能配得上。警告信息不是致命錯誤,編譯仍然可以繼續(xù),如果整個編譯過程只有警告信息而沒有錯誤信息,仍然可以生成可執(zhí)行文件。但是,警告信息也是不容忽視的。出警告信息說明你的程序?qū)懙貌粔蛞?guī)范,可能有Bug,雖然能編譯生成可執(zhí)行文件,但程序的運行結(jié)果往往是不正確的,例如上面的程序運行時出了一個段錯誤,這屬于運行時錯誤。各種警告信息的嚴重程度不同,像上面這種警告幾乎一定表明程序中有Bug,而另外一些警告只表明程序?qū)懙貌粔蛞?guī)范,一般還是能正確運行的,有些不重要的警告信息gcc默認是不提示的,但這些警告信息也有可能表明程序中有Bug。一個好的習(xí)慣是打開gcc的-Wall選項,也就是讓gcc提示所有的警告信息,不管是嚴重的還是不嚴重的,然后把這些問題從代碼中全部消滅。比如把上例中的printf("Hello,world.\n");改成printf(0);然后編譯運行:$gccmain.c
$./a.out
編譯既不報錯也不報警告,一切正常,但是運行程序什么也不打印。如果打開-Wall選項編譯就會報警告了:$gcc-Wallmain.c
main.c:Infunction‘main’:
main.c:7:warning:nullargumentwherenon-nullrequired(argument1)
如果printf中的0是你不小心寫上去的(例如錯誤地使用了編輯器的查找替換功能),這個警告就能幫助你發(fā)現(xiàn)錯誤。雖然本書的命令行為了突出重點通常省略-Wall選項,但是強烈建議你寫每一個編譯命令時都加上-Wall選項。習(xí)題1、盡管編譯器的錯誤提示不夠友好,但仍然是學(xué)習(xí)過程中一個很有用的工具。你可以像上面那樣,從一個正確的程序開始每次改動一小點,然后編譯看是什么結(jié)果,如果出錯了,就盡量記住編譯器給出的錯誤提示并把改動還原。因為錯誤是你改出來的,你已經(jīng)知道錯誤原因是什么了,所以能很容易地把錯誤原因和錯誤提示信息對應(yīng)起來記住,這樣下次你在毫無防備的情況下撞到這個錯誤提示時就會很容易想到錯誤原因是什么了。這樣反復(fù)練習(xí),有了一定的經(jīng)驗積累之后面對編譯器的錯誤提示就會從容得多了。第2章常量、變量和表達式目錄1.繼續(xù)HelloWorld2.常量3.變量4.賦值5.表達式6.字符類型與字符編碼1.繼續(xù)HelloWorld在第4節(jié)“第一個程序”中,讀者應(yīng)該已經(jīng)嘗試對Helloworld程序做各種改動看編譯運行結(jié)果,其中有些改動會導(dǎo)致編譯出錯,有些改動會影響程序的輸出,有些改動則沒有任何影響,下面我們總結(jié)一下。首先,注釋可以跨行,也可以穿插在程序之中,看下面的例子。例2.1.帶更多注釋的HelloWorld#include<stdio.h>
/*
*comment1
*main:generatesomesimpleoutput
*/
intmain(void)
{
printf(/*comment2*/"Hello,world.\n");/*comment3*/
return0;
}
第一個注釋跨了四行,頭尾兩行是注釋的界定符(Delimiter)/和/,中間兩行開頭的*號(Asterisk)并沒有特殊含義,只是為了看起來整齊,這不是語法規(guī)則而是大家都遵守的C代碼風(fēng)格(CodingStyle)之一,代碼風(fēng)格將在第9章編碼風(fēng)格詳細介紹。使用注釋需要注意兩點:注釋不能嵌套(Nest)使用,就是說一個注釋的文字中不能再出現(xiàn)/和/了,例如/*text1/*text2*/text3*/是錯誤的,編譯器只把/*text1/*text2*/看成注釋,后面的text3*/無法解析,因而會報錯。有的C代碼中有類似//comment的注釋,兩個/斜線(Slash)表示從這里直到該行末尾的所有字符都屬于注釋,這種注釋不能跨行,也不能穿插在一行代碼中間。這是從C++借鑒的語法,在C99中被標準化。C語言標準C語言的發(fā)展歷史大致上分為三個階段:OldStyleC、C89和C99。KenThompson和DennisRitchie最初發(fā)明C語言時有很多語法和現(xiàn)在最常用的寫法并不一樣,但為了向后兼容性(BackwardCompatibility),這些語法仍然在C89和C99中保留下來了,本書不詳細講OldStyleC,但在必要的地方會加以說明。C89是最早的C語言規(guī)范,于1989年提出,1990年首先由ANSI(美國國家標準委員會,AmericanNationalStandardsInstitute)推出,后來被接納為ISO國際標準(ISO/IEC9899:1990),因而有時也稱為C90,最經(jīng)典的C語言教材[K&R]就是基于這個版本的,C89是目前最廣泛采用的C語言標準,大多數(shù)編譯器都完全支持C89。C99標準(ISO/IEC9899:1999)是在1999年推出的,加入了許多新特性,但目前仍沒有得到廣泛支持,在C99推出之后相當長的一段時間里,連gcc也沒有完全實現(xiàn)C99的所有特性。C99標準詳見[C99]。本書講C的語法以C99為準,但示例代碼通常只使用C89語法,很少使用C99的新特性。C標準的目的是為了精確定義C語言,而不是為了教別人怎么編程,C標準在表達上追求準確和無歧義,卻十分不容易看懂,[StandardC]和[StandardCLibrary]是對C89及其修訂版本的闡釋(可惜作者沒有隨C99更新這兩本書),比C標準更容易看懂,另外,參考[C99Rationale]也有助于加深對C標準的理解。像"Hello,world.\n"這種由雙引號(DoubleQuote)引起來的一串字符稱為字符串字面值(StringLiteral),或者簡稱字符串。注意,程序的運行結(jié)果并沒有雙引號,printf打印出來的只是里面的一串字符Hello,world.,因此雙引號是字符串字面值的界定符,夾在雙引號中間的一串字符才是它的內(nèi)容。注意,打印出來的結(jié)果也沒有\(zhòng)n這兩個字符,這是為什么呢?在第2節(jié)“自然語言和形式語言”中提到過,C語言規(guī)定了一些轉(zhuǎn)義序列(EscapeSequence),這里的\n并不表示它的字面意思,也就是說并不表示\和n這兩個字符本身,而是合起來表示一個換行符(LineFeed)。例如我們寫三條打印語句:printf("Hello,world.\n");
printf("Goodbye,");
printf("cruelworld!\n");
運行的結(jié)果是第一條語句單獨打到第一行,后兩條語句都打到第二行。為了節(jié)省篇幅突出重點,以后的例子通常省略#include和intmain(void){...}這些Boilerplate,但讀者在練習(xí)時需要加上這些構(gòu)成一個完整的程序才能編譯通過。C標準規(guī)定的轉(zhuǎn)義字符有以下幾種:表2.1.C標準規(guī)定的轉(zhuǎn)義字符\'單引號'(SingleQuote或Apostrophe)\"雙引號"\?問號?(QuestionMark)\\反斜線\(Backslash)\a響鈴(Alert或Bell)\b退格(Backspace)\f分頁符(FormFeed)\n換行(LineFeed)\r回車(CarriageReturn)\t水平制表符(HorizontalTab)\v垂直制表符(VerticalTab)如果在字符串字面值中要表示單引號和問號,既可以使用轉(zhuǎn)義序列\(zhòng)'和\?,也可以直接用字符'和?,而要表示\或"則必須使用轉(zhuǎn)義序列,因為\字符表示轉(zhuǎn)義而不表示它的字面含義,"表示字符串的界定符而不表示它的字面含義??梢娹D(zhuǎn)義序列有兩個作用:一是把普通字符轉(zhuǎn)義成特殊字符,例如把字母n轉(zhuǎn)義成換行符;二是把特殊字符轉(zhuǎn)義成普通字符,例如\和"是特殊字符,轉(zhuǎn)義后取它的字面值。C語言規(guī)定了幾個控制字符,不能用鍵盤直接輸入,因此采用\加字母的轉(zhuǎn)義序列表示。\a是響鈴字符,在字符終端下顯示這個字符的效果是PC喇叭發(fā)出嘀的一聲,在圖形界面終端下的效果取決于終端的實現(xiàn)。在終端下顯示\b和按下退格鍵的效果相同。\f是分頁符,主要用于控制打印機在打印源代碼時提前分頁,這樣可以避免一個函數(shù)跨兩頁打印。\n和\r分別表示LineFeed和CarriageReturn,這兩個詞來自老式的英文打字機,LineFeed是跳到下一行(進紙,喂紙,有個喂的動作所以是feed),CarriageReturn是回到本行開頭(Carriage是卷著紙的軸,隨著打字慢慢左移,打完一行就一下子移回最右邊),如果你看過歐美的老電影應(yīng)該能想起來這是什么。用老式打字機打完一行之后需要這么兩個動作,\r\n,所以現(xiàn)在Windows上的文本文件用\r\n做行分隔符,許多應(yīng)用層網(wǎng)絡(luò)協(xié)議(如HTTP)也用\r\n做行分隔符,而Linux和各種UNIX上的文本文件只用\n做行分隔符。在終端下顯示\t和按下Tab鍵的效果相同,用于在終端下定位表格的下一列,\v用于在終端下定位表格的下一行。\v比較少用,\t比較常用,以后將“水平制表符”簡稱為“制表符”或Tab。請讀者用printf語句試試這幾個控制字符的作用。注意"Goodbye,"末尾的空格,字符串字面值中的空格也算一個字符,也會出現(xiàn)在輸出結(jié)果中,而程序中別處的空格和Tab多一個少一個往往是無關(guān)緊要的,不會對編譯的結(jié)果產(chǎn)生任何影響,例如不縮進不會影響程序的結(jié)果,main后面多幾個空格也沒影響,但是int和main之間至少要有一個空格分隔開:intmain(void)
{
printf("Hello,world.\n");
return0;
}
不僅空格和Tab是無關(guān)緊要的,換行也是如此,我甚至可以把整個程序?qū)懗梢恍校莍nclude必須單獨占一行:#include<stdio.h>
intmain(void){printf("Hello,world.\n");return0;}
這樣也行,但肯定不是好的代碼風(fēng)格,去掉縮進已經(jīng)很影響可讀性了,寫成現(xiàn)在這個樣子可讀性更差。如果編譯器說第2行有錯誤,也很難判斷是哪個語句有錯誤。所以,好的代碼風(fēng)格要求縮進整齊,每個語句一行,適當留空行。2.常量常量(Constant)是程序中最基本的元素,有字符(Character)常量、整數(shù)(Integer)常量、浮點數(shù)(FloatingPoint)常量和枚舉常量。枚舉常量將在第3節(jié)“數(shù)據(jù)類型標志”介紹。下面看一個例子:printf("character:%c\ninteger:%d\nfloatingpoint:%f\n",'}',34,3.14);
字符常量要用單引號括起來,例如上面的'}',注意單引號只能括一個字符而不能像雙引號那樣括一串字符,字符常量也可以是一個轉(zhuǎn)義序列,例如'\n',這時雖然單引號括了兩個字符,但實際上只表示一個字符。和字符串字面值中使用轉(zhuǎn)義序列有一點區(qū)別,如果在字符常量中要表示雙引號"和問號?,既可以使用轉(zhuǎn)義序列\(zhòng)"和\?,也可以直接用字符"和?,而要表示'和\則必須使用轉(zhuǎn)義序列。[\h2]計算機中整數(shù)和小數(shù)的內(nèi)部表示方式不同(將在第14章計算機中數(shù)的表示詳細介紹),因而在C語言中是兩種不同的類型(Type),例如上例的34和3.14,小數(shù)在計算機術(shù)語中稱為浮點數(shù)。這個語句的輸出結(jié)果和Helloworld不太一樣,字符串"character:%c\ninteger:%d\nfloatingpoint:%f\n"并不是按原樣打印輸出的,而是輸出成這樣:character:}
integer:34
floatingpoint:3.14
printf中的第一個字符串稱為格式化字符串(FormatString),它規(guī)定了后面幾個常量以何種格式插入到這個字符串中,在格式化字符串中%號(PercentSign)后面加上字母c、d、f分別表示字符型、整型和浮點型的轉(zhuǎn)換說明(ConversionSpecification),轉(zhuǎn)換說明只在格式化字符串中占個位置,并不出現(xiàn)在最終的打印結(jié)果中,這種用法通常叫做占位符(Placeholder)。這也是一種字面意思與真實意思不同的情況,但是轉(zhuǎn)換說明和轉(zhuǎn)義序列又有區(qū)別:轉(zhuǎn)義序列是編譯時處理的,而轉(zhuǎn)換說明是在運行時調(diào)用printf函數(shù)處理的。源文件中的字符串字面值是"character:%c\ninteger:%d\nfloatingpoint:%f\n",\n占兩個字符,而編譯之后保存在可執(zhí)行文件中的字符串是character:%c換行integer:%d換行floatingpoint:%f換行,\n已經(jīng)被替換成一個換行符,而%c不變,然后在運行時這個字符串被傳給printf,printf再把其中的%c、%d、%f解釋成轉(zhuǎn)換說明。有時候不同類型的數(shù)據(jù)很容易弄混,例如"5"、'5'、5,如果你注意了它們的界定符就會很清楚,第一個是字符串字面值,第二個是字符,第三個是整數(shù),看了本章后面幾節(jié)你就知道為什么一定要嚴格區(qū)分它們之間的差別了。習(xí)題1、總結(jié)前面介紹的轉(zhuǎn)義序列的規(guī)律,想想在printf的格式化字符串中怎么表示一個%字符?寫個小程序試驗一下。[\h2]讀者可能會奇怪,為什么需要規(guī)定一個轉(zhuǎn)義序列\(zhòng)?呢?因為C語言規(guī)定了一些三連符(Trigraph),在某些特殊的終端上缺少某些字符,需要用Trigraph輸入,例如用??=表示#字符。Trigraph極不常用,介紹這個只是為了讓讀者理解C語言規(guī)定轉(zhuǎn)義序列的作用,即特殊字符轉(zhuǎn)普通字符,普通字符轉(zhuǎn)特殊字符,?也是一種特殊字符。極不常用的C語法在本書中通常不會介紹。3.變量變量(Variable)是編程語言最重要的概念之一,變量是計算機存儲器中的一塊命名的空間,可以在里面存儲一個值(Value),存儲的值是可以隨時變的,比如這次存?zhèn)€字符'a'下次存?zhèn)€字符'b',正因為變量的值可以隨時變所以才叫變量。常量有不同的類型,因此變量也有不同的類型,變量的類型也決定了它所占的存儲空間的大小。例如以下四個語句定義了四個變量fred、bob、jimmy和tom,它們的類型分別是字符型、整型、浮點型:charfred;
intbob;
floatjimmy;
doubletom;
聲明和定義C語言中的聲明(Declaration)有變量聲明、函數(shù)聲明和類型聲明三種。如果一個變量或函數(shù)的聲明要求編譯器為它分配存儲空間,那么也可以稱為定義(Definition),因此定義是聲明的一種。在接下來幾章的示例代碼中變量聲明都是要分配存儲空間的,因而都是定義,等學(xué)到第2節(jié)“定義和聲明”我們會看到哪些變量聲明不分配存儲空間因而不是定義。在下一章我們會看到函數(shù)的定義和聲明也是這樣區(qū)分的,分配存儲空間的函數(shù)聲明可以稱為函數(shù)定義。從第7章結(jié)構(gòu)體開始我們會看到類型聲明,聲明一個類型是不分配存儲空間的,但似乎叫“類型定義”聽起來也不錯,所以在本書中“類型定義”和“類型聲明”表示相同的含義。聲明和語句類似,也是以;號結(jié)尾的,但是在語法上聲明和語句是有區(qū)別的,語句只能出現(xiàn)在{}括號中,而聲明既可以出現(xiàn)在{}中也可以出現(xiàn)在所有{}之外。浮點型有三種,float是單精度浮點型,double是雙精度浮點型,longdouble是精度更高的浮點型。它們之間的區(qū)別和轉(zhuǎn)換規(guī)則將在第15章數(shù)據(jù)類型詳解詳細介紹,在隨后的幾章中我們只使用double類型,上一節(jié)介紹的常量3.14應(yīng)該看作double類型的常量,printf的%f也應(yīng)該看作double型的轉(zhuǎn)換說明。給變量起名不能太隨意,上面四個變量的名字就不夠好,我們猜不出這些變量是用來存什么的。而像下面這樣起名就很好:charfirstletter;
charlastletter;
inthour,minute;
我們可以猜得到這些變量是用來存什么的,前兩個變量的取值范圍應(yīng)該是'A'~'Z'或'a'~'z',變量hour的取值范圍應(yīng)該是0~23,變量minute的取值范圍應(yīng)該是0~59,所以應(yīng)該給變量起有意義的名字。從這個例子中我們也看到兩個相同類型的變量(hour和minute)可以一起聲明。給變量起名有一定的限制,C語言規(guī)定必須以字母或下劃線(Underscore)開頭,后面可以跟若干個字母、數(shù)字、下劃線,但不能有其它字符。例如這些是合法的變量名:Abc、`_abc、_123。但這些是不合法的變量名:3abc、ab$`。其實這個規(guī)則不僅適用于變量名,也適用于所有可以由程序員起名的語法元素,例如以后要講的函數(shù)名、宏定義、結(jié)構(gòu)體成員名等,在C語言中這些統(tǒng)稱為標識符(Identifier)。另外要注意,表示類型的char、int、float、double等雖然符合上述規(guī)則,但也不能用作標識符。在C語言中有些單詞有特殊意義,不允許用作標識符,這些單詞稱為關(guān)鍵字(Keyword)或保留字(ReservedWord)。通常用于編程的文本編輯器都會高亮顯示(Highlight)這些關(guān)鍵字,所以只要小心一點通常不會誤用作標識符。C99規(guī)定的關(guān)鍵字有:autobreakcasecharconstcontinuedefaultdodoubleelseenumexternfloatforgotoifinlineintlongregisterrestrictreturnshortsignedsizeofstaticstructswitchtypedefunionunsignedvoidvolatilewhile_Bool_Complex_Imaginary還有一點要注意,一般來說應(yīng)避免使用以下劃線開頭的標識符,以下劃線開頭的標識符只要不和C語言關(guān)鍵字沖突的都是合法的,但是往往被編譯器用作一些功能擴展,C標準庫也定義了很多以下劃線開頭的標識符,所以除非你對編譯器和C標準庫特別清楚,一般應(yīng)避免使用這種標識符,以免造成命名沖突。請記?。豪斫庖粋€概念不是把定義背下來就行了,一定要理解它的外延和內(nèi)涵,也就是什么情況屬于這個概念,什么情況不屬于這個概念,什么情況雖然屬于這個概念但一般推薦的做法(BestPractice)是要盡量避免這種情況,這才算是真正理解了。4.賦值定義了變量之后,我們要把值存到它們所表示的存儲空間里,可以用賦值(Assignment)語句實現(xiàn):charfirstletter;
inthour,minute;
firstletter='a';/*givefirstletterthevalue'a'*/
hour=11;/*assignthevalue11tohour*/
minute=59;/*setminuteto59*/
注意變量一定要先聲明后使用,編譯器必須先看到變量聲明,才知道firstletter、hour和minute是變量名,各自代表一塊存儲空間。另外,變量聲明中的類型表明這個變量代表多大的一塊存儲空間,這樣編譯器才知道如何讀寫這塊存儲空間。還要注意,這里的等號不表示數(shù)學(xué)里的相等關(guān)系,和1+1=2的等號是不同的,這里的等號表示賦值。在數(shù)學(xué)上不會有i=i+1這種等式成立,而在C語言中表示把變量i的存儲空間中的值取出來,再加上1,得到的結(jié)果再存回i的存儲空間中。再比如,在數(shù)學(xué)上a=7和7=a是一樣的,而在C語言中后者是不合法的??偨Y(jié)一下:定義一個變量,就是分配一塊存儲空間并給它命名;給一個變量賦值,就是把一個值保存到這塊存儲空間中。變量的定義和賦值也可以一步完成,這稱為變量的初始化(Initialization),例如要達到上面代碼的效果也可以這樣寫:charfirstletter='a';
inthour=11,minute=59;
在初始化語句中,等號右邊的值叫做Initializer,例如上面的'a'、11和59。注意,初始化是一種特殊的聲明,而不是一種賦值語句。就目前來看,先定義一個變量再給它賦值和定義這個變量的同時給它初始化所達到的效果是一樣的,C語言的很多語法規(guī)則既適用于賦值也適用于初始化,但在以后的學(xué)習(xí)中你也會了解到它們之間的不同,請在學(xué)習(xí)過程中注意總結(jié)賦值和初始化的相同和不同之處。如果在紙上“跑”一個程序(每個初學(xué)編程的人都要練這項基本功),可以用一個框表示變量的存儲空間,在框的外邊標上變量名,在框里記上它的值,如下圖所示。圖2.1.在紙上表示變量你可以用不同形狀的框表示不同類型的變量,這樣可以提醒你給變量賦的值必須符合它的類型。如果所賦的值和變量的類型不符會導(dǎo)致編譯器報警告或報錯(這是一種語義錯誤),例如:inthour,minute;
hour="Hello.";/*WRONG!*/
minute="59";/*WRONG!!*/
注意第3個語句,把"59"賦給minute看起來像是對的,但是類型不對,字符串不能賦給整型變量。既然可以為變量的存儲空間賦值,就應(yīng)該可以把值取出來用,現(xiàn)在我們?nèi)〕鲞@些變量的值用printf打?。簆rintf("Currenttimeis%d:%d",hour,minute);
變量名用在等號左邊表示賦值,而用在printf中表示把它的存儲空間中的值取出來替換在那里。不同類型的變量所占的存儲空間大小是不同的,數(shù)據(jù)表示方式也不同,變量的最小存儲單位是字節(jié)(Byte),在C語言中char型變量占一個字節(jié),其它類型的變量占多少字節(jié)在不同平臺上有不同的規(guī)定,將在第15章數(shù)據(jù)類型詳解詳細討論。5.表達式常量和變量都可以參與加減乘除運算,例如1+1、hour-1、hour*60+minute、minute/60等。這里的+-*/稱為運算符(Operator),而參與運算的常量和變量稱為操作數(shù)(Operand),上面四個由運算符和操作數(shù)所組成的算式稱為表達式(Expression)。和數(shù)學(xué)上規(guī)定的一樣,hour*60+minute這個表達式應(yīng)該先算乘再算加,也就是說運算符是有優(yōu)先級(Precedence)的,和/是同一優(yōu)先級,+和-是同一優(yōu)先級,和/的優(yōu)先級高于+和-。對于同一優(yōu)先級的運算從左到右計算,如果不希望按默認的優(yōu)先級計算則要加()括號(Parenthesis)。例如(3+4)*5/6應(yīng)先算3+4,再算*5,再算/6。前面講過打印語句和賦值語句,現(xiàn)在我們定義:在任意表達式后面加個;號也是一種語句,稱為表達式語句。例如:hour*60+minute;
這是個合法的語句,但這個語句在程序中起不到任何作用,把hour的值和minute的值取出來加乘,得到的計算結(jié)果卻沒有保存,白算了一通。再比如:inttotal_minute;
total_minute=hour*60+minute;
這個語句就很有意義,把計算結(jié)果保存在另一個變量total_minute里。事實上等號也是一種運算符,稱為賦值運算符,賦值語句就是一種表達式語句,等號的優(yōu)先級比+和都低,所以先算出等號右邊的結(jié)果然后才做賦值操作,整個表達式`total_minute=hour60+minute`加個;號構(gòu)成一個語句。任何表達式都有值和類型兩個基本屬性。hour*60+minute的值是由三個int型的操作數(shù)計算出來的,所以這個表達式的類型也是int型。同理,表達式total_minute=hour*60+minute的類型也是int,它的值是多少呢?C語言規(guī)定等號運算符的計算結(jié)果就是等號左邊被賦予的那個值,所以這個表達式的值和hour*60+minute的值相同,也和total_minute的值相同。等號運算符還有一個和+-*/不同的特性,如果一個表達式中出現(xiàn)多個等號,不是從左到右計算而是從右到左計算,例如:inttotal_minute,total;
total=total_minute=hour*60+minute;
計算順序是先算hour*60+minute得到一個結(jié)果,然后算右邊的等號,就是把hour*60+minute的結(jié)果賦給變量total_minute,這個結(jié)果同時也是整個表達式total_minute=hour*60+minute的值,再算左邊的等號,即把這個值再賦給變量total。同樣優(yōu)先級的運算符是從左到右計算還是從右到左計算稱為運算符的結(jié)合性(Associativity)。+-*/是左結(jié)合的,等號是右結(jié)合的?,F(xiàn)在我們總結(jié)一下到目前為止學(xué)過的語法規(guī)則:表達式→標識符表達式→常量表達式→字符串字面值表達式→(表達式)表達式→表達式+表達式表達式→表達式-表達式表達式→表達式*表達式表達式→表達式/表達式表達式→表達式=表達式語句→表達式;語句→printf(表達式,表達式,表達式,...);變量聲明→類型標識符=Initializer,標識符=Initializer,...;(=Initializer的部分可以不寫)注意,本書所列的語法規(guī)則都是簡化過的,是不準確的,目的是為了便于初學(xué)者理解,比如上面所列的語法規(guī)則并沒有描述運算符的優(yōu)先級和結(jié)合性。完整的C語法規(guī)則請參考[C99]的AnnexA。表達式可以是單個的常量或變量,也可以是根據(jù)以上規(guī)則組合而成的更復(fù)雜的表達式。以前我們用printf打印常量或變量的值,現(xiàn)在可以用printf打印更復(fù)雜的表達式的值,例如:printf("%d:%dis%dminutesafter00:00\n",hour,minute,hour*60+minute);
編譯器在翻譯這條語句時,首先根據(jù)上述語法規(guī)則把這個語句解析成下圖所示的語法樹,然后再根據(jù)語法樹生成相應(yīng)的指令。語法樹的末端的是一個個Token,每一步展開利用一條語法規(guī)則。圖2.2.語法樹根據(jù)這些語法規(guī)則進一步組合可以寫出更復(fù)雜的語句,比如在一條語句中完成計算、賦值和打印功能:printf("%d:%dis%dminutesafter00:00\n",hour,minute,total_minute=hour*60+minute);
理解組合(Composition)規(guī)則是理解語法規(guī)則的關(guān)鍵所在,正因為可以根據(jù)語法規(guī)則任意組合,我們才可以用簡單的常量、變量、表達式、語句搭建出任意復(fù)雜的程序,以后我們學(xué)習(xí)新的語法規(guī)則時會進一步體會到這一點。從上面的例子可以看出,表達式不宜過度組合,否則會給閱讀和調(diào)試帶來困難。根據(jù)語法規(guī)則組合出來的表達式在語義上并不總是正確的,例如:minute+1=hour;
等號左邊的表達式要求表示一個存儲位置而不是一個值,這是等號運算符和+-*/運算符的又一個顯著不同。有的表達式既可以表示一個存儲位置也可以表示一個值,而有的表達式只能表示值,不能表示存儲位置,例如minute+1這個表達式就不能表示存儲位置,放在等號左邊是語義錯誤。表達式所表示的存儲位置稱為左值(lvalue)(允許放在等號左邊),而以前我們所說的表達式的值也稱為右值(rvalue)(只能放在等號右邊)。上面的話換一種說法就是:有的表達式既可以做左值也可以做右值,而有的表達式只能做右值。目前我們學(xué)過的表達式中只有變量可以做左值,可以做左值的表達式還有幾種,以后會講到。我們看一個有意思的例子,如果定義三個變量inta,b,c;,表達式a=b=c是合法的,先求b=c的值,再把這個值賦給a,而表達式(a=b)=c是不合法的,先求(a=b)的值沒問題,但(a=b)這個表達式不能再做左值了,因此放在=c的等號左邊是錯的。關(guān)于整數(shù)除法運算有一點特殊之處:hour=11;
minute=59;
printf("%dand%dhours\n",hour,minute/60);
執(zhí)行結(jié)果是11and0hours,也就是說59/60得0,這是因為兩個int型操作數(shù)相除的表達式仍為int型,只能保存計算結(jié)果的整數(shù)部分,即使小數(shù)部分是0.98也要舍去。向下取整的運算稱為Floor,用數(shù)學(xué)符號??表示;向上取整的運算稱為Ceiling,用數(shù)學(xué)符號??表示。例如:?59/60?=0?59/60?=1?-59/60?=-1?-59/60?=0在C語言中整數(shù)除法取的既不是Floor也不是Ceiling,無論操作數(shù)是正是負總是把小數(shù)部分截掉,在數(shù)軸上向零的方向取整(TruncatetowardZero),或者說當操作數(shù)為正的時候相當于Floor,當操作符為負的時候相當于Ceiling?;氐较惹暗睦?,要得到更精確的結(jié)果可以這樣:printf("%dhoursand%dpercentofanhour\n",hour,minute*100/60);
printf("%dand%fhours\n",hour,minute/60.0);
在第二個printf中,表達式是minute/60.0,60.0是double型的,/運算符要求左右兩邊的操作數(shù)類型一致,而現(xiàn)在并不一致。C語言規(guī)定了一套隱式類型轉(zhuǎn)換規(guī)則,在這里編譯器自動把左邊的minute也轉(zhuǎn)成double型來計算,整個表達式的值也是double型的,在格式化字符串中應(yīng)該用%f轉(zhuǎn)換說明與之對應(yīng)。本來編程語言作為一種形式語言要求有簡單而嚴格的規(guī)則,自動類型轉(zhuǎn)換規(guī)則不僅很復(fù)雜,而且使C語言的形式看起來也不那么嚴格了,C語言這么設(shè)計是為了書寫程序簡便而做的折衷,有些事情編譯器可以自動做好,程序員就不必每次都寫一堆繁瑣的轉(zhuǎn)換代碼。然而C語言的類型轉(zhuǎn)換規(guī)則非常難掌握,本書的前幾章會盡量避免類型轉(zhuǎn)換,到第3節(jié)“類型轉(zhuǎn)換”再集中解決這個問題。習(xí)題1、假設(shè)變量x和n是兩個正整數(shù),我們知道x/n這個表達式的結(jié)果要取Floor,例如x是17,n是4,則結(jié)果是4。如果希望結(jié)果取Ceiling應(yīng)該怎么寫表達式呢?例如x是17,n是4,則結(jié)果是5;x是16,n是4,則結(jié)果是4。6.字符類型與字符編碼字符常量或字符型變量也可以當作整數(shù)參與運算,例如:printf("%c\n",'a'+1);
執(zhí)行結(jié)果是b。我們知道,符號在計算機內(nèi)部也用數(shù)字表示,每個字符在計算機內(nèi)部用一個整數(shù)表示,稱為字符編碼(CharacterEncoding),目前最常用的是ASCII碼(AmericanStandardCodeforInformationInterchange,美國信息交換標準碼),詳見圖A.1“ASCII碼表”。表中每一欄的最后一列是字符,前三列分別是用十進制(Dec)、十六進制(Hx)和八進制(Oct)表示的字符編碼,各種進制之間的換算將在第2節(jié)“不同進制之間的換算”介紹。從十進制那一列可以看出ASCII碼的取值范圍是0~127。表中的很多字符是不可見字符(Non-printableCharacter)或空白字符(Whitespace)[\h3],不能像字母a這樣把字符本身填在表中,而是用一個名字來描述該字符,例如CR(carriagereturn)、LF(NLlinefeed,newline)、DEL等等。作為練習(xí),請讀者查一查表2.1“C標準規(guī)定的轉(zhuǎn)義字符”中的字符在ASCII碼表中的什么位置。回到剛才的例子,在ASCII碼中字符a是97,字符b是98。計算'a'+1這個表達式,應(yīng)該按ASCII碼把'a'當作整數(shù)值97,然后加1,得到98,然后printf把98這個整數(shù)值當作ASCII碼來解釋,打印出相應(yīng)的字符b。之前我們說“整型”是指int型,而現(xiàn)在我們知道char型本質(zhì)上就是整數(shù),只不過取值范圍比int型小,所以以后我們把char型和int型統(tǒng)稱為整數(shù)類型(IntegerType)或簡稱整型,以后我們還要學(xué)習(xí)幾種類型也屬于整型,將在第1節(jié)“整型”詳細介紹。字符'a'~'z'、'A'~'Z'、'0'~'9'的ASCII碼都是連續(xù)的,因此表達式'a'+25和'z'的值相等,'0'+9和'9'的值也相等。注意'0'~'9'的ASCII碼是十六進制的30~39,和整數(shù)值0~9是不相等的。字符也可以用ASCII碼轉(zhuǎn)義序列表示,這種轉(zhuǎn)義序列由\加上1~3個八進制數(shù)字組成,或者由\x或大寫\X加上1~2個十六進制數(shù)字組成,可以用在字符常量或字符串字面值中。例如'\0'表示NUL字符(NullCharacter),'\11'或'\x9'表示Tab字符,"\11"或"\x9"表示由Tab字符組成的字符串。注意'0'的ASCII碼是48,而'\0'的ASCII碼是0,兩者是不同的。[\h3]空白字符在不同的上下文中有不同的含義,在C語言中空白字符定義為空格、水平Tab、垂直Tab、換行和分頁符,本書在使用“空白字符”這個詞時會明確說明在當前上下文中空白字符指的是哪些字符。第3章簡單函數(shù)目錄1.數(shù)學(xué)函數(shù)2.自定義函數(shù)3.形參和實參4.全局變量、局部變量和作用域1.數(shù)學(xué)函數(shù)在數(shù)學(xué)中我們用過sin和ln這樣的函數(shù),例如sin(π/2)=1,ln1=0等等,在C語言中也可以使用這些函數(shù)(ln函數(shù)在C標準庫中叫做log):例3.1.在C語言中使用數(shù)學(xué)函數(shù)#include<math.h>
#include<stdio.h>
intmain(voi
溫馨提示
- 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)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 中國瀝清軟化點測試儀項目投資可行性研究報告
- 調(diào)取證據(jù)申請書刑事
- 中國小型風(fēng)力發(fā)電工業(yè)未來趨勢預(yù)測分析及投資規(guī)劃研究建議報告
- 2025-2030年臺燈行業(yè)市場調(diào)研及前景趨勢預(yù)測報告
- 2025年剎車背板項目可行性研究報告
- 2025年度文化旅游經(jīng)營權(quán)質(zhì)押租賃協(xié)議
- 2025年度企業(yè)合同主體變更與品牌戰(zhàn)略調(diào)整補充協(xié)議范本
- 2025年度網(wǎng)絡(luò)安全項目勞務(wù)用工協(xié)議
- 監(jiān)理質(zhì)量評估報告范文共
- 貸款個人申請書
- 張五常子女和婚姻合約中的產(chǎn)權(quán)執(zhí)行問題
- 2024年廣東深圳高三二模英語讀后續(xù)寫試題講評課件
- 校園安全派出所
- 餐廳值班管理培訓(xùn)
- (正式版)JBT 11517-2024 刮板取料機
- XXXX無線維護崗位認證教材故障處理思路及案例分析
- 2024年浙江省自然資源集團有限公司招聘筆試參考題庫附帶答案詳解
- 酒店春節(jié)營銷方案
- 營銷管理方案中的定價策略與盈利模式
- 2024年西寧城市職業(yè)技術(shù)學(xué)院高職單招(英語/數(shù)學(xué)/語文)筆試歷年參考題庫含答案解析
- 2024年臨沂市高三一模(學(xué)業(yè)水平等級考試模擬試題)物理試卷
評論
0/150
提交評論