Punteros y matrices

18 de mayo 2009 Publicado en C / C + +

En el primer post de esta serie, hablamos un poco acerca de los punteros. En el segundo caso, hablamos de referencias. Hoy nos centraremos en la relación íntima (ui!) Entre apuntadores y arreglos (o matrices).

Recordando las matrices

Una matriz, o matriz, o un acuerdo, es una abstracción matemática utilizada para representar un conjunto de datos homogéneos, es decir, del mismo tipo (int, float, etc.) Esta abstracción está organizada en formato de tabla, con filas y columnas. Cada elemento de la matriz sólo tiene coordenadas (fila y columna), de modo que un determinado elemento E (i, j) representa el único elemento en la línea "i", "la columna j.

La sintaxis para declarar matrices es el siguiente:

  / / One-matrices de dimensiones o vectores
 10 ] ; // 10 elementos, do 0 ao 9 ivet int [10] / / 10 elementos del 0 al 9
 23 ] ; // 23 elementos, do 0 ao 22 char cvet [23] / / 23 elementos, 0 a 22

 / / Dos matrices de dimensiones
 2 ] [ 3 ] ; // 2 linhas (0 a 1) e 3 colunas (0 a 2) IMAT int [2] [3] / / 2 líneas (0 a 1) y 3 columnas (0 a 2)
 10 ] [ 2 ] ; // 10 linhas (0 a 9) e 2 colunas (0 a 1) DMAT doble [10] [2] / / 10 líneas (0 a 9) y 2 columnas (0 a 1) 

Cada elemento de la matriz es independiente de los demás y se puede acceder en la siguiente sintaxis:

  10 ] ; ivet int [10];
 3 ] [ 4 ] ; IMAT int [3] [4];

 / / Cambiar el cuarto elemento de la ivet vector.
 / / Recuerde que comienza a correr desde el elemento cero
 ] = 13 ; ivet [3] = 13;

 / / Leer el segundo elemento del vector ivet
 ivet [ 1 ] ; int num = ivet [1];

 / / Cambiar el elemento de la primera fila, segunda columna IMAT
 ] [ 1 ] = 42 ; IMAT [0] [1] = 42;

 / / Lectura del elemento en la tercera fila, cuarta columna IMAT
 imat [ 2 ] [ 3 ] ; int foo = IMAT [2] [3]; 

En este post no vamos a discutir la matriz de fondo, sólo investigar la relación entre matrices y punteros en un bastante intuitivo.

Matrices de tallas

Dado que una matriz es una abstracción que contiene varios valores del mismo tipo, ¿cómo es de grande? ¿Cuánto espacio ocupa en la memoria?

Considere el siguiente código:

  17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 10 ] ; char cvet [ 13 ] ; double dvet [ 20 ] ; char cmat [ 3 ] [ 4 ] ; int imat [ 5 ] [ 4 ] ; cout << "sizeof(int)     = " << sizeof ( int ) << endl ; cout << "sizeof(char)    = " << sizeof ( char ) << endl ; cout << "sizeof(double)  = " << sizeof ( double ) << endl ; cout << "sizeof(ivet)    = " << sizeof ( ivet ) << endl ; cout << "sizeof(cvet)    = " << sizeof ( cvet ) << endl ; cout << "sizeof(dvet)    = " << sizeof ( dvet ) << endl ; cout << "sizeof(cmat)    = " << sizeof ( cmat ) << endl ; cout << "sizeof(imat)    = " << sizeof ( imat ) << endl ; ivet int [10]; cvet char [13]; ETVD doble [20]; CMAT char [3] [4], IMAT int [5] [4] cout << "sizeof (int) =" <<sizeof ( int) <<endl << "sizeof (char) =" <<sizeof (char) <<endl << "sizeof (double) =" <<sizeof (double) <<endl << " sizeof (FPI) = "<<sizeof (FPI) <<endl <<" sizeof (EFPC) = "<<sizeof (EFPC) <<endl <<" sizeof (ETVD) = "<<sizeof ( ETVD) <<endl << "sizeof (CMAT) =" <<sizeof (CMAT) <<endl << "sizeof (IMAT) =" <<sizeof (IMAT) <<endl; 

El resultado es bastante razonable. El espacio ocupado por una matriz es igual al número de elementos multiplicados por el tamaño del tipo de elemento (filas x columnas x sizeof (tipo)). Sin embargo, si cada elemento es independiente, se supone que cada uno ocupa un lugar aparte en la memoria, de lo contrario uno se sobrescribirá otro elemento. Por lo tanto, es que cada elemento tiene su propia dirección de memoria?

Matrices y las direcciones de memoria

Por conveniencia, a continuación, se analizarán las posibles direcciones de una matriz de caracteres, cuyo tamaño es sólo se da de 1 byte:

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
  5 ; const int max = 5;
 max ] = { 'A' , 'B' , 'C' , 'D' , 'E' } ; cvet char [max] = ( 'A', 'B', 'C', 'D', 'E');

 / / Mostrar el contenido, los valores y las direcciones de los datos.
 "Índice \t Valor \t Endereço do elemento \n " ) ; printf ( "Index \ valor de t \ dirección de elemento t \ n");
 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%d \t %c \t %p \n " , i, cvet [ i ] , & cvet [ i ] ) ; printf ( "% d \ t% c \ t% p \ n", i, cvet [i], y cvet [i]);
 )

 / / Impresión de la dirección de la matriz de
 "Endereço da matriz: %p \n " , & cvet ) ; printf ( "Dirección de la matriz:% p \ n", y cvet);

 / / Impresión de la dirección de la matriz de nuevo
 "Endereço da matriz: %p \n " , cvet ) ; printf ( "Dirección de la matriz:% p \ n", cvet); 

Las direcciones de los elementos son secuenciales, es decir, se almacena cada elemento junto a la primera. Además, hay dos hechos más interesantes:

  1. La dirección de la matriz (& cvet), que se muestra en la línea 25, es el mismo que el primer elemento de la matriz;
  2. La cvet muy variable puede ser interpretado como un puntero, como se muestra en la línea 28;

En C + +, una matriz común es un bloque contiguo de memoria, cuyo nombre puede ser interpretado (CAST), como un puntero que señala a su primer elemento. Además es válido para hacer un punto de puntero a una matriz en donde el puntero hasta el destino es el mismo tipo que el tipo de los elementos de la matriz. Durante atrubuição una matriz a un puntero, el compilador realiza una conversión de tipos implícitos. El puntero de destino, debe interpretarse como un puntero a la zona de memoria ocupada por la matriz.

Una de las consecuencias no tan evidente es que durante el modelo implícito, se pierde la información de que el área fue una matriz de memoria. Así, se pierde la información del tamaño de la matriz. Desde el punto de vista del puntero, que apunta al comienzo de una de bloques de memoria, un tamaño demasiado arbitraria. Echa un vistazo a una serie e ir a un puntero que representa pasar de una abstracción más restrictivas y más alto nivel de abstracción a una menos restrictivas y de nivel inferior.

Por otra parte intenta asignar un puntero a una matriz genera un error de compilación de tipos incompatibles. Una matriz es un bloque de memoria de datos de N (en bytes), como un puntero tiene una sola entrada, una dirección. El compilador no puede saber de antemano si un puntero apunta a un área de 1, 2 o 200 bytes.

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
  300 ; const int max = 300;
 max ] ; cvet char [max];
 pc = 0 ; char * pc = 0;

 " \n Antes da atribuição \n " ) ; printf ( "\ Antes de la asignación \ n");
 "cvet = %p \n " , cvet ) ; printf ( "p = cvet% \ n", cvet);
 "pc   = %p \n " , pc ) ; printf ( "PC =% p \ n", PC);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ( "sizeof (EFPC) =% lu \ n", sizeof (EFPC));
 "sizeof(pc)   = %lu \n " , sizeof ( pc ) ) ; printf ( "sizeof (PC) =% lu \ n", sizeof (PC));

 PC = cvet;

 " \n Depois da atribuição \n " ) ; printf ( "\ Después de la asignación \ n");
 "cvet = %p \n " , cvet ) ; printf ( "p = cvet% \ n", cvet);
 "pc   = %p \n " , pc ) ; printf ( "PC =% p \ n", PC);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ( "sizeof (EFPC) =% lu \ n", sizeof (EFPC));
 "sizeof(pc)   = %lu \n " , sizeof ( pc ) ) ; printf ( "sizeof (PC) =% lu \ n", sizeof (PC)); 

Tenga en cuenta que antes de la cesión (línea 25), la PC puntero. es nulo, por lo que fue inicializado. Dado que los tamaños indican que la matriz tiene 300 bytes y el puntero sólo 8 (mi máquina es un AMD 64). Después de la asignación de ambos pasan el "punto" a la misma área de memoria, pero los tamaños no cambian. Hubo un reparto implícito a char [300] a char *, y el puntero en el juego de PC que no se puede saber el tamaño del área de memoria a la que apunta. Dado que la matriz sigue cvet saber exactamente lo que es, sin ningún tipo de crisis existencial.

La aritmética de punteros - vulgar, ¿y qué?

¿Por qué, si sé que los datos de una matriz están dispuestas lado a lado, puedo usar un puntero que saltará a la siguiente dirección y acceder al siguiente elemento. El nombre de este es punteros de la aritmética.

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
  6 ; const int max = 6; 
 max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; cvet char [max] = ( 'B', 'L', 'A', 'B', 'O', 'S');

 pc = cvet ; char * pc = cvet;

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * ( pc + i ) ) ; printf ( "% c", * (PC + i));
 )
 " \n " ) ; printf ( "\ n");

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * pc ++ ) ; printf ( "% c", * pc + +);
 )
 " \n " ) ; printf ( "\ n");


 / / Ahora el conjunto
 max ] = { 1 , 2 , 3 , 4 , 5 , 6 } ; ivet int [max] = (1, 2, 3, 4, 5, 6);
 pi = ivet ; int * pi = ivet;

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%p = %d \n " , pi, * pi ++ ) ; printf ( "% p =% d \ n", pi, pi * + +);
 )
 " \n " ) ; printf ( "\ n");

 / / En dos dimensiones
 2 ] [ 3 ] = { { 'B' , 'L' , 'A' } , { 'B' , 'O' , 'S' } } ; Char CMAT [2] [3] = (( 'B', 'L', 'A'), ( 'B', 'O', 'S'));
 ppc ; char * PPC;

 char * ) cmat ; PPC = (char *) CMAT;

 int i = 0 ; i < 2 ; i ++ ) { for (int i = 0; i <2; i + +) (
     int j = 0 ; j < 3 ; j ++ ) { for (int i = 0, j <3 j + +) (
         "%c" , * ( ppc + 3 * i + j ) ) ; printf ( "% c", * (PPC + 3 * i + j));
     )
     " \n " ) ; printf ( "\ n");
 )
 " \n " ) ; printf ( "\ n"); 

En la línea 18 del PC puntero es ahora el punto a la cvet matriz y por lo tanto para su primer elemento, el carácter 'B'. En la línea 21 el contenido de PC, que es la dirección donde se almacena con es decir, la "B", se incrementa luego de-referencia. En la primera semana el valor de i es cero, por lo que eliminar la referencia al valor 'B'. En el siguiente, la siguiente dirección se está de-hace referencia a otros caracteres almacenados en la matriz original. Es más o menos lo que el compilador hace internamente cuando se utiliza la sintaxis cvet [i]. La abstracción de la matriz le da una forma más fácil de tratar con las zonas contiguas de memoria * (PC + i).

Pero si la abstracción de la matriz es más simple, para usar que la aritmética de punteros?

Una respuesta es la línea 26. Ella hace lo mismo que la línea 21, pero un poco más rápido. En la sintaxis de la línea 21, o similar, la sintaxis de la matriz, el acceso a cualquiera de la información se puede resumir en una muy dura en los comandos:

  1. Tome la dirección base de la matriz;
  2. Añadir a abordar el valor del índice;
  3. De las referencias de la nueva dirección;

Ya con la aritmética de punteros se parece a esto:

  1. De las referencias esta dirección;

El incremento de comandos (o realizados con respecto a las direcciones) no contarán porque es parte del ciclo, a pesar de i + +; es más rápido que a = b + c;. Ahora imagina este pequeño aumento del 66% aplicado a un área de datos de 1 MB. Habrá más de 2 millones comandos de menos!

La técnica de utilizar un puntero para manejar una zona arbitraria de la memoria se utiliza generalmente en la programación de bajo nivel (el más cercano a la máquina), la manipulación de buffers y cuerdas, entre otros trucos sucios. En las entrañas de las computadoras, las operaciones que las áreas de barrido grande de memoria, son a menudo realizados con aritmética de punteros. En este nivel, Darwin reina y sólo el preparado sobrevivir. Desde aquí, el lenguaje comienza a dar un poder que sólo los puros de corazón pueden entender.

Una nota importante es que entre las líneas 31 y 38 de la experiencia se repite con números enteros. Tenga en cuenta que como los números enteros de 4 bytes, los incrementos son automáticamente a 4 de 4 bytes, no 1 a 1, es decir, el incremento se calcula automáticamente para sizeof (tipo). Mejorar un puntero medios para acceder a la siguiente área de la memoria similar a los datos reales, no sólo a la siguiente dirección. Como el tamaño de un char es un byte, cuando incremetamos un puntero a char, se mueven sólo 1 byte. Si la promoción de un puntero a una doble, vamos a pasar de 8 bytes, y así sucesivamente.

Otra observación es que un arreglo de dos dimensiones puede ser "lineal", como se muestra en las líneas 40 a 52. Esto es útil, cuando proceda, para hacer un mejor uso de la caché del procesador, por ejemplo.

void *, los punteros Pansexual

Antes he dicho que sólo fue posible asignar una matriz a un puntero que era para el mismo tipo que los datos de matriz. I mentir descaradamente! La razón es que para alguien que abandonó el cargo antes de este tema, es más seguro para creer que puede :) !

Hay dos excepciones a la regla. La primera es cuando hay una conversión explícita de tipos y de destino puntero "piensa" que está señalando el tipo adecuado. Un ejemplo es en la línea 44 del código anterior.

El segundo es el caso de los punteros a void. Un puntero a void es un puntero que no exige el tipo de datos que está en el área de la memoria a la que apunta. Él es un puntero pra un área general de la memoria, algo muy bajo nivel.

Para utilizar cualquiera de la información a la que apunta un puntero nulo antes de la referencia, usted debe hacer una conversión explícita a un tipo válido, es un int * es de-referencia a un int y char * Se hace referencia a de - char, adivinar lo que es de-referencia a un void *?

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
  6 ; const int max = 6; 
 max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; cvet char [max] = ( 'B', 'L', 'A', 'B', 'O', 'S');

 pv = cvet ; void * pv = cvet;

 / / Error de compilación.
 / / * PV
 / / ¿Cuánto es sizeof (void)?

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * ( ( ( char * ) pv ) + i ) ) ; printf ( "% c", * (((char *) PV) + i));
 )
 " \n " ) ; printf ( "\ n"); 

Puntero a void se utilizan cuando es necesario apuntar a un área general de la memoria sin tener el control / conocimiento del tipo de datos que contiene este ámbito, o funciones que no pueden hacer suposiciones sobre los tipos de sus parámetros, tales como la pthreads API lib (enlace arbitraria).

Clausura

Cuanto más ahondamos en los temas de los punteros, junto a la máquina de nosotros. Gran parte de la potencia de C y C + + viene de allí, y muchos de los problemas. La complejidad es cada vez mayor y los riesgos también. Para muchos ahí está la diversión!

Enlaces

Comentarios

Una respuesta a "Punteros y matrices

  1. Leandro Melo el 21 de mayo 2009 21:50

    Bueno didáctico. Me gustó el "punteros pansexual. :)

Deje una contestación




Powered by WP Hashcash