Al llevar a cabo el estudio del entorno Visual Basic en cualquiera de sus versiones, parecería que una vez que se ha entendido lo que es la programación-orientada-a-objetos y el diseño visual de un programa el oficio del programador es una gran fiesta en donde se tiene una oficio muy divertido. Esto puede ser cierto cuando se están desarrollando programas de aplicación que no involucren algoritmos muy elaborados difíciles de entender, al programador de aplicaciones se le han simplificado mucho sus labores con la introducción de los entornos de programación visual. Sin embargo, este tipo de programación basado en cosas como Visual Basic tiene limitaciones severas para otro tipo de cosas.
Tómese, por ejemplo, el caso de un experimentador que quiere desarrollar un programa para controlar y manipular los movimientos de un sistema robótico manejado directamente desde la computadora conectada con cables al sistema robótico. Las primeras computadoras personales caseras modeladas en torno a las arquitecturas utilizadas por empresas como IBM y Apple tenían para este fin varios puertos disponibles desde los cuales se podían enviar señales eléctricas a un sistema electromecánico externo a la computadora, así como recibir señales del sistema electromecánico mediante los sensores puestos en el sistema electromecánico para tales efectos. Para el envío y recepción de los pulsos eléctricos digitales enviados y recibidos por la computadora, se tenían en la misma recursos como el puerto RS232 usado para la transmisión y recepción serial de pulsos digitales, y el puerto paralelo LPT1 usualmente utilizado para la transmisión y recepción paralela de datos, principalmente las impresoras. Más recientemente, se han agregado a las computadoras los puertos USB para la transmisión y recepción serial de señales eléctricas permitiendo la conexión no de uno sino de varios aparatos a un concentrador (hub) USB. El problema es que Visual Basic carece en su repertorio de funciones de algo con lo cual se puedan enviar o recibir señales eléctricas a través de dichos puertos; ya que eso está al nivel del hardware, y Visual Basic utiliza un lenguaje de alto nivel, un nivel demasiado alto como para poder tener control directo sobre lo que ocurre al nivel de hardware.
Se puede, desde luego, tratar de llevar a cabo la programación de un sistema electromecánico controlado por computadora recurriendo a un lenguaje de bajo nivel, o sea a un lenguaje ensamblador, habido el hecho de que tratar de programar directamente en lenguaje de máquina, en los unos y ceros que entiende la unidad de procesamiento central CPU, está fuera de discusión por lo impráctico y laborioso que resulta tratar de programar directamente en el lenguaje binario de la máquina. Pero hemos visto ya que para poder hacer cualquier cosa inclusive al nivel de un lenguaje ensamblador se requiere de una cantidad enorme de instrucciones. Inclusive con un buen programa ensamblador que ayude a elaborar un programa que será convertido en un programa ejecutable, el gran número de instrucciones que se deben especificar hace que dicho proceso sea sumamente ineficiente.
A continuación tenemos un programa elaborado en lenguaje ensamblador para los procesadores Intel 8088 y el viejo sistema operativo DOS, cuyo propósito es tomar los bytes que hay en cierto dispositivo de entrada (por ejemplo, un archivo guardado en el disco duro) y entregar dicha información en una forma legible a un dispositivo de salida (por ejemplo, una impresora). Esto es representativo del tipo de programas conocidos como dump, y se agrega de paso que uno de ellos, el memory dump (vaciado de la memoria) es usado en aplicaciones especializadas para explorar a fondo lo que hay en la memoria RAM con la finalidad de encontrar errores que ocasionan problemas inesperados (o inclusive la presencia de virus informáticos). El programa que se muestra escrito en lenguaje ensamblador tiene que se convertido a código ejecutable por algún ensamblador como el MASM (Microsoft Assembler):
;*********************************************************; ;* P R O G R A M A D U M P *; ;*********************************************************; ;== Constantes ============================================ NUL equ 0 ;Caracter ASCII NUL BEL equ 7 ;ASCII-Bell BS equ 8 ;ASCII-Backspace TAB equ 9 ;ASCII-Tabulator LF equ 10 ;ASCII-Linefeed CR equ 13 ;ASCII-Carriage Return EOF equ 26 ;ASCII-End of File ESC equ 27 ;ASCII-Escape ;== El programa empieza aqui ============================= code segment para 'CODE' ;Definicion segmentos CODE org 100h assume cs:code, ds:code, es:code, ss:code ;-- Empezar rutina ---------------------------------------- dump label near ;-- Leer 9 bytes del dispositivo de entrada ----- xor bx,bx mov cx,9 ;leer 9 caracteres mov dx,offset newbyte ;domicilio del buffer mov ah,3Fh int 21h ;invocar funcion DOS or ax,ax ;caracteres leidos? jne dodump ;SI --> procesar linea jmp dumpend ;NO --> DUMPEND dodump: mov dx,ax ;anotar numero caracteres ;-- Llenar buffer de salida con espacios -------- mov cx,15 ;15 palabras (30 bytes) mov ax,2020h ;ASCII de " " a AH y AL mov di,offset dumpbuf ;domicilio buffer salida cld ; rep stosw ;llenar buffer con espacios ;-- Construir buffer de salida ------------------ mov cx,dx ;numero caracteres leidos mov di,offset dumpbuf+31 ;posicionar en el buffer mov bx,offset newbyte ;puntero a buffer entrada mov si,offset dumpbuf ;posicion codigos Hex bytein: mov ah,[bx] ;leer byte push si ;almacenar SI en la pila mov si,offset sotab ;domicilio puesto en tabla mov dx,offset sotext-6 ;domicilio texto sotest: add dx,6 ;siguiente entrada lodsb ;cargar codigo de tabla cmp al,255 ;fin de la tabla? je noso ;SI --> no caracteres esp. cmp ah,al ;codigos en concordancia? jne sotest ;NO --> siguiente en tabla ;-- El codigo fue un caracter especial ---------- push cx ;almacenar contador mov si,dx ;copiar DX a SI lodsb mov cl,al rep movsb pop cx ;obtener contador pop si ;regresar SI de la pila mov al,ah ;copiar caracter a AL jmp short hex ;calcular codigo Hex noso: pop si ;regresar SI de la pila mov al,ah ;copiar caracter a AL stosb ;almacenar en buffer hex: mov al,ah ;codigo de caracter a AL and ah,1111b shr al,1 ;corrimiento AL 4 bits shr al,1 shr al,1 shr al,1 or ax,3030h ;convertir AH and AL a ;codigos ASCII cmp al,"9" ;es AL una letra? jbe nobal ;NO --> no correcion add al,"A"-"1"-9 ;AL correcto nobal: cmp ah,"9" ;es AH una letra? jbe hexout ;NO --> no correcion add ah,"A"-"1"-9 ;AH correcto hexout: mov [si],ax ;guardar Hex en buffer add si,3 ;apuntar hacia la ;siguiente posicion inc bx ;puntero a siguiente byte loop bytein ;procesar siguiente byte mov al,219 ;separador de conjunto stosb mov ax,LF shl 8 + CR ;CR y LF terminan buffer stosw ;escribir en el buffer ;-- Enviar dump a la terminal de salida ------ mov bx,1 mov cx,di ;determinar numero de sub cx,offset dumpbuf ;caracteres a transmitir mov dx,offset dumpbuf ;domicilio del buffer mov ah,40h int 21h ;invocar funcion DOS jmp dump ;leer siguientes 9 bytes dumpend label near mov ax,4C00h int 21h ;== Datos ================================================= newbyte db 9 dup (?) ;los 9 bytes leídos en dumpbuf db 30 dup (?), 219 ;el buffer de salida db 49 dup (?) sotab db NUL,BEL,BS,TAB ;tabla caracteres control db LF,CR,EOF,ESC db 255 sotext equ this byte db 5,"Suponiendo que el programa anterior esté guardado en un archivo con nombre (incluyendo extensión) como DUMP.ASM, la conversión del programa anterior a un archivo ejecutable llamado DUMP.COM se lleva a cabo invocando desde una ventana de líneas de comandos al ensamblador MASM con las siguientes instrucciones:" ;NUL db 5," " ;Bell db 4," " ;Backspace db 5," " ;Tabulator db 4," " ;Linefeed db 4," " ;Carriage-Return db 5," " ;End of File db 5," " ;Escape code ends end dump
masm dump
link dump
exe2bin dump dump.com
Esto nos produce un archivo ejecutable DOS del tipo COM (ya obsoleto) que es usado bajo el sistema operativo PC-DOS (o MS-DOS) de la siguiente manera:
dump [<Entrada] [>Salida]
Si repasamos el programa dado arriba en lenguaje ensamblador, no debe quedar duda alguna de que para poder elaborarlo el programador no solo requiere conocer bien el lenguaje; también se requiere conocer a fondo el tipo de arquitectura del procesador (8088) en el cual se ejecutará el programa ejecutable una vez que haya sido compilado por el ensamblador MASM, debe estar familiarizado a fondo con el hardware de la máquina, y además se requiere de un conocimiento detallado de todas las funciones primitivas internas del sistema operativo como la que es invocada con la interrupción int 21h.
Quizá el mayor agravante de todos en el uso de cualquier lenguaje ensamblador es que su portabilidad es cero, en virtud de que un ensamblador no contiene más que una representación simbólica de las mismas instrucciones en lenguaje de máquina que ejecuta un procesador CPU en particular, y esto varía de una máquina a otra, de un fabricante a otro, inclusive dentro de una misma familia de computadoras construídas por una misma empresa. No hay nada más desagradable para cualquier programador que haya gastado seis u ocho meses de su vida en elaborar en assembler un programa de contabilidad ejecutable en un procesador Motorola 68000, que recibir de sus superiores las órdenes de tener que elaborar el mismo programa pero para que se ejecute en un procesador Intel 80386, lo cual significa que tendrá que comenzar a partir de cero, y lo único que lo motivará a llevar a cabo esta duplicidad absoluta de esfuerzos no es la pasión por programar sino la paga que le ofrecen como compensación por su sacrificio y sus servicios.
Confrontados con la disyuntiva de las comodidades de un entorno de alto nivel como Visual Basic que no nos permite tomar un control directo del hardware, y un lenguaje de bajo nivel laborioso y extenuante como el ensamblador EDTASM (para el procesador Motorola 6809) que ya vimos en una entrada previa o como el ensamblador MASM (para el procesador 8088) cuya muestra se dió arriba, lo ideal sería tener algo intermedio, algo que pueda ofrecer las ventajas de un lenguaje de alto nivel pero que pueda ser lo suficientemente flexible para comunicarse directamente con el hardware de la máquina; con los programas fuente elaborados en código casi universal que con cambios mínimos pueda ser transportado de una computadora a otra pese a que tengan distintas arquitecturas y distintos tipos de hardware. Y aquí es donde entra el lenguaje C.
A C frecuentemente se le refiere como un lenguaje de nivel-intermedio, situado entre un nivel bajo (ensamblador) y Pascal (nivel alto). Parte de la razón por la cual se inventó C fue para darle al programador un lenguaje de alto nivel que podía ser usado como substituto del lenguaje ensamblador. El lenguaje ensamblador que usa una representación simbólica de las instrucciones ejecutadas por una computadora y que guarda una relación de uno-a-uno entre las instrucciones simbólicas en ensamblador y las instrucciones en lenguaje de máquina, aunque hace posible escribir programas extremadamente rápidos y eficientes (esta es la razón que justifica el uso continuado de los ensambladores), es demasiado extenuante y propenso a que se cometan errores. Por otro lado, los lenguajes como Pascal están demasiado removidos de la máquina, y un enunciado en Pascal no tiene virtualmente ninguna relación con la secuencia de instrucciones que son ejecutadas al nivel del lenguaje de máquina. Sin embargo, mientras que C retiene estructuras de control de alto nivel, como las que encontramos en Pascal, le permite al programador la manipulación de bits, bytes y domicilios en una manera más asociada a la máquina que a la abstracción presentadas por otros lenguajes de alto nivel. Por esta razón C ha sido llamado ocasionalmente “un lenguaje ensamblador de alto nivel”. En virtud de su naturaleza dual, C le permite a los programadores crear programas extremadamente rápidos y eficientes sin tener que recurrir al lenguaje ensamblador.
La filosofía detrás de C es que el programador sabe bien lo que está haciendo. Por esta razón, C nunca se interpone en el camino del programador, y éste puede usar o abusar del lenguaje en la manera que quiera. Si por alguna extraña razón el programador quiere alterar directamente la memoria en la cual el programa reside, no hay nada que un compilador C haga para impedirlo. La razón detrás de “el programador es rey” es que le permite a un compilador C poder crear código extremadamente eficiente y rápido, porque pone la responsabilidad de verificación de errores sobre los hombros del programador. Un compilador C, usado dentro de un entorno integrado de desarrollo, permite desde luego la detección de algunos errores tales como errores ortográficos en la sintaxis de los comandos o el uso ilegal de comandos, lo cual tal vez se pueda detectar en ejecuciones simuladas bajo un buen entorno de desarrollo mediante advertencias sobre peligros potenciales, pero en la libertad que C le dá al programador si el código cumple con las reglas del juego el programador está en plena libertad de construír un programa que termine borrándole todos los archivos que tiene almacenados en el disco duro.
El lenguaje C fue inventado en 1972 en lo que fue la Bell Labs (la primera gran empresa telefónica de los Estados Unidos) por Dennis Ritchie, e implementado por vez primera en una computadora PDP-11 fabricada por la empresa Digital Equipment Corporation (se trataba de las computadoras bautizadas en ese entonces como mini-computadoras), cuando él y Ken Thompson trabajaban en el diseño del sistema operativo UNIX. C fue el resultado de un proceso de desarrollo que comenzó con otro lenguaje más viejo llamado BCPL, el cual a su vez influenció un lenguaje inventado por Ken Thompson llamado B, que a su vez condujo al desarrollo de C (no hubo aquí, como pudiera suponerse, un primer lenguaje A).
El lenguaje C ha ido evolucionando, pasando por C++ y llegando a C#, y para fines didácticos lo ideal es empezar con un común denominador, una versión generalizada cuyo código pueda ser compilado por la mayoría de los compiladores C en existencia. La primera versión formalizada del lenguaje C aceptada como vehículo casi universal para la elaboración de programas capaces de ser portados con cambios mínimos de una máquina a otra es el ANSI C, acordada por la American National Standards Institute, y es a lo que trataremos de apegarnos al empezar nuestro estudio de este lenguaje.
Es importante dejar aclarado desde un principio que C es sensible a la capitalización. Esto significa que los caracteres en minúsculas y los caracteres en mayúsculas son tratados como caracteres separados. En otros lenguajes, por ejemplo, los nombres de variables conteo, Conteo y CONTEO son tres maneras de especificar la misma variable. Sin embargo, en C representan tres variables distintas. Y mientras que hay palabras reservadas en C que no pueden ser usadas para otro propósito más que la cosa para la cual fueron reservadas, como la palabra clave return, un programador puede perfectamente definir una variable nueva como RETURN o como Return sin que haya conflicto alguno en el proceso de compilación (sin embargo, esta práctica no es recomendable porque se puede prestar a confusiones en la lectura del código de un programa).
Empezaremos nuestra introducción al lenguaje C con lo que quizá sea un programa de lo más sencillo posible, el programa de bienvenida “Hola, mundo!”:
#include <stdio.h>
/* Programa de muestra # 1 */
main()
{
printf("Hola, mundo!");
}
La gran mayoría de los entornos de desarrollo IDE para el lenguaje C proporcionan los medios para ejecutar un programa C como el anterior antes de convertir en un archivo ejecutable el programa fuente en lenguaje C que haya sido elaborado en un editor de texto, con un recurso como una línea de menú en donde usualmente se encuentra una opción que puede ser Run (Correr, Ejecutar). Al seleccionarse esta opción, el entorno compilará el programa y lo enlazará con las funciones de librería que se requieran, llevando a cabo la ejecución del mismo. En este caso, se imprimirá en una ventana de línea de comandos el siguiente mensaje:
Hola, mundo!
|
Veamos más de cerca el programa. La primera línea:
#include <stdio.h>
le indica al compilador que incluya el archivo de cabecera STDIO.H en el proceso de compilación. Este archivo contiene la información requerida por el programa para garantizar el funcionamiento correcto de las funciones convencionales (standard) de entrada/salida I/O (input/output). En lo que al programa de arriba concierne, el archivo de cabecera STDIO.H (Standard Input Output) contiene en su librería de funciones lo que requiere la función print() para ser ejecutada. Algunos programas requerirán más de un archivo de cabecera, como lo es el caso de un programa que utilice alguna función matemática para la extracción de raíces cuadradas o la evaluación de la tangente trigonométrica de un ángulo, en cuyo caso se requerirá otro archivo de cabecera que tendrá un nombre como MATH.H y el cual contendrá la definición de funciones matemáticas básicas. Obsérvese que los archivos de cabecera que son incluídose en C tienen que ser encapsulados entre los símbolos:
“<” y “>”
La segunda línea:
/* Programa de muestra # 1 */
es la manera en la que se hacen los comentarios en C. Todos los comentarios empiezan con la secuencia /* y terminan con la secuencia */. Podemos poner un comentario en cualquier parte de un programa escrito en C, lo cual puede ser de mucha ayuda para dar legibilidad a los programas. Cualquier cosa (¡lo que sea!) que esté puesta entre estos delimitadores será ignorada por el compilador. Un hecho importante es que el alcance de los delimitadores para comentarios en C se puede extender a varias líneas y no a una sola. De este modo, lo siguiente es el equivalente a un comentario válido en C:
/* Esta es una muestra de un comentario en C que se extiende a cuatro líneas de texto, puesto entre los delimitadores usados para marcar comentarios en C. */
Si examinamos de cerca el programa que se ha dado, observaremos que a la línea del comentario le sigue una línea en blanco. En C, todas las líneas en blanco son permitidas y no tienen efecto alguno en el programa ya que el compilador las ignora; podemos poner líneas en blanco a nuestro antojo para darle mayor legibilidad a un programa.
La línea:
main()
especifica el nombre de una función muy especial, la función main(). Todos los programas en C comienzan su ejecución empezando primero que nada con una invocación a la función main(). Es importante asentar desde un principio que aunque que la función main() aparezca puesta al principio de un programa que tenga varias funciones, en una gran mayoría de compiladores tiene que ser puesta al final (con todos sus contenidos) de un programa. Veremos posteriormente las razones para ello.
Vemos en la siguiente línea el caracter:
{
que no es más que un corchete sencillo que marca el inicio de lo que es el código de la función main().
La primera (y única) línea de código dentro de la función main() es una que produce una salida para ser imprimida en la pantalla:
printf("Hola, mundo!");
La línea invoca la función de impresión printf(), la cual se encarga de imprimir en la pantalla monocromática de un monitor clásico de blanco y negro la hilera (o cadena, como se acostumbra llamarla en algunos textos) “Hola, mundo!”.
Obsérvese que la instrucción de impresión en el programa fuente en C termina en un semicolon (punto y coma). Todos los enunciados en C tienen que ser terminados con un semicolon. Es lo que usa C para poder separar y no confundir las diversas instrucciones que contiene un programa.
Obsérvese que lo que será impreso por la función printf() se le tiene que proporcionar como argumento puesto entre dobles comillas. Todo lo que será impreso en una ventana de línea de comandos por la función printf() tiene que ser puesto entre dobles comillas.
La última línea del programa es un corchete de cierre:
}
y marca el final de la función main(). Cuando se ha llegado al final de la función main(), la ejecución del programa C es terminada.
Veamos otro programa un poco más elaborado:
#include <stdio.h>
/* Programa de muestra # 2 */
main()
{
int edad;
edad = 98;
printf("Mi edad es %d\n", edad);
}
Puesto que la única función que es invocada dentro de main() es la función printf(), el único archivo de cabecera que necesitamos es el archivo STDIO.H, incluído en la primera línea del programa, a la cual le sigue un comentario y la función main().
La primera línea de código dentro de la función main() es:
int edad;
Esta línea declara una variable llamada edad y con el declarador de tipo int le dice al compilador que se trata de un entero (integer). En C, todas las variables tienen que ser declaradas antes de ser usadas. El proceso de declaración involucra la especificación del nombre de la variable así como el tipo de variable. En este caso, edad es de tipo int, la palabra clave usada por C para un entero. Hay otros tipos de datos como char, float y double que iremos introduciendo conforme vayamos avanzando.
La siguiente línea es:
edad = 98;
que podemos tomar como un ejemplo de un enunciado de asignación. Coloca el valor 98, el cual es un entero, en la variable edad. Obsérvese que C utiliza el signo de la igualdad para hacer la asignación. Nuevamente, obsérvese también que el enunciado es terminado con un semicolon, en concordancia con el hecho de que todos los enunciados en C tienen que ser terminados con un semicolon.
La siguiente línea, que envía información imprimible a la pantalla de la ventana de línea de comandos, es:
printf("Mi edad es %d\n", edad);
En este caso, se imprimirá en una ventana de línea de comandos el siguiente mensaje:
Mi edad es 98
|
La línea del código para la salida de la hilera de texto consiste de dos argumentos, "Mi edad es %d\n" y edad. Puesto que es un enunciado C, termina en un semicolon. La función printf() (print function) trabaja de este modo: el primer argumento es una hilera entre dobles comillas (algunas veces llamada hilera de control) que puede contener ya sea caracteres normales o códigos de formatos que comienzan con el signo de porcentaje (%). Los caracteres normales son impresos en la pantalla tal cual en el mismo orden en el que son encontrados. Un código de formato le informa a printf() que un artículo que no es un caracter sencillo va a ser impreso. En este caso, el %d significa que un entero será sacado en formato decimal. El valor a ser sacado es encontrado en el segundo argumento de la función prinf(), en este caso, edad. El código \n es un código especial que instruye a C de implementar una secuencia de salto hacia una nueva línea, lo que se conoce como un “regreso de carro y salto a la siguiente línea” en la terminología C. Para entender la relación que hay entre los caracteres normales y los códigos de formato, podemos cambiar la línea escribiéndola de la siguiente manera:
printf("Mi %d edad es\n", edad);
El cambio hará que se escriba lo siguiente:
Mi 89 edad es
|
El punto sobresaliente es que el lugar en donde ocurre el código de formato en la hilera de texto determina en dónde se imprimirá el segundo argumento proporcionado a printf().
Hemos visto dos programas en los cuales no hay interacción alguna con el usuario, simplemente se imprime algo en la pantalla de la ventana de línea de comandos. A continuación veremos un programa en el cual se obtiene entrada del usuario recurriendo a una segunda función de biblioteca llamada scanf() que es usada en una ventana de línea de comandos para darle entrada a información introducida por el usuario a través del teclado:
#include <stdio.h>
/* Programa de muestra # 3 */
main()
{
int pies;
float metros;
printf("Dame el numero de pies: ");
scanf("%d", &pies);
metros = pies * 0.3048 /* Conversion pies a metros */
printf("%d pies son igual a %f metros\n", pies, metros);
}
En este programa se declaran al principio dos variables, pies es un entero (un número que no admite punto decimal), y metros es de tipo float (un número que admite punto decimal y por lo tanto puede tener una parte fraccionaria).
La función scanf() es usada por el programa para leer un entero introducido por el usuario desde el teclado:
scanf("%d", &pies);
El %d en el primer argumento le dice a scanf() que lea un entero y que coloque el resultado de la lectura en la variable que sigue. El símbolo ampersand & puesto como prefijo a pies es necesario para que scanf() pueda trabajar en forma apropiada (posteriormente se verá el por qué se requiere esto).
Hasta este punto, ya se ha llevado a cabo un diálogo interactivo con el usuario en la ventana de líneas de comandos. Aunque en las pantallas clásicas monocromáticas del ayer no había capacidad para producir texto en colores, adoptaremos de aquí en delante una convención: el texto en blanco (sobre el fondo negro de la pantalla) en las simulaciones que se irán mostrando es lo que imprime en la pantalla el programa C bajo las órdenes de la función printf(), y el texto en color amarillo (también sobre el fondo negro de la pantalla) es lo que escribe el usuario. Si en la simulación suponemos que el usuario escribe el valor 345 en respuesta a la petición prompt de la computadora, entonces antes de que el usuario oprima la tecla de entrada [Enter] se tendrá lo siguiente en la pantalla:
Dame el numero en pies: 345
|
Al oprimirse la tecla de entrada [Enter], el número de pies es convertido a su equivalente en metros usando el factor de conversión requerido:
metros = pies * 0.3048
Obsérvese que aunque pies es un entero, puede ser dividido entre un número de punto flotante y asignado a una variable de punto flotante. A diferencia de de otros lenguajes de programación como FORTRAN, C permite que distintos tipos de datos puedan ser mezclados en una expresión. Y así como se acostumbra en muchos otros lenguajes, el símbolo del asterisco (*) significa multiplicación.
La conversión es presentada usando un llamado a printf(). Como puede verse, en esta ocasión printf() toma tres argumentos: la hilera de control y las variables pies y metros. La regla general es que en printf() deben seguir tantos argumentos a la hilera de control como el número de códigos de formato que haya en la hilera de control. Puesto que hay dos códigos de formato, se requiere de dos argumentos adicionales. Estos argumentos deben estar apareados en orden, de izquierda a derecha, con los códigos de formato. Si se observa con detenimiento, se verá que se usa un %f para imprimir metros y no un %d. Esto en virtud de que printf() debe saber en forma precisa qué tipo de datos va a imprimir. El %f significa que se trata de un valor de tipo float.
De este modo, la computadora proporciona su respuesta en la siguiente línea de texto, imprimiéndose algo como lo que se muestra a continuación:
345 pies son igual a 105.15598 metros
|
Una vez que estamos satisfechos con la manera en la cual se ejecuta el código de un programa C y que estamos seguros (o casi seguros) de que está libre de errores, podemos compilar el programa fuente a un archivo ejecutable, recurriendo a una opción de línea de menú del entorno integrado de desarrollo bajo un nombre como Compile. o como Make EXE File. Si el programa fuente en C es guardado en un archivo de texto como PROG0003.C y es compilado, el resultado será un archivo ejecutable titulado como PROG0003.EXE. De este modo, suponiendo que seguimos con el programa anterior, en la ventana de línea de comandos de un antigüo sistema operativo como el DOS de Microsoft ponemos en marcha el programa situándonos en el directorio (carpeta) que contiene el archivo ejecutable, escribiendo el nombre del archivo (en el caso de los sistemas operativos Microsoft, no es necesario escribir la extensión .EXE para que el programa sea tomado como un programa ejecutable), y oprimiendo la tecla [Enter]. Suponiendo que el programa ejecutabler PROG0003.EXE se encuentra en el directorio raíz del disco duro C: entonces la secuencia de operaciones será la siguiente:
C:>PROG0003
Dame el numero en pies: 345 345 pies son igual a 105.155998 metros C:> |
De este modo, al llevarse a cabo la ejecución del archivo PROG0003.EXE el control de la máquina es transferido por el sistema operativo al programa ejecutable que se encarga de hacer lo suyo, y una vez que el programa ha terminado de hacer su labor el programa le devuelve al sistema operativo el control de la máquina, volviendo a aparecer el indicador C:> que señala al usuario que el sistema operativo DOS está de regreso y que el directorio actual está situado en el directorio raíz del disco duro C:.
En un sistema operativo Windows de Microsoft, al llevarse a cabo la compilación de un programa C el archivo ejecutable es guardado anexándosele un ícono convencional distintivo de los programas ejecutables que corren bajo una interfaz DOS:
Resulta muy tentador echar a andar un programa ejecutable DOS bajo la interfaz visual de un sistema operativo Windows simplemente haciendo clic o doble clic con el Mouse en un ícono como el que se tiene arriba. Sin embargo, en un programa que no recibe entrada del usuario (lo cual cuando sí ocurre pone a la computadora en un estado de espera), lo que se verá es una ventana DOS que se abre rápidamente y que se cierra de inmediato, sin tenerse tiempo de apreciar absolutamente nada. Ello se debe a que al encontrarse con un programa ejecutable tipo DOS, Windows invoca su propia ventana de líneas de comandos en forma automática y abre el programa DOS, cerrando también de manera automática la ventana de línea de comandos cuando la última instrucción binaria del programa DOS ha sido ejecutada.
Para poder apreciar toda la acción, en vez de hacer simplemente clic con el Mouse sobre el ícono que representa un programa ejecutable DOS se vuelve necesario abrir de antemano la ventana DOS, para lo cual en un sistema operativo como Windows XP nos vamos al botón de inicio (Start) situado en la esquina inferior izquierda de la pantalla, con la finalidad de invocar la opción de ejecución Run...:
Al echar a andar dicha opción, se abre una ventana de diálogo en la cual tenemos que escribir la palabra “command” que invocará la maquinaria del interpretador COMMAND.COM con lo cual se echa a andar la ventana de línea de comandos:
Si hacemos lo anterior, se abrirá la ventana de líneas de comandos DOS desde la cual se pueden echar a andar programas ejecutables DOS bajo un entorno de simulación protegido (en el caso de Windows XP, éste nos sitúa de manera automática y predeterminada no en el directorio raíz del disco duro sino en la carpeta DOCS puesta a su vez en el directorio raíz del disco duro):
De este modo, podemos echar a andar programas ejecutables DOS bajo esta ventana de líneas de comandos y repasar los resultados que se van obteniendo sin que Windows cierre dicha ventana al llegar al final de una instrucción ejecutable. Sin embargo, puesto que Windows no cerrará en forma automática la ventana DOS al hacer clic con el Mouse en el botón “X” usualmente puesto por Windows en la esquina superior derecha para tales efectos, el programador lo tiene que hacer desde el mismo DOS usando la orden de terminación DOS que puede ser:
exit
o también:
quit
con lo cual desaparece la ventana de líneas de comandos.
Una limitación del programa de muestra # 3 dado arriba es que solo puede convertir a metros números enteros de pies. Un programa más flexible permite convertir no solo números enteros sino también valores de punto flotante a metros. Esto se puede lograr fácilmente cambiando el programa de la manera en que se muestra a continuación:
#include <stdio.h>
/* Programa de muestra # 4 */
main()
{
float pies, metros; /* Hacer pies un float */
printf("Dame el numero de pies: ");
scanf("%f", &pies); /* Leer un float */
metros = pies * 0.3048 /* Conversion pies a metros */
printf("%f pies son igual a %f metros\n", pies, metros);
}
Como puede apreciarse, lo primero que ha cambiado es que pies es ahora del tipo float. Obsérvese que se pueden declarar varias variables del mismo tipo usando una lista separada por comas:
float pies, metros;
Por capacidades como esta es que hablamos de la coma como el operador coma. Después, la función scanf() es invocada usando un código de formato %f en lugar de un %d. Esto ocasiona que se lea una variable como variable de punto flotante (aunque el usuario introduzca solo un número entero desde el teclado). Obsérvese la similitud entre los códigos de formato de printf() y scanf(), son los mismos. La función printf() ahora requiere de un código de formato %f para escribir los pies.
Podemos crear otras funciones en C de la misma manera en la que se crea la función principal main(), para llamarlas desde otras partes del programa. A modo de ejemplo, el siguiente programa usa la función hola() para imprimir “Hola” en la pantalla de la ventana de línea de comandos.
#include <stdio.h>
/* Programa de muestra # 5 */
/* Un programa sencillo con dos funciones. */
main() {
hola(); /* llamado a la funcion hola() */
}
hola {
printf("Hola otra vez!\n")
}
Obsérvese que en este programa se ha usado otro estilo para colocar los corchetes que encapsulan el contenido de cada función. Es simplemente otro estilo diferente de escribir un programa en C para darle legibilidad, tan válido como el estilo que fue usado en los programas anteriores. Al compilador C no le importan en lo absoluto los espacios en blanco ni los saltos de una línea a otra, ya que va removiendo diligentemente todos los espacios en blanco y los saltos de línea conforme va compilando todo para convertirlo en un programa binario ejecutable (en lenguaje de máquina). Si se ejecuta el programa anterior se imprime:
Hola otra vez!
|
En el programa anterior en el cual se usan dos funciones; y la función principal main() es puesta al principio. Sin embargo, en muchos compiladores (por no decir todos), para que el programa pueda ser compilado sin errores y convertido a un programa ejecutable la función principal main() tiene que ser puesta no al principio sino al final, de lo contrario el programa no será compilado y se marcarán errores fatales (esto ocurre también con el entorno Borland C++). Esto se discutirá en mayor detalle en la siguiente entrada. Por lo pronto, y por razones didácticas, seguiremos poniendo la función main() al principio de los programas de ejemplo en C en esta obra.
Aquellos con inclinaciones matemáticas podrían hacer la observación de que una verdadera función es algo capaz de poder recibir argumentos que le sean proporcionados, como la función de la raíz cuadrada que toma un argumento numérico como 2 y esperamos que nos regrese un número como 1.4142:
sqrt(2) = 1.4142
Y en efecto, para poder ser realmente de utilidad debemos poder declarar en C funciones capaces de recibir argumentos. Un argumento de función, el que sea, es simplemente un valor que le es pasado a la función al momento de ser invocada. Hemos visto ya dos funciones que pueden tomar argumentos: printf() y scanf(). Del mismo modo, podemos crear funciones que toman argumentos. Por ejemplo, la función cuadrado() en el siguiente programa toma un argumento entero e imprime en la pantalla el cuadrado del número:
#include <stdio.h>
/* Un programa que toma una función con un argumento. */
main() {
int num;
num = 100;
cuadrado(num); /* invocar a cuadrado() con num */
}
cuadrado(int x) {
printf("El cuadrado de %d es %d\n", x, x*x)
}
Como puede verse en la declaración de la función cuadrado(), la variable x que recibirá el valor que le es pasado a cuadrado() es declarada dentro de los paréntesis que siguen al nombre de la función. Las funciones que no toman argumentos no requieren variable alguna así que en tal caso el interior de los paréntesis está vacío. Cuando cuadrado() es invocada, el valor de num, en este caso 100, es pasado a x. Esto ocasiona que la línea:
El cuadrado de 100 es 10000
|
sea puesta en la pantalla.
Es importante no confundir lo que es el argumento que le es pasado a una función y lo que es el parámetro formal de la función. El argumento es el valor usado cuando se invoca una función, mientras que la variable que recibe el valor de los argumentos usados en la función es el parámetro formal. En el ejemplo dado arriba, num es el argumento usado en la invocación de la función cuadrado(), mientras que x es el parámetro formal que recibe el valor que le es pasado. De hecho, las funciones que reciben argumentos son llamadas funciones parametrizadas. Lo importante a recordar es que la variable usada como un argumento en la invocación de una función no tiene nada que ver con el parámetro formal que recibe su valor.
Ahora tenemos otro ejemplo sencillo de una función parametrizada, la cual toma no uno sino dos argumentos:
#include <stdio.h>
/* Programa de muestra # 6 */
/* Un ejemplo de funcion con dos argumentos. */
main()
{
mul(10, 11);
}
mul(int a, int b)
{
printf("%d" , a*b);
}
La función de multiplicacion mul() imprime el producto de sus dos argumentos enteros, o sea que imprimirá simplemente el número “110”. Obsérvese que los dos parámetros para mul() son declarados usando una lista separada mediantes comas (o mejor dicho, operadores comas):
mul(10, 11);
Es importante recordar siempre que el tipo del argumento usado para invocar una función debe ser del mismo tipo que el parámetro formal que recibe ese argumento. En el ejemplo dado arriba, no se debe tratar de invocar la función mul() con dos números de punto flotante. De cualquier modo, el lenguaje C proporciona lo que se conoce como conversión automática de tipos para proporcionar cierta flexibilidad en esto, aunque es mejor desde un principio acostumbrarse a que el tipo de un argumento esté aparejado con el tipo de parámetro.
Las funciones sencillas que hemos visto arriba en realidad no son el equivalente de una función matemática como la función matemática tangente que toma un ángulo como 60 grados y nos regresa un valor como 1.732050808, ya que en vez de regresar un cierto valor simplemente imprimen algo en la pantalla sin regresar nada. Sin embargo, es posible definir una función en C de modo tal que nos regrese algo después de efectuar su labor. Y esto es importante porque en todos los sistemas operativos muchas de las funciones de biblioteca C nos regresan un valor. En C, una función puede regresar un valor a una rutina que la invoca usando la palabra clave return. Para ilustrar esto, el programa previo que imprime el valor del producto de dos números puede ser reescrito de la siguiente manera:
#include <stdio.h>
/* Programa de muestra 7 */
/* Un programa que usa return. */
main()
{
int respuesta;
respuesta = mul(10,11); /* asignar valor de retorno */
printf("La respuesta es %d\n", respuesta);
}
/* Esta es una funcion que regresa un valor */
mul(int a, int b)
{
return a*b;
}
Obsérvese que el valor de retorno es asignado a una variable poniendo la funcion al lado derecho del enunciado de asignación:
respuesta = mul(10,11)
En este ejemplo, mul regresa el valor de <b>a*b</b> usando el enunciado return. El valor es asignado entonces a respuesta. Esto es, el valor regresado por el enunciado return se convierte en el valor de mul() en la rutina de invocación de la función.
De nueva cuenta, así como hay distintos tipos de datos para especificar las variables, también hay distintos tipos de valores de retorno. Hay que asegurarse de que la variable que recibe un valor de retorno es del mismo tipo que el valor regresado por una función.
Es posible causar que una función ejecute un regreso usando el enunciado return sin que haya algún valor que le sea anexado, ocasionando que el valor de retorno sea indefinido.
Estamos en condiciones de poder especificar la forma general de una función en C:
retorno nombre(lista de parámetros)
{
código de la función
}
en donde nombre es el nombre que el programador le dá a la función, retorno es el tipo de dato que la función producirá y regresará al código que la invoca (entero, punto flotante, caracter, hilera de texto, etcétera), lista de parámetros es la lista separada con comas de los parámetros formales de la función, y el código de la función son las instrucciones en lenguaje C que la función ejecutará.
Ahora introduciremos dos comandos sencillos de C: if y for, siendo el primero un enunciado condicional y el segundo un enunciado para la implementación de bucles (loops),
El enunciado if de C trabaja de la misma manera en la que trabaja un enunciado IF en cualquier otro lenguaje como BASIC. La sintaxis del enunciado en su expresión más sencilla es (obsérvese el semicolon puesto al final y usado para terminar la instrucción):
if(condición) enunciado;
en donde la condición es una expresión que es evaluada a falsa o verdadera. En C, el falso es igual a cero, y el true es cualquier cosa que no sea igual a cero. La siguiente instrucción imprime en la ventana de líneas de comandos la frase “5 es menor que 8”:
if(5<8) printf("5 es menor que 8");
Los operadores de comparación son similares que los que son usados en otros lenguajes como BASIC, tal como “<” para “menor que” y “>=” para “mayor o igual que”. Sin embargo, en C el operador de igualdad para fines de comparación lógica es:
==
y no debe ser confundido con el operador de asignación “=” usado para asignar valores a variables. Por lo tanto, la siguiente instrucción no imprime “Hola!”:
if(3==4) printf("Hola!");
El bucle for en C funciona de la misma manera que el bucle FOR en otros lenguajes como Pascal o BASIC. En su forma más sencilla, tiene la siguiente sintaxis (obsérvese el semicolon puesto al final y usado para terminar la instrucción):
for(inicialización, condición, incremento) enunciado;
en donde condición es usada para fijar a un valor inicial la variable de control del bucle, la condición es una expresión que es probada cada vez que se repite el bucle, y el incremento incrementa la variable de control del bucle cada vez que se repite la ejecución. En el siguiente ejemplo, el programa imprime en la pantalla los números 5 al 30:
#include <stdio.h>
/* Programa de muestra # 8 */
/* Un programa que ilustra el bucle for. */
main()
{
int conteo;
for(conteo=5; conteo<=30; conteo++) printf("%d ", conteo);
}
Como puede verse, conteo es inicializado a 5. Cada vez que el bucle se repite, la condición conteo<=30 es probada. Si es verdadera, la función printf() es ejecutada y conteo es incrementado en una unidad. Los dos signos + puestos juntos inmediatamente después de conteo le indican a C que hay que incrementar conteo en una unidad cada vez que se repite el bucle. Cuando el conteo es mayor que 30, el bucle se dá por terminado
Así pues, el lenguaje C está basado en el concepto de bloques de construcción, en “ladrillos”. Un programa C no es más que una colección de una o más funciones. Para crear un programa C, primero creamos funciones y tras ello las juntamos en un solo lugar, el programa. Esto dá soporte a bloques de código. Un bloque de código es un grupo de enunciados del programa conectados lógicamente que es tratado como una unidad. En C, creamos un bloque de código poniendo una secuencia de enunciados entre corchetes de apertura y cierre. En el ejemplo:
if(x<5) {
printf("muy bajo, intentalo de nuevo");
scanf("%d", &x);
}
los dos enunciados después de if y puestos entre el par de corchetes son ejecutados ambos si x es menor que 5. Los dos enunciados junto con los corchetes representan un bloque de código. Son una unidad lógica, uno de ellos no se puede ejecutar sin que se ejecute el otro también. En C, el objetivo de la mayoría de los comandos puede ser un enunciado sencillo o un código de bloque. Los códigos de bloque no solo permiten que muchos algoritmos sean implementados con mayor claridad, eficiencia y elegancia, también le ayudan al programador a conceptualizar la naturaleza verdadera de una rutina.
Un tipo importante de dato en C es char, que significa “caracter” (del inglés, character). Un caracter es un valor de un byte que puede ser usado para almacenar caracteres capaces de ser impresos en una impresora o enteros en el rando de 0 a 255. Una constante de caracter debe estar encerrada entre comillas sencillas (¡no comillas dobles como en el caso de las hileras!). El siguiente programa imprime en la pantalla las letras “ABC” (obsérvese que se ha introducido un nuevo código de formato para printf(), %c que imprime un caracter sencillo):
#include <stdio.h>
/* Programa de muestra # 9 */
/* un ejemplo sencillo usando caracteres. */
main()
{
char ch; /* variable declarada como tipo char */
ch = 'A';
printf("%c", ch);
ch = 'B';
printf("%c", ch);
ch = 'C';
printf("%c", ch);
}
Aunque es posible usar scanf() para leer un caracter sencillo del teclado, una manera más común disponible en compiladores como Turbo C es usar la función de biblioteca getche() (que significa “get character with echo”, produciéndose un “eco” o impresión del caracter en la pantalla). La función getche() espera a que la tecla sea oprimida, y regresa dicho resultado que puede ser asignado a una variable declarada previamente como una variable tipo char. Esto se muestra en el siguiente programa:
#include <stdio.h>
#include <conio.h>
/* Programa de muestra # 10 */
/* Uso de la funcion getche() */
main()
{
char ch;
ch = getche(); /* lee un caracter del teclado */
if(ch=='E') printf("Oprimiste la tecla magica\n");
}
El programa, que ilustra la manera en la cual los caracteres pueden ser usados en enunciados if, no imprimirá nada en la pantalla excepto el caracter de la tecla oprimida, a menos de que la tecla oprimida sea la tecla “E” y tiene que ser en mayúscula, no en minúscula en cuyo caso inmediatamente a la derecha del caracter “E” se imprimirá “Oprimiste la tecla magica”. Algo nuevo que el lector debe observar en el programa anterior es la inclusión del archivo de cabecera CONIO.H (Console Input Output Header), que es de donde el compilador C extrae la función getche().
En lo que toca a las hileras (o cadenas), una hilera es un array (arreglo) de caracteres terminados con un nulo (null). En C, un nulo es especificado como la constante de caracter '\0'. El lenguaje C carece de un tipo para representar hileras (y no faltan quienes critican esto como una omisión), se requiere declarar un array de caracteres y usar funciones de hilera de alguna biblioteca para poder manipular el array. A reserva de ver posteriormente en mayor detalle el tema de los arrays, daremos una introducción a algunos principios básicos de los mismos.
Un array puede ser de una sola dimensión o de varias dimensiones. Cuando se trata de un array de una sola dimensión, debe ser una lista de variables del mismo tipo. Para crear un array, adjudicamos el tamaño del array encerrándolo entre paréntesis rectangulares después del nombre dado al array. El siguiente fragmento declara un array de caracteres de 60 elementos llamado hilera:
char hilera[60];
Para referenciar un elemento del array, ponemos el índice dentro de los paréntesis rectangulares que siguen al array. Todos los arrays en C están indexados a partir de cero. De este modo, hilera[0] vendría siendo el primer elemento del array, hilera[1] el segundo elemento, e hilera[59] el 60-avo y último elemento.
Lo más importante a recordar aquí es que en C no hay verificación de acotamientos (bounds checking). Esto significa que, si el programador no es cuidadoso, el programa se “desbordará” del final de un array. Por lo pronto, la manera más fácil de evitar esto es usar un array que será lo suficientemente grande para lo que queramos meter en él. Recuérdese que todas las hileras deben terminar con un nulo. Así, para que un array pueda ser lo suficientemente grande para almacenar “hombre”, requerirá por lo menos de siete caracteres, seis para almacenar la hilera y uno más para el terminador nulo, en la manera que se muestra:
H | o | m | b | r | e | '\0' |
Para poder leer una hilera del teclado, primero creamos un array de caracteres para el almacenamiento de la hilera, y usamos una función de biblioteca como la función gets() (que significa “get string”). Esta función toma el nombre de la hilera como argumento y lee los caracteres tocados desde el teclado no de uno en uno sino hasta que el usuario oprime la tecla de entrada [Enter]. La opresión de la tecla [Enter] no es almacenada como parte de la hilera, pero es reemplazada por el terminador nulo. El siguiente programa ilustra esto:
#include <stdio.h>
/* Programa de muestra 11 */
/* Un ejemplo de una hilera */
main()
{
char str[80];
printf("Dame tu nombre: ");
gets(str);
printf("Hola %s", str);
}
obsérvese que se ha introducido un nuevo código de formato para printf(), %s que imprime una hilera (o cadena) de caracteres.
Podemos hacer un resumen de los códigos de formato usados con printf() que hemos visto hasta este punto:
Código | Significado |
%d | Pone un entero en formato decimal |
%f | Pone un flotante en formato decimal |
%c | Pone un caracter |
%s | Pone una hilera de caracteres |
Los comandos de control de formato pueden ser puestos en cualquier parte de una hilera de control. Cuando se invoca printf() la hilera de control es escaneada. Todos los caracteres regulares son impresos tal cual. Cuando se encuentra un código de formato, printf() lo recuerda y lo usa cuando se imprima el argumento apropiado. Los códigos de formato y los argumentos son apareados de izquierda a derecha. El número de códigos de formato que hay en la hilera de control le dice a printf() cuántos argumentos subsecuentes se puede esperar. Veamos dos últimos ejemplos de esta función en acción. La primera:
printf("%s %d", "Esta hilera tiene el numero ", 2014);
imprime esto:
Esta hilera
tiene el numero 2014
|
mientras que la segunda:
printf("%d es un entero, %f es un flotante", 127, 3.1416);
imprime esto:
17 es un
entero, 3,1416 es un flotante
|
Resta decir que se tiene que tener el mismo número de argumentos que el número de códigos de formato que hay en la hilera de control; de no ser así y si el entorno de compilación no lo detecta entonces el programa ejecutable producirá en la pantalla cosas sin sentido o simplemente no se pondrá nada en la pantalla.
Parecería que hay muchas palabras reservadas en el lenguaje C. Sin embargo, en la primera versión del lenguaje C universalmente aceptada, ANSI C, sólo había 32 palabras clave reservadas:
auto | double | int | struct |
break | else | long | switch |
case | enum | register | typedef |
char | extern | return | union |
const | float | short | unsigned |
continue | for | signed | void |
default | goto | sizeof | volatile |
do | if | static | while |
Obsérvese que todas las palabras clave reservadas por ANSI C están en minúsculas. Una palabra clave reservada no puede ser usada para ningún otro propósito más que el que le corresponde. A la convención ANSI C original varios fabricantes de compiladores le han ido agregando otras palabras clave que tienen como propósito darle mayor flexibilidad a C en su control del hardware de la máquina. A modo de ejemplo, el entorno de compilación Turbo C para la familia de procesadores Intel 8088/8086 fabricado por la empresa Borland incorporaba las siguientes palabras clave:
asm | cdecl |
_cs | _ds |
_es | _export |
far | huge |
interrupt | _loadds |
near | pascal |
_regparam | _saveregs |
_seg | _ss |
A modo de ejemplo, con la palabra reservada asm (que no forma parte de ANSI C) es posible meter código propio de lenguaje ensamblador directamente dentro de un programa elaborado en C en lo que es conocido como ensamblaje en-línea (inline assembly) teniendo que tomar tan solo la precaución de especificar el uso de tal opción al efectuarse el proceso de compilación. Un ejemplo de esto vendría siendo:
mifuncion()
{
int i;
int x;
if (i>0)
asm mov x,4
else
i = 7;
}
Esta construcción es un enunciado if válido en C. Obsérvese que no se requiere de un semicolon después de la instrucción en assembler mov x,4 en virtud de que los enunciados asm son los únicos enunciados en Turbo C que dependen de que ocurra una línea nueva de texto; esto no es en conformancia con el lenguaje C, pero es la convención adoptada que fue adoptada por varios compiladores basados en el sistema operativo UNIX. Al usar Turbo C junto el ensamblador TASM (Turbo Assembler, fabricado por la misma empresa Borland), si se desea incluír no uno sino varios enunciados asm dentro de un bloque se les debe encerrar entre corchetes, por ejemplo:
asm {
pop az; pop ds
iret
}
Lo importante a resaltar es la posibilidad de que, dependiendo del fabricante del compilador, se pueda incrustar código propio de un lenguaje ensamblador dentro de código elaborado en lenguaje C.
millas = 25.0 + 60.0*ALTURA/VELOCIDAD;
Este enunciado tiene una suma, una multiplicación, y una división. ¿Cúal operación se lleva a cabo primero? ¿Es 25.0 sumado primero a 60.0, y el resultado de 65.0 es entonces multiplicado por ALTURA, y ese resultado es dividido entonces entre VELOCIDAD? ¿O acaso 60.0 es multiplicado por ALTURA, el resultado es sumado a 25.0, y la respuesta es dividida entre VELOCIDAD? Supóngase que ALTURA es igual 6.0 y que VELOCIDAD es igual a 2.0. Si trabajamos estos valores, encontraremos que la primera interpretación produce un valor de 255, mientras que la segunda interpretación produce un valor de 192.5. Sin embargo, un compilador C debe tener otra cosa en mente, ya que el resultado que produce para la evaluación es igual a 205.0.
Obviamente, y en ausencia de paréntesis, el orden en la ejecución de las operaciones puede producir una diferencia, y es por ello que C requiere reglas estrictas para determinar qué es lo que se lleva a cabo primero, y lo hace asignándole a los operadores aritméticos un orden de prioridad. La multiplicación y la división tienen una mayor precedencia que la suma y la resta, así que son llevadas a cabo primero. ¿Y qué del caso en el que dos operadores tengan la misma precedencia? Entonces son ejecutados de acuerdo al orden en el que ocurren en un enunciado. Para la mayoría de los operadores aritméticos, el orden es de izquierda a derecha (el operador de asignación = es una excepción a la regla). Por lo tanto, en el enunciado:
millas = 25.0 + 60.0*ALTURA/VELOCIDAD;
el orden en el cual se llevan a cabo las operaciones es:
60.0*ALTURA (el primer * ó / en el enunciado)
360.0/VELOCIDAD (el segundo * ó / en el enunciado)
25.0 + 180 (el primer + o - en el enunciado, dando 205.0)
¿Pero qué en caso de que queramos que se lleve a cabo primero una adición antes de que ocurra la división? En tal caso, agregamos paréntesis para fijar tal orden de operaciones:
millas = (25.0 + 60.0*ALTURA)/VELOCIDAD;
Cualquier operación puesta dentro de paréntesis es llevada a cabo primero. Dentro de los paréntesis, las reglas usuales se mantienen en pie. En el ejemplo dado con paréntesis, primero se lleva a cabo la multiplicación, y después se lleva a cabo la multiplicación. Eso dá cuenta de lo que estaba dentro de los paréntesis. Solo después el resultado es dividido entre VELOCIDAD.
La siguiente tabla proporciona las reglas de precedencia de operadores aritméticos, en orden de precedencia decreciente (de arriba abajo):
Operador | Asociatividad |
( ) | De izquierda a derecha |
- (unario) | De izquierda a derecha |
* / | De izquierda a derecha |
+ - (resta) | De izquierda a derecha |
= | De derecha a izquierda |
Cuando se tenga duda sobre el orden en el cual el compilador evaluará las expresiones, hágase lo mismo que lo que se hace en las matemáticas escolares: úsense paréntesis.