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.