Указатели на С: когда использовать амперсанд и звездочку?


298

Я только начинаю с указателей, и я немного запутался. Я знаю, что &означает адрес переменной и который *может быть использован перед переменной указателя, чтобы получить значение объекта, на который указывает указатель. Но все работает иначе, когда вы работаете с массивами, строками или когда вы вызываете функции с копией указателя на переменную. Трудно увидеть паттерн логики внутри всего этого.

Когда я должен использовать &и *?


5
Пожалуйста, проиллюстрируйте, как вы видите вещи, которые иногда работают по-другому. В противном случае, мы должны угадать, что вас смущает.
Дрю Дорманн

1
Согласен с Нилом Баттервортом. Вероятно, я получу гораздо больше информации из первых рук, и объяснение K & R вполне понятно.
Том

145
Я не согласен со всеми вами, кто говорит, что не стоит задавать такие вопросы на SO. ТАК стал ресурсом номер 1 при поиске в Google. Вы не уделяете должного внимания этим ответам. Прочитайте ответ Дэна Олсона. Этот ответ действительно проницательный и невероятно полезен для новичков. RTFMбесполезно, и, откровенно говоря, очень грубо. Если у вас нет времени, чтобы ответить, относитесь с уважением к тем, кто находит время, чтобы ответить на эти вопросы. Хотелось бы @ это «анонить», но, очевидно, у него / нее нет времени внести какой-либо значимый вклад.
SSH Это

18
Что сказал SSH Это абсолютно верно. Некоторые люди кричат ​​«Просто гуглите это», но в настоящее время все наоборот: «Просто посмотрите на StackOverflow». Этот вопрос полезен для многих людей. (Отсюда и отрицательные и отрицательные голоса.)
MC Emperor

Ответы:


610

У вас есть указатели и значения:

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

Вы превращаете указатель в значение с помощью *:

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

Вы превращаете значение в указатель с помощью &:

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

Изменить: В случае массивов, они очень похожи на указатели. Если вы думаете о них как о указателях, вы будете использовать *для получения значений внутри них, как описано выше, но есть и другой, более распространенный способ использования []оператора:

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

Чтобы получить второй элемент:

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

Так что []оператор индексирования - это особая форма *оператора, и он работает так:

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

2
Почему это не работает? int aX[] = {3, 4}; int *bX = &aX;
Питер

5
Массивы являются специальными и могут быть прозрачно преобразованы в указатели. Это подчеркивает другой способ получить указатель на значение, я добавлю его в объяснение выше.
Дэн Олсон

4
Если я правильно понимаю это ... пример int *bX = &aX;не работает, потому что aXуже возвращает адрес aX[0](то есть &aX[0]), поэтому &aXполучил бы адрес, который не имеет смысла. Это верно?
Питер

6
Вы правы, хотя бывают случаи, когда вам может понадобиться адрес этого адреса. В этом случае вы бы объявили его как int ** bX = & aX, но это более сложная тема.
Дэн Олсон

3
@ Дан, учитывая int aX[] = {3,4};, int **bX = &aX;является ошибкой. &aXимеет тип «указатель на массив [2] из int», а не «указатель на указатель на int». В частности, имя массива не рассматривается как указатель на его первый элемент для унарного &. Вы можете сделать:int (*bX)[2] = &aX;
Alok Singhal

28

Существует закономерность при работе с массивами и функциями; сначала это немного сложно увидеть.

При работе с массивами полезно помнить следующее: когда выражение массива появляется в большинстве контекстов, тип выражения неявно преобразуется из «N-элементного массива T» в «указатель на T», и его значение устанавливается чтобы указать на первый элемент в массиве. Исключениями из этого правила являются случаи, когда выражение массива отображается как операнд оператора или &или sizeof, или когда это строковый литерал, используемый в качестве инициализатора в объявлении.

Таким образом, когда вы вызываете функцию с выражением массива в качестве аргумента, функция получит указатель, а не массив:

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

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

Вот почему вы не используете &оператор для аргументов, соответствующих "% s" в scanf():

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

Из-за неявного преобразования scanf()получает char *значение, указывающее на начало strмассива. Это справедливо для любой функции , вызываемой с выражением массива в качестве аргумента (только о каком - либо из str*функций, *scanfи *printfфункций и т.д.).

На практике вы, вероятно, никогда не будете вызывать функцию с выражением массива, используя &оператор, как в:

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

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

Такой код не очень распространен; Вы должны знать размер массива в объявлении функции, и функция работает только с указателями на массивы определенных размеров (указатель на массив из 10 элементов T отличается от указателя на массив из 11 элементов). Т).

Когда выражение массива появляется в качестве операнда &оператора, тип получающегося выражения - «указатель на массив из N элементов из T» или T (*)[N], который отличается от массива указателей ( T *[N]) и указателя на базовый тип ( T *).

При работе с функциями и указателями следует помнить следующее правило: если вы хотите изменить значение аргумента и отразить его в вызывающем коде, вы должны передать указатель на то, что вы хотите изменить. Опять же, массивы вбивают немного обезьяны, но сначала мы разберемся с обычными случаями.

Помните, что C передает все аргументы функции по значению; формальный параметр получает копию значения в фактическом параметре, и любые изменения формального параметра не отражаются в фактическом параметре. Типичным примером является функция подкачки:

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

Вы получите следующий вывод:

до обмена: а = 1, б = 2
после обмена: а = 1, б = 2

Формальные параметры xи yявляются отличными объектами от aи b, поэтому изменения xи yне отражаются в aи b. Поскольку мы хотим изменить значения aи b, мы должны передать им указатели на функцию 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);

Теперь ваш вывод будет

до обмена: а = 1, б = 2
после обмена: а = 2, б = 1

Обратите внимание, что в функции подкачки мы не меняем значения xи y, а только значения, на которые xи y указывают . Запись в *xотличается от записи в x; мы не обновляем само значение x, мы получаем местоположение xи обновляем значение в этом месте.

Это в равной степени верно, если мы хотим изменить значение указателя; если мы напишем

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

затем мы изменяем значение входного параметра stream, а не то, на что он stream указывает , поэтому изменение streamне влияет на значение in; чтобы это работало, мы должны передать указатель на указатель:

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

Опять же, массивы бросают немного обезьяньего гаечного ключа в работу. Когда вы передаете выражение массива функции, функция получает указатель. Из-за того, как определяется подписка на массив, вы можете использовать оператор указателя для указателя так же, как вы можете использовать его для массива:

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

Обратите внимание, что объекты массива не могут быть назначены; то есть вы не можете сделать что-то вроде

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

поэтому вы должны быть осторожны, когда имеете дело с указателями на массивы; что-то вроде

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

не сработает


16

Проще говоря

  • &означает адрес-адрес , вы увидите, что в заполнителях функций для изменения переменной параметра, как в C, переменные параметров передаются по значению, используя амперсанд для передачи по ссылке.
  • *означает разыменование переменной-указателя, что означает получение значения этой переменной-указателя.
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
   /* ... */
}

В приведенном выше примере показано, как вызвать функцию fooс помощью передачи по ссылке, сравните с этим

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

Вот иллюстрация использования разыменования

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

Выше показано, как мы получили адрес y и присвоили его переменной-указателю p. Тогда мы разыменовываем p путем присоединения *к фронту его , чтобы получить значение p, то есть *p.


10

Да, это может быть довольно сложно, так как *используется в C / C ++ для разных целей.

Если *появляется перед уже объявленной переменной / функцией, это означает, что:

  • а) *дает доступ к значению этой переменной (если тип этой переменной является типом указателя или перегружен *оператором).
  • б) *имеет значение оператора умножения, в этом случае должна быть другая переменная слева от*

Если *появляется в объявлении переменной или функции, это означает, что эта переменная является указателем:

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

Если &появляется в объявлении переменной или функции, это обычно означает, что эта переменная является ссылкой на переменную этого типа.

Если &появляется перед уже объявленной переменной, он возвращает адрес этой переменной

Кроме того, вы должны знать, что при передаче массива в функцию вы всегда должны будете также передавать размер массива этого массива, кроме случаев, когда массив является чем-то вроде cstring с нулевым символом в конце (массив символов).


1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (недопустимый пробел)
PixnBits

4

Когда вы объявляете переменную-указатель или параметр функции, используйте *:

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

Примечание: каждая объявленная переменная нуждается в своем собственном *.

Если вы хотите получить адрес значения, используйте &. Если вы хотите прочитать или записать значение в указателе, используйте *.

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

a = *f(&a);

Массивы обычно обрабатываются как указатели. Когда вы объявляете параметр массива в функции, вы можете также легко объявить, что это указатель (это означает то же самое). Когда вы передаете массив функции, вы фактически передаете указатель на первый элемент.

Функциональные указатели - единственные вещи, которые не совсем следуют правилам. Вы можете получить адрес функции без использования &, и вы можете вызвать указатель на функцию без использования *.


4

Я просматривал все многословные объяснения, поэтому вместо этого обратился к видео из Университета Нового Южного Уэльса за спасением. Вот простое объяснение: если у нас есть ячейка с адресом xи значением 7, косвенный способ запросить адрес значения 7- это &7и косвенный способ попросить значение по адресу xявляется *x.so (cell: x , value: 7) == (cell: &7 , value: *x).Another способ смотреть на это: Johnсидит 7th seat.the *7th seatукажут Johnи &Johnпередаст address/ расположение 7th seat. Это простое объяснение помогло мне и надеюсь, что оно поможет и другим. Вот ссылка на отличное видео: нажмите здесь.

Вот еще один пример:

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

Надстройка: всегда инициализируйте указатель перед их использованием. Если нет, указатель будет указывать на что-либо, что может привести к сбою программы, поскольку операционная система не позволит вам получить доступ к памяти, которой она не владеет. p = &x;Мы присваиваем указателю определенное место.


3

На самом деле, у вас есть все, что вам нужно знать :-)

Я бы просто добавил следующие биты:

  • две операции противоположных концов спектра. &берет переменную и дает вам адрес, *берет адрес и дает вам переменную (или содержимое).
  • массивы "ухудшаются" до указателей, когда вы передаете их функциям.
  • на самом деле вы можете иметь несколько уровней косвенности (это char **pозначает, что pуказатель на указатель на char.

Что касается вещей, работающих по-другому, не совсем:

  • массивы, как уже упоминалось, уменьшаются до указателей (до первого элемента в массиве) при передаче в функции; они не сохраняют информацию о размере.
  • в Си нет строк, только символьные массивы, которые, по соглашению, представляют собой строку символов, оканчивающихся символом нуля ( \0).
  • Когда вы передаете адрес переменной в функцию, вы можете отменить ссылку на указатель, чтобы изменить саму переменную (обычно переменные передаются по значению (за исключением массивов)).

3

Я думаю, что вы немного смущены. Вы должны прочитать хороший учебник / книгу по указателям.

Этот урок очень хорош для начинающих (четко объясняет, что &и *есть). И да, не забудьте прочитать книгу « Указатели на С » Кеннета Рика.

Разница между &и *очень очевидна.

Пример:

#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

Хорошо, похоже, ваше сообщение было отредактировано ...

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

Посмотрите, как вы можете использовать, &чтобы получить адрес начала структуры массива? Последующий

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

будет делать то же самое.


Вопрос был помечен C не C ++.
Прасун Саурав

1
И я удалил оскорбившегося
кута
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.