2010-05-29 12 views
6

Actualmente estoy trabajando mi camino a través de "Accelerated C++" y acabo de encontrar esto en el capítulo 3:invariantes de bucle (Específicamente ch.3 de "C++ acelerada")

// invariant: 
// we have read count grades so far, and 
// sum is the sum of the first count grades 
while (cin >> x) { 
    ++count; 
    sum += x; 
} 

Los autores siguen este explicando que el invariante necesita atención especial porque cuando la entrada se lee en x, leeremos count + 1 y por lo tanto la invariante será falsa. Del mismo modo, cuando hayamos incrementado el contador, sum ya no será la suma de las calificaciones del último recuento (en caso de que no lo haya adivinado, es el programa tradicional para calcular las calificaciones de los estudiantes).

Lo que no entiendo es por qué esto importa. Seguramente para casi cualquier otro ciclo, una afirmación similar sería cierta. Por ejemplo, esta es la primera while bucle del libro (la salida se rellena después):

// invariant: we have written r rows so far 
while (r != rows) { 
    // write a row of output 
    std::cout << std::endl; 
    ++r; 
} 

Una vez que hemos escrito la fila correspondiente de la producción, sin duda el invariante es falso hasta que hayamos incrementado r, al igual que en el otro ejemplo?

¿Qué hace que estas dos condiciones sean diferentes?

EDIT: Gracias por todas sus respuestas. Creo que lo tengo, pero voy a dejarlo un poco más de tiempo antes de elegir una "Respuesta aceptada" solo para estar seguro. Hasta ahora, todas las respuestas básicamente coinciden, por lo que apenas parece justo, pero vale la pena hacerlo, supongo.

El párrafo original, tal como se solicita a continuación:

"Descripción de la invariante para este circuito requiere un cuidado especial, porque la condición en el tiempo tiene efectos secundarios Estos efectos secundarios afectan a la verdad de la invariante:. Ejecutar con éxito cin >> x hace que la primera parte de la invariante-la parte que dice que hemos leído cuente grados-falso. Por consiguiente, debemos cambiar nuestro análisis para dar cuenta del efecto que la condición misma podría tener en la invariante.

Sabemos que la invariante era cierta antes de evaluar la condición, por lo que sabemos que ya hemos leído las calificaciones. Si cin >> x tiene éxito, entonces ahora hemos leído t + 1 grados. Podemos hacer que esta parte de lo invariante sea verdad nuevamente incrementando el recuento. Sin embargo, al hacerlo, falsifica la segunda parte del invariante, la parte que dice que la suma es la suma de los primeros grados del recuento, porque después de haber incrementado el recuento, la suma es ahora la suma del primer recuento: 1 grado, no el primero contar calificaciones. Afortunadamente, podemos hacer que la segunda parte de lo invariante sea verdadera ejecutando sum + = x; para que todo el invariante sea cierto en viajes posteriores a través del tiempo.

Si la condición es falsa, significa que nuestro intento de entrada falló, por lo que no obtuvimos más datos, por lo que el invariante sigue siendo verdadero. Como resultado, no tenemos que dar cuenta de los efectos secundarios de la condición después de que termine el tiempo. "

+0

Si no es demasiado largo, ¿podría usted copiar la * frase * exacta del autor? Tal vez malinterpretaste algo de sutilidad en la explicación. – nico

+0

Editado, gracias. – Owen

+2

+1 solo por usar este libro para aprender C++. ':)' – sbi

Respuesta

2

De su descripción parece que el autor está diciendo tonterías. Sí, el invariante se vuelve temporalmente falso entre las instrucciones, pero eso es va a suceder siempre que tenga operaciones no atómicas como esta. Siempre que no haya ningún punto de quiebre claro que pueda llevar a que el invariante sea incorrecto y el programa esté en un estado incoherente, estará bien.

En este caso, la única manera que podría pasar es si std :: cout arroja una excepción mientras que un invariante no es cierto, entonces atrapa esa excepción en alguna parte pero continúa la ejecución en mal estado. Me parece que el autor está siendo excesivamente pedante.Así que de nuevo, siempre y cuando no tenga ninguna declaración de interrupción/continuidad en el lugar equivocado o se emitan excepciones, está bien. Dudo que mucha gente se moleste en centrarse en su código de ejemplo porque es muy simple.

+0

¡Gracias! El programa tal como está en esta etapa del libro requiere que el usuario realmente escriba "fin de archivo" en el indicador de entrada. Solo puedo suponer que el resto vendrá después. Leyendo las respuestas, creo que tal vez los autores están tratando de inculcar buenas prácticas para el código posterior que puede ser más probable que arroje excepciones? Intento "hacer esto bien" desde el principio, así que pensé que era mejor ser pedante al respecto. – Owen

+0

Escogida como la mejor respuesta, otras personas han cubierto puntos individuales con más detalle, pero creo que resume bien los puntos. – Owen

+6

Decir que uno de los principales expertos en C++ es una tontería en el que podría decirse que el mejor libro para principiantes en C++ probablemente descalifique una respuesta de ser la mejor respuesta ... – KTC

2

Creo que el libro se refiere a la forma en que se detienen los bucles while. En el segundo caso, es muy fácil ver que el ciclo se detendrá una vez que "r" se haya incrementado lo suficiente para igualar "filas". Debido a que la mayoría de los recuentos en C++ se basan en cero, lo más probable es que genere una línea para cada fila.

Por otro lado, el primer ejemplo es usar la sobrecarga del operador para ">>" en el objeto cin. El ciclo while continuará mientras esta función no devuelva un cero. Esa sobrecarga del operador no devolverá este valor hasta que la entrada se haya cerrado.

¿Qué tecla puede presionar para que "cin >>" devuelva un 0? De lo contrario, el ciclo nunca terminará. Debe asegurarse de no crear un bucle así.

Se necesita agregar una línea para detener el ciclo fuera de la condición. Busque las declaraciones "break" y "continue".

+1

Puede presionar^Z (control + Z) para hacer que cin vuelva a cero, pero eso es obvio. – AndrejaKo

+0

Algunas personas han mencionado esto; Me olvidé de mencionar que el programa tal como está es muy simple y le pide al usuario que realmente ingrese "fin de archivo" en el aviso. – Owen

4

En general, se entiende que las invariantes solo se aplican entre las iteraciones del ciclo. (Por lo menos así es como yo los leo!)

El caso general es así:

[invariant true]; 
while (keep going) { 
    [state transformation]; 
    [invariant true]; 
} 

embargo, durante la transformación del estado, el invariante no necesariamente cierto.

Y como una nota de estilo independiente: si quieres ser un súper codificador, en lugar de dejar tus invariantes en comentarios, ¡hazles aserciones!

// Loop invariant: x+y = -4 
for (int x = 0; x < 10; x++) { 
    [do something]; 
    assert(x+y == -4); // Loop invariant here! 
} 

De esta forma tiene código de autocomprobación.

+0

¡Gracias! Es de suponer que las afirmaciones vendrán más adelante en el libro, pero me gusta mucho esa idea. – Owen

2

Esto es realmente interesante/importante en el contexto de la seguridad de excepciones.

cuenta la situación siguiente:

  • "recuento" es una clase definida por el usuario con una sobrecarga operator++
  • Eso sobrecargado operator++ se produce una excepción.
  • La excepción es capturado más tarde fuera del bucle (es decir, el bucle se ve como lo hace ahora, no hay try/catch)

En ese caso, el bucle invariante ya no se sostiene, y el estado de todo lo que sucede en el circuito está en cuestión. Fue la fila escrita? ¿Se actualizó el recuento? ¿La suma sigue siendo correcta?

Se necesita cierta protección adicional (en forma de variables temporales para almacenar valores intermedios y algunos intentos/capturas) para garantizar que todo permanezca constante incluso cuando se produce una excepción.

1

El libro parece complicar las cosas mucho más de lo que debería. Realmente no creo que explicar bucles con invariantes sea algo bueno. Es como explicar la suma con la física cuántica.

Los autores siguen esta explicando que el invariante requiere especial atención que se presta a ello porque cuando la entrada se lee en la variable x, habremos leído recuento + 1 grados y por lo tanto el invariante a ser falso. De manera similar, cuando hayamos incrementado el contador, la suma variable ya no será la suma de las calificaciones del último recuento (en caso de que no lo haya adivinado, es el programa tradicional para calcular las calificaciones de los estudiantes).

En primer lugar, la invariante no está clara. Si el invariante está "al final de una iteración del ciclo while, hemos leído count grados con la suma de sum", entonces eso me parece correcto. El invariante no está claramente especificado, por lo que no tiene sentido ni siquiera hablar de cuándo es y no se respeta.

Si el invariante es "en cualquier punto de una iteración del bucle while, hemos leído ...", entonces, estrictamente hablando, ese invariante no será verdadero. En lo que respecta a los bucles, creo que un invariante debe referirse al estado que existe al principio, al final o en un punto fijo del ciclo.

No tengo ese libro y no sé si las cosas se aclaran o no, pero parece que estar utilizando invariantes mal. Si su invariante no es verdad, ¿por qué siquiera molestarse en usar uno en primer lugar?

No creo que deba preocuparse demasiado por esto. Mientras entienda cómo funcionan estos bucles, estás bien. Si quieres para entenderlos a través de invariantes, puedes, pero debes prestar atención a la invariante que elijas. No elijas uno malo, o derrota el propósito. Debes elegir un invariante para el cual sea fácil escribir un código que lo respete, no elegir uno aleatorio y luego esforzarte por escribir un código que lo respete, y definitivamente no elegir uno vago y escribir un código que no tenga nada que ver con eso. y luego diga "uno debe prestar atención ya que esto no respeta realmente el vago invariante que elegí".

Lo que no entiendo es por qué esto importa. Seguramente para casi cualquier otro ciclo, una afirmación similar sería cierta.

Depende de la invariante utilizada (el libro es bastante vago por lo que ha dicho), pero sí, parece ser correcto en este caso.

Para que este código:

// invariant: we have written r rows so far 
int r = 0; // this is also important! 
while (r != rows) { 
    // write a row of output 
    std::cout << std::endl; 
    ++r; 
} 

El invariante "Al final de una iteración del bucle while, hemos escrito r filas" es definitivamente cierto.

No tengo el libro, así que no sé si estas cosas se abordarán más adelante. Sin embargo, por lo que dijiste, esta parece una manera realmente pésima de explicar bucles.

+0

El comentario en el código identifica el invariante como "hemos leído las calificaciones de recuento hasta el momento, y la suma es la suma de las calificaciones del primer recuento". Siempre. No solo "al final del ciclo while". Es cierto antes del ciclo while (ha leído 0 grados hasta el momento y 0 es la suma de los primeros 0 grados), es verdadero después del ciclo while, y es verdadero incluso en algunos puntos del ciclo while. –

+0

Un bucle invariante es un concepto bien definido en CS, y el autor lo aplica correctamente. Es cierto que hace que explicar este bucle en particular sea mucho más complicado, pero creo que el autor está realmente explicando * invariantes *, no este bucle en particular. En ese contexto, el fragmento extendido (ahora publicado por el OP) tiene sentido y es una buena explicación. –

1

Estoy de acuerdo en que debe saber qué es el invariante. Digamos que estoy escribiendo una buena Cuenta bancaria antigua. Puedo decir que siempre y en todo momento, el total de todas las transacciones en el historial de transacciones se agregará al saldo de la cuenta. Eso suena sensato. Sería bueno que fuera cierto. Pero durante el puñado de líneas cuando estoy procesando una transacción, no es cierto. O bien actualizo el saldo primero o agrego la transacción al historial y actualizo el saldo. Por un momento, el invariante no es verdad.

Me imagino un libro que quiere que entiendas que un invariante es algo que dices ser verdadero siempre y en todo momento, pero que a veces no lo es, y que es bueno saber cuándo no es verdad , porque si regresas, o lanzas una excepción, o cedes a la concurrencia, o lo que sea, cuando no es cierto, entonces tu sistema estará completamente desordenado. Es un poco más difícil imaginar que su paráfrasis es lo que los autores intentaron decir. Pero supongo que si giré la cabeza hacia un lado y entrecerré los ojos, puedo reformularlo porque "la suma es la suma de las calificaciones del primer recuento" es verdadero siempre y en todo momento, excepto cuando estamos ocupados leyéndolos y agregándolos, puede que no sea así. cierto durante ese proceso. ¿Tener sentido?

+0

Ok, entonces ahora que tengo su cita me da la sensación de que los autores quieren que piense que la segunda y la tercera líneas están escritas * para mantener el invariante verdadero *, que creo que es bastante extenso. Pero así es como funcionan sus cerebros, supongo. –

1

Después de su actualización, el autor describe correctamente cómo se debe "recuperar" un bucle invariante en cada iteración de un bucle.

Lo que no entiendo es por qué esto importa. Seguramente para casi cualquier otro ciclo, una afirmación similar sería cierta.

Sí, eso es cierto - no hay nada de especial en este bucle (OK, la condición de bucle tiene un efecto secundario pero esto se puede reescribir fácilmente).

Pero creo que el hecho importante que el autor quería señalar es este: después de una acción realizada dentro del ciclo, el bucle invariante ya no es cierto en general. Esto, por supuesto, es un problema para el invariante a menos que declaraciones subsiguientes corrija esto tomando las medidas adecuadas.

0

En el primer ejemplo, la variable de conteo no se usa para nada más que simplemente incrementarse para cada ciclo de entrada. El ciclo continuará hasta que >> devuelva NULL.

En el segundo ejemplo, las filas se deben inicializar con el número de filas que se escribirán. El ciclo continuará hasta que se escriba la cantidad de filas especificadas.

while (cin >> x) { 
    ++count; 
} 


rows = NROWS; 
r = 0; 

while (r != rows) { 
    // write a row of output 
    std::cout << std::endl; 
    ++r; 
} 
Cuestiones relacionadas