2009-10-14 14 views
28

Quiero iterar sobre std::cin, línea por línea, direccionando cada línea como std::string. ¿Qué es mejor:¿Cómo puedo iterar sobre cin línea por línea en C++?

string line; 
while (getline(cin, line)) 
{ 
    // process line 
} 

o

for (string line; getline(cin, line);) 
{ 
    // process line 
} 

? ¿Cuál es la forma normal de hacer esto?

Respuesta

1

El primero.

Ambos hacen lo mismo, pero el primero es mucho más fácil de leer, además de que tiene que mantener la variable de cadena después de que se realiza el bucle (en la segunda opción, su cerrado en el bucle for alcance)

+7

¿No es bueno mantener la línea en el alcance? Fuera del alcance no sirve de mucho, ya que terminará reteniendo el valor de la última línea o algo así. – cppLearner

+0

@cppLearner: Buen punto, pero tal vez deberías poner esto en una función propia, por lo que la cadena utilizada temporalmente queda fuera de alcance de todos modos. – UncleBens

+0

Siempre puede usar llaves vacías, en el caso de que haya una buena razón para limitar el alcance de la cadena, pero también una buena razón para que algún otro bit de código antes y/o después del bucle esté en la misma función. No creo que limitar el alcance deba determinar si usar "durante" o "mientras", lo que debería determinar si está esperando que algo sea falso (while), o atravesar algo que es conceptualmente un rango (para). Obviamente, la diferencia entre los dos es un límite difuso. Son lógicamente equivalentes, se trata de cómo concibes el ciclo. –

0

Go con la declaración while.

Consulte el Capítulo 16.2 (específicamente las páginas 374 y 375) de Code Complete 2 de Steve McConell.

Para citar:

No utilice un bucle for cuando un bucle while es más apropiado. Un abuso común de la estructura flexible de bucles en C++, C# y Java es desordenar aleatoriamente el contenido de un bucle while en un encabezado de bucle for.

.

C Ejemplo ++ de un bucle while Crammed abusivamente en una de Cabecera Loop

for (inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile(); recordCount++) { 
    inputFile.GetRecord(); 
} 

C Ejemplo ++ de uso apropiado de un bucle while

inputFile.MoveToStart(); 
recordCount = 0; 
while (!InputFile.EndOfFile()) { 
    inputFile.getRecord(); 
    recordCount++; 
} 

He omitido algunas partes en el medio pero espero que eso te dé una buena idea.

+6

Como de costumbre, Steve tiene una buena idea, pero mala ejecución. En primer lugar, este uso del bucle for no es particularmente abusivo. En segundo lugar, y lo que es más importante, ambas versiones (utilizando el para o el tiempo) muestran un antipatrón, usando EndOfFile() como la condición de salida del bucle, que es bastante probable que dé resultados incorrectos. –

+0

Acepte el principio de abuso for (;;). Pero no estoy de acuerdo con que el ejemplo sea abuso. Y como Jerry poniendo la prueba EndOfFile(), hay un no-no. Grita Ant-Pattern. Aunque probablemente movería recordCount en el cuerpo y movería GetRecord() al (;;) –

+0

@Martin, él proporciona otro ejemplo del 'for' que considera ligeramente mejor que el que describe –

8

Lo que he (escrito como un ejercicio, pero tal vez resulte útil un día), es LineInputIterator:

#ifndef UB_LINEINPUT_ITERATOR_H 
#define UB_LINEINPUT_ITERATOR_H 

#include <iterator> 
#include <istream> 
#include <string> 
#include <cassert> 

namespace ub { 

template <class StringT = std::string> 
class LineInputIterator : 
    public std::iterator<std::input_iterator_tag, StringT, std::ptrdiff_t, const StringT*, const StringT&> 
{ 
public: 
    typedef typename StringT::value_type char_type; 
    typedef typename StringT::traits_type traits_type; 
    typedef std::basic_istream<char_type, traits_type> istream_type; 

    LineInputIterator(): is(0) {} 
    LineInputIterator(istream_type& is): is(&is) {} 
    const StringT& operator*() const { return value; } 
    const StringT* operator->() const { return &value; } 
    LineInputIterator<StringT>& operator++() 
    { 
     assert(is != NULL); 
     if (is && !getline(*is, value)) { 
      is = NULL; 
     } 
     return *this; 
    } 
    LineInputIterator<StringT> operator++(int) 
    { 
     LineInputIterator<StringT> prev(*this); 
     ++*this; 
     return prev; 
    } 
    bool operator!=(const LineInputIterator<StringT>& other) const 
    { 
     return is != other.is; 
    } 
    bool operator==(const LineInputIterator<StringT>& other) const 
    { 
     return !(*this != other); 
    } 
private: 
    istream_type* is; 
    StringT value; 
}; 

} // end ub 
#endif 

Así que su bucle podría sustituirse con un algoritmo (otra de las prácticas recomendadas en C++):

for_each(LineInputIterator<>(cin), LineInputIterator<>(), do_stuff); 

Tal vez una tarea común es almacenar cada línea en un recipiente:

vector<string> lines((LineInputIterator<>(stream)), LineInputIterator<>()); 
66

Desde que UncleBen trajo su LineInputIterator, pensé que agregaría un par de métodos alternativos. En primer lugar, una clase muy simple que actúa como un proxy cadena:

class line { 
    std::string data; 
public: 
    friend std::istream &operator>>(std::istream &is, line &l) { 
     std::getline(is, l.data); 
     return is; 
    } 
    operator std::string() const { return data; }  
}; 

Con esto, todavía había leído utilizando un istream_iterator normal.Por ejemplo, para leer todas las líneas de un archivo en un vector de cadenas, podría utilizar algo como:

std::vector<std::string> lines; 

std::copy(std::istream_iterator<line>(std::cin), 
      std::istream_iterator<line>(), 
      std::back_inserter(lines)); 

El punto crucial es que cuando estás leyendo algo, se especifica una línea - pero de lo contrario, solo tienes cadenas.

Otra posibilidad utiliza una parte de la biblioteca estándar que la mayoría de la gente apenas sabe que existe, sin mencionar que tiene un uso muy real. Cuando lee una cadena con el operador >>, la transmisión devuelve una cadena de caracteres hasta lo que la configuración regional de la secuencia dice que es un carácter de espacio en blanco. Especialmente si usted está haciendo un montón de trabajo que es todo alineación orientada, puede ser conveniente crear un escenario con una faceta ctype que sólo clasifica nueva línea como un espacio en blanco:

struct line_reader: std::ctype<char> { 
    line_reader(): std::ctype<char>(get_table()) {} 
    static std::ctype_base::mask const* get_table() { 
     static std::vector<std::ctype_base::mask> 
      rc(table_size, std::ctype_base::mask()); 

     rc['\n'] = std::ctype_base::space; 
     return &rc[0]; 
    } 
}; 

Para usar esto, usted imbuye la secuencia desde la que va a leer con una configuración regional utilizando esa faceta, luego solo lee cadenas normalmente, y el operador >> para una cadena siempre lee una línea completa. Por ejemplo, si quisiéramos leer en las líneas, y escribir las líneas únicas en forma ordenada, podríamos usar un código como éste:

int main() { 
    std::set<std::string> lines; 

    // Tell the stream to use our facet, so only '\n' is treated as a space. 
    std::cin.imbue(std::locale(std::locale(), new line_reader())); 

    std::copy(std::istream_iterator<std::string>(std::cin), 
     std::istream_iterator<std::string>(), 
     std::inserter(lines, lines.end())); 

    std::copy(lines.begin(), lines.end(), 
     std::ostream_iterator<std::string>(std::cout, "\n")); 
    return 0; 
} 

Tenga en cuenta que esto afecta a toda la entrada de la corriente. El uso de esto prácticamente descarta mezclar la entrada orientada a la línea con otra entrada (por ejemplo, leer un número de la transmisión usando stream>>my_integer normalmente fallaría).

+2

+1 para el apoderado. Una verdadera revelación :) – UncleBens

+0

Gracias - Me gusta también. :-) –

+3

Sé que había una manera de especificar en qué personaje parar ... bueno, todavía espero que haya una manera más simple ... Me pregunto cómo el lenguaje se volvió tan grosero, a veces me entristece. +1 para el proxy. Fácil de escribir, fácil de usar y mucho más flexible. –

Cuestiones relacionadas