Sentencias de salto

Como vimos al hablar sobre las etiquetas, los programas C++ se ejecutan secuencialmente, pero existen formas de romper este orden secuencial, mediante el uso de sentencias de salto, que veremos a continuación.

Sentencia de ruptura

El uso de esta sentencia dentro de un bucle, una sentencia de selección o de un bloque, transfiere la ejecución del programa a la primera sentencia que haya a continuación. Esto es válido para sentencias switch, como vimos hace un rato, pero también lo es para sentencias while, do...while, for e if.

En general, una sentencia break transfiere la ejecución secuencial a la siguiente sentencia, abandonando aquella en que se ejecuta.

Sintaxis:

break

Ejemplo:

int c = 0;
{
   for(int x=0; x < 100; x++) c+=x;
   break;
   c += 100;
}
c /= 100;

En este ejemplo, la sentecia c += 100; no se ejecutará, ya que la sentencia break transfiere la ejecución secuencial fuera del bloque.

Otro ejemplo:

y = 0;
x = 0;
while(x < 1000)
{
   if(y == 1000) break;
   y++;
}
x = 1;

En este otro ejemplo el bucle no terminaría nunca si no fuera por la línea del break, ya que x no cambia. Después del break el programa continuaría en la línea x = 1.

Sentencia continue

El uso de esta sentencia dentro de un bucle ignora el resto del código de la iteración actual, y comienza con la siguiente, es decir, se transfiere la ejecución a la evaluación de la condición del bucle. Sintaxis:

continue

Ejemplo:

y = 0;
x = 0;
while(x < 1000)
{
   x++;
   if(y >= 100) continue;
   y++;
}

En este ejemplo la línea y++ sólo se ejecutaría mientras y sea menor que 100, en cualquier otro caso el control pasa a la siguiente iteración, con lo que la condición del bucle volvería a evaluarse.

Sentencia de salto

Con el uso de esta sentencia el control se transfiere directamente al punto etiquetado con el identificador especificado.

Nota: El goto es un mecanismo que está en guerra permanente, y sin cuartel, con la programación estructurada. Las sentencias goto no se deben usar cuando se resuelven problemas mediante programación estructurada, se incluye aquí porque existe, pero siempre puede y debe ser eludido. Existen mecanismos suficientes para hacer de otro modo todo aquello que pueda realizarse con mediante goto.
En cualquier caso, nosotros somos los programadores, y podemos decidir que para cierto programa o fragmento de programa, las ventajas de abandonar la programación estructurada pueden compensar a los inconvenientes. A veces es imperativo sacrificar claridad en favor de velocidad de ejecución. Pero de todos modos, serán situaciones excepcionales.

Sintaxis:

goto <identificador>

Ejemplo:

x = 0;
Bucle:
x++;
if(x < 1000) goto Bucle;

Este ejemplo emula el funcionamiento de un bucle for como el siguiente:

for(x = 0; x < 1000; x++);

Sentencia de retorno

Esta es la sentencia de salida de una función, cuando se ejecuta, se devuelve el control a la rutina que llamó a la función.

Además, se usa para especificar el valor de retorno de la función. Sintaxis:

return [<expresión>]

Ejemplo:

int Paridad(int x)
{
   if(x % 2) return 1; 
   return 0;
}

Este ejemplo ilustra la implementación de una función que calcula la paridad de un valor pasado como parámetro. Si el resto de dividir el parámetro entre 2 es distinto de cero, implica que el parámetro es impar, y la función retorna con valor 1. El resto de la función no se ejecuta. Si por el contrario el resto de dividir el parámetro entre 2 es cero, el parámetro será un número par y la función retornará con valor cero.

Es importante dejar siempre una sentencia return sin condiciones en todas las funciones. Esto es algo que a veces no se tiene en cuenta, y aunque puede no ser extrictamente necesario, siempre es conveniente. El ejemplo anterior se podría haber escrito de otro modo, sin tener en cuenta esto:

int Paridad(int x)
{
   if(x % 2) return 1; 
   else return 0;
}

En este caso, para nosotros está claro que siempre se ejecutará una de las dos sentencias return, ya que cada una está en una de las alternativas de una sentencia if...else. Sin embargo, el compilador puede considerar que todas las sentencias return están en sentencias de selección, sin molestarse en analizar si están previstas todas las salidas posibles de la función, con lo que puede mostrar un mensaje de error.

El primer ejemplo es mejor, ya que existe una salida incondicional. Esto no sólo evitará errores del compilador, sino que nos ayudará a nosotros mismos, ya que vemos que existe un comportamiento incondicional.

El único caso en que una sentencia return no requiere una expresión es cuando el valor de retorno de la función es void.

Existe un mal uso de las sentencias return, en lo que respecta a la programación estructurada. Por ejemplo, cuando se usa una sentencia return para abandonar una función si se detecta un caso especial:

int Dividir(int numerador, int denominador)
{
   int cociente;
   
   if(0 == denominador) return 1;
   cociente = numerador/denominador;
   return cociente;
}

Esta función calcula el cociente de una división entera. Pero hemos querido detectar un posible problema, ya que no es posible dividir por cero, hemos detectado ese caso particular, y decidido (erróneamente) que cualquier número dividido por cero es uno.

Sin embargo, en este caso, el primer return se comporta como una ruptura de secuencia, ya que se abandona la función sin procesarla secuencialmente. Siendo muy puristas (o pedantes), podemos considerar que esta estructura no corresponde con las normas de la programación estructurada. Un ejemplo más conforme con las normas sería:

int Dividir(int numerador, int denominador)
{
   int cociente;
   
   if(0 == denominador) cociente = 1;
   else cociente = numerador/denominador;
   return cociente;
}

Sin embargo, a menudo usaremos estos atajos para abandorar funciones en caso de error, sacrificando el método en favor de la claridad en el código.

Uso de las sentencias de salto y la programación estructurada

Lo dicho para la sentencia goto es válido en general para todas las sentencias de salto. En el caso de la sentencia break podemos ser un poco más tolerantes, sobre todo cuando se usa en sentencias switch, donde resulta imprescindible. En general, es una buena norma huir de las sentencias de salto.

Comentarios

Los comentarios no son sentencias, pero me parece que es el lugar adecuado para hablar de ellos. En C++ pueden introducirse comentarios en cualquier parte del programa. Su función es ayudar a seguir el funcionamiento del programa durante la depuración o en la actualización del programa, además de documentarlo. Los comentarios en C, que también se pueden usar en C++, se delimitan entre /* y */, cualquier cosa que escribamos en su interior será ignorada por el compilador.

Sólo está prohibido su uso en el interior de palabras reservadas, de identificadores o de cadenas literales. Por ejemplo:

int main(/*Sin argumentos*/void)

está permitido, pero sin embargo:

int ma/*función*/in(void)

es ilegal.

Esto no es una limitación seria, a fin de cuentas, se trata de aclarar y documentar, no de entorpecer la lectura del código.

En C++ existe otro tipo de comentarios, que empiezan con //. Estos comentarios no tienen marca de final, sino que terminan cuando termina la línea. Por ejemplo:

int main(void) // Esto es un comentario 
{
   return 0;
}

El cuerpo de la función no forma parte del comentario.

Palabras reservadas usadas en este capítulo

break, case, continue, default, do, else, for, goto, if, return, switch y while.

Comentarios de los usuarios (13)

JuDelCo
2011-01-04 19:15:19

Continue se puede decir entonces que es lo mismo que break no?

JuDelCo
2011-01-04 19:29:51

Vale, no hagáis caso al comentario. Con los ejercicios me he dado cuenta ya que break sirve para terminar el bucle y continuar y continue para terminar esa REPETICIÓN del bucle. O al menos eso es lo que he entendido xD

Steven R. Davidson
2011-01-12 00:09:50

Hola JuDelCo,

Efectivamente, \'break\' sirve para terminar el bucle de inmediato para continuar la ejecución del programa a las siguientes sentencias fuera del bucle.

En cuanto a \'continue\', terminamos \"esta\" iteración de inmediato y volvemos la siguiente iteración. Es decir, nos detenemos para recomenzar el bucle.

Esto nos viene bien cuando estamos verificando ciertos datos recogidos. Si éstos no son correctos, entonces ignoramos esta iteración y la rehacemos, para conseguir nuevos datos. Cuando los datos sean correctos, seguimos con el resto de la iteración.

Sin embargo, como buena práctica, no usamos \'continue\', porque dificulta la legibilidad y comprensión del código fuente. En su lugar, usaríamos condiciones y posiblemente variables booleanas para indicar el estado de control: seguir iterando o ignorar el resto de la iteración para \"reiniciar\" el bucle, por así decirlo.

Steven

Víctor Giménez Ábalos
2012-01-23 20:02:00

Buenas, me llamo Víctor y tengo dudas sobre el goto.

Ya sé que debe ser evitado y eso, pero estoy intentando crear un videojuego de rol con la ventanita de iostream, la de toda la vida. Voy por el capitulo de los punteros, así que no se si se comentará luego.

El caso es que quiero hacer que el personaje se pueda mover por cuadrantes tipo 1, 1 a 2, 1 siguiendo las coordenadas cartesianas, pero mi duda es esta: ¿se puede hacer un goto dentro de un switch que te dirija a un identificador de fuera del switch? Aquí pongo el ejemplo, por si no me he explicado bien:

http://codepad.org/gYJljOng

PD: el código no esta completo, le faltan cosas en el switch, los demás cuadrantes y eso. Si no se puede, ¿podrías explicarme una alternativa? No se me ocurre nada.

Gracias por anticipado

Steven R. Davidson
2012-01-24 00:38:54

Hola Víctor,

Bueno, en cuanto a tu duda, la mejor forma de averiguar esto es haciéndolo :) Haz la prueba y mira si el compilador te da algún error y luego comprueba que se realiza la lógica que quieres.

En cuanto al uso de 'goto', aquí en tu código fuente, desde luego que no lo necesitas. Además, no uses variables globales.

En primer lugar, define cada área de una forma que comparta la misma estructura - la misma definición. Por ejemplo,

const int MAX_TITULO_AREA = 64;
const int MAX_DESC_AREA = 128;
enum direccion_t { NORTE, SUR, ESTE, OESTE };

struct area
{
  char szTitulo[MAX_TITULO_AREA];
  char szDescripcion[MAX_DESC_AREA];
  area *aSalida[4];
};

Como puedes ver, modelamos todos los conceptos que sean importantes: los valores de la dirección que el jugador puede elegir, el área, y los vínculos a otras áreas desde ESTE área. He puesto hasta 4 posibles salidas simultáneas, pero si hay más, podrías crear un array dinámico de 'area *'. De esta forma, es más fácil acceder al siguiente área. Si no te gusta o crees que es demasiado difícil de implementar, entonces podrías guardar los índices que representan las coordenadas de los cuadrantes. Podríamos representar todos los cuadrantes como un array de 'area'. Por ejemplo,

const int MAX_FILAS = 25;
const int MAX_COLS = 80;
...
area mapa[MAX_FILAS][MAX_COLS];

El problema es que quizá no usemos todos los elementos como áreas. Si se da este caso, entonces tendremos que modelar el caso de que un 'area' sea inválida. Podríamos crear un nuevo campo para 'area', para representar una estructura inválida; por ejemplo,

const bool AREA_VALIDA = true;
const bool AREA_INVALIDA = !AREA_VALIDA;

const int MAX_TITULO_AREA = 64;
const int MAX_DESC_AREA = 128;
enum direccion_t { NORTE, SUR, ESTE, OESTE };

struct area
{
  char szTitulo[MAX_TITULO_AREA];
  char szDescripcion[MAX_DESC_AREA];
  area *aSalida[4];
  bool bValida;
};

Obviamente, el resto del código debe seguir el diseño que acabamos de establecer: comprobando si cada 'area' es válidad o no, antes de tomar una decisión.

De todas maneras, sugiero usar un array de 'area *', para representar el "mapa", ya que te otorga mayor versatilidad a la hora de crear mapas y vincular cada área con otra. Esto te permite vincular un área a sí misma, por ejemplo, o incluso crear una lógica difícil de seguir, como puede ser el hecho de estar atrapado en un laberinto. Los MUD's solían hacer estas cosas: tenías que averiguar la secuencia exacta de direcciones para salir del laberinto.

En fin, espero haberte aclarado algunas cosas del tema.

Hasta luego,

Steven

Voodoo
2012-04-24 19:54:01

Hola,

Una pregunta:

Por que en el ejemplo el break se aplica al while y no al if?

y = 0;

x = 0;

while(x < 1000)

{

if(y == 1000) break;

y++;

}

x = 1;

Muchas gracias....este curso el espectacular!!!!!!!

Gustavo F. Paredes
2013-02-28 21:45:04

Dentro del While, no se incrementa la variable x, por lo tanto es un bucle infinito.

Pero el if y su "tope" de 1000 sobre una variable (en este caso y) hace que al llegar a 1000 (la variable y) se ejecute el break y en un bucle infinito (aparentemente) haya una salida (el break) gracias al if y el incremento de y.

Saludos.

Gustavo

Gustavo F. Paredes
2013-02-28 21:49:45

En mis comienzos en la programación (BASIC y COBOL) fui (por enseñanza de mis profesores de ese momento) un asiduo usuario del GOTO.

Pero al llegar a la Universidad me choqué con una pared. El GOTO estaba prohibido.

Finalmente me acostumbré (menos mal) y encontrarlo aquí me resulta raro. Yo lo evitaría casi a cualquier costo.

Pero en definitiva (y esto es algo que muchos no le hacen caso) en el lenguaje binario (que es el que nuestro micro comprende) el goto es cosa extremadamente común.

O sea, se prohibe algo que al micro si se le tiene permitido (sic).

Igual está todo bien.

Steven R. Davidson
2013-02-28 22:29:28

Hola Gustavo,

La instrucción GOTO se le prohíbe a los programadores (humanos) porque somos unos torpes, hablando claramente. Como nos equivocamos, es mejor intentar reducir la posibilidad de cometer errores. Por esta razón, los programadores no usan GOTO, favoreciendo otras estructuras y soluciones que previamente se hacían con GOTO: bucles, selecciones, y llamadas a funciones o subrutinas. Como la máquina no se equivoca, sí dejamos que use "saltos" o ramificaciones, porque es una forma muy rápida de controlar el seguimiento o flujo de un programa.

Como sabes, C/C++ son lenguajes más bien híbridos o intermedios, porque pueden servir tanto para programar a nivel bajo (máquina) o a nivel alto (humano). Por esta razón, existe 'goto'. Además, el uso de 'goto' viene muy bien, si la programación se automatiza; por ejemplo, si uno quiere traducir de ensamblador a C/C++, existe 'goto' para realizar esta tarea directamente.

Dicho todo esto, sí hay algunos casos válidos que requieren la "mano" de 'goto' para simplificar la implementación de algoritmos. En general, uno no se debería acostumbrar a usar 'goto' para la programación, pero sí tiene su nicho.

Hasta pronto,

Steven

Raul Rivera Z
2013-11-15 17:09:07

Hola,mi nombre es Raúl Rivera.

En el ejemplo del return, cuando el divisor es cero, ¿no sería mejor retornar con -1?, porque sí el dividendo y el divisor son iguales tambien retorna 1.

Gracias.

Steven R. Davidson
2013-11-17 04:26:52

Hola Raúl,

Podríamos, pero estaríamos casi en las mismas que con retornar 1: -x / x = -1; como por ejemplo, -7 / 7 = -1, al igual que 7 / (-7) = -1. El problema es que cualquier valor retornado es válido. En realidad, habría que retornar dos valores: el resultado y su validez. Veremos la forma de retornar varios valores en posteriores capítulos.

Hasta pronto,

Steven

Javier Lo
2016-01-26 17:53:20

He aprendido a programar de manera callejera, y estoy tratando de eliminar todas esas malas mañas que uno aprende para escribir codigo lo mas profesional que se me haga posible, aprendi con lenguaje ensamblador para Microcontroladores y recuerdo que mis amigos usaban mucho el goto lo que hacia que un codigo con un centenar de lineas se volviera incomprensible, en ese caso empece a valerme del stack del procesador (8 niveles, lo que lo hace muy limitado) y a usar instrucciones call y return o retlw XX (return X en C) de verdad que el codigo toma una forma modular muy facil de comprender, de verdad, les recomiendo a los que se inician, eviten como cosa de vida o muerte usar ese tipo de saltos, con el fin de que esas funciones que van creando, puedan ser reutilizadas mas adelante, les ahorrara muchisimo trabajo, y les permitira construir sus propias librerias. Gracias por el curso, esta muy bueno.

The_RiPPeR
2017-02-16 17:22:09

no entiendo lo del break que termina el while y no el if.