Está en la página 1de 28

0.1.

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.

0.2 DECLARACIÓN DE ESTRUCTURAS


Entonces una estructura funciona como un nuevo tipo de dato, del cual podemos
declarar las variables que queramos de la misma forma que se declaran variables
de los tipos de datos “usuales” del lenguaje. En lenguaje C la declaración de la
estructura se hace de la siguiente manera:
struct N o m b r e _ e s t r u c t u r a
2

{
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 ;

O en algunas implementaciones de lenguaje C se puede omitir la palabra


struct.
N o m b r e _ e s t r u c t u r a Nombre Variable ;

Si el compilador no permite declarar variables del tipo de la estructura sin


la palabra struct, se puede hacer explicita la declaración de un nuevo tipo de
dato con la palabra reservada typedef en la declaración de la estructura, y ya
podemos omitir la palabra struct al inicio de la declaración de variables de dicho
tipo, así:
typedef struct
{
tipo1 NombreCampo1 ;
tipo2 NombreCampo2 ;
tipo3 NombreCampo3 ;
...
tipoN NombreCampoN ;
} Nombre_estructura ;

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 ;
};

Y para declarar la variable V1 de tipo vértice, se escribe:


Vertice V1 ;
0.3. ACCESO A CAMPOS DE LA ESTRUCTURA 3

0.3 ACCESO A CAMPOS DE LA ESTRUC-


TURA
Entonces, una vez le hemos indicado al compilador que existe una variable del
tipo de la estructura, ya podemos usarla para conocer o modificar los valores
de cada uno de los campos, utilizando el operador de acceso a campos que en
lenguaje C es el punto (.); indicando primero el nombre de la variable y luego
el nombre del campo a utilizar, así:
Nombr eVariabl e . NombreCampo

Asi, si queremos que la variable V1, represente el vértice ubicado en la


posición (100,200) tendríamos que escribir el siguiente segmento de código:
V1 . PosX =100; V2 . PosY =200;

En lenguaje C también es posible indicar los valores iniciales de los cam-


pos cuando se declara la variable, de tal suerte que si quisiéramos una vari-
able V2, que inicie indicando el vértice (50,150), se podría escribir la siguiente
declaración:
Vertice V2 ={50 ,150};

Es posible omitir valores iniciales de algunos campos, así si se declara:


Vertice V3 ={80};

Se le indica al compilador que la variable V3 inicia con un valor de 80 en el


campo PosX y el campo PosY puede iniciar con cualquier valor.
Normalmente, cada que queramos referirnos a una variable que es una es-
tructura, tenemos que hacer referencia a un campo en particular. Así parezca
lógico, el siguiente segmento de código es erróneo:
V1 ={10 ,20};

Pues esto solo se puede hacer en la declaración. Y si se escribiera:


V1 =20;

También se generaría un error pues el compilador no sabría si se quiere


asignar el 20 al campo PosX o al campo PosY.
Solo existe una situación en la cual se puede acceder a toda la estructura,
y es cuando se quiere copiar toda una variable en otra. Entonces, continuando
con las dos variables V1 y V2 de ejemplo se puede escribir el código:
V1 = V2 ;

En cuyo caso cada uno de los campos de V1 será igual a cada uno de los
campos de V2.

0.4 ARREGLOS DE ESTRUCTURAS


No solo podemos crear variables simples del tipo de una estructura, también
es posible declarar un arreglo con variables del tipo de la estructura. Entonces
4

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];

Y si por ejemplo queremos tener un cuadrilatero que inicialmente represente


un cuadrado con un vértice en 0,0 y de lado 100, podemos escribir el siguiente
segmento de código.
Vertice Cuadrado [4] ={{0 ,0} ,{0 ,100} ,{100 ,100} ,{0 ,100}};

Si en la variable Cuadrilatero queremos contener la información de un rec-


tángulo de 100 unidades de ancho por 50 unidades de alto, con el vértice de la
esquina superior derecha (asumiendo que la coordenada y aumenta hacia abajo)
en (30,20) después de haber sido declarada, entonces escribiremos el siguiente
segmento de código:
Cuadrilatero [0]. PosX = 30 ; Cuadrilatero [0]. Posy =20; // primer v é
rtice
Cuadrilatero [1]. PosX = 130; Cuadrilatero [1]. Posy =20; // segundo v é
rtice
Cuadrilatero [0]. PosX = 130; Cuadrilatero [0]. Posy =70; // tercer v é
rtice
Cuadrilatero [0]. PosX = 30; Cuadrilatero [0]. Posy =70; // cuarto v é
rtice

(Ojo, primero se indica el índice del elemento y luego el campo al que se


quiere acceder).

0.5 ESTRUCTURAS CON CAMPOS QUE SON


ESTRUCTURAS
Es posible declarar una estructura contenga campos que a su vez también sean
estructuras. Así por ejemplo una forma alternativa de definir cuadriláteros a
partir de la estructura Vértice, puede ser definir primero una estructura que
tenga cuatro vértices y luego declarar una variable de éste nuevo tipo para
representar un cuadrilátero. Así:
struct tCuadrilatero // se asume que antes ha sido d e c l a r a d a la
e s t r u c t u r a Vertice
{
Vertice V1 ;
Vertice V2 , V3 ; // se pueden d e c l a r a r varios campos de un mismo
tipo de una vez .
Vertice V4 ;
};

Y luego declarar las variables tipo tCuadrilatero, así:


tCuadrilatero Cuadrilatero1 , Cuadrilatero2 ={
{0 ,0} ,{100 ,0} ,{100 ,100} ,{0 ,100}};
0.6. ESTRUCTURAS CON CAMPOS QUE SON ARREGLOS 5

(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;
...

Note como se debe establecer el nombre del campo de la estructura tCuadri-


latero y después indicarle el nombre del campo de la estructura Vertice.

0.6 ESTRUCTURAS CON CAMPOS QUE SON


ARREGLOS
Otro ejemplo, puede ser de una vez implementar una estructura que me permita
representar cualquier polígono (de máximo 20 vértices). Para lo cual podría
definir una estructura que tuviera un arreglo de vértices y un campo que indicara
la cantidad de vértices que se están usando para una figura en particular. Así:
struct tPoligono
{
int nVertices ;
V é rtices V [20];
};

Y para definir las diferentes figuras se podría escribir:


tPoligono Triangulo2 ={3 ,{ {0 ,0} ,{100 ,100} ,{0 ,100}} };

tPoligono Cuadrado1 ={4 ,{ {100 ,100} ,{120 ,100} ,{120 ,150} ,{100 ,150}
} };

Para indicar los valores de los vértices de la variable Triangulo1, se podría


escribir un segmento de código similar a:
tPoligono Triangulo1 ;

Triangulo1 . nVertices =3;


Triangulo1 . V [0]. PosX =0; Triangulo1 . V [0]. PosY =80;
...

0.7 FUNCIONES Y ESTRUCTURAS


Como ya se ha dicho una vez declarada una estructura, ésta se constituye en
un nuevo tipo de dato, y por lo tanto al igual que con los otros tipos de datos
6

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 ) ;

Ya en la implementaci ó n de la funci ó n podemos acceder a cada uno


de los campos de la variable C para hacer los c á lculos
respectivos :

\ 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 ;
}

Como ejercicio, podría el lector implementar la función:


float Per í metro ( tPoligono 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 ;

printf ( " V é rtice 1 = " ) ; scanf ( " % d % d " ,& px ,& py ) ;


C . V1 . PosX = px ; C . V2 . PosY = py ;
printf ( " V é rtice 2 = " ) ; scanf ( " % d % d " ,& px ,& py ) ;
C . V2 . PosX = px ; C . V2 . PosY = py ;
printf ( " V é rtice 3 = " ) ; scanf ( " % d % d " ,& px ,& py ) ;
C . V3 . PosX = px ; C . V3 . PosY = py ;
printf ( " V é rtice 4 = " ) ; scanf ( " % d % d " ,& px ,& py ) ;
C . V4 . PosX = px ; C . V4 . PosY = py ;

return C ;
}
0.8. EJERCICIOS RESUELTOS 7

Como ejercicio, podría el lector implementar la función:


tPoligono LeerPoligono ( void ) ;

El desarrollo del tema de estructuras se ha hecho con las estructuras para


manejar dibujos, sin embargo la parte de dibujar los objetos se ha omitido debido
a que en el sistema operativo DOS no se tiene una manera estandar de acceder a
la pantalla en modo gráfico y esta es implementada en cada compilador de una
manera distinta; sin embargo para el estudiante que este interesado en dibujar
los objetos, le recomiendo leer las ayudas que trae Borland sobre el manejo
gráfico (en éste entorno se conoce como BGI, Borland Graphics Interface).
Veamos otros ejemplos donde utilicemos estructuras:

0.8 EJERCICIOS RESUELTOS


1. Cuando accedemos a una página en internet, al explorador le suministramos
su dirección en una cadena. por ejemplo “www.uniquindio.edu.co”. Pero para
ubicar el servidor que contiene dicha página este nombre solo no es suficiente,
pues es necesario conocer su dirección IP, que no es más que un código de
cuatro números de 0 a 255 que sirve para identificar unívocamente cualquier
computador en el mundo. Entonces el programa explorador busca dentro de
las últimas direcciones utilizadas si ya conoce la dirección IP de la página
solicitada, si la encuentra intenta hacer conexión con el computador con el
respectivo IP, sino entonces hace conexión con un servidor de direcciones (del
cual ya conoce su dirección IP) al cual envía la cadena con el nombre de la
página y le solicita el valor del IP y si el servidor de direcciones la ubica
entonces procede a hacer la conexión, sino le indica al usuario que la página
no existe. El ejercicio consiste en declarar las estructuras necesarias para
manejar la dirección IP y la cadena con el nombre de la página para poder
realizar la búsqueda ya sea en la base de datos del explorador o en el servidor
de direcciones:
Solución:
La dirección IP como ya se dijo es un código de 4 dígitos de 0 a 255. Es
necesario definirlo con el tamaño más pequeño posible pues es un dato que
viaja por la red y entre más pequeño sea el dato mejor (mas rápido viaja),
además debe ser compatible con la representación que tienen los otros com-
putadores, entonces con una estructura con cuatro datos tipo unsigned char
se puede representar la dirección IP, así:
struct IPAddress
{
u n s i g n e d char c1 ;
u n s i g n e d char c2 ;
u n s i g n e d char c3 ;
u n s i g n e d char c4 ;
};
8

Como necesitamos asociar la dirección IP a una página en particular entonces


podemos idearnos otra estructura que contenga tanto la dirección IP como
el nombre de la página, esta podría tener la siguiente declaración:
Struct wwwDirName
{
IPAddress IP ;
char Pagina [200];
};

Con estas dos estructuras declaradas, la resolución de la dirección IP dado


el nombre de la página es solo cuestión de buscar el nombre de la página en
un arreglo que contenga elementos de la estructura wwDirName y utilizar el
respectivo valor del IP hallado, esta función la podemos denominar GetDir.
Podríamos también definir una función que nos muestre el valor del IP y otra
que nos muestre el valor de la página para que el siguiente código funciones
comc se indica:
IPAdress IPA ;
WWWDirName WN ;
WWWDirName Directorio [20]={ { { 0 x10 ,0 x20 ,0 x30 ,0 x40 } , " www .
microsoft . com " } ,
{ { 0 x20 ,0 x25 ,0 x15 ,0 x35 } , " www ,
analog . com " } ,
....{ 0 x29 ,0 x30 ,0 x10 ,0 x08 } , " www .
uniquindio . edu . co " } , ...
};

IPA . c4 = 0 x29 ; IPA . c3 = 0 x30 ; IPA . c2 =0 x10 ; IPA . c1 =0 x8 ; /* rango de 0


a 0 xFF */

WA = GetDir ( IPA , Directorio ,20) ;

PrintWName ( WA ) ; /* en p a n t a l l a aparece " www . u n i q u i n d i o . edu . co " */


PrintWDir ( WA ) ; /* en p a n t a l l a aparece 41. 48.16.8 */

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

(NOTA: lo más seguro es que estos valores aumenten en el transcurso del


tiempo).
Si por ejemplo el usuario compra dos botellas de aguardiente, un salchichón
(para acompañar el aguardiente) y un jabon de cocina (para lavar el mugrero
que deje la parranda). La factura que se debe imprimir será:
Producto Precio Cant. IVA Subtotal
Botella de aguardiente Cristal 20.000 2 18% 40.000
Salchichón Cervecero Zenú 2lb 8.000 1 12% 8.000
Jabón Ajax 150 mg. 3.000 1 15% 3.000
Total a Pagar 51.000
Total IVA esta Factura 7.350
Solución:
Por lo pronto no entremos en detalles de cómo se hacen los cálculos. Pensemos
primero de que forma podemos representar los datos necesarios para iden-
tificar un producto y para indicar los productos que contiene una compra.
Entonces debemos pensar en utilizar dos estructuras: una para el producto
y otra para la factura. De un producto se tiene nombre, precio y tipo de
IVA. De una factura se tienen varios productos y la cantidad comprada de
cada producto (los otros valores que aparecen en la factura pueden ser cal-
culados por el programa en el momento de imprimir la factura). Entonces
las declaraciones de las estructuras podrían ser:
struct Producto
{
char nombre [100];
long Precio ;
int tipoIVA ;
};

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]) ;

La variable nProductos servirá para indicar la cantidad de productos que


vende el supermercado. En la declaración se ha inicializado esta variable con
el tamaño del arreglo productos. Como ya se comentó en el tema de arreglos
esta forma de calcular el tamaño de un arreglo es muy utilizada pues nos
ahorra el tiempo de tener que contar los elementos y además permite que
si en algún momento se cambia el arreglo se actualice apropiadamente el
tamaño.
Construyamos entonces dos funciones: una que pida los productos y los al-
macene en la estructura Factura y otra que imprima los campos de la estruc-
tura Factura. Los prototipos de estas dos funciones podrían ser:
Factura LeerProductos () ;
void I mp ri mi r Fa ct ur a ( Factura F ) ;

Para la implementación de la función LeerProductos, se muestran los pro-


ductos y el usuario elige uno y la cantidad que desea y repite el proceso hasta
el último producto. En lenguaje C, la función quedaría:
Factura LeerProductos ()
{
int i , j =0 ,p , op , c ;
Factura F ;
do {
do {
0.8. EJERCICIOS RESUELTOS 11

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

IVA Producto = Costo*15%/115%


Entonces se cálcula para cada una de las compras el respectivo IVA y se suma
en una sola variable, para el ejemplo tenemos que:
IVA Botella Aguardiente = 40.000*18%/118% = 6101,7
IVA Salchichón = 8.000*12%/112% = 857,1
IVA Jabon = 3.000*15%/115% = 391,3
Total Iva = 7350,1
Debe tenerse en cuenta que en el calculo del IVA se hace el porcentaje con el
precio equivalente al precio por la cantidad (por ejemplo para la botella de
aguardiente se hizo con 40.000 y no con 20.0000). La función queda:
void I mp ri mi r Fa ct ur a ( Factura F )
{
int i ;
float ivaP , Total_IVA , TotalFactura ;
int iva [] ={15 ,18 ,12};
float IVAporTipo [ ] = { 15/115 ,18/118 ,12/112};
Total_IVA =0; TotalFactura =0;
printf ( " % -35 s % -10 s % -10 s % -5 s % -5 s \ n " ,
" Producto " ," Precio " ," Cantidad " ," IVA " ," Subtotal " ) ;
for ( i =0; i < F . nProductos ; i ++)
{
printf ( " % -35 s " ,F . P [ i ]. nombre ) ;
printf ( " %7 ld " ,F . P [ i ]. Precio ) ;
printf ( " %7 d " ,F . CantxP [ i ]) ;
printf ( " %9 d " , iva [ F . P [ i ]. tipoIVA ]) ;
printf ( " %7 ld \ n " ,F . P [ i ]. Precio * F . CantxP [ i ]) ;
TotalFactura += F . P [ i ]. Precio * F . CantxP [ i ];
Total_IVA += F . P [ i ]. Precio * F . CantxP [ i ]* IVAporTipo [ F . P [ i ].
tipoIVA ];
}
printf ( " %+57 s %7.0 f \ n " ," Total a Pagar " , TotalFactura ) ;
printf ( " %+57 s %8.1 f \ n " ," Total IVA esta Factura " , Total_IVA ) ;
}

Yo estaba convencido de que éste código funcionaba, pero lo ejecute y siempre


me aparece el Total IVA esta Factura con un valor de 0, y me demoré 10
minutos encontrando el error, será capaz usted de encontrar el detalle en
menos tiempo?
Observe como se utilizan los indices en las estructuras en este ejemplo.
Como nota curiosa mire como se hace para imprimir justificando a la izquierda
una cadena en las líneas que imprimen los totales: utilizando el modificador
%+57s.
Si bien la impresión se hizo sobre la pantalla, para convertir este código a uno
que imprima sobre la impresora simplemente basta cambiar cada sentencia
printf por fprintf ( stdprn ..)

Así por ejemplo la línea:


print ( " % -30 s " ,F . P [ i ]. nombre ) ;
0.8. EJERCICIOS RESUELTOS 13

se cambia por la línea:


fprint ( stdprn , " % -30 s " ,F . P [ i ]. nombre ) ;

Cuando se llegue al tema de archivos se explicará en detalle la razón para


que esto funcione.
Como siempre, queda una parte pendiente y es la implementación de la fun-
ción principal, que se le deja al lector como tarea.
3. Se desea escribir un programa que manipule los datos de una lista de notas
de una materia en particular. Es decir que el profesor pueda indicar cada
nota de cada estudiante, y luego pueda hacer consultas sobre dichas notas, o
calcular el promedio, etc.
Solución: Una vez se conocen las estructuras, el diseño de un programa
parte de especificar las estructuras necesarias para manipular los datos que
se quieren representar para luego indicar las funciones apropiadas para que
cada una de las acciones que se quieren realizar sobre los datos sea llevada
a buen termino, (si se compara este procedimiento con respecto a la forma
como se diseñan los programas una vez que se conocen las funciones hay un
cambio drástico en la metodología pues en aquel entonces nos preocupábamos
primero por establecer la función principal, a partir de ahí se definían las
funciones a implementar y la forma en que se representarían las variables
no era tan importante, es decir que la forma de representar el “mundo real”
con los datos era irrelevante, mientras que ahora se constituye en el paso
principal).
Entonces, si el programa debe procesar datos de un estudiante, el diseño de
la estructura es cuestión de determinar cuales datos de un estudiantes son
importantes para poder procesar sus notas: podemos hacer el ejercicio men-
tal de determinar los atributos necesarios para cada objeto (en ésta caso cada
estudiante) o si ya se tiene un documento que nos sirva, pues nos ahorramos
la energía consumida en dicho proceso mental, entonces si miramos el doc-
umento que usa el profesor para registrar las notas de un grupo, vemos que
en éstas aparece para cada estudiante, el código, el nombre y unas casillas
donde se registra cada una de sus notas y al final una casilla que indica la nota
definitiva. (Cuando se decide sistematizar un procedimiento, es buena cos-
tumbre partir de los “formatos” que el usuario manipula para que el manejo
de nuestro programa le sea más familiar a la hora de utilizarlo). Una primera
aproximación a la estructura que utilizaremos para un estudiante podría ser:
struct Alumno
{
char nombre [60];
long Codigo ;
float Nota1 , Nota2 , Nota3 , Nota4 , Nota5 , Nota5 ;
float Definitiva ;
};

Antes de pensar en las funciones, pensemos que ventajas y que desventa-


jas tiene la anterior declaración. El hecho de reunir en una sola variable el
14

código, con el nombre y sus respectivas notas, permitirá que cuando en el


programa se asigne una nota se pueda conocer unívocamente a quien se le
está asignando dicha nota (pues en este caso se puede mostrar el código y el
nombre respectivo), (una alternativa pudo haber sido usar un solo arreglo de
flotantes para las notas y referirse a un estudiante por el número de lista, pero
es mucho más fácil equivocarse al asignar una nota con ésta representación).
Sin embargo la forma de indicar cada nota como una variable independiente
(Nota1, Nota2, etc), será un problema a la hora de hacer los cálculos, pues
se deberá conocer cuantas notas se han procesado para luego especificar una
forma de calcular el promedio de acuerdo a dicho valor. Entonces si usamos
el concepto de arreglo y las notas no las declaramos como variables indepen-
dientes sino como un arreglo, se pueden simplificar los procedimientos. Pero
como se tiene un arreglo, es necesario que en todo momento se pueda conoce
lar cantidad de elementos que se están utilizando en dicho arreglo, para ver
hasta donde se realizan los ciclos que lo recorren; por lo tanto a la estruc-
tura le cambiamos los campos de las notas por un arreglo y una variable que
determine la cantidad de elementos utilizados:
struct Alumno
{
char nombre [60];
long Codigo ;
float Notas [10];
int nNotas ;
float Definitiva ;
};

Una vez declarada la estructura, ya podemos pensar en algunas funciones


útiles para el programa que nos hemos propuesto. Las dos más elementales
pueden ser una que nos permitan ingresar los datos de un alumno y otra
que nos permita mostrarlos. Entonces diseñemos primero la función para
que pida los datos de un alumno y retorne una estructura tipo Alumno que
contenga todos los datos ingresados, su prototipo podría ser:
Alumno Ingres arAlumno ( void ) ;

Para su implementación utilizaremos una variable Alumno, a la cual le “llen-


amos” cada uno de los campos con los datos digitados por el usuario y luego
la retornamos.
Alumno Ingres arAlumno ( void )
{
Alumno A ;
int i ;
float Nota ;
printf ( " Nombre : " ) ; gets ( A . nombre ) ; // leer el nombre
printf ( " C ó digo : " ) ; scanf ( " % ld " ,& A . Codigo ) ;
printf ( " Cantidad de notas " ) ; scanf ( " % d " ,& A . nNotas ) ;
A . Definitiva = 0;
for ( i =0; i < A . nNotas ; i ++)
{
printf ( " Nota (% d ) = " ,i +1) ;
0.8. EJERCICIOS RESUELTOS 15

scanf ( " % f " ,& Nota ) ;


A . Notas [ i ]= Nota ;
A . Definitiva += Nota ;
}
A . Definitiva /= nNotas ;
return A ;
}

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 ) ;

Y su implementación podría ser:


void MostrarAlumno ( Alumno A )
{
int i ;
printf ( " Nombre % s \ n " ,A . nombre ) ;
printf ( " C ó digo % ld \ n " , A . Codigo ) ;
printf ( " Notas : " ) ;
for ( i =0; i < A . nNotas ; i ++)
{
printf ( " %3.1 f \ t " ,A . Notas [ i ]) ;
}
printf ( " \ nNota Definitiva =%3.1 f \ n " , A . Definitiva ) ;
}

Pero si en el programa se quiere es manipular un grupo de notas que rep-


resenta las notas de todos los alumnos de un curso, pues debemos utilizar
un arreglo que contenga todas las notas de cada uno de los alumnos. Una
forma simple de manejar el programa sería utilizar un arreglo global de tipo
Alumno (y otra que nos indicara la cantidad de alumnos), para que en cada
función que tuviera que acceder al grupo de notas se pudieran conocer di-
chos datos, de esta manera nos ahorrariamos el tener que pasar el arreglo
como parámetro a dichas funciones. Y la otra forma sería utilizar un arreglo
declarado en la función main y declarar todas las funciones que tengan que
acceder al arreglo de alumnos con dos parámetros: uno sería el arreglo de
tipo Alumno y otro la cantidad de alumnos. Entonces la forma que tendría
el programa para las dos alternativas son:
Declarando el arreglo global:
16

Alumno Alumnos [40];


int nAl ;
// d e c l a r a c i ó n de las f u n c i o n e s
void MostrarAlumno ( Alumno A ) ;
Alumno Ingres arAlumno ( void ) ;
void ListaAlumnos ( void ) ;
void Ordenar ( void ) ;
...
void main ()
{
...
...
ListaAlumnos () ;
...
...
Ordenar () ;
}

void ListaAlumnos ( void )


{

...
for ( i =0; i < nAl ; i ++)
MostrarAlumno ( Alumnos [ i ]) ;
}
...

Y declarando el arreglo como una variable local:


// d e c l a r a c i ó n de las f u n c i o n e s
void MostrarAlumno ( Alumno A ) ;
Alumno Ingres arAlumno ( void ) ;
void ListaAlumnos ( Alumno LA [] , int n ) ;
void Ordenar ( Alumno LA [] , int n ) ;
...
void main ()
{
Alumno Alumnos [40];
int nAl ;
...
...
ListaAlumnos ( Alumnos , nAl ) ;
...
...
Ordenar ( Alumnos , nAl ) ;
}

void ListaAlumnos ( Alumno LA [] , int n ) ;


{

...
for ( i =0; i < n ; i ++)
{
MostrarAlumno ( LA [ i ]) ;
...
}
}
...
0.8. EJERCICIOS RESUELTOS 17

No se muestra el programa completo, solo la forma que tendría en cada caso.


En los dos listados aparecen resaltadas las partes que se implementarían de
forma diferente en cada una de las alternativas planteadas.
La primera versión tiene la ventaja de que al declarar las funciones sin
parámetros su invocación resulta más simple y más rápida, sin embargo tiene
la desventaja de que las funciones implementadas son dependientes de dicho
arreglo; así por ejemplo, si luego quisiéramos adaptar el programa ya no para
manejar un grupo sino varios grupos, pues tocaría volver a codificar dichas
funciones para que accedieran de forma apropiada a cada grupo (la vari-
able Alumnos ya no serviría pues solo contiene la información de un grupo).
La segunda versión tiene la desventaja de que al declarar las funciones con
parámetros su invocación resulta más compleja y más lenta, pero tiene la
ventaja de que las funciones implementadas son independientes de arreglos
globales, entonces cuando quisiéramos adaptar el programa ya no para mane-
jar un grupo sino varios grupos, pues simplemente es necesario modificar el
código en la función principal (redeclarando los arreglos necesarios y mod-
ificando los parámetros que se pasan a las funciones mencionadas). Este
planteamiento que hacemos en éste momento sirve de ejemplo de cómo pro-
ceder cuando estamos escribiendo un programa: si las funciones que estamos
implementando son demasiado específicas y a lo mejor nunca más tengamos
que realizar una aplicación similar, pues el camino a seguir sería trabajar con
variables globales y algunas de las funciones se pueden trabajar sin parámet-
ros pues los valores se toman de dichas variables globales. Pero si pensamos
que lo más seguro es que algunas de las funciones las utilicemos en otras
aplicaciones, pues nos adelantamos a la situación y las escribimos lo más
independiente posible de la aplicación en particular, de tal suerte que no sea
necesario cambiarlas en las futuras implementaciones (obviamente, en éste
caso la dependencia surge de usar la variable global Alumnos). Más adelante
veremos que inclusive se puede crear una librería con funciones propias de
tal suerte que ni siquiera es necesario recompilarlas en cada aplicación donde
se utilicen, sino que solo se compilan una vez y luego solo se le debe indicar
al compilador que use nuestra librería de funciones cada que las queramos
invocar.
Si elegimos el segundo método de implementación que hemos descrito, debe-
mos implementar la función:
void ListaAlumnos ( Alumno LA [ ] , int n ) ;

Y si bien ya mostramos parte del código, en la implementación real debe-


mos tener en cuenta algunas consideraciones prácticas: Como ya se tiene
una función que muestra los datos de un alumno (MostrarAlumno), pues
simplemente se hace un ciclo que invoque a dicha función para cada indice
del arreglo LA (esto es justo lo que vemos en la parte del código mostrado).
Pero si se tiene en cuenta que el tamaño de dicho arreglo puede ser tal que
los datos de todos los alumnos no se puedan visualizar simultáneamente en
la pantalla, pues al ejecutar la función el usuario solo podrá ver los últimos
18

datos. Para solucionar éste problema pueden surgir varias alternativas de


solución.
Se me ocurren tres:
- Una pausa por cada estudiante. - Una pausa por cada grupo de estudiantes.
- Visualizar en forma tabular de tal suerte que “quepan” mas alumnos en la
pantalla.
- La primera sería la más simple y corresponde con hacer una pausa después
de visualizar los datos de cada estudiante, dicha pausa puede ser con un
retardo de tiempo (con la función delay), o esperando una tecla (con la
función getch), está implementación puede ser:
void ListaAlumnos ( Alumno LA [ ] , int n )
{
int i ;
for ( i =0; i < n ; i ++)
{
MostrarAlumno ( LA [ i ]) ;
getch () ; // pausa para que el usuario vea cada alumno ,
// tambien podr í a u t i l i z a r s e una pausa de
tiempo de 200 ms
// con delay (200)
}
}

- La segunda solución es similar a la primera, pero puede ser más cómodo


para el usuario permitirle ver varios estudiantes al tiempo (justo los que
quepan en un “pantallazo”). Entonces en ésta versión la pausa que decidamos
utilizar (getch o delay) se debe ejecutar solo para cuando el índice tome
ciertos valores (éstos valores se relacionan con cantidad de estudiantes a
ver de una). Entonces revisando la función que hicimos para mostrar un
registro vemos que la impresión de los datos de un estudiante consume justo
4 renglones, y en la pantalla hay en total 25 renglones, entonces caben 6
registros. Entonces la pausa hay que hacerla cuando i vale 5, 11, 17, 23, etc.
Podría hacerse un condicional múltiple con la condición (i==5)||(i==11). . . .
Pero es más elegante encontrar una forma de codificar todas estas condiciones
en una sola. Sabemos que se debe cumplir cada 6, pero no es exactamente
en posiciones múltiplos de 6 (si fuera así simplemente se pondría la condición
(i%6==0)), pero entonces se puede “cuadrar” de dos formas el condicional,
una cambiando el valor al cual se le aplica el módulo o, la otra, cambiando
el valor con el que se compara, la primera sería ((i+1)%6==0) y la segunda
((i%6)==5). Entonces la implementación de esta versión de la función sería:
void ListaAlumnos ( Alumno LA [ ] , int n )
{
int i ;
clrscr () ;
for ( i =0; i < n ; i ++)
{
MostrarAlumno ( LA [ i ]) ;
if (( i +1) %6==0) // o tambi é n if (( i %6) ==0)
0.8. EJERCICIOS RESUELTOS 19

{
printf ( " Pulse una tecla para continuar " ) ;
getch () ; clrscr () ;
}
}
}

Si se prueba esta función veremos que funciona tal como lo describimos


cuando se tiene una cantidad de alumnos múltiplo de 6. Pero si no es así,
para los últimos estudiantes no se hace la pausa en la visualización. Así,
si por ejemplo se tienen 20 alumnos, se mostrarían 3 pantallazos donde se
hace la pausa, y los últimos dos se mostrarán pero no se hace la pausa para
su visualización (y probablemente sean borrados por el programa principal).
Podría el lector inventarse una forma de corregir este error?.
- La tercera opción podría realizarse si en vez de imprimir en varios renglones
la información de un alumno, se imprime todo en un solo renglón, de esta
forma en vez de mostrar 7 estudiantes de una sola vez en la pantalla podrían
mostrarse 23 (asumiendo que dejamos un renglón para poner el título de
cada campo y uno para el mensaje pulse una tecla, si es el caso). Pero esta
solución implica que la información de un alumno que antes desplegabamos en
varios renglones ahora se muestre en uno solo, y para solucionarlo tendremos
dos opciones: o cambiamos la función MostrarAlumno que ya hicimos o
escribimos una nueva que tenga el comportamiento indicado. Para la primera
solución también se tienen dos caminos a seguir: la más simple es cambiar la
función para que opere como se necesita en éste momento o si queremos que
la función trabaje de las dos formas (desplegando la información en cuatro
renglones en algunas ocasiones o en uno solo en otras ocasiones) entonces a
la función MostrarAlumno le agregamos un parámetro que indique la forma
en que queremos que se visualice la información ya sea con una bandera o
ya sea que le pasamos las cadenas (en un arreglo) que aparecen en printf
como parámetros. Se deja como ejercicio al lector la implementación de esta
solución.
Retomando el tema de las funciones del programa, estábamos tratando de
implementar las funciones que operaban sobre el conjunto de notas, y de las
que propusimos falta implementar la función:
void OrdenarNotas ( Alumno Al [] , int nAl ) ;

Tal como aparece en la declaración, a la función le llega el arreglo de alumnos


y la cantidad de alumnos y ella debe retornar en el mismo arreglo los Alumnos
debidamente ordenados. Ahora, cuando se ordena un arreglo simple (que no
es una estructura) con el enunciado anterior es suficiente y solo es cuestión de
decidir si se ordenan ascendentemente o descendentemente para empezar a
plantear la solución; pero si el arreglo a ordenar es de una estructura, además
debemos indicar que campo(s) utilizamos como criterio para la ordenación, y
en este caso podrían plantearse varias ordenaciones: por código, por nombre
(alfabéticamente), por Nota definitiva o algo más inusual por una nota en
20

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

supongamos que se tiene un arreglo de 5 enteros que inicialmente se encuentra


desordenado y veamos como se ordena ascendentemente utilizando el método
de la burbuja, así:
Indice Valor
0 5
1 9
2 4
3 2
4 7
El primer paso es determinar donde se encuentra el menor de todos los datos,
o sea que encontramos el valor 2 que se encuentra en el índice 3. Entonces
intercambiamos éste valor con el valor ubicado en la posición 0. El arreglo
queda:
Indice Valor
0 2
1 9
2 4
3 5
4 7
Ya no volvemos a tocar a la posición 0 y vemos el arreglo como si empezara
en la posición 1 y volvemos a repetir la búsqueda del menor. En este paso
observamos que el menor es 4 y se ubica en la posición 2, pues intercambiamos
éste con el valor 9 y entonces el nuevo arreglo queda:
Indice Valor
0 2
1 4
2 9
3 5
4 7
La aplicación del algoritmo indicado para las siguientes posiciones modificará
el arreglo como aparece en las siguientes tablas:
Indice Valor
0 2
1 4
2 5
3 9
4 7
Indice Valor
0 2
1 4
2 5
3 7
4 9
22

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 ;

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


{
// a d a p t a c i ó n del c ó digo de buscar posici ó n del menor
menor = Datos [ i ]; pMenor = i ;
for ( j =0; j < tam ; j ++)
{
if ( Datos [ j ] < menor )
{
menor = Datos [ j ];
pMenor = j ;
}
}
// 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 ;
}

Si bien esta implementación se hizo para ordenar ascendentemente el arreglo


de enteros del ejemplo, si revisamos el código, justo aquellas líneas que apare-
cen resaltadas serán las que tenemos que modificar para una ordenación en
otro sentido, o cuando el arreglo sea de un tipo diferente a entero. Por ejem-
plo, para ordenar en sentido descendente simplemente se cambia la condición
if(Datos[j]<menor) por if(Datos[j]>menor) (aunque por claridad en el código
sería mejor denominar la variable mayor y no menor). Si no me cree escriba
el código y pruebe los cambios indicados. Ahora si el arreglo es de una estruc-
tura como es el caso del problema que estamos tratando de resolver, pues en
el condicional compararemos los elementos por el campo por el cual se desee
ordenar y, claro está, la variable Menor debe ser también de tipo alumno. Así
por ejemplo para ordenar por promedio en forma descendente se escribiría:
int i ,j , pMenor ;
Alumno menor ;
0.8. EJERCICIOS RESUELTOS 23

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


{
// a d a p t a c i ó n del c ó digo de buscar posici ó n del menor
menor = Datos [ i ]; pMenor = i ;
for ( j =0; j < tam ; j ++)
{
if ( Datos [ j ]. promedio > menor . promedio )
{
menor = Datos [ j ];
pMenor = j ;
}
}
// 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 ;
}

Trate usted de codificar la ordenación por código en forma ascendente. Con-


tando con la codificación que usted haga, solo falta la codificación de la orde-
nación por el campo nombre, que usualmente se utiliza en forma ascendente.
Lo único raro para codificar esta ordenación es la forma como determinamos
cuando un elemento es menor a otro, afortunadamente existe una función
que permite comparara alfabeticamente dos cadenas y es la función strcmp
declarada en el archivo de cabecera string.h. A esta función se le pasan dos
cadenas y la función retorna un número entero con la siguiente convención:
strcmp(char cad1[ ] ,char cad2[ ]) retorna =0 si cad1 == cad2
<0 si cad1 < cad2
>0 si cad1 > cad2
Entonces el condicional se puede escribir como:
if ( strcmp ( Datos [ j ]. nombre , menor . Nombre ) <0) ...

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 ;
}
}

La segunda opción seria una variación de la primera y consistiria en no es-


cribir el código en la misma función sino escribir una función por cada tipo
de ordenación e invocarla en cada caso:
void OrdenarNotas ( Alumno Al [ ] , int nAl , int op )
{

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
}
}

void O r d e n a r P o r P r o m e d i o ( Alumno Al [ ] , int nAl )


{
int i ,j , pMenor ;
Alumno menor ;
for ( i =0; i < nAl ; i ++)
{
menor = Alumno [ i ]; iMenor = i ;
for ( j = i ;j < nAl ; j ++)
{
if ( Alumno [ j ]. promedio > menor . promedio ) ...
}
...
...
0.8. EJERCICIOS RESUELTOS 25

void O r d e n a r P o r C o di g o ( Alumno Al [ ] , int nAl )


{
int i ,j , pMenor ;
Alumno menor ;
for ( i =0; i < nAl ; i ++)
{
menor = Alumno [ i ]; iMenor = i ;
for ( j = i ;j < nAl ; j ++)
{
...
...
}

void O r d e n a r P o r N o mb r e ( Alumnno Al [ ] , int nAl )


{
...
...
...
}

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).

0.9 EJERCICIOS PROPUESTOS


1. De acuerdo al uso que se le da a la variable ListaDatos (de tipo Datos) en
el programa, declarar la estructura Datos.
main ()
{
struct Datos ListaDatos [20];
int i ;

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


{ ...
gets ( ListaDatos [ i ]. nombre ) ;
...
scanf ( " % ld " , ListaDatos [ i ]. codigo ) ;
...
for ( j =0; j < ListaDatos [ i ]. n_notas ; j ++)
{
...
scanf ( " % f " , ListaDatos [ i ]. Notas [ j ]) ;
ListaDatos [ i ]. Prom += ListaDatos [ i ]. Notas [ j ];
}
...
ListaDatos [ i ]. Prom /= j ;
..
}
}

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

Por ejemplo si se escribe el siguiente codigo:


char Unidades [ ][20]= { " metros " ," kilometros " ," Megametros " ,
" milimetros " ," micrometros " };

q = Escalar ( 0.0035) ; // q v a r i a b l e del tipo de la


estructura

printf ( " Valor =%5.2 f % s " , q . val , Unidades [ q .


unidades ] ) ;
// En p a n t a l l a aparece Valor = 3.50 m i l i m e t r o s .

v = Escalar ( -900035.0) ; // v v a r i a b l e del tipo de la


estructura

printf ( " Valor =%5.2 f % s " , v . val , Unidades [ v .


unidades ] ) ;
// En p a n t a l l a aparece Valor = -90.00 k i l o m e t r o s

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 ;

P . fuerza [0] = 4; P . angulo [0] = 0;


P . fuerza [1] = 3; P . angulo [1] = PI /2;
P . nFuerzas = 2;

VerResultante ( P ) ;

// En p a n t a l l a aparece : fuerza r e s u l t a n t e sobre la


p a r t i c u l a F = 5.0 angulo = 0.785 ( PI /4) ???

4. Dada la estructura Formula1(declararla) , escribir una función a la cual


se le pasa un arreglo con los competidores ordenados por posición en la
carrera, la cantidad de competidores y el nombre de un competidor, y en
la función se imprima el nombre, la posicion, la cantidad de entradas a
pits, la velocidad promedio de ultima vuelta, y las velocidades del anterior
y del siguiente competidor. Ejemplo:
Formula1 Monza [ ] ={ { " Barichelo " , 1 , 268.0} ,
{ " M . Shumaher " , 2 , 260.0} ,
{ " Montoya " , 2 , 265.0} ,
{ " Irvine " , 2 , 250.0} };

M o s t r a r C o m p e t i d o r ( Monza ,4 , " Montoya " ) ;

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;

otraCarta = MasCartas ( PC ) ; // al r e t o r n a r o t r a C a r t a =1 , (4+7=11)

6. Definir una estructura, que contenga la cantidad de billetes de cada de-


nominación (20 mil,10 mil y 5 mil) que contiene un cajero en un momento
dado, y escribir una función a la cual se le pasa dicha estructura y una
cantidad de dinero a descontar y retorne una estructura con la cantidad
descontada (tener en cuenta que se pueden agotar los billetes de cualquiera
de las denominaciones). Ejemplo:
// s u p o n i e n d o que la e s t r u c t u r a se llama b i l l e t e s
Billetes Cajero ;
.
.
.
// por ejemplo si Cajero tiene 10 b i l l e t e s de 20.000 , 5 de 10
mil y 20 b i l l e t e s de 5 mil
Cajero = Descontar ( Cajero ,195000) ;
// al r e g r e s a r Cajero tiene 1 billete de 20.000 , 4 b i l l e t e s de
10 mil y 19 b i l l e t e s de 5 mil .

También podría gustarte