从ifstream读取时无法读取空格

32
我正在使用C++实现自定义词法分析器,但在尝试读取空格时,ifstream不会将其读出。我使用“>>”逐个字符地读取,但所有的空格都不见了。有没有办法让ifstream保留所有的空格并将其读出?我知道当读取整个字符串时,读取将停止在空格处,但我希望通过逐个字符地读取来避免这种行为。
尝试过.get(),很多答案都推荐它,但它与std::noskipws具有相同的效果,也就是说,现在我可以获取所有的空格,但我无法获取我需要用于词法分析的换行符。
以下是有问题的代码(扩展注释已被截断):
while(input >> current) {
    always_next_struct val = always_next_struct(next);
    if (current == L' ' || current == L'\n' || current == L'\t' || current == L'\r') {
        continue;
    }
    if (current == L'/') {
        input >> current;
        if (current == L'/') {
            // explicitly empty while loop
            while(input.get(current) && current != L'\n');
            continue;
        }

我在while行上断点,并查看每个current值的输入情况,但\r\n绝对不会出现在其中 - 输入只是跳到输入文件的下一行。


如果您在 while 行上中断,那么您肯定不希望在 current 中看到 \n,因为如果 get 遇到 \n,您将会在 continue 行而不是 while 行上。或者我误解了吗? - CB Bailey
L'\n' 是16位的 wchar_t,不是8位的 char,但是这应该没有任何区别。 - René Richter
@Charles:那么它就不会再出现错误,也不会开始显示文件中下一行的内容了。@Rene:这是一个wifstream - Puppy
2
三个输入都使用 input.get() 吗? - René Richter
@Rene:前两个完全按预期工作,它们没有空格需要跳过。 - Puppy
那可能是逻辑问题?请看我的编辑后的答案。 - René Richter
10个回答

36

有一个操纵器可用于禁用空格跳过行为:

stream >> std::noskipws;

1
我已经获取了所有空格,但仍然没有换行符。 - Puppy
3
你也可以使用 stream.unsetf(ios_base::skipws); 手动去除这个格式标志。 - sth
1
@sth:这正是noskipws所做的。 - R. Martinho Fernandes

14

运算符>>会吞掉空格(空格、制表符、换行符)。使用yourstream.get()来读取每个字符。

编辑:

注意:不同平台(Windows、Unix、Mac)在换行符的编码上有所不同。它可以是“\n”、“\r”或两者都有。它还取决于打开文件流的方式(文本或二进制)。

编辑(分析代码):

  while(input.get(current) && current != L'\n');
  continue;

如果没有到达文件结尾,current 中会出现一个 \n。然后你可以继续执行最外层的 while 循环。在那里,下一行的第一个字符将被读入 current 中。这不是你想要的吗?

我试图复现你的问题(使用 charcin 替代 wchar_twifstream):

//: get.cpp : compile, then run: get < get.cpp

#include <iostream>

int main()
{
  char c;

  while (std::cin.get(c))
  {
    if (c == '/') 
    { 
      char last = c; 
      if (std::cin.get(c) && c == '/')
      {
        // std::cout << "Read to EOL\n";
        while(std::cin.get(c) && c != '\n'); // this comment will be skipped
        // std::cout << "go to next line\n";
        std::cin.putback(c);
        continue;
      }
     else { std::cin.putback(c); c = last; }
    }
    std::cout << c;
  }
  return 0;
}

这个程序可以应用于自身,消除输出中的所有C++行注释。内部的while循环不会吃掉文件结尾后的所有文本。请注意putback(c)语句。如果没有它,换行符将不会出现。

如果对于wifstream无法达到同样效果,除非有一个原因:当打开的文本文件未保存为16位字符时,\n字符可能出现在错误的字节中...


不要获取\r,我在Windows上以文本模式打开,即CRLF。 - Puppy
@DeadMG:当我使用int c = std::cin.get();时,在Windows机器上按ENTER键会得到ASCII 10,因此在相同的方式下,它应该适用于ifstream - René Richter
这不是发生的事情。实际上发生的是整个文件在while循环中被读取,而且没有退出。预期的情况基本上就像你所说的-当找到行尾时,循环终止并继续外部循环。 - Puppy
@DeadMG:这很奇怪...所以我又尝试了一次。 - René Richter

6

您可以以二进制模式打开流:

std::wifstream stream(filename, std::ios::binary);

如果您这样做,将失去流提供的任何格式操作。

另一个选择是将整个流读入字符串,然后处理该字符串:

std::wostringstream ss;
ss << filestream.rdbuf();

当然,从ostringstream获取字符串需要额外复制一次字符串,因此如果您感到冒险,可以考虑在某些时候更改为使用自定义流。编辑:有人提到了istreambuf_iterator,这可能是比将整个流读入字符串更好的方法。


4

将流(或其缓冲区)包装在std::streambuf_iterator中?这样应该会忽略所有格式,并为您提供一个好的迭代器接口。

另一种更有效、更可靠的方法可能是使用Win32 API(或Boost)来内存映射文件。然后,您可以使用普通指针遍历它,并保证运行时不会跳过或转换任何内容。


有趣的想法。我以前没有真正使用过那个特定的迭代器类,我会看一下。 - Puppy
我发现这个迭代器是处理IOStreams的唯一明智方式,如果你想要对自己正在做的事情和发生的事情有任何控制。当然,它仍然很慢,因为你期望任何将IOStreams(慢)与逐字符I/O(同样慢)结合起来的东西都会很慢。但它能用! - jalf

3
您可以使用 std::streambuf_iterator 将流进行包装,以获取包含所有空格和换行符的数据,就像这样。
           /*Open the stream in default mode.*/
            std::ifstream myfile("myfile.txt");

            if(myfile.good()) {
                /*Read data using streambuffer iterators.*/
    vector<char> buf((std::istreambuf_iterator<char>(myfile)), (std::istreambuf_iterator<char>()));

                /*str_buf holds all the data including whitespaces and newline .*/
                string str_buf(buf.begin(),buf.end());

                myfile.close();
            } 

1
+1 对于使用 myfile.good() - 我以为那是一个打字错误,但是看到 good() 在 "good()=1 eof()=0 fail()=0 bad()=0" 时为 true - 所以似乎比 eof() 检查更优秀。 - Goblinhack
顺便提一下,您也可以这样做:“std::vector<char> buf(std::istreambuf_iterator<char>(myfile), {});” - 不确定这是否更清晰,据我所知,它会调用默认构造函数,因此{}可能更能说明这一点。 - Goblinhack

3

默认情况下,该 ifstream 对象已经设置了 skipws 标志,因此我们必须 禁用它。ifstream 对象具有这些默认标志,是因为 std::basic_ios::init 在每个新 ios_base 对象上调用(更多细节)。以下任何一种方法都可以:

in_stream.unsetf(std::ios_base::skipws);
in_stream >> std::noskipws; // Using the extraction operator, same as below
std::noskipws(in_stream); // Explicitly calling noskipws instead of using operator>>

其他标识符列于cpp reference上。


2

流提取器的行为相同,会跳过空格。

如果您想读取每个字节,可以使用未格式化的输入函数,例如stream.get(c)


根据@CharlesBailey的回答:我仍然没有理解换行符。 - Puppy

2
为什么不直接使用getline呢?这样可以获取所有的空格,虽然无法获取行尾字符,但你仍然可以知道它们所在的位置 :)

0

只需使用getline。

while (getline(input,current))
{
      cout<<current<<"\n";

}

不是一个好的答案...你可能会有一行没有以'\n'结尾...在这种情况下,即使它不存在,你也会添加'\n'。 - StyleZ

-4
我最终只是打开了Windows API,并使用它首先将整个文件读入缓冲区,然后逐字符读取该缓冲区。谢谢大家。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接