前面已經做了兩個題目,都是輸出一個固定的字串。但是這樣的程式好像沒有太大的用處,現實生活中,大部份的程式都需要輸入一些資料,再根據所輸入的資料來做一些處理然後再輸出所得到的資訊。接下來我們就來看第一個需要輸入資料的題目:
這個題目要求我們輸入一個西元年份,把它轉成民國年份後輸出。這題很簡單,因為大家都知道只要把西元年份減掉 1911 就會變成民國年份了。問題是在C++ 的程式中要如何去輸入呢?
C++ 中的輸出指令是 <<,輸出的標的如果是螢幕,就用 cout。而 C++ 中的輸入指令則是>>,輸入的來源如果是鍵盤,就用 cin (代表 console input,唸成 see-in)。因此,在C++中要輸入資料可以寫成以下陳述式:
cin >> 變數;
這個陳述式所代表的動作為「從鍵盤輸入資料並存入指定的變數中」。在執行這個指令之前,我們要先定義一個變數來接收從鍵盤所接收到的資料。定義變數的語法如下:
資料型態 變數名稱;
比如說,如果你要定義一個名稱為 y 的整數變數,你可以寫成:
int y;
其中的 int 為型態名稱,y 則是所定義的變數名稱。當我們在程式中定義一個 int 變數時,VC++ 會為這個變數保留 4 個 Byte來它儲存這個數字。C++ 定義了許多不同的資料型態,int 是其中的一種,這個型態的變數可以儲存一個介於 (含) -2147483648 ~ 2147483647 之間的整數。
其實除了 int 以外,C++還定義了很多其它的「基礎資料型態」。坊間的電腦書多是在第一章或第二章就開始介紹各種資料型態,但是沒寫過程式的初學者很難了解這些概念。因此,我們把「基礎資料型態」留到第五章再仔細地討論,第四章以前所有的題目都只需要 int 變數就夠了。
在數學上,我們習慣使用一個字母的變數名稱,但是在寫程式時,我們卻希望變數名稱可以望文生義、不解自明。例如上面的例子我們定義了一個名稱為 y的變數,看程式的人大概很難從它的名稱去猜到這個變數所代表的意義。但是如果你把變數名稱定義為year,那麼每個人都可以知道這個變數就是用來儲存年份的。
C++允許你用較長的變數名稱來增加程式的可讀性,但是變數名稱的訂定也有它的規則:p
下表則是一些合法及不合法的變數名稱:
待補
不過由於本課程是以解題為主軸,而這些題目上如果已經定義了一些變數的名稱,我們倒是不妨予以沿用。一來這些變數名稱在題目中已經定義好了,會去看你的程式的人通常也都已經先看過題目了,所這些變數名稱不是沒有意義的。二來這些變數名稱比較短,可以讓你的程式看起來「簡潔」些。這題的「輸入說明」中已經把輸入的整數定義為y,代表一個西元年份,所以我們的程式中的變數不妨也沿用這個名稱。
有些解題的程式師喜歡把程式寫得很短,有的線上裁判甚至會公布你的程式碼的長度,於是就有人想盡辦法來縮短程式碼,以炫耀自己的功力。以軟體工程的角度來看,這是很要不得的,因為程式的可讀性遠比它的長度來得重要。筆者還是鼓勵大家用有意義的字來作為變數名稱。就算要使用較短的變數名稱,好歹也要用有意義的文字的縮寫。以下面的等加速度物體的位移公式為例:
d = v0t + 1/2 at2
其中的變數名稱雖然都只有一個字母,但卻都是有意義的:
d: displacement (位移)
v0: velocity at time 0 (時間 0 時的速度,初速度)
t: time (時間)
a: acceleration (加速度)
定義好了變數,也用 >> 從 cin 輸入了一個值並存到變數 y 裡,接下來我們就可以用 y來做運算,並把結果顯示出來,所得的程式如下:
電腦在執行你的程式時,是按照順序一行一行往下執行的,所以上面這個程式的三個陳述式出現的順序不可以隨意調換。一定要先用 int y;定義一個變數 y,接下來才會有一個變數 y 可以讓 cin >> y;來輸入資料;一定要先輸入一個西元年份 y,才能根據 y來計算民國年份。
程式寫好了,按 F5 開始執行。這時候出現了一個黑底的 DOS 視窗,但視窗上卻沒有任何的顯示。這時候請按一下工具列的
通常使用者在執行程式時,如果看到一個黑色的畫面卻沒有任何的顯示,由於他們不知道程式正在等他們輸入,所以他們很可能會以為程式當掉了。為了避免這樣的誤會,一般的程式設計書籍會建議在讓使用者輸入資料前,先輸出一些提示,如下:
如此一來,程式執行時就會先顯示「Please enter a year:」,使用者看到時便會知道要輸入一個年份(如果他英文不是太爛的話)。不過這樣的做法在解題時卻反而會造成一些問題,因為我們的程式不是要給一般的使用者來執行的,而是要上傳給線上裁判來執行。線上裁判不需要任何提示,只要程式一開始執行,它就會自動依題目中「輸入說明」的規定開始輸入資料。而程式中輸出到 cout的任何資料卻都會被視為你的答案的一部份,甚至包括一開始的「Please enter a year:」。由於你的答案比預存在伺服器上的標準答案多出了一些文字,比對的結果當然也就不一樣,你的程式也會因此而得到WA。因此,在寫解題用的程式時,除了在「輸出說明」所要求的輸出以外,千萬不要再畫蛇添足地加一些文字,因為那會影響答案的判斷。
現在你知道如何輸入一個值到變數裡,然後再利用那個變數去計算題目所要求的結果了。現在讓我們來看看這一題:
這個題目看起來很複雜,其實它非常簡單。不管賽程如何安排,每一場比賽都會淘汰一個隊伍,所以如果有 n 個隊伍,那麼就要舉行 n - 1場比賽才能產生最後的冠軍。簡單嗎?趕快去增加你的 AC 題數吧!
再練習一題:
題目大意:輸入的值只有兩種可能:0 與 1,請你輸出與輸入「相反」的數字,也就是輸入如果是 0,請你輸出 1;輸入如果是 1,請你輸出 0。
假設所輸入的值儲存於變數 x 中,你可以利用之前所教的「算術運算子」算出與 x「相反」的值嗎?
這題可以有很多種不同的寫法,你不妨先拿出一張紙來,不要看下面的答案,自己試著想一想,看看你的方法和筆者一不一樣。
直接用 1 去減掉 x 就好了。
在 C++ 所定義的 5 個「算術運算子」中,「/」(除) 是要比較小心的一個,因為它所執行的是「整數除法」。如果你執行:
所顯示的結果不會是 2.75,而是 2。
筆者戲稱它為「小三除法」(是「小三」,請你不要倒過來唸!),因為小學三年級的小朋友沒有學過小數,如果你要他算 11 除以4,他只會算到整數位,然後告訴你「2餘3」。也就是說,他會給你兩個值,一個是「商」、一個是「餘數」。但是在程式語言中,一個運算子只能回傳一個值,於是 C++ 便分別為「商」和「餘數」各定義了一個運算子:「/」傳回商,「%」傳回餘數。因此,p
會顯示 2,而
會顯示 3。
在上一節中,我們用減法很簡單地就解決了問題。但是那並不是唯一的解法,你也可以用餘數來解這一題:
先加 1 再除以 2 求餘數。如果 x 是 0,加 1 後就變成 1 了,但是如果 x 是 1,加 1 以後雖然變成了 2,但是再除以 2求餘數就變成 0 了。
那如果你同時需要商及餘數時怎麼辦?這時候你只能用「/」及「%」各算一次了。下面這題便是一個例子:
題目大意:一支鉛筆 5 元,一打鉛筆 50 元。n 支鉛筆多少錢?
你可以用「/」求出要買幾打、用「%」求出要零買幾支,再依價錢算出總價即可。
2.1.2 無條件進位
由於「/」只算到整數位,之後的便捨去不再計算,因此,這樣的商可以視為是「無條件捨去」之後的結果。可是如果要「無條件進位」時怎麼辦?
題目大意:從1號開始,每3個人分成一組。給你某人的編號n,請問他編在第幾組?
既然是每三個人一組,當然會用到 / 3 的運算。但是如果你直接寫:
這個寫法只有在n是3的倍數時才會有正確的結果,其他的情況所求出的組別會比實際的組別少 1。比如說,1 號和 2 號除以 3 會得到 0。
從另一個角度來看,「/」是無條件捨去,但是這題是要無條件進位。在執行整數的除法運算時,如果要無條件進位,只要在除以前先加上比除數還小 1的值即可。比如說,這題的除數是 3,那麼就在除法運算之前先加上 2 就可以了:
接下來再給你兩個練習題。在做這兩題時,提醒你要善用 % (餘數)運算子。
我們再看一題:
這個題目要求我們計算 a 與 b 之間一共有幾個偶數。在計算之前,我們必須先定義並輸入 a 及 b 這兩個變數。首先,定義兩個變數你可以這麼寫:
但是如果你要定義的兩個變數的型態是相同的,(在這個兩個變數都是 int 型態),你可以把它們寫在同一個陳述式裡:
兩個變數之間要以一個逗號隔開來。
接下來要輸入這兩個變數的值,我們可以這樣寫:
題目中的「輸入說明」部份提到所輸入的這兩個變數會用空白隔開來。其實 >> 可以讀入以任何「白空白」(white space)隔開的數字,也就是你在輸入這兩個數字時,把這兩個數字打在同一行,用空白隔開來;或是兩個數字各自打在一行都沒有關係,因為「空白」與「換行」都是「白空白」。
如果兩個數字是用白空白以外的字元隔開的話,那就比較麻煩了。如果我們輸入「1,2」,第一個陳述式 cin >> a; 會把 1 讀到a 裡,第二個陳述式 cin >> b; 卻讀到了「,2」,C++ 並沒有辦法正確地把 「,2」轉成整數,所以 b就沒有辦法得到正確的值了。還好,絕大部份的題目的輸入資料都是用空白來隔開數字,所以不用太擔心這個問題。
當我們有兩個、或兩個以上的變數需要輸入時,其實也可以把它寫成一行,(不同型態也沒有關係):
為什麼可以寫成這樣呢?這個你得耐心聽我講。 之前當我們講到 >> 時,我們說它是「輸入指令」,<< 則是「輸出指令」。其實用更確切的方式來說,它們是運算子:>> 是「輸入 (擷取) 運算子」(Extraction Operator);<< 是「輸出 (插入)運算子」(Insertion Operator) 。查閱附錄B的運算子優先順序表,你會發現它們的優先順序為6,結合性則是由左至右。這種運算子的運作方式顛覆了你對運算子的既有印象。 傳統的算術運算子在執行過後會傳回一些我們所要的資訊。比如說 + 運算子會傳回兩數的和,- 運算子會傳回兩數的差......等。如果你執行123 + 456 的運算,它就會產生 579 的結果回傳給你。但是實在看不出來陳述式 cin >> a; 中 >>這個運算子倒底做了什麼「運算」?的確,並不是每 C++中的運算子都會透過計算產生一些計算來產生新的資訊回傳給你,在使用這些運算子時,它們的「副作用」(Side Effect)要遠比它們的回傳值還來得重要。 所謂的「副作用」就是在執行運算時對程式中的變數或執行環境產生了一些改變。以陳述式 cin >> a;為例,它的副作用就是從鍵盤輸入一個整數,並存入變數 a 之中。這個運算執行完畢時,a 的值就會被改變,這就是 >>運算子的「副作用」。 相對地,算術運算子卻是完全沒有「副作用」運算子。當你執行 a + b 的運算時,它會產生 a 與 b 的和並回傳給你,但是運算執行完後,變數a 與 b 本身的值並沒有任何改變。 就像算術運算子會有回傳的值一樣,其實每一個運算子都會有一個回傳的值。可是 >> 和 << 運算子倒底會回傳什麼東西呢? >> 和 << 運算子在「運算」完畢之後,會回傳該運算子左側的「物件」。以陳述式 cin >> a;為例,>> 左側的物件就是 cin 本身,所以它回傳的「值」也是 cin。C++語言會自動忽略每個陳述式最後所回傳的值,所以執行陳述式 cin >> a; 之後所回傳的 cin 也被 C++所忽略,所以我們平常並不太在意 >> 運算子回傳的「值」是什麼。 但是當你在一個陳述式中使用好幾個 >> 運算子時,它的回傳值就很值得玩味了。 在陳述式 cin >> a >> b; 中,我們用了兩個 >> 運算子。因為 >>運算子的結合性為由左至右,因此左邊的那個 >> 運算子會先「算」,算完以後會回傳 cin,那麼下一個 >>運算子的運算就變成 cin >> b; 了。 為了讓你有個更清楚的概念,我們先用算術運算子的例子來讓你了解「回傳值」的應用。以下為運算式 1 + 2 * 3 的求值過程: 1 + 2 * 3 因為 * 的優先順序為 4,+ 的優先順序為 5,所以 * 先算。 1 + (2 * 3) 2 * 3 的回傳值回 6,所以用 6 來取代 (2 * 3)。 1 + 6 1 + 6 的回傳值為 7,所以用 7 來取代 1 + 6。 7 最後的回傳值為 7。 接下來我們來看看運算式 cin >> a >> b 的求值過程。 cin >> a >> b >> 的結合性為由左至右,所以左邊的>> 先算。 (cin >> a) >> b cin >> a 的回傳值為 cin(副作用:輸入 a 值),所以用 cin 來取代 (cin >> a) cin >> b cin >> b 的回傳值為 cin (副作用:輸入 b值),所以用 cin 來取代 cin >> b cin 最後的回傳值為 cin。 因為 C++ 會自動忽略陳述式的最後一個回傳值,所以陳述式 cin >> a >> b; 最後的回傳值 cin被忽略掉了。但是這類運算子重要的是它們的「副作用」,雖然最後的回傳值被忽略了,但是在求值的過程中,變數 a 和 b 的值卻都已經輸入完畢。 由於 C++會自動忽略最後一個回傳值,所以一個陳述式的最後一個執行的運算子必須是有「副作用」的,否則那個運算子就沒有意義了。比如說,下面這個陳述式是完全合法的,你也可以把它編譯後執行,但是這個陳述式卻沒有任何意義,因為它辛苦求得的最後回傳值 106638 被 C++ 給忽略了。 123 + (456 - 321) * 789; 但是如果把它改成: cout << 123 + (456 - 321) * 789; 這陳述式中最後一個執行的運算為 cout << 106638,其回傳值為 cout,雖然它被 C++ 給忽略了,但是運算式 123+ (456 - 321) * 789 的結果卻已經透過 << 的「副作用」顯示在螢幕上了。 |
下面這個程式可以輸入兩個整數的值 a, b (b ≥ a),然後輸出它們的差:
但是「d485. 我愛偶數」這題不是要你求它們的差,而是要你求它們之間有幾個偶數。很難嗎?沒有關係,筆者給你一些提示:≥ a 的最小偶數為 a+ a%2;≤ b 的最大偶數為 b - b%2。這樣你會算了嗎?
小提醒:要注意運算子的順序,必要時要加括號。
給你三角形的三邊長,要你求出這個三角形的面積的平方。
講到三角形的面積,你一定會想到「海龍公式」。
T=sqrt{s(s-a)(s-b)(s-c)} sqrt:根號
其中 s=(a+b+c)除以2.
只是因為我們求的是面積的平方,所以「海龍公式」最後一個開平方的動作就可以不用作了。可是要把這個公式寫成程式時,卻產生了一個問題:這個公式是二段式的,我們得先求出 s (週長的一半),再把 s 代入公式的第二段。可是到目前為止,我們所使用的 cout <<輸出方式好像只能一次就把答案求出來,不能先產 s 這個中間產物。當然,我們也可以把 s 直接代入第二段寫成以下陳述式:
我們也可以用數學的觀念將這個陳述式簡化為:
但是它還是比原來的海龍公式還要長且複雜,電腦要花較長的時間來計算這個公式,而且沒有幾個人看得出來這是海龍公式。
要達到海龍公式原始的二段式模式,我們需要用到「指定運算子」(Assignment Operator),也就是 =(等號)。和大多數的算術運算子一樣,「=」是一個二元運算子,也就是說,等號的前後都需要有一個運算元。其語法為:
v = Ep
在等號的左邊必須擺一個變數,把 = 右邊的值指定給 = 左邊的變數。
和 << 及 >> 運算子一樣,這個運算子的「副作用」比它的回傳值還重要。
參考一下附錄 B 的運算子優先順序,/ 的優先順序為 4,= 的優先順序為 15,所以 / 會先算,所得的回傳值會由 =運算子指定給s作為它的值。當這個陳述式執行完畢時,螢幕上並不會有任何的顯示,但是 s 會得到一個新的值,這就是 = 運算子的「副作用」。
一旦 s 變數內已經有了週長的一半,接下來我們就可以把它代入第二階段的海龍公式並把結果輸出了。
題目中有說明三邊長 a, b, c 均為整數,即使如此,所求得的 s 卻可能有小數。不過題目也說明了,答案一定是整數,可是如果 s 有小數且a, b, c 均是整數,答案就不可能是整數。因此,題目中所給的資料所求出的 s 也不可能有小數,你可以放心地把 s 定義為整數變數。
要計算一個整數區間 (a 與 b 之間) 的偶數和,我們可以利用梯形公式。如果 a 與 b 本身都是偶數,這題就簡單了:
但是因為 a 和 b 不一定是偶數,所以我們得額外再做些處理。之前我們在解「d485.我愛偶數」時,用的就是上面「高」的公式。在「高」的公式中,a 和 b 都只出現一次,我們可以直接用 a + a%2 來取代公式中的 a、用 b- b%2 取代公式中的 b 就行了。但是這次我們用的是「面積」的公式,其中 a 和 b 各出現 2次,如果我們還是直接代進去,一來公式變很長,二來額外的運算也會浪費 CPU 的時間。
即然我們學過了 = 運算子,我們可以用之前的二段式處理:
a' = a + a%2
b' = b - b%2
然後再用 a' 及 b' 去代上面的梯形面積公式。
但是之前我們說過,變數名稱中只能有英文字母、數字、及底線。因此 a' 及 b' 並不是合法的 C++ 變數名稱。如果你要的話,可以用 a1 及b1 來代替 a' 及 b' 。
你只要把上面的這段程式套入程式的「殼」裡,並定義及輸入所需要的變數,這題就可以 AC了。不過筆者在這裡要更進一步地討論指定運算字的一些特性與運用。
我們先仔細看一下 a1 = a + a%2; 這個陳述式。在這個陳述式中一共有 =, +, 及 %三個運算子,根據運算子的優先順序,它們執行的順序為先算 %,再算 +,最後再算 =。
假設 a 等於 5,那麼 a + a%2 就會等於 6,仔細觀察一下上面的程式,當電腦算完這個部份的運算之後,a變數的值就再也沒有用到了,所以 a 內容也沒有繼續保留的必要。這時候程式大可以把所求得的 6 直接回存到 a 變數裡,而不需要再另外定義一個a1 變數。於是這個陳述式就變成了:
這樣的寫法對很多初學者而言是難以接受的,因為他們會把它和數學上的表示法互相混淆。以數學的角度來看上面的式子,你會說 a一定是偶數,否則它不成立。但是這個 = 運算子並不是數學上用在等式中的那個等號,而是用來指定一個值給它左側的變數。我們來看另一個更極端的式子:
從數學的角度來看,這個式子根本就是無解。可是在 C++ 裡,這個陳述式卻代表把 a 變數的值加 1。如果 a 原來是 5,執行後就會變6;如果 a 原來是 6,執行後就會變 7。
這樣的運算式在電腦中很常見,我們通常稱之為「累加」。除了「累加」以外,如果搭配 *運算子的話,就成了「累乘」了,而這類的運算我們就統稱為「累算」。由於這類的運算很常見,所以 C++就提供了更簡潔、更有效率的「複合指定運算子」(Compound Assignment Operators)。
a = a + 1; 可以寫成 a += 1;
a = a + a%2; 可以寫成 a += a%2;
a = a * 10; 可以寫成 a *= 10;
以此類推
你可以在附錄 B 的運算子優先順序表中找到所謂的「複合指定運算子」。不過使用時要注意,和 = 運算字結合的運算子必須是最後一個執行的運算,例如:
a = a * 10 + 1; 就不可以寫成 a *= 10 + 1;
因為 a *= 10 + 1; 的效果其實等於 a = a * (10 + 1);
如果 a 原來等於 5,執行 a = a * 10 + 1; 之後 a 會等於 51;可是執行 a *= 10 + 1; 之後 a 卻變成55 了。
整合以上的討論,本題的程式如下:
把一個較複雜的程式分成幾段來寫也比較容易讓人了解。分段的方式也方便我們為程式加上「註解」。在 C++ 裡註解是以 // (兩個斜線)開始,至該行的結尾結束。以「d490. 我也愛偶數」的程式為例,我們為它加上註解如下:
當程式在編譯時,所有介於 //與行尾之間的文字都會被編譯器所忽略,因此它不會對程式的執行產生任何影響。註解的主要目的是為了讓程式比較容易看懂。很多程式師懶得為自己的程式寫註解,結果幾年後連他自己都看不懂自己的程式,因此程式師們應該養成寫註解的良好習慣。
C++ 有兩種註解的方式:「單行註解」及「多行註解」。上面所介紹的是單行註解,這種註解的方式是 C++ 才有的新方式,C語言只有多行註解可用。多行註解以 /* 作為開始,以 */ 作為結束,中間可以打很多行的註解。上面的程式改成多行的註解方式如下: 多行註解有很多的問題。首先,如果第 8 行的結尾你忘了打 */ (如下),那麼第 9 行整行都會被視為註解,其中的 b -= b % 2;陳述式也不會執行了。 像這樣的錯誤並不會被編譯器偵測到,程式可以順利執行,但是結果卻不對,很難去抓錯。/p 其次,有時候我們會想要取消一部份程式碼的作用,但是又不想要把它刪掉,我們會把它們變成「註解」。這時候你可能會認為多行的註解比較好用,因為你要在要註解掉的程式碼的前後分別加上一個 /* 及 */ 就可以了,哪怕是幾百行的程式也是一次 OK!不過這是很危險的一件事。以上面這個程式為例,把第8 行及第 9 行註解掉以後程式如下: 要留意多行註解是不能套疊的,第 8 行的 /* 開始了註解,可是到了第 9 行的行尾看到了 */,以為註解已經結束了,所以便開始執行第 10行的程式。 因此我們不建議使用多行的註解,(可憐的 C 程式師只有多行註解可用)。 其實現的編譯器提供的很強大的編輯功能。以 C++ 為例,如果你要把一大段的程式碼註解掉,你只要把那段程式碼反白,再按工具列上的 |