1. Podstawy informatyki, DOS-u, UNIX-a 1.1. Pojecia - jednostki informacji: bit, bajt/znak, rekord, plik 1.2. Plik (file): nazwa, podstawowe operacje na pliku jako calosci 1.3. Skorowidz (directory): moze zawierac pliki i skorowidze, ... 1.4. Pare elementow Novell-a (map, salvage, purge) 2. Edytowanie programu, uzywanie kompilatora 2.1. Jak wystartowac zintegrowane srodowisko TC lub BC 2.2. Najwazniejsze polecenia edytora 2.3. Opcje zintegrowanego srodowiska TC 2.4. Mamy napisany program i co dalej? 2.5. Najprostszy program w C 2.6. Nastepny program w C 2.7. Program przedstawia sie 2.8. Parametry programu 2.9. Zmienne srodowiska DOS 3. Dyrektywy preprocesora 3.1. #include 3.2. #define 3.2.1. bez argumentow lub z argumentami 3.2.2. dlaczego potrzeba uzywac nawiasow 3.2.3. operator # - definicja show(x) 3.2.4. operator ## - laczenie nazw 3.3. #undef 3.4. kompilacja warunkowa: #ifdef, #ifndef, #if, #else, #elif, #endif 3.5. #error 3.6. #line 3.7. #pragma 1.1. Pojecia - jednostki informacji: bit, bajt/znak, rekord, plik bit - najmniejsza jednostka informacji, odpowiedz tak/nie bajt (lub znak) - jednostka informacji, ktora moze zawierac znak - litere, cyfre, lub inny; zwykle bajt = 8 bitow - to pozwala miec 256 znakow w "alfabecie" rekord - pojedynczy zapis, ktory cos oznacza; w systemach DOS i UNIX mozna traktowac bajt jako rekord, ale np. w pliku tekstowym zwykle traktuje sie linie jako rekord; system VMS ma rekordy "wbudowane" w system, i nie pozwala czytac informacji po bajcie plik - zbior rekordow, ustawionych w okreslonej kolejnosci, ktore tworza pewna calosc, z mozliwoscia operacji na nim jako calosci. 1.2. Plik (file): nazwa, podstawowe operacje na pliku jako calosci nazwa w DOS-ie (i paru innych starszych systemach, np. RSX-11) sklada sie z dwoch czesci oddzielonych kropka, pierwsza moze miec 8 znakow, druga 3 (wewnetrznie DOS pamieta 11 znakow - kropki nie), te druga czesc nazywa sie rozszerzeniem (extension) albo typem pliku, zwykle odpowiada ona jego zawartosci - np. program napisany w C ma typ .C, a skompilowany i gotowy do wykonania .EXE; w UNIX-ie kropka z nazwie pliku jest znakiem niemal rownie dobrym jak kazdy inny, jedynie jej uzycie na poczatku nazwy ma specjalne znaczenie, takie pliki nie sa normalnie widoczne, i nazwa moze byc duzo dluzsza, niemniej jednak tez zwykle nazwy plikow konczy sie nazwa typu; operacje na plikach: tworzenie zalezy od typu pliku kopiowanie copy (DOS) cp (UNIX) nazwa nazwa-wynikowa usuwanie del/delete/erase (DOS) rm (UNIX) nazwa zmiana nazwy ren/rename (DOS) mv (UNIX) nazwa nowa-nazwa przenoszenie move (DOS) mv (UNIX) nazwa skorowidz-docelowy uwagi: ? i * uzyte w nazwie oznaczaja: ? - dowolny znak, * - dowolny ciag znakow, w DOS-ie * dziala tak, jak ciag ? do kropki, lub konca nazwy, a do ? moze pasowac spacja (do A?.TXT pasuja A1.TXT, A.TXT), w UNIX-ie do wszystkich znakow podanej nazwy cos musi byc dopasowane (do * pasuje rowniez 0 znakow, ale do ? nie; w DOS-ie np. do A*1.TXT pasuje A2.TXT, w UNIX-ie nie - 1.TXT musi byc na koncu znalezionej nazwy); DOS zezwala przy kopiowaniu i zmianie nazwy na uzycie znakow ? i * w nowej nazwie - oznacza to podstawienie w dane miejsce nazwy fragmentu oryginalnej nazwy, np. COPY A*.TX? B*.LS? skopiuje pliki, ktorych nazwy zaczynaja sie na A, a rozszerzenia na TX, i kopie beda mialy nazwy zaczynajace sie na B, z rozszerzeniem na LS, reszta nazw bez zmian; w UNIX-ie tego nie ma, za to mozna podawac wiele nazw tam, gdzie ma to jakis sens (kopiowanie i przenoszenie wielu plikow do skorowidzu, usuwanie wielu plikow), i ? i * moga byc w nazwie sciezki (pojecia skorowidz i sciezka sa w 1.3), a w DOS-ie nie. 1.3. Skorowidz (directory): moze zawierac pliki i skorowidze, mozna to traktowac jako drzewo, ktore ma jakis pien - to jest glowny skorowidz (root directory), z niego wyrastaja konary (inne skorowidze), z nich dalsze galezie (skorowidze zawarte w tych odpowiadajacych konarom), i wszedzie (rowniez na pniu) moga rosnac liscie (to sa pliki). Adresowanie na tym drzewie: pelny adres pliku podaje ktore drzewo (w DOS-ie - w UNIX-ie jest tylko jedno), i jak po kolei wybierac rozgalezienia (kazde ma nazwe), zeby dojsc na galaz z lisciem, i na koniec nazwa liscia. W DOSi-e nazwy sa oddzielone '\', w UNIX-ie '/', wyglada to (w DOS-ie) tak: H:\PROGRAMY\C\PROG1.C - tutaj drzewem jest dysk H:, pierwszym konarem PROGRAMY, na nim rosnie galaz C, i na niej lisc PROG1.C. Skorowidz aktualny (w DOS-ie jest okreslony oddzielnie dla kazdego dysku, w UNIX-ie jest jeden), jesli po nazwie dysku (w DOS-ie, tu H:), lub na poczatku adresu (w UNIX-ie, lub w DOS-ie jesli nie podaje sie nazwy dysku - oznacza to przyjecie dysku aktualnego) nie ma znaku oddzielajacego, to adres jest wzgledny - liczy sie od skorowidzu aktualnego (w DOS-ie dla podanego, lub aktualnego dysku). Czesc adresu miedzy nazwa dysku i pliku nazywa sie sciezka (path). Adres moze zawierac elementy specjalne - kropke lub dwie kropki; kropka oznacza ten sam skorowidz, dwie kropki oznaczaja cofniecie sie na drzewie o jedno rozgalezienie - jesli aktualnym dyskiem jest H:, i aktualnym skorowidzem na nim H:\PROGRAMY\BASIC, to nazwe pliku, ktory byl w poprzednim przykladzie, mozna podac jako ..\C\PROG1.C. Te dwie nazwy - . i .. - sa nazwami skorowidzow, ktore sa na poczatku kazdego skorowidzu (z wyjatkiem glownego, ale nawet tam . dziala). W Novell-u (taki system, ktory jest serwerem plikow dla DOS-u - udaje dyski, choc komputery ich nie maja) mozna podac wiecej kropek naraz, i oznacza to cofniecie sie o wiecej rozgalezien (np. ... - o 2). Operacje na skorowidzach: tworzenie md (DOS) mkdir (DOS, UNIX) nazwa (w UNIX-ie nazwy) usuwanie rd (DOS) rmdir (DOS, UNIX) nazwa (w UNIX-ie nazwy) wybor aktualnego cd (DOS, UNIX) chdir (DOS) nazwa jaki aktualny? cd (DOS - bez parametru) pwd (UNIX) (w UNIX-ie cd bez parametru wybiera skorowidz HOME) zmiana nazwy move (DOS) mv (UNIX) stara-nazwa nowa-nazwa przenoszenie moze dzialac tak samo jak zmiana nazwy co zawiera? dir (DOS) ls (UNIX) nazwa (nazwe mozna pominac) 1.4. pare elementow Novell-a (login, logout, map, salvage, purge) LOGIN (mozna podac jako parametr nazwe uzytkownika) - sluzy do poinformowania serwera kto jest uzytkownikiem, i zwykle wymaga podania hasla (password) uzytkownika; tego hasla nalezy uzywac wylacznie samemu, i nikomu go nie podawac; jesli podejrzewa sie, ze ktos mogl je podpatrzec, nalezy je zmienic poleceniem SETPASS; powoduje, ze serwer uznaje prawa uzytkownika do dostepu do jego (i innych udostepnionych mu) plikow LOGOUT - informuje serwer, ze uzytkownik zakonczyl prace - konczy dzialanie uprawnien uzytkowika uzyskane poleceniem LOGIN MAP - sluzy do okreslania jak maja byc udostepnione dyski serwera, samo "MAP" wypisze informacje o aktualnych definicjach wszystkich "dyskow", "MAP X:" wypisze definicje dysku X:, map "X:=definicja" zdefiniuje dysk X:; definicja sklada sie z: nazwy serwera (OKWF1, jesli podaje sie te nazwe, to po niej / lub \), nazwy dysku serwera (SYS, USR, HOME, HOME1, SWAP na OKWF1, po tej nazwie ma byc :), i sciezki (nazw skorowidzow oddzielonych / lub \; nie trzeba uzywac / ani \ na poczatku sciezki - dysk serwera jest zawsze na "root"); dodatkowo mozna po MAP napisac ROOT - wtedy podany skorowidz bedzie widoczny jako "root" zdefiniowanego dysku, bez tego "root" dysku X: bedzie taki sam, jak dysku serwera, a jedynie zostanie ustawiony aktualny skorowidz na X:, MAP ROOT J:=HOME:STAFF/SYSTEM/EXAMPLES zdefiniuje dysk J: tak, by bylo na nim widac materialy do cwiczen; SALVAGE - sluzy do odzyskiwania skasowanych plikow PURGE - ostatecznie usuwa skasowane pliki; jesli sie tego nie zrobi, to Novell za jakis czas sam je usunie, gdy zabraknie miejsca. Prawa dostepu: uzytkownik ma prawo do wszystkich operacji na swoim skorowidzu (home directory), widocznym na dysku H:, i na swojej skrzynce pocztowej, widocznej na F:\MAIL\numer_uzytkownika, i na dysku C: (przewidzianego na obszar roboczy - nie nalezy go uzywac do przechowywania plikow); moze uzywac programow zainstalowanych na serwerze (zwykle moze tez je czytac - jakies programy moga byc zabezpieczone przed czytaniem), oraz moze zapisac plik do czyjejs skrzynki pocztowej (o ile zna jej nazwe - nie moze jej zobaczyc); ograniczeniem jest przydzial miejsca, i nalezy zachowac szczegolna ostroznosc przy pisaniu do czyjejs skrzynki pocztowej - zapisany tam plik uzywa miejsca przydzielonego temu, kto go zapisal, dopoki nie zostanie skasowany, a prawo do skasowania ma wlasciciel tej skrzynki, a nie ten, co go zapisal - jest to sposob na przeslanie komus pliku, ale pod warunkiem, ze mozna temu komus zaufac. 2.1. Jak wystartowac zintegrowane srodowisko TC lub BC (dotyczy tylko pracy na komputerach OKWF) po zalogowaniu sie (jesli nie mamy konta to mozna uzyc nazwy fizykxx gdzie za xx podstawia sie numer napisany na komputerze) nalezy najpierw napisac G:\P, a potem - dla TC: INIT_TC i potem TC - dla BC: INIT_BC3 i potem BC z tego G:\P i INIT_cos musza byc wpisane bez Norton Commandera 2.2. Najwazniejsze polecenia edytora Wpisywanie tekstu dziala w ten sposob, ze nowo wpisany znak pojawi sie tam, gdzie jest kursor; znak, ktory byl w tym miejscu, moze byc usuniety, lub przesuniety razem ze wszystkimi dalszymi w tej linii, to drugie nastapi przy wlaczonym trybie Insert (wstaw), i zwykle pracuje sie w tym trybie; do wlaczania i wylaczania trybu Insert sluzy klawisz Insert, tryb Insert jest sygnalizowany napisem nieco na lewo od srodka linii z informacjami (na gorze). Inne informacje, na ktore warto zwrocic uwage, to: numer linii i kolumny (z lewej), Indent i Tab (mozna je wlaczac i wylaczac przez Ctrl-O I, Ctrl-O T, Indent okresla zachowanie przy przejsciu do nowej linii przez Enter, Tab przy wcisnieciu klawisza Tab) na srodku, w prawo od Tab: Fill (Ctrl-O F, powoduje zastepowanie spacji tabulatorami jesli to mozliwe - skraca program), Unindent (Ctrl-O U, steruje dzialaniem Backspace - tak, ze moze kasowac kilka spacji, dopasowujac sie do linii powyzej), i w zasadzie mozna to wszystko miec wlaczone; dalej w prawo gwiazdka pokazuje, ze plik zostal zmieniony i nie zapisany na dysku (zapisuje sie przez F2), i na koniec nazwa pliku (z nazwa dysku - warto zwrocic uwage, czy jest to dysk na ktorym mamy prawo pisac, bo inaczej beda klopoty przy probie zapisania i nie tylko). Po tekscie mozna poruszac sie strzalkami - ale to czasem moze byc za wolno. Klawisze PageUp i PageDown sluza do przewijania tekstu o caly ekran, a Home i End do ustawienia kursora na poczatek/koniec linii. Przy wcisnietym Ctrl strzalki <- i -> przesuwaja kursor o cale slowo, Home i End w pionie zamiast w poziomie (gora/dol ekranu), a PageUp i PageDown na poczatek/koniec tekstu. Wiekszosc liter razem z Ctrl cos robi - W i Z przesuwaja tekst na ekranie o jedna linie, ale bez przesuwania kursora wzgledem tekstu, R i C dzialaja tak, jak PageUp i PageDown - przesuwaja o strone, nie zmieniajac polozenia kursora na ekranie (chyba, ze dojdzie sie do poczatku lub konca tekstu), S,D,E,X dzialaja jak strzalki, A i F jak strzalki z Ctrl, Q zaczyna sekwencje sterujace, np. Ctrl-Q S = Home, Ctrl-Q F - szukanie napisu w tekscie, Ctrl-Q A - szukanie i zamiana, to szukanie pyta o opcje: w-tylko cale slowa, u-nie rozroznia malych/duzych liter, b-do tylu, n-bez pytania, g-ile razy sie da (mozna podac liczbe), powtorzenie szukania (lub szukania z zamiana) uzywkuje sie przez Ctrl-L. Kasowanie - sa dwa kasowania pojedynczego znaku: tego, pod ktorym jest kursor (Delete lub Ctrl-G), i tego przed kursorem (Backspace lub Ctrl-H). Wiecej skasuje Ctrl-T (do konca slowa, i ewentualnie spacje do poczatku nastepnego), Ctrl-Q Y (do konca linii), Ctrl-Y (cala linie - wtedy nie dziala odzyskiwanie linii przez Ctrl-Q L). Operacje z blokiem - wszystkie polecenia zaczynaja sie od Ctrl-K: B i K - zaznaczanie poczatku i konca bloku (BloK), C - kopiowanie, V - przeniesienie, Y - kasowanie, H - ukrycie (hide - powtorne pokaze go znowu), R - przeczytanie z pliku, W - zapisanie na plik; 1/2/3 - zaznacza miejsce - potem mozna uzyc np. Ctrl-Q 1 do powrotu do miejsca zaznaczonego przez Ctrl-K 1, mozna tez Ctrl-Q B lub K... 2.3. Opcje zintegrowanego srodowiska TC Zeby sie do nich dostac, trzeba wcisnac Alt-O. Interesujace sa: Compiler, i dalej Model - na razie bedziemy uzywac Small Code generation, i dalej Instruction set - mozna ustawic 80186/80286, bo o 8088 juz trudno Floating point - warto pomyslec co wybrac: None warto wybrac, bo skroci program, jesli nie chce sie uzywac liczb zmiennopozycyjnych; jesli sie ich uzywa, trzeba wybrac albo Emulation - to bedzie dzialac nawet jesli komputer nie ma koprocesora (czasem takie sie trafiaja), albo 8087/80287 (wtedy koprocesor bedzie potrzebny, zeby program mogl sie wykonac) Alignment - dla Byte program bedzie nieco krotszy i nieco wolniejszy niz dla Word Test stack overflow, Line numbers i OBJ debug information lepiej miec ustawione na On dopoki program nie jest przetestowany Optimization - warto wlaczyc (On) Register i Jump optimization Errors - tu sa 4 menu, w ktorych trzeba wszystko ustawin na On: Portability warnings, ANSI violations, Common errors, Less common errors - zeby wszystkie ostrzezenia byly pokazywane Environment - mozna wlaczyc Config auto save i Edit auto save, a jak sie chce widziec wiecej linii na ekranie, to ustawic Screen size i na koniec trzeba uzyc Save options, zeby zapisac zmienione opcje. 2.4. Mamy napisany program i co dalej? Alt-C wybiera menu kompilacji, w nim sa do wyboru: Compile - kompilacja = robi .OBJ z .C Make - "oszczedne" tworzenie .EXE (jesli cos zostalo juz raz zrobione, to sie nie juz powtarza, kryterium oceny jest czas, kiedy zostaly zapisane pliki - jesli np. .C jest starszy niz .OBJ, to nie wykonuje sie kompilacji; bywa to zawodne, jesli komputer ma zle ustawiony zegar, albo jesli zmieni sie opcje kompilatora, np. Model) Link - robi .EXE z .OBJ Build all - Compile+Link bezwarunkowo Alt-R wybiera menu wykonywania, w nim sa do wyboru: Run - zwyczajnie wykonuje program Program reset - konczy program - mozna wykonac powtornie Goto cursor - wykonuje program, zatrzymujac go kiedy dojdzie do linii, w ktorej jest kursor (jak tam dojdzie) Trace into - wykonuje jedna linie, pokazujac wejscie do funkcji jesli linia zawiera wywolanie funkcji z programu (na funkcje z biblioteki to nie zadziala) Step over - wykonuje jedna linie, nie wchodzac do funkcji User screen - pokaze ekran, na ktory pisze wykonujacy sie program z tych polecen wszystkie oprocz ostatniego automatycznie robia Make, wiec mozna po napisaniu programu nie kompilowac go, tylko od razu Alt-R i Run, ale... lepiej najpierw go zapisac, bo jesli zawiesi sie komputer, to nie bedzie nawet mozna zobaczyc od jakiego bledu; i do tego wykonujac od razu Run traci sie informacje o ostrzezeniach. Polecenia "Goto cursor", "Trace into" i "Step over" przydaja sie, jesli chce sie wykonywac program po trochu i obserwowac, ale zamiast wybierac je z menu wygodniej jest uzywac klawiszy F4, F7 i F8. Warto jeszcze zapamietac, ze Alt-F5 pokazuje ekran ("User screen"). Reasumujac, mozna podejsc tak: - napisac program i zapisac go wciskajac F2 - wcisnac Alt-C B i popatrzec na informacje o bledach - wcisnac Alt-R R i jak sie skonczy Alt-F5 i obejrzec wyniki 2.5. Najprostszy program w C Mozna wpisac taka linie: main(void) { printf("cos\n"); return 0; } a nastepnie to skompilowac i wykonac - program wypisze to "cos" na ekranie, i na tym sie zakonczy. 2.6. Nastepny program w C Jesli sa wlaczone wszystkie ostrzezenia, to kompilator zaalarmuje, ze nie wie, co to jest za funkcja printf (ze nie ma prototypu). Zeby wiedzial (nie musi, program dziala i bez tego), mozna mu napisac: int printf(const char *format,...); przed ta linia z "main", i bedzie wszystko w porzadku. Ale zeby nie trzeba bylo pamietac, co tam ma byc napisane, ta i podobne definicje zostaly umieszczone w pliku stdio.h, i mozna go wlaczyc do programu piszac na poczatku linie: #include Jesli to, co program ma wypisac, ma byc nieco dluzsze, wygodniej bedzie zapisac to oddzielnie - mozna to zrobic tak: #include const char napis[]= "Ala ma kota.\n" "To jest kot Ali.\n"; main(void) { printf(napis); return 0; } Uwagi: tekst do wypisania jest w cudzyslowie, ale nie moze zajmowac wiecej niz jednej linii; mozna jednak napisac wiele linii z tekstem w cudzyslowie, i jesli nie ma na zewnatrz cudzyslowow innych znakow, to kompilator potraktuje to jako jeden dlugi tekst; jesli przy tym bedzie on podzielony na linie zgodnie z tym, jak ma byc wypisany, to jego tresc bedzie latwiejsza do czytania. W tekscie trzeba uwazac na znaki \ i %, ktore dzialaja nieco inaczej: \ w stalej znakowej sluzy do umieszczenia w niej znakow, ktorych nie mozna "normalnie" wpisac: jesli potrzeba \, to wpisuje sie \\, jesli ' lub ", to poprzedza sie je \-em, \n to znak przejscia do nowej linii, \t - tabulatora, \r - powrotu do poczatku linii, \a - alarm, \nnn (trzy cyfry <7) oznacza znak o kodzie nnn (osemkowo - najwiekszy kod 377) - teraz mozna by zamiast "Ala ma kota.\n" napisac "Ala ma kota.\a\a\a\r" - ten napis pojawi sie, bedzie slychac 3 razy "bip", a potem napis sie zmieni na "To jest kot Ali."; % ma specjalne znaczenie dla printf w formacie. 2.7. Program przedstawia sie Dla printf znaki %s uzyte w formacie oznaczaja polecenie wypisania napisu, a %d liczby calkowitej. Mozna tego uzyc, by program wypisal informacje, jak sie nazywa, i ile ma parametrow - w taki sposob: #include main(int argc, char **argv) { printf("Jestem %s, mam %d parametrow.\n", argv[0], argc); return 0; } Program wypisuje informacje od systemu - nazwe, przez ktora zostal wywolany, i ilosc parametrow (otrzymuje on rowniez parametry, ale do ich wypisania trzeba juz wiecej roboty). Nazwa zwykle jest razem ze sciezka, i typem (.EXE), mozna by je wyrzucic, zeby bylo ladniej: #include main(int argc, char **argv) { char *p; int i,n,t; p=argv[0]; for(i=n=t=0; p[i]!=0; i++) { if(p[i]==':' || p[i]=='/' || p[i]=='\\') { n=i+1; t=0; } if(p[i]=='.') t=i; } if(t==0) t=i; printf("Jestem %.*s, mam %d parametrow.\n", t-n, p+n, argc); return 0; } ale program juz sie zrobil bardziej skomplikowany - trzeba przejrzec nazwe, zapamietac gdzie sie zaczyna ostatnia nazwa po ':', '/', '\', zapamietac gdzie jest kropka (jesli jest) po tej nazwie, policzyc ile znakow ma nazwa, i tyle ich wypisac (zaczynajac od poczatku nazwy). 2.8. Parametry programu Skad sie biora: jesli wykonuje sie program jako polecenie w DOS-ie, i oprocz nazwy programu napisze sie cos jeszcze, to to cos jest do tego programu przekazywane jako parametry; jesli wykonuje sie program poleceniem Run (w TC lub BC), to w opcjach mozna ustawic Arguments. Nastepujacy program pokaze liste swoich parametrow: #include main(int argc, char **argv) { int ai; for(ai=1; ai main(int argc, char **argv, char **env) { int ei; for(ei=0; env[ei]!=NULL; ei++) printf("e[%d]=\"%s\"\n", ei, env[ei]); return 0; } 3. Dyrektywy preprocesora Wszystkie zaczynaja sie od znaku # na poczatku linii, w zasadzie ten znak nie powinien byc poprzedzany spacjami, chociaz wiele kompilatorow akceptuje to i rozpoznaje je mimo wszystko. Mozna natomiast, jesli to zwiekszy czytelnosc programu, uzyc spacji pomiedzy # i nazwa dyrektywy. 3.1. #include Mozliwe sa dwie formy: #include "nazwa" i #include ; sluzy do wlaczenia pliku do kompilowanego programu tak, jakby sie go wczytalo przy edycji programu w miejsce tej dyrektywy; pierwsza forma szuka pliku najpierw w skorowidzu, w ktorym jest kompilowany program, potem w skorowidzach zdefiniowanych w "sciezce wlaczania" (include path), druga szuka wylacznie w sciezce wlaczania; nazwa musi zawierac typ (czyli np. stdio.h, a nie samo stdio), i moze zawierac sciezke. 3.2. #define Sluzy do zdefiniowania wartosci badz wyrazen, ktore nastepnie beda uzywane w programie; moze zdefiniowac nazwe jako pusta, co jednak nie jest tym samym, co brak definicji, i daje sie odroznic chocby przez dyrektywy #ifdef, #ifndef, i defined() w #if, #elif. 3.2.1. bez argumentow lub z argumentami #define nazwa definicja powoduje, ze nazwa, jesli wystapi w programie jako oddzielne slowo, zostanie zastapiona przez podana definicje (nie zostanie zastapiona w napisie umieszczonym w cudzyslowie - robi sie to nieco inaczej). #define nazwa(argumenty) definicja powoduje, ze nazwa, jesli wystapi w programie wraz z nawiasem i lista argumentow (musi sie zgadzac ich ilosc), zostanie zastapiona przez definicje, w ktorej nazwy argumentow zostana zastapione wartosciami podanymi przy uzyciu tej nazwy; rozpoznawanie argumentow w definicji nie dziala w napisie umieszczonym w cudzyslowie, argument musi byc wyodrebnionym slowem - dalej bedzie informacja jak mozna to obejsc. 3.2.2. dlaczego potrzeba uzywac nawiasow Wezmy taka definicje: #define badsqr(x) x*x Zadziala to sensownie, jesli za x podstawi sie liczbe, albo nazwe zmiennej - np. badsqr(2) zamieni sie na 2*2, badsqr(a) na a*a - niby sensownie, ale juz badsqr(a+b) zamieni sie na a+b*a+b - zupelnie nie to, co trzeba, bo mnozenie ma wyzszy priorytet niz dodawanie, i to, co wyszlo, nie jest suma a+b podniesiona do kwadratu; oczywiscie, mozna napisac badsqr((a+b)), ale zamiast tego lepiej napisac: #define sqr(x) ((x)*(x)) i mozna sie nie przejmowac ani tym, ze x jest wyrazeniem, w ktorym sa operatory z priorytetem nizszym, niz mnozenie, ani tym, uzywa sie sqr w polaczeniu z operatorami o priorytecie wyzszym niz mnozenie. 3.2.3. jednoliniowa - kontynuacja linii przez \ W zasadzie w jezyku C instrukcje mozna "ciagnac" przez wiele linii, ale dyrektyw preprocesora to nie dotyczy - musza "zmiescic sie" w jednej; poniewaz #define bywa dosc dlugie, czasem przydaje sie kontunuacja linii przez \ na koncu - powoduje on, ze nastepna linia zostaje potraktowana jako ciag dalszy biezacej (tak, jakby nie bylo znaku \ i przejscia do nowej linii) - z #define mozna tylko tak... 3.2.4. operator # - definicja show(x) Uzycie #argument w definicji jest zastepowane przez wartosc tego argumentu w cudzyslowie; jest to szczegolnie uzyteczne w takiej definicji: #define show(x) printf(#x "=%u\n",x) ktorej mozna potem uzyc tak: show(1+4) A mozna tez przerobic nasz pierwszy program na cos takiego: #define P(x) printf(#x "\n") main(void) { P(Ala ma kota.); P(To jest kot Ali.); return 0; } 3.2.5. operator ## - laczenie nazw Operator ## dziala po zastapieniu nazw argumentow w definicji ich wartosciami, i po prostu znika razem ze spacjami przed nim i po nim - moze wiec byc uzyty do laczenia nazw, na przyklad: #define PREFIX xx #define PP(a,b) a ## b #define P(x) PP(PREFIX,x) i teraz P(a) zostanie zastapione przez xxa. 3.3. #undef Sluzy do anulowania definicji #define; jest to wskazane, jesli cos zostalo zdefiniowane, i definicja ma byc zmieniona - inaczej byloby to traktowane jako blad przy kompilacji; jest tez potrzebne, jesli zdefiniowanie badz brak zdefiniowania czegos ma sterowac kompilacja; praktycznie przydaje sie przy duzych programach, podzielonych na wiele czesci, z ktorych jedna zawiera parametry konfiguracyjne. 3.4. kompilacja warunkowa: #ifdef, #ifndef, #if, #else, #elif, #endif Ogolnie konstrukcja wyglada tak: #if warunek1 ...instrukcje1... #elif warunek2 ...instrukcje2... #else ...instrukcje3... #endif i powoduje to, ze jesli jest spelniony warunek1, to kompiluja sie instrukcje1, jesli nie, a jest spelniony warunek2, to kompiluja sie instrukcje2, a jesli zaden nie jest spelniony, to instrukcje3; jest to sterowanie kompilacja, i instrukcje pomijane moga nawet nie byc poprawnymi instrukcjami w C, byleby tylko nie bylo wsrod nich tych dyrektyw, o ktorych tu mowa (a jak juz by byly, to w komplecie) - mozna tego uzyc do "zakomentowania" kawalka programu, na przyklad: #if 0 ...cos, co ma byc zignorowane, na przyklad komentarz... #endif bywa to uzyteczne, jesli robi sie wieksze zmiany w programie, i chce sie zachowac gdzies poprzednia wersja - mozna tak zakomentowac czesc programu, skopiowac ja poza obszar #if...#endif, poprzerabiac, potem sprobowac, jak dziala, i moc latwo wrocic do poprzedniej, zwlaszcza jesli zrobi sie to tak: #if 0 ...stare instrukcje... #else ...nowe instrukcje... #endif i zeby wrocic do starej wersji wystarczy zmienic 0 na inna liczbe, spowoduje to skompilowanie starych instrukcji zamiast nowych. Ale jeszcze wygodniej jest uzyc zamiast #if 0 innej dyrektywy - #if defined(nazwa) lub #ifdef nazwa - obie wersje dzialaja tak samo, instrukcje po tej dyrektywie zostana skompilowane jesli nazwa jest zdefiniowana przez #define nazwa (moze nie miec wartosci); #ifndef rozni sie od #ifdef odwroceniem warunku (nazwa nie zdefiniowana). I moze przyklad - program, ktory uzywa stdio.h lub conio.h: #ifdef USECONIO # include #else # include #endif main(int argc, char **argv) { #ifdef USECONIO cprintf("Jestem %s\r\n", #else printf("Jestem %s\n", #endif *argv); return 0; } Mozna go skompilowac bez zdefiniowania USECONIO, i wtedy uzyje on funkcji printf do pisania na STDOUT (mozna to skierowac na plik wywolujac program przez: nazwa-programu.exe > nazwa-pliku, nazwa pliku moze byc np. NUL jesli nie chce sie tego nigdzie zapisywac), lub zdefiniowac USECONIO (wybrac w menu Options, Compiler, Defines i wpisac USECONIO), i wtedy uzyje cprintf do pisania bezposrednio na ekran (wypisze sie na ekranie nawet gdy uzyje sie >). 3.5. #error Sluzy do zasygnalizowania sytuacji, okreslonej przez definicje (#define, lub w opcjach kompilatora), ktora nie jest sensowna, dla ktorej program nie moze dzialac w sposob przewidziany przez autora. 3.6. #line Sluzy do podania numeru linii i ewentualnie nazwy pliku, ten numer i ta nazwa beda uzyte przy sygnalizacji bledu zamiast rzeczywistego numeru linii i nazwy pliku - ma zastosowanie, gdy tekst programu w C zostal przetlumaczony, i zmienila sie ilosc linii, i nazwa pliku. 3.7. #pragma Sluzy do przekazywania kompilatorowi dyrektyw specyficznych dla niego, na przyklad rodzajow bledow do sygnalizowanie lub nie. Parametry dyrektywy pragma moga byc dla kazdego kompilatora inne. Przyklad: #pragma warn -par - wylacza sygnalizowanie nieuzywanego parametru (main(int argc, char **avec) i nie uzywa sie argc).