W C tablice i wskaźniki idą w parze. Nie da się napisać efektywnego kodu bez zrozumienia ich relacji. Opanowanie tego wymaga czasu. Ale nagroda — natychmiastowa kontrola pamięci — przychodzi natychmiast.
Spójrzmy na podstawowy przykład. Deklarujesz dwie tablice całkowite o rozmiarze dziesięć.
Spróbuj skompilować ten kod. To się nie skompiluje. Nie można przypisać jednej tablicy do drugiej za pomocą znaku równości. C nie obsługuje kopiowania bloków danych poprzez instrukcje przypisania. Aby skopiować a do b, musisz przejść przez każdy element w pętli.
Można skompresować tę pętlę do jednej linii, używając przecinka, ale wtedy będzie ona mniej czytelna. Standardowe podejście polega na użyciu funkcji memcpy z pliku nagłówkowego string.h. Skutecznie wykonuje kopiowanie bajt po bajcie.
Niepowodzenie przypisania „b=a” ujawnia podstawową zasadę. W C zmienne tablicowe same w sobie nie są tablicami. Są to stałe wskaźniki. a i b są stałymi identyfikatorami wskazującymi adres początkowy ich bloków pamięci. a wskazuje na „a[0]”. b wskazuje na „b[0]”. Adresy te są niezmienne. Nie można przekierować nazwy zmiennej do innego miejsca w pamięci. Z tego powodu zadanie nie powiedzie się. Kompilator traktuje nazwę tablicy jako stały wskaźnik.
Ponieważ nazwa jest zasadniczo wskaźnikiem, możesz użyć arytmetyki wskaźników, aby uzyskać dostęp do danych. W tym miejscu składnia staje się potężnym narzędziem.
Tutaj p jest zmienną wskaźnikową. Możesz przypisać adres tablicy a do zmiennej p. Wyrażenie p=a kopiuje adres. Teraz p zawiera tę samą wartość, co nazwa tablicy a. Dereferencja p za pomocą *p zwraca pierwszy element. Wynik będzie zerowy.
To rozróżnienie jest ważne, ponieważ zmienne wskaźnikowe można modyfikować. Możesz zmusić p, aby wskazywało „a[1]” lub „a[5]”, używając arytmetyki. Sama nazwa tablicy pozostaje na początku stała. Ta elastyczność umożliwia funkcjom iterację po pamięci bez kopiowania dużych struktur danych. Jest to podstawa wydajności niskiego poziomu w C.
Zrozumienie tego połączenia pozwala uniknąć subtelnych błędów. Wyjaśnia, dlaczego niektóre funkcje akceptują wskaźniki zamiast składni tablicowej. Wyjaśnia strukturę pamięci. Zlekceważ tę wiedzę, a będziesz miał problemy z błędami segmentacji lub niezamierzonym nadpisywaniem danych. Opanuj to, a zobaczysz dokładnie, co robi maszyna.
Na początku składnia może wydawać się nietypowa. Podwójne znaczenie nawiasów kwadratowych i gwiazdek dezorientuje początkujących. Ale kiedy model staje się jasny, abstrakcja znika. Pozostają tylko adresy i wartości. Nic więcej.
Czy warto? Kod staje się szybszy. Zużycie pamięci jest zmniejszone. Otrzymujesz bezpośredni dostęp do operacji na poziomie sprzętu. Większość programów C polega
Możesz bezpośrednio przypisać zmienną tablicową do wskaźnika, ponieważ w zasadzie są one w zasadzie tym samym.
p=a; nie działa ze względu na magię, ale dlatego, że a rozkłada się na wskaźnik. Mianowicie wskazuje adres pamięci pierwszego elementu tablicy (o indeksie 0). Ponieważ ten element jest liczbą całkowitą, a działa jako wskaźnik do pojedynczej liczby całkowitej. Deklarowanie p jako wskaźnika do liczby całkowitej i przypisanie jej wartości a oznacza po prostu nadanie dwóm zmiennym wspólnego adresu. Zamiast tego możesz napisać p=&a[0];. Wynik będzie identyczny.
Gdy p zostanie połączone z początkiem a, możesz się poruszać. Nazwy tablic, takie jak a, są stałymi; Są to adresy stałe, których nie można zmienić. Wskaźniki takie jak p zachowują się inaczej. Można je przenosić za pomocą arytmetyki wskaźników.
Tutaj C pokazuje swoją pomysłowość.
Kiedy piszesz p++;, kompilator nie dodaje tylko jednego bajtu do adresu. Wie, że p wskazuje liczbę całkowitą. Zamiast tego dodaje bajty „sizeof(int)”, uderzając dokładnie w następny element. Jeśli p wskazywałoby na strukturę o długości 100 bajtów, p++ przesunęłoby wskaźnik o 100 bajtów do przodu. C wykonuje całą matematykę za Ciebie. Po prostu przesuwasz wskaźnik.
Kopiowanie tablic bez indeksów
Za pomocą tych wskaźników możesz skopiować jedną tablicę do drugiej. Zamiast standardowej pętli z indeksami:
Możesz używać wskaźników:
To jest szczegółowe. Można go skompresować.
Lub skompresuj wszystko w jedną linię.
To tutaj operatorzy wykonują najcięższą pracę. Operator * ma wyższy priorytet niż ++, ale inkrementacja następcza następuje po odebraniu wartości. Zatem *p++ pobiera bieżącą wartość, a następnie przesuwa wskaźnik. *q++ robi to samo dla miejsca docelowego. Przypisanie następuje pomiędzy tymi działaniami. Jest kompaktowy. To jest szybkie. Ale łatwo jest również błędnie odczytać, jeśli nie jesteś ostrożny.
Strefa niebezpieczna
Co się stanie, jeśli usuniesz p lub q z tablicy?
C Cię nie zatrzyma.
Kompilator zakłada, że wiesz, co robisz. Będzie nadal zwiększać wskaźniki, kopiując dane do pamięci, które nie należą do tablicy. Możesz nadpisać inne zmienne. Możesz zepsuć stos. Możesz spowodować awarię programu później, w miejscu niezwiązanym z błędem.
Dlatego sprawdzanie granic w C odbywa się ręcznie. Język Ci ufa. Wykraczając poza zasady, płacisz cenę.
Przekazywanie tablic do funkcji
Tablice można przekazywać do funkcji na dwa sposoby. Wyglądają inaczej, ale zachowują się podobnie.
Rozważmy funkcję generującą tablicę:
Lub wersja ze wskaźnikami:
Parametr nia (liczba elementów tablicy) jest wymagany. Tablice w C nie przechowują swojego rozmiaru same w sobie. Funkcje muszą wiedzieć, kiedy się zatrzymać.
W obu przypadkach przekazujesz wskaźnik na początek tablicy, a nie







































