Presentación 2 - Universidad Nacional Del Sur

Preview only show first 6 pages with water mark for full document please download

Transcript

El Lenguaje de Programación C (Pt. 2) Organización de Computadoras Depto. Cs. e Ing. de la Comp. Universidad Nacional del Sur Copyright Copyright © 2011-2015 A. G. Stankevicius Se asegura la libertad para copiar, distribuir y modificar este documento de acuerdo a los términos de la GNU Free Documentation License, Versión 1.2 o cualquiera posterior publicada por la Free Software Foundation, sin secciones invariantes ni textos de cubierta delantera o trasera. Una copia de esta licencia está siempre disponible en la página http://www.gnu.org/copyleft/fdl.html. Organización de Computadoras 2 Contenidos Introducción al paradigma imperativo. Sentencias de control. Entrada y salida estándar. Pasaje de parámetros. Tipos de datos estructurados. Gestión de la memoria dinámica. Estructuras de datos dinámicas. Gestión de archivos. Organización de Computadoras 3 Punteros Las variables declaradas de tipo puntero representan direcciones de memoria. En todo momento se debe tener clara la diferencia entre una variable de tipo puntero y el dato alojado en la dirección por él apuntada. Los punteros se declaran con un asterisco delante del identificador de la variable: int *px, y; /* px es un puntero e y es un entero */ Organización de Computadoras 4 Operaciones sobre punteros En relación a los punteros, existen dos operadores fundamentales: Desreferenciamiento: si px es un puntero, la expresión *px denota al contenido apuntado por el puntero (es decir, el valor almacenado en la dirección referenciada por el puntero). Enreferenciamiento: si x es una variable, la expresión &x denota a la dirección de memoria donde se encuentra almacenado el valor de x. Organización de Computadoras 5 Operaciones sobre punteros int x = 10, y = 5; int *px; px = &x; *px = &y; 1000: 10 int x 1001: 5 int y 1002: ? int *px 1000: 1001 int x 1001: 5 int y 1002: 1000 int *px px = 1234; // ¿tendrá sentido? Organización de Computadoras 6 Aritmética sobre punteros Los punteros se comportan como cualquier otra tipo elemental, es decir, admiten toda la gama de operaciones aritméticas. La clave para entender esto radica en que los punteros almacenan en esencia direcciones de memoria, en otras palabras, enteros positivos. Las operaciones aritméticas se comportan de manera inteligente, teniendo en cuenta el tipo base al cual apuntan los punteros. Organización de Computadoras 7 Aritmética sobre punteros int vector[5] = {10, 20, 30, 40, 50}; int *pv = vector; // aquí, ¿qué denota *pv? *pv = 15; // ¿y aquí? *(pv + 3) += 5; char string[250] = {'M', 'a', 'r', 'í', 'a'}; char *ps = &string[2]; *(ps + 2) = 'o'; // ¿en qué cambió string? Organización de Computadoras 8 Pasaje por referencia Los punteros permiten simular un pasaje de parámetros por referencia: int reset(int *a, int b) { *a = 0; b = 0; // *a y b son reseteados. } void main() { int x = 1, y = 1; reset(&x, y); // x vale 0, pero y vale 1. } Organización de Computadoras 9 Pasaje por referencia int a = 10, b = 20; void cambiar(int p, int *q) { p += 2; *q += p; } int main () { cambiar(a, &b); printf(“a vale %i y b vale %i”, a, b); } Organización de Computadoras 10 Punteros genéricos Para declarar un puntero genérico, el cual sea capaz de apuntar datos de distintos tipos, se debe apelar al tipo void. Este tipo solo puede figurar como tipo base de un punteros, es decir, declarar a una variable común de tipo void carece de sentido. void *px; // válido. void x; // inválido. Organización de Computadoras 11 Definición de arreglos La definición de arreglos y matrices se realiza indicando las dimensiones entre corchetes: // un arreglo de 25 enteros int a[25] // una matriz de reales de 4x4 float vx[4][4]; // un arreglo de carácteres char string[250]; Organización de Computadoras 12 Indexado de arreglos A diferencia de otros lenguajes, en C la primer componente de un arreglo ocupa la posición 0: int i, vector[10]; for (i = 0; i < 10; i++) { // ¡empieza en 0 vector[i] = i; // y termina en 9! } Organización de Computadoras 13 Inicialización de arreglos Los arreglos en C pueden inicializarse al mismo tiempo que son definidos: int vector[5] = {0, 1, 2, 3, 4}; int matrix[2][3] = { {1, 2, 3}, {4, 5, 6} }; char cadena1[250] = {'H', 'o', 'l', 'a', '!'}; char cadena2[250] = “Hola!”; // ¿será cadena1 == cadena2? Organización de Computadoras 14 Cadenas de caracteres En este lenguaje las cadenas de caracteres son simples arreglos de caracteres que terminan en un caracter nulo ('\0'): char cadena1[250] = {'H', 'o', 'l', 'a', '!', '\0'}; char cadena2[250] = “Hola!”; /* ahora cadena1 y cadena2 resultan equivalentes. */ Organización de Computadoras 15 Asignación de arreglos El operador de asignación no puede utilizarse con arreglos ni con cadenas de caracteres. int a[5], b[5]; a[0] = 1; a[2] = 3; b = a; // ¡Error! No confundir con la inicialización de arreglos, la cual si está permitida: int c[5] = {0, 3, 2, 0, 0}; Organización de Computadoras 16 Funciones de librería C dispone de una gran cantidad de funciones de librería sobre cadenas de caracteres (presentes en la librería strings): strcat(): para concatenar strings. strcpy(): para copiar strings. strcmp(): para comparar dos strings lexicográficamente. strlen(): para determinar la longitud de un string. strstr(): para buscar un string en otro. Organización de Computadoras 17 Entrada estándar de strings El modificador “%s” permite el ingreso de cadenas de caracteres por parte del usuario: char str[25]; scanf(“%s”, str); // ¿¿no falta algo?? /* lee hasta encontrar un separador (blancos, fin de líneas, etc.) */ Para leer un string hasta el fin de línea se debe usar el modificador “%[^\n]”: scanf(“%[^\n]”, str); // lee hasta el '\n' Organización de Computadoras 18 Arreglos y punteros Una variable de tipo arreglo se asimila a un puntero a su primer componente. En consecuencia, puede ser utilizada como tal: int *pb, *pc; int a[5] = {10,20,30,40,50}; pb = a; *pb = 15; a: 15 10 20 30 45 40 50 pc = &a[3]; *pc += 5; pb: pc: Organización de Computadoras 19 Pasaje de arreglos Al pasar un arreglo como argumento, se debe dejar su primera dimensión sin especificar: void times(int vector[], int matrix[][4]) { ... } int main() { int a[12], b[4][4]; times(a, b); } Organización de Computadoras 20 Pasaje de arreglos Cabe señalar que, producto de esta omisión, no resulta del todo trivial determinar la longitud de los arreglos recibidos como argumento. Una alternativa relativamente directa consiste en agregar un parámetro adicional que informe el valor de la dimensión faltante. Otra posibilidad es usar alguna variable global para comunicar ese dato. Una tercer propuesta es acordar que el arreglo tenga un tamaño predeterminado, haciendo uso por caso de una constante para denotar ese tamaño. Organización de Computadoras 21 Usando un parámetro extra void mostrar(int vx[], int size) { int i; for (i = 0; i < size; i++) printf(“Elto nro. %i = %i\n”, i, vx[i]); } int main() { int a[5] = {10, 20, 30, 40, 50}; mostrar(a,5); } Organización de Computadoras 22 Usando una variable global int size = 5; void mostrar(int vx[]) { int i; for (i = 0, i < size, i++) printf(“Elto nro. %i = %i\n”, i, vx[i]); } int main() { int a[5] = {10, 20, 30, 40, 50}; mostrar(a); } Organización de Computadoras 23 Registros Frecuentemente hace falta manipular múltiples datos de una cierta entidad. Por caso, nombre, apellido y número de registro de un alumno. También, frecuentemente hay que retener datos de múltiple entidades. ¿Qué estructura de datos resulta más conveniente usar? ¿Múltiples arreglos, uno para cada “atributo” de las entidades? Organización de Computadoras 24 Definición de un registro Los registros son un tipo de dato estructurado compuesto de un conjunto de campos, los que son de otros tipos (básicos o complejos) y que se les asocia una etiqueta a cada uno: struct etiqueta { tipo1 campo1; tipo2 campo2; ... } variable1, variable2, ...; Organización de Computadoras 25 Definición de un registro struct persona { char nombre[20]; int edad; float altura; } profesor, alumnos[10]; struct persona unprofesor = {“Juan Perez”, 32, 1.82}; struct persona *palumno; Organización de Computadoras 26 Acceso a los campos Los campos se acceden de dos maneras: Usando el operador punto (.), si es un registro. O bien, usando el operador flecha (->), si se trata de un puntero a un registro. struct persona el, *ella, todos[20]; printf(“Nombre: %s\n”, el.nombre); todos[2].edad = 20; ella = &todos[2]; printf(“Su edad es %d\n”, ella->edad); Organización de Computadoras 27 Pasaje de registros En general, ninguna estructura de datos compleja debe ser pasada por valor . La razón es que esto implica el copiado en tiempo de ejecución de mucha información. Por ende, los registros no deben ser pasados como argumentos, al menos no de forma directa. En otras palabras, siempre conviene pasar por valor un puntero a la estructura en cuestión. En todas las arquitecturas, los punteros ocupan apenas unos bytes. Organización de Computadoras 28 Definición de enumerados Los enumerados son conjuntos de constantes numéricas definidas por el usuario. enum colores {rojo, verde, azul}; enum colores fondo, borde = verde; enum booleano { falso = 0, verdadero = 1 }; enum booleano condicion = falso; Organización de Computadoras 29 Definición de tipos de datos Para definir nuevos tipos de datos a partir de otros ya definidos se usa la sentencia typedef: typedef int booleano; typedef struct persona tPersona; typedef struct punto { int coordenadas[3]; enum colores color; } tPunto; tPunto plano[3]; Organización de Computadoras 30 Modificadores de variables La declaración de variables acepta los siguientes modificadores: static: el valor de la variable se debe conservar entre las llamadas a la función. register: la variable es almacenada, de ser posible, en un registro del procesador, en vez de hacerlo en memoria principal. volatile: la variable puede ser modificada por un proceso exterior. const: la variable no puede ser modificada, sólo inicializada. Organización de Computadoras 31 Modificadores de variables #include void count() { static int acc = 0; printf(“%d\n”, acc++); } int main() { // ¿qué valores se imprimen? count(); count(); count(); return 0; } Organización de Computadoras 32 Modificadores de variables const int max = 10; int letra(const char *text, char l) { int i, acc = 0; for (i=0; i < max && text[i]; i++) if(text[i] == l) acc++; return acc; } Organización de Computadoras 33 Declaraciones constantes Existen dos formas de declarar una variable como constante: const int x = 5; int const x = 5; Pero, al tratarse de punteros no da lo mismo: const char * origen; char * const origen; Consejo práctico: ¡leer la declaración siempre de atrás para adelante! Organización de Computadoras 34 Modificadores de funciones Las funciones también pueden ser declaradas con otros modificadores: static: esta es una restricción de enlace. Denota que sólo se puede usar dentro del archivo de código fuente actual. extern: la variable o función en cuestión será declarada pero no será definida (su definición será provista en otro archivo fuente). inline: la función debe ser expandida íntegramente al ser invocada (es decir, no se va a generar un salto a la función). Organización de Computadoras 35 Modificadores de funciones uno.c static void ... dos.c f() { extern void f(); extern void g(); } int main() { void g() { g(); f(); } f(); // ¡Error! } Organización de Computadoras 36 Memoria dinámica La declaración de toda variable reserva un espacio de tamaño previamente conocido en el registro de activación en curso. El espacio reservado para las variables de tipos de dato elementales coincide con lo retornado por la función sizeof(). El espacio reservado para las variables de tipos de dato estructurados coincide con la suma del espacio reservado para cada uno de sus componentes. Por otra parte, también es posible gestionar la reserva y liberación de memoria dinámica. Organización de Computadoras 37 Memoria dinámica C cuenta con las siguientes funciones para la gestionar la asignación de memoria dinámica: void* malloc(size_t): esta función intenta reservar la cantidad indicada de memoria dinámica. free(void*): esta función libera la porción de memoria dinámica que comienza donde se indica. void* realloc(void*, size_t): esta función reajusta al tamaño solicitado el espacio de memoria dinámica que comienza donde se indica. Organización de Computadoras 38 Memoria dinámica Al terminar de hacer uso de grandes estructuras dinámicas enlazadas se debe tener cuidado al liberar el espacio que ocupaban. La invocación a free() sólo libera el espacio reservado por el malloc() que generó el puntero pasado como argumento. Si una estructura se armó mediante múltiples invocaciones a malloc(), su espacio deberá ¡IMPORTANTE! ser retornado mediante múltiples invocaciones a free(). Organización de Computadoras 39 Memoria dinámica int *i; char *c; struct persona p; i = (int *) malloc(sizeof(int)); c = (char *) malloc(sizeof(char)); p = (struct persona *) malloc(sizeof(struct persona)); free(i); c = (char *) realloc(c, sizeof(char) * 9); Organización de Computadoras 40 Java vs. C Hasta ahora los lenguajes de programación Java y C han compartido bastantes similitudes, sobre todo a nivel de sintaxis. En este punto aparece una diferencia que estamos obligados a tener siempre en cuenta: Java se hace cargo por completo de la recuperación de la memoria dinámica asignada a un objeto cuyo uso finalizó. C, en contraste, deja esa tarea por completo en manos del programador. Organización de Computadoras 41 Listas Recordemos las principales características de la estructura de datos dinámica “lista”: Es una estructura de datos dinámica. El espacio ocupado por sus elementos es asignado a medida que se necesite. Cada elemento apunta al siguiente, determinando una relación lineal. Puede ser simple o doblemente enlazada. Permite implementar pilas y colas. Organización de Computadoras 42 Listas struct celda { tipo elemento; struct celda *sig; }; elto sig elto elto sig sig elto sig Organización de Computadoras 43 Listas Recordemos algunas de las particularidades de esta estructura de datos: Los elementos usualmente son todos del mismo tipo. Por lo general, cada celda es creada por separado invocando repetidamente a la función malloc(). Cada celda apunta a la siguiente. La última celda apunta a NULL. La lista completa se representa mediante un puntero al primer elemento. Organización de Computadoras 44 Posición directa vs. indirecta La posición de un elemento denota su ubicación dentro de la lista. Existen principalmente dos variantes para el concepto de posición: Posición directa: la posición se denota mediante un puntero a la celda conteniendo el elemento deseado. Posición indirecta: la posición se denota mediante un puntero a una celda conteniendo un puntero a la celda conteniendo el elemento deseado. Organización de Computadoras 45 Posición directa vs. indirecta Considerando el elemento elto, el puntero pd representa su posición directa y pi su posición indirecta: pd - sig elto - sig sig - sig pi Organización de Computadoras 46 Inserción en listas Veamos cómo agregar una nueva celda *cel a continuación del elemento en la posición *pd (usando el concepto de posición directa): struct celda *cel, *pd; pd 10 cel sig 20 25 sig sig 30 sig Organización de Computadoras 47 Inserción en listas Veamos cómo agregar una nueva celda *cel antes que el elemento en la posición *pd (usando el concepto de posición directa): struct celda *cel, *pd; ¿cómo hago para acceder a esta celda? 10 cel pd sig 20 15 sig sig 30 sig Organización de Computadoras 48 Inserción en listas Para agregar un elemento a una lista antes de otro hace falta poder acceder al elemento anterior en la lista. Esto se puede resolver de varias formas, siempre pagando algún costo: Recorriendo la lista desde el principio. Haciendo uso de posición indirecta en vez de directa. Implementando una lista doblemente enlazada. ¿Qué costo pago en cada caso? Organización de Computadoras 49 Fases de la compilación Compilación (traducción): Preprocesado. Generación de código assembler. Generación de código objeto. Enlazado o vinculación: Enlazado con otros archivos objeto. Enlazado con librerías estándar. Generación del ejecutable. Organización de Computadoras 50 Fases de la compilación .c preprocesado .c generación de código assembler compilación .exe .o enlazado librerías estándares y del usuario .s ensamblado .o.c .c .a.a Organización de Computadoras 51 Directrices al preprocesador Las directrices al preprocesador son interpretadas antes de la compilación: #define: define una nueva constante o macro del preprocesador. #include: indica que se debe incluir el contenido de otro archivo en el punto indicado. #ifdef, #ifndef: señala el comienzo de un bloque de preprocesamiento condicionado. #endif: marca el fin de un bloque de preprocesamiento condicionado. Organización de Computadoras 52 Constantes y macros El preprocesador permite asociar valores constantes a ciertos identificadores que serán expandidos antes de la compilación propiamente dicha: #define variable valor-cte Análogamente, las macros son funciones que han de ser expandidas antes de la compilación: #define macro(args, ...) def-función Organización de Computadoras 53 Constantes y macros #define PI 3.14 #define CANT 5 #define AREA(rad) PI * rad * rad #define MAX(a, b) (a > b ? a : b) int main() { int i; float vector[CANT]; for(i = 0; i < CANT; i++) vector[i] = MAX(i * 5.2, AREA(i)); } Organización de Computadoras 54 Archivos de encabezamiento Los archivos de encabezamientos incluidos mediante la directiva #include suelen tener la extensión “.h”. Las funciones declaradas en los archivos de encabezamiento no incluyen sus implementaciones. Las variables que allí aparezcan están declaradas como extern, ya que su definición ha de figurar en otro archivo fuente. Organización de Computadoras 55 Preprocesado condicional Para incluir código cuya compilación dependa de ciertas circunstancias, se puede hacer uso de las siguientes directivas: #ifdef variable #endif #ifndef variable #endif Organización de Computadoras 56 Preprocesado condicional #define DEBUG int main() { int i, acc; for (i = 0; i < 10; i++) acc = i * i - 1; #ifdef DEBUG printf(“fin del bucle: %d”, acc); #endif } Organización de Computadoras 57 Argumentos en línea En C torna simple acceder a los argumentos suministrados en la línea de comandos: void main(int argc, char *argv[]) { int i; printf(“%d argumentos”, argc); for (i = 1; i < argc; i++) { printf(“%d: %s\n”, i, argv[i]); } } Organización de Computadoras 58 Argumentos en línea Cabe acotar que la convención es que el primer argumento es el nombre del programa que se está ejecutando. printf(“Invocado como: %s”,argv[0]); Es decir, los argumentos en las las restantes posiciones (de 1 a argc-1), son los argumentos en la línea de comando efectivos. Nótese que los argumentos se reciben como cadenas de caracteres, incluso al tratarse de números. Organización de Computadoras 59 Gestión de archivos C comparte la filosofía UNIX en el sentido de considerar prácticamente todo como si fuera un archivo: La lectura de información desde un archivo convencional no difiere del ingreso de datos a través del el teclado. La escritura de información hacia un archivo convencional no difiere de la salida de datos por pantalla. Organización de Computadoras 60 Funciones de librería C cuenta con diversas funciones de librería sobre archivos: FILE* fopen(char*, char*): apertura de un archivo para lectura o escritura. int fclose(FILE*): cierre de un archivo. int fprintf(FILE*, ...): escritura con formato. int fscanf(FILE*, ...): lectura con formato. int feof(FILE*): determina si se ha alcanzado el final de un archivo. Organización de Computadoras 61 Archivos especiales Todo programa asocia tres constantes de tipo FILE* por defecto a los siguientes archivos: stdin: entrada estándar (descriptor 0). stdout: salida estándar (descriptor 1). stderr: error estándar (descriptor 2). Por caso: fprintf(1, “Usuario: ”); fscanf(0, “%s”, usuario); fprintf(stderr, “Error: No válido”); Organización de Computadoras 62 Conversión de tipos C cuenta con diversas funciones de librería para asistir al programador en la conversión entre tipos de datos: int atoi(char*): traduce de strings a enteros. long atol(char*): traduce de strings a enteros largos. double atof(char*): traduce de strings a reales. char* itoa(char*, int): traduce de enteros a strings. Organización de Computadoras 63 Código enrevezado C, fiel a su filosofía minimalista, permite como válido código un tanto llamativo, a saber: int a[5]; int b = 3; a[1] = 101; // a[1] = 101 2[a] = 102; // a[2] = 102 a[b] = 103; // a[3] = 103 ++b[a]; // a[3] = 104 (++b)[a] = 104; // a[4] = 104 Organización de Computadoras 64 ¿Preguntas? Organización de Computadoras 65