AMIGA REVIEW online
  Uvodná stránka     Software     Hry     Obaly     Download     Amiga na PC     Amiga Forever  

Kurz jazyka C - 5. díl

Pavel Čížek

Vážení přátelé, je to tady. Začínáme se skutečným programováním v jazyce C - pomalu, opatrně, aby všichni stačili; ale začínáme. Dovolte mi přivítat všechny, kteří se rozhodli poprat se s Céčkem.

Jak jsem již dříve předeslal, mění se styl výkladu. V předchozích částech jsme si "suše" vyložili základy programovacího jazyka C - teoreticky. Zvláště méně zkušeným se to může zdát příliš komplikované. Ale nebojte se, už začíná praktická část. Slíbil jsem, že se pustíme do nějakého malého projektu, který poroste s našimi zkušenostmi (nebo naopak). Situace je následující: budeme vytvářet program - kalkulačku - který bude schopen především vyhodnocovat v podstatě jakékoliv výrazy. Nejprve bude zpracovávat vstup z příkazové řádky, až se to naučí, může dostat vzhled opravdové kalkulačky, a také něco navíc. Naučíme náš program vytvářet tabulky funkcí a kreslit grafy a pokud budete chtít, tak je i budeme umět ukládat na disk v IFF formátu. Pokud by měl někdo připomínky, návrhy apod., může je všem sdělit prostřednictvím redakce.
To vše je budoucnost, doufám, že světlá. Pokud nebude mít nikdo nic proti, budou se kompletní zdrojové texty k našemu kurzu objevovat na Coverdiscích Amiga Review (stejně by se všechno nevešlo na stránky časopisu). A bude-li místo, budou se objevovat obrázky prezentující činnost a schopnosti programu ty budou zpočátku sice malé, ale porostou.
Dovolte mi ještě pár slov v úvodu našeho programování v Céčku: Céčko je znám jako jazyk velice "volný"; teoreticky můžete vzít program v Céčku a zmáčknout ho do jediné řádky bez ohledu na jeho délku. Program se může (v nevhodném zápisu) stát velmi těžko čitelným (omluvte kvalitu výpisů v časopise, ale možnosti jsou omezené zdrojové soubory na Cover disku by to měly napravit). Pokud má ovšem spolupracovat několik lidí na tomtéž kódu, je třeba se domluvit na několika základních pravidlech při psaní, aby si mohli dobře rozumět. Přitom není podstatné, na čem se kdo s kým dohodne, ale musí se daná pravidla dodržovat.

Pár pravidel hned na začátku
Při formátování funkce je většinou snaha, aby bylo dobře vidět jméno funkce a aby všechen kód funkce byl odsazen. Původní styl dle Kernighan & Ritchie vypadá takto:

void Nesmysl(a, b)
int a; char b;
{
... odsazený kód funkce ...
}

My budeme používat novější styl podle ANSI normy jazyka C, v němž jsou deklarace typů proměnných přímo v hlavičce funkce:

void Nesmysl(int a, char b)
{
... odsazený kód funkce ...
}

Poznamenejme, že někdo používá ANSI zápis s tím, že typ návratové hodnoty je uveden na samostatném řádku.
Další důležitou věcí je pojmenovávání proměnných v programech. Jména globálních proměnných mají obvykle velká písmena na počátku každého slova svého jména, jako např. "DocasnySoubor", "VeIkyNesmysl". Totéž se týká statických proměnných (obzvláště, jsou-li deklarovány uvnitř funkcí). Původně se používala pro zčitelnění podtržítka "_", ale pomalu se od toho upouští. Automatické proměnné (ty, které jsou lokální v jednotlivých funkcích) se vždy píší malými písmeny. Naopak makra a konstanty se vždy píší velkými písmeny, aby se odlišily od proměnných. Jména procedur se obvykle píší s velkými počátečními písmeny, jedná-li se o hlavní/důležité funkce, malými písmeny se píší názvy pomocných funkcí. A zde je příklad:

char VyslednyRetezec[256];
int NactiData(char * jmeno)
{ int i, j;
static char TempBuf[20];
...
}

Důležitou součástí zdrojového kódu je zápis bloku začíná { a končí }. Kdykoliv se objeví nový blok, kód v něm obsažený je vždy o několik mezer odsazen - zpravidla o délku tabelátoru (viz dále). Jedná se především o bloky = těla podmínek a cyklů. Otevírací závorka je zpravidla na tomtéž řádku jako podmínka nebo samostatně na řádku následujícím. Uzavírací závorka je samostatně na konci bloku a je odsazena od levého okraje stejně jako příslušná podmínka / cyklus. Příkladem může být následující cyklus:

while (! konec)
{
... něco dělej ...
}

Mezery a tabelátory hrají při zápisu zdrojového kódu důležitou roli, neboť mohou výrazně zlepšit čitelnost. Porovnejte např. tyto dva výrazy:

i=j*(k+4)/-23-(i+j)
i=j * (k + 4) / -23 - (i + j)

Pravidlo je jednoduché - kolem binárních operátorů umisťujeme mezery z obou stran a mezeru umístíme i před unární operátor. Mezery se obvykle vynechávají za levou a před pravou závorkou. Další výjimka jsou zápisy polí a struktur:

i = uk->mi_Hodnota + nesmysl[j + 2];

U volání procedur a funkcí se většinou před čárkami mezery nepíší, za nimi ano: MojeFunkce(a, retezec, 2);.
Jak bylo zmíněno, míra odsazování a tedy i úprava závisí na používaných tabelátorech. Používají se tabelátory o velikostí 3, 4, 5 či 8 mezer - většinou to závisí na osobním vkusu. Dříve se často používal tabelátor o velikostí 8 mezer, ale v nových, hodně strukturovaných programech by docházelo k velikému odsazení a byla by vidět jen malá část kódu. Používat by se nemělo ani odsazení menší než 3 - činí to strukturu programu nepřehlednou.

Začínáme s kalkulačkou
Podívejme se konečně na náš program - jeho první verzi. Dnes je uveden v plném znění, abychom si mohli ukázat řadu základních věcí. Když se na výpis podíváme, hned v úvodu spatříme komentář obsahující jméno souboru a krátkou poznámku; nemusím snad už připomínat, že komentáře jsou uzavřeny dvojicemi znaků /* a */. Je zvykem (zvláště u rozsáhlejších projektů) na začátku souboru uvést stručnou informaci o tom, co obsahuje a k čemu slouží (existují pomocné utilitky, které Vám tuto informaci vypíší např. u všech souborů v daném adresáři - pak je tímto komentářem usnadněno hledání). Prohlédněme si další řádky, které nepatří do těla žádné funkce.
Následují řádky začínající příkazem preprocesoru "#include" , který zajistí vložení příslušného souboru do programu. Jak bylo již několikrát vidět, zde se jedná o standardní soubory obsahující deklarace funkcí dodávaných ke kompilátoru (protože Céčko samo o sobě neumí skoro nic); "stdio.h" obsahuje deklarace funkcí sloužících pro vstup/výstup a "string.h" nám definuje funkce pro práci s řetězci.
A následují další příkazy pro preprocesor - "#define". Slouží k definici symbolických výrazů či maker. Preprocesor (jak jistě víte) před kompilací nahradí všechny výskyty definovaného symbolu tím, co za ním následuje (zde např. všechny výskyty MAX_DELKA budou v textu nahrazeny číslem 1000). Symbolické konstanty slouží mimo jiné ke zpřehlednění programu. Umožňují ale také snadné změny příslušného výrazu - pokud bychom chtěli aby maximální délka (označená MAX_DELKA) nebyla 1000, ale 500, nemusíme procházet cely program a zkoumat, která tisícovka se má nahradit číslem 500 - stačí přepsat hodnotu v příkazu "#define".
Kromě symbolu MAX_DELKA je zde definován i symbol ZNAME_OPERATORY - to souvisí s tím, co zatím naše kalkulačka bude umět. Zmíním se o tom právě nyní. Program "Kalkulacka" bude zatím spustitelný pouze z příkazové řádky. Parametrem bude nějaký číselný výraz obsahující pouze operace sčítání + a odčítání - (např. 5 + 3.14 - 10), čísla jsou bez exponentu. Program tento výraz vyhodnotí a vypíše výsledek - jednoduché, ale s něčím se začít musí. No, a je vidět, že ZNAME_OPERATORY je řetězec obsahující jediné povolené operátory: + a -. MAX_DELKA udává maximální délku zpracovávaného výrazu.
Mezi dalšími definicemi najdete řetězec obsahující jméno našeho programu jeho verzi a datum. Navíc je zde definována globální proměnná - ukazatel na řetězec - která ukazuje na zvláštní pole znaků: toto pole obsahuje nulový byte, pak text "$VER:" a poté název a verzi programu. Jedná se o identifikační řetězec, který umožňuje zjištění verze programu stačí pak třeba v CLI napsat příkaz "version Kalkulacka" (viz obrázek). A hle, vypsalo se jméno programu a jeho verze.
Posledním příkazem napsaným v úvodu programu (je hned za #include příkazy) je typedef. Jak již víte, můžete si s jeho pomocí nadefinovat svoje vlastní typy dat. My zde definujeme typ pro logickou hodnotu měl by obsahovat hodnoty typu pravda/nepravda. Zároveň jsou zde definovány symboly pro pravdu (TRUE = 1) a nepravdu (FALSE = 0). Je to samozřejmě symbolický zápis, který však zlepšuje čitelnost programu. Je jistě zřejmé, že je ve shodě s Céčkovským pojetím logických hodnot (nenulová hodnota = pravda, nulová hodnota = nepravda). Tento typ spolu s mnoha dalšími je definován v systémovém hlavičkovém souboru <exec/types.h>.

Jak to vlastně pracuje?
Podívejme se nyní na výkonnou část programu - skládá se ze dvou částí. První z nich je funkce ProjdiRetezec(), která slouží k vyhodnocení zadaného výrazu. O ní si povíme za chvíli. Druhá část je tvořena funkcí main(), která musí být obsažena v každém Céčkovském programu. Když svůj program spustíte (po kompilaci), pak po nezbytných počátečních inicializacích (které zařídí kód připojený během kompilace, nemusíte se o to starat) je zavolána právě funkce main(); je to jakýsi vstupní bod vašeho programu. Povězme si o ní tedy něco více.
Deklarace funkce main() má následující tvar:

int main(int argc, char *argv[]).

První parametr je číslo, které udává počet položek v příkazové řádce, jíž byl program spuštěn (argc = argument count = počet argumentů). V tomto čísle je obsažen i vlastní název programu. Druhý parametr je pole řetězců. Připomeňme si, že v Céčku je řetězec pole po sobě jdoucích znaků ukončené nulovým bytem. Pokud se s řetězci manipuluje stačí předávat pouze adresu prvního znaku řetězce ostatní následují za ním a konec poznáme. Jestliže tedy "argv" je pole ukazatelů na char (= znak), lze ho chápat jako pole řetězců (ty musí samozřejmě být uloženy někde v paměti, o to se zde ale nemusíme starat). Nezapomeňte, že pole jsou číslována od nuly a jméno pole zároveň reprezentuje adresu prvního prvku! Pokud tedy bude mít příkazová řádka tvar

Delete soubor1 soubor2 ALL,

pak "argc" bude rovno 4 a pole "argv" bude mít tvar { "Delete", "soubor1, "soubor2", "ALL" }.

Jistě Vás napadlo, jak to vypadá když se program spouští z Workbenche tam žádná příkazová řádka neexistuje. Většinu potřebných věcí zařídí opět kód přidaný kompilátorem. Některé kompilátory (nejen Céčka, ale i Pascalu) při spuštění z Workbenche zavolají funkci main() a počet parametrů "argc" položí roven nule. Jiné implementace definují pro spouštění z Workbenche dnou vstupní funkci (např. wbmain()) - pokud není definována, pak se při spuštění z Workbenche prostě nic nestane. Pokud Vám vrtá hlavou, k čemu je návratová hodnota u funkce main(), pak vězte, že je to chybový kód vrácený tomu, kdo program spustil většinou AmigaDOSu.
Nyní je snad jasné, jak se program dozví o zadaných parametrech. Podívejme se na vlastní kód funkce main(). Na počátku jsou deklarované reálné proměnná "vysledek" a pomocná proměnná "i" typu přirozené číslo (význam vyplyne dále). Dále je zde definováno pole znaků o délce MAX_DELKA. Toto pole bude obsahovat řetězec reprezentující vyhodnocovaný výraz, tedy posloupnost znaků ukončených nulovým bytem. Na počátku toto pole obsadíme prázdným řetězcem - "" (tj. jedním nulovým bytem).
Příkazy následující deklarace proměnných jsou více než jasné. Testujeme hodnotu argc na nulu (ošetření spouštění z WB) a na hodnotu 1 (pak příkazová řádka obsahovala pouze jméno programu, žádné parametry). K tomu nám slouží podmíněný příkaz "if". Tělo prvního podmíněného příkazu (if (argc == 0)) je tvořeno jediným příkazem, tělo druhého podmíněného příkazu je složený příkaz blok, který vytiskne hlášení a ukončí program. Jak už jistě víte návrat z funkce je realizován příkazem "return". Navíc zde používáme jednu z funkcí deklarovaných v souboru <stdio.h> printf() - pro formátovaný výstup; zde slouží pouze k výpisu řetězce (připomínám, že znak " " představuje konec řádku).
V další části musíme spojit jednotlivé parametry do jednoho řetězce protože celou příkazovou řádku považujeme za příkaz k vyhodnocení. Výsledek si dáme do připraveného pole "vyraz" . Využíváme zde for-cyklus - for(výraz1; výraz2; výraz3). Jak jsme si řekl minule, výraz1 se provede jen poprvé (přiřadí do i číslo 1), výraz2 je podmínka cyklu (dokud i < argc, tj. dokud jsme neprošli všechny parametry), výraz3 se provede po každém provedení těla cyklu (zde zvyšuje hodnotu i o 1). Tělo cyklu je tvořeno podmíněným příkazem. Podmínka testuje, zda je v poli vyraz dost místa na připojení dalšího řetězce (délka toho, co tam už je + délka připojovaného + 1 (na nulový byte)). K tomu využíváme funkci "strlen()", která je deklarována v souboru "string.h" a určuje počet znaků řetězce (bez koncového nulového bytu). Jak je vidět, je-li podmínka splněna (je dost místa), provede se připojení opět pomocí knihovní funkce "strcat()" - první parametr určuje, kam se bude připojovat, druhý parametr určuje, co se bude připojovat. Pokud podmínka splněna není, přeruší se provádění cyklu příkazem "break" (viď minule).
Pak už stačí zavolat jen vyhodnocovací funkci ProjdiRetezec() a vytisknout výsledek. Funkce ProjdiRetezec() vrací hodnotu typu BOOL - úspěch (= TRUE) nebo neúspěch (= FALSE, něco se pokazilo). Má dva parametry -ukazatel na vyhodnocovaný výraz a adresu proměnné, do níž má uložit výsledek. Všimněte si předávání hodnoty výsledku - protože se parametry funkcí předávají hodnotou, vytvoří se při volání funkce proměnná potřebného typu, do níž se uloží předávaný parametr a která po skončení volané funkce zmizí. Pokud tedy chceme aby nám funkce ProjdiRetezec() dosadila do "vysledek" nějakou hodnotu, musíme jí předat adresu proměnné (aby věděla, KAM má psát). K získání adresy objektu lze použít operátor "&" (jak víme z předchozích dílů). Když víme, jak získat výsledek, stačí už jen vytisknout - zde poznamenám pouze tolik, že kombinace "%s" ve formátovacím řetězci pro funkci printf() vytiskne příslušný řetězec uvedený jako parametr a " %g" vytiskne reálné číslo.
A na závěr si povíme něco o funkci pro vlastní vyhodnocení výrazu:

BOOL ProjdiRetezec(char *vyraz, double *vysledek).

Parametry této funkce jsou (jak jsme již řekli) řetězec = vyhodnocovaný výraz a adresa, kam se má zapsat určený výsledek. Lokální proměnné jsou čtyři - vysl (do ní si průběžně budeme zapisovat mezivýsledek součtu), operand (tam budeme dávat hodnotu právě načteného čísla), operator (obsahuje znak naposledy přečteného operátoru - zatím jen + nebo-) a end (booleovská proměnná určující, zda jsme již prošli celý výraz).
Tělo funkce je tvořeno především while-cyklem (dělej, dokud platí podmínka) - zde je podmínka "negace end" (! je negace), tj. dokud nedojdeme na konec. Vyhodnocení je založeno na tom, že nejprve musí být ve výrazu číslo, pak operátor, a tak stále dokola (na konci je opět číslo).
V cyklu se proto vždy pokusíme nejprve načíst číslo. To udělá funkce "sscanf()", opět z <stdio.h>. Tato funkce se chová stejně jako standardní funkce pro formátovaný vstup "scanf()" s tím rozdílem, že data čte ze zadaného řetězce (a to se nám hodí). Parametrem je tedy vstupní řetězec, formátovací řetězec (obsahuje kódy stejné jako printf() - když se pomocí %g vypíše reálné číslo, pak se tím i načte) a adresy proměnných, kam se mají výsledky načíst (je to stejný mechanismus jako při předávání výsledku z této funkce do main()). Funkce sscanf() přeskočí i případné počáteční mezery. Návratová hodnota určuje, do kolika proměnných se něco načetlo, resp. je -1, pokud nešlo nic načíst. Pokud tedy vrácené číslo ze sscanf() není 1, nepodařilo se číslo načíst (byla tam např. písmena nebo konec řetězce) a považujeme to za chybu. Samozřejmě to oznámíme pomocí printf() a ukončíme funkci pomocí příkazu "return" - vracíme hodnotu FALSE, neboť se nepovedlo rozebrat korektně celý řetězec. Pozn.: k funkcím jako scanf() a printf() se někdy vrátíme a popíšeme je podrobněji.
Jestliže jsme načetli do "operand" další číslo, pokusíme se najít další operátor (pokud už tam žádný není, jsme na konci). K tomu nám opět poslouží knihovní funkce "strpbrk()", která v prvním řetězci (vyraz) vyhledá první výskyt libovolného znaku obsaženého v druhém řetězci (ZNAME_OPERATORY) - najde tedy další plus nebo minus. strpbrk() vrací ukazatel na nalezený znak; pokud nic nenajde, vrací NULL, což je v jazyce C symbolická hodnota pro ukazatel, který nikam neukazuje (nemá přiřazenu adresu).
Vše zbývající je už jednoduché - podle toho, jaký operátor předcházel, přičteme resp. odečteme načtený operand. Poté buď nastavíme end na TRUE (vyraz == NULL-> chceme ukončit cyklus, protože už nic nenásleduje), nebo si zapamatujeme nový operátor (je na adrese určené ukazatelem "vyraz" a obsah adresy získáváme pomocí operátoru "*") a posuneme se na další znak (pomocí vyraz++).
Všimněte si, jak se v cyklu manipuluje s ukazatelem vyraz - na počátku je v něm adresa prvního znaku řetězce; můžeme si jej představit jako nějaké ukazovátko na řadu políček (pole znaků) za sebou, přičemž toto ukazovátko lze libovolně posunovat (a to taky děláme). Funkce strpbrk() nám řekne, kam ho posunout, aby ukazovalo na další "+" nebo "-". Když si pomocí příkazu (operator=vyraz) vyzvedneme znak, na který vyraz ukazuje, posuneme ho za tento znak příkazem vyraz++ - tj. posuneme ukazovátko o jednu pozici dále, na další znak v řetězci. Funkce sscanf() pak už nedostane ukazatel na počátek řetězce (jako poprvé), ale ukazatel na počátek zatím nezpracované části řetězce.
A abychom nezapomněli - na závěr (po ukončení cyklu) se zapíše na dodanou adresu výsledek (aby ho mohla použít funkce main()) - všimněte si, že operátor "*" aplikovaný na ukazatel reprezentuje věrně obsah dané adresy, lze na ní takto i přiřazovat.

/* Calculator.c projekt pro kurs C v Amiga Review */

/* potřebné hlavičkové soubory */
#include <stdio.h>
#include <string.h>

/* definice booleovského typu i s hodnotami; standardně je definován v <exec/types.h> */
typedef short BOOL;
#define TRUE 1
#define FALSE 0

/* definice používaných symbolů */
#define MAX_DELKA 1000
#define ZNAME_OPERATORY "+-"

/* řetězec identifikující program a jeho verzi */
#define VERZE_KALKULACKY "Kalkulačka 1.00 (5. 9. 1995)"
char *VERSION = "$VER:"VERZE_KALKULACKY;

/* funkce, která projde řetězec s výrazem a určí jeho hodnotu; je-li výraz chybný, vrátí FALSE; v opačném případě vrací TRUE a výsledek uloží do proměnnévysledek */
BOOL ProjdiRetezec(char *vyraz, double *vysledek)
{
/* pomocné reálné proměnné pro vyhodnocení výrazu */
double vysl = 0, operand;
/* operator bude obsahovat značku posledního operátoru; zatím umíme jen +, - */
char operator =+;
/* booleovská proměnná - máme již skončit? */
BOOL end = FALSE;
while (!end)
{ /* mělo následovat číslo (případně předcházené mezerami apod.), přečteme jej; */
if (0 >= sscanf(vyraz, "%g", &operand))
/* nenásledovalo číslo, ale něco jiného=chyba ve výrazu */
{ printf("Chyba v zadaném výrazu! ");
return(FALSE);
}
/* nalezneme další operátor v řetězci */
vyraz=strpbrk(vyraz, ZNAME_OPERATORY);
/* pokud odčítáme, vynásobíme operand -1 a přičteme ho -- zatím nic jiného než sčítání a odčítání neumíme */
if (operator ==-) operand *=-1;
vysl += operand;
if (vyraz == NULL)
/* nebyl nalezen žádný další operátor = konec výrazu */
end = TRUE;
else
/* v opačném případě si nový operátor zapamatujeme a posuneme se na další znak znak za operátorem */
{ operator=*vyraz;
vyraz++;
}
}
/* vyhodnotili jsme úspešně výraz -> vrátíme výsledek volající funkci */
*vysledek = vysl;
return(TRUE);
}
/* hlavní funkce programu = vstupní bod */
int main(int argc, char *argv[])
/* argc udává počet argumentů = řetězců v příkazové řádce */
/* argv představuje pole řetězců = slov v příkazové řádce */
/* návratová hodnota je chybový kód vrácený AmigaDOSu */
{
/* proměnná navy sledek výpočtu */
double vysledek;
/* pomocna celočíseÍná proměnná */
int i;
/* pole znaků = řetězec; na počátku je prázdný */
/* samotné jméno vyraz pak reprezentuje adresu pole = řetězce */
char vyraz[MAX DELKA] = "";
/* zatím neumíme startovat z Workbenche */
if (argc == 0) return(20!);
/* požadujeme, aby byl zadán výraz k vyhodnocení */
if (argc == 1)
{ printf("Nebyly zadány žádné argumety! );
printf("Není co počítat... ");
return(5);
}
/* všechny parametry spojíme do jednoho řetezce */
/* testujeme, zda je v poli vyraz dost místa; pokud je již zaplněno, přerušíme připojování */
for(i = 1; i < argc; i++)
if ( strlen(vyraz) + strlen(argv[i]) + 1 < MAX_DELKA)
strcat(vyraz, argv[i]);
else break;
/* ve vyraz je nyní celý výraz určený ke spočtení > spočteme a vytiskneme zadaný výraz a výsledek výpočtu */
if (ProjdiRetezec(vyraz, &vysledek))
printf("Výsledek: %s = %G " vyraz vysledek);
/* vše proběhlo korektně */
return(0);
}

Vytlačiť článok


© ATLANTIDA Publishing Všechna práva vyhrazena.
Žádna část nesmí být reprodukována nebo jinak šířena bez písemného svolení vydavatele.



Amiga na Vašem PC rychle, snadno a zdarma!


none

AMIGA REVIEW

57 ( 11-12 / 2000 )
56 ( 9-10 / 2000 )
55 ( 7-8 / 2000 )
54 ( 5-6 / 2000 )
53 ( 3-4 / 2000 )
52 ( 1-2 / 2000 )
 
51 ( 12 / 1999 )
50 ( 11 / 1999 )
49 ( 10 / 1999 )
48 ( 9 / 1999 )
46-47 ( 7-8 / 1999 )
45 ( 6 / 1999 )
44 ( 5 / 1999 )
43 ( 4 / 1999 )
42 ( 3 / 1999 )
41 ( 2 / 1999 )
40 ( 1 / 1999 )
 
39 ( 12 / 1998 )
38 ( 11 / 1998 )
37 ( 10 / 1998 )
36 ( 9 / 1998 )
35 ( x / 1998 )
34 ( x / 1998 )
33 ( 1-2 / 1998 )
 
32 ( 11-12 / 1997 )
31 ( 9-10 / 1997 )
30 ( 7-8 / 1997 )
29 ( 6 / 1997 )
28 ( 5 / 1997 )
27 ( 4 / 1997 )
26 ( 3 / 1997 )
25 ( 2 / 1997 )
24 ( 1 / 1997 )
 
23 ( 12 / 1996 )
22 ( 11 / 1996 )
21 ( 10 / 1996 )
20 ( 9 / 1996 )
18-19 ( 7-8 / 1996 )
17 ( 6 / 1996 )
16 ( 5 / 1996 )
15 ( 4 / 1996 )
14 ( 3 / 1996 )
13 ( 2 / 1996 )
12 ( 1 / 1996 )
 
11 ( 12 / 1995 )
10 ( 11 / 1995 )
9 ( 10 / 1995 )
8 ( 9 / 1995 )
7 ( 7 / 1995 )
6 ( 5 / 1995 )

ATLANTIDA NEWS

5 ( 3 / 1995 )
4 ( 1 / 1995 )
 
3 ( 11 / 1994 )
2 ( 9 / 1994 )
1 ( 7 / 1994 )
0 ( 5 / 1994 )