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, 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++
-
El tipo de dato del apuntador debe coincidir con el de la
variable cuya posición en memoria apuntan. En el ejemplo
vemos que tanto variable como apuntador son enteros.
-
Siempre que queremos usar el apuntador debemos anteponer
el asterisco (*) para indicar que usaremos el valor en la
posición de memoria apuntada.
-
De no usar el asterisco el comportamiento sería
impredecible. Estaremos haciendo uso de la dirección de
memoria más no del valor almacenado en ésta.
-
Después de usar un puntero, especialmente si trabajamos
con arreglos o matrices, es MUY recomendable liberar la
memoria utilizada con la función delete (tal como en el
ejemplo)
-
Un puntero o apuntador puede ser de cualquier tipo de
dato, inclusive los podemos usar con tipos complejos.
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
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
****************************************************************************************************