Practica1

=Practica 1: Introducción a las Clases=

1.Introducción básica
En este primer tema vamos a aprender los fundamentos de C++. Comenzaremos creando una clase Entero que representara un valor entero. Vamos a comenzar creando el fichero makefile. code all:integer integer: main.cpp integer.cpp g++ -I. main.cpp integer.cpp -o integer clean: rm *.o *~ integer -f code Ahora vamos a crear nuestro main.cpp code format="cpp" //////////////////////////////////////////////////// // main.cpp ////////////////////////////////////////////////////

using namespace std; using namespace values; int main(int argc,char **argv) {   Integer I;    cout<
 * 2) include

Este el nuestro fichero main.cpp. En el, lo primero que hacemos es incluir a el fichero //integer.h// que contendrá la declaración de la clase que vamos a crear. Después, incluimos el fichero //iostream// que contiene la definición para entrada salida de C++. Nótese que no es iostream.h. En C++, la mayoría de los ficheros de inclusión estandard, acaban sin .h y a veces comienzan con c. Ejemplos de ficheros de C que pasan a C++

code format="cpp" code
 * 1) include   --> #include
 * 2) include   --> #include

Después, las directivas

code using namespace std; using namespace values; code indican al compilador que vamos a usar los espacios de nombres std (estándar) y values (que nosotros creamos). En C++, las clases se encapsulan en espacios de nombres. De esta forma se organizan mejor para proyectos grandes. El espacio de nombres std define el espacio de nombres estándar de C++. Si no indicamos explícitamente que lo vamos a usar, el compilador nos dará un error a tratar de usar cout, ya que esta es una clase de este espacio de nombres. En la función main, observamos la siguiente declaración:

code format="cpp" Integer I; code Esto está diciendo a compilador que debe crear una variable de tipo Integer. Es decir, que vamos a crear una variable de la clase Integer, o también se dice que hemos creado un objeto de la clase Integer.

Como ya se ha visto en teoría, podemos llamar a las funciones del los objetos usando el operador “.” (punto). Por esto, la expresión (I.getValue) es una llamada a la función getvalue del objeto I. Si tuviesemos el codigo siguiente,

code format="cpp" Integer I,I2; cout<<I.getValue<<endl; cout<<I2.getValue<<endl;

code

Estaríamos creado dos objetos distintos, ambos de la clase Integer. Y I.getValue es una llamada a la función del objeto, que seria distinto a llamar a I2.getValue. La diferencia, como ya veremos mas adelante, es que I.getValue usara los valores del objeto I, y I2.getValue usara los valores de I2. Vamos por fin a ver como se definen las clases. Una clase se define en dos ficheros, un .h y un .cpp. El fichero .h contiene la definición o declaración de la clase, mientras que el .cpp contiene la implementación de la clase. Veamos el archivo integer.h

code format="cpp" //////////////////////////////////////////////////// // integer.h //////////////////////////////////////////////////// namespace values { /**\brief This class represents an integer value class Integer { public: /** Returns the current value of the integer */   int getValue; /**Sets a value in this object */   void setValue(int v);
 * 1) ifndef _Integer_h_
 * 2) define _Integer_h_

private: int _ivalue; };

} code
 * 1) endif

Las clases se definen dentro de un espacio de nombres “namespace”. Los espacios de nombres comienzan por letra minúscula por convenio. El nombre de las clases comienza con mayúscula (por convenio). La clase “Integer” que representa un entero. La clase permitirá manejar el entero mediante //funciones miembro// de la clase.

Las clases se definen usando la palabra clave //class.// Después se define la parte publica, es decir, aquella que podrá ser usada por otras clases o funciones. Mas adelante se define la privada, donde se colocan las variables o funciones que solo podrán accederse desde funciones miembro de la misma clase.

La clase ha definido dos funciones publicas. La primera sirve para retornar el valor del entero y la otra para fijar el valor. Finalmente en la parte privada se ha declarado un entero que representa el valor entero de la clase. //Las variables privadas se declaran comenzando por subrayado “_” (por convenio).//

Otro aspecto muy importante es el de la documentación. Se debe documentar el .h poniendo un comentario del tipo: code /** code Veremos mas adelante como existen herramientas que nos crearan una documentación html del código gracias al uso de este tipo de comentario. Ahora, vamos a crear el .cpp

code format="cpp" //////////////////////////////////////////////////// // integer.cpp //////////////////////////////////////////////////// namespace values{
 * 1) include "integer.h"

//////////////////////////////////// // //////////////////////////////////// int Integer::getValue { return _ivalue; }

//////////////////////////////////// // //////////////////////////////////// void Integer::setValue(int v) { _ivalue=v; }

} code Finalmente, compile haciendo make y ejecute el programa.

2.1 Constructor vacío
Probablemente, a estas alturas se haya dado cuenta de que el valor inicial que tiene el entero es un valor basura. Sin embargo, es posible alterar este comportamiento de la clase añadiendo constructores. El constructor es una función que se invoca al crear un objeto de la clase, pero para ello ha de estar definida. Vamos a crear de primeras el constructor vacio. Añada el siguiente código a la definición de la clase justo bajo la palabra clave “public”

code format="cpp" /**Empty constructor Integer;

code Esto es un constructor, es una función que no devuelve nada y que se llama igual que la clase. Fíjese muy bien que no solo no devuelve nada, sino que tan siquiera dice void. al ser una función, tiene parámetros, pero en este caso es vacio de parámetros. //El/los constructores son la primeras funciones de la clase y deben ser públicos.// Ahora veamos su implementación en el .cpp code format="cpp" /////////////////////////// // ///////////////////////// Integer::Integer { _ivalue=0; } code Lo que hacemos es asignar la variable a valor 0 para que no sea basura al crearse el objeto. Finalmente, volvamos a compilar y ejecutar. Como se observa, la salida ahora es 0 10.

2.2 Sobrecarga de constructores
En C++, es posible crear varias funciones miembro con el mismo nombre aunque con diferentes argumentos. A esto se llama //sobrecarga//. Será el compilador el que se encargue de decidir a que función se debe llamar, dependiendo de los argumentos que usemos. Vamos a probar la sobrecarga añadiendo dos constructores típicos que son: el constructor parametrizado y el de copia. El primero servirá para asignar un valor a la variable en la creación y el segundo servirá para crear un objeto que sea copia de otro. Añadamos el siguiente código a .h

code format="cpp" /**Parametrized constructor Integer(int v); /**Copy constructor Integer(const Integer &I);

code Y ahora al .cpp

code /////////////////////////// // ///////////////////////// Integer::Integer(int v) { _ivalue=v; }

/////////////////////////// // ///////////////////////// Integer::Integer(const Integer & I) { _ivalue=I._ivalue; }

code Obsérvese que el primero recibe como argumento una valor entero y lo asigna a la variable.

El segundo requiere un poco mas de explicación. Es denominado operador de copia, y lo que hace es que hace que el objeto creado sea una copia del que se pasa. Pero cuales son los argumentos? const Integer & I

La palabra clave //const// sirve para indicar que el elemento pasado no debe ser modificado en el interior de la función. Después vemos el símbolo &. En C había dos formas básicas de pasar argumentos, por valor y por referencia. En el primer, caso se pasaba un elemento y dentro de la función se hacia una copia con la que se trabajaba. Por referencia (puntero), se pasaba la dirección de memoria se podía modificar el valor. Cuando queremos pasar como parámetro una clase en C++, tenemos la misma idea, paso por valor o por referencia. Sin embargo, el paso por valor no es recomendable cuando se pasa una clase. Esto es por que las clases pueden contener una gran cantidad de variables y el realizar una copia podría llegar a ser muy costoso e ineficiente. Por tanto, //es preferible hacer el paso de un objeto siempre por referencia//. Para ello, en C++ se define el operador &. Cuando pasamos &, estamos indicando que podemos modificar el valor de la variable dentro de la función. Veamos un ejemplo muy simple. code format="cpp" main { int myV=10; modifica(v); cout<<v<<endl; }

void modifica(int &v) { v=0; } code Bien, en el caso del constructor de copia, estamos pasando por referencia el objeto (para evitar una copia de todos sus datos), pero a la vez le estamos diciendo que se haga constante. Es decir, que no sea posible modificar sus valores en el interior.

Por tanto, el siguiente código sería incorrecto: code format="cpp" /////////////////////////// // ///////////////////////// Integer::Integer(const Integer & I) { I._ivalue=101; }

code ya que estamos modificando la variable _ivalue del objeto I que se supone que es constante dentro de la función. Bueno, dicho esto, vamos a ver como funcionan los constructores. Reescribimos nuestra función main para que quede de la siguiente manera

code format="cpp" int main(int argc,char **argv) {

Integer I; //constructor vacio cout<<I.getValue<<endl; Integer I2(91); //constructor parametrizado cout<<I2.getValue<<endl;

Integer I3(I2); //constructor de copia cout<<I3.getValue<<endl;

} code En el ejemplo, I es creado usando el constructor vacío. I2 usando el constructor parametrizado y I3 usando el de copia.

2.3 Operadores
Otra de las ventajas de las clases es que permite definir operadores sobre las clases (=,+,-,*,/). Vamos a definir el operador de asignación. Añada en la parte publica de la clase Integer la siguiente definición code format="cpp" /**Assign operator Integer & operator=(const Integer & I);

code y en el .cpp code format="cpp" Integer & Integer::operator=(const Integer & I) { _ivalue=I._ivalue; return *this; }

code

Los operadores son funciones miembros que permiten definir operaciones habituales. Todos los operadores devuelven un copia del objeto actual con el objetivo de poder realizar un encadenamiento (ahora después quedara esto mas claro).

El valor de retorno es Integer &, es decir, una referencia a un objeto de la clase. Si observamos la implementación, una vez se realiza la copia, se retorna (*this); Veamos que es esto. La palabra reservada this, es una referencia (es decir, puntero) al objeto a actual. Esto probablemente no se entienda aun. El puntero this, usado dentro de una función de la clase Integer, es un puntero a al objeto, por tanto en este caso es un puntero de tipo Integer*. Dentro de una clase Float (por ejemplo, sera un Float*). Se que aun no se entiende bien, pero tratemos de seguir. Si this es un Integer*, entonces *this es el propio objeto, es decir (Integer). Entonces, lo que estamos devolviendo es el propio objeto al hacer return *this; ¿Para que sirve esto? Veamos el ejemplo:

code format="cpp" int main(int argc,char **argv) {

Integer I(10); //constructor vacio Integer I2; cout<<I2.getValue<<endl; I2=I; cout<<I2.getValue<<endl; };

code En este ejemplo, I es asignado a I2. Por tanto, I2 sera una copia de I. Veamos ahora entonces: code format="cpp" int main(int argc,char **argv) {

Integer I(10); //constructor vacio Integer I2,I3; cout<<I2.getValue<<endl; I3=I2=I;

}; code En este ejemplo, I es asignado a I2, que a su vez es asignado a I3. Al escribir I3=I2=I, el compilador primero realizara I2=I. El resultado de esta operación es un entero (I2), que será asignado a I3. Es decir, el compilador descompondrá la operación en: code format="cpp" I2=I; I3=I2; code

Esto es posible gracias a que el resultado de la asignación es un Integer &.

2.3.1 Sobrecarga de operadores
Como hemos indicado anteriormente, en C++ es posible sobrecargar funciones miembro. Los operadores no van a ser menos. Vamos a sobrecargar el operador de asignación de forma que sea posible hacer algo como I=12; Para ello, lo único que deberemos hacer es crear un nuevo operador que reciba como parámetro un entero en lugar de un Integer. Por tanto, en el .h añadimos

code format="cpp" /**Assign operator Integer & operator=(int v);

code y en el .cpp code Integer & Integer::operator=(int v) { _ivalue=v; return *this; } code

Ahora podemos hacer code format="cpp" int main(int argc,char **argv) {

Integer I(10); //constructor vacio Integer I2,I3; cout<. El primer caso se usaba cuando el fichero a incluir estaba en la ruta local, es decir, en el directorio de compilación. Cuando usábamos <>, estábamos indicando al compilador que el fichero se encontraba en algunos de los directorios de búsqueda por defecto. Estos directorios depende de la configuración del compilador, pero suelen incluir a /usr/include y /usr/local/include. Dentro de estos lugares están la mayoría de los .h del estándar de C y C++. Probar a hacer un listado (ls). Sin embargo, nosotros podemos decirle al compilador que añada directorios a los que el tiene por defecto usando la opción -I (de include). Por eso, en el makefile que hicimos pusimos
 * 1) include 

code g++ -I. main.cpp integer.cpp -o integer code

Le estábamos indicando que incluyera el directorio local “.”. Esto nos permite hacer declaraciones <> dentro de nuestros programas a archivos .h que nosotros generamos. ¿Por qué esto es importante? Bien, si nosotros queremos distribuir una librería, lo mas útil es que generemos un programa (o un dpkg o rpm) que instale los archivos .h en un lugar estándar como (/usr/include/ o /usr/local/include). En este caso, lo más útil es que el que use nuestra librería haga inclusiones del tipo <>. De todas formas, esto quedará más claro conforme avance el curso.

4 Ejercicio
Como ejercicio, debéis hacer la clase Float, al mismo estilo que la clase Integer.