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.