Puntatori in C: quando usare la e commerciale e l'asterisco?


298

Ho appena iniziato con i puntatori e sono leggermente confuso. So che &indica l'indirizzo di una variabile e che *può essere utilizzato davanti a una variabile puntatore per ottenere il valore dell'oggetto a cui punta il puntatore. Ma le cose funzionano diversamente quando lavori con array, stringhe o quando chiami funzioni con una copia puntatore di una variabile. È difficile vedere uno schema logico all'interno di tutto questo.

Quando dovrei usare &e *?


5
Illustra come vedi le cose a volte funzionare diversamente. Altrimenti, dobbiamo indovinare cosa ti confonde.
Drew Dormann,

1
D'accordo con Neil Butterworth. Probabilmente otterremo molte più informazioni ottenendole di prima mano da un libro e la spiegazione di K&R è abbastanza chiara.
Tom,

145
Non sono d'accordo con tutti voi che affermate che non è una buona idea porre questo tipo di domande su SO. SO è diventata la risorsa numero 1 durante la ricerca su Google. Non stai dando abbastanza credito a queste risposte. Leggi la risposta di Dan Olson. Questa risposta è davvero perspicace e incredibilmente utile per i neofiti. RTFMè inutile, e francamente molto scortese. Se non hai tempo di rispondere, sii rispettoso di chi si prende il tempo di rispondere a queste domande. Vorrei poterlo fare per "anon" ma ovviamente lui / lei non ha il tempo di contribuire in modo significativo.
SSH Questo

18
Cosa SSH ha detto questo è assolutamente vero. Alcune persone gridano "Just Google it", ma al giorno d'oggi è il contrario: "Basta guardare StackOverflow". Questa domanda è utile per molte persone. (Da qui i voti positivi e non i voti negativi.)
MC Emperor

Risposte:


610

Hai puntatori e valori:

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

Trasforma un puntatore in un valore con *:

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

Trasformi un valore in un puntatore con &:

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

Modifica: nel caso degli array, vengono trattati in modo molto simile ai puntatori. Se li consideri come puntatori, utilizzerai *per ottenere i valori al loro interno come spiegato sopra, ma c'è anche un altro modo più comune di utilizzare l' []operatore:

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

Per ottenere il secondo 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

Quindi l' []operatore di indicizzazione è una forma speciale *dell'operatore e funziona in questo modo:

a[i] == *(a + i);  // these two statements are the same thing

2
Come mai questo non funziona? int aX[] = {3, 4}; int *bX = &aX;
Pieter,

5
Le matrici sono speciali e possono essere convertite in puntatori in modo trasparente. Questo evidenzia un altro modo per passare da un puntatore a un valore, lo aggiungerò alla spiegazione sopra.
Dan Olson,

4
Se lo capisco correttamente ... l'esempio int *bX = &aX;non funziona perché aXrestituisce già l'indirizzo di aX[0](cioè &aX[0]), quindi &aXotterrebbe l'indirizzo di un indirizzo che non ha senso. È corretto?
Pieter,

6
Hai ragione, anche se ci sono casi in cui potresti effettivamente desiderare l'indirizzo dell'indirizzo. In tal caso lo dichiareresti come int ** bX = & aX, ma questo è un argomento più avanzato.
Dan Olson,

3
@Dan, dato int aX[] = {3,4};, int **bX = &aX;è un errore. &aXè di tipo "puntatore all'array [2] di int", non "puntatore al puntatore a int". In particolare, il nome di un array non viene considerato come un puntatore al suo primo elemento per unario &. Puoi fare:int (*bX)[2] = &aX;
Alok Singhal,

28

C'è uno schema quando si tratta di matrici e funzioni; all'inizio è solo un po 'difficile.

Quando si ha a che fare con le matrici, è utile ricordare quanto segue: quando un'espressione di matrice appare nella maggior parte dei contesti, il tipo di espressione viene implicitamente convertito da "matrice di elementi N di T" a "puntatore a T" e il suo valore è impostato per puntare al primo elemento dell'array. Le eccezioni a questa regola sono quando l'espressione di matrice appare come un operando dell'operatore &o sizeof, oppure quando si utilizza un letterale stringa come inizializzatore in una dichiarazione.

Pertanto, quando si chiama una funzione con un'espressione di matrice come argomento, la funzione riceverà un puntatore, non una matrice:

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

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

Ecco perché non usi l' &operatore per argomenti corrispondenti a "% s" in scanf():

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

A causa della conversione implicita, scanf()riceve un char *valore che punta all'inizio strdell'array. Questo vale per qualsiasi funzione chiamata con un'espressione matrice come argomento (praticamente qualsiasi delle str*funzioni, *scanfe *printffunzioni, ecc).

In pratica, probabilmente non chiamerai mai una funzione con un'espressione array usando l' &operatore, come in:

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

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

Tale codice non è molto comune; devi conoscere la dimensione dell'array nella dichiarazione della funzione e la funzione funziona solo con i puntatori ad array di dimensioni specifiche (un puntatore a un array a 10 elementi di T è un tipo diverso rispetto a un puntatore a un array a 11 elementi di T).

Quando un'espressione di matrice appare come un operando &all'operatore, il tipo dell'espressione risultante è "puntatore alla matrice di elementi N di T" o T (*)[N], che è diverso da una matrice di puntatori ( T *[N]) e un puntatore al tipo di base ( T *).

Quando si ha a che fare con funzioni e puntatori, la regola da ricordare è: se si desidera modificare il valore di un argomento e farlo riflettere nel codice chiamante, è necessario passare un puntatore all'oggetto che si desidera modificare. Ancora una volta, gli array gettano un po 'di chiave inglese nelle opere, ma ci occuperemo prima dei casi normali.

Ricorda che C passa tutti gli argomenti delle funzioni in base al valore; il parametro formale riceve una copia del valore nel parametro effettivo e qualsiasi modifica al parametro formale non si riflette nel parametro effettivo. L'esempio comune è una funzione di scambio:

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);

Otterrai il seguente output:

prima dello scambio: a = 1, b = 2
dopo lo scambio: a = 1, b = 2

I parametri formali xe ysono oggetti distinti da ae b, perciò la modifica xe ynon si riflettono ae b. Dato che vogliamo modificare i valori di ae b, dobbiamo passare i puntatori ad essi alla funzione di scambio:

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);

Ora il tuo output sarà

prima dello scambio: a = 1, b = 2
dopo lo scambio: a = 2, b = 1

Si noti che, nella funzione di scambio, non cambiamo i valori di xe y, ma i valori di cosa xe y puntiamo a . Scrivere su *xè diverso dallo scrivere su x; non stiamo aggiornando il valore in xsé, otteniamo una posizione da xe aggiorniamo il valore in quella posizione.

Questo è altrettanto vero se vogliamo modificare un valore di puntatore; se scriviamo

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

quindi stiamo modificando il valore del parametro di input stream, non quello a cui stream punta , quindi la modifica streamnon ha alcun effetto sul valore di in; affinché questo funzioni, dobbiamo passare un puntatore al puntatore:

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

Ancora una volta, gli array gettano un po 'di chiave inglese nelle opere. Quando si passa un'espressione di matrice a una funzione, ciò che la funzione riceve è un puntatore. A causa della modalità di definizione della sottoscrizione di array, è possibile utilizzare un operatore di sottoscrizione su un puntatore nello stesso modo in cui è possibile utilizzarlo su un array:

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;}

Si noti che gli oggetti array non possono essere assegnati; cioè, non puoi fare qualcosa del genere

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

quindi vuoi stare attento quando hai a che fare con i puntatori alle matrici; qualcosa di simile a

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

non funzionerà.


16

In poche parole

  • &indica l' indirizzo di , vedrai che nei segnaposto per le funzioni per modificare la variabile di parametro come in C, le variabili di parametro vengono passate per valore, usando la e commerciale significa passare per riferimento.
  • *indica la dereferenza di una variabile puntatore, che significa ottenere il valore di quella variabile puntatore.
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
   /* ... */
}

L'esempio sopra mostra come chiamare una funzione foousando il pass-by-reference, confrontarlo con questo

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
   /* ... */
}

Ecco un'illustrazione dell'uso di una dereferenza

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

Quanto sopra illustra come abbiamo ottenuto l' indirizzo di y e assegnato alla variabile puntatore p. Quindi dereference p attaccando il *davanti ad esso per ottenere il valore di p, cioè *p.


10

Sì, può essere abbastanza complicato poiché *viene utilizzato per molti scopi diversi in C / C ++.

Se *appare davanti a una variabile / funzione già dichiarata, significa che:

  • a) *dà accesso al valore di quella variabile (se il tipo di quella variabile è un tipo di puntatore o ha sovraccaricato l' *operatore).
  • b) *ha il significato dell'operatore moltiplica, in tal caso, ci deve essere un'altra variabile alla sinistra di*

Se *appare in una dichiarazione di variabile o funzione significa che quella variabile è un puntatore:

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 &appare in una dichiarazione di variabile o funzione, generalmente significa che quella variabile è un riferimento a una variabile di quel tipo.

Se &appare davanti a una variabile già dichiarata, restituisce l'indirizzo di quella variabile

Inoltre, dovresti sapere che quando passi un array a una funzione, dovrai sempre passare anche le dimensioni dell'array di quell'array, tranne quando l'array è qualcosa come un cstring con terminazione a 0 (array di caratteri).


1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (spazio non valido)
PixnBits

4

Quando si dichiara una variabile del puntatore o un parametro di funzione, utilizzare *:

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

NB: ogni variabile dichiarata ha bisogno del proprio *.

Quando vuoi prendere l'indirizzo di un valore, usa &. Quando si desidera leggere o scrivere il valore in un puntatore, utilizzare *.

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

a = *f(&a);

Le matrici di solito vengono trattate come puntatori. Quando si dichiara un parametro array in una funzione, è possibile dichiarare altrettanto facilmente che è un puntatore (significa la stessa cosa). Quando si passa un array a una funzione, si passa effettivamente un puntatore al primo elemento.

I puntatori a funzione sono le uniche cose che non seguono del tutto le regole. Puoi prendere l'indirizzo di una funzione senza usare & e puoi chiamare un puntatore a funzione senza usare *.


4

Stavo esaminando tutte le spiegazioni prolifiche, quindi invece mi sono rivolto a un video dell'Università del New South Wales per il salvataggio. Ecco la semplice spiegazione: se abbiamo una cella che ha indirizzo xe valore 7, il modo indiretto per chiedere l'indirizzo di valore 7è &7e il modo indiretto per chiedere valore all'indirizzo xè *x. (cell: x , value: 7) == (cell: &7 , value: *x)Quindi . Un altro modo per esaminarlo: si Johnsiede a 7th seat. Il *7th seatpunto indicherà Johne &Johndarà address/ posizione del 7th seat. Questa semplice spiegazione mi ha aiutato e spero che possa aiutare anche gli altri. Ecco il link per l'eccellente video: clicca qui.

Ecco un altro esempio:

#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();
}

Componente aggiuntivo: inizializza sempre il puntatore prima di usarli. In caso contrario, il puntatore punterà a qualsiasi cosa, il che potrebbe causare l'arresto anomalo del programma perché il sistema operativo ti impedirà di accedere alla memoria che sa di non possedere. p = &x;, stiamo assegnando al puntatore una posizione specifica.


3

In realtà, ce l'hai con te, non c'è niente di più che devi sapere :-)

Vorrei solo aggiungere i seguenti bit:

  • le due operazioni sono estremità opposte dello spettro. &accetta una variabile e ti dà l'indirizzo, *prende un indirizzo e ti dà la variabile (o i contenuti).
  • le matrici "degradano" ai puntatori quando le si passa alle funzioni.
  • puoi effettivamente avere più livelli su indiretta ( char **psignifica che pè un puntatore a un puntatore a char.

Per quanto riguarda le cose che funzionano diversamente, non proprio:

  • le matrici, come già accennato, si degradano ai puntatori (al primo elemento dell'array) quando passano alle funzioni; non conservano le informazioni sulla dimensione.
  • non ci sono stringhe in C, ma matrici di caratteri che, per convenzione, rappresentano una stringa di caratteri terminata da un carattere zero ( \0).
  • Quando si passa l'indirizzo di una variabile a una funzione, è possibile rimuovere il riferimento al puntatore per modificare la variabile stessa (normalmente le variabili vengono passate in base al valore (tranne che per le matrici)).

3

Penso che tu sia un po 'confuso. Dovresti leggere un buon tutorial / libro sui puntatori.

Questo tutorial è ottimo per i principianti (spiega chiaramente cosa &e cosa *sono). E sì, non dimenticare di leggere il libro Pointers in C di Kenneth Reek.

La differenza tra &ed *è molto chiara.

Esempio:

#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;
}

1

Ok, sembra che il tuo post sia stato modificato ...

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

Vedi come puoi usare il &per ottenere l'indirizzo dell'inizio della struttura dell'array? Il seguente

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

farà la stessa cosa.


La domanda è stata taggata C non C ++.
Prasoon Saurav,

1
E ho rimosso la cout offensiva <<
wheaties
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.