【问题标题】:How to get the cout buffer to flush on ubuntu如何让 cout 缓冲区在 ubuntu 上刷新
【发布时间】:2020-01-18 21:06:24
【问题描述】:

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

我尝试以多种方式刷新系统(正如我从其他帖子中发现的那样),例如:std::cerr、std::cout

这是我的(缩短的)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

每当我尝试比较字符串时,输出都会返回一个空行:

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 作为我的开发终端的愿望。

【问题讨论】:

  • “我会在同一行输出几个字符串,它们会相互覆盖”...您是否偶然在 windows 下创建了文件并且它有嵌入式'\r'(回车——就像旧打字机一样——将马车返回到起始位置?)
  • 这是输入文件使用\r\n换行符的明显案例
  • 除非有什么特别的事情发生,否则不要使用cout.flush。相反,如果你真的想刷新一行,只需使用std::cout &lt;&lt; std::endl

标签: c++ linux string ubuntu cout


【解决方案1】:

不要使用!cars.eof() 控制您的读取循环,请参阅:Why !.eof() inside a loop condition is always wrong.。问题的症结在于您的最后一次成功读取之后,file-position-indicator 位于 end-of-file 之前,您的信息流中没有设置.eofbit()。然后检查!cars.eof()(测试true)并继续。

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

相反,要么循环不断检查使用的每个输入函数的返回,要么使用读取函数的返回作为读取循环中的条件,例如,您可以执行类似以下操作:

    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 中使用。

现在开始你的经典回车问题。很明显,您正在读取的文件包含 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',但不将其存储为返回字符串的一部分。这会在您存储的每个字符串的末尾留下一个嵌入的'\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 行结尾的输出:

使用/输出示例

$ ./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

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

【讨论】:

    猜你喜欢
    • 2011-08-26
    • 2020-04-06
    • 2020-12-12
    • 2010-09-20
    • 1970-01-01
    • 2015-08-10
    • 2021-09-03
    • 2018-07-11
    相关资源
    最近更新 更多