Está en la página 1de 20

Informtica II

Programacin avanzada en C

Operadores aritmticos: Operadores monarios: sizeof (tipo)

% ++ !

(cambio de signo)

(Por ej.: sizeof(int) retorna la cantidad de bytes que ocupa una variable de tipo int) Para forzar un tipo de variable Ejemplo: int n; double x=15.2; .... n = (int) x; <= == && > != || >=

Operadores relacionales: Operadores de igualdad: Operadores lgicos: Expresin condicional:

<

(expresin_1) ? expresin_2 : expresin_3; si expresin_1 es TRUE retorna expresin_2 si no retorna expresin_3 Ejemplo: int a, b, c; .... c = (a > b)? ab : ba; = += = *= /= %=

Operadores de asignacin:

Adems de estos operadores existen otros que sern tratados posteriormente. Cuando el compilador evala una expresin en la que existen varios operadores, lo hace teniendo en cuenta una serie de reglas de PRECEDENCIA y ASOCIATIVIDAD de operadores. Por ej.: si a, b y c son variables de tipo int, qu valor final tendr c/u? c = ++ a && --b;

Informtica II

Programacin avanzada en C

Tabla de precedencia de operadores para la evaluacin de expresiones.

Obs.: esta tabla no est completa, ver el apndice C del libro de Gottfried
Categora de operador monarios aritmticos aritmticos relacionales de igualdad Y lgica O lgica expresin condicional de asignacin = += = < Operadores ++ ! * / % + <= == > != >= Asociatividad DI ID ID ID ID ID ID DI /= &= DI

&& || ?: *=

Punteros Un puntero es una variable cuyo contenido es una direccin de memoria. Si bien se declaran con el tipo al cual apuntan, no hay diferencias internas entre punteros a distintos tipos. La diferencia se presenta en lo que se denomina "aritmtica de punteros" Ejemplo 1: int a[10], *p1; double b[10], *p2; .... p1 = a; // p1 apunta al arreglo a p2 = b; // p2 apunta al arreglo b p1++; p2++; // p1 se incrementa en sizeof(int) // p2 se incrementa en sizeof(double) (2) (4)

Obs.: ambos punteros apuntan ahora al siguiente dato Ejemplo 2: double x[]={10.2, 15.3, 23.4, 42.3}, *p=x, n; n = *(p + 2); // n se carga con x[2], pero // para ello a p se le suma 8.

Informtica II

Programacin avanzada en C

Punteros y arreglos Hay una estrecha relacin entre punteros y arreglos. Un puntero contiene una direccin y el nombre de un arreglo equivale a la direccin del primer elemento del arreglo. Dados un arreglo de int y un puntero a int: int a[10]; int *p; Cmo inicializamos el puntero p para que apunte al arreglo a? Hay dos opciones bsicas: p = a; p = &a[0]; Cmo accedemos al elemnto i-simo del arreglo a? Hay varias opciones: Con notacin de arreglos: a[i] p[i] Con notacin de punteros: *(p + i) *(a + i) Obsrvese que es indistinto usar el puntero o el nombre del arreglo. Si quiero que el puntero p apunte al elemento i-simo del arreglo, tengo estas opciones. p = a + i; p = &a[i]; Ejercicio 1: A qu elemento del arreglo a se accede en el siguiente trozo de programa? p = &a[5]; printf( "%d", p[-2] ); Ejercicio 2: Proponer distintas maneras de recorrer el arreglo a.

Informtica II

Programacin avanzada en C

Declaracin de un puntero a un arreglo Si se declara un puntero a un tipo dado, como por ejemplo: int *p; Luego es posible utilizarlo para apuntar a un arreglo del tipo correspondiente. Sin embargo, tambin se puede expresar ms formalmente que dicho puntero va a ser utilizado para apuntar a un arreglo, de la siguiente forma: int (*p)[]; Si se evala la primera declaracin se lee: "p es un puntero a int" Mientra que la segunda dice: "p es un puntero a un arreglo de int" A los fines prcticos es indistinto declararlo de una forma u otra. Puntero a puntero Es un puntero que apunta a otro puntero. Es habitual su uso en estructuras complejas. Ejemplo: int n; int * p; int **pp; p = &n; pp = &p; **pp = 15; Arreglo de punteros Contiene una sucesin de punteros del mismo tipo. Ejemplo: int a[5] = { 10, 17, 9, 3, 24 }; int *p[5] = { a+3, a+2, a, a+1, a+4 }; int i; for(i=0; i<5; i++) printf( "%d\n", *p[i] );

Informtica II

Programacin avanzada en C

Una variante en la declaracin de un arreglo: Qu diferencia hay entre las siguientes declaraciones? a) b) int n[] = {10, 15, 29, 17}; int *p = {10, 15, 29, 17};

En ambos casos se declara e inicializa un arreglo de cuatro int, con los mismos valores iniciales. Sin embargo, en el segundo caso adems de la memoria utilizada por los cuatro int, tambin est el puntero p. El siguiente diagrama muestra las diferencias: a) b) Por otra parte, la sintaxis para el acceso a los datos en uno y otro caso puede hacerse de manera similar. Por ejemplo: a) for( i=0; i<4; i++ ) printf( "%d\n", n[i] ); for( i=0; i<4; i++ ) printf( "%d\n", p[i] );

b)

Ejercicio: reescribir ambos printf() usando notacin de punteros. Un ejemplo de punteros un poco ms complejo: Analizar el siguiente ejemplo. Qu imprime? int int int int n[] = {10, 20, 30, 40}; *pi[] = {n, n+1, n+2, n+3}; **pp[] = {p+3, p+2, p+1, p}; ***p = pp; "%d\n", "%d\n", "%d\n", "%d\n", "%d\n", **p[1]); ***p); ***(p+2) ); *(*(*(p+3)+1)-1) ); p[0][0][0] );

printf( printf( printf( printf( printf(

Respuesta: 30, 40, 20, 10, 40

Informtica II

Programacin avanzada en C

Un ejemplo de punteros con arreglos de char: Analizar el siguiente ejemplo. Qu imprime? char *s[]={"APUNTE", "NORMAL", "FIERROS", "BENDITOS"}; char **p[] = {s+1, s+3, s, s+2}; char ***pp = p; printf( printf( printf( printf( "%s\n", "%s\n", "%s\n", "%s\n", *(pp[0]) *(pp[1]) *(pp[2]) *(pp[3]) + + + + 3 3 1 4 ); ); ); );

Respuesta: ? Uso de argumentos en la funcin main() La funcin main() puede tener una lista de argumentos, lo que posibilita pasarle parmetros a un programa al momento de invocarlo. Los argumentos de main son dos: - Una variable de tipo int, cuyo contenido es la cantidad de parmetros que se le han pasado al programa. - Un arreglo de punteros a char. Cada puntero de este arreglo apunta a un arreglo de char que contiene el texto de cada parmetro. Por ejemplo: void main( int argc, char *argv[] ) { int i; for(i=0; i<argc; i++) printf("%s\n", argv[i]); } Este programa imprime el nombre del archivo ejecutable (apuntado por argv[0]), y el resto de los parmetros que se le han pasado. Si este programa se llama ARGUM.EXE y se lo invoca as: argum uno dos tres El programa anterior imprimir: argum uno dos tres

Informtica II

Programacin avanzada en C

Asignacin dinmica de memoria Uno de los usos habituales de los punteros es en combinacin con la asignacin dinmica de memoria. En la librera del C existe la funcin malloc() que retorna un puntero a un rea de memoria con la cantidad de bytes que se le han solicitado. Ejemplo: #include <stdlib.h> void main() { int *n, *a, i; double *x; // asigna memoria para un entero n = (int *) malloc( sizeof(int) ); // asigna memoria para un arreglo de 10 enteros a = (int *) malloc( 10 * sizeof(int) ); // asigna memoria para un arreglo de 10 double x = (double *) malloc( 10 * sizeof(double) ); // Escribe un cero en todas las variables creadas *n = 0; for( i = 0; i < 10; i++ ) { *( a + i ) = 0; //Obs.:puede usarse a[i] *( x + i ) = 0.0; } // libera la memoria dinmica free(n); free(a); free(x); } //Obs.:puede usarse x[i]

Informtica II

Programacin avanzada en C

Puntero a funcin Al igual que lo que sucede con los arreglos, cuyo nombre equivale al puntero al arreglo (o la direccin inicial del arreglo), el nombre de una funcin equivale a la direccin de entrada de la funcin. Cuando a continuacin del nombre de una funcin se colocan los parntesis con la lista de argumentos, se invoca a dicha funcin. El nombre de la funcin por si solo es el puntero a la funcin. Entonces, es posible declarar una variable de tipo puntero a funcin y asignarle la direccin de una funcin u otra. Ejemplo: void saludo() { printf(Hola\n) } void despedida() { printf(Adis\n) } void main() { void (*f)(void);

// puntero a funcin que no // espera parmetros ni // devuelve resultados // imprime Hola // imprime Adis

f = saludo; f(); f = despedida; f(); }

Tambin se pueden usar punteros a funciones que esperan parmetros y/o devuelven resultados. Ejemplo: double (*f)(double, double); // Ptr a funcin que espera // dos parmetros double y // retorna double

Informtica II

Programacin avanzada en C

El siguiente ejemplo muestra el uso de un arreglo de punteros a funcin, como una manera de ramificar la ejecucin de un programa. double suma(double a, double b) { return a + b; } double resta(double a, double b) { return a - b; } void main() { double (*f[2])(double, double); double x=12.5, y=4.5; f[0] = suma; f[1] = resta; printf(suma = %lf\n, f[0](x, y) ); printf(resta = %lf\n, f[1](x, y) ); } Recursividad Cuando una funcin se invoca a si misma se dice que la misma es recursiva. Debido a que los parmetros de una funcin son variables locales, cada instancia de la funcin maneja su propia copia de los parmetros. Si al analizar un problema se descubre un comportamiento recursivo, es posible resolverlo mediante una funcin recursiva. Ejemplo: una funcin recursiva que calcula el factorial de un nmero. unsigned long factorial( unsigned int n ) { if( !n ) return 1; else return n * factorial( n 1 ); }

Informtica II

Programacin avanzada en C

10

Ejemplo: una funcin recursiva que imprime un arreglo de char en orden inverso. void invierte( char *s ) { if( s && s[0] ) { invierte( s + 1 ); putchar( *s ); } } Observacin: debido al uso intensivo de la pila (stack) que se efecta en las funciones recursivas, no siempre es conveniente utilizarlas. Si no se limita la cantidad de recursiones se puede agotar la totalidad del stack. Uso de sscanf y sprintf para conversin de formatos. La funcin sscanf es similar a scanf, excepto que en lugar de obtener caracteres desde el teclado los obtiene de un arreglo de char. Esto permite convertir un texto en otro tipo de variable. Ejemplo: # include <stdio.h> char s[] = ALFA = 1567.48; double n, m; // carga en la variable n el valor 1567.48 // obtenido a partir del octavo carcter de s sscanf( &s[7], %lf, &n ); // carga en la variable m el valor 67.48 // obtenido a partir del dcimo carcter de s sscanf( &s[9], %lf, &m );

A 0

L 1

F 2

A 3 4

= 5 6

1 7

5 8

6 9

7 10

. 11

4 12

8 13

\0 14

Informtica II

Programacin avanzada en C

11

La funcin sprintf es similar a printf, salvo que en lugar de escribir en la salida estndar (por defecto la pantalla), escribe en un arreglo de char. Ejemplo: # include <stdio.h> char s[80]; double n = 10.5 , m = 4.3; sprintf(s, n+m = %-ld + %-ld = &-ld, n, m, n+m); En el arreglo s qued almacenado el siguiente contenido: n + m = 1 0 . 5 + 4 . 3 = 1 4 . 8 \0

Macros. La sentencia #define adems de utilizarse para declarar constantes permite definir macros, que externamente son parecidas a funciones pero internamente son cosas diferentes. Veamos un ejemplo: #define max(a,b) ((a>b)?a:b)

Esta macro permite seleccionar al mayor de dos nmeros (sin tener en cuenta el tipo). Cuando el compilador (en realidad el preprocesador) se encuentra con una expresin como la siguiente: m = max(x,y); La reemplaza por la siguiente: m = ((x>y)?x:y); Esto se denomina expansin de una macro, y se repite tantas veces como ocurrencias haya de una expresin similar. Comparacin macro vs funcin: Una funcin es nica y se la invoca pasndole parmetros, por lo que se consume tiempo y recursos en la entrada y salida a la funcin. En la macro esto no sucede. Por otro lado, al usar macros se repite el mismo cdigo por cada ocurrencia de la misma. Conclusin: para operaciones sencillas conviene usar macros, para operaciones ms extensas conviene usar funciones.

Informtica II

Programacin avanzada en C

12

Algunos ejemplos de macros: // convierte una letra a mayscula #define mayscula(c) ((c>=a&& c<=z)?c+A-a:c) // convierte una letra a minscula #define minscula(c) ((c>=A&& c<=Z)?c+a-A:c)

Estructuras. Si se desean registrar distintos tems de informacin referidos a una misma entidad, en principio habra que declarar una variable por cada tem. Esto da lugar a que la informacin sobre una misma entidad est desparramada en distintas variables. Por ejemplo consideremos las variables necesarias para registrar los datos de un alumno de algn curso: long libreta; char nombre[40]; double notas_parcial[4]; double notas_recup[4]; double notas_lab[10]; char cursado; // // // // // // N de Libreta Universitaria nombre del alumno notas de hasta 4 parciales notas de hasta 4 recuperat. notas de hasta 10 laborat. = no curs, x = curs

Los datos de la misma persona estn desparramados en 6 variables ! Esto complica el procedimiento para almacenar y recuperar esta informacin en archivos. Si adems, en lugar de un alumno se tratara de registrar los datos de todo un curso por ejemplo hasta 50 alumnos-, habra que disponer de un arreglo de cada uno de los datos anteriores. long libreta[50]; char nombre[50][40]; double notas_parcial[50][4]; double notas_recup[50][4]; double notas_lab[50][10]; char cursado[50];

Informtica II

Programacin avanzada en C

13

Adems de los tipos de datos nativos (como char, int, double, etc.), en C es posible declarar estructuras que agrupan variables miembro de distintos tipos. De esta manera los distintos elementos de informacin que corresponden a una misma entidad se pueden almacenar en una nica variable. Siguiendo el ejemplo anterior, ahora usando struct: struct alumno { long libreta; char nombre[40]; double notas_parcial[4]; double notas_recup[4]; double notas_lab[10]; char cursado; } Ahora, struct alumno es un nuevo tipo de datos que se puede usar para declarar variables: struct alumno A; A es una variable que contiene toda la informacin de un alumno. Una representacin grfica de su distribucin en la memoria sera as: libreta long nombre 40 char notas_parcial notas_recup notas_lab 4 double 4 double 10 double cursado char

El tamao (sizeof) de una variable de estructura es la suma de los tamaos de sus variables miembro. En el caso anterior, sizeof(A) sera igual a 117. Para acceder a cada variable miembro se usa el operador punto . Ejemplo: imprimir algunos datos del alumno A printf(L.U.: %ld Nombre: %s\n, A.libreta, A.nombre);

printf(Notas de parciales: ); for( i=0; i<4; i++) printf(%lf\t, A.notas_parcial[i]); Tambin es posible declarar arreglos de estructuras. Por ejemplo, para llevar el registro de los alumnos de un curso se podra hacer lo siguiente: struct alumno info_ii[50]; info_ii es un arreglo de 50 variables de tipo struct alumno.

Informtica II

Programacin avanzada en C

14

Si se quiere acceder a los datos de uno de ellos la sintaxis es como se ve a continuacin: Ejemplo: imprimir N de L.U. y nombre de todos los alumnos: for( i=0; i<50; i++) printf( L.U.: %ld Nombre: %s\n, info_ii[i].libreta, info_ii[i].nombre ); Una estructura dentro de otra. Una estructura puede contener variables miembro que a su vez tambin sean estructuras. Por ejemplo, para registrar algunos datos de un empleado como ser legajo, nombre y fecha de ingreso, se podra hacer lo siguiente: struct fecha // estructura para almacenar una fecha { int dia, mes, anio; } struct empleado { int legajo; char nombre[40]; struct fecha f_in; }

// fecha de ingreso

// Declaracin de una variable para almacenar datos de // un empleado struct empleado Emp; ... ... ... // Imprimir la fecha de ingreso printf(Fecha de ingreso: %-d / %-d / %-d\n, Emp.f_in.dia, Emp.f_in.mes, Emp.f_in.anio );

Informtica II

Programacin avanzada en C

15

Acceso a estructuras mediante punteros. Tambin es posible manipular estructuras mediante punteros del tipo adecuado. El acceso a una variable miembro mediante un puntero se realiza mediante el operador > (una flecha formado por el signo menos y el mayor). Siguiendo con el ejemplo anterior: struct empleado Emp; struct empleado *p; ... ... p = &Emp; // p apunta a la variable Emp printf( Nombre: %s\n, p->nombre ) ; printf( Ao de ingreso: %d\n, p->f_ing.anio ) ;

Uniones. La declaracin de una unin es similar a la de una estructura, aunque las distintas variables miembro estn superpuestas sobre las mismas posiciones de memoria. Las uniones permiten declarar variables que pueden ser usadas en distintos momentos para almacenar datos de diferente tipo. Ejemplo: union U1 { int n; double f; char s[40]; } union U1 var; // var: variable de tipo union U1

La variable var puede ser usada para almacenar datos de tipo int, double o un arreglo de char. Sin embargo, al escribir un dato de un tipo se destruye el dato de otro tipo que estuviera cargado previamente. var.n = 10; var.f = 24.5; // var contiene un int // ahora var contiene un double

El acceso a uniones mediante punteros es igual que para estructuras.

Informtica II

Programacin avanzada en C

16

Operadores lgicos para manejo de bits Una de las caractersticas del lenguaje C que lo hacen atractivo para desarrollar aplicaciones de bajo nivel es la facilidad para efectuar operaciones a nivel de bits o grupos de bits. & efecta una operacin AND bit a bit entre los dos operandos. unsigned char a=39, b=96, c; c = a & b; a = 39 b = 96 & c = 32 // a c se le asigna 32

0010 0111 0110 0000 0010 0000

| efecta una operacin OR bit a bit entre los dos operandos. unsigned char a=39, b=96, c; c = a | b; a = 39 b = 96 | c = 103 // a c se le asigna 103

0010 0111 0110 0000 0110 0111

^ efecta una operacin OR EXCLUSIVA entre los dos operandos. unsigned char a=39, b=96, c; c = a ^ b; a = 39 b = 96 ^ c = 71 // a c se le asigna 71

0010 0111 0110 0000 0100 0111

~ efecta una negacin bit a bit del operando. unsigned char a=39, b; b = ~a; a = 39 ~a = // a b se le asigna 216 0010 0111 1101 1000

216

Informtica II

Programacin avanzada en C

17

Otros operadores binarios son los de desplazamiento a izquierda o derecha a << n desplaza los bits de a n lugares a la izquierda

unsigned char a=7, b; b = a << 3; // a b se le asigna 56

a=7 a << 3

0000 0111 0011 1000 56

----------------a >> n desplaza los bits de a n lugares a la derecha

unsigned char a=224, b; b = a >> 4; // a b se le asigna 14

a = 224 a >> 4

1110 0000 0000 1110 14

Pregunta: Qu operacin matemtica se realiz en un caso y en el otro? Respuesta: en el primer caso se multiplic por 8 (23) en el segundo caso se dividi por 16 (24) Si se combinan los distintos operadores se pueden hacer operaciones a nivel de bits tales como: Preguntar por el valor de un bit: if( a & (1 << n) ) printf( bit %d en uno\n, n ); else printf( bit %d en cero\n, n ); Poner un bit a uno: a = a | (1 << n) ; Poner un bit a cero: a = a & ~(1 << n);

Informtica II

Programacin avanzada en C

18

Estructuras de campos de bits (o Bit Fields). Se declaran de manera parecida a una estructura comn, aunque en realidad las distintas variables miembro son porciones (de uno o ms bits) de una variable entera. Ejemplo: struct bits { unsigned unsigned unsigned unsigned } struct bits b; El ejemplo anterior define una estructura de campos de bits que representa un byte, dividido en tres porciones. Luego, con ese tipo se declara la variable b. sin uso 15 14 13 12 11 10 9 8 msb 7 6 b4_6 5 4 3 b1_3 2 1 lsb 0

int int int int

lsb b1_3 b4_6 msb

: : : :

1; 3; 3; 1;

// // // //

bit menos significativo bits 1, 2 y 3 bits 4, 5 y 6 bit 7

b.lsb b.b1_3 b.b4_6 b.msb

variable de un bit, valores posibles: 0 y 1 variable de 3 bits. Valores posibles: 0 a 7 variable de 3 bits. Valores posibles: 0 a 7 variable de 1 bit. Valores posibles: 0 y 1

Si la variable b contiene inicialmente todos los bits en cero, y se ejecuta la siguiente instruccin: b.b4_6 = 5; El resultado es: sin uso 0 0 12 11 msb 0 7 b4_6 1 0 1 6 5 4 b1_3 0 0 0 3 2 1 lsb 0 0

0 15

0 14

0 13

0 10

0 9

0 8

Informtica II

Programacin avanzada en C

19

Definicin de tipos usando typedef. La palabra reservada typedef se utiliza para crear un nuevo nombre de tipo a partir de un tipo existente. Por ejemplo: typedef unsigned char byte;

A partir de esta sentencia es posible declarar variables de tipo unsigned char usando el nombre de tipo byte: Por ejemplo: byte b;

Esto tiene dos ventajas: Es posible usar un nombre de tipo ms breve. El nombre de tipo es ms representativo del uso que se har de las variables de ese tipo. Uso de typedef con enumeraciones Las enumeraciones son secuencias de nombres que equivales a una sucesin de nmeros, que por defecto comienza en cero. Son una alternativa a la declaracin de varias constantes consecutivas mediante #define. Ejemplo: enum marcas_de_autos { RENAULT, VW, FORD, FIAT };

Mediante typedef es posible definir un tipo de variable entera para las cuales slo est previsto que tomen valores de una cierta enumeracin. Ejemplo: typedef enum {FALSE, TRUE} boolean;

En este caso, el tipo boolean est definido como un entero que tomar dos valores 0 (FALSE) y 1 (TRUE). Por ejemplo: boolean b1 = TRUE, b2 = FALSE, b3; b3 = b1 ^ b2; Otro ejemplo con enumeraciones: typedef enum {MENOS=-1, CERO, MAS} signo; signo s = MAS; // Qu valor toma c? // Qu valor toma b3?

Informtica II

Programacin avanzada en C

20

Uso de typedef con estructuras y uniones Otro posible uso de typedef es para asignar un nombre de tipo a una estructura o union. Por ejemplo: struct fecha { int dia, mes, anio; } struct fecha f;

Una variable de este tipo se declarara as: Usando typedef lo anterior podra escribirse as: typedef struct { int dia, mes, anio; }Fecha;

De esta segunda forma una variable de este tipo se declarara:

Fecha f;

De manera similar se puede proceder con las uniones, como se ve en el siguiente ejemplo: typedef struct { unsigned int b0 : 1; // bit 0 unsigned int b1 : 1; unsigned int b2 : 1; unsigned int b3 : 1; unsigned int b4 : 1; unsigned int b5 : 1; unsigned int b6 : 1; unsigned int b7 : 1; // bit 7 }Bits; typedef union { Bits bit; unsigned char byte; }Byte; Byte b; b.byte = 0; b.bit.b4 = 1; // Qu valor tiene b.byte?