Professional Documents
Culture Documents
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.
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
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
alfa = 1;
beta = 2;
zeta++;
}
(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++
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
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
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
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.
) 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
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[.
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)
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;
}
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
<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.
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):
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:
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;
}
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
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
void main()
{
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);
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];
}
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
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
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
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:
// Programa principal
void main()
{
}
436 Capítulo 9 La codificación en la programación moderna: C++
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,
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;
}
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í:
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:
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++
#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()
{
} // Fin de main
void modulo_A()
{
} // Fin de módulo_A
void modulo_B()
{
while ( x == 's' ) {
int i; // Estrictamente local al while
}
i = 0; // TERROR de compilación: i es inválida!
pasaría el cuarto elemento del arreglo como parámetro por referencia a un cierto módulo,
cuyo prototipo fuera, por ejemplo
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
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:
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":
Como ilustración, el siguiente programa suma dos vectores enteros, elemento a ele-
mento:
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++
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:
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:
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
return x * x;
}
Y allí se dijo que entonces con este fragmento se escribiría el menor de tres números
elevado al cuadrado:
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
return x * x;
}
Y el resultado es:
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:
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:
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
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:
// 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];
}
return maximo;
}
(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()
{
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++
// 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()
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:
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.
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:
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:
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;
} // 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
(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
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
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
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++
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.
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:
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
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:
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> -->
(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 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
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
que asigna valores iniciales de cero a todos sus componentes, aunque el compilador mar-
cará un error si se intenta escribir
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
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í:
// Prototipos
void eliminar();
void altas();
void retocar(char);
void impresion();
int existe(char[],char[], int &, long &);
/* main() *1
void main()
char opcion;
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
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.
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
ifstream prueba;
ofstream disco;
}
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);
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
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();
/* 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};
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 « "1n Total de alumnos borrados: " « i-1 « endl;
else cout « "1n Total de calificaciones cambiadas: " « i-1 « endl;
}
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:
(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++
/* 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;
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.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:
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:
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));
prom += alumno.calif;
cout.precision(2);
cout.setf(ios::fixed);
cout.width(6);
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 ®)
{
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:
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++
Su calificación anterior es 82
Nueva calificación (0-100): fi4
Cambiada.
Borrado.
Su calificación anterior es 95
Nueva calificación (0-100): 95
Es la misma...
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
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
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
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.
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
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
comienza
e,
e2
e3
termina
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):
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:
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
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
uses Wincrt;
e nd
Sección 10.3 Estructuras adicionales de control 479
alfa:
Dirección 1 2 3 4 5 6 7 8 9 10
t t
Límite inferior Límite superior
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.
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
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.
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;
for i := 1 to 10 do
ALFA[i] := 0;
i = 0
mientras (i < 10)
comienza
ALFA[i] = 0
i = i + 1
termina
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)
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;
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;
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;
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:
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
—
program principal;
(* aquí van las declaraciones de los datos del programa aunque
no necesariamente todas las que se muestran*)
const ;
type ;
var
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.
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 *)
• 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.
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 *)
PRINCIPAL
.UNO.TRES
.DOS.UNO.TRES
FIN
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
program principal;
(* Suma con parámetros *)
uses Wincrt;
var a, b, resultado: integer;
begin (* principal *)
write('Dame los valores de a y b: ');
readln(a,b);
suma(a, b, resultado);
writeln('a + b = ', resultado)
end.
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.
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
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
produciría
DOS: A = 1.000
DOS: A = 1.0000000000E+00
DOS: A = 1.0E+00
DIVIDE(355, 113);
y la definición del módulo dentro del programa principal es, por ejemplo:
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.
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 *)
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:
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
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:
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:
4
8
16
Sección 10.4 Módulos 495
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:
Y allí se dijo que entonces con este fragmento se escribiría el menor de tres números
elevado al cuadrado:
(* 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:
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:
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:
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.
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:
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 *)
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.
(* 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
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
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
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
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
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
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
Adiós.
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
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>);
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:
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:
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
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.
Archivo inexistente.
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 > >
(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 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 );
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;
,
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];
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
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í:
pero consideramos más elegante que el nombre esté inmediatamente después del apellido,
seguidos de la calificación en una posición fija:
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
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;
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
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:
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
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
Su calificación anterior es 82
Nueva calificación (0-100): 84
Cambiada.
Sección 10.6 Manejo de archivos 519
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
¿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).
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