Estructuras de selección - Nacimiento de un Pájaro
En casi todas las instancias en que queremos resolver un problema seleccionamos entre distintas opciones dependiendo de si se cumplen o no ciertas condiciones. Los programas de computadoras se construyen para resolver problemas y, por lo tanto, deben tener una estructura que permita tomar decisiones y seleccionar alternativas. En C++, las selecciones se estructuran utilizando if
, else
, else if
o switch
. Muchas veces el uso de estas estructuras también envuelve el uso de expresiones de relación y operadores lógicos. En la experiencia de laboratorio de hoy, practicarás el uso de algunas estructuras de selección completando un diseño utilizando una clase llamada Bird
. También repasarás conceptos relacionados a objetos.
Objetivos:
- Utilizar expresiones relacionales y seleccionar operadores lógicos adecuados para la toma de decisiones.
- Aplicar estructuras de selección.
- Analizar la declaración de una clase para entender cómo crear y manipular objetos de esa clase.
- Practicar la creación y manipulación de objetos, y la invocación de "setters" y "getters".
Pre-Lab:
Antes de llegar al laboratorio debes haber:
Repasado los siguientes conceptos relacionados a estructuras de decisión:
a. operadores lógicos.
b.
if
,else
,else if
.Repasado los siguientes conceptos relacionados a objetos y clases en C++:
a. la creación de objetos de una clase.
b. la utilización de métodos "getters" para acceder a los atributos de un objeto.
c. la utilización de métodos "setters" para modificar los atributos de un objeto.
Estudiado la documentación de la clase
Bird
disponible en este enlace.Estudiado los conceptos e instrucciones para la sesión de laboratorio.
Tomado el quiz Pre-Lab, disponible en Moodle.
Para facilitar la ejecución de esta experiencia de laboratorio, comenzaremos repasando algunos conceptos relacionados a objetos y describiremos la clase Bird
.
Clases y objetos en C++
Un objeto es un ente que contiene datos y procedimientos para manipularlos. Al igual que cada variable tiene un tipo de dato asociada a ella, cada objeto tiene una clase asociada que describe las propiedades de los objetos: sus datos (atributos), y los procedimientos con los que se pueden manipular los datos (métodos).
Para definir y utilizar un objeto no hay que saber todos los detalles de los métodos del objeto pero hay que saber cómo crearlo, y cómo interactuar con él. La información necesaria está disponible en la documentación de la clase. Antes de crear objetos de cualquier clase debemos familiarizarnos con su documentación. La documentación nos indica, entre otras cosas, que ente se está tratando de representar con la clase, y cuáles son los interfaces o métodos disponibles para manipular los objetos de la clase.
Dale un vistazo a la documentación de la clase Bird
que se encuentra en este enlace.
Clases
Una clase es una descripción de los datos y procesos de un objeto. La declaración de una clase establece los atributos que tendrá cada objeto de esa clase y los métodos que pueden invocar.
Si no se especifica lo contrario, los atributos y métodos definidos en una clase serán privados. Esto quiere decir que esas variables solo se pueden acceder y cambiar por los métodos de la clase (constructores, "setters" y "getters", entre otros).
Lo siguiente es el esqueleto de la declaración de una clase:
class NombreClase {
// Declaraciones
private:
// Declaraciones de variables o atributos y
// prototipos de métodos
// que sean privados para esta clase
tipo varPrivada;
tipo nombreMetodoPrivado(tipo de los parámetros);
public:
// Declaraciones de atributos y
// prototipos de métodos
// que sean públicos para todo el programa
tipo varPública;
tipo nombreMetodoPúblico(tipo de los parámetros);
};
Puedes ver la declaración de la clase Bird
en el archivo bird.h
incluido en el programa de esta experiencia de laboratorio.
Objetos
Un objeto es un ente que contiene datos (al igual que una variable), llamados sus atributos
, y también contiene procedimientos, llamados métodos
, que se usan para manipularlos. Los objetos son instancias de una clase que se crean de manera similar a como se definen las variables:
NombreClase nombreObjeto;
Una vez creamos un objeto, podemos interactuar con él usando los métodos de la clase a la que pertenece.
Métodos de una clase
Los métodos de una clase determinan qué acciones podemos tomar sobre los objetos de esa clase. Los métodos son parecidos a las funciones en el sentido de que pueden recibir parámetros y regresar un resultado. Una forma elemental de conocer los métodos de una clase es leyendo la declaración de la clase. Por ejemplo, el siguiente código es parte de la declaración de la clase Bird
en el archivo bird.h
.
class Bird : public QWidget {
.
.
.
Bird(int , EyeBrowType , QString , QString, QWidget *parent = 0) ;
int getSize() const;
EyeBrowType getEyebrow() const ;
QString getFaceColor() const;
QString getEyeColor() const;
Qt::GlobalColor getColor(QString) const;
void setSize(int) ;
void setEyebrow(EyeBrowType) ;
void setFaceColor(QString) ;
void setEyeColor(QString) ;
.
.
.
};
Una vez creado un objeto, sus métodos proveen la única forma de cambiar sus atributos, obtener información o hacer cómputos en los mismos. Es por esto que comúnmente se llama interfaz al conjunto de métodos de una clase. Los métodos son la interfaz entre el usuario de un objeto y su contenido.
En general, en cada clase se definen los prototipos de los métodos para construir los objetos, y para buscar, manipular y guardar los datos. Lo siguiente es el formato general del prototipo de un método:
tipoDevolver nombreMetodo(tipo de los parámetros);
Luego, en el código del proyecto se escribe la función correspondiente al método, comenzando con un encabezado que incluye el nombre de la clase a la cuál pertenece la función:
TipoDevolver NombreClase::NombreMetodo(parámetros)
Para que los objetos que sean una instancia de una clase puedan tener acceso a las variables privadas de la clase se declaran métodos que sean públicos y que den acceso a estas clases (ver abajo "setters" y "getters"). Es preferible utilizar variables privadas y accederlas mediante los "setters" y "getters", a declararlas públicas ya que de esta manera el objeto que está asociado a estas variables tiene el control de los cambios que se hacen.
Para invocar un método escribimos el nombre del objeto, seguido de un punto y luego el nombre del método:
nombreObjeto.nombreMetodo(argumentos);
Constructores
Los primeros métodos de una clase que debemos entender son los constructores. Una clase puede tener múltiples constructores. Uno de los constructores será invocado automáticamente cada vez que se crea un objeto de esa clase. En la mayoría de los casos, los constructores se utilizan para inicializar los valores de los atributos del objeto. Para poder crear objetos de una clase, debemos conocer cuáles son sus constructores.
En C++, los constructores tienen el mismo nombre que la clase. No se declara el tipo que devuelven porque estas funciones no devuelven ningún valor. Su declaración (incluida en la definición de la clase) es algo así:
nombreMetodo(tipo de los parámetros);
El encabezado de la función será algo así:
NombreClase::NombreMetodo(parámetros)
La clase Bird
que estarás usando en la sesión de hoy tiene dos constructores (funciones sobrecargadas):
Bird (QWidget *parent=0)
Bird (int, EyeBrowType, QString, QString, QWidget *parent=0)
Puedes ver las declaraciones de los prototipos de estos métodos en la declaración de la clase Bird
en el archivo bird.h
del proyecto. La documentación se encuentra en este enlace. El primer constructor, Bird (QWidget *parent=0)
, es un método que se puede invocar con uno o ningún argumento. Si al invocarlo no se usa argumento, el parámetro de la función toma el valor 0.
El constructor de una clase que se puede invocar sin usar argumentos es el constructor por defecto ("default") de la clase; esto es, el constructor que se invoca cuando creamos un objeto usando una instrucción como:
Bird pitirre;
Puedes ver las implementaciones de los métodos de la clase Bird en el archivo bird.cpp
. Nota que el primer constructor, Bird (QWidget *parent=0)
, asignará valores aleatorios ("random") a cada uno de los atributos del objeto. Más adelante hay una breve explicación de la función randInt
.
Dale un vistazo a la documentación del segundo constructor, Bird (int, EyeBrowType, QString, QString, QWidget *parent=0)
. Esta función requiere cuatro argumentos y tiene un quinto argumento que es opcional porque tiene un valor predeterminado. Una manera para usar este constructor es creando un objeto como el siguiente:
Bird guaraguao(200, Bird::UPSET, "blue", "red");
"Setters" ("mutators")
Las clases proveen métodos para modificar los valores de los atributos de un objeto que se ha creado. Estos métodos se llaman "setters" o "mutators". Usualmente se declara un "setter" por cada atributo que tiene la clase. La clase Bird
tiene los siguientes "setters":
void setSize (int)
void setEyebrow (EyeBrowType)
void setFaceColor (QString)
void setEyeColor (QString)
Puedes ver las declaraciones de los métodos en la Figura 1 y en la declaración de la clase Bird
en bird.h
, y la implementación de algunos de los métodos en bird.cpp
. El código en el siguiente ejemplo crea el objeto bobo
de la clase Bird
y luego cambia su tamaño a 333.
Bird bobo;
bobo.setSize(333);
"Getters" ("accessors")
Las clases también proveen métodos para acceder ("get") el valor del atributo de un objeto. Estos métodos se llaman "getters" o "accessors". Usualmente se declara un "getter" por cada atributo que tiene la clase. La clase Bird
tiene los siguientes "getters":
int getSize ()
EyeBrowType getEyebrow ()
QString getFaceColor ()
QString getEyeColor ()
Puedes ver las declaraciones de los métodos en la Figura 1 y en la declaración de la clase Bird
en bird.h
, y las implementaciones de algunos de métodos en bird.cpp
. El código en el siguiente ejemplo crea el objeto piolin
de la clase Bird
e imprime su tamaño.
Bird piolin;
cout << piolin.getSize();
Otras funciones o métodos que utilizarás en esta experiencia de laboratorio
MainWindow: El archivo mainwindow.h
contiene la declaración de una clase llamada MainWindow
. Los objetos que sean instancias de esta clase podrán utilizar los métodos sobrecargados
void MainWindow::addBird(int x, int y, Bird &b)
void MainWindow::addBird(Bird &b)
que añadirán a la pantalla un dibujo del objeto de la clase Bird
que es recibido como argumento. El código en el siguiente ejemplo crea un objeto w
de la clase MainWindow
, crea un objeto zumbador
de la clase Bird
y lo añade a la posición (200,200) de la pantalla w
usando el primer método.
MainWindow w;
Bird zumbador;
w.addBird(200,200,zumbador);
Figura 1. Ventana w
con la imagen del objeto zumbador
en la posición (200, 200).
¡Importante! No es suficiente solo crear los objetos Bird
para que éstos aparezcan en la pantalla. Es necesario usar uno de los métodos addBird
para que el dibujo aparezca en la pantalla.
randInt: La clase Bird
incluye el método
int Bird::randInt(int min, int max)
para generar números enteros aleatorios ("random") en el rango [min, max]. El método randInt
depende de otra función para generar números aleatorios que requiere un primer elemento o semilla para ser evaluada. En este proyecto, ese primer elemento se genera con la invocación srand(time(NULL)) ;
.
hijo
de clase Bird tenga la altura de su padre
(también objeto de clase Bird)?
hijo.setSize( padre.setSize(10) );
hijo.getSize( padre.getSize(10) );
hijo.setSize( padre.getSize() );
hijo.setSize() = padre.get(10);
setSize
del hijo usando como argumento el resultado de invocar a getSize
al padre. La respuesta es hijo.setSize( padre.getSize() );
mama
tiene los ojos color "red"
, de qué color tendrá los ojos el objeto hijo
después del siguiente código?
"white"
"blue"
"red"
"yellow"
mama.getEyeColor()
devolverá ”red”
, la expresión condicional de la línea 16 se hará cierta, por lo tanto se asignará el color ”white”
a los ojos de hijo
. Observe que la expresión condicional de la línea 19 también se hace cierta, pero en una estructura if-else if
el primer enunciado que se haga cierto es el que determina cuál de los bloques se ejecutará. De hecho, el bloque del else if
es un bloque muerto, pues nunca será ejecutado.
mama
tiene los ojos color ”red”
, de que color tendrá los ojos el objeto hijo
después del siguiente código?
"white"
"blue"
"red"
"yellow"
mama.getEyeColor()
devolverá ”red”
, la expresión condicional de la línea 16 se hará cierta, por lo tanto se asignará el color ”white”
a los ojos de hijo
. Luego la expresión condicional de la línea 19 también se hace cierta y se le asigna el color ”blue”
. El color final es ”blue”
.
hijo
si la madre tiene tamaño 10?
hijo.setSize( madre.getSize() < 10 ? hijo.getSize() : hijo.getSize() * 2 );
9
10
15
30
madre.getSize() < 10
es falsa (pues 10 no es menor que 10), por lo tanto el resultado del enunciado condicional madre.getSize() < 10 ? hijo.getSize() : hijo.getSize() * 2
es 15 * 2
, o sea 30
.
Sesión de laboratorio:
Ejercicio 1 - Estudiar las reglas de herencia de la familia Birds
La familia Birds
Juana y Abelardo, mamá y papá pájaros, están a punto de tener un bebé al que llamarán Piolín. Al igual que los pájaros reales, los "pájaros Qt" esperan que sus bebés tengan atributos heredados de su madre y su padre.
Al terminar esta experiencia de laboratorio tu programa creará dos pájaros (Juana y Abelardo) con características aleatorias y un tercer pájaro (Piolín), con características determinadas por las características de sus padres, siguiendo un conjunto de reglas parecidas a la reglas de herencia genética.
Reglas de herencia
Color de ojos
El bebé siempre hereda el color de ojos de la madre.
Tamaño
El tamaño del bebé es el más pequeño entre el tamaño del padre o de la madre.
Color de la cara
La dominancia de los genes del color de la cara está dada por la siguiente lista (en inglés), ordenada desde el color más dominante al color menos dominante:
- blue
- green
- red
- white
- yellow
El bebé heredará el color de cara más dominante de los colores de sus padres. Por ejemplo, un bebé cuya madre tiene la cara verde y su padre tiene la cara blanca, tendrá cara verde.
Cejas
La dominancia de los genes de las cejas está dada por la siguiente lista (en inglés), ordenada desde las cejas más dominantes a las cejas menos dominantes:
- Bird::ANGRY
- Bird::BUSHY
- Bird::UNI
- Bird::UPSET
Los genes de las cejas siguen las siguientes reglas:
- Si ambos padres tienen cejas "angry", el bebé tendrá cejas "unibrow".
- Si ambos padres tienen cejas "unibrow", el bebé tendrá cejas "upset".
- En los otros casos, el bebé heredará las cejas más dominantes de las cejas de sus padres.
Ejercicio 2 - Estudiar la función main
Instrucciones:
Carga a
QtCreator
el proyectoBirthOfABird
. Hay dos maneras de hacer esto:- Utilizando la máquina virtual: Haz doble “click” en el archivo
BirthOfABird.pro
que se encuentra en el directorio/home/eip/labs/selections-birthofabird
de la máquina virtual. - Descargando la carpeta del proyecto de
Bitbucket
: Utiliza un terminal y escribe el comandogit clone http:/bitbucket.org/eip-uprrp/selections-birthofabird
para descargar la carpetaselections-birthofabird
deBitbucket
. En esa carpeta, haz doble “click” en el archivoBirthOfABird.pro
.
- Utilizando la máquina virtual: Haz doble “click” en el archivo
Configura el proyecto.
Compila y corre el proyecto. Debes ver una pantalla con dos pájaros que representan a Juana y Abelardo. Luego de un segundo, serás testigo del nacimiento de su bebé Piolín. Sin embargo, este Piolín pudo ser haber volado de otro nido y no ser su hijo, ya que tiene características aleatorias.
Abre el archivo
main.cpp
(no harás cambios a ningún otro archivo de este proyecto). Estudia la funciónmain
. NO harás cambios a la funciónmain
. Nota que la funciónmain
esencialmente hace dos cosas:i. Crea los tres pájaros y añade dos de ellos a la pantalla.
ii. Crea un cronómetro ("timer") que espera un segundo y luego invoca a la función
birth
pasándole una referencia a la ventana y a los tres pájaros.Figura 2. Función
main
.
Ejercicio 3 - Escribir el código para determinar las características de Piolín
Estudia el encabezado de la función birth
. En esta función, escribe el código necesario para que el bebé Piolín tenga las características dictadas por las reglas de herencia explicadas anteriormente.
Figura 3. Función birth
.
Entregas
Utiliza "Entrega" en Moodle para entregar el archivo main.cpp
con las modificaciones que hiciste a la función birth
. Recuerda utilizar buenas prácticas de programación, incluye el nombre de los programadores y documenta tu programa.
Referencias
https://sites.google.com/a/wellesley.edu/wellesley-cs118-spring13/lectures-labs/lab-2