1. INTRODUCCIÓN
Los apuntadores son una parte fundamental de C. Si usted no puede usar los apuntadores
apropiadamente entonces esta perdiendo la potencia y la flexibilidad que C ofrece
básicamente. El secreto para C esta en el uso de apuntadores.
C, usa los apuntadores en forma extensiva. ¿Por qué?
Es la única forma de expresar algunos cálculos.
Se genera código compacto y eficiente.
Es una herramienta muy poderosa.
C, usa apuntadores explícitamente con:
Es la única forma de expresar algunos cálculos.
Se genera código compacto y eficiente.
Es una herramienta muy poderosa.
C, usa apuntadores explícitamente con:
Arreglos,
Estructuras y
Funciones
El uso de apuntadores en C y C++ es muy importante debido a que permite hacer los
programas más eficientes y más flexibles. En este artículo se explica de una manera
sencilla y breve todo lo referente a la utilización de apuntadores tanto en C como en C++.
Todo lo explicado en este artículo aplica tanto para C como para C++, a menos que se
especifique un lenguaje en particular. En algunos ejemplos de código que son aplicables a
C aparecen instrucciones de entrada y salida de las librerías estándar de C++.
LOS APUNTADORES:
Los apuntadores son variables que almacenan direcciones de memoria.
En general una variable contiene un valor específico dependiendo de como fue
declarada.
Un apuntador contiene la dirección de una variable que contiene un valor
específico.
Una variable se refiere directamente a un valor y un apuntador se refiere
indirectamente a un valor.
Apuntadores usados en C debido a que a veces son la única manera de expresar
un cálculo.
2. Se puede llegar a obtener un código más compacto y eficiente.
Cuando se emplean sin cuidado pueden crear programas imposibles de entender.
Cuentan con una declaración propia.
Los apuntadores disponen de dos operadores: El operador unario o monádico “&”
devuelve la dirección de memoria de una variable; El operador
de indirección o desreferencia “*” devuelve el ``contenido de un objeto apuntado
por un apuntador''.
Declaración De Apuntadores:
Cuando se declara una variable, el compilador reserva un espacio de memoria para ella y
asocia el nombre de ésta a la dirección de memoria desde donde comienzan los datos de
esa variable. Las direcciones de memoria se suelen describir como números en
hexadecimal. Un apuntador es una variable cuyo valor es la dirección de memoria de otra
variable. Se dice que un apuntador “apunta” a la variable cuyo valor se almacena a partir
de la dirección de memoria que contiene el apuntador. Por ejemplo, si un apuntador p
almacena la dirección de una variable x, se dice que “p apunta a x”.
Los apuntadores como cualquier otra variable deben de ser declarados antes de
que puedan ser utilizados.
El tipo de un apuntador lo proporciona implícitamente el tipo de la variable a la que
apunta.
Los apuntadores pueden ser declarados para apuntar a objetos de cualquier clase.
La sintaxis general de declaración es:
<tipo> * <variable>
Ejemplos de declaraciones:
La variable contPtr es del tipo apuntador a entero, (int *) y se lee ``contPtr es un
apuntador a int'' o ``contPtr apunta a una variable entera''.
NOTA: Un apuntador a cualquier tipo de variables es una dirección en memoria, la cual es
una dirección entera, pero un apuntador NO es un entero.
La razón por la cual se asocia un apuntador a un tipo de dato, es por que se debe conocer
en cuantos bytes esta guardado el dato. De tal forma, que cuando se incrementa un
apuntador, se incrementa el apuntador por un ``bloque'' de memoria, en donde el bloque
esta en función del tamaño del dato. Por lo tanto para un apuntador a un char, se agrega
un byt a la dirección y para un apuntador a entero o a flotante se agregan 4 bytes. De esta
forma si a un apuntador a flotante se le suman 2, el apuntador entonces se mueve dos
posiciones float que equivalen a 8 bytes.
Los Operadores De Los Apuntadores:
Un operador de dirección &:
Representa la dirección de memoria de la variable que le sigue; Operador unario que
regresa la dirección de su operando, ejemplo:
3. main()
{
int y;
int *yPtr;
y = 5;
yPtr = &y;
}
2. Un operador de indirección o de desreferencia:
*. El operador * aplicado al nombre de un apuntador indica el valor de la variable apuntada;
Regresa el valor del objeto hacia el cual su operando apunta, es decir un apuntador,
ejemplo:
main()
{
int x,y;
int *py;
y = 5;
*py = y;
x = *py + 5;
printf(''%d %d nn'',*py,x);
}
Veamos con un ejemplo en C la diferencia entre todos estos conceptos
Es decir: int x = 25, *pint;
pint = &x;
La variable pint contiene la dirección de memoria de la variable x. La expresión: *pint
representa el valor de la variable (x) apuntada, es decir 25. La variable pint también tiene
su propia dirección: &pint
Inicialización de APUNTADORES:
< Almacenamiento > < Tipo > * < Nombre > = < Expresión >
Si <Almacenamiento> es extern o static, <Expresión> deberá ser una expresión constante
del tipo <Tipo> expresado.
Si <Almacenamiento> es auto, entonces <Expresión> puede ser cualquier expresión del
<Tipo> especificado.
Ejemplos:
La constante entera 0, NULL (cero) proporciona un apuntador nulo a cualquier tipo de
dato:
int *p;
4. p = NULL; //actualización
El nombre de un arreglo de almacenamiento static o extern se transforma según la
expresión:
a) float mat[12];
float *punt = mat;
b) float mat[12];
float *punt = &mat[0];
Un “cast” apuntador a apuntador:
int *punt = (int *) 123.456;
Inicializa el apuntador con el entero. Esto es, en la dirección a la que apunta la variable
punt se almacena el valor 123.
Un apuntador a carácter puede inicializarse en la forma:
char *cadena = Esto es una cadena”;
Se pueden sumar o restar valores enteros a las direcciones de memoria en la forma:
(aritmética de APUNTADORES)
static int x;
int *punt = &x+2, *p = &x-1;
Equivalencia: Dos tipos definidos como APUNTADORES a objeto P y apuntador a
objeto son equivalentes sólo si P y Q son del mismo tipo. Aplicado a matrices:
nombre_apuntador = nombre_matriz;
Apuntadores y Funciones:
Cuando C pasa argumentos a funciones, los pasa por valor, es decir, si el parámetro es
modificado dentro de la función, una vez que termina la función el valor pasado de la
variable permanece inalterado.
Hay muchos casos que se quiere alterar el argumento pasado a la función y recibir el
nuevo valor una vez que la función ha terminado. Para hacer lo anterior se debe usar
una llamada por referencia, en C se puede simular pasando un puntero al argumento. Con
esto se provoca que la computadora pase la dirección del argumento a la función.
Para entender mejor lo anterior consideremos la función swap() que intercambia el valor de
dos argumentos enteros:
void swap(int *px, int *py);
main()
5. {
int x, y;
x = 10;
y = 20;
printf("x=%dty=%dn",x,y);
swap(&x, &y);
printf("x=%dty=%dn",x,y);
}
void swap(int *px, int *py)
{
int temp;
temp = *px; /* guarda el valor de la direccion x */
*px = *py; /* pone y en x */
*py = temp; /* pone x en y */
APUNTADORES Y ARREGLOS:
Existe una estrecha relación entre apuntadores y arreglos, tanto que pueden ser
usados en forma casi indistinta. En C, un nombre de un arreglo es un índice a la
dirección de comienzo del arreglo. En esencia, el nombre de un arreglo es un
puntero al arreglo.
Una variable de tipo arreglo puede considerarse como un apuntadora tipo del
arreglo.
Los apuntadores pueden ser utilizados en cualquier operación que involucre
subíndices de arreglos.
Ejemplo:
main()
{
int tabla[10],i,x,*pt,*ptr;
pt = &tabla[0];
x = *pt;
for (i=0; i!10; i++)
*(pt+i) = random();
ptr = tabla;
6. for (i=0; i!10; i++)
printf(''%d nn'',*(ptr+i),tabla[i]);
}
Cuando se suma 1 a un apuntador el incremento se adecua al tamaño en memoria
del objeto apuntado.
Un apuntador es una variable, por lo que operaciones como pa = a y pa++ son
permitidas.
Un nombre de un arreglo es una constante, no una variable, de ahí que a = pa o a++ o p =
a.
Arreglos de apuntadores:
Los arreglos pueden contener apuntadores.
El uso más común es el de formar arreglos de cadenas de caracteres.
Cada entrada en el arreglo es un apuntador al primer carácter de la cadena.
Sea la declaración:
char * mensaje[4] = {''Hola'',''Adios'',''Bye'',''Salut''}
Cada cadena está almacenada en memoria como una cadena de caracteres
terminada en NULL n0.
En el arreglo no están colocadas las cadenas, tan solo están almacenados los
apuntadores.
Aunque el arreglo es de tamaño fijo, permite el acceso a cadenas de caracteres de
cualquier longitud.
En C se pueden tener arreglos de apuntadores ya que los apuntadores son variables.
A continuación se muestra un ejemplo de su uso: ordenar las líneas de un texto de
diferente longitud.
Los arreglos de apuntadores son una representación de datos que manejan de una forma
eficiente y conveniente líneas de texto de longitud variable.
¿Cómo se puede hacer lo anterior?
Guardar todas las líneas en un arreglo de tipo char grande. Observando que n marca el
fin de cada línea. Ver figura 1.1.
Guardar los apuntadores en un arreglo diferente donde cada apuntador apunta al primer
caracter de cada línea.
Comparar dos líneas usando la función de la biblioteca estándar strcmp().
7. Si dos líneas están desacomodadas -- intercambiar (swap) los apuntadores (no el
texto).
Figura 1.1: Arreglos de apuntadores (Ejemplo de ordenamiento de cadenas).
Con lo anterior se elimina:
El manejo complicado del almacenamiento.
Alta sobrecarga por el movimiento de líneas.
Apuntadores y arreglos multidimensionales:
Puede provocar confusión el uso de arreglos de dos dimensiones y un arreglo de
apuntadores.
Considerar:
int a[10][10];
int *b[10];
El uso de a y b puede ser parecido, desde el momento en que a[5][5] y b[5][5] son
referencias validas a un int
El arreglo a es un arreglo verdadero, existen 100 celdas de memoria asignadas y
se efectúa el cálculo de subíndices rectangulares convencional para localizar un
elemento dado
Sin embargo, a b la declaración solo le asigna 10 apuntadores, cada uno de los
cuales deberá de apuntar a un arreglo de enteros
La desventajas de b son:
Ocupa más espacio, suponiendo que cada uno apunta a un arreglo de 10
elementos, el tamaño será de 10 apuntadores más 100 elementos.
Se debe de crear el espacio de los arreglos antes de asignarlos.
8. o La declaración b tiene dos ventajas:
o El acceso se hace más rápido, una inderección es más rapida que el hacer
una multiplicación seguida de una suma.
o El tamaño de los arreglos apuntados por cada una de las diez localidades
pueden ser diferentes.
Un arreglo multidimensional puede ser visto en varias formas en C, por
ejemplo:
Un arreglo de dos dimensiones es un arreglo de una dimensión, donde
cada uno de los elementos es en sí mismo un arreglo.
Por lo tanto, la notación
a[n][m]
nos indica que los elementos del arreglo están guardados renglón por
renglón.
Cuando se pasa una arreglo bidimensional a una función se debe
especificar el número de columnas -- el número de renglones es
irrelevante.
La razón de lo anterior, es nuevamente los apuntadores. C requiere
conocer cuantas son las columnas para que pueda brincar de renglón en
renglón en la memoria.
Considerando que una función deba recibir int a[5][35], se puede declarar
el argumento de la función como:
f( int a[][35] ) { ..... }
o aún
f( int (*a)[35] ) { ..... }
En el último ejemplo se requieren los parénteis (*a) ya que [ ] tiene una
precedencia más alta que *.
Por lo tanto:
int (*a)[35]; declara un apuntador a un arreglo de 35 enteros, y por ejemplo
si hacemos la siguiente referencia a+2, nos estaremos refiriendo a la
dirección del primer elemento que se encuentran en el tercer renglón de la
matriz supuesta, mientras que
int *a[35]; declara un arreglo de 35 apuntadores a enteros.
Ahora veamos la diferencia (sutil) entre apuntadores y arreglos. El manejo
de cadenas es una aplicación común de esto.
9. Considera:
char *nomb[10];
char anomb[10][20];
En donde es válido hacer nomb[3][4] y anomb[3][4] en C.
Sin embargo:
anomb es un arreglo verdadero de 200 elementos de dos
dimensiones tipo char.
El acceso de los elementos anomb en memoria se hace bajo la
siguiente fórmula 20*renglon + columna + dirección_base
En cambio nomb tiene 10 apuntadores a elementos.
NOTA: si cada apuntador en nomb indica un arreglo de 20 elementos
entonces y solamente entonces 200 chars estarán disponibles (10
elementos).
Con el primer tipo de declaración se tiene la ventaja de que cada apuntador
puede apuntar a arreglos de diferente longitud.
Considerar:
char *nomb[] = { "No mes", "Ene", "Feb", "Mar", .... };
char anomb[][15] = { "No mes", "Ene", "Feb", "Mar", ... };
Lo cual gráficamente se muestra en la figura 1.2. Se puede indicar que se
hace un manejo más eficiente del espacio haciendo uso de un arreglo de
apuntadores y usando un arreglo bidimensional.
10. Figura 1.2: Arreglo de 2 dimensiones VS. arreglo de apuntadores.
Diferentes formas de declarar a[i][j]:
Cuando se ve la referencia a un arreglo de dos dimensiones, a[i][j] , no se
puede deducir inmediatamente como fue declarado a :
Como un arreglo de 10 arreglos de tamaño 20
int a[10][20];
Como un arreglo de tamaño 20 de vectores de longitud variable
int *a[10];
Como un apuntador de apuntadores a enteros
int **a;
Como un apuntador a un arreglo de enteros de tama~no 20
int (* a)[20];
Para poder direccionar un elemento de un apuntador de
apuntadores se sigue la regla:
tab[i][j] * ( *(tab + i) + j)
ARGUMENTOS EN LA LÍNEA DE COMANDOS:
11. Existe una forma de transmitr al programa los argumentos de la
línea de comando, o parámetros, cuando comienza la ejecución
Cuando se invoca el main() se le puede pasar dos argumentos, (los
nombres son por convención):
argc es el número de argumentos en la línea de comandos.
argv[] es un apuntador a un arreglo de cadena de caracteres que
contienen los argumentos, uno por cadena
Ejemplo de uso:
main( int argc, char *argv[])
{
int i;
for (i=0; i!argc; i++)
printf(''Argumento %d :'' %s'',);
}
Ya que el primer elemento del arreglo, ( *argv[] ) apunta a la
cadena que contiene el nombre del comando, argc es al
menos igual a 1.
Esta estructura de datos es creada por el sistema operativo,
(Unix u otro), por lo que la única preocupación del
programador es usarla, no generarla.
Fallas comunes con apuntadores:
A continuación se muestran dos errores comunes que se hacen con
los apuntadores:
No asignar un apuntador a una dirección de memoria antes
de usarlo:
int *x
*x = 100;
lo adecuado será, tener primeramente una localidad física de
memoria, digamos int y;
int *x, y;
x = &y;
*x = 100;
Indirección no válida:
Supongamos que se tiene una función llamada malloc() la cual trata
de asignar memoria dinámicamente (en tiempo de ejecución), la
12. cual regresa un apuntador al bloque de memoria requerida si se
pudo o un apuntador a nulo en otro caso.
char *malloc() -- una función de la biblioteca estándar que se verá
más adelante.
Supongamos que se tiene un apuntador char *p
Considerar:
*p = (char *) malloc(100): /* pide 100 bytes de la memoria */
*p = 'y';
Existe un error en el código anterior. ¿Cuál es?
El * en la primera línea ya que malloc regresa un apuntador y *p no
apunta a ninguna dirección.
El código correcto deberá ser:
p = (char *) malloc(100);
Ahora si malloc no puede regresar un bloque de memoria, entonces
p es nulo, y por lo tanto no se podrá hacer:
*p = 'y';
Un buen programa en C debe revisar lo anterior, por lo que el
código anterior puede ser reescrito como:
p = (char *) malloc(100): /* pide 100 bytes de la memoria */
if ( p == NULL )
{
printf("Error: fuera de memorian");
exit(1);
}
*p = 'y';
Apuntadores de mayor complejidad:
int *p; p es un apuntador a un entero
int *p[10]; p es un arreglo de 10 apuntadores a enteros
int (*p)[10]; p es un apuntador a un arreglo de 10 enteros
int *p(void); p es una función que devuelve un apuntador a entero
13. p es una función que acepta un argumento que es un apuntador a
int p(char *a);
carácter, devuelve un entero
p es una función que acepta un argumento que es un apuntador a
int *p(char *a);
carácter, devuelve un apuntador a entero
p es un apuntador a función que acepta un argumento que es un
int (*p)(char *a);
apuntador a carácter, devuelve un apuntador a entero
int (*p(char p es una función que acepta un argumento que es un apuntador a
*a))[10]; carácter, devuelve un apuntador a un arreglo de 10 enteros
p es un apuntador a función que acepta un argumento que es un
int p(char (*a)[]); apuntador a un arreglo de caracteres, devuelve un apuntador a
entero
p es un apuntador a función que acepta un argumento que es un
int p(char *a[]);
arreglo de apuntadores a caracteres, devuelve un apuntador a en
p es una función que acepta un argumento que es un arreglo de
int *p(char a[]);
caracteres, devuelve un apuntador a entero
p es una función que acepta un argumento que es un apuntador a
int *p(char (*a)[]);
arreglo de caracteres, devuelve un apuntador a entero
p es una función que acepta un argumento que es un apuntador a
int *p(char *a[]);
arreglo de apuntadores a caracteres, devuelve un apuntador a en
int (*p)(char p es una función que acepta un argumento que es un apuntador a
(*a)[]); arreglo de caracteres, devuelve un apuntador a entero
p es un apuntador a una función que acepta un argumento que es
int *(*p)(char
apuntador a un arreglo de apuntadores a caracteres, devuelve un
(*a)[]);
apuntador a entero
int *(*p)(char p es un apuntador a una función que acepta un argumento que es
*a[]); arreglo de apuntadores a caracteres, devuelve un apuntador a en
p es una arreglo de 10 apuntadores a función, cada función devu
int(*p[10])(void);
un entero
int (*p[10])(char * p es un arreglo de 10 apuntadores a función; cada función acepta
a); argumento que es un apuntador a carácter y devuelve un entero
int *(*p[10])(char p es un arreglo de 10 apuntadores a función; cada función acepta
a); argumento que es un carácter, y devuelve un apuntador a entero
char
p es un arreglo de 10 apuntadores a función; cada función acepta
*(*p[10])(char *
argumentoque es un carácter, y devuelve un apuntador a carácte
a);
BIBLIOGRAFÍA
8. Apuntadores
14. Los apuntadores son una parte fundamental de C. Si usted no
puede usar los apuntadores apropiadamente entonces esta
perdiendo la potencia y la flexibilidad que C ofrece básicamente.
El secreto para C esta en el uso de apuntadores.
C usa los apuntadores en forma extensiva. ¿Porqué?
Es la única forma de expresar algunos cálculos.
Se genera código compacto y eficiente.
Es una herramienta muy poderosa.
C usa apuntadores explícitamente con:
Es la única forma de expresar algunos cálculos.
Se genera código compacto y eficiente.
Es una herramienta muy poderosa.
C usa apuntadores explícitamente con:
Arreglos,
Estructuras y
Funciones
8.1 Definición de un apuntador
Un apuntador es una variable que contiene la dirección en
memoria de otra variable. Se pueden tener apuntadores a
cualquier tipo de variable.
El operador unario o monádico & devuelve la dirección de
memoria de una variable.
El operador de indirección o dereferencia * devuelve el
``contenido de un objeto apuntado por un apuntador''.
Para declarar un apuntador para una variable entera hacer:
int *apuntador;
Se debe asociar a cada apuntador un tipo particular. Por
ejemplo, no se puede asignar la dirección de un short int a
un long int.
Para tener una mejor idea, considerar el siguiente código:
15. main()
{
int x = 1, y = 2;
int *ap;
ap = &x;
y = *ap;
x = ap;
*ap = 3;
}
Cuando se compile el código se mostrará el siguiente mensaje:
warning: assignment makes integer from pointer
without a cast.
Con el objetivo de entender el comportamiento del código
supongamos que la variable x esta en la localidad de la
memoria 100, y en 200 y ap en 1000. Nota: un apuntador es
una variable, por lo tanto, sus valores necesitan ser guardados
en algún lado.
int x = 1, y = 2;
int *ap;
ap = &x;
100 200 1000
x 1 y 2 ap 100
Las variables x e y son declaradas e inicializadas
con 1 y 2 respectivamente, ap es declarado como un apuntador
a entero y se le asigna la dirección de x (&x). Por lo que ap se
carga con el valor 100.
y = *ap;
100 200 1000
x 1 y 1 ap 100
Después y obtiene el contenido de ap. En el ejemplo ap apunta
a la localidad de memoria 100 -- la localidad de x. Por lo
tanto, y obtiene el valor de x -- el cual es 1.
x = ap;
16. 100 200 1000
x 100 y 1 ap 100
Como se ha visto C no es muy estricto en la asignación de
valores de diferente tipo (apuntador a entero). Así que es
perfectamente legal (aunque el compilador genera un aviso de
cuidado) asigna el valor actual de ap a la variable x. El valor
de ap en ese momento es 100.
*ap = 3;
100 200 1000
x 3 y 1 ap 100
Finalmente se asigna un valor al contenido de un apuntador
(*ap).
Importante: Cuando un apuntador es declarado apunta a algún
lado. Se debe inicializar el apuntador antes de usarlo. Por lo
que:
main()
{
int *ap;
*ap = 100;
}
puede generar un error en tiempo de ejecución o presentar un
comportamiento errático.
El uso correcto será:
main()
{
int *ap;
int x;
ap = &x;
*ap = 100;
}
Con los apuntadores se puede realizar también aritmética
entera, por ejemplo:
main()
{
17. float *flp, *flq;
*flp = *flp + 10;
++*flp;
(*flp)++;
flq = flp;
}
NOTA: Un apuntador a cualquier tipo de variables es una
dirección en memoria -- la cual es una dirección entera, pero un
apuntador NO es un entero.
La razón por la cual se asocia un apuntador a un tipo de dato, es
por que se debe conocer en cuantos bytes esta guardado el
dato. De tal forma, que cuando se incrementa un apuntador, se
incrementa el apuntador por un ``bloque'' de memoria, en donde
el bloque esta en función del tamaño del dato.
Por lo tanto para un apuntador a un char, se agrega un byte a la
dirección y para un apuntador a entero o a flotante se agregan 4
bytes. De esta forma si a un apuntador a flotante se le suman 2,
el apuntador entonces se mueve dos posiciones float que
equivalen a 8 bytes.
8.2 Apuntadores y Funciones
Cuando C pasa argumentos a funciones, los pasa por valor, es
decir, si el parámetro es modificado dentro de la función, una
vez que termina la función el valor pasado de la variable
permanece inalterado.
Hay muchos casos que se quiere alterar el argumento pasado a
la función y recibir el nuevo valor una vez que la función ha
terminado. Para hacer lo anterior se debe usar una llamada por
referencia, en C se puede simular pasando un puntero al
argumento. Con esto se provoca que la computadora pase la
dirección del argumento a la función.
Para entender mejor lo anterior consideremos la
función swap() que intercambia el valor de dos argumentos
enteros:
18. void swap(int *px, int *py);
main()
{
int x, y;
x = 10;
y = 20;
printf("x=%dty=%dn",x,y);
swap(&x, &y);
printf("x=%dty=%dn",x,y);
}
void swap(int *px, int *py)
{
int temp;
temp = *px; /* guarda el valor de la
direccion x */
*px = *py; /* pone y en x */
*py = temp; /* pone x en y */
}
8.3 Apuntadores y arreglos
Existe una relación estrecha entre los punteros y los arreglos.
En C, un nombre de un arreglo es un índice a la dirección de
comienzo del arreglo. En esencia, el nombre de un arreglo es un
puntero al arreglo. Considerar lo siguiente:
int a[10], x;
int *ap;
ap = &a[0]; /* ap apunta a la direccion de
a[0] */
x = *ap; /* A x se le asigna el contenido
de ap (a[0] en este caso) */
*(ap + 1) = 100; /* Se asigna al segundo
elemento de 'a' el valor 100 usando ap*/
Como se puede observar en el ejemplo la sentencia a[t] es
idéntica a ap+t. Se debe tener cuidado ya que C no hace una
revisión de los límites del arreglo, por lo que se puede ir
fácilmente más alla del arreglo en memoria y sobreescribir otras
cosas.
19. C sin embargo es mucho más sútil en su relación entre arreglos
y apuntadores. Por ejemplo se puede teclear solamente:
ap = a; en vez de ap = &a[0]; y también *(a + i) en vez
de a[i], esto es, &a[i] es equivalente con a+i.
Y como se ve en el ejemplo, el direccionamiento de apuntadores
se puede expresar como:
a[i] que es equivalente a *(ap + i)
Sin embargo los apuntadores y los arreglos son diferentes:
Un apuntador es una variable. Se puede hacer ap = a y ap++.
Un arreglo NO ES una variable. Hacer a = ap y a++ ES
ILEGAL.
Este parte es muy importante, asegúrese haberla entendido.
Con lo comentado se puede entender como los arreglos son
pasados a las funciones. Cuando un arreglo es pasado a una
función lo que en realidad se le esta pasando es la localidad de
su elemento inicial en memoria.
Por lo tanto:
strlen(s) es equivalente a strlen(&s[0])
Esta es la razón por la cual se declara la función como:
int strlen(char s[]); y una declaración equivalente es int
strlen(char *s);
ya que char s[] es igual que char *s.
La función strlen() es una función de la biblioteca estándar que
regresa la longitud de una cadena. Se muestra enseguida la
versión de esta función que podría escribirse:
int strlen(char *s)
{
char *p = s;
while ( *p != '0' )
p++;
return p - s;
20. }
Se muestra enseguida una función para copiar una cadena en
otra. Al igual que en el ejercicio anterior existe en la biblioteca
estándar una función que hace lo mismo.
void strcpy(char *s, char *t)
{
while ( (*s++ = *t++) != '0' );
}
En los dos últimos ejemplos se emplean apuntadores y
asignación por valor. Nota: Se emplea el uso del caracter nulo
con la sentencia while para encontrar el fin de la cadena.
8.4 Arreglos de apuntadores
En C se pueden tener arreglos de apuntadores ya que los
apuntadores son variables.
A continuación se muestra un ejemplo de su uso: ordenar las
líneas de un texto de diferente longitud.
Los arreglos de apuntadores son una representación de datos
que manejan de una forma eficiente y conveniente líneas de
texto de longitud variable.
¿Cómo se puede hacer lo anterior?
1. Guardar todas las líneas en un arreglo de tipo char grande.
Observando que n marca el fin de cada línea. Ver figura 8.1.
2. Guardar los apuntadores en un arreglo diferente donde cada
apuntador apunta al primer caracter de cada línea.
3. Comparar dos líneas usando la función de la biblioteca
estándar strcmp().
4. Si dos líneas están desacomodadas -- intercambiar (swap) los
apuntadores (no el texto).
21. Figura 8.1: Arreglos de apuntadores (Ejemplo de ordenamiento de cadenas).
Con lo anterior se elimina:
el manejo complicado del almacenamiento.
alta sobrecarga por el movimiento de líneas.
8.5 Arreglos
multidimensionales y
apuntadores
Un arreglo multidimensional puede ser visto en varias formas en
C, por ejemplo:
Un arreglo de dos dimensiones es un arreglo de una dimensión,
donde cada uno de los elementos es en sí mismo un arreglo.
Por lo tanto, la notación
a[n][m]
nos indica que los elementos del arreglo están guardados
renglón por renglón.
Cuando se pasa una arreglo bidimensional a una función se
debe especificar el número de columnas -- el número de
renglones es irrelevante.
La razón de lo anterior, es nuevamente los apuntadores. C
requiere conocer cuantas son las columnas para que pueda
brincar de renglón en renglón en la memoria.
22. Considerando que una función deba recibir int a[5][35], se
puede declarar el argumento de la función como:
f( int a[][35] ) { ..... }
o aún
f( int (*a)[35] ) { ..... }
En el último ejemplo se requieren los parénteis (*a) ya que [
] tiene una precedencia más alta que *.
Por lo tanto:
int (*a)[35]; declara un apuntador a un arreglo de 35 enteros, y
por ejemplo si hacemos la siguiente referencia a+2, nos estaremos
refiriendo a la dirección del primer elemento que se encuentran en el
tercer renglón de la matriz supuesta, mientras que
int *a[35]; declara un arreglo de 35 apuntadores a enteros.
Ahora veamos la diferencia (sutil) entre apuntadores y arreglos.
El manejo de cadenas es una aplicación común de esto.
Considera:
char *nomb[10];
char anomb[10][20];
En donde es válido hacer nomb[3][4] y anomb[3][4] en C.
Sin embargo:
-
anomb es un arreglo verdadero de 200 elementos de dos
dimensiones tipo char.
-
El acceso de los elementos anomb en memoria se hace bajo la
siguiente fórmula20*renglon + columna + dirección_base
-
En cambio nomb tiene 10 apuntadores a elementos.
NOTA: si cada apuntador en nomb indica un arreglo de 20
elementos entonces y solamente entonces 200 chars estarán
disponibles (10 elementos).
23. Con el primer tipo de declaración se tiene la ventaja de que cada
apuntador puede apuntar a arreglos de diferente longitud.
Considerar:
char *nomb[] = { "No mes", "Ene", "Feb", "Mar",
.... };
char anomb[][15] = { "No mes", "Ene", "Feb",
"Mar", ... };
Lo cual gráficamente se muestra en la figura 8.2. Se puede
indicar que se hace un manejo más eficiente del espacio
haciendo uso de un arreglo de apuntadores y usando un arreglo
bidimensional.
Figura 8.2: Arreglo de 2 dimensiones VS. arreglo de apuntadores.
8.6 Inicialización estática de
arreglos de apuntadores
La inicialización de arreglos de apuntadores es una aplicación
ideal para un arreglo estático interno, por ejemplo:
func_cualquiera()
24. {
static char *nomb[] = { "No mes", "Ene",
"Feb", "Mar", .... };
}
Recordando que con el especificador de almacenamiento de
clase static se reserva en forma permanente memoria el arreglo,
mientras el código se esta ejecutando.
8.7 Apuntadores y estructuras
Los apuntadores a estructuras se definen fácilmente y en una
forma directa. Considerar lo siguiente:
main()
{
struct COORD { float x,y,z; } punto;
struct COORD *ap_punto;
punto.x = punto.y = punto.z = 1;
ap_punto = &punto; /* Se asigna punto al
apuntador */
ap_punto->x++; /* Con el operador -> se
accesan los miembros */
ap_punto->y+=2; /* de la estructura
apuntados por ap_punto */
ap_punto->z=3;
}
Otro ejemplo son las listas ligadas:
typedef struct {
int valor;
struct ELEMENTO *sig;
} ELEMENTO;
ELEMENTO n1, n2;
n1.sig = &n2;
La asignación que se hace corresponde a la figura 8.3
25. Figura 8.3: Esquema de una lista ligada con 2 elementos.
Nota: Solamente se puede declarar sig como un apuntador
tipo ELEMENTO. No se puede tener un elemento del tipo variable
ya que esto generaría una definición recursiva la cual no esta
permitida. Se permite poner una referencia a un apuntador ya
que los los bytes se dejan de lado para cualquier apuntador.
8.8 Fallas comunes con
apuntadores
A continuación se muestran dos errores comunes que se hacen
con los apuntadores.
No asignar un apuntador a una dirección de memoria antes
de usarlo
int *x
*x = 100;
lo adecuado será, tener primeramente una localidad física
de memoria, digamos int y;
int *x, y;
x = &y;
*x = 100;
Indirección no válida
Supongamos que se tiene una función
llamada malloc() la cual trata de asignar memoria
dinámicamente (en tiempo de ejecución), la cual regresa
un apuntador al bloque de memoria requerida si se pudo o
un apuntador a nulo en otro caso.
char *malloc() -- una función de la biblioteca
estándar que se verá más adelante.
Supongamos que se tiene un apuntador char *p
26. Considerar:
*p = (char *) malloc(100): /* pide 100
bytes de la memoria */
*p = 'y';
Existe un error en el código anterior. ¿Cuál es?
El * en la primera línea ya que malloc regresa un
apuntador y *p no apunta a ninguna dirección.
El código correcto deberá ser:
p = (char *) malloc(100);
Ahora si malloc no puede regresar un bloque de
memoria, entonces p es nulo, y por lo tanto no se podrá
hacer:
*p = 'y';
Un buen programa en C debe revisar lo anterior, por lo
que el código anterior puede ser reescrito como:
p = (char *) malloc(100): /* pide 100
bytes de la memoria */
if ( p == NULL )
{
printf("Error: fuera de memorian");
exit(1);
}
*p = 'y';
8.2 Apuntadores y Funciones
Cuando C pasa argumentos a funciones, los pasa por valor, es
decir, si el parámetro es modificado dentro de la función, una
vez que termina la función el valor pasado de la variable
permanece inalterado.
27. Hay muchos casos que se quiere alterar el argumento pasado a
la función y recibir el nuevo valor una vez que la función ha
terminado. Para hacer lo anterior se debe usar una llamada por
referencia, en C se puede simular pasando un puntero al
argumento. Con esto se provoca que la computadora pase la
dirección del argumento a la función.
Para entender mejor lo anterior consideremos la
función swap() que intercambia el valor de dos argumentos
enteros:
void swap(int *px, int *py);
main()
{
int x, y;
x = 10;
y = 20;
printf("x=%dty=%dn",x,y);
swap(&x, &y);
printf("x=%dty=%dn",x,y);
}
void swap(int *px, int *py)
{
int temp;
temp = *px; /* guarda el valor de la
direccion x */
*px = *py; /* pone y en x */
*py = temp; /* pone x en y */
}
8.3 Apuntadores y arreglos
Existe una relación estrecha entre los punteros y los arreglos.
En C, un nombre de un arreglo es un índice a la dirección de
comienzo del arreglo. En esencia, el nombre de un arreglo es un
puntero al arreglo. Considerar lo siguiente:
int a[10], x;
int *ap;
ap = &a[0]; /* ap apunta a la direccion de
a[0] */
28. x = *ap; /* A x se le asigna el contenido
de ap (a[0] en este caso) */
*(ap + 1) = 100; /* Se asigna al segundo
elemento de 'a' el valor 100 usando ap*/
Como se puede observar en el ejemplo la sentencia a[t] es
idéntica a ap+t. Se debe tener cuidado ya que C no hace una
revisión de los límites del arreglo, por lo que se puede ir
fácilmente más alla del arreglo en memoria y sobreescribir otras
cosas.
C sin embargo es mucho más sútil en su relación entre arreglos
y apuntadores. Por ejemplo se puede teclear solamente:
ap = a; en vez de ap = &a[0]; y también *(a + i) en vez
de a[i], esto es, &a[i] es equivalente con a+i.
Y como se ve en el ejemplo, el direccionamiento de apuntadores
se puede expresar como:
a[i] que es equivalente a *(ap + i)
Sin embargo los apuntadores y los arreglos son diferentes:
Un apuntador es una variable. Se puede hacer ap = a y ap++.
Un arreglo NO ES una variable. Hacer a = ap y a++ ES
ILEGAL.
Este parte es muy importante, asegúrese haberla entendido.
Con lo comentado se puede entender como los arreglos son
pasados a las funciones. Cuando un arreglo es pasado a una
función lo que en realidad se le esta pasando es la localidad de
su elemento inicial en memoria.
Por lo tanto:
strlen(s) es equivalente a strlen(&s[0])
Esta es la razón por la cual se declara la función como:
int strlen(char s[]); y una declaración equivalente es int
strlen(char *s);
ya que char s[] es igual que char *s.
29. La función strlen() es una función de la biblioteca estándar que
regresa la longitud de una cadena. Se muestra enseguida la
versión de esta función que podría escribirse:
int strlen(char *s)
{
char *p = s;
while ( *p != '0' )
p++;
return p - s;
}
Se muestra enseguida una función para copiar una cadena en
otra. Al igual que en el ejercicio anterior existe en la biblioteca
estándar una función que hace lo mismo.
void strcpy(char *s, char *t)
{
while ( (*s++ = *t++) != '0' );
}
En los dos últimos ejemplos se emplean apuntadores y
asignación por valor. Nota: Se emplea el uso del caracter nulo
con la sentencia while para encontrar el fin de la cadena.
8.4 Arreglos de apuntadores
En C se pueden tener arreglos de apuntadores ya que los
apuntadores son variables.
A continuación se muestra un ejemplo de su uso: ordenar las
líneas de un texto de diferente longitud.
Los arreglos de apuntadores son una representación de datos
que manejan de una forma eficiente y conveniente líneas de
texto de longitud variable.
¿Cómo se puede hacer lo anterior?
1. Guardar todas las líneas en un arreglo de tipo char grande.
Observando que n marca el fin de cada línea. Ver figura 8.1.
2. Guardar los apuntadores en un arreglo diferente donde cada
apuntador apunta al primer caracter de cada línea.
30. 3. Comparar dos líneas usando la función de la biblioteca
estándar strcmp().
4. Si dos líneas están desacomodadas -- intercambiar (swap) los
apuntadores (no el texto).
Figura 8.1: Arreglos de apuntadores (Ejemplo de ordenamiento de cadenas).
Con lo anterior se elimina:
el manejo complicado del almacenamiento.
alta sobrecarga por el movimiento de líneas.
8.5 Arreglos
multidimensionales y
apuntadores
Un arreglo multidimensional puede ser visto en varias formas en
C, por ejemplo:
Un arreglo de dos dimensiones es un arreglo de una dimensión,
donde cada uno de los elementos es en sí mismo un arreglo.
Por lo tanto, la notación
a[n][m]
nos indica que los elementos del arreglo están guardados
renglón por renglón.
31. Cuando se pasa una arreglo bidimensional a una función se
debe especificar el número de columnas -- el número de
renglones es irrelevante.
La razón de lo anterior, es nuevamente los apuntadores. C
requiere conocer cuantas son las columnas para que pueda
brincar de renglón en renglón en la memoria.
Considerando que una función deba recibir int a[5][35], se
puede declarar el argumento de la función como:
f( int a[][35] ) { ..... }
o aún
f( int (*a)[35] ) { ..... }
En el último ejemplo se requieren los parénteis (*a) ya que [
] tiene una precedencia más alta que *.
Por lo tanto:
int (*a)[35]; declara un apuntador a un arreglo de 35 enteros, y
por ejemplo si hacemos la siguiente referencia a+2, nos estaremos
refiriendo a la dirección del primer elemento que se encuentran en el
tercer renglón de la matriz supuesta, mientras que
int *a[35]; declara un arreglo de 35 apuntadores a enteros.
Ahora veamos la diferencia (sutil) entre apuntadores y arreglos.
El manejo de cadenas es una aplicación común de esto.
Considera:
char *nomb[10];
char anomb[10][20];
En donde es válido hacer nomb[3][4] y anomb[3][4] en C.
Sin embargo:
-
anomb es un arreglo verdadero de 200 elementos de dos
dimensiones tipo char.
-
32. El acceso de los elementos anomb en memoria se hace bajo la
siguiente fórmula20*renglon + columna + dirección_base
-
En cambio nomb tiene 10 apuntadores a elementos.
NOTA: si cada apuntador en nomb indica un arreglo de 20
elementos entonces y solamente entonces 200 chars estarán
disponibles (10 elementos).
Con el primer tipo de declaración se tiene la ventaja de que cada
apuntador puede apuntar a arreglos de diferente longitud.
Considerar:
char *nomb[] = { "No mes", "Ene", "Feb", "Mar",
.... };
char anomb[][15] = { "No mes", "Ene", "Feb",
"Mar", ... };
Lo cual gráficamente se muestra en la figura 8.2. Se puede
indicar que se hace un manejo más eficiente del espacio
haciendo uso de un arreglo de apuntadores y usando un arreglo
bidimensional.
Figura 8.2: Arreglo de 2 dimensiones VS. arreglo de apuntadores.
33. 8.6 Inicialización estática de
arreglos de apuntadores
La inicialización de arreglos de apuntadores es una aplicación
ideal para un arreglo estático interno, por ejemplo:
func_cualquiera()
{
static char *nomb[] = { "No mes", "Ene",
"Feb", "Mar", .... };
}
Recordando que con el especificador de almacenamiento de
clase static se reserva en forma permanente memoria el arreglo,
mientras el código se esta ejecutando.
8.7 Apuntadores y estructuras
Los apuntadores a estructuras se definen fácilmente y en una
forma directa. Considerar lo siguiente:
main()
{
struct COORD { float x,y,z; } punto;
struct COORD *ap_punto;
punto.x = punto.y = punto.z = 1;
ap_punto = &punto; /* Se asigna punto al
apuntador */
ap_punto->x++; /* Con el operador -> se
accesan los miembros */
ap_punto->y+=2; /* de la estructura
apuntados por ap_punto */
ap_punto->z=3;
}
Otro ejemplo son las listas ligadas:
typedef struct {
int valor;
struct ELEMENTO *sig;
} ELEMENTO;
34. ELEMENTO n1, n2;
n1.sig = &n2;
La asignación que se hace corresponde a la figura 8.3
Figura 8.3: Esquema de una lista ligada con 2 elementos.
Nota: Solamente se puede declarar sig como un apuntador
tipo ELEMENTO. No se puede tener un elemento del tipo variable
ya que esto generaría una definición recursiva la cual no esta
permitida. Se permite poner una referencia a un apuntador ya
que los los bytes se dejan de lado para cualquier apuntador.
8.8 Fallas comunes con
apuntadores
A continuación se muestran dos errores comunes que se hacen
con los apuntadores.
No asignar un apuntador a una dirección de memoria antes
de usarlo
int *x
*x = 100;
lo adecuado será, tener primeramente una localidad física
de memoria, digamos int y;
int *x, y;
x = &y;
*x = 100;
Indirección no válida
Supongamos que se tiene una función
llamada malloc() la cual trata de asignar memoria
dinámicamente (en tiempo de ejecución), la cual regresa
35. un apuntador al bloque de memoria requerida si se pudo o
un apuntador a nulo en otro caso.
char *malloc() -- una función de la biblioteca
estándar que se verá más adelante.
Supongamos que se tiene un apuntador char *p
Considerar:
*p = (char *) malloc(100): /* pide 100
bytes de la memoria */
*p = 'y';
Existe un error en el código anterior. ¿Cuál es?
El * en la primera línea ya que malloc regresa un
apuntador y *p no apunta a ninguna dirección.
El código correcto deberá ser:
p = (char *) malloc(100);
Ahora si malloc no puede regresar un bloque de
memoria, entonces p es nulo, y por lo tanto no se podrá
hacer:
*p = 'y';
Un buen programa en C debe revisar lo anterior, por lo
que el código anterior puede ser reescrito como:
p = (char *) malloc(100): /* pide 100
bytes de la memoria */
if ( p == NULL )
{
printf("Error: fuera de memorian");
exit(1);
}
*p = 'y';
8.9 Ejercicios
36. 1. Escribir el programa que ordena las líneas de un texto leído
desde la entrada estándar, donde cada línea tiene diferente
longitud, según lo descrito en la sección de arreglo de
apuntadores.
2. Escribir una función que convierta una cadena s a un número de
punto flotante usando apuntadores. Considerar que el número
tiene el siguiente formato 99999999.999999, es decir, no se
dará en notación científica. La función deberá suministrársele
una cadena y deberá devolver un número.
3. Escribir un programa que encuentre el número de veces que
una palabra dada (esto es, una cadena corta) ocurre en una
sentencia (una cadena larga).
Leer los datos de la entrada estándar. La primera línea es
una sola palabra, en la segunda línea se tiene un texto
general. Leer ambas hasta encontrar un caracter de nueva
línea. Recordar que se debe insertar un caracter nulo
antes de procesar.
La salida típica podría ser:
La palabra es "el"
La sentencia es "el perro, el gato y el
canario"
La palabra ocurrio 3 veces.
Los apuntadores son un tipo de dato, que almacena informacion en la memoria de
la direccion de otras variables.
Es decir si tienes este algoritmo:::
#include<iostream>
using namespace std;
int main (){
char a;
a = 's';
char * ap;
ap = &a;
return 0;
37. }
tienes una variable a que se guarda en una direccion x de la memoria RAM, la
variable *ap; guarda esa direccion x. y eso es un apuntador.
Clase 2: Apuntadores
Esquema de Un Computador
La Memoria del Computador
Almacena datos e instrucciones de programa. Puede considerarse a la
memoria como una lista o arregl o de casillas identificadas o enumeradas
En las direcciones de memoria se almacenan datos en la forma de palabras
Las palabras est�n constituidas por uno o mas Bytes, dependiendo de la
arquitectura de la maquina
38. Los Bytes, est�n compuestos por 8 bits
Los bits, o d�gitos binarios, son la unidad m�nima de almacenamiento,
almacenan los valores 1 = encendido, y 0 = apagado
Caracter�sticas de los Apuntadores
Son variables que mantienen direcciones de memoria
Poderosos para manejar datos, de manera no posible en otros
lenguajes
Permiten el pasaje de par�metros por referencia.
Cuando se utilizan de manera incorrecta, son una fuente de bugs en
los programas y frustraci�n en el programador
Introducci�n
Al ejecutarse un programa, las variables se almacenan en memoria,
cada una en su propia y �nica direcci�n o localidad. Las variables en
general contienen un valor odato. Por ejemplo cuando se declara:
int count = 5;
El valor "5" es almacenado en memoria y puede ser accedido usando la variable
�count�.�
Un apuntador es un tipo especial de variable que en lugar de contener un valor o
dato, contiene una direcci�n de memoria. As� como los datos pueden modificarse
cuando se trabaja con una variable normal, el valor de la direcci�n almacenada en
un apuntador tambi�n puede modificarse. Usualmente, la direcci�n almacenada
en el apuntador es la direcci�n correspondiente a alguna otra variable del
programa.
int *ptr;
ptr = &count /* Guarda direcci�n de count en ptr */
/* el operador unario & retorna la direcci�n de la �variable */
Para tomar el valor o contenido que es almacenado en la localidad de memoriam
enelapuntador, es necesario �de referenciar� al apundator. Esto se hace usando el
operador unario �*�.
int total;
39. total = *ptr;
/* The value in the address stored in ptr is assigned to total */
La mejor manera de aprender apuntadores es con la pr�ctica y los ejemplos. Los
apuntadores son un t�pico dif�cil. No se preocupen si el panorama no esta
completamente claro todav�a.
Ejemplo de Declaraci�n e Inicializaci�n
int main()
{
int j;
int k;
int l;
int *pt1; /* Declares an integer pointer */
int *pt2; /* Declares an integer pointer */
float values[100];
float results[100];
float *pt3; /* Declares a float pointer */
float *pt4; /* Declares a float pointer */
j = 1;
k = 2;
pt1 = &j; /* pt1 contains the address of the variable j */
pt2 = &k; /* pt2 contains the address of variable k */
pt3 = values;
/* pt3 contains the address of the first element of values */
pt3 = &values[0];
/* This is the equivalent of the above statement */
return 0;
}
Dereferenciacion de Apuntadores y Asignaci�n de valores
La dereferenciacion permite la manipulaci�n de los datos contenidos en la
direcci�n de memoria guardada en el apuntador. El apuntador guarda una
direcci�n de memoria. Al de referenciar un apuntador, podemos modificar los datos
o valores contenidos en esa direcci�n de memoria. El operadorunario �*�, es
utilizado para de referenciar a los apuntadores.
*pt1 =*pt1 + 2;
40. Esta instrucci�n suma dos al valor apuntado por pt1. Es decir, la instrucci�n suma
dos a; contenido de la direcci�n de memoria almacenada por pt1. Entonces, a
partir del programa principal, pt1 contiene �la direcci�n de j. La variable "j" fue
inicializada con 1. El efecto de la instrucci�n mostrada arriba es sumar 2 a j.
El contenido de la direcci�n almacenada en un apuntador, puede ser asignado a
otro apuntador o a otra variable
*pt2 = *pt1;
/* assigns the contents of the memory pointed to by pt1 */
/* to the contents of the memory pointer to by pt2; */
k = *pt2;
/* assigns the contents of the address pointer to by pt2 to k. */
Aritm�tica de Apuntadores
Parte del poder de los apuntadores proviene de la habilidad de hacer aritm�tica en
los apuntadores mismos. Los apuntadores pueden ser incrementados,
decrementados y manipulados utilizando expresiones aritm�ticas. Consideremos el
apuntador "pt3" y el arreglo de float "values" declarados en el programa principal
mostrado anteriormente:
pt3 = &values[0];
/* The address of the first element of "values" is stored in pt3*/
pt3++;
/* pt3 now contains the address of the second element of values */
*pt3 = 3.1415927;
/* The second element of values now has pie (actually pi)*/
pt3 += 25;
/* pt3 now points to the 27th element of values */
*pt3 = 2.22222;
/ The 27th element of values is now 2.22222 */
pt3 = values;
/*pt3 points to the start of values, now */
for (ii = 0; ii < 100; ii++)
{
*pt3++ = 37.0; /* This sets the entire array to 37.0 */
}
pt3 = &values[0];
/* pt3 contains the address of the first element of values */
pt4 = &results[0];
41. /* pt4 contains the address of the first element of results */
for (ii=0; ii < 100; ii++)
{
*pt4 = *pt3;
/* The contents of the address contained in pt3 are assigned to
the contents of the address contained in pt4 */
pt4++;
pt3++;
}
Pasaje de Parámetros
Por valor: Los valores que se pasan se copian a los parámetros de la función, si el
valor de un parámetro se modifica dentro de la función, no se altera su valor en el
programa que lo llama.
Por Referencia: Permiten modificar dentro de la función el valor actual de la
variable que fue pasada como parámetro. Es decir, el valor de la variable si se
altera en el programa que llama.
Podemos distinguir entre parámetros formales y parámetros reales
Parámetros Formales: Aquellos que van en la definición de la función o un
prototipo. Son como variables locales a la función que sirven de comunicación con
el exterior.
Parámetros Reales: Se colocan en la invocación de la función, pueden ser
expresiones o variables del mismo tipo del parámetro formal correspondiente.
Deben colocarse en la misma posición del parámetro formal correspondiente,
respetando el número tipo y posición de los parámetros formales.
Ejemplo función Swap
void Swap(int x, int y) {
int temp ;
temp = x;
x = y;
y = temp;
}
Con la invocacion, Swap(a,b) ? no tendria ningun efecto, pues los parametros se
han pasado por valor. Es decir, no se afectan los valores de a y b.
Debe hacerse pasando los parametros por referencia
void Swap(int *apx, int *apy)
{
42. int temp ;
temp = *apx;
*apx = *apy;
*apyy = temp ;
}
main()
{
int a = 20, b = 100;
Swap(&a, &b);
// Los valores de a y b se intercambian, a = 100, b
= 20
}
Apuntadores
Definición 2 Un apuntador es una variable que contiene una dirección de
memoria.
Supongamos una variable de tipo entero que se llama contenidoRAM y otra
variable que se llama direccionRAM que puede contener una variable de tipo
entero. En C/C++ una variable precedida del operador & devuelve la dirección
de la variable en lugar de su contenido. Así que para asignar la dirección de
una variable a otra variable del tipo que contiene direcciones se usan
sentencias como esta:
direccionRam = &contenidoRAM
43. Figura 2: contenidoRAM se asigna a la localidad de memoria con dirección 7751
En la figura 2 se ilustra el nombre de la variable contenidoRAM y se observa
que se encuentra en la dirección 7751 de la memoria. El contenido de esta
localidad no se muestra. Una variable que contiene una dirección, tal
como direccionRAM, se llama variable apuntador o simplemente apuntador.
Despues que la sentencia anterior se ejecuta, la dirección de contenidoRAM será
asignada a la variable apuntador direccionRAM. La relación se expresa
diciendo que direccionRAM apunta acontenidoRAM. La figura 3 ilustra esta
relación.
Figura 3: Notación de flecha para los apuntadores
El accceso al contenido de una celda cuya dirección está almacenada en la
variable direccionRAM es tan sencillo como poner al inicio de la variable
apuntador un asterisco: *direccionRAM. Lo que se ha hecho es eliminar la
referencia directa. Por ejemplo, si se ejecutan las siguientes dos sentencias, el
valor de la celda llamada contenidoRAM será de 20 (véase la figura 4).
direccionRAM = &contenidoRAM;
*direccionRAM = 20;
44. Figura 4: A contenidoRAM se le asigna el valor entero 20
Introducción a los apuntadores
Los apuntadores son variables que guardan direcciones en C y C++. Proporcionan
mucha utilidad al programador para accesar y manipular datos de maneras que no es
posible en otros lenguajes. Tambien son utiles para pasarle parametros a las
funciones de tal modo que les permiten modificar y regresar valores a la rutina que las
llama. Cuando se utilizan incorrectamente, son también fuente tanto de fallas en el
programa como de frustración para el programador :P.
Introducción
Mientras un programa esta corriendo todas las variables se guardan en memoria, cada
una tiene su dirección o localidad única. Generalmente, una variable y su localidad
asociada contienen valores. Por ejemplo, cuando declaras:
int count = 5;
El valor "5" se guarda en memoria y puede ser accesado usando la variable "count".
Un apuntador es un tipo especial de variable que contiene una dirección de memoria
en lugar del valor de un dato. Tal como un dato es modificado cuando una variable
normal es usada, el valor de la dirección guardado en el apuntador cambia cuando
este es manipulado.
Generalmente, la dirección guardada en el apuntador es la dirección de alguna otra
variable.
int *ptr;
ptr = &count /* Guarda la dirección de count en ptr */
45. /* El operador unario & regresa la dirección de una variable */
Para obtener el valor que esta guardado en la localidad de memoria del apuntador es
necesario referenciar el apuntador. La referencia se hace con el operador unario "*".
int total;
total = *ptr;
/* El valor de la direccipon guardada en ptr es asignada a total */
La mejor manera de aprender a usar apuntadores es con ejemplos. Hay ejemplos de
los tipos de operaciones ya discutidas abajo. Los apuntadores son un tema dificil. No
te preocupes si todavía no queda todo claro.
Declaración e inicialización
Declarar e inicializar apuntadores es bastante fácil.
int main()
{
int j;
int k;
int l;
int *pt1; /* Declara un apuntador entero */
int *pt2; /* Declara un apuntador entero */
float values[100];
float results[100];
float *pt3; /* Declara un apuntador flotante */
float *pt4; /* Declara un apuntador flotante */
j = 1;
k = 2;
pt1 = &j; /* pt1 contiene la dirección de la variable j */
pt2 = &k; /* pt2 contiene la dirección de la variable k */
pt3 = values;
/* pt3 contiene la dirección del primer elemento de values */
pt3 = &values[0];
/* Esto es equivalente a la afirmación de arriba */
return 0;
}
Referencia de apuntadores/ Asignación de valores
La referencia permite manipular los datos contenidos en la dirección de memoria
guardada en el apuntador. El apuntador guarda una dirección de memoria. La
referencia permite que los datos en esa dirección de memoria sean modificados. El
operador unario "*" se usa para la referencia. Por ejemplo:
*pt1 =*pt1 + 2;
Esto le añade dos al valor "apuntado por" pt1. Esto quiere decir que esta instrucción le
suma 2 al contenido de la dirección de memoria guardada en el apuntador pt1. Así, en
el programa main, pt1 contiene la dirección de j. La variable "j" fue inicializada en 1. El
efecto del código de arriba es sumar 2 a j.
El contenido de las direcciones guardadas en un apuntador pueden ser asignadas a
otro apuntador o variable.
46. *pt2 = *pt1;
/* asigna el contenido de la memoria apuntada por pt1 */
/* al contenido de la memoria apuntada por pt2 */
k = *pt2;
/* asigna el contenido del apuntador pt2 por dos a k */
Aritmética de apuntadores
Parte del poder de los apuntadores viene de la habilidad de realizar operaciones
matemáticas sobre los mismos apuntadores. Los apuntadores pueden ser
incrementados, decrementados y manipulados usando expresiones matemáticas.
Recordando el apuntador flotante "pt3" y el arreglo flotante "values" declarados en el
programa main de arriba.
pt3 = &values[0];
/* La dirección del primer elemento de "values" se guarda en pt3*/
pt3++;
/* pt3 ahora contiene la dirección del segundo elemento de values
*/
*pt3 = 3.1415927;
/* El segundo elemento de values tiene pay (de hecho pi)*/
pt3 += 25;
/* pt3 ahora apunta al elemento 27 de values */
*pt3 = 2.22222;
/* el elemento 27 de values ahora es 2.22222 */
pt3 = values;
/*pt3 apunta al primer elemento de values, ahora */
for (ii = 0; ii < 100; ii++)
{
*pt3++ = 37.0; /* esto pone todo el arreglo en 37.0 */
}
pt3 = &values[0];
/* pt3 contiene la dirección del primer elemento de values */
pt4 = &results[0];
/* pt4 contiene la dirección del primer elemento de results */
for (ii=0; ii < 100; ii++)
{
*pt4 = *pt3;
/* Los contenidos de las direcciones de pt3 se le asignan a
los contenidos de las direcciones de pt4 */
pt4++;
pt3++;
}
7.1 Apuntadores
En una computadora cada posición de memoria tiene una dirección y un
valor específico almacenado en esa posición. Se han utilizado nombres de
variables en lugar de direcciones porque los nombres son más fáciles de
47. recordar. Para almacenar un nuevo valor en memoria se asigna a una
variable, y la computadora envía una dirección a la memoria seguida por
el valor a almacenar en esa posición.
Pascal proporciona un tipo especial de variable denominado apuntador,
que es una variable cuyo valor es una dirección de una posición de
memoria.
Al definir un tipo apuntador se debe indicar el tipo de valores que se
almacenarán en las posiciones designadas por los apuntadores. La razón
es que los diferentes tipos de datos requieren diferentes cantidades de
memoria para almacenar sus constantes, una variable apuntador puede
contener una dirección de una posición de memoria adecuada sólo para
un tipo dado. Por esta razón se dice que un apuntador apunta a una
variable particular.
Una variable tipo apuntador contiene la dirección de la posición de otra
variable
Para declarar una variable de tipo apuntador en Pascal se debe
especificar el nombre de la variable y el tipo del valor que se almacenará
en la posición de memoria a la que el apuntador se refiere.
Formato:
Type
tipo_apuntador = ^tipo dato
Una variable de tipo tipo_apuntador es un apuntador hacia un dato de
tipo nombre del tipo. El signo '^' se lee "apunta hacia".
Ejemplos:
1. Type
2. Apuntador = ^real;
3. Var
4. P : Apuntador;
5. {define el tipo,cuyos valores apuntan
6. a posiciones que contienen números reales
7. P apunta a posiciones que
8. contienen números reales}
9. Var
48. 10. P : ^real;
11.
12. Type
13. registro = record
14. nombre:string[10];
15. edad :integer;
16. sexo :char
17. end;
18. Var
19. P :integer;
20. Q :^registro;
P sólo puede contener direcciones de posiciones de memoria adecuada
para almacenar enteros. Q se limita a datos de tipo registro.
Capítulo 29:
Punteros
Enlaces patrocinadosRepetro - Databras
Regime Especial Aduaneiro Repetro Databras
www.repetro.com
Introducción
Como vimos anteriormente en la lección número 5, las
estructuras de datos dinámicasson aquellas cuya ocupación de
memoria puede aumentar o disminuir durante el tiempo de
ejecución. Mediante los punteros, tema que estudiaremos a
continuación, podemos crear estructuras de datos dinámicas que
tienen capacidad de variar en tamaño y ocupar tanta memoria como
realmente requieran. Estas estructuras son llamadas punteros.
7.1 Concepto
49. Los punteros son también llamados apuntadores. Son tipos
de datos que permiten crear estructuras de datos dinámicas, las
cuales pueden variar en tamaño y memoria requerida. Las variables
que se crean y se destruyen durante la ejecución se
llaman variables dinámicas o anónimas. Así, durante la ejecución de
un programa, puede haber una posición de memoria específica
asociada con una variable dinámica y posteriormente puede no
existir ninguna posición de memoria asociada con ella.
Una estructura de datos dinámica es una colección de
elementos llamados nodos de la estructura - normalmente tipo
registro- que se enlazan o encadenan juntos. Este enlace se
establece asociando con cada nodo un puntero que apunta al nodo
siguiente de la estructura.
Las estructuras de datos dinámicas son útiles para
almacenar y procesar conjuntos de datos cuyos tamaños cambian
durante la ejecución del programa, por ejemplo, el conjunto de
trabajos que se han introducido en una computadora y están
esperando su ejecución o el conjunto de nombres de pasajeros y
asignación o el conjunto de nombres de pasajeros y asignación
respectiva de asientos de un vuelo de avión determinado.
Pascal proporciona los métodos para asignar y liberar
espacio de memoria utilizando punteros y los
procedimientos new y dispose.
El tipo de datos puntero es de tipo simple pues no se puede
romper en otros componentes más pequeños, como sí sucede con
el array o el registro. Los punteros son variables que se utilizan para
almacenar la dirección de memoria de otra variable. Las variables
que se utilizan para almacenar direcciones son llamadas variables
puntero o simplemente puntero.
Gráficamente se representa así
50. Al definir un puntero se debe indicar el tipo de valores que se
almacenarán en las posiciones designadas por los punteros. Esto
se debe a que los diferentes tipos de datos requieren distintas
cantidades de memoria para almacenar sus constantes, una
variable puntero puede contener una dirección de una posición de
memoria adecuada sólo para un tipo dado.
Por esta razón se dice que un puntero apunta a una variable
particular, es decir, a otra posición de memoria.
Una variable tipo puntero contiene la dirección de la posición
de otra variable.