C++官网参考链接:Basic Input/Output - C++ Tutorials (cplusplus.com)
前面几节的示例程序提供的与用户的交互很少(如果有的话)。它们只是在屏幕上打印简单的值,但是标准库提供了许多额外的方法来通过其输入/输出特性与用户交互。本节将简要介绍一些最有用的方法。
C++使用一种称为流的便利抽象在顺序媒体(如屏幕、键盘或文件)上执行输入和输出操作。流是一个实体,程序可以在其中插入或提取字符。
不需要知道与流相关的媒体或任何内部规范的详细信息。我们只需要知道流是字符的源/目的地,并且这些字符是按顺序提供/接收的(即一个接一个)。
标准库定义了一些流对象,可用于访问被程序运行环境视为标准字符源和目标的对象:
stream | description |
---|---|
cin | standard input stream(标准输入流) |
cout | standard output stream(标准输出流) |
cerr | standard error (output) stream(标准错误(输出)流) |
clog | standard logging (output) stream(标准日志(输出)流) |
我们将更详细地了解cout和cin(标准输出流和输入流);cerr和clog也是输出流,所以它们本质上像cout一样工作,唯一的区别是它们识别用于特定目的的流:错误消息和日志记录;在很多情况下,在大多数环境设置中,它们实际上做的是完全相同的事情:它们在屏幕上打印,尽管它们也可以单独重定向。
标准输出(cout)
在大多数程序环境中,默认的标准输出是屏幕,用于访问屏幕的C++流对象是cout。
对于格式化的输出操作,cout与插入操作符一起使用,插入操作符写成<<(即两个“小于”符号)。
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不是用双引号括起来。双引号是造成差异的原因;当文本被夹在它们(引号)之间时,文本按字面量打印;如果不是,则将文本解释为变量的标识符,并打印其值。例如,这两个句子有非常不同的结果:
cout << "Hello"; // prints Hello
cout << Hello; // prints the content of variable 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;
第一个语句声明了一个int类型的变量age,第二个语句从cin中提取一个存储在其中的值。这个操作使程序等待cin的输入;通常,这意味着程序将等待用户用键盘输入某个序列。在这种情况下,请注意,使用键盘引入的字符仅在按下ENTER(或RETURN)键时传输到程序。一旦到达对cin进行提取操作的语句,程序将根据需要等待很长时间,直到引入一些输入。
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;
}
输出:
如您所见,从cin中提取似乎使从标准输入获取输入的任务变得非常简单和直接。但是这种方法也有一个很大的缺点。在上面的例子中,如果用户输入了其他不能解释为整数的内容,会发生什么?在这种情况下,提取操作失败了。默认情况下,这会让程序继续运行,而不为变量i设置一个值,如果i的值稍后被使用,则会产生不确定的结果。
这是非常糟糕的程序行为。无论用户类型是什么,大多数程序都应该按照预期的方式运行,适当地处理无效值。只有非常简单的程序才应该依赖于直接从cin中提取的值,而不需要进一步检查。稍后我们将看到如何使用stringstream更好地控制用户输入。
在cin上的提取也可以被链接在一个语句中请求多个数据:
cin >> a >> b;
这个语句等价于:
cin >> a;
cin >> b;
在这两种情况下,用户都需要引入两个值,一个用于变量a,另一个用于变量b。任何类型的空白都用于分隔两个连续的输入操作;这可以是空格、制表符或换行符。
cin和string
提取操作符可以在cin上使用,以与基本数据类型相同的方式获取string:
string mystring;
cin >> mystring;
然而,cin提取总是将空白(空格、制表符、换行符……)视为终止被提取的值,因此提取字符串总是意味着提取一个单词,而不是一个短语或整个句子。
要从cin获取一整行内容,存在一个名为getline的函数,它以流(cin)作为第一个实参,以string变量作为第二个实参。例如:
// 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;
}
输出:
注意,在对getline的两次调用中,我们使用了相同的字符串标识符(mystr)。程序在第二个调用中所做的只是将之前的内容替换为引入的新内容。
大多数用户期望控制台程序的标准行为是,每次程序向用户查询输入时,用户引入字段,然后按ENTER(或RETURN)。也就是说,通常期望在控制台程序上按行进行输入,这可以通过使用getline从用户那里获得输入来实现。因此,除非有充分的理由不这样做,否则应该始终使用getline在控制台程序中获取输入,而不是从cin中提取。
stringstream
标准头文件<sstream>定义了一种名为stringstream的类型,该类型允许将string作为流处理,从而允许对string进行提取或插入操作,就像在cin和cout上执行操作一样。这个特性对于将string转换为数值或将数值转换为字符串很有用。例如,为了从一个字符串中提取一个整数,我们可以这样写:
string mystr ("1204");
int myint;
stringstream(mystr) >> myint;
这声明了一个初始值为"1204"的string和一个int类型的变量。然后,第三行使用这个变量从string构造的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中提取数值,而是将其中的行提取到一个string对象(mystr)中,然后将该string中的值提取到变量price和quantity中。一旦这些是数值,就可以对它们进行算术操作,比如将它们相乘得到一个总价。
通过这种获取整行并提取其内容的方法,我们将获取用户输入的过程与作为数据的解释分离开来,使输入过程符合用户的期望,同时获得更多的控制权,使程序能够将其内容转换为有用的数据。