Operadores de comparación, lógica y el operador ternario

Para tomar decisiones en un programa, el código necesita comparar valores y evaluar condiciones. Los operadores relacionales (<, >, <=, >=, ==, !=) son las herramientas que nos permiten preguntar si un número es mayor que otro o si dos valores son exactamente iguales. En C, estas comparaciones no devuelven un tipo booleano puro por defecto, sino un int: si la condición es verdadera, el resultado es 1; si es falsa, es 0. Aunque C99 [C99] introdujo el tipo _Bool para manejar valores lógicos de forma más semántica, en el núcleo de la máquina, la lógica se basa en la distinción entre cero y cualquier valor distinto de cero.

Los operadores lógicos permiten combinar múltiples comparaciones. El operador && (AND lógico) requiere que ambas condiciones sean verdaderas para devolver 1. El operador || (OR lógico) solo necesita que una de las dos sea verdadera. El operador ! (NOT lógico) es el inversor: si le das un 1, te devuelve 0, y viceversa. Un comportamiento crítico que debes conocer es la evaluación de cortocircuito (short-circuit evaluation): en una expresión A && B, si A ya es falso, el compilador ni siquiera mira B, porque sabe que el resultado final será falso de todos modos. Esto es vital para evitar errores de acceso a memoria.

El operador ternario (condición ? expresión_si_verdadero : expresión_si_falso) es la única forma en C de tener una estructura condicional que sea, a la vez, una expresión. Mientras que un if es una sentencia (una instrucción que ejecuta un bloque), el operador ternario produce un valor que puede ser asignado directamente a una variable o pasado como argumento a una función.

Si usas mal las comparaciones, tu programa seguirá ejecutando código en rutas lógicas que nunca deberían alcanzarse. Si ignoras la evaluación de cortocircuito, podrías intentar acceder a un puntero nulo, provocando un fallo de segmentación (segmentation fault).

#include <stdio.h>

typedef struct {
    int lectura;
    int activo;
} Sensor;

int main(void) {
    // Configuración inicial
    Sensor s1 = { .lectura = 85, .activo = 1 };
    Sensor s2 = { .lectura = 10, .activo = 0 };
    Sensor *s3 = NULL; // Puntero nulo para probar cortocircuito
    int umbral_critico = 70;

    // 1. Uso de operadores relacionales y lógicos con cortocircuito
    // Gracias al cortocircuito, 's3->lectura' nunca se evalúa porque s3 es NULL.
    // Si no existiera el cortocircuito, el programa colapsaría aquí.
    if (s3 != NULL && s3->lectura > umbral_critico) {
        printf("Alerta: Sensor 3 fuera de rango.\n");
    } else {
        printf("Sensor 3: Seguro (no evaluado o nulo).\n");
    }

    // 2. Combinación de condiciones lógicas
    if (s1.activo && s1.lectura > umbral_critico) {
        printf("Alerta: Sensor 1 detectó nivel crítico: %d\n", s1.lectura);
    }

    // 3. Operador ternario para asignación directa
    // El ternario devuelve un valor, lo que permite su uso en inicializadores.
    const char *estado_s2 = (s2.activo && s2.lectura > umbral_critico) ? "CRÍTICO" : "NORMAL";
    printf("Estado de Sensor 2: %s\n", estado_s2);

    // 4. Comparación de igualdad y desigualdad
    if (s1.lectura != s2.lectura) {
        printf("Los sensores reportan valores distintos.\n");
    }

    return 0;
}

En el código anterior, observa cómo s3 != NULL actúa como un guardia. Debido a la evaluación de cortocircuito del operador &&, cuando el compilador ve que s3 es NULL (lo cual hace que la primera parte sea falsa), descarta la segunda parte s3->lectura > umbral_critico. Esto es lo que evita que el procesador intente acceder a una dirección de memoria inválida.

La variable estado_s2 se inicializa usando el operador ternario. Fíjate en que la expresión (s2.activo && s2.lectura > umbral_critico) ? "CRÍTICO" : "NORMAL" se evalúa y su resultado (un puntero a constante char) se asigna directamente. Esto es mucho más compacto que usar un bloque if/else para una asignación simple.

Finalmente, las comparaciones relacionales como s1.lectura > umbral_critico devuelven un valor que el if interpreta para decidir si el bloque de código siguiente se ejecuta o no, basándose en si el resultado es distinto de cero.

El error frecuente

Un error clásico y extremadamente peligroso para principiantes es confundir el operador de asignación = con el operador de igualdad == dentro de una condición.

int nivel = 10;

// ERROR: Esto NO comprueba si nivel es 5.
// Esto asigna 5 a 'nivel' y luego evalúa el resultado (5), que es distinto de cero (verdadero).
if (nivel = 5) { 
    printf("Esto se ejecutará siempre, y el nivel ahora vale 5.\n");
}

Este error es sutil porque el código es sintácticamente válido en C. El compilador suele emitir un aviso (warning) si usas la bandera -Wall o -Wparentheses de gcc, alertándote de que una asignación se está realizando dentro de una expresión condicional. Herramientas como AddressSanitizer no detectarán esto porque no es un error de memoria, sino un error de lógica pura que corrompe el estado de tus variables.

29

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio