OPERADORES LÓGICOS Y RELACIONALES.







Tabla de Operadores Lógicos (estándar y bit a bit)
La tabla de operadores lógicos estándar en C es la siguiente:


 && Conjunción lógica
|| Disyunción lógica
! Negación lógica


Estos operadores admiten operandos lógicos, esto es, de valor verdadero o falso según el criterio habitual en C (nulo es falso, no nulo es verdadero). No se puede afirmar nada acerca de la estructura de bits del resultado; solo se sabe que será falso o verdadero en los términos anteriores.

Las expresiones lógicas se evalúan de izquierda a derecha (esto es, primero se evalúa el operando de la izquierda, luego el de la derecha y finalmente se aplica el operador lógico).

Los operadores lógicos && y || de C poseen una propiedad interesante: son operadores de cortocircuito ; esto significa que en expresiones del tipo A && B y A || B sólo se evaluará B en caso de necesidad. Ténagase en cuenta que una expresión de conjunción lógica ( && , AND) es falsa en cuanto uno de sus operandos sea falso, independientemente del valor verdadero o falso del otro operando. Por otra parte, en una expresión de disyunción lógica ( || , OR), si un operando es verdadero, entonces la expresión es verdadera, independientemente del valor verdadero o falso del otro operando. Entonces, si se evalúa el primer operando de una conjunción, y es falso, la expresión es falsa. Si se evalúa el primer operando de una disyunción, y es verdadero, la expresión es verdadera. Cuando C se encuentra con una de estas situaciones, en las que ya es conocido el valor de la expresión sin evaluar el segundo operando, no evalúa el segundo operando y proporciona el valor (correcto, ciertamente) de la expresión. Esto se hace porque la evaluación del segundo operando requiere un cierto tiempo, que se ahorra de esta manera. Por tanto, al construir expresiones de conjunción o disyunción, se optimizan los tiempos de ejecución si se pone en primer lugar la condición de evaluación más rápida. Así se reduce al mínimo la cantidad de cálculos realizada.

Adicionalmente, C ofrece operadores lógicos bit a bit. Téngase en cuenta que estos operadores llevan a cabo operaciones bit a bit, y que solo admiten como argumentos los de tipos ordinales (caracteres y enteros). El resultado está formado por el resultado de aplicar estos operadores a parejas de bits homólogos individuales en sus operandos. Los operadores binarios bit a bit no son operadores de cortocircuito.

 & Conjunción lógica, AND
| Disyunción lógica, OR
^ Disyunción excluyente, XOR
~ Negación lógica, NOT
>> Desplazamiento hacia la derecha
<< Desplazamiento hacia la izquierda


Características de los distintos operadores

 & 0 1  El operador & (AND) aplica a los bits homólogos de sus operandos la conocida tabla de conjunción lógica, caracterizada porque el resultado solo es 1 cuando ambos bits valen 1; en todos los demás casos el resultado es 0.
 1  0 1
0 0 0
 | 0 1  El operador | (OR) aplica a los bits homólogos de sus operandos la tabla de disyunción lógica, caracterizada porque el resultado es 1 cuando uno o los dos bits valen 1; en los demás casos el resultado es 0.
 1  1 1
0 0 1
 ^ 0 1  El operador ^ (XOR) aplica a los bits homólogos de sus operandos la tabla de conjunción lógica excluyente, caracterizada porque el resultado es 1 cuando solo uno de los bits bits es 1; en todos los demás casos el resultado es 0.
 1  1 0
0 0 1
 ~  El operador ~ (NOT) invierte el valor de sus operandos, transformando el valor 0 en 1 y el valor 1 en 0. Esto se hace bit a bit!
 1 0
0 1


Operadores de desplazamiento

Los operadores ">>" y "<<" poseen dos operandos, ambos de tipo ordinal (carácter o entero). Al aplicar este operador, los bits del primer operando se desplazan tantas posiciones como indique el segundo operando. A efectos numéricos, se trata de dividir por dos (desplazamiento hacia la derecha) o de multiplicar por dos (desplazamiento hacia la izquierda). El primer operando no se ve afectado, aunque puede asignarse el resultado de la operación al primer operando. Los bits que "entran" por la derecha o por la izquierda son siempre ceros, esto es, no hay rotación de bits. Véase el oportuno Ejercicio .

El tipo Boolean
El tipo predefinido que se emplea en C es normalmente el char , con la convención consistente en que se considera "falso" el valor numérico 0 y "verdadero" cualquier valor numérico distinto de cero. Esto significa que los operadores lógicos producen el 0 para indicar el valor falso, y cualquier otro valor (que no tiene por qué ser 1, ni tampoco positivo) para representar el valor verdadero.

Ejemplo
#include<stdio.h>

int main(void)
{
        char verdadero = -34;
        char falso = 0;
        if (verdadero)
                printf("verdadero es \"verdadero\" y vale %d\n", verdadero);
        if (falso)
                printf("Algo ha fallado!\n");
        else
                printf("falso es \"falso\" y vale %d\n", falso);
        return 0;
}

El resultado de ejecutar este programa es el siguiente:

[cauldron:COTIC]> ./a.out 
verdadero es "verdadero" y vale -34
falso es "falso" y vale 0

El resultado de evaluar 1 < 2 es 1, esto es, verdadero

El resultado de evaluar 1 > 2 es 0, esto es, falso
[cauldron:COTIC]>

Como puede observarse, se interpreta como verdadero cualquier valor no nulo. Casualmente el operador de comparación (" > " y " < ") ha producido un 1 para denotar el valor verdadero, pero esto es solamente una casualidad; el estándar de C no lo indica.

Tabla de Operadores Relacionales.
La tabla de operadores relacionales del lenguaje C es la siguiente.


 > Mayor
>= Mayor o igual
< Menor
<= Menor o igual
== Igual
!= Distinto


Todos los operadores relacionales admiten como operandos los que pertenezcan a tipos atómicos, incluyendo las enumeraciones. El resultado de los operadores relacionales es siempre lógico, esto es, un valor verdadero (no nulo) o falso (nulo). La comparación de caracteres sigue la secuencia definida por el código ASCII. Téngase en cuenta que el código ASCII no ordena correctamente las palabras en Español, Francés, Alemán o cualquier otro idioma que posea signos diacríticos. La comparación de cadenas no puede realizarse en empleando los operadores relacionales habituales . Las cadenas son un caso particular de listas, y al comparar el nombre de dos cadenas mediante los operadores == o != se comparan las direcciones en que comienzan ambas. Como quiera que dos cadenas iguales van a tener generalmente distinta dirección, el resultado no va a ser correcto. Para comparar dos cadenas debe emplearse una función como strcmp() o strncmp() , definidas ambas en string.h .

Ejemplo

#include <stdio.h>
#include <string.h>

int main(void)
{
  char nombre_uno[] = "Alicante";
  char nombre_dos[] = "Barcelona";
  char nombre_tres[] = "Barcelona";
  
  printf("\n\nFunciones de comparación de cadenas\n\n");
  
  printf("nombre_uno = %s\n", nombre_uno);
  printf("nombre_dos = %s\n", nombre_dos);
  printf("nombre_tres = %s\n", nombre_tres);
  printf("\n\n");
  printf("nombre_uno == nombre_dos = %d\n", nombre_uno == nombre_dos);
  printf("Esto tiene sentido porque %p != %p\n", nombre_uno, nombre_dos);
  printf("\n\n");
  printf("nombre_uno == nombre_tres = %d\n", nombre_uno == nombre_tres);
  printf("Esto tiene sentido porque %p != %p\n", nombre_uno, nombre_tres);
  printf("\n\n");
  printf("nombre_dos == nombre_tres = %d\n", nombre_dos == nombre_tres);
  printf("Esto tiene sentido porque %p != %p\n", nombre_dos, nombre_tres);
  printf("\n\n");
  printf("strcmp(nombre_uno, nombre_dos) = %d\n", strcmp(nombre_uno, nombre_dos));
  printf("strcmp(nombre_uno, nombre_tres) = %d\n", strcmp(nombre_uno, nombre_tres));
  printf("strcmp(nombre_dos, nombre_tres) = %d\n", strcmp(nombre_dos, nombre_tres));
  return 0;
}


Y el resultado de ejecutar este programa es como sigue:
[cauldron:03xx_PROGS]> ./a.out 


Funciones de comparación de cadenas

nombre_uno = Alicante
nombre_dos = Barcelona
nombre_tres = Barcelona


nombre_uno == nombre_dos = 0
Esto tiene sentido porque 0xbffffc00 != 0xbffffc10


nombre_uno == nombre_tres = 0
Esto tiene sentido porque 0xbffffc00 != 0xbffffc20


nombre_dos == nombre_tres = 0
Esto tiene sentido porque 0xbffffc10 != 0xbffffc20


strcmp(nombre_uno, nombre_dos) = -1
strcmp(nombre_uno, nombre_tres) = -1
strcmp(nombre_dos, nombre_tres) = 0
[cauldron:03xx_PROGS]> 

Los especificadores de formato "%d" , "%s" y "%p" están descritos en la sección oportuna, que puede encontrarse aquí . Los resultados de aplicar el operador de comparación "==" a una pareja de cadenas son los que se obtienen al comparar numéricamente las direcciones de comienzo de los operandos (las dos cadenas). Como nombre_uno , nombre_dos y nombre_tres son variables distintas, tienen direcciones de comienzo distintas; aunque su contenido sea el mismo, como es el caso de nombre_dos y nombre_tres , su dirección es diferente, y la comparación mediante "==" produce un resultado falso, porque las direcciones no son iguales.
Cuando se hace uso de la función strcmp() , se obtiene un resultado más acorde con lo esperable. En efecto, esta función (véanse los resultados de escribir " man strcmp " en una shell) produce un resultado negativo, nulo o positivo según la primera cadena sea menor, igual o mayor que la segunda, desde un punto de vista lexicográfico. En nuestro caso, "Alicante" es menor que "Barcelona", luego el resultado de esa comparación es -1. Cuando se comparan nombre_dos y nombre_tres , el resultado es 0, porque son iguales.

Ejercicio .- Construir un programa capaz de admitir un número entero para después desplazar sucesivamente sus bits. El programa mostrará el resultado del desplazamiento, escribiendo en pantalla el valor númerico asociado.
#include <stdio.h>
/*
  Este programa muestra el resultado de desplazar bits.
*/
void main(void)
{
  unsigned char unByte = 1;
  printf("Desplazamiento de bits\n\n");
  printf("Valor inicial: %d\n\n", unByte);
  unByte = unByte << 1;
  printf("%3d\n", unByte);
  unByte = unByte << 1;
  printf("%3d\n", unByte);
  unByte = unByte << 1;
  printf("%3d\n", unByte);
  unByte = unByte << 1;
  printf("%3d\n", unByte);
  unByte = unByte << 1;
  printf("%3d\n", unByte);
  unByte = unByte << 1;
  printf("%3d\n", unByte);
  unByte = unByte << 1;
  printf("%3d\n", unByte);
  unByte = unByte << 1;
  printf("%3d\n", unByte);
  printf("\n\nTerminación normal del programa.\n");
}
/*
Desplazamiento de bits
Valor inicial: 1
  2
  4
  8
 16
 32
 64
128
  0
Terminación normal del programa.

*/
Comentarios .- Obsérvese que el último valor es cero. Esto se debe a que se han ido introduciendo ceros por la derecha; en el último desplazamiento, el 1 del octavo bit sale y es sustituido por el cero que había en el séptimo bit. También podría comenzar el ejercicio con el valor 128, efectuándose desplazamientos a la derecha. Téngase en cuenta que el número de bits de desplazamiento puede no ser 1: a efectos, esto permite estudiar (sin modificar) el valor de cualquiera de los bits que aparezcan en el primer operando. ¿Sabría el lector aplicar esta idea para estudiar la representación interna de un número real, tal como se hacía con una unión? Una solución puede verse aquí .

Ejercicios propuestos



  1. Ejercicio 0302r01.- Construir un programa que admita dos enteros A y B. El programa debe mostrar el valor numérico de A < B, A <= B, A == B, A > B y A >= B.

  2. Ejercicio 0302r02.- Escribir un programa que admita tres números y los muestre por orden ascendente.

  3. Ejercicio 0302r03.- Escribir un programa que admita tres números y los muestre por orden ascendente. Emplear las funciones sort y system de Unix.

  4. Ejercicio 0302r04.- Escribir un programa que admita tres números y los muestre por orden ascendente. Emplear la función qsort de Unix.

  5. Ejercicio 0302r05.- Escribir un programa que admita tres nombres y los muestre por orden ascendente.

  6. Ejercicio 0302r06.- Escribir un programa que admita tres nombres y los muestre por orden ascendente. Emplear las funciones sort y system de Unix. ¿Qué ocurre con los signos diacríticos?

  7. Ejercicio 0302r07.- Escribir un programa que admita tres nombres y los muestre por orden ascendente. Emplear la función qsort de Unix.

  8. Ejercicio 0302r08.- Consultar las páginas man correspondientes a qsort , heapsort y mergesort . Construir una lista formada por 10.000 palabras. Aplicar la función time para estudiar el rendimiento de los tres métodos de ordenación anteriores.