Capitulo 2

Estructuras de datos en C++.

 Uso, manejo y ventajas.
Arreglos, vectores, matrices y demás

Las estructuras de datos en C++ se pueden entender como un tipo de dato compuesto (no complejo). Las estructuras de datos permiten almacenar de manera ordenada una serie de valores dados en una misma variable. Las estructuras de datos más comunes son los vectores o arreglos y las matrices, aunque hay otras un poco más diferentes como son el struct y las enumeraciones.

En esta serie de contenidos aprenderemos de éstas con detalle. Las estructuras de datos han sido creadas para solucionar una gran variedad de problemáticas que no eran solucionables (o al menos no muy fácilmente) con los tipos de datos primitivos. Tal como mencioné hace un momento las estructuras de datos se pueden ver como una agrupación o estructuración para una serie de tipos de datos primitivos (aunque también pueden poseer tipos de datos complejos) que pueden ser fácilmente utilizadas e identificadas. Sin la existencia de las estructuras de datos sería imposible o bastante complicado por ejemplo conocer y manejar todos los números de identificación, nombres y direcciones de todos los usuarios de un sistema (que normalmente serían muchísimos) pero ahora con las estructuras de datos es muy simple definir una serie de posiciones en memoria para cada valor que deseamos guardar o definir un orden o valores específicos para cada campo y accediendo a ellos generalmente por medio de una única variable, todo esto es sencillo hacerlo con el uso de las estructuras de datos y sin desperdiciar recursos.

Vamos entonces a comenzar con la sección de arreglos o vectores para comprender mejor de lo que estoy hablando.

Arrays

Particularidades de los Arrays

Recorrer un Array

Matrices en C

Recorrer una matriz en C

Los punteros

Array dinámico

New

Funciones

Plantillas

ESTRUCTURAS



Arrays, arreglos o vectores en C++. Uso, declaración y sintaxis de los vectores en C++

Los arrays, arreglos o vectores forman parte de la amplia variedad de estructuras de datos que nos ofrece C++, siendo además una de las principales y más útiles estructuras que podremos tener como herramienta de programación. Los arrays, arreglos o vectores (como los quieras llamar), son utilizados para almacenar múltiples valores en una única variable. En un aspecto más profundo, los arrays, permiten almacenar muchos valores en posiciones de memoria continuas, lo cual permite acceder a un valor u otro de manera rápida y sencilla. Estos valores pueden ser números, letras o cualquier tipo de variable que deseemos incluso tipos de datos propios.

En múltiples ocasiones es necesario almacenar gran cantidad de información en una variable y a menudo sucede que no conocemos con exactitud la cantidad de datos que debemos almacenar, pero sabemos que sí sería más de uno, como por ejemplo almacenar las identificaciones de las personas ingresadas al sistema. Los arrays, arreglos o vectores son una estructura que nos permite solucionar este tipo de problemas. Para explicar mejor de lo que hablo, pongamos un ejemplo:

Ejemplo de Arrays o Vectores en C++

Imaginemos que queremos crear un programa con el cual podamos de algún modo almacenar los títulos y los autores de diferentes libros. El usuario es el encargado de suministrar la información de cada libro, así entonces, dado que es el usuario quien lo hace, nosotros no tenemos manera alguna de saber cuántos libros va querer él ingresar por medio de nuestro programa. El caso principal es que queremos almacenar en la memoria el titulo y el autor de TODOS y cada uno de los libros. Entonces ¿cómo crees que podrías hacer esto? Con lo que sabemos hasta hora, se nos podrían ocurrir un par de cosas. Veamos:

Posible Solución 1: Sin usar vectores (errónea):

Podríamos pensar primero, "listo, está bien, es fácil, declaro una variable llamada titulo y otra autor, ambas de tipo string y se las pido al usuario", pues bien, esta solución digamos que nos permite almacenar la información del primer libro que el usuario ingrese, pero en cuanto desee ingresar otro libro ¿qué vamos a hacer?, si lo hacemos así, cuando el usuario ingrese la información para un nuevo libro, va a sobrescribir los valores anteriores y habremos perdido la información del primero, de manera que esta solución no es válida.

Posible Solución 2: Sin usar vectores o matrices (errónea):

Pensando un poco más en esto, se nos ocurre una forma de almacenar la información de cada libro, podríamos crear un par de variables distintas para cada libro. Pero de inmediato nos damos cuenta que si por ejemplo al usuario se le cruzara por la cabeza ingresa información para 10 libros tendríamos entonces ¡20 variables distintas!, 2 por cada libro, no es mucho, pero si se le ocurriera ingresar 1000 libros, ¿estarías dispuesto a declarar 2000 variables?. De modo que esta alternativa es incluso peor que la anterior y seguimos aún sin solucionar nuestro problema. (una alternativa a esto es almacenar toda la información en archivos de datos C++ puede realizar esto, asi como crear algoritmos para almacenamiento, búsqueda, modificación, y borrado de datos, mas aun se puede enlazar C++ con MySQL, SQL, MariaSQL, Oracle, que son motores de bases de datos pero esto son en cursos mas avanzados)

Posible Solución 3: Usando vectores o matrices (correcta):

¡Pues bien!, tal y como mencioné antes, los arrays o los vectores han venido para ayudarnos en múltiples circunstancias similares a esta. Dado que un array, arreglo o vector es capaz de almacenar múltiples valores en una misma variable, tenemos el elemento perfecto para almacenar la información de todos los libros, podremos crear un vector de un tamaño cualquiera capaz de contener en sí los nombres de los autores y otro con los títulos de los libros o alternativamente podríamos crear una matriz de dos columnas que contenga en la primera columna los autores y en la segunda los títulos; ambas soluciones son validas y vamos a ver ambas, usando vectores en esta sección y usando matrices en la sección de matrices. (esta es la solución para usar con array, pero recuerda que esta no es la solución final, ni la unica, existen métodos para almacenar datos en disco, y que puedan ser modificados guardados y recuperaros, adicionalmente se puede usar C++ con algunos sistema de bases de datos)

Nota: En C++, a diferencia de algunos otros lenguajes de programación, los vectores y las matrices presentan un "inconveniente" con el tamaño. Es decir, no es posible crear de una manera sencilla un vector capaz de almacenar una cantidad de información indefinida, es necesario ingresar con antelación la cantidad de datos (tamaño) que el vector o la matriz tendrá. Este problema se puede solucionar, pero es algo que no veremos en esta sección.

¿Cómo declarar un Array o Vector en C++?

Para declarar un vector en C++, se deben seguir las mismas normas básicas que se siguen para declarar una variable cualquiera, con un pequeño cambio en la sintaxis. Para declarar un vector, arreglo o como lo quieras llamar, necesitaremos saber el tipo de los datos que irán al interior de este, es decir, serán número enteros, o numero decimales o cadenas de texto, etc. necesitamos también, como siempre, un nombre para el vector y un tamaño máximo. Ahora te preguntaras entonces con tantas restricciones en que uso los arrays, pues bien hay métodos que necesitaras leer datos y pasarlos a un array ya sea para ordenarlos o realizar búsqueda de datos, pero se verán a su momento. La sintaxis para declarar un vector en C++ es la siguiente:

tipo_de_dato nombre_del_vector[tamaño];

Tenemos entonces, tal como mencioné antes, que para declarar un vector en C++, debemos definirle un tipo de los datos, sea entero, float, string, etc., debemos darle un nombre y al interior de los corchetes "[]" debemos poner el tamaño máximo que tendrá el vector, es decir la cantidad máxima de datos que podrá contener (recuerda que en C++ esto es necesario hacerlo). Veamos un ejemplo en el cual pondré la declaración de varios vectores de diferentes tipos y tamaños en C++.

Declaración de un Array o Vector en C++

int my_vector1[10];
float my_vector2[25];
string my_vector3[500];
bool my_vector4[1000];
char my_vector5[2];

Veamos lo que representa cada línea del código anterior.


Línea 1 - int my_vector1[10];

Esta línea contiene la declaración de un vector llamado my_vector1, el cual contendrá un máximo de 10 elementos de tipo entero.


Línea 2 - float my_vector2[25];

Esta línea contiene la declaración de un vector llamado my_vector2, el cual contendrá un máximo de 25 elementos de tipo float.

Línea 3 - string my_vector3[500];

Esta línea contiene la declaración de un vector llamado my_vector3, el cual contendrá un máximo de 500 elementos de tipo string.

Línea 4 - bool my_vector4[1000];

Esta línea contiene la declaración de un vector llamado my_vector4, el cual contendrá un máximo de 1000 elementos de tipo booleano.


Línea 5 - char my_vector5[2];

Esta línea contiene la declaración de un vector llamado my_vector5, el cual contendrá un máximo de 2 elementos de tipo char.

Ya que está claro cómo se declara un vector, vamos a ver cómo inicializarlo, es decir inicializar un vector en C++ o en otras palabras darle valores a un vector.


¿Cómo inicializar un Array o Vector en C++?

En cuanto tenemos declarado un vector, es posible asignarle valores, evidentemente estos valores deben coincidir con el tipo de dato que le asignamos a dicho vector, no tendría sentido ingresar como valores de un vector cadenas de caracteres si el tipo de dato de dicho vector es numérico.

Voy a mostrar a continuación formas distintas de inicializar un vector, todas son validas, ya es cuestión de nuestras necesidades y conocimientos determinar cuál es útil y en qué momento. Veamos entonces:

Forma 1 de declarar un Array o Vector en C++

string vector[5] = {"5", "hola", "2.7", "8,9", "adios"};

Aquí hemos declarado un vector de tipo string tamaño 5 (son los datos que contendra) y lo hemos inicializado con diferentes valores, es necesario notar que cada valor va entre comillas dobles "" puesto que son strings (una serie de caracteres pueden se de 1 a 255). El valor inicial corresponde a la casilla o índice 0 (los arreglos comienzan con el elemento 0 y no por 1, por tanto un arreglo de 10 valores serian en orden [0] [1] ... [9], contamos dede 0 al 9 que son 10 elementos), y tiene el valor de "5", el índice 1 el valor es "hola" y el índice 4 el valor es "adiós", es importante notar que el primer índice de n array o vector no es el UNO sino que es el CERO.

Forma 2 de declarar un Array o Vector en C++

int vector2[] = {1,2,3,4,10,9,80,70,19};

Aquí hemos declarado un vector de tipo int (entero numérico, refiérase al capitulo anterior los valores que pueden adquirir los diferentes declaraciones de variables) y no especificamos su tamaño, si el tamaño no se especifica entre los corchetes, el vector tendrá como tamaño el número de elementos incluidos en la llave, para este caso es 9, es el valor por default.

Particularidades de los Arrays, arreglos o Vectores en C++

Con C++, existen algunas particularidades, en cuanto a la declaración de vectores, que me parece importante destacarla para que en momento de quizá caer en ellas comprender como podrían cambiar las cosas o básicamente en que consiste el error, veamos:

Particularidad 1 al momento de declarar o inicializar un Vector o Array en C++

int vector2[3];
vector2[3] = {1,5,10};

Dadas las características de C++, es fácil pensar que es factible crear o declarar un vector de un tamaño cualquiera y posteriormente inicializarlos de forma habitual como se muestra en este código, sin embargo hacer esto es un error, si declaramos un vector y no lo inicializamos inmediatamente, no es posible inicializarlo de la forma que hemos visto antes, es decir entre llaves cada valor, como en la línea 2 del código anterior.

La única forma de inicializar el vector, o mejor dicho, darle valores a cada una de sus casillas, es hacerlo uno por uno, es decir darle un valor a la casilla cero a la uno y a la 2 (para un vector de tamaño 3). Por defecto, al declarar un vector sin ser inicializado, cada una de las casillas de este vector toma como valor el valor por defecto del tipo de variable, para el caso de los enteros (int) es -858993460. Así entonces para asignar valores a cada casilla lo hacemos así:

int vector2[3]; // declaracion del arreglo el arreglo se llama vector2
vector2[0] = 1; // inicializa el arreglo vector2 con el primer elemento del array [0]
vector2[1] = 3; // inicializa el arreglo vector2 con el segundo elemento del array [1]
vector2[2] = 10; // inicializa el arreglo vector2 con el tercer elemento del array [2]

Es importante notar en este código, que el número que va entre corchetes ya no indica tamaño (pues vector2 ya está declarado) sino que indica el índice o el numero de la casilla con la cual estaremos operando (recordemos que el primer índice es cero y no uno), en el código anterior, habíamos declarado un vector de tamaño 3, por lo cual debíamos asignar valores a los índices 0, 1 y 2.

es decir que:
vector2[0] tiene el valor de 1
vector2[1] tiene el valor de 3
vector2[2] tiene el valor de 10

Particularidad 2 al momento de declarar o inicializar un Vector o Array en C++

float vector3[5] = {10.5};

En C++ en el momento de inicializar un array, arreglo o Vector, estamos acostumbrados a que si inicializamos inmediatamente después de declarar el vector, debemos poner la misma cantidad de elementos al interior de las llaves de manera que corresponda con el tamaño del vector, pues bien, estos es lo más recomendable, sin embargo si ponemos una cantidad de elementos menor a la del tamaño real del vector, estamos queriendo decir que estos elementos toman los valores puestos entre las llaves y los demás serian cero, para el caso del código anterior el primer elemento (el del índice cero) va a tener un valor de 10.5 y los otros 4 elementos van a tener el valor de cero.

Es decir que:

float vector3[0] tiene el valor de 10.5

float vector3[1] tiene el valor de 0

float vector3[2] tiene el valor de 0

float vector3[3] tiene el valor de 0

float vector3[4] tiene el valor de 0

Ya tenemos claro cómo declarar un array o vector en C++, algunas características un tanto particulares de estos, sin embargo aun no sabemos cómo obtener los datos de un array, es decir una vez el array o vector este lleno con los elementos que queremos, como podemos obtener esa información y más aun, como obtener un valor específico dentro del array.

Veámoslo:

Obtener el valor de una casilla específica en un array en C++

Es muy común el caso en el que tenemos un vector con una enorme cantidad de elementos, sin embargo de todos estos, solo nos interesa uno en especial y corremos con la suerte de saber cuál es su índice, sabiendo el índice de un elemento en un array es bastante sencillo obtener el valor de este:


float vector4[5] = {10.5, 5.1, 8.9, 10, 95.2}; //Array con 5 elementos
float numero5 = vector4[4]; //Para acceder al elemento 5, se usa el índice 4, es decir el elemento en la quinta posicion esta en el lugar [4], y este se le asigna a una variable (numero5)
float numero1 = vector4[0]; //Para el primer elemento se usa el índice 0


Como podemos ver, para acceder a un valor específico conociendo el índice del elemento, solo basta con escribir dicho índice entre los corchetes "[ ]", recuerda que el índice comienza desde cero, así por lo tanto en un vector de 5 elementos (como el del ejemplo), el último elemento esta en el índice 4 y el primer elemento del array en el índice 0.

Ya tenemos entonces una forma de acceder de forma individual a un elemento de un array o vector, vamos a ver ahora como recuperar todos los elementos de un vector de forma simple

Recorrer un Array o Vector en C++

Para obtener todos los datos que se encuentran al interior de un vector, es necesario recorrer el array o vector, para recorrerlo, se usa casi siempre un ciclo for, en algunos casos mas específicos un ciclo while, pero generalmente el ciclo for es el ideal para esto, dado que conocemos el tamaño del array. La lógica de este procedimiento es la siguiente, el ciclo for comenzara desde cero e ira hasta el tamaño del vector, de modo que la variable de control que generalmente llamamos "i", será la que va a ir variando entre cero y el tamaño del array, de esta forma al poner la "i" al interior de los corchetes, estaremos accediendo al valor de cada casilla del vector y podremos hacer lo que sea necesario con dicho valor, veamos:

Nota: A veces no es posible determinar con facilidad el tamaño exacto de un vector, pero en C++ existen varias formas de determinar el tamaño de un array o vector fácilmente, aquí explicare un método. Cabe notar que este tamaño es el que ira como tope del ciclo for y sería equivalente a que nosotros mismos, en caso de saber el tamaño del vector, lo pongamos allí, sin embargo como veremos en otra sección no siempre es posible saber con certeza el tamaño de un vector, es por esto que explico cómo hacerlo.

#include <iostream.h>

using namespace std;

int main(int argc, char* argv[])
{
    int edades[12] = {1,2,9,8,16,32,9,50,36,20,1,87};  // doce elementos en el arreglo
    int limite = (sizeof(edades)/sizeof(edades[0])); //sizeof vease Nota 1 (al final del documento)
    for (int i = 0; i < limite; i++)
    {
            cout<<edades[i]<<endl;
    }
}

Vamos a ver de forma resumida en qué consiste y que hace cada una de estas líneas
Línea 1: - int edades[12] = {1,2,9,8,16,32,9,50,36,20,1,87};

Tenemos en la primera línea la declaración de un vector que contiene las edades de 12 personas.

Línea 2: - int limite = (sizeof(edades)/sizeof(edades[0]));

En la segunda línea, tenemos la declaración del límite del ciclo o en otras palabras el tamaño del array. El tamaño de un array se puede calcular de varias formas, aquí lo obtenemos calculando el tamaño del array entero, dividido por el tamaño del primer elemento de dicho array, para mas detalles de esto, verifica la información sobre el operador sizeof, Nota 1.

Línea 3 a 6:

  for (int i = 0; i < limite; i++)
    {
            cout<<edades[i]<<endl;
    }

Desde la tercera línea hasta la sexta, tenemos entonces un ciclo for que comienza en cero y termina en el límite (es importante notar que la condición usada es estrictamente menor "<" y no menor o igual "<="), al interior de este ciclo, es donde accedemos a cada uno de los elementos del vector por medio de la sintaxis explicada anteriormente

Línea 5:

La quinta línea es quizá la más vital aunque sin las demás no tendríamos nada. En esta línea, estamos accediendo a cada uno de los elementos del array de edades, un elemento por cada vuelta que da el ciclo, accedemos a cada elemento poniendo entre los corchetes la variable i, que es la que esta cambiando a medida que el ciclo va progresando, así estaremos accediendo a todos los elementos e imprimiéndoles por pantalla

Muy bien, llego el momento de afianzar nuestros conocimientos viendo un ejemplo. Ahora que tenemos claro como declarar un vector en C++, como recorrerlo y como acceder a sus datos, vamos a ver un ejemplo basado en el problema que planteé al inicio de esta sección (el de los libros).

Ejemplo 1 de Arrays o Vectores en C++

El problema es simple, queremos crear un programa con el cual podamos guardar los títulos y los autores de diferentes libros sin perder ninguno de ellos. El usuario es el encargado de suministrar la información de cada libro. Vamos a suponer que el usuario solo podrá ingresar un máximo de 5 libros, para así tener un tamaño de vector fijo. Veamos entonces como se haría esto, el programa se llamara arreglo_1.cpp:

//nombre del programa arreglo_1.cpp

#include <iostream.h>
#include <string.h>

using namespace std;

int main(int argc, char* argv[])
{
    string titulos[5];
    string autores[5];
    cout << "Por favor ingrese la siguiente información de los Libros: \n";
    for(int i = 0; i < 5; i++)
    {
        cout << "\n******* Libro " << i + 1 << "********:\n";
        cout << "Titulo: ";
        //cin >> titulos[i]; //No funciona con espacios
        getline(cin, titulos[i]);
        cout << "Autor: ";
        //cin >> autores[i]; //No funciona con espacios
        getline(cin, autores[i]);
    }
     cout << "Mostrando la informacion de los libros ";
      for (int j = 0; j <5; j++)
        {
       cout << "\n******* Libro " << j + 1 <<"********:\n";
       cout << "Titulo: " << titulos[j];
       cout << "\n Autor: " << autores[j] << "\n";

      }
}

Estoy seguro de que a estas alturas comprendes bien qué hace cada una de estas líneas. En caso de que no comprendas nada de esto, te recomiendo leer nuevamente  la sección de ciclos o la sección de entrada y salida de datos. Usamos cadenas de texto completas (string) de modo que al ingresar un titulo o un autor podamos poner textos completos

Como puedes apreciar, hemos reemplazado las líneas que usaban cin para leer los datos por la función getline(...) que recibe como primer argumento el flujo de entrada de cin y como segundo argumento la variable en la que queremos poner el valor.

Para CodeBlocks




Para NetBeans




Para CLI



Ahora que ya sabemos usar los ciclos (For, While, Switch, Do.. while, y arreglos arrays) ponemos unas características especiales de cin, como recordaras cin permite de forma fácil capturar valores desde el método estándar (método entrada  en este caso el teclado), y observamos que al teclear una serie de caracteres estos los recibe pero si hay algún espacio entre ellos solo recibe aquellos que están antes de ese espacio lo demás queda truncado, y no no ayuda mucho, pero hay variantes de cin que veremos a continuación que nos ayudaran de muchas formas, en el ejemplo anterior modificamos el programa para usar getline y ayudándonos de cin, daremos una explicación mas detallada de getline antes de seguir, recuerda estos son recursos que puedes usar en tus programas mas adelante, revisa la Nota 2, al final del documento para conocer mas del getline.

Ahora veremos como podemos usar cin de otra manera para recibir datos de string, primero haremos un ejemplo diferente a los anteriores para que lo puedas comprender.

//programa ejem_1_cin_array.cpp

#include <iostream.h>
#include <string.h>

using namespace std;

int main(int argc, char* argv[])

{
    char Cadena[40];
    int N;
        cout << "Introduzca una cadena :";
        cin.getline(Cadena,40); // Se toma una cadena de 40 caracteres como máximo.
        cout << endl << endl; // Dos líneas en blanco
        cout  << (Cadena); // Se da salida a los 40 caracteres
        cout.flush();         // Vuelca el stream al correspondiente dispositivo, limpia el buffer de datos
        cout << endl << "Introduzca un numero :";
        cin >> N;
        cout << endl << "Su representacion es :" << N;
        cout.flush(); // Vuelca el numérico al correspondiente dispositivo

return 0;
}

Para CodeBlocks



Para NetBeans



Para CLI



En este ejemplo podemos tomar el string del teclado y pasarlo a Cadena [40], pero si no se llega a completar 40 caracteres los llenara de elementos de espacios en blanco, esto es normal ya que espera recibir los 40 si faltan los completara como sea, ayuda a recibir datos pero tiene este inconveniente, sea corto o largo el nombre antes de lo 40, seran llenados el resto en espacios, cuidado no esta vació, hay espacios tal como los espacios que se aplican cuando usas la barra espaciadora en el teclado no se ven pero están ahí, se conocen en el código ASCII como código 32 

La instrucción flush es para borrar la variable de la memoria y liberarla.

Bien ahora veremos otras variantes de cin, útiles y sera usando un ciclo, el programa lo llamaremos programa ejem_2_cin.cpp, en el ejemplo el programa espera datos para un array numérico la entrada de datos fnalizara cuando se de alguna de esta condiciones, se llene el arreglo, se presente un error, o usando el EOF que es usar Ctrl+z (^Z). El dato sera solcitado de nuevo si se introduce una letra, y con un sonido (alarma audible con la instruccion '\a' verificar en la tabla ASCII)

// programa ejem_2_cin_array.cpp
#include <iostream.h>

using namespace std;

int main(int argc, char* argv[])
{
int notas[7], i=0, eof; // declaramos variables e instanciamos en la misma declaración
cout << "Introducir notas (numeros). Eof + Enter para finalizar. \n\n";

do
{
    cout << "nota[" << i << "] = "; // imprime en pantalla nota [i]
    cin >> notas[i++]; //extraer nota [1]; siguiente i
    eof = cin.eof();   // valor del indicador de fin de archivo
    if (cin.fail())    // si el dato no es correcto
    {
        cerr << '\a'; //señal acustica dato incorrecto
        cin.clear();  //desactivar los indicadores de error
        cin.ignore(128,'\n'); // desechar datos incorrectos
        i--;
    }
    }
    while (!eof && !cin.bad() && i <7 );

    if (cin.bad())
    {
        cerr << "Error irrecuperable "<< endl;
        return 0;
    }
    //escribir datos leidos
    cout << "recuperando datos leidos "<< endl;
     // el valor de i en este momento es de 7
    for (int k=0;k<i;k++)
    {
       cout << "nota[" << k << "] = " << notas[k]<< endl; // imprime en pantalla nota [k]
    }
}

Cuando se introduce un dato erróneo (como una letra, en lugar de un numero)  la sentencia cin.ignore(128,'\n') extrae y desecha hasta 128 caracteres, en cualquier caso termina cuando se extrae '\n'. Analiza el comportamiento de do while y la condición que usa.

Se muestra en este ejemplo que al introducir un dato alfanumérico, solicita de nuevo el dato que sea numérico.

Para CodeBlocks


Para NetBeans



Para CLI



Matrices en C++. Uso, declaración, y sintaxis de las matrices en C++

Las matrices o como algunos las llaman "arreglos multidimensionales" son una estructura de datos bastante similar a los vectores o arreglos. De hecho, una matriz no es más que una serie de vectores contenidos uno en el otro (u otros), es decir, una matriz es un vector cuyas posiciones son otros vectores. Hablemos con más detalle de esto para quedar más claros.

Primero, dejemos claro qué es una matriz. En términos generales, una matriz es una estructura conformada por filas y columnas, idealmente más de dos filas y columnas, de hecho, podemos decir que si una "matriz" tiene una única fila o una única columna, entonces estamos hablando de un vector y no una matriz como tal.

La intersección de una fila y una columna de la matriz son las casillas y cada una de ellas podrá poseer información, simple o compleja (ya dependerá de nuestras necesidades).

Ahora, tal como dije antes, un vector posee una única fila (o columna, como lo quieras ver) y de este modo un grupo de vectores unidos conforman una matriz, es por esto que al comienzo dije que una matriz es un vector conformado por otra serie de vectores.

Viéndolo desde el punto de vista de la programación, una matriz es un vector cuyas posiciones (de la cero a la n) son, cada una de ellas, otro vector.

Como siempre, la mejor forma de comprender algo es viendo un ejemplo en acción, así que veamos un buen ejemplo de matrices en C++

Matrices en C++ un buen ejemplo

Muy bien voy a retomar el ejemplo de la sección anterior donde teníamos la necesidad de almacenar los títulos y los autores de una serie de libros dada.

Tal como mencioné en dicha sección, una solución alternativa al problema, además de correcta y adecuada; es crear una matriz que almacenará en la primera columna los títulos, y en la segunda columna los autores; cada fila será entonces la información completa de un libro, se le puede conocer como un registro.

¿Cómo se crea una Matriz en C++?

Declarar una matriz en C++ es muy similar a la de un vector, se deben seguir las mismas normas para declarar una variable pero una vez más con un pequeño cambio en la sintaxis. Primero necesitaremos saber el tipo de los datos que irán al interior de este (números, decimales o cadenas de texto, etc.) necesitamos también, como siempre, un nombre para la matriz y un tamaño máximo tanto para las filas como para las columnas. La sintaxis para declarar una matriz en C++ es la siguiente:

tipoDato nombreMatriz[filas][columnas];

Nota: Recuerda que en C++, no es posible crear de una manera sencilla un vector (y por ende una matriz) capaz de almacenar una cantidad de información indefinida, es necesario ingresar con antelación la cantidad de datos (filas y columnas) que la matriz tendrá.

Tenemos entonces, como podrás ver, que la sintaxis es casi la misma excepto que hemos añadido un par de corchetes "[]" más esta vez y al interior de éstos debemos poner el número de filas y columnas máximas de la matriz, respectivamente. Veamos un ejemplo en el cual pondré la declaración de varias matrices de diferentes tipos y tamaños en C++.

Declaración de una matriz en C++

int myMatriz1[10][5];
float myMatriz2[5][10];
string myMatriz3[15][15];
bool myMatriz4[1000][3];


Línea 1

Esta línea contiene la declaración de una matriz llamada myMatriz1 que tendrá 10 filas y 5 columnas y cada una de las 50 casillas tendrá datos de tipo entero.

Línea 2

Esta línea contiene la declaración de una matriz llamada myMatriz2 que tendrá 5 filas y 10 columnas y cada una de las 50 casillas tendrá datos de tipo flotante.

Línea 3

Esta línea contiene la declaración de una matriz llamada myMatriz3 que tendrá 15 filas y 15 columnas (una matriz cuadrada) y cada una de las 225 casillas tendrá datos de tipo string.

Línea 4

Esta línea contiene la declaración de una matriz llamada myMatriz4 que tendrá 1000 filas (sí, leíste bien) y 3 columnas y cada una de las 3000 casillas (también leíste bien, tres mil casillas) tendrá datos de tipo booleano.

Ya que está claro cómo se declara una matriz, vamos a inicializarla, es decir darle un valor a cada casilla, según su tipo de dato.

¿Cómo inicializar una matriz en C++?

En cuanto tenemos declarado una matriz, es posible asignarle valores a cada una de sus casillas, evidentemente estos valores deben coincidir con el tipo de dato que le asignamos a dicha matriz

Voy a mostrar a continuación formas distintas de inicializar una matriz, todas son validas, ya es cuestión de nuestras necesidades y conocimientos determinar cuál es útil y en qué momento. Veamos entonces:

Forma 1 de declarar una matriz


int myMatriz1[2][2] = {{1,2},{3,4}};

Aquí hemos declarado una matriz de tipo int de dos filas y dos columnas y la hemos inicializado con diferentes valores. El valor inicial corresponde a la casilla 0,0 (fila cero, columna cero) y tiene el valor de 1, en la fila cero columna cero, tenemos el valor de 2, en la fila cero columna uno, y finalmente en la fila uno columna uno el valor de 4. Es importante notar que el primer tanto la fila como la columna comienzan desde cero y no desde uno, por esto la primer casilla corresponde a la fila y columna cero.


Columna 0
Columna 1
Fila 0
1
2
Fila 1
3
4

¡Bien! Ya sabemos cómo declarar una matriz en C++, sin embargo, aún no sabemos cómo acceder a los datos que estas contienen. Veámoslo:

Obtener el valor de una casilla específica
Para acceder al valor de una casilla nuevamente haremos uso de los corchetes, pero esta vez no para declarar tamaños (porque eso ya lo hicimos) sino para indicar posiciones (fila y columna).

int myMatriz1[2][2] = {{1,2},{1,1}}; //Matriz con 4 elementos

int fila1Casilla1 = myMatriz[1][1]; //Para acceder a la casilla 1,1 se usan dichos indices
int primerNumero = myMatriz[0][0]; //La primer casilla siempre será la de la fila 0 columna 0

Como podemos ver, para acceder a un valor específico conociendo el índice de la casilla, solo basta con escribir dicho índice entre los corchetes "[][]", recuerda que el índice comienza desde cero, así por lo tanto en una matriz de vector de 2 por 2 (como el ejemplo), el último elemento está en el índice 1 y el primer elemento en el índice 0.

Recorrer una matriz en C++

Para obtener todos los datos que se encuentran al interior de una matriz, debemos acceder a cada posición y esto se hace fácilmente con dos ciclos for (anidados). La lógica de este procedimiento es la siguiente, el primer ciclo for comenzará desde cero e ira hasta el número de filas, de modo que la variable de control que generalmente llamamos "i", será la que va a ir variando entre cero y el tamaño del array, de esta forma al poner la i al interior de los corchetes, estaremos accediendo al valor de cada fila y el segundo ciclo irá de cero al número de columnas y normalmente se usa la variable llamada "j" para acceder a cada columna, veamos:


Nota: En el siguiente código uso una forma sencilla y rápida de obtener la cantidad o número de filas de una matriz y también cómo obtener el número o cantidad de columnas de una matriz. Ten en cuenta que esto es importante, pues a veces no tenemos la certeza del tamaño de la matriz.

Para reservar memoria se debe saber exactamente el número de bytes que ocupa cualquier estructura de datos. Tal y como se ha comentado con anterioridad, una peculiaridad del lenguaje C es que estos tamaños pueden variar de una plataforma a otra. ¿Cómo sabemos, entonces, cuántos bytes reservar para una tabla de, por ejemplo, 10 enteros? El propio lenguaje ofrece la solución a este problema mediante la función sizeof().

La función recibe como único parámetro o el nombre de una variable, o el nombre de un tipo de datos, y devuelve su tamaño en bytes. De esta forma, sizeof(int) devuelve el número de bytes que se utilizan para almacenar un entero.


// programa matriz_1.cpp

#include <iostream.h>

using namespace std;

int main (int argc, char *argv[])
{
    int edades[3][2] = {{1,2},{9,8},{14,21}};

    int filas = (sizeof(edades)/sizeof(edades[0]));
    int columnas = (sizeof(edades[0])/sizeof(edades[0][0]));

    cout << "tamaño del arreglo edades  -- en columnas es " << sizeof(edades) << " Tamaño del elemento edades [0] es: " << sizeof(edades[0][0]) << " el tamaño total  edades / edades[0] es: " << (sizeof(edades)/sizeof(edades[0])) << "\n";
    cout << "tamaño del elemento edades [0]  -- en filas es " << sizeof(edades[0]) << " tamaño del arreglo edades[0][0] es: " <<  sizeof(edades[0][0]) << " el total  edades [0] / edades[0][0] es: "<<  sizeof(edades[0])/sizeof(edades[0][0]) << "\n ";


    for (int i = 0; i < filas; i++)
    {
        for (int j = 0; j < columnas; j++)
        {
            cout<<edades[i][j]<<endl;
        }
    }
return 0;
}


Vamos a ver de forma resumida en qué consiste y que hace cada una de estas líneas

Línea 1:

  int edades[3][2] = {{1,2},{9,8},{14,21}};

Tenemos en la primera línea la declaración de una matriz que contiene las edades de tres parejas de personas y asignamos cada uno de los valores.

Líneas 2 y 3:

 int filas = (sizeof(edades)/sizeof(edades[0]));
 int columnas = (sizeof(edades[0])/sizeof(edades[0][0]));

En estas líneas, tenemos la declaración del número de filas y columnas de la matriz, que serán el límite del primer y segundo ciclo, respectivamente. Para más detalles de esto, verifica la información sobre el operador sizeof.

cout << "tamaño del arreglo edades  -- en columnas es " << sizeof(edades) << " Tamaño del elemento edades [0] es: " << sizeof(edades[0][0]) << " el tamaño total  edades / edades[0] es: " << (sizeof(edades)/sizeof(edades[0])) << "\n";
    cout << "tamaño del elemento edades [0]  -- en filas es " << sizeof(edades[0]) << " tamaño del arreglo edades[0][0] es: " <<  sizeof(edades[0][0]) << " el total  edades [0] / edades[0][0] es: "<<  sizeof(edades[0])/sizeof(edades[0][0]) << "\n ";

Estas lineas son de informacion es decir presentan los valores que se usaran en el ciclo anidado

El resto de Líneas:

  for (int i = 0; i < filas; i++)
    {
        for (int j = 0; j < columnas; j++)
        {

Aquí, tenemos entonces un ciclo for que comienza en cero y termina en el número de filas y luego tenemos otro ciclo for (anidado) que irá de cero hasta el número de columnas (es importante notar que la condición usada en ambos ciclos es estrictamente menor "<" y no menor o igual "<="), al interior del segundo ciclo, es donde accedemos a cada una de las casillas de la matriz usando los corchetes.

Ultina Línea:

 cout<<edades[i][j]<<endl;

La ultima línea es quizá la más vital aunque sin las demás no tendríamos nada. En esta línea, estamos accediendo a cada una de las casillas de la matriz, fila por fila y columna por columna. Accedemos a cada elemento poniendo entre los corchetes la variable i y j, que son las que están cambiando a medida que los ciclos van "girando", así estaremos accediendo a todos los elementos e imprimiéndolos por pantalla por medio de cout.

Para CodeBlocks

Para NetBeans


Para CLI

Muy bien, llegó el momento de afianzar nuestros conocimientos viendo un ejemplo. Ahora que tenemos claro como declarar un vector en C++, como recorrerlo y como acceder a sus datos, vamos a ver un ejemplo basado en el problema que planteé al inicio de esta sección (el de los libros).

Ejemplo de Matrices en C++

El problema es simple, queremos crear un programa con el cual podamos guardar los títulos y los autores de diferentes libros sin perder ninguno de ellos. El usuario es el encargado de suministrar la información de cada libro. Vamos a suponer que el usuario solo podrá ingresar un máximo de 5 libros, para así tener un tamaño de vector fijo. Veamos entonces cómo se haría esto usando matrices:

// programa matriz_2.cpp
#include <iostream.h>
#include <stdio.h>
#include <string.h>

using namespace std;

int main(int argc, char *argv[])
{
    string libros[5][2];
    cout << "Por favor ingrese la siguiente información de los Libros: \n";
    string titulo ,autor;
    for(int i = 0; i < 5; i++)
    {
        cout << "\n******* Libro " << i + 1 << "********:\n";
        cout << "Titulo: ";
        getline(cin,titulo); // esta linea se uso anteriormente para tomar string con espacios
        cout << "Autor: ";
        getline(cin,autor); // esta linea se uso anteriormente para tomar string con espacios
        libros[i][0] = titulo;
        libros[i][1] = autor;
    }
    cout << "Presentando valores de la matriz de datos " << "\n";
    for(int j=0;j<5;j++)
    {
    cout << " Titulo:  " << libros[j][0] << " Autor: " << libros[j][1] << endl;
    }
    return 0;
}


Para CodeBlocks



Para NetBeans


Para CLI



Notar que en el código anterior, debido a que tenemos la completa certeza de sólo usar dos columnas, no es necesario usar otro ciclo for (de hecho, eso complicaría todo) basta con poner de manera explícita que el valor del título va en la columna cero y el del autor en la columna uno.

En este caso tenemos una matriz con una cantidad de fila que dependen de la cantidad de libros y dos columnas. Para el ejemplo, decidimos tener 5 posibles libros. Donde cada uno tendrá su respectivo título y autor. Así entonces, en la columna cero (0) iría el titulo y en la columna uno (1) el autor.

Por cada libro que queremos agregar, debemos especificar su título y su autor. Por ello, la segunda posición que se specifica en esta matriz de libros siempre será 0 o 1, según sea el caso. Mientras que en la primera posición usamos la variable i del ciclo, que va a variar de 0 a 4, para un total de cinco libros, que serian las filas de la matriz.

Los punteros y elementos dinámicos en C++

Los punteros en C++ (o apuntadores) son quizá uno de los temas que más confusión causan al momento de aprender a programar en C++, sin embargo verás que no es para tanto y que todo depende de dos elementos: el signo & (ampersand) y el * (asterisco) que los explicaré en breve. Durante esta lectura verás entonces que no es para nada difícil hacer y usar punteros y que además son de gran ayuda al momento de necesitar valores y estructuras dinámicas, por ejemplo, para crear un array dinámico, con dinámico me refiero a que su tamaño puede ser establecido en tiempo de ejecución y lo mismo se puede hacer con las matrices (que en realidad son un array multidimensional).

Muy bien para comenzar veamos un pequeño ejemplo y luego su correspondiente explicación. No te preocupes de entender todo con este primer ejemplo, pues se explicara cada componente, su sintaxis y el final, cómo aprovechar dichas funcionalidades para nuestro beneficio junto con algún ejercicio.

Ejemplo de punteros

int variable;  //Creamos un entero

int * apuntador = &variable;  //Creamos una apuntador a la posición en memoria de "variable"
*apuntador = 20;  //Le asignamos un valor a esa posición de memoria.

delete [] apuntador; //Después de operar con punteros es necesario liberar la memoria.
puntero = NULL;


otra forma


char v[10];   // arreglo de 10 caracteres
char *p;       // puntero a un caracter


v[10] tiene 10 elementos de v[0] a v[9]


p=&v[3]      // apunta al cuarto elemento, donde p se definió como un apuntador


& representa o es “la dirección de “

Muy bien, ya hemos creado y usado nuestro primer puntero ¿Notaste el uso del asterisco y del ampersand? espero que sí y además de eso hay otros detalles que debemos considerar, veamos:

Detalles al crear y usar punteros en C++

Ya que sabemos algunos trucos y detalles sobre los apuntadores en C++, vamos a definir formalmente la utilidad del operador & y del asterisco.

Los punteros y el ampersand.

El ampersand es un operador de C++ y es comúnmente utilizado para los punteros. Este operador nos permite obtener la dirección de memoria de una variable cualquiera y es justo esto (la dirección en memoria) lo que utilizan los punteros para referenciar valores.

• Los punteros son direcciones.

• El puntero es una variable que contiene una dirección de memoria.

• El puntero es una variable que apunta a otra.

& : entrega la dirección del operando (es la direccion de memoria no de la variable).

Los apuntadores y el asterisco

El asterisco es, por decirlo de alguna forma, el operador por excelencia de los punteros. Su utilidad radica en que si el valor de dicho apuntador corresponde a una dirección de memoria, el asterisco nos permite resolverla y acceder al valor almacenado allí. Viéndolo desde otro enfoque, un apuntador es únicamente una dirección de memoria (un número) y el asterisco es el que hace la magia de obtener el valor referenciado por dicha dirección.

* : indica que la variable es un puntero

Veamos otro ejemplo con cada elemento detallado paso a paso

Ejemplo de apuntadores

char *apuntador = NULL; //Declaramos un puntero
//Es recomendable inicializar un puntero en null, para detectar errores fácilmente

char letra; //Declaramos una variable primitiva llamada letra

apuntador = &letra; //Asignamos al apuntador la dirección de memoria de la variable primitiva

*apuntador = 'x'; //Modificamos la variable a través del apuntador

cout << letra; //Muestra x por pantalla

En este ejemplo vemos que podemos usar cualquier tipo de dato, que un puntero se puede inicializar independientemente y luego se le puede asignar su referencia correspondiente. Nótese que al asignar (línea 6) no utilizamos el asterisco, pues estamos definiendo la dirección de memoria y no el valor en dicha dirección (recuerda que el * resuelve la dirección de memoria y no es lo que requerimos en esa línea).

Los únicos operadores permitidos con punteros son + y -.
Si por ejemplo un puntero p a entero esta apuntado a la dirección 0xABCDE y se realiza la siguiente operación

p++

quedará apuntando a la dirección ................

También se puede hacer operaciones del tipo p+n, por

ejemplo,

p+9

En este caso apuntará al noveno elemento, tomando como base p.

Ahora que hemos visto los ejemplos y tenemos claro el uso del ampersand y el asterisco podemos entonces realizar algunos ejercicios interesantes.

Ejercicios con punteros en C++

Parámetros por referencia

Usualmente al enviar un parámetro a una función todo lo que se haga con dicho parámetro allí adentro NO tiene efecto por fuera. Por ejemplo si a una función la se le envía una variable cuyo valor es diez y al interior de la función le sumamos un cinco, después de la ejecución de la función el valor de la variable seguirá siendo diez en vez de quince. Lo que pasó al interior de la función se quedó allí. Para solucionar esto, si queremos que el valor cambie definitivamente, usamos punteros para pasar no el valor del parámetro sino una referencia a éste (paso por referencia).

Aqui estamos incluyendo un concepto nuevo que se llama funcion, la funcion es un proceso que podemos usar en repetidas ocaciones, y con el proposito de evitar repetir los mismos pasos dentro del programa principal, este se vuelve muy grade y complicado para darle mantenimiento, si tenemos que modificar algo en el proceso tendriamos que relizarlo muchas veces, ahora bien, si lo definimos una sola ocacion y cuando lo necesitemos que podrian ser en muchas ocaciones dentro del mismo programa, es mas simple modificarlos una sola ocacion que muchas de estas en el programa.

 Veamos:

Ejemplo de un procedimiento (son un conjunto de instrucciones que se ejecutan sin retornar ningún valor, hay quienes dicen que un procedimiento no recibe valores o argumentos)

//programa funcion_1.cpp

#include <iostream.h>
using namespace std;


void funcionlinea() // linea que define la funcion es este caso no recibe datos (void), ni argumentos
                               // notese que no tiene ; al final de la declaracion de la funcion
    {
         int j;
            for (j=1; j<25;j++) // imprime en pantalla simbolos de codigo ascii codigo 42
             cout << char(42);
            cout << "\n";
        return; //regresa donde fue llamada la funcion
   }

int main (int argc, char *argv[])
{

    funcionlinea(); //llama a la funcion
    cout << "Miguel Santos Montoya" << endl;
   funcionlinea(); //llama a la funcion

return 0;
}

Para CodeBlocks:



Para NetBeans



Para CLI




Usando los apuntadores con funciones es como que sigue:

//programa apuntador_1.cpp
#include <iostream.h>
#include <stdio.h>

using namespace std;

// aqui se declara la funcion, tambien se conoce como subrutina

int funcion(int valor) // vea quese declara como tipo entero recibe datos de tipo int (int valores)
{
    valor = valor + 10; //Se le suma 10
    return valor;   //regresa el valor calculado por la funcion al lugar donde fue llamada
}
// termina funcion

// aqui se realiza la asignacion por referencia por punteros es una funcion que se llama desde el programa

int funcionPunteros(int* valor)// recibe el valor numerico (numero) y se asigna a valor son de igual tipo int
{
    *valor = *valor + 10; //Se le suma 10 a la posición en memoria
    return *valor; //regresa el valor por apuntador  desde la funcion que llamada
}

//termina la funcion por punteros

int main(int argc, char* argv[])
{
    int numero = 10;

    cout << "Antes de funcion " << numero << "\n"; // el valor es 10

    funcion(numero); //Se pasa por valor, llama a la funcion declarada arriba

    cout << "Despues de funcion " << numero << "\n"; //10

    cout << "Antes de funcionPunteros " << numero << "\n"; //10

    funcionPunteros(&numero); //Se envía la dirección de memoria y la función resuelve la referencia
    /// la funcion regresa "valor" a "numero" con su nuevo cambio  de 10 a 20
    cout << "Despues de funcionPunteros " << numero << "\n"; //20 (10+10)
 
    return 0;
}

Para CodeBlock



Para NetBeans



Para CLI





Como podrás comprobar si ejecutas el código del ejercicio al llamar a "funcion" sólo enviamos el valor y por ende éste no es modificado por fuera de la función (esto se conoce como ambito de accion), con "funcionPunteros" estamos manipulando la posición en memoria del parámetro recibido (por eso usamos el *) y  al ejecutarla el valor de la variable se modifica. De ese modo ya hicimos el primer ejercicio con punteros en C++ y ya comprendemos el paso por referencia.

Array dinámico

Como se menciono al comienzo, por medio de apuntadores podemos crear arreglos o vectores dinámicos, es decir, un array al cual se le define su tamaño o capacidad durante la ejecución del código y no antes, lo cual nos permite definirle el tamaño deseado por el usuario. 

Para este ejercicio retomaré el ejemplo del artículo de arreglos o vectores: Queremos crear un programa con el cual podamos guardar los títulos y los autores de diferentes libros sin perder ninguno de ellos. El usuario es el encargado de suministrar la información de cada libro. En esta ocasión ya sabemos usar punteros, así que será también el usuario quien nos diga cuántos libros desea ingresar, ya no necesitamos suponer que sólo ingresará 5 libros. Veamos:

// programa apuntador_2.cpp

#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // aqui esta la funcion atoi

using namespace std;

int main(int argc, char* argv[])
{
    string* titulos = NULL; //Se inicializa el puntero (inicia en null)
    string* autores = NULL; //Se inicializa el puntero (inicia en null)

    int tam ; //Se inicializa la variable

    cout << "Cuantos libros desea ingresar? ";

    string entrada;

    getline(cin, entrada); //Se asigna el valor ingresado

    tam = atoi(entrada.c_str()); // atoi convierte de ascii a entero, se transforma la entrada en número ascii to integer

    //Declaramos un arreglo del tamaño ingresado para los titulos

    titulos = new string[tam]; //reserva memoria para el registro

    //Declaramos un arreglo del tamaño ingresado para los autores
    autores = new string[tam]; //reserva memoria para el registro

    cout << "Por favor ingrese la siguiente información de los Libros: \n";
    for(int i = 0; i < tam; i++)
    {
        cout << "\n******* Libro " << i + 1 << "********:\n";
        cout << "Titulo: ";
        //cin >> titulos[i]; //No funciona con espacios
        getline(cin, titulos[i]);
        cout << "Autor: ";
        //cin >> autores[i]; //No funciona con espacios
        getline(cin, autores[i]);
    }

    //Liberamos la memoria de ambos punteros
    delete [] titulos;
    delete [] autores;
    titulos = NULL;
    autores = NULL;

   return 0;
}


Para CodeBlocks



Para NetBeans



Para CLI




c_str devuelve una const char* que apunta a una cadena terminada en cero (es decir, una cadena de estilo C). Es útil cuando se quiere pasar el "contenido" de una std::string a una función que espera trabajar con una cadena estilo C.

Tenemos un nuevo elemento en el programa new():

Asignacion dinamica de memoria al estilo de C++

Para asignar memoria dinamicamente en C++ se utilizan los operadores new y delete. El operador new reserva memoria para un tipo de datos específico y retorna su dirección, o retorna NULL en caso de no haber conseguido suficiente memoria; y el operador delete permite liberar la memoria reservada a través de un apuntador. La sintaxis de ambos operadores es como sigue:

Para reservar y liberar un solo bloque:

<apuntador> = new <tipo de dato>

delete <apuntador>

Para reservar y liberar varios bloques (un arreglo):

<apuntador> = new <tipo de dato>[<numero de bloques>]

delete [] <apuntador>


El tipo del apuntador especificado del lado izquierdo del operador new debe coincidir con el tipo especificado del lado derecho. De no ser así, se produce un error de compilación.

Así entonces tuvimos dos punteros, uno para todos los autores y otro para todos los títulos. Haciendo uso de ellos pudimos definir la cantidad de libros a ingresar por medio del usuario, es decir lo hicimos de manera dinámica, en tiempo de ejecución.

Matrices dinámicas

Así como lo hicimos con los array, también podemos tener matrices dinámicas y definir su tamaño, número de filas o número de columnas (o las dos) según sea necesario.

Para esto tomaré el mismo ejemplo de los libros, pero usando una matriz, en vez de dos vectores, tal y como se solucionó en la sección de matrices veamos:

//programa apuntador_3.cpp

#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // aqui esta la funcion atoi

using namespace std;

int main(int argc, char* argv[])

{
    int cols = 2; //El número de columnas es fijo (sólo título y autor)

    string** libros; //Si inicializa la matriz (punteros de punteros)

    int tam ; //Se inicializa la variable

    cout << "Cuantos libros desea ingresar? ";

    string entrada;

    getline(cin, entrada); // Se asigna el valor ingresado

    tam = atoi(entrada.c_str()); //Se transforma la entrada en número

    libros = new string*[tam];//Se asigna el número de filas según el usuario

    cout << "Por favor ingrese la siguiente información de los Libros: \n ";
    string titulo ,autor;
    for(int i = 0; i < tam; i++)
    {
        libros[i] = new string[cols]; //Cada fila contendrá dos columnas
        //Notar que cols pudo haber sido ingresada por el usuario también

        cout << "\n******* Libro " << i + 1 << "********:\n";
        cout << "Titulo: ";
        getline(cin,titulo);
        cout << "Autor: ";
        getline(cin,autor);
        libros[i][0] = titulo;
        libros[i][1] = autor;
    }

    //Para liberar la memoria debemos recorrer fila por fila primero.
    for (int i = 0; i < tam; ++i)
    {
        delete [] libros[i]; //Cada fila de libros es otro array de punteros
        //Por eso son punteros a punteros
    }

    //Luego de limpiar las columnas, quitamos la fila única que quedó
    delete [] libros;

    return 0;
}

Para CodeBlocks



Para NetBeans



Para CLI



Este ejercicio es el perfecto para aclarar dudas o darse cuenta si realmente comprendes el concepto de apuntadores y su aplicación para arrays dinámicos. Debido a que la cantidad de columnas es fija, no se lo pedimos al usuario, simplemente lo declaramos con valor dos. Luego tenemos el puntero, pero no es un puntero cualquiera, al ser una matriz, será un puntero que tendrá otros punteros adentro, por eso se usa doble asterisco, luego se obtiene el tamaño del usuario (cantidad de libros) y al momento de inicializar la fila estamos indicando que es un arreglo de punteros, por eso se usa el * en la línea 23. Luego al interior del ciclo, cuando estamos llenando la matriz, debemos indicar que cada fila estará compuesta por un array de punteros de tamaño dos (dos columnas) y así construimos nuestra matriz dinámica.

Debes notar también que la liberación de la memoria es un poco más latosa, pues debemos ir fila por fila liberando la memoria de las columnas creadas y luego liberar la fila completa. Ahí podrás notar la diferencia en eficiencia y uso de memoria al usar arreglos o usar matrices.

Nota: La instrucción new utilizada con los strings es necesaria para asignar el espacio en memoria para esa colección de elementos. No te preocupes por ello de momento, solo asegúrate de usarla y ya el lenguaje se hará cargo del resto.

Ahora podemos aprender acerca de Funciones en C++

Aneriormente ya habiamos usado las funciones (procedimientos, subrutinas) ahora las revisaremos a mas detalle.

Las funciones son una herramienta indispensable para el programador, tanto las funciones creadas por él mismo como las que le son proporcionadas por otras librerías, cualquiera que sea el caso, las funciones permiten automatizar tareas repetitivas, encapsular el código que utilizamos, e incluso mejorar la seguridad, confiabilidad y estabilidad de nuestros programas. Dominar el uso de funciones es de gran importancia, permiten modularizar nuestro código, separarlo según las tareas que requerimos, por ejemplo una función para abrir, otra para cerrar, otra para actualizar, etc. básicamente una función en nuestro código debe contener la implementación de una utilidad de nuestra aplicación, es decir que por cada utilidad básica (abrir, cerrar, cargar, mover, etc.) sería adecuado tener al menos una función asociada a ésta.

¿Funciones, métodos o procedimientos?

En el mundo de la programación, muchos acostumbramos hablar indistintamente de estos tres términos sin embargo poseen deferencias fundamentales.

Funciones:

Las funciones son un conjunto de procedimiento encapsulados en un bloque, usualmente reciben parámetros, cuyos valores utilizan para efectuar operaciones y adicionalmente retornan un valor. Esta definición proviene de la definición de función matemática la cual posee un dominio y un rango, es decir un conjunto de valores que puede tomar y un conjunto de valores que puede retornar luego de cualquier operación.

Métodos:

Los métodos y las funciones son funcionalmente idénticos, pero su diferencia radica en el contexto en el que existen. Un método también puede recibir valores, efectuar operaciones con estos y retornar valores, sin embargo en método está asociado a un objeto, básicamente un método es una función que pertenece a un objeto o clase, mientras que una función existe por sí sola, sin necesidad de un objeto para ser usada.

Procedimientos:

Los procedimientos son básicamente son un conjunto de instrucciones que se ejecutan sin retornar ningún valor, hay quienes dicen que un procedimiento no recibe valores o argumentos, sin embargo en la definición no hay nada que se lo impida. En el contexto de C++ un procedimiento es básicamente una función void que no nos obliga a utilizar una sentencia return. Hablaremos sobre procedimientos y funciones, los métodos son parte de un tema diferente.

Declarando funciones en C++

La sintaxis para declarar una función es muy simple:

tipo nombreFuncion([tipo nombreArgumento,[tipo nombreArgumento]...])
{
    /*
        * Bloque de instrucciones
    */

    return valor;
}

Recordemos que una función siempre retorna algo, por lo tanto es obligatorio declararle un tipo (el primer componente de la sintaxis anterior), luego debemos darle un nombre a dicha función, para poder identificarla y llamarla durante la ejecución, después al interior de paréntesis, podemos poner los argumentos o parámetros. Luego de la definición de la "firma" de la función, se define su funcionamiento entre llaves; todo lo que esté dentro de las llaves es parte del cuerpo de la función y éste se ejecuta hasta llegar a la instrucción return.

Acerca de los argumentos o parámetros

Hay algunos detalles respecto a los argumentos de una función:
Una función o procedimiento pueden tener una cantidad cualquier de parámetros, es decir pueden tener cero, uno, tres, diez, cien o más parámetros. Aunque habitualmente no suelen tener más de 4 o 5.

Si una función tiene más de un parámetro cada uno de ellos debe ir separado por una coma.

Los argumentos de una función también tienen un tipo y un nombre que los identifica. El tipo del argumento puede ser cualquiera y no tiene relación con el tipo de la función.

Consejos acerca de return

Debes tener en cuenta dos cosas importantes con la sentencia return:

Cualquier instrucción que se encuentre después de la ejecución de return NO será ejecutada. Es común encontrar funciones con múltiples sentencias return al interior de condicionales, pero una vez que el código ejecuta una sentencia return lo que haya de allí hacia abajo no se ejecutará.

El tipo del valor que se retorna en una función debe coincidir con el del tipo declarado a la función, es decir si se declara int, el valor retornado debe ser un número entero.

Ejemplos de funciones

Veamos algunos ejemplos prácticos de funciones en C++.

Ejemplo 1:

int funcionEntera()//Función sin parámetros
{
    int suma = 5+5;
    return suma; //Acá termina la ejecución de la función
    return 5+5;//Este return nunca se ejecutará
    //Intenta intercambiar la línea 3 con la 5
    int x = 10; //Esta línea nunca se ejecutará
}

Como puedes ver es un ejemplo sencillo, si ejecutas esto, la función te retornará el valor de suma que es 10 (5+5). Las líneas posteriores no se ejecutarán nunca, aunque no generan error alguno, no tienen utilidad. Puedes notar que para este caso es lo mismo haber escrito return suma que escribir return 5+5. Ambas líneas funcionan equivalentemente.

Ejemplo 2:


char funcionChar(int n)//Función con un parámetro
{
    if(n == 0)//Usamos el parámetro en la función
    {
        return 'a'; //Si n es cero retorna a
        //Notar que de aquí para abajo no se ejecuta nada más
    }
    return 'x';//Este return sólo se ejecuta cuando n NO es cero
}

Aquí hicimos uso se múltiples sentencia return y aprovechamos la característica de que al ser ejecutadas finalizan inmediatamente la ejecución de la parte restante de la función. De este modo podemos asegurar que la función retornará 'a' únicamente cuando el valor del parámetro n sea cero y retornará un 'x' cuando dicho valor no sea cero.

Ejemplo 3:


bool funcionBool(int n, string mensaje)//Función con dos parámetros
{
    if(n == 0)//Usamos el parámetro en la función
    {
        cout << mensaje;//Mostramos el mensaje
        return 1; //Si n es cero retorna 1
        return true;//Equivalente
    }
    return 0;//Este return sólo se ejecuta cuando n NO es cero
    return false;//Equivalente
}


Aquí ya tenemos una función que recibe dos parámetros, uno de ellos es usado en el condicional y el otro para mostrar su valor por pantalla con cout, esta vez retornamos valores booleanos 0 y 1, pudo ser true o false también.

Hablemos un poco de los procedimientos

Los procedimientos son similares a las funciones, aunque más resumidos. Debido a que los procedimientos no retornan valores, no hacen uso de la sentencia return para devolver valores y no tienen tipo específico, solo void. Veamos un ejemplo:

Ejemplo de procedimientos

void procedimiento(int n, string nombre) // no tiene ; al final
{
    if(n == 0)
    {
        cout << "hola" << nombre;
        return;
    }
    cout << "adios" << nombre;
}

De este ejemplo podemos ver que ya no se usa un tipo sino que se pone void, indicando que no retorna valores, también podemos ver que un procedimiento también puede recibir parámetros o argumentos.

Atención: Los procedimientos también pueden usar la sentencia return, pero no con un valor. En los procedimientos el return sólo se utiliza para finalizar allí la ejecución de la función.

Invocando funciones y procedimientos en C++

Ya hemos visto cómo se crean y cómo se ejecutan las funciones en C++, ahora veamos cómo hacemos uso de ellas.

nombreFuncion([valor,[valor]...]); // aqui si lleva ;

Como puedes notar es bastante sencillo invocar o llamar funciones en C++ (de hecho en cualquier lenguaje actual), sólo necesitas el nombre de la función y enviarle el valor de los parámetros. Hay que hacer algunas salvedades respecto a esto.

Detalles para invocar funciones

El nombre de la función debe coincidir exactamente al momento de invocarla.

El orden de los parámetros y el tipo debe coincidir. Hay que ser cuidadosos al momento de enviar los parámetros, debemos hacerlo en el mismo orden en el que fueron declarados y deben ser del mismo tipo (número, texto u otros).

Cada parámetro enviado también va separado por comas.

Si una función no recibe parámetros, simplemente no ponemos nada al interior de los paréntesis, pero SIEMPRE debemos poner los paréntesis.

Invocar una función sigue siendo una sentencia habitual de C++, así que ésta debe finalizar con ';' como siempre.

El valor retornado por una función puede ser asignado a una variable del mismo tipo.

Una función puede llamar a otra dentro de sí misma o incluso puede ser enviada como parámetro a otra.

Ejemplos de uso de funciones

En el siguiente código vamos a hacer un llamado a algunas de las funciones y al procedimiento, que declaramos anteriormente.

int main()
{
    funcionEntera(); //Llamando a una función sin argumentos

    bool respuesta = funcionBool(1, "hola"); //Asignando el valor retornado a una variable

    procedimiento(0, "Juan");//Invocando el procedimiento

    //Usando una función como parámetro
    procedimiento(funcionBool(1, "hola"), "Juan");

    return 0;
}

En el código anterior podemos ver cómo todas las funciones han sido invocadas al interior de la función main (la función principal), esto nos demuestra que podemos hacer uso de funciones al interior de otras. También vemos cómo se asigna el valor retornado por la función a la variable 'respuesta' y finalmente, antes del return, vemos cómo hemos usado el valor retornado por 'funcionBool' como parámetro del procedimiento.

Ejemplos de funciones

// programa funcion_2
#include <iostream.h>

using namespace std;

// Declaracion de la funcion
int ValorMaximo(int x, int y) //recibe de xx y yy en ese orden
{
    int NumMax;  //declara valor en esta funcion solo en este ambito funciona
    if (x>y)
      {
           NumMax = x;
      }
else
      {
           NumMax = y;
      }
return NumMax; //retorna el valor de la variable a la funcion
}

int xx,yy;
int num;

int main (int argc, char* argv[])
{
      cout << "Ingresa el primer numero entero :";
      cin >> xx;
      cout << "Ingresa el segundo numero entero :";
       cin >> yy;
       cout << endl;
       num = ValorMaximo(xx,yy); //xx y yy ya estan declaradas como int, y se envian a la funcion se reciben como x y
                                                    //retorna a la variable num
      cout << "El numero maximo es : "<< num << " usando el valor de la variable "<< endl;
       // el resultado de la funcion (retorno) es asignado a num

      cout << "El numero maximo es : " << ValorMaximo(xx,yy)<< " usando la misma funcion " << endl;
      // el resultado de la funcion se muestra directamente usando la llamada a la función, sin asignar a una variable

return 0;
}

Para CodeBlocks

Para NetBeans


Para CLI




Plantillas (templates) en C++

Estudiaremos una característica genial de C++, se trata de las plantillas o templates.

Una plantilla es una manera especial de escribir funciones y clases para que estas puedan ser usadas con cualquier tipo de dato, similar a la sobrecarga (C++ permite especificar más de una función del mismo nombre en el mismo ámbito), en el caso de las funciones, pero evitando el trabajo de escribir cada versión de la función. Las ventajas son mayores en el caso de las clases, ya que no se permite hacer sobrecarga de ellas y tendríamos que decidirnos por una sola o hacer especializaciones usando la herencia.

¿Cómo funcionan?

La magia de las plantillas está en no definir un tipo de dato desde el principio, sino dejar esto como algo pendiente y usar algo que permita manejar varias opciones, de hecho se usa una variable para este propósito. Veamos la sintaxis para el caso de las funciones:

// Para una función, ambas opciones son equivalentes
template <class identificador> definición_de_función;
template <typename identificador> definición_de_función;

El identificador es  el símbolo que guarda el tipo de dato que se ha de usar una vez elegido, por lo que en la definición de la función deberá utilizarse en lugar de los nombres de los tipos de datos, de esta manera queda hecha una función genérica a la cual podemos llamar función-plantilla.

Tal vez con un ejemplo esta idea quede más clara. Pensemos en una función que nos retorne el mayor de dos datos que le demos como argumentos y que sirva con cualquier tipo:

template <class tipo>
tipo mayor(tipo dato1, tipo dato2){
  return (dato1 > dato2 ? dato1 : dato2);
}

El identificador que usamos es “tipo”, por eso el tipo de dato de retorno y el de los parámetros debe ser ese identificador. Durante la compilación del programa, en la invocación a la función, se resuelve el tipo que se usará y el compilador escribirá, con base en la plantilla, una función que sirva con el tipo de dato resuelto y será la que realmente se utilice para dicha invocación.

Probemos nuestra función-plantilla y veamos lo que pasa:

// programa funcion_3
#include <iostream.h>

using namespace std;

// Declaracion de la funcion plantilla
template <class TIPO>
void ValorAbsoluto(TIPO numero)
{

    if (numero < 0)
        {numero = numero * -1;}
        cout << "El valor absoluto del numero es :" << numero << endl;

}


int num1;
float num2;
double num3;

int main (int argc, char*argv[])
{
      cout << "Ingresa el numero entero positivo o negativo : ";
      cin >> num1;
      cout << endl;
      ValorAbsoluto(num1);

      cout << "Ingresa el  numero flotante positivo o negativo :";
      cin >> num2;
      cout << endl;
      ValorAbsoluto(num2);

      cout << "Ingresa el numero double positivo o negativo :";
      cin >> num3;
      cout << endl;
      ValorAbsoluto(num3);

return 0;

}

Para CodeBlocks



Para NetBeans


Para CLI



Ahora probemos esta modificación el programa anterior:


// programa funcion_3.cpp
// Autor:
// Fecha:
#include "iostream"

using namespace std;
template <class RECIBI>
 
void Valor(RECIBI numero)
{
 if(numero>100)
   //{numero=numero *-1;}
 { cout <<"El valor RECIBIDO ES mayor que 100 y es :"<<numero<<endl;}
 else
  { cout <<"El valor RECIBIDO ES menor que 100 y es :"<<numero<<endl;}
}

int num1;
int num2;
float num3;
char nom;

int main(int argc,char* argv[])
{
  cout <<"Ingresa el entero  numero positivo o negativo: ";
  cin >> num1;
  cout << endl;
  Valor(num1);

  cout <<"Ingresa el entero numero positivo o negativo: ";
  cin >> num2;
  cout << endl;
  Valor(num2);

  cout <<"Ingresa el flotante numero positivo o negativo: ";
  cin >>num3;
  cout << endl;
  Valor(num3);

  cout <<"Ingresa el texto ";
  cin >>nom;
  cout << endl;
  Valor(nom);

 return 0;

}


Compilalo y ejecutalo en la ultima solicitud de datos agrega una letra o simbolo ASCII, y comenta el resultado y presenta tus conclusiones

Paso de parámetros en C++, funciones

El paso de parámetros en C++ se puede hacer de dos formas:

1. Paso de parámetros por valor.

2. Paso de parámetros por referencia.

Paso de parámetros por valor.

Pasar parámetros por valor significa que a la función se le pasa una copia del valor que contiene el parámetro actual.

Los valores de los parámetros de la llamada se copian en los parámetros de la cabecera de la función. La función trabaja con una copia de los valores por lo que cualquier modificación en estos valores no afecta al valor de las variables utilizadas en la llamada.

Aunque los parámetros actuales (los que aparecen en la llamada a la función) y los parámetros formales (los que aparecen en la cabecera de la función) tengan el mismo nombre son variables distintas que ocupan posiciones distintas de memoria.

Por defecto, todos los argumentos salvo los arrays se pasan por valor.

Ejemplo de paso de parámetros por valor

El siguiente programa lee un número entero y llama a una función que invierte las cifras del número:

 // programa funcion_4 Ejemplo de paso de parámetros por valor

#include <iostream>

using namespace std;

int invertir(int num) //función que recibe por valor
{
    int inverso = 0, cifra;
    while (num != 0)
    {
        cifra = num % 10; //modulo residuo
        inverso = inverso * 10 + cifra;
        num = num / 10;
    }
    return inverso;
}

int main(int argc, char* argv[])
{
    int num;
    int resultado;

    cout << "Introduce un numero entero: ";
    cin >> num;

    resultado = invertir(num); //llamado a la funcion por valor (num)

    cout << "Numero introducido: " << num << endl;
    cout << "Numero con las cifras invertidas: " << resultado << endl;
return 0;
}


Para CodeBlocks



Para NetBeans



Para CLI



En la llamada a la función el valor de la variable num se copia en la variable num de la cabecera de la función. Aunque tengan el mismo nombre, se trata de dos variables distintas.

Dentro de la función se modifica el valor de num, pero esto no afecta al valor de num en main. Por eso, al mostrar en main el valor de num después de la llamada aparece el valor original que se ha leído por teclado.

Paso de parámetros por referencia.

El paso de parámetros por referencia permite que la función pueda modificar el valor del parámetro recibido.

Vamos a explicar dos formas de pasar los parámetros por referencia:

1 Paso de parámetros por referencia basado en punteros al estilo C.
2 Paso de parámetros por referencia usando referencias al estilo C++.

1 Paso de parámetros por referencia utilizando punteros.

Cuando se pasan parámetros por referencia, se le envía a la función la dirección de memoria del parámetro actual y no su valor. La función realmente está trabajando con el dato original y cualquier modificación del valor que se realice dentro de la función se estará realizando con el parámetro actual.


Para recibir la dirección del parámetro actual, el parámetro formal debe ser un puntero.

Ejemplos de paso de parámetros por referencia
Ejemplo 1:

Programa c++ que lee dos números por teclado y los envía a una función para que intercambie sus valores.

//programa funcion_5.cpp Ejemplo de paso de parámetros por referencia
#include <iostream.h>

using namespace std;

void intercambio(int *x, int *y) //declaración de la función de intercambio, usando por referencia
{
    int z;                     
    z = *x;
    *x = *y;
    *y = z;
}

int main(int argc, char* argv[] )
{
    int a, b;

    cout << "Introduce primer numero: ";
    cin >> a;
    cout << "Introduce segundo numero: ";
    cin >> b;
    cout << endl;

    cout << "valor de a: " << a << " valor de b: " << b << endl;

    intercambio(&a, &b);

    cout << endl << "Despues del intercambio: " << endl << endl;
    cout << "valor de a: " << a << " valor de b: " << b << endl;
  
}

Para CodeBlocks



Para NetBeans



Para CLI




En la llamada, a la función se le envía la dirección de los parámetros. El operador que obtiene la dirección de una variable es &.

intercambio(&a, &b);

En la cabecera de la función, los parámetros formales que reciben las direcciones deben ser punteros. Esto se indica mediante el operador *

void intercambio(int *x, int *y)

Los punteros x e y reciben las direcciones de memoria de las variables a y b. Al modificar el contenido de las direcciones x e y, indirectamente estamos modificando los valores a y b. Por tanto, pasar parámetros por referencia a una función es hacer que la función acceda indirectamente a las variables pasadas.

Ejemplo 2:

En el siguiente ejemplo, se lee una hora (hora, minutos y segundos) y se calcula la hora un segundo después. El programa utiliza una función segundo_despues que recibe la hora, minutos y segundos leídos y los modifica de forma que al finalizar contienen la hora un segundo después.

//programa funcion_6.cpp Ejemplo de paso de parámetros por referencia con punteros
#include <iostream.h>
#include <iomanip>//esta libreria debe ser sin .h, si no existe podemos agregarla * Nota 3 final de pagina

using namespace std;

//function c++ que recibe una hora expresada en
//horas, minutos y segundos y calcula la hora
//un segundo después

void segundo_despues(int *h, int *m, int *s) //declaracion de la funcion pasa por referencia
                                                                      // es una funcion void
{
     (*s)++;
     if(*s == 60)
     {
            *s = 0;
            (*m)++;
            if(*m == 60)
                {
                    *m = 0;
                    (*h)++;
                    if(*h == 24)
                        {
                            *h = 0;
                        }
                }
    }
}


int main(int argc, char* argv[])
{
  int horas, minutos, segundos;
    do
    {
        cout << "Introduce hora: ";
        cin >> horas;

    }while(horas<0 || horas > 23);
    do
    {
        cout << "Introduce minutos: ";
        cin >> minutos;
    }while(minutos<0 || minutos > 59);
    do
    {
        cout << "Introduce segundos: ";
        cin >> segundos;
    }while(segundos<0 || segundos > 59);

    //llamada a la función
    segundo_despues(&horas, &minutos, &segundos);

    cout << setfill('0'); //se utiliza para establecer el carácter de relleno de la biblioteca en función del carácter especificado como parámetro de este método.
    cout << endl << "Hora un segundo despues: ";
    cout << setw(2) << horas << ":"; // setw() Modifica la anchura de campo únicamente para la siguiente entrada o salida
    cout << setw(2) << minutos << ":";   //establece la anchura de campo a 2 antes de mostrar cada número
    cout << setw(2) << segundos << endl;
return 0;
}

Para CodeBlocks


Para NetBeans



Para CLI



Esta función no devuelve nada (aparece void como tipo devuelto), por lo que no es necesario poner la instrucción return.

Mediante return una función solo puede devolver un valor. En casos como la función anterior en los que es necesario devolver más de un valor, habrá que hacerlo pasando los parámetros por referencia.

Este paso de parámetros por referencia basado en punteros es el utilizado por C y por tanto también puede usarse en C++. Pero C++ incorpora una nueva forma de paso de parámetros que no hace uso de punteros.

Es el paso de parámetros utilizando referencias.

Paso de parámetros utilizando referencias.

Una referencia es un nombre alternativo (un alias, un sinónimo) para un objeto. Una referencia no es una copia de la variable referenciada, sino que es la misma variable con un nombre diferente.

Utilizando referencias, las funciones trabajan con la misma variable utilizada en la llamada. Si se modifican los valores en la función, realmente se están modificando los valores de la variable original.

Ejemplo:

El primer ejemplo del punto anterior utilizando referencias lo podemos escribir así:

// programa funcion_7.cpp ejemplo de paso de variables por referencia c++
#include <iostream>

using namespace std;

void intercambio(int &, int &);  //declaración de la función este tiene ; al final

void intercambio(int &x, int &y) //definicion de la funcion
{
    int z;                     
    z = x;
    x = y;
    y = z;
}

int main( int argc, char* argv[])
{  
    int a, b;

    cout << "Introduce primer numero: ";
    cin >> a;
    cout << "Introduce segundo numero: ";
    cin >> b;
    cout << endl;

    cout << "valor de a: " << a << " valor de b: " << b << endl;

    intercambio(a, b); // llamada a la funcion

    cout << endl << "Despues del intercambio: " << endl << endl;
    cout << "valor de a: " << a << " valor de b: " << b << endl;

    return 0;
}


Para CodeBlock



Para NetBeans



Para CLI


En la declaración de la función y en la definición se coloca el operador referencia a & en aquellos parámetros formales que son referencias de los parámetros actuales:

void intercambio(int &, int &);  //declaración de la función

void intercambio(int &x, int &y)  //definición de la función

Cuando se llama a la función:  

intercambio(a, b);

se crean dos referencias (x e y) a las variables a y b de la llamada. Lo que se haga dentro de la función con x e y se está haciendo realmente con a y b.

La llamada a una función usando referencias es idéntica a la llamada por valor.

Ejemplo:

El segundo ejemplo anterior, utilizando referencias:

// programa funcion_8.cpp paso por referencias
#include <iostream>
#include <iomanip>

using namespace std;

void segundo_despues(int &, int &, int &); // declaracion de la funcion

void segundo_despues(int &h, int &m, int &s) // definicion de la funcion
{
     s++;
     if(s == 60)
     {
            s = 0;
            m++;
            if(m == 60)
                {
                    m = 0;
                    h++;
                    if(h == 24)
                        {
                            h = 0;
                        }
                }
    }
}



int main(int argc, char* argv[])
{  

    int horas, minutos, segundos;
    do
    {
        cout << "Introduce hora: ";
        cin >> horas;
    }while(horas<0 || horas > 23);
    do
    {
        cout << "Introduce minutos: ";
        cin >> minutos;
    }while(minutos<0 || minutos > 59);
    do
    {
        cout << "Introduce segundos: ";
        cin >> segundos;
    }while(segundos<0 || segundos > 59);

    segundo_despues(horas, minutos, segundos);

    cout << setfill('0');
    cout << endl << "Hora un segundo despues: ";
    cout << setw(2) << horas << ":";
    cout << setw(2) << minutos << ":";
    cout << setw(2) << segundos << endl;

 return 0;
}


Para CodeBlocks



Para NetBeans



Para CLI


ESTRUCTURAS


Las estructuras nos permiten agrupar varios datos, aunque sean de distinto tipo, que mantengan algún tipo de relación, y manipularlos todos juntos, con un mismo identificador, o por separado.


Las estructuras son llamadas también muy a menudo registros, o en inglés "records".


Y son estructuras análogas en muchos aspectos a los registros de bases de datos. Y siguiendo la misma analogía, cada variable de una estructura se denomina a menudo campo, o "field".

Sintaxis:

struct [<nombre de la estructura>] {
[<tipo> <nombre de variable>[,<nombre de variable>,...]];
.
} [<variable de estructura>[,<variable de estructura>,...];

El nombre struct es un nombre opcional para referirse a la estructura.


Las variables de estructura son variables declaradas del tipo de la estructura, y su inclusión también es opcional. Sin bien, al menos uno de estos elementos debe  existir, aunque ambos sean opcionales.


En el interior de una estructura, entre las llaves, se pueden definir todos los elementos que consideremos necesarios, del mismo modo que se declaran las variables.

Las estructuras pueden reverenciarse completas, usando su nombre, como hacemos con las variables que ya conocemos, y también se puede acceder a los elementos en el interior de la estructura usando el operador de selección (.), un punto.

También pueden declararse más variables del tipo de estructura en cualquier parte del programa, de la siguiente forma:


[struct] <nombre de la estructura> <variable de estructura>[,
<variable de estructura>...];


Ejemplo:

struct Persona {
char Nombre[65];
char Direccion[65];
int AnyoNacimiento;
} Miguel;

Este ejemplo declara a Miguel como una variable de tipo Persona. Para acceder al nombre de Fulanito, por ejemplo para visualizarlo, usaremos la forma:

cout << Miguel.Nombre;

C++,  permite incluir funciones en el interior de las estructuras.


Normalmente estas funciones tienen la misión de manipular los datos incluidos en la estructura.

Aunque esta característica se usa casi exclusivamente con las clases, como veremos más adelante, también puede usarse en las estructuras.

Dos funciones muy particulares son las de inicialización, o constructor, y el destructor. Veremos con más detalle estas funciones cuando asociemos las estructuras y los punteros.

El constructor es una función sin tipo de retorno y con el mismo nombre que la estructura. El destructor tiene la misma forma, salvo que el nombre va precedido el operador "~".

Veamos un ejemplo sencillo para ilustrar el uso de constructores:

Forma 1:

struct Punto {
int x, y;
Punto() {x = 0; y = 0;} // Constructor
} Punto1, Punto2;

Forma 2:

struct Punto {
int x, y;
Punto(); // Declaración del constructor
} Punto1, Punto2;
Punto::Punto() {
estructura
x = 0;
y = 0;
}


Si no usáramos un constructor (inicializacion), los valores de x e y para Punto1 y Punto2 estarían indeterminados, contendrían la "basura" que hubiese en la memoria asignada a estas estructuras durante la ejecución. Con las estructuras éste será el caso más habitual.

Mencionar aquí, sólo a título de información, que el constructor no tiene por qué ser único. Se pueden incluir varios constructores, pero veremos esto mucho mejor y con más detalle cuando veamos las clases.

Usando constructores nos aseguramos los valores iniciales para los elementos de la estructura. Veremos que esto puede ser una gran ventaja, sobre todo cuando combinemos estructuras con punteros, en capítulos posteriores.


También podemos incluir otras funciones, que se declaran y definen como las funciones que ya conocemos, salvo que tienen restringido su ámbito al interior de la estructura.

Para acceder a cada uno de los datos que forman el registro, tanto si queremos leer su valor como si queremos cambiarlo, se debe indicar el nombre de la variable y el del dato (o campo) separados por un punto.

El siguiente programa usa struct en su forma mas simple, por el momento no usaremos constructores estructura_1.cpp

// programa estructura_1.cpp
#include <iostream.h>
#include <stdio.h>


using namespace std;


int main(int argc, char* argv[])
{

struct
    {
        string nombre;
        char  inicial;
        int   edad;
        float dia_nac;
        float mes_nac;
        float annio_nac;

    } persona1;

    cout << "Nombre :";
    getline(cin, persona1.nombre);
    cout << endl;
    cout << "Inicial de tu nombre:";
    cin >> persona1.inicial;
    cout << endl;
    cout << "Edad :";
    cin >>  persona1.edad;
    cout << endl;
    cout << "Dia de nacimiento ";
    cin >> persona1.dia_nac;
    cout << endl;
    cout << "Mes de Nacimiento ";
    cin >> persona1.mes_nac;
    cout << endl;
    cout << "Año de nacimiento ";
    cin >> persona1.annio_nac;
    cout << endl;

    cout << "Extraemos de struct \"registro\" los datos ";
    cout << endl;

    cout << "El nombre es :" << persona1.nombre << endl;
    cout << "La inicial del Nombres es : " << persona1.inicial<<endl;
    cout << "La edad es " << persona1.edad<< endl;
    cout << "Dia de nacimiento "<< persona1.dia_nac << endl;
    cout << "Mes de nacimiento "<< persona1.mes_nac << endl;
    cout << "Ano de nacimiento "<< persona1.annio_nac << endl;

return 0;
}


Es fácil observar como esta diseñado el programa

Para CodeBlocks



Para NetBeans



Para CLI



Esta forma de crear las estructuras se acerca al modo de C tradicional, también podemos realizar esto  declarar primero cómo van a ser nuestros registros, y más adelante definir variables de ese tipo:

//programa estructura_2.cpp
#include <iostream>
#include <string>
using namespace std;

int main( int argc, char *argv[])
{
    // se declaran primero los registros
    struct datosPersona
    {
        string nombre;
        char  inicial;
        int   edad;
        float dia_nac;
        float mes_nac;
        float annio_nac;
    };

    datosPersona persona1;  //véase la forma de asignar los datos al registro, este modelo es mas parecido al C++
                                             // lo demás es igual

   cout << "Nombre :";
    getline(cin, persona1.nombre);
    cout << endl;
    cout << "Inicial de tu nombre:";
    cin >> persona1.inicial;
    cout << endl;
    cout << "Edad :";
    cin >>  persona1.edad;
    cout << endl;
    cout << "Dia de nacimiento ";
    cin >> persona1.dia_nac;
    cout << endl;
    cout << "Mes de Nacimiento ";
    cin >> persona1.mes_nac;
    cout << endl;
    cout << "Año de nacimiento ";
    cin >> persona1.annio_nac;
    cout << endl;

 cout << "Extraemos de struct \"registro\" los datos ";
    cout << endl;

    cout << "El nombre es :" << persona1.nombre << endl;
    cout << "La inicial del Nombres es : " << persona1.inicial<<endl;
    cout << "La edad es " << persona1.edad<< endl;
    cout << "Dia de nacimiento "<< persona1.dia_nac << endl;
    cout << "Mes de nacimiento "<< persona1.mes_nac << endl;
    cout << "Ano de nacimiento "<< persona1.annio_nac << endl;

    return 0;
}

Para CodeBlocks



Para NetBeans



Para CLI



Modificamos el programa anterior ahora atomizamos los datos, es decir los separamos en unidades básicas, el nombre lo dividimos en 3 partes: nombre, apellido1, apellido2, con esto podemos posteriormente tratar los datos de forma eficiente, ya que teniendo todo el nombre y apellidos en un solo campo se nos dificultara la manipulación de datos.

Usamos new (ya anteriormente la usamos) esto es para reservar espacio en memoria de los datos a capturar, incluimos un ciclo donde controlamos la cantidad de datos a introducir, todo esto ayudándonos de struct.

Se puede utilizar el operador new para crear variables de cualquier tipo. New devuelve, en todos los casos, un puntero a la variable creada. También se pueden crear variables de tipos definidos por el usuario.

Cuando una variable ya no es necesaria se destruye con el operador delete para poder utilizar la memoria que estaba ocupando, mediante una instrucción del tipo:


delete p;

A continuación se presenta a modo de ejemplo un programa que reserva memoria de modo dinámico para un vector de caracteres:


#include <iostream.h>
#include <string.h>

using namespace std;

int main(int argc, char*argv[])

{

char Nombre[50];
cout << "Introduzca su Nombre:";
cin >> Nombre;
    char *CopiaNombre = new char[strlen(Nombre)+1]; //strlen logitud del string
    // Se copia el Nombre en la variable CopiaNombre
    strcpy(CopiaNombre, Nombre);
    cout << CopiaNombre;
    delete [] CopiaNombre; //borra el valor del apuntador

return 0;
}

El siguiente programa usa struct y new

// programa estructura_3.cpp
#include <iostream.h>
#include <string.h>
#include <iomanip>

using namespace std;

int main(int argc, char* argv[])
{
int i,ii;

cout << "Numero de registros a capturar ";
cin >> ii;
cout << endl;

        struct datosPersona
    {
         string  nombre;
         string apellido1;
         string apellido2;
        char  inicial;
        int   edad;
        float dia_nac;
        float mes_nac;
        float annio_nac;
    };
        datosPersona *persona = new datosPersona[ii];

    cout << "Presentar valores de inicializacion i y ii " << " " << i << " " << ii << endl;
    // linea que muestra el inicio de las variables para iniciar el ciclo no es necesario, pero es una forma de observar el comportamiento de los ciclos
    for (int i=0; i<ii; i++)
    {

    cout << "Nombre persona numero  : " << i+1 ; // sumamos +1 a i para que el conteo que presenta en pantalla no se vea el 0
    cout << " ";
    cin >> persona[i].nombre;
    cout << "Inicial de tu nombre: " ;
    cin >> persona[i].inicial;
    cout << "Primer apellido : ";
    cin >> persona[i].apellido1;
    cout << "Segundo apellido : ";
    cin >> persona[i].apellido2;
    cout << "Edad : ";
    cin >>  persona[i].edad;
    cout << "Dia de nacimiento : " ;
    cin >> persona[i].dia_nac;
    cout << "Mes de Nacimiento : ";
    cin >> persona[i].mes_nac;
    cout << "Año de nacimiento : ";
    cin >> persona[i].annio_nac;
    cout << endl;
    cin.ignore();
    }

    std::cout << std::setfill ('*') << std::setw (40); // se usa para llenar con * una secuencia de 40
    std::cout << "Impresion de salida ";
  

    cout << "Comienza la presentacion datos ";
    cout << "Presentar valores de i y ii " << " " << i << " " << ii << endl;
    // linea que muestra el inicio de las variables para iniciar el ciclo no es necesario, pero es una forma de observar el comportamiento de los ciclos
    for (int i=0; i<ii; i++)
    {

    cout << "Nombre persona numero  : " << i+1; // sumamos +1 a i para que el conteo que presenta en pantalla no se vea el 0
    cout << " ";
    cout << persona[i].nombre<< endl;
    cout << "Inicial de tu nombre: " ;
    cout << persona[i].inicial<< endl;
    cout << "Primer apellido : ";
    cout << persona[i].apellido1<< endl;
    cout << "Segundo apellido : ";
    cout << persona[i].apellido2<< endl;
    cout << "Edad : ";
    cout <<  persona[i].edad<< endl;
    cout << "Dia de nacimiento " ;
    cout << persona[i].dia_nac<< endl;
    cout << "Mes de Nacimiento ";
    cout << persona[i].mes_nac<< endl;
    cout << "Año de nacimiento ";
    cout << persona[i].annio_nac<< endl;
    cout << endl;
    }

    return 0;
}


Para CodeBlocks





Para NetBeans





Para CLI. en este caso tenemos un aviso de Warning pero no es critico solo hacemos un cambio pequeño y queda sin problemas, el compilador nativo g++ detecta ese warning pero los IDE no es porque evaluan todo el programa, y el compilador detecta "inconsistencias" ambos sitemas hace su funcion de diferente forma.





Viendo este caso reconsideremos algunos aspectos del compilador

Ejemplo 1: Compilado un programa


Considere un  programa cualquiera.


  #include "iostream.h"
  int main()
  {
    cout << "Hello\n";
  }

La forma de compilación usual es:  g++ hello.C -o hello

Este comando compila  hello.C a un programa ejecutable, ejecutándose como ./hello en linea de comandos (CLI).

Alternativamente el programa puede ser compilado usando los 2 siguientes comandos.


  g++ -c hello.C
  g++ hello.o -o hello

El resultado final es el mismo, pero el método usa 2 pasos primero compila rst compiles hello.C en la maquina en un archivo de código llamadole "hello.o" y entonces lo enlaza "link" con las librerías  y produce el programa final "hello". En efecto el primer método también realiza 2 pasos de compilación y enlazado, pero el proceso es transparente e inmediatamente el archivo intermedio "hello.o" es eliminado del proceso.

Hagamos que el compilador genere muchas advertencias sobre código sintácticamente correcto pero de aspecto cuestionable. Es una buena práctica usar siempre esta opción con gcc y g ++.


g++ -Wall hello.c -o hello

Genere información simbólica para gdb y muchos mensajes de advertencia.


g++ -g -Wall myprog.C -o myprog

Genere código optimizado en una máquina Linux.


g++ -O myprog.C -o myprog

Compile myprog.C cuando contenga rutinas gráficas Xlib.


g++ myprog.C -o myprog -lX11

Si "myprog.c" es un programa en C, entonces todos los comandos anteriores funcionarán reemplazando g ++ con gcc y "myprog.C" con "myprog.c". A continuación, se muestran algunos ejemplos que se aplican solo a los programas C.


Compile un programa en C que use funciones matemáticas como "sqrt".


gcc myprog.C -o myprog -lm

Compile un programa en C con la biblioteca "cerca eléctrica". Esta biblioteca, disponible en todas las máquinas Linux, hace que muchos programas escritos incorrectamente se bloqueen tan pronto como ocurre un error. Es útil para depurar, ya que la ubicación del error se puede determinar rápidamente usando gdb. Sin embargo, solo debe usarse para depurar, ya que el ejecutable myprog será mucho más lento y usará mucha más memoria de lo habitual.


gcc -g myprog.C -o myprog -lefence

Ejemplo 2: Compilar un programa con varios archivos fuente.

Si el código fuente está en varios archivos, diga "file1.C" y "file2.C", entonces se pueden compilar en un programa ejecutable llamado "myprog" usando el siguiente comando:


 g++ file1.C file2.C -o myprog

Se puede lograr el mismo resultado usando los siguientes tres comandos:


  g++ -c file1.C
  g++ -c file2.C
  g++ file1.o file2.o -o myprog

La ventaja del segundo método es que compila cada uno de los archivos fuente por separado. Si, por ejemplo, los comandos anteriores se usaron para crear "myprog" y "file1.C" se modificó posteriormente, los siguientes comandos actualizarían correctamente "myprog".


  g++ -c file1.C
  g++ file1.o file2.o -o myprog

Tenga en cuenta que no es necesario volver a compilar file2.C, por lo que el tiempo necesario para reconstruir myprog es más corto que si se usara el primer método para compilar myprog. Cuando hay numerosos archivos de origen y solo se realiza un cambio en uno de ellos, el ahorro de tiempo puede ser significativo. Este proceso, aunque algo complicado, generalmente se maneja automáticamente mediante un archivo MAKE.

Estos métodos los usaremos posteriormente

De los ejemplos mostrado anteriormente que la estructura llamada Struct, nos permite crear un grupo de variables que consta de tipos de datos mixtos en una sola unidad.

Veamos la estructura anidada

//programa estructura_4.cpp
#include <iostream>
#include <string>
using namespace std;
 
struct fechaNacimiento
{
    int  dia;
    int  mes;
    int anyo;
}; // Termina fechaNacimiento
 
struct datosPersona
{
    string nombre;
    char  inicial;
    struct fechaNacimiento diaDeNacimiento; //se establecen miembros de la estrucura a unir
    struct fechaNacimiento mesDeNacimiento;
    struct fechaNacimiento anyoDeNacimiento;
    float nota;
}; // Termina datosPersona

int main(int argc, char*argv[])

{

cout << "\e[1;1H\e[2J"; // 
\e[1;1H pone en la pantalla al 1 (primer) renglon 1(primer) columna. \e[2J sobre escribe todos los caracteres en panatalla.

    datosPersona persona; // realiza llamada a estructura datosPersona
                          // esta estructura hace llamada a fechaNacimiento
                          // y hace mas facil el diseño del programa
                         // persona es la referencia hacia la estructura
 
        cout << "Nombre ";
        cin >> persona.nombre; //envia a nombre en  datosPersona
        cout << '\n' << "Letra Inicial del Nombre ";
        cin >> persona.inicial; //envia a inicial en  datosPersona
       cout << '\n' << "Dia de Nacimiento ";
        cin >> persona.diaDeNacimiento.dia; //envia a dia a fecha de nacimiento a traves del miembre de la estructura fechaNacimiento
       cout << '\n' << "Mes de Nacimiento ";
       cin >> persona.mesDeNacimiento.mes;
       cout << '\n' << "Año de Nacimiento ";
       cin >> persona.anyoDeNacimiento.anyo;
       cout << '\n' << "Nota ";
       cin >> persona.nota;

    cout << "La nota es " << persona.nota;
        cout << " sus datos son: Nombre Inicial Mes  Dia Año de Nacimiento " << persona.nombre << " " << persona.inicial << " "<< persona.mesDeNacimiento.mes << " " << persona.diaDeNacimiento.dia << " " << persona.anyoDeNacimiento.anyo << endl;

    return 0;
}


en cout << "\e[1;1H\e[2J";  es un nuevo conjuntro de acciones sobre pantalla para borrar cualquier dato, o caracteres previos a la ejecucion del programa.

Para CodeBlocks

Para NetBeans

Para CLI




tenemos ahora un programa con varias opciones :

// programa estructura_5.cpp
#include <iostream>
#include <string>

using namespace std;
 
struct tipoDatos
{
    string nombreFich;        // Nombre del fichero
    long tamanyo;            // El tamaño en bytes
};
 
int numeroFichas=0;  // Número de fichas que ya tenemos
int i;                            // Para bucles
int opcion;                 // La opcion del menu que elija el usuario
 
string textoTemporal; // Para pedir datos al usuario
int numeroTemporal;
 
int main(int argc, char* argv[])
{
cout << "\e[1;1H\e[2J";

    tipoDatos *fichas = new tipoDatos[1000];
 
    do
    {
        // Menu principal
        cout << endl;
        cout << "Escoja una opción:" << endl;
        cout << "1.- Añadir datos de un nuevo fichero" << endl;
        cout << "2.- Mostrar los nombres de todos los ficheros" << endl;
        cout << "3.- Mostrar ficheros que sean de mas de un cierto tamaño" << endl;
        cout << "4.- Ver datos de un fichero" << endl;
        cout << "5.- Salir" << endl;
 
    cout << "Cual ? ";       
    cin >> opcion;
 
        // Hacemos una cosa u otra según la opción escogida
       
    switch(opcion)
        {
            case 1: // Añadir un dato nuevo
                if (numeroFichas < 1000)   // Si queda hueco
                {
                    cout << "Introduce el nombre del fichero: ";
                    cin >> fichas[numeroFichas].nombreFich;
                    cout << "Introduce el tamaño en KB: ";
                    cin >> fichas[numeroFichas].tamanyo;
                    numeroFichas++;  // Y tenemos una ficha más
                }
                else   // Si no hay hueco para más fichas, avisamos
                    cout << "Máximo de fichas alcanzado (1000)!" << endl;
                break;
 
            case 2: // Mostrar todos
                for (i=0; i<numeroFichas; i++)
                    cout << "Nombre: " << fichas[i].nombreFich
                        << "; Tamaño: " << fichas[i].tamanyo
                        << "Kb" << endl;
                break;
 
            case 3: // Mostrar según el tamaño
                cout << "¿A partir de que tamaño quieres que te muestre? ";
                cin >> numeroTemporal;
                for (i=0; i<numeroFichas; i++)
                    if (fichas[i].tamanyo >= numeroTemporal)
                        cout << "Nombre: " << fichas[i].nombreFich
                            << "; Tamaño: " << fichas[i].tamanyo
                            << " Kb" << endl;
                break;
 
            case 4: // Ver todos los datos (pocos) de un fichero
                cout << "¿De qué fichero quieres ver todos los datos?";
                cin >> textoTemporal;
                for (i=0; i<numeroFichas; i++)
                    if (fichas[i].nombreFich == textoTemporal)
                        cout << "Nombre: " << fichas[i].nombreFich
                            << "; Tamaño: " << fichas[i].tamanyo
                            << " Kb" << endl;
                break;
 
            case 5: // Salir: avisamos de que salimos
                cout << "Fin del programa" << endl;
                break;
 
            default: // Otra opcion: no válida
                cout << "Opción desconocida!" << endl;
                break;
        }
    } while (opcion != 5);  // Si la opcion es 5, terminamos
 
    return 0;
}

Para CodeBlocks



Para NetBeans


Para CLI



Los constructores brindan soporte para múltiples formas de creación e inicialización de objetos.
Constructor es una función miembro especial.
Cada declaración de un objeto da como resultado la llamada de un constructor.

De la misma manera, un constructor es un método especial, que se llama automáticamente cuando se declara un objeto para la clase, en un lenguaje de programación orientado a objetos.

Entonces, combinando estas dos metodologías diferentes, podemos decir que cuando estos constructores se definen dentro de una estructura, estos se denominarían constructores de estructuras. Aprendamos sobre esta funcionalidad en el lenguaje de programación C ++.

Después de definir la estructura, se trata de la declaración de todas las variables con diferentes tipos de datos. Entonces habíamos definido el constructor. Se define como una función con el mismo nombre que el nombre de la estructura. En la sintaxis, mostramos la declaración del constructor predeterminado y parametrizado.

¿Cómo funciona Struct Constructor en C ++?

Aquí, revisemos el código de muestra y comprendamos su funcionamiento en el lenguaje de programación C ++

// programa construct.cpp                              
#include <iostream.h>

using namespace std;

struct area
    {
         float a; //para la altura
         int b; //para el ancho
        area() // inicia el constructor
       {
        cout << "La altura es : ";cin >>a;
        cout << "El ancho es  : ";cin >>b;
        cout << "El calculo del area : "<<a*b<<endl;
       }
        //termina constrctor
    };

int main(int argc, char* argv[])

{
 // las siguientes lineas producen error: ‘r1’ no se declaró en este ámbito
// puede retirar los comentarios para ver el error
//cout << "La altura es : " << r1.a << endl;
//cout << "El ancho es  : " << r1.b << endl;

//las siguientes lineas son correctas y devuelven los datos

area r1; // llama a struct con el constructor, r1 es miembro de struct
cout << "La altura es : " << r1.a << endl; // elemento miembro r1.variable
cout << "El ancho es  : " << r1.b << endl; // elemento miembro r1.variable  

return 0;
}


Para CodeBlocks



Para NetBeans



Para CLI



Explicacion del código

Repetiremos lo que se mencionaron anteriormente

El constructor es una función sin tipo de retorno y con el mismo nombre que la estructura. El destructor tiene la misma forma, salvo que el nombre va precedido el operador "~".

Veamos un ejemplo sencillo para ilustrar el uso de constructores:

Forma 1:

struct Punto {
int x, y;
Punto() {x = 0; y = 0;} // Constructor
} Punto1, Punto2;

Forma 2:

struct Punto {
int x, y;
Punto(); // Declaración del constructor
} Punto1, Punto2;
Punto::Punto() {
estructura
x = 0;
y = 0;
}

Si no usáramos un constructor (inicializacion), los valores de x e y para Punto1 y Punto2 estarían indeterminados, contendrían la "basura" que hubiese en la memoria asignada a estas estructuras durante la ejecución. Con las estructuras éste será el caso más habitual.

Mencionar aquí, sólo a título de información, que el constructor no tiene por qué ser único. Se pueden incluir varios constructores, pero veremos esto mucho mejor y con más detalle cuando veamos las clases.

Usando constructores nos aseguramos los valores iniciales para los elementos de la estructura. Veremos que esto puede ser una gran ventaja, sobre todo cuando combinemos estructuras con punteros, en capítulos posteriores.

En base a lo anterior, analicemos el fragmento del código del programa anterior:


struct area
    {
         float a; //para la altura
        int b; //para el ancho
        area() // inicia el constructor
       {
        cout << "La altura es : ";cin >>a;
        cout << "El ancho es  : ";cin >>b;
        cout << "El calculo del area : "<<a*b<<endl;
       }
        //termina constrctor
    };

Podemos deducir lo siguiente, se define una estructura (struct) llamada área.

Se declaran las variables a y b, como float y de tipo int, estas variables se inicializaran en este punto, antes de esto esta variables no están disponibles en el curso del programa, cuando son accedidas o llamadas desde main() estas estan disponibles.

area() {     }

Se inicia el constructor, las variables inicializadas estan listas para usarse, y son usadas para la multiplicacion.

area r1; // llama a struct con el constructor, r1 es miembro de struct

area es la estructura que se esta definiendo, r1 es la asignacion o creacion de un miembro de la estructura, podemos ir creando o asignando mas miembros a la estructura con el miembro r2:

area r2; // llama a struct con el constructor, r2 es miembro de struct
cout << "La altura es : " << r2.a << endl; // elemento miembro r2.variable
cout << "El ancho es  : " << r2.b << endl; // elemento miembro r2.variable

Modicamos el programa anterior con mensajes en diferentes partes del programa para identificar las acciones que se llevan a cabo en el programa.

// programa construct_1.cpp                             
#include <iostream.h>
#include <iomanip>

using namespace std;

struct area
    {
         float a; //para la altura
         float b; //para el ancho
        area() // inicia el constructor
       {
        cout << "La altura es : ";cin >>a;
        cout << "El ancho es  : ";cin >>b;
        cout << "El calculo del area : "<<a*b<< " se reliza en struct "<<endl;
       }
        //termina constrctor
    };
int main(int argc, char* argv[])

{
 // las siguientes lineas producen error: ‘r1’ no se declaró en este ámbito
// puede retirar los comentarios para ver el error
//cout << "La altura es : " << r1.a << endl;
//cout << "El ancho es  : " << r1.b << endl;

//las siguientes lineas son correctas y devuelven los datos
cout << setfill ('*') << setw (40) << '\n';
cout << "Para el miembro r1 "<<endl;

area r1; // llama a struct con el constructor, r1 es miembro de struct
cout << setfill ('+')<< setw (40) <<'\n';
cout << " datos despues de regresar de struct "<<endl;
cout << "La altura es : " << r1.a << endl; // elemento miembro r1.variable
cout << "El ancho es  : " << r1.b << endl; // elemento miembro r1.variable 

cout << setfill ('*') << setw (40) << '\n'; // se usa para llenar con * una secuencia de 40
cout <<    "Para el miembro r2 "<< endl;
area r2; // llama a struct con el constructor, r2 es miembro de struct
cout << setfill ('+')<< setw (40) << '\n';
cout << " datos despues de regresar de struct "<<endl;
cout << "La altura es : " << r2.a << endl; // elemento miembro r2.variable
cout << "El ancho es  : " << r2.b << endl; // elemento miembro r2.variable

return 0;
}

Para CodeBlocks



Para NetBeans



Para CLI



Como se puede observar hemos hecho algunos cambios y es necesario que los detecten, y obtengan sus conclusiones.

Ese programa que presentamos es el ultimo de estruct, el cual combina varios elementos, este proceso puede server posteriormente para evaluar fechas posteriormente:

//programa estrctura_6.cpp fechas verificar
#include <iostream.h>

//Definicion de un nuevo tipo CFecha

struct CFecha
{
  int dia, mes, anyo;
};

//Definicion Funciones (o prototipo de funciones)
void AsignarFecha(CFecha *);
void ObtenerFecha(int *, int *, int *, const CFecha &);
int FechaCorrecta(const CFecha &);
int Bisiesto(int anyo);
void VisualizarFecha(const CFecha &);

//Declaraciones de funciones

//Establecer fecha
void AsignarFecha(CFecha *pfecha) //CFecha es definicion de la estructura
                                                          // *pfecha apuntador
{

 //el operador -> es para acceder a los miembros de la estructura
 //usando apuntadores
  std::cout << "Dia, ##    :";std::cin >>pfecha->dia;
  std::cout << "Mes, ##    :";std::cin >>pfecha->mes;
  std::cout << "Año, ####  :";std::cin >>pfecha->anyo;
}

// Funcion que verifica si fecha sea correcta
int FechaCorrecta(const CFecha &fecha)
{
  int DiaCorrecto, MesCorrecto, AnyoCorrecto;
  // año correcto ?
  AnyoCorrecto = (fecha.anyo >=1582);
   // mes correcto ?
  MesCorrecto = (fecha.mes >= 1)&&(fecha.mes <= 12);
  switch (fecha.mes)
  // dia correcto ?
    {
      case 2:
    if ( Bisiesto(fecha.anyo) )
           DiaCorrecto = (fecha.dia >= 1 && fecha.dia <= 29);
        else
           DiaCorrecto = (fecha.dia >= 1 && fecha.dia <= 28);
        break;
     case 4: case 6: case 9: case 11:
          DiaCorrecto=(fecha.dia >= 1 && fecha.dia <= 30); //regresa booleano
        break;
      default:
          DiaCorrecto=(fecha.dia >= 1 && fecha.dia <= 31); //regresa boolea$
    }
    if (!(DiaCorrecto && MesCorrecto && AnyoCorrecto))
       {
         std::cout << "\n Datos no validos \n\n";
         return 0; // fecha incorrecta, booleano que regresa valor 0
       }
    else

        return 1; //fecha correcta, booleano que regresa valor 1
 }
// Funcion Verifica si el año es bisiesto
int Bisiesto(int anyo)
{
     if ((anyo%4 ==0 && anyo%100 !=0)||anyo%400 ==0)
       { return 1;}
     else
       { return 0;}
}
//Visualizar una fecha
void VisualizarFecha(const CFecha &fecha)
{
   int dd,mm,aa;
   ObtenerFecha(&dd,&mm,&aa,fecha); // llamada a otra función desde esta función
   std::cout << dd << "/" << mm << "/" << aa << "\n";
}

//Obtener una fecha
void ObtenerFecha(int *pd,int *pm,int *pa, const CFecha &fecha)
{
   *pd=fecha.dia, *pm=fecha.mes, *pa=fecha.anyo;
}

// inicia main()
int  main(int argc, char* argv[])
{
  //Definir la estructura CFecha con su miembro fecha
  CFecha fecha;

  // Establecer una fecha y verificarla
  do
    AsignarFecha(&fecha); //notese que usa apuntador
  while (!FechaCorrecta(fecha));

  VisualizarFecha(fecha);
  // son llamadas a las funciones

return 0;
}
// termina main()


Para CodeBlocks


Para NetBeans



Para CLI



Los objetos globales std :: cout y std :: wcout controlan la salida a un búfer de flujo de tipo definido por la implementación (derivado de std :: streambuf), asociado con el flujo de salida estándar de C stdout.

Se garantiza que estos objetos se inicializarán durante o antes de la primera vez que se construye un objeto de tipo std :: ios_base :: Init y están disponibles para su uso en los constructores y destructores de objetos estáticos con inicialización ordenada (siempre que <iostream> sea incluido antes de que se defina el objeto).

A menos que se haya emitido sync_with_stdio (falso), es seguro acceder simultáneamente a estos objetos desde varios subprocesos para la salida tanto formateada como sin formato.

Por especificación de std :: cin, std :: cin.tie () devuelve & std :: cout. Esto significa que cualquier operación de entrada en std :: cin ejecuta std :: cout.flush () (a través del constructor de std :: basic_istream :: sentry). De manera similar, std :: wcin.tie () devuelve & std :: wcout.

Por especificación de std :: cerr, std :: cerr.tie () devuelve & std :: cout. Esto significa que cualquier operación de salida en std :: cerr ejecuta std :: cout.flush () (a través del constructor de std :: basic_ostream :: sentry). De manera similar, std :: wcerr.tie () devuelve & std :: wcout. (desde C ++ 11)

Notas

La 'c' en el nombre se refiere al "carácter" (FAQ de stroustrup.com); cout significa "salida de caracteres" y wcout significa "salida de caracteres amplia".


Debido a que la inicialización dinámica de las variables con plantilla no está ordenada, no se garantiza que std :: cout se haya inicializado a un estado utilizable antes de que comience la inicialización de dichas variables, a menos que se haya construido un objeto de tipo std :: ios_base :: Init.






Clases vs estructuras

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

https://www.bogotobogo.com/cplusplus/class.php









https://ccodigo.wordpress.com/2010/09/15/como-usar-cin-getline-en-c/
https://www.programarya.com/Cursos/C++/Estructuras-de-Datos
https://www.programarya.com/Cursos/C++/Estructuras-de-Datos/Arreglos-o-Vectores
https://www.programarya.com/Cursos/C++/Estructuras-de-Datos/Matrices
https://www.programarya.com/Cursos/C++/Estructuras-de-Datos/Punteros
https://www.programarya.com/Cursos/C++/Funciones
Apuntes de Clases INFO 161 : El Lenguaje de Programación C++ (Apun_C++.PDF)
https://www.codingame.com/playgrounds/51214/manejo-dinamico-de-memoria-y-polimorfismo-practica-4/punteros-en-c#:~:text=Los%20punteros%20almacenan%20un%20valor,sea%20apuntado%20a%20otra%20variable.
https://lenguajedeprogramacion.com/programacion-c/que-es-un-puntero-usos/
http://fpsalmon.usc.es/genp/doc/cursos/C++/punteros/cap2.html
entradas y salidas de C++
http://agora.pucp.edu.pe/inf2170681/3-1.htm
https://www.aprendeaprogramar.com/cursos/verApartado.php?id=16007
https://www.javatpoint.com/cpp-tutorial // muy bueno en ingles
https://www.aprendeaprogramar.com/cursos/verApartado.php?id=16001

getline: https://www.geeksforgeeks.org/getline-string-c/, Enciclopedia del lenguaje C++, 2da edicion, Fco. Javier Ceballos Sierra Pag 117
sizeof: Enciclopedia del lenguaje C++, 2da edicion, Fco. Javier Ceballos Sierra Pag 48





Nota 1:

sizeof()


Para reservar memoria se debe saber exactamente el número de bytes que ocupa cualquier estructura de datos. Una peculiaridad del lenguaje C es que estos tamaños pueden variar de una plataforma a otra. ¿Cómo sabemos, entonces, cuántos bytes reservar para una tabla de, por ejemplo, 10 enteros? El propio lenguaje ofrece la solución a este problema mediante la función sizeof().

La función recibe como único parámetro o el nombre de una variable, o el nombre de un tipo de datos, y devuelve su tamaño en bytes. De esta forma, sizeof(int) devuelve el número de bytes que se utilizan para almacenar un entero.
 

****************************************************************************************************************************************

Nota 2:


getline(is,str,delim)

Extrae caracteres de is  y los almacena en str hasta que se encuentra el carácter de delimitación delim (o el carácter de nueva línea, '\ n', este espor defecto, noes necesario indicarlo).
La extracción también se detiene si se llega al final del archivo en es o si se produce algún otro error durante la operación de entrada.
Si se encuentra el delimitador, se extrae y se descarta (es decir, no se almacena y la siguiente operación de entrada comenzará después).
Tenga en cuenta que cualquier contenido en str antes de la llamada se reemplaza por la secuencia recién extraída.

// extraccion a string
#include <iostream.h>
#include <string.h>

using namespace std;

int main (int argc, char*argv[])
{
   string name;

  cout << "Su nombre completo : ";
  getline (cin,name); // getline() recibe espacios entre el string
  cout << "Hola, " << name << "!\n";

  return 0;
}


*****************************************************************************************************************************************
Nota 3:
si no se encuentra la biblioteca:   iomanip         puede agregarla con esto:

recuperado de : http://freesourcecode.net/cprojects/85646/sourcecode/IOMANIP.H#.YJm-2KlR3k0


/*  iomanip.h -- streams I/O manipulator declarations

    Copyright (c) 1990, 1991 by Borland International
    All rights reserved
*/

#ifndef __cplusplus
#error Must use C++ for the io stream manipulators.
#endif

#ifndef __IOMANIP_H
#define __IOMANIP_H

#if !defined( __DEFS_H )
#include <_defs.h>
#endif

#if !defined( __IOSTREAM_H )
#include <iostream.h>
#endif

#if !defined( __GENERIC_H )
#include <generic.h>
#endif

#pragma option -Vo-

#define SMANIP(typ)     _Paste2(smanip_, typ)
#define SAPP(typ)       _Paste2(sapply_, typ)
#define IMANIP(typ)     _Paste2(imanip_, typ)
#define OMANIP(typ)     _Paste2(omanip_, typ)
#define IOMANIP(typ)    _Paste2(iomanip_, typ)
#define IAPP(typ)       _Paste2(iapply_, typ)
#define OAPP(typ)       _Paste2(oapply_, typ)
#define IOAPP(typ)      _Paste2(ioapply_, typ)

#define IOMANIPdeclare(typ)                                             \
class _CLASSTYPE SMANIP(typ) {                                                     \
        ios & (_Cdecl *_fn)(ios &, typ);                                          \
        typ _ag;                                                        \
public:                                                                 \
        _Cdecl SMANIP(typ)(ios & (_Cdecl *_f)(ios &, typ), typ _a) : _fn(_f), _ag(_a) { }\
        friend istream & _Cdecl operator>>(istream & _s, SMANIP(typ) & _f) {       \
                        (*_f._fn)(_s, _f._ag); return _s; }             \
        friend ostream & _Cdecl operator<<(ostream & _s, SMANIP(typ) & _f) {       \
                        (*_f._fn)(_s, _f._ag); return _s; }             \
        };                                                              \
class _CLASSTYPE SAPP(typ) {                                                       \
        ios & (_Cdecl *_fn)(ios &, typ);                                          \
public:                                                                 \
        SAPP(typ)(ios & (_Cdecl *_f)(ios &, typ)) : _fn(_f) { }                   \
        SMANIP(typ) _Cdecl operator()(typ _z) { return SMANIP(typ)(_fn, _z); }  \
        };                                                              \
class _CLASSTYPE IMANIP(typ) {                                                     \
        istream & (_Cdecl *_fn)(istream &, typ);                                  \
        typ _ag;                                                        \
public:                                                                 \
        _Cdecl IMANIP(typ)(istream & (_Cdecl *_f)(istream &, typ), typ _z ) :            \
                _fn(_f), _ag(_z) { }                                    \
        friend istream & _Cdecl operator>>(istream & _s, IMANIP(typ) & _f) {       \
                return(*_f._fn)(_s, _f._ag); }                          \
        };                                                              \
class _CLASSTYPE IAPP(typ) {                                                       \
        istream & (_Cdecl *_fn)(istream &, typ);                                  \
public:                                                                 \
        _Cdecl IAPP(typ)(istream & (_Cdecl *_f)(istream &, typ)) : _fn(_f) { }           \
        IMANIP(typ) _Cdecl operator()(typ _z) {                         \
                return IMANIP(typ)(_fn, _z); }                          \
        };                                                              \
class _CLASSTYPE OMANIP(typ) {                                                     \
        ostream & (_Cdecl *_fn)(ostream &, typ);                                  \
        typ _ag;                                                        \
public:                                                                 \
        _Cdecl OMANIP(typ)(ostream & (_Cdecl *_f)(ostream &, typ), typ _z ) :            \
                _fn(_f), _ag(_z) { }                                    \
        friend ostream & _Cdecl operator<<(ostream & _s, OMANIP(typ) & _f) {       \
                return(*_f._fn)(_s, _f._ag); }                          \
        };                                                              \
class _CLASSTYPE OAPP(typ) {                                                       \
        ostream & (_Cdecl *_fn)(ostream &, typ);                                  \
public:                                                                 \
        _Cdecl OAPP(typ)(ostream & (_Cdecl *_f)(ostream &, typ)) : _fn(_f) { }           \
        OMANIP(typ) _Cdecl operator()(typ _z) {                         \
                return OMANIP(typ)(_fn, _z); }                          \
        };                                                              \
class _CLASSTYPE IOMANIP(typ) {                                                    \
        iostream & (_Cdecl *_fn)(iostream &, typ);                                \
        typ _ag;                                                        \
public:                                                                 \
        _Cdecl IOMANIP(typ)(iostream & (_Cdecl *_f)(iostream &, typ), typ _z ) : \
                _fn(_f), _ag(_z) { }                                    \
        friend istream & _Cdecl operator>>(iostream & _s, IOMANIP(typ) & _f) {     \
                return(*_f._fn)(_s, _f._ag); }                          \
        friend ostream & _Cdecl operator<<(iostream & _s, IOMANIP(typ) & _f) {     \
                return(*_f._fn)(_s, _f._ag); }                          \
        };                                                              \
class _CLASSTYPE IOAPP(typ) {                                                      \
        iostream & (_Cdecl *_fn)(iostream &, typ);                                \
public:                                                                 \
        _Cdecl IOAPP(typ)(iostream & (_Cdecl *_f)(iostream &, typ)) : _fn(_f) { }        \
        IOMANIP(typ) _Cdecl operator()(typ _z) { return IOMANIP(typ)(_fn, _z); }\
        }



IOMANIPdeclare(int);
IOMANIPdeclare(long);

// set the conversion base to 0, 8, 10, or 16
smanip_int      _Cdecl setbase(int _b);

// clear the flags bitvector according to the bits set in b
smanip_long     _Cdecl resetiosflags(long _b);

// set the flags bitvector according to the bits set in b
smanip_long     _Cdecl setiosflags(long _b);

// set fill character for padding a field
smanip_int      _Cdecl setfill(int _f);

// set the floating-point precision to n digits
smanip_int      _Cdecl setprecision(int _n);

// set the field width to n
smanip_int      _Cdecl setw(int _n);

#pragma option -Vo.

#endif
****************************************************************************************************