Estructura de un Programa en C. Un programa en C consta de una o más funciones, las cuales son similares a las subrutinas en FORTRAN o los procedimientos en PASCAL. #Include Archivo de cabecera main() Función principal { Inicio del cuerpo de la función printf ("Bienvenido"); Función de librería } Fin de la función * Los archivos de cabecera tienen extensión .h, empiezan con el signo de # (número) y están encerrados entre los signos de < > (menor y mayor). Todo lo que comienza con los signos de < > (menor y mayor) son instrucciones de preprocesador, estos archivos de cabecera pueden ser editados. * main es la función principal y las funciones se distinguen por que luego del nombre vienen los signos de ( ) (paréntesis) y dentro de estos están los argumentos, si la función no utiliza argumentos entonces se pone la palabra void; si esta seguido de un (;) (punto y coma) significa que se esta llamando a esa función pero si esta seguida de un signo de { (llaves) indica que es el inicio del cuerpo de la función. * printf imprime sus argumentos en el standard out (stdout) o sea la pantalla. Puntuación en la programación en C Generalmente se puede dejar espaciamiento o cortar líneas de código sin que esto cause problemas aunque no es aconsejable. En C las letras minúsculas y mayúsculas son significativas o sea que existe diferencia main() { printf("Bienvenidos"); } main () { printf("Bienvenidos"); } main ( ) { printf("Bienvenidos"); } Sentencias en C Las sentencias son las principales componentes de las funciones, se pueden clasificar por su efecto, además en C es posible unir sentencias una a continuación de otra luego del (;) (punto y coma). Comentarios en C Los comentarios se inician con un ( /* ) slash seguido de un asterisco y se finaliza con ( */ ), ejemplo /* Esto es un comentario */. Identificadores En C los nombres de variables suelen llamarse identificadores, un identificador o nombre consiste en cualquier combinación de caracteres válidos como son las letras A hasta la Z en mayúsculas o minúsculas, los dígitos de 0 a 9 y el carácter ( _ ) subrayado. Los identificadores deben empezar con una letra o carácter subrayado, los dígitos no pueden ser el primer carácter de un identificador; estas reglas son las mismas para los nombres de funciones. Se puede utilizar el tipo de letra que se desee en los identificadores, en general utilizar identificadores que solo difieran en letras mayúsculas o minúsculas es arriesgarse a tener problemas; los identificadores pueden tener una longitud de hasta 31 caracteres según ANSI C. Palabras Reservadas Las palabras reservadas son las propias del lenguaje como son: do while main else if break for case goto getch entre otras. Tipos de Datos y Declaración de Variables En C una variable queda definida por tres atributos, su clase de ubicación en la memoria, su tipo y su nombre. Tipos Fundamentales char int long float double Declaración de Variables En C se debe declarar todas las variables antes de su utilización, normalmente al comienzo de la función main() { int a; /* el tipo de variable es int y el nombre de la variable es a */ float b; double c,d; /* el tipo de las variables es double y el nombre de la variable es c y d */ char x; . . . . . . } Asignación de Valores Para la asignación de valores se utiliza el signo ( = ) igual, por ejemplo: a=5; b=4; x=43; n=4.76; en C es posible asignar a varias variables un solo valor ejemplo: a = b = c = 7; x = y = n = 34; p=d=m=56.8 k=i=23 Inicialización de Variables Algunos lenguajes automáticamente inicializan variables numéricas a cero y variables tipo carácter a blanco o a carácter nulo, en C no es así ya que se puede declarar la variable e inicializarla al mismo tiempo, por ejemplo: main() { int x=5; /* El % se llama especificadores de formato e imprime de acuerdo al tipo de dato */ printf("El valor de x es %d \n",x); /* EL \n es un carácter de control */ } Tipo char:- Es un tipo de dato para almacenar un carácter y puede almacenar todos los números enteros desde el 0 hasta el 255. Tipo int:- Puede tomar todos los valores positivos, negativos o el cero ( 0 ) inclusive. También existen en C los enteros sin signo los cuales pueden ser positivos o cero ( 0 ). Int desde -32768 hasta 32767; unsigned int desde 0 hasta 65535. Tipo long:- El largo de un entero a veces resulta corto y con long tenemos una longitud desde menos 231 hasta 231 positivo, si esto resulta pequeño entonces se declara como unsigned long. Tipo float:- Este tipo representa lo que se conoce como un número en punto flotante y es aquel que puede tener una parte fraccionaria y se le suele representar en tres partes, un signo, un valor numérico y un exponente. En este tipo la mantisa es de 7 cifras. Tipo double:- Es un número en punto flotante pero con doble precisión, con este tipo se puede ampliar la mantisa de 7 cifras a 14 cifras significativas. Operadores Aritméticos Se dispone de los cinco operadores aritméticos clásicos como son: (+) Suma (-) Resta (*) Multiplicación (/) División El quinto operador es el que proporciona el resto de la división (residuo) (%) Módulo. Operadores de Asignación Aritmética Estos resultan de la unión de los operadores aritméticos con el operador de asignación el signo (=), o sea: (+=) Suma igual ( -=) Resta igual ( *=) Multiplicación igual ( /=) División igual (% ) Módulo igual. Estos operadores se aplican de la siguiente manera: ( x += 5 ), en este ejemplo se toma el operando de la izquierda lo suma con el operando de la derecha y lo asigna al operando de la izquierda, en este caso la variable x. Operadores de Incremento y Decremento El operador de incremento es el (++) y el de decremento es el (--), son operadores unarios de muy elevada prioridad y sirven para incrementar o decrementar una unidad. Pre-incremento y Post-incremento:- La mayor diferencia entre las formas pre-fija y post-fija del operador incremento es que el formato pre-fijo primero incrementa y usa después el nuevo valor en la expresión. El formato post-fijo primero usa el valor e incrementa después el valor actual en la expresión; por tanto incrementa el valor después de usarlo. Operadores Relaciónales Estos establecen la magnitud relativa de dos elementos y son los siguientes: Expresión Significado a < b Es a menor que b a > b Es a mayor que b a == b Es a igual a b a != b Es a diferente o no igual a b a <= b Es a menor o igual a b a >=b Es a mayor o igual a b Recordemos que estas operaciones nos dan resultados lógicos de 1 ó 0 es decir valores de verdadero o falso; lenguaje C considera todo valor no cero como un valor verdadero. Operadores Lógicos C proporciona operadores lógicos para combinar los resultados de varias condiciones. Una expresión compuesta es aquella que utiliza operadores como estos y que se pueden evaluar para obtener un único resultado de verdadero o falso. Dos de los operadores lógicos son binarios porque usan dos operandos, devuelven un resultado basado en los operandos recibidos y en el operador. && (AND) Este operador conocido como producto lógico retorna un valor de verdadero si los operandos son verdaderos. || (OR) El operador de suma lógica retorna un valor de verdadero si los operandos o uno de los operandos es verdadero. ! (NOT) Operador de negación, tiene por efecto invertir el resultado de la expresión que le sigue es decir si la expresión es verdadera después de aplicar este operador la expresión será falsa y viceversa. Los operadores lógicos tienen una prioridad bastante baja, menos que los operadores de igualdad pero mayor que el operador de asignación. Jerarquía de Prioridad de los Operadores Operadores Unario / Binario Comentario !, &, +, -, sizeof() Unario Operadores Unarios *, /, % Binario Multiplicador Aritmético +, - Binario Aditivos Aritméticos <, <=, >, >= Binario Operadores Relaciónales ==, != Binario Operadores de Igualdad && Binario Multiplicador Lógico || Binario Aditivo Lógico = Binario Operador de Asignación Estructuras Condicionales if - if - else Esta es una construcción if su sintaxis es: if (expresión) instrucción(es); Si la expresión es no 0 es verdadera se ejecuta el cuerpo del laso if (instrucciones), de lo contrario el control salta a la instrucción siguiente. En una proposición if la expresión suele ser de relación, de igualdad o de lógica. Cuando el cuerpo del if está formado por una sola instrucción puede o no ir entre llaves, si existe más de una instrucción entonces si lleva obligatoriamente las llaves ( {} ). En ocasiones se desearía indicar acciones alternativas que se realizan cuando la condición del if no se cumple. En estos caso la estructura if también requerirá una alternativa else la misma que se asocia con el if más cercano. El Operador Condicional C ofrece una forma abreviada de expresar la sentencia if - else se denomina expresión condicional y emplea el operador condicional ( ? : ), la sintaxis es: variable=(test)? valor si expresión es verdadera : valor si expresión es falsa ejemplo: if (y<0) x=-y x = (y < 0) ? -y : y; else x=y; switch La instrucción de selección o conmutación de clave switch es una instrucción de selección múltiple que permite efectuar un desvío directo hacia las acciones según el resultado obtenido, ésta expresión al ser evaluada debe proporcionar como resultado un valor entero. La sintaxis es: Switch (expresión) { case 'constante1' : instrucciones; break; case 'constante2' : instrucciones; break; case 'constante3' : instrucciones; break; default : instrucciones; break; } La estructura switch comienza con la palabra reservada switch seguida de una expresión entre paréntesis. Luego de esto vienen las etiquetas de selección a través de ña palabra reservada case ésta palabra debe tener como argumento obligatoriamente constantes enteras sea bajo forma numérica o simbólica. En varios de los casos puede hacerse referencia a una misma acción para ello se disponen en secuencia y la última cláusula case es la que hará referencia a la secuencia de instrucciones asociada. Por lo común la ultima instrucción antes de la siguiente etiqueta es la instrucción break, ésta palabra reservada provoca el abandono de la estructura switch. Si no existe una proposición break la ejecución continua con las instrucciones de la siguiente etiqueta. La ausencia de instrucciones break es una causa de error frecuente en un switch. Por último puede haber una instrucción etiquetada como default y representa el caso en el que el valor de la expresión no coincida con ningún valor de las etiquetas utilizadas. No es necesario incluir la sentencia default. while Permite repetir una serie de acciones mientras se verifica una condición la sintaxis es: while (expresión) instrucciones instrucción siguiente; Primero se evalúa la expresión si ésta es falsa el control pasa directamente a la instrucción siguiente, pero si es verdadera entonces se ejecuta el cuerpo del laso while devolviendo el control al principio del mismo. Es evidente que una de las acciones internas del bucle while debe modificar en un momento dado una de las variables que intervienen en la expresión que condiciona el bucle para que dicha condición se haga falsa y se pueda abandonar el bucle. Si el cuerpo del laso esta formada por una sola instrucción no necesita las llaves que delimitan el cuerpo del bucle, pero si tiene más de una instrucción entonces las llaves son obligatorias. do while El laso do es muy similar al laso while solo que while verifica el test y luego ejecuta el cuerpo del laso en cambio el laso do ejecuta el cuerpo del laso y luego verifica el test. Por lo tanto el cuerpo del laso do se ejecuta al menos una vez, su sintaxis es: do { instruccion(es); } while (test) for for (expresión1;expresión2;expresión3) La primera expresión es la que establece la condición inicial para el bucle. No se ejecuta más que una sola vez y generalmente es una instrucción de asignación. Expresión2 normalmente es una expresión de relación cuyo resultado verdadero o falso determina respectivamente la continuación o terminación del bucle. La expresión3 actúa la mayor parte del tiempo sobre el valor de la condición. El for en C es mucho más general que en otros lenguajes no existe ninguna restricción sobre las expresiones que pueden intervenir en una instrucción de clave for. Los tres campos pueden no tener ningún vínculo entre ellos. Cada una de las componentes del for es facultativa u optativa lo que permite efectuar las construcciones siguientes. for (; ; ) /* Laso infinito */ for (expresión1;expresión2;) /* No tiene expresión3 */ for (;expresión2;) /* Es equivalente a un while */ for (;expresión2;expresión3) /* No existe valor inicial */ Funciones Lenguaje C está basado en el concepto de bloques construidos que se denominan funciones. Definimos entonces como un programa en C como una colección de una o más funciones y que generalmente realizan una sola tarea. Cada función tiene un nombre y una lista de argumentos que recibe la función. De manera general se puede dar a la función el nombre que se quiera con la excepción de main que se reserva en C para indicar el inicio de un programa, los nombres para las funciones utilizan las mismas reglas de los identificadores. Ejemplo: main() { datos_menu(); while (datos) { ing_datos(); ver_datos(); } } Declaración de la Función Sintaxis tipo_retorno nombre (parámetros formales); tipo_retorno:- Se refiere al tipo de dato que retorna la función, el mismo que puede ser int, char, etc. En caso de que la función no retorne ningún valor se utiliza el especificador de tipo void. Si se omite el tipo de retorno C asume que se trata de un int. parámetros formales:- La sintaxis utilizada es en base a declaraciones de prototipos de funciones. En ella se incluye información acerca de los tipos de datos que la función espera como argumento. La declaración de la función se hace antes de la función main, esto es lo recomendable con el fin de poder visualizar todas las declaraciones de las funciones a utilizar; antes de main aunque es posible ubicarla después de main. Ejemplo: int centrar(. . . .); main() main() { { int centrar(. . . ); . . . . . . . . } } Si la función no tiene parámetros formales se debe poner void ejemplo: void líneas(void) La palabra void al inicio de la función indica que no retorna ningún valor. La sintaxis es de acuerdo al prototipo de funciones ejemplo: Definición double división (double valor1, int valor) Esta definición es idéntica a la declaración pero no lleva punto y coma al final ( ; ) al final de la misma, éste es el formato sin prototipo, los identificadores de los parámetros formales se incluyen en la cabecera de la función los mismos que deben ir entre paréntesis ( ); estos parámetros se definen entre la cabecera y el cuerpo de la función. En el formato con prototipo los identificadores de los parámetros y sus tipos se incluyen en la cabecera de la función. No hay declaración separada para los parámetros. double división (valor1, valor2) double valor1; /* Declaración sin prototipo */ int valor2; { /* Cuerpo de la función */ . . . . } double división1 (double valor1, int valor2); /*Declaración del prototipo */ main() { /* Cuerpo del programa principal */ } double división1 (double valor1, int valor2); { /* Cuerpo de la función */ /* Función con prototipo */ . . . . } Proposición return Esta concluye la ejecución de la función y devuelve el control al medio que lo llamo si la proposición return contiene una expresión el valor de ésta también regresa al medio que hizo la llamada; además el valor retornado se convertirá al tipo dado por el especificador de tipo de la función. double g (char a, char b) { int y; . . . . return(y); } En una función puede haber cero o más proposiciones return; si no hay ninguna, el control vuelve al medio que hizo la llamada al llegar a la llave que delimita el cuerpo de la función. double valor_abs(double x) { if (x>0.0) return(x); else return(-x); } Clase de Almacenamiento de Variables y su Alcance Todas la variables y funciones en C poseen dos atributos que son su clase de almacenamiento y el tipo. Existen cuatro clases de almacenamiento que son: automáticas, externas, registros y estáticas. Variables Automáticas auto Las variables declaradas dentro de la definición de una función son automáticas por omisión y esto significa que son alojadas dinámicamente en un sitio especial de la memoria en el momento en que se llama a dicha función y este sitio que ocupa queda liberado cuando se vuelve a la función que hizo la llamada. Variables Externas extern Cuando una variable se define fuera de una función se dice que es externa la misma que también puede ser declarada dentro de la función que la emplea utilizando la palabra clave extern. Se puede omitir por completo el grupo de declaraciones extern si las definiciones originadas se encuentran en el mismo archivo y antes de la función que las utiliza. Es a veces útil y recomendable el agrupar las variables en un archivo único que luego pueda ser incluido mediante el comando include. Las variables externas son generalmente ubicadas después de los define y de los include. Variables Registro register Si una variable local debe utilizarse intensamente el programador tiene la posibilidad mediante la palabra clave register, de situar esa variable en uno de los registros internos de la máquina. Los tipos de objetos admitidos en los registros son aquellos cuyo tamaño es inferior o igual al tamaño de la palabra interna usada por el microprocesador. De manera general para ésta clase de almacenamiento no se dispone de más de tres registros. La clase de almacenamiento register es solamente una petición no una orden. Si el compilador no puede conceder la solicitud de almacenamiento en registro la variable pasa a ser automática. Variables Estáticas static Estas tienen su propio sitio reservado en la memoria en el momento de la compilación del programa. El alojamiento de una variable estática es pues permanente en la memoria y su contenido puede variar a lo largo de la ejecución del programa. La declaración de una variable estática se hace explícitamente con la palabra reservada statics que procede a la clave especificadora del tipo de variable. Clase de Almacenamiento typedef Este indicador se diferencia de los demás indicadores de clase de almacenamiento en que no especifica explícitamente una clase de almacenamiento. En lugar de hacer esto se agrupa con los indicadores de clase de almacenamiento sobre todo por conveniencia sintáctica según lo recomienda ANSI. Cuando se crea un typedef el compilador no reserva ningún espacio memoria; pero en cambio nos permite proporcionar un sinónimo para un identificador de tipo ya existente por ejemplo: typedef double DISTANCIA; typedef int NUMERO; DISTANCIA millas; static DISTANCIA kilómetro; Estas definiciones se incluyen después del número include (# include). A menudo se emplean letras mayúsculas para estas definiciones con el fin de recordar al usuario que el nombre del tipo es en realidad una abreviatura simbólica. Nota sobre paso de Argumentos en Funciones * Una función se ejecuta invocándola. Esto se logra escribiendo su nombre y una lista adecuada de argumentos (si los posee). Si tiene argumentos estos deben coincidir en número y tipo con los parámetro listados en el prototipo de la función. * Todos los argumentos pasan con una llamada por valor. Esto significa que se evalúa cada argumento y su valor se utiliza en forma local en lugar de parámetro formal. De ésta manera si una variable pasa a una función, el valor almacenado de esa variable no cambiara en el medio que la llama. Arreglos Multidimensionales En C se puede manejar arreglos de 2, 3 o más dimensiones siendo los más comunes los de 2 dimensiones representadas por filas y columnas ejemplos: static int valor [3,3] = {1,2,3,4,5,6,7,8,9}; Cuando se envía un arreglo a una función es obligatorio poner el número de columnas del arreglo ejemplo: sorteo (int lista[ ][3]) Punteros Un puntero en C es una variable cuyo contenido es una dirección de memoria. Normalmente esta dirección es una posición de otra variable en la memoria. Los punteros son muy usados en C por las siguientes razones: * Accesar y manipular un string. * Gran velocidad de ejecución usando punteros, al trabajar con grandes bloques de datos. * El uso de punteros puede dar como resultado códigos más compactos y eficientes. * Indirectamente retorna más de un valor desde una función. Variables tipo puntero Si una variable va ha contener un puntero entonces se debe declarar como tal. La declaración consiste en un tipo base, el carácter asterisco (*) y el nombre de la variable ejemplo: sintaxis: tipo * identificador; char *text; int num, *conta; El asterisco antes de conta le dice al compilador que ésta variable es un puntero y como es declarado de tipo int el compilador sabrá que la variable contendrá la dirección de una variable entera. Operadores & y * * &:- Es un operador monario (requiere de un solo operando) éste operador devuelve la dirección de memoria de su operando. * *:- Es el complemento del operador & (ampersan) y devuelve el valor de la variable situada en la dirección que contiene, ejemplos: num = 5 conta = # /*asigna la dirección de num */ destino = *conta /*asigna el valor de num a destino */ Utilización de punteros para comunicación entre funciones (Parámetros por referencia) Cuando se pasa un puntero hacia una función se pasa una copia de su valor, pero el valor que se pasa en este caso es una dirección. En general se pueden enviar a las funciones 2 tipos de información acerca de una variable los tipos de envíos son: * Llamada por valor. * Llamada por referencia. Llamada por valor Se utiliza si la función necesita el valor para hacer algún cálculo o alguna acción ejemplo: función (valor); /* llamada a la función */ función (tipo variable) /* definición */ Llamada por referencia Se utiliza si la función necesita alterar las variables del programa de llamada ejemplo: función (&variable,&...); /* llamada a la función */ función (int *num1, int *num2) /* definición */ Punteros y Arreglos En C existe una estrecha relación entre punteros y arreglos suficientemente estrechas para que se los trate simultáneamente. Cualquier operación que se pueda realizar mediante la indexación de un arreglo se puede realizar en base a punteros. En efecto el nombre del arreglo representa la dirección del elemento 0 del arreglo. La versión con punteros será más eficiente y por consiguiente más rápida. Lenguaje C permite dos métodos de acceso a los elementos de un arreglo. Este hecho es importante por que la aritmética de punteros es más rápida que los índices de arreglos. Como la velocidad es un factor importante en la programación el uso de punteros para acceder a los elementos de un arreglo es común en C. Indexando un puntero En C se puede indexar un puntero como si fuera un arreglo. Esto indica nuevamente la estrecha relación entre punteros y arreglos, ejemplo: int num[20], *pa; /* si tenemos esto vemos que num[i]==*(pa+i) es idéntico a pa=num; pa=num */ Al aplicar el operador & a las dos partes de esta equivalencia &num[i]==num+i, se deduce que &num[i] es lo mismo que num+i donde num+i es la dirección del i-ésimo elemento de num. Por otra parte si pa es un apuntador en las expresiones puede presentarse con un sub índice pa[i]==*(pa+i). Entonces tenemos que en cualquier expresión en la que se tenga un arreglo y un sub índice se puede escribir como un puntero y un desplazamiento y viceversa. Sin embargo existe una diferencia entre el nombre de un arreglo y un puntero. Un apuntador es una variable por lo que operaciones como: pa=num; pa++; /* son operaciones correctas. Pero el nombre de un arreglo es una dirección o puntero fijo de ahí que expresiones como las siguientes son totalmente ilegales */ ++num; num+=2; ARREGLOS COMO ARGUMENTOS DE FUNCIONES Y PUNTEROS COMO ARGUMENTOS Dado que el nombre de un arreglo es en realidad una referencia a la dirección de comienzo de un arreglo, el paso de un arreglo comoargumento de una función es siempre un paso por referencia. Por lo tanto los cambios que se hagan dentro de la función afectarán el arreglo original. Como el compilador no tiene que reservar espacio de memoria para almacenar una copia del arreglo no es necesario especificar el tamaño del arreglo. int strlen(char *pa) { int cont=0; while(*pa++) cont++; return(cont); } Punteros a Punteros Como se ha visto un puntero es una variable que almacena direcciones y esta dirección es usualmente la localización en memoria de otra variable. Sin embargo en C ésta otra variable puede ser un puntero. Este no es el límite de la relación puntero si no que esto se puede extender de punteros a punteros, etc. Pero esto nos llevaría a un código muy complicado de entender. int **pp, *p, num=3; num imprime 3 p=# *p imprime 3 pp=&p; **pp imprime 3 *pp 629