我最近提交了一份作业,我开始使用 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”中的实现:
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 作为我的开发终端的愿望。
不要用!cars.eof()
see控制你的读循环:Why !.eof() inside a loop condition总是错误的。. 问题的关键是在您最后一次成功读取文件位置指示器位于文件结尾之前.eofbit()
,您的流中没有设置。然后您检查!cars.eof()
(哪些测试true
)并继续。
然后你打电话,例如std::getline
4 次从不检查 return。读取在您的第一个std::getline(cars, tempIDstr);
设置时失败.eofbit()
,但您无法检测到这一点,因此您继续调用Undefined Behavior反复尝试从带有.eofbit()
set的流中读取,然后使用 等中的不确定值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'
(回车)做它的同名说什么。就像老式打字机一样,光标位置被重置到行首。(解释为什么你看到你的输出被覆盖——它是)你写文本直到'\r'
遇到a然后光标回到行的开头,接下来写的内容会覆盖你刚刚输出的内容。
相反,您的文件应该是带有 Unix/POSIX 行尾的文件:
带有 Unix/POSIX'\n'
行结尾的示例输入文件
请注意 Unix/POSIX 行尾表示为0a
(decimal 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
)
仔细检查一下,如果您还有其他问题,请告诉我。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句