You are on page 1of 103

C a t u

La codificación en
la programación
moderna: C++

Temas y áreas de conocimiento del capítulo


Área general de conocimientos del capítulo:
6.2.1 Familias y tipos de lenguajes
Sección 9.1 Introducción (PI14)
Sección 9.2 Estructuras fundamentales de control (P13, PI14)
Sección 9.3 Estructuras adicionales de control (P13, PI5, PI14-15)
Sección 9.4 Módulos (P13, P126)
Sección 9.5 Ejemplo de un diseño completo codificado:
las 8 damas (ídem)
Sección 9.6 Manejo de archivos (PI7)
422 Capítulo 9 La codificación en la programación moderna: C++

9.1 INTRODUCCIÓN

Si el lector ha estudiado con cuidado los capítulos anteriores, sin mayor problema podrá
dedicar sus esfuerzos a la siguiente parte de la meMiología que se ha explicado, consistente
en expresar los algoritmos ya diseñados en algún lenguaje de programación en particular.
El plan de este capítulo es, pues, como sigue: se tomarán los ejemplos y las ideas
anteriormente esbozadas (y otras nuevas) y se desarrollarán hasta el punto de la codifica-
ción final en C++.
El lenguaje de programación C sigue siendo uno de los más populares dentro de la
programación "seria", y ya son muchos los programas de estudio universitarios que lo
adoptan como el primer lenguaje a aprender por parte de los alumnos. Aunque se sigue
considerando como un lenguaje "moderno", no dispone de facilidades para la programa-
ción por objetos, y por ello se creó C++ como superconjunto sucesor (es decir, C es un
subconjunto de C++) orientado a objetos, aunque bien podría considerársele como un len-
guaje "mixto", porque también permite la definición y operación de los mecanismos tra-

1
dicionales de la programación estructurada.
Por su parte, el nuevo lenguaje Java sí fue creado explícitamente para la programa-
ción orientada a objetos, y sigue la sintaxis de C. En el nivel de complejidad manejado en
este libro (que se detiene justo antes de entrar la programación por objetos), los ejemplos
en C, C++ y Java serían muy similares.
Antes de comenzar, advertimos seriamente al lector que no nos proponemos enseñar-
le a programar en C/C++, sino que únicamente se empleará el lenguaje como ejemplo de
codificación de algoritmos. En la bibliografía se proponen algunos libros y manuales
(entre tantos otros) sobre este lenguaje de programación, por lo que nos limitaremos a
explicar sus características de tal forma que permitan la comprensión de lo que aquí se
dice, lo cual es muy diferente de aprenderlo a fondo, o de hacer sistemas completos.
Además de las referencias citadas en los dos capítulos anteriores, se proponen (entre mu-
chos posibles) [DEID99] y [SAVW00] para C++. La referencia estándar original para el
lenguaje C es [KERR91], como lo es [STRB97] para C++ y [ARNG97] para Java.

9. 2 ESTRUCTURAS FUNDAMENTALES DE CONTROL

Nuestra primera tarea es, pues, aprender a expresar las estructuras de secuenciación, se-
lección e iteración condicional en este lenguaje. En general, una instrucción puede iniciar
en cualquier parte del renglón y extenderse a varios. Esto se debe a que toda expresión
tiene un terminador obligatorio, que indica al compilador dónde termina una y dónde
comienza la siguiente. El terminador de C/C++ (y Java) es un punto y coma (; ) al final de
cada enunciado.

Secuenciación
La expresión en pseudocódigo

el; e2; e3

donde los enunciados son, por ejemplo, instrucciones de asignación, se expresará en


C/C++ como:

alfa = 39; beta = 1; zeta = 1000;


Sección 9.2 Estructuras fundamentales de control 423

Es forzoso que todas y cada una de las variables usadas en un programa estén declara-
das al inicio, asunto que se tratará un poco más adelante, al hablar de los tipos de datos.
Cuando se desea expresar la secuenciación de varios enunciados de tal forma que
aparezca como un solo enunciado elemental, en pseudocódigo se emplean los metapa-
réntesist comienza y termina, mientras que C/C++ emplean las llaves: }.
Es decir, la expresión en pseudocódigo

comienza
e,
e2
e3
termina

Se escribe en C/C++ de esta forma:

alfa = 1;
beta = 2;
zeta++;
}

Aquí, la expresión zet a++; es un modismo de este lenguajett, y es equivalente a


zeta = zeta + 1;
En realidad se trata de un operador (++) unario posfijo, que incrementa en uno el
valor de la variable sencilla que tiene como operando. También se puede emplear en mo-
dalidad prefija, ++z et a. La diferencia consiste en que en la versión prefija el incremento
se realiza antes de emplear la variable (si ésta forma parte de una expresión más comple-
ja), mientras que la versión posfija lo realiza después. Igualmente, existe el operador para
decremento, --. Ambos se usan sin espacios intermedios.
Por ejemplo, si ALFA es un arreglo, entonces el fragmento
i = 1;
ALFA[i++] = 0;

(t) Por razones de claridad seguiremos subrayando las palabras clave en los programas en pseu-
docódigo, aunque ya no se hará en los escritos en C++, para que el lector tenga claro que no es
necesario subrayarlas en los programas ya codificados. Todos los programas y fragmentos mos-
trados fueron compilados en nuestra computadora personal.
(II) ¡Al fin se comprende la motivación que tuvo el investigador noruego Bjarne Stroustrup para
dar ese nombre tan raro al lenguaje orientado a objetos sucesor de C, que desarrolló en los Labora-
torios Bell en 1982!
424 Capítulo 9 La codificación en la programación moderna: C++

es equivalente a ALFA[i] = 0; i = i + 1; pues realiza dos operaciones en una. El


resultado final es ALFA[ 1 = 0 y además 1 = 2.
Por su parte, ALFA[++i] = 0; equivale a i = i + 1 ; ALFA[ i] = 0; con lo que el
resultado final es i = 2 y ALFA[2] = 0.

Selección
La estructura ... entonces ... otro del pseudocódigo se escribe en C/C++ en-
cerrando entre paréntesis la condición del if, razón por la cual no se emplea la palabra
then:
if() else

Estas dos expresiones son equivalentes:

Pseudocódigo C/C++
11. alfa = 85 if (alfa == 85)
entonces beta = 38 beta = 38;
91.12Q beta = 10 else beta = 10;

Observe con cuidado el distinto uso que se hace del signo de comparación por igual-
dad y del de asignación, pues son muy diferentes. C/C++ utiliza un signo sencillo para la
asignación y uno doble para la prueba por igualdadt.
Es decir, en C/C++ se emplea el signo sencillo para la asignación: beta = 38 y uno
compuesto para la comparación por igualdad: ¿es alfa == 85?
La formulación de expresiones más complejas en C/C++ es muy similar al pseudo-
código, como en el siguiente ejemplo:

C3 entonces comienza
el
e,
termina
otro comienza
ez,
e,
termina

que es equivalente a:

if (alfa == 3)

beta = 1;
zeta = 7;
}

else {
beta = 21;
zeta = 5;
}

(t) A estas alturas del curso es cuando cada estudiante debe ponderar si este tipo de carreras profe-
sionales es lo adecuado para él o ella, pues de aquí en adelante habrá que prestar atención a todos
los nimios detalles. Ya no existe la libertad del pseudocódigo: el que manda es el compilador, y no
es uno de los nuestros. Además, no hay ninguna posibilidad de "negociar" o de "llegar a un acuer-
do", lo cual es un distintivo de las llamadas ciencias exactas.
Sección 9.2 Estructuras fundamentales de control 425

Iteración condicional
C/C++ dispone de la instrucción while, muy parecida al mi e nt ras del pseudocódigo, por
lo que no habrá mayor problema para usarla. Existen, claro diferencias de detalle.
Esta expresión en pseudocódigo

mientras (alfa <> 10)


comienza
beta = beta - 1
alfa = alfa + 1
termina

se escribe así en C/C++:

while (alfa != 10){


beta--;
alfa++;
}

Procederemos ahora a combinar y anidar estructuras (lo cual el lector ya debe domi-
nar bien en pseudocódigo), y a codificarlas en C++.
Tomando el ejemplo de la pág. 325, que decía:

ai C, entonces comienza
C2 entonces e 5
otro el
mientras(C 15 ) e 9
termina
otro fij C21 entonces comienza
e2

e33
termina
otro comienza
e37
e,
termina

su codificación empleando condiciones y enunciados arbitrarios será:

if (C1 > 19)


if (C2 < 8) zeta = 5;
else zeta = 1;
while (C15 <= 0) C15 = gama + 9;
426 Capítulo 9 La codificación en la programación moderna: C++

} else if (C21 == -1) {


psi = 2;
tau = 33;
}

else {
psi = 37;
tau = 11;

Por otra parte, los comentarios en C pueden ocupar cualquier lugar donde pueda
haber un espacio en blanco y se escriben rodeados del doble símbolo / * por la izquierda y
de */ por la derecha, aunque empleen más de un renglón. Además de lo anterior, en C++
puede emplearse el doble símbolo / / para iniciar un comentario, y el compilador ignorará
desde allí hasta el final de ese renglón.

Características propias de C++


Llegó el momento de mencionar las particularidades indispensables de C++ que per-
mitan escribir un programa completo y acabado. Se trata principalmente de las forzosas
declaraciones de los tipos de datos y las funciones, sin entrar en demasiados detalles
adicionales.
Un programa en C++ suele iniciar con las palabras reservadas void main ( ) que
definen el encabezado del programa principalt. Éste módulo (como todos los demás
que pudieran existir) comienza con una llave que abre { y debe terminar con la correspon-
diente llave que cierra }, aunque en medio usualmente habrá muchos otros pares balancea-
dos de estas llaves tipo comienza y termina. Inmediatamente a continuación de la primera
llave vienen las declaraciones de variables, seguidas de los enunciados y expresiones del
programa, separando cada elemento estructural mediante un punto y coma. Forzosamente
deben primero declararse las variables, antes de emplearlas dentro de alguna expresión.
Lo mismo sucede con los módulos, aunque este tema ("prototipos") se analizará un poco
más adelante.
Además, es muy común que el compilador requiera tener acceso a módulos residen-
tes en bibliotecas, empleados para las operaciones matemáticas, de entrada/salida y de
manejo de archivos. El código de estas funciones se inserta en el programa durante la com-
pilación mediante el uso de macroinstruccionestt predefinidas, invocadas mediante una
o varias llamadas especiales #include, que se colocan antes del módulo main ( ).
Es decir, la estructura mínima es:

// Programa genérico en C++


#include <biblioteca>
void main()
{

--- declaraciones de variables ---


--- instrucciones ejecutables
}

) Con algunos compiladores ya no es necesario que la función principal se llame main: por
ejemplo, en la programación para Windows ® se llama WinMain y se establece al definir lo que
allí se conoce como el "proyecto".
(tt) Recuérdese la importancia del macroprocesamiento, explicado en la sección 5.3 de este
libro.
Sección 9.3 Estructuras adicionales de control 427

Así, estos dos fragmentos de programa son equivalentes:

Pseudocódigo C++
entero alfa[10] void main()
entero beta, zeta {
real lambda, mu int alfa[10];
int beta, zeta;
float lambda, mu;

Un detalle muy importante es que en C/C++ los arreglos inician siempre con el índice
cero, por lo cual el primer elemento de alfa se llama alfa [0] y el décimo es alfa[9].
Es decir, el vector de diez posiciones enteras está definido internamente así:

alfa [10] :

Dirección 0 1 2 3 4 5 6 7 8 9

Límite inferior
t
Límite superior

El lector debe tomar esto muy en cuenta y considerarlo como si fuera una ley de la
naturaleza si espera tranquilidad en sus días futuros.
Además, C/C++ es particularmente rico en el manejo de los detalles internos emplea-
dos por el compilador para representar las variables y los arreglos en la memoria de la
computadora, lo cual se logra mediante los apuntadores, que permiten al programador
manipular muy eficientemente las direcciones de sus elementos. Por ejemplo —aunque
no los emplearemos aquí— el nombre mismo de un arreglo es un apuntador al primero de
sus elementos[.

9.3 ESTRUCTURAS ADICIONALES DE CONTROL

Se explicará ahora cómo usar algunas de las construcciones adicionales que ofrece C/C++,
y que enriquecen considerablemente el poder expresivo de los programas.

(t) Ciertamente que el uso de apuntadores en C/C++ confiere a estos lenguajes mucha de su po-
tencia, sobre todo para realizar programas de software de base, pero también es cierto que su
manejo es un tanto obscuro, y puede causar errores durante la ejecución si el programador no lo
sabe hacer bien. El lenguaje Java no emplea apuntadores, debido precisamente a la capacidad de
éstos de otorgar al programa en ejecución acceso a toda la memoria de la máquina, con lo cual las
aplicaciones distribuidas o remotas podrían entonces husmear fuera del entorno para el cual
fueron diseñadas.
428 Capítulo 9 La codificación en la programación moderna: C++

La construcción de pseudocódigo

repite
comienza
e2
e3
termina
Pasta (condición)

tiene expresión en C/C++ mediante la construcción do ... while ( ) :

do
alfa = alfa + 2;
beta = beta - 3;
} while (alfa < 10);

Debe observarse que el ciclo do ... while ( ) de C/C++ termina cuando la condición
se vuelve falsa, exactamente al revés que en el repite ... hasta del pseudocódigo, en
donde la iteración termina al cumplirse la condición, pues while (o sea, mientras) es pre-
cisamente lo contrario de hasta.
Como todo repite éste siempre se ejecuta al menos una vez, porque primero se
ejecuta su enunciado (o enunciados dentro de las llaves) y después se pregunta si se debe
seguir haciéndolo, al evaluar la expresión tipo mient ras que cierra el ciclo ...aunque ya
sea demasiado tarde.
No obstante que este nuevo fragmento parece similar al anterior, en realidad no lo es,
porque puede no ejecutarse, si de entrada alfa es mayor o igual a 10:
while (alfa < 10) {
alfa = alfa + 2;
beta = beta - 1;
}

Iteración controlada numérica y lógicamente


La construcción de pseudocódigo

ejecuta i = Li , L.
e

en realidad es una instrucción original de los lenguajes de programación, por lo que mere-
ce menos el nombre de pseudocódigo que casi todas las demás. En el caso de C++ se
llama for, y es un tanto más genérica. Con mucho, es la instrucción de iteración más
usual, porque combina la potencia y versatilidad tanto del re p ite como del mie nt ras y el
e j ecut a, aunque tal vez inicialmente sea un poco difícil de entender.
La sintaxis es

for (<inicio>; <condición>; <control>) e;

<inicio> es una expresión que asigna un valor de arranque al índice que controla la
iteración.
<condición> evalúa si se debe seguir con el ciclo, lo cual sucederá mientras sea verda-
dera. Es decir, la iteración bien podría realizarse cero veces si la condi-
ción de entrada es falsa.
Sección 9.3 Estructuras adicionales de control 429

<control> es una expresión que determina el valor que tendrá el índice después
de efectuar la siguiente iteración.

e es el enunciado simple o compuesto que se repite.

y el funcionamiento es como sigue: primero se ejecuta la expresión que asigna un cierto


valor al índice de control; a continuación se evalúa la condición; si es verdadera, entonces
se ejecuta una vez el enunciado e inmediatamente después se ejecuta la expresión de
control, para recomenzar el ciclo evaluando nuevamente la condición. Toda la iteración
termina cuando la condición es falsa.
Así, la siguiente construcción llena el arreglo ALFA( 10) con ceros:

for (i = 0; i < 10; i++) ALFA[i] = 0;

y es equivalente a este pseudocódigo:

i = 0
mientras (i < 10)
comienza
ALFA[i] = 0
i = i + 1
termina
La condición que gobierna la iteración no está limitada a ser numérica, sino que
puede aprovecharse para lo que convenga al programador. Por ejemplo, el siguiente ciclo
se detiene al encontrar el primer valor del arreglo ALFA[1 0 ] que contenga el valor 7,
cuidando además de no salirse de sus límites. De hecho, todo se efectúa en la condición,
así que ni siquiera hace falta ejecutar ningún enunciado (y por ello se usa el enunciado
nulo, representado por el punto y coma):

for (i = 0; (ALFA[i] 1= 7) && (i < 10); i++);

La siguiente tabla muestra los operadores lógicos empleados en el lenguaje:

AND &&
OR
XOR (OR exclusivo)
NOT 1
¿IGUAL? ==
¿DIFERENTE? 1=
¿MENOR QUE?
¿MAYOR QUE?
¿MENOR O IGUAL? <=
¿MAYOR O IGUAL? >=

Como ejemplo adicional, este nuevo fragmento imprime los valores (previamente
definidos) del vector ALFA [ 10 ) en orden decreciente:

for (i = 9; i >= 0; i--)


cout « ALFA[i];
430 Capítulo 9 La codificación en la programación moderna: C++

Además, se hizo uso de la instrucción de salida cout, que en realidad es un objeto


predefinido por una biblioteca de C++, el cual recibe "mensajes" de otros objetos y los
envía a la pantalla, empleando el operador «. En este caso, el mensaje es el nombre de un
objeto (elemento de un arreglo) a ser mostrado por la pantalla. Más adelante se explica el
uso de las instrucciones de entrada/salida.

Selección múltiple
La construcción caso del pseudocódigo es muy útil para expresar algoritmos de manera
elegante, y puede usarse en C/C++, aunque existen importantes diferencias en su empleo
...comenzando con el nombre, que aquí es switch.
El siguiente fragmento de pseudocódigo muestra en la pantalla de la computadora
un menú para que el usuario escoja alguna acción deseada. Supóngase que existen cinco
módulos ya programados, los cuales efectúan otras tantas funciones que por lo pronto no
nos preocupan:

repite
comienza
escribe "Escriba su selección (1-4)'
escribe "Para terminar, escriba 0
111 digito
caso dígito dft
0: escribe "Adiós";
1: UNO
2: DOS
3: TRES
4: CUATRO
: ERROR
fin-caso
termina
hasta (digito = 0)

Para codificarla habrá que hacer las siguientes consideraciones: la estructura switch
de C/C++ compara la variable de control (que únicamente puede ser de tipo int o c ha r)
contra un conjunto de valores definidos mediante la palabra especial case. Además, estos
casos no son mutuamente excluyentes, como en nuestro pseudocódigo, por lo que el pro-
gramador debe incluir la palabra especial break al final de cada uno para que no se genere
un "efecto de cascada", en el cual la variable de control se compare sucesivamente contra
todos los casos posibles, independientemente de que ya haya sido elegido uno, aunque en
ocasiones este efecto sí pueda resultar de utilidad.
Así pues, ésta es la traducción del fragmento anterior a C++:

do {
cout « "\nEscriba su selección (1-4)" endl;
cout « "Para terminar, escriba 0: ";
cin » digito;
switch (digito) {
case 0: cout « "Adiós.\n"; break;
case 1: UNO;
break;
case 2: DOS;
break;
Sección 9.3 Estructuras adicionales de control 431

case 3: TRES;
break;
case 4: CUATRO;
break;
default: ERROR;
}

while (digito != 0);

Hay varios puntos importantes adicionales. cout por sí mismo no baja el cursor de la
pantalla cuando escribe un mensaje, sino que lo mantiene en la última posición, por lo que
para forzar un salto de renglón, como parte integrante de la cadena de letras debe "escri-
birse" un símbolo especial 1n para cambiar de renglón. Cuando no haya una cadena lite-
ral, también puede emplearse el elemento terminador de flujo e ndl para cambiar al siguiente
renglón. Además, se usó la instrucción de entrada c in junto con el operador » para depo-
sitar en la variable digito el número tecleado por el usuario. Para invocar a un módulo
simplemente se menciona su nombre, seguido por punto y coma. Obsérvese también que
la construcción switc h requiere el uso de las llaves { } para delimitar su alcance sintáctico,
y que la etiqueta vacía del pseudocódigo se representa mediante la palabra especial default.
Sugerimos volver a revisar el código en este momento.
1
Entrada/salida
Como ya se han utilizado las instrucciones cin y cout para la entrada y la salida de datos,
conviene explicar un poco más. Cuando se añade la macroinstrucción #include
<iostream.h> como primer renglón de un programa (antes del encabezado main), el
compilador de C++ acepta c in y cout como instrucciones para lectura y escritura desde
el teclado y la pantalla, respectivamente. En realidad éstos son objetos con significado
especial para la biblioteca del sistema iostream h, que especifica una clase estándar,
iost ream, utilizada con el flujo de entrada/salida, como su nombre en inglés indica. Este
flujo es un área temporal en memoria (buffer) en donde se manipulan caracteres mediante
el operador de extracción » y el operador de inserción « .
Aunque inicialmente no resulte muy intuitivo, la instrucción cin extrae del flujo co-
nectado con el teclado el dato proporcionado por el usuario, y lo deposita en la variable
indicada, gracias al operador de extracción ». Es decir, el renglón

cin » entra;

extrae un dato del flujo de entrada y lo deja en la variable predefinida entra, con lo cual
efectivamente se está efectuando una operación de lectura. Tal vez ayude a la compren-
sión el considerar la secuencia

teclado cin » entra;

El objeto cin ignora lo que en C++ se conoce como "espacio en blanco": blancos,
tabuladores y <Enter>. Además, lo único que se lee es un dato del tipo especificado por la Li mitantes
variable en cuestión; es decir, si entra es de tipo int, entonces se leerá un número com- del objeto cin
pleto de varios dígitos (con todo y posible signo). Si fuera una variable de tipo char
entonces se leería un solo carácter no blanco, etcétera.
Por su parte, la instrucción cout inserta en el flujo conectado con la pantalla el conteni-
do de la variable indicada, gracias al operador de inserción «. De esta forma, el renglón
cout « sale;
432 Capítulo 9 La codificación en la programación moderna: C++

inserta en el flujo de salida el valor de la variable especificada sale, con lo cual efectiva-
mente se está efectuando una operación de escritura. Ahora la secuencia es

pantalla <-1 cout « sale;


Como todos los objetos, cin y cout tienen asociadas funciones (métodos) para ope-
rar dentro de ellos. En la clase iost ream existen diversas funciones miembro que, cuando
se invocan junto con esos objetos —mediante la notación punto—, varían el comporta-
miento de las operaciones de entrada y salida (formato, precisión, etc.). Un ejemplo son
las invocaciones cin . get del programa de ejemplo para manejar los caracteres individuales
de un renglón, en el anexo al final del capítulo anterior. La función miembro get activa el
objeto c in para que éste modifique su comportamiento en formas preespecificadas.
Estos mismos operadores de inserción («) y extracción (») del flujo también se
emplean para leer y escribir en archivos, lo cual se explicará más adelante.
Por otra parte, siendo C++ un superconjunto de C, también puede emplear las instruc-
ciones de entrada/salida tradicionales de su hermano menor, tales como scanf y printf
Para ello habrá primero que escribir la directiva #include <stdio h>, para que el
compilador las pueda aceptar.

Ejemplo: multiplicación de matrices


Tomaremos ahora el ejemplo de la pág. 360 (multiplicación de matrices) para seguirnos
ejercitando en la codificación. En este caso, la traducción del pseudocódigo no deberá
representar mayores problemas:

// Multiplicación de matrices en C++


#include <iostream.h>

void main()
{

// Declaración de variables y límites máximos aceptables


const int LIM = 10;
int i, j, k, m, n, p;
int mal;
float A[LIM][LIM], B[LIM][LIM], C[LIM][LIM];

do {
mal = 1;
cout « "\n Número de renglones de la matriz A (1-10): ";
cin » m;
cout « "\n Número de columnas de la matriz A (1-10): ";
cin » n;
cout « "\n NúmeroÁe columnas de la matriz B (1-10): ";
cin » p;
if ( (m > 0) && (m <= LIM) && (n > 0) && (n <= LIM)
&& (p > 0) && (p <= LIM))
mal = 0;
11
else cout « "\nLa dimensión de estas matrices es
"inválida.\n\n\n";
} while (mal);

// Se leen los valores de la primera matriz, A[m x n]


cout « "\n";
Sección 9.3 Estructuras adicionales de control 433

for (i = 0; i < m; i++) {


cout « "1n";
for (j = 0; j < n; j++) {
cout « " Valor de A[" < < i + 1 « < < j+1 « " ]

cin » A[i][j];
}

}
// Se leen los valores de la segunda matriz, B[n X p]
cout « "1n";
for (i = 0; i < n; i++)
cout « "\n";
for (j = 0; j < p; j++) {
cout « " Valor de B[" « 1+1 « "," « j+1 «
cin » B[i][j];
}

cout « "\n\n La matriz producto C (" « m « "X" « p


« ") es:1n1n";

// Aquí se realiza el cálculo


for (i = 0; i < m; i++)
for (j = 0; j < p; j++) {
C[i][j] = 0 ;
for (k = 0; k < n; k++)
C[i][j] = C[i][j] + A[i][k] * B[k][j];
}

// Se imprimen los resultados de la matriz, C[m X p]


for (i = 0; i < m; i++) {
for (j = 0; j < p; j++)
cout « " " « C[i][j] « "
cout « "\n\n";
}

Además de lo elaborado de los formatos para pedir y presentar los datos, en este
programa se emplearon varias particularidades del lenguaje C++, que se describen a con-
tinuación.
La declaración

const int LIM = 10;

define a la variable LIM como un valor entero constante, que en nuestro caso representa
las dimensiones máximas de los arreglos. Algunos compiladores rechazan el uso de este
tipo de constantes dentro de las declaraciones de arreglos; en ese caso, en su lugar
habría que usar una macrodefinición, como

#define LIM 10;

Debe notarse que C/C++ considera a las matrices o arreglos —vectores bidimen-
sionales— como un vector de vectores (un vector cuyos elementos a su vez son vectores),
434 Capítulo 9 La codificación en la programación moderna: C++

y por ello las declaraciones se escriben, por ejemplo, como A[10] [ 7] y no A[10,7]. Es
decir, la matriz A de 10 renglones y 7 columnas es internamente vista por el compilador
como un vector de 10 elementos, cada uno de los cuales contiene 7 números del tipo espe-
cificado. En principio, fuera de esta notación sintáctica un tanto particular, las referencias
a arreglos y vectores en C/C++ y Java operan en forma similar a las de los demás lengua-
jes de programación usuales.t
El lenguaje C carece de un tipo de datos booleano (es decir, cuyos únicos valores
En C/C++ un int que sean t rue y f als e) aunque C++ y Java sí disponen de una clase booleana (bool y boolean,
vale 0 es equivalente respectivamente). Sin embargo, para C/C++ cualquier valor entero diferente de cero tam-
a FALSO, bién es considerado como verdadero, y por ello en el programa se usó el renglón
y un int diferente
} while (mal);
de o es equivalente
a VERDADERO. para terminar el ciclo do ... while, e impedir que se acepten valores inválidos para las
dimensiones de las matrices. O sea, while ( mal ) es equivalente a while (mal == "ver-
dadero"), y esa iteración tipo repite terminará cuando la variable entera mal deje de
tener el valor inicial de uno que se le asignó.
Para que el lector vea con precisión el efecto de los ciclos empleados para pedir y
mostrar los datos, a continuación se muestra un ejemplo de la ejecución del programa de
multiplicación de matrices, tomado directamente de la computadora. Aparece subrayado
lo que tecleó el usuario.
Número de renglones de la matriz A (1-10): 2
Número de columnas de la matriz A (1-10): a
Número de columnas de la matriz B (1-10): 2
Valor de A[1,1]: 1
Valor de A[1,21: 2
Valor de A[1,3]: a
Valor de A[2,1]: 4
Valor de A[2,2]: á
Valor de A[2,3]: 2
Valor de B[1,1]: z
Valor de B[1,21: a
Valor de B[2,1]: 2
Valor de B[2,2]:
Valor de B[3,1]: 1
Valor de B[3,2]: 2
La matriz producto C (2 X 2) es:
28 34
79 94

(t) Aunque en Java además se requiere utilizar la palabra new:


//Declaración del arreglo en C/C++
int arreglo[10];
//Declaración del arreglo en Java
int arreglo[' = new int[10];
El programa de multiplicación de matrices escrito en Java está en el anexo 8.10 de este libro.
Sección 9.4 Módulos 435

9.4 M Ó DULO S

Del capítulo anterior es importante tomar en cuenta que los módulos de C/C++ no son
ejecutables, sino tan sólo invocables, por medio de su nombre. El único módulo directa-
mente ejecutable es el principal, ya conocido, llamado main. Es decir, existe un programa
principal y varias (o ninguna) funciones independientes, que incluso pueden compilarse
por separado. Esto significa que, en principio, el programa principal no conoce las funcio-
nes ni, por tanto, sus variables, aunque en programas pequeños —como los de este capítu-
lo— usualmente el mismo archivo fuente contiene tanto el módulo principal como los
demás módulos, que simplemente se escriben a continuación de la llave final que cierra a
main. Cada módulo comienza con un declarador de la clase de datos a la que pertenece,
seguida por su nombre. (La clase debe ser vo id, a menos de que —como más adelante se
analiza— se trate de una función que devuelve un valor.)

Prototipos
Como la regla general es que la declaración de los módulos debe preceder a su uso dentro
de una invocación (algo similar a lo que sucede con las variables), la sintaxis de C++
obliga a declarar todos los módulos que se emplearán en el programa, escribiendo antes
del procedimiento main su clase, nombre y parámetros, terminando cada declaración con
un punto y coma. Esto se conoce como el "prototipo" del módulo, y más adelante en el
cuerpo del programa deberá escribirse su definición completa.
De hecho, ésa es la función de los renglones tipo #define <biblioteca. h> que se
incluyen al inicio de casi todos los programas: servir de prototipo para las funciones con-
tenidas en las bibliotecas de E/S, aunque por supuesto en este caso el programador no
escribe las funciones, porque el cargador del sistema operativo realiza el ligado, según
se explicó en la sección "esquemas de carga" del apartado 5.4 de este libro.
Con lo anterior, la estructura general de un programa en C++ es como sigue:

#include <biblioteca 1.h>

#include <biblioteca_n. h>


clase módulo_1 (parámetros); // Prototipo del módulo 1

clase módulo_n (parámetros) ; // Prototipo del módulo n

// Programa principal
void main()
{

- -- variables locales ---


--- cuerpo del programa principal ---

// Definición del módulo 1


clase modulo_1 (parámetros)

- -- variables locales ---


- -- cuerpo del módulo 1 ---

}
436 Capítulo 9 La codificación en la programación moderna: C++

// Definición del módulo n


clase modulo_n (parámetros )
{

--- variables locales ---


--- cuerpo del módulo n

Se traducirá ahora a C++ el pseudocódigo empleado al explicar los módulos en el


capítulo anterior, que se muestra nuevamente por comodidad:

proc principal
entero a, b, resultado
escribe "Dame los valores de a y b:"
lee a, b
suma(a,b,resultado)
escribe "a + b =", resultado
fin,

proc suma(entero sl, entero s2, entero r)


r = sl + s2
regresa
fin.

A continuación está la versión que emplea parámetros. Observe que es imprescindible


que el resultado sea un parámetro pasado por referencia (marcándolo con el signo &), pues
de no ser así tendría un valor indeterminado, porque no "llegaría" de ninguna parte:

// Suma con parámetros


#include <iostream.h>
void suma(int sl, int s2, int &r); // Prototipo
void main()
{

int a, b, resultado;
cout « "Dame los valores de a y b\n"
« "separados con un espacio: ";
cin » a » b;
suma(a,b,resultado);
cout « "a + b = " « resultado « endl;
}

void suma(int sl, int s2, int &r)


{

r= + $2;
}

Además, lo que en realidad cuenta del prototipo es el tipo y posición relativa de las
variables, no tanto sus nombres, que incluso pueden omitirse. Es decir, el prototipo ante-
rior podría opcionalmente verse así:

void suma(int, int, int &); // Prototipo


Sección 9.4 Módulos 437

Variables globales
También es posible declarar variables antes del procedimiento main, con lo cual adquie-
ren la categoría de globales y son conocidas por todos los módulos de ese archivo de
texto, incluyendo al principal. Por regla general, no es recomendable usarlas, porque están
"desprotegidas", y cualquiera las puede cambiar. Suelen emplearse para transferencias
masivas de datos entre módulos, que de otra forma obligarían a emplear muchos parámetros
individuales.
Existen entonces dos maneras fundamentales de comunicar valores entre procedimien-
tos de un programa en C++: mediante paso de parámetros, y por medio de las variables
globales.
Ésta es la versión equivalente del programa anterior, que no emplea parámetros, sino
variables globales:

// Suma sin parámetros


#include <iostream.h>
int a, b, resultado; // Cuidado: variables globales
void suma(); // Prototipo
void main()
{

cout « "Dame los valores de a y b\n"


« "separados con un espacio: ";
cin » a » b;
suma();
cout « "a + b = " « resultado « endl;
}

void suma()

resultado = a + b;
}

En esta segunda versión, las variables globales (declaradas antes de main) eliminaron
la necesidad del paso de parámetros, pues logran que el programa principal y la función
tengan acceso a los mismos datos.
Aparentemente, las dos formas de manejar las variables entre módulos son equiva-
lentes, pero esto no es del todo cierto. Cuando un módulo llama a otro y le pasa una lista
de parámetros, sólo las variables pasadas por referencia pueden resultar afectadas por
cualquier acción que se realice sobre ellas (cambiarlas de valor, leerlas, etc.), pero cuando
son globales se vuelven comunes, estando entonces por completo expuestas a algún cam-
bio de valor imprevisto.
El estudio detallado de estos posibles efectos secundarios constituye por sí mismo un
tema específico del diseño de sistemas computacionales, donde se manejan muchos otros
conceptos que no tenemos oportunidad de tratar en este nivel (la creación de sistemas com-
plejos, el cumplimiento de las especificaciones de diseño, etc.), y que conforman la ya
mencionada ingeniería de software.

Bloques
Siguiendo con el tema de las variables, en C/C++ cualquier conjunto de instrucciones eje-
cutables encerrado entre llaves { se conoce como bloque: un sub-espacio de direcciones
en el que pueden declararse variables, que serán locales a él. Un ejemplo ya conocido de
bloque es el que inicia luego de la llave inicial de main y termina con la última llave que
438 Capítulo 9 La codificación en la programación moderna: C++

cierra el programa principal. Si a continuación de ésta se escribe la definición de una función


(habiendo, claro, declarado su prototipo antes), entonces la llave con la que inicia de hecho
crea un nuevo bloque, dentro del cual se pueden declarar nuevas variables locales.
Es decir, las variables declaradas dentro de un bloque tienen validez o alcance (scope,
como se le llamó en el capítulo anterior) únicamente a partir del bloque en el cual fueron
declaradas, y en todos los sub-bloques allí contenidos. Ésa es la razón técnica de que las
variables de un programa sean reconocidas a lo largo de todo su texto, pues están "ampa-
radas" por el par de llaves más externo.
Los lectores sagaces ya descubrieron dos propiedades que necesariamente se siguen
de lo anterior: a) si una variable se re-declara dentro de un cierto bloque, entonces es estric-
tamente local a éste y, aunque tenga el mismo nombre, no es la misma que en el bloque
"padre" y, b) las variables declaradas antes de la llave inicial de un cierto bloque tienen
validez a partir de allí y hasta el final del texto del programa.
Esta segunda propiedad es la explicación técnica de las variables globales utilizadas
en el programita previo pues, habiendo sido declaradas antes de la llave inicial de main,
valen para todo el texto que sigue, incluyendo la función suma Q.
En el siguiente esquema se muestran diversas variables de los tipos mencionados:

#include <biblioteca.h>
int gi, g2, g3; // Variables globales en todo el archivo
void modulo_A(); // Prototipo A
void modulo_B(); // Prototipo B

void main()
{

int ml, m2; // Variables locales a main

} // Fin de main

int sl, s2; // Variables "sub-globales" de aquí en adelante

void modulo_A()
{

float x; // Variable local a módulo_A


// gi, g2, g3, sl, s2, x se reconocen aquí

x = ml; // TERROR de compilación: ml es inválida!

} // Fin de módulo_A

void modulo_B()
{

char x; // Variable local a módulo_B; no tiene nada que


// ver con la x anterior, aunque se llame igual
// gl, g2, g3, sl, s2, x se reconocen aquí
//(pero ahora es un nuevo tipo local de x)

while ( x == 's' ) {
int i; // Estrictamente local al while

}
i = 0; // TERROR de compilación: i es inválida!

} // Fin de módulo_B y fin de archivo de texto.


Sección 9.4 Módulos 439

Paso de arreglos como parámetros


Si se desea pasar un elemento específico de un arreglo como parámetro, bastará con
incluirlo en la lista de argumentos de la llamada, por valor o por referencia, según conven-
ga al programador, pues se trata de variables simples. Así, dado el arreglo int ALFA[10] ,
la llamada
modulol(&ALFA[3]); // Se envía un elemento

pasaría el cuarto elemento del arreglo como parámetro por referencia a un cierto módulo,
cuyo prototipo fuera, por ejemplo

void modulol(int &); // Prototipo

Aquí no hubo nada nuevo, pues se hizo en la misma forma como se le pasaría cual-
quier otra variable individual de tipo entero; sólo debe recordarse que en C/C++ los arre-
glos inician con el índice 0.
Por otra parte, si se deseara pasar el arreglo completo como parámetro, entonces el
lenguaje ofrece la posibilidad de enviarlo todo —tan sólo por referencia, para ahorrarse
el esfuerzo de sacarle copias a cada uno de los elementos— únicamente mencionando su
nombre, sin especificar ninguno de sus elementos individuales. De esta forma, la llamada

modulo2(10, ALFA); // Pasa todo el arreglo

envía los 10 elementos del arreglo al módulo en cuestión, siempre que su prototipo (y su
definición, claro) mencione que ese parámetro formal es un arreglo, terminándolo con un
par de corchetes vacíos:

void modulo2(int, int[]); // Prototipo: los corchetes


// vacíos indican un arreglo

Se pasó también la longitud del arreglo en otro parámetro, para posibles usos por
parte del módulo receptor, aunque esto no es forzoso.
Como el arreglo completo se pasa como parámetro por referencia, queda sujeto a
posibles cambios efectuados por el módulo receptor. Si se desea asegurar que no sufrirá
cambios, tanto en el prototipo como en la definición del módulo habrá que antecederlo de
la palabra especial const, para que el compilador lo trate como si hubiera sido pasado por
valor, aunque de hecho sigue siendo pasado por referencia, pero en una especie de modo
"protegido":

void modulo3(const int[]); // El arreglo no cambiará

Como ilustración, el siguiente programa suma dos vectores enteros, elemento a ele-
mento:

// Suma de dos vectores


#include <iostream.h>
void suma(int, const int[], const int[], int[]); // Prototipo

void main()
{
const int lim = 3;
int i;
int A[lim], B[lim], C[lim];
440 Capítulo 9 La codificación en la programación moderna: C++

cout « "Dame los " « lim « " valores de A, "


« "separados por espacios: ";
for (i = 0; i < lim; i++) cin » A[i];
cout « "\nDame los « lim « " valores de B, "
« "separados por espacios: ";
for (i = 0; i < lim; i++) cin » B[i];

suma(lim, A, B, C); // Se pasan los arreglos

cout « "\nLa suma es: ";


for (i = 0; i < lim; i++) cout « C[i] « " ";
} // Fin de main

void suma(int lim, const int A[], const int B[], int C[])
{
int i;
for (i = 0; i < lim; i++) C[i] = A[i] + B[i];
}

Aquí, la variable lim se pasó por valor y los arreglos completos A, B y C por referen-
cia, aunque los dos primeros protegidos como constantes para evitar que sean alterados
por el módulo receptor. Los resultados son:

Dame los 3 valores de A, separados por espacios: 1 2 3


Dame los 3 valores de B, separados por espacios: 4 5 6
La suma es: 579

Funciones
Como se ha dicho ya, un módulo o procedimiento es un caso particular de una función
que no devuelve ningún valor por sí misma (aunque sí puede tener parámetros), lo cual en
C/C++ se denota mediante la palabra especial void. Esto significa que las funciones de-
ben pertenecer a una clase de datos, sea primitiva o construida por el programador, y
devolver un valor mediante la palabra especial re t u rn. Por comodidad, se repiten aquí las
funciones analizadas en el capítulo anterior:

entero func cuadrado(entero x)


cuadrado = x * x
regresa

cuyas llamadas eran, por ejemplo:

escribe cuadrado(2) ! Ojo: sin comillas


escribe cuadrado(cuadrado(2))
escribe cuadrado(2) + cuadrado(2)

Y ahora se codificarán en C++ en forma casi directa:

// Uso de la función cuadrado


#include <iostream.h>
int cuadrado(int); // Prototipo
Sección 9.4 Módulos 441

void main()
{

// Llamadas a la función
cout « cuadrado(2) « endl; // Vale 4
cout « cuadrado(2) + cuadrado(2) « endl; // Vale 8
cout « cuadrado(cuadrado(2)) « endl; // Vale 16
} // Fin de main

int cuadrado(int x) // Función cuadrado


{

return x * x;
}

El programa, claro, produce como salida


4
8
16

De hecho, la única diferencia con respecto al pseudocódigo es que en C/C++ el


valor a ser devuelto no se asigna al nombre de la función, sino que se coloca luego de la
palabra return.
Continuando con el tema, también en el capítulo 8 se diseñó una nueva función, que
podría utilizarse en conjunto con la anterior:

entero func mínimo3(entero x, entero y, entero z)


entero mín 1 Variable local auxiliar
mín = x
g_¡ y < mín entonces mín = y
z < mín entonces mín = z
mínimo3 = mín
regresa
fin

Y allí se dijo que entonces con este fragmento se escribiría el menor de tres números
elevado al cuadrado:

escribe "Dame tres números enteros: "


lee a, b, c
escribe cuadrado(mínimo3(a, b, c))

Pues bien, ésta es la codificación de todo el conjunto en C++:

// Uso combinado de funciones


#include <iostream.h>
int cuadrado(int); // Prototipo
int minimo3(int, int, int); // Prototipo

void main()
{

int a, b, c;
cout « "Dame tres números enteros "
« "separados con un blanco: ";
442 Capítulo 9 La codificación en la programación moderna: C++

cin » a» b» c;
cout « "El cuadrado del mínimo es ";
cout « cuadrado(minimo3(a, b, c)) « endl;
} // Fin de main

int cuadrado(int x) // Función cuadrado


{

return x * x;
}

// Función que devuelve el mínimo de tres enteros


int minimo3(int x, int y, int z)
{

int min; // Variable local auxiliar


min = x;
if (y < min) min = y;
if (z < min) min = z;
return min;
}

Y el resultado es:

Dame tres números enteros separados con un blanco: 34 12 46


El cuadrado del mínimo es 144

Para continuar con los ejemplos del capítulo anterior, nuevamente se muestra el
pseudocódigo de la función que aumentaba el IVA (15%) al precio de un producto:

real func MAS_IVA(real x)


constante IVA = 0.15
MAS_IVA = (x * IVA) + x
regresa

Y así se sacaba el promedio de tres valores:

real func prom3(real a, real b, real c)


prom3 = (a + b + c) / 3
regresa
fin.

Por lo que la siguiente combinación de funciones obtiene el valor promedio del primero
y el tercero con IVA, más el segundo sin IVA (suponiendo, claro, que a alguien le pudiera
interesar):

prom3(MAS_IVA(a), b, MAS_IVA(c))

He aquí todo eso codificado en C++, lo cual ya debería resultar muy sencillo:

// Nuevo uso combinado de funciones


#include <iostream.h>
float MAS_IVA(float); // Prototipo
float prom3(float, float, float); // Prototipo
Sección 9.4 Módulos 443

void main()
{

float a, b, c;
cout « "Dame los precios de tres productos\n"
« "separados con un blanco: ";
cin » a » b » c;
cout « "\nEl promedio del primero y el tercero \n"
« "(ambos con IVA), más el segundo (sin IVA) es:
prom3(MAS_IVA(a), b, MAS_IVA(c));
} // Fin de main

// Función que añade el IVA


float MAS_IVA(float x)
{

const float IVA = 0.15;


return (x * IVA) + x;
}

// Función que calcula el promedio


float prom3(float a, float b, float c)
{
return (a + b + c) / 3;

El resultado de todo es:

Dame los precios de tres productos


separados con un blanco: 3 4 5
El promedio del primero y el tercero
(ambos con IVA), más el segundo (sin IVA) es: 4.4

Desde este punto de vista, una función es un módulo que se comporta como si fuera
una nueva instrucción, directamente ejecutable del lenguaje de programación, y que sirve
para evaluar una cierta tarea específica, diseñada ex profeso.
El último ejemplo de la sección 8.7 era un algoritmo para encontrar el máximo valor
dentro de un vector, con pseudocódigo:

entero func buscam (vector [LO])


Leer un vector y obtener el número más grande
1 Se supone que se tiene acceso al vector en cuestión
entero máximo, índice
máximo = vector[1]
índice = 2
mientras (no se termine el vector)
comienza
lj vector[índice] > máximo entonces máximo = vector[índice]
índice = índice + 1
termina
buscam = máximo

Y su codificación en C++ es inmediata:

// Búsqueda en un vector
#include <iostream.h>
int buscam(const int[]); // Prototipo
const int lim = 5;
444 Capítulo 9 La codificación en la programación moderna: C++

void main ( )
{
int i, alfa[lim];
for (i = 0; i < lim; i++) {
cout « "Dame el valor No. " « i+1 « " : ";
cin » alfa[i];
}

cout « "\nEl valor más grande del vector fue "


« buscam(alfa);
}

int buscam(const int alfa[])


{
int maximo, indice; // Variables locales
maximo = alfa[0];
indice = 1;
while (indice < lim) {
if (alfa[indice] > maximo) maximo = alfa[indice];
indice++;
}

return maximo;
}

Los resultados son:

Dame el valor No. 1 : 42


Dame el valor No. 2 : fi
Dame el valor No. 3 : 55
Dame el valor No. 4 : fi
Dame el valor No. 5 : 1
El valor más grande del vector fue 55

Se puede observar cómo en el programa se pasó un arreglo como parámetro, en la


forma ya antes descrita.
Cuando main Como se ha dicho, todo programa en C/C++ comienza a ejecutar a partir de la pri-
sí devuelve un valor mera instrucción del módulo principal main pero, ¿quién llama a main? Si se piensa en
de control. términos del modelo de von Neumann (y de todo lo penosamente aprendido en los
capítulos anteriores de este libro), la respuesta no podrá ser otra que "el sistema opera-
tivo"t. Aunque no es una regla, la convención en los sistemas tipo Unix, es que main sí
devuelva un valor entero al terminar su ejecución y "llegue de regreso" al sistema rector
del uso de la computadora. Como todo viaje, éste puede terminar con éxito (pues se rea-
lizó correctamente la función a desempeñar) o con fracaso (no se pudo completar la
tarea, tal vez porque no se encontró algún archivo requerido), lo cual se señalaría al siste-
ma operativo haciendo que main devuelva el valor O si tuvo éxito en la ejecución, y algún
valor diferente de O si no terminó correctamente. Para ello, claro, main debió declararse
como una función capaz de devolver un valor int, empleando un esquema similar al
mostrado a continuación:

(t) ¿Y quién invocó al sistema operativo? Esperamos que el lector recuerde con nostalgia las dis-
cusiones sobre el problema de cargar al cargador.
Sección 9.4 Módulos 445

int main()
{

--- declaraciones ---


--- cuerpo del programa principal ---
if ( terminó con éxito ) return 0;
else return 1;

Compilación de módulos por separado


Por otra parte, lo usual es que las diversas funciones que componen un sistema mediano o
grande se diseñen, escriban y compilen en archivos por separado, para reutilizar módulos
ya existentes o añadir clases predefinidas en bibliotecas. Así, el programador básicamente
sólo escribe en un archivo su programa principal —y tal vez algunos módulos propios
adicionales—, invocando desde allí módulos y funciones externas (es decir, no presentes
en ese archivo). De esto se derivan dos problemas: 1) Debe existir alguna forma de dar a
conocer a main, y a los demás componentes del archivo fuente, la estructura de los módulos
y funciones externas a las que se invocará, y 2) Todos los módulos participantes —presen-
tes y externos— deben enlazarse de alguna forma y colocarse en la memoria central de la
computadora para la posterior ejecución del sistema completo.
Del primer problema se encargan, como hemos visto, los prototipos, pues allí se espe-
cifica la forma y tipo de parámetros empleados por los módulos que se invocarán, que
por supuesto deben coincidir con sus posteriores definiciones. Una práctica muy común
en C/C++ es incluir antes de main archivos especiales de encabezado, que únicamente
contienen los prototipos de una cierta biblioteca preexistente, o de alguna escrita por el
programador. La tradición pide dar a estos archivos nombres como archivo. h (la letra
"h" es de header, que significa encabezado en inglés), y su uso es como sigue

#include <library.h> // Para el caso usual de


// bibliotecas del sistema, como
// <iostream. h> para C++

#include "trayectoria de mi_biblioteca. h"


// Para el caso de bibliotecas
// creadas por el programador

Obsérvese que los nombres de los archivos residentes en el directorio de trabajo del
compilador (o sus subdirectorios) se encierran entre llaves triangulares, mientras que los
escritos por el programador se delimitan entre comillas, y debe además incluirse la trayec-
toria completa —dentro del sistema de archivos— como parte del nombre. Por supuesto,
los detalles específicos dependen del tipo de sistema que se esté empleando, y vienen
explicados en el manual de uso.
El segundo problema —el de la carga y ligado de archivos externos— se resuelve
mediante directivas para que el sistema operativo traiga del disco magnético los archi-
vos invocados y los incorpore al sistema definido por el programador. Igualmente, en
este caso los detalles varían (mucho) entre sistemas operativos: en Unix, por ejemplo,
debe pedirse explícitamente la compilación de los programas fuente recién escritos, así
como su posterior ligado con los módulos objeto preexistentes, que residen en (sub)direc-
torios específicos. Como se mencionó, en los entornos integrados bajo Windows® debe
crearse un "proyecto" y especificar allí los nombres y trayectorias de los archivos y
bibliotecas deseados.
Debido al alcance introductorio de este libro, todos nuestros ejemplos consisten en
un solo archivo, donde reside tanto main como los demás módulos fuente, aunque sí se
hace uso de las <bibliotecas> del sistema.
446 Capítulo 9 La codificación en la programación moderna: C++

9.5 EJEMPLO DE UN DISEÑO COMPLETO


CODIFICADO: LAS 8 DAMAS

A continuación se expone y comenta la codificación en C++ del programa de las ocho


damas, diseñado en el capítulo anterior. Por propósitos didácticos aparece separado en
módulos, aunque en realidad forma un solo archivo fuente, que fue compilado y ejecutado
en una computadora personal.
Como puede observarse, el programa es bastante sencillo, y ya no deberán existir sor-
presas para pasar del pseudocódigo de las págs. 370-373 al lenguaje de programación.
Existen tres arreglos globales, que serán usados por todos los módulos para determinar
si una dama es atacada, tanto en la diagonal ascendente o descendente como en el renglón
mismo en que se intentó colocarla: en vector se guarda la posición en que la dama actual
quedó; baja y sube guardan las restas y sumas, respectivamente, de las posiciones en que
se colocó la dama, con vistas a facilitar las funciones del módulo libre. Nuevamente,
debe recordarse que en C/C++ los arreglos inician con el índice 0.
Éste es el programa principal, que efectúa el algoritmo de búsqueda de soluciones e
invoca a los módulos requeridos por el pseudocódigo:

// Programa de las 8 Damas en C++


// Programa damasC.cpp
#include <iostream.h>

// Variables globales
int dama; // Dama actual
int vector[8]; II Damas colocadas
int baja[8], sube[8]; // Diagonales amenazadas
int sol = 0; // Contador de soluciones

// Prototipos
void libre(int &, int &);
void coloca(int);
void atras (int &, int &);
void muestra();

void main()

const int numero = 6; // Número de soluciones a mostrar


int ren; // Casilla que la dama ocupa dentro de su columna
int result = 1; // Variable de control del módulo "libre"
int ultima = 0; // Indicador de fin de búsqueda
// Colocar la primera dama en su posición inicial
dama = 0;
ren = 0;
coloca(ren);

// Comienza la búsqueda de soluciones


do {
while (dama < 7) {
if (result) {
dama++; // Seleccionar la siguiente dama
ren = -1; // Desde su primera casilla (es -1
// porque "libre" inicia avanzándola)
Sección 9.5 Ejemplo de un diseño completo codificado: las 8 damas 447

libre(ren, result); // Verificar si hay lugar libre


// Si hubo lugar libre, colocarla;
// en otro caso, regresar a la dama anterior
if (result) coloca(ren);
else atras(ren, ultima);
}

// Se encontró una solución: mostrarla


// y buscar la siguiente
if (lultima) {
muestra();
atras(ren, ultima); // Buscar una nueva solución
result = 0; // Para continuar, se debe suponer que
// la recién encontrada estuvo mal
}

} while (lultima && sol < numero);


cout « nnAdiós.";
} // Fin de main

El módulo libre emplea una proposición for para controlar las diagonales amena-
zadas; esto es necesario para asegurarse de que se han revisado las posibilidades de
ataque de todas las columnas anteriores a aquélla donde se intenta colocar la dama. La
variable entera result actúa como indicador booleano de si hubo o no posición libre
para esa dama:

// Módulo para averiguar si hay una posición libre


void libre(int &ren, int &result)
{

int j;
result = 0;
while ((ren < 7) && ( !result )) {
// Avanzar la dama dentro de su columna, para
// continuar a partir de la posición previa
ren++;
result = 1; // Condición de entrada a la iteración
// Revisar las posiciones amenazadas por las j damas
// anteriores; salir al encontrar la primera atacada
for (j = 0; (j < dama) && (result); j++)
if ((baja[j] == (dama - ren)) II
(sube[j] == (ren + dama)) II
vector[j] == ren) result = 0;
}
} // Fin de libre

Por su parte, el módulo coloca asigna la dama que se le indica (la dama-ésima) a la
posición ren-ésima dentro de su columna.

// Módulo para colocar una dama en el tablero


void coloca(int ren)
{

vector[dama] = ren; // Marcar la posición de la dama


// Tomar nota de las diagonales amenazadas
baja[dama] = dama - ren;
sube[dama] = ren + dama;
} // Fin de coloca
448 Capítulo 9 La codificación en la programación moderna: C++

Tal como dice su pseudocódigo, el módulo at ras elimina la dama actual del tablero,
porque se demostró que su posición no era buena. En vector se toma nota del renglón en
que se quedó dentro de su columna, para no volver a comenzar desde el primero otra vez,
sino seguirla avanzando a partir de esa casilla:

// Módulo para efectuar el 'backtrack'


void atras (int &ren, int &ultima)
{

dama--; // Regresar a la dama anterior


// Tomar nota de su última posición, para que
// pueda seguir avanzando a partir de allí
if (dama >= 0) ren = vector[dama];
else ultima = 1; // Ya no hay más soluciones, pues
// la primera dama llegó al final
} // Fin de atrás

Por último, el módulo muestra despliega tres tableros por la pantalla y se detiene,
hasta que el usuario oprima <Enter> para proseguir. Se utilizan variables tipo char para
controlar los for porque para C/C++ un char y un int son intercambiables en aplicacio-
nes numéricas (mas no al revés; si la variable x hubiera sido int el compilador emitiría
un mensaje de error al intentar emplearla dentro de c in g et). Como suele suceder con las
rutinas de despliegue, resultó ser la más larga de todas, por varias razones, que se explican
más adelante. Ésta es su codificación:

int tabl[8][8] = {0}; // Variable persistente: externa al


// módulo, con valores iniciales cero
// Módulo para mostrar soluciones
void muestra()
{

char i, j, x;
sol++; // Llevar la cuenta
// "Llenar" el tablero con la solución encontrada
for (i = 0; i < 8; i++)
tabl[vector[i]][i] = i + 1;

// Mostrar los primeros 4 renglones centrados


for (i = 0; i < 4 ; i++) {
cout « "\n ";
for (j = 0; j < 8; j++)
cout « tabl[i][j] « " ";
}

// Escribir el título en paralelo con el 4o. renglón


cout « " Solución número " « sol;
// Mostrar los segundos 4 renglones centrados
for (i = 4; i < 8 ; i++) {
cout « "\n ";
for (j = 0; j < 8; j++)
cout « tabl[i][j] « " ";
}

cout « "\n\n"; // Separación entre tableros

// Borrar las posiciones recién utilizadas


for (i = 0; i < 8; i++)
tabl[vector[i]][i] = 0;
Sección 9.5 Ejemplo de un diseño completo codificado: las 8 damas 449

// Detenerse cada tres tableros


if (sol % 3 == 0) {
cout « "\nOprima <ENTER> para continuar.\n";
cin.get(x);

} // Fin de muestra

Lo primero que se observa es que el arreglo bidimensional que sirve como tablero se
declaró en forma externa al módulo, aunque tampoco es una variable global, porque no La persistencia
es accesible al resto del programa. Esto es así para garantizar su persistencia; es decir, que de las variables
sus valores iniciales (cero en este caso) se mantengan durante las diversas ocasiones en
que el módulo principal llama a muestra. Como en C/C++ los módulos constituyen espa-
cios de direcciones independientes, sin esta protección nada garantizaría que el código
generado por el compilador no reutilizara temporalmente esas celdas de memoria para los
datos o el código de los otros módulos cuando el actual termina de ejecutar, y bien podría
suceder que contuvieran valores extraños la siguiente vez que se invocara (como de
hecho sucedió con nuestra computadora)t. La alternativa a esto sería "limpiar" todo el
tablero antes de volverlo a emplear cada vez, mediante dos ciclos anidados que recorran
sus 64 casillas. Por otra parte, las ocho posiciones del tablero recién ocupadas por las
damas sí deben regresarse explícitamente a cero antes de la siguiente invocación.
En forma normal, para "dibujar" un tablero de ocho filas, cada una con ocho caracte-
res que simulan las celdas, se usan dos ciclos for anidados: el primero gobierna las filas
y cambia de renglón al final de cada una, y el interno recorre el arreglo completo de
valores de cada fila e imprime sus elementos sin cambiar de renglón, separándolos con un
espacio en blanco. Es decir, el código

for (i = 0; i < 8 ; i++) {


for (j = 0; j < 8; j++)
cout « tabl[i][j] «
cout « endl;
}

produce una salida como:


1 0 0 0 0 0 0 0
0 0 0 0 0 0 7 0
0 0 0 0 5 0 0 0
0 0 0 0 0 0 0 8
0 2 0 0 0 0 0 0
0 0 0 4 0 0 0 0
0 0 0 0 0 6 0 0
0 0 3 0 0 0 0 0

Pero en este caso, para propósitos de presentación en la pantalla, cada "tablero" se


dividió internamente en dos secciones: los primeros cuatro renglones se escriben de la
forma indicada (aunque cambiando de renglón al inicio, y no al final), y al terminar de
desplegar el cuarto, en lugar de cambiar de renglón se avanza varios espacios y se indica
de cuál se trata. Luego se muestran los siguientes cuatro renglonestt:

(t) Durante la ejecución, el compilador sólo garantiza la persistencia de las variables globales a
todo el programa o locales a main; todas las demás variables "nacen" y "muerFn" cada vez que se
entra y sale del módulo al que pertenecen.
(tt) ¿Recuerda el lector la advertencia hecha en el capítulo 8 acerca de la molesta pero imprescin-
dible atención a los más nimios detalles dentro de la programación?
450 Capítulo 9 La codificación en la programación moderna: C++

1 0 0 0 0 0 0 0
0 0 0 0 0 0 7 0
0 0 0 0 5 0 0 0
0 0 0 0 0 0 0 8 Solución número 1
0 2 0 0 0 0 0 0
0 0 0 4 0 0 0 0
0 0 0 0 0 6 0 0
0 0 3 0 0 0 0 0

Por su parte, la instrucción


if (sol % 3 == 0)

hace uso del operador módulo ( %) para detener el despliegue de la pantalla cada 3 table-
ros. Esta operación calcula el residuo de dividir sol entre 3: si es igual a cero, significa
que el valor de sol es múltiplo exacto de 3 y, por tanto, hay que detener el despliegue en
pantalla.
Estudie el lector esta codificación, y compárela contra el pseudocódigo original. En
todo momento tome en cuenta que ésta no es de ninguna manera "la mejor" solución para
este problema, y puede perfectamente haber otros esquemas igualmente válidos, cosa que
además conviene siempre tener en mente en el oficio de la programación.
Estas son las primeras seis soluciones:
1 0 0 0 0 0 0 0
0 0 0 0 0 0 7 0
0 0 0 0 5 0 0 0
0 0 0 0 0 0 0 8 Solución número 1
0 2 0 0 0 0 0 0
0 0 0 4 0 0 0 0
0 0 0 0 0 6 0 0
0 0 3 0 0 0 0 0

1 0 0 0 0 0 0 0
0 0 0 0 0 0 7 0
0 0 0 4 0 0 0 0
0 0 0 0 0 6 0 0 Solución número 2
0 0 0 0 0 0 0 8
0 2 0 0 0 0 0 0
0 0 0 0 5 0 0 0
0 0 3 0 0 0 0 0

1 0 0 0 0 0 0 0
0 0 0 0 0 6 0 0
0 0 0 0 0 0 0 8
0 0 3 0 0 0 0 0 Solución número 3
0 0 0 0 0 0 7 0
0 0 0 4 0 0 0 0
0 2 0 0 0 0 0 0
0 0 0 0 5 0 0 0

Oprima <ENTER> para continuar.

1 0 0 0 0 0 0 0
0 0 0 0 5 0 0 0
0 0 0 0 0 0 0 8
0 0 0 0 0 6 0 0 Solución número 4
0 0 3 0 0 0 0 0
0 0 0 0 0 0 7 0
0 2 0 0 5 0 0 0
0 0 0 4 0 0 0 0
Sección 9.6 Manejo de archivos 451

00 0 0 0 6 0 0
1 0 0 0 0 0 0 0
0 0 0 0 5 0 0 0
0 2 0 0 0 0 0 0 Solución número 5
00 0 0 0 0 0 8
00 3 0 0 0 0 0
00 0 0 0 0 7 0
0 0 0 4 0 0 0 0

0 0 0 4 0 0 0 0
1 0 0 0 0 0 0 0
0 0 0 0 5 0 0 0
0 0 0 08 0 0 0 Solución número 6
02 0 0 0 0 0 0
00 0 0 0 0 7 0
0 0 3 0 0 0 0 0
0 0 0 4 0 6 0 0

Oprima <ENTER> para continuar.


Adiós.

9.6 MANEJO DE ARCHIVOS

A continuación se retoman los conceptos y operaciones elementales sobre archivos des-


critas en la sección 8.8, para traducirlos a C++.

Lectura y escritura a disco


Como se dijo en el capítulo 8, dentro de un programa los archivos se manejan mediante su
descriptor. Además, en C++ para recibir las salidas producidas por el programa se debe
seleccionar un archivo de salida (output) , lo cual se logra con la clase especial

ofstream archivo;

perteneciente a la biblioteca <fst ream. h>, que debe previamente incluirse en el programa
fuente. A partir de allí, la variable especial archivo (un nombre escogido por el progra-
mador) se usará como el descriptor del archivo en disco, y será el nombre interno con el
que el programa lo identificará.
Pero aún hace falta asignar un espacio físico en el disco (o diskette) magnético de
la computadora, lo cual se logra mediante la invocación a la función miembro (o méto-
do) open del objeto archivo, que requiere ya conocer el nombre externo asignado al
archivo físico (que también —por simplicidad— puede ser una cadena fija de caracteres
entre comillast):

archivo.open(nombre_externo);

De allí en adelante se podrá enviar datos a arc hivo mediante el operador de inserción
ya conocido. Es decir, la instrucción

archivo « algo;

(t) Como el carácter \ tiene significado especial en C++, deben emplearse dos de ellos juntos (1
para especificar trayectorias tipo MS-DOS en los nombres de archivos.
452 Capítulo 9 La codificación en la programación moderna: C++

escribe en el disco el contenido de la variable algo, con el formato correspondiente al tipo


de dato del que se trate.
Antes de terminar el programa debe cerrarse el archivo, mediante la función miem-
bro close:
archivo.close();

Al finalizar la ejecución, en el sistema de archivos de la computadora habrá un nuevo


archivo, por completo independiente del programa recién utilizado para crearlo.
De esta forma, el pseudocódigo de la pág. 381 quedó codificado en C++ como sigue:

// Programa para escribir registros en el disco


#include <iostream.h>
#include <fstream.h> // Biblioteca para manejo de disco
void main()
{

const int n = 40;


int dato;
int i;
char nombre[n];
ofstream archivo; // Archivo para salida
cout « "\nDame nombre para nuevo archivo: ";
cin.getline(nombre,n);
archivo.open(nombre);
cout « "\nDame un núm. entero o <Ctrl>-Z para terminar:
for (i = 0; cin » dato; i++) {
archivo « dato « endl;
cout « "Dame un núm. entero o <Ctrl>-Z para terminar:
}

cout « "\nSe escribieron " « i


« " registros en el archivo." « endl;
archivo.close();
1

Hay otros aspectos propios de C++ utilizados en el programa. Con la instrucción


compuesta
cin.getline(nombre,n);

se leen hasta n-1 caracteres en la variable nombre, definida como un arreglo de char. La
lectura incluye todos los caracteres que se tecleen hasta antes de oprimir la tecla <Enter>
o llegar al límite predefinido de n-1 (porque el n-ésimo se reserva para el terminador del
arreglo de caracteres), y es una forma estándar de leer nombres en C++.
Luego se envía un mensaje pidiendo el primer dato, que el f or se encarga de leer de
la pantalla y escribir al disco, lo cual seguirá haciendo mientras el usuario no dé la señal
de fin de archivo. Como se dijo en el capítulo 8, para indicar fin de archivo desde el
teclado, en los sistemas Unix y Macintosh se emplea el par <Ctrl>-D (las teclas "Control"
y "D" al mismo tiempo), mientras que en las computadoras pesonales se usa <Ctrl>-Z. Es
decir, la expresión
cin » dato

que además funciona como la parte condicional del for, tiene un valor de verdad 1 (verda-
dero o true) mientras la variable dato sea diferente de fin de archivo y por ello puede
Sección 9.6 Manejo de archivos 453

usarse para controlar el ciclo. Al llegar al fin de archivo se volverá 0, con lo que se aban-
dona el ciclo de lectura. Otra forma de hacer lo mismo sería preguntar por el valor lógico
devuelto por la llamada especial cin eof ( ), que es t rue si en alguna operación anterior
se ha detectado el fin de archivo en el flujo de entrada y false en caso contrario, aunque
en este programa no se usó.
Cuando en C++ un flujo llega al fin de archivo, cambia internamente su estado a una
condición de error y queda inutilizable. Luego se puede cerrar, o bien restaurar a su valor
"bueno" original mediante la llamada especial

cin.clear( )t.

A continuación se muestra un ejemplo real de la ejecución del programa .

Dame nombre para nuevo archivo: ALFA


Dame un núm. entero o <Ctrl>-Z para terminar: 7
Dame un núm. entero o <Ctrl>-Z para terminar: 32
Dame un núm. entero o <Ctrl>-Z para terminar: 0
Dame un núm. entero o <Ctrl>-Z para terminar: 15
Dame un núm. entero o <Ctrl>-Z para terminar: <Ctrl>-Z
Se escribieron 4 registros en el archivo.

Por otra parte, se puede escribir un segundo programa para tomar un archivo ya exis-
tente en disco, abrirlo y extraer (») su contenido. Para ello deberá declararse la existencia
de un archivo con datos de entrada (input) mediante la clase ifstream, también pertene-
ciente a la biblioteca <fstream. h>:

ifstream archivo;

Después habrá que abrirlo, aunque en este caso nos interesa abrir solamente archivos
de datos previamente creados, para lo cual se usa la instrucción:

archivo.open(nombre_externo, ios::nocreate);

que evita crearlo si aún no existen. De allí en adelante se podrá leer datos de archivo
mediante el operador de extracción ya conocido.
Es decir, la instrucción

archivo » algo;

(t) Y sin embargo, con algunos compiladores surgen problemas inesperados al manejar el carác-
ter de fin de archivo desde el flujo de entrada c in, representado por el teclado, porque ya no es
posible recuperarlo del estado de error eof , con lo que el programa queda inutilizable. En el ejem-
plo anterior no sucede eso porque el programa termina inmediatamente después de haber leído el
fin de archivo.
En teoría, es claro que un compilador no debería tener errores como éstos, pero la realidad es
tristemente diferente.
(tt) Siendo C++ un lenguaje "para especialistas", al emplear esta opción se escribe una construc-
ción completa de la programación por objetos y, mediante el operador : : de resolución de alcance, se
especifica la clase (ios) a la que pertenece la función miembro deseada (nocreate). ¿Podría
ser más claro?
454 Capítulo 9 La codificación en la programación moderna: C++

extrae del archivo en disco un valor del tipo de algo y se lo asigna a esa variable, em-
pleando un formato correspondiente al tipo de dato en cuestión. Se espera que la variable
receptora de la "rebanada" extraída del disco sea precisamente del mismo tipo que el de la
variable previamente grabada allí; de no ser así podrá haber toda clase de molestos pro-
blemas (que el programa aborte, que entre en un ciclo de ejecución ilimitado, o que los
datos sean espurios; todo lo cual constituye un amplio tema de estudio detallado, a parte
del cual nos referiremos más adelante).
Como en caso anterior, el archivo debe cerrarse antes de salir del programa. Ésta es la
traducción a C++ del programa para leer datos de un archivo en disco:

// Programa para leer registros del disco


#include <iostream.h>
#include <fstream.h> // Biblioteca para manejo de disco
void main()
{

const int n = 40;


char nombre[n];
int dato;
int i = 0;
ifstream archivo; // Archivo de entrada
do {
cout « "\nDame nombre del archivo: ";
if (Icin.getline(nombre,n)) return ; // Cuidado con EOF
archivo.open(nombre, ios::nocreate);
if (!archivo) cerr « "\nArchivo inexistente.\n";
} while (!archivo);
cout « "\nContenido del archivo:\n\n";
archivo » dato; // Lectura inicial
while (!archivo.eof()) {
i++;
cout « dato « endl;
archivo » dato;
}
cout « "\nSe leyeron " << « " registros." « endl;
archivo.close();
}

Nuevamente, hay algunos aspectos propios de C++ empleados en el programa. Se


explicó ya el uso de

archivo.open(nombre, ios::nocreate);

para evitar que el compilador automáticamente cree un archivo si éste no existe. Podemos
entonces enviar un mensaje de error (utilizando el flujo cerr, que se garantiza será la
pantalla) para avisar al usuario que proporcionó un nombre de archivo inexistente, y pro-
ceder nuevamente a solicitarle otro.
Como además, por definición en C++, el flujo cin internamente devuelve un valor 1
mientras no le haya llegado el fin de archivo, la instrucción

if (lcin.getline(nombre,n)) return; // Cuidado con EOF

simplemente (aunque con poca elegancia) termina la ejecución si el usuario pro-


porciona <Ctrl>-Z como "nombre" del archivo, para evitar el molesto ciclo ilimita-
Sección 9.6 Manejo de archivos 455

do que ocurriría en ese caso, y que obligaría al usuario a abortar el programa o pedir
ayuda.t
Por su parte, el ciclo de lectura emplea la variante

while (larchivo.eof())

para extraer los datos del disco e imprimirlos mientras no se haya llegado al fin de
archivo.
Finalmente, éste es un ejemplo real de la ejecución del programa:

Dame nombre del archivo: nada


Archivo inexistente.

Dame nombre del archivo: ALFA

Contenido del archivo:

7
32
0
15

Se leyeron 4 registros.

Existe una gran cantidad de variantes para el manejo de archivos en C++, y algunas
de ellas se explorarán a continuación.

Sistema de calificaciones
Ahora se codificará en C++ el pequeño sistema de calificaciones cuyo pseudocódigo se
diseñó en la sección 8.8.
Como se dijo, la fuente fundamental de información será un archivo principal con los
datos de cada alumno. Ésta es la forma definida para el archivo:
APELLIDO NOMBRE CALIFICACIÓN
4 20 caracteres <-15 caracteres ---> <entero> -->

Para manejarla se empleará una nueva característica del lenguaje, consistente en la


declaración de una estructura de datos compuesta creada especialmente por el programador
para este fin (véase la sección 8.4). Esta estructura es muy limitada para ser de verdadera
utilidad práctica, pues no permite sino un número fijo (y pequeño) de caracteres para cada
campo, pero será suficiente para nuestras finalidades, que de antemano restringimos.
const int ap = 21; // Longitud máxima del apellido
const int nom = 16; // Longitud máxima del nombre
// Estructura global de un registro de datos
struct registro {
char apellido[ap];
char nombre[nom];
int calif;
Y;

(t) El tema de la "programación a la defensiva" para evitar errores o engaños por parte de los
usuarios es largo y agotador, y muchas veces nos obligará a incluir código adicional para probar
casos "imposibles" o "ilógicos" que, si se aceptaran, harían al programa entrar en ciclos ilimitados
o comportarse en formas inesperadas o sumamente desagradables.
456 Capítulo 9 La codificación en la programación moderna: C++

Con la instrucción st ruct se declaran estructuras de datos con una morfología, o


forma interna, escogida por el programador, para lo cual puede hacer uso de las estructu-
ras de datos primitivas atómicas (int , float , etc.) o compuestas (arreglos).
Después de declarar la estructura se deberá definir al menos un ejemplo (instance, en
inglés) de ella; es decir, primero se define para el compilador la existencia de entidades
de un nuevo tipo (registro en este caso) y luego se les "da vida" declarándolas y apar-
tando memoria para ellas, como en este caso:

registro alumno; // Nueva variable de tipo registro

Con lo anterior se crea una nueva variable, alumno : el primer caso de lo que llama-
Acceso a elementos mos una "estructura de datos compuesta no homogénea creada por el programador". Pre-
de una estructura cisamente porque se trata de una variable compuesta, muchas veces debemos distinguir
entre sus componentes internos, lo cual en C++ se logra mediante el "operador punto": .
que indica el componente al cual se está haciendo referencia. Por ejemplo, alumno . c alif
se refiere al número entero que le sirve a la variable alumno como calificación. Es decir,
es válido escribir cosas como

alumno.calif = 90;

o tal vez

cout « "Su calificación es: " « alumno.calif « endl;

Una de las pocas veces en las que no es necesario distinguir entre los componentes
internos de una variable tipo st ruct es cuando se le declaran valores iniciales a tiempo de
compilación, como en

registro alumno = { 0}; // Sólo es válido en declaraciones

que asigna valores iniciales de cero a todos sus componentes, aunque el compilador mar-
cará un error si se intenta escribir

alumno = { 0}; // Error

como si fuera una inocente asignación del tipo i = 0; . porque no se está especificando cuál
de los múltiples campos recibirá el valor en ese momento.
Pero una instrucción de asignación (parcial) como

alumno.apellido[0] = ' A';

sí es válida, porque es totalmente explícita.


Como en las primeras versiones de C++ no existen datos tipo cadena, las cosas se
complican un poco al manejar cadenas de caracteres, como veremos más adelante, porque
el lenguaje sólo reconoce directamente arreglos de caracteres individuales.
Es decir, desafortunadamente no es posible escribir asignaciones como

alumno.apellido = "Salinas"; // Error de compilación

a menos que la cadena entrecomillada tenga exactamente un carácter menos que la longi-
tud declarada para el componente apellido (porque, como se mencionó en la sección
anterior, el compilador reserva la última posición de un arreglo de caracteres para el ca-
rácter especial que funge como terminador de la cadena: \0).
Sección 9.6 Manejo de archivos 457

Por otra parte, sí resulta fácil y conveniente emplear las funciones miembro prede-
finidas de C++ para leer cadenas de caracteres, como

cin.getline(alumno.apellido, ap);

que llena el arreglo apellido con hasta ap-1 caracteres tecleados por el usuario.
Con los anteriores comentarios, ya estamos listos para estudiar el programa principal
del sistema y sus declaraciones previas. Antes de seguir advertiremos al lector que los
diversos compiladores y sistemas operativos disponen de operaciones e instrucciones que
no siempre están estandarizadas, por lo que es casi seguro que los detalles cambien de una
instalación a otra.
Todo nuestro código está incluido en un solo archivo fuente, e inicia así:

// Pequeño sistema de manejo de archivos


// alumnoC.cpp

// Diversas bibliotecas requeridas:


#include <stdlib.h>
#include<stdio.h>
#include <string.h>
#include <iomanip.h>
#include <fstream.h>

// Nombre del archivo físico


const char princ[] = "C2000";

const int ap = 21; // Longitud máxima del apellido


const int nom = 16; // Longitud máxima del nombre

// Prototipos
void eliminar();
void altas();
void retocar(char);
void impresion();
int existe(char[],char[], int &, long &);

/* main() *1
void main()

char opcion;

cout « "1n PEQUEÑO SISTEMA DE REPORTES C++1n";


cout « "1n Estas son las operaciones disponibles:1n";
do
cout « endl « endl;
cout « " A. PONER CALIFICACIÓN A UN NUEVO ALUMNO\n";
cout « " B. BORRAR UN REGISTRO\n";
cout « " C. CAMBIAR UNA CALIFICACION\n";
cout « " D. IMPRIMIR EL ARCHIVO\n";
cout « " X. ELIMINAR EL ARCHIVO DE DATOS\n";
cout « " F. FIN\n";
cout « "\n Escriba su opción: ";
458 Capítulo 9 La codificación en la programación moderna: C++

if (!cin.get(opcion)) break; // Para atrapar EOF


cin.ignore(80, '\n'); // Ignorar el resto del renglón
opcion = toupper(opcion); // Unificar todo a mayúsculas
switch (opcion) {
case 'A': altas(); break;
case 'B': retocar('b'); break;
case 'C': retocar('c'); break;
case 'D': imprimir(); break;
case 'X': eliminar(); break;
case 'F': break;
default: cout « "1n Opción desconocida\n";
}

while (opcion I= 'F');


cout « "\n Adiós.";
} // main

Observe que hay cinco prototipos de funciones: cuatro corresponden a rutinas de


segundo nivel —llamadas directamente por main— y una más es una función auxiliar,
para determinar la posible existencia de un dato previamente grabado en el archivo en
disco. Además, a diferencia del pseudocódigo del capítulo 8, decidimos unificar en una
sola las funciones de borrar un registro y cambiar una calificación, porque internamente
son muy parecidas; la diferencia estará dada por la letra b o c que se le envíe como parámetro,
como se comprenderá cuando se revise ese código.
Tal vez lo único que resalte hasta ahora sea el renglón

opcion = toupper(opcion);

en donde se hace uso de una función (incluida en la biblioteca <stdlib.h> o en <ctype . h>)
para convertir el carácter recién leído a mayúsculas (o dejarlo como estaba si ya era una
letra mayúscula).
A continuación viene el código de una función para eliminar el archivo de datos, que
tampoco existía en el pseudocódigo; además de considerarla útil, expone más sobre el
manejo de archivos en C++:

/* eliminar()
// Eliminar el archivo de datos
void eliminar()
{
char c;

ifstream prueba;
prueba.open(princ, ios::nocreate);
prueba.close();
if (prueba)
cout « "\n ¿Seguro? (s/n): ";
cin.get(c);
cin.ignore(80, '\n');
c = toupper(c);
if (c == 'S') {
if (remove(princ)) // La función devuelve 0 si lo hizo
cout « "1n No se pudo eliminar el archivo.";
else cout « " Archivo eliminado.1n";
Sección 9.6 Manejo de archivos 459

else ; // Enunciado nulo, imprescindible en este caso.


}

else cout « "\n El archivo no existe.\n";


} // eliminar

Tuvimos que hacer uso del descriptor de archivos prueba para poder emplear la op-
ción los : : nocreat e ya explicada, que devuelve cero si el archivo —que debe ser de tipo
ifstream — no existe. Primero averiguamos su existencia, y sólo entonces pedimos con-
firmación para eliminarlo mediante la función especial predefinida remove. Siempre de-
bemos tratar de formular la menor cantidad de preguntas al usuario.
Luego viene la declaración de la estructura genérica del registro de datos, que poste-
riormente cada función empleará para definir variables locales de ese tipo. En esta sec-
ción "semi-global" —con validez desde este punto y hasta el final del archivo fuente—
definimos también el signo especial que servirá para marcar el primer carácter de los
registros borrados. Escogimos un símbolo que no se pueda dar desde el teclado, para lo
cual consultamos una tabla ASCII como la que aparece al final de este libro.

// Estructura global de un registro de datos


struct registro
char apellido[ap];
char nombre[nom];
int calif;
};
char marca = .\ 177'; // Carácter especial de "borrado"

Toca el turno al primer módulo, con el que se pone calificación a un alumno. Como
indicaba el pseudocódigo del capítulo 8, se crea un nuevo archivo de datos si aún no
existe, y se procede a pedir el nombre y calificación de los nuevos alumnos, verificando
antes que no estén duplicados:

/* altas( ) */
// Da de alta nombres y calificaciones en el archivo en disco
void altas()
{

int i = 1;
int x; // Parámetro auxiliar; aquí sólo ocupa un espacio
long y; // Parámetro auxiliar; aquí sólo ocupa un espacio

// Definición de una nueva variable tipo "registro"


registro alumno = {0};

ifstream prueba;
ofstream disco;

/ /Abrir el archivo de datos, o crearlo si no existía


prueba open ( orine , ios : : nocreate) ;
prueba . close ( ) ;
if ( !prueba) {
cout « " \ n \ n El archivo no existía; se crea ahora . \ n" ;
disco . open ( princ) ;
disco . close ;

}
460 Capítulo 9 La codificación en la programación moderna: C++

// Lectura inicial
cout « endl;
cout.width(3); // Alinear a la derecha el contador
cout « i « " Apellido, o un 0 para terminar: ";
if (!cin.getline(alumno.apellido,ap)) return; // Cuidar EOF
// Continuar si no fue el último
while (alumno.apellido[0] 1= '0') {
cout « " Nombre: ";
if (!cin.getline(alumno.nombre,nom)) return; // Cuidar EOF
// Cuidado con datos duplicados
if (!existe(alumno.apellido, alumno.nombre, x, y)) {
do {
cout « " Calificación (0-100): ";
cin » alumno.calif;
cin.ignore(80, '\n'); // Ignorar el resto del renglón
} while (alumno.calif < 0 II alumno.calif > 100);
disco.open(princ, ios::app);
disco.write((char *)&alumno, sizeof(registro));
disco.close();
i++;
1
else cout « "\n ERROR: esa persona ya existe.\n";
cout « endl;
cout.width(3);
cout « i « " Apellido, o un 0 para terminar: ";
cin.getline(alumno.apellido,ap);
}
cout « "\ 11 Total de alumnos dados de alta: " « i-1 « endl;
disco.close();
} // altas

Hay dos variables auxiliares x, y que en esta función no cumplen ningún papel más
allá de ocupar los últimos dos espacios predefinidos en el prototipo de la función auxiliar
existe, que devuelve cero si en el archivo en disco no existe el alumno cuyo apellido y
nombre le fueron pasados como parámetros. Las siguientes funciones sí aprovechan esos
dos parámetros finales devueltos por existe, pero aquí no se usan.
El módulo inicia escribiendo un número secuencial que servirá de guía para saber
cuántos alumnos se han dado de alta. Se usó una función miembro especial del flujo de
salida para hacer que la siguiente impresión aparezca alineada a la derecha:

cout.width(3);

Definimos una variable local llamada alumno, de tipo registro, con valores inicia-
les de cero, para almacenar los datos de cada alumno procesado. Si el apellido y nombre
son nuevos entonces se pide (y valida) su calificación, para proceder a grabar esos datos
en el disco magnético. Para ello debe abrirse el archivo (que fuera declarado como archi-
vo de salida al inicio del módulo) en el modo app (to append en inglés significa "agregar
al final"):

disco.open(princ, ios::app);

Luego vendrá la operación misma de escritura, que requiere explicación. En el pequeño


programa para escribir números en disco expuesto al inicio de esta sección, se decía que
Sección 9.6 Manejo de archivos 461

el descriptor del archivo se puede emplear para las operaciones de entrada y salida usua-
les, como si fuera un flujo más: Lectura/escritura
de variables
disco « dato « endl; tipo struct

Pero esto sólo es válido cuando la variable no es compuesta y definida por el progra-
mador, como en nuestro caso actual, en donde en realidad se trata de un struct de tipo
registro. Para "subir" al disco (o para leer de él) una variable así, es forzoso emplear la
función miembro write (o bien read), que opera sobre el flujo mediante el operador
punto. Pero además, estas funciones únicamente están definidas para procesar datos tipo
char, por lo que si la variable a escribir (o a leer) es de un tipo diferente, debe ser conver-
tida utilizando el operador ( cha r *) , que mediante un apuntador la obliga temporalmente
a comportarse como si fuera char; para ello, la variable debe antecederse del operador &,
que obtiene su dirección. Por último, write (o read) espera saber la cantidad de bytes que
transportará al flujo (al disco en este caso), para lo cual se usa la función predefinida
sizeof ( ), que la calcula cuando se le invoca. Con todo estot, la instrucción para grabar
en el archivo en disco una "rebanada" que contiene la variable alumno es

disco.write((char *)&alumno, sizeof(registro));

Es decir, una sola instrucción transporta al flujo toda una elaborada estructura, que
en este caso consta de dos arreglos de caracteres y un entero. Naturalmente, la instruc-
ción para leer esa rebanada del archivo en disco deberá ser similar, como veremos más
adelante.
Luego de la operación de escritura habrá que cerrar el archivo, mediante la función
miembro ya descrita

disco.close();

También se podría mantener abierto el archivo durante todo el procesamiento, pero


entonces habría que pasar su descriptor como parámetro por referencia a las demás
funciones, para mantenerlo actualizado en todo momento. Para no complicar más las
cosas, en este pequeño sistema decidimos abrir el archivo, y cerrarlo después, en cada
acceso.
A continuación transcribimos el módulo siguiente, encargado tanto de borrar un re-
gistro existente como de cambiar una calificación. Aunque en el pseudocódigo del capítu-
lo 8 éstos eran dos módulos separados, decidimos unificarlos por su gran similaridad.
Dijimos ya que la gran limitante de los archivos secuenciales es la práctica imposibilidad
de eliminar en realidad un registro, porque el archivo no puede contener "huecos" físicos;
esto obliga a simular su eliminación mediante alguna marca que lo vuelva "invisible"
—en términos lógicos— ante el programa aunque siga ocupando un lugar dentro del
archivo. Una vez localizado (si es que existe, claro), se marcará como "borrado", alteran-
do el primer carácter del apellido.

/* retocar(modo) */
// Borrar (modo='b') o cambiar (modo='c') datos del archivo
void retocar(char modo)
{

(t) ¿Recuerda el lector nuestro comentario del capítulo 7 sobre la imprudencia de llamar "juego de
niños" a la programación en C++?
462 Capítulo 9 La codificación en la programación moderna: C++

int i = 1;
int calif; // Variable temporal
long reg; // Contador de registros
registro alumno = {0};

// Verificar si existe el archivo


ifstream prueba;
ofstream disco;
prueba.open (princ, ios::nocreate);
prueba. clase();
if (prueba) {
// Lectura inicial
cout « endl;
cout.width(3);
cout « i « " Apellido del alumno ";
if (modo == 'b') cout « "a borrar,\n";
else cout « "a quien\n se desea cambiar la calificación,\n";
cout « " o un 0 para terminar: ";
if (!cin.getline(alumno.apellido,ap)) return; // Cuidar EOF
// Continuar si no fue el último
while (alumno.apellido[0] != '0') {
cout « " Nombre: ";
if (!cin.getline(alumno.nombre,nom)) return; // LEOF?
if (existe(alumno.apellido, alumno.nombre, calif, reg)) {
if (modo == 'b')
// Marcarlo como borrado e inutilizarlo
alumno.apellido[0] = marca;
else { // Cambios
cout « "\n Su calificación anterior es " « calif « endl;
do {
cout « " Nueva calificación (0-100): ";
cin » alumno.calif;
cin.ignore(80, '\n'); // Ignorar el resto del renglón
if (calif == alumno.calif) {
cout « " Es la misma...\n";
i--;
}
} while (alumno.calif < 0 11 alumno.calif > 100);
}

disco.open(princ, ios::ate);
// Va directo a ese registro
disco.seekp(reg*sizeof(registro), ios::beg);
disco.write((char *)&alumno, sizeof(registro));
disco.close();
j++;
if (modo == 'b') cout « "\n Borrado.\n";
else if (calif 1= alumno.calif) cout « "\n Cambiada.\n";
}
else cout « "\n ERROR: esa persona no está registrada.\n";
cout « endl;
cout.width(3);
cout « i « " Apellido del alumno ";
Sección 9.6 Manejo de archivos 463

if (modo == 'b') cout « "a borrar,\n";


else cout « "a quien1n se desea cambiar la calificación,1n";
cout « " o un 0 para terminar: ";
if (!cin.getline(alumno.apellido,ap)) return; // ¿EOF?
}

if (modo == 'b') cout « "1n Total de alumnos borrados: " « i-1 « endl;
else cout « "1n Total de calificaciones cambiadas: " « i-1 « endl;
}

else cout « "\n El archivo no existe:\n"


aún no se han dado de alta alumnos.\n";
} // retocar

La novedad en este módulo es el acceso directo a un registro. Como todas las rebana-
das del archivo son del mismo tamaño —es decir, sizeof(registro)—, se podrá llegar Acceso directo
directamente a cualquiera de ellas sin tener que leer las previas (saltándoselas), mediante a archivos
las funciones miembro see kg (para lectura: get) y seekp (para escritura: put). La instruc-
ción de acceso directo en modo de escritura es

disco.seekp(reg*sizeof(registro), ios::beg);

que llega directamente al registro localizado en la posición reg -1 habiendo partido desde el
inicio (beg ining) del archivo. El parámetro por referencia reg —que debe ser de tipo long—
fue previamente calculado por la función existe, que lo inicia desde cero y lo incrementa
en uno cada vez que lee un nuevo registro, indicando así su posición relativa al contar el
número de registros de tamaño fijo que se saltaron hasta llegar a él. Aquí sí se hace uso de los
dos parámetros por referencia calif y reg de la función existe, que en el módulo anterior
de altas no se emplearon. El primero simplemente trae de regreso la calificación contenida
en el registro pedido, para mostrarla al usuario y pedirle la modificación.
Dependiendo del parámetro con el que fue llamada la función retocar, entonces se
actualiza el registro encontrado, porque se marcó como borrado o bien se alteró su cali-
ficación:

disco.write((char *)&alumno, sizeof(registro));


disco.close();

Existe un caso especial, cuando la nueva calificación es la misma que la anterior, lo


cual amerita un mínimo mensaje de advertencia de que se ignoró ese reemplazo por ser
obvio. Hemos dicho que conviene reducir la cantidad de mensajes y preguntas para el
usuario, haciendo que la lógica del programa se anticipe a las situaciones previsibles.
Ahora se muestra el módulo de impresión del archivo, en donde —como siempre—
se dedican bastantes renglones para lograr una mínima presentación visual atractiva, aun-
que ni siquiera se intenta hacer uso de las muy elaboradas (y muy costosas en términos de
cantidad de líneas de programa fuente) facilidades para desplegar colores, tipos de letra y
ventanas o hacer uso del inestimable "ratón". De hecho, la llamada "programación vi-
sual" prácticamente requiere un curso (y un libro) especial, dada la amplitud, versatilidad
y complejidad de la GUI (Graphical User Interface: Interfaz gráfica para el usuario —son
los temas PI18 e IH17-18 de los Modelos Curriculares)t.

(t) Una parte muy considerable del código de los programas que funcionan bajo entornos gráficos
tipo Windows®, se dedica a las complejidades del despliegue en la pantalla, y otra a manejar e
interpretar el movimiento del ratón.
464 Capítulo 9 La codificación en la programación moderna: C++

Pues bien, nuestra rutina de presentación de resultados se comporta en forma pri-


mitiva, porque simplemente despliega renglones secuencialmente en la pantalla, aunque
gracias a eso es lo más sencilla posible.

/* imprimir() *1
// Muestra los contenidos del archivo en disco
void imprimir()
{

int i = 0;
float suma = 0.0;
registro alumno = {0};

ifstream prueba;
ifstream disco;

// Verificar si existe el archivo


prueba.open(princ, ios::nocreate);
prueba.close();
if (prueba) {
// Leer los datos del disco
disco.open(princ, ios::beg);
cout « "1n REPORTE DE CALIFICACIONES\n";
disco.read((char *)&alumno, sizeof(registro)); // Lectura inicial
while(Idisco.eof()) {
if (alumno.apellido[0] 1= marca) {

cout « endl;
cout.width(3);
cout « i «
cout « alumno.apellido « " ";
cout « alumno.nombre;
cout.width(ap+nom-strlen(alumno.apellido)-
strlen(alumno.nombre));
cout « alumno.calif;
suma += alumno.calif;
}

disco.read((char *)&alumno, sizeof(registro));


}
if (i > 0) {
cout « endl « endl;
cout.width(ap+nom+3);
cout « "Promedio: ";
cout.precision(2);
cout.setf(ios::fixed);
cout.width(6);
cout « suma/i « endl;
} else cout « "\n El archivo está vacío.\n";
}

else cout « "1n El archivo no existe:\n"


« o aún no se han dado de alta alumnos.\n";
} // imprimir

Como en este módulo se lee a partir del archivo, su descriptor fue


ifstream disco;
Sección 9.6 Manejo de archivos 465

El algoritmo es muy sencillo: colocarse al inicio del archivo, con

disco.open(princ, ios::beg);

y, mediante la función miembro para leer como un todo una estructura del archivo en disco
—en forma idéntica a como se escribió—, ir secuencialmente obteniendo los registros:

disco.read((char *)&alumno, sizeof(registro));

Después, si el registro no está marcado como "borrado", se imprime y se lleva cuenta


de la calificación, para al final mostrar el promedio.
Quisimos mostrar los datos de cada alumno en una forma menos rígida que el estilo
tabular, pero ello no es sin costo. Si simplemente hubiéramos escrito el apellido (un arre-
glo de caracteres de tamaño fijo ap) y luego el nombre (ídem, de tamaño nom), seguido de
la calificación, los renglones aparecerían así:

RODRÍGUEZ JUAN 100


SOTO ADRIANA 90

pero consideramos más elegante que el nombre esté inmediatamente después del apelli-
do, seguidos por la calificación en una posición fija:

RODRÍGUEZ JUAN 100


SOTO ADRIANA 90

Para eso es necesario calcular la longitud efectiva de cada campo (es decir, la canti-
dad de caracteres hasta antes del primer blanco), y restarla de la suma de sus longitudes
fijas. C/C++ dispone de un conjunto de funciones para manipulación de cadenas de carac-
teres, que se obtienen habiendo incluido la biblioteca <st ring . h> . st rien ( arreglo de
caracteres) devuelve la longitud de una cadena, y entonces con la instrucción

cout.width(ap+nom-strlen(alumno.apellido)-
strlen(alumno.nombre));

se logra el efecto deseado.


Por su parte, el renglón

prom += alumno.calif;

es la forma abreviada de decir a = a + b para cualesquier variables a y b. (Es decir,


a += b; es sinónimo de a = a + b;.)
Por último, las instrucciones

cout.precision(2);
cout.setf(ios::fixed);
cout.width(6);

especifican un formato de seis caracteres de ancho, con un valor redondeado a dos


decimales.
La última función se encarga de determinar la existencia de un cierto registro en el
archivo en disco. Recibe las dos cadenas (apellido y nombre) a buscar como parámetros
y, si encontró el registro, devuelve su posición relativa y su calificación. La función de-
vuelve cero cuando llega el fin de archivo sin haber encontrado el registro pedido.
466 Capítulo 9 La codificación en la programación moderna: C++

Después de leer la rebanada del archivo en disco, la averiguación se efectúa mediante


dos invocaciones (una para el apellido y otra para el nombre) a la función de compara-
ción de cadenas de C/C++, que devuelve cero si tiene éxito:

strcmp(temp.apellido, cadí) 11 strcmp(temp.nombre, cad2)

Observe que la lectura se hace sobre una variable local temp, de tipo registro, y que
los últimos dos parámetros se pasan por referencia para que conserven su valor y lo pue-
dan reportar de regreso.

/* existe() *1
// Localizar un registro en el archivo
int existe(char cadí[], char cad2[], int &calif, long &reg)
{

int ya = 0;
registro temp = {0};

ifstream disco;

disco.open(princ);
reg = OL;
disco.seekg(reg, ios::beg); // Colocarse al inicio del archivo
disco.read((char *)&temp, sizeof(registro)); // Lectura inicial
calif = temp.calif; // La usará el módulo retocar('c')
while ((!disco.eof()) && !ya)
// strcmp devuelve 0 si las cadenas son iguales
if (strcmp(temp.apellido, cadí) 11 strcmp(temp.nombre, cad2)) {
disco.read((char *)&temp, sizeof(registro)); // Siguiente
calif = temp.calif;
reg++; // Lleva cuenta del número de registros leídos
}

else ya = 1; // Sí fue
return ya;
disco.close();
} // existe

Con esto termina el código fuente del sistema de reporte de calificaciones en C++. A
continuación se muestran ejemplos reales de su uso en nuestra computadora; aparecen
subrayadas las respuestas del usuario:

PEQUEÑO SISTEMA DE REPORTES C++

Estas son las operaciones disponibles:


A. PONER CALIFICACIÓN A UN NUEVO ALUMNO
B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: d
El archivo no existe:
aún no se han dado de alta alumnos.
Sección 9.6 Manejo de archivos 467

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: a

El archivo no existía; se crea ahora.


1 Apellido, o un 0 para terminar: Sartre
Nombre: Jean Paul,
Calificación (0-100): 100

2 Apellido, o un 0 para terminar: Kissinger


Nombre: Henry
Calificación (0-100): fil
3 Apellido, o un 0 para terminar: Pérez
Nombre: María
Calificación (0-100): 92

4 Apellido, o un 0 para terminar: Madero


Nombre: Francisco
Calificación (0-100): lá

5 Apellido, o un 0 para terminar: Pérez


Nombre: María

ERROR: esa persona ya existe.

5 Apellido, o un 0 para terminar: Star


Nombre: Ringo
Calificación (0-100): al

6 Apellido, o un 0 para terminar: 1


Total de alumnos dados de alta: 5

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: d

REPORTE DE CALIFICACIONES
1 Sartre Jean Paul 100
2 Kissinger Henry 80
3 Pérez María 82
4 Madero Francisco 84
5 Star Ringo 95
Promedio: 88.20
468 Capítulo 9 La codificación en la programación moderna: C++

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: c

1 Apellido del alumno a quien


se desea cambiar la calificación,
o un 0 para terminar: Pérez
Nombre: Pedro

ERROR: esa persona no está registrada.

1 Apellido del alumno a quien


se desea cambiar la calificación,
o un 0 para terminar: Pérez
Nombre: María

Su calificación anterior es 82
Nueva calificación (0-100): fi4

Cambiada.

2 Apellido del alumno a quien


se desea cambiar la calificación,
o un 0 para terminar: 0
Total de calificaciones cambiadas: 1

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: b

1 Apellido del alumno a borrar,


o un 0 para terminar: Madero
Nombre: Francisco

Borrado.

2 Apellido del alumno a borrar,


o un 0 para terminar: 0
Total de alumnos borrados: 1

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
Sección 9.6 Manejo de archivos 469

X. ELIMINAR EL ARCHIVO DE DATOS


F. FIN
Escriba su opción: 1

1 Apellido del alumno a quien


se desea cambiar la calificación,
o un 0 para terminar: Star
Nombre: Ringo

Su calificación anterior es 95
Nueva calificación (0-100): 95
Es la misma...

1 Apellido del alumno a quien


se desea cambiar la calificación,
o un 0 para terminar: 0
Total de calificaciones cambiadas: 0

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: d

REPORTE DE CALIFICACIONES
1 Sartre Jean Paul 100
2 Kissinger Henry 80
3 Pérez María 84
4 Star Ringo 95
Promedio: 89.75

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: x
¿Seguro? (s/n): s
Archivo eliminado.
A. PONER CALIFICACIÓN A UN NUEVO ALUMNO
B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: f
Adiós.
470 Capítulo 9 La codificación en la programación moderna: C++

Pedimos al lector que estudie cuidadosamente todo el programa en C++ para que,
ayudado por el pseudocódigo, avance en la comprensión de estos puntos. Este sistema,
como todos los programas y ejemplos del libro, fue compilado y ejecutado en nuestra
computadora.
Resta tan sólo terminar este largo capítulo con la misma recomendación que hemos
dado antes: practique cuanto pueda.

EJERCICIOS

1. ¿Cuántas soluciones existen para el problema de las ocho damas? Para averi-
guarlo, modifique el programa de modo que encuentre todas las soluciones (es
preferible que no las imprima, sino que sólo indique cuántas son) y ejecútelo en
su computadora.
2. En el ejercicio 12 del capítulo 8 se pedía modificar el pseudocódigo del problema
de las ocho damas para que encontrara las soluciones que son independientes de la
rotación del tablero. Codifique esta nueva versión e imprima las soluciones únicas,
varias por página.
3. Codifique en C++ los programas que se pidieron en los ejercicios 2 a 8 del capítulo
8 y ejecútelos en su computadora.
4. Escriba un programa en C++ para traducir números arábigos a números romanos.
El programa debe emplear la representación abreviada para los números 4, 9, 40,
90, etc. Es decir, si recibe como entrada el número 44, por ejemplo, debe producir
como resultado xLiv y no xxxxiiii.
Éste es un buen ejemplo de un programa en el que se debe diseñar cuidadosa-
mente la relación entre el algoritmo (escrito, como siempre, inicialmente en pseu-
docódigo) y las estructuras de datos, ya que una buena elección de éstas producirá
un programa sencillo y conciso. Un ejemplo extremo de cómo el algoritmo podría
ser casi inexistente y las estructuras de datos complejas sería una gran tabla que
contuviera la representación de todos los números menores que 5000, en donde el
algoritmo simplemente localizara el número pedido y mostrara su equivalencia.
Otro ejemplo, en el extremo contrario, sería un algoritmo complejo que trabajara
sólo sobre los caracteres I, V, X, C, M y D, y que los agrupara según se requiriera.
Sin embargo, un algoritmo así tendría que considerar los (múltiples) casos especia-
les que surgen con las abreviaturas. Es preferible entonces encontrar un equilibrio
entre el algoritmo y las estructuras de datos (cosa que, además, vale para todo
programa).
5. El pequeño sistema de reportes expuesto en el apartado 9.6 añade los nuevos regis-
tros de datos siempre al final del archivo secuencial, aunque también podría reutilizar
el espacio ocupado por los registros "borrados", porque éstos no desaparecen del
archivo sino que sólo se marcan como no existentes. Modifique el programa para
que busque y llene esos espacios marcados, antes de simplemente agregar un nuevo
registro al final. ¿Habrá que modificar la estructura general del sistema? ¿Cuáles
módulos tendrá que cambiar? ¿Vale la pena el esfuerzo adicional?
6. El pequeño sistema de reportes muestra los registros en el orden en que fueron
proporcionados, sin clasificarlos alfabéticamente. Aunque éste es tema de un curso
posterior (de estructuras de datos), intente escribir un módulo en C++ que los
ordene, empleando el método más sencillo posible (que seguramente no será
muy eficiente).
Referencias para el capítulo 9 471

PALABRAS Y CONCEPTOS CLAVE

En esta sección se agrupan las palabras y conceptos de importancia estudiados


en el capítulo, para que el lector pueda hacer una autoevaluación, consistente en
describir con cierta precisión el significado de cada término, y no sentirse satisfe-
cho sino hasta haberlo logrado. Se muestran en el orden en que se describieron
o se mencionaron.
OPERADOR UNARIO
CLASE ESTÁNDAR
FLUJO DE ENTRADA/SALIDA
OPERADOR DE EXTRACCIÓN
OPERADOR DE INSERCIÓN
OBJETO cin
OBJETO cout
NOTACIÓN PUNTO
PROTOTIPOS
VARIABLE LOCAL
PASO DE PARÁMETROS
VARIABLE GLOBAL
BLOQUES
PASO DE ARREGLOS COMO PARÁMETROS
FUNCIONES
VALORES DE RETORNO
ARCHIVOS DE ENCABEZADO
PERSISTENCIA DE VARIABLES
FIN DE ARCHIVO
OPERADOR DE RESOLUCIÓN DE ALCANCE
REGISTROS DE UN ARCHIVO EN DISCO
PROGRAMACIÓN A LA DEFENSIVA
ESTRUCTURA DE DATOS COMPUESTA
INSTRUCCIÓN struct
DESCRIPTOR DE ARCHIVOS
ACCESO DIRECTO A ARCHIVOS
PROGRAMACIÓN VISUAL

REFERENCIAS PARA
En el capítulo 7 se reseñaron siete textos especializados en la enseñanza de la progra-
EL CAPÍTULO 9
mación mediante C/C++, que también deben considerarse propios para este capítulo.
Además, existen varias revistas mensuales (en inglés) dedicadas a la programación en
C, C++ y Java.
[ARNG97] Arnold, Ken y James Gosling, El lenguaje de programación Java, Addison-
Wesley, Madrid, 1997.
Escrito por uno de los autores originales del lenguaje Java, de la em-
presa Sun Microsystems (Gosling), en este libro se exponen los principios
sobre los que se basa este nuevo lenguaje, empleado incialmente en
Internet y de uso cada vez más extendido. En la página 1 dice: "Los pro-
gramas en Java se construyen a partir de clases. De una definición de
clase se pueden crear objetos que se conocen como ejemplos de esa cla-
se" y a partir de esa no muy extensa definición inician 14 capítulos,
dedicados —como lo indica la contraportada —"a los programadores
serios de Java". Hay libros más amigables para el neófito.
472 Capítulo 9 La codificación en la programación moderna: C++

[DEID99] Deitel, Harvey M. y Paul J. Deitel, C++ Cómo programar, segunda edición,
Pearson, México, 1999.
Traducción de un voluminoso libro (1184 páginas) sobre programa-
ción en C++. Los prolíficos autores (sobre todo el padre) han producido
muchos otros textos, entre los que sobresale el antecesor de éste: Cómo
programar en C/C++, mencionado en el capítulo 7. Este nuevo texto de-
dica las primeras 361 páginas —seis capítulos— a las técnicas de la
programación estructurada, porque "los objetos que construiremos se
compondrán en piezas de programas estructurados", aunque sí tiene dos
páginas al final de cada uno de los primeros capítulos para lo que los
autores llaman "pensando en objetos". Monumental.
[KERR91 ] Kernighan, Brian, y Dennis Ritchie, El lenguaje de programación C, se-
gunda edición, Prentice Hall, México, 1991.
Traducción de la edición definitiva sobre el lenguaje C, escrito por
dos investigadores ampliamente reconocidos en el mundo, coautores tam-
bién de varios otros libros sobre el tema, y del sistema operativo Unix.
Esta segunda edición recoge las modificaciones que dieron lugar al
estándar ANSI C. No es un libro muy voluminoso (320 pp.), y tampoco es
el más claro o amigable que se pueda encontrar sobre el tema.
[ SAVW00] Savitch, Walter, Resolución de problemas con C++. El objetivo de la pro-
gramación, segunda edición, Prentice Hall, México, 2000.
Traducción de un voluminoso libro (915 pp.) sobre programación mo-
derna. Las primeras 304 páginas se dedican a presentar los tipos bási-
cos de datos y las construcciones empleadas en C++ para desarrollar la
programación estructurada; luego introduce (en ese orden) los concep-
tos de clase, tipos abstractos de datos, arreglos, cadenas, apuntadores,
funciones recursivas, plantillas y listas ligadas, y termina con algunos
conceptos sobre herencia. Este reconocido autor también se menciona
en el siguiente capítulo, por su excelente libro sobre Pascal.
[STRB97] Stroustrup, Bjarne, El lenguaje de programación C++, Addison- Wesley,
Madrid, 1997.
Traducción de la segunda edición del manual escrito nada menos
que por el autor original del lenguaje. De más de 900 páginas de exten-
sión, se trata de un libro no exactamente amigable, dirigido a programa-
dores con experiencia previa.
C a p í t u I o

La codificación en
la programación
moderna: Pascal

Temas y áreas de conocimiento del capítulo


Área general de conocimientos del capítulo:
6.2.1 Familias y tipos de lenguajes
Sección 10.1 Introducción (PI14)
Sección 10.2 Estructuras fundamentales de control (PI3, PI14)
Sección 10.3 Estructuras adicionales de control (PI3, PI5, PI14)
Sección 10.4 Módulos (PI3, PI26)
Sección 10.5 Ejemplo de un diseño completo codificado: las 8
damas (ídem)
Sección 10.6 Manejo de archivos (PI7)
474 Capítulo 10 La codificación en la programación moderna: Pascal

10.1 INTRODUCCIÓN

Si el lector ha estudiado con cuidado los capítulos anteriores, sin mayor problema podrá
dedicar sus esfuerzos a la siguiente parte de la metodología que se ha explicado, con-
sistente en expresar los algoritmos ya diseñados en algún lenguaje de programación en
particular.
El plan de este capítulo es, pues, como sigue: se tomarán los ejemplos y las ideas
anteriormente esbozadas (y otras nuevas) y se desarrollarán hasta el punto de la codifi-
cación final en Pascal.
Aunque existen desde hace tiempo versiones de Pascal orientadas a objetos, no se
trata de un lenguaje especialmente diseñado para ello, pero su uso sigue siendo muy po-
pular dentro de los cursos iniciales de programación.
Antes de comenzar, advertimos seriamente al lector que no nos proponemos enseñar-
le a programar en Pascal, sino que únicamente se empleará el lenguaje como ejemplo de
codificación de algoritmos. En la bibliografía se proponen algunos libros y manuales
(entre muchos otros) sobre lenguajes de programación, por lo que nos limitaremos a explicar
sus características de tal forma que permitan la comprensión de lo que aquí se dice, lo cual
es muy diferente de aprenderlo a fondo, o de hacer sistemas completos. Además de las
referencias citadas en los capítulos 7 y 8, se proponen (entre tantos posibles) los libros
[LEEN99], [GROP96], [HAIP94], [SALW94] y [SAVW95]. Por su parte, [KERP81] es
una excelente fuente de creación de sistemas "serios" en Pascal, y [JENK74] fue la refe-
rencia estándar original para este lenguaje.

10.2 ESTRUCTURAS FUNDAMENTALES DE CONTROL

Nuestra primera tarea es, pues, aprender a expresar las estructuras de secuenciación, se-
lección e iteración condicional en Pascal. En general, una instrucción puede iniciar en
cualquier parte del renglón y extenderse a varios. Esto se debe a que toda expresión tiene
un delimitador, que indica al compilador dónde termina una y dónde comienza la siguiente.
En Pascal se emplea un punto y coma ; para separar los enunciados cuando a continuación
no viene una palabra clave, como se verá más adelante.

Secuenciación
La expresión en pseudocódigo
el ; e2; e3

donde los enunciados son, por ejemplo, instrucciones de asignación, se expresará en Pascal
como

alfa := 1; beta := 2; zeta := 1000;

Es muy importante tomar en cuenta que Pascal exige el uso del símbolo compuesto
: = para la asignación (sin espacios intermedios). Si por error se escribe el signo sencillo
(que en realidad se emplea para la comparación por igualdad), habrá problemas que no
siempre son fáciles de detectar.
Como regla general, se requiere que todas y cada una de las variables usadas en un
programa estén declaradas al inicio, asunto que se tratará un poco más adelante, al hablar
de los tipos de datos.
Sección 10.2 Estructuras fundamentales de control 475

Cuando se desea expresar la secuenciación de varios enunciados de tal forma que


aparezca como un solo enunciado elemental, en pseudocódigo se emplean los metapa-
réntesis comienza y termina. Pascal dispone de las palabras clave begin y end, que se
usan de la misma forma que en el pseudocódigo.t
Como se mencionó, en Pascal, el punto y coma se usa como separador, no como ter-
minador, lo que significa que sirve para indicar al compilador la diferencia entre uno o más
enunciados. Si el último enunciado antecede directamente a una palabra clave de Pascal, ya
no será necesario separarlo de lo que sigue con un punto y coma, puesto que el compila-
dor notará que la siguiente palabra es especial, y determinará que el enunciado terminó.
Es decir, la expresión en pseudocódigo

comienza
e,
e2
e3
termina

Se expresará en Pascal así:

begin
alfa := 1;
beta := 2;
zeta := 3
end

Selección
La estructura .11 . . . entonces . . . otro del pseudocódigo tiene expresión idéntica en
Pascal (if then . else), por lo que estas dos expresiones son equivalentes:

Pseudocódigo Pascal
§.1. alfa = 85 if alfa = 85 then beta := 38
entonces beta = 38 else beta := 10;
otro beta = 10

Observe con cuidado el distinto uso que se hace del signo de comparación por igual-
dad y del de asignación, pues son muy diferentes. Pascal utiliza un signo compuesto para
la asignación y uno sencillo para la prueba por igualdad[ [.

(t) Por razones de claridad seguiremos subrayando las palabras clave en los programas en pseu-
docódigo, aunque ya no se hará en los escritos en Pascal, para que el lector tenga claro que no es
necesario subrayarlas en los programas ya codificados.
(tt) A estas alturas del curso es cuando cada estudiante debe ponderar si este tipo de carreras
profesionales es lo adecuado para el o ella, pues de aquí en adelante habrá que prestar atención a
todos los nimios detalles. Ya no existe la libertad del pseudocódigo: el que manda es el compilador,
476 Capítulo 10 La codificación en la programación moderna: Pascal

Es decir, y aunque lo lógico sería escribir un solo símbolo para algo que se usa con
mucha frecuencia, en Pascal se emplea el signo compuesto para la asignación: beta : = 38
y uno sencillo para la comparación por igualdad: ¿ es alfa = 85 ? que se utiliza mucho
menos frecuentemente.
Por otra parte, es vital entender que el punto y coma separador no debe ir después de
la proposición beta : = 38, porque eso indicaría al compilador que la estructura de selec-
ción terminó allí, y entonces la cláusula else estaría fuera de contexto (o sea, no estaría
dentro del alcance sintáctico del if). Es necesario comprender (¡y siempre seguir al pie de
la letra!) las convenciones sintácticas de cada lenguaje
La formulación de expresiones más complejas en Pascal es muy similar al pseudo-
código, como en el siguiente ejemplo:

C3 entonces comienza
e,
e7
termina
otra comienza
e21
e,
termina

que en Pascal se escribe casi igual (cuidando el uso ya explicado del punto y coma):

if alfa = 3 then begin


beta := 1;
zeta := 7
end
else begin
beta := 21;
zeta := 5
end;

Iteración condicional
Pascal dispone de la instrucción while, muy parecida al mientras del pseudocódigo, por
lo que no habrá mayor problema para usarla. Existen, claro, diferencias de detalle, como
el uso obligado de la palabra do al lado derecho de la condición, para así delimitarla por
un while a la izquierda y un do a la derecha, que actúan igual que los paréntesis usados en
el pseudocódigo.
Es decir, esta expresión en pseudocódigo:

mientras (alfa <> 10)


comienza
beta = beta - 1
alfa = alfa T 1
termina

y no es uno de los nuestros. Además, no hay ninguna posibilidad de "negociar" o de "llegar a un


acuerdo"; lo cual es un distintivo de las llamadas ciencias exactas.
Sección 10.2 Estructuras fundamentales de control 477

se escribe así en Pascal:

while alfa <> 10 do


begin
beta := beta - 1;
alfa := alfa + 1
end;

Se procede ahora a combinar y anidar estructuras (lo cual el lector ya debe dominar
bien en pseudocódigo), y a codificarlas.
Tomando el ejemplo de la pág. 325, que decía:

Cl entonces comienza
C2 entonces e 5
otro e,
mientras (C 15 ) e 9
termina
otro 51. C 21 entonces comienza
e2
e33
termina
otro comienza
en
e,
termina

su codificación empleando condiciones y enunciados arbitrarios será:

if C1 > 10 then begin


if C2 < 0 then zeta := 5
else zeta := 1;
while C15 <= 25 do gama:= gama + 9
end
else if C21 = - 1 then begin
psi := 2;
tau := 33
end
else begin
psi := 37;
tau := 11
end;

Por otra parte, los comentarios en Pascal pueden ocupar cualquier lugar que llene
un espacio en blanco; se escriben rodeados del doble símbolo (* por la izquierda, y *)
por la derecha, aunque empleen más de un renglón. También se pueden delimitar me-
diante llaves {
478 Capítulo 10 La codificación en la programación moderna: Pascal

Características propias de Pascal


Llegó el momento de mencionar las particularidades indispensables de Pascal que per-
mitan escribir un programa completo y acabado. Se trata principalmente de las declara-
ciones de las estructuras de datos, sin entrar en demasiados detalles, porque el lenguaje es
especialmente rico en este campo.
Un programa en Pascal debe comenzar con la palabra clave program, seguida del
nombre del programa y punto y coma. Las variables deben ser declaradas usando la pala-
bra var, seguida de la lista de variables de un cierto tipo (separadas por comas), que
terminará con dos puntos y la palabra integer, real, char o string (por lo pronto).
Este último tipo sirve para especificar cadenas de caracteres y es muy útil para manejar
letras, como se verá más adelante.
Luego debe ir la palabra begin, que marca el inicio de las proposiciones ejecutables
de Pascal, mismas que deben terminar con la palabra e nd seguida de un punto.
Es decir, la estructura mínima es:

(* Programa genérico en Pascal


program uno;
var
- --declaraciones de variables -- -
begin
- - -instrucciones ejecutables - - -
e nd .

Como instrucciones de entrada/salida usaremos readln, write y writeln, y un


compilador Borland® para Windows ®, dejando al lector la tarea de averiguar los detalles
específicos que usa el compilador con el que va a trabajar, porque suele haber variaciones.
En nuestro entorno de programación es imprescindible que todo programa que pretenda
leer o escribir incluya el renglón

uses Wincrt;

inmediatamente después del renglón de program.


Así, este fragmento de pseudocódigo:
entero alfa[10]
real zeta[25]
entero beta
real lambda, mu

tiene como equivalente en Pascal a:


program ejemplo;
uses Wincrt;
var alfa: array[1..10] of integer;
zeta: array[1..25] of real;
beta: integer;
lambda, mu: real;
begin

e nd
Sección 10.3 Estructuras adicionales de control 479

Observe que Pascal pide la especificación de los límites inferior y superior de un


vector. Es decir, el vector alfa con diez posiciones enteras está definido así:

alfa:

Dirección 1 2 3 4 5 6 7 8 9 10

t t
Límite inferior Límite superior

mientras que esta nueva declaración determina otra organización:

var alfa: array[8..17] of integer;

porque representa el siguiente vector

alfa:

Dirección 8 9 10 11 12 13 14 15 16 17

t t
Límite inferior Límite superior

Aquí, si el compilador se encuentra en el programa una instrucción que diga, por ejemplo

alfa[2] := 0;

marcará un mensaje de error, pues la casilla con dirección 2 del arreglo alfa no está
definida.

10.3 ESTRUCTURAS ADICIONALES DE CONTROL

Se explicará ahora cómo usar algunas de las construcciones adicionales que ofrece Pascal,
y que enriquecen considerablemente el poder expresivo de los programas.
La construcción de pseudocódigo

repite
comienza
e2
e3
termina
hasta (condición)
480 Capitulo 10 La codificación en la programación moderna: Pascal

tiene expresión inmediata en Pascal, de esta forma:

repeat
alfa := alfa + 2;
beta := beta - 3
until alfa = 10;

Nótese que las palabras repeat y until sirven como paréntesis, por lo que no es
obligatorio delimitar los enunciados con begin y en d.

Iteración controlada numéricamente


La construcción de pseudocódigo

ejecuta i = Ls
e

en realidad es una instrucción original de los lenguajes de programación, por lo que mere-
ce menos el nombre de pseudocódigo que casi todas las demás.
Cuando el límite inferior es numéricamente menor que el superior, la iteración es
progresiva (el caso usual), mientras que se llama iteración decreciente en el caso contra-
rio. En Pascal se expresa de dos formas:

Progresiva Decreciente
for i := 1 to 25 do for i := 25 downto 1 do
begin begin

end; end;

Así, la siguiente construcción llena el arreglo ALFA [ 10] con ceros:

for i := 1 to 10 do
ALFA[i] := 0;

y es equivalente a este pseudocódigo:

i = 0
mientras (i < 10)
comienza
ALFA[i] = 0
i = i + 1
termina

Este programa imprime los valores (previamente definidos) de un vector en orden


decreciente:

program ejemplo;
uses Wincrt;
var LISTA: array [1..10] of real;
i : integer;
Sección 10.3 Estructuras adicionales de control 481

begin

for i := 10 downto 1 do
writeln(LISTA[i]);

end.

Selección múltiple
La construcción caso del pseudocódigo es muy útil para expresar algoritmos de manera
elegante, y puede usarse directamente en Pascal.
El siguiente programa muestra en la pantalla de la computadora un menú para que el
usuario escoja alguna acción deseada. Supóngase que existen cinco módulos ya progra-
mados, los cuales efectúan otras tantas funciones que por lo pronto no nos preocupan:

repite
comienza
escribe "Escriba su selección (1-4)"
escribe "Para terminar, escriba 0 :"
lee digito
caso dígito dg
0: escribe "Adiós";
1: UNO
2: DOS
3: TRES
4: CUATRO
: ERROR
fin-caso
termina
hasta (digito = 0)

La estructura case de Pascal estándar no tiene la posibilidad de etiquetas vacías ni


maneja adecuadamente la situación cuando la variable de control no toma ninguno de los
valores definidos en las etiquetas. Así pues, habrá que simularlas:

repeat
writeln;
writeln('Escriba su seleccion (1-4)');
write('Para terminar, escriba 0: ');
readln(digito);
if digito in [0..4]
then
case digito of
0: writeln('Adiós.');
1: UNO;
2: DOS;
3: TRES;
4: CUATRO;
end
else ERROR
until digito = 0;
482 Capítulo 10 La codificación en la programación moderna: Pascal

Hay varios puntos que conviene notar: la instrucción write no baja el cursor de la
pantalla cuando escribe un mensaje, sino que lo mantiene en la última posición, mientras
que la instrucción writeln cambia de renglón después de escribir el mensaje, y también
puede usarse en forma independiente para simplemente escribir una línea en blanco.
Se empleó una nueva y útil instrucción llamada in, que sirve para probar si una varia-
ble tiene o no un valor dentro de un conjunto. Se tuvo que usar, pues el case de Pascal
estándar carece, como se dijo, de la etiqueta vacía para atrapar errores, aunque algunas
otras versiones del lenguaje sí incluyen esa posibilidad mediante una etiqueta else, con
lo que el fragmento se vería como:

repeat
writeln;
writeln('Escriba su seleccion (1-4)');
write('Para terminar, escriba 0: ');
readln(digito);
case digito of
0: writeln('Adiós.');
1: UNO;
2: DOS;
3: TRES;
4: CUATRO;
else ERROR
end
until digito = 0;

Ejemplo: multiplicación de matrices


Tomaremos ahora el ejemplo de la pág. 360 (multiplicación de matrices) para seguimos
ejercitando en la codificación. En este caso, la traducción del pseudocódigo no deberá
representar mayores problemas:

Program matmult;
uses Wincrt;
(* Multiplicacion de matrices en Pascal *)
(* Declaracion de variables y límites máximos aceptables
const lim = 10;
type matriz = array [1..lim, 1..lim] of integer;
var 1..lim; j: 1..lim; k: 1..lim;
m: 1..lim; n: 1..lim; p: 1..lim;
mal: integer;
A: matriz;
B: matriz;
C: matriz;
begin
repeat
mal := 1;
writeln;
write(' Número de renglones de la matriz A (1-10): ');
readln(m);
writeln;
write(' Número de columnas de la matriz A (1-10): ');
readln(n);
Sección 10.3 Estructuras adicionales de control 483

writeln;
write(' Número de columnas de la matriz B (1-10): ');
readln(p);
if (m in [1..lim]) and (n in [1..lim]) and (p in [1..lim])
then mal := 0
else writeln( ' La dimensión de estas matrices es inválida.')
until mal = 0;

(* se leen los valores de la primera matriz A[m x n] *)


writeln;
for i := 1 to m do
begin
writeln;
for j := 1 to n do
begin
write(' Valor de A[', j, ');
readln(A[i,j])
end
end;

(* se leen los valores de la segunda matriz B[n X p] *)


writeln;
for i := 1 to n do
begin
writeln;
for j := 1 to p do
begin
write(' Valor de B[', i , j, ']: ');
readln( B[i,j] )
end
end;

writeln ; writeln ;
writeln(' La matriz producto C(', m, ' X ', p, ') es: ');
writeln;

(* Comienza el cálculo *)
for i := 1 to m do
for j := 1 to p do
begin
C[i,j] := 0;
for k := 1 to n do
C[i,j] := C[i,j] + A[i,k] * B[k,j]
end;

(* Se imprimen los resultados de la matriz C[m X p] *)


for i := 1 to m do
begin
for j := 1 to p do
write(", C[i ,j], ");
writeln
end
end.
484 Capítulo 10 La codificación en la programación moderna: Pascal

En este programa se volvió a utilizar la instrucción in para verificar que los valores
estén dentro de los límites aceptables. Obsérvese que en la declaración misma de las
matrices se usaron otras facilidades de este lenguaje: una que define durante la compila-
ción los límites que una variable puede tomar, como en

var 1..lim;

que declara a la variable i con valores aceptables entre 1 y la constante lim, que a su vez
Nuevos tipos fuera declarada por medio de una instrucción anterior. Una importante característica de
de datos Pascal es que permite definir nuevas variables en términos de otras previamente declara-
das. Para esto se requiere el uso de la palabra especial type:

type matriz = array [1..lim, 1..lim] of integer;

que define nuevos tipos de variables, en este caso llamadas matriz. Luego ya será posible
declarar variables de ese nuevo tipo, como en:
A: matriz;
B: matriz;
C: matriz;

La alternativa —menos general— hubiera sido declarar cada matriz por separado y
en forma repetitiva:
A: array [1..lim, 1..lim] of integer;
B: array [1..lim, 1..lim] of integer;
C: array [1..lim, 1..lim] of integer;

Para que el lector vea con precisión el efecto de los ciclos empleados para pedir y
mostrar los datos, a continuación se muestra un ejemplo de la ejecución del programa de
multiplicación de matrices, tomado directamente de la computadora. Aparece subrayado
lo que tecleó el usuario.
Número de renglones de la matriz A (1-10): 2
Número de columnas de la matriz A (1-10): a
Número de columnas de la matriz B (1-10): 2
Valor de A[1,1]: 1
Valor de A[1,2]: 2
Valor de A[1,3]: 3
Valor de A[2,1]: 4
Valor de A[2,2]: 5
Valor de A[2,3]: 6
Valor de B[1,1]: 7
Valor de B[1,2]: 8
Valor de B[2,1]: 9
Valor de B[2,21: le
Valor de B[3,1]: 1
Valor de B[3,2]: 2
La matriz producto C (2 X 2) es:
28 34
79 94
Sección 10.4 Módulos 485

10.4 MÓDULOS

Pascal emplea una filosofía especial para el manejo de la modularidad; en términos gene-
rales, usa un esquema jerárquico para el paso de parámetros y para la transmisión de la
información que manejan las variables, como se verá a continuación.
Como se dijo, un programa consiste en un texto que comienza con la palabra especial
program, y termina con end ., con el punto final obligatorio. Si se desea incluir módulos,
éstos aparecerán en algún lugar intermedio del programa, y deberán comenzar con la
palabra procedure, seguida del nombre del módulo y el punto y coma. Todo procedure
termina con su correspondiente end; (con punto y coma), que "cierra" el begin que necesa-
riamente debió aparecer. También existen las funciones (es decir, procedimientos que
devuelven un valor, como se estudió en el capítulo 8), que en Pascal se llaman f unct ion.
Es importante tomar en cuenta que los módulos de Pascal no son ejecutables, sino tan
sólo invocables, por medio de su nombre. Esto significa que dentro de un programa con
estructura así, el flujo de control ignora los grupos de renglones que comiencen con la
palabra procedure, y sólo ejecuta las instrucciones del programa principal (que por supuesto
podrán contener llamadas a los módulos); mismas que se encuentran entre el primer begin
y el end. final (con punto).
La buena práctica de la programación en lenguajes con estructura de bloques —así se
llaman— dicta que los módulos (procedure o f un ct ion) deben escribirse al comienzo
del program principal —antes de su begin para dejar claramente diferenciada el área

ejecutable del área que puede ser llamada.


La estructura que se propone entonces es la siguiente, para un programa principal y
dos módulos (se dejaron renglones en blanco para mayor claridad):

program principal;
(* aquí van las declaraciones de los datos del programa aunque
no necesariamente todas las que se muestran*)

const ;
type ;
var

procedure modulol; (* inicia el texto del primer módulo *)


const (* con sus declaraciones de datos *)
type (* locales si se requieren *)
var
begin (* inicio de las instrucciones del primer módulo *)

end; (* termina el primer módulo *)

procedure modulo2; (* inicia el texto del segundo módulo *)


const ; (* con sus declaraciones de datos *)
type ; (* locales si se requieren *)
var
begin (* inicio de las instrucciones del segundo módulo *)

end; (* termina el segundo módulo *)


486 Capítulo 10 La codificación en la programación moderna: Pascal

begin (* aquí comienzan las instrucciones ejecutables *)


(* del programa principal, únicas que *)
(* son directamente ejecutables *)

modulo2; (* ésta es una llamada *)

modulol; (* ésta es otra *)

end. (* aquí termina el programa principal y, con él,


todo el programa completo *)

Surge una pregunta: ¿cómo puede el programa principal pasar información hacia
(o desde) un módulo? Existen dos formas: mediante parámetros y por medio de lo que
llamaremos la regla general del manejo de bloques, que define el comportamiento del
intercambio de variables en Pascal.

Regla general del manejo de bloques


Todas las variables declaradas en el programa princi-
pal son pasadas a los módulos (bloques) internos como
"herencia", a menos que éstos decidan rechazarlas
declarando sus propias variables localmente.
Pero además, si un módulo requiere la utilización de variables que no sean globales, en-
tonces puede definirlas localmente, en el entendido de que éstas no serán accesibles desde
ningún punto externo a ese módulo. Como tal vez esté claro ya, este mecanismo evita los
efectos secundarios asociados con las variables compartidas, puesto que sólo el módulo
en cuestión puede alterar los valores de sus datos locales.
Esta regla es recursiva en el sentido de que, si se desea, un módulo cualquiera puede
tomar el papel del programa principal y englobar otros, pasándoles a ellos su herencia, en
la forma descrita; estos módulos de tercer nivel podrían rechazarla, declarando sus pro-
pias variables, y así sucesivamente.
Se establece así una jerarquía de herencias, que en en inglés se conoce como scope
of variables (alcance de las variables). Como en el caso de las estructuras de control,
conviene no abusar del anidamiento de módulos, porque no aporta mucho a la claridad
de los programas.
El programa principal es el padre (siguiendo con la idea de la jerarquía) de todos los
módulos, y uno puede convertirse en padre de otro si lo engloba físicamente. Dos proc edu re
o f u nct io n pueden ser hermanos si ambos son hijos del prog ram principal y ninguno
contiene al otro. Es decir, Pascal sí permite el anidamiento de funciones, lo cual no es
posible realizar en C/C++ ni en Java.
Jerarquía de llamadas De la misma forma, los hijos de un proc ed u re cualquiera solamente pueden ser llama-
dos por él mismo o por su padre. Esto significa que, al igual que con las variables, los
módulos en Pascal pueden ser o no compartidos.
Por otra parte, para que varios módulos hermanos puedan llamar a otro de su misma
jerarquía (es decir, algún hermano; que no esté englobado en ninguno de ellos), éste tiene
que aparecer textualmente antes que ellos dentro del programa completo, para que el
compilador "esté enterado" de su existencia y pueda resolver adecuadamente la llamada.
Todo esto puede parecer confuso a primera vista, pero no es más que un resultado de
la regla general de manejo de bloques.
Sección 10.4 Módulos 487

Como ejemplo se propone la siguiente estructura, en donde los módulos UNO y DOS
son hermanos, y el módulo TRES es hijo de UNO:

program;
procedure UNO;
procedure TRES; il
begin

end; (* TRES *)
begin

end; (* UNO *)
procedure DOS;
begin

end; (* DOS *)
begin

end. (* program *)

Debido a la regla de manejo de bloques, se tiene que:

• El programa principal puede llamar a UNO y a DOS, porque son hijos de él.
• El programa principal no puede llamar a TRES porque no es directamente su hijo;
sería error de sintaxis: "Identificador desconocido".
• DOS no puede llamar a TRES; sería error de sintaxis, pues no forma parte de su
entorno.
• Como UNO aparece antes que su hermano DOS dentro del programa, DOS sí puede
llamar a UNO, pero lo inverso causaría un error.
• TRES no puede llamar a DOS; sería error de sintaxis, aunque sí puede llamar a UNO,
pero eso sería una llamada indirecta a sí mismo (recursiva), que no se explora en
este libro, y que se deja para un siguiente curso.

Pedimos al lector que estudie el programa que aparece a continuación. Además, si lo


teclea y lo compila, puede emplearlo como una verdadera herramienta de laboratorio para
hacer experimentos consistentes en elaborar una hipótesis sobre el comportamiento es-
perado de los módulos o sus variables, modificar el programa para reflejarla, incluyendo
instrucciones writ eln alusivas al tema, y compilarlo y ver el resultado; todo en unos
pocos segundos:

program principal;
uses Wincrt;
var alfa, beta: integer;

procedure UNO;
488 Capítulo 10 La codificación en la programación moderna: Pascal

procedure TRES;
begin
writeln('TRES')
end; (* TRES *)

begin (* UNO *)
write('UN0');
write('.');
TRES;
end; (* UNO *)

procedure DOS;
begin
write('DOS');
write('.');
UNO
end; (* DOS *)

begin (* principal *)
writeln('PRINCIPAL');
write('.');
UNO;
write(");
DOS;
writeln('FIN');
end. (* principal *)

Los resultados de este experimento aparecen en pantalla:

PRINCIPAL
.UNO.TRES
.DOS.UNO.TRES
FIN

Y reflejan claramente el comportamiento derivado de la jerarquía de llamadas.


A continuación se muestra el pseudocódigo empleado al explicar los módulos en el
capítulo 8, que usa un módulo para hacer una suma:

proc principal
entero a, b, resultado
escribe "Dame los valores de a y b:"
lee a, b
suma(a,b,resultado)
escribe "a + b =", resultado

proc suma(entero si, entero s2, entero r)


r = sl + s2
regresa
fin.

Y ésta es su codificación directa en Pascal. Observe que es imprescindible que el


resultado sea un parámetro pasado por referencia (mediante la especificación va r), pues
de no ser así tendría un valor indeterminado, porque no "llegaría" de ninguna parte:
Sección 10.4 Módulos 489

program principal;
(* Suma con parámetros *)
uses Wincrt;
var a, b, resultado: integer;

procedure suma(a, b: integer; var r: integer);


begin
r:= a + b
end; (* suma *)

begin (* principal *)
write('Dame los valores de a y b: ');
readln(a,b);
suma(a, b, resultado);
writeln('a + b = ', resultado)
end.

Pero ésta otra es la codificación utilizando la regla de herencia derivada de la estruc-


tura de bloques de Pascal, y sin emplear parámetros:

program principal;
uses Wincrt;
var a, b: integer;

procedure suma;
var r: integer; (* Variable local a suma *)
begin
r := a + b;
writeln;
writeln('a + b = r)
end; (* suma *)

begin (* principal *)
write('Dame los valores de a y b: ');
readln(a, b);
suma
end.

Antes de seguir analizando el manejo de parámetros en Pascal, pedimos al lector


dedicar unos minutos a entender el funcionamiento de este nuevo programa (que cierta-
mente es un poco rebuscado, pero representa una buena oportunidad para afirmar los
conceptos recién explicados):

program principal;
uses Wincrt;
var A, B: real;
procedure UNO;
var J, K: integer;
procedure TRES;
var R: real; (* variables locales a TRES *)
I: integer;
begin (* TRES *)
R := A + B;
writeln;
490 Capítulo 10 La codificación en la programación moderna: Pascal

writeln ('A + B = R:2:2);


I := J + K;
writeln('J + K = I);
writeln;
end; (* TRES *)
begin (* UNO *)
write ('UNO: DAME EL VALOR ENTERO (J): ');
readln(J);
write('UNO: DAME EL VALOR ENTERO (K): ');
readln(K);
TRES
end; (* UNO *)
procedure DOS;
var R: real; (* Esta no es la misma R que en el módulo TRES *)
begin (* DOS *)
writeln('DOS: A = ', A:2:2);
writeln('DOS: B = B:2:2);
writeln;
writeln('DOS: A + B = R:2:2)
end; (* DOS *)
begin (* principal *)
write('PRINCIPAL: DAME EL VALOR REAL (A): ');
readln(A);
write('PRINCIPAL: DAME EL VALOR REAL (B): ');
readln(B);
UNO;
DOS
end.

Éste es un ejemplo de la ejecución y de los valores que entrega (aparecen subrayados


los valores que se dieron como respuesta a las peticiones):

PRINCIPAL: DAME EL VALOR REAL (A): 1


PRINCIPAL: DAME EL VALOR REAL (B): 2
UNO: DAME EL VALOR ENTERO (J): 4
UNO: DAME EL VALOR ENTERO (K): 5

A + B = 3.00
J + K = 9

DOS: A = 1.00
DOS: B = 2.00
DOS: A + B = 15458860555000000.00

El último número es la respuesta que dio nuestra computadora cuando se le pidió que
imprimiera el valor de la variable R, local al módulo DOS; debe ser considerado como
"basura" (pudo haber sido cero, o cualquier otro, porque estaba declarado pero nunca se
le asignó ningún valor).
Por otra parte, obsérvese el uso especial de los modificadores de formato en la
instrucción
writeln('DOS: A = A:2:2);
Sección 10.4 Módulos 491

que piden escribir una variable real con precisión 2 —el primer modificador— y con dos
decimales —el segundo.
Así, por ejemplo, la instrucción

writeln('DOS: A = ', A:2:3);

produciría

DOS: A = 1.000

Pero si no se incluyen, y sólo se escribiera

writeln('DOS: A = ', A);

entonces aparecería algo difícil de leer, como

DOS: A = 1.0000000000E+00

Por último, si sólo se especifica la precisión:

writeln('DOS: A = ', A:2);

entonces se vería esto:

DOS: A = 1.0E+00

Como se dijo anteriormente, el manejo de los parámetros en Pascal está supeditado a


la regla general de alcance de las variables. En términos generales, se emplea para lograr
que un mismo procedure trabaje sobre diferentes conjuntos de datos cuando es llamado
en diferentes ocasiones. Una llamada a un módulo al que se pasa una lista de argumentos
aparece así:

DIVIDE(355, 113);

y la definición del módulo dentro del programa principal es, por ejemplo:

procedure DIVIDE (x, y: real);


begin
writeln(x, ' entre ', y, ' = x/y)
end;

En la lista de parámetros formales del procedure se exige que estén individualmente


declarados (separados con punto y coma si son de tipos diferentes) con los mismos atribu-
tos que se espera de ellos a la hora de la invocación.
Dentro del mismo programa, ésta es otra llamada válida:

DIVIDE(0.15, - 1.14);

Es importante tomar en cuenta que los parámetros son estrictamente locales al módu-
lo donde se declaran y que, por tanto, cualquier referencia a ellos fuera del módulo en
cuestión sería ilegal, dando lugar a un mensaje de error por parte del compilador. Esto
tiene sus ventajas, ya que un módulo no puede cambiar el valor original de ninguna variable
492 Capítulo 10 La codificación en la programación moderna: Pascal

que le fuera pasada como parámetro por su padre, y podrá usarla sin temer efecto secun-
dario alguno.

Paso de arreglos como parámetros


Supóngase que existe un módulo especial para invertir matrices. Algunos métodos ma-
temáticos eficientes para lograr esto tienen como desventaja el hecho de que destruyen
los valores originales de la matriz, siendo entonces peligroso su uso indiscriminado. Si
se pasa la matriz al módulo como parámetro, entonces el programa original no tiene nada
que temer, pues ningún cambio se verá reflejado en el programa principal. Esto signi-
fica, necesariamente, que el compilador de Pascal generó código para que los valores de
estos parámetros sean copiados por el módulo invocado (para que en realidad se trabaje
con una copia de la matriz, y no con la original). Si bien este esquema ya conocido,
denominado paso de parámetros por valor, es seguro, también es costoso, por lo que se
recomienda mesura al usarlo. (Algunos compiladores de Pascal no permiten el paso
de arreglos por valor.)
Cuando se requiere que tanto el módulo como el programa principal trabajen física-
mente sobre el mismo parámetro (y no uno con el original y el otro con la copia), entonces
hay que declararlo como var dentro del procedure. Si esto sucede, entonces todos los
cambios de valor que el módulo haga a ese parámetro serán permanentes, y tendrán repercu-
siones en el programa principal; recuerde que esto se conoce como paso de parámetros
por referencia.
En ambos esquemas, si se desea pasar un arreglo (array) como parámetro, primero
tendrá que declararse dentro de una especificación type, puesto que Pascal no permite
declaraciones complejas como parte de la lista de parámetros formales. Por ejemplo,
esta declaración es incorrecta:

procedure EQUIS (alfa: array [1..25] of integer); { MAL }

La forma correcta de hacerlo es, como se dijo, empleando la cláusula type:

type vector = array [1..25] of integer;

procedure EQUIS(alfa: vector);

El siguiente programa es un ejemplo sencillo del uso de los dos tipos de parámetros
ya explicados, donde la variable i se pasa por referencia y j se pasa por valor, siendo tan
sólo la primera afectada por los cambios que le hizo el módulo:

program principal;
uses Wincrt;
var i, j: integer;
procedure UNO( var integer; j: integer);
(* Obsérvese el paso del parámetro por referencia *)
begin
i := i + 1;
j := j - 1;
writeln ('UNO: para mí, i vale ', i, ' y j vale ', J);
writeln
end; (* UNO *)
Sección 10.4 Módulos 493

begin (* principal *)
i := 20;
j := 90;
writeln ( 'Principal: originalmente, i vale ', i, ' y j vale ', j);
writeln;
UNO(i, j) ;
writeln ('Principal: y ahora, i vale ', i, ' y j vale ', j )
end. (* principal *)

Examínelo con cuidado para entender que si entrega estos resultados:

Principal: 'originalmente, i vale 20 y j vale 90


UNO: para mí, i vale 21 y j vale 89
Principal: y ahora, i vale 21 y j vale 90

es porque, desde el punto de vista del programa principal, la variable i sí es afectada por
el módulo (porque fue declarada como parámetro v a r), mientras que j sólo es alterada de
forma local.
En resumen, y como se explicó en el capítulo 8, desde el punto de vista del programa
principal (o de cualquier módulo padre), cuando se desea que un parámetro sirva sola-
mente para llevar valores, se empleará la declaración por valor (i.e., sin la plabra var)
mientras que cuando un parámetro deba traer valores de regreso habrá de emplear la de-
claración var (por referencia).
Como ilustración, el siguiente programa suma dos vectores enteros, elemento a elemento:

(* Suma de dos vectores *)


program vectores;
uses Wincrt;
const lim = 3;
type vector = array[1..lim] of integer;
var
integer;
A, B, C: vector;

procedure suma(lim: integer; A, B: vector; var C: vector);


var integer;
begin
for i := 1 to lim do C[i] := A[i] + B[i];
end; (* suma *)

begin (* principal *)
writeln;
write('Dame los ', lim, ' valores de A,',
'separados por espacios: ');
for i := 1 to lim do read (A[i]);
writeln;
write('Dame los ', lim, ' valores de B,',
'separados por espacios: ');
for i := 1 to lim do read (B[i]);
suma(lim, A, B, C); (* Se pasan los arreglos *)
writeln;
494 Capítulo 10 La codificación en la programación moderna: Pascal

write('La suma es: ');

for i := 1 to lim do write(C[i], ");


end.

Los parámetros se pasaron por valor, menos el arreglo C, que tuvo que ser por refe-
rencia (va r) para que devuelva los valores calculados. Los resultados son:

Dame los 3 valores de A, separados por espacios: 1 2 3

Dame los 3 valores de B, separados por espacios: 4 5 6


La suma es: 5 7 9

Funciones
Como se ha dicho ya, un módulo o procedimiento es un caso particular de una función
que no devuelve ningún valor por sí misma (aunque sí puede tener parámetros), como los
p roced u re empleados. Esto significa que las funciones deben devolver un tipo de datos,
sea primitivo o construido por el programador. Para el análisis que sigue se repiten las
funciones analizadas en el capítulo 8. La primera fue:

entero func cuadrado(entero x)


cuadrado = x * x
regresa
fin.

cuyas llamadas eran, por ejemplo:

escribe cuadrado(2) ! Ojo: sin comillas


escribe cuadrado(cuadrado(2))
escribe cuadrado(2) + cuadrado(2)

Y ahora se codificarán en Pascal en forma prácticamente directa:

(* Uso de la función cuadrado *)


program cuad;
uses Wincrt;
function cuadrado(x: integer): integer;
begin
cuadrado := x * x
end;
begin (* principal *)
(* Llamadas a la función *)
writeln(cuadrado(2)); (* Vale 4 *)
writeln(cuadrado(2) + cuadrado(2)); (* Vale 8 *)
writeln(cuadrado(cuadrado(2))); (* Vale 16 *)
-end.

El programa, claro, produce como salida

4
8
16
Sección 10.4 Módulos 495

Lo importante aquí es tomar nota de la declaración de una función:

function <nombre> (<lista de parámetros>): <tipo>

donde <tipo> es la descripción del único valor que devolverá la función en cuestión. Por
fuerza, al menos una vez debe aparecer una instrucción de la forma

<nombre> := <expresión>

dentro de la definición de la función, que servirá para devolver el valor obtenido, que debe
ser precisamente del tipo declarado.
La llamada de la función simplemente consiste en mencionar su nombre seguido de
los argumentos necesarios, como si fuera una variable simple. En el programa de ejemplo,
esto se hizo dentro de la instrucción writeln, en los últimos renglones.
Continuando con el tema, también en el capítulo 8 se diseñó otra función, que podría
utilizarse en conjunto con la anterior:

entero func mínimo3(entero x, entero y, entero z)


entero mín Variable local auxiliar
mín = x
y < mín entonces mín = y
11. z < mín entonces mín = z
mínimo3 = mín
regresa
fin.

Y allí se dijo que entonces con este fragmento se escribiría el menor de tres números
elevado al cuadrado:

escribe "Dame tres números enteros: "


lee a, b, c
escribe cuadrado(mínimo3(a, b, c))

Pues bien, ésta es la codificación de todo el conjunto en Pascal:


(* Uso combinado de funciones *)
program mina;
uses Wincrt;

function cuadrado(x: integer): integer;


begin
cuadrado := x * x
end;

(* Función que devuelve el mínimo de tres enteros *)


function minimo3(x: integer; y: integer;
z: integer): integer;
var min: integer; (* Variable local auxiliar *)
begin
min := x;
if y < min then min := y;
if z < min then min := z;
minimo3 := min
end;
496 Capítulo 10 La codificación en la programación moderna: Pascal

(* principal *)
var a, b, c: integer;
begin
write('Dame tres números enteros ',
'separados con un blanco: ');
readln(a, b, c);
write('El cuadrado del mínimo es ',
cuadrado(minimo3(a,b,c)));
end. (* principal *)

Y el resultado es:

Dame tres números enteros separados con un blanco: 34 12 46


El cuadrado del mínimo es 144

Para continuar con los ejemplos del capítulo 8, nuevamente se muestra el pseudocódigo
de la función que aumentaba el IVA (15%) al precio de un producto:

real func MAS_IVA(real x)


constante IVA = 0.15
MAS_IVA = (x * IVA) + x
regresa
fin.

Y así se sacaba el promedio de tres valores:

real func prom3(real a, real b, real c)


prom3 = (a + b + c) / 3
regresa

Por lo que la siguiente combinación de funciones obtiene el valor promedio del pri-
mero y el tercero con IVA, más el segundo sin IVA (suponiendo, claro, que a alguien le
pudiera interesar):

prom3(MAS_IVA(a), b, MAS_IVA(c))

He aquí todo lo anterior, codificado en Pascal, lo cual ya debería resultar muy sencillo:

(* Nuevo uso combinado de funciones *)


program IVA;
uses Wincrt;
var a, b, c: real;
(* Función que añade el IVA *)
function MAS_IVA(x: real): real;
const IVA = 0.15;
begin
MAS_IVA := (x * IVA) + x
end;

(* Función que calcula el promedio *)


function prom3(a: real; b: real; c: real): real;
Sección 10.4 Módulos 497

begin
prom3 := (a + b + c) / 3
end;

begin (* principal *)
writeln('Dame los precios de tres productos');
write('separados con un blanco: ');
readln(a, b, c);
writeln;
writeln('El promedio del primero y el tercero ');
write('(ambos con IVA), más el segundo (sin IVA) es:
prom3(MAS_IVA(a), b, MAS_IVA(c)):2:2)
end.

El resultado de todo esto es:

Dame loá\ precios de tres productos


separados con un blanco: 3 4 5
El promedio del primero y el tercero
(ambos con IVA), más el segundo (sin IVA) es: 4.4

Desde este punto de vista, una función es un módulo que se comporta como si fuera
una nueva instrucción, directamente ejecutable del lenguaje de programación, y que sirve
para evaluar una cierta tarea específica, diseñada ex profeso.
El último ejemplo de la sección 8.7 era un algoritmo para encontrar el máximo valor
dentro de un vector, con pseudocódigo:

entero func buscam (vector [LIM])


1 Leer un vector y obtener el número más grande
! Se supone que se tiene acceso al vector en cuestión
entero máximo, índice
máximo = vector[1]
índice = 2
mientras (no se termine el vector)
comienza
5j vector[índice] > máximo entonces máximo = vector[índice]
índice = índice + 1
termina
buscam = máximo

Y su codificación en Pascal es inmediata:

program buscavect;
uses Wincrt;
const lim = 5;
type vector = array [1..lim] of integer;
var integer;
alfa: vector;
function buscam (var alfa: vector): integer;
var maximo, indice: integer;
begin
maximo := alfa[1];
indice := 2;
while indice <= lim do
498 Capítulo 10 La codificación en la programación moderna: Pascal

begin
if alfa[indice] > maximo then maximo := alfa[indice];
indice := indice + 1
end;
buscam := maximo
end; (* buscam *)
begin (* principal *)
for i:= 1 to lim do
begin
write('Dame el valor No. ', i, ' : ');
readln(alfa[i])
end;
writeln('El valor más grande del vector fue ', buscam(alfa))
end. (* principal *)

Los resultados son:

Dame el valor No. 1 :


Dame el valor No. 2 :
Dame el valor No. 3 : 55
Dame el valor No. 4 : 2
Dame el valor No. 5 : 1

El valor más grande del vector fue 55

Se puede observar cómo en el programa se pasó un arreglo como parámetro, en la


forma ya antes descrita.

10.5 EJEMPLO DE UN DISEÑO COMPLETO


CODIFICADO: LAS 8 DAMAS

A continuación se expone y comenta la codificación en Pascal del programa de las ocho


damas, diseñado en el capítulo 8. Como puede observarse, el programa es bastante senci-
llo, y ya no deberán existir sorpresas para pasar del pseudocódigo de las págs. 370-373 al
lenguaje de programación.
Existen tres arreglos globales, que serán usados por todos los módulos para determi-
nar si una dama es atacada, tanto en la diagonal ascendente o descendente como en el
renglón mismo en que se intentó colocarla: en vector se guarda la posición en que la
dama actual quedó; baja y sube guardan las restas y sumas, respectivamente, de las posi-
ciones en que se colocó la dama, con vistas a facilitar las funciones del módulo libre.
El módulo libre emplea una proposición repeat para controlar las diagonales amena-
zadas; esto es necesario para asegurarse de que se han revisado las posibilidades de ata-
que de todas las columnas anteriores a aquélla donde se intenta colocar la dama. La variable
booleana re suit actúa como indicador de si hubo o no posición libre para esa dama.
Por su parte, el módulo coloca asigna la dama que se le indica (la dama - ésima) a la
posición ren-ésima dentro de su columna.
Tal como dice su pseudocódigo, el módulo at ras elimina la dama actual del tablero,
porque se demostró que su posición no era buena. En vector se toma nota del renglón en
que se quedó dentro de su columna, para no volver a comenzar desde el primero otra vez,
sino seguirla avanzando a partir de esa casilla.
Sección 10.5 Ejemplo de un diseño completo codificado: las 8 damas 499

Por último, el módulo muestra despliega un tablero por la pantalla y se detiene, hasta
que el usuario oprima <Enter> para proseguir. Para "dibujar" un tablero de ocho filas,
cada una con ocho caracteres que simulan las celdas, se usan dos ciclos f or anidados: el
primero gobierna las filas y cambia de renglón al final de cada una, y el interno recorre
el arreglo completo de valores de cada fila e imprime sus elementos sin cambiar de ren-
glón, separándolos con un espacio en blanco.
Observe cómo en algunos módulos se hizo uso de los parámetros por referencia (decla-
rados como var en el encabezado del p roc edu re), porque sí nos interesa que los cambios
que sufran sean permanentes, lo cual no se lograría si no se hubieran declarado así.
Estudie el lector esta codificación, y compárela contra los pseudocódigos origina-
les. En todo momento tome en cuenta que esta no es de ninguna manera "la mejor"
codificación para este problema, y puede perfectamente haber otros esquemas igual-
mente válidos, cosa que además conviene siempre tener en consideración en el oficio de
la programación.

(* Programa de las ocho damas en Pascal *)


program damasP;
uses Wincrt;
const numero = 6; (* Número de soluciones a mostrar *)
type arreglo = array [1..8] of integer;
var baja, sube, vector: arreglo;
dama, ren, sol, ultima: integer;
result: boolean;

(* libre *)
procedure libre(var ren: integer; var result: boolean);
(* Módulo que averigua si existe una posición libre *)
var j: integer;
begin
result := false;
while (ren < 8) and (not result) do
begin
(* Avanzar la dama dentro de su columna *)
ren := ren + 1;
result := true;
j := 1;
repeat
if (baja[j] = (dama - ren)) or
(sube[j] = (ren + dama)) or
(vector[j] = ren) then result := false;
j := j + 1;
until (j >= dama) or (not result)
end
end;(* libre *)

(* coloca *)
procedure coloca(ren: integer);
(* Módulo que coloca una dama en el tablero *)
begin
vector[dama) := ren;
baja[dama] := dama - ren;
sube[dama] := ren + dama
end; (* coloca *)
500 Capítulo 10 La codificación en la programación moderna: Pascal

(* atrás *)
procedure atras(var ren, ultima: integer);
(* Módulo que regresa todo a la posición anterior *)
begin
dama := dama - 1;
(* Tomar nota de la última posición, para que pueda seguir avanzando
a partir de allí *)
if dama >= 1 then ren := vector[dama]
else ultima := 1
end; (* atras *)

(* muestra *)
procedure muestra;
(* Módulo que imprime una solución *)
var tab: array [1..8, 1..8] of integer;
j: integer ;
begin
for i := 1 to 8 do
for j := 1 to 8 do
tab[i,j] := 0;
sol := sol + 1; (* Llevar la cuenta *)
(* "Llenar" el tablero con la solución encontrada *)
for i := 1 to 8 do
tab[vector[i],i] := i;
writeln;
writeln(' Sol. Núm. ', sol);
writeln;
for i := 1 to 8 do
begin
write(");
for j := 1 to 8 do
write(tab[i,j], ");
writeln;
end;
writeln; writeln;
(* Borrar las posiciones recién utilizadas *)
for i := 1 to 8 do
tab[vector[i],i] := 0;
end; (*imprime*)

(* principal *)
begin (* Programa Principal *)
result := true; ultima := 0;
ren := 1; dama := 1; sol := 0;
coloca(ren); (* Comienza la búsqueda de soluciones *)
repeat
while (dama < 8) do (* Trata de colocar la próxima dama *)
begin
if result then
begin
dama := dama + 1; (* Seleccionar la siguiente dama desde
su primera casilla *)
ren := 0 ;
end;
Sección 10.5 Ejemplo de un diseño completo codificado: las 8 damas 501

(* Verificar si hay lugar libre para la nueva dama *)


libre(ren, result);
if result then coloca(ren)
else atras(ren, ultima) (* Regresar a la anterior *)
end;
if ultima = 0 then
begin
muestra; (* Se encontró una solución: mostrarla *)
write('Presione <Enter> para la siguiente solución ');
readln;
atras(ren, ultima); (* Buscar una nueva solución *)
result := false (* Para poder continuar *)
end
until (ultima <> 0) or (sol >= numero);
writeln; writeln('Adiós.');
end.

Estas son las primeras seis soluciones:

Sol. Núm. 1

1 0 0 0 0 0 0 0
0 0 0 0 0 0 7 0
0 0 0 0 5 0 0 0
0 0 0 0 0 0 0 8
0 2 0 0 0 0 0 0
0 0 0 4 0 0 0 0
0 0 0 0 0 6 0 0
0 0 3 0 0 0 0 0

Presione <Enter> para la siguiente solución

Sol. Núm. 2

1 0 0 0 0 0 0 0
0 0 0 0 0 0 7 0
0 0 0 4 0 0 0 0
0 0 0 0 0 6 0 0
0 0 0 0 0 0 0 8
0 2 0 0 0 0 0 0
0 0 0 0 5 0 0 0
0 0 3 0 0 0 0 0

Presione <Enter> para la siguiente solución

Sol. Núm. 3

1 0 0 0 0 0 0 0
0 0 0 0 0 6 0 0
0 0 0 0 0 0 0 8
0 0 3 0 0 0 0 0
0 0 0 0 0 0 7 0
0 0 0 4 0 0 0 0
0 2 0 0 0 0 0 0
0 0 0 0 5 0 0 0
502 Capítulo 10 La codificación en la programación moderna: Pascal

Presione <Enter> para la siguiente solución

Sol. Núm. 4

1 0 0 0 0 0 00
00 0 0 5 0 00
00 0 0 0 0 08
00 0 0 0 6 00
00 3 0 0 0 00
00 0 0 0 0 70
02 0 0 0 0 00
00 0 4 0 0 00

Presione <Enter> para la siguiente solución

Sol. Núm. 5

000 00 6 00
1 00 00 0 00
000 05 0 00
020 00 0 00
000 00 0 08
003 00 0 00
000 00 0 70
000 40 0 00

Presione <Enter> para la siguiente solución

Sol. Núm. 6

00 04 0 0 00
1 0 00 0 0 00
00 00 5 0 00
00 00 0 0 0 8
02 00 0 0 0 0
00 00 0 0 7 0
00 30 0 0 0 0
00 00 0 6 0 0

Presione <Enter> para la siguiente solución

Adiós.

10.6 MANEJO DE ARCHIVOS

A continuación se retoman los conceptos y operaciones elementales sobre archivos des-


critas en la sección 8.8 paró traducirlos a Pascal.

Lectura y escritura a disco


Como se dijo en el capítulo 8, dentro de un programa los archivos se manejan mediante su
descriptor, que en Pascal se declara con la instrucción

<archivo>: file of <tipo>;


Sección 10.6 Manejo de archivos 503

como si fuera una variable más, aunque ésta de una clase especial file. Además, <archivo>
es un nombre definido por el programador.
Por su parte, las instrucciones para asignar un espacio físico en el disco (o diskette)
magnético de la computadora y ligarlo con el nombre lógico o descriptor con el que se
conoce dentro del programa varían de compilador a compilador. En nuestra versión de
Turbo Pascal® para computadora personal se emplea la instrucción

assign(<archivo>,<nombre>);

<nombre> puede ser una cadena fija entre apóstrofos o bien una variable tipo cadena (st ring)
proporcionada por el usuario.
Luego habrá que crear el archivo y abrirlo, mediante la instrucción

rewrite(<archivo>); (* crear y abrir *)

De allí en adelante se podrá enviar datos a archivo mediante la instrucción writ e


usual, aunque ahora adicionada del nombre de ese descriptor, como en:

write(archivo, dato);

que escribe en archivo en disco el contenido de la variable dato, con el formato corres-
pondiente al tipo de dato en cuestión.
Antes de terminar el programa debe cerrarse el archivo, mediante la instrucción:

close(<archivo>);

Al finalizar la ejecución, en el sistema de archivos de la computadora habrá un nuevo


archivo, por completo independiente del programa recién utilizado para crearlo.
De esta forma, el pseudocódigo de la pág. 381 quedó codificado en Pascal como sigue:
(* Programa para escribir registros en el disco *)
program discol;
uses Wincrt;
type cadena = string[12];
var
integer;
dato, nombre: cadena;
archivo: file of cadena;
begin
write(' Dame nombre para nuevo archivo: ');
readln(nombre);
assign(archivo,nombre); (* Liga con el sistema de archivos *)
rewrite(archivo); (* Crea el archivo y lo abre *)
i := 1;
writeln;
write(i:3, ' Dame un núm. entero o <Ctrl>-C para terminar: ');
while not eof do
begin
readln(dato);
write(archivo, dato);
i := i + 1;
write(i:3,' Dame un núm. entero o <Ctrl>-C para terminar: ')
end;
close(archivo);
writeln('FIN')
end.
504 Capítulo 10 La codificación en la programación moderna: Pascal

Hay otros aspectos propios de Pascal utilizados en el programa. Decidimos que las
"rebanadas" del archivo sean cadenas pequeñas de caracteres, y por simplicidad defini-
mos un nuevo tipo de datos compuesto:

type cadena = string[12];

para entonces poder declarar variables de ese tipo:

dato, nombre: cadena;

así como el descriptor del archivo en disco:

archivo: file of cadena;

al que entonces se asignará el nombre de archivo proporcionado por el usuario. Luego de


enviar un mensaje pidiendo el primer dato, el while se encarga de leer de la pantalla y
escribir al disco, lo cual seguirá haciendo mientras no se dé la señal de fin de archivo.
Como se dijo en el capítulo 8, para indicar fin de archivo desde el teclado se emplea una
combinación especial, que en nuestro caso resultó ser el par <Ctrl>-C (las teclas "Con-
trol" y "C" al mismo tiempo). Así, con la función especial eof (end of file) —que puede
referirse al teclado o a un archivo en disco— se detecta el indicador de finalización.
Por lo demás, el programa es muy sencillo. A continuación se muestra un ejemplo
real de la ejecución:

Dame nombre para nuevo archivo: ALFA

1 Dame un núm. entero o <Ctrl>-C para terminar: 7


2 Dame un núm. entero o <Ctrl>-C para terminar: 12
3 Dame un núm. entero o <Ctrl>-C para terminar: 1
4 Dame un núm. entero o <Ctrl>-C para terminar: 15
5 Dame un núm. entero o <Ctrl>-C para terminar: <Ctrl>-C

Por otra parte, se puede escribir un segundo programa para tomar un archivo ya
existente en disco, abrirlo y leer su contenido. Como antes, primero debe declararse la exis-
tencia de un descriptor de archivo con "rebanadas" del tipo acordado:

archivo: file of cadena;

Luego se le pide un nombre al usuario, para ligarlo con su descriptor mediante la


instrucción
assign(archivo,nombre);

Después habrá que abrir el archivo, pero teniendo antes cuidado de averiguar si exis-
tía. Para ello, Pascal permite desactivar temporalmente sus funciones internas de verifica-
ción de errores de E/S, mediante el "comentario" especial

que impide que el sistema operativo aborte la ejecución del programa por haber intentado
abrir un archivo inexistente. Es decir, la instrucción

reset(archivo); (* Abre el archivo *)


Sección 10.6 Manejo de archivos 505

sirve para abrir un archivo, pero queda como responsabilidad del programador atrapar
internamente el posible error que se emitiría si no existe. Eso se logra manipulando la
función especial ioresult, que devuelve el valor O si no hubo errores en la última opera-
ción de E/S efectuada. Sin embargo, la función se vuelve cero luego de llamarla para
preguntar su estado, independientemente de su valor anterior, por lo que habrá que guar-
dar el resultado en alguna variable local si se está dentro de un ciclo, como se verá en el
programa.
De allí en adelante se podrá leer datos del archivo mediante la instrucción de lectura
usual, adicionada del descriptor:
read(archivo, dato);

Con esto se lee del archivo en disco un valor del tipo dato, empleando un formato
correspondiente al tipo de dato en cuestión. Se espera que la variable receptora de la
"rebanada" extraída del disco sea precisamente del mismo tipo que el de la variable pre-
viamente grabada allí; de no ser así podrá haber toda clase de molestos problemas (que el
programa aborte, que entre en un ciclo de ejecución ilimitado, o que los datos sean espu-
rios; todo lo cual constituye un amplio tema de estudio detallado).
Como en caso anterior, el archivo debe cerrarse antes de salir del programa. Ésta es la
traducción a Pascal del programa para leer datos de un archivo en disco:
(* Programa para leer registros del disco
program disco2;
(* Atrapa mensajes de error de E/S *)
uses Wincrt;
type cadena = string[12];
var
nombre, dato: cadena;
error, i: integer;
archivo: file of cadena;
begin
repeat
error := 0;
write('Dame nombre del archivo: ) ;

readln(nombre);
writeln;
assign(archivo,nombre); (* liga con el sistema de archivos *)
reset(archivo); (* abre el archivo *)
if ioresult <> 0 then
begin
writeln('Archivo inexistente.');
writeln;
error := 1
end;
until error = 0;
{$I+} (* Activa la protección de errores de E/S *)
writeln('Contenido del archivo:');
writeln;
i := 0;
while not eof(archivo) do
begin
read(archivo, dato);
writeln(dato);
i := i + 1
end;
506 Capítulo 10 La codificación en la programación moderna: Pascal

close(archivo);
writeln;
write('Se leyeron ', i , ' registros.')
end.

Observe el uso de {$I+} después de la verificación de existencia del archivo, para


volver a activar la protección de errores de E/S por parte del sistema operativo, porque
todavía falta leer los datos y conviene prevenir posibles errores catastróficos¡.
Nuevamente se empleó la función especial eof,, aunque esta vez referida al archivo
en disco y no al teclado, para controlar la lectura:

while not eof(archivo) do

Éste es un ejemplo real de la ejecución del programa:


Dame nombre del archivo: nada

Archivo inexistente.

Dame nombre del archivo: ALFA

Contenido del archivo:

7
32
0
15

Se leyeron 4 registros.

Sistema de calificaciones
Ahora se codificará en Pascal el pequeño sistema de calificaciones cuyo pseudocódigo se
diseñó en la sección 8.8.
Como se dijo, la fuente fundamental de información será un archivo principal con los
datos de cada alumno. Ésta es la forma definida para el archivo:
APELLIDO NOMBRE CALIFICACIÓN
20 caracteres 4-15 caracteres> <<entero > >

Para manejarla se empleará una nueva característica del lenguaje, consistente en la


declaración de una estructura de datos compuesta creada especialmente por el programador
para este fin (véase la sección 8.4). Esta estructura es muy limitada para ser de verdadera
utilidad práctica, pues no permite sino un número fijo (y pequeño) de caracteres para cada
campo, pero será suficiente para nuestras finalidades, que de antemano restringimos.

type registro = record (* Estructura del registro *)


apellido: string[20];
nombre: string[15];
calif: integer
end;

(t) El tema de la "programación a la defensiva" para evitar errores o engaños por parte de los
usuarios es largo y agotador, y muchas veces nos obligará a incluir código adicional para probar
casos "imposibles" o "ilógicos" que, si se aceptaran, harían al programa entrar en ciclos ilimitados
o comportarse en formas inesperadas o sumamente desagradables.
Sección 10.6 Manejo de archivos 507

Con la instrucción record se declaran estructuras de datos con una morfología, o


forma interna, escogida por el programador, para lo cual puede hacer uso de las estructu-
ras de datos primitivas atómicas (integer, real, etc.) o compuestas (array).
Una vez declarado un nuevo tipo de datos de Pascal, es posible definir otras nuevas
variables en términos de él, dentro de la declaración var que debe seguir, formando así
toda una red de variables complejas (definidas con este esquema de entrelazamientos).
Esta es una característica importante de Pascal que permite un uso complejo de las estruc-
turas de datos. Sin embargo, tal recurso es un arma de dos filos, porque si se abusa de él
pueden llegarse a escribir programas difíciles de entender, debido precisamente al alto
grado de interdependencia de las variables.
Es decir, después de declarar la estructura se define al menos un ejemplo (instance,
en inglés) de ella; primero se define para el compilador la existencia de entidades de un
nuevo tipo (registro en este caso) y luego se les "da vida" declarándolas y apartando
memoria para ellas, como en este caso:

var alumno: registro; (* Nueva variable de tipo registro *)

Con lo anterior se crea una nueva variable, alumno : el primer caso de lo que antes
llamamos una "estructura de datos compuesta no homogénea creada por el programador". Acceso a elementos
Precisamente porque se trata de una variable compuesta, muchas veces debemos distin- de un registro
guir entre sus componentes internos, lo cual en Pascal se logra mediante el "operador
punto":. que indica el componente al cual se está haciendo referencia. Por ejemplo,
alumno. calif se refiere al número entero que le sirve a la variable alumno como califica-
ción. Es decir, es válido escribir cosas como

alumno.calif := 90;

o tal vez
writeln( 'Su calificación es: ' , alumno. calif );

También se pueden manejar cadenas de caracteres encerradas entre apóstrofos, como


alumno.nombre := 'Nepomuceno';

siempre que no se exceda la longitud declarada para la variable, porque entonces se


truncaría.
Con los anteriores comentarios, ya estaremos listos para comenzar a estudiar el pro-
grama completo. Antes de seguir advertiremos al lector que los diversos compiladores y
sistemas operativos disponen de operaciones e instrucciones que no siempre están estanda-
rizadas, por lo que es casi seguro que los detalles cambien de una instalación a otra.
Todo nuestro código está incluido en el siguiente archivo fuente, que analizaremos
más adelante:

(* Codificación en Pascal del sistema de reportes *)


program alumnoP;
uses Wincrt;
{$I-} (* Evita errores de E/S en tiempo de ejecución, *)
(* permitiendo que los "atrape" el programa *)
{$R-} (* Deshabilita verificación de "rangos" *)
const
MARCA = #177; (* Carácter especial de "borrado" *)
TITULO = 'P2000';
508 Capítulo 10 La codificación en la programación moderna: Pascal

ap = 20;
nom = 15;
type registro = record (* Estructura del registro *)
apellido: string[ap];
nombre: string[nom];
calif: integer
end;
var
disco: file of registro;
opcion: char;

(* archivo *)
function archivo: boolean;
(* Determinar si el archivo existe (true) o no (false) *)
begin
reset(disco); (* abre el archivo *)
if ioresult = 0 then archivo := true
else archivo := false
end;

(* existe *)
(* Está colocado antes de los módulos hermanos que lo llamarán *)
function existe(datos: registro; var reg: longint): boolean;
(* Localizar un registro en el archivo *)
var
ya: boolean;
temp: registro; (* Variable auxiliar *)
begin
ya := false;
reg := 0; (* La primera vez, apuntar al inicio del archivo *)
reset(disco); (* abrir el archivo *)
while (not eof(disco)) and (not ya) do
begin
seek(disco, reg);
read(disco,temp);
if (temp.apellido = datos.apellido) and
(temp.nombre = datos.nombre)
then ya := true (* Sí fue *)
else reg := reg + 1; (* Avanzar *)
end;
existe := ya
end; (* existe *)

eliminar *)
(*
procedure eliminar;
(* Eliminar el archivo de datos *)
var c: char;
begin
if archivo then
begin
write(' ¿Seguro? (s/n): ');
readln(c);
if (c = 'S') or (c = 's') then
Sección 10.6 Manejo de archivos 509

begin
erase(disco);
if ioresult <> 0
then writeln(' No se pudo eliminar el archivo.')
else writeln(' Archivo eliminado.')
end
end
else writeln(' El archivo no existe.')
end; (* eliminar *)

(* altas *)
procedure altas;
(* Da de alta nombres y calificaciones en el archivo *)
var i, err: integer;
x: longint; (* Parámetro auxiliar; aquí sólo ocupa un espacio *)
c: string; (* Auxiliar para leer calificación *)
alumno: registro;
begin
(* Abrir el archivo de datos, o crearlo si no existía *)
if not archivo then
begin
writeln;
writeln(' El archivo no existía; se crea ahora.');
rewrite(disco)
end
else reset(disco);
(* Lectura inicial *)
writeln;
i := 1;
write(i:3,' Apellido, o un 0 para terminar: ');
readln(alumno.apellido);
while alumno.apellido <> '0' do
begin
write(' Nombre: ');
readln(alumno.nombre);
(* Cuidado con datos duplicados *)
if not(existe(alumno, x)) then
begin
repeat
write(' Calificación (0-100): ');
readln(c);
val(c, alumno.calif, err); (* Convierte a numérico *)
if err <> 0 then alumno.calif := 200
until alumno.calif in [0..100];
write(disco, alumno);
i := i+1
end
else
begin
writeln;
writeln(' ERROR: esa persona ya existe.')
end;
, 510 Capítulo 10 La codificación en la programación moderna: Pascal

writeln;
write(i:3,' Apellido, o un 0 para terminar: ');
readln(alumno.apellido)
end;
writeln;
write(' Total de alumnos dados de alta: ', i-1);
writeln;
close(disco)
end; (* altas *)

(* borrar ) *
procedure borrar;
(* Borrar datos del archivo *)
var i, calif: integer;
reg: longint;
alumno: registro; (* Variable local *)
begin
i : 1;
if archivo then
begin
writeln(i:3, ' Apellido del alumno a borrar,');
write(' o un 0 para terminar: ');
readln(alumno.apellido);
while alumno.apellido <> '0' do
begin
write(' Nombre: ');
readln(alumno.nombre);
writeln;
if existe(alumno, reg) then
begin
seek(disco,reg); (* Para ir directo a ese registro *)
(* Marca el registro como "inexistente" *)
alumno.apellido := MARCA;
write(disco, alumno);
i := i + 1;
writeln(' Borrado.')
end
else writeln(' ERROR: esa persona no está registrada.');
writeln;
writeln(i:3, ' Apellido del alumno a borrar,');
write(' o un 0 para terminar: ');
readln(alumno.apellido)
end;
writeln;
writeln(' Total de alumnos borrados: ', i-1)
end
else begin
writeln(' El archivo no existe:');
writeln(' aún no se han dado de alta alumnos.')
end
end; (* borrar *)
Sección 10.6 Manejo de archivos 511

(* cambiar *)
procedure cambiar;
(* Cambiar datos del archivo *)
var i calif, err: integer;
,

c: string; (* Auxiliar para leer calificación *)


reg: longint;
alumno: registro; (* Variable local *)
begin
i := 1;
if archivo then
begin
writeln(i:3, ' Apellido del alumno a quien');
writeln(' se desea cambiar la calificación,');
write(' o un 0 para terminar: ');
readln(alumno.apellido);
while alumno.apellido <> '0' do
begin
write(' Nombre: ');
readln(alumno.nombre);
writeln;
if existe(alumno, reg) then
begin
seek(disco,reg); (* Para ir directo a ese registro *)
read(disco,alumno);
writeln(' Su calificación anterior es ', alumno.calif);
repeat
write(' Nueva calificación (0-100): ');
readln(c);
val(c, calif, err); (* Convierte a numérico *)
if err <> 0 then alumno.calif := 200;
if calif = alumno.calif
then begin
writeln(' Es la misma...');
i := i - 1
end
until calif in [0..100];
i := i + 1;
if calif <> alumno.calif
then begin
alumno.calif := calif;
seek(disco, reg);
write(disco,alumno);
writeln;
writeln(' Cambiada.')
end
end
else writeln(' ERROR: esa persona no está registrada.');
writeln;
writeln(i:3, ' Apellido del alumno a quien');
writeln(' se desea cambiar la calificación,');
write(' o un 0 para terminar: ');
readln(alumno.apellido)
end;
512 Capítulo 10 La codificación en la programación moderna: Pascal

writeln;
writeln(' Total de calificaciones cambiadas: ', i-1)
end
else begin
writeln(' El archivo no existe:');
writeln(' aún no se han dado de alta alumnos.')
end
end; (* cambiar *)

(* imprimir *)
procedure imprimir;
(* Impresión del archivo *)
var i,p: integer;
suma: real;
alumno: registro;
begin
i := 0;
suma := 0;
if archivo then
begin
(* Lectura secuencial del archivo, sin imprimir los registros
"borrados" *)
writeln(' REPORTE DE CALIFICACIONES');
writeln;
reset(disco); (* Abrir el archivo *)
while not eof(disco) do
begin
read(disco, alumno);
if alumno.apellido <> MARCA then
begin
i := i + 1;
with alumno do
begin
write(i:3, ", apellido, ", nombre);
(* p: ancho del campo combinado *)
p := ap + nom - length(apellido) - length(nombre);
writeln(calif:p);
suma := suma + calif
end
end;
end;
if i > 0 then
begin
writeln;
writeln('Promedio: ':ap+nom+3, (suma/i):6:2);
end
else writeln(' El archivo está vacío.')
end
else
begin
writeln(' El archivo no existe:');
writeln(' aún no se han dado de alta alumnos.');
exit (* No cerrarlo sin haberlo abierto antes *)
end;
close(disco)
end; (* imprimir *)
Sección 10.6 Manejo de archivos 513

(* programa principal
begin
assign(disco,TITULO);
writeln;
writeln(' PEQUEÑO SISTEMA DE REPORTES PASCAL');
writeln;
writeln(' Estas son las operaciones disponibles:');
repeat
writeln; writeln;
writeln(' A. PONER CALIFICACIÓN A UN NUEVO ALUMNO . );
writeln(' B. BORRAR UN REGISTRO');
writeln(' C. CAMBIAR UNA CALIFICACIÓN');
writeln(' D. IMPRIMIR EL ARCHIVO');
writeln(' X. ELIMINAR EL ARCHIVO DE DATOS');
writeln(' F. FIN.');
writeln;
write(' Escriba su opción: ');
readln(opcion);
writeln;
opcion := upcase(opcion);
case opcion of
'A':altas;
'B':borrar;
'C':cambiar;
'D':imprimir;
'X': eliminar;
F': ;
else writeln(' Opción desconocida.')
end
until opcion = 'F';
writeln(' Adiós.')
end.

Observe que existen siete módulos "hermanos" de los cuales cinco corresponden a
rutinas llamadas directamente por el programa principal; además, archivo es una función
auxiliar, llamada por casi todos los demás módulos para detectar la existencia del archivo
de datos, y existe determina si un dato ya fue previamente grabado en el archivo en
disco. De acuerdo con la regla de manejo de bloques de Pascal, estos dos módulos deben
situarse antes de aquéllos de su misma jerarquía que los llamarán, para que el compilador
los conozca previamente y no emita mensajes de error.
La función booleana archivo emplea la función interna de Pascal ioresult para
devolver el valor de verdad verdadero (t rue) si el archivo existe (cuando ioresult = 0),
y false en caso contrario. Todo esto es posible, como ya se dijo, luego de deshabilitar
la función automática para interceptar errores de E/S, mediante el comentario especial
{$1 } .
Por otra parte, varios módulos del sistema emplean las facilidades de acceso directo
a archivos, que ya forman parte de todos los compiladores actuales de Pascal, aunque no Acceso directo
estaban definidas en el estándar original. Mediante el manejo directo de archivos se puede a archivos
llegar a un registro en particular sin pasar por todos los anteriores, lo cual se logra con la
instrucción

seek(<archivo>, <númeroL>);
514 Capítulo 10 La codificación en la programación moderna: Pascal

donde <n ú me ro L> es una variable tipo long int (apta para mantener valores enteros gran-
des) que especifica a cuál registro del archivo se referirá la próxima instrucción read o
write que aparezca luego, aunque no sea inmediatamente a continuación. La función
existe, por ejemplo, inicia leyendo el primer registro del archivo (definido como el nú-
mero O por Pascal) y preguntando si contiene los datos que le fueron pasados como paráme-
tro; si los datos coinciden, devuelve (mediante un parámetro va r por referencia) el número
de registro en donde los encontró, y en otro caso continúa leyendo registros, mientras no
se agote el archivo.
El módulo altas pone calificación a un alumno. Como indicaba el pseudocódigo
del capítulo 8, se crea un nuevo archivo de datos si aún no existe, y se procede a pedir el
nombre y calificación de los nuevos alumnos, verificando antes que no estén duplicados.
Si el archivo de datos existe, lo abre, y en caso contrario lo crea mediante la instrucción
rewrit e. Luego pide al usuario los datos del nuevo alumno a ser dado de alta, mostrando
un número secuencial que servirá de guía para saber cuántos van. Sin embargo, antes de
grabar los datos en el archivo, con una llamada a existe se pregunta si son nuevos. Como
lo único que interesa es saber si existen o no, se ignora su posible posición dentro del
archivo, empleando para ello la variable local x simplemente para ocupar el lugar obliga-
torio de ese parámetro.
En este módulo también se emplea la instrucción propia de Turbo Pascal ®, val, para
convertir una cadena de dígitos a un valor numérico, y que toma tres parámetros: el primero
es una variable de tipo cadena que contiene los caracteres numéricost proporcionados por
el usuario como respuesta a la pregunta sobre la calificación del alumno; el segundo es
una variable de tipo int eg e r para recibir el valor numérico resultante de la conversión, y
el tercero es una variable entera que toma un valor diferente de cero si la cadena de caracte-
res contenía símbolos no numéricos. Si éste fuera el caso —por error del usuario, claro—,
entonces asignamos un valor imposible a la calificación, para que sea automáticamente
rechazada por la lógica con la que fue diseñado el módulo, como se comprende de inme-
diato al leer el código. Como la validez numérica de la calificación se verifica mediante la
instrucción de Pascal

in [0..100];

es necesario antes deshabilitar la función automática de detección de violación de límites


predefinidos para valores, mediante el comentario especial

Si los datos del nuevo alumno son correctos (es decir, si no estaba ya dado de alta y
además su calificación es válida), entonces el registro se graba en el disco magnético, al
final del archivo. Ésa será necesariamente su posición, puesto que la función existe llegó
hasta el fin de archivo sin haberlo encontrado. Observe también que el módulo pide ini-
cialmente los datos para poder entrar al while la primera vez, y antes de finalizar vuelve
a pedir datos para decidir si realiza o no nuevamente el ciclo. Al terminar, el módulo
cierra el archivo.
El módulo borrar funciona en forma similar, tratando de localizar el registro que
contiene los datos proporcionados por el usuario. Dijimos ya que la gran limitante de los
archivos secuenciales es la práctica imposibilidad de eliminar en realidad un registro,
porque el archivo no puede contener "huecos" físicos; esto obliga a simular su elimina-

(f) Recuerde que el valor numérico 7 NO es lo mismo que el carácter ASCII '7'.
Sección 10.6 Manejo de archivos 515

ción mediante alguna marca que lo vuelva "invisible" —en términos lógicos— ante el
programa, aunque siga ocupando un lugar dentro del archivo.
Para ello se emplea la posición actual del registro que se desea "eliminar", para llegar
directamente allí mediante la instrucción seek ya mencionada y reemplazar el registro
actual por uno nuevo, sobreescribiendo en el apellido un carácter especial de marca que
no pueda confundirse con ninguna de las letras que forman un nombre cualquiera. Deci-
dimos emplear el carácter ASCII 177, que no es directamente accesible desde el teclado.
En Pascal los valores ASCII se denotan iniciando con el símbolo #, por lo que este carác-
ter especial se definió primero mediante

const MARCA = #177;

para que entonces pueda decirse

alumno.apellido = MARCA;

con lo cual efectivamente se está fabricando un apellido que los usuarios no podrían pro-
porcionar equivocadamente.
El módulo cambiar es muy parecido, pero en lugar de ocultar el registro deseado,
reemplaza su calificación actual por una nueva, siempre y cuando sea válida, para lo cual
emplea nuevamente la instrucción especial val dentro del ciclo de verificación. Existe un
caso especial, cuando la nueva calificación es la misma que la anterior, lo cual amerita
un mínimo mensaje de advertencia de que se ignoró ese reemplazo por ser obvio. Hemos
dicho que conviene reducir la cantidad de mensajes y preguntas para el usuario, haciendo
que la lógica del programa se anticipe a las situaciones previsibles.
Luego está el módulo de impresión del archivo, en donde –como siempre– se dedican
renglones para lograr una mínima presentación visual atractiva, aunque ni siquiera se
intenta hacer uso de las muy elaboradas (y muy costosas en términos de cantidad de líneas
de programa fuente) facilidades para desplegar colores, tipos de letra y ventanas o hacer
uso del inestimable "ratón". De hecho, la llamada "programación visual" prácticamente
requiere un curso (y un libro) especial, dada la amplitud, versatilidad y complejidad de la
GUI (Graphical User Interface: interfaz gráfica para el usuario —son los temas PI18 e
IH17-18 de los Modelos Curriculares)—t. Para el caso particular de Pascal, el lenguaje
visual Delphi® representa esa posibilidad, pero no es trivial, aunque así lo aparente.
Pues bien, nuestra rutina de presentación de resultados se comporta en forma pri-
mitiva, porque simplemente despliega renglones secuencialmente en la pantalla, aunque
gracias a eso es lo más pequeña posible.
El algoritmo es muy sencillo: abrir el archivo y colocarse en el inicio, para ir secuen-
cialmente leyendo los registros mediante la instrucción

read(disco, alumno);

que obtiene una "rebanada" completa de datos con la estructura predefinida. Después, si
el registro no está marcado como "borrado", se imprime y se lleva cuenta de la califica-
ción, para al final mostrar el promedio.
Quisimos mostrar los datos de cada alumno en una forma menos rígida que el estilo
tabular, pero ello no es sin costo. Si simplemente hubiéramos escrito el apellido (un arre-

(t) Una parte muy considerable del código de los programas que funcionan bajo entornos gráficos
tipo Windows®, se dedica a las complejidades del despliegue en la pantalla, y otra a manejar e inter-
pretar el movimiento del ratón.
516 Capítulo 10 La codificación en la programación moderna: Pascal

glo de caracteres de tamaño fijo ap) y luego el nombre (ídem, de tamaño nom), seguido de
la calificación, los renglones aparecerían así:

RODRÍGUEZ JUAN 100


SOTO ADRIANA 90

pero consideramos más elegante que el nombre esté inmediatamente después del apellido,
seguidos de la calificación en una posición fija:

RODRÍGUEZ JUAN 100


SOTO ADRIANA 90

Para eso es necesario calcular la longitud efectiva de cada campo (es decir, la canti-
dad de caracteres hasta antes del primer blanco), y restarla de la suma de sus longitudes
fijas. Turbo Pascal® dispone de un conjunto de funciones para manipulación de cadenas
de caracteres; lengt h(cadena) devuelve la longitud de una cadena, y entonces con la
secuencia de instrucciones

p := ap + nom - length(alumno.apellido) - length(alumno.nombre);


writeln(alumno,calif:p);

se logra el efecto deseado. Además, para evitar la repetición del prefijo alumno . se utilizó
la facilidad sintáctica with, que mantiene su validez dentro del par begin end que
sigue al do, como se observa en el fragmento

with alumno do
begin
write(i:3, ", apellido, ", nombre);
(* p: ancho del campo combinado *)
p := ap + nom - length(apellido) - lerigth(nombre);
writeln(calif:p);
suma := suma + calif
end;

Por último, la instrucción

writeln('Promedio: ':ap+nom+3, (suma/i):6:2);

coloca la palabra "Promedio:" en su posición correcta, y especifica un formato de seis


caracteres de ancho para su valor, con un valor redondeado a dos decimales.
Sigue después el módulo eliminar, que hace uso de la instrucción especial e r as e(archi-
vo) para borrar el archivo de datos, pidiendo antes confirmación al usuario, porque en este
caso la pregunta sí es significativa.
Como todo sistema de Pascal, éste termina con el código del programa principal, que
inicia a partir de su begin inicial. La primera instrucción es

assign(disco, TITULO);

que en esta versión de Pascal sirve para realizar la liga entre un nombre de archivo físico
definido por el usuario (a través de la declaración con st TITULO = `nombre';) y el descriptor
de archivo lógico disco.
Sección 10.6 Manejo de archivos 517

Tal vez lo único que resalte luego sea el renglón

opcion := upcase(opcion);

para convertir el carácter recién leído a mayúsculas (o dejarlo como estaba si ya era una
letra mayúscula).
Con esto termina el código fuente del sistema de reporte de calificaciones en Pascal.
Pedimos al lector que estudie cuidadosamente el programa para que, ayudado por el
pseudocódigo, avance en la comprensión de estos puntos.
A continuación se muestran ejemplos reales de su uso. Como todos los programas y
ejemplos del libro, el sistema fue compilado y ejecutado en nuestra computadora; apare-
cen subrayadas las respuestas del usuario:

PEQUEÑO SISTEMA DE REPORTES PASCAL

Estas son las operaciones disponibles:


A. PONER CALIFICACIÓN A UN NUEVO ALUMNO
B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: d

El archivo no existe:
aún no se han dado de alta alumnos.
A. PONER CALIFICACIÓN A UN NUEVO ALUMNO
B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: a

El archivo no existía; se crea ahora.


1 Apellido, o un 0 para terminar: Sartre
Nombre: Jean Paul
Calificación (0-100): 100

2 Apellido, o un 0 para terminar: Kissinger


Nombre: Henry
Calificación (0-100): 80

3 Apellido, o un 0 para terminar: Pérez


Nombre: María
Calificación (0-100): 82

4 Apellido, o un 0 para terminar: Madero


Nombre: Francisco
Calificación (0-100): 84
518 Capítulo 10 La codificación en la programación moderna: Pascal

5 Apellido, o un 0 para terminar: Pérez


Nombre: María

ERROR: esa persona ya existe.

5 Apellido, o un 0 para terminar: Star


Nombre: Ringo
Calificación (0-100): 25

6 Apellido, o un 0 para terminar: 1

Total de alumnos dados de alta: 5

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: 1

REPORTE DE CALIFICACIONES
1 Sartre Jean Paul 100
2 Kissinger Henry 80
3 Pérez María 82
4 Madero Francisco 84
5 Star Ringo 95
Promedio: 88.20

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción:

1 Apellido del alumno a quien


se desea cambiar la calificación,
o un 0 para terminar: Pérez
Nombre: Pedro
ERROR: esa persona no está registrada.
1 Apellido del alumno a quien
se desea cambiar la calificación,
o un 0 para terminar: Pérez
Nombre: María

Su calificación anterior es 82
Nueva calificación (0-100): 84

Cambiada.
Sección 10.6 Manejo de archivos 519

2 Apellido del alumno a quien


se desea cambiar la calificación,
o un 0 para terminar: O
Total de calificaciones cambiadas: 1

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: h

1 Apellido del alumno a borrar,


o un 0 para terminar: Madero
Nombre: Francisco
Borrado.
2 Apellido del alumno a borrar,
o un 0 para terminar: º
Total de alumnos borrados: 1

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: Q

1 Apellido del alumno a quien


se desea cambiar la calificación,
o un 0 para terminar: Star
Nombre: Rin()
Su calificación anterior es 95
Nueva calificación (0-100): 25
Es la misma...
1 Apellido del alumno a quien
se desea cambiar la calificación,
o un 0 para terminar: 0
Total de calificaciones cambiadas: 0

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: d
520 Capítulo 10 La codificación en la programación moderna: Pascal

REPORTE DE CALIFICACIONES
1 Sartre Jean Paul 100
2 Kissinger Henry 80
3 Pérez María 84
4 Star Ringo 95
Promedio: 89.75

A. PONER CALIFICACIÓN A UN NUEVO ALUMNO


B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN
Escriba su opción: x

¿Seguro? (s/n): s
Archivo eliminado.
A. PONER CALIFICACIÓN A UN NUEVO ALUMNO
B. BORRAR UN REGISTRO
C. CAMBIAR UNA CALIFICACIÓN
D. IMPRIMIR EL ARCHIVO
X. ELIMINAR EL ARCHIVO DE DATOS
F. FIN

Escriba su opción: f

Adiós.

Resta tan sólo terminar este largo capítulo con la misma recomendación que hemos
dado antes: practique cuanto pueda.

EJERCICIOS

1. ¿Cuántas soluciones existen para el problema de las ocho damas? Para averi-
guarlo, modifique el programa de modo que encuentre todas las soluciones (es
preferible que no las imprima, sino que sólo indique cuántas son) y ejecútelo en
su computadora.
2. En el ejercicio 12 del capítulo 8 se pedía modificar el pseudocódigo del problema
de las ocho damas para que encontrara las soluciones que son independientes de la
rotación del tablero. Codifique esta nueva versión e imprima las soluciones únicas,
varias por página.
3. Codifique en Pascal los programas que se pidieron en los ejercicios 2 a 8 del capí-
tulo 8 y ejecútelos en su computadora.
4. Escriba un programa en Pascal para traducir números arábigos a números roma-
nos. El programa debe emplear la representación abreviada para los números 4, 9,
40, 90, etc. Es decir, si recibe como entrada el número 44, por ejemplo, debe pro-
ducir como resultado >cuy y no xxxxim.
Éste es un buen ejemplo de un programa en el que se debe diseñar cuidadosa-
mente la relación entre el algoritmo (escrito, como siempre, inicialmente en
Palabras y conceptos clave 521

pseudocódigo) y las estructuras de datos, ya que una buena elección de éstas pro-
ducirá un programa sencillo y conciso. Un ejemplo extremo de cómo el algorit-
mo podría ser casi inexistente y las estructuras de datos complejas sería una gran
tabla que contuviera la representación de todos los números menores que 5000,
en donde el algoritmo simplemente localizara el número pedido y mostrara su equi-
valencia. Otro ejemplo, en el extremo contrario, sería un algoritmo complejo que
trabajara sólo sobre los caracteres I, V, X, C, M y D, y que los agrupara según se
requiriera. Sin embargo, un algoritmo así tendría que considerar los (múltiples)
casos especiales que surgen con las abreviaturas. Es preferible entonces encontrar
un equilibrio entre el algoritmo y las estructuras de datos (cosa que, además, vale
para todo programa).
5. El pequeño sistema de reportes expuesto en el apartado 10.6 añade los nuevos re-
gistros de datos siempre al final del archivo secuencial, aunque también podría
reutilizar el espacio ocupado por los registros "borrados", porque éstos no desapa-
recen del archivo sino que sólo se marcan como no existentes. Modifique el progra-
ma para que busque y llene esos espacios marcados, antes de simplemente agregar
un nuevo registro al final. ¿Habrá que modificar la estructura general del sistema?
¿Cuáles módulos tendrá que cambiar? ¿Vale la pena el esfuerzo adicional?
6. El pequeño sistema de reportes muestra los registros en el orden en que fueron
proporcionados, sin clasificarlos alfabéticamente. Aunque éste es tema de un curso
posterior (de estructuras de datos), intente escribir un módulo en Pascal que los
ordene, empleando el método más sencillo posible (que seguramente no será muy
eficiente).

PALABRAS Y CONCEPTOS CLAVE

En esta sección se agrupan las palabras y conceptos de importancia estudiados en


el capítulo, para que así el lector pueda hacer una autoevaluación, consistente
en ser capaz de describir con cierta precisión el significado de cada término, y no
sentirse satisfecho sino hasta haberlo logrado. Se muestran en el orden en que se
describieron o se mencionaron.
VARIABLE LOCAL
PASO DE PARÁMETROS POR VALOR
VARIABLE GLOBAL
MANEJO DE BLOQUES
JERARQUÍA DE HERENCIAS
PASO DE PARÁMETROS POR REFERENCIA
ALCANCE DE UNA VARIABLE
MODIFICADOR DE FORMATO
PASO DE ARREGLOS COMO PARÁMETROS
FUNCIONES
VALORES DE RETORNO
ARCHIVO
DESCRIPTOR DE ARCHIVO
CREAR UN ARCHIVO
ABRIR UN ARCHIVO
CERRAR UN ARCHIVO
EOF
PROGRAMACIÓN A LA DEFENSIVA
PROTECCIÓN DE ERRORES
REGISTROS DE UN ARCHIVO EN DISCO
522 Capítulo 10 La codificación en la programación moderna: Pascal

NOTACIÓN PUNTO
ESTRUCTURA DE DATOS COMPUESTA
INSTRUCCIÓN record
ACCESO DIRECTO A ARCHIVOS
PROGRAMACIÓN VISUAL

REFERENCIAS PARA
[GROP96] Grogono, Peter, Programación en Pascal, segunda edición, revisada,
EL CAPÍTULO 10
Addison-Wesley, México, 1996.
Traducción de la nueva edición de un exitoso libro, que durante mu-
cho tiempo se consideró como estándar en los cursos de programación
en Pascal. Ahora hay libros más amplios, y que consideran además la
utilización del lenguaje en las computadoras personales, pero éste sigue
siendo un libro importante.
[HAIP94] Haiduk, Paul, Turbo Pascal orientado a objetos, McGraw-Hill, México, 1994.
Traducción de un buen libro de programación, que toca desde temas
introductorios hasta técnicas de programación orientada a objetos. En
676 páginas, abarca tipos de datos sencillos y compuestos, estructuras
de datos, métodos de ordenamiento, manejo de archivos, recursividad y
apuntadores; además, dedica todo un capítulo a los tipos abstractos de
datos como preámbulo para los objetos. Todos los programas de ejem-
plo están en español.
[JENK74] Jensen, Kathleen y Niklaus Wirth, Pascal Manual and Report, Springer-
Verlag, Nueva York, 1974.
Referencia original del lenguaje de programación Pascal. Incluye la
definición formal del lenguaje en términos su gramática. El segundo autor,
Wirth, es ampliamente conocido por sus estudios sobre programación y
sistemas operativos, y se ha mencionado en los capítulos anteriores.
[KERP81] Kernighan, Brian y P.J. Plauger, Software Tools in Pascal, Addison-Wesley,
Massachusetts, 1981.
Adaptación para el lenguaje Pascal del excelente Software Tools es-
crito por los mismos autores, que se empleó como referencia en el capí-
tulo 5 ([KERB76]). Todos los algoritmos del libro original están traducidos
a Pascal, con la misma metodología y filosofía, lo cual lo hace valioso,
aun después de la aparición de las computadoas personales y de la
programación orientada a objetos.
[LEEN99] Leestma, Sanford y Larry Nyhoff, Programación en Pascal, cuarta edi-
ción, Prentice Hall, Madrid, 1999.
Traducción de un amplio libro (824 páginas) sobre principios de pro-
gramación y Pascal, con temas sobre recursividad, manejo de archivos,
estructuras dinámicas de datos y (a partir de la página 535) programa-
ción por objetos. Toca y explica temas específicos de versiones de Pascal
para diveross tipos de computadoras, incluyendo las personales. Todos
los ejemplos están codificados en español. Incluye un diskette con los
programas.
[SALW94] Salmon, William, Structures and Abstractions, segunda edición, McGraw-
Hill, Nueva York, 1994.
Buen libro introductorio a la programación y a Pascal, orientado al
compilador Turbo Pascal e para computadoras personales. Además de 440
páginas iniciales sobre estructura de programas, tipos de datos y estruc-
turas de control, dedica otras tantas a las abstracciones de datos y es-
tructuras compuestas, y concluye con varios capítulos sobre análisis de
algoritmos y complejidad.
Referencias para el capítulo 10 523

[ SAVW95] Savitch, Walter, Pascal, cuarta edición, Benjamin/Cummings, California,


1995.
Excelente texto subtitulado An Introduction to the Art and Science of
Programming, que en 17 capítulos cubre casi todos los temas existentes,
aunque no entra en la programación orientada a objetos, pero sí estudia
los tipos abstractos de datos y los emplea a lo largo de los muchos
ejemplos que contiene. Es un texto muy didáctico.
Savitch, como coautor con Michael Main, tiene otro libro que emplea
Turbo Pascal: Data Structures and Other Objects, Benjamin/Cummings,
California, 1995, dedicado a un segundo curso de programación, objetos
y estructuras de datos.

You might also like