Punteros en C: ¿cuándo usar el ampersand y el asterisco?

298

Estoy empezando con punteros y estoy un poco confundido. Sé que &significa la dirección de una variable y que *se puede usar frente a una variable de puntero para obtener el valor del objeto al que apunta el puntero. Pero las cosas funcionan de manera diferente cuando trabaja con matrices, cadenas o cuando llama a funciones con una copia de puntero de una variable. Es difícil ver un patrón de lógica dentro de todo esto.

¿Cuándo debo usar &y *?

Pieter
fuente
55
Por favor, ilustra cómo ves que las cosas a veces funcionan de manera diferente. De lo contrario, tenemos que adivinar qué es lo que te confunde.
Drew Dormann el
1
De acuerdo con Neil Butterworth. Probablemente obtendrá mucha más información obteniéndola de primera mano de un libro, y la explicación de K&R es bastante clara.
Tom
145
No estoy de acuerdo con todos ustedes que dicen que no es una buena idea hacer este tipo de preguntas sobre SO. SO se ha convertido en el recurso número 1 al buscar en Google. No estás dando suficiente crédito a estas respuestas. Lea la respuesta de Dan Olson. Esta respuesta es verdaderamente perspicaz e increíblemente útil para los novatos. RTFMes inútil y, francamente, muy grosero. Si no tiene tiempo para responder, sea respetuoso con quienes se toman el tiempo para responder estas preguntas. Desearía poder @ esto para "anon", pero obviamente él / ella no tiene el tiempo para contribuir de manera significativa.
SSH Este
18
Lo que dijo SSH es absolutamente cierto. Algunas personas gritan "Solo Google", pero hoy en día es al revés: "Solo mira StackOverflow". Esta pregunta es útil para muchas personas. (De ahí los votos a favor y en contra).
MC Emperor
3
beej.us/blog/data/c-pointers
Sr. Kennedy el

Respuestas:

610

Tienes punteros y valores:

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

Convierte un puntero en un valor con *:

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

Convierte un valor en un puntero con &:

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

Editar: en el caso de las matrices, se tratan mucho como punteros. Si los considera punteros, los usará *para obtener los valores dentro de ellos como se explicó anteriormente, pero también hay otra forma más común de usar el []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 obtener el 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

Entonces, el []operador de indexación es una forma especial del *operador, y funciona así:

a[i] == *(a + i);  // these two statements are the same thing
Dan Olson
fuente
2
¿Cómo es que esto no funciona? int aX[] = {3, 4}; int *bX = &aX;
Pieter
55
Las matrices son especiales y se pueden convertir en punteros de forma transparente. Esto destaca otra forma de pasar de un puntero a un valor, lo agregaré a la explicación anterior.
Dan Olson
44
Si entiendo esto correctamente ... el ejemplo int *bX = &aX;no funciona porque aXya devuelve la dirección de aX[0](es decir &aX[0]), por &aXlo que obtendría la dirección de una dirección que no tiene sentido. ¿Es esto correcto?
Pieter
66
Tienes razón, aunque hay casos en los que es posible que quieras la dirección de la dirección. En ese caso, lo declararías como int ** bX = & aX, pero este es un tema más avanzado.
Dan Olson
3
@Dan, dado int aX[] = {3,4};, int **bX = &aX;es un error. &aXes de tipo "puntero a matriz [2] de int", no "puntero a puntero a int". Específicamente, el nombre de una matriz no se trata como un puntero a su primer elemento para unario &. Puedes hacer:int (*bX)[2] = &aX;
Alok Singhal
28

Hay un patrón cuando se trata de matrices y funciones; Es un poco difícil de ver al principio.

Cuando se trata de matrices, es útil recordar lo siguiente: cuando una expresión de matriz aparece en la mayoría de los contextos, el tipo de la expresión se convierte implícitamente de "matriz de elementos N de T" a "puntero a T", y su valor se establece para apuntar al primer elemento en la matriz. Las excepciones a esta regla son cuando la expresión de matriz aparece como un operando ya sea del &o de sizeoflos operadores, o cuando se trata de una cadena literal de ser utilizado como un inicializador en una declaración.

Por lo tanto, cuando llama a una función con una expresión de matriz como argumento, la función recibirá un puntero, no una matriz:

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

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

Es por eso que no utiliza el &operador para argumentos correspondientes a "% s" en scanf():

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

Debido a la conversión implícita, scanf()recibe un char *valor que apunta al comienzo de la strmatriz. Esto es válido para cualquier función llamada con una expresión de matriz como argumento (casi cualquiera de las str*funciones *scanfy *printffunciones, etc.).

En la práctica, probablemente nunca invocará una función con una expresión de matriz utilizando el &operador, como en:

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

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

Tal código no es muy común; debe conocer el tamaño de la matriz en la declaración de la función, y la función solo funciona con punteros a matrices de tamaños específicos (un puntero a una matriz de 10 elementos de T es un tipo diferente que un puntero a una matriz de 11 elementos de T).

Cuando una expresión de matriz aparece como un operando para el &operador, el tipo de la expresión resultante es "puntero a la matriz de elementos N de T", o T (*)[N], que es diferente de una matriz de punteros ( T *[N]) y un puntero al tipo base ( T *)

Cuando se trata de funciones y punteros, la regla a recordar es: si desea cambiar el valor de un argumento y hacer que se refleje en el código de llamada, debe pasar un puntero a lo que desea modificar. Una vez más, las matrices arrojan un poco de una llave inglesa en las obras, pero primero nos ocuparemos de los casos normales.

Recuerde que C pasa todos los argumentos de función por valor; el parámetro formal recibe una copia del valor en el parámetro real, y cualquier cambio en el parámetro formal no se refleja en el parámetro real. El ejemplo común es una función de intercambio:

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

Obtendrá el siguiente resultado:

antes del intercambio: a = 1, b = 2
después del intercambio: a = 1, b = 2

Los parámetros formales xy yson objetos distintos de ay b, por lo que cambia xy yno se refleja en ay b. Como queremos modificar los valores de ay b, debemos pasarles punteros a la función de intercambio:

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

Ahora su salida será

antes del intercambio: a = 1, b = 2
después del intercambio: a = 2, b = 1

Tenga en cuenta que, en la función de intercambio, no cambiamos los valores de xy y, sino los valores de what xy y point to . Escribir en *xes diferente de escribir en x; no estamos actualizando el valor en xsí mismo, obtenemos una ubicación xy lo actualizamos en esa ubicación.

Esto es igualmente cierto si queremos modificar un valor de puntero; si escribimos

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

entonces estamos modificando el valor del parámetro de entrada stream, no lo que stream apunta , por lo que cambiar streamno tiene efecto sobre el valor de in; Para que esto funcione, debemos pasar un puntero al puntero:

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

Una vez más, las matrices arrojan un poco de una llave inglesa en las obras. Cuando pasa una expresión de matriz a una función, lo que recibe la función es un puntero. Debido a cómo se define la suscripción de matriz, puede usar un operador de subíndice en un puntero de la misma manera que puede usarlo en una 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;}

Tenga en cuenta que los objetos de matriz pueden no asignarse; es decir, no puedes hacer algo como

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

por lo tanto, debe tener cuidado cuando trabaje con punteros a matrices; algo como

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

no funciona

John Bode
fuente
16

En pocas palabras

  • &significa la dirección de , verá que en los marcadores de posición para que las funciones modifiquen la variable del parámetro como en C, las variables de parámetro se pasan por valor, utilizando los medios y los medios para pasar por referencia.
  • *significa la desreferencia de una variable de puntero, lo que significa obtener el valor de esa variable de puntero.
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
   /* ... */
}

El ejemplo anterior ilustra cómo llamar a una función foomediante el uso de paso por referencia, compárelo con esto

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

Aquí hay una ilustración del uso de una desreferencia

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

Lo anterior ilustra cómo obtuvimos la dirección y y la asignamos a la variable de puntero p. Luego, desreferenciamos p uniendo el *frente al mismo para obtener el valor de p, es decir *p.

t0mm13b
fuente
10

Sí, eso puede ser bastante complicado ya que *se usa para muchos propósitos diferentes en C / C ++.

Si *aparece delante de una variable / función ya declarada, significa que:

  • a) *da acceso al valor de esa variable (si el tipo de esa variable es un tipo de puntero o sobrecargado al *operador).
  • b) *tiene el significado del operador de multiplicación, en ese caso, tiene que haber otra variable a la izquierda del*

Si *aparece en una declaración de variable o función, significa que esa variable es un puntero:

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

Si &aparece en una declaración de variable o función, generalmente significa que esa variable es una referencia a una variable de ese tipo.

Si &aparece delante de una variable ya declarada, devuelve la dirección de esa variable

Además, debe saber que al pasar una matriz a una función, siempre tendrá que pasar el tamaño de la matriz también, excepto cuando la matriz sea algo así como una cadena de caracteres terminada en 0 (matriz de caracteres).

smerlin
fuente
1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (espacio no válido)
PixnBits
4

Cuando declare una variable de puntero o parámetro de función, use *:

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

NB: cada variable declarada necesita su propia *.

Cuando desee tomar la dirección de un valor, use &. Cuando desee leer o escribir el valor en un puntero, use *.

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

a = *f(&a);

Las matrices generalmente solo se tratan como punteros. Cuando declara un parámetro de matriz en una función, puede declarar con la misma facilidad que es un puntero (significa lo mismo). Cuando pasa una matriz a una función, en realidad está pasando un puntero al primer elemento.

Los punteros de función son las únicas cosas que no siguen las reglas. Puede tomar la dirección de una función sin usar &, y puede llamar a un puntero de función sin usar *.

Jay Conrod
fuente
4

Estaba revisando todas las explicaciones profundas, así que en su lugar recurrí a un video de la Universidad de Nueva Gales del Sur para que me rescataran. Aquí está la explicación simple: si tenemos una celda que tiene dirección xy valor 7, la forma indirecta de pedir una dirección de valor 7es &7y la forma indirecta de pedir valor en la dirección xes *x. (cell: x , value: 7) == (cell: &7 , value: *x)Entonces . Otra forma de verlo: se Johnsienta en 7th seat. El *7th seatapuntará Johny &Johndará address/ ubicación del 7th seat. Esta simple explicación me ayudó y espero que también ayude a otros. Aquí está el enlace para el excelente video: haga clic aquí.

Aquí hay otro ejemplo:

#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: siempre inicialice el puntero antes de usarlos; de lo contrario, el puntero apuntará a cualquier cosa, lo que podría provocar el bloqueo del programa porque el sistema operativo le impedirá acceder a la memoria que sabe que no posee. p = &x;, estamos asignando al puntero una ubicación específica.


fuente
3

En realidad, lo tienes claro, no hay nada más que necesites saber :-)

Solo agregaría los siguientes bits:

  • Las dos operaciones son extremos opuestos del espectro. &toma una variable y le da la dirección, *toma una dirección y le da la variable (o contenido).
  • Las matrices se "degradan" a punteros cuando las pasa a funciones.
  • en realidad puede tener múltiples niveles en indirección ( char **psignifica que pes un puntero a un puntero a un char.

En cuanto a las cosas que funcionan de manera diferente, en realidad no:

  • las matrices, como ya se mencionó, se degradan a punteros (al primer elemento de la matriz) cuando se pasan a funciones; No conservan la información del tamaño.
  • no hay cadenas en C, solo matrices de caracteres que, por convención, representan una cadena de caracteres terminada por un carácter cero ( \0).
  • Cuando pasa la dirección de una variable a una función, puede desreferenciar el puntero para cambiar la variable en sí misma (normalmente las variables se pasan por valor (excepto las matrices)).
paxdiablo
fuente
3

Creo que estás un poco confundido. Deberías leer un buen tutorial / libro sobre punteros.

Este tutorial es muy bueno para empezar (explica claramente qué &y qué *son). Y sí, no te olvides de leer el libro Pointers in C de Kenneth Reek.

La diferencia entre &y *es muy clara.

Ejemplo:

#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
fuente
1

Ok, parece que tu publicación fue editada ...

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

¿Ves cómo puedes usar &para obtener la dirección del comienzo de la estructura de la matriz? El seguimiento

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

Hará lo mismo.

Wheaties
fuente
La pregunta ha sido etiquetada C no C ++.
Prasoon Saurav
1
Y he eliminado el ataque ofensivo <<
wheaties