在本课程中,我们将更详细地讨论 std::cout,它曾在我们的“Hello world!”程序中用于向控制台输出文本“Hello world!”。我们还将探讨如何从用户那里获取输入,这将使我们的程序更具交互性。
输入/输出库
输入/输出库(io 库)是 C++ 标准库的一部分,用于处理基本的输入和输出。我们将使用此库中的功能从键盘获取输入并将数据输出到控制台。iostream 中的“io”部分代表“输入/输出”。
要使用 iostream 库中定义的功能,我们需要在任何使用 iostream 中定义的内容的代码文件顶部包含 iostream 头文件,如下所示
#include <iostream>
// rest of code that uses iostream functionality here
std::cout
iostream 库包含一些预定义变量供我们使用。其中最有用的是 std::cout,它允许我们将数据发送到控制台以文本形式打印。cout 代表“字符输出”。
提醒一下,这是我们的 Hello world 程序
#include <iostream> // for std::cout
int main()
{
std::cout << "Hello world!"; // print Hello world! to console
return 0;
}
在此程序中,我们包含了 iostream,以便我们可以访问 std::cout。在我们的 main 函数中,我们使用 std::cout 以及插入运算符 (<<
),将文本“Hello world!”发送到控制台进行打印。
std::cout 不仅可以打印文本,还可以打印数字
#include <iostream> // for std::cout
int main()
{
std::cout << 4; // print 4 to console
return 0;
}
这会产生结果
4
它还可以用于打印变量的值
#include <iostream> // for std::cout
int main()
{
int x{ 5 }; // define integer variable x, initialized with value 5
std::cout << x; // print value of x (5) to console
return 0;
}
这会产生结果
5
要在同一行打印多个内容,插入运算符 (<<
) 可以在单个语句中多次使用,以连接(链接)多个输出片段。例如
#include <iostream> // for std::cout
int main()
{
std::cout << "Hello" << " world!";
return 0;
}
这使用了两次 <<
运算符,首先输出 Hello
,然后输出 world
。
因此,此程序打印
Hello world!
提示
将 <<
运算符(和 >>
运算符)想象成一个传送带可能会有所帮助,它沿指示的方向移动数据。在这种情况下,当内容传送到 std::cout
时,它就会被输出。
这里是另一个示例,我们在同一语句中打印文本和变量值
#include <iostream> // for std::cout
int main()
{
int x{ 5 };
std::cout << "x is equal to: " << x;
return 0;
}
这个程序打印
x is equal to: 5
相关内容
我们在第 2.9 课 -- 命名冲突和命名空间简介中讨论了 std:: 前缀的实际作用。
使用 std::endl
输出换行符
您希望这个程序打印什么?
#include <iostream> // for std::cout
int main()
{
std::cout << "Hi!";
std::cout << "My name is Alex.";
return 0;
}
您可能会对结果感到惊讶
Hi!My name is Alex.
单独的输出语句不会导致控制台上的输出分行。
如果我们要向控制台输出独立的行,我们需要告诉控制台将光标移动到下一行。我们可以通过输出换行符来做到这一点。换行符是 OS 特定的字符或字符序列,它将光标移动到下一行的开头。
输出换行符的一种方法是输出 std::endl
(代表“end line”)
#include <iostream> // for std::cout and std::endl
int main()
{
std::cout << "Hi!" << std::endl; // std::endl will cause the cursor to move to the next line
std::cout << "My name is Alex." << std::endl;
return 0;
}
这会打印
Hi! My name is Alex.
提示
在上面的程序中,第二个 std::endl
在技术上并非必要,因为程序会立即结束。然而,它有几个有用的目的。
首先,它有助于指示输出行是一个“完整的思想”(而不是稍后在代码中完成的部分输出)。从这个意义上说,它的功能类似于标准英语中的句号。
其次,它将光标定位在下一行,这样如果我们在以后添加额外的输出行(例如让程序说“bye!”),这些行将出现在我们期望的位置(而不是附加到之前的输出行)。
第三,从命令行运行可执行文件后,有些操作系统在再次显示命令提示符之前不会输出新行。如果我们的程序没有以光标在新行结束,命令提示符可能会附加到之前的输出行,而不是出现在新行的开头,这与用户的预期不同。
最佳实践
当一行输出完成后,输出一个换行符。
std::cout
是缓冲的
想象一下您最喜欢的游乐园里的过山车。乘客们(以不同的速度)排队。定期地,一辆列车到达并搭载乘客(最多可达列车最大容量)。当列车满员,或者当足够的时间过去时,列车带着一批乘客出发,游乐设施开始。任何未能搭乘当前列车的乘客都将等待下一辆。
这个类比与 C++ 中通常处理发送到 std::cout
的输出的方式相似。程序中的语句请求将输出发送到控制台。但是,该输出通常不会立即发送到控制台。相反,请求的输出“排队”,并存储在一块专门用于收集此类请求的内存区域中(称为缓冲区)。定期地,缓冲区会刷新,这意味着缓冲区中收集的所有数据都会传输到其目的地(在本例中为控制台)。
作者注
再举一个例子,刷新缓冲区有点像冲洗马桶。您收集的所有“输出”都会被传送到……下一个目的地。呃。
这也意味着,如果您的程序在缓冲区刷新之前崩溃、中止或暂停(例如,为了调试目的),任何仍在缓冲区中等待的输出都不会显示。
关键见解
缓冲输出的对立面是无缓冲输出。对于无缓冲输出,每个单独的输出请求都会直接发送到输出设备。
将数据写入缓冲区通常很快,而将一批数据传输到输出设备相对较慢。通过将多个输出请求批量处理,最大限度地减少向输出设备发送输出的次数,缓冲可以显著提高性能。
std::endl
与 \n
使用 std::endl
通常效率低下,因为它实际上做了两件事:它输出一个换行符(将光标移动到控制台的下一行),并且它会刷新缓冲区(这很慢)。如果我们输出多行以 std::endl
结尾的文本,我们将得到多次刷新,这很慢并且可能不必要。
将文本输出到控制台时,我们通常不需要自己显式刷新缓冲区。C++ 的输出系统设计为定期自刷新,让它自行刷新既简单又高效。
要在不刷新输出缓冲区的情况下输出换行符,我们使用 \n
(在单引号或双引号内),它是一个特殊符号,编译器将其解释为换行符。\n
将光标移动到控制台的下一行,而不会导致刷新,因此它通常会表现得更好。\n
也更简洁,并且可以嵌入到现有的双引号文本中。
这是一个以几种不同方式使用 \n
的示例
#include <iostream> // for std::cout
int main()
{
int x{ 5 };
std::cout << "x is equal to: " << x << '\n'; // single quoted (by itself) (conventional)
std::cout << "Yep." << "\n"; // double quoted (by itself) (unconventional but okay)
std::cout << "And that's all, folks!\n"; // between double quotes in existing text (conventional)
return 0;
}
这会打印
x is equal to: 5 Yep. And that's all, folks!
当 \n
未嵌入到现有双引号文本行中时(例如 "hello\n"
),通常使用单引号('\n'
)。
致进阶读者
在 C++ 中,我们使用单引号表示单个字符(例如 'a'
或 '$'
),使用双引号表示文本(零个或多个字符)。
尽管 '\n' 在源代码中表示为两个符号,但编译器将其视为单个换行符 (LF) 字符(ASCII 值为 10),因此通常使用单引号(除非嵌入到现有的双引号文本中)。我们将在 第 4.11 课 -- 字符中更详细地讨论这一点。
当输出 '\n' 时,执行输出的库负责将此单个 LF 字符转换为给定操作系统的相应换行序列。有关操作系统换行符约定的更多信息,请参阅 Wikipedia。
作者注
虽然不常见,但我们认为在标准输出语句中使用(甚至更喜欢)双引号 "\n"
是可以的。
这有两个主要好处
- 对所有输出文本使用双引号比确定哪些应该使用单引号和双引号要简单。
- 更重要的是,它有助于避免意外的多字符字面量。我们在第 4.11 课 -- 字符中介绍了多字符字面量以及它们可能导致的一些意外输出。
在非输出情况下应首选单引号。
我们将在字符的课程中更详细地介绍 '\n' 是什么 (4.11 -- 字符)。
最佳实践
当将文本输出到控制台时,首选 \n
而不是 std::endl
。
警告
'\n'
使用反斜杠(所有 C++ 中的特殊字符都如此),而不是正斜杠。
使用正斜杠(例如 '/n'
)或在单引号内包含其他字符(例如 ' \n'
或 '.\n'
)会导致意外行为。例如,std::cout << '/n';
通常会打印为 12142
,这可能不是您所期望的。
std::cin
std::cin
是 iostream
库中的另一个预定义变量。std::cout
将数据打印到控制台(使用插入运算符 <<
提供数据),而 std::cin
(代表“字符输入”)从键盘读取输入。我们通常使用提取运算符 >>
将输入数据放入变量中(然后可以在后续语句中使用)。
#include <iostream> // for std::cout and std::cin
int main()
{
std::cout << "Enter a number: "; // ask user for a number
int x{}; // define variable x to hold user input (and value-initialize it)
std::cin >> x; // get number from keyboard and store it in variable x
std::cout << "You entered " << x << '\n';
return 0;
}
尝试编译此程序并自行运行。当您运行程序时,第 5 行将打印“输入一个数字: ”。当代码到达第 8 行时,您的程序将等待您输入。一旦您输入一个数字(并按回车),您输入的数字将被赋值给变量 x
。最后,在第 10 行,程序将打印“您输入了 ”,后跟您刚刚输入的数字。
例如(输入值 4 作为输入)
Enter a number: 4 You entered 4
这是一种从用户获取键盘输入简便方式,我们将在后续许多示例中使用它。
提示
请注意,在接受一行输入时,您无需输出 '\n'
,因为用户需要按 enter 键才能接受其输入,这将把光标移动到控制台的下一行。
- 如果您的屏幕在输入数字后立即关闭,请参阅第 0.8 课 -- 几个常见的 C++ 问题以获取解决方案。
- 如果您使用 CLion 并且“您输入了”前面有一个空格,这是 CLion 中的一个错误。请参阅 CLion 错误跟踪器以获取解决方法。
就像可以在一行中输出多个文本一样,也可以在一行中输入多个值
#include <iostream> // for std::cout and std::cin
int main()
{
std::cout << "Enter two numbers separated by a space: ";
int x{}; // define variable x to hold user input (and value-initialize it)
int y{}; // define variable y to hold user input (and value-initialize it)
std::cin >> x >> y; // get two numbers and store in variable x and y respectively
std::cout << "You entered " << x << " and " << y << '\n';
return 0;
}
这会产生输出
Enter two numbers separated by a space: 5 6 You entered 5 and 6
输入的值应由空白字符(空格、制表符或换行符)分隔。
关键见解
关于在通过其他来源(例如 std::cin)提供用户提供的值之前是否需要立即初始化变量存在一些争议,因为用户提供的值只会覆盖初始化值。根据我们之前的建议,变量应始终初始化,最佳实践是先初始化变量。
致进阶读者
C++ I/O 库不提供在用户不必按 enter 键的情况下接受键盘输入的方法。如果这是您希望的功能,您将不得不使用第三方库。对于控制台应用程序,我们推荐 pdcurses、FXTUI、cpp-terminal 或 notcurses。许多图形用户界面库都有自己的函数来完成这类事情。
std::cin
是缓冲的
在上一节中,我们指出输出数据实际上是一个两阶段过程
- 每个输出请求的数据都被添加到输出缓冲区(末尾)。
- 稍后,输出缓冲区(开头)中的数据被刷新到输出设备(控制台)。
关键见解
将数据添加到缓冲区的末尾并从缓冲区的开头移除数据可确保数据以与添加时相同的顺序处理。这有时称为 FIFO(先进先出)。
同样,输入数据也是一个两阶段过程
- 您输入的单个字符会添加到输入缓冲区(在
std::cin
内部)的末尾。回车键(按下以提交数据)也存储为'\n'
字符。 - 提取运算符“>>”从输入缓冲区的开头移除字符,并将其转换为一个值,该值通过复制赋值分配给关联的变量。然后可以在后续语句中使用此变量。
关键见解
输入缓冲区中的每行输入数据都以 '\n'
字符结尾。
我们将使用以下程序来演示这一点
#include <iostream> // for std::cout and std::cin
int main()
{
std::cout << "Enter two numbers: ";
int x{};
std::cin >> x;
int y{};
std::cin >> y;
std::cout << "You entered " << x << " and " << y << '\n';
return 0;
}
此程序将输入到两个变量(这次是单独的语句)。我们将运行此程序两次。
运行 #1:当遇到 std::cin >> x;
时,程序将等待输入。输入值 4
。输入 4\n
进入输入缓冲区,值 4
被提取到变量 x
中。
当遇到 std::cin >> y;
时,程序将再次等待输入。输入值 5
。输入 5\n
进入输入缓冲区,值 5
被提取到变量 y
中。最后,程序将打印 您输入了 4 和 5
。
这次运行应该没有什么令人惊讶的。
运行 #2:当遇到 std::cin >> x
时,程序将等待输入。输入 4 5
。输入 4 5\n
进入输入缓冲区,但只有 4
被提取到变量 x
中(提取在空格处停止)。
当遇到 std::cin >> y
时,程序不会等待输入。相反,仍在输入缓冲区中的 5
被提取到变量 y
中。然后程序打印 您输入了 4 和 5
。
请注意,在运行 2 中,当提取到变量 y
时,程序没有等待用户输入额外的输入,因为输入缓冲区中已经有可用的先前输入。
关键见解
std::cin
是缓冲的,因为它允许我们将输入与输入提取分开。我们可以输入一次,然后对其执行多次提取请求。
以下是操作符 >>
用于输入的简化视图。
- 如果
std::cin
处于不正常状态(例如,之前的提取失败且std::cin
尚未清除),则不会尝试提取,提取过程立即中止。 - 前导空白字符(缓冲区前面的空格、制表符和换行符)将从输入缓冲区中丢弃。这将丢弃前一行输入中剩余的未提取换行符。
- 如果输入缓冲区现在为空,操作符
>>
将等待用户输入更多数据。输入的任何前导空白字符都将被丢弃。 - 操作符
>>
然后提取尽可能多的连续字符,直到遇到换行符(表示输入行的结束)或对要提取的变量无效的字符。
提取过程的结果如下
- 如果在步骤 1 中提取中止,则未发生提取尝试。没有其他事情发生。
- 如果在上面步骤 4 中提取了任何字符,则提取成功。提取的字符将转换为一个值,然后通过复制赋值分配给变量。
- 如果在上面步骤 4 中没有提取到任何字符,则提取失败。被提取的对象被复制赋值为值
0
(C++11 起),并且任何未来的提取都将立即失败(直到std::cin
被清除)。
任何未提取的字符(包括换行符)都可用于下一次提取尝试。
相关内容
我们将在第 9.5 课 -- std::cin 和处理无效输入中讨论如何检测和处理提取失败、处理多余输入以及清除 std::cin
。
例如,给定以下代码片段
int x{};
std::cin >> x;
以下是三种不同输入情况的发生情况
- 如果用户输入
5a
并回车,5a\n
将被添加到缓冲区。5
将被提取,转换为整数,并赋值给变量x
。a\n
将留在输入缓冲区中,供下次提取。 - 如果用户输入 'b' 并回车,
b\n
将被添加到缓冲区。由于b
不是有效的整数,无法提取任何字符,因此这是提取失败。变量x
将被设置为0
,并且未来的提取将失败,直到输入流被清除。 - 如果
std::cin
由于之前的提取失败而处于不正常状态,则此处不发生任何事情。变量x
的值不会被更改。
我们将在下面的测验中探索更多情况。
operator<<
vs operator>>
新程序员经常混淆 std::cin
、std::cout
、插入运算符 (<<
) 和提取运算符 (>>
)。这里有一个简单的记忆方法
std::cin
和std::cout
始终位于运算符的左侧。std::cout
用于输出值(cout = 字符输出)。std::cin
用于获取输入值(cin = 字符输入)。<<
与std::cout
一起使用,并显示数据移动的方向。std::cout << 4
将值4
移动到控制台。>>
与std::cin
一起使用,并显示数据移动的方向。std::cin >> x
将用户从键盘输入的值移动到变量x
中。
我们将在第 1.9 课 -- 字面量和运算符简介中讨论更多运算符。
题外话…
如果您想知道为什么 C++ 使用 std::cout
和 std::cin
而不是其他名称,请参阅 https://en.wikipedia.org/wiki/Standard_streams。
小测验时间
问题 #1
考虑我们上面使用的以下程序
#include <iostream> // for std::cout and std::cin
int main()
{
std::cout << "Enter a number: "; // ask user for a number
int x{}; // define variable x to hold user input
std::cin >> x; // get number from keyboard and store it in variable x
std::cout << "You entered " << x << '\n';
return 0;
}
程序期望您输入一个整数值,因为用户输入将放入的变量 x
是一个整数变量。
多次运行此程序,并描述当您输入以下类型的输入时产生的输出
a) 一个字母,例如 h
。
b) 带小数部分的数字(例如 3.2
)。尝试小于 0.5 和大于 0.5 的小数部分数字(例如 3.2
和 3.7
)。
c) 一个小的负整数,例如 -3
。
d) 一个单词,例如 Hello
。
e) 一个非常大的数字(至少 30 亿)。
f) 一个小数字后跟一些字母,例如 123abc
。
g) 几个字母后跟一个小数字,例如 abc123
。
h) +5
(三个空格,后跟一个加号和一个 5)。
i) 5b6
。
问题 #2
要求用户输入三个值。然后程序应该打印这些值。在 main()
函数上方添加适当的注释。
程序应与以下输出匹配(当输入值为 4
、5
和 6
时运行)
Enter three numbers: 4 5 6 You entered 4, 5, and 6.