Está en la página 1de 13

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.

4.10.2  Sentencias de selección


§1  Sinopsis

Las sentencias de selección, también llamadas de control de flujo, permiten decidir entre distintos
cursos de acción en función de ciertos valores.  En C++ existen tres tipos de estas sentencias de
selección:

 if...else  .

 else if    .
 switch   .

Recuerde que de no ser por estas sentencias, el flujo de ejecución del programa estaría siempre
constreñido a la ejecución de sus sentencias en el orden en que están colocadas en el fuente.

§2  if … else

En su forma abreviada, cuando no existe la cláusula else, esta sentencia permite escoger entre
ejecutar o no una sentencia, en función del resultado de una expresión lógica.  En su forma
ampliada, cuando la cláusula else está presente, permite escoger entre dos opciones alternativas.

§2.1  Sintaxis

if ( <condición> )  <sentencia1>;

[ else  <sentencia2>; ]

§2.2  Descripción

  <condición> debe ser una expresión relacional ( 4.9.12) que devuelve un valor lógico, es
decir, un bool ( 3.2.1b), y estar obligatoriamente entre paréntesis.  Pueden declararse variables
dentro de la <condición>.  Por ejemplo, la siguiente es una sintaxis válida:

if (int val = func(arg))

   val = z ;

else  val = y;
El ámbito de la variable val incluye toda la sentencia if,  incluyendo, en su caso, el bloque
<sentencia2> de else.  La cláusula else es opcional, pero no puede haber sentencias entre el if
y else.    Recuerde las precauciones indicadas respecto de las expresiones relacionales (
4.9.12), ya que son motivo frecuente de error en este tipo de sentencias.

  <sentencia1>. Es una sentencia o bloque de código que se ejecuta si <condicion> se


evalúa como cierto (true  !=  0).

  <sentencia2> es una sentencia o bloque de código que se ejecuta si existe un else y


<condicion> resulta falso (false  ==  0)

Puesto que el if simplemente chequea el valor resultante de <condicion> (igual o desigual a


cero), las dos expresiones siguientes son equivalentes:

if ( expresion )  <sentencia> ;

if ( expresion !=0 ) <sentencia> ;

La forma más general es:

if (<condicion>) {

   <sentencia1>;

else  {

   <sentencia2>;

§2.3  Ejemplos

Uno sencillo:

if (salida == 'S') break;

Otro ejemplo:

if (a > b)

   z = a;

else
   z = b;

Se podría haber escrito de forma más comprimida:

if (a > b) z = a;

else z = b;

También:

a>b?z=a:z=b;

o mejor aún:

z = (a > b ? a : b);

Otro ejemplo:

if (int val = func(count)) { /* sentencias */ }

else {                     // otra vía de acción

   cout << "val es falso";

Puesto que la cláusula else es opcional, en los if... else anidados podría haber ambigüedad sobre
a qué if corresponde un else;  esto se evita asociando el else al if más interno sin else.   Por
ejemplo, en los dos trozos de código siguientes, el primero tiene una indentación que no se
corresponde con la realidad lógica del programa.

// mal indentado: ------------

if ( n > 0 )

   if ( a > b )

      z = a;

else

   z = b;

// bien indentado: -----------

if ( n > 0 )
   if ( a > b )

      z = a;

   else

      z = b;

§3  else if

Estas sentencias no representan en realidad nada nuevo, solo una sucesión de if  else anidados,
aunque de uso muy frecuente, por lo que haremos una consideración especial de este caso.

if ( <expresion1> )

   <sentencia1> ;

else if ( <expresion2> )

   <sentencia2> ;

else if ( <expresion3> )

   <sentencia3> ;

else

   <sentencia4> ;

En realidad, a la luz de lo expuesto en el apartado anterior, su indentación correcta sería:

if ( <expresion1> )

   <sentencia1> ;

else

   if ( <expresion2> )

      <sentencia2> ;

   else

      if ( <expresion3> )

         <sentencia3> ;
      else

         <sentencia4> ;

Las expresiones <expresion> son evaluadas correlativamente hasta que se encuentra la primera
que devuelve un valor cierto ( != 0 ), en cuyo caso se ejecuta el bloque de código <sentencia>
correspondiente y acaba la evaluación. En caso de que ninguna de las <expresion> sea cierta,
se ejecuta la <sentencia> correspondiente al else (si existe).

§4 switch

Se trata de una sentencia condicional multi-salida en la que las decisiones se toman en función de
un valor numérico entero de entre una serie de opciones posibles.  Puede existir una cláusula por
defecto o bien no adoptarse ninguna acción.

§4.1  Sintaxis

switch ( <expresion> ) {

  case <const1> : <sentencia1>; [break;]

  case <const2> : <sentencia2>; [break;]

    .

  .

    .

  case <constN> : <sentenciaN>; [break;]

 [default : <sentenciaD>; ]

§4.2  Descripción

La sentencia switch comprueba cuando una expresión <expresion> entre paréntesis (que se
traduce en un valor numérico) coincide con alguno de una serie de valores enteros constantes y
diferentes  (<constX>). En cuyo caso, se ejecuta un bloque de código específico <sentencia>. 
En caso de estar presente la cláusula opcional default y no existir concordancia con ninguno de los
valores anteriores, se ejecuta una sentencia por defecto (<sentenciaD>).

Los valores case y default pueden aparecer en cualquier orden, aunque lo usual es colocar
default al final.  Los dos puntos : después de <constX> son imprescindibles.
§4.3  Las expresiones <constX> son expresiones constantes ( 3.2.3a) que se resuelven en un
entero.  Pueden ser simples o compuestas, como se muestra en el ejemplo.

switch (c - 48) {
   case 0: case 1: case 2: case 3: case 4:

   case 5: case 6: case 7: case 8: case 9:


      cout << "Dígito" << endl;
      break;
   case 17: case 21: case 25: case 31: case 37:

   case 49: case 53: case 57: case 63: case 69:
      cout << "Vocal" << endl;
      break;
   default:
      cout << "Otro carácter" << endl;
}

§4.4   Después de ejecutado el bloque de código correspondiente a una concordancia, siguen


las comprobaciones, por lo que puede ser conveniente incluir un break para abandonar el control
del switch (no es necesario ningún break después de la última).  Ver ejemplo .

§4.5  Es ilegal que la transferencia de control originada en alguna cláusula case o default 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:

switch (n) {
   case 1: case 10:
      int y;            // L-3: Ok. no incluye inicialización
      cout << "Caso-1" << endl;
      break;
   case 2:
     {
      int z = 0;        // L-8: Ok. dentro de un bloque
      cout << "Caso-2" << endl;
      break;
     }
   case 3:
      int x = 3;        // L-13: Error-1
      cout << "Caso-3" << endl;
      break;
   case 4:              // L-16:
      for (int i = 0; i<4; i++)   // L-17: Error-2
      cout << "Caso-4" << endl;
      break;
   default:             // L-20:
      cout << "Resto de casos" << endl;
}

Observación: es posible que en estos casos, el error señalado por el compilador se produzca en
las sentencias responsables de que la transferencia de control se salte la declaración. En el código
del ejemplo, el Error-1 podría ser señalado en las sentencias L.16 y L.20, mientras que el Error-2
en la sentencia L.20. Como muestra se incluyen los mensajes obtenidos con el compilador MS
Visual C++ 6.0 en el caso anterior:

xxx.cpp(16) : error C2360: initialization of 'x' is skipped by 'case' label


xxx.cpp(13) : see declaration of 'x'
xxx.cpp(20) : error C2361: initialization of 'x' is skipped by 'default' label
xxx.cpp(13) : see declaration of 'x'
xxx.cpp(20) : error C2361: initialization of 'i' is skipped by 'default' label
xxx.cpp(17) : see declaration of 'i'

También podría gustarte