重要的事情说三遍:
前几节的示例程序几乎没有与用户的交互,只是在屏幕上打印简单的值。然而,标准库提供了许多其他方式,通过其输入/输出功能与用户交互。本节将简要介绍一些最有用的方法。
C++使用一种称为 流 的便捷抽象来在屏幕、键盘或文件等顺序介质上执行输入和输出操作。流是一个实体,程序可以向其插入或从中提取字符。我们不需要知道与流相关的介质或其任何内部规范的详细信息。我们只需要知道流是字符的来源/目的地,这些字符是顺序提供/接受的(即,一个接一个)。
标准库定义了一些流对象,可用于访问程序运行环境中被认为是标准字符来源和目的地的对象:
流 | 描述 |
---|---|
cin | 标准输入流 |
cout | 标准输出流 |
cerr | 标准错误(输出)流 |
clog | 标准日志记录(输出)流 |
我们将更详细地介绍cout
和cin
(标准输出和输入流);cerr
和clog
也是输出流,因此它们的工作方式与cout
基本相同,唯一的区别是它们用于特定目的:错误消息和日志记录;在许多情况下,在大多数环境设置中,它们实际上做同样的事情:在屏幕上打印,尽管它们也可以单独重定向。
标准输出(cout)
在大多数程序环境中,标准输出默认是屏幕,而C++流对象定义为访问它的是cout
。
对于格式化输出操作,cout
与 插入运算符 一起使用,插入运算符写作<<
(即两个“小于”符号)。
cout << "Output sentence"; // 在屏幕上打印Output sentence
cout << 120; // 在屏幕上打印数字120
cout << x; // 在屏幕上打印变量x的值
<<
运算符将其后的数据插入到其前的流中。在上面的示例中,它将字面字符串Output sentence
、数字120
和变量x
的值插入到标准输出流cout
中。注意,第一个语句中的句子用双引号("
)括起来,因为它是一个字符串字面量,而最后一个语句中的x
则没有。双引号使区别不同;当文本用它们括起来时,文本按字面打印;当没有括起来时,文本被解释为变量的标识符,并打印其值。例如,这两句话有非常不同的结果:
cout << "Hello"; // 打印Hello
cout << Hello; // 打印变量Hello的内容
多个插入操作(<<
)可以在一个语句中链接:
cout << "This " << " is a " << "single C++ statement";
最后一个语句将打印文本This is a single C++ statement
。链接插入在单个语句中混合字面量和变量特别有用:
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
)。例如:
cout << "First sentence.\n";
cout << "Second sentence.\nThird sentence.";
这将产生以下输出:
First sentence.
Second sentence.
Third sentence.
或者,也可以使用endl
操纵符来换行。例如:
cout << "First sentence." << endl;
cout << "Second sentence." << endl;
这将打印:
First sentence.
Second sentence.
endl
操纵符生成换行符,与插入'\n'
的效果完全相同;但它还有一个附加行为:流的缓冲区(如果有)将被刷新,这意味着请求物理写入设备(如果尚未完成)。这主要影响 完全缓冲 的流,cout
通常不是 完全缓冲 的流。尽管如此,通常只有在刷新流是一项功能时才使用endl
,而在不需要时使用'\n'
。请记住,刷新操作会带来一定的开销,在某些设备上可能会产生延迟。
标准输入(cin)
在大多数程序环境中,标准输入默认是键盘,而C++流对象定义为访问它的是cin
。
对于格式化输入操作,cin
与 提取运算符 一起使用,提取运算符写作>>
(即两个“大于”符号)。然后,该运算符后跟存储提取数据的变量。例如:
int age;
cin >> age;
第一个语句声明一个名为age
的int
类型变量,第二个语句从cin
中提取一个值并存储在其中。此操作使程序等待从cin
输入;通常,这意味着程序将等待用户通过键盘输入一些序列。在这种情况下,注意到使用键盘输入的字符只有在按下ENTER(或RETURN)键时才会传输到程序。一旦执行到包含cin
提取操作的语句,程序将等待,直到引入某些输入为止。
在cin
上的提取操作使用>>
运算符后的变量类型来确定如何解释从输入中读取的字符;如果是整数,则期望格式是数字序列,如果是字符串,则是字符序列,等等。
// 输入/输出示例
#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;
}
如你所见,从cin
提取似乎使从标准输入获取输入变得相当简单和直接。但是这种方法也有一个很大的缺点。如果用户输入了无法解释为整数的内容,该怎么办?在这种情况下,提取操作将失败。默认情况下,这会使程序继续执行而不为变量i
设置值,如果稍后使用i
的值,将产生不确定的结果。
这是非常糟糕的程序行为。大多数程序应无论用户输入什么都能按预期行为运行,适当地处理无效值。只有非常简单的程序才应该依赖从cin
直接提取的值而不进一步检查。稍后我们将看到如何使用 stringstreams 来更好地控制用户输入。
在cin
上的提取操作也可以在一个语句中链式请求多个数据:
cin >> a >> b;
这等同于:
cin >> a;
cin >> b;
在这两种情况下,期望用户输入两个值,一个给变量a
,另一个给变量b
。任何类型的空格用于分隔两个连续的输入操作;这可以是空格、制表符或换行符。
cin和字符串
提取运算符可以在cin
上用于获取字符串,就像对基本数据类型一样:
string mystring;
cin >> mystring;
然而,cin
提取总是将空格(空格、制表符、换行符……)视为终止提取值,因此提取字符串总是提取单个单词,而不是短语或整个句子。
为了从cin
获取整行,存在一个名为getline
的函数,它将流(cin
)作为第一个参数,字符串变量作为第二个参数。例如:
// 使用cin处理字符串
#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;
}
注意在两次调用getline
时,我们使用了相同的字符串标识符(mystr
)。程序在第二次调用时所做的只是用新输入的内容替换之前的内容。
大多数用户对控制台程序的标准行为是,每次程序查询用户输入时,用户输入字段,然后按ENTER(或RETURN)键。也就是说,输入通常以控制台程序中的行的形式进行,这可以通过使用getline
从用户那里获取输入来实现。因此,除非有强烈的理由不这样做,否则在控制台程序中获取输入时应始终使用getline
,而不是从cin
提取。
stringstream
标准头文件<sstream>
定义了一种类型,称为stringstream
,允许将字符串视为流,因此允许从/向字符串进行提取或插入操作,就像对cin
和cout
进行操作一样。这个功能对于将字符串转换为数值或将数值转换为字符串非常有用。例如,为了从字符串中提取整数,我们可以写:
string mystr ("1204");
int myint;
stringstream(mystr) >> myint;
这声明了一个初始化为值"1204"
的字符串和一个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;
}
在此示例中,我们间接从 标准输入 获取数值:不是直接从cin
提取数值,而是从中获取行并放入字符串对象(mystr
),然后从这个字符串中提取值到变量price
和quantity
。一旦这些值是数值,就可以对它们执行算术操作,例如将它们相乘以获得总价。
通过这种获取整行并提取其内容的方法,我们将获取用户输入的过程与其作为数据的解释分离开来,使输入过程符合用户的期望,同时通过程序更好地控制其内容转换为有用的数据。