tsya takzhe i "global'nymi", tak chto vse ssylki na takuyu peremennuyu, ispol'zuyushchie odno i to zhe imya (dazhe iz funkcij, skompilirovannyh nezavisimo), budut ssylkami na odno i to zhe. V etom smysle vneshnie pere- mennye analogichny peremennym COmMON v fortrane i EXTERNAL v PL/1. Pozdnee my pokazhem, kak opredelit' vneshnie peremennye i funkcii takim obrazom, chtoby oni byli dostupny ne global'- no, a tol'ko v predelah odnogo ishodnogo fajla. V silu svoej global'noj dostupnosti vneshnie peremennye predostavlyayut druguyu, otlichnuyu ot argumentov i vozvrashchaemyh znachenij, vozmozhnost' dlya obmena dannymi mezhdu funkciyami. Esli imya vneshnej peremennoj kakim-libo obrazom opisano, to lyubaya funkciya imeet dostup k etoj peremennoj, ssylayas' k nej po etomu imeni. V sluchayah, kogda svyaz' mezhdu funkciyami osushchestvlyaetsya s pomoshch'yu bol'shogo chisla peremennyh, vneshnie peremennye okazy- vayutsya bolee udobnymi i effektivnymi, chem ispol'zovanie dlinnyh spiskov argumentov. Kak, odnako, otmechalos' v glave 1, eto soobrazhenie sleduet ispol'zovat' s opredelennoj osto- rozhnost'yu, tak kak ono mozhet ploho otrazit'sya na strukture programm i privodit' k programmam s bol'shim chislom svyazej po dannym mezhdu funkciyami. Vtoraya prichina ispol'zovaniya vneshnih peremennyh svyazana s inicializaciej. V chastnosti, vneshnie massivy mogut byt' inicializirovany a avtomaticheskie net. My rassmotrim vopros ob inicializacii v konce etoj glavy. Tret'ya prichina ispol'zovaniya vneshnih peremennyh obuslov- lena ih oblast'yu dejstviya i vremenem sushchestvovaniya. Avtoma- ticheskie peremennye yavlyayutsya vnutrennimi po otnosheniyu k fun- kciyam; oni voznikayut pri vhode v funkciyu i ischezayut pri vy- hode iz nee. Vneshnie peremennye, naprotiv, sushchestvuyut posto- yanno. Oni ne poyavlyayutya i ne ischezayut, tak chto mogut sohra- nyat' svoi znacheniya v period ot odnogo obrashcheniya k funkcii do drugogo. V silu etogo, esli dve funkcii ispol'zuyut nekotorye obshchie dannye, prichem ni odna iz nih ne obrashchaetsya k drugoj , to chasto naibolee udobnym okazyvaetsya hranit' eti obshchie dan- nye v vide vneshnih peremennyh, a ne peredavat' ih v funkciyu i obratno s pomoshch'yu argumentov. Davajte prodolzhim obsuzhdenie etogo voprosa na bol'shom primere. Zadacha budet sostoyat' v napisanii drugoj programmy dlya kal'kulyatora, luchshej,chem predydushchaya. Zdes' dopuskayutsya operacii +,-,*,/ i znak = (dlya vydachi otveta).vmesto infiks- nogo predstavleniya kal'kulyator budet ispol'zovat' obratnuyu pol'skuyu notaciyu,poskol'ku ee neskol'ko legche realizovat'.v obratnoj pol'skoj notacii znak sleduet za operandami; infik- snoe vyrazhenie tipa (1-2)*(4+5)= zapisyvaetsya v vide 12-45+*= kruglye skobki pri etom ne nuzhny Realizaciya okazyvaetsya ves'ma prostoj.kazhdyj operand po- meshchaetsya v stek; kogda postupaet znak operacii,nuzhnoe chislo operandov (dva dlya binarnyh operacij) vynimaetsya,k nim pri- menyaetsya operaciya i rezul'tat napravlyaetsya obratno v stek.tak v privedennom vyshe primere 1 i 2 pomeshchayutsya v stek i zatem zamenyayutsya ih raznost'yu, -1.posle etogo 4 i 5 vvo- dyatsya v stek i zatem zamenyayutsya svoej summoj,9.dalee chisla -1 i 9 zamenyayutsya v steke na ih proizvedenie,ravnoe -9.ope- raciya = pechataet verhnij element steka, ne udalyaya ego (tak chto promezhutochnye vychisleniya mogut byt' provereny). Sami operacii pomeshcheniya chisel v stek i ih izvlecheniya ochen' prosty,no, v svyazi s vklyucheniem v nastoyashchuyu programmu obnaruzheniya oshibok i vosstanovleniya,oni okazyvayutsya dosta- tochno dlinnymi. Poetomu luchshe oformit' ih v vide otdel'nyh funkcij,chem povtoryat' sootvetstvuyushchij tekst povsyudu v prog- ramme. Krome togo, nuzhna otdel'naya funkciya dlya vyborki iz vvoda sleduyushchej operacii ili operanda. Takim obrazom, struk- tura programmy imeet vid: WHILE( postupaet operaciya ili operand, a ne konec IF ( chislo ) pomestit' ego v stek eLSE IF ( operaciya ) vynut' operandy iz steka vypolnit' operaciyu pomestit' rezul'tat v stek ELSE oshibka Osnovnoj vopros, kotoryj eshche ne byl obsuzhden, zaklyuchaet- sya v tom,gde pomestit' stek, t. E. Kakie procedury smogut obrashchat'sya k nemu neposredstvenno. Odna iz takih vozmozhnos- tej sostoit v pomeshchenii steka v MAIN i peredachi samogo steka i tekushchej pozicii v steke funkciyam, rabotayushchim so stekom. No funkcii MAIN net neobhodimosti imet' delo s peremennymi, up- ravlyayushchimi stekom; ej estestvenno rassuzhdat' v terminah po- meshcheniya chisel v stek i izvlecheniya ih ottuda. V silu etogo my reshili sdelat' stek i svyazannuyu s nim informaciyu vneshnimi peremennymi , dostupnymi funkciyam PUSH (pomeshchenie v stek) i POP (izvlechenie iz steka), no ne MAIN. Perevod etoj shemy v programmu dostatochno prost. Vedushchaya programma yavlyaetsya po sushchestvu bol'shim pereklyuchatelem po ti- pu operacii ili operandu; eto, po-vidimomu, bolee harakter- noe primeneie pereklyuchatelya, chem to, kotoroe bylo prodemons- trirovano v glave 3. #DEFINE MAXOP 20 /* MAX SIZE OF OPERAND, OPERATOR * #DEFINE NUMBER '0' /* SIGNAL THAT NUMBER FOUND */ #DEFINE TOOBIG '9' /* SIGNAL THAT STRING IS TOO BIG * MAIN() /* REVERSE POLISH DESK CALCULATOR */ /( INT TUPE; CHAR S[MAXOP]; DOUBLE OP2,ATOF(),POP(),PUSH(); WHILE ((TUPE=GETOP(S,MAXOP)) !=EOF); SWITCH(TUPE) /( CASE NUMBER: PUSH(ATOF(S)); BREAK; CASE '+': PUSH(POP()+POP()); BREAK; CASE '*': PUSH(POP()*POP()); BREAK; CASE '-': OP2=POP(); PUSH(POP()-OP2); BREAK; CASE '/': OP2=POP(); IF (OP2 != 0.0) PUSH(POP()/OP2); ELSE PRINTF("ZERO DIVISOR POPPED\N"); BREAK; CASE '=': PRINTF("\T%F\N",PUSH(POP())); BREAK; CASE 'C': CLEAR(); BREAK; CASE TOOBIG: PRINTF("%.20S ... IS TOO LONG\N",S) BREAK; /) /) #DEFINE MAXVAL 100 /* MAXIMUM DEPTH OF VAL STACK */ INT SP = 0; /* STACK POINTER */ DOUBLE VAL[MAXVAL]; /*VALUE STACK */ DOUBLE PUSH(F) /* PUSH F ONTO VALUE STACK */ DOUBLE F; /( IF (SP < MAXVAL) RETURN(VAL[SP++] =F); ELSE /( PRINTF("ERROR: STACK FULL\N"); CLEAR(); RETURN(0); /) /) DOUBLE POP() /* POP TOP VALUE FROM STEACK */ /( IF (SP > 0) RETURN(VAL[--SP]); ELSE /( PRINTF("ERROR: STACK EMPTY\N"); CLEAR(); RETURN(0); /) /) CLEAR() /* CLEAR STACK */ /( SP=0; /) Komanda C ochishchaet stek s pomoshch'yu funkcii CLEAR, kotoraya takzhe ispol'zuetsya v sluchae oshibki funkciyami PUSH i POP. k funkcii GETOP my ochen' skoro vernemsya. Kak uzhe govorilos' v glave 1, peremennaya yavlyaetsya vnesh- nej, esli ona opredelena vne tela kakoj by to ni bylo funk- cii. Poetomu stek i ukazatel' steka, kotorye dolzhny ispol'- zovat'sya funkciyami PUSH, POP i CLEAR, opredeleny vne etih treh funkcij. No sama funkciya MAIN ne ssylaetsya ni k steku, ni k ukazatelyu steka - ih uchastie tshchatel'no zamaskirovano. V silu etogo chast' programmy, sootvetstvuyushchaya operacii = , is- pol'zuet konstrukciyu PUSH(POP()); dlya togo, chtoby proanalizirovat' verhnij element steka, ne izmenyaya ego. Otmetim takzhe, chto tak kak operacii + i * kommutativny, poryadok, v kotorom ob容dinyayutsya izvlechennye operandy, nesu- shchestvenen, no v sluchae operacij - i / neobhodimo razlichat' levyj i pravyj operandy. Uprazhnenie 4-3 --------------- Privedennaya osnovnaya shema dopuskaet neposredstvennoe rasshirenie vozmozhnostej kal'kulyatora. Vklyuchite operaciyu de- leniya po modulyu /%/ i unarnyj minus. Vklyuchite komandu "ste- ret'", kotoraya udalyaet verhnij element steka. Vvedite koman- dy dlya raboty s peremennymi. /|to prosto, esli imena pere- mennyh budut sostoyat' iz odnoj bukvy iz imeyushchihsya dvadcati shesti bukv/. 4.5. Pravila, opredelyayushchie oblast' dejstviya Funkcii i vneshnie peremennye, vhodyashchie v sostav "C"-programmy, ne obyazany kompilirovat'sya odnovremenno; programma na ishodnom yazyke mozhet raspolagat'sya v neskol'kih fajlah, i ranee skompilirovannye procedury mogut zagruzhat'sya iz bibliotek. Dva voprosa predstavlyayut interes: Kak sleduet sostavlyat' opisaniya, chtoby peremennye pra- vil'no vosprinimalis' vo vremya kompilyacii ? Kak sleduet sostavlyat' opisaniya, chtoby obespechit' pra- vil'nuyu svyaz' chastej programmy pri zagruzke ? 4.5.1. Oblast' dejstviya Oblast'yu dejstviya imeni yavlyaetsya ta chast' programmy, v kotoroj eto imya opredeleno. Dlya avtomaticheskoj peremennoj, opisannoj v nachale funkcii, oblast'yu dejstviya yavlyaetsya ta funkciya, v kotoroj opisano imya etoj peremennoj, a peremennye iz raznyh funkcij, imeyushchie odinakovoe imya, schitayutsya ne ot- nosyashchimisya drug k drugu. |to zhe spravedlivo i dlya argumentov funkcij. Oblast' dejstviya vneshnej peremennoj prostiraetsya ot toch- ki, v kotoroj ona ob座avlena v ishodnom fajle, do konca etogo fajla. Naprimer, esli VAL, SP, PUSH, POP i CLEAR opredeleny v odnom fajle v poryadke, ukazannom vyshe, a imenno: INT SP = 0; DOUBLE VAL[MAXVAL]; DOUBLE PUSH(F) {...} DOUBLE POP() {...} CLEAR() {...} to peremennye VAL i SP mozhno ispol'zovat' v PUSH, POP i CLEAR pryamo po imeni; nikakie dopolnitel'nye opisaniya ne nuzhny. S drugoj storony, esli nuzhno soslat'sya na vneshnyuyu pere- mennuyu do ee opredeleniya, ili esli takaya peremennaya oprede- lena v fajle, otlichnom ot togo, v kotorom ona ispol'zuetsya, to neobhodimo opisanie EXTERN. Vazhno razlichat' opisanie vneshnej peremennoj i ee oprede- lenie. opisanie ukazyvaet svojstva peremennoj /ee tip, raz- mer i t.d./; opredelenie zhe vyzyvaet eshche i otvedenie pamyati. Esli vne kakoj by to ni bylo funkcii poyavlyayutsya strochki INT SP; DOUBLE VAL[MAXVAL]; to oni opredelyayut vneshnie peremennye SP i VAL, vyzyvayut ot- vedenie pamyati dlya nih i sluzhat v kachestve opisaniya dlya os- tal'noj chasti etogo ishodnogo fajla. V to zhe vremya strochki EXTERN INT SP; EXTERN DOUBLE VAL[]; opisyvayut v ostal'noj chasti etogo ishodnogo fajla peremennuyu SP kak INT, a VAL kak massiv tipa DOUBLE /razmer kotorogo ukazan v drugom meste/, no ne sozdayut peremennyh i ne otvo- dyat im mesta v pamyati. Vo vseh fajlah, sostavlyayushchih ishodnuyu programmu, dolzhno soderzhat'sya tol'ko odno opredelenie vneshnej peremennoj; dru- gie fajly mogut soderzhat' opisaniya EXTERN dlya dostupa k nej. /Opisanie EXTERN mozhet imet'sya i v tom fajle, gde nahoditsya opredelenie/. Lyubaya inicializaciya vneshnej peremennoj provo- ditsya tol'ko v opredelenii. V opredelenii dolzhny ukazyvat'sya razmery massivov, a v opisanii EXTERN etogo mozhno ne delat'. Hotya podobnaya organizaciya privedennoj vyshe programmy i maloveroyatna, no VAL i SP mogli by byt' opredeleny i inicia- lizirovany v odnom fajle, a funkciya PUSH, POP i CLEAR opre- deleny v drugom. V etom sluchae dlya svyazi byli by neobhodimy sleduyushchie opredeleniya i opisaniya: v fajle 1: ---------- INT SP = 0; /* STACK POINTER */ DOUBLE VAL[MAXVAL]; /* VALUE STACK */ v fajle 2: ---------- EXTERN INT SP; EXTERN DOUBLE VAL[]; DOUBLE PUSH(F) {...} DOUBLE POP() {...} CLEAR() {...} tak kak opisaniya EXTERN 'v fajle 1' nahodyatsya vyshe i vne treh ukazannyh funkcij, oni otnosyatsya ko vsem nim; odnogo nabora opisanij dostatochno dlya vsego 'fajla 2'. Dlya programm bol'shogo razmera obsuzhdaemaya pozzhe v etoj glave vozmozhnost' vklyucheniya fajlov, #INCLUDE, pozvolyaet imet' vo vsej programme tol'ko odnu kopiyu opisanij EXTERN i vstavlyat' ee v kazhdyj ishodnyj fajl vo vremya ego kompilyacii. Obratimsya teper' k funkcii GETOP, vybirayushchej iz fajla vvoda sleduyushchuyu operaciyu ili operand. Osnovnaya zadacha pros- ta: propustit' probely, znaki tabulyacii i novye stroki. Esli sleduyushchij simvol otlichen ot cifry i desyatichnoj tochki, to vozvratit' ego. V protivnom sluchae sobrat' stroku cifr /ona mozhet vklyuchat' desyatichnuyu tochku/ i vozvratit' NUMBER kak signal o tom, chto vybrano chislo. Procedura sushchestvenno uslozhnyaetsya, esli stremit'sya pra- vil'no obrabatyvat' situaciyu, kogda vvodimoe chislo okazyva- etsya slishkom dlinnym. Funkciya GETOP schityvaet cifry podryad /vozmozhno s desyatichnoj tochkoj/ i zapominaet ih, poka posle- dovatel'nost' ne preryvaetsya. Esli pri etom ne proishodit perepolneniya, to funkciya vozvrashchaet NUMBER i stroku cifr. Esli zhe chislo okazyvaetsya slishkom dlinnym, to GETOP otbrasy- vaet ostal'nuyu chast' stroki iz fajla vvoda, tak chto pol'zo- vatel' mozhet prosto perepechatat' etu stroku s mesta oshibki; funkciya vozvrashchaet TOOBIG kak signal o perepolnenii. GETOP(S, LIM) /* GET NEXT OPRERATOR OR OPERAND */ CHAR S[]; INT LIM; { INT I, C; WHILE((C=GETCH())==' '\!\! C=='\T' \!\! C=='\N') ; IF (C != '.' && (C < '0' \!\! C > '9')) RETURN(C); S[0] = C; FOR(I=1; (C=GETCHAR()) >='0' && C <= '9'; I++) IF (I < LIM) S[I] = C; IF (C == '.') { /* COLLECT FRACTION */ IF (I < LIM) S[I] = C; FOR(I++;(C=GETCHAR()) >='0' && C<='9';I++) IF (I < LIM) S[I] =C; } IF (I < LIM) { /* NUMBER IS OK */ UNGETCH(C); S[I] = '\0'; RETURN (NUMBER); } ELSE { /* IT'S TOO BIG; SKIP REST OF LINE */ WHILE (C != '\N' && C != EOF) C = GETCHAR(); S[LIM-1] = '\0'; RETURN (TOOBIG); } } CHto zhe predstavlyayut iz sebya funkcii 'GETCH' i 'UNGETCH'? CHasto tak byvaet, chto programma, schityvayushchaya vhodnye dannye, ne mozhet opredelit', chto ona prochla uzhe dostatochno, poka ona ne prochtet slishkom mnogo. Odnim iz primerov yavlyaetsya vybor simvolov, sostavlyayushchih chislo: poka ne poyavitsya simvol, ot- lichnyj ot cifry, chislo ne zakoncheno. No pri etom programma schityvaet odin lishnij simvol, simvol, dlya kotorogo ona eshche ne podgotovlena. |ta problema byla by reshena, esli by bylo by vozmozhno "prochest' obratno" nezhelatel'nyj simvol. Togda kazhdyj raz, prochitav lishnij simvol, programma mogla by pomestit' ego ob- ratno v fajl vvoda takim obrazom, chto ostal'naya chast' prog- rammy mogla by vesti sebya tak, slovno etot simvol nikogda ne schityvalsya. k schast'yu, takoe nepoluchenie simvola legko immi- tirovat', napisav paru dejstvuyushchih sovmestno funkcij. Funk- ciya GETCH dostavlyaet sleduyushchij simvol vvoda, podlezhashchij ras- smotreniyu; funkciya UNGETCH pomeshchaet simvol nazad vo vvod, tak chto pri sleduyushchem obrashchenii k GETCH on budet vozvrashchen. To, kak eti funkcii sovmestno rabotayut, ves'ma prosto. Funkciya UNGETCH pomeshchaet vozvrashchaemye nazad simvoly v sov- mestno ispol'zuemyj bufer, yavlyayushchijsya simvol'nym massivom. Funkciya GETCH chitaet iz etogo bufera, esli v nem chto-libo imeetsya; esli zhe bufer pust, ona obrashchaetsya k GETCHAR. Pri etom takzhe nuzhna indeksiruyushchaya peremennaya, kotoraya budet fiksirovat' poziciyu tekushchego simvola v bufere. Tak kak bufer i ego indeks sovmestno ispol'zuyutsya funk- ciyami GETCH i UNGETCH i dolzhny sohranyat' svoi znacheniya v pe- riod mezhdu obrashcheniyami, oni dolzhny byt' vneshnimi dlya obeih funkcij. Takim obrazom, my mozhem napisat' GETCH, UNGETCH i eti peremennye kak: #DEFINE BUFSIZE 100 CHAR BUF[BUFSIZE]; /* BUFFER FOR UNGETCH */ INT BUFP = 0; /* NEXT FREE POSITION IN BUF */ GETCH() /* GET A (POSSIBLY PUSHED BACK) CHARACTER */ { RETURN((BUFP > 0) ? BUF[--BUFP] : GETCHAR()); } UNGETCH(C) /* PUSH CHARACTER BACK ON INPUT */ INT C; { IF (BUFP > BUFSIZE) PRINTF("UNGETCH: TOO MANY CHARACTERS\N"); ELSE BUF [BUFP++] = C; } My ispol'zovali dlya hraneniya vozvrashchaemyh simvolov massiv, a ne otdel'nyj simvol, potomu chto takaya obshchnost' mozhet prigo- dit'sya v dal'nejshem. Uprazhnenie 4-4 ---------------- Napishite funkciyu UNGETS(S) , kotoraya budet vozvrashchat' vo vvod celuyu stroku. Dolzhna li UNGETS imet' delo s BUF i BUFP ili ona mozhet prosto ispol'zovat' UNGETCH ? Uprazhnenie 4-5 ---------------- Predpolozhite, chto mozhet vozvrashchat'sya tol'ko odin simvol. Iz- menite GETCH i UNGETCH sootvetstvuyushchim obrazom. Uprazhnenie 4-6 ---------------- Nashi funkcii GETCH i UNGETCH ne obespechivayut obrabotku vozv- rashchennogo simvola EOF perenosimym obrazom. Reshite, kakim svojstvom dolzhny obladat' eti funkcii, esli vozvrashchaetsya EOF, i realizujte vashi vyvody. 4.6. Staticheskie peremennye Staticheskie peremennye predstavlyayut soboj tretij klass pamyati, v dopolnenii k avtomaticheskim peremennym i EXTERN, s kotorymi my uzhe vstrechalis'. Staticheskie peremennye mogut byt' libo vnutrennimi, libo vneshnimi. Vnutrennie staticheskie peremennye tochno tak zhe, kak i avtomaticheskie, yavlyayutsya lokal'nymi dlya nekotoroj fun- kcii, no, v otlichie ot avtomaticheskih, oni ostayutsya sushchest- vovat', a ne poyavlyayutsya i ischezayut vmeste s obrashcheniem k etoj funkcii. eto oznachaet, chto vnutrennie staticheskie pere- mennye obespechivayut postoyannoe, nedostupnoe izvne hranenie vnutri funkcii. Simvol'nye stroki, poyavlyayushchiesya vnutri funk- cii, kak, naprimer, argumenty PRINTF , yavlyayutsya vnutrennimi staticheskimi. Vneshnie staticheskie peremennye opredeleny v ostal'noj chasti togo ishodnogo fajla, v kotorom oni opisany, no ne v kakom-libo drugom fajle. Takim obrazom, oni dayut sposob skryvat' imena, podobnye BUF i BUFP v kombinacii GETCH-UNGETCH, kotorye v silu ih sovmestnogo ispol'zovaniya dolzhny byt' vneshnimi, no vse zhe ne dostupnymi dlya pol'zova- telej GETCH i UNGETCH , chtoby isklyuchalas' vozmozhnost' konf- likta. Esli eti dve funkcii i dve peremennye ob容denit' v odnom fajle sleduyushchim obrazom STATIC CHAR BUF[BUFSIZE]; /* BUFFER FOR UNGETCH */ STATIC INT BUFP=0; /*NEXT FREE POSITION IN BUF */ GETCH() {...} UNGETCH() {...} to nikakaya drugaya funkciya ne budet v sostoyanii obratit'sya k BUF i BUFP; fakticheski, oni ne budut vstupat' v konflikt s takimi zhe imenami iz drugih fajlov toj zhe samoj programmy. Staticheskaya pamyat', kak vnutrennyaya, tak i vneshnyaya, spe- cificiruetsya slovom STATIC , stoyashchim pered obychnym opisani- em. Peremennaya yavlyaetsya vneshnej, esli ona opisana vne kakoj by to ni bylo funkcii, i vnutrennej, esli ona opisana vnutri nekotoroj funkcii. Normal'no funkcii yavlyayutsya vneshnimi ob容ktami; ih imena izvestny global'no. vozmozhno, odnako, ob座avit' funkciyu kak STATIC ; togda ee imya stanovitsya neizvestnym vne fajla, v kotorom ono opisano. V yazyke "C" "STATIC" otrazhaet ne tol'ko postoyanstvo, no i stepen' togo, chto mozhno nazvat' "privatnost'yu". Vnutrennie staticheskie ob容kty opredeleny tol'ko vnutri odnoj funkcii; vneshnie staticheskie ob容kty /peremennye ili funkcii/ oprede- leny tol'ko vnutri togo ishodnogo fajla, gde oni poyavlyayutsya, i ih imena ne vstupayut v konflikt s takimi zhe imenami pere- mennyh i funkcij iz drugih fajlov. Vneshnie staticheskie peremennye i funkcii predostavlyayut sposob organizovyvat' dannye i rabotayushchie s nimi vnutrennie procedury takim obrazom, chto drugie procedury i dannye ne mogut prijti s nimi v konflikt dazhe po nedorazumeniyu. Napri- mer, funkcii GETCH i UNGETCH obrazuyut "modul'" dlya vvoda i vozvrashcheniya simvolov; BUF i BUFP dolzhny byt' staticheskimi, chtoby oni ne byli dostupny izvne. Tochno tak zhe funkcii PUSH, POP i CLEAR formiruyut modul' obrabotki steka; VAR i SP tozhe dolzhny byt' vneshnimi staticheskimi. 4.7. Registrovye peremennye CHetvertyj i poslednij klass pamyati nazyvaetsya registro- vym. Opisanie REGISTER ukazyvaet kompilyatoru, chto dannaya pe- remennaya budet chasto ispol'zovat'sya. Kogda eto vozmozhno, pe- remennye, opisannye kak REGISTER, raspolagayutsya v mashinnyh registrah, chto mozhet privesti k men'shim po razmeru i bolee bystrym programmam. Opisanie REGISTER vyglyadit kak REGISTER INT X; REGISTER CHAR C; i t.d.; chast' INT mozhet byt' opushchena. Opisanie REGISTER mozh- no ispol'zovat' tol'ko dlya avtomaticheskih peremennyh i for- mal'nyh parametrov funkcij. V etom poslednem sluchae opisaniya vyglyadyat sleduyushchim obrazom: F(C,N) REGISTER INT C,N; { REGISTER INT I; ... } Na praktike voznikayut nekotorye ogranicheniya na registro- vye peremennye, otrazhayushchie real'nye vozmozhnosti imeyushchihsya apparatnyh sredstv. V registry mozhno pomestit' tol'ko nes- kol'ko peremennyh v kazhdoj funkcii, prichem tol'ko opredelen- nyh tipov. V sluchae prevysheniya vozmozhnogo chisla ili ispol'- zovaniya nerazreshennyh tipov slovo REGISTER ignoriruetsya. Krome togo nevozmozhno izvlech' adres registrovoj peremennoj (etot vopros obsuzhdaetsya v glave 5). |ti specificheskie ogra- nicheniya var'iruyutsya ot mashiny k mashine. Tak, naprimer, na PDP-11 effektivnymi yavlyayutsya tol'ko pervye tri opisaniya REGISTER v funkcii, a v kachestve tipov dopuskayutsya INT, CHAR ili ukazatel'. 4.8. Blochnaya struktura YAzyk "C" ne yavlyaetsya yazykom s blochnoj strukturoj v smys- le PL/1 ili algola; v nem nel'zya opisyvat' odni funkcii vnutri drugih. Peremennye zhe, s drugoj storony, mogut opredelyat'sya po metodu blochnogo strukturirovaniya. Opisaniya peremennyh (vklyu- chaya inicializaciyu) mogut sledovat' za levoj figurnoj skob- koj,otkryvayushchej lyuboj operator, a ne tol'ko za toj, s koto- roj nachinaetsya telo funkcii. Peremennye, opisannye takim ob- razom, vytesnyayut lyubye peremennye iz vneshnih blokov, imeyushchie takie zhe imena, i ostayutsya opredelennymi do sootvetstvuyushchej pravoj figurnoj skobki. Naprimer v IF (N > 0) { INT I; /* DECLARE A NEW I */ FOR (I = 0; I < N; I++) ... } Oblast'yu dejstviya peremennoj I yavlyaetsya "istinnaya" vetv' IF; eto I nikak ne svyazano ni s kakimi drugimi I v program- me. Blochnaya struktura vliyaet i na oblast' dejstviya vneshnih peremennyh. Esli dany opisaniya INT X; F() { DOUBLE X; ... } To poyavlenie X vnutri funkcii F otnositsya k vnutrennej pere- mennoj tipa DOUBLE, a vne F - k vneshnej celoj peremennoj. eto zhe spravedlivo v otnoshenii imen formal'nyh parametrov: INT X; F(X) DOUBLE X; { ... } Vnutri funkcii F imya X otnositsya k formal'nomu parametru, a ne k vneshnej peremennoj. 4.9. Inicializaciya My do sih por uzhe mnogo raz upominali inicializaciyu, no vsegda mimohodom , sredi drugih voprosov. Teper', posle togo kak my obsudili razlichnye klassy pamyati, my v etom razdele prosummiruem nekotorye pravila, otnosyashchiesya k inicializacii. Esli yavnaya inicializaciya otsutstvuet, to vneshnim i sta- ticheskim peremennym prisvaivaetsya znachenie nul'; avtomati- cheskie i registrovye peremennye imeyut v etom sluchae neopre- delennye znacheniya (musor). Prostye peremennye (ne massivy ili struktury) mozhno ini- cializirovat' pri ih opisanii, dobavlyaya vsled za imenem znak ravenstva i konstantnoe vyrazhenie: INT X = 1; CHAR SQUOTE = '\''; LONG DAY = 60 * 24; /* MINUTES IN A DAY */ Dlya vneshnih i staticheskih peremennyh inicializaciya vypolnya- etsya tol'ko odin raz, na etape kompilyacii. Avtomaticheskie i registrovye peremennye inicializiruyutsya kazhdyj raz pri vhode v funkciyu ili blok. V sluchae avtomaticheskih i registrovyh peremennyh inicializa- tor ne obyazan byt' konstantoj: na samom dele on mozhet byt' lyubym znachimym vyrazheniem, kotoroe mozhet vklyuchat' opredelen- nye ranee velichiny i dazhe obrashcheniya k funkciyam. Naprimer, inicializaciya v programme binarnogo poiska iz glavy 3 mogla by byt' zapisana v vide BINARY(X, V, N) INT X, V[], N; { INT LOW = 0; INT HIGH = N - 1; INT MID; ... } vmesto BINARY(X, V, N) INT X, V[], N; { INT LOW, HIGH, MID; LOW = 0; HIGH = N - 1; ... } Po svoemu rezul'tatu, inicializacii avtomaticheskih peremen- nyh yavlyayutsya sokrashchennoj zapis'yu operatorov prisvaivaniya. Kakuyu formu predpochest' - v osnovnom delo vkusa. my obychno ispol'zuem yavnye prisvaivaniya, potomu chto inicializaciya v opisaniyah menee zametna. Avtomaticheskie massivy ne mogut byt' inicializirovany. Vnesh- nie i staticheskie massivy mozhno inicializirovat', pomeshchaya vsled za opisaniem zaklyuchennyj v figurnye skobki spisok na- chal'nyh znachenij, razdelennyh zapyatymi. Naprimer programma podscheta simvolov iz glavy 1, kotoraya nachinalas' s MAIN() /* COUNT DIGITS, WHITE SPACE, OTHERS */ ( INT C, I, NWHITE, NOTHER; INT NDIGIT[10]; NWHITE = NOTHER = 0; FOR (I = 0; I < 10; I++) NDIGIT[I] = 0; ... ) Ozhet byt' perepisana v vide INT NWHITE = 0; INT NOTHER = 0; INT NDIGIT[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; MAIN() /* COUNT DIGITS, WHITE SPACE, OTHERS */ ( INT C, I; ... ) |ti inicializacii fakticheski ne nuzhny, tak kak vse prisvai- vaemye znacheniya ravny nulyu, no horoshij stil' - sdelat' ih yavnymi. Esli kolichestvo nachal'nyh znachenij men'she, chem uka- zannyj razmer massiva, to ostal'nye elementy zapolnyayutsya nu- lyami. Perechislenie slishkom bol'shogo chisla nachal'nyh znachenij yavlyaetsya oshibkoj. K sozhaleniyu, ne predusmotrena vozmozhnost' ukazaniya, chto nekotoroe nachal'noe znachenie povtoryaetsya, i nel'zya inicializirovat' element v seredine massiva bez pere- chisleniya vseh predydushchih. Dlya simvol'nyh massivov sushchestvuet special'nyj sposob inicializacii; vmesto figurnyh skobok i zapyatyh mozhno is- pol'zovat' stroku: CHAR PATTERN[] = "THE"; |to sokrashchenie bolee dlinnoj, no ekvivalentnoj zapisi: CHAR PATTERN[] = { 'T', 'H', 'E', '\0' }; Esli razmer massiva lyubogo tipa opushchen, to kompilyator opre- delyaet ego dlinu, podschityvaya chislo nachal'nyh znachenij. V etom konkretnom sluchae razmer raven chetyrem (tri simvola plyus konechnoe \0). 4.10. Rekursiya V yazyke "C" funkcii mogut ispol'zovat'sya rekursivno; eto oznachaet, chto funkciya mozhet pryamo ili kosvenno obrashchat'sya k sebe samoj. Tradicionnym primerom yavlyaetsya pechat' chisla v vide stroki simvolov. kak my uzhe ranee otmechali, cifry gene- riruyutsya ne v tom poryadke: cifry mladshih razryadov poyavlyayutsya ran'she cifr iz starshih razryadov, no pechatat'sya oni dolzhny v obratnom poryadke. |tu problemu mozhno reshit' dvumya sposobami. Pervyj spo- sob, kotorym my vospol'zovalis' v glave 3 v funkcii ITOA, zaklyuchaetsya v zapominanii cifr v nekotorom massive po mere ih postupleniya i posleduyushchem ih pechatanii v obratnom poryad- ke. Pervyj variant funkcii PRINTD sleduet etoj sheme. PRINTD(N) /* PRINT N IN DECIMAL */ INT N; { CHAR S[10]; INT I; IF (N < 0) { PUTCHAR('-'); N = -N; } I = 0; DO { S[I++] = N % 10 + '0'; /* GET NEXT CHAR */ } WHILE ((N /= 10) > 0); /* DISCARD IT */ WHILE (--I >= 0) PUTCHAR(S[I]); } Al'ternativoj etomu sposobu yavlyaetsya rekursivnoe reshe- nie, kogda pri kazhdom vyzove funkciya PRINTD snachala snova obrashchaetsya k sebe, chtoby skopirovat' lidiruyushchie cifry, a za- tem pechataet poslednyuyu cifru. PRINTD(N) /* PRINT N IN DECIMAL (RECURSIVE)*/ INT N; ( INT I; IF (N < 0) { PUTCHAR('-'); N = -N; } IF ((I = N/10) != 0) PRINTD(I); PUTCHAR(N % 10 + '0'); ) Kogda funkciya vyzyvaet sebya rekursivno, pri kazhdom obra- shchenii obrazuetsya novyj nabor vseh avtomaticheskih peremennyh, sovershenno ne zavisyashchij ot predydushchego nabora. Takim obra- zom, v PRINTD(123) pervaya funkciya PRINTD imeet N = 123. Ona peredaet 12 vtoroj PRINTD, a kogda ta vozvrashchaet upravlenie ej, pechataet 3. Tochno tak zhe vtoraya PRINTD peredaet 1 tret'ej (kotoraya etu edinicu pechataet), a zatem pechataet 2. Rekursiya obychno ne daet nikakoj ekonomiii pamyati, pos- kol'ku prihoditsya gde-to sozdavat' stek dlya obrabatyvaemyh znachenij. Ne privodit ona i k sozdaniyu bolee bystryh prog- ramm. No rekursivnye programmy bolee kompaktny, i oni zachas- tuyu stanovyatsya bolee legkimi dlya ponimaniya i napisaniya. Re- kursiya osobenno udobna pri rabote s rekursivno opredelyaemymi strukturami dannyh, naprimer, s derev'yami; horoshij primer budet priveden v glave 6. Uprazhnenie 4-7 -------------- Prisposob'te idei, ispol'zovannye v PRINTD dlya rekursiv- nogo napisaniya ITOA; t.e. Preobrazujte celoe v stroku s po- moshch'yu rekursivnoj procedury. Uprazhnenie 4-8 -------------- Napishite rekursivnyj variant funkcii REVERSE(S), kotoraya raspolagaet v obratnom poryadke stroku S. 4.11. Preprocessor yazyka "C" V yazyke "s" predusmotreny opredelennye rasshireniya yazyka s pomoshch'yu prostogo makropredprocessora. odnim iz samyh rasp- rostranennyh takih rasshirenij, kotoroe my uzhe ispol'zovali, yavlyaetsya konstrukciya #DEFINE; drugim rasshireniem yavlyaetsya vozmozhnost' vklyuchat' vo vremya kompilyacii soderzhimoe drugih fajlov. 4.11.1. Vklyuchenie fajlov Dlya oblegcheniya raboty s naborami konstrukcij #DEFINE i opisanij (sredi prochih sredstv) v yazyke "s" predusmotrena vozmozhnost' vklyucheniya fajlov. Lyubaya stroka vida #INCLUDE "FILENAME" zamenyaetsya soderzhimym fajla s imenem FILENAME. (Kavychki obya- zatel'ny). CHasto odna ili dve stroki takogo vida poyavlyayutsya v nachale kazhdogo ishodnogo fajla, dlya togo chtoby vklyuchit' obshchie konstrukcii #DEFINE i opisaniya EXTERN dlya global'nyh peremennyh. Dopuskaetsya vlozhennost' konstrukcij #INCLUDE. Konstrukciya #INCLUDE yavlyaetsya predpochtitel'nym sposobom svyazi opisanij v bol'shih programmah. |tot sposob garantiru- et, chto vse ishodnye fajly budut snabzheny odinakovymi opre- deleniyami i opisaniyami peremennyh, i, sledovatel'no, isklyu- chaet osobenno nepriyatnyj sort oshibok. Estestvenno, kogda ka- koj-TO vklyuchaemyj fajl izmenyaetsya, vse zavisyashchie ot nego fajly dolzhny byt' perekompilirovany. 4.11.2. Makropodstanovka Opredelenie vida #DEFINE TES 1 privodit k makropodstanovke samogo prostogo vida - zamene imeni na stroku simvolov. Imena v #DEFINE imeyut tu zhe samuyu formu, chto i identifikatory v "s"; zamenyayushchij tekst sover- shenno proizvolen. Normal'no zamenyayushchim tekstom yavlyaetsya os- tal'naya chast' stroki; dlinnoe opredelenie mozhno prodolzhit', pomestiv \ v konec prodolzhaemoj stroki. "Oblast' dejstviya" imeni, opredelennogo v #DEFINE, prostiraetsya ot tochki opre- deleniya do konca ishodnogo fajla. imena mogut byt' pereopre- deleny, i opredeleniya mogut ispol'zovat' opredeleniya, sde- lannye ranee. Vnutri zaklyuchennyh v kavychki strok podstanovki ne proizvodyatsya, tak chto esli, naprimer, YES - opredelennoe imya, to v PRINTF("YES") ne budet sdelano nikakoj podstanov- ki. Tak kak realizaciya #DEFINE yavlyaetsya chast'yu raboty maKropredprocessora, a ne sobstvenno kompilyatora, imeetsya ochen' malo grammaticheskih ogranichenij na to, chto mozhet byt' opredeleno. Tak, naprimer, lyubiteli algola mogut ob座avit' #DEFINE THEN #DEFINE BEGIN { #DEFINE END ;} i zatem napisat' IF (I > 0) THEN BEGIN A = 1; B = 2 END Imeetsya takzhe vozmozhnost' opredeleniya makrosa s argumen- tami, tak chto zamenyayushchij tekst budet zaviset' ot vida obra- shcheniya k makrosu. Opredelim, naprimer, makros s imenem MAX sleduyushchim obrazom: #DEFINE MAX(A, B) ((A) > (B) ? (A) : (B)) kogda stroka X = MAX(P+Q, R+S); budet zamenena strokoj X = ((P+Q) > (R+S) ? (P+Q) : (R+S)); Takaya vozmozhnost' obespechivaet "funkciyu maksimuma", kotoraya rasshiryaetsya v posledovatel'nyj kod, a ne v obrashchenie k funk- cii. Pri pravil'nom obrashchenii s argumentami takoj makros bu- det rabotat' s lyubymi tipami dannyh; zdes' net neobhodimosti v razlichnyh vidah MAX dlya dannyh raznyh tipov, kak eto bylo by s funkciyami. Konechno, esli vy tshchatel'no rassmotrite privedennoe vyshe rasshirenie MAX, vy zametite opredelennye nedostatki. Vyrazhe- niya vychislyayutsya dvazhdy; eto ploho, esli oni vlekut za soboj pobochnye effekty, vyzvannye, naprimer, obrashcheniyami k funkci- yam ili ispol'zovaniem operacij uvelicheniya. Nuzhno pozabotit'- sya o pravil'nom ispol'zovanii kruglyh skobok, chtoby garanti- rovat' sohranenie trebuemogo poryadka vychislenij. (Rassmotri- te makros #DEFINE SQUARE(X) X * X pri obrashchenii k nej, kak SQUARE(Z+1)). Zdes' voznikayut dazhe nekotorye chisto leksicheskie problemy: mezhdu imenem makro i levoj krugloj skobkoj, otkryvayushchej spisok ee argumentov, ne dolzhno byt' nikakih probelov. Tem ne menee apparat makrosov yavlyaetsya ves'ma cennym. Odin prakticheskij primer daet opisyvaemaya v glave 7 standar- tnaya biblioteka vvoda-vyvoda, v kotoroj GETCHAR i PUTCHAR opredeleny kak makrosy (ochevidno PUTCHAR dolzhna imet' argu- ment), chto pozvolyaet izbezhat' zatrat na obrashchenie k funkcii pri obrabotke kazhdogo simvola. Drugie vozmozhnosti makroprocessora opisany v prilozhenii A. Uprazhnenie 4-9 --------------- Opredelite makros SWAP(X, Y), kotoryj obmenivaet znache- niyami dva svoih argumenta tipa INT. (V etom sluchae pomozhet blochnaya struktura).  * 5. Ukazateli i massivy *  Ukazatel' - eto peremennaya, soderzhashchaya adres drugoj pe- remennoj. ukazateli ochen' shiroko ispol'zuyutsya v yazyke "C". |to proishodit otchasti potomu, chto inogda oni dayut edinst- vennuyu vozmozhnost' vyrazit' nuzhnoe dejstvie, a otchasti poto- mu, chto oni obychno vedut k bolee kompaktnym i effektivnym programmam, chem te, kotorye mogut byt' polucheny drugimi spo- sobami. Ukazateli obychno smeshivayut v odnu kuchu s operatorami GOTO, harakterizuya ih kak chudesnyj sposob napisaniya prog- ramm, kotorye nevozmozhno ponyat'. |to bezuslovno sprAvedlivo, esli ukazateli ispol'zuyutsya bezzabotno; ochen' prosto vvesti ukazateli, kotorye ukazyvayut na chto-to sovershenno neozhidan- noe. Odnako, pri opredelennoj discipline, ispol'zovanie uka- zatelej pomogaet dostich' yasnosti i prostoty. Imenno etot as- pekt my popytaemsya zdes' proillyustrirovat'. 5.1. Ukazateli i adresa Tak kak ukazatel' soderzhit adres ob容kta, eto daet voz- mozhnost' "kosvennogo" dostupa k etomu ob容ktu cherez ukaza- tel'. Predpolozhim, chto h - peremennaya, naprimer, tipa INT, a rh - ukazatel', sozdannyj nekim eshche ne ukazannym sposobom. Unarnaya operaciya & vydaet adres ob容kta, tak chto operator rh = &h; prisvaivaet adres h peremennoj rh; govoryat, chto rh "uka- zyvaet" na h. Operaciya & primenima tol'ko k peremennym i elementam massiva, konstrukcii vida &(h-1) i &3 yavlyayutsya ne- zakonnymi. Nel'zya takzhe poluchit' adres registrovoj peremen- noj. Unarnaya operaciya * rassmatrivaet svoj operand kak adres konechnoj celi i obrashchaetsya po etomu adresu, chtoby izvlech' soderzhimoe. Sledovatel'no, esli Y tozhe imeet tip INT, to Y = *rh; prisvaivaet Y soderzhimoe togo, na chto ukazyvaet rh. Tak pos- ledovatel'nost' rh = &h; Y = *rh; prisvaivaet Y to zhe samoe znachenie, chto i operator Y = X; Peremennye, uchastvuyushchie vo vsem etom neobhodimo opisat': INT X, Y; INT *PX; s opisaniem dlya X i Y my uzhe neodonokratno vstrechalis'. Opisanie ukazatelya INT *PX; yavlyaetsya novym i dolzhno rassmatrivat'sya kak mnemonicheskoe; ono govorit, chto kombinaciya *PX imeet tip INT. |to oznachaet, chto esli PX poyavlyaetsya v kontekste *PX, to eto ekvivalentno peremennoj tipa INT. Fakticheski sintaksis opisaniya peremen- noj imitiruet sintaksis vyrazhenij, v kotoryh eta peremennaya mozhet poyavlyat'sya. |to zamechanie polezno vo vseh sluchayah, svyazannyh so slozhnymi opisaniyami. Naprimer, DOUBLE ATOF(), *DP; govorit, chto ATOF() i *DP imeyut v vyrazheniyah znacheniya tipa DOUBLE. Vy dolzhny takzhe zametit', chto iz etogo opisaniya sledu- et, chto ukazatel' mozhet ukazyvat' tol'ko na opredelennyj vid ob容ktov. Ukazateli mogut vhodit' v vyrazheniya. Naprimer, esli PX ukazyvaet na celoe X, to *PX mozhet poyavlyat'sya v lyubom kon- tekste, gde mozhet vstretit'sya X. Tak operator Y = *PX + 1 prisvaivaet Y znachenie, na 1 bol'shee znacheniya X; PRINTF("%D\N", *PX) pechataet tekushchee znachenie X; D = SQRT((DOUBLE) *PX) poluchaet v D kvadratnyj koren' iz X, prichem do peredachi fun- kcii SQRT znachenie X preobrazuetsya k tipu DOUBLE. (Smotri glavu 2). V vyrazheniyah vida Y = *PX + 1 unarnye operacii * i & svyazany so svoim operandom bolee krepko, chem arifmeticheskie operacii, tak chto takoe vyrazhenie beret to znachenie, na kotoroe ukazyvaet PX, pribavlyaet 1 i prisvaivaet rezul'tat peremennoj Y. My vskore vernemsya k to- mu, chto mozhet oznachat' vyrazhenie Y = *(PX + 1) Ssylki na ukazateli mogut poyavlyat'sya i v levoj chasti prisvaivanij. Esli PX ukazyvaet na X, to *PX = 0 polagaet X ravnym nulyu, a *PX += 1 uvelichivaet ego na edinicu, kak i vyrazhenie (*PX)++ Kruglye skobki v poslednem primere neobhodimy; esli ih opus- tit', to poskol'ku unarnye operacii, podobnye * i ++, vypol- nyayutsya sprava nalevo, eto vyrazhenie uvelichit PX, a ne tu pe- remennuyu, na kotoruyu on ukazyvaet. I nakonec, tak kak ukazateli yavlyayutsya peremennymi, to s nimi mozhno obrashchat'sya, kak i s ostal'nymi peremennymi. Esli PY - drugoj ukazatel' na peremennuyu tipa INT, to PY = PX kopiruet soderzhimoe PX v PY, v rezul'tate chego PY ukazyvaet na to zhe, chto i PX. 5.2. Ukazateli i argumenty funkcij Tak kak v "s" peredacha argumentov funkciyam osushchestvlyaet- sya "po znacheniyu", vyzvannaya procedura ne imeet neposredst- vennoj vozmozhnosti izmenit' peremennuyu iz vyzyvayushchej prog- rammy. CHto zhe delat', esli vam dejstvitel'no nado izmenit' argument? naprimer, programma sortirovki zahotela by pome- nyat' dva narushayushchih poryadok elementa s pomoshch'yu funkcii s imenem SWAP. Dlya etogo nedostatochno napisat' SWAP(A, B); opredeliv funkciyu SWAP pri etom sleduyushchim obrazom: SWAP(X, Y) /* WRONG */ INT X, Y; { INT TEMP; TEMP = X; X = Y; Y = TEMP; } iz-za vyzova po znacheniyu SWAP ne mozhet vozdejstvovat' na agumenty A i B v vyzyvayushchej funkcii. K schast'yu, vse zhe imeetsya vozmozhnost' poluchit' zhelaemyj effekt. Vyzyvayushchaya programma peredaet ukazateli podlezhashchih izmeneniyu znachenij: SWAP(&A, &B); tak kak operaciya & vydaet adres peremennoj, to &A yavlyaetsya ukazatelem na A. V samoj SWAP argumenty opisyvayutsya kak uka- zateli i dostup k fakticheskim operandam osushchestvlyaetsya cherez nih. SWAP(PX, PY) /* INTERCHANGE *PX AND *PY */ INT *PX, *PY; { INT TEMP; TEMP = *PX; *PX = *PY; *PY = TEMP; } Ukazateli v kachestve argumentov obychno ispol'zuyutsya v funkciyah, kotorye dolzhny vozvrashchat' bolee odnogo znacheniya. (Mozhno skazat', chto SWAP vOzvrashchaet dva znacheniya, novye zna- cheniya ee argumentov). V kachestve primera rassmotrim funkciyu GETINT, kotoraya osushchestvlyaet preobrazovanie postupayushchih v svobolnom formate dannyh, razdelyaya potok simvolov na celye znacheniya, po odnomu celomu za odno obrashchenie. Funkciya GETINT dolzhna vozvrashchat' libo najdennoe znachenie, libo priznak kon- ca fajla, esli vhodnye dannye polnost'yu ischerpany. |ti zna- cheniya dolzhny vozvrashchat'sya kak otdel'nye ob容kty, kakoe by znachenie ni ispol'zovalos' dlya EOF, dazhe esli eto znachenie vvodimogo celogo. Odno iz reshenij, osnovyvayushcheesya na opisyvaemoj v glave 7 funkcii vvoda SCANF, sostoit v tom, chtoby pri vyhode na ko- nec fajla GETINT vozvrashchala EOF v kachestve znacheniya funkcii; lyuboe drugoe vozvrashchennoe znachenie govorit o nahozhdenii nor- mal'nogo celogo. CHislennoe zhe znachenie najdennogo celogo vozvrashchaetsya cherez argument, kotoryj dolzhen byt' ukazatelem celogo. |ta organizaciya razdelyaet status konca fajla i chis- lennye znacheniya. Sleduyushchij cikl zapolnyaet massiv celymi s pomoshch'yu obrashche- nij k funkcii GETINT: INT N, V, ARRAY[SIZE]; FOR (N = 0; N < SIZE && GETINT(&V) != EOF; N++) ARRAY[N] = V; V rezul'tate kazhdogo obrashcheniya V stanovitsya ravnym sleduyushche- mu celomu znacheniyu, najdennomu vo vhodnyh dannyh. Obratite vnimanie, chto v kachestve argumenta GETINT neobhodimo ukazat' &V a ne V. Ispol'zovanie prosto V skoree vsego privedet k oshibke adresacii, poskol'ku GETINT polagaet, chto ona rabota- et imenno s ukazatelem. Sama GETINT yavlyaetsya ochevidnoj modifikaciej napisannoj nami ranee funkcii ATOI: GETINT(PN) /* GET NEXT INTEGER FROM INPUT */ INT *PN; { INT C,SIGN; WHILE ((C = GETCH()) == ' ' \!\! C == '\N' \!\! C == '\T'); /* SKIP WHITE SPACE */ SIGN = 1; IF (C == '+' \!\! C == '-') { /* RECORD SIGN */ SIGN = (C == '+') ? 1 : -1; C = GETCH(); } FOR (*PN = 0; C >= '0' && C <= '9'; C = GETCH()) *PN = 10 * *PN + C - '0'; *PN *= SIGN; IF (C != EOF) UNGETCH(C); RETURN(C); } Vyrazhenie *PN ispol'zuetsya vsyudu v GETINT kak obychnaya pere- mennaya tipa INT. My takzhe ispol'zovali funkcii GETCH i UNGETCH (opisannye v glave 4) , tak chto odin lishnij simvol, kototryj prihoditsya schityvat', mozhet byt' pomeshchen obratno vo vvod. Uprazhnenie 5-1 --------------- Napishite funkciyu GETFLOAT, analog GETINT dlya chisel s plavayushchej tochkoj. Kakoj tip dolzhna vozvrashchat' GETFLOAT v ka- chestve znacheniya funkcii? 5.3. Ukazateli i massivy V yazyke "C" sushchestvuet sil'naya vzaimosvyaz' mezhdu ukaza- telyami i massivami , nastol'ko sil'naya, chto ukazateli i mas- sivy dejstvitel'no sleduet rassmatrivat' odnovremenno. Lyubuyu operaciyu, kotoruyu mozhno vypolnit' s pomoshch'yu indeksov massi- va, mozhno sdelat' i s pomoshch'yu ukazatelej. variant s ukazate- lyami obychno okazyvaetsya bolee bystrym, no i neskol'ko bolee trudnym dlya neposredstvennogo ponimaniya, po krajnej mere dlya nachinayushchego. opisanie INT A[10] opredelyaet massiv razmera 10, t.e. Nabor iz 10 posledova- tel'nyh ob容ktov, nazyvaemyh A[0], A[1], ..., A[9]. Zapis' A[I] sootvetstvuet elementu massiva cherez I pozicij ot nacha- la. Esli PA - ukazatel' celogo, opisannyj kak INT *PA to prisvaivanie PA = &A[0] privodit k tomu, chto PA ukazyvaet na nulevoj element massiva A; eto oznachaet, chto PA soderzhit adres elementa A[0]. Teper' prisvaivanie X = *PA budet kopirovat' soderzhimoe A[0] v X. Esli PA ukazyvaet na nekotoryj opredelennyj element mas- siva A, to po opredeleniyu PA+1 ukazyvaet na sleduyushchij ele- ment, i voobshche PA-I ukazyvaet na element, stoyashchij na I pozi- cij do elementa, ukazyvaemogo PA, a PA+I na element, stoyashchij na I pozicij posle. Takim obrazo