Está en la página 1de 155

Programacin en C

Fundamentos de programacin
UNED

ndice
VALORES Y TIPOS ........................................................................................... 5
Valores constantes ...................................................................................... 5
Valores numricos enteros ................................................................................... 5
Valor numricos reales ........................................................................................ 5
Caracteres............................................................................................................ 6
Cadena de caracteres (strings) ............................................................................ 6

Tipos predefinidos ...................................................................................... 7


Tipo entero (int) ................................................................................................... 7
Tipo real (float).................................................................................................... 8
Tipo carcter (char)............................................................................................. 8

Operaciones de escritura: Procedimiento printf ........................................ 10


Estructura de un programa ........................................................................ 12
CONSTANTES Y VARIABLES.......................................................................... 14
Identificadores .......................................................................................... 14
Vocabulario C ......................................................................................... 14
Constantes ................................................................................................. 15
Variables ................................................................................................... 16
Sentencia de asignacin ............................................................................ 17
Operaciones de lectura simple. El procedimiento scanf ........................... 18
Estructura de un programa con declaraciones........................................... 20
METODOLOGA DE DESARROLLO DE PROGRAMAS (I) ................................ 22
La programacin como resolucin de problemas ..................................... 22
Desarrollo por refinamientos sucesivos .................................................... 23
Aspectos de estilo ..................................................................................... 26
Encolumnado ..................................................................................................... 26
Comentarios ....................................................................................................... 26
Eleccin de nombres .......................................................................................... 27
Uso de letras maysculas y minsculas ............................................................. 27
Constantes con nombre ...................................................................................... 28

ESTRUCTURAS BSICAS DE LA PROGRAMACIN IMPERATIVA ................... 29


Programacin estructurada ....................................................................... 29
Diagramas de flujo ............................................................................................ 29
Secuencia ........................................................................................................... 31
Seleccin ............................................................................................................ 31
Iteracin............................................................................................................. 32
Estructura anidadas ........................................................................................... 32

Expresiones condicionales ........................................................................ 32


Estructuras bsicas en C ......................................................................... 35
1

Secuencia ........................................................................................................... 35
Sentencia IF ....................................................................................................... 36
Sentencia WHILE ............................................................................................... 38
Sentencia FOR ................................................................................................... 39

METODOLOGA DE DESARROLLO DE PROGRAMAS (II) ............................... 42


Desarrollo con esquemas de seleccin e iteracin .................................... 42
Esquema de seleccin ........................................................................................ 42
Esquema de iteracin......................................................................................... 43

Verificacin de programas ........................................................................ 45


Razonamientos sobre sentencias de asignacin ................................................. 46
Razonamiento sobre el esquema de seleccin .................................................... 47
Razonamiento sobre el esquema de interaccin: invariante, terminacin ......... 49

Eficiencia de programas. Complejidad ..................................................... 51


FUNCIONES Y PROCEDIMIENTOS ................................................................. 53
Concepto de subprograma ........................................................................ 53
Funciones .................................................................................................. 54
Funciones predefinidas y funciones estndar .................................................... 56

Procedimientos ......................................................................................... 57
Paso de argumentos .................................................................................. 59
Paso de argumentos por valor ........................................................................... 59
Paso de argumentos por referencia ................................................................... 60

Ejemplo de un programa ........................................................................... 61


METODOLOGA DE DESARROLLO DE PROGRAMAS (III) ............................. 65
Operaciones abstractas.............................................................................. 65
Funciones. Argumentos...................................................................................... 67
Acciones abstractas. Procedimientos ................................................................. 67

Desarrollo usando abstracciones ............................................................... 68


Desarrollo descendente ..................................................................................... 69
Desarrollo ascendente ....................................................................................... 69

Programas robustos ................................................................................... 70


DEFINICIN DE TIPOS................................................................................... 73
Tipos definidos ......................................................................................... 73
Tipo enumerado ........................................................................................ 74
Tipo predefinido bool ............................................................................... 76
Tipos Estructurados .................................................................................. 77
Tipo vector ................................................................................................ 77
Declaracin de vectores .................................................................................... 78
Inicializacin de un vector ................................................................................. 79
Operaciones con elementos de vectores............................................................. 80
Operaciones globales con vectores .................................................................... 80
Paso de argumento de tipo vector ...................................................................... 81

Vector de caracteres: Cadena (string) ....................................................... 82


Tipo tupla .................................................................................................. 84
Tipo registro (struct) ................................................................................. 84
APLICACIN A ESTRUCTURAS DE CONTROL ............................................... 88
Estructuras complementarias de iteracin................................................. 88
Repeticin: sentencia DO .................................................................................. 88
Sentencia CONTINUE ....................................................................................... 89

Estructuras complementarias de seleccin ................................................ 90


Sentencia SWITCH ............................................................................................ 90

Equivalencia entre estructuras .................................................................. 91


Seleccin por casos ............................................................................................ 92
Bucle con contador ............................................................................................ 93
Repeticin .......................................................................................................... 93

ESTRUCTURA DE DATOS ............................................................................... 94


Argumentos de tipo vector abierto ............................................................ 94
Formaciones anidadas. Matrices ............................................................... 95
El tipo unin ............................................................................................. 98
Esquemas de datos y esquemas de acciones ........................................... 100
Estructuras combinadas .......................................................................... 100
Formas de combinacin ................................................................................... 101
Tablas .............................................................................................................. 101

ESQUEMAS TPICOS DE OPERACIN CON FORMACIONES .......................... 102


Esquema de recorrido ............................................................................. 102
Recorrido de vectores ...................................................................................... 102
Recorrido de matrices ...................................................................................... 103
Recorrido no lineal .......................................................................................... 104

Bsqueda secuencial ............................................................................... 105


Insercin ................................................................................................. 106
Ordenacin por insercin directa ............................................................ 108
Bsqueda por dicotoma ......................................................................... 110
Simplificacin de las condiciones de contorno ....................................... 111
Tcnica centinela ............................................................................................. 111
Matrices orladas .............................................................................................. 113

PUNTEROS Y VARIABLES DINMICAS ........................................................ 117


Estructuras de datos no acotadas............................................................. 117
La estructura secuencia ........................................................................... 117
Variables dinmicas ................................................................................ 119
Punteros ........................................................................................................... 119
Uso de variables dinmicas ............................................................................. 120

Realizacin de secuencias mediante punteros ........................................ 122


3

Operaciones con secuencias enlazadas ........................................................... 124

Punteros y paso de argumentos ............................................................... 127


Paso de punteros como argumentos ................................................................ 127
Paso de argumentos mediante punteros........................................................... 128

Punteros y vectores en C y C++.............................................................. 131


Nombres de vectores como punteros................................................................ 131
Paso de vectores como punteros ...................................................................... 131
Matrices y vectores de punteros ....................................................................... 131

TIPOS ABSTRACTOS DE DATOS ................................................................... 132


Concepto de tipo abstracto de datos (TAD) ............................................ 132
Realizacin de tipos abstractos en C..................................................... 133
Definicin de tipos abstractos como tipos registro (struct) ............................. 133
Ocultacin........................................................................................................ 136

Metodologa basada en abstracciones ..................................................... 137


MDULOS ................................................................................................... 139
Concepto de modulo ............................................................................... 139
Compilacin separada ..................................................................................... 140
Descomposicin modular................................................................................. 141

Mdulos en C ....................................................................................... 142


Proceso de compilacin simple........................................................................ 143
Mdulo principal ............................................................................................. 143
Mdulos no principales.................................................................................... 143
Uso de mdulos................................................................................................ 145
Declaracin y definicin de elementos pblicos .............................................. 147
Conflicto de nombres en el mbito global........................................................ 148
Unidades de compilacin en C ...................................................................... 149
Compilacin de programas modulares. Proyectos .......................................... 150

Desarrollo modular basado en abstracciones .......................................... 151


Implementacin de abstracciones como mdulos ............................................ 151
Dependencias entre ficheros. Directivas ......................................................... 151
Datos encapsulados ......................................................................................... 152
Reutilizacin de mdulos ................................................................................. 153

VALORES Y TIPOS
VALORES CONSTANTES
Valores numricos enteros
Los valores enteros representan un nmero exacto de unidades y no pueden
tener parte fraccionada. Se escribe mediante una secuencia de uno o ms
dgitos del 0 al 9 sin separadores de ninguna clase entre ellos y precedidos de
los smbolos ms (+) o menos (-). Ejemplos:
2
+25
0
-2564832

Valor numricos reales


Representan cualquier cantidad, incluyendo fracciones de la unidad. Se
pueden expresar de dos maneras distintas:
Notacin decimal: el valor real escribe con una parte entera terminada
siempre por un punto (.) y seguida opcionalmente por una secuencia de
dgitos que constituyen la parte fraccionada decimal. Ejemplos:
5.25
-0.56
+2365.23
1235.0205
Notacin cientfica: se escribe con una mantisa, que es un nmero real en
notacin decimal, seguida de un factor de escala que se escribe como una E
seguida del exponente de una potencia de 10 por el que se multiplica la
mantisa. Ejemplo:
6.023E23 equivale s 6.023x1023

Por lo tanto un valor real, al contrario que el entero tiene muy diversas
representaciones vlidas. Ejemplo:
4.623

46.23E-1

4.623E0 0.4623E1

Caracteres
Dentro de un texto en C el valor de un carcter concreto se escribe poniendo
dicho carcter entre apstrofos (). Ejemplos:
a

El ltimo carcter es el espacio en blanco.


El juego de caracteres (charset), depende de la mquina que se est
utilizando. Existen unos caracteres de control que no tienen smbolo grfico,
se representan por una secuencia de escape con la siguiente notacin:
\n
\r
\t
\
\\
\f

Salto al comienzo de una nueva lnea de escritura


Retorno al comienzo de una lnea de escritura
Tabulacin
Apstrofo
Barra inclinada
Salto a una nueva pgina o borrado de pantalla

Cadena de caracteres (strings)


Una cadena de caracteres (strings) se escribe como una secuencia de
caracteres incluidos entre comillas (). Ejemplos:
Este texto es una cadena de caracteres
Conteste \Si\ o \No\

El ltimo ejemplo representa la cadena vaca.

TIPOS PREDEFINIDOS
Tipo de datos define:
a) Una coleccin de valores posibles.
b) Las operaciones significativas entre ellos.

Tipo entero (int)


a) Coleccin de valores posibles:
Depende de la plataforma, lo rangos ms comunes son:
Tamao de
la palabra
16 bits
32 bits
64 bits

Rango de valores enteros


-32.768 0 32.767
-2.147.483.648 0 2.147.483.648
-9.223.372.036.854.775.808 0 9.223.372.036.854.775.808

b) Operaciones:
+
*
/
%
+
-

Suma de enteros
Resta de enteros
Multiplicacin de enteros
Divisin de enteros
Resto de la divisin
Identidad de un entero
Cambio de signo de un entero

a+b
a-b
a*b
a/b
a%b
+a
-a

El operador ( / )hace la divisin entre nmeros enteros, por lo que el


resultado es el cociente de la divisin. El resto de la divisin la da el operador
(%), as por ejemplo:
15 / 4 = 3

15 % 4 = 3

Se cumple:
a = b * (a / b) + (a % b)
Es decir:
Dividendo = Divisor x Cociente + Resto
7

Tipo real (float)


a) Coleccin de valores posibles:
Tamao de la palabra y
precisin

Rango de valores reales


-3.4E+38 -1.2E-38
0
+1.2E+38 +3.4E+38
-1.7E+308 -2.3E-308
0
+2.3E+308 +1.7E+308

32 bits; 6 cifras decimales


64 bits; 15 cifras decimales

b) Operaciones:
+
*
/
+
-

Suma de reales
Resta de reales
Multiplicacin de reales
Divisin de reales
Identidad de un real
Cambio de signo de un real

a+b
a-b
a*b
a/b
+a
-a

Para valores reales no siempre se cumple:


a = b * (a / b)

Tipo carcter (char)


a) Coleccin de valores posibles:
Cada carcter no se representa por un dibujo (glifo) sino por un cdigo
numrico que lo representa, la coleccin completa de caracteres se establece
por medio de una tabla (charset) que asocia cada carcter el cdigo numrico
(codepoint) que le corresponde, ver (Fig. 1).
b) Operaciones:
Se puede representar cualquier carcter con la notacin char (x) donde x es el
cdigo del carcter, as si miramos la Fig. 1, vemos que:
8

char(10)
char(13)
char(65)

Salto al comienzo de una nueva lnea. Posicin 10


Retorno al comienzo de la misma lnea. Posicin 13
Letra A mayscula. Posicin 65

Fig. 1 Tabla de cdigos ASCII de 8 bits

En sentido inverso, el cdigo numrico de un determinado carcter c se


expresa como int(c), Por ejemplo:
int( A )
int( Z )
int( { )

65
90
123

Por lo tanto se cumple que:


char(int(c)) = c
int(char(x)) = x

Algunas de las funciones del modulo de librera ctype (cabecera <ctype.h>),


asociada a los caracteres:
isalpha( c )
isascii( c )
isblank( c )
iscntrl( c )
isdigit( c )
islower( c )
isspace( c )
isupper( c )
tolower( c )
toupper( c )

Indica si c es una letra


Indica si c es un carcter ASCII
Indica si c es un carcter de espacio o tabulacin
Indica si c es un carcter de control
Indica si c es un digito decimal (0-9)
Indica si c es un letra minscula
Indica si c es un espacio en blanco o salto de lnea o pgina
Indica si c es una letra mayscula
Devuelve la minscula correspondiente a c
Devuelve la mayscula correspondiente a c

OPERACIONES DE ESCRITURA: PROCEDIMIENTO PRINTF


Las acciones que envan resultados al exterior se llaman, en general
operaciones de escritura, con independencia que sea una impresora, pantalla
o grabacin en disco. El mdulo stdio (cabecera <stdio.h>) emplea:
printf( cadena-de-caracteres );
El procedimiento printf escribe en la pantalla del ordenador la cadena de
caracteres. Por ejemplo:
Operacin de escritura
printf( Mira este texto );
printf( Cmo ests? );

Resultado
Mira este texto
Cmo ests?

Si se quiere escribir una representacin como texto de una serie de valores de


cualquier tipo, existe la siguiente forma general de la orden printf
printf( cadena-con-formatos, valor1, valor2, valorN );
La cadena con formatos, deber incluir en su interior una especificacin del
formato por cada valor que se quiera ingresar. Esto se realiza mediante %x,
donde x es una letra de cdigo que indica el tipo de formato a aplicar.
Algunos de los ms habituales son:

10

Cdigo
d
f
e
g
c
s

Nemotcnico
(ingls)
decimal
fixed point
exponential
general
character
string

Tipo de valor
entero
real
real con notacin exponencial
real con/sin notacin exponencial
un carcter
una cadena de caracteres

As por ejemplo:
Operacin de escritura
printf( %d , 120/12 );
printf( Datos:%d#%d , 23*67 , -25 );
printf( Datos: %d # % d , 23*67 , -25 );

Resultado
10
Datos:1541#-25
Datos:1541#-25

() representa el espacio en blanco

Se pueden poner los espacios en blaco de manera explicita indicando cuantos


caracteres debe tener cada valor, esto se hace interponiendo el numero de
caracteres entre el signo % y el cdigo del formato. Por ejemplo:
Operacin de escritura
printf( %6d , 120/12 );
printf( Datos:%7d#%5d , 23*6 , -25 );
printf( %3d , 1000*67 );

Resultado
10
Datos:138#-25
670000

() representa el espacio en blanco

Si el nmero de caracteres indicado es insuficiente para expresar el valor se


emplean todos los caracteres, como ocurre en el ltimo ejemplo. Cuando se
utilizan un formato f, e, g se puede indicar el nmero de cifras decimales
que se deben escribir despus del punto decimal. Por ejemplo
Operacin de escritura
printf( %10.3f , 1.2 );
printf( %10.4e , 23.1*67.4 );
printf( %15.3g , -50.6E-6 );

Resultado
1.200
0.1557E+04
-50.600E-6

() representa el espacio en blanco

Si no se dice lo contrario los resultados obtenidos mediante secuencias de


escritura van apareciendo en el dispositivo de salida uno tras otro en la
misma lnea de texto. Para escribir los resultados en varias lneas habr que
11

colocar dentro de la cadena los caracteres especiales mediante secuencia de


escape.
Operacin de escritura
printf( rea = );
printf( %10.4f , 24.45 );
printf( Mi ciudad es vila );
printf( Descuento: );
printf( %5.2f , 12.5 );
printf( %c , % );
printf( rea = );
printf( %10.4f\n , 24.45 );
printf( Mi ciudad es vila\n );
printf( Descuento: );
printf( %5.2d , 12.5 );
printf( %c\n , % );

Resultado

rea=24.4500Mi ciudad es vilaDescuento:12.50%

rea=24.4500
Mi ciudad es vila
Descuento:12.50%

() representa el espacio en blanco

ESTRUCTURA DE UN PROGRAMA

Fig. 2 Estructura de un programa en C

12

En la Fig. 2 se observa la estructura de un programa. Este comienza con el


smbolo # donde se indican las directivas para el compilador. La usada en
C es #include, a continuacin se indica el mdulo de librera stdio
(cabecera <stdio.h>). A continuacin se coloca int main() y el bloque del
programa entre los smbolos { de comienzo y } final, que contiene las
sentencias ejecutables. Cada sentencia del programa termina con un punto y
coma (;). En el programa se pueden incluir comentarios dentro de los
smbolos /* y */
Ejemplo de programa: calcular el rea y el volumen de un cilindro a parir de
su radio R y su altura A:
rea = 2R2 + 2RA = 2R(R+A)
volumen = R2 A
Programa
/** Programa: Cilindro */
/* Clculo del rea y el volumen de un cilindro */
#include <stdio.h>
int main() {
printf( %s\n , Dado un cilindro de dimensiones: );
printf( %s\n , radio = 1,5 y altura = 5,6 );
printf( %s , su rea es igual a: );
printf( %g\n , 2.0*3.141592*1.5*(1.5+5.6) );
printf( %s , y su volumen es igual a: );
printf( %20.8f\n , 3.141592*1.5*1.5*5.6 );
}
Resultado
Dado un cilindro de dimensiones:
radio = 1,5 y altura = 5,6
su rea es igual a: 66.9159
y su volumen es igual a:
39.58405920

13

CONSTANTES Y VARIABLES
IDENTIFICADORES
En programacin se llaman identificadores a los nombres usados para
identificar cada elemento de un programa. En C los identificadores son una
palabra formada por caracteres alfabticos o numricos seguidos, sin espacio
en blanco ni signos de puntuacin intercalados y deben comenzar por una
letra. Pueden usarse las 52 letras maysculas y minsculas del alfabeto
ingls, el guin bajo (_), y los dgitos decimales del 0 al 9. Adems el
lenguaje distingue las letras maysculas y minsculas. Ejemplos: Indice
diaDelMes Nombre_Apellidos j5 Eje_3.
El Manual de Estilo recomiendan las siguientes reglas para los
identificadores:

Por defecto escribir todo en minsculas.


Escribir en maysculas o empezando por maysculas los nombres
de las constantes que sean globales.
Usar guiones o maysculas intermedias para los nombres
compuestos.

VOCABULARIO C
and
bool
compl
delete
enum
friend
mutable
or
reinterpret
static
this
typename
volatile

and_eq
break
const
do
explicit
goto
namespace
or_eq
t_cast
static_cast
throw
union
wchar_t

asm
case
const_cast
double
extern
if
new
private
return
true
unsigned
while

auto
catch
dynamic_cast
false
inline
not
protected
short
struct
try
using
xor

bitand
char
continue
float
int
not_eq
public
signed
switch
typedef
virtual
xor_eq

Fig. 3 Palabras reservadas en C++. En negrita las incluidas en C

14

bitor
class
default
else
for
long
operator
register
sizeof
template
typeid
void

En la Fig. 3 tenemos las palabras reservadas que no pueden ser referenciadas


por el programador para utilizarlas para otros fines. En negrita son las
palabras reservadas en el lenguaje C y el resto corresponde al C++.
Existen otros identificadores que no deben ser utilizados como por ejemplo:
main, NULL, std, string

CONSTANTES
Una constante es un valor fijo que se utiliza en un programa, no vara durante
la ejecucin del mismo. La declaracin de una constante con nombre, se
realiza por medio de la palabra clave const seguida del tipo y nombre
simbolico de la misma, a continuacin del signo igual el valor asociado:
const float Pi = 3.14159265;
Si una constante es del tipo cadena de caracteres, esta se declara utilizando
los smbolos [] a continuacin del nombre de la constante:
const char Pregunta[] = Cdigo postal?;
Las constantes con nombre son declaradas antes de ser utilizadas.
Se puede declarar una constante en forma de una expresin, siempre que sean
valores constantes ya declaradas y que las operaciones entre ellas sean
operadores fijos del lenguaje o funciones predefinidas. Por ejemplo la
constante diametro esta declarada como una expresin:
const float radio = 1.5;
const float diametro = 2*radio;
Todas las constantes con nombre se pueden utilizar exactamente igual que el
valor literal que representan.

15

VARIABLES
El concepto de variable en los lenguajes de programacin difiere del
algebraico. Una variable representa un valor almacenado que se puede
conservar indefinidamente para ser usado tantas veces como se quiera. El
valor de una variable se puede modificar en cualquier momento y ser este
nuevo valor el almacenado a partir de entonces.
Las variables han de ser declaradas en el programa antes de ser utilizadas. La
declaracin simple de una variable especifica su nombre y el tipo de valor
asociado. Por ejemplo la variable edad, que es un nmero entero de aos se
declara:
int edad;
As para declarar una variable se indica en tipo, el nombre y termina con un
punto y coma (;). Si son varias variables tienen el mismo tipo, se pueden
declarar todas conjuntamente, escribiendo sus nombres separados por comas
(,) despus del tipo comn de todas. Por ejemplo:
int dia, mes, anno;
El valor almacenado de una variable puede ser empleado como orerando en
una expresin aritmtica, siempre que no se combinen tipos diferentes. Por
ejemplo:
int
int
float
char

Variables
base, altura;
saldo, meses, das;
volumen, area, gastos;
modelo, codigo;

Expresiones correctas

Expresiones inadecuadas

base * altura
dias + int( cdigo )
volumen / area

area / base
saldo + gastos
base + modelo

Para usar una variable de manera correcta hay que inicializarla. Inicializar
una variable es simplemente darle un valor determinado la primera vez. Para
inicializarla solo hay que poner al nombre de la variable un signo igual
seguido de su valor. Ejemplo:
float gastos = 0.0;
char modelo = '?';

16

Segn el Manual de Estilo, solo se puede inicializar una variable en una


declaracin individual de la misma, para evitar confusiones.

SENTENCIA DE ASIGNACIN
La forma de conseguir que una variable guarde un determinado valor es
mediante una sentencia de asignacin. Por ejemplo:
base = 18;
area = 56.89;
codigo = ' z ';
El signo igual (=) es el operador de asignacin, este operador indica que el
valor de la derecha debe ser asignado a la variable cuyo identificador est a la
izquierda. As en el ejemplo, base, area y codigo sustituyen el valor que
tenan por 18, 56.89 y el carcter z respectivamente. Siempre se utiliza el
valor de la variable en ese momento, as ante la secuencia:
meses = 2; /* meses toma el valor 2*/
dias = meses; /* dias toma el valor actual de meses que es 2*/
meses = 7; /* meses sustituye el valor 2 que tena por 7*/
saldo = meses; /*saldo toma el valor actual de meses que es 7*/
Existe un caso especial, es aquel en que una variable se le asigna el valor de
na expresin de la que forma parte la propia variable. Por ejemplo:
dias = dias + 30;
As si la variable dias tena el valor 16, esta sentencia almacenara el nuevo
valor en 46, que ser 16+30. En general cada vez que pase el programa por
esta sentencia la variable dias incrementar su valor actual en 30 unidades.
En ocasiones hay que incrementar o disminuir una variable en una unidad, es
decir:
variable = variable + 1;
variable = variable - 1;

17

En estos casos existe una sentencia especial de autoincremento:


variable++;
Y para autodecremento:
variable--;
Dado que el lenguaje C no es fuertemente tipado y permite la ambigedad
que supone la asignacin de un valor de un tipo de variable a otro tipo, el
Manual de Estilo establece que para la realizacin de programas en C es
obligatorio que se realice una conversin explicita de tipos en estos casos.
Por ejemplo:
int saldo;
float gastos;

saldo = int(gastos);

OPERACIONES DE LECTURA SIMPLE. EL PROCEDIMIENTO SCANF


El procedimiento scanf permite leer datos de entrada y almacenarlos en
determinadas variables, se escribir:
scanf(cadena-con-formatos, &variable1, &variable2, &variableN);
La cadena de caracteres con los formatos sigue las mismas reglas que el
procedimiento printf, debe contener un formato de conversin (%x) para
cada variable a leer. Por ejemplo:
scanf ( "%d %f %d" , &mes, &saldo, &dia );
Si introducimos los datos de entrada 123 4.5 6, las variables quedan:
mes = 123

saldo = 4.5

dia = 6

La lectura interactiva permite programar una operacin de lectura


inmediatamente despus de una de escritura en la que se indica qu dato el
que se solicita en cada momento. Por ejemplo:
18

float saldo;

printf( "Cantidad pendiente? " );


scanf( "%f" , &saldo);
As en la pantalla se ver:
Cantidad pendiente? _

El smbolo _ indica que el programa est esperando que introduzcamos un


valor, lo introducimos y pulsamos la tecla intro
Cantidad pendiente? -45768
_

En este momento se asignado a la variable saldo el valor -45768. Si se quiere


introducir ms de un valor con la misma pregunta y en la misma lnea se
puede utilizar como separador de datos el espacio en blanco. Por ejemplo:
float saldo, gastos;

printf( "Cantidad pendiente y gastos? " );


scanf( "%f%f" , &saldo, &gastos);
Los introducimos y pulsamos la tecla intro
Cantidad pendiente y gastos? -45768 10456.5
_

En este momento se asignado las variables saldo y gastos el valor -45768 y


10456.5 respectivamente. Si se programa en varias lneas se obtendra el
mismo resultado:
float saldo, gastos;

printf( "Cantidad pendiente y gastos? " );


scanf( "%f" , &saldo );
scanf( "%f" , &gastos );
19

Cantidad pendiente y gastos? -45768


10456.5
_

En este caso habra que pulsar intro cada vez que se introduce el dato.

ESTRUCTURA DE UN PROGRAMA CON DECLARACIONES


El bloque del programa que veamos en la Fig. 2 ahora podemos dividirlo en
dos partes, una contendr la declaracin de todas las variables y constantes
del programa y la segunda incluir todas las sentencias ejecutables
correspondientes a las acciones a realizar siguiendo el orden de ejecucin.
Veamos un ejemplo de programa de realizacin de un recibo
Programa
/** Programa: Recibo */
/* Clculo impresin de un recibo */
#include <stdio.h>
int main() {
int cantidad, IVA
char cdigo;
float precio, totalIVA, subtotal, total;
printf( Cdigo del producto? );
scanf( %c , &codigo );
printf( Cantidad? );
scanf( %d , &cantidad );
printf( Precio unitario? );
scanf( %f , &precio );
printf( IVA aplicable? );
scanf( %d , &IVA );
subtotal = float(cantidad) * precio;
total IVA = subtotal * float(IVA) /100.0;
total = subtotal + totalIVA;
printf( \n
RECIBO de COMPRA\n\n );
printf( Cantidad Concepto
Euros/unidad
Total\n );
printf( %5d
Producto: %c %12.2f%12.2f\n\n ,
cantidad, codigo, precio, subtotal );
printf( %28d%% IVA %12.2f\n\n , IVA, totalIVA );
printf(
TOTAL%14.2f\n , total );
}

20

Resultado
Cdigo del producto? A

Cantidad? 12
Precio unitario? 2345
IVA aplicable? 16
RECIBO DE COMPRA
Cantidad Concepto
Euros/unidad
Total
12
Producto A
2345.00
28140.00
16% IVA

4502.40

TOTAL

32642.40

21

METODOLOGA DE DESARROLLO DE PROGRAMAS (I)


LA PROGRAMACIN COMO RESOLUCIN DE PROBLEMAS
La programacin consiste en un caso particular de la resolucin de
problemas. Resolver un problema es encontrar la estrategia a seguir para
conseguir una solucin partiendo de la informacin que se nos dan unos datos
de entrada y obteniendo unos datos de salida.
El mtodo ms directo en la resolucin de problemas no triviales es
descomponer el problema original en subproblemas ms sencillos,
continuando el proceso hasta llegar a subproblemas que pueden ser resueltos
de forma directa. Por ejemplo, consideremos el siguiente problema:
(0) Obtener una caja de madera barnizada
Para expresar la estrategia de solucin de forma imperativa comencemos a
formular la solucin como una accin global que consigue el objetivo
propuesto, en este caso ser:
(0) Construir una caja de madera barnizada
Esta formulacin necesita un refinamiento, por lo tanto como primer paso se
puede descomponer en tres subproblemas:
1) Obtener las piezas de madera
2) Montar la caja
3) Barnizarla
El proceso de descomposicin en subproblemas debe continuar hasta que los
subproblemas se puedan resolver mediante acciones consideradas
directamente ejecutables por el agente que ha de proporcionar la solucin.
As en nuestro ejemplo habr que decidir si el subproblema 1) ha de
considerarse resoluble mediante una accin simple o compuesta. Si por
ejemplo en la tienda de bricolaje, no compramos las piezas ya cortadas a la
medida necesaria, 1) hay que dividirlo en subproblemas ms sencillos:

22

1.1) Obtener el tablero de madera


1.2) Dibujar sobre l la silueta de las piezas
1.3) Recortar el tablero sigue indo la silueta
De igual manera se seguira con el resto de subproblemas planteados hasta
llegar a la descomposicin en acciones simples.

DESARROLLO POR REFINAMIENTOS SUCESIVOS


La construccin de programas mediante refinamientos sucesivos es la
metodologa que se sigue en la denominada programacin estructurada. Esta
tcnica consiste en expresar el programa como una accin global que se
descompone en acciones ms sencillas hasta llegar a acciones que pueden ser
expresadas directamente como sentencias del lenguaje de programacin. Esta
descomposicin exige:

Identificar las acciones componentes.


Identificar la manera de combinar las acciones componentes para
conseguir el efecto global.

La forma en que varias acciones se combinan en una accin compuesta


constituye el esquema de la accin compuesta. Por el momento solo hemos
tratado el denominado esquema secuencial, que consiste en realizar una
accin compuesta a base de realizar una tras otra, en secuencia, dos o ms
acciones componentes.
Para desarrollar una accin compuesta segn un esquema secuencial se
necesitar:
(a) Identificar las acciones componentes de la secuencia. Identificar las
variables necesarias para disponer de la informacin adecuada al
comienzo de cada accin, y almacenar el resultado.
(b) Identificar el orden en que deben ejecutarse las acciones
componentes.
Para ilustrar esta tcnica elijamos un problema trivial, obtener la suma de dos
nmeros enteros. As tenemos:

23

(a) Acciones componentes

Clculos: obtener la suma


suma = dato1 + dato2

Operaciones de entrada: leer datos


printf(
scanf(
scanf(
printf(

"Dar dos nmeros: " );


"%d" , &dato1 );
"%d" , &dato2 );
"\n" );

Operaciones de salida: imprimir resultado


printf( "La suma es%10d\n:" , suma);

Variables necesarias: datos y resultado


int dato1, dato2, suma
(b) Orden de ejecucin
1) Leer los datos
2) Calcular la suma
3) Imprimir el resultado
As vemos que la accin global del problema se va descomponiendo en
acciones cada vez ms sencillas, si empleamos la siguiente notacin de
refinamiento, en donde () indica que una accin complicada se
descompone o refina en otras ms sencillas:
Accin compuesta
Accin1
Accin2
etc.
Aplicando esta notacin al ejemplo anterior:

24

Obtener la suma de dos numeros


Leer los datos
Calcular las suma
Imprimir resultado
Se refinamos hasta llegar a las sentencias de C:
Leer los datos
printf(
scanf(
scanf(
printf(

"Dar dos nmeros: " );


"%d" , &dato1 );
"%d" , &dato2 );
"\n" );

Calcular suma
suma = dato1 + dato2
Imprimir resultado
printf( "La suma es%10d\n:" , suma);
Uniendo todos las fragmentos finales de cdigo en el orden adecuado y
aadiendo la declaracin de las variables tenemos el programa completo:
/** Programa sumaDosNumeros */
/* Obtener la suma de dos nmeros enteros*/
#include <stdio.h>
int main() {
int dato1, dato2, suma;
printf(
scanf(
scanf(
printf(

"Dar dos nmeros: " );


"%d" , &dato1 );
"%d" , &dato2 );
"\n" );

suma = dato1 + dato2


printf( "La suma es%10d\n:" , suma);
}

25

ASPECTOS DE ESTILO
El estilo de redaccin del programa en su forma final es algo fundamental
para conseguir que sea claro y fcilmente comprensible por parte de quienes
hayan de leerlo. Analizaremos algunas recomendaciones en la manera de
presentar un programa para su fcil compresin. Estas pautas se encuentran
en el Manual de Estilo del lenguaje C

Encolumnado

Fig. 4 Encolumnado de los elementos compuestos

El encolumnado o sangrado (indent), consiste en ampliar el margen izquierdo


de un texto consiguiendo que un elemento compuesto de un texto ocupe una
zona rectangular. Por medio de este recurso se consigue que de una manera
visual se destaque claramente su organizacin por partes (Fig. 4).

Comentarios
Otro recurso utilizable para mejorar la calidad de un programa es el empleo
de cometarios, que en el lenguaje C se consigue intercalndolos en el texto
de un programa entre los smbolos /* y */.
El lenguaje permite el uso de comentarios con total libertad, pero es
aconsejable seguir unas pautas que corresponden a diferentes clases de
comentarios, cada uno con un propsito diferente, entre ellas podemos
mencionar:
26

1. Cabeceras de programa: Suelen presentarse en una caja al


comienzo del texto del programa ocupando todo el ancho del
listado. Se suele incluir identificacin, finalidad, descripcin
general, etc.
2. Cabeceras de seccin: Sirven para documentar partes importantes de
un programa relativamente largo.
3. Comentarios-orden: Sirven para documentar los refinamientos
empleados en el desarrollo del programa. Este tipo de comentario
delimita una accin compuesta y las acciones componentes se
escribirn dejando un mayor margen a la izquierda.
4. Comentarios al margen: Son los que aclaran el significado de ciertas
sentencias del programa, se suelen colocar a la derecha del listado,
en las mismas lneas que las sentencias que se comentan.

Eleccin de nombres
Los nombres que tenga que inventar el programador deben ser elegidos con
criterio nemotcnico, de manera que se recuerden fcilmente el significado
de los elementos nombrados. Se suele elegir una categora gramatical acorde
con el element nombrado:
Los valores (constantes, variables, etc.) deben ser designados
mediante sustantivos.
Las acciones (procedimientos, etc.) deben ser designadas con
verbos. Estos verbos hay que utilizarlos en el mismo tiempo
verbal, en este caso en infinitivo.
Los tipos deben ser designados mediante nombres genricos. Se
suele emplear el sufijo T_

Uso de letras maysculas y minsculas


Los lenguajes de programacin que permiten distinguir entre las letras
maysculas y minsculas facilitan la construccin de nombres en programas
largos, en donde hay que inventar un gran nmero de ellos. Se suelen seguir
las siguientes pautas en cuanto al empleo de maysculas y minsculas:
27

Los nombres de tipos procedimientos y funciones empiezan por


mayscula:
CalcularDias LeerFecha Hoy

Los nombres de variables y constantes empiezan por minscula:


fechaCumple fechaHoy dias

Los nombres que son palabras compuestas usan maysculas


intercaladas al comienzo de cada siguiente palabra componente:
TopoLongitud fechaHoy EscribirFecha

Hay que evitar el empleo de nombres totalmente en maysculas, ya que


dificulta la lectura del texto.

Constantes con nombre


Es aconsejable declarar las constantes con nombres simblicos, en vez de su
valor numrico, ya que mejora la claridad del programa. Su empleo por
ejemplo en factores de conversin as como en los parmetros del programa
hace que su identificacin sea mejor.

28

ESTRUCTURAS BSICAS DE LA PROGRAMACIN IMPERATIVA


PROGRAMACIN ESTRUCTURADA
La programacin estructurada es una metodologa de programacin que
fundamentalmente trata de construir programas que sean fcilmente
compresibles. Un programa no solamente debe funcionar correctamente, sino
que adems debe estar escrito de manera que se facilite su compresin
posterior.
Esta metodologa se basa en lo que hemos denominado refinamientos
sucesivos, que consiste en plantear una operacin global a realizar un
programa y se descompone en otras ms sencillas. A su vez ests pueden
descomponerse en otras todava ms elementales, hasta que llegamos a las
estructuras bsicas disponibles en el lenguaje de programacin que se est
empleando.

Diagramas de flujo
Las estructuras de los programas se representan tradicionalmente mediante
los diagramas de flujo (flow-chart). Estos diagramas contienen dos
elementos bsicos, correspondientes a acciones y condiciones, que se
representan mediante tringulos y rombos respectivamente (Fig. 5). El flujo
de control durante la ejecucin del programa se refleja mediante lneas o vas
que van de un elemento a otro.

Fig. 5 Smbolos de la accin y condicin

29

Las acciones tienen una sola entrada o comienzo y una terminacin o salida.
Las condiciones tienen una entrada y dos vas de salida marcadas con Si y
No. Durante la ejecucin, cuando el flujo llega a la entrada de una accin,
la accin se realiza y el flujo se dirige hacia la salida. Cuando se llega a la
entrada de una condicin, la condicin se evala, y si resulta ser cierta se
contina por la salida Si, pero si es falsa se contina por la salida No.

Fig. 6 Ejemplo de diagrama de flujo y accin compuesta

En la Fig. 6 contiene un ejemplo sencillo de un diagrama de flujo, en la


figura se indica tambin como un fragmento del diagrama, que tenga un
punto de entrada y una salida, puede ser considerado como una accin
compuesta.
La programacin estructurada recomienda descomponer las acciones usando
las estructuras ms sencillas posibles. Entre ellas se reconocen tres
estructuras bsicas: Secuencia, Seleccin e Iteracin. Estas tres estructuras
estn disponibles en todos los modernos lenguajes de programacin en forma
de sentencias del lenguaje. Combinando unos esquemas con otros se pueden
llegar a construir programas con una estructura tan complicada como sea
necesaria.
30

Secuencia

Fig. 7 Secuencia

Es la estructura ms sencilla en la descomposicin es utilizar una secuencia


de acciones o partes que se ejecutan de forma sucesiva.

Seleccin

Fig. 8 Seleccin

La estructura seleccin consiste en ejecutar una accin u otra dependiendo de


uan determinada condicin que se analiza a la entrada de la estructura. En la
Fig. 8 se puede ver que si la condicin analizada <?> su resultado es Si se
ejecuta la accin A y si el resultado es No se realiza la accin B.
Como se puede apreciar la entrada y la salida es nica.

31

Iteracin

Fig. 9 Iteracin

La iteracin es la repeticin de una accin mientras se cumpla una


determinada condicin. La Fig. 9 muestra la estructura de la iteracin. Cada
vez se analiza la condicin <?> y puede dar dos resultados, si este es Si se
ejecuta la accin, que una vez ejecutada se vuelve a analizar la condicin
<?>. En el momento que el resultado sea No sa alcanza el punto final de la
estructura. A la iteracin tambin se le denomina bucle.

Estructura anidadas
Cualquier parte de un programa puede estar compuesto por cualquiera de las
estructuras descritas, por lo que el anidamiento entre ellas puedes er tan
complejo como sea necesario. Mediante la tcnica de refinamientos sucesivos
se definen inicialmente las estructuras ms externas del programa y en los
pasos sucesivos de van detallando la estructura de cada accin compuesta.

EXPRESIONES CONDICIONALES
Para poder utilizar las estructuras de seleccin e iteracin es necesario
expresar las condiciones <?> que controlan ambas estructuras. Esto se realiza
mediante la construccin de expresiones condicionales. Estas expresiones slo
pueden dar como resultado dos valores: Si (cierto), cuando se cumple la
condicin de la expresin, y No (falso), en el caso que no se cumpla.

32

Una primera forma de construir estas expresiones condicionales es mediante


el empleo de operadores de comparacin de expresiones aritmticas. Estos
operadores permiten realizar comparaciones entre dos valores del mismo
tipo. El Manual de Estilo establece que no se pueden comparar elementos de
distinto tipo, es decir enteros con caracteres, reales con enteros, etc.
Las operaciones de comparacin disponibles y sus operadores en C son las
siguientes:
Comparacin
Mayor que
Mayor o igual que
Menor que
Menor o igual que
Igual a
Diferente a

Smbolo matemtico
>

<

Operador C
>
>=
<
<=
==
!=

Por ejemplo, sean las variables declaradas:


int largo, ancho;
float presion, temperatura;
char letra, modelo;
Podemos formar las siguientes expresiones condicionales:
largo > 5
ancho == largo
presion <= 23.5
modelo = 'Z'
letra != modelo
presion != temperatura
Como con estos operadores de comparacin solo se puede evaluar una
condicin y en ocasiones ests suelen ser complejas, es necesario crear
condiciones compuestas constituidas por expresiones lgicas. Cada trmino
de una expresin lgica podra ser una expresin condicional simple.
Las operaciones lgicas entre dos expresiones simples E1, E2 y los
correspondientes operadores disponibles en C son los siguientes:
33

Operacin lgica
Conjuncin (E1 y E2)
Disyuncin (E1 o E2)
Negacin (no E1)

Smbolo matemtico

Operador C
&&
||
!

La operacin conjuncin (E1 && E2) da el resultado ciento si tanto como E1


como E2 son ciertos. En el lenguaje C se evala la expresin E1, el primer
operando y si el resultado es falso ya no se evala la expresin E2, ya que no
se cumple la condicin de que ambos sean ciertos. Por este motivo se dice
que el operador && se evala en cortocircuito. Esta misma regla se aplica en
el caso de realizar la conjuncin de n expresiones: E1 && E2 && E3 &&
&& En. Solo evaluar la nueva expresin Ei cuando las anteriores sean
ciertas.
El operador disyuncin (E1 | | E2) da resultado cierto si una de las dos, E1 o
E2, o ambas son ciertas. Tambin el operador | | se evala en cortocircuito y
en las expresiones de disyuncin de n expresiones E1 | | E2 | | E3 | | | | En.
Solo se evaluar la nueva expresin Ei cuando todas las anteriores hayan sido
falsas.
El operador negacin (!) se aplica a un solo trmino y niega el resultado de
dicho trmino.
Ejemplos:
(largo > 5) && (ancho < 7)
(modelo == 'A') | | (modelo == 'Z')
! (letra == 'Q')
(temperatura <= 123.7) && (presion < 12.3)
Los parntesis indican que primero se evalan las comparaciones aritmticas
y despus las lgicas.
La complejidad de las expresiones puede ser tan grande como sea necesario,
adems cada valor numrico se puede obtener mediante una expresin
aritmtica. Por ejemplo, son expresiones condicionalmente vlidas las
siguientes:

34

(largo < 3) && (ancho < 9) && (largo*ancho < 25)


! ((letra == 'Q' | | letra == 'Z' ))
(3.5*temperatura presin/5.6) < 54.6
Si no se utilizan parntesis, el orden de evaluacin en el lenguaje C es el
siguiente:
1.
2.
3.
4.
5.
6.
7.

Operadores Unarios
Operadores Multiplicativos
Operadores Aditivos
Operadores de comparacin
Operadores de igualdad
Operador de Conjuncin
Operador de Disyuncin

!+*/ %
+> >= < <=
== =
&&
||

Como resumen el Manual de Estilo establece las siguientes reglas:

Es aconsejable utilizar parntesis adicionales para evitar cualquier


ambigedad o dificultad de interpretacin de la expresin.
Es aconsejable utilizar parntesis adicionales siempre que se mejore
la claridad de la expresin.
No es aconsejable utilizar parntesis adicionales en aquellas
expresiones que, aprovechando los niveles de prioridad por defecto
del lenguaje, estn ampliamente consensuadas y no planteen
ninguna duda en su interpretacin.
No estn permitidas las comparaciones de elementos de distintos
tipos.
Los operadores lgicos solo se pueden utilizar con elementos de tipo
Si(cierto)/No(falso)

ESTRUCTURAS BSICAS EN C
Secuencia
Es una secuencia de acciones o partes que se ejecutan de forma sucesiva, tal
como se ve en la Fig. 7, es decir se escribe:
Accin A
Accin B

35

Sentencia IF
En C la estructura de la seleccin (Fig. 8) se programa como una sentencia
IF que tiene el siguiente formato:
if ( Condicin ) {
Accin A
} else {
Accin B
}
La ejecucin de la sentencia if consiste en evaluar la expresin Condicin, y
a continuacin ejecutar la Accin A (si se cumple la condicin), o bien la
Accin B (si la condicin no se cumple) Las palabras clave if y else separan
las distintas partes de la sentencia. Por ejemplo:
if ( largo > ancho ) {
ladoMayor = largo;
} else {
LadoMayor = ancho;
}
En ocasiones no es necesario ejecutar nada cuando la Condicin no se
cumple (Fig. 10)

Fig. 10 Seleccin simple

36

El formato de la sentencia en C es ahora:


if ( Condicin ) {
Accin
}
En este caso se ejecuta la accin cuando la Condicin se cumple y en caso
contrario no se ejecuta nada. Por ejemplo:
ladoMayor = ancho;
if ( largo > ancho ) {
LadoMayor = largo;
}

Fig. 11 Seleccin en cascada

Si la evaluacin de las condiciones se realiza en cascada (Fig. 11), es decir


que se atiende a una de ellas solo si todas las anteriores han sido falsas. Se
puede simplificar la sentencia IF eliminando las llaves {} de las ramas else
para expresar directamente una cadena de selecciones.

37

El formato de la sentencia if para la seleccin en cascada es:


if ( Condicin 1 ) {
Accin A
} else if ( Condicin 2 ) {
Accin B
.
} else if ( Condicin N ) {
Accin J
} else {
Accin K
}
As por ejemplo, si tenemos distintas tarifas segn la edad:
Nios de 0 a 6 aos
Jvenes de 6 hasta 18 aos
Adultos de 18 hasta 65 aos
Jubilados de 65 aos en adelante

Gratis
50 %
100 %
25 %

Podemos realizar la seleccin en cascada de la siguiente manera:


if ( edad < 6 ) {
tarifa = 0.0;
} else if ( edad < 18 ) {
tarifa = 0.5;
} else if ( edad < 65 ) {
tarifa = 1.0;
} else {
tarifa = 0.25;
}

Sentencia WHILE
En C la secuencia de iteracin (Fig. 9) se consigue mediante la sentencia
WHILE, que tiene el siguiente formato:

38

while ( Condicin ) {
Accin
}
Su significado es que mientras la expresin Condicin resulte cierta, se
ejecutar la Accin de forma repetitiva. Cuando el resultado es falso finaliza
la ejecucin de la sentencia. Si la Condicin es falsa en la primera
evaluacin, la Accin no se ejecuta nunca.
Por ejemplo para calcular la factorial de un nmero entero n:
n! = 1 x 2 x 3 x 4 x x n
Se calcula utilizando una sentencia while de la siguiente forma:
factorial = 1
while ( n > 1 ) {
factorial = factorial * n;
n--;
}
As con la sentencia de autodecremento la variable n va disminuyendo su
valor de uno en uno en cada repeticin del bucle, al tiempo que esos valores,
se van multiplicando sucesivamente, guardando el producto acumulado en
factorial, hasta que n se reduce a 1. Si inicialmente el valor de n es igual o
menor que 1, no se pueden ejecutar nunca las sentencias dentro del bucle, por
lo que la variable factorial termina con el mismo valor inicial igual a 1.

Sentencia FOR
Existen ocasiones en que las iteraciones del bucle se controlan mediante una
variable que va contado las veces que se ejecuta. La cuanta puede ser en
sentido ascendente o descendente. La Condicin de la iteracin se limita a
comprobar si se ha alcanzado el lmite correspondiente al nmero de
repeticiones previstas.
Dado que es habitual esta situacin los lenguajes existen sentencias que
simplifican su construccin. El C se dispone de la sentencia FOR, cuya
forma para incremento creciente es:
39

for (int ndice = Inicial ; ndice <= Final ; ndice ++) {


Accin
}
La variable ndice sirve de contador para controlar el nmero de
interacciones a realizar. Inicialmente la variable ndice toma el valor Inicial y
se incrementa automticamente en una unidad con cada nueva ejecucin de
Accin. La Accin se ejecuta repetidamente hasta que la variable ndice
alcanza el valor Final. Ambos valores, Inicial y Final, pueden ser
expresiones aritmticas. Estas expresiones se evalan solo una vez al
comienzo de la sentencia FOR y no se modifican durante todo su ejecucin.
Si el valor inicial es mayor que el valor final, la Accin no se ejecuta nunca.
La variable ndice puede ser utilizada dentro de la Accin pero nunca debe
ser modificada, pues se perdera el control automtico de las repeticiones.
Esto es una norma del Manual de Estilo que es obligatorio aplicar a cualquier
sentencia FOR. La variable ndice se declara dentro del propio FOR, y solo
existe mientras se ejecuta. Al terminar la ejecucin la variable ndice ya no es
visible en las siguientes sentencias del programa.
As, por ejemplo, el clculo de la factorial se puede escribir de la siguiente
manera:
factorial = 1;
for (int indice = 2 ; ndice <= n ; indice ++) {
factorial = factorial * ndice;
}
La sentencia FOR de C tiene una versin para decrementar el contador en
cada repeticin. En este caso el formato es el siguiente:
for (int ndice = Inicial ; ndice >= Final ; ndice --) {
Accin
}

40

As se puede realizar el clculo de la factorial en sentido inverso:


factorial = 1;
for (int indice = n ; ndice >= 2 ; indice --) {
factorial = factorial * ndice;
}

41

METODOLOGA DE DESARROLLO DE PROGRAMAS (II)


DESARROLLO CON ESQUEMAS DE SELECCIN E ITERACIN
Esquema de seleccin
Un esquema de seleccin consiste en platear una accin compuesta como la
realizacin de una accin entre varias posibles, dependiendo de ciertas
condiciones, es decir, se trata de elegir una sola entre varias posibles
alternativas (Fig. 8).
Para desarrollar un esquema de seleccin, deberemos identificar sus
elementos, es decir:
(a) Identificar cada una de las alternativas del esquema, y las acciones
correspondientes.
(b) Identificar las condiciones para seleccionar una alternativa u otra.
Por ejemplo si queremos saber cuntos das tiene el mes de febrero, este
esquema de seleccin dispone de los siguientes elementos:
(a) Las alternativas son que tenga 28 das o que tenga 29 das. Las
acciones son asignar dicho valor a un variable que almacene el
nmero de das:
dias = 28
o bien
dias = 29
(b) La condicin para elegir una accin u otra es que el ao sea bisiesto,
de forma simplificada para los aos comprendidos entre 1901 y
2099 es que el ao sea mltiplo de cuatro:
anno % 4 == 0
Colocando cada elemento identificativo en su lugar correspondiente
tendremos:

42

if ( anno % 4 == 0 ) {
dias = 29;
} else {
dias = 28;
}
De manera similar se pueden desarrollar esquemas de seleccin simplificados
con solo una accin combinada o esquema de seleccin en cascada en que
haya un nmero ms o menos grande de alternativas.

Esquema de iteracin
Una interaccin o bucle consiste en la repeticin de una accin o grupo de
acciones hasta conseguir el resultado deseado (Fig. 9). Para desarrollar un
esquema de interaccin, deberemos identificar sus elementos, as como las
variables adecuadas para almacenar la informacin necesaria, es decir:
(a) Identificar las acciones tiles a repetir, y las variables necesarias.
Precisar el significado de cada una de ellas al comienzo y al final de
cada repeticin.
(b) Identificar como actualizar la informacin al pasar de cada iteracin
a la siguiente. Puede ser necesario introducir nuevas variables.
(c) Identificar la condicin de terminacin. Puede ser necesario
introducir nuevas variables e incluso acciones adicionales para
mantenerlas actualizadas.
(d) Identificar los valores iniciales de las variables, y si es necesario
alguna accin para asignrselos antes de entrar en el bucle.
Por ejemplo, si queremos hallar los trminos de la serie de Fibonacci, en la
que cada termino es la suma de los dos anteriores. La serie comienza con los
trminos 0 y 1, que se suponen ya impresos antes del bucle. Se trata de
imprimir tantos como sean posibles.
(a) Acciones tiles a repetir: Imprimir un trmino
printf( "%10d\n:" , termino);

43

Variables necesarias: El termino a imprimir


int termino
Valor al empezar la repeticin: ltimo trmino impreso hasta el
momento.
(b) Actualizacin de las variables al pasar de una repeticin a la
siguiente: Antes de imprimir el trmino actual a partir de los dos
anteriores (se necesita tener almacenado el penltimo)
aux = termino + anterior;
anterior = termino;
termino = aux;
Variables adicionales: El penltimo termino, y una variable temporal.
int anterior;
int aux;
(c) Condicin de terminacin: El trmino siguiente excedera del rango de
los enteros. Hay que evaluar la condicin sin calcular explcitamente el
valor de dicho trmino, porque se producira overflow
INT_MAX-termino < anterior
(Obsrvese que esta expresin equivale en teora a INT_MAX < termino
+ anterior)
(d) Valores iniciales de las variables: Los primeros trminos, 0 y 1
anterior = 0;
termino = 1;
El bucle completo sera:

44

int termino;
int anterior;
int aux;
.....
anterior = 0;
termino = 1;
while (INT_MAX-termino >= anterior) {
aux = termino + anterior;
anterior = termino;
termino = aux;
printf( "%10d\n:" , termino);
}

VERIFICACIN DE PROGRAMAS
La verificacin de un programa es la tcnica que permite comprobar el
perfecto funcionamiento del mismo. En la prctica, esta verificacin se puede
hacer mediante ensayos. Un ensayo (testing) consiste en ejecutar un
programa con datos preparados de antemano y de los cuales se sabe el
resultado que debe dar. Si en la ejecucin no se dan los resultados esperados
entonces el programa tiene un error, el cual hay que detectar y eliminar. Este
proceso se denomina depuracin (debugging).
Pero puede pasar que el programa sea correcto o que solo lo es para los datos
preparados, es decir que con este mtodo, no estamos seguros al cien por cien
de la correccin del programa. Por lo que la manera ms fiable de verificar
un programa es demostrar formalmente que el programa cumple con las
especificaciones. Para ello es preciso escribir estas especificaciones con toda
precisin en forma de expresiones lgico-matemticas y luego realizar la
demostracin lgico-matemtica que el programa las cumple.
Con esta tcnica se comprueba que el programa cumple con las
especificaciones del mismo, pero que estas describan realmente el problema a
resolver es una cuestin aparte.
Usando expresiones y reglas de la lgica, y conociendo la semntica
(significado) de las acciones, es posible demostrar si un programa es o no
correcto respecto a una especificacin. Para programas que siguen el modelo
imperativo el proceso de demostracin se realiza en dos partes:
45

1) Correccin parcial: si el programa termina el resultado correcto.


2) Correccin total: lo anterior, y si adems para todo dato de entrada
vlido el programa termina.
La base de la correccin parcial es:

Anotar el comienzo y el final del programa como aserciones o


asertos (afirmaciones, formalizadas como expresiones lgicas)
correspondientes a las condiciones iniciales y al resultado deseado.
La condicin al comienzo se suele denominar precondicin y la del
final postcondicin. La precondicin y la postcondicin,
conjuntamente constituyen la especificacin formal del programa.
Anotar los puntos intermedios del programa con aserciones similares
respecto al estado de cmputo en ese punto.
Demostrar que si se cumple una asercin en un punto del programa
y se siguen cada una de las lneas de ejecucin posibles hasta llegar
a otro punto con asercin, dicha asercin ha de cumplirse, segn las
reglas de la lgica y de acuerdo con las acciones realizadas.

La correccin total se consigue aadiendo la demostracin de que todos los


bucles del programa terminan tras un nmero finito de repeticiones. Para
demostrar la terminacin se puede:

Asociar a cada bucle una funcin montona (siempre estrictamente


creciente) llamada variante, y que debe tener un valor acotado para
que el bucle se repita.

De esta manera tras un cierto nmero de repeticiones se alcanzar la cota o


lmite de dicha funcin, y el bucle terminar.
Las aserciones se escriben en el texto del programa entre comillas
tipogrficas para distinguirlas del cdigo del programa en C.

Razonamientos sobre sentencias de asignacin


Para analizar el comportamiento de un fragmento de programa
correspondiente a una sentencia de asignacin, comenzaremos por anotar
46

delante de dicha sentencia todas las condiciones que sabemos que se cumplen
inmediatamente antes de ejecutarla. A continuacin anotaremos detrs de la
sentencia las condiciones que podemos demostrar que se cumplen despus de
su ejecucin y que sern:

Las condiciones anteriores en las que no intervenga la variable


asignada.
La condicin de que la variable tiene el valor asignado.

Por ejemplo (asumiendo que es cierto lo que se indica al comienzo de esta


sentencia):
ASERTO: (x > y) (a > b) (a > x)
a = 36
ASERTO: (x > y) (a = 36)
En la anotacin final se ha suprimido las condiciones (a > b) (a > x), ya que
en ellas interviene la variable a y esta ha tomado un valor fijo de 36, por lo
tanto solo queda la condicin (x > y).

Razonamiento sobre el esquema de seleccin


Para analizar el comportamiento de un fragmento de programa
correspondiente a un esquema de seleccin, comenzamos anotando delante
de dicho esquema las condiciones que sepamos que se cumplen
inmediatamente antes de examinar la condicin.

Fig. 12 Razonamiento sobre un esquema de seleccin

En el esquema de la derecha de la Fig. 12 estn anotadas las condiciones de


seleccin, en funcin que se elija la alternativa Si y se cumplan las
47

condiciones iniciales y adems la condicin de la seleccin, y que al


comienzo de la alternativa No se cumplieran las condiciones iniciales y no
se cumpliera la condicin de seleccin.
En la parte de terminacin del esquema (izquierda Fig. 12) anotaremos las
condiciones que se deduzcan de la ejecucin de cada alternativa en particular;
y anotaremos como condicin de salida que ha de cumplirse alguna de las
condiciones de terminacin, correspondientes a las dos alternativas posibles.
Aplicaremos este razonamiento a un fragmento del programa que calcule en
m el mximo de dos nmeros. Dicho fragmento podra ser:
if ( a > b ) {
m = a;
} else {
m = b;
}
Anotando las aserciones al comienzo:
if ( a > b ) {
ASERTO: a > b
m = a;
} else {
ASERTO: a b
m = b;
}
Razonando sobre cada sentencia de asignacin, tenemos:
if ( a > b ) {
ASERTO: a > b
m = a;
ASERTO: (a > b) (m = a) ASERTO: m = Max(a,b)
} else {
ASERTO: a b
m = b;
ASERTO: (a > b) (m = b) ASERTO: m = Max(a,b)
}
48

Ahora se puede escribir la asercin final como unin de las dos alternativas:
if ( a > b ) {
ASERTO: a > b
m = a;
ASERTO: (a > b) (m = a) ASERTO: m = Max(a,b)
} else {
ASERTO: a b
m = b;
ASERTO: (a > b) (m = b) ASERTO: m = Max(a,b)
}
ASERTO: m = Max(a,b) m = Max(a,b) ASERTO: m = Max(a,b)
Luego el funcionamiento es el correcto.

Razonamiento sobre el esquema de interaccin: invariante,


terminacin
Para analizar el comportamiento de un fragmento de programa
correspondiente a un esquema de interaccin, se tienen que identificar, por
una parte, las condiciones que deben cumplirse siempre inmediatamente
antes de examinar la condicin de repeticin. Estas condiciones son lo que se
denomina invariante del bucle.

Fig. 13 Razonamiento sobre un esquema de interaccin

En la Fig. 13 se representa el diagrama de flujo de un bucle tipo WHILE, en


el que el invariante es p.
49

Razonando como en el esquema de seleccin, deduciremos que al comienzo


de cada repeticin de la accin del bucle habr de cumplirse el invariante y
adems la condicin de repeticin, y que al terminar las repeticiones y salir
del bucle se cumplir el invariante y adems no se cumplir la condicin de
repeticin. La identificacin del invariante, en ocasiones es complicada, ya
que no se trata de anotar todas las condiciones que sabemos que se cumplen
al llegar al bucle por primera vez, sino aquellas que se seguirn cumpliendo
despus de cada repeticin. Por otro lado para garantizar que el bucle
termina, hay que identificar una funcin estrictamente montona y acotada,
denominada variante, que no podr variar indefinidamente al estar acotada
asegurando que el bucle tendr fin.
Tomemos como ejemplo un fragmento de programa para calcular en f el
factorial de un nmero n. Para ello se usar un contador k que vaya tomando
valores de 1 a n:
k=1
f=1
while ( k < n ) {
k++
f = f * k;
}
Usaremos como invariante (k n) (f = k!). Esto es vlido para los casos
en que (n 1) y como variante la expresin n k. Las anotaciones en el bucle
sern:
k=1
f=1
INVARIANTE: (k n) (f = k!) VARIANTE: n - k
while ( k < n ) {
ASERTO: (k n) (f = k!)
k++
ASERTO: (k n) (f = (k - 1)!)
f = f * k;
ASERTO: (k n) (f = k!)
}
ASERTO: (k n) (k n) (f = k!) ASERTO: f = n!
50

EFICIENCIA DE PROGRAMAS. COMPLEJIDAD


La eficiencia de un programa se define en funcin de la cantidad de recursos
que consume durante su ejecucin. Un programa es ms eficiente que otro si
consume menos recursos.
Las principales medidas de los recursos empleados en un programa son:

El tiempo que tarda en ejecutarse el programa.


La cantidad de memoria usada para almacenar los datos.

En muchos casos ambos factores son mutuamente dependientes, pero el


primero es ms influyente por lo que trataremos de la eficiencia en tiempo de
un programa.
La determinacin de la eficiencia (o complejidad) de un programa se hace
analizando los siguientes elementos:

Cunto tarda en ejecutarse cada instruccin bsica del lenguaje


utilizado.
Cuantas instrucciones de cada clase se realizan durante una
ejecucin del programa.

Si consideramos que cada operacin elemental del lenguaje de programacin


(suma, resta, escritura, lectura, asignacin de un valor, etc.) dura una unidad
de tiempo, con esta simplificacin el anlisis de la eficiencia de un programa
se centra en establecer cuntas instrucciones se ejecutan en total,
dependiendo del tamao o cantidad de los datos a procesar.
Se emplear como criterio de anlisis de la complejidad (nmero de
instrucciones ejecutadas) de los esquemas bsicos de los programas las
siguientes reglas:
1. La complejidad de un esquema de secuencia ser la suma de las
complejidades de sus acciones componentes.

51

2. La complejidad de un esquema de seleccin equivale a la de la


alternativa ms compleja, es decir, de ejecucin ms larga, ms la
complejidad de la evaluacin de la condicin de seleccin.
3. La complejidad e un esquema de interaccin s obtiene sumando la
serie correspondiente al nmero de instrucciones en las repeticiones
sucesivas.
Por ejemplo, este fragmento de programa que obtiene el mximo de dos
nmeros:
Cdigo
maximo = a;
if ( a < b ) {
maximo = b;
}

Nmero de instrucciones ejecutadas


1
2

3
(Regla 2)
1
Total =

(Regla 1)

La complejidad es fija y no depende del tamao del problema.


Para el bucle que obtiene en f la factorial de n:
Cdigo
k=1
f=1
while ( k < n ) {
k++
f = f * k;
}

Nmero de instrucciones ejecutadas


1
1
2
1

5(n-1)
(Regla 3)
2
Total =

5n-3

(Regla 1)

La complejidad aparece expresada en funcin de n, que en este caso resulta


una medida natural del tamao del problema.
En ocasiones la complejidad de un programa posee lo que se conoce como un
comportamiento asinttico, ya que para tamaos pequeos tiene una
eficiencia buena ero para tamaos grandes disminuye su eficiencia.

52

FUNCIONES Y PROCEDIMIENTOS
CONCEPTO DE SUBPROGRAMA
Un subprograma es una parte de un programa que se desarrolla por separado
y se utiliza invocndolo mediante un nombre simblico. El empleo de
subprogramas, desarrollando por separado ciertas partes del programa, resulta
espacialmente ventajoso en los siguientes casos:
1. En programas complejos. Si el programa se escribe todo seguido
resulta muy complicado de entender, porque se difumina la visin
de su estructura global entre la gran cantidad de operaciones que
forman el cdigo del programa. Aislado ciertas partes como
subprogramas separados se reduce la complejidad del mismo.
2. Cuando se repiten operaciones anlogas: Definiendo esa operacin
como subprograma separado, su cdigo se escribir solo una vez,
aunque luego se use en muchos puntos del programa. As el tamao
total del programa ser menor que si se escribiera el cdigo
completo.

Fig. 14 Estructura de un programa con declaracin global

Los subprogramas se definen en la denominada declaracin global (Fig. 14),


que se encuentra despus de la directiva #include y antes del inicio del
programa principal indicado mediante int main(). Esta zona se describen no
solo los subprogramas sino el resto de elementos globales que se necesiten
53

para el desarrollo del programa (constantes, variables, y tipos). Cada


subprograma est constituido por su propio bloque de cdigo a manera
semejante a un programa completo. Existen dos formas fundamentales de
subprogramas en programacin imperativa: funciones y procedimientos.

FUNCIONES
Una funcin es un tipo de subprograma que calcula como resultado un valor
nico a partir de otros valores dados como argumento. En lneas generales
una funcin se asemeja bastante a la idea matemtica de funcin F(x,y,)
con argumentos x,y, Por ejemplo:
xn
lado3
(base * altura)/2
((x1 x2)2 + (y1 y2)2)1/2

Potencia:
Volumen de un cubo:
rea de un tringulo:
Distancia entre dos puntos:

El primer paso para la definicin de una funcin es declarar su nombre, los


argumentos que necesita con los correspondientes tipos para cada uno de
ellos, y el tipo del resultado que proporciona. As en C la cabecera de una
funcin es de la siguiente forma:
TopoResultado NombreFuncin( Tipo1 argumento1, Tipo2 argumento2, )
As las cabeceras de las anteriores funciones quedaran:
Potencia:
Volumen de un cubo:
rea de un tringulo:
Distancia entre dos puntos:

float Potencia( float x, int n )


int VolumenCubo( int lado )
float AreaTriangulo( float base, float altura )
float Distancia(float x1, float y1, float x2, float y2 )

A continuacin de la cabecera se define el cuerpo de la funcin que tiene la


misma estructura que un Bloque de programa completo. Es decir estar
formado por una parte declarativa, donde se pueden declarar las variables y
constantes locales que solo son visibles en el cuerpo de la funcin. La parte
ejecutiva estar constituida por una secuencia de sentencias. La funcin
termina devolviendo un valor que es el resultado de la funcin. La sentencia
que se encuentra dentro de la parte ejecutable de la funcin que devuelve el
valor de la misma es:
54

return expresin;
Se puede utilizar ms de una sentencia return en una funcin. Pero hay que
tener claro que la ejecucin acaba cuando se ejecuta cualquiera de las
sentencias return.
As la definicin completa de las funciones definidas queda:
Potencia:

float Potencia( float x, int n ) {


float p = 1.0;
for (int k = 1; k <= n; k++) {
p = p * x;
}
return p;
}

Volumen de un cubo:

int VolumenCubo( int lado ) {


return lado*lado*lado;
}

rea de un tringulo:

float AreaTriangulo( float base, float altura ) {


return (base * altura) / 2.0;
}

Distancia entre dos


puntos:

float Distancia(float x1, float y1, float x2, float y2 ) {


float deltaX, deltaY;
deltaX = x2 x1;
deltaY = y2 y1;
return sqrtf( deltaX*deltaX + deltaY*deltaY );
}

Como ejemplo de una funcin con varias sentencias de retorno, podemos ver
la que nos devuelve el mximo valor de dos nmeros enteros:
int Maximo2( int x, int y) {
if (x>0 y) {
return x;
} else {
return y;
}
}

55

Para usar una funcin en los clculos de un programa se invoca dicha funcin
escribiendo el nombre y a continuacin, entre parntesis, los valores
concretos de los argumentos, separados por comas. El efecto de una funcin
puede describirse de forma simplificada de la siguiente manera:
1. Se evalan las expresiones de los valores de los argumentos.
2. Se asignan dichos valores a los correspondientes argumentos
formales,
3. Se ejecuta el cdigo de la definicin de la funcin, hasta alcanzar
una sentencia de retorno.
4. El valor retornado se usa en el punto donde se invoc la funcin.

Funciones predefinidas y funciones estndar


Se consideran funciones predefinidas las que forman parte del propio
lenguaje de programacin. El lenguaje C no tiene funciones predefinidas, y
el C y C++ tiene de muy pocas.
Al realizar programas en C se pueden emplear funciones definidas en
mdulos ya redactados de antemano. Algunos mdulos constituyen libreras
estndar y estn disponibles en la mayora de los compiladores de C y C++.
Las funciones definidas en los mdulos estndar se denominan funciones
estndar y pueden ser utilizadas sin necesidad de escribir su definicin, pero
a diferencia de las funciones predefinidas hay que indicar expresamente que
se van a utilizar dichos mdulos de librera mediante la directiva #include del
correspondiente modulo que le contenga.
As por ejemplo la directiva #include <ctype.h> son funciones empleadas en
el manejo de caracteres, tales como:
bool
bool
bool
bool
bool
bool
bool
bool
bool
bool

isalpha( char c )
isascii(char c )
isblank(char c )
iscntrl(char c )
isdigit(char c )
islower(char c )
isspace(char c )
isupper(char c )
tolower(char c )
toupper(char c )

Indica si c es una letra


Indica si c es un carcter ASCII
Indica si c es un carcter de espacio o tabulacin
Indica si c es un carcter de control
Indica si c es un digito decimal (0-9)
Indica si c es un letra minscula
Indica si c es un espacio en blanco o salto de lnea o pgina
Indica si c es una letra mayscula
Devuelve la minscula correspondiente a c
Devuelve la mayscula correspondiente a c

56

En lo referente a funciones matemticas, se dispone del mdulo estndar


#include <math.h> Este mdulo dispone de un gran nmero de funciones
matemticas con nombres distintos dependiendo del tipo de argumento y el
tipo de resultado, algunas de ellas son las siguientes:
float
float
float
float
float
float
float
float
float

sqrtf( float x )
expf( float x )
logf( float x )
powf( float x, float y )
sinf( float x )
cosf( float x )
tanf( float x )
atanf( float x )
roundf( float x )

raz cuadrada de x
exponencial ex
logaritmo neperiano de x
potencia xy
seno de x
coseno de x
tangente de x
arcotangente de x
valor de x redondeando a entero

PROCEDIMIENTOS
Un procedimiento es un subprograma que realiza una determinada accin. A
diferencia de las funciones, un procedimiento no tiene como objetivo, en
general, devolver un valor obtenido por clculo.
un procedimiento es una forma de subprograma que agrupa una sentencia o
grupo de sentencias que realizan una accin, y permite darles un nombre por
el que se puede identificar posteriormente. Estas sentencias se pueden
parametrizar con una serie de argumentos, tal como se haca con las
funciones, pero a diferencia de ests no nos devuelve ningn valor.
Ejemplo de procedimientos seran:
Trazar una lnea de longitud dada
Imprimir un resultado
Ordenar dos valores
Leer las coordenadas de un punto
Estas acciones se pueden definir como procedimientos y luego invocarlas en
el programa cuando interese.

57

Un procedimiento se define en C de la siguiente forma:


void NombreProcedimiento( Tipo1 argumento1, Tipo2 argumento2, )
As por ejemplo, el procedimiento de trazar una lnea de longitud dada sera:
void TrazarLinea( int longitud ) {
for (int k = 1; k<= longitud; k++) {
printf( "-" );
}
}

En ocasiones se pueden definir procedimientos sin argumentos, como por


ejemplo imprimir un resultado:
void EscribirResuktado ( ) {
printf( "Resultado:%10f\n", resultado );
}
Si se desea en la definicin de un procedimiento se puede usarse la sentencia
de retorno, pero con un significado distinto que en las funciones:
return;
En este caso no tiene ninguna expresin, ya que no hay ningn valor a
devolver. Esta sentencia sirve simplemente para terminar la ejecucin del
procedimiento en ese momento y volver al punto siguiente a donde se invoc.
Por ejemplo, otra posible definicin del procedimiento de imprimir un
resultado sera:
void EscribirResuktado ( ) {
if (resultado < 0) {
printf( "Problema no resuelto" );
return;
}
printf( "Resultado:%10f\n", resultado );
}

En este caso si se cumple la condicin de la sentencia if la sentencia final de


escritura no se ejecutar.
58

Para usar un procedimiento hay que invocarlo, esto se realiza mediante la


siguiente sentencia de llamada:
NombreProcedimiento( argumento1, argumento2, )
Los valores d los argumentos pueden darse por lo general mediante
expresiones. Si no hay argumentos no se suprimen los parntesis. As por
ejemplo:
Lado = 5
TrazarLinea( 3*Lado )

Dara como resultado:


--------------As, la invocacin de un procedimiento produce un efecto anlogo a la
secuencia de acciones siguientes:
1. Se evalan las expresiones de los valores de los argumentos.
2. Se asignan dichos valores a los correspondientes argumentos
formales.
3. Se ejecuta el cdigo de la definicin del procedimiento, hasta
alcanzar el final del bloque o una sentencia de retorno.
4. El programa que invoc el procedimiento continua en el punto
siguiente a la sentencia de llamada.

PASO DE ARGUMENTOS
La manera fundamental de comunicar la informacin entre las sentencias de
un subprograma y e programa que lo utilizan es mediante los argumentos. En
C existen dos formas distintas de realizar esta comunicacin: paso de
argumentos por valor y paso de argumentos por referencia.

Paso de argumentos por valor


Este es la forma utilizada hasta ahora en las funciones y procedimientos, que
puede describirse de la siguiente manera:
59

1. Se evalan las expresiones de los argumentos reales usados en la


llamada.
2. Los valores obtenidos se copian en los argumentos formales.
3. Los argumentos formales se usan como variables dentro del
subprograma. Si a estas variables se les asignan nuevos valores, no
se estar modificando el argumento real, sino solo la copia.
Por ejemplo podemos escribir la funcin que calcula la distancia entre dos
puntos de la siguiente manera:
float Distancia(float x1, float y1, float x2, float y2 ) {
x1 = x2 x1;
y1 = y2 y1;
return sqrtf( x1*x1 + y1*y1 );
}

Dentro del procedimiento se asignan nuevos valores a algunos de los


argumentos. Peso a ello, un fragmento del programa tal como:
xA = 23.5; yA = 12.3
xB = 5.7; yB = 2.6;
distancia AB = Distancia( xA, yA, xB, yB )
No modifica las variable externas xA e yA usadas como argumentos, que
mantienen los valores que tenan antes de la llamada. Dado que esto puede
causar confusin el Manual de Estilo recomienda evitarlo.

Paso de argumentos por referencia


En ciertos casos es deseable que el subprograma pueda modificar las
variables que se usen como argumentos. Esto permite producir
simultneamente varios resultados y no solo uno. Esto se consigue con el
paso de argumentos por referencia, indicando en la cabecera del subprograma
anteponiendo el smbolo & al nombre del argumento formal, de la siguiente
manera:
TipoResultado Nombre( TipoArgumento & argumento, )

60

Cuando se pasa un argumento por referencia ya no ser vlido usar como


argumento real una expresin. El argumento real usado en la llamada debe
ser necesariamente una variable del mismo tipo. Esta variable ser utilizada
en el subprograma como si fuera suya, es decir, la asignacin de nuevo valor
al argumento modifica realmente la variable externa pasada como argumento.
As esta paso puede describirse:
1. Se seleccionan las variables usadas como argumentos reales.
2. Se asocia cada variable con el argumento formal correspondiente.
3. Se ejecutan las sentencias el subprograma como s los argumentos
formales fueran argumentos reales.
As por ejemplo los procedimientos para ordenar dos valores y leer las
coordenadas de un punto seran:
void OredenarDos(int & y; int & z ) {
int aux;
if (y > z) {
aux = y;
y = z;
z = aux;
}
void LeerCoordenadas( char Punto. float & x, float & y ) {
printf( "Punto %c\n",Punto );
printf( "Coordenada X ?" );
scanf( "%f", &x);
printf( "Coordenada Y ?" );
scanf( "%f", &y);
printf( "\n" );
}

EJEMPLO DE UN PROGRAMA
Escribamos un programa que calcule las races de una ecuacin de segundo
grado:
ax 2 + bx + c = 0

61

Las races pueden ser reales o imaginarias. Los coeficientes a, b y c sern


reales y se leern del teclado. El programa tendr en cuenta los siguientes
casos:

Si a, b y c son iguales a cero: Se considera la ecuacin no vlida.


Si a y b son iguales a cero la ecuacin es imposible.
Si a es igual a cero: Una nica raz.
Si a, b y c distintas de cero: races reales o imaginarias.

En este ltimo caso las races se calculan por:


raices =

b b 2 4ac
2a

Se utilizaran los siguientes procedimientos: Procedimiento de lectura de los


coeficientes y funcin para el clculo del discriminante (b2-4ac)
Programa:
/********************************************************************
* Programa: Races
*
* Descripcin:
*
Este programa calcula las races de una
*
ecuacin de segundo grado: ax2 + bx + c
********************************************************************/
# include <stdio.h>
# include <math.h>
/**Funcin para calcular el discriminante */
float Discriminante( float a, float b, float c ) {
return b*b - 4.0*a*c;
}
/**Procedimiento de lectura de un coeficiente */
void LeerValor( int grado, float & valor ) {
printf( Coeficientes de grado %1d?, grado );
scanf( %f, &valor );
}

62

/**Programa principal */
int main() {
float valorA, valorB, valorC; /* Coeficientes de la ecuacin */
float parteUno, parteDos;
/* Variables intermedias de clculo */
float valorD;
/* Discriminante de la ecuacin */
LeerValor( 2, valoraA );
LeerValor( 1, valoraB );
LeerValor( 0, valoraC );
if (valorA == 0.0) {
if (valorB == 0.0) {
if (valorC == 0.0) {
printf( Ecuacin no vlida\n );
} else {
printf( Solucin imposible\n );
}
} else {
printf( Raiz nica = %10.2f\n, -valorC/valorB );
}
} else {
parteUno = - valorB/(2.0*valora);
valorD = Discriminante( valorA, valorB, valorC );
if (valorD >= 0.0) {
parteDos = sqrt (valorD)/(2.0*valorA)
printf( Races reales :\n );
printf( %10.2f y \n, parteUno+parteDos );
printf( %10.2f \n, parteUno-parteDos );
} else {
parteDos = sqrt (-valorD)/(2.0*valorA)
printf( Races complejas :\n );
printf( Parte real=
%10.2f y\n, parteUno );
printf( Parte imaginaria = %10.2f \n, parteDos );
}
}
}

63

Resultado

Solucin a la ecuacin: x2 + 2x + 2 = 0
Coeficientes de grado 2? 1.0
Coeficientes de grado 1? 2.0
Coeficientes de grado 0? 2.0
Races complejas :
Parte real=
-1.00 y
Parte imaginaria =
1.00

64

METODOLOGA DE DESARROLLO DE PROGRAMAS (III)


OPERACIONES ABSTRACTAS
Una abstraccin es una visin simplificada de una cierta entidad, de la que
solo consideraremos sus elementos esenciales, prescindiendo en lo posible de
los detalles. Las entidades que podemos abstraer para materializarlas como
subprogramas son, en general, operaciones (accin o funcin).
Al plantear las operaciones abstractas se definen dos posibles visiones: la
visin abstracta o simplificada, que es cuando se usa dicha operacin sin ms
que conocer qu hace dicha operacin. La visin detallada o completa es la
que define cmo se hace dicha operacin, y permite que el procesador la
ejecute. La primera visin representa el punto de vista de quienes han de
utilizar la operacin (especificacin o interfaz de la operacin), y la visin
detallada representa el punto de vista de quien ha de ejecutar dicha accin
(realizacin o implementacin). Resumiendo:
Especificacin: Qu hace la operacin (punto de vista de quien la
invoca).
Realizacin: Cmo se hace la operacin (punto de vista de quien lo
ejecuta).
La forma ms sencilla de especificacin o interfaz consiste simplemente en
indicar cul es el nombre de la operacin y cules son sus argumentos, en C
es una cabecera de subprograma. La especificacin completa debe establecer
tambin cual es la semntica o significado de la operacin, esto se consigue
mediante los comentarios que describen la relacin entre los argumentos y el
resultado de la operacin.
La realizacin o implementacin ser la definicin completa del
subprograma, en forma de bloque de cdigo.
As por ejemplo, si tenemos una funcin que calcule el mximo y el mnimo,
su especificacin y realizacin ser:

65

int Maximo2( int a, int b ) {


/* Maximo2(a, b) es el mximo de a y b */
int (a > b) {
return a;
} else {
return b;
}

Especificacin:

Sintaxis
Semntica

Realizacin

Si sabemos solo la especificacin podemos invocar la funcin cuando la


necesitemos. Por ejemplo, podemos escribir:
alturaTotal = Maximo2( altura1, altura2 );
Pero Maximo2 se invocara igual si la funcin tuviera la misma
especificacin pero distinta realizacin:
int Maximo2( int a, int b ) {
/* Maximo2(a, b) es el mximo de a y b */
int m;
m = a;
if (b > m) {
m = b;
}
return m;

Especificacin:

Sintaxis
Semntica

Realizacin

Esto evidencia que la especificacin es una visin abstracta de lo que hace la


funcin, con independencia de los detalles de cmo lo hace. Las reglas de
visibilidad de C permiten usar subprogramas como operaciones abstractas,
con ocultacin de los detalles de realizacin.
Hay que hacer constar que si describimos la semntica de la operacin en un
lenguaje humano solo es una especificacin informal. Si se necesita un mayor
66

rigor se puede usar un lenguaje lgico-matemtico para especificar


formalmente las condiciones que relacionan los datos de entrada y los
resultados. As la especificacin formal del mximo de dos valores podra
ser:
Maximo2(a, b) = (a b a|b)

Funciones. Argumentos
En programacin la idea de funcin surge al aplicar el concepto de
abstraccin a las expresiones aritmticas. Si buscamos que el concepto de
funcin en programacin se aproxime al concepto matemtico de funcin, el
paso de argumentos siempre debe ser por valor.
As para conseguir una transparencia referencial, es decir que una funcin
devolver siempre el mismo resultado cada vez que se invoque con los
mismos argumentos, es necesario que la funcin no utilice datos exteriores a
ella, es decir, si NO emplea:

Variables externas al subprograma a las que accede directamente por


nombre, de acurdo con las reglas de visibilidad de bloque.
Datos procedentes del exterior, obtenidos por sentencias de lectura.
Llamadas a otras funciones o procedimientos que no posean
transparencia referencial. Las sentencias de lectura son en realidad
un caso particular de este.

Las funciones que cumplen la transparencia referencial se denominan


funciones puras.

Acciones abstractas. Procedimientos


Al igual que las funciones, los procedimientos pueden ser considerados como
acciones abstractas, igualmente parametrizadas. Un procedimiento
representa una accin, que se define por separado, y que se invoca por su
nombre. Como accin abstracta podemos tener una visin abstracta o
especificacin y la parte de realizacin. Por ejemplo la accin abstracta de
intercambiar los valores de dos variables es:

67

void Intercambiar( int & a, int & b ) {


Especificacin:

/* (a,b) = (b, a) */

Sintaxis
Semntica

int aux;
aux = a;
a = b;
b = aux;
}

Realizacin

En algn fracmento del programa podemos utilizarlo:


if (p > q) {
intercambiar( p, q )
}
Sin tener que saber cul es la realizacin del procedimiento intercambiar.
En los procedimientos es ms normal pasar los argumentos por referencia
que por valor, por ello se recomienda que siempre se utilicen procedimientos
puros, para ello en su realizacin NO utiliza:

Variables externas al subprograma, a las que se accede directamente


por nombre, de acuerdo con las reglas de visibilidad de bloques.
Llamadas a otros subprogramas que no sean procedimientos o
funciones puras.

Comparada con las funciones se ha quitado la restriccin que el


procedimiento no lea datos del exterior.

DESARROLLO USANDO ABSTRACCIONES


La metodologa de programacin estructurada puede ampliarse con la
posibilidad de definir operaciones abstractas mediante subprogramas. A
continuacin se describen dos estrategias de desarrollo diferentes, segn que
se escriba primero, si la definicin de los subprogramas, o el programa
principal que los utiliza.

68

Desarrollo descendente
La estrategia del desarrollo descendente (Top-Down), es simplemente el
desarrollo de refinamientos sucesivos, teniendo en cuenta adems la
posibilidad de definir operaciones abstractas. En cada etapa de refinamiento
habr que considerar la posibilidad de optar por cada una de las siguientes
opciones:

Considerar operaciones como operacin terminal, y codificarla


mediante sentencias del lenguaje de programacin.
Considerar la operacin como operacin compleja, y
descomposicin en otras ms sencillas.
Considerar como operacin abstracta, y especificarla, escribiendo
ms adelante el subprograma que la realiza.

Para decidirse por una operacin abstracta habr que analizar las ventajas que
conlleva, como alguna de las siguientes:

Evitar mezclar en un determinado fragmento de programa


operaciones con un nivel de detalle diferente.
Evitar escribir repetidamente fragmentos de cdigo que realicen
operaciones anlogas.

La realizacin de ciertas operaciones como subprogramas independientes


facilita lo que se llama la reutilizacin de software. Si la operacin
identificada como abstracta tiene cierto sentido en s misma, es muy posible
que resulte de utilidad en otros programas, adems de en aquel para el cual se
ha desarrollado.

Desarrollo ascendente
El desarrollo ascendente (Bottom-Up), consiste en ir creando subprogramas
que realicen operaciones significativas de utilidad para el programa que se
intenta construir, hasta que finalmente sea posible escribir el programa
principal, de manera relativamente sencilla, apoyndose en los subprogramas
desarrollados hasta ese momento.

69

La tcnica tiene una cierta analoga con el desarrollo de subprogramas


pensados en una reutilizacin posterior.

PROGRAMAS ROBUSTOS
La correccin de un programa exige que los resultados sean los esperados,
siempre que el programa se ejecute con los datos de entrada aceptables.
Ahora hay que plantearse cul va ser el comportamiento del programa si los
datos son incorrectos? Para ello se busca un programa robusto, es decir que
la operacin del mismo se mantenga controlada aunque se suministren datos
errneos.
La postura ms cmoda es declinar toda responsabilidad por parte del
programador, si se suministran datos errneos, sin embargo esta postura no es
admisible en la prctica, para ello se emplea la programacin a la defensiva
(defesive programming), que consiste en que cada programa o subprograma
est escrito de manera que desconfe sistemticamente de los datos o
argumentos con que se le invoca y devuelva siempre como resultado:
(a) El resultado correcto, si los datos son admisibles, o bien
(b) Una indicacin precisa del error, si los datos no son admisibles.
Lo que no hace nunca es devolver un dato como si fuera correcto, cuando en
realidad es errneo, ni abortar, ya que es mucho ms difcil encontrar
donde se ha producido el error.
As pues ante la posibilidad de la existencia de errores en los datos con que se
opera, hay que considerar dos actividades diferentes:
1. Deteccin de la situacin de error.
2. Correccin de la situacin de error.
El modelo denominado modelo de terminacin, detecta el error en una
seccin o bloque del programa, la accin de tratamiento del error remplaza al
resto de acciones pendientes de dicha seccin, con lo cual tras la accin
correctora se da por terminado el bloque.

70

En el lenguaje C, existen sentencias especiales para el manejo de


excepciones. As un subprograma desarrollado siguiendo el modelo de
terminacin podra programarse de la siguiente manera:
void Oparacion ( argumentos)
......
accion1
if ( error1 ){
throw excepcion1 /* Terminacin con excepcion1 */
}
accion2
if ( error2 ){
throw excepcion2 /* Terminacin con excepcion2 */
}
....
}

La sentencia throw provoca la terminacin del subprograma de manera


semejante a la sentencia return. Sin embargo, ambas terminaciones son
distintas, con return se realiza una terminacin normal y con throw se
realiza una terminacin con excepcin. La sentencia throw puede devolver
cualquier resultado en excepcin, adems es la encargada de detectar la
situacin de error (actividad1) y lanzar el mensaje de tratamiento de
excepciones. Quien utiliza el subprograma ser el encargado de realizar la
correccin de la situacin de error (actividad2).
Por ejemplo, en el subprograma que calcula la factorial:
int FactorialRobusto(int n) {
int f = 1;
if (n < 0) {
throw 0;
}
for (int k = 2; k <= n; k++) {
if (f > INT_MAX/k) {
throw k;
}
f = f * k;
}
return f;
}

71

Vemos que podemos detectar que se ha producido un exceso en la capacidad,


ya que se ha pedido la factorial de un nmero cuyo valor provoca un
overflow, el programa devuelve el valor de ese nmero (throw k). Y por otro
lado lanza una excepcin de valor cero cuando se solicita la factorial de un
nmero negativo (throw 0).
Con las herramientas convencionales de un lenguaje de programacin, el
esquema tpico de excepciones sera el siguiente:
Algoritmo del Problema (inicio);
OperacionRobusta ( argumentos );
if (Excepcion) {
Tratamiento de la Excepcin
}
Algoritmo del problema (continuacin)
Este esquema tiene el inconveniente de que hay que insertar el tratamiento de
la excepcin dentro del cdigo del algoritmo del problema que se est
resolviendo, con lo que se pierde claridad. Existen unas sentencias en C
para el manejo de excepciones que permiten separar ambos cdigos:
try {

Algoritmo del Problema (inicio);


OperacionRobusta ( argumentos );
Algoritmo del problema (continuacin)
}
catch (Excepcion) {

Tratamiento de la Excepcin
}
La sentencia try agrupa el cdigo sin tener en cuenta las excepciones y catch
agrupa el cdigo de tratamiento de la excepcin que se declara entre
parntesis.

72

DEFINICIN DE TIPOS
TIPOS DEFINIDOS
Uno de las ventajas de los lenguajes de alto nivel es que el programador tiene
la posibilidad de definir sus propios tipos de datos. Los tipos predefinidos
int, char y float, en la mayora de los casos no se consigue que la
informacin que maneja el ordenador tenga un significado especfico. As el
tipo establece los posibles valores que puede tener un dato. Adems asociado
al tipo viene determinada una serie de operaciones que se pueden realizar con
l. Por lo tanto, la definicin de tipos supone crear un nuevo nivel de
abstraccin dentro del programa.
En C la declaracin de tipos se realiza dentro de las Declaraciones del
programa principal o en cualquiera de sus procedimientos y funciones. La
declaracin de tipos se inicia con la palabra clave typedef, antes del nombre
o identificador, se debe enumerar a que tipo, ya definido, es equivalente o
sinnimo:
typedef Identificador de tipo Identificador de tipo nuevo;
Por ejemplo
typedef int TipoEdad;
typedef char TipoSexo;
typedef float TipoAltura;
Esto quiere decir que TipoEdad, por ejemplo, ahora tiene las caractersticas y
operaciones que se pueden hacer un tipo entero (int), es decir que la edad es
positiva y no puede ser superior a un valor determinado o que el TipoSexo
solo puede tomar unos determinados valores.
Una vez definidos los nuevos tipos, es necesario declarar las variables
asociadas a los mismos, as se podran utilizar los tipos annimos anteriores
para declarar las variables edad1, edad2, sexo y altura, por ejemplo:
Tipoedad edad1, edad2;
TipoSexo sexo;
73

TipoAltura altura;
edad2 = edad1 + 5;
sexo = "V";
altura = 1.75;
El tipo sinnimo, as definido permite utilizar slo tipos con nombres
propios. Por ejemplo:
typedef int entero;
typedef char character;
typedef float real;
A partir de ese momento los tipos nuevos: entero, caracter y real, sustituyen a
los predefinidos por el lenguaje.

TIPO ENUMERADO
Aparte de los valores bsicos en C se pueden definir y utilizar nuevos
valores simblicos de la manera que se indica a continuacin.
El tipo enumerado se define detrs de la palabra clave enum mediante un
identificador de tipo y a continuacin se detalla la lista con los valores
separados por comas (,), y encerrados entre llaves {}. Cada posible valor
se describe mediante una identificacin. Estos identificadores al mismo
tiempo quedan declarados como valores constantes.
typedef enum Identificador de tipo nuevo { identificador1, identificador2, identificador (N-1)};

Por ejemplo:
typedef enum TipoDia {
Lunes, Martes, Miercoles, Jueves,
Viernes, Sabado, Domingo
};
typedef enum TipoMes {
Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio
Agosto, Septiembre, Octubre, Noviembre, Diciembre
};
74

typedef enum TipoOrientacin { Norte, Sur, Este, Oeste }


typedef enum TipoColor { Rojo, Amarillo, Azul }
La enumeracin implica un orden que se establece entre los valores
enumerados, as el primer valor de la lista ocupa la posicin 0, el siguiente la
1, y as sucesivamente hasta el ltimo, que ocupa la posicin N-1, donde N es
el nmero de elementos enumerarles.
Los tipos enumerados se emplean de manera similar a los tipos predefinidos.
El identificador de tipo se puede emplear para definir variables de ese tipo, y
los identificadores de los valores enumerados se emplean como constantes
con nombre. As usando las anteriores definiciones podemos escribir:
TipoDia diaSemana;
TipoColor colorCoche = Rojo;
TipoMes mes;
diaSemana = Lunes;
colorCoche = Azul;
mes = Marzo;
Puesto que entre los valores enumerados existe un orden definido, podemos
emplear con ellos los operadores de comparacin para programasr sentencias
del tipo:
if (mes >= Julio) { }
while (diasemana > Sabado) { }
if (colorCoche = Rojo) { }
Si empleamos la notacin int(e) se obtiene la posicin de un valor en la lista
de valores del tipo. Por ejemplo:
int (Martes) == 0
int (Dicienbre) == 11
La operacin inversa que permite conocer qu valor enumerado ocupa una
determinada posicin, se consigue mediante la notacin:
75

TipoEnumerado (N)
Por ejemplo:
TipoOrientacion(3) == Este
TipoMes (3) == Abril

TIPO PREDEFINIDO BOOL


En C existe un tipo predefinido bool, que es similar a la siguiente definicin
de un tipo enumerado:
typedef enum bool {false, true}
Esta definicin no es necesaria ya que est implcita en el lenguaje. El
nombre bool es el identificador del tipo y las constantes false y true
corresponden a los valores de verdad falso y cierto respectivamente. Como
tipo ordinal se cumple:
int (false) == 0
int (true) == 1
Ahora se pueden declarar variables de este tipo y utilizarlas, Por ejemplo:
bool bisiesto;
bisiesto = (anno % 4) == 0; /* vlido entre 1901 y 2099 */
Adems podemos definir operaciones entre ellas:
Operacin lgica
Conjuncin (A y B)
Disyuncin (A o B)
Negacin (no A)

Operador C
&&
||
!

Esto permite formar expresiones y sentencias tales como la siguiente:


if (bisiesto && (mes > Febrero)) {
totalDias = totalDias + 1; }
76

Los resultados de las expresiones lgicas para los distintos operandos y


operadores son los siguientes:
a
true
true
false
false

b
true
false
true
false

a && b
true
false
false
false

a||b
true
true
true
false

!a
false
false
true
true

El tipo booleano, como cualquier otro tipo enumerado, se puede pasar como
argumento de un procedimiento o funcin y puede ser devuelto como
resultado de una funcin. Las funciones cuyo resultado es un valor booleano
se denominan predicados.

TIPOS ESTRUCTURADOS
Un tipo estructurado de datos o estructura de datos, es un tipo cuyos valores
se construyen agrupando datos de otros tipos ms sencillos. Los elementos de
informacin se integran un valor estructurado se denominan componentes. A
continuacin veremos una primera aproximacin a estos tipos estructurados
formacin (vector) y tupla.

TIPO VECTOR
Un vector (Fig. 15) est constituido por una serie de valores, todos ellos del
mismo tipo, a los que un nombre comn que identifica a toda la estructura
globalmente.
Nombre comn
ndice
Elementos

V0

V0

V0

Vector

n-3

n-2

n-1

Vn-3

Vn-2

Vn-1

Fig. 15 Estructura vector

77

Cada valor concreto dentro de la estructura se distingue por su ndice o


nmero de orden que ocupa en la serie. C utiliza el convenio por el que el
ndice del primer elemento es cero
Esta estructura es anloga al concepto matemtico de vector:
V = (V0, V1, V2, V3,, Vn-2, Vn-1)

Declaracin de vectores
La estructura de tipo vector se declara de la siguiente forma:
typedef TipoElemento TipoVector[NumeroElementos]
TipoVector es el nombre del nuevo tipo vector que se declara y
NumeroElementos es un valor constante que indica el nmero de elementos
que constituyen un vector. TipoElemento corresponde al tipo de dato de cada
uno de los elementos del vector y puede ser cualquier tipo de dato
predefinido del lenguaje o definido por el programador. Los siguientes
ejemplos utilizan tipos predefinidos y algunos tipos definidos por
enumeracin:
typedef enum TipoDia {
Lunes, Martes, Miercoles, Jueves,
Viernes, Sabado, Domingo
};
typedef enum TipoColor { Rojo, Amarillo, Azul };
typedef float TipoMedidas[3];
typedef TipoColor TipoPaleta[5];
typedef char TipoCadena[30];
typedef TipoDia TipoAgenda[7];
typedef bool TipoEstados[8];
typedef int TipoVector[10];
En ocasiones el tamao de un vector es un parmetro del programa que
podra tener que cambiarse al adaptarlo a las nuevas necesidades. Por
ejemplo, estas constantes podran haber sido declaradas previamente de la
siguiente forma:

78

const int
const int
const int
const int

NumeroEstados = 8;
LongitudAgenda = 7;
NumeroLetras = 30;
NumeroElementos = 10;

typedef char TipoCadena[NumeroLetras];


typedef TipoDia TipoAgenda[LongitudAgenda];
typedef bool TipoEstados[NumeroEstados];
typedef int TipoVector[NumeroElementos];
As, el programa queda parametrizado por estas constantes. Para poder
utilizar los tipos declarados es necesario declarar a su vez, posteriormente, las
correspondientes variables:
TipoAgenda agendaUno, agendaDos;
TipoCadena frase;
TipoEstados estadoMotor; estadoPanel;
TipoVector vectorUno, vectorDos;

Inicializacin de un vector
Para inicializar un vector hay que hacerlo en todos los elementos, para ello la
notacin es algo especial y en ella se indica el valor inicial de todos los
elementos agrupndolos entre llaves {} y separndolas con comas (,). A
continuacin se declaran algunas de las variables anteriores nuevamente
incluyendo su inicializacin.
TipoAgenda agendaUno = {
Lunes, Viernes, Domingo, Martes,
Martes, Martes, Sabado,
};
TipoEstados estadoMotor = {
true, false, true, true, false,
false, false,true
};
TipoVector vectorUno = { 12, 7, 34. -5, 0, 0, 4, 23, 9, 11 };
TipoVector miVector = { 1, 1, 1. 1, 1, 0, 0, 0, 0, 0 };

79

Operaciones con elementos de vectores


La mayora de las operaciones con vectores hay que realizarlas operando con
sus elementos uno por uno. La referencia a un elemento concreto de un
vector se hace mediante el nombre del vector seguido, entre corchetes, del
ndice del elemento referenciado. Por ejemplo:
vectorUno[0]
frase[13]
estadoMotor[5]
miVector{3]
Un elemento de un vector puede formar parte de cualquier expresin con
constantes, variables u otros elementos. Para estas expresiones se tendr en
cuenta el tipo de elementos del vector y las reglas de compatibilidad. Por
ejemplo:
miVector[3] = 3*vectorUno[0] + 2*vectorDos[0];
frase[13] = 'A';
estadoMotor[5] = true;

Operaciones globales con vectores


En otros lenguajes de programacin es posible realizar una asignacin global
de un vector a otro, siempre que estos sean compatibles entre s. Sin embargo
en C no existe esa posibilidad y la asignacin se tiene que programar
explcitamente mediante un bucle que realice la copia elemento a elemento:
for (int i = 0; i < NumeroElementos; i++) {
vectorDos[i] = vectorUno[i];
}
La condicin de terminacin (i < NumeroElementos) es bastante habitual
para recorrer formaciones con N elementos. Como los ndices van desde 0 a
N-1, la forma ms natural de expresar la condicin de terminacin sera
(ndice< N) as tendramos las siguientes posibilidades para la sentencia for
en la asignacin global:

80

for (int i = 0; i <= N; i++) {


vectorDos[i] = vectorUno[i];
}
for (int i = 0; i < N; i++) {
vectorDos[i] = vectorUno[i];
}
for (int i = 0; i >= N; i--) {
vectorDos[i] = vectorUno[i];
}

Paso de argumento de tipo vector


El paso de argumentos de tipo formacin (tipo vector) utilizados en
procedimientos o funciones, se realiza como paso por referencia. Por
ejemplo, si tenemos las siguientes declaraciones:
void LeerVector( TipoVector v ) { }
void ConocerEstado( TipoEstados e ) { }
Cuando se invocan estos procedimientos, hay que tener en cuenta que el
argumento real puede ser modificado, as en las siguientes invocaciones:
LeerVector( vectorUno );
ConocerEstado( EstadoMotor );
Las variables vectroUno y estadoMotor podran sufrir modificaciones al
ejecutar ej correspondiente procedimiento. En el resto de tipos por defecto el
paso de argumento es siempre por valor. Pero cuando se utilizan datos de tipo
formacin si no queremos que se modifiquen los parmetros reales en la
llamada del procedimiento deben ir precedidos de la palabra clave const, por
ejemplo:
void EscribirVector( const TipoVector v ) { }
void PintarEstado(const TipoEstados e ) { }
La invocacin, por ejemplo ser:

81

EscribirVector( vectorDos );
PintarEstado( estadoPanel );
Las variables vectorDos y estadoPanel permanecern inalteradas despus de
ejecutar en correspondiente procedimiento, es decir es como si hubiramos
pasado este argumento por valor.

VECTOR DE CARACTERES: CADENA (STRING)


Hasta ahora habamos definido constates tipo cadena de caracteres (string),
pero no habamos definido variables de este tipo. La razn es que las
variables cadena son de tipo vector. En C cualquier vector cuya declaracin
es:
typedef char Nombre[ N ];
Se considera una cadena (string), con independencia de su longitud
particular, es decir el valor de N. Su peculiaridad es:
Una cadena de caracteres (string) es un vector en que se pueden almacenar
textos de diferentes longitudes (si caben). Para distinguir la longitud til
en cada momento se reserva siempre espacio para un carcter ms, y se
hace que toda la cadena termine con un carcter nulo '\0' situado al
final.
Por ejemplo para declarar una cadena de un mximo de veinte caracteres se
hace de la siguiente forma:
typedef char Cadena20[21];
La declaracin de variables de este tipo se hace de la forma habitual:
TipoCadena idioma = "ingls";
Cadena20 nombre, apellido;
TipoCadena direccin = "Calle El de Pagan 44";
En este caso la declaracin de variables se realiza como las constantes
carcter, no elemento a elemento separados por comas como para el resto de
los vectores. Como se puede observar, en la inicializacin no hace falta
82

asignar una cadena con los diecinueve, veinte o treinta caracteres. Los
caracteres asignados ocupan posiciones seguidas desde el principio y al final
e coloca el carcter especial nulo '\0' que no se puede escribir. lo que nunca
se puede hacer es asignar ms caracteres de los declarados.
En C se dispone de una librera de cabecera <string.h> que facilita el
manejo de caracteres, la librera incluye una variedad de funciones y de ellas
las ms comunes son las siguientes:
strcpy( c1, c2 )
strcat( c1, c2 )
strlen( c1 )
strcmp( c1, c2 )

Copia c2 en c1
Concatena c2 a continuacin de c1
Devuelve la longitud de c1
Devuelve en resultado cero si c1 y c2
son iguales, menor que cero si c1
precede a c2 en orden alfabtico, y
mayor que cero si c2 precede a c1 en
orden alfabtico.

Como a cualquier tipo vector, no se pueden hacer asignaciones globales de


cadenas, pero se puede utilizar la funcin strcpy, tal como se muestra a
continuacin:
strcpy( apellido, "Suay" );
strcpy( nombre, apellido );
Esto equivaldra a las siguientes sentencias que seran errneas:
apellido = "Suay"; /* ERROR en C */
nombre = apellido; /* ERROR en C */
Las variables de tipo cadena se pueden utilizar como argumento de
procedimientos y funciones. Ms concretamente en el procedimiento de
escritura printf se pueden utilizar constantes o variables de cadena
empelando el formato de escritura %s. Por ejemplo:
printf("Datos: %s - %s : %s %s\n", idioma, direccion, "Juan", apellido)

Produce el resultado:

83

Datos: ingls Calle El de Pagan 44 : Juan Suay

Tambin se utilizan variables cadena en los procedimientos de lectura scanf,


utilizando tambin el formato de lectura %s. Por ejemplo el fragmento de
cdigo:
printf( "Nombre y Apellido? ");
scanf( "%s%s", nombre, apellido);
Un ejemplo de ejecucin en la pantalla sera:
Nombre y Apellido? Juan Suay

TIPO TUPLA
Otra forma de construir un dato estructurado es agrupar elementos de
informacin usando el esquema tupla o agregado. En este esquema, el dato
estructurado est formado por una coleccin de componentes, cada uno de los
cuales puede ser de un tipo diferente. Por ejemplo, una fecha es un conjunto
formado por los elementos da, mes y ao. Un punto en un plano cartesiano
se describe mediante dos nmeros que son sus coordenadas. El nombre
completo de una persona es la coleccin formada por si nombre de pila y sus
dos apellidos.
As podemos definir:
Tupla: Coleccin de elementos componentes, de diferentes tipos, cada uno de
los cuales se identifica por un nombre.

TIPO REGISTRO (STRUCT)


La estructura tupla se emplea en C definindolas como estructuras del tipo
registro (struct), que es una estructura de datos formada por una coleccin de
elementos de informacin llamados campos.
La declaracin de un tipo registro se hace utilizando la palabra clave struct
de la siguiente forma:

84

typedef struct Tipo-registro {


Tipo-campo-1 nombre-campo-1;
Tipo-campo-1 nombre-campo-2;

Tipo-campo-N nombre-campo-N;
};
Cada una de las parejas Tipo-campo y nombre-campo, separadas de punto y
coma (;), define un campo o elemento componente y su correspondeinte tipo.
Adems hay que tener en cuenta que la estructura acaba siempre con punto y
coma (;). Por ejemplo:
typedef enum TipoMes {
Enero, Febrero, Marzo, Abril, Mayo,
Junio, Julio, Agosto, Septiembre,
Octubre, Noviembre, Diciembre
};
typedef struct TipoFecha {
int dia;
TipoMes mes;
int anno;
};
typedef struct TipoPunto {
float x;
float y;
};
Para declarar variables de tipo registro es necesario haber realizado
previamente la definicin del tipo de registro, para a continuacin declarar las
variables:
TipoFecha ayer, hoy;
TipoPunto punto1, punto2;
Estas variables se pueden inicializar en la declaracin de uan manera
semejante a las formaciones agrupando los valores iniciales entre llaves {}
y separndolas por una coma (,), por ejemplo:

85

TipoFecha hoy = { 19, septiembre, 2013 };


TipoPunto punto1 = { 12.5, -76.9 };
Para operar con tipos estructurados, se puede llevar a cabo de dos maneras
distintas: operando con el dato completo o bien operar con cada campo por
separado. El caso de campo completo es muy limitado, la nica operacin
admisible es la asignacin, es decir que el valor de un dato tipo registro
puede asignarse directamente a una variable de su mismo tipo. Por ejemplo:
punto2 = punto1
Tambin es posible pasar como argumento un dato tipo registro a una funcin
o procedimiento. Por ejemplo podemos especificar los subprogramas
siguientes:
/* Leer el da, mes y ao */
void LeerFecha( TipoFecha & fecha ) {}
/* Distancia entre p1 y p2 */
float Distancia( TipoPunto pi, TipoPunto p2 ) {}
Las operaciones de tratamiento de estructuras consisten normalmente en
operar con sus campos por separado. La forma de hacer referencia a su
campo es mediante la notacin:
registro.campo
As podemos dar una posible definicin de los subprogramas anteriores:
/* Leer el da, mes y ao */
void LeerFecha( TipoFecha & fecha ) {
int aux;
scanf( "%d", &aux );
if (( aux > 0 ) && ( aux <= 31 )) {
fecha.dia = aux;
} else {
fecha.dia = 1;
}
LeerMes( fecha.mes );

86

scanf( "%d", &aux );


fecha.anno = aux
}

Tambin es posible devolver como resultado un valor estructurado de tipo


registro. Por ejemplo:
TipoPunto Puntomedio(TipoPunto p1, TipoPunto p2 ) {
TipoPunto m;
m.x = (p1.x + p2.x) / 2.0;
m.y = (p1.y + p2.y) / 2.0;
return m;
}
TipoPunto a, b, centro;
centro = PuntoMedio( a, b );

87

APLICACIN A ESTRUCTURAS DE CONTROL


ESTRUCTURAS COMPLEMENTARIAS DE ITERACIN
Repeticin: sentencia DO

Fig. 16 Repeticin

En ocasiones resulta ms natural comprobar la condicin que controla las


iteraciones al finalizar cada una de ellas, en lugar de hacerlo al comienzo de
las mismas. En este caso siempre se ejecuta al menos una interaccin (Fig.
17). El formato de la estructura de repeticin en C es:
do {
Accin
} while ( Condicin );
La Condicin es una expresin de tipo bool, de tal forma que si el resultado
es true se vuelve a ejecutar la Accin y cuando el resultado es false finaliza
la ejecucin de la estructura.
Una situacin tpica de esta estructura es el caso de que una vez ejecutada
una accin se pregunta al operador si dese continuar con una nueva. En todos
los acsos el programa ejecuta la primera iteracin y pregunta se se desea o no
realizar otra ms. Por ejemplo:

88

do {

Operacin

printf( "Otra operacin (S/N)?" );


scanf( " %c", &tecla )
} while ( tecla == 'S' );
Otro ejemplo de uso es cuando solamente son vlidos valores concretos de un
determinada respuesta. Si la respuesta no es correcta se solicitar de nuevo y
no se continuar hasta obtener una respuesta dentro de los valores vlidos.
Por ejemplo:
do {
printf( "Mes actual?" );
scanf( " %d", &mes )
} while ( (mes < 1) | | (mes >12 ));

Sentencia CONTINUE
La sentencia continue dentro de cualquier clase de bucle (while, for o do)
finaliza la iteracin en curso e inicia la siguiente iteracin. En ocasiones no
tiene sentido terminar la iteracin que se est realizando y resulta ms
adecuadoiniciar una nueva. Esto puede suceder cuando alguno de los datos
suministrados para realizar los clculos es errneo y puede dar una operacin
imposible (dividir por cero, raz de un nmero negativo, etc.). Por ejemplo
supongamos que tenemos un vector con N coeficientes por los que hay que
dividir al realizar un clculo salvo obviamente cuando uno de los coeficientes
sea cero. Esto se evitara de la siguiente manera:
for ( int i = 0; i < N; i++) {

if ( vectorCoeficientes[i] == 0) {
continue;
}

calculo = calculo / vectorCoeficientes[i];


}

89

La sentencia continue siempre estar incluida dentro de otra sentencia


condicional puesto que en caso contrario nunca se ejecutara parte de la
iteracin posterior a la sentencia continue.

ESTRUCTURAS COMPLEMENTARIAS DE SELECCIN


Sentencia SWITCH
Cuando la seleccin entre varias casos alternativos depende del valor que
toma una determinada variable o del resultado final de una expresin, es
necesario realizar comparaciones de la misma variable o expresin con todos
los valores que puede tomar, uno por uno, para elegir el camino a elegir. En
este caso estamos en lo que se denomina esquema de seleccin por casos de
la Fig. 18.

Fig. 18 Seleccin por casos

As en funcin de los valores de la expresin x ( v1, v2, v3, vN u otro


distinto) se ejecuta la accin a, B, C, H. As si el valor es v1 se ejecuta
accionA, si es igual a v2, v3 y v4, se ejecuta la accionB, si es v5 y v6 se ejecuta
la accinC. Se establece la accinH cuando no toma ningn de esos valores.
La sentencia switch es la que implementa esta estructura C. La sentencia
comienza con la palabra clave switch y a continuacin, entre parntesis, se
indica la expresin o variable cuyo valor determina los casos que se quieren
analizar, seguida del smbolo de abrir llave ({). para cada va de ejecucin
posible se detallan primeramente los valores que van a tomar la expresin
precedidos por la palabra clave case y seguido por dos puntos (:). la
correspondiente accin como una secuencia de sentencias se detalla a
90

continuacin de los correspondientes valores. Aunque es opcional, el Manual


de Estilo de C, establece que cada accin finaliza con la sentencia break.
La alternativa para el resto de valores posibles es opcional y va preferida de
la palabra clave default. La sentencia finaliza con el smbolo de cerrar llave
(}).
switch (expresin) {
case valor1:
accionA;
break;
case valor2:
case valor3:
case valor4:
accionB;
break;
case valor5:
case valor6:
accionC;
break;
....
default:
accionH;
}
Esta sentencia no se puede utilizar cuando la variable o resultado de la
expresin es de tipo float u otro tipo no simple.

EQUIVALENCIA ENTRE ESTRUCTURAS


Las estructuras bsicas estrictamente necesarias para la programacin
estructurada son la seleccin entre dos alternativas (if) y la iteracin (while).
Estas se pueden considerar estructuras primarias, el resto son estructuras
secundarias, ms complejas y tiene por objetivo lograr programas ms
sencillos en situaciones particulares. Siempre una estructura secundaria
puede expresarse en funcin de las primarias, sin embargo no siempre es
posible expresar una sentencia primaria en funcin de una secundaria.

91

Seleccin por casos


As dada la estructura:
switch (expresin) {
case v1 :
SentenciasA;
break;
case v2 :
case v3 :
case v4 :
SentenciasB;
break;
case v5 :
case v6 :
SentenciasC;
break;
....
default:
SentenciasH;
}
Se puede remplazar por la siguiente seleccin en cascada:
valor = Expresion;
if (valor == v1) {
SentenciasA;
} else if ((valor == v2) | | (valor == v3) | | (valor == v4)) {
SentenciasB;
} else if ((valor == v5) | | (valor == v6)) {
SentenciasC;
....
} else {
SentenciasH;
}

92

Bucle con contador


Esta estructura se puede hacer mediante un control explcito del contador del
bucle. Se la estructura siguiente:
for (int ndice = inicial; ndice <= Final; ndice++) {
Sentencias;
}
Cuando el incremento es positivo se puede realizar de la siguiente forma:
int indice = inicial;
while (indice <= Final) {
Sentencias;
ndice++
}

Repeticin
La sentencia do se puede transformar en while forzando la ejecucin
incondicional de la primera iteracin. La estructura:
do {
Sentencias;
while (Condicin);
}

Se puede convertir en esta otra:


Sentencias
while (Condicin) {
Sentencias
}
O bien:
seguir = true;
while (seguir) {
Sentencias
seguir = Condicin;
}

93

ESTRUCTURA DE DATOS
ARGUMENTOS DE TIPO VECTOR ABIERTO
Si un subprograma debe operar con un vector recibido como argumento,
necesita toda la informacin del tipo de dicho vector, es decir, el tipo y
nmero de sus elementos, por ejemplo en este fragmento de cdigo:
const int NumeroElementos = 10;
typedef int TipoVector[NumeroElementos];
void EscribirVector( const TipoVector v ) {}
TipoVector vectorUno, vector Dos;
EscribirVector ( vectorDos );
El cdigo EscribirVector puede redactarse con seguridad ya que se conoce
toda la informacin de tipo de argumento:
void EscribirVector( const TipoVector v ) {
for (int i = 0; i < NumeroElementos; i++) {
printf( "%10d", v[i] );
}
printf( "\n" );
}
Si ahora tenemos que trabajar con vectores de otro tamao, tenemos que
definir un nuevo tipo y, lamentablemente otro nuevo procedimiento de
escritura:
const int NumeroElementos = 100;
typedef int TipoVector[NumeroElementosLargo];
void EscribirVectorLargo( const TipoVectorLargo v ) {
for (int i = 0; i < NumeroElementosLargo; i++) {
printf( "%10d", v[i] );
}
printf( "\n" );
}
94

El procedimiento es el mismo, solo difiere en el nmero de elementos. Por lo


tanto para no tener que duplicar el cdigo, en la prctica se escribe un
procedimiento general de escritura de vectores de nmeros enteros, y pasar
como parmetro el tamao del vector. Para ello hace falta un mecanismo para
expresar que un argumento de tipo vector puede tener un tamao cualquiera,
es decir indefinido. Estos se denominan vectores abiertos. Para ello en la
declaracin de tipo vector se homite el tamao pero no los corchetes ([ ]).
Ejemplo:
void EscribirVectorAbierto( const int v [ ], int numElementos ) {
for (int i = 0; i < NumeroElementos; i++) {
printf( "%10d", v[i] );
}
printf( "\n" );
}
Ahora podemos usar este procedimiento para escribir vectores de cualquiera
de los tipos anteriores:
TipoVector vectorUno, vectorDos;
TipoVectorLargo vectorLargo;
EscribirVectorAbierto( vectorDos, NumeroElementos );
EscribirVectorAbierto( vectorLargo, NumeroElementosLargo );

FORMACIONES ANIDADAS. MATRICES


Las matrices son estructuras de tipo formacin (array) de dos o ms
dimensiones. Una menara de plantear la definicin de estas estructuras es
considerarlas como vectores cuyos elementos son a su vez vectores (o
matrices).
La Fig. 19 muestra la estructura de una matriz de dos dimensiones formada
por filas o columnas. All se representa la matriz como un vector cuyos
elementos son las filas de la matriz, que a su vez son vectores de elementos
de la matriz.

95

Fig. 19 Matriz de dos dimensiones

Usando directamente declaraciones de tipos vector podramos escribir la


declaracin de una matriz de nmeros enteros de la siguiente manera:
cont int NumFilas = 10;
cont int NumColumnas = 15;
typedef int TipoElemento;
typedef TipoElementp TipoFila[NumColumnas];
typedef TipoFila TipoMatriz[NumFilas];
Para designar un elemento de la matriz se usar un doble ndice:
TipoMatriz matriz;
matriz[3][5] = 27;
La declaracin puede simplificarse si se usa esta notacin:
cont int NumFilas = 10;
cont int NumColumnas = 15;
typedef int TipoElemento;
typedef TipoElemento TipoMatriz[NumFilas][NumColumnas];
TipoMatriz matriz;
96

Las operaciones con elementos individuales de una matriz pueden hacerse


directamente, de forma anloga a la operacin con variables simple de ese
tipo. En cambio las operaciones globales con matrices han de plantearse de
manera similar a las operaciones globales con vectores. En general habra que
operar elemento a elemento, o a lo sumo por filas completas.
Poe ejemplo para imprimir matrices del tipo declarado anteriormente
podramos escribir:
void EscribirMatriz( const TipoMatriz m ) {
for (int i = 0; i < NumFilas; i++) {
for (int i = 0; i < NumColumnas; i++) {
printf( "%10d", m[i][j] );
}
printf( "\n" );
}
Tambin se podra haber operado por filas completas:
void EscribirFila( const TipoFila f ) {
for (int i = 0; i < NumColumnas; i++) {
printf( "%10d", f[i] );
}
printf( "\n" );
}
void EscribirMatriz( const TipoMatriz m ) {
for (int i = 0; i < NumFilas; i++) {
EscribirFila( m[i] );
}
}
Con las matrices no se puede definir argumentos de tamao indefinido, como
ocurra con los vectores.

97

EL TIPO UNIN
Hay aplicaciones en las que resulta deseable que el tipo de un dato vare
segn las circunstancias Si las posibilidades de variacin son un conjunto
finito de tipos, entonces se pueden decir que el tipo de dato corresponde a un
esquema unin de los tipos particulares posibles. Cada uno de os tipos
particulares constituyen una variante o alternativa del tipo unin.
Las situaciones tpicas que se pueden aplicar los esquemas unin tenemos,
entre otras, las siguientes:

Datos que pueden representarse de diferentes maneras.


Programas que operan indistintamente con varias clases de datos.
Datos estructurados con elementos opcionales.

Por ejemplo un programa que opere con nmeros de distintas clases (entero,
fraccin, real) o dos sistemas de coordenadas (cartesianas o polares).
En C se define un tipo unin como una coleccin de campos alternativos,
de tal manera que cada dato particular solo usar uno de esos campos en un
momento dado, dependiendo de la alternativa aplicable. La definicin es
similar a la de un agregado o struct, usando ahora las palabras clave unin.
typedef struct TipoFraccin {
int numerador;
int denominador;
};
typedef union TipoNumero {
int valorEntero;
float valorReal;
TipoFranccion valorRacional;
};
La referencia a los elementos componentes se hace tambin como en los tipos
struct:

98

TipoNumero numero, otro, fraccion1, franccion2;


numero.valorEntero = 33;
otro.valorReal = float(numero.valorEntero);
fraccion2.valorRacional = fraccion1.valorRacional;
Si una variante de una unin es a su vez otra tupla o unin habr que usar
varios cuantificadores para designar los campos anidados:
fraccion1.valorRacional.numerador = 33;
fraccion1.valorRacional.denominador = 44;
Un ato tipo unin no mantiene es s mismo informacin de cul es la variante
activa en un momento dado. Para evitar esto se emplean los denominados
registros con variantes. Se trata de agregados o tuplas en la que hay una
coleccin de campos fijos aplicables en todos los casos y campos variantes
que se definen segn el esquema unin. Adems suele reservarse un campo
fijo para indicar explcitamente cul es la variante aplicable en cada
momento. A dicho campo se le llama discriminante. Por ejemplo:
typedef struct TipoFraccin {
int numerador;
int denominador;
};
typedef union TipoValor {
int valorEntero;
float valorReal;
TipoFranccion ValorRacional;
};
typedef struct TipoNumero {
ClaseNumero clase; /* discriminante*/
TipoValor valor;
};
void EscribirNumero( TipoNumero n ) {
switch (n.clase)
case Entero:
printf( "%d" , n.valor.valorEntero );
break

99

case Real:
printf( "%d" , n.valor.valorReal );
break
case Fraccin:
printf( "%d" , n.valor.valorRacional.numerador,
n.valor.valorRacional.denominador );
break
default
printf( "????" );
}

ESQUEMAS DE DATOS Y ESQUEMAS DE ACCIONES


Es interesante hacer notar la analoga que puede establecerse entre lso
esquemas de acciones y los esquemas de datos. Los esquemas bsicos de
acciones eran la secuencia, la seleccin y la iteracin. Estos esquemas se
corresponden, respectivamente con los esquemas de datos, tupla, unin y
formacin. La analoga se pone de manifiesto si describimos dichos
esquemas de forma generalizada comn a datos y acciones:

Tupla secuencia: Coleccin de elementos de tipos diferentes,


combinados en un orden fijo.

Unin Seleccin: Seleccin de un elemento entre varios posibles,


de tipos diferentes.

Formacin Iteracin: Coleccin de elementos del mismo tipo.

ESTRUCTURAS COMBINADAS
Como cualquier lenguaje de programacin actual se pueden combinar las
estructuras tupla, unin y formacin para definir estructuras de datos
complejas, exactamente como se combinan la secuencia, seleccin e iteracin
para construir el cdigo de acciones complejas.
Con las estructuras de datos se pueden definir estructuras cuyas componentes
son a su vez estructuras, sin lmite de complejidad de los esquemas de datos
resultantes.

100

Formas de combinacin
La manera de combinar las estructuras de datos es hacer que los elementos de
una estructura sean a su vez otras estructuras, sin limitacin en la
profundidad del anidamiento.

Tablas
En el esquema tabla, la estructura de datos puede plantearse como una
formacin simple de registros. En otros contextos se le da tambin el nombre
de diccionario o relacin. Los esquemas tabla son el fundamento de las bases
de datos relacionales.

101

ESQUEMAS TPICOS DE OPERACIN CON FORMACIONES


ESQUEMA DE RECORRIDO
Recorrido de vectores
El esquema de recorrido consiste en realizar cierta operacin con todos y
cada uno de los elementos de una formacin (en algunos casos con parte de
ellos). Estos esquemas de formacin se pueden aplicar a formaciones de
cualquier dimensin tales como matrices con dos o ms ndices, sim embargo
para facilitar la compresin las explicaciones se circunscriben al caso de un
vector (una dimensin).
La forma ms general se recorridos sera:
iniciar operacin
while (quedan elementos sin tratar) {
elegir uno de ellos y tratarlo
}
completar operacin
Si es aceptable tratar todos los elementos en el orden de sus ndices, el
esquema de recorrido se puede concretar como un bucle for, ms sencillo de
entender. Para el caso de un vector v (una dimensin) con un nmero N de
elementos:
cont int N = ;
typedef T_Elemento ;
typodef T_Elemento T_Vector[N];

iniciar operacin
for (int i = 0; i<N; i++) {
tartar v[i]
}
completar operacin
Por ejemplo esta funcin calcula la sume de los elementos de un vector
abierto de nmeros reales:
102

float SumaV( conts float v[], int N ) {


float suma = 0;
for (int i = 0; i<N; i++) {
suma = suma + v[i];
}
return suma;

Recorrido de matrices
Si la formacin es de tipo matriz se necesitan, para hacer el recorrido, tantos
for anidados como dimensiones tenga la formacin. El recorrido tpico es
cuando se quieren inicializar todos los elementos de la matriz. El siguiente
fragmento de cdigo inicializa todos los elementos de una matriz z de
nmeros enteros:
cont int N = ;
typedef int T_Matriz[N][N];
t_Matriz z;

for (int i = 0; i<N; i++) {


for (int j = 0; j<N; j++) {
z[i][i] = 0
}
}
Si se quieren escribir los valores de la matriz z por filas, se puede utilizar el
mismo esquema, en el que al finalizar cada fila se salta a una nueva lnea:
printf( "\n" )
for (int i = 0; i<N; i++) {
for (int j = 0; j<N; j++) {
printf( "%5d", z[i][i] );
}
printf( "\n" )
}

103

Si la matriz z es la multiplicacin de matrices x e y, esta se puede calcular:


for (int i = 0; i<N; i++) {
for (int j = 0; j<N; j++) {
z[i][j] = 0;
for (int k = 0; k<N; k++) {
z[i][j] = z[i][j] + x[i][k] * y[k][j];
}
}
}

Recorrido no lineal
En los ejemplos anteriores el ndice usado para controlar el bucle nos
sealaba directamente al elemento de procesar en cada iteracin. En ciertos
casos el elemento a procesar debe elegirse realizando ciertos clculos, y el
contador de iteraciones sirve fundamentalmente para contabilizar el avance
del recorrido y detectar el final del bucle.

Fig. 20 Recorrido no lineal. Construccin de un cuadrado mgico

Un ejemplo puede ser la construccin de un cuadrado mgico, en el que la


suma de los elementos de cada fila, columna y diagonal principal es siempre
la misma. Si el lado es impar, se puede construir rellenando las casillas con
nmeros correlativos empezando por el centro de la fila superior y avanzando
en diagonal hacia arriba y a la derecha. Al salir del cuadro por un lado pasa a
la casilla correspondiente del lado contario. Si la siguiente casilla en avance
diagonal ya est ocupada no se avanza, sino que se desciende a la casilla
104

inmediatamente debajo para continuar el recorridos. Para un cuadrado de


lado 3 el proceso se muestra en la Fig. 20. El fragmento de cdigo para
rellenar el cuadro de tamao N, suponiendo que previamente todas las
casillas tienen valor cero sera:
cont int N = ;
typedef int T_Matriz[N][N];
T_Matriz cuadro;
int fil, col;

fil = 0;
col = N/2;
for (int k = 1; k <= N*N; k++) {
cuadro[fil][col] = k;
fil = (fil+N-1) % N;
col = (col+1) % N;
if (cuadro[fil][col] ! = 0) {
fil = (fil+2) % N;
col = (col+N-1) % N;
}
}

BSQUEDA SECUENCIAL
En las operaciones de bsqueda secuencial se examinan uno a uno los
elementos de la coleccin para tratar de localizar los que cumplen una
determinada condicin. Si lo que queremos es encontrar todos los que
existan, estamos ante un recorrido como los mostrados en la seccin anterior.
Como ejemplo, si queremos determinar el nmero de Apariciones de un
cierto elemento buscado dentro de un vector v podemos utilizar el programa
de recorrido siguiente:
typedef T_Elemento ;
int Apariciones( T_Elemento buscado, cont T_Elemento v[], int N ) {
int veces = 0;
for (int i = 0; i < N; i++) {
if (v[i] == buscado) {
105

veces++;
}
}
return veces;
}
Si no queremos localizar todos los elementos que cumplen la condicin sino
solo uno de ellos, si lo hay, entonces no necesitamos recorrer la coleccin en
su totalidad. El recorrido se debe detener en cuanto se encuentre el elemento
buscado dentro de la coleccin. Ahora se utiliza:
iniciar operacin
while (quedan elementos sin tratar y no se ha encontrado ninguno aceptable) {
elegir uno de ellos y ver si es aceptable
}
completar operacin

El esquema de bsqueda secuencial se puede plantear:


typedef T_Elemento ;
int Indice( T_Elemento buscado, cont T_Elemento v[], int N ) {
int pos = 0;
while (po<N && v[pod]! = buscado) {
pos++;
}
if (pos >=N) {
pos = -1;
}
return pos;

INSERCIN
El problema que se plantea es insertar un nuevo elemento en una coleccin
de elementos ordenados, manteniendo el orden de la coleccin. Se supone
que los elementos estn almacenados en un vector, ocupando posiciones
desde el principio, y que queda algo de espacio libre al final del vector, (si el
vector estuviera lleno no se podran insertar nuevos elementos).
106

Fig. 21 Insercin de un nuevo elemento

La operacin se puede realizar de forma interactiva, examinando los


elementos empezando por el final hasta encontrar uno que sea inferior o igual
que se quiere insertar. Los elementos mayores que el que se quiere insertar se
van moviendo una posicin hacia adelante, con lo que quedando un hueco en
medio del vector. Al encontrar un elemento menor que el nuevo, se copia el
nuevo elemento en el hueco que hay en ese momento. La Fig. 21 muestra la
insercin en una coleccin de valores numricos enteros ordenados de
manera creciente, el nuevo valor 10 debe insertarse entre el 8 y el 11.
El esquema general del cdigo de la operacin sera:
Iniciar insercin
while ( ! Final && ! Encontrando hueco ) {
Desplazar elemento
Pasar al siguiente elemento
}
Insertar nuevo elemento
A continuacin se concreta el cdigo para la insercin de un nuevo elemento
entre los N elementos de un vector, que estn ordenados. Tras la insercin el
vector tendr un elemento ms:
typedef T_Elemento ;
void Insertar( T_Elemento v[ ], int N, T_Elemento elemento ) {
int j = N;
107

while (j > 0 && elemento < v[j-1]) {


v[j] = v[j-1]
j-}
v[j] = elemento;
}

ORDENACIN POR INSERCIN DIRECTA


El mtodo de ordenacin por insercin directa es uno de los mtodos ms
sencillos de ordenacin de los datos almacenados en un vector. As, por
ejemplo, se trata de ordenar un vector v de diez elementos (ndices de 0 a 9)
y que inicialmente est desordenado, tal como se muestra en la

Fig. 22 Vector inicial

Para comenzar, el primer elemento (21) est ya ordenado consigo mismo. A


continuacin, extraemos el segundo elemento (5) y se genera un hueco, que
se puede utilizar para ampliar la parte del vector ya ordenada. El mtodo de
ordenacin consiste en insertar el elemento extrado en su lugar
correspondiente entre los elementos ya ordenados. Este proceso se repite con
el tercero, cuarto, quinto,y decimo elemento hasta quedar ordenado todo el
vector. La secuencia de iteraciones se los sucesivos elementos del vector se
muestra en la Fig. 23.
En la secuencia se muestra con distinto fondo la posicin del hueco
inmediatamente antes de la insercin del siguiente elemento. El final de la
parte del vector ya ordenada se marca con una lnea de mayor grosor.
Adems, los nmeros ya ordenados tambin estn en negrita. La insercin se
realiza desde la posicin anterior al la del elemento extrado.
El cdigo de una posible realizacin se muestra a continuacin. La variable
valor guarda el elemento extrado de la posicin i. La ordenacin total del
vector se consigue mediante el recorrido de todos los elementos, desde el
segundo, para buscarles su hueco e insertarlos en l.
108

Fig. 23 Secuencia de inserciones para la ordenacin

tipedef T_Elemento ;
void Ordenar( T_Elemento v[ ], int N) {
T_Elemento valor;
int j;
for (int i = 1; i<N; i++) {
valor = v[i];
j = I;
while (j > 0 && valor < v[j-1]) {
v[j] = v[j-1];
j--;
}
109

v[j] = valor;
}
}

BSQUEDA POR DICOTOMA


Cuando estn los datos ordenados la bsqueda resulta mucho ms rpida, ya
que podemos comparar el elemento a buscar con el que est justo a la mitad
de los datos ordenados, podemos decidir si este es el elemento que buscamos
o debemos continuar buscando, pero solo en la mitad derecha o slo en la
mitad izquierda. El mismo proceso se ejecuta en la mitad elegida hasta que se
encuentra el elemento buscado o bien cuando la zona a examinar queda
reducida a un solo elemento (o ninguno), despus de sucesivas mitades.
Est bsqueda se denomina bsqueda por dicotoma, cuyo esquema general
es:
Iniciar operacin
while (quedan elementos por examinar y no se ha encontrado ninguno
aceptable) {
elegir el elemento central y ver si es aceptable
}
Completar operacin
Por ejemplo, realizaremos la bsqueda por dicotoma de un valor buscado en
un vector v de N elementos, ahora se necesitan las variables izq, dch y mitad
para acotar el trozo de bsqueda y la variable pos para almacenar el resultado
de la bsqueda o bien un valor negativo, -1 por ejemplo, si el elemento
buscado nos e encuentra:
typedef T_Elemento
int ndice( T_Elemento buscado, const T_Elemento v[ ], int N ) {
int iqz, dch, mitad, pos;
iqz = 0; dch = N-1; pos = -1
while (pos < 0 && izq <= dch) {
mitad = (izq + dch) / 2;
if ( v[mitad] == buscado) {
pos = mitad;
110

} else if (v[mitad] < buscado) {


dch = mitad 1;
} else {
Izq = mitad +1;
}
}
return pos;
}

SIMPLIFICACIN DE LAS CONDICIONES DE CONTORNO


La programacin de operaciones con vectores exige con frecuencia realizar
un tratamiento especial de los elementos extremos del vector o, en general, de
los elementos del contorno de una formacin. A continuacin se muestran
algunas tcnicas empleadas para evitar la necesidad de detectar de manera
explcita si se ha llegado a un elemento del contorno y/o realizar con l un
tratamiento especial.

Tcnica centinela
En el procedimiento de bsqueda es necesario comprobar en cada iteracin
una condicin doble: si no se ha alcanzado todava el final del vector, y si se
encuentra el elemento buscado. Esta doble condicin complica el cdigo y
supone un tiempo adicional de bsqueda. Si garantizamos que el dato
buscado est dentro de la zona de bsqueda, antes o despus se terminar
encontrndolo, y no ser necesario comprobar explcitamente si se alcanza el
final del vector.

Fig. 24 Vector con centinela situado al final

La manera de asegurar esto es incluir el dato buscado en el vector antes de


comenzar la bsqueda. El vector se amplia, segn se muestra en la Fig. 24,
con un elemento ms situado al final (si se hace la bsqueda hacia adelante) o
111

situado al principio (si se hace hacia atrs). En ese elemento adicional se


copia el dato a buscar antes de iniciar la bsqueda para que acte como
centinela (C) y asegure que la bsqueda nunca acaba de forma infrutuosa.
Ahora el esquema general de bsqueda se simplifica de la siguiente forma
iniciar operacin (colocar centinela)
while (no se ha encontrado un elemento aceptable) {
elegir otro elemento y ver si es aceptable
}
completar la operacin ( si se ha encontrado el centinela, indicar fallo en la
bsqueda)
El fragmento de programa de bsqueda queda:
typedef T_Elemento
int ndice( T_Elemento buscado, const T_Elemento v[ ], int N ) {
int pos = 0
v[N] = buscado; /*centinela*/
while (v[pos]!=buscado) {
pos++;
}
If (pos>=N) { /* lo que se encuentra es el centinela */
pos = -1;
}
return pos;
}
La tcnica del centinela se puede emplear en la ordenacin por insercin,
aprovechando que cada nuevo elemento a insertar entre los anteriores est
situado al final de la parte ordenada. Ahora la localizacin del lugar le
corresponde y el movimiento de los elementos posteriores para hacer hueco
se hacen por separado, y la bsqueda entre los elementos anteriores se hace
hacia adelante y no hacia atrs. La nueva redaccin del programa es la
siguiente.

112

tipedef T_Elemento ;
void Ordenar( T_Elemento v[ ], int N) {
T_Elemento valor;
int j;
for (int i = 1; i<N; i++) {
valor = v[i];
/*-- Buscar la nueva posicin del elemento, sin mover nada --*/
j = 0;
while (valor < v[j]) {
j--;
/*-- Mover elementos mayores y poner el elemento en su sitio --*/
for (int k = i-1; k>=j; k--) {
v[k+1] = v[k];
}
v[j] = v[k];
}
}

Matrices orladas

Fig. 25 Matriz orlada

113

Cuando se trabaja con matrices, tambin se puede simplificar las condiciones


de contorno utilizando matrices orladas. Estas matrices se dimensionan con
dos filas y dos columnas ms de las necesarias, tal como se muestra en la Fig.
25. Antes de operar con la matriz se inicializan las filas y columnas extra con
un valor de contorno (C) que permita simplificar la operacin de modo que el
tratamiento de los elementos del borde de la matriz original sea idntico al de
los dems.
Para ilustrar el usos de una matriz orlada supongamos que tenemos una
imagen en blanco y negro (escala de grises) de Ancho x Alto pixeles
almacenada en una matriz definida de la siguiente forma:
const int Ancho
const int Alto
const int Borde
const int Blanco
const int Negro

= 40; /* Anchura de la imagen */


= 20; /* Altura de la imagen */
= -1; /* Indicador de borde de la imagen */
= 0; /* Nivel bajo de grises = blanco */
= 5; /* Nivel alto de grises = negro */

typedef int Imagen_t[Alto+2][Ancho+2]


imagen_t imagen;
Los puntos de la imagen ocupan las columnas 1 a Ancho y las filas 1 a Alto.
Cada elemento de la matriz guarda el nivel de gris correspondiente a un
punto de la imagen. Las columnas 0 y Ancho+1 y las filas 0 y Alto+1 son
elementos extra de contorno. Para tratar cada punto de la imagen
individualmente basta hacer un recorrido completo, sin ninguna
complicacin. Por ejemplo, para contratar la imagen y reducir todos los
puntos de gris claro a blanco y todos los gris oscuro a negro, bastara escribir:
for (int i = 1; i<=Alto; i++) {
for (int j = 1; j<=Ancho; j++) {
if (imagen[i][j] <= nivel)
{
imagen[i][j] = Blanco;
} else {
imagen[i][j] = Negro;
}
}
}

114

En esta fraccin de cdigo no se ha usado el contorno de la matriz.


Supongamos ahora que queremos recortar la imagen, es decir eliminar los
puntos externos de la imagen que estn en blanco, pero dejando los puntos
blancos que son interiores y estn rodeados de puntos negros. El tratamiento
de cada punto exige examinar al mismo tiempo los puntos contiguos. El
programa se simplifica si se garantiza que en todo punto til de la imagen
tiene puntos contiguos en todas las direcciones, es decir, no hay situaciones
excepcionales en los bordes. Para ello aprovecha el contorno de la matriz, es
decir la orla, inicializndola con el valor que indicar los elementos del
borde.
for (int i = 0; i<=Alto+1; i++) {
imagen[i][0] = Borde;
imagen[i][Ancho+1] = Borde;
}
for (int i = 0; i<=Ancho+1; i++) {
imagen[0][i] = Borde;
imagen[Alto+1][i] = Borde;
}
Suponiendo que imagen est contrastada anteriormente y que su contorno
est inicializado al valor Borde, el recorte se realizar mediante sucesivos
recorridos de toda la matriz en los que los puntos blancos que tienen algn
punto Borde alrededor deben pasar tambin a puntos Borde. El proceso de
recorte termina cuando su recorrido completo de toda la imagen no hay
ningn punto que cambie. El fragmento de programa que realiza el recorte es
el siguiente:
bool fin;
do {
fin = true;
for (int i = 1; i<=Alto; i++) {
for (int j = 1; j<=Ancho; j++) {
if ((imagen[i][j] == Blanco) &&
((imagen[i-1][j] == Borde) | |
(imagen[i][j-1] == Borde) | |
(imagen[i][j+1] == Borde) | |
(imagen[i+1][j] == Borde))) {
115

imagen[i][j] = Borde;
fin = false
}
}
}
Esta forma de operar se denomina fuerza bruta, y puede ser poco eficiente
pero es muy sencilla de programar.

116

PUNTEROS Y VARIABLES DINMICAS


ESTRUCTURAS DE DATOS NO ACOTADAS
Hasta ahora hemos visto las estructuras de datos que pueden definirse en C.
Todas ellas tienen una caracterstica en comn: la capacidad total (nmero de
elementos componentes) se determina explcitamente al definirlas. Una
estructura podr usarse de manera que el nmero de componentes que
contengan informacin significativa sea variable, pero nunca mayor que el
tamao total de la estructura.
En ocasiones resulta til disponer de estructuras de datos que no tuvieran un
tamao fijado de antemano, sino que pudieran ir creciendo en funcin de los
datos particulares que estn manejando en cada ejecucin del programa.
Estas estructuras de satos se denominan, en general, estructuras dinmicas, y
posen la cualidad de que su tamao es potencialmente ilimitado, aunque,
naturalmente, no podr exceder la capacidad fsica del computador que
ejecute el programador.

LA ESTRUCTURA SECUENCIA
Las estructura secuencia pueden definirse como un esquema de datos del tipo
interactivo, pero con un nmero variable de componentes. La estructura
secuencia resulta parecida a una formacin con nmero variable de
elementos.
Existe en realidad diferentes esquemas secuenciales de datos, para describir
las distintas alternativas distinguiremos entre operaciones de construccin y
acceso, con las primeras podemos aadir o modificar el valor de las
componentes de la secuencia. Con las segundas podemos obtener o modificar
el valor de las componentes que existen en un momento dado.
Las operaciones de construccin pueden incluir:

Aadir o retirar componentes al principio de la secuencia.


Aadir o retirar componentes al final de la secuencia.
Aadir o retirar componentes en posiciones intermedias de la
secuencia.
117

Las operaciones de acceso pueden ser.

Acceso secuencias: Las componentes deben tratarse uno por una, en


el orden en que aparecen en la secuencia.
Acceso directo: Se pueden acceder a cualquier componente
directamente indicando su posicin, como en una formacin o
vector.

Cuando se trata de una secuencia, en particular en un acceso secuencial, su


tratamiento se realiza mediante un cursor. El cursor es una variable que
seala a un elemento de la secuencia. El acceso, insercin o eliminacin de
componentes de la secuencia se hace actuando sobre el elemento sealado
por el cursor. Dicho elemento lo representamos simblicamente como
cursor, empleando la flecha () como smbolo grfico para designar el
elemento de informacin sealado por otro. Para actuar sobre el cusor se
sulen plantear las siguientes operaciones:

Iniciar: Pone el cursor sealando al primer elemento.


Avanzar: El cursor pasa a sealar el siguiente elemento.
Fin: Es una funcin que indica si el cursor ha llegado al final de la
secuencia

El empleo del cursor se ilustra en la siguiente figura:

Fig. 26 Manejo de una secuencia mediante cursor

118

VARIABLES DINMICAS
Una manera de realizar estructuras de datos ilimitadas es mediante el empleo
de variables dinmicas. Una variable dinmica nos e declara como tal, sino
que se crea en el momento necesario, y se destruye cuando ya no se necesita.
Las variables dinmicas no tienen nombre, sino que se designan mediante
otras variables llamadas punteros o referencias.

Punteros
Un puntero o referencias en C son variables simples cuyo contenido es
precisamente una referencia a otra variable. El valor del puntero no es
representable como nmero o texto. En su lugar usaremos usa representacin
grfica que utilizaremos una flecha para enlazar una variable de tipo puntero
con la variable que hace referencia:

Fig. 27 Puntero y su variable apuntada

El tipo de un puntero especifica en realidad el tipo de variable a la que puede


apuntar. La declaracin es:
typedef Tipo de variable* Tipo-puntero;
Ahora se pueden declarar variables puntero de dicho tipo. Una variable
puntero se puede usar para designar la variable apuntada mediante la
notacin:
*puntero
Por ejemplo:
typedef int* Tp_Entero;
Tp_Entero pe;

119

*pe = 33;
printf( "%d", *pe );
Estas sentencias asignan el valor 33 a la variable dinmica sealada por el
puntero pe, y luego la imprimen. Para que las sentencias funcionen
correctamente es necesario que exista reamente la variable apuntada. Si el
puntero no seala realmente a la variable dinmica, el resultado de usar
*puntero ser imprevisible.
Para poder detectar si un puntero seala realmente o no a otra variable, existe
el valor especial NULL. Este valor debe ser asignado a cualquier puntero que
no seala a nadie, y normalmente se usar para inicializar lso punteros al
comienzo del programa. Por ejemplo:
if (pe != NULL) {
*pe = 33;
printf( "%d", *pe );
}

Uso de variables dinmicas


Las variables dinmicas no tienen espacio de memoria reservado de
antemano, sin que se crean a partir de punteros en el momento en que se
indique. Adems una vez creadas siguen existiendo incluso despus de que
termine la ejecucin del subprograma donde se crean. La forma ms encilla
de crear una variable dinmica es mediante el operador new:
typedef Tipo-de-variable* tipo-puntero;
Tipo-puntero puntero;
puntero = new Identificador_de_tipo;
La variable dinmica n tiene nombre, y solo se puede hacer referencia a ella a
travs del puntero. Podemos representar grficamente el efecto de esta
sentencia:

120

Fig. 28 Creacin de una variable dinmica

Las variables dinmicas, una vez creadas, siguen existiendo hasta que se
indique explcitamente que ya no son necesarias, en cuyo caso el espacio que
se haba reservado para ellas quedar otra vez disponible para crear nuevas
variables. Pare ello existe la sentencia delete, que permite destruri la variable
dinmica a la que seala el puntero:
delete puntero;
Una variable dinmica puede estar referenciada a ms de un puntero. Esto
ocurre cuando se copia un puntero en otro. Por ejemplo:
typedef int* Tp_Entero;
Tp_Entero p1, p2;
p1 = new int;
p2 = p1;
Grficamente ocurre lo siguiente:

Fig. 29 Copia de un puntero en otro

Tanto p1 como p2 apuntan a la misma variable dinmica.


Un problema delicado al manejar variables dinmicas es que pueden quedar
perdidas, sin posibilidad de hacer referencia a ellas. Esto ocurre en el
siguiente ejemplo:

121

typedef int* Tp_Entero;


Tp_Entero p1, p2;
p1 = new int;
p2 = new int;
p2 = p1;
En la figura siguiente se ve el resultado:

Fig. 30 Variable dinmica perdida

En este caso la variable creada p2 = new int; queda perdida, sin posibilidad
de ser usada, esto puede generar una prdida de capacidad de memoria en la
ejecucin del programa (memory leak), ya que la variable dinmica sigue
ocupando un espacio pero ya no est sealada por ningn puntero.

REALIZACIN DE SECUENCIAS MEDIANTE PUNTEROS


Los punteros son un elemento de programacin de muy bajo nivel, pero dado
que las estructuras de datos con tamao variable presentan muchas
complicaciones para manejarlas de manera eficiente, es frecuente que los
lenguajes de programacin prescindan de estas estructuras de datos de
tamao variable. Por esta razn se deben emplear los punteros, siempre que
se haga con cuidado y emplearlos de una manera precisa.
La definicin simblica de una estructura ilimitada basndose en esquemas
con un nmero fijo de elementos ser, normalmente, recursiva. Una
definicin recursiva es aquella e que se hace referencia a s misma. Sera
deseable que una sentencia ilimitada se pudiera definir de manera recursiva,
sin necesidad de punteros, de forma parecida a la siguiente:

122

typedef struct Tipo-secuencia {


bool vacia; /* indica si es la secuencia vaca */
Tipo-componente primero; /* slo si no es vaca */
Tipo-secuencia resto; /* slo si no es vaca */ /* ERROR*/
};
Esta definicin nos dice que una secuencia ilimitada de componentes es una
de dos cosas posibles: o bien una secuencia vaca, o bien una primera
componente seguida de la secuencia formada por el resto de las componentes.
Lamentablemente esta forma de definicin no es admisible en C. Para
definir una secuencia ilimitada tendremos que recurrir al empleo de variables
dinmicas y punteros. Una manera de hacerlo es usar punteros para enlazar
cada elemento d la secuencia con la siguiente tal y como se muestra en la
siguiente figura:

Fig. 31 Secuencia enlazada mediante punteros

Cada elemento de la secuencia se materializa como un registro con dos


campos. el primero contiene el valor de una componente, y el segundo es un
puntero que seala al siguiente. El ltimo elemento tendr el puntero al
siguiente con valor NULL. La secuencia completa es accesible a travs de un
puntero que seala el comienzo d ela misma.
Aplicado el esquema:
typedef struct Tipo-nodo {
Tipo-componente primero;
Tipo-nodo * resto;
};
typedef Tipo-nodo * Tipo-secuencia;

123

Una vez definidos los tipos de la secuencia y sus componentes se podrn


declarar variables de dichos tipos y operar con ellos:
Tipo-componente valor;
Tipo-secuencia secuencia, siguiente;
if ( secuencia != NULL) {
(*secuencia).primero = valor;
siguiente = (*secuencia).resto;
}
La combinacin del operador de desreferenciacin del puntero (*) y la
seleccin de campo de registro (.= es incomoda de escribir, porque reuiere
parntesis, y difcil de leer. Por esa razn C permite combinar ambos en un
operador nico con una grafa amigable (->). Las sentencias anteriores se
pueden poner de la forma siguiente:
if ( secuencia != NULL) {
secuencia->primero = valor;
siguiente = secuencia->resto;
}

Operaciones con secuencias enlazadas


Describiremos la manera de realizar algunas operaciones tpicas sobre
secuencias enlazadas con punteros.
DEFINICIN la definicin de la secuencia ser:
typedef struct TipoNodo {
int valor;
TipoNodo * siguiente;
};
typedef TipoNodo * TipoSecuencia;
TipoSecuencia secuencia;
RECORRIDO El recorrido de toda la secuencia se consigue mediante un
bucle de acceso a elementos y avance de cursor.

124

typedef TipoNodo * TipoPuntNodo


TipoPuntNodo cursor;
cursor = secuencia;
while (cursor != NULL) {
printf( "%5d", cursor->valor );
cursor = cursor->siguiente;
}
BSQUEDA La bsqueda en una secuencia enlazada ha de hacerse de
forma secuencial. La bsqueda es parecida al recorrido, pero la condicin de
terminacin cambiar, de hecho hay una doble condicin de terminacin: que
se localice el elemento buscado, y/o que se agote la secuencia. A
continuacin se presenta la bsqueda de la posicin en que ha de insertarse
un nuevo nmero en la secuencia ordenada. La posicin ser la que ocupe el
primer valor igual o mayor que el que se quiere insertar.
int numero; /* valor a buscar*/
TipoPuntNodo cursor, anterior;
cursor= secuencia;
anterior = NULL;
while (cursor != NULL && cursor->valor < numero) {
anterior = cursor;
cursor = cursor->siguiente;
}
Al salir del bucle cursor queda sealado al punto que debera insertarse el
nuevo elemento, y anterior seala al elemento que lo precede. Esto resulta
til para realizar luego operaciones de insercin o borrado, como se ver a
continuacin.
INSERCIN La insercin de un nuevo elemento se consigue creando una
variable dinmica para contenerlo, y modificando los punteros para enlazar
dicha variable dentro de la secuencia. El caso ms sencillo es insertar un
elemento detrs de uno dado. La representacin grfica se muestra en la
siguiente figura:

125

Fig. 32 Insercin de una secuencia de punteros

En la figura, el nuevo elemento creado tiene un fondo diferente, adems se ha


marcado con lnea discontinua los enlaces creados o modificados por estas
operaciones. El cdigo ser:
int numero; /* valor a insertar*/
TipoPuntNodo cursor, anterior, nuevo;
nuevo = new TipoNodo;
/* 1 paso */
nuevo->valor = numero;
nuevo->siguiente = anterior->siguiente; /* 2 paso */
anterior->siguiente = nuevo;
/* 3 paso */
BORRADO Para borrar un elemento hay que quitar el nodo que lo
contiene, enlazndolo directamente al anterior con el siguiente tal como se
indica en la siguiente figura:

Fig. 33 Borrado en una secuencia de punteros

126

Igual que antes se ha marcado don una lnea discontinua los enlaces
modificados por las operaciones de borrado. Adems, el elemento borrado
aparece con un fondo diferente. Para hacer el cdigo ms robusto se ha
forzado el cursor a valor nulo, ya que de no hacerlo as quedara apuntando a
un lugar que ya no existe (marcado con X en la figura). El cdigo ser:
TipoPuntNodo cursor, anterior;
anterior->siguiente = cursor->siguiente;
delete cursor;
cursor = NULL;

PUNTEROS Y PASO DE ARGUMENTOS


Paso de punteros como argumentos
Como cualquier dato, un puntero puede pasarse como argumento a un
subprograma. El cdigo del procedimiento y un ejemplo de cmo invocarlo
sera:
void ImprimirLista( TipoSecuencia lista ) {
TipoPuntNodo cursor = lista;
while (cursor != NULL) {
printf( %5d, cursor->valor );
cursor = cursor->siguiente;
}
printf( /n );
}
TipoSecuencia secuencia;
ImprimirLista( secuencia );
Por defecto los datos tipo puntero se pasan como argumentos por valor. Si se
desea usar un subprograma para modificar datos de tipo puntero, entonces
habr que pasar el puntero por referencia. Por ejemplo, si planteamos como

127

subprograma la operacin de bsqueda en un secuencia enlazada podramos


escribir:
void Buscar( TipoSecuencia lista, int numero,
TipoPuntNodo & curso, TipoPuntNodo & anterior ) {
cursor = lista;
anterior = NULL;
while (cursor != NULL && cursor->valor != numero) {
anterior = cursor;
cursor = cursor->siguiente;
}
}
TipoSecuencia secuencia;
TipoPuntNodo encontrado, previo;
int dato;
En la llamada a Buscar la variable secuencia no podr ser modificada, ya que
el argumento lista se pasa por valor. Por lo contrario, las variables de tipo
puntero encontrado y previo sern modificadas por el subprograma para
reflejar el resultado de la bsqueda.

Paso de argumentos mediante punteros


Desde un punto de vista conceptual el paso de un puntero como argumento
puede ser considerado equivalente a pasar como argumento la variable
apuntada.
Por ejemplo si queremos pasar como argumento una variable dinmica
podemos recurrir a un puntero como elemento intermedio para designarla.
Esto representa la dificultad aadida para entender cmo funciona un
determinado cdigo, con el correspondiente peligro que representa cometer
errores de codificacin. La dificulta estriba en que el hecho de pasar un
puntero por valor no evita que el subprograma pueda modificar la variable
apuntada. Por ejemplo:

128

typedef int* Tp_Entero;


void Imprimir(Tp_Entero val ) {
printf( %d, *val );
}
Void Incrementar(Tp_Entero val ) {
*val = *val + 1;
}
Tp_Entero p1;
p1 = new int;
Imprimir( p1 );
Incrementar( p1 );
Tanto el procedimiento Imprimir como el de Incrementar reciben un
puntero pasado por valor. El primero no modifica la variable apuntada pero el
segundo s.
En realidad los punteros se usan implcitamente para pasar argumentos por
referencia. Cuando se declara un argumento pasado por referencia, lo que
hace realmente el compilador es pasar un puntero a la variable externa usada
como argumento actual en la llamada. Dado el subprograma:
void Duplicar( int & valor ) {
valor = 2 * valor;
}
El cdigo generado por el compilador equivale a:
typedef int * p_int;
void Duplicar( int & valor ) {
*p_valor = 2 * (*p_ valor);
}

129

De hecho la notacin & para indicar el paso de argumento por referencia es


una mejora de C++ respecto a C, en este lenguaje ese operador corresponde
tambin al smbolo & como muestra a continuacin:
int numero
Duplicar( &numero)
Ahora queda claro porque algunas funciones estndar de C, como por
ejemplo scanf() se escriben:
scanf( %d, &numero );
Lo que est haciendo en esta llamada es pasar como argumento un puntero
que seala la variable nmero, a travs de la cual se puede modificar su valor.
Hay que hacer notar que el puntero en s se est pasando por valor, y eso es
equivalente a pasar el dato apuntado por referencia.
En bastantes casos cuandos e trabaja con variables dinmicas, que solo son
accesibles a travs de punteros, resulta natural usar un puntero explcito para
pasar como argumento la variable dinmica apuntada. Insistiremos en que el
paso del puntero equivale a pasar la variable apuntada siempre por referencia.
En C para pasar la variable apuntada por valor hay que hacerlo de la manera
convencional, como en el siguiente cdigo:
typedef int* Tp_Entero;
void Imprimir(Tp_Entero val ) { /* paso por valor*/
printf( %d, *val );
}
Void Incrementar(Tp_Entero val ) {
*val = *val + 1;
}

/* paso por referencia */

Tp_Entero p1;
p1 = new int;
Imprimir( p1 );
Incrementar( p1 );
130

PUNTEROS Y VECTORES EN C Y C++


En C/C++ existe una estrecha relacin entre las formaciones y los punteros,
pero desde el punto de vista metodolgico esta relacin resulta ambigua, por
esta razn en C se ha tratado ambos conceptos de manera expresa, pero
tiene la restriccin de impedir el manejo de punteros como formaciones
debido al carcter semntico de esta relacin, as el Manual de Estilo se
incorpora la regla que prohbe el uso de punteros como formaciones.
Se muestra a continuacin las analogas existentes en C/C++ entre punteros y
las formaciones.

Nombres de vectores como punteros


Cuando se ha declarado una variable de tipo vector el nombre de dicha
variable equivale en gran medida a un puntero que apunta al comienzo de un
vector

Paso de vectores como punteros


El paso de un valor de un puntero equivale al paso por referencia de la
variable apuntada.

Matrices y vectores de punteros


Si un puntero es anlogo aun vector, entonces un vector de punteros es
anlogo a una matriz.

Fig. 34 Matriz y vector de punteros a filas

131

TIPOS ABSTRACTOS DE DATOS


CONCEPTO DE TIPO ABSTRACTO DE DATOS (TAD)
En la programacin tradicional, se identificaba el concepto de tipo de dato
con el conjunto de valores que pueden tomar los datos de ese tipo. As por
ejemplo para operar con los valores correspondientes a los meses del ao,
podamos representarlos por nmeros (1 al 12) o bien mediante caracteres
(ENE, FEB, etc.), entre otras posibilidades. Un enfoque ms moderno de
programacin trata de asociar la idea de tipo de datos con la clase de valores,
abstractos, que pueden tomar los datos. Esto quiere decir que la
representacin o codificacin particular de los valores no cambia, de hecho,
el tipo de dato considerado.
El enfoque actual de la programacin se identifica los tipos de datos de forma
completamente abstracta, llegando a la idea de tipo abstracto de datos
(TAD). Esto quiere decir que un programa que use ese tipo de datos no
deber necesitar ningn cambio por el hecho de modificar la representacin o
codificacin de los valores de ese tipo. Si analizamos con cuidado qu se
necesita un programa para poder usar datos de este tipo, encontraremos que
hace falta:

Hacer referencia al tipo en s, mediante un nombre, para poder


definir variables, subprogramas, etc.
Hacer referencias a algunos valores particulares, generalmente como
constantes con nombre.
Invocar operaciones de manipulacin de los valores de ese tipo, bien
usando operadores en expresiones aritmticas o bien mediante
subprogramas.

El conjunto de todos estos elementos constituye el tipo abstracto de datos


(TAD):
Un tipo abstracto de datos (TAD) es una agrupacin de una coleccin de
valores y una coleccin de operaciones de manipulacin.
Es importante comprender que estas colecciones cerradas, es decir slo se
deben poder usar los valores adstratos y las operaciones declaradas para ese
132

tipo. Adems los detalles de cmo se representan los valores y cmo se


implementan las operaciones pueden estar ocultos para quien utiliza el tipo
abstracto. Esto no ocurra cuando se asociaba el tipo de valor con su forma de
representacin, por ejemplo si representamos los meses del ao mediante
nmeros, podemos usar el nmero 33, aunque no sea ningn mes vlido, de
igual forma que podemos multiplicar los meses sin que tenga sentido.
La programacin orientada a objetos se basa en este concepto, con la
diferencia que habla de clases y objetos en lugar de tipos y datos, y de
mtodos en lugar de operaciones.

REALIZACIN DE TIPOS ABSTRACTOS EN C


Definicin de tipos abstractos como tipos registro (struct)
Hasta ahora se ha visto que los tipos de registro permiten definir estructuras
con varios campos de datos con nombre y tipo individual. Ahora podemos
aadir otros elementos, en particular subprogramas, y distinguir entre
elementos pblicos y privados. Se pueden definir tipos abstractos de datos, ya
que:

Los campos de datos sirven para almacenar el contenido de


informacin como dato abstracto.
Los subprogramas permiten definir operaciones sobre esos datos.
La posibilidad de declarar ciertos elementos como privados permite
ocultar detalles de implementacin, y dejar visible solo el interfaz
del tipo abstracto.

As se puede declarar el tipo abstracto TipoPunto, correspondiente a un


punto del plano eucldeo. Cada punto se representa por sus coordenadas
cartesianas y se le asocian subprogramas para leer y escribir puntos
(coordenadas), y para calcular la distancia entre dos puntos.
typedef struct TipoPunto {
float x;
/* Coordenada x */
float y;
/* Coordenada y */
void Leer(); /**Leer un punto con formato (x, y) */
void Escribir(); /**Escribir un punto con formato (x, y) */
float Distancia ( TipoPunto p ); /** calcular la distancia de un punto a otro*/
};

133

Como se observa, los subprogramas correspondientes a las operaciones sobre


el tipo abstracto se declaran simplemente por su cabecera, porque lo que se
declara aqu es slo la interfaz de tipo. La notacin para referirse a las
operaciones es la misma que para los campos de datos, usando el punto (.)
como operador de cualificacin. El esquema de esta notacin y un ejemplo de
uso son:
variable.campo
Variable.operacion( argumentos )

Referencia a campo de datos


Referencia a operacin

TipoPunto p, q;
p.x = 3.3;
p.y = 4.4;
p.Leer();
p.Escribir():
printf( %f, p.Distancia( q ) );
Como complemento de la declaracin de la interfaz se necesita adems
definir la implementacin de las operaciones. Esta implementacin se hace
fuera de la declaracin del tipo registro, usando la notacin Tipo:: Operacin
como nombre de subprograma. El cdigo de la implementacin sera:
#include <stdio.h>
#include <math.h>
/* Excepcin en lectura */
type enum TipoPuntoError { PuntoNoLeido };
/** Leer un punto con formato (x,y) */
void TipoPunto: :Leer() {
int campos;
campos = scanf( ( %f , %f ), &x, &y );
if ( campos < 2) { /* comprobar que se ha ledo dos valores */
throw PuntoNoLeido;
}
}

134

/** Escribir un punto con formato (x,y) */


void TipoPunto: :Escribir( ) {
printf( (%f, %f), x,y );
}
/** Calcular la distancia de un punto a otro */
float TipoPunto: :Distancia( TipoPunto p ) {
float deltaX, deltaY;
deltaX = x p.x;
deltaY = y p.y;
return sqrt( deltaX*deltaX + deltaY*deltaY)
}

Una vez definido el cdigo abstracto se puede escribir el cdigo que lo use:
#include <stdio.h>
/* Definicin del tipo abstracto PUNTO*/
/* aqu se incluye el cdigo anterior */
/** Programa principal */
int main() {
TipoPunto a, b;
bool seguir = true;
while (seguir) {
try {
a.Leer();
b.Leer();
printf( Segmento: );
a.Escribir();
printf( )
b.Escribir();
printf( Logitud: %f\n, a.Distancia( b ) );
} catch (TipoPuntoError e) {
seguir = false;
}
}
}

El programa se plantea como un bucle indefinido que va leyendo pares de


puntos y calcula e imprime la longitud del segmento que definen. El
135

programa termina cuando se produce una excepcin en la lectura de un


punto, bien porque termina el fichero de datos de entrada o porque aparece un
dato que es una representacin vlida de un punto (x,y), con los parntesis y
la coma de separacin.

Ocultacin
Para que un tipo sea realmente abstracto hara falta que los detalles de
implementacin no fueran visibles. Los subprogramas, como mecanismo de
abstraccin, ya ocultan de talles de la realizacin de operaciones. Sin
embargo queda la cuestin de como ocultar la manera de representar los
valores del tipo abstracto.
Por ejemplo si se almacena el valor de una fecha como un tupla numrica
(da, mes, ao) no se puede admitir cualquier combinacin de valores. La
fecha (26, 2, 1961) es correcta, pero (2, 26, 1961) no lo es, y menos an (45, 5, 5210). si se quiere definir el tipo fecha como tipo abstracto ser necesario
ocultar los detalles de la representacin interna de los valores, de manera que
slo se puedan construir fechas usando operaciones que garanticen la
correccin de los valores del tipo.
Para permitir la ocultacin los tipos struct admiten l posibilidad de declarar
ciertos elementos componentes como privados, usando la palabra clave
private para delimitar una zona de declaraciones privadas dentro de la
estructura. La interfaz de TipoFecha podra ser:
typedef struct TipoFecha {
/* Dar valor a un dato fecha */
void Poner( int dia, int mes, int anno );
/* Obtener el contenido de un dato fecha */
int Dia();
int Mes();
int Anno();
private:
int dia, mes, anno;
};

136

Como contrapartida a ocultar los elementos internos de representacin de las


fechas, ha sido necesario aadir operaciones explicitas para asignar valor y
recuperar contenido de una fecha. Estas operaciones son bsicas, citaremos
algunas operaciones, como por ejemplo:
typedef struct TipoFecha {
void Leer();
void Escribir( cont char formato[ ] );
int DiasHasta( TipoFecga f );
void Incrementar (int das );
bool EsAnterior( TipoFecha f );
bool EsIgual( TipoFecha f );
bool EsCorrecta( );
TipoDiaSemana DiaSemana();
etc.
};

METODOLOGA BASADA EN ABSTRACCIONES


La tcnica de programacin estructurada, basada en refinamientos sucesivos,
puede ampliarse para completar la descomposicin modular de un programa.
El desarrollo deber atender tanto a la organizacin de las operaciones como
a la de los datos sobre las que operan, de manera que habr que ir realizando
simultneamente las siguientes actividades:

Identificar las operaciones a realizar, y refinarlas.


Identificar las estructuras de informacin y refinarlas.

El refinamiento sobre un desarrollo descendente con abstracciones


funcionales, se vio, que haba que optar por una de las alternativas siguientes:

Considerar operaciones como operacin terminal, y codificarla


mediante sentencias del lenguaje de programacin.

137

Considerar la operacin como operacin compleja, y


descomposicin en otras ms sencillas.
Considerar como operacin abstracta, y especificarla, escribiendo
ms adelante el subprograma que la realiza.

Ahora se pueden reformular estas opciones para las estructuras de datos a


utilizar:

Considerar el dato como un dato elemental, y usar directamente un


tipo predefinido del lenguaje para representarlo
Considerar el dato como dato complejo, y descomponerlo en otros
ms sencillos (como registro, unin o formacin).
Considerar el dato como abstracto y especificar su interfaz, dejando
para ms adelante los detalles de su implementacin.

138

MDULOS
CONCEPTO DE MODULO
En programacin un mdulo es, en general, un fragmento de programa
utilizado en algn momento en la ejecucin de un programa. Lo que
distingue un fragmento arbitrario del programa es que ese fragmento haya
sido reconocido como tal. As pues podemos definir:
Mdulo. Fragmento de programa desarrollado de forma independiente.
Este desarrollo independiente debe ser serlo en el mximo grado posible.
Atendiendo a las tcnicas de preparacin de programas en lenguajes de
programacin simblicos, diremos que un mdulo debera ser compilado y
probado por separado, y no tratarse de un simple fragmento de texto dentro
de un nico programa fuente.
La necesidad de copilar por separado los distintos mdulos obedece a
criterios de limitar la complejidad de lo que se est elaborando por un
programador. El concepto de mdulo est ligado a la idea de abstraccin. Un
mdulo debe definir un elemento abstracto(o varios relacionados entre s) y
debe ser usado desde fuera con slo saber qu hace el mdulo, pero sin
necesidad de conocer cmo lo hace.
Como todo elemento abstracto, en un mdulo deberemos distinguir los dos
puntos de vista, correspondientes a su especificacin y a su realizacin, es
decir que hace y como lo hace.
La realizacin del mdulo consistir en la realizacin de cada uno de los
elementos abstractos contenidos en dicho mdulo.
La especificacin de un mdulo es todo lo que se necesita para poder usar los
elementos definidos en l. Esta especificacin constituye la interfaz (en
ingls interface) entre el mdulo (incluida su realizacin) y el programa que
lo usa. Referida a los mdulos, la ocultacin consiste en que el programa que
usa un elemento de un mdulo slo tiene visible la informacin de la interfaz,
pero no su realizacin.

139

Compilacin separada
Los lenguajes de programacin que permiten programar usando mdulos
pueden emplear diversas tcnicas para definirlos e invocar los elementos
definidos en ellos. Para ellos es importante que stos puedan compilarse por
separado. Por otra parte, para que el uso de los elementos de un mdulo sea
correcto, habr que hacerlo de acuerdo con la interfaz establecida. La interfaz
debe ser tenida en cuenta al compilar un programa que use elementos de un
mdulo separado.

Fig. 35 Visibilidad de un mdulo

En la Fig. 35 se representa grficamente la visibilidad deseable entre un


mdulo y el programa que lo usa. Por lo tanto tenemos:

Compilacin deseada: El programa est formado por varios ficheros


fuente, cada uno de los cuales se compila por separado.

Compilacin segura: Al compilar un fichero fuente el compilador


comprueba que el uso de elementos de otros mdulos es consistente
con la interfaz.

Ocultacin: Al compilar un fichero fuente el compilador no usa


informacin de los detalles de realizacin de los elementos de otros
mdulos.

Entre las tcnicas empleadas por lenguajes de programacin de uso frecuente


en lo que respecta a compilacin separada, tomaremos situaciones tales como
las siguientes:

140

(a)

(b)

(c)

(d)

El fichero del programa y el mdulo se tratan de


forma totalmente separada, sin visibilidad de la
interfaz (lenguaje FORTRAN y primeras versiones
de C)
La parte necesaria de la interfaz se copia o importa
manualmente en el programa que la usa. La
compilacin de los ficheros del programa y el
mdulo se hace con total independencia (lenguaje
C ANSI con prototipos, C++, algunas versiones
del lenguaje Pascal)
La interfaz del mdulo y su realizacin se escriben
en ficheros separados. El mismo fichero de
interfaz se usa tanto para comprobar al compilar la
realizacin del mdulo como para compilar el
programa que lo usa (lenguajes Modula-2 y Ada
La interfaz del mdulo y su realizacin se
combinan en un solo fichero fuente. Al compilar el
programa que lo usa el compilador lee el fichero
fuente del mdulo, pero solo utiliza los elementos
de interfaz (lenguajes Oberon y Java)

No hay compilacin
segura

Compilacin ms
segura pero con
posibilidad de
errores

Compilacin
completamente
segura

El lenguaje C est basado en el C++ y comparte sus caractersticas en


cuanto a compilacin separada y compilacin segura

Descomposicin modular
La posibilidad de compilar mdulos de forma separada permite repartir el
trabajo de desarrollo de un programa, a base de realizar su descomposicin
modular. Los diferentes mdulos pueden ser encargados a programadores
distintos y as pueden trabajar al mismo tiempo.
La descomposicin modular de un programa puede reflejarse en un diagrama
de estructura, tal como el de la Fig. 36. En este diagrama se representa cada
mdulo como un rectngulo, con el nombre del mdulo en su interior, y se
usan lnias para indicar las relaciones de uso entre ellos.

141

Fig. 36 Ejemplo de diagrama de estructura

En el ejemplo el mdulo A usa elementos de los mdulos B y C, y el mdulo


B usa elementos de C y D. Los elementos C y D no usan ningn otro mdulo.
Las lneas que indican relaciones de usos pueden llevar punta de flecha si es
necesario indicar expresamente cul es el sentido de la relacin.
Para que la descomposicin en mdulos sea la adecuada, desde el punto de
vista de los programadores que trabajan simultneamente, conviene que los
mdulos resulten tan independientes unos de otros como sea posible. Esta
independencia se analiza segn dos criterios, denominados acoplamiento y
cohesin.
El acoplamiento entre mdulos indica cuntos elementos distintos o
caractersticas de uno o varios mdulos han de ser tenidos en cuenta a
la vez al usar un mdulo desde otro. Este acoplamiento debe reducirse a
un mnimo.
La cohesin indica el grado de relacin que existe entre los distintos
elementos de un mismo mdulo, y debe ser lo mayor posible. Esto
quiere decir que dos elementos ntimamente relacionados deeran ser
definidos en el mismo mdulo, y que un mismo mdulo no debe incluir
elementos sin relacin entre s.

MDULOS EN C
Un programa descompuesto en mdulos se escribe como un conjunto de
ficheros fuente relacionados entre s, y que pueden compilarse por separado.
Cada fichero fuete constituye as una unidad de compilacin.
142

C C++ (por lo tanto C) no incorporan ninguna estructura sintctica para


realizar programacin modular. A continuacin se muestra la manera de
redactar programas compuesto pos mdulos e estos programas.

Proceso de compilacin simple

Fig. 37 Proceso de compilacin simple

La compilacin de un fichero fuente (.cpp) produce un fichero objeto (.o) que


contiene la traduccin del cdigo C a instrucciones mquina (Fig. 37). En
general un fichero objeto no se puede ejecutar directamente. Se necesita un
paso adicional de montaje para obtener un programa o fichero ejecutable
(.exe en MS-Window).
En C y C++ es frecuente que el montador y el compilador sean una misma
herramienta o al menos que se invoquen como si lo fueran.

Mdulo principal
Cuando se descompone un programa C en varios mdulos uno de ellos ha
de ser el programa principal o mdulo principal. Este mdulo ser el que
contenga la funcin main(). La ejecucin del programa completo equivale a
la ejecucin de dicha funcin principal.

Mdulos no principales
Los mdulos de la aplicacin que no contienen un funcin main() no
permiten generar un programa ejecutable por si solos. Al escribir el cdigo de
estos mdulos no principales hay que distinguir claramente entre los
elementos pblicos, que deben ser visibles desde fuera del mdulo para poder
usarlos, y los elementos privados, que no necesitan ser visibles en el interior
del mdulo. la distincin de estos dos elementos pblicos y privados, se hace
repartiendo el cdigo del mdulo en dos ficheros fuente separados: un fichero
interfaz o cabecera y fichero de implementacin.

143

Veamos el siguiente ejemplo del cdigo de un mdulo que ofrece facilidades


para imprimir series de valores numricos tabulando en varias columnas.
Fichero interfaz: (Tabulacion.h)
/************************************************************
* Interfaz del mdulo Tabulacin
*
* Este mdulo contiene los elementos para
* Imprimir series de nmeros en varias columnas
************************************************************/
#pragma once
extern int numColumnas; /* nmero de columnas */
extern int anchoColumna; /* ancho de cada una */
/* -- Iniciar la impresin --*/
void Iniciar( char titulo[ ] );
/* Imprime un nmero tabulado*/
void Imprimir( int numero );
/* Completa la impresin de la ltima lnea*/
void Terminar():

Fichero implementacin: (Tabulacin.cpp)


/************************************************************
* Mdulo Tabulacin
*
* Este mdulo contiene los elementos para
* Imprimir series de nmeros en varias columnas
************************************************************/
#include <stdio.h>
#include <string.h>
#include "Tabulacion.h"
int numColumnas = 4; /* nmero de columnas */
int anchoColumna = 10; /* ancho de cada una */
static int columna = 1 /* Columna actual */

144

/* -- Iniciar la impresin --*/


void Iniciar( char titulo[ ] ) {
Terminar(); /* la serie anterior, por si acaso*/
printf( "%s/n", titulo );
columna = 1;
}
/* Imprime un nmero tabulado*/
void Imprimir( int numero ) {
if (columna > numColumnas) {
printf( "\n" );
columna = 1;
}
printf( "%*d", anchoColumna, numero );
columna++;
}
/* Completa la impresin de la ltima lnea*/
void Terminar() {
if (columna > 1 ) {
printf( "\n" );
}
columna = 1;
}

Los nuevos elementos (#pragma once, extern, static) se explicaran ms


adelante.

Uso de mdulos
Para usar los elementos pblicos definidos en un mdulo hay que incluir la
interfaz de ese mdulo donde se vaya a utilizar. Esto se consigue con la
directiva #include poniendo el nombre del fichero entre comillas "" y no
entre ngulos <> como las libreras, esto indica que el compilador buscara
el fichero en donde reside el cdigo fuente de la aplicacin, y no donde est
instalada la herramienta de compilacin. Por ejemplo:
/************************************************************
* Programa: Serie
* Este programa imprime la serie de nmeros
* del 1 al 20 en varias columnas
************************************************************/
145

#include "Tabulacion.h"
int main() {
Iniciar( "-- Columnas por defecto --" );
for (int k = 1; k <= 20; k++) {
imprimir(k)
}
Terminar();
numColumnas = 3;
anchoColumna = 13;
iniciar( "-- 3 columnas de 13 caracteres --" );
for (int k = 1; k <= 20; k++) {
imprimir(k)
}
Terminar();
numColumnas = 6;
anchoColumna = 5;
iniciar( "-- 6 columnas de 5 caracteres --" );
for (int k = 1; k <= 20; k++) {
imprimir(k)
}
Terminar();
}
El resultado es:
-- Columnas por defecto
1
5
9
13
17

2
6
10
14
18

3
7
11
15
19

4
8
12
16
20

146

-- 3 columnas de 13 caracteres
1
4
7
10
13
16
19

2
5
8
11
14
17
20

3
6
9
12
15
18

-- 6 columnas de 5 caracteres
1
7
13
19

2
8
14
20

3
9
15

4
10
16

5
11
17

6
12
18

Declaracin y definicin de elementos pblicos


En los programas modulares, en los que hay elementos de un mdulo que se
usan en otros, es preciso distinguir a veces entre la declaracin y la
definicin de un elemento.
En la declaracin de un elemento hay que especificar lo necesario para que el
compilador pueda compilar correctamente el cdigo que usa dicho
elemento.
En la definicin de un elemento hay que especificar lo necesario para que el
compilador genere el cdigo del propio elemento.
En el caso de los elementos pblicos de los mdulos, la declaracin debe
ponerse en el fichero interfaz, y la definicin en el fichero de
implementacin. La siguiente tabla recoge un resumen de cmo se declaran y
definen en C las siguientes clases de elementos sintcticos.

147

Declaracin (fichero.h)
typedef TipoNuevo ;
const Tipo constante = valor;
extern Tipo variable;
Tipo Subprograma(argumentos);

Definicin (fichero.cpp)
(no aplicable)
(no aplicable)
Tipo variable = valor;
Tipo Subprograma(argumentos) {
cdigo
}

Conflicto de nombres en el mbito global


En el mbito ms externo en la jerarqua de bloques del programa principal y
de todos los mdulos de una aplicacin constituye un espacio de nombres
global y nico, en la que no debe haber nombres repetidos. Por ejemplo si
dos mdulos diferentes definen cada uno una operacin de inicializacin
ambos le dan el nombre Iniciar(), se obtendr un error al tratar de combinar
y/o montar un programa que use ambos mdulos.
Una tcnica sencilla para evitar en lo posible los conflictos de nombres
pblicos globales es asignar a cada mdulo un prefijo diferente que se habr
de usar en los nombres de todos los elementos pblicos. En el ejemplo
anterior del mdulo de tabulacin se podra haber empleado el prefijo TAB
en los nombres de las operaciones pblicas:
/************************************************************
* Interfaz del mdulo Tabulacin
************************************************************/
#pragma once
extern int TAB_numColumnas; /* nmero de columnas */
extern int TAB_anchoColumna; /* ancho de cada una */
/* -- Iniciar la impresin --*/
void TAB_Iniciar( char titulo[ ] );
/* Imprime un nmero tabulado*/
void TAB_Imprimir( int numero );
/* Completa la impresin de la ltima lnea*/
void TAB_Terminar():

148

El mbito global ms externo incluye tambin los elementos privados, que no


figuran en la interfaz de los mdulos. Afortunadamente en este caso el
lenguaje C ofrece un mecanismo que evita los conflictos de nombres
repetidos, ya que es posible especificar elementos en el mbito ms externo
que slo sern visibles en el fichero fuente donde se definen. Para ello basta
poner la palabra clave static delante de la definicin del elemento, tal como
se ha hecho en el ejemplo de tabulacin con la variable auxiliar que almacena
el estado del proceso de impresin de varias columnas:
/************************************************************
* Mdulo Tabulacin
************************************************************/
static int columna = 1 /* Columna actual */

Con esta definicin es posible reutilizar el nombre columna para elementos


globales de otros mdulos, sin que haya conflicto entre ellos.

Unidades de compilacin en C
Por lo tanto tenemos como unidades de compilacin:

El mdulo principal del programa .cpp


El fichero de interfaz de un mdulo: modulo.h
El fichero de implementacin de un mdulo modulo.cpp

Cuando se prepara una aplicacin se mandan a compilar los ficheros con la


extensin .cpp que son los que generan el cdigo objeto. El fichero interfaz
con extensin .h no se mandan compilar por s mismos, ya que en principio
no generan cdigo objeto.

Fig. 38 Expresin de la directiva #include durante el proproceso

149

En la Fig. 38 muestra cmo se procesa un mdulo o programa de nombre A


que usa otro de nombre B. La directiva #include es equivalente a copiar ene
se punto el contenido del fichero fuente indicado. Esta copia o inclusin se
hace sobre la marcha durante la compilacin, en una fase inicial de la misma
denominada preproceso.

Compilacin de programas modulares. Proyectos


El proceso de compilacin y montaje de un programa cuyo cdigo fuente est
repartido entre varios mdulos requiere una cadena de operaciones, tal como
se indica en la para el ejemplo del programa de tabulacin.

Fig. 39 Compilacin y montaje de un programa modular

Es importante observar que el fichero interfaz Tabulacion.h se incluye tanto


en el programa principal Serie.cpp como el propio fichero de
implementacin Tabulacin.cpp. Es necesario para asegurar un compilacin
segura para detectar posibles errores de codificacin que hagan inconsistentes
la definicin de los elementos. La generacin del programa ejecutable final
exige:

Compilar los mdulos uno a uno, generando el correspondiente


fichero objeto (.o) a partir del fuente (.cpp). Cada compilacin
individual usa tambin los ficheros interfaz (.h) mencionados en las
directivas #include del mdulo.

150

Mostrar el programa ejecutable combinando todos los ficheros


objeto de los mdulos.

Todo proyecto debe contener un fichero con la informacin mnima para


construir los ficheros ejecutables de la aplicacin:

Nombre del proyecto (= nombre del programa ejecutable).


Lista de ficheros fuente de implementacin .cpp (incluyendo el
programa principal).
Forma de invocar al compilador, con opciones particulares para ese
proyecto.
etc.

DESARROLLO MODULAR BASADO EN ABSTRACCIONES


Implementacin de abstracciones como mdulos
La mayora de los caos los tipos abstractos de datos identificados en una
aplicacin son buenos candidatos para ser codificados como mdulos
independientes y lo mismo ocurre con las abstracciones funcionales de cierta
complejidad. Por lo tanto el desarrollo de abstracciones lleva implcita una
posible descomposicin de un programa en mdulos.

Dependencias entre ficheros. Directivas


Las relaciones de uso entre mdulos se corresponden, en principio, con las
directivas #include usadas en un fichero fuente para hacer visibles los
elementos de otro, y que pueden aparecer en el fichero .cpp y/o en el .h. La
recomendacin es:

Un fichero xxx.h debe incluir otros yyy.h que use directamente.


Un fichero xxx.cpp debe incluir su propio xxx.h y otros yyy.h que
use directamente. Pero no hace falta hacerlo explcitamente si ya los
incluye su xxx.h.

151

Datos encapsulados
Al definir un tipo abstracto de datos hay que declarar luego variables de ese
tipo para trabajar con ellas. En algunos casos concretos resulta solo necesaria
una nica variable del tipo abstracto. Si es as, existe la posibilidad de
encapsular dicha variable en un mdulo y evitar la declaracin explcita del
tipo. La facilidad de ocultacin que provee la programacin modular es
suficiente para conseguir la abstraccin del dato, de forma que solo sean
visibles las operaciones que lo manipulan pero no los detalles de su
implementacin.
La siguiente tabla compara los esquemas generales de cdigo
correspondiente a la declaracin y uso de un tipo abstracto de datos y a un
dato encapsulado. En ambos casos se usa un mdulo separado para el
elemento abstracto:
Tipo abstracto

Dato encapsulado
Interfaz

typedef struct Tipo {


void Operacion1();
void Operacion2();
private:
UnTipo valorInterno;
void Operacion3();
};
void Tipo: :Operacion3() {

}
void Tipo: :Operacion1() {
valorInterno
}
void Tipo: :Operacion2() {
valorInterno
}

void Operacion1();
void Operacion2();

Implementacin
static UnTipo valorInterno;
static void Operacin3() {

}
void Operacion1() {
valorInterno
}
void Operacion2() {
valorInterno
}
Uso

Tipo dato;
Operacio1();
Dato.Operacion1();

152

Conviene recordar que los nombres de variables y subprogramas definidos en


el nivel ms externo de un fichero fuente son globales, por defecto. para que
sean tratados como identificadores locales al fichero debe ser marcados como
static.
Como puede verse el segundo esquema es ms sencillo, ya que ni el tipo ni el
dato son visibles. Esto es posible por la limitacin de que solo hay un dato
del tipo abstracto. El dato encapsulado aparece simplemente como una
coleccin de operaciones que manipulan la misma variable interna, oculta.

Reutilizacin de mdulos
Los expertos de desarrollo de software consideran que la descomposicin
modular basada en abstracciones es una buena metodologa para desarrollar
mdulos con bastantes posibilidades de reutilizarlos de nuevo en un futuro.
Los mdulos que definen abstracciones pueden agruparse en bibliotecas o
libreras (library) que se ponen a disposicin para desarrollar aplicaciones
en un campo determinado.

153

También podría gustarte