Programování pro fyziky 2021/22 – přednáška č. 4

Opakování

Před týdnem na třetí přednášce jsme se soustředili na postupy imperativního strukturovaného programování. Základní vlastností je sekvenční provádění příkazů a příkazových konstrukcí, z nichž klíčové jsou podmíněné příkazy a příkazy cyklu.

Dnešní cíl

Nahlédneme na stránku praktických ukázek minialgoritmů, která by vás měla inspirovat k pokusu o jejich přenos do vašeho oblíbeného programovacího jazyka. Rozšíříme náš repertoár konstrukcí strukturovaného programování o ošetření výjimek a projdeme si obvyklé příkazy pro vstup a výstup dat. Ukážeme si obecný aparát operačních systémů pro vstup, resp. výstup dat ze, resp. do souborů.

KOMENTÁŘE

Pestře volené jsou symboly pro vyznačování komentářů ve zdrojových textech různých programovacích jazyků. Obecně může programátor použít řádkový komentář, platný od příslušného symbolu do konce řádku, nebo blokový komentář, platný od příslušného symbolu do jeho párového protějšku. Řádkové komentáře v Pascalu jsme výše použili, jsou uvozeny dvojitým lomítkem //. Totéž platí v C99, ve Fortranu uvozuje ! (a kdysi C v prvním sloupci), v Octave %, v Pythonu #. Blokové komentáře: (* Pascal *), { Pascal }, /* C */, %{ Octave %}, ''' Python '''.

PŘÍKAZY SKOKU A VÝJIMKY

Obecný příkaz skoku je ve strukturovaném programování hlavním otloukánkem. Jazyky ho poskytují v podobě příkazu goto label, kde návěští label je (v Pascalu) identifikátor nebo číselný kód, označující řádek v aktuální programové jednotce. Na ten se má nesekvenčně přeskočit. Je radno se skoku goto vyhýbat a ve specifických kontextech jej nahrazovat vhodnějším příkazem. Viděli jsme skoky pro předčasné ukončení cyklu (continue, break) a uvidíme skok pro předčasné opuštění procedury (exit).

Další situací vyvolávající potřebu někam si odskočit je vznik chybového stavu programu neboli vyvolání softwarové výjimky (exception). Překladače vkládají do programů kód se standardním ošetřením softwarových výjimek, nejčastěji vedoucí k ukončení programu, některé programovací jazyky však programátorovi dovolují reagovat na výjimky vlastním kódem. V následujících konstrukcích se provádějí příkazy v části try, a nastane-li chyba (zachytí-li se výjimka), přeskočí se na příkazy v části except, resp. catch:

// Free Pascal
try commandsTry except commandsExcept end;  // konstrukce může být složitější

% Octave
try
  commandsTry
catch
  commandsCatch
end

# Python
try:
  cmdsTry
except exception:
  cmdsException ...
else:
  cmdsElse
finally:
  cmdsFinally

Poté program pokračuje sekvenčně příkazem za konstrukcí try.

Dělení nulou. Dělení reálné nenuly nulou vrátí jako výsledek znaménkové nekonečno (±Infinity, ±Inf) a dělení nuly nulou nečíslo (Not-a-Number, NaN); obojí patří mezi hardwarové výjimky zachycované procesorem (floating-point exceptions), které mohou a nemusí vyvolat softwarovou výjimku. V Octavu hardwarové výjimky nevadí a program pokračuje bez softwarové výjimky, v Pascalu a Pythonu se softwarová výjimka vyvolá:

// Free Pascal
for i:=-1 to 1 do writeln(1/i:4:1);  // výpis -1.0 a chyba Runtime error 200 Division by zero (Lazarus: External SIGFPE)

% Octave
for i=-1:1, 1/i, 0/i, end             % výpis -1 0, Inf NaN, 1 0

# Python
for i in (-1,0,1): print(1./i)        # výpis -1.0 a chyba ZeroDivisionError: float division by zero

Nevyhovuje-li standardní ošetření softwarové výjimky, může programátor připravit vlastní postup pomocí try. V Octave jsou hardwarové výjimky neškodné a větev catch je nečinná, v Pascalu (v Linuxu, nikoliv ve Windows) a Pythonu větev except už v akci uvidíme:

// Free Pascal
for i:=-1 to 1 do
  try writeln(1/i:4:1) except writeln('error') end;  // výpis -1.0, error, 1.0 (Linux) nebo totéž jako bez try (Windows)

% Octave
for i=-1:1, try, 1/i, 0/i, catch, 'error', end, end   % výpis -1 0, Inf NaN, 1 0

# Python
for i in (-1,0,1):                                    # výpis -1.0, error, 1.0
  try: print(1./i)
  except: print('error')

PŘÍKAZY PRO VSTUP A VÝSTUP DAT

Vstupní data lze do programu vpravit pomocí přiřazovacích příkazů ve zdrojovém textu nebo pomocí příkazů pro vstup dat z klávesnice nebo z diskového souboru. Výstupní data mohou pomocí příkazů pro výstup mířit na obrazovku nebo do diskového souboru. Zde se soustředíme na vstup z klávesnice a výstup na obrazovku, ke čtení a zápisu souborů se vrátíme později. Frekventovanými slovy programovacích jazyků pro příkaz vstupu jsou read, scan a input, pro výstup se užívá print, write, disp aj. Někdy (Pascal, C) je vstup a výstup formálně chápán jako příkaz volání příslušné standardní procedury či funkce.

Syntaxe v Pascalu – vstup: read(data), readln(data), výstup: write(data), writeln(data). Varianty s koncovkou ln provedou odřádkování (čteme read-line, write-line).

V C se nabízí dvojice scanf a printf, ve Fortranu read a print/write, v Octave input a disp a v Pythonu input a print. V interaktivním režimu Octavu a Pythonu je běžné vypsat hodnotu výrazu pouhým jeho zápisem.

Vstup a kontrolní výpis dat. Na výzvu odpovíme zadáním čísla z klávesnice a údaj vypíšeme s implicitním i explicitním formátováním. Pascal může explicitně formátovat jednoduchým uvedením délky výpisu za výraz, např. writeln(1:2,3.:4:1) pro 2znakový výpis celočíselné 1 a 4znakový výpis reálné 3. s 1 znakem za desetinnou tečkou (tj. _1_3.0). V jazycích dominuje formátování inspirované funkcí printf jazyka C. Ta obsahuje formátové specifikace, pomocí nichž se formátují výstupy celočíselné (%d, %nd), reálné (%f, %nf, %n.nf, %n.ne), znakové (%s, %ns) ad. Free Pascal poskytuje v knihovně sysutils funkci format, jejímž prvním argumentem je řetězec s obdobnými formátovými specifikacemi. Fortran používá specifikace I pro integer, F a E pro real, L pro logical a A pro znaky.

// Pascal
write('Zadej n: ');   readln(n);      writeln('n = ',n); writeln(format('%s%d',['n = ',n]));

(* C *)
printf("Zadej n: ");  scanf("%d",&n);                    printf("%s%d\n","n = ",n);

// C++
cout << "Zadej n: ";  cin >> n;       cout << "n = " << n;

! Fortran
print *,'Zadej n: ';  read *,n;       print *,'n = ',n;  print '(A,I0)','n = ',n

% Octave
n=input('Zadej n: ');                 n,                 printf('%s%d\n','n = ',n)

# Python 2 a 3
n=input('Zadej n: ');                 print('n = ',n);   print('%s%d'%('n = ',n))
n=input('Zadej n: '); n=int(n);       print('n = ',n);   print('%s%d'%('n = ',n))

Stojí za to si zkusit a pochopit, co provedou formátové specifikace %d, %0d, %3d, %0.3d, %5.3d celočíselné 1, jak to dopadne s reálnou 1. po výpisu specifikacemi %f, %0f, %0.3f, %7.3f a %e, %0e, %0.3e, %10.3e a jak se vypíše řetězec abc specifikacemi %s, %0s, %5s, %0.2s, %5.2s.

Pozastavení programu. Volání readln bez argumentů slouží k pozastavení programu. Pokračuje se stisknutím klávesy Enter:

program prg;
begin
  write('Press Enter to continue . . . ');
  readln;
end.

PŘESMĚROVÁNÍ STANDARDNÍHO VSTUPU A VÝSTUPU

Následující aparát dovoluje v operačních systémech (Windows i Linux) nahradit textový vstup z klávesnice čtením dat ze souboru a textový výstup na obrazovku zápisem dat do souboru, aniž by bylo třeba něco dělat se samotným programem. Příkaz operačního systému echo vypíše své argumenty na obrazovku. Přidáme-li k takovému příkazu symbol > a jméno souboru,

echo ahoj > pozdrav.txt          (pokud existoval soubor pozdrav.txt, vytvoří se nanovo a zapíše se do něj výpis příkazu)

vytvoří se soubor pozdrav.txt a bude obsahovat text ahoj, který se nevypsal na obrazovku. Zopakujeme-li stejnou sekvenci, soubor pozdrav.dat se přepíše, zapíšeme-li variantu

echo ahoj >> pozdrav.txt         (pokud existoval soubor pozdrav.txt, přidá se k němu výpis příkazu)

k souboru se přidá další řádek. Symbolem > jsme přesměrovali standardní výstupní kanál. Existuje i druhý, chybový výstupní kanál, který lze přesměrovat symbolem 2>:

mkdir adresar                    (vytvoří adresář, čímž je znemožněno jeho znovuvytvoření)
mkdir adresar                    (chybový výstup neřešen)
mkdir adresar > err.txt          (chybový výstup nepotlačen)
mkdir adresar 2> err.txt         (chybový výstup přesměrován)

Přesměrovat lze oba kanály nezávisle, i oba propojit do společného souboru:

command > std.txt 2> err.txt     (standardní výstup do std.txt, chybový výstup do err.txt)
command > out 2>&1               (oba výstupní kanály do souboru out)

Volbou speciálních jmen souborů lze výstup zcela potlačit:

command > nul                    (Windows; potlačení standardního výstupu)
command > /dev/null              (totéž v Linuxu)

Přesměrovat lze i vstup tak, aby se místo z klávesnice načítal ze souboru:

sort                             (setřídí abecedně řádky vložené z klávesnice)
sort < in                        (setřídí abecedně řádky načtené ze souboru in)
sort < in > out                  (setřídí abecedně řádky načtené ze souboru in a vše zapíše do souboru out)

Přesměrovat lze i výstup jednoho programu na vstup druhého programu (této variantě se říká roura):

(echo 2 && echo 1) | sort        (Windows; program sort setřídí vstup zaslaný dvojicí programů echo)
(echo 2; echo 1) | sort          (totéž v Linuxu)

což je výsledkem ekvivalentní sekvenci:

echo 2 > tmp && echo 1 >> tmp && sort < tmp && del tmp  (Windows; ukládá se do mezisouboru tmp, který se nakonec smaže)
echo 2 > tmp; echo 1 >> tmp; sort < tmp; rm tmp         (totéž v Linuxu)

Pokud je žádoucí vidět standardní výstup na obrazovce a souběžně jej kopírovat do souboru, hodí se k tomu linuxovský program tee:

echo ahoj | tee pozdrav.txt      (Linux; zapisuje se souběžně na standardní výstup a do souboru pozdrav.txt)

Cest k tee ve Windows je více (PowerShell, Windows Subsystem for Linux, GnuWin packages), a zkusit lze i tento trik:

echo ahoj > pozdrav.txt | type pozdrav.txt