Operadores y expresiones
Los símbolos con los que se combinan los datos: aritméticos, relacionales, lógicos y de asignación, el orden en que se aplican, y la confusión entre el igual que asigna y el igual que compara.
Qué es una expresión y qué es un operador
Guardar datos en variables no sirve de nada si no se puede operar con ellos. Una expresión es cualquier combinación de datos y símbolos que, al calcularse, produce un valor. El valor 34 es una expresión; aciertos + 1 también; y nota >= 5 también, solo que esta última produce un valor booleano en vez de un número.
Los símbolos que combinan los datos son los operadores, y los datos sobre los que actúan son los operandos. La mayoría de operadores son binarios, es decir, necesitan dos operandos, uno a cada lado (el más en a + b). Algunos son unarios y actúan sobre uno solo, como el menos que cambia el signo o la negación lógica.
Lo que hay que retener de este apartado es que toda expresión acaba reduciéndose a un único valor, y que el tipo de ese valor no tiene por qué coincidir con el de los operandos. Comparar dos números no produce un número, produce un booleano, y esa es la razón de que las comparaciones puedan meterse dentro de un if.
Operandos son los datos, operadores los símbolos que los combinan, y expresión el conjunto, que siempre se reduce a un valor único. Los operadores relacionales y lógicos producen booleanos aunque trabajen con números.
Para el examen
Expresión: todo lo que se reduce a un único valor
Operadores relacionales y lógicos: producen booleanos aunque operen con números
Por eso: caben dentro de un if
Operadores aritméticos
Son los que operan con números y producen números. Los cuatro primeros no necesitan presentación; los dos últimos son los que hay que aprender, y aparecen en casi cualquier ejercicio de bucles.
| Operador | Qué hace | Ejemplo | Resultado |
|---|---|---|---|
| + | Suma | 34 + 16 | 50 |
| - | Resta | 50 - 16 | 34 |
| * | Multiplicación | 5 * 20 | 100 |
| / | División | 7.0 / 2.0 | 3.5 |
| / entre enteros | División entera: descarta la parte decimal | 7 / 2 | 3 |
| % | Módulo o resto de la división entera | 7 % 2 | 1 |
El módulo devuelve lo que sobra al dividir. Parece poco útil hasta que se ve para qué se usa: comprobar si un número es par (basta con que su resto entre 2 sea cero), repartir elementos en grupos, o hacer algo cada cierto número de vueltas de un bucle.
entero preguntas = 100
entero porTanda = 8
entero tandasCompletas = preguntas / porTanda // 12
entero sobrantes = preguntas % porTanda // 4
// 100 preguntas en tandas de 8 dan 12 tandas completas y sobran 4.
if (preguntas % 2 == 0) {
escribir("El numero de preguntas es par")
}Las dos primeras líneas del cálculo son la misma división mirada de dos formas: la barra da cuántas veces cabe el divisor entero y el porcentaje da lo que queda fuera. Y el if final es la forma estándar de preguntar si un número es par, porque un número es par exactamente cuando el resto de dividirlo entre dos es cero.
Entre enteros, la barra no redondea: trunca. 7 / 2 da 3 y no 3.5, y 7 % 2 da 1. Si se quiere el resultado decimal, alguno de los dos operandos tiene que ser real.
Para el examen
División entre enteros: trunca: 7 / 2 = 3
Módulo: da el resto: 7 % 2 = 1
Comprobar si un número es par: n % 2 == 0
Operadores relacionales, y la confusión entre = y ==
Los operadores relacionales o de comparación toman dos valores y devuelven un booleano: verdadero o falso. Son la materia prima de los condicionales y de los bucles, porque son los que producen la respuesta que esas estructuras consultan.
| Operador | Pregunta que hace | Ejemplo | Resultado |
|---|---|---|---|
| == | ¿Son iguales? | nota == 5 | verdadero si vale exactamente 5 |
| != | ¿Son distintos? | nota != 5 | verdadero si no vale 5 |
| > | ¿Es mayor? | nota > 5 | falso si vale exactamente 5 |
| < | ¿Es menor? | nota < 5 | falso si vale exactamente 5 |
| >= | ¿Es mayor o igual? | nota >= 5 | verdadero si vale 5 |
| <= | ¿Es menor o igual? | nota <= 5 | verdadero si vale 5 |
Y aquí está el error número uno de todo el que empieza a programar. En la mayoría de lenguajes el signo igual solo, =, no compara nada: asigna. Para comparar hacen falta dos, ==. Se parecen en la pantalla y hacen cosas completamente distintas.
entero nota = 3
if (nota == 5) { ... } // COMPARA: ¿vale 5? Aqui es falso, y no se entra.
if (nota = 5) { ... } // ASIGNA: mete un 5 en «nota» y despues evalua
// el resultado. La variable ha cambiado sin querer
// y ademas el if puede darse por cierto.El segundo if hace dos destrozos a la vez: modifica una variable que solo se quería consultar y decide el camino a seguir según un valor que se acaba de inventar. Muchos lenguajes modernos rechazan directamente esa construcción, pero otros como C la aceptan sin rechistar, y ahí el fallo es silencioso y muy difícil de encontrar.
Dos advertencias más sobre las comparaciones. La primera: en muchos lenguajes el operador == aplicado a cadenas de texto no compara el contenido sino si son el mismo objeto en memoria, y para comparar textos hay un método específico. La segunda: comparar dos reales con == es mala idea, por la falta de precisión de la coma flotante.
Un igual asigna y dos iguales comparan. Es el fallo más repetido de quien empieza y una pregunta habitual de examen.
Para el examen
Un igual (=): asigna
Dos iguales (==): comparan
El fallo típico: if (nota = 5) mete un 5 en la variable sin avisar
Operadores lógicos
Los operadores lógicos combinan valores booleanos para construir condiciones compuestas. Sirven para expresar cosas como «si el alumno ha aprobado y además ha terminado el tema» sin escribir dos condicionales anidados.
| Operador | Nombre | Es verdadero cuando |
|---|---|---|
| && (AND) | Conjunción, «y» | Los dos operandos son verdaderos. Basta que uno falle para que todo sea falso |
| || (OR) | Disyunción, «o» | Al menos uno de los dos es verdadero. Solo es falso si fallan los dos |
| ! (NOT) | Negación, «no» | Su único operando es falso. Da la vuelta al valor |
booleano temaLeido = true
booleano testAprobado = false
temaLeido && testAprobado // false: falla el segundo
temaLeido || testAprobado // true : basta con que uno sea cierto
!testAprobado // true : da la vuelta al false
// Condicion compuesta de verdad:
if (nota >= 5 && faltas <= 3) {
escribir("Tema superado")
}El if final se lee de dentro afuera. Primero se resuelven las dos comparaciones por separado, y cada una produce un booleano. Después el operador && une esos dos booleanos, y solo si los dos son verdaderos se entra en el bloque. Con || bastaría con que se cumpliera uno.
El error habitual aquí es escribir la condición como se dice en castellano. «Si la nota es mayor que 5 o que 7» no se traduce como nota > 5 || 7, porque el 7 suelto no es una comparación. Hay que repetir la variable en cada lado: nota > 5 || nota > 7.
El AND exige que se cumplan todas las condiciones y el OR se conforma con una. Cada lado de un operador lógico tiene que ser una condición completa por sí mismo.
Para el examen
AND (&&): verdadero solo si lo son los dos lados
OR (||): falso solo si fallan los dos
NOT (!): invierte
Regla de escritura: cada lado debe ser una condición completa: nota > 5 || nota > 7
Operadores de asignación
El operador de asignación básico es el igual simple, que guarda en la variable de la izquierda el valor calculado a la derecha. A partir de él, casi todos los lenguajes ofrecen versiones abreviadas para el caso frecuentísimo de operar una variable consigo misma.
| Forma abreviada | Equivale a | Efecto si aciertos vale 10 |
|---|---|---|
| aciertos += 5 | aciertos = aciertos + 5 | pasa a valer 15 |
| aciertos -= 5 | aciertos = aciertos - 5 | pasa a valer 5 |
| aciertos *= 2 | aciertos = aciertos * 2 | pasa a valer 20 |
| aciertos /= 2 | aciertos = aciertos / 2 | pasa a valer 5 |
| aciertos++ | aciertos = aciertos + 1 | pasa a valer 11 |
| aciertos-- | aciertos = aciertos - 1 | pasa a valer 9 |
Los dos últimos, el incremento y el decremento, son los que más se ven, porque avanzar un contador de uno en uno es lo que hace prácticamente cualquier bucle. No añaden nada nuevo: son solo una forma más corta de escribir la suma de uno.
entero aciertos = 0
aciertos++ // 1 ha respondido bien una pregunta
aciertos++ // 2
aciertos += 3 // 5 corrige de golpe tres mas
aciertos-- // 4 se anula una pregunta
escribir(aciertos) // 4Las formas abreviadas no son operadores nuevos: son atajos de escritura de la asignación de siempre. La variable tiene que existir ya, porque su valor anterior interviene en el cálculo.
Para el examen
aciertos += 3: equivale a aciertos = aciertos + 3
aciertos++: suma uno
Qué son: atajos de la asignación, no operadores nuevos
Precedencia: en qué orden se resuelve una expresión
Cuando una expresión mezcla varios operadores, el resultado depende del orden en que se apliquen. Ese orden no se improvisa: cada operador tiene asignada una prioridad, la precedencia, y el lenguaje resuelve primero los de prioridad más alta. Es la misma idea de la aritmética escolar, donde la multiplicación va antes que la suma, ampliada al resto de familias de operadores.
| Prioridad | Operadores | Familia |
|---|---|---|
| 1 (la más alta) | ( ) | Paréntesis |
| 2 | ! y el menos unario | Unarios |
| 3 | * / % | Aritméticos multiplicativos |
| 4 | + - | Aritméticos aditivos |
| 5 | < <= > >= | Relacionales de orden |
| 6 | == != | Relacionales de igualdad |
| 7 | && | Conjunción lógica |
| 8 | || | Disyunción lógica |
| 9 (la más baja) | = += -= *= /= | Asignación |
La consecuencia práctica de esa tabla es que los aritméticos se resuelven antes que los relacionales, y los relacionales antes que los lógicos, y la asignación siempre la última. Por eso una condición como nota >= 5 && faltas <= 3 funciona sin paréntesis: las dos comparaciones se resuelven primero y el && actúa después sobre sus resultados.
entero resultado = 2 + 3 * 4
// Paso 1: se resuelve la multiplicacion -> 2 + 12
// Paso 2: se resuelve la suma -> 14
entero conParentesis = (2 + 3) * 4
// Paso 1: el parentesis manda sobre todo -> 5 * 4
// Paso 2: se multiplica -> 20Los paréntesis están por encima de todo lo demás y son la forma de imponer el orden que uno quiera. La recomendación práctica es usarlos siempre que la expresión no se lea de un vistazo, aunque técnicamente no hagan falta: una expresión con paréntesis de más se entiende, y una sin ellos se malinterpreta.
El orden general es paréntesis, unarios, aritméticos, relacionales, lógicos y por último la asignación. Los paréntesis son la única forma de saltárselo.
Para el examen
El orden: paréntesis, unarios, aritméticos, relacionales, lógicos y por último la asignación
Dentro de los aritméticos: * / % antes que + -
Dentro de los lógicos: && antes que ||
Ejemplo: 2 + 3 * 4 da 14, no 20
Asociatividad, cortocircuito e incremento prefijo
La precedencia decide qué familia de operadores va antes, pero no resuelve el empate entre operadores de la misma prioridad. De eso se ocupa la asociatividad. Casi todos los operadores son asociativos por la izquierda, es decir, se aplican de izquierda a derecha: en 100 / 5 / 2 se divide primero 100 entre 5 y el resultado entre 2, con lo que sale 10 y no 40.
La excepción notable es la asignación, que asocia por la derecha. En a = b = 0 se asigna primero el cero a b, y el valor resultante de esa asignación se asigna después a a. Los operadores unarios y el condicional ternario también asocian por la derecha.
El segundo detalle fino es la evaluación en cortocircuito de los operadores lógicos. Si el operando izquierdo de un && ya es falso, el resultado global será falso pase lo que pase, así que el lenguaje ni siquiera evalúa el derecho. Con el || ocurre lo simétrico: si el izquierdo es verdadero, el derecho se salta.
// El cortocircuito no es solo optimizacion: se usa para protegerse.
if (totalPreguntas != 0 && aciertos / totalPreguntas > 0.5) { ... }
// Si totalPreguntas vale 0, la primera parte ya es falsa y la division
// NUNCA llega a ejecutarse. Invertir el orden de las dos condiciones
// provocaria una division entre cero.El tercer detalle es la diferencia entre el incremento prefijo y el posfijo. Los dos suman uno a la variable, y si la instrucción no hace nada más son idénticos. La diferencia aparece cuando el incremento va dentro de una expresión mayor: con ++i primero se incrementa y después se usa el valor nuevo; con i++ primero se usa el valor viejo y después se incrementa.
entero i = 5
entero a = ++i // i pasa a 6, y a recibe 6
entero j = 5
entero b = j++ // b recibe 5, y solo despues j pasa a 6En el cortocircuito, el operando derecho puede no evaluarse nunca, así que el orden de las condiciones importa cuando la segunda solo es segura si la primera se cumple.
Para el examen
Asociatividad general: por la izquierda: 100 / 5 / 2 = 10
Excepción: la asignación asocia por la derecha
Cortocircuito: si el && ya es falso a la izquierda, el lado derecho no se evalúa
Prefijo frente a sufijo: b = i++ usa el valor viejo; b = ++i, el nuevo