Capitulo 3
Clases en C++.
Clases vs estructuras
En C++ los objetos son las variables concretas que se
crean de una determinada clase. A veces se llaman también
instancias o data objects.
Las clases de C++ son como una generalización de las
estructuras de C.
El esquema tradicional de un programa,
independientemente del lenguaje que se utilice, está compuesto
por una secuencia de sentencias, más o menos agrupadas en
rutinas o funciones, que van operando sobre una información
contenida en campos o variables. El problema de esta estructura
estriba en que ni las sentencias tienen un control de las
variables con las que trabajan, ni estas variables están
relacionadas en forma alguna con las sentencias que habrán de
tratarlas.
La filosofía de la POO (Object Oriented
Programming, Programación Orientada a Objetos) rompe con este
esquema, dando lugar a una nueva idea, el objeto.
El objeto es una abstracción en la que se
unen sentencias y datos, de tal forma que a un objeto sólo lo
van a poder tratar los métodos definidos para él, y estos
métodos están preparados para trabajar específicamente con él.
Este grado de compenetración evita que un método pueda tratar
datos no apropiados, o bien que unos datos puedan ser tratados
por un método no adecuado, ya que la llamada a cualquier
método ha de ir siempre precedida del objeto sobre el que se
quiere actuar, y éste sabe si ese método se ha definido o no
para él. C++ es un lenguaje que contiene estos y otros
conceptos de POO.
En terminología POO, cuando se quiere ejecutar
un método (función) sobre un objeto, se utiliza un mensaje que
se envía al objeto, de tal forma que el objeto llame al método y
éste se relacione con qué objeto lo ha llamado.
En C++ las clases son verdaderos tipos de
datos definidos por el usuario y pueden ser utilizados de igual
manera que los tipos de datos propios del C++, tales como int o
float. Los objetos son a las clases como las variables a los
tipos de variables. Un objeto tiene su propio conjunto de datos
o variables miembro, aunque no de funciones, que aunque se
aplican a un objeto concreto son propias de la clase a la que
pertenece el objeto.
A partir de ahora, asumirá que un programa orientado a objetos
sólo se compone de objetos y que un objeto es la concreción de
una clase. Es hora pues de entrar con detalle en la programación
orientada a objetos, la cual tiene un elemento básico: la clase.
Objetos
y mensajes
Un Objeto es una entidad que contiene
información y un conjunto de acciones que operan sobre los
datos. Para que un objeto realice una de sus acciones se le
envia un mensaje. Por tanto, la primera ventaja de la
programación orientada a objetos es la encapsulación de datos y
operaciones, es decir, la posibilidad de definir Tipos
Abstractos de Datos.
De cualquier forma la encapsulación es una ventaja mínima de la
programación orientada a objetos. Una característica mucho más
importante es la posibilidad de que los objetos puedan heredar
características de otros objetos. Este concepto se incorpora
gracias a la idea de clase.
DEFINICIÓN
DE UNA CLASE
Una clase es un tipo definido por el usuario
que describe los atributos y los métodos de los objetos que
se crearán a partir de la misma. Los atributos definen el estado
de un determinado objeto y los métodos son las operaciones que
definen su comportamiento. Forman parte de estos métodos los
constructores, que permiten iniciar un objeto, y los
destructores, que permiten destruirlo. Los atributos y los
métodos se denominan en general miembros de la clase.
La definición de una clase consta de dos partes: el nombre de
la clase precedido por la palabra reservada class y el cuerpo de
la clase encerrado entre llaves y seguido de un punto y coma.
Esto es:
class nombre_clase
{
cuerpo de la clase
};
Antes de poder definir un objeto debemos
definir la clase a la que pertenece. La forma general de
describir una clase seria mas o menos:
class nombre_clase
{
private:
datos y funciones privados;
public:
datos y funciones publicos;
funcion constructora;
funcion destructora;
};
El cuerpo de la clase en general consta de
modificadores de acceso (public, protected y private),
atributos, mensajes y métodos. Un método implícitamente define
un mensaje (el nombre del método es el mensaje).
class Fecha {
public:
void
Establecer (unsigned int dia, unsigned mes, unsigned int
anho);
void Obtener (unsigned
int& dia, unsigned int& mes, unsigned int& anho)
const;
void Imprimir ();
private:
unsigned int fDia, fMes,
fAnho;
};
void Fecha::Obtener
(unsigned int& dia, unsigned int& mes, unsigned
int& anho)
{
dia = fDia; mes = fMes;
anho = fAnho;
}
Especificadores de acceso: private (por defecto), public y
protected .
Constructores: constructor por defecto, constructor copia y
constructores adicionales.
class Fecha
{
// ...
public:
Fecha (unsigned int dia,
unsigned int mes, unsigned int anho);
Fecha (const char*
cadena);
Fecha (); // Constructor
por defecto.
Fecha (const Fecha&
fecha); // Constructor copia.
};
Este grado de compenetración evita que un
método pueda tratar datos no apropiados, o bien que unos datos
puedan ser tratados por un método no adecuado, ya que la llamada
a cualquier método ha de ir siempre precedida del objeto sobre
el que se quiere actuar, y éste sabe si ese método se ha
definido o no para él.
C++ es un lenguaje que contiene estos y otros conceptos de POO
En terminología POO, cuando se quiere ejecutar
un método (función) sobre un objeto, se utiliza un mensaje que
se envía al objeto, de tal forma que el objeto llame al método y
éste "conozca" qué objeto lo ha llamado.
Las clases y estructuras son esencialmente las
mismas, excepto que el modificador de acceso predeterminado de
las estructuras es público.
La estructura es una transferencia de C. En C ++, las clases
se utilizan generalmente.
Class |
Struct |
class Point { public: double
x,y; } |
Struct Point {
double x,y; } |
private by default |
public by default |
Resumen de esta sección:
POO:
Siglas de "Programación Orientada a Objetos".
En inglés se pone al revés "OOP". La idea básica de este tipo de
programación es agrupar los datos y los procedimientos para
manejarlos en una única entidad: el objeto. Un programa es un
objeto, que a su vez está formado de objetos. La idea de la
programación estructurada no ha desaparecido, de hecho se
refuerza y resulta más evidente, como comprobarás cuando veamos
conceptos como la herencia.
Objeto:
Un objeto es una unidad que engloba en sí
mismo datos y procedimientos necesarios para el tratamiento de
esos datos. Hasta ahora habíamos hecho programas en los que los
datos y las funciones estaban perfectamente separadas, cuando se
programa con objetos esto no es así, cada objeto contiene datos
y funciones. Y un programa se construye como un conjunto de
objetos, o incluso como un único objeto.
Mensaje:
El mensaje es el modo en que se comunican los
objetos entre si. En C++, un mensaje no es más que una llamada a
una función de un determinado objeto. Cuando llamemos a una
función de un objeto, muy a menudo diremos que estamos enviando
un mensaje a ese objeto.
En este sentido, mensaje es el término
adecuado cuando hablamos de programación orientada a objetos en
general.
Método:
Se trata de otro concepto de POO, los mensajes
que lleguen a un objeto se procesarán ejecutando un determinado
método. En C++ un método no es otra cosa que una función o
procedimiento perteneciente a un objeto.
Clase:
Una clase se puede considerar como un patrón
para construir objetos. En C++, un objeto es sólo un tipo de
variable de una clase determinada. Es importante distinguir entre
objetos y clases, la clase es simplemente una declaración, no
tiene asociado ningún objeto, de modo que no puede recibir
mensajes ni procesarlos, esto únicamente lo hacen los objetos.
Interfaz:
Las clases y por lo tanto también los objetos,
tienen partes públicas y partes privadas.
Algunas veces llamaremos a la parte pública de
un objeto su interfaz. Se trata de la única parte del objeto que
es visible para el resto de los objetos, de modo que es lo único
de lo que se dispone para comunicarse con ellos.
Herencia:
Veremos que es posible diseñar nuevas clases
basándose en clases ya existentes. En C++ esto se llama derivación
de clases, y en POO herencia. Cuando se deriva una clase de otra,
normalmente se añadirán nuevos métodos y datos. Es posible que
algunos de estos métodos o datos de la clase original no sean
válidos, en ese caso pueden ser enmascarados en la nueva clase o
simplemente eliminados. El conjunto de datos y métodos que
sobreviven, es lo que se conoce como herencia.
Definir una clase
La primera palabra que aparece es lógicamente
class que sirve para declarar una clase. Su uso es parecido a la
ya conocida struct:
class <identificador de clase> [<:lista de clases base>]
{
<lista de miembros>
} [<lista de objetos>];
La lista de clases base
se usa para derivar clases, de momento no le prestes demasiada
atención, ya que por ahora sólo declararemos clases base.
La lista de miembros
será en general una lista de funciones y datos.
Los datos se declaran del mismo modo en que lo
hacíamos hasta ahora, salvo que no pueden ser inicializados,
recuerda que estamos hablando de declaraciones
de clases y no de definiciones de objetos. Veremos
el modo de inicializar las variables de un objeto.
Las funciones pueden ser simplemente
declaraciones de prototipos, que se deben definir aparte de la
clase o también definiciones.
Cuando se definen las variables / asignan, fuera
de la clase se debe usar el operador de ámbito "::", esto se vio
al final de los ejercicios de estruct.
Mostrare una clase sencilla, cuyas variables
miembro se asignan desde las funciones miembro:
//programa pelicula.cpp
#include <iostream>
#include <string.h>
using namespace std;
class pelicula
{
public:
std::string nombre;
//variable miembro
std::string actor_principal;
//variable miembro
std::string actor_secundario; //variable
miembro
void
mostrar_pelicula(void); //prototipo de
funcion de clase
void
inicializa(std::string nombre,std::string principal,std::string
secundario); //prototipo de función de
clase
};
void pelicula::mostrar_pelicula(void)
//
^
^
// clase
funcion
{
std::cout <<"Nombre de la pelicula
"<<nombre<<std::endl;
std::cout <<"Estelarizada por : "<<actor_principal<<" y "<<actor_secundario<<std::endl<<std::endl;
// variables presentes como miembros de la
clase
}
void pelicula::inicializa(std::string
nombre_pelicula,std::string principal,std::string secundario)
//
^
^
// clase
funcion
{
nombre=nombre_pelicula;
actor_principal=principal;
actor_secundario=secundario;
//
^
^
//
miembros
variables
// de clase
locales
}
int main(void)
{
pelicula fugitivo,terminator;
//
^ ^
//
genera 2 objetos
// las siguientes lineas son miembros en
linea
fugitivo.inicializa("El
fugitivo","Harrison Ford","Tomy Lee Jones");
//
^
^
^
^
^
// objeto
funcion
parametros enviados a la funcion
terminator.inicializa("Terminator","Arnold
Schwarzenegger","Linda Hamilton");
//
^
^
^
^
^
// objeto
funcion
parametros enviados a la funcion
std::cout << "Las ultimas dos peliculas
que vi fueron :"<<fugitivo.nombre<<"
y "<<terminator.nombre<<std::endl;
//
^
^
//
acceso desde los objetos a los miembros de clase
std::cout << "Pienso que
"<<fugitivo.actor_principal<<"
actuo muy bien "<<std::endl;
return 0;
}
1.- Evidencia modificar el
programa anterior para que muestre otra película (incluyendo las
2 anteriores), tu decide que película y que actores
2.- Crea un programa que
incluya una clase de tipo public, tu encojes el tema puede ser
reducido, usa el nombre de mi_programa.cpp
Para este caso las variables miembro y la
funciones miembro son de tipo public, en este punto no es útil
hacerlo así ya que no están encapsulados los datos, pero nos sirve
para ver como funciona la clase, en el ejercicio que sigue
trataremos los atributos, por el momento revisa el programa
pelicula.cpp, y analiza lo que se desarrollo .
Para CodeBlocks

Para NetBeans

Para CLI
Lo veremos otro ejemplo, usando private.
//programa clase_1.cpp
#include <iostream>
#include <string.h>
using namespace std;
class Persona
{
private: // Datos miembro de la clase "Persona"
se conoce como atributos o encapsulamiento
// este nombre es porque son privados
int a, b, edad;
std::string nombre;
std::string apellido1;
std::string apellido2;
std::string direccion;
public: // Funciones miembro de la clase "Persona" o métodos
void guardaDatos(int _a2,
int _b2, std::string _nombre,std::string _apellido1,std::string _apellido2,std::string _direccion,int _edad)
{
a
= _a2;
b
= _b2;
nombre
= _nombre; // nombre, apellido1,
apellido2, direccion, edad, a, b ya estan definidos en private
apellido1 = _apellido1;
// y son los miembros de la clase
apellido2
= _apellido2;
direccion
= _direccion;
edad
= _edad;
std::cout << "
****** termine de guardar datos ....! "<< std::endl;
std::cout <<
std::endl;
}
void leeDatos(int &a2, int &b2,std::string &nombre,std::string
&apellido1,std::string
&apellido2,std::string
&direccion,int
&edad);
//declaración de la función Lee
}; // termina la declaracion de clase
/* definicion de la funcion */
void Persona::leeDatos(int &a2, int &b2,std::string
&nom,std::string &ap1,std::string &ap2,std::string &dirr,int &ed)
//observe que aqui no hay ;
{
std::cout << "
Leyendo datos " << std::endl;
a2
= a;
b2
= b;
nom
= nombre;
ap1
= apellido1;
ap2 = apellido2;
dirr
= direccion;
ed
= edad;
//observe que asignamos
al contario las variables de guardaDatos, se regresan a los
valores del objeto
std::cout << "
****** termine de leer datos ******** "<<std::endl;
std::cout <<
std::endl;
}
int main(int argc, char *argv[])
{
Persona par1; //llamada
a la clase Persona estableciendo el objeto a: par1
int x, y, ed;
// inicializacion de variables
std::string nom,ap1,ap2,dirr;
// estas variables son de este ambito de accion local
// se capturan los datos a guardar
std::cout <<" Nombre :",std::cin >>
nom;
std::cout <<" Primer Apellido :",std::cin
>> ap1;
std::cout <<" Segundo Apellido
:",std::cin >> ap2;
std::cout <<" Direccion :"
,std::cin.ignore(),getline(std::cin,dirr); //std::cin.ignore(), no
toma '/n', de lo contrario salta esta captura de datos
std::cout <<" Edad :",std::cin >> ed;
std::cout << std::endl;
//
par1.guardaDatos(12, 32,
nom,ap1,ap2,dirr,ed); // el objeto par1 hace uso de la
función guardaDatos de la clase
// estos
valores ^ ^ son para asignar a las variables x, y,
no tienen un significado solo para demostrar como se realiza el
paso de los valores
x=0,y=0,nom="",ap1="",ap2="",dirr="",ed=0; //
borra datos para que no intervengan en este ambito y asegurar que
se recuperan de la clase
par1.leeDatos(x,
y,nom,ap1,ap2,dirr,ed);
// el objeto par1 hace uso de la función Lee de la clase
std::cout <<
"Valor del objeto par1.a: " << x << std::endl;
std::cout <<
"Valor del objeto par1.b: " << y << std::endl;
std::cout <<
"Valor del objeto par1.c: " << nom << std::endl;
std::cout <<
"Valor del objeto par1.d: " << ap1 << std::endl;
std::cout <<
"Valor del objeto par1.e: " << ap2 << std::endl;
std::cout <<
"Valor del objeto par1.f: " << dirr << std::endl;
std::cout <<
"Valor del objeto par1.g: " << ed << std::endl;
return 0;
}
Para CodeBlocks

Para NetBeans

Para CLI
Evidencia agrega mas variables miembro, y
de forma que puedan estar relacionadas con lo que estamos usando
como: telefono, curp, etc. envia la evidencia con el
nombre de clases_private.cpp
Nuestra clase "Persona" tiene los miembros de tipo de datos:
a,b,edad, de tipo int, y nombre,apellido1,apellido2,direccion de
tipo string, hay que observar como se establecen como miembros de
la clase, std::string nombre, puede omitirse si se añade using
namespace std; pero lo usaremos para ubicar estas funciones
miembro
Y dos funciones, una para leer esos valores y otra para
modificarlos.
En el caso de la función "leeDatos" la hemos
declarado en el interior de la clase y definido fuera, observa que
en el exterior de la declaración de la clase tenemos que usar la
expresión:
void Persona::leeDatos(int &a2, int &b2,std::string
&nom,std::string &ap1,std::string &ap2,std::string &dirr,int &ed)
Para que quede claro que nos referimos a la
función "leeDatos" de la clase "Persona". Ten en cuenta que pueden
existir otras clases que tengan funciones con el mismo nombre, y
también que si no especificamos que estamos definiendo una función
de la clase "Persona", en realidad estaremos definiendo una
función tradicional.
En el caso de la función "guardaDatos" la hemos
definido en el interior de la propia clase.
Esto lo haremos sólo cuando la definición sea
muy simple, ya que dificulta la lectura y comprensión del
programa.
Además, las funciones definidas de este modo
serán tratadas como "inline", y esto sólo es recomendable para
funciones cortas, ya que, (como recordarás), en estas funciones se
inserta el código cada vez que son llamadas.
inline :- Las funciones miembro definidas dentro
del cuerpo de la declaración de la clase se denominan definiciones
de funciones en línea ( inline ). Para el caso de funciones más
grandes, es preferible codificar sólo el prototipo de la función
dentro del bloque de la clase y codificar la implementación de la
función en el exterior. Esta forma permite al creador de una clase
ocultar la implementación de la función al usuario de la clase
proporcionando sólo el código fuente del archivo de cabecera,
junto con un archivo de implementación de la clase precompilada.
Especificaciones de acceso:
Dentro de la lista de miembros, cada miembro
puede tener diferentes niveles de acceso. En nuestro ejemplo hemos
usado dos de esos niveles, el privado y el público, aunque hay
más.
class <identificador de clase>
{
public:
<lista de miembros>
private:
<lista de miembros>
protected:
<lista de miembros>
};
Acceso privado, private:
Los miembros privados de una clase sólo son accesibles por los
propios miembros de la clase y en general por objetos de la misma
clase, pero no desde funciones externas o desde funciones de
clases derivadas.
Acceso público, public:
Cualquier miembro público de una clase es accesible desde
cualquier parte donde sea accesible el propio objeto.
Acceso protegido, protected:
Con respecto a las funciones externas, es
equivalente al acceso privado, pero con respecto a las clases
derivadas se comporta como público.
Cada una de éstas palabras, seguidas de ":", da comienzo a una
sección, que terminará cuando se inicie la sección siguiente o
cuando termine la declaración de la clase. Es posible tener varias
secciones de cada tipo dentro de una clase.
Si no se especifica nada, por defecto, los miembros de una clase
son privados.
Aunque las secciones públicas, privadas y
protegidas pueden aparecer en cualquier orden, los programadores
suelen seguir ciertas reglas en el diseño que citamos a
continuación, y que usted puede elegir la que considere más
eficiente.
1. Poner los miembros privados primero, debido a
que contiene los atributos (datos).
2. Poner los miembros públicos primero debido a
que los métodos y los constructores son la interfaz del usuario de
la clase.
En realidad, tal vez el uso más importante de
los especificadores de acceso es implementar la ocultación de la
información. El principio de ocultación de la información indica
que toda la interacción con un objeto se debe restringir a
utilizar una interfaz bien definida que permite que los detalles
de implementación de los objetos sean ignorados. Por consiguiente,
los datos y métodos públicos forman la interfaz externa del
objeto, mientras que los elementos privados son los aspectos
internos del objeto que no necesitan ser accesibles para usar el
objeto.
El principio de encapsulamiento significa
que las estructuras de datos internas utilizadas en la
implementación de una clase no pueden ser accesibles
directamente al usuario de la clase.
Objetos
Una vez que una clase ha sido definida, un
programa puede contener una instancia de la clase, denominada un
objeto de la clase. Un objeto se crea de forma estática, de igual
forma que se define una variable. También de forma dinámica, con
el operador new aplicado a un constructor de la clase. El proceso
de creación de un objeto con frecuencia se conoce como instanciar
un objeto o creación de instancia del objeto.
Por ejemplo, un objeto de la clase Punto inicializado a las
coordenadas (2,1) :
Punto p1(2,
1);
// objeto creado de forma estática
Punto* p2 = new Punto(2, 1);
// objeto creado dinámicamente
Formato para crear un objeto
NombreClase varObj(argumentos_constructor);
Formato para crear un objeto dinámico
NombreClase* ptrObj;
ptrObj = new NombreClase(argumentos_constructor);
El operador de acceso a un miembro del objeto, selector punto (
. ), selecciona un miembro individual de un objeto de la clase.
Por ejemplo:
Punto p2;
// llama al constructor sin argumentos
p2.fijarX(10);
cout << " Coordenada x es " << p2.leerX();
El otro operador de acceso es el selector flecha
( -> ), selecciona un miembro de un objeto desde un puntero a
la clase. Por ejemplo:
Punto* p;
p = new Punto(2, -5); //
crea objeto dinámico
cout << " Coordenada y es " << p
-> leerY();
Constructores
Un constructor es un método que se ejecuta de
manera automática al instanciar un objeto de una clase. El
constructor tiene como finalidad la incialización de las variables
de la clase y posiblemente ejecutar algunos de los métodos de la
clase. Una clase puede tener tantos constructores (sobrecargas)
como el desarrollador lo estipule. La característica más
sobresaliente de los constructores es que su nombre es el
mismo que el de la clase, es decir, son métodos de la clase
que se nombran igual que la clase, y que además no tienen
valor de retorno. Las distintas sobrecargas del
constructor de una clase van a depender de las distintas
combinaciones de parámetros de entrada de la función. Cuando no se
declara un constructor de manera explícita para una clase entonces
C++ se encarga de asignar un constructor implícito por defecto a
la clase, no tendría ningún sentido declarar un constructor como
privado, ya que siempre se usan desde el exterior de la clase, ni
tampoco como protegido, ya que no puede ser heredado.
Añadamos un constructor a nuestra clase Persona:
//programa clase_2.cpp con constructor
#include <iostream>
#include <string.h>
using namespace std;
class Persona
{
private: // Datos
miembro de la clase "Persona" se conoce como atributos o
encapsualamiento
// este nombre es porque son privados, std:: lo usamos
porque la clase es un ámbito diferente
int a, b, edad;
std::string nombre;
std::string apellido1;
std::string apellido2;
std::string direccion;
public: // Funciones miembro de la
clase "Persona" o metodos
// Constructor
Persona (int _a2, int _b2, std::string
_nombre,std::string _apellido1,std::string
_apellido2,std::string _direccion,int _edad);
void guardaDatos(int _a2,
int _b2, std::string _nombre,std::string
_apellido1,std::string
_apellido2,std::string _direccion,int _edad)
{
a
= _a2;
b
= _b2;
nombre
= _nombre; // nombre,
apellido1, apellido2, direccion, edad, a, b ya estan definidos en
private,
apellido1 = _apellido1;
// y son los miembros de la clase
apellido2
= _apellido2;
direccion
= _direccion;
edad
= _edad;
std::cout << "
****** termine de guardar datos ....! "<< std::endl;
std::cout <<
std::endl;
}
// definición de la función
leeDatos
void leeDatos(int &a2,
int &b2,std::string &nombre,std::string &apellido1,std::string &apellido2,std::string &direccion,int &edad);
};// termina la declaración de clase
// aquí va la declaración del constructor,
observe la asignacion de varibles miembro
Persona::Persona(int _a2, int _b2,std::string
_nombre,std::string _apellido1,std::string
_apellido2,std::string _direccion,int _edad)
{
a = _a2;
b = _b2;
nombre = _nombre,apellido1 = _apellido1,apellido2 =
_apellido2,direccion=_direccion,edad=_edad; //otra forma de asignar valores en
la misma linea
}
/* definición de la funcion */
void Persona::leeDatos(int &a2, int &b2,std::string
&nom,std::string &ap1,std::string &ap2,std::string &dirr,int &ed)
{
std::cout << "
Leyendo datos " << std::endl;
a2
= a;
b2
= b;
nom
= nombre;
ap1
= apellido1;
ap2
= apellido2;
dirr
= direccion;
ed
= edad;
//observe que asignamos el comentario las variables de
guardaDatos, se regresan los valores del objeto
std::cout << "
****** termine de leer datos ******** "<<std::endl;
std::cout <<
std::endl;
}
int main(int argc, char *argv[])
{
Persona
par1(0,0,"","","","",0); //llamada a la clase Persona con
el constructor
std::cout << "Se inicializo el
constructor"<<std::endl; //aqui es un
mensaje para saber cuando inicia el constructor
int x, y, ed;
// inicializacion de variables
std::string nom,ap1,ap2,dirr;
// estas variables son de este ambito de accion
// se capturan los datos a guardar desde el
teclado
std::cout <<"
Nombre
:",std::cin
>> nom;
std::cout <<" Primer
Apellido :",std::cin >> ap1;
std::cout <<" Segundo Apellido
:",std::cin >> ap2;
std::cout <<"
Direccion
:" ,std::cin.ignore(),getline(std::cin,dirr); //std::cin.ignore(),
no toma '/n', de lo contrario salta esta captura
std::cout <<"
Edad
:",std::cin
>> ed;
std::cout << std::endl;
//
los valores 12 y 32 son constantes en este caso solo es para
demostrar como se almacenan
// pedir un string (que el
usuario la introduzca por medio del teclado) getline(std::cin,dirr) Esta función necesita tres datos o parámetros:
// Nombre. El nombre de la variable que va a
contener el string
//Longitud. La cantidad de caracteres que queremos que se
puedan introducir (nunca mayor que la longitud del string).
//Caracter de fin. El caracter que el usuario va usar
como final de la cadena. Por lo general es el ‘enter‘ que se
representa como ‘\n’ (diagonal n).
par1.guardaDatos(12, 32,
nom,ap1,ap2,dirr,ed); // el objeto par1 hace uso de
la función guardaDatos de la clase
x=0,y=0,nom="",ap1="",ap2="",dirr="",ed=0; //se borran
las variables para mostrar que estas se obtienen del objeto
par1.leeDatos(x,y,nom,ap1,ap2,dirr,ed);
// el objeto par1 hace uso de la función Lee de la clase
std::cout <<
"Valor del objeto par1.a: " << x <<
std::endl;
std::cout <<
"Valor del objeto par1.b: " << y
<< std::endl;
std::cout <<
"Valor del objeto par1.c: " << nom <<
std::endl;
std::cout <<
"Valor del objeto par1.d: " << ap1
<< std::endl;
std::cout <<
"Valor del objeto par1.e: " << ap2
<< std::endl;
std::cout <<
"Valor del objeto par1.f: " << dirr <<
std::endl;
std::cout <<
"Valor del objeto par1.g: " << ed <<
std::endl;
return 0;
}
revise los resultados de este programa y compararlos con el
código del programa
Si una clase posee constructor, será llamado siempre que se
declare un objeto de esa clase, y si requiere argumentos, es
obligatorio suministrarlos.
Por ejemplo, las siguientes declaraciones son ilegales:
Persona par1;
Persona par1();
Y las siguientes declaraciones son válidas:
Persona par1(12,43,"Miguel","Santos","Montoya","Manuel Dobaldo
7000",58);
Persona par1 = Persona(12,43,"Miguel","Santos","Montoya","Manuel
Dobaldo 7000",58);
Persona par1(0,0,"","","","",0);
Cuando no especifiquemos un constructor para una
clase, el compilador crea uno por defecto sin argumentos.
Por eso el ejemplo anterior ha este, sin declarcion de constructor
funcionaba correctamente. Cuando se crean objetos locales, los
datos miembros no se inicializarían, contendrían la "basura" que
hubiese en la memoria asignada al objeto.
No tienen tipo de retorno, y por lo tanto no retornan ningún
valor.
Si se trata de objetos globales, los datos
miembros se inicializan a cero.
Para declarar objetos usando el constructor por defecto o un
constructor que hayamos declarado sin parámetros no se
debe usar el paréntesis:
Persona par2();
Se trata de un error frecuente cuando se empiezan a usar clases,
lo correcto es declarar el objeto sin usar los paréntesis:
Persona par2;
Destructores
El complemento a los constructores de una clase es el
destructor. Así como el constructor se llama al declarar o crear
un objeto, el destructor es llamado cuando el objeto va a dejar de
existir por haber llegado al final de su vida. En el caso de que
un objeto (local o auto) haya sido definido dentro de un bloque
{…}, el destructor es llamado cuando el programa llega al final de
ese bloque.
Si el objeto es global o static su duración es la misma que la
del programa, y por tanto el destructor es llamado al terminar la
ejecución del programa. Los objetos creados con reserva dinámica
de memoria (en general, los creados con el operador new) no están
sometidos a las reglas de duración habituales, y existen hasta que
el programa termina o hasta que son explícitamente destruidos con
el operador delete. En este caso la responsabilidad es del
programador, y no del compilador o del sistema operativo.
A diferencia del constructor, el destructor es siempre único (no
puede estar sobrecargado) y no tiene argumentos en ningún caso.
Tampoco tiene valor de retorno. Su nombre es el mismo que el de la
clase precedido por el carácter tilde (~), carácter que se
consigue con Alt+126 en el teclado del PC. En el caso de que el
programador no defina un destructor, el compilador de C++
proporciona un destructor de oficio, que es casi siempre
plenamente adecuado (excepto para liberar memoria de vectores y
matrices).
En el caso de que la clase Persona necesitase un destructor, la
declaración sería así:
//Destructor
~Persona();
y la definición de la clase, añadiendo en este
caso como variable miembro una cadena de caracteres que contenga
el nombre del titular, podría ser como sigue, podemos colocarlo
antes de main():
Persona::~Persona()
{
std::cout <<"\n";
std::cout<<"Ejecutando
destructor...objeto " <<endl;
}
Como se realiza un destructor:
//programa clase_2_a.cpp con destructor
#include <iostream>
#include <string.h>
using namespace std;
class Persona
{
private: // Datos miembro de la clase "Persona"
se conoce como atributos o encapsualamiento
// este nombre es porque son privados
int a, b, edad;
std::string nombre;
std::string apellido1;
std::string apellido2;
std::string direccion;
public: // Funciones miembro de la clase "Persona" o
metodos
// Constructor
Persona (int _a2, int _b2, std::string
_nombre,std::string _apellido1,std::string _apellido2,std::string
_direccion,int _edad);
void guardaDatos(int _a2, int _b2, std::string
_nombre,std::string _apellido1,std::string _apellido2,std::string
_direccion,int _edad)
{
a = _a2;
b = _b2;
nombre = _nombre; //
nombre, apellido1, apellido2, direccion, edad, a, b ya estan
definidos en private,
apellido1 =
_apellido1; // y son los miembros de la clase
apellido2 = _apellido2;
direccion = _direccion;
edad = _edad;
std::cout << "
****** termine de guardar datos ....! "<< std::endl;
std::cout <<
std::endl;
}
// definición de la
función guardaDatos
void leeDatos(int &a2, int
&b2,std::string &nombre,std::string
&apellido1,std::string &apellido2,std::string
&direccion,int &edad);
//Destructor
~Persona();
};// termina la declaración de clase
// aquí va la declaración del constructor
Persona::Persona(int _a2, int _b2,std::string _nombre,std::string
_apellido1,std::string _apellido2,std::string _direccion,int
_edad)
{
a = _a2;
b = _b2;
nombre = _nombre,apellido1 = _apellido1,apellido2 =
_apellido2,direccion=_direccion,edad=_edad; //otra forma de
asignar valores en la misma linea
}
/* definición de la funcion */
void Persona::leeDatos(int &a2, int
&b2,std::string &nom,std::string &ap1,std::string
&ap2,std::string &dirr,int &ed)
{
std::cout << "
Leyendo datos " << std::endl;
a2 = a;
b2 = b;
nom = nombre;
ap1 = apellido1;
ap2 = apellido2;
dirr = direccion;
ed = edad;
//observe que asignamos
al contario las variables de guardaDatos, se regresan los valores
del objeto
std::cout << "
****** termine de leer datos ******** "<<std::endl;
std::cout <<
std::endl;
}
Persona::~Persona()
{
std::cout <<"\n";
std::cout<<"Ejecutando destructor...objeto " <<endl;
}
int main(int argc, char *argv[])
{
Persona par1(0,0,"","","","",0);
//llamada a la clase Persona con el constructor
std::cout << "Se inicializo el
constructor"<<std::endl; //aqui es un mensaje para saber
cuando inicia el constructor
int x, y,
ed; //
inicializacion de variables
std::string nom,ap1,ap2,dirr; // estas
variables son de este ambito de accion
//std::string
nombre,apellido1,apellido2,direccion; //estan son las que se
asignaron pero estan comentadas
// se capturan los datos a guardar
std::cout <<"
Nombre
:",std::cin >> nom;
std::cout <<" Primer
Apellido :",std::cin >>
ap1;
std::cout <<" Segundo
Apellido :",std::cin >> ap2;
std::cout <<"
Direccion
:" ,std::cin.ignore(),getline(std::cin,dirr); //std::cin.ignore(),
no toma '/n', de lo contrario salta esta captura
std::cout <<"
Edad
:",std::cin >> ed;
std::cout << std::endl;
// los valores 12 y 32 son constantes en este
caso solo es para demostrar como se almacenan
par1.guardaDatos(12, 32,
nom,ap1,ap2,dirr,ed); // el objeto par1 hace uso de la
función guardaDatos de la clase
x=0,y=0,nom="",ap1="",ap2="",dirr="",ed=0; //se borran
las variables para mostrar que estas se obtienen del objeto
par1.leeDatos(x,y,nom,ap1,ap2,dirr,ed);
// el objeto par1 hace uso de la función Lee de la clase
std::cout <<
"Valor del objeto par1.a: " << x << std::endl;
std::cout <<
"Valor del objeto par1.b: " << y << std::endl;
std::cout <<
"Valor del objeto par1.c: " << nom << std::endl;
std::cout <<
"Valor del objeto par1.d: " << ap1 << std::endl;
std::cout <<
"Valor del objeto par1.e: " << ap2 << std::endl;
std::cout <<
"Valor del objeto par1.f: " << dirr << std::endl;
std::cout <<
"Valor del objeto par1.g: " << ed << std::endl;
return 0;
}
Inicialización
de objetos:
Hay un modo simplificado de inicializar los datos miembros de
los objetos en los constructores.
Se basa en la idea de que en C++ todo son objetos, incluso las
variables de tipos básicos como int, char o float.
Según eso, cualquier variable (u objeto) tiene un constructor
por defecto, incluso aquellos que son de un tipo básico.
Sólo los constructores admiten inicializadores. Cada
inicializador consiste en el nombre de la variable miembro a
inicializar, seguida de la expresión que se usará para
inicalizarla entre paréntesis.
Los inicializadores se añadirán a continuación del paréntesis
cerrado que encierra a los parámetros del constructor, antes del
cuerpo del constructor y separado del paréntesis por dos puntos
":".
Por ejemplo, en el caso anterior de la clase "Persona":
// aquí va la declaración del
constructor
Persona::Persona(int _a2, int _b2,std::string _nombre,std::string
_apellido1,std::string _apellido2,std::string _direccion,int
_edad)
// aqui comienzan los inicializadores (variables, las que se
asignan)
{
a = _a2;
b = _b2;
nombre = _nombre,apellido1 = _apellido1,apellido2 =
_apellido2,direccion=_direccion,edad=_edad; //otra forma de
asignar valores en la misma linea
}
Podemos sustituir el constructor por:
Persona(int _a2, int _b2,std::string
_nombre,std::string _apellido1,std::string _apellido2,std::string
_direccion,int _edad) : a(_a2), b(_b2), nombre(_nombre),
apellido1(_apellido1), apellido2(_apellido2),
direccion(_direccion), edad(_edad) {}
Sobrecarga
de constructores:
Además, también pueden definirse varios
constructores para cada clase, es decir, la función constructor
puede sobrecargarse. La única limitación es que no pueden
declararse varios constructores con el mismo número y el mismo
tipo de argumentos.
Por ejemplo, añadiremos un constructor adicional a la clase
"pareja" que simule el constructor por defecto:
class pareja
{
private:
// Datos miembro de la clase "pareja"
int a, b;
public:
// Constructor
pareja(int _a2, int _b2) :
a(_a2), b(_b2) {}
pareja() : a(0), b(0) {}
// Funciones miembro de la
clase "pareja"
void Guarda(int _a2, int _b2);
void Lee(int &a2, int &b2);
};
De este modo podemos declarar objetos de la
clase "pareja" especificando los dos argumentos o ninguno de
ellos, en este último caso se inicializarán los datos miembros con
ceros.
Consideremos el siguiente ejemplo para usar sobrecarga, de
funciones, la restricción para la sobrecarga de métodos es que los
mismos deben diferir en cantidad o tipo de parámetros. Es decir
podemos definir dos métodos con el mismo nombre pero uno tenga por
ejemplo 3 parámetros y otro tenga 2 parámetros:
//Sobrecarga de funciones
miembro sobrecarga.cpp
#include<iostream>
using namespace std;
class Matematica {
public:
int mayor(int x1,int x2);
int mayor(int x1,int x2,int x3);
float mayor(float x1,float x2);
float mayor(float x1,float x2,float x3);
};
int Matematica::mayor(int x1,int x2)
{
if (x1>x2)
return x1;
else
return x2;
}
int Matematica::mayor(int x1,int x2,int x3)
{
if (x1>x2 && x1>x3)
return x1;
else
if (x2>x3)
return x2;
else
return x3;
}
float Matematica::mayor(float x1,float x2)
{
if (x1>x2)
return x1;
else
return x2;
}
float Matematica::mayor(float x1,float x2,float x3)
{
if (x1>x2 && x1>x3)
return x1;
else
if (x2>x3)
return x2;
else
return x3;
}
int main(int argc, char *argv[])
{
Matematica m1;
cout<<"Mayor entre 6 y 8 : ";
cout<<m1.mayor(6,8);
cout <<"\n";
cout<<"Mayor entre 10, 40 y 5 : ";
cout<<m1.mayor(10,40,5);
cout <<"\n";
cout<<"Mayor entre 6.2 y
9.3 : ";
cout<<m1.mayor(6.2f,9.3f);
cout <<"\n";
cout<<"Mayor entre 7 , 12.5
y 4.2 : ";
cout<<m1.mayor(7.0f,12.5f,4.2f);
cout <<"\n";
return 0;
}
Para CodeBlocks
Para NetBeans
Para CLI
Resumen de esta sección:
A continuación se definen
las sintaxis en C++ de los conceptos esenciales de clases en la
POO usando el lenguaje de programación C++
Declaración de una clase:
La sintaxis, para la declaración de
clases, se expresa a continuación:
class <identificador de clase> [<:lista de clases
base>] {
<lista de miembros>
} [<lista de identificadores de objetos>];
La lista de miembros será en general una
lista de operaciones (métodos) y atributos (datos).
Los atributos se declaran del mismo modo
en que lo hacíamos hasta ahora, salvo que no pueden ser
inicializados.
Las operaciones pueden ser
simplemente declaraciones de prototipos, que se deben definir
aparte de la clase pueden ser también definiciones.
Cuando se definen fuera de la clase se
debe usar el operador de ámbito "::".
El siguiente ejemplo muestra la
declaración de la clase entero:
class Entero{
private:
//atributos
int valor;
public:
//métodos
Entero sumar(Entero);
Entero restar(Entero);
Entero invAd();
void setValor(int);
int getValor();
};
void Entero::setValor(int v)
{
valor=v;
}
int Entero::getValor(){ return valor;}
Entero Entero::sumar(Entero
a)
{
Entero suma;
suma.setValor(valor+a.getValor());
return suma;
}
Entero Entero::restar(Entero a)
{
Entero resta;
return sumar(a.invAd());
}
Entero Entero::invAd()
{
Entero inv;
inv.setValor(-1*valor);
return inv;
}
La clase Entero tiene un miembro de tipo de
datos o atributo llamado valor y cinco operaciones, una para
leerlo (setValor), uno para mostrarlo (getValor) y el resto para
efectuar operaciones con él (sumar, restar e invAd). Nótese que su
sintaxis es similar a la del tipo de datos definidos por struct.
Modificadores de acceso
Los modificadores son elementos del lenguaje que
se colocan delante de la definición de variables locales, datos
miembro, métodos o clases y que alteran o condicionan el
significado del elemento en la cual cada miembro puede tener
diferentes niveles de acceso. C++ tiene 3 modificadores de acceso
para los miembros de las clases a saber:
public: Cualquier miembro público de una clase es accesible
desde cualquier parte donde sea accesible el propio objeto.
private: Los miembros privados de una clase sólo
son accesibles por los propios miembros de la clase y en general
por objetos de la misma clase, pero no desde funciones externas o
desde funciones de clases derivadas.
protected: Con respecto a las funciones
externas, es equivalente al acceso privado, pero con respecto a
las clases derivadas se comporta como público.
Cada una de éstas palabras, seguidas de ":", da
comienzo a una sección, que terminará cuando se inicie la sección
siguiente o cuando termine la declaración de la clase. Es posible
tener varias secciones de cada tipo dentro de una clase.
Si no se especifica nada, por defecto, los miembros de una clase
son privados.
Constructores y destructores
En C++, las clases contienen dos categorías de métodos
especiales los cuales son esenciales para el buen funcionamiento
de la clase. Estos son los constructores y los destructores.
Los constructores
Los constructores son métodos especiales que sirven para
inicializar un objeto de una determinada clase al mismo tiempo que
se declara.
Los constructores son especiales por varios motivos:
1. Tienen el mismo nombre que la clase a la que pertenecen.
2. No tienen tipo de retorno, y por lo tanto no retornan ningún
valor.
3. No pueden ser heredados.
4. Por último, deben ser públicos, no tendría ningún sentido
declarar un constructor como privado, ya que siempre se usan desde
el exterior de la clase, ni tampoco como protegido, ya que no
puede ser heredado.
Su sintaxis es:
class <identificador de clase>
{
public:
<identificador de
clase>(<lista de parámetros>);
...
};
<identificador de
clase>::<identificador de clase>(<lista de
parámetros>) [: <lista deconstructores>]
{
<código del
constructor>
}
La clase Entero, al añadir un constructor, quedaría así:
class Entero
{
private:
//atributos
int valor;
public:
//constructor
Entero(int);
//metodos
Entero
sumar(Entero);
Entero
restar(Entero);
Entero
invAd();
void setValor(int);
int getValor();
};
Entero::Entero(int v)
{
valor=v;
}
Al desarrollar el constructor este permite realizar instancias
de objetos de tipo Entero siguiendo la siguiente sintaxis:
int main ()
{
Entero a(6); //crea un Objeto de tipo entero en
el cual su valor inicial es 6
}
Constructor
por defecto
Es un constructor sin parámetros inicialmente
creado por el compilador de forma automática cuando a la clase no
se le define un constructor de manera explícita.
Cuando se crean Objetos de forma local los
atributos de estos tendrías valores al azar trayendo “basura” que
hubiese en la memoria asignada al objeto. En cambio, si se tratan
de objetos globales, dichos atributos se inicializarían en cero.
Para declarar objetos usando el constructor por
defecto o un constructor que hayamos declarado sin parámetros no
se debe usar el paréntesis.
Ejemplo:
Entero gl; //Crea un objeto
de ámbito global de tipo entero el cual su valor inicial es cero
int main ()
{
Entero a; //crea un Objeto de ámbito local
de tipo entero el cual su valor inicial es desconocido
}
Inicialización de objetos
En C++ las variables de tipos básicos como int,
char o float son objetos. En C++ cualquier variable (u objeto)
tiene, al menos un constructor, el constructor por defecto,
incluso aquellos que son de un tipo básico.
Sólo los constructores de las clases admiten
inicializadores. Cada inicializador consiste en el nombre del
atributo a inicializar, seguida de la expresión que se usará para
inicializarla entre paréntesis. Los inicializadores se añadirán a
continuación del paréntesis cerrado que encierra a los parámetros
del constructor, antes del cuerpo del constructor y separado del
paréntesis por dos puntos ":".
Por ejemplo la implementación del constructor:
Entero::Entero(int v)
{
valor=v;
}
Quedaría así:
Entero::Entero(int v):valor(v) { }
Sobrecarga
de constructores
Los constructores son funciones, y por ende en C++, también
pueden definirse varios funciones con el mismo nombre (en nuestro
caso constructores) para cada clase, es decir, el constructor
puede sobrecargarse. La única limitación (como en todos los casos
de sobrecarga) es que no pueden declararse varios constructores
con el mismo número y el mismo tipo de argumentos.
Por ejemplo, es posible agregar un constructor adicional a la
clase Entero que simule el constructor por defecto:
class Entero
{
private:
//atributos
int valor;
public:
//constructores
Entero::Entero(int);
Entero::Entero();
//métodos
Entero sumar(Entero);
Entero restar(Entero);
Entero invAd();
void setValor(int);
int getValor();
};
Entero::Entero(int v){
valor=v;
}
Entero::Entero():valor(0){}
//inicializa en cero el valor del entero en caso de que no se use
el otro constructor
Constructores con argumentos por defecto
También pueden asignarse valores por defecto a los argumentos
del constructor, de este modo se reduce significativamente el
número de constructores necesarios. La asignación se realiza en la
definición del constructor
El ejemplo muestra la clase Entero haciendo uso de un
constructor el cual tiene su único argumento por defecto y este
valor es igual a cero.
class Entero
{
private:
//atributos
int valor;
public:
//constructor
Entero::Entero(int=0);
//métodos
Entero
sumar(Entero);
Entero
restar(Entero);
Entero
invAd();
void
setValor(int);
nt
getValor();
};
Entero::Entero(int v){
valor=v;
}
Si la función main es la siguiente:
Entero gl;
int main (){
Entero a;
cout<<gl.getValor()<<" , "<<
a.getValor()<<endl;
}
La salida fuera esta:
0 , 0
Ya que al no realizar las instancias de los objetos con valores,
el compilador los inicializa de acuerdo a la definición del los
argumentos por defecto presente en la definición del constructor,
el cual es cero.
Funciones
miembro constantes
Un método de una clase se puede declarar de
forma que nos impida modificar el contenido del objeto (es decir,
como si para la función el parámetro this fuera constante). Para
hacer esto basta escribir la palabra después de la declaración de
la función:
class empleado {
...
float cuanto_cobra (void)
const;
...
};
float
empleado::cuanto_cobra (void) const
{
return sueldo;
}
Las funciones miembro constantes se pueden
utilizar con objetos constantes, mientras que las que no lo son no
pueden ser utilizadas (ya que podrían modificar el objeto).
Usando el atributo protected:
//programa clase_3.cpp usando atributo protected
#include <iostream>
using namespace std;
// Definicion de la clase CFecha
class CFecha
{
// Datos miembro de la clase CFecha
private:
int dia,mes,anio;
//Funciones miembro de la clase
protected:
int Bisiesto();
public:
void AsignarFecha();
void ObtenerFecha(int *,int *,int *) const;
// Métodos
constantes “prometen” de no modificar ningún dato dentro de la
clase.
int FechaCorrecta();
};
//establecer fecha
void CFecha::AsignarFecha()
{
std::cout<<"dia
## :",std::cin>>dia;
std::cout<<"mes
## :",std::cin>>mes;
std::cout<<"año ####
:",std::cin>>anio;
}
//Verificar fecha correcta, recibe de la
funcion miembro AsignarFecha
int CFecha::FechaCorrecta()
{
int DiaCorrecto,
MesCorrecto, AnioCorrecto;
// año correcto ?
AnioCorrecto=(anio
>= 1582); //evalua como booleano
// mes correcto ?
MesCorrecto=(mes>=1)&&(mes<=12); //evalua como
booleano
switch(mes)
// dia correcto?
{
case 2:
if(Bisiesto())
//accede a la funcion miembro Bisiesto, 1 si es, 0 no es, evalua
segun boooleano
DiaCorrecto=(dia>=1 && dia<=29);
else
DiaCorrecto=(dia>=1 && dia<=28);
break;
case 4: case 6: case 9: case 11:
DiaCorrecto=(dia>=1 && dia <=30);
break;
default:
DiaCorrecto=(dia>=1 && dia <=31);
}
if (!(DiaCorrecto && MesCorrecto)
&& AnioCorrecto)
{
std::cout <<"Datos
incorrectos\n\n";
return 0; //fecha
incorrecta
}
else
return 1; //fecha correcta
}
//Verificar si el año es bisiesto
int CFecha::Bisiesto()
{
if (((anio%4==0)&&(anio%100 !=0)) ||
(anio%400 ==0))
return 1; //año bisiesto
else
return 0;
} //regresa 1 si es bisiesto, 0 si no es bisiesto
//Obtener una fecha
void CFecha::ObtenerFecha(int *pd,int *pm,int *pa) const
{
*pd=dia,*pm=mes,*pa=anio;
}
// Funciones prototipo
void VisualizarFecha(const CFecha
&fecha);
// Visualizar una fecha
void VisualizarFecha(const CFecha &fecha)
{
int dd,mm,aa;
fecha.ObtenerFecha(&dd,&mm,&aa);
std::cout << dd
<<"/"<<mm<<"/"<<aa<<"\n";
}
// inicia main()
int main(int argc, char*argv[])
{
CFecha fecha; //fecha
es objeto de DFecha
do
fecha.AsignarFecha(); //el objeto fecha
lleva las variables a AsignarFecha
while (!fecha.FechaCorrecta()); //verifica si
la fecha es correcta, si hay error regresa un 1
//while evalua si continua el ciclo o no, se evalua como booleano
VisualizarFecha(fecha);
}
La palabra clave const aumenta bastante las emociones que uno
podrá tener sobre C++: cuando es indudablemente un elemento para
elevar el estilo de programación, también es uno que puede
complicar bastante la compilación.
Es cierto que un programa funciona sin constantes. Por eso hay
tendencia de no usarlas. Lo gordo viene cuando uno empieza a
usarlas en un código que en general no lo usa. Esto puede causar
errores de compilación hasta en la n-ésima llamada anidada a una
función, que por alguna razón requiere que un valor no sea
constante – aunque sólo lo lea y no lo escribe.
¿Cómo podemos poner orden en el caos? Pues, básicamente en
usar wrappers. En nuestro código usamos constantes de forma
correcta. Si tenemos que llamar a una función que no podemos o
queremos modificar, entonces convertimos los datos de la
constante a una variable. El caso más simple sería una simple
asignación.
const int constante = 5;
int parametro = constante;
funcion_sin_const_correctness(parametro);
Si no podemos copiar el valor, entonces podemos usar dos
técnicas. Una es la de C mediante punteros.
const int constante = 5;
int& parametro_referencia = *(int*)(void*)(&constante);
La otra es con la expresión de C++ const_cast<TIPO>:const
int constante = 5;
int& parametro_referencia =
const_cast<int>(constante);
El
atributo protected:
Los miembros protegidos son accesibles en la clase que los
define y en las clases que heredan de esa clase.
Los miembros protegidos de una clase "A" no son
accesibles fuera del código de "A", pero es accesible desde el
código de cualquier clase derivada de "A".
Entonces podemos decir que: Si no desea que se filtre el
estado interno, entonces declarar todas sus variables miembro
como private es el camino a seguir.
Si realmente no le importa que las subclases puedan acceder al
estado interno, entonces protected es lo suficientemente bueno.
Adelante veremos como nos ayuda protected para generar herencia.
La funcionalidad de esta clase esta soportada por los datos
miembros privados mes, dia anio, y por funciones miembro AsignarFecha,
ObtenerFecha, FechaCorrecta Bisiesto.
Del programa anterior sus resultados es:
Para CodeBlocks

Para NetBeans

Para CLI
Ámbito de clase
Cualquier función miembro de una clase puede acceder a otro
miembro de su misma clase.
Los miembros privados de una clase, datos o
funciones son locales a la misma y solo pueden ser accedidas por
la funciones miembro de su clase o por una función friend de la
misma clase.
Los miembros public locales son locales a la
clase, pueden ser accedidos por funciones miembro de su clase y
por cualquier otra función que defina un objeto o un puntero, a un
objeto de esa clase,
Es decir un miembro publico de una clase es local a su clase y
solo es accesible:
-
A través de un objeto de su clase o de una clase derivada de
esta utilizando el operador " . "
-
A través de un puntero a un objeto de su clase o una una
clase derivada de esta utilizando el operador "->"
-
A través del nombre de sus clase o de una clase
derivada de esta utilizando el operador "::"
El
puntero implícito this
Cada objeto de una clase mantiene su propia
copia de los datos miembro de su clase, pero no de las funciones
miembro, de los cuales solo existe una sola copia para todos los
objetos de esa clase. Cada objeto tiene su propia estructura de
datos, y comparten todos su codigo, para que una función miembro
conozca la identidad del objeto particular para el cual dicha
funcion ha sido invocada, C++ proporciona un apuntador llamado this,
si declaramos un objeto fecha1 y enviamos un mensaje
AsignarFecha.
fecha1.AsignarFecha();
C++ define un puntero this para permitir
referirse al objeto fecha1 en el cuerpo de la función que se
ejecuta como respuesta al mensaje.
CFecha *const this = &fecha1;
AsignarFecha también puede ser definida de esta forma: //this hace
referencia al objeto que haya sido
// invocada la funcion
void CFecha::AsignarFecha()
{
std::cout<<"dia
## :",std::cin>> this->dia;
std::cout<<"mes
## :",std::cin>>this->mes;
std::cout<<"año ####
:",std::cin>>this->anio;
}
Incluiremos un constructor en el programa anterior
//programa clase_4.cpp usando atributo protected con
constructor
#include <iostream>
using namespace std;
// Definicion de la clase CFecha
class CFecha
{
// Datos miembro de la clase CFecha
private:
int dia,mes,anio;
//Funciones miembro de la clase
protected:
int Bisiesto();
public:
CFecha(int dd,int
mm,int aa); //definicion del constructor
void
AsignarFecha();
void
ObtenerFecha(int *,int *,int *) const;
// Métodos
constantes “prometen” de no modificar ningún dato dentro de la
clase.
int
FechaCorrecta();
};
// Constructor
CFecha::CFecha(int
dd,int mm,int aa)
{
dia=dd,mes=mm,anio=aa;
}
//establecer fecha
void CFecha::AsignarFecha()
{
std::cout<<"dia
## :",std::cin>>dia;
std::cout<<"mes
## :",std::cin>>mes;
std::cout<<"año ####
:",std::cin>>anio;
}
//Verificar fecha correcta, recibe de la funcion miembro
AsignarFecha
int CFecha::FechaCorrecta()
{
int DiaCorrecto,
MesCorrecto, AnioCorrecto;
// año correcto ?
AnioCorrecto=(anio
>= 1582); //evalua como booleano
// mes correcto ?
MesCorrecto=(mes>=1)&&(mes<=12); //evalua como
booleano
switch(mes)
// dia correcto?
{
case 2:
if(Bisiesto()) //accede
a la funcion miembro Bisiesto, 1 si es, 0 no es, evalua segun
boooleano
DiaCorrecto=(dia>=1 && dia<=29);
else
DiaCorrecto=(dia>=1 && dia<=28);
break;
case 4: case 6: case 9: case 11:
DiaCorrecto=(dia>=1 && dia <=30);
break;
default:
DiaCorrecto=(dia>=1 && dia <=31);
}
if (!(DiaCorrecto && MesCorrecto) &&
AnioCorrecto)
{
std::cout <<"Datos
incorrectos\n\n";
return 0; //fecha incorrecta
}
else
return 1; //fecha correcta
}
//Verificar si el año es bisiesto
int CFecha::Bisiesto()
{
if (((anio%4==0)&&(anio%100 !=0)) ||
(anio%400 ==0))
return 1; //año bisiesto
else
return 0;
} //regresa 1 si es bisiesto, 0 si no es bisiesto
//Obtener una fecha
void CFecha::ObtenerFecha(int *pd,int *pm,int *pa) const
{
*pd=dia,*pm=mes,*pa=anio;
}
// Funciones prototipo
void VisualizarFecha(const CFecha &fecha);
// Visualizar una fecha
void VisualizarFecha(const CFecha &fecha)
{
int dd,mm,aa;
fecha.ObtenerFecha(&dd,&mm,&aa);
std::cout << dd
<<"/"<<mm<<"/"<<aa<<"\n";
}
// inicia main()
int main(int argc, char*argv[])
{
CFecha hoy(11,06,2021);
//crear e inicializar el objeto hoy, usando el constructor
definido
// en el
programa anterior no se define constructor, se realiza de
forma automatica
// aqui se define usando (11,06,2021), pruebe quitando el
paréntesis, para ver el resultado
VisualizarFecha(hoy);
}
Para CodeBlocks

Para NetBeans

Para CLI
para que se vea mas claro este asunto observe el siguiente
programa, y su resultado:
//programa clase_4a.cpp usando un constructor
definido
#include <iostream>
#include <string>
using namespace std;
class Fecha
{
private:
int dia,mes,anio;
public:
Fecha(int dia,int mes,int anio);
//definicion del constructor
//la funcion
miembro siguiente se puede acceder fuera de la clase
// usando
apuntadores de memoria y de forma const y void de retorno
void mostrar_fecha(int *,int *,int *)
const;
};
//constructor
Fecha::Fecha(int _d,int _m,int _a)
{
dia=_d,mes=_m,anio=_a;
std::cout << "Inicia Constructor de fecha con
:"<<dia<<"/"<<mes<<"/"<<anio<<std::endl;
//al llegar al constructor muestra el mensaje que llego hasta
aqui
}
void
Fecha::mostrar_fecha(int *pd,int *pm,int *pa) const
{
//muestra valores asignados en el constructor
std::cout << "funcion mostrar fecha "<< dia
<<"/"<< mes<<"/"<<anio<<" ";
}
// Funciones prototipo que estan afuera de la clase, que no
son funciones miembro
// tipo void const que es del tipo de la funcion miembro
mostrar_fecha, const clase Fecha y una direccion de memoria
&f
void VisualizarFecha(const Fecha &f);
// Visualizar una fecha
void VisualizarFecha(const Fecha &f)
{
int dd,mm,aa;
f.mostrar_fecha(&dd,&mm,&aa);//realiza
el llamado a al función miembro mostrar_fecha
}
int main(int argc,char *argv[])
{
//creacion de objetos en la misma clase Fecha
Fecha dia_festivo(11,06,21); //crea el objeto y por la
definicion de clase y lo lleva al constructor
Fecha Navidad(12,25,21);
Fecha Hallowen(10,31,21);
Fecha Septiembre_independencia(9,15,21);
Fecha Anio_nuevo(12,31,21);
Fecha cumpleanios(06,11,21);
//llamada a la funcion para visualizar los valores de lo
objetos
VisualizarFecha(dia_festivo),std::cout <<"Dia festivo
"<<std::endl;
VisualizarFecha(Navidad),std::cout <<"Dia Navidad
"<<std::endl;
VisualizarFecha(Hallowen),std::cout <<"Dia Hallowen
"<<std::endl;
VisualizarFecha(Septiembre_independencia),std::cout
<<"Dia independecia "<<std::endl;
VisualizarFecha(Anio_nuevo),std::cout <<"Dia año nuevo
"<<std::endl;
VisualizarFecha(cumpleanios),std::cout <<"Dia
cumpleaños"<<std::endl;
}
Para CodeBlocks
Para NetBeans
Para CLI
Cuando una clase tiene un constructor, este sera invocado
automaticamente siempre que se cree un nuevo objeto de esta
clase. Si el constructor tiene argumentos estan deberan de
especificarse en una lista de valores entre parentesis:
CFecha hoy(11,06,2021);
Hay otra manera de iniciar el constructor
CFecha( int dd=1,int mm=1,int
aa=2020); // otra forma de realizar el constructor de esta forma
puede recibir varios argumentos
Ahora el Cosnstructor definido con argumentos por omision admite
llamadas con 0, 1, 2, o 3 argumentos
CFecha fecha;
CFecha fecha (10);
CFecha fecha (10,2);
CFecha fecha (10,2,2020);
Cuando una clase tiene un constructor , este será invocado
automáticamente siempre que se cree un nuevo objeto de esta
clase. En el caso de que el constructor tenga argumentos, esto
deberán ser especificados en una lista de valores entre
paréntesis a continuación de la declaración del objeto:
Cfecha hoy(11,06,2021);
Define un objeto hoy e inicializa sus
datos miembro dia, mes y anio, a los valores 11 06
2021. Para ello se invoca el constructor
CFecha::CFecha(int dd,int
mm,int aa)
{
dia=dd,mes=mm,anio=aa;
}
Esto es llamada explicita al constructor, ha
sustituido al llamado implícito del constructor, al ejecutarse CFecha Otrodia; marcara un error ya que
hace llamada al constructor sin argumentos, para solucionar esto
debemos hacer llamada al constructor por omisión, de la siguiente
forma:
public:
CFecha Otrodia(){}; //constructor por omision
En este ejemplo se ha hecho el constructor sin argumentos, esto
hace que la linea
CFecha Otrodia;
invoque a los constructores por omisión para cada uno de los
miembros del objeto Otrodia
podemos definir diferentes constructores con el
mismo nombre y diferentes argumentos, con el fin de poder
inicializar objetos con diferentes formas.
Considerando lo anterior el constructor podemos decir que el
constructor puede quedar asi:
class CFecha
{
// Datos miembro de la clase CFecha
private:
int dia,mes,anio;
//Funciones miembro de la clase
protected:
int Bisiesto();
public:
CFecha(int dd=11,int mm=06,int aa=2021);
//declaracion del constructor
void
AsignarFecha();
void
ObtenerFecha(int *,int *,int *) const;
// Métodos
constantes “prometen” de no modificar ningún dato dentro de la
clase.
int
FechaCorrecta();
};
// Constructor
CFecha::CFecha(int
dd,int mm,int aa)
{
dia=dd,mes=mm,anio=aa;
}
************************************************************************************************************************************************************
Retomemos el apuntador this, para que nos quede claro el
funcionamiento de this.
En otras palabras podemos decir que, podemos
crear varios objetos de una clase. Cada objeto tiene una copia en
memoria de todos los atributos (variables) que son independientes
de los otros objetos. En el lenguaje C++ para poder
identificar a que objeto en particular estamos accediendo
cuando llamamos a un método se le pasa
un parámetro llamado this (significa este objeto).
Veamos con un ejemplo donde aparece este puntero:
//programa apuntador this
#include<iostream>
using namespace std;
class Temperatura {
private:
int minima;
int maxima;
int actual;
void imprimir();
public:
Temperatura(int min, int
max, int actual); //declara el constructor
};
Temperatura::Temperatura(int min, int max, int act)
//inicializar constructor
{
this->minima = min;
this->maxima = max;
this->actual = act;
this->imprimir();
}
void Temperatura::imprimir()
{
cout <<"imprimiendo datos de
temperatura ";
cout << this->minima << " "
<< this->actual << " " << this->maxima
<< "\n";
}
int main(int argc, char * argv[])
{
Temperatura
temperatura1(10, 20, 15);
Temperatura temperatura2(25,
35, 29);
return 0;
}
Podemos ver que cada vez que accedemos a un
atributo o llamamos en un método dentro de la clase le antecedemos
el puntero this.
Este puntero almacena la dirección de memoria
donde se guardan los atributos del objeto respectivo, por ejemplo
cuando en la main creamos un objeto de la clase Temperatura:
Temperatura temperatura1(10, 20,
15);
Al constructor ademas de pasar los tres enteros
10, 20 y 15 se le pasa un cuarto parámetro que es la dirección de
memoria donde se almacenan los atributos del objeto
"temperatura1".
Luego dentro del constructor cuando escribimos:
this->minima = min;
this->maxima = max;
this->actual = act;
Cuando se ejecuta este código this tiene la dirección del objeto
temperatura1 o temperatura2 según el caso.
También cuando llamamos a un método dentro de la clase estamos
antecediendo el puntero this para saber de cual objeto se trata:
this->imprimir();
Pero ¿porqué no lo hemos estado haciendo hasta
ahora? Es que no es obligatorio anteceder a los nombres de
atributos y métodos, pero en realidad la dirección de los
atributos de cada objeto en C++ se resuelve mediante este puntero.
Ya veremos casos en conceptos sucesivos donde es muy importante
utilizar este puntero this explícitamente.
Un caso muy sencillo donde debemos utilizar el puntero this
obligatorio es cuando un método tiene parámetros con el mismo
nombre que los atributos de la clase.
Para CodeBlocks
Para NetBeans
Para CLI
*****************************************************************************************************************************************************************
Asignacion
de Objetos
Otra forma de asignar objetos es usando el operador =.
CFecha hoy (11,06,2021);
CFecha Otrodia;
....
Otrodia=hoy;
Esto define los objetos hoy y Otrodia
la asignación se hace de miembro a miembro, cuando se hace
asignación no se construye objeto, si no que ambos existen:
//programa clase_5.cpp usando asignación de objetos
#include <iostream>
using namespace std;
// Definicion de la clase CFecha
class CFecha
{
// Datos miembro de la clase CFecha
private:
int dia,mes,anio;
//Funciones miembro de la clase
protected:
int Bisiesto();
public:
CFecha(int dd=1,int mm=1,int aa=2020);
//definicion del constructor
CFecha(const CFecha &Obfecha);//constructor
copia
const CFecha
&operator =(const CFecha &);//sobrecarga de constructor de
asignación
int operator
==(const CFecha &) const ;//igualdad
void
AsignarFecha();
void
ObtenerFecha(int *,int *,int *) const;
// Métodos
constantes “prometen” de no modificar ningún dato dentro de la
clase.
int
FechaCorrecta();
};
//
Constructor
CFecha::CFecha(int
dd,int mm,int aa)
{
dia=dd,mes=mm,anio=aa;
}
//Operador de asignacion, permite asignar un
objeto a otro objeto, Obfecha solo es una posicion apara recibir
parametro
const CFecha &CFecha::operator=(const
CFecha &Obfecha)
{
dia=Obfecha.dia;
mes=Obfecha.mes;
anio=Obfecha.anio;
return *this;
}
//establecer fecha
void CFecha::AsignarFecha()
{
std::cout<<"dia
## :",std::cin>>dia;
std::cout<<"mes
## :",std::cin>>mes;
std::cout<<"año ####
:",std::cin>>anio;
}
//Verificar fecha correcta, recibe de la funcion miembro
AsignarFecha
int CFecha::FechaCorrecta() //const
{
int DiaCorrecto,
MesCorrecto, AnioCorrecto;
// año correcto ?
AnioCorrecto=(anio >=
1582); //evalua como booleano
// mes correcto ?
MesCorrecto=(mes>=1)&&(mes<=12); //evalua como
booleano
switch(mes)
// dia correcto?
{
case 2:
if(Bisiesto()) //accede a
la funcion miembro Bisiesto, 1 si es, 0 no es, evalua segun
boooleano
DiaCorrecto=(dia>=1 && dia<=29);
else
DiaCorrecto=(dia>=1 && dia<=28);
break;
case 4: case 6: case 9: case 11:
DiaCorrecto=(dia>=1 && dia <=30);
break;
default:
DiaCorrecto=(dia>=1 && dia <=31);
}
if (!(DiaCorrecto && MesCorrecto) &&
AnioCorrecto)
{
std::cout <<"Datos
incorrectos\n\n";
return 0; //fecha incorrecta
}
else
return 1; //fecha correcta
}
//Verificar si el año es bisiesto
int CFecha::Bisiesto()
{
if (((anio%4==0)&&(anio%100 !=0)) || (anio%400
==0))
return 1; //año bisiesto
else
return 0;
} //regresa 1 si es bisiesto, 0 si no es bisiesto
//Obtener una fecha
void CFecha::ObtenerFecha(int *pd,int *pm,int *pa) const
{
*pd=dia,*pm=mes,*pa=anio;
}
// Visualizar una fecha
void VisualizarFecha(const CFecha &fecha)
{
int dd,mm,aa;
fecha.ObtenerFecha(&dd,&mm,&aa);
std::cout << dd
<<"/"<<mm<<"/"<<aa<<"\n";
}
// inicia main()
int main(int argc, char*argv[])
{
CFecha hoy(11,06,2021);
//crear e inicializar el objeto hoy, usando el constructor definido
// en el programa anterior no se define constructor, se
realiza de forma automatica
// aqui se declara usando (11,06,2021), pruebe quitando el
paréntesis, para ver el resultado
CFecha Otrodia; //crea un objeto Otrodia
Otrodia = hoy; //llama a
la funcion operator
VisualizarFecha(Otrodia);//muestra el valor del objeto
Otrodia, donde se la asigno miembro a miembro
}
La funcion retorna una referencia al objeto resultante lo que
genera que se puedan hacer asignaciones multiples (a=b=c)
Constructor copia, una nueva copia de un objeto a partir de otra
existente, tiene un solo argumento, el cual hace refereferencia a
un objeto constante de la misma clase, y no hay valor de
retorno:
CFecha(const CFecha
&Obfecha);//constructor copia
//Operador de asignacion, permite asignar un
objeto a otro objeto, Obfecha solo es una posicion apara recibir
parametro
const CFecha &CFecha::operator=(const
CFecha &Obfecha)
{
dia=Obfecha.dia;
mes=Obfecha.mes;
anio=Obfecha.anio;
return *this;
}
Tambien posible reescribir la asignación como:
CFecha Otrodia(hoy);
Para CodeBlocks

Para NetBeans

Para CLI
En los programas, se pueden usar objetos con funciones en
forma similar con las estructuras y funciones, se puede pasar un
objeto a una función por valor y por referencia (si se necesita
cambiar un valor del miembro) y las funciones pueden producir
objetos, pasaremos el objeto mensaje a dos funciones
diferentes la primer función usa la llamada por valor para
exhibir a las variables miembro del objeto, la segunda función
usa la llamada por referencia para cambiar las variables
miembro.
Entrada
y Salida de datos
(escritura y lectura de datos en C++)
En el primer capitulo se conoció y se practico como presentar
mensajes y recibir datos del teclado usando cout y cin,
que son propios de C++. Y sabemos que requerimos de la
biblioteca iostream.h, para poder realizar estas operaciones.
Ahora usaremos otra biblioteca llamada fstream.h, donde se
despenden estas otras funciones para poder realizar la salida a
los archivos de datos, esta se llama ofstream,
y para entrada de datos ifstream.
Ficheros en C++
Para trabajar con ficheros en C++ debe incluirse la cabecera
fstream.
Dependiendo de lo que deseemos hacer con el fichero, usaremos
objetos de las clases:
ifstream ( input file stream), clase
orientada para la lectura
ofstream (output file stream). clase
orientada para la escritura
fstream (file stream), cuando deseemos
alternativamente leer o escribir del mismo fichero en el mismo
programa.
A diferencia de cin y cout, que son objetos predefinidos de
acceso global, listos para su uso, los objetos que representan
flujos de datos a/desde ficheros deben ser definidos por el
programador.
La apertura del fichero consiste en definir un objeto de la
clase deseada (ifstream, ofstream ó fstream).
Para escribir o leer, se usan los operadores de inserción
<< y extracción >> como si del teclado o consola se
tratase.
El fichero se cierra implícitamente cuando el objeto sale del
ámbito en el que se ha definido o explícitamente llamando a la
función miembro close().
En lo que sigue vamos a ver un pequeño subconjunto de las
posibilidades que ofrece C++, y solo para trabajar con ficheros
tipo texto.
Para muchos problemas, con las herramientas que aquí se
estudian, es más que suficiente.
Para escribir salida a un archivo (es decir de
los datos en RAM que usa el programa y enviarlos al disco, con un
nombre) se debe de crear un objeto ofstream
en seguida se debe abrir el archivo usando la función miembro open cuando se han escrito los datos
debemos cerrar el archivo, usando la función miembro close.
Comenzamos con ejercicios simples y fáciles he iremos aumentando
las características y formas.
// file_out.cpp
//programa escritura datos en texto
// este programa creara un archivo que recibiría
datos desde el programa y los guarda en disco,
//en un archivo
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ofstream archivo_salida; //crea el objeto
archivo_salida
archivo_salida.open("libros.dat");//Crea y
Abre el archivo de salida libros.dat
archivo_salida <<"Titulo: Exito con
C++" <<endl;
archivo_salida <<"Autor: Yo he llegado
muy lejos con C++"<<endl;
archivo_salida <<"Editorial Mi
esfuerzo"<<endl;
archivo_salida.close();
return 0;
}
NOTA: CUANDO SE REALICE EN CODEBLOCK O APACHE
NETBEANS, UBICAR EL DIRCTORIO DONDE SE REALIZO LA COMPILACION Y
EJECUTAR EL PROGRAMA Y REVISAR EL DIRECTORIO, DEBERA DE MOSTRAR EL
ARCHIVO DE DATOS .DAT.
Para CodeBlocks

Para NetBeans

como no podemos ver directamente el producto de Apache NetBeans
lo revisamos desde su directorio de proyectos (libros.dat):

Para CLI
Usar funciones miembro de salida y manipuladores
Cuando los programas realizan operaciones
(flujos) en archivos, se pueden usar los manipuladores de
salida, en el siguiente programa file_out1.cpp, crea un archivo de
datos llamado file_out_data1.dat, que contendrá las
representaciones en Decimal, Octal y Hexadecimal, del 1 al 255.
Para usar los manipuladores debemos usar la bliblioteca de
encabezados "iomanip.h":
//programa file_out1.cpp
#include "fstream"
#include "iomanip"
using namespace std;
int main(int argc, char* argv[])
{
int i;
ofstream salida ("file_out_data1.dat");
salida << "Decimal " << "Octal "
<< "Hexadecimal" << endl;
for (i=1; i <= 255; i++)
{ salida << dec << setw(5) <<
i <<" " << setw(5) << oct << i
<<" "<< setw(5) << hex
<< i << endl;
}
salida.close();
return 0;
}
Para CodeBlocks

Para NetBeans

NOTA: CUANDO SE REALICE EN CODEBLOCK O APACHE
NETBEANS, UBICAR EL DIRCTORIO DONDE SE REALIZO LA COMPILACION Y
EJECUTAR EL PROGRAMA Y REVISAR EL DIRECTORIO, DEBERA DE MOSTRAR EL
ARCHIVO DE DATOS .DAT.
Para CLI
Se puede usar la función miembro put para
agregar caracteres en el archivo de datos (archivo de salida),
el programa escribiría los caracteres en el archivo de salida
file_out_data2.dat, que el programa file_out2.cpp genera:
// programa file_out2.cpp
//autor:
//fecha
#include "fstream"
using namespace std;
int main(int argc,char* argv[])
{
ofstream alfabeto ("file_out_data2.dat");
// genera/abre el
archivo file_out_data2.dat
for(int letra = 'A'; letra
<= 'Z'; letra++)
{alfabeto.put((char)letra);}
alfabeto.close();
return 0;
}
Para CodeBlocks
Para NetBeans

NOTA: CUANDO SE REALICE EN CODEBLOCK O APACHE
NETBEANS, UBICAR EL DIRCTORIO DONDE SE REALIZO LA COMPILACION Y
EJECUTAR EL PROGRAMA Y REVISAR EL DIRECTORIO, DEBERA DE MOSTRAR
EL ARCHIVO DE DATOS .DAT.
Para CLI
El siguiente programa realiza lo mismo pero usando el
operador de inserción.
#include "fstream"
using namespace std;
int main(void)
{
ofstream alfabeto("file_out_data3.dat");
for(char letra = 'A'; letra <= 'Z';
letra ++)
{alfabeto << letra;}
}
Para CodeBlocks
Para NetBeans
NOTA: CUANDO SE REALICE EN CODEBLOCK O APACHE
NETBEANS, UBICAR EL DIRCTORIO DONDE SE REALIZO LA COMPILACION Y
EJECUTAR EL PROGRAMA Y REVISAR EL DIRECTORIO, DEBERA DE MOSTRAR
EL ARCHIVO DE DATOS .DAT.
Para CLI
Antes de continuar debemos recordar esto:
setw(n) donde n es un
numero entero positivo
- Modifica la anchura de campo únicamente para la siguiente
entrada o salida. Por defecto es 0, pero se expande cuanto sea
necesario. ejemplo :
i=3;
cout << setw(10) << i << endl;
setprecision(n) donde n es un
numero entero positivo
Establece la precisión decimal que se utilizará para dar
formato a los valores de coma flotante en las operaciones de
salida, ejemplo :
double f =3.14159;
std::cout << std::setprecision(5) << f
<< '\n';
std::cout << std::setprecision(9) << f
<< '\n';
setiosflags() se utiliza para
establecer los indicadores de formato. (formar una mascara, un
formato de presentación), ejemplo:
cout << "|" << 5 <<"|";
cout << "|" << setw(4) << 5 << "|";
cout << "|" << setw(5) <<
setiosflags(ios::fixed) << setprecision(2)
<< 534.264 << "|";
como resultado:
|5|
| 5|
|534.26|
Ahora podemos utilizar esto para salida de datos a archivo con
formato:
//programa file_out4.cpp
#include "fstream"
#include <iomanip>
using namespace std;
int main(void)
{
ofstream reporte("file_out_data4.dat");
struct {
char nombre[64];
int edad;
char seguro[64];
float sueldo;
} empleados[] = {{"Roberto
López",51,"1111111",5500.00},
{"Erendira López",43,"222222",6000.00},
{"Regina Pérez",30,"333333",7000.00}};
reporte <<
"Nombre\t\tedad\tSeguro\t\tSueldo" <<endl;
for (int i=0;i<3;i++)
{
reporte <<
setiosflags(ios::left) << setw(18)<<
empleados[i].nombre << setw(8) <<empleados[i].edad
<< setw(16)<< empleados[i].seguro
<< setprecision(2) <<
setiosflags(ios::showpoint|ios::fixed) <<
empleados[i].sueldo << endl; // la secuencia es en una
sola linea
}
reporte.close();
return 0;
}
Para CodeBlocks
Para NetBeans

NOTA: CUANDO SE REALICE EN CODEBLOCK O APACHE
NETBEANS, UBICAR EL DIRCTORIO DONDE SE REALIZO LA COMPILACION Y
EJECUTAR EL PROGRAMA Y REVISAR EL DIRECTORIO, DEBERA DE MOSTRAR
EL ARCHIVO DE DATOS .DAT.
Para CLI
Control de apertura de archivos de salida
Dependiendo del propósito del programa en ocasiones se desea
que se agreguen datos al archivo de salida, es decir al final
del mismo, o en ocasiones no usar un archivo de datos que ya
existe para evitar borrar y perder la información.
Se puede controlar la forma de apertura de los archivos de
datos, se puede especificar la forma de apertura:
ofstream output("archivo.dat", ios::app);
ios::app Abre el archivo para añadir
informacion.
si existe "archivo.dat" lo abre para añadir datos, si no
existe lo genera.
El siguiente programa crea el archivo de datos y agrega
información.
//programa file_out5.cpp
//Toma la fecha y la hora del sistema
//usa un manipulador y va agreagando al archivo
file_out_data5.dat
//la fecha y hora en cada ocacion que se ejecuta el
programa
//demostrando que el ios::app, agrega datos al archivo de datos
#include "fstream"
#include <iomanip>
#include <ctime>
using namespace std;
int main(int argc, char* argv[])
{
ofstream
archivo_salida("file_out_data5.dat",ios::app);
auto t = time(nullptr);
auto tm = *::localtime(&t);
// Cuando se usa en una
expresión put_time(tmb, fmt)
// convierte la información de fecha y hora
de una hora del calendario determinada tmb
// a una cadena de caracteres según la
cadena de formato fmt
archivo_salida << put_time(&tm,
"%d-%m-%Y %H-%M-%S") << endl; //el manipulador envia
directo al archivo_salida
//
^ ^
//
put_time
(tmb, fmt)
// put_time es un
stream manipulador, así que también puede utilizarse junto a
stringstream para
// convertir una fecha en una cadena de
caracteres:
stringstream oss;
oss << put_time(&tm, "%d-%m-%Y
%H-%M-%S"); // aqui no se usan
apuntadores, pero oss recibe el "formato" de put_time
auto str = oss.str();
// lo pasa a string y lo instancia a str
archivo_salida << str; // y str es asignado a achivo_salida
archivo_salida<<endl;
archivo_salida.close(); // tendremos 2 formatos de fecha en el
archivo_salida
return 0;
}
Para CodeBlocks
Para NetBeans
Para CLI
Si esperamos un rato para que el reloj avance y ejecutamos
de nueva cuenta file_out5, tomara el nuevo valor fecha/hora
y lo agregara al archivo file_out_data5.dat
Debemos de observar que los datos que se agregan iran quedando
en la parte inferior
La función miembro open usa varios especificadores.
Al especificar la apertura de un archivo como
se ha mostrado en los programas anteriores, el programa
sobreescribe cualquier archivo existente, en el directorio de
trabajo del programa. Dependiendo del propósito del programa es
posible que sea necesario agregar datos a los ya existentes en
el archivo, o quizá sea necesario que las operaciones del
programa no se lleven a cabo en caso de que el archivo
especificado exista en el disco, para éstos casos podemos
especificar el modo de apertura del archivo incluyendo un
parámetro adicional en el constructor, cualquiera de los
siguientes:
ios::app Operaciones de añadidura.
ios::ate Coloca el apuntador del archivo al final del mismo.
ios::in Operaciones de lectura. Esta es la opción por defecto
para objetos de la clase ifstream.
ios::out Operaciones de escritura. Esta es la opción por defecto
para objetos de la clase ofstream.
ios::nocreate Si el archivo no existe se suspende la operación.
ios::noreplace Crea un archivo, si existe uno con el mismo
nombre la operación se suspende.
ios::trunc Crea un archivo, si existe uno con el mismo nombre lo
borra.
ios::binary Operaciones binarias.
Operaciones de control archivo de
entrada
Para abrir un archivo de entrada (es del disco a la memoria
RAM, que esta usando el programa), se crea un objeto del tipo ifstream
El siguiente programa abre un archivo de datos (.dat) desde la
linea de comandos:
//programa file_in.cpp
#include "fstream"
#include "iostream"
using namespace std;
int main(int argc, char** argv)
{
ifstream entrada(argv[1]);
if (entrada.fail())
{cerr << "Error
al abrir el archivo :" << argv[1] <<endl;}
else
{
while (! entrada.eof()) //lee caracteres en el archivo cuando
encuentra el eof se detiene y cierra el archivo
cout.put((char)entrada.get()); //muestra el caracter
entrada.close();
}
cout << endl;
return 0;
}
Para CodeBlocks
Al crear el programa pero nos presentara error en Code:Block
porque espera un argumento de entrada, cualquier archivo de
datos .dat creados anteriormente, para solucionar el problema
usaremos el botón de compilación únicamente y creara el
ejecutable en el directorio:
ya escrito el programa en code:blocks solo lo compilamos,
primero revisemos la carpeta donde se generan los ejecutables:
se compila con esto, genera el ejecutable:
el icono que tiene la forma
de engrane
y se observa lo siguiente en el directorio:

observamos el código fuente, y a la izquierda el ejecutable
Para NetBeans

podemos observar que muestra errores pero si es posible
compilar y ejecutar dando por resultado Error al abrir el
archivo:

en el directorio se genera el ejecutable.
Para CLI
En este caso no muestra problemas ni Warning este es el
ejemplo como debemos usarlo en los anteriores ya sea copiando el
ejecutable al directorio donde se encuentran los archivos de
datos .dat o viceversa.
Los programas precedentes han usado la función miembro fail
para determinar si fallo o tubo éxito la apertura del archivo de
datos .dat, las funciones miembro tambien pueden ser de la
siguiente manera:
Función miembro
|
Proposito
|
good
|
Produce 1 si la operación previa fue exitosa
|
eof
|
Produce 1 si llego al final del archivo de datos
|
fail
|
Produce 1 cuando hay un error
|
bad
|
Produce 1 si se produce una operación invalida
|
Podemos decir entonces que:
if(entrada.fail()) y
if( ! entrada)
son similares
De igual manera:
if(entrada.good()) y
if(entrada)
son similares en el resultado
Operaciones en
archivos de datos binarios.
Cuando se lee un achivo de datos de modo texto se considera
que el el valor 26 de ASCII (Crtl Z) es el final del archivo, si
se intenta leer un archivo binario de datos es muy probable que
en cualquier lugar se encuentre el ASCII 26,provocando que
termine la lectura antes de lo que esl final real del archivo.
Para evitar esto debemos abrir el archivo de forma binaria:
ifstream entrada(argv[1],ios..binario);
En seguida , necesitamos las operaciones para entrada y
salida, con las funciones miembro
entrada.read(buffer, sizeof(buffer));
entrada.write(buffer, sizeof(buffer));
El siguiente programa escribira 30 datos de tipo numerico, de
punto flotante, que es la manera adecuada de guardar datos
numericos
//progama file_in1.cpp
#include "fstream"
#include "iostream"
using namespace std;
int main(int argc, char* argv[])
{
int count;
float precio;
ofstream
existencias("existencias_articulos.dat",ios::binary);
if (existencias.fail())
{cerr <<"Error al abrir archivo
existencias" << endl;}
else
{
for (count =1; count <= 30;count ++)
{
precio = count*100.0;
existencias .write((char *) &precio,sizeof(float));
}
existencias.close();
}
return 0;
}
Para CodeBlocks

Para NetBeans

Para CLI

Para verificar esto deberán de ingresara a cada directorio
donde se estan generando estos archivos de datos y verificarlos
con cat en LINUX y type en windows
El programa invoca la función miembro .write, pasando la
dirección del valor a donde escribira al archivo:
existencias.write((char *)
&precio,sizeof(float));
direccion del dato
tamaño de dato en Bytes
Estos archivos de datos no pueden se leídos como texto porque
esta en binario, para leerlos debemos usar el siguiente programa:
//programa file_in2.cpp
//
#include "fstream"
#include "iostream"
#include "iomanip"
using namespace std;
int main(int argc, char* argv[])
{
float precio;
ifstream
existencias("existencias_articulos.dat",ios::binary);
while(!existencias.eof())
{
existencias.read((char *)&precio,
sizeof(float));
cout <<setprecision(2) <<
setiosflags(ios::showpoint | ios::fixed) <<precio <<
endl;
}
existencias.close();
return 0;
}
Para CodeBlocks

Para NetBeans
Para este caso hay que solucionar un detalle el archivo que
debemos leer no estará en este directorio, y lo que se realiza es
que se leen los archivos desde el directorio actual por lo cual,
debemos copiar el archivo de datos binario desde le proyecto
file_in1 hacia el proyecto file_in2, esto se hace desde el
administrador de archivos o desde linea de comandos:

de otra forma regresara 0.00 en un loop infinito ya que no
encuentra el EOF.

Para CLI
El resultado es muy extenso asi que realizamos pausa por
pantalla para ver resultado:



Operaciones de inserción y extracción de
archivos de datos en binario
Para realizar operación de inserción y
extracción en binarios usamos read y write, se pueden
encontrar errores inesperados , cuando se manipulan los datos, de
binario al pasar a ASCII, POR LOS VALORES QUE
REPRESENTAN,consideremos el siguiente programa:
// programa datos_mal.cpp
#include "fstream"
using namespace std;
int main(int argc,char* argv[])
{
ofstream salida("datos_mal.dat",ios::binary);
salida << 100.0/11.1;
salida << 22.0/7.0;
salida << 100.0/11.1;
return 0;
}
Para CodeBlocks

Para NetBeans

Para CLI

Ahora el siguiente programa lee estos datos mostrando los
valores leídos y los datos que debieran ser
//programa lee datos mal, leer_datos_mal.cpp
#include "fstream"
#include "iostream"
using namespace std;
int main(int argc, char* argv[])
{
ifstream entrada("datos_mal.dat",ios::binary);
float valor;
entrada >> valor;
cout << valor << " deberia ser "
<< 100.0 /11.1 <<endl;
cout << valor << " deberia ser "
<< 22.0/7.0 <<endl;
cout << valor << " deberia ser "
<< 100.0/11.1 <<endl;
entrada.close();
return 0;
}
Para CodeBlocks
Para NetBeans
Al igual que en el caso anterior tenemos un detalle en la
lectura de datos aquí, puesto que son proyectos distintos debemos
copiar el archivo de datos .dat del anterior a este proyecto,
primero realizamos el proyecto ya que no existe el directorio y
luego copiamos el archivo .dat


Para CLI

Como podemos observar no son los resultados que
esperamos y la razón es la siguiente, cuando se almacenan datos de
forma binaria en el disco, estos tiene un diseño, un tanto cuanto
especifico, y hasta ahi esta bien pero al recuperarlos (leerlos)
deben pasar a ASCII, y ahi es cuando se presenta el problema, a
continuación veremos la forma correcta de realizar esto, debemos
de sobrecargar los operadores read y write,
usaremos el tipo de inserción hidefloat
Antes de abrir un fichero hay que crear un
flujo, es decir un objeto de las clases ifstream, ofstream o
fstream e indicar el modo de apertura (lectura, escritura, lectura
y escritura, …).
Los modos en los que se puede abrir un fichero
son:
ios::append //añadir datos al final del
fichero
ios::in
//abrir fichero para leer datos
ios::out
//abrir fichero para escribir datos
en el siguiente ejemplo para usar un archivo de
datos de tipo binario se debe usar 1 solo argumento
(ios::out ios::in ios::append)
//programa datos_salida.cpp
#include "fstream"
#include "iostream"
using namespace std;
struct hidefloat {float datos;};
ostream& operator<<(ostream&
archivo,hidefloat valor)
{
archivo.write((char *)&valor.datos,sizeof(float));
return(archivo);
}
int main(int argc,char* argv[])
{
ofstream salida("datos.dat",ios::out); //usamos un solo
argumento para ofstream (escribe al disco, por eso es salida)
hidefloat valor;
valor.datos=100.0/11.1;
salida<<valor;
cout << valor<<endl; //
proposito ver el conenido escrito en el disco como binario
valor.datos=22.0/7.0;
salida<<valor;
cout << valor<<endl; // proposito ver el conenido
escrito en el disco como binario
valor.datos=100.0/11.1;
salida<<valor;
cout <<valor<<endl; // proposito ver el conenido
escrito en el disco como binario
salida.close();
return 0;
}
ostream&
operator<<(ostream& archivo,hidefloat
valor) ostream::operator<<
Este operador ( <<) aplicado a un flujo de salida se
conoce como operador de inserción . Está sobrecargado como función
miembro para:
(1) tipos aritméticos
Genera una secuencia de caracteres con la
representación de val , formateada correctamente según la
configuración regional y otras configuraciones de formato
seleccionadas en la secuencia, y los inserta en la secuencia de
salida.
ejemplos:
ostream& operator<< (bool val);
ostream& operator<< (short val);
ostream& operator<< (unsigned short val);
ostream& operator<< (int val);
ostream& operator<< (unsigned int val);
ostream& operator<< (long val);
ostream& operator<< (unsigned long val);
ostream& operator<< (float val);
ostream& operator<< (double val);
ostream& operator<< (long double val);
ostream& operator<< (void* val);
ostream& operator<< (streambuf* sb );
(2) buffers de flujo
Recupera tantos caracteres como sea posible de
la secuencia de entrada controlada por el objeto de búfer de
secuencia (si lo hay) y los inserta en la secuencia, hasta que se
agota la secuencia de entrada o hasta que la función no se inserta
en la secuencia.
ejemplos:
ostream& operator<< (streambuf* sb );
(3) manipuladores
Llamadas pf(*this), donde pf puede ser un manipulador .
Los manipuladores son funciones diseñadas específicamente para ser
invocadas cuando se usan con este operador.
Esta operación no tiene ningún efecto en la secuencia de salida y
no inserta caracteres (a menos que el propio manipulador lo haga,
como endl ó ends
ejemplos:
ostream& operator<< (ostream&
(*pf)(ostream&));
ostream& operator<< (ios& (*pf)(ios&));
ostream& operator<< (ios_base&
(*pf)(ios_base&));
Cada ocacion que el compilador encuentra un
valor de tipo hidefloat usando con el operador de insercion y un
flujo de archivo de salida, el programa llama a la función
operator el cual usa la funcion miembro write para escribir el
valor binario en el archivo de datos .dat
Para CodeBlocks

como resultado se aprecia caracteres que son en cierta forma
incomprensibes pero es lo que escribe en el disco
Para NetBeans

Para CLI
Para leer el archivo binario usamos esto:
//programa datos_entrada.cpp
//
#include "fstream"
#include "iostream"
using namespace std;
struct hidefloat {float datos;};
ifstream& operator>>(ifstream&
archivo,hidefloat *valor)
{
archivo.read((char *) valor,sizeof(float));
return(archivo);
}
int main(int argc,char* argv[])
{
ifstream entrada("datos.dat",ios::in);// lee datos del disco en
binario
hidefloat valor;
entrada>>&valor;
cout << valor.datos << " deberia ser
"<<100.0/11.1<<endl;
entrada>>&valor;
cout << valor.datos << " deberia ser
"<<22.0/7.0<<endl;
entrada>>&valor;
cout << valor.datos << " deberia ser
"<<100.0/11.1<<endl;
entrada.close();
return 0;
}
compárese con el programa que escribe datos al
disco:
ostream&
operator<<(ostream& archivo,hidefloat valor)
{
archivo.write((char *)&valor.datos,sizeof(float));
return(archivo);
}
.
.
.
ofstream
salida("datos.dat",ios::out);
el que extrae datos del disco
ifstream&
operator>>(ifstream& archivo,hidefloat *valor)
{
archivo.read((char *) valor,sizeof(float));
return(archivo);
}
.
.
.
ifstream
entrada("datos.dat",ios::in)
Puedes identificar los que es diferente y que es lo que es
similar.
Se concluye que:
Para abrir un fichero para lectura de datos creando un fstream
fichero:
ifstream <entrada>("datos.dat",
ios::in);
y para escritura en ese mismo fichero:
ofstream <salida>("datos.dat",
ios::out);
Las clases ifstream, ofstream y fstream tienen también
constructores que permiten abrir ficheros de forma automática
ifstream <fichero>("datos.dat");
donde se sobreentiende que el fichero se abre para lectura por
haber utilizado ifstream. Si se hubiese utilizado ofstream el
fichero se hubiera abierto para escritura.
Para CodeBlocks

Para NetBeans
Al igual que en el caso anterior tenemos un detalle en la
lectura de datos aquí, puesto que son proyectos distintos debemos
copiar el archivo de datos .dat del anterior a este proyecto,
primero realizamos el proyecto ya que no existe el directorio y
luego copiamos el archivo .dat


Para CLI
En este caso cuando el programa encuentra un
apuntador a una variable de tipo hidefloat usado en el
operador de extracción y un flujo de entrada de archivo el
programa llama a la función de operador. Debido a que
la función debe de cambiar el valor de un miembro de la
estructura, la dirección de la estructura se pasa a la
función, es un proceso posible de realizar pero es confuso de
realizar sin embargo los datos se encapsulan y no son visibles
como los archivos en ASCII.
Apertura de archivos para operaciones de Lectura
y Escritura
En los ejemplos
anteriores se han "abierto" archivos para escritura o lectura de
datos, tambien conocidos secuenciales ya que se van agregando
datos al final del archivo de datos.
Cuando se crean programas, lo que es usar
archivos de datos para escritura/lectura de datos, para esos casos
usaremos fstream
fstream archivo_datos("datos.dat", ios::in ||
ios::out);
Cuando se abre un archivo para entrada y salida
de datos el programa mantiene 2 apuntadores, uno para entrada y
otro para salidas, y estos archivos de datos son usados para
acceso aleatorio, estos no inician desde el principio, el programa
puede "moverse" entre los diferentes registros, para este
propósito se usan las funciones miembro seekg y seekp.
La función seekg coloca el apuntador en algún
valor especifico dentro del archivo de datos, (seekg proviene de
seek "busqueda" y g de get "obtener", la función seekp para
operación de salida (seek "buscar" y p de put "colocar")
la sintaxis es:
seekg(despalzamiento, desde_la_posición)
seekp(despalzamiento, desde_la_posición)
desplazamiento es la cantidad de bytes que se movera en un
archivo sea positivo o negativo, y desde_la_posicion es el lugar
donde parte la búsqueda.
Valor enumerado
|
Posición del archivo |
ios::beg |
Desde el principio del archivo
|
ios::cur
|
Desde la posicion del apuntador
|
ios::end
|
Desde el fin del archivo
|
Este programa creara el archivo de datos: letras.dat
//programa archiv_aleatorio_1.cpp
#include "fstream"
using namesapce std;
int main(int argc, char* argv[])
{
ofstream salida("letras.dat");
for(char letra ='A' ; letra <='H' ;
letra++ )
salida << letra;
for(char letra = 'O';letra <='Z'; letra++)
salida << letra;
salida.close();
return 0;
}
Para CodeBlocks

Para ver el contenido del archivo generado:

Para NetBeans

Para ver el contenido del archivo generado:

Para CLI
Para ver el contenido del archivo generado:

El siguiente programa prepara al archivo de datos "letras.dat"
para operación de lectura y escritura(fstream
letras("letras.dat",ios::in | ios::out);), para comenzar
el apuntador se coloca en la posición de byte que sigue a la letra
H, luego se escriben en el las letras I a Z. Enseguida el
apuntador de entrada get se coloca al inicio del archivo lee y
presenta el contenido.
// programa archiv_aleatorio_2.cpp
#include "fstream"
#include "iostream"
using namespace std;
int main(int argc,char * argv[])
{
// las siguintes lineas dan un espacio al inicio
cout << endl;
system ("cat letras.dat");// realiza una
llamada al sistema operativo y ejecuta el comado cat para
mostrar el contenido
// del archivo creado anteriormente antes de ser alterado desde
la posicion
cout <<endl;
fstream letras("letras.dat",ios::in
| ios::out);
letras.seekp(8, ios::beg);
for(char letra='I' ;letra <= 'Z'; letra++)
letras<<letra;
letras.seekp(0, ios::beg);
while( !letras.eof())
cout.put((char)letras.get());//presenta el archivo
modificado hasta el final del archivo eof()
letras.close();
return 0;
}
Para CodeBlocks

Para NetBeans
Al igual que en el caso anterior tenemos un detalle en la
lectura de datos aquí, puesto que son proyectos distintos debemos
copiar el archivo de datos .dat del anterior a este proyecto,
primero realizamos el proyecto ya que no existe el directorio y
luego copiamos el archivo .dat


Para CLI

para el siguiente programa necesitamos 2 archivos de datos,
usaremos el programa archiv_aleatorio_1.cpp, el cual modificaremos
un poco para ver el contenido de la salida y lo llamaremos archiv_aleatorio_1_a.cpp
//programa archiv_aleatorio_1_a.cpp
#include "fstream"
#include "iostream"
using namespace std;
int main(int argc, char* argv[])
{
ofstream salida("letrasmay.dat");
for(char letra ='A' ; letra <='H' ;
letra++ )
salida << letra;
for(char letra = 'O';letra <='Z'; letra++)
salida << letra;
salida.close();
cout << "El archivo de datos contine lo siguiente
:"<<endl;
system("cat letrasmay.dat");// mostrara las letras en mayusculas
cout << endl;
return 0;
}
y este mismo con el nombre de archiv_aleatorio_1_b.cpp
esta modificacion:
//programa archiv_aleatorio_1_b.cpp
#include "fstream"
#include "iostream"
using namespace std;
int main(int argc, char* argv[])
{
ofstream salida("letrasmin.dat");
for(char letra ='a' ; letra <='h' ;
letra++ )
salida << letra;
for(char letra = 'o';letra <='z'; letra++)
salida << letra;
salida.close();
cout << "El archivo de datos contine lo siguiente
:"<<endl;
system("cat letrasmin.dat");// mostrara las letras en minusculas
cout << endl;
return 0;
}
Los programas pueden controlar como se abren los archivos usando
los especificadores de modo de apertura. En el siguiente programa
es al forma de manejar los datos con archivos aleatorios:
// programa aleatorio_ejem.cpp: Ejemplo de ficheros
de acceso aleatorio.
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <cstring>
using namespace std;
// Funciones auxiliares:
int Menu();
long LeeNumero();
// Clase registro primer clase declarada
class Registro {
private:
char valido; // Campo que
indica si el registro es válido
// S->Válido, N->Inválido
char nombre[34];
int dato[4];
public:
Registro(char *n=NULL, int
d1=0, int d2=0, int d3=0, int d4=0) : valido('S') {
if(n) strcpy(nombre, n); else
strcpy(nombre, "");
dato[0] = d1;
dato[1] = d2;
dato[2] = d3;
dato[3] = d4;
}
void Leer();
void Mostrar();
void Listar(long n);
const bool Valido() { return valido == 'S'; }
const char *Nombre() { return nombre; }
};
// Implementaciones de clase Registro:
// Permite que el usuario introduzca un registro por pantalla
void Registro::Leer() {
cout<<"\e[1;1H\e[2J";
cout << "Leer registro:" << endl <<
endl;
valido = 'S';
cout << "Nombre: ";
cin.getline(nombre, 34);
for(int i = 0; i < 4; i++) {
cout << "Dato Numerico ["
<< i << "]: ";
dato[i] = LeeNumero();
}
}
// Muestra un registro en pantalla, si no
está marcado como borrado
void Registro::Mostrar()
{
cout<<"\e[1;1H\e[2J";
if(Valido()) {
cout << "Nombre: " <<
nombre << endl;
for(int i = 0; i < 4; i++)
cout <<
"Dato Numerico[" << i << "]: " << dato[i]
<< endl;
}
cout << "Pulsa una tecla";
cin.get();
}
// Muestra un registro por pantalla en forma
de listado,
// si no está marcado como borrado
void Registro::Listar(long n) {
int i;
if(Valido()) {
cout << "[" << setw(6)
<< n << "] ";
cout << setw(34) <<
nombre;
for(i = 0; i < 4; i++)
cout << ",
" << setw(4) << dato[i];
cout << endl;
}
}
// Clase Datos, almacena y trata los
datos, segunda clase declarada.
class Datos :public fstream {
private:
void Empaquetar();
public:
Datos() : fstream("alea.dat", ios::in | ios::out |
ios::binary) {
if(!good()) {
open("alea.dat", ios::in | ios::out | ios::trunc | ios::binary);
cout <<
"Archivo de datos creado, Enter para continuar ..." <<
endl;
cin.get();
}
}
~Datos() {
Empaquetar();
}
void Guardar(Registro ®);
bool Recupera(long n, Registro ®);
void Borrar(long n);
};
// Implementación de la clase Datos.
void Datos::Guardar(Registro ®) {
// Insertar al final:
clear();
seekg(0, ios::end);
write(reinterpret_cast<char *> (®),
sizeof(Registro));
cout << reg.Nombre() << endl;
}
bool Datos::Recupera(long n, Registro ®) {
clear();
seekg(n*sizeof(Registro), ios::beg);
read(reinterpret_cast<char *> (®),
sizeof(Registro));
return gcount() > 0;
}
// Marca el registro como borrado:
void Datos::Borrar(long n) {
char marca;
clear();
marca = 'N';
seekg(n*sizeof(Registro), ios::beg);
write(&marca, 1);
}
// Elimina los registros marcados como
borrados
void Datos::Empaquetar() {
ofstream ftemp("alea.tmp", ios::out);
Registro reg;
clear();
seekg(0, ios::beg);
do {
read(reinterpret_cast<char *>
(®), sizeof(Registro));
cout << reg.Nombre() <<
endl;
if(gcount() > 0 &&
reg.Valido())
ftemp.write(reinterpret_cast<char *> (®),
sizeof(Registro));
} while (gcount() > 0);
ftemp.close();
close();
remove("alea.bak");
rename("alea.dat", "alea.bak");
rename("alea.tmp", "alea.dat");
open("alea.dat", ios::in | ios::out | ios::binary);
}
int main(int argc, char* argv[])
{
Registro reg; //crea
objeto reg
Datos datos;
//crea objeto datos
int opcion;
long numero;
do {
opcion = Menu();
switch(opcion) {
case '1': //
Añadir registro
reg.Leer();
datos.Guardar(reg);
break;
case '2': //
Mostrar registro
cout<<"\e[1;1H\e[2J";
cout << "Mostrar registro: ";
numero = LeeNumero();
if(datos.Recupera(numero, reg))
reg.Mostrar();
break;
case '3': //
Eliminar registro
cout<<"\e[1;1H\e[2J";
cout << "Eliminar registro: ";
numero = LeeNumero();
datos.Borrar(numero);
break;
case '4': //
Mostrar todo
numero = 0;
cout<<"\e[1;1H\e[2J";
cout <<
"Nombre
Datos" << endl;
while(datos.Recupera(numero, reg)) reg.Listar(numero++);
cout << "pulsa return";
cin.get();
break;
}
} while(opcion != '0');
return 0;
}
// Muestra un menú con las opciones disponibles y captura una
opción del usuario
int Menu()
{
char resp[20];
do {
cout<<"\e[1;1H\e[2J";
cout << "MENU
PRINCIPAL" << endl;
cout << "--------------"
<< endl << endl;
cout << "1- Insertar
registro" << endl;
cout << "2- Mostrar registro"
<< endl;
cout << "3- Eliminar
registro" << endl;
cout << "4- Mostrar todo"
<< endl;
cout << "0- Salir" <<
endl;
cin.getline(resp, 20);
} while(resp[0] < '0' && resp[0] >
'4');
//return resp[0];
}
// Lee un número suministrado por el usuario
long LeeNumero()
{
char numero[6];
fgets(numero, 6, stdin);
return atoi(numero);
}
Curso_cpp Salvador Pozos (pag 147, 148, 149 )
Estructura de Datos en C++ (pag 71 a 94 )
Manual_basico c++ (pag 18 - 23)
Metodologia de al programacion II clases
POO_C++ (pag 21)
Programacion y resolucion de problemas con C++ (pag 429
- )
https://elvex.ugr.es/decsai/builder/intro/5.html
http://conclase.net/c/curso/cap29
Programación orientada a objetos con C++, Fco. Javier Ceballos Cap.
3
https://www.tutorialesprogramacionya.com/cmasmasya/index.php?inicio=0
https://ccodigo.help/2010/09/15/como-usar-cin-getline-en-c/
https://www2.eii.uva.es/fund_inf/cpp/temas/10_ficheros/ficheros_cpp.html
https://cplusplus.com/reference/iomanip/put_time/
C++ Programacion Exitosa -- Kris Jamsa
http://www.programacionenc.net/index.php?option=com_content&view=article&id=69:manejo-de-archivos-en-c&catid=37:programacion-cc&Itemid=55
https://www2.eii.uva.es/fund_inf/cpp/temas/10_ficheros/ficheros_cpp.html
https://conclase.net/c/ficheros/cap4