Apuntadores en lenguajes de programación

Informática. Lenguaje C y C++. Variables. Memoria. Valores. Operadores booleanos. Inicialización de apuntadores. Línea de comandos

  • Enviado por: Eduardo Aranguren
  • Idioma: castellano
  • País: Venezuela Venezuela
  • 19 páginas

publicidad

'Apuntadores en lenguajes de programación'
REPUBLICA BOLIVARIANA DE VENEZUELA

MINISTERIO DE EDUCACIÓN SUPERIOR

INSTITUTO UNIVERSITARIO DE TECNOLOGÍA “CUMANÁ”

INFORMÁTICA II

DEPARTAMENTO DE ELECTRICIDAD - MENCIÓN ELECTRÓNICA

ÍNDICE

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=%d\ty=%d\n",x,y);

    swap(&x, &y);

    printf("x=%d\ty=%d\n",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). 

  • 'Apuntadores en lenguajes de programación'

    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.

      • La declaración b tiene dos ventajas:

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

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

        'Apuntadores en lenguajes de programación'

        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 memoria\n");

          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

          int p(char *a);

          p es una función que acepta un argumento que es un apuntador a carácter, devuelve un entero

          int *p(char *a);

          p es una función que acepta un argumento que es un apuntador a carácter, devuelve un apuntador a entero

          int (*p)(char *a);

          p es un apuntador a función que acepta un argumento que es un apuntador a carácter, devuelve un apuntador a entero

          int (*p(char *a))[10];

          p es una función que acepta un argumento que es un apuntador a carácter, devuelve un apuntador a un arreglo de 10 enteros

          int p(char (*a)[]);

          p es un apuntador a función que acepta un argumento que es un apuntador a un arreglo de caracteres, devuelve un apuntador a entero

          int p(char *a[]);

          p es un apuntador a función que acepta un argumento que es un arreglo de apuntadores a caracteres, devuelve un apuntador a entero

          int *p(char a[]);

          p es una función que acepta un argumento que es un arreglo de caracteres, devuelve un apuntador a entero

          int *p(char (*a)[]);

          p es una función que acepta un argumento que es un apuntador a un arreglo de caracteres, devuelve un apuntador a entero

          int *p(char *a[]);

          p es una función que acepta un argumento que es un apuntador a un arreglo de apuntadores a caracteres, devuelve un apuntador a entero

          int (*p)(char (*a)[]);

          p es una función que acepta un argumento que es un apuntador a un arreglo de caracteres, devuelve un apuntador a entero

          int *(*p)(char (*a)[]);

          p es un apuntador a una función que acepta un argumento que es un apuntador a un arreglo de apuntadores a caracteres, devuelve un apuntador a entero

          int *(*p)(char *a[]);

          p es un apuntador a una función que acepta un argumento que es un arreglo de apuntadores a caracteres, devuelve un apuntador a entero

          int(*p[10])(void);

          p es una arreglo de 10 apuntadores a función, cada función devuelve un entero

          int (*p[10])(char * a);

          p es un arreglo de 10 apuntadores a función; cada función acepta un argumento que es un apuntador a carácter y devuelve un entero

          int *(*p[10])(char a);

          p es un arreglo de 10 apuntadores a función; cada función acepta un argumento que es un carácter, y devuelve un apuntador a entero

          char *(*p[10])(char * a);

          p es un arreglo de 10 apuntadores a función; cada función acepta un argumentoque es un carácter, y devuelve un apuntador a carácter.

          BIBLIOGRAFÍA

          El presente articulo fue realizado con la ayuda de:

          • El libro “Programación en C++”, del autor Luís Joyanes Aguilar, editorial Mc Graw Hill.

          • Y las siguientes paginas de Internet:

          http://webdia.cem.itesm.mx/ac/rogomez/Tutorial-LengC/apuntadores.html

          http://www.ldc.usb.ve/~gabro/teaching/CI2126/Clase1_Apuntadores.htm

          http://www.itq.edu.mx/vidatec/maestros/sis/mlopez/Tutorial/apunt.htm

          http://garota.fismat.umich.mx/mn1/manual/node9.html

          http://www.geocities.com/eztigma/apuntadores.html

          18

          INTEGRANTE:

          • Eduardo R., Aranguren P.; 15.743.681

          GRUPO: 02

          FECHA DE ENTREGA: 28/05/2007

          int *contPtr, cont;
          float *res;
          unsugned int *nosigno;
          char *mensaje;