Glib

Contents

  1. Tipos de datos de GLib
  2. Mensajes de salida
    1. Mensajes de salida.
    2. Funciones de depuración.
    3. Funciones de registro.
  3. Trabajar con cadenas
    1. Manipular el contenido de una cadena.
      1. Duplicación de cadenas.
      2. Verificación de caracteres.
      3. Copia y concatenación de cadenas.
    2. GString : la otra manera de ver una cadena.
      1. Cómo se aloja una cadena con GString.
      2. Cómo puedo liberar un GString
      3. Añadir, insertar y borrar cadenas en un GString.
      4. Introducir texto preformateado a un GString.
      5. Otras funciones útiles para el manejo de GString.
    3. GQuarks.
  4. Jugando con el tiempo
    1. Funciones de manejo de fechas y horas.
      1. Creación de una nueva fecha.
    2. Midiendo intervalos de tiempo con GTimer.
      1. Descripción de las funciones para manejar GTimer.
  5. Miscelánea de funciones
    1. Números aleatorios.
    2. Funciones de información sobre entorno.
  6. Bucles de ejecución
    1. Alarmas
    2. Tiempos de inactividad.
  7. Tratamiento de ficheros y canales de Entrada/Salida
    1. Obtención de un GIOChannel.
    2. Generalidades en el trabajo con GIOChannels.
    3. Operaciones básicas.
    4. Integración de canales al bucle de eventos.
    5. Configuración avanzada de canales.
      1. Codificación
      2. Terminador de línea.
      3. Configuración de buffer interno.
  8. Manejo de memoria dinámica
    1. Reserva de memoria.
    2. Liberación de memoria.
    3. Realojamiento de memoria.
    4. Perfil de uso de memoria.
  9. Estructuras de datos: listas enlazadas, pilas y colas
    1. Listas enlazadas.
      1. Introducción
      2. GSList
    2. Listas doblemente enlazadas.
      1. Introducción.
      2. GList
    3. GQueue: pilas y colas.
      1. Colas
        1. Iniciar cola.
        2. Cola vacía.
        3. Consultar el frente.
        4. Consultar el final.
        5. Meter
        6. Sacar
        7. Vaciar cola.
      2. Pilas
        1. Iniciar pila.
        2. Pila vacía.
        3. Consultar pila.
        4. Meter
        5. Sacar
        6. Vaciar pila.
  10. Estructuras de datos avanzadas
    1. Tablas de dispersión.
      1. Qué es una tabla de dispersión.
      2. Cómo se crea una tabla de dispersión.
      3. Cómo manipular un tabla de dispersión.
      4. Búsqueda de información dentro de la tabla.
      5. Manipulación avanzada de tablas de dispersión.
    2. Arboles binarios balanceados
      1. Creación de un árbol binario.
      2. Agregar y eliminar elementos a un árbol binario.
      3. Búsqueda y recorrida en un árbol binario.
    3. GNode : Arboles de orden n.
      1. Agregar nodos a un árbol.
      2. Eliminar Vs. desvincular.
      3. Información sobre los nodos.
      4. Buscar en el árbol y recorrerlo.
    4. Caches
      1. Creación del caché.
      2. Gestión de los datos del caché.
      3. Destrucción del caché.
  11. GLib Avanzado
    1. Hilos en Glib.
    2. UTF-8: las letras del mundo.
      1. Unicode: Una forma de representación (UCS).
      2. Qué es UTF-8.
      3. Conversiones del juego de caracteres.
        1. Nombres de archivos y UTF-8.
        2. Configuración local de caracteres y UTF-8.
      4. Otras funciones relacionadas con conversión de cadenas.
    3. Como hacer plugins.
      1. Abrir una biblioteca.
      2. Obtener un símbolo determinado.
      3. Preparar el plugin.
      4. Ejemplo de uso de GModule.

La biblioteca GLib es una de las más importantes que existen en GNOME. Esta biblioteca es, junto a la biblioteca GTK+, el pilar sobre el que se sustentan todas las aplicaciones.

Tipos de datos de GLib

Dentro de la biblioteca GLib está implementada una serie de tipos de datos que facilita, si cabe, el tratamiento de los datos y además tiene la propiedad de mejorar la portabilidad de los programas. Los tipos que se usan en GLib no son muy diferentes de los utilizados en el estándar de C, por lo cual no resultará complicado familiarizarse con ellos. A continuación se podrá observar una tabla de correspondencia entre los tipos de GLib contra los de estándar de C.

Tabla 6-1.1. Tipos de GLib que corresponden con los del estándar C.

Tipos GLib.

Tipos estándar C.

gchar

char

gint

int

gshort

short

glong

long

gfloat

float

gdouble

double

Como se ha demostrado en la tabla, los tipos de GLib no difieren en gran medida de los utilizados al programar con los tipos estándar de C, aunque es altamente recomendable la familiarización con estos tipos si se desea trabajar con la plataforma GNOME. GLib también posee una serie de tipos que hacen más sencillo el tratamiento de datos. Algunos tienen su correspondencia en C, pero facilitan su utilización; otros sirven para mantener el mismo tamaño de datos de una plataforma a otra y, finalmente, hay otros que no tienen correspondencia con el estándar de C, pero que resultarán bastante útiles.

Tabla 6-1.2. Tipos de GLib que facilitan el uso de tipos del estándar C.

Tipos de GLib

Tipos del C estándar.

Definición

gpointer

void *

gpointer es un puntero sin tipo, que luce mejor que escribir void *.

gconstpointer

gconstpointer es un puntero a una constante. Los datos a los que apuntan no deberían ser cambiados. Este tipo suele usarse en los prototipos de funciones para indicar que el valor al que apunta la función no debería ser cambiado en el interior de la función.

guchar

unsigned char

Como se puede observar, estos tipos son idénticos a los tipos de C que carecen de signo, pero con la ventaja de que son más visuales y fáciles de usar.

guint

unsigned int

gushort

unsigned short

gulong

unsigned long

Tabla 6-1.3. Tipos de GLib que aseguran el tamaño del dato entre plataformas.

Tipo

Rango de tamaño del dato.

gint8

-128 a 127

guint8

0 a 255

gint16

-32.768 a 32.767

guint16

0 a 65535

gint32

-2.147.483.648 a 2.147.483.647

guint32

0 a 4.294.967.295

gint64

-9.223.372.036.854.775.808 a 9.223.372.036.854.775.807

guint64

0 a 18.446.744.073.709.551.615

Tabla 6-1.4. Tipos de GLib nuevos que no están en el estándar de C.

Tipos de GLib

Definición

gboolean

Este tipo es booleano y sólo contendrá los valores TRUE o FALSE.

gsize

Es un entero de treinta y dos bits sin signo que sirve para representar tamaños de estructuras de datos.

gssize

Es un entero de 32 bits con signo, que sirve para representar tamaños de estructuras de datos.

Quizás, la existencia de tipos tales como gint8, gint16, gsize, etcétera, pudiese sugerir mayor complejidad a la hora de aprender a utilizar la biblioteca GLib. Sin embargo es importante denotar que cuando se programa mediante esta biblioteca, los tipos más utilizados son char, int, double, etc., aunque ahora se antepondrá una letra "g" a cada tipo.

Mensajes de salida

Mensajes de salida.

GLib implementa una serie de funciones para el envío de mensajes al exterior del programa. Al igual que en el ANSI C se dispone de la mítica función printf (), GLib posee una función que se asemeja en todo menos en el nombre; esta función es gprint () y su prototipo de función es:

void g_print (const gchar *format, ...);

Como se puede ver, el uso de g_print () es idéntico al de printf (). Es una función que recibe como parámetros un formato y las variables que se usan dentro de las características del formato, al igual que printf ().

Ejemplo 6-2.1. Mensajes de salida.

   1 /* ejemplo del uso de g_print */
   2 #include <glib.h>
   3 int
   4 main (int argc, char *argv[])
   5 {
   6         gint numero = 1;
   7         gchar *palabra = "hola";
   8         g_print ("glib te saluda : %s\nglib te dice un número : %d\n", palabra,
   9                  numero);
  10         return 0;
  11 }

Además, GLib provee de un método para dar formato a todos los mensajes de salida que se emitan con la función g_print (), por lo que se podría decir que, cada vez que se envía un mensaje con formato a g_print (), éste debe salir por pantalla con algún tipo de mensaje adicional. Esto es debido a la función g_set_print_handler (), que tiene como parámetros un puntero a función. Esta función, que es representada por el puntero a función, será definida por el programador y, con ella, se podrá decir qué mensaje adicional queremos que se vea. Para entenderlo mejor, se mostrará a continuación la sinopsis de esta función, así como un ejemplo.

GPrintFunc g_set_print_handler (GPrintFunc funcion);

Esta es la sinopsis de la función g_set_print_handler (), que recibirá como parámetro un puntero a función. Esta función podrá tener el siguiente aspecto

void (* GPrintFunc) (const gchar * cadena );

Éste sería el aspecto que ha de tener la función que se le pasará como parámetro a g_set_print_handler. El siguiente ejemplo ha sido concebido con la intención de facilitar su comprensión.

Ejemplo 6-2.2. Personalización de mensajes de salida.

   1 /* ejemplo de cómo añadir información a los mensajes de g_print */
   2 #include <glib.h>
   3 
   4 void funcion_personal (const gchar *cadena);
   5 
   6 /* funcion_personal es la función que le pasaremos a
   7    g_set_print_handler como parámetro */
   8 void
   9 funcion_personal (const gchar *cadena)
  10 {
  11         printf ("** Soy tu programa y te digo ** ");
  12         printf (cadena);
  13 }
  14 
  15 int
  16 main ()
  17 {
  18         /* g_print normal */
  19         g_print ("Soy un mensaje normal de salida\n");
  20 
  21         /* g_print personalizado */
  22         g_set_print_handler (funcion_personal);
  23         g_print ("mensaje de salida personalizado\n");
  24         return 0;
  25 }

GLib también provee de una función para comunicar mensajes de salida por la salida de errores estándar (stderr). Para el envío de mensajes existe la función g_printerr que funciona exactamente igual a g_print. Existe también una función como g_set_print_handler, llamada g_set_printerr_handler que funciona igual que la anterior.

Se puede ver, en conclusión, que el formateo personalizado de los mensajes de salida en GLib es bastante simple.

Funciones de depuración.

La depuración de un programa siempre es un trasiego por el cual todo programador, ya sea principiante o avanzado, ha de pasar. Y para hacer la vida más fácil al programador de GNOME, GLib nos ofrece una serie de funciones que harán más sencilla la depuración del código.

Estas funciones son útiles para comprobar que se cumple una serie de condiciones lógicas delimitadas por el programador. En caso de no ser cumplida, GLib emitirá un mensaje por consola, explicando que la condición que el programador ha puesto no se ha cumplido e indicando el archivo, la línea y la función en las que se ha roto la condición.

#define g_assert ( expresion );

La primera función de este tipo que se abordará es g_assert. Esta función recibe como parámetro un enunciado lógico y comprueba su validez. Para ejemplificar su uso, se expondrá un caso en el que a una función se le pase un puntero. Y se pondrá g_assert (ptr != NULL) para certificar que ese caso no se dé nunca. De hecho, si se diese tal caso, la función g_assert comprobaría que esa expresión no es válida y mandaría interrumpir la ejecución del programa. Para tener más claro este concepto, obsérvese la ejecución del siguiente ejemplo:

Ejemplo 6-2.3. Depuración de programas con g_assert.

   1 /* ejemplo del uso de g_assert */
   2 #include <glib.h>
   3 void
   4 escribe_linea (gchar *cadena)
   5 {
   6         /* si a la función se le pasa un puntero a NULL
   7            el programa cesara en la ejecución */
   8         g_assert (cadena != NULL);
   9         g_print ("%s\n", cadena);
  10 }
  11 
  12 int
  13 main ()
  14 {
  15         gchar *cadena = "esto es una cadena";
  16         gchar *no_es_cadena = NULL;
  17         /* como no se pasa un NULL sino una cadena, se escribirá
  18            la cadena "esto es una cadena" por pantalla */
  19         escribe_linea (cadena);
  20         /* como se le pasa un NULL el assert de la función
  21            escribe_linea surtirá efecto */
  22         escribe_linea (no_es_cadena);
  23         return 0;
  24 }

Si se compila este programa, en la salida del mismo, se podrá observar que cuando entra por segunda vez en la función escribe_linea, cesa la ejecución del programa. Además, por la consola saldrá un mensaje similar al siguiente.

        esto es una cadena

        ** ERROR **: file ejemplo.c: line 9 (escribe_linea): assertion failed: (cadena != NULL)
        aborting...

El mensaje muestra por pantalla información del archivo, de la línea y de la función en las que se viola la condición por la cual g_assert ha parado la ejecución. También indica qué expresión se ha incumplido, ya que pudiese darse el caso de tener varios g_assert en una misma función.

Quizá un método tan drástico como parar la ejecución del programa no sea lo más adecuado. Es probable que sólo se busque algo que termine la ejecución de la función y avise que una condición no ha sido cumplida. Para estos casos existen varias macros que facilitarán mucho la vida de los programadores. Estas macros son:

#define g_return_if_fail ()( expresion );

#define g_return_val_if_fail ()( expresion , val );

Ambas macros son similares en su funcionamiento, ya que comprueban si la expresión lógica que se les pasa como primer parámetro es verdadera o falsa. En el caso de ser falsa, aparecerá por consola un mensaje explicando que esa condición no ha sido cumplida. Esto es muy parecido a lo que hacía g_assert () la diferencia estriba en que estas dos funciones no finalizan la ejecución del programa, sino que simplemente hacen un retorno dentro de la propia función y, en consecuencia, el resto del programa seguirá ejecutándose.

Como previamente se mencionó, estas macros son similares. La diferencia radica en que la segunda, g_return_val_if_fail, hace que la función devuelva el valor que se le pasa como segundo parámetro.

Obviamente, g_return_if_fail se usará en funciones que no devuelvan ningún valor y g_return_val_if_fail en funciones que devuelvan algún valor. Para entender mejor estas funciones obsérvese el siguiente ejemplo.

Ejemplo 6-2.4. Depuración de programas con g_return_if_fail y g_return_val_if_fail.

   1 /* ejemplo del uso de g_return_if_fail y f_return_val_if_fail */
   2 #include <glib.h>
   3 
   4 void
   5 escribe_linea (gchar *cadena)
   6 {
   7         g_return_if_fail (cadena != NULL);
   8         g_print ("%s\n", cadena);
   9 }
  10 
  11 /* escribe línea 2 devolverá
  12    TRUE si la ha escrito bien
  13    FALSE si incumple la expresión */
  14 gboolean
  15 escribe_linea2 (gchar *cadena)
  16 {
  17         g_return_val_if_fail (cadena != NULL, FALSE);
  18         g_print ("%s\n", cadena);
  19         return  TRUE;
  20 }
  21 
  22 int
  23 main ()
  24 {
  25         gchar *cadena = "esto es una cadena";
  26         gchar *no_es_cadena = NULL;
  27         gboolean resultado ;
  28         escribe_linea (cadena);
  29         resultado = escribe_linea2 (cadena);
  30         if (resultado == TRUE)
  31                 g_print ("\n-- escribe_linea2 ha devuelto TRUE --\n");
  32         escribe_linea (no_es_cadena);
  33         resultado = escribe_linea2 (no_es_cadena);
  34         if (resultado == FALSE)
  35                 g_print ("\n-- escribe_linea2 ha devuelto FALSE --\n");
  36         return 0;
  37 }

Y, por consiguiente, la salida de este programa será similar a la siguiente:

esto es una cadena
esto es una cadena

-- escribe_linea2 ha devuelto TRUE --

** (process:1054): CRITICAL **: escribe_linea: assertion `cadena != NULL' failed

** (process:1054): CRITICAL **: escribe_linea2: assertion `cadena != NULL' failed

-- escribe_linea2 ha devuelto FALSE --

Como se puede ver, su ejecución también ofrece información útil, como el identificador del proceso al cual pertenece el mensaje y al igual que en el caso de la función g_assert, entrega como parte de la salida, la función en la cual no se ha cumplido la expresión y la condición que se viola, salvo que a diferencia de g_assert, esta vez no detiene la ejecución del programa.

Estas son las funciones para depuración que se pueden encontrar dentro de la biblioteca GLib. Aunque no son las únicas, sí son las más comunes.

Funciones de registro.

GLib tiene cuatro macros básicas para el tratamiento mensajes, por niveles de importancia. Estas macros permiten al programador realizar un registro del recorrido del programa especificando el nivel de importancia de los sucesos acontecidos dentro del mismo.

Estas cuatro macros reciben parámetros de la misma forma que lo hacían g_print o printf. De este modo, no sólo se tendrá un registro por niveles, sino que, además, se podrá especificar un texto preformateado. De este modo, los mensajes pueden representar por pantalla el contenido de variables o cualquier otra información que nos pudiera ser útil. Las cuatro macros de las que estamos hablando son :

#define g_message (...);

#define g_warning (...);

#define g_critical (...);

#define g_error (...);

Como se puede observar, las cuatro macros tienen nombres bastante intuitivos. Se puede notar, por los nombres de las funciones, de que se encuentran ordenadas de menor a mayor importancia, siendo g_message menos importante que g_error. A continuación se explicará lo que hace cada una de ellas.

  • g_message es una macro que emite un mensaje normal con la información que se le ha pasado como parámetro. La información que emite esta macro no tiene por que estar asociada a un error en el programa. Su principal aplicación es el envío de mensajes con la información que se crea conveniente en el proceso de realización del programa.

  • g_warning emite un mensaje de aviso con la información que le ha sido pasada como parámetro. Esta información puede servir para ser avisados de situaciones peligrosas que se podrían dar en el programa.

  • g_critical cumple la misma función que g_warning, sólo que éste además tiene la posibilidad de abortar el programa usando la función g_log_set_always_fatal().

  • g_error emite el mensaje que le haya sido pasado como argumento y además aborta irremediablemente la ejecución del programa. Esta macro se usa para obtener información de donde ha sucedido el error irrecuperable con la seguridad de que, a causa de este error, el programa terminara abortando su ejecución de todas maneras.

Para una mejor comprensión se muestra el siguiente ejemplo. En él se han definido cuatro funciones, cada una de las cuales emite un mensaje diferente. En este ejemplo no se hace que g_critical tenga capacidad de abortar el programa porque si no, no veríamos como funciona g_error.

Ejemplo 6-2.5. Uso de mensajes de registro.

   1 /* ejemplo del uso de g_warning, g_critical, ... */
   2 #include <glib.h>
   3 void
   4 funcion_con_mensaje (){
   5         g_message ("Yo soy solamente un mensaje de aviso\n");
   6 }
   7 void
   8 funcion_con_aviso (){
   9         g_warning ("Esto es un aviso, algo puede ir mal\n");
  10 }
  11 void
  12 funcion_con_aviso_critico (){
  13         g_critical ("Esto se pone difícil :( \n");
  14 }
  15 void
  16 funcion_con_un_error (){
  17         g_error ("3, 2, 1... acabo de morirme. RIP\n");
  18 }
  19 main (){
  20         funcion_con_mensaje ();
  21         funcion_con_aviso ();
  22         funcion_con_aviso_critico ();
  23         funcion_con_un_error ();
  24 }

Aunque este ejemplo no tiene mucha funcionalidad, sirve para ver en acción a las cuatro macros en un mismo programa y da la posibilidad de contemplar en la salida de este programa los mensajes que envía a la consola. Si se ejecuta el ejemplo su salida sería:

** Message: Yo soy solamente un mensaje de aviso


** (process:13070): WARNING **: Esto es un aviso, algo puede ir mal


** (process:13070): CRITICAL **: Esto se pone difícil :( 


** ERROR **: 3, 2, 1... acabo de morirme. RIP

aborting...
Aborted (core dumped)

Como se puede ver, todas las funciones y macros que se han mostrado en este apartado serán especialmente útiles a la hora de realizar un programa y posibilitarán una vía estandarizada y muy flexible para la emisión de información al exterior. Facilitando la corrección del código y haciendo más facil la tarea del programador.

Trabajar con cadenas

Manipular el contenido de una cadena.

La manipulación de cadenas es una tarea muy común en la vida diaria de un desarrollador. El desarrollador necesita realizar tareas como duplicar cadenas, transformarlas en mayúsculas o verificar si algún carácter corresponde a un tipo en especial. Para este tipo de tareas, esta biblioteca dispone de una serie de funciones cuyo uso no dista mucho de las ya desarrolladas en la Libc.

Duplicación de cadenas.

Si lo que se desea es duplicar cadenas, las funciones g_strdup y g_strndup resolverán esa necesidad. Las dos funciones realizan el mismo trabajo: duplican la cadena que se le ha pasado como parámetro. La diferencia radica en que la segunda limita el número de caracteres que devuelve a un número que le es indicado por un segundo parámetro de la función.

gchar * g_strdup (const gchar * cadena_para_duplicar );

gchar * g_strndup (const gchar * cadena_para_duplicar , gsize longitud_maxima);

Verificación de caracteres.

El título se refiere al conjunto de funciones capaces de responder a preguntas como: ¿Este carácter es un dígito? o ¿este carácter es una letra?... Este tipo de funciones es muy usado en el tratamiento de cadenas y su funcionamiento es muy simple. Estas funciones reciben un carácter y devuelven cierto o falso. La siguiente tabla explica cuáles son esas funciones y cuál es la verificación que realizan. Se recomienda la observación del prototipo de la función g_ascii_isalnum, usado como ejemplo para una mejor comprensión de la tabla, ya que todas las funciones comparten el mismo prototipo.

gboolean g_ascii_isalnum (gchar carácter );`

Tabla 6-3.1. Funciones de verificación de caracteres.

Nombre de función

Es cierto si...

g_ascii_isalnum

Es un carácter alfanumérico.

g_ascii_isalpha

Es un carácter del alfabeto.

g_ascii_iscntrl

Es un carácter de control.

g_ascii_isdigit

Es un dígito.

g_ascii_isgraph

Es un carácter imprimible y no es un espacio.

g_ascii_islower

Es una letra minúscula.

g_ascii_isprint

Es un carácter imprimible.

g_ascii_ispunct

Es un carácter de puntuación.

g_ascii_isspace

Es un espacio.

g_ascii_isupper

Es una letra mayúscula.

Copia y concatenación de cadenas.

Otra acción típica para el programador es la copia de una cadena o la concatenación de de múltiples cadenas. Para realizar esta tarea se puede utilizar la función g_stpcpy. Esta función desempeña la tarea de copiar una cadena que se le pase como parámetro a una cadena destino, también pasada como parámetro. Esta función tiene la particularidad de devolver un puntero a la última posición de la cadena destino, de tal modo que, si se quisiera concatenar cadenas, sólo se tendría que pasar el puntero devuelto por el primer g_stpcpy al segundo como si fuera el destino. Así el segundo g_stpcpy entendería que ha de copiar la segunda cadena al final de la primera que se ha copiado anteriormente y así sucesivamente. Siguiendo este método, se podría concatenar una cadena tras otra.

gchar * g_stpcpy (gchar * cadena_destino , const gchar * cadena_origen );

GString : la otra manera de ver una cadena.

GString es un TAD (tipo abstracto de datos) que implementa GLib. Este TAD es un buffer de texto que tiene la capacidad de expandirse, realojando memoria automáticamente, sin que el programador tenga que actuar. Esto es realmente útil para el trabajo con cadenas, porque cuando se reserva memoria para texto y, más adelante, es insuficiente, el programador tiene que tomarse la molestia de reservar más. Por eso GLib implementa GString, que facilita enormemente el manejo de cadenas sin necesidad de estar pendientes de su gestión de memoria.

Para empezar a estudiar este TAD, lo primero que se debe conocer es la estructura de datos sobre la que esta diseñada y después se estudiaran las funciones que interactúan con esa estructura.

   1                   struct GString
   2                   {
   3                       gchar  *str;                                      (1)
   4                       gsize len;                                        (2)
   5                       gsize allocated_len;
   6                   };

(1)

  • En este puntero a una cadena de caracteres, es donde se almacenará el contenido de la cadena que se especifique. De este modo, si en algún momento se necesita el contenido de la cadena, sólo es necesario acudir a este campo.

(2)

  • Este campo hace referencia a la longitud total de la cadena que está alojada en el campo str de la estructura GString. De este modo, si en str está alojada la cadena "hola", el campo len contendrá el número 4.

Ahora se ha observado cómo es la estructura GString. El siguiente paso será ver las funciones que trabajan sobre GString con el fin de sacarle el mayor partido a este tipo abstracto de datos.

Cómo se aloja una cadena con GString.

Alojar una cadena con GString es extremadamente sencillo e intuitivo. Para ello se utilizará la función g_string_new (), que tiene la forma:

GString * g_string_new (const gchar * cadena_inicial );

Esta función recibe como parámetro una cadena y devuelve la estructura GString. De este modo, dentro de la estructura GString, se tendrá toda la información y, como se puede observar, no se ha sido necesario realizar movimiento alguno para alojar memoria.

Ejemplo 6-3.1. Crear una cadena con GString.

   1 /* ejemplo del uso de g_string_new */
   2 #include <glib.h>
   3 
   4 int
   5 main ()
   6 {
   7         
   8         GString *cadena ;
   9         
  10         cadena = g_string_new ("!Qué fácil es hacer una cadena!");
  11         
  12         g_print ("la cadena que hemos alojado : %s\n", cadena->str);
  13         g_print ("la longitud de la cadena es : %d\n", cadena->len);
  14         return 0;
  15 }

Como se puede ver en la ejecución de este ejemplo, la creación de una cadena con GString es muy sencilla. Pero, además, GLib proporciona otra función para crear cadenas. La sinopsis de esta función es:

GString * g_string_new_len (const gchar * cadena_inicial,
                            gssize longitud_inicial );

La ventaja que tiene el uso de g_string_new_len es que permite especificar una longitud inicial y esto trae consigo que la cadena introducida no tiene necesariamente que estar terminada con el carácter NULL ("\0") y, además, puede tener varios caracteres NULL embebidos dentro de la cadena en cuestión.

Cómo puedo liberar un GString

Siendo GString una estructura, lo suyo sería liberar uno a uno todos los campos pero, gracias a la implementación que ofrece GLib, se dispondrá de una función que ayuda con la liberación de memoria que se ha reservado con la creación de un GString. La sinopsis de esta función sería:

gchar * g_string_free (GString * cadena , gboolean liberar_contenido_cadena );

Con la función g_string_free, se podrá liberar el GString que se entrega como primer argumento a la función. Además, esta función también da la posibilidad de liberar o no la cadena que estaría dentro de GString. Y esto se conseguiría si como segundo parámetro se le pasara el booleano TRUE. En caso contrario, si se le pasara un booleano FALSE, la cadena de caracteres no sería liberada.

Añadir, insertar y borrar cadenas en un GString.

Con los conocimientos necesarios para crear y liberar este tipo de datos, ha llegado el momento de aprender a trabajar con la funciones que tratan la información que aloja y, para empezar, se aprenderá como añadir texto a una cadena creada como GString. A la hora de añadir texto, puede interesar añadir texto al final o al principio de la cadena y se podrá hacer con GString. Las funciones que se verán ahora son exactamente iguales, salvo que contienen las palabras append o prepend, que indican que la función añade el texto al final o al principio respectivamente.

GString * g_string_append (GString * cadena,
                           gchar * cadena_para_añadir_al_final );

GString * g_string_prepend (GString * cadena,
                            gchar * cadena_para_añadir_al_principio );

Estas son las primeras funciones para el añadido de texto que se repasarán. La primera, g_string_append, se encargará de añadir el texto pasado como parámetro al final de la cadena contenida dentro del GString y la segunda, g_string_prepend, se encargará de añadir el texto al principio.

Con el siguiente ejemplo, se demostrará el uso práctico de estas funciones.

Ejemplo 6-3.2. Añadir cadena a un GString.

   1 /* ejemplo del uso de g_string_append y g_string_prepend */
   2 #include <glib.h>
   3 
   4 int
   5 main ()
   6 {
   7 
   8         GString *cadena ;
   9         
  10         cadena = g_string_new ("*");
  11         cadena = g_string_append (cadena, "| texto añadido al final");
  12         cadena = g_string_prepend (cadena, "texto añadido al principio |");
  13         
  14         g_print ("\n%s\n", cadena->str);
  15         return 0;
  16 }

Una vez que se sabe añadir texto por delante y por detrás de la cadena contenida dentro del GString, el siguiente paso natural sería aprender cómo se pueden introducir datos dentro de las cadenas, es decir, insertar una cadena. En el caso que se tenga un GString con una cadena. Con la función g_string_insert se puede insertar, en la posición elegida, la cadena que desee. Como se puede observar, en la sinopsis de la función y en el siguiente ejemplo, Lo único que se debe hacer es indicar la posición en la cual se quiere insertar la cadena y la cadena que se insertará.

GString * g_string_insert (GString * cadena, gssize posicion,
                           gchar * cadena_para_insertar );

Ejemplo 6-3.3. Insertar una cadena a un GString.

   1 /* ejemplo del uso de g_string_insert */
   2 #include <glib.h>
   3 
   4 int
   5 main ()
   6 {
   7         
   8         GString *cadena ;
   9         gchar *cadena_para_insertar = "muy muy ";
  10         
  11         cadena = g_string_new ("El día esta soleado");
  12         cadena = g_string_insert (cadena, 13, cadena_para_insertar);
  13         
  14         g_print ("\n%s\n", cadena->str);
  15         
  16         g_string_free (cadena, TRUE);
  17         return 0;     
  18 }

Con las funciones mostradas anteriormente, se puede añadir información a un GString. Si lo que se desea es borrar, se cuenta con la función g_string_erase. Sólo hay que indicar la posición desde la que quiere empezar a borrar y el número de caracteres que desea borrar desde esa posición.

GString * g_string_erase (GString * cadena, gssize posicion,
                          GString * numero_de_caracteres_que_desea_borrar );

Ejemplo 6-3.4. Borrar una cadena a un GString.

   1 /* ejemplo del uso de g_string_erase */
   2 #include <glib.h>
   3 
   4 int
   5 main ()
   6 {
   7         
   8         GString *cadena ;
   9         
  10         cadena = g_string_new ("Esto es una cadena --esto sera borrado--");
  11         cadena = g_string_erase (cadena, 19, 21);
  12         
  13         g_print ("\n%s\n", cadena->str);
  14         g_string_free (cadena, TRUE);
  15         return 0;
  16 }

Introducir texto preformateado a un GString.

Esta es una de las aplicaciones más interesantes que se le puede dar a un GString. Este tipo de datos permite trabajar con texto preformateado, como cuando se trabaja con un printf. Esta opción siempre es interesante, pues una vez que se tenga ese texto dentro del GString, se podrán aprovechar las múltiples facetas que posee este tipo.

La siguiente función, g_string_printf, recordará a la ya conocida sprintf() de C estándar; la diferencia está en que el buffer tiene la capacidad de crecer automáticamente. Otra cosa a tener en cuenta en el uso de esta función es que cuando se usa, el contenido anterior existente (si existiese) es destruido.

void g_string_printf (GString * cadena , const gchar formato , ... );

Ejemplo 6-3.5. Crear texto preformateado con GString.

   1 /* ejemplo del uso de g_string_printf */
   2 #include <glib.h>
   3 
   4 int
   5 main ()
   6 {
   7         
   8         GString *cadena ;
   9         gchar *frase = "lo bueno, si breve, dos veces bueno" ;
  10         gint numero = 7 ;
  11         
  12         cadena = g_string_new ("");
  13         g_string_printf (cadena, "FRASE TIPICA : %s\nNUMERO DE LA SUERTE : %d",
  14                          frase, numero);
  15         
  16         g_print ("%s\n",cadena->str);
  17         g_string_free (cadena, TRUE);
  18         return 0;       
  19 }

GLib también dispone de una función similar a la anterior. La única diferencia estriba en que esta función añade el texto preformateado. Es decir, si se tuviera un texto dentro del GString, el texto formateado se añadiría detrás del texto existente. Esto puede resultar útil en el sentido de que la anterior función destruía el contenido anterior y puede que eso no le convenga.

void g_string_append_printf (GString * cadena , const gchar formato , ... );

Ejemplo 6-3.6. Añadir texto preformateado con GString.

   1 /* ejemplo del uso de g_string_append_printf */
   2 #include <glib.h>
   3 
   4 int
   5 main ()
   6 {
   7         
   8         GString *cadena ;
   9         gchar *frase = "lo bueno, si breve, dos veces bueno" ;
  10         gint numero = 7 ;
  11         
  12         cadena = g_string_new ("Línea ya existente\n");
  13         g_string_append_printf (cadena,
  14                                 "FRASE TIPICA : %s\nNUMERO DE LA SUERTE : %d",
  15                                 frase, numero);
  16         g_print ("%s\n",cadena->str);
  17         g_string_free (cadena, TRUE);
  18         return 0;     
  19 }

Otras funciones útiles para el manejo de GString.

En las secciones anteriores se han explicado las funciones más comunes con las que poder manejar una estructura del tipo GString, pero estas funciones no son las únicas que existen para trabajar con este tipo de datos. En la siguiente sección, se explicarán otras que, aunque no son tan fundamentales, pueden resultar útiles.

Para empezar, puede que no siempre sea de gran interés insertar o añadir una cadena y sólo se quiera trabajar con un carácter. Para ello se dispone de estas funciones que, aunque surten el mismo efecto que si se usase g_string_append y similares, con un sólo carácter, puede que estas funciones sirvan para facilitar la legibilidad del código en un momento dado.

GString * g_string_append_c (GString * cadena , gchar caracter );

GString * g_string_prepend_c (GString * cadena , gchar caracter);

GString * g_string_insert_c (GString * cadena, gssize posicion,
                             gchar * caracter );

Ejemplo 6-3.7. Añadir e insertar caracteres a un GString.

   1 /* ejemplo del uso de g_string_{insert|append|prepend}_c */
   2 #include <glib.h>
   3 
   4 int
   5 main ()
   6 {
   7         
   8         GString *cadena;
   9         gchar caracter ;
  10         
  11         cadena = g_string_new ("");
  12         
  13         caracter = 'c' ;
  14         cadena = g_string_append_c (cadena, caracter);
  15         caracter = 'a' ;
  16         cadena = g_string_prepend_c (cadena, caracter);
  17         caracter = 'b' ;
  18         cadena = g_string_insert_c (cadena, 1, caracter);
  19         
  20         g_print ("la cadena resultante es... %s\n",cadena->str);
  21         g_string_free (cadena, TRUE);
  22         return 0;     
  23 }

Otras acciones que pueden resultar interesantes son la capacidad de truncar un texto por el lugar que se desee y la capacidad de convertir todo el texto a mayúsculas o minúsculas. Pues para satisfacer este tipo de necesidades se dispone de g_string_truncate, cuya sintaxis se muestra a continuación.

GString * g_string_truncate (GString * cadena , gsize longitud );

longitud se refiere al número de caracteres desde el principio de la cadena que se desea permanezcan una vez terminada la función.

GString * g_string_up (GString * cadena );

GString * g_string_down (GString * cadena );

La primera función, g_string_up, es la que convierte a mayúsculas un texto íntegro del GString y la segunda, g_string_down la que lo convierte en minúsculas.

GQuarks.

Los Quarks son asociaciones entre una cadena de texto y un identificador numérico (gunint32). Ya sea proporcionando el GQuark o la cadena de texto es posible obtener la información relacionada el uno con el otro. Los Quarks son utilizados en los Datasets y en los Keyed Data List.

Para crear un Quark nuevo de una cadena de texto se usa g_quark_from_string () o g_quark_from_static_string (). Para encontrar la cadena de texto correspondiente a un GQuark se utiliza g_quark_to_string (). Y para encontrar el Quark de una cadena de texto se puede usar g_quark_try_string ().

GQuark g_quark_from_string (const gchar *string );

Obtiene el identificador GQuark de la cadena que se pase como parámetro. Si la cadena de texto no tiene asociado un GQuark, un nuevo GQuark es creado, usando una copia de la cadena de texto.

GQuark g_quark_from_static_string (const gchar *string );

Obtiene el identificador GQuark de una cadena de texto estática. Si la cadena de texto no tiene asociado un GQuark, un nuevo GQuark es creado, vinculándolo con la cadena.

Esta función es idéntica a g_quark_from_string () excepto que si un GQuark es creado la propia cadena de texto es usada en lugar que una copia. Esto ahorra memoria, pero solo puede usarse si la cadena de texto siempre existe. Esto puede usarse con cadenas de texto que están alojadas estáticamente en el programa principal, pero no con las alojadas en memoria en el módulo cargado dinámicamente.

const gchar * g_quark_to_string (GQuark quark );

Obtiene la cadena de texto asociada con un GQuark.

GQuark g_quark_try_string (const gchar * string );

Obtiene el GQuark asociado con la cadena de texto. Regresa el GQuark asociado con la cadena de texto, o 0 si no hay un GQuark asociado con la cadena de texto.

GQuark es usado junto con Keyed Data Lists y Datasets, que son listas para organizar elementos que son accedidos por medio de una cadena de texto o por medio de un identificador GQuark, y Datasets es utilizado para asociar grupos de datos con posiciones de memoria.

Jugando con el tiempo

Funciones de manejo de fechas y horas.

GLib proporciona una API completa para poder programar, utilizando fechas dentro de nuestras aplicaciones de forma sencilla. El objetivo fundamental es dar soporte a las fechas, cuya programación puede ser pesada sin estas funciones, aunque también se proporcionan algunos métodos para relacionar tiempos y fechas. Para ello, GLib dispone de la estructura GDate, la cual es la estructura que incorpora toda la información sobre las fechas y, además, una serie de funciones que interactúan con esa estructura.

Antes de empezar, han de aclararse unos conceptos sobre la expresión del tiempo, en cuanto al formato de las fechas se refiere. Hay dos formas de expresar las fechas: día-mes-año y formato Juliano; este último es simplemente el número de días que han transcurrido desde una fecha de referencia (en el caso de GLib, dicha fecha es el 1 de Enero del año 1).

Creación de una nueva fecha.

El primer paso para poder utilizar una fecha es crearla. Para crear una fecha se dispone de varias funciones, la primera, g_date_new, permite crear una estructura GDate sin ningún valor. Esta función resultará útil más adelante cuando se vea como ajustar la estructura GDate a una determinada fecha. Las dos siguientes permiten crear una fecha pero, al contrario que la anterior, que sólo crea la estructura, éstas añaden además la fecha: la función g_date_new_julian, que nos permite crear una fecha utilizando el formato juliano y, por último, g_date_new_dmy, a la que se le pasa un día, mes y año para crear el GDate.

GDate * g_date_new ( void );

GDate * g_date_new_julian (guint32 dia_juliano );

GDate * g_date_new_dmy (GDateDay dia , GDateMonth mes , GDateYear año );

En esta última función, es conveniente que se examinen los tipos de datos GDateDay, GDateMonth, GDateYear.

El tipo de datos GDateDay es un entero (entre 1 y 31) que representa el día del mes. La constante G_DATE_BAD_DAY representa un día inválido del mes.

El tipo GDateMonth es un tipo enumerado, por tanto se puede referir a él con un número del 1 al 12, refiriéndose a los meses de enero a diciembre o también con los elementos G_DATE_JANUARY, G_DATE_FEBRUARY, etc. La constante G_DATE_BAD_MONTH representa un mes inválido.

Y, por último, está el tipo GDateYear. Este tipo es, en realidad, un entero; por tanto, para representar el año, basta con poner el número del año deseado.

Midiendo intervalos de tiempo con GTimer.

Dentro del mundo de la programación, medir el tiempo entre dos instantes dentro del programa es una actividad bastante común. Por ejemplo, en programas que transfieren un archivo y miden la velocidad de transferencia o en programas de instalación, para mostrar por pantalla el tiempo de instalación usado. La medida del tiempo es tan necesaria como, a veces, dependiente de la plataforma para la que se desarrolle. Por eso, GLib provee la estructura GTimer y sus funciones asociadas, gracias a lo cual, se podrá calcular intervalos de tiempo con una resolución del orden de los microsegundos.

Descripción de las funciones para manejar GTimer.

La estructura GTimer es opaca para el programador. Todo lo necesario para gestionar GTimer se realiza con las funciones que se describen a continuación, lo que quiere decir que no se mostrará en el libro la estructura en detalle como se hizo en otras secciones, ya que ni el mismo programador la ve. Sólo se interactúa con ella a través de funciones, como ya se ha dicho.

Para empezar, lo primero que se ha de hacer, como en la gran mayoría de estructuras de GLib, es inicializar o crearla. Para este fin se dispone de la función g_timer_new. Esta función se encarga única y exclusivamente de inicializar la estructura GTimer.

GTimer * g_timer_new ( void );

Una vez inicializada la estructura GTimer, lo siguiente es ver qué se puede hacer con ella. Como se mencionó previamente, esta estructura está dedicada a medir intervalos de tiempo y se propuso el ejemplo de un programa en el cual se transfiere un archivo y mide el tiempo de transferencia. Si se abstrae el problema en sí y se fija la atención solamente en lo que tendría que hacer el programa para medir los tiempos, se llegará a la conclusión que se requiere una función que arranque el contador de tiempo para que, cuando empezase la transferencia, se marcase de alguna manera el tiempo en el que se empezó a transferir, y una función que pare el contador cuando el archivo acabe de transferirse. Y, por último, una función con la cual se pueda consultar el tiempo en un momento dado, una vez arrancado el contador. Pues todas esas funciones están implementadas en GTimer.

Para empezar, existe la función g_timer_start, que tiene la función de marcar el tiempo de inicio en el que se ha arrancado el contador. Luego está g_timer_stop que marca el momento final de la medida de tiempo. Y por último g_timer_elapsed, que tiene un carácter más complejo, pero que resulta sencillo de comprender. Esta función devuelve el número de segundos que hayan transcurrido y, si se desea más precisión el puntero a microsegundos devolverá el número de microsegundos. Ahora bien, si se ha iniciado el contador pero aún no se ha detenido con g_timer_stop, obtiene el tiempo transcurrido desde que se inició el contador. Si se ha llamado a g_timer_stop, obtiene el tiempo transcurrido entre el inicio del contador de tiempo y el momento en que fue detenido.

void g_timer_start ( GTimer * contador );

void g_timer_stop ( GTimer * contador );

gdouble g_timer_elapsed ( GTimer * contador , gulong * microsegundos );

Ahora, en un contexto un poco más avanzado, GTimer también dispone de una función que reinicia el contador: g_timer_reset. Esta función es equivalente a llamar a g_timer_start si el contador ya está activo, es decir, reinicia el contador de tiempo poniendo como instante inicial, el momento de la llamada a esta función. En el caso de estar parado, lo pone a cero.

GTimer * g_timer_reset ( GTimer * timer );

Y, por ultimo, y dado que la estructura GTimer es una estructura opaca al desarrollador, necesitamos una función que libere todos los recursos de memoria que haya requerido la estructura, es decir, una función que libere de memoria la estructura GTimer. Para ello está g_timer_destroy.

GTimer * g_timer_destroy ( GTimer * timer );

Como unas líneas de código valen más que mil palabras, aquí se propone un ejemplo que aclara dudas sobre el uso de estas funciones si las hubiere.

Ejemplo 6-4.1. Ejemplo de uso de GTimer

   1 /* ejemplo del uso de GTimer */
   2 #include <unistd.h> /* contiene la función sleep */
   3 #include <glib.h>
   4 
   5 int
   6 main ()
   7 {
   8         GTimer *timer1, *timer2;
   9         gdouble elapsed;
  10         gulong usec;
  11         
  12         timer1 = g_timer_new (); /* Llama implícitamente a g_timer_start() */
  13         timer2 = g_timer_new ();
  14         
  15         g_print ("Duermo 5 segundos...");
  16         sleep (5);
  17         g_print ("\n\n");
  18         
  19         g_print ("Paro el timer 1...\n");
  20         g_timer_stop (timer1);
  21         
  22         elapsed = g_timer_elapsed (timer1, &usec);
  23         g_print ("Tiempo transcurrido timer 1: %gs %luus\n", elapsed, usec);
  24         
  25         elapsed = g_timer_elapsed (timer2, &usec);
  26         g_print ("Tiempo transcurrido timer 2: %gs %luus\n\n", elapsed, usec);
  27         
  28         g_print ("Duermo 1 segundo más...\n");
  29         sleep (1);
  30         
  31         elapsed = g_timer_elapsed (timer1, &usec);
  32         g_print ("Tiempo transcurrido timer 1 (igual que antes): \
  33                   %gs %luus\n", elapsed, usec);
  34         elapsed = g_timer_elapsed (timer2, &usec);
  35         g_print ("Tiempo transcurrido timer 2 (un segundo más): \
  36                   %gs %luus\n\n", elapsed, usec);
  37         
  38         g_print ("Reseteo el timer 1 (que esta parado).\n");
  39         g_timer_reset (timer1);
  40         g_print ("Reseteo el timer 2 (que esta activo).\n");
  41         g_timer_reset (timer2);
  42         
  43         g_print ("Duermo otro segundo más...\n");
  44         sleep (1);
  45         
  46         elapsed = g_timer_elapsed (timer1, &usec);
  47         g_print ("Tiempo transcurrido timer 1 \
  48                   (0, no se ha llamado a g_timer_start()): \
  49                   %gs %luus\n", elapsed, usec);
  50         
  51         elapsed = g_timer_elapsed (timer2, &usec);
  52         g_print ("Tiempo transcurrido timer 2 (1 segundo): \
  53                   %gs %luus\n\n", elapsed, usec);
  54         
  55         g_print ("Llamo a g_timer_start () con timer 1.\n");
  56         g_timer_start (timer1);
  57         
  58         g_print ("Y vuelvo a dormir, 2 segundos...\n");
  59         sleep (2);
  60         
  61         elapsed = g_timer_elapsed (timer1, &usec);
  62         g_print ("Tiempo transcurrido timer 1 (2 segundos): \
  63                   %gs %luus\n", elapsed, usec);
  64         elapsed = g_timer_elapsed (timer2, &usec);
  65         g_print ("Tiempo transcurrido timer 2 (3 segundos): \
  66                   %gs %luus\n\n", elapsed, usec);
  67         
  68         g_timer_destroy (timer2);
  69         g_timer_destroy (timer1);
  70         
  71         return 0;
  72 }

Miscelánea de funciones

Números aleatorios.

En determinadas ocasiones es necesario trabajar con valores aleatorios, por ejemplo a la hora de programar juegos. En los juegos, muchas veces, hace falta elegir al azar qué carta se va a levantar, dónde se va a poner la mina, etc. Para tomar este tipo de decisiones hacen falta valores aleatorios, es decir, al azar o que se acerquen mucho a ello. Debido a esta necesidad se ha implementado en GLib la biblioteca gran.h, que consiste en una estructura de datos GRand y un conjunto de funciones. Estas funciones sirven para inicializar la estructura GRand, darle valores iniciales y obtener números aleatorios de distintos tipos.

La referencia a los tipos en el párrafo anterior es en el sentido más informático de la palabra, porque todos son números aleatorios, con la diferencia de que en unos se obtiene un guint32, gdouble, gboolean (este último, no siendo número, sino un valor booleano). Después algunas funciones establecen rangos para los números. Un número aleatorio entre el X y el Y.

Cuando se habla de números pseudoaleatorios, hay que irremediablemente hablar de dos conceptos: PRNG (generador de números pseudo aleatorios) y "semilla".

Un PRNG no es más que una estructura de datos con algoritmos asociados que, a partir de un valor inicial, genera secuencias de números en un orden, aparentemente aleatorio o, lo que es lo mismo, al azar y es aparentemente ya que no es real; si se tuviera la tecnología y el tiempo suficiente, se podría encontrar un cierto orden lógico en las secuencias y predecirlas, pero eso es otro tema; a los efectos, serán considerados como realmente aleatorios.

En el caso de GLib, este PRNG, será GRand, que es una estructura que, aunque oculta a la visión del programador, es usada internamente por las funciones de la biblioteca grand.h.

El otro concepto es el de semilla. La semilla, no es más que un valor inicial mencionado con anterioridad. Un valor, de ser posible, bastante aleatorio en sí mismo. Para conseguir esta aleatoriedad, se le entrega este valor a la función correspondiente. En caso de que no se quiera pasar la semilla, el constructor de GRand, g_rand_new(), se encargará de obtener un valor aleatorio de /dev/urandom, si existe, o la hora actual en caso contrario.

Para usar este generador de números pseudo aleatorios, es necesario incluir la biblioteca grand.h.

   1 #include <grand.h>

Para obtener números aleatorios rápidamente se pueden usar las siguientes funciones:

void g_random_set_seed(guint32 semilla);

Con esta función se establece la semilla para la generación de aleatorios. Estableciendo esta semilla con un mismo número cada vez que se ejecute nuestro código, se obtendrá el mismo resultado para los números aleatorios que se generen. Si se desea que los valores aleatorios no coincidan nunca, basta con no establecer la semilla.

gboolean g_random_boolean();

Con esta función, se obtiene un booleano aleatorio.

guint32 g_random_int(void);

Se obtiene un gunt32 distribuido sobre el rango [0..2^32-1].

gint32 g_random_int_range(gint32 principio, gint32 end);

Se obtiene un gint32 distribuido sobre el rango [principio..fin-1].

gdouble g_random_double(void);

Se obtiene un gdouble distribuido sobre el rango [0..1].

gdouble g_random_double_range(gdouble principio, gdouble fin);

Se obtiene un gdouble distribuido sobre el rango [principio..fin].

En todos estos casos, internamente, se ha hecho uso de un tipo de dato: GRand. Si lo que se requiere es obtener una serie de números aleatorios reproducibles, será una mejor opción trabajar directamente con este dato.

La manera de hacerlo es crear primero un GRand y después trabajar con las funciones del tipo g_rand*.

GRand *g_rand_new(void);

Crea un nuevo GRand. Los números aleatorios necesitan una semilla para inicializarse, pero en esta función no se le entrega ninguna, así que se utilizará como semilla un valor de /dev/urandom, si existe, o la hora actual en caso contrario.

GRand *g_rand_new_with_seed(guint32 semilla);

En este caso se crea un GRand usando una semilla.

void g_rand_set_seed(GRand *rand, guint32 semilla);

Con esta función se establece una nueva semilla a un GRand.

void g_rand_free(GRand *rand);

Libera la memoria usada por un GRand.

gboolean g_rand_boolean(GRand *rand);

Devuelve el siguiente booleano aleatorio desde rand.

guint32 g_rand_int(GRand *rand);

Devuelve el siguiente guint32 aleatorio desde rand entre los valores [0..2^32-1].

gint32 g_rand_int_range(GRand *rand, gint32 principio, gint32 fin);

Devuelve el siguiente gint32 aleatorio desde rand entre los valores [principio..fin-1].

gdouble g_rand_double(GRand *rand);

Devuelve el siguiente gdouble aleatorio desde rand entre los valores [0..1].

gdouble g_rand_double_range(GRand *rand, gint32 principio, gint32 fin);

Devuelve el siguiente gdouble aleatorio desde rand entre los valores [principio..fin].

Con estas funciones se pueden obtener números o series de números pseudoaleatorios, de una manera rápida y fácil. Además con la ventaja añadida de la portabilidad que nos provee GLib.

Funciones de información sobre entorno.

Muchas veces, en el desarrollo de aplicaciones, es necesario conocer e interactuar con información del entorno en el cual se esté trabajando. Por ejemplo, en un momento dado, podría ser necesario conocer información del usuario como su nombre, su directorio de trabajo o el directorio donde se pueda almacenar información temporal. Para todas estas acciones e incluso algunas más, se dispone de las siguientes funciones.

gchar* g_get_user_name (void);

gchar* g_get_home_dir (void);

gchar* g_get_tmp_dir(void);

gchar* g_get_current_dir(void);

Después de haber visto la sintaxis de estas funciones no resulta muy difícil identificar para qué sirve cada una, pero por si queda alguna duda, la primera devolverá el nombre del usuario que esté ejecutando el programa en ese momento; la segunda, su directorio de trabajo; la tercera, el directorio temporal habilitado en su sistema para ese fin y la última función devolverá el directorio en el que se esté trabajando actualmente.

Estas funciones pueden resultar útiles, pero para aquellos que provengan del universo UNIX o GNU/Linux puede que no sea suficiente. Esto es debido a que, en este tipo de sistemas operativos, el uso de variables de entorno en sus diferentes shells es muy extendido y en un momento dado, podrá requerirse recurrir a ellas. Para ello existe la función g_getenv. La cual es realmente simple de utilizar ya que sólo requiere como parámetro la variable que se necesite y esta función devolverá su valor en una cadena.

gchar* g_getenv (const gchar * variable );

Ejemplo 6-5.1. Obtención de variables de entorno.

   1 /* ejemplo de como obtener información sobre las variables de entorno */
   2 #include <glib.h>
   3 
   4 int
   5 main ()
   6 {
   7         g_print("Yo soy el usuario %s\n", g_get_user_name());
   8         g_print("Vivo en %s\n", g_get_home_dir());
   9         g_print("Puedo escribir archivos temporales en %s\n", g_get_tmp_dir());
  10         g_print("Y ejecuto este programa desde %s\n", g_get_current_dir());
  11         g_print("Mi interprete de comandos es %s\n", g_getenv("SHELL"));
  12 
  13         return 0;
  14 }

Lo cual podría entregarnos un resultado similar a:

Yo soy el usuario basilio
Vivo en /home/basilio
Puedo escribir archivos temporales en /tmp
Y ejecuto este programa desde /home/basilio/Code/ejemplos
Mi interprete de comandos es /bin/bash

Bucles de ejecución

Los bucles de ejecución son estructuras que permiten realizar programas asíncronos, es decir, no bloqueantes; hacen posible ejecutar un bucle que permanece a la escucha de diversos eventos y que envía "señales" a distintas funciones que se registran como interesadas en determinados eventos.

De esta manera se pueden programar aplicaciones, que por ejemplo, realicen cierta función cada determinado tiempo, esto se logra mediante "alarmas", pero no solo se limita a intervalos de tiempo, también está la posibilidad de ejecutar funciones que respondan a eventos de dispositivos de E/S o intervalos de inactividad de un programa.

La manera de utilizar los bucles de ejecución es la siguiente:

  • Primero se crea la variable GMainloop
    • GMainLoop *bucle;

  • Luego el bucle
    • bucle = g_main_loop_new(NULL, FALSE);

  • Finalmente se ejecuta el bucle
    • g_main_loop_run(bucle);

  • Para detener el bucle se utiliza
    • g_main_loop_quit (bucle);

Una vez que se utilice g_main_loop_run, el control de la aplicación pasa a manos de Glib. Para poder hacer uso de las alarmas existen dos opciones, la primera cuando se cumple cierto intervalos de tiempo, g_timeout_add, y la otra cuando la aplicación no esta haciendo nada, g_idle_add.

Alarmas

Para utilizar un bucle conjuntamente con una alarma se realizan los pasos anteriormente explicados, pero se usa la siguiente función:

guint g_timeout_add (guint interval, GSourceFunc function, gpointer data);

Veamos los parámetros de esta función:

  • guint interval: Es el intervalo de tiempo en que se llama a la función, debe de estar en milisegundos.

  • GSourceFunc function: Función a llamar.

  • gpointer data: Dato que se pasa a la función como parámetro.

  • Retorno: El id del evento origen.

Tiempos de inactividad.

Lo mismo ocurre para los tiempos de inactividad, es decir aquellos en los que la aplicación no hace nada. Para poder usarlos se utiliza la siguiente función:

guint g_idle_add (GSourceFunc function, gpointer data);

Veamos los parámetros de esta función:

  • GSourceFunc function: Función a llamar.

  • gpointer data: Dato que se pasa a la función como parámetro.

  • Retorno: El id del evento origen.

Un factor a considerar es que en ambos casos cuando se llama a la función externa, la prioridad de esta es asignada de modo por omisión, G_PRIORITY_DEFAULT. Como ya se había comentado, la prioridad de la función a llamar es asignada automáticamente; así pues, la función puede ser retrasada por otros procesos de mayor prioridad.

En ambas funciones, la llamada a la función externa tiene que se de un tipo determinado, es decir, GSourceFunc representa un puntero a una función de la siguiente forma:

gboolean funcion_a_llamar (gpointer data);

Así la función es llamada en varias ocasiones hasta que devuelve FALSE y sale del bucle.

Ejemplo 6-6.1. Veamos un ejemplo de la forma de utilizar la función g_timeout_add.

   1 #include <glib.h>
   2 
   3 gboolean funcion_a_llamar(gpointer data);
   4 
   5 int
   6 main (int argc, char *argv[])
   7 {
   8         GMainLoop *bucle;
   9         guint b;
  10         guint i = 5000;
  11         
  12         bucle = g_main_loop_new(NULL, FALSE);
  13         b = g_timeout_add(i, funcion_a_llamar, bucle);
  14         g_main_loop_run(bucle);
  15         g_print("Ya salimos del bucle.\n");
  16         return 0;
  17 }
  18 
  19 gboolean funcion_a_llamar(gpointer data)
  20 {
  21         gint i=0;
  22         do{
  23                 g_print("Uso de g_timeout_add: %d\n", i);
  24                 i++;
  25         }while(i<3);
  26         g_print("Ahora vamos a finalizar el bucle.\n");
  27         g_main_loop_quit(data);
  28         
  29         return FALSE;
  30 }

Tratamiento de ficheros y canales de Entrada/Salida

Para el tratamiento de archivos y canales de entrada/salida en general, GLib provee un tipo de dato llamado GIOChannel, que permite encapsular archivos, sockets y tuberías. Esta abstracción no sólo posibilita el acceso uniforme a todos estos recursos, sino que también asegura portabilidad e integración al bucle de eventos.

Actualmente sólo hay soporte completo para UNIX, aunque el tratamiento con archivos puede hacerse independientemente de la plataforma.

Obtención de un GIOChannel.

Para crear un GIOChannel, primero se debe crear el descriptor de archivo de UNIX por los métodos convencionales (open, socket, etc.) y luego utilizar g_io_channel_unix_new o bien g_io_channel_new_file para acceder a archivos directamente.

GIOChannel *g_io_channel_unix_new(int fd);

Esta función devuelve el canal creado a partir del descriptor. Dicho parámetro se puede recuperar después utilizando la función g_io_channel_unix_get_fd.

GIOChannel *g_io_channel_new_file(const gchar *nombre_archivo,
                                  const gchar *modo, GError **error);

Devuelve un nuevo canal para acceder al archivo nombre_archivo. El modo tiene la misma forma que el parámetro modo de la función de C estándar fopen. Esto es:

  • r para lectura.

  • r+ para lectura y escritura.

  • w para escritura (Si el archivo no existe, lo crea; en caso contrario lo trunca).

  • w+ para lectura y escritura (con el archivo hace lo mismo que el parámetro w

  • a para escritura al final del archivo (lo crea si no existe).

  • a+ para lectura y escritura al final del archivo (lo crea si no existe).

El parámetro error debe apuntar a una estructura tipo GError previamente creada y es aquí donde se informa de cualquier problema al abrir el archivo. En este último caso la función retorna NULL.

Generalidades en el trabajo con GIOChannels.

Una vez obtenido el GIOChannel, en general, todas las llamadas a funciones para realizar alguna operación sobre el canal (lectura, escritura, etc.) tomarán como primer parámetro al propio canal y como último un puntero, error, a una estructura GError, donde se informará de cualquier inconveniente ocurrido al intentar la operación. Además, la mayoría de las funciones retornan un código del tipo GIOStatus que puede tomar los siguientes valores:

  • GIO_STATUS_ERROR: ocurrió un error (los detalles del mismo se encuentran en el parámetro error).

  • G_IO_STATUS_NORMAL: la operación se realizó con éxito.

  • G_IO_STATUS_EOF: se alcanzó el fin de archivo.

  • G_IO_STATUS_AGAIN: el recurso solicitado no está disponible (p.e., se intentó leer datos, pero no estaban en ese momento).

En caso de que el código de retorno sea G_IO_STATUS_ERROR, en la estructura GError se almacena otro código, indicando con mayor detalle la naturaleza del problema. Este nuevo código es de tipo GIOChannelError y puede tomar los siguientes valores:

  • G_IO_CHANNEL_ERROR_FBIG: el archivo es muy grande.

  • G_IO_CHANNEL_ERROR_INVAL: argumento inválido.

  • G_IO_CHANNEL_ERROR_IO: error general de entrada/salida.

  • G_IO_CHANNEL_ISDIR: el archivo es un directorio, pero se intentó acceder a él como un archivo regular.

  • G_IO_CHANNEL_ERROR_NOSPC: No hay más espacio disponible para escritura.

  • G_IO_CHANNEL_ERROR_NXIO: No existe tal dispositivo o dirección.

  • G_IO_CHANNEL_ERROR_OVERFLOW: el valor proporcionado es mayor de lo que permite su tipo.

  • G_IO_CHANNEL_ERROR_PIPE: algunos de los extremos de la tubería lo desconectó.

  • G_IO_CHANNEL_ERROR_FAILED: otros errores.

Los primeros valores tienen su correspondencia directa con los valores de la variable errno estándar de C. El último se reserva para otra clase de errores no identificados.

Los canales tienen un tiempo de vida controlado por conteo de referencias. Inicialmente, los canales se crean con una cuenta de 1 y, cuando la misma cae a 0, se destruyen. Para obtener una referencia se utiliza la función g_io_channel_ref y para liberar una referencia g_io_channel_unref. Cabe aclarar que, aunque el objeto GIOChannel se destruya, no necesariamente se cierra el canal. En concreto, si el objeto fue creado a partir de un descriptor de UNIX, el programador es responsable de cerrarlo (a menos que se utilice la función g_io_channel_set_close_on_unref.

Las funciones mencionadas tienen la siguiente forma:

void g_io_channel_ref(GIOChannel *canal);

void g_io_channel_unref(GIOChannel *canal);

Para cerrar de un canal se utiliza g_io_channel_shutdown:

GIOStatus g_io_channel_shutdown(GIOChannel *canal, gboolean descarga,
                                GError **error);

Aquí, el parámetro descarga controla si se escriben los datos que pueda haber en el buffer interno del canal antes de cerrarlo (para forzar una descarga en cualquier momento se puede utilizar g_io_channel_flush.

Operaciones básicas.

GLib provee una serie de funciones para acceder a un canal abierto de diversas maneras: por caracteres individuales, por bloques de cantidad de bytes fijos y por líneas.

Si el tipo de canal lo permite, la función g_io_channel_seek_position sirve para establecer la siguiente posición donde se leerá o escribirá. Normalmente sólo los archivos permiten posicionamiento aleatorio.

GIOStatus g_io_channel_seek_position(GIOChannel *canal, glong posicion,
                                     GSeekType tipo_posicion, GError **error);

La semántica de posicion (que es un número con signo) queda determinada por el tipo_posicion, que puede tomar los siguientes valores:

  • G_SEEK_CUR: la nueva posición es relativa a la actual.

  • G_SEEK_SET: la nueva posición se toma a partir del principio del archivo.

  • G_SEEK_END: la nueva posición es absoluta respecto al fin de archivo.

Para efectuar lecturas en un canal se utilizan algunas de las siguientes funciones:

GIOStatus g_io_channel_read_chars(GIOChannel *canal, gchar *buffer,
                                  gsize tamaño_buffer, gsize *bytes_leidos,
                                  GError **error);

Lee una cantidad máxima (tamaño_buffer) de bytes del canal como caracteres y los almacena en buffer. Éste debe estar previamente reservado (p.e. con g_malloc). bytes_leidos contiene la cantidad de bytes efectivamente leídos (puede ser menor que la solicitada).

Es importante notar que si el canal está configurado para trabajar con caracteres Unicode, un carácter puede ocupar más de un byte y, si el buffer no es lo suficientemente grande, puede que no sea posible almacenar ningún carácter. En estas condiciones bytes_leidos es cero, pero no hay indicación de error.

GIOStatus g_io_channel_read_line(GIOChannel *canal, gchar **puntero_cadena,
                                 gsize *bytes_leidos, gsize *terminador,
                                 GError **error);

Lee una línea completa del canal y almacena el puntero a la misma en puntero_cadena. Una vez finalizada la operación con la línea, es responsabilidad del programador liberar la memoria con g_free. En bytes_leidos se devuelve la cantidad total de bytes leídos (incluyendo caracteres de fin de línea) y en terminador, sólo la cantidad perteneciente a la línea en sí. Tanto bytes_leidos como terminador pueden ser nulos si la información no es de interés.

GIOStatus g_io_channel_read_line_string(GIOChannel *canal, GString *cadena,
                                        gsize *terminador, GError **error);

Similar a g_io_channel_read_line, sólo que, para devolver el resultado, utiliza una estructura GString. terminador puede ser nulo.

GIOStatus g_io_channel_read_to_end(GIOChannel *canal, gchar **puntero_cadena,
                                   gsize *bytes_leidos, GError **error);

Lee todos los bytes disponibles en el canal hasta encontrar el fin de archivo. En puntero_cadena se devuelve un puntero al espacio de memoria asignado a tal fin que luego debe ser liberado con g_free.

Para la escritura se utiliza la contra parte de g_io_channel_read_chars:

GIOStatus g_io_channel_write_chars(GIOChannel *canal, gchar *buffer,
                                   gsize cantidad_bytes, gsize *bytes_escritos,
                                   GError **error);

Como su propio nombre indica, esta función escribe en el canal cantidad_bytes del buffer. Puede que no lleguen a escribirse todos los bytes que se solicitaron, por lo que la función devuelve la cantidad de bytes efectivamente escritos. Si cantidad_bytes es -1 se considera al buffer como terminado en nulo.

Se aplican las mismas restricciones en cuanto a codificación del canal que para g_io_channel_read_chars.

Integración de canales al bucle de eventos.

Uno de los aspectos más interesantes de los GIOChannels es que es posible integrarlos fácilmente al bucle de eventos y así, por ejemplo, hacer que la aplicación reaccione ante la llegada de datos.

Los posibles tipos de eventos que puede generar un canal están dados por el tipo GIOCondition que toma los siguientes valores:

  • G_IO_IN: hay datos disponibles en el canal.

  • G_IO_OUT: se pueden escribir datos al canal sin que éste bloquee.

  • G_IO_PRI: hay datos urgentes para leer.

  • G_IO_ERR: condición de error.

  • G_IO_HUP: desconexión; normalmente, se aplica a sockets y tuberías. Alguno de los extremos rompió la conexión.

  • G_IO_NVAL: solicitud no válida (p.e. el descriptor UNIX no está abierto).

La forma básica de integrar un objeto generador de eventos al bucle es obtener una estructura GSource. En el caso de los canales, la función para ello es g_io_create_watch:

GSource *g_io_create_watch(GIOChannel *canal, GIOCondition condición);

Esta función devuelve un GSource que luego se registra en algún bucle de eventos con g_source_attach. Sin embargo, por conveniencia, GLib provee un par adicional de funciones para integrar directamente un canal al bucle principal: g_io_watch y g_io_add_watch_full.

guint g_io_add_watch(GIOChannel *canal, GIOCondition condición,
                     GIOFunc función, gpointer datos_usuario);

Esta función, que devuelve el identificador de evento, registra el canal para que sea controlado según las condiciones especificadas. En caso de que se cumpla alguna de las condiciones, se llama a la función con los datos_usuario adicionales.

La función especificada en el registro debe tener la siguiente forma:

gboolean (* GIOFunc)(GIOChannel *canal, GIOCondition condición,
                     gpointer datos_usuario);

La condición que la función recibe es aquella que causó el evento en primer lugar. datos_usario es lo que se especificó en el momento del registro.

La otra función, g_io_add_watch_full permite además especificar la prioridad del canal y una función de notificación cuando el canal se desconecta del bucle de eventos.

Configuración avanzada de canales.

Codificación

Los GIOChannels son capaces de codificar o decodificar los caracteres que se escriben o leen del mismo. La codificación que se utiliza internamente es UTF-8, por lo que, eventualmente, se puede transformar de UTF-8 a alguna otra codificación para escribir a un archivo, o viceversa.

Si se necesita que un canal no efectúe transformación alguna a los datos (por ejemplo, para tratamiento de datos binarios) se debe optar por la codificación nula (NULL).

Las funciones para manipular la codificación son g_io_channel_set_encoding y g_io_channel_get_encoding.

GIOStatus g_io_channel_set_encoding(GIOChannel *canal,
                                    const gchar *codificación, GError **error);

G_CONST_RETURN gchar *g_io_channel_get_encoding(GIOChannel *canal);

Por omisión, la codificación externa utilizada para archivos es UTF-8. Para cambiarla se deben tener en cuenta las siguientes condiciones:

  • El canal acaba de ser creado.
  • El canal es para escritura solamente.
  • El canal es sobre un archivo y el puntero de lectura y escritura acaba de ser colocado con g_io_channel_seek_position.

  • La codificación actual es UTF-8 o nula.
  • Se ha llamado a una función de lectura y ha devuelto G_IO_STATUS_EOF o, en el caso de g_io_channel_read_to_end, G_IO_STATUS_NORMAL.

Terminador de línea.

En UNIX, el fin de línea se indica con un carácter de linefeed (código ASCII 10), pero esto puede no ser cierto para otras plataformas (p.e. Win32).

Para que las funciones de lectura y escritura por líneas den los resultados esperados para cada plataforma, en caso de ser necesario el acceso a archivos escritos en otra plataforma, se debe configurar el terminador de línea en el canal. Las funciones para ello son:

void g_io_channel_set_line_term(GIOChannel *canal, const gchar *terminador,
                                gint longitud);

G_CONST_RETURN gchar *g_io_channel_get_line_term(GIOChannel *canal,
                                                 gint *longitud);

Configuración de buffer interno.

La API de GIOChannel permite modificar alguno de los parámetros de funcionamiento interno del canal. Es posible modificar el tamaño del buffer interno del mismo para optimizar el rendimiento de lectura y escritura en casos especiales. Para ello se utilizan las funciones g_io_channel_get_buffer_size y g_io_channel_set_buffer_size.

gsize g_io_channel_get_buffer_size(GIOChannel *canal);

void g_io_channel_set_buffer_size(GIOChannel *canal, gsize tamaño);

Si en g_io_channel_set_buffer_size se especifica un tamaño 0, GLib elegirá un tamaño adecuado al canal.

Se ha visto antes que, utilizando el bucle de eventos, se puede supervisar un canal y producir llamadas a funciones ante determinados eventos. También es posible verificar manualmente si un canal tiene datos listos para la lectura o si se pueden escribir datos en él. Para ello se utiliza la función g_io_channel_get_buffer_condition.

GIOCondition g_io_channel_get_buffer_condition(GIOChannel *canal);

Esta función puede devolver solamente G_IO_IN y G_IO_OUT y, por supuesto, su significado es exactamente el mismo que se explicó antes.

Por último, con las funciones [#g_io_channel_set_flags g_io_channel_set_flags] y [#g_io_channel_get_flags g_io_channel_get_flags], es posible saber y, en parte, modificar el comportamiento que tiene un canal.

GIOFlags g_io_channel_get_flags(GIOChannel *canal);

GIOStatus g_io_channel_set_flags(GIOChannel *canal, GIOFlags banderas,
                                 GError **error);

Los valores que puede tomar el parámetro banderas son G_IO_FLAG_APPEND y/o G_IO_FLAG_NONBLOCK (los demás valores son de sólo lectura). El valor obtenido en g_io_channel_get_flags es una composición de:

  • G_IO_FLAG_APPEND: canal en modo de agregado.

  • G_IO_FLAG_NONBLOCK: canal en modo no bloqueante; esto es, si se intenta, p.e., leer y no hay datos disponibles, se devuelve el control inmediatamente con un estado de G_IO_STATUS_AGAIN.

  • G_IO_FLAG_IS_READABLE: el canal se puede leer.

  • G_IO_FLAG_IS_WRITEABLE: se puede escribir en el canal.

  • G_IO_FLAG_IS_SEEKABLE: se puede mover el puntero en el canal mediante la utilización de g_io_channel_seek_position.

Manejo de memoria dinámica

Sobre el tratamiento de memoria, GLib dispone de una serie de instrucciones que sustituyen a las ya conocidas por todos malloc, free, etc. y, siguiendo con el modo de llamar a las funciones en GLib, las funciones que sustituyen a las ya mencionadas son g_malloc y g_free.

Reserva de memoria.

La función g_malloc posibilita la reserva de una zona de memoria, con un número de bytes que le pasemos como parámetro. Además, también existe una función similar llamada g_malloc0 que, no sólo reserva una zona de memoria, sino que, además, llena esa zona de memoria con ceros, lo cual nos puede beneficiar si se necesita un zona de memoria totalmente limpia.

gpointer g_malloc (gulong numero_de_bytes );

gpointer g_malloc0 (gulong numero_de_bytes );

Existe otro conjunto de funciones que nos permiten reservar memoria de una forma parecida a cómo se hace en los lenguajes orientados a objetos. Esto se realiza mediante las siguientes macros definidas en GLib /gmem.h:

   1         /* Convenience memory allocators
   2         */
   3         #define g_new(struct_type, n_structs)           \
$^
                ((struct_type *) g_malloc (((gsize) sizeof (struct_type)) * ((gsize) (n_structs))))
   4         #define g_new0(struct_type, n_structs)          \
$^
                ((struct_type *) g_malloc0 (((gsize) sizeof (struct_type)) * ((gsize) (n_structs))))
   5         #define g_renew(struct_type, mem, n_structs)    \
$^
                ((struct_type *) g_realloc ((mem), ((gsize) sizeof (struct_type)) * ((gsize) (n_structs))))

Como se puede apreciar, no son más que macros basadas en g_malloc, g_malloc0 y g_realloc. La forma de funcionamiento de g_new y g_new0 es mediante el nombre de un tipo de datos y un número de elementos de ese tipo de datos, de forma que se puede hacer:

   1         GString *str = g_new (GString,1);
   2         GString *arr_str = g_new (GString, 5);

En estas dos líneas de código, se asigna memoria para un elemento de tipo GString, que queda almacenado en la variable str, y para un array de cinco elementos de tipo GString, que queda almacenado en la variable arr_str.

g_new0 funciona de la misma forma que g_new, con la única diferencia de que inicializa a 0 toda la memoria asignada. En cuanto a g_renew, ésta funciona de la misma forma que g_realloc, es decir, reasigna la memoria asignada anteriormente.

Liberación de memoria.

Cuando se hace una reserva de memoria con g_malloc y, en un momento dado, el uso de esa memoria no tiene sentido, es el momento de liberar esa memoria. Y el sustituto de free es g_free que, básicamente, funciona igual que la anteriormente mencionada.

void g_free (gpointer memoria_reservada );

Se presenta un ejemplo sencillo sobre el uso de g_malloc () y de g_free ().

Ejemplo 6-8.1. Uso de g_malloc () y g_free ().

   1 /* ejemplo del uso de g_malloc y g_free */
   2 #include <glib.h>
   3 int main (int argc, char *argv[])
   4 {
   5         gchar *text;
   6         gint len = strlen ("Hola a todos") + 1;
   7         text = g_malloc (len);
   8         g_snprintf (text, len, "Hola a todos");
   9         g_print ("%s\n", text);
  10         g_free (text);
  11         return 0;
  12 }

Realojamiento de memoria.

En determinadas ocasiones, sobre todo cuando se utilizan estructuras de datos dinámicas, es necesario ajustar el tamaño de una zona de memoria (ya sea para hacerla más grande o más pequeña). Para eso, GLib ofrece la función g_realloc, que recibe un puntero a memoria que apunta a una región que es la que será acomodada al nuevo tamaño y devuelve el puntero a la nueva zona de memoria. El anterior puntero es liberado y no se debería utilizar más:

gpointer g_realloc (gpointer memoria_reservada , gulong numero_de_bytes );

Perfil de uso de memoria.

Un perfil de uso de memoria es la información sobre el uso de memoria que hace nuestra aplicación. Para obtener un perfil de nuestro programa se hace uso de la siguiente función:

void g_mem_profile (void );

Para que se obtenga la información sobre el uso de memoria de nuestra aplicación se debe de inicializar una tabla de perfiles de memoria, esto se logra mediante la variable extern GMemVTable *glib_mem_profiler_table; la cual se debe de usar conjuntamente con g_mem_set_vtable() la cual debe ser llamada antes que cualquier otra función de glib.

Veamos un ejemplo sobre su uso:

Ejemplo 6-8.2. Uso de g_mem_profile ()

   1 /* ejemplo del uso de g_mem_profile */
   2 #include <glib.h>
   3 int main (int argc, char *argv[])
   4 {
   5         gchar *text;
   6         gint len = strlen ("Hola a todos") + 1;
   7         g_mem_set_vtable (glib_mem_profiler_table);
   8         text = g_malloc (len);
   9         g_snprintf (text, len, "Hola a todos");
  10         g_print ("%s\n", text);
  11         g_free (text);
  12         g_mem_profile ();
  13         return 0;
  14 }

El resultado es el siguiente:

Figura 6-8.1. Salida de g_mem_profile ().

mem_profile.png

Primero se invoca g_mem_set_vtable (). para la preparación del ambiente y asi obtener un perfil de la memoria empleada, y antes de finalizar pedimos un resumen con la función g_mem_profile ().

Estructuras de datos: listas enlazadas, pilas y colas

Listas enlazadas.

Introducción

La lista enlazada es un TDA que nos permite almacenar datos de una forma organizada, al igual que los vectores pero, a diferencia de estos, esta estructura es dinámica, por lo que no tenemos que saber "a priori" los elementos que puede contener.

En una lista enlazada, cada elemento apunta al siguiente excepto el último que no tiene sucesor y el valor del enlace es null. Por ello los elementos son registros que contienen el dato a almacenar y un enlace al siguiente elemento. Los elementos de una lista, suelen recibir también el nombre de nodos de la lista.

            struct lista {
            gint dato;                                           (1)
            lista *siguiente;                                    (2)
            };

(1)

  • Representa el dato a almacenar. Puede ser de cualquier tipo; en este ejemplo se trata de una lista de enteros.

(2)

  • Es un puntero al siguiente elemento de la lista; con este puntero enlazamos con el sucesor, de forma que podamos construir la lista.

Figura 6-9.1. Esquema de un nodo y una lista enlazada.

lista.png

Para que esta estructura sea un TDA lista enlazada, debe tener unos operadores asociados que permitan la manipulación de los datos que contiene. Los operadores básicos de una lista enlazada son:

  • Insertar: inserta un nodo con dato x en la lista, pudiendo realizarse esta inserción al principio o final de la lista o bien en orden.
  • Eliminar: elimina un nodo de la lista, puede ser según la posición o por el dato.
  • Buscar: busca un elemento en la lista.
  • Localizar: obtiene la posición del nodo en la lista.
  • Vaciar: borra todos los elementos de la lista

Después de esta breve introducción, que sólo pretende servir como recordatorio, pasaremos a ver cómo es la estructura GSList que, junto con el conjunto de funciones que la acompañan, forman el TDA lista enlazada en GLib.

GSList

La definición de la estructura GSList o, lo que es lo mismo, un nodo de la lista, está definido de la siguiente manera:

            struct GSList {
              gpointer data;                                     (3)
              GSList *next;                                      (4)
            };

(3)

  • Representa el dato a almacenar. Se utiliza un puntero genérico por lo que puede almacenar un puntero a cualquier tipo de dato o bien almacenar un entero utilizando las macros de conversión de tipos.

(4)

  • Se trata de un puntero al siguiente elemento de la lista.

Las macros de conversión disponibles son las siguientes:

  • GINT_TO_POINTER ()

  • GPOINTER_TO_INT ()

  • GUINT_TO_POINTER ()

  • GPOINTER_TO_UINT ()

Más adelante, en esta misma sección, se verán ejemplos del uso de estas macros.

Las funciones que acompañan a la estructura GSList y que implementan los operadores básicos de las listas enlazadas, son las siguientes:

Tabla 6-9.1. Operadores de inserción en listas enlazadas.

Operador

Funciones asociadas a GSList.

Insertar al principio.

GSList* g_slist_prepend (GSList *list, gpointer data)

Insertar al final.

GSList* g_slist_append (GSList *list, gpointer data)

Insertar en la posición indicada.

GSList* g_slist_insert (GSList *list, gpointer data, gint position)

Insertar en orden.

GSList* g_slist_insert_sorted (GSList *list, gpointer data, GCompareFunc func)

Las funciones de inserción al principio de la lista, g_slist_prepend, y al final, g_slist_append, son sencillas de usar. Sólo hay que pasarles como parámetros la lista donde queremos añadir el dato así como el dato a insertar y la función devuelve una lista con el nuevo dato insertado.

La función g_slist_insert inserta el dato en la posición indicada. Su uso también es sencillo como puede verse en el siguiente ejemplo.

Ejemplo 6-9.1. Insertar un nuevo dato en una posición determinada.

   1     /* obtiene el numero de nodos de la lista */
   2     length = g_slist_length (list);
   3 
   4     g_print ("\nEscribe el nº de indice donde se insertara el dato (el indice maximo es %d): ", length);
   5     scanf ("%d", &index);
   6 
   7     /* inserta el valor en la posicion indicada */
   8 
   9     if (index < length) {
  10       list = g_slist_insert (list, GINT_TO_POINTER (value), index);
  11       print_list (list);
  12         }

En este ejemplo se utiliza la función g_slist_length para obtener el número de nodos que contiene la lista. A esta función hay que pasarle como parámetro la lista de la que se desea obtener el número de nodos y devuelve como resultado el número de nodos de ésta.

guint * g_slist_length ( GSList * list );

La función g_slist_insert_sorted inserta los elementos a la lista de forma ordenada. Esta función utiliza el parámetro GCompareFunc para insertar el dato en la posición correcta.

GCompareFunc es una función que se utiliza para comparar dos valores y saber así cual de ellos hay que insertar primero. En los dos ejemplos que hay a continuación, se puede observar una función de tipo GCompareFunc y su uso para insertar datos en una lista en orden creciente.

Ejemplo 6-9.2. Parámetro GCompareFunc para insertar en orden creciente.

   1 gint compare_value1 (gconstpointer a, gconstpointer b) {
   2   gint *value1 = (gint *) a;
   3   gint *value2 = (gint *) b;
   4 
   5   return value1 > value2;
   6 }

Ejemplo 6-9.3. Insertar elementos en orden creciente.

   1   gint values[] = {8, 14, 5, 12, 1, 27, 3, 13};
   2   gint i;
   3   /* insertando valores en orden creciente */
   4   for (i = 0; i < 8; i++) {
   5     list =  g_slist_insert_sorted (list, GINT_TO_POINTER (values[i]),
   6                                    compare_value1);
   7   }

Tabla 6-9.2. Operadores de eliminación en listas enlazadas.

Operador

Funciones asociadas a GSList.

Eliminar un nodo.

GSList* g_slist_remove (GSList *list, gconstpointer data)

Eliminar nodos según un patrón.

GSList* g_slist_remove_all (GSList *list, gconstpointer data)

Las dos funciones expuestas para la eliminación de nodos, si bien tienen una definición prácticamente idéntica, el resultado obtenido es distinto. En el caso de g_slist_remove, se eliminará el nodo que contenga el valor data. Si hay varios nodos con el mismo valor, sólo se eliminará el primero. Si ningún nodo contiene ese valor, no se realiza ningún cambio en el GSList. En el caso de g_slist_remove_all, se eliminan todos los nodos de la lista que contengan el valor data y nos devuelve la nueva lista resultante de la eliminación de los nodos.

Ejemplo 6-9.4. Elimina un elemento de la lista.

   1   if (list2 != NULL) {
   2     g_print ("\nEl dato %d sera eliminado de la lista.\n", list2->data);
   3 
   4     /* eliminando un elemento de la lista */
   5     g_slist_remove (list, list2->data);
   6    }

Tabla 6-9.3. Operadores de búsqueda en listas enlazadas.

Operador

Funciones asociadas a GSList.

Buscar un nodo según un valor.

GSList* g_slist_find (GSList *list, gconstpointer data)

Buscar un nodo según un criterio.

GSList* g_slist_find_custom (GSList *list, gconstpointer data, GCompareFunc func)

Localizar el índice de un nodo.

GSList* g_slist_index (GSList *list, gconstpointer data)

Localizar la posición de un nodo.

GSList* g_slist_position (GSList *list, GSList *llink)

Obtener el último nodo.

GSList* g_slist_last (GSList *list)

Obtener el siguiente nodo.

g_slist_next (slist)

Obtener un nodo por su posición.

GSList* g_slist_nth (GSList *list, guint n)

Obtener el dato de un nodo según su posición.

gpointer g_slist_nth_data (GSList *list, guint n)

Todas estas funciones, a excepción de g_slist_nth_data, devuelven un nodo de la lista o NULL si el elemento no existe. La función g_slist_nth_data devuelve el valor del elemento según la posición que se le pasa como argumento en el parámetro n o NULL si la posición que se le pasa está más allá del final de la lista.

La función g_slist_next, es una macro que nos devuelve el siguiente nodo. Esta macro la podemos utilizar para recorrer la lista.

Ejemplo 6-9.5. Función que imprime una lista.

   1 void print_list (GSList *list) {
   2 
   3   gint i = 0;
   4 
   5   while (list != NULL) {
   6     g_print ("Node %d content: %d.\n", i, list->data);
   7 
   8     /* apunta al siguiente nodo de la lista */
   9     list = g_slist_next (list);
  10     i++;
  11   }
  12 }

Tabla 6-9.4. Operador para vaciar la lista.

Operador

Funciones asociadas a GSList.

Vacía la lista y libera la memoria usada.

void g_slist_free (GSList *list)

La función g_slist_free libera la memoria de la lista que se le pasa como parámetro.

Con estas funciones, quedan definidos los operadores básicos del TDA lista enlazada. GSList trae otras funciones además de los operadores básicos. Para más información sobre estas, está disponible el manual de referencia de GLib.

Listas doblemente enlazadas.

Introducción.

El TDA lista doblemente enlazada, al igual que la lista enlazada, es un TDA dinámico lineal pero, a diferencia de este, cada nodo de la lista doblemente enlazada contiene dos punteros, de forma que uno apunta al siguiente nodo y el otro al predecesor. Esta característica, permite que se pueda recorrer la lista en ambos sentidos, cosa que no es posible en las listas simples.

La declaración del tipo lista doblemente enlazada de enteros es la siguiente:

            struct lista_doble {
            gint dato;                                           (5)
            lista_doble *siguiente;                              (6)
            lista_doble *anterior;                               (7)
            };

(5)

  • Representa el dato a almacenar, que puede ser de cualquier tipo. En este ejemplo se trataría de una lista de enteros.

(6)

  • Se trata de un puntero al siguiente elemento de la lista. Con este puntero se enlaza con el sucesor, de forma que podamos construir la lista.

(7)

  • Es un puntero al elemento anterior de la lista. Este puntero enlaza con el elemento predecesor de la lista y permite recorrerla en sentido inverso.

Sobre este TDA se definen los mismos operadores básicos que en las listas simples.

GList

La definición de la estructura GList, que es un nodo de la lista doblemente enlazada, está definido de la siguiente manera:

            struct GList
            {
            gpointer data;                                       (8)
            GList *next;                                         (9)
            GList *prev;                                         (10)
            };

(8)

  • Representa el dato que se va a almacenar. Se utiliza un puntero genérico por lo que puede almacenar un puntero a cualquier tipo de dato o bien almacenar un entero utilizando las macros de conversión de tipos.

(9)

  • Se trata de un puntero al siguiente elemento de la lista.

(10)

  • Se trata de un puntero al elemento anterior de la lista.

Las funciones que acompañan a la estructura GList, que implementan los operadores básicos de las listas enlazadas, son las siguientes:

Tabla 6-9.5. Operadores de inserción en listas doblemente enlazadas.

Operador

Funciones asociadas a GList

Insertar al principio.

GList* g_list_prepend (GList *list, gpointer data)

Insertar al final.

GList* g_list_append (GList *list, gpointer data)

Insertar en la posición indicada.

GList* g_list_insert (GList *list, gpointer data, gint position)

Insertar en orden.

GList* g_list_insert_sorted (GList *list, gpointer data, GCompareFunc func)

Como puede observarse en la definición de las funciones, su uso es el mismo que en las listas simples, al igual que las macros de conversión, por lo que todo lo explicado en esa sección es válido en el caso de las listas doblemente enlazadas.

Ejemplo 6-9.6. Insertar un nuevo dato en una posición determinada.

   1     /* obtiene el numero de nodos de la lista */
   2     length = g_list_length (list);
   3 
   4     g_print ("\nEscribe el numero de indice donde se insertara el dato (el indice maximo es %d): ", length);
   5     scanf ("%d", &index);
   6 
   7     /* inserta el valor en la posicion indicada */
   8 
   9     if (index < length) {
  10       list = g_list_insert (list, GINT_TO_POINTER (value), index);
  11       print_list (list);
  12         }

Ejemplo 6-9.7. Insertar elementos en orden creciente.

   1   gint values[] = {8, 14, 5, 12, 1, 27, 3, 13};
   2   gint i;
   3 
   4   for (i = 0; i < 8; i++) {
   5     list =  g_list_insert_sorted (list, GINT_TO_POINTER (values[i]),
   6                                    compare_value1);
   7   }

Tabla 6-9.6. Operadores de eliminación en listas doblemente enlazadas.

Operador

Funciones asociadas a GList

Eliminar un nodo.

GList* g_list_remove (GList *list, gconstpointer data)

Eliminar nodos según un patrón.

GList* g_list_remove_all (GList *list, gconstpointer data)

Ejemplo 6-9.8. Eliminar un elemento de la lista.

   1   if (list2 != NULL) {
   2     g_print ("\nEl dato %d sera eliminado de la lista.\n", list2->data);
   3 
   4     /* eliminando un elemento de la lista */
   5     g_list_remove (list, list2->data);
   6     print_list (list);
   7    }

Tabla 6-9.7. Operadores de búsqueda en listas doblemente enlazadas.

Operador

Funciones asociadas a GList.

Buscar un nodo según un valor.

GList* g_list_find (GList *list, gconstpointer data)

Buscar un nodo según un criterio.

GList* g_list_find_custom (GList *list, gconstpointer data, GCompareFunc func)

Localizar el índice de un nodo.

GList* g_list_index (GList *list, gconstpointer data)

Localizar la posición de un nodo.

GList* g_list_position (GList *list, GSList *llink)

Obtener el último nodo.

GList* g_list_last (GList *list)

Obtener el siguiente nodo.

g_list_next (list)

Obtener un nodo por su posición.

GList* g_list_nth (GList *list, guint n)

Obtener el dato de un nodo según su posición.

gpointer g_list_nth_data (GList *list, guint n)

Ejemplo 6-9.9. Busca un valor dado en la lista.

   1   g_print ("\nEntra un valor entero a buscar: ");
   2   scanf ("%d", &value);
   3   g_print ("\n");
   4 
   5   /* buscando un elemento en la lista */
   6   list2 = g_list_find (list, GINT_TO_POINTER (value));
   7 
   8   if (list2 != NULL) {
   9     index = g_list_index (list, list2->data);
  10     g_print ("\nEl valor %d esta en el nodo %d.\n", list2->data, index);

Tabla 6-9.8. Operador para vaciar la lista

Operador

Funciones asociadas a GList

Vacía la lista y libera la memoria usada.

void g_list_free (GList *list)

Con estas funciones, quedan definidos los operadores básicos del TDA lista enlazada. Al igual que GSList, GList trae otras funciones además de los operadores básicos. Para más información sobre estas, está disponible el manual de referencia de GLib.

GQueue: pilas y colas.

Otra de las novedades que incorpora esta versión de GLib es la estructura GQueue que, junto con las funciones que la acompañan, nos proporciona la posibilidad de implementar los TDA cola y pila estándar.

GQueue utiliza estructuras GList para almacenar los elementos. La declaración de la estructura de datos es la siguiente.

            struct GQueue {
               GList  *head;                                     (11)
               GList  *tail;                                     (12)
               guint length;                                     (13)
               };

(11)

  • Es un puntero al primer elemento de la cola.

(12)

  • Es un puntero al último elemento de la cola.

(13)

  • Esta variable almacena el número de elementos de la cola.

Aunque para referirnos al TDA GQueue utilizamos el término cola, con esta misma estructura también tenemos la posibilidad de implementar el TDA pila, gracias a las funciones que lo acompañan.

Colas

Una cola es una estructura de datos donde el primer elemento en entrar es el primero en salir, también denominadas estructuras FIFO (First In, First Out).

Esta estructura de datos se puede definir como una lista enlazada con acceso FIFO a la que sólo se tiene acceso al final de la lista para meter elementos y al principio de esta para sacarlos.

Los operadores asociados a este TDA y las funciones que los implementan en GLib son:

Tabla 6-9.9. Operadores asociados al TDA Cola.

Operador

Funciones asociadas a GQueue.

Iniciar cola.

GQueue* g_queue_new (void)

Cola vacía.

gboolean g_queue_is_empty (GQueue* queue)

Consultar frente cola.

gpointer g_queue_peek_head (GQueue* queue)

Consultar final cola.

gpointer g_queue_peek_tail (GQueue* queue)

Meter

void g_queue_push_tail (GQueue* queue, gpointer data)

Sacar

gpointer g_queue_pop_head (GQueue* queue)

Vaciar cola.

void g_queue_free (GQueue* queue)

Iniciar cola.

El operador "Iniciar cola" es el encargado de crear una nueva cola y ponerla en estado de cola vacía.

Ejemplo 6-9.10. Creando una nueva cola.

   1    GQueue* cola;
   2    cola = g_queue_new ();

Cola vacía.

Este operador consulta si la cola está vacía. Es necesaria su utilización antes de realizar la operación de "sacar elementos" de la cola.

Ejemplo 6-9.11. Función que comprueba si una cola está vacía.

   1 gboolean cola_vacia (GQueue* cola) {
   2    return g_queue_is_empty (cola);
   3 }

Consultar el frente.

Esta operación consulta el contenido del frente de la cola sin sacarlo.

Ejemplo 6.9-12. Función que consulta el frente de la cola.

   1 gpointer consultar_frente (GQueue* cola) {
   2    return g_queue_peek_head (cola);
   3 }

Consultar el final.

Esta operación consulta el contenido del final de la cola sin sacarlo.

Ejemplo 6-9.13. Función que consulta el final de la cola.

   1 gpointer consultar_final (GQueue* cola) {
   2    return g_queue_peek_tail (cola);
   3 }

Meter

Este operador introduce elementos al final de la cola.

Ejemplo 6-9.14. Introducir un nuevo elemento en la cola.

   1 GQueue* meter_cola (GQueue* cola, gpointer dato) {
   2    g_queue_push_tail (cola, dato);
   3 
   4