ESCUELA POLITÉCNICA SUPERIOR INFORMÁTICA. CURSO 2015-2016 PRÁCTICA 1: INTRODUCCIÓN A LA PROGRAMACIÓN EN C PRERREQUISITOS • • Para el correcto desarrollo de esta práctica, el alumno ANTES DE LA SESIÓN DE PRÁCTICA, debe leer con atención y comprender el contenido de la parte teórica de la misma. Además, se recomienda que descargue, instale e intente habituarse al entorno de programación que se utilizará durante todo el curso: C-Free (tiene disponible en enseñanza virtual los archivos necesarios para instalar el entorno en su ordenador). OBJETIVOS Esta práctica tiene como objetivo la iniciación en la programación en lenguaje C: • Se introduce el concepto de programa y programación. • Se presentan los primeros conceptos del lenguaje C: variables, sus tipos y la instrucción de asignación. • Se describe el entorno de programación C-Free 4.0 que se usará en el presente curso académico. Al finalizar esta práctica, el alumno debe ser capaz de: • Usar correctamente el entorno de programación. • Crear nuevos ficheros de código fuente en C, abrir ficheros existentes, editar e identificar código correctamente, compilar, identificar errores de compilación y ejecutar el código compilado. • Escribir programas en C que manejen variables y observar su funcionamiento usando el depurador. PROGRAMACIÓN IMPERATIVA Los circuitos electrónicos de un computador pueden reconocer datos e instrucciones que están constituidos por conjuntos de unos y ceros. Es lo que se conoce por lenguaje máquina. El lenguaje máquina es difícil de manejar y raramente puede contener operaciones más complejas que realizar operaciones aritméticas con dos números, mover datos de una parte de la memoria a otra y saltar a una instrucción si se cumple una condición. Una forma de enfrentarse a este problema es crear un nuevo lenguaje, llamado lenguaje de programación de alto nivel, que sea más fácil de utilizar por un humano, pero no para el computador. Para que el computador pueda ejecutarlos es necesario traducirlos a su propio lenguaje máquina. Ésta es una tarea que realiza un programa especial llamado compilador. Los programas, que son secuencias de instrucciones, escritos en lenguaje de alto nivel se almacenan con una extensión determinada: para el caso del lenguaje de alto nivel que estudiamos en este curso, el lenguaje C, la extensión de los ficheros es .c. La tarea del compilador se suele descomponer en dos etapas: - En la primera etapa traduce el programa del fichero .c al lenguaje ensamblador produciendo el fichero .o. El lenguaje ensamblador es un lenguaje más próximo al del computador. - En la segunda etapa genera el programa ejecutable en lenguaje máquina, que es el fichero con extensión .exe (en sistemas operativos Windows) Como se ha descrito previamente, un programa es una secuencia de instrucciones que describe cómo ejecutar cierta tarea. Las instrucciones de los programas se ejecutan –ordinariamente- de modo secuencial, es decir, cada una a continuación de la anterior. Recientemente se está haciendo un gran esfuerzo en desarrollar programas paralelos, es decir, programas que se pueden ejecutar simultáneamente en varios procesadores. La programación paralela es mucho más complicada que la secuencial y no se hará referencia a ella en este curso. PROGRAMACIÓN EN LENGUAJE C Función main Un programa C suele estar dividido en funciones (que se llaman procedimientos o rutinas en otros lenguajes). Cada función ejecuta una parte de un programa, pudiendo recibir unos valores (de entrada) y pudiendo devolver otro (de salida). El programador puede hacer cuantas funciones necesite y darles los nombres que quiera, pero siempre debe haber una función main (en inglés significa “principal”). En las primeras prácticas ÚNICAMENTE trabajaremos con esta función principal. La apariencia de la función main es un bloque encerrado entre llaves donde habrá comentarios e instrucciones: main () { // Esto es un comentario y se ignora hasta el final de la linea /* Esto también es un comentario y no se traduce a código máquina */ lista de instrucciones; } 1 Todo lo que hay entre las llaves { ... } es el cuerpo de la función. El cuerpo está formado por una lista de instrucciones, donde cada instrucción termina con el carácter ‘;’. Las instrucciones se procesan de forma secuencial. En esta práctica se trabajará con programas secuenciales. En prácticas posteriores veremos otras estructuras de control para los programas. Variables Las variables nos permiten almacenar datos. Estos datos pueden ser de distinto tipo o formato numérico y de distinto tamaño (en bits o bytes). Hay diversos tipos de variables en C: enteras, caracteres, flotantes, vectores... Es necesario declararlas al principio (después de main () { ...) con el objetivo de indicar al compilador que reserve un espacio determinado en memoria. La longitud en bytes de este espacio será función del tipo de variable. La sintaxis de la declaración de variables es: tipo lista_de_nombres; - tipo: indica el tipo de dato que se almacena en la variable (entero, flotante, carácter ASCII, ...). - lista_de_nombres: uno o más nombres de variables separados por comas. Existen una serie de normas para los nombres de variables: pueden contener el carácter guion bajo (_), dígitos numéricos y letras del código ASCII estándar (no pueden contener eñes ni caracteres con tildes). No pueden comenzar por un dígito numérico. Se diferencian mayúsculas y minúsculas. Ejemplos: - Nombres correctos: importe3, hora_comienzo, dia, Dia, DIA - Nombres incorrectos: día, 3importe, año Tipos básicos de variables: Nomenclatura int unsigned int short unsigned short long unsigned long float Tipo Entero 32 bits Natural de 32 bits Entero de 16 bits Natural de 16 bits Entero de 64 bits Natural de 64 bits Real 32 bits Almacenamiento Complemento a 2 Binario natural Complemento a 2 Binario natural Complemento a 2 Binario Natural Punto Flotante de simple precisión Rango de valores [-231, 231-1] [0, 232-1] [-215, 215-1] [0, 216-1] [-263, 263-1] [0, 264-1] [1.18-38, 3.4038] (precisión: 7 dígitos) double Real 64 bits Punto Flotante de doble precisión [2.23-308, 1.79308] (precisión: 15 dígitos) long double Real 80 bits Punto Flotante precisión extendida [3.37-4932, 1.184932] (precisión: 18 dígitos) char Entero de 8 bits (carácter*) Complemento a 2 [-128, 127] unsigned char Natural de 8 bits (carácter*) Binario Natural [0, 255] *El tipo char suele utilizarse para representar el valor numérico de un carácter de la tabla ASCII, debido al rango de valores que abarca. Esto se verá en la práctica de “Cadenas de caracteres”. Constantes Una constante es un dato con un nombre, un tipo y un valor asociado que no puede modificarse una vez definido. La sintaxis de la declaración de una constante es: #define NOMBRE valor_cte Nótese que detrás del valor constante no hay un punto y coma. Las constantes se definen al principio del programa, antes de la función main(). No se dice explícitamente de qué tipo es la constante ya que el compilador lo reconoce por el aspecto de valor_cte. Instrucción de asignación Instrucción que sirve para almacenar un dato en una variable. Su sintaxis es: Nombre_de_variable = expresión ; - nombre_de_variable: Nombre de variable que ha de estar declarada previamente - operador de asignación (=): Indica que el valor calculado en la expresión debe ser almacenado en Nombre_de_variable. - expresión: Indica cómo se calcula el valor a almacenar en la variable. Nótese que el sentido de la asignación es de derecha a izquierda 2 Ejemplo de programa con los conceptos explicados: #define PI 3.14 main() { double perimetro; double radio = 3; // Declaración de variable con inicialización perimetro = 2 * PI * radio; area = PI * radio * radio ; //Fallo porque la variable área no está declarada } PRIMERA PARTE: Creación, edición, compilación y ejecución de un programa C 1. Haz clic en este icono, o pulsa Ctrl+N, para crear un nuevo fichero de código C 2. En la ventana principal del editor, teclea estas líneas de código. ¡No pongas los números de línea, eso ya lo hace solo! 3. Haz clic en este icono, o pulsa Ctrl+S, para grabar el código fuente al disco duro. 4. Elige un sitio en el disco duro para guardar tu programa, y ponle un nombre. ¡No olvides el .c al final del nombre! 5. Haz clic en este icono, o pulsa la tecla F5, para compilar y ejecutar el programa que has hecho. 6. Comprueba el resultado. Va a fallar porque la variable área no está declarada 7. Haz los cambios necesarios en el código fuente, para corregir los errores. (declara la variables area). 8. Guarda tu programa con los cambios corregidos, compílalo y ejecútalo. ¡Ya funciona! 3 SEGUNDA PARTE: Depuración de programas C-Free, como la mayoría de los entornos de programación, tiene dos formas de compilar un programa: modo “Debug” y modo “Release”. El modo “Debug” se usa cuando se está comenzando a escribir el programa y es muy probable que éste tenga errores. Nos permite ejecutar paso a paso nuestros programas e ir viendo en cada uno de ellos qué es lo que ocurre, viendo si en algún momento el resultado no es el que debiera. De esta forma podemos detectar errores lógicos o de funcionamiento. El entorno nos va mostrando el contenido de las variables, resultado de operaciones, rumbos que va tomando la ejecución, etc. La depuración sólo puede realizarse cuando el programa compila con 0 errores (da igual el número de “warnings” que tenga), ya que la depuración se realiza durante la ejecución y para ello el compilador tiene que haber sido capaz de generar un fichero ejecutable. Para depurar un programa, primero hay que asegurarse de que el compilador está en modo “Debug”. Esto se indica con una D entre corchetes en el cuadro de selección del compilador. En la imagen siguiente por ejemplo, aparece una R (de “Release”), así que tenemos que cambiar a modo “Debug” usando el botón indicado. De esta forma ya podemos “depurar” el programa que estemos editando en ese momento. Para ello, en lugar lo que hicimos anteriormente, lanzamos la depuración usando la opción mostrada o pulsando F9. TAREA: Escribir el siguiente código en un nuevo fichero y lanzar la depuración: #include <stdio.h> main() { int a; a = 10; a = a + 5; printf("Hola mundo\n"); } Cómo veis no ocurre nada diferente. La razón es que para que la ejecución vaya paso a paso debemos interrumpirla en algún punto de nuestro programa: Son los denominados puntos de ruptura o Breakpoints. En C-Free los puntos de ruptura se establecen o se anulan haciendo un simple clic con el botón izquierdo del ratón a la izquierda del número de línea en la que se quiere colocar el punto de ruptura. 4 Si hacemos eso, aparece un punto rojo a la izquierda del número de línea, y toda la línea se marca para destacarla. Si colocamos un punto de ruptura como se indica en la imagen y ejecutamos en modo de depuración nuestro programa veremos que se detiene en dicho punto. En la parte inferior de la ventana podéis ver un cuadro en el que aparece nuestra variable y el valor que contiene. Podéis comprobar que el valor de '10' no ha sido asignado. Eso es porque la línea en la que hemos puesto el breakpoint AUN NO SE HA EJECUTADO. El programa, por lo tanto, está ahora interrumpido, a la espera de una orden para seguir: tenemos varias opciones, todas ellas resumidas en un pequeño panel que aparece solamente cuando se está en medio de una sesión de depuración. El significado de los iconos que usaremos es el siguiente: Termina la sesión de depuración y vuelve al editor. Esta opción termina bruscamente el programa. Cuando se identifica un error durante la depuración, es necesario cerrar la sesión antes de realizar cambios en el código fuente. Si el programa se bloquea o deja de responder durante la depuración, este botón puede ayudarnos a retomar el control terminándolo prematuramente. STEP INTO. Tiene el mismo efecto que pulsar la tecla F7 durante la depuración. Ejecuta la línea de código que está marcada en este momento en el depurador y pasa a la siguiente. Si la línea de código es una llamada a una función (lo veremos en su momento), la siguiente línea que se marca es la primera línea de código de la función llamada. 5 STEP OVER. Tiene el mismo efecto que pulsar la tecla F8 durante la depuración. Ejecuta la línea de código que está marcada en este momento en el depurador y pasa a la siguiente. Si la línea de código es una llamada a una función (lo veremos en su momento), se ejecuta a velocidad normal, parándose de nuevo la ejecución en la línea siguiente a aquella que contiene la llamada a la función. Esta opción por tanto, trata a las funciones como instrucciones simples, y debe usarse para evitar “entrar” en funciones que ya sabemos que funcionan correctamente. STEP OUT. Mismo efecto que pulsar Mayúsculas-F7. Usando esta opción desde el contexto de una función cualquiera, ejecuta dicha función hasta el final, a velocidad normal, parándose en la siguiente línea a aquella que tenía la llamada a la función. Puede usarse para ejecutar funciones a velocidad normal cuando hemos entrado en ellas por error con F7 (cuando lo que queríamos era pulsar F8) o bien cuando la inspección visual determina que la función opera correctamente y no tiene sentido seguir ejecutándola paso a paso. RUNS TO CURSOR. Mismo efecto que pulsar Ctrl-F8. Permite “saltar” una parte del código que no nos interesa ejecutar paso a paso. Simplemente movemos el cursor de escritura a la línea en la que queremos volver a parar la ejecución y pulsamos esta opción: el programa se ejecutará a velocidad normal hasta llegar a la línea donde está el cursor, entonces se parará. Por tanto, si en este momento pulsamos F7 ó F8, la línea con el breakpoint se ejecutará, y veremos como el valor de la variable a ha cambiado. La línea resaltada pasa a ser ahora la siguiente. Si volvemos a pulsar F8, el contenido de la variable vuelve a cambiar. Si seguimos pulsando F7 ó F8 seguiremos ejecutando el programa hasta el final. Si no queremos ir paso a paso podemos ejecutar F9 para que el programa siga ejecutándose normalmente de forma automática, hasta que el entorno encuentre otro breakpoint. EJERCICIOS 1. 2. 3. 4. Crear al menos una variable de cada uno de los tipos explicados, asignarles diversos valores y comprobar mediante el depurador la asignación de los mismos. Crear una constante llamada PI cuyo valor sea el del número pi. Use tantos decimales como le sea posible (20 decimales está bien). Asigne el valor de PI a las variables de distinto tipo creadas en el punto anterior. ¿Qué limitaciones observa en el valor almacenado según el tipo de variable? Intentar asignar un valor a PI dentro de nuestra función main. ¿Es posible? Escribir en un nuevo programa el siguiente código: #include <stdio.h> main() { int a, b; a = 10; b = 20; b = a; a = 40; printf ("Hola mundo\n"); } ¿Cambia el valor de b tras asignarle un nuevo valor a la variable a (a=40)? 6 Expresiones aritméticas Como ya hemos visto en el epígrafe anterior, podemos asignar valores a las variables mediante una sentencia de asignación. La asignación en lenguaje C tiene la siguiente sintaxis: variable = expresión; Tanto las variables como las operaciones de suma que pusimos en el lado derecho de la asignación constituyen dos tipos de expresiones aritméticas básicas del lenguaje C. El resto de expresiones que podemos utilizar las mostramos en el siguiente cuadro: EXPRESIÓN ARITMÉTICA Resultado de la expresión Constante El resultado de la expresión es el valor de la constante Nombre_de_Variable El resultado de la expresión es el valor de la variable Expresión1 + Expresión2 Suma Expresión1 y Expresión2, que son, a su vez, expresiones más pequeñas Expresión1 – Expresión2 Resta Expresión2 a Expresión1 Expresión1 * Expresión2 Multiplica Expresión1 por Expresión2 Expresión1 / Expresión2 Divide Expresión1 entre Expresión2 - Expresión El resultado es la Expresión cambiada de signo (operador unario) Expresión1 % Expresión2 Operador módulo: Devuelve el resto de la división entera Expresión1 entre Expresión2 variable -- Auto-Decremento: Devuelve el valor de la variable; después resta 1 al valor de la variable variable ++ Auto-Incremento: Devuelve el valor de la variable; después suma 1 al valor de la variable. Veamos algunos ejemplos de expresiones incluyendo algunos errores. Vamos a suponer que en alguna parte de nuestro programa hemos definido la constante PI y las variables radio y perímetro: #define PI 3.141592 double radio = 3000000; double perimetro; EXPRESIÓN ARITMÉTICA Constante EJEMPLOS 3.32 PI ‘A’ perimetro area Radio Expresión1 + Expresión2 area+perimetro 3+radio+34 ‘A’+3 Expresión1 – Expresión2 perimetro-PI ‘A’-’a’+3 3+4-5.6-3 Expresión1 * Expresión2 2*PI*radio PI*radio*radio PI*(radio+2) Expresión1 / Expresión2 perimetro/PI 3/2 resultado:1 3.0/2.0 resultado: 1.5 -2 -PI -sqrt(radio) 3%2 resultado:1 7%4 resultado:3 7.0%5 ERROR variable -- radio-- (area+3)--ERROR PI-- ERROR variable ++ area++ sqrt(34)++ ERROR PI++ ERROR Nombre_de_Variable - Expresión Expresión1 % Expresión2 7 Como vemos en las tablas, una expresión puede contener como operando otras expresiones, de esta forma podemos escribir algo como: a = 12*x*x + 4*x + 2; La forma en la que se evalúan o interpretan las expresiones siguen unas reglas similares a las de la aritmética de las matemáticas. Primero se evalúan las de mayor precedencia y al final la de menor. La precedencia de las operaciones aritméticas es: Mayor Menor ++, -- - unario *, / y % +, - En el caso de que haya varios operadores de la misma precedencia, se evalúan de izquierda a derecha. Con los paréntesis podemos cambiar la precedencia. a = 12*x*(x + 4)*x + 2; //El paréntesis se evalúa primero Ejercicio 5. Dado el siguiente código: main() { float result; result = 3 / 2; result = 3 / 2.0; result = 3.0 + 3/2; } Realizar las operaciones en papel y comprobar el valor calculado por el ordenador mediante la depuración. ¿Por qué no realiza la operación como esperábamos? Solución: Conversión implícita de datos result = 3 / 2; int: 1 float: 1.0 main() { float result; result = 3 / 2; result = 3 / 2.0; result = 3.0 + 3/2; } result = 3 / 2.0; float: 3.0 float: 1.5 result = 3.0 + 3/2; int: 1 float: 1.0 float: 4.0 8