¿Cuándo se debe usar una doble dirección indirecta en C? ¿Alguien puede explicar con un ejemplo?
Lo que sé es que un doble direccionamiento indirecto es un puntero a un puntero. ¿Por qué necesitaría un puntero a un puntero?
manju
¿Cuándo se debe usar una doble dirección indirecta en C? ¿Alguien puede explicar con un ejemplo?
Lo que sé es que un doble direccionamiento indirecto es un puntero a un puntero. ¿Por qué necesitaría un puntero a un puntero?
pmg
Si desea tener una lista de caracteres (una palabra), puede usar char *word
Si desea una lista de palabras (una oración), puede usar char **sentence
Si quieres una lista de oraciones (un monólogo), puedes usar char ***monologue
Si quieres una lista de monólogos (una biografía), puedes usar char ****biography
Si desea una lista de biografías (una bio-biblioteca), puede utilizar char *****biolibrary
Si quieres una lista de bio-bibliotecas (un ??lol), puedes usar char ******lol
… …
sí, sé que estas podrían no ser las mejores estructuras de datos
Ejemplo de uso con un muy muy muy aburrido lol
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int wordsinsentence(char **x) {
int w = 0;
while (*x) {
w += 1;
x++;
}
return w;
}
int wordsinmono(char ***x) {
int w = 0;
while (*x) {
w += wordsinsentence(*x);
x++;
}
return w;
}
int wordsinbio(char ****x) {
int w = 0;
while (*x) {
w += wordsinmono(*x);
x++;
}
return w;
}
int wordsinlib(char *****x) {
int w = 0;
while (*x) {
w += wordsinbio(*x);
x++;
}
return w;
}
int wordsinlol(char ******x) {
int w = 0;
while (*x) {
w += wordsinlib(*x);
x++;
}
return w;
}
int main(void) {
char *word;
char **sentence;
char ***monologue;
char ****biography;
char *****biolibrary;
char ******lol;
//fill data structure
word = malloc(4 * sizeof *word); // assume it worked
strcpy(word, "foo");
sentence = malloc(4 * sizeof *sentence); // assume it worked
sentence[0] = word;
sentence[1] = word;
sentence[2] = word;
sentence[3] = NULL;
monologue = malloc(4 * sizeof *monologue); // assume it worked
monologue[0] = sentence;
monologue[1] = sentence;
monologue[2] = sentence;
monologue[3] = NULL;
biography = malloc(4 * sizeof *biography); // assume it worked
biography[0] = monologue;
biography[1] = monologue;
biography[2] = monologue;
biography[3] = NULL;
biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
biolibrary[0] = biography;
biolibrary[1] = biography;
biolibrary[2] = biography;
biolibrary[3] = NULL;
lol = malloc(4 * sizeof *lol); // assume it worked
lol[0] = biolibrary;
lol[1] = biolibrary;
lol[2] = biolibrary;
lol[3] = NULL;
printf("total words in my lol: %d\n", wordsinlol(lol));
free(lol);
free(biolibrary);
free(biography);
free(monologue);
free(sentence);
free(word);
}
Producción:
total words in my lol: 243
Solo quería señalar que un arr[a][b][c]
no es un ***arr
. Puntero de punteros usan referencias de referencias, mientras que arr[a][b][c]
se almacena como una matriz habitual en el orden principal de las filas.
– MCCCS
23 de junio de 2018 a las 9:50
@pmg puedo usar char *ptr= “mi oración”; y pase ptr a la función ¿por qué ** entonces?
– naumaan
26 de marzo de 2021 a las 20:15
@user143252 — ¿Qué pasa si tienes "my sentence"
, "his sentence"
, "her sentence"
y "their sentence"
? Tu puedes hacer char *ptr1 = "my sentence";
etc.… pero es más cómodo hacer una matriz de 5 (4 + NULL) punteros: char *ptrs[5] = {"my sentence", "his sentence", ..., NULL}
. Cuando pasas esa matriz a una función (foo(ptrs)
) la matriz se convierte automáticamente al tipo char**
!
– pmg
26 de marzo de 2021 a las 20:29
Asha
Una razón es que desea cambiar el valor del puntero pasado a una función como argumento de la función, para hacer esto necesita un puntero a un puntero.
En palabras simples, Usar **
cuando desea conservar (O retener el cambio en) la Asignación de memoria o Asignación incluso fuera de una llamada de función. (Entonces, pase dicha función con doble puntero arg.)
Este puede no ser un muy buen ejemplo, pero le mostrará el uso básico:
#include <stdio.h>
#include <stdlib.h>
void allocate(int **p)
{
*p = (int *)malloc(sizeof(int));
}
int main()
{
int *p = NULL;
allocate(&p);
*p = 42;
printf("%d\n", *p);
free(p);
}
¿Qué sería diferente si asignar fuera void allocate(int *p)
y lo llamaste como allocate(p)
?
– Incerteza
7 sep 2014 a las 20:03
@AlexanderSupertramp Sí. El código fallará. Por favor, vea la respuesta de Silviu.
– Abhishek
30 de septiembre de 2014 a las 5:37
@Asha, ¿cuál es la diferencia entre allocate(p) y allocate(&p)?
– usuario2979872
10 oct 2017 a las 15:23
@Asha – ¿No podemos simplemente devolver el puntero? Si debemos mantenerlo vacío, ¿cuál es un caso de uso práctico de este escenario?
– Shabirmean
9 oct 2018 a las 23:27
@user2979872 allocate(p): p se pasa por valor y, por lo tanto, los cambios en la función no se reflejan en el método principal. allocate(&p): p se pasa por referencia y, por lo tanto, los cambios en p se reflejan en el método principal. Sin embargo, hay una trampa. Si se usa allocate(p) y cambiamos el valor en la dirección señalada por b, entonces los cambios se reflejarán en main() para el valor, ya que el cambio ocurrió en la ubicación de la memoria directamente. Solo para reiterar, el cambio de valor en p aún no se reflejará.
–Ankit Arora
9 abr 2021 a las 10:40
Brian Joseph Spinos
pointer1 = pointer2
le das al puntero1 la dirección del puntero2.¡pero! si hace eso dentro de una función y desea que el resultado persista después de que se complete la función, necesita hacer un trabajo adicional. necesita un nuevo pointer3 solo para apuntar a pointer1. pasar pointer3 a la función.
Aquí hay un ejemplo. mire la salida a continuación primero, para entender.
#include <stdio.h>
int main()
{
int c = 1;
int d = 2;
int e = 3;
int * a = &c;
int * b = &d;
int * f = &e;
int ** pp = &a; // pointer to pointer 'a'
printf("\n a's value: %x \n", a);
printf("\n b's value: %x \n", b);
printf("\n f's value: %x \n", f);
printf("\n can we change a?, lets see \n");
printf("\n a = b \n");
a = b;
printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
printf("\n cant_change(a, f); \n");
cant_change(a, f);
printf("\n a's value is now: %x, Doh! same as 'b'... that function tricked us. \n", a);
printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
printf("\n change(pp, f); \n");
change(pp, f);
printf("\n a's value is now: %x, YEAH! same as 'f'... that function ROCKS!!!. \n", a);
return 0;
}
void cant_change(int * x, int * z){
x = z;
printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}
void change(int ** x, int * z){
*x = z;
printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}
Aquí está la salida: (Leé esto primero)
a's value: bf94c204
b's value: bf94c208
f's value: bf94c20c
can we change a?, lets see
a = b
a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see...
cant_change(a, f);
----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see
a's value is now: bf94c208, Doh! same as 'b'... that function tricked us.
NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a'
change(pp, f);
----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see
a's value is now: bf94c20c, YEAH! same as 'f'... that function ROCKS!!!.
Esta es una gran respuesta y realmente me ayudó a visualizar el propósito y la utilidad de un puntero doble.
– Justin
11 de abril de 2017 a las 15:11
@Justin, ¿revisaste mi respuesta arriba de esta? es mas limpio 🙂
– Brian Joseph Spinos
11 de abril de 2017 a las 23:51
Gran respuesta, solo falta explicar que void cant_change(int * x, int * z)
falla porque sus parámetros son solo nuevos punteros de alcance local que se inicializan de la misma manera que los punteros a y f (por lo que no son lo mismo que a y f).
– Pedro Reyes
17 mayo 2017 a las 16:59
¿Sencillo? ¿En serio? 😉
– alk
16 de abril de 2019 a las 16:46
esta respuesta realmente explica uno de los usos más comunes de puntero a puntero, ¡gracias!
– tonyjosi
27 de enero de 2020 a las 5:51
Silvio
Agregando a la respuesta de Asha, si usa un solo puntero al ejemplo a continuación (por ejemplo, alloc1() ), perderá la referencia a la memoria asignada dentro de la función.
#include <stdio.h>
#include <stdlib.h>
void alloc2(int** p) {
*p = (int*)malloc(sizeof(int));
**p = 10;
}
void alloc1(int* p) {
p = (int*)malloc(sizeof(int));
*p = 10;
}
int main(){
int *p = NULL;
alloc1(p);
//printf("%d ",*p);//undefined
alloc2(&p);
printf("%d ",*p);//will print 10
free(p);
return 0;
}
La razón por la que ocurre así es que en alloc1
el puntero se pasa por valor. Entonces, cuando se reasigna al resultado de la malloc
llama dentro de alloc1
el cambio no pertenece al código en un ámbito diferente.
Ziyuán
Vi un muy buen ejemplo hoy, de esta entrada de blogcomo resumo a continuación.
Imagina que tienes una estructura para nodos en una lista enlazada, que probablemente sea
typedef struct node
{
struct node * next;
....
} node;
Ahora desea implementar un remove_if
función, que acepta un criterio de eliminación rm
como uno de los argumentos y atraviesa la lista enlazada: si una entrada satisface el criterio (algo así como rm(entry)==true
), su nodo será eliminado de la lista. Al final, remove_if
devuelve el encabezado (que puede ser diferente del encabezado original) de la lista enlazada.
puedes escribir
for (node * prev = NULL, * curr = head; curr != NULL; )
{
node * const next = curr->next;
if (rm(curr))
{
if (prev) // the node to be removed is not the head
prev->next = next;
else // remove the head
head = next;
free(curr);
}
else
prev = curr;
curr = next;
}
como tu for
círculo. El mensaje es, sin punteros dobles, tienes que mantener un prev
variable para reorganizar los punterosy manejar los dos casos diferentes.
Pero con punteros dobles, en realidad puedes escribir
// now head is a double pointer
for (node** curr = head; *curr; )
{
node * entry = *curr;
if (rm(entry))
{
*curr = entry->next;
free(entry);
}
else
curr = &entry->next;
}
no necesitas un prev
ahora porque puedes modificar directamente lo que prev->next
señaló a.
Para aclarar las cosas, sigamos un poco el código. Durante la eliminación:
entry == *head
: será *head (==*curr) = *head->next
— head
ahora apunta al puntero del nuevo nodo de encabezado. Esto se hace cambiando directamente head
el contenido de un nuevo puntero.entry != *head
: similar, *curr
qué es prev->next
señaló, y ahora apunta a entry->next
.No importa en qué caso, puede reorganizar los punteros de forma unificada con punteros dobles.
1. Concepto básico –
Cuando declara lo siguiente: –
1. char *ch – (llamado puntero de carácter)
– ch contiene la dirección de un solo carácter.
– (*ch) eliminará la referencia al valor del carácter.
2. char **ch –
‘ch’ contiene la dirección de una matriz de punteros de caracteres. (como en 1)
‘*ch’ contiene la dirección de un solo carácter. (Tenga en cuenta que es diferente de 1, debido a la diferencia en la declaración).
(**ch) eliminará la referencia al valor exacto del carácter.
Agregar más punteros expande la dimensión de un tipo de datos, de carácter a cadena, a matriz de cadenas, etc. Puede relacionarlo con una matriz 1d, 2d, 3d.
Entonces, el uso del puntero depende de cómo lo declare.
Aquí hay un código simple …
int main()
{
char **p;
p = (char **)malloc(100);
p[0] = (char *)"Apple"; // or write *p, points to location of 'A'
p[1] = (char *)"Banana"; // or write *(p+1), points to location of 'B'
cout << *p << endl; //Prints the first pointer location until it finds '\0'
cout << **p << endl; //Prints the exact character which is being pointed
*p++; //Increments for the next string
cout << *p;
}
2. Otra aplicación de punteros dobles –
(esto también cubriría pase por referencia)
Suponga que desea actualizar un carácter de una función. Si intenta lo siguiente: –
void func(char ch)
{
ch="B";
}
int main()
{
char ptr;
ptr="A";
printf("%c", ptr);
func(ptr);
printf("%c\n", ptr);
}
La salida será AA. Esto no funciona, ya que ha “Pasado por valor” a la función.
La forma correcta de hacerlo sería –
void func( char *ptr) //Passed by Reference
{
*ptr="B";
}
int main()
{
char *ptr;
ptr = (char *)malloc(sizeof(char) * 1);
*ptr="A";
printf("%c\n", *ptr);
func(ptr);
printf("%c\n", *ptr);
}
Ahora extienda este requisito para actualizar una cadena en lugar de un carácter.
Para esto, debe recibir el parámetro en la función como un puntero doble.
void func(char **str)
{
strcpy(str, "Second");
}
int main()
{
char **str;
// printf("%d\n", sizeof(char));
*str = (char **)malloc(sizeof(char) * 10); //Can hold 10 character pointers
int i = 0;
for(i=0;i<10;i++)
{
str = (char *)malloc(sizeof(char) * 1); //Each pointer can point to a memory of 1 character.
}
strcpy(str, "First");
printf("%s\n", str);
func(str);
printf("%s\n", str);
}
En este ejemplo, el método espera un puntero doble como parámetro para actualizar el valor de una cadena.
jason
Los punteros a punteros también son útiles como “identificadores” de la memoria donde desea pasar un “identificador” entre funciones para la memoria reubicable. Básicamente, eso significa que la función puede cambiar la memoria a la que apunta el puntero dentro de la variable handle, y cada función u objeto que usa el handle apuntará correctamente a la memoria recién reubicada (o asignada). A las bibliotecas les gusta hacer esto con tipos de datos “opacos”, es decir, tipos de datos en los que no tiene que preocuparse por lo que están haciendo con la memoria que se apunta, simplemente pasa el “mango” entre el funciones de la biblioteca para realizar algunas operaciones en esa memoria … las funciones de la biblioteca pueden asignar y desasignar la memoria bajo el capó sin que tenga que preocuparse explícitamente por el proceso de administración de la memoria o hacia dónde apunta el controlador.
Por ejemplo:
#include <stdlib.h>
typedef unsigned char** handle_type;
//some data_structure that the library functions would work with
typedef struct
{
int data_a;
int data_b;
int data_c;
} LIB_OBJECT;
handle_type lib_create_handle()
{
//initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
handle_type handle = malloc(sizeof(handle_type));
*handle = malloc(sizeof(LIB_OBJECT) * 10);
return handle;
}
void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }
void lib_func_b(handle_type handle)
{
//does something that takes input LIB_OBJECTs and makes more of them, so has to
//reallocate memory for the new objects that will be created
//first re-allocate the memory somewhere else with more slots, but don't destroy the
//currently allocated slots
*handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);
//...do some operation on the new memory and return
}
void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }
void lib_free_handle(handle_type handle)
{
free(*handle);
free(handle);
}
int main()
{
//create a "handle" to some memory that the library functions can use
handle_type my_handle = lib_create_handle();
//do something with that memory
lib_func_a(my_handle);
//do something else with the handle that will make it point somewhere else
//but that's invisible to us from the standpoint of the calling the function and
//working with the handle
lib_func_b(my_handle);
//do something with new memory chunk, but you don't have to think about the fact
//that the memory has moved under the hood ... it's still pointed to by the "handle"
lib_func_c(my_handle);
//deallocate the handle
lib_free_handle(my_handle);
return 0;
}
Espero que esto ayude,
jason
¿Cuál es la razón por la que el tipo de identificador es char sin firmar**? ¿Funcionaría void** igual de bien?
–ConnorClark
31 de mayo de 2016 a las 5:44
unsigned char
se usa específicamente porque estamos almacenando un puntero a datos binarios que se representarán como bytes sin procesar. Utilizando void
requerirá una conversión en algún momento y, por lo general, no es tan legible como la intención de lo que se está haciendo.
– Jasón
7 junio 2016 a las 14:15
Ten cuidado; la frase “doble puntero” también se refiere al tipo
double*
.–Keith Thompson
19 de octubre de 2016 a las 2:02
Tome nota: la respuesta a esta pregunta es diferente para C y C++; no agregue la etiqueta c+ a esta pregunta muy antigua.
– BЈовић
1 de agosto de 2017 a las 8:32
@BЈовић Aunque es una pregunta y un comentario antiguos, ¿cuál es la diferencia en el uso del puntero doble entre C y C++? Después de ver su comentario de que son diferentes, traté de dar la respuesta por mí mismo, pero aún veo poca diferencia en el uso de punteros dobles en C y C++.
– Sangjun Lee
14 de septiembre de 2020 a las 4:53
se puede usar para una matriz irregular de caracteres, es decir, una lista de listas donde cada lista tiene una longitud diferente
– campo propio
20 de enero a las 2:40