Condiciones en C, C++ y Objective-C
SOLUCIONARIO
Condiciones en C, C++ y Objective-C
2024-09-25
Por
Occam's Razor

#include <stdio.h>

int main () {
  int n = 42;
  int h = 0, v = 0;

  printf ("Adivina en que número estoy pensando ? ");
  scanf ("%d", &v);

  if (v < 0) {
    fprintf (stderr, "Sólo números positivos\n");
    return -1;
  }
  
  if (v == n) printf ("Bien Hecho!\n");
  else {
    printf ("Sigue Probando!");
    printf ("El número es %s\n", v < n ? "MAYOR" : "MENOR");
    
    /* Si hemos fallado genera una pista */
    printf ("Pista : ");
    h = (v > n) ? v - n : n - v;
    
    if (h < 5) printf (" TE QUEMAS!!!\n");
    else if (h < 10) printf (" CALIENTE\n");
    else if (h < 15) printf (" TEMPLADO\n");
    else printf (" FRIO\n");
  }
  return 0;
}

En C y C++, la construcción if-then-else no necesita que escribamos el then, lo cual es bastante común (Bash y Lua son excepciones). Como podemos ver en el ejemplo, cuando a cada parte del bloque if-else sigue una sola instrucción no es necesario utilizar las llaves {}. De hecho, en C y C++ varias instrucciones agrupadas con {} son sintácticamente un única instrucción.

La condición de la instrucción if debe ir siempre entre paréntesis y, como en la mayoría de otros lenguajes, el bloque else es opcional. Es posible, como se puede ver en el programa, añadir un nuevo bloque if tras la clausura else para refinar todavía más el flujo del programa.

C permite definir bloques if-then-else de una forma alternativa, si bien, más allá de la curiosidad su interés es limitado. Esta construcción la veremos en otros lenguajes como Python o Bash, y nos permite re-escribir el bloque de código que genera la pista de la siguiente forma:

  (h < 5)  && puts ("TER QUEMAS") ||
  (h < 10) && puts ("CALIENTE")   ||
  (h < 15) && puts ("TEMPLADO")   ||
  puts ("FRIO");

Esta construcción se aprovecha de la forma en la que se evaluan los operadores && y ||. Puesto que un AND lógico, requiere que ambas operadores sean ciertos, la segunda expresión (en este caso la instrucción puts) debe ser evaluada si la primera es cierta. Si la primera es falsa, ya no tiene sentido evaluar la segunda ya que el resultado del AND será 0, independientemente del valor de la segunda expresión.

Estas expresiones se pueden encadenar con el operador || que realiza un OR lógico y sigue una lógica similar, pero teniendo en cuenta que un OR es solo falso solo si los dos operadores son falsos.. En el caso de que el primer operador sea verdadero, ya no es necesario evaluar el segundo (lo que equivaldría a comprobar la siguiente condición), pero en caso de que sea falso, hay que comprobar el segundo valor para obtener el resultado.

El operador ternario, es ?: y se utiliza de la siguiente forma:

(condición) ? entonces-haz-esto : sino-haz-esto

SABÍAS QUE

La secuencia de ifs de nuestro ejemplo la podríamos haber escrito usando el operador ternario. Sería algo como esto:

    (h < 5) ? printf (" TE QUEMAS!!!\n")
        : (h < 10) ? printf (" CALIENTE\n")
        : (h < 15) ? printf (" TEMPLADO\n")
        : printf (" FRIO\n");

Como podéis ver la estructura es muy similar a el uso de cond && inst1 || inst2, la diferencia es que el operador ternario es un operador y por tanto nos permite construir expresiones, mientras que el uso de los operadores lógicos no lo permiten. En otras palabras, el operador ternario nos permite devolver un valor arbitrario, mientras que la operación lógica solo devolverá cero o 1.

Ya os adelanto que los lenguajes que proporcionan este operador lo hacen usando esta misma sintaxis (Python es la excepción). La última línea en al que calculamos el valor de h para generar la pista, se podría re-escribir de la siguiente forma:

h = abs (v - n);

Donde abs es la función que calcula el valor absoluto de un entero y que está definida en stdlib.h.

NOTA

La función scanf (Escanea con Formato), es la complementaria a printf y nos permite leer valores desde la entrada estándar. La cadena que pasamos como primer parámetro se llama cadena de formato (format string) y nos permite especificar los tipos de los datos que queremos leer en las variables que pasamos como siguientes parámetros.

En este caso %d significa Decimal y se utiliza para leer un número en formato decimal.

Otros lenguajes proporcionan soluciones similares y en muchas ocasiones utilizan el mismo (o muy similar) conjunto de caracteres para las cadenas de formato.

Para terminar, las condiciones, tanto la instrucción if como el operador ternario ?: no tienen porque ser operaciones booleanas en C, pero es recomendable que lo sean. En el caso de que no lo sean, un valor 0 o NULL es equivalente al valor lógico FALSO y cualquier otra cosa es equivalente a VERDADERO.

SABÍAS QUE

El lenguaje C introdujo el tipo bool para representar valores booleanos en el estándar C99. En C++ el tipo bool está soportado desde siempre y en objective-C, si bien podemos utilizar bool igual que en C99, gran cantidad de código usa la redefinición de tipo BOOL que no es más que un unsigned char. Objective-C BOOL toma valores YES and NO en lugar de los true o false del tipo estándar bool.

C (al igual que C++ y Objective C) también ofrece el comando switch, el cual se puede ver como una secuencia de ifs. Su sintaxis es la siguiente:

switch (variable) {
    case CONSTANTE1: //código
    break;
    case CONSTANTE2: //código
    break;
    default: //Código 
}

Este código evaluará la variable y saltará al case correspondiente o a la etiqueta default en caso de que el valor de la variable no se encuentre listado en ninguno de los casos. switch funciona literalmente como hemos descrito. El flujo del programa se desvía al case correspondiente y por lo tanto deberemos terminar el bloque de cada caso con una instrucción break o de lo contrario, el programa seguirá ejecutándose secuencialmente pasando por todos los case restantes.

SOY CURIOSO

Busca el dispositivo de Duff (Duff’s Device) para ver un uso curioso de switch

C++ define el atributo [[fallthrough]] para indicar la situación en la que el break no se incluye a propósito y se espera que el código que sigue se ejecute. A efectos de código generado el atributo no tiene ningún efecto y solo es usado por compiladores o utilidades que realicen análisis estático del código reportando cosas como olvidarnos el break en un switch.

Este programa utiliza tres variables de tipo entero: v, n y h. En C es necesario declarar las variables (en realidad esto aplica a todo) antes de poder usarlas, esto es, darles un nombre ( v,n o h en este caso) y un tipo que en el ejemplo anterior es el tipo entero int.

SOY NOVATO

Las variables nos permiten almacenar valores y modificarlos según necesitemos en nuestros programas. En este caso la variable v la utilizamos para leer el valor que introduce el usuario. La variable h se utiliza para almacenar un resultado intermedio. Podríamos reutilizar la variable v ya que no la volvemos a utilizar en el resto del programa, pero en general eso no es muy buena idea a no ser que queramos parecer guays (o tengamos graves restricciones de memoria), puesto que hace que el programa sea más difícil de seguir.

Se considera buena práctica dar a las variables nombres con significado, especialmente hoy en día que no tenemos restricciones de memoria y los entornos de desarrollo van a escribir la mayoría de los nombres por nosotros. Sin embargo, en programas o funciones sencillas a veces es práctico usar nombres cortos, sobre todo si hay operaciones o condiciones muy largas que se hacen difíciles de leer. Al final, utiliza el sentido común. En este ejemplo, el programa es tan sencillo que realmente no hay diferencia entre uno u otro enfoque. Pero quizás el código sería un poco más sencillo de leer declarando las variables como:

int  numero;
int  pista, valor;

La variable n… bueno, no cambia de valor, así que podría ser una constante… En C/C++ esto lo podemos hacer de varias formas:

#define N 42

const int n = 42;

Tradicionalmente C utiliza la directiva del precompilador #define. En este caso, lo que el pre-procesador hace es literalmente sustituir el valor de H por 42 en todo el código antes de compilarlo… De la misma forma que lo haría un procesador de textos. En general esto funciona perfectamente, pero es importante saber que hacemos para evitar efectos indeseados. #define es realmente un macro de sustitución más que una constante formalmente hablando.

En el segundo caso, la palabra clave const le indica al compilador que esta variable solo puede ser inicializada una vez, tras lo cual su valor no puede ser modificado. Si bien esto no es del todo cierto, en programas que no hagan filigranas con punteros la declaración funcionará como una constante a efectos de compilación.

La principal ventaja del segundo método es que tenemos información de tipo. Si nuestra cadena es un entero, nuestra constante también lo es. Una vez más, esto no es un grave problema si sabes lo que haces, pero sino, las promociones automáticas de tipos de C/C++ te pueden jugar una mala pasada.

Resumen

  • Soporta if ... else [if] ... y (condicion) && ... || ...
  • Soporta operador terciario (cond) ? valor_Verdad : Valor_Falso
  • Versiones anteriores a C99 No soporta booleanos: ==0 -> FALSO y !=0 VERDADERO
  • Soporta switch (variable) { case VALORES: break; default:}
  • Constantes con preprocesador: #define H 42 (solo una secuencia de caracteres)
  • Constantes con modificador: const int h = 42 (tipo asociado)

SOBRE Occam's Razor
Somos una revista libre que intenta explicar de forma sencilla conceptos tecnológicos y científicos de los que no podemos escapar. Al final, todo es más fácil de lo que parece!

 
Tu publicidad aquí :)