PP 11, Podstawy programowania


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]

L[8]

L[9]

...

L[255]

#7

'P'

'r'

'o'

'g'

'r'

'a'

'm'

...

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.

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'; {Nazwisko = 'Kowalski'}

Miasto:='Konstantynopol'; {Miasto = 'Konstantynop'}

end.

Łańcuchowi wolno przypisać znak. Na przykład, jeżeli:

var Z:Char; L:string;

to prawidłowe jest przypisanie:

L:=Z;

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;

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;

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);

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 S, 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 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 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 (po kroku nr 1}

Adam ski i Janina -Kowalska {po kroku nr 2}

Adam ski i Janina -ska {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 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



Wyszukiwarka

Podobne podstrony:
PP temat6, Podstawy programowania
PP W7, Podstawy programowania
PP W6, Podstawy programowania
PP temat3, Podstawy programowania
PP W1, Podstawy programowania
PP W4, Podstawy programowania
PP 10, Podstawy programowania
PP W5, Podstawy programowania
PP W8, Podstawy programowania
PP temat2, Podstawy programowania
PP W9, Podstawy programowania
PP temat4, Podstawy programowania
PP temat5, Podstawy programowania
PP W2, Podstawy programowania
PP W10, Podstawy programowania
PP temat6, Podstawy programowania
11-nkb~1, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, l2
zasady zaliczeń PP IG, Politechnika Białostocka, ZiIP (PB), Semestr 1, Podstawy programowania, Progr
pp projekty2004, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania

więcej podobnych podstron