Есть шаблон при работе с массивами и функциями; сначала это немного сложно увидеть.
При работе с массивами полезно помнить следующее: когда выражение массива появляется в большинстве контекстов, тип выражения неявно преобразуется из «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
, мы должны передать им указатели в функцию подкачки:
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 = ...;
}
не будет работать.