semestr drugi Wyk ad drugi ł 1. Wska niki (przypomnienie) ź
Transkrypt
semestr drugi Wyk ad drugi ł 1. Wska niki (przypomnienie) ź
Podstawy Programowania – semestr drugi Wykład drugi 1. Wskaźniki (przypomnienie) Zmienna wskaźnikowa jest zmienną, która nie przechowuje bezpośrednio danej, lecz adres komórki pamięci (lub pierwszej komórki z grupy komórek), w której zawarta jest dana. Adres ten nazywany jest również wskaźnikiem, dowiązaniem lub odniesieniem. Często również o zmiennej wskaźnikowej mówi się w skrócie „wskaźnik”. W języku Pascal typ zmiennej wskaźnikowej możemy określić w następujący sposób: type wskaznik = ^typ_zmiennej; gdzie typ_zmiennej może określać zarówno prosty typ danych, jak i takie typy jak np.: tablica i rekord. Istnieje również typ zmiennej wskaźnikowej, który nie określa typu wartości na którą zmienna wskazuje – pointer. Mając zdefiniowany typ możemy utworzyć zmienną typu wskaźnikowego w następujący sposób: var wsk:wskaznik; Możemy to również uczynić w inny sposób (w praktyce może się on okazać mniej użyteczny niż ten zaprezentowany powyżej): var wsk:^typ_zmiennej; Wartością początkową globalnej zmiennej wskaźnikowej jest specjalna wartość oznaczająca, że zmienna ta nie wskazuje na nic. W języku Pascal nosi ona nazwę NIL. Zanim będziemy mogli skorzystać ze zmiennej wskaźnikowej musimy przydzielić jej obszar pamięci do której będziemy zapisywać dane. Procedury przydzielające i zwalniające pamięć stosownie do naszych potrzeb będą opisane w dalszej części wykładu. Możemy też sprawić, aby zmienna wskaźnikowa wskazywała na obszar pamięci, który zajmuje „zwykła” zmienna: 1 1 program wskazniki; 2 uses crt; 3 var 4 s:byte; 5 w:^byte; 6 begin 7 clrscr; 8 s:=4; 9 w:=@s; 10 writeln(s); Operator „@” (wymawiamy jako „et”) odczytuje adres zmiennej globalnej „s” i zapisuje go w zmiennej wskaźnikowej „w”, 2 która ma wskazywać na wartość typu byte (wiersz 9 ), czyli takiego samego typu, jak wartość przechowywana w zmiennej „s”. Po odczytaniu tego na co wskazuje „w” i co jest zapisane w zmiennej „s” okazuje się, że jest to ta sama wartość. Co więcej, wystarczy zmodyfikować tę wartość dla którejkolwiek zmiennej, aby zmieniła się również dla drugiej. Dzieje się tak, ponieważ zmienna „w” wskazuje na obszar pamięci, z którym związana jest zmienna „s”. Zanim objaśnimy bliżej to zjawisko warto zwrócić uwagę na to w jaki sposób posługujemy się zmienną wskaźnikową. Jeśli chcemy zapisać jakąś wartość do obszaru pamięci wskazywanego przez zmienną wskaźnikową to robimy to tak, jak ma to miejsce w wierszu 14 programu. W podobny sposób dokonujemy odczytu (wiersz 11). Jeśli użylibyśmy zapisu np.: w:=3 otrzymalibyśmy błąd czasu kompilacji. Ten zapis oznaczałby, że chcemy, aby zmienna „w” wskazywała na komórkę o adresie „3”, a ponieważ nie jest to prawidłowe określenie adresu, kompilator zaprotestuje. Aby lepiej wyobrazić sobie działanie wskaźników możemy posłużyć się bardzo uproszczonym modelem pamięci. Pamięć będzie tablicą elementów typu byte. Elementy te nazywamy komórkami (w rzeczywistości komórki też mają rozmiar 1 bajta). Indeksy elementów będziemy traktować jak adresy komórek. Załóżmy, że w mamy dwie zmienne, takie same, jak w programie obok. 3 Zmienne „s” i „w” są wiązane na etapie kompilacji z pewnymi komórkami w pamięci , jednak zmienna „w” potrafi przechowywać adresy innych komórek. Po wykonaniu dziewiątego wiersza programu obraz pamięci prezentuje się następująco: 11 writeln(w^); zawartość 12 s:=5; w 1 2 13 writeln(w^); s 2 4 14 w^:=3; 3 15 writeln(s); 4 16 readln; 17 end. 2. adres Zmienna „w” zawiera adres komórki pamięci (2) z którą związana jest zmienna „s”. Zaprezentowany w programie obok sposób posługiwania się zmiennymi wskaźnikowymi w niektórych zagadnieniach jest bardzo przydatny, ale zmienne te są daleko bardziej elastyczne. Struktury dynamiczne W poprzednim paragrafie wspomniano o możliwości przydzielania obszaru pamięci, w którym będą przechowywane informacje. Te obszary będziemy nazywać zmiennymi dynamicznymi (lub zmiennymi wskazywanymi, choć jest to pojęcie trochę szersze). Możemy przydzielić miejsce w pamięci nie tylko na pojedynczą zmienną, ale również na całą strukturę. To rozwiązanie posiada dwie zalety. Rozmiar struktury dynamicznej nie jest z góry określony tak, jak ma to miejsce w przypadku struktur globalnych lub lokalnych. Pamięć na struktury dynamiczne możemy przydzielać (alokować) i zwalniać (dealokować) podczas działania programu, w zależności od potrzeb. Proces ten nie dzieje się automatycznie, jak w przypadku zmiennych lokalnych (zwanych czasem z tego powodu automatycznymi), ale programista ma na niego wpływ, co umożliwia mu lepsze zarządzanie pamięcią. Miejsce dla zmiennych lub struktur dynamicznych jest przydzielane w obszarze pamięci programu, który 4 nazywa się stertą . Istnieje kilka procedur przydzielających i zwalniających pamięć. Najprostszymi są new i dispose. Procedura new przydziela tyle miejsca na stercie ile wynika z typu zmiennej wskaźnikowej do której zapisywany jest adres tej pamięci. Pamięć przydzieloną przez new zwalniamy przy pomocy dispose. Jeśli chcemy przydzielić ściśle określoną ilość pamięci możemy posłużyć się procedurą getmem. Do zwolnienia tej pamięci używamy procedury freemem. Procedura mark zapamiętuje bieżący wskaźnik sterty. Procedura release zwalnia obszar pamięci od miejsca na które wskazuje bieżący wskaźnik sterty do miejsca, którego adres został zapamiętany przy pomocy procedury mark. Przykłady użycia tych procedur, jak i przykłady tworzenia prostych dynamicznych struktur danych zostały omówione na poprzednim wykładzie, więc tutaj zostaną pominięte. Zmienne dynamiczne pozwalają jednak tworzyć bardziej zaawansowane struktury, które nazywamy strukturami abstrakcyjnymi. Abstrakcyjny typ danej określa nie tylko zbiór wartości jakie może przechowywać zmienna tego typu, ale również operacje jakie możemy wykonywać na tych wartościach. Najprostszym przykładem abstrakcyjnej struktury danych (również struktury dynamicznej) jest stos. 1 2 3 4 Zamiast tego operatora możemy użyć funkcji „addr”. Numery wierszy nie są częścią kodu źródłowego! Tak jest w uproszczonym modelu pamięci jaki przyjęliśmy. W rzeczywistości zmienna wskaźnikowa zajmuje 4 lub 8 komórek pamięci (4 lub 8 bajtów). Turbo Pascal posiada dyrektywę $M, która pozwala regulować rozmiar stosu i sterty. W tym celu w nawiasach klamrowych umieszczamy nazwę dyrektywy ($M), a za nią trzy liczby rozdzielone przecinkami. Pierwsza z nich określa rozmiar stosu, druga minimalny rozmiar sterty, a trzecia maksymalny rozmiar sterty. 1 Podstawy Programowania – semestr drugi 3. Stos 5 Stos jest przypadkiem szczególnym listy . Elementy stosu, w których zapisane są dane powiązane są ze sobą tworząc listę. Operacje dodawania i usuwania dotyczą tylko jednego końca tej listy. Działanie stosu określa się angielskim skrótem LIFO (ang. Last In First Out – ostatni nadszedł, pierwszy wychodzi). Pojedynczy element stosu może mieć następującą strukturę: Do zdefiniowania typu pojedynczego elementu stosu (nazywanego również typem bazowym stosu) posługujemy się typem rekordowym. Pole dana może mieć type oczywiście inny typ, niż ten który został zaprezentowany. W zapisie obok możemy dostrzec dwa ciekawe elementy składniowe. Typ wskaźnikowy na zmienną typu „element” jest zdefiniowany wcześniej niż sam typ „element”. Rekord zawiera pole („wsk”), które jest wskaźnikiem na rekord tego samego typu co on. Z tego względu stos nazwany jest też strukturą rekurencyjną. Mając określony typ elementu stosu musimy jeszcze określić podprogramy, które na elementach tego typu będą wykonywały operacje. Tych podprogramów może być kilka, jednak najważniejsze z nich to „push” i „pop”. wskaznik=^element; element = record dana:integer; wsk:wskaznik; end; Procedura „push” umieszcza nowy element na stosie. Przez parametry pobiera wskaźnik na element, który znajduje się na szczycie stosu („x”) oraz wartość, która ma być zapisana w elemencie („y”). Należy zwrócić uwagę, że wskaźnik jest przekazany przez zmienną, gdyż procedura będzie modyfikowała jego zawartość. Procedura posiada również zmienną lokalną, która jest typu „wskaźnik na element stosu”. W wierszu szóstym za pomocą procedury „new” zostaje przydzielona pamięć na nowy element stosu, którego adres zostaje zapisany w zmiennej „top”. W wierszu siódmym w tym elemencie zostaje zapisana informacja, którą ma przechowywać. Teraz należy już tylko umieścić nowy element na szczycie stosu. Najpierw musimy „połączyć” go z resztą stosu. Adres elementu, który jest bieżącym wierzchołkiem stosu jest zapisany w parametrze „x”. Należy więc zawartość tej zmiennej przepisać do pola „wsk” nowego elementu, co też czynimy w wierszu ósmym. Po wykonaniu instrukcji 6 zawartej w tym wierszu nowy element staje się pierwszym elementem stosu, jednakże parametr „x” nie zawiera jeszcze jego adresu, lecz adres elementu poprzedniego. Należy więc zmodyfikować zawartość zmiennej „x” i zapisać w niej adres nowego wierzchołka stosu, co zrealizowane jest w wierszu dziewiątym 7 procedury „push”. Jej działanie można również zobrazować rysunkiem : 1 procedure push(var x:wskaznik; y:integer); 2 {Odkłada element na stos.} 3 var 4 top:wskaznik; 5 begin 6 new(top); 7 top^.dana:=y; 8 top^.wsk:=x; 9 x:=top; 10 end; 1. Przed wykonaniem wiersza ósmego procedury „push” 2. Po wykonaniu wiersza ósmego 3. Po wykonaniu wiersza dziewiątego top dana wsk top dana wsk top dana wsk x dana wsk x dana wsk x dana wsk dana NIL dana NIL dana NIL Wartości jakie znajdą się w polach „dana” i „wsk” elementu mogą być różne, w zależności od tego do czego użyjemy stosu i jak zostanie on stworzony na stercie. Wyjątkiem jest tylko pole „wsk” ostatniego elementu stosu. Musi ono mieć wartość „NIL”, oznaczającą że pole to nie wskazuje na żaden inny element, a element je 8 zawierający jest ostatnim elementem stosu. Należy zastanowić się, czy procedura „push” zadziała prawidłowo, kiedy będziemy tworzyć taki element. Wartość pola „wsk” elementów jest ustalana w wierszu ósmym procedury. Jeśli adres wierzchołka stosu będziemy przechowywać w zmiennej globalnej, którą będziemy następnie przekazywać do procedury „push”, to w polu „wsk” ostatniego elementu zostanie zapisana prawidłowa wartość, gdyż globalna zmienna wskaźnikowa domyślnie jest inicjalizowana wartością „NIL”. W innych przypadkach należy zadbać o odpowiednią inicjalizację zmiennej przechowującej adres wierzchołka przed wywołaniem po raz pierwszy w programie procedury „push”. Wyjaśnienia wymaga również kwestia zmiennej „top”. Ponieważ jest ona zmienną lokalną, więc po zakończeniu procedury jest automatycznie niszczona i nie będzie wskazywać na żaden z elementów stosu. Trzeba również zwrócić uwagę, że procedura „push” nie posługuje się zmienną lokalną wskaźnikową (w tym wypadku „top”), dopóki nie przydzieli na pomocą procedury „new” obszaru pamięci na który ta zmienna będzie wskazywać. To spostrzeżenie jest prawdziwe nie tylko dla stosu, ale również dla innych struktur dynamicznych. Posługiwanie się niezainicjalizowaną lokalną zmienną wskaźnikową jest szczególnie niebezpieczne, ponieważ zmienne tego rodzaju nie są domyślnie inicjalizowane wartością „NIL” i mogą zawierać adres dowolnego miejsca w pamięci operacyjnej. 5 6 7 8 Na tym wykładzie będzie to lista liniowa. Oba pojęcia (listy i listy liniowej) zostaną wyjaśnione na przyszłych wykładach. To znaczy: znajdującym się na szczycie stosu. Rysunek pokazuje w jaki sposób obrazujemy powiązania między poszczególnymi elementami struktur dynamicznych. Prostokąty obrazują zmienne wskaźnikowe i elementy stosu. Strzałki określają, że zmienna wskaźnikowa, lub pole od którego strzałka „wychodzi” przechowuje adres elementu do którego strzałka „dochodzi”. To znaczy: znajduje się na dnie stosu. 2 Podstawy Programowania – semestr drugi Procedura „pop" ma działanie odwrotne do procedury „push” - usuwa element, który znajduje się na szczycie stosu. Posiada ona dwa parametry, pierwszy jest wskaźnikiem na pierwszy element stosu („x”), a poprzez drugi zostanie zwrócona wartość, która jest zapisana w tym elemencie („y”). Obydwa parametry są przekazywane przez zmienną. Procedura ta posiada również jedną zmienną lokalną, w której będzie zapamiętywany wskaźnik do elementu znajdującego się za pierwszym elementem na stosie. W wierszu szóstym procedury parametrowi „y” zostaje przypisana wartość, która umieszczona jest w polu „dana” elementu ze szczytu stosu. Teraz należy ten element zdjąć ze stosu. Najpierw w wierszu siódmym, w zmiennej „bottom” zapamiętywana jest zawartość pola „wsk” bieżącego wierzchołka stosu (jest to adres elementu znajdującego się „pod” wierzchołkiem), następnie, przy pomocy procedury „dispose” zwalniana jest pamięć przydzielona na ten element. Innymi słowy jest on usuwany. Ostatnią czynnością jaka jest wykonywana w procedurze „pop” jest modyfikacja zawartości wskaźnika „x”, tak aby wskazywał na element, który obecnie znajduje się na szczycie stosu. Jego adres został zapamiętany w zmiennej „bottom”, wystarczy więc przepisać zawartość tej zmiennej do parametru „x” (wiersz 9). Działanie tej procedury można przedstawić za pomocą następującego rysunku: 1 procedure pop(var x:wskaznik; var y:integer); 2 {Pobiera element ze stosu.} 3 var 4 bottom:wskaznik; 5 begin 6 y:=x^.dana; 7 bottom:=x^.wsk; 8 dispose(x); 9 x:=bottom; 10 end; 1. Po wykonaniu wiersza siódmego x dana wsk bottom dana wsk dana NIL 2. Po wykonaniu wiersza ósmego x bottom dana wsk 3. Po wykonaniu wiersza dziewiątego x dana wsk bottom dana NIL dana wsk dana NIL Procedura „dispose” oznacza jedynie pamięć w której jest zapisany element jako zwolnioną, natomiast nie niszczy zawartości tej pamięci, ani nie zmienia wartości przechowywanej w zmiennej wskaźnikowej (wbrew temu, co pokazuje rysunek). Mimo, że po wywołaniu „dispose” możliwe jest „sięgnięcie” do zwolnionego elementu, nigdy nie należy tego robić ! Taka operacja może w przypadku innych języków programowania i bardziej zaawansowanych aplikacji okazać się bardzo niebezpieczna i co gorsza trudna do wykrycia. Zmienna „bottom”, podobnie jak zmienna „top” w procedurze „push” jest automatycznie niszczona po zakończeniu wykonania procedury. Aby procedura działała poprawnie należy zadbać, aby parametr „x” nie miał wartości początkowej równej „NIL”. Taka wartość wskaźnika oznaczałaby oczywiście, że nie ma żadnego elementu na stosie, ale również spowodowałaby błąd wykonania w szóstym wierszu procedury. 4. Zastosowania stosu Część pamięci operacyjnej, która należy do programu, przeznaczona jest na stos sprzętowy. W tej pamięci zapamiętywane są, po każdym uruchomieniu podprogramu (procedury lub funkcji), ramki stosu, inaczej zwane rekordami aktywacyjnymi, które zawierają parametry wywołania podprogramu, jego zmienne lokalne oraz adres powrotu do programu. Pozwala to na wywoływanie podprogramów z wnętrza innych podprogramów, a także na rekurencyjne wywołania podprogramu. W tym przypadku stos jest jednak obsługiwany sprzętowo i programista nie musi go tworzyć samemu. Stos jest również używany przez translatory języków programowania (kompilatory i interpretery). W edytorach tekstu struktura stosu może zostać użyta do realizacji wielopoziomowej operacji „cofnij” (ang. undo). W tym ostatnim przypadku na stosie zapamiętywana jest pewna liczba ostatnio wykonanych przez użytkownika operacji, takich jak: wprowadzanie znaków, zmiana czcionki, itd. 5. Przykłady Poniżej zaprezentowano trzy programy, które pokazują proste zastosowania stosu. Pierwszy z nich zapisuje liczby, które wprowadzi użytkownik na stos, a następnie odczytuje je ze stosu. Zgodnie z zasadą LIFO zostaną one wypisane na ekranie w odwrotnej kolejności niż zostały umieszczone na stosie. 1 program stos; 2 uses crt; 3 type 4 wskaznik=^element; 5 element = record 6 dana:integer; 7 wsk:wskaznik; 8 end; 9 var 10 p:wskaznik; 11 nu,ne:integer; 3 Podstawy Programowania – semestr drugi W bloku głównym programu, w wierszach 35, 46, 52 wypisywana jest na ekran wielkość wolnej pamięci. Pozwala to ustalić, czy pamięć poprawnie została przydzielona, a następnie zwolniona. Pętla w wierszach 48 – 51 kończy się w momencie, kiedy wskaźnik na element znajdujący się na szczycie stosu będzie miał wartość „NIL”, stąd wymóg, aby pole „wsk” ostatniego elementu na stosie miało właśnie tę wartość. 12 13 procedure push(var x:wskaznik; y:integer); 14 var 15 top:wskaznik; 16 begin 17 new(top); 18 top^.dana:=y; 19 top^.wsk:=x; 20 x:=top; 21 end; 22 23 procedure pop(var x:wskaznik; var y:integer); 24 var 25 bottom:wskaznik; 26 begin 27 y:=x^.dana; 28 bottom:=x^.wsk; 29 dispose(x); 30 x:=bottom; 31 end; 32 33 begin 34 clrscr; 35 writeln('Dostępna pamięć: ',MemAvail); 36 writeln('Ile elementów odłożyć na stos ?'); 37 readln(ne); 38 while ne>0 do 39 begin 40 writeln('Podaj wartość elementu:'); 41 readln(nu); 42 push(p,nu); 43 dec(ne); 44 end; 45 clrscr; 46 writeln('Dostępna pamięć: ',MemAvail); 47 writeln('Na stos odłożono następujące elementy: '); 48 while p<>nil do begin 49 pop(p,nu); 50 writeln(nu); 51 end; 52 writeln('Dostępna pamięć: ',MemAvail); 53 readln; 54 end. 4 Podstawy Programowania – semestr drugi Program „onp” oblicza wartości prostych wyrażeń arytmetycznych zapisanych w Odwrotnej Notacji Polskiej. Liczby użyte w tych wyrażeniach muszą składać się wyłącznie z jednej cyfry. Przykładowymi wyrażeniami, których wartości mogą być obliczone przez program są: 45+3*, czyli w „zwykłej” notacji (4+5)*3 lub 45+23+* (czyli w notacji wrostkowej (4+5)*(2+3)). Zasadniczą częścią programu jest procedura „wyrazenie”. Czyta ona znak po znaku wyrażenie wprowadzane przez użytkownika. Jeśli odczytany znak nie jest znakiem działania, to konwertowany jest na liczbę, która zapamiętywana jest na stosie (wiersz 64). Jeśli zostanie odczytany znak działania, np. „+”, to ze stosu zdejmowane są dwie liczby (wiersze 46, 47), wykonywane jest dodawanie (wiersz 48) i na stosie zapamiętywany jest jego wynik (wiersz 49). W momencie kiedy użytkownik naciśnie klawisz „Enter” procedura wychodzi z pętli (wiersz 42), zdejmuje ze stosu element, w który zapamiętana jest wartość wyrażenia i ją wypisuje na ekran. 1 program onp; 2 uses crt; 3 type 4 wskaznik=^element; 5 element = record 6 dana:integer; 7 wsk:wskaznik; 8 end; 9 var 10 p:wskaznik; 11 12 procedure push(var x:wskaznik; y:integer); Zamieszczony obok program można napisać bez jawnego użycia stosu lub implementując stos nie w oparciu o listę, a w oparciu o tablicę. Można go również rozbudować, tak aby akceptował liczby wielocyfrowe i bardziej skomplikowane wyrażenia. 13 {Odkłada element na stos.} 14 var 15 top:wskaznik; 16 begin 17 new(top); 18 top^.dana:=y; 19 top^.wsk:=x; 20 x:=top; 21 end; 22 23 procedure pop(var x:wskaznik; var y:integer); 24 {Pobiera element ze stosu.} 25 var 26 bottom:wskaznik; 27 begin 28 y:=x^.dana; 29 bottom:=x^.wsk; 30 dispose(x); 31 x:=bottom; 32 end; 33 34 procedure wyrazenie(var x:wskaznik); 35 var 36 a:char; 37 op1,op2,wyn:integer; 38 begin 39 writeln('Podaj wyrażenie w Odwrotnej Notacji Polskiej: '); 40 repeat 41 a:=readkey; 42 if a=#13 then break; 43 write(a); 44 case a of 45 '+': begin 5 Podstawy Programowania – semestr drugi 46 47 48 pop(x,op1); pop(x,op2); wyn:=op2+op1; 49 push(x,wyn); 50 51 end; '-': begin 52 pop(x,op1); 53 pop(x,op2); 54 wyn:=op2-op1; 55 push(x,wyn); 56 57 end; '*': begin 58 pop(x,op1); 59 pop(x,op2); 60 wyn:=op2*op1; 61 push(x,wyn); 62 63 end else 64 65 push(x,ord(a)-48); end; 66 until false; 67 writeln; 68 writeln('Wartość wyrażenia: '); 69 pop(x,wyn); 70 write(wyn); 71 end; 72 begin 73 clrscr; 74 wyrazenie(p); 75 readln; 76 end. Program bin2dec zamienia podaną na wejście liczbę binarną na liczbę dziesiętną. Procedura „pobierz” zapamiętuje kolejne cyfry liczby binarnej na stosie do momentu, kiedy użytkownik naciśnie klawisz „Enter”. Procedura „zwroc” zdejmuje kolejne cyfry tej liczby ze stosu, mnoży je przez 1 program bin2dec; 2 uses crt; 3 kolejne potęgi dwójki (zapamiętywane w zmiennej „b”), a otrzymane w ten sposób wyniki cząstkowe dodaje do siebie i zapamiętuje w zmiennej „wyn”. Po zdjęciu ze stosu i wykonaniu działań na ostatniej cyfrze wypisywany jest na ekran wynik i procedura kończy swoje działanie. 4 type 5 wskaznik=^element; 6 element = record 7 dana:integer; 8 wsk:wskaznik; 9 Ten program można oczywiście napisać bez użycia stosu. end; 10 var 11 p:wskaznik; 12 13 procedure push(var x:wskaznik; y:integer); 14 var 6 Podstawy Programowania – semestr drugi 15 top:wskaznik; 16 begin 17 new(top); 18 top^.dana:=y; 19 top^.wsk:=x; 20 x:=top; 21 end; 22 23 procedure pop(var x:wskaznik; var y:integer); 24 var 25 bottom:wskaznik; 26 begin 27 y:=x^.dana; 28 bottom:=x^.wsk; 29 dispose(x); 30 x:=bottom; 31 end; 32 33 procedure pobierz(var x:wskaznik); 34 var 35 a:char; 36 begin 37 clrscr; 38 writeln('Podaj liczbę binarną: '); 39 repeat 40 a:=readkey; 41 if a=#13 then break; 42 write(a); 43 push(x,ord(a)-48); 44 until false; 45 writeln; 46 end; 47 48 procedure zwroc(var x:wskaznik); 49 var 50 wyn,a,b:integer; 51 begin 52 wyn:=0; 53 b:=1; 54 while x<>nil do 55 begin 56 pop(x,a); 57 wyn:=wyn+b*a; 58 59 b:=b*2; end; 7 Podstawy Programowania – semestr drugi 61 end; 62 writeln('Liczba zapisana w kodzie dziesiętnym: '); 63 write(wyn); 64 writeln; 65 end; 66 67 begin 68 pobierz(p); 69 zwroc(p); 70 readln; 71 end. 8