Resumen Sebesta u 7 y 8

October 21, 2017 | Author: pomelo09 | Category: Programming Language, Compiler, Java (Programming Language), C++, Multiplication
Share Embed Donate


Short Description

Descripción: resumen de sebesta de la unidad 7 y 8 de lenguajes de programacion....

Description

Capítulo 7 Expresiones y Sentencias de Asignación Perfil del Capítulo 7.1 Introducción 7.2 Expresiones Aritméticas 7.3 Sobrecarga de Operadores 7.4 Conversión de Tipos 7.5 Expresiones Booleanas y Relacionales 7.6 Evaluación de Corto-Circuito 7.7 Sentencias de Asignación 7.8 Sentencia Mezclada de Asignación Como el título lo indica, el enfoque de este capítulo son las expresiones y las sentencias de asignación. Las reglas de semántica que determinan el orden de evaluación de los operadores en expresiones son discutidas primero. Esto se sigue por los problemas potenciales de implementación-definida por el orden de evaluación de operandos cuando las expresiones pueden tener los efectos colaterales. La sobrecarga de operadores, ambos predefinidos y definidos por el usuario, se discute entonces, junto con sus efectos en las expresiones de los programas. Luego, se discuten las expresiones del modo-mezclado y se evalúan. Esto lleva a la definición y evaluación de ampliación y reducción de las conversiones del tipo, implícito y explícito. Las expresiones Boolean y Relacional son entonces discutidas, incluyendo la idea de evaluación del cortocircuito. Finalmente, la sentencia de asignación, desde su forma más simple hasta todas sus variaciones, se cubre, incluso las asignaciones como las expresiones y asignaciones del modo-mezclado. El material en este capítulo se restringe a lo convencional ni lenguajes de programación funcional y ni lógica, los cuales utilizan notación infija para las expresiones lógicas y aritméticas. Los problemas de especificación de laexpresión y evaluación en lenguajes funcional y lógico se discuten en los Capítulos 15 y 16, respectivamente. Cadenas de caracteres estructuran el modelo de las expresiones discutidas como una parte del material de cadenas de caracteres en el Capítulo 6, por lo que ellos no se mencionan en este capítulo. 7.1 Introducción Las expresiones son los medios fundamentales de especificar los cómputos en un lenguaje de la programación. Es crucial para un programador entender la sintaxis y semántica de las expresiones. Se describieron métodos de describir la sintaxis de las expresiones en el Capítulo 3. En este capítulo, nos enfocamos en la semántica de las expresiones-esto es, que significan ellos-lo cual es determinado por cómo ellos son evaluados. Para entender la evaluación de la expresión, es necesario estar familiarizado con el orden de evaluación del operador y del operando. El orden de evaluación del operador de expresiones es manejado por la asociatividad y las reglas de

precedencia del lenguaje. Aunque el valor de una expresión a veces depende de él, el orden de evaluación del operando en las expresiones es a menudo no expuesto por los diseñadores del lenguaje, una situación que permite a los programas producir los resultados diferentes en implementaciones diferentes. Otros problemas en la semántica de las expresiones son las desigualdades del tipo, coerciones, y evaluación del cortocircuito. La esencia de los lenguajes de la programación imperativos es el papel dominante de sentencias de asignación. El propósito de una sentencia de asignación es cambiar el valor de unavariable. Así una parte íntegra de todos los lenguajes imperativos es el concepto de variables cuyos valores cambian durante la ejecución del programa. (Los lenguajes imperativos a veces incluyen variables de una clase diferente, como los parámetros de funciones en los lenguajes funcionales.) Una sentencia de asignación puede causar un valor simplemente para ser copiado de una celda de memoria a otra. Pero en muchos casos, las sentencias de asignación incluyen las expresiones con operadores, las cuales causan valores para ser copiados al procesador y para ser operados antes, y los resultados para ser copiados como apoyo a la memoria. Las sentencias de asignación simples especifican una expresión a ser evaluada y una localización designada en la cual colocar el resultado de la evaluación de expresión. Como veremos en este capítulo, hay varias variaciones en este forma básica. 7.2 Expresiones Aritméticas La evaluación automática de expresiones aritméticas similar a aquellas encontrada en matemática fue una de las metas primarias en la era de los primeros lenguajes de la programación de alto nivel. La mayoría de las características de las expresiones aritméticas en los lenguajes de programación fueron heredadas de convenciones que habían evolucionado en la matemática. En lenguajes de programación, las expresiones aritméticas consisten en operadores, operandos, paréntesis, y llamadas a función. Los operadores pueden ser unarios, significando que ellos tienen un solo operando, o binario, significando que ellos tienen dos operandos. C, C++, y Java incluyen a un operador ternario que tiene tresoperandos como lo discutidos en la Sección 7.2.1.4. En más lenguajes de programación imperativos, los operadores binarios son infijos, lo cual significa que ellos aparecen entre sus operandos. Una excepción es Perl, el cual tiene algunos operadores que son prefijos, lo cual significa que ellos preceden a sus operandos. El propósito de una expresión aritmética es especificar un cómputo aritmético. Una implementación de tal cómputo debe causar dos acciones: sacando los operando, usualmente de la memoria, y ejecutando las operaciones aritméticas en esos operandos. En las secciones siguientes, nosotros investigamos los detalles del diseño común de expresiones aritméticas en los lenguajes imperativos.

Siguiendo están los diseños primarios de los problemas para las expresiones aritméticas, las cuales todas se discute en esta sección: ¿Qué son las reglas de precedencia de operador? ¿Qué son las reglas de asociatividad de operador? ¿Qué es el orden de evaluación del operando? ¿Hay restricciones en el efecto colateral de la evaluación de operandos? ¿El lenguaje permite que el usuario defina una sobrecarga del operador? ¿Qué modo mezclado es permitido en expresiones? 7.2.1 Orden de Evaluación del Operador Nosotros investigamos las reglas del lenguaje que especifican el orden de evaluación de los operadores primero. 7.2.1.2 Precedencia El valor de una expresión depende, por lo menos en parte, del orden de evaluación de los operadores en la expresión. Considere la expresión siguiente: a+b*c Suponga las variables a, b, y c que tienen los valores 3, 4, y 5,respectivamente. Evaluado de izquierda a derecha (la suma primero y luego la multiplicación), el resultado es 35. Evaluado de derecha a izquierda, el resultado es 23.. En lugar de evaluar el orden simplemente de izquierda a derecha o de derecha a izquierda, los matemáticos han desarrollado el concepto de colocar operadores en una jerarquía de evaluación de prioridades y basando el orden de evaluación de expresiones en parte en esta jerarquía. Por ejemplo, en matemática, se considera que la multiplicación es de prioridad más alta que la suma. Si nosotros seguimos esa convención en nuestra expresión del ejemplo, la multiplicación se evaluaría primero. La regla de precedencia del operador para la evaluación de la expresión define el orden en que se evalúan los operadores de niveles de precedencia diferentes. La reglas de precedencia del operador para las expresiones están basadas en la jerarquía de prioridades del operador, como son visto por el diseñador del lenguaje. La regla de precedencia del operador de los lenguajes imperativos comunes son casi todos los mismos, porque ellos están todos basados en aquellos de matemática. En estos lenguajes, la exponenciación tiene la precedencia más alta (cuando es proporcionado por el lenguaje), siguiendo por la multiplicación y división en el mismo nivel, seguido por la suma binaria y substracción en el mismo nivel. Muchos lenguajes también incluyen versiones unarias de suma y substracción. La suma unaria es llamada el operador de identidad porque usualmente no tiene operación asociada y así no tiene el efecto en su operando. Ellis yStroustrup, hablando sobre C++, llama e esto un accidente histórico y correctamente lo etiqueta inútil (Ellis y Stroustrup, 1990, pág. 56). En Java, el unario actualmente tiene un efecto cuando el operando es char, sort, o byteesto causa una conversión implícita de ese operando al tipo del int. El menos del Unario, claro, siempre cambia el signo de su operando. En todos los lenguajes imperativos comunes, los operadores menos del unario

pueden aparecer al principio o en cualquier parte en una expresión dentro de la expresión, con tal de que sea parentizado para impedirle ser adyacente a otro operador. Por ejemplo, A + (- B) * C es legal, pero A+-B*C usualmente no es. Cuando veamos en la sección 7.2.1.2, la precedencia de los operadores del unarios es raramente relevante. La precedencia de los operadores aritméticos de algunos lenguajes de programación comunes son como sigue: FORTRAN Pascal C Ada Alta ** *, /, div, mod postfija ++, -- **, abs *, / todos +, - prefija ++, -- *, /, mod todos +, - unario +, - unario +, *, /, % binario +, Baja binario +, El operador ** es exponenciación. La / y div operadores de Pascal son descriptos en la sección 7.3. El operador % de C esexactamente lo mismo que el operador mod de Pascal y Ada: Este toma dos operandos enteros y produce el resto del primero después de dividirlo por el segundo. Los operadores ++ y - de C son descriptos en la sección 7.7.5. La regla de precedencia de C++ y estas de C son las mismas, excepto que en C++, todos los operadores ++ y - tienen igual precedencia. Las reglas de precedencia de Java son estas de C++. El operador abs de Ada es un operador unario que produce el valor absoluto de estos operandos. APL es extraño entre los lenguajes porque este tiene un único nivel de precedencia, como lo ilustrado en la próxima sección. La precedencia cuenta solo para algunas de las reglas, para el orden de evaluación del operador; las reglas de asociatividad también las afecta. 7.2.1.2 Asociatividad Considerar la siguiente expresión: a-b+c-d Si los operadores de la suma y de la substracción tienen el mismo nivel de precedencia, las reglas de precedencia dicen nada sobre el orden de evaluación de los operadores en esta expresión. Cuando una expresión contiene dos ocurrencias adyacentes de operadores con el mismo nivel de precedencia, la pregunta por la cual el operador es evaluado primero se contesta por las reglas de asociatividad del lenguaje. Un operador puede tener ambas asociatividades izquierda o derecha, significando que la ocurrencia mas a la izquierda es evaluada primero o la ocurrencia más a la derecha es evaluada primero, respectivamente. Asociatividad en lenguajes imperativos comunes es de izquierda a derecha, excepto que el operador de exponenciación (cuando es provisto)asocia de

derecha a izquierda. En Fortran la expresión A–B+C el operador izquierdo es evaluado primero. Pero la exponenciación en Fortran es asociado a la derecha, como en la expresión A ** B ** C el operador derecho es evaluado primero. En Ada, la exponenciación no es asociativa, lo cual significa que la expresión A ** B ** C es ilegal en Ada. Como una expresión puede ser parentizada representa desear orden, como en ambos (A ** B) ** C o A ** (B ** C) Ahora podemos explicar el porqué la precedencia de operadores unarios es más frecuentemente no importante. Las operaciones menos unario y binario de Fortran tienen la misma precedencia, pero en Ada (y otros lenguajes comunes más) menos unarios tienen precedencia sobre menos binarios. Sin embargo, considerar la expresión -A-B porque Fortran usa asociatividad izquierda para ambos menos unario y menos binario, y porque Ada obtiene precedencia para menos unario sobre menos binario, esta expresión es equivalente a (- A) - B en ambos lenguajes. Luego considerar la siguiente expresión: -A/B -A*B - A ** B en el primero de los dos casos, la precedencia relativa del operador menos unario y el operador binario es irrelevante, el orden de evaluación de los dos operadores no tiene efecto en el valor de la expresión. En el último caso, sin embargo, esto importa. De los lenguajes de programación comunes, solo Fortran y Ada tienen operador de exponenciación. En ambos casos, la exponenciación tiene más alta precedencia que los menos unarios, como en - A ** B es equivalente a -(A ** B) Cuando los operadores unitarios aparecen en otras posiciones que a la izquierda final de la expresión, ellos deben ser parentizadas en ambos lenguajes, como en estas situaciones estos operadores son forzados a tener la más alta precedencia (los paréntesis son discutidos en la sección 7.2.1.3). Las reglas de asociatividad para algunos de los más comunes lenguajes imperativos son obtenidas abajo: Lenguaje Reglas de Asociatividad Fortran Izquierda: *, /, +, -

Derecha: ** Pascal Izquierda: todos C Izquierda: postfija ++, postfija - -, *, /, %, binario +, binario – Derecha: prefija ++, prefija - -, unario +, unario – C++ Izquierda: *, /, %, binario +, binario – Derecha: ++, - -, unario -, unario + Ada Izquierda: todos excepto ** No asociativo: ** Como lo declarado en la sección 7.2.1.1, en APL, todos los operadores tienen el mismo nivel de precedencia. Así el orden de evaluación de operadores en las expresiones de APL es completamente determinado por la regla de asociatividad, la cual es de derecha a izquierda para todos los operadores. Por ejemplo, en la expresión AxB+C el operador de suma se evalúa primero, siguiendo por el operador de la multiplicación (x es en APL el operador de multiplicación). Si A era 3, B era 4, y C era 5, el valor de esta expresión de APL sería 27. Muchos compiladoreshacen uso del hecho que algunos operadores aritméticos son matemáticamente asociativos, significando que las reglas de asociatividad no tienen el impacto en el valor de una expresión que contiene sólo esos operadores. Por ejemplo, la suma es matemáticamente asociativa, como en la matemática el valor de la expresión A+B+C no dependa del orden de evaluación del operador. Si las operaciones de puntoflotante para operaciones matemáticamente asociativas también fueran asociativas, el compilador podría usar este hecho para realizar algunas simples optimizaciones. Específicamente, si el compilador es permitido para reordenar la evaluación de operadores, puede poder producir el código ligeramente más rápido para la evaluación de la expresión. Los compiladores actualmente hacen estos tipos de optimizaciones. Desdichadamente, los dos, las representaciones del punto-flotante y las operaciones aritméticas de punto-flotante son sólo aproximaciones de matemática (debido a las limitaciones del tamaño). El hecho que un operador matemático es asociativo necesariamente no implica que la correspondiente operación de punto-flotante es asociativa. De hecho, sólo si todos los operadores y resultados intermedios pueden representarse exactamente en notación de punto-flotante será el proceso precisamente asociativo. Por ejemplo, hay situaciones patológicas en las cuales la suma de entero en una computadora no es asociativa. Por ejemplo, suponga que un programa debe evaluar la expresión

A+B+C+D y que A y C son muy largos números positivos, y B y D son números negativos con muy largos valoresabsolutos. En esta situación, agregando B a A no causa un sobre fluido, pero agregando C a A, si. Igualmente, agregando C a B no causa el sobre fluido, pero agregando D a B, si. Debido a las limitaciones de aritmética de la computadora, la suma es catastróficamente el no asociativo en este caso. Por consiguiente, si el compilador reordena estas operaciones de suma, afecta al valor de la expresión. Esto es, claro, un problema que puede ser evitado por el programador, asumiendo que los valores aproximados de las variables son conocidos. El programador puede simplificar parentizando la expresión (vea sección 7.2.1.3) para asegurar que sólo el orden seguro de la evaluación es posible. Sin embargo, esta situación puede levantarse de maneras más sutiles, en las que el programador probablemente notará menos el orden de dependencia. 7.2.1.3 Paréntesis Los Programadores pueden alterar las reglas de precedencia y la asociatividad colocando paréntesis en las expresiones. Una parte de la parentización de una expresión tiene la precedencia sobre sus partes no parentizadas adyacentes. Por ejemplo, aunque la multiplicación tiene la precedencia sobre la suma, en la expresión (A + B) * C la suma se evaluará primero. Matemáticamente, esto es perfectamente natural. En esta expresión, el primer operando del operador de la multiplicación no está disponible hasta que la suma en la parentización de la subexpresión es evaluada. Lenguajes que permiten los paréntesis en las expresiones aritméticas podrían distribuirse con todas las reglas de precedencia y simples asociatividad; todos los operadores deizquierda a derecha o de derecha a izquierda. El programador especificaría el orden deseado de evaluación con los paréntesis. Esto sería simple porque ni el autor ni los lectores de programas necesitarían recordar cualquier regla de precedencia o asociatividad. La desventaja de este esquema es que hace las expresiones escritas más tediosas, y también compromete seriamente la legibilidad del código. Todavía esta era la opción hecha por Ken Iverson, el diseñador de APL. 7.2.1.4 Expresiones Condicionales Ahora miremos al operador ternario ?: el cual es parte de C, C++, y Java. Este operador es utilizado para formar las expresiones condicionales. A veces se utilizan las sentencias if-then-else para realizar una asignación de la expresión condicional. Por ejemplo, considere if (count = =0) average = 0 else average = sum / count;

En C, C++, y Java, esto puede especificarse más convenientemente en una sentencia de asignación utilizando una expresión condicional que tiene la forma expresión_1 ? expresión2 : expresión_3 donde la expresión_1 es interpretada como una expresión Bolean. Si la expresión_1 evalúa para verdadero, el valor de toda expresión es la del valor de la expresión_2; si no, este es el valor de la expresión_3. Por ejemplo, el efecto del if-then-else de abajo puede ser realizado con la siguiente sentencia de asignación, utilizando una expresión condicional: average = (count = = 0) ? 0 : sum / count; En efecto, el signo de interrogación denota el principio de la cláusula then, y los dos puntosmarcan el principio de la cláusula else. Ambas cláusulas son obligatorias. ¿Noto que? es utilizada en expresiones condicionales como un operador ternario. Pueden utilizarse las expresiones condicionales en cualquier parte en programas C, C++, o Java dónde cualquier otra expresión puede utilizarse. 7.2.2 Orden de Evaluación de los Operandos Comúnmente menos discutido es el diseño de las características de las expresiones en el orden de evaluación de los operandos. Las variables en las expresiones son evaluadas sacando sus valores de la memoria. A veces se evalúan las constantes de la misma manera. En otros casos, una constante puede ser parte de la instrucción del lenguaje de máquina y no requiere traerlo de la memoria. Si un operando es una expresión parentizada, entonces todos los operandos contenidos deben evaluarse antes de que su valor pueda utilizarse como un operando. Si ninguno de los operandos de un operador tiene los efectos colaterales, entonces el orden de evaluación del operando es irrelevante. Por consiguiente, el único caso interesante se sugiere cuando la evaluación de un operando tiene los efectos colaterales. 7.2.2.1 Efectos Colaterales Un efecto colateral de una función, es llamada un efecto colateral funcional, ocurre cuando la función cambia cualquiera de los dos, o sus parámetros o una variable global. (Una variable global se declara fuera de la función pero es accesible en la función.) Considere la expresión a + fun(a) Si fun no tiene el efecto colateral de cambiar a, entonces el orden de evaluación de los dos operandos, a y fun (a), no tiene elefecto en el valor de la expresión. Sin embargo, si fun cambia a, hay un efecto. Considere la situación siguiente: fun devuelve el valor de su argumento dividido por 2 y cambia su parámetro para tener el valor 20. Suponga que nosotros tenemos lo siguiente: a = 10; b = a + fun(a); Entonces, si el valor de a se saca primero (en el proceso de evaluación de

expresión), su valor es 10 y el valor de la expresión es 15. Pero si el segundo operando se evalúa primero, entonces el valor del primer operando es 20 y el valor de la expresión es 25. El siguiente programa en C ilustra el mismo problema cuando una función cambia una variable global que aparece en una expresión: int a = 5; int fun1() { a = 17; return 3; } /* de fun1 */ void fun2() { a = a + fun1(); } /* de fun2 */ void main() { fun2(); } /* de main */ El valor computado para a en fun2 depende del orden de evaluación de los operandos en la expresión a + fun1 (). El valor de a será u 8 o 20. Hay dos soluciones al problema de orden de evaluación del operando. Primero, el diseñador del lenguaje podría desaprobar la evaluación de la función desafectando los valores de expresiones por una simple desaprobación funcional del efecto colateral. El segundo método de evitar el problema es declarar en la definición del lenguaje que los operandos en las expresiones serán evaluadas en un orden particular y se exigirá la garantía del implementador que la ordene. Desaprobando los efectos colaterales funcionales es difícil, y esto elimina un poco la flexibilidad por el programador.Considere el caso de C y C++ que tienen sólo funciones. Eliminar los efectos colaterales de parámetros bidireccionales y todavía proporcionar subprogramas que devuelven más de un valor, un nuevo tipo del subprograma que es similar a los procedimientos de los otros lenguajes imperativos se exigiría. El acceso a variables globales en las funciones también tendría que ser desaprobado. Sin embargo, cuando la eficacia es importante, usando el acceso a las variables globales, evitar pasar el parámetro es un método importante aumentando la rapidez en la ejecución. En los compiladores, por ejemplo, el acceso global a los datos como la tabla de símbolos es común. El problema con tener un orden de la evaluación estricto es que algunas técnicas de optimización de código utilizadas por los compiladores involucran la reordenación de evaluaciones del operando. Un desaprobado orden garantiza esos métodos de la optimización cuando las llamadas a funciones están involucradas. No hay ninguna solución perfecta por consiguiente, como es confirmado por los diseños del lenguaje actual. Los diseñadores de FORTRAN 77 previeron una tercera solución. La definición de FORTRAN 77 declara que expresiones que tienen llamadas a función sólo

son legales si las funciones no cambian los valores de otros operandos en la expresión. Desdichadamente, no es fácil para el compilador determinar el efecto exacto que una función puede tener en las variables fuera de la función, especialmente en la presencia de variables globales proporcionada por COMMON y los alias proporcionado por el EQUIVALENCE. Éste es un caso dónde ladefinición específica del lenguaje bajo la cual condiciona una estructura que es legal pero deja al programador asegurar que tales estructuras son específicamente legales en el programa. Pascal y Ada permiten evaluar los operandos de operadores binarios en cualquier orden escogido por el implementador. Además, las funciones en estos lenguajes pueden tener los efectos colaterales, para que el problema discutido abajo pueda ocurrir. Se discuten los efectos colaterales funcionales en el Capítulo 9. La definición del lenguaje Java garantiza que los operandos aparecen para ser evaluados en el orden de izquierda-a-derecha, eliminando el problema discutido en esta sección. 7.3 Sobrecarga del Operador Los operadores aritméticos se usan a menudo para más de un propósito. Por ejemplo, + frecuentemente se usa para la suma de cualquier operando del tipo numérico. Algunos lenguajes, Java por ejemplo, también lo usa para la concatenación de cadenas. Este múltiple uso de un operador se llama sobrecarga de operador y generalmente se piensa que es aceptable, con tal de que la legibilidad y/o fiabilidad no sufran. Algunos creen que hay demasiada sobrecarga de operador APL y SNOBOL dónde la mayoría de los operadores se usan para operaciones unarias y binarias. Como un ejemplo de los posibles peligros de sobrecarga, considere el uso del ampersand (&) en C. Como un operador binario, especifica un papel prudente lógico de la operación AND. Como operador de unario, sin embargo, su significado es totalmente diferente. Como operador de unario con una variable como su operando, el valor de la expresión esla dirección de esa variable. En este caso, el ampersand se llama la dirección del operador. Por ejemplo, la ejecución de x = & y; causa la dirección de y para ser puesto en x. Hay dos problemas con este múltiple uso del ampersand. Primero, usando el mismo símbolo para dos completamente no relacionadas operaciones es perjudicial a la legibilidad. Segundo, el error del tecleo simple de omitir el primer operando para un papel prudente de operación AND puede ir no detectado por el compilador, porque se interpreta como un dirección-de operador. Tal como un error puede ser difícil diagnosticar. Virtualmente todos los lenguajes de programación tienen un problema menos serio pero similar que es a menudo debido a la sobrecarga del operador menos. El problema es sólo que el compilador no puede determinar si el operador quiere decir ser binario o unario. Así una vez más, fracasa la inclusión del

primer operando cuando el operador significa ser binario no pudiendo ser detectado como un error por el compilador. Sin embargo, los significados de las dos operaciones, unaria y binaria, se relacionan por lo menos estrechamente, para que la legibilidad no se afecte adversamente. Los distintos símbolos del operador no sólo aumentan la legibilidad, sino que ellos a veces son también convenientes al uso común de las operaciones. El operador de la división es un ejemplo. Considere el problema de encontrar el promedio del punto flotante de una lista de enteros. Normalmente la suma de esos enteros se computa como un entero. Suponga que esto se ha hecho en la variable sum, y el número de valoresestá en count. Ahora, si el promedio del punto flotante es computado y se coloca en la variable avg de punto flotante, este cómputo podría especificarse en C++ como avg = sum / count; Pero esta asignación produce un resultado incorrecto en la mayoría de los casos. Porque ambos operandos del operador de la división son el tipo del entero, una operación de división de entero toma lugar en el resultado que es truncado a un entero. Entonces, a pesar de que el destino (avg) es el tipo del punto flotante, su valor de esta asignación no puede tener una parte fraccionaria. El resultado de la división del entero se convierte a punto flotante para la asignación después del truncamiento de la división del entero. Cuando un símbolo distinto del operador para la división del punto flotante está disponible, la situación se simplifica. Por ejemplo, en Pascal dónde / significa la división del punto flotante, la siguiente asignación pueden usarse avg := sum / count donde avg es del tipo del punto flotante, y sum y count son del tipo del entero. Se convertirán ambos operandos implícitamente para el punto flotante, y una operación de división del punto flotante es utilizada. Este tipo de operación de conversión implícita se discute en la sección siguiente. La división del entero en Pascal se especifica por el operador div que toma los operandos del entero y produce un resultado del entero. Cuando ningún operador distinto para la división del punto flotante se proporciona, deben usarse las conversiones explícitas. Se discuten tales conversiones en la sección 7.4.2. Algunoslenguajes que soportan los tipos de datos abstractos (vea Capítulo 11); por ejemplo, Ada, C++, y FORTRAN 90 permiten al programador la extensa sobrecarga de los símbolos del operador. Por ejemplo, suponga que un usuario quiere definir el operador * entre un escalar entero y un array de entero para significar que cada elemento del array será multiplicado por el escalar. Esto podría hacerse escribiendo un subprograma función nombrado * que realiza esta nueva operación. El compilador escogerá el significado correcto cuando una sobrecarga de operador se especifica, basado en los tipos de los operandos, como con la sobrecarga de operadores definidos en los lenguajes. Por ejemplo, si esta nueva definición para * se define en un programa de Ada, un compilador de Ada usará la nueva definición para * siempre que el operador * aparece con un entero simple como el operando izquierdo y un array de

entero como el operando derecho. Cuando sensiblemente usa, los operadores sobrecargados definidos por el usuario puede ayudar a la legibilidad. Por ejemplo, si + y * se sobrecargan por una matriz de tipos de datos abstractos y A, B, C, y D son las variables de ese tipo, entonces, A*B+C*D Puede ser utilizado a cambio de MatrixAdd(MatrixMult(A, B), MatrixMult(C, D)) Por otro lado, la sobrecarga definida por el usuario puede ser dañina para la legibilidad. En primer lugar, nada previene a un usuario definir + para significar multiplicación. Además, viendo un operador de * en un programa, el lector debe hallar ambos tipos de operandos y la definición del operador paradeterminar este significado. Cualquiera o todas estas definiciones podrían estar en otros archivos. C++ tiene algunos operandos que no pueden ser sobrecargados. Entre estas están el class u operador miembro de estructura (.) y el operador de resolución del ámbito (::). Interesantemente, la sobrecarga del operador fue una de las características de C++ que fue copiada por Java 7.4 Conversiones de Tipos Las conversiones de tipos son o reducidas o ampliadas. Una conversión reducida convierte un valor a un tipo que no puede guardar incluso las aproximaciones de todos los valores del tipo original, por ejemplo, convirtiendo un double a un float en Java (el rango de double es más grande que el del float). Una conversión ampliada convierte un valor a un tipo que puede incluir las aproximaciones por lo menos de todos los valores del tipo original, por ejemplo, convirtiendo un int a un float en Java. Las conversiones ampliadas casi siempre están seguras, considerando que las conversiones reducidas no lo son. Como un ejemplo de un problema potencial con una conversión ampliada, considere lo siguiente. En muchas implementaciones del lenguaje, aunque las conversiones del entero a float son conversiones ampliadas, alguna exactitud puede perderse. Por ejemplo, en algunas implementaciones, se guardan los enteros en 32 bits, que permite por lo menos nueve dígitos decimales de precisión. Pero en muchos casos, se guardan también los valores del punto flotante en 32 bits, con sólo aproximadamente siete dígitos decimales de precisión. Así que, el entero a punto flotante ampliado puede resultar en la pérdida dedos dígitos de precisión. Las conversiones de tipos pueden ser explícitas o implícitas. Las siguientes dos subsecciones discuten estos dos tipos de conversiones del tipo. 7.4.1 Coerciones en Expresiones Una de las decisiones del diseño concerniente a las expresiones aritméticas es si un operador puede tener operandos de tipos diferentes. Lenguajes que permiten tales expresiones se llaman expresiones del modo-mixto, debe definir las convenciones para las conversiones de tipo de operando implícitas,

llamadas coerciones, porque las computadoras usualmente no tienen operaciones binarias que tomen operandos de diferentes tipos. Recordemos que en el Capítulo 5 definimos coerción para ser una conversión del tipo implícito que se inicializa por el compilador. Nosotros nos referimos al tipo de conversión explícita requerida por el programador como las conversiones explícitas, o lanzamientos, no coerciones. Aunque algunos símbolos del operador pueden ser sobrecargados, asumimos que un sistema de la computadora, o en el hardware o en algún nivel de simulación del software, se opera para cada tipo del operando y operador definido en el lenguaje. Para la sobrecarga de operadores en un lenguaje que usa el tipo estático obligado, el compilador escoge el tipo correcto de operación en base a los tipos de los operandos. Cuando los dos operandos de un operador no son del mismo tipo y eso es legal en el lenguaje, el compilador debe escoger uno de ellos para ser coercionado y proporcionar el código para esa coerción. En la siguiente discusión, examinamos las opciones de diseño de coerción devarios lenguajes comunes. Los diseñadores del lenguaje no están de acuerdo en el problema de las coacciones en las expresiones aritméticas. Aquellos contra un rango ancho de coacciones se preocupan por los problemas de fiabilidad que pueden ser el resultado de las tales coacciones, porque ellos eliminan los beneficios de comprobación del tipo. Aquellos que incluirían más bien todas estas coacciones se preocupan más por la flexibilidad que del resultado de las restricciones. El problema es si programadores deben tener relación con esta categoría de errores o si el compilador debe descubrirlos. Como una ilustración simple del problema, considere el método de Java del siguiente esqueleto: void myMethod() { int a, b, c; float d; … a = b * d; … } Asuma que el segundo operando del operador de la multiplicación era supuestamente c, pero debido a un error del tecleo se tecleó como d. Porque las expresiones del modo-mixto son legales en Java, el compilador no descubriría esto como un error. Insertaría el código simplemente para coaccionar el valor del otro operando, b, a float. Si las expresiones del modomixto no fueran legales en Java, este error del tecleo se habría descubierto por el compilador como un error de tipo. Como un ejemplo más extremo de los peligros y costos de demasiada coacción, considere los esfuerzos de PL/I para lograr la flexibilidad en las expresiones. En PL/I, unavariable de cadena de caracteres puede combinarse con un entero en una expresión. En tiempo de ejecución, la cadena se examina para un valor

numérico. Si el valor pasa para contener un punto decimal, se asume que el valor es de tipo del punto flotante, el otro operando se coacciona para punto flotante, y el resultado de la operación es punto flotante. Esta política de coacción es muy cara porque deben hacerse el chequeo del tipo y la conversión en el tiempo de ejecución. También elimina la posibilidad de detección de errores en expresiones por el programador, porque un operador binario puede combinar un operando de cualquier tipo con un operando de virtualmente cualquier otro tipo. Porque el descubrimiento del error está reducido cuando se permiten las expresiones del modo-mixto, Ada permite muy pocos tipos de operandos en expresiones mixtas. No permite mezcla de operando entero y operando del punto flotante en una expresión, con una excepción: El operador de exponenciación, * *, puede tomar un punto flotante o un entero para el primer operando y un tipo entero para el segundo operando. Ada permite algunos otros tipos de operando para mezclar los tipos, normalmente relacionado a los tipos del subrango. En la mayoría de los otros lenguajes comunes, no hay ninguna restricción del modo-mixto de las expresiones aritméticas. C++ y Java tienen tipos del entero que son más pequeños que el tipo del int. En C++, éstos son char y short int; en Java, ellos son byte, short, y char. Los operando de todos estos tipos se coaccionan al int siempre que virtualmente cualquier operador se apliquea ellos. Así mientras pueden guardarse los datos en las variables de estos tipos, no pueden ser manipulados antes de la conversión a un tipo más grande. Por ejemplo, considere el siguiente código en Java: byte a, b, c; … a= b + c; Los valores de b y c son coaccionados a int y una suma int es representada. Luego la suma es convertida a byte y colocada en a. 7.4.2 Conversión del Tipo Explícito La mayoría de los lenguajes proveen un poco de capacidad haciendo las conversiones explícitas, ambas reduciendo y ampliando. En algunos casos, los mensajes de advertencia son producidos cuando el resultado de una conversión reducida explícita cambia el significado para el valor del objeto al inicio de la conversión Ada proporciona operaciones de conversión explícitas que tienen la sintaxis de llamadas de la función. Por ejemplo, nosotros podemos tener AVG := FLOAT(SUM) / FLOAT(COUNT) donde AVG es del tipo punto flotante, y SUM y COUNT pueden ser cualquier tipo numérico. En el lenguaje basado en C, la conversión del tipo explícito es llamada cast (lanzar). Las sintaxis de un cast no es de una llamada a función; más bien, el tipo deseado es colocado en paréntesis sólo antes de ser convertida la

expresión. Como se representa en (int) angle Una de las razones de conversión de los paréntesis en C es que C tiene varios tipos llamados doble-palabra, como el long int. 7.4.3 Errores en Expresiones Un número de errores puede ocurrir en una expresión de evaluación. Si el lenguaje requiere chequeo de tipo, entonces el error de tipo operando no puedeocurrir. Ya discutiremos el error que puede ocurrir debido a las coacciones de operandos en expresiones. Los otros tipos de errores son debidos a las Iimitaciones de aritmética de la computadora y las limitaciones inherentes de aritmética. El error más común se crea cuando el resultado de una operación no puede representarse en la celda de memoria dónde debe guardarse. Esto se llama sobre fluido o no fluido, dependiendo si el resultado también era demasiado largo o demasiado pequeño. Una limitación de la aritmética es la división por cero no permitida. Claro, el hecho que no se permite matemáticamente no impide a un programa intentar hacerlo. El sobre fluido y el no fluido del punto flotante, y la división por el cero son ejemplos de errores en tiempo de ejecución, que a veces se llaman excepciones. Los recursos del lenguaje que permiten a los programas descubrir y tratar con las excepciones se discuten en el Capítulo 14. 7.5 Expresiones Booleans y Relacionales En la suma de expresiones aritméticas, los lenguajes de programación tienen expresiones booleanas y relacionales. 7.5.1 Expresiones Relacionales Un operador relacional es un operador que compara los valores de sus dos operandos. Una expresión relacional tiene dos operandos y un operador relacional. El valor de una expresión relacional es Boolean, excepto cuando Boolean no es un tipo en el lenguaje. Los operadores relacionales son a menudo sobrecargados por la variedad de tipos. La operación que determina la verdad o falsedad de una expresión relacional depende del tipo del operando. Puede ser simple, en cuanto a los operando delentero, o complejo, en cuanto a los operando de cadena de caracteres. Típicamente, los tipos de los operando que pueden usarse para los operadores relacionales son tipos numéricos, cadenas, y los tipos ordinales. La sintaxis de los operadores relacionales disponible en algunos lenguajes comunes son como sigue: Operación Ada Java Fortran 90 Igual = = = .EQ. o == No igual /= != .NE. o Mayor que > > .GT. o > Menor que < < .LT. o < Mayor o igual que >= >= .GE. o >= Menor o igual que B and A < C or K = 0 Es ilegal en Ada. Esta expresión puede ser legalmente escrita como

(A > B and A < C) or K = 0 o A > B and (A < C or K = 0) El operador Boolean de Ada and then y or else son discutidos en la próxima sección El C es raro entre los lenguajes imperativos populares porque no tiene ningún Boolean tipo y así ningún valor de Boolean. En cambio, se usan los valores numéricos para representar los valores de Boolean. En lugar de los operandos de Boolean, se usan variables numéricas y constantes, con cero considerado falso y todos los valores no ceros considerados verdadero. El resultado de evaluar tal expresión es un entero, con el valor 0 si es falso y 1 si es verdadero. Un resultado raro del diseño de C es la expresión a>b>c es legal. El operador relacional más a la izquierda se evalúa primero porque los operadores relacionales de C están a la izquierda asociados, produciendo 0 o 1. Entonces este resultado se compara con la variable c. Nunca hay una comparación entre b y c. Cuando los operadores no aritméticos de C, C++, yJava son incluidos, hay más de 40 operadores y por lo menos 15 niveles diferentes de precedencia. Ésta es evidencia clara de la riqueza de las colecciones de operadores y la complejidad de las expresiones posibles en estos lenguajes. Los dictados de legibilidad que un lenguaje debe incluir en un tipo de Boolean, lo declaramos en el Capítulo 6, en lugar de utilizar tipos numéricos simplemente en las expresiones de Boolean, como en C. Algún descubrimiento del error está perdido en el uso de los tipos numéricos de C, porque cualquier expresión numérica, si intenta o no, es un operando legal a un operador de Boolean. En otros lenguajes imperativos, cualquier expresión no Boolean usada como un operando de un operador de Boolean se descubre como un error. En Pascal, los operadores de Boolean tienen la más alta precedencia que los operadores relacionales, para la expresión a > 5 or a < 0 es ilegal (porque 5 no es un operando Boolean legal). La versión correcta es (a > 5) or (a < 0) 7.6 Evaluación del Cortocircuito Una evaluación de cortocircuito de una expresión es uno en el cual el resultado es determinado junto a la evaluación de todos los operandos y/o operadores. Por ejemplo, el valor de la expresión aritmética (13 * a) * (b / 13 - 1) es independiente del valor de (b / 13 - 1) si a es 0, porque 0 * x = 0 para cualquier x. Así cuando a es 0 no hay necesidad de evaluar (b / 13 -1) o representar la segunda multiplicación. Sin embargo, en expresiones aritméticas este corto circuito no es fácil de detectar durante la ejecución, así esto nunca es tomado.

El valor de laexpresión Bolean (a >= 0) and (b < 10) es independiente de la segunda expresión relacional si a < 0, porque (FALSE and x) es FLASE para todos los valores de x. Así cuando a < 0, no hay necesidad de evaluar b, la constante 10, la segunda expresión relacional, o la operación and. Distinto a los casos de expresiones aritméticas, este cortocircuito puede ser fácilmente descubierto durante la ejecución y tomada. Para ilustrar el potencial problema de las expresiones Bolean con una evaluación sin cortocircuito, suponga que Java no tiene evaluación de cortocircuito. Ahora suponga que escribimos en una tabla parecida al ciclo usando la sentencia while. Una simple versión del código Java parecido, asumiendo que list, el cual tiene lista de elementos listlen, que es un array a ser buscado y la clave es buscada por valores, index = 1; while ((index < listlen) && (list [index] key)) index = index + 1 Si la evaluación no es de corto circuito, ambos la expresión relacional en la expresión Boolean de la sentencia while es evaluada sin tener en cuenta el valor del primero. Así, si la clave no está en list, el programa terminará con un error de fuera-de-rango del subíndice. La misma iteración que tiene index == listlen referenciará list [listlen], el cual causa el error de posicionamiento porque la list se declara para tener listlen-1 como un valor del subíndice limitado superior. Si un lenguaje proporciona evaluación de cortocircuito de expresiones Boolean y se usa, éste no es un problema. En el ejemplo precedente, un esquema de evaluación de cortocircuito evaluaría elprimer operando del operador AND, pero saltaría al segundo operando si el primero operando es falso. La evaluación de cortocircuito de expresiones expone el problema de permitir efectos colaterales en expresiones. Suponga que la evaluación de cortocircuito es utilizada en una expresión y parte de la expresión que contiene un efecto colateral no es evaluado; entonces el efecto colateral sólo ocurrirá en evaluaciones completas de toda expresión. Si la corrección del programa depende del efecto colateral, la evaluación de cortocircuito puede resultar en un serio error. Por ejemplo, considere la expresión en C el programa (a > b) II (b++ / 3) En esta expresión, b se cambia (en la segunda expresión aritmética) sólo cuando a | step until | while Un significado diferente entre este y otros ciclos controlados por contador en estas construcciones es que pueden combinar un contador y una expresión Boolean para el control de los ciclos. Las tres más simples formas son ejemplificadas siguientemente: for count := 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 do

list [count] := 0 for count := 1 step 1 until 10 do list [count] := 0 for count := 1, count + 1 while (count 0 goto out [loop body] for_var = for_var + step goto loop out: … abajo está una descripción semántica operacional de ejemplos más complejos de sentencia for: for count := 10 step 2 * count until init * init, 3 * count while sum 0 goto loop2 sum = sum + count count = count + step goto loop1 loop2: count = 3 * count if sum > 1000 goto out sum =sum + count goto loop2 out: … 8.4.1.4 La sentencia for del Pascal La sentencia for de Pascal es el modelo de simplicidad. Su forma es for variable := valor_inicial (to | downto) valor_final do sentencia La opción to | downto permite que el valor de la variable crezca o disminuya en los pasos de a 1. Las opciones del diseño for de Pascal son como sigue: La variable ciclo debe ser un tipo ordinal, y este tiene el ámbito de su declaración. En el fin del ciclo normal, la variable ciclo es indefinida. Si el ciclo es terminado prematuramente, tiene su último valor. La variable ciclo no puede ser cambiada en el cuerpo del ciclo. Los valores inicial y final, los cuales pueden ser expresiones de cualquier tipo que son compatibles con la variable del ciclo, puede ser cambiados en el ciclo, pero porque ellos son evaluados solo una vez, esto no puede afectar el control del ciclo. 8.4.1.5 La sentencia for de Ada La sentencia for de Ada es similar a la versión de Pascal. Este es un pre examinado ciclo con la forma: for variable in [reserve] rango_discreto loop … end loop; Un rango discreto es un subrangos de enteros o un tipo enumerado, tal como 1..10.ç La nueva característica más interesante de la sentencia for de Ada el ámbito de la variable ciclo, la cual es el rango del ciclo. La variable es implícitamente declarada en la sentencia for e implícitamente no declarada después del fin del ciclo. Por ejemplo, en:

COUNT : FLOAT := 1.35; for COUNT in 1..10 loop SUM := SUM + COUNT; end loop: La variable FLOAT COUNT no es afectada por el ciclo for. Al terminarel ciclo, la variable COUNT es todavía de tipo FLOAT con el valor 1.35. También la variable de tipo FLOAT, COUNT es ocultada del código en el cuerpo del ciclo, siendo enmascarado por el contador del ciclo COUNT, el cual es implícitamente declarado para ser el tipo de rango discreto, INTEGER. La variable del ciclo de Ada no puede ser asignada un valor en el cuerpo del ciclo. Las variables usadas para especificar el rango discreto puede ser cambiadas en el ciclo, pero porque el rango es evaluado solo una vez, estos cambios no afectan el control del ciclo. Esta bifurcación no es legal dentro del cuerpo del ciclo for de Ada. Abajo está una descripción semántica operacional del ciclo for de Ada: [define for_var (este es el tipo de rango discreto)] [rango discreto evaluado] loop: if [no hay elementos izquierdo en el rango discreto] goto out for_var = [próximo elemento del rango discreto] [loop body] goto loop out: [indefinido for_var] 8.4.1.6 La sentencia for de C, C++, y Java La forma general de la sentencia for de C es: for (expresión_1; expresión_2; expresión_3) loop body El cuerpo de la sentencia puede ser una sola sentencia, una sentencia compuesta, o una sentencia nula. Porque las sentencias en C producen resultados y así pueden ser consideradas expresiones, las expresiones en una sentencia for son frecuentemente sentencias. La primera expresión es para inicialización y es evaluada solo una vez, cuando la sentencia for inicia su ejecución. La segunda expresión es el ciclo de control y es evaluada antes de cada ejecución del cuerpo del código. Comoes inusual en C, un valor cero significa falso y todo valor no cero significa verdadero. Por lo tanto, si el valor de la segunda expresión es cero, el for es terminado; de otra manera las sentencias del ciclo son ejecutadas. La última expresión en el for es ejecutada antes de cada ejecución del cuerpo del ciclo. Esto es frecuentemente usado para incrementar el contador del ciclo. Una descripción semántica operacional de la sentencia for de C es mostrada abajo. Porque las expresiones de C son también sentencias, mostraremos evaluación de expresión como sentencias. expresión_1

loop: if expresión_2 = 0 goto out [loop body] expression_3 goto loop` out: … Un típico contador del ciclo de C es for (index = 0; index > indat; while (indat >= 0) { sum += indat; cin >> indat; } cin >> valor; do { valor /= 10; dígitos ++; } while (valor > 0); Note que todas la variables en estos ejemplos son de tipo entero. cin es el flujo de entrada estándar (el teclado), y >> es el operador de entrada. En la versión pre examinada (while) la sentencia es ejecutada tan largo como la expresión evalúa verdadero. En C, C++ y Java las sentencias post examinadas (do), del cuerpo del ciclo es ejecutada hasta la que la expresión evalúa falso. La sola diferencia real entre el do y el while es que el do siempre causa que el cuerpo del ciclo sea ejecutado por lo menos una vez. En ambos casos, la sentencia puede ser compuesta. Las descripciones semánticas operacionales de estas dos sentencias son obtenidas abajo: while do-while loop: loop: if expresión = 0goto out [cuerpo del ciclo] [cuerpo del ciclo] if expresión = 0 goto loop goto loop out: ... Es legal en ambos C y C++ bifurcar dentro de ambos cuerpos de ciclos while y do. Las sentencias while y do de Java son similares a las de C y C++, excepto la expresión de control debe ser del tipo boolean, y porque Java no tiene un goto, los cuerpo del ciclo no pueden ser introducidos por cualquier parte sino por su origen. FORTRAN 90 no tiene ni un pre examinador ni un post examinador de ciclo lógico. Ada tiene un pre examinador de ciclo lógico pero no una versión de post examinador de ciclo lógico. Perl tiene dos pre examinadores de ciclos lógicos, while y until, pero no post examinador de ciclo. El until es similar a while pero usa el valor inverso de la expresión de control.

La sentencia post examinadora del ciclo lógico de Pascal, repeat-until, difiere del do-while de C, C++ y Java en que la expresión de control lógico es reservada. El cuerpo del ciclo es ejecutado mientras que la expresión evaluada sea falsa, en lugar de verdadero, como es en el caso de C, C++ y Java. repeat-until es extraño porque su cuerpo puede ser o una sentencia compuesta o una secuencia de sentencia. Es solo la estructura de control de Pascal con su flexibilidad. Este es otro ejemplo de la falta de ortogonalidad en el diseño de Pascal. Los ciclos post examinados son infrecuentemente conveniente y también puede ser algunas veces dañino en el sentido que los programadores algunas veces pierden el cuerpo del ciclo que siempre será ejecutado por lo menos una vez. El diseñosintáctico de colocar el control post examinador después del cuerpo del ciclo, donde este tiene su efecto semántico, ayuda a evitar tal problema confeccionando una lógica clara. 8.4.3 Mecanismos de Control de Ciclos Localizados por el Usuario En algunas situaciones, es conveniente para un programador escoger una localización para otro control del ciclo que está es el tope o en el fondo del ciclo. Como resultado, algunos lenguajes proporcionan esta capacidad. Un mecanismo sintáctico para el control del ciclo localizado por el usuario puede ser relativamente simple, para que su diseño no sea difícil. Quizás la pregunta más interesante es si un solo ciclo o varios ciclos anidados pueden terminarse. El problema del diseño para tal mecanismo es lo siguiente: ¿Debería el mecanismo condicional ser una parte integral de la salida? ¿Debería el mecanismo ser permitido aparecer en un ciclo controlado o sólo en uno sin cualquier otro control? ¿Debería sólo un cuerpo del ciclo ser terminado, o puede también adjuntar los ciclos terminados? Varios lenguajes, incluso Ada, tienen sentencias de ciclo que no tienen el control de la iteración; ellos son ciclos infinitos a menos que los controles sean agregados por él programador. El forma del ciclo infinito de Ada es loop ... end loop El exit de Ada puede ser condicional o incondicional, y esto puede aparecer en cualquier ciclo. Su forma general es exit [nombre_ciclo] [when condición] Con ninguna de las partes opcionales, exit causa le terminación de solo el ciclo en el cual aparece. Por ejemplo, en loop ... if SUM >= 10000then exit; end if: … end loop;

el exit, cuando se ejecuta, transfiere el control de la primera sentencia después del final del ciclo. Un exit con un when termina la condición de su ciclo solo si la condición específica es verdadera. Por ejemplo, el ciclo de abajo puede ser escrito como loop ... exit when SUM >= 10000; … end loop; Cualquier ciclo puede ser nombrado, y cuando un ciclo nombrado es incluido en el exit, el control es transferido a la sentencia inmediatamente siguiendo lo referenciado por el ciclo. Por ejemplo, considere el siguiente segmento de código: OUTER_LOOP: for ROW in 1..MAX_ROWS loop INNER_LOOP: for COL in 1..MAX_COLS loop SUM := SUM + MET(ROW, COL); exit OUTER_LOOP when SUM > 1000.0; end loop INNER_LOOP; end loop OUTER_LOOP; En este ejemplo, el exit es una bifurcación condicional de la primera sentencia después del ciclo externo. Si el exit estaba en cambio exit when SUM > 1000.0; esto sería una bifurcación condicional de la primera sentencia después del ciclo interno. Note que las sentencias exit son frecuentemente usados para manipulación inusual o condiciones de error. C, C++, y Pascal tienen innombradas incondicionales (break en C y C++; y last en Perl); FORTRAN 90 y Java tienen salidas nombradas incondicionalmente (EXIT en FORTRAN 90 y break en Java), como Ada, excepto que en la versión de Java el indicador puede estar en cualquier sentencia compuesta encerrada. C y C++ incluyen un mecanismo de control, continue, que transfiere el control del mecanismo de control del más pequeño ciclo encerrado. Esta no esuna salida sino más bien una manera para saltar del resto de las sentencias del ciclo en la actual iteración sin terminar la estructura del ciclo. Por ejemplo, considerar lo siguiente: while (sum < 1000) { getnext (valor); if (valor < 0) continue; sum += valor; } Un valor negativo causa la sentencia de asignación para ser saltado, y el control se transfiere en cambio al condicional en la cima del ciclo. Por otro lado, en:

while (sum < 1000) { getnext (valor); if (valor < 0) break; sum += valor; } un valor negativo termina el ciclo. FORTRAN 90, Perl, y Java tienen sentencias similares a continue, excepto que ellos pueden incluir nombres que especifican que ciclo esta ser continuado. Ambos exit y break proporcionan salidas múltiples para los ciclos, los cuales tienen algunos impedimentos a la legibilidad. Sin embargo, las condiciones inusuales que requieren la terminación del ciclo son así comunes tal como una construcción es justificada. Además, la legibilidad no es un serio perjuicio, porque el indicador de todos tal como las salidas de ciclos es la primera sentencia después del ciclo en lugar de justamente en cualquier lugar en el programa. El break de Java y el last de Perl son excepciones porque sus indicadores pueden estar en cualquier sentencia compuesta encerrada. 8.4.4 Estructuras de Datos basadas en Iteración Sólo un tipo adicional de estructura de ciclo resta ser considerado aquí: la iteración que depende de las estructuras de datos. En lugar de tener un contador o una expresión de control de iteraciones Boolean, estos ciclos se controlan por elnúmero de elementos en una estructura del datos. COMMON LISP y Perl tienen tales sentencias. En COMMON LISP, la función dolist itera en listas simples que son las estructuras de datos más comunes en los programas LISP. Debido a esta restricción, el dolist es automático en el sentido que él siempre implícitamente itera en los elementos de la lista. Causa la ejecución de su cuerpo una vez para cada elemento en la lista dada. La sentencia foreach de Perl es similar; él itera en los elementos de listas o arrays. Por ejemplo, @names = (“Bob”, “Carol”, “Ted”, “Beelzebub”); … foreach $name (@names) { print $name; } Una sentencia de iteración basado en datos más general usa estructuras de datos definidos por el usuario y una función definida por el usuario para pasar por los elementos de la estructura. Esta función es llamada iterador. El iterador es llamado al principio de cada iteración, y es llamado en cada momento, el iterador retorna un elemento de una estructura del datos particular en algún orden específico. Por ejemplo, suponga un programa que tiene un árbol binario de nodos de los datos, y el dato en cada nodo deben procesarse en algún orden particular. Una sentencia de iteración definida por el usuario para el árbol pondría consecutivamente el conjunto de variables del ciclo para apuntar a los nodos en el árbol, uno para cada iteración. La ejecución inicial de la sentencia de iteración definida por el usuario necesita emitir una llamada

especial al iterador para conseguir el primer elemento del árbol. El iterador siempre deben recordar qué nodo que presentó en último lugarpara que visite todos los nodos sin visitar cualquier nodo más de una vez. Así que un iterador debe ser la historia sensible. Una sentencia de iteración definida por el usuario termina cuando el iterador no encuentra más elementos. El constructor for de C, C++, y Java, debido a su gran flexibilidad, puede usarse para simular una sentencia de iteración definida por el usuario. Una vez más, suponga que los nodos de un árbol binario son para ser procesado. Si la raíz del árbol apunta a una variable nombrada raíz, y si el travesaño es una función que pone su parámetro para apuntar al próximo elemento de un árbol en el orden deseado, lo siguiente podría usarse: for (ptr = root; ptr = = null; traverse(ptr)){ … } En esta sentencia, traverse es el iterador. Las sentencias de iteración definidas por el usuario son más importantes en la programación orientada a objetos porque sus paradigmas fueron más fáciles de desarrollar en software. Este resultado del hecho que el usuario ahora rutinariamente construya tipos de datos abstractos para las estructuras de datos. En tales casos, una sentencia de iteración definida por el usuario y su iterador deben ser provistos por el autor de la abstracción de datos porque la representación de objetos en los tipos no son conocidas por el usuario. En C+ +, el iterador para el tipo definido por el usuario, o clases, son frecuentemente implementadas como funciones amigas a las clases o como clases separadas por el iterador. 8.5 Bifurcación Incondicional Una sentencias de bifurcación incondicional transfiere el control de la ejecución a un lugar específicoen el programa. 8.5.1 Problemas con la Bifurcación Incondicional El más acalorado debate en los diseños de los lenguajes de los últimos 1960 fue sobre problema de si la bifurcación incondicional debería ser parte de cualquier lenguaje de alto nivel, y si así es, si su uso debería ser restringido. La bifurcación incondicional, o goto, es la sentencia más poderosa para controlar l flujo de la ejecución de las sentencias de un programa. Sin embargo, usando el goto descuidadamente puede dejar problemas. El goto tiene un estupendo poder y gran flexibilidad (todas las otras estructuras de control pueden ser construidas con goto y un seleccionador), pero su mucha fuerza hace su uso peligroso. Sin restricciones en su uso, impuestos por los diseños del lenguaje o los estándares de la programación, las sentencias goto pueden hacer programas virtualmente ilegibles, y como resultado, sumamente ilegibles y difíciles de mantener. Estos problemas siguen directamente de la capacidad de un goto de forzar cualquier sentencia del programa a seguir a cualquier otra en la secuencia de ejecución, sin tener en cuenta que si la sentencia precede o sigue el primer

orden textual. La legibilidad es mejor cuando el orden de ejecución de las sentencias es cercana al mismo como el orden en el cual ellos aparecen- en nuestro caso, esto debería significar la cima o el fondo, el cual es el orden para el cual están acostumbrados. Así que restringiendo los gotos de modo que puedan transferir el control solo descendentemente en un programa parcialmente situando el problema. Esto permite a los gotos transferir el controlalrededor de la sección de código en respuesta al error o las condiciones inusuales, pero desaprobando su uso para cualquier clase de ciclo. Aunque varias precavidas personas los han sugerido tempranamente, fue Edsger Dijkstra quién dio al mundo informático la primera amplia lectura descubriendo el peligro del goto. En su carta noto, “La sentencia goto es como su posición también primitiva; este es también mucho más que una invitación a hacer un enredo de unos programas” (Dijkstra, 1968a). Durante los primeros años después de la publicación de la visión de los goto de Dijkstra, un gran número de personas argumentaron públicamente la expulsión absoluta o por lo menos la restricción del goto. Entre estos quienes no favorecían a la completa eliminación estaba Donald Knuth, quién argumentó que estos fueron ocasionados cuando la eficiencia del goto pesaba más que su daño a la legibilidad (Knuth, 1974). Algunos lenguajes han sido diseñados sin un goto por ejemplo, Modula-2 y Java. Sin embargo, la mayoría de los lenguajes populares actuales incluyen una sentencia goto. Kernighan y Ritchie (1978) llamaron al goto infinitivamente maltratado, pero es no obstante incluido en el lenguaje C de Ritchie. Los lenguajes que tienen eliminado el goto tienen suministrado sentencias de control adicional, usualmente en la forma de ciclos salidas a subprogramas, reemplaza muchas de las aplicaciones del goto. Todas las sentencias de salida del ciclo discutidas en la sección 8.4.3 son actualmente sentencias camufladas goto. Ellas son, sin embargo, gotos severamente restringidos y no son dañinos a lalegibilidad. De hecho, puede ser argumentado que ellos mejoran la legibilidad porque evitan que el uso de su resultado en enroscados e innaturales códigos pudieran ser mucho más difíciles de entender. 8.5.2 Formas Nombradas Algunos lenguajes , como ALGOL 60 y C, usan su forma identificador para sus nombres. FORTRAN y Pascal usan constantes enteras sin signo para sus nombres. Ada usa la forma de su identificador como parte del indicador de su sentencia goto, pero cuando el nombre aparece en una sentencia, este debe ser delimitado por los símbolos . Por ejemplo, considerar lo siguiente: goto FINISHED; … SUM := SUM +NEXT; El delimitador hace los nombres más fáciles de hallar cuando el programa es leído. En otros muchos lenguajes, los nombres son adjuntados a las sentencias por dos puntos, como en

finished: sum := sum + next En su diseño de nombres, solo PL/I toma otra vez una construcción para su limite de flexibilidad y complejidad. En lugar de tratando los nombres como simples constantes, PL/I permite que sean variables. En su forma de variable, pueden ser asignados valores y usados como parámetros de subprogramas. Esto permite un goto para ser indicado virtualmente en cualquier parte en un programa, y el indicador no puede ser estáticamente determinado. Aunque su flexibilidad es algunas veces poderosa, a lo lejos es también perjudicial para la legibilidad valga la pena. Imagine probando leer y entender un programa que tiene bifurcación cuyo indicador depende del valor asignado en el tiempo de ejecución. Considerar un subprograma que tiene varios nombres y un goto cuyo nombreindicador es un parámetro formal. Para determinar el indicador del goto, uno debe conocer la unidad de llamada al programa y el valor del parámetro actual usado en la llamada. La implementación de la variable nombrada es también compleja, primariamente porque de todas las posibles maneras de variables nombradas puede ser un salto al valor. 8.6 Comandos Protegidos Una alternativa y diferente forma de selección estructuras de ciclos fueron sugeridas por Dijkstra (1975). Su motivación fue proporcionar sentencias de control que serían soportadas en una metodología de diseño de programa que asegure la corrección durante el desarrollo en lugar de confiar en la verificación o chequeo de los programas completados para asegurar su corrección. Esta metodología es descripta por Dijkstra (1976). Los comandos protegidos son cubiertos en este capítulo porque ellos son la base para dos mecanismos lingüísticos desarrollados más tarde para programación actual en dos lenguajes, CSP (Hoare, 1978) y Ada. Concurrentemente es discutido Ada en el capítulo 13. Las construcciones de selección de Dijkstra tiene la forma if -> [] -> [] … [] -> fi La palabra reservada de cerrado, fi, es la palabra reservada operando deletreada al revés. Esta forma de palabra reservada de cerrado es tomado de ALGOL 68. los bloques pequeños, llamados fatbars, son usados para separar la cláusula protegida y permite a la clá la ser una secuencia de sentencias. Esta construcción de selección tiene la apariencia de una selección múltiple, pero su semántica es diferente. Todas las expresiones Boolean son evaluadas cadavez que la construcción es alcanzada durante la ejecución. Si más de una expresión es verdadera, una de las sentencias correspondiente es no determinadamente escogida por ejecución. Si no es verdadera, un error en

tiempo de ejecución ocurre que causa el termino del programa. Esto forza al programador a considerar y listar todas las posibilidades, como con la sentencia case de Ada. Considerar el siguiente ejemplo: if i = 0 -> sum := sum +i [] i > j -> sum := sum + j [] j > I -> sum := sum + I fi Si i =0 y j > i, esta construcción escoge no indeterminadamente entre las primera y tercera sentencias de asignación. Si i es igual a j y no es cero, un error en tiempo de ejecución ocurre porque ninguna de las condiciones es verdadera. Esta construcción puede ser una manera elegante de permitir al programador condicionar el orden de ejecución, en algunos casos, es irrelevante. Por ejemplo, hallar el más largo de dos números, podemos usar if x >= y -> max := x [] y >= x -> max := y fi Este cómputo deseado resulta sin sobre especificando la solución. En particular, si x y y son iguales, no importa que asignamos a max. Es una forma de abstracción proporcionada por la no determinada semántica de la sentencia. Otra situación en la cual la construcción de selección de Dijsktra es valiosa es la siguiente: Supongamos que estamos escribiendo un programa que interrumpe servicios, y el interruptor tiene la misma prioridad. Por esto, necesitamos una construcción que elija entre interruptores actuales en algunos maneras aleatorias. La semántica de los comandos protegidos sondifíciles de describir precisamente. Aunque los diagramas de flujo no son buenas herramientas para el diseño de programas, son algunas veces poderosas descripciones para las semántica. La figura 8.1 es un diagrama de flujo describiendo la aproximación usada por la sentencia de selección de Dijkstra. Note que este diagrama de flujo es relativamente impreciso, reflejando la dificultad en la captura de la semántica de los comandos protegidos. La estructura de ciclo proporcionada por Dijkstra tiene la forma do -> [] -> [] … [] -> od La semántica de esta construcción es que todas las expresiones Boolean son evaluadas en cada iteración. Si más de una es verdadera, una de las sentencias asociadas es no determinadamente escogida por la ejecución, después del cual la expresiones son evaluadas otra vez. Cuando todas las expresiones son simultáneamente falsa, el ciclo termina. Considerar el siguiente código, el cual aparece en forma ligeramente diferente

en Dijkstra (1975). Las cuatro variables q1, q2, q3, y q4 son para tener su valor reorganizados para que q1 q3 -> temp := q2; q2 := q3; q3 := temp; [] q3 > q4 -> temp := q3; q3 := q4; q4 := temp; od Un diagrama de flujo describiendo la aproximación es usada las sentencias de ciclo de Dijkstra es mostrada en la figura 8.2. Una vez más, note que la semántica del flujo de control de esta construcción no puede ser completamente descripta en un diagrama de flujo.

Figura 8.1 Diagrama de Flujo aproximado usado con el seleccionador de sentencias de Dijkstra T

FT

F

Los comandos protegidos por Dijkstra, como estas dos construcciones son conocidas, son interesante en parte porque ellas ilustran como la sintaxis y la semántica de las sentencias puede tener un impacto en le verificación del programa, y viceversa. La verificación del programa es virtualmente imposible cuando las sentencias goto son usadas. La verificación es muy simplificada si solo los ciclos lógicos ciclos de selección, como los de Pascal, son usados, o los comandos protegidos son usados. La semántica axiomática de los comandos protegidos es convenientemente especificada (Gries, 1981). Debería ser obvio, sin embargo, aquí es considerablemente incrementada la complejidad en la implementación de los comandos protegidos sobre su complemento determinado convencional.

8.6 Conclusiones Figura 8.1 Diagrama de Flujo aproximado usado con sentencias de ciclo de Dijkstra

T

F

T

F

Tenemos descripto y discutido una variedad de niveles de sentencias de estructuras de control. Una breve evaluación parece ahora estar en orden. Primero, tenemos resultados teóricamente que solo ciclos de secuencias, selección y lógicos pre examinados son absolutamente requeridos para expresar cálculos. (Böhm y Jacopini, 1966). Este resultado ha sido ampliamente usado por estos quienes deseaban prohibir completamente la bifurcación incondicional. Por supuesto, hay suficientes problemas prácticos ya con el goto condenándolosin también hallar una razón teórica. Una aplicación del goto que se puede creer es justificada, es su uso para permitir salidas prematuras de ciclos en lenguajes que no tienen sentencias de salida. Un obvio mal uso del resultado de Böhm y Jacopini es argumentar en contra de la inclusión de todas las estructuras de control más allá de los ciclos pre examinados lógicos y de selección. El lenguaje no ampliamente usado no tiene

todavía que tomar este paso; además, dudamos que todas manden debido al efecto en la escritura y la legibilidad. Los programas escritos con una sola selección y ciclos pre examinados lógicos son generalmente menos naturales en estructura, más complejos, y por lo tanto difíciles de escribir y más difíciles de leer. Por ejemplo, la estructura de selección múltiple de Ada es una gran ganancia para la escritura de Ada, con claros negativos. Otro ejemplo es la estructura de ciclo contadora de muchos lenguajes, especialmente cuando la sentencia es simple, como en Pascal y Ada. No es claro para qué la utilidad de muchas de las otras estructuras de control que han sido propuestas es mérito de su inclusión en lenguajes (Ledgard y Marcotty, 1975). Esta cuestión apoya a un largo grado en la cuestión fundamental de si el tamaño de los lenguajes debe ser minimizado. Ambos, Wirth (1975) y Hoare (1973), fuertemente avalaron la simplicidad en el diseño del lenguaje. En el caso de las estructuras de control, la simplicidad significa que solo algunas sentencias de control deberían estar en el lenguaje, y todas ellas deberían ser simples. La rica variedad de niveles de sentencias deestructuras de control que han sido inventadas muestran la diversidad de opiniones entre los diseñadores del lenguaje. Después de todo el invento, la discusión, y la evaluación, no hay todavía unanimidad de opinión en el preciso conjunto de sentencias de control que deberían estar en un lenguaje. Muchos lenguajes contemporáneos, por supuesto, tienen sentencias de control similar, pero hay todavía algunas variaciones en los detalles de su sintaxis y semántica. Además, hay todavía desacuerdo en si un lenguaje debería incluir un goto; C++ y Ada 95 lo hacen, pero Java no. Una nota final: Las estructuras de control de lenguajes de programación funcional y lógica y Smalltalk son todas bastante diferentes de estas descriptas en este capítulo. Estos mecanismos son discutidos en algunos detalles en el Capítulo 15, 16, y 12, respectivamente. Resumen Las sentencias de control de los lenguajes imperativos suceden en varias categorías: selección, selección múltiple, iterativa, y bifurcación incondicional. FORTRAN introdujo un seleccionador de sentencia de una manera, el lógico IF. El seleccionador de ALGOL 60 es más avanzado, permitiendo la selección de sentencias compuestas e incluyendo un cláusula opcional else. Muchas estructuras de control se beneficiaron de las sentencias compuestas que ALGOL 60 introdujo. El IF aritmético de FORTRAN es seleccionador de tres maneras que usualmente requiere otra bifurcación incondicional. FORTRAN introdujo dos formas de sentencias de selección múltiple: el calculado GO TO y el asignado GO TO . Cierto a sus nombres, ambos son actualmenteramificaciones de manera múltiple. El case de Pascal es representativo de modernas sentencias de selección múltiple; incluye ambos, encapsulación de segmentos seleccionables y ramificaciones implícitas en el

final de cada punto único de salida. Un número largo de diferentes sentencias ciclos han sido inventadas por los lenguajes de alto nivel, comenzando con el contador de FORTRAN DO. La sentencia for de ALGOL 60 fue también complejo, combinando controles contadores y lógicos en una sola sentencia. La sentencia for de Pascal es, en términos de complejidad, lo opuesto. Elegantemente implementa solo la forma más comúnmente necesaria de contar ciclos. La sentencia for de C es la construcción iterativa más flexible. C, C++, FORTRAN 90, Java, Perl, y Ada tienen sentencias de salida para sus ciclos, estas sentencias toman el lugar de uno de los más comunes usos de la sentencias goto. Los iteradores basado en datos son construcciones de ciclos para procesar estructuras de datos, como las listas enlazadas, hashes, y árboles. La bifurcación incondicional, o goto, ha sido parte de muchos lenguajes imperativos. Sus problemas han sido ampliamente discutidos y debatidos. El actual consenso es que debería permanecer en más lenguajes pero su perjuicio debería ser minimizado a través de disciplinas de programación. Los comandos protegidos de Dijkstra son construcciones de control alternativa con características teóricas emántica aparece en los actuales mecanismos de CSP y Ada. positivas. Aunque ellas no han sido adoptadas como construcciones de control de un lenguajes, parte de la s

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF