Manual
logo



Programación C++ con AGU desde 0

Interactuando con el usuario

ámbitos y punteros

Siendo sinceros nuestro programa es muy cutre, eso de salir por consola,... Lo suyo es que aunque sea igualmente inútil, los mensajes vayan saliendo en nuestra etiqueta.

Podíamos probar a que nuestro programa modifique el texto de la etiqueta de la siguiente forma, pero...:

#include <iostream>
#include <gtkmm.h>

int veces=0;//declaración e inicialización de veces
void pulsar_boton();

int main(int argc, char *argv[])
{  
   
    Gtk::Main programa(argc, argv);

    Gtk::Window miventanica;
    Gtk::VBox principal;
    Gtk::HSeparator linea;
    Gtk::Label etiqueta ("Pulsa abajo");//declaración de etiqueta
    Gtk::Button contador("¡AQUÍ!");
   
    contador.signal_clicked().connect(sigc::ptr_fun(&pulsar_boton));
    miventanica.add(principal);
   
   
    principal.pack_start(etiqueta);
    principal.pack_start(linea);
    principal.pack_start(contador);
   
    principal.show();
    etiqueta.show();
     contador.show();   
    linea.show();
   
    miventanica.set_title("maestrodenada.com");
    miventanica.resize(260,110);
   

    Gtk::Main::run(miventanica);
   
    return 0;
}
void pulsar_boton()
{
    char h[30];
    sprintf (h, "has pulsado %i veces el botón", ++veces);
    etiqueta.set_text(h);
       
}

... la compilación del programa nos da error porque etiqueta no es conocido en el ámbito de pulsar_botón.
¿Quéeee?

Las variables declaradas dentro de una función son conocidas dentro de esa función, pero no fuera, son denominadas variables locales. Por ejemplo, etiqueta es declarada en main (variable local de main), pero no en pulsar_boton de ahí el error.

Si declaramos etiqueta dentro de pulsar_boton tal que así...
...
void pulsar_boton()
{
    Gtk::Label etiqueta ("etiqueta2");
    char h[30];
    sprintf (h, "has pulsado %i veces el botón", ++veces);
    etiqueta.set_text(h);
      
}

... el programa compila perfectamente pero en la ejecución no se observa nada,...
 ELEMENTAL mi querido Watson, no es igual la etiqueta de pulsar_boton que la etiqueta de main. Son dos "etiqueta"s distintas.
Una variable local se crea (se habilita espacio en la memoria) con el inicio de la función y se destruye (se libera la memoria reservada) con el final [Normalmente].

Si observas el programa, verás que existe una variable entera veces que está declarada antes de main y fuera de esta, variable que es utilizada por la función pulsar_boton. Las variables declaradas antes y, por tanto, fuera de las funciones son variables globales conocidas por todas las funciones.

¿Podría ser una solución llevar la declaración de etiqueta antes de main?  Probemos...

#include <iostream>
#include <gtkmm.h>


Gtk::Label etiqueta ("Pulsa abajo");//declaración de etiqueta delante de main
int veces=0;
void pulsar_boton();

int main(int argc, char *argv[])
{  
   
    Gtk::Main programa(argc, argv);

    Gtk::Window miventanica;
    Gtk::VBox principal;
    Gtk::HSeparator linea;
    
    Gtk::Button contador("¡AQUÍ!");
   
    contador.signal_clicked().connect(sigc::ptr_fun(&pulsar_boton));
    miventanica.add(principal);
   
   
    principal.pack_start(etiqueta);
    principal.pack_start(linea);
    principal.pack_start(contador);
   
    principal.show();
    etiqueta.show();
     contador.show();   
    linea.show();
   
    miventanica.set_title("maestrodenada.com");
    miventanica.resize(260,110);
   

    Gtk::Main::run(miventanica);
   
    return 0;
}
void pulsar_boton()
{
    char h[30];
    sprintf (h, "has pulsado %i veces el botón", ++veces);
    etiqueta.set_text(h);
       
}
... compila a la perfección, pero al ejecutarlo ¡MY GOD! ¡¡¡qué hemos hecho!!!!

La siguiente explicación no es muy ortodoxa,[ supongo ], pero espero permita dar una idea al lector ... Un ordenador tiene una memoria RAM que es la utilizada por los procesos cuando estos se ejecutan. Esto es, del disco se cargan en la memoria RAM y se procesan por el procesador, valga la redundancia.
La memoria RAM en Unix tiene dos  grandes divisiones la pila y el heap. En la pila se organiza la información como en una pila [de ahí el nombre] de libros o documentos, uno encima de otro. En el montón (heap), la información se  organiza más como una estantería.
Al compilar el archivo, el compilador asigna la memoria de la PILA (stack)  acorde a las variables globales definidas y el código (todo son 0 y 1 a fin de cuentas). La variable veces es entera, esto es, tiene un tamaño fijo (4 bytes), tamaño que permanece invariable durante todo el programa sea cual sea la asignación que se le realice.
Los objetos de Gtk tienen un tamaño variable [Sería impensable que tuvieran un tamaño estándar, no es posible que una ventana como la nuestra solicitará los mismos recursos que la de el programa Gimp -programa original de las librerías Gtk]. Así, que al igual que las cadenas de texto, los objetos de Gtk solicitarán memoria en función de la complejidad del mismo. Cuando nosotros compilamos el programa, este asigna la memoria especificada en la declaración del objeto, que será la mínima. A la hora de ejecutar el programa, éste tratará de inicializar nuestro objeto, lo cual implicará el aumento de su tamaño. Pero el aumento de tamaño no puede realizarse en este punto de la RAM, dando un error en la gestión del proceso (error 11, segmentation fault ). En la pila, no puede aumentarse el tamaño de nuestro objeto, porque habría que reorganizar todos los datos de la pila; imaginate una serie de carpetas apiladas, de repente, encuentras documentos que pertenecen a la segunda carpeta, para introducir esos documentos debes levantar todas las carpetas superiores.

Para solucionar el problema podemos decir que existen en C++ (y en C) unas variables muy poderosas (y peligrosas) que permiten la manipulación de la memoria denominados punteros.  A su vez en todo proceso de Unix existe otro espacio en la memoria Ram, denominado montón (HEAP), que es el utilizado para la gestión dinámica de la memoria, esto es, la utilización durante la ejecución del programa; de tal suerte, que no es igual la memoria que pueda gestionar un editor de texto cuando escribimos un pequeño poema a cuando escribimos el Quijote.
Un puntero es una variable que almacena una dirección de memoria, esta dirección de memoria puede "apuntar" al comienzo de un objeto almacenado en otro lugar de la memoria. Si durante la ejecución del programa nuestro objeto aumenta o varia de tamaño, nuestra variable puntero seguirá siendo igual y "apuntando" al mismo sitio.

Un puntero se declara así:  nombre_tipo * nombre_variable;

nombre_variable almacena una dirección de memoria.

En principio, un puntero es una dirección de memoria, todos los punteros son direcciones de memoria. Pero los desarrolladores de C++ fueron buenos chicos y nos permitieron declarar el tipo de objeto al que apuntaba esa dirección de memoria para que a la hora de manipularlos advertirnos de posibles errores en tiempo de compilación (durante la programación), y es que los errores con punteros pueden ser difíciles de localizar.

* nombre_variable devuelve el contenido (0 y 1) que comienza en la dirección contenida nombre_variable, considerándola del tipo nombre_tipo. Esto es, si declaro int * x;  la variable x almacena una dirección de memoria, si en el programa escribo *x, el ordenador leerá a partir de la dirección de memoria contenida en x, 4 bytes (lo que ocupa un int) y devolverá el valor - los 0 y 1- como si se tratará de un entero.

& nombre_variable devuelve la dirección de memoria de la variable nombre_variable. Para asignar a una variable puntero una dirección de un objeto, debemos utilizar el operador &.
Si eres un buen observador verás que cuando conectamos las señales con la función, el nombre de la función va precedido del símbolo &, porque en realidad el argumento es la dirección de la función.
Veamos un ejemplo:

#include <iostream>
int main()
{
    int * v; // variable puntero a entero
    int p=7;// variable entera
    v=&p; // v apunta a p
    std::cout<<" v = "<< v <<std::endl;
    std::cout<<" *v = "<< *v <<std::endl;
    std::cout<<" p = "<< p <<std::endl;
    std::cout<<" &p = "<< &p <<std::endl;
   // observa que la variable v almacena una dirección,
   //pero ella  como cualquier otra está alojada en otra dirección.
   //  v es distinto de &v
    std::cout<<"&v = "<<&v<<std::endl;  
    return 0;
}

Solución

Conocidas las variables puntero, que nos van a permitir solucionar el problema de los ámbitos, modifiquemos el archivo de main.cc para que quede tal que así:

/* *************************
 *    MAESTRODENADA.COM    *
 *        presenta         *
 *           el            *
 *      programa básico    *
 *       con Gtk           *
 ***************************/

#include <iostream>
#include <gtkmm.h>

Gtk::Label* etiqueta;//variable puntero global
int veces=0;
void pulsar_boton();

int main(int argc, char *argv[])

  
    Gtk::Main programa(argc, argv); // Creación del proceso gráfico

    // Diseño del entorno gráfico, declaración de componentes
    Gtk::Window miventanica;
    Gtk::VBox principal;
    Gtk::HSeparator linea;
    Gtk::Label etiqueta2 ("Pulsa en el botón de abajo");
    Gtk::Button contador("¡AQUÍ!");

    // Diseño del entorno gráfico, composición de componentes
    miventanica.add(principal);
   
    principal.pack_start(etiqueta2);
    principal.pack_start(linea);
    principal.pack_start(contador);
  
    // Diseño del entorno gráfico, hacer visibles los controles
    principal.show();
    etiqueta2.show();
     contador.show();  
    linea.show();
  
    // Diseño del entorno gráfico, retoques
    miventanica.set_title("maestrodenada.com");
    miventanica.resize(260,110);

    // Diseño del entorno gráfico, conexión señales con funciones
   
    contador.signal_clicked().connect(sigc::ptr_fun(&pulsar_boton));


    etiqueta = &etiqueta2; // Inicialización de variables punteros

    Gtk::Main::run(miventanica); // lanzamiento/ejecución del proceso
  
    return 0;
}
void pulsar_boton()
{
    char h[30];
    sprintf (h, "Has pulsado %i veces el botón", ++veces);
    etiqueta->set_text(h);
      
}

Explicación:

Para manipular un control o componente de Gtk debemos realizar lo siguiente:

   1. Declarar variables GLOBALES punteros a los componentes que deseamos acceder, en nuestro caso un Gtk::Label.
   2. Asociar la variable puntero con el objeto a manipular. En el ejemplo hemos asignado a etiqueta (variable puntero) la dirección de etiqueta2 (variable Label) usando el operador &.
   3. Acceder a o manipular los elementos mediante el puntero (en la función que gestiona la señal).


Para acceder a los elementos hemos usado  ->
En C y C++, para acceder a los datos individuales de una variable de un objeto complejo (con varios datos), se utiliza el [.], tal y como se observa en main.
Sin embargo, en las variables puntero de objetos complejos, el acceso se realiza a través de ->.

Como curiosidad, decir que la instrucción: etiqueta->set_text(h);  es igual a (*etiqueta).set_text(h);
En una variable puntero, * nombre_variable, devuelve el contenido al que apunta, por lo que (*etiqueta) devuelve un objeto Gtk::Label, con el punto accedemos a la función del objeto Gtk::Label devuelto.