Documentos de Académico
Documentos de Profesional
Documentos de Cultura
ESTRUCTURAS 1
0.1 ESTRUCTURAS
En el diseño de un programa implícitamente estamos representando parte del
“mundo real” como números que toman diversos valores dependiendo de que
parte de ese mundo real estemos representado con dicho número o variable.
Sin embargo, en el mundo real las cosas no son tan simples y normalmente
una “cosa” (suena mejor un objeto), necesita de varios números para su rep-
resentación y a cada número o conjunto de números lo podemos denominar
atributo del objeto. Por ejemplo, si quisiéramos escribir un programa para
dibujar, tenemos que de un dibujo existen varios atributos, por mencionar al-
gunos: dimensiones (ancho, alto), color del fondo, color del lápiz, si el dibujo se
basa en solo polígonos (por ejemplo un plano) tendríamos necesariamente que
tener una forma de representar cada polígono (número de vértices, y cada uno
de sus vértices; y un vértice, a la vez, tendría coordenada x e y), si en el dibujo
podemos incluir texto, tendríamos que tener variables para conocer el tamaño
del texto, el tipo de letra, color, orientación y un arreglo de caracteres para
almacenar los caracteres de la cadena que representa el texto. Y si seguimos
pensando en las partes que pueden contener el dibujo que se hace con nuestro
hipotético programa (piense que atributos tendría un círculo o una elipse), ver-
emos que se pueden ir deduciendo cada una de las partes y en algunos casos,
a su vez, cada una de ellas se puede descomponer en atributos cada vez más
simples, hasta llegar a la situación en la cual podemos decir en términos de los
tipos de datos que conocemos como se representaría el “mundo de los dibujos”
que se quiere manejar en nuestro programa. Pero para poder hacer el programa
quizás sea más simple y organizado ponerle nombres a los diversos objetos y
sus respectivos atributos que se pueden manipular en nuestro programa. Pues
bien, para éste propósito la mayoría de los lenguajes de alto nivel tienen las
estructuras, con las cuales podemos agrupar atributos de un objeto en una sola
variable, y luego referirnos a cada atributo de la variable con un nombre de
atributo que se conoce normalmente como campo o miembro de la estructura.
Para simplificar el proceso, lo que se hace es que antes de definir la(s) variable(s)
que poseerán los atributos que queramos agrupar, se define la forma que estas
tendrán (declaración de la estructura), es decir que se especifican los campos
que la componen (indicando el tipo de dato de cada campo) y a este conjunto
se le indica un nombre conocido como nombre de la estructura, y luego simple-
mente para cada objeto que se quiera representar con los atributos declarados
en la estructura se declara una variable del tipo de la estructura declarada.
{
tipo1 NombreCampo1 ;
tipo2 NombreCampo2 ;
tipo3 NombreCampo3 ;
...
tipoN NombreCampoN ;
};
Y cuando se quiera tener una variable (o sea un objeto con los atributos
definidos en la estructura) del tipo de la estructura declarada, simplemente se
utiliza la misma sintaxis que se utiliza para declarar variables:
struct N o m b r e _ e s t r u c t u r a Nombre Variable ;
No sobra decir que los nombres válidos para las estructuras tienen las mismas
reglas que se tienen para los identificadores en lenguaje C (nombres de variables,
nombres de funciones, etc), es decir que pueden medir hasta 32 caracteres, no
pueden contener espacios ni caracteres especiales, y deben empezar por letra o
el caracter 0 _0 .
Por ejemplo para declarar la estructura que contenga la información de un
vértice de los polígonos mencionados anteriormente, se declararía:
struct Vertice
{
int PosX ;
int PosY ;
};
En cuyo caso cada uno de los campos de V1 será igual a cada uno de los
campos de V2.
podemos tener un arreglo de vértices, cada uno de los cuales contiene dos pares
de valores que indican una coordenada. Por ejemplo un cuadrilátero podría
representarse con un arreglo de 4 vértices, así:
Vertice Cuadrilatero [4];
(Es una buena costumbre anteponer a los nombres de tipos de datos la letra
t, para no confundirlos con nombres de variables).
En la anterior declaración podemos observar como inicializar una estructura
que contiene estructuras (la variable Cuadrilatero2), se deben usar llaves para
indicarle el valor de cada campo que sea una estructura.
Si después de declarada la variable Cuadrilatero1, quisiéramos establecer el
valor de sus vértices se podría escribir el siguiente segmento de código:
Cuadrilatero1 . V1 . PosX = 10;
Cuadrilatero1 . V2 . PosY = 20;
...
tPoligono Cuadrado1 ={4 ,{ {100 ,100} ,{120 ,100} ,{120 ,150} ,{100 ,150}
} };
podemos declarar funciones que tomen como argumento variables del tipo de
la estructura y de la misma manera retornar también estructuras (de hecho,
en algunos casos la finalidad de declarar la estructura es poder retornar varios
valores en una función).
Así, si por ejemplo, si quisiéramos escribir una función que calcule el perímetro
de una variable del tipo tCuadrilatero mencionada anteriormente se podría
declarar una función de la siguiente forma:
float Per í metro ( tCuadrilatero C ) ;
\ begin { lstlisting }
float Per í metro ( tCuadrilatero C )
{
float P , deltaX , deltaY ;
deltaX = C . V1 . PosX - C . V2 . PosX ; deltaY = C . V1 . PosY - C . V2 . PosY ;
P = sqrt ( deltaX * deltaX + deltaY * deltaY ) ;
deltaX = C . V2 . PosX - C . V3 . PosX ; deltaY = C . V2 . PosY - C . V3 . PosY ;
P += sqrt ( deltaX * deltaX + deltaY * deltaY ) ;
deltaX = C . V4 . PosX - C . V3 . PosX ; deltaY = C . V4 . PosY - C . V3 . PosY ;
P += sqrt ( deltaX * deltaX + deltaY * deltaY ) ;
deltaX = C . V1 . PosX - C . V4 . PosX ; deltaY = C . V1 . PosY - C . V4 . PosY ;
P += sqrt ( deltaX * deltaX + deltaY * deltaY ) ;
return P ;
}
Un ejemplo de una función que retorne una estructura podría ser, escribir
una función que lea los valores de los vértices de un cuadrilátero y retorne todos
estos vértices en una variable tipo tCuadrilatero.
tCuadrilatero Le e r C u a d r i l a t e r o ( void ) ;
Y su implementación, sería:
tCuadrilatero Le e r C u a d r i l a t e r o ( void )
{
tCuadrilatero C ;
int px , py ;
return C ;
}
0.8. EJERCICIOS RESUELTOS 7
Podría el lector declarar y definir las tres funciones utilizadas (GetDir, Print-
WName y PrintWDir)?
2. Supongamos que nos piden hacer un programa para imprimir un recibo de
compra en un supermercado que maneja tres tipos de productos de acuerdo
al tipo de impuesto IVA que se pague por cada uno de ellos. Entonces
queremos que en la factura aparezca el nombre del producto, la cantidad,
el precio unitario, el precio por la cantidad y el IVA respectivo y al final el
precio total de las compras y la cantidad total de IVA pagado. Con tanta
reforma tributaria no sé los valores reales, pero la clasificación del impuesto
IVA se me ocurre que podría ser:
Tipo de Producto IVA
Aseo (0) 15%
Licor (1) 18%
Canasta Familiar (2) 12%
0.8. EJERCICIOS RESUELTOS 9
struct Factura
{
Producto P [50];
int CantxP [50];
int nProductos ;
};
Se pudo haber utilizado valor del iva en vez de tipo de iva, pero pensemos
que si en algún momento cambiara la reglamentación y por ejemplo no se
cobrara 18% por los licores sino el 20%, tal como se implementó con solo
cambiar en un sitio se cambiaria en todos los productos del mismo tipo, pero
si hubieramos utilizado el valor, sería necesario hacer los cambios en cada
uno de los productos.
Para esta aplicación es suficiente con los campos descritos, pero en una apli-
cación de verdad, a lo mejor se le agregen otros campos al producto como por
ejemplo: código de barras, cantidad en stock (para procesos de inventario),
proveedor (para efectos de compras), etc. Y si se dispone de un código por
cada producto sería mucho mas eficiente que en vez de tener en la estructura
Factura un campo con un arreglo de la estructura Producto, se tuviera un
10
campo con un arreglo de enteros con el código cada producto (de esta forma
se ahorraria mucha memoria pues un arreglo de enteros mide mucho menos
que un arreglo de tipo Producto). Pero como es solo un ejemplo, pensemos
que podemos manejarlo de la forma descrita.
Entonces los productos que se venden en el supermercado podrían estar al-
macenados en un arreglo global de tipo Producto, y simplemente cuando se
especifique una compra, el operador indicará los productos que hacen parte
de la compra y el programa lo almacenará en una variable tipo Factura la
cual se utilizará para imprimir la factura. (Simplificaremos el programa di-
vidiendo en dos el proceso que se hace en una compra de verdad, primero se
especifican los productos y por aparte se hacen los cálculos).
El arreglo globlal Producto podría declararse y a la vez inicializarse con cada
uno de los productos (en una aplicación de verdad, lo mejor sería tener estos
datos almacenados en un archivo, de tal suerte que se pudieran agregar o
quitar elementos de acuerdo a las compras que realice el supermercado).
Producto productos [ ] ={ { " Jab ó n Supremo 200 mg " ,2300 ,0} ,
{ " Jab ó n Ajax 150 mg " ,3000 ,0} ,
{ " Arroz FlorHuila 500 mg " ,4000 ,2} ,
{ " Salchich ó n Cervecero Zen ú 2 lb "
,8000 ,2} ,
{ " Botella de Aguardiente Cristal "
,20000 ,1} ,
{ " Botella de Brandy Domeq " ,23000 ,1}};
int nProductos = sizeof ( productos ) / sizeof ( productos [0]) ;
clrscr () ;
for ( i =0; i < nProductos ; i ++)
{
printf ( " % d % s $ % ld \ n " ,i +1 , productos [ i ]. nombre ,
productos [ i ]. Precio ) ;
}
printf ( " Eliga el Producto (0 para terminar ) " ) ; scanf ( "
% d " ,& p ) ;
if ( p ==0) break ;
p - -;
printf ( " Producto % s \ n " , productos [ p ]. nombre ) ;
printf ( " \ nSeguro ? " ) ; op = getch () ;
if (( op == ’s ’) ||( op == ’S ’) )
{
printf ( " \ nCantidad = " ) ;
scanf ( " % d " ,& c ) ;
F . P [ j ]= productos [ p ]; // a l m a c e n a el p r o d u c t o en la
factura
F . CantxP [ j ]= c ; j ++; // y la c a n t i d a d y
pasa al s i g u i e n t e e l e m e n t o
}
} while (1) ;
printf ( " \ nTotal productos esta factura % d \ n \ n " ,j ) ;
F . nProductos = j ;
printf ( " # Producto Cantidad \ n " )
;
for ( i =0; i < j ; i ++)
printf ( " %3 d % -35 s % d \ n " ,i +1 , F . P [ i ]. nombre , F . CantxP [ i ]) ;
printf ( " \ nDesea repetir la factura " ) ;
op = getch () ;
if (( op != ’S ’) &&( op != ’s ’) ) break ;
} while (1) ;
clrscr () ;
return F ;
}
Esta es una función muy sencilla, lo mejor sería que el usuario pudiera modi-
ficar el listado quitando o agregando elementos (sin tener que repetirla toda),
pero tengamos en cuenta que es solo un ejemplo.
En la implementación de la función ImprimirFactura se muestra uno a uno los
productos tal como aparece en el ejemplo de impresión y simultáneamente se
va calculando el total del IVA sumando el respectivo IVA de cada producto
y claro está, el total de la factura sumando los repectivos precios. Es el
momento de describir exactamente como se calculó el IVA total en el ejemplo
de la factura para que podamos escribir dicho cálculo en la función. El IVA
total es la suma del IVA de todos los productos. Y el IVA de cada producto se
calcula de la siguiente manera. En los precios de los artículos que aparecen
en los supermercados ya está incluido el costo del respectivo IVA, quiere
decir esto que si por ejemplo tenemos que el producto “Jabon Ajax 150 mg”
tiene un costo final de $3.000, en realidad estamos pagando el 100% del valor
del artículo más un recargo del 15%. O sea que 3.000 corresponde con el
115% del costo real del producto, pero si queremos calcular solo el 15% pues
utilizando una regla de tres simple podemos decir que:
12
Podrá notar el lector como para leer cada nota no hemos escrito scanf("%f",&A.Notas[i]);
que podría ser una forma más simple a la utilizada, y en vez de eso, utilizamos
la variable Nota y luego se la asignamos al elemento correspondiente del ar-
reglo A.Notas[i], y esto debido a que en la implementación del compilador de
Borland por alguna misteriosa razón la dirección calculada con &A.Nota[i],
no corresponde con la posición iésima del arreglo. Esto se debe tener en
cuenta cada que se quiera calcular la dirección de un elemento de un arreglo
que hace parte de una estructura.
El campo A.Definitiva no se le pide al usuario, pues éste puede ser calculado
a partir de los valores de las notas ingresadas.
La función que permita mostrar los datos de un alumno puede ser declarada
de la siguiente forma:
void MostrarAlumno ( Alumno A ) ;
...
for ( i =0; i < nAl ; i ++)
MostrarAlumno ( Alumnos [ i ]) ;
}
...
...
for ( i =0; i < n ; i ++)
{
MostrarAlumno ( LA [ i ]) ;
...
}
}
...
0.8. EJERCICIOS RESUELTOS 17
{
printf ( " Pulse una tecla para continuar " ) ;
getch () ; clrscr () ;
}
}
}
particular; también puede ocurrir que se quiera ordenar con dos campos: por
ejemplo por nota definitiva y por primera nota (es decir que si la ordenación
es descendente y dos alumnos tienen la misma definitiva aparecerá primero
el que tenga la primera nota más alta). La solución más general podría ser
que se redeclare la función con un parámetro más que indique el criterio de
ordenación (o en otras palabras por que campo se desea ordenar), algo así
como:
void OrdenarNotas ( Alumno Al [ ] , int nAl , int op ) ;
Donde para op=0, se ordena por el campo nombre, para op=1 se ordena
por el campo código, etc. Asumamos que cambiamos la declaración por la
indicada. Entonces debemos hacer cada una de las ordenaciones indicadas
(luego decidiremos si cada ordenación en particular la implementamos en la
misma función o como una función aparte que se invoca de acuerdo al valor
del parámetro op). Veamos un poco acerca de cómo se ordena un arreglo,
independientemente del campo a ordenar:
Ordenar un arreglo es un problema que surge en muchos programas y por lo
tanto es necesario saber como se hace porque lo más seguro es que tengamos
que hacerlo en otros programas (Haremos una breve discusión sobre como se
ordena, aunque la verdad es que este tópico a lo mejor ya se discutió en clase
cuando se trataba el tema de arreglos). Y precisamente por ser un problema
que se presenta tan a menudo, ya otras personas le han dedicado bastantes
horas a diseñar algoritmos de ordenación; en algunos libros inclusive se le
dedica todo un capitulo a enseñar métodos de ordenación, le recomiendo que,
si tiene tiempo, se dedique a leer sobre estos métodos. Los diferentes métodos
que se pueden aplicar varían en complejidad dependiendo de la velocidad
con que se ordenan los datos y/o los recursos a nivel de máquina (memoria,
capacidad de recursividad, etc) que requieren para su implementación (existe
inclusive una disciplina que se encarga de determinar la complejidad de un
algoritmo y uno de los temas fundamentales que trabajan es determinar cual
de los algoritmos de ordenación es más eficiente). Pero independiente del
método, siempre se deben comparar los datos entre si para decidir quien se
ubica en una posición específica, lo que distingue un método de otro es la
forma en que se recorre el arreglo para comparar los datos entre sí. El método
que explicaremos será el más simple de codificar, pero lamentablemente no
es un método muy eficiente cuando la cantidad de datos crece. Se conoce
como el método de la “burbuja”.
En pocas palabras el método de la burbuja consiste en buscar el menor de
todos los datos y ubicarlo en la primera posición (intercambiando el ele-
mento que este en la primera posición con el menor encontrado), luego le
aplica el mismo tratamiento al resto del arreglo (partiendo desde la segunda
posición) y así sucesivamente hasta llegar a la última posición del arreglo
(La explicación anterior es para ordenar ascendentemente, para ordenarlo
descendentemente cambiar la palabra menor por mayor). Para mayor clari-
dad mostremos como funciona el algoritmo con un pequeño arreglo de datos:
0.8. EJERCICIOS RESUELTOS 21
Pensemos, ahora si, como se codificaría en lenguaje c los pasos que acabamos
de plantear. Recapitulando todo se podría decir que:
Para cada posición i del arreglo (cada posición indica que i varia desde 0
hasta el tamaño del arreglo) buscar la posición del menor de los datos que se
encuentran de i hasta la posición final. Llamemos a esta posición pMenor.
Una vez hallado el menor intercambiar el elemento de la posición pMenor
con el elemento de la posición i.
Pero si recordamos, ya implementamos una función que nos buscaba la posi-
ción donde se encontraba el menor elemento de un arreglo (en un ejercicio
resuelto del tema de arreglos), entonces incorporamos ese código dentro de
un ciclo de i hasta el tamaño del arreglo (otra forma podría ser adaptar la
función que se escribió aquella vez para que se invocara en éste punto), y
después de terminada la búsqueda del menor intercambiamos el elemento de
la posición iesima con el de la posición pMenor. Todo esto en lenguaje C se
codifica:
int i ,j , pMenor ;
int menor ;
Ya teniendo escrita las tres ordenaciones que se van a realizar, debemos de-
cidirnos por la forma de codificarlas ya en la aplicación: aunque pueden exi-
stir muchar formas de hacerlo veamos tres de ellas y analicemos sus ventajas
y desventajas.
Una primera forma sería decidir al inicio de la función cuál tipo de ordenación
se desea hacer (con el parámetro op) y en una estructura switch incorporar
las tres implementaciones que hemos escrito como parte del código. La es-
tructura de la función OrdenarNotas sería:
void OrdenarNotas ( Alumno Al [ ] , int nAl , int op )
{
int i ,j , pMenor ;
Alumno menor ;
switch ( op )
{
case 0: // por nota d e f i n i t i v a
for ( i =0; i < nAl ; i ++)
{
menor = Alumno [ i ]; iMenor = i ;
for ( j = i ;j < nAl ; j ++)
24
{
if ( Alumno [ j ]. promedio > menor . promedio ) ...
}
...
...
break ;
case 1: // por c ó digo
for ( i =0; i < nAl ; i ++)
{
menor = Alumno [ i ]; iMenor = i ;
for ( j = i ;j < nAl ; j ++)
{
...
...
break ;
case 2: // por nombre
for ( i =0; i < nAl ; i ++)
{
menor = Alumno [ i ]; iMenor = i ;
for ( j = i ;j < nAl ; j ++)
{
if ( strcmp ( Datos [ j ]. nombre , menor . Nombre ) <0) ...
...
...
break ;
}
}
switch ( op )
{
case 0: O r d e n a r P o r P r o m e d i o ( Al , nAl ) ; break ; // por nota
definitiva
case 1: O r d e n a r P o r C o d i go ( Al , nAl ) ; break ; // por c ó digo
case 2: O r d e n a r P o r N o m b re ( Al , nAl ) ; break ; // por nombre
}
}
De estas dos formas podemos comentar que si bien la segunda organiza mejor
el código al separar cada ordenación como una función aparte, parte del
código que se implementa para las tres opciones en ambos casos se repite
por cada una de ellas (los ciclos de i y de j, la elección del menor y el
intercambio). En la tercera forma usamos solo una vez la parte común de
las tres ordenaciones y solo cuando toque comparar los dos elementos del
arreglo, utilizamos un switch para que ésta se realice de forma apropiada en
cada caso, así:
void OrdenarNotas ( Alumno Al [ ] , int nAl , int op )
{
int i ,j , pMenor ;
Alumno menor ;
for ( i =0; i < nAl ; i ++)
{
menor = Alumno [ i ]; iMenor = i ;
for ( j = i ;j < nAl ; j ++)
{
switch ( op )
{
case 0:
if ( Alumno [ j ]. promedio > menor . promedio )
{
menor = Alumno [ j ]; pMenor = j ;
}
break ;
case 1:
...
break ;
case 2:
if ( strcmp ( Alumno [ j ]. nombre , menor . nombre ) <0)
{
menor = Alumno [ j ]; pMenor = j ;
}
26
break ;
}
}
// i n t e r c a m b i o de los e l e m e n t o s
temp = Datos [ i ]; Datos [ i ]= Datos [ pMenor ]; Datos [ pMenor ]=
temp ;
}
}
Inclusive existe una cuarta forma y sería implementar como función lo que
se escribe dentro del condicional e invocarla apropiadamente para cada valor
de op (código que se simplificaría bastante si se utiliza punteros a funciones
que será un tema que trataremos posteriormente).
La implementación de la función main le queda al lector como tarea (¿tanta
carreta para no mostrar el programa completo? Pues si, o que creyó? que se
lo iba a hacer todo? No señor, aquí trabajamos todos).
2. Escribir una funcion que tenga como parametro un flotante con una mag-
nitud en metros y retorne una estructura con el numero escalado y las
respectivas unidades en las que queda mejor representado el numero (dos
decimales), teniendo la siguiente convencion:
0 en metros, 1 en kilometros, 2 en Megametros, 3 en milimetros, 4 en
micrometros.
0.9. EJERCICIOS PROPUESTOS 27
3. Declarar una estructura para representar cada una de las fuerzas que
actuan sobre una partícula: valores y ángulos en radianes y escribir una
funcion a la cual se le pasa una variabe con la estructura y en la funcion
se muestre el valor y el ángulo de la fuerza resultante.
Por ejemplo si se escribe:
float PI = 3.141592;
Particula P ;
VerResultante ( P ) ;
En pantalla aparece
Montoya. Puesto 3. Pits 2. Velocidades= 265. M.Shumaher=260.0
Irvine=250.0
28
5. Se decide hacer un programa para que juege el juego de naipes 21, para
lo cual se utiliza una estructura asi:
struct Partida
{
int Ncartas ;
int Cartas [10];
};
Escribir una función que tenga como parámetro una partida y retorne un
1 si se debe pedir otra carta y 0 si debe deternerse. (debe escoger la mejor
opción, tal como lo haría un buen jugador). Por ejemplo:
Partida PC ;
...
// si PC . Ncartas =2; PC . Cartas [0]=4; PC . Cartas [1]=7;