Указатели на C: когда использовать амперсанд и звездочку?
Я только начинаю с указателей, и я немного смущен. Я знаю, что &
означает адрес переменной и что *
может использоваться перед переменной указателя, чтобы получить значение объекта, на который указывает указатель. Но все работает по-разному, когда вы работаете с массивами, строками или когда вы вызываете функции с копией указателя переменной. Трудно увидеть схему логики внутри всего этого.
Когда следует использовать &
и *
?
Ответы
Ответ 1
У вас есть указатели и значения:
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
Существует закономерность при работе с массивами и функциями; сначала это немного сложно увидеть.
При работе с массивами полезно помнить следующее: когда выражение массива появляется в большинстве контекстов, тип выражения неявно преобразуется из "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);
Вы получите следующий вывод:
before swap: a = 1, b = 2
after swap: a = 1, b = 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);
Теперь ваш вывод будет
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);
затем мы изменяем значение входного параметра 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 = ...;
}
не сработает
Ответ 3
Да, это может быть довольно сложно, так как *
используется для разных целей в 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
Если &
появляется в объявлении переменной или функции, обычно это означает, что эта переменная является ссылкой на переменную этого типа.
Если &
появляется перед уже объявленной переменной, он возвращает адрес этой переменной
Кроме того, вы должны знать, что при передаче массива в функцию вам всегда придется передавать размер массива этого массива, кроме случаев, когда массив является чем-то вроде cstring (char) с 0-символом.
Ответ 4
Положите просто
-
&
означает адрес, вы увидите, что в заполнителях для функций для изменения переменной параметра, как в 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
с помощью pass-by-reference, сравнить с этим
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
.
Ответ 5
Когда вы объявляете переменную-указатель или параметр функции, используйте *:
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);
Массивы обычно обрабатываются как указатели. Когда вы объявляете параметр массива в функции, вы можете так же легко объявить, что это указатель (это означает одно и то же). Когда вы передаете массив функции, вы фактически передаете указатель на первый элемент.
Указатели функций - это единственные вещи, которые не совсем соответствуют правилам. Вы можете взять адрес функции без использования &, и вы можете вызвать указатель на функцию без использования *.
Ответ 6
На самом деле, у вас есть это погладить, вам больше ничего не нужно знать: -)
Я бы просто добавил следующие бит:
- две операции - противоположные концы спектра.
&
принимает переменную и дает вам адрес, *
берет адрес и дает вам переменную (или содержимое).
- массивы "деградируют" до указателей при передаче их функциям.
- вы можете иметь несколько уровней для косвенности (
char **p
означает, что p
является указателем на указатель на char
.
Что касается вещей, работающих по-другому, не совсем:
- массивы, как уже упоминалось, делятся на указатели (на первый элемент в массиве) при передаче в функции; они не сохраняют информацию о размере.
- в C нет строк, а только массивы символов, которые по соглашению представляют строку символов, завершаемую символом нуля (
\0
).
- Когда вы передаете адрес переменной функции, вы можете отменить указатель на изменение самой переменной (обычно переменные передаются по значению (кроме массивов)).
Ответ 7
Я думаю, вы немного смущены. Вы должны прочитать хороший учебник/книгу по указателям.
Этот учебник очень хорош для начинающих (ясно объясняет, что &
и *
). И да, не забывайте читать книгу Указатели в 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;
}
Ответ 8
Я просматривал все многословные объяснения, поэтому вместо этого обратился к видео из Университета Нового Южного Уэльса для спасения. Вот простое объяснение: если у нас есть ячейка с адресом x
и значением 7
, косвенным способом запроса адреса значения 7
является &7
, а косвенный способ задать значение по адресу x
- *x
.So (cell: x , value: 7) == (cell: &7 , value: *x)
. Другой способ взглянуть на него: John
сидит на 7th seat
. *7th seat
будет указывать на John
, а &John
даст address
/location 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;
, мы назначаем указателю определенное местоположение.
Ответ 9
Хорошо, похоже, что ваше сообщение отредактировано...
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]; }
сделает то же самое.