2016-09-26 2 views
3

Je veux vérifier si un flux (en pratique un ifstream) se termine par une nouvelle ligne. Je suis venu avec ceci:Vérifier si un flux se termine par une nouvelle ligne

bool StreamEndsWithNewline(std::basic_istream<char> & the_stream) 
{ 
    if (the_stream.peek() == EOF) { 
     the_stream.clear(); //clear flags set by peek() 
     return false; 
    } 
    std::string line = "blah"; 
    while (std::getline(the_stream, line)) { 
     // ... 
    } 
    return line.empty(); 
} 

L'idée étant que si la dernière ligne du flux a un caractère de fin \n, la boucle while fera une itération supplémentaire (parce que eof n'a pas été atteint) où le chaîne vide sera affectée à l'argument de ligne.

Le cas particulier d'un flux "vide" doit être traité séparément.

Il semble fonctionner sur Windows (vs2010). Puis-je le faire de cette façon en général?

+0

Les flux de fichiers doivent se terminer par un saut de ligne. Ce test détectera un flux qui a ** seulement ** un seul saut de ligne ou qui se termine par ** deux ou plus de ** nouvelles lignes. –

+0

Un flux de texte qui ne se termine pas par un saut de ligne n'est pas un flux d'entrée valide en C ou C++. Les opérations d'entrée sur ce flux ne sont pas nécessaires pour le gérer de manière rationnelle. –

+0

Donc avec std :: stringstream the_stream (""); the_stream << "bonjour!"; est l'entrée illégale the_stream? Je crois que c'est ce que je reçois si je lis dans un fichier (en utilisant ifstream) qui ne se termine pas par un saut de ligne. – Jens

Répondre

1

tldr; Oui, ceci est garanti pour fonctionner, à moins que le flux ne soit initialement vide.


Il y a deux bits à considérer: le bit fail et le bit eof. std::getline le fait, à partir de [string.io]:

Après avoir construit un objet sentry, si la sentinelle se transforme en vrai, appelle str.erase() et extraits puis de caractères est et les str joint à comme si en appelant str.append(1, c) [. ..] Si la fonction extrait pas de caractères, il appelle is.setstate(ios::failbit)

Et sentry le fait, de [istream :: sentry]:

Effets: Si is.good() est false, appelle is.setstate(failbit). Sinon, prépare une entrée formatée ou non formatée. [...] Si is.rdbuf()->sbumpc() ou is.rdbuf()->sgetc() retours traits::eof(), la fonction appelle setstate(failbit | eofbit)

Donc, étant donné tout cela, nous allons marcher à travers deux exemples:


Cas 1: "hello\n". Le premier appel à getline(), the_stream.good() est vrai, nous extrayons les caractères à travers le \n, le flux est toujours good(), et nous entrons dans le corps de la boucle avec line mis à "hello".

Le deuxième appel à getline(), le flux est encore good(), de sorte que l'objet sentry convertit en vrai, et nous appelons str.erase(). La tentative d'extraction des caractères suivants échoue, puisque nous en avons terminé avec le flux, ainsi le failbit est défini. Cela provoque le retour getline() de convertir en faux afin que nous n'entrent pas dans le corps de la boucle une deuxième fois. À la fin de la boucle, line est vide.


Cas 2: "goodbye", sans saut de ligne. Le premier appel à getline(), the_stream.good() est vrai, nous extrayons les caractères jusqu'à ce que nous atteignions eof().Le flux failbit n'est pas encore défini, donc nous entrons toujours dans le corps de la boucle, avec la ligne définie sur "goodbye".

Le deuxième appel à getline(), la construction de l'objet sentry échoue parce que is.good() est faux (is.good() contrôles à la fois le eofbit et le failbit). En raison de cet échec, nous ne passons pas à la première étape de getline() qui appelle str.erase(). Et à cause de cet échec, le failbit est réglé de sorte que nous n'entrons pas à nouveau dans le corps de la boucle. À la fin de la boucle, line est toujours "goodbye".


Cas 3: "". Ici, getline() n'extrayera aucun caractère, ainsi le failbit est défini et la boucle n'est jamais entrée, et line est toujours vide. Il existe plusieurs façons de distinguer ce cas de cas 1:

  • Vous pouvez, à l'avant, peek() pour voir si le premier caractère est traits::eof() avant de faire quoi que ce soit d'autre.
  • Vous pouvez compter combien de fois vous entrez dans la boucle et vérifiez qu'elle est différente de zéro.
  • Vous pouvez initialiser line à une valeur non vide de sentinelle. A la fin de la boucle, la ligne ne sera vide que si le flux se termine par le délimiteur.
+0

Re l'exemple 'hello \ n', oui, je suis d'accord je vais obtenir une seule chaîne, mais la boucle while fera une itération supplémentaire parce que eof n'est pas atteint. Et dans cette itération, j'obtiens une chaîne vide affectée au paramètre string. Donc, pour moi, il semble que je n'ai pas besoin de la vérification explicite. – Jens

+1

@Jens Non, vous ne le faites pas. L'appel suivant 'getline()' se terminera par un flux ayant échoué, vous n'entrerez donc pas dans la boucle. – Barry

+1

Je suis d'accord pour ne pas entrer dans la boucle, mais 'line' aura déjà une nouvelle valeur avant d'entrer dans la boucle, non? – Jens

1

Votre code fonctionne.

Cependant, vous pouvez essayer la recherche du flux et de tester le dernier caractère uniquement ou jeter les caractères lus:

#include <cassert> 
#include <iostream> 
#include <limits> 
#include <sstream> 

bool StreamEndsWithNewline(std::basic_istream<char>& stream) { 
    const auto Unlimited = std::numeric_limits<std::streamsize>::max(); 
    bool result = false; 
    if(stream) { 
     if(std::basic_ios<char>::traits_type::eof() != stream.peek()) { 
      if(stream.seekg(-1, std::ios::end)) { 
       char c; 
       result = (stream.get(c) && c == '\n'); 
       stream.ignore(Unlimited); 
      } 
      else { 
       stream.clear(); 
       while(stream && stream.ignore(Unlimited, '\n')) {} 
       result = (stream.gcount() == 0); 
      } 
     } 
     stream.clear(); 
    } 
    return result; 
} 

int main() { 
    std::cout << "empty\n"; 
    std::istringstream empty; 
    assert(StreamEndsWithNewline(empty) == false); 

    std::cout << "empty_line\n"; 
    std::istringstream empty_line("\n"); 
    assert(StreamEndsWithNewline(empty_line) == true); 

    std::cout << "line\n"; 
    std::istringstream line("Line\n"); 
    assert(StreamEndsWithNewline(line) == true); 

    std::cout << "unterminated_line\n"; 
    std::istringstream unterminated_line("Line"); 
    assert(StreamEndsWithNewline(unterminated_line) == false); 

    std::cout << "Please enter ctrl-D: (ctrl-Z on Windows)"; 
    std::cout.flush(); 
    assert(StreamEndsWithNewline(std::cin) == false); 
    std::cout << '\n'; 

    std::cout << "Please enter Return and ctrl-D (ctrl-Z on Windows): "; 
    std::cout.flush(); 
    assert(StreamEndsWithNewline(std::cin) == true); 
    std::cout << '\n'; 

    return 0; 
}