我一直试图解决这个问题好几天,我无法得到它。基本上我的代码应该读取由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;
}
修改 O.K.,我正在尝试编写BOM(FF FE 0D 00 0A),因为之前没有写过。我正在写一个带有十六进制值的char数组,但是添加了一个额外的0x0D(FF FE 0D 00 0D 0A)。它还使用额外的空格保存内部变量。那个可能不是问题,因为我可以修改我的代码来解释它,但这不是最佳的。有什么想法吗?
EDIT2: 所以我想我不需要BOM。我现在的主要问题是只读取UTF-16LE文件并将数据保存到没有额外空格的结构中。我需要一些帮助正确的方式,因为我想知道将来如何防止这种情况。谢谢你帮助大家,这个bug至关重要。
答案 0 :(得分:4)
这闻起来很像文本编码问题,所以我继续尝试运行你提供的命令,果然,输出文件是用UCS16LE编码的。 (这是16位字符,小端。)尝试在十六进制编辑器中打开文件,看看它实际上是什么样的。
在尝试使用宽字符串时,您处于正确的路径,但处理Unicode可能会非常棘手。接下来的几段将为您提供一些如何处理这一难题的技巧,但如果您需要快速简便的解决方案,请跳到最后。
要注意两件事。首先,确保您也使用宽流,例如wcout。值得将每个字符转换为int来仔细检查输出格式是否存在问题。
其次,wcout,wstring等的格式不是标准的。在某些编译器中,每个字符串为2个字节,而在其他编译器上则为4个。您通常可以在编译器设置中更改它。 C ++ 11还提供了std :: u16string和std :: u32string,它们的大小更明确。
对于C ++库来说,读取Unicode文本可能会有点麻烦,因为即使你有正确的字符串大小,你也需要处理BOM和endian格式,更不用说规范化了。
有哪些库可以帮助解决这个问题,但最简单的解决方案可能只是在记事本中打开txt文件,选择另存为,然后选择您更熟悉的编码,如ANSI。 / p>
修改:如果您对快速而肮脏的解决方案不满意,并且您不想使用更好的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>));
我知道这看起来有点吓人。它正在做的是做一个&#34; locale&#34;从现有区域设置的副本,然后添加一个&#34; facet&#34;到处理格式转换的语言环境。
&#34;区域设置&#34;处理一大堆东西,主要与本地化有关(例如如何标点货币,例如&#34; 100.00&#34; vs&#34; 100,00&#34;)。语言环境中的每个规则称为构面。在C ++标准库中,文件编码被视为其中一个方面。
(背景:回想起来,将文件编码与本地化混合起来可能并不是一个明智的想法,但在设计这个部分的时候,文件编码通常由程序的语言决定。 ,这就是我们如何陷入这种情况。)
因此上面的locale
构造函数将文件流创建的默认locale
的副本作为其第一个参数,第二个参数是要使用的新构面。
codecvt_utf16
是转换为utf-16的一个方面。第一个参数是&#34;宽&#34; type,也就是说,程序使用的类型,而不是字节流中使用的类型。我在这里指定了char
,它适用于Visual Studio,但根据标准,它实际上并不有效。我稍后会谈到的。
第二个参数是您想要接受的最大Unicode值而不会抛出错误,并且在可预见的将来,0x10FFFF代表最大的Unicode字符。
最后一个参数是一个位掩码,用于更改构面的行为。我认为std::consume_header
对您特别有用,因为wmic
会输出BOM(至少在我的机器上)。这将消耗该BOM,并选择是否将其视为小端或大端流,具体取决于它获取的内容。
您还会注意到我已使用new
在堆栈上创建构面,但我没有在任何地方调用delete
。这不是一种在现代C ++中设计库的非常安全的方法,但正如我所说,locales是库中相当古老的部分。
请放心,您不需要delete
这个方面。这并没有很好地记录(因为语言环境在实践中很少使用),但默认构造的方面将由它附加的语言环境自动delete
。
现在,请记住我如何使用char
作为宽类型无效?该标准规定您必须使用whcar_t
,char16_t
或char32_t
,如果您想支持非ASCII字符,那么您肯定希望这样做。使此有效的最简单方法是使用wchar_t
,将ifstream
,string
,cout
和istringstream
更改为wifstream
,{ {1}},wstring
和wcout
,然后确保你的字符串/字符常量在它们前面有一个wistringstream
,如下所示:
L
这些是您使用宽字符串所需的所有更改。但是,还要注意Windows控制台无法处理非ANSI字符,因此如果您尝试输出这样的字符(当我运行代码时,我点击了一个字符),wcout流将无效并停止输出任何内容。如果您要输出到文件,这不应该是一个问题。
你可能会说我对标准库的这一部分并不特别兴奋。在实践中,大多数想要使用Unicode的人会使用不同的库(就像我在评论中提到的那样),或者使用他们自己的编码器/解码器。
答案 1 :(得分:0)
如果您的数据没有任何空格,您可以使用我的示例:
std::string s = "test, delim, ";
std::string delims = ", ";
size_t pos = 0;
std::string token;
while((pos=s.find(delimiter))!=std::string::npos))
{ token = s.substr(0,pos);
std::cout<<token<<std::endl;
s.erase(0, pos + delimiter.length());
}
std::cout<<s<<std::endl //last word
或者,您可以使用strtok
库中的cstring
。您也可以查看我的问题,它是完全相同的:strtok() analogue in C++
答案 2 :(得分:0)
如果数据在每个字符后有一个额外的空格,我想这意味着它在常规空格之后还有一个额外的空格。
因此,您可以安全地擦除之前没有其他空格的每个空间(实际上是每个字符)。这假设您在原始数据中没有连续两个空格,但如果这样做,您只需要一个额外的标记来处理它。
所以你的代码可能会变成这样:
while(getline(infoFile, line))
{
int lsize = line.size(), at = 1;
for(int i = 1; i < lsize; ++i)
if(line[i-1] == ' ') line[at++] = line[i];
// if there is no space behind it, skip it, it is a broken space itself!
line.resize(at);
lineNum++;
// std::cout << "\nLine #"...
我意识到这并不是完全理想的,因为你实际上并没有阻止核心问题的发生,但考虑到你已经尝试了好几天,这至少可以通过在问题发生之后修复它来有效地缓解这个问题。 p>
检查live demo。