C Primer Plus(第五版)7
第 7 章 C 控制語句:分支和跳轉
在本章中你將學習下列內容:
· 關鍵字:if(如果),else(否則),switch(切換),continue(繼續),break(中斷),
case(情況),default(缺省),goto(轉到)
· 運算符: &&(邏輯與), ||(邏輯或), ?: (條件運算)
· 函數: getchar(), putchar()以及 ctype.h 系列。
· 怎樣使用 if 和 if else 語句以及如何嵌套使用它們。
· 使用邏輯運算符將關系表達式組合為更加復雜的判斷表達式。
· C 的條件運算符。
· switch 語句 。
· break,continue,和 goto 跳轉。
· 使用 C 的字符 I/O 函數:getchar()和 putchar()。
· 由 ctype.h 頭文件提供的字符分析函數系列。
對 C 語言掌握到一定程序以后,你或許想要處理一些更復雜的任務。這時候就需要一些手段來控制和組織這些項目,C 有方法來滿足這些需要。前面已經學習了使用循環來處理重復的任務。在本章中,你將學習如何使用分支結構,如 if 和 switch, 以便允許程序按照所檢查的條件執行相應的動作。另外,還要介紹 C 的邏輯運算符,它們能使你對 while 或 if 條件中的多個關系進行判斷。此外,我們還將學習跳轉語句,它將程序流程切換到程序的其他部分。學完本章,你將獲得設計按你希望的方式運行的程序所需的全部基本知識。
7.1 if 語句
讓我們以程序清單 7.1 展示的一個 if 語句的簡單例子開始。這個程序讀入一系列每日的最低溫度(攝氏度),并報告輸入的總數,以及最低溫度在零度以下的天數的百分率。在一個循環里使用 scanf()讀入數值,在每個循環中增加計數器的值來統計輸入數值的個數。if 語句檢測低于零度以下的溫度并單獨統計這些天的數目。
程序清單 7.1 colddays.c 程序
--------------------------------------------------------------------
/* colddays.c ---- 求出溫度低于零度的天數的百分率 */
#include <stdio.h>
int main (void)
{
const int FREEZING = 0;
float temperature;
int cold_days = 0;
int all_days = 0;
printf ("Enter the list of daily low temperatures . \n");
printf ("Use Celsius, and enter q to quit .\n");
while (scanf ("%f",&temperature) == 1)
{
all_days++;
if (temperature < FREEZING)
cold_days++;
}
if (all_days != 0)
printf ("%d days total; %.1f%% were below freezing .\n",
all_days,100.0 * (float)cold_days / all_days);
if (all_days == 0)
printf ("NO data entered !\n");
system("PAUSE");
return 0;
}
下面是一個運行示例:
Enter the list of daily low temperatures .
Use Celsius, and enter q to quit .
12 5 -2.5 0 6 8 -3 -10 5 10 q
10 days total; 30.0% were below freezing .
while 循環的判斷條件利用 scanf()的返回值在 scanf()遇到非數字輸入的時候終止循環。用 float 而不是 int 來聲明 temperature ,這樣程序就既能接受像 8 那樣的輸入,也能接受 -2.5 這樣的輸入。
while 代碼中的新語句如下:
if (temperature < FREEZING)
cold_days++;
該 if 語句指示計算機,如果剛讀入的數值 (temperature)小于 -0 就將 cold_days 加 1 。如果 temperature 值不小于 0 將是什么情形呢?那樣的話就會跳過 cold_days++;語句,while 循環繼續讀取下一個溫度值。
該程序使用兩次 if 語句來控制輸出。如果有數據的話,程序就會打印出結果,如果沒有數據,那么程序就會報告該事實(稍后你將看到可以用一種更好的方法來處理程序的這個部分)。
為了避免整數除法,示例程序在計算百分率時使用了類型轉換 float。并不是真的需要類型轉換,因為在表達式 100.0 * cold_days / all_days 中,先求子表達式 100.0 * cold_days 的值并通過自動類型轉換規則強制轉換為浮點型數值。然后使用類型轉換可以表明你的意圖,并保護程序免受不完善編譯器的影響。
if 語句被稱為分支語句(branching statement)或選擇語句(selection statement),因為它提供了一個交匯點,在此處程序需要選擇兩條分支的一條前進。一般的形式如下:
if (expression)
statement
如果 expression 求得的值為真(非零),就執行 statement;否則,跳過該語句。和 while 循環一樣, statement 既可以是單個語句也可以是一個代碼塊(術語上稱為復合語句)。這種結構和 while 語句很相似。主要的區別在于在 if 語句中,判斷和執行(如果可能的話)僅有一次,而在 while 循環中,判斷和執行可以重復多次。
通常,expression 是一個關系表達式。也就是說,它比較兩個量的大小,像表達式 x>y 和 c== 6 那樣。如果 expression 的值為真 (x大于y,或者c等于6);就執行語句;否則,將忽略語句。更一般地,可以使用任何表達式,表達式的值為 0 就被視為假。語句部分可以是一個簡單語句,就像例子中的那樣;也可以是一個由花括號標出的復合語句(代碼塊):
if (score > big )
printf ("Jackpot \n"); // 簡單語句
if (joe > ron ) // 復合語句
{
jackpot++
printf ("you lose,Ron \n");
}
7.2 在 if 語句中添加 else 關鍵字
簡單形式的 if 語句使你可以選擇執行一條語句(可能是復合語句)或忽略它。C 還可以通過使用 if else 形式來在兩個語句間做出選擇。讓我們使用 if else 形式侯程序清單 7.1 中略顯笨拙的程序段。
if (all_days != 0)
printf ("%d days total: %.1f%% were below freezing .\n",
all_days, 100.0 * (float)cold_days / all_days);
if (all_days == 0)
printf ("No data entered \n");
如果程序發現 all_days 不等于 0 的,那么它應該知道 all_days 肯定等于 0,這樣就不必等閑判斷。使用 if else ,你可以將這段程序改寫為如下形式;
if (all_days != 0)
printf ("%d days total: %.1f%% were below freezing .\n",
all_days, 100.0 * (float)cold_days / all_days);
else
printf ("No data entered \n");
僅僅進行了一次判斷。如果 if 的判斷表達式為真,則打印溫度數據;如果某值為假,則打印警告消息。
注意,if else 語句的通用形式為:
if (expression)
statement1
else
statement2
如果 expression 為真(非零),就執行 statement1;如果 expression 為假或零,則執行跟在 else 后的那一條語句 (statement2)。語句可以是簡單的或復合的。C 不要求縮排,但這是標準的風格。縮排使語句依賴于判斷而執行這一事實顯得一目了然。
如果希望在 if 和 else 之間有多條語句,必須使用花括號創建一個代碼塊。下面的結構了 C 語法,因為編譯器期望 if 和 else 之間只有一條語句(單條的或復合的):
if (x > 0)
printf ("Incrementing x :\n");
x++;
else // 將產生一個錯誤
printf ("x <= 0 \n");
編譯器會把 printf () 語句看作 if 語句的部分,將 x++;語句看作一條單獨的語句,而不把它作為 if 語句的一部分。然后會認為 else 沒有所屬的 if ,這是個錯誤。應該使用這種形式:
if (x > 0)
{
printf ("Incrementing x :\n");
x++;
}
else
printf ("x <= 0 \n");
if 語句使你能夠選擇是否執行某個動作。if else 語句使你可以在兩個動作之間進行選擇。
7.2.1 另一個例子:介紹 getchar()和 putchar()
前面的多數程序所輸入的內容都是數字。為了練習使用其他的形式,讓我們來看一個面向字符的例子。你已經知道了怎樣使用 scanf()和 printf()來以 %c 說明符讀寫字符,現在我們將接觸專門為面向字符 I/O 而設計的一對 C 函數: getchar()和 putchar()。
getchar()函數沒有參數,它返回來自輸入設備的下一個字符。例如,下面的語句讀取下一個輸入字符并將它的值賦給變量 ch:
ch = getcahr();
該語句與下面的語句有同樣的效果:
scanf("%c",&ch);
putchar()函數打印它的參數。例如,下面的語句將先前賦給 ch 的值作為字符打印聘為:
putchar(ch);
該語句與下面的語句有同樣的效果:
printf ("%c",ch);
因為這些函數僅僅處理字符,所以它們比更通用的 scanf()和 printf()函數更快而且更簡潔。同樣,注意到它們不需要格式說明符,因為它們只對字符起作用。這兩個函數通常都在 stdio.h 文件中定義(而且,它們通常只是預處理器宏(macro),而不是真正的函數;我們將在第 16 章“C 預處理器和 C 庫”中討論類似函數的宏)。
下面將通過一個程序來說明這些函數是怎么工作的。這個程序再現輸入的一行,但將每個非空格字符替換為該字符在 ASCII 碼序列中的后一個字符。空格還是作為空格生機。可以將希望程序做出的響應表述為“如果字符是空格,打印之;否則,打印它在 ASCII 序列中的下一個字符”。
C 的代碼看起來很像該陳述,這一點你可以在程序清單 7.2 中看到。
程序清單 7.2 cypher1.c 程序
----------------------------------------------------------------
/* cypher1.c --- 改變輸入,只保留其中的空格 */
#include <stdio.h>
#define SPACE " " /* SPACE 相當于 “引號-空格-引號” */
int main (void)
{
char ch;
ch = getchar(); /* 讀入一個字符 */
while (ch != '\n') /* 當一行末結束時 */
{
if (ch == SPACE) /* 空格不變 */
putchar(ch); /* 不改變這個字符 */
else
putchar(ch + 1); /* 改變其他字符 */
ch = getchar(); /* 獲取下一個字符 */
}
putchar(ch); /* 打印換行字符 */
system("PAUSE");
return 0;
}
下面是一個運行示例;
CALL MB HAL.
DBMM NC IBM/
把這個循環和程序清單 7.1 中的循環進行比較。程序清單 7.1 中用 scanf()返回的狀態而不是輸入元素的值來決定何時終止循環。而在程序清單 7.2 中,則是由輸入元素的值本身決定何時終止循環,這種不同導致這個循環結構與前面的略有不同。這里,一個“讀”語句在循環之前,另一個“讀”語句在每次循環的結尾。然而,C 靈活的語法使你可以通過將讀取和判斷合并為單個表達式來仿效程序清單 7.1 。那也就是說,你可以把這種形式的循環:
ch = getchar(); // 讀入一個字符
while (ch != '\n') //當一行末結束時
{
...
ch = getchar(); // 獲取下一個字符
}
替換為下面的形式:
while ((ch = getchar()) != '\n')
{
... // 處理字符
}
關鍵的一行是:
while ((ch = getchar()) != '\n')
這體現了典型的 C 編程風格:將兩個動作合并為一個表達式。C 自由的格式可以使這行語句中的每個部分更清晰:
while (
(ch = getchar()) // 為 ch 賦一個值
!= '\n') // 把 ch 與 \n 相比較
該動作將一個值賦給 ch 并將這個值與換行符進行比較。圓括號括住 ch = getchar()使之成為“!=”運算符的左操作數。為了求得該表達式的值,計算機首先必須調用 getchar()函數,然后將它的返回值賦給 ch 。因為賦值表達式的值就等于表達式左側數的值,所以 ch = getchar()的值就等于 ch 的新值。因此,讀取 ch 之后,判斷條件縮減為 ch != '\n' ,也就是說,ch 不等于換行符。
這種獨特的習慣用法在 C 編程中看常見,所以你應該熟悉它。也一定要記住適當地使用圓括號來組合子表達式。
所有的圓括號都是必需的。假如錯誤地這樣使用;
while (ch = getchar() != '\n')
!= 運算符的優先級比 = 要高,所以首先被計算的表達式是 getchar() != '\n' 。因為這是一個關系表達式,因此其值是 0 或 1 (真或假)。然后這個值被賦給 ch 。遺漏了圓括號就意味著 ch 的值被賦為 0 或 1,而不是 getchar()的返回的值,顯然這不是我們所希望的。
下面的語句:
putchar (ch + 1); // 改變其他字符
再次表明了字符實際上是作為整數被存儲的。在表達式 ch + 1 中,ch 被擴展為 int 型以用于計算,然后 int 型的計算結果被傳給 putchar()。這個函數接受一個 int 型的參數,但僅僅使用最后一個字節來決定顯示哪個字符。
?
7.2.2 ctype.h 系列字符函數
注意到程序清單 7.2 的輸出顯示出句號被轉換為斜杠;這是因為斜杠字符對應的 ASCII 碼與句號的 ASCII 碼大 1 。但是如果程序指明只轉換字母,將所有的非字母字符(而不只是空格符)保留下來,將會更好。本章稍后討論的邏輯運算提供一種方法以判斷字符是否不是空格,逗號,如此等等,但是羅列所有的可能性太麻煩。幸好, ANSI C 有一系列標準的函數可以用來分析字符;ctype.h 頭文件包含了這些函數的原型。這些函數接受一個字符作為參數,如果該字符屬于某特定的種類則返回非零值(真),否則返回零(假)。例如,如果 isalpha()函數的參數是個字母,則它返回一個非零值。程序清單 7.3 通過使用該函數擴展了程序清單 7.2,它也加入了剛才討論過的精簡后的循環結構。
程序清單 7.3 cypher2.c 程序
--------------------------------------------------------------
// cypher2.c -- 改變輸入,只留非字母字符
#include <stdio.h>
#include <ctype.h> // 為 isalpha()提供原型
int main (void)
{
char ch;
while ((ch = getchar()) != '\n')
{
if (isalpha(ch)) //如果是一個字母
putchar (ch+1); // 則改變它
else //否則
putchar (ch); // 原樣打印它
}
putchar(ch); // 打印換行字符
system("PAUSE");
return 0;
}
下面是一個運行示例;注意大寫字母和小寫字母被譯碼,而空格和標點符號則沒有;
Look! It's a programmer!
Mppl! Ju't b qsphsbnnfs!
表 7.1 和表 7.2 列出了 ctype.h 頭文件所包含的一些函數。有些函數提到了本地化,這里指的是難免指定一個本地以修改或擴展 C 的基本用法的 C 工具(例如,許多國家在書寫十進制小數的時候,使用逗號來替代小數點,于是,特定的本地化工具就能夠指定逗號在浮點小數的輸出中起到小數點一樣的作用。因此,就顯示為 123,45)。注意,映射函數并不改變原始的參數,它們只返回改變后的值。也就是說,下列語句不改變 ch 的值:
tolower(ch) // 對 ch 沒有影響
沒有改變 ch ,若要改變 ch,可以這樣做:
ch = tolower(ch); // 把 ch 轉換為小寫 注: CBC++ 不一定可行
表 7.1 ctype.h 的字符判斷函數
---------------------------------------------------------------------------
函數名 為如下參數時,返回值為真
----------------------------------------------------------------------------
isalnum() 字母數字(字母或數字)
----------------------------------------------------------------------------
isalpha() 字母
----------------------------------------------------------------------------
isblank() 一個標準的空白字符(空格水平制表或換行)或任何其他本地化指定為空白字符
--------------------------------------------------------------------------------------
iscntrl() 控制符,例如 Ctrl + B
---------------------------------------------------------------------------------
isdigit() 阿拉伯數字
-------------------------------------------------------------------------------
isgraph() 除空格之外的所有可打印字符
--------------------------------------------------------------------------------
islower() 小字字母
--------------------------------------------------------------------------------
isprint() 可打印字符
-------------------------------------------------------------------------------
ispunct() 標點符號(除空格和字母數字外的可打印字符)
--------------------------------------------------------------------------------
isspace() 空白字符:空格,換行,走紙,回車,制表符,或其他本地化定義字符
--------------------------------------------------------------------------------
isupper() 大寫字母
---------------------------------------------------------------------------------
isxdigit() 十六進制數字字符
----------------------------------------------------------------------------------
表 7.2 ctype.h 的字符映射函數
-----------------------------------------------------------------------------------
函數名 動作
-----------------------------------------------------------------------------------
tolower() 如果參數是大寫字符,返回相應的小寫字符;否則,返回原始參數
-----------------------------------------------------------------------------------
toupper() 如果參數是小寫字符,返回相應的大寫字符;否則,返回原始參數
-------------------------------------------------------------------------------------
?
7.2.3 多重選擇 else if
日常生活經常會給我們提供兩個以上的選擇。可以用 else if 擴展 if else 結構來適應這種情況。我們來看一個例子。公用事業公司通常使計費依賴于客戶使用能量的總數。這里有某個公司電力計費的費率,單位是千瓦時(kWh):
-------------------------------------------------------------------
第一個 360 kWh: 每 kWh $0.12589
-------------------------------------------------------------------
下一個 320 kWh: 每 kWh $0.17901
--------------------------------------------------------------------
超過 680 kWh; 每 kWh $0.20971
----------------------------------------------------------------------
如果你比較在意用電管理,可以編寫程序來計算你的用電費用。程序清單 7.4 所示程序就是完成這一任務的第一步。
程序清單 7.4 electric.c 程序
----------------------------------------------------------------------
/* electric.c -- 計算用電帳目 */
#include <stdio.h>
#define RATE1 0.12589 // 第一個 360 kWh: 每 kWh $0.12589
#define RATE2 0.17901 // 下一個 320 kWh: 每 kWh $0.17901
#define RATE3 0.20971 // 超過 680 kWh; 每 kWh $0.20971
#define BREAK1 360.0 // 費率的第一個分界點
#define BREAK2 680.0 // 費率的第二個分界點
#define BASE1 (RATE1 * BREAK1) // 用電 360 kWh 的費用
#define BASE2 (BASE1 + (RATE2 * (BREAK2 - BREAK1))) // 用電 680 kWh 的費用
int main (void)
{
double kwh; // 用電的千瓦小時數
double bill; // 費用
printf ("Please enter the kwh used .\n");
scanf ("%lf",&kwh); // %lf 是 double 類型的說明符
if (kwh <= BREAK1)
bill = RATE1 * kwh;
else if (kwh <= BREAK2) // 用電量在 360 kWh 和 680 kWh 之間時
bill = BASE1 + (RATE2 * (kwh - BREAK1));
else
bill = BASE2 + (RATE3 * (kwh - BREAK2)); // 用電超過 680 kWh時
printf ("The charge for %.1f kwh is $%1.2f \n",kwh,bill);
system("PAUSE");
return 0;
}
下面是一個輸出示例:
Please enter the kwh used .
580
The charge for 580.0 kwh is $84.70
程序清單 7.4 用符號常量表示費率,以便這些常量可以很方便地放置在一起。這樣一旦電力公司改變費率(這是很可能的),就很容易更新這些被放在一起的費率。該清單也用符號表示了費率的分界點。它們也有可能改變。BASE1 和 BASE2 根據費率和分界點來表示。這樣,如果費率或分界點改變了,它們也會自動地更新。你可能回想起預處理器是不做計算的。程序中 BASE1 出現的地方將用 0.12589 * 360.0 代替。不用擔心;編譯器會求得表達式的數值 (45.3204)以便最終的程序代碼使用 45.3204而不是一個計算符。
程序的流程簡單明了的。該程序按照 kwh 的數值選擇三個公式中的一個。應該特別注意的是僅當 kwh 大于 360 時程序才到達第一個 else 。所以,像程序注釋所注明的那樣,else if (kwh <= BREAK2) 行實際上相當于要求 kwh 在 360 和 680 之間。同樣,僅當 kwh 超過 680, 才能夠到達最后一個 else 。 最后請注意, BASE1 和 BASE2 分別代表前 360 和 680千瓦時的總費用。因此,當用電量超過這些值時,僅需要加上額外的費用。
實際上,else if 是你已經學過的形式的一種變化。例如,該程序的核心部分只不過是下面語句的另一種寫法;
if (kwh <= BREAK1)
bill = RATE1 * kwh;
else
if (kwh <= BREAK2)
bill = BASE1 + RATE2 * (kwh - BREAK1);
else
bill = BASE2 + RATE3 * (kwh - BREAK2);
也就是說,程序所包含的一個 if else 語句是另一個 if else 語句的 else 語句部分。我們稱第二個 if else 語句被嵌套(nested)在第一個里面。回憶一下,整個 if else 結構作為一條語句,這就是為什么不必將被嵌套的 if else 放在花括號中。然而使用花括號可以更清楚地表明這種特殊格式的含義。
兩種形式完全等價。唯一的區別在于空格和換行的使用位置,而編譯器會忽略這些差別。盡管如此,第一種形式還是更好些,因為它更清晰地展示出你做出了三種選擇。這種格式更易于快速瀏覽程序時看清楚各個選擇。只在必要的時候使用縮排的嵌套格式,比如在必須判斷兩個單獨的量時。這種情形的一個例子是僅在夏季對超過 680 千瓦的用電量加收 10%的額外費用。
如下段所展示的,可以把多個(當然,需要在編譯器的限制范圍內)所需的 else if 語句連成一串使用。
if (score < 1000)
bonus = 0;
else if (score < 1500)
bonus = 1;
else if (score < 2000)
bonus = 2;
else if (score < 2500)
bonus = 4;
else
bonus = 6;
(這很可能是某個游戲程序的一部分,bonus 描述你為下一輪游戲所獲得的炸彈或食品的多少。)
說到編譯器的限制,C99 標準要求編譯器最少支持 127 層嵌套。
?
7.2.4 把 else 與 if 配對
當有眾多 if 和 else 的時候,計算機是怎樣判斷哪個 if 對應 哪個 else 的?例如,考慮下面的程序段:
if (number > 6)
if (number < 12)
printf (" You're close !\n");
else
printf (" Sorry,you lose a turn \n");
什么時候打印“Sorry,you lose a turn ”?是在 number 小于等于 6 時,還是在大于等于 12 的時候?換句話說,這個 else 對應第一個還是第二個 if 呢?答案是,else 對應第二個 if 。 也就是說,你可能得到下列響應
數字 響應
----------------------------------------
5 沒有任何響應
----------------------------------------
10 You're close
-----------------------------------------
15 Sorry,you lose a turn
----------------------------------------
規則是如果沒有花括號指明, else 與和它最接近的一個 if 相匹配。
例1 :
if (condition)
do this;
if (condition)
do this;
else
do this; else 與和它最接近的一個 if 相匹配。(即例中的最后一個 if)
例2:
if (condition)
{
do this;
if (condtion)
do this;
}
else
do this; 由于內部的 if 語句包含在花括號中,所以 else 與第一個 if 相匹配。
?
第一個例子的縮排使得 else 那會兒是與第一個 if 匹配的。但要記住,編譯是忽略縮排的。如果真的希望 else 與第一個 if 匹配,可以這樣寫:
if (number > 6)
{
if (number < 12)
printf (" You're close !\n");
}
else
printf (" Sorry,you lose a turn \n");
你可能會得到下面的響應;
數字 響應
----------------------------------------
5 You're close
----------------------------------------
10 You're close
-----------------------------------------
15 Sorry,you lose a turn
----------------------------------------
7.2.5 多層嵌套的 if
前面所看到的 if...else if...else 序列是嵌套 if 的一種形式,這是從一系列的二選一中進行選擇的。當進行了特定的選擇后又導致了額外的選擇時將使用另一種嵌套 if 。例如,一個程序可能用 if else 來區別不同收入的群體。
我們試著用這種形式的嵌套 if 來解決下面的問題:給定一個整數,顯示所有能帶除它的約數;如果沒有,則報告該數是個素數。
該問題在編寫代碼之前需要預先計劃好。首先,需要對程序進行總體設計。為了方便,程序需要用一個循環來使能輸入被測試的數。這樣,在希望檢驗一個新數的時候就無須每次都重新運行程序。我們已經為這種循環研究出一個模式:
prompt user // 提示用戶
while the scanf()return value is 1 //如果輸入的返回值為 1
analyze the number and report results //分析輸入的數值并檢查循環是否該終止
prompt user // 提示用戶
回憶一下,通過在循環判斷條件中使用 scanf(),程序試圖讀入一個數值并檢查循環是否應該終止。
下一步,需要計劃怎么來找到除數。或許最顯而易見的方法是這樣的:
for (div = 2; div < num ; div++)
if (num % div == 0)
printf ("%d is divisible by %d\n",num,div);
該循環檢查界于 2 到 num 之間的所有數,看它們是否可以整除 num。不幸的是,這種方法浪費了計算機的時間。我們可以做得更好。例如,考慮搜索 144 的約數。可以發現 144 % 2為 0,這意味著 144 能被 2 整除。如果明確地拿 144 除以 2 ,可以得到 72 ,這也是個約數,因為一次成功的
num % div 測試可能得到兩個約數而不是一個。然而真正的性能指標在于循環測試的改變。為了弄清這是怎樣工作的,看一下循環中所得到的成對的約數: 2 和 72,3 和 48, 4 和 36,8 和 18, 9 和 16, 12 和12, 19 和 9, 18 和 8,如此等等。哦,在得到 12 和 12 這對數后,又開始得到與已找到的相同的約數(以相反的次序)。無須循環到 143,在達到 12 后就可以停止。這就節省了大量的循環周期!
歸納以后,可以確定必須測試的數只要到 num 的平方根就可以了,而不必到 num 。對于像 9 這樣的數,這并不會節省很多時間,但對于像 10 000 這樣的數,差別就很大了。然而,我們不必在程序中計算平方根,而是像下面這樣描述判斷條件;
for (div = 2 ;(div * div) <= num; div++)
if (num % div == 0)
printf ("%d is divisble by %d and %d \n",num,div,num / div);
如果 num 為 144, 循環到 div = 12 終止。如果 num 為 145,循環運行到 div = 13 終止。
利用這種方法判勝于利用平方根判斷有兩個原因。第一,整數乘法比求平方根更快;第二,平方根的函數還沒正式介紹。
我們還需要提出兩個問題,然后才能準備開始編程。第一,如果測試的數是一個完全平方根怎么辦?報告 144 可被 12 和 12 整除顯然有些愚蠢,但可以用嵌套 if 語句來判斷 div 是否等于 num / div 。如果是,程序將只打印一個約數而不是兩個。
for (div = 2; (div * div)<= num; div++)
{
if (num % div == 0)
{
if (div * div != num)
printf ("%d is divisible by %d and %d \n",num,div,num/div);
else
printf ("%d is bivisible by %d \n",num,div);
}
}
--------------------------------------------------------
PS : 花括號
從技術角度講,if else 語句作為單個語句,所以不需要括上花括號。外部 if 也是單個語句,所以不需要花括號。然而,當語句很長時,花括號使人更容易清楚發生了什么,并且當后來將其他語句加到 if 或循環中時它們提供了保護。
----------------------------------------------------------
第二,怎樣知道一個數是素數呢?如果 num 是素數,程序流程永遠也進不了 if 語句。為了解決這個問題,可以在循環外設置一個變量為某一值,比方說 1 ,在 if 語句中將這個變量為 0 。那么,循環完成后,可以檢查該變量是否仍然是 1 。如果是,則從沒進入過 if 語句,這個數是素數。這樣的變量通常被稱為(flag)。
傳統上, C 習慣用 int 類型作為標志,但新型的 _Bool型變量極佳地符合了這種需求。而且,通過包含 stdbool.h 文件,可以用 bool 代替關鍵字 _Bool 表示這種類型,并用標識符 true 和 false 代替 1 和 0 。
程序清單 7.5 續保了所有這些想法。為了擴大滿園,程序用類型 long 代替 類型 int (如果系統不支持 _Bool 類型,可以讓 isPrime 使用 int 類型并用 1 和 0 代替 true 和 false 。
程序清單 7.5 divisors.c 程序
----------------------------------------------------------
/* divisors.c -- 使用嵌套 if 顯示一個數的約數 */
#include <stdio.h>
#include <stdbool.h>
int main ( void)
{
typedef int bool;
unsigned long num; // 要檢查的數
unsigned long div; // 可能的約數
bool isPrime; // 素數的標志
printf ("please enter an integer for analysis : ");
printf ("enter q to quit \n");
while (scanf ("%lu",&num) == 1)
{
for (div = 2, isPrime = true; (div * div)<= num; div++)
{
if ( num % div == 0)
{
if ((div * div) != num)
printf ("%lu is divisible by %lu and %lu \n",num,div,num/div);
else
printf ("%lu is divisible by %lu ",num,div);
isPrime = false;
}
}
if (isPrime)
printf ("%lu is prime \n",num);
printf ("please enter an integer for analysis : ");
printf (" enter q to quit \n");
}
printf ("Bye \n");
return 0;
}
注意,該程序在 for 循環控制表達式中使用了逗號運算符,以針對每個新輸入的數將isPrime 初始化為 true 。
下面是一個運行示例:
please enter an integer for analysis : enter q to quit
36
36 is divisible by 2 and 18
36 is divisible by 3 and 12
36 is divisible by 4 and 9
36 is divisible by 6 please enter an integer for analysis : enter q to quit
149
149 is prime
please enter an integer for analysis : enter q to quit
3077
3077 is divisible by 17 and 181
please enter an integer for analysis : enter q to quit
q
這個程序將會把 1 判斷為素數,而實際上, 1 不是素數。下一部分將要介紹的邏輯運算符將使你能夠把 1 排除在素數之外。
?
?
?
PS: 總結: 使用 if 語句進行選擇
關鍵字: if,else
總體注解:
下列每種形式中,語句部分可以是一個簡單語句或者一個復合語句。一個真表達式著它具有非零值。
-----------------------------------------------------------
形式1;
if (exprssion)
statement
如果 expression 為真 則執行 statement 。
-----------------------------------------------------------
形式2:
if (exprssion)
statement1
else
statement2
如果 exprssion 為真 執行 statement1;否則執行 statement2 。
------------------------------------------------------------
形式3:
if (exprssion1)
statement1
else if(exprssion2)
statement2
else
statement3
如果 exprssion1 為真 執行 statement1;如果 exprssion1 為假而 exprssion2 為真,則執行
statement2 ;否則,如果兩個表達式都為假,執行 statement3 。
例如;
if (legs == 4)
printf ("It might be a horse \n");
else if (legs > 4)
printf ("It is not a horse \n");
else /* case of legs < 4 */
{
legs++;
printf ("Now it has one more leg\n");
--------------------------------------------------------------
7.3 獲得邏輯性
對于 if 和 while 語句通常怎樣使用關系表達式作為判斷條件你已經了。有時你會覺得將兩個或多個表達式組合起來很有用處。例如,假設需要編寫一個程序,用來計算一個輸入的句子中除單引號和雙引號以外的字符出現了多少次。可以用邏輯運算符來實際該目的,可以用(英文的)句號(.)來標識一個兔子的結束,程序清單 7.6 用一個簡短的程序闡明了這種方法。
程序清單 7.6 chcount.c 程序
----------------------------------------------------------------------------
/* chcount.c -- 使用邏輯與運算符 */
#include <stdio.h>
#include <stdlib.h> /* 為了使用 system("PAUSE"); 功能 */
#define PERIOD '.'
int main (void)
{
int ch;
int charcount = 0;
while ((ch = getchar()) != PERIOD)
{
if (ch != '"' && ch!= '\'')
charcount++;
}
printf ("Ther are %d non-quote characters \n",charcount);
system("PAUSE");
return 0;
}
下面是一個運行實例:
I didn't raad the "I'm a programdding Fool" best seller.
Ther are 51 non-quote characters
首先,程序讀入一個字符并檢查它是否是一個句號,因為句號標志著一個句子的結束。接下來的語句中,使用了邏輯與運算符 && ,這是我們以前沒有遇到的。可以將該 if 語句翻譯為:“如果字符不是雙引號并且它不是單引號,那么 charcount 增加 1 。”
要使整個表達式為真,則兩個條件必須都為真。邏輯運算符的優先級低于關系運算符,所以不必使用圓括號組合子表達式。
C 有三個邏輯運算符,請參見表 7.3
表 7.3 C 的邏輯運算符
------------------------------------------------------
運算符 含義
-----------------------------------------------------
&& 與
----------------------------------------------------
|| 或
----------------------------------------------------
! 非
----------------------------------------------------
假如 exp1 和 exp2 是兩個簡單的關系表達式,例如 cat > rat 且 debt == 1000 。那么可以聲明如下:
· 僅當 exp1 和 exp2 都為真的時候, exp1 && exp2 才為真。
· 如果 exp1 或 exp2 為真或二者都為真, exp1 || exp2 為真。
· 如果 exp1 為假,則 !exp1 為真;并且如果 exp1 為真,則 !exp1 為假 。
下面是一個具體的例子:
· 5 > 2 && 4 > 7為假,因為只有一個子表達式為真。
· 5 > 2 || 4 > 7 為真,因為至少有一個子表達式為真。
· !(4 > 7) 為真,因為 4 不大于 7.
順便說說,最后一個不成功與下面的表達式等價:
4 <= 7
如果你與邏輯運算符不熟悉或感到很別扭,請記住;
(練習 && 時間) == 完美
?
7.3.1 改變拼寫法: iso646.h 頭文件
C 是在美國使用標準美工鍵盤的系統上發展而來。但在世界各地,并不是所有的鍵盤都有與美式鍵盤相同的符號。因此,C99 標準為邏輯運算符增加了可供選擇的拼寫法。它們在 iso646.h 頭文件里定義。如果包含了這個頭文件,就可以用 and 代替 &&, 用 or 代替 ||,用 not 代替 ! 。例如,下列語句:
if (ch != '"' && ch!= '\'')
charcount++;
可以重寫為以下方式
if (ch != '"' and ch!= '\'')
charcount++;
表 7.4 列出了這些選擇;很容易記住它們。事實上,你可能想知道為什么 C 不完全使用這些新術語。回答可能是 C 歷史上一直設法保持盡量少的關鍵字。參考資料7 “擴展的字符支持”中列出了我們還沒遇到過的一些運算符的更多可供選擇的拼寫法。
表 7.4 邏輯運算符的可選表示法
------------------------------------------------
傳統的 iso646.h
------------------------------------------------
&& and
-----------------------------------------------
|| or
-----------------------------------------------
! not
-----------------------------------------------
7.3.2 優先級
!運算符的優先級很高。它高于乘法運算,和增量運算符的優先級相同,僅次于圓括號。&&運算符的優先級高于 || ,這二者的優先級都低于關系運算符而高于賦值運算。因此,下列表達式:
a > b && b > c || b > d
將被認為是這樣的:
((a > b)&& (b > c)||(b > d)
也就是說,b 界于 a 和 c 之間,或者 b 大于 d 。
很多程序員都愿意使用圓括號,正如上面第二種寫法那樣,盡管這并不是必需的。這樣的話,即使你不能完全記住邏輯運算符的優先級,表達式的含義仍然很清楚的。
7.3.3 求值的順序
除了那些兩個運算符共享一個操作數的情況以外,C 通常不保證復雜表達式的哪個部分首先被求值。
例如在下面的語句里,可能先計算表達式 5 + 3 的值,也可能先計算 9 + 6 的值。
apples = (5 + 3)* (9 + 6);
C 語言允許這種不確定性,以便編譯器設計者可以針對特定的系統做出最有效率的選擇。一個例外是對邏輯運算符的處理。C 保證邏輯表達式是從左到右求值的。&& 和 || 運算符是序列的分界點,因此在程序從一個操作數前進到下一個操作數之前,所有的副作用都會生效。而且,C 保證一旦發現某個元素使表達式總體無效,求值將立刻停止。這些約定使像下面的這樣的結構成為可能;
while ((c = getchar()) != '' && c != '\n')
這個結構建立一個循環讀入字符,直到出現第一個空格符或換行符。第一個子表達式給 c 賦值,然后 c 的值被用在第二個子表達式中。如果沒有順序保障,計算機可能試圖在 c 被賦值之前判斷第二個子表達式。
下面是另外一個例子:
if (number != 0 && 12/number == 2)
printf ("The number is 5 or 6 \n");
如果 number 值為 0,那么第一個子表達式為假,就不再對關系表達式求值。這就避免了計算機試圖把 0 作為除數。很多語言都沒有這個特性,在知道 number 為 0 后,它們仍繼續后面的條件檢查。
最后,考慮這個例子:
while (x++ < 10 && x + y < 20)
&& 運算符是序列分界點這一事實保證了在對右邊表達式求值之前,先把 x 的值增加 1 。
PS: 總結:邏輯運算符和表達式
邏輯運算符:
邏輯運算符通常使用關系表達式作為操作數。! 運算符帶一個操作數。 其他兩個邏輯運算符帶有兩個操作數:一個在左邊,一個在右邊。
運算符 意義
&& 與
|| 或
! 非
邏輯表達式:
當且僅當兩個表達式都為真時,expression1 && expression2 為真。 如果其中一個為真或兩個表達式都為真, expression1 || expression2 為真。 如果 expression 為假,則 !expression 為真,反之亦然。
求值順序:
邏輯表達式是從左到右求值的。一旦發現有使表達式為假的因素,立即停止求值。
例如:
6 > 2 && 3 == 3 為真
!(6 > 2 && 3 == 3) 為假
x != 0 && (20 /5) < 5 只有當 x 不為 0 時,才計算第二個表達式的值
?
7.3.4 范圍
可以把 && 運算符用于測試范圍。例如,若要檢查 90 到 100 范圍內的得分,可以這樣做:
if (range >= 90 && raned <= 100)
printf (" Good show \n");
一定要注意避免效法像下面這樣的數學上常用的寫法:
if (90 <= raned <= 100) // 不! 不要這樣寫!
printf (" Good show \n");
問題在于該代碼是語義錯誤,而不是語法錯誤,所以編譯器并不會捕獲它(盡管可能會發出警告)。因為對 <= 運算符的求值順序是由左到右的,所以會把該測試表達式解釋為如下形式:
(90 <= range )<= 100
子表達式 90 <= range 的值為 1 (真)或 0 (假)。任何一個值都小于 100,因此不管 range 的值是什么,整個表達式總為真,所以需要使用 && 來檢查范圍。
大量現有代碼利用范圍測試來檢測一個字符是不是(比方說)小寫字母。例如,假設 ch 是個 char 變量:
if (ch >= 'a' && ch <= 'z')
printf ("That's a lowercase character \n");
這對于像 ASCII 那樣的字符編碼可以工作,因為在這種編碼中連續字母的編碼是相鄰的數值。然而,對于包括 EBCDIC 在內的一些編碼就不正確了。進行這種測試的移植性更好的方法是使用 ctype.h 系列(請參見 表 7.1 )中的 islower()函數:
if (islower(ch))
printf ("That's a lowercase charcter \n");
不管使用哪種特定的字符編碼,islower()函數都能很好地運行(不過,一些早期的實現沒有 ctype.h 系列的頭文件)。
?
7.4 一個統計字數的程序
現在我們已經有工具來編寫一個統計字數的程序了。統計字數的程序讀取輸入的字符并報告其中的單詞個數。處理時也可以統計字符個數和行數。我們來看看這樣一個程序包含哪些步驟。
首先,這個程序應該逐個讀取字符,并且應該有些方法判斷何時停止;第二,它應該識別并統計下列單位:字符,行和單詞。下面是偽代碼描述:
read a character // 讀取一個字母
while there is more input // 如果輸入的類型
increment character count // 是字符的字母計數加 1
if a line has been read increment line count // 如果是一行 行計數加1
if a word has been read increment word count // 如果是單詞 單詞計數加1
read next character //繼續讀入下一個
前面已經有輸入的循環的模型了:
while ((ch = getchar()) != STOP)
{
...
}
這里,STOP 代表通知輸入結束的 ch 取值。前面的示例程序已經使用了換行符和句號來用于該目的,但對于一個通用的單詞統計程序這兩個都不合適。現在我們暫且選擇一個在文本中不常見的字符(|)。在第 8 章“字符輸入/輸出和輸入確認”中,我們將會介紹一個更好的解決方案,以使程序既能處理文本文件又能處理鍵盤輸入。
現在來考慮一下循環體。因為程序使用 getchar()來輸入字符,所以可以在每個循環周期通過遞增一個計數器的值來統計字符。為了統計行數,程序可以檢查換行符。如果字符是換行符,程序就遞增行數計數器的值。有個問題是如果 STOP 字符出現在一行的中間該怎么辦。行數計數器應該不應該增加呢?一種做法是將它作為一個不完整行統計,也就是說,該行有字符而沒有換行符。可以通過追蹤一個字符來識別這種情況。如果 STOP 之前所讀入的最后一個字符不是換行符,就計為一個不完整行。
最棘手的部分是識別單詞。首先,必須明確定義一個單詞意味著什么。讓我們以一個相對簡單的方法將一個單詞定義為不包括空白字符(也就是說,沒有空格,制表符或換行符)的一系列字符。因此,“glymxch ”和 “r2d2”是單詞。一個單詞以程序首次遇到非空白字符開始,在下一個空白字符出現時結束。檢測非空白字符最簡單明了的判斷表達式是這樣的:
c != '' && c != '\n' && c != '\t' // 當 c 不是空白字符時,表達式為真
檢測空白字符最簡單明了的判斷是:
c == '' || c == '\n' || c = '\t' // 當 c 是空白字符時,該表達式為真
但是,使用 ctype.h 中的 isspace()函數會更簡單。如果該函數的參數是空白字符,它就返回真。因此如果 c 是空白字符,isspace(C) 為真; 而如果 c 不是空白字符,!isspace(c)為真。
為了知道一個字符是不是在某個單詞里,可以在讀入一個單詞的首字符時把一個標志(命名為 inword)設置為 1 。也可以在此處遞增單詞計數。
然后,只要 inword 保持為 1 (或真),后續的非空白字符就不標記為一個單詞的開始。到出現下一個空白字符時,必須將此標志重置為 0 (或假),并且程序準備搜索下一個單詞。轉換為偽代碼是這樣的:
if c is not whitespace and inword is false // 如果 c 不是空白字符和標志為假
set inveord to true and count the word // 將標志置1(或真)和單詞計數遞增
if c is whitespace and inword is true // 如果 c 是空白字符和標志為真
set inword to false // 將標志置0 (或假)
這種方法在每個單詞開始時將 inword 設為 1 (真),而在每個單詞結束時將其設為 0 (假)。僅在該標志從 0 變為 1 時對單詞計數。如果在你的系統上可以使用 _Bool 型變量,可以包含stdbool.h 頭文件并用 bool 作為 inword 的類型,取值分別為 true 和 false 。否則,就使用 int 類型, 取值為 0 和 1.
如果使用布爾型變量,通常的習慣是用變量自身的值作為判斷條件。也就是說用:
if (inword)
來代替
if (inword == true)
并且用
if(!inword)
來代替
if (inword == false)
依據是,如果 inword 為 true,則表達式 inword == true 結果為 true;而如果 inword 為false,則該表達式也為 false。因此,倒不只用 inword 作為判斷條件。與之類似,!inword 與表達式 inword == false 值相同(非真即false,非假即 true)。
程序清單 7.7 將這些思想(識別行,識別不完整行以及識別單詞)翻譯為 C 代碼。
程序清單 7.7 wordcnt.c 程序
-----------------------------------------------------------------
/* wordcnt.c -- 統計字符,單詞 和 行 */
#include <stdio.h>
#include <ctype.h> // 為 isspace()提供函數原型
#include <stdlib.h>
#define STOP '|'
int main (void)
{
char c; //讀入字符
char prev; //前一個讀入字符
long n_chars = 0L; //字符數
int n_lines = 0; //行數
int n_words = 0; //單詞數
int p_lines = 0; // 不完整的行數
bool inword = false; // 如果 c 在一個單詞中,則 inwrd 等于 true
printf ("Enter text to be analyzed(| to terminate) : \n");
prev = '\n'; // 用于識別完整的行
while ((c = getchar()) != STOP)
{
n_chars++; // 統計字符
if (c == '\n')
n_lines++; // 統計行
if (!isspace(c) && !inword)
{
inword = true; // 開始一個新單詞
n_words++; // 統計單詞
}
if (isspace(c) && inword)
inword = false; // 到達單詞的尾部
prev = c; //保存字符值
}
if (prev != '\n')
p_lines = 1;
printf (" characters = %d, words =%d, lined = %d",n_chars,n_words,n_lines);
printf (" partial lines = %d\n",p_lines);
system("PAUSE");
return 0;
}
下面是一個運行實例:
Enter text to be analyzed(| to terminate) :
Reason is a
Dowerful servant but
an inadequate msster.
|
characters = 55, words =9, lined = 3 partial lines = 0
程序用邏輯運算符把偽代碼翻譯為 C 代碼。例如:
if (!isspace(c) && !inword)
注意,!inword 等價于 inword == false 。整個判斷條件當然比單獨判斷每個空白字符要更可讀。
if (c != ' ' && c != '\n' && c != '\t' && !inword)
上面的任何一種形式都表示:“如果 c 不是空白字符,并且如果 c 不處于一個單詞里”。假如兩個條件都滿足,那么一定是新單詞的開始,所以遞增 n_words。如果處于一個單詞的中間,那么第一個條件是滿足的,但是 inword 為 true,所以 n_words 不遞增。
當遇到下一個空白字符時,將 inword 再次設為 false。檢查一下代碼,看看當兩個單詞之間有數個空格時,程序是否正確。第 8 章說明了怎樣修改這個程序以統計一個文件中的單詞。
7.5 條件運算符 ?:
C 提供一種簡寫方式來表示 if else 語句的一種形式。這被稱為條件表達式,并使用條件運算符
(?:)。這是個有三個操作數的分兩部分的運算符。回憶一下,有一個操作數的運算符稱為一元運算符,有兩個操作數的運算符稱為二元運算符。按照該慣例,有三個操作數的運算符就應該稱為三元運算符,條件運算符就是 C 的該類型的唯一的一個例子。下面是一個得到一個數的絕對值的例子:
x = (y < 0) ? -y : y;
在 = 和分號之間就是條件表達式。這個語句的意思是:“如果 y 小于 0 ,那么 x =-y ;否則,x = y 。以 if else 的說法,可以這樣表達:
if (y < 0)
x = -y;
else
x = y;
下面是條件表達式的一般形式;
expression1 ? expression2 : expression3
如果 expression1 為真(非零),整個條件表達式的值和 expression2 的值相同。如果
expression1 為假(零),整個條件表達式的值等于 expression3 的值。
當希望將兩個可能的值中的一個賦給時,可以使用條件表達式。典型的例子是將兩個值中最大的值賦給變量:
max = (a > b) ? a: b; (如果 a 大于 b 那么 max 等于 a ,否則等于 b)
通常,if else 語句能完成與條件運算符同樣的功能。但是,條件運算符語句更簡潔;并且,依賴編譯器,可以產生更精簡的程序代碼。
我們來看看程序清單 7.8 所示的一個噴漆程序的例子。這個程序計算向給定的平方英尺的面積涂油漆,全部涂完需要多少罐油漆。基本的數學法則很簡單;用平方英尺數除以每罐油漆能涂抹的平方英尺數。但是,假設結果是 1.7 罐會怎樣呢?商店整罐賣油漆,而不拆開賣,所以必須買兩罐。因此,程序在得到非整數罐的結果時應該進 1 。條件運算符常用于處理這種情況,而且在適當的時候也用來打印 can 或 cans
程序清單 7.8 paint.c 程序
---------------------------------------------------
/* paint.c --- 使用條件運算符 */
#include <stdio.h>
#define COVERAGE 200 /* 每罐油漆可噴平方英尺數 */
int main (void)
{
int sq_feet;
int cans;
printf ("Enter number of square feet to be painted : \n");
while (scanf("%d",&sq_feet) == 1)
{
cans = sq_feet / COVERAGE;
cans += ((sq_feet % COVERAGE == 0)) ? 0:1;// 輸入除常量的余數=0則cans+0 否則+1
printf ("You need %d %s of paint \n",cans, cans == 1? "can":"cans");
printf ("Enter next value(q to quit) :\n");
}
return 0;
}
下面是一個運行示例:
Enter number of square feet to be painted :
200
You need 1 can of paint
Enter next value(q to quit) :
215
You need 2 cans of paint
Enter next value(q to quit) :
q
因為程序使用 int 類型,所以除法被截斷了;也就是說,215/200 的結果是 1 。因此, cans 被四舍五入為整數部分。如果 sq_feet % COVERAGE 等于 0, 那么 sq_feet 被 COVERAGE 整除, cnas值不變,否則有余數,所以加上 1 。這由下列語句完成的:
cans += ((sq_feet % COVERAGE == 0)) ? 0:1;
它將 cans 加上 += 右邊表達式的值。右邊的表達式是個條件表達式,值為 0 或 1,依賴于
sq_feet 是否被 COVERAGE 整除。
printf()函數最終的參數也是一個條件表達式
cans == 1? "can":"cans");
如果 cans 的值是 1,使用字符串 can ,否則使用字符串 cans 。這表明條件運算符也可以使用字符串作為它的第二和第三個操作數 。
-------------------------------------------------------
總結: 條件運算符
條件運算符:
?:
總體注解:
這個運算符帶有三個操作數,每個操作數都是一個表達式。它們如下排列:
expression1 ? expression2 : expression3;
如果 expression1 為真,它的值為 expression2 ,否則為 expression3.
例如:
(5 > 3) ? 1: 2 // 5 > 3 為真,所以結果為 1
(3 > 5) ? 1: 2 // 3 > 5 為假,所以結果為 2
(a > b)? a: b // a > b 為真,取值a, 為假的話 取值 b
?
7.6 循環輔助手段: continue 和 break
一般說來,進入循環體以后,在下次循環判斷之前程序執行循環體中所有的語句。continue 和 break 語句使你可以根據循環體內進行的判斷結果來忽略部分循環甚至終止它。
7.6.1 continue 語句
該語句可以用于三種循環形式。當運行到該語句時,它將導致剩余的迭代部分被忽略,開始下一次迭代。如果 continue 語句處于嵌套結構中,那么它僅僅影響包含它的最里層的結構。讓我們在程序清單 7.9 簡短程序中來試驗一下 continue 。
程序清單 7.9 skippart.c 程序
-----------------------------------------------------------------------
/* skippart.c --- 使用 continue 跳過部分循環 */
#include <stdio.h>
int main (void)
{
const float MIN = 0.0f;
const float MAX = 100.0f;
float score;
float total = 0.0f;
int n = 0;
float min = MAX;
float max = MIN;
printf (" Enter the first score (q to quit) :");
while (scanf("%f",&score) == 1)
{
if (score < MIN || score > MAX)
{
printf ("%0.1f is an invalid value. Try again :",score);
continue;
}
printf (" Acceptiong %0.1f :\n",score);
min = (score < min) ? score: min;
max = (score > max) ? score: max;
total += score;
n++;
printf ("Enter next score (q to quit) : ");
}
if (n > 0)
{
printf (" Average of %d scores is % 0.1f.\n",n,total/n);
printf ("Low = % 0.1f, high = %0.1f \n",min,max);
}
else
printf ("No valid scores were entered .\n");
system("PAUSE");
return 0;
}
在程序清單 7.9 中,while 循環讀取輸入內容,直到輸入非數字數據。循環里的 if 語句篩選出無效的分數值。比如,如果輸入 188 ,那么程序就會說明: 188 is an invalid value 。然后,continue 語句導致程序跳過循環其余的用于處理有效輸入的部分。程序開始下一個循環周期,試圖讀取下一個輸入值。
注意,有兩種方法可以避免使用 continue 。 一種是省去 continue ,將循環的剩余部分放在一個 else 代碼塊中:
if (score < 0 || score > 100)
/* printf () 語句 */
else
{
/* 語句 */
}
或者用這種格式來代替:
if (score >= 0 && score <= 100)
{
/* 語句 */
}
在這種情況中使用 continue 的一個臼是可以在主語句組中消除一級縮排。當語句很長或者已經有很深的嵌套時,簡練可以增強可讀性。
continue 的另一個用處是作為占位符。例如,下面的循環讀取并丟棄輸入,直到一行的末尾(包括行尾字符):
while (getchar() != '\n')
;
當程序已經從一行中讀取了一些輸入并需要跳到下一行的開始時,使用上面的語句很方便。問題是孤立的分號難以被注意。如果使用 continue, 代碼就更具可讀性,如下所示:
while ((ch = getchar()) != '\n')
{
if (ch == '\t')
continue;
putchar(ch);
}
該循環跳過制表符,并且僅當遇到換行符時退出。它可以更簡潔地這樣表示:
while ((ch = getchar()) != '\n')
if (ch != '\t')
putchar(ch);
通常,在這種情況下,可以把 if 的判斷取逆以消除對 continue 的需求。
你已經看到了 continue 語句導致循環體的剩余部分被跳過。那么在什么地方繼續循環呢?對于 while 和 do while 循環,continue 語句之后發生的動作是求循環判斷表達式的值。例如,考慮下列循環:
count = 0;
while (count < 10);
{
ch = getchar();
if (ch == '\n')
continue;
putchar(ch);
count++;
}
它讀入 10 個字符(換行符除外,因為當 ch 為換行符時會跳過 count++; 語句)并回顯它們,其中不包括換行符。continue 語句被執行時,下一個被求值的表達式是循環判斷條件。
對于 for 循環,下一個動作是先求更新表達式的值,然后再求循環判斷表達式的值。例如,考慮下列循環:
for (count = 0; count < 10; count++)
{
ch = getchar();
if (ch == '\n')
continue;
putchar(ch);
}
在本例中,當 continue 語句被執行時,首先遞增 count,然后把 count 與 10 相比較。因此,這個循環的動作稍稍不同于 while 循環的例子。像前面那樣,僅僅顯示非換行符,但這時換行符也被包括在計數中,因此它讀取包含換行符在內的 10 個字符。
7.6.2 break 語句
循環中的 break 語句導致程序終止包含它的循環,并進行程序的下一階段。在程序清單 7.9 中,如果用 break 代替 continue ,那么(比方說)在輸入了 188 的時候,不是跳到下一個循環周期,而是導致循環退出。圖 7.4 比較了 continue 和 break 。如果 break 語句位于嵌套循環里,它只影響包含它的最里層的循環。
圖 7.4
-----------------------------------------------------------------------------------------
-------------------
| |
while ((ch + getchar()) != EOF) while ((ch = getchar()) != EOF) |
{ { |
blahblah(ch); blahblah(ch); |
if (ch == '\n'); if (ch == '\n'); |
break; ----------- continue; >---------------------
yakyak(ch); | yakyak(ch);
} | }
blunder(n,m); <------- blunder(n,m);
比較 break 和 continue
------------------------------------------------------------------------------------------
有時,break 被用于在出現其他原因時退出循環。程序清單 7.10 用一個循環來計算矩形的面積。如果用戶輸入一個非數字作為矩形的長度或寬度,那么循環終止。
程序清單 7.10 break.c 程序
----------------------------------------------------
/* break.c --- 使用 break 退出循環 */
#include <stdio.h>
int main (void)
{
float length,width;
printf (" Enter the length of the rectangle : \n");
while ( scanf ("%f",&length) == 1)
{
printf (" Length = %0.2f; \n",length);
printf (" Enter its width : \n");
if (scanf ("%f", &width) != 1)
break;
printf (" Width = %0.2f; \n",width);
printf (" Area = %0.2f :\n",length * width);
printf (" Enter the length of the rectangle :\n");
}
printf ("Done \n");
return 0;
}
也可以這樣控制循環:
while (scanf ("%f %f",&length, &width) == 2)
但是,使用 break 可以使單獨回顯每個輸入值方便。
和 continue 一樣,當 break 使代碼更復雜時不要使用它。例如,考慮下列循環:
while ((ch = getchar()) != '\n')
{
if (ch == '\t')
break;
putchar(ch)'
}
如果兩個判斷都在同一個位置,邏輯就更清晰了:
while ((ch = getchar()) != '\n' && ch != '\t')
putchar(ch);
break 語句實質上是 switch 語句的附屬物,這在后面討論。
break 語句使程序直接轉到緊接著該循環后的第一條語句去執行;在 for 循環中,與 continue 不同,控制段的更新部分也將被跳過。嵌套循環中的 break 語句只是使程序跳出里層的循環,要跳出外層的循環則還需要另外一個 break 語句。
int p,q;
scanf("%d",&p);
while (p > 0)
{
printf ("%d\n",p);
scanf ("%d",&q);
while ( q > 0)
{
printf ("%d\n",p * q);
if ( q > 100)
break; // 跳出里層循環
scanf ("%d",&q);
}
if ( q > 100)
break; // 跳出外層循環
scanf ("%d",&p);
}
?
7.7 多重選擇: switch 和 break
使用條件運算符和 if else 結構可以很容易地編寫從兩個選擇中進行選擇的程序。然而,有時程序需要從多個選擇中選擇一個。可以利用 if else if...else 來這樣做,但多數情況下,使用 C 的switch 語句更加方便。程序清單 7.11 舉例說明了 switch 語句是怎樣工作的。該程序讀入一個字符,然后相應地輸出以該字符開關的動物名稱。
程序清單 7.11 animals.c 程序
--------------------------------------------------------
/* animals.c ---- 使用 switch 語句 */
#include <stdio.h>
#include <ctype.h>
int main (void)
{
char ch;
printf ("Give me a letter of the alphabet,and I will give ");
printf (" an animal name\nbeginning with that letter \n");
printf (" Please type in a letter : type # to end my act .\n");
while ((ch = getchar()) != '#')
{
if ('\n' == ch)
continue;
if (islower(ch)) // 只識別小寫字母
switch(ch)
{
case 'a':
printf ("argali,a wild sheep of Asia \n");
break;
case 'b':
printf ("babirusa,a wild pig of Malay \n");
break;
case 'c':
printf (" coati,racoonlike mammal \n");
break;
case 'd':
printf ("desman,aquatic,molelike critter \n");
break;
case 'e':
printf (" echidna,the spiny anteater \n");
break;
case 'f':
printf (" fisher,brownish marten \n");
break;
default:
printf (" That's a stumper \n");
} // switch 語句結束
else
printf (" I recognize only lowercase letters \n");
while (getchar() != '\n')
continue; // 跳過輸入行的剩余部分
printf (" Please type another letter of a #\n");
} // while 循環結束
printf ("bye \n");
return 0;
}
我們很懶散地只到 f 就停止了,但是后面可以依此類推。
本程序的兩個主要特點是 switch 讒的使用和輸入的處理。首先看看 switch 語句怎樣工作。
7.7.1 使用 switch 語句
緊跟在單詞 switch 后的圓括號里的表達式被求值。在這里,它就是剛剛輸入給 ch 的值。然后程序掃描標簽(label)列表(case'a':, case'b':,以此等等),如果有被標記為 default:的標簽行,程序就跑到該行;否則,程序繼續處理跟在 switch 語句之后的語句。
break 語句有什么作用呢?它導致程序脫離 switch 語句,跳到 switch 之后的下一條語句(請參見圖 7.5)。如果沒有 break 語句,從相匹配的標簽到 switch 末尾的每一條語句都被處理。例如,如果把程序里面的 break 去掉,然后輸入字母 d 來運行程序,會得到如下交互結果:
從 case'd': 到 switch 結尾的所有語句都被執行了。
順便提一下,break 語句用于循環和 switch 中,而 continue 僅用于循環。但是,如果 switch 語句位于一個循環中,則可以把 continue 用于 switch 語句的一部分。在這種情況下,就像在其他的循環中一樣,continue 導致程序跳過該循環的其余部分,其中包括 switch 的其余部分。
如果你熟悉 pascal,就會發覺 switch 語句和 pascal 的 case 語句很相似。最大的差別在于,如果僅希望處理某個帶有標簽的語句,switch 語句要求使用 break。另外,不能在 C 的 case 中使用一個范圍。
圓括號中的 switch 判斷表達式應該具有整數值(包括 char 類型)。case 標簽必須是整型(包括 char)常量或者整數常量表達式(僅包含整數常量的表達式)。不能用變量作為 case 標簽。因而,switch 結構是這樣的:
switch (integer expression)
{
case constant1:
statements -- 可選
case constant2:
statements -- 可選
default:
statements -- 可選
}
?
7.7.2 只讀取一行的首字符
animals.c 的另一個特點是它讀取輸入的方法。在運行示例中你可能已經注意到,當輸入 dab 時,僅僅處理了第一個字符。這種特性在期望響應單字符的交互式程序中通常很合適。產生這種動作的是下面的代碼:
while (getchar() != '\n');
continue; // 跳過輸入行的剩余部分
這個循環從輸入讀取字符,直到出現由回車鍵產生的換行字符。注意,函數返回值沒有被賦給 ch ,因此,字符僅被讀取并丟棄。因為最后一個被丟棄的字符是換行符,所以下個讀入的字符是下一行的首字符。在外層 while 循環中,由 getchar() 讀取它并將其值賦給 ch 。
假設用戶開始時按下了回車鍵,以致遇到的第一個字符是換行符。下面的代碼處理這種可能:
if (ch == '\n')
continue;
?
7.7.3 多重標簽
如程序清單 7.12 所示,可以對一個給定的語句使用多重 case 標簽;
程序清單 7.12 vowels.c 程序
-------------------------------------------------------------------------
/* vowels.c -- 使用多重標簽 */
#include <stdio.h>
int main (void)
{
char ch;
int a_ct, e_ct, i_ct, o_ct, u_ct;
a_ct = e_ct = i_ct = o_ct = u_ct = 0;
printf (" Enter some text: enter # to quit \n");
while ((ch = getchar()) != '#')
{
switch(ch)
{
case 'a':
case 'A': a_ct++;
break;
case 'e':
case 'E': e_ct++;
break;
case 'i':
case 'I': i_ct++;
break;
case 'o':
case 'O': o_ct++;
break;
case 'u':
case 'U': u_ct++;
break;
default : break;
} // switch 語句結束
} // while 循環結束
printf (" number of vowels : A E I O U \n");
printf (" %4d %4d %4d %4d %4d\n",a_ct, e_ct, i_ct, o_ct, u_ct);
return 0;
}
假定 ch 是字母 i ,則 switch 語句定位到標簽為 case 'i': 的位置。因為沒有 break 同該標簽相關聯所以程序流程繼續前進到下一個語句,即 i_ct++; 如果 ch 是 I ,程序流程就直接寫作到那條語句。本質上,兩個標簽都指向相同的語句。
嚴格地講,case 'U' 的 break 語句并不是必需的,因為即便沒有這個 break,程序還是會進行
switch 結構的下一個語句,也就是 default 情況下的 break 語句。因此, case 'U' 的 break 語句可以去掉以縮短代碼。另一方面,如果后面還需要再添加其他的情況(例如,你可能需要把 y 計為元音),那么現在保留這個 break 可以防止你到時候記了添加。
下面是一個運行示例:
Enter some text: enter # to quit
I see under the overseer.#
number of vowels : A E I O U
0 7 1 1 1
在這個特例中,可以通過使用 ctype.h 系列 (表 7.2 )中的 toupper()函數在進行判斷之前將所有的字母轉換為大寫字母來避免多重標簽。
while ((ch = getchar()) != '#')
{
switch(ch)
{
case 'a':
case 'A': a_ct++;
break;
case 'e':
case 'E': e_ct++;
break;
case 'i':
case 'I': i_ct++;
break;
case 'o':
case 'O': o_ct++;
break;
case 'u':
case 'U': u_ct++;
break;
default : break;
} // switch 語句結束
} // while 循環結束
或者,如果希望保留 ch 的值不變化,可以這樣使用該函數:
switch (toupper(ch))
?
------------------------------------------------------------------------
PS 總結: 使用 switch 進行多重選擇
關鍵字:
switch
總體注解:
程序控制按照 expression 的值跳轉到相應的 case 標簽處。然后程序流程繼續通過所有剩余的語句,直到再次由 break 語句重定向。 expression 和 case 標簽必須都是整型值(包括類型 char ),那么控制定位到標簽為 default的語句,如果它存在的話。否則,控制傳遞給緊跟著 switch 語句的下一條語句。
格式:
switch (expression)
{
case label1: statement1 // 使用 break 跳至 結尾處
case label2:statement2
default: statement3
}
可以存在兩以上的標簽語句,并且 default 語句是可選的。
例如:
switch (choice)
{
case 1:
case 2: printf (" Darn tootin \n");
case 3: printf ("Quite right \n");
case 4: printf (" Good show \n");
default: printf ("Have a nice day \n");
}
如果 choice 為整型值 1 或 2,則打印第一條消息。如果它的值為 3,則打印第二條和第三條消息(因為在 case 3 后沒有 break 語句,所以流程繼續到隨后的語句)。如果它的值為 4,則打印第三條消息。對于其他值,僅打印最后一條消息。
?
7.7.4 switch 和 if else
什么時候該使用 switch ,而什么時候又該使用 if else 結構呢?通常是沒有選擇的。如果選擇是基于求一個浮點型變量或表達式的值,就不能使用 switch。如果變量必須落入某個范圍,也不能很方便地使用 switch 。這樣寫是很簡單的:
if ( integer < 1000 && integer > 2)
很不幸,用一個 switch 語句覆蓋該范圍將涉及為從 3 到 999 的每個整數建立 case 標簽。然而,如果可以使用 switch,程序通常運行得稍快點,而且占據較小的代碼。
?
7.8 goto 語句
早期版本 BASIC 和 FORTRAN 所依賴的 goto 語句在 C 語言中是有效的。然而,不同于那兩種語言,C 沒有它也可以工作得相當好。Kernighan 和 Ritchie 認為 goto 語句“非常容易被濫用”,并且建議“要謹慎使用,或者根本不用”。我們首先介紹怎樣使用 goto,然后說明為什么通常不需要使用它。
goto 語句包括兩個部分:goto 和一個標簽名稱。標簽的命名遵循與命名變量相同的約定,如下例所示:
goto part2;
為使上述語句工作,函數必須包含由 part2 標簽定位的其他語句。這可以通過以標簽名緊跟一個冒號來開始一條語句完成:
part2: printf ("Refined analysis: \n");
避免 goto
原則上,C 程序根本不需要使用 goto 語句。但是如果你有使用早期版本 FORTRAN
或 BASIC --- 這兩種語言都需使用 goto 的背景,可能會有依賴使用 goto 的開發設計習慣。為幫你克服這種依賴性,我們將略述一些常見的 goto 情形,然后展示一個 C 習慣的方式。
---------------------------------------------------
1. 處理需要多條語句的 if 情形:
if (size > 12)
goto a;
goto b;
a: cost = cost * 1.05;
flag = 2;
b: bill = cost * flag;
在舊式風格的 BASIC 和 FORTRAN 中,只有直接跟在 if 條件后的單條語句隸屬于該 if 。沒有代碼塊或復合語句的規定。我們已經將這種模式轉換為與 C 等價的模式。使用復合語句或代碼塊的標準 C 方法更易于使用:
if ( size > 12)
{
cost = cost * 1.05;
flag = 2;
}
bill = cost * flag;
-------------------------------------------------------
2. 二中選一;
if (ibex > 14)
goto a;
sheds = 2;
goto b;
a: sheds = 3;
b: help = 2* sheds;
C 語言可以使用 if else 結構更清晰地表示這種選擇:
if (ibex > 14)
sheds = 3;
else
sheds = 2;
help = 2 * sheds;
實際上,新版本的 BASIC 和 FORTRAN 已經將 else 加入到新的語法中。
---------------------------------------------------------------------
3. 建立不確定循環:
reabin : scanf ("%d",&score);
if (score < 0)
goto stage2;
lots of statements;
goto reabin;
stage2: more stuff;
用 while 循環代替
while ((scanf ("%d",&score)) <= 0)
{
lots of statements;
scanf ("%d",&score);
}
more stuff;
-------------------------------------------------------------------------
4. 跳到循環末尾并開始下一輪循環: 用 continue 代替。
-------------------------------------------------------------------------
5. 跳出循環: 用 break 代替。實際上,break 和 continue 是 goto 的特殊形式。使用它們的好處是它們的名字表明它們的意味著什么;并且,因為它們不使用標簽,所以不存在放錯標簽位置的潛在危險。
---------------------------------------------------------------------------
6. 胡亂地跳轉到程序的不同部分: 千萬不要!!
---------------------------------------------------------------------------
但有一種 goto 的使用被許多 C 專業人員所容忍:在出現故障時從一組嵌套循環中跳出(單條 break 僅僅跳出最里層的循環)。
while (funct > 0)
{
for ( i=1; i <= 100; i++)
{
for (j = 1; j <= 50; j++)
{
statements galore;
if (bit trouble)
goto help;
statements;
}
more statements;
}
yet more statemens;
}
and more statements;
help: bail out;
正如從其他的例子可以看到的,可供選擇的形式比 goto 形式更清晰。當這幾種情形混合在一起時,這種差異甚至變得更明顯。哪些 goto 協助 if ,哪些 goto if else ,哪些 goto 控制循環,哪些只是因為你的程序已經無路可走才放在那里的?過度地使用 goto ,會引起程序流程的錯綜復雜。如果不熟悉 goto ,要不使用它;如果已經習慣于使用它,試著訓練自己不使用。具有諷刺意味的是, C 不需要 goto ,卻有一個比大多數語言更好的 goto ,因為它允許你在標簽中使用描述性的單詞而不是數字。
?
PS: 總結: 程序跳轉
關鍵字:
break,continue,goto
總體注解:
下面三條指令導致程序流程從程序的一個位置跳轉到另一個位置
-----------------------------------------------------------------------
break命令:
break 命令可以與三種循環形式中的任何一種以及 switch 語句一起使用。它導致程序控制跳過包含它的循環或 switch 語句的剩余部分,繼續執行緊跟在循環或 switch 后的下一條命令。
例如:
switch (number)
{
case 4: printf (" Thas's a good choice \n");
break;
case 5: printf (" That's a fair choice \n");
break;
default: printf (" Thas's a poor choice \n");
}
---------------------------------------------------------------------------------
continue 命令:
continue 命令可以與三種循環形式中的任何一種一起使用,但不能和 switch 語句一起使用。它導致程序控制跳過循環中的剩余語句。對于 while 或 for 循環,開始下一個循環周期。對于 do while 循環,對退出條件進行判斷,如果必要,開始一個循環周期。
例如:
while ((ch = getchar()) != '\n')
{
if (ch == ' ')
continue;
putchar(ch);
chcount++;
}
這段代碼回顯并統計非空格字符。
----------------------------------------------------------------------------
goto命令:
goto 語句導致程序控制跳轉到由指定標簽定位的語句。冒號用來將被標記的語句同它的標簽相分隔。標簽名遵循變量的命名規則。被標記的語句可以出現在 goto 之前或之后。
格式:
goto label:
.
.
.
label;statement
例如:
top: ch =getcahr();
.
.
.
if (ch != 'y');
goto top;
?
7.9 關鍵概念
智能的一個體現方面是根據環境調節反應的能力。所以,選擇語句是開發具有智能行為程序的基礎。在 C 中, if ,if else 和 switch 語句,連同條件運算符 (?:) 一起實現了選擇。
if 和 if else 語句使用一個判斷條件來決定執行哪條語句。任何非零值被視為 true,零值被視為 false。 典型地,判斷包括關系表達式(它比較兩個值)以及邏輯表達式(它使用邏輯運算符組合或更改其他表達式)。
需要牢記的一條通用規則是,如果想要判斷兩個條件,應該使用邏輯運算符將兩個完整的判斷表達式連接起來。例如,發下兩個嘗試是錯誤的。
if (a < x < z) // 錯誤。沒有邏輯運算符
if (ch != 'q' && != 'Q') // 錯誤,缺少完整的判斷
記住,正確的方法是用邏輯運算符將兩個關系表達式連接起來;
if (a < x && x < z) // 使用 && 組合兩個表達式
if (ch != 'q' && ch != 'Q') // 使用 &7 組合兩個表達式
最近兩章所介紹的控制語句使你能夠處理比在這之前所處理的更加強大和更具挑戰性的程序。只要將這些章節的一些例子與前些章節的相比較,你就可以看出這一點。
?
7.10 總結
本意給出了相當多的要回顧的主題,那么讓我們來看看。
if 語句利用判斷條件來控制程序是否執行緊跟在判斷條件后的一個簡單語句或代碼塊。如果判斷表達式為非零值,就執行語句;如果為零值,則不執行語句。
if else 語句使你能夠從兩個選項中進行選擇。如果判斷條件為非零值,就執行 else 之前的語句。如果判斷表達式的結果為非零值,就執行 else 之前的語句。如果判斷表達式的結果為零值,執行緊跟在 else 之后的語句。通過緊跟在 else 語句之后使用另一個 if 語句,可以建立在一系列可供選擇的事物中進行選擇的結構。
判斷條件通常是一個關系表達式,也就是用一個關系運算符構成的表達式,例如 < 或者 == 。利用
C 的邏輯運算符,可以組合多個關系表達式以創建更復雜的判斷。
使用條件運算符 (?:) 可以產生一個表達式,這樣的表達式在多數情況下比 if else 語句提供更加簡潔的二中選一。
ctype.h 系列字符函數(例如 isspace() 和 isalpha())為創建基于分類字符的判斷表達式提供了使得的工具。
switch 語句使你能夠從一系列以整數值為標簽的語句中進行選擇。如果緊跟在 switch 關鍵字后的判斷條件的整數值與某標簽相匹配,執行就定位到由該標簽定位的語句。然后執行繼續完成緊跟在該標簽語句后的語句,直到遇到一個 break 語句。
break ,continue 和 goto 是跳轉語句,導致程序流程跳轉到程序的其他位置。break 語句導致程序跳轉到緊跟在包含它的循環或 switch 末尾的下一條語句。continue 語句導致程序跳過包含它的循環的剩余部分,開始下一下循環周期。
?
7.11 復習題
----------------------------------------------------------
1. 確定哪個表達式為 true,哪個為 false。
a. 100 > 3 && 'a' > 'c' // false 因為不確定 a 是否大于 c &&是要表達式都為真才為真
b. 100 > 3 || 'a' > 'c' // true 因為 || 只要一個表達式為真 即為真
c. !(100 > 3) // false 因為 ! 表達式為真即為假,或相反
-------------------------------------------------------------------
2. 構造一個表達式來表示下列條件:
a. number 等于或大于 90 ,但是小于 100 // number >= 90 && number < 100;
b. ch 不是字符 q 也不是字符 k // ch != 'q' && ch != 'k';
c. number 界于 1 到 9 之前(包括 1 和 9 ),但是不等于 5 。
// number >= 1 && number <=9 && number != 5;
d. number 不在 1 到 9 之間 // number < 1 || number > 9 ;
--------------------------------------------------------------------------
3. 下面程序中的關系表達式過于復雜,并有些錯誤,請簡化并改正它。
#include <stdio.h>
int main (void) // 1
{ // 2
int weight,height; // weight 以磅為單位, height 以英寸為單位 // 3
// 4
scanf ("%d,weight,height); // 5
if (weight < 100 && height > 64) // 6
if ( height >= 72) // 7
printf ("you are very tall for your weighe \n"); // 8
else if (height < 72 && >64) // 9
printf ("you are tall for your weight \n"); //10
else if (weight > 300 && !(weight <= 300)) // 11
&& height < 48) // 12
if (!(height >= 48) // 13
printf ("you are qutie short for your weight \n"); //14
else // 15
printf ("your weight is ideal \n"); // 16
return 0; // 17
}
答:
第5行: 應該是 scanf("%d %d",&weight,&height); 在 scanf()中不要忘記使用 & 運算符。這一行前面也應該有提示輸入的語句。但第 6 行已經保證 heigh > 64,因此,不需要任何測試,并且 if
else 應該是 else 。
第9行; 它的意思是 (height < 72 && height > 64)。但是表達式的第一部分是不必要的,因為既然程序已經到達了這個 else if ,那么 height 必然小于 72 。因此一個簡單的 (height > 64)就可以了。
第11行; 條件冗余;第二個子表達式(weight 不是小于或等于300的)與第一個子表達式意義相同。所需要的只是一個簡單的( weight > 300)。但是這里還有更多的問題。第11行屬于一個不正確的if!
很明顯,這個 else 是與第 6行中的 if 相匹配的。但是根據 if 的“最接近規則”,它會與第 9行的 if 相匹配。因此會在 weight 小于 100 并且 height 小于或等于 64 時到達第11行。這就使得在到達該語句時 weight 不可能超過 300 。
第 7 到 9 行: 應該用花括號括起來。這樣第 11 行就會是這第 6行而不是第 9行的可選情況。而如果第 9行的 else if 由一個簡單的 else 替代了,就不再需要花括號了。
第 13行: 應該簡化為 if (height > 48)。其實這一行完全可以忽略,因為第 12行已經作了這種測試。
第 15行: 這個else 與第 13行的 if 相匹配。把第 13行和 14行括在花括號中可以強制使這個 else與第 11 行的 if 相匹配。或者,按照建議,簡單地刪掉第 13行。
下面是一個正確的版本;
#include <stdio.h>
int main (void)
{
int weight,heght; /* weight 以磅為單位,height 以英寸為單位 */
printf ("Enter your weight in pounds and ");
printf ("your height in inches \n");
scanf ("%d %d",&weight,%height);
if (weight < 100 && height > 64)
if (height >= 72)
printf ("you are very tall for your weight \n");
else
printf ("you are tall ofr your weight \n");
else if (weight > 300 && height < 48)
printf ("you are quite short for your weight \n"):
else
printf ("your weight is ideal \n");
return 0;
}
-----------------------------------------------------------------------------------------
4. 下列每個表達式的數值是多少?
a. 5 > 2 // 表達式為真 所以為 1
b. 3 + 4 > 2 && 3 < 2 // 3 < 2 不對,右邊表達式為假 && 既然都為假 所以值為 0
c. x >= y || y > x // 表達式一個為真 即為真, 所以值為 1
d. d = 5 + (6 > 2) // d = 6 因為 (6>2)為真 值為1 5+1=d 即 6
e. 'X' > 'T' ? 10:5 // 值為 10 因為判斷的條件為真
f. x > y ? y > x: x > y // 值為 0 因為如果 x > y 為真那么表達式的值就是 y > x 這種 情況下它就為假或0 ,如果 x > y 為假,表達式的值就是 x > y
這種情況下它也是為假或0.
----------------------------------------------------------------------------------------
5. 下列程序將打印出什么?
#include <stdio.h>
int main (void)
{
int num;
for (num = 1; num <= 11; num++)
{
if (num % 3 == 0)
putchar ('$');
else
putchar ('*');
putchar ('#')
printf ('%');
}
putchar ('\n');
return 0;
}
答: *#%*#%$#%*#%*#%$#%*#%$#%*#%
--------------------------------------------------------------------------------
6. 下列程序將打印出什么?
#include <stdio.h>
int main (void)
{
int i = 0;
while ( i < 3){
switch (i++){
case 0: printf ("fat");
case 1: printf ("hat");
case 2: printf ("cat");
default : printf("Oh no!");
}
putchar ('\n');
}
return 0;
}
答:
fathatcatOh no!
hatcatOh no!
catOh no!
注; 第一次 i = 0 i++ 會等于1 但是是先選擇 case 0 顯示后 值才為 1
----------------------------------------------------------------------------------
7. 下列程序有什么錯誤 ?
#include <stdio.h>
int main (void)
{
char ch;
int lc = 0; /* 統計小寫字符 */
int uc = 0; /* 統計大寫字符 */
int oc = 0; /* 統計其他字符 */
while ((ch = getchar()) != '#')
{
if ('a' <= ch >= 'z')
lc++;
else if (!(ch <'A') || !(ch >'Z')
uc++;
oc++;
}
printf ("%d lowercase, %d uppercase, %d other ,lc,uc,oc);
return 0;
}
答
表達式 'a'<= ch >='z' 應該被寫成這樣:
ch >= 'a' && ch <= 'z'
或者用一種更簡單也更通知的方法:包含 ctype.h 文件并使用 islower()。順便提一下,'a'<= ch >='z' 在 C 中是合法的,只是不具有正確的意義。因為關系運算符是從左到右結合的,所以這個表達式被解釋為值編碼。0 和 1 都不能滿足這個條件,所以整個表達式的值總是為 0(假)。在第二個判斷表達式中,|| 應該是 && 。盡管 !(ch < 'A' 是合法的,而且意義也正確,但 ch >= 'A' 更為簡單。'Z' 后面需要有兩個結束圓括號而不是一個。再一次,更簡單的方法是使用 isupper()。應該在 oc++; 語句前面誘因一個 else,否則,每輸入一個字符,它都會增加 1. 在 printf()調用中的控制表達式應該用雙引號引起來。
下面是正確的版本
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main (void)
{
char ch;
int lc = 0; /* 統計小寫字符 */
int uc = 0; /* 統計大寫字符 */
int oc = 0; /* 統計其他字符 */
printf ("please input chars : \n");
//ch = getchar(); /* 如果要這個語句的話 第一個字母將會不算進統計 */
while ((ch = getchar()) != '#')
{
if (islower(ch))
lc++;
else if (isupper(ch))
uc++;
else
oc++;
}
printf ("%d lowercase, %d uppercase, %d other \n", lc,uc,oc);
system("PAUSE");
return 0;
}
-------------------------------------------------------------------
8. 下列程序將打印出什么?
/* retire.c */
#include <stdio.h>
int main (void)
{
int age = 20;
while (age++ <= 65)
{
if ((age %20) == 0) /* age 能被 20 整除么? */
printf (" You are %d .Here is a raise.\n",age);
if (age = 65)
printf (" You are %d .Here is your gold watch \n",age);
}
return 0;
}
答: 不幸的是,它無休止地打印同一行:
You are 65 .Here is your gold watch
問題在于 if (age = 65)
這行代碼把 age 設置為 65 使得每個循環周期中判斷條件都為真。
---------------------------------------------------------------------------------------
9. 當給定下述輸入時,下列程序將打印出什么?
q
c
g
b
#include <stdio.h>
int main (void)
{
char ch;
while ((ch =getchar()) != '#')
{
if (ch == '\n')
continue;
printf ("Step 1\n");
if (ch == 'c')
continue;
else if (ch == 'b')
break;
else if (ch == 'g')
goto laststep;
printf ("Step 2 \n");
laststep: printf ("Step 3\n");
}
printf ("Done \n");
return 0;
}
答:q :Step 1
Step 2
Step 3
c: Step 1
g: Step 1
Step 3
b: Step 1
Done
-----------------------------------------------------------------
10. 重寫題目 9 的程序,以使它表現相同的行為但不使用 continue 或 goto
答:
#include <stdio.h>
int main (void)
{
char ch;
while ((ch =getchar()) != '#')
{
if (ch != '\n')
{
printf ("Step 1\n");
if (ch == 'b')
break;
else if (ch != 'c')
{
if (ch != 'g')
printf ("Step 2 \n");
printf ("Step 3\n");
}
}
}
printf ("Done \n");
return 0;
}
?
?
?
7.12 編程練習
---------------------------------------------------------------------------------------
1. 編寫一個程序。該程序讀取輸入直到遇到 # 字符,然后報告讀取的空格數目,讀取的換行符數目以及讀取的所有其他字符數目。
解:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
char ch;
int blank = 0; // 空格
int line = 0; // 行
int chars = 0; // 其他字母
printf (" please input chars ( # to exit ) : \n");
while ((ch = getchar()) != '#')
{
if (ch == ' ')
blank++;
else if (ch == '\n')
line++;
else
chars++;
}
printf (" blank: %d, line : %d, chars : %d \n",blank,line,chars);
system("PAUSE");
return 0;
}
--------------------------------------------------------------------------------------
2. 編寫一個程序,該程序讀取輸入直到遇到 # 字符。使程序打印每個輸入的字符以及它的十進制 ASCII 碼。每行打印 8 個字符/編碼對。建議:利用字符計數和模運算符 (%)在每 8個循環周期打印一個換行符。
解:
#include <stdio.h>
#include <stdlib.h>
#define LINE 8
int main (void)
{
char ch;
int num = 0;
printf (" Please input words (# to quit ) : \n");
while (( ch = getchar()) != '#')
{
num++;
printf ("%c: %2d |",ch,ch);
if (num % LINE == 0)
printf ("\n");
}
system("PAUSE");
return 0;
}
---------------------------------------------------------------------------------------
3. 編寫一個程序。該程序讀取整數,直到輸入 0 。輸入終止后,程序應該報告輸入的偶數(不包括 0 )總個數,偶數的平均值,輸入的奇數總個數以及奇數的平均值。
解:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int num = 0;
int odd_count = 0 ; // 奇數的個數
int odd_sum = 0; // 奇數的總值
int dual_count = 0 ; // 偶數的個數
int dual_sum = 0; // 偶數的總值
float odd_average = 0;
float dual_average = 0;
printf ("Please input numbers (0 to quit) : \n");
while((scanf("%d",&num)==1)&&(num!=0))
{
if (num % 2 == 0)
{
odd_count++;
odd_sum += num;
continue;
}
else
{
dual_count++;
dual_sum += num;
continue;
}
}
if (odd_count > 0)
{
odd_average = odd_sum / odd_count ;
printf (" odd count : %d , doo sum :%.2f \n",odd_count,odd_average);
}
if (dual_count >0)
{
dual_average = dual_sum / dual_count;
printf (" dual count: %d, dual sum ;%.2f \n",dual_count,dual_average);
}
printf (" The End\n");
system("PAUSE");
return 0;
}
-----------------------------------------------------------------------------------------
4. 利用 if else 語句編寫程序讀取輸入,直到 #,用一個感嘆號代替每個句號,將原有的每個感嘆號用兩個感嘆號代替,最后報告進行了多少次替代。
解:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
char ch;
int num = 0;
printf(" Please input chars (# to quit) : \n");
while ((ch = getchar()) != '#')
{
if ( ch == '.')
{
ch = '!';
num++;
}
else if (ch == '!')
{
putchar('!!');
num++;
}
putchar(ch);
}
printf ("一共替換了 %d 次\n",num);
system("PAUSE");
return 0;
}
-------------------------------------------------------------------------------------
5. 用 switch 重做練習 3
解:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int num = 0;
/* bool s_num; 使用另外一個語句要增加的布爾變量 */
int o_count, d_count ; // 奇數和偶數的計數
int o_sum, d_sum; // 奇數和偶數的總值
float o_average,d_average;
o_count = d_count = o_sum = d_sum = o_average = d_average = 0;
printf ("Please input number (0 to quit) : \n");
while ((scanf("%d",&num) == 1) && (num != 0))
{
switch((num % 2 == 1)||(num % 2 != 0))
/* switch (s_num = num %2) 如果此語句比較明了,但要增加多一個布爾變量 */
{
case 0:
d_count++;
d_sum += num;
break;
case 1:
o_count++;
o_sum +=num;
break;
}
}
if (d_count >0) {
d_average = d_sum / d_count;
printf ("dual : %d age; dou sum : %.2f \n" ,d_count,d_average);
}
if (o_count > 0) {
o_average = o_sum / o_count;
printf ("odd : %d gae; dou sum ;%.2f \n",o_count,o_average);
}
printf (" The End \n");
system("PAUSE");
return 0;
}
----------------------------------------------------------------------------------
6. 編寫一個程序讀取輸入,直到 # ,并報告序列 ei 出現的次數。
PS: 此程序必須要記住前一個字符和當前字符。用諸如 Receive your eieio avard 的輸入測試它
解:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
char ch,t_ch;
int count = 0;
printf (" Please input chars (# to quit ): \n");
while ((ch = getchar()) != '#')
{
if ((ch == 'i') && (t_ch == 'e') ) // 關鍵1
count++;
t_ch = ch; // 關鍵2
}
printf ("You have 'ei' %d 's",count);
system("PAUSE");
return 0;
}
-----------------------------------------------------------------------------------------
7. 編寫程序,要求輸入一周中的工作小時數,然后打印工資總額,稅金以及凈工資。作如下假設;
a. 基本工資等級 10.00 美元 / 小時
b. 加班 (超過 40 小時) = 1.5倍時間
c. 前 300 美元為 15%
下一個 150 美元為 20%
余下的為 25% 212.5
用 #define 定義常量,不必關心本例是否符合當前的稅法。
解;
#include <stdio.h>
#include <stdlib.h>
#define BASEPAY 10.00 /* 每小時工資 */
#define BASEHOUR 40 /* 一周工作的基本時間 */
#define OVERTIME 1.5 /* 加班計時倍數*/
#define BEFORE 300 /* 第一個稅金金額 */
#define NEXT 150 /* 第二個稅金金額 */
#define TAX_A 0.15 /* 第一稅金比例 */
#define TAX_B 0.20 /* 第二稅金比例 */
#define TAX_C 0.25 /* 第三稅金比例 */
float time (float t_hour);
float fnutax (float f_sum);
int main (void)
{
float hour = 0;
float sum,tax,wage;
sum = tax = wage = 0;
printf (" Please input time : \n");
while ((scanf("%f",&hour)) == 1)
{
time(hour);
sum = time(hour) * BASEPAY; /* 這里工作時間要用函數的返回值,直接用 hour的話 得到的就是輸入的時間而不會有加班費的計數 */
tax = fnutax(sum);
wage = sum - tax;
printf (" sum = $%.2f, tax = $%.2f, wage =%.2f \n",sum,tax,wage);
break;
}
system("PAUSE");
return 0;
}
float time (float t_hour) /* 工作小時計算函數 */
{
if (t_hour <=BASEHOUR)
t_hour = t_hour;
else
t_hour = (t_hour - BASEHOUR) * OVERTIME + BASEHOUR;
return t_hour; /* 函數的返回值 只需要在函數原型上定義,便可以返回 命名不論*/
}
float fnutax (float f_sum) /* 稅金計算函數 */
{
if ( f_sum <= BEFORE)
f_sum = f_sum * TAX_A;
else if (f_sum <=( BEFORE +NEXT))
f_sum = (f_sum -BEFORE) * TAX_B + BEFORE * TAX_A ;
else
f_sum = BEFORE * TAX_A + NEXT * TAX_B + (f_sum -BEFORE - NEXT) * TAX_C;
return f_sum;
}
-------------------------------------------------------------------------------
8. 修改練習 7 中的假設 a,使程序提供一個選擇工資等級的菜單。用 switch 選擇工資等級。程序運行的開頭應該像這樣:
*************************************************************************
Enter the number corresponding to the desired pay rate or action
1) $ 8.75 /hr 2)$ 9.33 /hr
3) $ 10.00 /hr 4)$ 11.20 /hr
5) $ quit
*************************************************************************
如果選擇 1 到 4 ,那么程序應該請求輸入工作小時數。程序應該一直循環運行,直到輸入 5 。如果輸入 1 到 5 以外的選項,那么程序應該提醒用戶合適的選項是哪些,然后再循環。用 #diefne 為各種工資等級和稅率真定義常量。
解:
#include <stdio.h>
#include <string.h> 一
#include <stdlib.h>
#include <ctype.h>
#define ONE 8.75 // 第一類工資
#define TWO 9.33 // 第二類工資
#define THREE 10.00 // 第三類工資
#define FOUR 11.20 // 第四類工資
#define BASEHOUR 40 /* 一周工作的基本時間 */
#define OVERTIME 1.5 /* 加班計時倍數*/
#define BEFORE 300 /* 第一個稅金金額 */
#define NEXT 150 /* 第二個稅金金額 */
#define TAX_A 0.15 /* 第一稅金比例 */
#define TAX_B 0.20 /* 第二稅金比例 */
#define TAX_C 0.25 /* 第三稅金比例 */
#define STR "Enter the number corresponding to the desired pay rate or action"
float time (float t_hour);
float fnutax (float f_sum);
void star (char ch, int num);
void temp (float num);
int main (void)
{
float hour = 0;
float sum,tax,wage;
char ch;
sum = tax = wage = 0;
float num;
begin: // goto 跳轉
star ('*' ,strlen(STR)); // 程序頭
printf("%s \n",STR);
printf(" 1) $ 8.75 /hr 2)$ 9.33 /hr \n");
printf(" 3) $ 10.00 /hr 4)$ 11.20 /hr \n");
printf(" 5) $ quit \n");
star('*',strlen(STR));
while ((ch = getchar()) != '5')
{
if (isalnum(ch)) // 調用isalnum() 函數
switch(ch)
{
case '1':
num = ONE;
printf (" Please input time : ");
temp(num);
printf ("\n");
goto begin;
break;
case '2':
num = TWO;
printf (" Please input time : ");
temp(num);
printf ("\n");
goto begin;
break;
case '3':
num = THREE;
printf (" Please input time : ");
temp(num);
printf ("\n");
goto begin;
break;
case '4':
num = FOUR;
printf (" Please input time : ");
temp(num);
printf ("\n");
goto begin;
break;
default:
printf ("你的輸入有錯誤,請重新輸入 \n");
goto begin;
break;
}
}
printf ("程序退出 \n");
system("PAUSE");
return 0;
}
float time (float t_hour) /* 工作小時計算函數 */
{
if (t_hour <=BASEHOUR)
t_hour = t_hour;
else
t_hour = (t_hour - BASEHOUR) * OVERTIME + BASEHOUR;
return t_hour; /* 函數的返回值 只需要在函數原型上定義,便可以返回 命名不論*/
}
float fnutax (float f_sum) /* 稅金計算函數 */
{
if ( f_sum <= BEFORE)
f_sum = f_sum * TAX_A;
else if (f_sum <=( BEFORE +NEXT))
f_sum = (f_sum -BEFORE) * TAX_B + BEFORE * TAX_A ;
else
f_sum = BEFORE * TAX_A + NEXT * TAX_B + (f_sum -BEFORE - NEXT) * TAX_C;
return f_sum;
}
void star (char ch, int num) /* 原型使用 star('*',num) * = ch, num = num */
{
int temp;
for (temp = 0; temp < num; temp++) {
putchar(ch);
}
printf ("\n");
}
void temp (float num) // 暫時稅金調用函數
{
float hour = 0;
float sum,tax,wage;
sum = tax = wage = 0;
scanf("%f",&hour);
time(hour);
sum = time(hour) * num;
tax = fnutax(sum);
wage = sum - tax;
printf (" sum = $%.2f, tax = $%.2f, wage =%.2f \n",sum,tax,wage);
}
------------------------------------------------------------------------------------------
9. 編寫一個程序,接受一個整數的輸入,然后顯示所有小于或等于該數的素數
解:
#include<stdio.h>
int main(void)
{
int num,begin,end,temp;
bool isprime;
printf("Hey you,the foolish guy,input your number: ");
while(scanf("%d",&num)==1){
for(begin=2;begin<=num;begin++){
for(end=2,isprime=true;end<=begin/2;end++){
if (begin%end==0)
isprime=false;
}
if(isprime){
printf("%5d",begin);
}
}
printf("\n\nTry again(Enter q to quit): ");
}
printf("Done.");
return 0;
}
注:求質數還是算法和效率上的問題,由于對算法還不理解,暫抄了網上的答案。
以后再自己想。。
-------------------------------------------------------------------------------
10. 1988 年 United States Federal Tax Schedule 是近期最基本的。它分為 4 類,每類有兩個等級。下面是其摘要;美元數為應征稅的收入。
---------------------------------------------------------------
各類 稅金
--------------------------------------------------------------
單身 前 17,850美元按 15%,超出部分按 28%
--------------------------------------------------------------
戶主 前 23,900美元按 15%,超出部分按 28%
--------------------------------------------------------------
已婚,共有 前 29,750美元按 15%,超出部分按 28%
--------------------------------------------------------------
已婚,離異 前 14,875美元按 15%,超出部分按 28%
--------------------------------------------------------------
編寫一個程序,讓用戶指定稅金各類和應征稅收入,然后計算稅金。使用循環以便用戶可以多次輸入。
解:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define BASE_A 17850
#define BASE_B 23900
#define BASE_C 29750
#define BASE_D 14875
#define STR "請選擇你的稅金種類"
void star (char ch, int num);
float fnutax (float f_sum);
void temp (float num);
int main (void)
{
char ch;
float sum, tax;
sum = tax = 0;
float tax_a,tax_b;
tax_a = 0.15;
tax_b = 0.28; // switch 中 case 中好像不能用常量進行計算 這里只好用變量
begin:
star('-',strlen(STR)*3);
printf ("%s \n",STR);
printf (" A) 單身 : 前 17,850美元按 15%,超出部分按 28% \n");
printf (" B) 戶主 : 前 23,900美元按 15%,超出部分按 28% \n");
printf (" C) 已婚,共有 : 前 29,750美元按 15%,超出部分按 28% \n");
printf (" D) 已婚,離異 : 前 14,875美元按 15%,超出部分按 28% \n");
printf (" E) 退出程序 \n");
star('-',strlen(STR)*3);
while ((ch = getchar()) != 'E')
{
if (isalnum(ch))
switch(ch)
{
case 'A':
printf (" 請輸入你的收入 :");
scanf("%f",&sum);
if (sum <= BASE_A)
{
tax = sum * tax_a;
printf("\n 你的收入是 %.2f 應付稅金為 %.2f \n ",sum,tax);
}
else
{
tax = (sum - BASE_A) * tax_b + BASE_A * tax_a;
printf("\n 你的收入是 %.2f 應付稅金為 %.2f \n ",sum,tax);
}
goto begin;
break;
case 'B':
printf (" 請輸入你的收入 :");
scanf("%f",&sum);
if (sum <= BASE_B)
{
tax = sum * tax_a;
printf("\n 你的收入是 %.2f 應付稅金為 %.2f \n ",sum,tax);
}
else
{
tax = (sum - BASE_B) * tax_b + BASE_B * tax_a;
printf("\n 你的收入是 %.2f 應付稅金為 %.2f \n ",sum,tax);
}
goto begin;
break;
case 'C' :
printf (" 請輸入你的收入 :");
scanf("%f",&sum);
if (sum <= BASE_C)
{
tax = sum * tax_a;
printf("\n 你的收入是 %.2f 應付稅金為 %.2f \n ",sum,tax);
}
else
{
tax = (sum - BASE_C) * tax_b + BASE_C * tax_a;
printf("\n 你的收入是 %.2f 應付稅金為 %.2f \n ",sum,tax);
}
goto begin;
break;
case 'D':
printf (" 請輸入你的收入 :");
scanf("%f",&sum);
if (sum <= BASE_D)
{
tax = sum * tax_a;
printf("\n 你的收入是 %.2f 應付稅金為 %.2f \n ",sum,tax);
}
else
{
tax = (sum - BASE_D) * tax_b + BASE_D * tax_a;
printf("\n 你的收入是 %.2f 應付稅金為 %.2f \n ",sum,tax);
}
goto begin;
break;
default:
printf ("\n 你的輸入有錯誤,請重新輸入 \n");
goto begin;
break;
}
}
system("PAUSE");
return 0;
}
void star (char ch, int num)
{
int temp;
for (temp = 0; temp < num; temp++) {
putchar(ch);
}
printf ("\n");
}
-------------------------------------------------------------------------------------
11. ABC Mail Order Grocery 朝鮮薊的售價是 1.25 美元/磅,甜菜的售價是 0.65美元/磅,胡蘿卜的售價是 0.89美元/磅。在添加運輸費用之前,他們為 100 美元的訂單提供 5%的打優惠折。對 5 磅以下的定單收取 3.50美元的運輸和裝卸費用;超過 5磅而不足 20磅的定單收到 10.00美元的運輸和裝卸費用;20磅以上的運輸在 8 美元的基礎上每磅加收 0.1 美元。編寫程序,在循環中使用 switch 語句。以便對輸入 a 的響應是讓用戶輸入所需的朝鮮薊磅數,b 為甜菜磅數, c 為胡蘿卜磅數,而 q 允許用戶退出訂購過程。然后程序計算總費用,折扣和運輸費用(如果有運輸費用的話),以及總數。隨后程序應該顯示所有購買信息:每磅的費用,訂購的磅數,該訂單每種蔬菜的費用,訂單的總費用,折扣,如果有的話加上運輸費用, 以及所有費用的總數。 279 265.5 12.8
解:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define THISTLE 1.25 //朝鮮薊
#define BEET 0.65 //甜菜
#define CARROT 0.89 //胡蘿卜
#define REBATE 0.05 // 5%的打折優惠 (100美元以上)
#define BASE 100.0 // 打折優惠的金額下限 100
#define HEFT_A 5.0 // 5 磅
#define HEFT_B 20.0 // 20 磅
#define HEFT_R_A 3.50 // 5 磅的運費
#define HEFT_R_B 10.00 // 5 - 20磅的運費
#define HEFT_R_C 8.0 // 20磅的運費
#define HEFT_R_C_A 0.1 // 20+ 每磅的運費
#define STR " 請選擇你要購買的種類: a)朝鮮薊 b)甜菜 c)胡蘿卜 q) 結算&退出"
void star (char ch, int num);
int main (void)
{
char ch;
float thistle_c,beet_c,carrot_c; // 商品的計數
thistle_c = beet_c = carrot_c = 0;
float total_t,total_b,total_c; // 商品的累積計數
total_t = total_b = total_c = 0;
float sum_t,sum_b,sum_c; // 商品的總金額
sum_t = sum_b = sum_c = 0;
float sum_all,heft, all,rate,base;
sum_all = heft = all = rate = base = 0;
float heft_c,all_sum;
heft_c = all_sum = 0;
begin:
star ('-',strlen(STR));
printf ("\n %s \n \n",STR);
star ('-',strlen(STR));
while ((ch = getchar()) != 'q')
{
if (isalnum(ch))
switch(ch)
{
case 'a':
printf (" 請輸入你要購買的數量 朝鮮薊(磅) :");
scanf ("%f",&thistle_c);
total_t += thistle_c;
goto begin;
break;
case 'b':
printf (" 請輸入你要購買的數量 甜菜(磅) :");
scanf ("%f",&beet_c);
total_b += beet_c;
goto begin;
break;
case 'c':
printf (" 請輸入你要購買的數量 胡蘿卜(磅) :");
scanf ("%f",&carrot_c);
total_c += carrot_c;
goto begin;
break;
default:
printf ("輸入錯誤,請重新輸入\n");
goto begin;
break;
}
}
if ((total_t > 0) || (total_b > 0) || (total_c > 0))
{
printf ("朝鮮薊:$1.25/磅 甜菜:$0.65/磅 胡蘿卜:$0.89/磅\n");
}
if (total_t != 0)
{
sum_t = THISTLE * total_t;
printf("你購買了朝鮮薊 %.2f /磅,共 $%.2f(美元)\n",total_t,sum_t);
}
if (total_b != 0)
{
sum_b = BEET * total_b;
printf("你購買了甜菜 %.2f /磅,共 $%.2f(美元)\n",total_b,sum_b);
}
if (total_c != 0)
{
sum_c = CARROT * total_c;
printf("你購買了胡蘿卜 %.2f /磅,共 $%.2f(美元)\n",total_c,sum_c);
}
if ((sum_t != 0) || (sum_b != 0) || (sum_c != 0))
{
sum_all = sum_t + sum_b + sum_c;
heft = total_t + total_b + total_c;
if (heft <= HEFT_A)
heft_c = HEFT_R_A;
if ((heft >HEFT_A) && ( heft <=HEFT_B))
heft_c = HEFT_R_B;
if (heft > HEFT_B)
heft_c = HEFT_R_C + (heft - HEFT_B) * HEFT_R_C_A;
if (sum_all >= BASE)
all = sum_all * REBATE;
printf ("%.2f",sum_all);
all_sum = sum_all - all +heft_c;
printf("你購買的磅數共為 %.2f 運費 $%.2f 折扣為 $%.2f 總費用為 $%.2f \n",
heft,heft_c,all,all_sum);
}
printf ("程序退出\n");
system("PAUSE");
return 0;
}
void star (char ch,int num)
{
int temp;
for (temp =0; temp <= num; temp++) {
putchar(ch);
}
printf ("\n");
}
注; 做得真他媽的累。。。 要考慮的東西太多了 編譯器也不夠智能。。
if 作為判斷那句 一定小心不能用 分號, 有不 編譯正常,運算的時候卻有點差錯。
轉載于:https://www.cnblogs.com/dream-for/p/5125402.html
總結
以上是生活随笔為你收集整理的C Primer Plus(第五版)7的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 老李分享:Android -自动化埋点
- 下一篇: GO逆转字符串