Andrej Bogatyrev. Hrestomatiya po programmirovaniyu na Si v Unix © Copyright Andrej Bogatyrev. 1992-95 Email: abs@decart.msu.su ¡ mailto:abs@decart.msu.su Txt version is located at ¡ http://decart.msu.su:100/~abs/
A. Bogatyrev, 1992-95 - 1 - Si v UNIX 0. Naputstvie v kachestve vstupleniya. Um podoben zheludku. Vazhno ne to, skol'ko ty v nego vlozhish', a to, skol'ko on smozhet perevarit'. V etoj knige vy najdete ryad zadach, primerov, algoritmov, sovetov i stilistiches- kih zamechanij po ispol'zovaniyu yazyka programmirovaniya "C" (Si) v srede operacionnoj sistemy UNIX. Zdes' sobrany etyudy raznoj slozhnosti i "shtrihi k portretu" yazyka Si. Takzhe opisany razlichnye "podvodnye kamni" na kotoryh neredko terpyat krushenie novichki v Si. V etom smysle etu knigu mozhno mestami nazvat' "Kak ne nado programmirovat' na Si". V bol'shinstve sluchaev v kachestve platformy ispol'zuetsya personal'nyj komp'yuter IBM PC s kakoj-libo sistemoj UNIX, libo SPARCstation 20 s sistemoj Solaris 2 (tozhe UNIX svr4), no mnogie primery bez kakih-libo izmenenij (libo s minimumom takovyh) mogut byt' pereneseny v sredu MS DOS|=, libo na drugoj tip mashiny s sistemoj UNIX. |to vasha VTORAYA kniga po Si. |ta kniga ne uchebnik, a hrestomatiya k uchebniku. Ona ne yavlyaetsya ni sistematicheskim kursom po Si, ni spravochnikom po nemu, i prednaz- nachena ne dlya odnorazovogo posledovatel'nogo prochteniya, a dlya chteniya v neskol'ko pro- hodov na raznyh etapah vashej "zrelosti". Poetomu chitat' ee sleduet vmeste s "nastoya- shchim" uchebnikom po Si, sredi kotoryh naibolee izvestna kniga Kernigana i Ritchi. |ta kniga - ne POSLEDNYAYA vasha kniga po Si. Vo-pervyh potomu, chto koe-chto v yazyke vse zhe menyaetsya so vremenem, hotya i nastal chas, kogda standart na yazyk Si nakonec prinyat... No poyavilsya yazyk C++, kotoryj razvivaetsya dovol'no dinamichno. Eshche est' Objective-C. Vo-vtoryh potomu, chto est' biblioteki i sistemnye vyzovy, kotorye raz- vivayutsya vsled za razvitiem UNIX i drugih operacionnyh sistem. Sleduyushchimi vashimi (nastol'nymi) knigami dolzhny stat' "Spravochnoe rukovodstvo": man2 (po sistemnym vyzo- vam), man3 (po bibliotechnym funkciyam). Moshch' yazyka Si - v sushchestvuyushchem mnogoobrazii bibliotek. Proshu vas s pervyh zhe shagov sledit' za stilem oformleniya svoih programm. Delajte otstupy, pishite kommentarii, ispol'zujte osmyslennye imena peremennyh i funkcij, otdelyajte logicheskie chasti programmy drug ot druga pustymi strokami. Pomnite, chto "lishnie" probely i pustye stroki v Si dopustimy vezde, krome izobrazhenij konstant i imen. Programmy na Si, nabitye v odnu kolonku (kak na FORTRAN-e) ochen' tyazhelo chitat' i ponimat'. Iz-za etogo byvaet trudno nahodit' poteryannye skobki { i }, poteryannye simvoly `;' i drugie oshibki. Sushchestvuet neskol'ko "shkol" oformleniya programm - priglyadites' k primeram v etoj knige i v drugih istochnikah - i vyberite lyubuyu! Nichego strashnogo, esli vy budete smeshivat' eti stili. No - PODALXSHE OT FORTRAN-a !!! Programmu mozhno avtomaticheski sformatirovat' k "kanonicheskomu" vidu pri pomoshchi, naprimer, programmy cb. cb < NashFajl.c > /tmp/$$ mv /tmp/$$ NashFajl.c no luchshe srazu oformlyat' programmu pravil'no. Vydelyajte logicheski samostoyatel'nye ("zamknutye") chasti programmy v funkcii (dazhe esli oni budut vyzyvat'sya edinstvennyj raz). Funkcii - ne prosto sredstvo izbezhat' povtoreniya odnih i teh zhe operatorov v tekste programmy, no i sredstvo strukturirovaniya processa programmirovaniya, delayushchee programmu bolee ponyatnoj. Vo- pervyh, vy mozhete v drugoj programme ispol'zovat' tekst uzhe napisannoj vami ranee funkcii vmesto togo, chtoby pisat' ee zanovo. Vo-vtoryh, operaciyu, oformlennuyu v vide funkcii, mozhno rassmatrivat' kak nedelimyj primitiv (ot dovol'no prostogo po smyslu, vrode strcmp, strcpy, do dovol'no slozhnogo - qsort, malloc, gets) i zabyt' o ego vnutrennem ustrojstve (eto horosho - nado men'she pomnit'). ____________________ |= MS DOS - torgovyj znak firmy Microsoft Corporation. (chitaetsya "Majkrosoft"); DOS - diskovaya operacionnaya sistema. A. Bogatyrev, 1992-95 - 2 - Si v UNIX Ne gonites' za kratkost'yu v ushcherb yasnosti. Si pozvolyaet poroj pisat' takie vyra- zheniya, nad kotorymi mozhno polchasa lomat' golovu. Esli zhe ih zapisat' menee mudreno, no chut' dlinnee - oni samoochevidny (i etim bolee zashchishcheny ot oshibok). V sisteme UNIX vy mozhete posmotret' opisanie lyuboj komandy sistemy ili funkcii Si, nabrav komandu man nazvanieFunkcii (man - ot slova manual, "rukovodstvo"). Eshche odno naputstvie: uchite anglijskij yazyk! Prakticheski vse yazyki programmirova- niya ispol'zuyut anglijskie slova (v kachestve klyuchevyh slov, terminov, imen peremennyh i funkcij). Poetomu luchshe ponimat' znachenie etih slov (hotya i vospriyatie ih kak prosto nekih simvolov tozhe imeet opredelennye dostoinstva). Obratno - programmirova- nie na Si pomozhet vam vyuchit' anglijskij. Po razlichnym prichinam na territorii Rossii sejchas ispol'zuetsya mnogo raznyh vos'mibitnyh russkih kodirovok. Sredi nih: KOI-8 Istoricheski prinyataya na russkih UNIX sistemah - samaya rannyaya iz poyavivshihsya. Otlichaetsya tem svojstvom, chto esli u nee obrezan vos'moj bit: c & 0177 - to ona vse zhe chitaema s terminala kak transliteraciya latinskih bukv. Imenno etoj kodi- rovkoj pol'zuetsya avtor etoj knigi (kak i bol'shinstvo UNIX-sites seti RelCom). ISO 8859/5 |to amerikanskij standart na russkuyu kodirovku. A russkie programmisty k ee razrabotke ne imeyut nikakogo otnosheniya. Eyu pol'zuetsya bol'shinstvo kommercheskih baz dannyh. Microsoft 1251 |to ta kodirovka, kotoroj pol'zuetsya Microsoft Windows. Vozmozhno, chto imenno k etoj kodirovke pridut i UNIX sistemy (gipoteza 1994 goda). Al'ternativnaya kodirovka dlya MS DOS Russkaya kodirovka s psevdografikoj, ispol'zovavshayasya v MS DOS. Kodirovka dlya Macintosh |to velikoe "raznoobrazie" prichinyaet massu neudobstv. No, gospoda, eto Rossiya - chto znachit - shirota dushi i absolyutnyj bardak. Relax and enjoy. Mnogie primery v dannoj knige dany vmeste s otvetami - kak obrazcami dlya podra- zhaniya. Odnako my nadeemsya, chto Vy uderzhites' ot iskusheniya i snachala proverite svoi sily, a lish' potom posmotrite v otvet! Itak, chitaya primery - delajte po analogii. A. Bogatyrev, 1992-95 - 3 - Si v UNIX 1. Prostye programmy i algoritmy. Syurprizy, sovety. 1.1. Sostav'te programmu privetstviya s ispol'zovaniem funkcii printf. Po tradicii prinyato pechatat' frazu "Hello, world !" ("Zdravstvuj, mir !"). 1.2. Najdite oshibku v programme #include <stdio.h> main(){ printf("Hello, world\n"); } Otvet: raz ne ob®yavleno inache, funkciya main schitaetsya vozvrashchayushchej celoe znachenie (int). No funkciya main ne vozvrashchaet nichego - v nej prosto net operatora return. Korrektno bylo by tak: #include <stdio.h> main(){ printf("Hello, world\n"); return 0; } ili #include <stdio.h> void main(){ printf("Hello, world\n"); exit(0); } a uzh sovsem korrektno - tak: #include <stdio.h> int main(int argc, char *argv[]){ printf("Hello, world\n"); return 0; } 1.3. Najdite oshibki v programme #include studio.h main { int i i := 43 print ('V godu i nedel'') } 1.4. CHto budet napechatano v privedennom primere, kotoryj yavlyaetsya chast'yu polnoj programmy: int n; n = 2; printf ("%d + %d = %d\n", n, n, n + n); 1.5. V chem sostoyat oshibki? A. Bogatyrev, 1992-95 - 4 - Si v UNIX if( x > 2 ) then x = 2; if x < 1 x = 1; Otvet: v Si net klyuchevogo slova then, usloviya v operatorah if, while dolzhny brat'sya v ()-skobki. 1.6. Napishite programmu, pechatayushchuyu vashe imya, mesto raboty i adres. V pervom vari- ante programmy ispol'zujte bibliotechnuyu funkciyu printf, a vo vtorom - puts. 1.7. Sostav'te programmu s ispol'zovaniem sleduyushchih postfiksnyh i prefiksnyh opera- cij: a = b = 5 a + b a++ + b ++a + b --a + b a-- + b Raspechatajte poluchennye znacheniya i proanalizirujte rezul'tat. 1.8. Cikl for ________________________________________________________________________________ for(INIT; CONDITION; INCR) BODY ________________________________________________________________________________ INIT; repeat: if(CONDITION){ BODY; cont: INCR; goto repeat; } out: ; Cikl while ________________________________________________________________________________ while(COND) BODY ________________________________________________________________________________ cont: repeat: if(CONDITION){ BODY; goto repeat; } out: ; A. Bogatyrev, 1992-95 - 5 - Si v UNIX Cikl do ________________________________________________________________________________ do BODY while(CONDITION) ________________________________________________________________________________ cont: repeat: BODY; if(CONDITION) goto repeat; out: ; V operatorah cikla vnutri tela cikla BODY mogut prisutstvovat' operatory break i continue; kotorye oznachayut na nashih shemah sleduyushchee: #define break goto out #define continue goto cont 1.9. Sostav'te programmu pechati pryamougol'nogo treugol'nika iz zvezdochek * ** *** **** ***** ispol'zuya cikl for. Vvedite peremennuyu, znacheniem kotoroj yavlyaetsya razmer kateta tre- ugol'nika. 1.10. Napishite operatory Si, kotorye vydayut stroku dliny WIDTH, v kotoroj snachala soderzhitsya x0 simvolov '-', zatem w simvolov '*', i do konca stroki - vnov' simvoly '-'. Otvet: int x; for(x=0; x < x0; ++x) putchar('-'); for( ; x < x0 + w; x++) putchar('*'); for( ; x < WIDTH ; ++x) putchar('-'); putchar('\n'); libo for(x=0; x < WIDTH; x++) putchar( x < x0 ? '-' : x < x0 + w ? '*' : '-' ); putchar('\n'); 1.11. Napishite programmu s ciklami, kotoraya risuet treugol'nik: * *** ***** ******* ********* A. Bogatyrev, 1992-95 - 6 - Si v UNIX Otvet: /* Treugol'nik iz zvezdochek */ #include <stdio.h> /* Pechat' n simvolov c */ printn(c, n){ while( --n >= 0 ) putchar(c); } int lines = 10; /* chislo strok treugol'nika */ void main(argc, argv) char *argv[]; { register int nline; /* nomer stroki */ register int naster; /* kolichestvo zvezdochek v stroke */ register int i; if( argc > 1 ) lines = atoi( argv[1] ); for( nline=0; nline < lines ; nline++ ){ naster = 1 + 2 * nline; /* lidiruyushchie probely */ printn(' ', lines-1 - nline); /* zvezdochki */ printn('*', naster); /* perevod stroki */ putchar( '\n' ); } exit(0); /* zavershenie programmy */ } 1.12. V chem sostoit oshibka? main(){ /* pechat' frazy 10 raz */ int i; while(i < 10){ printf("%d-yj raz\n", i+1); i++; } } Otvet: avtomaticheskaya peremennaya i ne byla proinicializirovana i soderzhit ne 0, a kakoe-to proizvol'noe znachenie. Cikl mozhet vypolnit'sya ne 10, a lyuboe chislo raz (v tom chisle i 0 po sluchajnosti). Ne zabyvajte inicializirovat' peremennye, voz'mite opisanie s inicializaciej za pravilo! int i = 0; Esli by peremennaya i byla staticheskoj, ona by imela nachal'noe znachenie 0. V dannom primere bylo by eshche luchshe ispol'zovat' cikl for, v kotorom vse operacii nad indeksom cikla sobrany v odnom meste - v zagolovke cikla: for(i=0; i < 10; i++) printf(...); A. Bogatyrev, 1992-95 - 7 - Si v UNIX 1.13. Vspomogatel'nye peremennye, ne nesushchie smyslovoj nagruzki (vrode schetchika pov- torenij cikla, ne ispol'zuemogo v samom tele cikla) prinyato po tradicii oboznachat' odnobukvennymi imenami, vrode i, j. Bolee togo, vozmozhny dazhe takie kur'ezy: main(){ int _ ; for( _ = 0; _ < 10; _++) printf("%d\n", _ ); } osnovannye na tom, chto podcherk v identifikatorah - polnopravnaya bukva. 1.14. Najdite 2 oshibki v programme: main(){ int x = 12; printf( "x=%d\n" ); int y; y = 2 * x; printf( "y=%d\n", y ); } Kommentarij: v tele funkcii vse opisaniya dolzhny idti pered vsemi vypolnyaemymi opera- torami (krome operatorov, vhodyashchih v sostav opisanij s inicializaciej). Ochen' chasto posle vneseniya pravok v programmu nekotorye opisaniya okazyvayutsya posle vypolnyaemyh operatorov. Imenno poetomu rekomenduetsya otdelyat' stroki opisaniya peremennyh ot vypolnyaemyh operatorov pustymi strokami (v etoj knige eto chasto ne delaetsya dlya eko- nomii mesta). 1.15. Najdite oshibku: int n; n = 12; main(){ int y; y = n+2; printf( "%d\n", y ); } Otvet: vypolnyaemyj operator n=12 nahoditsya vne tela kakoj-libo funkcii. Sleduet vnesti ego v main() posle opisaniya peremennoj y, libo perepisat' ob®yavlenie pered main() v vide int n = 12; V poslednem sluchae prisvaivanie peremennoj n znacheniya 12 vypolnit kompilyator eshche vo vremya kompilyacii programmy, a ne sama programma pri svoem zapuske. Tochno tak zhe pro- ishodit so vsemi staticheskimi dannymi (opisannymi kak static, libo raspolozhennymi vne vseh funkcij); prichem esli ih nachal'noe znachenie ne ukazano yavno - to podrazumevaetsya 0 ('\0', NULL, ""). Odnako nulevye znacheniya ne hranyatsya v skompilirovannom vypolnyae- mom fajle, a trebuemaya "chistaya" pamyat' raspisyvaetsya pri starte programmy. 1.16. Po povodu opisaniya peremennoj s inicializaciej: TYPE x = vyrazhenie; yavlyaetsya (pochti) ekvivalentom dlya TYPE x; /* opisanie */ x = vyrazhenie; /* vychislenie nachal'nogo znacheniya */ A. Bogatyrev, 1992-95 - 8 - Si v UNIX Rassmotrim primer: #include <stdio.h> extern double sqrt(); /* kvadratnyj koren' */ double x = 1.17; double s12 = sqrt(12.0); /* #1 */ double y = x * 2.0; /* #2 */ FILE *fp = fopen("out.out", "w"); /* #3 */ main(){ double ss = sqrt(25.0) + x; /* #4 */ ... } Stroki s metkami #1, #2 i #3 oshibochny. Pochemu? Otvet: pri inicializacii staticheskih dannyh (a s12, y i fp takovymi i yavlyayutsya, tak kak opisany vne kakoj-libo funkcii) vyrazhenie dolzhno soderzhat' tol'ko konstanty, poskol'ku ono vychislyaetsya KOMPILYATOROM. Poetomu ni ispol'zovanie znachenij peremennyh, ni vyzovy funkcij zdes' nedopustimy (no mozhno brat' adresa ot peremennyh). V stroke #4 my inicializiruem avtomaticheskuyu peremennuyu ss, t.e. ona otvoditsya uzhe vo vremya vypolneniya programmy. Poetomu vyrazhenie dlya inicializacii vychislyaetsya uzhe ne kompilyatorom, a samoj programmoj, chto daet nam pravo ispol'zovat' peremennye, vyzovy funkcij i.t.p., to est' vyrazheniya yazyka Si bez ogranichenij. 1.17. Napishite programmu, realizuyushchuyu eho-pechat' vvodimyh simvolov. Programma dolzhna zavershat' rabotu pri poluchenii priznaka EOF. V UNIX pri vvode s klaviatury priznak EOF obychno oboznachaetsya odnovremennym nazhatiem klavish CTRL i D (CTRL chut' ran'she), chto v dal'nejshem budet oboznachat'sya CTRL/D; a v MS DOS - klavish CTRL/Z. Ispol'zujte getchar() dlya vvoda bukvy i putchar() dlya vyvoda. 1.18. Napishite programmu, podschityvayushchuyu chislo simvolov postupayushchih so standartnogo vvoda. Kakie dostoinstva i nedostatki u sleduyushchej realizacii: #include <stdio.h> main(){ double cnt = 0.0; while (getchar() != EOF) ++cnt; printf("%.0f\n", cnt ); } Otvet: i dostoinstvo i nedostatok v tom, chto schetchik imeet tip double. Dostoinstvo - mozhno podschitat' ochen' bol'shoe chislo simvolov; nedostatok - operacii s double obychno vypolnyayutsya gorazdo medlennee, chem s int i long (do desyati raz), programma budet rabotat' dol'she. V povsednevnyh zadachah vam vryad li ponadobitsya imet' schetchik, otlichnyj ot long cnt; (pechatat' ego nado po formatu "%ld"). 1.19. Sostav'te programmu perekodirovki vvodimyh simvolov so standartnogo vvoda po sleduyushchemu pravilu: a -> b b -> c c -> d ... z -> a drugoj simvol -> * Kody strochnyh latinskih bukv raspolozheny podryad po vozrastaniyu. 1.20. Sostav'te programmu perekodirovki vvodimyh simvolov so standartnogo vvoda po sleduyushchemu pravilu: A. Bogatyrev, 1992-95 - 9 - Si v UNIX B -> A C -> B ... Z -> Y drugoj simvol -> * Kody propisnyh latinskih bukv takzhe raspolozheny po vozrastaniyu. 1.21. Napishite programmu, pechatayushchuyu nomer i kod vvedennogo simvola v vos'merichnom i shestnadcaterichnom vide. Zamet'te, chto esli vy naberete na vvode stroku simvolov i nazhmete klavishu ENTER, to programma napechataet vam na odin simvol bol'she, chem vy nab- rali. Delo v tom, chto kod klavishi ENTER, zavershivshej vvod stroki - simvol '\n' - tozhe popadaet v vashu programmu (na ekrane on otobrazhaetsya kak perevod kursora v nachalo sleduyushchej stroki!). 1.22. Razberites', v chem sostoit raznica mezhdu simvolami '0' (cifra nul') i '\0' (nulevoj bajt). Napechatajte printf( "%d %d %c\n", '\0', '0', '0' ); Postav'te opyt: chto pechataet programma? main(){ int c = 060; /* kod simvola '0' */ printf( "%c %d %o\n", c, c, c); } Pochemu pechataetsya 0 48 60? Teper' napishite vmesto int c = 060; strochku char c = '0'; 1.23. CHto napechataet programma? #include <stdio.h> void main(){ printf("ab\0cd\nxyz"); putchar('\n'); } Zapomnite, chto '\0' sluzhit priznakom konca stroki v pamyati, a '\n' - v fajle. CHto v stroke "abcd\n" na konce neyavno uzhe raspolozhen nulevoj bajt: 'a','b','c','d','\n','\0' CHto stroka "ab\0cd\nxyz" - eto 'a','b','\0','c','d','\n','x','y',z','\0' CHto stroka "abcd\0" - izbytochna, poskol'ku budet imet' na konce dva nulevyh bajta (chto ne vredno, no zachem?). CHto printf pechataet stroku do nulevogo bajta, a ne do zakryvayushchej kavychki. Programma eta napechataet ab i perevod stroki. Vopros: chemu raven sizeof("ab\0cd\nxyz")? Otvet: 10. 1.24. Napishite programmu, pechatayushchuyu celye chisla ot 0 do 100. 1.25. Napishite programmu, pechatayushchuyu kvadraty i kuby celyh chisel. A. Bogatyrev, 1992-95 - 10 - Si v UNIX 1.26. Napishite programmu, pechatayushchuyu summu kvadratov pervyh n celyh chisel. 1.27. Napishite programmu, kotoraya perevodit sekundy v dni, chasy, minuty i sekundy. 1.28. Napishite programmu, perevodyashchuyu skorost' iz kilometrov v chas v metry v sekun- dah. 1.29. Napishite programmu, shifruyushchuyu tekst fajla putem zameny znacheniya simvola (nap- rimer, znachenie simvola C zamenyaetsya na C+1 ili na ~C ). 1.30. Napishite programmu, kotoraya pri vvedenii s klaviatury bukvy pechataet na termi- nale klyuchevoe slovo, nachinayushcheesya s dannoj bukvy. Naprimer, pri vvedenii bukvy 'b' pechataet "break". 1.31. Napishite programmu, otgadyvayushchuyu zadumannoe vami chislo v predelah ot 1 do 200, pol'zuyas' podskazkoj s klaviatury "=" (ravno), "<" (men'she) i ">" (bol'she). Dlya uga- dyvaniya chisla ispol'zujte metod deleniya popolam. 1.32. Napishite programmu, pechatayushchuyu stepeni dvojki 1, 2, 4, 8, ... Zamet'te, chto, nachinaya s nekotorogo n, rezul'tat stanovitsya otricatel'nym iz-za pere- polneniya celogo. 1.33. Napishite podprogrammu vychisleniya kvadratnogo kornya s ispol'zovaniem metoda kasatel'nyh (N'yutona): x(0) = a 1 a x(n+1) = - * ( ---- + x(n)) 2 x(n) Iterirovat', poka ne budet | x(n+1) - x(n) | < 0.001 Vnimanie! V dannoj zadache massiv ne nuzhen. Dostatochno hranit' tekushchee i predydu- shchee znacheniya x i obnovlyat' ih posle kazhdoj iteracii. 1.34. Napishite programmu, raspechatyvayushchuyu prostye chisla do 1000. 1, 2, 3, 5, 7, 11, 13, 17, ... A. Bogatyrev, 1992-95 - 11 - Si v UNIX /*#!/bin/cc primes.c -o primes -lm * Prostye chisla. */ #include <stdio.h> #include <math.h> int debug = 0; /* Koren' kvadratnyj iz chisla po metodu N'yutona */ #define eps 0.0001 double sqrt (x) double x; { double sq, sqold, EPS; if (x < 0.0) return -1.0; if (x == 0.0) return 0.0; /* mozhet privesti k deleniyu na 0 */ EPS = x * eps; sq = x; sqold = x + 30.0; /* != sq */ while (fabs (sq * sq - x) >= EPS) { /* fabs( sq - sqold )>= EPS */ sqold = sq; sq = 0.5 * (sq + x / sq); } return sq; } /* tablica prostyh chisel */ int is_prime (t) register int t; { register int i, up; int not_div; if (t == 2 || t == 3 || t == 5 || t == 7) return 1; /* prime */ if (t % 2 == 0 || t == 1) return 0; /* composite */ up = ceil (sqrt ((double) t)) + 1; i = 3; not_div = 1; while (i <= up && not_div) { if (t % i == 0) { if (debug) fprintf (stderr, "%d podelilos' na %d\n", t, i); not_div = 0; break; } i += 2; /* * Net smysla proveryat' chetnye, * potomu chto esli delitsya na 2*n, * to delitsya i na 2, * a etot sluchaj uzhe obrabotan vyshe. */ } return not_div; } A. Bogatyrev, 1992-95 - 12 - Si v UNIX #define COL 6 int n; main (argc, argv) char **argv; { int i, j; int n; if( argc < 2 ){ fprintf( stderr, "Vyzov: %s chislo [-]\n", argv[0] ); exit(1); } i = atoi (argv[1]); /* stroka -> celoe, eyu izobrazhaemoe */ if( argc > 2 ) debug = 1; printf ("\t*** Tablica prostyh chisel ot 2 do %d ***\n", i); n = 0; for (j = 1; j <= i; j++) if (is_prime (j)){ /* raspechatka v COL kolonok */ printf ("%3d%s", j, n == COL-1 ? "\n" : "\t"); if( n == COL-1 ) n = 0; else n++; } printf( "\n---\n" ); exit (0); } 1.35. Sostav'te programmu vvoda dvuh kompleksnyh chisel v vide A + B * I (kazhdoe na otdel'noj stroke) i pechati ih proizvedeniya v tom zhe vide. Ispol'zujte scanf i printf. Pered tem, kak ispol'zovat' scanf, prover'te sebya: chto neverno v nizheprivedennom ope- ratore? int x; scanf( "%d", x ); Otvet: dolzhno byt' napisano "ADRES ot x", to est' scanf( "%d", &x ); 1.36. Napishite podprogrammu vychisleniya kornya uravneniya f(x)=0 metodom deleniya otrezka popolam. Privedem realizaciyu etogo algoritma dlya poiska celochislennogo kvad- ratnogo kornya iz celogo chisla (etot algoritm mozhet ispol'zovat'sya, naprimer, v mashin- noj grafike pri risovanii dug): /* Maksimal'noe unsigned long chislo */ #define MAXINT (~0L) /* Opredelim imya-sinonim dlya tipa unsigned long */ typedef unsigned long ulong; /* Funkciya, koren' kotoroj my ishchem: */ #define FUNC(x, arg) ((x) * (x) - (arg)) /* togda x*x - arg = 0 oznachaet x*x = arg, to est' * x = koren'_kvadratnyj(arg) */ /* Nachal'nyj interval. Dolzhen vybirat'sya ishodya iz * osobennostej funkcii FUNC */ #define LEFT_X(arg) 0 #define RIGHT_X(arg) (arg > MAXINT)? MAXINT : (arg/2)+1; /* KORENX KVADRATNYJ, okruglennyj vniz do celogo. * Reshaetsya po metodu deleniya otrezka popolam: * FUNC(x, arg) = 0; x = ? A. Bogatyrev, 1992-95 - 13 - Si v UNIX */ ulong i_sqrt( ulong arg ) { register ulong mid, /* seredina intervala */ rgt, /* pravyj kraj intervala */ lft; /* levyj kraj intervala */ lft = LEFT_X(arg); rgt = RIGHT_X(arg); do{ mid = (lft + rgt + 1 )/2; /* +1 dlya oshibok okrugleniya pri celochislennom delenii */ if( FUNC(mid, arg) > 0 ){ if( rgt == mid ) mid--; rgt = mid ; /* priblizit' pravyj kraj */ } else lft = mid ; /* priblizit' levyj kraj */ } while( lft < rgt ); return mid; } void main(){ ulong i; for(i=0; i <= 100; i++) printf("%ld -> %lu\n", i, i_sqrt(i)); } Ispol'zovannoe nami pri ob®yavlenii peremennyh klyuchevoe slovo register oznachaet, chto peremennaya yavlyaetsya CHASTO ISPOLXZUEMOJ, i kompilyator dolzhen popytat'sya razmestit' ee na registre processora, a ne v steke (za schet chego uvelichitsya skorost' obrashcheniya k etoj peremennoj). |to slovo ispol'zuetsya kak register tip peremennaya; register peremennaya; /* podrazumevaetsya tip int */ Ot registrovyh peremennyh nel'zya brat' adres: &peremennaya oshibochno. 1.37. Napishite programmu, vychislyayushchuyu chisla treugol'nika Paskalya i pechatayushchuyu ih v vide treugol'nika. C(0,n) = C(n,n) = 1 n = 0... C(k,n+1) = C(k-1,n) + C(k,n) k = 1..n n - nomer stroki V raznyh variantah ispol'zujte cikly, rekursiyu. 1.38. Napishite funkciyu vychisleniya opredelennogo integrala metodom Monte-Karlo. Dlya etogo vam pridetsya napisat' generator sluchajnyh chisel. Si predostavlyaet standartnyj datchik CELYH ravnomerno raspredelennyh psevdosluchajnyh chisel: esli vy hotite poluchit' celoe chislo iz intervala [A..B], ispol'zujte int x = A + rand() % (B+1-A); CHtoby poluchat' raznye posledovatel'nosti sleduet zadavat' nekij nachal'nyj parametr posledovatel'nosti (eto nazyvaetsya "randomizaciya") pri pomoshchi srand( chislo ); /* luchshe nechetnoe */ CHtoby povtorit' odnu i tu zhe posledovatel'nost' sluchajnyh chisel neskol'ko raz, vy dolzhny postupat' tak: srand(NBEG); x=rand(); ... ; x=rand(); /* i povtorit' vse snachala */ srand(NBEG); x=rand(); ... ; x=rand(); Ispol'zuemyj metod polucheniya sluchajnyh chisel takov: A. Bogatyrev, 1992-95 - 14 - Si v UNIX static unsigned long int next = 1L; int rand(){ next = next * 1103515245 + 12345; return ((unsigned int)(next/65536) % 32768); } void srand(seed) unsigned int seed; { next = seed; } Dlya randomizacii chasto pol'zuyutsya takim priemom: char t[sizeof(long)]; time(t); srand(t[0] + t[1] + t[2] + t[3] + getpid()); 1.39. Napishite funkciyu vychisleniya opredelennogo integrala po metodu Simpsona. /*#!/bin/cc $* -lm * Vychislenie integrala po metodu Simpsona */ #include <math.h> extern double integral(), sin(), fabs(); #define PI 3.141593 double myf(x) double x; { return sin(x / 2.0); } int niter; /* nomer iteracii */ void main(){ double integral(); printf("%g\n", integral(0.0, PI, myf, 0.000000001)); /* Zamet'te, chto myf, a ne myf(). * Tochnoe znachenie integrala ravno 2.0 */ printf("%d iteracij\n", niter ); } A. Bogatyrev, 1992-95 - 15 - Si v UNIX double integral(a, b, f, eps) double a, b; /* koncy otrezka */ double eps; /* trebuemaya tochnost' */ double (*f)(); /* podyntegral'naya funkciya */ { register long i; double fab = (*f)(a) + (*f)(b); /* summa na krayah */ double h, h2; /* shag i udvoennyj shag */ long n, n2; /* chislo tochek razbieniya i ono zhe udvoennoe */ double Sodd, Seven; /* summa znachenij f v nechetnyh i v chetnyh tochkah */ double S, Sprev;/* znachenie integrala na dannoj i na predydushchej iteraciyah */ double x; /* tekushchaya abscissa */ niter = 0; n = 10L; /* chetnoe chislo */ n2 = n * 2; h = fabs(b - a) / n2; h2 = h * 2.0; /* Vychislyaem pervoe priblizhenie */ /* Summa po nechetnym tochkam: */ for( Sodd = 0.0, x = a+h, i = 0; i < n; i++, x += h2 ) Sodd += (*f)(x); /* Summa po chetnym tochkam: */ for( Seven = 0.0, x = a+h2, i = 0; i < n-1; i++, x += h2 ) Seven += f(x); /* Predvaritel'noe znachenie integrala: */ S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); do{ niter++; Sprev = S; /* Vychislyaem integral s polovinnym shagom */ h2 = h; h /= 2.0; if( h == 0.0 ) break; /* poterya znachimosti */ n = n2; n2 *= 2; Seven = Seven + Sodd; /* Vychislyaem summu po novym tochkam: */ for( Sodd = 0.0, x = a+h, i = 0; i < n; i++, x += h2 ) Sodd += (*f)(x); /* Znachenie integrala */ S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); } while( niter < 31 && fabs(S - Sprev) / 15.0 >= eps ); /* Ispol'zuem uslovie Runge dlya okonchaniya iteracij */ return ( 16.0 * S - Sprev ) / 15.0 ; /* Vozvrashchaem utochnennoe po Richardsonu znachenie */ } A. Bogatyrev, 1992-95 - 16 - Si v UNIX 1.40. Gde oshibka? struct time_now{ int hour, min, sec; } X = { 13, 08, 00 }; /* 13 chasov 08 minut 00 sek.*/ Otvet: 08 - vos'merichnoe chislo (tak kak nachinaetsya s nulya)! A v vos'merichnyh chislah cifry 8 i 9 ne byvayut. 1.41. Dan tekst: int i = -2; i <<= 2; printf("%d\n", i); /* pechat' sdvinutogo i : -8 */ i >>= 2; printf("%d\n", i); /* pechataetsya -2 */ Zakommentiruem dve stroki (isklyuchaya ih iz programmy): int i = -2; i <<= 2; /* printf("%d\n", i); /* pechat' sdvinutogo i : -8 */ i >>= 2; */ printf("%d\n", i); /* pechataetsya -2 */ Pochemu teper' voznikaet oshibka? Ukazanie: gde konchaetsya kommentarij? Otvet: Si ne dopuskaet vlozhennyh kommentariev. Vmesto etogo chasto ispol'zuyutsya konstrukcii vrode: #ifdef COMMENT ... zakommentirovannyj tekst ... #endif /*COMMENT*/ i vrode /**/ printf("here");/* otladochnaya vydacha vklyuchena */ /* printf("here");/* otladochnaya vydacha vyklyuchena */ ili /* vyklyucheno(); /**/ vklyucheno(); /**/ A vot deshevyj sposob bystro isklyuchit' operator (s vozmozhnost'yu vosstanovleniya) - konec kommentariya zanimaet otdel'nuyu stroku, chto pozvolyaet otredaktirovat' takoj tekst redaktorom pochti ne sdvigaya kursor: /*printf("here"); */ 1.42. Pochemu programma pechataet nevernoe znachenie dlya i2 ? A. Bogatyrev, 1992-95 - 17 - Si v UNIX int main(int argc, char *argv[]){ int i1, i2; i1 = 1; /* Inicializiruem i1 / i2 = 2; /* Inicializiruem i2 */ printf("Numbers %d %d\n", i1, i2); return(0); } Otvet: v pervom operatore prisvaivaniya ne zakryt kommentarij - ves' vtoroj operator prisvaivaniya polnost'yu proignorirovalsya! Pravil'nyj variant: int main(int argc, char *argv[]){ int i1, i2; i1 = 1; /* Inicializiruem i1 */ i2 = 2; /* Inicializiruem i2 */ printf("Numbers %d %d\n", i1, i2); return(0); } 1.43. A vot "shal'noj" kommentarij. void main(){ int n = 10; int *ptr = &n; int x, y = 40; x = y/*ptr /* dolzhno byt' 4 */ + 1; printf( "%d\n", x ); /* pyat' */ exit(0); } /* ili takoj primer iz zhizni - vzyat iz perepiski v Relcom */ ... cost = nRecords/*pFactor /* divided by Factor, and */ + fixMargin; /* plus the precalculated */ ... Rezul'tat nepredskazuem. Delo v tom, chto y/*ptr prevratilos' v nachalo kommentariya! Poetomu binarnye operacii prinyato okruzhat' probelami. x = y / *ptr /* dolzhno byt' 4 */ + 1; 1.44. Najdite oshibki v direktivah preprocessora Si |- (vertikal'naya cherta oboznachaet levyj kraj fajla). ____________________ |- Preprocessor Si - eto programma /lib/cpp A. Bogatyrev, 1992-95 - 18 - Si v UNIX | | #include <stdio.h> |#include < sys/types.h > |# define inc (x) ((x) + 1) |#define N 12; |#define X -2 | |... printf( "n=%d\n", N ); |... p = 4-X; Otvet: v pervoj direktive stoit probel pered #. Diez dolzhen nahodit'sya v pervoj pozicii stroki. Vo vtoroj direktive v <> nahodyatsya lishnie probely, ne otnosyashchiesya k imeni fajla - preprocessor ne najdet takogo fajla! V dannom sluchae "krasota" poshla vo vred delu. V tret'ej - mezhdu imenem makro inc i ego argumentom v kruglyh skobkah (x) stoit probel, kotoryj izmenyaet ves' smysl makroopredeleniya: vmesto makrosa s parametrom inc(x) my poluchaem, chto slovo inc budet zamenyat'sya na (x)((x)+1). Zametim odnako, chto probely posle # pered imenem direktivy vpolne dopustimy. V chetvertom sluchae pokazana harakternaya opechatka - simvol ; posle opredeleniya. V rezul'tate napi- sannyj printf() zamenitsya na printf( "n=%d\n", 12; ); gde lishnyaya ; dast sintaksicheskuyu oshibku. V pyatom sluchae oshibki net, no nas ozhidaet nepriyatnost' v stroke p=4-X; kotoraya rasshiritsya v stroku p=4--2; yavlyayushchuyusya sintaksicheski nevernoj. CHtoby izbezhat' podob- noj situacii, sledovalo by napisat' p = 4 - X; /* cherez probely */ no eshche proshche (i luchshe) vzyat' makroopredelenie v skobki: #define X (-2) 1.45. Napishite funkciyu max(x, y), vozvrashchayushchuyu bol'shee iz dvuh znachenij. Napishite analogichnoe makroopredelenie. Napishite makroopredeleniya min(x, y) i abs(x) (abs - modul' chisla). Otvet: #define abs(x) ((x) < 0 ? -(x) : (x)) #define min(x,y) (((x) < (y)) ? (x) : (y)) Zachem x vzyat v kruglye skobki (x)? Predpolozhim, chto my napisali #define abs(x) (x < 0 ? -x : x ) vyzyvaem abs(-z) abs(a|b) poluchaem (-z < 0 ? --z : -z ) (a|b < 0 ? -a|b : a|b ) U nas poyavilas' "dikaya" operaciya --z; a vyrazhenie a|b<0 sootvetstvuet a|(b<0), s sov- sem drugim poryadkom operacij! Poetomu zaklyuchenie vseh argumentov makrosa v ego tele v kruglye skobki pozvolyaet izbezhat' mnogih neozhidannyh problem. Priderzhivajtes' etogo pravila! Vot primer, pokazyvayushchij zachem polezno brat' v skobki vse opredelenie: #define div(x, y) (x)/(y) Pri vyzove A. Bogatyrev, 1992-95 - 19 - Si v UNIX z = sizeof div(1, 2); prevratitsya v z = sizeof(1) / (2); chto ravno sizeof(int)/2, a ne sizeof(int). Variant #define div(x, y) ((x) / (y)) budet rabotat' pravil'no. 1.46. Makrosy, v otlichie ot funkcij, mogut porozhdat' nepredvidennye pobochnye effekty: int sqr(int x){ return x * x; } #define SQR(x) ((x) * (x)) main(){ int y=2, z; z = sqr(y++); printf("y=%d z=%d\n", y, z); y = 2; z = SQR(y++); printf("y=%d z=%d\n", y, z); } Vyzov funkcii sqr pechataet "y=3 z=4", kak my i ozhidali. Makros zhe SQR rasshiryaetsya v z = ((y++) * (y++)); i rezul'tatom budet "y=4 z=6", gde z sovsem ne pohozhe na kvadrat chisla 2. 1.47. ANSI preprocessor|- yazyka Si imeet operator ## - "sklejka leksem": #define VAR(a, b) a ## b #define CV(x) command_ ## x main(){ int VAR(x, 31) = 1; /* prevratitsya v int x31 = 1; */ int CV(a) = 2; /* dast int command_a = 2; */ ... } Starye versii preprocessora ne obrabatyvayut takoj operator, poetomu ran'she ispol'zo- valsya takoj tryuk: #define VAR(a, b) a/**/b v kotorom predpolagaetsya, chto preprocessor udalyaet kommentarii iz teksta, ne zamenyaya ih na probely. |to ne vsegda tak, poetomu takaya konstrukciya ne mobil'na i pol'zo- vat'sya eyu ne rekomenduetsya. 1.48. Napishite programmu, raspechatyvayushchuyu maksimal'noe i minimal'noe iz ryada chisel, vvodimyh s klaviatury. Ne hranite vvodimye chisla v massive, vychislyajte max i min srazu pri vvode ocherednogo chisla! ____________________ |- ANSI -