En la programación orientada a objetos, los datos y el código que actúan sobre los datos se convierten en una única entidad denominada clase. La clase es una evolución del concepto de estructura, ya que contiene la declaración de los datos. Pero se le añade la declaración de las funciones que manipulan dichos datos, denominadas funciones miembro o, también, métodos. Además, en la clase se establecen unos permisos de acceso a sus miembros. Por defecto, en una clase los datos y las funciones se declaran como privados. Este ocultamiento de la información niega a las entidades exteriores el acceso a los miembros privados de un objeto. De este modo, las entidades exteriores acceden a los datos de una manera controlada a través de algunas funciones miembro.
Un objeto es semejante a cualquier otro tipo de variable. Primero lo declaramos reservando espacio en memoria, luego lo inicializamos, guardando en memoria un dato o un conjunto de datos. Posteriormente usamos esos datos guardados en memoria. Pero un objeto de una determinada clase no solamente sirve para guardar datos, sino que además puede manipular dichos datos, a través de las llamadas a las funciones miembro.
Suponemos que el lector está familiarizado con los aspectos básicos de un lenguaje de programación estructurado:
Además, introduciremos dos aspectos del lenguaje C++: los comentarios en una sola línea, y la entrada/salida, mucho más simples en C++ que en C.
Recordaremos que el lenguaje C++ mantiene los delimitadores de comentarios empleados en el lenguaje C, pero añade comentarios en una sola línea.
//éste es un comentario de una sola línea
El archivo cabecera STDIO.H contiene la información relativa a las operaciones estándar de entrada/salida en el lenguaje C. Las funciones más utilizadas de esta librería son scanf y printf. Estas funciones pueden seguir siendo utilizadas en C++, pero su uso es más complicado, ya que hemos de pasar no sólo la variable, sino también el formato: %d para enteros, %f para números decimales, etc.
En C++, los detalles de la entrada/salida estándar están en el archivo IOSTREAM.H: la entrada se efectúa mediante el stream cin>> y la salida mediante el stream cout< <. En los ejemplos que siguen se compara la entrada/salida en C++ y en C.
Salida
float f=3.24;
cout << "Numero real =" << f << 'n';
printf ("Numero real %f=n", f);
Entrada
int i;
cin >> i;
scanf ("%d", &i);
Se declaran las variables que van a guardar los datos, y las funciones que van a manipular dichos datos. Se sitúan convenientemente los controladores de acceso a la clase public o private (por defecto), teniendo en cuenta la regla general de que una clase debe ocultar tanta información como sea posible.
class Punto{ int x; //miembros dato
int y; char ch;
public:
void mostrar(); //miembros función
void ocultar();
};
Se definen las funciones miembro de la clase. Para indicar que una función es miembro de una determinada clase se pone, entre el tipo de variable que retorna y el nombre de la función, el nombre de la clase y el símbolo (operador de resolución de ámbito).
void Punto::mostrar() { gotoxy(x,y); cout << ch;
}
void Punto::ocultar() { gotoxy(x,y); cout << " ";
}
En la pantalla de texto se pueden imprimir 80 caracteres horizontalmente y 25 caracteres verticalmente. La función gotoxy, sitúa el cursor en la posición indicada por las coordenadas x e y.
Una vez que se ha definido una clase, para usarla se ha de definir un objeto. Se define una variable de la clase Punto, exactamente igual que se define una variable de un tipo predefinido (int, float, etc.), o definido por el usuario. Las variables de una clase se denominan objetos. Los objetos usan la misma notación que cualquier tipo de variable, y su alcance se extiende desde la línea donde se ha declarado hasta el final del bloque.
Un objeto perteneciente a una clase se crea llamando a una función especial denominada constructor de la clase. El constructor se llama de forma automática cuando se crea un objeto, para situarlo en memoria e inicializar los miembros dato declarados en la clase. El constructor tiene el mismo nombre que la clase. Lo específico del constructor es que no tiene tipo de retorno, por lo que su sintaxis es más simple:
Punto:: Punto (argumentos)
Declaración del constructor:
class Punto{ //...
public: Punto (char chl, int x1, int yl); //...
};
Definición del constructor. El constructor inicializa los miembros dato:
Punto::Punto (char ch1,int x1, int y1){ ch=chl; x=xl; y=yl;
}
Llamada al constructor. Para crear un objeto pt1 de la clase Punto, basta una única sentencia
Punto ptl(‘*’, 40, 13);
En dicho objeto, el miembro dato ch guardará el carácter *, el miembro dato x, el número entero 40, y el miembro y, el entero 13.
Se llama a las funciones miembro desde el objeto pt1
pt1.mostrar();
ptl.ocultar();
Podemos tener más de un constructor, por ejemplo uno que fije el carácter pero que permita cambiar las coordenadas del punto.
Declaración del constructor
class Punto{ //...
public: Punto (int xl, int yl); //...
};
Definición del constructor: se fija el carácter, y se le pasan las coordenadas del punto.
Punto::Punto(int xl, int yl){ x 1=xl; y=y1; ch=’*’;
}
Se llama al constructor para crear un objeto pt2
Punto pt2(40, 13);
En dicho objeto, el miembro dato ch guardará el carácter *, el miembro dato x, el número entero 40, y el miembro y, el entero 13.
Se llama a las funciones miembro desde el objeto pt2
pt2.mostrar();
pt2.ocultar();
Una clase, como hemos visto, puede tener más de un constructor. Cuando un constructor no tiene argumentos, se dice que es el constructor por defecto.
Declaración del constructor por defecto de la clase
class Punto{ //...
public: Punto(); //...
};
Definición del constructor por defecto: los miembros dato se inicializan en el bloque de dicho constructor
Punto::Punto(){ ch=’*’; x=40; y=13;
}
Para llamar al constructor por defecto, basta escribir:
Punto def;
No escribir la sentencia:
Punto def();
//Error
//Se llama a las funciones miembro desde el objeto def
def.mostrar();
def.ocultar();
El destructor es una función especial, que tiene el mismo nombre que la clase pero que va precedido del símbolo ~ (ASCII 126). El destructor es único y no tiene argumentos: se puede definir dentro de la clase o fuera de la misma. Si no se ha definido explícitamente un destructor dentro de la clase, C++ proporciona uno. Cuando una variable sale del ámbito en la que se ha declarado, se libera la memoria que ocupa: un objeto llama para este propósito al destructor. Por ejemplo, en anteriormente creamos pt1 y def, son objetos definidos en el bloque de main. Cuando el programa alcanza el final del bloque, dichos objetos salen de ámbito llamando al destructor de la clase, e imprimiendo en la pantalla dos mensajes idénticos (objeto de la clase Punto destruido).
int (main){ Punto ptl(‘*’, 20, 10); Punto def; //...
return 0;
}
Declaración y definición del destructor de la clase. La definición puede hacerse también fuera de la clase.
class Punto{ public: //...
~Punto() { cout << "nobjeto Punto destruido"; }
};
class Punto{
public: int x; int y; char ch;
Punto (char ch1, int x1, int y1); void mostrar (); void ocultar ();
};
class Punto{ int x; int y; char ch;
public: Punto(char chl, int x1, int yl); void mostrar(); void ocultar();
};
La diferencia de las declaraciones de las clases está en el acceso a los miembros dato de la clase, private, por defecto. Los datos son, por tanto, privados y las funciones son públic
as. Cualquier intento de modificar directamente los valores de la abscisa x, la ordenada y, o del carácter ch, dará lugar a un error en tiempo de compilación. Luego, en main no podremos escribir las sentencias:
pt.x=19;
pt.y=5;
pt.ch=’+’;
El hecho de que los datos sean privados no quiere decir que sean invisibles: simplemente quiere decir que no se pueden cambiar desde el exterior. Si es imposible acceder a los datos privados, se pueden añadir a la clase funciones que permitan conocer sus valores. Si nos interesa conocer la abscisa x y la ordenada y del punto, podernos añadir a la clase dos funciones miembro denominadas getx y gety, que retornan los valores de x y de y.
class Punto{ int y; char ch;
public: Punto(char chl, int x1, int y1); void mostrar (); void ocultar (); int getx () { return x;} int gety () { return y;}
};
Este ejemplo ilustra una faceta importante del lenguaje C++ denominada encapsulación. El acceso a los miembros de una clase está controlado. Para usar una clase, solamente se necesita saber qué funciones miembro se pueden llamar y qué datos son accesibles. A esto se le denomina interfaz de la clase. No necesitamos saber cómo está hecha la clase, cómo son sus detalles internos. A esto se le denomina ocultamiento de la información. Una vez que la clase está depurada y probada, la clase es como una caja negra. Los objetos de dicha clase guardan unos datos, y están caracterizados por una determinada conducta.
Para acceder a un miembro público (dato o función), desde un objeto de la clase Punto basta escribir:
objeto.miembro_público;
Habitualmente, los miembros públicos son funciones, como mostrar, ocultar, getx, gety. Las llamadas a dichas funciones miembro desde un objeto pt1 de la clase Punto, se escribirán:
ptl.mostrar (); cout << "nabscisa del punto= " << ptl.getx();
cout << "nordenada del punto= "<< ptl.gety ();
Podemos declarar y definir funciones dentro de la clase, para no tener que definirla a parte en su respectivo archivo .cpp: a estas funciones se las denomina inline.
class Punto.{ //...
public: //...
int gety () {return y;}
};
Para que una función definida fuera de la clase sea inline es necesario especificarlo con esta palabra reservada. Las funciones inline hacen una sustitución del código, igual que las macros #define en C, pero con la ventaja de la depuración y la comprobación de los tipos de datos. Corno regla general, se definirá una función dentro de la clase, si consta de un pequeño número de sentencias.
Los operadores new y delete ofrecen la posibilidad de reservar y liberar de forma dinámica la porción de memoria que ocupa un objeto, de un modo similar a la de las funciones malloc y free. La ventaja principal estriba en que new y delete forman parte del lenguaje C++, no son funciones de una librería. Hay dos tipos de operadores new y delete, según traten o no con arrays. En el segundo caso, operator new( admite opcionalmente una inicialización. La sentencia:
int* int_ptr= new int(3);
reserva memoria para un entero, y lo inicializa con el número 3. El operador new devuelve la dirección del bloque de la memoria donde está guardado el valor de la variable. Para conocer el valor de la variable a la que apunta int_ptr, basta escribir:
cout << *int_Ptr;
Algo que podemos expresar mediante la siguiente regla general:
contenido = *direccion;
Para liberar la memoria anteriormente reservada se llama a operator delete().
delete int_ptr;
Los pasos para declarar y usar punteros a objetos son idénticos a los empleados con otras variables:
Punto* ptro_pt;
ptro_pt=new Punto('*', 20, 10); //primer constructor
ptro_pt=new Punto(20, 10); //segundo constructor
ptro_pt=new Punto(); //constructor por defecto
ptro_pt->mostrar ();
puntero_a_objeto->miembro_público;
delete ptro_pt; //llama al destructor
Un array sirve para guardar un conjunto de entidades pertenecientes a la misma clase. Para reservar espacio en memoria para un array, y para liberar la memoria, se ha de llamar a las funciones operator new[]()
y operator delete[]()
, respectivamente. En este caso, new no acepta inicializadores. Por ejemplo, en la siguiente porción de código se reserva espacio en memoria para un array de 3 enteros. Se inicializan, se usan, y por último, se libera dicho espacio en memoria reservado cuando ya no se precisa más el array.
//se reserva espacio en memoria para un array de 3 elementos
int* ip=new int[3];
//se inicializan los elementos del array
ip[0]=1;
ip[1]=2;
ip[3]=3;
//... se usa el array
//se libera la porción de memoria reservada
delete[] ip;
Lo mismo podremos hacer para un array de caracteres, que puede guardar hasta un máximo de 20.
char* cp= new char[20];
strcpy(cp, “esto es una cadena”);
//... se usa el array
delete[] cp;
Primero reservamos espacio para un array de tres objetos de la clase Punto. Luego inicializamos dichos objetos, llamando al constructor. Hacemos uso de los objetos, y por último, liberamos el espacio de memoria reservada.
//reserva espacio para tres elementos
Punto* objs= new Punto[3];
//inicializa los elementos del array
objs[0]=Punto();
objs[1]=Punto(‘+’,40,5);
objs[2]=Punto(60,10);
//desde cada elemento se llama a las funciones miembro
objs[1].mostrar();
//destructor del array
delete[] objs;
//llama tres veces al destructor
Primero reservamos espacio para un array de tres punteros a objetos de la clase Punto. Luego asignamos las direcciones de los objetos creados a los elementos del array, hacernos uso de los punteros a objetos, y por último, liberamos el espacio de memoria reservada, destruyendo cada elemento del array, y el array mismo.
//reserva espacio para tres elementos
Punto** pObjs=new Punto*[3];
//inicializa los elementos del array
pObjs[0l=new Punto(10, 20);
pObjs[l]=new Punto('+', 40, 5);
pObjs[2]=new Punto(‘@’, 60, 10);
//desde cada elemento se llama a las funciones miembro
//pObjs[1] mostrar();
//destrucción del array
for (int i=0; i<3; i++) delete pObjs[i];
delete[] pObjs;
Punteros y arrays están íntimamente relacionados por las reglas de la aritmética de punteros. El compilador interpreta el nombre de array corno un puntero a su primer elemento. Es decir, si objs es el nombre del array, objs
es equivalente a &objs[0]
. Por las reglas de la aritmética de punteros, si se añade un entero i
al nombre de un array, el resultado es un puntero al elemento i
del array: esto es, objs+i
es equivalente a &objs[i]
. Esta equivalencia la podemos expresar mediante la regla:
*(objs+i) //es equivalente a objs[i]
Podemos usar los operadores unarios ++
y --
para acceder a elementos consecutivos del array objs del siguiente modo:
//objs es objs[0]
objs++; //avanza a objs[1]
objs++; //avanza a objs[2]
objs--; //retrocede a objs[1]
Vamos a estudiar cómo se pasa una variable a una función en uno de sus argumentos.
int funcion (int parm); // declaración
funcion (arg); // llamada
int funcion (int parm) // definición
{ cout << parm << 'n'; //...
parm=88;
}
Así, cuando la variable arg se pasa por valor a una función, la función recibe una copia de arg
. Cualquier cambio en la copia parm
dentro del cuerpo de la función no afecta para nada al valor de arg
. La función no puede, por tanto, alterar directamente el parámetro pasado por valor. Así pues, durante el curso de la llamada a la función existen la variable arg
y su copia parm
. Cuando se sale de la función, la variable parm
está fuera de ámbito y se libera la memoria que ocupaba. En este caso, se asigna el valor 88 a parm
, no a arg
.
int funcion(int* parml); // declaración
funcion (&arg1); // llamada
int funcion (int * parm) // definición
{ cout << *parm << 'n'; //...
*parm=88;
}
Se pasa a la función no arg
, sino &arg
, la dirección de arg
. La dirección de arg
, &arg
, se pasa por valor, y la función recibe una copia de ésta en parm
. La función accede, por tanto, a la dirección de arg
a través de la copia que recibe, y de este modo puede o no modificar el valor de arg
. En este caso, se asigna el valor 88 a la dirección apuntada por parm
, 88 es por tanto asignado a arg
.
Las referencias son una evolución de los punteros, y son exclusivas del lenguaje C++, Facilitan mucho el trabajo de la programación sin los inconvenientes que presenta la notación de punteros. Pasar una variable a una función por referencia es mucho más claro que hacerlo por dirección. Se puede entender la referencia como un alias o apodo de una variable en el ámbito de una función.
Declaración de una referencia: ínt& ir=i;
Cualquier cambio que experimente ir
, lo experimenta i
, y viceversa. La potencialidad de las referencias está en el paso de una variable a una función, y en segundo lugar – aunque no lo veremos – en la posibilidad de, que las funciones devuelvan referencias.
int funcion (int& parm); // declaración
funcion (arg); // llamada
int funcion (int& parm) // definición
{ cout << parm << 'n'; parm=88;
}
asigna el valor 88
a parm
, pero parm
, es el alias de arg
, por tanto, modificando parm
se modifica arg
. Como podernos apreciar, el paso por referencia es semejante al paso por valor, excepto en la declaración de la función.
Una vez explicada toda esta teoría pasemos a realizar unos cuantos ejercicios.
A continuacion realizaremos un programa que contenga 2 clases alumno y maestro para eso necesitaremos una sub-clase que sera nuestra clase persona en pocas palabras sera nuestra clase padre. cada clase tendra determinados atributos por ejemplo la clase persona tendra: EDAD Y NOMBRE. y nuestras clases hijas que son maestro y alumno tendran los siguientes atributos: NUMERO DE CONTROL Y CALIFICACION para alumnos. y para nuestra clase profesor o maestros los atributos seran: HORAS E ID. procederemos a hacer los constructores de cada clase y nuestras respectivas funciones que utilizaremos. una vez realizado esto podremos trabajar con el int main donde pondremos el funcionamiento de nuestro programa. usaremos la sentencia if else para que se cumpla una condicion ya sea alumno o maestro y un do-while para repetir el programa cuantas veces queramos.
PRUEBA TU MISMO!!! http://cpp.sh/9ajso
Al final nos quedara esta salida de datos.
Dependiendo de la clase que hayas elegido sea alumno o maestro serán los resultados o atributos de cada clase los que se muestren incluyendo los de la clase padre que es persona, nuestra sentencia if-else nos ayudara a tomar la decisión de cual clase sera la que sea elegida por el usuario y nuestro do-while nos ayudara a repetir la operación cuantas veces desee el usuario.
A continuación, haremos otro programa donde declararemos nuestra clase al principio seguido de sus atributos publicos y privados. Nuestros atributos privados seran edad y nombre y nuestros atributos publicos sera nuestro constructor y nuestras funciones.
PRUEBA TU MISMO!! http://cpp.sh/5sxgv
Como salida obtendremos una combinación de nuestra clase y nuestras funciones.
Por ultimo realizaremos otro programa. Una calculadora con suma, multiplicación, división, y resta. Para eso crearemos nuestra clase operación donde se compartirán las variables a y b para cualquier operación que desee realizar el usuario y donde pondremos nuestras funciones void que llevaran la metodología para resolver las operaciones. una vez declarado el constructor y nuestras funciones void con todas las operaciones. Procedemos a crear nuestro int main donde usaremos un varios do-while para repetir las operaciones o bien para cambiar de operación. y usaremos nuestra sentencia switch para elegir la operacion que queramos.
PRUEBA TU MISMO!!!! http://cpp.sh/737sb (No olvides agregar la Liberia #conio.h y el getch(); si quieres ejecutar el programa en IDE de escritorio como DeVC++. La razón por la que no están el código que proporciono es porque el IDE online no es compatible con ellas.)
Como salida obtendremos dependiendo de la operación que hayamos elegido. una suma, resta, multiplicación o división de dos valores a y b que fueron proporcionados por nuestra clase creada al principio de nuestro programa.