Trocha nápovědy do Pascalu
Z obsahu:
[o úroveň výše]
Jak pracovat s ukazatelem Pointer v Pascalu?
Použijte například tuto procedurku, která to zařídí:
procedure PosunUkazatel(var p:Pointer;okolik:Integer);
type tub=^Byte;
var ub:tub;
begin
ub:=p;Inc(ub,okolik);p:=ub;
end;
Poznámka: Stačí ukazatel převést např. na ukazatel na Byte, který
lze již přesouvat a potom převést zase zpět. Zajímavá je použití
procedury Inc, která provede změnu ukazatele (bude ukazovat na něco jiného).
Použití: Ukazatel typu Pointer nastavím např. na začátek pole a
posunem mohu zpřístupnit další prvky pole (i na to, které má předem neznámou
délku - dynamicky alokované pole - např. matice, jinak bychom mohli definovat
ukazatel na Byte jako ^Byte). Mohlo by to vypadat jako adresová aritmetika v
C++, ale ta je v C++ trochu mocnější, ale zase nebezpečnější.
Jak psán binárně do textového souboru v Pascalu?
Použijte například tuto procedurku, která to zařídí:
procedure Zapis(var f:Text;var adresa;pocet:Word);
const n=250;
var s:String;
adr:Pointer;
begin
adr:=Addr(adresa);
while (pocet>n) do {Řetězec je v Pascalu dlouhý max. 255 znaků!!!}
begin
s[0]:=Chr(n);Move(adr^,s[1],n);Write(f,s);
PosunUkazatel(adr,n);Dec(pocet,n);
end;
s[0]:=Chr(pocet);Move(adr^,s[1],pocet);
Write(f,s); end;
Poznámka: Komentář není nutný pro toho kdo to bude chtít použít a
začátečník to asi těžko použije. Snad jen tolik, že v Pascalu lze zapsat
do textového souboru řetězec String, který je dlouhý maximálně 255 znaků.
Této možnosti využijeme. Nejdříve si definujeme pomocný ukazatel, který
bude probíhat pole, které je větší než řetězec. Nastavíme ho na začátek
pole nebo toho, co chceme zapsat. Potom pole rozdělíme na části po n bytech
(n musí být menší než 255), abychom se vešli do max. délky řetězce - ta
je 255. V nultém prvku (znaku) řetězce je délka řetězce String v Pascalu.
Ke zkopírování části pole do řetězce použijeme Move. Nezapomeneme za adr
napsat "^", což je důležité, abychom nepřepisovali paměť, která
nechceme. Znak ^ označuje, že chceme pracovat s pamětí kam ten ukazatel adr
ukazuje a ne z místem, kde je ukazatel uložen. Prostě ho musíme
dereferencovat, v Pascalu se to dělá ^. Důvod s[1] je ten, že v nultém
prvku řetězci (jak jsme si již řekli) je délka řetězce. Jelikož je to
jen jeden Byte - znak, z toho vyplývá, že String - řetězec v Pascalu může
být dlouhý jen 255 znaků. K posunu (aby ukazoval na další část pole)
ukazatele adr použijeme proceduru PosunUkazatel.
Použití: Například jsem nahrál WWW stránku do paměti - do velkého
pole znaků. Číst to pomocí read je zdlouhavé, pomocí BlockRead je to
rychlejší. Potom jsem chtěl WWW stránku s modifikacemi zapsat do textového
souboru. Jenže smůla, zapsat toto pole (leč je to pole znaků, nejde, je to
proti syntaxi jazyka Pascalu). C++ je rozumější. Tak jsem si musel vymyslet
trik jak to obejít a tím je tato procedure Zapis. Na případné dotazy Vám
mohu odpovědět. Pokud bych ten soubor otevřel pro zápis jako binární, tak
zase do něj nemohu z zapisovat textově, ale i to myslím by naprogramovat šlo.
Co je u lidí nemožné, u Boha jest možné. Mnoho věcí dříve nemožných
se stalo možnými... Bůh, jakožto nejvyšší a také nejchytřejší bytost
skutečně asi nezná výraz nemožný. Pokud tedy lidé pochopí Boží myšlení,
nebude pro ně ani vzkříšení nemožné... Nyní již mohu zapsat do textového
souboru textově (normálně pomocí write resp. writeln), ale také binárně nějaké
pole (pokud to má rozumný smysl) pomocí námi definované procedury Zapis.
Otázky a odpovědi:
- Jaký se vrátí chybový kód, když použiju samotný příkaz Halt bez
parametrů?
- Vrátí se nula. Dále je nutné upozornit na to, že Halt má 1 parametr
typu Word, ale nakonec je stejně v DOSERROR číslo typu BYTE, tzn. že v
DOSERROR může být hodnota 0-255.
- Jaký je rozdíl mezi constructor Init a procedure Init?
- Pokud nepoužijeme virtuální metody v objektovém programování, potom
je to ekvivalentní. V případě, že ovšem definujeme nějakou metodu jako
virtuální, potom constructor navíc (narozdíl od samotné procedury) prý
nastavuje adresy virtálních metod v tzv. virtual method table. Navíc použití
constructor Init je programátorsky "hezčí" než procedure Init.
- Jaký je rozdíl mezi destructor Done a procedure Done?
- Nevím, ale snad je to jakýsi programátorský zvyk, když něco rušíme,
potom je "hezčí" použít destructor než proceduru. Pokud se vyznáte
v přeloženém Pascal programu, možná že zjistíte nějaký rozdíl v překladu.
- Jak lze zjistit, adresu návěští, když Seg(návěští), Ofs(návěští)
nebo (návěští) syntaxe Pascalu toto nedovoluje?
- Bez Assembleru to asi nepůjde. Nejlepší je použít např. MOV
registr,OFFSET návěští resp. MOV registr,SEG návěští. Instrukce se do
Pascalu vkládají nejlépe mezi příkazy ASM a END;.
- Jak lze zjistit 2. rozměr dvourozměrného pole, když 1. rozměr lze
zjistit např. pomocí funkcí High(pole) resp. Low(pole)?
- 1.rozměr: Low(pole) ... High(pole)
2.rozměr: Low(pole[index]) ... High(pole[index])
kde index může být libovolný index 1. rozměru (tedy z intervalu Low(pole)
... High(pole).
Takže řešení je tedy: Low(pole[Low(pole)]) ... High(pole[Low(pole)]); Je
nutné zdůraznit, že dvourozměrné pole je ekvivaletní s jednorozměrným
polem, jehož prvky jsou zase pole.
- Jak lze předat jako parametr funkce či procedury pole předem neznámé
délky?
- V proceduře či funkci napíšeme:
procedure název(pole:Array of typ);
var i:Longint;
begin
for i:=Low(pole) to High(pole) do Writeln('pole[',i,'] = ',pole[i]);
end;
Pokud je předávané pole dvourozměrné, potom nastanou vážně dost značné
problémy. V tomto případě bude asi lepší předávat ukazatel na
dynamicky alokované pole (např. pomocí new, kde jako parametr délky zadáme
např. sirka*vyska*SizeOf(typ).
- Jak lze něco provést před startem vlastního programu?
- Příkazy, které chcete provést, dejte do inicializační jednotky,
kterou bude Váš program používat. Potom se bude tato část automaticky
volat ve všech programech používající tuto jednotku.
unit Jmeno;
interface
...
...
...
implementation
...
...
...
{Inicializační část:}
begin
{příkazy}
end.
- Jak lze něco provést po skončení vlastního programu?
- Je důležité upozornit, že program může skončit několika způsoby:
- zaseknutím
- nekonečným cyklem
- RESETem (ať již studený nebo teplý start)
- přerušením
- chybou
- abnormální konec
- stiskem Break
- normální řádný konec
(Ad 1)
Tento stav je nepříjemný, systém přejde do stavu ztuhnutí (např. při
zprávě: "System halted." apod.). Výhoda: Systém přestane
pracovat ještě dříve, než provede něco nehezkého či nějakou
destruktivní činnost. (Existuje instrukce HALT nebo HLT). V takovém případě,
nezlobte se, bych nic od programu nechtěl vykonávat. Jedině snad vypsat zprávu:
"Systém se v nejbližší době zasekne. Proveďte RESET." Ale jak
naprogramovat, aby se toto hlášení vypsalo před zaseknutím, toť otázka...
(Ad 2)
Tento stav lze těžko kontrolovat, je chybou samotných programů, že takhle
skončí. U normálních programů by měla být možnost program nějak přerušit,
v nejhorším případě alespoň stiskem Break. U tohoto nekonečného cyklu
je vždy problém rozeznat, kdy ještě počítač na něco čeká a kdy už
je skutečně v nekonečném cyklu. Konec konců, nakonec to většinou vzdáme
my... Jak určit ovšem dobu, od které se již čekání bude považovat za
nekonečný cyklus?
Řešení: Nastavte časovač na přípustnou dobu a po té, když program
nic nevykonná (i to je někdy obtížné otestovat...), provede se přerušení
od časovače, např. $1C a provedete příslušnou požadovanou činnost (např.
automatický RESET PC).
(Ad 3)
V případě studeného startu (tlačítkem RESET nebo vypínačem na PC) je těžko
něco vykonávat a při opětovném zapnutí či RESETu se vždy skočí do
obslužného programu v paměti ROM. V případě teplého startu (např.
CTRL+ALT+DELETE) se někdy skočí (nebo také ne) na přerušení 19H (nemusí
to být pravidlo!!!).
Řešení: Pokud je tedy u nějakého počítače ekvivalentní stisk
CTRL+ALT+DELETE s provedením instrukce INT 19H, potom je možné vektor
tohoto přerušení přesměrovat a provést tak vytouženou akci. Pokud se
neshoduje, bude asi nutné zavést rezidentní program, který čte okamižitý
kód stisknutých kláves a rozeznat stisk kláves CTRL+ALT+DEL dříve než
se skutečně teplý start provede. Před tím by bylo ovšem dobré vyprázdnit
klávesnicový buffer, aby se teplý start neprovedl. Praxe však ukazuje, že
bude dosti problematické něco podobného řešit...
(Ad 4)
Při ukončení přerušení např. NMI je asi nerozumné něco provádět.
Ale když je to opravdu nutné, nikdo Vám nemůže zabránit změnit vektor přerušení
a tak zajistit provedení Vaší akce. Bylo by ovšem dobré zavolat původní
obsluhu toho přerušení, které jste změnili. Ani zde se ovšem bez části
rezidentního programu neobejdeme nebo program by musel být stále v paměti,
když je na něj nastaven vektor přerušení (aby se při příchodu přerušení
neskočilo někam, kde už žádné instrukce nejsou).
(Ad 5)
Jsou to např. dělení nulou, přetečení, podtečení, chyba zásobníku či
paměti resp. parity apod. Pokud chyba není ošetřena jinak, většinou následuje
HALT nebo-li zaseknutí počítače. Platí zde to stejné jako v (1). Jakákoliv
akce před zaseknutím je asi nemožná...
(Ad 6)
Abnormální konec může nastat v případě, že systém je přetížen
(mimochodem se většina systémů zasekává, takže uživatelský program se
to ani nedozví, až pouze uživatel potom zoufale buší do klávesnice v domění,
že se mu podaří povzbudit program k další činnosti, ale zpravidla vždy
je nutný nový start počítače). Podobná chyba může nastat v případě,
že by se Vám omylem podařilo zavřít standardní vstup či výstup a potom
by nastaly problémy s otevíráním. Sice se to stává zřídka, ale lze si
domyslet, že počítač se v takovém případě zasekne... Většinou, až
možná na několik neznámých vyjímek, se situace podobá bodu (1).
(Ad 7)
Stisk Break je "uživatelská poslední naděje", když uživatel
neví, jak program ukončit nebo když mu počítač sám nebo program začne
dělat, co uživatel po něm nechce nebo pokud program již dlouho běží a uživatel
již nechce čekat...
Mnoho programů ovšem vypíná možnost přerušení Break, tak u mnoha
programů není možné program tímto způsobem přerušit.
Existují asi 3 metody ochrany před skončení stiskem Break:
- Přesměrují vektor přerušení Break na svou akci, spíše tedy
rovnou na instrukci pro návrat z přerušení (RETI) a tevolají potom
standardní obsluhu přerušení, takže se operační systém o stisku
Break nedozví.
- Nastavením proměnné operačního systému (v DOSu v souboru
CONFIG.SYS BREAK=ON resp. OFF). Těžko říci, jak je to v jiných DOSech
např. v UNIXu či CP/M apod. Nastavením příslušné proměnné lze Break
ignorovat. Hodnotu proměnné DOSu je možné měnit i po zavedení DOSu.
- Vypnutím komunikace s klávesnice nebo jiným podivným způsobem.
Řešení:
V některých z těchto případů je skutečně možné přesměrováním
vektoru přerušní BREAK 23H (platí pouze v MS-DOSu) na obslužnou rutinu,
lze skutečně provést požadovanou činnost, ikdyž problémů s tímto
vektorem je poměrně dost. !!! MS-DOS tento vektor dost často obnovuje,
hlavně při ukončení programu a návratu zpět do DOSu. !!! Jak již bylo
řečeno, je snadné Break ignorovat, ale je dost obtížné něco provést při
jeho stisku. (Přerušení 23H platí jen v MS-DOSu !!!)
(Ad 8)
Program se může řádně ukončit dvěmi způsoby:
- předáním řízení MS-DOSu a uvolněním z paměti
- ukončením jako rezidentní - část programu nebo celý zustane v paměti
(jako vir) a potom provádí, co potřebuje
Ve všech případech můžete změnit vektory všech přerušení, které se
používají k ukončení programu a tak docílit provedení požadované akce
před skončením svého programu (pokud skončí normálně).
Je nutné upozornit na možnost ukončení programu přerušením MS-DOSu 21H,
kde je nutné testovat registr AH jako kód požadované funkce a rozlišit,
zda se skutečně jedná o konec programu.
Jsou ovšem také programy, které vrací řízení tomu programu, který je
spustil. To je většinou asi zajištěno změnou adresy, kam se má skočit při
ukončení programu. !!! Je nutné upozornit, že MS-DOS má většinu pro většinu
svých vektorů uschovanou svou původní hodnotu, takže musíte dát pozor,
co nastavujete a kdy to nastavujete. Uvedené vektory se obnovují při ukončení
Vašeho programu a při návratu do DOSu. !!!
- Problém "Data Segement too large"
- Toto řešení pomáhá jen někdy, co lze udělat, aby se toto hlášení
již neobjevilo:
- deklarujte co nejméně globál. proměnných
- pro uchovávání proměnných používejte zásobník (lokální proměnné)
nebo haldu (používejte příkazy New, Dispose, GetMem, FreeMem pro práci
s dynamickými proměnnými)
- pokud máte v programu moc textů, vytvořte si raději soubor s těmito
texty (většina PC je dnes již tak rychlá, že dobu přístupu na disk,
pokud není častá, lze zanedbat). Při spuštění programu přečtěte
obsah souboru a data uložte jako lokální nebo jako dynamicky alokovaná
- nejvíce místa zabírají řetězce - většinou jsou dlouhé (pokud
nezadáte jinak) 256 Bytů, takže v deklaraci místo "var
s:String;" pište "var s:String[100];" - Většinou je tato délka
100 znaků postačující a do paměti se Vám vejde více řetězců
- Pozor na pole řetězců: "var pole:Array[1..100] of String;"
-zde je délka 256*100=25600 Bytů, ale pokud zadáme kratší
String[delka], můžeme ušetřit dost paměti.
- Ostatně, když se člověk spokojí s málem, nevadí mu více. Opačné
tvrzení neplatí vždy... Stejně jako v životě skromnost jistě neuškodí...
- vyvarujte se zbytečných údajů na obrazovku, popravdě řečeno,
mnoho uživatelů to stejně nečte (a nemusí se jednat pouze o cizince).
Např. když je na obrazovce někde v rohu napsáno: Nyní stiskněte ESC,
tak mnoho uživatelů (s politováním, praxe skutečně někdy ukazuje, že
se tací najdou a není jich málo) například čeká, nebo metodou pokusů
a omylů mačkají cokoliv (třeba někteří nejsou tak zkušení a mohou
existovat i takoví, kterým najití nějaké klávesy dá pěknou fušku!).
Praxe ukazuje, že je i dosti problematické i menu, ve kterém mnoho uživatelů
zmateně hledá... (někdy i já u některých programů ve Windows). Nejlepším
řešením jsou ikonky s obrázky, kterým rozumí i cizinci i malé děti,
které ještě neumí číst. Je dobré, když se program dá ovládat myší
nebo joystickem, protože s klávesnicí jsou problémy, jednak proto, že
rozložení klávesnic se od sebe mírně odlišuje, nemluvě o přehazování
Y-Z, psaní písmen se znaménky atd.
Dále je problém, že u většiny programů se najde vždy nějaká klávesa,
jejíž stisk vypíše jiný znak, než je na klávesnici napsán, a nebo se
stisk klávesy ignoruje.
- vyvarujte se tedy předefinování funkcí kláves, většina uživatelů,
kterým Vaše předefinování klávesnice nevyhovuje mohou právě proto Váš
program odmítnout... Popravdě to zbytečně plýtváte svým časem pro
tvorbu takového programu a také zabírá dost paměti.
- jako čítače cyklů, pokud to jde, používejte stejné proměnné.
Toto sice není tak nutné, jednoduce proměnné pro cyklus mohou být maximálně
10B veliké (a to u reálných čísel Extended).
-
Jakého typu jsou proměnné p1 a p2 v této proceduře. Překlad projde až
k přiřazení p:=p1, kde ohlásí chybu:
procedure Vymen_Byte(var p1,p2);
var p:Byte;
begin
{ Writeln('Low: ',Low(p1),' High: ',High(p1));}
{ Writeln(SizeOf(p2));}
{ p:=p1;p1:=p2;p2:=p;}
{ Writeln(p1,' ',p2);}
end;
var a,b:Array[1..10,1..20] of Byte;
c,d:Byte;
begin
Vymen_Byte(a,b);
Vymen_Byte(c,d);
end.
Var p1,p2 v deklaraci oznamujete, že pod p1 a p2 zde mohou být skoro
libovolné proměnné. S takovou proměnnou se špatně pracuje, protože je
to vlastně taková novinka, ikdyž stará asi od doby Borland Pascal 7.0 :-)
Upozorňuji, že konstanta není proměnná, tedy zde nemůže být. Já jsem
to použil jen v jednom případě, kde se mi to hodilo. Kromě
toho si myslím, že těžko půjde v Pascalu udělat univerzální proceduru
pro výměnu proměnných. V C++ jsem si udělal takové univerzální makro,
které prohazuje číselné proměnné.
Problém, že nelze naprogramovat univerzální proceduru v Pascalu na
prohazování obsahu proměnných, není ani v tom, že by nešlo použít
SizeOf jako délku do Move, ale spíše v přísné syntaxi. Nelze deklarovat
proměnnou, která je předem neznámého typu. Jediný trik bych viděl v
dynamické alokaci proměnné p a potom ji používat. Stačí taková odpověď?
Jak v Pascalu v textovém souboru uchovat a obnovit pozici?
Dejme tomu, že máme překladač, který bychom chtěli naprogramovat v Pascalu
a přitom použít Pascalovský typ textového souboru text a provést
něco na více průchodů.
Řešením může být například tyto funkce:
var Stare0:TTextRec;
l0:Longint;
procedure UschovejPozici(var f:Text);
begin
Move(f,Stare0,SizeOf(Text));
r.ah:=$42;r.al:=1;r.bx:=Stare0.Handle;r.cx:=0;r.dx:=0;MsDos(r);
l0:=r.dx*65536+r.ax;
end;
procedure ObnovPozici(var f:Text);
begin
Move(Stare0,f,SizeOf(Text));
r.ah:=$42;r.al:=0;r.bx:=Stare0.Handle;r.cx:=l0 div 65535;r.dx:=l0 mod 65536;
MsDos(r);
end;
Poznámky: TTextRec je záznam, který odpovídá typu text.
Jinými slovy: Typ text je identicky rovný typu TTextRec. Nelze
je však zaměňovat, protože to syntaxe nedovolí. Kde se očekává Text, musí
být proměnná typu Text. Jsou-li identické, potom můžeme zkopírovat proměnnou
f typu Text do Stare0, což je proměnná typu TTextRec = záznam. Samotné
uchování proměnné typu Text nestačí k uložení pozice v textovém
souboru, rovněž nestačí uložení pouze ukazatele. Je nutné jednak uložit
ukazatel a současně i záznam odpovídající typu Text. Ke zjištění
ukazatele v souboru použijeme funkci DOSu AH = 42H. Funkce se normálně používá
pro přesun ukazatele, ale my jí můžeme použít i pro zjištění aktuální
pozice tak, že ukazatel v souboru posuneme o 0 (tedy nebudeme posouvat). Funkce
DOSu 42H vrátí v registrech DX a AX aktuální ukazatel. Funkce požaduje jako
vstupní parametr v registru BX file handle, který získáme ze struktury
TTextRec nebo-li je také obsažen i v proměnné typu Text. CX (horní) a DX
(dolní) jsou wordy, tvořící 32 bitový ukazatel v souboru, se kterým lze
pracovat v Pascalu jako s proměnnou typu Longint (pokud uvažujeme pouze kladné
hodnoty). Funkce vrátí také 2 wordy aktuálního ukazatele v registru DX
(horní) a AX (dolní) word tvořící 32 bitový ukazatel v souboru.
[o úroveň výše]
|