value - tutorialspoint c++ pointer




Quais são as barreiras para entender os indicadores e o que pode ser feito para superá-los? (19)

Por que os indicadores são um fator importante de confusão para muitos alunos de nível universitário novos e antigos, em C ou C ++? Existem ferramentas ou processos de pensamento que ajudaram você a entender como os ponteiros funcionam na variável, função e além do nível?

Quais são algumas das boas práticas que podem ser feitas para trazer alguém ao nível de "Ah-hah, eu entendi", sem envolvê-las no conceito geral? Basicamente, perfure como cenários.


Por que os indicadores são um fator importante de confusão para muitos estudantes novos e até mesmo idosos em nível universitário na linguagem C / C ++?

O conceito de um espaço reservado para um valor - variáveis ​​- mapeia em algo que nos é ensinado na escola - álgebra. Não existe um paralelo existente que você possa desenhar sem entender como a memória é fisicamente projetada dentro de um computador, e ninguém pensa sobre esse tipo de coisa até estar lidando com coisas de baixo nível - no nível de comunicação C / C ++ / byte .

Existem ferramentas ou processos de pensamento que ajudaram você a entender como os ponteiros funcionam na variável, função e além do nível?

Caixas de endereços. Lembro-me de quando eu estava aprendendo a programar o BASIC em microcomputadores, havia esses livros bonitos com jogos neles, e às vezes você tinha que colocar valores em endereços específicos. Eles tinham uma foto de um monte de caixas, rotuladas incrementalmente com 0, 1, 2 ... e foi explicado que apenas uma pequena coisa (um byte) poderia caber nessas caixas, e havia muitas delas - alguns computadores tinha até 65535! Estavam ao lado um do outro e todos tinham um endereço.

Quais são algumas das boas práticas que podem ser feitas para trazer alguém ao nível de "Ah-hah, eu entendi", sem envolvê-las no conceito geral? Basicamente, perfure como cenários.

Para uma broca? Faça um struct:

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

Mesmo exemplo acima, exceto em C:

// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

Saída:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

Talvez isso explique algumas das noções básicas através do exemplo?


A razão pela qual eu tive dificuldade em entender os ponteiros, a princípio, é que muitas explicações incluem muita porcaria sobre passar por referência. Tudo isso faz é confundir o problema. Quando você usa um parâmetro de ponteiro, ainda está passando por valor; mas o valor passa a ser um endereço em vez de, digamos, um int.

Alguém já se conectou a este tutorial, mas posso destacar o momento em que comecei a entender os ponteiros:

Um tutorial sobre ponteiros e matrizes em C: Capítulo 3 - Ponteiros e Strings

int puts(const char *s);

Por enquanto, ignore o const. O parâmetro passado para puts() é um ponteiro, que é o valor de um ponteiro (já que todos os parâmetros em C são passados ​​por valor), e o valor de um ponteiro é o endereço para o qual ele aponta, ou simplesmente, um endereço . Assim, quando escrevemos puts(strA); como vimos, estamos passando o endereço de strA [0].

No momento em que li essas palavras, as nuvens se abriram e um feixe de luz do sol me envolveu com a compreensão do ponteiro.

Mesmo que você seja um desenvolvedor VB .NET ou C # (como eu sou) e nunca use código não seguro, ainda vale a pena entender como os ponteiros funcionam ou você não entenderá como as referências de objeto funcionam. Então você terá a noção comum, mas equivocada, de que passar uma referência de objeto a um método copia o objeto.


As complexidades dos indicadores vão além do que podemos ensinar facilmente. Fazer com que os alunos apontem uns para os outros e usar pedaços de papel com endereços de casa são ótimas ferramentas de aprendizado. Eles fazem um ótimo trabalho de introduzir os conceitos básicos. De fato, aprender os conceitos básicos é vital para o sucesso do uso de ponteiros. No entanto, no código de produção, é comum entrar em cenários muito mais complexos do que essas demonstrações simples podem encapsular.

Estive envolvido com sistemas em que tínhamos estruturas apontando para outras estruturas apontando para outras estruturas. Algumas dessas estruturas também continham estruturas embutidas (em vez de ponteiros para estruturas adicionais). É aqui que os ponteiros ficam realmente confusos. Se você tem vários níveis de indireção, e você começa a acabar com o código como este:

widget->wazzle.fizzle = fazzle.foozle->wazzle;

pode ficar confuso muito rapidamente (imagine muito mais linhas e potencialmente mais níveis). Jogue em matrizes de ponteiros e ponteiros nó a nó (árvores, listas vinculadas) e fica ainda pior. Eu vi alguns desenvolvedores realmente bons se perderem quando eles começaram a trabalhar em tais sistemas, mesmo desenvolvedores que entendiam o básico muito bem.

Estruturas complexas de ponteiros não indicam necessariamente uma codificação ruim (embora possam indicar). A composição é uma peça vital de uma boa programação orientada a objetos e, em linguagens com ponteiros brutos, ela inevitavelmente levará a uma indireção de várias camadas. Além disso, os sistemas geralmente precisam usar bibliotecas de terceiros com estruturas que não combinam entre si em estilo ou técnica. Em situações como essa, a complexidade naturalmente vai surgir (embora, certamente, devamos lutar o máximo possível).

Acho que a melhor coisa que as faculdades podem fazer para ajudar os alunos a aprender os ponteiros é usar boas demonstrações, combinadas com projetos que exigem uso de ponteiros. Um projeto difícil fará mais pela compreensão do ponteiro do que mil manifestações. As demonstrações podem lhe dar uma compreensão superficial, mas para compreender profundamente os ponteiros, você precisa realmente usá-las.


Eu encontrei o "Tutorial sobre ponteiros e arrays em C" de Ted Jensen, um excelente recurso para aprender sobre ponteiros. Ele é dividido em 10 lições, começando com uma explicação de quais ponteiros são (e para que servem) e terminando com ponteiros de função. http://home.netcom.com/~tjensen/ptr/cpoint.htm

Seguindo em frente, o Guia de Beej para Programação de Rede ensina a API de sockets do Unix, a partir da qual você pode começar a fazer coisas realmente divertidas. http://beej.us/guide/bgnet/


Eu pensei em adicionar uma analogia a essa lista que achei muito útil ao explicar ponteiros (antigamente) como um Tutor de Ciência da Computação; Primeiro vamos:

Prepare o palco :

Considere um estacionamento com 3 espaços, esses espaços estão numerados:

-------------------
|     |     |     |
|  1  |  2  |  3  |
|     |     |     |

De certa forma, isso é como locais de memória, eles são seqüenciais e contíguos .. mais ou menos como uma matriz. No momento não há carros neles, então é como um array vazio ( parking_lot[3] = {0} ).

Adicione os dados

Um estacionamento nunca fica vazio por muito tempo ... se isso acontecesse, seria inútil e ninguém construiria nenhum. Então, digamos que com o passar do dia o lote se enche com 3 carros, um carro azul, um carro vermelho e um carro verde:

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |R| | |G| |
| o-o | o-o | o-o |

Esses carros são todos do mesmo tipo (carro), então uma maneira de pensar nisso é que nossos carros são algum tipo de dados (digamos, um int ), mas eles têm valores diferentes ( blue , red , green ; que poderia ser um enum colorido)

Digite o ponteiro

Agora, se eu levá-lo para este estacionamento, e pedir-lhe para encontrar um carro azul, você estica um dedo e usa-o para apontar para um carro azul no ponto 1. É como pegar um ponteiro e atribuí-lo a um endereço de memória ( int *finger = parking_lot )

Seu dedo (o ponteiro) não é a resposta para minha pergunta. Olhando para o seu dedo não me diz nada, mas se eu olhar para onde você está apontando o dedo (desreferenciando o ponteiro), eu posso encontrar o carro (os dados) que eu estava procurando.

Reatribuindo o ponteiro

Agora eu posso pedir para você encontrar um carro vermelho e redirecionar seu dedo para um carro novo. Agora seu ponteiro (o mesmo de antes) está me mostrando novos dados (o local de estacionamento onde o carro vermelho pode ser encontrado) do mesmo tipo (o carro).

O ponteiro não mudou fisicamente, ainda é seu dedo, apenas os dados que ele estava me mostrando mudaram. (o endereço "vaga de estacionamento")

Ponteiros duplos (ou um ponteiro para um ponteiro)

Isso funciona com mais de um ponteiro também. Eu posso perguntar onde está o ponteiro, que está apontando para o carro vermelho e você pode usar a outra mão e apontar com um dedo para o primeiro dedo. (isso é como int **finger_two = &finger )

Agora, se eu quiser saber onde fica o carro azul, posso seguir a direção do primeiro dedo até o segundo dedo, até o carro (os dados).

O ponteiro pendente

Agora digamos que você está se sentindo muito parecido com uma estátua, e quer segurar sua mão apontando para o carro vermelho indefinidamente. E se esse carro vermelho for embora?

   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |

Seu ponteiro ainda está apontando para onde o carro vermelho estava, mas não é mais. Digamos que um carro novo esteja lá ... um carro laranja. Agora, se eu perguntar de novo: "onde está o carro vermelho", você ainda está apontando para lá, mas agora está errado. Isso não é um carro vermelho, que é laranja.

Aritmética ponteiro

Ok, então você ainda está apontando para o segundo lugar de estacionamento (agora ocupado pelo carro laranja)

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |

Bem, eu tenho uma nova pergunta agora ... Eu quero saber a cor do carro no próximo estacionamento. Você pode ver que está apontando para o ponto 2, então basta adicionar 1 e apontar para o próximo ponto. ( finger+1 ), agora que eu queria saber quais eram os dados, você tem que verificar o ponto (não apenas o dedo) para que você possa deferir o ponteiro ( *(finger+1) ) para ver que há um verde carro presente lá (os dados naquele local)


Na minha primeira aula de Comp Sci, fizemos o seguinte exercício. Concedido, esta era uma sala de aula com cerca de 200 alunos ...

Professor escreve no quadro: int john;

John se levanta

Professor escreve: int *sally = &john;

Sally se levanta, aponta para john

Professor: int *bill = sally;

Bill se levanta, aponta para John

Professor: int sam;

Sam se levanta

Professor: bill = &sam;

Bill agora aponta para Sam.

Eu acho que você entendeu a ideia. Acho que passamos cerca de uma hora fazendo isso, até que examinamos o básico da atribuição de ponteiros.


Uma analogia que achei útil para explicar ponteiros é hiperlinks. A maioria das pessoas entende que um link em uma página da Web aponta para outra página na Internet e, se você conseguir copiar e colar esse hiperlink, ambos apontarão para a mesma página da Web original. Se você editar essa página original, siga um desses links (ponteiros) para obter essa nova página atualizada.



Eu acho que a principal barreira para entender os ponteiros são maus professores.

Quase todos aprendem mentiras sobre ponteiros: Que eles não são nada mais do que endereços de memória , ou que eles permitem que você aponte para locais arbitrários .

E claro que eles são difíceis de entender, perigosos e semi-mágicos.

Nada disso é verdade. Os ponteiros são na verdade conceitos bastante simples, contanto que você se atenha ao que a linguagem C ++ tem a dizer sobre eles e não os imbui com atributos que "normalmente" acabam funcionando na prática, mas mesmo assim não são garantidos pela linguagem e, portanto, não fazem parte do conceito real de um ponteiro.

Eu tentei escrever uma explicação sobre isso alguns meses atrás neste post - espero que ajude alguém.

(Nota, antes que alguém se torne pedante em mim, sim, o padrão C ++ diz que os ponteiros representam os endereços de memória. Mas não diz que "ponteiros são endereços de memória e nada além de endereços de memória e podem ser usados ​​ou pensados ​​de forma intercambiável endereços ". A distinção é importante)


Eu acho que o que faz com que os ponteiros sejam difíceis de aprender é que até os ponteiros você está confortável com a idéia de que "neste local de memória há um conjunto de bits que representam um int, um duplo, um caractere, qualquer que seja".

Quando você vê um ponteiro pela primeira vez, você realmente não entende o que está no local da memória. "O que você quer dizer com um endereço ?"

Eu não concordo com a noção de que "você quer pegá-los ou não".

Eles se tornam mais fáceis de entender quando você começa a encontrar usos reais para eles (como não passar grandes estruturas em funções).


Número da caixa postal.

É uma informação que permite que você acesse alguma outra coisa.

(E se você fizer aritmética em números de caixa postal, você pode ter um problema, porque a carta vai na caixa errada. E se alguém se mover para outro estado - sem endereço de encaminhamento - então você tem um ponteiro pendente. Por outro lado, se os correios encaminharem o correio, você terá um ponteiro para um ponteiro.


A confusão vem das múltiplas camadas de abstração misturadas no conceito "ponteiro". Os programadores não se confundem com referências comuns em Java / Python, mas os ponteiros são diferentes na medida em que expõem as características da arquitetura de memória subjacente.

É um bom princípio separar claramente as camadas de abstração, e os ponteiros não fazem isso.


Algumas respostas acima afirmaram que "os ponteiros não são realmente difíceis", mas não passaram a abordar diretamente onde "ponteiro é difícil!" vem de. Há alguns anos, eu ensinei alunos de primeiro ano de ciências da computação (por apenas um ano, já que eu claramente o engajei) e ficou claro para mim que a idéia de ponteiro não é difícil. O difícil é entender por que e quando você quer um ponteiro .

Eu não acho que você pode se divorciar dessa pergunta - por que e quando usar um ponteiro - de explicar questões mais amplas de engenharia de software. Por que toda variável não deve ser uma variável global, e por que alguém deve fatorar código semelhante em funções (que, para isso, usam ponteiros para especializar seu comportamento em seu site de chamada).


Eu acho que a principal razão que as pessoas têm problemas com isso é porque geralmente não é ensinado de uma forma interessante e envolvente. Eu gostaria de ver um palestrante receber 10 voluntários da multidão e dar a eles uma régua de 1 metro cada, colocá-los em uma certa configuração e usar as réguas para apontar um ao outro. Em seguida, mostre a aritmética do ponteiro movendo as pessoas ao redor (e onde elas apontam suas réguas). Seria uma maneira simples, mas eficaz (e acima de tudo memorável) de mostrar os conceitos sem ficar muito atolados na mecânica.

Depois de chegar ao C e C + +, parece ficar mais difícil para algumas pessoas. Eu não tenho certeza se isso é porque eles estão finalmente colocando a teoria que eles não entendem corretamente na prática ou porque a manipulação do ponteiro é inerentemente mais difícil nesses idiomas. Não consigo me lembrar bem da minha própria transição, mas conheci ponteiros em Pascal e depois mudei para C e me perdi totalmente.


Eu gosto da analogia do endereço da casa, mas eu sempre pensei que o endereço fosse para a caixa de correio em si. Dessa forma, você pode visualizar o conceito de desreferência do ponteiro (abrindo a caixa de correio).

Por exemplo, seguindo uma lista encadeada: 1) comece com o seu papel com o endereço 2) Vá para o endereço no papel 3) Abra a caixa de correio para encontrar um novo pedaço de papel com o próximo endereço nele

Em uma lista vinculada linear, a última caixa de correio não contém nada (fim da lista). Em uma lista vinculada circular, a última caixa de correio tem o endereço da primeira caixa de correio.

Observe que a etapa 3 é onde a desreferência ocorre e onde você irá travar ou dar errado quando o endereço for inválido. Supondo que você poderia ir até a caixa de correio de um endereço inválido, imagine que há um buraco negro ou algo lá que vira o mundo de dentro para fora :)


Eu não acho que os ponteiros sejam confusos. A maioria das pessoas consegue entender o conceito. Agora, quantos ponteiros você pode pensar ou com quantos níveis de indireção você está confortável. Não é preciso muito para colocar as pessoas no limite. O fato de que eles podem ser alterados acidentalmente por bugs no seu programa também pode torná-los muito difíceis de depurar quando as coisas dão errado no seu código.


Eu poderia trabalhar com ponteiros quando só conhecia o C ++. Eu meio que sabia o que fazer em alguns casos e o que não fazer por tentativa / erro. Mas a coisa que me deu compreensão completa é a linguagem assembly. Se você fizer alguma depuração séria do nível de instrução com um programa de linguagem de montagem que você tenha escrito, você deve ser capaz de entender muitas coisas.


O problema com ponteiros não é o conceito. É a execução e linguagem envolvida. Conflitos adicionais resultam quando os professores assumem que é o CONCEITO de ponteiros que é difícil, e não o jargão, ou a confusa confusão que C e C ++ fazem do conceito. Uma quantidade tão grande de esforço é capaz de explicar o conceito (como na resposta aceita para essa pergunta) e é praticamente desperdiçado com alguém como eu, porque eu já entendo tudo isso. Está apenas explicando a parte errada do problema.

Para dar uma ideia de onde eu venho, sou alguém que entende os ponteiros perfeitamente, e posso usá-los com competência na linguagem assembler. Porque na linguagem assembler eles não são referidos como ponteiros. Eles são referidos como endereços. Quando se trata de programar e usar ponteiros em C, eu cometo muitos erros e fico realmente confuso. Eu ainda não resolvi isso. Deixe-me lhe dar um exemplo.

Quando uma API diz:

int doIt(char *buffer )
//*buffer is a pointer to the buffer

o que quer?

poderia querer:

um número que representa um endereço para um buffer

(Para dar isso, eu digo doIt(mybuffer)ou doIt(*myBuffer)?)

um número que representa o endereço para um endereço para um buffer

(é isso doIt(&mybuffer)ou doIt(mybuffer)ou doIt(*mybuffer)?)

um número que representa o endereço para o endereço para o endereço para o buffer

(talvez doIt(&mybuffer)seja isso doIt(&&mybuffer)? ou é ? ou até mesmo doIt(&&&mybuffer))

e assim por diante, e a linguagem envolvida não deixa isso claro, porque envolve as palavras "ponteiro" e "referência" que não possuem tanto significado e clareza para mim quanto "x mantém o endereço para y" e " esta função requer um endereço para y ". A resposta depende, além disso, do que o "mybuffer" é para começar, e o que ele pretende fazer com ele. A linguagem não suporta os níveis de aninhamento encontrados na prática. Como quando eu tenho que entregar um "ponteiro" para uma função que cria um novo buffer, e ele modifica o ponteiro para apontar para o novo local do buffer. Quer realmente o ponteiro, ou um ponteiro para o ponteiro, para saber onde ir para modificar o conteúdo do ponteiro.Na maioria das vezes eu só tenho que adivinhar o que significa "ponteiro" e na maioria das vezes eu estou errado, independentemente da quantidade de experiência que eu adivinho.

"Ponteiro" está sobrecarregado. Um ponteiro é um endereço para um valor? ou é uma variável que contém um endereço para um valor. Quando uma função quer um ponteiro, ela quer o endereço que a variável ponteiro contém ou deseja o endereço para a variável do ponteiro? Estou confuso.


Todo iniciante em C / C ++ tem o mesmo problema e esse problema ocorre não porque "os ponteiros sejam difíceis de aprender", mas "quem e como é explicado". Alguns alunos reúnem verbalmente alguns visualmente e a melhor maneira de explicá-lo é usar o exemplo "treinar" (ternos para exemplo verbal e visual).

Onde "locomotiva" é um ponteiro que não pode segurar nada e "vagão" é o que "locomotiva" tenta puxar (ou apontar para). Depois, você pode classificar o "vagão" em si, ele pode conter animais, plantas ou pessoas (ou uma mistura deles).





pointers