Download 4 LENGUAJE C
Document related concepts
no text concepts found
Transcript
59 Arquitectura y programación de ordenadores 4 LENGUAJE C 4.1 INTRODUCCIÓN El C fue inventado por Dennis Ritchie en los años 70, pero no fue estandarizado hasta 1989 (C89), por el instituto nacional de estándares americano (ANSI), y adoptado por la organización internacional de estándares (ISO). Por eso es habitual que el estandar se refiere al estándar ANSI/ISO. En 1995 se adoptó la enmienda 1 del estándar de C, que entre otras cosas, incorpora varias funciones de biblioteca nuevas. Este documento es la base del estandar de C++, definiendo el subconjunto de C presente en C++. En 1999 se desarrolló uno de estandar para C, normalmente referido como C99, que mantiene prácticamente todas las características de C89, incorporando varias bibliotecas numéricas, arrays de tamaño variable, el cualificador restric para punteros, dos nuevos tipos de variables (complex y bool) y un nuevo cualificador de almacenamiento (mutable). Aunque en sentido estricto, C no es un lenguaje estructurado en bloques, normalmente este lenguajes de define como sencillamente estructurado. Tienen similitudes estructurales con otros lenguajes estructurados como Algol, Pascal y Modula-2. La razón por la que C no es tecnicamente un lenguaje estructurado en bloques es de que un lenguaje estructurado en bloques permite declarar procedimientos o funciones dentro de otros procedimientos o 60 Arquitectura y programación de ordenadores funciones. Puesto que C no permite la creación de funciones dentro de funciones, no puede ser formalmente denominado lenguaje estructurado en bloques. La característica distintiva de lenguaje estructurado es la compartimentalización del código y datos. Se trata de la capacidad de un lenguaje de seccionar y esconder del resto del programa toda la información y las instrucciones necesarias para llevar a cabo una determinada tarea. Una forma con la que se consigue la compartímentalización es mediante el uso de subrutinas que empleen variables locales (temporales). Con el uso de variables locales es posible escribir subrutinas de forma que lo que ocurra en su interior no provoque efectos secundarios en otras partes del programa. Esta posibilidad hace que sea muy fácil que los programas en C compartan secciones de código. Al desarrollar funciones compartimentalizadas sólo es necesario conocer qué es lo que la función hace, no como lo hace. El uso excesivo de variables globales (variables conocidas a lo largo de todo el programa) para hacer que los errores proliferen en el programa, al permitir a los efectos secundarios no deseados. El componente estructural principal de C es la función; una subrutina independiente. En C, las funciones son los bloques constitutivos en los que se desarrolla toda la actividad de los programas. Permiten definir las tareas de un programa y codificarlas por separado, haciendo que los programas sean modulares. Una vez que se ha creado una función que trabaja perfectamente, se pueda aprovechar en distintas situaciones, sin crear efectos secundarios en otras partes del programa. El hecho de poder crear funciones independientes es algo extremadamente crítico en grandes proyectos donde es importante que el código de un programador no afecte accidentalmente al de otro. Otra forma de estructuración viene dada por uso de bloques de código, que es un grupo de instrucciones de un programa conectadas de forma lógica y tratadas como una unidad. En C un bloque de código se crea colocando una serie de instrucciones entre llaves. En este ejemplo: if (x<10) { printf("Demasiado bajo, prueba han de nuevo. \n"); scanf( y en " la %d", &x); 61 Arquitectura y programación de ordenadores } las dos instrucciones que las el if y entre las llaves se ejecuta juntas si x es menor que 10. Estas dos instrucciones junto con las llaves representan un bloque de código. 4.1.1 Compiladores frente a intérpretes Es importante comprender que un lenguaje de computadora define la naturaleza de un programa y no la forma en que el programa se ejecuta. Dos son los métodos generales de ejecución de un programa: puede ser compilado o interpretado. Aunque los programas escritos en cualquier lenguaje de programación pueden ser compilados o interpretados, algunos lenguajes han sido diseñados más para una forma de ejecución que para la otra. Por ejemplo, Java fue diseñado para ser interpretado y C para ser compilado. Sin embargo, en el caso de C es importante tener en cuenta que ha sido especialmente optimizado como lenguaje compilado. Aunque se han escrito algunos intérpretes de C para ciertos entornos (especialmente, ayudas en la depuración o plataformas experimentales), C fue desarrollado con la compilación en mente. En su forma más sencilla, un intérprete le el código fuente de un programa línea a línea, realizando las instrucciones específicas contenidas en esa línea. Esta es la forma en la que trabajan las primeras versiones de BASIC. El lenguajes como Java, el código fuente de un programa se convierte primero en una forma intermedia que es la que se interpreta entonces. En cualquier caso se requiere la presencia de un intérprete de tiempo de ejecución para que se pueda ejecutar el programa. Un compilador le el programa entero y lo convierte a código objeto, que es una traducción del código fuente del programa a una forma que puede ser ejecutada directamente por la computadora. El código objeto también se suelen denominar código binario o código máquina. Una vez que el programa está compilado, las líneas de código fuente dejan de tener sentido durante la ejecución del programa. 62 Arquitectura y programación de ordenadores En general un programa interpretado se ejecuta más despacio que un programa compilado. Recuérdese que un compilador convierte el código fuente de un programa en código objeto que se puede ejecutar directamente en la computadora. Por tanto, la compilación sólo pasa factura una vez, mientras que el código interpretado tiene un precio cada vez que se ejecuta un programa. Forma de un programa en C Existen 32 palabras claves definidas en el estándar C89. Estas son también las palabras clave que constituyen el subconjunto de C presente en C++. La siguientes tablas muestran estas palabras clave y las añadidas estándar C99. Tabla 1 Palabras clave definidas en C89 auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do If static while Tabla 2 Palabras clave añadidas en C99 _Bool _Imaginary _Complex inline restrict 63 Arquitectura y programación de ordenadores Además, muchos compiladores de C han añadido varias palabras clave más que permiten explotar de forma más eficiente su entorno operativo. Por ejemplo, varios compiladores incluyen palabras clave que gestionan la organización de la memoria de la familia de procesadores 8086, facilita la programación usando distintos lenguajes y acceden a las interrupciones. A continuación se muestra una lista de las palabras clave ampliadas que se usan con más frecuencia: Tabla 3 Palabras clave añadidas más frecuentemente asm _ds huge pascal cdecl _es interrupt _ss _cs far near En C, las mayúsculas y las minúsculas son diferentes. Una palabra clave no debe ser usada para otro propósito en un programa; es decir, no debe servir como nombre de variables o de función. Todos los programas en C consisten en una o más funciones. Como regla general, la única función que debe estar presente es la denominada main( ), siendo la primera función que es invocada cuando comienza la ejecución del programa. En códigos de bien escrito, main( ) contiene lo que esencia esboza lo que el programa ha de. Aunque no es una palabra clave, se ha de tratar como si lo fuera. Por ejemplo, no se debe intentar utilizar main como nombre de una variable, ya que probablemente confundida al compilador. La forma general que tiene un programa en C es la que se ilustra a continuación: Inclusión de librerías (funciones necesarias en el programa, incluidas en ficheros.h) Declaraciones globales (int, double, float, char, void, _Bool(C99) _Complex(C99), etc…) 64 Arquitectura y programación de ordenadores int main(lista de parámetros) { secuencia de instruccones } tipo-devueto f1(lista de parámetros) /*tipo-devuelto puede ser int, char, double etc..*/ { secuencia de instrucciones } tipo-devueto f2(lista de parámetros) { secuencia de instrucciones } . . . tipo-devueto fN(lista de parámetros) /*Estas N funciones son utilizadas en main */ { secuencia de instrucciones } 4.1.2 La biblioteca y el enlace Técnicamente, se puede crear un programa en C que se ha funcional y útil y que consiste únicamente en instrucciones que se construyan con las palabras clave de C. Sin embargo, esto es bastante raro ya que C no proporciona palabras clave para llevar a cabo cosas como operaciones de entrada/salida. Por ello, la mayoría de los programas incluyen llamadas a varias funciones contenidas en la biblioteca estándar de C. 65 Arquitectura y programación de ordenadores Todos los compiladores de C incorporan una biblioteca estándar que proporciona las funciones que se necesitan para llevar a cabo las tareas más usuales. El estándar de C específica el conjunto mínimo de funciones que deben ser proporcionadas por todos los compiladores. Sin embargo, es muy probable que el compilador contenga muchas otras funciones. Por ejemplo la biblioteca estándar no define ninguna función gráfica, pero seguramente el compilador que se tenga incluirá algunas. Cuando se llama a una función de la biblioteca, el compilador de C recuerda su nombre. Más tarde, el enlanzador combina el código que se ha escrito, con el código objeto que ya se encuentra en la biblioteca estándar. Este proceso se denomina enlace. Algunos compiladores tiene su propio enlanzador, mientras que otros usan el enlanzador estándar proporcionado por el sistema operativo. Las funciones de la biblioteca se encuentran en formato reubicable. Esto significa que las direcciones de memoria de las diferentes instrucciones de código máquina no han sido definidas de forma absoluta; sólo se mantiene información sobre desplazamientos. Cuando salazón programa con funciones de la el lote que estándar, se usan esos desplazamientos de memoria para obtener las direcciones reales. Muchas de las funciones necesarias para escribir programa se encuentra en el biblioteca estándar. Se comporta como bloques constitutivos que simplemente hay que combinar. Si se escribe una función que se va a usar repetidas veces, también puede ser situada en la biblioteca. Compilación separada La mayoría de los programas cortos de C están enteramente contenidos en un archivo fuente. Sin embargo, a medida que aumenta la longitud del programa, así lo hace el tiempo de compilación. Por ello, se permite partir un programa en muchos archivos y que cada uno se ha 66 Arquitectura y programación de ordenadores compilado por separa. Una vez que han sido compilados todo los archivos, se enlazan entre sí, junto con las rutinas de la biblioteca, para formar el código objeto completo. La ventaja de la compilación separada es que un cambio en el código de uno de los archivos no requiere la recompilación del programa entero, y además permite que los proyectos trabajen fácilmente varios programadores en equipó, como también se constituye en un medio de realización del código en los grandes proyectos. Compilación de un programa en C. La creación de una forma ejecutable de un programa en C consiste en estos tres pasos: 1. Creación del programa. 2. Compilación del programa. 3. Enlace del programa con las funciones que se necesitan de la biblioteca. Actualmente, la mayoría de los compiladores de C proporcionan entornos integrados de desarrollo que incluyen un editor. Los compiladores sólo admiten como entrada archivos de texto estándar. Por ejemplo, el compilador no aceptará archivos creados con ciertos procesador este texto, debido a que contendrán códigos de control y caracteres no imprimibles. El método exacto que se utilice para compilar un programa, dependerá del compilador de C que según se usan. Además, la forma en que se lleve a cabo el enlace variará entre distintos compradores y entornos. Por ejemplo, el lanzador puede estar incluido como parte del compilador o puede ser una aplicación independiente . 67 Arquitectura y programación de ordenadores Mapa de memoria de C Un programa compilado en C crea y usa cuatro regiones de memoria lógicas diferentes que sirven para funciones específicas. La primera región es la memoria contiene realmente el código del programa. La siguiente región es la memoria donde se guardan las variables globales. Las dos regiones restantes son la pila (stack) y el montón (heap). La pila se usa para muchas cosas durante la ejecución del programa. Mantiene las direcciones de vuelta para las llamadas funciones, así como las variables locales. También sirve para guardar el estado actual de la CPU. El montón es la región de memoria libre que puede usar el programa mediante las funciones de asignación dinámicas. Aunque la disposición física esa acta de cada una de las cuatro regiones de memoria difiere entre los distintos tipos de procesador y entre las distintas implantaciones de C, las siguiente figura muestra de forma conceptual como aparece el programa de C en la memoria. 68 Arquitectura y programación de ordenadores 4.2 FUNCIONES, EXPRESIONES, VARIABLES Y OPERANDOS Las expresiones se forman con los elementos atómicos de C: los datos y los operadores. Los datos se puede representar mediante variables, constante o valores devueltos por las funciones. C admite varios tipos diferentes de datos. También proporciona una amplia variedad de operadores. Tipos de datos básicos C89 define cinco tipos de datos básicos: carácter, entero, real, real de doble presión y sin valor. Se declaran con char, int, float, double y void, respectivamente. Todos los demás tipos de datos se basan en alguno de estos. El tema y el intervalo de estos tipos varían con cada tipo de procesador y de compilador. Sin embargo, en todo los casos un carácter es un byte. El tamaño entero normalmente es el mismo que el tamaño de una palabra de memoria en el entorno de ejecución del programa. Para la mayoría de los entornos de 16 bits, como DOS y Windows 3.1, un int ocupa dieciséis bits. Para la mayoría de los entornos de 32 bits, como Windows 95/98/NT/2000, un int ocupa 36 bits punto es importante saber que C sólo estipula el intervalo mínimo para cada tipo de datos pero los tamaño en byte. Los valores del tipo char, se usan normalmente para guardar valores definidos en el juego de caracteres ASCII. Los valores que estén fuera de este intervalo pueden ser manejados de forma diferente por los distintos compiladores. el intervalo de los tipos float y double dependerá del método utilizado para representar los reales en como flotante el estándar de C específica el mínimo intervalo para un valor real en coma flotante es de 1E-37 a 1E+37. En la tabla 4 se muestra los mínimos números de dígitos de presión para cada tipo de como flotante. El tipo void, o bien declara explícitamente una función que no devuelve valor alguno, o crea punteros genéricos. 69 Arquitectura y programación de ordenadores Tabla 4 Todos los tipos de datos definidos por el estándar de C Tipo Tamaño Intervalo mínimo aproximado en bits char 8 -127 a 127 unsigned char 8 0 a 255 signed char 8 -127 a 127 int 16 o 32 -32278 a 32767 unsigned int 16 o 32 0 a 65535 signed int 16 o 32 Igual que int short int 16 -32278 a 32767 unsigned short int 16 0 a 65535 signed short int 16 Igual que short int long int 32 -2.147.483.467 a 2.147.483.467 long long int 64 -(263-1) a 263-1 (Añadido por C99) signed long int 32 Igual que long int unsigned long int 32 0 a 4.294.967.295 unsigned long long int 64 0 a 264-1 (Añadido por C99) float 32 1E-37 a 1E+37 con seis dígitos de precisión double 64 1E-37 a 1E+37 con diez dígitos de precisión long double 80 1E-37 a 1E+37 con diez dígitos de precisión Modificación de los tipos básicos A excepción del tipo void, los tipos de datos básicos pueden tener distintos modificadores precediendolos. Un modificador se usa para alternar el significado del tipo base de forma que se ajuste a las necesidades de cada momento. A continuación se muestra la lista de modificadores: signed 70 Arquitectura y programación de ordenadores unsigned long short El tipo int se pueda modificar con signed, unsigned, long y short. El tipo char se puede modificar con signed y unsigned. long también se puede aplicar a double ( C99 también permite que long modifique a long). Variables Una variable es una posición de memoria con nombre que se usa para mantener un valor que puede ser modificado por el programa. Todas las variables han de estar declaradas antes de poder ser utilizadas. La forma general de declaración es: tipo lista_de_variables; Aquí, tipo debe ser un tipo de dato válido con algún modificador, y la lista de variables puede consistir en uno o más nombres de identificadores separados por comas. A continuación se muestran algunas declaraciones: int i, j, l; short int si; unsigned int ui; double balance, beneficio, pérdida; Existen tres sitios básicos donde se pueden declarar variables: dentro de las funciones, en la definición de parámetros de función y de fuera de todas las funciones que. En las variables son, respectivamente, las variables locales, los parámetros formales y las variables globales. 71 Arquitectura y programación de ordenadores Variables locales Las variables que se declaran dentro de la función se denominan variables locales, y sólo pueden ser utilizadas en las instrucciones que estén dentro del bloque en el que han sido declaradas. Por lo tanto no pueden ser conocidas fuera de su propio bloque de código. La variables locales sólo existe mientras está ejecutando el bloque de código en el que son declaradas, o sea, se crea al entrar en el bloque y se destruye al salir de él. Más aún, una variable declarada dentro de un bloque de código no tienen que ver con otra variable con el mismo nombre que se ha declarado en otro bloque de código distinto. En C89 se deben declarar todas las variables locales al principio del bloque en el que se usen, antes de cualquier instrucción ejecutable. Sin embargo en C99 (y en C++), pueden declararse variables locales en cualquier lugar de un bloque, previamente a su primer uso. Aunque las variables locales no pueda retener sus valores entre llamadas, se puede indicar al compilador que retenga sus valores mediante el uso del modificado static. A menos que se especifique lo contrario, las variables locales se guardan en la pila. El hecho de que la pila sea la región de memoria dinámica y cambiante, explica por qué las variables locales no pueden, en general, mantener sus valores entre llamadas a funciones. A los nuevos continuación se muestran algunos ejemplos del uso de variables locales en funciones y bloques. void func1(void) { int x; x=10; } 72 Arquitectura y programación de ordenadores void func2(void) { int x; x=-100; } La variable entera x se declara dos veces, pero se corresponden ni tiene relación. Tal como se dijo, cada x sólo es conocida en el propio código en que se ha declarado la variable. void f(void) { int t; scanf(" %d ",&t); if (t==1) { char s[80]; /*esta se crea solo al entrar en este bloque*/ printf("Introduzca el nombre:"); gets(s); /*Hacer algo*/ } /* s no se conoce aquí*/ } Aquí la variable local s las se crea tras entrar en el bloque de código if y se destruye al salir de él, y sólo es conocida dentro de éste y no puede ser referenciada fuera de él; ni siquiera en otras partes de la función que la contiene. 73 Arquitectura y programación de ordenadores #include <stdio.h> int main(void) { int x; x=10; if(x==10) { int x; /* esta x oculta a la x externa */ x=99; printf("x interna: %d\n", x); } printf("x externa: %d\n", x); return 0; } el programa muestra los siguiente: x interna: 99 x externa: 10 en este ejemplo, la x que se ha declarado en el bloque if interno oculta a la x externa. por tanto, la x interna y la x externa son dos objetos independientes y distintos. Una vez que se bloque termina, la x externa vuelve a ser visible. 74 Arquitectura y programación de ordenadores Parámetros formales Si una función va a usar argumentos, debe declarar las variables que van a aceptar los valores de los argumentos. Esas variables son los parámetros formales de la función. Se comportan como cualquier otra variable local de la función. Como se muestra en el siguiente fragmento de programa, sus declaraciones se dan las el nombre de la función y entre paréntesis: /* Devuelve 1 si c es parte de la cadena s; si no, 0 */ int esta_en(char *s, char c) { while(*s) if (*s==c) return 1; else s++; return 0; } Esta función tiene dos parámetros, s y c, y devuelve 1 se el carácter especificado en c está contenido en la cadena s; si no es así, devuelve 0. Parámetros formales reciben los valores de los argumentos que se pasan a la función, por lo demás se comportan como cualquier otra variable local. Por ejemplo, se pueden realizar asignaciones a los parámetros o utilizarlos en expresiones correctamente construidas. Hay que tener presente que, igual que las variables locales, también son dinámicas y se destruyen a salir de la función. Variables globales A diferencia de las variables locales, las variables globales se conocen a lo largo de todo el programa y se pueden usar en cualquier parte del código. Además, mantiene en sus valores durante toda la ejecución del programa. Las variables globales se crean al declararlas en 75 Arquitectura y programación de ordenadores cualquier parte fuera de una función. Pueden ser accedidas por cualquier expresión, independientemente de la función en la que se encuentre la expresión. #include <stdio.h> int cuenta; /* cuenta es global */ void func1(void); void func2(void); int main(viod) { cuenta=10; func1( ); return 0; } void func1(void) { int temp; temp=cuenta; func2( ); printf("cuenta es %d", cuenta); /* imprimirá 100 */ } void func2(void) { int cuenta; 76 Arquitectura y programación de ordenadores for(cuenta=1; cuenta<10; cuenta++); putchar('.'); } Debe estar claro que aunque ni main( ) ni func1( ) han declarado la variable cuenta, ambas pueden usarla. Sin embargo, la función func2( ) declara una variable local llamada cuenta. Cuando func2( ) referencia cuenta como sólo referencias su variable local, no la global. Si una variable global y una variable local tienen el mismo nombre, todas las referencias a ese nombre de variable dentro de la función donde se ha declarado la variable local se efieren a esa variable local y no tienen efecto sobre la variable global. El almacenamiento de las variables globales se hace en una región de memoria fija establecida para este propósito por el compilador. Las variables globales son muy útiles cuando se usan los mismos datos en varias funciones del programa. Sin embargo, se debe evitar el uso de variables globales innecesarias. Utiliza memoria todo el tiempo de ejecución programa, no sólo cuando se necesitan. Además, en uso de una variable global cuando se podría usar una variable local hace que la función sea menos general debido a que depende de algo que debe estar fuera de ella. Finalmente, que el uso de un gran número de variables globales puede producir errores en el programa debido a efectos secundarios desconocidos y no deseables. Un gran problema en el desarrollo de grandes programas es el cambio accidental del valor de una variable por haber sido utilizada en alguna otra parte del programa. Esto puede ocurrir en C si se usan demasiadas variables globales en los programas. 77 Arquitectura y programación de ordenadores 4.3 INSTRUCCIONES Secuenciación La manera de secuenciar instrucciones en C consiste en escribirlas consecutivamente (notemos que el punto y coma en C es un terminador de instrucciones y no un separador). Por otra parte, para aumentar la legibilidad de los programas, es hábito escribir instrucciones diferentes en líneas diferentes y respetar el uso habitual de la identación que ya hemos utilizado en los algoritmos. Alternación La estructura de alternación en C se realiza mediante la instrucción if—else—if, con el formato siguiente: if (condicion_1) { secuencia_de_instrucciones_1 } else if (condicion_2) { secuencia_de_instrucciones_2 }... else if (condicion_k) { secuencia_de_instrucciones_k } Observaciones: 78 Arquitectura y programación de ordenadores ( i ) En caso de tener una sola instrucción en lugar de una secuencia, no hace falta cerrarla entre llaves. ( ii ) Si condicion_k es exactamente la negación de todas las condiciones precedentes, podemos no escribir la parte if (condicion_k), ya que C interpreta el else como lo que significa en inglés, o sea, en caso contrario. ( iii ) Además, si secuencia_de_instrucciones_k es la secuencia vacía, podemos no escribir toda la parte else if (condicion_k) { secuencia_de_instrucciones_k } Hay un caso para el cual C provee una instrucción alternativa especifica. Cuando en la estructura anterior todas las protecciones afirman la igualdad de una variable determinada con constantes predefinidas, se puede utilizar la instrucción switch que presenta el formato siguiente: switch (variable) { case constante_1: { secuencia_de_instrucciones_1 } break; case constate_2: { secuencia_de_instrucciones_2 } break; ... 79 Arquitectura y programación de ordenadores case constante_k: { secuencia_de_instrucciones_k } break; default: { secuencia_de_instrucciones_por_defecto } } Al igual que con el if, si la secuencia_de_instrucciones_por_defecto es la secuencia vacía, se pueden omitir el default y la secuencia misma. Iteraciones En C hay tres maneras de implementar iteraciones. La más básica es la sentencia de iteración while: while (condición) { secuencia_de_instrucciones } Una variación del while es la instrucción do, que se diferencia de la primera en que verifica la condición después de ejecutar la secuencia_de_instrucciones y, por lo tanto, la ejecuta como mínimo una vez. Su sintaxis es do { 80 Arquitectura y programación de ordenadores secuencia_de_instrucciones } while (condición) Finalmente, la instrucción for (variable = valor_inicial; variable<=valor_final; variable = variable + paso) { secuencia_de_instrucciones } donde paso indica el incremento (o decremento) de la variable. 4.4 ARRAYS, CADENAS Y PUNTEROS La declaración de vectores en C se hace mediante el siguiente formato: tipo nombre_vector [dim] Donde dim ha de ser un número entero y denota la dimensión o longitud del vector. Así, las instrucciones siguientes: int datos[10]: char nombre[12], apellido[12]; Declaran un vector de enteros de 10 elementos y dos vectores de caracteres de 12 elementos cada uno. Una vez declarado un vector de n elementos, se puede leer y escribir en sus componentes teniendo en cuenta que éstas se numeran de la 0 a la n-1. Por ejemplo, para referenciar la 81 Arquitectura y programación de ordenadores primera letra de apellido se ha de escribir apellido[0] y para referenciar el último elemento de datos se ha de escribir datos[9]. Para declarar variables indexadas con mas de un índice, el formato es Tipo nombre_variable[dim_1][dim_2]...[dim_k] Usualmente se llama vector una variable con un índice y matriz una variable con dos índices. Además, el primer índice se llama fila, el segundo columna y el tercero página. A partir del cuarto índice no se utilizan nombres específicos. Cadenas de caracteres Es frecuente emplear vectores para la manipulación de cadenas de caracteres, como nombres de personas, de calles, etc. En estos casos, hemos de declarar la variable con una longitud capaz de alojar el nombre más largo de entre los valores posibles. Así, para almacenar nombres de personas, podemos declarar el vector char nombre[15] si se sabe que 15 es una cota para el tamaño de los nombres que aparecerán durante la ejecución. Ahora bien, resulta muy incómodo tener que rellenar con espacios el resto de las letras hasta la 15ª cada vez que se lee un nombre desde el teclado. Para facilitar el tratamiento de estos vectores de caracteres con longitud variable, C tiene un tipo predefinido que resulta muy conveniente: el tipo string (cadena de caracteres). Su declaración es la normal de un vector de caracteres. Lo que cambia es su lectura y escritura. En el momento de leer una cadena de caracteres, el computador los leerá uno a uno hasta encontrar un cambio de línea. Cuando esto suceda, añadirá al final de la secuencia leída un carácter nulo (el carácter con código ASCII igual a 0). Esta marca le permitiría saber posteriormente donde se encuentra el fin de la cadena. Huelga decir que si sabemos que la longitud máxima de las cadenas a tratar es k, hace falta declarar el vector de dimensión k+1 para poder alojar también esta marca. Así, en el ejemplo anterior se debería declarar char nombre[16] 82 Arquitectura y programación de ordenadores La lectura de una cadena de caracteres se realiza poniendo en el control el formato %s y quitando del nombre de la variable el prefijo & en la instrucción scanf. Existe otra manera de leer una cadena desde el teclado, usando la instrucción gets con el formato gets (nombre_cadena); que lee todos los caracteres tecleados hasta pulsar la tecla retorno. Para la escritura, también hace falta utilizar el formato %s en la parte de control del printf. C también posee una gran variedad de funciones para manipular cadenas. Las más corrientes son strcpy (cad_1, cad_2); strcat (cad_1, cad_2); res_comparacion = strcmp (cad_1, cad_2); longitud = strlen (cad); La primera copia el contenido de cad_2 sobre cad_1. La segunda añade el contenido de cad_2 al final de cad_1. La tercera compara lexicográficamente los contenidos de las cadenas que recibe como entradas y retorna 0 si son la misma. Si cad_1 es mayor que cad_2, entonces retorna un número positivo y, si es menor, uno negativo. Finalmente strlen(cad) retorna la longitud de cad. Para utilizar estas instrucciones hace falta importarlas del módulo string. Finalmente, cabe destacar que las cadenas constantes se encierran entre comillas dobles ". Por ejemplo, en strcpy (cad, "hola"); se asocia la cadena hola a la variable cad. 83 Arquitectura y programación de ordenadores 4.5 ESTRUCTURAS, UNIONES, ENUMERACIONES Y TYPEDEF 4.5.1 Estructuras Las estructuras (struct) son agrupaciones de una o más variables de tipos posiblemente diferentes, agrupadas bajo un solo nombre, que permite un manejo más cómodo de la agrupación de la información. Las struct son estructuras de datos similares a los registros (record) de Pascal. La forma general de definición es: struct tipo_estructura { tipomiembro_1; tipo miembro_2; . tipo miembro_n; } variable_estructura; donde tipo_estructura o variable_estructura pueden omitirse pero no ambos. Las estructuras ayudan a agrupar información relacionada, por ejemplo los datos de un carnet de identidad, las coordenadas de un punto, etc. Ejemplos de declaración de estructuras struct datos { char nombre[20]; char direccion[20]; int codigo; }; struct datos a,b[5]; /* b es un array de estructuras */ 84 Arquitectura y programación de ordenadores Estructuras anidadas: se producen cuando algún miembro de la estructura es a su vez otra estructura. struct fecha { int dia; int mes; int anio; }; struct persona { char nombre[20]; struct fecha nacimiento; }; struct persona p; Referencia a los elementos de una estructura Los elementos individuales de una estructura se referencian utilizando el operador punto (.) entre el nombre de la variable de tipo estructura y el nombre del miembro de la estructura. A los elementos de una estructura se les denomina miembros. Continuando con las estructuras de los ejemplos anteriores, se pueden tener las siguientes referencias a miembros: a.nombre a.dirección p.nombre a.codigo p.nacimiento.dia b[2].codigo p.nacimiento.mes Funciones y Estructuras A Paso por valor de miembros de una estructura a una función. Se realiza como si fueran variables simples. Por ejemplo, para pasar por valor el miembro a.codigo 85 Arquitectura y programación de ordenadores void funcion f1(int) /*declaración de la función prototipo*/ f1(a.codigo); /* llamada a la función */ void f1(int x) /* definición de la función */ { ........ ..........} B Paso por dirección de miembros de una estructura a una función. Se realiza como si fueran variables simples. Por ejemplo, para pasar por valor el miembro a.codigo void funcion f1(int *) /*declaración de la función prototipo*/ f1(&a.codigo); /* llamada a la función */ void f1(int *x) /* definición de la función */ {.......... ..........} Hay que tener en cuenta que si lo que se pasa a una función es un miembro de una estructura que sea un array éste siempre se pasa por dirección (ya que el nombre de un array es la dirección del primer elemento del mismo). C Paso por valor de estructuras completas a funciones. En el siguiente ejemplo suma es una función que recibe dos estructuras pasadas por valor y a su vez devuelve una estructura. struct vector { int x,y,z; }; struct vector (struct vector, struct vector); void main() { 86 Arquitectura y programación de ordenadores struct vector v1,v2,v3; ... v3=suma(v1,v2); ... } struct vector suma(struct vector v1, struct vector v2) { v1.x+=v2.x; v1.y+=v2.y; v1.z+=v2.z; return (v1); } D Paso por dirección de estructuras completas a funciones Cuando las estructuras son muy grandes es más eficiente pasarlas por dirección. En ese caso se utilizan punteros a estructuras para realizar la comunicación. Para acceder a los miembros de la estructura debe utilizarse la combinación de los operadores * y punto. Sin embargo el operador punto tiene más precedencia que * siendo necesario el uso de paréntesis para asegurar que se aplique primero * y después punto. También puede utilizarse el operador -> para acceder a los miembros de una estructura referenciada por un puntero. #include <stdio.h> struct pareja { int a,b; }; void f1(struct pareja *); void main() { struct pareja p = { 13, 17} /* inicialización de los 87 Arquitectura y programación de ordenadores miembros*/ f1(&p); printf("valor de a:%d valor de b:%d\n",p.a,p.b); /* escribe 14 y 18 */ } void f1(struct pareja *q) { q->a++; /* equivalente a (*q.a)++ pero más usado */ q->b++; return; } 4.5.2 Uniones Las uniones tienen una sintaxis similar a las estructuras, pero se diferencian de éstas en que sus miembros comparten almacenamiento. Una variable unión define a un conjunto de valores alternos que pueden almacenarse en una porción compartida de memoria. Es una versión C de los registros variantes de otros lenguajes como Pascal. El compilador asigna una porción de almacenamiento que pueda acomodar al más grande de los miembros especificados. La notación para acceder a un miembro de la unión es idéntica a la que se emplea para acceder a un miembro de una estructura. Un ejemplo de unión es el siguiente: union ejemplo{ int codigo; char nombre[10]; char nif[12]; }; Una unión sólo puede ser inicializada con un valor del tipo de su primer miembro union ejemplo a={123}; 88 Arquitectura y programación de ordenadores 4.5.3 Enumeraciones La definición de tipos por extensión o enumeración sirve para definir tipos de datos que toman valores en un conjunto finito y tiene una sintaxis muy simple. typedef enum {valor_1, valor_2, ..., valor_k} nombre_tipo con esta definición, además, los k valores quedan ordenados con el orden en el que han sido declarados. Si se mira la definición del tipo booleano se puede ver que ha sido definida de esta manera. El hecho de poder hacer estas identificaciones puede resultar útil en muchos casos. Por ejemplo: typedef enum {lunes=0, martes=1, miercoles=2, jueves=3,viernes=4} dia_laborable; Para definir tipos nuevos mediante el constructor tabla se ha de utilizar la palabra reservada typedef, esta vez con el formato typedef tipo_conocido tipo_nuevo[dim_1]...[dim_k]; donde dim_k es la longitud del k-ésimo dominio. El formato del constructor tupla en C es el siguiente typedef struct { tipo_1 nombre_1; tipo_k nombre_k; } tipo_nombre_nuevo; donde, naturalmente, los tipos de la declaración anterior pueden ser cualesquiera de entre los tipos primitivos o definidos previamente y los nombres pueden ser de variables indexadas. Para acceder a los campos de una variable con un tipo definido mediante el constructor tupla, también se utiliza el punto. Así, si cliente es una variable del tipo persona y este tipo está definido como: typedef struct { char nombre[15]; char apellido[15]; int edad; char ciudad[15]; } persona; 89 Arquitectura y programación de ordenadores las expresiones siguientes: cliente.nombre cliente.edad cliente.ciudad[0] denotan respectivamente, el nombre, la edad y la primera letra de la ciudad de cliente.