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
doubleo usa un literal flotante (2.0). - Evita hacer el cast después de la división:
(double)(7/2)produce3.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); // -1Operadores relacionales y lógicos
Relacionales
Devuelven 0 (falso) o 1 (verdadero) en expresiones típicas:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
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 4Regla 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 definidoForma 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
uen constantes de bits (1u) para trabajar enunsigned. - 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; conunsignedsuele 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ón | Cómo se interpreta | Recomendación |
|---|---|---|
a + b * c | a + (b * c) | Paréntesis si quieres lo contrario |
a < b == c | (a < b) == c | Evita encadenar; separa en dos comparaciones |
x & 1 == 0 | x & (1 == 0) porque == tiene mayor precedencia que & | Escribe (x & 1) == 0 |
a || b && c | a || (b && c) | Paréntesis para claridad |
mask << 1 + n | mask << (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 < cno 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 doubleCast 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
doubleaintsin 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
unsigneden comparaciones. - Convierte de forma explícita solo si estás seguro: por ejemplo, valida
s >= 0antes 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 3Guía práctica:
- Si necesitas decimales, no guardes en
int. - Si reduces de
doubleafloat, 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
%.2fesperadouble.)
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)?