如何让 cout 缓冲区在 ubuntu 上刷新

How to get the cout buffer to flush on ubuntu

我最近提交了一个作业,我已经开始使用 VS 代码和 Ubuntu WSL 终端和 G++ 编译器,但不得不切换到 Visual Studio 2019,因为当我在同一行,他们会互相覆盖。作业让我从文件中读取数据并将每个元素放入 "Car" 对象的 "ArrayList"(我们自制的向量 class)中,并将每个汽车元素输出给用户。我们还必须搜索汽车列表以找到特定型号的汽车并打印该型号的所有汽车。我不仅无法在同一行中计算出所有这些元素,而且无法将元素(字符串)相互比较。为什么这只会发生在 Ubuntu 上?为什么我不能用 std::cout.flush(); 清除 cout 缓冲区?或 std::cout << std::flush;?为什么我不能相互比较元素?

我已尝试通过多种方式刷新系统(正如我从其他帖子中发现的那样),例如:std::cerr、std::cout << std::flush;。唯一似乎有效的是如果我使用 std::endl,但是,我需要将它们放在同一行。我也无法使用 == 操作数(或任何其他操作数)比较两个字符串。虽然,我可以很好地比较两个 int 元素。

这是我的(缩短的)cars.data(.data 是作业的要求),它包含所有要存储到 "ArrayList" 中的汽车元素:

1
Tesla
Model 3
Black
2
Chevrolet
Volt
Grey
3
Tesla
Model S
White
4
Nissan
Leaf
White
5
Toyota
Prius
Red

我将每个元素存储到 "ArrayList" 中的实现:

    ArrayList cars_list(15);

    std::fstream cars;
    cars.open("cars.data");

    int tempID;
    std::string tempIDstr;
    std::string tempMake;
    std::string tempModel;
    std::string tempColor;

    if (cars.is_open())
    {
        for (int i = 0; !cars.eof(); ++i)
        {
            std::getline(cars, tempIDstr);
            tempID = std::stoi( tempIDstr );
            std::getline(cars, tempMake);
            std::getline(cars, tempModel);
            std::getline(cars, tempColor);

            Car tempCar(tempID, tempMake, tempModel, tempColor);

            std::cout.flush();
            std::cout << tempIDstr << " ";
            std::cout.flush();
            std::cout << tempMake << " ";
            std::cout.flush();
            std::cout << tempModel << " ";
            std::cout.flush();
            std::cout << tempColor << " " << std::endl;

            cars_list.push_back(tempCar);
        }
    }
cars.close();

还有一个我用来比较字符串以搜索列表的函数:

void searchByMake(ArrayList list)
{
    std::string make;
    std::cout << "Enter the make you would like to search: ";
    std::cin >> make;
    std::cin.clear();
    std::cin.ignore(10000,'\n');

// Searching through the cars_list for the Make
    for (int i = 0; i < list.size(); ++i) 
    {  
        Car tempCar = list.get(i);
        if (make.compare(tempCar.getMake()) == 0)
        {
            std::cout << "ID:\t" << tempCar.getID() << "\n" 
                    << "Make:\t" << tempCar.getMake() << "\n" 
                    << "Model:\t" << tempCar.getModel() << "\n" 
                    << "Color:\t" << tempCar.getColor() << "\n\n";
        }
    }
}

第一段代码的结果是(注意每次输出前的空格):

 Black 3
 Greyvrolet
 White S
 Whitean
 Redusta

预期输出应如下所示:

1 Tesla Model 3 Black
2 Chevrolet Volt Grey
3 Tesla Model S White
4 Nissan Leaf White 
5 Toyota Prius Red

每当我尝试比较字符串时,输出 returns 一个空行:

Enter the make you would like to search: Tesla

预期输出为:

Enter the make you would like to search: Tesla
id: 1
Make: Tesla
Model: Model 3
Color: Black

id: 3
Make: Tesla
Model: Model S
Color: White

我的老师提到问题可能出在 Ubuntu 本身即使提示时也无法清除缓冲区,但我仍然找不到解决方案。仅供参考,这是一项已通过的作业,我无法再获得信任,这个问题完全出于好奇,并且希望仍然使用 Ubuntu WSL 作为我的开发终端。

不要用 !cars.eof() 控制你的读循环见:Why !.eof() inside a loop condition is always wrong.。问题的症结在于 上次成功读取 之后 file-position-indicator 立即坐下在 文件结尾 之前,您的流上没有设置 .eofbit()。然后检查 !cars.eof()(测试 true)并继续。

然后你打电话,例如std::getline 4 次 从不检查 return。读取在您的第一个 std::getline(cars, tempIDstr); 设置 .eofbit() 上失败,但您无法检测到这一点,所以您继续,调用 Undefined Behavior 反复尝试读取设置了 .eofbit() 的流,然后使用 tempIDstr 中的不确定值,等等。就好像它们包含有效数据一样。

相反,要么循环不断地检查每个使用的输入函数的 return,要么使用读取函数的 return 作为读取循环中的条件,例如,您可以做类似的事情:

    std::ifstream f(argv[1]);    /* open file for reading */
    ...
    while (getline (f,tmp.IDstr) && getline (f,tmp.Make) && 
            getline (f,tmp.Model) && getline (f,tmp.Color))
        cars_list[n++] = tmp;

以上,只有当您对 getline 的所有调用都成功时,您的循环才会成功,并且只有在所有调用都成功时,您的数据才会在 cars_list.

中使用

现在开始您的经典 carriage-return 问题。很明显,您正在读取的文件包含 DOS 行结束符。例如,如果您查看输入文件的实际内容,您将看到:

使用 DOS "\r\n" 行尾的示例输入文件

注意 DOS 行结尾表示为 0d 0a(十进制 13 10)"\r\n":

$ hexdump -Cv dat/cars_dos.txt
00000000  31 0d 0a 54 65 73 6c 61  0d 0a 4d 6f 64 65 6c 20  |1..Tesla..Model |
00000010  33 0d 0a 42 6c 61 63 6b  0d 0a 32 0d 0a 43 68 65  |3..Black..2..Che|
00000020  76 72 6f 6c 65 74 0d 0a  56 6f 6c 74 0d 0a 47 72  |vrolet..Volt..Gr|
00000030  65 79 0d 0a 33 0d 0a 54  65 73 6c 61 0d 0a 4d 6f  |ey..3..Tesla..Mo|
00000040  64 65 6c 20 53 0d 0a 57  68 69 74 65 0d 0a 34 0d  |del S..White..4.|
00000050  0a 4e 69 73 73 61 6e 0d  0a 4c 65 61 66 0d 0a 57  |.Nissan..Leaf..W|
00000060  68 69 74 65 0d 0a 35 0d  0a 54 6f 79 6f 74 61 0d  |hite..5..Toyota.|
00000070  0a 50 72 69 75 73 0d 0a  52 65 64 0d 0a           |.Prius..Red..|
0000007d

您的文件有 DOS "\r\n" 行结尾。 getline() 是如何工作的?默认情况下 getline() 读取第一个 '\n' 字符,从输入流中提取 '\n',但不将其存储为字符串 returned 的一部分。这会在您存储的每个字符串的末尾留下一个嵌入的 '\r' 。为什么这很重要? '\r' (carriage-return) 就像它的名字所说的那样。就像旧打字机一样,光标位置重置为行首。 (解释为什么你看到你的输出被覆盖 - 它是)你写文本直到遇到 '\r' 然后光标回到行的开头,接下来写的内容会覆盖你刚刚在那里输出的内容.

相反,您的文件应该是具有 Unix/POSIX 行结尾的文件:

带有 Unix/POSIX '\n' 行结尾的示例输入文件

注意 Unix/POSIX 行尾表示为 0a(十进制 10)'\n':

$ hexdump -Cv dat/cars.txt
00000000  31 0a 54 65 73 6c 61 0a  4d 6f 64 65 6c 20 33 0a  |1.Tesla.Model 3.|
00000010  42 6c 61 63 6b 0a 32 0a  43 68 65 76 72 6f 6c 65  |Black.2.Chevrole|
00000020  74 0a 56 6f 6c 74 0a 47  72 65 79 0a 33 0a 54 65  |t.Volt.Grey.3.Te|
00000030  73 6c 61 0a 4d 6f 64 65  6c 20 53 0a 57 68 69 74  |sla.Model S.Whit|
00000040  65 0a 34 0a 4e 69 73 73  61 6e 0a 4c 65 61 66 0a  |e.4.Nissan.Leaf.|
00000050  57 68 69 74 65 0a 35 0a  54 6f 79 6f 74 61 0a 50  |White.5.Toyota.P|
00000060  72 69 75 73 0a 52 65 64  0a                       |rius.Red.|
00000069

要查看效果,让我们看一个读取输入文件的简短示例,使用 Unix/POSIX 行结尾和再次使用 DOS 行结尾,以查看操作上的差异。简短的例子可以是:

#include <iostream>
#include <fstream>

struct ArrayList {
    std::string IDstr, Make, Model, Color;
};

int main (int argc, char **argv) {

    if (argc < 2) {
        std::cerr << "error: insufficient input.\n" <<
                    "usage: " << argv[0] << " filename.\n";
        return 1;
    }

    std::ifstream f(argv[1]);
    ArrayList cars_list[15], tmp;
    size_t n = 0;

    while (getline (f,tmp.IDstr) && getline (f,tmp.Make) && 
            getline (f,tmp.Model) && getline (f,tmp.Color))
        cars_list[n++] = tmp;

    for (size_t i = 0; i < n; i++)
        std::cout   << " " << cars_list[i].IDstr 
                    << " " << cars_list[i].Make
                    << " " << cars_list[i].Model 
                    << " " << cars_list[i].Color << '\n';
}

现在让我们看看您的文件是否有 Unix/POSIX 行结尾的输出:

例子Use/Output

$ ./bin/arraylist_cars dat/cars.txt
 1 Tesla Model 3 Black
 2 Chevrolet Volt Grey
 3 Tesla Model S White
 4 Nissan Leaf White
 5 Toyota Prius Red

现在让我们看一下读取以 DOS 行结尾的文件后的输出:

$ ./bin/arraylist_cars dat/cars_dos.txt
 Black 3
 Greyrolet
 White S
 Whiten
 Redusa

奇怪的是,这看起来与您报告的输出相似。提示,在 WSL 中,您应该有一个名为 dos2unix 的工具(它将行尾从 DOS 转换为 Unix)。在您的输入文件上使用它,例如dos2unix filename。现在重新运行你的程序(修复你的读取循环后)使用文件作为输入,你的问题应该消失。

(如果您没有安装dos2unix,请安装它,例如sudo apt-get dos2unix

检查一下,如果您还有其他问题,请告诉我。