martes, 10 de enero de 2017

ARRAYS Y CADENAS

CAPÍTULO 6     ARRAYS Y CADENAS




Cuando declaramos una variable estamos apartando en memoria espacio para
guardar sus posibles valores dependiendo del tipo de dato que se trata. Un array o
arreglo son una serie de localidades en memoria consecutivas que están asignadas
a un solo nombre y un mismo tipo de datos.
Valor
almacenado
Localidad en memoria
Arreglo[0] 1550 FFFB
Arreglo[1] 130 FFFC
Arreglo[2] 45 FFFD
Arreglo[3] 90 FFFE
Arreglo[4] 76 FFFF
Ya anteriormente hemos presentado ejemplos en los que se hace uso de los
arreglos, y aunque no comprendimos exactamente que significado tenía la aplicación
de los arreglos nos dio una idea de sus utilidades.
DECLARACIÓN
La forma de declarar un arreglo de cualquier tipo de datos es la siguiente:
tipo nombre [tamaño] ;
Por ejemplo, podemos declarar un arreglo de enteros con 12 elementos.
63
int MiArreglo [12] ;
El compilador se encargará de asignar la memoria requerida para almacenar
determinados valores.
Cuando se declara un arreglo de caracteres se trata entonces de una cadena.
char nombre[20] ;
ASIGNACIÓN DE VALORES
Al momento de declarar un arreglo de cualquier tipo, podemos inicializarlo con los
valores que queramos. Para inicializar un arreglo de enteros:
int MiArreglo[5] ={2,34,78,1,9};
Así, estos valores estarán almacenados en cada elemento del array. Es muy
importante hacer notar que el primer elemento de un arreglo es el elemento 0,
entonces, MiArreglo[0] contendrá el número 2, el segundo ( MiArreglo[1] ) contendrá
el número 34 y así sucesivamente hasta MiArreglo[4] que es el último elemento del
array. Si un arreglo cuenta con menos inicalizadores que elementos entonces el resto
se inicializará a 0.
Y en caso de que se trate de una cadena de caracteres podemos hacerlo de 2
formas:
char MiCadena[13]= “hola a todos”;
o bien, declarar cada elemento
64
char MiArray[5]={'h','o','l','a','\0'};
Cuando se inicializa una cadena por el primer método, automáticamente se coloca
un carácter de terminación de cadena (el carácter \0), en cambio, de la segunda
forma debemos de ponerlo nosotros. También se puede excluir el tamaño del arreglo,
el compilador lo determinará en la compilación.
Para acceder a cada elemento del arreglo debemos especificar su posición (la
primera es la posición 0). En tiempo de ejecución podemos asignar valores a cada
elemento cuidando siempre de no sobrepasar el tamaño del arreglo, si
sobrepasamos ese tamaño se escribirán datos en un área de la memoria que no está
asignada para ese array, puede escribir datos en un área en donde se almacenaba
otra variable del programa o un componente del sistema, esto ocasionaría resultados
no deseados.
#include <iostream.h>
#include <iomanip.h>     //para presentacion con formato
#include <stdlib.h>
#include <time.h>
int main(){
    const int tam_max=20;
    int aleatorios[tam_max];
    int i,suma=0;
    float promedio;
    srand((unsigned)time(NULL));
    for(i=0;i<tam_max;i++){
         aleatorios[i]=rand()%128;   //asigna el numero
         cout<<"aleatorio generado: "   //presenta el numero
             <<setw(5)<<aleatorios[i]<<endl;   
    }
    for(i=0;i<tam_max;)
         suma+=aleatorios[i++];     //suma los elementos
    promedio=(float)suma/tam_max;
    cout<<endl<<"el promedio es: "<<promedio<<endl;
    cin.get();
    return 0;
}
65
El programa anterior muestra el uso de los arreglos. Ya anteriormente habíamos
hecho un ejercicio que calculaba el promedio de una serie de números introducidos
por el usuario, en este caso el programa guarda los números generados por la
máquina en un arreglo para luego calcular el promedio.
Para asignar cada valor aleatorio generado, se utiliza un ciclo for con un contador “i”
que va de 0 hasta el tamaño máximo que pudiera tener el array. El tamaño del array
fue declarado como una constante al principio de la función principal, esto ayudará
en caso de que se quiera aumentar el tamaño del arreglo, así no tendremos que
modificar cada parte de nuestro código en la que se refiera al tamaño, bastará con
cambiar el valor de la constante.
Dentro del ciclo se genera un número aleatorio con la función rand(), usamos el
operador para obtener el resto de la división entera, así nos dará un número menor
de 128. Previamente se utilizó la función srand() para establecer una semilla para
generar los números aleatorios, esa semilla la tomamos llamando al reloj del sistema
con la función time(). Al mismo tiempo imprimimos el número que fue generado.
Posteriormente, se hace de nuevo un ciclo para acceder a los valores almacenados y
sumarlos en una variable, aunque esto se pudo haber hecho dentro del ciclo anterior
servirá para mostrar las formas de asignación y consulta de valores. Además del uso
del incremento después de la asignación.
Finalmente se calcula el promedio.
En el caso de las cadenas de caracteres también podemos acceder sus elementos
de la misma manera, para la asignar valores es mejor usar las funciones para la
lectura de caracteres, tales como getline(), porque nos permite tener un control
de los caracteres que se introducirán y también introduce el carácter de terminación
66
de cadena, cosa que no es posible con cin>> directamente y además de que puede
escribir en sectores de memoria no adecuados.
El siguiente programa es una muestra del uso de arreglos de caracteres.
#include <iostream.h>
int main(){
    const int tam_cad=20;
    char cadena[tam_cad];
    cin.getline(cadena,tam_cad,'\n');
    for(int i=0;(i<tam_cad)&&(cadena[i]!='\0');i++)
                 cadena[i]+=5;
    cout<<cadena<<endl;
    cin.get();
    return 0;
}
¿Qué hace este programa?, sencillamente podemos decir que almacena una cadena
introducida por el usuario, para luego modificar cada carácter por medio de un ciclo
hasta que encuentre el carácter de terminación (\0) o llegue al limite de caracteres.
Quizá no parezca muy interesante este ejemplo, pero hay que recordar que con
cosas pequeñas se construyen grandes cosas, adornemos un poco nuestro
programa, supongamos que la cadena que introduce el usuario es un mensaje que
debe ser secreto, en lugar de sumar 5 a cada carácter podemos sumarle el número
que quiera el usuario, ¿por qué no una clave?.
El mismo programa pero un poco “adornado” o “presuntuoso” queda de la siguiente
forma.
#include <iostream.h>
int main(){
    const int tam_cad=20;
    char cadena[tam_cad];
    int clave;
67
    cout<<"introduce el texto a cifrar"<<endl;
    cin.getline(cadena,tam_cad,'\n');
    cout<<"ahora dame tu clave (no mas de 3 digitos)"<<endl;
    cin>>clave;
    for(int i=0;(i<tam_cad)&&(cadena[i]!='\0');i++)
                 cadena[i]+=clave;
    cout<<"el texto cifrado es: "<<endl;
    cout<<cadena<<endl;
    cin.ignore();
    cin.get();
    return 0;
}
Como ejercicio sugiero que haga un programa que realice la acción contraria, de un
texto cifrado saque el mensaje oculto, y ¿por qué no? intente descifrar un mensaje
sin la clave.
La manipulación de cadenas es uno de los temas más extensos en cuestiones de
programación y de los más importantes. Desde las acciones más básicas como la
concatenación de cadenas, la ordenación y búsqueda en arreglos, hasta los
complicados algoritmos de criptografía o la búsqueda a velocidades increíbles.
PASO DE ARGUMENTOS
Así como en ejemplos anteriores pasamos una variable a una función, también se
puede pasar como argumento un arreglo. Para pasarlo es necesario hacer una
llamada como la siguiente:
MiFuncion (arreglo);
Y el prototipo de la función debe de ser de la siguiente forma:
68
tipoRetorno MiFuncion (tipoArreglo nombre [ ] );
No se requiere incluir el tamaño del arreglo dentro de los corchetes, si se escribiera
el compilador lo ignora.
Cuando pasamos como argumento a una función una variable de tipo int, float,
char, etc., estamos pasando el valor de esta variable a otra variable (podría decirse
una copia) que se define en la función, esta forma de pasar argumentos se denomina
“por valor”. Cualquier modificación de valor a esta variable no tendrá efecto en la
variable “original” que pasó su valor a la función. Esta última variable, al ser local,
será destruida cuando termine de ejecutarse la función que la contiene.
En el caso del paso de arreglos, este no se hace por valor, sino que se hace un paso
por referencia simulado. Cuando se declara un arreglo, el nombre de éste es una
variable que apunta al primer elemento del arreglo, es decir que contiene su
dirección en memoria.
FFF0
NombreArreglo
Elemento 0 Elemento 1
FFFA FFFB
Cuando pasamos esta dirección a una función, cualquier modificación que se haga
dentro de esa función de nuestro arreglo tendrá efectos en la variable original.
El siguiente programa da un ejemplo:
69
#include <iostream.h>
void modifica(int arreglo[], int tamano);
int main(){
    const int tam_arr=5;
    int MiArreglo[tam_arr]={5,10,15,20,25};
    int i;
    modifica(MiArreglo, tam_arr);
    for(i=0;i<tam_arr;++i){
         cout<<"elemento "<<i<<"es "
             <<MiArreglo[i]<<endl;
    }
    cout<<"presiona enter para terminar"<<endl;
    cin.get();
    return 0;
}
void modifica(int arreglo[], int tam_arr){
     for(int i=0;i<tam_arr;++i)
             arreglo[i]*=2;
}
Se ha declarado e inicializado un arreglo de enteros de 5 elementos. Posteriormente
se llama a la función modifica, la cual multiplica cada elemento del arreglo por dos,
estos cambios de valor en los elementos del arreglo tienen efecto en “MiArreglo”
aunque el nombre del parámetro de la función modifica sea diferente.
También se puede pasar a una función el valor de un elemento del array, sólo se
pasará el valor del elemento, no se puede modificar el elemento original.
FuncionInvocada ( array [elemento] );
En ocasiones puede resultar poco conveniente que alguna de las funciones que
reciben un arreglo tenga derecho a modificarlo, a veces puede que cambie sus
valores sin que nosotros nos percatemos inmediatamente. Para eso podemos incluir
el calificador const dentro del prototipo y definición de la función, de esta manera no
podrá modificarse nada de este arreglo.
70
tipoRetorno nombreFuncion (const tipoArreglo nombre [ ] );
ARREGLOS MULTIDIMENSIONALES
Como sabemos, se pueden crear arreglos de cualquier tipo de datos, ahora veremos
que también se pueden crear arreglos de arreglos. La forma de declararlos es con
múltiples subíndices, cuantos se quieran tener, nosotros trabajaremos a lo más con
dobles subíndices.
tipoArreglo nombre [subíndice1] [subíndice2] ;
La forma en que queda estructurado un arreglo de dos dimensiones es más o menos
como se muestra en la figura.
Arreglo
Arreglo[0] Arreglo[0][0] Arreglo[0][1] Arreglo[0][2]
Arreglo[1] Arreglo[1][0] Arreglo[1][1] Arreglo[1][2]
Arreglo[2] Arreglo[2][0] Arreglo[2][1] Arreglo[2][2]
Arreglo[3] Arreglo[3][0] Arreglo[3][1] Arreglo[3][2]
El nombre del arreglo tiene la dirección del primer elemento (Arreglo[0]), y éste a su
vez tiene la dirección del otro arreglo (Arreglo[0][0]), así para cada elemento de
arreglo( 0, 1, 2, 3).
Cuando se inicializa un arreglo multidimensional se inicializa entre llaves cada
arreglo o bien, por medio de elemento seguidos:
Array1 [2] [3] = {1, 2, 3, 4, 5, 6};
Array2 [2] [3] = {{2, 4, 6}, {8, 10, 12}};
71
Al mandar un arreglo de dos dimensiones, debemos de especificar en el prototipo y
definición de la función el subíndice2 de nuestro array. El siguiente ejemplo mostrará
varios aspectos de ellos.
#include <iostream.h>
#include <iomanip.h>
#include <stdlib.h>
#include <time.h>
const int vendedores=5, meses=4;
void inicializa(int ventas[][meses]);
void calculaTotal(const int ventas[][meses]);
int main(){
    int ventas[vendedores][meses];
    int i;
    
    //Las siguientes instrucciones solo presentan un
    //   encabezado para la tabla
    cout<<"ventas:"<<setw(23);
    for(i=1;i<=meses;++i)
        cout<<"   mes"<<i;
    cout<<endl;
    //fin de encabezado
    
    inicializa(ventas);
    calculaTotal(ventas);
    cout<<endl<<endl<<"presiona enter para terminar"<<endl;
    cin.get();
    return 0;
}
void inicializa(int ventas[][meses]){
     int i,j;     //contadores
     srand((unsigned)time(NULL));
     for(i=0;i<vendedores;++i){
          cout<<"ventas del vendedor "<<i+1<<": ";
          for(j=0;j<meses;++j){
               ventas[i][j]=rand()%50;
               cout<<setw(7)<<ventas[i][j];
          }
72
          cout<<endl;
     }
}
void calculaTotal(const int ventas[][meses]){
     int suma=0,total=0,i,j;
     cout<<endl<<endl;
     for(i=0;i<vendedores;++i){
          suma=0;
          for(j=0;j<meses;++j){
               suma+=ventas[i][j];
          }
          cout<<"el vendedor "<<i+1<<" vendio "
              <<suma<<" productos"<<endl;
          total+=suma;
     }
     cout<<endl<<endl<<"en total se vendieron: "
         <<total<<" productos en "<<meses<<" meses"<<endl;
}
Notemos primero cómo el prototipo de las funciones “inicializa” y “calculaTotal” debe
incluir el segundo subíndice del arreglo. La función “calculaTotal” muestra un ejemplo
del uso del calificador const para evitar que en esta función se modifique algún
valor del arreglo.
La función “inicializa” llena el arreglo con valores aleatorios menores a 50, se utilizan
dos contadores para dos ciclos for, “i” para el primer subíndice, “j” para el segundo.
El ciclo recorre la primera “fila” (viendo el arreglo como una tabla de valores) usando
el contador i y a cada celda, con ayuda de “j”, asigna valores, así cuando “j” llega al
límite de celdas (meses) se aumenta una unidad el contador “i” para continuar con la
otra fila y recorrerla de nuevo.
La función “calculaTotal” tiene una forma muy similar, pero en ésta no se modificarán
valores, sino que se hará la suma de los elementos de cada fila para presentarlos en
pantalla. Una variable “total” se encarga de ir sumando los productos vendidos cada
73
que se cambia de fila en la tabla, para después, al término de los ciclos presentar el
total de artículos vendidos en esos meses.
Puede aumentarse el número de filas y columnas de la tabla modificando los valores
de las constantes “vendedores” y “meses” respectivamente, definidas al comienzo
del programa.
En cuanto al uso de arreglos cadenas de caracteres se dejará para el siguiente
capítulo, porque es necesario considerar conceptos de direcciones de memoria, el
uso de los apuntadores.

No hay comentarios:

Publicar un comentario