算法競賽-入門經典-做者劉汝佳.doc算法
第1部分 語 言 篇編程
學習目標網絡
þ 熟悉C語言程序的編譯和運行函數
þ 學會編程計算並輸出常見的算術表達式的結果學習
þ 掌握整數和浮點數的含義和輸出方法測試
þ 掌握數學函數的使用方法ui
þ 初步瞭解變量的含義編碼
þ 掌握整數和浮點數變量的聲明方法翻譯
þ 掌握整數和浮點數變量的讀入方法設計
þ 掌握變量交換的三變量法
þ 理解算法競賽中的程序三步曲:輸入、計算、輸出
þ 記住算法競賽的目標及其對程序的要求
計算機速度快,很適合作計算和邏輯判斷工做。本章首先介紹順序結構程序設計,其基本思路是:把須要計算機完成的工做分紅若干個步驟,而後依次讓計算機執行。注意這裏的「依次」二字——步驟之間是有前後順序的。這部分的重點在於計算。
接下來介紹分支結構程序設計,用到了邏輯判斷,根據不一樣狀況執行不一樣語句。本章內容不復雜,可是不容忽視。
注意:編程不是看會的,也不是聽會的,而是練會的,因此應儘可能在計算機旁閱讀 本書,以便把書中的程序輸入到計算機中進行調試,順便再作作上機練習。千萬不要圖 快——若是沒有足夠的時間用來實踐,那麼學得快,忘得也快。
(爲幫助沒有分值的朋友能下載,特此修改文檔,以避免上傳不了)
計算機的「本職」工做是計算,所以下面先從算術運算入手,看看如何用計算機進行復雜的計算。
程序1-1 計算並輸出1+2的值
#include<stdio.h>
int main()
{
printf("%d\n", 1+2);
return 0;
}
這是一段簡單的程序,用於計算1+2的值,並把結果輸出到屏幕。若是你不知道如何編譯並運行這段程序,可閱讀附錄或向指導教師求助。
即便你不明白上述程序除了「1+2」以外的其餘內容,仍然能夠進行如下探索:試着把「1+2」改爲其餘東西,而不要去修改那些並不明白的代碼——它們看上去工做狀況良好。
下面讓咱們作4個實驗:
實驗1:修改程序1-1,輸出3-4的結果
實驗2:修改程序1-1,輸出5×6的結果
實驗3:修改程序1-1,輸出8÷4的結果
實驗4:修改程序1-1,輸出8÷5的結果
直接把「1+2」替換成「3+4」便可順利解決實驗1,但讀者很快就會發現:沒法在鍵盤上找到乘號和除號。解決方法是:用星號「*」代替乘號,而用正斜線「/」代替除號。這樣,4個實驗都順利完成了。
等一下!實驗4的輸出結果竟然是1,而不是正確答案1.6。這是怎麼回事?計算機出問題了嗎?計算機沒有出問題,問題出在程序上:這段程序的實際含義並不是和咱們所想的一致。
在C語言中,8/5的確切含義是8除以5所得商值的整數部分。一樣地,(-8)/5的值是-1,不信能夠本身試試。那麼若是非要獲得8÷5=1.6的結果怎麼辦?下面是完整的程序。
程序1-2 計算並輸出8/5的值,保留小數點後1位
#include<stdio.h>
int main()
{
printf("%.1lf\n", 8.0/5.0);
return 0;
}
注意,百分號後面是個小數點,而後是數字1,再而後是小寫字母l,最後是小寫字母f,千萬不能打錯,包括大小寫——在C語言中,大寫和小寫字母表明的含義是不一樣的。
再來作3個實驗:
實驗5:把%.1lf中的數字1改爲2,結果如何?能猜測出「1」的確切意思嗎?若是把小數點和1都刪除,%lf的含義是什麼?
實驗6:字符串%.1lf不變,把8.0/5.0改爲原來的8/5,結果如何?
實驗7:字符串%.1lf改爲原來的%d,8.0/5.0不變,結果如何?
實驗5並不難解決,但實驗6和實驗7的答案就很難簡單解釋了——真正緣由涉及整數和浮點數編碼,相信多數初學者對此都不感興趣。緣由並不重要,重要的是規範:根據規範作事情,則一切盡在掌握中。
提示1-1:整數值用%d輸出,實數用%lf輸出[①]。
這裏的「整數值」指的是1+二、8/5這樣「整數之間的運算」。只要運算符的兩邊都是整數,則運算結果也會是整數。正由於這樣,8/5的值纔是1,而不是1.6。
8.0和5.0被看做是「實數」,或者說得更專業一點,叫「浮點數」。浮點數之間的運算結果是浮點數,所以8.0/5.0=1.6也是浮點數。注意,這裏的運算符「/」實際上是「多面手」,它既能夠拿來作整數除法,又能夠拿來作浮點數除法。
提示1-2:整數/整數=整數,浮點數/浮點數=浮點數。
這條規則一樣適用於加法、減法和乘法,不過沒有除法這麼容易出錯——畢竟整數乘以整數的結果原本就是整數。
算術表達式能夠和數學表達式同樣複雜,例如:
程序1-3 複雜的表達式計算
#include<stdio.h>
#include<math.h>
int main()
{
printf("%.8lf\n", 1+2*sqrt(3)/(5-0.1));
return 0;
}
相信讀者不難把它翻譯成數學表達式: 。儘管如此,讀者可能仍是有一些疑惑:5-0.1的值是什麼?「整數-浮點數」是整數仍是浮點數?另外,多出來的#include<math.h>是作什麼用的?
第1個問題相信讀者可以「猜到」結果:整數-浮點數=浮點數。但其實這個說法並不許確。確切的說法是:整數先「變」成浮點數,而後浮點數-浮點數=浮點數。
第2個問題的答案是:由於程序1-3中用到了數學函數sqrt。數學函數sqrt(x)的做用是計算x的算術平方根(若不信,可輸出sqrt(9.0)的值試試)。通常來講,只要在程序中用到了數學函數,就須要在程序最開始的地方包含頭文件math.h,並在編譯時鏈接數學庫。若是你不知道如何編譯並運行這段程序,可閱讀附錄或向指導教師求助。
1.1節的程序雖好,但有一個遺憾:計算的數據是事先肯定的。爲了計算1+2和2+3,咱們不得不編寫兩個程序。可不可讓程序讀取鍵盤輸入,並根據輸入內容計算結果呢?答案是確定的。程序以下:
程序1-4 A+B問題
#include<stdio.h>
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", a+b);
return 0;
}
該程序比1.1節的複雜了許多。簡單地說,第一條語句「int a, b」聲明瞭兩個整型(即整數類型)變量a和b,而後讀取鍵盤輸入,並放到a和b中。注意a和b前面的&符號——千萬不要漏掉,不信能夠試試[②]。
如今,你的程序已經讀入了兩個整數,能夠在表達式中自由使用它們,就比如使用十二、597這樣的常數。這樣,表達式a+b就不難理解了。
提示1-3:scanf中的佔位符和變量的數據類型應一一對應,且每一個變量前須要&符號。
能夠暫時把變量理解成「存放值的場所」,或者形象地認爲每一個變量都是一個盒子、瓶子或箱子。在C語言中,變量有本身的數據類型,例如int型變量存放整數值,而double型變量存放浮點數值(專業的說法是「雙精度」浮點數)。若是硬要把浮點數值塞給一個int型變量,將會丟失部分信息——咱們不推薦這樣作。
下面來看一個複雜一點的例子。
例題1-1 圓柱體的表面積
輸入底面半徑r和高h,輸出圓柱體的表面積,保留3位小數,格式見樣例。
樣例輸入:3.5 9
樣例輸出:Area = 274.889
【分析】
圓柱體的表面積由3部分組成:上底面積、下底面積和側面積。因爲上下底面積相等,完整的公式能夠寫成:表面積=底面積×2+側面積。根據平面幾何知識,底面積= ,側面積= 。不難寫出完整程序:
程序1-5 圓柱體的表面積
#include<stdio.h>
#include<math.h>
int main()
{
const double pi = 4.0 * atan(1.0);
double r, h, s1, s2, s;
scanf("%lf%lf", &r, &h);
s1 = pi*r*r;
s2 = 2*pi*r*h;
s = s1*2.0 + s2;
printf("Area = %.3lf\n", s)
return 0;
}
這是本書中第一個完整的「競賽題目」,由於和正規比賽同樣,題目中包含着輸入輸出格式規定,還有樣例數據。大多數的算法競賽包含以下一些相同的「遊戲規則」。
首先,選手程序的執行是自動完成的,沒有人工干預。不要在用戶輸入以前打印提示信息(例如「Please input n:」),這不只不會爲程序贏得更高的「界面友好分」,反而會讓程序丟掉大量的(甚至全部的)分數——這些提示信息會被看成輸出數據的一部分。例如剛纔的程序若是加上了「友好提示」,輸出信息將變成:
Please input n:
Area = 274.889
比標準答案多了整整一行!
其次,不要讓程序「按任意鍵退出」(例如調用system(「pause」),或者加一個多餘的getchar()),由於不會有人來「按任意鍵」的。很多早期的C語言教材會建議在程序的最後加這樣一條語句來「觀察輸出結果」,但注意千萬不要在算法競賽中這樣作。
提示1-4:在算法競賽中,輸入前不要打印提示信息。輸出完畢後應當即終止程序,不要等待用戶按鍵,由於輸入輸出過程都是自動的,沒有人工干預。
在通常狀況下,你的程序不能直接讀取鍵盤和控制屏幕:不要在算法競賽中使用getch()、getche()、gotoxy()、clrscr()(早期的教材中可能會介紹這些函數)。
提示1-5:在算法競賽中不要使用頭文件conio.h,包括getch()、clrscr()等函數。
最後,最容易忽略的是輸出的格式:在不少狀況下,輸出格式是很是嚴格的——多一個或者少一個字符都是不能夠的!
提示1-6:在算法競賽中,每行輸出均應以回車符結束,包括最後一行。除非特別說明,每行的行首不該有空格,但行末一般能夠有多餘空格。另外,輸出的每兩個數或者字符串之間應以單個空格隔開。
總結一下,算法競賽的程序應當只作3件事情:讀入數據、計算結果、打印輸出。不要打印提示信息,不要在打印輸出後「暫停程序」,更不要嘗試畫圖、訪問網絡等與算法無關的任務。
回到剛纔的程序,它多了幾個新東西。首先是「const double pi = 4.0 * atan(1.0);」。這裏也聲明瞭一個叫pi的「符號」,可是const關鍵字代表它的值是不能夠改變的——pi是一個真正的數學常數[③]。
提示1-7:儘可能用const關鍵字聲明常數。
接下來是s1 = pi * r * r。這條語句應該如何理解呢?「s1等於pi*r*r」嗎?並非這樣的。不信,你把它換成「pi * r * r = s1」試試,編譯器會給出錯誤信息:invalid lvalue in assignment。若是這條語句真的是「兩者相等」的意思,爲什麼不容許反着寫呢?
事實上,這條語句的學術說法是賦值(assignment),它不是一個描述,而是一個動做。它的確切含義是:先把「等號」右邊的值算出來,而後塞到左邊的變量中。注意,變量是「喜新厭舊」的,即新的值將覆蓋原來的值,一旦被賦了新的值,變量中原來的值就丟失了。
提示1-8:賦值是個動做,先計算右邊的值,再賦給左邊的變量,覆蓋它原來的值。
最後是那個「Area = %.3lf\n」,它的用法很容易被猜到:只有以%開頭的部分纔會被後面的值替換掉,其餘部分原樣輸出。
提示1-9:printf的格式字符串中能夠包含其餘可打印符號,打印時原樣輸出。
例題1-2 三位數反轉
輸入一個三位數,分離出它的百位、十位和個位,反轉後輸出。
樣例輸入:127
樣例輸出:721
【分析】
首先將三位數讀入變量n,而後進行分離。百位等於n/100(注意這裏取的是商的整數部分),十位等於n/10%10(這裏的%是取餘數操做),個位等於n%10。程序以下:
程序1-6 三位數反轉(1)
#include<stdio.h>
int main()
{
int n;
scanf("%d", &n);
printf("%d%d%d\n", n%10, n/10%10, n/100);
return 0;
}
此題有一個沒有說清楚的細節,即:若是個位是0,反轉後應該輸出嗎?例如輸入是520,輸出是025仍是25?若是在算法競賽中遇到這樣的問題,可向監考人員詢問[④]。可是在這裏,兩種狀況的處理方法都應學會。
提示1-10:算法競賽的題目應當是嚴密的,各類狀況下的輸出均應有嚴格規定。若是在比賽中發現題目有漏洞,應向相關人員詢問,而儘可能不要本身隨意假定。
上面的程序輸出025,但要改爲輸出25彷佛會比較麻煩——咱們必須判斷n%10是否是0,但目前尚未學到「根據不一樣狀況執行不一樣指令」(分支結構程序設計是1.4節的主題)。
一個解決方法是在輸出前把結果儲存在變量m中。這樣,直接用%d格式輸出m,將輸出25。要輸出025也很容易,把輸出格式變爲%03d便可。
程序1-7 三位數反轉(2)
#include<stdio.h>
int main()
{
int n, m;
scanf("%d", &n);
m = (n%10)*100 + (n/10%10)*10 + (n/100);
printf("%03d\n", m);
return 0;
}
例題1-3 交換變量
輸入兩個整數a和b,交換兩者的值,而後輸出。
樣例輸入:824 16
樣例輸出:16 824
【分析】
按照題目所說,先把輸入存入變量a和b,而後交換。如何交換兩個變量呢?最經典的方法是三變量法:
程序1-8 變量交換(1)
#include<stdio.h>
int main()
{
int a, b, t;
scanf("%d%d", &a, &b);
t = a;
a = b;
b = t;
printf("%d %d\n", a, b);
return 0;
}
能夠將這種方法形象地比喻成將一瓶醬油和一瓶醋藉助一個空瓶子進行交換:先把醬油倒入空瓶,而後將醋倒進原來的醬油瓶中,最後把醬油從輔助的瓶子中倒入原來的醋瓶子裏。這樣的比喻雖然形象,可是初學者應當注意它和真正的變量交換的區別。
藉助一個空瓶子的目的是:避免把醋直接倒入醬油瓶子——直接倒進去,兩者混合之後,將很難分開。在C語言中,若是直接進行賦值a=b,則原來a的值(醬油)將會被新值(醋)覆蓋,而不是混合在一塊兒。
當醬油被倒入空瓶之後,原來的醬油瓶就變空了,這樣才能裝醋。但在C語言中,進行賦值t = a後,a的值不變,它只是把值拷貝(即複製)給了變量t而已,自身並不會變化。儘管a的值立刻就會被改寫,可是從原理上看,t=a的過程和「倒醬油」的過程有着本質區別。
提示1-11:賦值a = b以後,變量a原來的值被覆蓋,而b的值不變。
另外一個方法沒有藉助任何變量,可是較難理解:
程序1-9 變量交換(2)
#include<stdio.h>
int main()
{
int a, b;
scanf("%d%d", &a, &b);
a = a + b;
b = a - b;
a = a - b;
printf("%d %d\n", a, b);
return 0;
}
此次就不太方便用倒醬油作比喻了:硬着頭皮把醋倒在醬油瓶子裏,而後分離出醬油倒回醋瓶子?比較理性的方法是手工模擬這段程序,看看每條語句執行後的狀況。
在順序結構程序中,程序一條一條依次執行。爲了不值和變量名混淆,假定用戶輸入的是a0和b0,所以scanf語句執行完後a = a0, b = b0。
執行完a = a+b後:a = a0+b0, b = b0。
執行完b = a-b後:a = a0+b0, b = a0。
執行完a = a-b後:a = b0, b = a0。
這樣就不難理解兩個變量是如何交換的了。這個方法看起來很好(少用一個變量),但實際上不多使用,由於它的適用範圍很窄:只有定義了加減法的數據類型才能這麼作[⑤]。事實上,筆者並不推薦讀者採用這樣的技巧實現變量交換:三變量法已經足夠好了,這個例子只是幫助讀者提升程序閱讀能力。
提示1-12:交換兩個變量的三變量法適用範圍廣,推薦使用。
那麼是否是說,三變量法是解決本題的最佳途徑了呢?答案是否認的。多數算法競賽採用黑盒測試,即只考查程序解決問題的能力,而不關心它採用的什麼方法。對於本題而言,最合適的程序莫過於:
程序1-10 變量交換(3)
#include<stdio.h>
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d %d\n", b, a);
return 0;
}
換句話說,咱們的目標是解決問題,而不是爲了寫程序而寫程序,同時應保持簡單(Keep It Simple and Stupid,KISS),而不是本身創造條件去展現編程技巧。
提示1-13:算法競賽是在比誰能更好地解決問題,而不是在比誰寫的程序看上去更高級。
例題1-4 雞兔同籠
已知雞和兔的總數量爲n,總腿數爲m。輸入n和m,依次輸出雞的數目和兔的數目。若是無解,則輸出「No answer」(不要引號)。
樣例輸入:14 32
樣例輸出:12 2
樣例輸入:10 16
樣例輸出:No answer
【分析】
設雞有a只,兔有b只,則a+b=n,2a+4b=m,聯立解得a=(4n-m)/2,b=n-a。在什麼狀況下此解「不算數」呢?首先,a和b都是整數;其次,a和b必須是非負的。能夠經過下面的程序判斷:
程序1-11 雞兔同籠
#include<stdio.h>
int main()
{
int a, b, n, m;
scanf("%d%d", &n, &m);
a = (4*n-m)/2;
b = n-a;
if(m % 2 == 1 || a < 0 || b < 0)
printf("No answer\n");
else
printf("%d %d\n", a, b);
return 0;
}
上面的程序用到了if語句,它的通常格式是:
if(條件)
語句1;
else
語句2;
注意語句1和語句2後面的分號,以及if後面的括號。「條件」是一個表達式,當該表達式的值爲「真」時執行語句1,不然執行語句2。另外,「else語句2」這個部分是能夠省略的。語句1和語句2前面的空行是爲了讓程序更加美觀,並非必需的,但強烈推薦讀者使用。
提示1-14:if語句的基本格式爲:if(條件) 語句1; else 語句2。
換句話說,m%2==1 || a < 0 || b < 0是一個表達式,它的字面意思是「m是奇數,或者a小於0,或者b小於0」。這句話可能正確,也可能錯誤。所以這個表達式的值可能爲真,也可能爲假,取決於m、a和b的具體數值。
這樣的表達式稱爲邏輯表達式。和算術表達式相似,邏輯表達式也由運算符和值構成,例如「||」運算符稱爲「邏輯或」,a || b表示a爲真,或者b爲真。換句話說,a和b只要有一個爲真,a || b就爲真;若是a和b都爲真,則a || b也爲真。
提示1-15:if語句的條件是一個邏輯表達式,它的值可能爲真,也可能爲假。
細心的讀者也許發現了,若是a爲真,則不管b的值如何,a || b均爲真。換句話說,一旦發現a爲真,就沒必要計算b的值。C語言正是採起了這樣的策略,稱爲短路(short-circuit)。也許你會以爲,用短路的方法計算邏輯表達式的惟一優勢是速度更快,但其實並非這樣,稍後咱們會經過幾個例子予以證明。
提示1-16:C語言中的邏輯運算符都是短路運算符。一旦可以肯定整個表達式的值,就再也不繼續計算。
例題1-5 三整數排序
輸入3個整數,從小到大排序後輸出。
樣例輸入:20 7 33
樣例輸出:7 20 33
【分析】
a、b、c 3個數一共只有6種可能的順序:a b c、a c b、b a c、b c a、c a b、c b a,因此最簡單的思路是使用6條if語句。
程序1-12 三整數排序(1)(錯誤)
#include<stdio.h>
int main()
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a < b && b < c)printf("%d %d %d\n", a, b, c);
if(a < c && c < b)printf("%d %d %d\n", a, c, b);
if(b < a && a < c)printf("%d %d %d\n", b, a, c);
if(b < c && c < a)printf("%d %d %d\n", b, c, a);
if(c < a && a < b)printf("%d %d %d\n", c, a, b);
if(c < b && b < a)printf("%d %d %d\n", c, b, a);
return 0;
}
上述程序看上去沒有錯誤,並且能經過題目中給出的樣例,但惋惜有缺陷:輸入1 1 1將得不到任何輸出!這個例子告訴咱們:即便經過了題目中給出的樣例,程序仍然可能存在問題。
提示1-17:算法競賽的目標是編程對任意輸入均獲得正確的結果,而不只是樣例數據。
稍微修改一下:把全部的小於符號「<」改爲小於等於符號「<=」(在一個小於號後添加一個等號)。這下總能夠了吧?很遺憾,仍是不行。對於「1 1 1」,6種狀況所有符合,程序一共輸出了6次1 1 1。
一種解決方案是人爲地讓6種狀況沒有交叉:把全部的if改爲else if。
程序1-13 三整數排序(2)
#include<stdio.h>
int main()
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a <= b && b <= c) printf("%d %d %d\n", a, b, c);
else if(a <= c && c <= b) printf("%d %d %d\n", a, c, b);
else if(b <= a && a <= c) printf("%d %d %d\n", b, a, c);
else if(b <= c && c <= a) printf("%d %d %d\n", b, c, a);
else if(c <= a && a <= b) printf("%d %d %d\n", c, a, b);
else if(c <= b && b <= a) printf("%d %d %d\n", c, b, a);
return 0;
}
最後一條語句還能夠簡化成單獨的「else」(想想,爲何),不過,幸虧程序正確了。
提示1-18:若是有多個並列、狀況不交叉的條件須要一一處理,能夠用else if語句。
另外一種思路是把a、b、c這3個變量自己改爲a≤b≤c的形式。首先檢查a和b的值,若是a>b,則交換a和b(利用前面講過的三變量交換法);接下來檢查a和c,最後檢查b和c,程序以下:
程序1-14 三整數排序(3)
#include<stdio.h>
int main()
{
int a, b, c, t;
scanf("%d%d%d", &a, &b, &c);
if(a > b) { t = a; a = b; b = t; }
if(a > c) { t = a; a = c; c = t; }
if(b > c) { t = b; b = c; c = t; }
printf("%d %d %d\n", a, b, c);
return 0;
}
爲何這樣作是對的呢?由於通過第一次檢查之後,必然有a≤b,而第二次檢查之後a≤c。因爲第二次檢查之後a的值不會變大,因此a≤b依然成立。換句話說,a已是3個數中的最小值。接下來只需檢查b和c的順序便可。
一個很天然的問題產生了:其餘檢查順序是否也能夠呢?例如先(a,b),而後(b,c),最後(a,c)?這個問題留給讀者思考。提示:上機實驗。
注意上面的程序中惟一的新東西:花括號。前面講過,if語句中有一個「語句1」和可選的「語句2」,且都要以分號結尾。有一種特殊的「語句」是由花括號括起來的多條語句。這多條語句能夠做爲一個總體,充當if語句中的「語句1」或「語句2」,且後面不須要加分號。固然,當if語句的條件知足時,這些語句依然會按順序逐條執行,和普通的順序結構同樣。
提示1-19:能夠用花括號把若干條語句組合成一個總體。這些語句仍然按順序執行。
最後一種思路再次利用了「問題求解」這一目標——它實際上並無真的進行排序:求出了最小值和最大值,中間值是能夠計算出來的。
程序1-15 三整數排序(4)
#include<stdio.h>
int main()
{
int a, b, c, x, y, z;
scanf("%d%d%d", &a, &b, &c);
x = a; if(b < x) x = b; if(c < x) x = c;
z = a; if(b > z) z = b; if(c > z) z = c;
y = a + b + c - x - z;
printf("%d %d %d\n", x, y, z);
return 0;
}
注意程序中包含的「當前最小值」x和「當前最大值」z。它們都初始化爲a,可是隨着「比較」操做的進行而慢慢更新,最後變成真正的最小值和最大值。這個技巧極爲實用。
提示1-20:在難以一次性求出最後結果時,能夠用變量儲存「臨時結果」,從而逐步更新。
通過前幾個小節的學習,相信讀者已經初步瞭解順序結構程序設計和分支結構程序設計的核心概念和方法,然而對這些知識進行總結,而且完成適當的練習是很必要的。
首先,咱們給出一些實驗,旨在加深讀者對本章內容的認識。
實驗A1:表達式11111*11111的值是多少?把5個1改爲6個1呢?9個1呢?
實驗A2:把實驗A1中的全部數換成浮點數,結果如何?
實驗A3:表達式sqrt(-10)的值是多少?嘗試用各類方式輸出。在計算的過程當中系統會報錯嗎?
實驗A4:表達式1.0/0.0、0.0/0.0的值是多少?嘗試用各類方式輸出。在計算的過程當中系統會報錯嗎?
實驗A5:表達式1/0的值是多少?在計算的過程當中系統會報錯嗎?
你沒必要解釋背後的緣由,但需注意現象。
若是用語句scanf(「%d%d」, &a, &b)來輸入兩個數,那麼這兩個數應以怎樣的格式輸入呢?(提示:能夠在輸入以後用printf打印出來,看看打印的結果是否和輸入一致。)
實驗B1:在同一行中輸入12和2,並以空格分隔,是否獲得了預期的結果?
實驗B2:在不一樣的兩行中輸入12和2,是否獲得了預期的結果?
實驗B3:在實驗B1和B2中,在12和2的前面和後面加入大量的空格或水平製表符(TAB),甚至插入一些空行。
實驗B4:把2換成字符s,重複實驗B1~B3。
一樣,你沒必要解釋背後的緣由,但需注意現象。
和上面的實驗不一樣,除了注意現象以外,你還須要找到問題的解決方案。
實驗C1:僅用一條printf語句,打印1+2和3+4的值,用兩個空行隔開。
實驗C2:試着把%d中的兩個字符(百分號和小寫字母d)輸出到屏幕。
實驗C3:試着把\n中的兩個字符(反斜線和小寫字母n)輸出到屏幕。
實驗C4:像C二、C3那樣也須要「特殊方法」才能輸出的東西還有哪些?哪些是printf函數引發的問題,哪些不是?
如何用實驗方法肯定如下問題的答案?注意,不要查書,也不要在網上搜索答案,必須親手嘗試——實踐精神是極其重要的。
問題1:int型整數的最小值和最大值是多少?(須要精確值)
問題2:double型浮點數能精確到多少位小數?或者,這個問題自己值得商榷?
問題3:double型浮點數最大正數值和最小正數值分別是多少(沒必要特別精確)?
問題4:邏輯運算符號&&、||和!(它表示邏輯非)的相對優先級是怎樣的?也就是說,a&&b||c應理解成(a&&b)||c仍是a&&(b||c),或者隨便怎麼理解均可以?
問題5:if(a) if(b) x++; else y++的確切含義是什麼?這個else應和哪一個if配套?有沒有辦法明確表達出配套方法,以免初學者爲之困惑?
對於很多讀者來講,本章的內容都是直觀、容易理解的,但這並不意味着全部人都能很快地掌握全部內容。相反,一些勤于思考的人反而更容易對一些常人沒有注意到的細節問題產生疑惑。對此,筆者提出以下兩條建議。
一是重視實驗。哪怕不理解背後的道理,至少要清楚現象。例如,讀者若親自完成了本章的探索性實驗和上機練習,必定會對整數範圍、浮點數範圍和精度、特殊的浮點值、scanf對空格、TAB和回車符的過濾、三角函數使用弧度而非角度等知識點有必定的瞭解。這些東西都沒有必要死記硬背,但必定要學會實驗的方法。這樣即便編程時忘記了一些細節,手邊又沒有參考資料,也能輕鬆得出正確的結論。
二是學會模仿。本章始終沒有介紹#include<stdio.h>語句的做用,但這絲絕不影響讀者編寫簡單的程序。這看似是在鼓勵讀者「不求甚解」,但實爲考慮到學習規律而做出的決策:初學者自學和理解能力不夠,自信心也不夠,不適合在動手以前被灌輸大量的理論。若是初學者在一開始就被告知「stdio是standard I/O的縮寫,stdio.h是一個頭文件,它在XXX位置,包含了XXX、XXX、XXX等類型的函數,能夠方便地完成XXX、XXX、XXX的任務;但其實這個頭文件只是包含了這些函數的聲明,還有一些宏定義,而真正的函數定義是在庫中,編譯時用不上,而在鏈接時……」,多數讀者會茫然不知所云,甚至自信心會受到打擊,對學習C語言失去興趣。正確的處理方法是「抓住主要矛盾」——始終把學習、實驗的焦點集中在最有趣的部分。若是直觀的解決方案行得通,就沒必要追究其背後的機理。若是對一個東西不理解,就不要對其進行修改;若是非改不可,則應根據本身的直覺和猜想嘗試各類改法,而沒必要過多地思考「爲何要這樣」。
固然,這樣的策略並不必定持續好久。當學生有必定的自學、研究能力以後,本書會在適當的時候解釋一些重要的概念和原理,並引導學生尋找更多的資料進一步學習。要想把事情作好,必須學得透徹——但沒有必要操之過急。
程序設計是一門實踐性很強的學科,讀者應在繼續學習以前確保下面的題目都能作出來。
習題1-1 平均數(average)
輸入3個整數,輸出它們的平均值,保留3位小數。
習題1-2 溫度(temperature)
輸入華式溫度f,輸出對應的攝氏溫度c,保留3位小數。提示:c=5(f-32)/9。
習題1-3 連續和(sum)
輸入正整數n,輸出1+2+…+n的值。提示:目標是解決問題,而不是練習編程。
習題1-4 正弦和餘弦(sincos)
輸入正整數n(n<360),輸出n度的正弦、餘弦函數值。提示:使用數學函數。
習題1-5 距離(distance)
輸入4個浮點數x1, y1, x2, y2,輸出平面座標系中點(x1,y1)到點(x2,y2)的距離。
習題1-6 偶數(odd)
輸入一個整數,判斷它是否爲偶數。若是是,則輸出「yes」,不然輸出「no」。提示:能夠用多種方法判斷。
習題1-7 打折(discount)
一件衣服95元,若消費滿300元,可打八五折。輸入購買衣服件數,輸出須要支付的金額(單位:元),保留兩位小數。
習題1-8 絕對值(abs)
輸入一個浮點數,輸出它的絕對值,保留兩位小數。
習題1-9 三角形(triangle)
輸入三角形三邊長度值(均爲正整數),判斷它是否能爲直角三角形的三個邊長。若是能夠,則輸出「yes」,若是不能,則輸出「no」。若是根本沒法構成三角形,則輸出「not a triangle」。
習題1-10 年份(year)
輸入年份,判斷是否爲閏年。若是是,則輸出「yes」,不然輸出「no」。提示:簡單地判斷除以4的餘數是不夠的。