1 c++ csv string stringstream getline
几天来我一直试图解决这个问题,但我无法解决。基本上我的代码应该读取由 wmic 生成的 .csv 文件并将其保存到结构中。我可以读取数据并且正在存储它,但是数据在每个字符后都有一个额外的空格。我曾尝试切换到函数的 Unicode 版本并使用宽字符串,但它们只会使数据更加混乱(它们将“n”变成了“ÿ”)。
这是我认为是问题的代码:
system("wmic product get name,version,installdate,vendor /format:csv > product.txt");
std::ifstream infoFile("./program.txt"); // The file wmic wrote in csv format.
if(infoFile.is_open())
{
std::string line;
int lineNum = 0;
while(getline(infoFile, line))
{
lineNum++;
std::cout << "\nLine #" << lineNum << ":" << std::endl;
Program temp;
std::istringstream lineStream(line);
std::string cell;
int counter = 0;
int cellNum = 0;
while(getline(linestream, cell, ','))
{
cellNum++;
std::cout << "\nCell #" << cellNum << ":" << cell << std::endl;
switch(counter)
{
case 0:
break;
case 1:
temp.installDate = cell;
break;
case 2:
temp.name = cell;
break;
case 3:
temp.vendor = cell;
break;
case 4:
temp.version = cell;
break;
default:
std::cout << "GetProductInfo(): Invalid switch value: " << counter << std::endl;
break;
}
counter++;
}
information->push_back(temp); // Vector to save all of the programs.
}
infoFile.close();
}
else
{
std::cout << "GetProductInfo(): Failed to open the input file." << std::endl;
return 1;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编辑: 好的,我正在尝试编写 BOM(FF FE 0D 00 0A),因为它之前没有编写过。我正在编写一个带有十六进制值的字符数组,但添加了一个额外的 0x0D(FF FE 0D 00 0D 0A)。它还使用额外的空格保存内部变量。这可能不是问题,因为我可以修改我的代码来解决它,但这不是最佳的。有任何想法吗?
Edit2: 所以我想我不需要 BOM。我现在的主要问题是读取 UTF-16LE 文件并将数据保存到一个没有额外空格的结构中。我需要一些帮助以正确的方式来做,因为我想弄清楚将来如何防止这种情况发生。感谢大家的帮助,这个bug很关键。
这闻起来很像文本编码问题,所以我继续尝试运行您提供的命令,果然,输出文件是用 UCS16LE 编码的。(这是 16 位字符,little-endian。)尝试在十六进制编辑器中打开文件以查看它的实际外观。
尝试使用宽字符串时,您走在正确的道路上,但处理 Unicode 可能会很棘手。接下来的几段将为您提供一些有关如何以艰难的方式处理此问题的提示,但如果您需要快速简便的解决方案,请跳到最后。
有两件事要小心。首先,确保您还使用宽流,例如 wcout。值得将每个字符转换为 int 以仔细检查输出格式是否有问题。
其次,wcout、wstring等格式不规范。在某些编译器上,每个字符是 2 个字节,而在其他编译器上,它是 4 个字节。您通常可以在编译器设置中更改它。C++11 还提供了 std::u16string 和 std::u32string,它们更明确地说明了它们的大小。
不幸的是,使用 C++ 库读取 Unicode 文本可能有点麻烦,因为即使您有正确的字符串大小,您也需要处理 BOM 和字节序格式,更不用说规范化了。
有一些库可以帮助解决这个问题,但最简单的解决方案可能只是在记事本中打开 txt 文件,选择另存为,然后选择一种您更喜欢的编码,如 ANSI。
编辑:如果您对快速而肮脏的解决方案不满意,并且不想使用更好的 Unicode 库,则可以使用标准库来执行此操作,但前提是您使用的是支持 C+ 的编译器+11,例如 Visual Studio 2012。
C++11 添加了一些codecvt
方面来处理不同 Unicode 文件类型之间的转换。这应该适合您的目的,但是库的这一部分的底层设计是在过去或过去设计的,并且可能相当难以理解。抓住你的裤子。
在您打开 的行下方ifstream
,添加以下代码:
infoFile.imbue(std::locale(infoFile.getloc(), new std::codecvt_utf16<char, 0x10FFFF, std::consume_header>));
Run Code Online (Sandbox Code Playgroud)
我知道这看起来有点吓人。它正在做的是从现有语言环境的副本制作“语言环境”,然后向处理格式转换的语言环境添加一个“方面”。
“Locales”处理一大堆东西,主要与本地化有关(例如如何标点货币,例如“100.00”与“100,00”)。语言环境中的每条规则都称为一个方面。在 C++ 标准库中,文件编码被视为这些方面之一。
(背景:回想起来,将文件编码与本地化混合起来可能不是一个非常明智的想法,但是在设计库的这一部分时,文件编码通常由程序语言决定,所以这就是我们陷入了这种情况。)
所以locale
上面的构造函数locale
将文件流创建的默认值的副本作为它的第一个参数,第二个参数是要使用的新构面。
codecvt_utf16
是用于与 utf-16 相互转换的方面。第一个参数是“wide”类型,即程序使用的类型,而不是字节流中使用的类型。我char
在此处指定,这适用于 Visual Studio,但根据标准实际上无效。稍后我会谈到这一点。
第二个参数是你想要接受的最大 Unicode 值而不抛出错误,在可预见的未来,0x10FFFF 代表最大的 Unicode 字符。
最后一个参数是一个位掩码,它改变了 facet 的行为。我认为std::consume_header
这对你特别有用,因为wmic
输出了一个 BOM(至少在我的机器上)。这将消耗该 BOM,并根据它获得的内容选择是将其视为小端流还是大端流。
您还会注意到,我正在使用 来创建堆栈上的构面new
,但我没有delete
在任何地方调用。这不是在现代 C++ 中设计库的一种非常安全的方式,但正如我所说,语言环境是库中相当古老的部分。
请放心,您不需要delete
这个方面。这并没有被很好地记录下来(因为语言环境在实践中很少使用),但是默认构造的 facet 将被delete
它所附加的语言环境自动d。
现在,还记得我说过将其char
用作宽类型是无效的吗?标准说你必须使用whcar_t
, char16_t
or char32_t
, 如果你想支持非 ASCII 字符,你肯定想要这样做。使这一有效最简单的方法是使用wchar_t
,变更ifstream
,string
,cout
,和istringstream
以wifstream
,wstring
,wcout
,和wistringstream
,然后确保你的字符串/字符常量有一个L
在他们面前,就像这样:
std::wcout << L"\nLine #" << lineNum << L":" << line << std::endl;
Run Code Online (Sandbox Code Playgroud)
这些都是使用宽字符串所需的所有更改。但是,还要注意 Windows 控制台无法处理非 ANSI 字符,因此如果您尝试输出这样的字符(当我运行代码时遇到了 ™ 字符),wcout 流将失效并停止输出任何内容。如果您输出到文件,这应该不是问题。
您可能会说我对标准库的这一部分并不特别感兴趣。在实践中,大多数想要使用 Unicode 的人会使用不同的库(就像我在评论中提到的那些),或者滚动他们自己的编码器/解码器。