SlideShare une entreprise Scribd logo
1  sur  50
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.
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:
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;
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()
{

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;
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().
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.
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.
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.
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:
   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
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
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
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:
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;
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()
    {
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:
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.
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;
}

  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).
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.
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).
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()
{
        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
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
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.
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] */
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.
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.
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.
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.
     -
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.
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;
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
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
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;
}

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
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;
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;
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];
/* 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)
{
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
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;
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 */
/* 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.
*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
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
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
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í
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.

Contenu connexe

Tendances

Mapa conceptual poo
Mapa conceptual pooMapa conceptual poo
Mapa conceptual poosaulalex2017
 
Algebra relacional
Algebra relacionalAlgebra relacional
Algebra relacionalLuis Jherry
 
Sistema Masa Resorte Amortiguador
Sistema Masa Resorte AmortiguadorSistema Masa Resorte Amortiguador
Sistema Masa Resorte AmortiguadorNatt-N
 
Diseño Base Datos
Diseño Base DatosDiseño Base Datos
Diseño Base Datosjhonnyjpo
 
Librerias Básicas y sus Funciones Lenguaje de Programación C
Librerias Básicas y sus Funciones Lenguaje de Programación CLibrerias Básicas y sus Funciones Lenguaje de Programación C
Librerias Básicas y sus Funciones Lenguaje de Programación CCristian Maza
 
Estructuras de seleccion
Estructuras de seleccionEstructuras de seleccion
Estructuras de seleccionClariza
 
Tipos de datos definidos por el programador en pseudocódigo
Tipos de datos definidos por el programador en pseudocódigoTipos de datos definidos por el programador en pseudocódigo
Tipos de datos definidos por el programador en pseudocódigoAbrirllave
 
Identificadores variables y constates en pseudocódigo
Identificadores variables y constates en pseudocódigoIdentificadores variables y constates en pseudocódigo
Identificadores variables y constates en pseudocódigoAbrirllave
 
Ejercicios de programación en C (Estructuras condicionales-Selectivas)
Ejercicios de programación en C (Estructuras condicionales-Selectivas)Ejercicios de programación en C (Estructuras condicionales-Selectivas)
Ejercicios de programación en C (Estructuras condicionales-Selectivas)Maynor Mendoza
 
2.1 metodo de intervalo
2.1 metodo de intervalo2.1 metodo de intervalo
2.1 metodo de intervalomorenito9001
 

Tendances (20)

Mapa conceptual poo
Mapa conceptual pooMapa conceptual poo
Mapa conceptual poo
 
Arreglos c1. p3.
Arreglos  c1. p3.Arreglos  c1. p3.
Arreglos c1. p3.
 
Algebra relacional
Algebra relacionalAlgebra relacional
Algebra relacional
 
Clase 1 Lenguaje C++
Clase 1 Lenguaje C++Clase 1 Lenguaje C++
Clase 1 Lenguaje C++
 
Punteros y funciones
Punteros y funciones Punteros y funciones
Punteros y funciones
 
Sistema Masa Resorte Amortiguador
Sistema Masa Resorte AmortiguadorSistema Masa Resorte Amortiguador
Sistema Masa Resorte Amortiguador
 
Funciones de entrada y salida
Funciones de entrada y salidaFunciones de entrada y salida
Funciones de entrada y salida
 
Diseño Base Datos
Diseño Base DatosDiseño Base Datos
Diseño Base Datos
 
Librerias Básicas y sus Funciones Lenguaje de Programación C
Librerias Básicas y sus Funciones Lenguaje de Programación CLibrerias Básicas y sus Funciones Lenguaje de Programación C
Librerias Básicas y sus Funciones Lenguaje de Programación C
 
Arreglos c++
Arreglos c++Arreglos c++
Arreglos c++
 
Elementos de un arreglo
Elementos de un arregloElementos de un arreglo
Elementos de un arreglo
 
Estructuras lineales
Estructuras linealesEstructuras lineales
Estructuras lineales
 
Estructuras de seleccion
Estructuras de seleccionEstructuras de seleccion
Estructuras de seleccion
 
Tipos de datos definidos por el programador en pseudocódigo
Tipos de datos definidos por el programador en pseudocódigoTipos de datos definidos por el programador en pseudocódigo
Tipos de datos definidos por el programador en pseudocódigo
 
Arboles de expresion
Arboles de expresionArboles de expresion
Arboles de expresion
 
Identificadores variables y constates en pseudocódigo
Identificadores variables y constates en pseudocódigoIdentificadores variables y constates en pseudocódigo
Identificadores variables y constates en pseudocódigo
 
Mètodos de Ordenaciòn y bùsqueda
Mètodos de Ordenaciòn y bùsquedaMètodos de Ordenaciòn y bùsqueda
Mètodos de Ordenaciòn y bùsqueda
 
Ejercicios de programación en C (Estructuras condicionales-Selectivas)
Ejercicios de programación en C (Estructuras condicionales-Selectivas)Ejercicios de programación en C (Estructuras condicionales-Selectivas)
Ejercicios de programación en C (Estructuras condicionales-Selectivas)
 
2.1 metodo de intervalo
2.1 metodo de intervalo2.1 metodo de intervalo
2.1 metodo de intervalo
 
Tipos De Datos
Tipos De DatosTipos De Datos
Tipos De Datos
 

En vedette

PUNTEROS (APUNTADORES) EN C++
PUNTEROS (APUNTADORES) EN C++PUNTEROS (APUNTADORES) EN C++
PUNTEROS (APUNTADORES) EN C++die_dex
 
Apuntadores y direccionamiento
Apuntadores y direccionamientoApuntadores y direccionamiento
Apuntadores y direccionamientoSergio Ramos
 
Estructuras de datos_dinamicas_definicion_e_implementacion_
Estructuras de datos_dinamicas_definicion_e_implementacion_Estructuras de datos_dinamicas_definicion_e_implementacion_
Estructuras de datos_dinamicas_definicion_e_implementacion_Diosmary Marrón Dellán
 
Administración de memoria continuación -matrices estáticas y dinámicas
Administración de memoria continuación -matrices estáticas y dinámicasAdministración de memoria continuación -matrices estáticas y dinámicas
Administración de memoria continuación -matrices estáticas y dinámicasUVM
 
Arrays bidimensionales
Arrays bidimensionalesArrays bidimensionales
Arrays bidimensionalesasvargas
 
PUNTEROS (APUNTADORES) EN C++
PUNTEROS (APUNTADORES) EN C++PUNTEROS (APUNTADORES) EN C++
PUNTEROS (APUNTADORES) EN C++die_dex
 
Administración de memoria arreglos dinamicos
Administración de memoria arreglos dinamicosAdministración de memoria arreglos dinamicos
Administración de memoria arreglos dinamicosUVM
 
Funciones con vectores y matrices
Funciones con vectores y matricesFunciones con vectores y matrices
Funciones con vectores y matricesJohanna Marin
 
Administración de memoria - arreglos estáticos y dinámicos
Administración de memoria - arreglos estáticos y dinámicosAdministración de memoria - arreglos estáticos y dinámicos
Administración de memoria - arreglos estáticos y dinámicosUVM
 
Aplicación de arreglos bidimensionales
Aplicación de arreglos bidimensionalesAplicación de arreglos bidimensionales
Aplicación de arreglos bidimensionalesmarigelcontreras
 
Programación en c++
Programación en c++Programación en c++
Programación en c++andermijan
 
Aplicación de vectores y matrices en c++
Aplicación de vectores y matrices en c++Aplicación de vectores y matrices en c++
Aplicación de vectores y matrices en c++Wladimir Pineida
 

En vedette (20)

Apuntadores
ApuntadoresApuntadores
Apuntadores
 
PUNTEROS (APUNTADORES) EN C++
PUNTEROS (APUNTADORES) EN C++PUNTEROS (APUNTADORES) EN C++
PUNTEROS (APUNTADORES) EN C++
 
Apuntadores y direccionamiento
Apuntadores y direccionamientoApuntadores y direccionamiento
Apuntadores y direccionamiento
 
Exp compi(2)
Exp compi(2)Exp compi(2)
Exp compi(2)
 
Trabajo de estructura de datos
Trabajo de estructura de datosTrabajo de estructura de datos
Trabajo de estructura de datos
 
Memoria memoria dinamica
 Memoria memoria dinamica Memoria memoria dinamica
Memoria memoria dinamica
 
Estructuras de datos_dinamicas_definicion_e_implementacion_
Estructuras de datos_dinamicas_definicion_e_implementacion_Estructuras de datos_dinamicas_definicion_e_implementacion_
Estructuras de datos_dinamicas_definicion_e_implementacion_
 
Administración de memoria continuación -matrices estáticas y dinámicas
Administración de memoria continuación -matrices estáticas y dinámicasAdministración de memoria continuación -matrices estáticas y dinámicas
Administración de memoria continuación -matrices estáticas y dinámicas
 
Arrays bidimensionales
Arrays bidimensionalesArrays bidimensionales
Arrays bidimensionales
 
PUNTEROS (APUNTADORES) EN C++
PUNTEROS (APUNTADORES) EN C++PUNTEROS (APUNTADORES) EN C++
PUNTEROS (APUNTADORES) EN C++
 
Arreglos multidimensionales y de apuntadores
Arreglos multidimensionales y de apuntadoresArreglos multidimensionales y de apuntadores
Arreglos multidimensionales y de apuntadores
 
Ejercicios punteros cadenas-vectores
Ejercicios punteros cadenas-vectoresEjercicios punteros cadenas-vectores
Ejercicios punteros cadenas-vectores
 
colas de prioridad
colas de prioridad colas de prioridad
colas de prioridad
 
Solucion ejercicios punteros cadenas-vectores
Solucion ejercicios punteros cadenas-vectoresSolucion ejercicios punteros cadenas-vectores
Solucion ejercicios punteros cadenas-vectores
 
Administración de memoria arreglos dinamicos
Administración de memoria arreglos dinamicosAdministración de memoria arreglos dinamicos
Administración de memoria arreglos dinamicos
 
Funciones con vectores y matrices
Funciones con vectores y matricesFunciones con vectores y matrices
Funciones con vectores y matrices
 
Administración de memoria - arreglos estáticos y dinámicos
Administración de memoria - arreglos estáticos y dinámicosAdministración de memoria - arreglos estáticos y dinámicos
Administración de memoria - arreglos estáticos y dinámicos
 
Aplicación de arreglos bidimensionales
Aplicación de arreglos bidimensionalesAplicación de arreglos bidimensionales
Aplicación de arreglos bidimensionales
 
Programación en c++
Programación en c++Programación en c++
Programación en c++
 
Aplicación de vectores y matrices en c++
Aplicación de vectores y matrices en c++Aplicación de vectores y matrices en c++
Aplicación de vectores y matrices en c++
 

Similaire à Apuntadores

Clase 11- fundamentos de la programacion
Clase 11- fundamentos de la programacionClase 11- fundamentos de la programacion
Clase 11- fundamentos de la programaciondiego MC
 
Apuntadorkeurjeh4jj4by un 4hh4j4u4jj4h4y4jh4
Apuntadorkeurjeh4jj4by un 4hh4j4u4jj4h4y4jh4Apuntadorkeurjeh4jj4by un 4hh4j4u4jj4h4y4jh4
Apuntadorkeurjeh4jj4by un 4hh4j4u4jj4h4y4jh4hassanbadredun
 
Trabajo programacion 1 jose silva
Trabajo programacion 1 jose silvaTrabajo programacion 1 jose silva
Trabajo programacion 1 jose silvaJhosse Ant Siilva
 
Luis hernandez 22310621
Luis hernandez   22310621Luis hernandez   22310621
Luis hernandez 22310621Luis Miguel
 
Programación en c (iii parte)
Programación en c (iii parte)Programación en c (iii parte)
Programación en c (iii parte)MarielaCuriel
 
Tema 8 www.fresymetal.com
Tema 8 www.fresymetal.comTema 8 www.fresymetal.com
Tema 8 www.fresymetal.comFresyMetal
 
Implementacion de punteros
Implementacion de punterosImplementacion de punteros
Implementacion de punterosKareliaRivas
 
Gestión Dinámica de la Memoria
Gestión Dinámica de la MemoriaGestión Dinámica de la Memoria
Gestión Dinámica de la MemoriaMago Julio Cesar
 
Lenguaje c diapositivas
Lenguaje c diapositivasLenguaje c diapositivas
Lenguaje c diapositivasstarduslex
 
Lenguajes diapositivas
Lenguajes diapositivasLenguajes diapositivas
Lenguajes diapositivasstarduslex
 
Lenguajes diapositivas
Lenguajes diapositivasLenguajes diapositivas
Lenguajes diapositivasstarduslex
 
Administración de memoria y apuntadores
Administración de memoria y apuntadoresAdministración de memoria y apuntadores
Administración de memoria y apuntadoresFranklin Chavez
 
Apuntadores, cadenas y estructuras
Apuntadores, cadenas y estructurasApuntadores, cadenas y estructuras
Apuntadores, cadenas y estructurasXendor Resendiz
 
Estructura de Datos
Estructura de DatosEstructura de Datos
Estructura de Datosluna_72
 
Algoritmos y lenjuage de programacion
Algoritmos y  lenjuage de programacionAlgoritmos y  lenjuage de programacion
Algoritmos y lenjuage de programacionYsaac Ruiz
 

Similaire à Apuntadores (20)

Clase 11- fundamentos de la programacion
Clase 11- fundamentos de la programacionClase 11- fundamentos de la programacion
Clase 11- fundamentos de la programacion
 
Apuntadorkeurjeh4jj4by un 4hh4j4u4jj4h4y4jh4
Apuntadorkeurjeh4jj4by un 4hh4j4u4jj4h4y4jh4Apuntadorkeurjeh4jj4by un 4hh4j4u4jj4h4y4jh4
Apuntadorkeurjeh4jj4by un 4hh4j4u4jj4h4y4jh4
 
Trabajo programacion 1 jose silva
Trabajo programacion 1 jose silvaTrabajo programacion 1 jose silva
Trabajo programacion 1 jose silva
 
Apuntadores
Apuntadores Apuntadores
Apuntadores
 
nodo
nodonodo
nodo
 
Luis hernandez 22310621
Luis hernandez   22310621Luis hernandez   22310621
Luis hernandez 22310621
 
Programación en c (iii parte)
Programación en c (iii parte)Programación en c (iii parte)
Programación en c (iii parte)
 
Tema 8 www.fresymetal.com
Tema 8 www.fresymetal.comTema 8 www.fresymetal.com
Tema 8 www.fresymetal.com
 
Implementacion de punteros
Implementacion de punterosImplementacion de punteros
Implementacion de punteros
 
Gestión Dinámica de la Memoria
Gestión Dinámica de la MemoriaGestión Dinámica de la Memoria
Gestión Dinámica de la Memoria
 
Lenguaje c diapositivas
Lenguaje c diapositivasLenguaje c diapositivas
Lenguaje c diapositivas
 
Lenguajes diapositivas
Lenguajes diapositivasLenguajes diapositivas
Lenguajes diapositivas
 
Lenguajes diapositivas
Lenguajes diapositivasLenguajes diapositivas
Lenguajes diapositivas
 
Administración de memoria y apuntadores
Administración de memoria y apuntadoresAdministración de memoria y apuntadores
Administración de memoria y apuntadores
 
Apuntadores, cadenas y estructuras
Apuntadores, cadenas y estructurasApuntadores, cadenas y estructuras
Apuntadores, cadenas y estructuras
 
Estructura de Datos
Estructura de DatosEstructura de Datos
Estructura de Datos
 
Material iii parcial
Material iii parcialMaterial iii parcial
Material iii parcial
 
08 - Punteros en lenguaje C
08 - Punteros en lenguaje C08 - Punteros en lenguaje C
08 - Punteros en lenguaje C
 
Algoritmos y lenjuage de programacion
Algoritmos y  lenjuage de programacionAlgoritmos y  lenjuage de programacion
Algoritmos y lenjuage de programacion
 
Teoria punteros
Teoria punterosTeoria punteros
Teoria punteros
 

Plus de luisabn

Ensayo 1 de Anyuris Gamez
Ensayo 1 de Anyuris GamezEnsayo 1 de Anyuris Gamez
Ensayo 1 de Anyuris Gamezluisabn
 
Esquema 1 anyuris gamez
Esquema 1 anyuris gamezEsquema 1 anyuris gamez
Esquema 1 anyuris gamezluisabn
 
Ensayo 1 anyuris gamez
Ensayo 1 anyuris gamezEnsayo 1 anyuris gamez
Ensayo 1 anyuris gamezluisabn
 
Ensayo 2 aisa
Ensayo 2 aisaEnsayo 2 aisa
Ensayo 2 aisaluisabn
 
2do Ensayo- Ibelise Oropeza
2do Ensayo- Ibelise Oropeza2do Ensayo- Ibelise Oropeza
2do Ensayo- Ibelise Oropezaluisabn
 
2do Ensayo- Ibelise Oopeza
2do Ensayo- Ibelise Oopeza2do Ensayo- Ibelise Oopeza
2do Ensayo- Ibelise Oopezaluisabn
 
2do ensayo luisa Bolivar
2do ensayo luisa Bolivar2do ensayo luisa Bolivar
2do ensayo luisa Bolivarluisabn
 
2do ensayo Luisa Bolívar
2do ensayo Luisa Bolívar2do ensayo Luisa Bolívar
2do ensayo Luisa Bolívarluisabn
 
Esquema ensayo1-luisa
Esquema ensayo1-luisaEsquema ensayo1-luisa
Esquema ensayo1-luisaluisabn
 
1er ensayo luisa bolivar
1er ensayo luisa bolivar1er ensayo luisa bolivar
1er ensayo luisa bolivarluisabn
 
Ensayo de ibelise
Ensayo de ibeliseEnsayo de ibelise
Ensayo de ibeliseluisabn
 
Esquema ensayo aisa
Esquema ensayo aisaEsquema ensayo aisa
Esquema ensayo aisaluisabn
 
Ensayo de aisa
Ensayo de aisaEnsayo de aisa
Ensayo de aisaluisabn
 

Plus de luisabn (13)

Ensayo 1 de Anyuris Gamez
Ensayo 1 de Anyuris GamezEnsayo 1 de Anyuris Gamez
Ensayo 1 de Anyuris Gamez
 
Esquema 1 anyuris gamez
Esquema 1 anyuris gamezEsquema 1 anyuris gamez
Esquema 1 anyuris gamez
 
Ensayo 1 anyuris gamez
Ensayo 1 anyuris gamezEnsayo 1 anyuris gamez
Ensayo 1 anyuris gamez
 
Ensayo 2 aisa
Ensayo 2 aisaEnsayo 2 aisa
Ensayo 2 aisa
 
2do Ensayo- Ibelise Oropeza
2do Ensayo- Ibelise Oropeza2do Ensayo- Ibelise Oropeza
2do Ensayo- Ibelise Oropeza
 
2do Ensayo- Ibelise Oopeza
2do Ensayo- Ibelise Oopeza2do Ensayo- Ibelise Oopeza
2do Ensayo- Ibelise Oopeza
 
2do ensayo luisa Bolivar
2do ensayo luisa Bolivar2do ensayo luisa Bolivar
2do ensayo luisa Bolivar
 
2do ensayo Luisa Bolívar
2do ensayo Luisa Bolívar2do ensayo Luisa Bolívar
2do ensayo Luisa Bolívar
 
Esquema ensayo1-luisa
Esquema ensayo1-luisaEsquema ensayo1-luisa
Esquema ensayo1-luisa
 
1er ensayo luisa bolivar
1er ensayo luisa bolivar1er ensayo luisa bolivar
1er ensayo luisa bolivar
 
Ensayo de ibelise
Ensayo de ibeliseEnsayo de ibelise
Ensayo de ibelise
 
Esquema ensayo aisa
Esquema ensayo aisaEsquema ensayo aisa
Esquema ensayo aisa
 
Ensayo de aisa
Ensayo de aisaEnsayo de aisa
Ensayo de aisa
 

Apuntadores

  • 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.