¿Por qué necesito usar el tipo ** para señalar el tipo *?

He estado leyendo Learn C The Hard Way durante unos días, pero esto es algo que realmente quiero entender. Zed, el autor, escribió que char ** es para un “puntero a (un puntero a char)”, y diciendo que esto es necesario porque estoy tratando de señalar algo bidimensional.

Aquí está lo que está escrito exactamente en la página web

Un char * ya es un “puntero a char”, por lo que es solo una cadena. Sin embargo, necesita 2 niveles, ya que los nombres son bidimensionales, lo que significa que necesita caracteres ** para un tipo de “puntero a (puntero a char)”.

¿Esto significa que tengo que usar una variable que pueda apuntar a algo de 2 dimensiones, y por eso necesito dos ** ?

Solo un poco de seguimiento, ¿esto también se aplica a n dimension?

Aquí está el código relevante

 char *names[] = { "Alan", "Frank", "Mary", "John", "Lisa" }; char **cur_name = names; 

No, ese tutorial es de calidad cuestionable. No recomendaría continuar leyendo.

Un char** es un puntero a puntero. No es una matriz 2D. No es un puntero a una matriz. No es un puntero a una matriz 2D.

El autor del tutorial probablemente se confunda porque existe una práctica errónea e incorrecta ampliamente extendida que dice que debe asignar matrices 2D dinámicas como esta:

 // BAD! Do not do like this! int** heap_fiasco; heap_fiasco = malloc(X * sizeof(int*)); for(int x=0; x 

Sin embargo, esto no es una matriz 2D, es una tabla de búsqueda lenta y fragmentada asignada en todo el montón. La syntax de acceder a un elemento en la tabla de búsqueda, heap_fiasco[x][y] , se parece a la syntax de indexación de matriz, por lo que mucha gente, por alguna razón, cree que así es como se asignan las matrices 2D.

La forma correcta de asignar dinámicamente una matriz 2D es:

 // correct int (*array2d)[Y] = malloc(sizeof(int[X][Y])); 

Puede ver que el primero no es una matriz porque si hace memcpy(heap_fiasco, heap_fiasco2, sizeof(int[X][Y])) el código se bloqueará y se quemará. Los elementos no están asignados en la memoria adyacente.

De memcpy(heap_fiasco, heap_fiasco2, sizeof(*heap_fiasco)) similar, memcpy(heap_fiasco, heap_fiasco2, sizeof(*heap_fiasco)) también se bloqueará y se quemará, pero por otros motivos: obtienes el tamaño de un puntero no de una matriz.

Mientras que memcpy(array2d, array2d_2, sizeof(*array2d)) funcionará, porque es una matriz 2D.

Los indicadores me tomaron un tiempo para entender. Recomiendo dibujar diagtwigs.

Por favor, lea y comprenda esta parte del tutorial de C ++ (al menos con respecto a los punteros, los diagtwigs realmente me ayudaron).

Decirle que necesita un puntero a un puntero a char para una matriz bidimensional es una mentira. No lo necesita, pero es una forma de hacerlo.

La memoria es secuencial. Si quiere poner 5 caracteres (letras) seguidos como en la palabra hola , podría definir 5 variables y recordar siempre en qué orden usarlas, pero ¿qué sucede cuando quiere guardar una palabra con 6 letras? ¿Define más variables? ¿No sería más fácil si los almacenaras en la memoria en una secuencia?

Entonces, lo que usted hace es preguntar al sistema operativo por 5 caracteres (y cada carácter resulta ser un byte) y el sistema le devuelve una dirección de memoria donde comienza su secuencia de 5 caracteres. Toma esta dirección y la almacena en una variable que llamamos puntero, porque apunta a su memoria.

El problema con los punteros es que son solo direcciones. ¿Cómo sabes qué se almacena en esa dirección? ¿Es 5 caracteres o es un gran número binario que tiene 8 bytes? ¿O es parte de un archivo que cargó? ¿Cómo lo sabes?

Aquí es donde el lenguaje de progtwigción como C intenta ayudar dándote tipos. Un tipo le dice qué almacena la variable y los punteros también tienen tipos, pero sus tipos le indican a qué apunta el puntero. Por lo tanto, char * es un puntero a una ubicación de memoria que contiene un único char o una secuencia de chars . Tristemente, la parte acerca de cuántas char hay allí necesitarás recordarte a ti mismo. Usualmente almacenas esa información en una variable que guardas para recordar cuántos caracteres hay.

Entonces, cuando quieres tener una estructura de datos bidimensional, ¿cómo la representas?

Esto se explica mejor con un ejemplo. Hagamos una matriz:

 1 2 3 4 5 6 7 8 9 10 11 12 

Tiene 4 columnas y 3 filas. ¿Cómo almacenamos eso?

Bueno, podemos hacer 3 secuencias de 4 números cada una. La primera secuencia es 1 2 3 4 , la segunda es 5 6 7 8 y la tercera y última secuencia es 9 10 11 12 . Entonces, si queremos almacenar 4 números, le pediremos al sistema que nos reserve 4 números y nos dé un puntero a ellos. Estos serán punteros a números . Sin embargo, dado que necesitamos 3 de ellos, le pediremos al sistema que nos dé 3 punteros a los números de los punteros .

Y así es como terminas con la solución propuesta …

La otra forma de hacerlo sería darse cuenta de que necesita 4 veces 3 números y simplemente pedirle al sistema que almacene 12 números en una secuencia. Pero, ¿cómo accedes al número en la fila 2 y la columna 3? Aquí es donde entra la matemática, pero probemos en nuestro ejemplo:

 1 2 3 4 5 6 7 8 9 10 11 12 

Si los almacenamos uno al lado del otro, se verían así:

 offset from start: 0 1 2 3 4 5 6 7 8 9 10 11 numbers in memory: [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] 

Entonces nuestro mapeo es así:

 row | column | offset | value 1 | 1 | 0 | 1 1 | 2 | 1 | 2 1 | 3 | 2 | 3 1 | 4 | 3 | 4 2 | 1 | 4 | 5 2 | 2 | 5 | 6 2 | 3 | 6 | 7 2 | 4 | 7 | 8 3 | 1 | 8 | 9 3 | 2 | 9 | 10 3 | 3 | 10 | 11 3 | 4 | 11 | 12 

Y ahora tenemos que encontrar una fórmula fácil y fácil para convertir una fila y una columna en una compensación … Volveré cuando tenga más tiempo … Ahora mismo necesito llegar a casa (lo siento). ..

Editar: Estoy un poco tarde, pero déjame continuar. Para encontrar el desplazamiento de cada uno de los números de una fila y columna, puede usar la siguiente fórmula:

 offset = (row - 1) * 4 + (column - 1) 

Si notas los dos -1 aquí y piensas en ello, entenderás que es porque nuestras numeraciones de filas y columnas comienzan con 1 que tenemos que hacer esto y es por eso que los científicos informáticos prefieren las compensaciones basadas en cero (debido a esta fórmula). Sin embargo, con punteros en C, el lenguaje mismo aplica esta fórmula cuando utiliza una matriz multidimensional. Y, por lo tanto, esta es la otra forma de hacerlo.

De su pregunta lo que entiendo es que usted está preguntando por qué necesita char ** para la variable que se declara como * nombres []. Entonces la respuesta es cuando simplemente escribes nombres [], que esa es la syntax de la matriz y la matriz es básicamente un puntero.

Entonces cuando escribe * nombres [] significa que está apuntando a una matriz. Y como una matriz es básicamente un puntero, eso significa que tiene un puntero a un puntero y es por eso que el comstackdor no se quejará si escribe

char ** cur_name = nombres;

En la línea superior, usted declara un puntero a un puntero de carácter y luego lo inicializa con el puntero a una matriz (recuerde que la matriz también es un puntero).