Guia De Practica 3

10
GUÍA DE PRÁCTICA I.Datos Generales Elaboró: Raúl Peralta Meza Práctica Nro: 3 Curso: Programación Electrónica 2 Título: Apuntadores Programa: P. P. de Ingeniería Electrónica, Universidad Católica de Santa María de Arequipa II.Pre-requisitos Conocimientos básicos de WINDOWS. Uso del IDE BorlandC 3.1. Diagramas de flujo. Instrucciones de control de flujo, funciones y apuntadores. Nociones de variables, memoria y apuntadores. III.Objetivos :Analizar y comprender la aritmética de apuntadores. :Establecer la relación entre apuntadores y arreglos. :Implementar a través de apuntadores algunas funciones estandar del C. IV.Procedimiento a) Definiciones previas. No aplica. Mas adelante se explica cada uno de los puntos en detalle. b) Pasos a ejecutar en BORLANDC 3.1 1. Usando punteros en una comparación En el siguiente ejemplo queremos comprobar si dos variables son iguales usando punteros: #include <stdio.h> int main() {int a, b; int *punt1, *punt2; a = 5; b = 5; punt1 = &a; punt2 = &b; if ( punt1 == punt2 ) printf( "Son iguales\n" ); } Alguien podría pensar que el if se cumple y se mostraría el mensaje Son iguales en pantalla. Pues no es así, el programa es erróneo. Es cierto que a y b son iguales. También es cierto que punt1 apunta a 'a' y punt2 a 'b'. Lo que queríamos comprobar era si a y b son iguales. Sin embargo con la condición estamos comprobando si punt1 apunta al mismo sitio que punt2, estamos comparando las direcciones donde apuntan. Por supuesto a y b están en distinto sitio en la memoria así que la condición es falsa. Para que el programa funcionara deberíamos usar asterisco (operador de indirección): #include <stdio.h>

Transcript of Guia De Practica 3

Page 1: Guia De Practica 3

GUÍA DE PRÁCTICA

I.Datos Generales

Elaboró: Raúl Peralta Meza Práctica Nro: 3Curso: Programación Electrónica 2 Título: ApuntadoresPrograma: P. P. de Ingeniería Electrónica, Universidad Católica de Santa María de Arequipa II.Pre-requisitos

•Conocimientos básicos de WINDOWS.•Uso del IDE BorlandC 3.1.•Diagramas de flujo. Instrucciones de control de flujo, funciones y apuntadores.•Nociones de variables, memoria y apuntadores.

III.Objetivos

:Analizar y comprender la aritmética de apuntadores.:Establecer la relación entre apuntadores y arreglos.:Implementar a través de apuntadores algunas funciones estandar del C.

IV.Procedimiento

a) Definiciones previas.

No aplica. Mas adelante se explica cada uno de los puntos en detalle.

b) Pasos a ejecutar en BORLANDC 3.1

1. Usando punteros en una comparación En el siguiente ejemplo queremos comprobar si dos variables son iguales usando punteros:

#include <stdio.h>

int main(){int a, b; int *punt1, *punt2;

a = 5; b = 5; punt1 = &a; punt2 = &b;

if ( punt1 == punt2 ) printf( "Son iguales\n" );}

Alguien podría pensar que el if se cumple y se mostraría el mensaje Son iguales en pantalla. Pues no es así, el programa es erróneo. Es cierto que a y b son iguales. También es cierto que punt1 apunta a 'a' y punt2 a 'b'. Lo que queríamos comprobar era si a y b son iguales. Sin embargo con la condición estamos comprobando si punt1 apunta al mismo sitio que punt2, estamos comparando las direcciones donde apuntan. Por supuesto a y b están en distinto sitio en la memoria así que la condición es falsa. Para que el programa funcionara deberíamos usar asterisco (operador de indirección):#include <stdio.h>

int main(){int a, b,*punt1, *punt2; a = 5; b = 5; punt1 = &a; punt2 = &b;

if ( *punt1 = = *punt2 ) printf( "Son iguales\n" );}

Ahora sí. Estamos comparando el contenido de las variables a las que apuntan punt1 y punt2. Debemos tener mucho cuidado con esto porque es un error que se comete con mucha facilidad.Vamos a cambiar un poco el ejemplo. Ahora 'b' no existe y punt1 y punt2 apuntan a 'a'. La condición se cumplirá porque apuntan al mismo sitio.

Page 2: Guia De Practica 3

1. Usando punteros en una comparación En el siguiente ejemplo queremos comprobar si dos variables son iguales usando punteros:

#include <stdio.h>

int main(){int a,*punt1, *punt2;

a = 5; punt1 = &a; punt2 = &a;

if ( punt1 == punt2 ) printf( "punt1 y punt2 iguales\n" );}

2. Aritmética de punteros Supongamos:

int *ptr,x[10];//Declaramos un puntero 'ptr' y un arreglo 'x' con 10 elementos

ptr=&x[0]; //Asignamos al puntero la dirección del primer elemento del arreglo x

*ptr = 2; // El contenido de la dirección indicada por ptr será 2. (x[0]=2)

Como ptr fue declarado apuntando a un número entero, el compilador copiaría 2 bytes (asumiendo que nuestro sistema operativo asigna 2 bytes a un variable entera. De forma similar para los datos tipo float y los tipo double el compilador copiaría el numero apropiado de bytes (4).

Qué sucede cuando escribimos:

ptr=ptr + 1;

Como el compilador "sabe" que el puntero señala la dirección de un número entero (supongamos la dirección, 100h), el compilador agrega 2 al valor de ptr en vez de 1, el puntero señala la dirección del siguiente número entero, que esta en la posición de memoria 102h (x[1]). De forma similar si hubiéramos declarado ptr como puntero a un char, el compilador hubiera agregado 1 en vez de 2. En C se refiere a este tipo de adición como "aritmética punteros", ++ptr y ptr++ es equivalente a ptr=ptr + 1.

Considere el siguiente código:

#include <stdio.h>

int array[6] = {1,23,17,4,-5,100};int *ptr;

int main(void){ int i; ptr = &array[0]; /* apunta al primer elemento del arreglo */ for (i = 0; i < 6; i++){ printf("array[%d] = %d y direccion: %p ",i,array[i],&array[i]); /*<-- A */ printf("ptr + %d = %d y direccion: %p\n",i, *(ptr + i),ptr+i); /*<-- B */ } return 0;}

Aquí imprimimos el contenido del arreglo usando la notación de subindices o “desdireccionado“ nuestro puntero. En C, el estándar indica que podemos utilizar &var_name[0 ] o el var_name , así en nuestro código escribiríamos:

ptr = &array[0 ]; o podemos escribir: ptr = my_array;

para alcanzar el mismo resultado. Este puede ser un buen momento para explicar el uso del (void *) :

void *vptr;

Un apuntador vacío es un apuntador de tipo genérico. Por ejemplo, mientras que C no permitiría la comparación de un apuntador declarado como número entero con un apuntador del tipo carácter, cualquiera de ellos se puede comparar con un apuntador vacío.

Page 3: Guia De Practica 3

2. Aritmética de punteros Supongamos:

3. Apuntadores y cadenas

El estudio de cadenas (strings) es útil para aclarar más la relación entre los punteros y arreglos. También ilustra cómo algunas de las funciones estándares del C pueden ser implementadas. Finalmente ilustra cómo y cuando los punteros pueden y deben ser pasados a las funciones. En C, las cadenas son arreglos de caracteres terminados con un carácter nulo (escrito como ' \0 ' ). Considere, por ejemplo:

char my_string[40] = {'T', 'e', 'd', '\0',};

Pero esto también toma tiempo mecanografiar. El C nos permite:

char my_string[40] = "Ted";

Cuando se utilizan las comillas dobles, en vez de los apóstrofes como fue hecho en los ejemplos anteriores, el carácter nul ('\0') se añade automáticamente al extremo de la secuencia. En todos los casos anteriores, la misma cosa sucede. El compilador separo un bloque de 40 bytes continuos en la memoria para llevar a mantener los caracteres e inicializados con 4 caracteres que son Ted\0 .

Ahora, considere el programa siguiente:

#include <stdio.h>

char strA[80] = "Una cadena a ser usada para propositos demostrativos";char strB[80];

int main(void){

char *pA; /* a apuntador tipo caracter */ char *pB; /* otro apuntador tipo caracter */ puts(strA); /* muestra el contenido de la cadena A, BASURA*/ pA = strA; /* el apuntador pA contiene la direccion de la cadena A */ puts(pA); /* muestra la cadena que apunta pA (strA) */ pB = strB; /* el apuntador pB contiene la direccion de la cadena B */ while(*pA != '\0') /* linea A */ { *pB= *pA; /* linea B */

pA++; pB++;

} *pB = '\0'; /* linea C */ puts(strB); /* muestra la cadena a la que apunta pB (strB) */ return 0;}

En el anterior código comenzamos definiendo dos matrices de 80 caracteres cada una. strA tiene inicializado cierto contenido.

Luego declaramos dos apuntadores tipo carácter y mostramos la cadena en la pantalla. Entonces "apuntamos" PA en strA . Es decir, por medio de la declaración de asignación copiamos la dirección de strA[0 ] en nuestro puntero PA. Utilizamos puts() para demostrar a que datos esta apuntado PA en la pantalla. Considere aquí la definición de la función puts():

int puts(const char *s);

" el const " usado como modificador del parámetro informa al usuario que la función no modificará la cadena señalada por s , es decir tratará esa secuencia como constante. . El parámetro pasado en puts() es un puntero, ése es el valor de un puntero (puesto que todos los parámetros en C son pasados por el valor), y el valor de un puntero es la dirección a la cual señala, o, simplemente, una dirección. Así cuando escribimos el puts(strA); como hemos visto, estamos pasando la dirección de strA[0 ] .

Similarmente, cuando escribimos el puts(pA); estamos pasando la misma dirección, puesto que hemos fijado pA = strA;

Ahora fíjese en el código debajo de la declaración del while() en la línea A:

Page 4: Guia De Practica 3

Mientras que el carácter señalado por pA (es decir * pA ) no es un carácter nul (es decir el ' \0 que termina '), haga lo siguiente:

Linea B: copie el carácter señalado por pA al espacio señalado en pB , después incremente pA (apuntará al siguiente carácter) y Pb (apunta al siguiente espacio disponible).

Cuando hemos copiado el último carácter, colocamos el carácter “nul” al final de la cadena apuntada por pB.

Por supuesto, qué el programa anterior ilustra una manera simple de copiar una cadena. Después de jugar con el programa anterior usted tiene una buena comprensión de lo qué está sucediendo, podemos proceder a crear nuestro propio reemplazo para la función estándar el strcpy() como la cual viene el C. Seria algo como:

char *my_strcpy(char *destination, char *source)

{ char *p = destination; while (*source != '\0') { *p++ = *source++; } *p = '\0'; return destination; }

En este caso, hemos seguido la práctica usada en la rutina estándar de devolver un puntero al destino.

Una vez más la función es diseñada para aceptar los valores de dos punteros tipo char, es decir direcciones, así en el programa anterior podríamos escribir:

int main(void)

{ my_strcpy(strB, strA); puts(strB); }

Vamos hacer un paréntesis para analizar

char *my_strcpy(char *destination, const char *source);

Aquí el modificador "const" es usado para asegurar al usuario que la función no modificará el contenido señalado por el puntero “source”. Usted puede probar esto modificando la función arriba, y su prototipo, para incluir "const" según lo demostrado. Entonces, dentro de la función usted puede agregar una declaración que trate de cambiar el contenido de el que señalado por a la fuente, por ejemplo:

*source = ' X ';

esto cambiaría normalmente el primer carácter de la cadena a X. El modificador const le permite al compilador coger esto como error. Inténtelo y vea el resultado.

Page 5: Guia De Practica 3

4. Precedencia de operadores

Primero, considere el hecho que * ptr++ debe ser interpretado como extraer el valor apuntado por ptr y después incrementar el valor del puntero. Esto tiene que ver con la precedencia de los operadores. Si escribiéramos (* ptr)++ incrementaríamos, no el puntero, sino lo que el puntero señala! es decir si estuvo utilizado el primer carácter de la cadena anterior del ejemplo seria la ' T ' después de aplicar la instrucción sería la ' U '. Si hubiera habido 1 entonces seria reemplazado por 2, etc, Usted puede escribir un código simple para ilustrar esta característica.

Recuerde una vez mas que la cadena no es nada más que un arreglo de caracteres, donde el último carácter es un '\0'. Lo qué hemos hecho arriba es el copiado de un arreglo. Pareciera ser una técnica que solo se aplica a arreglos de caracteres pero puede ser usado para arreglos del tipo entero, float, etc. En esos casos, sin embargo, no estaríamos hablando de las cadenas que terminan con un caracter nul.

Podríamos implementar una versión que asuma un valor especial para identificar el extremo. Por ejemplo, podríamos copiar un arreglo de números enteros positivos marcando el extremo con un número entero negativo. Por otra parte, seria mejor escribir una función que copie un arreglo en otro indicándole la cantidad de artículos a copiar, e.g. algo como en el prototipo siguiente:

void int_copy(int *ptrA, int *ptrB, int nbr);

donde nbr es el número de los números enteros a copiar. Usted puede jugar con esta idea y crear un arreglo de números enteros y ver si usted puede escribir la función int_copy() que haga el trabajo.

Esto permite el usar de funciones para manipular arreglos grandes. Por ejemplo, si tenemos un arreglo de 5000 números enteros que deseemos manipular con una función, necesitamos pasar solamente a esa función la dirección del arreglo (y de cualquier información auxiliar tal como nbr, dependiendo de lo que estamos haciendo). El arreglo en sí no es pasado, es decir el arreglo del conjunto no se copia y no se usa la pila, sólo se envía su dirección.

Esto es diferente de pasar, un número entero, a una función. Cuando pasamos un número entero lo que hacemos es una copia del número entero, es decir consiguimos su valor y lo ponemos en la pila. Dentro de la función cualquier manipulación del valor no tiene efecto en el número entero original. Pero, con órdenes e indicadores podemos pasar la dirección de la variable y por lo tanto manipular los valores de las variables originales (paso de parámetros por referencia).

Page 6: Guia De Practica 3

5. APUNTADORES Y ARREGLOS MULTIDIMENSIONALES

Considere, por ejemplo el arreglo:

char multi[5][10];

multi[5 ] es sí mismo es un arreglo que indica que hay 5 elementos cada uno que es un arreglo de 10 caracteres. Por lo tanto tenemos un arreglo de 5 arreglos de 10 caracteres cada uno.

Asúmanos que hemos llenado el arreglo de dos dimensiones con datos de una cierta clase. En memoria, puede ser que se mire como si hubiéramos inicializando 5 arreglos usando algo como:

multi[0] = {'0','1','2','3','4','5','6','7','8','9'}; multi[1] = {'a','b','c','d','e','f','g','h','i','j'}; multi[2] = {'A','B','C','D','E','F','G','H','I','J'}; multi[3] = {'9','8','7','6','5','4','3','2','1','0'}; multi[4] = {'J','I','H','G','F','E','D','C','B','A'};

A la vez, los elementos individuales pueden ser direccionados con la sintaxis:

multi[0][3 ] = ' 3' multi[1][7 ] = ' h ' multi[4][0 ] = ' J '

Puesto que los arreglos son contiguos en memoria, nuestro bloque de memoria para el anterior debe ser parecido a:

0123456789abcdefghijABCDEFGHIJ9876543210JIHGFEDCBA ^ |_____ aquí esta &multi[0][0] (dirección)

Observe que no escribimos multi[0 ] = "0123456789 " por que sino hubieramos puesto una terminación en ' \0 ' debido a que las comillas dobles usan un carácter ' \0 ' al final. En ese caso debimos haber definido 11 caracteres por fila en vez de 10.

Nuestra meta en lo anterior es ilustrar cómo la memoria se presenta en los arreglos en 2 dimensionales.

Ahora, el compilador sabe cuántas columnas están presentes en el arreglo así que puede interpretar &multi[0][0] + 10 como la dirección de la ' a ' en la 2da fila arriba. Es decir, agrega 10 direcciones de char. Si tuviéramos un arreglo de números enteros con iguales dimensiones el compilador agregarían 10*sizeof(int) que, en mi máquina, sería 20 (cada entero ocupa 2 bytes). Así, la dirección de '9' en la 4ta fila sería & multi[3][0 ] o (&multi[0][0] + 30) en la notación de punteros. Para conseguir el contenido del 2do elemento en la 4ta fila agregamos 1 a esta dirección e indireccionamos el resultado

* ((&multi[0][0] + 3* 10 ) + 1)

Podemos resumir:

* ((&multi[0][0] + fila*10) + columna) y multi[row][col] da los mismos resultados.

El programa siguiente ilustra órdenes de este número entero que usan en vez de matrices de caracteres.

#include <stdio.h>#include <conio.h>

#define ROWS 4#define COLS 3

void imprime(int *matriz){int i,j;

for (j= 0; j< ROWS; j++){for (i = 0; i< COLS; i++){

printf(" %d",*((matriz+j*COLS)+i));}

Page 7: Guia De Practica 3

Ahora, el compilador sabe cuántas columnas están presentes en el arreglo así que puede interpretar &multi[0][0] + 10 como la dirección de la ' a ' en la 2da fila arriba. Es decir, agrega 10 direcciones de char. Si tuviéramos un arreglo de números enteros con iguales dimensiones el compilador agregarían 10*sizeof(int) que, en mi máquina, sería 20 (cada entero ocupa 2 bytes). Así, la dirección de '9' en la 4ta fila sería & multi[3][0 ] o (&multi[0][0] + 30) en la notación de punteros. Para conseguir el contenido del 2do elemento en la 4ta fila agregamos 1 a esta dirección e indireccionamos el resultado

* ((&multi[0][0] + 3* 10 ) + 1)

Podemos resumir:

* ((&multi[0][0] + fila*10) + columna) y multi[row][col] da los mismos resultados.

El programa siguiente ilustra órdenes de este número entero que usan en vez de matrices de caracteres.

}}

int main(void){int multi[ROWS][COLS]; int row, col,i;

clrscr();i=0;for (row = 0; row < ROWS; row++){

for (col = 0; col < COLS; col++){multi[row][col] = i++;printf(" %d",multi[row][col]);

}}printf("\n");imprime(&multi[0][0]);return 0;

}

6. Pasar argumentos a un programa o función main

Ya sabemos cómo pasar argumentos a una función. La función main también acepta argumentos. Sin embargo sólo se le pueden pasar dos argumentos. Veamos cuáles son y cómo se declaran:

int main( int argc, char *argv[] )

El primer argumento es argc (argument count). Es de tipo int e indica el número de argumentos que se le han pasado al programa.

El segundo es argv (argument values). Es un array de strings (o puntero a puntero a char). En el se almacenan los parámetros. Normalmente (como siempre depende del compilador) el primer elemento (argv[0]) es el nombre del programa con su ruta. El segundo (argv[1]) es el primer parámetro, el tercero (argv[2]) el segundo parámetro y así hasta el final.

A los argumentos de main se les suele llamar siempre así, no es necesario pero es costumbre.

Veamos un ejemplo para mostrar todos los parámetros de un programa:

#include<stdio.h>

int main(int argc,char *argv[]) { int i; for( i=0 ; i<argc ; i++ ) printf( "Argumento %i: %s\n", i, argv[i] ); }

Si por ejemplo llamamos al programa argumentos.c y lo compilamos (argumentos.exe) podríamos

Page 8: Guia De Practica 3

#include<stdio.h>

int main(int argc,char *argv[]) { int i; for( i=0 ; i<argc ; i++ ) printf( "Argumento %i: %s\n", i, argv[i] ); }

teclear (lo que está en negrita es lo que tecleamos):

c:\programas> argumentos hola amigos

Tendríamos como salida:

Argumento 0: c:\programas\argumentos.exe Argumento 1: hola Argumento 2: amigos

Pero si en vez de eso tecleamos:

c:\programas> argumentos "hola amigos"

Lo que tendremos será:

Argumento 0: c:\programas\argumentos.exe Argumento 1: hola amigos

En el siguiente ejemplo la función main recibe parametros que son números expresados como letras '1' o caracteres. De allí que después de recibir los valores se hace una diferencia -'0'. El '0' vale 40 en la tabla de códigos ASCII, el '1' vale 41, ..... '9' vale 49. De allí que argv-'0' calcula el valor numérico del parámetro.

#include <stdio.h>

int main(int argc,char *argv[]){int par1, par2;

par1= *argv[1] - '0'; par2= *argv[2] - '0'; printf("%d + %d = %d\n",par1,par2,par1+par2);}

Cuestionario Final

En base a punteros desarrollar dos funciones:

/Una que permita calcular la longitud de una cadena (incluyendo el caracter nulo) . Similar a la función strlen.

/Una función que permita concatenar dos cadenas. Similar a strcat.