




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