- Keil Cx51 V7.0單片機高級語言編程與μVision2應用實踐
- 徐愛鈞 彭秀華編著
- 728字
- 2018-12-29 19:18:20
第2章 Cx51程序設計基礎
Keil Cx51是一種專為8051單片機設計的高級語言C編譯器,支持符合ANSI標準的C語言進行程序設計,同時針對8051單片機自身特點作了一些特殊擴展。為了幫助以前慣于使用匯編語言編程的單片機用戶盡快掌握Cx51編程技術,本章對C語言的一些基本知識結合Cx51特點進行闡述。
2.1 標識符與關鍵字
C語言的標識符是用來標識源程序中某個對象名字的。這些對象可以是函數、變量、常量、數組、數據類型、存儲方式、語句等。一個標識符由字母、數字和下劃線等組成,第一個字符必須是字母或下劃線。C語言是對大小寫字母敏感的,如“max”與“MAX”是兩個完全不同的標識符。程序中對于標識符的命名應當簡潔明了,含義清晰,便于閱讀理解,如用標識符“max”表示最大值,用“TIMER0”表示定時器0等。
關鍵字是一類具有固定名稱和特定含義的特殊標識符,有時又稱為保留字。在編寫C語言源程序時一般不允許將關鍵字另作他用,換句話說就是對于標識符的命名不要與關鍵字相同。與其他計算機語言相比,C語言的關鍵字是比較少的,ANSI C標準一共規定了32個關鍵字,表2-1按用途列出了ANSI C標準的關鍵字。
表2-1 ANSI C標準的關鍵字

Keil Cx51編譯器除了支持ANSI C標準的關鍵字以外,還根據8051單片機自身特點擴展了如表2-2所示的關鍵字。
表2-2 Keil Cx51編譯器的擴展關鍵字

2.2 Cx51程序設計的基本語法
雖然C語言對語法的限制不太嚴格,用戶在編寫程序時有較大的自由,但它畢竟還是一種程序設計語言,與其他計算機語言一樣,采用C語言進行程序設計時,仍需要遵從一定的語法規則。
2.2.1 數據類型
任何程序設計都離不開對于數據的處理,一個程序如果沒有數據,它就無法工作。數據在計算機內存中的存放情況由數據結構決定。C語言的數據結構是以數據類型出現的,數據類型可分為基本數據類型和復雜數據類型,復雜數據類型由基本數據類型構造而成。C語言中的基本數據類型有char,int,short,long,float和double。對于Cx51編譯器來說,short類型與int類型相同,double類型與float類型相同。
1.char:字符型
有signed char(帶符號數)和unsigned char(無符號數)之分,默認值為signed char。它們的長度均為一個字節,用于存放一個單字節的數據。對于singed char類型數據,其字節中的最高位表示該數據的符號,“0”表示正數,“1”表示負數。負數用補碼表示,數值的表示范圍是-128~+127;對于unsigned char類型數據,其字節中的所有位均用來表示數據的數值,數值的表示范圍是0~255。
2.int:整型
有signed int和unsigned int之分,默認值為signed int。它們的長度均為兩個字節,用于存放一個雙字節的數據。signed int是有符號整型數,字節中的最高位表示數據的符號,“0”表示正數,“1”表示負數。所能表示的數值范圍是-32768~+32767。unsigned int是無符號整型數,所能表示的數值范圍是0~65535。
3.long:長整型
有signed long和unsigned long之分,默認值為signed long。它們的長度均為四個字節。signed long是有符號的長整型數據,字節中的最高位表示數據的符號,“0”表示正數,“1”表示負數,數值的表示范圍是-2147 483648~+2147 483647。unsigned long是無符號長整型數據,數值的表示范圍是0~4294 967295。
4.float:浮點型
它是符合IEEE-754標準的單精度浮點型數據,在十進制數中具有7位有效數字。float類型數據占用四個字節(32位二進制數),在內存中的存放格式如下:

其中,S為符號位,“0”表示正,“1”表示負。E為階碼,占用8位二進制數,存放在兩個字節中。注意,階碼E值是以2為底的指數再加上偏移量127,這樣處理的目的是為了避免出現負的階碼值,而指數是可正可負的。階碼E的正常取值范圍是1~254,從而實際指數的取值范圍為-126~+127。M為尾數的小數部分,用23位二進制數表示,存放在三個字節中。尾數的整數部分永遠為1,因此不予保存,但它是隱含存在的。小數點位于隱含的整數位“1”的后面。一個浮點數的數值范圍是(-1)S×2E-127×(1.M)。
例如,浮點數-12.5=0xC1480000,在內存中的存放格式為:

需要指出的是,對于浮點型數據除了有正常數值之外,還可能出現非正常數值。根據IEEE標準,當浮點型數據取以下數值(16進制數)時即為非正常值:
0xFFFFFFFF非數(NaN)
0x7F800000正溢出(+INF)
0xFF800000負溢出(-INF)
另外,由于8051單片機不包括捕獲浮點運算錯誤的中斷向量,因此必須由用戶自己根據可能出現的錯誤條件用軟件來進行適當的處理。
除了以上四種基本數據類型之外,還有以下一些數據類型。
5.*:指針型
指針型數據不同于以上四種基本數據類型,它本身是一個變量,但在這個變量中存放的不是普通數據而是指向另一個數據的地址。指針變量也要占據一定的內存單元,在C51中指針變量的長度一般為1~3個字節。指針變量也具有類型,其表示方法是在指針符號“*”的前面冠以數據類型符號,如char * point1表示point1是一個字符型的指針變量;float* point2表示point2是一個浮點型的指針變量。指針變量的類型表示該指針所指向地址中數據的類型。使用指針型變量可以方便地對8051單片機各部分物理地址直接進行操作。
6.bit:位類型
這是Keil Cx51編譯器的一種擴充數據類型,利用它可定義一個位變量,但不能定義位指針,也不能定義位數組。
7.sfr:特殊功能寄存器
這也是Keil Cx51編譯器的一種擴充數據類型,利用它可以定義8051單片機的所有內部8位特殊功能寄存器。sfr型數據占用一個內存單元,其取值范圍是0~255。
8.sfr16:16位特殊功能寄存器
它占用兩個內存單元,取值范圍是0~65535,利用它可以定義8051單片機內部16位特殊功能寄存器。
9.sbit:可尋址位
這也是Keil Cx51編譯器的一種擴充數據類型,利用它可以定義8051單片機內部RAM中的可尋址位或特殊功能寄存器中的可尋址位。
例如,采用如下語句:
sfr P0=80H; sbit FLAG1=P0^1;
可以將8051單片機P0口地址定義為80H,將P0.1位定義為FLAG1。
表2-3列出了Keil Cx51編譯器能夠識別的數據類型。
表2-3 Keil Cx51編譯器能夠識別的數據類型

在C語言程序的表達式或變量賦值運算中,有時會出現運算對象的數據不一致的情況,C語言允許任何標準數據類型之間的隱式轉換。隱式轉換按以下優先級別自動進行:
bit→char→int→long→float
signed→unsigned
其中箭頭方向僅表示數據類型級別的高低,轉換時由低向高進行,而不是數據轉換時的順序。例如,將一個bit(位類型)變量賦給一個int(整型變量)時,不需要先將bit型變量轉換成char型之后再轉換成int型,而是將bit型變量直接轉換成int型并完成賦值運算。一般來說,如果有幾個不同類型的數據同時參加運算,先將低級別類型的數據轉換成高級別類型,再作運算處理,并且運算結果為高級別類型數據。C語言除了能對數據類型作自動的隱式轉換之外,還可以采用強制類型轉換符“()”對數據類型作顯式轉換,強制類型轉換符“()”的應用將在2.2.5節中介紹。
Keil Cx51編譯器除了能支持以上這些基本數據之外,還能支持復雜的構造類型數據,如結構類型、聯合類型等。這些復雜的數據類型將在本書第5章詳細討論。
2.2.2 常量
常量又稱為標量,它的值在程序執行過程中不能改變。常量的數據類型有整型、浮點型、字符型和字符串型等。
1.整型常量
整型常量就是整型常數,可表示為以下幾種形式。
十進制整數:如1234,-5678,0等。
十六進制整數:ANSI C標準規定十六進制數據以0x開頭,數字為0~9,a~f。如0x123表示十六進制數,相當于十進制數291。-0x1a表示十六進制數,相當于十進制數-26。
長整數:在數字后面加一個字母L就構成了長整數,如2048L、0123L、0xff00L等。
2.浮點型常量
浮點型常量有十進制數表示形式和指數表示形式。十進制數表示形式又稱定點表示形式,由數字和小數點組成。如0.3141、.3141、314.1、3141.及0.0都是十進制數表示形式的浮點型常量。在這種表示形式中,如果整數或小數部分為0可以省略不寫,但必須有小數點。指數表示形式為:
[±]數字[.數字]e[±]數字
其中,[ ]中的內容為可選項,根據具體情況可有可無,但其余部分必須有。如123e4、5e6、-7.0e-8等都是合法的指數形式浮點型常量;而e9、5e4.3和e都是不合法的表示形式。
3.字符型常量
字符型常量是單引號內的字符,如'a','b'等。對于不可顯示的控制字符,可以在該字符前面加一個反斜杠“\”組成轉義字符。利用轉義字符可以完成一些特殊功能和輸出時的格式控制。常用轉義字符如表2-4所示。
表2-4 常用轉義字符表

4.字符串型常量
字符串型常量由雙引號""內的字符組成,如"ABCD"、"$1234"等都是字符串常量。當雙引號內的字符個數為0時,稱為空串常量。需要注意的是,字符串常量首尾的雙引號是界限符,當需要表示雙引號字符串時,可用雙引號轉義字符“\"”來表示。另外,C語言將字符串常量作為一個字符類型數組來處理,在存儲字符串常量時要在字符串的尾部加一個轉義字符\0作為該字符串常量的結束符。因此不要將字符常量與字符串常量混淆,如字符常量'a'與字符串常量"a"是不一樣的。
2.2.3 變量及其存儲模式
變量是一種在程序執行過程中其值能不斷變化的量。使用一個變量之前,必須進行定義,用一個標識符作為變量名并指出它的數據類型和存儲模式,以便編譯系統為它分配相應的存儲單元。在Cx51中對變量進行定義的格式如下:
[存儲種類]數據類型 [存儲器類型]變量名表;
其中,“存儲種類”和“存儲器類型”是可選項。變量的存儲種類有四種:自動(auto)、外部(extern)、靜態(static)和寄存器(register)。定義一個變量時如果省略存儲種類選項,則該變量將為自動(auto)變量。定義一個變量時除了需要說明其數據類型之外,Keil Cx51編譯器還允許說明變量的存儲器類型。Keil Cx51編譯器完全支持8051系列單片機的硬件結構和存儲器組織,對于每個變量可以準確地賦予其存儲器類型,使之能夠在單片機系統內準確地定位。表2-5列出了Keil Cx51編譯器所能識別的存儲器類型。
表2-5 Keil Cx51編譯器所能識別的存儲器類型

定義變量時如果省略“存儲器類型”選項,則按編譯時使用的存儲器模式SMALL、COMPACT或LARGE來規定默認存儲器類型,確定變量的存儲器空間,函數中不能采用寄存器傳遞的參數變量和過程變量也保存在默認的存儲器空間。Keil Cx51編譯器的三種存儲器模式(默認的存儲器類型)對變量的影響如下。
1.SMALL
變量被定義在8051單片機的片內數據存儲器中,對這種變量的訪問速度最快。另外,所有的對象,包括堆棧,都必須位于片內數據存儲器中,而堆棧的長度是很重要的,實際棧長取決于不同函數的嵌套深度。
2.COMPACT
變量被定義在分頁尋址的片外數據存儲器中,每一頁片外數據存儲器的長度為256字節。這時對變量的訪問是通過寄存器間接尋址(MOVX @Ri)進行的,堆棧位于8051單片機片內數據存儲器中。采用這種編譯模式時,變量的高8位地址由P2口確定,低8位地址由R0或R1的內容決定。采用這種模式的同時,必須適當改變啟動配置文件STARTUP.A51中的參數:PDATASTART和PDATALEN;在用BL51進行連接時還必須采用連接控制命令“PDATA”對P2口地址進行定位,這樣才能確保P2口為所需要的高8位地址。
3.LARGE
變量被定義在片外數據存儲器中(最大可達64KB),使用數據指針DPTR來間接訪問變量(MOVX @DPTR)。這種訪問數據的方法效率是不高的,尤其是對于2個以上字節的變量,用這種方法相當影響程序的代碼長度。
需要特別指出的是,變量的存儲種類與存儲器類型是完全無關的。例如:
static unsigned char data x; /* 在片內數據存儲器中定義一個靜態無符 號字符型變量x */ int y; /* 定義一個自動整型變量y,它的存儲器類型由 編譯模式確定 */
8051系列單片機具有多種內部寄存器,其中一些是特殊功能寄存器,如定時器方式控制寄存器TMOD、中斷允許控制寄存器IE等。為了能夠直接訪問這些特殊功能寄存器,Keil Cx51編譯器擴充了關鍵字sfr和sfr16,利用這種擴充關鍵字可以在C語言源程序中直接對8051單片機的特殊功能寄存器進行定義。定義方法如下:
sfr特殊功能寄存器名=地址常數;
例如:
sfr P0 = 0x80; /* 定義I/O口P0,其地址為0x80 */
這里需要注意的是,在關鍵字sfr后面必須跟一個標識符作為寄存器名,名字可任意選取,但應符合一般習慣。等號后面必須是常數,不允許有帶運算符的表達式,而且該常數必須在特殊功能寄存器的地址范圍之內(0x80~0xFF)。在新一代的8051單片機中,特殊功能寄存器經常組合成16位來使用。采用關鍵字sfr16可以定義這種16位的特殊功能寄存器。例如,對于8052單片機的定時器T2,可采用如下的方法來定義:
sfr16 T2 = 0xCC; /* 定義TIMER2,其地址為T2L=0xCC,T2H=0xCD */
這里T2為特殊功能寄存器名,等號后面是它的低字節地址,其高字節地址必須在物理上直接位于低字節之后。這種定義方法適用于所有新一代的8051單片機中新增加的特殊功能寄存器。
在8051單片機應用系統中經常需要訪問特殊功能寄存器中的某些位,Keil Cx51編譯器為此提供了一個擴充關鍵字sbit,利用它定義可位尋址對象。定義方法有如下三種。
(1)sbit位變量名=位地址
這種方法將位的絕對地址賦給位變量,位地址必須位于0x80~0xFF之間。例如:
sbit OV = 0xD2; sbit CY = 0xD7;
(2)sbit位變量名=特殊功能寄存器名^位位置
當可尋址位位于特殊功能寄存器中時可采用這種方法,“位位置”是一個0~7之間的常數。例如:
sfr PSW = 0xD0; sbit OV = PSW^2; sbit CY = PSW^7;
(3)sbit位變量名=字節地址^位位置
這種方法以一個常數(字節地址)作為基地址,該常數必須在0x80H~0xFF之間。“位位置”是一個0~7之間的常數。例如:
sbit OV = 0xD0^2; sbit CY = 0xD0^7;
當位對象位于8051單片機片內存儲器中可位尋址區時稱之為“可位尋址對象”。Keil Cx51編譯器提供了一個bdata存儲器類型,允許將具有bdata類型的對象放入8051單片機片內可位尋址區。例如:
int bdata ibase; /*在位尋址區定義一個整型變量ibase */ char bdata bary[4]; /*在位尋址區定義一個數組array[4] */
使用關鍵字sbit可以獨立訪問可位尋址對象中的某一位。例如:
sbit mybit0=ibase^0; sbit mybit15=ibase^15; sbit Ary07=bary[0]^7; sbit Ary37=bary[3]^7;
采用這種方法定義可位尋址變量時要求基址對象的存儲器類型為bdata,操作符“^”后面“位位置”的最大值取決于指定的基地址類型,對于char類型來說是0~7;對于int類型來說是0~15;對于long類型來說是0~31。
需要注意的是,sbit是一個獨立的關鍵字,不要將它與關鍵字bit相混淆。關鍵字bit是Keil Cx51編譯器的一種擴充數據類型,用來定義一個普通位變量,它的值是二進制數的0或1。一個函數中可以包含bit類型的參數,函數的返回值也可為bit類型。例如:
static bit direction_bit /* 定義一個靜態位變量direction_bit */ extern bit lock_prt_port /* 定義一個外部位變量lock_prt_port */ bit bfunc(bit b0,bit b1); /* 定義一個返回位型值的函數bfunc,函 {... 數中包含有兩個位型參數b0 和b1 */ return(b1) /* 返回一個位型值b1 */ }
如果在函數中禁止使用中斷(#pragma disable)或者函數中包含有明確的寄存器組切換(using n),則該函數不能返回位型值,否則在編譯時會產生編譯錯誤。另外,不能定義位指針,也不能定義位數組。
上面介紹了變量及其定義方法,這在編寫C語言程序時是十分重要的。從變量的作用范圍來看,還有全局變量和局部變量之分。全局變量是指在程序開始處或各個功能函數的外面定義的變量,在程序開始處定義的全局變量對于整個程序都有效,可供程序中所有函數共同使用;而在各功能函數外面定義的全局變量只對從定義處開始往后的各個函數有效,只有從定義處往后的那些功能函數才可以使用該變量,定義處前面的函數則不能使用。
局部變量是指在函數內部或以花括號{ }圍起來的功能塊內部所定義的變量,局部變量只在定義它的函數或功能塊以內有效,在該函數或功能塊以外則不能使用。局部變量可以與全局變量同名,但在這種情況下局部變量的優先級較高,而同名的全局變量在該功能塊內被暫時屏蔽。
從變量的存在時間來看又可分為靜態存儲變量和動態存儲變量。
靜態存儲變量是指該變量在程序運行期間其存儲空間固定不變;動態存儲變量是指該變量的存儲空間不確定,在程序運行期間根據需要動態地為該變量分配存儲空間。一般來說,全局變量為靜態存儲變量,局部變量為動態存儲變量。
在進行程序設計的時候經常需要給一些變量賦以初值,C語言允許在定義變量的同時給變量賦初值。下面是一些變量定義的例子。
char data var1; /* 在data區定義字符型變量var1 */ int idata var2; /* 在idata區定義整型變量var2 */ int a=5; /* 定義變量a,同時賦以初值5,變量a位于 由編譯模式確定的默認存儲區 */ char code text[ ]="ENTER PARAMETER:"; /* 在code區定義字符串數組 */ unsigned char xdata vecter [10][4][4]; /* 在xdata區定義無符號字符型三維數組 變量vecter[10][4][4] */ static unsigned long xdata array [100]; /* 在xdata區定義靜態無符號長整型數組 變量array[100]*/ extern float idata x,y,z; /* 在idata區定義外部浮點型變量x,y,z*/ char xdata * px; /* 在xdata區定義一個指向對象類型為 char的指針px,指針px自身在默認存 儲區(由編譯模式確定),長度為2字節 (0~0xFFFF)*/ char xdata * data pdx; /* 除了指針明確定位于內部數據存儲器區 (data)之外,與上例完全相同,由于指定 了存儲器類型,所以與編譯模式無關 */ extern bit data lock_prt_port; /* 在data區定義一個外部位變量 */ char bdata flags; /* 在bdata區定義字符型變量 */ sbit flag0=flags^0; /* 在bdata區定義可位尋址變量 */ sfr P0=ox80; /* 定義特殊功能寄存器P0 */ sfr16 T2=0xCC; /* 定義特殊功能寄存器T2 */
2.2.4 用typedef重新定義數據類型
在C語言程序中除了可以采用上面所介紹的數據類型之外,用戶還可以根據自己的需要對數據類型重新定義。重新定義時需用到關鍵字typedef,定義方法如下:
typedef 已有的數據類型 新的數據類型名;
其中“已有的數據類型”是指上面所介紹的C語言中所有的數據類型,包括結構、指針和數組等,“新的數據類型名”可按用戶自己的習慣或根據任務需要決定。關鍵字typedef的作用只是將C語言中已有的數據類型作了置換,因此可用置換后的新數據類型名來進行變量的定義。例如:
typedef int word; /* 定義word為新的整型數據類型名 */ word i,j; /* 將I,j定義為int型變量 */
在這個例子中,先用關鍵字typedef將word定義為新的整型數據類型,定義的過程實際上是用word置換了int,因此下面就可以直接用word對變量i,j進行定義,而此時word等效于int,所以i,j被定義成整型變量。例如:
typedef int NUM[100]; /* 將NUM定義為整型數組類型 */ NUM n; /* 將n定義為整型數組變量 */ typedef char * POINTER; /* 將POINTER定義為字符指針類型 */ POINTER point; /* 將point定義為字符指針變量 */
用typedef還可以定義結構類型:
typedef struct /* 定義結構體 */ { int month; int day; int year; } DATE;
這里DATE為一個新的數據類型(結構類型)名,可以直接用它來定義變量:
DATE birthday; /* 定義birthday為結構類型變量 */ DATE * point; /* 定義指向這個結構類型數據的指針 */
關于結構類型數據在本章后面還要詳細討論。一般而言,用typedef定義的新數據類型用大寫字母表示,以便與C語言中原有的數據類型相區別。另外還要注意,用typedef可以定義各種新的數據類型名,但不能直接用來定義變量。typedef只是對已有的數據類型作了一個名字上的置換,并沒有創造出一個新的數據類型,例如前面例子中的word,它只是int類型的一個新名字而已。
采用typedef來重新定義數據類型有利于程序的移植,同時還可以簡化較長的數據類型定義(如結構數據類型等)。在采用多模塊程序設計時,如果不同的模塊程序源文件中用到同一類型的數據時(尤其是像數組、指針、結構、聯合等復雜數據類型),經常用typedef將這些數據重新定義并放到一個單獨的文件中,需要時再用預處理命令#include將它們包含進來。
2.2.5 運算符與表達式
C語言對數據有很強的表達能力,具有十分豐富的運算符。運算符就是完成某種特定運算的符號,表達式則是由運算符及運算對象所組成的具有特定含義的一個式子。C語言是一種表達式語言,在任意一個表達式的后面加一個分號“;”就構成了一個表達式語句。由運算符和表達式可以組成C語言程序的各種語句。
運算符按其在表達式中所起的作用,可分為賦值運算符、算術運算符、增量與減量運算符、關系運算符、邏輯運算符、位運算符、復合賦值運算符、逗號運算符、條件運算符、指針和地址運算符、強制類型轉換運算符和sizeof運算符等。運算符按其在表達式中與運算對象的關系,又可分為單目運算符、雙目運算符和三目運算符等。單目運算符只需要有一個運算對象,雙目運算符要求有兩個運算對象,三目運算符要求有三個運算對象。掌握各種運算符的意義和使用規則,對于編寫正確的C語言程序是十分重要的。
1.賦值運算符
在C語言中,符號“=”是一個特殊的運算符,稱之為賦值運算符。賦值運算符的作用是將一個數據的值賦給一個變量,利用賦值運算符將一個變量與一個表達式連接起來的式子稱為賦值表達式,在賦值表達式的后面加一個分號“;”便構成了賦值語句。賦值語句的格式如下:
變量 = 表達式;
該語句的意義是先計算出右邊表達式的值,然后將該值賦給左邊的變量。上式中的“表達式”還可以是另一個賦值表達式,即C語言允許進行多重賦值。例如:
x=9; /* 將常數 9 賦給變量x */ x=y=8; /* 將常數 8 同時賦給變量x和y */
這些都是合法的賦值語句。在使用賦值運算符“=”時應注意不要與關系運算符“= =”相混淆,運算符“= =”用來進行相等關系運算。
2.算術運算符
C語言中的算術運算符有:
+ 加或取正值運算符
- 減或取負值運算符
* 乘運算符
/ 除運算符
% 取余運算符
上面這些運算符中加、減、乘、除為雙目運算符,它們要求有兩個運算對象。對于加、減和乘法符合一般的算術運算規則。除法運算有所不同,如果是兩個整數相除,其結果為整數,舍去小數部分,例如:5/3的結果為1,5/10的結果為0。如果是兩個浮點數相除,其結果為浮點數,例如:5.0/10.0的結果為0.5。取余運算要求兩個運算對象均為整型數據,例如:7%4的結果為3。取正值和取負值為單目運算符,它們的運算對象只有一個,分別是取運算對象的正值和負值。
用算術運算符將運算對象連接起來的式子即為算術表達式。算術運算的一般形式為:
表達式1 算術運算符 表達式2
例如:x+y/(a-b),(a+b)*(x-y)都是合法的算術表達式。C語言中規定了運算符的優先級和結合性。在求一個表達式的值時,要按運算符的優先級別進行。算術運算符中取負值(-)的優先級最高,其次是乘法(*)、除法(/)和取余(%)運算符,加法(+)和減法(-)運算符的優先級最低。
需要時可在算術表達式中采用圓括號來改變運算符的優先級,例如在計算表達式x+y/(a-b)的值時,首先計算(a-b),然后再計算y/(a-b),最后計算x+y/(a-b)。如果在一個表達式中各個運算符的優先級別相同,則計算時按規定的結合方向進行。例如計算表達式x+y-z的值,由于+和-優先級別相同,計算時按“從左至右”的結合方向,先計算x+y,再計算(x+y)-z。這種“從左至右”的結合方向稱為“左結合性”,此外還有“右結合性”。
3.增量和減量運算符
C語言中除了基本的加、減、乘、除運算符之外,還提供一種特殊的運算符:
++ 增量運算符
-- 減量運算符
增量和減量是C語言中特有的一種運算符,它們的作用分別是對運算對象作加1和減1運算。例如:++i,i++,--j,j--等。
看起來++i和i++的作用都是使變量i的值加1,但是由于運算符++所處的位置不同,使變量i加1的運算過程也不同。++i(或--i)是先執行i+1(或i-1)操作,再使用i的值,而i++(或i--)則是先使用i的值,再執行i+1(或i-1)操作。
增量運算符++和減量運算符--只能用于變量,不能用于常數或表達式。
例2.1:使用增量“++”和減量“- -”運算符的例子。
#include <stdio.h> main(){ int x,y,z; x = y = 8; z = ++x; printf("\n %d %d %d",y,z,x); x = y = 8; z = x++; printf("\n %d %d %d",y,z,x); x = y = 8; z = --x; printf("\n %d %d %d",y,z,x); x = y = 8; z = x--; printf("\n %d %d %d",y,z,x); printf("\n"); while(1); }
程序執行結果:
8 9 9 8 8 9 8 7 7 8 8 7
在這個程序例子中使用了Keil Cx51編譯器提供的輸出庫函數printf,在C語言程序中凡是使用了庫函數的,都必須在程序開始處將該庫函數的預定義文件包含進來,才能使程序得到正確的編譯和執行。本程序在開始處使用了預處理命令#include將聲明庫函數printf原型的頭文件stdio.h包含到程序中去。另外,為了使庫函數printf能夠在μVision2仿真調試狀態下正確工作,應在C語言源程序中增加對8051單片機串行口初始化的語句,或者將C語言源程序與修改后(加入了8051單片機串口初始化指令)的啟動程序STARTUP.A51連接在一起。關于輸入輸出庫函數的詳細介紹請參見本書第9章。
4.關系運算符
C語言中有6種關系運算符:
> 大于
< 小于
>= 大于等于
<= 小于等于
= = 等于
!= 不等于
前4種關系運算符具有相同的優先級,后兩種關系運算符也具有相同的優先級;但前4種的優先級高于后2種。用關系運算符將兩個表達式連接起來即成為關系表達式。關系表達式的一般形式為:
表達式1 關系運算符 表達式2
例如:x>y,x+y>z,(x=3)>(y=4)都是合法的關系表達式。
關系運算符通常用來判別某個條件是否滿足,關系運算的結果只有0和1兩種值。當所指定的條件滿足時結果為1,條件不滿足時結果為0。
例2.2:使用關系運算符的例子。
#include <stdio.h> main() { int x,y,z; printf("input data x,y ? \n"); scanf("%d %d",&x,&y); printf("\n x y x<y x<=y x>y x>=y x!=y x==y"); printf("\n%5d%5d",x,y); z = x < y; printf("%5d",z); z = x <= y; printf("%5d",z); z = x > y; printf("%5d",z); z = x >= y; printf("%5d",z); z = x != y; printf("%5d",z); z = x == y; printf("%5d",z); printf("\n"); while(1); }
程序執行結果(1):
input data x,y ? 5 3 回車 x y x<y x<=y x>y x>=y x!=y x==y 5 3 0 0 1 1 1 0
程序執行結果(2):
input data x,y ? -5 -3 回車 x y x<y x<=y x>y x>=y x!=y x==y -5 -3 1 1 0 0 1 0
程序執行結果(3):
input data x,y ? 4 4 回車 x y x<y x<=y x>y x>=y x!=y x==y 4 4 0 1 0 1 0 1
在本例中使用了Keil Cx51編譯器提供的輸入庫函數scanf,與printf函數一樣,scanf也是通過8051單片機的串行口實現數據輸入的,它的使用方法與printf函數類似。
5.邏輯運算符
C語言中有3種邏輯運算符:
|| 邏輯或
&& 邏輯與
! 邏輯非
邏輯運算符用來求某個條件式的邏輯值,用邏輯運算符將關系表達式或邏輯量連接起來就是邏輯表達式。邏輯運算的一般形式為:
邏輯與 條件式1 && 條件式2 邏輯或 條件式1 || 條件式2 邏輯非 ! 條件式
例如:x&&y,a||b,!z都是合法的邏輯表達式。
進行邏輯與運算時,首先對條件式1進行判斷,如果結果為真(非0值),則繼續對條件式2進行判斷,當結果也為真時,表示邏輯運算的結果為真(值為1);反之,如果條件式1的結果為假,則不再判斷條件式2,而直接給出邏輯運算的結果為假(值為0)。
進行邏輯或運算時,只要兩個條件式中有一個為真,邏輯運算的結果便為真(值為1),只有當條件式1和條件式2均不成立時,邏輯運算的結果才為假(值為0)。
進行邏輯非運算時,對條件式的邏輯值直接取反。
邏輯運算符的優先級為(由高至低):!(非)→&&(與)→||(或),即邏輯非的優先級最高。
例2.3:使用邏輯運算符的例子。
#include <stdio.h> main() { int x,y,z; printf("input data x,y ? \n"); scanf("%d %d",&x,&y); printf("\n x y !x x||y x&&y"); printf("\n%5d%5d",x,y); z = !x; printf("%8d",z); z = x || y; printf("%8d",z); z = x && y; printf("%8d",z); printf("\n"); while(1); }
程序執行結果(1):
input data x,y ? 12 8 回車 x y !x x||y x&&y 12 8 0 1 1
程序執行結果(2):
input data x,y ? 9 -3 回車 x y !x x||y x&&y 9 -3 0 1 1
程序執行結果(3):
input data x,y ? 0 81 回車 x y !x x||y x&&y 0 81 1 1 0
程序執行結果(4):
input data x,y ? -23 0 回車 x y !x x||y x&&y -23 0 0 1 0
程序執行結果(5):
input data x,y ? 0 0 回車 x y !x x||y x&&y 0 0 1 0 0
6.位運算符
能對運算對象進行按位操作是C語言的一大特點,正是由于這一特點使C語言具有了匯編語言的一些功能,從而使之能對計算機的硬件直接進行操作。C語言中共有6種位運算符:
~ 按位取反
<< 左移
>> 右移
& 按位與
^ 按位異或
| 按位或
位運算符的作用是按位對變量進行運算,并不改變參與運算的變量的值。若希望按位改變運算變量的值,則應利用相應的賦值運算。另外位運算符不能用來對浮點型數據進行操作。位運算符的優先級從高到低依次是:按位取反(~)→左移(<<)和右移(>>)→按位與(&)→按位異或(^)→按位或(|)。位運算的一般形式如下:
變量1 位運算符 變量2
表2-6列出了按位取反、按位與、按位或和按位異或的邏輯真值。
表2-6 按位取反、按位與、按位或和按位異或的邏輯真值

例2.4:位邏輯運算。
#include <stdio.h> main() { unsigned int x = 0x57db,y = 0xb0f3; printf("\n x y x&y x^y x|y ~x"); printf("\n%6x%6x%6x%6x%6x%6x",x,y,x&y,x^y,x|y,~x); printf("\n"); while(1); }
x y x&y x^y x|y ~x 57db b0f3 10d3 e728 f7fb a824
程序執行結果:
位運算符中的移位操作比較復雜。左移(<<)運算符是用來將變量1的二進制位值向左移動由變量2所指定的位數。例如:a=0x8f(即二進制數10001111),進行左移運算a<<2,就是將a的全部二進制位值一起向左移動2位,其左端移出的位值被丟棄,并在其右端補以相應位數的“0”。因此,移位的結果是a=0x3c(即二進制數(00111100)。
右移(>>)運算符是用來將變量1的二進制位值向右移動由變量2指定的位數。進行右移運算時,如果變量1屬于無符號類型數據,則總是在其左端補“0”;如果變量1屬于有符號類型數據,則在其左端補入原來數據的符號位(即保持原來的符號不變),其右端的移出位被丟棄。對于a= 0x8f,如果a是無符號數,則執行a>>2之后結果為a=0x23(即二進制數00100011);如果a是有符號數,則執行a>>2之后結果為a=0xe3(即二進制數11100011)。
例2.5:移位運算。
#include <stdio.h> main() { int a,b; unsigned int x,y; a = b = 0xaa55; x = y = 0xaa55; printf("\n a=%4x b=%4x x=%4x y=%4x",a,b,x,y); a = a << 1; b = b >> 1; x = x << 1; y = y >> 1; printf("\n a=%4x b=%4x x=%4x y=%4x",a,b,x,y); printf("\n"); while(1); }
程序執行結果:
a=aa55 b=aa55 x=aa55 y=aa55 a=54aa b=d52a x=54aa y=552a
7.復合賦值運算符
在賦值運算符“=”的前面加上其他運算符,就構成了所謂復合賦值運算符:
+= 加法賦值
-= 減法賦值
*= 乘法賦值
/= 除法賦值
%= 取模賦值
<<= 左移位賦值
>>= 右移位賦值
&= 邏輯與賦值
|= 邏輯或賦值
^= 邏輯異或賦值
~= 邏輯非賦值
復合賦值運算首先對變量進行某種運算,然后將運算的結果再賦給該變量。復合運算的一般形式為:
變量 復合賦值運算符 表達式
例如:a+=3等價于a=a+3;x*=y+8等價于x=x*(y+8)。凡是二目運算符,都可以和賦值運算符一起組合成復合賦值運算符。采用復合賦值運算符,可以使程序簡化,同時還可以提高程序的編譯效率。
例2.6:利用復合賦值運算符實現算術運算。
#include <stdio.h> main() { int a,b,c,d,x,y,z; x = 634; y = 19; z = 28; a = 3 * (b = x/(y-4)) - z/2; printf("\n%10d%10d",a,b); a = 100; b = 45; c = -19; d = 94; x = -2; y = 5; a += 6; b -= x; c *= 10; d /= x+y; z %= 8; printf("\n%10d%10d%10d%10d%10d",a,b,c,d,z); printf("\n"); while(1); }
程序執行結果:
112 42 106 47 -190 31 4
例2.7:利用復合賦值運算符實現位邏輯運算。
#include <stdio.h> main() { int x,y; x = 2; y = 3; x <<= 2; printf("\n%3d",x); x >>= 1; printf("\n%3d",x); x <<= y; printf("\n%3d",x); x = 2; x &= y; printf("\n%3d",x); x |= y; printf("\n%3d",x); x ^= y; printf("\n%3d",x); printf("\n"); while(1); }
程序執行結果:
8 4 32 2 3 0
8.逗號運算符
在C語言中逗號“,”是一個特殊的運算符,可以用它將兩個(或多個)表達式連接起來,稱為逗號表達式。逗號表達式的一般形式為:
表達式1,表達式2,…,表達式n
程序運行時對于逗號表達式的處理,是從左至右依次計算出各個表達式的值,而整個逗號表達式的值是最右邊表達式(即表達式n)的值。
例2.8:逗號運算符的使用。
#include <stdio.h> main() { int a,b,c,w,x,y,z; w = ( x=5,y=-11,z=43,3); printf("\n%d %d %d %d",w,x,y,z); a = 3 * (b = w + x,c = y * ( z -10)) -6; printf("\n%d %d %d",a,b,c); printf("\n"); while(1); }
程序執行結果:
3 5 -11 43 -1095 8 -363
在許多情況下,使用逗號表達式的目的只是為了分別得到各個表達式的值,而并不一定要得到和使用整個逗號表達式的值。另外還要注意,并不是在程序的任何地方出現的逗號,都可以認為是逗號運算符。例如函數中的參數也是用逗號來間隔的,上例中庫輸出函數printf("\n%d %d %d",a,b,c)中的“a,b,c”是函數的三個參數,而不是一個逗號表達式。
9.條件運算符
條件運算符“?:”是C語言中唯一的一個三目運算符,它要求有三個運算對象,用它可以將三個表達式連接構成一個條件表達式。條件表達式的一般形式如下:
邏輯表達式 ? 表達式1:表達式2
其功能是首先計算邏輯表達式,當值為真(非0值)時,將表達式1的值作為整個條件表達式的值;當邏輯表達式的值為假(0值)時,將表達式2的值作為整個條件表達式的值。例如:條件表達式max=(a>b) ? a:b的執行結果是將a和b中較大者賦值給變量max。另外,條件表達式中邏輯表達式的類型可以與表達式1和表達式2的類型不一樣。
10.指針和地址運算符
指針是C語言中的一個十分重要的概念,在C語言的數據類型中專門有一種指針類型。變量的指針就是該變量的地址,還可以定義一個指向某個變量的指針變量。為了表示指針變量和它所指向的變量地址之間的關系,C語言提供了兩個專門的運算符:
* 取內容
& 取地址
取內容和取地址運算的一般形式分別為:
變量 = * 指針變量 指針變量 = & 目標變量
取內容運算的含義是將指針變量所指向的目標變量的值賦給左邊的變量;取地址運算的含義是將目標變量的地址賦給左邊的變量。需要注意的是,指針變量中只能存放地址(即指針型數據),不要將一個非指針類型的數據賦值給一個指針變量。
例2.9:指針及地址運算符的使用。
#include <stdio.h> main() { int i; int *int_ptr; int_ptr = &i; *int_ptr = 5; printf("\n i = %d",i); while(1); }
程序執行結果:
i=5
11.強制類型轉換運算符
C語言中的圓括號“()”也可作為一種運算符使用,這就是強制類型轉換運算符,它的作用是將表達式或變量的類型強制轉換成為所指定的類型。在C語言程序中進行算術運算時,需要注意數據類型的轉換。有兩種數據類型轉換方式,即隱式轉換和顯式轉換。隱式轉換是在對程序進行編譯時由編譯器自動處理的。隱式轉換遵循以下規則。
① 所有char型的操作數轉換成int型。
② 用運算符連接的兩個操作數如果具有不同的數據類型,按以下次序進行轉換:如果一個操作數是float類型,則另一個操作數也轉換成float類型;如果一個操作數是long類型,則另一個操作數也轉換成long類型;如果一個操作數是unsigned類型,則另一個操作數也轉換成unsigned類型。
③ 在對變量賦值時發生的隱式轉換,將賦值號“=”右邊的表達式類型轉換成賦值號左邊變量的類型。例如,把整型數賦值給字符型變量,則整型數的高8位將喪失;把浮點數賦值給整型變量,則小數部分將喪失。在C語言中只有基本數據類型(即char、int、long和float)可以進行隱式轉換。其余的數據類型不能進行隱式轉換,例如,我們不能把一個整型數利用隱式轉換賦值給一個指針變量,在這種情況下就必須利用強制類型轉換運算符來進行顯式轉換。強制類型轉換運算符的一般使用形式為:
(類型)=表達式
顯式類型轉換在給指針變量賦值時特別有用。例如,預先在8051單片機的片外數據存儲器(xdata)中定義了一個字符型指針變量px,如果想給這個指針變量賦一初值0xB000,可以寫成:px=(char xdata *)0xB000;這種方法特別適合于用標識符來存取絕對地址。
例2.10:強制類型轉換運算符的使用。
#include <stdio.h> main() { char xdata * px; char q; int x = 0xf32a; long y = 0x901af364; float z = 3.14159; px=(char xdata *)0xB000; * px='A'; q=*((char xdata *)0xB000); printf("\n%bx %x %d %c",(char)x,(int)y,(int)z,q) ; while(1); }
程序執行結果:
2a f364 3 A
12.sizeof運算符
C語言中提供了一種用于求取數據類型、變量以及表達式的字節數的運算符:sizeof,該運算符的一般使用形式為:
sizeof(表達式)或sizeof(數據類型)
應該注意的是,sizeof是一種特殊的運算符,不要錯誤地認為它是一個函數。實際上,字節數的計算在程序編譯時就完成了,而不是在程序執行的過程中才計算出來的。
例2.11:sizeof運算符的使用。
#include <stdio.h> main() { printf("\n char: %bd byte",sizeof(char)); printf("\n int: %bd bytes",sizeof(int)); printf("\n long: %bd bytes",sizeof(long)); printf("\n float: %bd bytes",sizeof(float)); while(1); }
程序執行結果:
char: 1 byte int: 2 bytes long: 4 bytes float:4 bytes
前面對C語言中的各種運算符分別作了介紹,此外還有三個運算符:數組下標運算符“[ ]”、存取結構或聯合中變量的運算符“->”或“.”,它們將在第5章予以介紹。表2-7給出了這些運算符在使用過程中的優先級和結合性。
表2-7 運算符的優先級和結合性

2.3 Cx51程序的基本語句
2.3.1 表達式語句
C語言是一種結構化的程序設計語言,它提供了十分豐富的程序控制語句。表達式語句是最基本的一種語句。在表達式的后邊加一個分號“;”就構成了表達式語句。下面的語句都是合法的表達式語句:
a=++b*9; x=8; y=7; z=(x+y)/a; ++i;
表達式語句也可以僅由一個分號“;”組成,這種語句稱為空語句。空語句是表達式語句的一個特例。空語句在程序設計中有時是很有用的,當程序在語法上需要有一個語句,但在語義上并不要求有具體的動作時,便可以采用空語句。空語句通常有以下兩種用法。
① 在程序中為有關語句提供標號,用以標記程序執行的位置。例如,采用下面的語句可以構成一個循環。
repeat:; .. goto repeat ;
② 在用while語句構成的循環語句后面加一個分號,形成一個不執行其他操作的空循環體。這種空語句在等待某個事件發生時特別有用。例如,下面這段程序是讀取8051單片機串行口數據的函數,其中就用了一個空語句while (!RI);來等待單片機串行口接收結束。
#include <reg51.h> /* 插入8051單片機的預定義文件 */ char _getkey () /* 函數定義 */ { /* 函數體開始 */ char c; /* 定義變量 */ while (!RI); /* 空語句,等待8051單片機串行口接收結束 */ c = SBUF; /* 讀串行口內容 */ RI = 0; /* 清除串行口接收標志 */ return (c); /* 返回 */ } /* 函數體結束 */
采用分號“;”作為空語句使用時,要注意與簡單語句中有效組成部分的分號相區別。不能濫用空語句,以免引起程序的誤操作,甚至造成程序語法上的錯誤。
2.3.2 復合語句
復合語句是由若干條語句組合而成的一種語句,它是用一個大括號“{}”將若干條語句組合在一起而形成的一種功能塊。復合語句不需要以分號“;”結束,但它內部的各條單語句仍需以分號“;”結束。復合語句的一般形式為:
{ 局部變量定義; 語句1; 語句2; … 語句n; }
復合語句在執行時,其中的各條單語句依次順序執行。整個復合語句在語法上等價于一條單語句,因此在C語言程序中可以將復合語句視為一條單語句。復合語句允許嵌套,即在復合語句內部還可以包含別的復合語句。通常復合語句都出現在函數中,實際上,函數的執行部分(即函數體)就是一個復合語句。復合語句中的單語句一般是可執行語句,此外還可以是變量的定義語句(說明變量的數據類型)。
在復合語句內所定義的變量,稱為該復合語句中的局部變量,它僅在當前這個復合語句中有效。利用復合語句將多條單語句組合在一起,以及在復合語句中進行局部變量定義是C語言的一個重要特征。
例2.12:復合語句及其局部變量的使用。
#include <stdio.h> main() { /* 主函數體開始 */ int a,b,c,d; /* 定義變量a,b,c,d,它們在整個主函數中有效 */ a = 1; b = 2; c = 3; d = 4; printf("\nX: %d %d %d %d",a,b,c,d); { /* 復合語句1 */ int b,m; /* 定義局部變量b,m,它們僅在復合語句1 中有效 */ b = 8; m = 100; printf("\nY: %d %d %d %d | %d",a,b,c,d,m); { /* 復合語句2 */ int c,n; /* 定義局部變量c,n,它們僅在復合語句2中有效 */ c = 9; n = 150; printf("\nZ: %d %d %d %d | %d %d",a,b,c,d,m,n); } /* 復合語句2 結束 */ printf("\nY: %d %d %d %d | %d",a,b,c,d,m); } /* 復合語句1 結束 */ printf("\nX: %d %d %d %d",a,b,c,d); printf("\n"); while(1); } /* 主函數體結束 */
程序執行結果:
X: 1 2 3 4 Y: 1 8 3 4 | 100 Z: 1 8 9 4 | 100150 Y: 1 8 3 4 | 100 X: 1 2 3 4
在這個程序的主函數體開始處,定義了變量a,b,c,d,它們在整個主函數體中都是有效的,在主函數體中的復合語句1和復合語句2中都可以使用它們。另外,在復合語句1中又定義了局部變量m和與主函數體中定義的變量同名的局部變量b,這種局部變量m和b僅在定義它的復合語句1中有效,而且局部變量b的優先級高于在主函數體中定義的同名變量b。因此在復合語句1中執行printf函數輸出的b值為8,而不是2。
同樣,在復合語句2中定義了一個與主函數體中同名的局部變量c,在復合語句2中執行printf函數輸出的c值為9,而不是3。一旦出了復合語句,則其中的局部變量立即失效。如果有同名的局部變量,則恢復該變量在上一層位置所定義的初值。讀者可以通過仔細分析本程序的執行結果來弄清各個變量的作用范圍。
2.3.3 條件語句
條件語句又稱為分支語句,它是用關鍵字if構成的。C語言提供了三種形式的條件語句。
(1)if(條件表達式)語句
其含義為:若條件表達式的結果為真(非0值),就執行后面的語句;反之若條件表達式的結果為假(0值),就不執行后面的語句。這里的語句也可以是復合語句。這種條件語句的執行過程如圖2.1(a)所示。
(2)if(條件表達式)語句1
else語句2
其含義為:若條件表達式的結果為真(非0值),就執行語句1;反之若條件表達式的結果為假(0值),就執行語句2。這里的語句1和語句2均可以是復合語句。這種條件語句的執行過程如圖2.1(b)所示。

圖2.1 條件語句的執行過程
(3)if(條件表達式1)語句1
else if(條件式表達2)語句2
else if(條件式表達3)語句3
… …
else if(條件表達式n)語句m
else語句n
這種條件語句常用來實現多方向條件分支,其執行過程如圖2.2所示。

圖2.2 多分支條件語句的執行過程
例2.13:條件語句的使用——求一元二次方程的根。
#include <stdio.h> #include <math.h> main() { float a,b,c,x1,x2; float r,s; a = 2.0; b = 3.0; c = 4.0; r = b * b -4.0 * a * c; if( r > 0.0 ) { s = sqrt(r); x1 = (-b + s) / (2.0 * a); x2 = (-b - s) / (2.0 * a); printf("real: x1 =%15.7f,x2 =%15.7f\n",x1,x2); } else if( r == 0.0 ) printf("double: x1,x2 =%15.7f\n",-b/(2.0*a)); else { x1 = -b / (2.0 * a); x2 = sqrt(-r) / (2.0 * a); printf("complex: Re=%15.7f,Im=%15.7f\n",x1,x2); } while(1); }
程序執行結果:
complex: Re= -0.7500000,Im= 1.1989580
在這個程序中使用了庫函數sqrt(r)來求方程的根,sqrt是一個算術庫函數。為了使程序能得到正確的編譯和執行,在本程序的開始處使用了預處理命令#include將庫函數sqrt所在的預處理文件math.h包含到程序中去。
2.3.4 開關語句
開關語句也是一種用來實現多方向條件分支的語句。雖然采用條件語句也可以實現多方向條件分支,但是當分支較多時會使條件語句的嵌套層次太多,程序冗長,可讀性降低。開關語句直接處理多分支選擇,使程序結構清晰,使用方便。開關語句是用關鍵字switch構成的,它的一般形式如下:
switch (表達式) { case 常量表達式1:語句1 break; case 常量表達式2:語句2 break; … … case 常量表達式n:語句n break; default:語句d }
開關語句的執行過程是:將switch后面表達式的值與case后面各個常量表達式的值逐個進行比較,若遇到匹配時,就執行相應case后面的語句,然后執行break語句,break語句又稱間斷語句,它的功能是中止當前語句的執行,使程序跳出switch語句。若無匹配的情況,則只執行語句d。開關語句的執行過程如圖2.3所示。

圖2.3 開關語句的執行過程
例2.14:開關語句的使用。
本程序按照輸入的年份year和月份month,計算該月有多少天。程序需要判斷該年是否為閏年。閏年的2月有29天,平年的2月只有28天。閏年的條件是:年份數year能被4整除,但不能被100整除;或者年份數year能被400整除。這個條件可以用一個邏輯關系式來表達:
year%4= =0 && year%100 != 0 || year%400 = = 0
當這個表達式的值為真(非0值)時,year為閏年,否則為平年。
#include <stdio.h> main() { int year,month,len; while(1){ printf("Enter year & month: \n"); scanf("%d%d",&year,&month); switch(month) { case 1: len=31; break; case 3: len=31; break; case 5: len=31; break; case 7: len=31; break; case 8: len=31; break; case 10: len=31; break; case 12: len=31; break; case 4: len=30; break; case 6: len=30; break; case 9: len=30; break; case 11: len=30; break; case 2: if(year%4==0&&year%100 != 0||year%400==0) len=29; else len=28; break; default: printf("Input error \n"); len=0; break; } if(len != 0) printf("The lenth of%d,%dis %d \n",year,month,len); } }
程序執行結果:
Enter year & month: 1996 2 回車 The lenth of 1996,2 is 29
2.3.5 循環語句
實際應用中很多地方需要用到循環控制,如需要反復進行某種操作等,這時可以用循環語句來實現。在C語言程序中用來構成循環控制的語句有:while語句、do-while語句、for語句以及goto語句,分述如下。
① 采用while語句構成循環結構的一般形式如下:
while(條件表達式) 語句;
其意義為,當條件表達式的結果為真(非0值)時,程序就重復執行后面的語句,一直執行到條件表達式的結果變為假(0值)時為止。這種循環結構是先檢查條件表達式所給出的條件,再根據檢查的結果決定是否執行后面的語句。如果條件表達式的結果一開始就為假,則后面的語句一次也不會被執行。這里的語句可以是復合語句。圖2.4所示為while語句的執行過程。

圖2.4 while語句的執行過程
例2.15:使用while語句計算自然數1~100的累加和。
#include<stdio.h> main() { int i,s=0; i=1; while (i<=100) { /* 復合語句循環體 */ s=s+i; i++; } /* 循環體結束 */ printf("1+2+ … +100 = %d\n",s); while(1); }
程序執行結果:
1+2+ … +100 = 5050
② 采用do-while語句構成循環結構的一般形式如下:
do語句while(條件表達式);
這種循環結構的特點是先執行給定的循環體語句,然后再檢查條件表達式的結果。當條件表達式的值為真(非0值)時,則重復執行循環體語句,直到條件表達式的值變為假(0值)時為止。因此,用do-while語句構成的循環結構在任何條件下,循環體語句至少會被執行一次。圖2.5給出了這種循環結構的流程圖。

圖2.5 do-while循環結構的流程圖
例2.16:用do-while語句構成的循環計算自然數1~100的累加和。
#include<stdio.h> main() { int i,s=0; i=1; do { /* 復合語句循環體 */ s=s+i; i++; } /* 循環體結束 */ while(i<=100); printf("1+2+ … +100 = %d\n",s); while(1); }
程序執行結果:
1+2+ ··· +100 = 5050
例2.16的程序與例2.15的程序十分相似。它們的區別僅僅是執行循環體語句和判斷條件表達式的結果的順序不同。另外,用do-while語句構成的循環結構中,while(條件表達式)的后面必須有一個分號,而用while語句構成的循環結構中while(條件表達式)后面是沒有分號的。這一點在寫程序時一定要注意。
③ 采用for語句構成循環結構的一般形式如下:
for([初值設定表達式];[循環條件表達式];[更新表達式])語句
for語句的執行過程是:先計算出初值設定表達式的值,以此作為循環控制變量的初值,再檢查循環條件表達式的結果,當滿足條件時就執行循環體語句并計算更新表達式,然后再根據更新表達式的計算結果來判斷循環條件是否滿足,一直進行到循環條件表達式的結果為假(0值)時退出循環體。for語句的執行過程如圖2.6所示。

圖2.6 for語句的執行過程
例2.17:用for語句構成的循環計算自然數1~100的累加和。
#include<stdio.h> main() { int i,s=0; for (i=1; i<=100; i++) s=s+i; /* 循環體語句 */ printf("1+2+ … +100 = %d\n",s); while(1); }
程序執行結果:
1+2+…+100 = 5050
在C語言程序的循環結構中,for語句的使用最為靈活,它不僅可以用于循環次數已經確定的情況,而且可以用于循環次數不確定而只給出循環結束條件的情況。另外,for語句中的三個表達式是相互獨立的,并不一定要求三個表達式之間有依賴關系。并且for語句中的三個表達式都可能缺省,但無論缺省哪一個表達式,其中的兩個分號都不能缺省。一般不要缺省循環條件表達式,以免形成死循環。
例2.18:for語句中缺省表達式的例子——計算自然數1~100的累加和。
#include<stdio.h> main() { int i,s=0; i=1; /* 設置循環初值 */ for ( ;i<=100 ;) { /* 缺省初值設定表達式和更新表達式 */ s=s+i; /* 循環體語句 */ i++; /* 循環控制變量更新 */ } printf("1+2+ … +100 = %d\n",s); while(1); }
程序執行結果:
1+2+…+100 = 5050
④ goto語句是一個無條件轉向語句,它的一般形式為:
goto語句標號;
其中語句標號是一個帶冒號“:”的標識符。將goto語句和if語句一起使用,可以構成一個循環結構。但更常見的是在C語言程序中采用goto語句來跳出多重循環,需要注意的是只能用goto語句從內層循環跳到外層循環,而不允許從外層循環跳到內層循環。
例2.19:使用goto語句跳出循環結構。
本程序采用循環結構來求一整數的等差數列,該數列滿足條件:頭四個數的和值為26,積值為880。該數列的公差應為正整數,否則將產生負的項,此外該數列的首項數必須小于5,且其公差也應小于5,否則頭四項的和值將大于26。
#include<stdio.h> main() { int a,b,c,d,i; for(a=1; a<5; ++a) { for(d=1; d<5; ++d) { b=a+(a+d)+(a+2*d)+(a+3*d); c=a*(a+d)*(a+2*d)*(a+3*d); if(b==26 && c==880) goto pt; } } pt: for(i=0; i<=10; ++i) printf("%d,",a+i*d); printf("…\n"); while(1); }
程序執行結果:
2,5,8,11,14,17,20,23,26,29,…
在這個程序中采用for語句構成了兩重循環嵌套,即在第一個for語句的循環體中又出現了另一個for語句的循環體,需要時還可以構成多重循環結構。程序在最內層循環體中采用了一個goto語句,它的作用是直接跳出兩層循環,即跳到第一層循環體外邊由標號pt:所指出的地方。前面在介紹開關語句時提到采用break語句可以跳出開關語句,break語句還可以用于跳出循環語句。對于上面的例子,也可以采用break語句來終止循環。
例2.20:用break語句終止循環。
#include<stdio.h> main() { int a,b,c,d,i; for(a=1; a<5; ++a) { for(d=1; d<5; ++d) { b=a+(a+d)+(a+2*d)+(a+3*d); c=a*(a+d)*(a+2*d)*(a+3*d); if(b==26 && c==880) break; } if(b==26 && c==880) break; } for(i=0; i<=10; ++i) printf("%d,",a+i*d); printf("…\n"); while(1); }
程序執行結果:
2,5,8,11,14,17,20,23,26,29,…
從例2.20可以看到,對于多重循環的情況,break語句只能跳出它所處的那一層循環,而不像goto語句可以直接從最內層循環中跳出來。由此可見,要退出多重循環時,采用goto語句比較方便。需要指出的是,break語句只能用于開關語句和循環語句之中,它是一種具有特殊功能的無條件轉移語句。另外還要注意,在進行實際程序設計時,為了保證程序具有良好的結構,應當盡可能少地采用goto語句,以使程序結構清晰易讀。
在循環結構中還可以使用一種中斷語句continue,它的功能是結束本次循環,即跳過循環體中下面尚未執行的語句,把程序流程轉移到當前循環語句的下一個循環周期,并根據循環控制條件決定是否重復執行該循環體。continue語句的一般形式為:
continue;
continue語句通常和條件語句一起用在由while、do-while和for語句構成的循環結構中,它也是一種具有特殊功能的無條件轉移語句,但與break語句不同,continue語句并不跳出循環體,而只是根據循環控制條件確定是否繼續執行循環語句。
例2.21:利用continue語句把10~20之間不能被3整除的數輸出。
#include<stdio.h> main() { int n; for(n=10; n<=20; n++) { if(n%3==0) continue; printf("%d ",n); } while(1); }
程序執行結果:
10 11 13 14 16 17 19 20
2.3.6 返回語句
返回語句用于終止函數的執行,并控制程序返回到調用該函數時所處的位置。返回語句有兩種形式:
return(表達式); 或者return;
如果return語句后邊帶有表達式,則要計算表達式的值,并將表達式的值作為該函數的返回值。若使用不帶表達式的第2種形式,則被調用函數返回主調用函數時,函數值不確定。一個函數的內部可以含有多個return語句,但程序僅執行其中的一個return語句而返回主調用函數。一個函數的內部也可以沒有return語句,在這種情況下,當程序執行到最后一個界限符“}”處時,就自動返回主調用函數。
例2.22:return語句的使用。
#include <stdio.h> main() { /* 主函數體 */ int x,n,p,power(int x,int n); printf("calculate X to the power of N\n please input X and N ? "); scanf("%d%d",&x,&n); p = power(x,n); /* 在此處調用函數 */ printf("%d to the power of %d = %d\n",x,n,p); while(1); } int power(int x,int n) /* 被調用函數 */ int x,n; { int i,p = 1; for( i=0; i<n; ++i ) p *= x; return( p ); /* 帶值返回到調用處 */ }
程序執行結果:
calculate X to the power of N please input X and N ? 5 3 回車 5 to the power of 3 = 125
- Docker and Kubernetes for Java Developers
- 大學計算機應用基礎實踐教程
- 移動UI設計(微課版)
- Mastering QGIS
- 看透JavaScript:原理、方法與實踐
- C語言程序設計
- Groovy for Domain:specific Languages(Second Edition)
- JavaScript 程序設計案例教程
- Python編程從0到1(視頻教學版)
- 蘋果的產品設計之道:創建優秀產品、服務和用戶體驗的七個原則
- Java網絡編程核心技術詳解(視頻微課版)
- JavaScript腳本特效編程給力起飛
- Python程序設計與算法基礎教程(第2版)(微課版)
- TypeScript 2.x By Example
- 貫通Tomcat開發