Seja cuidadoso; a frase "ponteiro duplo" também se refere ao tipo double*.
21416 Keith Thompson
1
Tome nota: a resposta a esta pergunta é diferente para C e C ++ - não adicione a tag c + a essa pergunta muito antiga.
BЈовић
Respostas:
479
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);}
Só queria salientar que a 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.
MCCCS
170
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:
o que seria diferente se alocar void allocate(int *p)e você o chamou como allocate(p)?
アレックス
@AlexanderSupertramp Sim. O código irá falhar. Por favor, veja a resposta de Silviu.
Abhishek 30/09
@Asha, qual é a diferença entre alocar (p) e alocar (& p)?
user2979872
1
@Asha - Não podemos simplesmente devolver o ponteiro? Se precisarmos mantê-lo nulo, o que é um caso de uso prático desse cenário?
Shabirmean
91
Digamos que você tenha um ponteiro. Seu valor é um endereço.
mas agora você deseja alterar esse endereço.
você poderia. ao fazer 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);return0;}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!!!.
Essa é uma ótima resposta e realmente me ajudou a visualizar o objetivo e a utilidade de um ponteiro duplo.
11117 Justin
1
@ Justin, você checou minha resposta acima desta? seu limpador :)
Brian Joseph Spinos
10
Ótima resposta, só falta explicar que <code> void cant_change (int * x, int * z) </code> falha porque seus 'parâmetros são apenas novos ponteiros no escopo local que são inicializados da mesma forma a e f (por isso não são o mesmo que aef).
Pedro Reis
1
Simples? Realmente? ;)
alk
1
Esta resposta realmente explica um dos usos mais comuns de ponteiro para ponteiro, obrigado!
tonyjosi 27/01
49
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.
A razão pela qual isso ocorre é que no alloc1ponteiro é passado por valor. Portanto, quando é reatribuído ao resultado da mallocchamada dentro de alloc1, a alteração não se refere ao código em um escopo diferente.
Agora você deseja implementar uma remove_iffunção, que aceita um critério de remoção rmcomo 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_ifretorna 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 forloop. A mensagem é que, sem ponteiros duplos, é necessário manter uma prevvariável para reorganizar os ponteiros e lidar com os dois casos diferentes.
Mas com ponteiros duplos, você pode escrever
// now head is a double pointerfor(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 prevagora porque pode modificar diretamente o que prev->nextapontou .
Para tornar as coisas mais claras, vamos seguir um pouco o código. Durante a remoção:
if entry == *head: será *head (==*curr) = *head->next- headagora aponta para o ponteiro do novo nó de cabeçalho. Você faz isso alterando diretamente heado conteúdo de um novo ponteiro.
if entry != *head: da mesma forma, *curré o que prev->nextapontou e agora aponta para entry->next.
Não importa em que caso, você pode reorganizar os ponteiros de maneira unificada com ponteiros duplos.
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: -
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 pointersint 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.
kumar
" char ** ch - 'ch' contém o endereço de uma matriz de ponteiros de caracteres. " Não, contém o endereço do primeiro elemento de uma matriz de charponteiros. Um ponteiro para uma matriz de char*seria digitado, por exemplo, como este: char(*(*p)[42])define pcomo ponteiro para uma matriz de 42 ponteiros para char.
alq
O último trecho está completamente quebrado. Para iniciantes: aqui *str = ...stré desreferenciado, não inicializado, invocando um comportamento indefinido.
alq
Esta malloc(sizeof(char) * 10);não aloca espaço para 10 ponteiro para charmas para 10 charúnica ..
alk
Esse loop for(i=0;i<10;i++) { str = ... falha ao usar o índice i.
alq
17
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>typedefunsignedchar** handle_type;//some data_structure that the library functions would work withtypedefstruct{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);return0;}
Qual é o motivo do tipo de identificador ser char não assinado **? Void ** funcionaria tão bem?
Connor Clark
5
unsigned charé usado especificamente porque estamos armazenando um ponteiro para dados binários que serão representados como bytes brutos. O uso voidexigirá uma conversão em algum momento e geralmente não é tão legível quanto a intenção do que está sendo feito.
Jason
7
Exemplo simples que você provavelmente já viu muitas vezes antes
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 **argvsã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.
"notação de ponteiro ( 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.
Pmg
6
Strings são um ótimo exemplo de uso de ponteiros duplos. A string em si é um ponteiro; portanto, a qualquer momento que você precisar apontar para uma string, precisará de um ponteiro duplo.
Bom ponto, perda de sinal entre o cérebro e o teclado. Eu editei para fazer um pouco mais de sentido.
Jeff Foster
Por que não podemos fazer o seguinte ... void safeFree (void * memória) {if (memory) {free (memory); memória = NULL; }}
Peter_pk 27/04
@Peter_pk Atribuir memória a nulo não ajudaria porque você passou um ponteiro por valor, não por referência (daí o exemplo de um ponteiro para um ponteiro).
Jeff Foster
2
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 pque 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) * nbytes.
Não (!) É necessário converter o resultado de malloc em C.
alk
2
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.
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.
O objetivo é alterar o que o aluno A aponta, usando uma função.
#include<stdio.h>#include<stdlib.h>typedefstructPerson{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);return0;}/**
* OUTPUT:
* 1. studentA = brian (not changed)
* 2. studentA = brian (not changed)
* 3. studentA = erich (changed!)
*/
Não (!) É necessário converter o resultado de malloc em C.
alk
2
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 bigual à string a.
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::bnão é alterada. Após a chamada para Function_1, main::bainda é um ponteiro nulo.
O que acontece na fila Function_2(&a, &b);?
O tratamento da avariável é o mesmo: dentro da função, Function_2::aé o endereço da string main::a.
Mas a variável bagora está sendo passada como um ponteiro para um ponteiro. O "valor" de &main::b(o endereço do ponteiromain::b ) é copiado std::string** Function_2::b. Portanto, em Function_2, desreferenciando isso como *Function_2::birá 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 xfor um objeto, poderá assumir que fnão será modificado x. Mas se xfor um ponteiro, deverá suponha que fpossa modificar o objeto apontado por x.)
O código C ++ para responder a uma pergunta em C não é a melhor ideia.
alq
1
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-pointersdouble** A = calloc(num_rows,sizeof(double*));// return NULL if the memory couldn't allocatedif(A == NULL)return NULL;// For each double-pointer (row) allocate memory for num_cols floatsfor(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 memoryif(A[i]== NULL){for(int j =0; j < i; j++){
free(A[j]);}
free(A);return NULL;}}return A;}
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-pointersdouble** matrix = calloc(num_rows,sizeof(double*));// return NULL if the memory couldn't allocatedif(matrix == NULL)return NULL;// For each double-pointer (row) allocate memory for num_cols// doublesfor(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 memoryif(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);return0;}
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ê.
double*
.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
Resultado:
fonte
arr[a][b][c]
não é a***arr
. Ponteiro de ponteiros usa referências de referências, enquantoarr[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:
fonte
void allocate(int *p)
e você o chamou comoallocate(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.
Aqui está a saída: ( leia isto primeiro )
fonte
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.
A razão pela qual isso ocorre é que no
alloc1
ponteiro é passado por valor. Portanto, quando é reatribuído ao resultado damalloc
chamada dentro dealloc1
, a alteração não se refere ao código em um escopo diferente.fonte
free(p)
não é suficiente, você precisaif(p) free(*p)
também*p
avalia comoint
mantendo um valor de 10, passarint
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 é
Agora você deseja implementar uma
remove_if
função, que aceita um critério de remoçãorm
como um dos argumentos e percorre a lista vinculada: se uma entrada satisfizer o critério (algo comorm(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
como seu
for
loop. A mensagem é que, sem ponteiros duplos, é necessário manter umaprev
variável para reorganizar os ponteiros e lidar com os dois casos diferentes.Mas com ponteiros duplos, você pode escrever
Você não precisa de um
prev
agora porque pode modificar diretamente o queprev->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 diretamentehead
o conteúdo de um novo ponteiro.entry != *head
: da mesma forma,*curr
é o queprev->next
apontou e agora aponta paraentry->next
.Não importa em que caso, você pode reorganizar os ponteiros de maneira unificada com ponteiros duplos.
fonte
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 ..
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: -
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 -
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.
Neste exemplo, o método espera um ponteiro duplo como parâmetro para atualizar o valor de uma sequência.
fonte
#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 dechar*
seria digitado, por exemplo, como este:char(*(*p)[42])
definep
como ponteiro para uma matriz de 42 ponteiros parachar
.*str = ...
str
é desreferenciado, não inicializado, invocando um comportamento indefinido.malloc(sizeof(char) * 10);
não aloca espaço para 10 ponteiro parachar
mas para 10char
única ..for(i=0;i<10;i++) { str = ...
falha ao usar o índicei
.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:
Espero que isto ajude,
Jason
fonte
unsigned char
é usado especificamente porque estamos armazenando um ponteiro para dados binários que serão representados como bytes brutos. O usovoid
exigirá uma conversão em algum momento e geralmente não é tão legível quanto a intenção do que está sendo feito.Exemplo simples que você provavelmente já viu muitas vezes antes
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 escreverchar *argv[]
. Em outras palavraschar *argv[]
echar **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.
fonte
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.Strings são um ótimo exemplo de uso de ponteiros duplos. A string em si é um ponteiro; portanto, a qualquer momento que você precisar apontar para uma string, precisará de um ponteiro duplo.
fonte
Por exemplo, convém garantir que, ao liberar a memória de algo, defina o ponteiro como nulo posteriormente.
Quando você chama essa função, você o chama com o endereço de um ponteiro
Agora
myMemory
está definido como NULL e qualquer tentativa de reutilização estará obviamente muito errada.fonte
if(*memory)
efree(*memory);
Por exemplo, se você deseja acesso aleatório a dados não contíguos.
- em C
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) desizeof(T) * n
bytes.fonte
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 ...
Em seguida, faça uma matriz de ponteiros classificados para os objetos.
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.
fonte
Por que ponteiros duplos?
O objetivo é alterar o que o aluno A aponta, usando uma função.
fonte
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 à stringa
.O que acontece na fila
Function_1(&a, b);
?O "valor"
&main::a
(um endereço) é copiado para o parâmetrostd::string* Function_1::a
. Portanto,Function_1::a
é um ponteiro para (ou seja, o endereço de memória) da stringmain::a
.O "valor"
main::b
(um endereço na memória) é copiado para o parâmetrostd::string* Function_1::b
. Portanto, agora existem 2 desses endereços na memória, ambos ponteiros nulos. Na linhab = a;
, a variável localFunction_1::b
é alterada para igualFunction_1::a
(=&main::a
), mas a variávelmain::b
não é alterada. Após a chamada paraFunction_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 stringmain::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 ponteiromain::b
) é copiadostd::string** Function_2::b
. Portanto, em Function_2, desreferenciando isso como*Function_2::b
irá acessar e modificarmain::b
. Portanto, a linha*b = a;
está realmente configurandomain::b
(um endereço) igual aFunction_2::a
(= endereço demain::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ãoconst
. Geralmente, se você chamarf(x)
, sex
for um objeto, poderá assumir quef
não será modificadox
. Mas sex
for um ponteiro, deverá suponha quef
possa modificar o objeto apontado porx
.)fonte
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:
Aqui está uma ilustração:
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:
fonte
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ê.
fonte
Compare o valor de modificação da variável com o valor de modificação do ponteiro :
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):
NOVO (melhor):
fonte