12
Universidad Nacional del Litoral Facultad de Ingeniería y Ciencias Hídricas Departamento de Informática FUNDAMENTOS DE PROGRAMACIÓN Asignatura correspondiente al plan de estudios de la carrera de Ingeniería Informática UNIDAD 9 ARRAYS y STRUCTS Ing. Horacio Loyarte ® 2015

Fu Pro Guia09 Teoria Arrays Structs

Embed Size (px)

Citation preview

Universidad Nacional del Litoral Facultad de Ingeniería y Ciencias Hídricas

Departamento de Informática

FUNDAMENTOS DE PROGRAMACIÓN Asignatura correspondiente al plan de estudios

de la carrera de Ingeniería Informática

UNIDAD 9 ARRAYS y STRUCTS

Ing. Horacio Loyarte ® 2015

Unidad 9

UNIDAD 9

Arrays y Structs

Introducción En esta unidad aprenderemos a emplear dos importantes estructuras de datos en C++. Aplicaremos primero los conceptos estudiados en Fundamentos de Programación: arreglos, empleando la sintaxis de C++. Veremos cómo declarar y definir arreglos estáticos en C++, como se almacenan en memoria y como se acceden para ser modificados o utilizados. Analizaremos sus ventajas y limitaciones. Además estudiaremos otra estructura de datos importante, denomina en C/C++ struct , y analizaremos el porqué de su presencia y las ventajas de su empleo. Veremos que es posible combinar estructuras de datos de acuerdo a las necesidades del caso a resolver.

Definición de arreglo

Definimos como array a la estructura de datos formada por una secuencia de elementos homogéneos (de igual tipo). Cada elemento tiene una posición relativa ­que puede ser establecida por uno o más índices­ dentro de la secuencia.

Características de los arreglos estáticos en C++

Un arreglo es una colección de datos relacionados de igual tipo e identificados bajo un nombre genérico único.

La estructura completa ocupa un segmento de memoria único y sus elementos se almacenan en forma contigua.

Para procesar un elemento del arreglo, se debe especificar el nombre de la estructura y uno o más índices que determinan la posición del elemento.

Se puede emplear como índice cualquier expresión que arroje un entero dentro del rango establecido (dimensión) para dicho índice.

El índice que determina la posición de los elementos de un arreglo en C++ comienza siempre con cero.

Se debe establecer en la declaración, la cantidad de elementos (dimensión) que puede tener como máximo el arreglo en el programa.

Al compilar y ejecutar un programa C++, no se verifica automáticamente la validez de los índices, sino que es responsabilidad del programador

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

asegurarse de que nunca se utilice un índice inadecuado de acuerdo al tamaño de un arreglo. Si ese fuera el caso, el comportamiento del programa es indefinido.

Clasificación de los Arreglos

En base al número de índices que determinan la posición de un elemento en el arreglo podemos definir: Arreglos lineales o unidimensionales: la posición de un elemento la determina

un único índice. También conocidos como listas o vectores Ejemplo: en el caso de un arreglo lineal v de 200 elementos de tipo int:

v[0] v[1] v[2] v[3] v[4] v[5] v[6] .......... v[199] 23 56 71 19 33 90 48 .......... 74

Arreglos bidimensionales: se requieren 2 índices para posicionar un elemento en la colección. También conocidos como tablas o matrices.

Ejemplo: observemos gráficamente el caso de un arreglo de números de punto flotante (float)de 5 filas por 12 columnas:

0 1 2 3 11 0 5.34 6.71 4.22 12.02 ... ... ... ... 7.92 1 1.55 1.16 1.65 11.34 ... ... ... ... 1.16 2 6.87 3.12 6.37 13.00 ... ... ... ... 3.16 3 3.21 5.90 3.24 13.72 ... ... ... ... 5.98 4 4.04 8.00 10.11 13.82 ... ... ... ... 4.43

Arreglos multidimensionales: se requieren más de 2 índices para posicionar un

elemento en la colección. También conocidos como tablas o matrices multidimensionales.

Organización en memoria de los arreglos C++, como la mayoría de los lenguajes de programación, reserva segmentos de memoria contigua para almacenar los arreglos. El tamaño de memoria reservada se basa en la declaración del arreglo, y es un segmento estático: el arreglo existe mientras exista el scope(ámbito) en el que fue creado, y su tamaño siempre será el mismo. Si el arreglo se declara en main, durante toda la vida o ejecución del programa, estará ocupando la porción de memoria necesaria y ese recurso no podrá ser empleado en otra cosa. Si se lo declara dentro de una función o bloque, ocupará memoria mientras el control de ejecución opere dentro de la función o del bloque.

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

Declaración e inicialización de un arreglo Arreglos lineales Los arreglos estáticos en C++ se declaran y definen como cualquier otra variable (primero tipo y luego nombre), pero agregando su dimensión entre corchetes al final. La dimensión de la estructura es la cantidad máxima de elementos que puede contener: float lista[200]; for (int i=0; i<200; i++) cin >> lista[i]; Tener en cuenta que C++ considera al primer elemento de un array en la posición 0, por tanto una declaración como la del ejemplo, donde en dimensión se indica 200, las posiciones válidas del arreglo irán desde 0 a 199 inclusive (notese el signo de menor estricto en la condición del for). Si intenta acceder al elemento de la posición 200 el acceso será inválido, pero recuerde que en C++ la validez de los índices no se verifica al ejecutar, y por lo tanto el problema podría pasar desapercibido y ser muy difícil de encontrar. Al igual que ocurría con los arreglos en pseudocódigo, el tamaño en la declaración del mismo debe ser constante. Si no se conoce antes de comenzar la ejecución el tamaño óptimo para el arreglo se deberá recurrir a la sobredimensión. Sin embargo, en C++ podemos calificar a una variable como constante utilizando la palabra clave const, y en ese caso será posible utilizar dicha variable como tamaño para un arreglo: const int m = 100; int z[m]; Inicialización de los elementos de un arreglo Para inicializar un arreglo en el programa podemos recorrer cada elemento del mismo y asignar los valores correspondientes, tal como lo hacíamos en pseudocódigo. // arreglo x formado por 100 enteros al azar int x[100]; for (int i=0; i<100; i++) x[i] = rand(); En este ejemplo, al crear el arreglo, se reserva memoria suficiente para los 100 elementos enteros, pero no se inicializa en ese momento ninguna de esas posiciones. Se dice que el arreglo contiene basura (cualquier valor que haya estado en esa porción de memoria al momento de crear el arreglo). Si bien es común en la mayoría de los casos esa “basura” sea casualmente 0, el programador no puede considerar válido a este valor (esto sería nuevamente un comportamiento indefinido), y tiene la obligación de asignar valores a las posiciones del arreglo con las cuales deba operar (en el ejemplo, esto se hace en el for).

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

Existe además una sintaxis alternativa que permite definir e inicializar el arreglo en una misma instrucción: char z[7] = ‘J’, ‘M’, ‘L’, ‘P’, ‘Q’, ‘W’, ‘H’; En este caso, los 7 valores de tipo char del arreglo tomarán los valores enumerados en la lista de constantes de tipo char ingresada entre llaves (lista de inicialización). Finalmente, hay dos situaciones en las que se puede omitir parte de la información en una definición e inicialización: int arreglo_de_3_enteros[] = 0, 42, 100 ; int arreglo_de_100_ceros[100] = 0 ; En el primer caso, se omite la dimensión del arreglo. El compilador puede deducir la dimensión contando la cantidad de elementos de la lista de inicialización correpondiente. En el segundo caso se omite 99 de los 100 elementos en la lista de inicialización. El compilador completará la lista añadiendo ceros. Es importante notar que el valor con se completa automáticamente una lista en casos como el segundo es siempre 0, sin importar cual o cuales sean los valores explicitados en la lista. Arreglos bi/multi­dimensionales Para operar con arreglos bidimensionales (matrices) debemos declarar las dos dimensiones de la tabla, cada una entre corchetes: int mat[10][6]; // matriz mat de 10x6 enteros float tab[3][7][5]; // arreglo multimensional de 3x7x5 valores flotantes /* Inicialización de la matriz mat recorriéndola por filas con datos ingresados por consola */ for (int i=0; i<10; i++) for (int j=0; j<6; j++) cout<<"fila "<<i<<" columna "<<j<<":"; cin>>mat[i][j]; Cuando operamos sobre arreglos bi­dimensionales, es común en C++ asumir que el primer índice se corresponde con el número de filas, mientras que el segundo con el número de columnas. El ejemplo siguiente contiene el código C++ que permite mostrar una matriz dispuesta en filas y columnas en la pantalla.

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

// Ejemplo: mostrar una matriz de 2x3 elementos en // de tabla #include<iomanip> using namespace std; int main() int m[2][3]= 12,34,56, 78,90,100 ; int i,j; for (i=0;i<2;i++) for (j=0; j<3; j++) cout<<setw(4)<<m[i][j]; cout<<endl; //avanza a la próxima línea

En el ejemplo anterior se ha inicializado una matriz m de 6 elementos enteros donde se asignan los datos por filas. Es decir que: intm[2][3]=12,34,56,78,90,100;ha permitido asignar los datos de la manera siguiente:

m[0][0]=12 m[0][1]=34 m[0][2]=56 m[1][0]=78 m[1][1]=90 m[1][2]=100

SI quisiéramos mostrarlos por columnas solo deberíamos intercambiar los ciclos for del ejemplo.

Arreglos como parámetros de funciones Es posible utilizar arreglos como parámetros de funciones. En C++ no es posible pasar por valor un arreglo estático completo como parámetro a una función. Al pasar un arreglo a una función, aunque se lo que en realidad se envía a la función es la dirección de memoria de comienzo del mismo, lo cuál es rápido y eficiente. Pero, al pasar una dirección de memoria estamos efectuando un pasaje por referencia, ya que al modificar algún elemento del arreglo en la función, estamos modificando en realidad el arreglo del programa cliente (ya que utilizamos su misma dirección de memoria). Para admitir arreglos lineales como parámetros lo único que se debe hacer al declarar la función es especificar en el argumento el tipo de dato que contiene el arreglo, un identificador y un par de corchetes vacíos [ ]. Por ejemplo, observemos la siguiente función:

void leer_arreglo(int arg[]); Vemos que la función leer_arreglo( )admite un parámetro llamado arg que es un arreglo de enteros (int). ¿Donde está el pasaje por referencia o la

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

dirección de memoria del inicio del arreglo?. Respuesta: en el nombre del arreglo.

El nombre del arreglo identifica a una variable que contiene la dirección de memoria donde comienza el bloque donde se aloja el arreglo. En otras palabras: el nombre del arreglo tiene la dirección de memoria del elemento 0 del arreglo.

La sintaxis alternativa para el protipo de la función deja esto en evidencia:

void leer_arreglo(int *arg); Este prototipo es equivalente al anterior. El * en arg indica que es un puntero (una dirección de memoria). Para llamar a la función leer_arreglo() del ejemplo anterior debemos utilizar como parámetro actual un arreglo; para ello es suficiente utilizar el identificador del arreglo:

int miarreglo [30]; leer_arreglo(miarreglo);

Pero existe un problema adicional. Una función que recibe un arreglo como dirección de memoria donde comienzan sus datos, no puede determinar el tamaño del mismo (dónde terminan los datos). Es por ello que en general una función que recibe un arreglo estático también recibe un entero indicando el tamaño del mismo.

En el siguiente ejemplo se invoca 2 veces a la función muestra_arreglo( ) pasándole en cada caso un arreglo y su longitud. // Ejemplo: arreglos como parámetros #include <iostream> #include <iomanip> using namespace std; void muestra_arreglo (int lista[], int largo); int main () int v1[] = 35, 41, 22; int v2[] = 12, 4, 6, 15, 10; muestra_arreglo(v1,3); muestra_arreglo(v2,5); void muestra_arreglo (int lista[], int largo) for (int i=0; i<largo; i++) cout<<setw(4)<<lista[i]; cout<<endl;

La salida del programa será:

35 41 22

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

12 4 6 15 10 Obsérvese que en el ejemplo se invoca dos veces a la función muestra_arreglo() empleando como argumentos arreglos de diferente dimensión. Arreglos multidimesionales Si se trata de pasar como parámetro un arreglo de más de una dimensión, el parámetro formal debe tener los corchetes correspondientes al primer índice vacíos, pero establecer explícitamente las dimensiones restantes.

void leer_tabla(int t[][10], int num_filas); El modificador const y parámetros de tipo arreglo Hemos visto que al pasar un arreglo como parámetro, se pasa la dirección de memoria del inicio del arreglo y por lo tanto estamos efectuando un pasaje de parámetros por referencia. Esto implica que cualquier modificación efectuada en uno o más elementos del arreglo durante la ejecución de una función, implicará la automática modificación del arreglo utilizado como parámetro actual o de llamada. Para evitar que un arreglo sea modificado al pasarlo como parámetro, debe utilizarse el modificador const precediendo al parámetro formal en el prototipo de la función. Observemos las dos funciones siguientes: void permite_cambiar( int lista[], int n) ...... lista[3] = 255; //modifica el parámetro de llamada ..... void no_permite_cambiar(const int lista[], int n) ..... lista[10] = 320; //error de compilación ..... En la primer función, el cambio efectuado en lista[3] se producirá simultáneamente el arreglo que se utilice como parámetro para llamar a esta función. La segunda función producirá un error de compilación, pues se establece que el parámetro lista[] debe permanecer constante. Arreglos multidimesionales y funciones para arreglo lineales El nombre de un arreglo, sin sus correspondientes índices, se interpreta como una dirección de memoria o puntero que indica dónde comienzan los datos. Cuando el arreglo tiene más de una dimensión, algo similar ocurre cuando no se colocan todos los índices correspondientes: int A[10][5];

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

cout << “Direc. de A” << A << endl; cout << “Direc. de la 3er fila de A” << A[2] << endl; cout << “Valor de 3er fila, 2da col:” << A[2][1]; Entonces es posible utilizar una fila de un matriz como si fuera un arreglo lineal: void muestra_arreglo (int lista[], int largo) for (int i=0; i<largo; i++) cout<<setw(4)<<lista[i]; cout<<endl; int main() int matriz[3][4] = 1, 2, 3, 4 , 2, 4, 6, 8 , 3, 5, 7, 9 ; cout << “Segunda fila: “ << endl; muestra_arreglo( matriz[1], 4 ); Nótese que solo se puede obviar el segundo índice (en un caso general los últimos x índices). Es decir, este enfoque sirve para mostrar una columna en lugar de una fila. Es por esto que en C++ es común denominar fila al primer indice y columna al segundo.

Estructuras. El tipo struct. Un arreglo es una estructura de datos homogénea, es decir solo admite una colección de elementos de igual tipo. A menudo se requiere organizar los datos de una entidad en una estructura, pero admitiendo información de diferente naturaleza (tipo). C++ dispone para este caso del tipo struct. Un struct en C++ agrupa una colección de variables, que pueden ser de diferente tipo. Cada variable (denominada variable miembro, campo, o atributo) debe declararse individualmente. Su sintaxis general es la siguiente: struct nombre

miembro 1; miembro 2; miembro 3; ..... miembro n;

;

En la definición es obligatorio explicitar el tipo struct y a continuación el identificador (nombre en el ejemplo) de la estructura. Luego, entre paréntesis deben definirse cada uno de los componentes o miembros). Estos componentes o miembros pueden ser variables simples, arreglos, o incluso instancias de otros structs.

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

Tomemos el siguiente ejemplo:

ficha apellido nombres Dni Edad Cant_materias

Los nombres de estos miembros o componentes deben ser diferentes, pero pueden coincidir con el identificador de alguna otra variable definida fuera de dicha estructura, ya que sólo serán válidos dentro del struct. Un struct como el del ejemplo podría declararse de la siguiente manera: struct ficha

string apellido; string nombres; long dni; int edad; int cant_materias;

; Al definir una estructura estamos planteando el esquema de la composición pero sin definir ninguna variable en particular. Es decir, en el ejemplo decimo cómo van a ser las fichas, pero todavía no existe ninguna ficha en particular. ficha podrá ser considerado como un nuevo tipo de datos, y usado para declarar variables de ese tipo:

ficha x,y; // x e y se declaran de tipo ficha

Se puede utilizar una lista de inicialización de forma similar a la que se presentó para arreglos, listando los valores para cada campo en el orden en que fueron declarados en el struct: ficha x =“Lopez”, “Gerardo”, 24567890, 21, 11; En este ejemplo, ficha es el nombre de la estructura, xla variable de tipo ficha y los datos especificados entre los valores iniciales de los miembros apellido, nombres, dni, edad, cant_materias. Procesamiento de una variable struct Para procesar la información relacionada a una estructura podemos operar con sus miembros individualmente o en ocasiones con la estructura completa. Para acceder a un miembro individual debemos utilizar el identificador de la variable struct, un punto de separación y el nombre del miembro componente.

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

variable.miembro Considere el siguiente código de ejemplo.

1.#include <iostream> 2.using namespace std; 3.//­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ 4.struct registro 5. string ape; 6. string nom; 7. long dni; 8.;

//­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ 9.int main( ) 10. registro f,z; 11. cin>>f.ape; 12. cin>>f.nom; 13. cin>>f.dni; 14. z=f; 15. cout<<z.ape<<" "<<z.nom<<"­­­DNI:"<<z.dni<<endl; 16. return 0; 17.

Observe en la línea 10 la declaración de las variables struct f y z. Las líneas 11, 12 y 13 permiten asignar datos ingresando los valores de los miembros en modo consola. En la línea 14 se asigna la variable struct completa f a una variable de igual tipo z. En 15 se muestran los miembros de z. Es posible entonces asignar un struct a otro. Esto permite entonces, a diferencia del caso de arreglos, pasar efectivamente por copia structs a funciones, o hacer funciones que retornen structs. Sin embargo no es posible mostrar (cout) o leer (cin) un struct completo en una sola instruccion. Estas operaciones deben realizarse sobre cada uno de sus campos por separado.

Arreglos y structs Es posible emplear arreglos como miembros de una composición struct y también definir un arreglo cuyos elementos sean estructuras: ficha z[5] = “Lopez”, “Gerardo”,24567890, 21, 11, "Giménez","Ana", 25689901, 20, 15, "Zapata", "Andrés", 26701231, 19, 8,

"Farías", "Marina", 23199870, 22, 15, "Martino","Manuel", 24500654, 21, 10 ;

struct EquipoFutbol string nombre; string tecnico; string titulares[11]; string suplentes[5]; ;

Ingeniería Informática – Fundamentos de Programación 2015

Unidad 9

En el primer anterior se declara e inicializa un arreglo z de 5 elementos de tipo ficha. Para mostrar el miembro dni del tercer elemento del arreglo debemos escribir:

cout<<z[2].dni; Para cambiar el miembro edad a 22 en el 4to elemento del arreglo z debemos escribir:

z[3].edad=22; Para leer los apellidos, nombres y dni en modo consola:

for (int i=0; i<5; i++) cin.getline( z[i].apellido,15); cin.getline( z[i].nombres,20 ); cin>>z[i].dni; Para mostrar un listado con los miembros apelido y dni del arreglo z, se codifica de la siguiente forma:

for (int i=0; i<5; i++) cout<<z[i].apellido<<" "<<z[i].dni<<endl; En el segundo ejemplo se define un struct que representa un equipo de futbol, y contiene un arreglo de 11 nombres de jugadores titulares, y otro de 5 nombres para los suplentes. La lista de titulares se podría mostrar de la siguiente forma: EquipoFutbol e1; for (int i=0; i<11; i++) cout << e1.titulares[i] << endl;

Ingeniería Informática – Fundamentos de Programación 2015