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

How to get the cout buffer to flush on ubuntu

本文关键字:ubuntu 刷新 缓冲区 cout      更新时间:2023-10-16

我最近提交了一份作业,我开始使用VS代码和Ubuntu WSL终端和G ++编译器,但不得不切换到Visual Studio 2019,因为当我在同一行上输出几个字符串时,它们会相互覆盖。作业让我从文件中读取数据,并将每个元素放入"Car"对象的"ArrayList"(我们自制的矢量类)中,并将每个汽车元素输出给用户。我们还必须搜索汽车列表以查找某些型号的汽车并打印该型号的所有汽车。我不仅不能在同一行上列出所有这些元素,而且我无法将元素(字符串)相互比较。为什么这只发生在 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 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() << "nn";
}
}
}

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

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

不要用!cars.eof()来控制你的读取循环,请参阅:为什么!循环条件中的 eof() 总是错误的。问题的症结在于,在您最后一次成功读取文件位置指示器后,文件位置指示器位于文件末尾之前,您的流上没有设置.eofbit()。然后检查!cars.eof()(哪些测试true)并继续。

然后你打电话,例如std::getline4 次从不检查退货。读取在第一次std::getline(cars, tempIDstr);设置.eofbit()失败,但您无法检测到这一点,因此您继续,反复调用未定义的行为,尝试从设置.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"rn"行尾的示例输入文件

请注意 DOS 行尾表示0d 0a(十进制 13 10)"rn"

$ 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"rn"行尾。getline()如何工作?默认情况下,getline()读取第一个'n'字符,从输入流中提取'n',但不将其存储为返回的字符串的一部分。这会在您存储的每个字符串的末尾留下一个嵌入'r'。为什么这很重要?'r'(回车)正如其同名所说。就像一台旧打字机一样,光标位置被重置为行首。(解释为什么你看到你的输出被覆盖 - 它是)您编写文本直到遇到'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)

仔细查看,如果您有其他问题,请告诉我。