Referencia Detallada de Operadores en Lenguaje C

Operadores en Lenguaje C: Guía Completa

1. Operadores Aritméticos

Los operadores aritméticos ejecutan cálculos matemáticos fundamentales e incluyen: + (suma), - (resta), * (multiplicación), / (división) y % (módulo).

(1) Suma y Resta

int val1 = 8 + 7;       // Suma: 15
int val2 = 20 - 12;     // Resta: 8
float val3 = 5.5f - 2.5f;  // Resta con decimales: 3.0


Precaución:

Los tipos de los operandos deben ser compatibles. Mezclar int y float activa conversiones implícitas.

int res = 5 + 2.7;  // Peligroso: posible pérdida de datos (requiere conversión explícita)


(2) Multiplicación y División

  • División entera: si ambos operandos son enteros, el resultado se trunca descartando la parte decimal.
int producto = 4 * 6;       // Multiplicación: 24
int div_entera = 17 / 5;    // División entera: 3 (se descarta el decimal)
float div_decimal = 17.0f / 5.0f;  // División decimal: 3.4


  • División de coma flotante: si al menos un operando es decimal, el resultado preserva la parte fraccionaria.
float x = 11.0 / 4;   // 11.0 es decimal, resultado: 2.75
double y = 11 / 4.0;   // 4.0 es decimal, resultado: 2.75


Casos especiales:

int error_div = 8 / 0;     // Fallo: división por cero (error en tiempo de ejecución)
float inf_div = 8.0f / 0.0f;  // Resultado: inf (infinito)


(3) Operación Módulo (%)

El operador % calcula el resto de una división entera. Ambos operandos deben ser enteros. El signo del resto coincide con el del dividendo.

int resto1 = 14 % 5;     // Módulo: 4 (resto de 14/5)
int resto2 = -14 % 5;    // Resultado: -4 (depende del compilador, estándar C no lo define estrictamente)
int m1 = 9 % 4;    // 9/4 = 2 resto 1, m1 = 1
int m2 = -9 % 4;   // -9/4 = -2 resto -1, m2 = -1
int m3 = 9 % -4;   // 9/-4 = -2 resto 1, m3 = 1
int m4 = -9 % -4;  // -9/-4 = 2 resto -1, m4 = -1


Restricción:

Los operandos deben ser tipos enteros (int, long, etc.)

float mod_invalido = 5.5 % 2.0;  // Ilegal: módulo no soporta flotantes


2. Operadores de Desplazamiento

Los operadores de desplazamiento actúan sobre los bits de representación binaria de enteros. Existen dos: desplazamiento a la izquierda (<<) y desplazamiento a la derecha (>>).

En C, el desplazamiento aritmético se realiza directamente sobre la representación en complemento a dos.

(1) Representación Binaria de Enteros

  • Signo-magnitud: Conversión directa a binario donde el bit más significativo indica el signo (0=positivo, 1=negativo). Ej: +3 en 8 bits es 00000011, -3 es 10000011.
  • Complemento a uno: Para positivos coincide con signo-magnitud; para negativos, se invierten todos los bits excepto el de signo. Ej: -3 en signo-magnitud es 10000011, en complemento a uno es 11111100.
  • Complemento a dos: Para positivos coincide con signo-magnitud; para negativos, es el complemento a uno más 1. Ej: -3 en complemento a uno es 11111100, en complemento a dos es 11111101. Los enteros se almacenan en memoria como complemento a dos, y para mostrarlos se convierten de vuelta a signo-magnitud.

Ejemplo con 8 bits:

Valor: 7 Signo-magnitud: 0000 0111 Complemento a uno: 0000 0111 Complemento a dos: 0000 0111

Valor: -7 Signo-magnitud: 1000 0111 Complemento a uno: 1111 1000 (inversión sin signo) Complemento a dos: 1111 1001 (complemento a uno + 1)

(2) Desplazamiento a la Izquierda (<<)

  • Regla: Desplaza los bits del complemento a dos hacia la izquierda el número indicado de posiciones. Los bits que sobresalen por la izquierda se pierden, y los espacios vacíos a la derecha se rellenan con 0.
  • Característica: No modifica el valor original de la variable; genera un nuevo valor.
  • Ejemplo:
char num1 = 7; // Signo-magnitud: 00000111, Complemento a uno: 00000111, Complemento a dos: 00000111
char res1 = num1 << 1; // Desplazar complemento a dos 1 posición a la izquierda: 00001110, res1 = 14
char num2 = -7; // Signo-magnitud: 10000111, Complemento a uno: 11111000, Complemento a dos: 11111001
char res2 = num2 << 1; // Desplazar 1 posición: 11110010, convertir a signo-magnitud: restar 1 -> 11110001, invertir -> 10001110, res2 = -14


(3) Desplazamiento a la Derecha (>>)

int v1 = 24;     // Binario: 0000 0000 0000 0000 0000 0000 0001 1000
int v2 = v1 >> 3; // Desplazar 3 posiciones → 0000 0000 0000 0000 0000 0000 0000 0011 (decimal: 3)

int v3 = -24;    // Complemento a dos: 1111 1111 1111 1111 1111 1111 1110 1000
int v4 = v3 >> 3; // Desplazamiento aritmético → 1111 1111 1111 1111 1111 1111 1111 1101 (complemento a dos)
                 // Convertir a signo-magnitud: 1111 1101 → restar 1: 1111 1100 → invertir: 1000 0011 → decimal: -3


Dos modalidades de desplazamiento a la derecha:

Desplazamiento aritmético: Se descartan bits de la derecha y se rellena la izquierda con el bit de signo original (más común en compiladores). Al desplazar el complemento a dos, los bits excedentes a la derecha se pierden y los vacíos a la izquierda se completan con el bit de signo.

int w1 = -9; // Complemento a dos: 1111 1111 1111 1111 1111 1111 1111 0111
int w2 = w1 >> 1; // Rellenar con 1 a la izquierda: 1111 1111 1111 1111 1111 1111 1111 1011
                  // Complemento a dos a signo-magnitud: invertir + 1 → 1000 0101 → decimal: -5


Desplazamiento lógico: Se descartan bits de la derecha y se rellena la izquierda con 0. Los bits excedentes se pierden y los huecos se rellenan con ceros. Menos habitual.

Advertencia: Desplazar por una cantidad negativa es comportamiento indefinido en el estándar C.

int error = 15 >> -1;  // Ilegal: desplazamiento negativo (comportamiento indefinido)


3. Operadores a Nivel de Bits

(1) AND a nivel de bits (&)

int x = 12;     // Binario: 0000 0000 0000 0000 0000 0000 0000 1100
int y = 10;     // Binario: 0000 0000 0000 0000 0000 0000 0000 1010
int z = x & y;  // Resultado: 0000 0000 0000 0000 0000 0000 0000 1000 (decimal: 8)


Regla:

El resultado es 1 solo si ambos bits correspondientes son 1; en caso contrario, 0.

0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 1010

0000 0000 0000 0000 0000 0000 0000 1000

(2) OR a nivel de bits (|)

int x = 12;     // Binario: 0000 0000 0000 0000 0000 0000 0000 1100
int y = 10;     // Binario: 0000 0000 0000 0000 0000 0000 0000 1010
int z = x | y;  // Resultado: 0000 0000 0000 0000 0000 0000 0000 1110 (decimal: 14)


Regla:

El resultado es 1 si al menos uno de los bits correspondientes es 1; si ambos son 0, el resultado es 0.

0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 1010

0000 0000 0000 0000 0000 0000 0000 1110

(3) XOR a nivel de bits (^)

int x = 12;     // Binario: 0000 0000 0000 0000 0000 0000 0000 1100
int y = 10;     // Binario: 0000 0000 0000 0000 0000 0000 0000 1010
int z = x ^ y;  // Resultado: 0000 0000 0000 0000 0000 0000 0000 0110 (decimal: 6)


Regla:

El resultado es 1 cuando los bits correspondientes son diferentes; 0 si son iguales. 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 1010

0000 0000 0000 0000 0000 0000 0000 0110

Propiedades:

a ^ a = 0 a ^ 0 = a Conmutativa: a ^ b = b ^ a

4. Operadores de Asignación

Los operadores de asignación almacenan valores en variables. Es importante distinguir entre inicialización y asignación.

  • Diferencia: La inicialización ocurre al crear la variable con un valor; la asignación modifica el valor de una variable ya existente.

(1) Asignación simple (=)

int edad = 25;   // Inicialización
edad = 30;       // Asignación (sobrescribe el valor anterior)


Aclaración:

Inicializar es dar valor al crear; asignar es cambiar valor existente

int peso;        // Declaración
peso = 70;       // Asignación (no es inicialización)


(2) Operadores de asignación compuesta

Para simplificar operaciones, C ofrece operadores compuestos que combinan una operación aritmética con la asignación: +=, -=, *=, /=, %=, <<=, >>=, &=, |=, ^=. Realizan la operación y almacenan el resultado en la variable izquierda.

int base = 20;
base += 8;      // Equivalente: base = base + 8 (resultado: 28)
base -= 4;      // Equivalente: base = base - 4 (resultado: 24)
base *= 3;      // Equivalente: base = base * 3 (resultado: 72)
base /= 6;      // Equivalente: base = base / 6 (resultado: 12)
base %= 7;      // Equivalente: base = base % 7 (resultado: 5)
base <<= 1;     // Equivalente: base = base << 1 (resultado: 10)
base >>= 2;     // Equivalente: base = base >> 2 (resultado: 2)
base &= 3;      // Equivalente: base = base & 3 (resultado: 2)
base |= 1;      // Equivalente: base = base | 1 (resultado: 3)
base ^= 2;      // Equivalente: base = base ^ 2 (resultado: 1)


(3) Asignación encadenada

int p, q, r;
p = q = r = 50;  // Asignación encadenada (de derecha a izquierda)
// Equivalente: r = 50; q = r; p = q;


Recomendación: Evitar asignaciones encadenadas complejas para mantener la legibilidad

// No recomendado
m = (n = 3) + (o = 8);  // Difícil de entender

// Recomendado
n = 3;
o = 8;
m = n + o;


5. Operadores Unarios

Los operadores unarios requieren un solo operando:

(1) Operador sizeof

sizeof calcula el tamaño en bytes de una variable o tipo de dato. Es un operador, no una función. Para tipos se requieren paréntesis; para variables son opcionales.

int cantidad = 42;
printf("%lu\n", sizeof(cantidad));   // Salida: 4 (tamaño de int)
printf("%lu\n", sizeof(int));       // Salida: 4 (tamaño del tipo)
printf("%lu\n", sizeof cantidad);   // Válido: sin paréntesis
// printf("%lu\n", sizeof int);     // Ilegal: paréntesis obligatorios para tipos


Detalles:

sizeof es operador, no función Aplicado a un arreglo devuelve el tamaño total del arreglo

int datos[8];
printf("%lu\n", sizeof(datos));    // Salida: 32 (8×4 bytes)


(2) Operador de dirección (&)

int numero = 99;
int *puntero = &numero;  // Obtiene la dirección de numero
printf("%p\n", puntero);  // Muestra la dirección de memoria


Consideraciones:

No se puede obtener la dirección de variables register (ej: register int x; &x; es inválido) El nombre de un arreglo suele convertirse en la dirección del primer elemento, pero &nombre_arreglo representa la dirección del arreglo completo

int lista[6];
printf("%p\n", lista);      // Dirección del primer elemento
printf("%p\n", &lista);     // Dirección del arreglo completo (mismo valor numérico, tipo distinto)


(3) Operador de desreferencia (*)

El operador de desreferencia o acceso indirecto permite acceder al valor apuntado por un puntero.

int numero = 99;
int *puntero = &numero;
printf("%d\n", *puntero);  // Desreferencia: 99 (accede al valor apuntado)
*puntero = 77;            // Modifica el valor apuntado (numero pasa a 77)


(4) Operadores de incremento y decremento

int contador = 10;
int val_a = ++contador;  // Pre-incremento: incrementa y luego asigna (contador=11, val_a=11)
int val_b = contador++;  // Post-incremento: asigna y luego incrementa (contador=12, val_b=11)

int nivel = 5;
int val_c = --nivel;  // Pre-decremento: decrementa y luego asigna (nivel=4, val_c=4)
int val_d = nivel--;  // Post-decremento: asigna y luego decrementa (nivel=3, val_d=4)


Consejo de rendimiento:

Para iteradores (como los de std::vector), el pre-incremento ++it es más eficiente al evitar objetos temporales.

// Preferible (especialmente para tipos personalizados)
for (auto it = vec.begin(); it != vec.end(); ++it) {
    // ...
}


(5) Operadores de signo

int positivo = 15;
int negativo = -positivo;   // Negación: -15
int expl_pos = +positivo;   // Positivo explícito: 15 (generalmente omitido)


(6) Negación lógica (!)

El operador ! invierte el valor lógico. Si el operando es distinto de cero (verdadero), devuelve 0 (falso); si es 0 (falso), devuelve 1 (verdadero).

int activo = 10;   // No-cero significa verdadero
printf("%d\n", !activo);  // Salida: 0 (verdadero se invierte a falso)

int inactivo = 0;   // Cero significa falso
printf("%d\n", !inactivo);  // Salida: 1 (falso se invierte a verdadero)


(7) Negación a nivel de bits (~)

int origen = 7;    // Binario: 0000 0111
int invertido = ~origen;   // Binario: 1111 1000 (complemento a dos, decimal: -8)


(8) Conversión de tipo explícita ((type))

double pi = 3.14159;
int entero = (int)pi;  // Conversión a int (trunca decimales, resultado: 3)

int divisor = 10;
double resultado = (double)divisor / 4;  // Convierte a double para evitar división entera (resultado: 2.5)


Advertencia:

La conversión puede provocar pérdida de datos (flotante a entero) Conversiones incorrectas generan comportamiento indefinido

int *ptr_peligroso = (int*)0xABCD1234;  // Riesgoso: convertir dirección arbitraria a puntero


6. Operadores Relacionales

Los operadores relacionales comparan dos operandos y producen un valor lógico: 1 (verdadero) o 0 (falso). Incluyen:

  1. >: mayor que
  2. >=: mayor o igual que
  3. <: menor que
  4. <=: menor o igual que
  5. ==: igual a
  6. !=: distinto de

Ejemplo:

int m = 8, n = 3;
int r1 = m > n;   // m mayor que n, verdadero, r1 = 1
int r2 = m >= 8;  // m igual a 8, cumple mayor o igual, r2 = 1
int r3 = m < n;   // m no es menor, falso, r3 = 0
int r4 = n <= 3;  // n igual a 3, cumple menor o igual, r4 = 1
int r5 = m == n;  // m distinto de n, r5 = 0
int r6 = m != n;  // m distinto de n, r6 = 1


(1) Comparaciones básicas

int alpha = 12, beta = 7;
printf("%d\n", alpha > beta);   // Mayor: 1 (verdadero)
printf("%d\n", alpha < beta);   // Menor: 0 (falso)
printf("%d\n", alpha >= beta);  // Mayor o igual: 1 (verdadero)
printf("%d\n", alpha <= beta);  // Menor o igual: 0 (falso)
printf("%d\n", alpha == beta);  // Igual: 0 (falso)
printf("%d\n", alpha != beta);  // Distinto: 1 (verdadero)


(2) Trampa de comparación flotante

float producto = 0.1f * 7;     // Valor teórico: 0.7
float referencia = 0.7f;
printf("%d\n", producto == referencia);  // Puede dar 0 (falso) por precisión flotante

// Forma correcta
printf("%d\n", fabs(producto - referencia) < 1e-6);  // Comparar con margen de error


(3) Comparación de punteros

int var1 = 15, var2 = 25;
int *dir1 = &var1, *dir2 = &var2;
printf("%d\n", dir1 > dir2);  // Compara direcciones (resultado depende de la memoria)


Importante: Para verificar igualdad usar ==, nunca = (asignación), lo cual causa errores lógicos.

7. Operadores Lógicos

(1) AND lógico (&&)

int p = 5, q = 0;
printf("%d\n", p && q);  // Salida: 0 (falso, porque q es 0)

// Cortocircuito
int a_val = 0, b_val = 10;
if (a_val == 0 && ++b_val) {    // Izquierda falsa, derecha no se ejecuta
    printf("No se imprime\n");
}
printf("b_val=%d\n", b_val);    // Salida: b_val=10 (no se incrementó)


(2) OR lógico (||)

int p = 5, q = 0;
printf("%d\n", p || q);  // Salida: 1 (verdadero, porque p no es 0)

// Cortocircuito
int c_val = 1, d_val = 10;
if (c_val == 1 || ++d_val) {    // Izquierda verdadera, derecha no se ejecuta
    printf("Se imprime\n");
}
printf("d_val=%d\n", d_val);    // Salida: d_val=10 (no se incrementó)


(3) NOT lógico (!)

int x_val = 10;
printf("%d\n", !x_val);     // Salida: 0 (falso, porque x_val no es 0)

int y_val = 0;
printf("%d\n", !y_val);     // Salida: 1 (verdadero, porque y_val es 0)


(4) AND lógico vs AND a nivel de bits

int s1 = 12;    // Binario: 0000 0000 0000 0000 0000 0000 0000 1100
int s2 = 10;    // Binario: 0000 0000 0000 0000 0000 0000 0000 1010

printf("%d\n", s1 && s2);  // AND lógico: 1 (verdadero, ambos no-cero)
printf("%d\n", s1 & s2);   // AND a nivel de bits: 8 (Binario: 0000 0000 0000 0000 0000 0000 0000 1000)


(5) OR lógico vs OR a nivel de bits

int t1 = 12;    // Binario: 0000 0000 0000 0000 0000 0000 0000 1100
int t2 = 0;     // Binario: 0000 0000 0000 0000 0000 0000 0000 0000

printf("%d\n", t1 || t2);  // OR lógico: 1 (verdadero, t1 no es cero)
printf("%d\n", t1 | t2);   // OR a nivel de bits: 12 (Binario: 0000 0000 0000 0000 0000 0000 0000 1100)


8. Operador Ternario (Expresión Condicional)

El operador ternario tiene la sintaxis: exp1 ? exp2 : exp3.

  • Funcionamiento: Se evalúa exp1. Si es verdadera (no-cero), el resultado es exp2; si es falsa (cero), el resultado es exp3.
  • Característica: Simplifica condicionales simples, aunque expresiones complejas en exp2 y exp3 reducen la legibilidad.
int primero = 10, segundo = 5;
int mayor = (primero > segundo) ? primero : segundo;  // Si primero>segundo, mayor=primero; sino mayor=segundo
printf("mayor=%d\n", mayor);    // Salida: mayor=10

// Uso anidado
int num_a = 10, num_b = 20, num_c = 30;
int maximo = (num_a > num_b) ? ((num_a > num_c) ? num_a : num_c) : ((num_b > num_c) ? num_b : num_c);
printf("maximo=%d\n", maximo);  // Salida: maximo=30


Nota:

El operador ternario produce un valor utilizable en asignaciones o como argumento

int salida = (primero > segundo) ? primero * 2 : segundo * 3;  // Asignación
printf("%d\n", (primero > segundo) ? primero : segundo);       // Como argumento


9. Expresión de Coma

La expresión de coma separa múltiples subexpresiones: exp1, exp2, …, expN.

  • Regla: Se ejecutan de izquierda a derecha y el valor de la expresión completa es el de la última subexpresión.
  • Aplicaciones:
  • En bucles for, para manejar múltiples variables en inicialización y actualización.
  • Cuando se requieren múltiples efectos secundarios y se necesita el resultado final.
int c1 = 1, c2 = 2, c3;
c3 = (c1++, c2++, c1 + c2);  // Ejecuta c1++, luego c2++, luego calcula c1+c2
printf("c1=%d, c2=%d, c3=%d\n", c1, c2, c3);  // Salida: c1=2, c2=3, c3=5

// Uso típico: múltiples variables en for
for (int idx = 0, jdx = 10; idx < jdx; idx++, jdx--) {
    printf("idx=%d, jdx=%d\n", idx, jdx);
}


Propiedades:

Ejecución secuencial de izquierda a derecha El valor total es el de la última subexpresión

int valor = (printf("X"), printf("Y"), 200);  // Imprime: XY, valor=200


10. Operadores de Subíndice y Acceso a Miembros

El operador de subíndice [] es binario: sus operandos son el arreglo y el índice, y soporta conmutatividad.

  • Equivalencia: arr[5] es idéntico a *(arr + 5). arr es la dirección base, arr + 5 apunta al sexto elemento, y * obtiene su valor.
  • Conmutatividad: arr[5] también se puede escribir como 5[arr].

(1) Subíndice ([])

int secuencia[5] = {10, 20, 30, 40, 50};
printf("%d\n", secuencia[2]);  // Salida: 30 (tercer elemento)

// Equivalente
printf("%d\n", *(secuencia + 2));  // Acceso por desplazamiento de puntero

// Conmutatividad (no recomendado estilísticamente)
printf("%d\n", 2[secuencia]);  // Equivalente a secuencia[2], ya que 2[secuencia] → *(2 + secuencia)


(2) Acceso a miembro de estructura (.)

struct Coordenada {
    int posX;
    int posY;
};

struct Coordenada punto = {15, 25};
printf("posX=%d, posY=%d\n", punto.posX, punto.posY);  // Salida: posX=15, posY=25


(3) Acceso a miembro mediante puntero a estructura (->)

struct Coordenada punto = {15, 25};
struct Coordenada *ref = &punto;
printf("posX=%d, posY=%d\n", ref->posX, ref->posY);  // Salida: posX=15, posY=25

// Equivalente
printf("posX=%d, posY=%d\n", (*ref).posX, (*ref).posY);  // Desreferencia y acceso con .


11. Evaluación de Expresiones

El orden de evaluación depende de la precedencia, la asociatividad y las conversiones implícitas.

(1) Conversiones implícitas

① Promoción entera

Las operaciones aritméticas enteras en C se realizan con al menos la precisión del tipo entero por defecto. Para lograrlo, los operandos de tipo char y short se convierten a int antes de operar. Esto se llama promoción entera.

  • Regla: Tipos menores que int (char, short) se promocionan automáticamente a int durante operaciones.
  • Propósito: Mejorar la precisión y evitar errores de truncamiento.

Significado de la promoción entera:

  La ALU de la CPU opera con palabras del tamaño de int, que coincide con los registros
  de propósito general. Incluso una suma de dos char requiere que primero se conviertan
  al tamaño estándar de int de la CPU. Las CPUs de propósito general difícilmente
  pueden sumar directamente dos bytes de 8 bits. Por eso, los valores enteros menores
  que int deben convertirse a int o unsigned int antes de enviarse a la ALU.


Ejemplo:

char ch1 = 5;     // 8 bits: 0000 0101
char ch2 = 127;   // 8 bits: 0111 1111
char ch3 = ch1 + ch2; // Antes de sumar, ch1 y ch2 se promocionan a int: 0000 0000 0000 0101 y 0000 0000 0111 1111
                      // Suma: 0000 0000 1000 0100 (132)
                      // Al asignar a ch3 se trunca a 8 bits: 1000 0100 (complemento a dos, decimal: -124)
printf("%d\n", ch3);  // Salida: -124


**¿Cómo se realiza la promoción entera?**La promoción rellena los bits superiores según el bit de signo del tipo original.

// Promoción de número negativo
char s1 = -1;
// Bits del complemento a dos de s1 (8 bits):
11111111
// Como char es con signo, se rellena con el bit de signo (1)
// Resultado tras promoción:
11111111111111111111111111111111
// Promoción de número positivo
char s2 = 1;
// Bits del complemento a dos de s2 (8 bits):
00000001
// Como char es con signo, se rellena con 0
// Resultado tras promoción:
00000000000000000000000000000001
// Para unsigned char, siempre se rellena con 0


Ejemplos de promoción entera:

//Caso 1
int main()
{
 char val1 = 0xb6;
 short val2 = 0xb600;
 int val3 = 0xb6000000;
 if(val1==0xb6)
 printf("val1");
 if(val2==0xb600)
 printf("val2");
 if(val3==0xb6000000)
 printf("val3");
 return 0;
}


En el Caso 1, val1 y val2 sufren promoción entera, pero val3 no. Tras la promoción, val1 y val2 se vuelven negativos, por lo que las comparaciones val1 == 0xb6 y val2 == 0xb600 son falsas. val3 no cambia, y val3 == 0xb6000000 es verdadero.

Salida del programa:

val3


 //Caso 2
int main()
{
 char letra = 1;
 printf("%u\n", sizeof(letra));
 printf("%u\n", sizeof(+letra));
 printf("%u\n", sizeof(-letra));
 return 0;
}


En el Caso 2, al participar en una expresión, letra se promociona. +letra sufre promoción, así sizeof(+letra) es 4 bytes. -letra también se promociona, sizeof(-letra) es 4 bytes, pero sizeof(letra) sigue siendo 1 byte.

② Conversión aritmética

Si los operandos de un operador tienen tipos distintos, uno debe convertirse al tipo del otro. Esta jerarquía se conoce como conversiones aritméticas habituales.

  • Regla: En operaciones entre tipos distintos, el tipo de menor precisión se convierte al de mayor precisión.
  • Jerarquía: char → short → int → unsigned int → long → unsigned long → float → double → long double

Ejemplo:

int ent = 15;
double dec = 2.718;
double res = ent + dec;  // ent se convierte a double (15.0), luego se suma con dec


  • Razón: Los distintos tipos tienen diferentes representaciones y precisiones en memoria. Unificar tipos evita pérdida de información o errores de cálculo.
  • Significado: Garantiza que las operaciones entre tipos diferentes preserven la mayor cantidad de información posible.

El tipo de menor rango en la lista se convierte primero al del otro operando. **Advertencia:**Las conversiones aritméticas deben ser razonables; conversiones inapropiadas causan problemas.

double real = 3.14;
int truncado = real;//Conversión implícita, pérdida de precisión


12. Atributos de los Operadores

La evaluación de expresiones complejas depende de tres factores:

  • Precedencia de operadores
  • Asociatividad de operadores
  • Si el operador controla el orden de evaluación
  • Precedencia: Determina qué operador se evalúa primero cuando hay varios. Mayor precedencia implica evaluación prioritaria.
  • Ejemplo:
   int calc1 = 4 + 6 * 3; // * tiene mayor precedencia que +, primero 6*3=18, luego 4+18=22, calc1 = 22
   int calc2 = (4 + 6) * 3; // Paréntesis tienen máxima precedencia, primero 4+6=10, luego 10*3=30, calc2 = 30


  • Control del orden de evaluación: Algunos operadores definen un orden fijo (como &&, ||, coma), mientras que otros (+, -, *, /) no especifican el orden de evaluación de sus operandos.
  • Ejemplo:
int var_h = 0, var_i = 5;
int var_j = var_h++ && ++var_i; // && tiene orden definido: var_h++ (var_h=1, valor=0), izquierda falsa, ++var_i no se ejecuta, var_j = 0
int var_k = (var_h = 3, var_i = 4, var_h + var_i); // Coma: izquierda a derecha, var_k = 7
int var_m = funcion1() + funcion2(); // + no define orden: no se sabe si funcion1 o funcion2 se llama primero


Entre dos operadores adyacentes, se evalúa primero el de mayor precedencia. Si tienen igual precedencia, la asociatividad determina el orden. En a + b * c, * tiene mayor precedencia que +; en a / b * c, / y * tienen igual precedencia y asociatividad izquierda, así que se calcula a / b primero.

① Tabla de precedencia
Operador Descripción Ejemplo de uso Tipo resultado Asociatividad (L-R=izq. a der.) Controla orden
() Agrupación (expresión) Igual que la exp N/A No
() Llamada a función rexp(rexp,…,rexp) rexp L-R No
[ ] Subíndice rexp[rexp] lexp L-R No
. Acceso a miembro lexp.nombre lexp L-R No
-> Acceso a miembro por puntero rexp->nombre lexp L-R No
++ Post-incremento lexp ++ rexp L-R No
– Post-decremento lexp – rexp L-R No
! Negación lógica ! rexp rexp R-L No
~ Negación a nivel de bits ~ rexp rexp R-L No
+ Unario, valor positivo + rexp rexp R-L No
- Unario, valor negativo - rexp rexp R-L No
++ Pre-incremento ++ lexp rexp R-L No
– Pre-decremento – lexp rexp R-L No
* Desreferencia * rexp lexp R-L No
& Dirección & lexp rexp R-L No
sizeof Tamaño en bytes sizeof rexp, sizeof(tipo) rexp R-L No
(tipo) Conversión de tipo (tipo) rexp rexp R-L No
* Multiplicación rexp * rexp rexp L-R No
/ División rexp / rexp rexp L-R No
% Módulo entero rexp % rexp rexp L-R No
+ Suma rexp + rexp rexp L-R No
- Resta rexp - rexp rexp L-R No
<< Desplazamiento izq. rexp << rexp rexp L-R No
>> Desplazamiento der. rexp >> rexp rexp L-R No
> Mayor que rexp > rexp rexp L-R No
>= Mayor o igual rexp >= rexp rexp L-R No
< Menor que rexp < rexp rexp L-R No
<= Menor o igual rexp <= rexp rexp L-R No
== Igualdad rexp == rexp rexp L-R No
!= Desigualdad rexp != rexp rexp L-R No
& AND a nivel de bits rexp & rexp rexp L-R No
^ XOR a nivel de bits rexp ^ rexp rexp L-R No
| OR a nivel de bits rexp | rexp rexp L-R No
&& AND lógico rexp && rexp rexp L-R Sí
|| OR lógico rexp || rexp rexp L-R Sí
? : Condicional rexp ? rexp : rexp rexp N/A Sí
= Asignación lexp = rexp rexp R-L No
+= Asignación con suma lexp += rexp rexp R-L No
-= Asignación con resta lexp -= rexp rexp R-L No
*= Asignación con multiplicación lexp *= rexp rexp R-L No
/= Asignación con división lexp /= rexp rexp R-L No
%= Asignación con módulo lexp %= rexp rexp R-L No
<<= Asignación con desp. izq. lexp <<= rexp rexp R-L No
>>= Asignación con desp. der. lexp >>= rexp rexp R-L No
&= Asignación con AND lexp &= rexp rexp R-L No
^= Asignación con XOR lexp ^= rexp rexp R-L No
|= Asignación con OR lexp |= rexp rexp R-L No
, Coma rexp,rexp rexp L-R Sí
② Análisis de ejemplos

Ejemplo 1:

int op1 = 3, op2 = 5, op3 = 7;
int calc_res = op1 + op2 * op3;  // Primero multiplicación: 3 + (5*7) = 38
int calc_x = op1 * op2 / op3;   // Misma precedencia, izquierda a derecha: (3*5)/7 = 2
int calc_y = op1 << op2 + op3;  // Suma antes que desplazamiento: 3 << (5+7) = 3 << 12 = 12288


(3) Efectos secundarios y orden de evaluación

int var_x = 1;
int var_y = (var_x++) + (var_x++);  // ¡Comportamiento indefinido! Resultados varían entre compiladores

// Posibles cálculos:
// 1. Primer var_x++ (valor 1), luego segundo var_x++ (valor 2), resultado: 1+2=3
// 2. Ambos var_x++ (valor 1), luego suma, resultado: 1+1=2


Recomendación:

Evitar modificar la misma variable múltiples veces en una expresión Separar en instrucciones distintas con orden claro

// Escritura segura
int var_x = 1;
int temp_a = var_x++;  // temp_a=1, var_x=2
int temp_b = var_x++;  // temp_b=2, var_x=3
int calc_result = temp_a + temp_b;  // calc_result=3


Ejemplo 2:

//El orden de evaluación depende de la precedencia.
p*q + r*s + t*u


Dado que * tiene mayor precedencia que +, solo se garantiza que * se calcula antes que +. Sin embargo, la precedencia no determina si el tercer * se calcula antes que el primer +.

Posibles órdenes de evaluación:

p*q
r*s
p*q + r*s
t*u
p*q + r*s + t*u
O bien:
p*q
r*s
t*u
p*q + r*s
p*q + r*s + t*u


Ejemplo 3:

int contador_func()
{
     static int cuenta = 1;
     return ++cuenta;
}
int main()
{
     int respuesta;
     respuesta = contador_func() - contador_func() * contador_func();
     printf( "%d\n", respuesta);
     return 0;
}


¿Problema en este código? **¡Sí!**Aunque muchos compiladores producen el mismo resultado, en answer = contador_func() - contador_func() * contador_func(); la precedencia indica que la multiplicación va primero y luego la resta. Pero el orden de llamada a las funciones no está determinado por la precedencia de operadores.

13. Resumen

Tipo de operador Punto clave Precauciones/Trampas
Aritméticos División entera trunca decimales, módulo solo con enteros Evitar división por cero, atención al módulo con negativos
Desplazamiento Izquierda rellena con 0, derecha puede ser aritmético/lógico, opera sobre complemento a dos No desplazar cantidades negativas (indefinido)
A nivel de bits Operan bit a bit (&|^), XOR es conmutativo y autorreflexivo Distinguir de operadores lógicos
Asignación Soporta asignación compuesta (+=, -=, etc.), encadenada de derecha a izquierda Evitar encadenamiento complejo, reduce legibilidad
Unarios sizeof devuelve tamaño, ++/-- difieren en pre/post Pre-++ devuelve valor incrementado, post devuelve original
Relacionales Resultado 0 o 1, comparación flotante requiere margen de error No usar == directamente con flotantes
Lógicos && y || tienen cortocircuito, distinguir de operadores bit a bit Aprovechar cortocircuito para evitar cálculos innecesarios
Ternario Expresión condicional concisa, el resultado es un valor utilizable Evitar anidamiento profundo
Coma Ejecución izquierda a derecha, resultado de la última subexpresión Común en inicialización múltiple de bucles for
Subíndice y miembros arr[i] ≡ *(arr+i), -> para punteros a estructura Punteros a estructura requieren ->
Evaluación de expresiones Depende de precedencia, asociatividad y conversiones; atención a promoción y conversión aritmética No modificar la misma variable varias veces en una expresión

Etiquetas: C operadores complemento-a-dos bitwise precedencia

Publicado el 10-27 04:38