Eggdrop Tcl część 3
Z IrcWiki.
Spis treści |
Struktury danych
Tytuł tej części brzmi groźnie (jak ja lubię straszyć :), ale uczyć będziemy się w nim po prostu o zmiennych, i przechowywaniu w nim wartości różnego rodzaju.
Zmienne
Zmienna to takie pudełko. Można do niej schować, przechowywać w niej i odczytywać z niej jakąś wartość, np. tekst albo liczbę. Do zmiennej odwołujemy się przez jej nazwę.
Pamiętajmy, że TCL jest językiem bez kontroli typów. Przechowywanie w nim łańcucha, liczby czy wartości innego typu to kwestia umowna. Ma to swoje wady, ale ma też zalety. Każdą wartość możesz potraktować jako tekst. Możesz też odpowiedni tekst potraktować jako liczbę wykonując obliczenia.
Zmiennych nie trzeba deklarować przed użyciem. Zmienna jest tworzona w chwili przypisywania jej wartości. Można potem w ten sam sposób zmienić jej wartość.
Przypisanie wartości do zmiennej:
set Zmienna "Tralala"
Zmienną można też usunąć:
unset Zmienna
Wartość zmiennej można odczytywać poprzedzając jej nazwę znakiem dolara.
set sKomunikat "Ble" Debug "Wartosc zmienej to: $sKomunikat"
Zmienna musi istnieć w chwili jej odczytywania. Inaczej próba odczytu zakończy się błędem.
Zmienne lokalne
Zmienna lokalna to zmienna utworzona wewnątrz procedury. Istnieje tylko w jej wnętrzu i wraz z przechowywaną wartością zostaje zniszczona w momencie zakończenia danego wywołania procedury. To jest najprostszy rodzaj zmiennej i nie ma tutaj co więcej o niej pisać. Po prostu tworzy się ją i używa się jej w procedurze tak jak na przykładzie podanym wyżej.
Zmienne globalne
Zmienna globalna powstaje wtedy, kiedy instrukcja set wykonuje się bezpośrednio w kodzie skryptu, a nie wewnątrz procedury. Istnieje od tego momentu przez cały czas działania skryptu chyba, że zostanie usunięta. Nie usunie jej także polecenie .rehash. Z tym, że ponowne wykonanie instrukcji set spowoduje ponowne przypisanie jej początkowej wartości.
W kodzie pisanym globalnie zmiennej globalnej można używać tak, jak używa się zmiennej lokalnej wewnątrz procedury. Inaczej niż w większości języków programowania, aby użyć zmiennej globalnej wewnątrz procedury, trzeba ją specjalnie zadeklarować. Służy do tego polecenie global. Nic się nie stanie, jeśli w momencie takiej deklaracji zmienna nie istnieje. Po prostu zostaje ona utworzona.
Przykład zmiennej globalnej:
proc UstawZmiennaGlobalna {a_Wartosc} { global ZmiennaGlobalna set ZmiennaGlobalna $a_Wartosc }
W bezpośrednim kodzie skryptu mogłem równie dobrze wstawić instrukcję set, by naszej zmiennej przypisać jakąś wartość domyślną. Nie zrobiłem tego jednak i w ten sposób zmienna globalna zostaje stworzona dopiero w pierwszym wywołaniu procedury.
Zmienne w strafie nazw
Zmienna może też być składową strefy nazw. Importuje się ją do procedury w tej samej strefie instrukcją variable. Do zmiennej takiej można się też odwołać za pomocą operatora zakresu. Działa on wtedy jednakowo w każdym miejscu skryptu.
Przykład zmiennych w strefie nazw:
namespace eval Strefa { # Tym razem stworzymy zmienne od razu set m_sText "pechowa" set m_iLiczba 13 proc Procedura { } { variable m_sText variable m_iLiczba Debug "Twoja $m_sText liczba to: $m_iLiczba" } } proc ZmienZmienne { } { set Strefa::m_sText "szczesliwa" set Strefa::m_iLiczba 7 }
Istnieje niepisana zasada mówiąca, że instrukcje importujące zmienne global i variable pisze się na początku kodu procedury.
Parametry
Parametry już znamy i nic nowego o nich nie powiemy, ale nie wypada nie wspomnieć o nich przy okazji omawiania rodzajów zmiennych. Parametry stają się wewnątrz procedury zmiennymi lokalnymi o przekazanych wartościach.
Uwaga na dolary!
Jeśli programowałeś wcześniej w PHP, nieobce może ci być stawianie znaku dolara $ przez nazwami zmiennych. Jest jednak względem TCL pewna istotna różnica. Pisałem że to prawdopodobnie największa pułapka w tym języku i przyszedł właśnie czas na jej wyjaśnienie. Zapamiętaj więc raz na zawsze: Dolar stawiamy tylko, kiedy chodzi nam o pobranie wartości, którą przechowuje zmienna. Jeśli chodzi nam o samą zmienną, dolara nie stawiamy.
Wydaje się to proste, ale w praktyce często niełatwo jest zdecydować, który wariant jest poprawny. Trzeba się wtedy zdać na własną intuicję i doświadczenie, a w przypadku błędów lub złego działania skryptu w pierwszej kolejności właśnie pośród postawionych i niepostawionych dolarów szukać winowajcy.
- Jeśli dolar jest gdzie nie powinno go być, zamiast zwykłego łańcucha próbujemy odczytać wartość zmiennej o podanej nazwie. Zmienna taka prawdopodobnie nie istnieje i tak powstaje błąd.
- Jeśli dolara brakuje, zamiast wartości przechowywanej przez podaną zmienną wykorzystujemy w danym wyrażeniu wartość tekstową w postaci nazwy tej zmiennej.
Brrr... okropne!
No niestety :/ Pocieszę cię jednak, że w całej swej prostocie język TCL nie ma już gorszych pułapek niż ta.
Pomożemy sobie jeszcze w zrozumieniu tego zagadnienia przykładem:
# Nazwy parametrów to same... nazwy, a więc dolara nie ma proc MojaProcedura {a_sParametr} { # Wyciągamy z parametru wartość, a więc dolar jest Debug "Wartosc parametru to: $a_sParametr" # Przypisanie do zmiennej, czyli chodzi nam o zmienną, czyli dolara nie ma set sZmienna 666 # Interesuje nas wartość, którą ta zmienna przechowuje, czyli dolar jest Debug "Wartosc zmiennej to: $sZmienna" }
Uchwyty
Mimo braku kontroli typów nie sposób nie wyróżnić kilku odrębnych rodzajów wartości. Rozpoczynamy ciągnące się aż do końca tej najważniejszej części kursu omawianie poszczególnych rodzajów wraz ze sposobami operowania na takich wartościach.
Zaczniemy od najprostszego, czyli od uchwytów. Uchwyt to ogólne określenie na wartość, którą skądś otrzymujemy, gdzieś możemy przekazać i poza tym nie da się zrobić z nią niczego sensownego. Przykładami mogą być poznane już uchwyty do timerów, a także do otwartych plików i jeszcze do innych rzeczy. Ogólnie można powiedzieć, że najczęściej uchwyt do czegoś otrzymuje się po utworzeniu tego czegoś i zapamiętuje w jakiejś zmiennej celem późniejszego usunięcia tego czegoś :)
Cykliczne timery - Ostatnie starcie :)
Pamiętasz naszą próbę utworzenia wykonującego się cyklicznie timera i problem, który wtedy napotkaliśmy? Teraz bogatsi w wiedzę na temat zmiennych możemy sobie z nim poradzić.
Ostateczna wersja timera cyklicznego:
proc SetTimer { } { global g_hTimer set g_hTimer [timer 1 OnTimer] } proc KillTimer { } { global g_hTimer killtimer $g_hTimer } proc OnTimer { } { Debug "Minela kolejna minuta" SetTimer } proc OnPreRehash {type} { KillTimer } SetTimer bind evnt - prerehash OnPreRehash
Przed rehash (a jest to jedyna możliwa sytuacja, w której kod zostanie ponownie wykonany, czyli nowy timer utworzony, a stary nie zostałby usunięty) wykonuje się specjalna procedura i powoduje ona usunięcie timera.
Wartości logiczne
Wartość logiczna To taka wartość, która może przechowywać tylko jeden z dwóch stanów. W większości języków programowania jest to false i true. W TCL wartość logiczną reprezentuje liczba 0 lub 1. I to w zasadzie cała filozofia, trudno tu napisać coś więcej. Chyba, że omówimy sobie przy okazji...
Wyrażenia logiczne
Są to po prostu porównania mówiące nam, czy jakaś wartość jest równa, mniejsza itp. od drugiej. Dostępne są następujące operatory porównania:
< >
Mniejszy i większy
<= >=
Mniejszy lub równy i większy lub równy
== !=
Równy i nierówny (różny)
eq ne
Łańcuch równy i łańcuch nierówny (różny) - porównanie wartości traktowanych zawsze jako łańcuchy znaków
Wyrażenia porównujące zwracają wartość logiczną, ponieważ stwierdzają prawdę lub fałsz. Ujmując je w okrągłe nawiasy można je łączyć za pomocą operatorów:
!
NIE (ang. "NOT"), czyli negacja
&&
I (ang. "AND")
||
LUB (ang. "OR")
Takich wyrażeń nie można pisać ot tak, bezpośrednio w kodzie. Pamiętajmy, że TCL jest językiem opartym na poleceniach, a nie na wyrażeniach. Dlatego do obliczania wyrażeń służy specjalne polecenie: expr.
A teraz zagadka. Jaka będzie wartość zmiennej bOutput?
set bOutput [expr ( !( 666 < 13 ) || ( "2 + 2" == "4" ) ) && ( "7" != 7 ) ]
Rozwiązanie: powyższe wyrażenie jest oczywiście fałszywe, a więc wartością będzie 0. Mam nadzieję że w pełni rozumiesz dlaczego.
Liczby całkowite
Liczba całkowita to dodatnia lub ujemna liczba bez części ułamkowej. Każda liczba może być potraktowana jako łańcuch i każdy łańcuch będący poprawną liczbą może być potraktowany jako liczba. Liczby, jak wszystko, można zapisywać w cudzysłowach. Jednak nie jest to potrzebne i dla czytelności oraz dla podkreślenia niełańcuchowego charakteru wartości lepiej tego nie robić.
Inkrementacja
Do zwiększania lub zmniejszania wartości zmiennej będącej liczbą całkowitą służy wygodne polecenie incr.
Przykład:
set iLiczba 7 # Zwiększamy liczbę o 13 incr iLiczba 13 # Zmniejszamy liczbę o 3 incr iLiczba -3 # Inkrementujemy liczbę, czyli zwiększamy ją o 1 # W takim wypadku ostatni parametr można pominąć incr iLiczba
Polecenie incr zwiększa wartość podanej zmiennej. Zwróć uwagę na brak dolara przed jej nazwą. Nie piszemy go, ponieważ chodzi nam o samą zmienną a nie o jej wartość. Jeśli potrzebujesz wartość zmiennej zwiększoną o 1 (lub ilekolwiek), musisz użyć polecenia expr
Wyrażenia całkowite
W wyrażeniach expr możesz używać operatorów arytmetycznych do operacji na liczbach całkowitych:
+ -
Dodawanie i odejmowanie
\* / %
Mnożenie, dzielenie całkowite (z obcięciem reszty) i reszta z dzielenia
A także operatorów bitowych:
~
NIE (ang. "NOT")
<< >>
Przesunięcie bitowe (ang. "shift") w lewo i w prawo
&
I (ang. "AND")
|
LUB (ang. "OR")
^
XOR (exclusive OR)
Losowanie
Czasami zachodzi potrzeba otrzymania liczby pseudolosowej. Służy do tego wbudowane w Eggdropa polecenie rand. Następujące polecenie:
set iLiczba [rand 10]
Spowoduje przypisanie liczby z zakresu między 0 a 9.
Liczby rzeczywiste
Liczby rzeczywiste mogą przechowywać część ułamkową zapisywaną po kropce. Reprezentowane są w pamięci jako liczby zmiennoprzecinkowe o podwójnej precyzji.
Wyrażenia rzeczywiste
Omówione operatory porównania stosowalne są w większości do wszystkich omówionych typów danych. Operatory arytmetyczne stosowane mogą być do dowolnych liczb. Wyjątkiem jest reszta z dzielenia, którą można liczyć tylko dla liczb całkowitych. Dodatkowo w wyrażeniach operujących na liczbach rzeczywistych stosować można szereg funkcji matematycznych takich, jak funkcje trygonometryczne, potęgowe, logarytmiczne i inne. Ich wykaz znajdziesz w dokumentacji języka TCL, w opisie polecenia expr.
Jedną z nich jest bezparametrowa funkcja rand(), która zwraca losową liczbę rzeczywistą z przedziału od 0.0 do 1.0. Nie należy jej mylić z przedstawionym wyżej poleceniem rand!
Uwaga na rodzaje liczb
Na rozróżnienie liczb całkowitych od rzeczywistych należy zwracać baczną uwagę. Nie wszystkie operatory i funkcje stosują się do obydwu tych rodzajów. Liczba całkowita może zostać potraktowana jako rzeczywista. Jeśli przynajmniej jeden z operandów wyrażenia składowego (np. operacji arytmetycznej) jest liczbą rzeczywistą, wynik także będzie liczbą rzeczywistą.
Ciekawym przypadkiem jest dzielenie. Jeśli dzielisz dwie liczby całkowite, wykonane zostaje dzielenie całkowite z obcięciem reszty.
Przykład dzielenia:
set iLiczba1 [expr 5 / 2] set iLiczba2 [expr 5.0 / 2]
Wartością pierwszej zmiennej będzie 2, ponieważ wykonane zostało dzielenie całkowite. Wartością drugiej zmiennej będzie liczba rzeczywista 2.5, ponieważ jeden z operandów także był liczbą rzeczywistą.
Łańcuchy
Łańcuch (ang. "string", inaczej tekst) to ciąg dowolnych znaków. Nawet jeśli nie zawiera spacji, radzę ujmować go w cudzysłowy by podkreślić jego tekstowy charakter.
Do dołączania nowego tekstu na końcu łańcucha służy polecenie append.
Przykład z append:
append a $b set a $a$b
Te dwie instrukcje są logiczne równoważne, ale pierwsza jest lepsza. Użycie append zamiast przypisywania całej wartości może mieć duże znaczenie dla wydajności kodu, szczególnie w przypadku długich łańcuchów.
Inne możliwości manipulacji na łańcuchach zapewnia polecenie string.
string first <substr> <str> [index]
Zwraca indeks pierwszego znaku pierwszego znalezionego wystąpienia wyszukiwanego podłańcucha w przeszukiwanym łańcuchu. index, jeśli podany, oznacza indeks znaku, od którego rozpoczyna się przeszukiwanie. Jeśli nie podany, przeszukiwany jest cały łańcuch str.
string index <str> <index>
Zwraca łańcuch zawierający pojedynczy znak pobrany z podanego łańcucha spod podanego indeksu.
string is <class> <str>
Zwraca wartość logiczną zależnie od tego, czy wszystkie znaki łańcucha należą do podanej klasy, np. cyfr, liter, dużych liter itd. Szczegóły znajdziesz w dokumentacji.
string length <str>
Zwraca liczbę całkowitą odzwierciedlającą ilość znaków podanego łańcucha.
string range <str> <first> <last>
Zwraca podłańcuch złożony ze znaków podanego łańcucha począwszy od tego o indeksie podanym jako first, a skończywszy na tym o indeksie podanym jako last. Jako last można podać end. Zwrócony zostanie wtedy łańcuch skopiowany od pozycji podanej jako first aż do końca.
string replace <str> <first> <last> [newstr]
Zastępuje część łańcucha nowym łańcuchem. Właściwie to usuwa z podanego łańcucha znaki począwszy od indeksu first aż do indeksu last i w to miejsce wstawia nowy łańcuch, jeśli został podany.
Znaki łańcucha indeksowane są od 0. Dokładny opis tych i wielu innych możliwości polecenia string znajdziesz w dokumentacji języka TCL.
Wartość każdego rodzaju, w tym liczbę, listę, a nawet uchwyt, możesz zawsze potraktować jako łańcuch.
Porównywanie łańcuchów
Pracując z łańcuchami natknąłem się na pewien dziwny błąd. Jeśli dwa identyczne łańcuchy zawierały niektóre polskie litery w standardzie Windows-1250 (którego używa m.in. mIRC), jakiekolwiek ich porównania zawsze zwracały 0 (czyli fałsz) wskazując niepoprawnie, że nie są one jednakowe. Zarówno operator ==, eq, jak i polecenie string użyte do porównywania.
Ku mojemu zaskoczeniu okazało się, że błąd nie występuje, jeśli porównuje się łańcuchy znak po znaku. Mimo, że przecież taki pojedynczy wyciągnięty znak także jest łańcuchem. Napisałem dzięki temu własną funkcję do porównywania łańcuchów:
# Porównuje 2 łańcuchy znak po znaku unikając tym samym # błędu podczas porównywania polskich liter # Zwraca 1 lub 0 zależnie, czy podane łańcuchy są identyczne proc MyStrCmp {s1 s2} { set l1 [string length $s1] set l2 [string length $s2] if {$l1 != $l2} { return 0 } for {set i 0} {$i < $l1} {incr i} { if {[string index $s1 $i] != [string index $s2 $i]} { return 0 } } return 1 }
Formatowania IRC
Usługa IRC oferuje oprócz przesyłania zwykłego tekstu także pewne możliwości jego formatowania. Być może znasz je używając np. skrótów klawiszowych [Ctrl]+[K] i [Ctrl]+[B] w mIRCu. Ich wykorzystywanie na codzień nie jest zalecane, ponieważ nie wszyscy używają klienta IRC, który je obsługuje. Poza tym niektórych one po prostu denerwują.
Warto jednak znać sposób ich tworzenia. Dzięki temu pisząc w TCL rozbudowaną grę pod IRC możesz zaoferować każdemu zarejestrowanemu graczowi możliwość włączenia formatowań jeśli chce. Dzięki temu przekazywane informacje będą bardziej czytelne. Formatowań dokonuje się wstawiając do przeznaczonego na wyjście IRC tekstu odpowiednie znaki specjalne o ustalonych kodach: \037 Włącza lub wyłącza podkreślenie \002 Włącza lub wyłącza pogrubienie \003-- Zmienia kolor na podany w miejsce -- zapisany jako jedno- lub dwucyfrowa liczba w systemie dziesiętnym \003 Wyłącza kolor
Kolory przypisane poszczególnym numerom są takie same jak te widoczne w mIRCu po naciśnięciu kombinacji klawiszy [Ctrl]+[K]. Oto ich pełna lista:
- Biały
- Czarny
- Ciemny niebieski
- Ciemny zielony
- Czerwony
- Bordowy
- Fioletowy
- Pomarańczowy
- Żółty
- Jasny zielony
- Morski
- Błękitny
- Niebieski
- Różowy
- Ciemny szary
- Jasny szary
Projektując kolory weź też poprawkę na to, że nie wszystkie są czytelne na białym tle. Jeśli dodam do tego, że nie wszyscy mają ustawione w swoich klientach IRC właśnie białe tło, będzie to kolejny argument przeciwko używaniu tych formatowań.
Listy
To najciekawsze struktury danych w TCL. Lista przechowuje kilka wartości zachowując ich kolejność. Mogą to być wartości dowolnego rodzaju, nawet kolejne listy.
Tworzenie list
Aby utworzyć listę, można ją zadeklarować używając nawiasów klamrowych:
set lLista1 { { "Programowanie" "ciekawe" } { "TCL" "prosty" } }
W powyższym przykładzie powstała lista składająca się z dwóch elementów, a każdy z nich jest kolejną listą zawierającą po dwa łańcuchy.
Drugim sposobem na utworzenie listy jest polecenie list. Zwraca ono po prostu listę utworzoną ze wszystkich podanych elementów.
set l1 [list "Programowanie" "ciekawe"] set l2 [list "TCL" "Prosty"] set lLista2 [list $l1 $l2]
Dzielenie i łączenie
Aby podzielić łańcuch na elementy, które zwrócone zostaną w postaci listy, użyj polecenia:
split <łańcuch> [podzielnik]
Możesz też połączyć elementy listy w jeden łańcuch:
join <lista> [łącznik]
Łącznik/podzielnik to łańcuch (zazwyczaj jeden znak), który oddziela lub ma oddzielać kolejne elementy w łańcuchu. Jeśli nie podany, domyślnie przyjęta zostaje spacja.
Operacje na listach
Do operacji na listach istnieją następujące polecenia: concat [lista] [lista] [lista] itd... Zwraca listę utworzoną z połączenia wszystkich list. Nie tworzy listy zawierającej podane listy, ale łączy elementy podanych list w jedną listę. lappend <lista> [element] [element] [element] itd... Dodaje podane elementy na końcu podanej listy.
Przykład:
lappend a $b set a [concat $a [list $b]]
Te dwie instrukcje są logiczne równoważne, ale pierwsza jest lepsza. Użycie lappend zamiast przypisywania całej listy może mieć duże znaczenie dla wydajności kodu, szczególnie w przypadku dużych list.
lindex <lista> <index>
Zwraca element listy o podanym indeksie. Elementy listy indeksowane są od zera.
linsert <lista> <index> <element> [element] [element] itd...
Zwraca listę utworzoną po wstawieniu podanych elementów do podanej listy na podaną pozycję. Elementy wstawiane są przed element o podanym indeksie. Jako index można podać end. Nowe elementy zostaną wtedy dodane na końcu listy.
llength <lista>
Zwraca liczbę elementów na liście.
lrange <first> <last>
Zwraca listę będącą wycinkiem podanej listy składającą się z elementów począwszy od tego o indeksie first, a skończywszy na tym o indeksie last lub ostatnim, jeśli jako last podane zostało end.
lreplace <lista> <first> <last> [element] [element] [element] itd...
Zastępuje część elementów listy nowymi. Właściwie to usuwa z podanej listy element począwszy od indeksu first aż do indeksu last i w to miejsce wstawia nowe elementy, jeśli zostały podane.
lset <lista> <index> <wartość>
Zmienia wartość elementu podanej listy o podanym indeksie.
Istnieją też polecenia do sortowania i przeszukiwania list. Możesz nawet napisać własną procedurę, która zostanie wykorzystana przez TCL do porównywania elementów podczas takiego sortowania. Po szczegóły odsyłam do opisu poszczególnych poleceń w dokumentacji języka TCL.
Tablice
Tablica to nie kolejny, zwyczajny rodzaj danych. To pewien mechanizm TCL, który warto poznać i którego warto używać. Zmienna jest albo zwykłą pojedynczą wartością dowolnego rodzaju, albo tablicą.
Tablica w TCL ma postać niezachowującego kolejności zbioru wartości i skojarzonych z nimi kluczy. Tablice przeznaczone są głównie do tego, by uzyskiwać dostęp do poszczególnych wartości poprzez klucze. Zarówno klucz, jak i wartość może być dowolnego rodzaju. Możemy się domyślać, że tablice TCL są w jakiś sposób optymalizowane w kierunku szybkości dostępu do wartości o podanym kluczu nawet wobec dużej liczby pozycji w tablicy.
Tablice nie mogą być przekazywane jako parametry funkcji itp. Nie można pobierać ani kopiować zmiennej tablicowej jako całości. Wynika z tego, że przed nazwą zmiennej tablicowej nigdy nie stawiamy znaku dolara chyba, że chodzi nam o wartość konkretnego jej elementu.
Zmienna może być albo zwykła, przechowująca pojedynczą wartość, albo może być tablicą. Potraktowanie zwykłej zmiennej jako tablicy i odwrotnie zakończy się błędem. Zmienna nieistniejąca (niezainicjalizowana) nie jest tablicą i nie można odczytywać z niej jak z tablicy, dopóki nie zostanie ona tablicą w wyniku instrukcji przypisania:
set aTablica(Programowanie) "ciekawe" set aTablica(TCL) "prosty"
Jak widać, do poszczególnych elementów tablicy odwołujemy się podając klucz za nazwą zmiennej tablicowej ujęty w nawiasy okrągłe.
Uwaga! Klucz, którego nazwa w nawiasie ujęta zostanie w cudzysłowy potraktowany zostanie dosłownie - jako zawierający te cudzysłowy - i będzie to inny klucz niż nie ujęty w nie.
Aby odczytać wartość elementu tablicy o podanym kluczu, użyj konstrukcji podobnej do tej:
Debug $aTablica(Programowanie)
Polecenie unset można stosować do tablic na 2 sposoby:
unset aTablica(Programowanie) unset aTablica
Pierwsze polecenia powoduje usunięcie elementu o podanym kluczu z tablicy. Drugie usuwa całą zmienną tablicową.
Tablica (ani żadna inna wartość) nie może zawierać w środku drugiej tablicy. Niemożliwe jest więc tworzenie tablic wielowymiarowych inaczej, niż przez ich reprezentację jednowymiarową lub przez użycie zagnieżdżonych list. Niemożliwa jest konstrukcja w rodzaju: $aTablica(2)(3).
Operacje na tablicach
Do operowania na tablicach służy polecenie array. Niektóre jego możliwości to: array exists <tablica> Zwraca 1 lub 0 zależnie, czy podana zmienna istnieje i jest tablicą. Należy tego używać przed próbą odczytania z tablicy szczególnie jeśli nie ma pewności, że tablica nie jest pusta (bo wtedy taka zmienna nie zostanie uznana jako tablicowa)! array names <tablica> Zwraca listę zawierającą wszystkie klucze tablicy. array size <tablica> Zwraca liczbę elementów tablicy.
Dużo więcej dostępnych parametrów polecenia array znajdziesz w jego dokumentacji.
Przykład
Przykład tablicy:
set aTablica(Programowanie) "ciekawe" set aTablica(TCL) "prosty" foreach {sKlucz} [array names aTablica] { Debug "$sKlucz jest $aTablica($sKlucz)" }
Pętlę foreach poznamy już wkrótce, w następnej części. Powyższy kod spowoduje wypisanie:
[DEBUG] Programowanie jest ciekawe [DEBUG] TCL jest prosty
Tablice kontra listy
Tablice i listy to dwa sposoby na przechowywanie wielu informacji w jednej zmiennej. Czasami można stanąć przed koniecznością wyboru, którego lepiej użyć. Aby to ułatwić, prezentuję krótkie porównanie. Organizacja danych Listy - kolejno następujące po sobie elementy Tablice - pary klucz - wartość Kolejność Listy - zachowana Tablice - nie zachowana Wydajność Możemy się domyślać, że dostęp do elementu tablicy o podanym kluczu jest szybki. O szybkości dostępu do wskazanego elementu listy trudno coś powiedzieć. W przypadku częstego przechodzenia wszystkich elementów tablicy i tak posługujemy się listą. Elastyczność Listy - można przekazywać jako parametry procedur, zwracać itp. Tablice - nie można tego robić
Data i czas
Data i czas reprezentowana jest w TCL przez liczbę sekund, jakie upłynęły od jakiegoś tam umówionego momentu w przeszłości. Dzięki temu wartości tego typu można do siebie dodawać i odejmować od siebie, a także zwiększać i zmniejszać o podaną liczbę sekund.
Do operacji na wartościach czasowych służą 3 odmiany polecenia clock: clock seconds Zwraca aktualny czas. clock format <czas> -format <format> Zmienia wartość czasową na łańcuch wg podanego formatu. Format to łańcuch zawierający miejsca na poszczególne elementy daty i czasu, np. %A to nazwa dnia tygodnia, a %H to godzina. clock scan <łańcuch> Parsuje podany łańcuch na datę automatycznie próbując rozpoznać jego format. Wystarczy powiedzieć, że rozpoznany zostanie nie tylko tradycyjny format np. 2003-09-02 12:54:03, ale nawet słowo yesterday.
Po szczegółowy opis tego elastycznego polecenia odsyłam do dokumentacji TCL.
Przykład
Debug "Jutro będzie: [clock format [expr [clock seconds] + 60*60*24] -format "%Y-%m-%d %H:%M:%S"]
Powyższy kod wypisuje datę i czas, jaka będzie dokładnie za 24 godziny od chwili wykonania tej instrukcji.
Notacja węgierska
Zapewne zwróciłeś uwagę, że w dotychczasowym kodzie używałem tajemniczych przedrostków przed nazwami zmiennych. Teraz, kiedy znamy już wszystkie rodzaje danych TCL, możemy to wyjaśnić.
Notacja węgierska to pewien nieoficjalny standard nakazujący poprzedzanie nazw zmiennych i innych identyfikatorów podczas programowania przedrostkami wskazującymi na ich typ oraz zakres. Zdania na temat jego przydatności są podzielone.
Jedni twierdzą, że poprawia on czytelność kodu. Inni zaś, że to dodatkowy niepotrzebny kłopot. Faktycznie w językach programowania z kontrolą typów korzyści z używania notacji węgierskiej mogą być wątpliwe. W językach skryptowych takich jak TCL, szczególnie podczas pisania większych skryptów, warto jednak ją stosować. Ostateczny wybór należy do ciebie.
Przedstawię teraz mój pomysł na stosowanie notacji węgierskiej w skryptach TCL. Ogólny schemat konstrukcji nazwy zmiennej może wyglądać tak:
<zakres>_<typ><nazwa>
Zakresem może być:
- g
- (od ang. "global") Zmienna globalna
- m
- (od ang. "member") Zmienna składowa strefy nazw
- a
- (od ang. "argument") Parametr procedury
- (brak)
- Zmienna lokalna
- c
- (od ang. "const") Stała, czyli zmienna, której wartości nie będziemy modyfikować
Typem może być:
- h
- (od ang. "handle") Uchwyt
- b
- (od ang. "boolean") Wartość logiczna
- i
- (od ang. "integer") Liczba całkowita
- f
- (od ang. "floating point") Liczba rzeczywista
- s
- (od ang. "string") Łańcuch znaków
- c
- (od ang. "char") Pojedynczy znak, czyli łańcuch zawsze o długości 1
- l
- (od ang. "list") Lista
- a
- (od ang. "array") Tablica
- t
- (od ang. "time") Data i czas
- (brak)
- Typ nieznany lub dowolny
Rozważ następujący przykład:
namespace eval ble { set m_iLiczba 0 proc SetLiczba {a_iLiczba} { variable m_iLiczba set m_iLiczba $a_iLiczba } }
PROJEKT - heap
TCL jest językiem skryptowym i nie posiada wskaźników, referencji ani żadnych innych możliwości tego rodzaju. Tymczasem dynamiczna alokacja i zwalnianie pamięci takie jak w językach kompilowanych przydałoby się czasem do tworzenia zaawansowanych struktur danych takich, jak drzewa czy grafy. Spróbujemy napisać w TCL własny system dynamicznej alokacji pamięci.
Heap to po angielsku sterta. Jak podaje dokumentacja Windows, sterta to dostępne dla procesu miejsce w pamięci, na którym może on alokować bloki, używać ich przechowując w nich dane dowolnego rodzaju i na koniec zwalniać je. Każdy blok pamięci identyfikowany jest przez wskaźnik, który zachowuje się, mówiąc w przybliżeniu, podobnie jak poznane przez nas uchwyty.
Rozwiązaniem może być użycie tablicy. Wskaźniki do zaalokowanych bloków pamięci będą kluczami tablicy, co zapewni do nich szybki dostęp. Alokacja będzie polegała na dodaniu nowego elementu do tablicy, a zwolnienie bloku na usunięciu elementu z tablicy.
Pozostaje jeszcze kwestia wyboru sposobu generowania tych kluczy - wskaźników. Najprościej będzie podawać kolejne liczby całkowite.
Oprócz dostępu do zaalokowanych bloków w celu zapisania i odczytania całej zawartości, dodamy także odpowiedniki poleceń append i lappend, by zapewnić jak największą wydajność kodu używającego naszego modułu.
Razem z tym kodem należałoby rozszerzyć przedstawioną wyżej notację węgierską o nowy przedrostek typu: p (od ang. "pointer") - wskaźnik.
namespace eval mem { # ===== PRIVATE ===== # W tej tablicy przechowywane będą zaalokowane bloki pamięci # Struktura: [int] Wskaźnik > [any] Dane # m_aHeap # Ta zmienna to licznik do generowania nowych wskaźników # Wartością jest numer wskaźnika, który zostanie zaalokowany # w najbliższej alokacji set m_iCounter 0 # ===== PUBLIC ===== # Alokuje nowy blok pamięci i zwraca wskaźnik do niego proc new { } { variable m_aHeap variable m_iCounter set iResult $m_iCounter set m_aHeap($iResult) "" incr m_iCounter return $iResult } # Zwalnia zaakolowany wcześniej blok pamięci o podanym wskaźniku proc delete {p} { variable m_aHeap unset m_aHeap($p) } # Pozwala zapisać dane do pamięci o podanym wskaźniku proc write {p data} { variable m_aHeap set m_aHeap($p) $data } # Pozwala odczytać pamięć spod podanego wskaźnika proc read {p} { variable m_aHeap return $m_aHeap($p) } # Zwraca liczbę zaalokowanych bloków pamięci proc count { } { variable m_aHeap return [array size m_aHeap] } # Dodaje element do listy przechowywanej pod podanym wskaźnikiem proc append_list {p value} { variable m_aHeap lappend m_aHeap($p) $value } # Dołącza łańcuch do łańcucha przechowywanego pod podanym wskaźnikiem proc append_string {p value} { variable m_aHeap append m_aHeap($p) $value } }
Ten tutorial został wprowadzony tutaj za zgodą oryginalnego autora:
Adam Sawicki "Regedit"
http://www.programex.prv.pl
Przepisania do formatu wiki dokonał kemyd
