【问题标题】:C++ getline adding spacesC ++ getline添加空格
【发布时间】:2013-05-27 16:46:50
【问题描述】:

我已经尝试解决这个问题好几天了,但我无法解决。基本上我的代码应该读取由 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;
}

编辑: 好的,我正在尝试编写 BOM (FF FE 0D 00 0A),因为它以前没有被编写过。我正在用十六进制值编写一个 char 数组,但是添加了一个额外的 0x0D(FF FE 0D 00 0D 0A)。它还使用额外的空格保存内部变量。 可能不是问题,因为我可以修改我的代码来解决这个问题,但这不是最佳的。有什么想法吗?

编辑2: 所以我想我不需要BOM。我现在的主要问题是读取 UTF-16LE 文件并将数据保存到没有额外空格的结构中。我需要一些帮助以正确的方式来做这件事,因为我想弄清楚将来如何防止这种情况发生。谢谢大家的帮助,这个bug很严重。

【问题讨论】:

  • 在编辑器中打开文件 program.txt 是否也有多余的空格?还是在您在程序中阅读之后?
  • 您正在重定向到product.txt,但从program.txt 读取。这是故意的吗?

标签: c++ string csv getline stringstream


【解决方案1】:

这闻起来很像文本编码问题,所以我继续尝试运行您提供的命令,果然,输出文件以 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>));

我知道这看起来有点吓人。它所做的是从现有语言环境的副本中创建“语言环境”,然后向处理格式转换的语言环境添加“方面”。

“Locales”处理一大堆东西,主要与本地化有关(例如如何标点货币,例如“100.00”与“100,00”)。语言环境中的每个规则都称为一个方面。在 C++ 标准库中,文件编码被视为这些方面之一。

(背景:回想起来,将文件编码与本地化混合可能不是一个非常明智的想法,但是在设计库的这一部分时,文件编码通常由程序的语言决定,所以我们就是这样陷入这种境地的。)

因此,上面的locale 构造函数将文件流创建的默认locale 的副本作为其第一个参数,第二个参数是要使用的新方面。

codecvt_utf16 是用于与 utf-16 相互转换的方面。第一个参数是“宽”类型,也就是程序使用的类型,而不是字节流中使用的类型。我在这里指定了char,它适用于Visual Studio,但根据标准它实际上并不有效。稍后我会讲到。

第二个参数是你希望在不抛出错误的情况下接受的最大 Unicode 值,在可预见的未来,0x10FFFF 代表最大的 Unicode 字符。

最后一个参数是一个位掩码,用于改变构面的行为。我认为std::consume_header 对你特别有用,因为wmic 输出一个BOM(至少在我的机器上)。这将消耗该 BOM,并根据获得的内容选择是将其视为小端流还是大端流。

您还会注意到我正在使用new 在堆栈上创建构面,但我没有在任何地方调用delete。这不是在现代 C++ 中设计库的一种非常安全的方法,但正如我所说,语言环境是库中相当古老的部分。

请放心,您不需要delete 这个方面。这实际上并没有很好地记录(因为在实践中很少使用区域设置),但是默认构造的构面将由它所附加的区域设置自动deleted。

现在,还记得我说过使用char 作为宽类型无效吗?标准规定你必须使用whcar_tchar16_tchar32_t,如果你想支持非ASCII 字符,你肯定会想要这样做。使其有效的最简单方法是使用wchar_t,将ifstreamstringcoutistringstream 更改为wifstreamwstringwcoutwistringstream,然后确保您的字符串/字符常量前面有一个L,如下所示:

std::wcout << L"\nLine #" << lineNum << L":" << line << std::endl;

为了使用宽字符串,您需要进行这些更改。但是,还要注意 Windows 控制台无法处理非 ANSI 字符,因此如果您尝试输出这样的字符(当我运行代码时,我遇到了一个 ™ 字符),wcout 流将失效并停止输出任何内容。如果您要输出到文件,这应该不是问题。

您可能会说我对标准库的这一部分并不特别兴奋。在实践中,大多数想要使用 Unicode 的人会使用不同的库(就像我在 cmets 中提到的那样),或者使用自己的编码器/解码器。

【讨论】:

  • 感谢您提供的非常丰富的答案!有没有办法自动进行文件编码转换?我正在尝试编写一个几乎不需要用户交互的工具。
  • 有很多方法可以实现自动化。规范库称为 ICU,虽然它有点复杂。 Qt 的 QTextStream 更容易使用。如果您对一个简单的 hack 感到满意,并且只需要支持具有 PowerShell 的 Windows 版本,则可以使用 Set-Content cmdlet。
  • 好的,我正在尝试编写 BOM (FF FE 0D 00 0A),因为它以前没有被编写过。我正在用十六进制值编写一个 char 数组,但是添加了一个额外的 0x0D(FF FE 0D 00 0D 0A)。有任何想法吗?再次感谢您的帮助。
  • 您可能想用代码 sn-p 开始另一个问题,但我有一些想法:(1)您确定要 BOM 吗?如果您知道预期的格式,则可以更轻松地处理没有 BOM 的文件。 (2) BOM 是not FF FE 0D 0A。 BOM 只是 FF FE。如果您有 FF FE 0D 00 0A 00,那么这将是 16LE BOM,后跟回车符和换行符。 (3) 如果您正在使用 char 数组,请仔细检查您的演员表。请记住,如果您将 char 数组转换为 wchar_t 数组(例如),您会将相邻的字符混合在一起。
  • 谢谢,我想我不需要 BOM。我试图添加它,以便我可以在记事本等中打开它,但这并不是真正需要的。我现在的问题是如何从 wmic 读取数据(使用 UTF-16LE 编码)并将其保存到没有额外空格的 c++ 结构中。
【解决方案2】:

如果您的数据没有您需要的任何空格,您可以使用我的示例:

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

或者,您可以使用来自cstring 库的strtok。你也可以查看我的问题,基本一样:strtok() analogue in C++

【讨论】:

  • 我需要空格,因为我要保存程序名称和供应商,这些名称和供应商通常包含多个单词。
  • 您可以尝试erase 方法对字符串删除最后一个字符。
  • 什么意思?如果您说它将删除单元格的最后一个字符,那将不起作用,因为每个字符后面都有一个空格,而不仅仅是最后一个。
  • 据我所知:getline(linestream, cell, ',')) 你用分隔符','分割你的字符串。然后,您可以拨打cell.erase(cell.end()-1,cell.end());
  • 那不只是删除最后一个字符吗?问题是“单元格”中的每个字符后面都有一个空格。
【解决方案3】:

如果数据在每个字符之后有一个额外的空格,我想这意味着它在一个常规空格之后也有一个额外的空格。

因此,您可以安全地擦除之前没有其他空格的每个空格(实际上是每个字符)。这假设您在原始数据中没有连续两个空格,但如果有,您只需要一个额外的标志来处理它。

所以你的代码可能会变成这样:

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 #"...

我意识到这并不完全理想,因为您实际上并没有阻止核心问题的发生,但考虑到您已经尝试了几天,这至少可以通过在问题发生后解决它来有效地缓解问题。

检查live demo

【讨论】:

    猜你喜欢
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 2017-01-19
    • 1970-01-01
    • 2015-05-24
    • 2021-12-27
    • 1970-01-01
    相关资源
    最近更新 更多