Las flags, son opciones binarias que acompañan un dato o un sistema y valen para informar o definir su comportamiento o su estado entre otras cosas.
En muchos casos, utilizaremos flags para ver el resultado de una operación, imaginémonos que estamos pasando una serie de tests a un dato de texto:
- El dato no está vacío
- El dato tiene más de 3 letras
- El dato tiene menos de 20 letras
- El dato tiene caracteres no numéricos
- El dato tiene caracteres no números/letras
- El dato tiene caracteres de control
- El dato tiene caracteres no válidos
- El dato tiene formato de fecha
- El dato tiene formato de hora
- El dato tiene formato de teléfono
- El dato tiene formato de coordenadas
Bueno, así puedo poner muchas condiciones, lo importante es la forma de almacenarlo. Si usamos un array de enteros, estaremos ocupando 11 valores multplicado por 4 o por 8, resultan 44 u 88 bytes para almacenar sólo valores booleanos. Cuando podemos almacenarlo todo en un entero manipulando la variable a nivel de bit, definiendo el primer bit para la primera opción, el segundo para la segunda y así sucesivamente.
Tenemos que tener en cuenta el tamaño de la variable (la cantidad de bit que podemos meter). De esta forma ahorraremos espacio en memoria (imaginemos que tenemos un array o una lista de muchísimos elementos, y cada uno tiene sus flags) y tiempo en la ejecución y en la compilación (a nivel de CPU se puede optimizar mucho).
Un ejemplo que habrán visto especialmente las personas que trabajan con sistemas *nix son los permisos de los archivos, en los que vemos 3 grupos de 3 flags (lectura, escritura y ejecución. Si nos quedamos con un solo grupo, vemos que podemos darle valores del 0 al 7, por lo que:
- sin permiso = 0
- lectura (read) = 4
- escritura (write) = 2
- ejecución (execute) = 1
Y también podemos hacer combinaciones (lectura + escritura = 6), (escritura + ejecucón = 3), (lectura + escritura + ejecución = 7).
Aunque, ¿ por qué esos números tan curiosos que nos gustan tanto a los informáticos ? 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384… (vamos, todos potencia de 2 (20 = 1, 21 = 2, 22 = 4, 23 = 8, 24 = 16…) y la gracia está en que si lo expresamos en binario nos queda:
- ejecución (execute) = 0001
- escritura (write) = 0010
- lectura (read) = 0100
Por lo que vamos moviendo el bit de sitio dependiendo de lo que queramos, es más, podemos jugar poniendo y sacando bits dependiendo de las posiciones (lectura + escritura = 0110), (escritura + ejecucion = 0011), (lectura + escritura + ejecución = 0111), vamos al final todo coincide.
Pero claro, no podemos depender de acordarnos de la posición de un determinado bit a la hora de establecer las flags, leerlas y escribirlas, cuando programamos, siempre intentamos dar nombre a las cosas, porque un pequeño texto es siempre más fácil de recordar y evita errores (bit arriba, bit abajo, cuando tenemos la pantalla llena es fácil despistarse.
Por un lado, podemos utilizar #define, tantos como valores tengamos, no ocupa memoria, pero se nos pueden juntar muchas declaraciones, debemos comentar todo muy bien para poder mantener el código.
Podemos utilizar constantes, pero tenemos el mismo problema de antes, pero ahora sí que ocupan memoria, y cada constante ocupará como mínimo 1 byte (si las declaramos como short o como char, o incluso más, porque los valores que adquirirán estas variables desbordarían rápidamente los tipos más pequeños).
Otra opción es utilizar enum, definiendo los valores de los distintos elementos del tipo enumerado, de la siguiente manera:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
enum TPermisos
{
PERM_NINGUNO = 0,
PERM_LECTURA = 1,
PERM_ESCRITURA = 1 << 1,
PERM_CREACION = 1 << 2,
PERM_BORRADO = 1 << 3,
PERM_MODIFICAR_OTROS = 1 << 4,
PERM_TODOS = ~0
};
int main()
{
printf ("PERM_NINGUNO: %d\n", PERM_NINGUNO);
printf ("PERM_LECTURA: %d\n", PERM_LECTURA);
printf ("PERM_ESCRITURA: %d\n", PERM_ESCRITURA);
printf ("PERM_CREACION: %d\n", PERM_CREACION);
printf ("PERM_BORRADO: %d\n", PERM_BORRADO);
printf ("PERM_MODIFICAR_OTROS: %d\n", PERM_MODIFICAR_OTROS);
printf ("PERM_TODOS: %u\n", PERM_TODOS);
// Comprobación de valores
unsigned valor = PERM_ESCRITURA | PERM_CREACION | PERM_LECTURA;
printf ("LECTURA ? %d\n", valor & PERM_LECTURA);
printf ("CREACION ? %d\n", valor & PERM_CREACION);
printf ("BORRADO ? %d\n", valor & PERM_BORRADO);
return 0;
}
Aquí se muestran los valores de todos los permisos que hemos puesto en nuestro sistema. Como dato importante utilizamos los operadores binarios (<< desplazamiento), (~) negación, | suma lógica, & multiplicación lógica.
En el enum de arriba, a cada cadena le hemos asignado un valor, estos valores se calcularán en tiempo de compilación, por lo que no debemos preocuparnos, podemos incluso mezclar valores, en medio del enumerado podemos poner un PERM_LECTURAESCRITURA = PERM_LECTURA | PERM_ESCRITURA sin problema y trabajaremos con esos valores en nuestro código.
Para asignar los valores, utilizamos el desplazamiento para mover el bit definido a 1 a diferentes posiciones, si escribimos 1, en binario será 0001, pero 1 << 1 será 0010, 1 << 2 será 0100 y así sucesivamente). Por otro lado, en la opción TODOS, me interesa que todos los valores estén a 1, por lo que (niego un 0), es decir, si 0 = 0000, la negación de este valor será 1111.
Más detalles, en C solamente, podemos poner la variable valor de tipo (enum TPermisos), si lo escribimos en C++ debemos hacerla unsigned. Y en C++11 podemos utilizar un enum con ámbito:
1
2
3
4
5
6
7
8
9
10
11
enum TPermisos : unsigned
{
PERM_NINGUNO = 0,
PERM_LECTURA = 1,
PERM_ESCRITURA = 1 << 1,
PERM_CREACION = 1 << 2,
PERM_BORRADO = 1 << 3,
PERM_MODIFICAR_OTROS = 1 << 4,
PERM_READWRITE = PERM_ESCRITURA | PERM_LECTURA,
PERM_TODOS = (unsigned) ~0
};
Para decirle expresamente que utilice un unsigned para almacenar la información.
Para completar un poco la información, hace mucho tiempo escribí algunos artículos sobre manipulación de bits que pueden ser interesantes: Bailando con bits, Bailando con bits II.
Foto: (Flickr) CC-by