sábado, 6 de septiembre de 2014

Apuntes de C++

El siguiente post, no es un tutorial en sí. Son fragmentos a modo de síntesis de mis apuntes escritos durante la lectura del libro "C++ guia de autoenseñanza" de Herbert Schildt. Un libro lleno de ejemplos y perfecto para empezar de cero o volver a recordar C++.


Sobrecarga de funciones

En C++ varias funciones pueden tener el mismo nombre mientras el tipo o el número de sus argumentos sea distinto. Cuando ocurre esto, se dice que son funciones sobrecargadas.


Funciones constructoras en C++
Las funciones contructoras de una clase en C++ tienen el mismo nombre que la clase y no incluyen ningún tipo de retorno.

class Sprite{
public:
int x;
int y;
Sprite();
Sprite(int x, int y);
~Sprite();
};

Sprite::Sprite(){
x = 0;
y = 0;
}

Sprite::Sprite(int _x, int _y){
x = _x;
y = _y;
}

Sprite::~Sprite(){
cout << "Destruyendo el sprite \n";
}

int main(int argc, char *argv[]){
Sprite sp;
cout << "Position x " << sp.x;
}

Es importante ver que a diferencia de otros lenguajes como java o javascript, en C++ una sentencia de declaración de variable es una sentencia de acción, que desencadena que sea llamado el constructor sin parámetros.
También C++ permite que se ejecute una función cuando el objeto va a ser destruido, son las llamadas funciones destructoras, y tienen el mismo nombre que la clase pero vienen precedidas por el símbolo ~. Las funciones destructoras no pueden tener parámetros.
Los objetos locales se destruyen al salir de su ámbito y los objetos globales se destruyen al finalizar el programa.

No se puede obtener la dirección ni de las funciones constructoras ni de las destructoras.

Herencia en C++
En este ejemplo tenemos una clase B que hereda de una clase A:

class A{
int x;
public:
void setX(int _x);
int getX();
};

class B : public A{
int y;
public:
void setY(int _y);
int getY();
};

La declaración de la clase B indica al compilador que B heredará de la clase A, y tendrá públicos los mismos elementos que se hayan definido como públicos en la clase A, pero que todos los elementos de la clase base A permanecerán privados para B, de forma que no serán accesibles directamente.
El hecho de que una clase derivada no tenga acceso directo a las propiedades privadas de su clase base es para mantener la encapsulación.

La sintaxis parar realizar herencia es la siguiente:
class nombre_clase_derivada : tipo_acceso nombre_clase_base{...};

El tipo de acceso puede ser public, private o protected.

Punteros a objeto
A diferencia de lenguajes como Java en el que siempre que pasamos un objeto como parámetro de una función, este es pasado por referencia. Lo que significa que realmente estamos pasando la dirección de ese objeto y que todas las modificaciones que hagamos dentro de la función afectarán al objeto en sí. En C++ si queremos modificar un objeto dentro de una función, debemos especificar la dirección que hace referencia a dicho objeto, esto es lo que llamamos un puntero a un objeto.
Un puntero a objeto es una variable que contiene la dirección de memoria donde está contenido el objeto.

class Televisor{
int pulgadas;
public:
Televisor(int _pulgadas);
int getPulgadas();
};
Televisor::Televisor(int _pulgadas){
pulgadas = _pulgadas;                      
}
int Televisor::getPulgadas(){
return pulgadas;
}

int main(int argc, char *argv[]){
    Televisor tele(32);
    Televisor *t;
    t = &tele;
    cout << "Pulgadas " << t->getPulgadas();
    return 0;
}

"Televisor *t" define un puntero para apuntar a objetos de la clase Televisor.
"t = &tele" asigna la dirección del objeto tele al puntero t.
"t->getPulgadas()" ejecuta la función miembro del objeto tele.

Clases, estructuras y uniones

Las clases y las estructuras tienen prácticamente las mismas capacidades.
En C++ las estructuras pueden incluir funciones miembro, incluyendo funciones constructoras y destructoras.
La única diferencia es que por defecto los miembros de una clase son privados y los miembros de una estructura son públicos.

struct nombre_estructura{
...miembros públicos...
private:
...miembros privados...
}lista_objetos;

Tanto struct como class crean nuevos tipos de clase.
Al poseer las mismas capacidades, es una cuestión de preferencias decidir emplear struct o class. Pero lo más común es emplear struct para objetos que no tienen funciones miembro.

Las uniones en C++ también definen tipos de clase y también pueden contener funciones miembro.
Las uniones son como estructuras, por defecto tienen todos los miembros públicos hasta que se usa el especificador private. Su característica principal es que todos los miembros comparten la misma posición de memoria (como en C).
La capacidad de las uniones para enlazar código y datos permite crear tipos de clases en los que todos los datos usan una posición compartida. Esto no puede hacerse usando una clase.
Las uniones no pueden heredar de otra clase ni ser clases base. Tampoco pueden tener ningún miembro static, ni contener objetos que tengan un constructor o destructor. Pero las uniones sí pueden tener constructor y destructor.

Funciones insertadas

C++ permite definir funciones que realmente no son llamadas sinó que son insertadas en el código en el momento de la llamada. Como no tienen asociado el mecanismo de llamada y vuelta de la función, pueden ejecutarse más rápidamente que las funciones normales.
La desventaja es que si son demasiado largas y se las llama muy a menudo, el programa aumentará su longitud.
Una función inline se debe definir antes de ser llamada.

inline tipo_retorno nombre_funcion(parámetros){...}

inline es una solicitud no una obligación para el compilador. Todo depende de las restricciones del compilador en concreto, si no la hace insertada, la compilará como una función normal.

Inserción automática
En la declaración de una función miembro se puede incluir su definición si es suficientemente corta. Esto provoca que la función se convierta automáticamente en una función insertada, si es posible. La palabra clave inline, en este caso no es necesaria.

Asignación de objetos
Cuando asignamos un objeto a otro, se hace una copia a nivel de bits de todos los miembros. Los contenidos de los datos del objeto asignado se copian en los miembros equivalentes.
La asignación hace que los dos objetos sean idénticos, pero están realmente separados.

a = b;

Copia los valores de los datos miembros de b en el objeto a. Si posteriormente modificamos a o b, son completamente independientes.

Paso de objetos a funciones

Por omisión todos los objetos se pasan por valor a una función. Esto significa que se hace una copia idéntica del objeto y es la copia la que es usada por la función. Los cambios que se hagan dentro de la función en el objeto no afectan al objeto pasado como parámetro.
Cuando se hace una copia de un objeto para usarla en una llamada a función, no se llama a la función constructora. Sin embargo, cuando la función finaliza y la copia se destruye, se llama a la función destructora. Ya que los objetos pueden haber asignado memoria que debe ser liberada. Esto puede ser fuente de problemas. Ya que, si un objeto pasado como parámetro asigna memoria dinámica y al acabar la función se llama a su función destructora y esa memoria se libera, el objeto original quedará dañado y sin uso efectivo.
Hay que asegurarse de que las funciones destructoras de las copias de objetos pasados por valor, no provoquen efectos laterales que inutilicen los objetos originales.

Para evitar esto es mejor pasar la dirección de los objetos y no los objetos en sí. Al pasar una dirección, no se crea un nuevo objeto y no se llama a ningún destructor cuando se sale de la función.

Objetos devueltos por funciones

Cuando un objeto es devuelto por una función, se crea automáticamente un objeto temporal que guarda el valor devuelto. Después de devolver el valor, este objeto se destruye. La destrucción de este objeto temporal puede causar efectos laterales inesperados en algunas situaciones.
Si el objeto devuelto por una función tiene una función destructora que libera dinámicamente memoria asignada, esa memoria puede quedar liberada incluso aunque el objeto al que se asigna el valor devuelto lo siga utilizando.
Lo principal es entender que cuando un objeto es devuelto desde una función, el objeto temporal usado para hacer efectivo el retorno habrá llamado a su función destructora.

Introducción a las funciones amigas

Una función amiga no es un miembro de una clase pero tiene acceso a sus elementos privados.
Las funciones amigas principalmente son útiles para la sobrecarga de operadores y la creación de funciones de E/S o para que una función tenga acceso a los miembros privados de dos o más clases diferentes.

class A{
    int n;
public:
    friend int nombre_función ( A obj );
};

int nombre_función ( A obj ){
    obj.n++;
    return obj.n;
}

Las funciones amigas no son funciones miembro y no se puede acceder a ellas mediante un nombre de objeto. La llamada a la función se realiza como una llamada normal.
Las funciones amigas no se heredan. Si una clase base tiene una función amiga, esta función no es amiga de las clases derivadas de esta clase base.
Una función amiga puede ser amiga de más de una clase.

Referencias anticipadas
C++ permite hacer referencias anticipadas para poder emplear nombres de clases que todavía no han sido declarados. Para hacer esto, debemos advertir al compilador antes de realizar la referencia, que el nombre en concreto que vamos a usar pertenecerá a una clase. Lo hacemos de la siguiente forma:

class nombre_clase_A;

Función miembro de una clase y amiga de otra clase


class Cliente;

class Vendedor : public Persona{
    int id_vendedor;
    float salario;
public:
    int numero_ventas(Cliente c);
};

class Cliente : public Persona{
    int id_cliente;
public:
    friend int Vendedor::numero_ventas(Cliente c);
};

int Vendedor::numero_ventas(Cliente c){...}




Hasta aquí la primera parte de los apuntes sobre C++!
Un saludo a todos!!

No hay comentarios:

Publicar un comentario