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