Aby w pełni to zrozumieć, musisz zrozumieć następujące pojęcia:
Tablice nie są wskaźnikami!
Przede wszystkim (i zostało to wystarczająco nauczone) tablice nie są wskaźnikami . Zamiast tego w większości zastosowań „rozpadają się” na adres do ich pierwszego elementu, który można przypisać do wskaźnika:
int a[] = {1, 2, 3};
int *p = a; // p now points to a[0]
Zakładam, że działa to w ten sposób, że można uzyskać dostęp do zawartości tablicy bez kopiowania ich wszystkich. To tylko zachowanie typów tablicowych i nie ma na celu sugerowania, że są one tym samym.
Tablice wielowymiarowe
Tablice wielowymiarowe to tylko sposób na „partycjonowanie” pamięci w sposób, który kompilator / maszyna może zrozumieć i na którym może operować.
Na przykład int a[4][3][5]
= tablica zawierająca 4 * 3 * 5 (60) „fragmentów” pamięci o rozmiarze całkowitoliczbowym.
Zaletą używania int a[4][3][5]
vs zwykłego int b[60]
jest to, że są teraz „podzielone na partycje” (łatwiej jest pracować z ich „fragmentami”, jeśli to konieczne), a program może teraz wykonywać sprawdzanie powiązań.
W rzeczywistości int a[4][3][5]
jest przechowywany dokładnie tak , jak int b[60]
w pamięci - jedyną różnicą jest to, że program teraz zarządza nim tak, jakby były oddzielnymi jednostkami o określonych rozmiarach (w szczególności cztery grupy po trzy grupy po pięć).
Pamiętaj: oba int a[4][3][5]
i int b[60]
są takie same w pamięci, a jedyna różnica polega na tym, jak są obsługiwane przez aplikację / kompilator
{
{1, 2, 3, 4, 5}
{6, 7, 8, 9, 10}
{11, 12, 13, 14, 15}
}
{
{16, 17, 18, 19, 20}
{21, 22, 23, 24, 25}
{26, 27, 28, 29, 30}
}
{
{31, 32, 33, 34, 35}
{36, 37, 38, 39, 40}
{41, 42, 43, 44, 45}
}
{
{46, 47, 48, 49, 50}
{51, 52, 53, 54, 55}
{56, 57, 58, 59, 60}
}
Z tego widać wyraźnie, że każda „partycja” jest po prostu tablicą, którą program śledzi.
Składnia
Teraz tablice różnią się składnią od wskaźników . W szczególności oznacza to, że kompilator / maszyna będzie traktować je inaczej. Może się to wydawać oczywiste, ale spójrz na to:
int a[3][3];
printf("%p %p", a, a[0]);
Powyższy przykład wyświetla ten sam adres pamięci dwukrotnie, na przykład:
0x7eb5a3b4 0x7eb5a3b4
Jednak tylko jeden może być przypisany do wskaźnika tak bezpośrednio :
int *p1 = a[0]; // RIGHT !
int *p2 = a; // WRONG !
Dlaczego nie można a
go przypisać do wskaźnika, ale a[0]
można?
Jest to po prostu konsekwencja wielowymiarowych tablic i wyjaśnię, dlaczego:
Na poziomie „ a
” nadal widzimy, że mamy inny „wymiar”, na który czekamy. Jednak na poziomie „ a[0]
” jesteśmy już w najwyższym wymiarze, więc jeśli chodzi o program, patrzymy tylko na normalną tablicę.
Możesz zapytać:
Dlaczego ma to znaczenie, jeśli tablica jest wielowymiarowa, jeśli chodzi o tworzenie wskaźnika?
Najlepiej myśleć w ten sposób:
`` Zanik '' z wielowymiarowej tablicy to nie tylko adres, ale adres z danymi partycji (AKA nadal rozumie, że jego podstawowe dane składają się z innych tablic), który składa się z granic wyznaczonych przez tablicę poza pierwszym wymiarem.
Ta logika `` partycji '' nie może istnieć we wskaźniku, chyba że ją określimy:
int a[4][5][95][8];
int (*p)[5][95][8];
p = a; // p = *a[0] // p = a+0
W przeciwnym razie znaczenie właściwości sortowania tablicy zostanie utracone.
Zwróć również uwagę na użycie nawiasów wokół *p
: int (*p)[5][95][8]
- To jest określenie, że tworzymy wskaźnik z tymi granicami, a nie tablicą wskaźników z tymi granicami:int *p[5][95][8]
Wniosek
Przejrzyjmy:
- Tablice rozpadają się na adresy, jeśli nie mają innego celu w używanym kontekście
- Tablice wielowymiarowe są po prostu tablicami tablic - w związku z tym „zepsuty” adres będzie dźwigał ciężar „Mam wymiary podrzędne”
- Dane wymiarowe nie mogą istnieć we wskaźniku, chyba że im je podasz .
W skrócie: tablice wielowymiarowe rozpadają się na adresy, które niosą zdolność zrozumienia ich zawartości.