1. Introducción.

Una de las grandes cosas que han logrado los computadores es permitirnos realizar tareas complejas, principalmente en lo referente a manejo y organización de la información como en el cálculo numérico, con unas posibilidades casi ilimitadas. El diseñador de un computador no incluye todas las posibilidades de la máquina en su diseño sino que se limita a dar unas bases sobre las cuales un programa, aprovechando los recursos disponibles, se encarga de realizar una o varias tareas. El control se desplaza así en gran medida de la máquina física a la programación de la misma llamada comúnmente software. Desde que los computadores eran máquinas diseñadas en papel, las bases de la programación empezaron a darse.

El programa que reconoce un computador y que por lo tanto puede ejecutar está muy relacionado con la arquitectura interna del mismo, lo cual, en términos prácticos significa que aquello que funciona bien en una máquina no tiene sentido en otra. Los primeros computadores electrónicos se programaban cableando los componentes de la máquina así como manejando interruptores. No se diferenciaban en gran medida de los computadores mecánicos que se programaban a mano con la distribución de levas y engranajes. Más tarde vendrían las tarjetas perforadas con las cuales el programa hecho para una máquina podría ejecutarse en otra máquina similar.

Al combinar el teletipo, y mas tarde sus símiles basados en teclado y pantalla de texto, a los computadores, surgió una nueva forma de dar indicaciones a la máquina sobre qué comportamiento específico se esperaba de ella. La idea consistía ahora en introducir un texto con comandos que la máquina traducía (generalmente con ayuda de otro programa) a su lenguaje nativo (interruptores y cableado físico o lógico) y ejecutaba. Surgieron así los lenguajes de programación que conocemos comúnmente, varios de los cuales se basaron en lenguajes algorítmicos diseñados mucho antes que el primer computador electrónico entrara a funcionar.

Lenguajes como Basic, Logo, Pascal, Cobol, Fortran, Lisp, C, Modula II e incluso los ensambladores y macroensambladores, surgieron como respuesta. Aplicaciones específicas como manejadores de bases de datos y hojas de cálculo crearon sus propios lenguajes para satisfacer sus necesidades. Los lenguajes de macros, llamados también scripts, ayudan a automatizar tareas de otras aplicaciones. Incluso surgieron lenguajes como TEX y PostScript con funciones muy específicas tales como producir un papel impreso. Cada lenguaje tiene una aplicación específica y unos son más generales que otros. Aunque TEX puede realizar cálculos simples —y no tan simples— su fuerte es crear documentos escritos. Por el contrario un lenguaje como Cobol puede producir informes impresos, pero para manejar una impresora con mayor capacidad que un teletipo requiere de una sobrecarga de dispositivos y manejadores externos o un programa complicado; por el contrario, el uso de Cobol en aplicaciones matemáticas fuertes es ampliamente reconocido por su conveniencia.

Entre los así llamados lenguajes de propósito general, el Basic, el Pascal y el C son tal vez los lenguajes más difundidos. De ellos el que más se acerca a la forma como el computador finalmente resolverá sus problemas sin alejarse de la forma como el programador piensa en la respectiva solución es el C. Lenguajes como el Cobol está diseñado para que aquella persona que lea el texto del programa entienda el algoritmo y a la vez la máquina pueda traducirlo y ejecutarlos. El Basic fue diseñado para que el principiante pueda realizar tareas generales con un computador, pero no para una programación seria. Los ensambladores fueron diseñados pensando en la máquina, lo cual los aleja definitivamente del usuario común e incluso del programador de alto nivel. El Lisp se diseñó para que el mismo programa programara. El Pascal y el C se orientan a lo que el programador se alto nivel espera: facilidad en la lectura y escritura del código y utilización de los recursos de bajo nivel sin depender de la máquina.

De estos lenguajes, el C por su modularidad, la cual le permite desarrollar proyectos grandes en grupos de trabajo, así como mantener un soporte posterior al código y facilitar el enlace con otros lenguajes tales como los ensambladores, Pascal y Basic, se ha ganado un lugar de importancia en el desarrollo de aplicaciones para plataforma única, por encima del terreno ya ganado, gracias a su portabilidad, al desarrollo de aplicaciones para múltiples plataformas. De hecho los sistemas operativos Unix están escritos en su mayor parte en C y la mayor parte de las aplicaciones actuales para computadores personales son escritas en este lenguajes.

El C++ ha sido muchas veces visto como un C mejorado o como un C con soporte de programación orientada a objetos incluido —algo así como el Turbo Pascal con Objetos de Borland—. Es en realidad un lenguaje diferente pero basado en C y con muchas de sus características tales como la modularidad y la portabilidad.

Los lenguajes visuales, los grabadores-editores de macros, los CASE y otras ayudas de programación ayudan a crear aplicaciones básicas y complejas en el mundo actual de los computadores dominado por las interfaces gráficas de usuario. Pero para un desarrollo más complejo, tal como la implementación de un nuevo sistema operativo o incluso como complemento operativo de las pantallas creadas con una herramienta visual, los lenguajes basados en textos, y entre ellos el C y el C++ así como los ensambladores, no pasarán de moda.

2. Orientación.

Este manual está orientado a personas que conozcan los conceptos básicos de la programación estructurada, bien sea a través de lenguajes netamente algorítmicos como el LCG, de lenguajes estructurados por bloques como el Pascal o de lenguajes no estructurados como Basic, pero que hayan trabajado con conceptos de estructuración. Es posible que el lenguaje C esté limitado, en muchos aspectos, respecto a los lenguajes con los que se ha trabajado previamente, pero C no es un lenguaje que cubre todo lo que un computador específico puede hacer sino un lenguaje definido para todo lo que cualquier computador —incluso aquellos que no tienen un teclado y una pantalla sino, por ejemplo, un motor de combustión interna y unas palancas— puede hacer y a la vez permitir la flexibilidad de trabajar con una plataforma específica.

La limitaciones pueden ser en un principio molestas para el programador que ha pasado de una lenguaje a otro, pero en cuanto descubra las ventajas de la flexibilidad del C, las limitaciones suelen pasar a segundo plano. C no posee, por ejemplo, un tipo de datos de cadena de caracteres (string) pero si posee constantes de cadenas de caracteres que gracias al trabajo con apuntadores pueden actuar como las cadenas de otros lenguajes... pero hay que tener cuidado o se puede llevar al programa a un estado inestable. Tal vez la limitación de las cadenas pueda parecer una razón para no usar C, pero por otro lado trae la ventaja de que no hay ninguna sobrecarga para trabajar con elementos de los cuales una CPU corriente no tiene idea y permiten al programador definir las cadenas caracteres como el programador quiera (si no le gustan los apuntadores al primer carácter para indicar el comienzo de un vector o arreglo y el carácter ASCII 0 o NUL como final de la cadena tal y como C traduce las constantes de cadenas de caracteres, puede inventarse cualquier otro tipo de cadenas y definirle las funciones relacionadas).

El C es un lenguaje para el programador. El concepto detrás del lenguaje C es que el programador sabe lo que está haciendo y es así como C no tiene protecciones automáticas en tiempo de compilación y todo lo que esté sintácticamente correcto es compilable. Muchos compiladores incluyen las así llamadas advertencias (warning) las cuales procuran encontrar errores comunes del programador, pero que, por ser código sintácticamente correcto, es compilable. Es posible que lo que el compilador cree un error es código puesto intencionalmente por el programador para lograr un efecto obviando escritura innecesaria. Pero esta misma flexibilidad puede convertir al C en un lenguaje inestable si el programador no sabe lo que está haciendo. Este es el temor que muchos expresan para enseñar C como el primer lenguaje de programación ya que la inestabilidad puede ocasionar la caída del sistema, la pérdida de información valiosa u otros inconvenientes. Pero cierto cuidado puede disminuir los riesgos. Es mi opinión personal que no hay que temerle al lenguaje C.

El lenguaje C++ es un lenguaje orientado a objetos basado en C. Tiene más restricciones que el lenguaje C, lo cual lo puede convertir en un lenguaje más seguro sin necesidad de entrar en forma completa al concepto de la programación orientada a objetos. Muchos de los paquetes comerciales de desarrollo, compilación y enlace para lenguaje C que se consiguen en la actualidad para PC: Turbo C++, Microsoft C/C++, Borland C++, MS Visual C++, etc. como sus mismos nombres lo indican, soportan C++, por lo que se podría pensar en enseñar C con un compilador de C++. Aunque son lenguajes distintos, el C y el C++ conservan estructuras comunes que permiten escritura portable entre ambos lenguajes. El compilador de C de Unix: cc, no compila C++ pero esto no es limitante para escribir código C seguro y enlazarlo desde cualquier terminal Unix vía telnet. Y Unix tiene en su propia configuración de seguridad interna que evita que un usuario inexperto coloque al sistema completo en un estado inestable.

En los últimos años se ha puesto de moda la programación orientada a objetos. C++ no es un lenguaje orientado a objetos puro como Smalltalk. Las estructuras de C++ integrantes en sí del lenguaje, tales como funciones o tipos de datos, no son objetos y no existe un objeto puro. Esto permite escribir C en C++, permite usar los conceptos de programación estructurada que se pudieran haber aprendido con C, LCG o Pascal o incluso Basic. Pero la implementación de objetos en C++ es una implementación completa a diferencia de Visual Basic de Turbo Pascal con Objetos o de MS Quick Pascal. Todos los conceptos de la programación orientada a objetos tales como abstracción de datos, herencia y polimorfismo, así como sobrecarga de operadores y funciones y control de dominio se encuentran en C++. Las clases en C++ pueden verse como un complemento a la estructura general del lenguaje estructurado o como la base de la programación. Como éste manual no está orientado a la programación orientada a objetos, se trabajará sobre la primera aproximación.

2.1 Precisiones de lenguaje.

En este documento se resaltan distintos tipos de caligrafía, como ya ha podido notarse. La base de texto está escrita con caligrafía romana mientras que palabras específicas tales como nombres de lenguajes y marcas están escritos en caligrafía suiza o gótica (conocida también como ária o helvética). Las expresiones que hacen parte del código tal y como es tecleado a la máquina o impreso en la pantalla se escriben en texto monoespacioado. Para indicar palabras en escritura literal que reemplazan a otras palabras se usará el monoespaciado oblicuo. La letra itálica o romana oblicua será utilizada para dar el equivalente en inglés de una palabra en español además de otros usos.

Este documento procura escribirse completamente en español, lo cual ayuda al manejo de la terminología técnica en nuestro idioma; sin embargo, concientes de que gran parte de esta terminología se maneja en idioma inglés, los equivalentes en inglés son dados. A continuación hay una lista de términos en español con sus equivalentes en inglés.

advertencia warning

biblioteca library (la palabra española librería corresponde a una tienda de libros)

cadena string

desplazamiento offset

enlace linking

escrito script

herramienta hardware

letrero label

programación software (sin embargo la palabra software se usará aún en texto en español)

casting

La mayor parte de los programas dedicados a la interpretación, compilación y/o enlace en programación están documentados en inglés y rara vez son traducidos a idiomas como el español, por lo tanto puede ser corriente el uso de vocablos en inglés, además de que el uso sistemático de ciertos vocablos ingleses en español permiten expresar en ocaciones las ideas de una forma más clara y precisa que su traducción al español. Tal es el caso de palabras como hardware y software. En tales ocaciones, se procurará dar el equivalente en inglés siempre que se use el término correspondiente en español.

3. Tipos de lenguajes de programación.

Los lenguajes de programación pueden dividirse de varias formas, bien sea por su relación con la máquina, con el programador o con las aplicaciones, o bien sea por el paradigma de programación. Por la cercanía con la máquina y/o la lejanía del lenguaje humano natural se dividen los lenguajes entre lenguajes de bajo nivel, alto nivel y nivel medio. Por el paradigma de programación se dividen en lenguajes secuenciales o lineales, lenguajes estructurados, lenguajes funcionales o lenguajes orientados a objetos. Muchas veces un lenguaje puede estar dividido en varias categorías o las opiniones pueden estar divididas en su clasificación. C y C++ son lenguajes con los cuales las opiniones se encuentran.

3.1 Lenguajes de alto y bajo nivel.

Un computador entiende exclusivamente una lógica binaria (unos y ceros) la cual suele estar agrupada en paquetes. Existen así los dígitos binarios (bits) para designar la información minimal que un computador entiende y maneja. Por su limitación los bits se agrupan en grupos siendo la agrupación de 8 bits una de las más usuales y la cual ha recibido el nombre de byte y se considera la base de las medidas de memoria en el mundo actual junto con sus múltiplos kilobyte (1024 bytes), megabyte (1024 kilobytes) y gigabyte (1024 megabytes) —tal vez en algún futuro no muy lejano empiece a hablarse de terabytes— aunque los bits siguen utilizándose como medida de la capacidad lógica de un computador (se habla así de procesadores de 16 bits, 32 bits, 64 bits, gráficas de 24 bits o conjuntos de instrucciones de 5 o 6 bits en procesadores RISC). Cada computador tiene su propio conjunto de instrucciones que se basan en paquetes de bits de longitud fija (RISC) o longitud variable (CISC). Estos conjuntos de instrucciones son lo único que el computador entiende y cada instrucción es como una palabra cuya posición en una frase determina su significado. Junto con las instrucciones viene información que tiene el mismo formato que una instrucción: es un conjunto de unos y ceros. Pueden existir cierto tipo de puntuación pero por lo general el lenguaje que entiende una máquina empieza siempre con una instrucción la cual determina si la siguiente palabra —el siguiente byte, generalmente— es una instrucción o información relacionada con si misma o continuación de la instrucción. Hay algunas similitudes con el lenguaje y la lógica del lenguaje humano pero en una presentación tan diferente que sólo un experto en lenguajes —y me refiero aquí a un filólogo, un lingüista o un experto en semántica, no a un programador— sería capaz de entenderlas y usarlas para comunicarse.

He ahí la importancia de crear lenguajes que acerquen al programador con la máquina. Cuando uno de estos lenguajes se acerca al código nativo de la maquina está generalmente lejos del lenguaje nativo del programador u otro lenguaje comprensible con la naturaleza humana del mismo. Si el lenguaje busca ser comprensible por el programador o cualquier persona que lea el código, suele apartarse de lo que la máquina entiende. Esto ha dado lugar a la clasificación de los lenguajes por niveles.

El lenguaje de máquina y los ensambladores, que son formulaciones mnemotécnicas de los lenguajes de máquina se han llamado lenguajes de bajo nivel y suelen ser muy específicos del computador para el cual se diseñaron. El Basic y el Pascal son lenguajes fáciles de entender para el usuario, sobre todo para el usuario angloparlante o que entienda inglés, y como tal se denominan lenguajes de alto nivel, al alejarse de la máquina pueden aumentar la portabilidad: no importa como el computador coloque una cadena en la pantalla PRINT "Hola" y WriteLn('Hola'); escribirán Hola en la pantalla, siempre y cuando el intérprete o el compilador hayan sido diseñados para ese computador específico. Ciertos lenguajes procuran simular mejor el lenguaje natural humano, como el Modula II, y podrían llamarse de altísimo nivel. El Forth es un punto medio entre los de bajo nivel y los de alto nivel, recibe el nombre de lenguaje de nivel medio.

Sin embargo la clasificación adolece de fallas. ¿Cómo llamar a ciertos lenguajes experimentales que probando un paradigma ni se parece a las instrucciones de la máquina ni lo entiende nadie por fuera del grupo investigador? Estos suelen denominarse de alto nivel, porque una vez entendido el paradigma, éste determina la naturaleza del lenguaje, no la máquina, y bajo ese punto de vista el lenguaje puede ser perfectamente claro para una persona (o no menos claro que Basic o Pascal). La otra pregunta es: ¿Un lenguaje de qué nivel es el lenguaje C? En muchas estructuras podría considerarse lo suficientemente cercano a la máquina para ser de nivel medio e incluso hay quienes afirman que es un lenguaje de bajo nivel con presentación de lenguaje de alto nivel. Por otro lado, si bien no utiliza tantas palabras como Basic o Pascal (usa { y } en lugar de Begin y End además de || y && en lugar de Or o And) su forma externa tiende a ser de alto nivel. Pero a pesar de que estructuras como a+=b; tienden más a una escritura de máquina que A:=A+B; ésta última más fácil de entender por una persona, el lenguaje C resulta más portable que el Basic y el Pascal. Tal vez menos portable que el Modula II pero éste último es más limitado y no existen tantos compiladores para distintas plataformas como con el lenguaje C.

El lenguaje C++ se prestaría posiblemente menos a discusión, ya que el uso del paradigma de la programación orientada a objetos, tiende a definirlo como lenguaje de alto nivel. Pero su código puede hacerse aún más ilegible que con el lenguaje C.

3.2 Lenguaje de máquina.

Como se explicara previamente el lenguaje de máquina se compone de paquetes de bits, cuyo tamaño puede ser fijo o variable dependiendo de la máquina. En los computadores basados en tecnología RISC, cada instrucción consta de 5 bits o 6 bits u otra cantidad predeterminada de bits. El número de instrucciones se limita a 32 o 64 y por ser de longitud fija es más rápido su proceso. Como los paquetes minimales con los que un computador maneja su memoria suelen ser los byte (8 bits), suele haber un desperdicio fijo (3 o 2 bits) de memoria por cada instrucción almacenada. En un procesador CISC como los universalmente conocidos en el mundo de los PC: Intel 80x86; existen instrucciones de 8, 16 y 24 bits. El número de instrucciones es mayor que en los sistemas RISC y pueden así producir códigos en lenguaje nativo de máquina más pequeños. La longitud variable hace que el proceso de las instrucciones sea más lento.

Sin importar si es RISC o CISC, si una instrucción requiere información es seguida de la información (si se trata de un número) o una referencia a la misma (que en últimas es un número indicando una dirección). Un número determinado de bytes sigue por lo tanto a cada instrucción, dependiendo de ésta. Por lo tanto un programa se compone de un conjunto de instrucciones y números, como mínimo. Puede contener también información adicional que se sitúa en otra parte de la memoria o dentro del mismo código separado por instrucciones de salto. Todo esto se encuentra almacenado en forma de unos y ceros que al igual que los fonemas de nuestro lenguaje, requieren de un contexto para decir algo coherente. Para programar directamente en máquina, se requiere conocer muy bien el procesador con el que se está trabajando y utilizar unos y ceros (generalmente conjuntos de interruptores) o números hexadecimales, que no son más que una presentación un poco más intuitiva de los bytes y mejor presentable en, digamos, una pantalla.

El lenguaje de máquina es poco portable entre plataformas. Puede ser portable entre miembros de una misma familia, sobre todo de código escrito para un miembro pasarse al código de un miembro más joven o entre máquinas que se aleguen compatibles, pero no es portable entre arquitecturas distintas.

Ciertos sistemas operativos pueden, sin embargo, traducir al vuelo código de máquina de una plataforma a otra y de una arquitectura a otra. Sin embargo el funcionamiento no iguala a usar código de máquina nativo.

Como el código de máquina nativo es lo único que un computador entiende, todos los demás lenguajes tienen que traducir su código en lenguaje de máquina para la máquina en la cual pretenden trabajar. Esto se hace bien interpretando instrucción a instrucción o bien compilado, es decir traduciendo de antemano, para convertir el código fuente en código de máquina y ejecutar directamente el código de máquina.

3.3 Lenguaje ensamblador.

El lenguaje ensamblador (Assembler) es un conjunto de mnemotécnicos para programar en lenguaje de máquina. En ensamblador, las instrucciones y códigos de máquina son escritos como comandos de letras seguido, si es el caso, de valores o variables. Las variables internas del procesador reciben el nombre de registros, todas las demás variables corresponden a direcciones de memoria. El número de instrucciones así como de registros, su denominación y su funcionamiento dependen del procesador. La sintaxis corriente de una instrucción en ensamblador es el nombre de la instrucción seguido de espacio y luego los parámetros separados por comas, estos parámetros pueden ser la denominación de los registros o números o posiciones de memoria (números o registros rodeados de corchetes o paréntesis cuadrados). No todas la ordenes posibles son válidas, por ejemplo en los Intel 80x86 se puede sumar un entero a ciertos registros pero no a otros, obligando, si es el caso, a guardar un registro de uso común como AX, asignarle el valor del registro como CS que no se deja sumar, sumarle el entero a AX, asignarle AX a CS y recuperar el valor original de AX.

push ax

mov ax,cs

add ax,10

mov cs,ax

pop ax

Para simplificar la escritura de programas complejos se crearon los lenguajes macroensambladores que permiten la definición de nombres para indicar secciones de código, subrutinas y variables globales. Esto permite realizar una especie de programación estructurada en un lenguaje que nada tiene de estructurado. (Si alguien que paso de Basic a Pascal —o de GW-Basic a Quick Basic— lo primero que le advierten es que no use el GOTO, encontrará que la instrucción más importante del ensamblador es jmp.)

El código escrito en ensamblador puro suele ser directamente convertido a lenguaje de máquina y almacenado como tal. En macroensamblador, el código suele escribirse y almacenarse en texto y ser convertido en instrucciones de máquina en un proceso llamado ensamble, muy similar a la compilación. El ensamble puede generar un programa ejecutable o un módulo de código objeto. Código objeto es un código que combina instrucciones de máquina con indicadores de enlaces. Los enlaces reales son realizados en un proceso llamado precisamente enlace (linking), que puede combinar módulos independientes, módulos guardados en archivos grandes de módulos llamadas bibliotecas (libraries) y convertir el conjunto de módulos en un ejecutable.

Como el lenguaje ensamblador es una traducción mnemotécnica del lenguaje de máquina tiene los problemas de portabilidad del lenguaje de máquina con la desventaja de que al no ser ejecutable al vuelo, no puede ser traducido al vuelo, es decir que un programa hecho en ensamblador para correr en un PC Intel 8086 con MS-DOS 2.0 no correrá en un Apple Macintosh, así este último tenga un programa que le permita correr aplicaciones Intel/DOS, salvo que se corra un ensamblador para Intel/DOS, se ensamble y se corra.

3.4 Lenguajes interpretados.

Cuando se busca portabilidad en los lenguajes hay que alejarse de la máquina y es así como surgen los lenguajes de alto nivel. El programador no tiene que estar familiarizado con la arquitectura interna del programa, aunque conocerla no hace daño. Pero si el lenguaje no sigue fielmente a la arquitectura del computador, se necesita que exista un programa que traduzca del lenguaje de alto o medio nivel al lenguaje de máquina.

Una forma de lograr esta traducción es interpretando, se usa así un programa que lee, instrucción por instrucción, el programa y convierte cada instrucción en un conjunto de instrucciones de máquina que ejecuta inmediatamente.

De las grandes ventajas que tiene la interpretación sobre la traducción previa (compilación) es que el intérprete puede adaptarse a las circunstancias y actuar acorde a ellas, eso permite que los lenguajes de interpretación tengan estructuras menos rígidas que los lenguajes destinados a la compilación. Por otro lado, el programador puede evaluar lo que está haciendo, bien sea dando órdenes directamente, o corriendo un programa en desarrollo hasta que encuentre un error, bien en la lógica (el programa no se comporta como debería) o en la sintaxis (el intérprete encuentra un error y avisa), lo cual le permite al programador corregir el programa y ejecutarlo nuevamente, o incluso, continuar la ejecución desde donde iba.

Entre las desventajas se encuentra que requieren siempre que el intérprete esté presente y el proceso de interpretación suele influir negativamente en la velocidad de ejecución. Muchos lenguajes de interpretación son compilables, pero en ocasiones requieren revisar la escritura para permitir que ciertas estructuras del lenguaje y construcciones se puedan compilar. Un intérprete puede correr varias rutinas almacenadas en memoria o en un archivo en un mismo tiempo y cada una funcionando como un programa independiente o como una subrutina de otro programa, el proceso de compilación implicaría tener que escoger cual es la rutina principal para compilarla junto con todas las rutinas de apoyo. Esto no tiene sentido en varios lenguajes de interpretación.

Lenguajes de interpretación comunes están el Basic, el Lisp, el Smalltalk, el Logo y muchos otros. Los lenguajes de macros destinados a automatizar tareas de otras aplicaciones, así como los archivos de procesamiento por lotes, son lenguajes de interpretación.

Es común en los lenguajes de interpretación que el almacenamiento en memoria y disco se realice en un así llamado metalenguaje, que es una forma de almacenamiento binario del texto que el programador introduce. Esto facilita la interpretación ya que las instrucciones son almacenadas en conjuntos de uno o dos bytes que el intérprete reconoce más rápido que cadenas de letras de hasta ocho o más bytes, sin embargo, la mayor parte de los intérpretes actuales usan el almacenamiento, principalmente almacenamiento en disco, en forma de texto por ser más portable entre plataformas, por ser editable por fuera del intérprete y porque las velocidades de interpretación de textos han sido incrementadas tras años de investigación y la utilización de equipos más rápidos y de mayor capacidad.

3.5 Lenguajes de compilación.

Un lenguaje de compilación es un lenguaje que está destinado a ser traducido a lenguaje de máquina y a ejecutable antes de ser ejecutado. Para facilitar el proceso de compilación, suelen ser lenguajes rígidos en su estructura. El código, una vez compilado, corresponde a lenguaje de máquina y es ejecutado en una sola pasada sin necesidad de las herramientas con las que se creo el programa, es así como el ejecutable no requiere ni del editor, ni del compilador ni del enlazador. Esto permite que el código corra más rápido y con mayor disponibilidad de recursos que un programa interpretado.

Otra ventaja que tienen los lenguajes destinados a compilar es que facilitan la definición de tipos de datos complejos no soportados por el compilador en forma directa ya que el compilador puede crear el soporte para los datos en el mismo código compilado. Esta es una de las grandes ventajas de los lenguajes de compilación sobre los interpretados y una de las razones por las cuales la mayoría de los lenguajes de compilación no tienen intérpretes mientras que muchos lenguajes de interpretación sí poseen compiladores.

La otra gran ventaja de los lenguajes compilables es la modularidad que permite crear distintos módulos y compilarlos por aparte y luego en un proceso llamado ensamble unirlos para crear una aplicación grande y compleja. Esto permite realizar el desarrollo de aplicaciones grandes de una forma más cómoda que con un intérprete, distinguiendo las distintas etapas y trabajándolas por separado y permitiendo que los módulos creados para un desarrollo sean utilizables en otro. Al no existir un intérprete los módulos pueden ser numerosos y grandes e incluirse todos al tiempo en una aplicación. Los módulos pueden ser incluso desarrollados en distintos lenguajes, aprovechando las características de cada uno.

Entre los lenguajes para ser compilados se encuentran el Pascal, el C y C++, el Fortran y muchos otros. Con estos lenguajes se pueden hacer desarrollos grandes de programas autocontenidos. El enlace de compiladores con módulos en ensamblador permiten hacer desarrollos más específicos con la máquina y que aprovechan mejor las capacidades de la misma.

El código original escrito por el programador en un lenguaje de compilación es llamado código fuente y es generalmente almacenado en texto, lo que le permite ser editado en cualquier parte y ser portable entre plataformas, siempre y cuando mantengan el estándar ASCII de caracteres imprimibles. El compilador toma el código fuente y lo convierte en código objeto, es decir en lenguaje de máquina en el cual los enlaces entre las distintas rutinas no está hecho pero es dejado como referencias. Varios módulos en código objeto pueden ser agrupados en bibliotecas (libraries). El proceso final en el desarrollo de programas suele ser enlazar los distintos módulos, convirtiendo las referencias a los enlaces en enlaces reales y colocando al principio del programa un enlace con la rutina principal.

3.6 Lenguajes lineales.

Un programa se conforma de un conjunto secuencial de instrucciones. Esa es la forma como funciona el lenguaje de máquina y como funcionan la mayor parte de los lenguajes. Un lenguaje secuencial o lineal se basa en dar un conjunto de instrucciones en un orden determinado que indica en que orden las instrucciones son ejecutadas. Generalmente poseen controles de flujo los cuales pueden ser condicionados o no. Estos controles de flujo permiten saltar a distintas partes del código de instrucciones bien para repetir una serie de comandos o bien para evitar la ejecución de otros comandos.

Existen dos tipos de saltos, los saltos simples y los saltos con retorno. En los primeros la instrucción de salto simplemente indica al computador que continúe ejecutando las ordenes a partir del destino del salto, si se requiere regresar se necesita un salto de vuelta. En el segundo tipo de salto, el computador guarda la posición desde donde el salto es ordenado para regresar a la orden siguiente al salto una vez encuentre la orden de regreso.

Si bien se podría pensar en un lenguaje sin controles de flujo, el hecho de que la mayor parte de los algoritmos medianamente complejos requieran de condiciones y repetición de rutinas hacen que estos elementos sean necesarios en casi cualquier lenguaje.

Un lenguaje secuencial es ejecutado hasta que se agote la secuencia de instrucciones o encuentre un comando que le indique suspender la ejecución.

Ejemplos de lenguajes secuenciales son el lenguaje de máquina (y por lo tanto el ensamblador) y el Basic.

3.7 Lenguajes funcionales.

Un lenguaje funcional se basa en el concepto de que un programa es una función cuyos argumentos son funciones.

3.8 Lenguajes estructurados.

3.9 Lenguajes orientados a objetos.

Un lenguaje orientado a objetos desplaza el enfoque de la programación de las rutinas a los objetos lógicos que se emplean. En la programación estructurada corriente o en la programación secuencial, el trabajo del programador consiste en crear rutinas y llamar procedimientos siguiendo una secuencia de código. En la programación orientada a objetos se define una serie de objetos lógicos a los cuales se les define propiedades.

4. Elementos básicos de un programa en C.

4.1 Un primer programa en C.

El siguiente es un programa sencillo en C que expresará algunos puntos de la sintaxis del lenguaje C. Es una adaptación del programa “Hola Mundo!” con el que en muchas ocaciones se intruduce a un nuevo lenguaje.

/* primer programa en C */

/* Adaptación del tradicional programa "Hola Mundo" */

#define OKAY 0

main()

{

char*cadena="Hola Mundo!";

int i;

for(i=0;i<10;i++)

printf("%11s\n", cadena+i);

return OKAY;

}

Lo primero es indicar que el código entre la marca /* y la marca */ corresponde a comentarios y que como tal puede contener cualquier sintaxis ya que no será compilado.

El lenguaje C se basa en las así llamadas funciones y un programa ejecutable contará siempre con una función de entrada o rutina principal generalmente llamada main(). En C no se distinguen las funciones de los procedimientos o subrutinas. El programador es igualmente autónomo en determinar si usa o no el valor devuelto por una fución. Un ejemplo de función usada en el programa anterior es printf() la cual imprime una serie de caracteres en la pantalla y devuelve el número de caracteres impresos, lo cual podría llevar a detecciones de error, si el programador lo quisiera, pero que usualmente es dejado sin confirmar.

En lenguaje C existe la declaración de una función y su cuerpo. En el ejemplo no hay ninguna función declarada y se encuentra el cuerpo de la función main(), la cual consiste en los siguientes elementos: el tipo que devuelve la función tipo, el nómbre de la función funcion, paréntesis de apertura, lista de parámetros lista, paréntesis de cierre, declaración de los parámetros si no fueron declarados en la lista, llave de apertura, declaración de variables, procedimientos y llave de cierre así:

tipo funcion(lista)argumentos{variables;procedimientos;}

donde espacios y saltos de línea pueden ser insertados en cualquier sitio siempre y cuando no dañen un elemento atómico (no pueden haber espacios en medio de una palabra, por ejemplo). La declaración de una funcion es necesaria si se desea llamar la función desde un lugar previo al cuerpo, o desde un archivo que no contenga al cuerpo de la función y si la función puede representar problemas tales como: devuelve un tipo diferente a entero (int), se desea asegurar que los tipos de datos de los argumentos sean pasados apropiadamente. En el caso del ejemplo no es necesario declarar previamente la función printf() ya que el valor devuelto por la función no nos interesa y si nos llegara a interesar es de tipo int. Los argumentos tampoco ofrecen problemas por razones que se verán más adelante. La declaración de funciones consiste en el tipo (si es int puede omitirse), el nombre de la función, el paréntesis de apertura y el de cierre y un punto y coma (;). Entre los paréntesis puede ir la lista de argumentos la cual puede incluír o no declaraciones de los argumentos. Posibles decaraciones para printf() son las siguientes:

printf(); /*igual a no declarar*/

int printf(); /*igual a la enterior pero mas clara*/

printf(plantilla,argumento);/*indica que tiene dos argumentos*/

printf(char*plantilla,char*argumento);/*declara los argumentos*/

int printf(const char*,char*);/*declara tipos de los argumentos*/

int printf(const char*plantilla,...);/*definicion ANSI de printf(),

la inclusión de la elipsis (puntos suspensivos) se discutirá

más adelante. */

La declaración de una variable es siempre necesaria y puede estar localizada fuera de una función, lo que la convierte en variable global, o dentro de una función antes de cualquier procedimiento, lo que la convierte en una variable local (existen variantes que se discutirán más adelante). La declaración de una variable incluye el tipo (aún si es int), el nombre de la variable y un punto y coma (;). En la declaración de una variable, ésta puede ser inicializada colocando el operador = y un valor de inicialización entre el nombre de la variable y el punto y coma. El valor de inicialización puede ser una constante, una lista (al inicializar estructuras o vectores) o, incluso, una función o una operación, esto último es poco conveniente en variables globales.

Toda declaración, bien sea de variables o de funciones, y todo procedimiento debe estar terminado en un punto y coma (;).

La directiva #define, así como el símbolo OKAY no corresponden realmente al lenguaje C propiamente dicho pero hacen parte del así llamado preprocesador que es una parte integral del lenguaje C que puede ser usado con otros archivos de texto (scripts). El preprocesador de C se discute a continuación (ver 4.2).

El resultado del programa anterior una vez compilado y enlazado con la biblioteca ANSI estándar de entrada y salida (lugar donde ya está compilado printf() para el respectivo ambiente), es la impresión de:

Hola Mundo!

ola Mundo!

la Mundo!

a Mundo!

Mundo!

Mundo!

undo!

ndo!

do!

o!

en el dispositivo estandar de salida (la pantalla del computador o el terminal del shell, si no ha sido redireccionado a algún archivo). ¿Por qué no intentar escribir un programa en Pascal o Basic que produzca esta misma salida?

4.2 El preprocesador de C.

Un modulo en código fuente de C suele tener incluido dentro de él mismo dos lenguajes. Uno de ellos es el lenguaje C en sí que ha de convertirse en código ejecutable. El otro es el llamado lenguaje del preprocesador el cual contiene instrucciones que maneja el texto del programa y lo convierte como un paso previo a la compilación. En la práctica, muchos compiladores actuales preprocesan el código al tiempo que lo compilan, pero el preprocesador de C es un lenguaje independiente que puede ser usado con otros lenguajes basados en archivos de texto, bien sean lenguajes de programación como Pascal, o lenguajes de impresión como TEX.

El preprocesador reconoce los siguientes elementos de sintaxis: comentarios, comandos del preprocesador, cadenas de caracteres.

Los comentarios están enmarcados por un /* inicial y un */ final, o en el caso de un preprocesador con soporte para C++, un comentario puede comenzar con la secuencia // y terminar con un fin de línea. Todo lo que está escrito entre el comienzo de un comentario y el fin del mismo es omitido del texto preprocesado, incluidos otros inicios de comentarios. Los comentarios en C no pueden ir anidados.

Las constantes de tipo cadena, se basan en las constantes de tipo cadena de C y las constantes de tipo carácter en C. Las primeras empiezan y terminan en comilla doble (") y deben ir completas en una misma línea, si en una línea no coinciden las parejas de comillas el preprocesador lo interpreta como un error. Las constantes de carácter en C están enmarcadas por comillas sencillas (') y desde el punto de vista del preprocesador no hay distinción con las constantes de tipo cadena, pero no las mezcla: si una constante inicia con comilla doble, el preprocesador pasa todo el texto, en forma literal y sin preprocesar, hasta la siguiente comilla doble. Si inicia con comilla sencilla, pasa el texto, en forma literal y sin preprocesar, hasta la siguiente comilla sencilla. Si encuentra un fin de línea antes del cierre de la constante reporta un error. El texto entre el inicio y el fin de una constante de cadena es pasado literalmente, es decir: sin preprocesar. (En C, una comilla doble en medio de una constante de cadena debe ser precedida de un carácter de barra inversa \, igualmente una constante carácter indicando el carácter de comilla simple se denota '\'', las comillas externas denotando inicio y cierre de la constante y la comilla interna precedida de la barra inversa como el carácter en sí. El preprocesador tiene en cuenta esto y salta cualquier carácter precedido por la barra inversa dejando la combinación literal.)

Los comandos del preprocesador deben ir al comienzo de una línea (pueden ser precedidos de espacios y tabuladores) y están precedidos por un carácter de número o libras[1] (#) (carácter 23 hexadecimal). Cualquier número de espacios o tabuladores es válido antes del carácter de número o libras o entre éste y el comando. El comando termina al final de la línea.

4.2.1 Comandos del preprocesador.

#if seguido de una expresión condicional constante determina si el texto entre la línea del #if y la del siguiente #else, #elif o #endif es preprocesado o es omitido. Si el resultado de la evaluación es 0 o falso, el texto respectivo es omitido, si es no 0 (generalmente 1) o verdadero el resultado el texto es preprocesado. Todo #if debe tener su respectivo #endif.

#else Si el resultado de la evaluación del #if anterior y de todos los #elif entre el #if y el #else, son falsas, se preprocesa cualquier texto entre el #else y el #endif. Todo #else debe estar definido entre un #if, un #ifdef o un #ifndef y su respectivo #endif.

#endif Indica el final de un bloque condicional y continúa preprocesando el texto a partir de ahí.

#elif Dentro de un bloque condicional, evalúa una expresión si el resultado del anterior #if o #elif fue falso (0), si el resultado de la expresión es verdadero preprocesa el código hasta el siguiente #elif, #else o hasta el #endif, para omitir después todo lo que se encuentre de allí hasta el #endif respectivo. De lo contrario omite el texto entre él y la siguiente cláusula del bloque condicional.

#define Define una palabra. Su sintaxis es #define palabra definición, donde palabra es cualquier expresión de números, letras o caracteres de subrayado que no comience en número y definición es cualquier cosa desde que terminan los espacios y/o tabuladores después de palabra y el fin de línea. Si la definición quiere extenderse a través de varias líneas cada fin de línea debe ser precedido por una barra inversa. A partir de ahí, cada vez que el preprocesador encuentre la palabra palabra en el texto, la reemplaza por definición. Una palabra puede ser definida sin definición, en tal caso se reemplaza por nada; esto último es usado para condicionales del preprocesador o para compatibilidad entre versiones de C.

#undef Elimina la definición de una palabra. La sintaxis es #undef palabra donde palabra es una palabra definida previamente por #define o externamente por el compilador (en preprocesamiento directo) o la línea de comando.

#ifdef Preprocesa el texto entre él y el siguiente #else o #endif si la palabra que le sigue está definida, independiente de la definición o si ésta es nula. En caso contrario omite el texto respectivo.

#ifndef Preprocesa el texto entre él y el siguiente #else o #endif si la palabra que le sigue no está definida. En caso contrario omite el texto. (Si está definida sin definición sí está definida y omite el texto.)

#error Si el preprocesador encuentra esta cláusula mientras está preprocesando, detiene el preproceso y la compilación si es el caso, reportando un error fatal (error que impide continuar) y a continuación el mensaje que sigue a la cláusula. No tiene sentido colocar un #error por fuera de un bloque condicional.

#include Cuando el preprocesador encuentra esta directiva, abre el archivo indicado a continuación y lo preprocesa, para continuar con el preprocesamiento del archivo inicial una vez alcanzado el fin de archivo. Si el nombre del archivo se encuentra entre comillas dobles, el preprocesador busca el archivo en el directorio actual, si se encuentra entre paréntesis angulares, el preprocesador lo busca en un directorio preseleccionado para la búsqueda (generalmente el directorio de los archivos de encabezado de las bibliotecas estándar de C o aquel que se le indique en la línea de comando).

#pragma Las directivas pragma son comandos que no están orientados al preprocesador sino al compilador y son específicas de cada compilador. Un comando pragma que un compilador no reconozca no debe ser tomado como error. Los comandos pragma son comandos que van a continuación de la cláusula #pragma y suelen cambiar parámetros del compilador, desactivar o activar advertencias, etc.

4.2.2 Expresiones condicionales del preprocesador.

Dentro de una cláusula condicional del preprocesador es válida cualquier expresión constante, es decir que se componga de elementos que estén definidos en tiempo de preproceso. Las variables de C no son válidas dentro de las expresiones condicionales del preprocesador.

La función defined() es una expresión que devuelve verdadero si la palabra entre los paréntesis está definida y falso si no. Con #if defined(palabra) puede reemplazarse #ifdef palabra y con #if !defined(palabra) se reemplaza #ifndef palabra. La ventaja de defined() se encuentran cuando se usan operadores lógicos.

Los comparadores de C tales como igual (==), diferente (!=), menor que (<), mayor que (>), menor o igual (<=) y mayor o igual (>=) son operadores válidos para usar en expresiones del preprocesador (en #if o #elif)

Los operadores lógicos de C, tales como no (!), y (&&) y o (||) son válidos en expresiones el preprocesador.

4.2.3 Reemplazo de expresiones.

Con #define se puede cambiar una palabra por una expresión cualquiera que puede consistir en otra palabra, en una fórmula, un signo, un número, un comando o un conjunto de comandos. Esto permite, en el ejemplo de un programa en C, escribir en el código fuente un mnemotécnico en lugar de un valor, por ejemplo, o una expresión que puede cambiar entre distintas compilaciones, para permitir que el cambio se realice en una sola parte y no haya necesidad de revisar todo el código para cambiar la misma expresión en varias partes.

#define FALSE 0

#define PI 3.1415927

#define MAX_LINES 25

#define DEMORA for(i=0;i<32000;i++);

Con ayuda de los condicionales se puede definir una o varias palabras para que evaluando condiciones en tiempo de preproceso se compile en forma diferente en distintas circunstancias.

Si la definición de una palabra contiene otra palabra definida, esta se expande una vez expandida la primera palabra en su respectiva definición.

4.2.4 Macros.

Así como una palabra puede ser expandida en una expresión fija, pueden definirse también palabras que tomen argumentos y se expandan de acuerdo a estos. la sintaxis es la siguiente:

#define macro(arg1[,arg2[,...]]) definición

Es importante no dejar espacio entre la palabra macro y el primer paréntesis.

Cuando el preprocesador expande macro toma las expresiones entre los paréntesis y reemplaza todos los lugares en definición donde aparezcan arg1, arg2, etc. por las respectivas expresiones.

#define MIN(a,b) (a

#define DEMORA(n) for(n=1;n<=32000;n++)

#define NUEVO(tipo,variable) (variable=(tipo*)malloc(sizeof(tipo)))

4.2.5 Ejemplo.

A continuación siguen dos archivos simples de texto que pueden ser preprocesados por el preprocesador de C. Es importante observar que fuera de las constantes de cadena y de los comentarios tipo C, sólo son válidos los caracteres imprimibles ASCII, los espacios, tabuladores y saltos de línea.

Sea el siguiente archivo llamado “incluir.txt

/* Este es un archivo a incluír dentro de otro archivo */

Esta es una linea que debe ser preprocesada

#ifndef __INCLUIR_TXT

#define __INCLUIR_TXT

#define linea instruccion

Aqui puede existir codigo de algun tipo.

En C una instruccion puede ocupar una linea o mas, pero debe terminar siempre en un punto y coma (;).

Para C no hay diferencia entre tabuladores, espacios ni saltos de linea.

dentro de un archivo de inclucion pueden incluirse otros archivos asi:

#include "incluir.txt"

Todos los espacios a principio de la linea son eliminados.

"Cualquier texto incluído entre comillas es preprecesado tal cual."

"Así existan"

"palabras definidas como linea "

linea

#define macro1 (x) Reemplazo uno de x.

#define macro2(x) Reemplazo dos de x.

#endif

Este es el final del archivo __FILE__

Y sea el siguiente archivo "base.txt"

/* Este es un archivo a preprocesar */

#include "incluir.txt"

#if !defined (aborte )

el procesamiento continua

#else

#error El procesamiento se aborta ahora!

#endif

__DATE__

#ifdef linea

linea esta definida

# undef linea

#endif

/* Este es un comentario

/* Este es un comentario anidado */

este es el final de un comentario

*/

Toda linea que no este condicionada es procesada.

Se puede usar un macro asi:

macro1(macro2("Hola"))

macro1(ejecute)

macro2(2 + 3 + 5)

macro2(macro1("Hola"))

#if defined (linea)

esto debe procesar si linea esta definida.

#elif 0

esto debe procesar si 0 es verdadero

#elif 3*4==12

esto debe procesar si 3*4 es 12

# if 1

esto debe procesar si 1 es verdadero

# else

esto debe procesar si 1 no es verdadero

# endif

esto debe procesar si 3*4 es 12

#else

esto debe procesar si no ha procesado ninguna condicion anterior fue verdadera

#endif

Esta linea debe ser siempre procesada.

Si se posee una versión de C como Turbo C, Turbo C++ o Borland C++, ésta debe incluír un programa para DOS llamado cpp.exe. Si se está trabajando en Unix el mismo cc puede preprocesar sin compilar. La sintaxis de cpp.exe, para preprocesar los archivos de arriba es la sigueinte:

cpp -P- base.txt

El resultado debe ser un archivo con extención .i que puede ser onservado por un editor de texto.

4.3 Sintaxis de C.

En el lenguaje C existen ciertas normas de sintaxis. Muchas de las cuales se explicarán cuando se vean las estructuras relacionadas.

C no distingue los espacios de los tabuladores de los saltos de línea. Sin embargo el preprocesador si los distingue, así que una constante de cadena no puede estar repartida a través de varias líneas sin que se cierren las respectivas comillas, y los comentarios de fin de línea tipo C++ sí terminan con el fin de línea. A partir de ahora, los caracteres que se definen en ASCII como espacios: espacio atrás (BS), tabulador horizontal (HT), avance de línea (LF), tabulador vertical (VT), avance de página (FF), retorno del carro (CR) y espacio (caracteres de 8 a 13 decimal y 32 decimal, 08 a 0D y 20 hexadecimal) se llamarán simplemente espacios. Cualquier número de espacios a partir de uno pueden separar dos tóquenes y en algunos casos pueden usarse cero espacios. Se recuerda que ASCII no define el fin de línea o la nueva línea y que por lo tanto su indicación depende generalmente del sistema operativo, siendo LF (también llamada NL) en Unix o la combinación CR-LF en DOS/Windows. Otros sistemas pueden definir otras combinaciones para indicar el fin de línea o la nueva línea.

A continuación hay una lista de los tipos de tóquenes que distingue C:

4.3.1 Palabras.

Son conjuntos de caracteres entre letras mayúsculas y minúsculas, números y el carácter de subrayado que comience en letra o subrayado. Pueden equivaler a palabras reservadas, variables, constantes o definiciones o macros del preprocesador. Toda palabra debe estar separada de otra palabra o de una constante numérica por al menos un espacio, aunque no se presenta el caso de que una palabra y una constante numérica, de carácter o de cadena se encuentren seguidos sin un operador o un signo de puntuación entre ellos. Una palabra no necesita separarse de tóquenes tales como operadores por puntuaciones.

C diferencia entre mayúsculas y minúsculas, por lo que las palabras palabra, Palabra y PALABRA son diferentes.

4.3.2 Constantes numéricas.

Comienzan siempre por un número o por un punto. Existen distintos tipos de constantes numéricas:

4.3.2.1 Enteros decimales.

Un conjunto de cifras numéricas que no comiencen e cero (0) ni contengan puntos.

4.3.2.2 Enteros octales.

Un conjunto de cifras numéricas que comience en cero (0) y no contenga puntos ni ochos ni nueves (., 8, 9).

4.3.2.3 Enteros hexadecimales.

Un conjunto de cifras numéricas y letras entre la A y la F o entre la a y la f precedidos de un cero y una equis (0x). A continuación en este manual se usará esta notación para indicar números hexadecimales.

4.3.2.4 Punto flotante.

Un conjunto de cifras numéricas con algún punto entre ellas o al comienzo de ellas y ocasionalmente una e o una E seguida de un entero signado para indicar exponente decimal (notación científica). Una constante de la forma 1 es entero decimal, mientras que 1.0 es constante de punto flotante.

Además de esta sintaxis una constante numérica puede estar seguida inmediatamente de una L o l, como marca de modificador largo.

4.3.3 Constantes de carácter.

Una constante de carácter está enmarcada por comillas sencillas y posee un único carácter en medio o una barra inversa seguida de un único carácter o una barra inversa seguida de una equis seguida de uno o dos caracteres entre números y las letras de la A a la F o de la a a la f. Las combinaciones de barra inversa se presentan a continuación:

'\0' carácter nulo (ASCII 0x00: NUL).

'\m' carácter 0x0m, donde m es un número entre 0 y 9.

'\g' Campana o alarma (ASCII 0x07: BEL).

'\h' Espacio atrás (ASCII 0x08: BS).

'\t' Tabulador horizontal (ASCII 0x09: HT).

'\n' Nueva línea (NL) o avance de línea (ASCII 0x0a: LF).

'\b' Tabulador vertical (ASCII 0x0b: VT).

'\C' Avance de página (ASCII 0x0c: FF).

'\r' Retorno del carro (ASCII 0x0d: CR).

'\xNN' Carácter (no necesariamente ASCII) 0xNN.

'\"' Comilla doble (ASCII 0x22). Puede reemplazarse por '"'.

'\'' Comilla simple (ASCII 0x27).

'\\' Barra inversa (ASCII 0x5c).

'\y' Comportamiento indeterminado pero es tomado como carácter único.

Cabe observar que como C se desarrollo a la par con Unix, muchas veces el carácter ASCII 0x0a (LF) es tomado como carácter de nueva línea (NL). Esto es particularmente válido cuando se usan las rutinas de la biblioteca estándar de C, las cuales se encargan de convertir el carácter NL ('\n') en el carácter o secuencia de caracteres que el sistema reconozca como nueva línea cuando trabaja con textos (no lo hace cuando trabaja con archivos binarios).

En la mayor parte de las ocasiones una constante de carácter puede ser reemplazada por una constante numérica entera con el valor ASCII del carácter. Igualmente puede reemplazarse un número por una constante de carácter cuyo valor ASCII corresponda al número.

4.3.4 Constantes de cadena.

Una constante de cadena está enmarcada por comillas dobles y puede contener cualquier número de caracteres. Las secuencias de barra inversa reconocidas en las constantes de carácter son reconocidas en las constantes de cadena.

Dos o mas constantes de cadena separadas por espacios o sin separación son convertidas por el compilador en una sola cadena. Esto permite encontrar una forma de partir una constante de cadena entre varias líneas del código fuente: en cada línea se abre y cierra una constante de cadena y entre el cierre de una y la apertura de la siguiente puede permitirse la nueva línea, espacios y tabuladores, pero ningún signo de puntuación ni operadores.

printf("Esta el la impresión de una cadena muy larga que no "

"alcanza a estar contenida en una sola línea del código"

" fuente y por lo tanto tuvo que ser partida en varias "

"líneas.\n");

¡Observación! el nombre de archivo de una directiva #include del preprocesador no es una constante de cadena. Si se desea indicar un path completo de DOS/Windows la barra inversa debe ser sencilla.

#include "d:\path\filename.h"

#define ARCHIVO "d:\\path\\filename.txt"

4.3.5 Operadores.

Los operadores en C corresponden a caracteres o conjuntos de DOS caracteres. Algunos de los operadores figuran como símbolos de puntuación en otros lenguajes.

Los operadores se dividen en general entre operadores binarios y unitarios, existe un operador ternario y algunos símbolos como los paréntesis se definen como operadores. Un mismo símbolo puede ser operador y símbolo de puntuación o ser operador binario y unitario. La posición respecto a otros tóquenes definen su característica.

Un operador puede modificar o no a los elementos que opera. Si un elemento no es modificable por el operador puede ser usada indistintamente una variable o una constante. Si el elemento es modificado sólo puede usarse una variable.

Los operadores de C son los siguientes:

4.3.5.1 Operadores de asignación.

Son operadores binarios que modifican al miembro de la izquierda mas no al de la derecha. Se dividen entre el operador de asignación corriente (=) y los operadores de asignación aritméticos o de bit (+=, -=, *=, /=, %=, &=, ^=, |=, <<= y >>=), el primero asigna el valor de la derecha a la variable de la izquierda. Los demás equivalen a operar el valor de la variable de la izquierda con el valor a la derecha y asignar el resultado a la variable de la izquierda. A decir verdad, aunque ésta parece una forma compleja de trabajar, es más cercano a como la máquina trabaja: una secuencia como a=b+c corresponde en realidad a un a=b seguido de un a+=b o, más bien, tmp=b, tmp+=c y a=tmp donde tmp es una variable interna de la máquina, ya que la máquina no hace sumas en el aire: siempre le suma algo a alguien.

El resultado de la operación es el valor de la variable modificada. Este valor puede ser usado como parámetro de otra operación.

4.3.5.2 Operadores aritméticos.

Los operadores aritméticos trabajan sobre valores que no modifican. Están los operadores binarios los cuales son los operadores de adición (+), substracción (-), multiplicación (*), división (/) y modulación (%), cuyos resultados son respectivamente la suma, la diferencia, el producto, el cociente y el módulo o residuo de los DOS operandos, conservando el tipo del operando de la izquierda (la suma de un entero con un flotante es un entero, la suma de un flotante con un entero es un flotante). La división entre DOS enteros es el cociente entero y no la razón como flotante de los mismos ni la aproximación entera. El operador de modulación no esta definido entre números de punto flotante. Están también los operadores aritméticos unitarios + y -, el primero de los cuales no hace nada pero puede preferirse en ocasiones para dar claridad al código y el otro da como resultado el inverso aditivo del valor a la derecha.

4.3.5.3 Operadores de bit.

Los operadores de bit toman valores enteros (char, int, signed, unsigned, long, short, etc.) y los opera bit a bit con una operación lógica. Estos son negación (~) el cual es unitario, y los binarios conjunción (&), disyunción (|) y disyunción exclusiva (^). El primero invierte los bits del entero. La conjunción genera un entero con los bits comunes activados (1) y los demás en cero. La disyunción genera un entero con los bits activados correspondiente a los activados en alguno de los operandos y la disyunción exclusiva genera un entero que activa los bits activados en sólo uno de los operandos.

a=01100&01010; /*a vale el octal 01000 (decimal 512)*/

b=01100^01010; /*b vale el octal 0110 (decimal 72)*/

c=01100|01010; /*c vale el octal 01110 (decimal 584)*/

4.3.5.4 Operadores lógicos.

Los operadores lógicos son la negación (!), la conjunción (&&) y la disyunción (||). Trabajan sobre enteros bajo el concepto de que cero (0) es falso y no cero es verdadero. El no cero por defecto es uno (1) y éste es el resultado de las operaciones que generen verdadero. El resultado es siempre de tipo int. Se distingue que en los operadores de bit 0xaa&0x53 es igual a cero mientras que en los operadores lógicos 0xaa&&0x54 es no cero y no cero, por lo tanto igual a no cero: 0x01, a pesar de que el bit cero (correspondiente a uno) no pertenece a ninguno de los operandos. Igualmente 0xaa|0x54 es 0xfe mientras que 0xaa||0x54 es 0x01, ~~0xfe es 0xfe y !!0xfe es 0x01.

4.3.5.5 Operadores de comparación.

Los operadores de comparación toman dos valores de cualquier tipo, los compara y devuelve el resultado de la comparación como un entero (int) de la forma cero (0) si la comparación falla o no cero (1) si la comparación es verdadera. Los operadores de comparación son de igualdad (==) de diferencia (!=) de desigualdad estricta (< y >) o de desigualdad simple (<= y >=).

4.3.5.6 Operadores de apuntadores y direcciones y estructuras.

El operador de referencia dentro de una estructura o una unión es el operador binario punto (.) a cuya izquierda debe ir el nombre de una variable de tipo estructura o tipo unión y a la derecha el nombre de una variable miembro de la respectiva estructura o unión. (ver estructuras y uniones más adelante)

El operador de subindexación [], toma a la izquierda una variable o constante de tipo vector o arreglo y en medio de los corchetes un valor entero. El resultado es el respectivo elemento del vector.

Para los trabajos con apuntadores se usan los operadores unitarios * y &. El primero se lee lo apuntado por y se refiere al valor o la variable referenciada por el apuntador de la derecha. & se lee la dirección de y entrega la dirección de la variable o la constante a la derecha.

Cuando se tiene un apuntador a un estructura, la expresión *apuntador.miembro devuelve lo apuntado por el miembro miembro de la estructura apuntador, cuando de quiere el miembro miembro de lo apuntado por el apuntador apuntador. Para evitar el uso de paréntesis en (*apuntador).miembro y mejorar la presentación existe el operador -> que se usa como apuntador->miembro.

4.3.5.7 Otros operadores.

La palabra reservada sizeof es considerada en C como un operador que devuelve el tamaño en la mínima unidad de localización de memoria (generalmente bytes) del toquen de la derecha, bien sea una variable o un tipo de datos.

Los operadores de incremento (++) y decremento (--) son operadores unitarios que actúan tanto a la izquierda como a la derecha de una variable. La sintaxis depende del caso: variable++ incrementa a variable en una unidad y da como resultado el valor previo de variable, mientras que ++variable incrementa a variable en una unidad y da como resultado el nuevo valor de variable. Análogamente funciona el operador -- que decrementa la variable en unidad. ¿Preguntado por que C++ recibe ese nombre y no Object C o simplemente C plus? También existe un lenguaje llamado cmm que se lee C menos menos (C minus minus en inglés).

El operador ternario ?: es un operador condicional que toma tres argumentos, uno a la izquierda de la interrogación, otro entre la interrogación y los dos puntos y el último a la derecha de los dos puntos en el formato valor1?valor2:valor3. Si valor1 es cero el resultado es valor3, si valor1 es no cero, el resultado es valor2.

El operador coma (,) toma el valor a la derecha y luego el valor a la izquierda y no hace nada con ellos. Puede parecer en este caso que es un operador inútil pero obliga a que haya un valor a la derecha forzando otras operaciones tales como asignaciones y luego a que haya un valor a la izquierda.

El operador paréntesis (()), que no debe confundirse con el símbolo de puntuación paréntesis, toma el nombre de un tipo de datos entre sí y un valor a la derecha: (tipo)valor y el resultado es el valor valor tratado como si se tratara de un valor de tipo tipo. Esto puede forzar el comportamiento de otros operadores, ya que si bien, si a es una variable de punto flotante a=.5 asigna el valor de un medio a a, una expresión como a=1/2 le asigna el valor de 0.0, ya que primero ejecuta la división entera, cuyo cociente es 0 y ese valor se lo asigna automáticamente convertido en flotante a a. Si se escribe a=(float)1/2 el uno es tratado como flotante y se realiza una división de números de punto flotante que debe dar 0.5. En este ejemplo pudo haberse escrito también a=1.0/2 pero si el uno se tratara de una variable entera uno el .0 es un error de sintaxis ya que estaría buscando el miembro 0 de la estructura o unión uno.

4.3.5.8 Precedencia:

Como es común en la mayoría de los lenguajes una expresión como a=b+c*d, toma el producto entre c y d, lo suma con el valor de b y el resultado lo asigna a a. Por el contrario a=b*c+d toma el producto entre b y c le suma el valor de d y se lo asigna a a. En ambos casos el programa realiza primero la multiplicación, luego la adición y por último la asignación. No todas las precedencias de operadores son tan intuitivas o tan corrientes como las anteriores así que a continuación sigue una tabla con todos los operadores. Los operadores de más arriba tienen mayor precedencia (son ejecutados primero) que los de más abajo y los operadores del mismo nivel tienen la misma precedencia y son ejecutados de acuerdo al orden que tienen en la expresión en el sentido indicado en la columna de la derecha. La precedencia puede ser forzada con la ayuda de paréntesis. Se observa que los operadores de un mismo nivel tienen las mismas características respecto al número de operandos y de como estos son modificados. Esto ayuda a distinguir los operadores que pueden ser binarios o unitarios tales como +, *, & y -.

Operador

sentido

()

[]

->

.

izquierda a derecha

!

~

+

-

++

--

&

*

sizeof

derecha a izquierda

*

/

%

izquierda a derecha

+

-

izquierda a derecha

<<

>>

izquierda a derecha

<

<=

>

>=

izquierda a derecha

==

!=

izquierda a derecha

&

izquierda a derecha

^

izquierda a derecha

|

izquierda a derecha

&&

izquierda a derecha

||

derecha a izquierda

?:

izquierda a derecha

=

*=

/=

%=

+=

-=

&=

^=

|=

<<=

>>=

derecha a izquierda

,

izquierda a derecha

Esta tabla puede ayudar no escribir paréntesis innecesarios pero puede preferirse en ocasiones que por claridad se coloquen algunos paréntesis aunque éstos no tengan objeto. (a*b)/(c*d) es mas claro aunque hace lo mismo que a*b/c/d.

4.3.6 Puntuación.

Además de funcionar como operador los paréntesis son usados como puntuación con los siguientes objetivos:

Resolver la ambigüedad de la precedencia de operadores y/o forzar la precedencia tal como en a*(b+c) donde se evaluará la suma antes que el producto.

Indicar los parámetros de una función, tanto en la declaración como en la definición como en el llamado. En ocasiones este uso del símbolo paréntesis puede ser considerado como operador. Esto sucede en particular en C++.

Es parte de los comandos if, while, switch, do-while y for.

Igualmente la coma puede ser símbolo de puntuación para separar los argumentos de una función, tanto en la declaración como en la definición como en el llamado.

Los dos puntos (:) son también un símbolo de puntuación para indicar marcas (labels), tanto independientes como en las estructuras switch.

Los otros dos elementos importantes de la puntuación son:

Llaves ({ y }) enmarcan un bloque de instrucciones o un bloque de declaraciones.

El punto y coma (;) Finaliza cada comando unitario y cada declaración. Es además utilizado para separar los distintos elementos de un for. Una observación importante es que todo comando unitario debe ir finalizado por un punto y coma ya que este no indica separación de comandos como en Pascal sino finalización de comando.

En C un comando es cualquier cosa que este correctamente escrita y finalizada por un punto y coma o cualquier cosa que comience con abrir llave y termine con cerrar llave y en su interior contenga cero o más comandos bien escritos.

{} es un comando, en este caso un grupo de comandos.

; también es un comando, en este caso un comando sencillo.

;5; son dos comandos sencillos.

5-2; es un comando.

funcion(); es un comando.

"Hola" " Mundo!"; es otro comando..

{;;} es un comando.

{5-2} no es un comando.

{}; son dos comandos.

0,a=b; es un comando.

4.4 Palabras reservadas en C.

El C es un lenguaje en el que se ha buscado que el número de palabras reservadas sea mínimo. El estándar ANSI se C reserva las siguientes palabras:

auto, break, case, char, const, default, do, double, else, enum, extern, float, for, goto, if, int, long, return, short, signed, sizeof, struct, switch, typedef, union, unsigned, void, volatile y while.

Estas palabras se dividen entre controles de flujo, tipos de datos, modificadores y otras.

En C las marcas de inicio y fin de bloque, los comentarios, los operadores lógicos, etc. están representados por símbolos de uno o dos caracteres.

Varios compiladores de C reservan palabras adicionales

4.5 Tipos de datos.

C no es un lenguaje rico en tipos de datos predefinidos. En un principio C usaba exclusivamente datos de tipo entero a los que agregó los tipos de datos numéricos de punto flotante y los apuntadores. Los caracteres simples son tratados en C como enteros. Las cadenas de caracteres o strings no existen propiamente en C como tipo de datos (ver 4.2.4 para una discución sobre las constantes de tipo cadena). Cuando se trabaja con puntos de datos predefinidos la mayor parte de las asignaciones son válidas así como muchas de las operaciones aritméticas, el compilador se encarga de hacer las conversiones del caso.

Además de los tipos predefinidos C permite crear registros o estructuras para mantener como una sola entidad distintos tipos de datos así como crear uniones o enumeraciones.

La declaración de variables de distintos tipos sigue el siguiente esquema:

tipo1 variable1; /* declara variable1 de tipo tipo1 */

tipo2 variable2=valor; /* declara variable2 de tipo tipo2 y */

/* la inicia con el valor valor */

4.5.1 Tipos enteros de datos.

Existen dos tipos principales de tipos enteros de datos: char e int. El primero suele referirse generalmente a bytes y el segundo a unidades algo más grandes pero el tamaño de cada entidad no es definido por ANSI, sin embargo en los sistemas Intel/DOS y en muchos otros sistmas el tamaño suele estar definido en 8 bits para char y 16 bits para int. Para aumentar el número de tipos enteros se definen los siguientes modificadores: signed, unsigned, short y long. Los dos primeros modifican si se va a trabajar con enteros signados o sin signo (en un PC: signed char varía de -128 a 127, unsigned char de 0 a 255 mientras que signed int varía de -32768 a 32767 y unsigned int varía de 0 a 65535). Los modificadores short y long permiten definir enteros de mayor o menor número de bits. Se recuerda que ANSI no define el tamaño de estos tipos de datos con o sin modificadores. Se puede usar un modificador sin el tipo a modificar en cuyo caso se asume int. Aquí hay algunas declaraciones y como son tomadas en varios compiladores para PC Intel/DOS dejando las opciones por defecto.

char a; /* a es un entero entre -128 y 127 */

unsigned char b; /* b queda entre 0 y 255 */

unsigned c; /* lo mismo que unsigned int c */

unsigned int d; /* d es un entero sin signo de 16 bit (0 a 65535) */

unsigned long e; /* e es un entero sin signo de 32 bit */

signed short f; /* f es un entero signado de 16 bit */

int g; /* g es un entero signado de 16 bit (-32768 a 32767) */

Generalmente las constantes de tipo carácter (ver 4.2.3) corresponden a variables de tipo char, las constantes de tipo entero (4.2.2) a int y las constantes de tipo entero largo (modificador L) corresponden a long int. Por razones tradicionales a los enteros tipo char nos referiremos de ahora en adelante como caracteres.

4.5.2 Tipos numéricos de punto flotante.

En C se definen dos tipos de variables de punto floatante: float para variables de precisión sencilla y double para variables de doble precisión. Algunos compiladores definen ademas tipos como long double para números de mayor precisión. En un PC Intel/DOS se tiene, por lo general que float usa 4 bytes (32 bit), double usa 8 bytes (64 bit) y long double usa 10 bytes (80 bits), los cuales corresponden a los tipos de datos de punto flotante soportados por los coprocesadores Intel 80x87 y compatibles.

Un tipo de punto flotante divide los bits entre el signo, la mantisa y el exponente. Se basa en la escritura científica (6.23×1023), pero usando notación binaria y no decimal. Como en notación binaria la primera cifra siempre sería uno (1), ésta se suprime. La mantiza corresponde a los decimales de la expresión así: la última cifra de la mantisa (de derecha a izquierda o primera de izquierda a derecha) se multiplica por 1/2, la penúltima por 1/4, al antepenúltima por 1/8, etc. El resultado de la suma de estos términos (un número entre cero inclusive y uno exclusive) se le suma 1 y se multilica por una potencia positiva o negativa de dos dependiendo de los bits reservados para exponente y, si el bit de signo está activado, se multiplica por -1. En teoría no existe cero pero se toma el menor de los números positivos así generados como tal. Los bits reservados para mantisa y exponente dependen del sistema, a su vez su orden corresponden al sistema de almacenamiento que suele ser poco intuitivo para el iniciado.

4.5.3 Apuntadores.

Los apuntadores son realmente enteros que se refieren a posiciones de memoria. El formato real de un apuntador depende del sistema y puede en algunos sistemas como los PC Intel/DOS ser diferenciados para distintos modelos de memoria.

Sea tipo un tipo de dato de C, bien un tipo predefinido, un tipo predefinido con modificadores o un tipo creado por el usuario, la forma de declarar a una variable apuntador a tipo es:

tipo * variable;

Si el sistema permite modificadores a los apuntadores tales como _far o near, el formato si se desea que la variable sea un apuntador modificado es:

tipo modificador* variable;

Los modificadores a los apuntadores tienen sentido en ambientes como DOS donde la memoria está dividida por un sistema de segmentación e internamente trabaja con dos numeros de 16 bit para direccionar la memoria. El primer número, llamado desplazamiento (offset), puede diferenciar hasta 64 kilobytes de memoria. El segundo número, llamado segmento, diferencia 65536 bloques de 16 bytes cada uno. Una aplicación o una rutina pequeña tiene su codigo y sus datos confinados a un solo segmento y como tal le basta con el solo desplazamiento para direccionar datos y código. Una aplicacion grande o una rutina que maneje datos de una aplicación grande necesita direccionar la memoria completa y como tal necesita de segmento y desplazamiento. El compilador suele escoger automáticamente si los apuntadores son largos o lejanos o si son cortos o cercanos. Para los primeros usa un numero entero de 32 bit divididos en dos de 16 bit para segmento y desplazamiento y en los segundos utiliza un numero de 16 bit. En muchas ocaciones es necesario forzar si el apuntador debe comportarse como un apuntador lejano (desplazamiento más segmento) o como uno cercano (sólo desplazamiento). La mayoría de los compiladores para DOS reservan palabras como far y near o _far y _near para distinguir estos modificadores.

int*a; /* a es un apuntador a un entero. */

int**b; /* b es un apuntador a un apuntador a un entero. */

char*c; /* c es un apuntador a un caracter. Tiene otro significado. */

void*d; /* d es un apuntador sin tipo. */

Para definir un apuntador puro se usa la palabra reservada void que se refiere a un tipo de datos que no corresponde a tipo de datos alguno. void es una palabra para colocar en la posición de los tipos de datos cuando se quiere forzar a que no hayan datos, generalmente dentro de declaraciones. En el caso de los apuntadores, void* hace referencia a un apuntador que no apunta a ningún tipo de datos.

Para usar un apuntador se usa el operador * (ver 4.2.5.6). Si variable es un apuntador a tipo, *variable es un tipo.

int*a; /* declara a como apuntador a entero */

int b; /* declara b como entero */

*a=1; /* asigna 1 a lo apuntado por a ¡¡¡ No debe hacerse !!! */

b=*a; /* asigna a b lo apuntado por a */

En el ejemplo anterior se muestra como se puede referirse al contenido de un apuntador. El resultado final es que b contiente el valor 1. Hay, sin embargo, un error en la secuencia de código y es que a no ha sido inicializado; si bien se le está asignando algo a la dirección de memoria referenciada en a, en un principio a puede estar referida a un lugar cualquiera de la memoria... con seguridad a un sitio indeseado lo que podría llevar a la desestabilización del sistema.

Si bien apuntadores también existen en otros languajes como Pascal, y direcciones de memoria pueden ser referidas directamente con lenguajes como Basic (PEEK() y POKE), en éstos otros lenguajes el uso de los apuntadores corresponde a un curso de programción avanzada lo que minimiza que el aprindiz de programación cometa errores como los de arriba. En C, sin embargo, el uso de apuntadores es más directo, como se verá más adelante con variables a vectores, cadenas de caracteres, estructuras, etc.

La linea: a=(int*)malloc(sizeof(int)); puede ayudar a solucionar el problema, ya que aparta dinámicamente sizeof(int) unidades de memoria (bytes) y asigna a a la referencia a la memoria apartada.

Para referirse al apuntador a una variable se usa el operador & (ver 4.2.5.6). Si variable es de tipo tipo, &variable es de tipo apuntador a tipo.

int*a; /* declara a como apuntador a entero */

int b; /* declara b como entero */

a=&b; /* asigna la dirección de b a la variable a */

*a=1; /* asigna 1 a lo apuntado por a */

En este código, el resultado final es que b vale 1. A diferencia del anterior, este código no es inestable porque conocemos que a es una posición segura. Pero:

long *a; /* declara a como apuntador a entero largo */

short b; /* declara b como entero corto */

a=&b; /* asigna la dirección de b a la variable a */

*a=(short)1; /* asigna 1 a lo apuntado por a */

En este último caso hay un error ya que *a es un entero largo y el entero corto (short)1 al ser asignado a *a es automáticamente convertido a largo y el compilador escribe sobre memoria no correspondiente a b, pudiendo sobreescribir sobre otras variables tales como la misma a. Por el contrario en:

short*a; /* declara a como apuntador a entero corto */

long b; /* declara b como entero largo */

a=&b; /* asigna la dirección de b a la variable a */

*a=1L; /* asigna 1 largo a lo apuntado por a */

En este caso 1L es asignado al entero corto *a y por lo tanto es cortado no escribiendo sobre la totalidad del espacio reservado para b. No se garantiza así que b valga 1L.

Los apuntadores pueden ser operados aritméticamente con enteros, pueden ser incrementados y decrementados etc. Si apuntador es una variable de tipo apuntador al tipo de datos tipo, las siguientes expresiónes son válidas: apuntador++, apuntador-=2, apuntador=apuntador+1. En apuntador++ el valor entero de apuntador se ve incrementado en sizeof(tipo) y de forma similar se comportan las demás operaciones.

4.5.3.1 Apuntadores a funciones.

En el lenguaje C es posible definir y utilizar variables que contengan la dirección de memoria del inicio de una función. Esto permite pasar las funciones como tales como argumento de otras funciones. La declaración de una variable es:

tipo(*variable)(argumentos);

Donde tipo es el tipo de datos que devuelve la función y argumentos es la lista de argumentos de la misma si se requiere. Si funcion es una función correspondiente a la declaración

tipo funcion(argumentos);

es posible hacer asignaciones del tipo:

variable= &funcion;

o llamar a la función cuya dirección se almacena en variable usando:

(*variable)(parámetros);

donde parámetros es una serie de parámetros compatible con la definición de argumentos. Esta facilidad permite entre otras cosas pasar funciones como parámetros de otras funciones; crear vectores de funciones o definir tipos de datos con funciones incorporadas proporcionando una forma de programar orientado a objeto con el lenguaje C tradicional.

4.5.4 Vectores (arreglos) y matrices.

Un vector es un conjunto de tipos que se refieren por medio de subíndices. Es una forma de almacenar varios item de información del mismo tipo referidos por una sola variable y con ayuda de un subíndice. La declaración de un vector sigue el formato:

tipo variable[longitud];

donde tipo es el tipo de datos que componen al vector, variable es el nombre de la variable y longitud es una expresión constante indicando el número de términos del vector. Si se desea iniciar un vector se usa el formato:

tipo variable[longitud]={valor1,valor2,valor3,...,valorn};

donde valor1, valor2, etc. son expresiones constantes compatibles con tipo. Si longitud es mayor que el número de elementos enumerados, el resto es inicializado con basura. Si longitud es menor, pueden presentarse problemas. Para ahorrar inconvenientes se puede omitir el campo de longitud y el vector se inicializa automáticamente en el número de valores dados entre las llaves.

Para referirse a los distintos elementos del vector se usa el operador de subindexación o paréntesis cuadrados (corchetes): [] (ver 4.2.5.6). Los vectores siempre comienzan con el subíndice 0 y terminan en el subíndice longitud-1. Hay que tener cuidado porque C no hace ningún tipo de comprobación, ni en tiempo de compilación ni en tiempo de ejecución, para proteger que se usen subínidces fuera del rango.

La forma como C almacena una variable de tipo vector es como un apuntador al primer elemento del vector. Esto es particularmente cierto cuando se usa al vector como valor. Sin embargo a una variable tipo vector no se le puede asignar ningún tipo de valor.

4.5.5 Cadenas de caracteres (strings).

4.5.6 Estructuras (registros).

4.5.7 Uniones.

4.5.8 Enumeraciones.

4.5.9 La palabra reservada typedef.

La palabra reservada typedef permite definir nuevos nombres de tipos de datos así:

typedef unsigned char byte;

define a byte como otra palabra para indicar un tipo de datos entero del tamaño de un char y sin signo. Para usar typedef se define la palabra a definir nuevotipo como si se declarara a manera de variable y se antecede typedef a la declaración. en cuyo caso nuevotipo no queda declarado como variable sino definido como nuevo tipo de datos. Algunos ejemplos:

typedef float real; /* real reemplaza a float */

typedef char string[256]; /* string es un vector de 256 caracteres */

struct nodo{struct nodo*sig;void*info};

typedef struct nodo*lista; /* define lista como apuntador a struct nodo */

typedef struct{double real,imag;}complex; /*complex es un tipo compuesto de dos double*/

typedef enum{false=0,true}boolean; /*declara boolean como una

enumeración*/

typedef int(*comp)(void*,void*); /*comp es el apuntador a una función

que recibe dos apuntadores y devuelve un entero*/

Si bien en ejemplos sencillos, tales como typedef float real; se hubiera podido pensar en usar la habilidad del preprocesador y reemplazado por #define real float en casos más complejos se ve la utilidad de usar typedef.

Es corriente que cuando se usa typedef para definir una estructura, una unión o una enumeración, se dé un nombre al tipo de datos junto con el nombre a definir, por ejemplo:

typedef struct _nodo{

struct _nodo*siguiente;

void*informacion;

}nodo;

typedef struct _nodo*lista;

En el caso anterior se ve la necesidad de usar la palabra _nodo, ya que nodo no puede ser aún utilizada dentro de la definición de la estructura. En la siguiente línea pudo haberse escrito typedef nodo*lista;. Aún en los casos en los que no es necesidad es costumbre dar un nombre a la estructura.

4.6 Controles de flujo.

4.6.1 Es condicional simple if.

El condicional es el control de flujo más simple y su sintaxis es:

if(expresión)comando

o

if(expresión)comando1 else comando2

donde comando, comando1 y comando2 corresponden a un comando sencillo con su respectivo punto y coma final (;) o un bloque con sus respectivas llaves inicial y final ({ y }). Es importante observar que no colocar el punto y coma antes del else es un error si comando1 es un comando sencillo. También es un error colocar un punto y coma si comando1 es un bloque finalizado con llave de cierre.

Los comandos comando o comando1 se ejecutan si expresión es no cero. Si expresión es cero se ejecuta comando2 en el segundo caso. La expresión comando2 puede ser otro condicional del tipo if o if-else lo que permite secuencias de condicionales del tipo:

if(expresión1)comando1

else if(expresión2)comando2

...

else if(expresiónn)comandon

else comandonmás1

Estas secuencias son generalmente referidas como secuencias de else if.

4.6.2 El ciclo condicional preevaluado while.

La sintaxis del ciclo de mientras que es:

while(expresión)comando

donde comando es un comando sencillo con su respectivo punto y coma o un grupo de comandos con sus llaves respectivas. comando se ejecutará mientras expresión sea no cero.

4.6.3 El ciclo condicional postevaluado do-while.

Otro ciclo similar es el ciclo haga mientras cuya sintaxis es:

do comando while(expresión)

el cual garantiza que comando se ejecuta al menos la primera vez. Si comando es un comando sencillo es obligación colocar el punto y coma (;). Si comando es un bloque, colocar punto y coma después de la llave (}) es un error ya que lo convertiría en dos comandos.

4.6.4 El ciclo for.

El siguiente ciclo es el ciclo for cuya sintaxis es:

for(asignación;expresión;incremento)comando

donde asignación e incremento son comandos sencillos sin el punto y coma. asignación es un comando que se ejecuta al principio del ciclo, expresión es un valor que mientras se no cero indicará que comando se ejecute. incremento es un comando sencillo, sin punto y coma, que se ejecuta inmediatamente después de cada ejecución de comando y antes de reevaluar expresión. El for puede ser reemplazado por:

asignación;while(expresión){comando incremento;}

En aras de la claridad se recomienda usar for cuando asignación corresponde a una asignación, incremento a algún incremento y expresión a una expresión condicional que incluya la variable asignada en asignación e incrementada en incremento. Esto puede ser en expresiones como:

for(i=1;i<=n;i++)comando

o

for(tmp=lista->cabeza;tmp;tmp=tmp->siguiente)comando

Los argumentos del comando for pueden ser omitidos, en el caso de asignación e incremento se debe a la flexibilidad de lo que C considera un comando; sin embargo expresión también puede ser omitida, en cuyo caso se asume siempre verdadera. Esto permite usar a for como un ciclo por defecto, eterno en teoría pero que puede ser interrumpido por una orden de control de flujo como return, break o goto; o de una función de salida como exit().

4.6.5 El interruptor o llave condicional switch.

La llave condicional tiene el siguiente formato:

switch(llave){case caso1:lista_de_comandos1

case caso2:lista_de_comandos2

...

default:lista_de_comandosN}

Donde lista_de_comandosX puede ser cero, uno o más comandos (tanto comandos sencillos como bloques). El interruptor toma el valor de llave y lo compara con caso1, caso2, etc. Si el caso es encontrado en caseX ejecuta los comandos que se encuentren a partir case caseX:. Si el caso no es encontrado, ejecuta los comandos a partir de default:. La marca de deafult o case no implica la finalización de los comandos a ejecutar. En el ejemplo:

switch(llave) {

case 1:

case 2:

funcion1();

funcion2();

case 3:

funcion3();

default:

funcion4();

}

Se tiene que si llave toma un valor distinto a 1, 2 o 3, se ejecuta la función funcion4(). Si llave vale 3 ejecuta funcion3() y enseguida funcion4(). Si llave vale 2 ejecutará funcion1(), seguida de funcion2(), seguida de funcion3() y finalmente seguida de funcion4(). Si llave vale 1 se comporta igual a si valiera 2. Para interrumpir la ejecución de la lista de comandos dentro del bloque del interruptor se usa break.

Los casos caso1, caso2, etc. deben ser constantes y por lo tanto suele ser inconveniente usar switch cuando se trabaja con cadenas de caracteres (ver 4.4.5). Los casos no pueden ser variables ni variables constantes.

4.6.6 El salto goto.

El salto:

goto label;

Continua ejecutando el código a partir de la marca label:.

4.6.7 El interruptor de ciclos break.

La interrupción:

break;

Interrumpe el ciclo actual (for, while, do-while) o el interruptor de llave (switch).

4.6.8 El retorno de función return.

El retorno:

return valor;

return;

Termina la función actual. Si la función devuelve un valor, éste es suministrado como valor.

4.7 Funciones en C.

4.7.1 Utilización de funciones.

4.7.2 Declaración de funciones.

4.7.3 Funciones no declaradas.

No es nesesario declarar una función para utilizarla y la declaración de funciones no requiere declarar los parámetros. En las primeras versiones de C —y el formato lo soporta ANSI— las funciones no requerían declaración salvo cuando devolvían valores no enteros.

4.7.4 Procedimientos: funciones tipo void.

4.8 La función main().

Entre todas las funciones que se definen en un programa en C es necesario definir una función que

4.8.1 Forma simple de la función main().

4.8.2 Argumentos de main().

4.8.3 Otras funciones de inicialización.

La función main() se utiliza cuando C trabaja en forma teletipo o en ambientes en los cuales C se inicia desde modo teletipo tal como el COMMAND.COM de DOS o un shell de Unix.

4.9 Compilación y enlace.

4.9.1 El preprocesamiento.

4.9.2 La compilación.

4.9.3 Compilación y enlace de un programa simple.

4.9.4 Uso de bibliotecas on estándar.

4.9.5 Compilación y enlace de varios módulos.

4.10 Escritura de código compatible con C++.

El C++ puede verse como un C mejorado en muchos aspectos ya que incluye mayores niveles de protección y es más estricto en el uso del código. Esto puede llavar a pensar que el código escrito en C puede ser compilado en C++. Sin embargo hay diferencias entre ambos lenguajes que hacen que el código en C no siempre sea compatible con C++.

Es posible, sin embargo, escribir código que pueda compilar en los dos lenguajes, generalmente esto genera un código en C que es más legible y más estricto, además de que evita la utilización del extern "C" si se desea tomar el código ya escrito en C y agregarle características de C++ tales como definición de nuevos tipos con sus propiedades (definición de números complejos o de cadenas de caracteres, por ejemplo), el uso de parámetros por referencia, uso de sobrecarga de funciones y operadores y/o uso de plantillas.

4.10.1 Palabras reservadas de C++.

El primer paso si se quiere que el código en C compile en C++ es asegurarse que no se estén usando, a manera de nombres de variables, funciones o tipos, palabras reservadas de C++. (El uso de palabras reservadas de C++ como nombres de macros del preprocesador puede no ocacionar problemas siempre y cuando se use el C++ como compilador y no se usen atributos del mismo, ya que los nombres de los macros son reemplazados en tiempo de preproceso y desaparecen

4.10.2 Declaración de funciones.

4.10.3 Estructuras y uniones.

4.10.4 Enumeraciones.

5. Elementos básicos de un programa en C++.

5.1 C++: un C mejorado.

5.1.1 Comprobación fuerte de tipos.

5.1.2 Obligación de la declaración de funciones.

5.1.3 Comentarios de fin de línea.

5.1.4 Estructuras, uniones en C++.

5.1.5 Constantes y enumeraciones en C++.

5.2 Problemas de portabilidad de C a C++.

5.2.1 Palabras reservadas de C++.

5.2.2 Funciones sin definir y funciones definidas sin argumentos.

5.3 Las clases en C++.

5.3.1 Declaración de clases y funciones miembro.

5.3.2 El concepto de struct en C++.

5.4 Sobrecarga de operadores y funciones.

5.4.1 Sobrecarga de funciones miembro.

5.4.2 Sobrecarga de funciones externas.

5.4.3 Sobrecarga de operadores.

5.5 Herencia y sobremontaje.

5.5.1 Herencia de clases.

5.5.2 Funciones virtuales y contenedores de funciones.

5.5.3 Funciones virtuales puras y clases virtuales.

5.6 Plantillas en C++.

5.6.1 ¿Qué es una plantilla?

5.6.2 Plantilla de funciones.

5.6.3 El uso de plantillas sobre el uso de macros.

5.6.4 Plantilla de clases.

5.7 Código portable entre plataformas.

5.7.1 Uso de clases para establecer portabilidad.

5.7.2 Uso de plantillas para establecer portabilidad.

6. La biblioteca BÁSICA de C (estándar ANSI).

6.1 Rutinas de manejo de tipos y caracteres.

6.2 Rutinas de entrada y salida.

6.3 Rutinas matemáticas.

6.4 Rutinas para manejo de cadenas.

7. Bibliografía.

CAMPBELL, Joe. Comunicaciones Serie Guía de referencia del programador en C. Ediciones Anaya Multimedia, S.A. 1990, Madrid. Titulo de la obra Original: C Programmers Guide to Serial Communications. 1987.

ECKEL, Bruce. Aplique C++. McGraw-Hill/Interamericana de España, S.A. 1991, Madrid. Traducido de la primera edición en inglés: Using C++. Osborne McGraw-Hill, Inc. 1989, Berkeley.

LADD, Scott Robert. C++ Techniques & Applications. M&T Publishing, Inc. 1990, Redwood City, California.

OUALLINE, Steve & The Peter Norton Computing Group. Advance C Programming. Bradly Publishing. 1992, New York.

SCHILDT, Herbert. The Art of C. Osborn McGraw-Hill. 1991, Berkeley.



[1] En algunas versiones del código ASCII el carácter 23 hexadecimal corresponde al símbolo £ y es incluso llamado símbolo de libras aunque su representación gráfica sea #. Este símbolo no está plenamente definido en el documento ANSI X3.4 que define al ASCII.