/* wyraûenia zawarte w >...< proszë pogrubiê */ 10 RAZY E Rafaî Wiosna Tym razem bez zbëdnych wstëpów przystâpimy do omawiania nowego typu danej: Listy Stanowiâ podstawë jëzyka LISP. Moûna je traktowaê jako ciâgi, których elementami sâ nie znaki (CHAR), ale wskaúniki (LONG, czyli liczby 4-bajtowe -- longi). Zupeînie jak ARRAY OF LONG, tablica wskaúników. Listy majâ jednak të przewagë nad tablicami, ûe ich rozmiar moûe byê zmieniony. Mówiâc dokîadniej, rozmiar E-list moûe byê zmieniony, gdyû podobnie jak przy ciâgach, Amiga E dba o definiowane przez uûytkownika listy, a co za tym idzie, musi gdzieô skîadowaê pewne nadmiarowe informacje (nie sâ one dostëpne dla uûytkownika). Normalne listy sâ podobne do, wspomnianych dwa odcinki temu, zwykîych ciâgów -- nie moûna na nich prowadziê ûadnych operacji, posîugujâc sië specjalnymi funkcjami, chyba ûe jej opis na to pozwala. W przeciwieïstwie do ciâgów, lista nie musi zawieraê staîych (ciâg zawsze zawiera okreôlony napis), jej elementami mogâ byê zmienne. Listy zapisujemy uûywajâc nawiasów kwadratowych. Wewnâtrz nich, porozdzielane przecinkami, powinny sië znajdowaê elementy listy. Po jej zdefiniowaniu zwracany jest wskaúnik, oznaczajâcy poîoûenie w pamiëci. Oto pierwszy przykîad z uûyciem listy: DEF lista:PTR TO LONG, liczba liczba:=22 lista:=[1,2,3,liczba] Jak juû wspomniaîem, lista jest rozszerzonâ wersjâ tablicy wskaúników. Tak wiëc moûna by byîo napisaê tak: DEF lista[4]:ARRAY OF LONG, liczba liczba:=22 lista[0]:=1 lista[0]:=2 lista[0]:=3 lista[0]:=liczba Którâ wersjë wybierzesz, zaleûy tylko od Ciebie. Jednak, jak sië okaûe, korzystanie z list moûe uczyniê Twój program îatwiejszym do napisania i odczytania (pamiëtaj teû o komentarzach, bez nich po miesiâcu zupeînie niepotrzebnie bëdziesz sië zastanawiaî, "co ja chciaîem tu napisaê?"). Poza tym listy dajâ o wiele wiëcej niû tablice, ale o tym póúniej. Róûnicë miëdzy zwykîymi listami a E-listami moûna ujâê jednym zdaniem. E-listy definiujesz w DEF-ach procedury, okreôlajâc jej elementy potem, zwykîâ listë definiujesz w dowolnym miejscu i od razu okreôlasz jej elementy. E-listë, jak juû zauwaûyîeô, definiuje sië w nastëpujâcy sposób: DEF lista[30]:LIST Powyûszy przykîad zdefiniowaî listë o zaskasujâco oryginalnej nazwie "lista". Moûe ona liczyê maksymalnie 30 elementów (zupeînie jak przy E-ciâgach, tylko pamiëtaj, ûe tu jeden element to wskaúnik-LONG, a nie bajt-CHAR). Przy okazji zmienna o nazwie "lista" wskazuje na obszar pamiëci zarezerwowany przez definicjë. Listy sâ bardzo pomocne przy tworzeniu tzw. tagów, czyli wîaônie list. Odsyîam do czwartego odcinka kursu E, gdzie ta kwestia byîa szeroko omówiona. Amiga E oferuje programiôcie zestaw funkcji operujâcych na listach. Podobieïstwo do ciâgów jest znaczne, zarówno w nazwach funkcji, jak i ich przeznaczeniu. Takûe caîy mëtlik ze zwykîymi listami i E-listami jest niemalûe taki sam, jak w wypadku ciâgów i E-ciâgów. Dla przypomnienia: dîugoôê E-ciâgu moûe byê dowolnie zmieniana przez odpowiednie funkcje, a takûe E-ciâg jest zgodny "w dóî" z ciâgiem, co oznacza tyle, ûe wszëdzie tam, gdzie funkcja oczekuje podania zwykîego ciâgu, moûna wpisaê E-ciâg. Dokîadnie tak samo jest z listami. Wîaôciwie mógîbym przepisaê poczâtek tego akapitu, zastëpujâc sîowo "ciâg" sîowem "lista"... Pierwszâ funkcjâ, zwiâzanâ z listami, jest >List(MAX)<. Funkcja ta (tu kompletne zaskoczenie) rezerwuje w pamiëci miejsce na E-listë o MAX elementach. DEF lista[30]:LIST to to samo co: DEF lista:PTR TO LONG lista:=List(30) Funkcja zwraca wskaúnik do obszaru pamiëci, zarezerwowanego na nowo tworzonâ listë. Moûe teû zwróciê zero (NIL), co bëdzie oznaczaê, ûe system nie przydzieliî pamiëci ("I'm very sorry, out of memory"). Pamiëtaj, ûe zawsze naleûy sprawdzaê, czy funkcje podobne do List(), które rezerwujâ jakiô element w pamiëci, nie zwracajâ NIL. Inaczej, Twój program moûe spektakularnie zawiesiê czyjô komputer, chyba ûe stosujesz narzëdzia opracy, czyli opisane w numerach 12/94 i 1/95 MA programiki do testowania software (w tym wypadku chodzi o Memoration). Przy okazji: pamiëê zarezerwowana przez List() jest automatycznie zwalniana przy wyjôciu z programu. Nastëpnâ funkcjâ jest >ListCmp(LISTA1,LISTA2,DÎUGOÔÊ=ALL)<, która porównuje zawartoôê dwóch list lub E-list (lub E-listy i listy). Trzecim parametrem jest liczba porównywanych elementów list. Jeûeli go opuôcimy lub wpiszemy "ALL" (co jest wymagane w Amiga E 2.x, dopiero od trójki moûna uûywaê parametrów domyôlnych), porównane zostanâ wszstkie elementy list. Oto przykîady, dla których funkcja ListCmp() zwraca TRUE: ListCmp([1,2,3,4],[1,2,3,4]) ListCmp([1,2,3,4],[1,2,3,7],3) ListCmp([1,2,3,4,5],[1,2,3],3) Zauwaû, ûe w przykîadach uûyîem samych list, nie E-list, ale w tym wypadku nie ma to wiëkszego znaczenia. Waûny natomiast jest fakt, ûe ListCmp() porównuje elementy listy jako WARTOÔCI. Tak wiëc listy zawierajâce wskaúnik do ciâgów, które tak samo wyglâdajâ: ['blablabla',2,3] i ['blablabla',2,3] bëdâ dla ListCmp() RÓÛNE, gdyû pokazane tu ciâgi sâ reprezentowane przez róûne wskaúniki do nich. Typowâ funkcjâ jest >ListCopy(E-LISTA,LISTA,DÎUGOÔÊ=ALL)<. Jak moûna sië domyôliê, dziaîa ona tak samo jak StrCopy() z ciâgami -- kopiuje (E-)listë okreôlonâ przez drugi parametr do E-listy okreôlonej pierwszym. Przy okazji, moûna okreôliê teû liczbë kopiowanych elementów listy (parametr trzeci, domyôlnie -- wszystkie). Oto przykîad pokazujâcy, jak naleûy stworzyê E-listë: DEF li[7]:LIST, blabla blabla:=4 ListCopy(li, [1,2,3,blabla]) W wyniku wykonania powyûszego przykîadu powstanie nowa, uômiechniëta, czteroelementowa E-lista. Gdyby próbowaê funckcjâ ListCopy() skopiowaê do E-listy "li" (E-)listë o liczbie elementów wiëkszej niû siedem, to kompilator sam zadba o to, aby rozmiar "li", zdefiniowany w DEF-ach, nie zostaî przekroczony. (No i co na to zaparci zwolennicy asemblera?). Funkcja >ListAdd(E-LISTA,LISTA,DÎUGOÔÊ=ALL)< dodaje do istniejâcej E-listy wszystkie (albo i nie) elementy listy, bëdâcej drugim argumentem funkcji. Tak jak w poprzednim przykîadzie, trzeci argument oznacza liczbë elementów (w tym wypadku) dodawanych do E-listy (domyôlnie -- wszystkie). Kompilator zadba o to, aby zadeklarowany rozmiar E-listy nie zostaî przekroczony. Kolejne trzy funkcje nie wymagajâ przykîadów, gdyû sâ niemal identyczne z fukcjami operujâcymi na (E-)ciâgach. >ListLen(LISTA)< podaje liczbë elementów (E-)listy, bëdâcej argumentem funkcji. Podobna do niej jest inna funkcja, >ListMax(E-LISTA)<, która zwraca maksymalny rozmiar podanej E-listy. Przydatna jest jeszcze funkcja >SetList(E-LISTA,DÎUGOÔÊ)<, która okreôla dîugoôê E-listy, obcinajâc nadmiarowe elementy z tyîu. Zadeklarowana w drugim argumencie nowa dîugoôê E-listy nie moûe przekraczaê dîugoôci zadeklarowanej w DEF-ach. Bardzo przydatnâ funkcjâ jest >ListItem(LISTA,ELEMENT)<. Zwraca ona wskaúnik do danego elementu (E-)listy. Porównujâc do tablic moûna stwierdziê, ûe jeûeli "li" jest listâ, to "ListItem(li,3)" jest odpowiednikiem wyraûenia "li[3]". Oto przykîad podrëcznikowy: WriteF(ListItem(['pc-zîom','szmatari','Amiga','jabol'],2)) wypisze na ekranie nazwë naszego ulubionego komputera. (Przy okazji wychodzi na jaw, ûe elementy listy numerowane sâ od 0, a nie od 1). A oto przykîad z ûycia wziëty: ENUM OKAY,RTOPEN,SCROPEN,WINOPEN,FONTOPEN PROC main() HANDLE ... EXCEPT WriteF('Wystâpiî bîâd \d: \s\n',ListItem( ['nie moûna otworzyê reqtools.library', 'nie moûna otworzyê ekranu', 'nie moûna otworzyê okna', 'nie moûna otworzyê czcionki'],exception)) Nie tylko LONG Jednâ z zalet list jest fakt, ûe nie muszâ one zawieraê tylko danych typu LONG. Programista moûe w dowolny wîaôciwie sposób ksztaîtowaê pojedynczy element listy. Moûe to byê dana typu CHAR, INT, LONG lub typu definiowanego przez uûytkownika. Najpierw jednak krótkie wyjaônienie tego terminu. Typ definiowany przez uûytkownika jest okreôlany w Amiga E jako obiekt (OBJECT). Jest to odpowiednik struktury w C/C++, klasy w C++ lub rekordu w Pascalu. Nie bëdziemy sië zagîëbiaê w dokîadne omówienie obiektów, wystarczy krótki przykîad: OBJECT osoba imie:PTR TO CHAR nazwisko:PTR TO CHAR wiek:BYTE funkcja:PTR TO CHAR ENDOBJECT Na razie przyjmij bez tîumaczenia fakt, ûe zdefiniowaliômy nowy typ danej -- "osoba". Skîada sië on z trzech pól, "osoba.imië" (wzkaúnik), "osoba.nazwisko" (wskaúnik), "osoba.wiek" (bajt) i "osoba.funkcja" (wzkaúnik). Podobnie jak zmienne, obiekty moûna definiowaê globalnie (przed "PROC main()" jak i lokalnie). Wszystkie struktury systemowe sâ obiektami. No dobra, powróêmy do list. Otóû listë z okreôlonym typem danej (z angielska -- typed list) definiuje sië przez dodanie ":TYP" do jej definicji, np.: [1,2,3,4]:INT Oznacza listë o danych typu INT. No i tu sië drogi rozwidlajâ, gdyû definiujâc takâ listë odcinamy sië od wymienionych wczeôniej funkcji, operujâcych na (E-)listach. Po prostu na tym typie list nie bëdâ one dziaîaîy. Ûeby byîo ômieszniej, dwie listy: [1,2,3,4] oraz [1,2,3,4]:LONG mimo tego, ûe uûywajâ tego samego typu danej, nie sâ ze sobâ zgodne! Czas na przykîad uûywania "typed lists". OBJECT osoba imie:PTR TO CHAR nazwisko:PTR TO CHAR wiek:CHAR funkcja:PTR TO CHAR ENDOBJECT DEF bajty:PTR TO CHAR, mag_amiga:PTR TO osoba, los PROC main() bajty:=[1,2,3,4]:INT mag_amiga:=['Marek','Pampuch',98,'red nacz', 'Rafal','Wiosna',56,'z-ca red nacz', 'Elzbieta','Kozakiewicz',19,'sekr red', 'Roman','Sadowski',73,'gry']:osoba los:=Rnd(4) WriteF('Gîodówka Rotacyjna V3.59 (c) 1995 Dyrekcja\n') WriteF('Dziô gîoduje: \s \s, nasz \s (wiek \d)\n', mag_amiga[los].imie, mag_amiga[los].nazwisko, mag_amiga[los].funkcja, mag_amiga[los].wiek) ENDPROC Dane statyczne Ciâgi i listy (nie myliê z E-ciâgami i E-listami) sâ danymi statycznymi. Znaczy to, ûe adres w pamiëci jednego z wymienionych typów danych jest zawsze na poczâtku programu okreôlony. Zwykle nie musisz sië tym przejmowaê, ale gdy zachce Ci sië stworzyê tablicë skîadajâcâ sië z list o elementach obliczanych przez program, zapewne napisaîyô coô takiego: PROC main() DEF i, a[10]:ARRAY OF LONG, p:PTR TO LONG FOR i:=0 TO 9 a[i]:=[1, i, i*i] -> BÎÂD! ENDFOR FOR i:=0 TO 9 p:=a[i] WriteF('a[\d] zawiera listë o adresie \d\n', i, p) WriteF('Drugim elementem tej listy jest \d\n', p[1]) ENDFOR ENDPROC Amiga E Compiler/Assembler/Linker/PP v3.1a (c) '91-95 Wouter lexical analysing ... parsing and compiling ... no errors a[0] zawiera listë o adresie 19748938 Drugim elementem tej listy jest 9 a[1] zawiera listë o adresie 19748938 Drugim elementem tej listy jest 9 a[2] zawiera listë o adresie 19748938 Drugim elementem tej listy jest 9 a[3] zawiera listë o adresie 19748938 Drugim elementem tej listy jest 9 No i jesteô ugotowany. Tablica "a" zawiera wskaúniki do trzyelementowej STATYCZNEJ listy! Znaczy to, ûe jej adres jest z góry okreôlony przez kompilator i sië nie zmienia. Zaradziê temu problemowi moûna na kilka sposobów, jednak najproôciej jest uûyê funkcji List(), która dynamicznie zarezerwuje E-listë. Oto "dobra" wersja programu: PROC main() DEF i, a[10]:ARRAY OF LONG, p:PTR TO LONG FOR i:=0 TO 9 a[i]:=List(3) ListCopy(a[i],[1, i, i*i]) ENDFOR FOR i:=0 TO 9 p:=a[i] WriteF('a[\d] zawiera listë o adresie \d\n', i, p) WriteF('Drugim elementem tej listy jest \d\n', p[1]) ENDFOR ENDPROC Amiga E Compiler/Assembler/Linker/PP v3.1a demo (c) '91-95 Wouter lexical analysing ... parsing and compiling ... no errors a[0] zawiera listë o adresie 19200232 Drugim elementem tej listy jest 0 a[1] zawiera listë o adresie 19200696 Drugim elementem tej listy jest 1 a[2] zawiera listë o adresie 19200728 Drugim elementem tej listy jest 2 No i o to chodziîo! * Na nastëpny odcinek zostawiîem gwóúdú wykîadu z list -- listy îâczone. Tymczasem proponujë poszukaê archiwów CAPUS.LHA i CAPUS2.LHA. Sâ to zestawy przeróûnych uûytków, utorstwa Capus André (francuz, "dziëki" czemu niektóre opisy sâ w tym niezrozumiaîym jëzyku), kaûdy napisany w E, z peînym kodem úródîowym. Wspaniale nadajâ sië jako materiaî dydaktyczny do nauki Amiga E. Oprócz tego w archiwum znajdujâ sië gotowe moduîy (.m) do przeróûnych bibliotek (np. lh.library, nofrag.library, xpk.library)!!! Archiwa te moûna znaleúê w Aminecie, w katalogu dev/e, oraz w naszym redakcyjnym BBS-ie. Literatura: Jason R. Hulance, plik "Beginners.guide" rozprowadzany z Amiga E 3.x.