/* wyraûenia zawarte w >...< proszë pogrubiê */ AMIGA E9 Rafaî Wiosna Hej, ludzie! Idzie wiosna, zarówno za oknem, jak i na papierze. Tym razem przeprowadzë akcjë promocyjnâ kasety "Greatest Hits" Budki Suflera. Jak miîo znów odegraê stare ôwietne przeboje tej formacji. Czëôê z Czytelników moûe nie wiedzieê, co to jest ta ta Budka Suflera. Przypominam wiëc, ûe jest to POLSKI zespóî grajâcy DOBRÂ MUZYKË, który w dodatku istnieje dîuûej niû niektórzy z Was. W nim zaczynali Borysewicz, Urszula, Trojanowska... Lata osiemdziesiâte coraz silniej powracajâ na scenë muzycznâ (u nas Perfect, Budka Suflera, u nich np. Human Leauge). A i dobrze, bo duûa czëôê "pamiëtliwych" kawaîków powstaîa wîaônie wtedy. Ale co ja plotë, mamy rozwaûaê o Amiga E, a nie o muzyce... Tablice Bardzo czësto sië zdarza, ûe dane uûywane przez program muszâ byê w pewien sposób poukîadane, gîównie dlatego, aby dostëp do nich byî szybszy lub îatwiejszy. Najprostszym sposobem na wprowadzenie takiego porzâdku jest tablica, w E oznaczana jako typ ARRAY. Najpierw trochë teorii. Skombinuj sobie kartkë papieru. Narysuj na niej prostokât. Podziel go pionowymi liniami na mniejsze prostokâty. Proszë bardzo! Masz tablicë! Jest ona jednowymiarowa, gdyû wszystkie powstaîe prostokâty majâ wysokoôê tego, który narysowaliômy na poczâtku. Poîoûenie danego prostokâta moûesz okreôliê tylko jednâ liczbâ. Zacznij od pierwszego z lewej -- nad nim napisz zero. Potem nad drugim od lewej napisz jedynkë i postëpuj tak z resztâ. Teraz narysuj dowolnâ liczbë poziomych linii, od pierwszej do ostatniej linii pionowej naszej tablicy. Widzisz? Uzyskaîeô tablicë dwuwymiarowâ. Ponumeruj rzëdy tak poêwiartowanego prostokâta-tablicy, poczynajâc od górnego (jemu nadaj numer 0). Weú teraz kilka innych czystych kartek i przerysuj to, co jest na pierwszej. Nastëpnie kaûdâ z kartek ponumeruj, zaczynajâc od zera. Brawo! Zrobiîeô tablicë trójwymiarowâ (zaiste trudno w tym przykîadzie pokazaê trzeci wymiar na jednej kartce, która jest przecieû obiektem majâcym znikomâ wysokoôê -- a wiëc dwuwymiarowym). Teraz narysuj kropkë w dowolnym prostokâcie (tym maîym) na dowolnej kartce. Spróbuj okreôliê jego poîoûenie, np. trzecia kartka, czwarty rzâd, pierwsza kolumna. Moûna zapisaê to tak: tablica[2,3,0] Dlaczego nie [3,4,1]? A no dlatego, ûe przyjëliômy numeracjë od zera. Ten maîy przykîad miaî wyjaôniê cechy typowej tablicy. A wiëc: ^* ma ona jeden lub wiëcej wymiarów, ^* ma skoïczonâ, i co waûniejsze, z góry okreôlonâ liczbë pozycji, ^* kaûdy element tablicy ma swój "adres". Amiga E pozwala na definiowanie tablic, ale niestety tylko >jednowymiarowych<. Robi sië to w nastëpujâcy sposób: DEF a[132]:ARRAY, table[21]:ARRAY OF LONG, ints[3]:ARRAY OF INT, objs[54]:ARRAY OF mójobiekt Jak widzisz, >wielkoôê< tablicy jest okreôlana w nawiasach kwadratowych, natomiast >typ danych< jest okreôlany na samym koïcu. Domyôlnym typem jest CHAR, czyli znak (bajt), ale moûe to byê równieû LONG, INT i dowolny obiekt (o nich opowiem w nastëpnych odcinkach). Zauwaû, ûe typ LONG moûe równie dobrze byê wykorzystany jako wskaúnik do dowolnego innego obiektu. Po zdefiniowaniu tablicy mamy swobodny dostëp do jej elementów. Jak w wypadku naszego eksperymentu z kartkami i prostokâtami, kaûdy z nich ma swój "adres". Poniewaû jednak to sîowo w kontekôcie programowania uûywane jest do innego celu, umówmy sië, ûe do okreôlenia poîoûenia elementu w tablicy bëdziemy uûywaê angielskiego sîowa "indeks". (Tych, którzy chcâ mówiê "offset", informujë, ûe jest to nawet i poprawne, ale przydatne tylko w wypadku tablic jednowymiarowych o elementach typu CHAR...). Pierwszy element w dowolnym wymiarze tablicy zawsze ma indeks 0, drugi -- 1, trzeci -- 2 itp, aû do elementu ostatniego, n-tego (n oznacza tu wartoôê, którâ okreôlamy przy definiowaniu tablicy, w nawiasach kwadratowych), który ma indeks równy n-1. Moûe brzmi to bardzo dziwnie i nieintuicyjnie, ale tak wîaônie dziaîajâ komputery, a na domiar zîego Amiga E nie pozwala jeszcze na okreôlenie podstawy obliczania indeksu tablicy, tak jak ma to miejsce w inych jëzykach programowania, gdzie moûna zdefiniowaê tablicë o wymiarze [1945:1999]. My musimy zadowoliê sië sformuîowaniem [55]. Pora na przykîad. Spójrz na programik poniûej: DEF a[10]:ARRAY PROC main() DEF i FOR i:=0 TO 9 a[i]:=i*i ENDFOR WriteF('Siódmy element tablicy ma wartoôê \d\n', a[6]) a[a[2]]:=10 WriteF('Teraz tablica wyglâda nastëpujâco:\n') FOR i:=0 TO 9 WriteF(' a[\d] = \d\n', i, a[i]) ENDFOR ENDPROC Powinieneô zrozumieê caîy program bez problemu. Kîopoty moûesz mieê jedynie z liniâ ósmâ. Spróbuj sië domyôliê, jak zostanie ona wykonana. Tak jest, w tym wypadku indeks tablicy jest pobierany z jednego z jej elementów! Na przyszîoôê pamiëtaj, ûe stosowanie takich sztuczek moûe byê niebezpieczne -- nie tylko zaciemnia kod úródîowy, ale teû moûe doprowadziê do zîego dziaîania programu -- wystarczy, ûeby element tablicy, którego wartoôê brana jest za offset, sam miaî wartoôê wiëkszâ od rozmiaru tablicy... Nasz przykîadowy program powinien sprawdziê, czy na pewno element a[2] jest mniejszy od 10, czyli zadeklarowanego rozmiaru tablicy a[]. Jeûeli twój program zacznie mazaê po nie istniejâcych elementach tablicy, dziwne rzeczy mogâ sië zdarzyê. Zdarzenie moûe w ogóle nie zostaê zauwaûone, ale takûe moûe to wywoîaê wizytë Guru, gdy nie istniejâcy indeks wypadnie akurat na kawaîku wykonywanego lub majâcego sië wykonaê kodu maszynowego. Oto, co powyûszy program wypluwa na konsolë: Siódmy element tablicy ma wartoôê 36 Teraz tablica wyglâda nastëpujâco: a[0] = 0 a[1] = 1 a[2] = 4 a[3] = 9 a[4] = 10 a[5] = 25 a[6] = 36 a[7] = 49 a[8] = 64 a[9] = 81 Pewnym uîatwieniem dla programisty jest skrót, który moûna zastosowaê przy korzystaniu z pierwszego (zerowego) elementu tablicy. Zamiast pisaê a[0], moûna po prostu napisaê a[]. >Wskaúniki do tablicy< Gdy zadeklarujesz tablicë, jej poczâtek w pamiëci bëdzie zapisany w zmiennej o nazwie takiej, jak tablica, ale bez nawiasów kwadratowych. **************** RYSUNEK DO WYKONANIA PRZEZ GRAFICZNY *************** Program: DEF a[20]:ARRAY OF INT +--------+ |zmienna | +-------+ | 'a' | |indeks | |--------| |+-----+| | adres +----\ || typ || +--------+ \ +=======+ \ \ +-------+ +--\----+ +-------+ +-------+ +-------+ | ??? | | a[0] | | a[1] | | a[19] | | ??? | Pamiëê: |+-----+| |+-----+| |+-----+| ... |+-----+| |+-----+| || ??? || || INT || || INT || || INT || || ??? || +=======+ +=======+ +=======+ +=======+ +=======+ ********************************************************************* Jak widaê na ilustracji, zmienna "a" jest wskaúnikiem do zarezerwowanego obszaru pamiëci, który zawiera elementy tablicy. Zarówno poniûej, jak i powyûej tego obszaru znajdujâ sië komórki pamiëci oznaczone symbolem "???", nie naleûâce do tablicy. Zmiana ich zawartoôci, np. "dziëki" niepoprawnemu indeksowi, moûe prowadziê do zawieszenia sië komputera. Spójrz na poniûszy przykîad wykorzystujâcy wskaúnik do tablicy: DEF a[10]:ARRAY OF INT PROC main() DEF ptr:PTR TO INT,i FOR i:=0 TO 9 a[i]:=i ENDFOR ptr:=a ptr++ ptr[]:=22 FOR i:=0 TO 9 WriteF('a[\d] = \d\n', i, a[i]) ENDFOR ENDPROC A oto jego rezultat: a[0] = 0 a[1] = 22 a[2] = 2 a[3] = 3 a[4] = 4 a[5] = 5 a[6] = 6 a[7] = 7 a[8] = 8 a[9] = 9 Zauwaû, ûe ta dziwna, na razie nie znana Ci, technika, zaprezentowana w poprzednim listingu, w rezultacie zmienia drugi element tablicy. Zapis "ptr++" zwiëksza wkaúnik "ptr" tak, aby wskazywaî on nastëpny element tablicy a[]. Waûne jest to, ûeby zmiennâ "ptr" zadeklarowaê jako PTR TO INT, czyli WSKAÚNIK DO INT. INT, poniewaû tablica skîada sië z elementów typu INT. Zapis "ptr" jest w tym wypadku, po odpowiednim zdefiniowaniu zmiennej, równowaûny zapisowi "ptr[]", czyli "ptr[0]". Piszâc wiëc "ptr[1]" otrzymamy nastëpny element wskazywany przez zmiennâ "ptr", w tym wypadku trzeci element tablicy a[]. Przykîady uûycia: ptr:=a -> ptr wskazuje na a[0] ptr++ -> ptr wskazuje na a[1] ptr[3]:=22 -> zmiana elementu a[4] ptr[-1]:=99 -> zmiana elementu a[0] Jak widzisz, jest moûliwe takûe stosowanie ujemnych indekstów dla zmiennych typu PTR TO COÔTAM. Pamiëtaj jednak, ûe taki styl programowania moûe byê zabójczy zarówno dla programisty, jak i dla programu... A propos brzydkiego stylu programowania, to obie poniûsze definicje sâ poprawne: DEF a[20]:ARRAY OF INT DEF a:PTR TO INT Jeûeli uûyjesz tej drugiej, musisz sam zadbaê o zarezerwowanie pamiëci dla tablicy, a takûe o to, aby za kilka tygodni poîapaê sië, o co chodzi w programie... Przed chwilâ pokazaîem, jak w E zwiëkszaê zmiennâ-wskaúnik do elementu tablicy. Zmniejszanie takiej zmiennej (czyli ustawienie wskaúnika na poprzedni element tablicy) jest równie proste. Robi sië to tak: ptr-- Tak naprawdë zapis "ptr++" i "ptr--" to teû wskaúniki. Pierwszy oznacza element wskazywany przez ptr PRZED ZWIËKSZENIEM tej zmiennej, a drugi -- na element wskazywany przez ptr PO ZMNIEJSZENIU zmiennej. Zapis wiëc: addr:=p p++ to to samo co addr:=p++ a zapis p-- addr:=p moûna zastâpiê przez addr:=p-- Moûesz spytaê, dlaczego piszë o uûywaniu "++" i "--" przy operacjach na wskaúnikach. Czyû nie moûna po prostu dodawaê i odejmowaê od wskaúników. Otóû moûna, ale przenosisz wtedy na siebie koniecznoôê dbania o wielkoôê elementu, na który wskazuje zmienna. Maîe piwo, jeûeli jest to bajt (CHAR), dwa (INT) lub cztery (LONG). Problem przychodzi, jeûeli zdefiniujemy tablicë tak, ûe jej elementy bëdâ obiektami: MODULE 'intuition/screens' PROC main() DEF ekrany[10]:ARRAY OF screen, ekran ekran:=ekrany ... Jak teraz chcesz przejôê do nastëpnego ekranu w tablicy? Moûesz zrobiê to tak: ekran:=ekran + SIZEOF screen Ale czyû nie proôciej napisaê po prostu "ekran++"? Jeûeli chcesz zobaczyê, co to obiekt (w jëzyku C... struktura!) i jak wyglâda definicja obiektu "screen", to poszukaj go w wydruku produkowanym przez polecenie: E:bin/ShowModule E:Modules/intuition/screens.m >Przekazywanie tablic do procedur< Zacznijmy od tego, ûe przekazywanie tablic do procedur, jako parametr, nie jest dozwolone w Amiga E. Na szczëôcie, korzystajâc ze wskaúników moûna bardzo îatwo to zasymulowaê. Spójrz: DEF a[10]:ARRAY OF INT PROC main() DEF i wypelniaj(a, 10) FOR i:=0 TO 9 WriteF('a[\d] = \d\n', i, a[i]) ENDFOR ENDPROC PROC wypelniaj(ptr:PTR TO INT, x) DEF i FOR i:=0 TO x-1 ptr[]:=i ptr++ ENDFOR ENDPROC Moûna teû potraktowaê zmiennâ "prt" w sposób bardziej przypominajâcy tablicë: PROC wypelniaj(ptr:PTR TO INT, x) DEF i FOR i:=0 TO x-1 ptr[i]:=i ENDFOR ENDPROC Natomiast zaawansowani kursanci, którzy w dodatku pamiëtajâ mój wykîad sprzed miesiâca, w którym prezentowaîem alternatywnâ postaê pëtli FOR..ENDFOR, mogâ wklepaê coô takiego: PROC wypelniaj(ptr:PTR TO INT, x) DEF i FOR i:=0 TO x-1 DO ptr[]++:=i ENDPROC W takich "skrótach" miîujâ sië programiôci piszâcy w C. Aby nie zaciemniaê kodu úródîowego programu, proponujë nie uûywaê takich sposobików. Kto wie, kiedy przyjdzie Ci po pewnym czasie domyôlaê sië, co napisaîeô w tym kawaîku... Powyûszy sposób przekazywania tablic do procedur ma wadë. Za kaûdym razem musisz zadbaê o to, aby w definicji procedury, której parametrem jest wskaúnik do tablicy, typ danej przez niego wskazywanej zgadzaî sië z typem elementu przekazywanej tablicy. Musisz teû w jakiô sposób przekazaê procedurze rozmiar tablicy. Powyûszy przykîad dobrze obrazuje rozwiâzania obu tych problemów.