Quando um duplo indireção deve ser usado em C? Alguém pode explicar com um exemplo?
O que eu sei é que uma dupla indireção é um ponteiro para um ponteiro. Por que eu precisaria de um ponteiro para um ponteiro?
Quando um duplo indireção deve ser usado em C? Alguém pode explicar com um exemplo?
O que eu sei é que uma dupla indireção é um ponteiro para um ponteiro. Por que eu precisaria de um ponteiro para um ponteiro?
Respostas:
Se você deseja ter uma lista de caracteres (uma palavra), pode usar char *word
Se você quiser uma lista de palavras (uma frase), poderá usar char **sentence
Se você quiser uma lista de frases (um monólogo), poderá usar char ***monologue
Se você deseja uma lista de monólogos (uma biografia), pode usar char ****biography
Se você quiser uma lista de biografias (uma bio-biblioteca), poderá usar char *****biolibrary
Se você quiser uma lista de bibliotecas biológicas (a ?? lol), poderá usar char ******lol
... ...
sim, eu sei que essas podem não ser as melhores estruturas de dados
Exemplo de uso com um muito, muito, muito chato 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);
}
Resultado:
total de palavras na minha lol: 243
arr[a][b][c]
não é a ***arr
. Ponteiro de ponteiros usa referências de referências, enquanto arr[a][b][c]
é armazenado como uma matriz usual na ordem principal das linhas.
Uma razão é que você deseja alterar o valor do ponteiro passado para uma função como argumento da função; para fazer isso, é necessário um ponteiro para um ponteiro.
Em palavras simples, use **
quando quiser preservar (OU reter alterações) a alocação ou atribuição de memória mesmo fora de uma chamada de função. (Portanto, passe essa função com o ponteiro duplo arg.)
Este pode não ser um exemplo muito bom, mas mostra o uso básico:
void allocate(int** p)
{
*p = (int*)malloc(sizeof(int));
}
int main()
{
int* p = NULL;
allocate(&p);
*p = 42;
free(p);
}
void allocate(int *p)
e você o chamou como allocate(p)
?
pointer1 = pointer2
, você fornece ao ponteiro1 o endereço do ponteiro2.mas! se você fizer isso dentro de uma função e desejar que o resultado persista após a conclusão da função, precisará fazer um trabalho extra. você precisa de um novo ponteiro3 apenas para apontar para o ponteiro1. passe o ponteiro3 para a função
aqui está um exemplo. observe primeiro a saída abaixo, 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);
}
Aqui está a saída: ( leia isto primeiro )
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!!!.
Adicionando à resposta de Asha , se você usar um ponteiro único para o exemplo abaixo (por exemplo, assign1 ()), perderá a referência à memória alocada dentro da função.
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;
}
A razão pela qual isso ocorre é que no alloc1
ponteiro é passado por valor. Portanto, quando é reatribuído ao resultado da malloc
chamada dentro de alloc1
, a alteração não se refere ao código em um escopo diferente.
free(p)
não é suficiente, você precisa if(p) free(*p)
também
*p
avalia como int
mantendo um valor de 10, passar int
para free () `é uma má ideia.
alloc1()
introduz um vazamento de memória. O valor do ponteiro a ser liberado é perdido, retornando da função.
Eu vi um exemplo muito bom hoje, desta postagem do blog , como resumir abaixo.
Imagine que você tenha uma estrutura para nós em uma lista vinculada, o que provavelmente é
typedef struct node
{
struct node * next;
....
} node;
Agora você deseja implementar uma remove_if
função, que aceita um critério de remoção rm
como um dos argumentos e percorre a lista vinculada: se uma entrada satisfizer o critério (algo como rm(entry)==true
), seu nó será removido da lista. No final, remove_if
retorna a cabeça (que pode ser diferente da cabeça original) da lista vinculada.
Você pode escrever
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 seu for
loop. A mensagem é que, sem ponteiros duplos, é necessário manter uma prev
variável para reorganizar os ponteiros e lidar com os dois casos diferentes.
Mas com ponteiros duplos, você pode escrever
// 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;
}
Você não precisa de um prev
agora porque pode modificar diretamente o que prev->next
apontou .
Para tornar as coisas mais claras, vamos seguir um pouco o código. Durante a remoção:
entry == *head
: será *head (==*curr) = *head->next
- head
agora aponta para o ponteiro do novo nó de cabeçalho. Você faz isso alterando diretamente head
o conteúdo de um novo ponteiro.entry != *head
: da mesma forma, *curr
é o que prev->next
apontou e agora aponta para entry->next
.Não importa em que caso, você pode reorganizar os ponteiros de maneira unificada com ponteiros duplos.
1. Conceito Básico -
Quando você declara o seguinte: -
1. char * ch - (chamado ponteiro de caractere)
- ch contém o endereço de um único caractere.
- (* ch) desreferirá o valor do caractere.
2. char ** ch -
'ch' contém o endereço de uma matriz de ponteiros de caracteres. (como em 1)
'* ch' contém o endereço de um único caractere. (Observe que é diferente de 1, devido à diferença na declaração).
(** ch) desreferirá o valor exato do personagem.
A adição de mais ponteiros expande a dimensão de um tipo de dados, do caractere para a seqüência de caracteres, para a matriz de seqüências de caracteres, etc.
Portanto, o uso do ponteiro depende de como você o declara.
Aqui está um código simples ..
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. Outra aplicação de ponteiros duplos -
(isso também cobriria passagem por referência)
Suponha que você queira atualizar um caractere de uma função. Se você tentar o seguinte: -
void func(char ch)
{
ch = 'B';
}
int main()
{
char ptr;
ptr = 'A';
printf("%c", ptr);
func(ptr);
printf("%c\n", ptr);
}
A saída será AA. Isso não funciona, pois você "Passou por valor" para a função.
A maneira correta de fazer isso seria -
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);
}
Agora estenda esse requisito para atualizar uma sequência em vez de caractere.
Para isso, você precisa receber o parâmetro na função como um ponteiro duplo.
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);
}
Neste exemplo, o método espera um ponteiro duplo como parâmetro para atualizar o valor de uma sequência.
#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; }
Mas você pode fazer isso sem usar o ponteiro duplo também.
char
ponteiros. Um ponteiro para uma matriz de char*
seria digitado, por exemplo, como este: char(*(*p)[42])
define p
como ponteiro para uma matriz de 42 ponteiros para char
.
*str = ...
str
é desreferenciado, não inicializado, invocando um comportamento indefinido.
malloc(sizeof(char) * 10);
não aloca espaço para 10 ponteiro para char
mas para 10 char
única ..
for(i=0;i<10;i++) { str = ...
falha ao usar o índice i
.
Ponteiros para ponteiros também são úteis como "alças" para a memória, onde você deseja passar um "identificador" entre as funções para recolocar a memória. Isso basicamente significa que a função pode alterar a memória apontada pelo ponteiro dentro da variável do identificador, e cada função ou objeto que estiver usando o identificador apontará corretamente para a memória recém-realocada (ou alocada). Bibliotecas gostam de fazer isso com tipos de dados "opacos", ou seja, quando você não precisa se preocupar com o que eles estão fazendo com a memória apontada, basta passar o "identificador" entre os funções da biblioteca para executar algumas operações nessa memória ...
Por exemplo:
#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 isto ajude,
Jason
unsigned char
é usado especificamente porque estamos armazenando um ponteiro para dados binários que serão representados como bytes brutos. O uso void
exigirá uma conversão em algum momento e geralmente não é tão legível quanto a intenção do que está sendo feito.
int main(int argc, char **argv)
No segundo parâmetro, você tem: ponteiro para ponteiro para char.
Observe que a notação do ponteiro ( char* c
) e a notação da matriz ( char c[]
) são intercambiáveis nos argumentos da função. Então você também pode escrever char *argv[]
. Em outras palavras char *argv[]
e char **argv
são intercambiáveis.
O que o descrito acima é de fato uma matriz de seqüências de caracteres (os argumentos da linha de comando que são fornecidos a um programa na inicialização).
Consulte também esta resposta para obter mais detalhes sobre a assinatura da função acima.
char* c
) e notação de matriz ( char c[]
) são intercambiáveis" (e têm o mesmo significado exato) nos argumentos da função . Eles são diferentes, porém, fora dos argumentos da função.
Por exemplo, convém garantir que, ao liberar a memória de algo, defina o ponteiro como nulo posteriormente.
void safeFree(void** memory) {
if (*memory) {
free(*memory);
*memory = NULL;
}
}
Quando você chama essa função, você o chama com o endereço de um ponteiro
void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);
Agora myMemory
está definido como NULL e qualquer tentativa de reutilização estará obviamente muito errada.
if(*memory)
efree(*memory);
Por exemplo, se você deseja acesso aleatório a dados não contíguos.
p -> [p0, p1, p2, ...]
p0 -> data1
p1 -> data2
- em C
T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));
Você armazena um ponteiro p
que aponta para uma matriz de ponteiros. Cada ponteiro aponta para um dado.
Se sizeof(T)
for grande, pode não ser possível alocar um bloco contíguo (ou seja, usando malloc) de sizeof(T) * n
bytes.
Uma coisa que eu uso constantemente é quando tenho uma matriz de objetos e preciso realizar pesquisas (pesquisa binária) neles por campos diferentes.
Eu mantenho a matriz original ...
int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);
Em seguida, faça uma matriz de ponteiros classificados para os objetos.
int compare_object_by_name( const void *v1, const void *v2 ) {
OBJECT *o1 = *(OBJECT **)v1;
OBJECT *o2 = *(OBJECT **)v2;
return (strcmp(o1->name, o2->name);
}
OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
int i = 0;
for( ; i<num_objects; i++)
object_ptrs_by_name[i] = original_array+i;
qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);
Você pode criar quantas matrizes de ponteiro classificadas precisar e, em seguida, usar uma pesquisa binária na matriz de ponteiros classificados para acessar o objeto necessário pelos dados que você possui. A matriz original de objetos pode permanecer sem classificação, mas cada matriz de ponteiro será classificada por seu campo especificado.
Por que ponteiros duplos?
O objetivo é alterar o que o aluno A aponta, usando uma função.
#include <stdio.h>
#include <stdlib.h>
typedef struct Person{
char * name;
} Person;
/**
* we need a ponter to a pointer, example: &studentA
*/
void change(Person ** x, Person * y){
*x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}
void dontChange(Person * x, Person * y){
x = y;
}
int main()
{
Person * studentA = (Person *)malloc(sizeof(Person));
studentA->name = "brian";
Person * studentB = (Person *)malloc(sizeof(Person));
studentB->name = "erich";
/**
* we could have done the job as simple as this!
* but we need more work if we want to use a function to do the job!
*/
// studentA = studentB;
printf("1. studentA = %s (not changed)\n", studentA->name);
dontChange(studentA, studentB);
printf("2. studentA = %s (not changed)\n", studentA->name);
change(&studentA, studentB);
printf("3. studentA = %s (changed!)\n", studentA->name);
return 0;
}
/**
* OUTPUT:
* 1. studentA = brian (not changed)
* 2. studentA = brian (not changed)
* 3. studentA = erich (changed!)
*/
A seguir, é apresentado um exemplo C ++ muito simples que mostra que, se você deseja usar uma função para definir um ponteiro para apontar para um objeto, precisa de um ponteiro para um ponteiro . Caso contrário, o ponteiro continuará revertendo para nulo .
(Uma resposta C ++, mas acredito que seja a mesma em C.)
(Além disso, para referência: Google ("passar por valor c ++") = "Por padrão, os argumentos em C ++ são passados por valor. Quando um argumento é passado por valor, o valor do argumento é copiado no parâmetro da função.")
Então, queremos definir o ponteiro b
igual à string a
.
#include <iostream>
#include <string>
void Function_1(std::string* a, std::string* b) {
b = a;
std::cout << (b == nullptr); // False
}
void Function_2(std::string* a, std::string** b) {
*b = a;
std::cout << (b == nullptr); // False
}
int main() {
std::string a("Hello!");
std::string* b(nullptr);
std::cout << (b == nullptr); // True
Function_1(&a, b);
std::cout << (b == nullptr); // True
Function_2(&a, &b);
std::cout << (b == nullptr); // False
}
// Output: 10100
O que acontece na fila Function_1(&a, b);
?
O "valor" &main::a
(um endereço) é copiado para o parâmetro std::string* Function_1::a
. Portanto, Function_1::a
é um ponteiro para (ou seja, o endereço de memória) da string main::a
.
O "valor" main::b
(um endereço na memória) é copiado para o parâmetro std::string* Function_1::b
. Portanto, agora existem 2 desses endereços na memória, ambos ponteiros nulos. Na linha b = a;
, a variável local Function_1::b
é alterada para igual Function_1::a
(= &main::a
), mas a variável main::b
não é alterada. Após a chamada para Function_1
, main::b
ainda é um ponteiro nulo.
O que acontece na fila Function_2(&a, &b);
?
O tratamento da a
variável é o mesmo: dentro da função, Function_2::a
é o endereço da string main::a
.
Mas a variável b
agora está sendo passada como um ponteiro para um ponteiro. O "valor" de &main::b
(o endereço do ponteiro main::b
) é copiado std::string** Function_2::b
. Portanto, em Function_2, desreferenciando isso como *Function_2::b
irá acessar e modificar main::b
. Portanto, a linha *b = a;
está realmente configurando main::b
(um endereço) igual a Function_2::a
(= endereço de main::a
) que é o que queremos.
Se você deseja usar uma função para modificar uma coisa, seja um objeto ou um endereço (ponteiro), é necessário passar um ponteiro para essa coisa. O que você realmente passa não pode ser modificado (no escopo da chamada) porque é feita uma cópia local.
(Uma exceção é se o parâmetro for uma referência, como std::string& a
. Mas geralmente são const
. Geralmente, se você chamar f(x)
, se x
for um objeto, poderá assumir que f
não será modificado x
. Mas se x
for um ponteiro, deverá suponha que f
possa modificar o objeto apontado por x
.)
Um pouco atrasado para a festa, mas espero que isso ajude alguém.
Nas matrizes C, sempre alocam memória na pilha, portanto, uma função não pode retornar uma matriz (não estática) devido ao fato de que a memória alocada na pilha é liberada automaticamente quando a execução chega ao final do bloco atual. Isso é realmente irritante quando você deseja lidar com matrizes bidimensionais (ou seja, matrizes) e implementar algumas funções que podem alterar e retornar matrizes. Para conseguir isso, você pode usar um ponteiro para ponteiro para implementar uma matriz com memória alocada dinamicamente:
/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
// Allocate memory for num_rows float-pointers
double** A = calloc(num_rows, sizeof(double*));
// return NULL if the memory couldn't allocated
if(A == NULL) return NULL;
// For each double-pointer (row) allocate memory for num_cols floats
for(int i = 0; i < num_rows; i++){
A[i] = calloc(num_cols, sizeof(double));
// return NULL if the memory couldn't allocated
// and free the already allocated memory
if(A[i] == NULL){
for(int j = 0; j < i; j++){
free(A[j]);
}
free(A);
return NULL;
}
}
return A;
}
Aqui está uma ilustração:
double** double* double
------------- ---------------------------------------------------------
A ------> | A[0] | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
| --------- | ---------------------------------------------------------
| A[1] | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
| --------- | ---------------------------------------------------------
| . | .
| . | .
| . | .
| --------- | ---------------------------------------------------------
| A[i] | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
| --------- | ---------------------------------------------------------
| . | .
| . | .
| . | .
| --------- | ---------------------------------------------------------
| A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
------------- ---------------------------------------------------------
O ponteiro duplo para o ponteiro duplo A aponta para o primeiro elemento A [0] de um bloco de memória cujos elementos são os próprios ponteiros duplos. Você pode imaginar esses ponteiros duplos como as linhas da matriz. Essa é a razão pela qual cada ponteiro duplo aloca memória para elementos num_cols do tipo double. Além disso, A [i] aponta para a i-ésima linha, ou seja, A [i] aponta para A [i] [0] e esse é apenas o primeiro elemento duplo do bloco de memória para a i-ésima linha. Finalmente, você pode acessar o elemento na i-ésima linha e j-ésima coluna facilmente com A [i] [j].
Aqui está um exemplo completo que demonstra o uso:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
// Allocate memory for num_rows double-pointers
double** matrix = calloc(num_rows, sizeof(double*));
// return NULL if the memory couldn't allocated
if(matrix == NULL) return NULL;
// For each double-pointer (row) allocate memory for num_cols
// doubles
for(int i = 0; i < num_rows; i++){
matrix[i] = calloc(num_cols, sizeof(double));
// return NULL if the memory couldn't allocated
// and free the already allocated memory
if(matrix[i] == NULL){
for(int j = 0; j < i; j++){
free(matrix[j]);
}
free(matrix);
return NULL;
}
}
return matrix;
}
/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
for (int i = 0; i < rows; ++i){
for (int j = 0; j < cols; ++j){
matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
}
}
}
/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
for(int i = 0; i < rows; i++){
free(matrix[i]);
}
free(matrix);
}
/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
for(int i = 0; i < rows; i++){
for(int j = 0; j < cols; j++){
printf(" %- f ", matrix[i][j]);
}
printf("\n");
}
}
int main(){
srand(time(NULL));
int m = 3, n = 3;
double** A = init_matrix(m, n);
randn_fill_matrix(A, m, n);
print_matrix(A, m, n);
free_matrix(A, m, n);
return 0;
}
Hoje, usei ponteiros duplos enquanto programava algo para o trabalho, para poder responder por que tivemos que usá-los (é a primeira vez que tive que usar ponteiros duplos). Tivemos que lidar com a codificação em tempo real de quadros contidos em buffers que são membros de algumas estruturas. No codificador, tivemos que usar um ponteiro para uma dessas estruturas. O problema era que nosso ponteiro estava sendo alterado para apontar para outras estruturas de outro thread. Para usar a estrutura atual no codificador, tive que usar um ponteiro duplo, a fim de apontar para o ponteiro que estava sendo modificado em outro thread. Não era óbvio a princípio, pelo menos para nós, que tínhamos que adotar essa abordagem. Muitos endereços foram impressos no processo :)).
Você DEVE usar ponteiros duplos quando trabalha em ponteiros que são alterados em outros locais da sua aplicação. Você também pode achar ponteiros duplos obrigatórios quando lida com o hardware que retorna e endereça a você.
Compare o valor de modificação da variável com o valor de modificação do ponteiro :
#include <stdio.h>
#include <stdlib.h>
void changeA(int (*a))
{
(*a) = 10;
}
void changeP(int *(*P))
{
(*P) = malloc(sizeof((*P)));
}
int main(void)
{
int A = 0;
printf("orig. A = %d\n", A);
changeA(&A);
printf("modi. A = %d\n", A);
/*************************/
int *P = NULL;
printf("orig. P = %p\n", P);
changeP(&P);
printf("modi. P = %p\n", P);
free(P);
return EXIT_SUCCESS;
}
Isso me ajudou a evitar o retorno do valor do ponteiro quando o ponteiro foi modificado pela função chamada (usada na lista vinculada individual).
VELHO (ruim):
int *func(int *P)
{
...
return P;
}
int main(void)
{
int *pointer;
pointer = func(pointer);
...
}
NOVO (melhor):
void func(int **pointer)
{
...
}
int main(void)
{
int *pointer;
func(&pointer);
...
}
double*
.