American National Standards Institute, razrabotavshij standart na yazyk Si i ego okruzhenie. A. Bogatyrev, 1992-95 - 20 - Si v UNIX #include <stdio.h> main(){ int max, min, x, n; for( n=0; scanf("%d", &x) != EOF; n++) if( n == 0 ) min = max = x; else{ if( x > max ) max = x; if( x < min ) min = x; } printf( "Vveli %d chisel: min=%d max=%d\n", n, min, max); } Napishite analogichnuyu programmu dlya poiska maksimuma i minimuma sredi elementov mas- siva, iznachal'no min=max=array[0]; 1.49. Napishite programmu, kotoraya sortiruet massiv zadannyh chisel po vozrastaniyu (ubyvaniyu) metodom puzyr'kovoj sortirovki. Kogda vy stanete bolee opytny v Si, napi- shite sortirovku metodom SHella. /* * Sortirovka po metodu SHella. * Sortirovke podvergaetsya massiv ukazatelej na dannye tipa obj. * v------.-------.------.-------.------0 * ! ! ! ! * * * * * * elementy tipa obj * Programma vzyata iz knigi Kernigana i Ritchi. */ #include <stdio.h> #include <string.h> #include <locale.h> #define obj char static shsort (v,n,compare) int n; /* dlina massiva */ obj *v[]; /* massiv ukazatelej */ int (*compare)(); /* funkciya sravneniya sosednih elementov */ { int g, /* rasstoyanie, na kotorom proishodit sravnenie */ i,j; /* indeksy sravnivaemyh elementov */ obj *temp; for( g = n/2 ; g > 0 ; g /= 2 ) for( i = g ; i < n ; i++ ) for( j = i-g ; j >= 0 ; j -= g ) { if((*compare)(v[j],v[j+g]) <= 0) break; /* uzhe v pravil'nom poryadke */ /* obmenyat' ukazateli */ temp = v[j]; v[j] = v[j+g]; v[j+g] = temp; /* V kachestve uprazhneniya mozhete napisat' * pri pomoshchi curses-a programmu, * vizualiziruyushchuyu process sortirovki: * naprimer, izobrazhayushchuyu etu perestanovku * elementov massiva */ } } A. Bogatyrev, 1992-95 - 21 - Si v UNIX /* sortirovka strok */ ssort(v) obj **v; { extern less(); /* funkciya sravneniya strok */ int len; /* podschet chisla strok */ len=0; while(v[len]) len++; shsort(v,len,less); } /* Funkciya sravneniya strok. * Vernut' celoe men'she nulya, esli a < b * nol', esli a == b * bol'she nulya, esli a > b */ less(a,b) obj *a,*b; { return strcoll(a,b); /* strcoll - analog strcmp, * no s uchetom alfavitnogo poryadka bukv. */ } char *strings[] = { "YAsha", "Fedya", "Kolya", "Grisha", "Serezha", "Misha", "Andrej Ivanovich", "Vas'ka", NULL }; int main(){ char **next; setlocale(LC_ALL, ""); ssort( strings ); /* raspechatka */ for( next = strings ; *next ; next++ ) printf( "%s\n", *next ); return 0; } 1.50. Realizujte algoritm bystroj sortirovki. A. Bogatyrev, 1992-95 - 22 - Si v UNIX /* Algoritm bystroj sortirovki. Rabota algoritma "animiruetsya" * (animate-ozhivlyat') pri pomoshchi biblioteki curses. * cc -o qsort qsort.c -lcurses -ltermcap */ #include "curses.h" #define N 10 /* dlina massiva */ /* massiv, podlezhashchij sortirovke */ int target [N] = { 7, 6, 10, 4, 2, 9, 3, 8, 5, 1 }; int maxim; /* maksimal'nyj element massiva */ /* quick sort */ qsort (a, from, to) int a[]; /* sortiruemyj massiv */ int from; /* levyj nachal'nyj indeks */ int to; /* pravyj konechnyj indeks */ { register i, j, x, tmp; if( from >= to ) return; /* chislo elementov <= 1 */ i = from; j = to; x = a[ (i+j) / 2 ]; /* znachenie iz serediny */ do{ /* suzhenie vpravo */ while( a[i] < x ) i++ ; /* suzhenie vlevo */ while( x < a[j] ) j--; if( i <= j ){ /* obmenyat' */ tmp = a[i]; a[i] = a[j] ; a[j] = tmp; i++; j--; demochanges(); /* vizualizaciya */ } } while( i <= j ); /* Teper' obe chasti soshlis' v odnoj tochke. * Dlina levoj chasti = j - from + 1 * pravoj = to - i + 1 * Vse chisla v levoj chasti men'she vseh chisel v pravoj. * Teper' nado prosto otsortirovat' kazhduyu chast' v otdel'nosti. * Snachala sortiruem bolee korotkuyu (dlya ekonomii pamyati * v steke ). Rekursiya: */ if( (j - from) < (to - i) ){ qsort( a, from, j ); qsort( a, i, to ); } else { qsort( a, i, to ); qsort( a, from, j ); } } A. Bogatyrev, 1992-95 - 23 - Si v UNIX int main (){ register i; initscr(); /* zapusk curses-a */ /* poisk maksimal'nogo chisla v massive */ for( maxim = target[0], i = 1 ; i < N ; i++ ) if( target[i] > maxim ) maxim = target[i]; demochanges(); qsort( target, 0, N-1 ); demochanges(); mvcur( -1, -1, LINES-1, 0); /* kursor v levyj nizhnij ugol */ endwin(); /* zavershit' rabotu s curses-om */ return 0; } #define GAPY 2 #define GAPX 20 /* narisovat' kartinku */ demochanges(){ register i, j; int h = LINES - 3 * GAPY - N; int height; erase(); /* zachistit' okno */ attron( A_REVERSE ); /* risuem matricu uporyadochennosti */ for( i=0 ; i < N ; i++ ) for( j = 0; j < N ; j++ ){ move( GAPY + i , GAPX + j * 2 ); addch( target[i] >= target[j] ? '*' : '.' ); addch( ' ' ); /* Risovat' '*' esli elementy * idut v nepravil'nom poryadke. * Vozmozhen variant proverki target[i] > target[j] */ } attroff( A_REVERSE ); /* massiv */ for( i = 0 ; i < N ; i++ ){ move( GAPY + i , 5 ); printw( "%4d", target[i] ); height = (long) h * target[i] / maxim ; for( j = 2 * GAPY + N + (h - height) ; j < LINES - GAPY; j++ ){ move( j, GAPX + i * 2 ); addch( '|' ); } } refresh(); /* proyavit' kartinku */ sleep(1); } A. Bogatyrev, 1992-95 - 24 - Si v UNIX 1.51. Realizujte privedennyj fragment programmy bez ispol'zovaniya operatora goto i bez metok. if ( i > 10 ) goto M1; goto M2; M1: j = j + i; flag = 2; goto M3; M2: j = j - i; flag = 1; M3: ; Zamet'te, chto pomechat' mozhno tol'ko operator (mozhet byt' pustoj); poetomu ne mozhet vstretit'sya fragment { ..... Label: } a tol'ko { ..... Label: ; } 1.52. V kakom sluchae opravdano ispol'zovanie operatora goto? Otvet: pri vyhode iz vlozhennyh ciklov, t.k. operator break pozvolyaet vyjti tol'ko iz samogo vnutrennego cikla (na odin uroven'). 1.53. K kakomu if-u otnositsya else? if(...) ... if(...) ... else ... Otvet: ko vtoromu (k blizhajshemu predshestvuyushchemu, dlya kotorogo net drugogo else). Voobshche zhe luchshe yavno rasstavlyat' skobki (dlya yasnosti): if(...){ ... if(...) ... else ... } if(...){ ... if(...) ... } else ... 1.54. Makroopredelenie, ch'e telo predstavlyaet soboj posledovatel'nost' operatorov v {...} skobkah (blok), mozhet vyzvat' problemy pri ispol'zovanii ego v uslovnom opera- tore if s else-chast'yu: #define MACRO { x=1; y=2; } if(z) MACRO; else .......; My poluchim posle makrorasshireniya if(z) { x=1; y=2; } /* konec if-a */ ; else .......; /* else ni k chemu ne otnositsya */ to est' sintaksicheski oshibochnyj fragment, tak kak dolzhno byt' libo if(...) odin_operator; else ..... libo if(...){ posledovatel'nost'; ...; operatorov; } else ..... gde tochka-s-zapyatoj posle } ne nuzhna. S etim yavleniem boryutsya, oformlyaya blok {...} v vide do{...}while(0) #define MACRO do{ x=1; y=2; }while(0) Telo takogo "cikla" vypolnyaetsya edinstvennyj raz, pri etom my poluchaem pravil'nyj tekst: A. Bogatyrev, 1992-95 - 25 - Si v UNIX if(z) do{ x=1; y=2; }while(0); else .......; 1.55. V chem oshibka (dlya znayushchih yazyk "Paskal'")? int x = 12; if( x < 20 and x > 10 ) printf( "O'K\n"); else if( x > 100 or x < 0 ) printf( "Bad x\n"); else printf( "x=%d\n", x); Napishite #define and && #define or || 1.56. Pochemu programma zaciklivaetsya? My hotim podschitat' chislo probelov i tabulya- cij v nachale stroki: int i = 0; char *s = " 3 spaces"; while(*s == ' ' || *s++ == '\t') printf( "Probel %d\n", ++i); Otvet: logicheskie operacii || i && vypolnyayutsya sleva napravo; kak tol'ko kakoe-to uslovie v || okazyvaetsya istinnym (a v && lozhnym) - dal'nejshie usloviya prosto ne vychislyayutsya. V nashem sluchae uslovie *s==' ' srazu zhe verno, i operaciya s++ iz vtorogo usloviya ne vypolnyaetsya! My dolzhny byli napisat' hotya by tak: while(*s == ' ' || *s == '\t'){ printf( "Probel %d\n", ++i); s++; } S drugoj storony, eto svojstvo || i && cherezvychajno polezno, naprimer: if( x != 0.0 && y/x < 1.0 ) ... ; Esli by my ne vstavili proverku na 0, my mogli by poluchit' delenie na 0. V dannom zhe sluchae pri x==0 delenie prosto ne budet vychislyat'sya. Vot eshche primer: int a[5], i; for(i=0; i < 5 && a[i] != 0; ++i) ...; Esli i vyjdet za granicu massiva, to sravnenie a[i] s nulem uzhe ne budet vychislyat'sya, t.e. popytki prochest' element ne vhodyashchij v massiv ne proizojdet. |to svojstvo && pozvolyaet pisat' dovol'no neochevidnye konstrukcii, vrode if((cond) && f()); chto okazyvaetsya ekvivalentnym if( cond ) f(); Voobshche zhe if(C1 && C2 && C3) DO; ekvivalentno if(C1) if(C2) if(C3) DO; i dlya "ili" A. Bogatyrev, 1992-95 - 26 - Si v UNIX if(C1 || C2 || C3) DO; ekvivalentno if(C1) goto ok; else if(C2) goto ok; else if(C3){ ok: DO; } Vot eshche primer, pol'zuyushchijsya etim svojstvom || #include <stdio.h> main(argc, argv) int argc; char *argv[]; { FILE *fp; if(argc < 2 || (fp=fopen(argv[1], "r")) == NULL){ fprintf(stderr, "Plohoe imya fajla\n"); exit(1); /* zavershit' programmu */ } ... } Esli argc==1, to argv[1] ne opredeleno, odnako v etom sluchae popytki otkryt' fajl s imenem argv[1] prosto ne budet predprinyato! Nizhe priveden eshche odin soderzhatel'nyj primer, predstavlyayushchij soboj odnu iz voz- mozhnyh shem napisaniya "dvuyazychnyh" programm, t.e. vydayushchih soobshcheniya na odnom iz dvuh yazykov po vashemu zhelaniyu. Proveryaetsya peremennaya okruzheniya MSG (ili LANG): YAZYK: 1) "MSG=engl" anglijskij 2) MSG net v okruzhenii anglijskij 3) "MSG=rus" russkij Pro okruzhenie i funkciyu getenv() smotri v glave "Vzaimodejstvie s UNIX", pro strchr() - v glave "Massivy i stroki". #include <stdio.h> int _ediag = 0; /* yazyk diagnostik: 1-russkij */ extern char *getenv(), *strchr(); #define ediag(e,r) (_ediag?(r):(e)) main(){ char *s; _ediag = ((s=getenv("MSG")) != NULL && strchr("rRrR", *s) != NULL); printf(ediag("%d:english\n", "%d:russkij\n"), _ediag); } Esli peremennaya MSG ne opredelena, to s==NULL i funkciya strchr(s,...) ne vyzyvaetsya (ee pervyj frgument ne dolzhen byt' NULL-om). Zdes' ee mozhno bylo by uproshchenno zame- nit' na *s=='r'; togda esli s ravno NULL, to obrashchenie *s bylo by nezakonno (obrashche- nie po ukazatelyu NULL daet nepredskazuemye rezul'taty i, skoree vsego, vyzovet krah programmy). 1.57. Inogda logicheskoe uslovie mozhno sdelat' bolee ponyatnym, ispol'zuya pravila de- Morgana: a && b = ! ( !a || !b ) a || b = ! ( !a && !b ) a takzhe uchityvaya, chto ! !a = a ! (a == b) = (a != b) Naprimer: A. Bogatyrev, 1992-95 - 27 - Si v UNIX if( c != 'a' && c != 'b' && c != 'c' )...; prevrashchaetsya v if( !(c == 'a' || c == 'b' || c == 'c')) ...; 1.58. Primer, v kotorom ispol'zuyutsya pobochnye effekty vychisleniya vyrazhenij. Obychno znachenie vyrazheniya prisvaivaetsya nekotoroj peremennoj, no eto ne neobhodimo. Poetomu mozhno ispol'zovat' svojstva vychisleniya && i || v vyrazheniyah (hotya eto ne est' samyj ponyatnyj sposob napisaniya programm, skoree nekotoryj rod izvrashcheniya). Ogranichenie tut takovo: vse chasti vyrazheniya dolzhny vozvrashchat' znacheniya. #include <stdio.h> extern int errno; /* kod sistemnoj oshibki */ FILE *fp; int openFile(){ errno = 0; fp = fopen("/etc/inittab", "r"); printf("fp=%x\n", fp); return(fp == NULL ? 0 : 1); } int closeFile(){ printf("closeFile\n"); if(fp) fclose(fp); return 0; } int die(int code){ printf("exit(%d)\n", code); exit(code); return 0; } void main(){ char buf[2048]; if( !openFile()) die(errno); closeFile(); openFile() || die(errno); closeFile(); /* esli fajl otkrylsya, to die() ne vychislyaetsya */ openFile() ? 0 : die(errno); closeFile(); if(openFile()) closeFile(); openFile() && closeFile(); /* vychislit' closeFile() tol'ko esli openFile() udalos' */ openFile() && (printf("%s", fgets(buf, sizeof buf, fp)), closeFile()); } V poslednej stroke ispol'zovan operator "zapyataya": (a,b,c) vozvrashchaet znachenie vyra- zheniya c. 1.59. Napishite funkciyu, vychislyayushchuyu summu massiva zadannyh chisel. 1.60. Napishite funkciyu, vychislyayushchuyu srednee znachenie massiva zadannyh chisel. 1.61. CHto budet napechatano v rezul'tate raboty sleduyushchego cikla? for ( i = 36; i > 0; i /= 2 ) printf ( "%d%s", i, i==1 ? ".\n":", "); A. Bogatyrev, 1992-95 - 28 - Si v UNIX Otvet: 36, 18, 9, 4, 2, 1. 1.62. Najdite oshibki v sleduyushchej programme: main { int i, j, k(10); for ( i = 0, i <= 10, i++ ){ k[i] = 2 * i + 3; for ( j = 0, j <= i, j++ ) printf ("%i\n", k[j]); } } Obratite vnimanie na format %i, sushchestvuet li takoj format? Est' li eto tot format, po kotoromu sleduet pechatat' znacheniya tipa int? 1.63. Napishite programmu, kotoraya raspechatyvaet elementy massiva. Napishite prog- rammu, kotoraya raspechatyvaet elementy massiva po 5 chisel v stroke. 1.64. Sostav'te programmu schityvaniya strok simvolov iz standartnogo vvoda i pechati nomera vvedennoj stroki, adresa stroki v pamyati |VM, znacheniya stroki, dliny stroki. 1.65. Stilisticheskoe zamechanie: v operatore return vozvrashchaemoe vyrazhenie ne obyaza- tel'no dolzhno byt' v ()-skobkah. Delo v tom, chto return - ne funkciya, a operator. return vyrazhenie; return (vyrazhenie); Odnako esli vy vyzyvaete funkciyu (naprimer, exit) - to argumenty dolzhny byt' v krug- lyh skobkah: exit(1); no ne exit 1; 1.66. Izbegajte situacii, kogda funkciya v raznyh vetvyah vychisleniya to vozvrashchaet nekotoroe znachenie, to ne vozvrashchaet nichego: int func (int x) { if( x > 10 ) return x*2; if( x == 10 ) return (10); /* a zdes' - neyavnyj return; bez znacheniya */ } pri x < 10 funkciya vernet nepredskazuemoe znachenie! Mnogie kompilyatory raspoznayut takie situacii i vydayut preduprezhdenie. 1.67. Napishite programmu, zaprashivayushchuyu vashe imya i "privetstvuyushchuyu" vas. Napishite funkciyu chteniya stroki. Ispol'zujte getchar() i printf(). Otvet: #include <stdio.h> /* standard input/output */ main(){ char buffer[81]; int i; printf( "Vvedite vashe imya:" ); while((i = getstr( buffer, sizeof buffer )) != EOF){ printf( "Zdravstvuj, %s\n", buffer ); printf( "Vvedite vashe imya:" ); } } getstr( s, maxlen ) char *s; /* kuda pomestit' stroku */ int maxlen; /* dlina bufera: A. Bogatyrev, 1992-95 - 29 - Si v UNIX maks. dlina stroki = maxlen-1 */ { int c; /* ne char! (pochemu ?) */ register int i = 0; maxlen--; /* rezerviruem bajt pod konechnyj '\0' */ while(i < maxlen && (c = getchar()) != '\n' && c != EOF ) s[i++] = c; /* obratite vnimanie, chto sam simvol '\n' * v stroku ne popadet */ s[i] = '\0'; /* priznak konca stroki */ return (i == 0 && c == EOF) ? EOF : i; /* vernem dlinu stroki */ } Vot eshche odin variant funkcii chteniya stroki: v nashem primere ee sleduet vyzyvat' kak fgetstr(buffer,sizeof(buffer),stdin); |to podpravlennyj variant standartnoj funkcii fgets (v nej stroki @1 i @2 obmenyany mestami). char *fgetstr(char *s, int maxlen, register FILE *fp){ register c; register char *cs = s; while(--maxlen > 0 && (c = getc(fp)) != EOF){ if(c == '\n') break; /* @1 */ *cs++ = c; /* @2 */ } if(c == EOF && cs == s) return NULL; /* Zamet'te, chto pri EOF stroka s ne menyaetsya! */ *cs = '\0'; return s; } Issledujte povedenie etih funkcij, kogda vhodnaya stroka slishkom dlinnaya (dlinnee max- len). Zamechanie: vmesto nashej "rukopisnoj" funkcii getstr() my mogli by ispol'zovat' standartnuyu bibliotechnuyu funkciyu gets(buffer). 1.68. Ob®yasnite, pochemu d stalo otricatel'nym i pochemu %X pechataet bol'she F, chem v ishodnom chisle? Primer vypolnyalsya na 32-h bitnoj mashine. main(){ unsigned short u = 65535; /* 16 bit: 0xFFFF */ short d = u; /* 15 bit + znakovyj bit */ printf( "%X %d\n", d, d); /* FFFFFFFF -1 */ } Ukazanie: rassmotrite dvoichnoe predstavlenie chisel (smotri prilozhenie). Kakie prive- deniya tipov zdes' proishodyat? 1.69. Pochemu 128 prevratilos' v otricatel'noe chislo? main() { /*signed*/ char c = 128; /* bity: 10000000 */ unsigned char uc = 128; int d = c; /* ispol'zuetsya 32-h bitnyj int */ printf( "%d %d %x\n", c, d, d ); /* -128 -128 ffffff80 */ d = uc; printf( "%d %d %x\n", uc, d, d ); /* 128 128 80 */ } A. Bogatyrev, 1992-95 - 30 - Si v UNIX Otvet: pri privedenii char k int rasshirilsya znakovyj bit (7-oj), zanyav vsyu starshuyu chast' slova. Znakovyj bit int-a stal raven 1, chto yavlyaetsya priznakom otricatel'nogo chisla. To zhe budet proishodit' so vsemi znacheniyami c iz diapazona 128..255 (soderzha- shchimi bit 0200). Pri privedenii unsigned char k int znakovyj bit ne rasshiryaetsya. Mozhno bylo postupit' eshche i tak: printf( "%d\n", c & 0377 ); Zdes' c privoditsya k tipu int (potomu chto pri ispol'zovanii v argumentah funkcii tip char VSEGDA privoditsya k tipu int), zatem &0377 zanulit starshij bajt poluchennogo celogo chisla (sostoyashchij iz bitov 1), snova prevrativ chislo v polozhitel'noe. 1.70. Pochemu printf("%d\n", '\377' == 0377 ); printf("%d\n", '\xFF' == 0xFF ); pechataet 0 (lozh')? Otvet: po toj zhe prichine, po kotoroj printf("%d %d\n", '\377', 0377); pechataet -1 255, a imenno: char '\377' privoditsya v vyrazheniyah k celomu rasshireniem znakovogo bita (a 0377 - uzhe celoe). 1.71. Rassmotrim programmu #include <stdio.h> int main(int ac, char **av){ int c; while((c = getchar()) != EOF) switch(c){ case 'y': printf("Bukva y\n"); break; case 'j': printf("Bukva j\n"); break; default: printf("Bukva s kodom %d\n", c); break; } return 0; } Ona rabotaet tak: % a.out jfyv Bukva s kodom 202 Bukva s kodom 198 Bukva s kodom 217 Bukva s kodom 215 Bukva s kodom 10 ^D % Vypolnyaetsya vsegda default, pochemu ne vypolnyayutsya case 'y' i case 'j'? Otvet: russkie bukvy imeyut vos'moj bit (levyj) ravnyj 1. V case takoj bajt pri- voditsya k tipu int rasshireniem znakovogo bita. V itoge poluchaetsya otricatel'noe chislo. Primer: void main(void){ int c = 'j'; printf("%d\n", c); } pechataet -54 A. Bogatyrev, 1992-95 - 31 - Si v UNIX Resheniem sluzhit podavlenie rasshireniya znakovogo bita: #include <stdio.h> /* Odno iz dvuh */ #define U(c) ((c) & 0xFF) #define UC(c) ((unsigned char) (c)) int main(int ac, char **av){ int c; while((c = getchar()) != EOF) switch(c){ case U('y'): printf("Bukva y\n"); break; case UC('j'): printf("Bukva j\n"); break; default: printf("Bukva s kodom %d\n", c); break; } return 0; } Ona rabotaet pravil'no: % a.out jfyv Bukva j Bukva s kodom 198 Bukva y Bukva s kodom 215 Bukva s kodom 10 ^D % Vozmozhno takzhe ispol'zovanie kodov bukv: case 0312: no eto gorazdo menee naglyadno. Podavlenie znakovogo bita neobhodimo takzhe i v opera- torah if: int c; ... if(c == 'j') ... sleduet zamenit' na if(c == UC('j')) ... Sleva zdes' - signed int, pravuyu chast' kompilyator tozhe privodit k signed int. Priho- ditsya yavno govorit', chto sprava - unsigned. 1.72. Rassmotrim programmu, kotoraya dolzhna napechatat' chisla ot 0 do 255. Dlya etih chisel v kachestve schetchika dostatochen odin bajt: int main(int ac, char *av[]){ unsigned char ch; for(ch=0; ch < 256; ch++) printf("%d\n", ch); return 0; } Odnako eta programma zaciklivaetsya, poskol'ku v moment, kogda ch==255, eto znachenie men'she 256. Sleduyushchim shagom vypolnyaetsya ch++, i ch stanovitsya ravno 0, ibo dlya char A. Bogatyrev, 1992-95 - 32 - Si v UNIX vychisleniya vedutsya po modulyu 256 (2 v 8 stepeni). To est' v dannom sluchae 255+1=0 Reshenij sushchestvuet dva: pervoe - prevratit' unsigned char v int. Vtoroe - vsta- vit' yavnuyu proverku na poslednee znachenie diapazona. int main(int ac, char *av[]){ unsigned char ch; for(ch=0; ; ch++){ printf("%d\n", ch); if(ch == 255) break; } return 0; } 1.73. Podumajte, pochemu dlya unsigned a, b, c; a < b + c ne ekvivalentno a - b < c (pervoe - bolee korrektno). Namek v vide primera (on vypolnyalsya na 32-bitnoj mashine): a = 1; b = 3; c = 2; printf( "%u\n", a - b ); /* 4294967294, hotya v normal'noj arifmetike 1 - 3 = -2 */ printf( "%d\n", a < b + c ); /* 1 */ printf( "%d\n", a - b < c ); /* 0 */ Mogut li unsigned chisla byt' otricatel'nymi? 1.74. Dan tekst: short x = 40000; printf("%d\n", x); Pechataetsya -25536. Ob®yasnite effekt. Ukazanie: kakovo naibol'shee predstavimoe korot- koe celoe (16 bitnoe)? CHto na samom dele okazalos' v x? (lishnie sleva bity - obruba- yutsya). 1.75. Pochemu v primere double x = 5 / 2; printf( "%g\n", x ); znachenie x ravno 2 a ne 2.5 ? Otvet: proizvoditsya celochislennoe delenie, zatem v prisvaivanii celoe chislo 2 privoditsya k tipu double. CHtoby poluchilsya otvet 2.5, nado pisat' odnim iz sleduyushchih sposobov: double x = 5.0 / 2; x = 5 / 2.0; x = (double) 5 / 2; x = 5 / (double) 2; x = 5.0 / 2.0; to est' v vyrazhenii dolzhen byt' hot' odin operand tipa double. Ob®yasnite, pochemu sleduyushchie tri operatora vydayut takie znacheniya: A. Bogatyrev, 1992-95 - 33 - Si v UNIX double g = 9.0; int t = 3; double dist = g * t * t / 2; /* 40.5 */ dist = g * (t * t / 2); /* 36.0 */ dist = g * (t * t / 2.0); /* 40.5 */ V kakih sluchayah delenie celochislennoe, v kakih - veshchestvennoe? Pochemu? 1.76. Stranslirujte primer na mashine s dlinoj slova int ravnoj 16 bit: long n = 1024 * 1024; long nn = 512 * 512; printf( "%ld %ld\n", n, nn ); Pochemu pechataetsya 0 0 a ne 1048576 262144? Otvet: rezul'tat umnozheniya (2**20 i 2**18) - eto celoe chislo; odnako ono slishkom veliko dlya sohraneniya v 16 bitah, poetomu starshie bity obrubayutsya. Poluchaetsya 0. Zatem v prisvaivanii eto uzhe obrublennoe znachenie privoditsya k tipu long (32 bita) - eto vse ravno budet 0. CHtoby poluchit' korrektnyj rezul'tat, nado chtoby vyrazhenie sprava ot = uzhe imelo tip long i srazu sohranyalos' v 32 bitah. Dlya etogo ono dolzhno imet' hot' odin operand tipa long: long n = (long) 1024 * 1024; long nn = 512 * 512L; 1.77. Najdite oshibku v operatore: x - = 4; /* vychest' iz x chislo 4 */ Otvet: mezhdu `-' i `=' ne dolzhno byt' probela. Operaciya vida x @= expr; oznachaet x = x @ expr; (gde @ - odna iz operacij + - * / % ^ >> << & |), prichem x zdes' vychislyaetsya edinst- vennyj raz (t.e. takaya forma ne tol'ko koroche i ponyatnee, no i ekonomichnee). Odnako imeetsya tonkoe otlichie a=a+n ot a+=n; ono zaklyuchaetsya v tom, skol'ko raz vychislyaetsya a. V sluchae a+=n edinozhdy; v sluchae a=a+n dva raza. A. Bogatyrev, 1992-95 - 34 - Si v UNIX #include <stdio.h> static int x = 0; int *iaddr(char *msg){ printf("iaddr(%s) for x=%d evaluated\n", msg, x); return &x; } int main(){ static int a[4]; int *p, i; printf( "1: "); x = 0; (*iaddr("a"))++; printf( "2: "); x = 0; *iaddr("b") += 1; printf( "3: "); x = 0; *iaddr("c") = *iaddr("d") + 1; for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0; *p++ += 1; for(i=0; i < sizeof(a)/sizeof(*a); i++) printf("a[%d]=%d ", i, a[i]); printf("offset=%d\n", p - a); for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0; *p++ = *p++ + 1; for(i=0; i < sizeof(a)/sizeof(*a); i++) printf("a[%d]=%d ", i, a[i]); printf("offset=%d\n", p - a); return 0; } Vydacha: 1: iaddr(a) for x=0 evaluated 2: iaddr(b) for x=0 evaluated 3: iaddr(d) for x=0 evaluated iaddr(c) for x=0 evaluated a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=1 a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=2 Zamet'te takzhe, chto a[i++] += z; eto a[i] = a[i] + z; i++; a vovse ne a[i++] = a[i++] + z; 1.78. Operaciya y = ++x; ekvivalentna y = (x = x+1, x); a operaciya y = x++; ekvivalentna y = (tmp = x, x = x+1, tmp); ili y = (x += 1) - 1; gde tmp - vremennaya psevdoperemennaya togo zhe tipa, chto i x. Operaciya `,' vydaet A. Bogatyrev, 1992-95 - 35 - Si v UNIX znachenie poslednego vyrazheniya iz perechislennyh (podrobnee sm. nizhe). Pust' x=1. Kakie znacheniya budut prisvoeny x i y posle vypolneniya operatora y = ++x + ++x + ++x; 1.79. Pust' i=4. Kakie znacheniya budut prisvoeny x i i posle vypolneniya operatora x = --i + --i + --i; 1.80. Pust' x=1. Kakie znacheniya budut prisvoeny x i y posle vypolneniya operatora y = x++ + x++ + x++; 1.81. Pust' i=4. Kakie znacheniya budut prisvoeny i i y posle vypolneniya operatora y = i-- + i-- + i--; 1.82. Korrektny li operatory char *p = "Jabberwocky"; char s[] = "0123456789?"; int i = 0; s[i] = p[i++]; ili *p = *++p; ili s[i] = i++; ili dazhe *p++ = f( *p ); Otvet: net, standart ne predusmatrivaet, kakaya iz chastej prisvaivaniya vychislyaetsya pervoj: levaya ili pravaya. Poetomu vse mozhet rabotat' tak, kak my i podrazumevali, no mozhet i inache! Kakoe i ispol'zuetsya v s[i]: 0 ili uzhe 1 (++ uzhe sdelan ili net), to est' int i = 0; s[i] = i++; eto s[0] = 0; ili zhe s[1] = 0; ? Kakoe p budet ispol'zovano v levoj chasti *p: uzhe prodvinutoe ili staroe? Eshche bolee eta ideya dramatizirovana v s[i++] = p[i++]; Zametim eshche, chto v int i=0, j=0; s[i++] = p[j++]; takoj problemy ne voznikaet, poskol'ku indeksy oboih v chastyah prisvaivaniya nezavi- simy. Zato analogichnaya problema vstaet v if( a[i++] < b[i] )...; Poryadok vychisleniya operandov ne opredelen, poetomu neyasno, chto budet sdelano prezhde: vzyato znachenie b[i] ili znachenie a[i++] (togda budet vzyato b[i+1] ). Nado pisat' tak, chtoby ne polagat'sya na osobennosti vashego kompilyatora: if( a[i] < b[i+1] )...; ili *p = *(p+1); i++; ++p; A. Bogatyrev, 1992-95 - 36 - Si v UNIX Tverdo usvojte, chto i++ i ++i ne tol'ko vydayut znacheniya i i i+1 sootvetstvenno, no i izmenyayut znachenie i. Poetomu eti operatory NE NADO ispol'zovat' tam, gde po smyslu trebuetsya i+1, a ne i=i+1. Tak dlya sravneniya sosednih elementov massiva if( a[i] < a[i+1] ) ... ; /* verno */ if( a[i] < a[++i] ) ... ; /* neverno */ 1.83. Poryadok vychisleniya operandov v binarnyh vyrazheniyah ne opredelen (chto ran'she vychislyaetsya - levyj operand ili zhe pravyj ?). Tak primer int f(x,s) int x; char *s; { printf( "%s:%d ", s, x ); return x; } main(){ int x = 1; int y = f(x++, "f1") + f(x+=2, "f2"); printf("%d\n", y); } mozhet pechatat' libo f1:1 f2:4 5 libo f2:3 f1:3 6 v zavisimosti ot osobennostej povedeniya vashego kompilyatora (kakaya iz dvuh f() vypol- nitsya pervoj: levaya ili pravaya?). Eshche primer: int y = 2; int x = ((y = 4) * y ); printf( "%d\n", x ); Mozhet byt' napechatano libo 16, libo 8 v zavisimosti ot povedeniya kompilyatora, t.e. dannyj operator nemobilen. Sleduet napisat' y = 4; x = y * y; 1.84. Zakonen li operator f(x++, x++); ili f(x, x++); Otvet: Net, poryadok vychisleniya argumentov funkcij ne opredelen. Po toj zhe prichine my ne mozhem pisat' f( c = getchar(), c ); a dolzhny pisat' c = getchar(); f(c, c); (esli my imenno eto imeli v vidu). Vot eshche primer: ... case '+': push(pop()+pop()); break; case '-': push(pop()-pop()); break; ... A. Bogatyrev, 1992-95 - 37 - Si v UNIX sleduet zamenit' na ... case '+': push(pop()+pop()); break; case '-': { int x = pop(); int y = pop(); push(y - x); break; } ... I eshche primer: int x = 0; printf( "%d %d\n", x = 2, x ); /* 2 0 libo 2 2 */ Nel'zya takzhe struct pnt{ int x; int y; }arr[20]; int i=0; ... scanf( "%d%d", & arr[i].x, & arr[i++].y ); poskol'ku i++ mozhet sdelat'sya ran'she, chem chtenie v x. Eshche primer: main(){ int i = 3; printf( "%d %d %d\n", i += 7, i++, i++ ); } kotoryj pokazyvaet, chto na IBM PC |- i PDP-11 |= argumenty funkcij vychislyayutsya sprava nalevo (primer pechataet 12 4 3). Vprochem, drugie kompilyatory mogut vychislyat' ih sleva napravo (kak i podskazyvaet nam zdravyj smysl). 1.85. Programma pechataet libo x=1 libo x=0 v zavisimosti ot KOMPILYATORA - vychislya- etsya li ran'she pravaya ili levaya chast' operatora vychitaniya: #include <stdio.h> void main(){ int c = 1; int x = c - c++; printf( "x=%d c=%d\n", x, c ); exit(0); } CHto vy imeli v vidu ? left = c; right = c++; x = left - right; ili right = c++; left = c; x = left - right; A esli kompilyator eshche i rasparallelit vychislenie left i right - to odna programma v raznye momenty vremeni smozhet davat' raznye rezul'taty. ____________________ |- IBM ("Aj-bi-em") - International Buisiness Machines Corporation. Personal'nye komp'yutery IBM PC postroeny na baze mikroprocessorov firmy Intel. |= PDP-11 - (Programmed Data Processor) - komp'yuter firmy DEC (Digital Equipment Corporation), u nas izvestnyj kak SM-1420. |ta zhe firma vypuskaet mashinu VAX. A. Bogatyrev, 1992-95 - 38 - Si v UNIX Vot eshche dostojnaya zadachka: x = c-- - --c; /* c-----c */ 1.86. Napishite programmu, kotoraya ustanavlivaet v 1 bit 3 i sbrasyvaet v 0 bit 6. Bity v slove numeruyutsya s nulya sprava nalevo. Otvet: int x = 0xF0; x |= (1 << 3); x &= ~(1 << 6); V programmah chasto ispol'zuyut bitovye maski kak flagi nekotoryh parametrov (priznak - est' ili net). Naprimer: #define A 0x08 /* vhod svoboden */ #define B 0x40 /* vyhod svoboden */ ustanovka flagov : x |= A|B; sbros flagov : x &= ~(A|B); proverka flaga A : if( x & A ) ...; proverka, chto oba flaga est': if((x & (A|B)) == (A|B))...; proverka, chto oboih net : if((x & (A|B)) == 0 )...; proverka, chto est' hot' odin: if( x & (A|B))...; proverka, chto est' tol'ko A : if((x & (A|B)) == A)...; proverka, v kakih flagah razlichayutsya x i y : diff = x ^ y; 1.87. V programmah inogda trebuetsya ispol'zovat' "mnozhestvo": kazhdyj dopustimyj ele- ment mnozhestva imeet nomer i mozhet libo prisutstvovat' v mnozhestve, libo otsutstvo- vat'. CHislo vhozhdenij ne uchityvaetsya. Mnozhestva prinyato modelirovat' pri pomoshchi bitovyh shkal: #define SET(n,a) (a[(n)/BITS] |= (1L <<((n)%BITS))) #define CLR(n,a) (a[(n)/BITS] &= ~(1L <<((n)%BITS))) #define ISSET(n,a) (a[(n)/BITS] & (1L <<((n)%BITS))) #define BITS 8 /* bits per char (bitov v bajte) */ /* Perechislimyj tip */ enum fruit { APPLE, PEAR, ORANGE=113, GRAPES, RAPE=125, CHERRY}; /* shkala: n iz intervala 0..(25*BITS)-1 */ static char fr[25]; main(){ SET(GRAPES, fr); /* dobavit' v mnozhestvo */ if(ISSET(GRAPES, fr)) printf("here\n"); CLR(GRAPES, fr); /* udalit' iz mnozhestva */ } 1.88. Napishite programmu, raspechatyvayushchuyu vse vozmozhnye perestanovki massiva iz N elementov. Algoritm budet rekursivnym, naprimer takim: v kachestve pervogo elementa perestanovki vzyat' i-yj element massiva. Iz ostavshihsya elementov massiva (esli takie est') sostavit' vse perestanovki poryadka N-1. Vydat' vse perestanovki poryadka N, poluchayushchiesya sklejkoj i-ogo elementa i vseh (po ocheredi) perestanovok poryadka N-1. Vzyat' sleduyushchee i i vse povtorit'. Glavnaya problema zdes' - organizovat' ostavshiesya posle izvlecheniya i-ogo elementa elementy massiva v udobnuyu strukturu dannyh (chtoby postoyanno ne kopirovat' massiv). Mozhno ispol'zovat', naprimer, bitovuyu shkalu uzhe vybrannyh elementov. Vospol'zuemsya dlya etogo makrosami iz predydushchego paragrafa: A. Bogatyrev, 1992-95 - 39 - Si v UNIX /* GENERATOR PERESTANOVOK IZ n |LEMENTOV PO m */ extern void *calloc(unsigned nelem, unsigned elsize); /* Dinamicheskij vydelitel' pamyati, zachishchennoj nulyami. * |to standartnaya bibliotechnaya funkciya. * Obratnaya k nej - free(); */ extern void free(char *ptr); static int N, M, number; static char *scale; /* shkala vybrannyh elementov */ int *res; /* rezul'tat */ /* ... tekst opredelenij SET, CLR, ISSET, BITS ... */ static void choose(int ind){ if(ind == M){ /* raspechatat' perestanovku */ register i; printf("Rasstanovka #%04d", ++number); for(i=0; i < M; i++) printf(" %2d", res[i]); putchar('\n'); return; } else /* Vybrat' ocherednoj ind-tyj element perestanovki * iz chisla eshche ne vybrannyh elementov. */ for(res[ind] = 0; res[ind] < N; ++res[ind]) if( !ISSET(res[ind], scale)) { /* element eshche ne byl vybran */ SET(res[ind], scale); /* vybrat' */ choose(ind+1); CLR(res[ind], scale); /* osvobodit' */ } } void arrange(int n, int m){ res = (int *) calloc(m, sizeof(int)); scale = (char *) calloc((n+BITS-1)/BITS, 1); M = m; N = n; number = 0; if( N >= M ) choose(0); free((char *) res); free((char *) scale); } void main(int ac, char **av){ if(ac != 3){ printf("Arg count\n"); exit(1); } arrange(atoi(av[1]), atoi(av[2])); } Programma dolzhna vydat' n!/(n-m)! rasstanovok, gde x! = 1*2*...*x - funkciya "fakto- rial". Po opredeleniyu 0! = 1. Poprobujte peredelat' etu programmu tak, chtoby oche- rednaya perestanovka pechatalas' po zaprosu: res = init_iterator(n, m); /* pechatat' varianty, poka o