# Ch5. 字串 解題指引 ## 先備知識 ### 字元 `char`是C/C++的基本資料型態,大小為一個位元組 (1 byte),可以儲存單一個字元。 例如,宣告一個字元型態變數`ch`,值為字母 A,寫法如下: ```cpp char ch = 'A'; // 將字元 A 設定給字元變數 ch。 ``` C++使用一對**單引號`''`**,將一個字元括起來,代表他的 ASCII 碼。 我們可以宣告字元變數,搭配使用`cin`,嘗試從鍵盤讀入一個字元: ```cpp char c; cin >> c; cout << c; ``` 注意`cin`會跳過換行、空白、tab等空字元,只讀取一個字母。因此,若輸入為:`a b` ,以上程式將只會輸出字母 a 。 ### ASCII code ASCII code 全名為「American Standard Code for Information Interchange」,是一套依據拉丁字母的電腦編碼系統。在電腦中,所有的資料在儲存和運算時都要使用二進位數表示。而具體用哪些二進位數字表示哪個符號,這就是編碼。如果不同的電腦要想互相通信而不造成混亂,那麼每台電腦就必須使用相同的編碼規則,於是美國有關的標準化組織就推出了ASCII編碼。 字元最大的用處,就是以 ASCII 碼的形式,將文字資訊顯示出來。基本 ASCII code 對照表如下。數字 0~127 ,分別對應到大、小寫英文字母、阿拉伯數字、一些半形標點符號,還有一些控制字元。  我們可以將ASCII碼,設定給字元變數。例如,將字元 A 設定給字元變數 ch,以下寫法有相同的效果: ```cpp char ch = 65; // 將 ASCII 碼為 65 的字元設定給字元變數 ch。 ``` 程式將 ASCII 碼指定給`char`型態的變數,因此若接續以`cout`輸出,會顯示字母 A,而非整數 65。 ```cpp cout << ch; // A ``` ### 字串 當我們想要表達的文字超過一個字母時,就必須使用 **「字串」**。在C++語言中,有兩種儲存字串的方式,均須使用雙引號`""`將所需字串括起來: **1) 以字元陣列(`char array`)來宣告字串:** ```cpp char s2[ ] = "HAPPY" ; ``` 在上面這個例子,記憶體會使用連續的 6 個位元組來記錄字串`"HAPPY"`,參考課本3-5。最後一個字元為 `'\0'`,代表字串的結束。這種用法同時適用於C語言,但使用方法比較繁瑣,課堂上就不說明了,有興趣的孩子可以閱讀課本相關章節,或是參考[這篇教學文章](https://www.csie.ntu.edu.tw/~b98902112/cpp_and_algo/cpp02/string.html)。 **2) 以`string`類別宣告字串:** ```cpp string s1 = "abcd"; ``` `string` 是一個保存 `char` 的類別物件,是一個**長度可變動**之字元序列,減輕了 C 語言風格字串的麻煩。如需使用`string` ,必須引入標頭檔`string` 。 現在,我們來寫一個程式,使用者輸入什麼字,程式就吐出什麼字: ```cpp #include <iostream> #include <string> using namespace std; int main() { string s; // 宣告字串s cin >> s; // 輸入字串s cout << s << endl; // 輸出字串s return 0; } ``` | 範例輸入 | 範例輸出 | | --- | --- | | `Hello` | `Hello` | 從以上程式可以發現,在`main`函式之前要加上一行`#include <string>`,引入標頭檔`string`,否則編譯可能會發生錯誤。 請注意,如果讀取字串遇到空白,換行等空字元就會斷開,無法讀取整行。我們可以使用以下程式,將單字一個、一個讀取,作出相應行為,例如一個一個輸出: ```cpp string s; // 宣告字串s while(cin >> s) // 只要有下一個輸入字串s cout << s << endl; // 就輸出字串s ``` | 範例輸入 | 範例輸出 | | --- | --- | | `Tony Stark` | `Tony`<br/>`Stark` | 然而在某些狀況下,我們會需要一次讀取一整行輸入,此時就必須搭配使用`getline()`函式,請參考課本 3-5 的說明。 ## 秘密差 ### 解題步驟 - 以字串型態讀取輸入數字。 - 使用迴圈遍歷字串中每一個字元,根據索引值判斷奇/偶位數。 - 字元轉整數,奇/偶位數分別加總後求出秘密差。 ### 引導問題(點擊箭頭展開提示) ::: spoiler **Q. 如何輸入字串,然後依序檢查字串中的每個字元?** A. 建議使用`string`。接著請在課本中尋找,用什麼函式能取出**字串長度**,作為`for`迴圈的終止條件?在迴圈之中,又如何表示字串內的每個字元? ::: ::: spoiler **Q. 如何將每個位數的數字,從`char`轉成`int`型態?** A. 以下舉例說明,如何將數字 123 的每個位數依序顯示出來,中間以空白間隔。 【錯誤方法】 ```cpp string s = "123"; for(int i = 0; i < 3; i++) cout << (int)s[i] << " "; // 輸出結果:49 50 51 ``` 以上程式直接使用`int`進行型別轉換,然而轉換後的結果依序為數字 1, 2, 3 的ASCII碼,並非其真正的數值。 * ==【法一】扣掉 0 的 ASCII code== 已知數字 0 的 ASCII code 為 48 ,那麼將每個數字字元轉換成 ASCII 碼之後,每個都扣掉 48,就是原始數值了。 ```cpp string s = "123"; for(int i = 0; i < 3; i++) cout << (int)s[i] - 48 << " "; // 輸出結果:1 2 3 ``` * ==【法二】直接扣掉 '0'== 如果忘記數字 0 的 ASCII 碼,那有什麼關係!我們其實可以把兩個字元直接做加減,計算他們的「距離」。 上面的程式碼微調如下,計算字元`s[i]`與字元`0` 的 ASCII 碼相差多少,同樣能夠依序顯示 1, 2, 3。 ```cpp string s = "123"; for(int i = 0; i < 3; i++) cout << s[i] - '0' << " "; // 輸出結果:1 2 3 ``` ::: ::: spoiler **Q. 如何使用 `for` 迴圈,將每一回合得到的數字加起來?** A. 宣告總和變數,累加計算結果,上禮拜應該有稍微複習到。以下舉例說明,如何計算數字 1 ~ 10 的總和。 ```cpp int sum = 0; for(int i = 1; i <= 10; i++) sum += i; cout << sum; // 輸出 55 ``` 因為本題要分別計算奇位數字和和偶位數字和,會需要兩個總和變數。因此,你需要在`for`迴圈中,判斷該位數是奇位數字還是偶位數字,累加到相應的總和變數。 ::: ::: spoiler **Q. 如何判斷是奇數位數字,還是偶數位數字?** 取決於使用`for`迴圈檢查字元的順序。如果你從字串第 0 位,也就是最高位開始檢查,它可能是奇位數字或偶位數字,需要用**字串長度**判斷是哪一種。如果你從字串最後一位開始檢查(小心迴圈起始條件),那它一定會是奇位數字。程式架構如下: ```jsx for(int i...){ if(...) // 奇位數字 {...} // 加總奇位數字和 else // 偶位數字 {...} // 加總偶位數字和 } ``` ::: ## **a108: 計算字串間隔距離** ### 解題步驟 - 依序輸入一個字串,與一個目標字母。 - 檢查字串中的每個字元,找出目標字母第一次出現在哪裡,記錄下來。 - 當目標字母再次出現,輸出間隔距離,並記下目前所在位置。 - 注意:指定字母「不分大小寫」。例如:輸入大寫 A ,那麼字串中的 A 和 a ,都要納入考量。 ### 引導問題(點擊箭頭展開提示) ::: spoiler **Q. 能不能先將每個字元轉成大寫或小寫?** A. 當然!我們可以使用內建的字元轉換函式,將傳進來的字元參數由小寫轉大寫,或是由大寫轉小寫。 使用`tolower()`,可將括弧內的字元轉成小寫字母。注意如果參數不是字母,或已經是小寫字母,就不會有任何變化。 使用`toupper()`,則可將括弧內的字元轉成大寫字母。參考範例: ```cpp char c; c = toupper('m'); cout << c; // 輸出 M c = tolower(c); cout << c; // 輸出 m ``` ::: ::: spoiler **Q. 如何判斷一個字母為大寫還是小寫?** A. 有兩種方法:內建函式法與ASCII 距離判斷法。 * ==【法一】內建函式法== 常見的幾種字元分類函式如下: | 函式名稱 | 說明 | | --- | --- | | `isalpha` | 判斷是否為英文字母 | | `islower` | 判斷是否為小寫字母 | | `isupper` | 判斷是否為大寫字母 | | `isdigit` | 判斷是否為數字 | | `isalnum` | 判斷是否為英文字母或數字 | 左方函式都有個共同特性:若符合條件,則回傳一非零整數。若不符合條件,則回傳 0 。 至於這些函式該如何使用呢?練習用關鍵字找答案,自己去google吧!答案都在那裡了! * ==【法二】ASCII 距離判斷法== 如果忘記或懶得查詢內建函式怎麼用,以下為萬用方法。 我們從ASCII code table可以發現,0 到 9、A 到 Z、a 到 z,其 ASCII code都是連續的。因此,我們可以將兩個 ASCII code 直接相減,以差值判斷未知字元是哪一種字元。 ```cpp char ch; cin >> ch; if(ch-'A' < 26){ // 與 A 的距離未滿26,代表 ch 為大寫 } else if(ch-'a' < 26){ // 與 a 的距離未滿26,代表 ch 為小寫 } else {// 其他輸入情況 } ``` 請注意,以上程式適用的情況有限。像是輸入為 0 - 9,扣掉字母 A 的 ASCII code,結果為負數,在這個程式就會被誤判為大寫字母。 ::: ## ROT13 ### 解題步驟 - 使用`getline()`讀取一整行輸入字串。 - 檢查字串中的每個字元,判斷是大寫字母、小寫字母,還是其他字元(如:空白、逗點、句點)。 - 其他字元維持不變,大、小寫字母分別取代成13位之後的對應字母。 ### 引導問題(點擊箭頭展開提示) ::: spoiler **Q. 如何將字串中每個字元「平移」一個特定數值?** A. 舉例如下: ```cpp // ... 前略 string s = "This is a book!"; for(int i = 0; i < s.size(); i++) s[i] = s[i] + 5; // 將每個字元都改成往後5個字元。 cout << s; // 輸出字串為:Ymnx%nx%f%gttp& // 後略... ``` ::: ::: spoiler **Q. 題目提到「有需要時則重新繞回26英文字母開頭即可」,具體要怎麼繞?** A. 我們先來解一個比較簡單的小任務:如何寫一個程式,將`ABCDEFGHIJKLMNOPQRSTUVWXYZ`往後平移 3 ,變成`DEFGHIJKLMNOPQRSTUVWXYZABC`?顯然,把 X, Y, Z變成 A, B, C,是解題的關鍵。我們就可以區分兩個子問題: 1) 如何判斷 ASCII code 會加到超過 Z?(想出**判斷式**) 2) 如何回到 A 之後,繼續加上沒加完的平移量?(想出**四則運算式**) 提示是上一題可以使用的 ASCII 距離判斷法,你可以運用加號和減號,直接將 `s[i]` 與 單一字元做運算。例如:`s[i] - ‘A’`,用以計算字串 s 的第 i 個字元,其 ASCII code 比字元`A`多多少。 以下程式有兩個地方打上`???`,請思考這兩個地方分別要填什麼,嘗試完成這個小任務。 ```cpp string s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for(int i = 0; i < s.size(); i++){ s[i] += 3; if(???) s[i] = ???; } cout << s; ``` 這個小任務可以有好幾種做法。例如,有些人可能想要先將 ASCII code 平移到 0 ,再運用取商跟取餘數運算子,除以 26 進行一些運算,就不需判斷 ASCII code 相加有無超過,這樣也很好! ::: ## (延伸練習)跑長編碼與資料壓縮 ### 引導問題 ::: spoiler **Q. 可以直接`cin`每一行字串嗎?** A. 觀察輸入說明及範例輸入,一長串連續的位元中,是否可能出現空格呢?如果保證每一行無空格,才能使用`cin`,否則就需要使用`getline()`函式來處理輸入。 ::: ::: spoiler **Q. 如何判斷輸入是否為一個2進制位元串?** A. 宣告一整數或布林型態的變數,再進入迴圈檢查。當遇到不是 0 或 1 的字元,要做什麼事情?當然,你也可以自定義一個函式(之後也會教),以回傳值代表是否為合理的2進制位元串。 ::: ::: spoiler **Q. 4位元碼字是什麼東西?為什麼重複位元的長度用 3 個位元,會導致最大連續長度為 7?** A. 需要先了解何謂二進位制,可以google一下。簡單來說,幾個位元就能夠表示 2 的幾次方個數字。從 0 開始的話, $n$ 個位元最大可以表示的數字為 $2^n - 1$。因此,重複位元的長度用 3 個位元,最大連續長度為 $2^3 - 1 = 7$。 ::: ::: spoiler **Q. 重複位元最大連續長度為 7,會如何影響我寫的程式?** A. 觀察範例測資中,前兩組測試資料。連續長度若超過 7,即使位元與前面相同,仍須重新統計,以下一組4位元碼字來表示。 ::: ::: spoiler **Q. 我已經統計出重複次數,如何轉換為二進位制?** A. 你可以使用: * ==【法一】小孬孬法(窮舉)== 先求有再求好!因為重複字元很少,也就 7 種可能,可以使用選擇結構,根據重複次數輸出相應結果。 * ==【法二】進位轉換法== 上網搜尋 10 進位轉 2 進位的方式,發現規律後,嘗試以`while`迴圈搭配字串組合來完成任務。 :::
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up