基本輸入/輸出

前面幾節的示例程式幾乎沒有與使用者進行互動,或者說幾乎沒有。它們只是在螢幕上顯示簡單值,但標準庫透過其輸入/輸出功能提供了許多額外的與使用者互動的方式。本節將簡要介紹其中一些最有用的。

C++ 使用一種稱為*流*的方便的抽象來在螢幕、鍵盤或檔案等順序介質上執行輸入和輸出操作。*流*是一個程式可以從中插入或提取字元的實體。無需瞭解與流關聯的介質的詳細資訊或其任何內部規範。我們所需要知道的是,流是字元的源/目標,並且這些字元是順序提供/接受的(即一個接一個)。

標準庫定義了一系列流物件,可用於訪問程式執行環境所認為的標準字元源和目標。

描述
cin標準輸入流
、cout標準輸出流
、cerr標準錯誤(輸出)流
、clog標準日誌(輸出)流

我們將在下面更詳細地介紹 `cout` 和 `cin`(標準輸出和輸入流);`cerr` 和 `clog` 也是輸出流,因此它們的工作方式與 `cout` 基本相同,唯一的區別在於它們為特定目的標識流:錯誤訊息和日誌記錄;在許多情況下,在大多數環境設定中,它們實際上做的是相同的事情:它們在螢幕上列印,儘管它們也可以單獨重定向。

標準輸出 (cout)

在大多數程式環境中,標準輸出預設是螢幕,用於訪問它的 C++ 流物件是 `cout`。

對於格式化輸出操作,`cout` 與*插入運算子*一起使用,該運算子寫為 `<<`(即兩個“小於”號)。

1
2
3
cout << "Output sentence"; // prints Output sentence on screen
cout << 120;               // prints number 120 on screen
cout << x;                 // prints the value of x on screen  

`<<` 運算子將跟隨它的資料插入到它之前的流中。在上面的示例中,它將文字字串 `Output sentence`、數字 `120` 以及變數 `x` 的值插入到標準輸出流 `cout` 中。請注意,第一個語句中的句子用雙引號(`"`)括起來,因為它是一個字串字面量,而在最後一個語句中,`x` 則沒有。雙引號的區別在於;當文字被括在它們之間時,文字會被字面列印;當沒有時,文字會被解釋為變數的識別符號,並列印其值。例如,這兩個語句的結果大不相同。

1
2
cout << "Hello";  // prints Hello
cout << Hello;    // prints the content of variable Hello 

多個插入操作(`<<`)可以在單個語句中鏈式呼叫。

1
cout << "This " << " is a " << "single C++ statement";

最後一個語句將列印文字 `This is a single C++ statement`。鏈式插入特別適用於在單個語句中混合字面量和變數。

1
cout << "I am " << age << " years old and my zipcode is " << zipcode;

假設 age 變數包含值 24,zipcode 變數包含 90064,則上一條語句的輸出將是:

I am 24 years old and my zipcode is 90064
`cout` 預設不會自動新增換行符,除非指示它這樣做。例如,考慮以下兩個向 `cout` 插入的語句:
cout << "This is a sentence.";
cout << "This is another sentence.";

輸出將在一行中,中間沒有換行符。類似於:

This is a sentence.This is another sentence.
要插入換行符,應在需要換行的確切位置插入新行字元。在 C++ 中,新行字元可以指定為 `\n`(即反斜槓字元後跟一個小寫字母 `n`)。例如:

1
2
cout << "First sentence.\n";
cout << "Second sentence.\nThird sentence.";

這會產生以下輸出:

First sentence.
Second sentence.
Third sentence.

或者,也可以使用 `endl` 操縱符來換行。例如:

1
2
cout << "First sentence." << endl;
cout << "Second sentence." << endl;

這將列印:

First sentence.
Second sentence.

`endl` 操縱符會產生一個換行符,與插入 `'\n'` 的效果完全一樣;但它還有額外的行為:流的緩衝區(如果有)將被重新整理,這意味著輸出被請求物理寫入裝置,即使它尚未寫入。這主要影響*完全緩衝*的流,而 `cout`(通常)不是*完全緩衝*的流。儘管如此,最好僅在重新整理流有益時使用 `endl`,而在不希望重新整理時使用 `'\n'`。請記住,重新整理操作會產生一定的開銷,並且在某些裝置上可能會導致延遲。

標準輸入 (cin)

在大多數程式環境中,標準輸入預設是鍵盤,用於訪問它的 C++ 流物件是 `cin`。

對於格式化輸入操作,`cin` 與提取運算子一起使用,該運算子寫為 `>>`(即兩個“大於”號)。該運算子後面跟著儲存提取資料的變數。例如:

1
2
int age;
cin >> age;

第一個語句聲明瞭一個名為 `age` 的 `int` 型別變數,第二個語句從 `cin` 中提取一個值並存儲在其中。此操作會使程式等待來自 `cin` 的輸入;通常,這意味著程式將等待使用者從鍵盤輸入一些序列。在這種情況下,請注意,透過鍵盤輸入的字元只有在按下 ENTER(或 RETURN)鍵後才會傳輸到程式。一旦程式執行到提取操作語句,它將一直等待,直到輸入了一些內容。

`cin` 上的提取操作使用 `>>` 運算子之後的變數型別來確定它如何解釋從輸入中讀取的字元;如果是整數,則期望的格式是一系列數字,如果是字串,則是一系列字元,等等。

// i/o example

#include <iostream>
using namespace std;

int main ()
{
  int i;
  cout << "Please enter an integer value: ";
  cin >> i;
  cout << "The value you entered is " << i;
  cout << " and its double is " << i*2 << ".\n";
  return 0;
}
Please enter an integer value: 702
The value you entered is 702 and its double is 1404.

正如你所看到的,從 `cin` 提取似乎使從標準輸入獲取輸入變得非常簡單明瞭。但這種方法也有一個很大的缺點。如果使用者輸入了其他無法解釋為整數的內容,會發生什麼?在這種情況下,提取操作會失敗。預設情況下,這會讓程式繼續執行,而不會為變數 `i` 設定值,如果在後面使用了 `i` 的值,則會產生不確定的結果。

這是非常糟糕的程式行為。大多數程式應該在使用者輸入任何內容時都以可預期的行為執行,並適當地處理無效值。只有非常簡單的程式才應該依賴於直接從 `cin` 提取的值,而無需進一步檢查。稍後我們將看到如何使用*字串流*來更好地控制使用者輸入。
`cin` 上的提取操作也可以鏈式呼叫,以便在單個語句中請求多個數據。

1
cin >> a >> b;

這等同於:

1
2
cin >> a;
cin >> b;

在這兩種情況下,使用者都被期望輸入兩個值,一個給變數 `a`,另一個給變數 `b`。任何型別的空格都用於分隔兩個連續的輸入操作;這可以是一個空格、一個製表符或一個換行符。

cin 和字串

提取運算子可以與 `cin` 一起使用,以與基本資料型別相同的方式獲取字元字串。

1
2
string mystring;
cin >> mystring;

然而,`cin` 的提取操作總是將空格(空白字元、製表符、換行符……)視為終止正在提取的值,因此提取字串意味著總是提取單個單詞,而不是短語或整個句子。

要從 `cin` 獲取整行,有一個名為 `getline` 的函式,它將流(`cin`)作為第一個引數,將字串變數作為第二個引數。例如:

// cin with strings
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string mystr;
  cout << "What's your name? ";
  getline (cin, mystr);
  cout << "Hello " << mystr << ".\n";
  cout << "What is your favorite team? ";
  getline (cin, mystr);
  cout << "I like " << mystr << " too!\n";
  return 0;
}
What's your name? Homer Simpson
Hello Homer Simpson.
What is your favorite team? The Isotopes
I like The Isotopes too!

請注意,在 `getline` 的兩次呼叫中,我們都使用了相同的字串識別符號(`mystr`)。程式在第二次呼叫中所做的是簡單地用新輸入的內容替換之前的內容。

大多數使用者對控制檯程式的標準期望是,每次程式詢問使用者輸入時,使用者輸入欄位,然後按 ENTER(或 RETURN)。也就是說,在控制檯程式中,輸入通常是按行進行的,這可以透過使用 `getline` 從使用者那裡獲取輸入來實現。因此,除非有充分的理由不這樣做,否則在控制檯程式中獲取輸入時,您應該始終使用 `getline` 而不是從 `cin` 中提取。

stringstream

標準標頭檔案 `<sstream>` 定義了一個名為 `stringstream` 的型別,它允許將字串視為流,從而允許從字串進行提取或插入操作,就像在 `cin` 和 `cout` 上執行的操作一樣。此功能最常用於將字串轉換為數值或將數值轉換為字串。例如,為了從字串中提取一個整數,我們可以這樣寫:

1
2
3
string mystr ("1204");
int myint;
stringstream(mystr) >> myint;

這聲明瞭一個初始化為值 `"1204"` 的 `string`,以及一個 `int` 型別的變數。然後,第三行使用此變數從一個由該字串構建的 `stringstream` 中提取。這段程式碼將數值 `1204` 儲存在名為 `myint` 的變數中。

// stringstreams
#include <iostream>
#include <string>
#include <sstream>
using namespace std;

int main ()
{
  string mystr;
  float price=0;
  int quantity=0;

  cout << "Enter price: ";
  getline (cin,mystr);
  stringstream(mystr) >> price;
  cout << "Enter quantity: ";
  getline (cin,mystr);
  stringstream(mystr) >> quantity;
  cout << "Total price: " << price*quantity << endl;
  return 0;
}
Enter price: 22.25
Enter quantity: 7
Total price: 155.75

在此示例中,我們間接地從*標準輸入*獲取數值:而不是直接從 `cin` 中提取數值,而是將它們讀入一個字串物件(`mystr`),然後從該字串中提取值到變數 `price` 和 `quantity` 中。一旦它們是數值,就可以對它們進行算術運算,例如將它們相乘以獲得總價。

透過這種獲取整行並提取其內容的方法,我們將獲取使用者輸入的過程與其作為資料的解釋分開了,從而使輸入過程符合使用者的期望,並同時獲得了對程式內容轉換為有用資料過程的更多控制。
Index
目錄