Ponteiros em C: quando usar o e comercial e o asterisco?

298

Estou apenas começando com ponteiros e estou um pouco confuso. Eu sei que &significa o endereço de uma variável e que *pode ser usado na frente de uma variável de ponteiro para obter o valor do objeto apontado pelo ponteiro. Mas as coisas funcionam de maneira diferente quando você está trabalhando com matrizes, seqüências de caracteres ou quando está chamando funções com uma cópia de ponteiro de uma variável. É difícil ver um padrão de lógica dentro de tudo isso.

Quando devo usar &e *?

Pieter
fonte
5
Por favor, ilustre como você vê as coisas às vezes funcionando de maneira diferente. Caso contrário, temos que adivinhar o que é que está confundindo você.
Tirou Dormann
1
Concordo com Neil Butterworth. Provavelmente, você obterá muito mais informações sobre o livro em primeira mão, e a explicação da K&R é bastante clara.
Tom
145
Discordo de todos vocês que dizem que não é uma boa ideia fazer esses tipos de perguntas sobre SO. SO tornou-se o recurso número 1 ao pesquisar no Google. Você não está dando crédito suficiente a essas respostas. Leia a resposta de Dan Olson. Esta resposta é verdadeiramente perspicaz e incrivelmente útil para iniciantes. RTFMé inútil e, francamente, muito rude. Se você não tiver tempo para responder, seja respeitoso com quem tiver tempo para responder a essas perguntas. Eu gostaria de poder @ isso para "anon", mas obviamente ele / ela não tem tempo para contribuir de maneira significativa.
SSH Este
18
O que SSH Isto dito é absolutamente verdade. Algumas pessoas gritam "Just Google it", mas hoje em dia é o contrário: "Basta olhar no StackOverflow". Esta pergunta é útil para muitas pessoas. (Daí o upvotes e não downvotes.)
MC Emperor

Respostas:

610

Você tem ponteiros e valores:

int* p; // variable p is pointer to integer type
int i; // integer value

Você transforma um ponteiro em um valor com *:

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

Você transforma um valor em um ponteiro com &:

int* p2 = &i; // pointer p2 will point to the address of integer i

Edit: No caso de matrizes, elas são tratadas como ponteiros. Se você pensar neles como ponteiros, estará usando *para obter os valores dentro deles, conforme explicado acima, mas também há outra maneira mais comum de usar o []operador:

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

Para obter o segundo elemento:

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

Portanto, o []operador de indexação é uma forma especial do *operador e funciona assim:

a[i] == *(a + i);  // these two statements are the same thing
Dan Olson
fonte
2
Como isso não funciona? int aX[] = {3, 4}; int *bX = &aX;
Pieter
5
As matrizes são especiais e podem ser convertidas em ponteiros de forma transparente. Isso destaca outra maneira de passar de um ponteiro para um valor, acrescentando-o à explicação acima.
Dan Olson
4
Se eu entendi isso corretamente ... o exemplo int *bX = &aX;não funciona porque o aXjá retorna o endereço de aX[0](ou seja &aX[0]), então &aXobteria o endereço de um endereço que não faz sentido. Isso está correto?
Pieter
6
Você está correto, embora haja casos em que você pode realmente querer o endereço do endereço. Nesse caso, você o declararia como int ** bX = & aX, mas este é um tópico mais avançado.
Dan Olson
3
@ Dan, dado int aX[] = {3,4};, int **bX = &aX;é um erro. &aXé do tipo "ponteiro para matriz [2] de int", não "ponteiro para ponteiro para int". Especificamente, o nome de uma matriz não é tratado como um ponteiro para seu primeiro elemento por unário &. Você pode fazer:int (*bX)[2] = &aX;
Alok Singhal
28

Existe um padrão ao lidar com matrizes e funções; é um pouco difícil de ver no começo.

Ao lidar com matrizes, é útil lembrar o seguinte: quando uma expressão de matriz aparece na maioria dos contextos, o tipo da expressão é implicitamente convertido de "matriz do elemento N de T" para "ponteiro para T" e seu valor é definido para apontar para o primeiro elemento na matriz. As exceções a esta regra são quando a expressão da matriz aparece como um operando de &ousizeof operadores, ou quando é uma string literal sendo usado como um inicializador em uma declaração.

Portanto, quando você chama uma função com uma expressão de matriz como argumento, a função receberá um ponteiro, não uma matriz:

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

É por isso que você não usa o &operador para argumentos correspondentes a "% s" em scanf():

char str[STRING_LENGTH];
...
scanf("%s", str);

Por causa da conversão implícita, scanf()recebe um char *valor que aponta para o início da strmatriz. Isso vale para qualquer função chamada com uma expressão de matriz como argumento (praticamente qualquer uma das str*funções *scanfe*printf funções, etc.).

Na prática, você provavelmente nunca chamará uma função com uma expressão de matriz usando o &operador, como em:

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

Esse código não é muito comum; você precisa conhecer o tamanho da matriz na declaração da função, e a função funciona apenas com ponteiros para matrizes de tamanhos específicos (um ponteiro para uma matriz de 10 elementos de T é um tipo diferente de um ponteiro para uma matriz de 11 elementos de T).

Quando uma expressão de matriz aparece como um operando para o &operador, o tipo da expressão resultante é "ponteiro para a matriz do elemento N de T" ou T (*)[N], que é diferente de uma matriz de ponteiros ( T *[N]) e um ponteiro para o tipo base (T * )

Ao lidar com funções e ponteiros, a regra a ser lembrada é: se você deseja alterar o valor de um argumento e refleti-lo no código de chamada, deve passar um ponteiro para o que deseja modificar. Novamente, as matrizes jogam um pouco de chave inglesa nos trabalhos, mas vamos lidar com os casos normais primeiro.

Lembre-se que C passa todos os argumentos da função por valor; o parâmetro formal recebe uma cópia do valor no parâmetro real e quaisquer alterações no parâmetro formal não são refletidas no parâmetro real. O exemplo comum é uma função de troca:

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

Você obterá a seguinte saída:

antes da troca: a = 1, b = 2
após a troca: a = 1, b = 2

Os parâmetros formais xe ysão objetos distintos de ae b, portanto, muda para xe ynão são refletidos em ae b. Como queremos modificar os valores de ae b, devemos passar ponteiros para eles para a função swap:

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

Agora sua saída será

antes da troca: a = 1, b = 2
após a troca: a = 2, b = 1

Observe que, na função swap, não alteramos os valores de xe y, mas os valores do que xe y apontamos . Escrever para *xé diferente de escrever para x; não estamos atualizando o valor em xsi, obtemos um local xe atualizamos o valor nesse local.

Isso é igualmente verdadeiro se quisermos modificar um valor de ponteiro; se escrevermos

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

então estamos modificando o valor do parâmetro de entrada stream, não o que stream aponta para , portanto, a alteração streamnão afeta o valor de in; para que isso funcione, devemos passar um ponteiro para o ponteiro:

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

Novamente, as matrizes jogam um pouco de uma chave inglesa nos trabalhos. Quando você passa uma expressão de matriz para uma função, o que a função recebe é um ponteiro. Devido à forma como a assinatura de matriz é definida, você pode usar um operador de subscrito em um ponteiro da mesma maneira que em uma matriz:

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

Observe que os objetos da matriz não podem ser atribuídos; ou seja, você não pode fazer algo como

int a[10], b[10];
...
a = b;

então você quer ter cuidado ao lidar com ponteiros para matrizes; algo como

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

não vai funcionar.

John Bode
fonte
16

Simplificando

  • &significa o endereço de , você verá que, nos espaços reservados para as funções modificarem a variável de parâmetro como em C, as variáveis ​​de parâmetro são passadas por valor, usando os meios e comercial para passar por referência.
  • *significa a desreferência de uma variável de ponteiro, o que significa obter o valor dessa variável de ponteiro.
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

O exemplo acima ilustra como chamar uma função foousando passagem por referência, compare com esta

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

Aqui está uma ilustração do uso de uma desreferência

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

O exemplo acima ilustra como obtivemos o endereço y e o atribuímos à variável ponteiro p. Então, desreferenciamos p anexando o *à frente dele para obter o valor de p, ie *p.

t0mm13b
fonte
10

Sim, isso pode ser bastante complicado, pois *é usado para diversos fins em C / C ++.

Se *aparecer na frente de uma variável / função já declarada, significa que:

  • a) *fornece acesso ao valor dessa variável (se o tipo dessa variável for do tipo ponteiro ou sobrecarregar o *operador).
  • b) *tem o significado de operador multiplicador; nesse caso, deve haver outra variável à esquerda do*

Se *aparecer em uma declaração de variável ou função, significa que essa variável é um ponteiro:

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer as well which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are identical

void func_takes_int_ptr1(int *int_ptr){} // these two are identical
void func_takes int_ptr2(int int_ptr[]){}// and legal

Se &aparecer em uma declaração de variável ou função, geralmente significa que essa variável é uma referência a uma variável desse tipo.

Se &aparecer na frente de uma variável já declarada, ele retornará o endereço dessa variável

Além disso, você deve saber que, ao passar um array para uma função, você sempre terá que passar também o tamanho do array, exceto quando o array for algo como uma cstring terminada em 0 (array de caracteres).

smerlin
fonte
1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (espaço inválido)
PixnBits
4

Ao declarar uma variável de ponteiro ou parâmetro de função, use o *:

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

Nota: cada variável declarada precisa de um *.

Quando você quiser usar o endereço de um valor, use &. Quando você quiser ler ou escrever o valor em um ponteiro, use *.

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

Matrizes geralmente são apenas tratadas como ponteiros. Quando você declara um parâmetro de matriz em uma função, pode facilmente declarar que é um ponteiro (significa a mesma coisa). Quando você passa uma matriz para uma função, na verdade você está passando um ponteiro para o primeiro elemento.

Ponteiros de função são as únicas coisas que não seguem as regras. Você pode pegar o endereço de uma função sem usar & e pode chamar um ponteiro de função sem usar *.

Jay Conrod
fonte
4

Eu estava olhando através de todas as explicações prolixas assim em vez virou-se para um vídeo da Universidade de Nova Gales do Sul para rescue.Here é a explicação simples: se temos uma célula que tem o endereço xe valor 7, a forma indireta de pedir endereço de valor 7é &7ea forma indireta de pedir para o valor no endereço xé *x.Assim (cell: x , value: 7) == (cell: &7 , value: *x).Outra maneira de olhar para ele: Johnfica na 7th seat.A *7th seatirá apontar para Johne &Johndará address/ localização do 7th seat. Essa explicação simples me ajudou e espero que ajude outras pessoas também. Aqui está o link para o excelente vídeo: clique aqui.

Aqui está outro exemplo:

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

Complemento : sempre inicialize o ponteiro antes de usá-lo. Caso contrário, o ponteiro apontará para qualquer coisa, o que pode resultar em falha do programa, porque o sistema operacional impedirá que você acesse a memória que sabe que não possui. p = &x;, estamos atribuindo ao ponteiro um local específico.


fonte
3

Na verdade, você sabe disso, não há mais nada que você precise saber :-)

Gostaria apenas de adicionar os seguintes bits:

  • as duas operações são extremidades opostas do espectro. &pega uma variável e fornece o endereço, *pega um endereço e fornece a variável (ou conteúdo).
  • matrizes "degradam" para ponteiros quando você as passa para funções.
  • você pode realmente ter vários níveis de indireção ( char **psignifica que pé um ponteiro para um ponteiro para a char.

Quanto às coisas que funcionam de maneira diferente, não realmente:

  • matrizes, como já mencionado, degradam em ponteiros (para o primeiro elemento na matriz) quando passadas para funções; eles não preservam informações de tamanho.
  • não há seqüências de caracteres em C, apenas matrizes de caracteres que, por convenção, representam uma sequência de caracteres terminada por um \0caractere zero ( ).
  • Quando você passa o endereço de uma variável para uma função, pode desassociar o ponteiro para alterar a própria variável (normalmente variáveis ​​são passadas por valor (exceto para matrizes)).
paxdiablo
fonte
3

Eu acho que você está um pouco confuso. Você deve ler um bom tutorial / livro sobre ponteiros.

Este tutorial é muito bom para iniciantes (explica claramente o que é &e o que *é). E sim, não esqueça de ler o livro Pointers in C, de Kenneth Reek.

A diferença entre &e *é muito clara.

Exemplo:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}
Prasoon Saurav
fonte
1

Ok, parece que sua postagem foi editada ...

double foo[4];
double *bar_1 = &foo[0];

Veja como você pode usar o &para obter o endereço do início da estrutura da matriz? Os seguintes

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

fará a mesma coisa.

wheaties
fonte
A pergunta foi marcada com C e não com C ++.
Prasoon Saurav
1
E eu removi o cout ofensivo <<
wheaties