[C++]在 cin 後呼叫 getline 所遇到的問題
在 C++ 中,使用 cin 讀資料後,再用 getline 讀字串,就會遇到 buffer 沒有清空的問題。例如底下的程式碼:
這個例子中,程式先讀一個整數,將之放入 value 變數中。接著讀 value 個字串。所以要是 value 為 2 時,應該就要讀取 2 個字串。但在這個例子中,很奇怪就只會讀 1 個字串。
為什麼??
仔細看底下的輸出,我們會發現 string 1 是一個「空字串」!而 hello world 才是我們真正輸入的那個字串。那麼這個空字串是什麼?
它其實是個 new line,即 \n,或者說是一個 endl。
事情是這樣的。在第 8 行中,我們的程式碼要讀取一個整數:
cin >> value;
然後我們在鍵盤上鍵入 2,然後敲一個 enter。
這個時候,我們的電腦其實得到的是底下的資訊:
緩衝區中有一個 2,還有一個斷行。
那第8行再讀時,只會把 2 讀出來(因為我們要求讀一個整數,而 endl 不是整數,所以不會被讀出來)。
所以最後,緩衝區中會遺留一個 endl 在那兒。於是下次我們呼叫 getline 時,由於 C++ 偵測到緩衝區中還有資料,所以會先把那個 endl 讀出來。這就是為什麼我們只能讀到一個字串的原因。
那怎麼辦?
解決的方法有很多,最常見的有兩個:
這兩種作法都是把緩衝區清空,差別在於方法一需要多宣告一個字串變數。我個人是習慣方法2囉。
底下附上程式碼:
方法1:
方法2:
額外一題,這兒呼叫的 ignore 是一個預設的版本,它的運作方式是這樣的:清空緩衝區,直到底下的兩個條件,其中一個滿足為止!
上例中,使用者手殘,多輸入了一個 + (別笑,有時候打字快,總是會發生這種事)。這個時候由於 ignore 只清空一個字元,所以它會把 + 清掉,但是還是把 newline 留著,所以我們的程式還是會讀到那個 newline。
解法是要求 ignore 多清一些字元,程式碼示範如下:
上面的程式碼中,ignore 需要兩個參數,第1個參數要求 ignore 清空 1000 個字元。第2個參數要求 ignore 清空緩衝區,直到遇到 \n (new line)。這兩個要求只要其中之一滿足,ignore 就會停止。
有關 cin 和 getline 的問題,網路上有許多的討論,底下列了一些參考文獻,寫得很清楚,大家可以參考:
stackoverflow 的文章:cin and getline skipping input
ignore 的使用方式
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<iostream> | |
#include<string> | |
using namespace std; | |
void main() | |
{ | |
int value; | |
cin >> value; | |
for (int i = 0; i < value; i++) | |
{ | |
string s; | |
getline(cin, s); | |
cout << "[output] string " << i+1 << ": \"" << s + "\""<< endl; | |
} | |
} |
為什麼??
仔細看底下的輸出,我們會發現 string 1 是一個「空字串」!而 hello world 才是我們真正輸入的那個字串。那麼這個空字串是什麼?
它其實是個 new line,即 \n,或者說是一個 endl。
事情是這樣的。在第 8 行中,我們的程式碼要讀取一個整數:
cin >> value;
然後我們在鍵盤上鍵入 2,然後敲一個 enter。
這個時候,我們的電腦其實得到的是底下的資訊:
緩衝區中有一個 2,還有一個斷行。
那第8行再讀時,只會把 2 讀出來(因為我們要求讀一個整數,而 endl 不是整數,所以不會被讀出來)。
所以最後,緩衝區中會遺留一個 endl 在那兒。於是下次我們呼叫 getline 時,由於 C++ 偵測到緩衝區中還有資料,所以會先把那個 endl 讀出來。這就是為什麼我們只能讀到一個字串的原因。
那怎麼辦?
解決的方法有很多,最常見的有兩個:
- 在 cin 後,要是我們知道要呼叫 getline,那就「多呼叫一次 getline」,把緩衝區清空。
- 呼叫 cin.ignore() 把緩衝區清空。
這兩種作法都是把緩衝區清空,差別在於方法一需要多宣告一個字串變數。我個人是習慣方法2囉。
底下附上程式碼:
方法1:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<iostream> | |
#include<string> | |
using namespace std; | |
void main() | |
{ | |
int value; | |
cin >> value; | |
string strtmp; | |
getline(cin, strtmp); //strtmp 只是把緩衝區清空,沒有其他的作用 | |
for (int i = 0; i < value; i++) | |
{ | |
string s; | |
getline(cin, s); | |
cout << "[output] string " << i+1 << ": \"" << s + "\""<< endl; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<iostream> | |
#include<string> | |
using namespace std; | |
void main() | |
{ | |
int value; | |
cin >> value; | |
cin.ignore(); //呼叫 ignore 忽略緩衝區的資料 | |
for (int i = 0; i < value; i++) | |
{ | |
string s; | |
getline(cin, s); | |
cout << "[output] string " << i+1 << ": \"" << s + "\""<< endl; | |
} | |
} |
- 清空 1 個字元。
- 讀到 endl。
前述兩個條件,只要有一個滿足,那 ignore 就會停止所有的動作了。
條件 2 比較容易想像,本來 ignore 就是要清空緩衝區,直到遇到 endl 為止。條件 1 比較神奇,為什麼只清空 1 個字元?沒法子,這是人家工程師預設好的。就照規定來囉。
要是使用預設的版本時,有時候遇到使用者「手殘」,那就有問題了。舉例如下:
上例中,使用者手殘,多輸入了一個 + (別笑,有時候打字快,總是會發生這種事)。這個時候由於 ignore 只清空一個字元,所以它會把 + 清掉,但是還是把 newline 留著,所以我們的程式還是會讀到那個 newline。
解法是要求 ignore 多清一些字元,程式碼示範如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<iostream> | |
#include<string> | |
using namespace std; | |
void main() | |
{ | |
int value; | |
cin >> value; | |
/**ignore 的運作方式如下: | |
* 清空 1000 個字元,或是 | |
* 遇到 new line 時 | |
* 那就停止 | |
*/ | |
cin.ignore(1000, '\n'); | |
for (int i = 0; i < value; i++) | |
{ | |
string s; | |
getline(cin, s); | |
cout << "[output] string " << i+1 << ": \"" << s + "\""<< endl; | |
} | |
} |
有關 cin 和 getline 的問題,網路上有許多的討論,底下列了一些參考文獻,寫得很清楚,大家可以參考:
stackoverflow 的文章:cin and getline skipping input
ignore 的使用方式
留言
張貼留言