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

Definición de una Clase

Definir una clase

Constructores

Inicialización de objetos

Sobrecarga de constructores

Modificadores de acceso

Constructor por defecto

Destructores

Sobrecarga de constructores

Funciones miembro constantes

El atributo protected

El puntero implícito this

Asignacion de Objetos

Entrada y Salida de datos

Control de apertura de archivos de salida

Operaciones de Control archivo de entrada

Operaciones en archivos de datos binarios

Operaciones de inserción y extracción de datos binarios

Apertura de archivos para operaciones de Lectura y Escritura



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:

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 &reg);
   bool Recupera(long n, Registro &reg);
   void Borrar(long n);

 
};

// Implementación de la clase Datos.
void Datos::Guardar(Registro &reg) {
   // Insertar al final:
   clear();
   seekg(0, ios::end);
   write(reinterpret_cast<char *> (&reg), sizeof(Registro));
   cout << reg.Nombre() << endl;
}

bool Datos::Recupera(long n, Registro &reg) {
   clear();
   seekg(n*sizeof(Registro), ios::beg);
   read(reinterpret_cast<char *> (&reg), 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 *> (&reg), sizeof(Registro));
      cout << reg.Nombre() << endl;
      if(gcount() > 0 && reg.Valido())
         ftemp.write(reinterpret_cast<char *> (&reg), 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