【问题标题】:Detecting end of input using std::getline使用 std::getline 检测输入结束
【发布时间】:2013-11-09 12:00:01
【问题描述】:

我有一个带有以下 sn-p 的代码:

std::string input;
while(std::getline(std::cin, input))
{   
    //some read only processing with input
}

当我运行程序代码时,我通过文件 in.txt(使用 gedit 创建)重定向标准输入输入,它包含:

ABCD
DEFG
HIJK

上述每一行在文件 in.txt 中都以一个换行符结尾。

我面临的问题是,while循环运行3次后(每行),程序控制不前进,卡住了。我的问题是为什么会发生这种情况,我该怎么做才能解决这个问题?

一些澄清:

我希望能够像这样从命令行运行程序:

$ gcc program.cc -o out
$ ./out < in.txt

附加信息:

我做了一些调试,发现while循环实际上运行了4次(第四次输入为空字符串)。这会导致循环编程停止,因为 // 某些使用输入只读的处理 无法完成其工作。

所以我提炼的问题:

1) 为什么第四个循环会运行?

在 while 循环的条件下使用 std::getline() 背后的基本原理 必须是,当 getline() 无法读取更多输入时,它会返回 零,因此 while 循环中断。

与此相反,while 循环 而是继续一个空字符串!为什么那么有getline while 循环条件呢?这不是糟糕的设计吗?

2) 如果不使用 break 语句,如何确保 while 不会第四次运行?

目前我使用了如下的 break 语句和字符串流:

std::string input;
char temp;
while(std::getline(std::cin, input))
{       
    std::istringstream iss(input);
    if (!(iss >>temp))
    {    
        break;
    } 
    //some read only processing with input
}

但显然必须有更优雅的方式。

【问题讨论】:

  • 确实不应该卡住。你用的是什么编译器?
  • 我使用的是 gcc 版本 4.6.3
  • @zalenix 我很确定你的问题来自//some processing with input ...
  • @zalenix 'while 循环每行运行 3 次' 听起来很奇怪(顺便说一句,这不应该是编译器/lib 问题。我很确定如果曾经存在,我们会知道的!)

标签: c++ while-loop stdin getline gedit


【解决方案1】:

DeadMG's answer相反,我认为问题出在您输入文件的内容上,而不是您对换行符行为的期望。


更新:现在我有机会玩gedit,我想我知道是什么导致了问题。 gedit 显然旨在使创建最后一行没有换行符的文件变得困难(这是明智的行为)。如果您打开gedit 并输入三行输入,在每行末尾输入Enter,然后保存文件,它实际上将创建一个4 行文件,其中第4 行为空。使用您的示例,文件的完整内容将是"ABCD\nEFGH\nIJKL\n\n"。为避免创建额外的空行,请不要在最后一行的末尾键入 Entergedit 将为您提供所需的换行符。

(作为一种特殊情况,如果您根本不输入任何内容,gedit 将创建一个空文件。)

请注意这一重要区别:在gedit 中,键入Enter 会创建一个新行。在存储在磁盘上的文本文件中,换行符(LF,'\n')表示当前行的结束。


文本文件表示因系统而异。行尾标记最常见的表示是单个 ASCII LF(换行符)字符(Unix、Linux 和类似系统),以及两个字符的序列,CR 和 LF(MS Windows)。我将在这里假设类 Unix 的表示。 (更新:在评论中,您说您使用的是 Ubuntu 12.04 和 gcc 4.6.3,因此文本文件绝对应该采用 Unix 样式的格式。)

我刚刚根据您问题中的代码编写了以下程序:

#include <iostream>
#include <string>
int main() {
    std::string input;
    int line_number = 0;
    while(std::getline(std::cin, input))
    {   
        line_number ++;
        std::cout << "line " << line_number
                  << ", input = \"" << input << "\"\n";
    }
}

我创建了一个 3 行文本文件 in.txt:

ABCD
EFGH
IJHL

在文件in.txt 中,每一行都由一个换行符终止。

这是我得到的输出:

$ cat in.txt
ABCD
EFGH
IJHL
$ g++ c.cpp -o c
$ ./c < in.txt
line 1, input = "ABCD"
line 2, input = "EFGH"
line 3, input = "IJHL"
$

文件末尾的最后一个换行符不会开始换行符,它只是标记当前行的结尾。 (不以换行符结尾的文本文件甚至可能无效,具体取决于系统。)

如果我在in.txt 末尾添加一个换行符,我可以得到你描述的行为:

$ echo '' >> in.txt
$ cat in.txt
ABCD
EFGH
IJHL

$ ./c < in.txt
line 1, input = "ABCD"
line 2, input = "EFGH"
line 3, input = "IJHL"
line 4, input = ""
$

程序在输入文件的末尾看到一个空行因为在输入文件的末尾有一个空行

如果您检查in.txt 的内容,您会发现末尾有 两个 换行符 (LF),一个用于标记第三行的结尾,一个用于标记(空)第四行的结尾。 (或者如果它是一个 Windows 格式的文本文件,你会在文件的最后找到一个 CR-LF-CR-LF 序列。)

如果您的代码不能正确处理空行,那么您应该确保它的输入没有收到任何空行,或者更好地对其进行修改,以便正确处理空行。 如何处理空行?这取决于程序需要做什么,这可能完全取决于您。您可以静默跳过空行:

if (input != "") {
    // process line
}

或者您可以将空行视为错误:

if (input == "") {
    // error handling code
}

或者您可以将空行视为有效数据。

在任何情况下,您都应该确切地决定要如何处理空行。

【讨论】:

  • 这正是我的想法:一个空行应该由 2 个连续的 '\n' 字符组成。这也是你得到的行为。但在我的机器上,一个空行只需要 1 个 '\n' 字符。因此,混乱。谢谢你的回答
  • @zalenix:您使用的是什么系统(操作系统、编译器)? 必须有一种方法来表示最后一行不为空的文本文件。
  • Ubuntu 12.04 和 gcc 4.6.3
  • @zalenix:那么您误解了您的输入文件实际包含的内容。我自己正在使用一个非常相似的系统。如果您在输入文件的末尾看到一个空行,则必须有一个双换行符。试试od -c in.txt;你应该看到类似... \n I J H L \n \n
  • 太棒了!怎么可能呢?我只按了三次 Enter 键
【解决方案2】:

为什么第四个循环会运行?

因为文本输入包含四行。

换行符的意思就是——“开始一个新行”。这并不意味着“前面的行是完整的”,在这个测试中,这两种语义之间的区别被揭示了。所以我们有

1. ABCD
2. DEFG
3. HIJK
4.

第三行末尾的换行符开始一个新行 - 就像它应该做的那样,正如它的名字所说的那样。该行为空的事实是您返回空字符串的原因。如果您想避免它,请在第三行末尾修剪换行符,或者,只需特殊情况 if (input == "") break;

问题与您的代码无关,而在于您对换行符行为的错误预期。

【讨论】:

  • 换行符的意思就是——“开始新行”。这并不意味着“前面的行已经完成” -- 嗯?换行符表示前一行已完成。 OP 的输入文件末尾可能有一个双换行符,即它以空行结尾。见my answer
  • 我想 OP 可能正在使用具有奇怪文本文件表示的系统,但我从未见过这样的系统。我已经问过 OP 他使用的是什么系统。
  • 不,OP 使用的是 Ubuntu 和 gcc。除非我遗漏了一些明显的东西(这是可能的,但我认为不太可能),似乎 误解了换行符的语义(这令人惊讶)。
  • 如果您的答案是关于 C 的,那将是完全错误的:输入 API 返回的换行符 '\n' 确实表示一行结束。 Unix 下的文件格式也是如此:根据定义,文本文件是一系列行,每行都以换行符结尾。 C++ 做事有什么不同吗?
  • @Gilles: "C++ 做事有什么不同吗?" -- 没有。
【解决方案3】:

结局:

编辑:请阅读已接受的答案以获得对问题和解决方案的正确解释。


对于在 while 循环条件中使用 std::getline() 的人的说明,请记住检查循环内是否为空字符串并相应地中断,如下所示:

string input;
while(std::getline(std::cin, input))
{
    if(input = "")
        break;
    //some read only processing with input 
}

我的建议:在 while 循环条件中根本不要有 std::getline() 。而是像这样使用 std::cin :

while(std::cin>>a>>b)
{
    //loop body
}

这种方式不需要额外检查空字符串,代码设计更好。

上面提到的后一种方法否定了对空字符串的显式检查(但是,最好对输入的格式进行尽可能多的显式检查)。

【讨论】:

  • 这将使输入为空,无论以前是什么。
  • operator&gt;&gt; 不是逐行读取,而是逐字读取(直到第一个空格)。虽然它可能足以满足您的的情况,但它肯定不能替代std::getline
猜你喜欢
  • 2012-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多