W przypadku tablic i funkcji istnieje pewien wzorzec; na początku jest to trochę trudne.
Kiedy mamy do czynienia z tablicami, warto pamiętać, że: gdy wyrażenie tablicowe pojawia się w większości kontekstów, typ wyrażenia jest domyślnie konwertowany z „tablicy N-elementowej T” na „wskaźnik do T”, a jego wartość jest ustawiana wskazywać pierwszy element w tablicy. Wyjątki od tej reguły są, gdy ekspresja tablicy pojawia się jako operand albo &
czy sizeof
operatorzy, lub gdy jest to dosłowne ciąg używany jako inicjator w deklaracji.
Zatem, gdy wywołasz funkcję z wyrażeniem tablicowym jako argumentem, funkcja otrzyma wskaźnik, a nie tablicę:
int arr[10];
...
foo(arr);
...
void foo(int *arr) { ... }
Dlatego nie używasz &
operatora do argumentów odpowiadających „% s” w scanf()
:
char str[STRING_LENGTH];
...
scanf("%s", str);
Z powodu niejawnej konwersji scanf()
otrzymuje char *
wartość wskazującą na początek str
tablicy. Dotyczy to każdej funkcji wywoływanej z wyrażeniem tablicowym jako argumentem (prawie dowolna z str*
funkcji *scanf
i *printf
funkcji itp.).
W praktyce prawdopodobnie nigdy nie wywołasz funkcji z wyrażeniem tablicowym za pomocą &
operatora, jak w:
int arr[N];
...
foo(&arr);
void foo(int (*p)[N]) {...}
Taki kod nie jest bardzo powszechny; musisz znać rozmiar tablicy w deklaracji funkcji, a funkcja działa tylko ze wskaźnikami do tablic o określonych rozmiarach (wskaźnik do 10-elementowej tablicy T jest innego typu niż wskaźnik do 11-elementowej tablicy z T).
Gdy wyrażenie tablicowe pojawia się jako operator dla &
operatora, typem wynikowego wyrażenia jest „wskaźnik do tablicy N-elementowej T” lub T (*)[N]
, która różni się od tablicy wskaźników ( T *[N]
) i wskaźnika do typu podstawowego ( T *
).
W przypadku funkcji i wskaźników należy pamiętać o następującej zasadzie: jeśli chcesz zmienić wartość argumentu i odzwierciedlić go w kodzie wywołującym, musisz przekazać wskaźnik do rzeczy, którą chcesz zmodyfikować. Znów tablice wrzucają trochę małpiego klucza do prac, ale najpierw zajmiemy się normalnymi przypadkami.
Pamiętaj, że C przekazuje wszystkie argumenty funkcji według wartości; parametr formalny otrzymuje kopię wartości parametru rzeczywistego, a wszelkie zmiany parametru formalnego nie są odzwierciedlane w parametrze faktycznym. Typowym przykładem jest funkcja zamiany:
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);
Otrzymasz następujące dane wyjściowe:
przed zamianą: a = 1, b = 2
po zamianie: a = 1, b = 2
Parametry formalne x
i y
są odrębnymi obiektami od a
i b
, więc zmiany x
i y
nie są odzwierciedlone w a
i b
. Ponieważ chcemy zmodyfikować wartości a
i b
, musimy przekazać do nich wskaźniki do funkcji zamiany:
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);
Teraz twój wynik będzie
przed zamianą: a = 1, b = 2
po zamianie: a = 2, b = 1
Zauważ, że w funkcji wymiany nie zmieniamy wartości x
i y
, ale wartości what x
i y
point to . Pisanie do *x
różni się od pisania do x
; nie aktualizujemy samej wartości x
, uzyskujemy lokalizację x
i aktualizujemy wartość w tej lokalizacji.
Jest to równie prawdziwe, jeśli chcemy zmodyfikować wartość wskaźnika; jeśli napiszemy
int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);
następnie modyfikujemy wartość parametru wejściowego stream
, a nie to, na co stream
wskazuje , więc zmiana stream
nie ma wpływu na wartość in
; aby to zadziałało, musimy przekazać wskaźnik do wskaźnika:
int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);
Znów tablice wrzucają do prac trochę klucza małpiego. Gdy przekazujesz wyrażenie tablicowe do funkcji, funkcja otrzymuje wskaźnik. Ze względu na to, jak zdefiniowane jest indeksowanie tablic, można użyć operatora indeksu dolnego na wskaźniku w taki sam sposób, jak można go użyć na tablicy:
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;}
Zauważ, że obiekty tablicowe nie mogą być przypisane; tzn. nie możesz zrobić czegoś takiego
int a[10], b[10];
...
a = b;
więc chcesz zachować ostrożność, mając do czynienia ze wskaźnikami do tablic; coś jak
void (int (*foo)[N])
{
...
*foo = ...;
}
nie zadziała.