Punteros Un puntero es un tipo de variable que permite almacenar y manipular las direcciones de memoria donde se encuentran almacenados los datos, en lugar de los datos mismos. El manejo de punteros en el lenguaje C es muy poderoso y eficiente, aunque demanda extremo cuidado en su utilización. Bien utilizado hace que el código sea más compacto y eficiente; por el contrario, su uso descuidado lleva a tener poca claridad y frecuentemente conduce a la pérdida del control de la corrida, ocasionando un comportamiento indeseable del proceso. El uso de punteros nos permite acceder al valor de una variable en forma indirecta. Para esto se utilizan dos operadores: & El operador "dirección de memoria de" que permite obtener la dirección de memoria donde se aloja la variable a la cual precede ( &x es la dirección de memoria de x). * El operador "contenido de lo apuntado por" que permite acceder al valor almacenado en la dirección de memoria que indica el puntero al cual precede ( *px es el contenido de lo apuntado por px) Declaración Las declaraciones de los punteros se realizan en forma indirecta, declarando el tipo de dato que será apuntado por el mismo. Por ejemplo un puntero a un dato de tipo entero se declara como: int *px; Sean además x e y dos variables del tipo entero. Existen dos operadores unarios para usarlos con punteros: Inicialización Antes de poder ser utilizado, un puntero debe ser inicializado (como cualquier otra variable). Para inicializar un puntero se puede utilizar: a. la dirección de memoria de otra variable int y=2, *py; py = &y; b. el valor de otro puntero ya inicializado. int y=2, *py = &y, *ppy; ppy = py; 1 Pasaje de argumentos por referencia Supongamos que queremos realizar una función que recibe dos argumentos y que debe retornar como resultado los valores invertidos de las dos variables. Sea por ejemplo la siguiente implementación de esta función: void Swap(int x, int y) { int Temp; Temp = x; x = y; y = temp; } El efecto de esta función no es el deseado, ya que las variables son pasadas por valor, es decir que lo que se modifica no son las variables originales, sino copias de ellas. La forma de hacer esta operación es a través del paso de los argumentos por referencia. En C esto se implementa haciendo uso de punteros: void Swap(int *x, int *y) { int Temp; Temp = *x; *x = *y; *y = Temp; } Como lo que se están pasando a la función Swap son en realidad direcciones de memoria donde están alojadas las variables originales almacenadas, las modificaciones que se realizan afectan estos valores. La invocación de esta función debe ser de la forma: Swap(&a,&b); //Programa que suma un valor a una variable a través de un //puntero. #include <stdio.h> #include <conio.h> int main() { int a, *p; a=5; p=&a; *p+=7; printf("\nEl valor final de a es: %d", a); getch(); } 2 Estructuras, Typedef, Arreglos de Estructuras Una estructura es un conjunto de datos, posiblemente de tipos diferentes, agrupadas bajo un mismo nombre, para hacer más eficiente su manejo. Las estructuras ayudan a organizar datos complicados, particularmente en programas grandes, ya que permiten tratar como una unidad a un conjunto de variables relacionadas, en lugar de tratarlas como entidades independientes. Un buen criterio de uso de estructuras establece que la definición de una estructura debe ir acompañada por la definición de un conjunto de funciones que se encargan de realizar la manipulación directa de la nueva entidad creada. Definición de estructuras en Lenguaje C Una estructura se define en lenguaje C a través de la siguiente sintaxis: struct Nombre { tipo1 Campo1; tipo2 Campo2; ... tipoN CampoN; }; La declaración struct Nombre Var1; Declara una variable del tipo "struct Nombre", esto es, el compilador reserva la cantidad de memoria suficiente para mantener la estructura íntegra (es decir espacio para almacenar Campo1, Campo2, ..., CampoN). Cuando se hace referencia a la variable Var, se esta haciendo referencia a la estructura íntegra. Inicialización de estructuras Se puede inicializar una estructura externa o estática añadiendo a su definición la lista de inicializadores: struct Fecha { int Dia; char Mes[10]; int Anio; }; 3 struct Fecha Hoy = {8,"Mayo",1991}, VarFecha; ... VarFecha = Hoy; La asignación VarFecha = Hoy copia la estructura integra Hoy en VarFecha. Cuando dentro de los campos de una estructura aparecen arreglos de caracteres y se realiza este tipo de asignación, se esta copiando también arreglos de caracteres. Referencia de campos de una estructura Un campo de una estructura se utiliza como una variable más. Para referenciar un campo de una estructura se emplea el operador "." Ejemplos: Hoy.Dia = 24; Hoy.Anio = 1991; printf(“El mes es: %s\n",Hoy.Mes); Ejemplo para verificar año bisiesto: (Fecha.Anio % 4 == 0) && (Fecha.Anio % 100 != 0)) || (Fecha.Anio % 400 == 0) Las estructuras se pueden anidar: struct Person { char Name [NAMESIZE]; char Adress[ADRSIZE]; long ZipCode; double Salary; struct Fecha Nacimiento; }; Para referenciar un campo de una estructura interna se utiliza la misma sintaxis, referenciando campos internos dentro de campos: struct Person Juan; Juan.Nacimiento.Dia = 14; 4 Arreglos de Estructuras Es posible agrupar un conjunto de elementos de tipo estructura en un array. Esto se conoce como array de estructuras: struct trabajador { char nombre[20]; char apellidos[40]; int edad; }; struct trabajador fijo[20]; Así podremos almacenar los datos de 20 trabajadores. Ejemplos sobre como acceder a los campos y sus elementos: para ver el nombre del cuarto trabajador, fijo[3].nombre;. Para ver la tercera letra del nombre del cuarto trabajador, fijo[3].nombre[2];. Para inicializar la variable en el momento de declararla lo haremos de esta manera: struct trabajador fijo[20]={{"José","Herrero Martínez",29},{"Luis","García Sánchez",46}}; Typedef El lenguaje 'C' dispone de una declaración llamada typedef que permite la creación de nuevos tipos de datos. Ejemplos: typedef int entero; /* crear un tipo de dato llamado entero */ entero a, b=3; /* declaramos dos variables de este tipo */ Su empleo con estructuras está especialmente indicado. Se puede hacer de varias formas: Una forma de hacerlo: struct trabajador { char nombre[20]; char apellidos[40]; int edad; 5 }; typedef struct trabajador datos; datos fijo,temporal; Otra forma: typedef struct { char nombre[20]; char apellidos[40]; int edad; }datos; datos fijo,temporal; Ejemplos: 1.- Veamos antes que, además de int, char y float, otros tipos de datos simples son las enumeraciones o tipos enumerados, donde se listan las constantes del tipo. Este tipo se declara con la palabra reservada enum, como un conjunto de constantes enteras representadas por identificadores. Por ejemplo: enum ComunidadUniversitaria {obrero,administrativo,estudiante, profesor}; Los valores de una enumeración se inician con 0 a menos que se inicialice en otro valor, y se incrementan en 1. En el ejemplo anterior, los identificadores son definidos automáticamente en los enteros 0 a 3. Otro ejemplo, donde los identificadores tienen valores del 1 al 6 es: enum Deportes {Futbol=1,Beisbol,Tenis,Basketball,Natación,SoftBall}; Los identificadores en una enumeración deben ser únicos. Los nombres de las constantes no pueden ser modificados en el programa. /* Usando tipo enumerado */ #include <stdio.h> enum meses {ENE = 1, FEB, MAR, ABR, MAY, JUN, JUL, AGO, SEP, OCT, NOV, DIC}; main() { enum meses meses; char *mesNombre[] = {"", "Enero", "Febrero", "Marzo", 6 "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"}; for (meses = ENE; meses <= DIC; meses++) printf("%2d%11s\n", meses, mesNombre[meses]); getch(); } 2.- Además de los tipos simples, C provee los tipos estructurados los cuales permiten construir estructuras de datos más complejas. Entre ellos están los arreglos. Los arreglos ocupan espacio en memoria. En la declaración se especifica el tipo de cada elemento y el número de elementos requerido por cada arreglo. Para declarar un arreglo se escribe lo siguiente <nombre_de_tipo> <identificador>[<tamaño>]; Un ejemplo de esta declaración sería: int s[6]; Con los arreglos se pueden construir y manipular colecciones de datos del mismo tipo como si formaran un solo objeto. Una analogía común con los arreglos son los vectores, los cuales son una colección de elementos del mismo tipo (por ejemplo Reales) que tiene un tamaño y forman un solo objeto el vector. La declaración del tipo vector sería typedef float Vector[100]; En esta declaración se puede observar que el vector está formado por 100 elementos todos reales. La declaración de la variable sería Vector v1; El uso de un arreglo es como el de cualquier variable, pero debemos indicar cuál es el elemento que queremos accesar. Esto se realiza a través del índice del arreglo, el cual indica la posición del elemento a usar. Las posiciones en C, siempre empiezan en cero (0) y terminan en la posición <tamaño> - 1. En el caso de los vectores, para 7 obtener el valor de la posición 20 y colocarlo en una variable real, se puede hacer en algunas formas equivalentes como y=v1[19]; i=19; y=v1[i]; i=1; y=v1[20-i]; Note que en el índice del arreglo (señalado entre corchetes) podemos colocar una constante, una variable o una expresión, siempre que sean de tipo entero que el subrango indicado en la definición del arreglo. Algunos cuidados que debemos tener con los arreglos son: el índice no debe tomar valores fuera del subrango, por lo cual al usar variables o expresiones dentro del índice se debe hacer con precaución; el tamaño de los arreglos está limitado por la memoria disponible. 3.- Una de propiedades de los tipos estructurados en C es que éstos pueden ser definidos en función de otros tipos estructurados. Es así como, el tipo de los elementos de un arreglo podría ser un arreglo. En C se simplifican estas construcciones en lo que se denominan arreglos bidimensionales los cuales son análogos con las matrices. La definición de una matriz quedaría float Matriz [100][150]; En este caso se dice que la matriz (arreglo bidimensional) es de tamaño 100x150 y sus elementos son números reales. Después de la declaración de la variable m como Matriz se puede usar dentro de expresiones a través de dos índices, uno por cada dimensión del arreglo, como por ejemplo m[10][35] = m[10][36]+m[11][35]; 4.- Otro tipo estructurado provisto por C son las estructuras o struct. Estos permiten manipular elementos de tipos diferentes como si formaran un solo objeto. A estos elementos se les denomina campos de la estructura. Las estructuras se definen usando la siguiente instrucción: struct <nombre_del_tipo> { <tipo> <nombre_campo>; ... <tipo> <nombre_campo>; } <lista de identificadores>; Para usar una estructura debe especificarse todo el camino donde se localiza el campo. Por ejemplo, dada la definición de un tipo estructura que almacene una fecha typedef struct Fecha { int día; enum mes {ene,feb,mar,abr,may,jun, 8 jul,ago,sep,oct,nov,dic}; int año; }Tipo_fecha; Se puede asignar una variable con la fecha de cumpleaños de la siguiente manera: Tipo_Fecha cumpleaños; ... cumpleaños.día = 06; cumpleaños.mes = jul; cumpleaños.año = 1996; Como se mencionó anteriormente, todo tipo estructurado puede estar definido en función de otros tipos estructurados. Por ello, se pueden construir estructuras de estructuras, arreglos de estructuras, estructuras de arreglos y así sucesivamente. Ejercicios: 1.- Usando tipos enumerados, arreglos y estructuras, dé definiciones de tipos para: a) Colores primarios b) Días de la semana c) Deportes acuáticos d) Vocales y consonantes e) Cargos de profesores: instructor, asistente, asociado y titular f) Meses del año g) Trimestre Ene-Abr 94 h) Mayúsculas y minúsculas i) Minoría de Edad Ejemplos de Estructuras: a. Defina un tipo de datos llamado TFECHA, con los campos adecuados para almacenar el día, mes y año de las fechas. typedef struct { int dia; int mes; int año; } TFECHA; b. Defina un tipo de datos llamado TCD con campos adecuados para guardar el nombre del álbum, el cantante o grupo, la fecha que lo adquirió (que debe ser del tipo TFECHA), y el tipo de música (Pop, Rock, etc.). 9 typedef struct { char Album[15]; char Cantante_Grupo[15]; char Tipo_Musica[15]; TFECHA Fecha_Ad; } TCD; c. Declare un arreglo MisCDs de tamaño 100 de tipo TCD. TCD MisCDs[100]; d. Asigne en la primera posición del arreglo MisCDs, los datos correspondientes a su álbum musical favorito (si no recuerda la fecha en que lo adquirió, puede poner cualquiera) strcpy(MisCDs[0].Album,“Romances”); strcpy(MisCDs[0].Cantante_Grupo,“Luis Miguel”); strcpy(MisCDs[0].Tipo_Musica,“Bolero”); MisCDs[0].Fecha_Ad.dia = 30; MisCDs[0].Fecha_Ad.mes = 10; MisCDs[0].Fecha_Ad.año = 1996; Funciones 1. Escriba una función Norma que calcule y retorne la norma de un vector de dimensión n. La función debe recibir como parámetros el vector (cuyos componentes deben ser números reales) y un entero n que indique la dimensión del mismo. Recuerde que la norma de un vector se define como la raíz cuadrada de la suma de los cuadrados de sus componentes. double Norma(float v[], int n) { double SumCuad = 0; int i; for(i=0; i < n; i++) SumCuad += v[i]*v[i]; // Calculo la suma de los cuadrados 10 return(sqrt(SumCuad)); cuadrada } // Calculo y retorno la raiz 2. Escriba el un procedimiento LeerDatos que recibe un vector y su dimensión, y pide al usuario los datos para llenar las componentes del vector. void LeerDatos(float v[], int n) { double SumCuad = 0.0; int i; for(i=0; i < n; i++) { printf("Introduzca elemento %d: ",i); scanf("%f",&v[i]); } } 3. Escriba la función main que declare las variables necesarias, llame al procedimiento LeerDatos con una dimensión de vector de 100, luego calcule la norma de dicho vector usando la función Norma, y la imprima como resultado. void main() { float vector[3]; int i; LeerDatos(vector,3); printf("Norma = %f ",Norma(vector,3)); } Escriba las instrucciones en C que realicen lo que se les pide, solo las instrucciones y no el programa completo 2. Vectores y Matrices: a. Sea Ciudad un arreglo de caracteres que ya ha sido inicializado, escriba las instrucciones necesarias para: i. Imprimir el contenido del arreglo Ciudad. printf(“%s”, Ciudad); ii. Imprimir el valor del séptimo carácter de Ciudad printf(“%c”, Ciudad[6]); 11 iii. Asignar la constante Barquito al arreglo Ciudad strcpy(Ciudad, “Barquito”); b. Sea A una matriz de números reales de 1000 x 500, la cual ya tiene asignados valores en sus componentes. Calcule e imprima el promedio de los elementos correspondientes a la quinta columna de la matriz int i; double sum = 0; for(i=0; i<1000;i++) sum += A[i][4]; printf(Promedio = %f, sum/1000.0) c. Declare una matriz M de enteros de dimensiones 100 x 200, y luego asígnele los siguientes valores: 01010101… 10101010… 01010101… 10101010… etc. int M[100][200], i, j; for(i=0; i <10; i++) { if (i%2 == 0) { // Filas pares comienzan en cero for(j=0; j <20; j+=2) { M[i][j]= 0; M[i][j+1]= 1; } }else { // Filas impares comienzan en uno for(j=0; j <20; j+=2) { M[i][j]= 1; M[i][j+1]= 0; } } } 12