of(); double x = atof(str1); extern long atol(); long y = atol(str2); extern int atoi(); int i = atoi(str3); libo sscanf(str1, "%f", &x); sscanf(str2, "%ld", &y); sscanf(str3,"%d", &i); K slovu zametim, chto obratnoe preobrazovanie - chisla v tekst - udobnee vsego delaetsya pri pomoshchi funkcii sprintf(), kotoraya analogichna printf(), no sformirovannaya eyu stroka-soobshchenie ne vydaetsya na ekran, a zanositsya v massiv: A. Bogatyrev, 1992-95 - 58 - Si v UNIX char represent[ 40 ]; int i = ... ; sprintf( represent, "%d", i ); 1.117. Sostav'te programmu vychisleniya polinoma n-oj stepeni: n n-1 Y = A * X + A * X + ... + A0 n n-1 shema (Gornera): Y = A0 + X * ( A1 + X * ( A2 + ... + X * An )))...) Oformite algoritm kak funkciyu s peremennym chislom parametrov: poly( x, n, an, an-1, ... a0 ); O tom, kak eto sdelat' - chitajte razdel rukovodstva po UNIX man varargs. Otvet: #include <varargs.h> double poly(x, n, va_alist) double x; int n; va_dcl { va_list args; double sum = 0.0; va_start(args); /* inicializirovat' spisok arg-tov */ while( n-- >= 0 ){ sum *= x; sum += va_arg(args, double); /* izvlech' sled. argument tipa double */ } va_end(args); /* unichtozhit' spisok argumentov */ return sum; } main(){ /* y = 12*x*x + 3*x + 7 */ printf( "%g\n", poly(2.0, 2, 12.0, 3.0, 7.0)); } Prototip etoj funkcii: double poly(double x, int n, ... ); V etom primere ispol'zovany makrosy va_nechto. CHast' argumentov, kotoraya yavlyaetsya spiskom peremennoj dliny, oboznachaetsya v spiske parametrov kak va_alist, pri etom ona ob®yavlyaetsya kak va_dcl v spiske tipov parametrov. Zamet'te, chto tochka-s-zapyatoj posle va_dcl ne nuzhna! Opisanie va_list args; ob®yavlyaet special'nuyu "svyaznuyu" peremennuyu; smysl ee mashinno zavisim. va_start(args) inicializiruet etu peremennuyu spiskom fak- ticheskih argumentov, sootvetstvuyushchih va_alist-u. va_end(args) deinicializiruet etu peremennuyu (eto nado delat' obyazatel'no, poskol'ku inicializaciya mogla byt' svyazana s konstruirovaniem spiska argumentov pri pomoshchi vydeleniya dinamicheskoj pamyati; teper' my dolzhny unichtozhit' etot spisok i osvobodit' pamyat'). Ocherednoj argument tipa TYPE izvlekaetsya iz spiska pri pomoshchi TYPE x = va_arg(args, TYPE); Spisok argumentov prosmatrivaetsya sleva napravo v odnom napravlenii, vozvrat k A. Bogatyrev, 1992-95 - 59 - Si v UNIX predydushchemu argumentu nevozmozhen. Nel'zya ukazyvat' v kachestve tipov char, short, float: char ch = va_arg(args, char); poskol'ku v yazyke Si argumenty funkcii takih tipov avtomaticheski rasshiryayutsya v int, int, double sootvetstvenno. Korrektno budet tak: int ch = va_arg(args, int); 1.118. Eshche ob odnoj lovushke v yazyke Si na PDP-11 (i v kompilyatorah byvayut oshibki!): unsigned x = 2; printf( "%ld %ld", - (long) x, (long) -x ); |tot fragment napechataet chisla -2 i 65534. Vo vtorom sluchae pri privedenii k tipu long byl rasshiren znakovyj bit. Vstroennaya operaciya sizeof vydaet znachenie tipa unsigned. Podumajte, kakov budet effekt v sleduyushchem fragmente programmy? static struct point{ int x, y ;} p = { 33, 13 }; FILE *fp = fopen( "00", "w" ); /* vpered na dlinu odnoj struktury */ fseek( fp, (long) sizeof( struct point ), 0 ); /* nazad na dlinu odnoj struktury */ /*!*/ fseek( fp, (long) -sizeof( struct point ), 1 ); /* zapisyvaem v nachalo fajla odnu strukturu */ fwrite( &p, sizeof p, 1, fp ); /* zakryvaem fajl */ fclose( fp ); Gde dolzhen nahodit'sya minus vo vtorom vyzove fseek dlya polucheniya ozhidaemogo rezul'- tata? (Dannyj primer mozhet vesti sebya po-raznomu na raznyh mashinah, voprosy kasayutsya PDP-11). 1.119. Obratimsya k ukazatelyam na funkcii: void g(x){ printf("%d: here\n", x); } main(){ void (*f)() = g; /* Ukazatel' smotrit na funkciyu g() */ (*f)(1); /* Staraya forma vyzova funkcii po ukazatelyu */  f (2); /* Novaya forma vyzova */ /* V oboih sluchayah vyzyvaetsya g(x); */ } CHto pechataet programma? typedef void (*(*FUN))(); /* Popytka izobrazit' rekursivnyj tip typedef FUN (*FUN)(); */ FUN g(FUN f){ return f; } void main(){ FUN y = g(g(g(g(g)))); if(y == g) printf("OK\n"); A. Bogatyrev, 1992-95 - 60 - Si v UNIX } CHto pechataet programma? char *f(){ return "Hello, user!"; } g(func) char * (*func)(); { puts((*func)()); } main(){ g(f); } Pochemu bylo by neverno napisat' main(){ g(f()); } Eshche analogichnaya oshibka (posmotrite pro funkciyu signal v glave "Vzaimodejstvie s UNIX"): #include <signal.h> f(){ printf( "Good bye.\n" ); exit(0); } main(){ signal ( SIGINT, f() ); ... } Zapomnite, chto f() - eto ZNACHENIE funkcii f (t.e. ona vyzyvaetsya i nechto vozvrashchaet return-om; eto-to znachenie my i ispol'zuem), a f - eto ADRES funkcii f (ran'she eto tak i pisalos' &f), to est' metka nachala ee mashinnyh kodov ("tochka vhoda"). 1.120. CHto napechataet programma? (Primer posvyashchen ukazatelyam na funkcii i massivam funkcij): int f(n){ return n*2; } int g(n){ return n+4; } int h(n){ return n-1; } int (*arr[3])() = { f, g, h }; main(){ int i; for(i=0; i < 3; i++ ) printf( "%d\n", (*arr[i])(i+7) ); } 1.121. CHto napechataet programma? extern double sin(), cos(); main(){ double x; /* cc -lm */ for(x=0.0; x < 1.0; x += 0.2) printf("%6.4g %6.4g %6.4g\n", (x > 0.5 ? sin : cos)(x), sin(x), cos(x)); } to zhe v variante A. Bogatyrev, 1992-95 - 61 - Si v UNIX extern double sin(), cos(); main(){ double x; double (*f)(); for(x=0.0; x < 1.0; x += 0.2){ f = (x > 0.5 ? sin : cos); printf("%g\n", (*f)(x)); } } 1.122. Rassmotrite chetyre realizacii funkcii faktorial: n! = 1 * 2 * ... * n ili n! = n * (n-1)! gde 0! = 1 Vse oni illyustriruyut opredelennye podhody v programmirovanii: /* CIKL (ITERACIYA) */ int factorial1(n){ int res = 1; while(n > 0){ res *= n--; } return res; } /* PROSTAYA REKURSIYA */ int factorial2(n){ return (n==0 ? 1 : n * factorial2(n-1)); } /* Rekursiya, v kotoroj funkciya vyzyvaetsya rekursivno * edinstvennyj raz - v operatore return, nazyvaetsya * "hvostovoj rekursiej" (tail recursion) i * legko preobrazuetsya v cikl */ /* AVTOAPPLIKACIYA */ int fi(f, n) int (*f)(), n; { if(n == 0) return 1; else return n * (*f)(f, n-1); } int factorial3(n){ return fi(fi, n); } /* REKURSIYA S NELOKALXNYM PEREHODOM */ #include <setjmp.h> jmp_buf checkpoint; void fact(n, res) register int n, res; { if(n) fact(n - 1, res * n); else longjmp(checkpoint, res+1); } int factorial4(n){ int res; if(res = setjmp(checkpoint)) return (res - 1); else fact(n, 1); } 1.123. Napishite funkciyu, pechatayushchuyu celoe chislo v sisteme schisleniya s osnovaniem base. Otvet: A. Bogatyrev, 1992-95 - 62 - Si v UNIX printi( n, base ){ register int i; if( n < 0 ){ putchar( '-' ); n = -n; } if( i = n / base ) printi( i, base ); i = n % base ; putchar( i >= 10 ? 'A' + i - 10 : '0' + i ); } Poprobujte napisat' nerekursivnyj variant s nakopleniem otveta v stroke. Prive- dem rekursivnyj variant, nakaplivayushchij otvet v stroke s i pol'zuyushchijsya analogom funk- cii printi: funkciya prints - takaya zhe, kak printi, no vmesto vyzovov putchar(nechto); v nej napisany operatory *res++ = nechto; i rekursivno vyzyvaetsya konechno zhe prints. Itak: static char *res; ... tekst funkcii prints ... char *itos( n, base, s ) char *s; /* ukazyvaet na char[] massiv dlya otveta */ { res = s; prints(n, base); *res = '\0'; return s; } main(){ char buf[20]; printf( "%s\n", itos(19,2,buf); } 1.124. Napishite funkciyu dlya pobitnoj raspechatki celogo chisla. Imejte v vidu, chto chislo soderzhit 8 * sizeof(int) bit. Ukazanie: ispol'zujte operacii bitovogo sdviga i &. Otvet: printb(n){ register i; for(i = 8 * sizeof(int) - 1; i >= 0; --i) putchar(n & (1 << i) ? '1':'0'); } 1.125. Napishite funkciyu, sklonyayushchuyu sushchestvitel'nye russkogo yazyka v zavisimosti ot ih chisla. Naprimer: printf( "%d kirpich%s", n, grammar( n, "ej", "", "a" )); Otvet: char *grammar( i, s1, s2, s3 ) char *s1, /* prochee */ *s2, /* odin */ *s3; /* dva, tri, chetyre */ { i = i % 100; if( i > 10 && i <= 20 ) return s1; i = i % 10; if( i == 1 ) return s2; if( i == 2 || i == 3 || i == 4 ) return s3; return s1; } A. Bogatyrev, 1992-95 - 63 - Si v UNIX 1.126. Napishite operator printf, pechatayushchij chisla iz intervala 0..99 s dobavleniem nulya pered chislom, esli ono men'she 10 : 00 01 ... 09 10 11 ... Ispol'zujte uslovnoe vyrazhenie, format. Otvet: printf ("%s%d", n < 10 ? "0" : "", n); libo printf ("%02d", n ); libo printf ("%c%c", '0' + n/10, '0' + n%10 ); 1.127. Predosterezhem ot odnoj oshibki, chasto dopuskaemoj nachinayushchimi. putchar( "c" ); yavlyaetsya oshibkoj. putchar( 'c' ); verno. Delo v tom, chto putchar trebuet argument - simvol, togda kak "c" - STROKA iz odnogo simvola. Bol'shinstvo kompilyatorov (te, kotorye ne proveryayut prototipy vyzova stan- dartnyh funkcij) NE obnaruzhit zdes' nikakoj sintaksicheskoj oshibki (kstati, oshibka eta - semanticheskaya). Takzhe oshibochny operatory printf ( '\n' ); /* nuzhna stroka */ putchar( "\n" ); /* nuzhen simvol */ putchar( "ab" ); /* nuzhen simvol */ putchar( 'ab' ); /* oshibka v bukvennoj konstante */ char c; if((c = getchar()) == "q" ) ... ; /* nuzhno pisat' 'q' */ Otlichajte stroku iz odnogo simvola i simvol - eto raznye veshchi! (Podrobnee ob etom - v sleduyushchej glave). 1.128. Ves'ma chastoj yavlyaetsya oshibka "promah na edinicu", kotoraya vstrechaetsya v ochen' mnogih i raznoobraznyh sluchayah. Vot odna iz vozmozhnyh situacij: int m[20]; int i = 0; while( scanf( "%d", & m[i++] ) != EOF ); printf( "Vveli %d chisel\n", i ); V itoge i okazhetsya na 1 bol'she, chem ozhidalos'. Razberites' v chem delo. Otvet: argumenty funkcii vychislyayutsya do ee vyzova, poetomu kogda my dostigaem konca fajla i scanf vozvrashchaet EOF, i++ v vyzove scanf vse ravno delaetsya. Nado napi- sat' while( scanf( "%d", & m[i] ) != EOF ) i++; 1.129. Zamechanie po stilistike: pri vyvode soobshcheniya na ekran printf( "Hello \n" ); probely pered \n dostatochno bessmyslenny, poskol'ku na ekrane nikak ne otobrazyatsya. Nado pisat' (ekonomya pamyat') printf( "Hello\n" ); A. Bogatyrev, 1992-95 - 64 - Si v UNIX Edinstvennyj sluchaj, kogda takie probely znachimy - eto kogda vy vyvodite tekst inver- siej. Togda probely otobrazhayutsya kak svetlyj fon. Eshche nepriyatnee budet printf( "Hello\n " ); poskol'ku koncevye probely okazhutsya v nachale sleduyushchej stroki. 1.130. printf - interpretiruyushchaya funkciya, t.e. rabotaet ona dovol'no medlenno. Poe- tomu vmesto char s[20]; int i; ... printf( "%c", s[i] ); i printf( "\n" ); nado vsegda pisat' putchar( s[i] ); i putchar( '\n' ); poskol'ku printf v konce-koncov (sdelav vse preobrazovaniya po formatu) vnutri sebya vyzyvaet putchar. Tak sdelaem zhe eto srazu! 1.131. To, chto parametr "format" v funkcii printf mozhet byt' vyrazheniem, pozvolyaet delat' nekotorye udobnye veshchi. Naprimer: int x; ... printf( x ? "znachenie x=%d\n" : "x raven nulyu\n\n", x); Format zdes' - uslovnoe vyrazhenie. Esli x!=0, to budet napechatano znachenie x po for- matu %d. Esli zhe x==0, to budet napechatana stroka, ne soderzhashchaya ni odnogo %-ta. V rezul'tate argument x v spiske argumentov budet prosto proignorirovan. Odnako, nap- rimer int x = ... ; printf( x > 30000 ? "%f\n" : "%d\n", x); (chtoby bol'shie x pechatalis' v vide 31000.000000) nezakonno, poskol'ku celoe chislo nel'zya pechatat' po formatu %f ni v kakih sluchayah. Edinstvennym sposobom sdelat' eto yavlyaetsya yavnoe privedenie x k tipu double: printf("%f\n", (double) x); Budet li zakonen operator? printf( x > 30000 ? "%f\n" : "%d\n", x > 30000 ? (double) x : x ); Otvet: net. Uslovnoe vyrazhenie dlya argumenta budet imet' "starshij" tip - double. A znachenie tipa double nel'zya pechatat' po formatu %d. My dolzhny ispol'zovat' zdes' operator if: if( x > 30000 ) printf("%f\n", (double)x); else printf("%d\n", x); 1.132. Napishite funkciyu, pechatayushchuyu razmer fajla v udobnom vide: esli fajl men'she odnogo kilobajta - pechatat' ego razmer v bajtah, esli zhe bol'she - v kilobajtah (i megabajtah). #define KBYTE 1024L /* kilobajt */ #define THOUSAND 1024L /* kb. v megabajte */ A. Bogatyrev, 1992-95 - 65 - Si v UNIX void tellsize(unsigned long sz){ if(sz < KBYTE) printf("%lu bajt", sz); else{ unsigned long Kb = sz/KBYTE; unsigned long Mb = Kb/THOUSAND; unsigned long Dec = ((sz % KBYTE) * 10) / KBYTE; if( Mb ){ Kb %= THOUSAND; printf( Dec ? "%lu.%03lu.%01lu Mb." : "%lu.%lu Mb.", Mb, Kb, Dec ); } else printf( Dec ? "%lu.%01lu Kb.":"%lu Kb.", Kb, Dec); } putchar('\n'); } 1.133. Dlya pechati strok ispol'zujte printf("%s", string); /* A */ no ne printf(string); /* B */ Esli my ispol'zuem variant B, a v stroke vstretitsya simvol '%' char string[] = "abc%defg"; to %d budet vosprinyato kak format dlya vyvoda celogo chisla. Vo-pervyh, sama stroka %d ne budet napechatana; vo-vtoryh - chto zhe budet pechatat'sya po etomu formatu, kogda u nas est' lish' edinstvennyj argument - string?! Napechataetsya kakoj-to musor! 1.134. Pochemu operator char s[20]; scanf("%s", s); printf("%s\n", s); v otvet na vvod stroki Pushkin A.S. pechataet tol'ko "Pushkin"? Otvet: potomu, chto koncom teksta pri vvode po formatu %s schitaetsya libo \n, libo probel, libo tabulyaciya, a ne tol'ko \n; to est' format %s chitaet slovo iz teksta. CHtenie vseh simvolov do konca stroki, (vklyuchaya probely) dolzhno vyglyadet' tak: scanf("%[^\n]\n", s); %[^\n] - chitat' lyubye simvoly, krome \n (do \n) \n - propustit' \n na konce stroki %[abcdef] - chitat' slovo, sostoyashchee iz perechislennyh bukv. %[^abcde] - chitat' slovo iz lyubyh bukv, krome perechislennyh (prervat'sya po bukve iz spiska). Pust' teper' stroki vhodnoj informacii imeyut format: Frejd Zigmund 1856 1939 Pust' my hotim schityvat' v stroku s familiyu, v celoe y - god rozhdeniya, a prochie polya - ignorirovat'. Kak eto sdelat'? Nam pomozhet format "podavlenie prisvaivaniya" %*: scanf("%s%*s%d%*[^\n]\n", s, &y ); A. Bogatyrev, 1992-95 - 66 - Si v UNIX %* propuskaet pole po formatu, ukazannomu posle *, ne zanosya ego znachenie ni v kakuyu peremennuyu, a prosto "zabyvaya" ego. Tak format "%*[^\n]\n" ignoriruet "hvost" stroki, vklyuchaya simvol perevoda stroki. Simvoly " ", "\t", "\n" v formate vyzyvayut propusk vseh probelov, tabulyacij, perevodov strok vo vhodnom potoke, chto mozhno opisat' kak int c; while((c = getc(stdin))== ' ' || c == '\t' || c == '\n' ); libo kak format %*[ \t\n] Pered chislovymi formatami (%d, %o, %u, %ld, %x, %e, %f), a takzhe %s, propusk probelov delaetsya avtomaticheski. Poetomu scanf("%d%d", &x, &y); i scanf("%d %d", &x, &y); ravnopravny (probel pered vtorym %d prosto ne nuzhen). Neyavnyj propusk probelov ne delaetsya pered %c i %[... , poetomu v otvet na vvod stroki "12 5 x" primer main(){ int n, m; char c; scanf("%d%d%c", &n, &m, &c); printf("n=%d m=%d c='%c'\n", n, m, c); } napechataet "n=12 m=5 c=' '", to est' v c budet prochitan probel (predshestvovavshij x), a ne x. Avtomaticheskij propusk probelov pered %s ne pozvolyaet schityvat' po %s stroki, lidiruyushchie probely kotoryh dolzhny sohranyat'sya. CHtoby lidiruyushchie probely takzhe schity- valis', sleduet ispol'zovat' format scanf("%[^\n]%*1[\n]", s); v kotorom modifikator dliny 1 zastavlyaet ignorirovat' tol'ko odin simvol \n, a ne VSE probely i perevody strok, kak "\n". K sozhaleniyu (kak pokazal eksperiment) etot format ne v sostoyanii prochest' pustuyu stroku (sostoyashchuyu tol'ko iz \n). Poetomu mozhno sdelat' global'nyj vyvod: stroki nado schityvat' pri pomoshchi funkcij gets() i fgets()! 1.135. Eshche para slov pro scanf: scanf vozvrashchaet chislo uspeshno prochitannyh im dannyh (obrabotannyh %-ov) ili EOF v konce fajla. Neudacha mozhet nastupit', esli dannoe vo vhodnom potoke ne sootvetstvuet formatu, naprimer stroka 12 quack dlya int d1; double f; scanf("%d%lf", &d1, &f); V etom sluchae scanf prochtet 12 po formatu %d v peremennuyu d1, no slovo quack ne otve- chaet formatu %lf, poetomu scanf prervet svoyu rabotu i vydast znachenie 1 (uspeshno pro- chel odin format). Stroka quack ostanetsya nevostrebovannoj - ee prochitayut posleduyushchie vyzovy funkcij chteniya; a sejchas f ostanetsya neizmenennoj. 1.136. Si imeet kvalifikator const, ukazyvayushchij, chto znachenie yavlyaetsya ne peremen- noj, a konstantoj, i popytka izmenit' velichinu po etomu imeni yavlyaetsya oshibkoj. Vo mnogih sluchayah const mozhet zamenit' #define, pri etom eshche yavno ukazan tip konstanty, chto polezno dlya proverok kompilyatorom. A. Bogatyrev, 1992-95 - 67 - Si v UNIX const int x = 22; x = 33; /* oshibka: konstantu nel'zya menyat' */ Ispol'zovanie const s ukazatelem: Ukazuemyj ob®ekt - konstanta const char *pc = "abc"; pc[1] = 'x'; /* oshibka */ pc = "123"; /* OK */ Sam ukazatel' - konstanta char *const cp = "abc"; cp[1] = 'x'; /* OK */ cp = "123"; /* oshibka */ Ukazuemyj ob®ekt i sam ukazatel' - konstanty const char *const cpc = "abc"; cpc[1] = 'x'; /* oshibka */ cpc = "123"; /* oshibka */ Ukazatel' na konstantu neobhodimo ob®yavlyat' kak const TYPE* int a = 1; const int b = 2; const int *pca = &a; /* OK, prosto rassmatrivaem a kak konstantu */ const int *pcb = &b; /* OK */ int *pb = &b; /* oshibka, tak kak togda vozmozhno bylo by napisat' */ *pb = 3; /* izmenit' konstantu b */ 1.137. Standartnaya funkciya bystroj sortirovki qsort (algoritm quick sort) imeet takoj format: chtoby otsortirovat' massiv elementov tipa TYPE TYPE arr[N]; nado vyzyvat' qsort(arr,/* CHto sortirovat'? Ne s nachala: arr+m */ N, /* Skol'ko pervyh elementov massiva? */ /* mozhno sortirovat' tol'ko chast': n < N */ sizeof(TYPE),/* Ili sizeof arr[0] */ /* razmer odnogo elementa massiva*/ cmp); gde int cmp(TYPE *a1, TYPE *a2); funkciya sravneniya elementov *a1 i *a2. Ee argumenty - ADRESA dvuh kakih-to elementov sortiruemogo massiva. Funkciyu cmp my dolzhny napisat' sami - eto funkciya, zadayushchaya uporyadochenie elementov massiva. Dlya sortirovki po vozrastaniyu funkciya cmp() dolzhna vozvrashchat' celoe < 0, esli *a1 dolzhno idti ran'she *a2 < = 0, esli *a1 sovpadaet s *a2 == > 0, esli *a1 dolzhno idti posle *a2 > Dlya massiva strok elementy massiva imeyut tip (char *), poetomu argumenty funkcii imeyut tip (char **). Trebuemomu usloviyu udovletvoryaet takaya funkciya: A. Bogatyrev, 1992-95 - 68 - Si v UNIX char *arr[N]; ... cmps(s1, s2) char **s1, **s2; { return strcmp(*s1, *s2); } (Pro strcmp smotri razdel "Massivy i stroki"). Zametim, chto v nekotoryh sistemah programmirovaniya (naprimer v TurboC++ |-) vy dolzhny ispol'zovat' funkciyu sravneniya s prototipom int cmp (const void *a1, const void *a2); i vnutri nee yavno delat' privedenie tipa: cmps (const void *s1, const void *s2) { return strcmp(*(char **)s1, *(char **)s2); } ili mozhno postupit' sleduyushchim obrazom: int cmps(char **s1, char **s2){ return strcmp(*s1, *s2); } typedef int (*CMPS)(const void *, const void *); qsort((void *) array, ..., ..., (CMPS) cmps); Nakonec, vozmozhno i prosto ob®yavit' int cmps(const void *A, const void *B){ return strcmp(A, B); } Dlya massiva celyh goditsya takaya funkciya sravneniya: int arr[N]; ... cmpi(i1, i2) int *i1, *i2; { return *i1 - *i2; } Dlya massiva struktur, kotorye my sortiruem po celomu polyu key, goditsya struct XXX{ int key; ... } arr[N]; cmpXXX(st1, st2) struct XXX *st1, *st2; { return( st1->key - st2->key ); } Pust' u nas est' massiv long. Mozhno li ispol'zovat' long arr[N]; ... cmpl(L1, L2) long *L1, *L2; { return *L1 - *L2; } Otvet: okazyvaetsya, chto net. Funkciya cmpl dolzhna vozvrashchat' celoe, a raznost' dvuh long-ov imeet tip long. Poetomu kompilyator privodit etu raznost' k tipu int (kak pravilo obrubaniem starshih bitov). Pri etom (esli long-chisla byli veliki) rezul'tat mozhet izmenit' znak! Naprimer: main(){ int n; long a = 1L; long b = 777777777L; n = a - b; /* dolzhno by byt' otricatel'nym... */ printf( "%ld %ld %d\n", a, b, n ); } ____________________ |- TurboC - kompilyator Si v MS DOS, razrabotannyj firmoj Borland International. A. Bogatyrev, 1992-95 - 69 - Si v UNIX pechataet 1 777777777 3472. Funkciya sravneniya dolzhna vyglyadet' tak: cmpl(L1, L2) long *L1, *L2; { if( *L1 == *L2 ) return 0; if( *L1 < *L2 ) return (-1); return 1; } ili cmpl(L1, L2) long *L1, *L2; { return( *L1 == *L2 ? 0 : *L1 < *L2 ? -1 : 1 ); } poskol'ku vazhna ne velichina vozvrashchennogo znacheniya, a tol'ko ee znak. Uchtite, chto dlya ispol'zovaniya funkcii sravneniya vy dolzhny libo opredelit' funk- ciyu sravneniya do ee ispol'zovaniya v qsort(): int cmp(...){ ... } /* realizaciya */ ... qsort(..... , cmp); libo predvaritel'no ob®yavit' imya funkcii sravneniya, chtoby kompilyator ponimal, chto eto imenno funkciya: int cmp(); qsort(..... , cmp); ... int cmp(...){ ... } /* realizaciya */ 1.138. Pust' u nas est' dve programmy, pol'zuyushchiesya odnoj i toj zhe strukturoj dannyh W: a.c b.c -------------------------- ------------------------------ #include <fcntl.h> #include <fcntl.h> struct W{ int x,y; }a; struct W{ int x,y; }b; main(){ int fd; main(){ int fd; a.x = 12; a.y = 77; fd = open("f", O_RDONLY); fd = creat("f", 0644); read(fd, &b, sizeof b); write(fd, &a, sizeof a); close(fd); close(fd); printf("%d %d\n", b.x, b.y); } } CHto budet, esli my izmenim strukturu na struct W { long x,y; }; ili struct W { char c; int x,y; }; v fajle a.c i zabudem sdelat' eto v b.c? Budut li pravil'no rabotat' eti programmy? Iz nablyudaemogo mozhno sdelat' vyvod, chto esli dve ili neskol'ko programm (ili chastej odnoj programmy), razmeshchennye v raznyh fajlah, ispol'zuyut obshchie - tipy dannyh (typedef); - struktury i ob®edineniya; - konstanty (opredeleniya #define); - prototipy funkcij; to ih opredeleniya luchshe vynosit' v obshchij include-fajl (header-fajl), daby vse prog- rammy priderzhivalis' odnih i teh zhe obshchih soglashenij. Dazhe esli eti soglasheniya so A. Bogatyrev, 1992-95 - 70 - Si v UNIX vremenem izmenyatsya, to oni izmenyatsya vo vseh fajlah sinhronno i kak by sami soboj. V nashem sluchae ispravlyat' opredelenie struktury pridetsya tol'ko v include-fajle, a ne vyiskivat' vse mesta, gde ono napisano, ved' pri etom nemudreno kakoe-nibud' mesto i propustit'! W.h ----------------------- struct W{ long x, y; }; a.c b.c -------------------------- ------------------ #include <fcntl.h> #include <fcntl.h> #include "W.h" #include "W.h" struct W a; struct W b; main(){ ... main(){ ... printf("%ld... Krome togo, vynesenie obshchih fragmentov teksta programmy (opredelenij struktur, kons- tant, i.t.p.) v otdel'nyj fajl ekonomit nashi sily i vremya - vmesto togo, chtoby nabi- vat' odin i tot zhe tekst mnogo raz v raznyh fajlah, my teper' pishem v kazhdom fajle edinstvennuyu stroku - direktivu #include. Krome togo, ekonomitsya i mesto na diske, ved' programma stala koroche! Fajly vklyucheniya imeyut suffiks .h, chto oznachaet "header-file" (fajl-zagolovok). Sinhronnuyu perekompilyaciyu vseh programm v sluchae izmeneniya include-fajla mozhno zadat' v fajle Makefile - programme dlya koordinatora make|-: all: a b echo Zapusk a i b a ; b a: a.c W.h cc a.c -o a b: b.c W.h cc b.c -o b Pravila make imeyut vid cel': spisok_celej_ot_kotoryh_zavisit komanda komanda opisyvaet chto nuzhno sdelat', chtoby izgotovit' fajl cel' iz fajlov spisok_celej_ot_kotoryh_zavisit. Komanda vypolnyaetsya tol'ko esli fajl cel' eshche ne sushchestvuet, libo hot' odin iz fajlov sprava ot dvoetochiya yavlyaetsya bolee "molodym" (svezhim), chem celevoj fajl (smotri pole st_mtime i sisvyzov stat v glave pro UNIX). 1.139. Programma na Si mozhet byt' razmeshchena v neskol'kih fajlah. Kazhdyj fajl vystu- paet v roli "modulya", v kotorom sobrany shodnye po naznacheniyu funkcii i peremennye. Nekotorye peremennye i funkcii mozhno sdelat' nevidimymi dlya drugih modulej. Dlya etogo nado ob®yavit' ih static: - Ob®yavlenie peremennoj vnutri funkcii kak static delaet peremennuyu staticheskoj (t.e. ona budet sohranyat' svoe znachenie pri vyhode iz funkcii) i ogranichivaet ee vidimost' predelami dannoj funkcii. - Peremennye, opisannye vne funkcij, i tak yavlyayutsya staticheskimi (po klassu pamyati). Odnako slovo static i v etom sluchae pozvolyaet upravlyat' vidimost'yu etih peremennyh - oni budut vidimy tol'ko v predelah dannogo fajla. - Funkcii, ob®yavlennye kak static, takzhe vidimy tol'ko v predelah dannogo fajla. - Argumenty funkcii i lokal'nye (avtomaticheskie) peremennye funkcii i tak sushchest- vuyut tol'ko na vremya vyzova dannoj funkcii (pamyat' dlya nih vydelyaetsya v steke ____________________ |- Podrobnoe opisanie make smotri v dokumentacii po sisteme UNIX. A. Bogatyrev, 1992-95 - 71 - Si v UNIX pri vhode v funkciyu i unichtozhaetsya pri vyhode) i vidimy tol'ko vnutri ee tela. Argumenty funkcii nel'zya ob®yavlyat' static: f(x) static x; { x++; } nezakonno. Takim obrazom vse peremennye i funkcii v dannom fajle delyatsya na dve gruppy: - Vidimye tol'ko vnutri dannogo fajla (lokal'nye dlya modulya). Takie imena ob®yav- lyayutsya s ispol'zovaniem klyuchevogo slova static. V chastnosti est' eshche "bolee lokal'nye" peremennye - avtomaticheskie lokaly funkcij i ih formal'nye argumenty, kotorye vidimy tol'ko v predelah dannoj funkcii. Takzhe vidimy lish' v predelah odnoj funkcii staticheskie lokal'nye peremennye, ob®yavlennye v tele funkcii so slovom static. - Vidimye vo vseh fajlah (global'nye imena). Global'nye imena obrazuyut interfejs modulya i mogut byt' ispol'zovany v drugih modu- lyah. Lokal'nye imena izvne modulya nedostupny. Esli my ispol'zuem v fajle-module funkcii i peremennye, vhodyashchie v interfejs drugogo fajla-modulya, my dolzhny ob®yavit' ih kak extern ("vneshnie"). Dlya funkcij opi- sateli extern i int mozhno opuskat': // fajl A.c int x, y, z; // global'nye char ss[200]; // glob. static int v, w; // lokal'nye static char *s, p[20]; // lok. int f(){ ... } // glob. char *g(){ ... } // glob. static int h(){ ... } // lok. static char *sf(){ ... } // lok. int fi(){ ... } // glob. // fajl B.c extern int x, y; extern z; // int mozhno opustit' extern char ss[]; // razmer mozhno opustit' extern int f(); char *g(); // extern mozhno opustit' extern fi(); // int mozhno opustit' Horoshim tonom yavlyaetsya napisanie kommentariya - iz kakogo modulya ili biblioteki impor- tiruetsya peremennaya ili funkciya: extern int x, y; /* import from A.c */ char *tgetstr(); /* import from termlib */ Sleduyushchaya programma sobiraetsya iz fajlov A.c i B.c komandoj|= ____________________ |= Mozhno zadat' Makefile vida CFLAGS = -O AB: A.o B.o cc A.o B.o -o AB A.o: A.c cc -c $(CFLAGS) A.c B.o: B.c cc -c $(CFLAGS) B.c i sobirat' programmu prosto vyzyvaya komandu make. A. Bogatyrev, 1992-95 - 72 - Si v UNIX cc A.c B.c -o AB Pochemu kompilyator soobshchaet "x dvazhdy opredeleno"? fajl A.c fajl B.c ----------------------------------------- int x=12; int x=25; main(){ f(y) int *y; f(&x); { printf("%d\n", x); *y += x; } } Otvet: potomu, chto v kazhdom fajle opisana global'naya peremennaya x. Nado v odnom iz nih (ili v oboih srazu) sdelat' x lokal'nym imenem (isklyuchit' ego iz interfejsa modulya): static int x=...; Pochemu v sleduyushchem primere kompilyator soobshchaet "_f dvazhdy opredeleno"? fajl A.c fajl B.c ---------------------------------------------------- int x; extern int x; main(){ f(5); g(77); } g(n){ f(x+n); } f(n) { x=n; } f(m){ printf("%d\n", m); } Otvet: nado sdelat' v fajle B.c funkciyu f lokal'noj: static f(m)... Hot' v odnom fajle dolzhna byt' opredelena funkciya main, vyzyvaemaya sistemoj pri zapuske programmy. Esli takoj funkcii nigde net - kompilyator vydaet soobshchenie "_main neopredeleno". Funkciya main dolzhna byt' opredelena odin raz! V fajle ona mozhet naho- dit'sya v lyubom meste - ne trebuetsya, chtoby ona byla samoj pervoj (ili poslednej) funkciej fajla|=. 1.140. V chem oshibka? fajl A.c fajl B.c ---------------------------------------------------- extern int x; extern int x; main(){ x=2; f(){ f(); printf("%d\n", x); } } Otvet: peremennaya x v oboih fajlah ob®yavlena kak extern, v rezul'tate pamyat' dlya nee nigde ne vydelena, t.e. x ne opredelena ni v odnom fajle. Uberite odno iz slov extern! 1.141. V chem oshibka? fajl A.c fajl B.c ---------------------------------------------------- int x; extern double x; ... ... Tipy peremennyh ne sovpadayut. Bol'shinstvo kompilyatorov ne lovit takuyu oshibku, t.k. kazhdyj fajl kompiliruetsya otdel'no, nezavisimo ot ostal'nyh, a pri "sklejke" fajlov v ____________________ |= Esli vy pol'zuetes' "novym" stilem ob®yavleniya funkcij, no ne ispol'zuete proto- tipy, to sleduet opredelyat' kazhduyu funkciyu do pervogo mesta ee ispol'zovaniya, chtoby kompilyatoru v tochke vyzova byl izvesten ee zagolovok. |to privedet k tomu, chto main() okazhetsya poslednej funkciej v fajle - ee ne vyzyvaet nikto, zato ona vyzyvaet kogo-to eshche. A. Bogatyrev, 1992-95 - 73 - Si v UNIX obshchuyu vypolnyaemuyu programmu komponovshchik znaet lish' imena peremennyh i funkcij, no ne ih tipy i prototipy. V rezul'tate programma normal'no skompiliruetsya i soberetsya, no rezul'tat ee vypolneniya budet nepredskazuem! Poetomu ob®yavleniya extern tozhe polezno vynosit' v include-fajly: fajl proto.h ------------------ extern int x; fajl A.c fajl B.c ------------------ ------------------ #include "proto.h" #include "proto.h" int x; ... to, chto peremennaya x v A.c okazyvaetsya opisannoj i kak extern - vpolne dopustimo, t.k. v moment nastoyashchego ob®yavleniya etoj peremennoj eto slovo nachnet prosto ignoriro- vat'sya (lish' by tipy v ob®yavlenii s extern i bez nego sovpadali - inache oshibka!). 1.142. CHto pechataet programma i pochemu? int a = 1; /* primer Bjarne Stroustrup-a */ void f(){ int b = 1; static int c = 1; printf("a=%d b=%d c=%d\n", a++, b++, c++); } void main(){ while(a < 4) f(); } Otvet: a=1 b=1 c=1 a=2 b=1 c=2 a=3 b=1 c=3 1.143. Avtomaticheskaya peremennaya vidima tol'ko vnutri bloka, v kotorom ona opisana. CHto napechataet programma? /* fajl A.c */ int x=666; /*glob.*/ main(){ f(3); printf(" ::x = %d\n", x); g(2); g(5); printf(" ::x = %d\n", x); } g(n){ static int x=17; /*vidima tol'ko v g*/ printf("g::x = %2d g::n = %d\n", x++, n); if(n) g(n-1); else x = 0; } /* fajl B.c */ extern x; /*global*/ f(n){ /*lokal funkcii*/ x++; /*global*/ { int x; /*lokal bloka*/ x = n+1; /*lokal*/ A. Bogatyrev, 1992-95 - 74 - Si v UNIX n = 2*x; /*lokal*/ } x = n-1; /*global*/ } 1.144. Funkciya, kotoraya - ne soderzhit vnutri sebya staticheskih peremennyh, hranyashchih sostoyanie processa obrabotki dannyh (funkciya bez "pamyati"); - poluchaet znacheniya parametrov tol'ko cherez svoi argumenty (no ne cherez global'nye staticheskie peremennye); - vozvrashchaet znacheniya tol'ko cherez argumenty, libo kak znachenie funkcii (cherez return); nazyvaetsya reenterabel'noj (povtorno vhodimoj) ili chistoj (pure). Takaya funkciya mozhet parallel'no (ili psevdoparallel'no) ispol'zovat'sya neskol'kimi "potokami" obra- botki informacii v nashej programme, bez kakogo-libo nepredvidennogo vliyaniya etih "potokov obrabotki" drug na druga. Pervyj punkt trebovanij pozvolyaet funkcii ne zaviset' ni ot kakogo konkretnogo processa obrabotki dannyh, t.k. ona ne "pomnit" obrabotannyh eyu ranee dannyh i ne stroit svoe povedenie v zavisimosti ot nih. Vtorye dva punkta - eto trebovanie, chtoby vse bez isklyucheniya puti peredachi dannyh v funkciyu i iz nee (interfejs funkcii) byli perechisleny v ee zagolovke. |to lishaet funkciyu "pobochnyh effektov", ne predusmotrennyh programmistom pri ee vyzove (programmist obychno smotrit tol'ko na zagolovok funkcii, i ne vyiskivaet "tajnye" svyazi funkcii s programmoj cherez global'nye peremennye, esli tol'ko eto special'no ne ogovoreno). Vot primer ne reenterabel'noj funkcii: FILE *fp; ... /* global'nyj argument */ char delayedInput () { static char prevchar; /* pamyat' */ char c; c = prevchar; prevchar = getc (fp); return c; } A vot ee reenterabel'nyj ekvivalent: char delayedInput (char *prevchar, FILE *fp) { char c; c = *prevchar; *prevchar = getc (fp); return c; } /* vyzov: */ FILE *fp1, *fp2; char prev1, prev2, c1, c2; ... x1 = delayedInput (&prev1, fp1); x2 = delayedInput (&prev2, fp2); ... Kak vidim, vse "zapominayushchie" peremennye (t.e. prevchar) vyneseny iz samoj funkcii i podayutsya v nee v vide argumenta. Reenterabel'nye funkcii nezavisimy ot ostal'noj chasti programmy (ih mozhno skopi- rovat' v drugoj programmnyj proekt bez izmenenij), bolee ponyatny (poskol'ku vse zat- ragivaemye imi vneshnie peremennye perechisleny kak argumenty, ne nado vy