c++ - que - Utiliza para niveles mĂșltiples de referencias de puntero?
punteros en c (17)
¿Cuándo el uso de punteros en cualquier idioma requiere que alguien use más de uno, digamos un puntero triple? ¿Cuándo tiene sentido utilizar un puntero triple en lugar de usar un puntero normal?
Por ejemplo:
char * * *ptr;
en lugar de
char *ptr;
Arrays N-dimensional dinámicamente asignados, donde N> 3, requieren tres o más niveles de indirección en C.
Char *** foo se puede interpretar como un puntero a una matriz bidimensional de cadenas.
Cuando utiliza estructuras de datos anidadas dinámicamente asignadas (o vinculadas por puntero). Estas cosas están todas vinculadas por punteros.
ImageMagicks''s Wand tiene una función que se declara como
WandExport char* * * * * * DrawGetVectorGraphics ( const DrawingWand *)
La indirección doble simplifica muchos algoritmos de equilibrio de árboles, donde generalmente uno quiere ser capaz de "desvincular" de manera eficiente un subárbol de su padre. Por ejemplo, una implementación de árbol AVL podría usar:
void rotateLeft(struct tree **tree) {
struct tree *t = *tree,
*r = t->right,
*rl = r->left;
*tree = r;
r->left = t;
t->right = rl;
}
Sin el "doble puntero", tendríamos que hacer algo más complicado, como hacer un seguimiento explícito del padre de un nodo y si es una rama izquierda o derecha.
Las funciones que encapsulan la creación de recursos a menudo usan punteros dobles. Es decir, pasa la dirección de un puntero a un recurso. La función puede crear el recurso en cuestión y establecer el puntero para señalarlo. Esto solo es posible si tiene la dirección del puntero en cuestión, por lo que debe ser un doble puntero.
Los punteros a punteros raramente se usan en C ++. Ellos principalmente tienen dos usos.
El primer uso es pasar una matriz. char**
, por ejemplo, es un puntero a puntero a char, que a menudo se usa para pasar una matriz de cadenas. Los punteros a las matrices no funcionan por buenas razones, pero ese es un tema diferente (consulte las preguntas frecuentes de comp.lang.c si desea obtener más información). En algunos casos raros, es posible que vea un tercer *
utilizado para una matriz de matrices, pero comúnmente es más efectivo almacenar todo en una matriz contigua e indexarla manualmente (por ejemplo, array[x+y*width]
lugar de array[x][y]
). En C ++, sin embargo, esto es mucho menos común debido a las clases de contenedor.
El segundo uso es pasar por referencia. Un parámetro int*
permite que la función modifique el entero apuntado por la función de llamada, y se usa comúnmente para proporcionar múltiples valores de retorno. Este patrón de pasar parámetros por referencia para permitir retornos múltiples todavía está presente en C ++, pero, al igual que otros usos de pass-by-reference, generalmente se reemplaza por la introducción de referencias reales. La otra razón para pasar por referencia, evitar la copia de construcciones complejas, también es posible con la referencia de C ++.
C ++ tiene un tercer factor que reduce el uso de punteros múltiples: tiene string
. Una referencia a una cadena puede tomar el tipo char**
en C, de modo que la función puede cambiar la dirección de la variable de cadena que se pasa, pero en C ++, generalmente vemos string&
lugar.
Para ser sincero, rara vez he visto un triple puntero.
Eché un vistazo a la búsqueda de códigos de Google, y hay some examples , pero no muy esclarecedores. (ver enlaces al final - A SO no le gustan)
Como otros han mencionado, los indicadores dobles verán de vez en cuando. Los simples indicadores simples son útiles porque apuntan a algún recurso asignado. Los punteros dobles son útiles porque puede pasarlos a una función y hacer que la función complete el puntero "simple" para usted.
Parece que quizás necesites una explicación sobre qué son los indicadores y cómo funcionan. Primero debe comprender eso, si no lo hace ya.
Particularmente en dialectos de subproceso único de C que no usan agresivamente el análisis de alias basado en el tipo, a veces puede ser útil escribir administradores de memoria que puedan acomodar objetos reubicables. En lugar de dar a las aplicaciones punteros directos a trozos de memoria, la aplicación recibe punteros en una tabla de descriptores de identificador, cada uno de los cuales contiene un puntero a un fragmento real de memoria junto con una palabra que indica su tamaño. Si uno necesita asignar espacio para un struct woozle
, uno podría decir:
struct woozle **my_woozle = newHandle(sizeof struct woozle);
y luego acceder (algo torpemente en sintaxis C - la sintaxis es más clara en Pascal): (* my_woozle) -> someField = 23; es importante que las aplicaciones no mantengan punteros directos al destino de cualquier manejador en llamadas a funciones que asignan memoria, pero si solo existe un solo puntero a cada bloque identificado por un manejador, el administrador de memoria podrá mover las cosas en caso de que la fragmentación se convierta un problema.
El enfoque no funciona tan bien en los dialectos de C que persigue agresivamente el alias basado en tipos, ya que el puntero devuelto por NewHandle
no identifica un puntero de tipo struct woozle*
sino que identifica un puntero de tipo void*
, e incluso en las plataformas donde esos tipos de apuntadores tendrían la misma representación, el Estándar no requiere que las implementaciones interpreten un lanzamiento de puntero como una indicación de que debería esperarse que ocurra un aliasing.
Si tiene que modificar un puntero dentro de una función, debe pasarle una referencia.
Si trabajas con "objetos" en C, probablemente tengas esto:
struct customer {
char *name;
char *address;
int id;
} typedef Customer;
Si quieres crear un objeto, harías algo como esto:
Customer *customer = malloc(sizeof Customer);
// Initialise state.
Estamos utilizando un puntero a una struct
aquí porque los argumentos struct
se pasan por valor y tenemos que trabajar con un objeto. (También: Objective-C, un lenguaje de contenedor orientado a objetos para C, utiliza punteros internos pero visibles para struct
s)
Si necesito almacenar varios objetos, utilizo una matriz:
Customer **customers = malloc(sizeof(Customer *) * 10);
int customerCount = 0;
Como una variable de matriz en C apunta al primer elemento, uso un puntero ... otra vez. Ahora tengo doble punteros.
Pero ahora imagine que tengo una función que filtra la matriz y devuelve una nueva. Pero imagine que no puede hacerlo a través del mecanismo de devolución porque debe devolver un código de error; mi función accede a una base de datos. Necesito hacerlo a través de un argumento de referencia. Esta es la firma de mi función:
int filterRegisteredCustomers(Customer **unfilteredCustomers, Customer ***filteredCustomers, int unfilteredCount, int *filteredCount);
La función toma una serie de clientes y devuelve una referencia a una matriz de clientes (que son punteros a una struct
). También toma la cantidad de clientes y devuelve la cantidad de clientes filtrados (nuevamente, argumento de referencia).
Puedo llamarlo de esta manera:
Customer **result, int n = 0;
int errorCode = filterRegisteredCustomers(customers, &result, customerCount, &n);
Podría seguir imaginando más situaciones ... Esta es sin el typedef
:
int fetchCustomerMatrix(struct customer ****outMatrix, int *rows, int *columns);
Obviamente, sería un desarrollador horrible y / o sádico para dejarlo así. Entonces, usando:
typedef Customer *CustomerArray;
typedef CustomerArray *CustomerMatrix;
Puedo hacer esto:
int fetchCustomerMatrix(CustomerMatrix *outMatrix, int *rows, int *columns);
Si su aplicación se usa en un hotel donde usa una matriz por nivel, probablemente necesite una matriz en una matriz:
int fetchHotel(struct customer *****hotel, int *rows, int *columns, int *levels);
O solo esto:
typedef CustomerMatrix *Hotel;
int fetchHotel(Hotel *hotel, int *rows, int *columns, int *levels);
No me hagas comenzar siquiera en una variedad de hoteles:
int fetchHotels(struct customer ******hotels, int *rows, int *columns, int *levels, int *hotels);
... arreglado en una matriz (¿algún tipo de gran corporación hotelera?):
int fetchHotelMatrix(struct customer *******hotelMatrix, int *rows, int *columns, int *levels, int *hotelRows, int *hotelColumns);
Lo que estoy tratando de decir es que puedes imaginar aplicaciones locas para múltiples indirecciones. Solo asegúrese de usar typedef
si los punteros múltiples son una buena idea y decide usarlos.
(¿Este post cuenta como una aplicación para c2.com/cgi/wiki?ThreeStarProgrammer ?)
Tiene sentido usar un puntero a un puntero siempre que el puntero apunte hacia un puntero (esta cadena es ilimitada, por lo tanto, son posibles los "triples punteros", etc.).
La razón para crear dicho código es porque desea que el compilador / intérprete pueda verificar correctamente los tipos que está utilizando (evite errores misteriosos).
No tiene que usar dichos tipos; siempre puede usar un simple "void *" y encasillar cada vez que necesite desreferenciar el puntero y acceder a los datos a los que apunta el puntero. Pero eso suele ser una mala práctica y propenso a errores; sin duda, hay casos en los que usar void * es realmente bueno y hace que el código sea mucho más elegante. Piense que es más como su último recurso.
=> Es principalmente para ayudar al compilador a asegurarse de que las cosas se usen de la manera que se supone que deben usarse.
Un puntero es simplemente una variable que contiene una dirección de memoria.
Entonces, usa un puntero a un puntero cuando quiere mantener la dirección de una variable de puntero.
Si desea devolver un puntero y ya está utilizando la variable de retorno para algo, pasará la dirección de un puntero. La función luego desreferencia este puntero para que pueda establecer el valor del puntero. Es decir, el parámetro de esa función sería un puntero a un puntero.
Varios niveles de direccionamiento indirecto también se utilizan para matrices multidimensionales. Si desea devolver una matriz bidimensional, usaría un puntero triple. Al usarlos para matrices multidimensionales, tenga cuidado de lanzar correctamente a medida que avanza en cada nivel de indirección.
Aquí hay un ejemplo de devolver un valor de puntero a través de un parámetro:
//Not a very useful example, but shows what I mean...
bool getOffsetBy3Pointer(const char *pInput, char **pOutput)
{
*pOutput = pInput + 3;
return true;
}
Y llamas a esta función así:
const char *p = "hi you";
char *pYou;
bool bSuccess = getOffsetBy3Pointer(p, &pYou);
assert(!stricmp(pYou, "you"));
Un uso estándar de dos punteros, por ejemplo: myStruct ** ptrptr, es como un puntero a un puntero. Por ejemplo, como parámetro de función, esto le permite cambiar la estructura real a la que apunta la persona que llama, en lugar de solo poder cambiar los valores dentro de esa estructura.
Utiliza un nivel extra de direccionamiento indirecto, o apuntando, cuando es necesario, no porque sea divertido. Raramente ves triples punteros; No creo haber visto un puntero cuádruple (y mi mente se quedaría boquiabierta si lo hiciera).
Las tablas de estado se pueden representar mediante una matriz 2D de un tipo de datos apropiado (punteros a una estructura, por ejemplo). Cuando escribí un código casi genérico para hacer tablas de estado, recuerdo tener una función que tomaba un puntero triple, que representaba una matriz bidimensional de punteros a las estructuras. ¡Ay!
cada estrella debe leerse como "a la que apunta un puntero",
char *foo;
es "char que apunta por un puntero foo". sin embargo
char *** foo;
es "char que apunta por un puntero que apunta a un puntero que apunta a un puntero foo". Por lo tanto, foo es un puntero. En esa dirección hay un segundo puntero. En la dirección señalada por ese es un tercer puntero. La desreferenciación del tercer puntero da como resultado un char. Si eso es todo lo que hay que hacer, es difícil justificarlo en gran medida.
Sin embargo, aún es posible hacer algunos trabajos útiles. Imagina que estamos escribiendo un sustituto para bash, o algún otro programa de control de procesos. Queremos gestionar las invocaciones de nuestros procesos de forma orientada a objetos ...
struct invocation {
char* command; // command to invoke the subprocess
char* path; // path to executable
char** env; // environment variables passed to the subprocess
...
}
Pero queremos hacer algo elegante. Queremos tener una forma de explorar todos los diferentes conjuntos de variables de entorno según lo que ve cada subproceso. para hacer eso, juntamos cada conjunto de miembros env
de las instancias de invocación en una matriz env_list
y la pasamos a la función que trata con eso:
void browse_env(size_t envc, char*** env_list);
int main( int argc, char** argv );