Learning Objectives
- 用 cout 與 << 串接輸出文字、數字與運算結果
- 分辨 endl 與 '\n',使用 \t、\"、\\ 等跳脫字元
- 用 cin 與 >> 讀入一個或多個值,說明讀取方式由變數型別決定
- 用 getline 讀入含空白的一整行,避開與 cin >> 混用時的換行陷阱
- 用 <iomanip> 的 setw、left/right、setfill、fixed、setprecision 排出整齊的輸出
為什麼要學輸入輸出
程式要有用,就要能和使用者溝通:從鍵盤讀入資料,算完之後把結果印出來。
C++ 把輸入輸出看成資料流(stream):cout 是流向螢幕的輸出流,cin 是從鍵盤流進來的輸入流,兩者都在 <iostream> 裡。
cout << "答案是 " << 42; // 資料往 cout 流:<< 指向 cout
cin >> age; // 資料從 cin 流進 age:>> 指向變數
記住箭頭的方向就不會寫反:<< 把資料送進 cout,>> 把資料取出放進變數。
本課的動畫裡,
cin讀到的內容會顯示在輸出區(像你在終端機上打字一樣),所以輸入與輸出會交錯出現。
cout:串接、換行與跳脫字元
<< 可以一直串接,字串、整數、小數、運算式都可以放:cout << "總價 " << apples * price;。
換行有兩種寫法:
| 寫法 | 效果 |
|---|---|
endl |
換行,並且立刻把緩衝區的內容送到螢幕(flush) |
'\n' 或寫在字串裡 "…\n" |
只換行 |
輸出時資料會先暫存在緩衝區,累積一些再一起送出,這樣比較快。endl 每次都強制送出,大量輸出時會比較慢;
一般情況兩者看起來一樣,需要確定訊息立刻出現時(例如程式可能當掉前的除錯訊息)用 endl。
跳脫字元:字串裡有些字元不能直接打,要用反斜線開頭:
| 寫法 | 意思 |
|---|---|
\n |
換行 |
\t |
定位字元(Tab),對齊用 |
\" |
字串裡的雙引號 |
\\ |
反斜線本身 |
用 cout 輸出
注意第 9、10 行用 '\n' 換行,效果和 endl 一樣。第 12、13 行用 \t 讓「數量」和 3 對齊到同一個定位點;最後兩行示範在字串裡放雙引號與反斜線。
cin:讀入資料
cin >> 變數; 從鍵盤讀一筆資料放進變數。讀取的規則:
- 先跳過前面的空白、Tab、換行,再開始讀
- 讀到空白或換行就停:所以
string用cin >>只能讀到一個單字 - 可以串接:
cin >> a >> b >> c;依序讀三筆;輸入時用空白或換行隔開都可以 - 讀取方式由變數的型別決定:
int只讀數字、char只讀一個字元、double讀到小數
通常會先用 cout 印出提示,告訴使用者要輸入什麼。提示不換行,輸入就會接在提示後面。
用 cin 讀入資料
這個範例預先準備好的輸入是 Amy、19,以及一行 4 5 6。注意變數在 cin 那一行執行完之後才有值;第 16 行一次讀三個整數,三個變數同時被填好。
讀多少由型別決定
預先準備的輸入是三行:3.75、Apass、abc。看每個變數讀到什麼:int 遇到小數點就停,剩下的留給下一次讀取;char 只拿一個字元。最後一次讀取失敗,count 會變成 0,cin.fail() 變成 1(true)。
想一想:cout 與 cin
下面的程式會印出什麼?
cout << 1 << 2 << " " << 3 + 4;
使用者輸入一行 Tom Lee 20,執行下面的程式之後,name 與 age 分別是什麼?
string name;
int age;
cin >> name >> age;
endl 和 '\n' 的差別是什麼?
getline 與換行陷阱
要讀入含空白的一整行(例如全名、地址),用 <string> 的 getline:
string line;
getline(cin, line); // 讀到換行為止,換行字元會被讀掉但不放進 line
陷阱:cin >> id; 讀完數字後,那一行結尾的換行字元還留在輸入裡。緊接著的 getline 一看到換行就結束,讀到空字串。
解決方法是在 getline 之前先把留下的換行讀掉,兩種寫法擇一:
cin.ignore(); // 丟掉一個字元(就是那個換行)
getline(cin >> ws, s); // ws 先跳過所有空白與換行,再讀整行
如果數字後面還可能有多餘的空白,可以用 cin.ignore(numeric_limits<streamsize>::max(), '\n');(需要 <limits>)丟掉這一行剩下的所有字元。
getline 陷阱與解法
輸入有四行:1001、Chen Mei Ling、1002、Lin Zhi Wei。第 10 行的 getline 拿到的是空字串;姓名那一行要到第 12 行才被讀到。第二位同學先用 cin.ignore() 丟掉換行,getline 就能直接讀到姓名。
用 <iomanip> 控制格式
要輸出整齊的表格或固定位數的小數,引入 <iomanip>:
| 操作 | 效果 | 持續多久 |
|---|---|---|
setw(n) |
下一個輸出至少佔 n 格,不足補空白 | 只影響下一個輸出 |
left、right |
在 setw 的欄位裡靠左、靠右(預設靠右) | 一直有效,直到再改 |
setfill(c) |
用字元 c 代替空白補滿欄位 | 一直有效 |
setprecision(n) |
預設格式下是 n 位有效數字 | 一直有效 |
fixed |
改成固定小數格式,搭配 setprecision(n) 表示小數點後 n 位 |
一直有效 |
double 預設印出 6 位有效數字,所以 3.14159265 印成 3.14159;2.0 印成 2。
加上 fixed << setprecision(2) 之後,所有小數都固定印到小數點後 2 位,2.0 會印成 2.00。
對齊表格與小數位數
注意第 11 行之後 fixed 一直有效,第 12 行沒有再寫,兩個數字也都是小數點後 2 位。第 13、14 行對照 setw 只影響下一個輸出。迴圈裡名字靠左、分數靠右,排成兩欄。
想一想:getline 與格式
下面的程式會印出什麼?
cout << "[" << setw(4) << 5 << 6 << "]";
下面的程式會印出什麼?
double x = 7.0 / 4;
cout << fixed << setprecision(1) << x << " ";
cout << 3.0;
程式先 cin >> age; 再 getline(cin, name);,使用者輸入 20 按 Enter 後,name 是什麼?
動手寫:成績單
讀入一整行姓名(可能含空白),再讀入三科整數成績,印出平均,固定顯示到小數點後 1 位。輸入已經準備好:第一行 Wang Xiao Ming,第二行 80 92 75。輸入的內容會回顯在輸出裡,所以預期輸出是:
姓名:Wang Xiao Ming
三科成績:80 92 75
Wang Xiao Ming 的平均:82.3
重點回顧
cout << …輸出,cin >> …輸入,都需要#include <iostream>;<<、>>都可以串接endl換行並立刻送出,'\n'只換行;字串裡用\t、\"、\\放特殊字元cin >>會跳過空白與換行,讀到空白就停;讀多少由變數型別決定,讀取失敗時cin.fail()為 truegetline(cin, s)讀整行;接在cin >>後面時,先用cin.ignore()或cin >> ws處理留下的換行setw只影響下一個輸出;left/right、setfill、fixed、setprecision設定後一直有效fixed << setprecision(n)是小數點後 n 位;沒有fixed時setprecision(n)是 n 位有效數字