Tabla de Contenidos

C\C++ Orientado a objetos

Sintaxis

Definición de una clase

class <nombre> {};

Para indicar los elementos públicos y privados:

	public:
		<elementos>
	private:
		<elementos>

Declarar clases

Podemos declarar una clase mediante un archivo de definiciones (.h) poniendo su cuerpo en un archivo de código (.cpp).

class Probando
{
public:
	Probando(int i);
	virtual ~Probando();
};
#include "Probando.h"
#include <iostream>
using namespace std;
 
Probando::Probando(int i){
	cout << "hola";
}
 
Probando::~Probando(){
}
int Mates::Suma (int a, int b) {...}
class Perro 
{
	public: 
	Perro ()
	 {
	 	cout << "One ta namera";
	 }
 
	 void Exec()
	 {
	 	cout << this->Suma(3, 4);
	 }
 
	 int Suma (int a, int b)
	 {
	 	return a + b;
	 }
};

Constructores

Ejemplo, tenemos la siguiente declaración de clase:

class Probando
{
private:
	int X;
public:
	Probando();
	Probando(int xval);
};

Y su cuerpo:

Probando::Probando() : X(0)
{}
Probando::Probando(int xval) : X(xval)
{}	

Constructores copia

Son constructores que no tienen cuerpo y que únicamente se le pasa una referencia a otro objeto de la misma clase. El objeto construido es una copia de ese:

class pareja {
   public:
	pareja(const pareja &p);
	...

Destructores

Declarar un objeto

Para usarlo en una expresión (función): <clase>(<pars>)

Perro("Bobby");

Declararlo y contenerlo en un objeto: <clase> <obj> (<pars>)

Perro p ("Bobby");

Para llamar al constructor por defecto NO usaremos los parentesis:

Perro p;	// <- Se está llamando al constructor por defecto!

Podemos volver a declarar un objeto:

p = Perro("Bobby");

Acceder a miembros:

Si los objetos no son punteros podemos acceder mediante <obj>.<miembro>

Propiedades del lenguaje

Usar punteros

Para reservar un espacio de memoria para un objeto y colocar dicho objeto en esa posición usaremos el operador 'new' más el nombre de la clase y los parámetros del constructor, recuerda que si es el constructor por defecto no es ni necesario poner los parentesis:

new Clase;
new Clase("Parametro1");
Clase *obj = new Clase;

Para llamar a métodos de un puntero a un objeto usaremos el operador →

obj->Metodo();

O…

(*obj).Metodo();	// *obj.Metodo(); daría error 

Métodos y variables estáticas

En nuestras clases podemos crear métodos estáticos. Para usar estos no será necesario crear un objeto de dicha clase. Para usarlos haremos: NombreClase::NombreMetodo.
Recuerda que las variables estáticas deberán ser inicializadas en entorno global.
Ejemplo…

class Telephon {
private:
	static int nCalls;
public:
	static void call () {
		Telephon::nCalls++;
	}
	static int getNCalls () {
		return Telephon::nCalls;
	}
};
int Telephon::nCalls = 0;
 
int main() {
	Telephon::call();
	Telephon::call();
	cout << Telephon::getNCalls();
	return 0;
}

Herencia

public: 
	B () : A(2) {...}
void PruebaA (A* p) {
	p->Prueba();
}

A este podemos pasarle un objeto B*.

B* b = (B*)p;

Pero debemos tener cuidado, si el parámetro que viene es un objeto B o derivados no ocurrirá nada, pero si es un objeto A podemos tener problemas al acceder a los miembros de B.
Podemos llamar directamente a métodos de B sin tener que hacer la conversión:

((B*)p)->Prueba();

Herencia privada\protegida

Cuando hacemos class nombreclase : public nombreclase {…}; Estamos indicando que los miembros que hereda como públicos siguen siendolo… Imaginas que pasará cuando hagas herencia private o protected ;-)?

Herencia múltiple

Respecto a la herencia múltiple sólo decir que es posible ya que C++ permite hacer: class C : public A, public B { … };
Es posible que A y B tengan elementos que se llamen igual, y que C los herede. Al llamarlos daría error de ambigüedad, para solucionarlo sólo hay que codificar otras funciones que llamen a esas, por ejemplo en C:

void funcA ()
{
	A::func();
}
void funcB ()
{
	B::func();
}

Puede que por herencia dupliques la clase base, no directamente sino indirectamente, esto sería tener C que hereda de A y de B que, a la vez, estas dos heredan de cLetra, C no puede convertirse en cLetra porque no sabría a qué funciones acudir de cLetra, a las de A o a las de B. Para saltar este herror A y B deben heredar virtualmente de cLetra:

class cLetra
{
};
class A : virtual public cLetra
{
};
class B : virtual public cLetra
{
};
class C : public A, public B
{
};

Operador this

this->num = 4;

Métodos constantes

Dentro de la clase podemos declarar métodos constantes, estos métodos aseguran que en su cuerpo no se cambia el contenido del objeto al que pertenecen.
Por ejemplo:

void Probando::MetodoConst() const
{
	// X = 4;
	std::cout << "hola!";
}

La primera línea del cuerpo del método (X = 4;) cambiaría un valor de un dato interno de la clase, si no estubiese comentado esto petaría.

Variables globales desde dentro de la clase

Imaginemos el siguiente código:

int iNum;
class A {
private:
	int iNum;
public:
	A (){
		this->iNum = ::iNum;
	}
};

Es una declaración de una variable global y de una clase que tiene una variable interna que se llama igual a la global. Para acceder a la interna usaremos this-> y a la global: ::.

Clases internas

Podemos declarar clases internas en otras. Desde esta podremos crear objetos de la contenedora, pero no viceversa, además podremos acceder a los elementos internos y privados de ellas.
Para poder crear un objeto de una clase anidada (imaginemos que B está dentro de A), haremos:

A::B b;

También podemos definir una clase dentro de otra colocando en la definición “class nombreClase;”. Por ejemplo:

class A {
	class B;
}

Luego podemos definir class B así:

class A::B {
}

Y los métodos:

void A::B::Metodo () { }

Friends

Ejemplos:

friend class clase;
friend void metodo();
friend int operator+ (A, B);
class A {
private:
	int i;
	friend class B;
public:
	A (int d) {
		this->i = d;
	}
	void show () {
		printf("%d\n", this->i);
	}
};
 
class B {
	public:
	B (A* a) {
		a->i = 444;
	}
};
 
 
void main () {
	A a(1);
	B b(&a);
	a.show();
}

Notas sobre los amigos

Clases abstractas

virtual void func () = 0;

Ejemplo:

class cLetra {
public:
	virtual void func () = 0;
	void fanc() {
		cout << "hola";
	}
};
class A : public cLetra {
public:
	void func (){
		this->fanc();
		cout << "hola" << endl;
	}
};

Conversiones dinámicas

La conversión de toda la vida: double d = (double)i; es llamada conversión estática, y existe otra forma de realizarla, usando la sentencia static_cast:

double d = static_cast<double>(i);

Pero también existe la conversión dinámica, equivalente al as de c#, es intentar convertir una clase base en otra derivada, si el objeto que hay en la clase base no es del tipo de la derivada pone NULL en el objeto destino. Para hacer la conversión dinámica usaremos la sentencia dynamic_cast de la misma forma que usamos la conversión estática.

Para poder hacer alguna conversión de estas los elementos usados deben de ser punteros o referencias. Además, si lo que hacemos es una conversión dinámica, la clase base ha de tener algún elemento virtual.
Cuando se realiza una conversión dinámica con referencias, no podemos colocar un NULL, pero para indicar el error se lanza la excepción bad_cast.

A* a = new B;
try {
	C& c = dynamic_cast<C&>(*a);
}
catch (bad_cast) {
	cout << "error!" << endl;
}

Sacar información del tipo

B* b = new B;
if (typeid(b) == typeid(B))

Ten cuidado, aquí el objeto b no es un B, sino un B*, es decir, devuelve false.

Plantillas

Por ejemplo:

template<class T>
void func (T t)
{
	cout << typeid(t).name() << endl;
}

Si viene un objeto de la clase A, T valdrá 'clase A', por lo que ahora podemos hacer referencia a los objetos de esta. Hasta definirlos haciendo: T i; Estaríamos haciendo A i;
Para llamarla (con, por ejemplo, un integer) sólo tendríamos que hacer: func<int>(43);
Si hiciesemos lo siguiente:

template<class T>
void func (T t, T d)
{
	int i = t + d;
	cout << i << endl;
}

La clase que pasasemos a func debe de tener definido el operador '+' con otro miembro de la misma clase.

template<typename F, typename S>
class Pair
{
public:
   Pair(const F& a, const S& b);
   F get_first() const;
   S get_second() const;
private:
   F first;
   S second;
};

A la hora de crear un objeto de esta clase haríamos:

Pair<int, string> (3, "hola");
template<typename F, typename S>
Painr<F,S>::Pair(const F& a, const S& b)
{
	first = a;
	second = b;
}
F Pair<F,S>::get_first() const
{
	return first;
}

Podemos añadir argumentos a una plantilla:

template<typename T, int ROWS, int COLUMNS>
class Matrix
{
   . . .
private:
   T data[ROWS][COLUMNS];
};

Pero hemos de tener claro que a la hora de declarar un objeto de esta plantilla, los valores que irán en ROWS o COLUMNS deben de ser constantes.
Lo siguiente es correcto:

Matrix<double, 3, 4> a;

Lo siguiente no:

int i = 3;
Matrix<double, i, 4> a;

Además, al hacer:

 Matrix<int, 3, 4> a;
 Matrix<int, 5, 7> c;
 Matrix<int, 3, 4> d;

Podremos hacer a = d; ya que a y d son <int, 3, 4> en cambio a = c daría error, no son del mismo tipo.
Si queremos pasar un tipo plantilla a una función podemos hacerla de dos formas:

  1. Indicando qué tipo de plantilla es
  2. Pasar una plantilla genérica
// Ejemplo modo 1
void Incrementa (Plantilla<int> t) {...}
Aquí sólo podremos enviar variables creadas así: Plantilla<int> var; y no así, por ejemplo Plantilla<char> var;
// Ejemplo modo 2
template <class T> void Incrementa (Plantilla<T> t{ ... }
Ahora podemos pasar cualquier objeeto Plantilla.

Para declarar el método anterior como amigo haríamos:

friend void Incremena<>(Plantilla<T>);

Otro ejemplo:

template<int I>
class A {
public:
	void func () {
		printf("%d", I);
	}
};
 
void main () {
	A<3> a;
	a.func();
	system("pause");
}