2-arreglos

44
ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II _________________________________________________________________________________________________ ___ UNIDAD II ESTRUCTURA DE DATOS INCORPORADOS 2.1. ARREGLOS UNIDIMENSIONALES. DATOS DE TIPO ESTRUCTURADO.- En los lenguajes existen tipos de datos estructurados los cuales nos permiten tener variables que manejan conjunto de valores los cuales pueden ser del mismo tipo o de distinto tipo, con lo cual se puede hacer programas más eficiente sobre todo cuando se manejan de datos. ARREGLOS (array).- Los arreglos son variables de tipo estructurado que manejan un conjunto de datos del mismo tipo los arreglos pueden ser del tipo unidimensional y multidimensional, a los unidimensionales también se les conoce como vectores y a los bidimensionales (matrices) Un arreglo puede definirse como un grupo o una colección finita, homogénea y ordenada de elementos. Los arreglos pueden ser de los siguientes tipos: De una dimensión. De dos dimensiones. De tres o más dimensiones. Un arreglo unidimensional es un tipo de datos estructurado que está formado de una colección finita y ordenada de datos del mismo tipo. Es la estructura natural para modelar listas de elementos iguales. El tipo de acceso a los arreglos unidimensionales es el acceso directo, es decir, podemos acceder a cualquier elemento del arreglo sin tener que consultar a elementos anteriores o posteriores, esto mediante el uso de un índice para cada elemento del arreglo que nos da su posición relativa. Para implementar arreglos unidimensionales se debe reservar espacio _________________________________________________________________________________________________ ___ ESTRUCTURA DE DATOS ING. OSORNIO 12

Upload: haziel-diaz

Post on 08-Aug-2015

8 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

UNIDAD II

ESTRUCTURA DE DATOS INCORPORADOS

2.1. ARREGLOS UNIDIMENSIONALES.

DATOS DE TIPO ESTRUCTURADO.- En los lenguajes existen tipos de datos estructurados los cuales nos permiten tener variables que manejan conjunto de valores los cuales pueden ser del mismo tipo o de distinto tipo, con lo cual se puede hacer programas más eficiente sobre todo cuando se manejan de datos.

ARREGLOS (array).- Los arreglos son variables de tipo estructurado que manejan un conjunto de datos del mismo tipo los arreglos pueden ser del tipo unidimensional y multidimensional, a los unidimensionales también se les conoce como vectores y a los bidimensionales (matrices)

Un arreglo puede definirse como un grupo o una colección finita, homogénea y ordenada de elementos. Los arreglos pueden ser de los siguientes tipos:

De una dimensión. De dos dimensiones. De tres o más dimensiones.

Un arreglo unidimensional es un tipo de datos estructurado que está formado de una colección finita y ordenada de datos del mismo tipo. Es la estructura natural para modelar listas de elementos iguales.

El tipo de acceso a los arreglos unidimensionales es el acceso directo, es decir, podemos acceder a cualquier elemento del arreglo sin tener que consultar a elementos anteriores o posteriores, esto mediante el uso de un índice para cada elemento del arreglo que nos da su posición relativa.

Para implementar arreglos unidimensionales se debe reservar espacio en memoria, y se debe proporcionar la dirección base del arreglo, la cota superior y la inferior.

REPRESENTACION EN MEMORIA

Los arreglos se representan en memoria de la forma siguiente: x : array[1..5] de enteros Para establecer el rango del arreglo (número total de elementos) que componen el arreglo se utiliza la siguiente formula:

RANGO = Ls - (Li+1)donde: ls = Límite superior del arreglo li = Límite inferior del arreglo

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 12

Page 2: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

2.1.1. ARREGLOS UNIDIMENSIONALES DE CARACTERES (CADENAS), ENTEROS, FLOTANTES

2.1.1.1 ACCESO A LAS DIFERENTES LOCALIDADES DE UN ARREGLO DE CARACTERES UNIDIMENSIONALES

Existen tipos de datos estructurados que permiten tener variables que manejen un conjunto de datos al mismo tiempo (los datos que contiene pueden o no ser del mismo tipo), con lo que se puede manejar grandes cantidades de datos.

Los arreglos son variables de tipo estructurado que manejan datos del mismo tipo, los arreglos pueden ser unidimensionales o multidimensionales, a los unidimensionales se les conoce vectores, a los bidimensionales como matrices. Los nombres dados a estas variables deben seguir las reglas de cualquier identificador. Para que estas variables puedan manejar un conjunto de datos deben usar el mismo nombre pero con índices, a esta forma de direccionamiento se le conoce como localidad del arreglo, en donde cada una de las localidades (de memoria) guardará uno de los valores del conjunto de datos, todos los arreglos utilizados en C++ se direccionan a partir de la localidad cero.

Vectores. Se llama así a los arreglos unidimensionales,

x[0] x[1] Direccionamiento

x[2] (localidades del arreglo) x[3]

Para declarar un arreglo unidimensional se deberá poner el tipo de datos que maneja el arreglo, el nombre y entre corchetes cuadrados con un número entero (o una constante) la cantidad total de localidades, por ejemplo:

int x[20]; float y[10]; char a[15];

Los arreglos pueden inicializarse a la vez que se declaran, esto es, si enseguida de la declaración del arreglo se pone el signo igual y entre llaves los valores con los cuales sé inicializa el arreglo:

int z[5] = {1,4,0,10,-2};char c[6] = {‘a’,’e’,’i’,’o’,’u’,’\0};

Si se deseara inicializar con cero un arreglo numérico, se puede poner entre llaves un cero y de esta manera automáticamente todas las localidades tomarán el valor de cero, esto se muestra a continuación:

int w[5] ={0};

Para manejar adecuadamente este tipo de variables es importante recordar que junto con el nombre se debe indicar a qué localidad del arreglo se está haciendo mención, y de esta forma se puede usar como cualquier variable de tipo simple

por ejemplo. y = x[3] + 3* x[2]; x[0] = x[4]/4;

x[1] = 7; printf(“\n %d”,x[3]);

scanf (“ %d “,&x[2]);

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 13

Page 3: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

Cuando se desea leer la totalidad de un arreglo se debe hacer mediante una estructura de repetición, se recomienda el uso del for por ser la estructura que incluye más posibilidades en sí misma, aunque sin descartar las otras, un ejemplo de cómo se realiza esto se pone a continuación:

for (i=0; i <=9; i++){ printf(“\n Dé el valor de la localidad(%d):”,i); scanf(“%d”, &x[i]);}

Para desplegar el contenido de un arreglo también se emplea una estructura de control, for, preferentemente, como se muestra:

for(i=0; i<=9; i++)printf(“\n %d”, x[i]);

CADENA DE CARACTERES (STRING).- Un caso especial de los arreglos unidimensionales será él emplearlos para poder manejar variables de cadena es decir que si declara un arreglo de caracteres, con las funciones adecuadas se puede manejar como una sola cadena, las funciones a emplearse serán gets( ) y puts( ) con las cuales respectivamente se leerá la cadena o se desplegara en pantalla, en el siguiente ejemplo se supondrá que se declaro un arreglo unidimensional de caracteres el cual se usara para poder manejarse como cadena, en las cadenas se deberá considerar una localidad de la cadena para el terminador nulo ('\0') pues al leer la cadena esto es agregado automáticamente por lo que si la cadena a almacenarse en el arreglo requiere de la totalidad de las localidades es aconsejable declararlo con mas localidades pues el terminador nulo lo Tomaría la ultima, recortando la cadena a leer, también se requiere considerar que las funciones gets( ) y puts( ) al emplearse para leer o desplegar la cadena solo usara el nombre del arreglo pues al usarse como cadena no se requiere de los corchetes pues en ese caso se estaría empleando como arreglo de caracteres y no como una cadena.

char nombre[20];

gets(nombre); puts(nombre);

Para poder manipular cadena en los programas en turbo C esto se deberá de hacer mediante las funciones de cadena de las librería string.h si emplean esas funciones en un programa se deberá de declarar la librería mediante la palabra reservada include, a continuación se darán algunas de estas funciones ( las más comunes de emplearse) y su sintaxis.

char strcpy (char *dest, const char src) Copia src dest después de que el terminador nulo ha sido movido

char strcat (char *dest, const char src) Concadena cadenas.

int strcmp (const char *sl, const char *s2) Compara dos cadenas devuelve o si son iguales>0 si s1>s2 y <0 si s1<s2size_t strlen(const char *s) Determina la cantidad de caracteres contenida en una cadena.

char strlwr(const char *s) Convierte la cadenas a minúsculas

char strupr(const char *s) Convierte la cadenas a mayúsculas.

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 14

Page 4: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

A continuación se dan algunos ejemplos de programas que utilizan arreglos unidimensionales.

1.- Programa que inicializa un arreglo de enteros a ceros, lo lee y lo despliega

#include<stdio.h>#include<conio.h>

void main ( ){int i,x[10]={0};

clrscr( );gotoxy(6,3); printf(“Práctica # 7:”);gotoxy(6,5); printf(”ARREGLOS UNIDIMENSIONALES ”);gotoxy(6,7);printf(“Programa que inicializa un arreglo de enteros a ceros, lo lee y lo despliega”);printf(“los valores de inicializacion y los leidos en pantalla \n\n\n”);

for(i = 0; i<10; i++)printf(“%3d”,x[i]);printf(“\n\n”);

for (i=0; i<10; i++){printf(“\n Teclee el valor de la %d localidad:”, i);scanf(“%d”,&x[i]);}

printf(“\n\n”)for (i = 0; i<10; i++)

printf(“%3d”,x[i]);

getch();}

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 15

Page 5: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

2.- Este programa inicializara un arreglo de enteros a cero, después leerá n localidades del arreglo para ordenarlo y después desplegarlo y en orden, para la ordenación se empleara el método de la burbuja.

#include<stdio.h>#include<conio.h>#define size 100

void main ( ){int n,i,j,x[size]={0};clrscr( );gotoxy(6,3); printf(“Programa que lee arreglo de enteros, lo ordena y lo”);gotoxy(6,5); printf(”\n despliega ordenado en pantalla:”);gotoxy(6,7);

printf(“Cuantos datos va a leer en el arreglo”);scanf(“%d”,&n);

for (i=1; i<n; i++){printf(“\n Teclee el valor de la %d:”, i);scanf(“%d”,&x[i]);}

clrscr();

for (i = 1; i<n; i++)for (j = 1; j<n; j++)

if(x[j]>x[j+1]){ x[0]=x[j]; x[j]=x[j+1]; x[j+1]=x[0];

}printf(“\n\n”);for (i=1; i<=n; i++)

printf(“ %3d:”, x[i]);getch();}

2.2. ARREGLOS BIDIMENSIONALES 2.2.1. ARREGLOS BIDIMENSIONALES, MATRICES

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 16

Page 6: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

Arreglos bidimensionales (matrices). Para entender cómo funcionan este tipo de arreglos debemos imaginar que se trata de una caja (una cuadrícula en la cual cada división es una localidad del arreglo) y similarmente a los vectores se deben direccionar con índices, pero en este caso se trata de dos índices, es decir una fila y una columna, por lo que al declararla se debe poner el tipo de datos que va a manejar, el nombre y la totalidad de filas y columnas cada una entre corchetes,

int x[3][4]; int A[4][3]; float x[4][6]; char c[10]20];

En el ejemplo utilizado tenemos una matriz de 3 filas (de la 0 a la 2) y cuatro columnas(de la 0 a la 3), dando un total de 12 casillas que tendrán el nombre x y manejarán datos de tipo entero.

Podemos decir que el contenido de cada una de las casillas representa una localidad de memoria y el contenido entre ellas es independiente, con lo único que debe cumplirse que todos loa valores guardados sean del mismo tipo de datos que se declaró.Las casillas se direccionan a partir de la localidad 0,0, como se muestra:

x[0][0] x[0][1] x[0][2] x[0][3]

x[1][0] x[1][1] x[1][2] x[1][3]

x[2][0] x[2][1] x[2][2] x[2][3]

La manera de utilizar estas variables será siempre acompañadas del direccionamiento necesario para poder diferenciar cada localidad, esto es, no basta con el nombre del arreglo sino que se debe indicar entre corchetes la fila y columna de cada localidad utilizada. Las casillas pueden formar parte de expresiones, asignársele valores, leer o desplegar datos en forma individual de cada casilla del arreglo, como se muestra

A[2][0]=6; A[2][1]3*c + 2*a; printf(“\n %d”,A[2][2]); scanf(“%d”,&A[2][2]);

Algunas veces se requiere inicializar los arreglos, leerlos o desplegar la totalidad del arreglo, la inicialización se puede realizar de diferentes formas: int A[4][3]=0, es una de ellas

Se pueden utilizar estructuras de repetición anidadas para las diferentes funciones. Para leer el contenido de todas las localidades:

for(i=0;i<3;i++)

for(j=0;j<4;j++){printf(“\n Dé el valor de la localidad %d%d”,i,j);scanf(“%d”&A[i][j];}

De igual manera se usa una estructura de repetición anidada para desplegar el contenido del arreglo:

for(i=0;i<3;i++)

for(j=0;j<4;j++)printf(“\n %d”,A[i][j]);

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 17

Page 7: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

Los arreglos pueden manejar caracteres o cadenas de caracteres y se utilizan en C muy comúnmente.

#include<stdio.h>#include<conio.h>#include<time.h>#define NULL 0#define MZ 5#define MM 6

int i,j;float acum,zona[MZ],edad [MZ][MM];char enter;void main(){time_t t; t=time(NULL);textbackground(BLUE);textcolor(YELLOW); clrscr();gotoxy(25,1); printf("Fecha:%s",ctime(&t));gotoxy(6,5); printf("Manejo de ARREGLOS BIDIMENSIONALES");gotoxy(6,7); printf("1=Zona Norte;2=Zona Sur;3=Zona Este;4=Zona Oeste;5=Zona Centro");printf("\n\n\n");

for(i=0;i<MZ;i++) zona[i]=0;for(i=0;i<MZ;i++) for(j=0;j<MM;j++) edad[i][j]=0;

for(i=0;i<MZ;i++) for(j=0; j<MM;j++) { printf("Teclee de la zona(%d),la edad(%d):",i+1,j+1); scanf("%f%c",&edad[i][j],&enter); }for(i=0;i<MZ;i++) { acum=0; for(j=0;j<MM;j++) acum=acum + edad[i][j]; zona[i]=acum/MM; }printf("1=Zona Norte; 2=Zona Sur; 3=Zona Este, 4=Zona Oeste; %=Zona Centro");for(i=0;i<MZ;i++) printf("\n La zona (%d),tiene un promedio de edad de:%10.4f,i+1,zona[i]");printf("\n\nPresione cualquier letra para continuar...");getch();textbackground(BLACK);textcolor(WHITE);clrscr();}

2.3. REGISTROS . 2.3.1. REGISTROS Y PUNTEROS

2.3.1.1 ACCESOS A LOS CAMPOS DE UN REGISTRO MEDIANTE PUNTEROS2.3.2. ARREGLOS DE REGISTROS, REGISTROS DE REGISTROS

Los conjuntos de datos se organizan frecuentemente en la jerarquía de campos, registros y

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 18

Page 8: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

archivos. Concretamente un registro lo constituyen un conjunto de elementos relacionados entre sí cada uno de los cuales recibe el nombre de CAMPO o ATRIBUTO y un archivo un conjunto de registros similares. Cada elemento a la vez elemento. Aquellos elementos que son individuales reciben el nombre de elementos simples, átomos o escalares.

Aunque un registro es un conjunto de elementos se diferencia de un arreglo lineal por lo siguiente:

a) Un registro puede ser un conjunto de datos no homogéneos.b) Los elementos de un registro son referencia a través del nombre de cada atributo por lo que no existe

un orden natural de los elementos.

Los elementos de un registro forman una estructura jerárquica que puede describirse a través de números de nivel.

ESTUDIANTE

NOMBRE SEXO Fecha de nacimiento

EDAD

PATERNO MATERNO NOMBRES Día Mes Año

1. ESTUDIANTE

2. NOMBRE3. APELLIDO PATERNO3. APELLIDO MATERNO3. NOMBRES

2. SEXO2. FECHA DE NACIMIENTO

3. DIA3. MES3. AÑO

2. EDAD

ESTRUCTURA DE DATOS (struct).- Otro de los tipos de datos estructurado que existen en turbo C son las estructuras (REGISTROS) las cuales se puede manejar al igual que los arreglos un conjunto de datos pero a diferencia de estos pueden ser de distinto, la manera de declarar estas estructuras es fuera de todo bloque del programa principal, se constituye igual que un registro el cual se compone de un conjunto de datos, a continuación se muestra como debe declararse una estructura.

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 19

Page 9: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

struct <nombre>{

campo 1;campo 2;

campo n;} variables;

De lo anterior podemos decir que nombre se refiere al nombre que se le va a dar al tipo de estructura a crear la cual variara de aplicación es decir cuantos campos empleara y de que tipo será cada uno de estos por lo que el nombre equivale a declarar un tipo especifico de datos, las variables se refiere a que una vez definida la estructura y sus campos, estos deberán manejarse mediante una variable que corresponda a ese tipo, cada campo deberá declararse como se declaran la variables en un programa, a continuación se da un ejemplo de una estructura.

struct datos_per{

char nom[20];int edad;int estatura;

} d,x[5];

En el ejemplo anterior podemos observar que la estructura se nombra como datos_per que contiene tres campos uno de ellos es una cadena de caracteres (un arreglo unidimensional de caracteres), y dos campos de enteros para manejar datos numéricos, finalmente las variables a emplear por esta estructura y que corresponden al tipo datos_per son dos, la primera “d”, solo puede manejar un solo registro es decir nombre, una edad y una estatura, y la segunda equivale a un arreglo de registros es decir pueden manejar un conjunto de registros, por lo que la manera de leer o desplegar o asignar valores a los campos de un registro o estructura se muestra a continuación.

strcpy(d.nom, “Pedro”); d.edad=40; d. estatura=170;

printf (“%s”,d.nom); printf (“%d”,d.edad); printf (“%d”,d.estatura);

scanf(“%s”,&d.nom); scanf(“%d”,&d.edad); scanf(“%d”,&d.esatura);

Par el caso en el cual la variable sea un arreglo es decir un arreglo de registros se deberá asignar especificando el registro al que se refiere de la totalidad de los existentes en el arreglo (localidad), si se manejan de otra manera se deberá de hacer usando estructuras de control de repetición.

strcpy(x[2],nom,”Raul”) printf(“\n %s”,x[0].nom); scanf(“%s”,&x[3].nom);

for (i=0;i<5;i++)x[i ].edad=0

for (i=0;i<5;i++)printf(“%d”,x[i ].estatura);

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 20

Page 10: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

for (i=0;i<5;i++){ printf(“\n De ala edad del registro %d:”,i); scanf(“%d”, x[i ].edad);

Una variante de uso de estas estructuras es que al declarar la estructura no se declaran las variables y esta s se declaran posteriormente dentro del programa principal.

struct datos_per {

char nom[20];int edad;int estatura;

} d,x[5];

#include <stdio.h>#include <conio.h>

void main() {

struct datos per d, x[5];

El siguiente ejemplo nos muestra como debe declararse, leerse y desplegar en pantalla una estructura de datos primeramente solo se hará con una estructura sencilla.

#include <stdio.h>#include <conio.h>

struct agenda {

char nom[20];char tel [15];int edad;

} a;

void main(){clrscr();printf("PROGRAMA que registra el nombre edad y teléfono de 1 persona");printf("\n\n De el nombre ");scanf("%s",&a.nom);printf(" Da la edad ");scanf("%d",&a.edad);printf(" De el teléfono ");scanf("%s",&a.tel); printf("\n Los datos dela persona son:%s %d %s ",a.nom,a.edad,a.tel); getch();}

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 21

Page 11: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

El segundo ejemplo hará lo mismo que el programa anterior pero a diferencia del mismo es que la variable de la estructura será un arreglo que corresponda al tipo de l estructura, también se usara la variante de declararla dentro de la función.

#include <stdio.h>#include <conio.h>

struct agenda {

char nom[20];char tel [15];int edad;

}

void main(){int i; struct agenda b[5];clrscr();for (i=0;i<=4;i++)

{printf("PROGRAMA que registra el nombre edad y teléfono de 5 personas");printf("\n\n De el nombre %d: ",i);scanf("%s",&b[i].nom);printf(" Da la edad %d: ",i);scanf("%d",&b[i].edad);printf(" De el teléfono %d: ",i);scanf("%s",&b[i].tel);

}getch();clrscr();for (i=0;i<=4;i++)

printf("\nLos datos dela persona son:%20s %2d %15s ",b[i].nom,b[i].edad,b[i].tel); getch();}

INTRODUCCION A LOS PUNTEROS

Sea DATOS un arreglo decimos que la variable p es un puntero si p "apunta" hacia un elemento de DATOS es decir si p contiene la dirección de un elemento de DATOS de forma análoga decimos que un arreglo ptr es un arreglo de punteros si cada elemento de ptr es un puntero. Los punteros y los arreglo de punteros se utilizan para facilitar el procesamiento de información almacenada en DATOS.

Supongamos que una organización divide la lista de sus miembros en cuatro grupos donde cada grupo contiene una lista alfabética de los miembros que residen en una zona determinada.

Supongamos que la figura siguiente representa tal lista. Obsérvese que la lista contiene veintiún

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 22

Page 12: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

personas que cada grupo se compone de cuatro, nueve, dos y seis personas.

GRUPO 1 GRUPO 2 GRUPO 3 GRUPO 4SILVIA MARTHA TERESA MAYTEMARIO OMAR JUAN FRANCISCO

ALBERTO DAVID LAURAORLANDO AUSENCIO JULIA

GERMAN ALEJANDRARUBEN MAURICIOMIGUEL

ANTONIOSALVADOR

Supongamos que queremos almacenar la lista en memoria, pero diferenciando los distintos grupos en que esta dividido. Una forma de hacerlo es utilizando un arreglo bidimensional de 4xn donde cada fila contiene un grupo o también un arreglo de nx4 donde cada columna contiene 1. Aunque esta estructura nos permite llegar a cada elemento individualmente se desperdicia mucho espacio cuando los grupos varían mucho. Concretamente los datos necesitan un arreglo de 4x9 o de 4x4 es decir 36 elementos como mínimo para almacenar los 21 nombres.

Otra forma de almacenar la lista en memoria lo representamos en la siguiente figura.

1 SILVIA2 MARIO

GRUPO 13 ALBERTO4 ORLANDO5 MARTHA6 OMAR7 DAVID8 AUSENCIO9 GERMAN GRUPO 2

10 RUBEN11 MIGUEL12 ANTONIO13 SALVADOR14 TERESA

GRUPO 315 JUAN16 MAYTE17 FRANCISCO18 LAURA

GRUPO 419 JULIA20 ALEJANDRA21 MAURICIO2223 n-124 n

Es decir la lista se almacena en un arreglo lineal colocando un grupo detrás del otro. Claramente este método es muy eficiente en cuanto a la utilización del espacio también podemos procesar fácilmente

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 23

Page 13: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

la lista completa (podemos imprimir los nombre de la lista). Por el no existe para imprimir solo los miembros de algún grupo.

Una versión modificada de este método muestra en la siguiente figura.

1 SILVIA2 MARIO

GRUPO 13 ALBERTO4 ORLANDO5 $$$6 MARTHA7 OMAR8 DAVID9 AUSENCIO

10 GERMAN GRUPO 211 RUBEN12 MIGUEL13 ANTONIO14 SALVADOR 15 $$$16 TERESA

GRUPO 317 JUAN18 $$$19 MAYTE

20 FRANCISCO21 LAURA

GRUPO 422 JULIA23 ALEJANDRA24 MAURICIO25 $$$26 n-127 n

En este caso los nombre se almacenan secuencialmente pero intercalando entre cada grupo una marca para indicar el final de cada grupo.

Las dos estructuras anteriores pueden modificarse fácilmente de tal forma que cada grupo puede indexarse esto puede llevarse acabo utilizando un arreglo de punteros GRUPO que contiene las posiciones de los diferentes grupos o más concretamente, la posición del primer elemento de cada grupo.

1 SILVIA2 MARIO

GRUPO 13 ALBERTO4 ORLANDO5 MARTHA6 OMAR

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 24

Page 14: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

7 DAVID8 AUSENCIO9 GERMAN GRUPO 2

1 1 10 RUBEN2 5 11 MIGUEL3 14 12 ANTONIO4 16 13 SALVADOR5 22 14 TERESA

GRUPO 315 JUAN16 MAYTE17 FRANCISCO18 LAURA

GRUPO 419 JULIA20 ALEJANDRA21 MAURICIO2223 n-124 n

Obsérvese que el GRUPO[L] y GRUPO[L+]-1 contiene los elementos primero y ultimo del grupo L.

Los punteros en el Lenguaje C , son variables que " apuntan " , es decir que poseen la dirección de las ubicaciones en memoria de otras variables, y por medio de ellos tendremos un poderoso método de acceso a todas ellas .

Quizás este punto es el más conflictivo del lenguaje , ya que muchos programadores en otros idiomas , y novatos en C , lo ven como un método extraño ó al menos desacostrumbrado , lo que les produce un cierto rechazo . Sin embargo , y en la medida que uno se va familiarizando con ellos , se convierten en la herramienta más cómoda y directa para el manejo de variables complejas , argumentos , parámetros , etc , y se empieza a preguntar como es que hizo para programar hasta aquí , sin ellos .

La respuesta es que no lo ha hecho , ya que los hemos usado en forma encubierta , sin decir lo que eran . ( Perdón por el pequeño engaño ).

Veamos primero , como se declara un puntero :

tipo de variable apuntada *nombre_del_puntero ;

int *pint ;

double *pfloat ;

char *letra , *codigo , *caracter ;

En estas declaraciones sólo decimos al compilador que reserve una posición de memoria para albergar la dirección de una variable , del tipo indicado , la cual será referenciada con el nombre que hayamos dado al puntero .

Obviamente , un puntero debe ser inicializado antes de usarse , y una de las eventuales formas de hacerlo es la siguiente:

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 25

Page 15: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

int var1 ; /* declaro ( y creo en memoria ) una variable entera ) */

int *pint ; /* " " " " " un puntero que contendrá la dirección de una variable entera */

pint = &var1 ; /* escribo en la dirección de memoria donde está el puntero la dirección de la variable entera */

Como habiamos anticipado en capítulos anteriores " &nombre_de_una_variable " implica la dirección de la misma . Si se pregunta porque no se usaron otros símbolos en vez de & y * , que se confunden con la Y lógica de bits y el producto , ..... consuelese pensando que yo también me hice siempre esa pregunta . De cualquier manera es siempre obvio , en el contexto del programa el uso de los mismos .

Esquematicamente , lo que hemos hecho se puede simbolizar de la siguiente manera : donde dentro del recuadro está el contenido de cada variable .

Pint xxxxxx valor contenido por var1 Dirección de var1

yyyyyy (posición de memoria xxxxxx (posición de memoria ocupada por el puntero ) ocupada por la variable)

En realidad , como veremos más adelante , en la declaración del puntero , está implicita otra información : cual es el tamaño (enbytes) de la variable apuntada.

El símbolo & , ó dirección , puede aplicarse a variables , funciones , etc , pero nó a constantes ó expresiones , ya que éstas no tienen una posición de memoria asignada.

La operación inversa a la asignación de un puntero , de referenciación del mismo , se puede utilizar para hallar el valorcontenido por la variable apuntada . Así por ejemplo serán expresiones equivalentes :

y = var1 ;

y = *pint ;

printf("%d" , var1 ) ;

printf("%d" , *pint) ;

En estos casos , la expresión " *nombre_del_puntero " , implica " contenido de la variable apuntada por el mismo " . Veamos un corto ejemplo de ello :

#include <stdio.h>

main(){

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 26

Page 16: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

char var1 ; /*una variable del tipo caracter */

char *pchar; /* un puntero a una variable del tipo caracter */

pc = &var1 ; /*asignamos al puntero la direccion de la variable */

for (var1 = 'a'; var1 <<= 'z'; var1++)

printf("%c", *pchar) ; /* imprimimos el valor de la variable apuntada */

return 0 ;}

Vemos acá , que en el FOR se incrementa el valor de la variable , y luego para imprimirla usamos la dereferenciación de su puntero. El programa imprimirá las letras del abecedario de la misma manera que lo habría hecho si la sentencia del printf() huiera sido, printf("%c" , var1 ) .

Hay un error , que se comete con bastante frecuencia , y es cargar en la dirección apuntada por un puntero a un tipo dado de variable , el contenido de otro tipo de las mismas , por ejemplo :

double d = 10.0 ;

int i = 7 , *pint ;pint = &i ;

*pint = 10 ; /* correcto,equivale a asignar a i el valor 10 */ ;

*pint = d ; /* ERROR se pretende cargar en una variable entera un valor double */

pint = &d ; /* INCORRECTO se pretende apuntar a una variable double con un puntero declarado como apuntador a int */

pint = 4358 ; /* ?????? */

El primer error , la asignación de un double , produce la pérdida de información dada por la conversión automática de tipo de variable , ya vista anteriormente , el segundo produce un llamado de atención rotulado como " asignación sospechosa de un pointer " . Resumiendo , las variables ó constantes cargadas por dereferenciación de un puntero , deben coincidir en tipo con la declaración de aquel .

La asignación de una constante a un pointer , y no a la variable apuntada por él , es un serio error , ya que debe ser el compilador , el encargado de poner en él el valor de la dirección , aquel así lo declara dando un mensaje de " conversión de puntero no transportable " . Si bien lo compila , ejecutar un programa que ha tenido esta advertencia es similar a jugar a la ruleta rusa , puede "colgarse" la máquina ó lo que es peor destruirse involuntariamente información contenida en un disco , etc.

Hay un sólo caso en el que esta asignación de una constante a un puntero es permitida , muchas funciones para indicar que no pueden realizar una acción ó que se ha producido un error de algun tipo , devuelven un puntero llamado "Null Pointer" , lo que significa que no apunta a ningun lado válido , dicho puntero ha sido cargado con la dirección NULL ( por lo general en valor 0) , así la asignación : pint =

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 27

Page 17: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

NULL ; es válida y permite luego operaciones relacionales del tipo if( pint ) ..... ó if( print != NULL) para convalidar la validez del resultado devuelto por una función .

Una advertencia : si bien volveremos más adelante sobre este tema , debemos desde ahora tener en cuenta que los punteros no son enteros , como parecería a primera vista , ya que el número que representa a una posición de memoria , sí lo es . Debido al corto alcance de este tipo de variable , algunos compiladores pueden , para apuntar a una variable muy lejana , usar cualquier otro tipo , con mayor alcance que el antedicho .

PUNTEROS Y ARRAYS

Hay una relación muy cercana entre los punteros y los arrays . Yá vimos previamente que el designador ( ó nombre de un array) era equivalente a la dirección del elemento [0] del mismo . La explicación de ésto es ahora sencilla : el nombre de un array , para el compilador C , es un PUNTERO inicializado con la dirección del primer elemento del array . Sin embargo hay una importante diferencia entre ambos , que haremos notar más abajo. Veamos algunas operaciones permitidas entre punteros :

ASIGNACIONfloat var1 , conjunto[] = { 9.0 , 8.0 , 7.0 , 6.0 , 5.0 );

float *punt ;

punt = conjunto ; /* equivalente a hacer : punt = &conjunto [0] */

var1 = *punt ;

*punt = 25.1 ;

Es perfectamente válido asignar a un puntero el valor de otro , el resultado de ésta operación es cargar en el puntero punt la dirección del elemento [0] del array conjunto , y posteriormente en la variable var1 el valor del mismo (9.0) y para luego cambiar el valor de dicho primer elemento a 25.1 .

Veamos cual es la diferencia entre un puntero y el denominador de un array : el primero es una VARIABLE , es decir que puedo asignarlo , incrementarlo etc , en cambio el segundo es una CONSTANTE , que apunta siempre al primer elemento del array con que fué declarado , por lo que su contenido NO PUEDE SER VARIADO . Si lo piensa un poco , es lógico , ya que "conjunto" implica la dirección del elemento conjunto [0] , por lo que , si yo cambiara su valor , apuntaría a otro lado dejando de ser , "conjunto" . Desde este punto de vista , el siguiente ejemplo nos muestra un tipo de error bastante frecuente:

ASIGNACION ERRONEA

int conjunto[5] , lista[] = { 5 , 6 , 7 , 8 , 0 ) ;

int *apuntador ;

apuntador = lista ; /* correcto */

conjunto = apuntador; /* ERROR ( se requiere en Lvalue no constante ) */

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 28

Page 18: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

lista = conjunto ; /* ERROR ( idem ) */

apuntador = &conjunto /* ERROR no se puede aplicar el operador & (dirección) a una constante */

Veamos ahora las distintas modalidades del incremento de un puntero :

INCREMENTO O DECREMENTO DE UN PUNTERO

int *pint , arreglo_int[5] ;

double *pdou , arreglo_dou[6] ;

pint = arreglo_int ; /* pint apunta a arreglo_int[0] */

pdou = arreglo_dou ; /* pdou apunta a arreglo_dou[0] */

pint += 1 ; /* pint apunta a arreglo_int[1] */

pdou += 1 ; /* pdou apunta a arreglo_dou[1] */

pint++ ; /* pint apunta a arreglo_int[2] */

pdou++ ; /* pdou apunta a arreglo_dou[2] */

Hemos declarado y asignado dos punteros , uno a int y otro a double , con las direcciones de dos arrays de esas caracteristicas . Ambos estarán ahora apuntando a los elementos [0] de los arrays . En las dos instrucciones siguientes incrementamos en uno dichos punteros . ¿ adonde apuntaran ahora ?.

Para el compilador , éstas sentencias se leen como : incremente el contenido del puntero ( dirección del primer elemento del array ) en un número igual a la cantidad de bytes que tiene la variable con que fué declarado . Es decir que el contenido de pint es incrementado en dos bytes (un int tiene 2 bytes ) mientras que pdou es incrementado 8 bytes ( por ser un puntero a double ), el resultado entonces es el mismo para ambos , ya que luego de la operación quedan apuntando al elemento SIGUIENTE del array , arreglo_int[1] y arreglo_dou[1] .

Vemos que de ésta manera será muy facil "barrer" arrays , independientemente del tamaño de variables que lo compongan, permitiendo por otro lado que el programa sea transportable a distintos hardwares sin preocuparnos de la diferente cantidad de bytes que pueden asignar los mismos , a un dado tipo de variable. De manera similar las dos instrucciones siguientes , vuelven a a incrementarse los punteros , apuntando ahora a los elementos siguientes de los arrays.

Todo lo dicho es aplicable , en identica manera , al operador de decremento -- .

ARITMETICA DE DEREFERENCIA

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 29

Page 19: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

Debido a que los operadores * y ++ ó -- tienen la misma precedencia y se evaluan de derecha a izquierda , y los paréntesistienen mayor precedencia que ambos , muchas operaciones que los utilizan en conjunto a todos estos operadores , pueden parecer poco claras y dar origen a un sinnúmero de errores , (revise un poco la TABLA 13 del capítulo 3 ) analicémoslas detalladamente , partiendo de :

int *p , a[] = { 0 , 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 } ;

int var ;p = a ;

A partir de aquí , el puntero está apuntando a a[0] . Veamos las distintas variantes que puede tener la siguiente instrucción:

*p = 27 ;

La más sencilla de las opciones , simplemente asignamos al elemento apuntado por p ( a[0] ) un valor constante . Veamos lainversa de ella:

var = *p ;

var sería asignada al valor 0 (contenido de a[0]) , y p seguiría apuntando al mismo elemento. Que hubiera pasado, si en vez deello se hubiera escrito:

var = *( p + 1 ) ;

acá podríamos traducir el sentido de la operación como : cargue var con el contenido del elemento siguiente al apuntado por p ( a[1] ) . Lo interesante de remarcar acá es que p , en sí mismo , NO VARIA Y LUEGO DE ESTA SENTENCIA SEGUIRA APUNTANDO A a[0] . De la misma forma : var = *( p + 3 ) asignará 30 a var , sin modificar el contenido de p . En cambio la expresión :

var = *( p++ ) ;

podemos leerla como : asigne a var el valor de lo apuntado por p y LUEGO incremente éste para que apunte al proximo elemento . Así en var quedaría 0 ( valor de a[0] ) y p apuntaría finalmente a a[1] . Si en vez de ésto hubieramos preincrementado a p tendríamos :

var = *( ++p ) ;

la que puede leerse como : apunte con p al próximo elemento y asigne a var con el valor de éste . En este caso var sería igualada a 10 ( a[1] ) y p quedaría apuntando al mismo .

En las dos operaciones anteriores los paréntesis son superfluos ya que al analizarse los operadores de derecha a izquierda , daría lo mismo escribir :

var = *p++ ; /* sintácticamente igual a var = *(p++) */var = *++p ; /* " " " var = *(++p) */

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 30

Page 20: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

ARITMETICA DE PUNTEROS

La aritmética más frecuentemente usada con punteros son las sencillas operaciones de asignación , incremento ó decremento y dereferenciación . Todo otro tipo de aritmética con ellos está prohibida ó es de uso peligroso ó poco transportable . Por ejemplo no está permitido , sumar , restar , dividir , multiplicar , etc , dos apuntadores entre sí . Lo cual si lo pensamos un poco es bastante lógico , ya que de nada me serviría sumar dos direcciones de memoria , por ejemplo .

Otras operaciones estan permitidas , como la comparación de dos punteros , por ejemplo ( punt1 == punt2 ) ó ( punt1 <punt2 ) sin embargo este tipo de operaciones son potencialmente peligrosas , ya que con algunos modelos de pointers pueden funcionar correctamente y con otros no .

PUNTEROS Y VARIABLES DINAMICAS

Recordemos lo expresado sobre el ámbito ó existencia de las variables , la menos duradera de ellas era la del tipo local a una función , ya que nacía y moría con ésta . Sin embargo , esto es algo relativo , en cuanto a la función main() , ya que sus variables locales ocuparán memoria durante toda la ejecución del programa.

Supongamos un caso típico , debemos recibir una serie de datos de entrada , digamos del tipo double , y debemos procesar según un determinado algoritmo a aquellos que aparecen una ó más veces con el mismo valor . Si no estamos seguros de cuantos datos van a ingresar a nuestro programa , pondremos alguna limitación , suficientemente grande a los efectos de la precisión requerida por el problema , digamos 5000 valores como máximo , debemos definir entonces un array de doubles capaz de albergar a cinco mil de ellos , por lo que el mismo ocupará del orden de los 40 k de memoria .

Si definimos este array en main() , ese espacio de memoria permanecerá ocupado hasta el fín del

programa , aunque luego de aplicarle el algoritmo de cálculo ya no lo necesitemos más , comprometiendo seriamente nuestra disponibilidad de memoria para albergar a otras variables .

Una solución posible sería definirlo en una función llamada por main() que se ocupara de llenar el array con los datos , procesarlos y finalmente devolviera algún tipo de resultado , borrando con su retorno a la masiva variable de la memoria .

Sin embargo en C existe una forma más racional de utilizar nuestros recursos de memoria de manera conservadora . Los programas ejecutables creados con estos compiladores dividen la memoria disponible en varios segmentos , uno para el código ( en lenguaje máquina ) , otro para albergar las variables globales , otro para el stack ( a travez del cual se pasan argumentos y donde residen las variables locales ) y finalmente un último segmento llamado memoria de apilamiento ó amontonamiento (Heap ) .

El Heap es la zona destinada a albergar a las variables dinámicas , es decir aquellas que crecen

( en el sentido de ocupación de memoria ) y decrecen a lo largo del programa , pudiendose crear y desaparecer (desalojando la memoria que ocupaban) en cualquier momento de la ejecución .

Veamos cual sería la metodología para crearlas ; supongamos primero que queremos ubicar un único dato en el Heap , definimos primero un puntero al tipo de la variable deseada :

double *p ;

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 31

Page 21: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

notemos que ésta declaración no crea lugar para la variable , sino que asigna un lugar en la memoria para que posteriormente se guarde ahí la dirección de aquella Para reservar una cantidad dada de bytes en el Heap , se efectua una llamada a alguna de las funciones de Librería , dedicadas al manejo del mismo . La más tradicional es malloc() ( su nombre deriva de memory allocation ) , a esta función se le dá como argumento la cantidad de bytes que se quiere reservar , y nos devuelve un pointer apuntando a la primer posición de la "pila" reservada . En caso que la función falle en su cometido ( el Heap está lleno ) devolvera un puntero inicializado con NULL .

p = malloc(8) ;

acá hemos pedido 8 bytes ( los necesarios para albergar un double ) y hemos asignado a p el retorno de la función , es decir la dirección en el Heap de la memoria reservada.

Como es algo engorroso recordar el tamaño de cada tipo variable , agravado por el hecho de que , si reservamos memoria de esta forma , el programa no se ejecutará correctamente , si es compilado con otro compilador que asigne una cantidad distinta de bytes a dicha variable , es más usual utilizar sizeof , para indicar la cantidad de bytes requerida :

p = malloc( sizeof(double) ) ;

En caso de haber hecho previamente un uso intensivo del Heap , se debería averiguar si la reserva de lugar fué exitosa:

if( p == NULL )

rutina_de_error() ;

si no lo fué estas sentencias me derivan a la ejecución de una rutina de error que tomará cuenta de este caso . Por supuesto podría combinar ambas operaciones en una sola ,

if( ( p = malloc( sizeof(double) ) ) == NULL ) {

printf("no hay mas lugar en el Heap ..... Socorro !!" ) ;

exit(1) ;

}

se ha reemplazado aquí la rutina de error , por un mensaje y la terminación del programa , por medio de exit() retornando un código de error . Si ahora quisiera guardar en el Heap el resultado de alguna operación , sería tan directo como,

*p = a * ( b + 37 ) ;

y para recuperarlo , y asignarselo a otra variable bastaría con escribir :

var = *p ;

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 32

Page 22: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

PUNTEROS A STRINGS

No hay gran diferencia entre el trato de punteros a arrays , y a strings , ya que estos dos últimos son entidades de la misma clase . Sin embargo analicemos algunas particularidades . Así como inicializamos un string con un grupo de caracteres terminados en '\0' , podemos asignar al mismo un puntero :

p = "Esto es un string constante " ;

esta operación no implica haber copiado el texto , sino sólo que a p se le ha asignado la dirección de memoria donde reside la "E" del texto . A partir de ello podemos manejar a p como lo hemos hecho hasta ahora . Veamos un ejemplo

#include <stdio.h>

#define TEXTO1 "¿ Hola , como "

#define TEXTO2 "le va a Ud. ? "

main(){

char palabra[20] , *p ;

int i ;

p = TEXTO1 ;

for( i = 0 ; ( palabra[i] = *p++ ) != '\0' ; i++ ) ;

p = TEXTO2 ;

printf("%s" , palabra ) ;

printf("%s" , p ) ;

return 0 ;

}

Definimos primero dos strings constantes TEXTO1 y TEXTO2 , luego asignamos al puntero p la dirección del primero , y seguidamente en el FOR copiamos el contenido de éste en el array palabra , observe que dicha operación termina cuando el contenido de lo apuntado por p es el terminador del string , luego asignamos a p la dirección de TEXTO2 y finalmente imprimimos ambos strings , obteniendo una salida del tipo : " ¿ Hola , como le va a UD. ? " ( espero que bien ) .

Reconozcamos que esto se podría haber escrito más compacto, si hubieramos recordado que palabra tambien es un puntero y NULL es cero , así podemos poner en vez del FOR

while( *palabra++ = *p++ ) ;

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 33

Page 23: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

Vemos que aquí se ha agregado muy poco a lo ya sabido , sin embargo hay un tipo de error muy frecuente , que podemos analizar , fíjese en el EJEMPLO siguiente , ¿ ve algun problema ? .

( CON ERRORES )

#include <stdio.h>

char *p , palabra[20] ;

printf("Escriba su nombre : ") ;

scanf("%s" , p ) ;

palabra = "¿ Como le va " ;

printf("%s%s" , palabra , p ) ;

}

Pues hay dos errores , a falta de uno , el primero ya fue analizado antes , la expresión scanf("%s" , p ) es correcta pero , el error implícito es no haber inicializado al puntero p , el cual sólo fué definido , pero aun no apunta a ningun lado válido . El segundo error está dado por la expresión : palabra = " ¿ Como le va " ; ( también visto anteriormente ) ya que el nombre del array es una constante y no puede ser asignado a otro valor .

¿Como lo escribiríamos para que funcione correctamente ?

(CORRECTO)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

char *p , palabra[20] ;

p = (char *)malloc(sizeof(char)128) ;

printf("Escriba su nombre : ") ;

scanf("%s" , p ) ;

strcpy(palabra , "¿ Como le va " ) ;

printf("%s%s" , palabra , p ) ;

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 34

Page 24: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

}

Observe que antes de scanf() se ha inicializado a p, mediante el retorno de malloc() y a al array palabra se le copiado el string mediante la función vista anteriormente strcpy(). Debemos aclarar también que, la secuencia de control %s en el printf() impone enviar a la pantalla un string, estando éste apuntado por el argumento siguiente al control, éste puede ser tanto el nombre de un array, como un puntero, ya que ambos explicitan direcciones. Una forma alternativa de resolverlo , sería:

( CORRECTO )

#include <stdio.h>

main(){char p[20] , *palabra ;

printf("Escriba su nombre : ") ;

scanf("%s" , p ) ;

palabra = "¿ Como le va " ;

printf("%s%s" , palabra , p ) ;}

Obsérvese , que es idéntico al primero , con la salvedad que se ha invertido las declaraciones de las variables , ahora el puntero es palabra y el array es p . Ambas soluciones son equivalentes y dependerá del resto del programa , cual es la mejor elección .

ARRAYS DE PUNTEROS

Es una práctica muy habitual , sobre todo cuando se tiene que tratar con strings de distinta longitud , generar array cuyos elementos son punteros , que albergarán las direcciones de dichos strings. Si imaginamos a un puntero como una flecha , un array de ellos equivaldría a un carcaj indio lleno de aquellas . Asi como:

char *flecha;

definía a un puntero a un caracter , la definición

char *carcaj[5];

implica un array de 5 punteros a caracteres .

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 35

Page 25: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

INICIALIZACION DE ARRAYS DE PUNTEROS

Los arrays de punteros pueden ser inicializados de la misma forma que un array común , es decir dando los valores de sus elementos , durante su definición , por ejemplo si quisieramos tener un array donde el subíndice de los elementos coincidiera con el nombre de los días de la semana , podríamos escribir :

char *dias[] = { "número de día no válido" , "lunes" , "martes" , "miercoles" , "jueves" , "viernes" , "sabado" , "por fín es domingo" }

Igual que antes, no es necesario en este caso indicar la cantidad de elementos , ya que el compilador los calcula por la cantidad de términos dados en la inicialización. Asi el elemento dias[0] será un puntero con la dirección del primer string, dias[1], la del segundo, etc.

PUNTEROS A ESTRUCTURAS

Los punteros pueden también servir para el manejo de estructuras , y su alojamiento dinámico , pero tienen además la propiedad de poder direccionar a los miembros de las mismas utilizando un operador particular , el -> , (escrito con los símbolos "menos" seguido por "mayor" ) .

Supongamos crear una estructura y luego asignar valores a sus miembros , por los métodos ya descriptos anteriormente :

struct conjunto { int a ; double b ; char c[5] ; } stconj ;

stconj.a = 10 ;

stconj.b = 1.15 ;

stconj.c[0] = 'A' ; La forma de realizar lo mismo , mediante el uso de un puntero, sería la siguiente :

struct conjunto { int a ; double b ; char c[5] ; } *ptrconj ;

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 36

Page 26: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

ptrconj = (struct conjunto *)malloc( sizeof( struct conjunto )) ;

ptrconj->a = 10 ;

ptrconj->b = 1.15 ;

ptrconj->c[0] = 'A' ;

En este caso vemos que antes de inicializar un elemento de la estructura es necesario alojarla en la memoria mediante malloc(), observe atentamente la instrucción: primero se indica que el puntero que devuelve la función sea del tipo de apuntador a conjunto (ésto es sólo formal), y luego con sizeof se le da como argumento las dimensiones en bytes de la estructura.

Acá se puede notar la ventaja del uso del typedef , para ahorrar tediosas repeticiones de texto, y

mejorar la legilibilidad de los listados; podríamos escribir:

typedef struct { int a ; double b ; char c[5] ; } conj ;

conj *ptrconj ;

ptrconj = ( conj *)malloc( sizeof( conj )) ;

Es muy importante acá , repasar la TABLA 13 del final del capítulo 3 , donde se indican las precedencias de los operadores , a fín de evitar comportamientos no deseados , cuando se usan simultaneamente varios de ellos . Ya que c es un array podemos escribir :

x = *ptrconj -> c ;

la duda acá es, si nos referimos al contenido apuntado por ptrconj ó por c. Vemos en la tabla que, el operador -> es de mayor precedencia que la de * (dereferenciación), por lo que, el resultado de la expresión es asignar el valor apuntado por c, es decir el contenido de c[0] .

De la misma forma:

*ptrconj -> c++ ; incrementa el puntero c , haciendolo tener la direccion de c[1] y luego extrae el valor de éste .++ptrconj -> c ; incrementa el valor de c[0] .

En caso de duda , es conveniente el uso a discreción de paréntesis , para saltar por sobre las , a veces complicadas , reglas que impone la precedencia así , si queremos por ejemplo el valor de c[3] , la forma más clara de escribir es:

*( ptrconj -> ( c + 4 ) ) ;(Recuerde que c[3] es el CUARTO elemento del array ).

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 37

Page 27: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

PUNTEROS Y FUNCIONES

La relación entre los punteros y las funciones , puede verse en tres casos distintos , podemos pasarle a una función un puntero como argumento (por supuesto si su parámetro es un puntero del mismo tipo ) , pueden devolver un puntero de cualquier tipo , como ya hemos visto con malloc() y calloc() , y es posible también apuntar a la dirección de la función , en otras palabras , al código en vez de a un dato.

PUNTEROS COMO PARAMETROS DE FUNCIONES .

Supongamos que hemos declarado una estructura , se puede pasar a una función como argumento , de la manera que ya vimos anteriormente:

struct conjunto { int a ; double b ; char c[5] ; } datos ;

void una_funcion( struct conjunto datos );

Hicimos notar, en su momento, que en este caso la estructura se copiaba en el stack y así era pasada a la función, con el peligro que esto implicaba, si ella era muy masiva, de agotarlo.

Otra forma equivalente es utilizar un puntero a la estructura :

struct conjunto {

int a ;

double b ;

char c[5] ;

} *pdatos ;

void una_funcion( struct conjunto *pdatos ) ;

Con lo que sólo ocupo lugar en el stack para pasarle la dirección de la misma. Luego en la función, como todos los miembros de la estructuras son accesibles por medio del puntero, tengo pleno control de la misma. Un ejemplo de funciones ya usadas que poseen como parámetros a punteros son:

scanf(puntero_a_string_de_control, punteros_a_variables) printf(puntero_a_string_de_control, variables)

En ambas vemos que los strings de control son , como no podría ser de otro modo , punteros , es decir que los podríamos definir fuera de la función y luego pasarselos a ellas :

p_control = "valor : %d " ;

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 38

Page 28: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

printf( p_control , var ) ;

PUNTEROS COMO RESULTADO DE UNA FUNCION Las funciones que retornan punteros son por lo general aquellas que modifican un argumento ,

que les ha sido pasado por dirección ( por medio de un puntero ) , devolviendo un puntero a dicho argumento modificado , ó las que reservan lugar en el Heap para las variables dinámicas , retornando un puntero a dicho bloque de memoria . Así podremos declarar funciónes del tipo de:

char *funcion1( char * var1 ) ;

double *funcion2(int i , double j , char *k ) ;

struct item *funcion3( struct stock *puntst ) ;

El retorno de las mismas puede inicializar punteros del mismo tipo al devuelto , ó distinto , por medio del uso del casting . Algunas funciones , tales como malloc() y calloc() definen su retorno como punteros a void :

void *malloc( int tamano ) ;

de esta forma al invocarlas , debemos indicar el tipo de puntero de deseamos

p = (double *)malloc( 64 ) ;

OPERADORES DE PUNTEROS. En esta parte veremos dos operadores de punteros * y & los cuales se conocen respectivamente como operador de indirección o desreferencia y el segundo como operador de dirección la manera de emplearse cada uno de los operadores se muestra en el siguiente programa.

#include <stdio.h>#include <conio.h>

void main(){int *xptr,x=10,z; clrscr();xptr=&x;z=*xptr;printf(“la dirección de x es %p, el contenido de la dirección apuntada por xptr es %d “,xptr,z);getch();}

Del programa anterior podemos observar que usamos al operador & para extraer la dirección de x y asignárselo al puntero, y si al puntero se le aplica el operador * se obtiene el contenido de la dirección apuntada por el puntero, en la línea en la que se despliega el resultado vemos que el código de formato para un puntero es %p.

OPERACIONES ARITMÉTICAS CON PUNTEROS.- Los punteros solo pueden efectuar dos operaciones aritméticas la suma y la resta, las cantidades que se le pueden sumar o restar a los punteros son números enteros, el resultado de sumar o de restar a un puntero será un numero hexadecimal el cual

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 39

Page 29: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

dependerá del tipo de puntero es decir que si el puntero es de tipo entero al sumarle un uno, su dirección se modificara en dos unidades hexadecimales pues el tipo entero tiene como tamaño dos bytes por lo que cada vez que se le sume un entero se incrementara la dirección en dos, en el tipo de carácter será de uno en uno porque el tipo carácter solo requiere de un byte, también cambiara para el tipo real pues será de 4 bytes para float y de 5 para el tipo double, la manera en la que se efectúan las operaciones son mediante los operadores -,+,+=,’=,++,-- por medio de las cuales se le agregan números enteros a los punteros, las operaciones con puntero tienen sentido cuando se utilizan con arreglos pues las direcciones de memoria son continuas y tiene sentido moverse en direcciones de memoria que representan las distintas localidades del arreglo y pueden usarse dos o mas punteros y hacer operaciones entre ellos siempre y cuando estén relacionados por el arreglo, a continuación se dan ejemplos de operaciones con punteros.

int *xptr,x;

Suponiendo que la dirección de x es 0001 y se le asigne al puntero si le suma 1 la nueva dirección como se muestra en la siguiente tabla.

PUNTERO DIRECCIÓNxptr 001xptr+1 002xptr+2 004

PUNTEROS Y ARREGLOS.- Existe una relación muy estrecha entre los punteros y los arreglos de hecho se define que el arreglo es en si un apuntador constante, si a un arreglo se le aplica un apuntador este apuntara a la dirección inicial del arreglo y si el puntero se le suma o resta se moverá sobre el arreglo como se hace con las localidades del arreglos

int x[20];xptr=x; o xptr=&x[0]

OPERADOR sizeof().- Este operador es muy útil cuando se manejan punteros pues nos ayuda a definir el tamaño en bytes de los tipos de datos estándar, este operador devuelve un valor entero el cual nos dice el tamaño del dato por lo que esta función se deberá asignar a una variable de tipo entero o ejecutarse dentro de una sentencia printf() como se muestra en el ejemplo.

int y;y=sizeof(int);

#include <stdio.h>#include <conio.h>

void main(){

printf(“sizeof(char)= %d\n ““sizeof(short)= %d\n ““sizeof(int)= %d\n ““sizeof(long)= %d\n “

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 40

Page 30: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

“sizeof(float)= %d\n ““sizeof(double)= %d\n ““sizeof(long double)= %d\n “,sizeof(char),sizeof(short),sizeof(int),sizeof(long),sizeof(float),sizeof(double),sizeof(long double));

getch();}

ARREGLOS DE APUNTADORES.- Los arreglos pueden contener apuntadores y una manera común para esta estructura de datos es formar un arreglo de cadenas, al cual se le conoce como arreglo de cadenas, en donde cada una de las localidades es una cadena, pero en el turbo C a las cadenas es esencial que tengan un apuntador a su primer carácter, a continuación se muestra.

char *cartas[4]={“Corazones”,”Diamante”,”Picas”,”Treboles”}

La declaración anterior nos muestra que lo que estamos declarando es un arreglo de apuntadores y aunque lo estamos igualando a cuatro cadenas, en realidad solo se esta almacenando sus apuntadores.

2.3.3. LONGITUD, SUBCADENA, CONCATENACION CON ARREGLOS DE CARACTERES

2.3.4. IMPLEMENTACION DE ALGORITMOS DE ORDENACIÓN Y BÚSQUEDA SECUENCIAL EN REGISTROS

Ordenación método de la burbuja.

Sea A una lista de n números la ordenación es aquella operación que consiste en reorganizar los elementos de A para colocarlos en un orden creciente:

A[1] < A[2] < A[3] < ... < A[n]

Por ejemplo supongamos que A es la lista siguiente:8, 4, 19, 2, 7, 13, 5, 16.

Después de la ordenación quedara de la siguiente manera:2, 4, 5, 7, 8, 13, 16, 19.

Aunque la ordenación para A, a simple vista es una tarea trivial, ordenar eficientemente es una tarea complicada. De hacho existe una gran variedad de algoritmos de ordenación. Un algoritmo muy simple conocido como el método de la burbuja es la común más mente usada.

La definición de ordenación dada se refiere únicamente a la ordenación de datos numéricos en orden creciente esta restricción es solo por conveniencia de notación, la ordenación puede abarcar también la colocación de datos numéricos en orden decreciente o de datos no numéricos en ordenación alfabética, ordenar A implica colocar los registros de A en un determinado orden impuesto pero un campo de clave de los mismos.

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 41

Page 31: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

METODO DE LA BURBUJA.

Supongamos que almacenar en memoria la lista de números A[1], A[2], ..., A[n]. El algoritmo del método de la burbuja funciona de la siguiente manera:

1. Comparamos A[1] y A[2] y colocamos ambos en el orden deseado de tal forma que A[1]<A[2] después de la operación. Después comparamos A[2] con A[3] y colocamos ambos en tal horma que A[2]<A[3] y a continuación comparamos A[3] y A[4] y colocamos de tal forma que A[3]<A[4], a continuación de la siguiente forma comparamos A[n-1] y A[n] y lo colocamos en el orden adecuado.

Observe que le paso 1 implica n-1 comparaciones (durante el paso 1 el elemento mayor burbujea hacia arriba hasta llegar a la posición n-esima) cuando termina el paso A[n] almacenada el elemento mayor.

2. Repetir el paso 1 pero con una comparación menos en decir para remos después de estudiar la posible reordenación de A[n-2] y de A[n-1]. Cuando completamos este segundo paso el segundo elemento mayor ocupara el lugar A[n-1].

3. Repetir el paso 1 con dos comparaciones menos, es decir parando después de estudiar A[n-3] y A[n-2]...

Paso n-1. Comparemos A[1] con A[2] y lo colocamos en el orden adecuado.

Después de n-1 pasos la lista quedaran ordenada en orden creciente.

El proceso de atravesar totalmente o parcialmente una lista recibe el nombre de recorrido, por tanto cada uno de los pasos anteriores constituye un recorrido. De acuerdo con esto, el algoritmo de ordenación por la burbuja necesita n-1 recorridos donde n es el número de elementos de la entrada.

Supongamos que en el arreglo A almacena los siguientes números:

A=[32, 51, 27, 57, 66, 23, 13, 57].

PASO I

Comparamos A1 y A2, puesto que 32<51 no altera la lista.Comparamos A2 y A3 puesto que 31>27 intercambiamos ambos y la lista quedara:

32, 27, 51, 87, 66, 23, 13, 57.

Comparamos A3 y A4 puesto que 51<87 no alteramos la lista.Comparamos A4y A5 puesto que 87>66 intercambiamos ambos y la lista quedara:

32, 27, 51, 66, 87, 23, 13, 57.

Comparamos A5 y A6 puesto que 87>23 intercambiamos ambos y la lista quedara:32, 27, 51, 66, 23, 87, 13, 57.

Comparamos A6 y A7 puesto que 87>13 intercambiamos ambos y la lista quedara:32, 27, 51, 66, 23, 13, 87, 57.

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 42

Page 32: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

Comparamos A7 y A8 puesto que 87>57 intercambiamos ambos la lista quedara:32, 27, 51, 66, 23, 13, 57, 87.

Al final del paso I el número 87 es el mayor de todos ocupa la ultima posición sin embargo el resto de los números no están ordenados, aunque algunos de ellos han cambiado de posición.

Para los pasos restantes indicamos solo los intercambios.

PASO II32, 27, 51, 66, 23, 13, 57, 87.27, 32, 51, 66, 23, 13, 57, 87.27, 32, 51, 23, 66, 13, 57, 87.27, 32, 51, 23, 13, 66, 57, 87.27, 32, 51, 23, 13, 57, 66, 87.

Al final de paso II, el siguiente número más alto aparece en la penúltima posición.

PASO III27, 32, 23, 51, 13, 57, 66, 87.27, 32, 23, 13, 51, 57, 66, 87.

PASO IV27, 23, 32, 13, 51, 57, 66, 87.27, 23, 13, 32, 51, 57, 66, 87.

PASO V23, 27, 13, 32, 51, 57, 66, 87.23, 13, 27, 32, 51, 57, 66, 87.

PASO VI13, 23, 27, 32, 51, 57, 66, 87

El paso VI implica dos comparaciones A1 y de A2 con A3. La segunda comparación no implica intercambio.

El paso VII finalmente comparamos A1 con A2, puesto que 13 es menor que 23 no realiza ningún intercambio.

BUSQUEDA SECUENCIAL.

Sea DATOS un conjunto de datos almacenados en memoria. Supongamos que se nos da un determinado valor de tipo almacenado, ELEMENTO. Él termino BUSQUEDA hace referencia a la operación consistente en encontrar la posición que ocupa ELEMENTO en DATOS o bien a la impresión de un mensaje que nos indica que ELEMENTO no se encuentra en la lista. Decimos ELEMENTO en DATOS. En caso contrario diremos que la búsqueda ha sido fallida. Frecuentemente queremos insertar

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 43

Page 33: 2-arreglos

ESTRUCTURA DE DATOS INCORPORADOS UNIDAD II____________________________________________________________________________________________________

ELEMENTO en DATOS si la búsqueda ha sido fallida. En este caso podemos utilizar algoritmos de búsqueda siempre.

Existen varios algoritmos de búsqueda distintos. El algoritmo elegido depende de la forma en que se encuentra organizados los datos.

Sea DATOS un arreglo lineal con n ELEMENTOS. Si no disponemos de más información acerca de DATOS la forma más rápida de localizar el ELEMENTO dentro de DATOS de uno en uno así comparamos si DATOS{1}=ELEMENTO, en caso contrario DATOS{2}=ELEMENTO y así sucesivamente este método que recorre DATOS, secuencialmente hasta localizar ELEMENTO recibe el nombre de Búsqueda secuencial o Búsqueda lineal.

____________________________________________________________________________________________________ESTRUCTURA DE DATOS ING. OSORNIO 44