Introduccion a C++.
1. Clases.
Una clase contiene atributos y metodos.
Existen metodos especiales denominados constructores y destructores.
Un atributo puede ser de tipo escalar o no escalar.
La siguiente declaracion muestra el uso del concepto de clase.
/***
Clase1.h
Uso de una clase con atributos escalares, se declaran atributos y
metodos como publicos.
**/
#include
class Clase1 {
public:
int i;
long l;
char ch;
float f;
double d;
bool b;
void *p;
Clase1() {
cout << "Invocando constructor"<<'\n';
i= 10;
l= 123456789;
ch= 'a';
f=3.14159;
d=2.99E99;
b = 1==1;
p = this;
}
~Clase1() {
cout << "Invocando destructor"<<'\n';
}
void imprimir() {
cout << i <<'\n';
cout << l <<'\n';
cout << ch <<'\n';
cout << f <<'\n';
cout << d <<'\n';
cout << b <<'\n';
cout << p <<'\n';
}
};
//Clase1
Para poder utilizar esta clase en otro programa, se debe incluir el archivo Clase1.h y declarar
una variable de tipo Clase1.
Cuando se declara una variable de tipo Clase1, se invoca su constructor, que es la funcion que
se encarga de iniciarlizar los valores de los atributos.
Teniendo una variable de tipo clase, se puede usar todo atributo o metodo publico.
Cuando concluye el tiempo de vida de la variable, se invoca al constructor
2. Archivos de cabecera e implantacion
En C++ es usual declar una clase en un archivo ".h" pero sin codificar los metodos y en un
archivo ".cpp" o ".C" se implantan los metodos.
El siguiente ejemplo muestra una Clase2 que tiene un arreglo de objetos de tipo Clase1. En
el archivo Clase2.h se declara lo siguiente:
/** Clase2
Clase que contiene un arreglo de objetos de Clase1
**/
#define SZ 5
#include "Clase1.h"
class Clase2 {
public:
Clase1 arr[SZ]; //cuando se crea un objeto de tipo Clase2, se crean SZ objetos
void imprimir() ; //se declara la funcion
};
//Clase2
Y en otro archivo, Clase2.cpp, se define la funcion imprimir
/** Clase2
define el codigo de los metodos
**/
#include "Clase2.h"
void Clase2::imprimir() {
for (int i=0;i
Fecha::Fecha(): d(0),m(0),a(0) { }
Fecha::Fecha(int yyyy,int mm,int dd): d(dd),m(mm),a(yyyy) { }
void Fecha::imprimir() {
cout << "Y= "<
Contador::Contador():valor(0){ };
Contador::Contador(int v) {
valor = v;
if (!validar() ) {
cout << "Error al asignar valor \n";
}
}
int Contador::getValor() { return valor;}
void Contador::setValor(int v) {
valor = v;
if (!validar() ) {
cout << "Error al asignar valor \n";
}
}
void Contador::inc() {
valor++;
}
void Contador::dec() {
valor--;
if (!validar() ) {
cout << "Error al asignar valor \n";
}
}
//MainContador.cpp
#include "Contador.h"
#include
main() {
Contador c(2);
for(int i=5;i>0;i--) {
c.dec();
// c.validar(); //esto no compila
cout<
class Prestadora {
private:
int x;
public:
Prestadora():x(0) { }
friend void famiga(int dx,Prestadora &p);
friend class ImpresoraPrestadora;
};
class ImpresoraPrestadora{
public:
void imprimir(Prestadora pr ) {
cout<
void Clase3::apuntador(Clase3 * ptr) {
cout << ptr <<'\n';
ch = ptr->ch;
}
void Clase3::referencia(Clase3& ref) {
cout << &ref <<'\n';
ch = ref.ch;
}
void Clase3::valor(Clase3 nuevo) {
cout << &nuevo <<'\n';
ch = nuevo.ch;
}
void Clase3::imprimir() {
cout <dato = x*100;
return *this;
}
int Ego::getDato() {
return this->dato; //return dato;
}
void Ego::setDato(int x) {
this->dato = x;
}
//MainEgo.cpp
#include "Ego.h"
#include
void imprimir(Ego & tu) {
cout<
#include
NuevoTipo::NuevoTipo():numero(0) { strcpy(cadena,""); }
NuevoTipo::NuevoTipo(int x,const char *s): numero(x) { strcpy(cadena,s);}
NuevoTipo& NuevoTipo::operator+= (NuevoTipo &op) {
numero += op.numero;
strcat(cadena,op.cadena);
return *this;
}
NuevoTipo NuevoTipo::operator+(NuevoTipo& op1) {
int n = numero + op1.numero;
char cad[SZ] = "";
strcpy(cad,cadena);
strcat(cad,op1.cadena);
return NuevoTipo(n,cad);
}
void NuevoTipo::imprimir() {
cout << numero << " "<
main() {
NuevoTipo n1(10,"hola");
NuevoTipo n2(20,"mundo");
NuevoTipo n3;
n3 = n1+n2;
n1.imprimir();
n2.imprimir();
n3.imprimir();
n1 += n2;
n1.imprimir();
int i= n1;
cout << i+1 << '\n';
char * s= n1;
cout << s << '\n';
}
9. Herencia Simple.
La herencia simple se presenta cuando una clase toma la definicion de otra clase ya existente.
Se puede hacer herencia de atributos y metodos calificados como public y protected.
Un atributo o metodo calificado como protected permite que la clase descendiente la pueda accesar
pero la proteje de clases externas.
#ifndef __ANCESTRO_H__
#define __ANCESTRO_H__
/**
Clase Ancestro para ilustrar herencia
**/
class Ancestro {
private:
int x;
protected:
void imprimir();
public:
Ancestro():x(0) {};
Ancestro(int v):x(v) {};
int getX() const;
void setX(const int v);
operator int() const { return getX(); }
Ancestro & operator = (int v);
};
//Ancestro
#endif
//Ancestro.cpp
#include "Ancestro.h"
#include
int Ancestro::getX() const {
return x;
}
void Ancestro::setX(const int v) {
x = v;
}
Ancestro& Ancestro::operator=(int v) {
setX(v);
return *this;
}
void Ancestro::imprimir() {
cout<
Descendiente::Descendiente(char *s):Ancestro() {
cadena=s;
}
Descendiente::Descendiente(int d,char *s):Ancestro(d) {
cadena=s;
}
char * Descendiente::getCadena() const {
return cadena;
}
Descendiente& Descendiente::operator=(int v) {
setX(v);
return *this;
}
void Descendiente::p(){
imprimir();
}
//TestHerencia.cpp
#include "Ancestro.h"
#include "Descendiente.h"
#include
main() {
Ancestro papa;
Descendiente hijo("gollum");
papa = 2;
cout << (int) papa << '\n';
hijo = 10;
cout << (int) hijo << '\n';
cout << (char *) hijo << '\n';
}
10. Polimorfismo y ligadura dinamica.
Polimorfismo es la capacidad de un objeto de cambiar de forma con respecto al medio ambiente
(contexto) donde se desenvuelve.
El polimorfismo es implantado en los lenguajes via el concepto de ligadura dinamica.
Cuando un lenguaje tiene ligadura estatica, no es capaz de tomar la funcion a invocar basandose
en el tipo de dato de ejecucion.
C++ por omision implanta ligadura estatica.
#ifndef __A_H__
#define __A_H__
#include
class A{
public:
void p() {
cout << "soy A"<<'\n';
}
};
#endif
#ifndef __B_H__
#define __B_H__
#include "A.h"
#include
class B:public A{
public:
void p() {
cout << "soy B"<<'\n';
}
};
#endif
#include "A.h"
#include "B.h"
void f(A& ref){
ref.p();
}
main() {
A a;
B b;
a.p();
b.p();
f(b);
}
El polimorfismo debe presentarse en la funcion f(), donde espera recibir un objeto de tipo A
(en esta caso cualquier objeto de clase A o B tienen el tipo A). Sin embargo el resultado
al ejecutar
soy A
soy B
soy A
es decir, uso el objeto de tipo B como si fuera uno de tipo A; usando la alternativa de ligadura
estatica.
Para cambiar el comportamiento de que un metodo sea manejado con ligadura estatica, y sea
manipulado con ligadura dinamica, se debe usar la palabra reservada virtual. Al aplicar dicho
prefijo a un metodo se conoce como un metodo virtual.
#ifndef __C_H__
#define __C_H__
#include
class C{
public:
virtual void p() {
cout << "soy C"<<'\n';
}
};
#endif
#ifndef __D_H__
#define __D_H__
#include "C.h"
#include
class D:public C{
public:
virtual void p() {
cout << "soy D"<<'\n';
}
};
#endif
#include "C.h"
#include "D.h"
void g(C& ref){
ref.p();
}
main() {
C a;
D b;
a.p();
b.p();
g(b);
}
En este caso, la funcion g() es donde se hace presente el polimorfismo. Al pasar un objeto de
tipo D, dinamicamente y en tiempo de ejecucion descubre que debe invocar la funcion D::p()
gracias a la ligadura dinamica.
11. Clases Abstractas
En cualquier sistema orientado a objetos se debe diseņar pensando en el futuro.
Cuando existe un modulo cliente que utiliza un modulo proveedor, se debe tratar de aplicar un
acoplamiento debil, en donde basta con que se pongan de acuerdo con la interfaz de programacion
que ambos comparten.
El concepto de interfaz no existe en C++, pero una clase abstracta puede ayudar a solucionar esta
omision del lenguaje.
Una clase abstracta es aquella donde algunos metodos no estan definidos, o se conoce como una
clase que esta parcialmente definida. En consecuencia, una clase abstracta no tiene instancias.
Se define una clase abstracta por medio del concepto de funciones virtuales puras, que no tienen
codigo asociado.
Si una clase abstracta es tomada para herencia y se desea que objetos de la clase descendiente si
tengan instancias, se debe escribir el codigo asociado a la funcion virtual pura.
#ifndef __INSTRUMENTO__H
#define __INSTRUMENTO__H
//interfaz de programacion
class Instrumento {
public:
virtual char * tocar() = 0;
};
#endif
#ifndef __SALACONCIERTO__H
#define __SALACONCIERTO__H
#include
#include "Instrumento.h"
//clase cliente de un instrumento que utiliza polimorfismo y un
//acuerdo por medio de una interfaz, el Instrumento en este caso
class SalaConcierto {
public:
void ejecucion(Instrumento & instr) {
cout< class Comparador {
private:
T a;
T b;
public:
bool cmp(){
return a > b;
}
void setA(T x) {
a=x;
}
void setB(T x) {
b=x;
}
};
#endif
#include "Comparador.h"
#include
class Comparable {
private:
double x;
public :
Comparable():x(0) { }
Comparable(double v):x(v) { }
bool operator>(Comparable & otro) {
return x > otro.x;
}
};
main() {
Comparador cmpInt;
Comparador cmpFlt;
Comparador cmpRaro;
cmpInt.setA(2);
cmpInt.setB(1);
cout< class Portafolio: public ObjetoFinanciero {
private:
T arreglo[max];
int actual;
public:
Portafolio():actual(0){}
void agregar(T& objeto) {
if ( actual < max ) {
arreglo[actual] = objeto;
actual++;
}
}
virtual double valuar() {
double valor=0;
for (int i=0;i
main() {
double tasaLibreRiesgo (0.1);
double tasaMercado (0.3);
Accion a1(10,0.1,tasaLibreRiesgo,tasaMercado);
Accion a2(15,0.15,tasaLibreRiesgo,tasaMercado);
CuponCero b1(10,tasaLibreRiesgo);
CuponCero b2(1,tasaLibreRiesgo);
Portafolio port1;
Portafolio port2;
port1.agregar(b1);
port1.agregar(b2);
cout<
#include "IX.h"
#include "IY.h"
class CA : public IX, public IY {
private:
void p(char * msg) {
cout << msg <
class A{
public:
int x;
A(int v):x(v){ cout <<"invocando A(int)"<<"\n";}
A(const A& orig) { cout <<"invocando A(A&)"<<"\n";x= orig.x; }
A() { cout <<"invocando A()"<<"\n";
x=0;} //necesario para pasarlo como parametro o asignar a otro objeto
A& operator=(const A& orig){
cout <<"invocando A="<<"\n";
x=orig.x; return *this;}
};
class B{
public:
char ch;
A obj;
B() { cout <<"invocando B()"<<"\n";}
B(A o,char c):ch(c){
cout << "invocando B(A o,char c)"<<"\n";
obj=o;
}
B(const B& orig){ cout<<"invocando B(B&)"<<"\n";
ch=orig.ch;
obj=orig.obj;
}
B& operator=(const B& orig) {
cout <<"invocando B="<<"\n";
obj = orig.obj;
ch = orig.ch;
return *this;
}
};
main() {
A o1(5);
B o2(o1,'a');
B o3 = o2;
cout << o3.ch << " "<
class Figura { public: virtual void p() =0;};
class Circulo:public Figura {
public:
virtual void p() { cout<<"soy circulo"<<"\n"; }
};
class Cuadrado:public Figura {
public:
virtual void p() { cout<<"soy cuadrado"<<"\n";}
};
class Ajeno {
public:
void p() { cout <<"soy ajeno a la jerarquia"<<"\n";} };
void miniCAD(Figura * f) {
f->p();
}
main() {
Figura * pf;
Circulo c;
Circulo * pc = &c;
miniCAD(pc);
pf = static_cast(pc);
miniCAD(pf);
Cuadrado r;
Cuadrado * pr = &r;
pf = static_cast(pr);
miniCAD(pf);
Ajeno a;
Ajeno *pa= &a;
// pf = static_cast(pa);
// miniCAD(pf);
}
//UpCast.C
La otra, que es comun cuando se utilizan clases de tipo contenedor basadas en
herencia o delegacion; consiste en convertir una referencia a una clase generica
a una clase especifica. Este paso puede tener un peligro dado que en tiempo de
ejecucion se puede obligar que un objeto se convierta a una clase de la cual no descienda o no le pertenezca.
//DownCast.C
#include
class Figura { public: virtual void p() =0;};
class Circulo:public Figura {
public:
virtual void p() { cout<<"soy circulo"<<"\n"; }
};
class Cuadrado:public Figura {
public:
virtual void p() { cout<<"soy cuadrado"<<"\n";}
};
class Ajeno {
public:
void p() { cout <<"soy ajeno a la jerarquia"<<"\n";} };
Figura * upcast(Figura *p) {
return p;
}
void miniCAD(Figura * f) {
f->p();
}
main() {
Figura * pf;
Circulo c;
Cuadrado r;
Circulo * pc = &c;
Cuadrado * pr = &r;
pf = static_cast(pc);
pf=upcast(pf);
pc = static_cast(pf);
cout << pc << "\n";
miniCAD(pc);
pf = static_cast(pr);
pf=upcast(pf);
pr= static_cast(pf);
cout << pr << "\n";
miniCAD(pr);
pc = static_cast(upcast(pr));
cout << pc << "\n";
miniCAD(pc);
pc = dynamic_cast(upcast(pr));
cout << pc << "\n";
if (pc) {
miniCAD(pc);
}
}
//DownCast.C
El error surge cuando se trata de convertir un apuntador de Figura que apunta
a un Rectangulo a un Circulo. La funcion static_cast aplica una conversion pero
no da el comportamiento adecuado.
La funcion dynamic_cast detecta el error y asigna un apuntador nulo
17. Constantes y objetos compartidos
Un objeto puede ser declarado como constante cuando se desea almacenar datos de
solo lectura. Y los metodos que pueden ser invocados son de tipo const.
Un objeto constante es declardo como
const Clase objeto(...);
Y solo se pueden invocar metodos que tengan
tiporetorno metodo(...) const { ...}
Si se invocan metodos que no son const, el compilador retorna un error.
//Constante.C
#include
class Constante {
private:
double valor;
public:
Constante(double v):valor(v){}
double getValor() const { return valor;}
double setValor(double v){ valor=v;}
};
main() {
const Constante pi(3.14159);
Constante cambiante(1);
cout << pi.getValor()<<"\n";
cout << cambiante.getValor()<<"\n";
// pi.setValor(2.718);
cambiante.setValor(2);
cout << cambiante.getValor()<<"\n";
}
//Constante.C
Un atributo o metodo de una clase se puede calificar como estatico con el fin
de proporcionar un acceso sin tener que instanciar un objeto.
Esto permite compartir datos (escalares u objetos) entre clases o invocar
metodos para empezar un contacto con una clase sin tener que instanciarla.
Para declar un atributo static
static Tipo atributoestatico;
Para iniciar un atributo static se debe seguir la siguiente sintaxis:
Clase Clase::atributoestatico(valor);
//Singleton.C
#include
class Singleton {
private:
static Singleton e;
int i;
Singleton(int ii) : i(ii) {}
public:
static Singleton& nuevo() { return e; }
int val() const { return i; }
};
Singleton Singleton::e(47);
int main() {
// Singleton x(1);
cout << Singleton::nuevo().val() << endl;
}
//Singleton.C
Observar aqui el uso del constructor privado, para evitar que se defina una
instancia de la clase
18. Clases anidadas
Se puede definir una clase que tenga dentro de ella otra clase anidada.
Y se pueden instanciar objetos de tipo la clase anidada de la siguiente manera
ClaseNivel0::ClaseNivel1 objeto( ... );
//Anidadas.C
#include
class Externa {
private:
char ch;
public:
class Interna {
private:
static int constante;
double f;
public:
Interna():f(0){}
Interna(double v):f(v){}
int getConstante() const { return constante; }
};
Externa(char c):ch(c){}
char getCh() const{ return ch; }
Interna & getRaro(){ return raro;}
private:
Interna raro;
};
int Externa::Interna::constante(9);
main() {
Externa ext('a');
Externa::Interna in(3.14159);
cout << ext.getCh() << endl;
cout << in.getConstante() << endl;
cout << ext.getRaro().getConstante()<
#include
#include
#include
#include
#include
main() {
vector vec(4); //vector de 4 elementos
vec[0]="gus";
vec[1]="chiquis";
for (int i=0;i hash;
hash["vianney"]=10;
hash["gaby"]=15;
cout< lista(10);
}
20. Herramientas.
Al desarrollar un programa existen varias actividades:
+ Compilacion, que consiste en checar sintaxis y generar modulos objeto
+ Enlazado de cada modulo objeto para construir un programa ejecutable o
una libreria estatica o dinamica.
El comando make ayuda a construir una serie de instrucciones de compilacion
y generacion de librerias y/o ejecutables
El comando ar sirve para guardar varios modulos objeto en un solo archivo
El comando ld sirve para generar librerias estaticas o dinamicas o ejecutables
Una libreria estatica es aquella que al generar un programa ejecutable se
enlaza de una manera fija o estatica al programa ejecutable.
Una libreria dinamica se carga en tiempo de ejecucion y no es necesario que este
ligado al programa ejecutable. El sistema operativo se encarga de ayudar a la
carga de la libreria en tiempo de ejecucion.
Retomando el ejemplo de la clase Valuador, se puede generar un archivo
denominado valuador.mk, que es el que ejecuta con el comando make
CXX = g++
CXXFLAGS = -I.
HXXFILES = ObjetoFinanciero.H Bono.H Accion.H CuponCero.H Portafolio.H
CXXFILES = CuponCero.C \
Accion.C \
Valuador.C
ALLFILES = $(CXXFILES) $(HXXFILES)
OBJS = \
CuponCero.o \
Accion.o
EXE = Valuador
LIB = libvaluador.a
DLL = valuador.so
$(LIB) : $(OBJS)
ar ur $(LIB) $(OBJS)
LIBS= -L. -lvaluador
$(EXE): $(LIB) $(EXE).o
$(CXX) -o $@ $@.o $(LIBS)
LINKER = $(CXX)
LINK_FLAGS = -shared -nostdlib
dynamic: $(LIB)
$(CXX) -o $(DLL) $(LINK_FLAGS) $(OBJS)
$(CXX) -o $(EXE)_dyn $(EXE).o $(DLL)
all: $(EXE) dynamic
clean:
rm $(OBJS)
rm $(LIB)
rm $(DLL)
rm $(EXE)
rm $(EXE)_dyn
Una libreria dinamica permite que un programa carge en tiempo de ejecucion y
de funciones y/o clases.
Por ejemplo, se puede hacer una funcion de la siguiente manera
//dynhello.C
#include
extern "C" void hello() {
std::cout << "hello" << '\n';
}
//dynhello.C
Se tiene que declarar como extern "C" dado que es la unica manera que una
libreria dinamica puede ser utilizada para cargar facilmente funciones. No
se recomienda el uso de funciones C++, dado que tienen un estilo de nombres
de la funcion distintos.
Compilarlo como
g++ -o hello.so -shared -nostdlib dynhello.C
//maindynhello.C
#include
#include
int main() {
void* handle = dlopen("./hello.so", RTLD_LAZY);
if (!handle) {
cerr << "no se puede abrir libreria ... " << dlerror() << '\n';
return 1;
}
cout << "Cargando simbolo hello...\n";
typedef void (*hello_t)();
hello_t hello = (hello_t) dlsym(handle, "hello");
if (!hello) {
cerr << "No se puede cargar simbolo 'hello': " << dlerror() <<
'\n';
dlclose(handle);
return 1;
}
hello();
// close the library
dlclose(handle);
}
//maindynhello.C
Tambien se pueden utilizar clases y hacer uso del polimorfismo con librerias
dinamicas.
21. Ejercicio.
               (
geocities.com/gusdelact)