Archivo

Entradas Etiquetadas ‘clases’

¿ Y tú qué eres ? Identificando clases heredadas en C++

Viernes, 9 de Diciembre de 2011 Gaspar Fernández Sin comentarios

blogvision_estudios_small

En ocasiones, sobre todo cuando estamos con temas de herencia entre clases, tenemos la necesidad de consultar de qué tipo es una instancia. Es decir, tenemos una clase Mascota, y clases Perro y Gato (subclases de la primera). Imaginad que en el código hay muchas funciones, que queremos que sean comunes para todas las mascotas, y por tanto, aceptarán como parámetro una instancia de Mascota, pero en su ejecución, necesitan en un momento saber de qué tipo concreto son (Perro o Gato). ¿ Cómo podemos averiguarlo ?

Lo podemos hallar con dynamic_cast. dynamic_cast forma parte de la RTTI (RunTime Type Information / Información de tipos en tiempo de ejecución), y es capaz de guardar los datos del tipo específico de una instancia y utilizarla durante la ejecución del programa (no sólo en la compilación). Esto no está en la especificación original de C++ (del 1983), sino que fue introducido en 1991, además, en algunos compiladores no viene activado por defecto (según Wikipedia, es lógico, ya que esto puede causar un coste, tanto en memoria, espacio del ejecutable y uso de memoria.)

Una forma para comprobar el tipo que estamos utilizando es la siguiente:

1
2
3
4
5
Perro *a = dynamic_cast<Perro *>(m);
  if (a!=NULL)
    {
      // Acciones con una instancia de Perro llamada a
    }

En este caso estamos comprobando que nuestra Mascota es un Perro.

A continuación vemos el ejemplo completo, en el que tenemos una serie de mascotas y vamos a llevarlas al veterinario, pero para eso, el veterinario determina qué tipo de mascota es y lo deriva al doctor adecuado (es un ejemplo). El ejemplo estará disponible para descarga al final del post.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <string>

using namespace std;


class Mascota
{
public:
  Mascota(string nombre):nombre(nombre)
  {
  }
  virtual ~Mascota()
  {
  }

  string getNombre()
  {
    return nombre;
  }
private:
  string nombre;
};

class Perro : public Mascota
{
public:
  Perro(string nombre):Mascota(nombre)
  {
  }
};

class Gato : public Mascota
{
public:
  Gato(string nombre):Mascota(nombre)
  {
  }
};

class Iguana : public Mascota
{
public:
  Iguana(string nombre):Mascota(nombre)
  {
  }
};

void veterinarioPerro(Perro *p)
{
  cout << "Ponemos una inyección a tu perro " << p->getNombre() << endl;
}

void veterinarioGato(Gato *g)
{
  cout << "Ponemos una inyección a tu gato " << g->getNombre() << endl;
}

void veterinario(Mascota *m)
{
  cout << "Vas a llevar a tu mascota al veterinario..." << endl;

  Perro *a = dynamic_cast<Perro *>(m);
  if (a!=NULL)
    {
      veterinarioPerro(a);
    }
  else if (Gato *a = dynamic_cast<Gato *>(m))
    {
      veterinarioGato(a);
    }
  else
    {
      cout << "No sé qué tipo de mascota tienes" << endl;
    }
}

int main()
{
  Mascota *m;
  Mascota *m2;
  Mascota *i;

  m=new Gato("Mini");
  m2=new Perro("Rufus");
  i= new Iguana("Igor");

  veterinario(m);
  cout << endl;
  veterinario(m2);
  cout << endl;
  veterinario(i);
}

Tenemos que tener en cuenta un requisito primordial para dynamic_cast, y es que la clase sobre la que hagamos la llamada, tiene que ser una clase polimórfica, y será polimórfica cuando contenga al menos un método virtual. Si no sabemos qué método puede ser virtual en la clase, siempre podemos hacer que sea el destructor.

Aunque, por poner un ejemplo de más utilidad, imaginemos que en lugar de tener variables disponibles, cuyo control podemos llevarlo a mano, tenemos un vector de mascotas, y vamos a llevarlas a todas al veterinario. Sólo pego aquí el main(), en la descarga al final de la página estará el ejemplo completo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main()
{
  vector <Mascota *>mis_mascotas;
  Mascota *m;
 
  m=new Gato("Mini");
  mis_mascotas.push_back(m);
  m=new Perro("Rufus");
  mis_mascotas.push_back(m);
  m=new Iguana("Igor");
  mis_mascotas.push_back(m);
  m=new Perro("Ralph");
  mis_mascotas.push_back(m);
  m=new Gato("Pitu");
  mis_mascotas.push_back(m);
  m=new Perro("Pluto");
  mis_mascotas.push_back(m);
  m=new Gato("Kity");
  mis_mascotas.push_back(m);
  m=new Iguana("Motorcillo");
  mis_mascotas.push_back(m);

  for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      veterinario(*i);
      cout<<endl;
    }
}

Otra forma es utilizar typeid() para averiguar la clase a la que pertenece la instancia. Lo hacemos en la función veterinario(), es necesario incluir la biblioteca :

1
2
3
4
5
6
7
8
9
10
11
12
void veterinario(Mascota *m)
{
  cout << "Vas a llevar a tu mascota al veterinario..." << endl;

   cout << typeid(*m).name() << endl;
  if (typeid(*m)==typeid(Perro))
    veterinarioPerro((Perro*)m);
  else if (typeid(*m)==typeid(Gato))
    veterinarioGato((Gato*)m);
  else
    cout << "No sé qué tipo de mascota tienes" << endl;
}

Algo que también se puede hacer es averiguar el nombre de la clase e imprimirlo con cout, aunque intuitivamente no podremos, porque normalmente el nombre de los tipos no llegan al programa compilado, cuando se compila con soporte RTTI, tenemos la opción de almacenar estos nombres, así que cuidado al poner los nombres de las clases, que se podrá ver en el resultado final.
Para poner por pantalla los nombres de las clases, debemos hacer lo siguiente (pongo sólo la parte de main() correspondiente a recorrer el vector):

1
2
3
4
for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      cout << typeid(**i).name() << endl;
    }

typeid(instancia).name() nos devuelve en un formato textual el nombre de la clase. En el ejemplo hay ** (dos asteriscos) porque el iterador es un puntero a lo que estamos recorriendo, que son punteros a Mascota.
Hay que tener cuidado, porque esta opción no es igual en todos los compiladores, puede que algunos compiladores te devuelvan el nombre tal cual, y otros te devolverán el nombre con algún número o caracteres especiales en el caso de ser punteros, por ejemplo. El más claro ejemplo es GXX cuyo resultado es:

4Gato
5Perro
6Iguana
5Perro
4Gato
5Perro
4Gato
6Iguana

El número que precede los nombres de clase es el número de caracteres que tiene dicho nombre: Gato = 4, Perro = 5, Iguana = 6 . Si probamos con Mascota, saldrá un 7. Y no hay ninguna forma de hacerlo portable a diferentes compiladores por lo que tendremos que hallar soluciones específicas en el caso de que queramos tener el nombre de la clase en formato plano.

En GXX (g++) podemos utilizar lo siguiente, es una función que arregla el nombre para los humanos, no sólo de clases, sino más cosas. Podemos hacer lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
string demangle(const char *input)
{
  int status;
  string res(abi::__cxa_demangle(input, 0, 0, &status));
  return res;
}

int main()
{
  vector <Mascota *>mis_mascotas;
  Mascota *m;
 
  m=new Gato("Mini");
  mis_mascotas.push_back(m);
  m=new Perro("Rufus");
  mis_mascotas.push_back(m);
  m=new Iguana("Igor");
  mis_mascotas.push_back(m);
  m=new Perro("Ralph");
  mis_mascotas.push_back(m);
  m=new Gato("Pitu");
  mis_mascotas.push_back(m);
  m=new Perro("Pluto");
  mis_mascotas.push_back(m);
  m=new Gato("Kity");
  mis_mascotas.push_back(m);
  m=new Iguana("Motorcillo");
  mis_mascotas.push_back(m);

  for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      cout << demangle(typeid(**i).name()) << endl;
    }
}

He utilizado una función para simplificar un poco esto, ya que __cxa_demangle(), tiene varios parámetros y nos interesa tener una función que la llame con los parámetros por defecto, para hacer la tarea más directamente. También tenemos que incluir

En G++ podemos utilizar la opción -fno-rtti si no queremos compilar nuestros programas en C++ con este soporte.

Descárgate los ejemplos: dynamic_types.tar.bz2

Polimorfismos: enrevesando la herencia entre clases [C++]

Sábado, 26 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Cuando desarrollamos bajo el paradigma de la programación orientada a objetos, a poco que avanzamos tenemos la sensación de estar escribiendo más para hacer lo mismo que hacíamos antes sin clases ni instancias ni tanta estructuración.
Pero contamos con herramientas que, aunque al principio nos sonarán algo chocantes, a la larga terminarán abriéndonos un mundo de posibilidades y comprenderemos por qué es tan importante, tan utilizado y se exige en multitud de universidades y centros educativos.
Uno de esos grandes avances que nos permite la programación orientada a objetos y que hoy particularizaremos a C++ son los polimorfismos.

Imaginemos (el ejemplo es clases_animalun poco tonto, lo reconozco, pero es lo más sencillo que se me ocurre para no llenar el post de código. Estoy abierto a sugerencias) que vamos a crear un software de mascota virtual, para eso construimos una clase padre (o superclase) llamada Animal. El sistema está en una fase muy temprana de desarrollo y los animales sólo pueden emitir sonidos (aunque en este programa se pondrá una onomatopeya por pantalla).

A partir de la clase Animal crearemos las clases Perro y Gato. Lo que necesitamos es que todos los miembros de Animal tengan un método hablar() que les permita emitir su sonido característico. Nuestra función main() queda así:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
  Mascota *p, *g;
 
  p=new Perro("Ramiro");
  g=new Gato("Lancelot");

  cout << p->hablar() << endl;
  cout << g->hablar() << endl;

  return 0;
}

Lo que deseamos es que p (declarado como Mascota, pero en realidad es de clase Perro), diga “GUAU” y g (instancia de Gato) diga “MIAU”. Para eso necesitamos crear un polimorfismo (será un método que en cada subclase (clase heredera, o clase hija) ejecute el método correspondiente dependiendo de la clase de la instancia que tengamos. En C++ lo hacemos con métodos virtuales:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <string>

using namespace std;

class Mascota
{
public:
  Mascota(string nombre, int patas);
  virtual string hablar() = 0;

private:
  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

class Perro : public  Mascota
{
public:
  Perro(string nombre);

  string hablar();
};

Perro::Perro(string nombre):Mascota(nombre, 4)
{
}

string Perro::hablar()
{
  return "GUAU";
}

class Gato : public  Mascota
{
public:
  Gato(string nombre);

  string hablar();
};

Gato::Gato(string nombre):Mascota(nombre, 4)
{
}

string Gato::hablar()
{
  return "MIAU";
}

Cuando lo ejecutemos, una vez creados el Perro (Ramiro) y el Gato (Lancelot), cada uno dirá su palabra correspondiente. Para entender la diferencia de lo que origina utilizar virtual o no utilizarlo, lo mejor es probarlo. Vemos que para crear la función “virtual string hablar()” escribimos =0 (es una forma especial para que la función quede definida y no sólo declarada). Por tanto, para eliminar el virtual, debemos declarar la función como virtual string hablar() y definir su comportamiento. El fragmento quedaría así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Mascota
{
public:
  Mascota(string nombre, int patas);
  string hablar();

private:
  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

string Mascota::hablar()
{
  return "BUU";
}

En este caso, un “Animal por defecto” (que no pertenecería a ninguna especie) diría “BUU”, si ejecutamos el programa tal cual lo tenemos ahora, la salida sería:

Acaba de nacer tu mascota Ramiro con 4patas
Acaba de nacer tu mascota Lancelot con 4patas
BUU
BUU

¡ Los dos dirían BUU ! Y eso es porque inicialmente están declaradas como Animal, así que al llamar al método hablar() se llamará al de la clase Animal en lugar de Perro o Gato.

Ahora hacemos una pequeña modificación. Cambiamos los métodos virtual hablar() por palabra() y los ponemos en la parte privada, para el acceso desde main() creamos otra función hablar() no virtual en Mascota, cuyo contenido será:

1
2
3
4
string Mascota::hablar()
{
  return nombre+" dice: "+this->palabra();
}

El objetivo es que cada animal, antes de decir su palabra escriba: “(minombre) dice: (mipalabra)”, en el caso del perro: “Ramiro dice: GUAU”. Aunque claro, es un método de Animal, por tanto this apuntaría a un Animal, que lo es, pero también es un Perro y como perro tiene su palabra característica y su función palabra() definida y propia, en la parte privada, por lo que intuitivamente no tendríamos acceso; pero virtual también funciona aquí para poder seleccionar el método hablar() de la clase correspondiente.

El resultado final queda así (por si quieres copiar y pegar):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <string>

using namespace std;

class Mascota
{
public:
  Mascota(string nombre, int patas);
  string hablar();

private:
  virtual string palabra();

  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

string Mascota::palabra()
{
  return "BUUU";
}

string Mascota::hablar()
{
  return nombre+" dice: "+this->palabra();
}

class Perro : public  Mascota
{
public:
  Perro(string nombre);

  string palabra();
};

Perro::Perro(string nombre):Mascota(nombre, 4)
{
}

string Perro::palabra()
{
  return "GUAU";
}

class Gato : public  Mascota
{
public:
  Gato(string nombre);

  string palabra();
};

Gato::Gato(string nombre):Mascota(nombre, 4)
{
}

string Gato::palabra()
{
  return "MIAU";
}

int main()
{
  Mascota *m, *g;
 
  m=new Perro("Ramiro");
  g=new Gato("Lancelot");

  cout << m->hablar() << endl;
  cout << g->hablar() << endl;
  return 0;
}

Nota: A veces también se llama polimorfismo a la sobrecarga (de métodos, por ejemplo, donde podemos describir varios métodos homónimos (todos llevarán el mismo nombre), pero con diferentes tipos de argumento, y en función de ellos, el compilador podrá saber a qué método llamamos. Todo esto tiene como función hacer la programación algo más intuitiva y natural.

Cuando PHP jura en hebreo (PAAMAYIM NEKUDOTAYIM)

Sábado, 22 de Enero de 2011 Gaspar Fernández 2 comentarios

Son dos palabras raras que parece que quieren despistarnos. Y no es inglés, ni japonés, ni ruso… es hebreo.
Y todo se remonta al 1997 cuando Zend Technologies reescribe el motor de PHP planteado por Rasmus Lerdorf.
Zend es una compañía de origen israelí, y de ahí que haya palabras en hebreo.

Pero… qué quiere decir esto ? La traducción es “dobles dos puntos” o lo que es lo mismo “::”. Esto es el operador de resolución de contexto (o ámbito) (Scope Resolution Operator en inglés) que tiene PHP y que desde PHP 3 identifica a qué nos referimos; y cuando programamos orientado a objetos, ayudará a PHP a identificar si llamamos a un método, una constante, atributo, etc. (Como C++).
Cuando hacemos Clase::método() para referirnos a un método estático PHP pone en práctica todo esto.

¿Qué pasa cuando hay un error en esta llamada? PHP nos devolverá un error del tipo: “Unexpected T_PAAMAYIM_NEKUDOTAYIM”, diciendo que hemos puesto “::” de forma inesperada. Podemos probarlo así desde consola:

$ php -r “::”

Y es que :: tiene la misión de detectar que delante ponemos una clase, o una palabra clave que le haga referencia ($this, self, parent, static…), y luego un método/atributo/constante/… aunque como veremos a continuación hay que tener cuidado con la versión de PHP que se utilice.

Es un error común cuando utilizamos PHP5 (<5.3.0) y queremos llamar a un método que está contenido en una variable:

1
2
$metodo="metodo_que_quiero_llamar";
Clase::$metodo

ya que no está soportado en esta versión. En cambio a partir de la 5.3 sí que podremos hacerlo. Mientras tendremos que utilizar call_user_func().

Así que si nos encontramos con este error, querrá decir que PHP no tiene claro a qué nos referimos, a veces lo encontramos cerca de :: pero otras veces no es tan claro, como por ejemplo aquí:

1
2
3
4
5
<?php
define('miConstante', 'algo');
if (empty(foo))
   echo 'vacía';
?>

Aquí fallará al hacer la llamada a empty() y es que tenemos que introducirle una variable como parámetro y no una constante (recordemos que el operador de resolución de contexto es el encargado de identificar variables, constantes y todo esto).

Más información:
Scope Resolution Operator [ Wikipedia ]
Ámbito de las variables [ PHP.NET ]
PHP Paamayim Nekudotayim [ The secret's in the code ]

Programar en C++ puede llegar a ser frustrante

Miércoles, 20 de Octubre de 2010 Gaspar Fernández Sin comentarios

cuchilloSiempre se dice que una de las reglas de la programación es “escribir poco”, tenemos que aunque en ciertos lenguajes, tenemos que escribir dos veces las cosas. Por ejemplo, para un sencillo hola mundo con clases en C++ (se puede hacer todo en el mismo archivo, pero queremos el código bien organizado):
[ hwclass.h ]

1
2
3
4
5
6
7
8
class HolaMundo
{
 public:
  HolaMundo();
  ~HolaMundo();

  void coutVersion();
};

[ hwclass.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include "hwclass.h"

using namespace std;

HolaMundo::HolaMundo()
{
  cout<<"Hola Mundo!!"<<endl;
}

HolaMundo::~HolaMundo()
{
  cout<<"Adiós Mundo!!"<<endl;
}

void HolaMundo::coutVersion()
{
  cout<<"HolaMundo Versión 1.0"<<endl;
}

[ main.cpp ]

1
2
3
4
5
6
7
#include "hwclass.h"

int main()
{
  HolaMundo hm;
  hm.coutVersion();
}

Si queremos compilar el proyecto podemos hacer:

$ g++ -o holamundo main.cpp hwclass.cpp

Bueno, a lo que voy, como veis en hwclass.cpp y hwclass.h se repiten algunas cosas, el nombre de la clase, y el nombre de los métodos cuando vamos a definir el código de cada uno… cuando en el archivo .h se definen decenas de métodos resulta muy repetitivo teclear una y otra vez lo mismo y más aún mantener el orden de los métodos.

Para ello quiero presentar dos aplicaciones:

lzz

Podemos descargarlo desde aquí, tiene multitud de opciones y es bastante potente. Aunque yo veo un gran defecto, tenemos que crear un fichero lzz, que luego el programa lo convertirá a cpp y hpp extrayendo la información según corresponda. Por otra parte, para generar el ejecutable, necesitamos el binario de lzz (como si de un compilador se tratara)

stubgen

Lo encontramos aquí, y aunque le falta alguna que otra cosilla como personalización de la documentación, es bastante potente y nos permite trabajar normalmente con nuestro .h y generar el .cpp con todos los métodos empezados, justo para que nosotros completemos el código.

Foto: ~Brenda_Starr~ (Flickr)
¿Por qué un cuchillo? Un stub en programación es un trozo de código incompleto (tal vez simula lo que va a hacer, o sólo es una pincelada)… stub se parece a stab (puñalada).

Interfaces Gráficos en Linux con gtkmm 1 [Hola Mundo]

Jueves, 14 de Octubre de 2010 Gaspar Fernández 4 comentarios

ventanaAunque a veces, cuando nos dedicamos a programar en Linux, recurrimos a la consola (ya que muchos de nosotros tenemos siempre una abierta), a veces es interesante crear un Interfaz Gráfico de Usuario (en inglés GUI, Graphical User Interface), para ello, si no queremos complicarnos demasiado tenemos dos opciones: Gtk+ y Qt.

Bien, vamos con Gtk+, está muy extendido y escrita en C, aunque aquí hablaré de una interfaz de Gtk+ para C++ llamada gtkmm, que nos proporciona las clases necesarias para jugar con la potencia de Gtk+ de una forma un poco más amigable (ya que podemos crear un botón, una etiqueta o una ventana como objetos de C++, hará todo un poco más intuitivo).

Una primera prueba que me gusta hacer es poner todo el código junto, sin mucha organización, para crear un “hola mundo”, para ver más o menos a qué nos enfrentamos y cómo, si vamos a tener que escribir mucho y esas cosas:

[ holamundo.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <gtkmm.h>

using namespace Gtk;

int main(int argc, char *argv[])
{
  // Inicialización GTK
  Main entorno(argc, argv);

  // Declaración de objetos
  Window ventana;
  Label etiqueta;

  // Características de la ventana
  ventana.set_title("Hola Mundo!");
  ventana.set_border_width(5);
  ventana.set_default_size(400, 200);

  // Etiqueta
  etiqueta.set_text("Hola Mundo!!");
  ventana.add(etiqueta);

  // Mostrar todo
  ventana.show_all_children();

  // Ejecutar GUI
  entorno.run(ventana);

  return 0;
}

Para compilar debemos hacer lo siguiente:

$ g++ -o holamundo holamundo.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Eso sí, debemos sustituir el 2.4 por la versión de gtkmm que tengamos instalada. Para ver cuál es, podemos hacer lo siguiente:

$ pkg-config –list-all | grep gtkmm

Bien, una vez hecho esto, vamos a intentar adquirir una metodología de programación que nos permita reaprovechar código, y tenerlo todo organizado, vamos a crear archivos .h y .cpp:

[ hworld.h ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class HolaMundo : public Window
{
 public:
  HolaMundo();
  ~HolaMundo();

  Label etiqueta;
};

#endif

[ hworld.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "hworld.h"

HolaMundo::HolaMundo()
{
  this->set_title("Hola Mundo!");
  this->set_border_width(5);
  this->set_default_size(400, 200);

  etiqueta.set_text("Hola Mundo!!");
  this->add(etiqueta);

  this->show_all_children();
}

HolaMundo::~HolaMundo()
{
}

[ hellomain.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <gtkmm.h>
#include "hworld.h"

int main(int argc, char *argv[])
{
  Main entorno (argc, argv);

  // Creamos la ventana
  HolaMundo hmundo;

  // Ejecutamos
  entorno.run(hmundo);

  return 0;
}

Ahora compilamos con:

$ g++ -o hellom hellomain.cpp hworld.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Hará lo mismo, ¡vaya tontería! pero lo tendremos todo mucho más organizado, hemos introducido todo lo referente a la ventana en una misma clase.

Una pequeña nota: escribo arriba using namespace Gtk para no repetir todo el rato Gtk::[Tipo] , Gtk::[Método], ya que todo lo que estoy utilizando hasta ahora pertenece al espacio Gtk.

Ahora vamos a hacer algo más complicado, vamos a utilizar algunos elementos más y vamos a introducir algún botón, para que se desempeñe alguna acción:
[ hworld.h ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class HolaMundo : public Window
{
 public:
  HolaMundo();
  ~HolaMundo();


  void click_salir();
  void click_mensaje();

  VBox cajaV;
  HButtonBox botonera;
  Label etiqueta;
  Button *botonSalir, *botonMensaje;
};

#endif

Creamos un objeto derivado de Gtk::Window en el que incluimos los elementos que habrá en la ventana:

  • VBox es una caja con divisiones horizontales, es como si cortáramos horizontalmente en trozos a lo largo de la vertical
  • HButtonBox es una caja para poner botones a lo largo de la horizontal
  • Button es un botón. Pondremos dos, uno para salir y otro para mostrar un mensaje emergente
  • Label es nuestra etiqueta de “Hola Mundo”

Definimos dos métodos para los eventos click_salir() y click_mensaje().

Los botones los he declarado como punteros para demostrar cómo podemos trabajar con objetos de este tipo.

[ hworld.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "hworld.h"
#include <sstream>      // Para hacer string<<int

HolaMundo::HolaMundo()
{
  // Configuro la ventana
  set_title("Hola Mundo!");
  set_border_width(5);
  set_default_size(400, 200);

  // Configuro la etiqueta
  etiqueta.set_text("Hola Mundo!!");

  // Configuro la botonera
  botonSalir=new Button(Stock::QUIT);
  botonMensaje=new Button("Mensaje");

  botonSalir->signal_clicked().connect(sigc::mem_fun(*this, &HolaMundo::click_salir));
  botonMensaje->signal_clicked().connect(sigc::mem_fun(*this, &HolaMundo::click_mensaje));

  botonera.pack_start(*botonSalir, PACK_SHRINK);
  botonera.pack_start(*botonMensaje, PACK_SHRINK);

  // Configuro la división
  add(cajaV);
  cajaV.pack_start(etiqueta, PACK_EXPAND_WIDGET); // Esto ocupará el máximo tamaño
  cajaV.pack_start(botonera, PACK_SHRINK);

  show_all_children();
}

HolaMundo::~HolaMundo()
{
  delete botonSalir;
  delete botonMensaje;
}

void HolaMundo::click_mensaje()
{
  int res;
  std::stringstream ss;
  MessageDialog dialog("Esto es un mensaje emergente");
  res=dialog.run();

  if (res==RESPONSE_OK)
    etiqueta.set_text("Has dicho OK");
  else if (res==RESPONSE_DELETE_EVENT)
    etiqueta.set_text("Has cancelado el diálogo");
  else
    {
      ss<<"Has respondido otra cosa: "<<res;
      etiqueta.set_text(ss.str().data());
    }
}

void HolaMundo::click_salir()
{
  hide();
}

Vemos cómo en el constructor hemos configurado todos los elementos de la ventana, vemos cómo en la botonera y la cajaV, para añadir objetos se ha utilizado el método pack_start, las propiedades Gtk::PACK_SHRINK (encogerá el objeto contenedor) y Gtk::PACK_EXPAND_WIDGET (lo expanderá)

Pero la cajaV la añadiremos a la ventana con el método add.

Los botones los creamos con new (recordamos que eran punteros), tenemos muchos botones de Stock predefinidos, y podemos crear uno con esta propiedad (todos están en Gtk::Stock::XXXXX), aunque también podemos crearlos directamente con un texto.

Para conectar los eventos (de los botones por ejemplo) usamos connect(), como parámetro podemos incluir una función (con sigc::ptr_fun(&funcion)) o un método de clase (con sigc::mem_fun(*objeto, &Clase::metodo)), será la función o método que llamaremos cuando se genere el evento (en este ejemplo será el click sobre el botón).

Además, incluyo una caja de diálogo en la que luego distinguimos si se cierra por el botón aceptar (Gtk::RESPONSE_OK), o cerrando la ventana (Gtk::RESPONSE_DELETE_EVENT) modificando el texto del Label.

[ hellomain.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <gtkmm.h>
#include "hworld.h"

int main(int argc, char *argv[])
{
  Main entorno (argc, argv);

  // Creamos la ventana
  HolaMundo hmundo;

  // Ejecutamos
  entorno.run(hmundo);

  return 0;
}

Como vemos, el programa principal es exactamente igual que el anterior, como la compilación del proyecto:

g++ -o hellom hellomain.cpp hworld.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Clases abstractas e interfaces en PHP

Viernes, 4 de Junio de 2010 Gaspar Fernández 2 comentarios

objetoLos interfaces son bastante comunes en la programación orientada a objetos, en PHP, son algo así como una declaración de lo que debe implementar una clase. Tendrá información de los métodos (exclusivamente) que se deben implementar.

Las clases abstractas contienen atributos y métodos, algunos pueden estar implementados y otros pueden ser abstractos. Los métodos abstractos son aquellos que no están implementados. Como tienen métodos sin implementar no podemos crear una instancia de este tipo de clases; entonces, ¿ para qué valen ?
Los métodos abstractos, los debemos implementar en las clases derivadas.

Vemos que tanto interfaces como clases abstractas son muy parecidos, los dos muestran declaraciones que debemos implementar más adelante, pero veamos algunas diferencias:

  • Una interfaz no puede contener atributos ni métodos implementados, mientras que una clase abstracta sí que puede contenerlos
  • Una clase sólo puede heredas una clase abstracta, mientras que en una misma clase se pueden implementar varias interfaces.
  • Las clases abstractas pueden declarar los métodos con diferente visibilidad (public, protected, internal, etc; private no, esto indicaría que una clase derivada no podría ver el método, por tanto no tendría sentido; final tampoco, porque no admitiría implementaciones futuras, aunque una clase derivada sí podría contener un método como final), mientras que en los interfaces todos los métodos deben ser públicos (también static)
  • Los métodos que no implementamos en una clase abstracta, deben ser abstract, en un interface, todos los métodos son abstract, y ninguno debe ser implementado.

Por último, aunque no sea un ejemplo demasiado concreto, pondré un fragmento de código en el que se define una clase abstracta (Articulo, como un artículo de una tienda), y varios interfaces de algunos tipos de artículo. (Intentao demostrar todo lo que he contado antes, para nada es un ejemplo real):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php

abstract class Articulo
{
  private $stock;
  private $precio;

  abstract protected function venta($cantidad);

  abstract function calcula_precio_total($cantidad, $descuento);
}

interface En_caja
{
  function idiomas();
  function lectura($idioma);
}

interface Juguete
{
  function puedo_probarlo();
  static function probar();
  function tirar_al_suelo();
}

class HomerSapiens extends Articulo implements En_caja, Juguete
{
  protected function venta($cantidad)
  {
    ...
  }

  final function calcula_precio_total($cantidad, $descuento)
  {
    ...
  }

  function idiomas()
  {
    ...
  }

  function lectura($idioma)
  {
    ...
  }

  function puedo_probarlo()
  {
    ...
  }

  static function probar()
  {
    ...
  }

  function tirar_al_suelo()
  {
    ...
  }
}
?>

Foto: kevindooley (Flickr)

Enseñando a programar

Viernes, 7 de Mayo de 2010 Gaspar Fernández 5 comentarios

Desde hace tiempo, me ofrezco como profesor particular de programación en C/C++ (entre otros), he conocido bastantes alumnos, y metodologías de varios profesores. Este artículo es una opinión personal de mi experiencia.

En principio tengo que decir que muchos de mis alumnos, sólo venían para sacarse una asignatura, no tenían demasiado interés, aunque fuera una de las asignaturas claves de sus estudios, y algo que les ayudaría el día de mañana; aunque es cierto que no todo el mundo puede aprender a programar desde cero y con soltura en 3 ó 4 meses, requiere un entrenamiento, dedicación, mucho tiempo y enfrentarse con problemas una y otra vez, y si le sumamos a esto poco interés estamos perdidos.

Es cierto que igual que en todas las ciencias e ingenierías tiene un aspecto recursivo y es que lo más sencillo se enlaza con aspectos muy complicados y es difícil introducir a alguien en este mundo sin que queden preguntas sin resolver y huecos que en el futuro se rellenarán, pero algunas insaciables mentes quieren ir más deprisa, y cuando las respuestas a sus preguntas empiezan a sonar a chino, abandonan.

Es por esto, que como profesor hay que ofrecer algún valor adicional al alumno, y aunque no es complicado ofrecer en un grupo reducido, existen muchos centros de enseñanza en los que ni siquiera se intenta; no podemos pretender enseñar C a una persona con Borland C, un IDE del año 1992 (sí, ya es mayor de edad y aunque hace años luz de su salida, se sigue usando). Los alumnos de hoy en día no valoran que hace 16 años flipábamos haciendo un entorno en modo texo, y estábamos acostumbrados a los 80 caracteres x 25 líneas.
Hoy en día ver sólo 80 letras por línea y tan pocas líneas, agobia un poco, eso de no poder cambiar el tipo de letra, el tamaño y elegir entre una gran variedad de colores parece que lo hace todo un tanto inútil, y da la impresion de que nunca se van a utilizar esas cosas.

Soy consciente de que es difícil enseñar a programar prescindiendo del modo texto, ya que la entrada/salida es muy fácil de implementar y de probar, y si estamos empezando es normal que compilemos mil millones de veces sin éxito, y luego tengamos errores de en tiempo de ejecución y ocurran sucesos extraños en nuestros pequeños programas. Por lo que en principio propondría un poco de renovación de software, es cierto que los centros donde se sigue enseñando con Borland C y programas de la época llevan haciéndolo así durante años, pero aunque C va a seguir siendo el mismo, se pueden probar otros IDEs que nos permitan por lo menos hacer el tema un poco más ameno para el alumno. (Dev-C++, Netbeans, Anjuta, o utilizar Kate y compilar a mano, también soy consciente de que éstos también se utilizan en muchos sitios).

Por otra parte, me gustaría hablar sobre la programación amena, es cierto que al principio, tenemos que limitarnos a enseñar las posibles herramientas de las que disponemos, con paciencia, ejemplos, y viendo lo que es capaz de hacer cada herramienta; pero propongo hacer programas libres a corto plazo, es decir, embarcarnos en pequeños proyectos (siempre viendo lo que está dentro de las posibilidades de lo enseñado), con lo que se pueda practicar y seguir aprendiendo. Por ejemplo:

  • Si el alumno está interesado en la economía, se puede hacer un sencillo gestor de economía personal, o un pequeño simulador FOREX (Mercado de divisas) con unos pocos datos generados.
  • Si le gusta la fotografía, podemos empezar (con algunas funciones prediseñadas) a leer y escribir archivos BMP o JPG. Aunque suene complicado símplemente se pueden entregar un .h y un .c y una pequeña documentación de cómo llamar a esas funciones.
  • Está bien el hecho de hacer juegos, pero meternos en un juego directamente es una tarea un tanto complicada, ¿por qué no empezar con las tres en raya? O un juego de sumas y restas, un ahorcado…
  • Está bien empezar también con algo tangible y que todos conocemos, por ejemplo una máquina expendedora, un programa que diga una frase, tipo Facebook, un programa de envío de email (aunque tengamos que entregar algunas funciones extra).

Por esto, siempre empiezo preguntando a mis alumnos por algo que les guste, es verdad que para una clase particular es fácil, un grupo grande es mucho más difícil de llevar, sobre todo si cada uno hace un trabajo diferente, pero se puede guiar por lo que más o menos les interese a todos. Y una cosa importante, que hayamos hecho algo mil veces, no nos quita de currar, y de intentar hacerlo siempre lo mejor que podemos

Propongo unas pistas/consejos, para hacer la programación mucho más emocionante:

  • system() no sólo vale para hacer system(”PAUSE”); para que en Windows no se nos cierre la ventana; podemos ejecutar cualquier cosa, desde un programa que reproduce sonidos, hasta otro para mostrar una imagen, como un navegador web, lo que sea… podemos usarlo para más cosas.
  • Hay librerías fáciles de usar que nos pueden hacer la vida un poco más amena, por ejemplo xosd, con lo que podremos presentar mensajes en pantalla
  • Kdialog, dialog, Xdialog, Zenity… son programas que nos ayudarán a mostrar mensajes en pantalla de forma fácil, usémoslos.
  • Los alumnos se acostumbrarán antes a llamar código de terceros si les proporcionamos las librerías para ello, a lo mejor se ve demasiado complicado, pero obtener un texto de una página web, o que se vea que un mensaje se ha sacado de Google o Twitter, cuando acabas de empezar te anima un poco.
  • No podemos pedir ejercicios muy simples, y de repente un ejercicio extremadamente complicado, que nadie va a ser capaz de resolver, debemos ir progresivamente y pensar que aún no se tiene una mentalidad de desarrollador, tenemos que desarrollarla nosotros, no hacer que abandone.
  • Lo más importante, paciencia, nosotros tenemos años de experiencia y tratamos con personas que no tienen ninguna, y empiezan ahora, tenemos que hacer un seguimiento de lo que están haciendo, aunque es cierto que algún día hay que aprender a buscarse la vida, tenemos que hacerlo poco a poco y escoger el momento.

Casi todo este post, se ha basado en mi experiencia de programación en C/C++, y es verdad que en otros lenguajes (Visual Basic por ejemplo), se realizan otro tipo de ejercicios algo más interactivos y que generalmente gustan más; es cierto que nos deja muchas cosas hechas y no tenemos que recorrer tanto camino para llegar a algo complicado, aunque también es cierto que hay muchos más lenguajes cuya sintaxis se parece más a C que a Basic, por lo que considero uno de los grandes pilares de la programación.

Me gustaría dedicar un par de párrafos al pseudolenguaje o pseudocódigo, es cierto que a muchas personas les ha ayudado en la comprensión del código y el diseño de los algoritmos, así como cuando se quiere decir algo en un idioma diferente al idioma natal, primero lo pensamos en nuestro idioma (más o menos) y luego traducimos en dos o tres iteraciones (primero normal, luego cambiamos verbos de sitio, luego adjetivos…); aunque a la hora de programar, primero tenemos en mente nuestra idea de lo que queremos hacer, y luego diseñamos el algoritmo en pseudolenguaje, tras ello, traducimos línea por línea, en un proceso que puede llegar a durar varios minutos.

Es una expresión en lenguaje natural, que resulta de todo menos natural, y realmente lo veo un tanto pérdida de tiempo; para aprender a programar y a crear algoritmos uno de los procesos fundamentales es compilaro y probarlo hasta que salga, y con el pseudolenguaje no tenemos forma de hacerlo (sí, hay algún compilador, pero prácticamente no se utiliza). ¿Por qué no enseñamos a programar directamente? Así podemos probar lo que hacemos en tiempo real y sobre todo, saber que progresamos. Sí está bien utilizar diagramas de flujo y tratar de entender el algoritmo, hacer esquemas, etc; pero me refiero a intentar aprender una sintaxis nueva que en tres o cuatro meses que suele haber para aprender la materia se puede atragantar y evita que avancemos.

Documentando el código con Doxygen

Domingo, 11 de Abril de 2010 Gaspar Fernández Sin comentarios

librosssTanto o más importante que tirarse horas programando una aplicación es su documentación, y debemos hacerlo aunque nosotros seamos los únicos que intervengamos en su desarrollo.
Algo que siempre digo en mis clases de programación es que a poco que compliquemos el código si no comentamos lo que estamos haciendo, en séis meses cuando toque hacer una siguiente versión no tendremos ni idea de lo que hace; y esto conlleva pasar más tiempo para hacer las modificaciones que necesitamos, que al final se traducen en dinero.

Doxygen es una de esas herramientas que nos hacen la vida más fácil, ya que analizará los comentarios de nuestros archivos fuente y generará un documento (html, latex…) con todas las clases, funciones, métodos, variables globales, definiciones, etc que contenga nuestro código; se generará un documento muy valioso para hacer futuras modificaciones.

Este programa tiene multitud de opciones y palabras clave que podemos ver en su documentación. Pero yo comentaré lo justo para empezar, ya que no queremos perder mucho tiempo y queremos empezar a documentar código. Hay muchos lenguajes soportados, pero tanto para C, C++, PHP y algunos otros podemos hacer comentarios con /* …. */ o //, pues bien, Doxygen leerá los comentarios que comiencen por /** (barra, asterisco, asterisco) o /// (tres barras). Lo que situemos como comentario será la descripción de la función, clase, método, o lo que sea que pongamos detrás; así como la descripción del propio fichero que pondremos como primer comentario al principio de nuestro código.

Para Doxygen existen algunas palabras clave como por ejemplo:

  1. @file : Especifica el propio fichero que editamos, útil para la descripción del fichero.
  2. @brief : Breve descripcion de la función, método, clase, variable… que estamos describiendo.
  3. @date : Fecha
  4. @author : Autor
  5. @version : Versión
  6. @param : Parámetro (en caso de funciones y métodos)
  7. @return : Valor de la salida (en caso de funciones y métodos)

Propongo un pequeño código en PHP que no hace nada, pero que se documentará bien:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
 /**
******************************************************
*  @file test.php
*  @brief Breve descripción
* Pequeña documentación del archivo
* @author Gaspar
* @version 2.0
* @date Marzo 2010
*
*
*******************************************************/


/**
* Variable global
*/

$var_global='VALOR';

/** Ahora me da por definir cosas */
define('defino_algo', 'DEFINICION');
/*
* Description: Esta función algo hará
*
* @param $cadena
* Esto es una cadena graciosa
* @param $vector
* Esto es un vestor gracioso
*
* @return string
* Cadena resultante

*/

function estoyprobando($cadena, $vector)
{
}

/**
******************************************************
* @brief Clase para hacer pruebas
*
******************************************************/

class TestingPHP
{
/**
*************************************************************
* @brief Constructor
*************************************************************/

function __construct()
{
}

/**
*************************************************************
* @brief Metodo 1
* @param $par Primer parametro de entrada
*
* @return Genero una salida
*************************************************************/

function metodo1($par)
{

}
};
?>;

Para documentarlo con Doxygen, tendremos que hacer lo siguiente:

$ doxygen -g testing.cfg

Generaremos un archivo de configuración de la documentación de ejemplo, sólo tendremos que cambiar unas cuantas cosas para generar la documentación como nosotros queramos:

  • PROJECT_NAME=Miproyecto (una sóla palabra)
  • PROJECT_NUMBER=0.2 (la versión)
  • OUTPUT_DIRECTORY=doc (Directorio donde irá la documentación)
  • OUTPUT_LANGUAGE=Spanish (Queremos documentación en español
  • EXTRACT_ALL= YES (Incluso lo que no esté documentado aparecerá en la documentación
  • EXTRACT_PRIVATE=YES (Extraeremos los métodos públicos)
  • EXTRACT_STATIC= YES (Extraeremos los métodos estáticos)
  • RECURSIVE= YES (Extracción recursiva, incluiremos los subdirectorios)
  • CREATE_SUBDIRS=YES (Creará subdirectorios a la hora de documentar, es importante si tenemos muchas líneas de código fuente (muchas funciones, clases, archivos….) ya que de otra forma situará todos los archivos de la salida en el mismo directorio, y pueden ser muchos.)
  • FILE_PATTERNS = *.php3 *.php *.c *.cpp (Todas las extensiones de nuestros archivos fuente)
  • PDF_HYPERLINKS= YES (Crearemos una salida LaTeX con enlaces que se exportarán a PDF)
  • HAVE_DOT=YES (Crearemos mejores gráficos de clases)

Tras ello hacemos:

$ doxygen testing.cfg

Tendremos en el directorio doc toda la documentación de nuestra aplicación.

Foto: br1dotcom (Flickr)

La web a mi gusto: Descargar contenidos de Moodle rápidamente

Viernes, 12 de Febrero de 2010 Gaspar Fernández Sin comentarios

Quiero continuar con una serie de posts que llevarán una temática parecida: muchas páginas web funcionan de una forma fija, pero a mí me incomoda bastante esa forma de funcionar, pero como necesito cubrir mis necesidades, intento que funcionen como yo quiero.
Tras esa gran descripción, quiero introducir mi problema:
Moodle es un sistema ampliamente aceptado como solución web académica, tiene muchas opciones y está muy bien, personalmente, en la Universidad, los profesores cuelgan apuntes, ejercicios y más material a la web, lo malo es que cuando llego a una página, me gusta descargar a mi disco duro todos los contenidos (pdfs, documentos, etc), y para ello invierto mucho tiempo.
Para ello viene bien una extensión para Firefox: Chickenfoot, también podemos utilizar Greasemonkey (también hay extensiones para IE y Safari).

Sin más preámbulos, el script utilizado para que Moodle descargue los contenidos más rápido es el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var alltags = new Array();

function detecta_pdf(cont)
{
  var url_match = /https?:\/\/([-\w\.]+)+(:\d+)?(\/([\w/_\.]*(\?\S+)?)?)?/;
  var contenido=cont.match(url_match);
  document.location=contenido[0];
}

function pdfplay(que)
{
  var cont=que.innerHTML;
  que.innerHTML='';
  detecta_pdf(cont);  
}

function CogeElementoPorClase(tipo, clase, quehacer)
{
  var alltags=document.getElementsByTagName(tipo);

  for (i=0; i<alltags.length; i++)
  {
    if (alltags[i].className==clase)
    {
    quehacer(alltags[i]);
    }
  }
}

CogeElementoPorClase('div', 'resourcepdf', pdfplay);

La función CogeElementoPorClase() es la misma que utilicé en este post anterior para modificar la web de mi proveedor de hosting.

Tras ello debemos crear un trigger, es decir será el evento con el que se disparará automáticamente el script que acabamos de hacer; por ejemplo, para la Universidad de Málaga podemos decir que el script se dispare cuando la página a la que accedemos sea: http://*.cv.uma.es/mod/resource/view.php?id=*.

En principio el script fue pensado para los PDFs (el acrobat reader tarda bastante en abrirse cuando tienes que abrir más de 10 PDFs), pero puede servir para cualquier contenido. Por otra parte, aunque no está muy optimizado (la necesidad me llevó a hacer el script rápidamente) espero que os sea útil.

Visita otras webs de la red