Blog

02 – Tipos, operadores y expresiones

Las variables y las constantes son los objetos de datos básicos que se manipulan en un programa. Los operadores especifican lo que se hará con las variables. Las expresiones combinan variables y constantes para producir nuevos valores.

2.1 Nombres de variables
2.2 Tipos y tamaños de datos

Ejercicio 2-1

Escriba un programa en C para determinar los rangos de variables char, short, int y long, tanto signed como unsigned, imprimiendo los valores apropiados de los headers stándar y por cálculo directo. Es más difícil si los calcula: determine los rangos de los varios tipos de punto flotante.

/* Escriba un programa en C para determinar los rangos de variables char, short, int y long, 
   tanto signed como unsigned, imprimiendo los valores apropiados de los headers stándar 
   y por cálculo directo. Es más difícil si los calcula: determine los rangos de los varios 
   tipos de punto flotante.
*/

#include 
#include 

int main() {
   printf("Rangos utilizando headers stándar:\n\n");
   
   printf("char signed: %d a %d\n", CHAR_MIN, CHAR_MAX);
   printf("char unsigned: 0 a %u\n", UCHAR_MAX);
   
   printf("short signed: %d a %d\n", SHRT_MIN, SHRT_MAX);
   printf("short unsigned: 0 a %u\n", USHRT_MAX);
   
   printf("int signed: %d a %d\n", INT_MIN, INT_MAX);
   printf("int unsigned: 0 a %u\n", UINT_MAX);
   
   printf("long signed: %ld a %ld\n", LONG_MIN, LONG_MAX);
   printf("long unsigned: 0 a %lu\n", ULONG_MAX);
   
   printf("\nRangos por cálculo directo:\n\n");
   
   printf("char signed: %d a %d\n", -(char)((unsigned char)~0 >> 1) - 1, (char)((unsigned char)~0 >> 1));
   printf("char unsigned: 0 a %u\n", (unsigned char)~0);
   
   printf("short signed: %d a %d\n", -(short)((unsigned short)~0 >> 1) - 1, (short)((unsigned short)~0 >> 1));
   printf("short unsigned: 0 a %u\n", (unsigned short)~0);
   
   printf("int signed: %d a %d\n", -(int)((unsigned int)~0 >> 1) - 1, (int)((unsigned int)~0 >> 1));
   printf("int unsigned: 0 a %u\n", (unsigned int)~0);
   
   printf("long signed: %ld a %ld\n", -(long)((unsigned long)~0 >> 1) - 1, (long)((unsigned long)~0 >> 1));
   printf("long unsigned: 0 a %lu\n", (unsigned long)~0);
   
   return 0;
}
Este programa utiliza los headers stándar limits.h y stdio.h para obtener los valores máximos y mínimos de las variables char, short, int y long, tanto signed como unsigned. También utiliza cálculos directos para determinar los rangos de las variables utilizando operaciones bit a bit.

En C, las variables signed se representan con el complemento a 2. Para encontrar el valor mínimo de una variable signed, simplemente tomamos el negativo del valor máximo más uno. Por ejemplo, para un char signed, el valor máximo es 127 (0x7F) y el valor mínimo es -128 (0x80). Por lo tanto, podemos encontrar el valor mínimo de un char signed de la siguiente manera:
-(char)((unsigned char)~0 >> 1) - 1

Aquí, ~0 es un número que tiene todos los bits establecidos en 1, es decir, 0xFFFFFFFF en sistemas de 32 bits y 0xFFFFFFFFFFFFFFFF en sistemas de 64 bits. Luego, al hacer ~0 >> 1, cambiamos el bit más significativo a 0 para obtener 0x7FFFFFFF en sistemas de 32 bits y 0x7FFFFFFFFFFFFFFF en sistemas de 64 bits. Al hacer un cast a unsigned char, unsigned short, unsigned int o unsigned long respectivamente, estamos ampliando el tamaño del tipo para poder realizar cálculos aritméticos en bits. Luego, se hace otro cast a char, short, int o long para volver al tamaño original del tipo, lo que establece el signo de la variable. Finalmente, tomamos el negativo de este valor y le restamos uno para encontrar el valor mínimo de la variable.

Para encontrar el valor máximo de una variable signed, hacemos el mismo cálculo pero sin tomar el negativo y sin restar uno. Por ejemplo, para un char signed, el valor máximo es 127 (0x7F). Por lo tanto, podemos encontrar el valor máximo de un char signed de la siguiente manera:
(char)((unsigned char)~0 >> 1)

En el caso de las variables unsigned, simplemente tomamos el valor máximo como el valor más grande que puede ser representado por el tipo. Por ejemplo, para un char unsigned, el valor máximo es 255 (0xFF). Por lo tanto, podemos encontrar el valor máximo de un char unsigned de la siguiente manera:
(unsigned char)~0

2.3 Constantes
2.4 Declaraciones
2.5 Operadores aritméticos
2.6 Operaciones de relación y lógicos

Ejercicio 2-2

Imagina que para la iteración de un bucle for en C tenemos la siguiente expresión:
(i < lim-1 && (c = getchar()) != '\n' && c != EOF)
¿Podrías escribir esta iteración sin usar && ni ||?
for (i = 0; i < lim-1; i++) {
    if ((c = getchar()) == '\n') {
        break;
    }
    if (c == EOF) {
        break;
    }
}

En esta versión, la expresión se divide en dos declaraciones de if separadas.

Primero, se verifica si getchar() devuelve el caracter de nueva línea '\n'. Si es así, se rompe el bucle con la instrucción break.

Luego, se verifica si getchar() devuelve el final de archivo EOF. Si es así, también se rompe el bucle con la instrucción break.

De lo contrario, el bucle continúa iterando y se asigna el valor de getchar() a c.

2.7 Conversiones de tipo

Ejercicio 2-3

Escribe una funcion htoi(s) en C, que convierta una cadena de dígitos hexadecimales (incluyendo 0x ó 0X en forma optativa) en su valor entero equivalente. Los dígitos permitidos son del 0 al 9, de la a a la f, y de la A a la F.
/* Escribe una funcion htoi(s) en C, que convierta una 
   cadena de dígitos hexadecimales (incluyendo 0x ó 0X 
   en forma optativa) en su valor entero equivalente. 
   Los dígitos permitidos son del 0 al 9, de la a a la f, 
   y de la A a la F.*/

#include 
#include 

int htoi(char s[]) {
    int i, n, hexdigit, inhex;

    i = 0;
    if (s[i] == '0') {
        ++i;
        if (s[i] == 'x' || s[i] == 'X') {
            ++i;
        }
    }
    n = 0;
    inhex = 1;
    for (; inhex == 1; ++i) {
        if (isdigit(s[i])) {
            hexdigit = s[i] - '0';
        }
        else if (isalpha(s[i])) {
            hexdigit = tolower(s[i]) - 'a' + 10;
        }
        else {
            inhex = 0;
        }
        if (inhex == 1) {
            n = 16 * n + hexdigit;
        }
    }
    return n;
}

int main() {
    char cadena[]="0xAF50B";
    printf("%s es %d", cadena, htoi(cadena));
}
La función comienza con la verificación de si la cadena de entrada comienza con "0x" o "0X". Si es así, la función omite los dos primeros caracteres de la cadena. Luego, la función itera a través de la cadena de entrada, convirtiendo cada dígito hexadecimal en su valor entero equivalente. La función devuelve el valor entero final.

2.8 Operadores de incremento y decremento

Ejercicio 2-4

Escribe una función en C denominada squeeze(s1,s2) que borre cada carácter de s1 que coincida con cualquier carácter de la cadena s2.

#include 

/* squeeze: borra todas los caracteres de s1
   que coincidan con caracteres de s2*/

void squeeze(char s1[], char s2[]) {
    int i, j, k;
    for (i = j = 0; s1[i] != '\0'; i++) {
        for (k = 0; s2[k] != '\0' && s2[k] != s1[i]; k++);
        if (s2[k] == '\0') {
            s1[j++] = s1[i];
        }
    }
    s1[j] = '\0';
}

int main() {
	char s1[] = "987655895766212";
	char  s2[] = "562";
	squeeze(s1,s2);
	printf("%s\n",s1);
}
  • La función recibe dos argumentos de cadena (string), s1 y s2, que son las cadenas de las que se van a eliminar los caracteres que coincidan.
  • La función utiliza un bucle for anidado para recorrer cada carácter de la cadena s1 y cada carácter de la cadena s2.
  • Si el carácter actual de s1 no coincide con ningún carácter de s2, se copia en la posición j de s1 y j se incrementa. Esto significa que los caracteres que coinciden se eliminan de la cadena s1.
  • Al final, se establece el último carácter de s1 como '\0', lo que significa que la cadena se termina allí.

Ejercicio 2-5

Escribe la función any(s1,s2), que devuelve la primera posición de la cadena s1 en donde se encuentre cualquier caracter de la cadena s2, o -1 si s1 no contiene caracteres de s2.

#include 

/* any: devuelve la primera posicón de s1
   donde se encuentra cualquier carácter
   de s2, o -1 si no encuentra ninguno */

int any(char s1[], char s2[]) {
    int i, j;
    for (i = 0; s1[i] != '\0'; i++) {
        for (j = 0; s2[j] != '\0'; j++) {
            if (s1[i] == s2[j]) {
                return i;
            }
        }
    }
    return -1;
}

int main() {
    char s1[] = "hola mundo";
    char s2[] = "oe";
    int pos = any(s1, s2);
    printf("%d\n", pos);
    return 0;
}
  • La función recibe dos argumentos de cadena (string), s1 y s2, que son las cadenas en las que se busca la coincidencia de caracteres.
  • La función utiliza dos bucles for anidados para recorrer cada carácter de s1 y cada carácter de s2.
  • Si se encuentra una coincidencia, la función devuelve la posición actual en s1.
  • Si no se encuentra ninguna coincidencia, la función devuelve -1.

2.9 Operadores para manejo de bits

Ejercicio 2-6

Escribe una función setbits(x,p,n,y) en C, que regresa x con los n bits que principian en la posicion p iguales a los n bits más a la derecha de y, dejando los otros bits sin cambio.

#include 

/*Escribe una función setbits(x,p,n,y) en C, que regresa x con los 
  n bits que principian en la posicion p iguales a los n bits más 
  a la derecha de y, dejando los otros bits sin cambio.
*/

unsigned setbits(unsigned x, int p, int n, unsigned y);

int main(int argc, char *argv[]) {
    if (argc != 5) {
        printf("Usage: %s  

\n", argv[0]); return 1; } // Convertir los argumentos de la línea de comandos en enteros unsigned x = (unsigned) strtol(argv[1], NULL, 0); int p = (int) strtol(argv[2], NULL, 0); int n = (int) strtol(argv[3], NULL, 0); unsigned y = (unsigned) strtol(argv[4], NULL, 0); // Llamar a la función setbits y mostrar el resultado unsigned result = setbits(x, p, n, y); printf("setbits(%u,%d,%d,%u) = %u\n", x, p, n, y, result); return 0; } unsigned setbits(unsigned x, int p, int n, unsigned y) { // Se crea una máscara de n bits con 1's // y se desplaza p-n+1 bits a la izquierda unsigned mask = ~(~0 << n) << (p - n + 1); // Se obtiene los n bits más a la derecha de y unsigned bits_to_insert = (y & ~(~0 << n)) << (p - n + 1); // Se borran los n bits de x que se van a reemplazar x &= ~mask; // Se insertan los bits de y en la posición adecuada en x x |= bits_to_insert; // Se retorna el resultado return x; }

  1. Primero, creamos una máscara con n bits con todos los bits a 1.
  2. Luego, desplazamos la máscara p-n+1 bits a la izquierda para que esté en la posición correcta para cubrir los bits que vamos a modificar en x.
  3. A continuación, obtenemos los n bits más a la derecha de y usando una máscara similar a la de arriba.
  4. Luego, borramos los bits de x que se van a reemplazar utilizando la máscara creada en el paso 1.
  5. Finalmente, insertamos los bits de y en la posición adecuada en x y devolvemos el resultado.

Tenga en cuenta que esta función asume que los índices de bits comienzan en 0 (es decir, el bit más a la derecha es el bit 0). Además, esta función utiliza operadores de bits, por lo que es necesario utilizar el tipo de datos unsigned para asegurarse de que el comportamiento de los bits sea el correcto.

Imaginemos que ejecutamos el programa:

ejer02_06 1431655765 7 8 170

Este ejemplo utiliza los siguientes argumentos: x = 1431655765, p = 7, n = 8, y y = 170. El número 1431655765 es 0x55555555 en hexadecimal, que tiene el bit 7 en 1 y los bits 8 a 15 en 0. El número 170 es 0xAA en hexadecimal, que tiene los bits 0 a 7 en 1 y los bits 8 a 31 en 0. Entonces, si llamamos a setbits(x,p,n,y), deberíamos obtener un número en el que los bits 7 a 14 sean iguales a los bits 0 a 7 de y (que son todos 1) y el resto de los bits sean los mismos que en x. Después de ejecutar el programa, deberíamos obtener el siguiente resultado:

ejer02_06(1431655765,7,8,170) = 1431655939

Este resultado significa que los bits 7 a 14 de 1431655765 ahora son iguales a los bits 0 a 7 de 170, como se esperaba.

Ejercicio 2-7

Escribe en C una funcion invert(x,p,n) que regresa x con los n bits que principian en la posicion p invertidos (esto es, 1 cambiado a 0 y viceversa), dejando los otros sin cambio.
#include 
#include 

/*Escribe en C una funcion invert(x,p,n) que regresa x con 
los n bits que principian en la posicion p invertidos (esto es, 
1 cambiado a 0 y viceversa), dejando los otros sin cambio.
*/

unsigned invert(unsigned x, int p, int n);

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("Usage: %s  

\n", argv[0]); return 1; } // Convertir los argumentos de la línea de comandos en enteros unsigned x = (unsigned) strtol(argv[1], NULL, 0); int p = (int) strtol(argv[2], NULL, 0); int n = (int) strtol(argv[3], NULL, 0); // Llamar a la función invert y mostrar el resultado unsigned result = invert(x, p, n); printf("invert(%u,%d,%d) = %u\n", x, p, n, result); return 0; } unsigned invert(unsigned x, int p, int n) { // Se crea una máscara de n bits con 1's // y se desplaza p-n+1 bits a la izquierda unsigned mask = ~(~0 << n) << (p - n + 1); // Se invierten los bits de x que están en la posición adecuada x ^= mask; // Se retorna el resultado return x; }

  1. Primero, creamos una máscara con n bits con todos los bits a 1.
  2. Luego, desplazamos la máscara p-n+1 bits a la izquierda para que esté en la posición correcta para cubrir los bits que vamos a invertir en x.
  3. A continuación, invertimos los bits de x que están en la posición adecuada utilizando la máscara creada en el paso 1.
  4. Finalmente, devolvemos el resultado.

Tenga en cuenta que esta función asume que los índices de bits comienzan en 0 (es decir, el bit más a la derecha es el bit 0). Además, esta función utiliza operadores de bits, por lo que es necesario utilizar el tipo de datos unsigned para asegurarse de que el comportamiento de los bits sea el correcto.

Imaginemos que ejecutamos el programa:

ejer02_07 170 7 8

Este ejemplo utiliza los siguientes argumentos: x = 170, p = 7, y n = 8. El número 170 es 0xAA en hexadecimal, que tiene los bits 0 a 7 en 1 y los bits 8 a 31 en 0. Entonces, si llamamos a invert(x,p,n), deberíamos obtener un número en el que los bits 7 a 14 hayan sido invertidos (es decir, se hayan cambiado de 1 a 0 o de 0 a 1), y el resto de los bits sean los mismos que en x. Después de ejecutar el programa, deberíamos obtener el siguiente resultado:

invert(170,7,8) = 4294901883

Este resultado significa que los bits 7 a 14 de 170 han sido invertidos, como se esperaba.

Ejercicio 2-8

Escribe una funcion en C rightrot(x,n) que regresa el valor del entero x rotado a la derecha n posiciones de bits, e incluye la función en un programa que ilustre su funcionamiento.

#include 

/* Escribe una funcion en C rightrot(x,n) que regresa el 
valor del entero x rotado a la derecha n posiciones de 
bits, e incluye la función en un programa que ilustre 
su funcionamiento.
*/

unsigned rightrot(unsigned x, int n)
{
    int wordlength(void);
    unsigned rbit;

    while (n-- > 0) {
        rbit = (x & 1) << (wordlength() - 1);
        x = x >> 1;
        x = x | rbit;
    }
    return x;
}

int wordlength(void)
{
    int i;
    unsigned v = (unsigned) ~0;
    for (i = 1; (v = v >> 1) > 0; i++)
        ;
    return i;
}

int main()
{
    unsigned x = 0x12345678;
    int n = 4;
    printf("Original: %08x\n", x);
    printf("Rotated : %08x\n", rightrot(x, n));
    return 0;
}

La función rightrot(x,n) toma dos argumentos: el entero x que se va a rotar y el número de posiciones n que se van a rotar a la derecha. La función primero define una variable rbit que se usará para almacenar el bit más a la derecha de x antes de que se desplace a la derecha.

Dentro del bucle while, la función desplaza x a la derecha una posición y luego hace un desplazamiento a la izquierda con wordlength()-1 bits para recuperar el bit más a la derecha de x. El resultado se almacena en rbit. Luego, rbit se coloca en la posición más a la izquierda de x mediante una operación OR.

La función wordlength() devuelve la longitud en bits del tipo de datos unsigned, que se usa para determinar cuántos bits se deben desplazar a la izquierda para recuperar el bit más a la derecha de x.

En el programa principal, se define una variable x y se le da un valor inicial de 0x12345678. Se llama a la función rightrot() con x y n y se imprime el resultado de la rotación a la derecha.

Si se ejecuta el programa, la salida sería algo así:

Original: 12345678
Rotated : 81234567

Esto indica que el número 0x12345678 se ha rotado 4 posiciones a la derecha para producir el número 0x81234567.

2.10 Operadores de asignación y expresiones

Ejercicio 2-9

En un sistema de números de complemento a dos, x&=(x-1) borra el bit 1 de más a la derecha en x. Explica el porqué. Utiliza esta observación para escribir una versión más rápida de la función bitcount que pongo a continuación:


int bitcount(unsigned x)
{
    int b;
    for (b = 0; x != 0; x >>= 1)
        if (x & 01)
            b++;
    return b;
}
En un sistema de números de complemento a dos, el bit 1 de más a la derecha en un número x representa la parte negativa de su valor. Cuando restamos 1 a x, el bit 1 de más a la derecha se convierte en 0 y todos los bits a su derecha se convierten en 1. Luego, al hacer una operación de "y" (AND) entre x y (x-1), todos los bits a la derecha del bit 1 de más a la derecha se anulan, mientras que el bit 1 de más a la derecha se mantiene igual. Por lo tanto, al aplicar x &= (x-1), borramos efectivamente el bit 1 de más a la derecha en x.

Para una versión más rápida de la función bitcount, podemos aplicar este truco para contar los bits de 1 en un número de manera eficiente. En lugar de iterar a través de cada bit de x y verificar si es 1, podemos simplemente aplicar la operación x &= (x-1) repetidamente hasta que x se convierte en cero. En cada iteración, eliminamos el bit 1 de más a la derecha en x, y contamos el número de iteraciones para obtener el número total de bits de 1 en x. Por lo tanto, una versión más rápida de la función bitcount se vería así:


int bitcount(unsigned x) {
    int b;
    for (b = 0; x != 0; x &= (x-1)) {
        b++;
    }
    return b;
}

Esta implementación debería ser más rápida que la original, especialmente para números con muchos bits de 1.

#include 

/* Escribe una funcion en C rightrot(x,n) que regresa el 
valor del entero x rotado a la derecha n posiciones de 
bits, e incluye la función en un programa que ilustre 
su funcionamiento.
*/

/* bitcount: cuenta bits 1 en x */
/*int bitcount(unsigned x)
{
    int b;
    for (b = 0; x != 0; x >>= 1)
        if (x & 01)
            b++;
    return b;
}*/

/* version nueva más eficiente */
int bitcount(unsigned x) {
    int b;
    for (b = 0; x != 0; x &= (x-1)) {
        b++;
    }
    return b;
}

int main()
{
    unsigned x = 0x12345678;
    printf("Número de bits 1: %d\n", bitcount(x));
    return 0;
}

2.11 Expresiones condicionales
2.12 Precedencia y orden de evaluación

Ejercicio 2-10

Escribe una función lower en C, que convierta letras mayúsculas en minúsculas, con una expresión condicional ?: en vez de un if-else, e ilustra su funcionamiento con un programa.
#include 

/* Escribe una función lower en C, que convierta letras 
   mayúsculas en minúsculas, con una expresión condicional ?: 
   en vez de un if-else, e ilustra su funcionamiento con 
   un programa.
*/

char to_lower(char c) {
  return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;
}

int main() {
  char c = 'H';
  printf("Carácter original: %c\n", c);
  printf("Carácter convertido: %c\n", to_lower(c));
  return 0;
}
La función to_lower toma como entrada un carácter c y devuelve el mismo carácter convertido a minúsculas, si originalmente era una letra mayúscula. La expresión condicional ?: se utiliza para determinar si el carácter es una letra mayúscula o no. Si lo es, se suma la diferencia entre la letra mayúscula y la letra minúscula correspondiente (que en ASCII es 32) para convertirlo a minúsculas. Si no, simplemente se devuelve el carácter original.

En el programa de ejemplo, se ilustra el uso de la función to_lower con el carácter 'H'. La salida del programa es:


Carácter original: H
Carácter convertido: h

Como se puede ver, el carácter 'H' se convierte en el carácter 'h' utilizando la función to_lower.