Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.10.4  Sentencias de salto

§1  Sinopsis

Las sentencias de salto permiten transferir el control del programa de forma incondicional. Existen cuatro de estas sentencias: break , continue , goto y return .

Nota: aunque en los textos no suele aparecer recogido en este epígrafe, existe una quinta posibilidad de realizar saltos de forma incondicional utilizando el mecanismo de excepciones throw/catch ( 1.6). Entiendo que está ahí para utilizarlo (con las debidas precauciones) cuando se considere necesario.

§2  break

La sentencia break se usa para salir de forma incondicional de los bucles do, for y while, así como de las sentencias switch de multi-dicisión. Hay que tener en cuenta que en el caso de bucles anidados, break hace salir del bucle interior (ver nota a continuación).

§2.1  Sintaxis

break;

Ejemplo:

switch (c) {
    case 0:
        cout << "Falso" << endl;
        break;
    default:
        cout << "Cierto" << endl;
}

Ver otro ejemplo ( 4.10.1)

Nota: al contrario que otros lenguajes, C++ no dispone de la opción break (n) para definir el número de niveles que se saltarán hacia fuera en el caso de bucles anidados.

§3 continue

La sentencia continue se utiliza en los bucles for, while y do...while. En los primeros el control salta al final del bucle, con lo que el contador se incrementa y comienza otra comprobación. En los while, el control pasa inmediatamente a la condición de control.

§3.1  Sintaxis:

continue;

§3.2  Descripción

continue suele utilizarse cuando la lógica del bucle es complicada, de forma que establecer una nueva condición y un nuevo nivel de indentación puede volver esta demasiado profunda.

§3.3  Ejemplo

void main () {
  for (i = 0; i < 20; i++) {
     if (array[i] == 0)
       continue;
     array[i] = 1/array[i];
  }
}

§4  goto

goto es una sentencia de salto incondicional dentro del ámbito de una función.

§4.1  Sintaxis

goto <etiqueta> ;

§4.2  Descripción

La sentencia goto permite transferir el control de ejecución a la etiqueta especificada por el identificador <etiqueta> (las etiquetas terminan siempre en dos puntos : 4.10.1). Recordar que la etiqueta debe estar en la misma función que el goto (el ámbito de las etiquetas se limita a la función en que son declaradas).

§4.3  Comentario

Aunque en tiempos fue muy popular y los programas estaban llenos de "gotos" con saltos cuyos destinos eran la mayoría de las veces etiquetas numéricas (el número de línea de la instrucción a la que se quería saltar), podríamos afirmar sin rubor que actualmente se trata de la sentencia maldita. Ningún programador medianamente "elegante" empleará jamás un goto. Incluso existen lenguajes que simplemente no disponen de esta instrucción tan "hortera".

C++ y otros lenguajes disponen de esta sentencia aunque se considera que debe ser evitada. Sobre todo porque la secuencia lógica del código resulta difícil de entender ( Ejemplo), aunque a veces es de utilidad para salir de bucles anidados en los que el break no es suficiente. Por ejemplo en condiciones de error.

Como botón de muestra de la opinión que merece esta sentencia a algunos autores, adjuntamos una de ellas respetando el original inglés. Aunque se refiere al lenguaje Perl, podría ser perfectamente aplicable a C++:

"If you want to really mess around with the structure of your programs, you can use goto LABEL to jump anywhere in your program. Whatever you do, don't do this. This is not to be used. Don't go that way.

I'm telling you about it for the simple reason that if you see it in anyone else's Perl, you can laugh heartily at them.... But goto with a label is to be avoided like the plague... Don't use it unless you really, really, really understand why you shouldn't. And even then, don't use it. Larry Wall has never used goto with a label in Perl, and he worte it. Don't. (He's watching)" [1].

§4.4  Ejemplo

void foo() {

  Again:         // esta es la etiqueta

  ...

  goto Again;    // Ok. salto a la etiqueta

  ...

}

void faa() {

  ...

  goto Final:    // Ok. salto a la etiqueta

  ...

  Final:

  goto Again;    // Error!! destino en otra función

  ...

}


§4.5
  Con los goto hay que observar las mismas precauciones señaladas para las sentencias de selección ( 4.10.2). Es ilegal que la transferencia de control a la etiqueta se salte una declaración que incluya un inicializador implícito o explícito, a menos que la declaración esté situada en el interior de un bloque que sea saltado completamente durante la transferencia. Ejemplo:

void foo() {
   ...
   goto Caso-1       // Salto a la etiqueta
   int y;            // Ok. no incluye inicialización
   C c;              // Ok. no incluye inicialización explícita [2]
     {
       int z = 0;    // Ok. dentro de un bloque
     }
   int x = 3;                 // Error!!
   for (int i=0; i<10; i++)   // Error!!
     ...;
     ...
   Caso-1:           // Etiqueta
   ...
}


§4.6
  El salto a posiciones anteriores a la declaración de un objeto automático ( 4.1.5) implica la destrucción del objeto en el sitio en que se origina el salto. En consecuencia, situaciones como la que sigue implican la destrucción del objeto, lo que se realiza mediante una invocación al constructor incluida automáticamente por el compilador.

void foo() {

  Inicio:

    X x;

    ...

    goto Inicio;  // invocación del destructor de x!!

}

Ejemplo:

#include <iostream>
using namespace std;

int tot = 0;
struct E {
  int x;
  ~E() {            // destructor
    cout << "Destruyendo objeto" << endl;
    tot++;
  }
};

int main() {        // ================
  int n = 2;        // L1:
  Inicio:
    goto Evalua;
  Sigue:
    if (n <= 0) goto Termina;
    E e1;           // L6:
    goto Inicio;    // L7: invocación al destructor e1.~E()
  Evalua:
    --n;
    goto Sigue;
  Termina:
    cout << "Se han destruido " << tot << " objetos" << endl;
    return 0;
}

Salida:

Destruyendo objeto
Destruyendo objeto
Destruyendo objeto
Se han destruido 3 objetos
Destruyendo objeto

Comentario

Desde luego, el resultado es sorprendente a primera vista. Para entender el (a mi entender diabólico ;-) comportamiento del compilador, puede hacerse n = 0 en la sentencia L.1 y ejecutar el programa. El resultado no es menos sorprendente aunque revelador:

Destruyendo objeto
Se han destruido 1 objetos
Destruyendo objeto

En este caso estamos seguros que el programa no ha tenido ocasión de ejecutar las sentencias L6/L7. Sin embargo, se han destruido (y por tanto creado previamente) dos objetos. Es fácil deducir que la última salida corresponde a la destrucción que ocurre antes de salir de la función main, cuando se invocan los destructores de todos los objetos automáticos definidos en dicho ámbito. Sin embargo las dos construcciones (y la destrucción previa) no son tan evidentes.

La explicación es que este tipo de saltos transfiere el control de la ejecución, pero el compilador supone que ha recorrido el ámbito léxico de las sentencias atravesadas, y la creación y destrucción de objetos está relacionada con este recorrido. El esquema muestra gráficamente el proceso (las sentencias ejecutadas se han señalado con x):

int main() {        // ================
  int n = 0;        // L1bis:
  Inicio:
    goto Evalua;          -----|
  Sigue:                       |     ---------------------------
    if (n <= 0) goto Termina;  |R1   ^                         x
    E e1;                      |     |                         |
    goto Inicio;               |     |R2                       |
  Evalua:                      V     |                         |R3
    --n;                       x     |                         |
    goto Sigue;                -------                         |
  Termina:                                                     V
    cout << "Se han destruido " << tot << " objetos" << endl;  x
    return 0;                                                  x
}

El primer recorrido (R1) crea una instancia del objeto, que es destruida en el segundo (R2), ya que este transfiere el control a una posición anterior a la de declaración. El tercer recorrido (R3) vuelve a crear un objeto (es la misma situación que en R1). Finalmente el objeto es destruido al salir el programa del ámbito de main.

Puede realizarse una última comprobación encerrando la sentencia L6 en un bloque, de forma que el recorrido léxico no incluya a su interior en el ámbito:

{ E e1; }    // L6bis:

En este caso la salida es:

Se han destruido 0 objetos

§5 return

La sentencia return devuelve el control de ejecución desde la función que contiene el return a la rutina que la invocó; opcionalmente puede devolver un valor.

§5.1  Sintaxis

return [ <expresion> ] ;

§5.2  Descripción

La sentencia return devuelve el control de ejecución desde la función que contiene el return a la rutina que la invocó. Además, opcionalmente puede devolver un valor (contenido en <expresion>).

<expresion> es opcional, así como el paréntesis que se suele colocar, aunque no es necesario. Si no se indica <expresion> la función no devuelve nada.

  Más información en: Funciones: "Valores devueltos" ( 4.4.7)

§5.3  Ejemplo

double sqr(double x) {
  return (x*x);
}

  Inicio.


[1]  Simon Cozens y Peter Wainwright. "Begining Perl" Wrox Press Inc. (May 2000). La referencia a Larry Wall se refiere al creador de este lenguaje. Desde luego esta podría ser considerada una opinión extrema, pero de todas formas, es una pena que toda esta elegancia de la programación estructurada desaparezca en cuanto el compilador traduce todo a ensamblador, ya que este lenguaje está compuesto en gran parte por los nefastos gotos.

[2]  Para que esta sentencia sea posible es necesario que no se haya definido explícitamente un constructor por defecto.