533 80 3MB
Spanish Pages 322
CONTENIDOS
PRÓLOGO.............................................................................................. VIII 1. CONCEPTO DE COMPUTADOR.........................................................1 1.1. UNA PRIMERA APROXIMACIÓN AL COMPUTADOR ........................1 1.1.1 OPERACIONES BÁSICAS DEL PROCESADO DE DATOS ......................2 1.1.2 ALGORITMOS Y PROGRAMAS ..................................................................3 1.2. ANTECEDENTES HISTÓRICOS .................................................................5
1.3. ORGANIZACIÓN DE UN COMPUTADOR................................................9 1.3.1 LA ARQUITECTURA DE VON NEUMANN ...............................................9 1.3.2 UNIDADES FUNCIONALES.......................................................................10 1.4. EL CONCEPTO DE PROGRAMA ALMACENADO ...............................16 1.4.1 TIPOS DE INSTRUCCIONES......................................................................17 1.4.2 LENGUAJE MÁQUINA Y LENGUAJE ENSAMBLADOR.......................18 1.4.3 EJECUCIÓN DE UN PROGRAMA .............................................................20
1.5. CONCEPTO ACTUAL DEL COMPUTADOR..........................................24 1.5.1 DEFINICIÓN ACTUAL................................................................................24 1.5.2 PARÁMETROS BÁSICOS DE LA MÁQUINA ..........................................25
1.6. DEL COMPUTADOR A LA PROGRAMACIÓN .....................................26 1.6.1 LOS DATOS..................................................................................................27 1.6.2 LENGUAJES DE PROGRAMACIÓN DE ALTO NIVEL...........................27 1.6.3 ELEMENTOS BÁSICOS DE UN LENGUAJE DE ALTO NIVEL .............31 1.6.4 ORGANIZACIÓN DE UN PROGRAMA.....................................................35 1.6.5 TRADUCCIÓN DE PROGRAMAS..............................................................36 1.7. SISTEMA OPERATIVO Y PROGRAMAS DEL SISTEMA....................37
1.8. TIPOS DE COMPUTADORES ....................................................................39 1.9. COMUNICACIÓN DE DATOS Y REDES .................................................40 1.9.1 TRANSMISIÓN DE DATOS DENTRO DEL COMPUTADOR .................40 1.9.2 COMUNICACIÓN DE DATOS A LARGA DISTANCIA...........................42 1.9.3 REDES DE ORDENADORES ......................................................................43 III
IV
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
2. SOPORTE LÓGICO DE UN COMPUTADOR ................................. 45 2.1. CONCEPTO DE SOPORTE LÓGICO........................................................45
2.2. AYUDAS PARA LA PROGRAMACIÓN....................................................47 2.2.1 TRADUCTORES ...........................................................................................47 2.2.2 TIPOS DE LENGUAJES DE ALTO NIVEL ................................................52 2.2.3 UTILIDADES Y FASES EN LA EJECUCIÓN DE UN PROGRAMA........53 2.3. PROGRAMA DE ARRANQUE....................................................................55
2.4. SISTEMAS OPERATIVOS (SO)..................................................................57 2.4.1 FUNCIONES DE LOS SISTEMAS OPERATIVOS.....................................57 2.4.2 LA ESTRUCTURA DE UN SISTEMA OPERATIVO TÍPICO ...................58 2.4.3 ADMINISTRACIÓN DEL HARDWARE.....................................................60 2.4.4 ADMINISTRACIÓN DEL SISTEMA DE ARCHIVOS...............................62 2.4.5 APOYO A LA EJECUCIÓN DE PROGRAMAS DE APLICACIÓN ..........63 2.4.6 MÓDULOS PARA LA GESTIÓN DE REDES ............................................64 2.4.7 EJEMPLOS DE SISTEMAS OPERATIVOS................................................64
2.5. SOPORTE LÓGICO DE LAS REDES DE COMPUTADORES ..............72 2.5.1 SOPORTE LÓGICO BÁSICO.......................................................................72 2.5.2 RELACIONES EN UNA RED ......................................................................77 2.5.3 PROGRAMAS DE APLICACIONES DE LAS COMUNICACIONES .......78
3. ALGORITMOS Y PROGRAMAS....................................................... 81 3.1. CONCEPTO DE ALGORITMO ..................................................................81
3.2. LA RESOLUCIÓN DE PROBLEMAS Y EL USO DEL ORDENADOR 82 3.2.1 ANÁLISIS DEL PROBLEMA ......................................................................83 3.2.2 DISEÑO DEL ALGORITMO........................................................................84 3.2.3 PROGRAMACIÓN DEL ALGORITMO ......................................................88
3.3. REPRESENTACIÓN DE ALGORITMOS .................................................89 3.3.1 PSEUDOCODIGO .........................................................................................89 3.3.2 ORGANIGRAMAS .......................................................................................90 3.4. ESTRUCTURAS DE CONTROL.................................................................91 3.4.1 ESTRUCTURAS SECUENCIALES .............................................................92 3.4.2 ESTRUCTURAS SELECTIVAS...................................................................93 3.4.3 ESTRUCTURAS REPETITIVAS .................................................................99 3.5. PROGRAMACIÓN MODULAR................................................................111
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
V
3.5.1 FUNCIONES ...............................................................................................112 3.5.2 PROCEDIMIENTOS O SUBRUTINAS .....................................................115 3.5.3 ÁMBITO DE LAS VARIABLES................................................................117 3.5.4 PASO DE PARÁMETROS .........................................................................120 3.6. CONCEPTO DE PROGRAMACIÓN ESTRUCTURADA .....................126 3.7. RECURSIVIDAD.........................................................................................128
3.8. DESARROLLO Y GENERACIÓN DEL SOFTWARE...........................131 3.8.1 INGENIERÍA DEL SOFTWARE ...............................................................132 3.8.2 CICLO DE VIDA DEL SOFTWARE .........................................................133
4. ARITMÉTICA Y REPRESENTACIÓN DE LA INFORMACIÓN EN EL COMPUTADOR ..........................................................................137 4.1. SISTEMAS DE NUMERACIÓN EN INFORMÁTICA...........................137 4.1.1 DEFINICIÓN DEL SISTEMA BINARIO...................................................139 4.1.2 TRANSFORMACIONES ENTRE BASES BINARIA Y DECIMAL ........139 4.1.3 CÓDIGOS INTERMEDIOS ........................................................................141 4.2. OPERACIONES ARITMÉTICAS Y LÓGICAS......................................143 4.2.1 OPERACIONES ARITMÉTICAS CON NÚMEROS BINARIOS.............143 4.2.2 VALORES BOOLEANOS Y OPERACIONES LÓGICAS........................144 4.2.3 PUERTAS LÓGICAS..................................................................................145 4.2.4 ARITMÉTICA CON PUERTAS LÓGICAS...............................................147
4.3. REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR ..150 4.3.1 LA CODIFICACIÓN EN INFORMÁTICA................................................151 4.3.2 REPRESENTACIÓN INTERNA DE DATOS............................................154 4.3.3 REPRESENTACIÓN INTERNA DE PROGRAMAS ................................166 4.4. EL CONCEPTO DE TIPO DE DATO.......................................................167
5. ESTRUCTURAS DE DATOS .............................................................171 5.1. EL CONCEPTO DE DATOS ESTRUCTURADOS. ................................171 5.2. TIPOS DE DATOS ESTRUCTURADOS ..................................................172
5.3. ESTRUCTURAS DE DATOS CONTIGUAS............................................173 5.3.1 CADENAS...................................................................................................173 5.3.2 ARRAYS......................................................................................................175 5.3.3 REGISTROS ................................................................................................187
VI
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
5.4. ESTRUCTURAS DINÁMICAS Y PUNTEROS .......................................187
5.5. ESTRUCTURAS LINEALES .....................................................................188 5.5.1 LISTAS ENLAZADAS................................................................................189 5.5.2 VECTORES VS LISTAS ENLAZADAS....................................................198 5.5.3 PILAS...........................................................................................................198 5.5.4 COLAS.........................................................................................................201 5.6. ESTRUCTURAS NO LINEALES (ÁRBOLES)........................................202 5.6.1 ÁRBOLES BINARIOS ................................................................................203 5.6.2 ÁRBOL BINARIO DE BÚSQUEDA ..........................................................206
6. ARCHIVOS Y BASES DE DATOS ................................................... 213 6.1. ARCHIVOS: DEFINICIONES Y CONCEPTOS .....................................213
6.2. SOPORTE Y ACCESO A LOS ARCHIVOS ............................................216 6.3. EL SISTEMA OPERATIVO Y LA GESTIÓN DE ARCHIVOS ............218
6.4. ORGANIZACIÓN DE ARCHIVOS...........................................................218
6.5. OPERACIONES SOBRE ARCHIVOS......................................................220 6.5.1 APERTURA Y CIERRE DE UN ARCHIVO..............................................221 6.5.2 LECTURA Y ESCRITURA EN UN ARCHIVO ........................................222 6.6. PROCESAMIENTO DE ARCHIVOS .......................................................223 6.6.1 PROCESAMIENTO DE ARCHIVOS SECUENCIALES ..........................224 6.6.2 PROCESAMIENTO DE FICHEROS SECUENCIALES INDEXADOS ...230 6.6.3 PROCESAMIENTO DE FICHEROS DE ORGANIZACIÓN DIRECTA ..230 6.7. TIPOS DE ARCHIVOS ...............................................................................234
6.8. BASES DE DATOS ......................................................................................237 6.8.1 CONCEPTO DE BASE DE DATOS...........................................................240 6.8.2 ESTRUCTURA GENERAL DE UNA BASE DE DATOS.........................241 6.8.3 TIPOS DE BASES DE DATOS...................................................................242 6.8.4 SISTEMA DE GESTIÓN DE LA BASE DE DATOS ................................244
7. ALGORITMOS Y SU COMPLEJIDAD ........................................... 245 7.1. MEDIDA DE LA EFICIENCIA Y DE LA COMPLEJIDAD ALGORITMICA .................................................................................................245 7.1.1 ORDEN DE COMPLEJIDAD .....................................................................247 7.1.2 ANÁLISIS DE LOS ALGORITMOS DE BÚSQUEDA .............................249
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
VII
7.2. ALGORITMOS DE ORDENACIÓN Y SU COMPLEJIDAD ................250 7.2.1 ORDENACIÓN POR INSERCIÓN ............................................................250 7.2.2 ORDENACIÓN POR INTERCAMBIO ......................................................252 7.2.3 ALGORITMO DE SHELL ..........................................................................256 7.2.4 ALGORITMO DE ORDENACIÓN RÁPIDA (“QUICKSORT”) ..............258 7.3. EVALUACIÓN DE UN POLINOMIO ......................................................267 7.3.1 EVALUACIÓN DIRECTA .........................................................................267 7.3.2 ALGORITMO DE HORNER ......................................................................267 7.3.3 MÉTODO DEL ÁRBOL .............................................................................268
7.4. ALGORITMOS PARA LA BÚSQUEDA DE CADENAS DE CARACTERES ...................................................................................................272 7.4.1 ALGORITMO DE COMPARACIÓN .........................................................272 7.4.2 ALGORITMO DE BOYER-MOORE .........................................................274 7.5. NOTAS FINALES SOBRE COMPLEJIDAD...........................................276 7.5.1 ALGORITMOS NO-DETERMINISTAS....................................................276 7.5.2 PROBLEMAS DE CLASE P Y NP.............................................................277 7.5.3 INTRODUCCION A LAS MAQUINAS DE TURING Y A LOS PROBLEMAS ALGORITMICAMENTE IRRESOLUBLES ..............................278
ANEXO: SOPORTE FÍSICO DE UN COMPUTADOR......................285 ARQUITECTURA DE COMPUTADORES ....................................................285 PERIFÉRICOS DE LOS COMPUTADORES.................................................294
REDES Y CONEXIONES ENTRE COMPUTADORES................................308
BIBLIOGRAFÍA ......................................................................................317
PRÓLOGO
Con la puesta en marcha de los nuevos planes de estudios, en la Universidad española, han aparecido nuevas materias incorporadas a los curricula universitarios, que en los viejos planes de estudio no existían, o su contenido quedaba difuminado en el temario de otras asignaturas. Entre estas materias hay que situar el tema de los Fundamentos de la Informática y la Programación, que bajo distintos sinónimos, aparece en un gran número de Planes de Estudios, bien sea en forma de materia troncal, obligatoria u optativa. Este carácter de nueva materia, hace que, aunque en el mercado, exista una gran cantidad de excelentes textos, que cubren este material introductorio, el curso dirigido al estudiante de primer ciclo de los nuevos planes de estudio, esté por desarrollar, en parte, como tal material de apoyo a la tarea docente. Posiblemente, la constatación de esta carencia resulte sorprendente al lector acostumbrado a visitar los poblados anaqueles de la sección de informática de nuestras librerías; el libro de informática aparentemente está en plena fase de explosión; pero un análisis más cuidadoso indica que la mayor parte de nuevos títulos, se centran mucho más en la explicación más o menos profunda de los diversos paquetes o sistemas que cada temporada causan furor en el mundo del usuario del PC, que en el desarrollo metódico y detallado que toda disciplina científico-técnica merece. Nada más alejado de nuestra intención, criticar tal explosión de información en manos del público, al contrario, nos sentimos orgullosos de que nuestra especialidad profesional, sea la más abierta de todas las ramas científicas a la hora de transferir sus últimos conocimientos, para que sean utilizados por toda la ciudadanía, sin embargo queremos recordar que muchos productos informáticos, en su época considerados como sinónimos de alta especialidad, han pasado al olvido, barridos por otros que los superaban; con ellos muchos conocimientos y esfuerzos de aprendizaje han resultado obsoletos, ya que muchas veces, sólo se dominaba un determinado procedimiento, no una ciencia, ni siquiera una tecnología. Saltar de un paquete informático a otro, o de un procesador a otro, tiene poco sentido y mucho riesgo, sin una mínima, aunque sólida cultura informática. De ayudar a transferir cultura y conocimientos se trata y ésta es una de nuestras misiones, desde la universidad. A la hora de redactar el texto hemos pensado en dos tipos de posibles lectores. Por un lado el estudiante de Ingeniería Informática y por otro el estudiante VIII
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
IX
de otras titulaciones científicas. El primero, en sus primeras incursiones en su nueva carrera, tiende a identificar el aprendizaje de la informática, con el dominio de un lenguaje de programación y un conocimiento más o menos profundo de la estructura interna de la máquina. El segundo, sin embargo, aspira a que le enseñen el manejo del ordenador y la utilización de algunos paquetes de software que le vayan a resultar útiles para su futura profesión, además de alguna introducción elemental a la programación. Para ambos en este texto, hay un mensaje común: la Informática es mucho más que una serie de paradigmas ó filosofías de la programación y hay que aceptar que con su advenimiento, al árbol luliano de la ciencia, le ha brotado una nueva rama que con una cierta influencia anglosajona venimos llamando Ciencias de la Computación y con este nuevo bagaje tiene que iniciar su formación universitaria. Desde el punto de vista del estudiante de Informática, la filosofía de este texto esta en la línea de proporcionar un texto introductorio, que adapte los conocimientos de un estudiante español de primer ciclo, a las ideas desarrolladas por el Comité que elaboro el “ACM/IEEE Joint Curriculum”, que constituye uno de los esfuerzos más saludables que existen, de enlazar las necesidades profesionales, con los contenidos académicos. Para el universitario que ha elegido otra rama científica, este libro aspira a proporcionarle una cultura informática básica, que le permita por un lado poderla aplicar en su respectivo campo de conocimiento y por otro, al conocer sus principios básicos, poder hacer frente a las posibilidades que nos ofrecen los ordenadores, conociendo tanto sus fantásticas potencialidades, como sus limitaciones. Para tratar de cumplir, estos objetivos un tanto generalistas, apresurémonos a indicar, que este libro ha sido escrito, intentando abstraernos de cualquier lenguaje de programación concreto y en la medida de lo posible de cualquier máquina o sistema operativo de los que utilizamos diariamente. Esta tarea ha sido especialmente difícil, pues los autores tienen sus propias preferencias y profesionalmente están marcados, como todo el mundo, por sus experiencias personales. Esperamos que esta profilaxis haya sido fructífera, ya que estamos convencidos de que la única manera de introducir a un estudiante en la Informática, es tratando de separar constantemente lo que hay de contingente, en materia de útiles informáticos en el mercado en cada momento y lo que hay de nuevo para permanecer, por un cierto tiempo, en el acervo de la cultura informática. A pesar de lo anterior, es evidente que la programación debe probarse y experimentarse, mas allá de su cuerpo teórico, y para ello es necesario, que el estudiante maneje una máquina concreta, un sistema operativo determinado, adquiera ciertas habilidades de programación con un lenguaje de alto nivel razonablemente actual y maneje los últimos paquetes de software que le resulten útiles; esto tendrá lugar en el Laboratorio. Por ello no queda otro remedio que tomar decisiones en la planificación del curso; así el estudiante podrá trabajar sobre una terminal de un gran ordenador o moverse en al campo de la informática de PC o de Apple,
X
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
manejar DOS, UNIX u otro sistema, aprender C, Pascal, Fortran, Modula ó C++. Sin embargo, el libro si ha conseguido sus objetivos debe ser igual de útil para cualquiera que sea la decisión tomada.
SUGERENCIAS PARA USAR ESTE LIBRO
Este texto se ha elaborado pensando en un curso introductorio de nivel de primer ciclo, para estudiantes de Ciencias o Ingeniería. El material que aquí se presenta , asume que el estudiante va a recibir en paralelo un curso de carácter práctico, en el que se le explicará y ejercitará en un lenguaje de programación de alto nivel. Ello supone que en el laboratorio y frente al ordenador, el interesado acabará habituándose a una cierta máquina, a un sistema operativo, a un lenguaje y seguramente a un entorno de programación, que debe ser el complemento práctico de los conceptos aquí desarrollados. El material que aquí presentamos está dividido en 7 Capítulos y un anexo. El Capítulo 1 trata de recoger los conceptos que se consideran imprescindibles para poder enfrentarse a un ordenador y al proceso de programación. En nuestra opinión todo el contenido allí desarrollado tiene la vitola de fundamental y en consecuencia estos conceptos no deberían ser orillados en un curso de introducción. El Capítulo 2 dedicado al Soporte Lógico de la máquina puede considerarse como una profundización de los aspectos relacionados con el software, vistos en el Capítulo anterior; por tanto este capítulo puede evitarse si el número de créditos asignados al curso no fuera suficiente. Sin embargo el capítulo puede ser de utilidad como referencia durante las clases prácticas donde constantemente se usarán y practicarán módulos pertenecientes a este soporte lógico. El Capítulo 4 está dedicado a la representación interna de información en el ordenador, éste es un tópico que debe ser tratado en cualquier curso, sin embargo la profundidad con la que ello se haga, dependerá de la orientación que se pretenda dar en cada caso. En consecuencia queda al criterio del profesor la selección del material que considere oportuno para sus objetivos finales. Los Capítulos 3 (Algoritmos y Programas) y Capítulo 5 (Estructuras de Datos) constituyen el núcleo de un curso de Introducción a la Programación. De hecho, el texto se mueve constantemente bajo el principio de Programación = Algoritmo + Estructuras de Datos y de esta forma se ha estructurado el material con la intención que el estudiante termine su lectura habiendo conseguido una cierta destreza en las técnicas y metodología de la programación estructurada. Es posible que se considere que algunas de las estructuras descritas superen los niveles de algunos cursos, especialmente en lo que se refiere a las estructuras dinámicas. No existe ninguna dificultad en evitar este material, en una primera lectura, aunque consideramos que los recursos que el uso de punteros proporciona no deben ser
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
XI
ocultados al estudiante teniendo en cuenta las posibilidades de los modernos lenguajes de programación. El Capítulo 6 complementa las estructuras de datos, analizando la forma como se organiza y maneja la información que reside en un periférico de almacenamiento. De nuevo el material aquí presentado, puede ser objeto de una determinada selección en función de los objetivos y del tiempo con que se cuente. El Capítulo 7 trata de culminar el curso, desde la óptica de la complejidad algorítmica y de la comparación de algoritmos. Este es un capítulo que pasa de los temas más prácticos, como la ordenación, a las cuestiones más teóricas como las clases de algoritmos y la máquina de Turing en un intento de abrir a los ojos del estudiante, las cuestiones computables que existen más allá de la capacidad del computador. Ante el hecho de que, por un lado, estaba fuera de lugar en este libro un capítulo que profundizara en cuestiones de hardware y que por otro, se tiene que tener una cierta información acerca de dispositivos, redes, etc, hemos decidido incluir un Anexo con los términos más significativos del Soporte Físico del ordenador. Este Anexo trata de ser más que un simple glosario, de forma que, cada término desarrollado se pueda comprender con suficiente amplitud. Una nota final, es que en ningún caso el libro sustituye a curso alguno, de hecho, no se ha puesto especial énfasis en los ejemplos, ni se han incluido ejercicios (esperamos que se disculpen estas carencias); ya que éstas son decisiones que dependen de cada profesor y de cada curso.
AGRADECIMIENTOS
Este libro no sería pensable sin la existencia del LISITT (Laboratorio Integrado de Sistemas Inteligentes y Tecnologías de la información en Transporte) y de la Unitat Docent d’Informàtica del Departament d’Informàtica i Electrònica de la Universitat de València. Sin el especial espíritu de estos hombres y mujeres, muchos proyectos no habrían visto la luz y este libro es sólo un ejemplo más. Hay que dar las gracias tanto a los que han puesto su pluma cuando ha sido necesaria como a los que han expresado sus críticas o comentarios. La relación sería excesivamente extensa y esperamos que los amigos nos disculpen el que sólo nombremos a la única persona que ha trabajado todo el libro y seguramente nunca lo tendrá que explicar desde una pizarra: Cristina Roda, un ejemplo de lo que una mujer puede hacer con un procesador de textos y un corazón de no sabemos cuántos Megas.
XII
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Valencia, Septiembre de 1995 LOS AUTORES
CAPÍTULO 1
CONCEPTO DE COMPUTADOR
La Informática, definida por la Real Academia como el conjunto de conocimientos científicos y técnicos que hacen posible el tratamiento automático de la información por medio de computadoras, requiere previamente contestar a determinadas preguntas sobre el funcionamiento de estas máquinas. En este primer capítulo se pretende dar los conceptos mínimos e imprescindibles, para que el lector tenga una visión panorámica del contenido del libro, de forma que cuando se adentre en los próximos capítulos sepa enmarcar el sentido e importancia de cada uno de ellos.
3030" WPC"RTKOGTC"CRTQZKOCEKłP"CN"EQORWVCFQT De forma genérica y en una primera aproximación, un computador puede definirse como una “máquina digital, electrónica, programable para el procesamiento de información”. Notemos por tanto, que el computador1, en cuanto a “máquina” se refiere, está en la categoría de los molinos de vientos, telares, etc., que pueden funcionar correcta e incorrectamente. “Digital” significa que estas máquinas trabajan almacenando información, en forma de códigos, que representan las letras o dígitos de los números. “Electrónica” sugiere que está construida usando componentes electrónicos de estado sólido, conocidos por circuitos integrados. Programable significa que admite la posibilidad de ejecutar instrucciones a demanda de una sucesión de órdenes preestablecidas. Un ejemplo de máquina programable, son las lavadoras automáticas actuales ó los más tradicionales carrillones. 1 Al definir computador como un tipo de máquina (femenino), podríamos utilizar la palabra computadora, como se hace en Sudamérica. Por las funcionalidades de la máquina, el término “computador/a”, que proviene del latín computare, debería ser preferido al más popular de ordenador (ordenar es sólo una de las múltiples funciones que la máquina es capaz de llevar a cabo). En este libro usaremos libremente cualquiera de estos tres sinónimos.
1
2
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
El concepto “procesamiento de información”, es menos obvio. Información es un término general que abarca hechos y representaciones que pueden estar o no relacionadas entre sí. A dichas entidades codificadas, en condiciones de ser introducidas en el computador, los llamaremos datos que pueden usarse de forma individual o como grandes conjuntos, dotados de una estructura (por ejemplo una matriz); de hecho los datos son significativos en sí y en relación con la estructura a la que pertenecen (un número de teléfono no es de gran utilidad si no se sabe a quien pertenece). La unidad más elemental de información es una variable binaria, conocida como BIT (contracción de las palabras BInary y digiT), que toma el valor 0 ó 1. El bit representa la mínima información posible, ya que corresponde a la ocurrencia de un suceso, de entre dos posibilidades distintas. Por ejemplo, un bit es la cantidad de información correspondiente a un mensaje anunciando si determinado caballo ha ganado (1) o perdido (0) una carrera. 30303" QRGTCEKQPGU"DıUKECU"FGN"RTQEGUCFQ"FG"FCVQU Procesar los datos, consiste en someterlos a un conjunto de operaciones tales como ordenación, selección, ejecución de cálculos, etc. de forma que nos permita extraer conclusiones de los datos que manipulamos. Por tanto, procesar información es transformar datos primarios en información organizada, significativa y útil, que a su vez está compuesta de datos (Ver Figura 1.1.) Gpvtcfc (Datos)
Rtqegucfqt
Fig. 1.1.
Ucnkfc (Información)
Proceso de datos
En el concepto de procesar, entran ejemplos tan diversos como la obtención de las raíces de un polinomio a partir de los datos de los valores de sus coeficientes, o el control de un cohete en vuelo, a partir de todos los datos físicos del sistema. Nótese, que el concepto de procesador es más amplio que el de computador, ya que existen procesadores que no son computadores: Un termostato a partir del dato de la temperatura tiene como salida el control del aire acondicionado y nuestro propio sistema cardiovascular, a partir de los datos sensoriales reacciona adaptándose a las distintas circunstancias. De acuerdo con lo anterior, para procesar datos, el computador debe ser capaz de realizar ciertos tipos de operaciones:
CONCEPTO DE COMPUTADOR
3
• Entrada de datos: Suministrar información al computador desde su entorno exterior (ej. pulsar el teclado, o leer un código de barras). • Salida de datos: Obtener información de un computador. (ej. visualizar los resultados en una pantalla o impresora). • Almacenamiento: Hacer una copia permanente de la información con el objetivo, que el computador pueda emplearla de nuevo (ej. copiar en cintas y discos magnéticos). • Recuperación: Leer de nuevo la información almacenada (en cinta o discos magnéticos). • Transmisión: Transferir la información a otro computador a través de una red de comunicación de datos. • Recepción: Recibir la información enviada por otro computador. • Tratamiento: Operaciones sobre datos, tales como la ordenación, selección, combinación, reclasificación, así como la ejecución de cálculos, que permita obtener la información deseada. 30304" CNIQTKVOQU"["RTQITCOCU Llamaremos algoritmo a un conjunto de pasos y acciones, que especifican de forma no ambigua y finita, la secuencia de operaciones a realizar para procesar datos con un determinado objetivo. (Este concepto abarca, desde un método de resolución de ecuaciones, hasta la ejecución de una receta de cocina). Hagamos dos aclaraciones previas: a) La algorítmica existe con anterioridad al desarrollo de los computadores actuales (el propio origen de la palabra proviene de un matemático árabe que describió en el siglo VIII el método manual con el cual dividimos dos números). b) Una vez encontrada este conjunto de instrucciones que resuelven el problema, éstas se pueden ejecutar sin que sea necesaria la comprensión de los principios en los que se basa el algoritmo. Así cuando dividimos dos números o construimos una pajarita a partir de una cuartilla, nos limitamos a ejecutar una serie de acciones en un determinado orden, sin preocuparnos demasiado por sus correspondientes bases teóricas. Para el caso en que el procesador sea un computador, la forma de expresión de este algoritmo se llama programa. Cada paso del algoritmo se expresa en el programa, por medio de un conjunto de instrucciones escritas en un determinado lenguaje. Uno de los aspectos más llamativos de los computadores es el hecho de que con un reducido número de instrucciones básicas, pueda llevarse a cabo una gran cantidad de tareas. El conjunto de pasos que constituyen el programa debe escribirse de forma muy precisa. La ambigüedad inmanente a los lenguajes naturales (español, inglés, etc.) los invalida para expresar algoritmos, por ello existe un cierto tipo de lenguajes que
4
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
reúne la suficiente precisión como para expresar algoritmos: son los llamados lenguajes de programación. Estos lenguajes tienen, como todo lenguaje una sintaxis (cómo agrupar las símbolos y elementos propios del lenguaje para formar frases válidas) y una semántica (qué significa cada frase válida de este lenguaje) diferenciándose de los lenguajes naturales en cuatro características: 1) Su vocabulario y su sintaxis son sencillos y limitados. Esto los hace, inadecuados para describir cualquier tipo de prosa no algorítmica. 2) El vocabulario de un lenguaje de programación contiene solamente aquellos tipos de acciones básicas que puede ejecutar un ordenador (operaciones aritméticas, lógicas, entrada/salida, etc.) y no otras. 3) La sintaxis es muy rígida y no permite excepciones ni muchas variaciones. Por ejemplo para calcular una división de “X” por “A”, hay que escribir obligatoriamente “X/A”. 4) Su semántica es estricta y la ambigüedad no tiene cabida en ellos. Un mismo algoritmo puede expresarse en diferentes lenguajes y ejecutarse en distintos ordenadores. Contar con un algoritmo es una condición necesaria para la obtención de un programa, aunque no es suficiente, ya que su realización requiere además, creatividad y conocimientos sobre los recursos físicos y lógicos del ordenador. La fase de conversión del algoritmo a programa se denomina codificación, ya que el algoritmo escrito en un lenguaje específico de programación se denomina código. Para que se puedan procesar los datos por medio de un programa, el conjunto de instrucciones que lo constituyen debe ser accesible para la máquina y en consecuencia, ésta debe tener la capacidad de almacenarlo internamente, al objeto que pueda efectuar el control de las operaciones que constituyen el algoritmo. Aunque todavía no hayamos profundizado en el concepto de computador, sí que podemos deducir que éste tiene que ser capaz de trabajar y reconocer simultáneamente tanto las instrucciones, que determinan lo que hay que hacer, como los datos que se van a procesar. Este requisito es tan fundamental, que la forma como éste se ha venido plasmando, conceptual y técnicamente a lo largo de la historia, permite rastrear los orígenes de la informática.
3040" CPVGEGFGPVGU"JKUVłTKEQU En 1982, la revista TIME nombró “hombre del año” a la computadora. La noticia supuso un impacto cultural importante y oficializó el hecho de que esta máquina es un personaje más de la historia de la humanidad. Aunque considerada como un paradigma de la etapa moderna y con sólo 30 años de verdadera historia
CONCEPTO DE COMPUTADOR
5
(hasta mediados de los años 60 las computadoras eran caras y para usos muy específicos que sólo las grandes instituciones y universidades podían permitirse) su consecución, de una u otra forma, ha ocupado y preocupado a muchas de las cabezas que jalonan la historia de la ciencia. El hecho de que nuestro estado actual de avance tecnólogico haya permitido su construcción, no debe ocultar los esfuerzos hechos desde los inicios del pensamiento científico. Ábacos La necesidad de contar con un elemento mecánico que ayude a las personas en sus tareas de manipulación numérica, o lo que es lo mismo una máquina que ejecute algoritmos de forma más o menos automática es realmente antigua. Todos los historiadores coinciden en citar como antecedente de lo que hoy llamamos computador al ábaco; instrumento utilizado para la suma y la resta, desde hace 4000 años. Este consiste en un conjunto de alambres que llevan ensartados unas cuentas móviles, colocadas en un marco rectangular cruzado por una barra horizontal que atraviesa los alambres (Figura 1.2). Cada una de las cuentas situadas por encima de la barra horizontal vale cinco, y cada cuenta situada bajo la barra vale uno. Si comenzamos por la parte de la derecha y nos vamos moviendo hacia la izquierda, el primer alambre representa el dígito de las unidades; el segundo, el de las decenas; y sucesivamente las centenas, los millares, etc. Borramos o ponemos a cero el ábaco separando todas las cuentas de la barra horizontal. Introducimos los números moviendo las cuentas correspondientes hacia esa barra, comenzando por el dígito situado más hacia la derecha, y moviéndonos hacia la izquierda. Este artilugio resulta de especial interés para ayudar a sumar o restar dos números. La entrada de datos se hace a través de una persona, que también se encarga de la ejecución y control del algoritmo de suma o resta correspondiente, la salida de datos es consecuencia de la observación de las cuentas, por lo que esta máquina seria un sistema de almacenamiento, que necesita de un experto humano para procesar información (suma y resta).
6
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Fig. 1.2 Representación en un ábaco del número 1610 Calculadoras mecánicas A medida que las distintas otras ramas científicas se desarrollaron, se puso en evidencia la necesidad de optimizar la capacidad de cálculo habida cuenta de lo dificultoso de las operaciones que se tenían que llevar a cabo. Ello hizo que muchos matemáticos se plantearan el desarrollo de instrumentos que facilitaran al menos teóricamente esta labor. Así, John Napier, que recordamos por los logaritmos neperianos, publicó un estudio en 1617 que describía la utilización de cuentas con una marca especial, para realizar multiplicaciones y divisiones (la obra matemática de Napier condujo mucho más adelante al desarrollo de la regla de cálculo, venerable instrumento que durante muchos años fue para algunos de nosotros la “máquina” primaria para llevar a cabo cálculos complejos). En 1642, Blaise Pascal inventó la primera “sumadora” real, parecida a las calculadoras mecánicas que se popularizarian en nuestros años sesenta. Se trataba de una compleja combinación de ruedas, engranajes y ventanas a través de las cuales aparecían los números. A finales del siglo XVII, otro famoso matemático, Gottfried Leibnitz, desarrolló una máquina parecida, pero más avanzada; podía sumar, restar, multiplicar y dividir mecánicamente, e incluso sacar raíces cuadradas.
CONCEPTO DE COMPUTADOR
7
A pesar de la ingeniosidad de estos planteamientos mecánicos, hasta 1820 no se pudo disponer de la tecnología que permitiera la aparición de las primeras máquinas comerciales capaces de efectuar las cuatro operaciones matemáticas básicas. Esta es una constante en la historia de las máquinas de calcular, el decalaje existente entre los diseños originales y las disponibilidades tecnológicas para llevarlos a la práctica. En la máquina de Leibnitz, se produce un avance respecto al ábaco, puesto que el algoritmo esta ya incorporado en la propia estructura de la máquina y además respecto a la de Pascal supone un incremento en flexibilidad, pues el usuario puede seleccionar, la operación que desea llevar a cabo. Sin embargo, en su tiempo no pasaron de ser meras curiosidades, sin posibilidad de aplicación práctica. Las tarjetas perforadas y los computadores mecánicos A comienzos del siglo XIX se producen aportaciones, curiosamente ligadas a la resolución de problemas de naturaleza no numérica. En 1801, Joseph Jacquard, inventó un telar controlado mediante instrucciones almacenadas según un código representado en tarjetas perforadas; de esta forma, el algoritmo que segue la máquina podrá cambiarse fácilmente para conseguir un dibujo distinto sobre la tela. La máquina de Jacquard incorporó varios elementos básicos que constituyen los computadores actuales. La idea de usar en tarjetas perforadas, para guardar tanto números como instrucciones datos indujo, en 1835, a Charles Babbage, a inventar un computador digital matemático de tipo mecánico que recibió el nombre de máquina analítica. Babbage utilizó las tarjetas perforadas para programar su máquina, que podía utilizar los resultados de un cálculo como entrada del siguiente y que era capaz de manejar cálculos repetitivos, que más adelante llamaremos bucles. Un logro, aun más significativo, de la máquina analítica fue que, en vez de seguir las instrucciones del programa en la secuencia prefijada, podía saltar de una a otra (los programadores actuales hablamos de bifurcaciones condicionales). Aunque los computadores actuales están basados en muchos de los principios que Babbage utilizó, en su tiempo no existía ningún procedimiento que pudiera mover su cada vez más complejo artilugio mecánico. Una vez más, la tecnología no era la adecuada para el desarrollo teórico propuesto y el trabajo de Babbage se saldó con un fracaso, sólo mitigado por el apoyo de Ada Lovelace, hija de Lord Byron, considerada como la primera programadora de la historia y en cuyo honor el lenguaje de programación ADA lleva su nombre. El concepto de la tarjeta perforada para almacenar programas y datos llegó a prender, y Herman Hollerith para procesar el censo de 1890 en EEUU la recuperó con notable éxito (consiguió un ahorro de tiempo de varios meses) al incorporarla a máquinas alimentadas eléctrica y no mecánicamente. Hollerith puso las bases de
8
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
una de las empresas que más adelante se integraría en la International Business Machines (IBM). En paralelo, George Boole (1815-1864), el fundador de la teoría de la lógica matemática, nos legó un álgebra para representar cantidades lógicas e investigó las operaciones que se pueden realizar con estas variables (álgebra de Boole). La lógica booleana es la base teórica tanto para el diseño de circuitos electrónicos como para muchas técnicas de programación, aunque difícilmente, pudo ser Boole consciente de la trascendencia práctica de su teoría. Los computadores electromecánicos Coincidiendo con la aparición de las máquinas eléctricas y bajo le presión del esfuerzo bélico de la Segunda Guerra Mundial (resolución de claves secretas, etc, ) se desarrollaron computadores electromecánicos, como elMark I (1944) de la Universidad de Harvard. Se trataba de una máquina de grandes dimensiones, 15,5 metros de largo por 2,4 metros de altura, en la que las instrucciones se introducían mediante cinta de papel, y los datos mediante tarjeta perforada y un teletipo iba escribiendo los resultados. El Mark I podía multiplicar dos números en unos tres segundos. En 1947, su sucesor el Mark II podía llevar a cabo la misma multiplicación en un cuarto de segundo aproximadamente. Era doce veces más rápido y suponía, un gran avance; sin embargo su obsolescencia fue inmediata, como veremos a continuación, con el advenimiento de la electrónica. Computadores electrónicos La electrónica empieza con la válvula o tubo de vacío (un bulbo de vidrio, donde una placa de metal calentada por un filamento emite electrones que se desplazan en el vacío debido a una diferencia de potencial entre el cátodo y el ánodo). El primer computador digital electrónico utilizaba tubos de vacío y podía realizar una multiplicación en unos 2,8 milisegundos. Fue desarrollado en 1946 por un equipo de la Universidad de Pennsylvania. Esta máquina recibió el nombre de ENIAC, (Electronic Numerical Integrator and Computer) su primera aplicación, a pesar de la finalización de la guerra, fue el cálculo de las tablas de tiro de la artillería. El ENIAC se programaba cambiando manualmente los conectores y manipulando conmutadores, lo que, como podemos imaginar, requería gran cantidad de tiempo, por lo que su uso casi no traspasó el ámbito académico.De hecho, éstas máquinas no tuvieron ningún impacto comercial, aunque sentaron claramente las bases del diseño de los computadores actuales. Cuando en 1947 tiene lugar, en los laboratorios Bell, la invención del transistor (más pequeño que la válvula, menor consumo y mayor fiabilidad) se posibilita el paso del computador, de pieza experimental de laboratorio, a dispositivo con ciertas posibilidades comerciales.
CONCEPTO DE COMPUTADOR
9
Otro avance tecnológico posterior, los circuitos integrados, permitieron integrar en un único sustrato de silicio cientos de transistores (actualmente esta integración es ya del orden de millones, en los llamados chips2), con lo que quedaron sentadas las bases de los computadores actuales. A pesar de que quedaban muchos problemas por resolver, a principios de los años sesenta, las ideas y las tecnologías estaban ya maduras y se trataba de seguir una evolución, cuyos resultados no han dejado de sorprendernos hasta ahora. El computador es uno de los elementos claves de la actual revolución científico-industrial y su papel, en la historia de la humanidad, no será menor que el que jugaron en su día, la rueda, el motor de explosión o la energía nuclear.
3050" QTICPK\CEKłP"FG"WP"EQORWVCFQT 30503" NC"CTSWKVGEVWTC"FG"XQP"PGWOCPP John von Neumann3 (1903-1957), que había colaborado en el diseño varios computadores durante la Segunda Guerra Mundial, publica en 1946 artículo que esboza los principios generales de diseño de un computador en sentido actual: - La máquina es controlada por un conjunto de instrucciones, con pequeño número de elementos centrales de proceso.
de un su un
- El programa se almacena en el computador de forma que en su representación interna no se hacen distinciones entre datos e instrucciones, ambos almacenados en código binario. De esta forma el programa pasa a estar “dentro” del computador, y así el cambio de un programa a otro sólo implica un cambio en el valor de posiciones de memoria, en contraposición con lo hecho hasta la fecha que requería cambios manuales de clavijas. Estos principios, enriquecidos con importantes aportaciones tecnológicas, . La forma que adoptan estos circuitos encapsulados con sus conectores, los asemeja a las pulgas (chip en inglés), por lo que esta denominación es la que ha terminado por imponerse. 2
3
Von Neumann y sus colaboradores describieron una máquina constituida por dos órganos, uno constituido por las unidades de control, de aritmética y de entrada salida y otro formado por la memoria. En esta máquina la memoria ocupaba 4096 palabras o posiciones de memoria cada una de 40 bits. Cuando una palabra se interpretaba como dato, estos 40 bits representaban un número en notación binaria y cuando lo hacía como instrucción, cada palabra contenía 2 instrucciones de 20 bits. Los datos eran todos enteros y las operaciones aritméticas que podía efectuar eran suma, resta, multiplicación, división y valor absoluto, de forma que el resultado de cualquiera de ellas se situaba en un registro llamado acumulador. A cada posición de memoria podía asignársele el valor contenido en el acumulador, de forma que se reemplazaba el valor anterior almacenado en esta posición de memoria. El flujo de control del programa era el de pasar de una instrucción a la siguiente, pudiendo ser interrumpida esta secuencialidad a través de un salto incondicional (goto) de forma que la siguiente instrucción a ser ejectuada era la de la palabra de memoria que se mencionaba en la instrucción del salto incondicional.
10
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
se han mantenido vigentes hasta nuestros días en lo que llamamos máquina de von Neumann, y cuyo esquema general es el de la Figura 1.3.
30504" WPKFCFGU"HWPEKQPCNGU Las unidades funcionales de una máquina de von Neumann son cinco: Unidad de Control (UC), Unidad Aritmético-Lógica (ALU), Unidad de Entrada, Unidad de Salida y Memoria (principal y secundaria). Por su disposición y función, se llama unidad de central de proceso (CPU = Central Process Unit) al conjunto formado por la UC y la ALU, donde reside la ‘inteligencia’ de la máquina, es decir, la capacidad de procesamiento de la información. La memoria es el lugar donde se almacenan los datos y las instrucciones de forma permanente o transitoria. Un vistazo al esquema de la Figura 1.3., justifica que llamemos periféricos al conjunto de unidades de E/S y de memoria masiva ó auxiliar.
Fig. 1.3 Esquema de una máquina de von Neumann Veamos ahora con más detalle las unidades funcionales: UNIDAD DE ENTRADA (E). Son los dispositivos por donde se introducen los datos e instrucciones. En estas unidades se transforman las informaciones de entrada, en señales binarias de naturaleza eléctrica. Son unidades de entrada: el
CONCEPTO DE COMPUTADOR
11
teclado, un terminal de ventas, un digitalizador, una lectora de tarjetas de crédito, etc. UNIDAD DE SALIDA (S). Son los dispositivos por donde se obtienen los resultados de los programas ejecutados en el computador. La mayor parte de estas unidades transforman las señales eléctricas binarias en caracteres, escritos o visualizados, inteligibles por un humano o por otra máquina. Son dispositivos de salida: un monitor, una impresora, un registrador gráfico, el visor de un casco de realidad virtual, etc. LA UNIDAD DE PROCESAMIENTO CENTRAL (CPU). La CPU se encarga de manipular los datos, gracias a su capacidad para ejecutar una serie de tareas básicas que se corresponde con las operaciones incluidas dentro de sus circuitos, contenidos, normalmente, en un solo chip llamado microprocesador Estos están constituidos, al menos, por dos unidades básicas, la UC y la ALU. LA UNIDAD DE CONTROL. Es la encargada de administrar y coordinar los recursos y actividades del computador. Se puede pensar en la UC, como si fuera un guardia de tráfico, dirigiendo el flujo de datos dentro de la máquina, esto es, un coordinador que controla la ejecución “tirando de la cuerda adecuada, en el momento adecuado”. Por ello la UC tiene misiones relacionadas con: identificar instrucciones, supervisar su ejecución (enviando bits a las líneas de señal adecuadas en los momentos adecuados), y detectar señales eléctricas, procedentes del resto de unidades. Durante la ejecución de un programa la UC de forma sucesiva, debe seguir el siguiente ciclo: a) Captar de memoria una a una las instrucciones. b) Identificar las unidades involucradas en las operaciones asociadas a la ejecución de la misma. c) Generar, de acuerdo con el código de operación y con las señales de estado, las correspondiente señales de control. Más específicamente, las funciones de la UC son: 1. 2. 3. 4 5.
Interpretar el código y generar las señales de control que lo ejecutan. Controlar la secuencia en que se ejecutan las operaciones. Controlar el acceso a la memoria principal Enviar y recibir señales de control relacionadas con las operaciones que ejecuta la ALU Regular las temporizaciones de las unidades de E/S
UNIDAD ARITMÉTICO-LÓGICA. Esta unidad contiene los circuitos electrónicos con los que se llevan a cabo las operaciones básicas de tipo aritmético (suma, resta, multiplicación, división, etc.), y de tipo lógico (comparar dos números, hacer operaciones del álgebra de Boole, etc). Este aparentemente limitado conjunto de circuitos es más que suficiente, ya que todas las operaciones se pueden llevar a cabo en términos de un gran número de pequeños pasos, involucrando cada uno de ellos, uno o dos circuitos.
12
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
La ALU aparte de los circuitos operativos, contiene un grupo de registros, es decir, de pequeñas memorias construidas directamente en la CPU, que se usan para guardar los datos que van a ser procesados por la instrucción actual. Por ejemplo, la UC puede cargar dos números de memoria en los registros de la ALU, para luego ordenarle, que los divida (una operación aritmética) o que verifique si son iguales (una operación lógica), dejando el resultado correspondiente en otro registro de la unidad. MEMORIA La CPU contiene la lógica y los circuitos para que pueda funcionar la computadora, sin embargo carece de espacio para guardar programas y datos, esta es precisamente la misión de la unidad de MEMORIA: almacenar de forma eficiente tanto datos como programas. Notemos que, aunque la CPU contiene registros para datos e instrucciones, éstas son áreas de memoria muy pequeñas, que sólo pueden guardar unos cuantos bits a la vez; cuando hay necesidad de grandes cantidades de espacio de almacenamiento se hace necesaria la memoria externa a la CPU. Existen dos tipos básicos de memoria, diferenciadas tanto por sus funciones como por su velocidad: la principal y la secundaria. En ambas, la información se codifica en un alfabeto binario formado por bits, generalmente 8, llamado octeto o byte, que es la unidad utilizada para medir la capacidad de almacenamiento de una memoria. Como el byte es relativamente pequeño, es usual utilizar múltiplos: 10
3
1 Kbyte (o KB) = 2 bytes = 1024 bytes ### 10 bytes 10 20 1 Megabyte (o MB) = 2 Kbytes = 2 bytes = 1048 576 bytes ### 106 bytes 10 30 1 Gigabyte (o GB) = 2 Mbyte = 2 bytes = 1 073 741 824 bytes ### 109 bytes 10 40 12 1 Terabyte (o TB) = 2 Gbyte = 2 bytes ### 10 bytes Memoria principal (central o interna). Su función es la de almacenar los datos y el programa que va ejecutarse en cada momento, por ello es la más rápida y tiene optimizada sus transferencias, con la CPU Físicamente la memoria consiste en una serie de chips conectados con el microprocesador de la forma más integrada posible. La memoria está dividida en celdas o posiciones denominadas también palabras de memoria (Ver Figura 1.4.), estas celdas tienen como longitud, un número determinado de bits, normalmente coincidiendo con un múltiplo del byte: 8, 16, 32, 64, etc.
CONCEPTO DE COMPUTADOR
Fig. 1.4.
13
Disposición de las celdas o posiciones de una memoria
Existen distintos tipos de memorias y más de una manera de clasificarlas. Una manera de hacerlo es por la permanencia de la información en ellas. Algunos chips de memoria siempre conservan los datos que tienen aun cuando la computadora esté apagada; esta memoria se llama no volátil. Otros chips, que forman la mayor parte de la memoria, sí pierden su contenido cuando la computadora se apaga, la memoria de éstos es volátil. Una segunda clasificación de la memoria es por la manera en que puede usarse. Algunos chips de memoria siempre guardan los mismos datos, esto es, además de ser no volátiles sus datos no pueden ser cambiados. De hecho, cuando los datos se guardan en este tipo de memoria se llama “grabación permanente de los datos”. ya que sólo pueden leerse y usarse, nunca cambiarse. Este tipo de memoria se llama memoria de sólo lectura (Read Only Memory, ROM). Una de las razones por las que una computadora necesita ROM es para disponer de instrucciones en el momento del encendido, ya que sin datos grabados de forma permanente, no se podría producir todas las tareas que constituyen el proceso de arranque. La memoria cuyo contenido puede cambiarse se llama memoria de acceso aleatorio (Random-Access Memory, RAM). El propósito de la memoria RAM es guardar los distintos programas y datos, que vamos a utilizar en cada momento. Utilizamos el término acceso aleatorio porque la CPU accede a su memoria, utilizando una dirección de memoria, que es un número que indica un lugar en el chip de memoria, (de la misma forma que si fuera un número de apartado postal, que indica en qué buzón deberá ponerse el correo). De esta manera, la computadora no tiene que reconocer en toda la memoria para encontrar los datos que necesita; puede buscar la dirección e ir directamente a ella. En ambos tipos de memoria, la lectura es no destructiva ya que el contenido de una posición de memoria no se altera por muchas veces que se lea. Sin embargo, la escritura en una memoria RAM (recordemos que la memoria ROM no permite la
14
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
escritura) es destructiva, es decir, cuando se escribe algo en una posición de memoria automáticamente se destruye el contenido anterior. Memoria auxiliar masiva, (secundaria o externa). La memoria principal, aunque es muy rápida, no tiene gran capacidad de almacenamiento. Para guardar grandes cantidades de información, se utilizan otros tipos de memoria, tales como discos y cintas magnéticas, que siendo más lentas en la transferencia de datos, tienen mucha más capacidad que la memoria principal (del orden de mil veces más lentas pero más capaces). Frecuentemente los datos y programas se graban (introduciéndolos por las unidades de entrada) en la memoria masiva, de forma que si se ejecuta varias veces el programa o se utilizan repetidamente los datos, no es necesario introducirlos de nuevo por el dispositivo de entrada, sino que basta leerlos desde el disco o la cinta. Las memorias masivas han heredado el concepto de fichero de los sistemas manuales de tratamiento de la información, de forma que mucha de su terminología deriva de la tradicional. Así hablaremos de ficheros o archivos como una colección ordenada de datos relacionados entre sí. Existen distintos tipos de archivos, algunos de ellos tienen como unidad elemental el registro, que se correspondería con una ficha en un fichero manual. Los registros están formados por campos, que constituyen unidades de información (la forma más común de identificar un registro es eligiendo un campo dentro del mismo que llamaremos clave). En la Figura 1.5. puede verse un ejemplo de este tipo de fichero.
Fig. 1.5.
Elementos de un fichero
Jerarquía de memoria Una vez revisadas, las unidades funcionales, hay que hacer notar que la mayor parte del tratamieto de la información se lleva a cabo en la ALU, por lo que resulta importante aumentar la eficacia de su trabajo. Esta eficiencia depende de sus registros, que como sabemos ,son pequeñas memorias que se utilizan para el almacenamiento provisional de datos en el momento de ser objeto de procesamiento. Un registro de datos debe ser lo suficientemente grande como para almacenar los datos que puede manejar la ALU, es decir, si el dato estándar tiene una longitud de m bits, el registro tiene que ser capaz de almacenar los m bits.
CONCEPTO DE COMPUTADOR
15
Notemos que los datos transitan por los registros constantemente: cuando van a introducirse en la memoria, al acabar de ser extraídos de ella y cuando se obtienen resultados intermedios durante operaciones de la ALU. En cuanto una palabra, contenida en la memoria principal, se transfiere al registro adecuado, es cuando actúa como instrucción o como dato de un programa. Así el programa, que está en la memoria principal, al ejecutarse va captando, bien las diferentes instrucciones bien los datos sobre los cuales se va a actuar, para situarlos en los registros adecuados, consiguiendo con ello facilitar la implementación de las operaciones de la máquina y aumentar su eficacia. r e g is tr o s CPU
V e lo c id a d
T am año
C o s te
m á s r á p id a
m ás pequeñ a
m á s c o s to s a
m á s le n ta
m ás gran de
m á s a s e q u ib le
m e m o r ia ca ch é m e m o r ia p r in c ip a l m e m o r ia s e c u n d a r ia ( a u x ilia r o m a s iv a )
Fig. 1.6.
Estructura de una jerarquía de memoria
El conjunto de los tipos de memorias se organizan por niveles como una jerarquía de memorias (ver Figura 1.6). Los registros se usan para manejar los datos utilizados en la operación que se esta ejecutando, la memoria principal se utiliza para soportar los datos que serán utilizados en el futuro próximo y la memoria secundaria se usa para aquellos datos que normalmente no van a ser manipulados en el corto plazo. En muchas máquinas existe un nivel adicional, llamado memoria cache, consistente en una memoria de gran velocidad con tiempos de respuestas semejantes a los registros, normalmente situada en la propia CPU. La máquina trata de utilizar esta área de memoria como copia de la porción de memoria que le interesa en cada momento. En este caso la transferencia de datos en lugar de hacerse entre memoria y registros se hace entre memoria caché y registros, y resultando mucho más rápida. BUSES Un requisito para el buen funcionamiento de la máquina, es la conexión, para el intercambio de información, entre unas unidades y otras. Este intercambio entre unidades se tiene que lleva a cabo en paralelo (varios bits de información al
16
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
mismo tiempo) a través de una canal de información o bus que contiene un determinado número de hilos conductores. La palabra es la unidad de transferencia de información, al estar ligada a la estructura interna de la computadora, su longitud, en bits, indica tanto el tamaño de los datos con que opera la ALU, como la de los datos transferidos entre la memoria y la CPU. Por tanto, esta longitud acaba determinando las características físicas de los buses4. Así, por ejemplo, se se trabaja con palabras de memoria de 16 bits la información desde una unidad a otra debe transmitirse por un bus de 16 hilos conductores. A través del bus, la CPU puede extraer (leer) datos de la memoria principal, proporcionando tanto la dirección de la posición de memoria como su contenido y viceversa en el proceso de escritura en memoria. Al bus empleado para intercambiar información entre los distintos subsistemas se le llama bus de datos, mientras que si un bus es específicamente utilizado para indicar posiciones de memoria se conoce como bus de direcciones.
3060" GN"EQPEGRVQ"FG"RTQITCOC"CNOCEGPCFQ La idea de von Neumann de que tanto los datos como el programa residan en la memoria principal, supone que una máquina esta diseñada para reconocer ciertos patrones de bits que representan ciertas instrucciones, que ahora vamos a definir con mayor precisión: llamaremos instrucción a un conjunto de símbolos que representan una orden de operación, que suele realizarse con o sobre datos y en consecuencia programa es un conjunto ordenado de instrucciones, que indican al ordenador los procedimientos que se desean. 30603" VKRQU"FG"KPUVTWEEKQPGU Puesto que los circuitos de la UC reconocen y ejecutan un determinado repertorio de instrucciones, propias de su CPU, cuando se ejecuta un programa, la computadora interpreta las ordenes contenidas en las instrucciones y las ejecuta sucesivamente. Un programa se escribe, utilizando una sucesión de instrucciones y salvo indicación contraria, su ejecución coincide con el orden en que las instrucciones han sido escritas (Figura 1.7): Nótese que las longitudes de palabra de la CPU y de la memoria pueden no coincidir, pues la longitud de la palabra de CPU es el número de bits máximo de los datos con los que opera la ALU, mientras que la longitud de la palabra de memoria es el número de bits que conforman cada posición de memoria (número de bits que se pueden leer o escribir simultáneamente); este número suele coincidir con el número de bits (integrantes de datos o instrucciones) que se transmiten simultáneamente entre las distintas unidades en un instante dado. Y longitud de palabra debe ser tenida en cuenta a la hora de interconectar las distintas unidades.
4
CONCEPTO DE COMPUTADOR
17
instrucción 1 instrucción 2 . . . instrucción n Fig. 1.7.
Orden secuencial de las instrucciones de un Programa
Sin embargo, es básico, para que la flexibilidad del programa sea tal, que la UC pueda interrumpir el orden normal, mediante instrucciones de bifurcación, que permiten que el control del programa pase a una instrucción que no sea necesariamente la inmediata. Con ello el desarrollo líneal de un programa se interrumpe.(Figura 1.8) instrucción 1 . instrucción x instrucción x+1 . instrucción y . instrucción n Fig. 1.8.
Ruptura del orden secuencial de las instrucciones de un programa con una bifurcación
Una instrucción de bifurcación debe indicar el punto del programa hacia a donde se bifurca. Desde el punto de vista del ‘sentido’ del salto, una bifurcación puede ser hacia delante (en general puede considerarse que el computador deja de ejecutar determinadas instrucciones), o hacia atrás (con lo cual pasa a ejecutar instrucciones que pueden haberse ejecutado previamente). Desde el punto de vista de la causa que la produce, la bifurcación puede ser: incondicional, (el salto se da siempre que el flujo del programa pase por la instrucción), ó condicional, caso que la bifurcación dependa del cumplimiento de una determinada condición; si esta se cumple se produce el salto, si no, el programa continúa en la instrucción siguiente a la de bifurcación. Las instrucciones ejecutables por cualquier CPU, se pueden clasificar en los siguientes grupos:
18
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
-
-
Instrucciones de transferencias de datos como por ejemplo: de entrada o lectura (llevar un dato de una unidad de entrada a la memoria o a la ALU), de salida o escritura (llevar un dato de la memoria o de la ALU a una unidad de salida), transferencia de un dato de la memoria a la ALU o viceversa, etc. Instrucciones de tratamiento como por ejemplo, sumar dos datos, comparar dos datos para comprobar si son iguales o uno mayor que otro; en particular incluye las operaciones aritmético-lógicas que ejecuta la ALU. Instrucciones de bifurcación y saltos. Que permiten alterar el orden secuencial de ejecución. Otras instrucciones. Tal como detener el funcionamiento de la computadora a la espera de una acción del operador, cambiar el modo de funcionamiento de la CPU, etc.
Cada modelo de CPU, tiene su propio conjunto de instrucciones, sin embargo, los fabricantes tienden a agrupar las CPU en “familias”, con conjuntos de instrucciones semejantes. Cuando se desarrolla una nueva CPU, su conjunto de instrucciones, por lo general, contiene las mismas instrucciones que tenia su predecesor, además de algunas nuevas. Esto permite que un programa, desarrollado para una cierta CPU, pueda utilizarse con nuevas CPU de la misma familia (esta estrategia de fabricación es conocida como escalabilidad). 30604" NGPIWCLG"OıSWKPC"["NGPIWCLG"GPUCODNCFQT La colección de instrucciones ejecutables para cada CPU, junto con su sistema de codificación, se llama lenguaje máquina. Este lenguaje es el primer lenguaje de programación que se utilizó y el único que entiende directamente el computador, aunque, al estar ligado a la circuitería, queda muy alejado de la forma en que solemos expresar los problemas. Sus principales características son: • Las instrucciones son cadenas de ceros y unos. • Los datos se utilizan por medio de las direcciones de memoria donde están almacenados y no admite identificadores, sino que el programador debe hacer una asignación de direcciones de memoria para todas las variables y constantes del programa. • Las instrucciones realizan operaciones muy simples, dependientes de las posibilidades de la circuitería del procesador. Los primeros programadores se comunicaban con los computadores interconexionando cables, más adelante lo hicieron a través de números binarios, pero éstas tareas eran tan plúmbeas, que pronto inventaron notaciones simbólicas
CONCEPTO DE COMPUTADOR
19
que facilitasen este trabajo. Sin embargo, estas notaciones tenían que ser traducidas manualmente a binario, por lo que el siguiente paso fue conseguir programas que tradujeran la versión simbólica de las instrucciones a la versión binaria. Así el programador escribe: fAR A , M(16)
y el programa traductor convierte esta notación en: 000000010000
indicándole al computador que cargue en el registro A de la ALU, el contenido de la posición de memoria 16. Este programa traductor se llama ensamblador y el lenguaje simbólico utilizado se conoce como lenguaje ensamblador. Este lenguaje requiere que el programador escriba una línea por cada instrucción que seguirá la máquina. Observemos que ambos lenguajes (ensamblador y máquina) fuerzan al programador a pensar directamente en las funcionalidades primarias de la propia máquina. Siendo importante la aparición de los lenguajes ensambladores, la tarea de programación, con ellos, seguía siendo ardua (aunque para ciertas aplicaciones aun hoy, hay que recurrir a estos lenguajes). De hecho, los llamados ordenadores de primera generación se desarrollaron, a pesar de su buen funcionamiento electrónico, en medio de un cierto escepticismo, pues, al no estar conceptualizadas abstracciones más próximas al modo de razonar de las personas, se pensaba, no sin razón, que ni siquiera el propio creador de un programa alcanzaría su total comprensión, una vez escrito en términos binarios o en lenguaje ensamblador. Afortunadamente; los primeros programadores superaron tan negras perspectivas e idearon los lenguajes de programación de alto nivel (Fortran, Basic, Pascal, Lisp, Prolog, C, C++, etc.) mucho más amistosos, que se tratarán en un próximo apartado. 30605" GLGEWEKłP"FG"WP"RTQITCOC Llegados aquí, vamos a profundizar un poco más en el funcionamiento de la máquina de von Neumann, analizando la forma como se ejecuta un programa ya almacenadoen memoria. Supongamos que está almacenado a partir de la posición i de memoria y que la ejecución indica “pasar el control a la posición i de memoria”. A partir de ese momento, la UC repite sucesivamente las siguientes fases: a) Lleva de la memoria a la UC la instrucción que está en la posición i, y cambiar el valor de i por i+1. b) Interpreta el código de operación de la instrucción y, según sea éste y las señales de estado, envía señales de control a las unidades y circuitos que deben
20
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
intervenir para ejecutar la instrucción. Y efectua (microoperaciones) que ésta implica. Volver a la fase a)
las
operaciones
La fase a) es igual en todas las instrucciones, siendo la fase de captación de instrucción, y la b), es específica de cada instrucción, siendo la fase de ejecución de la misma. En el caso de que la ejecución de la instrucción implique saltar a otra, a la posición m por ejemplo, (alterándose por tanto el orden secuencial), la UC hace, en la fase de ejecución de la instrucción de salto, que se cambie i por m, de forma que en la siguiente fase de captación se ejecuta la instrucción que está en m, por ser éste el valor actual de i. A modo de ejemplo, vamos analizar un caso sencillo. Supóngase que se dispone de un computador, con un teclado como unidad de entrada, una impresora como unidad de salida (Ver Figura 1.9), y una CPU cuyo lenguaje máquina contiene, entre otras, las instrucciones siguientes (validas para cualquier CPU): - ENTrada de información que se transfiere a la posición m de memoria, que se indica en el campo de dirección de la propia instrucción. Abreviadamente: ENT M(m),E. - SALida de información procedente de la posición m de memoria.: SAL S,M(m). - MEMorizar en la posición m de memoria el contenido de la ALU. Abreviadamente: MEM M(m),A. - CARgar en el registro A de la ALU, el contenido de la posición m de memoria.: CAR A,M(m). - SUMar el contenido del registro A, con el contenido de la posición m de memoria, almacenándose el resultado en A.: SUM A,M(m). - FIN indica a la UC que debe acabar el procesamiento de ese programa. Se desea escribir un programa que sume dos números. Analizando el repertorio de instrucciones de que se dispone, hay que ejecutar los siguientes pasos: 1) Leer (“dar entrada”) los dos números llevándolos a la memoria, 2) Llevar uno de los sumandos a la ALU, y sumarlo con el otro , 3) El resultado almacenado en la ALU, llevarlo a la memoria y 4) Escribirlo a través de la unidad de salida. (Ver diagrama de la Figura 1.9.)
CONCEPTO DE COMPUTADOR
21
Fig. 1.9. Diagrama de las operaciones a realizar para el programa de sumar Además, el programador en lenguaje máquina, ha de determinar las posiciones de memoria que va a utilizar. En el ejemplo vamos a seleccionar: Primer sumando, Segundo sumando, Suma,
en posición 16 en posición 17 en posición 18
El programa es el siguiente: (1) Leer el primer dato y llevarlo a la posición 16. ENT M(16),E 00100 001 0000 (2) Leer el segundo dato y llevarlo a la posición 17. ENT M(17),E 00100 001 0001 (3) Llevar a la ALU el primer dato (que está en m=16). CAR A,M(16) 00000 001 0000 (4) Sumar el contenido de la ALU con el segundo sumando (que está en m=17). SUM A,M(17) 11000 001 0001 (5) Llevar el resultado de la ALU a la posición m=18. MEM M(18),A 00010 001 0010 (6) Escribir el resultado que está en m=18. SAL S,M(18) 00110 001 0010
22
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
(7) FIN Después de describir cada instrucción hemos escrito su abreviación y su correspondiente instrucción máquina, cuyo procesador suponemos esta en condiciones de manejar instrucciones de 12 bits: los cinco primeros son el código de operación y los siete restantes constituyen el campo de dirección ó la dirección de memoria que interviene en la instrucción.
Fig. 1.10 Contenido de la memoria después de ejecutado el programa en la computadora del ejemplo (Por simplicidad , se representan las instrucciones abreviadamente y los datos en decimal, aunque realmente son ceros y unos). Supóngase que el programa se carga en memoria a partir de la dirección i=8 , y se indica a la UC que pase su control a la instrucción que está en i=8, con el objetivo de sumar 50 + 26. Los pasos de la ejecución pueden seguirse fácilmente con ayuda de la Figura 1.10: (1.a) La UC capta la instrucción que está en i = 8 y cambia a i = 8 + 1 = 9. (1.b) La UC interpreta el código de la instrucción, 00100. y genera las señales de control para que el dispositivo de entrada lea un dato y sea escrito en la posición m = 001 0000 o 16 en decimal. Si el dato tecleado es 50, al final de la ejecución de la instrucción este valor en binario (0000 0011 0010) queda almacenado en la posición 16. (2.a) La UC capta la instrucción que está en i = 9 y cambia a i = 9 + 1 = 10 (2.b) La UC interpreta el código de la instrucción captada, 00100. y genera las mismas señales de control que en la instrucción anterior. En este caso m =
CONCEPTO DE COMPUTADOR
23
001 0001 (17 en decimal) y el segundo dato, 26 queda grabado en binario (0000 0001 1010) en la posición 17. (3.a) La UC capta la instrucción que está en i = 10 y cambia a i = 10 + 1 = 11. (3.b) La UC interpreta el código de operación de la instrucción 00000, generando las señales de control necesarias para que se lea el contenido de la posición m = 001 0000 (16 en decimal) y sea llevado a un registro de la ALU. Ningún contenido de la memoria cambia. (4.a) La UC capta la instrucción que está en i = 11 y cambia a i = 11 +1 = 12. (4.b) La UC interpreta el código de operación de la instrucción captada, en este caso 11000 y genera las señales de control para sumar el contenido de la ALU (0000 0011 0010, en decimal 50) con el contenido de la posición m = 001 0001 de la memoria (que es 0000 0001 1010, en decimal 26). El resultado de la suma (0000 0011 0010 + 0000 0001 1010 = 0000 0100 1100; en decimal 76), queda en el mismo registro de la ALU. (5.a) La UC capta la instrucción que está en i = 12 y cambia i a i = 12 +1 = 13. (5.b) El código de operación, en este caso 00010, es interpretado por la UC, dando ésta las señales de control para que el contenido de la ALU (0000 0100 1100) se grabe en la posición m = 0000 0001 0010 (18, en decimal) de la memoria. El resultado de la suma queda, en la posición 18 (6.a) La UC capta la instrucción que está en i = 13. y cambia a i = 13 + 1 = 14. (6.b) El código de operación, ahora 00110, se interpreta por la UC y ésta genera las señales de control para leer de memoria, el contenido de la posición m = 0000 0001 0010 (18 en decimal) y llevarlo a la unidad de salida. Allí el valor transferido, es convertido de forma que en la impresora se disparan los elementos necesarios para escribir 76. (7)
La UC capta la instrucción de FIN y acaba el procesamiento (La Figura 1.10 muestra el contenido de la memoria, al finalizar la ejecución).
Hay que insistir en que este problema, escrito en un lenguaje de alto nivel, habría resultado mucho más sencillo, aunque una vez traducido el ordenador trabajaría con un código muy parecido al que acabamos de usar.
3070" EQPEGRVQ"CEVWCN"FGN"EQORWVCFQT 30703" FGHKPKEKłP"CEVWCN
24
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Aunque no está en la óptica de este texto, el profundizar en las nuevas arquitecturas que presentan los computadores en la actualidad, si parece necesario poner en evidencia que el concepto de máquina de von Neumann tal como lo hemos tratado hasta ahora, (como un dispositivo de procesamiento individual controlado por un único programa), aunque adecuado, debe ser objeto de una definición más amplia, para llegar al actual concepto de computador. A modo de reflexión, hay que hacer notar que muchos computadores, contienen más de un procesador, requieren para obtener varios programas trabajando de forma aparentemente simultánea y que una computadora moderna puede tener gran cantidad (cientos) de unidades de entrada o salida, próximas o remotas.Vamos a dar una definición operativa de computador que amplie la máquina de von Neumann: Un computador es una colección de recursos, que incluyen: dispositivos de proceso electrónico digital, programas almacenados y conjuntos de datos, que, bajo el control de programas, produce salidas, almacena, recupera y procesa datos, pudiendo también transmitirlos y recibirlos hacia y desde otros computadores. En todo computador hay que distinguir dos soportes: El soporte físico (hardware en inglés) que es la máquina en sí: el conjunto de circuitos electrónicos, cable, armario, dispositivos electrónicos, etc. El soporte lógico (o software, neologismo inglés que contrapone soft (blando) con hard (duro), - logicielle en francés) que es el conjunto de programas que dirigen el funcionamiento del computador: sistema operativo, programa de usuario, etc. Notemos que en la práctica la distinción física/lógica es más difusa de lo que aparentemente parece, así en algunos computadores las multiplicaciones se hacen directamente por hardware, mientras que en otros se hace en forma de sumas múltiples, controladas por software. Además, parte del software está almacenado permanentemente en forma de ROM, que se conoce como firmware y que es un ente intermedio entre el hardware y el software. 30704" RCTıOGVTQU"DıUKEQU"FG"NC"OıSWKPC Existen varias características, relacionadas con el funcionamiento de las distintas unidades funcionales.que determinan el tipo y velocidad del ordenador central. En este sentido, hay que saber que la UC contiene un reloj o generador de pulsos, que sincroniza todas las operaciones elementales de la computadora. El período de esta señal se denomina tiempo de ciclo, cuyo orden de magnitud es de nanosegundos. La frecuencia del reloj (que suele darse en millones de
CONCEPTO DE COMPUTADOR
25
ciclos/segundo o Mhz) es un parámetro que en parte determina la velocidad de funcionamiento de la computadora. La longitud de palabra determina, la precisión de los cálculos, la característica de los buses y la variedad de instrucciones respecto a esta velocidad, sabemos que cada CPU tiene un conjunto de instrucciones, determinado. Hoy en día, hay dos grandes familias: Procesadores con un amplio conjunto de instrucciones (CISC = Complex Instruction Set Computer) y Procesadores con un conjunto de instrucciones reducido, pero optimizadas para que éstas se ejecuten más rápido (RISC = Reduced Instruction Set Computer). Es frecuente, en computadores convencionales, que la gestión de las operaciones con números reales (que arrastran un gran numero de decimales y por tanto requieren bastantes ciclos de ejecución en la ALU) se realicen por hardware con circuitos integrados auxiliares conocidos como coprocesador matemático. El que un computador disponga o no de coprocesador matemático varía enormemente su velocidad en aplicaciones que requieran gran número de operaciones con números reales. La tendencia actual es que el coprocesador matemático se integre dentro de la propia CPU y no se hable explícitamente del mismo (por ejemplo el 486 y el Pentium). Igualmente el tipo de bus (o buses) que conecta los distintos subsistemas del computador condiciona su potencia, especialmente en la adecuación del número de líneas del bus con el tamaño de palabra. Otros de los parámetros significativos son las capacidades de memoria principal y secundaria. La capacidad de la memoria principal, indica el tamaño de los programas (instrucciones+datos) que el computador puede procesar simultáneamente. La memoria secundaria sirve tanto como ‘ampliación’ de la memoria principal (dentro de la concepción de jerarquía de memorias ya indicada), como almacenamiento permanente de programas y datos. Al considerar todos estos parámetros, es importante recordar, que para acceder a instrucciones o datos, es imprescindible el poder direccionar todas y cada una de las palabras de la memoria principal. En una primera aproximación, esto se hace asignando a cada posición un número en base 2; así con n bits podemos direccionar 2n palabras. Sin embargo, hay que analizar lo que supone esta función de direccionamiento, a medida que la memoria crece; así para direccionar 64 Kpalabras necesitamos 16 bits y para 1 Mpalabra son necesarios 20 bits, con lo que al crecer la memoria se hace imprescindible acudir a técnicas de direccionamiento, para poder acceder a un número grande de palabras con un número relativamente pequeño de bits. Junto con la capacidad de la memoria secundaria. de los distintos periféricos, el tiempo de acceso (para lectura y para escritura en el periférico) es un parámetro importante para determinar la potencia de un computador. Además de cuantos y cuáles sean los periféricos que puede incorporar un determinado computador (p.e.
26
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
los computadores personales disponen de slots o ranuras sobre el bus para ‘clavar’ nuevos componentes; el número y tipo de los slots establece las posibilidades de ampliación de periféricos).
3080" FGN"EQORWVCFQT"C"NC"RTQITCOCEKłP Una vez visto el funcionamiento del computador, hay dos consecuencias a sacar sobre la naturaleza de esta máquina: 1) Desde el punto de vista de los datos, éstos se presentan en forma de estructuras binarias de tamaño predefinido. El computador sólo trabaja con estructuras aparentemente muy limitadas, que son las que incorporan los registros y las palabras de la memoria en las que se almacenan valores binarios. 2) Desde el punto de vista algorítmico, el número de operaciones que puede llevar a cabo viene prefijado y limitado por un conjunto de instrucciones dado,materializables a través de circuitos electrónicos. La pregunta que surge es inmediata, ¿cómo puede una sola máquina representar datos tan complejos y diferentes como números, textos,vectores y otras estructuras más elaboradas, con solo la utilización de bits y al tiempo, resolver un espectro tan amplio de problemas con la única utilización del repertorio del lenguaje máquina?. En otras palabras, ¿cómo supera el computador éstas limitaciones, tanto en materia de estructura de datos como de algoritmos? La respuesta la indicó el propio von Neumann: El computador es una máquina de propósito general, cuya naturaleza queda transformada por el programa que se le proporciona, de forma que en un momento dado, un caudal de información constituye un conjunto de datos procesados por el programa, y a continuación otro caudal de información se interpreta como instrucciones ejecutables. 30803" NQU"FCVQU Los datos son los objetos sobre los que opera un ordenador; sabemos que éste tiene capacidad de procesar datos de tres tipos básicos: numéricos, lógicos y caracteres alfanuméricos (no deja de ser sorprendente que, estando el cálculo científico en el origen del ordenador, sus actuales aplicaciones sean mucho más amplias que las derivadas del manejo de números). Los primeros representan las diferentes clases de números, enteros y reales, con distintas posibilidades de rango de magnitud y precisión. Los datos lógicos o booleanos son aquellos que pueden tomar valores ciertos o falsos y que nos servirán para tomar decisiones en función de que se cumplan o no determinadas condiciones. Los datos alfanuméricos son, a
CONCEPTO DE COMPUTADOR
27
grandes rasgos, los que puede producir un teclado habitual. Retengamos la idea, de que el ordenador está en condiciones de representar y diferenciar internamente estos tipos de datos simples, que a su vez pueden aparecer de forma aislada o agrupados en forma de una estructura. Como tendremos ocasión de ver, existen una importante variedad de datos estructurados, como por ejemplo: cadenas de caracteres. vectores y matrices (habitualmente de datos numéricos). registros y ficheros para el almacenamiento secundario. 30804" NGPIWCLGU"FG"RTQITCOCEKłP"FG"CNVQ"PKXGN Como ya anunciamos, ante los serios inconvenientes de los lenguajes máquina y ensamblador desde los inicios de los computadores actuales, se han desarrollado los llamados lenguajes de alto nivel, con algunas características muy apreciadas: están más cercanos a nuestra forma de resolver problemas, son relativamente independientes de la máquina, (una sola de sus instrucciones equivale a varias instrucciones de lenguaje máquina), facilitan enormemente el proceso de programación, acercándolo, en la medida de lo posible, a los lenguajes que los humanos utilizamos habitualmente, etc. Las sentencias o frases que se pueden construir a partir de la sintaxis de un lenguaje de alto nivel son de dos tipos: imperativas (o instrucciones) que indican acciones a tomar por el computador, o declarativas, que proporcionan información sobre determinadas circunstancias del programa. Así la sentencia: float Base
es declarativa, ya que no implica una acción visible dentro del contexto del programa, sino que indica al programa traductor, que la variable Base se va a utilizar como un dato numérico en coma flotante, de simple precisión para que lo tenga en cuenta, a efectos de representación interna en la memoria. Sin embargo, la sentencia: Area = Base * Altura
es una instrucción pues produce la ejecución de una operación, en este caso, multiplicar el valor de Base por el de Altura, y el valor del resultado asignárselo a la variable Area. Notemos que comparados con los lenguajes máquina, los de alto nivel permiten utilizar variables, símbolos y términos más parecidos a los usados por los humanos, en la resolución de problemas. Este proceso de abstracción nos permite pensar con independencia de las particularidades con las que trabaja la máquina, sin ello resultaría casi imposible crear programas de tamaño no trivial. El programador a la hora de resolver un problema, establece una jerarquía de abstracciones, hasta que el
28
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
computador puede encargarse de su traducción y posterior ejecución. Con ello tenemos una doble ventaja, por un lado que el programador piense en una determinada estructura de datos sin tener que preocuparse en cómo se organizan éstos realmente en memoria y por otro, que pueda utilizar operaciones, habituales para nosotros, que serán finalmente ejecutadas por el procesador. Por tanto los lenguajes de alto nivel deben permitir especificar en forma abstracta tanto estructuras de datos complejas como los distintos pasos que constituyen un algoritmo que implementa un programa. Esta idea constituirá el hilo conductor de este libro, obedeciendo a una de las claves de la Programación. PROGRAMA = ESTRUCTURA DE DATOS + ALGORITMOS Esto significa que todo proceso de programación, además de contar con un algoritmo que resuelva el problema planteado, ha de tener conceptualizada la forma como organizar los datos antes y después de ser procesados. De hecho, ambas cuestiones están interrelacionadas: una estructura de datos adecuada puede simplificar el diseño de un algoritmo, y un algoritmo menos adecuado que otro puede implicar manejar estructuras de datos más complejas. La evolución del arte y la tecnología de la programación es muy intensa y sigue en pleno desarrollo, en la actualidad. Veamos algunos hitos: programación clásica, representada por las primeras versiones de FORTRAN y BASIC, se caracteriza por el mero uso de una secuencia de ordenes y bifurcaciones; programación modular, añade un método de diseño que permite resolver un problema, mediante su descomposición en problemas más simples o módulos; programación estructurada (representada por PASCAL y C entre otros) supone. además un conjunto de técnicas que permiten desarrollar programa fáciles de escribir, verificar, leer y mantener. La naturaleza de todos ellos es procedural, es decir, está basada en procedimientos que hacen algo concreto, como escribir un mensaje en pantalla, obtener datos desde el teclado o ejecutar un proceso algorítmico, de forma que un programa puede incorporar fácilmente cientos de procedimientos individuales. En otros capítulos, nos extenderemos sobre esta variedad de técnicas de programación, limitémonos ahora, a añadir la llamada programación orientada a objetos, cuyos programas se construyen ensamblando partes denominadas objetos, que es un tipo especial de dato, en un intento de emular el mundo real, que se compone de objetos que interaccionan entre sí. La programación orientada a objetos presenta ventajas importantes, pero sus conceptos no son fáciles y en ningún caso accesibles, sin un buen dominio de la programación estructurada. Ante la actual babel de lenguajes de programación, el objetivo de este libro es ser compatible con la mayor parte de los procedurales, sin tomar partido por ninguno en particular, (es en otros textos y cursos donde se debe aprender a dominar algún lenguaje de programación específico). No obstante, como ejemplos significativos,
CONCEPTO DE COMPUTADOR
29
(desde la doble óptica de los años noventa y de unos estudios de ciencias e ingeniería), vamos a tratar de rastrear, en la medida de lo posible, cuatro de los más populares lenguajes, (alguno de los cuales debe de ser aprendido en paralelo con este curso), que por orden cronológico son: FORTRAN. Pasa por ser el primer lenguaje de alto nivel que contó con un compilador eficiente. Su propio nombre, acronismo de “FORmula TRANslation”, señala que fue diseñado con el objetivo de ser utilizado para calculo científico. Desarrollado en 1957 por un pionero de la informática John Backus y un equipo de científicos de IBM, su primera estandarización data de 1966 y desde entonces han aparecido dos mejoras sustantivas en 1977 y 1990. Aunque su panorámica se reduce casi exclusivamente al calculo científico y técnico, es tal la cantidad de software escrito en este veterano lenguaje, que su uso en los ambientes de calculo intensivo esta muy extendido y es difícil hacer un pronóstico acerca de su sustitución por otros lenguajes de alto nivel más sofisticados. BASIC. Fue desarrollado en 1964 por John Kemeny y Thomas Kurtz en el Darmouth College, comenzó siendo una herramienta para enseñar programación como indica su acronismo (Beginners All-purpose Symbolic Instruction Code). Su simplicidad le hizo especialmente popular, siendo el primer lenguaje de alto nivel que se utilizó con la aparición de los primeros ordenadores personales, incluso anteriores al primer PC de IBM . Existen versiones del Basic especialmente potentes y accesibles como Turbo-Basic, Quick-Basic, GW-Basic y Visual-Basic (que ya incorpora alguna de las características y métodos orientados a objetos). A pesar de su popularidad, el BASIC no ha cuajado completamente a nivel científico y profesional, ya que no tiene un gran repertorio de herramientas y sus compiladores no producen archivos ejecutables que sean tan compactos, veloces y eficientes como los producidos por otros lenguajes. Sin embargo, su simplicidad, puede llegar a satisfacer las necesidades de muchos usuarios. PASCAL. Desarrollado por el suizo Niklaus Wirth en 1971, en honor del sabio francés del siglo XVII del mismo nombre, es el primer lenguaje específicamente pensado para la programación estructurada. Sus puntos más interesantes son sus impecables medios para revisar el tipo de datos y para controlar el flujo de ejecución del programa. Son muchos los libros y cursos que lo utilizan como lenguaje de primera elección para aprender programación estructurada. Como no podía ser menos, existen muchas extensiones de PASCAL y en particular las últimas, orientadas a objetos. Los puntos negativos para los profesionales de la programación hay que buscarlos en su filosofía excesivamente académica, lo que conduce a hacerlo un poco tedioso. C y C++. El lenguaje C está considerado una especie de pura sangre por muchos programadores, ya que los programas escritos en C producen un código ejecutable
30
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
especialmente veloz y eficiente, pues con él la máquina puede hacer casi todo lo que su hardware le permite realizar. Fue desarrollado por Brian Kernighan y Dennis Ritchie a principios de la década de los 70 en los Laboratorios Bell; el segundo de sus autores tiene también la importante paternidad del sistema operativo UNIX. De hecho C y UNIX están íntimamente ligados ya que el código fuente C se puede trasladar de una máquina UNIX a otra. El costo de un lenguaje tan útil reside en que no es de los de más fácil aprendizaje, lo que puede llevar a cierto grado de desmoralización a los principiantes, que deben superar rápidamente este estado de ánimo. A principios de los 80, otro científico de los Laboratorios Bell, Bjarne Stroustrup, introdujo la orientación a objetos en C. El resultado fue un lenguaje poderoso y eficiente pero no sencillo de aprender, cosa que sólo se puede hacer una vez dominado el C. Hoy, a mitades de los noventa, el C++ aparece como un lenguaje candidato a ser utilizado en muchas aplicaciones informáticas. 30805" GNGOGPVQU"DıUKEQU"FG"WP"NGPIWCLG"FG"CNVQ"PKXGN Un programa consta de una sucesión de sentencias, escritas en lineas sucesivas.Veámos cuales son los elementos que están presentes en cualquier programa escrito en un lenguaje de programación de alto nivel. Palabras reservadas: Conjunto de colecciones de caracteres con significado especial, que el traductor del lenguaje interpreta según su significado. Estas palabras, permitirán formar sentencias, tanto declarativas como imperativas. Cada lenguaje tiene un conjunto de palabras reservadas. Veamos algunos ejemplos de palabras reservadas tomadas de cada lenguaje: FORTRAN
BASIC
PROGRAM, DATA COMPLEX, IF RCUECN
dim, step loop, select C
program, procedure begin, end
main, switch void, float
Constantes y Variables: Un programa contiene ciertos objetos o conjuntos de datos que no deben cambiar durante su ejecución, estos valores se llaman constantes, por contra una variable es un objeto o conjunto de datos cuyo valor puede cambiar durante el desarrollo del algoritmo o la ejecución del programa. Las variables tienen un nombre, conocido como identificador, que suele constar de varios caracteres alfanuméricos de los cuales el primero normalmente es una letra. Una variable por tanto se identifica por dos atributos: nombre y tipo Este último
CONCEPTO DE COMPUTADOR
31
define el conjunto de posibles valores que puede tomar la variable,a la vez que determina el tipo de dato que soporta su correspondiente representación interna. Expresiones: Combinaciones de constantes, variables, símbolos de operación, caracteres especiales, etc., análogas a las utilizadas en notación matemática. Cada expresión toma un valor que se determina tomando los valores de las variables y constantes implicadas y la ejecución de las operaciones indicadas. Una expresión consta de operandos y operadores. Según sea el tipo de objetos que manipulan, pueden ser: - Aritméticas. Donde las variables y constantes son numéricas y las operaciones son aritméticas, una vez ejecutadas, su valor numérico es: Ej.: x + (b+5) + z * z - Relacionales: Utiliza signos de comparación (, =, etc.) con valores de tipo numérico o carácter para producir un valor lógico (verdadero o falso), Ej.: (A-2) < (B-4) - Lógicas: Combina operadores lógicos (AND, OR, NOT) que operarán sobre valores lógicos, de acuerdo con sus tablas de verdad. Ej.: (AB) - De Carácteres: Que involucra cadenas de caracteres alfanuméricos. Ej.: Unión de dos cadenas. Etiquetas: Ciertas instrucciones de un programa utilizan etiquetas para referirse a una línea determinada de un programa, bien para realizar un salto a dicha línea, bien porque dicha línea contiene información adicional para una instrucción. La etiqueta puede tener un nombre (situado al principio de la línea que está etiquetando) o ser simplemente el número de la línea en cuestión. Comentarios: Con el único objetivo, de hacer más comprensible la lectura del programa por parte de un humano, esta prevista la inclusión de comentarios a la largo de su desarrollo, que hay que marcar debidamente, para que el ordenador las ignore. FORTRAN
BASIC
C comentario * comentario RCUECN
´ comentario REM comentario C
{comentario} (*comentario*)
/* comentario */ // comentario en algunos f
32
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Asignación: Es la acción por la cual le damos (asignamos) valores a una variable, la representaremos por el signo ←. Al escribir A←5 significamos que A toma el valor de 5 como real. La acción de asignar es destructiva, ya que el posible valor que tuviera la variable antes de la asignación se pierde y se reemplaza por el nuevo valor. Se debe pensar en la variable como en una posición de memoria, cuyo contenido puede variar mediante instrucciones de asignación (un símil es un marcador -de un campo de fútbol-, donde su contenido variará según el movimiento del partido; al poner un número en uno de los casilleros, desaparece el que hubiera anteriormente). Puesto que cada variable tiene un tipo determinado ,se entenderá que no se pueda (o no se deba) asignar valores a una variable de un tipo diferente del suyo y así se presentará un error si se trata de asignar valores de tipo carácter a una variable numérica o un valor numérico a una variable tipo carácter (¡ imaginemos que en nuestro marcador del campo de fútbol pusiéramos una letra A en el casillero del tanteo del equipo local !). El computador ejecuta la acción de asignar en dos pasos, en el primero de ellos, el valor de la expresión al lado derecho del operador se calcula, obteniendo un valor de un tipo específico. En el segundo paso este valor se almacena en la variable, cuyo nombre aparece a la izquierda. Veamos un ejemplo: ¿Cuál es el valor de las variables A, B y AUX antes y después de cada paso? 1. 2. 3. 4. 5. 6.
A ← 10 B ← 13 + 7 AUX ← A + 15 A←B B←A+4 AUX ← AUX + 1
Antes de la ejecución de las instrucciones, el valor de A, B, y AUX es indeterminado, es decir, el contenido de las posiciones de memoria correspondientes a ellas es desconocido. 1. A toma el valor 10
2. se evalúa la expresión 13+7 y B toma el valor resultante, 20
3. se evalúa la expresión con el valor de A en ese momento (10) más 15, AUX toma el valor 25 (A no se ve afectada) 4. A toma el valor de B en ese momento, o sea 20 (B no cambia)
5. se evalúa la expresión con el valor de A en ese momento (20), más 4, 24; B toma el valor resultante 24 (A no se ve afectada)
CONCEPTO DE COMPUTADOR
33
6. se evalúa la expresión con el valor de AUX en ese momento (25) más 1; AUX toma el valor resultante 26. Nótese que la operación de asignación, pese a que en algunos lenguajes se exprese con el signo = no es una igualdad matemática. Por ejemplo, la instrucción 4 del ejemplo anterior escrita en lenguaje C sería: A=B;
lo cual no quiere decir que a partir de ese momento A y B vayan a ser siempre iguales. Igualmente, la instrucción 6 del ejemplo en lenguaje C AUX=AUX+1;
tiene sentido como asignación, pero sería un completo contrasentido como igualdad matemática.Veámos la asignación en los distintos lenguajes: FORTRAN
BASIC
V=E
RCUECN
V=E C
v := e
v = e
Entrada: Las acciones de entrada permiten obtener determinados valores a partir de un periférico y asignarlos a determinadas variables. Esta entrada se conoce como operación de lectura. Y se representa por el formato: leer (lista de variables de entrada) Por ejemplo, la instrucción:
leer NUMERO, HORAS, TASA.
Lee del terminal los valores NUMERO, HORAS y TASAS, almacenándolos en la memoria; con la correspondiente acción de asignación a las tres variables. Si los tres números que se teclean en respuesta a la instrucción son: 12325, 32,1200, y equivale a la ejecución de las instrucciones. NUMERO ← 12325 HORAS ← 32 TASA ← 1200 Veamos como se realiza la entrada por teclado en algunos lenguajes: FORTRAN
BASIC
READ f, numero, horas, tasa
INPUT numero, horas, tasa
34
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
f: etiqueta de formato (* = formato libre) RCUECN
C
readln(numero, horas, tasa)
scanf(“%d %d %d”, &numero, &horas, &tasa)
Salida: Las acciones de salida, permiten transferir a un periférico los resultados obtenidos por el computador. Esta operación se conoce como escritura y se representa por el siguiente formato: escribir (lista de variables de salida) Como ejemplo, veamos el resultado de la ejecución de las siguientes instrucciones: A ← 100 B ← 200 C ← 300 escribir A, B, C Se visualizarán en pantalla o imprimirán en impresora los valores 100, 200 y 300 Veámos como se realiza la salida por pantalla en algunos lenguajes: FORTRAN
BASIC
WRITE f, numero, horas, tasa PRINT f, numero, horas, tasa f: etiqueta de formato (* = formato libre)
PRINT numero, horas, tasa
RCUECN
C
writeln(numero, horas, tasa)
printf(“%d %d horas,tasa)
%d”,numero,
30806" QTICPK\CEKłP"FG"WP"RTQITCOC Un programa escrito en un lenguaje procedural de alto nivel consta de un encabezamiento (opcional en algunos lenguajes) donde se indica información general del programa, tal como su nombre y algunas circunstancias que el traductor debe saber, y un cuerpo, donde se desarrolla el programa. En el cuerpo, primeramente debe hacerse la declaración de variables por la cual se indica que
CONCEPTO DE COMPUTADOR
35
variables van a emplearse en el subprograma y cual es el nombre y el tipo de cada una de ellas. A continuación aparecen las sentencias imperativas que, por medio de las variables que hayamos declarado representan el algoritmo. La estructura general que se espera que adopte un programa, es la siguiente: Identificador del programa o módulo {sección de declaraciones} inicio {datos de entrada} {sentencias imperativas, que ejecutan el algoritmo correspondiente} {datos de salida} fin Aunque es ligeramente prematuro, el lector deberá ser capaz de seguir sin dificultad la organización de los programas resultantes de codificar el problema de hallar el volumen de un cilindro a partir de su radio y altura en los cuatro lenguajes utilizados. FORTRAN
BASIC
PROGRAM volcilin C Hallar volumen de un cilindro REAL radio, altura, volumen DATA PI /3.14.16/ READ radio,altura volumen=PI*radio*radio*altura WRITE volumen END
REM radio, altura, volumen REM si no se declaran son SINGLE PRINT “Radio y altura : ” INPUT radio, altura volumen=3.1416* radio^2 *altura PRINT “el volumen es”;volumen END
RCUECN
C
program volcilin(input,output); const PI = 3.1416; (*Hallar volumen de un cilindro *) var radio, altura, volumen: real; begin write(“Radio y altura : “); readln(radio, altura); volumen=PI*radio*radio*altura; writeln(“el volumen es”,volumen); end.
#include #define PI 3.1416 /*Hallar volumen del cilindro */ main(){ float,radio, altura, volumen; printf(“Radio y altura : “); scanf(“%f%f”,&radio, &altura); volumen=PI*radio*radio*altura; printf(“volumen=%d\n”,volumen); }
30807" VTCFWEEKłP"FG"RTQITCOCU
36
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Una vez escrito un programa en lenguaje de alto nivel, para lo que se habrá empleado un editor de texto, tenemos lo que se llama un programa fuente. Para poderlo ejecutar, debemos recurrir a un programa traductor, que lo convierta en lenguaje máquina. Aunque el proceso de traducción puede hacerse de varias formas, lo más frecuente es hacerlo por medio de un compilador. Un compilador es un programa que traduce en su totalidad un programa fuente, escrito en un lenguaje de alto nivel, a un programa objeto, escrito en lenguaje máquina. Una vez obtenido el programa objeto, raramente puede ejecutarse por sí mismo, pues habitualmente presenta algunos cabos sin atar, que deben conectarse a otros programas del sistema para poder ejecutarse. Esta tarea corre a cargo a su vez de un programa llamado montador (linker) que genera el llamado programa ejecutable. Finalmente hay que introducir la totalidad del programa en la memoria. De esta tarea, se encarga un programa denominado cargador (loader) que sitúa las instrucciones en posiciones consecutivas de la memoria principal a partir de una determinada posición (con lo que tenemos el concepto ya conocido de programa almacenado) El proceso completo desde el código fuente hasta el programa almacenado se muestra en la Figura 1.11. Programa Fuente
Compilacion
Fig. 1.11.
Programa Objeto
Montaje
Programa Ejecutable
Carga
Programa Almacenado en memoria
Preparación de un programa para su ejecución
Las distintas versiones del programa (fuente, objeto y ejecutable), se suelen guardar en archivos, en un dispositivo de almacenamiento masivo, para ser usado posteriormente sin necesidad de volver a realizar la traducción. Por ello se habla de archivos fuente, archivos objeto y archivos ejecutables. Si queremos ejecutar un programa y disponemos de su archivo ejecutable, en la mayoría de casos será suficiente con escribir su nombre: el programa se cargará en la memoria como programa almacenado y podrá empezar a ejecutarse, sin necesidad de pasar por la compilación y el montaje.
3090" UKUVGOC"QRGTCVKXQ"["RTQITCOCU"FGN"UKUVGOC A medida que maduraba la programación, se vió lo ventajoso de reutilizar programas, se comenzó a almacenar programas o subprogramas cuya utilización es frecuente y básica para el trabajo del ordenador, (ej: los programas relacionados con la gestión de los distintos tipos de periféricos). Este fue el germen de los
CONCEPTO DE COMPUTADOR
37
primeros sistemas operativos: programas que gestionan las funciones básicas del computador, simplificando la tarea del programador que puede soslayar ciertos detalles de la máquina que quedan a cargo del sistema operativo. Hoy en día sería imposible o extremadamente difícil utilizar un computador sin disponer de un sistema operativo (S.O.). Los sistemas operativos son programas que gestionan los recursos de un computador en beneficio de los programas que se ejecutan en esa máquina. Un sistema operativo es en sí mismo un programa, pero forma parte integral de la máquina y es tan importante conocerlo como conocer sus componentes físicos. Sus tareas principales son: - Proporcionar al usuario una interfaz que le permita comunicarse con el computador. - Administrar los dispositivos de hardware del computador - Administrar y mantener los sistemas de archivo de disco. - Dar soporte a otros programas y a los programadores aislando los detalles físicos Cuando se enciende un computador, éste ejecuta primero una autoprueba para identificar los dispositivos que tiene conectados, contar la cantidad de memoria disponible y hacer una rápida comprobación de la misma. A continuación, busca un sistema operativo que le indique como interactuar con el usuario y como usar los dispositivos. Cuando lo encuentra lo carga en memoria y mantendrá parte del mismo en su memoria hasta que se apague el computador. Junto al sistema operativo hay un conjunto de programas conocidos como programas del sistema necesarios para el funcionamiento habitual de la máquina (algunos de estos programas se suministran con el sistema operativo y en ocasiones se consideran parte integrante del mismo). El software del sistema está constituido, además del sistema operativo, por: - Utilidades generales de programación, que contiene programas o utilidades que facilitan la construcción de las aplicaciones de los usuarios, sea cual sea la naturaleza de éstas. Incluye herramientas tales como: - Traductores (ensambladores, compiladores, etc.). - Editores de textos - Rastreadores/depuradores de errores de programación. - Sistemas de Gestión de archivos - Administrador de bibliotecas de programas. - etc. - Programas de diagnóstico, generación y mantenimiento. Que utilizan los responsables del mantenimiento y puesta al día del hardware y del software (incluida la generación y mantenimiento del propio SO). Con estos programas se
38
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
pretende por ejemplo localizar automáticamente las averías de un determinado dispositivo ó circuito, ó las causas de un mal funcionamiento de algún modulo del SO.
30:0" VKRQU"FG"EQORWVCFQTGU Hoy en día existe una gran variedad de computadores de uso general, que varían en tamaño capacidad y rendimiento. La propia evolución tecnológica dificulta la clasificación de los diferentes tipos de computadores, aunque se siguen distinguiendo una serie de grandes familias: SUPERCOMPUTADORES. Un supercomputador es el computador más potente disponible en un momento dado. Formado por procesadores múltiples, trabajan en paralelo accediendo a memorias de grandes dimensiones. Para el control de la entrada/salida de datos y de los canales de comunicaciones externos utilizan procesadores especiales, puesto que los requisitos de velocidad son muy elevados (los llamados front-end). Se suelen utilizar para efectuar cálculos complejos que requieren grandes cantidades de datos, tal como ocurre en astronomía, predicción meteorológica y en la simulación y modelado de procesos hidrodinámicos, aerodinámicos, químicos y biológicos. MACROCOMPUTADORES (mainframes). Son los ordenadores más potentes de uso común en aplicaciones comerciales. Están formados por grandes unidades independientes, con distintos procesadores centrales y otros procesadores que controlan la entrada/salida, los medios de almacenamiento masivo y los canales de comunicación de datos, provenientes de un gran número de terminales. Los mainframe permiten el trabajo concurrente de un gran numero de aplicaciones y usuarios, siendo su papel, ser el centro de sistemas transaccionales, tales como los que necesitan bancos, compañías aéreas, organismos de la administración, etc. SISTEMAS DE TAMAÑO MEDIO (minicomputadores). Se enmarcan, en un nivel intermedio entre los macrocomputadores y los computadores personales: Suelen disponer de varios módulos de proceso y de unidades de entrada/salida, que trabajan en paralelo, todos ellos montados en un bastidor central. Pueden soportar varias aplicaciones simultáneas, proporcionando servicio a varios usuarios, pudiendo hacer de enlace de información con otros sistemas de información remotos. Son computadores adecuados para laboratorios, control de la producción y organizaciones administrativas de tamaño medio; aunque cada vez están más en competencia con sistemas de menor coste. ESTACIONES DE TRABAJO. Son sistemas pensados para ser utilizado por un solo usuario, basados en microprocesadores muy potentes y dotados de gran
CONCEPTO DE COMPUTADOR
39
capacidad de memoria, diseñados desde el principio para soportar sistemas operativos capaces de ejecutar más de un programa de forma simultánea. Suelen estar conectados entre si, compartiendo recursos potentes como discos duros de alta velocidad, impresoras, y vías de comunicación con el exterior. Son especialmente adecuadas para diseño asistido por computador, cálculo científico y aplicaciones gráficas de alta resolución. Sin embargo, con la actual evolución, parece que en el futuro, tendremos dificultades para distinguir entre una estación de trabajo y un computador personal. COMPUTADORES PERSONALES. Diseñados inicialmente para trebajar de forma aislada e individual, han evolucionado de forma extraordinaria en prestaciones y reducción de costes. También llamados microcomputadores, son los ordenadores más difundidos, pudiéndose encontrar comúnmente tanto en oficinas y laboratorios como en aulas y hogares. La gama de aplicaciones que puede ejecutar es enorme y cada día penetra más en actividades básicas de nuestra vida cotidiana. La posibilidad de trabajar en red, incrementa sus prestaciones sin que pueda saberse con exactitud cuál es su futuro, sus dos representantes son los PC compatibles y los Apple. CALCULADORAS DE BOLSILLO (calculadoras programables). Con el aspecto tradicional de una calculador digital, incorporan las unidades funcionales que constituyen todo computador; con un teclado elemental para introducir datos y un sistema de presentación de una o pocas líneas, cada vez incluyen más cantidad de memoria, más posibilidades de programación y posibilidades de comunicación, con ordenadores próximos o lejanos. Su evolución es difícil de prever, al desaparecer las diferencias con los computadores personales portátiles.
30;0" EQOWPKECEKłP"FG"FCVQU"["TGFGU La posibilidad de interconectar computadoras brinda tantos beneficios que se ha convertido en una de las áreas con mayor crecimiento en la actualidad. A medida que las máquinas se dispersan, su interconexión es más deseable (a mayor numero de usuarios con máquina propia, se incrementa la necesidad de interconectarlas). Cuando tenemos un conjunto de ordenadores conectados entre si, decimos que forman una red informática. Con ello, la comunicación de datos y la transferencia electrónica de información, se ha convertido en un tema básico. 30;03" VTCPUOKUKłP"FG"FCVQU"FGPVTQ"FGN"EQORWVCFQT Antes de seguir con temas de comunicación, hay que resaltar, que cualquier tipo de transmisión de datos a larga distancia, funcionalmente, no debe
40
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
diferir de otras comunicaciones de datos, que se dan en el funcionamiento habitual del ordenador; tales como las que se producen dentro de los propios circuitos integrados (cuyo orden de magnitud es el mm) o las que se tienen lugar en una misma tarjeta, a través de los buses, entre procesador y memoria (del orden de cms). Incluso, tenemos conexiones de mayor longitud, en la comunicación entre el procesador y sus periféricos, que habitualmente requieren un soporte físico distinto, en forma de cables del orden de las decenas de centímetros. Estos cables constan de varios hilos, entonces se esta en condiciones de transmitir varios bits simultáneamente, uno por hilo, que ya definimos como transmisión de datos en paralelo, cuando hablamos de buses. Sin embargo cuando los datos tienen que transmitirse a cierta distancia, el uso de este tipo de cable resulta inviable; entonces se utiliza un conductor único. En estas circunstancias se transmiten los datos bit a bit, lo que se denomina transmisión de datos en serie. Las distintas posibilidades de intercomunicación implican que los dos soportes del computador (el físico y el lógico) tengan que adaptarse, a nuevos equipos y a procedimientos preestablecidos. Dados dos componentes, conectados entre si, llamaremos Interfaz, al conjunto de informaciones y equipos utilizadas para interconectarlos, que nos indican las secuencias de operaciones permitidas entre ellos y Protocolo, al conjunto de reglas y de procedimientos que permiten que desde uno de los componentes y a través de las interfaces respectivas, que el otro componente realice correctamente una función determinada. Por tanto las interfaces y protocolos especifican la forma de interconectar dos o más componentes de un sistema informático. Dado que hay dos tipos de transmisión, serie y paralelo, existen estas mismas dos clases genéricas de interfaces: y en cada uno de ellos se distinguen distintos tipos. En la Figura 1.12. se puede ver un ejemplo de un interfaz paralelo (tipo centronics) comúnmente utilizado para conectar un computador a una impresora.
Fig. 1.12.
Ejemplo de una conexión paralelo (tipo centronics)
CONCEPTO DE COMPUTADOR
41
30;04" EQOWPKECEKłP"FG"FCVQU"C"NCTIC"FKUVCPEKC Para llevar a cabo la comunicación entre dispositivos informáticos, se necesita un medio físico por donde transmitir las señales eléctricas, que van desde el simple es el par de cables trenzados hasta la fibra óptica, además de la posibilidad de conexión inalámbrica, a través de enlaces vía radio y vía satélite. Para transmisiones del orden de kilómetros, lo más común, es recurrir a la red telefónica ya existente, que está pensada para transmitir voz, por lo que su rango de frecuencias, de 300 a 3400 Hz, no la hace totalmente adecuada para la transmisión digital. Para poder transmitir datos mediante la red telefónica, debemos recurrir a una señal analógica, que codifique de cierta manera, los diferentes valores discretos, que caracterizan la información digital. Este proceso se lleva a cabo por un dispositivo llamado modem (que toma su nombre de la expresión modulador/demodulador). Los modems se conectan a la red telefónica de forma que se pueden utilizar la infraestructura de la misma, incluidas las conexiones celulares, de los teléfonos portátiles, para intercambiar información. Las velocidades de transmisión de datos varían considerablemente, dependiendo de las aplicaciones y el medio de comunicación. La unidad de medida del ritmo de transmisión de datos es el baudio. cuya definición precisa es realmente complicada, pero puede considerarse para propósitos prácticos como la velocidad de transmisión (bit/seg). La necesidad de interconectar dispositivos separados por grandes distancias, ha propiciado la aparición de una nueva disciplina técnica la Telemática, como rama interdisciplinar entre la Informática y las Telecomunicaciones, que versa sobre la utilización de equipos informáticos interconectados a través de líneas o redes de telecomunicación, bien sea periférico-computador, o computador-computador. Cuando introducimos estos nuevos elementos, aparecen una serie de nuevos factores que no se apreciaban en el tipo de interconexión que define el bus: el medio de transmisión -que va a requerir un tipo de circuitería especial y la aparición de nuevos errores de transmisión, debidos a la larga distancia y que no se dan en el interior del ordenador. Será de nuevo, tarea de la interfaz y del protocolo correspondiente, el establecer entre las máquinas, el convenio de transmisión de datos (piénsese que en una máquina una palabra puede ser de 1 byte y en otra de dos), la sincronización entre ambas, la velocidad de transmisión, la comprobación de errores de transmisión, etc. Con independencia del medio de comunicación entre dispositivos, analógico o digital, aparece la cuestión de gestionar la intercomunicación de múltiples y diferentes sistemas informáticos (networking en inglés), de forma que los distintos recursos puedan ser compartidos y utilizados de forma remota. En el caso de que estos recursos estén situados en un mismo edificio o en edificios próximos,
42
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
hablaremos de una LAN (Local Area Network), mientras que si hablamos de áreas geográficas de mayores dimensiones nos referiremos a WAN (Wide Area Network). 30;05" TGFGU"FG"QTFGPCFQTGU Entenderemos como red una manera de interconectar ordenadores de tal forma que cada uno de ellos sea consciente de esta circunstancia y de los recursos que puede compartir. La red viene a superar la utilización por múltiples usuarios de una misma máquina, habida cuenta que resulta mucho más funcional y económico, el dotar a cada usuario de su propio computador y conectarlos entre si, que poner en servicio complicados sistemas operativos que resuelvan este mismo problema. Las redes, en principio, consiguen aumentar la relación prestación/coste, proporcionando las siguientes posibilidades: • Aumentar la seguridad (fiabilidad) del sistema (si una computadora de la red falla, se puede utilizar otra de la misma red). • Si el equipo local, al que se tiene acceso directo, no dispone de las prestaciones adecuadas (poca memoria, está muy cargado de trabajo, no dispone de impresoras rápidas, etc.), el usuario puede conectarse a otro equipo de la red que reúna las características pertinentes y utilizar sus recursos o repartir su tarea entre varias máquinas. • Un servicio remoto para la utilización de aplicaciones, sin necesidad de que el usuario disponga realmente de ellas. • Permitir el acceso a bancos de datos ubicados a grandes distancias. • Posibilitan la existencia de sistemas de control industrial constituidos, por ejemplo, por varias computadoras de uso específico de una fábrica, interconectadas entre sí. • Utilización de la red de computadoras como medio de comunicación: correo electrónico. • etc.
CONCEPTO DE COMPUTADOR
43
1.1. UNA PRIMERA APROXIMACIÓN AL COMPUTADOR ........................1 1.1.1 OPERACIONES BÁSICAS DEL PROCESADO DE DATOS ......................2 1.1.2 ALGORITMOS Y PROGRAMAS ..................................................................3
1.2. ANTECEDENTES HISTÓRICOS .................................................................4 1.3. ORGANIZACIÓN DE UN COMPUTADOR................................................9
1.3.1 LA ARQUITECTURA DE VON NEUMANN ...............................................9 1.3.2 UNIDADES FUNCIONALES.......................................................................10 1.4. EL CONCEPTO DE PROGRAMA ALMACENADO ...............................16
1.4.1 TIPOS DE INSTRUCCIONES......................................................................16 1.4.2 LENGUAJE MÁQUINA Y LENGUAJE ENSAMBLADOR.......................18 1.4.3 EJECUCIÓN DE UN PROGRAMA .............................................................19
1.5. CONCEPTO ACTUAL DEL COMPUTADOR..........................................23 1.5.1 DEFINICIÓN ACTUAL................................................................................23 1.5.2 PARÁMETROS BÁSICOS DE LA MÁQUINA ..........................................24
1.6. DEL COMPUTADOR A LA PROGRAMACIÓN .....................................26
1.6.1 LOS DATOS..................................................................................................26 1.6.2 LENGUAJES DE PROGRAMACIÓN DE ALTO NIVEL...........................27 1.6.3 ELEMENTOS BÁSICOS DE UN LENGUAJE DE ALTO NIVEL .............30 1.6.4 ORGANIZACIÓN DE UN PROGRAMA.....................................................34 1.6.5 TRADUCCIÓN DE PROGRAMAS..............................................................35 1.7. SISTEMA OPERATIVO Y PROGRAMAS DEL SISTEMA....................36
1.8. TIPOS DE COMPUTADORES ....................................................................38 1.9. COMUNICACIÓN DE DATOS Y REDES .................................................39
1.9.1 TRANSMISIÓN DE DATOS DENTRO DEL COMPUTADOR .................39 1.9.2 COMUNICACIÓN DE DATOS A LARGA DISTANCIA...........................41 1.9.3 REDES DE ORDENADORES ......................................................................42
CAPÍTULO 2
SOPORTE LÓGICO DE UN COMPUTADOR
En el capítulo anterior distinguimos soporte lógico, de soporte físico a la hora de definir los distintos componentes de un computador. En este capítulo vamos a profundizar y a desarrollar el primero de ellos, cuyo conocimiento será imprescindible para poder proceder al proceso de programación. Muchos de los conceptos desarrollados en este capítulo se deberán confrontar frente al computador donde se valorará su utilidad. Un mayor desarrollo de los elementos del soporte físico del ordenador están fuera de la óptica de este libro, que trata de enfocar prioritariamente los fundamentos de programación. Sin embargo, existe un gran número de conceptos relacionados con el hardware que no deberían ser ignorados por el estudiante. Por esta razón hemos incluido al final del texto un apéndice que describe los elementos más representativos del hardware actual, a cuyo contenido podrá recurrir el lector si lo considera necesario.
4030" EQPEGRVQ"FG"UQRQTVG"NłIKEQ El soporte lógico o software de un ordenador es el conjunto de programas que permiten realizar las tareas asignadas a la máquina. En este concepto incluimos, tanto los programas suministrados en el momento de adquisición del ordenador, como los adquiridos a empresas de desarrollo y venta de programas y los escritos por los propios usuarios. El soporte lógico, según sea el nivel de trabajo de cada programa, se suele clasificar en software del sistema (necesario para administrar y mantener los recursos del ordenador de una forma eficiente) y software de aplicación (que corresponde a las aplicaciones específicas que utilizan los recursos del ordenador).
El software del sistema está constituido por: 45
46
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
* Programa de Arranque, es el primer programa que se ejecuta cuando arranca la máquina. Comprueba los dispositivos del ordenador y carga en memoria al Sistema Operativo. * Sistema Operativo (SO) conjunto de programas que controlan y supervisan el uso de los recursos del ordenador. * Programas de diagnóstico, generación y mantenimiento. Son utilizados por los responsables del mantenimiento y puesta al día del hardware y del software (incluida la generación y mantenimiento del propio SO). Con estos programas se pretende por ejemplo localizar automáticamente las averías de un determinado dispositivo o circuito, o las causas de un mal funcionamiento de algún modulo del SO. * Utilidades generales y Herramientas de programación que contienen programas o ayudas que facilitan la construcción o el uso de las aplicaciones, sea cual sea la naturaleza de éstas. Incluye herramientas tales como: - Traductores (ensambladores, compiladores e interpretes). - Editores de textos. - Rastreadores / depuradores de errores de programación. - Gestores de archivos. - Administradores de bibliotecas de programas. Por su parte el software de aplicación es de difícil clasificación, habida cuenta de la diversidad de campos donde se utiliza la informática. Una relación parcial de este tipo de software podría ser: - Procesadores de texto. - Bibliotecas matemáticas y estadísticas. - Hojas de cálculo. - Sistemas de gestión de archivo y de Bases de Datos. - Agenda Electrónica. - Correo Electrónico. - Aplicaciones Gráficas. - CAD/CAM (Computer Aided Design/ Manufacturing). - Gestión de comunicaciones. - Programas escritos por los usuarios. Diferenciar si ciertos programas de utilidades son software del sistema o software de aplicación es difícil. Así en principio el SO proporciona todos las características necesarias para el funcionamiento del sistema, sin embargo a veces no alcanzan a cumplir las necesidades del usuario o no es de fácil manejo. Estas deficiencias del SO se cubren mediante las llamadas utilidades, que se desarrollan posteriormente. Algunas de estas utilidades adquieren gran popularidad y las versiones posteriores
SOPORTE LÓGICO DE UN COMPUTADOR
47
del SO las van incorporando. La rápida evolución del software hace que muchos de estos programas de utilidades pasen de ser aplicaciones a ser software básico.
4040" C[WFCU"RCTC"NC"RTQITCOCEKłP 40403" VTCFWEVQTGU Como vimos en el primer capítulo, un programa escrito en un lenguaje de alto nivel debe ser traducido para que lo pueda ejecutar el computador. Cada traductor de programas es específico de cada tipo de computador y ‘entiende’ un lenguaje determinado. La transportabilidad de un programa consiste en que un mismo programa escrito en un lenguaje pueda ser entendido por traductores de ese lenguaje para distintos computadores. No obstante debe tenerse en cuenta que existen muchos “dialectos”, siendo necesario con frecuencia adaptar partes de los programas escritos en un dialecto de un lenguaje para pasarlos de una computadora a otra. Existen lenguajes prácticamente “independientes de la computadora” que están normalizados por organismos como el “American National Standars Institute” (ANSI), con el objetivo de garantizar la transportabilidad de los programas (ANSI FORTRAN, ANSI C, etc.). Para la traducción de los lenguajes de alto nivel, se utilizan los mismos métodos que nosotros usamos cuando tratamos de entender una conferencia expresada en un idioma que desconocemos: una opción es que alguien nos traduzca el contenido completo de la misma una vez concluida, otra es que alguien efectúe una traducción simultánea (frase a frase). Similarmente, según sea su forma de trabajar, existen dos tipos de traductores de programas: los compiladores y los intérpretes. 2.2.1.1" COMPILADORES Un compilador traduce en su totalidad un programa fuente, escrito en un lenguaje de alto nivel, a un programa objeto, escrito en lenguaje máquina. El programa fuente suele estar contenido en un archivo, y el programa objeto puede almacenarse como otro archivo en un dispositivo de almacenamiento masivo para ser procesado posteriormente, sin necesidad de volver a realizar la traducción. La traducción por un compilador (la compilación) consta de dos etapas fundamentales: Análisis del programa fuente (tanto lexicográfico como gramatical, llamado también parsing) y Síntesis del programa objeto.
48
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Análisis lexico Programa fuente
Fig. 2.1.
Parsing
Generación del código
Etapas de la compilación
Programa objeto
Como todo programa complejo, los compiladores estas diseñados modularmente, de forma que las distintas etapas del proceso de compilación, a su vez, se descomponen en varias fases o módulos, cada una de las cuales puede implicar el recorrer completamente el programa fuente. Vamos a describir brevemente la funcionalidad de cada uno de estos módulos o fases: a) Análisis lexicográfico.Consiste en descomponer el programa fuente en sus elementos constituyentes o símbolos: palabras reservadas, símbolos de operadores, identificadores constantes, comentarios, blancos, etc. Así por ejemplo los símbolos constitutivos de la sentencia: A=(C+D) * 17 son: operadores: = ( ) + * variables: A C D constantes: 17 b) Análisis sintáctico. Es la primera parte del análisis gramatical y tiene como misión identificar las estructuras (expresiones, sentencias declarativas, asignaciones, etc.) del programa. La sintaxis de un lenguaje de programación especifica cómo debe escribirse un programa, mediante las reglas correspondientes. Por ejemplo, es sintácticamente incorrecta la sentencia: A+B= (C-D)* 17 ya que el signo “=“ no tiene el sentido que se le da en matemáticas (para formar ecuaciones). En los lenguajes citados dicho signo se utiliza para asignar a una variable (no a una expresión aritmética) el resultado de evaluar una expresión (la que está a la derecha del signo igual). Es correcto sintácticamente escribir: A=(C-D) * 17 (“Calcular (C-D) x17, y el valor que resulte asignárselo a la variable A”).
SOPORTE LÓGICO DE UN COMPUTADOR
49
c) Análisis semántico. La semántica de un lenguaje de programación es el significado dado a las distintas construcciones sintácticas. Por ejemplo, asignar a una variable definida como dato numérico, el valor de una cadena de caracteres, es semánticamente incorrecto para algunos compiladores y si se detecta una circunstancia de este tipo, debe señalarse el error correspondiente. d) Optimización. En las fases de optimización se mejora el código intermedio analizándose el programa globalmente. Un programa, por ejemplo (ver Figura 2.2.a) puede incluir dentro de un bucle, que debe ejecutarse diez mil veces, una sentencia que asigna a una variable un valor constante (B= 7,5), no alterándose dicho valor (B) dentro del bucle. Con ello, innecesariamente se asignaría el valor 7,5 a la variable B diez mil veces. El optimizador sacaría la sentencia B= 7,5, fuera (antes) del bucle, ejecutándose así dicha instrucción una sola vez (ver Figura 2.2.b) . Hay que hacer notar que el programa inicial es correcto (con B=7,5 dentro del bucle los resultados del programa son los mismos), pero la optimización realizada por el compilador reduce el tiempo de ejecución. Desde I = 1 hasta I = 10 000 R= 37.-I*35 B=7,5 Z= B-SIN (-R/35000) fin hacer (a) Fig. 2.2.
B=7.5 Desde I = 1 hasta I=10.000 R=37.-I*35 Z=B-SIN(-R/35000) fin hacer
(b) Ejemplo de optimización de un bucle
e) Generación de código.En esta etapa se genera el código objeto definitivo. El archivo objeto generado puede ser (dependiendo del compilador) directamente ejecutable, o necesitar otros pasos previos a la ejecución, tales como ensamblado, encadenado y carga. Un programa objeto no es todavía ejecutable en la mayoría de los casos ya que es necesario incorporarle las llamadas a funciones estándar, que residen en bibliotecas de programas. Este proceso se llama encadenado (link) y tras él sí se consigue el código ejecutable. La compilación es un proceso complejo que consume a veces un tiempo muy superior a la propia ejecución del programa. En cualquiera de las fases de análisis, el compilador puede dar mensajes sobre los errores que detecta en el programa
50
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
fuente, cancelando en ocasiones, la compilación para que el usuario realice en el archivo fuente las correcciones oportunas. 2.2.1.2" INTÉRPRETES Una alternativa a la compilación, la cual sólo produce una traducción a lenguaje máquina del programa fuente, es la interpretación que ejecuta el programa directamente desde el programa fuente (Ver Figura 2.3) Un intérprete hace que un programa fuente escrito en un lenguaje vaya, sentencia a sentencia, traduciéndose y ejecutándose directamente por la computadora. El intérprete capta una sentencia fuente, la analiza e interpreta dando lugar a su ejecución inmediata, no creándose, por tanto, un archivo o programa objeto almacenable en memoria masiva para ulteriores ejecuciones. Programa compilado Programa fuente
Compilación
Programa objeto
Programa interpretado Programa fuente
Fig. 2.3.
Programa ejecutado por la máquina mediante el uso de un intérprete
Compilación e Interpretación
En la práctica el usuario crea un archivo con el programa fuente (esto suele realizarse con un editor específico del propio intérprete del lenguaje). Según se van almacenando las instrucciones simbólicas, éstas se analizan y se generan los mensajes de error a que dieran lugar; así el usuario puede proceder inmediatamente a su corrección. Una vez creado el archivo fuente, el usuario puede dar la orden de ejecución y el intérprete lo ejecuta línea a línea. El análisis siempre antecede inmediatamente a la ejecución, de forma que: a) Si una sentencia forma parte de un bucle, se analiza tantas veces como tenga que ejecutarse el bucle. Si el programa de la figura 2.2 a) fuese traducido por un
SOPORTE LÓGICO DE UN COMPUTADOR
51
intérprete, la sentencia R=37.-I*35, se analizaría 10 000 veces, y no sólo una vez, como ocurría en un compilador. b) Las optimizaciones sólo se hacen dentro del contexto de cada sentencia, y no contemplándose el programa o sus estructuras en conjunto. La optimización mostrada en la figura 2.2 b), no podría ser llevada a cabo por un intérprete, porque cuando llegue a B=7.5 ya se han ejecutado las sentencias anteriores, no pudiendo, por tanto, ubicar la sentencia B=7.5 antes del bucle. c) Cada vez que utilicemos un programa tenemos que volver a traducirlo, ya que en la traducción no se genera un archivo objeto que poder guardar en memoria masiva (y utilizarlo en cada nueva ejecución). Con un compilador, aunque en muchos casos la traducción sea más lenta, ésta sólo debe hacerse una vez (ya depurado el programa) y cuando deseamos ejecutar un programa ejecutamos el archivo ejecutable creado a partir del objeto. Los intérpretes, a pesar de los inconvenientes anteriores, a veces son preferibles a los compiladores, como por ejemplo, cuando el número de veces que se va a ejecutar el programa es muy bajo y no hay problemas de velocidad; además, con ellos puede ser más fácil desarrollar programas. Esto es así porque normalmente la ejecución de un programa bajo intérprete puede interrumpirse en cualquier momento para conocer los valores de las distintas variables y la instrucción fuente que acaba de ejecutarse. Cuando esto se hace se tiene una gran ayuda para la localización de errores en el programa. Con un programa objeto esto no se puede realizar, salvo que el programa se ejecute bajo el control de un programa especial de ayuda denominado depurador (“debugger”). Además, con un intérprete, cuando se localiza un error sólo debe modificarse este error y volver a ejecutarse a partir de la instrucción en cuestión. Con un compilador, si se localiza un error durante la ejecución, el programador debe corregir las instrucciones erróneas sobre el archivo fuente (no sobre el objeto) y volver a compilarlo en su totalidad después ensamblarlo, y en su caso, montarlo y cargarlo. Existen en la actualidad traductores que en cierta medida responden de las ventajas tanto de los intérpretes como de los compiladores. Estos traductores se denominan compiladores interactivos o incrementales. Un compilador interactivo es un sistema que permite la edición , compilación y ejecución de programas escritos en un determinado lenguaje de alto nivel, simplificando la creación y depuración del programa, permitiendo que el compilador durante la edición realice parte de las comprobaciones y la traducción a código intermedio. Ello permite detectar con antelación muchos errores y evitar la retraducción de todo el programa , si se han realizado sólo pequeños cambios.
52
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
40404" VKRQU"FG"NGPIWCLGU"FG"CNVQ"PKXGN En los primeros tiempos de la informática y considerando el pequeño tamaño de las memorias disponibles y la lentitud de los procesadores, se pensaba que los traductores iban a ser muy ineficientes; sin embargo con la aparición del primer compilador FORTRAN a mitad de los 50, su uso se extendió rápidamente, hasta que en 1965, se estima que la Babel informática llegaba ya, hasta los 1700 lenguajes de programación diferentes. Esta explosión continua en la actualidad, puesto que la experiencia acumulada y el advenimiento de nuevas tecnologías, tanto en software como en hardware, favorecen la aparición de nuevos lenguajes y el refinamiento y mejora de los existentes. Este frenesí, que conduce a que un lenguaje aprendido hoy, esté destinado a ser declarado mas o menos obsoleto dentro de pocos años, explica que esté fuera del objetivo de este libro, el estudio de un lenguaje particular. Nuestro objetivo es separar los principios de la informática y la programación, del conocimiento de una sintaxis y una semántica particular. Sin embargo es obvio, que la formación en Informática, no se entiende sin un cierto dominio de al menos un lenguaje de alto nivel( que debe conseguirse delante de un ordenador) aunque la selección del mismo dependerá de las necesidades y posibilidades de cada lector, ó curso. Con el objetivo de poner orden en esta Babel, en una primera clasificación podemos distinguir entre lenguajes de propósito general (empleados en todo tipo de aplicaciones: de gestión, científico-técnicas, de desarrollo de software de sistemas, etc.) cuyos ejemplos podrían ser el C, Pascal y otros de propósito más específico como sería el caso del FORTRAN para el calculo científico, el Cobol para la gestión o el LISP y el Prolog para la Inteligencia Artificial, por no referirnos a lenguajes específicos para la gestión de bases de datos, como el SQL. Una segunda clasificación, es posible atendiendo al estilo de programación: a) Lenguajes basados en la asignación de valores (Lenguajes Procedurales o Imperativos). Se fundamentan en la utilización de variables para almacenar valores y en la realización de operaciones con los datos almacenados, especificando la secuencia de acciones que conduce a la resolución del problema propuesto. La mayoría de los lenguajes son de este tipo : FORTRAN, BASIC, COBOL, Pascal, Modula, Ada, C, etc. b) Lenguajes declarativos, que se limitan a describir sus estructuras de datos y las relaciones entre ellas que puedan resultar significativas para la realización de una determinada tarea. Por tanto están basados en la definición de funciones o relaciones y no utilizan instrucciones de asignación , por lo que sus variables
SOPORTE LÓGICO DE UN COMPUTADOR
53
no almacenan necesariamente valores. Los programas están formados bien por una serie de definiciones de funciones (Lenguajes Funcionales, como LISP) o de predicados (Lenguajes de Programación Lógica, como PROLOG, CLP, etc.). c) Lenguajes orientados a objetos. Estos lenguajes utilizan estructuras de datos complejas, llamados objetos, que encapsulan simultáneamente información y operaciones. Estos objetos se comunican entre si a través de mensajes que son reconocibles de forma específica por cada objeto. Un programa es básicamente un universo de objetos que al ejecutarse intercambian mensajes entre sí, simulando sus respectivos comportamientos, en busca del resultado final. Ejemplo de estos lenguajes son el C++, Smalltalk, etc. 40405" WVKNKFCFGU"["HCUGU"GP"NC"GLGEWEKłP"FG"WP"RTQITCOC Ya hemos adelantado que una vez codificado un programa, para su ejecución era necesario realizar una serie de operaciones previas. Durante las primeras generaciones de computadoras estas operaciones (o su control) se hacían más o menos manualmente, pero hoy día existen programas de ayuda al usuario, de forma que la propia computadora se usa para auxiliar en la confección, prueba y control de la ejecución de los programas. Los programas que proporcionan estas herramientas y ayudas, genéricamente se denominan utilidades de programación. Por ejemplo, la ejecución de un programa llamado MEDIAS en un lenguaje de alto nivel, supone seguir el esquema de la Figura 2.4. Una vez redactado el programa, debe introducirse en la computadora. Para ello crea un archivo en memoria masiva (disco), con ayuda del editor de textos. Este es un programa de utilidad que nos permite introducir y modificar (borrar, intercalar, cambiar, ampliar, duplicar, etc.) cómodamente información (de programas o datos) en un archivo. Podríamos decir que esta fase es la introducción y corrección “mecanográfica” del programa. Cuando el archivo está creado (supongamos que con el nombre de MEDIAS.C en un PC con DOS), pasamos a compilar el programa. Con ello obtenemos el mismo programa en lenguaje ensamblador (suponiendo que este compilador no genera directamente código máquina). FASE 1) Introducción y corrección ‘mecanográfica’ del programa.
UTILIDAD O AYUDA Editor de textos (“text editor”).
3) Traducción
Ensamblador (“assembler”).
2) Traducción de lenguaje de alto nivel a lenguaje ensamblador.(*). de
lenguaje
Compilador (“compiler”)
54
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
ensamblador a lenguaje máquina. (*).
4) Montaje de un archivo ejecutable, enlazando los módulos que componen el programa completo.
Montador, Enlazador (“linker”).
6) Ejecución del programa.
Sistema Operativo.
5) Carga del programa en memoria principal. 7) Rastreo y depuración.
Cargador (“loader”). Rastreador y depurador (“tracer” y “debugger”).
(*) En el caso de que el compilador genera el código objeto directamente en lenguaje máquina y no en lenguaje ensamblador estos dos pasos se funden.
Fig. 2.4.
Fases y utilidades en la ejecución de un programa
A continuación se ensambla, generándose un nuevo archivo (en nuestro ejemplo de nombre MEDIAS.OBJ) que contiene el programa en lenguaje máquina. Este programa se suele denominar reubicable, y no es directamente ejecutable. En efecto, la mayoría de programas hacen llamadas a diversas rutinas o segmentos del propio usuario o del sistema (funciones de biblioteca, etc.) que deben “unirse” al programa principal. Todos estos segmentos, antes de unirse o montarse deben estar compilados y ensamblados. Todos los segmentos a unir han de ser reubicables, en el sentido de que su direccionamiento es relativo, comenzando la primera instrucción en la dirección 0 de memoria. Además tienen asociados unas tablas donde se encuentran anotadas las direcciones relativas de aquellas instrucciones que hacen referencia a otras direcciones de memoria bien del propio segmento (instrucciones de saltos, por ejemplo) o de otros segmentos (por ejemplo llamadas a subrutina). A veces, el programa principal y sus subrutinas no se cargan consecutivamente, sino que pueden ocupar zonas diversas de la memoria, y en este caso hay que transformar las direcciones relativas de los segmentos en las direcciones físicas en que realmente se ubicarán. Para efectuar la unión de los distintos segmentos o módulos, “encadenando” o “enlazando” las llamadas entre ellos, se utiliza una denominada montador, colector, enlazador o encadenador que genera un nuevo archivo (por ejemplo de nombre MEDIAS.EXE en un PC con DOS) denominado ejecutable ya que puede ser ejecutado directamente. El encadenador, para realizar su trabajo, utiliza las tablas de instrucciones con referencia a memoria de los módulos reubicables y genera una tabla de símbolos externos, que incluye los nombres y direcciones de las instrucciones a las que hay que saltar desde otros segmentos.
SOPORTE LÓGICO DE UN COMPUTADOR
55
La fase siguiente es introducir o cargar el programa ejecutable en memoria y prepararlo para su ejecución. Estas operaciones las realiza una utilidad denominada cargador. La siguiente fase es la ejecución del programa. Para ello un módulo del sistema operativo pasa el control de la CPU a la dirección de la palabra de memoria donde se encuentra la primera instrucción del programa (es decir, carga en el registro contador de programa la dirección física de dicha instrucción) a partir de la cual se sucederá la ejecución de las distintas instrucciones que constituyen el programa. Las utilidades de rastreo y depuración de errores son de uso opcional y permiten efectuar funciones tales como ejecutar el programa instrucción a instrucción, mostrándose después de cada ejecución el contenido de las variables que van cambiando; su objeto es el de detectar posibles errores u optimizar el programa.
4050" RTQITCOC"FG"CTTCPSWG Cuando se enciende un computador, lo primero que éste hace es llevar a cabo un autodiagnóstico llamado autoprueba de encendido (Power On Self Test, POST). Durante el mismo, la computadora identifica su memoria, sus discos, su teclado y cualquier otro dispositivo que tenga conectado. Lo siguiente es buscar un SO para arrancar (boot) como primer programa para “empezar a funcionar por sí misma”. Ello lleva a preguntarnos en qué momento empieza a trabajar el Sistema Operativo. Para entender este procedimiento hemos de recordar algunos elementos de la tecnología de la memoria principal. Sabemos que la RAM es volátil, mientras que la ROM retiene la información almacenada, aunque se apague el ordenador. En esta última hemos de cargar un software cuyo concurso es imprescindible para la citada identificación del hardware del ordenador y para que se produzca el proceso de arranque de la máquina. Una CPU está construida de forma que cada vez que se arranca, se inicializa el contador de programa en una dirección predeterminada, antes de su primer ciclo de máquina. Por tanto, de forma automática, interpreta que el contenido de esta posición de memoria, es el inicio del primer programa que va a ser ejecutado. En esta área de memoria ROM se arranca el programa, que entre otras cosas se encargará de cargar el sistema operativo (ver Figura 2.5). Evidentemente este programa en ROM no forma parte del propio sistema operativo, ya que su función consiste, normalmente, en acceder a un disco predeterminado, donde asume que se encuentra el inicio del sistema operativo (Un PC busca primero el SO en la unidad de disco flexible; si encuentra ahí un SO válido, lo utiliza; si no lo busca en el disco duro primario) y ejecuta una transferencia del disco al área de memoria donde se instala el sistema operativo (o parte de éste). De esta forma, al arrancarse la máquina, el Sistema Operativo se carga automáticamente en ella, en posiciones que salvo cambios son siempre las mismas (Ver Figura 2.5).
56
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Memoria Principal ROM
Programa de arranque
Sistema Operativo
Memoria volátil
Fase 1:
Disco
La máquina al empezar ejecuta el programa de arranque que está en memoria. El Sistema Operativo está almacenado en memoria secundaria.
Memoria Principal ROM
Memoria volátil
Fase 2:
Fig. 2.5.
Programa de arranque Sistema Operativo
Disco
Sistema Operativo
El programa de arranque transfiere el Sistema Operativo a memoria principal, que a partir de ahora controlará a la máquina.
Proceso de arranque y carga del sistema operativo.
4060" UKUVGOCU"QRGTCVKXQU"*UQ+ Un sistema operativo (SO) es en sí mismo un programa de computadora, aunque un tanto especial, quizás el más complejo e importante. El SO es en cierto sentido, una parte integral de la máquina y es tan importante conocerlo como conocer su hardware. Tras el arranque, ya sabemos, que el SO despierta a la computadora y hace que reconozca a la CPU, la memoria, el teclado, y los distintos periféricos. Una vez puesto en marcha el SO, mantiene al menos parte de éste en su memoria en todo momento. Todos los SO tienen dos objetivos fundamentales: a) Hacer posible el uso eficiente de los recursos del sistema y b) Ocultar las dificultades que supone el control directo del hardware del ordenador. El primer objetivo se ve entorpecido por el hecho de que algunos dispositivos funcionan mucho más rápidamente que otros; por ello, una de las misiones de los SO es la de garantizar que los dispositivos más rápidos, como los procesadores, no sean retenidos por otros dispositivos más lentos, como los dispositivos periféricos. Para conseguir el segundo, simplificar las operaciones del hardware, los SO generan una máquina virtual. Una máquina virtual es un ordenador simplificado, en el que los
SOPORTE LÓGICO DE UN COMPUTADOR
57
detalles corren a cargo del SO. Las personas que escriben programas sólo necesitan conocer la máquina virtual, sin necesidad de entrar en los detalles reales del hardware. Una consecuencia importante de ello es que una aplicación desarrollada para un SO, puede que sólo necesite recompilarse, para pasar de un entorno hardware a otro. 40603" HWPEKQPGU"FG"NQU"UKUVGOCU"QRGTCVKXQU0 Las tareas que desempeña un SO dependen en cierta medida del tipo de las características de cada ordenador. Así las funciones de un SO multitarea, en un gran ordenador, son diferentes de las de un ordenador personal. Sin embargo, existen ciertos puntos en común que permiten clarificar de forma general las tareas principales de un SO en: • Proporcionar una interfaz de usuario, para que este último se pueda comunicar con la computadora. • Administrar y controlar los dispositivos de hardware del computador. • Administrar y mantener los sistemas de archivo de disco. • Apoyar la ejecución de otros programas. En los grandes sistemas, existe un operador (o un equipo de ellos) cuya misión está relacionada con el SO, ellos arrancan y paran el ordenador, cargan programas y discos de datos, asignan recursos a los usuarios y responden a cualquier problema que pueda presentarse. Las redes de ordenadores suelen tener, además, un administrador de red cuyo trabajo consiste en mantener la red funcionando de forma eficiente, principalmente controlando los recursos compartidos como servidores de ficheros e impresoras. Estos operadores de ordenadores y administradores de redes trabajan directamente con el SO de los ordenadores. En los PC y estaciones de trabajo el usuario es también el operador, activando y parando programas, cargando disquetes, etc. Los SO como programas deben tener una serie de características relacionadas con su calidad, en particular: eficiencia, (supone que el SO debe ejecutar sus funciones de forma rápida ya que el tiempo que el SO emplea en su funcionamiento es tiempo no disponible para la aplicación que se ejecuta), fiabilidad, (un fallo en el SO puede inutilizar el ordenador que éste controla), facilidad de mantenimiento y un tamaño reducido (un SO pequeño ocupa menos espacio es menos propenso a los errores y funciona más rápidamente). En la práctica es necesario llegar a un equilibrio entre las facilidades que proporciona el SO y su tamaño.
58
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
40604" NC"GUVTWEVWTC"FG"WP"UKUVGOC"QRGTCVKXQ"V¯RKEQ La estructura de un SO varía de acuerdo con su tamaño y complejidad. Sin embargo, las partes o módulos que vamos a citar se encuentran en todos los SO de una u otra forma. Los encargados de desarrollar sistemas operativos utilizan la metáfora de una semilla para describir la estructura de los programas que escriben. Así las funciones centrales de un SO son controladas por el núcleo (kernel) mientras que la interfaz del usuario es controlada por el entorno (shell). Por ejemplo, la parte más importante del DOS, es un programa con el nombre COMMAND.COM. Este programa tiene las citadas dos partes: a) El núcleo, que se mantiene en memoria en todo momento contiene el código máquina de bajo nivel para manejar la administración del hardware que pueda necesitar el programa que se está ejecutando. b) La shell, que también se le llama intérprete de órdenes (es quien toma el control de la pantalla), permite que el usuario teclee, interpreta lo tecleado y lo lleva a cabo. El intérprete de órdenes es la parte del programa que establece la interfaz de línea de órdenes. En los sistemas DOS, al menos una parte del programa COMMAND.COM está siempre en la memoria, proporcionando a los programas los servicios de bajo nivel administración del hardware y del disco. Sin embargo, las funciones de bajo nivel del SO y las funciones de interpretación de órdenes están separadas, de tal forma que es posible reemplazar el intérprete de órdenes MS-DOS estándar con uno diferente. En otras palabras, puedes mantener el núcleo del DOS ejecutándose, pero utilizar una interfaz de usuario diferente. Esto es exactamente lo que sucede cuando cargas Windows. Al teclear “WIN” en el prompt de DOS, el programa Windows toma el lugar de la shell, reemplazando la interfaz de línea de órdenes con una interfaz gráfica del usuario. 2.4.2.1" NÚCLEO El propio núcleo es el módulo de más bajo nivel del SO y trabaja al servicio del resto de módulos. El núcleo trabaja directamente sobre el hardware del ordenador y proporciona una serie de servicios a las capas superiores del sistema. Entre estas tareas se incluyen: - Manejo de interrupciones: Entendemos como interrupción toda señal externa que detiene la ejecución de un programa. Cuando el hardware del ordenador detecta una interrupción, el control se transfiere al módulo de control de interrupciones del núcleo, que analiza el carácter de la interrupción y toma las acciones apropiadas (transferir el control a otro módulo del SO, iniciar otro programa o continuar la ejecución del programa interrumpido, etc.).
SOPORTE LÓGICO DE UN COMPUTADOR
59
- Asignación de trabajo al procesador: Para ello el núcleo transfiere el control al programa que el planificador ha determinado, para que sea el próximo en ejecutarse. El control se logra manteniendo una cola de mensajes en espera para cada uno de los programas activos. El núcleo recibe los mensajes y los va almacenando en la cola apropiada al destino en cuestión, para distribuirlos cuando el programa de destino se active. - Comunicación entre programas: La mayoría de los ordenadores disponen de instrucciones cuyo uso está restringido al núcleo, que transfieren el control de un programa a otro y que acceden a determinados registros. 2.4.2.2" MÓDULOS DE INTERFAZ CON EL OPERADOR Y LOS USUARIOS Las comunicaciones entre el SO y el mundo exterior tienen lugar a través de dos interfaces: el interfaz usuario/SO y el interfaz SO/operador. Esta idea puede resultar extraña para el usuario habitual de un ordenador personal, pues en estos sistemas, se confunden en uno solo. El interfaz con el usuario proporciona un lenguaje, cuyas instrucciones controlan la ejecución de cada programa, especificándose entre otras cosas el máximo tiempo de CPU que se puede utilizar, cuánta memoria se necesita y qué periféricos se van a utilizar. El interfaz SO/operador del sistema está constituido también por órdenes y mensajes de forma que el operador puede dirigir gran parte de las funciones del SO. Esto se aplica particularmente a la planificación y asignación de recursos. En todo momento el operador debe tener el control global del ordenador. Existen dos amplias categorías de interfaz de usuario: interfaces de línea de órdenes o interfaces gráficas de usuario. Para usar un SO con interfaz de línea de orden, se introduce palabras y símbolos desde el teclado de la computadora. Con un interfaz gráfico del usuario (Graphical User Interface, GUI), se seleccionan las acciones mediante el uso de un ratón o dispositivo indicador similar para pulsar sobre figuras llamadas iconos o seleccionar opciones de los menús. Cada SO proporciona una interfaz de usuario, de cualquiera de los tipos ya descritos. (Ya hemos visto que esto lo hace Windows respecto a DOS). La interfaz de línea de ordenes.Como ejemplo, la interfaz de línea de órdenes permite controlar las funciones mediante el tecleo de órdenes, después del indicador de petición de entrada o prompt. En DOS, el prompt por omisión es la letra que identifica la unidad activa de disco seguida de un signo mayor que (C>). El prompt indica que el SO está listo para aceptar una orden. Para introducirla, se utiliza el teclado para teclear las palabras y los símbolos. Si se teclea una orden en forma incorrecta, el
60
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
SO responde con un mensaje indicando que no entendió la orden. Cuando esto pasa, simplemente se vuelve a teclear la orden correctamente. La interfaz gráfica del usuario.Mucha gente piensa que el desarrollo más significativo en el mundo de las computadoras desde que los fabricantes comenzaron a construirlas en torno a microprocesadores, fue el desarrollo de la interfaz gráfica del usuario. Al fin, las computadoras permitían trabajar de la misma forma en que la gente trabaja: visualmente. Las interfaces más intuitivas utilizan objetos y símbolos llamadas iconos con los cuales cualquier persona está familiarizada, incluyendo aquellas personas que nunca antes han utilizado una computadora. Por ejemplo, todo el mundo sabe lo que es un bote de basura. Es bastante obvio para lo que sirve un icono representando un bote de basura: para tirar objetos. Tú te deshaces de algo llevando su icono al bote de basura. Cuando sueltas el botón del ratón, el objeto desaparece en el bote de basura, que se abulta. 40605" CFOKPKUVTCEKłP"FGN"JCTFYCTG Sin importar qué tipo de interfaz del usuario tenga la computadora (de línea de ordenes o gráfica), el SO intercepta las órdenes para usar la memoria y otros dispositivos, mantiene un registro de qué programas tienen acceso a qué dispositivos, y así sucesivamente. Así por ejemplo, cuando se introduce la orden directorio (dir) en el prompt del SO o se hace clic en una carpeta de una GUI, el SO interpreta la acción como una orden para mostrar los archivos en ese directorio o carpeta. La lógica del programa en el núcleo responde a la orden mediante la interrupción de la CPU y la instruye para que vaya a la unidad de disco y muestre los nombres de los archivos que encuentre en el directorio o en la carpeta. El SO intercepta la cadena de datos (los nombres de los archivos) regresándolos del disco y desplegándolos en la pantalla. Veamos con un poco mas de detalle, la interacción del SO con los distintos elementos del hardware del ordenador: 2.4.3.1" GESTIÓN DE LA MEMORIA La memoria principal de la mayoría de los ordenadores es mucho más pequeña de lo que sería necesario para contener todos los programas y datos que maneja un ordenador en un momento dado. El módulo de gestión de la memoria de un SO es el encargado de asignar ciertas porciones de la memoria principal a los diferentes programas o partes de los programas que la puedan necesitar, mientras el resto de los datos y los programas se mantienen en los dispositivos de almacenamiento masivo. De este modo, cuando se asigna una parte de la memoria principal se hace de una forma estructurada, siguiendo una determinada orden.
SOPORTE LÓGICO DE UN COMPUTADOR
61
La forma más común de gestión de la memoria supone crear una memoria virtual utilizando los dispositivos de almacenamiento masivo como si fueran parte de la memoria principal. El SO se encarga de controlar las transferencias entre los medios de almacenamiento masivo y la memoria, creando la sensación de una memoria mayor de la que en realidad existe. De este modo, para un usuario la memoria principal y la memoria de almacenamiento masivo forman parte de la misma cosa, la memoria virtual del sistema. 2.4.3.2" CONTROL DE ENTRADA/SALIDA Los problemas asociados con la E/S de datos tienen su origen en las diferentes características y velocidades de los dispositivos. Por ejemplo, una impresora de líneas produce una línea de caracteres cada vez, mientras que un teclado acepta un único carácter cada vez. Una impresora de líneas tiene una velocidad de transferencia de caracteres (más de cien veces mayor) que un teclado. El módulo de control de E/S de un SO trata estos problemas presentando al programador la entrada/salida como una cuestión independiente del dispositivo. Para los programadores, todos los dispositivos tienen las mismas características, siendo el SO el encargado de atender las particularidades de cada uno de ellos. Puesto que el sistema operativo es único, se entenderá que los detalles de cada periférico del sistema deban residir en ROM, para que esta información que maneja el SO no se pierda cada vez que se apague el ordenador. Además, también existen “drivers” (conductores) que son programas que se ejecutan en cada arranque para hacer de interfaz entre el SO y el periférico. 2.4.3.3" GESTIÓN DE LOS DISPOSITIVOS DE ALMACENAMIENTO MASIVO Los dispositivos de almacenamiento masivo de un ordenador constituyen la zona donde se guardan datos y programas tanto del sistema como de los diferentes usuarios. El módulo encargado de la gestión de estos dispositivos tiene la misión de mantener la estructura de esta información y de asegurar el uso eficiente de los medios de almacenamiento masivo. El SO se encarga de los aspectos físicos de la transferencia (determinar qué bloques y sectores de un disco se van a utilizar, etc.), dejando al programador libre para que sólo se preocupe de los aspectos lógicos de la transferencia (los registros y ficheros involucrados). Los datos y los programas de un dispositivo de almacenamiento masivo se mantienen en ficheros. Como veremos a continuación, el módulo de gestión de estos dispositivos de almacenamiento masivo supervisa la creación, actualización y
62
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
eliminación de los ficheros que existen en el sistema en cada momento y coopera con el módulo de gestión de la memoria durante las transferencias de datos desde y hacia la memoria principal. Si se dispone de un sistema de memoria virtual, existen transferencias entre la memoria principal y los medios de almacenamiento masivo para mantener la estructura de la memoria virtual. 40606" CFOKPKUVTCEKłP"FGN"UKUVGOC"FG"CTEJKXQU Sabemos cómo un controlador de disco y una unidad de disco trabajaban juntos para almacenar bits y bytes de información en un disco, pero no consideramos la información que el SO pasa a la computadora para leer y almacenar archivos. Los archivos pueden contener instrucciones de programas o información creada o usada por un programa. Cuando hay cientos de archivos en un disco, encontrar lo que se necesita puede tomar su tiempo, para reducirlo, se necesita usar los medios proporcionados por el SO para organizar estos archivos dentro de grupos más pequeños y más lógicos, habitualmente en forma de directorios. Los ficheros almacenados en los dispositivos de almacenamiento masivo contienen información que puede ser compartida, de carácter privado, o incluso secreta. Por tanto, cada fichero está dotado de un conjunto de privilegios de acceso, siendo una misión del SO asegurar que estos privilegios no sean violados. 40607" CRQ[Q"C"NC"GLGEWEKłP"FG"RTQITCOCU"FG"CRNKECEKłP Otra de las funciones importantes del SO es proporcionar servicios a otros programas. A menudo, estos servicios son similares a aquellos que el SO proporciona directamente a los usuarios. Por ejemplo, cuando se quiere que un procesador de texto recupere un documento, con el cual se ha estado trabajando, el procesador desplegará los archivos del directorio que se especifique. Para hacer esto, el programa llama al SO para que muestre los archivos. El SO lleva a cabo el mismo proceso para construir una lista de archivos sin importar si la solicitud viene directamente del usuario o de un programa de aplicación, (cuando la solicitud viene de una aplicación, el SO envía los resultados de su trabajo al programa de aplicación en lugar de mandarlos directamente a la pantalla de la computadora). Algunos otros servicios que el SO proporciona a los programas es guardar archivos en el disco, leerlos de disco a memoria, revisar espacio disponible en disco y en memoria, ubicar memoria para guardar información de un programa, leer la captura en el teclado y desplegar caracteres o gráficos en la pantalla. Cuando los programadores escriben programas de computadora, incluyen en sus programas instrucciones que solicitan los servicios del SO. Estas instrucciones son conocidas
SOPORTE LÓGICO DE UN COMPUTADOR
63
como llamadas del sistema debido a que el programa tiene que llamar al SO, para conseguir alguna información o servicios. La mayor parte del tiempo en que un ordenador está funcionando, la demanda de recursos es mayor que los que realmente existen. Para resolver este problema los SO disponen de una política de administración, a lo largo del tiempo y según las demandas, de estos recursos. El planificador es el responsable de llevar a la práctica esta política. El mecanismo sería muy sencillo si fuera posible utilizar una política directa, del tipo “se atenderá primero al que antes lo solicite”. Esta política puede llevar a situaciones de bloqueo. Esto sucede cuando dos programas quedan bloqueados al solicitar insistentemente recursos que están asignados al otro, como ocurre cuando dos vehículos pesados intentan atravesar simultáneamente un puente estrecho. El planificador se ocupa fundamentalmente de asignar tiempo del procesador a los programas de acuerdo a una cierta política, que varía notablemente de un SO a otro. Cuando distintos programas se ejecutan simultáneamente, es necesario protegerlos entre sí, incluido del propio SO. Esta protección debe ocurrir especialmente, frente a errores y al abuso deliberado de los recursos del sistema. Aunque es imposible para el SO prever los errores de los programas de aplicación, es esencial detectarlos y diagnosticarlos lo antes posible para limitar sus efectos. Un abuso deliberado del sistema es más difícil de resolver. La protección de la memoria principal es el aspecto más importante de la seguridad de un ordenador, ya que interviene en todos los procesos. El módulo de gestión de la memoria asigna a cada tarea una parte de la memoria principal del ordenador y a continuación asigna a cada una de estas porciones un grado de protección según la naturaleza de las tareas que tiene encomendadas. Se realizan comprobaciones reiteradas para asegurar que no se producen violaciones de la memoria. La forma más común de protección contra la violación de memoria se da cuando un programa trata de leer o escribir datos fuera del área de memoria que tiene asignada. 40608" OłFWNQU"RCTC"NC"IGUVKłP"FG"TGFGU Después de haber indicado la importancia de las redes de computadores, no podemos terminar esta descripción de las funciones de un SO, sin indicar las nuevas demandas a las que tienen que hacer frente éstos, para gestionar redes de ordenadores. La primera consideración a tener en cuenta, es la diversidad de topologías que se pueden dar en una red (en anillo, en bus, irregular, etc.), a las cuales tienen que adaptarse cada uno de los SO que gestiona cada una de las máquinas (Ver sección 2.5). Para que una red pueda operar, los protocolos deben ser gestionados debidamente y cada uno de estos SO debe estar en condiciones de
64
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
trabajar con el soporte lógico de las redes de computadores (que trataremos con más detalle en un apartado posterior de éste mismo capítulo). Otras funciones que en este escenario de red debe desarrollar el SO son: controlar la seguridad de las transacciones y evitar accesos no autorizados o vandálicos (introducción de virus en la red, bloqueo intencionado de la misma, etc.) 40609" GLGORNQU"FG"UKUVGOCU"QRGTCVKXQU 2.4.7.1" MS DOS El MSDOS (MicroSoft Disk Operating System) es un SO, diseñado inicialmente para un único usuario para los ordenadores que utilizan la familia de microprocesadores Intel 8086 -286-386-486 y Pentium. Fue desarrollado inicialmente en 1979 por Tim Paterson, que trabajaba en Seattle Computer Products. Siendo adquirido y terminado por Microsoft Corporation (con motivo de la aparición del IBM-PC) que es quien lo distribuye y ha realizado sucesivas extensiones, con el objetivo de adaptarlo a las capacidades de los nuevos ordenadores. CARACTERÍSTICAS GENERALES El espíritu del MSDOS es el de proporcionar una base para el software de un sistema, capaz de controlar todos los aspectos de la operación de un PC, particularmente el sistema de gestión de ficheros en disco, la transferencia de datos entre periféricos y la carga y ejecución de programas. El MSDOS dispone de un procesador de órdenes que llama al sistema de entrada/salida (E/S) y a las utilidades del sistema. El sistema de (E/S) tiene tres niveles: el sistema de gestión de ficheros, el sistema básico de (E/S) (BIOS) y las rutinas firmware de E/S, mientras que las operaciones a nivel de detalle, especialmente las transformaciones entre estructuras físicas y lógicas, corren a cargo del BIOS. El sistema de E/S dispone de dos formas de comunicación, una para las unidades de disco, que constituye el sistema de gestión de ficheros, hacia las que se transfieren los datos en bloques y otra para los periféricos hacia los que se transfieren los datos carácter a carácter. PROCESADOR DE ÓRDENES El procesador de órdenes tiene cuatro funciones: actuar de interfaz con el usuario, gestionar el sistema de interrupciones, tratar los errores y ejecutar las órdenes internas del MSDOS. Este procesador transfiere el control al sistema de E/S, a una de las utilidades del MSDOS o, a través del sistema de gestión de ficheros, a un programa, según los casos.
SOPORTE LÓGICO DE UN COMPUTADOR
65
El interfaz con el usuario es un conjunto de mensajes emitidos por el ordenador bien en respuesta a órdenes del usuario, bien de forma autónoma (prompt). El MSDOS dispone de facilidades de edición que permiten a los usuarios corregir las órdenes a medida que los van tecleando. También dispone de un editor de líneas para crear ficheros batch, los cuales contienen varias órdenes del propio SO que se van ejecutando secuencialmente una a una al llamar al fichero batch. Como ya hemos dicho todas estas características se han visto superadas con el advenimiento de las distintas versiones de Windows. El sistema de interrupciones dispone de una jerarquía sencilla de prioridades para tratar las interrupciones ocasionadas por los periféricos. Cuando el tratamiento de una interrupción termina, se devuelve el control al programa que se estaba ejecutando cuando sucedió la interrupción. El sistema de tratamiento de errores funciona de una forma muy similar, devolviendo el control al programa en que se produjo el error si ello es posible, y si no al MSDOS. Varias órdenes del MSDOS dependen directamente del procesador de órdenes. Estas son las órdenes que permiten conocer el directorio de un disco, borrar, cambiar el nombre y copiar ficheros. El MSDOS mantiene un control de la hora y el día, pudiéndose ajustar ambos mediante órdenes. GESTIÓN DEL SISTEMA DE ALMACENAMIENTO MASIVO La tarea más importante que desarrolla el MSDOS es la de controlar el sistema de gestión de ficheros del ordenador. Cada disco dispone de un directorio, que contiene los detalles de todos los ficheros del disco, así como los nombres de los subdirectorios. De esta forma los directorios constituyen una estructura jerárquica, en forma de árbol. En cualquier momento el usuario está “conectado” a un determinado directorio (el directorio por defecto), y , a menos que se especifique otra cosa, todos los ficheros se buscan o crean en ese directorio. Los nombres de los ficheros están compuestos por un identificativo seguido de una extensión de fichero de tres letras, que sirve para agrupar a los ficheros de un mismo tipo. Para localizar un fichero fuera del directorio por defecto hay que utilizar un pathname, que contiene los nombres de todos los directorios por los que hay que pasar para llegar al fichero en cuestión. Por ejemplo, el pathname que conduce a un fichero de nombre PBA850.TXT (en este caso la extensión sería TXT), situado en el subdirectorio CARTAS del directorio ADMIN del disco B, sería: B:\ADMIN\CARTAS\PBA850.TXT El sistema de gestión de ficheros controla esta estructura jerárquica. Dispone de facilidades para crear un fichero, abrir uno para lectura o escritura, transferir
66
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
información entre dos ficheros en el sentido que se indique y para informar del espacio disponible. Las transferencias tienen lugar sector a sector (un sector está compuesto por un bloque de 512 bytes), pudiéndose acceder a los registros de los ficheros de forma secuencial o aleatoria. El sistema de gestión de ficheros se encarga de los detalles relativos a la carga y ejecución de los programas de usuario y trata la mayoría de las llamadas que éstos hacen al SO. El espacio en disco se asigna tomando el siguiente sector disponible. Cuando un fichero se borra o se reduce su longitud, se liberan los sectores que no se necesitan. No existe un mecanismo de agrupación, y algunos ficheros, en particular, los que se usan para tratamiento de textos y que cambian cada vez que se editan, pueden fragmentarse notablemente. Si un fichero está muy disperso entre los sectores del disco, el acceso al fichero para lectura o escritura puede ser muy lento. ENTRADA/SALIDA ORIENTADA AL CARÁCTER Con la excepción de las unidades de disco, todos los datos se envían a todos los dispositivos periféricos carácter a carácter. Estos incluyen el teclado, la pantalla, la impresora y las líneas de comunicaciones. El MSDOS dispone de un conjunto de controladores de dispositivos, uno para cada uno de los dispositivos anteriores, que presentan un interfaz estándar para el programador. De esta forma todos los dispositivos parecen tener las mismas características a los ojos del programador. UTILIDADES Las utilidades del MSDOS, van creciendo a medida que aparecen nuevas versiones. Entre las utilidades existentes, se incluyen: formatear, copiar y verificar el estado de los discos, recuperar ficheros perdidos, conexión a red, trabajo en grupo, etc. Además se cuenta con: un editor de líneas, un depurador para detectar errores en los programas en ensamblador y un montador/enlazador para generar módulos ejecutables a partir de código relocalizable. Existe también un programa de ordenación para disponer las líneas de un fichero de texto en orden alfabético. NUEVAS CARACTERÍSTICAS La limitaciones del MSDOS, frente a la creciente potencia de los nuevos PC, han propiciado la aparición de aplicaciones tales como Windows que corren bajo MSDOS que permite a los usuarios interactuar con varios programas a la vez. Este software, crea una ventana en la pantalla para cada programa que está activo. Utilizando un ratón el usuario mueve el puntero a una ventana particular para interactuar con el programa que se está ejecutando en ella. A su vez las ventanas pueden disponerse en la pantalla y dimensionarse en la forma en que se desee. Los programas que se ejecutan bajo MS Windows disponen de interfaces de usuario
SOPORTE LÓGICO DE UN COMPUTADOR
67
estándar y utilizan menús desplegables. Los datos pueden transferirse de un programa a otro, por ejemplo, una porción de una hoja de cálculo puede incluirse en un documento de un procesador de texto. La evolución de estas aplicaciones está dando lugar a nuevo sistemas operativos, de tipo gráfico (Windows NT, Windows 95) totalmente diferentes de MSDOS. Estos nuevos sistemas operativos tratan de superar las carencias derivadas de los orígenes del MSDOS que impiden aprovechar todo el potencial de los nuevos procesadores. 2.4.7.2" UNIX El SO UNIX fue diseñado en los Bell Laboratories de la American Telephone and Telegraph Corporation (AT&T) que todavía siguen dándole soporte. La primera versión entró en funcionamiento en 1971. El UNIX se ha actualizado en varias ocasiones desde entonces y en este proceso de modificación se ha adaptado para usarse en una gran variedad de máquinas. Su implementación en el Cray-2 fue un espaldarazo notable, pues se demostró que podía ser un SO apropiado para un gran rango de sistemas, desde el PC hasta el superordenador. El UNIX combina las características de simplicidad, facilidad de uso y potencia con un pequeño tamaño y una considerable flexibilidad. En un SO con un núcleo pequeño que cuenta con un amplio grado de aceptación, especialmente entre los usuarios experimentados. Aunque tiene ya varios años, su popularidad sigue en aumento. La característica más importante del UNIX es que se ha convertido en un estándar, proporcionando una plataforma estándar, para desarrollar un gran número de aplicaciones y asegura, en la medida de lo posible, la transportabilidad del software entre sistemas UNIX sobre distinto hardware. CARACTERÍSTICAS GENERALES El UNIX es un SO de propósito general, multiusuario y multiproceso. Soporta multiprogramación y multiacceso. Dispone de un ensamblador, varios compiladores para lenguajes de alto nivel y de un editor de texto, entre otros elementos. El UNIX está pensado para que varios usuarios puedan acceder a un único procesador a través de terminales. Ningún usuario recibe la consideración de operador del ordenador. Todos los usuarios pueden enviar órdenes al SO durante su trabajo y el SO va respondiendo a estas órdenes. El UNIX implementa el concepto de memoria virtual, cada usuario parece tener acceso a la totalidad de la memoria del ordenador. CONTROL DE LA ENTRADA/SALIDA
68
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Cada dispositivo periférico del sistema está controlado mediante uno o más ficheros. Estos ficheros se tratan de la misma forma que los ficheros de datos ordinarios. En otras palabras, para mostrar un carácter en la pantalla, el carácter se escribe en el fichero asociado a ésta. Esto simplifica la entrada/salida y oculta el usuario las peculiaridades de los distintos dispositivos. GESTIÓN DE LOS DISPOSITIVOS DE ALMACENAMIENTO MASIVO Todo el volumen de almacenamiento masivo disponible se divide en ficheros. El contenido de un fichero puede estructurarse de acuerdo a los deseos del usuario, pero las relaciones entre ficheros están controladas a nivel de sistema mediante directorios. Los directorios permiten agrupar los ficheros y estructurar los métodos para dar acceso a ciertos ficheros a los distintos usuarios. El SO mantiene un directorio raíz, a través del cual se llega a todos los demás ficheros del sistema. El sistema de ficheros se considera una de las características más importantes de UNIX. PROTECCIÓN La protección dentro del sistema se logra mediante bits asociados a cada uno de los ficheros. Estos permiten otorgar permisos de lectura, escritura y ejecución, tanto para el propietario del fichero como para los demás usuarios. Diferentes usuarios pueden tener diferentes niveles de privilegios, que determinan la extensión con la que pueden acceder a los ficheros y por tanto utilizar los recursos del ordenador. El sistema dispone de mecanismos de tratamiento de errores, de modo que si se detecta un error durante la ejecución de un programa el SO salta a una subrutina de tratamiento de errores. Además el usuario dispone de la facilidad de interrumpir un programa cuya ejecución no parezca correcta. INTERFAZ CON EL USUARIO Todos las órdenes del SO son interpretados por un programa denominado shell (concha, para dar la idea que es el caparazón o la parte visible del sistema). Una característica notable del shell es que permite al usuario controlar la extensión del funcionamiento en multitarea del ordenador. En circunstancias normales el SO completa la ejecución de una orden antes de indicar al usuario que puede introducir otro. Sin embargo, un usuario puede pedir al SO que comience a trabajar con una orden a la vez que queda dispuesto para aceptar otro. Las acciones derivadas de ambas órdenes se ejecutan juntas en multiprogramación. En UNIX también se han producido importantes avances en materia de GUI (XWindows) y además, con la integración de la red en el SO se han desarrollado
SOPORTE LÓGICO DE UN COMPUTADOR
69
múltiples aplicaciones relacionadas con la distribución de información (WWW = World Wide Web) que sean accesibles desde aplicaciones que se ejecutan en otros ordenadores con otros SO (p.e. desde PC bajo MSDOS) En la actualidad hay UNIX para PC cuyo código y manuales pueden obtenerse por red gratuitamente al ser software de dominio público. Quizá el más popular es el LINUX. 2.4.7.3" SISTEMAS OPERATIVOS PARA MAINFRAMES Las grandes máquinas, han venido históricamente usando sistemas operativos especiales para cada una de ellas, sin embargo ésto está cambiando afortunadamente. Así, en la actualidad la mayoría incorporan el concepto de máquina virtual y podemos describir algún SO suficientemente representativo de los que utilizan este tipo de ordenador. Un ejemplo característico es el VME/B, que se diseñó y desarrolló a la vez que los ordenadores para los que está pensado (La serie 2900 de ICL). CARACTERÍSTICAS GENERALES El VME/B es un SO de propósito general muy sofisticado que incorpora varias características modernas. Soporta multiacceso, batch y tratamiento de transacciones. Uno de sus primeros objetivos es el de proporcionar un interfaz de alto nivel simple y coherente para todos los usuarios. Es flexible, en el sentido de que puede ser adaptado a los requisitos de una instalación particular. El VME/B está escrito en un lenguaje de alto nivel especialmente diseñado para ese propósito. Las comunicaciones con el VME/B se realizan mediante un lenguaje de control del sistema (SCL) que está estructurado como un lenguaje de alto nivel. La máquina virtual disponible para cada usuario incluye sus programas y sus datos, así como todas las facilidades del SO y las utilidades que necesite el usuario. Para un usuario la máquina virtual aparece como una entidad totalmente autocontenida, aunque el VME/B permite compartir determinadas áreas de datos y código para evitar la duplicación del software. Se utiliza un sistema de protección muy sofisticado para garantizar la seguridad de cada una de las máquinas virtuales y de los segmentos compartidos. ESTRUCTURA DEL SISTEMA Como muchos otros SO, el VME/B está constituido por una serie de capas. En el nivel más interno se sitúa el núcleo, que es el encargado de transformar el hardware del ordenador en un conjunto de máquinas virtuales. La siguiente capa está constituida por el software del sistema, que gestiona los recursos de las máquinas virtuales. La capa más externa contiene los programas de aplicación a
70
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
diferencia de otros SO más antiguos, el VME/B no presenta una separación rígida entre el software del sistema y los programas de aplicación. SISTEMA DE GESTIÓN DE MEMORIA Y COMUNICACIONES Cada una de las máquinas virtuales que corren bajo VME/B dispone de un área de almacenamiento virtual mucho mayor que la memoria principal del ordenador. El VME/B controla todas las transferencias de segmentos de programas y datos desde y hacia los medios de almacenamiento masivo para que el sistema de memoria virtual sea una realidad. Uno de sus objetivos generales es el proporcionar un eficiente servicio de comunicaciones. El aspecto hardware del problema se resuelve utilizando una serie de controladores de entrada/salida que enlazan los dispositivos periféricos con la memoria principal. El aspecto software está controlado por el VME/B , que controla y planifica todas las entradas/salidas. Así se libera a los usuarios de los problemas que plantean las comunicaciones y se hace un mejor uso de los recursos del ordenador. GESTIÓN DEL ALMACENAMIENTO MASIVO Todos los aspectos relativos al uso y la seguridad de los ficheros corren a cargo de un sistema de almacenamiento integrado que forma parte del VME/B. El usuario puede tomar la decisión de hacerse responsable del control del estado de sus ficheros en el sistema de almacenamiento masivo o dejar esta tarea bajo el control del SO. PROTECCIÓN Uno de los aspectos principales del VME/B es su sistema de protección que está basado en el concepto de niveles de privilegio. Cada proceso que se ejecuta en el sistema tiene asociado un nivel de privilegio, de 0 a 15, siendo el nivel 0 el de mayor privilegio y el nivel 15 el más bajo. Los niveles 0 a 2 están reservados para el núcleo; los niveles 3 a 9, para el software del sistema, y los niveles 10 a 15, para los programas de aplicación. Cuando un proceso se está ejecutando, un registro, denominado registro de control de acceso, contiene su nivel de privilegio. Cada uno de los segmentos de datos a los que el proceso pueda acceder tiene una clave de acceso para lectura y una clave de acceso para escritura. Cada clave tiene un valor entre 0 y 15. Sólo se permite el acceso si el nivel de privilegio del registro de control de acceso es menor o igual al valor de la clave. Por ejemplo, un proceso con nivel de privilegio 11 puede leer de un segmento con clave de acceso 14, pero no puede escribir en un segmento con clave de acceso 8. Además cada segmento dispone de un bit de autorización de
SOPORTE LÓGICO DE UN COMPUTADOR
71
ejecución. Sólo si éste tiene el valor 1 se puede ejecutar el código contenido en ese segmento.
4070" UQRQTVG"NłIKEQ"FG"NCU"TGFGU"FG"EQORWVCFQTGU Una vez enfatizados los módulos y programas del soporte lógico de un computador, que facilitan el proceso de programación, en este epígrafe trataremos la parte del soporte lógico que concierne al uso de las redes de computadores, ya que éstas, en la actualidad, han revolucionado el uso de la tecnología computacional. Aplicaciones que dependían de un sistema centralizado con una macrocomputadora y una serie de varias terminales, en la actualidad usan redes, en las cuales cada usuario cuenta con un ordenador propio. 40703" UQRQTVG"NłIKEQ"DıUKEQ 2.5.1.1" PAQUETES De la misma forma que cuando alguien escribe una carta, envía el texto metiéndolo en un sobre, generalmente de tamaño estándar. Un principio similar se usa en muchos sistemas de comunicación. Los datos se transmiten, no carácter a carácter, ni en grupos de caracteres, sino en paquetes. Un paquete es un conjunto de datos para transmitir, que se incluyen en una entidad mayor, en la que se encuentran datos para el correcto enrutamiento del paquete por la red de comunicaciones sobre la que se mueve, así como mecanismos (bastante sofisticados por cierto) para la detección y corrección de errores. Los datos que se incluyen en los paquetes siguen unas normas estrictas, ya que todos los dispositivos de una red envían y reciben los datos, utilizando paquetes del mismo tipo. Los paquetes se han transformado en la unidad de transmisión y recepción de datos dentro de muchas redes de comunicación de datos (denominadas redes de conmutación de paquetes). 2.5.1.2" PROTOCOLOS DE RED El tipo y topología de una red establecen su estructura básica, pero aún así, cada computadora necesita el hardware para transmitir y recibir información. El dispositivo que lleva a cabo esta función es la tarjeta de interfaz de red (NIC = network interface card). La NIC es un tipo de tarjeta de circuitos impresos, que se instala en una de las ranuras de expansión de la computadora y proporciona un puerto en la parte trasera de la PC al cual se conecta el cable de la red. La computadora también requiere del software que le indique cómo usar la NIC.
72
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Ambos, el software de la red y la NIC, se tienen que adherir al protocolo de la red, que es el conjunto de estándares para la comunicación. Un protocolo de red es como un lenguaje para la comunicación de información ya que para que dos máquinas intercambien información, deben hablar el mismo idioma. Como ocurre a menudo, los protocolos están en continuo estado de cambio, ya que cada vez que aparece un nuevo estándar, alguien inventa otro que hace el trabajo más rápido y en forma más fiable. Veamos los protocolos mas comunes (cada uno para cierta topología de red y con ciertas características estándar): 2.5.1.2.1" Ethernet Ethernet es actualmente el protocolo de red más común, que al basarse en la topología de bus lineal resulta de bajo costo y relativamente simple. Sin embargo, con un bus lineal, cada nodo de la red debe tomar su turno para enviar información y al igual que en una línea telefónica, que solo una persona puede usarla a la vez, cada vez que una máquina necesite enviar información a otra, primero deberá revisar si la red está disponible. Si la red está siendo utilizada por otra estación, espera brevemente y lo intenta de nuevo; como se adivina, cuando haya muchas computadoras en una red Ethernet, el tiempo de acceso se hace notoriamente lento. Afortunadamente existen configuraciones que permiten utilizar el Ethernet en bus lineal lógico en lugar de uno lineal físico, llamado 10 base-T, que utiliza un equipo que proporciona las ventajas de una topología en estrella centralizada con la flexibilidad y capacidad de un bus lineal. En cualquier caso los buses de la red Ethernet están limitados a 2 500 pies (762 metros) de distancia y corren a 10 Mbits por segundo. 2.5.1.2.2" Token Ring El protocolo de red Token Ring, como indica su nombre, se basa en la topología de anillo. Su característica consiste en que tiene un hardware de control que transmite direcciones electrónicas, por la red, muchas veces por segundo. Cada nodo examina estas direcciones para determinar si alguna le pertenece. En caso negativo, las direcciones siguen viajando por la red sin alteración. Si la dirección concuerda, el nodo puede anexarle un paquete de información y hacerlo llegar a otro nodo de la red y, a la inversa un ordenador de la red puede leer el contenido de un paquete relacionado con su dirección. Este proceso de hacer circular un grupo de paquetes dirigidos a cada estación para cogerlos o pasarlos, simula la acción de pasar una señal (token), de ahí el nombre de Token Ring.
SOPORTE LÓGICO DE UN COMPUTADOR
73
Las redes Token-Ting, aunque caras, tienen la ventaja de que la información viaja de una manera controlada a través del anillo en una dirección. Con este enfoque, la información no puede chocar y la red puede operar a mayores velocidades (probablemente pronto veamos velocidades de hasta 100 Mbits por segundo). 2.5.1.2.3" ARCNET La ARCNET se basa en la topología de estrella o estrella distribuida, con una topología y protocolo propios, utiliza cable coaxial y la estrella es perpetuada mediante el uso de paneles de control conectados a la red. La ARCNET es mas lenta, cerca de 2.5 Mbits por segundo, aunque barata, confiable y fácil de instalar y expandir. 2.5.1.3" MODELO DE CAPAS ISO Una de los metas principales para la interconexión de distintos equipos informáticos (diversos fabricantes, modelos, etc.) es conseguir la utilización de sistemas y procedimientos de intercambio de información comunes. En definitiva, el primer problema es establecer los interfaces y protocolos de interconexión, para ser fijados existen asociaciones internacionales especializadas como la ISO (International Standard Organization). Esta ha establecido una arquitectura para los sistemas de comunicación (Modelo ISO), que utiliza la sistemática de considerar el problema de análisis y diseño de redes de computadoras por capas (o niveles). Así para la interconexión de dos equipos informáticos, A y B, se definen 7 niveles, que se encargan de llevar a cabo la transmisión (Ver Figura 2.6). Cada nivel “proporciona” o “se relaciona” con el nivel superior por medio de interfaces, y las relaciones entre los dos equipos en cada nivel se efectúan a través de protocolos. En esencia, en cada capa o nivel i: a)
se diseña el protocolo nivel i (conexión Ai, Bi), utilizando las interfaces proporcionadas por el nivel inferior (i-1).(Niveles Ai - 1 y Bi - 1);
b)
se diseña la interfaz de nivel i como servicio para el nivel i+1 (relaciones Ai con Ai + 1, y Bi con Bi + 1), utilizando las interfaces proporcionadas por el nivel inferior (i-1) (Niveles Ai - 1 y Bi - 1).
74
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Fig.2.6. Esquema de protocolos en el Modelo de capas ISO para la interconexión de dos sistemas A y B. En la Figura 2.6, puede verse un esquema simplificado del Modelo de referencia ISO En los extremos figuran los sistemas entre los que se requiere efectuar la conexión (Sistemas A y B). En el centro figuran otros elementos que intervienen sólo con objeto de efectuar la transmisión (fundamentalmente equipos terminales de las líneas de comunicaciones e interfaces de procesadores de comunicaciones). Las capas o niveles conceptuales del modelo ISO son las siguientes: Nivel 1: CAPA FÍSICA. En este nivel se especifican los parámetros mecánicos, eléctricos, etc., de la interconexión entre el equipo terminal de datos y el equipo terminal de la línea de comunicaciones. Las unidades de información son bits y su misión es asegurar que si un emisor envía un 1 al receptor le llega un 1. Evidentemente, su relación con el soporte lógico del ordenador, no es muy relevante. Nivel 2. CAPA DE ENLACE DE DATOS. Es la capa, encargada de transmitir sin errores, ya no bits, sino bloques de información (llamadas tramas) entre dos puntos físicos de la red. Para realizar esto, distribuye las cadenas de bits proporcionadas por el nivel 1 en tramas de datos, emitiéndolas secuencialmente y comprobando si hay errores en la transmisión. Nivel 3. CAPA DE RED. Este nivel controla las operaciones en la red de transmisión. Los elementos de información que trata son paquetes, que están
SOPORTE LÓGICO DE UN COMPUTADOR
75
compuestos de tramas. Su responsabilidad por tanto es la de planificar el trayecto que deben seguir los paquetes en el interior de la red y de controlar la conmutación de los circuitos. Nivel 4. CAPA DE TRANSPORTE. Se encarga del transporte eficiente de la información, desde la fuente el destino, a través de la red de conmutación de paquetes. En este nivel, al igual que los de mayor nivel, las interacciones (protocolos) se realizan entre los equipos informáticos fuente y destino, frente a los protocolos de los niveles 1, 2 y 3 que se realizan entre equipos de nodos vecinos, sean o no fuente o destino. Las unidades de información que considera son mensajes , que están constituidos por paquetes. Nivel 5. CAPA DE SESION. Cada vez que se desea efectuar una comunicación entre dos sistemas o usuarios, debe establecerse una sesión de comunicaciones entre ambos. Este nivel es el responsable de la realización y control del diálogo entre procesos de distintos nudos: establecer la comunicación, sincronizar los diálogos, cerrar la sesión, etc. Para establecer una sesión el usuario debe proporcionar una dirección del equipo remoto al cual pretende conectarse (dirección de sesión). Este nivel, se encarga de transformar las direcciones de sesiones en direcciones de transporte, facilitándolas al nivel 4. Nivel 6. CAPA DE PRESENTACIÓN. Es el nivel responsable de la forma (o formato) de la estructura de datos intercambiados entre los procesos que dialogan. Se encarga de: interpretar las estructuras de las informaciones intercambiadas por los procesos de la aplicación, gestionar las terminales, transferir los archivos, comprimir los datos y en su caso de las transformaciones criptográficas que hubiera. Esta capa trata de homogeneizar los formatos de intercambio de información entre equipos de la red al objeto de facilitar las tareas de la capa de sesión. Nivel 7. CAPA DE APLICACIÓN. Este nivel es el que está en contacto con el usuario de toda la red, únicamente “ve” o “se las entiende” con esta capa. Los usuarios utilizan aplicaciones informáticas , las cuales al ejecutarse , quedan constituidas por conjuntos de tareas que requieren determinados recursos. Tanto estas tareas como los recursos , se hallan distribuidas a lo largo y ancho de la red. El nivel de aplicación se encarga de las funciones específicas de intercambio y cooperación tanto entre los procesos como entre los recursos , que utilizan la aplicación. 40704" TGNCEKQPGU"GP"WPC"TGF Al describir una red como LAN (Local Area Network) o WAN (Wide Area Network) se define el área geográfica que la red cubre. El segundo escalón es
76
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
describir cómo las computadoras individuales ó nodos, pueden interactuar con otras computadoras en la red , esto es de que manera están organizadas. 2.5.2.1" LA RELACIÓN CLIENTE-SERVIDOR Una manera de organizar redes es llamada cliente-servidor, una estrategia jerárquica en la cual una computadora en particular sirve a las necesidades de almacenamiento, y algunas veces a las necesidades de proceso, de todos los nodos de la red. El tipo más común de instalación de cliente-servidor es una LAN, compuesta de PC o estaciones de trabajo conectadas a un servidor de red, el cual puede o no, también ser usado como el dispositivo de almacenamiento principal de la red. Un programa cliente corriendo en uno de los nodos puede requerir información específica del servidor. El programa del servidor puede traer la información solicitada de sus bases de datos y pasársela al cliente. 2.5.2.2" COMPUTACIÓN PAR A PAR Otra distribución es la computación par a par, una estrategia de red en la cual las computadoras pueden actuar como cliente y servidores a la vez. En otras palabras, cada nodo tiene acceso a todos o a algunos de los recursos de los otros nodos. Por ejemplo, con la versión de Windows para trabajo en grupo, los usuarios tienen acceso a los discos duros e impresoras conectados a otras computadoras del grupo de trabajo. Una LAN par a par permite a los usuarios compartir periféricos, incluyendo el disco de almacenamiento, de tal forma que pueden tener acceso a la misma información y a los mismos programas. Algunas redes par a par, de alto rendimiento, tales como las redes de computadoras Unix, permiten la computación distribuida; esto es, permite que el usuario de una máquina tenga a su disposición la capacidad de proceso de otras computadoras de la red. Eso significa que se puede transferir aquellas tareas que requieran muchos recursos de CPU, a computadoras que en este momento estén disponibles, liberando a su propia máquina. 40705" RTQITCOCU"FG"CRNKECEKQPGU"FG"NCU"EQOWPKECEKQPGU Desde hace años los usuarios han reconocido el valor de intercambiar datos y software. La respuesta a esta demanda vino dada por Hayes Microcomputer Products, quien desarrolló en 1978, el Smartmodem, el primer módem para computadoras personales. Con él se introdujo una nueva industria que actualmente
SOPORTE LÓGICO DE UN COMPUTADOR
77
proporciona toda clase de servicios para ordenadores con módem, cosa que ha hecho que su soporte lógico se tuviera que adaptar a estas nuevas posibilidades. Correo electrónico.Una de las aplicaciones de mayor alcance en las comunicaciones de datos es el correo electrónico (e-mail), un sistema para intercambiar mensajes escritos (y crecientemente, mensajes de voz) a través de una red. E-mail es algo así como una mezcla entre el sistema postal y un contestador telefónico, donde cada usuario tiene una dirección única. Para enviar un mensaje e-mail a alguien, se teclea la dirección del destinatario y después el mensaje. Cuando se termina, el mensaje se envía a la dirección. Cuando aquél acceda al sistema e-mail, se le informa que le ha llegado correo. Después de leer el mensaje, el destinatario puede guardarlo, borrarlo, hacerlo llegar a alguien más o responderlo enviando un mensaje de respuesta. Además de enviar texto, muchos sistemas permiten anexar información como hojas de cálculo o documentos como complemento al mensaje. Una red local puede tener conexión a las grandes redes de información, lo que proporciona al usuario de red acceso por correo a literalmente millones de usuarios e-mail en todo el mundo. El correo electrónico es eficiente y de bajo costo. Los usuarios pueden enviar mensajes escritos sin preocuparse de que el destinatario esté o no usando la computadora en ese momento. A través de las redes centralizadas, el mensaje es remitido casi instantáneamente y además, una vez que se ha instalado la red, el correo electrónico es muy barato. Computadoras de acceso remoto.Uno de los grandes beneficios de un módem es que permite acceder a computadoras remotas desde cualquier teléfono, no importa donde se encuentre. Al marcar con un módem el número de la red se puede obtener información, acceder a archivos e intercambiar mensajes por correo electrónico. Con una ordenador y un módem, todo esto es posible y el ordenador debe estar preparado para esta nueva situación, a fin de obtener las ventajas y evitar los inconvenientes que pueden darse. Transferencia de archivos.Por transferencia de archivos entenderemos, simplemente, enviar un archivo de una computadora a otra y es la aplicación mas frecuente de los módem. Para que un archivo pueda ser transferido de una computadora a otra, ambas deben utilizar el mismo protocolo de transferencia de archivos. Como sus homólogos de redes, los protocolos de transferencia de archivos deben ser acordados previamente (por ejemplo Kermit, Xmodem etc.). Una de sus funciones de más importancia es
78
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
revisar los errores mientras se está enviando el archivo. Normalmente, la comunicación del módem es bidireccional, lo cual significa que la computadora que recibe puede responder a la que envía para asegurarse de que la información que recibe no contiene errores. Si existe algún error, la computadora que envía la información, transmite de nuevo, cualquier parte que sea incorrecta. Internet.Internet es una enorme red de redes que se enlaza a muchas de las redes científicas, y de investigación alrededor del mundo, así como a un número creciente de redes comerciales. La Internet, a menudo llamada “ la red” (“the net”), fue iniciada en 1969 por el Departamento de Defensa de EEUU y creció gradualmente hasta convertirse, en una red mundial para investigación científica. Ahora es mucho más que eso. La mayoría de la universidades están conectadas a la Internet, así como muchas compañías y la mayoría de compañías que proporcionan servicios de información en línea. Es difícil definir a la Internet, de hecho es como un gran servicio de información, que ofrece correo electrónico, boletines electrónicos y servicios de acceso de información que ponen a disposición del usuario directorios de archivos y bases de datos a nivel mundial. Se puede decir que la Internet es la más poderosa herramienta de investigación y búsqueda jamás creada y está creciendo a pasos agigantados. Por ello no hay que extrañarse, que los nuevos soporte lógicos para ordenadores, como Windows 95, incluyan ya el acceso a Internet como una de las utilidades incluidas en sus características.
SOPORTE LÓGICO DE UN COMPUTADOR
79
2.1. CONCEPTO DE SOPORTE LÓGICO .......................................................45 2.2. AYUDAS PARA LA PROGRAMACIÓN ...................................................47
2.2.1 TRADUCTORES...........................................................................................47 2.2.2 TIPOS DE LENGUAJES DE ALTO NIVEL................................................52 2.2.3 UTILIDADES Y FASES EN LA EJECUCIÓN DE UN PROGRAMA .......53 2.3. PROGRAMA DE ARRANQUE ...................................................................55
2.4. SISTEMAS OPERATIVOS (SO) .................................................................56
2.4.1 FUNCIONES DE LOS SISTEMAS OPERATIVOS. ...................................57 2.4.2 LA ESTRUCTURA DE UN SISTEMA OPERATIVO TÍPICO...................58 2.4.3 ADMINISTRACIÓN DEL HARDWARE ....................................................60 2.4.4 ADMINISTRACIÓN DEL SISTEMA DE ARCHIVOS...............................62 2.4.5 APOYO A LA EJECUCIÓN DE PROGRAMAS DE APLICACIÓN..........62 2.4.6 MÓDULOS PARA LA GESTIÓN DE REDES ............................................63 2.4.7 EJEMPLOS DE SISTEMAS OPERATIVOS................................................64
2.5. SOPORTE LÓGICO DE LAS REDES DE COMPUTADORES ..............71 2.5.1 SOPORTE LÓGICO BÁSICO ......................................................................71 2.5.2 RELACIONES EN UNA RED ......................................................................75 2.5.3 PROGRAMAS DE APLICACIONES DE LAS COMUNICACIONES .......76
CAPITULO 3
ALGORITMOS Y PROGRAMAS
Este capítulo trata de ser una introducción a la metodología y tecnología de la programación, con el objetivo de proporcionar al lector los procedimientos y técnicas para el desarrollo de programas. No por obvio, hay que olvidar que los programas se escriben con el ánimo de resolver problemas, con ayuda de las computadoras y que la primera medida a considerar, es el análisis del problema en cuestión y la obtención, en su caso, de un algoritmo adecuado. Por este punto empezaremos nuestra exposición, hasta llegar a los métodos y etapas a seguir para obtener una aplicación informática. Si bien los conceptos que aquí se introducen son fundamentales para la realización de programas, este capítulo no debe leerse como si se tratara de un manual de programación, sino como una fundamentación de lo que llamamos programación estructurada, mas allá de la sintaxis y de la semántica de un lenguaje de programación concreto.
5030" EQPEGRVQ"FG"CNIQTKVOQ Sabemos que para que un ordenador pueda llevar adelante una tarea cualquiera, se tiene que contar con un algoritmo que le indique, a través de un programa, que es lo que debe hacer con la mayor precisión posible. Quizás esta afirmación debería ser revisada desde la óptica de la Inteligencia Artificial, pero por el momento la mantendremos como válida dentro del carácter introductorio de este curso. Consecuencia de lo anterior es la importancia del estudio de los algoritmos dentro de las Ciencias de la Computación. Recordemos que un algoritmo es “una sucesión finita de pasos no ambiguos que se pueden ejecutar en un tiempo finito”, cuya razón de ser es la de resolver problemas; por tanto “problema” para nosotros, serán aquellas cuestiones, conceptuales o prácticas , cuya solución es expresable mediante un algoritmo. Afortunadamente, son muchos los problemas cuya solución puede describirse por medio de un algoritmo y ésta es 81
82
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
una de las razones subyacentes a la necesidad de que aprendamos a programar y a manejar un ordenador. Nótese que no es redundante el hecho de exigir que un conjunto finito de pasos o instrucciones acaben en un tiempo finito, pues una sola instrucción del tipo: “hacer acción A1 hasta que se cumpla la condición C1”, acaba dando lugar a un proceso infinito, si no llega a darse nunca la condición C1. El término ‘no ambiguo’ significa que la acción, a desarrollar en cada paso de la secuencia, viene unívocamente determinada, tanto por la instrucción como por los datos disponibles en este momento, de forma que en cada momento se sepa qué acción única, se tiene que llevar a cabo.
5040" NC" TGUQNWEKłP" FG" RTQDNGOCU" [" GN" WUQ" FGN QTFGPCFQT Antes de entrar en la codificación de la resolución de un problema, hemos de contar con una idea bastante precisa de cómo podemos llegar a esta solución. La experiencia personal de todos nosotros nos dice que la sistematización para la resolución de problemas no es fácil. Resolución de un problema Análisis del problema Fig. 3.1.
Diseño del algoritmo
Programación del algoritmo
La resolución de un problema en Informática
En esta línea, el matemático G. Poyla propuso, a finales de 1940, una metodología general para la resolución de problemas matemáticos, que ha sido adaptada para el caso en que se cuente con un ordenador como recurso. Esta sistemática, de forma muy esquematizada, se puede dividir en tres fases (Ver Figura 3.1): 1. Análisis del problema 2. Diseño del algoritmo 3. Programación del algoritmo 50403" CPıNKUKU"FGN"RTQDNGOC
ALGORITMOS Y PROGRAMAS
83
El objetivo del análisis del problema, es ayudar al programador a llegar a una cierta comprensión de la naturaleza del mismo. Este análisis supone, en particular, la superación de una serie de pasos (Ver Figura 3.2): -
Definir el problema con total precisión. Especificar los datos de partida necesarios para la resolución del mismo (especificaciones de entrada). Especificar la información que debe proporcionarse al resolverse (especificaciones de salida). Análisis del problema
Definición del problema
Especificaciones Especificaciones de entrada de salida Fig. 3.2. Análisis del problema
Ejemplo 1: Elaborar el análisis para obtener el área y la longitud de una circunferencia. 1.2.3.-
Utilizar las fórmulas del área y la circunferencia en función del radio. Las entradas de datos se reducen al dato correspondiente al radio del círculo. Dada la naturaleza del mismo y el procesamiento al cual lo someteremos, su tipo de dato debe ser un número real. Las salidas serán dos variables también reales: área y circunferencia.
La finalización de la fase de análisis del problema nos llevaría al siguiente resultado: Entradas: Salidas: Variables:
Radio del círculo (variable RADIO). Superficie del círculo (variable AREA). Circunferencia del círculo (variable CIRCUNFERENCIA). RADIO, AREA, CIRCUNFERENCIA: tipo real.
50404" FKUGÜQ"FGN"CNIQTKVOQ Diseñar un algoritmo puede ser una tarea difícil y su aprendizaje no es inmediato, ya que requiere una buena dosis de experiencia y creatividad. Hace ya 100 años, un matemático de la talla de Henri Poincare, que no sólo trabajó en temas
84
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
relacionados con la física, el álgebra y el análisis, sino también sobre la filosofía de la ciencia, trató de explicar sus experiencias personales de cómo un problema, a cuya resolución había dedicado mucho tiempo sin éxito, podía aparecer tiempo después resuelto repentinamente en su cabeza, incluso cuando se estaba dedicando a proyectos distintos. Desgraciadamente sus resultados en este empeño, distaron mucho de la brillantez de sus logros como físico y matemático. El periodo que existe entre el análisis de un problema y el diseño de su solución recibe el nombre de periodo de incubación y el proceso mental, que se da durante el mismo sigue siendo un tema de investigación para los psicólogos. Estamos por tanto en el terreno de la inspiración y la madurez mental. Seamos optimistas y pensemos que vamos a tener la capacidad de tener ideas, propias o adquiridas, para desarrollar algoritmos que nos permitan actuar ante los problemas que se nos planteen. Para diseñar algoritmos hay que tener presente los requisitos siguientes: • indicar el orden de realización de cada paso, • estar definido sin ambigüedad y • ser finito Ejemplo 2: Averiguar si un número es primo o no, suponiendo que razonamos de la siguiente forma: “Del análisis del hecho de que un número N es primo si sólo puede dividirse por sí mismo y por la unidad, un método que nos puede dar la solución sería dividir sucesivamente el número por 2, 3, 4..., etc. y, según el resultado, podríamos resolver el problema”. Un diseño del mismo sería: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
Inicio Poner X igual a 2 (X = 2, X, variable que representa a los posibles divisores de N) Dividir N por X (N/X) Si el resultado es entero, entonces N no es primo, y saltar al punto 9 (en caso contrario continuar el proceso en el siguiente punto, 5) Incrementar X en una unidad Si X es menor que N saltar al punto 3 (en caso contrario continuar el proceso en el siguiente punto, 7) Declarar N es primo; Saltar al Fin (punto 10) Declarar N no es primo Fin
Como parte del diseño de algoritmo está la selección de uno que sea razonablemente aceptable, entre todos los muchos posibles que resuelven el mismo problema (el ejemplo que acabamos de dar es claramente mejorable, pues si N no era divisible por 2 no tiene mucho sentido volverse a preguntar si lo es por 4).
ALGORITMOS Y PROGRAMAS
85
Durante el diseño es posible y aconsejable, realizar comparaciones entre algoritmos que resuelven el mismo problema. La bondad de un algoritmo puede medirse por dos factores: - El tiempo que se necesita para ejecutarlo. Para tener una idea aproximada de ello, basta con saber el número de instrucciones de cada tipo necesarias para resolver el problema. - Los recursos que se necesitan para implantarlo. Así, una vez diseñado un primer algoritmo, conviene realizar una evaluación del mismo, cuestión a veces nada banal y sobre la que volveremos en capítulos posteriores. Si se decide que éste no es eficiente será necesario o bien diseñar uno nuevo o bien optimizar el original. Optimizar un algoritmo consiste en introducir modificaciones en él, tendentes a disminuir el tiempo que necesita para resolver el problema o a reducir los recursos que utiliza. (En el ejemplo 2 el algoritmo se optimiza, si N se declara como primo cuando X supera a N/2). 3.2.2.1" Diseño Descendente o Modular Los problemas complejos se pueden resolver más eficazmente cuando se descomponen en subproblemas que sean más fáciles resolver el original. Este método se denomina divide y vencerás y consiste en convertir un problema complejo en otros más simples que, una vez resueltos, en su conjunto nos solucionen el original. Al procedimiento de descomposición de un problema en subproblemas más simples, (llamados módulos) para, a continuación, seguir dividiendo estos subproblemas en otros más simples, se le denomina diseño descendente. Las ventajas más importantes de este tipo de diseño son: 1) 2) 3)
El problema se comprende más fácilmente al dividirse en módulos o partes más simples. Adelantemos que cuando demos el salto a la programación, utilizaremos esta idea constantemente, de forma que hablaremos también de procedimientos, o subprogramas. Las modificaciones en los módulos son más fáciles, pues estamos ante algoritmos más sencillos. La comprobación del problema se puede realizar más fácilmente, al poder localizar los posibles fallos con mayor precisión.
3.2.2.2" Refinamiento por pasos Durante el diseño, entenderemos por refinamiento por pasos, la metodología por la que en un primer esbozo del algoritmo nos limitamos a señalar
86
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
o describir un reducido numero de pasos, que deberán ser expresados con mayor detalle posteriormente. Tras esta primera descripción, éstos se especifican con mayor minuciosidad, de forma más extensa y con más pasos específicos. En cada nivel de refinamiento hay que considerar dos fases: ¿Qué hace el módulo? para a continuación responder a ¿Cómo lo hace?. Como es natural, dependiendo de la complejidad del problema se necesitarán diferentes y sucesivos niveles de refinamiento antes de que pueda obtenerse un algoritmo con suficiente nivel de detalle. Así en el Ejemplo 1, del cálculo de la longitud y superficie de un círculo, a pesar de presentar un bajo nivel de complejidad, en su diseño, se puede descomponer en subproblemas más simples: 1) leer datos de entrada, 2) calcular superficie y longitud, 3) escribir resultados. El ejemplo siguiente, nos muestra el diseño de un algoritmo para un problema de carácter no numérico Ejemplo 3: Diseñar un algoritmo que responda a la pregunta: ¿Qué debo hacer para ver la película XYZ?. Un primer análisis nos conduce a un esbozo de solución, descomponiéndolo en cuatro módulos sucesivos: 1 2 3 4
ir al cine donde proyectan XYZ comprar una entrada ver la película regresar a casa
Estos cuatro pasos se pueden refinar un poco más y así este problema lo podríamos descomponer de la siguiente forma: inicio
{algoritmo para ver la película XYZ}
consultar la cartelera de cines si proyectan “XYZ” entonces ir al cine correspondiente si_no proyectan “XYZ” declarar el fracaso del objetivo y terminar acudir al cine correspondiente si hay cola entonces ponerse en ella
ALGORITMOS Y PROGRAMAS
87
mientras haya personas delante en la cola hacer avanzar en la cola preguntar si quedan entradas si hay entradas entonces comprar una entrada si_no quedan entradas declarar el fracaso del objetivo, regresar a casa y terminar
encontrar el asiento correspondiente mientras proyectan la película hacer ver la película abandonar el cine regresar a casa fin
Algunas de estas acciones son primitivas para nosotros, es decir, no es necesario descomponerlas más, como el abandonar el cine. Sin embargo hay otras acciones que son susceptibles de mayor descomposición. Este es el caso de la acción: “encontrar el asiento correspondiente” si los números de los asientos están impresos en la entrada, esta acción compuesta se resuelve con el siguiente algoritmo: inicio {algoritmo para encontrar el asiento del espectador} caminar hasta llegar a la primera fila de asientos repetir
comparar número de fila con número impreso en billete si no son iguales, entonces pasar a la siguiente fila hasta_que se localice la fila correcta
mientras número de asiento no coincida con número de billete hacer avanzar a través de la fila a la siguiente butaca sentarse en la butaca fin De esta forma, podríamos seguir hasta la descomposición de las distintas acciones en instrucciones susceptibles de ser interpretadas, directamente por el ordenador. 50405" RTQITCOCEKłP"FGN"CNIQTKVOQ Una vez que el algoritmo está diseñado y representado, se debe pasar a la fase de resolución práctica del problema con el ordenador. Esta fase se descompone a su vez en las siguientes subfases: (Ver Figura 3.3)
88
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
1. Codificación del algoritmo en un programa. 2. Ejecución del programa. 3. Comprobación del programa. Programación del algoritmo Codificación en un programa
Ejecución del programa
Comprobación del programa
Fig. 3.3 Programación del algoritmo La fase de conversión de un algoritmo en instrucciones de un lenguaje de programación, como sabemos, se denomina codificación. El código deberá estar escrito de acuerdo con la sintaxis del lenguaje de programación ya que solamente las instrucciones sintácticamente correctas pueden ser interpretadas por el computador. Nótese que durante el proceso de programación, se debe separar el diseño del algoritmo de su posterior implementación en un lenguaje de programación específico. Por ello distinguimos entre el concepto más general de programación y el más particular de codificación, que depende del lenguaje de programación utilizado. Al llegar a este punto, se supone que el lector conoce al menos uno de estos lenguajes y éste es el momento en el que tiene que mostrar sus habilidades para efectuar una codificación lo más correcta y eficiente posible. Tras la codificación del programa, éste deberá ejecutarse en un computador. El resultado de esta primera ejecución es incierto, ya que existe una alta posibilidad de que aparezcan errores, bien en la codificación bien en el propio algoritmo. Por tanto, el paso siguiente consiste en comprobar el correcto funcionamiento del programa y en asegurarse, en la medida de lo posible, de la validez de los resultados proporcionados por la máquina.
5050" TGRTGUGPVCEKłP"FG"CNIQTKVOQU Un algoritmo es algo puramente conceptual que necesita una forma de representación, bien para comunicarlo a otra persona bien para ayudar a convertirlo en un programa. De hecho, la codificación en un lenguaje de programación, es una representación muy utilizada de un algoritmo, sin embargo tiene el inconveniente de que no todas las personas conocen el lenguaje que se haya elegido. Por ello,
ALGORITMOS Y PROGRAMAS
89
existen diferentes métodos que permiten que se pueda independizar el algoritmo de su correspondiente codificación. Veamos dos de ellos: 50503" RUGWFQEQFKIQ El pseudocódigo es un lenguaje de especificación de algoritmos (no de programación) basado en un sistema notacional, con estructuras sintácticas y semánticas, similares a los lenguajes procedurales, aunque menos formales que las de éstos, por lo que no puede ser ejecutado directamente por un computador. El pseudocódigo utiliza para representar las sucesivas acciones, palabras reservadas similares a sus homónimas en los lenguajes de programación-, tales como start, end, stop, if-then-else, while-do, repeat-until, (inicio, fin, parar, si-entoncessino, mientras-hacer, repetir-hasta), etc. A lo largo de este capítulo, a medida que vayamos describiendo las estructuras de control utilizadas en los programas, iremos haciendo una lista de las instrucciones más usuales del pseudocódigo. La ventajas del uso del pseudocódigo residen en: - Su uso en la planificación de un programa; permitiendo que el programador se pueda concentrar en la lógica y en las estructuras de control y no tenga que preocuparse, por ahora de detalles acerca de las reglas sintácticas y semánticas de un lenguaje específico. Consiguientemente es más fácil de modificar, en el caso de que se descubran errores o anomalías en la lógica del algoritmo. - Aunque el pseudocódigo es independiente del lenguaje de alto nivel que vaya a utilizarse, un algoritmo expresado en pseudocódigo puede ser traducido más fácilmente a muchos de ellos. Ejemplo 4: Supongamos que tenemos un algoritmo para averiguar si un número es par, que puede ser descrito narrativamente de la siguiente forma: “Si restando consecutivamente doses del número se obtiene el numero 2, es par, si se obtiene otro valor (el 1), entonces es impar”. Este algoritmo escrito en pseudocódigo sería: leer N mientras N > 2 hacer N←N-2 si N = 2 entonces escribe “es par” sino escribe “es impar”
90
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
fin Nótese que en este ejemplo y en otros anteriores hemos utilizado dos estructuras que son muy usadas en programación: mientras-hacer y si-entoncessi_no; y que la escritura del pseudocódigo usa normalmente la indentación (sangría en el margen izquierdo) de diferentes líneas para ayudar a delimitar visualmente cada una de las estructuras utilizadas. 50504" QTICPKITCOCU Para ganar claridad expositiva se han desarrollado una serie de símbolos gráficos que permiten representar los algoritmos y que son universalmente reconocidos. Veamos algunos ejemplos:
Fig. 3.4.
Símbolos usados para confeccionar organigramas
Los organigramas o diagramas de flujo son herramientas gráficas utilizadas tanto para representar algoritmos, como en la ayuda en el diseño de programas. Están compuestos por una serie de símbolos, unidos con flechas, donde cada símbolo representa una acción distinta y las flechas el orden de realización de las acciones. Cada símbolo, por tanto, tendrá al menos una flecha que conduzca a él y una flecha que parta de él, exceptuando el comienzo y final del algoritmo. En la Figura 3.4, se muestran los símbolos utilizados habitualmente en la confección de organigramas, cuyo significado completaremos más adelante. La Figura 3.5. representa, en forma de organigrama, el algoritmo del Ejemplo 4 que ha sido expresado en pseudocódigo en la sección anterior.
ALGORITMOS Y PROGRAMAS
Fig. 3.5.
91
Organigrama del Ejemplo 4
5060" GUVTWEVWTCU"FG"EQPVTQN En el Capítulo 1, vimos los elementos básicos constitutivos de un programa: - palabras reservadas (inicio, si-entonces, etc.) - identificadores (nombres de variables, procedimientos, etc.) - caracteres especiales (coma, punto y coma, apóstrofo, etc.) - constantes - variables - expresiones - instrucciones Sin embargo como hemos visto al diseñar algoritmos para escribir un programa, además de estos elementos básicos, hemos de conocer determinadas estructuras, cuyo objetivo es controlar su ejecución y sin cuya comprensión es imposible programar. Llamaremos estructuras de control a las acciones que tienen por objeto marcar el orden de realización de los distintos pasos de un programa ó algoritmo. Cada estructura tiene un punto de entrada y uno de salida, lo que facilita la depuración de posibles errores. Estas son de tres tipos: • estructuras secuenciales
92
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
• estructuras selectivas • estructuras repetitivas y vamos a estudiarlas con un cierto detalle. El uso de las estructuras de control es una de las características de la programación estructurada que constituye la principal orientación de este texto, aunque otros lenguajes procedurales no estructurados también utilizan estas estructuras. 50603" GUVTWEVWTCU"UGEWGPEKCNGU Son aquéllas en las que una acción (instrucción) sigue a otra de acuerdo con su orden de escritura. Las tareas se suceden de tal modo que tras la salida (final) de una se efectúa la entrada (principio) en la siguiente y así sucesivamente hasta el fin del proceso. Su organigrama obedece al esquema de la Figura 3.6:
Fig. 3.6.
Esquema de una estructura secuencial
Las estructuras secuenciales se codifican de forma directa en cualquier lenguaje de programación, pues como sabemos el orden de ejecución de todo programa es precisamente, salvo orden en sentido contrario, de arriba abajo. A pesar de su simplicidad, ya sabemos, que algunos problemas se pueden resolver con la sola utilización de esta estructura, como por ejemplo el cálculo del volumen del cilindro visto en el Capítulo 1, y codificado en los cuatro lenguajes de programación. 50604" GUVTWEVWTCU"UGNGEVKXCU Como hemos tenido ocasión de comprobar, la especificación formal de algoritmos tiene utilidad real, cuando éstos requieren una descripción más complicada que una simple secuencia de instrucciones. Uno de estos casos se
ALGORITMOS Y PROGRAMAS
93
produce cuando existen varias alternativas, resultantes de la evaluación de una determinada condición, como ocurre, por ejemplo, al resolver una ecuación de segundo grado, donde el procedimiento a seguir es distinto según el discriminante sea positivo, nulo ó negativo. Las estructuras selectivas en un programa se utilizan para tomar decisiones, de ahí que se suelan denominar también estructuras de decisión o alternativas. En estas estructuras se evalúa una condición, especificada mediante expresiones lógicas, en función de cuyo resultado, se realiza una opción u otra. En una primera aproximación, para esta toma de decisiones, podemos pensar en una variable interruptor o conmutador (switch), que representa un estado y por tanto puede cambiar de valor a lo largo de la ejecución regulando el paso a una u otra parte del programa, lo que supone una bifurcación en el flujo del programa, dependiendo del valor que tome el conmutador. Los interruptores pueden tomar dos valores diferentes, frecuentemente 1 y 0, de ahí su nombre de interruptor (“encendido”/“apagado”, “abierto”/“cerrado”). En la Figura 3.7, si SW es igual a 1, se ejecuta la acción S1; y si SW es igual a 0, se ejecuta la acción S2.
Fig. 3.7.
Funcionamiento de un interruptor
Ejemplo 5: Sea un archivo formado por un conjunto de registros constituidos por dos campos, M y N. Se desea listar el campo M de los registros pares y el campo N de los registros impares. Expresar el algoritmo correspondiente en forma de organigrama (Ver Figura 3.8).
94
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Fig. 3.8.
Organigrama del Ejemplo 3
En el ejemplo SW es un interruptor que se inicializa con un valor determinado (0 en este caso) y luego se va modificando su valor alternativamente a medida que se leen los registros. De este modo, cuando SW = 0 se leerán las fichas impares y cuando SW = 1 se leerán las fichas pares. 3.4.2.1" Alternativas simples (si-entonces/if-then) La estructura alternativa más sencilla, es la llamada simple y se representa por si-entonces. Su efecto es el de ejecutar una determinada acción cuando se cumple una cierta condición y en caso contrario seguir el orden secuencial. La selección si-entonces evalúa la condición y de acuerdo con su resultado: -
Si es verdadera, entonces ejecuta una o varias acciones (S1).
ALGORITMOS Y PROGRAMAS
-
95
Si es falsa, entonces no hace nada y sigue la ejecución normal del programa, pasando a la instrucción siguiente a la finalización de la estructura selectiva. Para ello es necesario que ésta venga claramente delimitada, cosa que se hace en pseudocódigo con fin_si.
Su expresión en Pseudocódigo (para S1 compuesta de varias acciones) es: si entonces
fin_si FORTRAN
BASIC
IF logico IF (condición) S IF (condicion) GOTO etiqueta
IF expresión THEN numero linea IF expresión THEN GOTO etiqueta
RCUECN
C
kh condicion vjgp dgikp S1 S2 . Sn ....gpf
if (condicion) { S1 S2 ... Sn };
IF condición THEN S1 S2 . Sn END IF
IF condición THEN S1 S2 . Sn END IF
3.4.2.2" Alternativas dobles (si-entonces-si_no/if-then-else) La estructura anterior es muy limitada y muchas veces se necesitará una estructura que permita elegir entre dos opciones o alternativas posibles en función del cumplimiento o no de una condición, que juega el papel de un interruptor. Si la condición es verdadera, se ejecuta la acción o acciones S1, y si es falsa, se ejecuta la acción ó acciones S2, pasando en cualquier caso a la instrucción siguiente a la finalización de la estructura selectiva. El pseudocódigo correspondiente es el siguiente
96
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
si entonces
si_no
fin_si FORTRAN
BASIC
IF condición THEN acciones S1 ELSE acciones S2 END IF
IF condición THEN acciones S1 ELSE acciones S2 END IF
RCUECN
C
kh condicion vjgp dgikp acciones S1 gpf gnug dgikp acciones S2 gpf
if (condicion) { acciones S1 } else { acciones S2 }
Ejemplo 6: Informar si un estudiante ha superado o no un determinado examen consistente en 20 preguntas de igual valor y calcular su nota en caso de aprobar. leer num_correctas si num_correctas < 10 entonces escribir “no ha superado Vd. el examen” faltan ← 10 - num_correctas escribir “le faltaron” , faltan si_no nota ← num_correctas / 2 escribir “aprobó Vd. con un” , nota fin_si
ALGORITMOS Y PROGRAMAS
97
3.4.2.3" Alternativas múltiples (según_sea, en_caso_de) Con frecuencia existen más de dos elecciones posibles (por ejemplo, en la resolución de la ecuación de segundo grado hay tres casos). La estructura de alternativa múltiple evaluará una expresión que podrá tomar n valores (o grupos de valores) distintos e1, e2,....,en. Según el valor que tome la condición, se realizará una de las n acciones posibles, o lo que es lo mismo, el flujo del algoritmo seguirá un determinado camino entre los n posibles (Ver Figura 3.9).
Fig. 3.9.
Esquema de una alternativa múltiple
La estructura de decisión múltiple en pseudocódigo se puede representar de diversas formas, aunque la más simple es la siguiente (donde e1, e2, ....., en son los distintos valores (o grupos de ellos) que puede tomar la evaluación de la expresión que sirve de condición):
Ejemplo 7:
según_sea expresión (E) hacer e1: e2: . en: fin_según
Escribir un algoritmo que permita obtener las raíces reales de la ecuación de segundo grado, ax2 + bx + c. algoritmo RESOL2 inicio leer a,b,c D ← b^2-4*a*c segun_sea D hacer D0: rc← raizcua(D) x1← (-b-rc)/2a x2 ←(-b+rc)/2a escribir x1,x2 fin_segun
Para implementar esta estructura de alternativas múltiples hemos de recurrir a estructuras alternativas simples o dobles, adecuadamente enlazadas. Las estructuras de selección si-entonces y si_entonces_sino implican la selección de una de dos alternativas y utilizándolas debidamente, es posible diseñar con ellas estructuras de selección que contengan más de dos posibilidades. Una estructura si-entonces puede contener otra y así sucesivamente cualquier número de veces (se dice que las estructuras están anidadas o en cascada). El esquema es del tipo: si (condición e1) entonces
sino si (condición e2) entonces
sino si (condición e3) entonces
sino ...
fin_si fin_si fin_si 50605" GUVTWEVWTCU"TGRGVKVKXCU El computador está especialmente diseñado para aplicaciones en las que una operación o un conjunto de ellas deben repetirse muchas veces. En este sentido, definiremos bucle o lazo (loop), como un segmento de un programa cuyas instrucciones se repiten bien un número determinado de veces o mientras se cumpla una determinada condición. Es imprescindible que se establezcan mecanismos para controlar esta tarea repetitiva, ya que si éstos no existen, el bucle puede convertirse en un proceso
ALGORITMOS Y PROGRAMAS
99
infinito. Así, en el bucle representado por el organigrama de la Figura 3.10, se observa que las instrucciones incluidas en él se repiten indefinidamente. El mecanismo de control citado se establece mediante una condición que se comprueba en cada paso o iteración del bucle. En la Figura 3.11, se coloca una condición tras la lectura de la variable N (comprobar si su valor es cero), de forma que tenemos la oportunidad de que el bucle deje de ser infinito, ya que podrá interrumpirse cuando la condición sea verdadera.
Fig. 3.10
Bucle infinito
Fig. 3.11
Bucle con condición de salida
Los procesos que se repiten varias veces en un programa necesitan en muchas ocasiones contar el numero de repeticiones habidas. Una forma de hacerlo es utilizar una variable llamada contador, cuyo valor se incrementa o decrementa en una cantidad constante en cada repetición que se produzca. La Figura 3.12 presenta un diagrama de flujo para un algoritmo en el que se desea repetir 50 veces un grupo de instrucciones, que llamaremos cuerpo del bucle, donde el contador se representa con la variable CONT. La instrucción que actualiza al contador es la asignación: CONT ← CONT+1.
100
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Fig. 3.12
Ejemplo de bucle con contador positivo
El contador puede ser positivo (incrementos de uno en uno) o negativo (decrementos de uno en uno). En la Figura 3.12, el contador cuenta desde 1 a 50 y deja de repetirse cuando la variable CONT toma el valor 51 y termina el bucle. En la Figura 3.13 se muestra un algoritmo que efectúa la operación de multiplicación n x m, sumando m un número n de veces. En él, el contador se decrementa: comienza a contar en n y se va decrementando hasta llegar a cero; en ese momento se termina el bucle y se realiza la acción escribir.
ALGORITMOS Y PROGRAMAS
Fig. 3.13
101
Ejemplo de bucle con contador negativo
Otro tipo de variable, normalmente asociada al funcionamiento de un bucle es un acumulador o totalizador, cuya misión es almacenar una cantidad variable, resultante de operaciones sucesivas y repetidas. Un acumulador realiza una función parecida a la de un contador, con la diferencia de que el incremento o decremento, de cada operación es variable en lugar de constante. En la Figura 3.11 la instrucción, SUMA ← SUMA + N, añade en cada iteración el valor de la variable N, leída a través de un periférico, por lo que SUMA es un ejemplo de acumulador. Una estructura repetitiva es aquella que marca la reiteración de una serie de acciones basándose en un bucle. De acuerdo con lo anterior, esta estructura debe constar de tres partes básicas: - decisión (para finalizar la repetición) - cuerpo del bucle (conjunto de instrucciones que se repiten) - salida del bucle (instrucción a la que se accede una vez se decide finalizar) Tomando el caso de la Figura 3.11, donde para obtener la suma de una serie de números, hemos utilizado la estrategia siguiente: tras leer cada número lo añadimos a una variable SUMA que contenga las sucesivas sumas parciales (SUMA se hace igual a cero al inicio). Observemos que el algoritmo correspondiente deberá utilizar sucesivamente instrucciones tales como: leer número si N < 0 entonces escribir SUMA si-no SUMA ←SUMA+número fin-si que se pueden repetir muchas veces; éstas constituyen el cuerpo del bucle. Una vez se ha decidido el cuerpo del bucle, se plantea la cuestión de cuántas veces se debe repetir. De hecho conocemos ya la necesidad de contar con una condición para detener el bucle. En el Ejemplo 8, se pide al usuario el número N de números que desea sumar, esto es, el número de iteraciones del bucle. Usamos un contador de iteraciones, TOTAL, que se inicializa a N y a continuación se decrementa en uno cada vez que el bucle se repite; para ello introducimos una acción más al cuerpo del bucle: TOTAL ← TOTAL -1. También podríamos inicializar la variable TOTAL en 0 o en 1, e ir incrementándolo en uno, en cada iteración, hasta llegar al número deseado N.
102
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Ejemplo 8: Hallar la suma de N números, a través de una estructura repetitiva algoritmo suma_números {leer número total de números a sumar en variable N} TOTAL ← N SUMA ← 0 { la suma parcial es 0 al inicio} {comienzo de bucle} mientras que TOTAL > 0 hacer leer número SUMA ← SUMA+número TOTAL ← TOTAL-1 fin_mientras {fin del bucle} escribir “la suma de los” , N , “números es “ , SUMA Aunque la condición de finalización puede evaluarse en distintos lugares del algoritmo, no es recomendable que ésta se pueda efectuar a mitad del cuerpo del bucle, por lo que es bueno que se produzca al principio o al final del mismo. Según donde se sitúe la condición de salida, dará lugar a distintos tipos de estructuras repetitivas que analizaremos a continuación: estructura desde-hasta, estructura mientras y estructura repetir-hasta_que. 3.4.3.1" ESTRUCTURA DESDE-HASTA Esta estructura consiste en que la condición de salida se basa en un contador que cuenta el número de iteraciones. Por ejemplo, el ejemplo 8 podría hacerse de la siguiente manera: desde i = 1 hasta N con_incremento 1 hacer leer número SUMA ← SUMA + número fin_desde donde i es un contador que cuenta desde un valor inicial (1) hasta el valor final (N) con los incrementos que se consideren (de uno en uno en este caso). Esta es la llamada estructura Desde (“for”), que es la más simple desde el punto de vista de la condición de salida, ya que viene predeterminada por el código. Su utilidad reside en el hecho de que, en muchas ocasiones, se conoce de antemano el número
ALGORITMOS Y PROGRAMAS
103
de iteraciones. Esta estructura ejecuta las acciones del cuerpo del bucle, un número especificado de veces y de modo automático controla el número de iteraciones. Su formato en pseudocódigo es: desde v=vi hasta vf hacer
. . fin_desde v: variable índice vi, vf: valores inicial y final de la variable
La variable índice o de control normalmente será de tipo entero y es normal emplear como identificador, las letras I,J,K como herencia de los índices y subíndices utilizados en cálculo científico. El incremento de la variable índice es 1 en cada iteración si no se indica expresamente lo contrario. Si debemos expresar incrementos distintos de +1 el formato de la estructura es: desde v = vi hasta vf inc incremento hacer
si vi > vf entonces usar . en lugar de inc incremento . la expresión dec decremento fin_desde Obviamente, si el valor inicial de la variable índice es menor que el valor final, los incrementos deben ser positivos, ya que en caso contrario la secuencia de acciones no se ejecutaría. De igual modo si el valor inicial es mayor que el valor final, el incremento debe ser en este caso negativo. Así: desde I=20 hasta 10 hacer
fin_desde no ejecutaría nunca el cuerpo del bucle. FORTRAN
DO n I =M1, M2, M3
n CONTINUE
n : etiqueta de fin de bucle M1: valor inicial del contador M2: valor final del contador M3: valor de incremento (+ ó -) PASCAL
BASIC
FOR V = Vi TO Vf
NEXT V FOR V = Vi TO Vf STEP X
NEXT V
C
104
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
for v:=vi to vt do
for(v=vi; v 30 escribir “números leídos: 30” fin
106
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Este bucle se repite hasta que el valor de variable contador exceda a 30, lo que sucederá después de 30 ejecuciones del mismo. Nótese que si en vez de 30 pusiéramos 0, el cuerpo del bucle se ejecutará siempre al menos una vez. Ejemplo 9: Calcular el factorial de un número N, usando la estructura repetir. inicio leer N Factorial ←1 I←1 repetir Factorial← Factorial * I I ← I+1 hasta_que I > N escribir “el factorial del número”, N, “es”, Factorial fin Las tres estructuras repetitivas son susceptibles de intercambio entre ellas, así por ejemplo es posible, sustituir una estructura desde, por una mientras; con incrementos positivos o negativos de la variable índice. En efecto, la estructura desde con incremento positivo es equivalente a la estructura mientras marcada con la A), y la estructura desde con incremento negativo es equivalente a la estructura mientras marcada con la B). A)
v ← vi mientras v < = vf hacer
v ← v + incremento fin_mientras
B)
v ← vi mientras v > = vf hacer
v ← v - decremento fin_mientras
3.4.3.4" Anidamiento de estructuras repetitivas En un algoritmo pueden existir varias estructuras repetitivas, siendo sus bucles respectivos anidados o independientes. Se dice que los bucles son anidados cuando están dispuestos de tal modo que unos son interiores a otros. Nótese que, en ningún caso, se admite que sean cruzados, pues su ejecución sería ambigua (Ver Figura 3.14 donde las líneas indican el principio y el fin de cada bucle).
ALGORITMOS Y PROGRAMAS
Fig. 3.14.
107
Posiciones relativas de los bucles
En las estructuras repetitivas anidadas, la estructura interna debe estar incluida totalmente dentro de la externa, no pudiendo existir solapamiento entre ellas. Las variables índices o de control de los bucles toman valores tales que por cada valor de la variable índice del ciclo externo se ejecuta totalmente el bucle interno. Ejemplo 10: Calcular los factoriales de n números leídos por el teclado. El problema consiste en realizar una primera estructura repetitiva de n iteraciones del algoritmo de cálculo del factorial, que a su vez se efectúa con una segunda estructura repetitiva. inicio leer n {lectura de la cantidad de números} desde i = 1 hasta n hacer leer NUMERO FACTORIAL ← 1 desde j = 1 hasta NUMERO hacer FACTORIAL ←FACTORIAL *j fin_desde escribir “el factorial del número”, NUMERO, “es”, FACTORIAL fin_desde fin
108
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Nótese que cada valor del contador i, el bucle interno cuyo contador es j, se ejecuta totalmente por lo que las variables que sirven de contadores en ambos bucles deben ser distintas. (No es necesario que las estructuras anidadas sean iguales; podríamos haber anidado un mientras o un repetir dentro del desde). 3.4.3.5" Bucles infinitos A pesar de lo dicho hasta ahora, podemos encontrarnos en la práctica con bucles que no exigen una finalización y otros que no la incluyen en su diseño. Por ejemplo, un sistema de reservas de líneas aéreas puede repetir de forma indeterminada un bucle que permita al usuario añadir o borrar reservas sin ninguna condición de finalización. El programa y el bucle se ejecutan siempre, o al menos hasta que el computador se apaga. En otras ocasiones, un bucle no se termina porque nunca se cumple la condición de salida. Un bucle de este tipo se denomina bucle infinito o sin fin. Los bucles infinitos no intencionados, causados por errores de programación, pueden provocar bloqueos en el programa (Ver Ejemplo 11). Ejemplo 11: Escribir un algoritmo que permita calcular el interés producido por un capital a las tasas de interés comprendidos en el rango desde 10 a 20 % de 2 en 2 puntos, a partir de un capital dado. leer capital tasa ←10 mientras tasa < > 20 hacer interés← tasa *0.01*capital { tasa*capital/100=tasa*0.01*capital}. escribir “interés producido”, interés tasa ←tasa+2 fin_mientras escribir “continuación” Los sucesivos valores de la tasa serán 10, 12, 14, 16,18,20, de modo que al tomar ‘tasa’ el valor 20 se detendrá el bucle y se escribirá el mensaje “continuación”. Supongamos que ahora nos interesa conocer este dato de 3 en 3 puntos; para ello se cambia la última línea del bucle por tasa← tasa+3. Ahora los valores que tomará tasa serán 10, 13, 16, 19 saltando a 22 y nunca será igual a 20, dando lugar a un bucle infinito y nunca escribiría “continuación”. Para evitarlo deberíamos cambiar la condición de finalización por una expresión del tipo: tasa < 20 o bien tasa ≤ 19
ALGORITMOS Y PROGRAMAS
109
El Ejemplo 11, sugiere además que, a la hora de expresar las expresiones booleanas de la condición del bucle, se utilice mayor o menor en lugar de igualdad o desigualdad. 3.4.3.6" Terminación de bucles con datos de entrada En el caso, necesariamente muy frecuente, de que leamos una lista de valores por medio de un bucle, se debe incluir algún tipo de mecanismo para terminar la lectura. Existen cuatro métodos para hacerlo: 1.Simplemente preguntar con un mensaje al usuario, si existen más entradas. Así en el problema de sumar una lista de números el algoritmo sería: inicio Suma ←0 escribir “existen más números en la lista s/n” leer Resp {variable Resp, tipo carácter} mientras Resp = “S” o Resp =“s” hacer escribir “numero” leer N Suma ←Suma +N escribir “existen más números (s/n)” leer Resp fin_mientras fin Este método a veces es aceptable e incluso útil, pero es tedioso cuando trabajamos con grandes listas de números, ya que no es muy aconsejable tener que contestar a una pregunta cada vez que introducimos un dato. 2.Conocer desde el principio el número de iteraciones que ya ha sido visto en los ejemplos anteriores y que permite emplear a una estructura desde-hasta. 3.Utilizar un valor “centinela”, un valor especial usado para indicar el final de una lista de datos. En estos casos es especialmente importante, de cara al usuario, advertir la forma de terminar la entrada de datos, esto es, que valor o valores indican el fin de la lista. Por ejemplo, supongamos que se tienen unas calificaciones, comprendidas entre 0 y 100; un valor centinela en esta lista puede ser -999, ya que nunca será una calificación válida y cuando aparezca se deberá terminar el bucle. Si la lista de datos son números positivos, un valor centinela puede ser un número negativo que
110
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
indique el final de la lista. El siguiente ejemplo realiza la suma de todos los números positivos introducidos desde el teclado: suma ← 0 leer numero mientras numero >= 0 hacer suma ← suma + número leer número fin_mientras Obsérvese la ventaja, en este caso, de utilizar una estructura “mientras”, puesto que el último número leído de la lista no se añade a la suma, si es negativo, ya que se sale fuera del bucle. 4.Por agotamiento de datos de entrada, consiste en simplemente comprobar que no existen más datos de entrada. Ello es posible cuando los datos se obtienen a partir de un fichero, donde se puede detectar el agotamiento de los datos gracias al signo de fin de fichero (EOF “end of file”). En este caso la lectura de un EOF supone la finalización del bucle de lectura. En el caso de estar recibiendo datos de otro computador, el cierre de la conexión también supone el agotamiento de los datos de entrada.
5070" RTQITCOCEKłP"OQFWNCT Una vez estudiadas las estructuras de control, hemos de ver cómo se implementa la descomposición en módulos independientes denominados subprogramas o subalgoritmos. Un subprograma es una colección de instrucciones que forman una unidad de programación, escrita independientemente del programa principal, con el que se asociará a través de un proceso de transferencia, de forma que el control pasa al subprograma en el momento que se requieran sus servicios y volverá al programa principal cuando aquél se haya ejecutado. Esta descomposición nos interesa por dos razones: 1) Esta asociada al diseño descendente en la programación estructurada, ya que un subprograma, a su vez, puede llamar a sus propios subprogramas e incluso a sí mismo, recursivamente. 2) Permite reutilizar un programa dentro de la resolución de otros problemas distintos, puesto que los subprogramas se utilizan por el programa principal para ciertos propósitos específicos, aquellos pueden haber sido escritos con anterioridad para resolver otros problemas diferentes.
ALGORITMOS Y PROGRAMAS
111
El subprograma recibe datos desde el programa y le devuelve resultados. Se dice que el programa principal llama o invoca al subprograma. Este, al ser llamado, ejecuta una tarea y devuelve el control al programa principal. La invocación puede suceder en diferentes lugares del programa. Cada vez que el subprograma es llamado, el control retorna al lugar desde donde fue hecha la llamada. Algunos subprogramas son tan comunes, que están incluidos en el lenguaje de programación, para ser utilizados directamente en los programas, esto incluye, los propios operadores aritméticos, operaciones sobre cadenas de caracteres, manejo del cursor, etc. Para poder utilizar esta aproximación, hay que respetar una determinada metodología, denominada abstración procedimental y que consta de dos etapas: 1) Asignar un nombre que identifique e independice cada módulo. 2) Parametrizar adecuadamente la entrada y la salida, a fin de que los datos que manda al programa principal puedan ser adecuadamente interpretados por el subprograma y viceversa. Existen dos tipos de subprogramas: funciones y rutinas o procedimientos, aunque no todos los lenguajes distinguen entre ellos. Aquí mantendremos esta distinción, con objeto de facilitar la exposición de la programación modular. 50703" HWPEKQPGU Matemáticamente una función es una operación que a partir de uno o más valores, llamados argumentos, produce un valor denominado resultado o valor de la función, por medio de ciertas operaciones sobre los argumentos. Consideremos la función:
f ( x) =
x 1+ x2
‘f’ es el nombre de la función y, ‘x’ es el argumento. Para evaluar ‘f’ debemos darle un valor a ‘x’. Así con x=3 se obtiene el valor 0,3 que se expresa escribiendo: f (3) = 0, 3 . Una función puede tener varios argumentos. La función F(x,y) =
x + x − 2 y − y es una función con dos argumentos. Sin embargo, solamente un
único valor se asocia con la función, con independencia del número de argumentos que tenga ésta. Todos los lenguajes de programación tienen la posibilidad de manejar funciones, bien estén ya incorporadas en origen, por el propio lenguaje, bien sean definidas por el usuario. Las funciones están diseñadas para realizar tareas específicas a
112
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
partir de una lista de valores (argumentos) y devolver un único valor al programa principal. Se llama o evoca una función, utilizando su nombre en una expresión con los argumentos encerrados entre paréntesis, que deben coincidir en cantidad, tipo y orden con los que la función fue definida en su momento. Supongamos que durante la ejecución del programa principal nos encontramos con una instrucción, y = raízcua (A + cos (x)). El control, entonces, pasará a evaluar la función raizcua. Al hacerlo, se pasa primero al subprograma (función) coseno y se calcula cos (x). Una vez obtenido A+cos (x) donde A es una constante, este valor se utiliza como argumento de la función raizcua, que evalúa el resultado final según las instrucciones que la definan. El resultado correspondiente devolverá al lugar desde donde fue llamada para ser almacenado en la variable ‘y’ del programa que la ha llamado. Las funciones incorporadas al lenguaje en origen se denominan funciones internas o intrínsecas (caso de sen, cos, etc.), mientras que las funciones definidas por el programador se deben codificar mediante una definición de función por parte del mismo. Se supone que la relación de funciones internas de cada lenguaje es conocida por el programador que sólo debe llamarlas cuando las necesite, pero no definirlas. El mayor interés para el programador reside por tanto en las funciones definidas por el usuario. 3.5.1.1" Definición de funciones Una función, como tal subprograma, tiene una constitución similar a un programa. Por consiguiente, constará de una cabecera con el nombre y los argumentos de la función, seguida por el cuerpo de la función, formado por una serie de acciones o instrucciones cuya ejecución produce un valor, el resultado de la función, que se devolverá al programa principal. La expresión de una función es: función nombre-función(par1,par2,par3...)
fin función par1,par2, ,
lista de parámetros formales o argumentos: son nombres de identificadores que sólo se utilizan dentro del cuerpo de la función.
nombre-función
nombre asociado con la función, que será un nombre de identificador válido sobre el que se almacenará el resultado.
instrucciones que constituyen la función, y que deben contener al menos una acción que asigne un valor como resultado de la función.
ALGORITMOS Y PROGRAMAS
113
En pseudocódigo antes del final debe aparecer nombre-función ← expresión. Ejemplo 12: Escribir la función factorial función factorial (X) F←1 desde j=1 hasta X hacer F←F*j fin-desde factorial ← F fin Obsérvese que se ha utilizado el identificador que devuelve el valor de la función al acabar la ejecución de la misma, en lugar de haber utilizado este identificador sustituyendo la variable F, con lo que nos ahorraríamos una línea de código. Esto es una exigencia de algunos compiladores. 3.5.1.2" Invocación a las funciones Una función puede ser llamada sólo mediante una referencia directa a la misma de la forma siguiente: nombre-función (lista de parámetros actuales) nombre función
función que es invocada.
lista de parámetros actuales
constantes, variables, expresiones, valores de funciones, nombres de subprogramas, que se corresponden con los parámetros formales, que hemos visto durante en la declaración de funciones.
Al invocar una función, hay que pasarle una serie de parámetros, a fin de proporcionarle los argumentos de entrada necesarios para poder ejecutar sus acciones, para ello distinguiremos entre los argumentos de la definición de la función (parámetros formales o mudos) y los argumentos utilizados en su invocación (parámetros actuales). Cada vez que se llama a una función, se establece sistemáticamente una correspondencia entre parámetros formales y actuales. En consecuencia, debe haber exactamente una correspondencia, tanto en
114
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
número como en tipo, de los parámetros actuales con los formales presuponiéndose una correspondencia, uno a uno, de izquierda a derecha entre ambos. Una llamada a la función implica los siguientes pasos: 1. 2. 3.
A cada parámetro formal se le asigna el valor real de su correspondiente parámetro actual. Se ejecuta el cuerpo de acciones de la función. Se devuelve el valor de la función y se retorna al punto de llamada.
Ejemplo 13: Escribir la función: y = xn (potencia n de x, tanto si es positiva, como negativa) y utilizarla para calcular 1/(2.5)3 función potencia (x, n) inicio parámetros formales (real y entero) y←1 función interna (valor absoluto) desde i = 1 hasta abs(n) hacer y←y*x fin_desde si n < 0 entonces y ← 1/y potencia ← y fin Su utilización sería: z ← potencia (2.5, -3)
parámetros actuales
Transferencia: Una vez que los parámetros formales toman los valores de sus correspondientes parámetros actuales x←2.5 , n←3, se ejecuta el cuerpo de la función, devolviendo sus resultados al programa principal, resultando: z←0.064 50704" RTQEGFKOKGPVQU"Q"UWDTWVKPCU Aunque las funciones son herramientas de programación muy útiles para la resolución de problemas, su alcance está limitado por el hecho de devolver un solo valor al programa principal. Con frecuencia se requieren subprogramas que intercambien un gran número de parámetros, por lo que existen procedimientos o subrutinas, subprogramas que ejecutan un módulo o proceso específico, sin que ningún valor de retorno esté asociado con su nombre. Un procedimiento se invoca normalmente, desde el programa principal mediante su nombre (identificador) y
ALGORITMOS Y PROGRAMAS
115
una lista de parámetros actuales; en algunos lenguajes se tiene que hacer a través de una instrucción específica llamar_a (call). Las funciones devuelven un sólo valor, mientras las subrutinas pueden devolver ninguno o varios. La forma de expresión de un procedimiento es similar a la de funciones: procedimiento nombre(lista de parámetros formales)
fin_procedimiento El procedimiento se llama mediante la instrucción: [llamar_a] nombre (lista de parámetros actuales) Ejemplo 14: Usar un procedimiento que escriba en la pantalla tantos signos # como el valor del cociente de sus dos argumentos, y tantos signos $ como el resto de la división entera, escribiendo un programa que muestre del modo gráfico indicado el cociente y el resto de: M/N y de (M+N)/2, usando el procedimiento.
Procedimiento
Algoritmo principal
procedimiento división(DIVIDENDO, DIVISOR) COCIENTE ← DIVIDENDO/DIVISOR {división entera} RESTO ← DIVIDENDO - COCIENTE * DIVISOR desde i=1 hasta COCIENTE hacer escribir ‘# fin-desde desde i=1 hasta RESTO hacer escribir ‘$ fin-desde fin algoritmo aritmética inicio leer M, N llamar_a división(M, N) llamar_a división(M+N, 2) fin
Como ocurre con las funciones, cuando se llama a un procedimiento, cada parámetro formal se sustituye por su correspondiente parámetro actual. Así, en la primera llamada, se hace DIVIDENDO←M y DIVISOR←N.
116
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
En programación modular, es un buen consejo incluir comentarios y documentación que describan brevemente lo que hace la función, lo que representan sus parámetros o cualquier otra información que explique la definición de la función, y que facilite su utilización. Esto es útil, ya que, es normal que un programador cuente con un buen número de funciones, ya codificadas con anterioridad, a las que puede recurrir para la resolución de distintos problemas, durante su trabajo de programación. 50705" ıodkvq"fg"ncu"Xctkcdngu Para ser consecuente con la abstracción procedimental, en la que se basa la programación modular, hemos de garantizar la independencia de los módulos exigiendo dos condiciones básicas, no siempre fáciles de cumplir: a) Cada módulo se diseña sin conocimiento del diseño de otros módulos. b) La ejecución de un subprograma particular no tiene por qué afectar a los valores de las variables que se usan en otros subprogramas. La programación modular permite, en muchos casos, el anidamiento de subprogramas, de forma que el programa principal llama a un subprograma, éste a su vez lo hace a un segundo que puede ser el mismo y así sucesivamente. En cada una de estas llamadas se establece la necesaria correspondencia entre parámetros actuales del programa llamante y los parámetros formales del programa invocado. En este proceso de ejecución de procedimientos anidados, es necesario considerar el ámbito de actuación de cada identificador, ya que finalmente en el programa ejecutable se unirán en un bloque de código único; al juntarse, programa principal y sus subprogramas, que han sido escritos de forma independiente, puede plantear problemas relacionados con los identificadores de variables utilizados en las distintas partes del código. Para resolver este problema, las variables utilizadas en los programas principales y subprogramas se clasifican en dos tipos: Una variable local es aquella que está declarada y es utilizada dentro de un subprograma y es distinta de las posibles variables que con el mismo nombre, pueden estar declaradas en cualquier otra parte del programa. Por tanto su significado y uso se confina al procedimiento o función en que está declarada; esto significa que cuando otro subprograma utiliza el mismo nombre, en realidad se refiere a una posición diferente en memoria. Se dice entonces que tales variables son locales al subprograma en el que están declaradas. Si un subprograma asigna un valor a una de sus variables locales, este valor no es accesible al resto de subprogramas, a menos que demos instrucciones en sentido contrario.
ALGORITMOS Y PROGRAMAS
117
Una variable global es aquella que está declarada para el programa o algoritmo completo, esto es, tiene validez para el programa principal y todos sus subprogramas. Las variables globales tienen la ventaja de permitir a los diferentes subprogramas compartir información, sin que tengan que figurar en las correspondientes listas de transferencia de parámetros entre programas. El uso de variables locales tiene ventajas, siendo la más importante el hecho de facilitar el uso de subprogramas independientes, siempre que la comunicación entre el programa principal y los subprogramas funcione adecuadamente, a través de las listas de parámetros actuales y formales. Para utilizar un procedimiento, ya utilizado y comprobado, sólo necesitamos conocer que hace, sin tenernos que preocupar por los detalles de su codificación interna (¿Cómo lo hace?). Esta característica hace posible por un lado dividir grandes proyectos en módulos autónomos que pueden codificar diferentes programadores que trabajan de forma independiente y por otro facilitan la reusabilidad del software. Llamaremos ámbito (scope) de un identificador, sea variable, constante o procedimiento, a la parte del programa donde se le reconoce como tal. Si un procedimiento está definido localmente respecto a otro procedimiento, tendrá significado sólo dentro del ámbito de ese procedimiento. A las variables les sucede lo mismo; si están definidas localmente dentro de un procedimiento, su significado o uso se confina a cualquier función o procedimiento que pertenezca a esa definición. Los lenguajes que admiten variables locales y globales suelen tener la posibilidad explícita de definir dichas variables como tales en el cuerpo del programa, o lo que es lo mismo, ofrecen la posibilidad de definir su ámbito de actuación. Para ello se utilizan las cabeceras de programas y subprogramas, donde figuran declaradas las variables, de donde se puede extraer el ámbito correspondiente de cada una. (Ver Figura 3.15)
118
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN programa XX variables A,B procedimiento P1 (x,y) variables D funcion F (u,v) variable G inicio
fin inicio
fin
ıodkvq
Kfgpvkhkecfqt
Cuerpo del programa Cuerpo de P1 Cuerpo de F Cuerpo de P2
A, B, P1, P2 A, B, D, x, y, F, P1, P2 A, B, D, G, x, y, u v, F, P1 A, B, I, J, r, s, P1, P2
procedimiento P2 (r,s) variable I,J inicio
fin inicio
fin
Fig. 3.15 Ejemplo de ámbito de definición de variables Las variables definidas en un ámbito son accesibles en él y en todos sus procedimientos interiores. Así la Figura 3.15 muestra el esquema de un ejemplo de un programa principal con diferentes subprogramas y el ámbito correspondiente de sus identificadores, señalando las partes del programa a las que tienen acceso los diferentes identificadores (variables, procedimientos y funciones). Nótese que es perfectamente posible que P2 sea accesible por P1 y viceversa, y que un subprograma sea accesible por sí mismo, (a esta posibilidad le dedicaremos la sección siguiente: recursividad). Ejemplo 15: Considérese el siguiente programa y ejecútese paso a paso: algoritmo XXX {variable global A} {variables locales: X, Y} inicio X← 5 A← 10 Y← F(X) escribir X,A,Y fin {algoritmo} función F(N)
ALGORITMOS Y PROGRAMAS
119
{comienzo función} {variable global A} {variable local X} inicio A← 6 X←12 F←N+5+A fin {función} A la variable global A se puede acceder desde el algoritmo XXX y desde la función F. Sin embargo, X representa a dos variables distintas: una local al algoritmo que sólo se puede acceder desde él y otra local a la función. Al ejecutar el algoritmo XXX se obtendrían los siguientes resultados: 1) 2)
X←5, A←10, Y←F(5), la invocación de la función F(N) da lugar al paso del parámetro actual X al parámetro formal N.
Al ejecutarse la función, se hace N←5 ya que el parámetro actual en la llamada del programa principal F(X) se asigna al parámetro formal N. A←6, cosa que modifica el valor de A en el algoritmo principal por ser A global.
X←12 sin que se modifique el valor X en el algoritmo principal, porque X es local. 3)
4)
F←16 (5+5+6)
El resultado de la función se almacena en Y, de forma que Y←16.
El resultado final, es que se escriba la línea 5 6 16
ya que X es el valor de la variable local X en el módulo principal; A toma el valor de la última asignación (que fue dentro de la función) e Y toma el valor de la función F(X). Del Ejemplo 15 sacamos la conclusión de que, en general, toda la información que intercambia un programa con sus subprogramas, debe vincularse a través de la lista de parámetros, y no mediante variables globales. Si se usa este último método, se corre el riesgo de obtener resultados inesperados, indeseables en muchos casos, llamados efectos laterales; En el Ejemplo 15, posiblemente en contra de nuestros deseos, la variable global A ha cambiado de valor al ejecutarse la función, cosa que no ha ocurrido con X. 50706" Rcuq"fg"rctâogvtqu
120
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Los parámetros que intervienen en programación modular pueden jugar distintos papeles: entrada: Proporcionan valores desde el programa que llama y que se utilizan dentro de un subprograma. Las entradas de un subprograma se dice, en general, que son sus argumentos. salida: Corresponden a los resultados del subprograma. En el caso de una función el valor de retorno, se utiliza como salida. entrada/salida: Un mismo parámetro se utiliza tanto para mandar argumentos, como para devolver resultados. Como veremos a continuación, el papel que juega cada parámetro no es suficiente para caracterizar su funcionamiento ya que los distintos lenguajes de programación utilizan distintos métodos para pasar o transmitir parámetros, entre un programa y sus subprogramas. Es el correcto intercambio de información, a través de parámetros, quien hace que los módulos sean independientes, siendo preciso conocer el método utilizado por cada lenguaje particular, ya que esta elección afecta a la propia semántica del lenguaje. Dicho de otro modo, un mismo programa puede producir diferentes resultados, bajo diferentes sistemas de paso de parámetros. Veamos los principales métodos de transmisión de parámetros. 3.5.4.1" Paso por valor El paso por valor se utiliza en muchos lenguajes de programación, la razón para ello es la analogía que presenta con los argumentos de una función, donde los valores se proporcionan para el cálculo de resultados. Los parámetros se tratan como variables locales de forma que los parámetros formales reciben como valores iniciales los valores de los parámetros actuales. A partir de ellos se ejecutan las acciones descritas en el subprograma. Otra característica es que no tiene relevancia el que los argumentos sean variables, constantes o expresiones, ya que sólo importa el valor de la expresión que constituye el argumento o parámetro formal. A←5 B←7 llamar
PROC1
(A, 18, B * 3 + 4) 5
18
25
procedimiento PROC1 (X, Y, Z) Fig. 3.16
Ejemplo de paso por valor
ALGORITMOS Y PROGRAMAS
121
La Figura 3.16 muestra el mecanismo de paso por valor de un procedimiento con tres parámetros, que se resume así. En el momento de invocar a PROC1 la situación es ésta: Primer parámetro: valor de la variable A = 5; Segundo parámetro: valor constante = 18; Tercer parámetro: valor de la expresión B* 3+4 = 25. El paso por valor asigna estos tres, respectivamente a los parámetros formales X, Y, Z al iniciar la ejecución del procedimiento PROC1. Aunque el paso por valor es sencillo, tiene una limitación importante: no existe ninguna posibilidad de otra conexión con los parámetros actuales, y por tanto los cambios que se produzcan por efecto del subprograma no producen cambios en los argumentos originales. Por consiguiente, no se pueden devolver valores de retorno al punto de llamada. Es decir, todos los parámetros son solo de entrada y el parámetro actual no puede modificarse por el subprograma, ya que cualquier cambio realizado en los valores de los parámetros formales durante la ejecución del subprograma se destruye cuando finaliza éste. Así, en el ejemplo de la figura 3.16, aunque X, Y, Z variasen en el interior del procedimiento, A y B seguirían valiendo 5 y 7, en el programa principal. Esta limitación, no obstante, constituye al mismo tiempo una ventaja en determinadas circunstancias, ya que asegura que las variables de un módulo no serán modificadas al invocar a una subrutina o función, hagan éstas lo que hagan. Ejemplo 16: Escribir un programa y una función que por medio del paso por valor obtenga el máximo común divisor de dos números. algoritmo Maximo_comun_divisor inicio leer (x, y) m ← mcd (x, y) escribir (x, y, m) fin funcion mcd(a, b): entero {a, b: enteros} inicio mientras a b hacer si a > b entonces a ← a - b sino b ← b - a fin_si
122
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
fin_mientras mcd ← a fin Al ejecutarse este algoritmo se producirán los siguientes resultados: x 10
Variable del algoritmo y m 25
Variables de la función a b mcd 10 25 10 15 10 5 5 5 5
5
Obsérvese que el paso de mcd desde la función al programa principal es posible gracias a que ambos comparten el identificador mcd. 3.5.4.2" Paso por referencia En numerosas ocasiones se requiere que ciertos parámetros sirvan como parámetros de salida o de entrada/salida, es decir, se devuelvan los resultados a la unidad o programas que llama al subprograma. Este método se denomina paso por referencia. El programa que llama pasa al subprograma llamado la dirección de memoria donde se halla almacenado el parámetro actual (que está en el ámbito de la unidad llamante), es decir, se pasa una referencia a la variable, no el valor que contiene. Toda referencia al correspondiente parámetro formal se trata como una referencia a la variable, cuya dirección se ha pasado. Con ello, una variable pasada como parámetro actual es compartida, es decir, también se puede modificar directamente por el subprograma (además de utilizar su valor). Si comparamos la Figura 3.16 con 3.17, observamos que en esta última los parámetros formales se utilizan para referirse a las posiciones de memoria de los parámetros actuales. Por tanto utilizamos esta referencia para pasar información de entrada y/o salida. Nótese que un cambio en el parámetro formal durante la ejecución del subprograma, se refleja en un cambio en el correspondiente parámetro actual (ver Ejemplo 17), ya que ambos se refieren a la misma posición de memoria. A←5 B←7 C←B*3+4 llamar_a PROC1(
A,
B,
C)
5
7
25
procedim PROC1(REF X, REF Y, REF Z)
direcciones o posiciones de memoria
ALGORITMOS Y PROGRAMAS
Fig. 3.17.
123
Paso por referencia
Cuando un parámetro se pase por valor lo denotaremos, en pseudocódigo poniendo REF delante del parámetro formal. Ejemplo 17: Dado el siguiente esquema de programa, analizar las dos llamadas al procedimiento, asumiendo que el paso de parámetros se efectúa por referencia. programa XYZ {parámetros actuales a, b, c y d paso por referencia} procedimiento PP(REF x, REF y) inicio {procedimiento} {proceso de los valores de x e y} fin (1) (2)
inicio {programa} ... prueba (a,c) ... prueba (b,d) .. fin
La primera llamada en (1) conlleva que los parámetros formales x e y se refieran a los parámetros actuales a y c. Si los valores de x o y se modifican dentro de esta llamada, los valores de a o c en el algoritmo principal también lo habrán hecho. De igual modo, cuando x e y se refieren a b y d en la llamada (2) cualquier modificación de x o y en el procedimiento afectará también a los valores de b o d en el programa principal. Nótese, por tanto, que mientras en el paso por valor los parámetros actuales podían ser variables, constantes o expresiones (pues lo que se pasa es su valor), cuando un parámetro se pasa por referencia el parámetro actual indicado en la invocación debe ser siempre una variable que es la forma de referirse a una posición de memoria.
124
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
3.5.4.3" Comparaciones de los métodos de paso de parámetros.Para examinar de modo práctico los diferentes métodos, consideremos los ejemplos 18 y 19, en los que podemos observar los diferentes valores que toman los parámetros del mismo programa según sea el método de paso que utilicemos Ejemplo 18: Consideremos los siguientes algoritmos y el valor que toman sus variables: algoritmo ABC inicio A ←3 B←5 C ←17 llamar_a SUB(A, B, C) escribir A,B,C fin
algoritmo ABC inicio A ←3 B←5 C ←17 llamar_a SUB(A, B, C) escribir A,B,C fin
procedimiento SUB(x,y,z)
procedimiento SUB(REF x,REF y,REF z) inicio x ← x+1 z ← x+y fin
inicio x ← x+1 z ← x+y fin (a)
(b)
a) Paso por valor: No se transmite ningún resultado desde SUBR, por consiguiente ni A, ni B, se modifican y se escribirá 3, 5, 17, al ejecutarse el programa ABC. b) Paso por referencia: Al ejecutar el procedimiento ejecutará: x=x+1=3+1=4 (almacenado en la posición de A) z=x+y=4+5=9 (almacenado en la posición de C) Por lo tanto, el programa escribirá 4, 5, 9. Es importante señalar que un mismo subprograma puede tener simultáneamente parámetros pasados por valor y parámetros pasados por referencia, como podemos ver en el Ejemplo 19. Ejemplo 19:
ALGORITMOS Y PROGRAMAS
125
Consideremos el siguiente programa M con un subprograma N que tiene dos parámetros formales: i pasado por valor, y j, por referencia. programa M inicio A←2 B←3 llamar_a N(A,B) escribir A,B fin {programa M} procedimiento N(i, REF j) { i por valor, j por referencia} inicio i ← i+10 j ← j+10 escribir i , j fin {procedimiento N}. Al ejecutar el programa M, se escribirán: A y B en el programa principal e i y j en el procedimiento N. Como i es pasado por valor, se transmite el valor de A a i, es decir, i=A=2. Cuando i se modifica (por efecto de la instrucción, i ← i+10) a 12 , A no cambia, y por consiguiente a la terminación de N, A sigue valiendo 2. El parámetro B se transmite por referencia. Al comenzar la ejecución de N, el parámetro j se refiere a la variable B, y cuando se suma 10 al valor de j, el valor de B se cambia a 13. Cuando los valores i, j se escriben en N, los resultados son: 12 y 13. Cuando retornamos al programa M y se imprimen los valores de A y B, sólo ha cambiado el valor B. El valor de i=12 se pierde en N cuando éste termina. El valor de j también se pierde, pero éste es una dirección, no el valor 13. Se escribirá como resultado final (de la instrucción escribir A,B) 2, 13. Señalemos finalmente que muchos lenguajes de programación permiten pasar parámetros por valor y por referencia, especificando cada modalidad. La elección entre un método u otro viene determinado por cada programa en particular y por el objetivo de evitar efectos laterales no deseados.
5080" EQPEGRVQ"FG"RTQITCOCEKłP"GUVTWEVWTCFC
126
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Cuando introdujimos las estructuras de control indicamos que la orientación no excluyente de este texto iba hacia la programación estructurada, sin detallar en qué consistía este enfoque. Ahora estamos en condiciones de hacerlo. Llamaremos programa propio a un programa que cumpla las siguientes características: 1.2.3.-
Posee un solo punto de entrada y uno de salida. Existen caminos que van desde la entrada hasta la salida, que pasan por todas las partes e instrucciones del programa. Todas sus instrucciones son ejecutables y no presenta bucles infinitos.
Desde 1966, gracias a diferentes trabajos teóricos, sabemos que un programa propio puede ser escrito utilizando solamente los tres tipos de estructura de control que hemos analizado (secuenciales, selectivas, repetitivas). Llamaremos programación estructurada a un conjunto de técnicas de programación que incluye : • Uso del diseño descendente. • Descomposición modular, con independencia de los módulos. • Utilización de las tres estructuras de control citadas. • La programación estructurada, gracias a la utilización exclusiva de las tres estructuras de control, permite realizar fácilmente programas legibles. Sin embargo, en determinadas circunstancias, parece necesario realizar bifurcaciones incondicionales, no incluidas en las estructuras de control estudiadas, para lo que hay que recurrir a la instrucción ir_a (goto). FORTRAN
BASIC
GO TO etiqueta
GOTO nº de linea
PASCAL
C
goto etiqueta
GOTO etiqueta
Esta instrucción siempre ha sido problemática para los programadores y se recomienda evitar su utilización. Aunque ir_a es una instrucción incorporada por todos los lenguajes de programación, existen algunos lenguajes como FORTRAN y BASIC que dependen más de ella que otros. En general, no existe ninguna necesidad de utilizarla, ya que cualquier algoritmo o programa escrito con
ALGORITMOS Y PROGRAMAS
127
instrucciones ir_a se puede reescribir de forma que lleve a cabo las mismas tareas prescindiendo de bifurcaciones incondicionales. Sin embargo, esta instrucción es útil en algunas situaciones excepcionales, como en ciertas salidas de bucles o cuando se encuentra un error u otra condición brusca de terminación. La instrucción ir_a puede ser utilizada para saltar directamente al final de un bucle, subprograma o procedimiento; la instrucción a la cual se salte debe tener una etiqueta o numero de referencia. Por ejemplo, un programa puede diseñarse de forma que finalice cuando se detecte un determinado error, de la forma: si entonces ir a 100 . 100 fin En cualquier caso y para preservar las ventajas de la programación estructurada, conviene saber que en los lenguajes más desarrollados existen instrucciones específicas de ir_a_fin_de_bucle, continuar_bucle e ir_a_fin_de_rutina para evitar usos indiscriminados de los saltos incondicionales.
5090" TGEWTUKXKFCF Entenderemos por recursividad la propiedad que tienen muchos lenguajes de programación de que los subprogramas puedan llamarse a sí mismos. Esta idea es interesante pues es una alternativa a la repetición, si nos enfrentamos a problemas con “estructura de solución recursiva”. Esta estructura recursiva se presenta en situaciones como la siguiente: Sea un problema A; al diseñar su algoritmo conseguimos dividirlo en dos partes B y C. Si una de estas partes, por ejemplo C, es formalmente idéntica a A, el problema es recursivo ya que la resolución de A se basa en C, que a su vez se descompone de la misma forma y así sucesivamente. En términos de programación esto supone utilizar un procedimiento para computar el problema, que va a llamarse a sí mismo para resolver parte de ese problema. La potencia de la recursión reside en la posibilidad de definir un número potencialmente infinito de operaciones de cálculo mediante un subprograma recursivo finito. Para que esta solución sea aceptable debe haber un momento en que ya no sea necesario que el procedimiento se llame a sí mismo ya que si la recursión continuara por siempre, no hablaríamos de un genuino algoritmo. Insistamos que la solución recursiva no es nunca única, ya que siempre es posible utilizar una estructura repetitiva, en lugar del planteamiento recursivo. No obstante, en algunos casos la solución recursiva es la solución natural del problema, y por tanto la más clara. Veamos algunos ejemplos:
128
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Ejemplo 20: Obtener una función recursiva que calcule el factorial de un entero no negativo. Este problema ya ha sido resuelto de forma no recursiva, ahora lo haremos utilizando la condición recursiva: n! = n * (n-1) ! si n = > 0 y n! = 1 si n = 0 función Factorial (n) inicio si n = 0 entonces Factorial ←1 sino Factorial ← n * Factorial (n-1) fin_si fin Para demostrar cómo esta versión recursiva de FACTORIAL calcula el valor de n!, consideremos el caso de n = 3. Un proceso gráfico se representa en la Figura 2.18. Los pasos sucesivos extraídos de la Figura 2.18 son: 5 como n no es 0 se genera otra operacion
8
HCEVQTKCN 4
4 HCEVQTKCN
3
Fig. 2.18.
2
FACTORIAL (2) = FACTORIAL (1) * 2 = 2
3 HCEVQTKCN
como FACTORIAL de 0 es siempre 1, este valor se asignara a la variable FACTORIAL, y ahora se realizara el proceso en sentido contrario ascendente
FACTORIAL (3) = FACTORIAL (2) * 3 = 2.3 = 6
FACTORIAL (1) = FACTORIAL (0) * 1 = 1
3 HCEVQTKCN
Secuencia para el cálculo recursivo de factorial de n
Ejemplo 21: Leer una lista de números de un fichero y escribirlos en orden inverso.
ALGORITMOS Y PROGRAMAS
129
Para resolver este problema se podrían leer todos los números, invertir su orden y escribirlos. No obstante, se podría pensar en una solución más sencilla si se hace una descomposición recursiva del problema. El problema se podría resolver con el siguiente algoritmo: algoritmo escribir_en_orden_inverso leer N { Primer número } leer el resto de los números y escribir_ en_orden_inverso escribir N {El primer número se escribe el último } El algoritmo consta de tres acciones, una de las cuales -la segunda- es semejante al problema inicial. La necesidad de llamar al procedimiento desaparece cuando no quedan más números por leer, esto es, cuando se ha llegado al fin de fichero (FF). El algoritmo quedará, por tanto, como: algoritmo INVERTIR inicio leer N si (N FF) entonces INVERTIR escribir N fin
{ hay más números }
Observar que el primer número que se escribirá es el último leído, después de que el número de llamadas a INVERTIR, sea igual a la cantidad de números que contiene el archivo. Consecuentemente, el primer número leído será escrito en último lugar, después de que la llamada recursiva a INVERTIR haya leído y escrito el resto de los números. Esto supone que el programa, al ejecutarse, deberá guardar tantas copias del subprograma y sus datos como sean necesarios. La forma como hace esto el lenguaje de programación no debe importarnos, salvo que este proceso sature la memoria y aparezca el correspondiente error. Ejemplo 22: Escribir una función que calcule recursivamente el término n-esimo de la serie de Fibonacci, dada por la expresión: fib(1) = 1 fib(2) = 1
130
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
fib(n) = fib(n-1) + fib(n-2) para n>2 Esta serie fue concebida originalmente como modelo para el crecimiento de una granja de conejos (proceso claramente recursivo) por el matemático italiano del siglo XVI, Fibonacci. Como no podía ser menos, esta serie crece muy rápidamente. Como ejemplo, el término 15 es 610. función FIBONACCI(n) inicio si (n=1) o (n=2) entonces FIBONACCI ← 1 sino FIBONACCI ← FIBONACCI (n-2) + FIBONACCI (N-1) fin_si fin_función La recursión es una herramienta muy potente y debe ser utilizada como una alternativa a la estructura repetitiva. Su uso es particularmente idóneo en aquellos problemas que pueden definirse de modo natural en términos recursivos. El caso de la función factorial es un ejemplo claro. Insistamos en que, para evitar que la recursión continúe indefinidamente, es preciso incluir una condición de terminación. Como veremos en el capítulo siguiente, la posibilidad de implementar la recursividad se debe a la existencia de estructuras de datos específicos del tipo pila.
50:0" FGUCTTQNNQ"["IGPGTCEKłP"FGN"UQHVYCTG Una vez hemos visto cómo se podían implementar las estructuras modulares y de control sobre las que descansa la programación estructurada, volvamos al problema de la resolución de un problema por medio de un ordenador. De acuerdo con lo que allí afirmamos, cuando se ha comprobado el programa, éste se podría dar, en principio, por terminado y el problema abordado como resuelto. Sin embargo, un programa no puede darse como totalmente terminado hasta que no ha sido depurado y contrastado con toda una batería de datos de entrada distintos. Además, una vez se está razonablemente seguro de su funcionamiento, debe documentarse para que pueda ser utilizado por cualquier usuario, al tiempo que hay que tomar toda una serie de medidas que faciliten su actualización y
ALGORITMOS Y PROGRAMAS
131
mantenimiento, ya que un programa, siempre debe estar en condiciones de mejorar sus prestaciones y de adaptarse a pequeños cambios, sin tener que proceder a su reescritura completa. Aunque estas últimas fases están fuera de la óptica de este capítulo, conviene retener la totalidad de fases que constituyen el proceso completo de generación del software y que se esquematiza en la Figura 3.18.
Fig. 3.18
El proceso completo de programación
Para completar el capítulo, demos algunas ideas relacionadas con la programación de aplicaciones complejas, que dan lugar a la generación de una gran cantidad de líneas de código. 50:03" KPIGPKGT¯C"FGN"UQHVYCTG Conviene saber que la tarea de diseño y construcción de un programa puede ser, en algunos casos, bastante compleja. En las aplicaciones reales, no es usual que la resolución de un problema implique el desarrollo de un solo programa. Normalmente cualquier aplicación práctica requiere de más de un programa individual y conviene considerar alguna de las características que hay que tener en cuenta al enfrentarse a ellas desde un punto de vista profesional: A)
Volumen: un proyecto, en la práctica, suele ser grande (decenas de miles de líneas de código) lo que presenta problemas de: - Complejidad: El software es complejo y resulta difícil que una única persona conozca todos los detalles de una aplicación. - Coordinación: En el desarrollo de una aplicación intervienen muchas personas. Hay que coordinar el trabajo de todos, de forma que al final los distintos componentes y módulos encajen.
132
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
B)
Evolución: El software no es estático. Evoluciona con las necesidades del usuario, los cambios del entorno (nuevo hardware, nueva legislación, nuevas necesidades, ampliaciones, etc.) deben ser tenidos en cuenta.
C)
Comunicación: Cuando se desarrolla una aplicación es porque, de una forma u otra, hay alguien interesado en usarla. Antes de comenzar el desarrollo habrá que concretar con el futuro usuario las características de la aplicación. Esta comunicación conlleva problemas, pues normalmente el informático no conoce en profundidad las necesidades del cliente y éste no sabe discernir la información que es útil para el informático de la que no lo es.
La ingeniería del software se ocupa del estudio de estos problemas y de sus soluciones. Se puede definir la ingeniería del software como la disciplina que se ocupa del establecimiento y uso de principios de ingeniería para obtener software económico que sea fiable y funcione eficientemente en las máquinas que estén disponibles en cada momento. La ingeniería del software se ocupa de: la planificación y estimación de proyectos, análisis de requisitos, diseño del software, codificación, prueba y mantenimiento. Para realizar estas tareas propone una serie de métodos, como cualquier otra ingeniería. No obstante, existen grandes diferencias con éstas, la principal de las cuales procede de la ausencia del proceso de fabricación que se da en el caso del desarrollo de software. Cuando se construyen automóviles, una vez diseñados se fabrican. La ausencia de proceso de fabricación en la ingeniería software hace que el coste de la producción sea fundamentalmente el coste del diseño. Por otra parte, el mantenimiento del software es muy diferente al del hardware (o cualquier otro sistema físico en ingeniería), ya que el software no se desgasta con el tiempo. El mantenimiento del software tiene que ver con la detección de fallos o con la necesidad de adaptarlo a unas nuevas circunstancias. En ambos casos el proceso de mantenimiento no consiste en la sustitución de un componente por otro, sino en la repetición de parte del proceso de desarrollo. 50:04" EKENQ"FG"XKFC"FGN"UQHVYCTG La creación de cualquier sistema software implica la realización de tres pasos genéricos: definición, desarrollo y mantenimiento. En la fase de definición se intenta caracterizar el sistema que se ha de construir. Esto es, se trata de determinar qué información ha de usar el sistema, qué funciones ha de realizar, qué condiciones existen, cuáles han de ser las interfaces del sistema
ALGORITMOS Y PROGRAMAS
133
y qué criterios de validación se usarán. En resumen; se debe contestar detalladamente a la pregunta: ¿qué hay que desarrollar?. En la fase de desarrollo se diseñan las estructuras de datos (bases de datos o archivos) y de los programas; se escriben y documentan éstos y se prueba el software. La fase de mantenimiento comienza una vez construido el sistema, coincidiendo con su vida útil. Durante ella, el software es sometido a una serie de modificaciones y reparaciones. La detección de errores durante la definición y el desarrollo se realiza mediante revisiones de la documentación generada en cada fase. En estas revisiones se pueden detectar fallos o inconsistencias que obliguen a repetir parte de la fase o una fase anterior. Este esquema general se puede detallar más, dando lugar a modelos concretos del ciclo de vida del software. Dos son los paradigmas usados más frecuentemente: el ciclo de vida clásico y el de prototipos. 3.8.2.1" Ciclo de vida clásico El ciclo de vida clásico consta de fases, que siguen un esquema en cascada, análogo al esquema general que acabamos de ver. Este paradigma contempla las siguientes fases: Análisis del sistema: El software suele ser parte de un sistema mayor formado por hardware, software, bases de datos y personas. Por él, se debe comenzar estableciendo los requisitos del sistema, asignando funciones a los distintos componentes y definiendo las interfaces entre componentes. Análisis de los requisitos del software: Antes de comenzar a diseñar el software se deben especificar las funciones a realizar, las interfaces que deben presentarse y todos los condicionantes, tales como rendimiento, utilización de recursos, etc. Como fruto de este análisis se genera un documento conocido como especificación de requisitos del software. Diseño: El diseño del software consiste en construir una estructura para el software que permita cumplir los requisitos con la calidad necesaria. El diseño concluye con la confección de un documento de diseño. A partir de él, se debe responder a la pregunta ¿cómo se ha de construir el sistema?. Codificación: Consiste en plasmar el diseño en programas, escritos en un lenguaje de programación adecuado.
134
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Prueba: Cuando se han escrito los programas es necesario probarlos. En las pruebas se debe comprobar que los programas se corresponden con el diseño, realizan correctamente sus funciones y que el sistema satisface los requisitos planteados. Es decir, se ha de contestar a la pregunta ¿se ha construido el sistema que se deseaba?. Mantenimiento: Esta fase se corresponde con la fase de mantenimiento del esquema general. Dependiendo de la naturaleza y motivación de cada operación de mantenimiento concreta, será necesario revisar desde la codificación, desde el diseño o desde la fase de análisis. El conjunto de la documentación generada en todas las fases constituye la configuración del software. Un buen sistema no es solamente un conjunto de programas que funcionan, sino también una buena documentación. La documentación es fundamental para un buen desarrollo y es esencial en el proceso de mantenimiento del software. 3.8.2.2" Ciclo de vida de prototipos Hay situaciones en que no es posible usar el ciclo de vida clásico, fundamentalmente debido a la dificultad de establecer los requisitos del software a priori. En estas situaciones es posible seguir el modelo de ciclo de vida de prototipos. En esencia, este paradigma se basa en la construcción de un prototipo durante las primeras etapas del ciclo de vida. Un prototipo es un modelo “a escala reducida” del sistema que se va a construir. El prototipo incorpora sólo los aspectos relevantes del sistema y se utiliza como ayuda en la especificación del software, sirviendo como base tangible sobre la que discutir con el usuario final. El ciclo de vida comienza con la realización de un breve análisis de los requisitos, tras el cual se diseña y codifica el prototipo. Sobre el prototipo se discuten y detallan las especificaciones, modificando el prototipo de acuerdo con éstas, siguiendo un proceso cíclico. El resultado de este proceso es el documento de especificación de requisitos del software. Normalmente se desecha posteriormente el prototipo, diseñándose el sistema definitivo. A partir de este punto se puede seguir el mismo esquema que un ciclo clásico. Es posible que el lector quede abrumado por toda esta metodología cuando se está introduciendo en las técnicas de programación. Sin embargo, es posible que estas reflexiones le puedan ser de utilidad cuando se enfrente a la tarea de programar algún problema no trivial y se encuentre con que algo no funciona como debe. Construir software es una ingeniería y hemos tratado de esbozar sus normas.
ALGORITMOS Y PROGRAMAS
135
3.1. CONCEPTO DE ALGORITMO ..................................................................81 3.2. LA RESOLUCIÓN DE PROBLEMAS Y EL USO DEL ORDENADOR 82
3.2.1 ANÁLISIS DEL PROBLEMA ......................................................................82 3.2.2 DISEÑO DEL ALGORITMO .......................................................................83 3.2.3 PROGRAMACIÓN DEL ALGORITMO......................................................87 3.3. REPRESENTACIÓN DE ALGORITMOS .................................................88 3.3.1 PSEUDOCODIGO.........................................................................................89 3.3.2 ORGANIGRAMAS .......................................................................................90
3.4. ESTRUCTURAS DE CONTROL ................................................................91 3.4.1 ESTRUCTURAS SECUENCIALES .............................................................92 3.4.2 ESTRUCTURAS SELECTIVAS ..................................................................92 3.4.3 ESTRUCTURAS REPETITIVAS .................................................................98 3.5. PROGRAMACIÓN MODULAR ...............................................................110
3.5.1 FUNCIONES ...............................................................................................111 3.5.2 PROCEDIMIENTOS O SUBRUTINAS .....................................................114 3.5.3 ÁMBITO DE LAS VARIABLES ........................................................................116 3.5.4 PASO DE PARÁMETROS .................................................................................119 3.6. CONCEPTO DE PROGRAMACIÓN ESTRUCTURADA .....................125 3.7. RECURSIVIDAD.........................................................................................127 3.8. DESARROLLO Y GENERACIÓN DEL SOFTWARE...........................130
3.8.1 INGENIERÍA DEL SOFTWARE ...............................................................131 3.8.2 CICLO DE VIDA DEL SOFTWARE .........................................................132
CAPÍTULO 4
ARITMÉTICA Y REPRESENTACIÓN DE LA INFORMACIÓN EN EL COMPUTADOR
Dos de los aspectos básicos que se presentan en el tratamiento de la información son cómo representarla (de lo cual dependerá sus posibilidades de manipulación) y cómo almacenarla físicamente. La primera se resuelve recurriendo a un código adecuado a las posibilidades internas del computador, que abordaremos en el presente capítulo. La segunda tiene que ver con los llamados soportes de información y es una cuestión que pertenece al ámbito del soporte físico del ordenador. En la raíz de los desarrollos informáticos está el hecho de que todo dato puede ser representado por un conjunto de bits, circunstancia que permite a la ALU realizar un gran número de operaciones básicas utilizando su representación binaria. El paso a códigos binarios es una misión que el computador lleva a cabo automáticamente, por lo que el usuario puede despreocuparse de este proceso. Sin embargo es conveniente tener algunas ideas acerca de la forma como el computador codifica y opera internamente, cuestión indispensable para comprender determinados comportamientos de la máquina. Para ello empezaremos recordando algunos conceptos relativos al sistema de numeración binario y a las transformaciones entre éste y el sistema decimal.
6030" UKUVGOCU"FG"PWOGTCEKłP"GP"KPHQTOıVKEC Llamaremos sistema de numeración en base b, a la representación de números mediante un alfabeto compuesto por b símbolos o cifras. Así todo número se expresa por un conjunto de cifras, contribuyendo cada una de ellas con un valor que depende: a) de la cifra en sí, b) de la posición que ocupa dentro del número. En el sistema de numeración decimal, se utiliza, b = 10 y el alfabeto está constituido por diez símbolos, denominados también cifras decimales: 137
138
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} (3.1) y, por ejemplo, el número decimal 278.5 puede obtenerse como suma de: 200 70 8 0 .5 278 .5 es decir, se verifica que: 278.5 = 2×102 + 7×101 + 8×100 + 5×10-1 Cada posición, por tanto, tiene un peso específico (en el sistema decimal, cada posición además tiene un nombre): posición 0 peso b0 posición 1 peso b1 posición 2 peso b2
valor en el ejemplo
posición -1 peso b-1 ...............................................
8 7 2 5
nombre
unidades decenas centenas décimas
Generalizando, se tiene que la representación de un número en una base b: N ≥ ... n4 n3 n2 n1 n0 . n-1 n-2 n-3 ... (3.2)
es una forma abreviada de expresar su valor, que es: N ≥ ... n4b4 + n3b3 + n2b2 + n1b1 + n0b0 + n-1b-1 ... (3.3) donde el punto separa los exponentes negativos de los positivos. Nótese que el valor que tome b determina la longitud de la representación; así, por un lado, resulta más cómodo que los símbolos (cifras) del alfabeto sean los menos posibles, pero, por otra parte, cuanto menor es la base, mayor es el número de cifras que se necesitan para representar una cantidad dada. Como ejemplo veamos cual es el número decimal correspondiente de 175372, en base 8, (cuyo alfabeto es {0, 1, 2, 3, 4, 5, 6, 7} y recibe el nombre de sistema octal).
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
139
175372)8 = 1×85 + 7×84 + 5×83 + 3×82 + 7×81 + 2×80 = = 6×104 + 4×103 + 2×102 + 5×101 + 0×100 = 64250)10 60303" FGHKPKEKłP"FGN"UKUVGOC"DKPCTKQ En el sistema de numeración binario b es 2, y se necesita tan sólo un alfabeto de dos elementos para representar cualquier número: {0,1}. Los elementos de este alfabeto se denominan cifras binarias o bits. En la Tabla 4.1 se muestran los números enteros binarios que se pueden formar con 3 bits, que corresponden a los decimales de 0 a 7. binario decimal
Tabla 4.1.- Números binarios del 0 al 7 000 001 010 011 100 101 0 1 2 3 4 5
110 6
111 7
60304" VTCPUHQTOCEKQPGU"GPVTG"DCUGU"DKPCTKC"["FGEKOCN Para transformar un número binario a decimal: Basta tener en cuenta las expresiones (3.2) y (3.3), en las que b = 2. Ejemplo 1: Transformar a decimal los siguientes números binarios: 110100; 0.10100; 10100.001 110100)2 = (1×25) + (1×24) + (1×22) = 25 + 24 + 22 = 32 + 16 +4 = 52)10 0.10100)2 = 2-1 + 2-3 = (1/2) + (1/8) = 0.625)10 10100.001)2 = 24 + 22 + 2-3 = 16+4+(1/8) = 20125)10 Observando el Ejemplo 1 se deduce que se puede transformar de binario a decimal sencillamente sumando los pesos de las posiciones en las que hay un 1, como se pone de manifiesto en el Ejemplo 2. Ejemplo 2: Transformar a decimal los números: 1001.001 )2 y 1001101 )2
140
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
1001.001 )2 pesos → 1001101 )2 pesos →
1 8
1 64
0 4
0 2
0 32
1 1
0 16
0 1/2
1 8
1 4
0 1/4 0 2
1 = 8 + 1 + 1/8 = 9125 )10 1/8 1 1
= 64 + 8 + 4 + 1 = 77 ) 10
Para transformar un número decimal a binario: a) La parte entera del nuevo número (binario) se obtiene dividiendo la parte entera del número decimal por la base, 2, (sin obtener decimales en el cociente) de partida, y de los cocientes que sucesivamente se vayan obteniendo. Los residuos de estas divisiones y el último cociente (que serán siempre menores que la base, esto es, 1 o 0), en orden inverso al que han sido obtenidos, son las cifras binarias. Ejemplo 3: Pasar a binario el decimal 26 26 0
2 13 1
2
6
0
26 )10 = 11010 )2 2
3
1
2 1
b) La parte fraccionaria del número binario se obtiene multiplicando por 2 sucesivamente la parte fraccionaria del número decimal de partida y las partes fraccionarias que se van obteniendo en los productos sucesivos. El número binario se forma con las partes enteras (que serán ceros o unos) de los productos obtenidos, como se hace en el siguiente ejemplo. Ejemplo 4: Para pasar a binario el decimal 26.1875 separamos la parte fraccionaria: 0.1875 y la parte entera: 26 (ya transformada en el Ejemplo 3). 0.1875 ×2 0.3750
0.3750 ×2 0.7500
0.7500 ×2 1.5000
0.5000 ×2 1.0000
Por tanto, habiéndonos detenido cuando la parte decimal es nula, el número decimal 26.1875 en binario es: 26.1875)10 = 11010.0011)2
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
141
NOTA: un número real no entero presentará siempre cifras después del punto decimal, pudiendo ser necesario un número finito o infinito de éstas, dependiendo de la base en que se represente; por ejemplo el número 1.6)10 representado en binario sería 1.100110011001...)2, requiriendo infinitas cifras para ser exacto, como por otra parte ocurre con muchos números representados en decimal. 60305" EłFKIQU"KPVGTOGFKQU Como acabamos de comprobar, el código binario produce números con muchas cifras, y para evitarlo utilizamos códigos intermedios que son bases mayores, que no se alejen de la binaria. Estos se fundamentan en la facilidad de transformar un número en base 2, a otra base que sea una potencia de 2 (22=4; 23=8; 24=16, etc.), y viceversa. Usualmente se utilizan como códigos intermedios los sistemas de numeración en base 8 (u octal) y en base 16 (o hexadecimal). 4.1.3.1" BASE OCTAL Un número octal puede pasarse a binario aplicando los algoritmos ya vistos en 4.1.2. No obstante, al ser b=8=23, el proceso es más simple puesto que, como puede verse n525 + n424 + n323 + n222 + n121 + n020 + n-12-1 + n-22-2 + n-32-3 = (n522 + n421 + n320)× 23 + (n222 + n121 + n020)× 20 + (n-122 + n-221 + n-320)× 2-3 = (m1)× 81 + (m0)× 80 + (m-1)× 8-1 Cada 3 símbolos binarios (3 bits) se agrupan para formar una cifra de la representación en octal, por tanto en general puede hacerse la conversión fácilmente, de la forma siguiente: Para transformar un número binario a octal se forman grupos de tres cifras binarias a partir del punto decimal hacia la izquierda y hacia la derecha (añadiendo ceros no significativos cuando sea necesario para completar grupos de 3). Posteriormente se efectúa directamente la conversión a octal de cada grupo individual de 3 cifras, y basta con memorizar la Tabla 4.1 para poder realizar rápidamente la conversión. Así por ejemplo 010 001 101 100 . 110 100 2 1 5 4 6 4 = 2154.64)8 . De octal a binario se pasa sin más que convertir individualmente a binario (tres bits) cada cifra octal, manteniendo el orden del número original. Por ejemplo: 10001101100.1101)2 =
142
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
537.24 )8 =
5 101
3 011
7 . 2 4 111 . 010 100
= 101011111.0101 )2
Para transformar un número de octal a decimal se aplica la expresión (3.3) con b=8. Para transformar un número de decimal a octal se procede de forma análoga a como se hizo para pasar de decimal a binario dividiendo o multiplicando por 8 en lugar de por 2. Así se puede comprobar que 1367.25)8 = 759.328125)10 ó que 760.33)10 =1370.2507...)8 4.1.3.2" BASE HEXADECIMAL Para representar un número en base hexadecimal (esto es, b=16) es necesario disponer de un alfabeto de 16 símbolos: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F} Tabla 4.2.- Números binarios del 0 al 7
hexadec. 0 1 2 3 4 5 6 7 8 9 A B C D E F decimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 binario 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
Al ser b=16=24, de modo similar al caso octal, cada símbolo hexadecimal se corresponde con 4 símbolos binarios (4 bits) y las conversiones a binario se realizan agrupando o expandiendo en grupos de 4 bits. Se pueden comprobar las transformaciones siguientes: 10111011111.1011011 )2 = 1A7.C4 )H =
0101 1101 1111 . 1011 0110 5 D F . B 6
1 A 7 . C 4 0001 1010 0111 . 1100 0100
= 5DF.B6)H
= 110100111.110001)2
De la misma forma que manualmente es muy fácil convertir números de binario a octal, y viceversa, y de binario a hexadecimal, y viceversa, también resulta sencillo efectuar esta operación electrónicamente o por programa, por lo que a veces la computadora utiliza este tipo de notaciones intermedias como código interno o de entrada/salida, y también para visualizar el contenido de la memoria o de los registros.
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
143
Para transformar un número de hexadecimal a decimal se aplica la expresión (2.3) con b=16. Para pasar un número de decimal a hexadecimal se hace de forma análoga a los casos binario y octal: la parte entera se divide por 16, así como los cocientes enteros sucesivos, y la parte fraccionaria se multiplica por 16, así como las partes fraccionarias de los productos sucesivos. Así se puede comprobar que 12A5.7C)H = 4773.484375)10 ó que 16237.25)10 = 3F6D.4)H
6040" QRGTCEKQPGU"CTKVOÖVKECU"["NłIKECU
El procesamiento de la información incluye realizar una serie de operaciones con los datos; estas operaciones y las particularidades de las mismas en su realización por el computador son el objeto de los próximos apartados. 60403" QRGTCEKQPGU"CTKVOÖVKECU"EQP"PðOGTQU"DKPCTKQU
Las operaciones aritméticas básicas (suma, resta, multiplicación y división) en sistema binario se realizan en forma análoga a la decimal aunque, por la sencillez de su sistema de representación, las tablas son realmente simples: Tabla 4.3.- Operaciones básicas en binario
Suma aritmética
0+0=0 0+1=1 1+0=1 1 + 1 = 0 y llevo 1(*)
Resta aritmética
0-0=0 0 - 1 = 1 y debo 1 (*) 1-0=1 1-1=0
Producto aritmético 0.0=0 0.1=0 1.0=0 1.1=1
(*) Llamado normalmente acarreo. En binario 1+1=10 (es 0 y me llevo 1), igual que en decimal 6+6=12 (es 2 y me llevo 1) Ejemplo 5: Efectuar las siguientes operaciones aritméticas binarias: 1110101 +1110110 11101011 1101010 -1010111 0010011
1101010 ×11 1101010 1101010_ 100111110
1010011 ×10 0000000 1010011_ 10100110
110.01 10 010 10 00010 10 00
10 11.001
144
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
A partir del ejemplo anterior, se observa que multiplicar por 10)2 (es decir, por 2 en decimal) equivale a añadir un cero a la derecha, o desplazar el punto decimal a la derecha, siendo esto similar a multiplicar por 10)10 un número decimal. De la misma forma dividir por 10)2 = 2)10 equivale a eliminar un cero a la derecha, o desplazar el punto decimal a la izquierda. Por ejemplo: 1010011)2 × 2 = 10100110)2 10101.01)2 × 2 = 101010.1)2 1.101101)2 × 25 = 110110.1)2
1010100)2 / 2 = 101010)2 110.01)2 / 2 = 11.001)2 10101.101)2 / 26 = 0.010101101)2
60404" XCNQTGU"DQQNGCPQU"["QRGTCEKQPGU"NłIKECU Un dígito binario, además de representar una cifra en base dos, también puede interpretarse como un valor booleano o dato lógico (en honor a George Boole, citado en el capítulo 1), entendiendo como tal una cantidad que solamente puede tener dos estados (Verdadero/Falso, SI/NO, 1/0, etc.) y por tanto con capacidad para modelizar el comportamiento de un conmutador. Así, además de las operaciones aritméticas con valores binarios, se pueden llevar a cabo operaciones booleanas o lógicas en las que estos valores se consideran señales generadas por conmutadores. Las operaciones booleanas más importantes son: OR lógico (también denominado unión, suma lógica (+) o función O), AND lógico (también intersección, producto lógico ( . ) o función Y) la complementación ( - ) (o inversión, negación, o función NOT o NO). Nótese que las dos primeras son operaciones de dos operandos o binarios mientras que la complementación es unaria. Estas operaciones se rigen según la Tabla 4.4. Tabla 4.4.- Operaciones lógicas OR 0+0=0 0+1=1 1+0=1 1+1=1
AND 0.0=0 0.1=0 1.0=0 1.1=1
NOT
0=1 1=0
Como puede observarse, el AND y OR lógicos se corresponden parcialmente con el producto y suma binarios, y lo más significativo, es la posibilidad de
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
145
implementar estas operaciones lógicas, y por tanto las aritméticas binarias, en forma de circuitos. 60405" RWGTVCU"NłIKECU Necesitamos examinar la realización en hardware de algunas de las operaciones máquina básicas. Para poder hacer esto, vamos a diseñar y “construir” algunos de esos circuitos. A lo largo de este recorrido se irá poniendo de manifiesto la importancia de la lógica, en el proceso de diseño de componentes hardware. Tenemos tendencia a considerar a los números y a la aritmética como las entidades básicas de la informática. Después de todo, los números y las operaciones aritméticas es lo primero que se estudia en los cursos elementales. Sin embargo, tanto la suma como el resto de las operaciones de la máquina se tienen en términos de la lógica. Las computadoras trabajan con la lógica. Sabemos que el sistema binario se utiliza para representar números (o, de hecho, también otro tipo de información) en la computadora. Sin embargo, utilizar la codificación binaria es equivalente a definir los números en términos de las entidades lógicas básicas verdadero y falso. Por ejemplo 5)10 es 101)2. Pero esto es lo mismo que decir verdadero falso verdadero si asimilamos 1=verdadero y 0=falso. Parece, pues, apropiado comenzar nuestro proyecto de construcción con las operaciones lógicas and, not y or. Para ello, necesitamos entender la operación y conocer qué circuitos podemos construir. Las operaciones and y or son binarias: tiene dos entradas u operandos, y de ellos se obtiene una salida. Para poder determinar qué componentes pueden ser de utilidad en la construcción de estos circuitos and y or, conviene recordar que representamos la información como una secuencia de dos estados (verdadero/falso, 0/1). Por tanto, el elemento básico tiene que ser algún tipo de conmutador, es decir, algún dispositivo que pueda representar los dos estados, un simple interruptor1 puede hacerlo. Trabajaremos ahora con electricidad para simular la lógica, por lo que utilizaremos hilos en lugar de variables lógicas, cada hilo tiene voltaje o no lo tiene (1/0). En la Figura 4.1. pueden verse los circuitos buscados a los que denominaremos puertas lógicas (en general, dispositivos físicos que realizan operaciones lógicas). En la práctica un interruptor resulta demasiado lento, por lo que necesitamos algún tipo de conmutador que pueda cambiar de estado a velocidad electrónica. El transistor es este dispositivo; se trata de un conmutador que interrumpe o permite el paso de corriente eléctrica y utiliza la electricidad para realizar la conmutación. Este es el elemento activo de la CPU (más recientemente el circuito integrado; sin embargo, un circuito integrado no es más que una pequeña “tableta” de circuitería que incluye transitores y algunos otros componentes electrónicos básicos, como condensadores y resistencias). 1
146
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Circuito OR a: abierto (0) b: cerrado (1) salida: luce (1) ( 0 OR 1 = 1 ) Fig. 4.1.
Circuito AND a: abierto (0) b: cerrado (1) salida: no luce (0) ( 0 AND 1 = 0 ) Circuitos eléctricos cuyo comportamiento puede describirse mediante las funciones OR y AND.
Obsérvese que existen dos operandos de entrada y uno de salida. En el caso del circuito and, solo si los dos conmutadores están cerrados, (las entradas son verdadero) existirá voltaje a la salida, lo que se interpreta como verdadero (o 1). Análogamente, se reproduce con este circuito, el resto de filas de la tabla de verdad de la operación and. Otra operación muy utilizada es el llamado or-exclusivo, simbolizada por xor, se definido mediante la ecuación a xor b = (a or b) and not (a and b) Informalmente significa “a o b, pero no ambos”. Obsérvese que no es necesario construir un circuito diferente para ella, ya que un circuito que realice esta función puede conseguirse enlazando puertas elementales not, and y or, combinándolas como se hace en el lado derecho de la ecuación que la define. 60406" CTKVOÖVKEC"EQP"RWGTVCU"NłIKECU Las operaciones lógicas básicas, raramente se usan de forma aislada. Se puede imaginar las combinaciones de estas operaciones como conexiones de una puerta lógica a la entrada de otra de tal manera que al atravesar el circuito una corriente eléctrica, se van abriendo o cerrando hasta obtener la operación deseada. Algunos de los circuitos lógicos, incluso en los computadores más sencillos, son extremadamente complejos y no es objeto de este curso entrar en este tema, propio
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
147
de arquitectura de computadores, sin embargo es importante retener la idea de que los elementos lógicos pueden combinarse para realizar operaciones mas complejas y que la tabla de verdad de una combinación de ellas puede determinarse a partir de las tablas de las puertas elementales. A modo de ejemplo, ahora que conocemos las puertas lógicas básicas, abordamos el diseño de una unidad que sea capaz de sumar dos enteros en binario. Por ejemplo, podríamos querer realizar la suma de la tabla 4.5, siguiendo el algoritmo de suma tradicional, sumando primero los números de la columna de más a la derecha. Para realizar este proceso, primero calculamos la suma de la primera columna, sumando los bits 1+1. Después de hacer eso, resulta evidente que se nos produce un arrastre. Por tanto, la segunda cosa que debemos hacer para conseguir sumar enteros es calcular el arrastre. Tabla 4.5.- Suma en binario 001001 010101 011110 Esta primera suma nos confirma que la suma binaria debe considerar una doble salida: el resultado de aplicar la tabla de sumar y los posibles acarreos (“llevar” de una posición a otra). Así, las reglas para la suma de dos dígitos binarios se dan en la Tabla 4.6: Tabla 4.6.- Resultados de la suma en binario Sumandos 0 0 0 1 1 0 1 1
Suma 0 1 1 0
Acarreo 0 0 0 1
Puede notarse que la columna Suma puede obtenerse mediante una puerta XOR mientras que la del acarreo coincide con una puerta AND. En la Figura 4.2 se muestra un circuito lógico con estas puertas. Un circuito de este tipo se denomina semisumador.
148
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Fig. 4.2. Esquema de un semisumador La puerta de arriba representa la puerta lógica XOR y la de abajo la puerta lógica AND Sin embargo, todavía no hemos terminado. Volviendo a la suma anterior, recordemos que debemos continuar con el proceso de suma, procediendo de derecha a izquierda a través de una serie de bits. El problema es que algunas de esas sumas producen un arrastre que debe incluirse en la siguiente suma de la izquierda. En la tabla 4.5., esto ocurre en la primera suma, es decir, la suma de los dígitos de más a la derecha, puesto que 1+1=0 mas un arrastre de 1. Este bit arrastrado debe considerarse en la suma siguiente. ¡Parece que sólo hemos realizado la mitad del trabajo!. Si queremos extender nuestro semisumador a un sumador, el problema que nos encontramos es que necesitamos poder sumar a la vez tres bits, en lugar de dos. Necesitamos una circuiteria con tres entradas (el arrastre y los dos operandos) y dos salidas (el arrastre de salida y el bit suma). Esto puede hacerse con dos semisumadores y una puerta or, como se muestra en la Figura 4.3.
Fig. 4.3. Esquema de un sumador completo, construido a partir de dos semisumadores. La puerta que proporciona el resultado del acarreo es una puerta lógica OR Es fácil ver cómo el sumador produce el bit suma. El primer semisumador produce el dígito resultante de la suma de los operandos y el segundo semisumador suma a este resultado el bit de arrastre proveniente del sumador anterior. Que el sumador produce el bit de arrastre correcto es un poco más difícil de ver. Básicamente, el
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
149
sumador debe producir un bit de arrastre 1, bien si la suma de los operando es mayor que 1, bien cuando el resultado que se produce al sumar el arrastre anterior es mayor que 1. En términos del diagrama de la Figura 4.3, significa que siempre que cualquier semisumador produzca un arrastre de 1, se debe producir una salida 1 del bit de arrastre. Este es exactamente el efecto que produce la puerta or. Esta explicación no constituye una demostración de que el sumador trabaje correctamente, ya que para demostrar que el circuito es correcto, necesitaríamos aplicarle cada uno de las ocho posibles combinaciones para los valores de los operandos y del bit de arrastre de entrada. Nuestro objetivo de diseñar una unidad operativa para sumar enteros, no está aún alcanzado, puesto que nuestro sumador todavía solo suma correctamente una posición de un número binario. Sin embargo, puesto que sabemos que el proceso de suma es idéntico para todas las posiciones, es fácil construir la unidad operativa, pues basta encadenar juntos un número apropiado de sumadores. Por ejemplo, si nuestros números binarios tuvieran 4 bits, podemos construir un sumador de números de 4 bits utilizando cuatro sumadores en cascada. Este sumador puede verse en la Figura 4.4. En este diagrama, las tres entradas para cada bit se encuentran en la parte de arriba de la caja negra correspondiente, y las dos salidas se encuentran en la parte de abajo. Obsérvese que los sumadores se han conectado de manera que simulan la forma en que realizamos la suma binaria; el bit de arrastre de cada sumador se conecta al bit de entrada de arrastre del sumador de su izquierda. Este diseño se conoce como sumador helicoidal, debido a la forma en que se produce la propagación hacia la izquierda del bit de arrastre. De forma análoga, es posible construir sumadores para enteros con mayor número de bits.
Fig. 4.4.
Esquema de un sumador en paralelo para cuatro bits.
Nótese que existe un retraso asociado al paso de los datos a través de cada puerta lógica de un circuito. Aunque los circuitos de la figura operan en paralelo, puede ser necesario pasar un acarreo a través de todos los sumadores del circuito. Lo que supone un retraso considerable y la suma deja de ser “en paralelo”. Afortunadamente existen procedimientos para resolver estos problemas.
150
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
6050" TGRTGUGPVCEKłP"FG"KPHQTOCEKłP"GP"GN EQORWVCFQT Empecemos recordando que toda información que maneja y almacena el computador, (programas y datos) se introduce por los mismos dispositivos, utilizando un mismo alfabeto o conjunto de caracteres. Para evitar este tipo de problemas toda comunicación con el computador se realiza de acuerdo con los caracteres que admiten sus dispositivos de Entrada/Salida, y solo a partir de ellos, pueden los usuarios expresar cualquier dato o instrucción. Es decir, toda instrucción o dato se representará por un conjunto de caracteres tomados del alfabeto definido en el sistema a utilizar, lo que origina que algunos caracteres a veces no puedan reconocerse ( la letra ñ por ejemplo). Los caracteres reconocidos por los dispositivos de entrada/salida suelen agruparse en cuatro categorías, dejando aparte los caracteres gráficos: Caracteres alfabéticos: Son las letras mayúsculas y minúsculas del abecedario: A, B, C,...,X,Y,Z,
a,b,c,...,x,y,z
Caracteres numéricos: Están constituidos por las diez cifras decimales: 0,1,2,3,4,5,6,7,8,9 Caracteres de control: Representan órdenes de control, como el carácter indicador de fin de línea o el carácter indicador de sincronización de una transmisión o de que se emita un pitido en un terminal, etc. Muchos de los caracteres de control son generados e insertados por el propio computador y otros lo son por el usuario como por ejemplo, el carácter producido por la tecla de borrar. Estos caracteres suelen carecer de representación visual o impresa, pero desencadenan diferentes procesos cuando aparecen. Caracteres especiales: Son los símbolos no incluidos en los grupos anteriores, entre otros, los siguientes: ) ( , ; . : -_ ! * + = Ç ¿ ? ^ SP (espacio blanco, que separa dos palabras). A los caracteres que no son de control, se les denomina caracteres-texto y al conjunto de los dos primeros tipos se le denomina conjunto de caracteres alfanuméricos. 60503" NC"EQFKHKECEKłP"GP"KPHQTOıVKEC
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
151
Vamos a presentar ahora la forma como se codifican tanto los datos como las instrucciones, por lo que distinguiremos entre unos y otras aunque todas ellas estén formados por caracteres. A la hora de codificar información (sean datos o instrucciones), parece razonable aprovechar al máximo la memoria principal del computador; por ello, el número de bits de una palabra es normalmente un múltiplo entero del numero de bits con que se representa un carácter; desde el punto de vista de la CPU, los intercambios de información con la memoria se hacen por palabras y por caracteres y de esta forma para escribir o leer un dato o instrucción almacenado en la memoria principal, basta con proporcionar la dirección de la palabra correspondiente, para que se efectúe esta operación de escritura o lectura en paralelo (de forma simultánea todos los bits que la constituyen) gracias al bus. 4.3.1.1" CÓDIGOS DE ENTRADA/SALIDA Los códigos de entrada/salida (E/S) o códigos externos son códigos que asocian a cada carácter (alfanumérico o especial) una determinada combinación de bits. En otras palabras, un código de E/S es una correspondencia entre el conjunto de todos los caracteres: 0,1,2,...9,A,B,...Y,Z.a,b,...y,z,*,”,%,... y un conjunto de n-uplas binarias pertenecientes a:
{0,1}n,
El número de elementos, m, del primer conjunto depende del número de caracteres que el dispositivo o sistema informativo utilice y n dependerá a su vez de m, ya que con n bits se puede codificar m=2n símbolos o caracteres distintos, con lo que n debe ser el menor número entero que cumpla: n≥ log2 (m) = 3.32 log (m) Tabla 4.7.- Equivalencia entre valor decimal y carácter según el código ASCII
Valor Carácter Significado dec. de control 0 NUL Nulo 1 SOH Comz. de cabecera 2 STX Comienzo de texto 3 ETX Fin de Texto 4 EOT Fin de Transm 5 ENQ Pregunta 6 ACK Confirmación posit. 7 BEL Pitido 8 BS Espacio Atrás 9 HT Tabulador Horiz. 10 LF Salto de Línea
Valor Carácdecim. ter 32 33 ! 34 “ 35 # 36 $ 37 % 38 & 39 ‘ 40 ( 41 ) 42 *
Valor Carácdecim. ter 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G 72 H 73 I 74 J
Valor decim. 96 97 98 99 100 101 102 103 104 105 106
Carácter ` a b c d e f g h i j
152 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN VT FF CR SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
Tabulador Vertical Salto de Página Retorno de Carro Shift Out Shift In Escape unión datos Contrl dispositivo 1 Contrl dispositivo 2 Contrl dispositivo 3 Contrl dispositivo 4 Confirm. negativa Sincronización Fin Bloque de Texto Cancelar Fin del Medio Sustitución Escape Separad. de ficheros Separad. de grupos Separad de registros Sep. de unidades
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
+ , . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
k l m n o p q r s t u v w x y z { | } ~ Delete
Nótese que con n=7, se puede representar hasta 128 caracteres, con lo que se puede codificar además de los caracteres habituales, determinados caracteres de control y gráficos. El más usado de estos códigos es el ASCII (American Standard Code for Information Interchange) representado en la Tabla 4.7, que incluye además un octavo bit para detectar posibles errores de transmisión o grabación. 4.3.1.2" DETECCIÓN DE ERRORES La detección y corrección automáticas de los errores que se pueden producir, especialmente durante la transmisión de datos, es una cuestión que preocupa y como acabamos de indicar interviene incluso en la propia estructura del código. Una técnica muy valiosa, que sirve de gran ayuda para conseguir este objetivo, es el concepto de código redundante 2. Un código redundante es aquel que contiene una cierta cantidad de información adicional embebida en la expresión codificada de los datos, que permite determinar, a partir del análisis de esta expresión, si los datos han sido codificados (o transmitidos) correctamente. Los códigos redundantes más sencillos y comunes requieren la inclusión de un bit de paridad en el código de cada dato. Así el bit de paridad se pone a 0 ó a 1, según que el número de unos en el dato sea par, cuando se emplea paridad par, o impar, cuando se emplea paridad impar. Por ejemplo, empleando paridad par y el bit más Un ejemplo de código redundante es el N.I.F., en el que la letra depende de los dígitos del D.N.I. (en realidad es el resto de dividir el número del D.N.I. por 23, asignando a cada número del 0 al 22 una letra distinta). Con esta letra adicional es fácil detectar algunos errores de escritura (obviamente no todos). 2
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
153
significativo como bit de paridad, el número entero 0110101, al tener cuatro unos pone el bit más a la izquierda (llamado bit más significativo) a 0 para codificarse como el 00110101; mientras que el número 1001100, al tener tres unos, pone un 1 como bit más significativo para formar el 11001100 y tener un número par de unos. De esta manera es fácil comprobar si la paridad de un dato es correcta (comprobación de paridad). Estas comprobaciones se realizan sobre todo cuando se han transmitido los datos para entrada o salida, hacia o desde un dispositivo de almacenamiento masivo o a través de una red de comunicaciones, donde los errores son más probables. Así por ejemplo, si recibimos el código 01001100 en un código con paridad par, podemos afirmar que ha habido un error al tener un número impar de unos. 4.3.1.3" ENCRIPTADO DE DATOS Para muchas aplicaciones es esencial que los datos almacenados en disco o transmitidos mediante redes de comunicaciones tengan un cierto nivel de confidencialidad. Una técnica que proporciona un cierto grado de seguridad es el encriptado de los datos, que consiste en modificar los bits que los representan de modo que no sigan ningún código estándar y por consiguiente requieran una decodificación secreta antes de poder ser interpretados. En todos los casos en que los datos se encriptan de esta forma, la codificación y decodificación se realiza de manera automática por el hardware o el software del sistema. Las técnicas de encriptado son muy variadas. Algunas utilizan algoritmos que codifican y decodifican los datos, empleando números aleatorios. Otras están basadas en los restos que se obtienen cuando se dividen los valores de los datos entre grandes números primos. La clave para códigos de esta naturaleza está constituida por un gran número entero (con más de 100 dígitos decimales) que es el producto de dos números primos, uno de los cuales se emplea para la división. Si el número clave es desconocido para el intruso el proceso de factorización es muy difícil, y toma tanto tiempo que, incluso utilizando los computadores más potentes, cuando la información se decodifica ya es inútil, por obsoleta. 60504" TGRTGUGPVCEKłP"KPVGTPC"FG"FCVQU La procedencia de los datos a ser procesados, puede ser muy distinta y tener su origen en unidades muy diferentes del computador. Así, puede ser un carácter leído de un teclado, estar almacenado en un disco o ser un número que se encuentra en memoria principal. En cualquier situación, el dato tiene la misma
154
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
representación que dependerá de su naturaleza, que como sabemos puede ser: numérica (enteros y reales), lógica y alfanumérica. Los códigos de E/S, presentan problemas cuando los aplicamos a los datos numéricos, con el objetivo de operar aritméticamente con ellos, ya que desafortunadamente su representación binaria, obtenida por un código de E/S, no es adecuada para estas operaciones. Por ejemplo basta con comparar la notación de 253 en código ASCII, con bit de paridad. 253= 10110010 00110101 00110011 y su representación binaria: 253=11111101)2 para darnos cuenta que el código de E/S, utiliza un número excesivo de bits respecto a la representación del número en sistema binario. La justificación de ello es que un código como el ASCII no está pensando para representar solamente datos numéricos. De acuerdo con lo anterior, existe la necesidad de llevar a cabo una conversión de la representación simbólica de E/S de los datos a otra, que denominamos representación interna, que depende tanto de las características del computador, como del uso que el programador desee hacer con los datos. Con ello abrimos la posibilidad de que los datos puedan codificarse de forma distinta, según estemos en un proceso de E/S o consideremos los datos ya almacenados en una de las memorias del computador. Ello supone la aparición de un nuevo problema: un mismo conjunto de bits puedan interpretarse de forma distinta según sea la situación, así por ejemplo, el valor binario 1010011 puede significar: el valor de 83 si lo consideramos como un entero positivo, el carácter “S” en ASCII, y ello sin contar que podría ser una instrucción en vez de un dato, en cuyo caso, su interpretación dependería del lenguaje máquina del procesador. Afortunadamente, esta aparente complejidad se simplifica, gracias a la introducción del concepto de tipo de dato que desarrollaremos al final de este capítulo, de forma que para todo dato manejado, obligatoriamente se ha tenido que definir previamente la naturaleza de su tipo. Esta definición implicará que su representación interna será una u otra, al tiempo que determina, como ya veremos, las operaciones que podemos realizar a partir de este dato concreto. 4.3.2.1" REPRESENTACIÓN DE DATOS ALFANUMÉRICOS Los datos de tipo alfanumérico se representan internamente con el mismo valor binario que le corresponde al dato, según el código de E/S que se utilice. Por tanto en el caso alfanumérico, no hay diferencia entre su representación interna y la derivada de su código de E/S.
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
155
4.3.2.2" REPRESENTACIÓN INTERNA DE DATOS NUMÉRICOS El ordenador distingue entre números de tipo entero (sin decimales) y de tipo real (con decimales) y utiliza representaciones internas distintas para ambos tipos. Sin embargo, cualquiera que sea la forma empleada para representar los números en un computador, hay siempre unos límites, superior e inferior, al valor que pueda ser representado. Estos límites dependen tanto de la forma como se representen como del número de bits asignados para cada número. Se emplea el término overflow (desbordamiento) cuando una operación produce un número tan grande que se sale de estos límites (el término underflow se usa en relación al límite inferior). Por ejemplo, si un computador empleara ocho bits para almacenar enteros positivos, el número más grande que podría almacenar sería 11111111 (binario) =255 (decimal). Cualquier intento de almacenar un número de más de ocho bits produciría necesariamente un desbordamiento. Al objeto de reducir los errores de desbordamiento, y de reducir el espacio que se puede malgastar, (reservando un número excesivo de bits si éstos no son necesarios), existe una cierta flexibilidad en la representación. Así por ejemplo, la mayor parte de los computadores disponen de la posibilidad de representar los números en simple y doble precisión, de forma que los números en doble precisión ocupan más bits por lo que son más exactos. Afortunadamente los lenguajes de programación permiten a los programadores tener la posibilidad de elegir el tipo de representación más adecuada para cada número en función de como lo vayan a manejar a lo largo del programa. 4.3.2.2.1" Representación de datos de tipo entero Esta representación se basa en el sistema de numeración binario, basado en la posición de cada dígito, en una palabra con un número de bits determinado. Estos datos numéricos, se suelen denominar de punto fijo, en contraposición al punto flotante, que utilizaremos para los datos numéricos reales. Existen dos alternativas de representación, según tengan o no signo, reservando en este último caso el bit más representativo para indicar una cantidad negativa. Los límites de valores enteros que podemos representar, sin caer en un overflow, dependen naturalmente de la longitudes de palabra que utilicemos. Es posible definir distintas clases de datos de tipo entero de forma que cada una de ellas tenga un rango numérico distinto. Así por ejemplo, con 16 bits se pueden representar los valores de 0 a 65536, y con 32 bits se va desde 0 a 4294967296. Nótese que al operar con aritmética de números enteros, se puede producir un overflow cuando el resultado del cálculo excede el rango de números que pueden representarse. No hay forma de prevenir los overflows, todo lo que los sistemas
156
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
operativos pueden hacer es detectarlos cuando ocurren y avisar al usuario de esta circunstancia. 4.3.2.2.2" Representación en Complementos Para representar un número entero negativo habitualmente recurrimos a añadir el signo. Sin embargo hay otras alternativas para facilitar su utilización. Normalmente se utiliza una representación diferente en binario, según que el número sea positivo o negativo, de esta forma, como se verá más adelante, las sumas y restas quedan reducidas a sumas, independientemente de los signos de los operandos. Este sistema de representación es de sumo interés, ya que al utilizarlo se reduce la complejidad de los circuitos de la ALU, pues no son necesarios circuitos específicos para restar. Llamaremos complemento a la base de un número, N, al número que resulta de restar cada una de las cifras del número N a la base menos uno del sistema que se esté utilizando y posteriormente sumar uno a la diferencia obtenida. A partir de él, se puede dar la siguiente regla: Para restar dos números se puede sumar al minuendo el complemento a la base del sustraendo despreciando, en su caso, el acarreo del resultado. Veamos, ejemplos para el caso decimal y binario: a) En decimal (complemento a 10) • Complemento a 10 del número 63 : 37. • Complemento a 10 del número 16 : 84. En efecto: 99 - 63 36 +1 = 37
99 -16 83 +1 = 84
• Supongamos que queremos efectuar las operaciones: 77-63 y 97-16. Las podemos realizar de dos formas, directamente: 77-63=14; 97-16=81 o utilizando el complemento del sustraendo y despreciando los acarreos finales: 77 - 63
77 + 37 (1)14
97 - 16
97 + 84 (1)81
b) En binario (llamada representación en complemento a 2): • Complemento a 2 del número 1010 : 0110. •
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
157
• Complemento a 2 del número 0011 : 1101. En efecto: 1111 - 1010 0101 +1= 0110
1111 - 0011 1100 +1 = 1101
• Supongamos que queremos efectuar las operaciones: 1100-1010 y 0111-0011. Las podemos realizar de dos formas, directamente o utilizando el complemento del sustraendo y despreciando los acarreos finales: 1100 - 1010
1100 + 0110 (1)0010
0111 - 0011
0111 + 1101 (1)0100
Naturalmente los ceros a la izquierda del número no son necesarios y sólo se han colocado por motivos de claridad en la exposición. En el ejemplo anterior, observamos que para transformar un número binario, N, a complemento a 2 basta con cambiar los ceros por unos y los unos por ceros de N y sumar 1 al resultado. Esto es siempre cierto y por tanto, no es necesario hacer las restas anteriores para calcular el complemento a 2, y se pueden restar dos números sólo efectuando sumas. La representación interna más común para los números enteros con signo es la binaria de complemento a dos, donde si el bit más significativo es un 0 indica un valor positivo y si es un 1 indica un entero negativo cuyo valor absoluto es el complemento a 2 del número binario representado. Así por ejemplo, con 8 bits, tendríamos que el número 00110011 en binario representa el valor +51, mientras que el número 11001001 representa el entero negativo -55, ya que el bit más significativo a 1 indica que es negativo y debemos hacer el complemento a 2 de 11001001 que es 00110111 (55 en decimal) De este modo, los valores que se pueden representar van de -32768 a +32767 para el caso de 16 bits y de 2147483648 a +2147483647 usando 32 bits. Quedaría la cuestión de como construir un circuito que calcule el complemento a dos, sabemos que el complemento a dos es igual al complemento a uno al que se le suma 1, basta con construir un circuito que calcule el complemento a uno. Para ello basta con poner una serie de puertas not en paralelo, con una señal complementaria que sirve para “disparar” la función. Es decir, cuando aparece un 1 en la línea de señal, el complemento de todas las líneas de entrada se envía al de las salidas. En caso contrario, los bits de entrada se envían a la salida sin sufrir alteración.
158
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
4.3.2.2.3" Representación de datos de tipo real Al escribir un número en forma decimal, la cantidad de cifras decimales indica la precisión del mismo. Sabemos que muchos reales no pueden representarse mediante un número finito de cifras decimales. La precisión de una fracción decimal es una medida de cuánto se aproxima la representación al valor exacto del número. Por ejemplo, en base diez, el número racional 1/3 no puede representarse de forma exacta. Sin embargo, la representación con cuatro decimales 0.3333 es más precisa que la que sólo emplea dos: 0.33. En las CPU las fracciones tienen necesariamente que almacenarse mediante un número finito de dígitos binarios. Como en el caso de las fracciones decimales, esto limita su precisión e implica que los cálculos que empleen estos números, raramente proporcionarán un resultado exacto. Para representar números reales se recurre a las técnicas basadas en el uso de números en punto flotante3 que permiten expresar un rango mayor de los números que emplean un número dado de bits. Este método es similar al empleado para representar números en base diez en notación científica, denominada método estándar, que todo usuario de una calculadora de bolsillo conoce. Un número, en método estándar, consta de dos partes: la primera es una cantidad del intervalo [1, 10[, con posibles decimales, y la segunda, una potencia de diez. Por ejemplo: 5.75 x 104 = 57 500 6.7 x 10-5 = 0.000 067 Nótese que el orden de magnitud del número está determinado por la potencia de diez, y el número de cifras significativas, o precisión de número, está determinado por el número de cifras decimales en la primera parte. Para la representación interna de números en punto flotante, se emplea el mismo principio, aunque en base dos. Así como en decimal, la primera parte es un numero comprendido entre 100 y 101 , en binario un número se expresa como el producto de dos partes: La primera es una fracción entre 1 (20 ) y 2 (21 ), llamada mantisa, y la segunda, una potencia de 2 llamada, exponente. Obsérvese, que al exigir que la mantisa esté comprendida entre 1 y 2, ello supone que escribiremos siempre los números de la forma: 1.xxxxx * 2exponente
En España, la parte decimal se separa por una coma. Sin embargo, en los países anglosajones, y por ello en la terminología informática, la parte decimal se señala con un punto. Es por ello, y para evitar confusiones, que en este libro empleamos el término punto flotante y la notación con punto en lugar de la coma. 3
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
159
donde el numero de dígitos a la izquierda del punto en la mantisa, dependerá de los bits que se hayan asignado a la representación interna de la misma) Ejemplo 6: Expresar en forma de mantisa y exponente, los siguientes números binarios: 100100 = 1.001 * 25 111.101 = 1.11101 * 22 0.00111 = 1.11 * 2-3 Como consecuencia de este convenio notacional, siempre estará presente, en la representación del numero binario, el 1 de la parte entera de la mantisa, por lo que podemos prescindir de su representación interna, pues siempre se supone que existe; con ello nos ahorramos un bit en la representación binaria del mismo. Ello nos hace insistir en la diferencia que existe entre el concepto de mantisa y la representación interna que de ella hace el computador. Afortunadamente las unidades operativas de la ALU, están diseñadas teniendo en cuenta esta diferencia. Afinando mucho se observará que el número cero no puede ser representado internamente, ello se soluciona dando una codificación especial para el 0, que depende de cada ordenador y de cada lenguaje en particular. Los siguientes ejemplos emplean cuatro bits para cada una de las partes, representadas, empleando el método de codificación conocido por signo-ymagnitud, donde un bit indica el signo y el resto de bits indican el valor absoluto, tanto de la mantisa como del exponente: Signo 0 0 0 1
MANTISA 1/2 1/4 0 0 1 0 1 1 1 1
1/8 0 1 0 1
EXPONENTE Signo 4 0 0 0 1 1 0 0 1
2 0 0 1 1
1 1 0 0 0
valor = 1 x 21 = 2 = (1 + 5/8) * 24 = 26 = (1 + 3/4) * 2-2 = 7/16 = -(1 + 7/8) * 26 = -120
La forma de representar los números en punto flotante varían notablemente entre los distintos tipos de computadores. La mantisa se codifica en signo-y-magnitud o en complemento a dos. Además, el exponente se codifica a veces en exponentes sesgados. En este método, se resta un valor fijo al código, que representa al exponente, para determinar su valor real. Por ejemplo, si se emplean ocho bits para representar el exponente, los valores almacenados pueden variar entre 0 y 255. Sin embargo si se resta un valor prefijado, 128 (el sesgo), el rango de exponentes es de -128 a 127.
160
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
El número de bits que se asignan a cada parte de un número de punto flotante también varía entre los distintos computadores. El principio general es emplear el doble o el triple de bits para la mantisa que para el exponente. Veamos un ejemplo de codificación, empleando once bits para la mantisa y cinco para el exponente, signo-y-magnitud, para ambas partes del número.
Signo 0
1/2 1
Signo 0
1/4 0 8 1
1/8 1
MANTISA 1/16 1/32 1/64 1 0 0
EXPONENTE 4 2 1 0
1/128 1/256 1/512 1/1024 0 0 0 0
1 0
=(1 + 1/2 + 1/8 + 1/16) x 212= 11/16 x 4096 = 6912 El mismo número con un exponente sesgado (con desplazamiento 16) es: Signo 0 16 1
1/2 1
1/4 0 8 1
1/8 1
1/16 1
EXPONENTE 4 2 1 0
MANTISA 1/32 1/64 0 0
1/128 1/256 1/512 1/1024 0 0 0 0
1 0
El exponente almacenado es 11100)2= 28, 12 cuando se le resta el sesgo 16. En cualquier caso, una mayor cantidad de bits en el exponente permite representar un rango mayor de valores, mientras que una cantidad mayor de bits, dedicados a la mantisa permite representar el número con mayor precisión (con más cifras significativas). Es por ello que se habla de números reales de simple precisión y de doble precisión, al utilizar el doble de bits para representarlos. El número de bits destinados a la mantisa y al exponente en cada caso dependerá de cada computador. Por ejemplo utilizando un total de 4 bytes en simple precisión se representan números en el rango de ± 3.4 . 1038 con 7 cifras significativas, mientras que en doble precisión con 8 bytes el rango va de ± 1.7 . 10308 con 15 cifras significativas.
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
161
4.3.2.2.4" Aritmética en punto flotante Operar con números de punto flotante tiene una complejidad mayor que la que vimos para números enteros, por lo que conviene que repasemos la forma como se realizan las operaciones básicas, de lo cual sacaremos conclusiones importantes: Suma de dos números Dados dos números, x, y, expresados en punto flotante (recordando que sus mantisas, pertenecen a [1, 2[) se pueden escribir de la forma: x = d1 * 2n
y = d2 * 2m
la aritmética que utilizamos manualmente cumple que si m = n
x+y = d1 2n + d2 2n = (d1 + d2) 2n
Por tanto, si los números tienen igual exponente, su suma tiene como mantisa la suma de las mantisas y como exponente el mismo. Téngase en cuenta que el resultado de (d1 + d2) puede ser mayor que 2, en cuyo caso habrá que modificar debidamente su representación, esto es, si la suma de las mantisas “no cabe” en el lugar asignado, basta con despreciar los dígitos sobrantes menos significativos (“truncar”) e incrementar en uno el exponente de acuerdo con la siguiente propiedad: d * 2n = (d/2) * 2n+1 = (d/22) * 2n+2 Esta misma propiedad, es la que nos permite conseguir que los dos números puedan expresarse con el mismo exponente, en caso de que no les coincidiera en la representación original. En el siguiente ejemplo, vamos a analizar esta operación. Ejemplo 7: Sumar los decimales 26.1875 y 12.8, expresándolos en punto flotante, en un ordenador que reserva 1 bit de signo y 5 bits para su mantisa y 1 bit de signo y 3 bits para el exponente: En primer lugar hay que pasarlos a binario (Ver Ejemplo 4) y representarlos de acuerdo con las condiciones del ordenador que manejamos, efectuando los truncamientos que pudieran ser necesarios: 26.1875 = 11010.0011)2
= 1.10100)2* 24
162
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
12.8 = 1100.110011001..)2 = 1.10011)2 * 23 26.1875 12.8
Signo 0 0
1/2 1 1
Mantisa 1/4 1/8 0 1 0 0
1/16 0 1
1/32 0 1
Signo 0 0
Exponente 4 2 1 1 0 0 0 1 1
Una vez representados internamente, como el segundo número tiene el exponente menor, se desplaza la mantisa un lugar a la derecha (perdiendo el último 1). Esto es: 1.10011)2 * 23 = 0.110011)2 * 24 = (truncando) 0.11001)2 * 24 Una vez igualados los exponentes, se suman las mantisas, obteniendo: [1.10100)2* 24 ] + [0.11001)2 * 24 ] = [1.10100)2 + 0.11001)2 ]* 24 = 10.01101 )2 * 2 4 Puesto que la mantisa desborda al intervalo [1, 2[, truncamos los bits que no pueden representarse en el espacio disponible de la mantisa, quedando: 10.01101 )2 * 24 = 1.001101 )2 * 25 = (truncando) 1.00110 )2 * 25 Al final de la operación tendremos: Signo 0
1/2 0
Mantisa 1/4 1/8 0 1
1/16 1
1/32 0
Signo 0
4 1
Exponente 2 1 0 1
Nótese que el numero arriba representado, equivale al decimal 38, cuando el resultado de la suma es: 26.1875 + 12.8 = 38.9875. La diferencia entre el resultado hallado y el correcto se debe a los dígitos perdidos en el proceso. La conclusión es importante: la suma de dos números en punto flotante origina inexactitudes. Producto de dos números: Dados dos números en punto flotante, esta operación consiste en multiplicar las mantisas de los números y sumar los exponentes, transformando el resultado en el caso de que la mantisa desborde el intervalo [1, 2[. El signo del producto viene dado por la utilización de una puerta xor sobre los bit de signo ( 0+0=0 ; 1+0=1 ; 0+1=1 ; 1+1=0) Al igual, que en el caso de la suma, vamos a analizar esta operación a través de un ejemplo.
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
163
Ejemplo 8: Multiplicar los decimales 26.1875 y 6.4 en el mismo ordenador, usado en el ejemplo anterior. 26.1875 = 11010.0011)2 = 1.10100)2* 24 6.4 = 110..110011001..)2 = 1.10011)2 * 22 Obsérvese que al ser el segundo factor, la mitad del sumando, utilizado en el ejemplo anterior (6.4 = 12.8), la diferencia en binario entre ambos, se reduce a la posición del punto. [1.10100)2* 24] * [1.10011)2 * 22] = [1.10100)2 * 1.10011)2]*24+2 = 10.1001011100)2 * 26 = Puesto que la mantisa debe pertenecer al intervalo [1,2[, en este ejemplo es necesario desplazar la mantisa una posición a la derecha, e incrementar el exponente en una unidad, para representar adecuadamente el producto; el resultado se trunca al numero de bits disponibles para la mantisa: = 10.1001011100)2 * 26= 1.01001011100)2 * 27 = (truncando) 1.01001)2 * 27 Al final de la operación tendremos: Signo 0
1/2 0
Mantisa 1/4 1/8 1 0
1/16 0
1/32 1
Signo 0
4 1
Exponente 2 1 1 1
El numero arriba representado equivale al decimal (1 + 1/4 + 1/32) * 128 = 164, mientras que el resultado del producto es 26.1875 * 6.4 = 167.6, de donde se concluye que el producto de dos números en punto flotante también origina inexactitudes. Comparando la aritmética en punto flotante con la aritmética en punto fijo obtenemos dos conclusiones: • •
La aritmética en punto flotante produce errores de truncamiento La aritmética en punto flotante emplea más cálculo que la aritmética binaria para la misma operación matemática.
Por tanto, la utilización de números reales producirá muchos más errores de cálculo que el uso de números enteros, a la vez que empleará mayor tiempo de cómputo,
164
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
por lo que siempre que se pueda, es aconsejable utilizar números enteros. La utilización de coprocesadores matemáticos, reduce el tiempo de cómputo, aunque no los errores de truncamiento, que solo pueden ser reducidos incrementando el numero de bits destinados a la representación de la mantisa. 4.3.2.2.5" Limitaciones de la representación y la aritmética en el computador Hemos visto que, siendo una herramienta muy potente y casi imprescindible para el cálculo, el uso del computador para representar valores numéricos y operar con ellos, presenta ciertas limitaciones que no deben olvidarse y que por su importancia pasamos a resumir: rango: según la representación escogida, los valores que se pueden representar van desde un mínimo hasta un máximo. Esta limitación existe tanto con números enteros como con números reales. precisión: con números reales, la limitación del número de dígitos significativos implica que no todos los valores puedan representarse exactamente. De hecho, dentro del rango de cada representación, habrá un número finito de representaciones exactas, y los números reales se representarán por la más próxima de aquellas. overflow/underflow: estos errores se producen cuando al realizar una operación con dos números en una representación determinada (forzosamente la misma para los dos) el valor resultante cae fuera del rango de la misma y por tanto no puede ser codificado en dicha representación (o es codificado incorrectamente). El Sistema Operativo es el encargado de avisar de que se ha producido uno de estos errores. truncamiento: estos errores (o inexactitudes) se producen en las operaciones con números reales donde la limitación en el número de bits que guarda la mantisa limita la precisión del resultado, en el cual se pierden las cifras menos significativas. Acabemos esta sección, haciendo notar que tanto el software como el hardware juegan un papel importante en esta aritmética. Operaciones como la suma de números enteros se realizan directamente por hardware, mientras que las operaciones que se realizan en función de otras operaciones son supervisadas por el software de la máquina. De esta forma cada tipo de computador tiene su propia mezcla de hardware y software para la ejecución de las operaciones aritméticas. Este no es un tema menor, ya que está ligado al juego de instrucciones que incorpora la circuiteria del procesador. Como ya vimos, a la hora de seleccionar
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
165
una CPU hay que optar entre CISC, una máquina compleja que sea capaz de decodificar y ejecutar una mayor variedad de instrucciones o RISC un procesador más simple con un conjunto limitado de instrucciones más utilizadas. Ambas opciones tienen sus ventajas y sus inconvenientes y en la actualidad existen máquinas con una u otra arquitectura. 4.3.2.3" REPRESENTACIÓN INTERNA DE DATOS LÓGICOS Los datos de tipo lógico representan un valor binario, es decir falso (0) o verdadero (1). Su representación varia de un computador a otro, sin embargo lo mas común es representar el 0 lógico haciendo 0 todos los bits de la palabra y el uno lógico con que al menos un bit de la palabra sea 1. 60505" TGRTGUGPVCEKłP"KPVGTPC"FG"RTQITCOCU Desde el punto de vista de la representación, vamos a distinguir entre un programa fuente y uno ejecutable. Un programa fuente representa las acciones que debe realizar el computador expresadas en un determinado lenguaje de alto nivel y como tal, es una información más, que se interpreta como un texto compuesto de caracteres. Estos son codificados al introducirlos al computador según el correspondiente código de E/S (usualmente el código ASCII) que más tarde el traductor se encargará de transformar en un ejecutable, que se representa de acuerdo con unos formatos previamente establecidos por el diseñador de la máquina. Un programa ejecutable representa las acciones a tomar ya codificadas como conjunto de instrucciones máquina, y por tanto incomprensibles para el hombre, al estar codificados con los formatos preestablecidos para la máquina concreta. El programa adopta la forma de una sucesión de bits que engloba tanto el código binario de las instrucciones máquina como datos o direcciones utilizados en el programa. Como sabemos, la forma que adopta habitualmente el código binario es la de bloques o campos. El primero de ellos es el código de operación y después de este pueden haber más campos, que indica la acción correspondiente a la instrucción. Estos campos dependerán de la operación que se trate. La representación de cada instrucción debe adecuar su formato a la función que realiza, así: • Las instrucciones de transferencia deben indicar en los campos adicionales, la fuente y el destino de su transferencia, que tendrá que ver con alguno de los siguientes lugares:
166
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
- Un registro de la ALU, dando su número. - La memoria principal, especificando el lugar (posición) de la memoria donde se encuentra el dato con el que hay que operar o que hay que transferir o donde hay que llevarlo. - Un dispositivo de entrada o de salida, dando el número del dispositivo. • En las instrucciones aritmético-lógicas, hay que indicar dónde se encuentran los operandos y dónde hay que depositar el resultado. • Para codificar una instrucción que bifurque, habrá que indicar en ella la dirección a la que hay que saltar (esto es donde se encuentra la próxima instrucción a ejecutar) o donde se encuentra esa dirección de salto. • Las instrucciones de control son mas sencillas de representar, pues dan una orden, que normalmente no depende de ningún parámetro (y puede no necesitar más campo que el código de operación). Al contrario de lo que ocurre con la representación interna de datos, en el caso de la representación de instrucciones su relación con la longitud de palabra del computador es mucho menos rígida, debido a los distintos tipos de instrucciones que existen. Una instrucción máquina puede ocupar una o varias palabras de memoria y en algunos computadores, con palabra de varios bytes, se pueden empaquetar mas de una instrucción en una misma palabra. Además, la relación que existe entre el lenguaje máquina y el hardware hace que la longitud y composición de las instrucciones varíen considerablemente de un procesador a otro, aunque siempre manteniendo las características comunes que acabamos de describir.
6060" GN"EQPEGRVQ"FG"VKRQ"FG"FCVQ A lo largo del capítulo hemos establecido tanto cómo se representan internamente en el computador los datos enteros, reales, lógicos y caracteres, como la forma de operar con ellos. Más concretamente, hemos desarrollado distintos procedimientos de suma y producto, según sean los datos de punto fijo o flotante. Por otro lado, hemos visto que las variables son una forma de representar, a alto nivel, las posiciones de memoria donde se guardan sus valores. Llegados a este punto se hace evidente la necesidad de utilizar algún mecanismo para que auxilie al ordenador en dos tareas: 1) Que al consultar el contenido de una variable o alterar su valor, pueda saber cómo interpretar el valor contenido en la/s celda/s de memoria correspondientes a esta variable. 2) Que dada una expresión aritmética, exista un mecanismo para que pueda interpretar qué tipo de operación le está permitido llevar a cabo con los operandos que le proporciona, en cada momento, la ejecución del programa (p.e. la suma entera o la suma en punto flotante).
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
167
El mecanismo para resolver ambas cuestiones se basa en la introducción del concepto de tipo de dato; entenderemos como tal, tanto la capacidad de interpretación de un patrón de bits que representan datos, como las operaciones que pueden ser llevadas a cabo por estos datos. Afortunadamente durante la parte declarativa de un programa, existe la posibilidad de definir por parte del programador la naturaleza de los datos que va a utilizar (p.e. real o entero) y, en ciertos casos, la representación interna de cada uno de ellos (p.e. real de simple o doble precisión), lo cual determina automáticamente las operaciones permitidas entre ellos y la forma como se realizan éstas. Por tanto, los lenguajes de programación deben facilitar este proceso de especificación, de forma que antes de utilizar una variable, ésta deba declararse como perteneciente a un determinado tipo. De esta forma, el computador no tendrá ninguna ambigüedad en la representación interna de los valores de la variable ni en las operaciones a realizar con ellos. Veamos el siguiente ejemplo: caso a tipo x: entero tipo y: entero tipo z: entero
caso b tipo x: real tipo y: real tipo z: real
caso c tipo x: caracter tipo y: caracter tipo z: caracter
caso d tipo x: entero tipo y: entero tipo z: caracter
z←x*y
z←x*y
z←x*y
z←x*y
En el caso a), los valores de x, y, z serán representados internamente utilizando la representación en punto fijo, y el producto se realizará con aritmética binaria. En el caso b), los valores de las variables se representarán utilizando mantisa y exponente, y el producto se realizará con aritmética de punto flotante. En el caso c), la expresión entre las variables no tiene sentido, puesto que no podemos multiplicar dos caracteres. En el caso d), la expresión z←x*y tampoco tiene sentido, puesto que aunque es posible multiplicar los valores de x e y, su resultado es entero y no puede asignarse a una variable de tipo caracter. Así, la declaración de un tipo de dato en un programa, permite: - Una forma inequívoca de representar, interpretar y tratar la información. - Ahorrar memoria (eligiendo el tipo que, pudiendo representar los valores de una variable, ocupa menos memoria) - Efectuar las operaciones de la forma más eficiente (recuérdese que aunque las operaciones parecen las mismas, la aritmética en punto flotante es más costosa que la binaria).
168
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
- Evitar errores de truncamiento, utilizando la aritmética en punto flotante sólo en los casos en que sea imprescindible. - Detectar errores en las expresiones, sin conocer cual es su valor concreto en un momento dado (casos c y d en el ejemplo anterior). En algunos casos, existe una cierta flexibilidad y es posible efectuar operaciones que involucren variables que no sean del mismo tipo. Consideremos la siguiente situación: Precio y Tasa: tipo entero Total: tipo real Total ← Precio + Tasa en este caso el compilador utiliza la suma entera y su resultado lo recodificará en un formato de punto flotante, antes de asignarlo a Total. Esta conversión implícita entre tipos se llama coerción y depende de cada lenguaje el que avise de su existencia. Como veremos, los lenguajes de programación incorporan las definiciones de tipos de datos, sin que el programador tenga que preocuparse por su representación interna, por las operaciones permitidas o por la forma de llevarlas a cabo, ya que todo ello queda encapsulado dentro de la instrucción tipo, facilitando en gran medida la tarea de programación a alto nivel. Veamos como se declaran las variables en los lenguajes que estamos considerando: FORTRAN
BASIC
tipo lista de variables tipo es INTEGER
DEFINT - enteras DEFINT - simple precisión DEFDBL - doble precisión DEFSTR - cadena
REAL DOUBLE PRECISION LOGICAL COMPLEX CHARACTER * n (n, longitud de la cadena) RCUECN xct lista_de_nombres1: tipo1;
DEF tipo rango letras, letras... tipo: INT, SNG, DBL, STR
rango
C tipo1 lista_de_nombres1; tipo2 lista_de_nombres2;
ARITMÉTICA Y REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR
169
lista_de_nombres2: tipo2; (Existe la posibilidad de definir tipos por el programador). Es de destacar que, para obtener la mayor eficiencia en la representación de los datos y su manejo, suelen suministrarse muchos tipos posibles. Por ejemplo, el lenguaje Pascal tiene 5 tipos de enteros y 5 tipos de reales. Por poner un ejemplo más concreto, un determinado compilador de lenguaje C con palabras de 16 bits permite los siguientes tipos de datos simples numéricos char unsigned char int unsigned int long
1 byte 1 byte 2 bytes 2 bytes 4 bytes
complemento a 2 sin signo complemento a 2 sin signo complemento a 2
unsigned long float double long double
4 bytes 4 bytes 8 bytes 10 bytes
sin signo simple precisión doble precisión doble precisión
enteros de -128 a 127 enteros de 0 a 255 enteros de -32 768 a 32 767 enteros de 0 a 65 535 enteros de -2 147 483 648 a 2 147 483 647 enteros de 0 a 4 294 967 295 reales (7 dígitos) en ± 3.4 . 1038 reales (15 dígitos) en ± 1.7 .10308 reales (19 dígitos) en ± 1.2 104932
Los vistos a lo largo de este capítulo son los llamados datos de tipo simple: enteros, lógicos, reales y alfanuméricos (algunos lenguajes como el C tienen un tipo simple adicional, el tipo puntero, que es un valor numérico entero sin signo para guardar una dirección de memoria - se dice que ‘apunta’ a una posición de la memoria). A partir de la agrupación de datos de tipo simple se pueden construir tipos compuestos, dotados de una cierta estructura. Sin embargo desde el punto de representación interna, el computador lo tratará siguiendo los principios que hemos descrito hasta aquí.
170
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
4.1. SISTEMAS DE NUMERACIÓN EN INFORMÁTICA ...........................137 4.1.1 DEFINICIÓN DEL SISTEMA BINARIO...................................................139 4.1.2 TRANSFORMACIONES ENTRE BASES BINARIA Y DECIMAL.........139 4.1.3 CÓDIGOS INTERMEDIOS ........................................................................141 4.2. OPERACIONES ARITMÉTICAS Y LÓGICAS ......................................143
4.2.1 OPERACIONES ARITMÉTICAS CON NÚMEROS BINARIOS .............143 4.2.2 VALORES BOOLEANOS Y OPERACIONES LÓGICAS ........................144 4.2.3 PUERTAS LÓGICAS ..................................................................................145 4.2.4 ARITMÉTICA CON PUERTAS LÓGICAS ...............................................146
4.3. REPRESENTACIÓN DE INFORMACIÓN EN EL COMPUTADOR ..150 4.3.1 LA CODIFICACIÓN EN INFORMÁTICA ................................................150 4.3.2 REPRESENTACIÓN INTERNA DE DATOS ............................................153 4.3.3 REPRESENTACIÓN INTERNA DE PROGRAMAS ................................165
4.4. EL CONCEPTO DE TIPO DE DATO.......................................................166
CAPÍTULO 5
ESTRUCTURAS DE DATOS
En la práctica, la mayor parte de información útil no aparece aislada en forma de datos simples, sino que lo hace de forma organizada y estructurada. Los diccionarios, guías, enciclopedias, etc., son colecciones de datos que serían inútiles si no estuvieran organizadas de acuerdo con unas determinadas reglas. Además, tener estructurada la información supone ventajas adicionales, al facilitar el acceso y el manejo de los datos. Por ello parece razonable desarrollar la idea de la agrupación de datos, que tengan un cierto tipo de estructura y organización interna. Como tendremos ocasión de ver, la selección de una estructura de datos frente a otra, a la hora de programar es una decisión importante, ya que ello influye decisivamente en el algoritmo que vaya a usarse para resolver un determinado problema. El objetivo de este capítulo no es sólo la descripción de las distintas estructuras, sino también la comparación de las mismas en términos de utilidad para la programación. De hecho, se trata de dar una idea, acerca de los pros y contras de cada una de ellas con el propósito final de justificar la ya citada ecuación de: PROGRAMACION = ESTRUCTURAS DE DATOS + ALGORITMOS
7030" GN"EQPEGRVQ"FG"FCVQU"GUVTWEVWTCFQU0 Empecemos recordando que un dato de tipo simple, no esta compuesto de otras estructuras, que no sean los bits, y que por tanto su representación sobre el ordenador es directa, sin embargo existen unas operaciones propias de cada tipo, que en cierta manera los caracterizan. Una estructura de datos es, a grandes rasgos, una colección de datos (normalmente de tipo simple) que se caracterizan por su organización y las operaciones que se definen en ellos. Por tanto, una estructura de datos vendrá caracterizada tanto por unas ciertas relaciones entre los datos que la constituyen (p.e., el orden de las componentes de un vector de números reales), como por las operaciones posibles en ella. Esto supone que podamos expresar formalmente, mediante un conjunto de reglas, las relaciones y operaciones posibles (tales como insertar nuevos elementos o como eliminar los ya 171
172
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
existentes). Por el momento y a falta de otros, pensemos en un vector de números, como el mejor ejemplo de una estructura de datos. Llamaremos dato de tipo estructurado a una entidad, con un solo identificador, constituida por datos de otro tipo, de acuerdo con las reglas que definen cada una de las estructuras de datos. Por ejemplo: una cadena esta formada por una sucesión de caracteres, una matriz por datos simples organizados en forma de filas y columnas y un archivo, está constituido por registros, éstos por campos, que se componen, a su vez, de datos de tipo simple. Por un abuso de lenguaje, se tiende a hacer sinónimos, el dato estructurado con su estructura correspondiente. Aunque ello evidentemente no es así, a un primer nivel, en este libro, asumiremos esta identificación. Para muchos propósitos es conveniente tratar una estructura de datos como si fuera un objeto individual y afortunadamente, muchos lenguajes de programación permiten manipular estructuras completas como si se trataran de datos individuales, de forma que los datos estructurados y simples se consideran a menudo por el programador de la misma manera. Así a partir de ahora un dato puede ser tanto un entero como una matriz, por nombrar dos ejemplos. Las estructuras de datos son necesarias tanto en la memoria principal como en la secundaria, de forma que en este capítulo nos centraremos en las correspondientes a la memoria principal, dejando para el capítulo siguiente las estructuras más adecuadas para el almacenamiento masivo de datos.
7040" VKRQU"FG"FCVQU"GUVTWEVWTCFQU Los datos de tipo simple tienen una representación conocida en términos de espacio de memoria. Sin embargo, cuando nos referimos a datos estructurados esta correspondencia puede no ser tan directa; por ello vamos a hacer una primera clasificación de los datos estructurados en: contiguos y enlazados. Las estructuras contiguas o físicas son aquellas que al representarse en el hardware del ordenador, lo hacen situando sus datos en áreas adyacentes de memoria; un dato en una estructura contigua se localiza directamente calculando su posición relativa al principio del área de memoria que contiene la estructura. Los datos se relacionan por su vecindad o por su posición relativa dentro de la estructura. Las estructuras enlazadas son estructuras cuyos datos no tienen por qué situarse de forma contigua en la memoria; en las estructuras enlazadas los datos se relacionan unos con otros mediante punteros (un tipo de dato que sirve para ‘apuntar’ hacia otro dato y por tanto para determinar cuál es el siguiente datos de la estructura). La localización de un dato no es inmediata sino que se produce a través de los punteros que relacionan unos datos con otros.
ESTRUCTURAS DE DATOS
173
Los datos estructurados se pueden clasificar, también, según la variabilidad de su tamaño durante la ejecución del programa en: estáticos y dinámicos. Las estructuras estáticas son aquellas en las que el tamaño ocupado en memoria, se define con anterioridad a la ejecución del programa que los usa, de forma que su dimensión no puede modificarse durante la misma (p.e., una matriz) aunque no necesariamente se tenga que utilizar toda la memoria reservada al inicio (en todos los lenguajes de programación las estructuras estáticas se representan en memoria de forma contigua). Por el contrario, ciertas estructuras de datos pueden crecer o decrecer en tamaño, durante la ejecución, dependiendo de las necesidades de la aplicación, sin que el programador pueda o deba determinarlo previamente: son las llamadas estructuras dinámicas. Las estructuras dinámicas no tienen teóricamente limitaciones en su tamaño, salvo la única restricción de la memoria disponible en el computador. Estas dos clasificaciones nos ayudarán a exponer los distintos tipos de datos estructurados, incidiendo en las ventajas e inconvenientes para su almacenamiento y tratamiento, en términos de la eficacia de una determinada aplicación ya sea de economía espacial (no emplear más memoria de la necesaria) o temporal (emplear el menor tiempo posible en las operaciones).
7050" GUVTWEVWTCU"FG"FCVQU"EQPVKIWCU Vamos a estudiar una serie de agrupaciones de datos que son utilizadas en todos los lenguajes de programación, y que tienen en común la ubicación de sus datos en zonas de memoria adyacentes. 70503" Ecfgpcu La cadena es quizás la estructura más simple y se define como una secuencia de caracteres que se interpretan como un dato único. Su longitud puede ser fija o variable por lo que, además de saber que están constituidas por caracteres alfanuméricos, hemos de conocer su longitud. En una variable tipo cadena se puede almacenar una palabra, una frase, una matricula de coche, una temperatura, etc. La longitud de una cadena se puede determinar bien indicando al principio de la misma el número de caracteres que contiene, bien situando un carácter especial denominado fin-de-cadena. Los siguientes ejemplos muestran los dos métodos de representar la cadena “Capital 94” :
174 10
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN C
a
p
Long.
i
t
a
l
9
4
C
a
p
Cadena (long. = 10)
i
t
a
l
9
4
# Fin de cadena
Cadena (long. = 10)
en el segundo caso el carácter elegido como fin-de-cadena ha sido el #. La cadena que no contiene ningún carácter se denomina cadena vacía y su longitud es 0, que no tiene que ser confundida por una cadena formada sólo por blancos (o espacios), cuya longitud es igual al número de blancos que contiene. De esta manera, una variable de tipo cadena de tamaño 10 puede guardar cadenas de 10 caracteres, pero también de menos si indicamos dónde terminan los caracteres de la cadena. Por ejemplo la cadena “Jaca 99”: 7 Long.
J
a
c
a
9
Cadena (long. = 7)
9
J Libre
a
c
a
9
Cadena (long. = 7)
9
# Fin de cadena
Libre
Sobre datos de tipo cadena se pueden realizar las siguientes operaciones: Asignación: Guardar una cadena en una variable tipo cadena. Como en toda asignación a una variable, la cadena que se guarda puede ser una constante, una variable tipo cadena o una expresión que produzca un dato tipo cadena. Por ejemplo: nombre ← “Pepe” nombre ← mi-nombre-de-pila Concatenación: Formar una cadena a partir de dos ya existentes, yuxtaponiendo los caracteres de ambas. Si se denota por // al operador “concatenación”, el resultado de: “ab” // “cd” es “abcd” Nótese que las constantes de tipo cadena se escriben entre comillas, para no confundirlos con nombres de variables u otros identificadores del programa. Extracción de subcadena: Permite formar una cadena (subcadena) a partir de otra ya existente. La subcadena se forma tomando un tramo consecutivo de la cadena inicial. Si NOMBRE es una variable de tipo cadena que contiene “JUAN PEDRO ORTEGA” y denotamos por (n:m) la extracción de m caracteres tomados a partir del lugar n, entonces NOMBRE(6:5) es una subcadena que contiene “PEDRO”.
ESTRUCTURAS DE DATOS
175
Un caso particular de extracción que se utiliza a menudo es el de extraer un único caracter. Por ello se suele proporcionar un método directo: el nombre seguido por el lugar que ocupa dentro de la cadena. Así, en el ejemplo anterior, NOMBRE(6) = “P” = NOMBRE(6:1) Obtención de longitud: La longitud de una cadena es un dato de tipo entero, cuyo valor es el número de caracteres que contiene ésta. En el primero de los dos métodos anteriores de representación de cadenas, la longitud se obtiene consultando el número de la primera casilla; en el segundo método la longitud es el número de orden que ocupa el caracter de fin-de-cadena, menos uno. Comparación de cadenas: Consiste en comparar las cadenas carácter a carácter comenzando por el primero de la izquierda, igual que se consulta un diccionario. El orden de comparación viene dado por el código de E/S del ordenador (ASCII habitualmente). Así la expresión booleana: “Jose” < “Julio”, se evaluara como verdadera. Nótese que en los códigos de E/S, las mayúsculas y las minúsculas son diferentes, dando lugar a resultados paradójicos en la comparación, así pues, si el código de E/S es ASCII, donde las mayúsculas tienen códigos inferiores a las minúsculas, se cumpliría que “Z” < “a”. 70504" Cttc{u3 Es un conjunto de datos del mismo tipo almacenados en la memoria del ordenador en posiciones adyacentes. Sus componentes individuales se llaman elementos y se distinguen entre ellos por el nombre del array seguido de uno o varios índices o subíndices. Estos elementos se pueden procesar, bien individualmente, determinando su posición dentro del array, bien como array completo. El número de elementos del array se específica cuando se crea éste, en la fase declarativa del programa, definiendo el número de dimensiones o número de índices del mismo y los límites máximo y mínimo que cada uno de ellos puede tomar, que llamaremos rango. Según sea este número, distinguiremos los siguientes tipos de arrays: - unidimensionales (vectores) - bidimensionales (matrices) - multidimensionales Por ello hablaremos de arrays de dimensión 1, 2 ó n, cuyo producto por el rango (o rangos) especifica el número de elementos que lo constituyen. Este dato lo utiliza el compilador para reservar el espacio necesario para almacenar en memoria todos Utilizaremos el término array, ya que su traducción castellana, “arreglo, colección, etc” es poco significativa y la imensa mayoría de veces se usa en el argot informático el anglicismo array. Sin embargo, sí usaremos vector y matriz para referirnos a determinados tipos de array. 1
176
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
sus elementos ocupando un área contigua. Cada elemento ocupa el mismo número de palabras, que será el que corresponda al tipo de éstos. No debemos olvidar que, al nivel físico, la memoria es lineal; por ello los elementos se colocan en la memoria linealmente según un orden prefijado de los índices. En el ejemplo de la figura el número de dimensiones es 2, su rango (3;5) y el número total de elementos es 15. elemento (2,4) 1 2 3 1 2 3 4 5 Fig. 5.1. Representación de un array bidimensional. 5.3.2.1" Vectores Por motivos de simplicidad y mayor frecuencia de uso, a la hora de revisar las operaciones con arrays nos centraremos en los vectores, que además presentan la ventaja de ser estructuras ordenadas (sólo existe orden total cuando tenemos estructuras de una dimensión o lineales). Las notaciones algorítmicas que utilizaremos son: nombre_vector =
vector [inf . . sup] de tipo
nombre_vector inf .. sup
nombre válido del vector límites inferior y superior del rango (valor entero que puede tomar el índice) tipo de los elementos del vector {entero, real, carácter}
tipo
La declaración de un vector supone una mera reserva de espacio, pudiéndose asumir que, hasta que asignemos valores por cualquier mecanismo a sus distintos elementos, estamos ante una estructura vacía. NUMEROS = vector [1..10] de real significa que NUMEROS es un vector, que podrá contener como elementos, al menos 10 números de tipo real, cuyos índices varían desde el 1 hasta el 10. FORTRAN
BASIC
ESTRUCTURAS DE DATOS
REAL X(10)
177
DIM X(10) AS SINGLE
RCUECN x: array[1..10] of real
C loat x[10]
Es importante señalar que podemos implementar arrays cuyos elementos sean a su vez cadenas o elementos de otro tipo, así podemos pensar en situaciones como la siguiente, durante la fase declarativa tipo: palabra = cadena[16] COCHES = vector [1..9] de palabra lo que nos permitirá manipular en un solo vector hasta 9 cadenas conteniendo como máximo 16 caracteres cada una. Con lo cual COCHES puede contener una información tal como: 1 2 3 4 5 6 7 8 9
Alfa Romeo Fiat Ford Lancia Renault Seat
Ya hemos dicho que las operaciones sobre arrays se pueden realizar con elementos individuales o sobre la estructura completa mediante las correspondientes instrucciones y estructuras de control del lenguaje. Las operaciones sobre elementos del vector son: Asignación: Tiene el mismo significado que la asignación de un valor a una variable no dimensionada, ya que un vector con su índice representa la misma entidad que una variable no dimensionada. A[20] ← 5 asigna el valor 5 al elemento 20 del vector A A[17] ← B asigna el valor de la variable B al elemento 17 del vector A Acceso secuencial o recorrido del vector: Consiste en acceder a los elementos de un vector para someterlos a un determinado proceso, tal como introducir datos (escribir) en él, visualizar su contenido (leer), etc.. A la operación de efectuar una acción general sobre todos los elementos de un vector se la denomina recorrido y para ello utilizaremos estructuras repetitivas, cuyas variables de control se utilizan
178
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
como subíndices del vector, de forma que el incremento del contador del bucle producirá el tratamiento sucesivo de los elementos del vector. Ejemplo 1: Escribir un algoritmo para recorrer secuencialmente un vector H de 10 elementos (haciendo la lectura y escritura de cada elemento) primero con un bucle desde y luego con un bucle mientras. El pseudocódigo correspondiente será el siguiente: desde i ← 1 hasta 10 hacer leer (H[i]) escribir (H[i]) fin_desde
i←1 mientras i EOF hacer leer registro R ir a subprograma de obtención de la dirección leer registro S si R=S entonces llamar_a subprograma consulta sino leer área de sinónimos si FF en área de sinónimos ¨{no hay ningún sinónimo} entonces escribir “registro no existe” sino llamar_a subprograma consulta fin_si fin_si fin_mientras 6.6.3.3" ALTAS Para dar de alta a un registro, se debe introducir su número de orden y contenido. La inserción de un registro en el archivo, supone utilizar un campo adicional, alta/baja del registro, SW (interruptor) que tome el valor 1 ó 0, según que el registro esté dado de alta o de baja, ya que como veremos a continuación, la baja es solo de caracter lógico y es posible que demos de alta un registro que ya existe, pero que fue dado de baja anteriormente. El algoritmo para llevar a cabo el procedimiento de dar de alta es:
232
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
algoritmo altas inicio SW = 0 repetir leer” número de registro a dar de alta”. NR si 1 56, luego debemos intercambiarlos. 3.- El proceso continúa hasta que cada elemento del vector ha sido comparado con sus elementos adyacentes y se han realizado los intercambios necesarios.
ALGORITMOS Y SU COMPLEJIDAD
253
Los sucesivos contenidos del vector a lo largo de estos pasos, son los siguientes: A(8) A(7) A(6) A(5) A(4) A(3) A(2) A(1)
v.inicial 1ªcomp. 2ªcomp. 3ªcomp 4ªcomp 5ªcomp 6ªcomp 7ªcomp 9 9 9 9 9 9 9 1 12 12 12 12 12 12 1 9 1 1 1 1 1 1 12 12 35 35 35 35 14 14 14 14 14 14 14 14 35 35 35 35 56 56 15 15 15 15 15 15 15 15 56 56 56 56 56 56 50 50 50 50 50 50 50 50
Como se observa, al finalizar estos pasos, el elemento, cuyo valor sea menor, 1 en nuestro ejemplo, acabará ocupando la última posición; utilizando un símil físico, podemos decir que como elemento más ligero, sube hacia arriba, al igual que las burbujas de aire en un depósito o botella de agua. Por esta razón este algoritmo se conoce también como método de la burbuja. Por tanto, en el caso de una ordenación descendente, a partir de un vector cualquiera de N elementos, tras realizar un recorrido completo por todo el vector, conseguimos que el menor de ellos, quede situado en la última posición, que es la que le corresponderá, cuando el vector quede ordenado. En el segundo recorrido, sólo será necesario abarcar a los N-1 elementos restantes, y en él el segundo valor más pequeño llegará a la penúltima posición. Siguiendo de esta manera acabaremos ordenando el vector, colocando en cada iteración, secuencialmente, un valor en su posición definitiva. Basándonos en la anterior, podemos proponer un algoritmo de ordenación basado en dos etapas iterativas, una anidada dentro de la otra: 1ª etapa Dado un vector de N elementos Hacer el recorrido de la burbuja (ver segunda etapa) primero para N elementos, luego para el subvector con los N-1 primeros, luego para N-2, etc., hasta 2 elementos. 2ª etapa Recorrido de la burbuja: para los elementos a recorrer, comparar cada uno con el siguiente e intercambiarlos si contravienen el orden requerido. Mostrar la transformación del vector del ejemplo 2 de ordenación descendente, tras dos ejecuciones del bucle interno (etapas o recorridos de la burbuja) del método de intercambio3: En el ejemplo podemos apreciar que tras dos iteraciones de la etapa externa el vector ya está ordenado y no haría falta continuar con más recorridos, con el consiguiente ahorro de tiempo. La observación de este hecho permitiría escribir un algoritmo de menor coste en el caso medio, aunque no en el peor caso.. 3
254
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
estado inicial 9 12 1 35 14 56 15 50
después de 1ª etapa 1 9 12 14 35 15 56 50
después de 2ª etapa 1 9 12 14 15 35 50 56
La etapa interna, en un primer diseño sería: desde I=1 hasta R-1 hacer {siendo R el número de elementos a recorrer} si A(I) < A(I+1) entonces intercambiar A(I) con A(I+1) fin_desde La acción intercambiar los valores de dos elementos A(I), A(I+1), es muy común y al objeto de que no se pierda ningún valor, hay que reseñar que debe recurrirse a una variable auxiliar, AUX, de forma que el procedimiento sería: AUX← A(I) A(I)← A(I+1) A(I+1)← AUX Estamos en condiciones de presentar el algoritmo completo, donde cada una de las etapas viene representada por sendos bucles anidados. En esta ocasión, una vez justificado el nombre de burbuja, vamos a ordenar de forma ascendente, esto es de menor a mayor (la forma más habitual). algoritmo burbuja {ordenación ascendente} inicio desde I=1 hasta N hacer { lectura del vector} leer X(I) fin-desde desde I=1 hasta N-1 hacer desde J=1 hasta N-I hacer {no utilizamos los elementos ya ordenados} si X(J) > X(J+1) entonces {intercambiar} AUX← X(J) X(J) ←X(J+1) La inclusión de la mejora correspondiente no se contempla en este texto y queda como un ejercicio para el lector
ALGORITMOS Y SU COMPLEJIDAD
255
X(J+1) ←AUX fin-si fin-desde fin-desde desde J = 1 hasta N hacer {imprimir vector ordenado} escribir X(J) fin-desde fin Para analizar la complejidad del algoritmo, vamos a considerar el número de comparaciones que efectuamos. Como puede observarse el número de comparaciones será el mismo para cualquier vector, sólo las asignaciones cambiarán siendo en el peor caso tres por cada comparación, con lo que serán del mismo orden de magnitud. Este método de ordenación, para un vector de n elementos, precisa el siguiente numero de comparaciones (observando los valores que toman los contadores I y J respectivamente): (1 + 2 +
+ n-1) = (n -1) * (n/2)
Por tanto, estamos, de nuevo, con un algoritmo cuadrático, por lo que es interesante que mejoremos su eficacia. Ejemplo 3: Describir los diferentes pasos para clasificar en orden ascendente el vector siguiente por el método de la burbuja. A(1) 72
A(2) 64
A(3) 50
A(4) 23
A(5) 84
A(6) 18
A(7) 37
A(8) 99
A(9) 45
A(10) 8
Las sucesivas operaciones en cada uno de los pasos necesarios hasta obtener la clasificación final se muestra en la siguiente tabla, donde aparecen sombreados los elementos que ya quedan ordenados y por tanto ya no se recorren:
A(1) A(2) A(3) A(4) A(5) A(6)
vector 1 rec. 2 rec. 3 rec. inicial i=1 i=2 i=3 i=4 72 64 50 23 23 64 50 23 50 18 50 23 64 18 37 23 72 18 37 50 84 18 37 64 45 18 37 72 45 8
i=5 18 23 37 49 8 50
i=6 18 23 37 8 41 50
i=7 18 23 8 37 41 50
Fin de i=8 ordenación 18 8 8 18 23 23 37 37 41 41 50 50
256 A(7) A(8) A(9) A(10)
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
37 99 45 8
84 45 8 99
45 8 84 99
8 72 84 99
64 72 84 99
64 72 84 99
64 72 84 99
64 72 84 99
64 72 84 99
64 72 84 99
90405" CNIQTKVOQ"FG"UJGNN Tanto el método de inserción como de la burbuja son de orden cuadrático y en consecuencia su tiempo de ejecución se dispara cuando el número de elementos a ordenar es grande. Con el objetivo de mejorar ambas aproximaciones Donald Shell propuso un nuevo método que lleva su nombre y que, sin mejorar la complejidad en el peor caso, se comporta de forma sensiblemente más eficaz en la gran mayoría de casos. Shell cayó en la cuenta que en los métodos anteriores, el comparar cada elemento con su contiguo supone, en promedio, ejecutar muchas comparaciones antes de colocar los elementos extremos en su lugar definitivo. Su propuesta consiste en modificar los saltos contiguos resultantes de las comparaciones, por saltos de mayor tamaño y con ello conseguir una ordenación más rápida. El método se basa en fijar el tamaño de los saltos constantes, pero de más de una posición. Supongamos un vector de elementos 4
12
16
24
36
3
en el método de inserción directa, los saltos se hacen de una posición en una posición y se necesitarán 5 comparaciones. En el método de Shell, si los saltos son de dos posiciones, se realizarán tres comparaciones. El método de Shell se basa en tomar como salto inicial el valor N/2 (siendo N el número de elementos) y luego se va reduciendo a la mitad en cada repetición hasta que el salto o distancia se reduce a 1. Por tanto habrá que manejar la variable salto, de forma que los recorridos no serán los mismos en cada iteración. Sea un vector: X(1), X(2), X(3),..., X(N). El primer salto a dar que tendrá un valor de: 1+N 2 por lo que para redondear, se tomará la parte entera
1+N 2
ALGORITMOS Y SU COMPLEJIDAD
257
E = ent((1 + N)/2) El algoritmo resultante será: subalgoritmo Shell inicio E ← N+1 mientras E > 1 E ← ent(E/2) repetir ID ← verdadero I←1 desde I = 1 hasta N-E si X(I) > X(I+E) entonces AUX ← X(I) X(I) ← X(I+E) X(I+E) ← AUX ID ← falso fin-si fin-desde hasta-que (ID = verdadero) fin-mientras fin Ejemplo 4: Deducir las secuencias parciales de clasificación por el método de Shell para ordenar de modo ascendente el vector siguiente 6
1
5
2
3
4
0
Estas son: Nº de Recorrido vector inicial 1 2 3 4 5
Salto 3 3 3 1 1
vector resultante 6,1,5,2,3,4,0 2,1,4,0,3,5,6 0,1,4,2,3,5,6 0,1,4,2,3,5,6 0,1,2,3,4,5,6 0,1,2,3,4,5,6
intercambios (6,2),(5,4),(6,0) (2,0) ninguno (4,2),(4,3) ninguno
258
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
Aunque el análisis de la complejidad (que no haremos con detalle), nos demostraría que para el peor caso, el algoritmo de Shell es cuadrático y no mejoraría los algoritmos de ordenación anteriores (seguimos trabajando con dos bucles anidados), como hemos dicho, en la práctica se puede comprobar que la modificación propuesta por Shell supone una mejora importante para vectores no especialmente desordenados.
90406" CNIQTKVOQ"FG"QTFGPCEKłP"TıRKFC"*›SWKEMUQTVfi+ Este nuevo método, de naturaleza recursiva, llamado ordenación rápida (quicksort) propuesto por Hoare se basa en el hecho práctico de que es más rápido y fácil de ordenar dos listas pequeñas que una lista grande. Se denomina de ordenación rápida porque, en general, puede ordenar una lista de datos mucho más rápidamente que cualquiera de los métodos de ordenación ya estudiados; de hecho, se puede demostrar que su complejidad para el caso medio es O(n* log(n)). El método es un ejemplo de la estrategia “divide y vencerás” de forma que el vector inicial se divide en dos subvectores: uno con todos los valores, menores o iguales a un cierto valor específico, llamado pivote, y otro con todos los valores mayores que él. El valor elegido puede ser cualquier valor arbitrario dentro del vector. El primer paso consiste en seleccionar el valor pivote y en dividir el vector original V en dos partes: • El subvector VI que sólo contiene valores inferiores o iguales al valor pivote • El subvector VD que sólo contiene valores superior o iguales al valor pivote Notemos que los subvectores VI y VD no estarán ordenados, excepto en el caso de reducirse a un único elemento (obviamente ordenado), circunstancia ésta que será especialmente útil. Ejemplo 5: Consideremos el siguiente vector y llevemos a cabo la anterior descomposición de valores. 18
11
27
13
9
4
16
15
25
Se elige un valor pivote, por ejemplo 13. Se recorre la lista desde el extremo izquierdo y se busca un elemento mayor que 13 (el primero es 18). A continuación, se busca desde el extremo derecho un valor menor que 13 (se encuentra el 4). Se intercambian estos dos valores y se obtiene:
ALGORITMOS Y SU COMPLEJIDAD
4
11
27
13
9
18
16
15
259
25
Se sigue recorriendo el vector por la izquierda y se localiza el 27, y a continuación se busca por la derecha otro valor menor que el pivote, se encuentra a la derecha (el 9); al intercambiar estos dos valores se obtiene: 4
11
9
13
27
18
16
15
25
Al intentar este proceso una vez más, nos encontramos con que la búsqueda desde la izquierda de valores mayores que el pivote como la búsqueda desde la derecha de valores menores que el pivote, acaban cruzándose, esto es, las exploraciones desde los dos extremos acaban sin encontrar ningún valor que esté “fuera de lugar”. Por tanto, lo que tenemos es que todos los valores a la derecha son mayores que todos los valores a la izquierda del pivote. Por tanto, el objetivo se ha alcanzado, ya que tenemos una partición del vector original en dos listas más pequeñas: 4
11
9
[13]
27
18
16
15
25
Obsérvese que nadie nos garantiza que la situación anterior, donde el número de valores por encima del pivote a su izquierda ha coincidido con el número de valores menores que él a su derecha, se produzca para cualquier vector. Por ello el algoritmo tiene que incluir algún mecanismo para que el propio pivote se sitúe en su posición correcta. Siguiendo con el ejemplo anterior, hemos conseguido que el elemento pivote tenga valores menores delante y valores mayores detrás. Sin embargo, nos encontramos con que ninguna de ambas listas a la izquierda y a la derecha están ordenadas; sin embargo, basados en los resultados de la primera partición, se puede ordenar ahora cada una de las dos partes de forma independiente. Esto es, debemos ordenar los subvectores 4 27
11 18
9 16
15
25
para acabar con el vector totalmente ordenado. Para un caso totalmente general, vamos a esbozar en primer lugar el método para conseguir una partición del vector en VI -VD: establecer como pivote X el valor de un elemento arbitrario de la lista mientras la partición no se termina hacer recorrer desde la izquierda y buscar un valor >= X
260
FUNDAMENTOS DE INFORMÁTICA Y PROGRAMACIÓN
recorrer desde la derecha y buscar un valor