Існує закономірність роботи з масивами та функціями; спочатку просто трохи важко побачити
Маючи справу з масивами, корисно пам’ятати наступне: коли вираз масиву з’являється у більшості контекстів, тип виразу неявно перетворюється з «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-елементний масив з T).
Коли вираз масиву з'являється як операнд для &
оператора, тип результуючого виразу є "вказівник на N-елементний масив T", або T (*)[N]
, який відрізняється від масиву покажчиків ( T *[N]
) та вказівника на базовий тип ( T *
).
При роботі з функціями та покажчиками слід пам'ятати правило: якщо ви хочете змінити значення аргументу і відобразити це у викличному коді, ви повинні передати вказівник на те, що ви хочете змінити. Знову ж таки, масиви кидають на роботу трохи мавпового ключа, але ми спочатку розберемось із звичайними випадками.
Пам'ятайте, що C передає всі аргументи функції за значенням; формальний параметр отримує копію значення фактичного параметра, а будь-які зміни формального параметра не відображаються у фактичному параметрі. Загальний приклад - функція 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);
Ви отримаєте такий вихід:
перед свопом: a = 1, b = 2
після 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);
Тепер ваш вихід буде
перед свопом: a = 1, b = 2
після swap: a = 2, b = 1
Зауважте, що в функції swap ми змінюємо не значення, 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 = ...;
}
не буде працювати.