Operadores y expresiones con énfasis en conversiones

Capítulo 3

Tiempo estimado de lectura: 9 minutos

+ Ejercicio

Operadores aritméticos

Los operadores aritméticos combinan valores numéricos para producir otro valor. En C, el tipo del resultado depende de los tipos de los operandos y de las conversiones implícitas que ocurren durante la evaluación.

  • + suma
  • - resta (y negación unaria)
  • * multiplicación
  • / división
  • % módulo (resto), solo para enteros

División entera vs. división en punto flotante

La regla práctica: si ambos operandos de / son enteros, la división es entera (trunca hacia cero). Si al menos uno es flotante, la división es flotante.

#include <stdio.h>int main(void){    printf("%d\n", 7/2);        // 3    printf("%.1f\n", 7/2.0);   // 3.5    printf("%.1f\n", (double)7/2); // 3.5    return 0;}

Paso a paso para “forzar” división flotante de forma correcta:

  • Identifica si ambos operandos son enteros (por ejemplo, int).
  • Convierte explícitamente uno de ellos a double o usa un literal flotante (2.0).
  • Evita hacer el cast después de la división: (double)(7/2) produce 3.0, porque la división ya ocurrió como entera.

Módulo y signos

% funciona con enteros. Con números negativos, el signo del resultado sigue el signo del dividendo (el de la izquierda) en C moderno (truncamiento hacia cero).

printf("%d\n", -7 % 2); // -1

Operadores relacionales y lógicos

Relacionales

Devuelven 0 (falso) o 1 (verdadero) en expresiones típicas:

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

  • <, <=, >, >=
  • == igualdad
  • != desigualdad

Error frecuente: confundir asignación con comparación.

if (x = 5) { /* ... */ }   // asigna 5 a x, condición verdadera (warning típico)

Correcto:

if (x == 5) { /* ... */ }

Lógicos

&& (AND), || (OR), ! (NOT). Importante: cortocircuito. En a && b, si a es falso, b no se evalúa; en a || b, si a es verdadero, b no se evalúa.

if (den != 0 && num/den > 1) { /* seguro: evita dividir por cero */ }

Diferencia clave con operadores bit a bit: && y || trabajan con “verdadero/falso” y cortocircuitan; & y | operan bit a bit y evalúan ambos lados.

Asignación y asignación compuesta

La asignación simple es =. Las asignaciones compuestas combinan una operación con asignación: +=, -=, *=, /=, %=, <<=, >>=, &=, |=, ^=.

int x = 10; x += 3;   // x = x + 3;int y = 5;  y *= 2;   // y = y * 2;

Detalle importante: en x += y puede ocurrir una conversión implícita al tipo de x. Esto puede ocultar pérdida de precisión si x es más “pequeño” o si es entero y y es flotante.

int x = 0; x += 2.9;  // x termina siendo 2 (se pierde la parte decimal)

Incremento y decremento: prefijo vs. sufijo

++ y -- existen en forma prefija (++i) y sufija (i++).

  • ++i: incrementa y luego produce el valor incrementado.
  • i++: produce el valor actual y luego incrementa.
int i = 3;printf("%d\n", ++i); // imprime 4, i queda 4i = 3;printf("%d\n", i++); // imprime 3, i queda 4

Regla práctica: usa ++i o i++ como sentencia independiente (i++;) cuando no necesitas el valor de la expresión. Evita combinarlos en expresiones complejas.

Expresiones con comportamiento no definido (evitar)

Modificar una variable más de una vez entre puntos de secuencia (o leerla y modificarla sin reglas claras) puede llevar a comportamiento no definido.

int i = 1;int a = i++ + ++i; // NO: comportamiento no definido

Forma segura: separa en pasos.

int i = 1;int t1 = i++;int t2 = ++i;int a = t1 + t2;

Operadores bit a bit

Operan sobre la representación binaria de enteros (típicamente unsigned para evitar sorpresas con el bit de signo).

  • & AND
  • | OR
  • ^ XOR
  • ~ NOT
  • << desplazamiento a la izquierda
  • >> desplazamiento a la derecha

Máscaras: activar, desactivar y consultar bits

unsigned flags = 0;const unsigned READ  = 1u << 0; // 0001const unsigned WRITE = 1u << 1; // 0010flags |= READ;              // activar bit READflags |= WRITE;             // activar bit WRITEflags &= ~WRITE;            // desactivar bit WRITEif (flags & READ) { /* está activo */ }

Reglas prácticas:

  • Usa sufijo u en constantes de bits (1u) para trabajar en unsigned.
  • Evita desplazar por un número de bits mayor o igual al ancho del tipo (comportamiento no definido).
  • Con tipos con signo, >> puede ser aritmético (rellena con el bit de signo) y variar por implementación; con unsigned suele ser lógico (rellena con ceros).

Precedencia y asociatividad (y cómo evitar ambigüedades)

La precedencia define qué se agrupa primero; la asociatividad define cómo se agrupa cuando hay empate. Aunque memorizar toda la tabla no es necesario, sí conviene dominar los casos que generan bugs.

Casos típicos

ExpresiónCómo se interpretaRecomendación
a + b * ca + (b * c)Paréntesis si quieres lo contrario
a < b == c(a < b) == cEvita encadenar; separa en dos comparaciones
x & 1 == 0x & (1 == 0) porque == tiene mayor precedencia que &Escribe (x & 1) == 0
a || b && ca || (b && c)Paréntesis para claridad
mask << 1 + nmask << (1 + n) porque + gana a <<Escribe mask << (1 + n) o (mask << 1) + n según intención

Guía práctica para desambiguar expresiones

  • Si mezclas operadores de familias distintas (aritméticos, relacionales, bit a bit, lógicos), agrega paréntesis aunque “sepas” la precedencia.
  • No encadenes comparaciones como en matemáticas: a < b < c no significa lo que parece en C.
  • Cuando uses máscaras, parentiza siempre: (x & MASK) != 0.

Conversiones implícitas (promociones) y cast explícito

En muchas expresiones, C convierte automáticamente operandos a un tipo común antes de operar. Esto es útil, pero también puede introducir resultados inesperados.

Promociones enteras (idea práctica)

Tipos enteros “pequeños” suelen promoverse a int (o unsigned int) en expresiones: por ejemplo, char y short se convierten antes de sumar, comparar, etc. Esto afecta especialmente a operaciones bit a bit y a ~.

unsigned char c = 0xF0; // 240int r = ~c;              // c se promociona a int, luego se aplica ~printf("%d\n", r);

Si tu intención era limitarte a 8 bits, debes enmascarar:

unsigned char c = 0xF0;unsigned char r8 = (unsigned char)(~c); // o: (unsigned char)(~c & 0xFF)

Conversión usual aritmética: mezcla de int/float/double

Si mezclas enteros con flotantes, el entero se convierte a flotante. Si mezclas float con double, normalmente se trabaja como double.

int a = 1;double b = 2.0;double c = a + b; // a se convierte a double

Cast explícito: cuándo es necesario y cuándo oculta un problema

Un cast ((tipo)expresion) fuerza una conversión. Úsalo para expresar intención cuando la conversión es parte del diseño; evítalo para “silenciar” warnings sin entender la causa.

Necesario o razonable:

  • Para obtener división flotante: (double)num / den.
  • Para convertir un resultado a un tipo específico cuando sabes que el rango es seguro.
  • Para operaciones con bits donde quieres un ancho concreto, acompañado de máscaras.

Suele ocultar un problema:

  • Cast para evitar un warning de signed/unsigned sin revisar la lógica.
  • Cast que trunca un double a int sin validar rango.
  • Cast que convierte un valor negativo a unsigned (cambia totalmente el significado).

Casos frecuentes que causan bugs y warnings

1) Comparaciones entre signed y unsigned

Cuando comparas un entero con signo con uno sin signo, el con signo puede convertirse a unsigned. Si el valor era negativo, se vuelve un número muy grande, y la comparación cambia.

#include <stdio.h>int main(void){    int s = -1;    unsigned u = 1u;    if (s < u) printf("s < u\n"); else printf("s >= u\n");    return 0;}

Qué ocurre típicamente: s se convierte a unsigned, y entonces -1 pasa a ser un valor enorme; el resultado suele ser s >= u. Compiladores suelen advertir: “comparison of integer expressions of different signedness”.

Guía práctica:

  • Si la lógica admite negativos, evita mezclar con unsigned en comparaciones.
  • Convierte de forma explícita solo si estás seguro: por ejemplo, valida s >= 0 antes de convertir (unsigned)s.

2) Pérdida de precisión al asignar

double pi = 3.141592653589793;float f = (float)pi; // pierde precisiónint n = (int)pi;              // trunca a 3

Guía práctica:

  • Si necesitas decimales, no guardes en int.
  • Si reduces de double a float, asume pérdida y decide si es aceptable.
  • Si conviertes a entero, considera redondeo explícito (por ejemplo, con funciones de librería) en lugar de truncar.

3) Overflow y promociones: resultados inesperados

En enteros, un overflow con signo es comportamiento no definido; en unsigned es aritmética modular. Esto se combina con promociones y puede sorprender.

unsigned a = 4000000000u;unsigned b = 4000000000u;unsigned c = a + b; // se envuelve (mod 2^N)printf("%u\n", c);

Mini-retos: “¿qué imprime?” y “¿qué warning aparece?”

Compila con opciones de warnings activas (por ejemplo, -Wall -Wextra -Wconversion) y responde sin ejecutar primero. Luego verifica.

Reto 1: división

#include <stdio.h>int main(void){    int a = 5, b = 2;    printf("%d\n", a/b);    printf("%.2f\n", a/b);    printf("%.2f\n", (double)a/b);    return 0;}
  • ¿Qué imprime cada línea?
  • ¿Aparece algún warning? (Pista: formato %.2f espera double.)

Reto 2: precedencia con bits

#include <stdio.h>int main(void){    unsigned x = 2u;    printf("%u\n", x & 1u == 0u);    printf("%u\n", (x & 1u) == 0u);    return 0;}
  • ¿Por qué las dos líneas no significan lo mismo?
  • ¿Cuál es la forma correcta para probar si el bit 0 está apagado?

Reto 3: signed vs unsigned

#include <stdio.h>int main(void){    int s = -10;    unsigned u = 1u;    printf("%d\n", s < (int)u);    printf("%d\n", s < u);    return 0;}
  • ¿Qué imprime cada comparación?
  • ¿Qué warning esperas en la segunda?

Reto 4: pérdida de precisión silenciosa

#include <stdio.h>int main(void){    double d = 16777217.0;    float f = d;    printf("%.0f\n", (double)f);    return 0;}
  • ¿Qué imprime y por qué?
  • ¿Qué warning podría aparecer con -Wconversion?

Reto 5: incremento en expresiones

#include <stdio.h>int main(void){    int i = 0;    int a = i++ + 1;    int b = ++i + 1;    printf("%d %d %d\n", i, a, b);    return 0;}
  • ¿Qué imprime?
  • ¿Por qué aquí sí es definido (a diferencia de i++ + ++i)?

Ahora responde el ejercicio sobre el contenido:

¿Cuál de las siguientes opciones fuerza correctamente una división en punto flotante para obtener 3.5 al dividir 7 entre 2 en C?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Al convertir 7 a double antes de dividir, la operación pasa a ser flotante y el resultado es 3.5. En cambio, (double)(7/2) primero hace división entera (3) y luego convierte a 3.0.

Siguiente capítulo

Entrada y salida estándar en C con validación básica

Arrow Right Icon
Portada de libro electrónico gratuitaFundamentos de programación en C desde cero: variables, control de flujo y funciones
27%

Fundamentos de programación en C desde cero: variables, control de flujo y funciones

Nuevo curso

11 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.