Pozostawmy na chwilę wizualne aspekty Delphi i przyjrzyjmy się bliżej stanowiącemu podstawę Delphi językowi Object Pascal. Ponieważ niniejsza książka przeznaczona jest raczej dla zaawansowanych czytelników, z jednej strony ograniczyliśmy się jedynie do zestawienia najważniejszych cech tego języka, z drugiej natomiast wprowadziliśmy pewne porównania jego elementów z innymi językami wysokiego poziomu — jak C++, Visual Basic i Java — przy założeniu, że Czytelnik posiada o nich podstawową wiedzę. Obecna wersja Object Pascala różni się znacznie od tej z Delphi 1 czy Delphi 2, zalecamy więc uważne przestudiowanie treści niniejszego rozdziału — nie da się bowiem w pełni wykorzystać Delphi bez dogłębnej znajomości języka, na bazie którego zostało zbudowane.
Notatka
Ilekroć wspominamy w niniejszym rozdziale o języku C, mamy na myśli elementy wspólne dla C i C++. Gdy mowa jest o elementach specyficznych dla C++ zaznaczamy to wyraźnie.
Komentarz jest najprostszym elementem języka programowania — stanowi swobodny tekst mający znaczenie jedynie dla czytelności programu; przez kompilator jest on całkowicie ignorowany. Object Pascal dopuszcza trzy rodzaje ograniczników komentarza:
· nawiasy klamrowe { .. }, znane z Turbo Pascala,
· ograniczniki typu „nawias-gwiazdka” (* ... *), również występujące w Turbo Pascalu,
· podwójny ukośnik // (ang. double slash), zapożyczony z języka C++.
Oto przykłady poprawnych komentarzy w Object Pascalu:
{
to jest komentarz języka Object Pascal,
podstawy Delphi 6
}
(* to również jest komentarz,
tylko z innymi ogranicznikami
*)
//Ten komentarz musi zmieścić się w jednej linii
// Ten komentarz został, dla odmiany,
// podzielony pomiędzy kilka linii,
// z których każda traktowana jest przez kompilator jako
// niezależny komentarz,
// choć przecież dla użytkownika nie ma to żadnego znaczenia.
Za koniec komentarza rozpoczynającego się od podwójnego ukośnika przyjmuje się koniec linii.
Ostrzeżenie
Komentarze tej samej postaci nie mogą być zagnieżdżane, gdyż jest to sprzeczne z regułami składniowymi języka. Na przykład w poniższym przykładzie
{ { próba zagnieżdżenia komentarza } }
początkiem komentarza jest oczywiście pierwszy z nawiasów otwierających, lecz końcem — pierwszy, nie drugi nawias zamykający. Nie ma jednak żadnych przeszkód, aby zagnieżdżać komentarze różnych typów, na przykład:
(* Poniższy komentarz {nadmiar} wskazuje błędną instrukcję *)
{ poniższe komentarze w formie (* *) ograniczają tekst do usunięcia
W komentarzu rozpoczynającym się od podwójnego ukośnika znaki { } (* *) mogą oczywiście występować bez żadnych ograniczeń, gdyż końcem komentarza nie jest żaden wyróżniony znak, lecz koniec linii.
Ponieważ procedury i funkcje stanowią najbardziej uniwersalny element wszystkich języków algorytmicznych, zrezygnujemy w tym miejscu z ich wyczerpującego opisu (który Czytelnik znaleźć może m.in. w dokumentacji Delphi), koncentrując się na tych ich cechach, które odróżniają Object Pascal od Turbo Pascala oraz tych, które pojawiły się w późniejszych wersjach Delphi (począwszy od Delphi 3).
Wyjątkowo, opisywana w tym miejscu konstrukcja obecna jest w Object Pascalu już od Delphi 2, mimo to jest jednak na tyle mało popularna, iż zasługuje przynajmniej na krótką wzmiankę. Otóż, wywołując funkcję lub procedurę nie posiadającą parametrów, możemy użyć pary nawiasów na wzór języka C lub Java, na przykład:
Form1.Show();
...
R := CurrentDispersion();
co oczywiście jest równoważne
Form1.Show;
R := CurrentDispersion;
Nie jest to może żadna rewelacja językowa, lecz z pewnością drobne ułatwienie życia programistom, którzy oprócz Object Pascala używają również języków wcześniej wymienionych, w których użycie wspomnianych nawiasów jest obowiązkowe.
W Delphi 4 wprowadzono mechanizm przeciążania (overloading) procedur i funkcji, umożliwiający zdefiniowanie całej rodziny procedur (funkcji), posiadających tę samą nazwę, lecz różniących się postacią listy parametrów wywołania, na przykład:
function Divide(X, Y: Real): Real; overload;
begin
Result := X/Y;
end;
function Divide(X, Y: Integer): Integer; overload;
Result := X div Y;
Informacją o przeciążeniu procedury funkcji jest klauzula overload (jak w powyższym przykładzie).
Kompilator, analizując postać wywołania przeciążonej procedury (funkcji), automatycznie wybierze właściwy jej egzemplarz (zwany często jej aspektem), aby jednak zadanie to było wykonalne, poszczególne aspekty faktycznie muszą różnić się od siebie. Nie jest tak chociażby w poniższym przykładzie:
procedure Cap(S: string); overload;
procedure Cap(var Str: string); overload;
Wywołanie
Cap(S);
pasuje bowiem do obydwu aspektów — kompilator uzna drugi z nich za próbę redefinicji pierwszego i zasygnalizuje błąd.
Przeciążanie procedur i funkcji jest chyba najbardziej oczekiwaną nowością Object Pascala od czasu Delphi 1 i, jakkolwiek bardzo pożądane i użyteczne, stanowi jedno z odstępstw od rygorystycznej kontroli typów danych (tak charakterystycznej dla początków Pascala). Mamy wszak do czynienia z różnymi procedurami (funkcjami), kryjącymi się pod tą samą nazwą — przypomina to identycznie nazwane procedury (funkcje) rezydujące w różnych modułach. Należy więc korzystać z przeciążania rozsądnie, a w żadnym wypadku nie należy go nadużywać.
Zagadnieniem podobnym do przeciążania procedur i funkcji jest przeciążanie metod, którym zajmiemy się w dalszej części niniejszego rozdziału.
To kolejna nowość wprowadzona w Delphi 4, umożliwiająca uproszczenie wywołań procedur i funkcji poprzez pominięcie jednego lub więcej końcowych parametrów listy. W treści procedury (funkcji) parametry te posiadać będą wartości domyślne, ustalone w jej definicji. Oto przykład deklaracji procedury z jednym parametrem domyślnym:
procedure HasDefVal( S: String; I: Integer = 0);
Ponieważ drugiemu z parametrów definicja przyporządkowuje domyślną wartość 0, więc wywołanie
HasDefVal('Hello');
równoważne jest wywołaniu
HasDefVal('Hello', 0);
Parametry z wartościami domyślnymi muszą wystąpić w końcowej części listy — nie mogą być „przemieszane” z pozostałymi parametrami, tak więc poniższa deklaracja
Procedure NoProperDefs( X: Integer = 1; Y : Real);
jest błędna, poprawna jest natomiast deklaracja
Procedure ProperDefs( X: Integer = 1; Y : Real = 0.0);
Parametry z deklarowaną wartością domyślną nie mogą być przekazywane przez referencję (var), lecz jedynie przez wartość lub przez stałą (const); wiąże się to z oczywistym wymogiem, aby parametr aktualny wywołania był wyrażeniem o dającej się ustalić wartości. Wymóg ten narzuca również ograniczenie na typ parametru, któremu przypisuje się wartość domyślną — nie mogą w tej roli wystąpić rekordy, zmienne wariantowe, pliki, tablice i obiekty. Ograniczeniu podlega także sama wartość domyślna przypisywana parametrowi — może ona być wyrażeniem typu porządkowego, wskaźnikowego lub zbiorowego.
Pewnego komentarza wymaga użycie parametrów domyślnych w połączeniu z przeciążaniem procedur i funkcji. Należy uważać, aby nie uniemożliwić kompilatorowi jednoznacznego zidentyfikowania właściwego aspektu procedury (funkcji) przeciążanej — rozróżnienie takie nie jest możliwe chociażby w poniższym przykładzie:
procedure Confused(I: Integer); overload;
procedure Confused(I: Integer; J: Integer = 0); overload;
var
X: Integer;
Confused(X); // Ta instrukcja spowoduje błąd kompilacji
Mechanizm parametrów domyślnych oddaje nieocenione usługi przy unowocześnianiu istniejącego oprogramowania. Załóżmy na przykład, iż następującą procedurę
Procedure MyMessage( Msg:String);
wyświetlającą komunikat w środkowej linii okna, chcielibyśmy wzbogacić w możliwość jawnego wskazania linii (poprzez drugi parametr). Nawet jeżeli przyjmiemy, iż podanie 0 jako numeru linii oznaczać będzie tradycyjne wyświetlanie w środkowej linii, to i tak nie zwolni nas to z modyfikacji wszystkich wywołań procedury MyMessage() w kodzie programu. Ściślej — byłoby tak, gdyby nie mechanizm domyślnych parametrów, bowiem począwszy od Delphi 4 możemy dopuścić wywołanie procedury MyMessage() z jednym parametrem, przyjmując w takiej sytuacji, iż opuszczony, domyślny drugi parametr ma wartość 0:
Procedure MyMessage ( Msg : String; Line: byte = 0 );
Wówczas wywołanie
MyMessage('Hello', 1)
spowoduje wyświetlenie komunikatu w pierwszej linii okna, natomiast efektem wywołania
MyMessage('Hello')
będzie wyświetlenie komunikatu w linii środkowej. I nie trzeba przy tym niczego zmieniać, poza oczywiście samą procedurą MyMessage().
W języku C i w Javie możliwe jest deklarowanie zmiennych dopiero w momencie, gdy faktycznie okazują się potrzebne, na przykład:
void foo(void)
int x = 1;
x++;
int y = 2;
float f;
// ... i tak dalej
Natomiast w języku Pascal wszystkie deklaracje zmiennych muszą być zlokalizowane przed blokiem kodu danej procedury, funkcji lub programu głównego:
Procedure Foo;
x, y : integer;
f : double;
x := 1;
Inc(x);
y := 2;
(*
itd.
Może się to wydawać nieco krępujące, lecz w rzeczywistości prowadzi do programu bardziej czytelnego i mniej podatnego na błędy — co jest charakterystyczne dla Pascala, stawiającego raczej na bezpieczeństwo aplikacji niż hołdowanie określonym konwencjom.
Object Pascal i Visual Basic, w przeciwieństwie do Javy i C, niewrażliwe są na wielkość liter w nazwach elementów składniowych. Wrażliwość taka zwiększa co prawda elastyczność języka, lecz niepomiernie zwiększa również prawdopodobieństwo popełnienia trudnych do wykrycia błędów. W Pascalu raczej trudno odczuć brak takiej elastyczności, za to programista ma dość dużą swobodę stylistyczną w pisowni nazw, na przykład nazwa
prostesortowaniemetodąprzesiewaniazograniczeniami
staje się bardziej czytelna, gdy jest zapisana w tzw. notacji „wielbłądziej”
ProsteSortowanieMetodąPrzesiewaniaZOgraniczeniami
Deklaracje zmiennych tego samego typu mogą być łączone, na przykład deklarację
Zmienna1 : integer;
Zmienna2 : integer;
można skrócić do postaci
Zmienna1, Zmienna2 : integer;
Jak widać, po liście zmiennych następuje dwukropek i nazwa typu.
Nadawanie zmiennym wartości odbywa się w innym miejscu niż ich deklarowanie, mianowicie w treści programu. Nowością, która pojawiła się w Delphi 2 jest możliwość inicjowania zmiennych już podczas ich deklaracji, na przykład:
i : integer = 10;
P : Pointer = NIL;
s : String = 'Napis domyślny';
d : Double = 3.1415926 ;
Jest to jednak dopuszczalne wyłącznie dla zmiennych globalnych; kompilator nie zezwoli na inicjowanie w ten sposób zmiennych lokalnych w procedurach i funkcjach.
Wskazówka
Kompilator dokonuje również automatycznej inicjalizacji wszystkich zmiennych globalnych bez deklarowanej wartości początkowej, zerując zajmowaną przez nie pamięć; tak więc wszystkie zmienne całkowitoliczbowe otrzymują wartość 0, zmiennoprzecinkowe — wartość 0.0, łańcuchy stają się łańcuchami pustymi, wskaźniki otrzymują wartość NIL itp.
Stałe (ang. constants) są synonimami konkretnych wartości występujących w programie. Deklaracja stałych poprzedzona jest słowem kluczowym const i składa się z jednego lub więcej przypisań wartości nazwom synonimicznym, na przykład:
const
DniWTygodniu = 7;
Stanowisk = 7;
TaboretyNaStanowisku = 4;
TaboretyOgolem = TaboretyNaStanowisku * Stanowisk;
Komunikat = 'Przerwa śniadaniowa';
Zasadniczą różnicą między Object Pascalem a językiem C — w zakresie deklaracji stałych — jest to, że Pascal nie wymaga deklarowania typów stałych; kompilator sam ustala typ stałej na podstawie przypisywanej wartości. Ponadto, stałe synonimiczne typów skalarnych nie zajmują dodatkowego miejsca w pamięci programu, gdyż istnieją jedynie w czasie jego kompilacji. Więc na przykład następujące deklaracje języka C:
const float AdecimalNumber = 3.14
const int i = 10
const char *ErrorString = "Uwaga, niebezpieczeństwo";
posiadają następujące odpowiedniki w Pascalu:
AdecimalNumber = 3.14;
i = 10;
ErrorString = 'Uwaga, niebezpieczeństwo';
Kompilator, ustalając typ stałej, wybiera typy o możliwie najmniejszej liczebności. Typ stałej całkowitoliczbowej ustalany jest w następujący sposób:
Tabela 2.1
Tej tabeli nie ma w oryginale
Ustalane przez kompilator typy stałych całkowitoliczbowych
Wartość
Typ
od –2^63 do –2.147.483.649
Int64
od –2.147.483.648...
chandller