1. 串流 std::cin 與用 >> 讀取 token
當程式只是輸出時,它像廣播:向空間說話並希望有人在聽。但一旦出現輸入,程式就像對話:你提供資料,它回應。在 C++ 中通常透過 std::cin 來做輸入。把 cin 想像成一條字元帶(串流)會很有幫助:使用者輸入的東西放在上面,包括空格與換行。
最重要的一點:運算子 >> 並不會「讀取所有東西」,而是一次讀一份資料。而這份「資料」幾乎總是以空白字元為界限。
std::cin 與運算子 >>:將值讀入變數
要讀取東西,我們需要一個變數來放置它。邏輯是:宣告變數 → 將資料讀入變數 → 之後使用(例如輸出)。
最簡範例:讀取一個整數。
#include <iostream>
int main() {
int age = 0;
std::cin >> age;
std::cout << "Age = " << age << '\n'; // 如果輸入 20,將會:Age = 20
}
這裡要注意兩件事。首先,std::cin >> age; 會嘗試從輸入讀取下一個值並寫入 age。其次,變數的型別很重要:既然 age 是 int,程式期望輸入流中現在「放著」的是一個數字(例如 20、-5、0)。
到底 >> 讀取了什麼:token 與空白分隔符
現在會出現一個詞「token」,不用擔心:它只是「沒有空白的文字片段」。運算子 >> 的工作原則是:「跳過空白,然後連續取字元直到下一個空白」。這裡的空白不只指 ' '(一般空格),還包括換行 '\n' 與製表符 '\t'。
假設使用者輸入如下(符號 ⏎ 代表 Enter,也就是 '\n'):
12 34 ⏎
在 12 與 34 之間有三個空格。對於 >> 來說這不重要:它看到「數字」、然後「空白」、接著「數字」。
#include <iostream>
int main() {
int a = 0;
int b = 0;
std::cin >> a >> b;
std::cout << "a=" << a << ", b=" << b << '\n'; // 輸入 "12 34" 時:a=12, b=34
}
注意:std::cin >> a >> b; 是兩次連續的讀取,只是寫在同一行。先讀入 a,然後讀入 b。
分隔符:空格、Enter 與製表符同樣「切割」token
有時新手會以為 Enter 比空格「強」一點,或覺得空格只是為了美觀。對於 operator>> 而言恰好相反:任何空白字元都是 token 的邊界,就這麼簡單。
想像以下輸入(每個字在新的一行):
Ivan⏎
Ivanov⏎
以及這種輸入(兩個字在同一行):
Ivan Ivanov⏎
對下面程式碼來說,結果會是 相同的,因為空格與 Enter 都是 token 的分隔符。
#include <iostream>
#include <string>
int main() {
std::string first;
std::string last;
std::cin >> first >> last;
std::cout << first << " | " << last << '\n'; // Ivan | Ivanov
}
這是好消息:可以依照方便輸入資料(單行或多行)。但這也導致了接下來要討論的限制。
簡圖:>> 如何把輸入切成 token
為了把輸入從「魔法」變成模型,畫一次流程圖很有幫助。假設使用者這樣輸入:
23 Ada Lovelace⏎
那麼字元流可以在腦中被切成 token:
flowchart LR
A["23"] --> B["Ada"] --> C["Lovelace"]
而空格與換行只是被當作「分隔的糊糊」,在 token 之間被跳過。
一個很實用的結論是:如果先讀 int,再讀 std::string,那麼 int 會「吃掉」第一個 token,std::string 會讀下一個。>> 並不存在「你必須在新行輸入」的協定——它總是基於空白來切割。
2. 實作:簡易問卷與不含空白的字串
實作示例:製作簡易問卷
從現在開始我們會用同一個小專案:一個控制台問卷。它很簡單,但方便逐步擴充:今天讀 token,之後會學如何讀整行並妥善處理輸入。
版本 1:讀取年齡與名字(視為單一字)。
#include <iostream>
#include <string>
int main() {
int age = 0;
std::string name;
std::cin >> age >> name;
std::cout << "User: " << name << '\n';
std::cout << "Age: " << age << '\n';
}
如果輸入:
20 Ivan
程式會輸出:
User: Ivan
Age: 20
也可以這樣輸入(使用 Enter):
20
Ivan
就 >> 來說,這是等價的:token 會依序被讀取。
用 >> 讀字串:會讀到一個字,而非整句
現在進入常見驚訝:std::cin >> s 讀取 std::string 時只會取得一個字,也就是到空格/Enter/製表符為止的 token。如果你輸入多個字,它們不會消失,只會留在輸入流中等待被下一次的 >>
在我們的問卷上示範:讀取「city」為字串。
#include <iostream>
#include <string>
int main() {
std::string city;
std::cin >> city;
std::cout << "City=[" << city << "]\n";
}
若使用者輸入:
New York
那麼 city 會是 "New",而 "York" 會留在輸入流作為下一個 token。程式會輸出:
City=[New]
這不是錯誤或「怪異的 C++」,而是運算子 >> 的正常邏輯:它讀取一個 token。
說明性實驗:所謂「遺失」的字其實沒消失
那個沒有被放進第一個字串變數的字通常不會消失。它只是排隊等候下一次讀取。我們用兩個變數讀兩個字來示範。
#include <iostream>
#include <string>
int main() {
std::string w1;
std::string w2;
std::cin >> w1 >> w2;
std::cout << "w1=[" << w1 << "], w2=[" << w2 << "]\n";
}
如果輸入:
New York
會得到:
w1=[New], w2=[York]
也就是說,>> 非常適合「單詞輸入」的情境(名字、姓氏、隊伍名、登入名)。但對於像「地址」或「書名」這類通常含有空格的欄位,它就不方便了。
備忘:對於 >> 什麼算是分隔符
當你開始寫小程式時,會常常問自己「為什麼它在這裡停止?」通常答案如下表:
| 輸入中的字元 | 範例 | 對 >> 而言是… | 會發生什麼 |
|---|---|---|---|
| 空格 ' ' | |
分隔符 | 透過 >> 讀到的字串只會是 Ivan |
| 換行 '\n' (Enter) | |
分隔符 | 行為與空格相同 |
| 製表符 '\t' | |
分隔符 | token 同樣被分開 |
結論很簡單:>> 會讀到「最近的空白字元為止」。
如何根據 std::cin >> 選擇輸入格式
當你選擇使用 std::cin >> 時,實際上你在選擇資料格式:「使用者以獨立 token 輸入值」。這非常適合輸入格式像「兩個數字」、「三個數字」、「字與數字」、「名字與姓氏」、「指令與參數」的情況。
例如,我們的問卷在事先約定名字和姓氏分開輸入時就很適合 token 方式。可以把應用擴充為讀取名字、姓氏與年齡。
#include <iostream>
#include <string>
int main() {
std::string first;
std::string last;
int age = 0;
std::cin >> first >> last >> age;
std::cout << "Profile: " << first << " " << last << '\n';
std::cout << "Age: " << age << '\n';
}
如果輸入:
Ada Lovelace 36
那麼三個 token 會對應三個變數,一切正常。
微小差別:>> 不會把空格當作資料吃進來
當我們說「空格是分隔符」時要理解:空格不會成為被讀入字串的一部分。它只讀取 token 中「有意義的字元」。因此 std::string word; std::cin >> word; 永遠不會把 "Hello world" 這樣包含空格的整句讀入為一個 token:依 token 定義,內部的空格不可能出現。
若想讀取包含空格的整行,需要其他工具。這裡我們暫時不延伸:當前任務是熟練掌握 >>,不要期待它做未承諾的事。
3. >> 的限制:型別不匹配與安全讀取文字
當型別比你的期望更重要:讀取可能會「失敗」
第二大類限制來自型別不匹配。如果你正在讀取 int,但使用者輸入的是「文字」,程式無法把「kotik」(貓咪)正當地轉成數字。
舉一個看起來合理的情境:有人輸入了文字形式的年齡。
#include <iostream>
#include <string>
int main() {
int age = 0;
std::string name;
std::cin >> age >> name;
std::cout << "age=" << age << ", name=" << name << '\n';
}
如果輸入:
twenty Ivan
那麼程式就無法正確把 age 讀成數字。我們目前還沒學會優雅地處理這類錯誤(之後會在課程中討論)。因此現在有個簡單且誠實的規則:當你出題並指定輸入格式時,假定使用者會按照格式輸入正確的資料。
為何我們用 std::string 而不是 char[] 來讀字串
在舊的 C/C++ 範例中有時會看到像 char name[20]; std::cin >> name; 這樣的寫法。它可以工作,但有風險:如果使用者輸入過長的字,可能會造成緩衝區溢位等問題。
我們還沒學到陣列與指標,所以給新手的實務規則是:對於文字使用 std::string,它作為「字元容器」更安全得多。
4. 使用 std::cin >> 時常見錯誤
錯誤 1:期待 std::cin >> s; 會讀整個片語。
這是最常見的陷阱之一。新手輸入 New York,結果變數只得到 New。原因很簡單:>> 讀到空白分隔符為止。如果你需要包含空格的字串,表示選擇的輸入格式 >> 不合適。
錯誤 2:在讀取之前沒宣告變數。
有時候會想寫「讀入年齡」但不想先宣告變數,但 std::cin 不能把資料讀到「虛空」。必須先宣告一個合適型別的變數(例如 int age = 0;),然後再做 std::cin >> age;。這不是形式而已:變數是記憶體中放結果的位置。
錯誤 3:讀取順序與資料順序搞反。
如果程式做 std::cin >> age >> name;,但使用者輸入 Ivan 20,那麼 age 會嘗試接受 Ivan(這不是數字),讀取就會出錯。>> 不具備讀心術:它會按順序讀 token 並嘗試放入對應型別的變數。
錯誤 4:以為 Enter 「結束了程式的輸入」。
Enter 只是把字元 '\n' 加到輸入流中。對 >> 來說這只是另一個空白分隔符。所以「換行輸入」與「以空格分隔輸入」大多情況下是等價的。這很方便,但有時會破壞「我以為是逐行輸入」的預期。
錯誤 5:輸入錯誤型別然後驚訝於怪異結果。
若期望的是 int,輸入像 ten 無法被 >> 正確轉換為數字。在本課程此階段我們還沒教如何優雅地處理這種輸入,所以針對練習題最好的做法是確保遵守輸入格式,並以程式期望的形式提供資料。
GO TO FULL VERSION