基本输入/输出

前面几节的示例程序几乎没有与用户进行交互,或者说几乎没有。它们只是在屏幕上显示简单值,但标准库通过其输入/输出功能提供了许多额外的与用户交互的方式。本节将简要介绍其中一些最有用的。

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
目录