77 11. OPERACJE NA ŁAŃCUCHACH ZNAKOWYCH 11.1. Opis

Transkrypt

77 11. OPERACJE NA ŁAŃCUCHACH ZNAKOWYCH 11.1. Opis
11. OPERACJE NA ŁAŃCUCHACH ZNAKOWYCH
11.1. Opis typu i deklarowanie zmiennych łańcuchowych
Typ łańcuchowy o nazwie string opisuje strukturę danych, złożoną z 255 elementów typu
Char. Jest ona przeznaczona do pamiętania łańcuchów znakowych, czyli napisów (fragmentów
tekstu). W jednej zmiennej typu string można przechować najwyżej 255 znaków, ale nie wszystkie
stojące do dyspozycji miejsca muszą być wykorzystane. Dlatego odróżniamy długość deklarowaną
zmiennej łańcuchowej od jej długości rzeczywistej. Długość deklarowana jest to maksymalna liczba
znaków, jakie może pomieścić zmienna. Natomiast długość rzeczywista jest liczbą znaków, jaką
zajmuje łańcuch, który jest aktualnie przechowywany w zmiennej.
.Format zmiennej łańcuchowej w pamięci przedstawia przykład na rysunku 11.1. Pozycje
zmiennej są numerowane kolejno od 0 do 255. W zmiennej L jest aktualnie pamiętany łańcuch
‘Program’. W takim razie rzeczywista długość łańcucha wynosi 7. Szczególną rolę odgrywa
początkowa, zerowa pozycja zmiennej łańcuchowej. Przechowuje ona zawsze znak o numerze
równym aktualnej długości łańcucha. Wobec tego w podanym przykładzie będzie tam się
znajdować znak #7. Pisząc program, na ogół nie musimy pamiętać o zawartości zerowej pozycji
łańcucha. Wynika to z faktu, że operatory i podprogramy łańcuchowe Turbo Pascala po wykonaniu
operacji na łańcuchach same wpisują odpowiedni znak na zerową pozycję zmiennych
łańcuchowych.
L
L[0]
L[1]
L[2]
L[3]
L[4][
L[5]
L[6]
L[7]
#7
’P’
’r’
’o’
’g’
’r’
’a’
’m’
L[8]
L[9]
...
L[255]
...
Rys.11.1. Struktura zmiennej typu string
Deklaracja zmiennej łańcuchowej wygląda, jak następuje:
var Napis: string;
Podobnie, jak to miało miejsce dla innych typów, również zmiennym typu string można nadać
wartość początkową Wtedy zmienną deklarujemy po słowie const, na przykład:
const L:string = ’Program’;
11.2. Własne typy łańcuchowe
Bardzo często w programie używa się zmiennych łańcuchowych, o których wiemy, że ich
długość rzeczywista jest na pewno mniejsza, niż 255 bajtów, Wtedy dla oszczędności pamięci
można zdefiniować własny typ łańcuchowy o odpowiednio mniejszej długości deklarowanej. Na
przykład, jeżeli wiadomo, że używane w programie napisy nie są dłuższe, niż jeden wiersz tekstu na
ekranie, zajmujący 80 znaków, to zdefiniujemy typ:
type S_80 = string[80];
W tej definicji S_80 jest dowolnie przyjętą nazwą, a w nawiasie kwadratowym po słowie string
jest wpisana wartość 80, będąca deklarowaną długością zmiennych danego typu. Zauważmy, że
każda zmienna typu S_80 będzie zajmować 81 bajtów pamięci – jeden dodatkowy bajt na początku
obszaru zmiennej zawiera informację o aktualnej długości pamiętanego łańcucha.
77
11.3. Stałe łańcuchowe
Aby zapisać jawną stałą łańcuchową, odpowiedni ciąg znaków umieszcza się pomiędzy
dwoma apostrofami; na przykład jawnymi stałymi łańcuchowymi są:
’Turbo Pascal’
’Adam Mickiewicz’
’127’
’B:\JEZYKI\TP\TURBO.EXE’
’’
Apostrofy zamykające łańcuch są bardzo ważne, bo dzięki nim kompilator odróżnia stałą
łańcuchową od nazwy zmiennej lub od stałej liczbowej. Jeżeli w zapisie stałej łańcuchowej są tylko
dwa apostrofy, jak w ostatnim przykładzie, to zapis taki reprezentuje łańcuch pusty. Jego aktualna
długość wynosi 0. W bajcie zerowym takiego łańcucha znajduje się znak #0. (Nie jest to znak ‘0’!)
W przypadku, gdy w programie wielokrotnie występuje ten sam napis o znacznej długości,
wygodnie jest stosować stałą łańcuchową definiowaną, jak w przykładzie poniżej:
const C1=’Nacisnij klawisz <Enter>’;
C2=’Wprowadz liczbe: ’;
W wyniku tej definicji, w dowolnym miejscu bloku, w którym definicja obowiązuje, nazwa C1
będzie rozumiana jako stała wartość łańcuchowa ‘Nacisnij klawisz <Enter>’, a nazwa C2 – jako
stała wartość łańcuchowa ‘Wprowadz liczbe: ‘.
11.4. Wykorzystanie instrukcji przypisania
Podobnie, jak dla zmiennych innych typów, po lewej stronie operatora przypisania występuje
nazwa zmiennej, w tym przypadku typu łańcuchowego (standardowego lub własnego), a po prawej
– wyrażenie, w tym przypadku wyrażenie o wartości łańcuchowej. Łańcuchy krótsze od
deklarowanej długości zmiennej, lub tej samej jak ona długości, są kopiowane bez zmian.
Natomiast, gdy chcemy przypisać łańcuch dłuższy, niż na to pozwala deklarowana długość
zmiennej, to końcowe znaki łańcucha zostają po prostu obcięte. Ilustruje to poniższy przykład:
Przykład 11.1. Instrukcje przypisania operujące na zmiennych łańcuchowych
type S20=string[20]’
S12=string[12];
var Nazwisko: S20;
Miasto:S12;
begin
Nazwisko:=’Kowalski’;
Miasto:=’Konstantynopol’;
end.
Łańcuchowi wolno przypisać znak. Na przykład, jeżeli:
var Z:Char; L:string;
to prawidłowe jest przypisanie:
L:=Z;
78
{Nazwisko = ’Kowalski’}
{Miasto = ’Konstantynop’}
W wyniku tej operacji L będzie łańcuchem o aktualnej długości równej 1. Natomiast
przypisanie odwrotne jest niemożliwe, bo zmienna typu Char ma do dyspozycji jeden bajt pamięci,
podczas gdy zmienna typu string wymaga 256 bajtów pamięci!
11.5. Odwoływanie się do elementów łańcucha
Do elementów (czyli poszczególnych znaków) łańcucha można odwoływać się tak samo, jak
do elementów jednowymiarowej tablicy. Takie odwołania mogą być przeznaczone zarówno do
zapisu, jak odczytu znaków. Traktując łańcuch jako tablicę, warto stosować na początku programu
dyrektywę kompilatora {$R+}. Zabezpiecza to przed błędami w przypadku przekroczenia
deklarowanej długości łańcucha.
Chcąc odczytać K–ty znak łańcucha Lancuch i zapamiętać ten znak w zmiennej znakowej C,
napiszemy zatem:
C:=Lancuch[K];
przy czym K nie może być większe, niż aktualna długość łańcucha. W przykładzie 11.2 pokazano
procedurę, która wyprowadza na ekran podany na jej wejście łańcuch W drukiem rozstrzelonym,
wypisując spację (czyli znak #32) po każdym wyprowadzonym znaku. Zwróćmy uwagę na
zastosowanie standardowej funkcji Length. Funkcja ta zwraca aktualną długość łańcucha,
zapobiegając wypisaniu przypadkowych znaków, pamiętanych w obszarze poza aktualna długością
zmiennej W.
Przykład 11.2. Procedura wyprowadzająca łańcuch drukiem rozstrzelonym
procedure Pisz_szeroko(W:string);
begin
for K:=1 to Length(W) do begin
Write W[K];
Write(#32);
end;
end;
Następny przykład pokazuje funkcję, która zwraca łańcuch, stanowiący ciąg N jednakowych
znaków Z. Argument N nie może być większy, niż 255, więc jest typu Byte. Zwróćmy uwagę, że
przy tej metodzie tworzenia łańcucha zerowy bajt nie wypełnia się samoczynnie – należy wpisać do
niego znak o numerze N. Wykorzystano w tym celu standardowa funkcję Chr, która zwraca znak
o numerze zgodnym z jej argumentem.
Przykład 11.3. Funkcja zwracająca łańcuch N jednakowych znaków Z
function Lanc_znak(N:Byte;Z:Char):string;
var Lanc:string;
K:Byte;
begin
for K:=1 to N do Lanc[I]:=Z;
Lanc[0]:=Chr(N);
Lanc_znak:=Lanc;
end;
79
11.6. Operacja sklejania łańcuchów
Chcąc skleić dwa lub więcej łańcuchów, czyli połączyć je w jeden wspólny łańcuch, stosujemy
operator sklejania (konkatenacji), który wygląda tak samo, jak operator dodawania. Otrzymane
w ten sposób wyrażenie łańcuchowe przypisujemy do zmiennej łańcuchowej. Oczywiście wynik
takiej operacji zależy od porządku składników. W poniższym przykładzie widać, że argumentami
operacji sklejania mogą być również znaki:
Przykład 11.4. Sklejanie łańcuchów i znaków
const C1=’Jan’; C2=’Kowalski’;
var S1,S2: string;
begin
S1:=C1+#32+C2;
{S1=’Jan Kowalski’}
S2:=C2+#32+C1;
{S2=’Kowalski Jan’}
end;
11.7. Porównywanie łańcuchów
Porównywanie łańcuchów odbywa się za pomocą operatorów relacyjnych, działających na
łańcuchach:
=,<>,<,>,<=,>=
Przy porównywaniu są sprawdzane numery porządkowe kodu ASCII kolejnych znaków dwóch
porównywanych łańcuchów.
Łańcuch mniejszy, to ten, który na wcześniejszej pozycji ma znak o mniejszym numerze
porządkowym. Wynika stąd na przykład, że łańcuch pisany wielkimi literami jest mniejszy od tego
samego łańcucha pisanego małymi literami.
Łańcuchy są równe, gdy mają tę sama liczbę znaków i jednocześnie znaki na każdej ich
pozycji są wzajemnie jednakowe. W przeciwnym przypadku łańcuchy są różne.
W przykładzie 11.5 pokazano wykorzystanie operatora większości w procedurze, która sortuje
zawartość tablicy łańcuchów w porządku alfabetycznym. Do sortowania zastosowano wcześniej już
opisany w tej książce algorytm bąbelkowy. W programie pokazano także sposób zainicjowania
tablicy łańcuchów przy jej deklarowaniu.
Przykład 11.5. Sortowanie tablicy łańcuchów w porządku alfabetycznym
Program Ex11_5;
uses Crt;
type Tab=array [1..6] of string;
const T:Tab=('Tomasz','Jacek','Adam','Robert','Ewa','Beata');
procedure PokTab(var T:Tab);
var I:Integer;
begin
for I:=Low(T) to High(T) do Writeln(T[I]);
Writeln;
end;
80
Przykład 11.5. c.d.
procedure BubbleSort(var T:Tab);
var K,J:Integer;
Kopia:string;
begin
for K:=Low(T) to High(T)-1 do
for J:=K+1 to High(T) do
if T[K]>T[J] then begin
Kopia:=T[K];
T[K]:=T[J];
T[J]:=Kopia;
end;
end;
begin
Clrscr;
Poktab(T);
Bubblesort(T);
Poktab(T);
Readln;
end.
11.8. Funkcje i procedury standardowe operujące na łańcuchach
Poniżej pokazano nagłówki definicji i krótki opis działania standardowych funkcji i procedur
Turbo Pascala, przeznaczonych do operacji na łańcuchach. Ich stosowanie pozwala uniknąć
konieczności odwoływania się do elementów łańcucha. Wszystkie automatycznie wpisują
odpowiednią wartość do początkowego bajtu łańcuchów wynikowych, który przechowuje
informację o aktualnej długości łańcucha.
Przytoczone poniżej nagłówki służą jedynie do przekazania informacji o liczbie i typach
argumentów poszczególnych funkcji i procedur. Oczywiście w naszych programach stosujemy
jedynie wywołania odpowiednich funkcji i procedur, których gotowe definicje są wewnętrznie
zakodowane w Turbo Pascalu.
function Length(S:string):Integer;
Zwraca aktualną długość łańcucha S.
function Concat(S1,S2, . . . Sn):string;
Zwraca łańcuch będący sklejeniem kolejnych argumentów S1, S2, … Sn. Zamiast tej funkcji można
zastosować operatory sklejania ‘+’.
function Copy(S:string; Poz:Integer; D:Integer):string;
Łańcuch wynikowy jest wycinkiem łańcucha S zaczynającym się od pozycji Poz, mającym długość
określoną przez argument D.
function Pos(S1,S2:string):Byte;
Bada, czy w łańcuchu S2 znajduje się podłańcuch S1. Jeżeli nie ma takiego podłańcucha, zwraca
wartość 0; w przeciwnym przypadku zwraca numer pozycji S2, od której rozpoczyna się pierwsze
wystąpienie podłańcucha S1.
procedure Delete(var S:string; Poz:Integer; D:Integer);
81
Wycina podłańcuch z łańcucha S. Wycięty podłańcuch zaczyna się od pozycji Poz i ma D znaków.
Jeżeli Poz jest większa od aktualnej długości S, to postać łańcucha S nie ulega zmianie.
procedure Insert(S1:string; var S2:string; N:Integer);
Wstawia do łańcucha S2 podłańcuch S1, począwszy od pozycji następnej za znakiem N–tym
łańcucha S2. Jeżeli N jest większe od aktualnej długości S2, to podłańcuch S1 zostaje doklejony na
końcu S2.
procedure Str(X; var S:string);
Przekształca daną X dowolnego typu liczbowego na łańcuch znaków, reprezentujący odpowiedni
zapis dziesiętny tej danej. Pisząc argument X, można stosować parametry określające liczbę pozycji
zapisu i liczbę miejsc po kropce dziesiętnej, podobnie jak w instrukcjach Write, Writeln.
procedure Val(S:string; var X; var Kod:Integer);
Przekształca łańcuch znakowy S, stanowiący poprawny zapis dowolnej liczby, na liczbę X, będącą
zmienną odpowiedniego typu liczbowego. Jeżeli Łańcuch S był poprawnym zapisem liczby, to
argument wyjściowy Kod przyjmuje wartość 0; w przeciwnym przypadku Kod jest numerem
pozycji łańcucha S, na której wykryto pierwszy błąd zapisu. Uwaga: Procedura Val wykrywa także
błąd zakresu dla X typu Real lub Longint. Dla innych typów do kontroli zakresu należy
w programie zastosować dyrektywę kompilatora {$R+}.
11.9. Przykłady wykorzystania standardowych funkcji i procedur łańcuchowych
Wprowadzając wartość liczbową z klawiatury, można łatwo popełnić błąd, polegający na
próbie wprowadzenia niewłaściwego formatu liczby. Najprostszym przykładem może być użycie
przecinka zamiast kropki dziesiętnej przy odczycie z klawiatury liczby rzeczywistej. Instrukcja
Readln wykrywa to jako błąd wykonania, powodując natychmiastowe przerwanie programu, czemu
towarzyszy wydrukowanie komunikatu:
Error 106: Invalid numeric format.
Taka sytuacja jest bardzo niewygodna, ponieważ konieczne jest ponowne uruchomienie
programu od początku. Zatem lepiej jest napisać program w taki sposób, by użytkownik mógł po
prostu, nie przerywając obliczeń, powtórnie wprowadzić poprawnie napisaną wartość liczby. Jedna
z metod takiej programowej kontroli błędów wejścia polega na interpretacji wprowadzonego ciągu
znaków jako łańcucha i próbie przekształcenia tego łańcucha na liczbę za pomocą procedury Val.
Przykład 11.6 pokazuje odpowiednią procedurę własną, która realizuje tę metodę.
Przykład 11.6. Zastosowanie procedury Val do programowej kontroli formatu liczby
procedure CzytKontrol(var X:Real);
var S:string[80];
Kod:Integer;
begin
repeat
Write('Podaj liczbe: ');
Readln(S);
Val(S,X,Kod);
if Kod<>0 then Writeln('Blad na pozycji ',Kod);
until Kod=0;
end;
W procedurze zastosowano instrukcję powtarzająca repeat–until, która tak długo powtarza
prośbę o wprowadzenie liczby rzeczywistej X, dopóki format tej liczby nie będzie poprawny.
Liczba jest odczytywana jako łańcuch znaków i jej zapis znakowy zostaje zapamiętany w zmiennej
82
S. Następnie procedura Val sprawdza format S. Jeżeli stanowi on bezbłędny zapis liczby, to będzie
ona zapisana jako wartość typu Real w zmiennej X. Zmienna kontrolna Kod przyjmie wtedy
wartość 0, co jest warunkiem zakończenia instrukcji repeat–until. Jeżeli odczytany z klawiatury
łańcuch S nie jest poprawnym zapisem liczby, to zostaje wyprowadzony komunikat o błędzie na
określonej pozycji, a instrukcja powtarzająca rozpoczyna kolejny cykl pracy, ponownie prosząc
użytkownika o wprowadzenie danej.
Przykład 11.7 demonstruje program, w którym zdefiniowano funkcję, znajdująca liczbę
wystąpień podłańcucha P w łańcuchu L. Łańcuch i podłańcuch są argumentami funkcji. W ciele
funkcji zastosowano trzy spośród standardowych podprogramów Turbo Pascala: Length, Pos oraz
Delete. Użyta w ciele funkcji instrukcja while–do powtarza sekwencję trzech operacji: (1) Za
pomocą funkcji Pos szukamy początkowej pozycji pierwszego wystąpienia podłańcucha. (2) Po jej
znalezieniu zwiększamy o 1 wartość licznika wystąpień; funkcję tego licznika pełni tutaj zmienna
Licznik. (3) Z badanego łańcucha za pomocą procedury Delete usuwamy znaleziony podłańcuch. Ta
sekwencja trzech operacji powtarzana jest tak długo, dopóki instrukcja Pos(P,L) nie zwróci
wartości 0. Przyjęcie przez Pos wartości 0 oznacza, ze w badanym łańcuchu L nie ma już więcej
wystąpień podłańcucha P, więc w zmiennej Licznik znajduje się końcowy wynik, który należy
przekazać na wyjście funkcji za pośrednictwem końcowej instrukcji przypisania.
Przykład 11.7. Znajdowanie liczby wystąpień podłańcucha w łańcuchu
program Ex11_7;
{Zastosowanie standardowych funkcji i procedur łańcuchowych}
uses Crt;
type S_80 = string[80];
const L:S_80 = 'Adam Kowalski i Janina Kowal-Kowalska';
var P:S_80;
W:Byte;
function IlePodlanc(P,L:string):Byte;
{Zwraca liczbę wystąpień podłańcucha P w łańcuchu L.}
var Licznik:Byte;
Pozycja,D:Integer;
begin
D:=Length(P);
{Długość podłańcucha}
Licznik:=0;
while Pos(P,L)<>0 do begin
Pozycja:=Pos(P,L);
{Pozycja pierwszego wystąpienia P}
Licznik:=Licznik+1; {Przyrost licznika wystąpień P w L}
Delete(L,Pozycja,D) {Usunięcie z L kolejnego podłańcucha P}
end;
IlePodlanc:=Licznik;
end;
begin
Clrscr;
Writeln(L);
Write('Wpisz szukany podlancuch: ');
Readln(P);
W:=IlePodlanc(P,L);
Write('Liczba wystapien: ', W);
Readln;
end.
W przypadku, gdy łańcuch L jest zainicjowany jako ‘Adam Kowalski i Janina Kowal–
Kowalska’, a chcemy znaleźć liczbę wystąpień podłańcucha ‘Kowal’, instrukcja while wykona trzy
83
kroki. Po każdym z nich łańcuch L jest krótszy w wyniku usunięcia kolejnego wystąpienia
podłańcucha, jak pokazano poniżej:
Adam Kowalski i Janina Kowal–Kowalska
{postać początkowa}
Adam ski i Janina Kowal-Kowalska
Adam ski i Janina –Kowalska
Adam ski i Janina –ska
(po kroku nr 1}
{po kroku nr 2}
{po kroku nr 3}
Oczywiście, zmienna L użyta w programie głównym w instrukcji:
W:=IlePodlanc(P,L);
nie ulegnie zmianie w wyniku tego wywołania. Wiemy przecież z rozdziału o procedurach
i funkcjach, że podprogramy kopiują użyte w wywołaniu wartości argumentów wejściowych do
zmiennych lokalnych w obszarze stosu i dopiero na tych lokalnych zmiennych odbywają się
odpowiednie działania. Dlatego zawartość zmiennej L programu głównego pozostaje nienaruszona.
Przykład 11.8. Użycie funkcji Length do centrowania tekstu w elementach tablicy łańcuchów
program Ex11_8;
uses Crt;
type S_80 = string[80];
Tab = array [1..6] of S_80;
const T:Tab=('Ordinal types are a subset of simple types.',
'All simple types other than real types',
'are ordinal types.',
'Except for integer-type values,',
'the first value of every ordinal type',
'has ordinality 0.');
procedure Poktab(var T:Tab);
var K:Integer;
begin
for K:=Low(T) to High(T) do Writeln(T[K]);
Writeln;
end;
procedure Centruj(var T:Tab);
var K:Integer;
S:string;
begin
for K:=Low(T) to High(T) do begin
S:='';
repeat
S:=S+#32;
until 2*Length(S)+Length(T[K])>=79;
T[K]:=S+T[K];
end;
end;
begin
Clrscr;
Centruj(T);
PokTab(T);
Readln;
end.
W przykładzie 11.8 pokazano program, w którym zainicjowano tablicę łańcuchów
fragmentami tekstu w języku angielskim. Program zawiera dwie procedury własne. Procedura
Poktab wyprowadza w kolejnych wierszach zawartość tablicy. Procedura Centruj modyfikuje
84
zawartość tablicy, doklejając na początek każdego jej elementu T[K] łańcuch S, złożony z pewnej
liczby spacji. Łańcuch ten, początkowo pusty, jest konstruowany przez kolejne doklejanie
pojedynczych spacji w zagnieżdżonej pętli repeat–until Dla każdego elementu T[K] doklejanie
spacji kończy się, gdy suma podwójnej długości łańcucha S oraz długości łańcucha T[K] osiągnie
wartość, odpowiadającą liczbie znaków, mieszczących się w jednym wierszu ekranu, a więc po
spełnieniu relacji:
2*Length(S)+Length(T[K]}>=79
W rezultacie tego postępowania, fragmenty tekstu, zawarte w kolejnych elementach tablicy,
zostaną wypisane symetrycznie względem środka ekranu, jak pokazano poniżej.
Ordinal types are a subset of simple types.
All simple types other than real types
are ordinal types.
Except for integer-type values,
the first value of every ordinal type
has ordinality 0.
85

Podobne dokumenty