Tive o prazer recente de explicar indicadores para um iniciante em programação C e me deparei com a seguinte dificuldade. Pode não parecer um problema, se você já sabe usar ponteiros, mas tente observar o exemplo a seguir com uma mente clara:
int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);
Para o iniciante absoluto, a saída pode ser surpreendente. Na linha 2, ele / ela havia acabado de declarar * bar como & foo, mas na linha 4 acontece * bar é realmente foo em vez de & foo!
A confusão, você pode dizer, deriva da ambiguidade do símbolo *: na linha 2, é usado para declarar um ponteiro. Na linha 4, é usado como um operador unário que busca o valor em que o ponteiro aponta. Duas coisas diferentes, certo?
No entanto, essa "explicação" não ajuda em nada um iniciante. Introduz um novo conceito, apontando uma discrepância sutil. Este não pode ser o caminho certo para ensiná-lo.
Então, como Kernighan e Ritchie explicaram isso?
O operador unário * é o operador de indireção ou desreferenciação; quando aplicado a um ponteiro, ele acessa o objeto apontado pelo ponteiro. [...]
A declaração do ponteiro ip,
int *ip
pretende ser um mnemônico; diz que a expressão*ip
é um int. A sintaxe da declaração para uma variável imita a sintaxe das expressões nas quais a variável pode aparecer .
int *ip
deve ser lido como " *ip
retornará um int
"? Mas por que então a tarefa após a declaração não segue esse padrão? E se um iniciante quiser inicializar a variável? int *ip = 1
(leia: *ip
retornará um int
e o int
é 1
) não funcionará conforme o esperado. O modelo conceitual simplesmente não parece coerente. Estou faltando alguma coisa aqui?
Edit: Tentou resumir as respostas aqui .
*
em uma declaração é um token que significa "declarar um ponteiro", nas expressões é o operador de desreferência e que esses dois representam coisas diferentes que têm o mesmo símbolo (igual ao operador de multiplicação - mesmo símbolo, significado diferente). É confuso, mas qualquer coisa diferente do estado real das coisas será ainda pior.int* bar
tornar mais óbvio que a estrela é realmente parte do tipo, não parte do identificador. Claro que isso leva a problemas diferentes com coisas não intuitivas, comoint* a, b
.*
pode ter dois significados diferentes, dependendo do contexto. Assim como a mesma letra pode ser pronunciada de maneira diferente, dependendo da palavra em que ela fica difícil de aprender a falar muitas línguas. Se todo conceito / operação tivesse seu próprio símbolo, precisaríamos de teclados muito maiores, para que os símbolos sejam reciclados quando fizer sentido.int* p
), enquanto avisa o aluno contra o uso de várias declarações na mesma linha quando houver ponteiros. Quando o aluno entender completamente o conceito de ponteiros, explique ao aluno que aint *p
sintaxe é equivalente e depois explique o problema com várias declarações.Respostas:
Para que seu aluno entenda o significado do
*
símbolo em diferentes contextos, ele deve primeiro entender que os contextos são realmente diferentes. Depois que eles entendem que os contextos são diferentes (ou seja, a diferença entre o lado esquerdo de uma tarefa e uma expressão geral), não é um salto cognitivo entender muito bem quais são as diferenças.Em primeiro lugar, explique que a declaração de uma variável não pode conter operadores (demonstre isso mostrando que colocar um
-
ou+
símbolo em uma declaração de variável simplesmente causa um erro). Depois, mostre que uma expressão (ou seja, no lado direito de uma atribuição) pode conter operadores. Certifique-se de que o aluno entenda que uma expressão e uma declaração variável são dois contextos completamente diferentes.Quando eles entenderem que os contextos são diferentes, você pode explicar que quando o
*
símbolo está em uma declaração de variável na frente do identificador de variável, significa 'declarar essa variável como um ponteiro'. Em seguida, você pode explicar que, quando usado em uma expressão (como operador unário), o*
símbolo é o 'operador de desreferência' e significa 'o valor no endereço de' em vez de seu significado anterior.Para realmente convencer seu aluno, explique que os criadores de C poderiam ter usado qualquer símbolo para significar o operador de desreferência (isto é, eles poderiam ter usado
@
), mas por qualquer motivo, eles decidiram usar*
.Em suma, não há como explicar que os contextos são diferentes. Se o aluno não compreender os contextos são diferentes, eles não podem entender por que o
*
símbolo pode significar coisas diferentes.fonte
A razão pela qual a taquigrafia:
no seu exemplo, pode ser confuso: é fácil interpretá-lo erroneamente como sendo equivalente a:
quando realmente significa:
Escrito dessa maneira, com a variável declaração e atribuição separadas, não existe esse potencial de confusão, e o paralelismo de declaração de uso descrito em sua citação de K&R funciona perfeitamente:
A primeira linha declara uma variável
bar
, tal que*bar
é umaint
.A segunda linha atribui o endereço de
foo
parabar
, tornando*bar
(anint
) um alias parafoo
(também umint
).Ao introduzir a sintaxe do ponteiro C para iniciantes, pode ser útil, inicialmente, seguir esse estilo de separar declarações de ponteiro de atribuições e introduzir apenas a sintaxe abreviada combinada (com avisos apropriados sobre seu potencial de confusão) quando os conceitos básicos de ponteiro forem usados. C foram adequadamente internalizados.
fonte
typedef
.typedef int *p_int;
significa que uma variável do tipop_int
tem a propriedade que*p_int
é umint
. Então nós temosp_int bar = &foo;
. Incentivar qualquer pessoa a criar dados não inicializados e depois atribuí-los por uma questão de hábito padrão parece ... uma má idéia.int a[2] = {47,11};
, que não é uma inicialização do elemento (inexistente)a[2]
ouiher.*
deve fazer parte do tipo, não vinculado à variável, e você poderá escreverint* foo_ptr, bar_ptr
para declarar dois ponteiros. Mas na verdade declara um ponteiro e um número inteiro.Breves declarações
É bom saber a diferença entre declaração e inicialização. Declaramos variáveis como tipos e as inicializamos com valores. Se fizermos as duas coisas ao mesmo tempo, geralmente chamamos de definição.
1.
int a; a = 42;
Nós declaramos um
int
chamado um . Em seguida, inicializamos dando um valor a ele42
.2.
int a = 42;
Nós declaramos e
int
nomeado um e dar-lhe o valor de 42. Ele é inicializado com42
. Uma definição.3.
a = 43;
Quando usamos as variáveis, dizemos que operamos sobre elas.
a = 43
é uma operação de atribuição. Atribuímos o número 43 à variável a.Dizendo
declaramos que a barra é um ponteiro para um int. Dizendo
declaramos bar e inicializá-lo com o endereço do foo .
Depois de inicializar a barra , podemos usar o mesmo operador, o asterisco, para acessar e operar com o valor de foo . Sem o operador, acessamos e operamos no endereço apontado pelo ponteiro.
Além disso, deixei a imagem falar.
o que
Uma ASCIIMAÇÃO simplificada sobre o que está acontecendo. (E aqui uma versão do player, se você quiser fazer uma pausa, etc.)
fonte
A 2ª afirmação
int *bar = &foo;
pode ser vista pictoricamente na memória como,Agora
bar
é um ponteiro do tipo queint
contém o endereço&
defoo
. Usando o operador unário*
, deferimos para recuperar o valor contido em 'foo' usando o ponteirobar
.EDIT : Minha abordagem com iniciantes é explicar o
memory address
de uma variável ou sejaMemory Address:
Cada variável tem um endereço associado a ele fornecido pelo sistema operacional. Inint a;
,&a
é o endereço da variávela
.Continue explicando os tipos básicos de variáveis em
C
as,Types of variables:
Variáveis podem conter valores dos respectivos tipos, mas não endereços.Introducing pointers:
Como dito acima, variáveis, por exemploÉ possível atribuir,
b = a
mas nãob = &a
, uma vez que a variávelb
pode conter valor, mas não o endereço, portanto, precisamos de Ponteiros .Pointer or Pointer variables :
Se uma variável contém um endereço, é conhecida como variável de ponteiro. Use*
na declaração para informar que é um ponteiro.fonte
int *ip
"ip é um ponteiro (*) do tipo int", você fica com problemas ao ler algo parecidox = (int) *ip
.x = (int) *ip;
, obtenha o valor desreferenciando o ponteiroip
e converta o valor paraint
qualquer tipoip
.int* bar = &foo;
torna cargas mais sentido. Sim, eu sei que isso causa problemas quando você declara vários ponteiros em uma única declaração. Não, acho que isso não importa.Observando as respostas e os comentários aqui, parece haver um acordo geral de que a sintaxe em questão pode ser confusa para um iniciante. A maioria deles propõe algo nesse sentido:
Você pode escrever em
int* bar
vez deint *bar
destacar a diferença. Isso significa que você não seguirá a abordagem K&R "declaração imita o uso", mas a abordagem Stroustrup C ++ :Não declaramos
*bar
ser um número inteiro. Declaramosbar
ser umint*
. Se queremos inicializar uma variável recém-criada na mesma linha, é claro que estamos lidando com issobar
, não*bar
.int* bar = &foo;
As desvantagens:
int* foo, bar
vsint *foo, *bar
).Edit: Uma abordagem diferente que foi sugerida, é seguir o caminho "imitar" K&R, mas sem a sintaxe "abreviada" (veja aqui ). Assim que você deixar de fazer uma declaração e uma tarefa na mesma linha , tudo parecerá muito mais coerente.
No entanto, mais cedo ou mais tarde o aluno terá que lidar com ponteiros como argumentos de função. E ponteiros como tipos de retorno. E ponteiros para funções. Você terá que explicar a diferença entre
int *func();
eint (*func)();
. Acho que mais cedo ou mais tarde as coisas vão desmoronar. E talvez mais cedo seja melhor que mais tarde.fonte
Há uma razão pela qual os estilos K&R
int *p
e Stroustrupint* p
; ambos são válidos (e significam a mesma coisa) em cada idioma, mas como Stroustrup colocou:Agora, como você está tentando ensinar C aqui, isso sugere que você deve enfatizar expressões mais do que tipos, mas algumas pessoas podem entender mais rapidamente uma ênfase mais rapidamente que a outra, e isso é mais sobre elas do que com o idioma.
Portanto, algumas pessoas acharão mais fácil começar com a idéia de que um
int*
é uma coisa diferente do que umint
e partir daí.Se alguém entender rapidamente a maneira de vê-la, que costuma
int* bar
terbar
algo que não é um int, mas um indicadorint
, então verá rapidamente*bar
está fazendo algo parabar
, eo resto se seguirá. Depois disso, você poderá explicar mais tarde por que os codificadores C tendem a preferirint *bar
.Ou não. Se houvesse uma maneira de todos entenderem o conceito pela primeira vez, você não teria problemas em primeiro lugar, e a melhor maneira de explicá-lo a uma pessoa não será necessariamente a melhor maneira de explicá-lo a outra.
fonte
int* p = &a
então podemos fazerint* r = *p
. Tenho certeza de que ele o abordou no The Design and Evolution of C ++ , mas já faz muito tempo que eu li isso, e tolamente inclinei minha cópia para alguém.int& r = *p
. E aposto que o mutuário ainda está tentando digerir o livro.Var A, B: ^Integer;
deixa claro que o tipo "ponteiro para inteiro" se aplica a ambosA
eB
. Usar umK&R
estiloint *a, *b
também é viável; mas uma declaração comoint* a,b;
, no entanto, parece sera
eb
está sendo declarada comoint*
, mas na realidade ela declaraa
como umint*
eb
como umint
.tl; dr:
A: não. Explique os ponteiros para o iniciante e mostre a eles como representar seus conceitos de ponteiro na sintaxe C depois.
A sintaxe C da IMO não é horrível, mas também não é maravilhosa: não é um grande obstáculo se você já entende os ponteiros, nem ajuda em aprendê-los.
Portanto: comece explicando os ponteiros e verifique se eles realmente os entendem:
Explique-os com diagramas de caixa e seta. Você pode fazer isso sem endereços hexadecimais, se eles não forem relevantes, basta mostrar as setas apontando para outra caixa ou para algum símbolo nulo.
Explique com pseudocódigo: basta escrever o endereço de foo e o valor armazenado na barra .
Então, quando seu iniciante entender o que são ponteiros, e por que e como usá-los; em seguida, mostre o mapeamento na sintaxe C.
Suspeito que a razão pela qual o texto K&R não forneça um modelo conceitual seja porque eles já entenderam os indicadores , e provavelmente assumiram que todos os outros programadores competentes da época também o fizeram. O mnemônico é apenas um lembrete do mapeamento do conceito bem compreendido para a sintaxe.
fonte
Esse problema é um pouco confuso ao começar a aprender C.
Aqui estão os princípios básicos que podem ajudar você a começar:
Existem apenas alguns tipos básicos em C:
char
: um valor inteiro com o tamanho de 1 byte.short
: um valor inteiro com o tamanho de 2 bytes.long
: um valor inteiro com o tamanho de 4 bytes.long long
: um valor inteiro com o tamanho de 8 bytes.float
: um valor não inteiro com o tamanho de 4 bytes.double
: um valor não inteiro com o tamanho de 8 bytes.Observe que o tamanho de cada tipo geralmente é definido pelo compilador e não pelo padrão.
Os tipos inteiros
short
,long
elong long
são normalmente seguidas porint
.Não é obrigatório, no entanto, e você pode usá-los sem o
int
.Como alternativa, você pode apenas declarar
int
, mas isso pode ser interpretado de maneira diferente por diferentes compiladores.Então, para resumir isso:
short
é o mesmo que,short int
mas não necessariamente o mesmo queint
.long
é o mesmo que,long int
mas não necessariamente o mesmo queint
.long long
é o mesmo que,long long int
mas não necessariamente o mesmo queint
.Em um determinado compilador,
int
é oushort int
oulong int
oulong long int
.Se você declarar uma variável de algum tipo, também poderá declarar outra variável apontando para ela.
Por exemplo:
int a;
int* b = &a;
Portanto, em essência, para cada tipo básico, também temos um tipo de ponteiro correspondente.
Por exemplo:
short
eshort*
.Existem duas maneiras de "olhar para" a variável
b
(é isso que provavelmente confunde a maioria dos iniciantes) :Você pode considerar
b
como uma variável do tipoint*
.Você pode considerar
*b
como uma variável do tipoint
.Por isso, algumas pessoas declarariam
int* b
, enquanto outras declarariamint *b
.Mas o fato é que essas duas declarações são idênticas (os espaços não têm sentido).
Você pode usar
b
como um ponteiro para um valor inteiro ou*b
como o valor inteiro apontado real.Você pode obter (ler) o valor pontas:
int c = *b
.E você pode definir (escrever) o valor pontas:
*b = 5
.Um ponteiro pode apontar para qualquer endereço de memória e não apenas para o endereço de alguma variável que você declarou anteriormente. No entanto, você deve ter cuidado ao usar ponteiros para obter ou definir o valor localizado no endereço de memória apontado.
Por exemplo:
int* a = (int*)0x8000000;
Aqui, temos variáveis
a
apontando para o endereço de memória 0x8000000.Se esse endereço de memória não estiver mapeado no espaço de memória do seu programa,
*a
é provável que qualquer operação de leitura ou gravação que esteja causando o travamento do programa, devido a uma violação de acesso à memória.Você pode alterar com segurança o valor de
a
, mas deve ter muito cuidado ao alterar o valor de*a
.O tipo
void*
é excepcional no fato de que não possui um "tipo de valor" correspondente que pode ser usado (ou seja, você não pode declararvoid a
). Esse tipo é usado apenas como um ponteiro geral para um endereço de memória, sem especificar o tipo de dados que reside nesse endereço.fonte
Talvez percorrer um pouco mais o torne mais fácil:
Peça que eles digam o que eles esperam que a saída esteja em cada linha, depois faça com que eles executem o programa e vejam o que acontece. Explique as perguntas deles (a versão simplificada certamente levará alguns - mas você pode se preocupar com estilo, rigidez e portabilidade posteriormente). Então, antes que a mente deles pareça exagerada ou se tornem um zumbi depois do almoço, escreva uma função que tenha um valor e a mesma que use um ponteiro.
Na minha experiência, foi superando esse "por que isso é impresso dessa maneira?" hump e , imediatamente, mostrando por que isso é útil em parâmetros de função, brincando (como um prelúdio para algum material básico de K&R, como análise de strings / processamento de array) que faz com que a lição não faça apenas sentido, mas permaneça.
O próximo passo é fazer com que eles expliquem a você como
i[0]
se relaciona&i
. Se eles puderem fazer isso, eles não esquecerão e você pode começar a falar sobre estruturas, mesmo um pouco antes do tempo, apenas para que afunde.As recomendações acima sobre caixas e flechas também são boas, mas também podem terminar em uma discussão completa sobre como a memória funciona - que é uma palestra que deve acontecer em algum momento, mas pode desviar o foco do ponto imediatamente à mão. : como interpretar a notação do ponteiro em C.
fonte
int foo = 1;
. Agora, este é OK:int *bar; *bar = foo;
. Isso não está bom:int *bar = foo;
O tipo da expressão
*bar
éint
; assim, o tipo da variável (e expressão)bar
éint *
. Como a variável possui o tipo de ponteiro, seu inicializador também deve ter o tipo de ponteiro.Há uma inconsistência entre a inicialização e a atribuição da variável do ponteiro; isso é algo que precisa ser aprendido da maneira mais difícil.
fonte
Prefiro lê-lo como o primeiro se
*
aplica aint
mais debar
.fonte
int* a, b
que não faz o que eles pensam que faz.int* a,b
deva ser usado. Para melhor visibilidade, atualização, etc ... deve haver apenas uma declaração de variável por linha e nunca mais. Também é algo para explicar aos iniciantes, mesmo que o compilador possa lidar com isso.*
como parte do tipo e simplesmente desencorajarint* a, b
. A menos que você prefere dizer que*a
é do tipoint
em vez dea
um ponteiro paraint
...int *a, b;
não deve ser usado. Declarar duas variáveis com tipos diferentes na mesma instrução é uma prática muito ruim e uma forte candidata a problemas de manutenção na linha. Talvez seja diferente para aqueles de nós que trabalham no campo incorporado, onde umint*
e umint
geralmente têm tamanhos diferentes e às vezes são armazenados em locais de memória completamente diferentes. É um dos muitos aspectos da linguagem C que seria melhor ensinado como 'é permitido, mas não faça'.Question 1
: O que ébar
?Ans
: É uma variável de ponteiro (para digitarint
). Um ponteiro deve apontar para algum local válido da memória e posteriormente deve ser desreferenciado (* bar) usando um operador unário*
para ler o valor armazenado nesse local.Question 2
: O que é&foo
?Ans
: foo é uma variável do tipoint
.que é armazenada em algum local válido da memória e nesse local nós o obtemos do operador&
; agora, o que temos é um local válido na memória&foo
.Então, ambos juntos, ou seja, o que o ponteiro precisava era de um local de memória válido e isso é obtido,
&foo
para que a inicialização seja boa.Agora, o ponteiro
bar
está apontando para a localização válida da memória e o valor armazenado nela pode ser retirado da referência.*bar
fonte
Você deve indicar um iniciante que * tenha significado diferente na declaração e na expressão. Como você sabe, * na expressão é um operador unário e * Na declaração não é um operador e apenas um tipo de sintaxe combinada com o tipo para permitir que o compilador saiba que é um tipo de ponteiro. é melhor dizer um iniciante, "* tem um significado diferente. Para entender o significado de *, você deve descobrir onde * é usado"
fonte
Eu acho que o diabo está no espaço.
Eu escreveria (não apenas para iniciantes, mas também para mim): int * bar = & foo; em vez de int * bar = & foo;
isso deve tornar evidente qual é a relação entre sintaxe e semântica
fonte
Já foi observado que * possui várias funções.
Há outra idéia simples que pode ajudar um iniciante a entender as coisas:
Pense que "=" também tem várias funções.
Quando a atribuição é usada na mesma linha da declaração, pense nela como uma chamada de construtor, não como uma atribuição arbitrária.
Quando você vê:
Pense que é quase equivalente a:
Os parênteses têm precedência sobre o asterisco, portanto, "& foo" é muito mais facilmente atribuído intuitivamente a "bar" do que a "* bar".
fonte
Vi essa pergunta há alguns dias e depois estava lendo a explicação da declaração de tipo de Go no Go Blog . Ele começa fornecendo uma conta das declarações do tipo C, que parece ser um recurso útil para adicionar a esse segmento, embora eu ache que já existem respostas mais completas.
(Ele continua descrevendo como estender esse entendimento para ponteiros de função, etc.)
Essa é uma maneira que eu nunca pensei sobre isso antes, mas parece uma maneira bastante direta de explicar a sobrecarga da sintaxe.
fonte
Se o problema for a sintaxe, pode ser útil mostrar código equivalente com template / using.
Isso pode ser usado como
Depois disso, compare a sintaxe normal / C com essa abordagem somente em C ++. Isso também é útil para explicar ponteiros const.
fonte
A fonte da confusão surge do fato de que o
*
símbolo pode ter significados diferentes em C, dependendo do fato em que é usado. Para explicar o ponteiro para um iniciante, o significado de*
símbolo em diferentes contextos deve ser explicado.Na declaração
o
*
símbolo não é o operador indireto . Em vez disso, ajuda a especificar o tipo debar
informação ao compilador quebar
é um ponteiro para umint
. Por outro lado, quando aparece em uma declaração, o*
símbolo (quando usado como operador unário ) executa indiretamente. Portanto, a declaraçãoestaria errado, pois atribui o endereço de
foo
para o objeto quebar
aponta para, não parabar
si mesmo.fonte
"talvez escrevê-lo como int * bar torne mais óbvio que a estrela é realmente parte do tipo, não parte do identificador". Então eu faço. E eu digo que é algo como Type, mas apenas para um nome de ponteiro.
"É claro que isso gera problemas diferentes com coisas não intuitivas, como int * a, b."
fonte
Aqui você tem que usar, entender e explicar a lógica do compilador, não a lógica humana (eu sei, você é um humano, mas aqui deve imitar o computador ...).
Quando você escreve
os grupos de compiladores que, como
Ou seja: aqui está uma nova variável, seu nome é
bar
, seu tipo é ponteiro para int e seu valor inicial é&foo
.E você deve adicionar: as
=
denota acima uma inicialização não uma afetação, enquanto que no seguintes expressões*bar = 2;
que é uma afetaçãoEditar por comentário:
Cuidado: no caso de declaração múltipla, isso
*
está relacionado apenas à seguinte variável:bar é um ponteiro para int inicializado pelo endereço de foo, b é um int inicializado para 2 e em
bar no ponteiro imóvel para int ep é um ponteiro para um ponteiro para um int inicializado no endereço ou barra.
fonte
int* a, b;
declara a como ponteiro para anint
, mas b como anint
. O*
símbolo tem apenas dois significados distintos: em uma declaração, indica um tipo de ponteiro e, em uma expressão, é o operador de desreferência unária.*
rattached ao tipo, para que o ponteiro seja inicializado, enquanto que em uma ação afetada o valor apontado é afetado. Mas pelo menos você me deu um chapéu agradável :-)Basicamente, o ponteiro não é uma indicação de matriz. Iniciante facilmente pensa que o ponteiro se parece com matriz. a maioria dos exemplos de strings usando o
"char * pstr" é semelhante a
"char str [80]"
Mas, coisas importantes, o ponteiro é tratado como apenas um número inteiro no nível mais baixo do compilador.
Vejamos exemplos:
Os resultados serão 0x2a6b7ed0 como endereço de str []
Portanto, basicamente, lembre-se de que ponteiro é algum tipo de número inteiro. apresentando o endereço.
fonte
Eu explicaria que ints são objetos, assim como floats, etc. Um ponteiro é um tipo de objeto cujo valor representa um endereço na memória (daí o motivo de um ponteiro usar como padrão NULL).
Quando você declara um ponteiro pela primeira vez, usa a sintaxe type-pointer-name. É lido como um "ponteiro inteiro chamado nome que pode apontar para o endereço de qualquer objeto inteiro". Nós usamos essa sintaxe apenas durante a decleração, semelhante à forma como declaramos um int como 'int num1', mas usamos apenas 'num1' quando queremos usar essa variável, não 'int num1'.
int x = 5; // um objeto inteiro com um valor de 5
int * ptr; // um número inteiro com um valor NULL por padrão
Para fazer um ponteiro apontar para o endereço de um objeto, usamos o símbolo '&' que pode ser lido como "o endereço de".
ptr = & x; // now value é o endereço de 'x'
Como o ponteiro é apenas o endereço do objeto, para obter o valor real mantido nesse endereço, devemos usar o símbolo '*' que, quando usado antes de um ponteiro, significa "o valor no endereço apontado por".
std :: cout << * ptr; // imprime o valor no endereço
Você pode explicar brevemente que ' ' é um 'operador' que retorna resultados diferentes com diferentes tipos de objetos. Quando usado com um ponteiro, o ' operador ' não significa mais "multiplicado por".
Ajuda a desenhar um diagrama mostrando como uma variável tem um nome e um valor e um ponteiro tem um endereço (o nome) e um valor e mostra que o valor do ponteiro será o endereço do int.
fonte
Um ponteiro é apenas uma variável usada para armazenar endereços.
A memória em um computador é composta de bytes (um byte consiste em 8 bits) organizados de maneira seqüencial. Cada byte tem um número associado a ele, exatamente como o índice ou o subscrito em uma matriz, que é chamado de endereço do byte. O endereço do byte começa de 0 a um menor que o tamanho da memória. Por exemplo, digamos em 64 MB de RAM, existem 64 * 2 ^ 20 = 67108864 bytes. Portanto, o endereço desses bytes começará de 0 a 67108863.
Vamos ver o que acontece quando você declara uma variável.
marcas int;
Como sabemos que um int ocupa 4 bytes de dados (assumindo que estamos usando um compilador de 32 bits), o compilador reserva 4 bytes consecutivos da memória para armazenar um valor inteiro. O endereço do primeiro byte dos 4 bytes alocados é conhecido como o endereço das marcas de variável. Digamos que o endereço de 4 bytes consecutivos seja 5004, 5005, 5006 e 5007, então o endereço das marcas de variável será 5004.
Declarando Variáveis de Ponteiro
Como já foi dito, um ponteiro é uma variável que armazena um endereço de memória. Assim como qualquer outra variável, você precisa primeiro declarar uma variável de ponteiro antes de poder usá-la. Aqui está como você pode declarar uma variável de ponteiro.
Sintaxe:
data_type *pointer_name;
data_type é o tipo do ponteiro (também conhecido como o tipo base do ponteiro). pointer_name é o nome da variável, que pode ser qualquer identificador C válido.
Vamos dar alguns exemplos:
int * ip significa que ip é uma variável de ponteiro capaz de apontar para variáveis do tipo int. Em outras palavras, uma variável de ponteiro ip pode armazenar apenas o endereço de variáveis do tipo int. Da mesma forma, a variável de ponteiro fp pode armazenar apenas o endereço de uma variável do tipo float. O tipo de variável (também conhecido como tipo base) ip é um ponteiro para int e o tipo de fp é um ponteiro para flutuar. Uma variável de ponteiro do tipo ponteiro para int pode ser representada simbolicamente como (int *). Da mesma forma, uma variável de ponteiro do tipo ponteiro para flutuar pode ser representada como (float *)
Depois de declarar uma variável de ponteiro, o próximo passo é atribuir algum endereço de memória válido a ela. Você nunca deve usar uma variável de ponteiro sem atribuir um endereço de memória válido a ela, porque logo após a declaração ela contém valor de lixo e pode estar apontando para qualquer lugar da memória. O uso de um ponteiro não atribuído pode gerar um resultado imprevisível. Pode até causar uma falha no programa.
Fonte: thecguru é de longe a explicação mais simples e detalhada que já encontrei.
fonte