输入输出流介绍:cout、cin 和 endl

输入/输出库概述

在这节课中,我们将更多地讨论我们在 Hello world! 程序中使用的 std::cout,它用于将文本 Hello world! 输出到控制台。我们还将探索如何从用户那里获取输入,这将使我们的程序更具交互性。

输入/输出库基础

输入/输出库(io库)是处理基本输入和输出的C++标准库的一部分。我们将使用这个库中的函数从键盘获取输入并将数据输出到控制台。iostream中的io代表输入/输出。

包含iostream头文件

要使用iostream库中定义的功能,我们需要在任何使用iostream内容的代码文件顶部包含iostream头文件,如下所示:

#include <iostream>

// 使用iostream功能的其余代码在这里

std::cout 输出流

std::cout基本用法

iostream库包含了一些预定义的变量供我们使用。其中一个最有用的变量是std::cout,它允许我们将数据发送到控制台以文本形式打印。cout代表"字符输出"。

Hello world示例

作为提醒,这是我们的 Hello world程序:

#include <iostream> // 用于std::cout

int main()
{
    std::cout << "Hello world!"; // 将Hello world!打印到控制台

    return 0;
}

打印数字

std::cout不仅可以打印文本,还可以打印数字:

#include <iostream> // 用于std::cout

int main()
{
    std::cout << 4; // 将4打印到控制台

    return 0;
}

这将产生结果:

4

打印变量值

它也可以用来打印变量的值:

#include <iostream> // 用于std::cout

int main()
{
    int x{ 5 }; // 定义整数变量x,初始化值为5
    std::cout << x; // 将x的值(5)打印到控制台
    return 0;
}

这将产生结果:

5

连接多个输出

要在同一个行上打印多个内容,插入操作符(«)可以在单个语句中多次使用,以连接(链接在一起)多个输出片段。例如:

#include <iostream> // 用于std::cout

int main()
{
    std::cout << "Hello" << " world!";
    return 0;
}

这个程序两次使用«操作符,首先输出Hello,然后输出world。 因此,这个程序打印:

Hello world!

操作符形象化

提示 可能有助于想象«操作符(和»操作符)像传送带一样,按照指示的方向移动数据。在这种情况下,当内容传送到std::cout时,它会被输出。

打印文本和变量

这是另一个在同一语句中打印文本和变量值的例子:

#include <iostream> // 用于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> // 用于std::cout

int main()
{
    std::cout << "Hi!";
    std::cout << "My name is Alex.";
    return 0;
}

您可能会对结果感到惊讶:

Hi!My name is Alex.

单独的输出语句并不会导致控制台上的输出分成不同的行。

新行的概念

如果我们想要在控制台上打印不同的行,我们需要告诉控制台将光标移动到下一行。我们可以通过输出一个新行来做到这一点。新行是一个操作系统特定的字符或字符序列,它将光标移动到下一行的开始。

使用std::endl

输出新行的一种方法是输出std::endl(代表"end line"):

#include <iostream> // 用于std::cout和std::endl

int main()
{
    std::cout << "Hi!" << std::endl; // std::endl将导致光标移动到下一行
    std::cout << "My name is Alex." << std::endl;

    return 0;
}

这将打印:

Hi!
My name is Alex.

std::endl的额外用途

提示 在上面的程序中,第二个std::endl技术上是不必要的,因为程序随后立即结束。然而,它有几个用途。 首先,它有助于表明输出行是一个"完整的想法"(与稍后在代码中完成的部分输出相对)。在这种意义上,它的功能类似于标准英语中使用句号。 其次,它将光标定位在下一行,因此如果我们稍后添加额外的输出行(例如让程序说"再见!"),这些行将出现在我们期望的位置(而不是附加到之前的输出行)。 第三,在从命令行运行可执行文件后,一些操作系统在显示命令提示符之前不会输出新行。如果我们的程序没有在光标位于新行时结束,命令提示符可能会出现在之前的输出行上,而不是像用户期望的那样出现在新行的开始。

输出新行的最佳实践

最佳实践 每当输出行完成时,就输出一个新行。

std::cout缓冲机制

缓冲的概念

考虑您最喜欢的游乐园中的过山车。乘客以某种变化的速率出现并排队。定期,一列火车到达并载客(最多可容纳火车的最大容量)。当火车满员,或者过了足够的时间,火车就会带着一批乘客离开,游乐设施开始。任何无法登上当前火车的乘客都等待下一列。

缓冲区工作原理

这个类比类似于C++中发送到std::cout的输出通常是如何处理的。我们程序中的语句请求将输出发送到控制台。然而,该输出通常不会立即发送到控制台。相反,请求的输出"排队",并存储在专门收集此类请求的内存区域(称为缓冲区)中。定期,缓冲区被刷新,这意味着缓冲区中收集的所有数据都被传输到目的地(在这种情况下,是控制台)。

缓冲区刷新

作者注 使用另一个类比,刷新缓冲区有点像冲厕所。所有收集的"输出"都被转移到……它接下来要去的地方。Eew。

这也意味着,如果您的程序崩溃、中止或暂停(例如,出于调试目的),在缓冲区刷新之前,任何仍在缓冲区中等待的输出都不会显示。

缓冲与无缓冲

缓冲输出的相反是无缓冲输出。使用无缓冲输出时,每个单独的输出请求直接发送到输出设备。 将数据写入缓冲区通常很快,而将一批数据传输到输出设备相对较慢。缓冲可以通过批量处理多个输出请求来显著提高性能,从而最小化输出必须发送到输出设备的次数。

std::endl与\n比较

std::endl的效率问题

使用std::endl通常效率低下,因为它实际上做了两个工作:它输出一个新行(将光标移动到控制台的下一行),并刷新缓冲区(这很慢)。如果我们输出多行文本以std::endl结尾,我们将得到多次刷新,这既慢又可能不必要。

自动刷新机制

当向控制台输出文本时,我们通常不需要自己显式刷新缓冲区。C++的输出系统设计为定期自刷新,让它自己刷新既简单又高效。

使用\n字符

要输出一个新行而不刷新输出缓冲区,我们使用\n(在单引号或双引号内),这是一个特殊符号,编译器将其解释为新行字符。\n将光标移动到控制台的下一行,而不会引起刷新,因此它通常会表现得更好。\n也更简洁,可以嵌入到现有的双引号文本中。

\n的使用示例

这是一个使用\n的不同方式的例子:

#include <iostream> // 用于std::cout

int main()
{
    int x{ 5 };
    std::cout << "x is equal to: " << x << '\n'; // 单引号(单独)(传统)
    std::cout << "And that's all, folks!\n"; // 嵌入到双引号字符串中(方便)
    return 0;
}

std::cin

std::cin是iostream库中的另一个预定义变量。与std::cout使用插入操作符«提供数据打印到控制台不同,std::cin(代表"字符输入")从键盘读取输入。我们通常使用提取操作符»将输入数据放入变量(然后可以在后续语句中使用)。

#include <iostream>  // 用于std::cout和std::cin

int main()
{
    std::cout << "Enter a number: "; // 要求用户输入一个数字

    int x{};       // 定义变量x以保存用户输入(并值初始化它)
    std::cin >> x; // 从键盘获取数字并存储在变量x中

    std::cout << "You entered " << x << '\n';
    return 0;
}

尝试编译并运行这个程序。当您运行程序时,第5行将打印"Enter a number: “。当代码到达第8行时,您的程序将等待您输入。一旦您输入一个数字(并按回车),您输入的数字将被分配给变量x。最后,在第10行,程序将打印"You entered “,然后是您刚刚输入的数字。 例如(输入值为4):

Enter a number: 4
You entered 4

这是从用户那里获取键盘输入的简单方法,我们将在许多示例中使用它。

提示 请注意,在接收一行输入时,您不需要输出’\n’,因为用户需要按回车键才能接受他们的输入,这将把光标移动到控制台的下一行。

如果您的屏幕在输入数字后立即关闭,请参见第0.8课——一些常见的C++问题以获得解决方案。

如果您使用的是CLion,并且"You entered"之前有一个空格,这是CLion中的一个错误。请参阅CLion错误跟踪器以获取解决方法。

就像可以在一行中输出多个文本一样,也可以在一行中输入多个值:

#include <iostream>  // 用于std::cout和std::cin

int main()
{
    std::cout << "Enter two numbers separated by a space: ";

    int x{}; // 定义变量x以保存用户输入(并值初始化它)
    int y{}; // 定义变量y以保存用户输入(并值初始化它)
    std::cin >> x >> y; // 获取两个数字并分别存储在变量x和y中

    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库不提供一种方法来接受键盘输入,而不需要用户按回车。如果您需要这样做,您必须使用第三方库。对于控制台应用程序,我们推荐pdcurses、FXTUI、cpp-terminal或notcurses。许多图形用户界面库都有自己的函数来执行这种事情。

std::cin是缓冲的

在前面的部分中,我们指出输出数据实际上是一个两阶段过程:

每个输出请求的数据被添加(到输出缓冲区的末尾)。

之后,输出缓冲区中的数据(缓冲区的前端)被刷新到输出设备(控制台)。

向缓冲区的末尾添加数据,并从缓冲区的前端移除数据,确保数据以添加的顺序被处理。这有时被称为FIFO(先进先出)。

同样,输入数据也是一个两阶段过程:

您输入的每个字符都被添加到输入缓冲区(在std::cin内部)的末尾。回车键(按下以提交数据)也被存储为’\n’字符。

提取操作符’»‘从输入缓冲区的前端移除字符,并将它们转换为值,该值通过复制赋值分配给相关变量。然后,该变量可以在后续语句中使用。

输入缓冲区中的每行输入数据都以’\n’字符结束。

我们将使用以下程序来演示这一点:

#include <iostream>  // 用于std::cout和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。最后,程序将打印You entered 4 and 5。 这次运行没有什么令人惊讶的。

运行#2:当遇到std::cin » x时,程序将等待输入。输入4 5。输入4 5\n进入输入缓冲区,但只有4被提取到变量x(提取在空格处停止)。

当遇到std::cin » y时,程序不会等待输入。相反,输入缓冲区中仍然存在的5被提取到变量y。然后程序打印You entered 4 and 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的值没有改变。

我们将在下面的测验中探讨更多案例。 操作符«与操作符» 新程序员经常混淆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。

顺便说一句… 如果您想知道为什么C++使用std::cout和std::cin而不是其他东西,请参阅https://en.wikipedia.org/wiki/Standard_streams。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"