Jaka jest różnica między następującymi deklaracjami:
int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);
Jaka jest ogólna zasada rozumienia bardziej złożonych deklaracji?
const
i volatile
, które są ważne i trudne.
Jaka jest różnica między następującymi deklaracjami:
int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);
Jaka jest ogólna zasada rozumienia bardziej złożonych deklaracji?
const
i volatile
, które są ważne i trudne.
Odpowiedzi:
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers
Trzeci jest taki sam jak pierwszy.
Ogólną zasadą jest pierwszeństwo operatora . Może stać się jeszcze bardziej skomplikowane, gdy na ekranie pojawią się wskaźniki funkcji.
( ) [ ]
kojarzą od lewej do prawej i mają wyższy priorytet niż *
odczytane int* arr[8]
jako tablica wielkości 8, gdzie każdy element wskazuje na liczbę int (*arr)[8]
całkowitą i jako wskaźnik na tablicę wielkości 8, która zawiera liczby całkowite
Użyj programu cdecl , zgodnie z sugestią K&R.
$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>
Działa to również w drugą stronę.
cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )
Nie wiem, czy ma oficjalną nazwę, ale nazywam ją Prawicowo-Lewą Rzeczą (TM).
Zacznij od zmiennej, następnie idź w prawo, w lewo i w prawo ... i tak dalej.
int* arr1[8];
arr1
to tablica 8 wskaźników do liczb całkowitych.
int (*arr2)[8];
arr2
jest wskaźnikiem (nawias blokuje prawy-lewy) do tablicy 8 liczb całkowitych.
int *(arr3[8]);
arr3
to tablica 8 wskaźników do liczb całkowitych.
To powinno ci pomóc w przypadku złożonych deklaracji.
int *a[][10]
druga.
( ) [ ]
o lewostronnym połączeniu z prawym i lewym od* &
int *a[4]; // Array of 4 pointers to int
int (*a)[4]; //a is a pointer to an integer array of size 4
int (*a[8])[5]; //a is an array of pointers to integer array of size 5
[5]
) reprezentuje wymiar wewnętrzny. Oznacza to, że (*a[8])
jest to pierwszy wymiar, a zatem zewnętrzna reprezentacja tablicy. To, na co a
wskazuje każdy element, to inna tablica liczb całkowitych o rozmiarze 5.
Odpowiedź na dwa ostatnie pytania można również odjąć od złotej reguły w C:
Deklaracja następuje po użyciu.
int (*arr2)[8];
Co się stanie, jeśli będziesz się obawiać arr2
? Otrzymasz tablicę 8 liczb całkowitych.
int *(arr3[8]);
Co się stanie, jeśli weźmiesz element arr3
? Otrzymasz wskaźnik do liczby całkowitej.
Pomaga to również w przypadku wskaźników do funkcji. Weźmy przykład sigjuice:
float *(*x)(void )
Co się stanie, gdy się wyreżyserujesz x
? Otrzymujesz funkcję, którą możesz wywołać bez argumentów. Co się stanie, gdy go nazwiesz? Zwróci wskaźnik do float
.
Jednak pierwszeństwo operatorów jest zawsze trudne. Jednak użycie nawiasów może być mylące, ponieważ deklaracja następuje po użyciu. Przynajmniej dla mnie intuicyjnie arr2
wygląda jak tablica 8 wskaźników do liczb całkowitych, ale tak naprawdę jest odwrotnie. Wystarczy trochę przyzwyczaić się. Wystarczający powód, aby zawsze dodawać komentarz do tych deklaracji, jeśli mnie o to poprosisz :)
edycja: przykład
Nawiasem mówiąc, natknąłem się na następującą sytuację: funkcję, która ma macierz statyczną i używa arytmetyki wskaźnika, aby sprawdzić, czy wskaźnik wiersza jest poza zakresem. Przykład:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))
int *
put_off(const int newrow[2])
{
static int mymatrix[3][2];
static int (*rowp)[2] = mymatrix;
int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);
memcpy(rowp, newrow, sizeof(*rowp));
rowp += 1;
if (rowp == border) {
rowp = mymatrix;
}
return *rowp;
}
int
main(int argc, char *argv[])
{
int i = 0;
int row[2] = {0, 1};
int *rout;
for (i = 0; i < 6; i++) {
row[0] = i;
row[1] += i;
rout = put_off(row);
printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
}
return 0;
}
Wynik:
0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]
Zauważ, że wartość granicy nigdy się nie zmienia, więc kompilator może ją zoptymalizować. Różni się to od tego, czego początkowo możesz chcieć użyć:: const int (*border)[3]
deklaruje obramowanie jako wskaźnik do tablicy 3 liczb całkowitych, które nie będą zmieniać wartości tak długo, jak długo istnieje zmienna. Jednak wskaźnik ten może być w dowolnym momencie skierowany na dowolną inną tablicę. Zamiast tego chcemy tego rodzaju zachowanie argumentu (ponieważ ta funkcja nie zmienia żadnej z tych liczb całkowitych). Deklaracja następuje po użyciu.
(ps: możesz poprawić tę próbkę!)
typedef int (*PointerToIntArray)[];
typedef int *ArrayOfIntPointers[];
Jako zasada, prawda operatorów jednoargumentowych (jak []
,()
itp) preferencji przejąć lewych. Tak, int *(*ptr)()[];
będzie to wskaźnik, który wskazuje na funkcję, która zwraca tablicę wskaźników do int (Get Right operatorów tak szybko, jak to możliwe, jak wydostać się z nawiasu)
error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];
pod GCC 8 z$ gcc -std=c11 -pedantic-errors test.c
int *(*ptr)();
pozwala na użycie wyrażenia typu p()[3]
(lub (*p)()[3]
) później.
int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }
i nazwij to tak: foo(arr)[4];
co powinno zawierać arr[2][4]
, prawda?
Myślę, że możemy zastosować prostą zasadę ...
example int * (*ptr)()[];
start from ptr
„ ptr
jest wskaźnikiem„ idź w prawo .. jego ”)„ teraz idź w lewo to a ”(„ wyjdź idź w prawo ”()„ więc ”do funkcji, która nie przyjmuje argumentów„ idź w lewo ”i zwraca wskaźnik„ idź prawo do tablicy „idź w lewo” liczb całkowitych
)
, teraz idź w lewo ... to *
„wskaźnik do” idź w prawo ... to )
, teraz idź w lewo ... to (
wyjdzie, idź w prawo ()
tak „do funkcji, która nie wymaga żadnych argumentów” idź w prawo ... []
„i zwraca tablicę” idź w prawo ;
końca, więc idź w lewo ... *
„wskazówek” iść w lewo ... int
„całkowite”
Oto interesująca strona internetowa, która wyjaśnia, jak czytać złożone typy w C: http://www.unixwiz.net/techtips/reading-cdecl.html
Oto jak to interpretuję:
int *something[n];
Uwaga na temat pierwszeństwa: operator indeksu tablicy (
[]
) ma wyższy priorytet niż operator dereferencji (*
).
Tak więc tutaj zastosujemy []
wcześniej *
, dzięki czemu oświadczenie będzie równoważne z:
int *(something[i]);
Uwaga na temat tego, w jaki sposób deklaracja ma sens:
int num
oznaczanum
toint
,int *ptr
lubint (*ptr)
oznacza, że (wartość atptr
) jestint
, co stanowiptr
wskaźnik doint
.
Można to odczytać jako (wartość (wartość w indeksie czegoś)) jest liczbą całkowitą. Tak więc (wartość przy i-tym indeksie czegoś) jest (wskaźnikiem liczb całkowitych), co czyni z tego tablicę wskaźników liczb całkowitych.
W drugim
int (*something)[n];
Aby zrozumieć to oświadczenie, musisz znać ten fakt:
Uwaga na temat wskaźnika reprezentacji tablicy:
somethingElse[i]
jest równoważna z*(somethingElse + i)
Tak, zastępując somethingElse
z (*something)
, otrzymujemy *(*something + i)
, który jest liczbą całkowitą, jak na deklaracji. Tak, (*something)
dał nam tablicę, która sprawia, że coś odpowiednik (wskaźnik do tablicy) .
Wydaje mi się, że druga deklaracja jest dla wielu myląca. Oto prosty sposób, aby to zrozumieć.
Pozwala mieć tablicę liczb całkowitych, tj int B[8]
.
Miejmy także zmienną A, która wskazuje na B. Teraz wartość w A to B, tj (*A) == B
. Stąd A wskazuje na tablicę liczb całkowitych. W twoim pytaniu arr jest podobny do A.
Podobnie, w int* (*C) [8]
, C jest wskaźnikiem do tablicy wskaźników na liczbę całkowitą.
int *arr1[5]
W tej deklaracji arr1
jest tablica 5 wskaźników do liczb całkowitych. Powód: nawiasy kwadratowe mają wyższy priorytet niż * (operator wyrzeczenia). W tym typie liczba wierszy jest stała (tutaj 5), ale liczba kolumn jest zmienna.
int (*arr2)[5]
W tej deklaracji arr2
jest wskaźnikiem do liczby całkowitej zawierającej 5 elementów. Powód: tutaj nawiasy kwadratowe mają wyższy priorytet niż []. W tym typie liczba wierszy jest zmienna, ale liczba kolumn jest stała (tutaj 5).
We wskaźniku do liczby całkowitej, jeśli wskaźnik jest zwiększany, to przechodzi do następnej liczby całkowitej.
w tablicy wskaźnika, jeśli wskaźnik jest zwiększany, przeskakuje do następnej tablicy