Unidad 4
Unidad 4
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.
Sintaxis:
int a[5];
float arre[50];
char vector[20];
Elemento 1
Elemento 2
Elemento 3
Elemento 4
Elemento N
Un arreglo (array) matriz o vector es un conjunto ordenado de elementos homogéneos; esto es,
un arreglo unidimensional va de uno en uno empezando en la localidad número cero y de esta
manera se puede accesar en cualquier instante a la localidad que se desee. Un arreglo se
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
dice que es homogéneo porque todos sus elementos son del mismo tipo, por eso nunca
podremos encontrar un arreglo que contenga en su interior valores enteros conjuntamente con
valores de tipo carácter o veceversa.
Los arreglos están formados por un conjunto de elementos de un mismo tipo de datos que se
almacenan bajo un mismo nombre, y se diferencian por la posición que tiene cada elemento
dentro del arreglo de datos. Al declarar un arreglo, se debe inicializar sus elementos antes de
utilizarlos. Para declarar un arreglo tiene que indicar su tipo, un nombre único y la cantidad de
elementos que va a contener. Por ejemplo, las siguientes instrucciones declaran tres arreglos
distintos:
float costo_partes[50];
Calificaciones [0]
Calificaciones [1]
Calificaciones [2]
Calificaciones [99]
Para acceder a valores específicos del arreglo, use un valor de índice que apunte al elemento
deseado. Por ejemplo, para acceder al primer elemento del arreglo calificaciones debe utilizar
el valor de índice 0 (calificaciones[0]). Los programas en C siempre indican el primer elemento
de un arreglo con 0 y el último con un valor menor en una unidad al tamaño del arreglo.
El índice siempre va entre corchetes e indica la posición y orden de un vector, por ejemplo
estos serían ejemplos de arreglos y sus índices.
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
La inicialización de arreglos se realiza por medio del conjunto de valores iniciales de los
distintos elementos del arreglo, agrupado por medio de llaves.
La asignación de valores al arreglo unidimensional es en orden de secuencia con respecto al
valor del índice [0] a [N].
Asignación de un arreglo de tipo entero llamado “ a “ y uno de tipo real llamado “ datos “:
En el siguiente ejemplo se muestra el llenado de un arreglo (vector) con valores de cero a 10,
empezando a llenar en la localidad cero y terminando en la localidad nueve.
Algoritmo:
inicio
constante Tam 10
declarar variables
entero i, a[Tam]
para (i = 0; i < Tam; i++) hacer
a[i] ← 0
fin_para
para (i = 0; i < Tam; i++) hacer
escribir ( ‘ a[i] ‘ )
fin_para
return 0;
fin
Programa:
#include <stdio.h>
#define Tam 10
int main(void)
{
int i, a[Tam];
Cuando se usan arreglos, una operación común es usar una variable índice para acceder a los
elementos de un arreglo. Suponiendo que el arreglo se llamará valores, y la variable índice i
contiene el número 3, la siguiente instrucción asignará el valor 400 a dicho vector, como si
fuera una variable simple al ocupar una posición de memoria:
Partes de un arreglo:
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Los componentes. Hacen referencia a los elementos que forman el arreglo, es decir, a los
valores que se almacenan en cada una de las casillas del mismo. Los índices, permiten hacer
referencia a los componentes del arreglo, en forma individual especifican cuántos elementos
tendrá el arreglo y además, de qué modo podrán accesarse a esos componentes.
S1 S2 S3 . . . SN
Componentes
Las operaciones que se pueden realizar con vectores durante el proceso de resolución de un
problema son:
· Lectura/ escritura
· Asignación
· Ordenación
· Búsqueda
Ejemplos:
Sea arre un arreglo de 70 elementos enteros con índices enteros. Su representación nos
queda:
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Lectura
El proceso de lectura de un arreglo consiste en leer y asignar un valor a cada uno de sus
elementos. Normalmente se realizan con estructuras repetitivas, aunque pueden usarse
estructuras selectivas. Usamos los índices para recorrer los elementos del arreglo:
Algoritmo:
Programa:
Escritura:
Es similar al caso de lectura, sólo que en vez de leer el componente del arreglo, lo escribimos.
Algoritmo:
leer (N)
para ( i = 1 ; i <= N ; i++ ) hacer
escribir ( arre[i] )
fin_para
Programa:
Asignación:
No es posible asignar directamente un valor a todo el arreglo; sino que se debe asignar el valor
deseado en cada componente. Con una estructura repetitiva se puede asignar un valor a todos
los elementos del vector.
Por ejemplo:
Algoritmo:
Programa:
O bien
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Inicialización
Para inicializar con cero todos los elementos del arreglo:
Algoritmo:
Programa:
0 0 0 0 0 ……. 0 0
1 2 3 4 5 ........ 69 70
Arre [ 70 ]
El acceso a los elementos de un vector puede ser para leer en él o para escribir (visualizar su
contenido). Recorrido del vector es la operación de efectuar una acción general sobre todos los
elementos de ese vector.
Actualización.
Incluye añadir (insertar), borrar o modificar algunos de los ya existentes. Se debe tener en
cuenta si el arreglo está o no ordenado. Añadir datos a un vector consiste en agregar un nuevo
elemento al final del vector, siempre que haya espacio en memoria.
Ejemplo.
Algoritmo:
inicio
declarar variables
entero i
entero a[ 5 ]
para ( i = 0; i < 4; i++ ) hacer
excribir ( ‘ Ingrese el elemento: ‘ )
leer ( a [ i ] )
fin_para
para ( i = 0; i < 4; i++ ) hacer
excribir ( ‘ Elemento: ‘ , a [ i ] )
fin_para
pausa
fin
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Programa:
#include<stdio.h>
#include<conio.h>
void main()
{
int a[5]; // Definición de un arreglo de 5 posiciones
int i;
// Pedimos el ingreso de 5 números
for(i=0; i<4; i++)
//No olvidar que los arreglos van de 0 a longitud-1
{
printf(“ Ingrese el elemento: ”);
scanf(“%d”,&a[i]);
}
// Para imprimir será
for(i=0; i<4; i++)
{
printf(“ Elemento: %d ”,a[i]);
}
getch();
}
int a[10];
El vector a comprende los elementos a[0], a[1], a[2], . . . , a[9], todos de tipo int. Los índices de
los vectores en C empiezan en cero.
Al igual que ocurría con las variables “normales”, podemos dar valor a los elementos de un
arreglo al principio del programa.
Esta vez los indicaremos todos entre llaves, separados por comas
Algoritmo:
inicio
declarar variables
entero numero [ 5 ] = { 200, 150, 100, -50, 300 }
entero suma
suma = numero [0] + numero[1] + numero [2] + numero[3] + numero [4]
escribir ( ‘ Su suma es ‘, suma )
pausa
fin
Programa:
#include <stdio.h>
main()
{
int numero[5] ={200,150,100,-50,300};
//Un array de 5 números enteros
int suma; // Un entero que será la suma
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
suma = numero[0]+numero[1]+numero[2]+numero[3]+numero[4];
printf("Su suma es %d", suma);
getch();
}
En una misma línea puedes declarar más de un vector, siempre que todos compartan el mismo
tipo de datos para sus componentes.
Las cadenas en C son vectores de caracteres (elementos de tipo char) con una peculiaridad: el
texto de la cadena termina siempre en un carácter nulo.
El carácter nulo tiene código ASCII 0 y podemos representarlo tanto con el entero 0 como con
el carácter ’\0’.
Declaración de cadenas
Las cadenas se declaran como vectores de caracteres, así que debes proporcionar el número
máximo de caracteres que es capaz de almacenar: su capacidad. Esta cadena, por ejemplo, se
declara con capacidad para almacenar 10 caracteres:
char a[10];
Los arreglo bidimensionales son una forma de lograr que nuestro programa pueda hacer
tablas, utilizando un índice que será siempre un dato del tipo entero. La mayoría de las
definiciones de qué es una tabla son como la siguiente, "Una tabla es un conjunto de variables
que comparten el mismo tipo de dato y nombre y a las que se hace referencia a través de un
índice" (el índice como ya se dijo antes debe ser un tipo de dato entero).
Este tipo de arreglos al igual que los anteriores es un tipo de dato estructurado, finito ordenado
y homogéneo.
El acceso a ellos también es en forma directa por medio de un par de índices. Los arreglos
bidimensionales se usan para representar datos que pueden verse como una tabla con filas y
columnas. La primera dimensión del arreglo representa las columnas, cada elemento contiene
un valor y cada dimensión representa una relación.
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
También es importante mencionar que algunos autores hacen uso del termino "vector" en los
lenguajes de programación (igual que en las matemáticas) para referirse a las tablas de una
sola dimensión y que matriz siempre se refiere a tablas bidimensionales.
Un arreglo bidimensional “ a “, que contienen tres filas y cuatro columnas (es decir un arreglo
de tres por cuatro). En general, a este tipo de arreglo con m filas y n columnas se le llama
arreglo de n x m (bidimensional).
filas columnas
Por ejemplo:
1 <= I <= M
1 <= J <= N
1 2 J N
1
2
... … … …
I
M
constantes
M =valor1
N = valor2
declarar variables
real Matriz [M][N]
para ( i = 1; i<= M; i++ ) hacer
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Programa:
#include <stdio.h>
#define M valor1
#define N valor2
main()
{
float Matriz[M][N]
//Un array de M x N números reales
for(i=1;i<=M;i++)
for ( j = 1; j<= N; j++ )
printf(" %f ", Matriz [M][N]);
getch();
}
El recorrido por columnas se hace de manera similar, invirtiendo el sentido de los índices.
constantes
M valor1
N valor2
declarar variables
real Matriz [N][M]
para ( i = 1; i<= N; i++ ) hacer
para ( j = 1; j<= M; j++ ) hacer
escribir ( Matriz [ i] [j ] )
fin_desde
fin_desde
Programa:
#include <stdio.h>
#define M valor1
#define N valor2
main()
{
float Matriz[N][M]
//Un array de M x N números reales
for(i=1;i<=N;i++)
for ( j = 1; j<= M; j++ )
printf(" %f ", Matriz [N][M]);
getch();
}
Ejemplos.
1) Rellenar una matriz identidad de 4 por 4 elementos.
Una matriz identidad es aquella en la que la diagonal principal está llena de unos y el resto de
los elementos son cero. Para llenar la matriz identidad se debe verificar que cuando los índices
i y j sean iguales, la posición vale 1, en caso contrario se asigna cero al elemento i, j.
1 0 0 0
0 1 0 0
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
0 0 1 0
0 0 0 1
Figura 21. Matriz Identidad
Algoritmo
inicio
para ( i = 1; i<= 4; i++ ) hacer
para ( j = 1; j <= 4; j++) hacer
si ( i = j ) entonces
Matriz [i] [j] 1
si_no
Matriz[i] [j] 0
fin_si
fin_desde
fin_desde
fin
Programa:
#include <stdio.h>
void main()
{
for(i=1;i<=4;i++)
for ( j = 1; j<= 4; j++ )
if (i == j )
Matriz [i][j] = 1;
else
Matriz [i][j] = 0;
getch();
}
a) lectura
Este proceso consiste en leer un dato de un arreglo y asignar un valor a cada uno de sus
componentes.
Algoritmo:
Programa:
b) escritura
Consiste en después de asignarle valores a cada elemento del arreglo, mostrarlos en pantalla.
La escritura se realiza de la siguiente manera:
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Algoritmo:
Programa:
c) asignación
No es posible asignar directamente un valor a todo el arreglo, por lo que se realiza mediante
los ciclo de repetición cualquiera de ellos como el for, el do – while ó el while con cualquiera de
ellos el resultado es exactamente el mismo, y es de la manera siguiente:
Algoritmo:
Programa:
En el caso anterior el arreglo es llenado propiamente con la misma variable del ciclo for, por lo
tanto; el arreglo contiene valores que van del 1 al valor N que dio el usuario.
d) actualización
Dentro de esta operación se encuentran las operaciones de eliminar, insertar y modificar datos.
Para realizar este tipo de operaciones se debe tomar en cuenta si el arreglo está o no
ordenado.
La definición formal del parámetro le permite al compilador determinar las características del
valor del puntero que será pasado en tiempo de ejecución.
En una cadena que definamos como “char texto[40]” lo habitual es que realmente no ocupemos
las 39 letras que podríamos llegar a usar. Si guardamos 9 letras (y el carácter nulo que marca
el final), tendremos 30 posiciones que no hemos usado. Pero estas 30 posiciones
generalmente contendrán “basura”, lo que hubiera previamente en esas posiciones de
memoria, porque el compilador las reserva para nosotros pero no las “limpia”, por lo tanto el
carácter que marca el final de nuestra cadena es el nulo “/0”.
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Hay una función de cadena predefinida por lenguaje C que nos dice cuantas letras hemos
usado realmente en nuestra cadena sin necesidad de contabilizarlo nosotros mismos, lo hace
automáticamente y es “strlen”, que se usa así:
Algoritmo:
inicio
declarar variables
carácter texto[ 40 ]
escribir ( ‘ Introduce una palabra: ‘ )
leer ( texto )
escribir ( ‘ Has tecleado ‘ , strlen (texto) , ‘ letras ‘ )
pausa
fin
Programa:
#include <stdio.h>
#include <string.h>
main()
{
char texto[40];
printf("Introduce una palabra: ");
scanf("%s", texto);
printf("Has tecleado %d letras", strlen(texto));
getch();
}
Como es de esperar, si escribimos “Hola”, esta orden nos dirá que hemos tecleado 4 letras (no
cuenta el carácter nulo “\0” que se añade automáticamente al final).
Si empleamos esta orden, o alguna de las otras órdenes relacionadas con cadenas de texto
que veremos en este tema, debemos incluir la cabecera <string.h>, que es donde se definen
todas ellas.
Hemos visto que si leemos una cadena de texto con “scanf”, se paraba en el primer espacio en
blanco y no seguía leyendo a partir de ese punto. Existen otras órdenes que están diseñadas
específicamente para manejar cadenas de texto, y que nos podrán servir en casos como éste.
Para leer una cadena de texto (completa, sin parar en el primer espacio), usaríamos la orden
“gets”, así:
gets(texto);
De igual modo, para escribir un texto en pantalla podemos usar “puts”, que muestra la cadena
de texto y avanza a la línea siguiente:
puts(texto);
printf("%s\n", texto);
Cuando queremos dar a una variable el valor de otra, normalmente usamos construcciones
como a = 2, o como a = b. Pero en el caso de las cadenas de texto, esta NO es la forma
correcta, no podemos hacer algo como saludo = "hola" ni algo como texto1 = texto2. Si
hacemos algo así, haremos que las dos cadenas estén en la misma posición de memoria, y
que los cambios que hagamos a una de ellas se reflejen también en la otra. La forma correcta
de guardar en una cadena de texto un cierto valor es:
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Es decir, debemos usar una función llamada strcpy (cadena destino, cadena origen), que se
encuentra también en <string.h>. Vamos a ver dos ejemplos de su uso:
Para evitar este problema, tenemos una forma de indicar que queremos copiar sólo los
primeros n bytes de origen, usando la función strncpy, así:
Vamos a ver un ejemplo, que nos pida que tecleemos una frase y guarde en otra variable sólo
las 4 primeras letras:
Algoritmo:
inicio
declarar variables
carácter texto1[ 40 ]
carácter texto2[ 40 ]
carácter texto3[ 10 ]
limpiar pantalla
escribir ( ‘ Introduce una frase: ‘ )
leer ( texto1 )
copiar_cadena (text2, texto1)
escribir ( ‘ Una copia de tu texto es: ‘, texto2 )
copiar_cadena_n ( texto3, texto1, 4)
escribir ( ‘ Y sus cuatro primeras letras son: ‘, texto3 )
pausa
fin
Programa:
#include <stdio.h>
#include <string.h>
main()
{
char texto1[40], texto2[40], texto3[10];
clrscr();
printf("Introduce un frase: ");
gets(texto1);
strcpy(texto2, texto1);
printf("Una copia de tu texto es: %s\n", texto2);
strncpy(texto3, texto1, 4);
printf("Y sus cuatro primeras letras son %s\n", texto3);
getch();
}
Finalmente, existe otra orden relacionada con estas dos: podemos añadir una cadena al final
de otra (concatenarla), con:
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Vamos a ver un ejemplo de su uso, que nos pida nuestro nombre, nuestro apellido y cree una
nueva cadena de texto que contenga los dos, separados por un espacio:
Algoritmo:
inicio
declarar variables
carácter texto1[ 40 ]
carácter texto2[ 40 ]
carácter texto3[ 40 ]
limpiar pantalla
escribir ( ‘ Introduce tu nombre: ‘ )
leer ( texto1 )
escribir ( ‘ Introduce tu apellido: ‘ )
leer ( texto2 )
concatenar ( texto1, “ “ )
concatenar (texto1, texto2 )
escribir ( ‘ Te llamas ‘, texto1 )
pausa
fin
Programa:
#include <stdio.h>
#include <string.h>
main()
{
char texto1[40], texto2[40], texto3[40];
clrscr();
printf("Introduce tu nombre: ");
gets(texto1);
printf("Introduce tu apellido: ");
gets(texto2);
strcat(texto1, " "); /* Añado un espacio al nombre */
strcat(texto1, texto2); /* Y luego el apellido */
printf("Te llamas %s\n", texto1);
getch();
}
4.3. Apuntadores
Hasta ahora teníamos una serie de variables que declaramos al principio del programa o de
cada función. Estas variables, que reciben el nombre de ESTÁTICAS, tienen un tamaño
asignado desde el momento en que se crea el programa.
Un Apuntador es una variable que contiene una dirección de memoria, la cual corresponderá a
un dato o a una variable que contiene el dato. Los apuntadores también deben de seguir las
mismas reglas que se aplican a las demás variables, deben tener nombre únicos y deben de
declararse antes de usarse.
Cada variable que se utiliza en una aplicación ocupa una o varias posiciones de memoria.
Estas posiciones de memoria se accesan por medio de una dirección.
*h
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN h
UNIDAD IV ESTRUCUTRAS DE DATOS
H e l l o
Figura 22. Puntero
Los apuntadores dan flexibilidad a los programas en C y en C++; también permiten que estos
crezcan dinámicamente. Utilizando un apuntador hacia un bloque de memoria que se asigna al
momento de ejecución, un programa puede ser más flexible que uno que asigna toda su
memoria de una sola vez. También, un apuntador es más fácil de guardar que una estructura
grande o un objeto de una clase. Debido a que un apuntador sólo guarda una dirección, puede
fácilmente pasarse a una función. Uno de las desventajas que pueden presentar los
apuntadores es que un apuntador sin control o no inicializado puede provocar fallas en el
sistema, además de que su uso incorrecto puede generar fallas muy complejas de encontrar.
Este tipo de variables son sencillas de usar y rápidas... si sólo vamos a manejar estructuras de
datos que no cambien, pero resultan poco eficientes si tenemos estructuras cuyo tamaño no
sea siempre el mismo.
Es el caso de una agenda: tenemos una serie de fichas, e iremos añadiendo más. Si
reservamos espacio para 10, no podremos llegar a añadir la número 11, estamos limitando el
máximo. Una solución sería la de trabajar siempre en el disco: no tenemos límite en cuanto a
número de fichas, pero es muchísimo más lento.
Lo ideal sería aprovechar mejor la memoria que tenemos en el ordenador, para guardar en ella
todas las fichas o al menos todas aquellas que quepan en memoria. Una solución “típica” (pero
mala) es sobredimensionar: preparar una agenda contando con 1000 fichas, aunque
supongamos que no vamos a pasar de 200. Esto tiene varios inconvenientes: se desperdicia
memoria, obliga a conocer bien los datos con los que vamos a trabajar, sigue pudiendo verse
sobrepasado, etc.
La solución suele ser crear estructuras DINÁMICAS, que puedan ir creciendo o disminuyendo
según nos interesen. Ejemplos de este tipo de estructuras son:
Las pilas. Como una pila de libros: vamos apilando cosas en la cima, o cogiendo de la
cima.
Las colas. Como las del cine (en teoría): la gente llega por un sitio (la cola) y sale por
el opuesto (la cabeza).
Las listas, en las que se puede añadir elementos, consultarlos o borrarlos en cualquier
posición.
Y la cosa se va complicando: en los árboles cada elemento puede tener varios sucesores, etc.
Todas estas estructuras tienen en común que, si se programan bien, pueden ir creciendo o
decreciendo según haga falta, al contrario que un array, que tiene su tamaño prefijado.
En todas ellas, lo que vamos haciendo es reservar un poco de memoria para cada nuevo
elemento que nos haga falta, y enlazarlo a los que ya teníamos. Cuando queramos borrar un
elemento, enlazamos el anterior a él con el posterior a él (para que no “se rompa” nuestra
estructura) y liberamos la memoria que estaba ocupando.
4.3.1. Concepto
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Un puntero no es más que una dirección de memoria. Lo que tiene de especial es que
normalmente un puntero tendrá un tipo de datos asociado: por ejemplo, un “puntero a entero”
será una dirección de memoria en la que habrá almacenado (o podremos almacenar) un
número entero.
El Operador de Dirección ( &) regresa la dirección de una variable. Este operador está
asociado con la variable a su derecha: &h; esta línea regresa la dirección de memoria de la
variable h, la que contiene nuestra máquina internamente.
*(&h)=42;
Tipo : Especifica el tipo de objeto apuntado y puede ser cualquier tipo visto con anterioridad.
Algoritmo:
inicio
declarar variables
entero x 1, y 2
entero *ip
limpiar pantalla
escribir ( ‘ Antes del uso de los apuntadores ‘ )
escribir ( ‘ X = ‘, x , ‘ Y = ‘, y, ‘ *ip = ‘ , *ip )
ip &x
y *ip
*ip 10
escribir ( ‘ Después del uso de los operadores ‘ )
escribir ( ‘ X = ‘, x , ‘ Y = ‘, y, ‘ *ip = ‘ , *ip )
pausa
fin
Programa:
#include<stdio.h>
#include<conio.h>
int x=1; y=2;
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
X= 10 y=1 *ip = 10
Algoritmo:
inicio
declarar variables
entero datos [100]
entero cuantos
entero i
largo suma 0
limpiar pantalla
hacer
escribir ( ‘ Cuantos números desea sumar ? ‘ )
leer ( cuantos )
si ( cuantos > 100 ) hacer
escribir ( ‘ Demasiados. Solo se puede hasta 100. ‘ )
fin_si
mientras ( cuantos > 100 )
para ( i = 0; i < cuantos; i++ ) hacer
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Programa:
#include <stdio.h>
main() {
int datos[100];
/* Preparamos espacio para 100 numeros */
int cuantos;
/* Preguntaremos cuantos desea introducir */
int i;
/* Para bucles */
long suma=0;
/* La suma, claro */
clrcsr();
do {
printf(" Cuantos numeros desea sumar? ");
scanf(" %d ", &cuantos);
if (cuantos>100)
/* Solo puede ser 100 o menos */
printf("Demasiados. Solo se puede hasta 100.");
} while (cuantos>100);
/* Si pide demasiado, no le dejamos */
/* Pedimos y almacenamos los datos */
for (i=0; i<cuantos; i++) {
printf(" Introduzca el dato número %d: ", i+1);
scanf(" %d ", &datos[i]);
}
/* Calculamos la suma */
for (i=0; i<cuantos; i++)
suma += datos[i];
printf(" Su suma es: %ld\n ", suma);
}
Los más avispados se pueden dar cuenta de que si sólo quiero calcular la suma, lo podría
hacer a medida que leo cada dato, no necesitaría almacenar todos. Vamos a suponer que sí
necesitamos guardarlos (en muchos casos será verdad, si los cálculos son más complicados).
Entonces nos damos cuenta de que lo que hemos estado haciendo hasta ahora no es eficiente:
• Si quiero sumar 1000 datos, o 500, o 101, no puedo. Nuestro límite previsto era de 100, así
que no podemos trabajar con más datos.
• Si sólo quiero sumar 3 números, desperdicio el espacio de 97 datos que no uso.
• Y el problema sigue: si en vez de 100 números, reservamos espacio para 5000, es más difícil
que nos quedemos cortos pero desperdiciamos muchísima más memoria.
La solución es reservar espacio estrictamente para lo que necesitemos, y eso es algo que
podríamos hacer así:
Algoritmo:
inicio
declarar variables
entero *datos
entero cuantos
entero i
largo suma 0
limpiar pantalla
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
hacer
escribir ( ‘ Cuantos números desea sumar ? ‘ )
leer ( cuantos )
datos (entero *) malloc (cuantos * sizeof(entero))
si (datos = NULL)
escribir ( ‘ No caben tantos datos en memoria. ‘ )
fin_si
mientras ( datos = NULL )
fin_hacer
escribir ( ‘ Introduzca el dato número ‘, i+1 )
leer ( datos + i )
fin_para
para ( i = 0; i < cuantos; i++ ) hacer
suma suma + *datos + i
fin_para
escribir ( ‘ Su suma es: ‘, suma )
liberar (datos)
pausa
fin
Programa:
#include <stdio.h>
#include <stdlib.h>
main()
{
int* datos; /* Necesitaremos espacio para varios numeros */
int cuantos; /* Preguntaremos cuantos desea introducir */
int i; /* Para bucles */
long suma=0; /* La suma, claro */
do
{
printf("Cuantos numeros desea sumar? ");
scanf("%d", &cuantos);
datos = (int *) malloc (cuantos * sizeof(int));
if (datos == NULL) /* Solo puede ser 100 o menos */
printf("No caben tantos datos en memoria.");
} while (datos == NULL); /* Si pide demasiado, no le dejamos */
/* Pedimos y almacenamos los datos */
for (i=0; i<cuantos; i++) {
printf("Introduzca el dato número %d: ", i+1);
scanf("%d", datos+i);
}
/* Calculamos la suma */
for (i=0; i<cuantos; i++)
suma += *(datos+i);
printf("Su suma es: %ld \n", suma);
free(datos);
}
Este programa funciona perfectamente si sólo queremos sumar 5 números, pero también si
necesitamos sumar 120.000 (y si caben tantos números en la memoria disponible de nuestro
equipo, claro).
En primer lugar, lo que antes era int datos[100] que quiere decir “a partir de la posición de
memoria que llamaré datos, quede espacio para a guardar 100 números enteros”, se ha
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
convertido en int * datos que quiere decir “a partir de la posición de memoria que llamaré datos
voy a guardar varios números enteros (pero aún no sé cuantos)”.
Luego reservamos el espacio exacto que necesitamos, haciendo datos = (int *) malloc (cuantos
* sizeof ( int ); Esta orden suena complicada, así que vamos a verla por partes:
• “malloc” es la orden que usaremos para reservar memoria cuando la necesitemos (es la
abreviatura de las palabra “memory” y “allocate”).
• Como parámetro, le indicamos cuanto espacio queremos reservar. Para 100 números
enteros, sería “100*sizeof(int)”, es decir, 100 veces el tamaño de un entero. En nuestro caso,
no son 100 números, sino el valor de la variable “cuantos”. Por eso hacemos “malloc
(cuantos*sizeof(int))”.
• Para terminar, ese es el espacio que queremos reservar para nuestra variable “datos”.
Y esa variable es de tipo “int *” (un puntero a datos que serán números enteros). Para que todo
vaya bien, debemos “convertir” el resultado de “malloc” al tipo de datos correcto, y lo hacemos
forzando una conversión como vimos en el apartado 2.4 (operador “molde”), con lo que nuestra
orden está completa:
• Si “malloc” nos devuelve NULL como resultado (un “puntero nulo”), quiere decir que no ha
encontrado ninguna posición de memoria en la que nos pudiera reservar todo el espacio que le
habíamos solicitado.
Finalmente, la forma de acceder a los datos también cambia. Antes leíamos el primer dato
como datos[0], el segundo como datos[1], el tercero como datos[2] y así sucesivamente. Ahora
usaremos el asterisco (*) para indicar que queremos saber el valor que hay almacenado en una
cierta posición: el primer dato será *datos, el segundo *(datos+1), el tercero será *(datos+2) y
así en adelante. Por eso, donde antes hacíamos suma += datos[i]; ahora usamos suma +=
*(datos+i).
También aparece otra orden nueva: free. Hasta ahora, teníamos la memoria reservada
estáticamente, lo que supone que la usábamos (o la desperdiciábamos) durante todo el tiempo
que nuestro programa estuviera funcionando. Pero ahora, igual que reservamos memoria justo
en el momento en que la necesitamos, y justo en la cantidad que necesitamos, también
podemos volver a dejar disponible esa memoria cuando hayamos terminado de usarla. De eso
se encarga la orden “free”, a la que le debemos indicar qué puntero es el que queremos liberar.
El ejemplo anterior era “un caso real”. Generalmente, los casos reales son más aplicables que
los ejemplos puramente académicos, pero también más difíciles de seguir. Por eso, antes de
seguir vamos a ver un ejemplo más sencillo que nos ayude a asentar los conceptos:
Reservaremos espacio para un número real de forma estática, y para dos números reales de
forma dinámica, daremos valor a dos de ellos, guardaremos su suma en el tercer número y
mostraremos en pantalla los resultados.
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Algoritmo:
inicio
declarar variables
real n1
real *n2, *suma
limpiar pantalla
n1 5.0
n2 (float *) malloc (sizeof(float))
*n2 6.7
suma (float *) malloc (sizeof(float))
*suma n1 + *n2
escribir ( ‘ El valor prefijado para la suma era ‘, *suma)
escribir ( ‘ Ahora es tu turno: Introduce el primer número ‘)
leer ( n1 )
escribir ( ‘ Introduce el segundo número ‘ )
leer ( n2 )
*suma n1 + *n2
escribir ( ‘ Ahora la suma es ‘, *suma)
liberar (n2)
liberar (suma)
pausa
fin
Programa:
#include <stdio.h>
#include <stdlib.h>
main() {
float n1; /* Primer número, estático */
float *n2, *suma; /* Los otros dos números */
n1 = 5.0; /* Damos un valor prefijado a n1 (real)
*/
n2 = (float *) malloc (sizeof(float));
/* Reservamos espacio para n2 */
*n2 = 6.7; /* Valor prefijado para n2 (puntero a
real) */
suma = (float *) malloc (sizeof(float));
/* Reservamos espacio para suma */
*suma = n1 + *n2; /* Calculamos la suma */
printf("El valor prefijado para la suma era %4.2f\n",
*suma);
printf("Ahora es tu turno: Introduce el primer número ");
scanf("%f",&n1); /* Leemos valor para n1 (real) */
printf("Introduce el segundo número ");
scanf("%f",n2); /* Valor para n2 (puntero a real) */
*suma = n1 + *n2; /* Calculamos nuevamente la suma */
printf("Ahora la suma es %4.2f\n", *suma);
free(n2); /* Liberamos la memoria reservada */
free(suma);
}
n1 es un “float”, así que le damos valor normalmente: n1 = 0; Y pedimos su valor con scanf
usando & para indicar en qué dirección de memoria se encuentra: scanf("%f", &n1).
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
n2 (y también “suma”) es un “puntero a float”, así que debemos reservarle espacio con “malloc”
antes de empezar a usarlo, y liberar con “free” el epacio que ocupaba cuando terminemos de
utilizarlo. Para guardar un valor en la dirección de memoria “a la que apunta”, usamos un
asterisco: *n2 = 0; Y pedimos su valor con scanf, pero sin necesidad de usar &, porque el
puntero ES una dirección de memoria: scanf("%f", n2).
En este ejemplo, no hemos comprobado si el resultado de “malloc” era NULL, porque sólo
pedíamos espacio para dos variables, y hemos dado por sentado que sí habría memoria
disponible suficiente para almacenarlas; en un caso general, deberemos asegurarnos siempre
de que se nos ha concedido ese espacio que hemos pedido.
Como se ha visto a lo largo de este manual todas las variables se enlazan a tipos específicos,
con lo cual es de concluirse que una variable apuntador también; por lo cual se enlazan a tipos
de datos específicos del lenguaje (apuntadores a variables de cierto tipo), de forma tal que a un
apuntador sólo se le pueden designar direcciones de variables del mismo tipo, que se
específico en la declaración del apuntador. Este termino es algo complejo de entender por
lo tanto se verá un ejemplo de su manejo:
Algoritmo:
entero *apun1
real *apun2
entero a
apun1 &a
apun2 &a
Programa:
int *apun1;
float *apun2;
int a;
apun1 = &a; // Esto es válido
apun2 = &a; // Esto no es válido (ya que el puntero 2 es de
tipo float, y por lo tanto debe apuntar hacia
una variable del mismo tipo y en este caso no
es así, con lo cual se genera un error)
1. Cuando un apuntador se declara, al igual que cualquier otra variable, el mismo posee un
valor cualquiera que no se puede conocer con anticipación, hasta que se inicialice con algún
valor (dirección), por ejemplo:
Algoritmo:
real *apun;
escribir ( ‘ El valor apuntado por apun es: ‘ , *apun ) // Incorrecto
*apun 3.5 // Incorrecto
Programa:
float *apun;
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
printf ( " El valor apuntado por apun es: " , *apun );// Incorrecto
*apun = 3.5; // Incorrecto
2. Después de que un apuntador ha sido inicializado, la dirección que posee puede dejar de ser
válida si se libera la memoria reservada en esa dirección, ya sea porque la variable asociada
termina su ámbito o porque ese espacio de memoria fue reservado dinámicamente y luego se
liberó1, ejemplo de esto es el siguiente:
Algoritmo:
entero *apun
entero b
function func()
entero a 40
apun &a
b *apun; // Correcto
*apun 23; // Correcto
Fin_func
inicio
limpiar pantalla
llamar a la función func()
b *apun // Incorrecto
*apun 25 // Incorrecto
pausa
fin
Programa:
int *apun, b;
void func()
{
int a = 40;
apun = &a;
b = *apun; // Correcto
*apun = 23; // Correcto
}
void main()
{
clrscr();
func();
b = *apun; // Incorrecto
*apun = 25; // Incorrecto
getch();
}
Si se intenta desreferenciar un apuntador que contiene una dirección inválida pueden ocurrir
cosas como las siguientes:
Se obtiene un valor incorrecto en una o más variables debido a que no fue debidamente
inicializada la zona de memoria que se accede a través de la dirección en cuestión. Esto puede
ocasionar que el programa genere resultados incorrectos.
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
ocurrir que el programa genere un error de ejecución y el sistema operativo lo detenga, o que el
programa no responda y deje al sistema operativo inestable.
Si declaramos una variable como int n=5 y posteriormente hacemos n++, debería resultar claro
que lo que ocurre es que aumenta en una unidad el valor de la variable n, pasando a ser 6.
Pero ¿qué sucede si hacemos esa misma operación sobre un puntero?
Algoritmo:
Declarer variables
entero *n
n (int *) malloc (sizeof(int))
*n 3
nn+1
Programa:
int *n;
n = (int *) malloc (sizeof(int));
*n = 3;
n++;
En cambio, nosotros hemos aumentado el valor de “n”. Como “n” es un puntero, estamos
modificando una dirección de memoria. Por ejemplo, si “n” se refería a la posición de memoria
número 10.000 de nuestro ordenador, ahora ya no es así, ahora es otra posición de memoria
distinta, por ejemplo la 10.001.
Porque, como ya sabemos, el espacio que ocupa una variable en C depende del sistema
operativo. Así, en un sistema operativo de 32 bits, un “int” ocuparía 4 bytes, de modo que la
operación n++.
Haría que pasáramos de mirar la posición 10.000 a la 10.004. Generalmente no es esto lo que
se quiere, sino modificar el valor que había almacenado en esa posición de memoria. Olvidar
ese * que indica que queremos cambiar el dato y no la posición de memoria puede dar lugar a
fallos muy difíciles de descubrir (o incluso a que el programa se interrumpa con un aviso de
“Violación de segmento” porque estemos accediendo a zonas de memoria que no hemos
reservado.
En C hay muy poca diferencia “interna” entre un puntero y un array. En muchas ocasiones,
podremos declarar un dato como array (una tabla con varios elementos iguales, de tamaño
predefinido) y recorrerlo usando punteros. Vamos a ver un ejemplo:
Algoritmo:
inicio
declarar variables
entero datos [ 10 ]
entero i
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
limpiar pantalla
para ( i = 1; i < 10; i++ ) hacer
datos [ i ] i * 2
fin_para
para ( i = 1; i < 10; i++ ) hacer
escribir ( datos + i )
fin_para
pausa
fin
Programa:
main()
{
int datos[10];
int i;
/* Damos valores normalmente */
clrscr();
for (i=0; i<10; i++)
datos[i] = i*2;
/* Pero los recorremos usando punteros */
for (i=0; i<10; i++)
printf ("%d ", *(datos+i));
getch();
}
Pero también podremos hacer lo contrario, declarar de forma dinámica una variable usando
“malloc” y recorrerla como si fuera un array:
Algoritmo:
inicio
declarar variables
entero *datos
entero i
limpiar pantalla
datos ( entero * )
escribir ( ‘ Uso como puntero … ‘ )
para ( i = 0; i < 20; i++ ) hacer
*(datos + i ) i * 2
fin_para
para ( i = 0; i < 20; i++ ) hacer
escribir ( *( datos + i ) )
fin_para
escribir ( ‘ Uso como array … ‘ )
para ( i = 0; i < 20; i++ ) hacer
datos [ i ] i * 3
fin_para
para ( i = 0; i < 20; i++ ) hacer
escribir ( datos [ i ] )
fin_para
liberar (datos)
pausa
fin
Programa:
#include <stdio.h>
#include <stdlib.h>
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
main()
{
int *datos;
int i;
/* Reservamos espacio */
datos = (int *) malloc (20*sizeof(int));
/* Damos valores como puntero */
clrscr();
printf(" Uso como puntero... ");
for (i=0; i<20; i++)
*(datos+i) = i*2;
/* Y los mostramos */
for (i=0; i<10; i++)
printf ("%d ", *(datos+i));
/* Ahora damos valores como array */
printf("\n Uso como array... ");
for (i=0; i<20; i++)
datos[i] = i*3;
/* Y los mostramos */
for (i=0; i<10; i++)
printf ("%d ", datos[i]);
/* Liberamos el espacio */
free(datos);
getch();
}
Existe otra posibilidad de usar punteros y arreglos, que son los arreglos de punteros,
Igual que creamos “arrays” para guardar varios datos que sean números enteros o reales,
podemos hacerlo con punteros, esto es podemos reservar espacio para “20 punteros a
enteros” haciendo:
int *datos[20];
Algoritmo:
inicio
declarar variables
carácter *mensajesError[3] {"Fichero no encontrado",
"No se puede escribir", "Fichero sin datos"}
limpiar pantalla
escribir ( ‘ El primer mensaje de error es: ‘, mensajesError[0])
escribir ( ‘ El segundo mensaje de error es: ‘, mensajesError[1])
escribir ( ‘ El tercer mensaje de error es: ‘, mensajesError[2])
pausa
fin
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN
UNIDAD IV ESTRUCUTRAS DE DATOS
Programa:
#include <stdio.h>
main() {
char *mensajesError[3]={"Fichero no encontrado","No se puede
escribir","Fichero sin datos"};
clrscr();
printf("El primer mensaje de error es: %s\n",mensajesError[0]);
printf("El segundo mensaje de error es: %s\n",mensajesError[1]);
printf("El tercer mensaje de error es: %s\n",mensajesError[2]);
getch();
}
1
Autores: Alejandra Gutierrez Reyes - Enrique Martínez Roldán - Academia de Computación – Ingenieria Eléctrica
ESIME Zacatenco IPN