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

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

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

272
задан Pieter 7 April 2012 в 12:42
поделиться

8 ответов

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

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
578
ответ дан 23 November 2019 в 02:14
поделиться

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

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

NB: каждая заявленная переменная нуждается в себе *.

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

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

a = *f(&a);

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

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

4
ответ дан 23 November 2019 в 02:14
поделиться

На самом деле, у вас есть патенты, нет ничего больше, что вам нужно знать: -)

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

  • Две операции противоположные концы спектра. & принимает переменную и дает вам адрес, * принимает адрес и дает переменную (или содержимое).
  • Массивы «ухудшаются» к указателям, когда вы передаете их в функции.
  • Вы на самом деле можете иметь несколько уровней на подражании ( Char ** P означает, что P является указателем на указатель на Char .

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

  • массивы, как уже упоминалось, ухудшаются на указатели (к первому элементу в массиве) при передаче к функциям; они не сохраняют информацию о размере.
  • нет нищих В C просто массивы символов, которые по согласованию, представляют собой строку символов, прекращенную ноль ( \ 0 ).
  • , когда вы передаете адрес переменной в функцию, вы можете -Reference Указатель для изменения самой переменной (обычно переменные пропускаются по значению (кроме массивов)).
3
ответ дан 23 November 2019 в 02:14
поделиться

Сделайте просто

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

Приведенный выше пример иллюстрирует, как позвонить в функцию FO , используя Pass-Researt, сравните с этим

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 Отказ

12
ответ дан 23 November 2019 в 02:14
поделиться

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

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

сделает то же самое.

1
ответ дан 23 November 2019 в 02:14
поделиться

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

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

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

Пример:

#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;
}
3
ответ дан 23 November 2019 в 02:14
поделиться

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

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

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

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

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 aswell 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 indentical

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

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

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

дополнительно вы должны знать, что при прохождении массива на функцию вы всегда должны пройти Размер массива этого массива Aswell, за исключением случаев, когда массив является чем-то вроде 0-оверженной CSTRING (массив CHAR).

9
ответ дан 23 November 2019 в 02:14
поделиться

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

При работе с массивами полезно запоминать следующее: когда в большинстве контекстов появляется выражение массива, тип выражения неявно преобразуется из «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 . Это верно для любой функции, называемой выражением массива в качестве аргумента (почти любой из функций , * ScanF и * PrintF Функции и т. Д. .).

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

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

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

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

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

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

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

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

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

before swap: a = 1, b = 2
after swap: a = 1, b = 2

Формальные параметры x и y являются различными объектами из A и B , поэтому изменения x и y не отражаются в A и B . Поскольку мы хотим изменить значения A и B , мы должны пройти указатели к ним на функцию свопа:

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

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

before swap: a = 1, b = 2
after swap: a = 2, b = 1

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

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

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(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;}

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

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

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

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

не будет работать.

27
ответ дан 23 November 2019 в 02:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: