1. Elementy.

1.1 Prosty program w C. Funkcja a procedura.

   Moze na poczatek pare uwag: jezyk C rozroznia male i duze litery.
   Instrukcje zwykle koncza sie srednikiem (ale blok juz nie).

/* To co jest miedzy tymi znakami / jest komentarzem */
/* ale uwaga: komentarz sie zaczyna i konczy sekwencjami dwuznakowymi,
   sama * ani sam / nie jest znakiem poczatku ani konca komentarza. */

#include <stdio.h> /* To kaze kompilatorowi wlaczyc w tym miejscu
   plik "stdio.h" z katalogu "include", tak jakby byl on tu wpisany.
   Wiecej o dyrektywach bedzie w 1.5; dyrektywa NIE MA srednika */

int main(void) { /* Funkcja (w C procedura to tez funkcja, o wyniku
   typu "void", co oznacza "nic") main musi byc w kazdym programie
   w C - chyba ze nie chcemy uzywac standardowej biblioteki ani kodu
   startujacego, ale to juz do specjalnych zastosowan. Jesli program
   sklada sie z wielu plikow zrodlowych, to funkcje main powinien miec
   tylko jeden z nich. Zwykle deklaruje sie, ze ta funkcja jest typu
   int (integer), choc DOS bierze z tego wyniku tylko dolne 8 bitow.
   Nawias po nazwie oznacza, ze to funkcja. Slowo void w nawiasie, ze
   nie ma parametrow (jego brak oznaczalby, ze nie jest to okreslone).
   Ogolna postac funcji: typ nazwa(parametry) { tresc }.
   Nawiasy klamrowe {} oznaczaja blok (wewnatrz nich umieszcza sie
   tresc funkcji, rowniez wewnatrz takich nawiasow mozna umiescic
   wiele instrukcji tam, gdzie bez tego moglaby byc tylko jedna.
   O mozliwych parametrach funkcji main bedzie nieco dalej, w 1.2. */

  printf("to nie jest elegancki jezyk programowania\n"
         "ale za to malo jest roboty z pisaniem programu");
  /* printf jest funkcja z biblioteki C, ktora formatuje i wypisuje
     na standardowe wyjscie (stdout), pierwszym jej parametrem jest
     jest format - i moze to byc po prostu tekst do wypisania, tylko
     bez znakow '%', bo taki znak zaczyna sekwencje sterujaca - jak
     juz trzeba go wypisac, trzeba napisac %%, wypisze sie jeden %.
     Uzycie sekwencji sterujacej wymaga dodatkowych parametrow, ktore
     beda potraktowane w sposob opisany przez te sekwencje. Jest pare
     odmian tej funkcji, ktore dzialaja nieco inaczej - przed formatem
     moze byc dodatkowy parametr, rozne moga byc sposoby przekazywania
     dalszych parametrow - ale format jest zawsze taki sam, i kazda ma
     w swojej nazwie "printf". Bardziej szczegolowo bedzie w 1.3. */

  return(0); /* To jest najprostszy sposob zakonczenia programu -
     instrukcja return z funkcji main. Wartosc w nawiasie podaje jaka
     ma byc wartosc funkcji, i DOS pozwala ja testowac w poleceniu
     "if errorlevel ..." w plikach .BAT (w zakresie 0-255). Jesli typ
     funkcji jest void, nalezy uzyc return bez wartosci funkcji. */

} /* i to juz koniec funkcji main i pierwszego programu */

1.2 Parametry funkcji main() i jak wygladaja tablice w C.

   Moze na poczatek ogolnie o deklaracji i parametrach funkcji w C:

     Trzeba rozroznic deklaracje i definicje funkcji - deklaracja ma
     postac: typ nazwa(parametry); (srednik po nawiasie zamykajacym)
     i nie zawiera tresci funkcji; w definicji zamiast srednika jest
     { tresc }. Jesli funkcja byla deklarowana, definicja musi byc
     zgodna z deklaracja. Deklaracja jest niezbedna, jesli wywolanie
     funkcji poprzedza jej definicje, ale warto deklarowac wszystkie
     funkcje gdzies na poczatku, zeby program byl bardziej czytelny.
     Mozna zadeklarowac funkcje nie deklarujac parametrow - piszac
     po nazwie pusty nawias - i kiedys w C deklarowalo sie funkcje
     tylko w taki sposob - ale lepiej jest podawac parametry.

     C przekazuje parametry przez stos, odkladajac najpierw ostatni,
     na koncu pierwszy - w ten sposob w pamieci sa one w kolejnosci
     w jakiej wystepuja przy wywolaniu. Przekazywac mozna wartosc
     lub adres, ale w przypadku gdy parametr jest tablica - zawsze
     przekazuje sie adres; rowniez adres zostanie przekazany, jesli
     jako parametr poda sie nazwe funkcji bez nawiasu (jesli poda
     sie z nawiasem, to funkcja zostanie wywolana podczas wylicznia
     parametrow i zwrocona wartosc bedzie parametrem). Nie zaleca
     sie rowniez przekazywania przez wartosc typow zlozonych.

     Lista parametrow - podaje sie typ, nazwe (w deklaracji mozna ja
     pominac), i oddziela przecinkami poszczegolne parametry. Jesli
     jest pusta, wpisuje sie slowo void (brak czegokolwiek w nawiasie
     oznacza - w deklaracji - niepodanie parametrow, a nie ich brak).
     Kiedys (kompilatory C nadal to przyjmuja) podawano parametry tak:
       typ_funkcji nazwa_funkcji(nazwa_parametru1,nazwa_parametru2)
         typ_parametru1 nazwa_parametru1;
         typ_parametru2 nazwa_parametru2;
         { tresc funkcji }
     ale wtedy zadeklarowane typy parametrow nie sa przekazywane poza
     funkcje; zeby przekazywac je i sprawdzac zgodnosc z deklaracja,
     pisze sie (i dla C++ jest to wymagane) tak:
       typ_funkcji nazwa_funkcji(
         typ_parametru1 nazwa_parametru1,
         typ_parametru2 nazwa_parametru2)
         { tresc funkcji }
     tyle, ze w starej wersji jesli kilka parametrow bylo tego samego
     typu mozna bylo ich typ podac razem, a w nowej nie.

     Jesli do funkcji przekazuje sie adres, trzeba to zadeklarowac.
     Jesli przekazuje sie tablice, mozna zadeklarowac parametr tak,
     jak deklaruje sie tablice (np. int tablica[3]), albo jako adres
     elementu tablicy (np. int *tablica). Bardziej skompilowane jest
     deklarowanie funkcji jako parametru - jako wskaznika do funkcji
     - i rowniez jej uzycie. Wyglada to np. tak:
       int fun1(int (*fun)(int),int arg) { return((*fun)(arg)); }
     - uzycie gwiazdki w deklaracji parametru powoduje zadeklarowanie
     wskaznika, w tresci uzycie fun jako adresu, bez nawiasu wokol
     *fun kompilator zrozumialby fun jako funkcje, ktorej wartosc jest
     wskaznikiem do typu int, a nie jako wskaznik do funkcji typu int.

     Zeby przekazac parametr do funkcji, trzeba przy wywolaniu:
     - jesli parametr przekazuje sie przez wartosc, podac na danej
       pozycji wartosc, badz wyrazenie (np. nazwe zmiennej), ktora
       ma byc przekazana do funkcji;
     - jesli przez adres, a dany obiekt nie jest ani tablica, ani
       funkcja, trzeba jego nazwe poprzedzic znakiem &.
     Ten podzial nie jest scisly: przekazany adres moze byc wynikiem
     wyliczenia jakiegos wyrazenia, i wtedy w wywolaniu funkcji moze
     wystapic wyrazenie, choc w deklaracji parametru jest adres.
     Raczej nalezy myslec o tym jako o typie przekazywanej wartosci
     - czy jest to typ wskaznikowy (i do jakiego typu), czy nie.

   W pelnej wersji funkcja main() moze miec 3 parametry:

     main(int arg_count, char *arg_vector[], char *env_strings[])

   Pierwszy z nich jest typu int (w C deklaracja zmiennej podaje
   najpierw typ, a potem nazwy) i podaje, ile napisow jest w tablicy
   arg_vector, bedacej drugim parametrem. Jako pierwszym z napisow
   jest przekazywana nazwa programu - zwykle z pelna sciezka, dalsze
   sa kolejnymi napisami z linii wywolania programu - jesli jakies sa.
   Trzeci przekazuje, w analogiczny sposob, ale bez podania ilosci,
   napisy ze srodowiska (environment). Te tablice zawieraja wskazniki
   do napisow, i po ostatnim z nich jest zawsze wskaznik NULL - pusty.
   Deklaracja typu (zamiast *nazwa[] mozna tu rownie dobrze napisac
   **nazwa, * przed nazwa oznacza ze jest ona traktowana jako wskaznik
   i wartoscia ma byc to, co jest wskazywane, [] po nazwie oznacza ze
   jest to tablica, i jesli chodzi o uzywanie tego - nie ma roznicy)
   oznacza, ze sa to tablice o nieokreslonej dlugosci, ktore zawieraja
   wskazniki do typu char (=znak). Taki wskaznik moze byc potraktowany
   jako tablica - tu uwaga: w C tablice indeksuje sie od zera, dlatego
   arg_vector[1] tym, co uzytkownik nazywa pierwszym parametrem, i np.
   arg_vector[0][1] jest drugim znakiem z nazwy programu - zwykle ':'
   (napisy sa reprezentowane w taki sposob, ze wskaznik pokazuje ich
    pierwszy znak, a po ostatnim jest zero, oznaczajace jego koniec).

   Roznice miedzy uzyciem * i [] najlepiej widac po takim przykladzie:
   char *napis1="Ala ma kota"; - deklaruje zmienna napis1, ktora jest
      wskaznikiem do napisu "Ala ma kota", i w pamieci komputera jest
      wpisany adres takiego napisu (ta forma deklaracji nadaje jej
      wartosc poczatkowa), natomiast:
   char napis2[]="Ala ma kota"; - deklaruje tablice zawierajaca napis
      "Ala ma kota" (ten napis jest jej wartoscia poczatkowa), i adres
      napisu nigdzie sie nie zapisuje; dlugosc tablicy jest dlugoscia
      napisu plus 1 (na konczace 0).
   Pierwsza roznica jest taka, ze druga forma zajmie w sumie mniej
   miejsca - o jeden wskaznik, druga taka, ze uzycie nazwy zmiennej
   co innego oznacza - w pierwszym przypadku bierze wartosc zmiennej,
   w drugim adres; na napis1 mozna podstawic adres innego napisu przez
   proste: napis1="Ala ma Asa", natomiast na napis2 mozna co najwyzej
   przepisac cos innego, i jeszcze trzeba uwazac, zeby sie zmiescilo.
   Niemniej jednak, jesli uzyje sie napis1 lub napis2 jako parametru
   np. funkcji printf() - wynik bedzie dokladnie taki sam.
   W przypadku parametru funkcji main() jeszcze jedna roznica, ktora
   powoduje, ze nieco wygodniej jest uzywac char **arg_vector, jesli
   program ma jakies parametry modyfikujace jego dzialanie (czesto
   podaje sie je ze znakiem '-' na poczatku, ale to juz kwestia umowy,
   mozna sobie napisac program z opcjami, i +opcja uzywac do wlaczenia,
   -opcja do wylaczenia), ktore sa podane na poczatku listy parametrow,
   i ich ilosc moze byc rozna, a po nich nastepuja pozostale parametry
   w stalej kolejnosci - mozna przetworzyc je na poczatku, a nastepnie
   zwiekszyc arg_vector o ich ilosc tych opcji, i miec stale parametry
   w ustalonych miejscach tablicy arg_vector. A z tablica sie nie da.

1.3 Funkcje printf(), scanf() i ich odmiany.

   printf(char *format, parametr1, parametr2, ...)
   vprintf(char *format, tablica_parametrow)
   fprintf(FILE *stream, char *format, parametr, ...)
   vfprintf(FILE *stream, char *format, tablica_parametrow)
   sprintf(char *bufor, char *format, parametr, ...)
   vsprintf(char *bufor, char *format, tablica_parametrow)
   cprintf(char *format, parametr1, parametr2, ...)
   (przydalaby sie do kompletu vcprintf, ale chyba takiej nie ma)

   scanf(char *format, &parametr1, &parametr2, ...)
   (i taki sam zestaw z dodanymi v,f,vf,s,vs,c na poczatku)

   Funkcje o nazwach zaczynajacych sie od 'v' potrzebuja jednego
   parametru po formacie - jest on tablica zawierajaca parametry,
   ktore dla innych funkcji musza byc wyliczone (o ile jakies sa).
   'f' przed "printf" oznacza, ze zamiast na stdout pisze sie na
   stream (i w szczegolnosci mozna tam napisac stdout). 's' oznacza,
   ze wynik formatowania ma byc wpisany do tablicy znakow, dobrze
   zeby sie zmiescil, bo sprintf nie ma jak tego sprawdzic. Wartoscia
   kazdej z tych funkcji jest ilosc wypisanych znakow, a w wypadku
   bledu pisania (nie w 'c' i 's') - stala EOF. Sa one zdefiniowane
   w stdio.h, z wyjatkiem cprintf, zdefiniowanej w conio.h.

   Funkcje printf(),vprintf(),fprintf() mozna latwo zdefiniowac
   uzywajac vfprintf() - przekazujac stdio jako pierwszy parametr,
   jesli definiowana funkcja nie jest fprintf (wtedy przekazuje sie
   pierwszy parametr taki jaki jest), i adres nastepnego parametru
   po formacie jako trzeci parametr, jesli nie jest nia vprintf.

   Funkcje bez 'v' maja zmienna ilosc parametrow - robi sie to tak:
     int printf(const char *format,...) {
       return(vfprintf(stdio,format,1+&format));
     }

   Wielokropek (...) oznacza, ze moze byc wiecej parametrow, i funkcja
   sama sie zorientuje, ile ich jest. A znak & oznacza wziecie adresu,
   w tym wypadku parametru format. Do tego adresu dodaje sie jedynka,
   i tu ujawnia sie kolejna cecha C: dodawanie do adresu zwieksza go
   nie o wartosc dodawana, ale dziala tak, jak indeksowanie tablicy,
   pokazuje nastepny element - czyli 1+&format jest adresem tego, co
   znajduje sie po wskazniku do formatu - a tam sa dalsze parametry.
   Slowo 'const' oznacza, ze parametr ma byc stala - funkcja nie moze
   go zmieniac (o ile w samej funkcji nie jest zadeklarowany inaczej).

   I chyba trzeba powiedziec przy okazji, jak przekazuje sie parametry
   do funkcji: sa one odkladane na stos, zaczynajac od ostatniego, tak
   ze funkcja je dostaje w pamieci w takiej kolejnosci, w jakiej sa
   napisane (odkladanie na stos odwraca kolejnosc).

   Funkcja scanf() i jej odmiany robia cos odwrotnego do printf() -
   rozpakowuja dane, przeczytane np. ze standardowego wejscia (stdin),
   i umieszczaja je w zmiennych. Ale maja nieco niedogodnosci.
   Rzecz w tym, ze jesli funkcji scanf() kazemy przeczytac liczbe -
   to albo zasygnalizuje blad, jesli trafi na cos, co nie jest liczba,
   albo bedzie wczytywac kolejne linie, jesli beda puste, i nie jest
   na tyle "madra", by czytajac z klawiatury rozpoznac spacje jako
   koniec wczytywanej liczby - musi byc koniec linii i juz. Z tych
   powodow lepiej wczytywac cala linie do bufora i uzywac sscanf() -
   przynajmniej nie bedzie zadnych niespodzianek, a jak bedzie blad,
   to mozna pokazac, co sie przeczytalo i zobaczyc, co bylo zle.
   No, chyba ze wiadomo dokladnie jak bedzie wygladalo to, co ma byc
   czytane i mamy pewnosc, ze nie bedzie z tym klopotow...

   A teraz nieco o formacie (nie wszystko, bo tego jest dosc na spora
   ksiazke). Moze na poczatek o tym, jak w C podaje sie stala typu
   napis (char *) - tekst napisu umieszcza sie miedzy znakami ". A co
   jesli taki znak jest potrzebny w napisie? Jest sposob: piszemy \".
   Jak potrzebny \ - piszemy \\ (mozna sie pomylic przy nazwach plikow
   w DOS-ie - kompilator zinterpretuje po swojemu pojedyncze \-e!).
   Podobny jest sposob, by podawac stale typu char - podaje sie w ',
   np. 'a', i jak trzeba podac apostrof, to tak: '\''. Ale znak \ ma
   tez inne zastosowanie: pozwala podawac znaki specjalne, np. \n to
   znak przejscia do nowej linii (LF), \r to powrot do poczatku linii
   (CR), \t to tabulator, \b - backspace, \a - alarm. A co zrobic,
   jesli jest potrzebny jakis inny znak specjalny? Zawsze mozna podac
   jego kod, np. \012 oznacza kod 12 oktalnie (czyli 10, a wiec \n),
   nalezy podawac 3 cyfry, chyba ze nastepny znak nie jest cyfra,
   mozna tez heksadecymalnie, np. \x12 oznacza kod 12 hex (czyli 18).

   Sekwencje sterujace zaczynaja sie od znaku % (i jesli potrzeba taki
   znak wypisac, powtarza sie go 2 razy (i to juz jest co innego, niz
   ze znakiem \, sekwencja z \ jest przetwarzana przy kompilacji na
   pojedynczy znak, a %% po skompilowaniu pozostaje %% i dopiero przy
   wypisywaniu zamienia sie na pojedynczy). Potem moga byc rozne znaki
   modyfikujace format, i na koniec znak okreslajacy rodzaj konwersji.

   Najwazniejsze z tych znakow to: d=i=dziesietnie ze znakiem, u=bez
   znaku (unsigned), o=oktalnie, x=heksadecymalnie (mozna i X, bedzie
   duzymi litarami przy wypisywaniu, a wczyta na long przy czytaniu),
   s=napis(string), c=znak(char), f=zmiennoprzecinkowo (z .), e tez,
   tylko z wykladnikiem (np. 1.23e12), g=f lub e, G i E jak g i e,
   p=wskaznik (w zaleznosci od modelu near lub far, ale mozna wymusic
   jaki sie chce przez modyfikator F/N), n=ilosc znakow przetworzonych
   ma byc wpisana do zmiennej, ktorej adres jest parametrem, przydaje
   sie zeby przy czytaniu liczby dowiedziec sie, ile miala cyfr...

   Modyfikatory: l=long (dla calkowitych), double przy czytaniu liczb
   zmiennoprzecinkowych, h=short (dla calkowitych) - tych uzywa sie
   bezposrednio przed typem konwersji, przedtem moze byc opis rozmiaru
   pola - liczba podajaca ilosc znakow, i po kropce dodatkowa liczba,
   moze ona podawac ilosc znakow po kropce, minimalna ilosc znakow dla
   liczby calkowitej (%4.3d wypisze minimum 3 cyfry w polu 4 znakowym),
   maksymalna ilosc znakow z napisu (np. %.7s), a na poczatku moze byc
   + (zeby zawsze byl znak, nawet dodatniej), - (przesuniecie w lewo,
   uzyteczne dla napisow, jak chcemy w polu 20 znakow umiescic napis,
   a nie wiadomo ile on ma - uzywamy %-20.20s i zawsze dobrze wyjdzie),
   mozna jeszcze uzyc # (inna forma) lub * zamiast liczby podajacej
   ilosc znakow (ktorakolwiek) - to wezmie wartosc z parametru...

   Warto zapamietac przynajmniej formaty d, u, x, c, s, modyfikator
   l, i sposob z wypisywaniem napisu - to zawsze sie moze przydac...

   Jak przekazywac parametry? Do printf() zwykle przez wartosc, poza
   formatem %n, ktory wymaga adresu zmiennej typu int (i o to trzeba
   sie postarac, piszac & przed nazwa zmiennej), i %s, ktory wymaga
   adresu napisu (a to akurat wyjdzie samo, jak sie poda zmienna typu
   char *, albo nazwe tablicy - w C tablic nie mozna przekazywac przez
   wartosc, i jest przekazywany adres). Do scanf() - wszystko przez
   adres (chyba ze jest * w modyfikatorze formatu, ale po co?), wiec
   zwykle trzeba pisac & przed nazwami zmiennych, na ktore ma byc cos
   wczytane - wyjatkiem sa oczywiscie typy wskaznikowe (np. char *),
   jesli wartosc zmiennej jest adresem, pod ktory mamy wczytac.

   Wartoscia funkcji printf i podobnych jest ilosc wypisanych znakow,
   wartoscia scanf jest ilosc poprawnie przetworzonych parametrow
   (nie wlicza sie do nich zapytanie o ilosc znakow). Zeby sprawdzic,
   ze przetworzony napis byl liczba i nie bylo po niej nic wiecej,
   mozna uzyc funkcji sscanf z formatem np. "%ld%c", podajac jako
   kolejne parametry adresy zmiennych typu long i int, i sprawdzajac,
   czy wynik jest 1 (0 oznacza brak liczby, 2 jakies znaki po niej),
   lub "%ld%n", wynik powinien byc 1, i zmienna typu int powinna
   przybrac wartosc rowna dlugosci napisu (napis[zmienna]==0).

1.4 Stale, zmienne i typy.

   Tak jak wiekszosc jezykow programowania, C posiada typy zmiennych
   calkowitych (char, int, short, long) i zmiennoprzecinkowych (float,
   double, long double). Zakres calkowitych moze zalezec od komputera
   i od uzywanego kompilatora, w zasadzie przyjmuje sie, ze:
     char jest typem calkowitym o zakresie najbardziej odpowiednim na
       pojedynczy znak tekstu, dajacym sie adresowac - zwykle adresy
       kolejnych elementow tablicy tego typu roznia sie o 1;
     int jest typem najbardziej odpowiednim dla procesora do operacji
       na liczbach calkowitych;
     long jest nie mniejszy od int, i nie mniejszy niz 32-bitowy
     short jest nie wiekszy od int, zwykle 16-bitowy.
   Zwykle liczby calkowite sa ze znakiem (mozna podac, ze char ma byc
   bez znaku, ale lepiej tego unikac, bo cos moze zaczac zle dzialac),
   mozna nazwe typu poprzedzic przez unsigned i uzyskac typ bez znaku.
   Dla wiekszosci kompilatorow na PC zakresy zmiennych calkowitych sa:
     typ    unsigned       ze znakiem
     char   0..255         -128..127
     int    0..65535       -32768..32767
     short  0..65535       -32768..32767
     long   0..4294967295  -2147483648..2147483647
   ale dla 32-bitowych zakres int moze byc taki, jak long.
   Jezeli napisze sie jakas liczbe, kompilator zwykle uznaje, ze ma
   ona typ int. Jesli jest istotne, aby miala jakis inny, mozna uzyc
   litery L po liczbie, aby byla long, U zeby byla unsigned, lub LU
   zeby byla unsigned long. Moze to miec znaczenie przy porownaniach,
   latwo zrobic blad deklarujac zmienna jako unsigned i sprawdzajac,
   czy ma wartosc -1 (moze miec -1U, ale -1 nie). A jesli jest bardzo
   wazne, by miec typ char lub unsigned char - trzeba rzutowac typ.
   Wyliczajac wyrazenie calkowite kompilator dla kazdego dzialania
   wybiera typ, zamienia operandy na ten typ, i wtedy wylicza. Jesli
   ktorys z operandow jest typu krotszego od int - zamieni go na int,
   nastepnie jesli ktorys jest long - zamieni drugi na long, i jesli
   teraz ktorys jest unsigned - potraktuje drugi jako unsigned. Tyle
   teorii, praktyka pokazuje, ze suma dwoch unsigned jest mniejsza od
   zera, jesli nastapilo przeniesienie, ale unsigned long juz nie.

   Co do zakresu zmiennoprzecinkowych sa jakies standardy:
     float jest typem 32-bitowym (3.4e-38..3.4e+38)
     double jest typem 64-bitowym (1.7e-308..1.7e+308)
     long double nie jest standardem, jak jest na PC to 80-bitowy
     (3.4e-4932..1.1e+4932; zakresy sa podane we FLOAT.H).

   Przy dzialaniach, jesli jest argument zmiennoprzecinkowy, oba sa
   zamieniane na double (na PC na long double; jesli byl krotszy od
   int, jest zamieniany poprzez int). Wynik zwykle zamienia sie na
   double (jesli np. ma byc uzyty jako parametr funkcji, a nie jest
   okreslone jaki typ parametru jest wymagany).

   Od kazdego typu mozna utworzyc wskaznikowy, piszac w deklaracji
   zmiennej gwiazdke przed nazwa zmiennej (nieco to sie komplikuje,
   jesli typem zmiennej ma byc adres funkcji), mozna tez tworzyc
   adres do zmiennej wskaznikowej piszac dwie gwiazdki...

   Mozna definiowac wlasne typy strukturalne - wyglada to np. tak:
     struct nazwa_struktury { typ1 zmienna1,zmienna2; typ2 zmienna3;
     } nazwa_s1,nazwa_s2; - cos takiego zadeklaruje zmienne nazwa_s1
   i nazwa_s2 jako struktury zawierajace pola o podanych nazwach
   i typach, mozna sie do nich odwolywac np. przez nazwa_s1.zmienna1.
   Zamiast struct mozna uzyc union - roznica jest taka, ze w struct
   kazdy element ma oddzielne miejsce w pamieci, a w union wspolne -
   union przydaje sie, jesli chcemy do tych samych komorek w pamieci
   odwolywac sie przez rozne nazwy zmiennych o roznych typach.

   Definicje typu i zmiennej wygladaja prawie tak samo - roznia sie
   tylko slowem "typedef" w definicji typu - definiujac zmienna mozna
   za to uzyc jednego ze slow okreslajacych "storage class": auto
   (jest tworzona na stosie przy wejsciu do funkcji - tylko zmienne
   wewnatrz funkcji), static (jest pamietana na stale, dostepna tylko
   w funkcji, ktora ja definiuje, lub w funkcjach wpisanych w pliku,
   w ktorym jest zdefiniowana - jesli poza funkcja), extern (jest
   zdefiniowana w innym pliku), register (jesli mozliwe, w rejestrze
   procesora - nie ma adresu). Jesli nie jest uzyte zadne z nich, to
   zmienne wewnatrz funkcji sa auto, a zdefiniowane poza funkcja -
   dostepne przez deklaracje extern z innych plikow. W odniesieniu do
   funkcji mozna uzyc static (funkcja dostepna tylko w tym pliku),
   albo extern (tylko w deklaracji - ze jest w innym pliku, ale jak
   sie go nie poda, kompilator i tak przyjmie extern).
   Uwaga: C ma jakies wlasne funkcje i zmienne, przypadkowe uzycie
   nazwy identycznej z ktoras z nazw funkcji lub zmiennych C moze
   spowodowac fatalne skutki jesli nazwa jest dostepna z zewnatrz
   (jest nazwa funkcji lub zmiennej definiowanej poza funkcja, i nie
    jest static), i na dodatek oznacza zupelnie co innego.

   Przyklad: typ charp = char * i zmienne c1 i c2 typu char
   oraz cp1 i cp2 typu char * mozna zdefiniowac w taki sposob:
     typedef char *charp;
     char c1,*cp1,c2;
     charp cp2;
   A oto pare deklaracji funkcji i zmiennych wskazujacych na nie:
     char fc(char *);  /* funkcja typu char, parametr char * */
     char *fcp(char *); /* tu funkcja typu char * */
     char (*pfc)(char *)=fc; /* a to zmienna typu wskaznika do tej
       pierwszej funkcji, z nadana wartoscia poczatkowa */
     char *(*pfcp)(char *); /* i zmienna typu wskaznika do drugiej */
   Przy definiowaniu struktur i unii mozna sie obejsc bez typedef -
   zawsze struct nazwa albo union nazwa bedzie oznaczac ich typ, ale
   mozna tez uzyc typedef do nadania innej nazwy strukturze:
     typedef struct nazwa1 { cos tam } nazwa2;
   i teraz nazwa2 oznacza to samo, co struct nazwa1.
   Mozna sobie zdefiniowac nazwy typow zmiennych bez znaku, np.:
     typedef unsigned char byte,BYTE,*bptr; /*dwie nazwy i wskaznik*/
     typedef unsigned short word;
     typedef unsigned long ulong,dword;

   No i zagadnienie gdzie mozna umieszczac te definicje - mozna
   gdziekolwiek "nie w srodku" czegos innego - ale zwykle grupuje sie
   je gdzies na poczatku pliku zrodlowego, po dyrektywach #include
   i #define, i w kolejnosci: typedef, zmienne, deklaracje funkcji;
   i mozna wewnatrz dowolnego bloku (ale jesli w bloku sa wykonywalne
   instrukcje, to tylko przed nimi), poza danymi.

   Pisownia stalych: zmiennoprzecinkowe powinny zawierac kropke (po
   tym sie je rozpoznaje) i moga po e miec wykladnik. Calkowite moga
   byc pisane w roznych notacjach: oktalnie, dziesietnie, hex - i to
   trzeba jakos podac. Zasady sa takie: jak sie zaczyna od zera, to
   jest oktalnie, jak od 0x, to hex, jak od cyfry >0 - dziesietnie.
   Umieszczenie na koncu L daje stala long, U - unsigned (mozna i UL).

   Stale typu char pisze sie w apostrofach, np. 'a'. Niektore stale
   trzeba zapisywac z \, np. '\'' oznacza apostrof, '\n' koniec linii.

   Stale typu char * pisze sie w cudzyslowie, np. "jezyk C". Tu jesli
   w stalej ma wystapic cudzyslow trzeba go poprzedzic znakiem \. Te
   stale w pamieci komputera maja zero na koncu napisu (mozna uzyskac
   stala bez zera na koncu, jesli jest to wartosc poczatkowa tablicy,
   i to konczace zero juz sie w niej nie miesci - kompilator wtedy
   jeszcze nie protestuje, nie zaprotestuje tez jesli tablica jest
   dluzsza niz potrzeba), i jest ono wykorzystywane przez funkcje ze
   string.h (i nie tylko) do rozpoznawania konca napisu. Jak sie chce,
   mozna zapisac jeden napis zawierajacy zera w srodku - ale wtedy
   trzeba jakos poznawac, ze to nie jest koniec np. "Ala\0ma\0kota\0"
   - tu na koncu beda dwa zera, mozna uzyc funkcji strlen(), zeby
   dostac dlugosc napisu, dodac do wskaznika dlugosc i jedynke, znowu
   uzyc strlen(), i powtarzac az wyjdzie dlugosc 0. Taka strukture
   maja zmienne srodowiska (environment) w systemie DOS na PC.

   I jeszcze taki problem: co robic, jak stala typu char * jest za
   dluga na jedna linie? W C mozna wprawdzie pisac bardzo dlugie
   linie, ale to sie zle czyta. Dlatego mozna pisac ja w kawalkach,
   konczac kazdy kawalek i zaczynajac nastepny cudzyslowem. Innym
   sposobem mogloby byc kontynuowanie linii, ale to gorzej wyglada.

1.5 Dyrektywy preprocesora.

  W odroznieniu od instrukcji, ktore moga byc wieloliniowe, i koncza
  sie srednikiem, dyrektywy sa jednoliniowe - jesli nie mieszcza sie,
  trzeba napisac \ na koncu linii i kontynuowac w nastepnej, i nie
  maja srednika na koncu. Zaczynaja sie znakiem # na poczatku linii.

  W programie przykladowym wystapila dyrektywa #include - zeby wlaczyc
  do programu definicje z pliku stdio.h z katalogu "include". Moze
  jeszcze male uzupelnienie: jesli nazwa pliku jest w <>, to plik
  bedzie poszukiwany tylko w tym katalogu, jesli w "" - to na poczatku
  kompilator poszuka go w aktualnym katalogu (lub w podanym, jesli
  nazwa jest podana ze sciezka) - tej formy nalezy uzyc, jesli tworzy
  sie wlasny plik z definicjami.

  Inna czesto uzywana dyrektywa jest #define - sluzy do zdefiniowania
  nazwy, ktora wszedzie w tekscie programu zostanie zastapiona przez
  to, co sie zdefiniuje. Mozna napisac po prostu tak:
    #define Pi 3.1415926536 /* liczba Pi (to tez jest w definicji) */
  i lepiej nie napisac srednika w tej linii... no, akurat liczby Pi
  moze sie za czesto nie uzywa tak, by dalo to blad niewykrywalny przy
  kompilacji, ale jesli zdefiniuje sie przesuniecie skali Celsjusza
    #define Celsjusz 273.16;
  i napisze instrukcje:  temp_kelvina=Celsiusz+temp_celsiusza;
  to wynikiem bedzie temp_kelvina=273.16;+temp_celsiusza;
  i na temp_kelvina zawsze bedzie wpisywane to samo; moze kompilator
  da ostrzezenie, ze wyrazenie +temp_celsiusza; nie ma efektu...

  Ale mozna zdefiniowac cos wiecej (i wtedy o wiele latwiej zrobic
  podobny blad, i wiele innych): wyrazenie z parametrami, np.
    #define szescian(x) x*x*x
  to akurat jest z bledem: jesli napisze sie szescian(1+2), wyjdzie
  1+2*1+2*1+2, co po wyliczeniu daje 7, a nie, jakby sie chcialo, 27.
  Zeby wyszlo "dobrze", trzeba uzywac nawiasow wokol x:
    #define szescian(x) (x)*(x)*(x)
  Wyrazenie moze miec wiele parametrow, oddzielonych przecinkami. Nie
  wolno jednak uzyc w nim spacji w pierwszej czesci, okreslajacej co
  jest definiowane, bo pierwsza spacja oznacza koniec tej czesci.
  Mozna tez napisac definicje pusta, np.: #define puste(x,y), zeby
  takie wyrazenia w programie byly traktowane tak jakby ich nie bylo.

  Jest tez dyrektywa #undef, do usuwania definicji z #define.

  Do kompilacji warunkowej sa: #ifdef, #ifndef, #if, #else, #endif.
    #ifdef nazwa
  powoduje, ze nastepujace po niej linie zostana zignorowane, jesli
  nazwa nie jest zdefiniowana; #ifndef ma odwrotne dzialanie,
    #if wyrazenie
  wylacza kompilacje, jesli wartoscia wyrazenia jest zero. Zakres
  ktorego to dotyczy, konczy sie na dyrektywie #else, ktora odwraca
  wynik odpowiadajacej jej (bo te dyrektywy mozna zagniezdzac)
  dyrektywy #if (lub #ifdef, #ifndef), lub #endif, ktora konczy zakres
  dzialania kompilacji warunkowej. Przykladem zastosowania moze byc
  uzaleznienie kompilowania pewnych fragmentow od systemu, lub uzytego
  kompilatora - np. pisanie z adresowaniem ekranu w DOS-ie na PC mozna
  zrobic uzywajac definicji z conio.h, a w Unix-ie tego nie ma, uzywa
  sie definicji z curses.h, ktorych z kolei nie ma w DOS-ie. Sa one
  rowniez stosowane w plikach wlaczanych przez #include - powtorne
  wlaczenie tego samego pliku jest wykrywane (pierwsze definiuje jakas
  nazwe przez #define, nastepne sprawdzaja przez #ifdef) i powoduje
  zignorowanie tego pliku - zeby oszczedzac czas.

1.6 Operatory i wyrazenia.

  W jezyku C wystepuja nastepujace operatory, podane tu w kolejnosci
  priorytetu (jesli nie jest podane inaczej, przez P w komentarzu,
  lacznosc jest lewostronna - czyli jesli sa rownorzedne operatory,
  najpierw wykonuje sie lewy), od najwyzszego do najnizszego:
    /* nawiasy, wybor elementu */ () [] -> .
    /* jednoargumentowe (P)    */ ! ~ - ++ -- (typ) * & sizeof
    /* mnozenie/dzielenie/mod  */ * / %
    /* dodawanie/odejmowanie   */ + -
    /* przesuniecie bitow      */ << >>
    /* porownywanie            */ < <= > >=
    /* porownywanie            */ == !=
    /* iloczyn bitowy          */ &
    /* roznica bitowa          */ ^
    /* suma bitowa             */ |
    /* iloczyn logiczny        */ &&
    /* suma logiczna           */ ||
    /* wybor wyrazenia (P)     */ ?:
    /* przypisanie (P)         */ = operator=
    /* wyliczanie kolejne      */ ,
  Jak widac jest tego calkiem duzo. Niektore wymagaja omowienia.

  Nawias [] sluzy do wyboru elementu tablicy, kropka do wyboru pola
  w strukturze, -> tez wybiera pole w strukturze, ale po lewej jego
  stronie powinien byc wskaznik do struktury - i ze wzgledu na to, ze
  te operatory maja najwyzszy priorytet, jest on potrzebny zeby nie
  trzeba bylo uzywac nawiasow, zeby dostac sie do elementu struktury,
  kiedy mamy jej adres (np. przekazany jako parametr funkcji).

  Negacja logiczna ! (1 jesli argument jest 0, inaczej 0), bitowa ~
  (wszystkie bity odwrocone, arytmetyczna -; zwiekszanie i zmniejszanie
  o 1 (++ --, te operatory wyjatkowo moga wystapic rowniez po swoim
  argumencie, i wtedy dzialaja po jego uzyciu w wyrazeniu, czyli jesli
  i bylo 1, to ++i ma wartosc 2, a i++ - 1, i w kazdym wypadku stanie
  sie 2); rzutowanie typu (casting) wykonuje sie przez (typ); przez *
  mozna dostac to, co wskazuje argument, przez & mozna dostac adres
  argumentu (wiec *&arg znaczy to samo, co arg; (*arg).pole to samo, co
  arg->pole; arg.pole to samo, co (&arg)->pole - nawiasy konieczne);
  sizeof zwraca rozmiar argumentu w bajtach.

  Przesuwanie bitow - przyklady: 1<<3 jest 8, 5>>1 jest 2.
  Porownywanie: == znaczy rowne, != nierozne, inne sa oczywiste.
  Moze mniej oczywiste jest to, ze wynikiem porownanie jest 0 lub 1
  (0 jesli warunek nie jest spelniony, 1 jesli jest spelniony).

  Iloczyn i suma logiczna (nie mylic z bitowymi): wynik jest 0 lub 1;
  dla iloczynu 0 jesli ktorys argument jest 0, dla sumy 1 jesli jest
  nie 0; jesli argumenty sa wyrazeniami, a po wyliczeniu pierwszego
  wiadomo jaki bedzie wynik, to drugie nie jest wyliczane - to moze
  byc wazne, jesli wyliczenie ma efekt uboczny, bo zawiera operator
  np. ++, albo wywoluje funkcje.

  Wybor wyrazenia wyglada tak: wyrazenie1 ? wyrazenie2 : wyrazenie3,
  i jesli wyrazenie1 jest niezerowe, to wylicza sie wyrazenie2, i to,
  co wyjdzie, jest wartoscia; a jesli zerowe, wylicza sie wyrazenie3,
  i to, co wyjdzie, jest wartoscia. Pozwala to zdefiniowac wybieranie
  mniejszego i wiekszego elementu (definicje sa w stddef.h):
  #define min(a,b) ( (a)<(b) ? (a) : (b) )
  i analogicznie wiekszego, ale moze to zle zadzialac, jesli a lub b
  jest wyrazeniem, ktorego wyliczenie daje efekt uboczny, ktory jest
  potrzebny raz - bo moze zadzialac dwa razy; przykladem moze chyba
  byc czytanie znaku i wypisywanie spacji jesli jest < spacji:
     putch(max(getch(),' ');
  bo jak poda sie znak wiekszy od spacji, wczyta nastepny, i juz bez
  sprawdzania, czy jest mniejszy czy wiekszy wyswietli go.

  Wyliczanie - warto zwrocic uwage, ze przecinki oddzielajace
  zmienne w deklaracji (mozna im od razu nadac wartosci poczatkowe),
  lub parametry funkcji, nie sa operatorami kolejnego wyliczania,
  i kolejnosc wyliczania ich wartosci nie jest okreslona (i nawet
  w przypadku parametrow funkcji jest z reguly odwrotna): wylicza sie
  po kolei podane wyrazenia, wynik ostatniego jest wartoscia calosci.
  Ma mniejszy priorytet niz przypisanie - pozwala bez uzycia nawiasow
  napisac kilka przypisan jako jedna instrukcje.

  I przypisanie: fakt, ze to jest operator dopuszczalny w wyrazeniu,
  powoduje ze mozna wewnatrz wyrazenia dokonac dodatkowych przypisan,
  tylko trzeba uzywac nawiasow. Przyklad zastosowania:
    while((c=getchar())!=EOF) { ... (cos z przeczytanym znakiem) }
  a mozna i tak: while(c=getchar(),c!=EOF) { ... }
  Ale przypisanie, oprocz prostego =, moze uzyc operatorow:
  *= /= %= += -= <<= >>= &= ^= |=
  i dziala to w ten sposob, ze z tym, co jest po lewej stronie takiego
  operatora, wykonuje sie dzialanie dwuargumentowe (drugim argumentem
  jest wartosc wyrazenia po prawej stronie), i wynik zapisuje na to,
  co jest po lewej stronie, np. a+=2; dodaje do a 2. Ale to nie jest
  to samo, co napisanie a=a+2; w przypadku, gdy a jest wyrazeniem (sa
  pewne ograniczenia co do wyrazen, jakie moga wystapic po lewej
  stronie operatora przypisania - musi ono okreslac cos, na co mozna
  cos przypisac, np. element tablicy), jego wyliczenie wykona sie raz,
  a nie dwa razy, co da zauwazalny skutek jesli a bedzie s[i++]:
    s[i++]+=1; - zwiekszy s[i] o 1, a nastepnie i tez o 1;
    s[i++]=s[i++]+1; - najprawdopodobniej wezmie s[i], doda 1, doda
                       1 do i, zapisze wartosc pierwszej sumy na s[i]
                       (ale juz z nowym i), i znow doda 1 do i.
    s[i]=s[i]+1; i++; - dziala tak, jak pierwsze, poza tym, ze adres
                        s[i] wylicza sie dwa razy, zamiast raz.

1.7 Instrukcje sterujace.

  if(wyrazenie) instrukcja; - istrukcja wykona sie, jesli wyliczenie
                              wyrazenia da wartosc rozna od zera;
  if(wyrazenie) instr1; else instr2; - w zaleznosci od tego, czy
    wyrazenie bedzie nie zero, czy zero, wykona sie instr1 lub instr2.
  Warto zauwazyc, ze w C instrukcja przed else konczy sie srednikiem
  (chyba, ze jest to blok w {}, wtedy nie ma srednika po nawiasie }).
  Jesli wyrazenie zawiera przypisanie, to warto pisac to tak:
    if((a=b)!=0) ..., bo przy if(a=b) ... kompilator bedzie alarmowal,
  ze byc moze zostal zrobiony blad, ze moze mialo byc if(a==b) ...
  Jesli jest kilka if-ow, i potem jakies else, kompilator odnosi to
  else do najblizszego if-a, do ktorego jeszcze nie przypisal else,
  jesli ma byc inaczej, to trzeba to zaznaczyc nawiasami klamrowymi.

  Jesli potrzebna jest sekwencja instrukcji typu:
  if(i==1) instr1; else if(i==2) instr2; else if(i==3) instr3; ...
  to mozna to zapisac inaczej uzywajac instrukcji switch:
  switch(i) { case 1: instr1; break; case 2: instr2; break; ... }
  Instrukcja break wewnatrz switch ma specjalne znaczenie - konczy
  wykonywanie switch, bez niej po instr1 wykonalaby sie instr2. Za to
  nie trzeba pisac nawiasow klamrowych, jesli ma byc kilka instrukcji
  dla jakiejs wartosci i (te wartosci musza byc calkowite). Mozna tez
  umiescic wewnatrz switch etykiete default - jesli wartosc i nie
  bedzie zadna z podanych, wykonaja sie intrukcje po tej etykiecie.
  Ale jej brak nie powoduje bledu jesli wartosc i nie "trafi".

  Instrukcje petli - najprostsza jest while(wyrazenie) instrukcja;
  i dopoki wyrazenie jest niezero wykonuje instrukcje, przy czym
  wylicza wyrazenie na poczatku i po kazdym wykonaniu instrukcji.

  Mozna jednak chciec przed rozpoczeciem petli ustawic wartosc jakiejs
  zmiennej i uzywac jej jako licznika powtorzen - do tego jest forma:
    for(instr1; wyrazenie; instr3) instr2;
  instr1 wykonuje sie raz na poczatku; wyrazenie jest wyliczane przed
  kazdym wykonaniem petli i petla nie jest wiecej wykonywana jesli
  wartosc bedzie zero; w kazdym przejsciu petli wykonuje sie najpierw
  instr2 a potem instr3. Po co tak jest widac z takiego przykladu:
    for(i=0; i<arg_count; i++) printf("%s\n",arg_vector[i]);
  Uwaga: instr1 i instr3 musza byc wyrazeniami - nie moga byc ani
  instrukcjami sterujacymi, ani blokami - {} nie sa dopuszczalne.

  Jesli warunek ma byc sprawdzany dopiero po wykonaniu petli, mozna
  uzyc instrukcji: do { instrukcje } while(warunek);
  Mozna, jesli wewnatrz jest tylko jedna instrukcja, pisac to i bez
  nawiasu klamrowego, ale zaleca sie pisac go, jesli while nie jest
  w tej samej linii, co do - zeby bylo lepiej widac co to jest.

  W petlach dzialaja dwie instrukcje sterujace: break i continue.
  Pierwsza konczy wykonywanie petli (uwaga jesli wewnatrz petli jest
  switch - wewnatrz switch zadziala na switch, a nie na petle), druga
  konczy dane przejscie petli (ale np. dla for wykona instr3).

