ak uzhe otmechalos' ranee, vyrazheniya, v kotorye vhodit odna iz associativnyh i kommutativnyh operacij (*, +, &, ^, e), mogut peregruppirovyvat'sya, dazhe esli oni zaklyucheny v kruglye skobki. V bol'shinstve sluchaev eto ne privodit k ka- kim by to ni bylo rashozhdeniyam; v situaciyah, gde takie ras- hozhdeniya vse zhe vozmozhny, dlya obespecheniya nuzhnogo poryadka vychislenij mozhno ispol'zovat' yavnye promezhutochnye peremen- nye. V yazyke "C", kak i v bol'shinstve yazykov, ne fiksiruetsya poryadok vychisleniya operandov v operatore. Naprimer v opera- tore vida X = F() + G(); snachala mozhet byt' vychisleno F, a potom G, i naoborot; poe- tomu, esli libo F, libo G izmenyayut vneshnyuyu peremennuyu, ot kotoroj zavisit drugoj operand, to znachenie X mozhet zaviset' ot poryadka vychislenij. Dlya obespecheniya nuzhnoj posledovatel'- nosti promezhutochnye rezul'taty mozhno opyat' zapominat' vo vremennyh peremennyh. Podobnym zhe obrazom ne fiksiruetsya poryadok vychisleniya argumentov funkcii, tak chto operator PRINTF("%D %D\N",++N,POWER(2,N)); mozhet davat' (i dejstvitel'no daet) na raznyh mashinah raznye rezul'taty v zavisimosti ot togo, uvelichivaetsya li N do ili posle obrashcheniya k funkcii POWER. Pravil'nym resheniem, konech- no, yavlyaetsya zapis' ++N; PRINTF("%D %D\N",N,POWER(2,N)); Obrashcheniya k funkciyam, vlozhennye operacii prisvaivaniya, operacii uvelicheniya i umen'sheniya privodyat k tak nazyvaemym "pobochnym effektam" - nekotorye peremennye izmenyayutsya kak pobochnyj rezul'tat vychisleniya vyrazhenij. V lyubom vyrazhenii, v kotorom voznikayut pobochnye effekty, mogut sushchestvovat' ochen' tonkie zavisimosti ot poryadka, v kotorom opredelyayutsya vhodyashchie v nego peremennye. primerom tipichnoj neudachnoj si- tuacii yavlyaetsya operator A[I] = I++; Voznikaet vopros, staroe ili novoe znachenie I sluzhit v ka- chestve indeksa. Kompilyator mozhet postupat' raznymi sposobami i v zavisimosti ot svoej interpretacii vydavat' raznye re- zul'taty. Tot sluchaj, kogda proishodyat pobochnye effekty (prisvaivanie fakticheskim peremennym), - ostavlyaetsya na us- motrenie kompilyatora, tak kak nailuchshij poryadok sil'no zavi- sit ot arhitektury mashiny. Iz etih rassuzhdenij vytekaet takaya moral': napisanie programm, zavisyashchih ot poryadka vychislenij, yavlyaetsya plohim metodom programmirovaniya na lyubom yazyke. Konechno, neobhodimo znat', chego sleduet izbegat', no esli vy ne v kurse, kak ne- kotorye veshchi realizovany na raznyh mashinah, eto nevedenie mozhet predohranit' vas ot nepriyatnostej. (Otladochnaya prog- ramma LINT ukazhet bol'shinstvo mest, zavisyashchih ot poryadka vy- chislenij.  * 3. Potok upravleniya *  Upravlyayushchie operatory yazyka opredelyayut poryadok vychisle- nij. V privedennyh ranee primerah my uzhe vstrechalis' s nai- bolee upotrebitel'nymi upravlyayushchimi konstrukciyami yazyka "C"; zdes' my opishem ostal'nye operatory upravleniya i utochnim dejstviya operatorov, obsuzhdavshihsya ranee. 3.1. Operatory i bloki Takie vyrazheniya, kak X=0, ili I++, ili PRINTF(...), stanovyatsya operatorami, esli za nimi sleduet tochka s zapya- toj, kak, naprimer, X = 0; I++; PRINTF(...); V yazyke "C" tochka s zapyatoj yavlyaetsya priznakom konca opera- tora, a ne razdelitelem operatorov, kak v yazykah tipa algo- la. Figurnye skobki /( i /) ispol'zuyutsya dlya ob容dineniya opisanij i operatorov v sostavnoj operator ili blok, tak chto oni okazyvayutsya sintaksicheski ekvivalentny odnomu operatoru. Odin yavnyj primer takogo tipa dayut figurnye skobki, v koto- rye zaklyuchayutsya operatory, sostavlyayushchie funkciyu, drugoj - figurnye skobki vokrug gruppy operatorov v konstrukciyah IF, ELSE, WHILE i FOR.(na samom dele peremennye mogut byt' opi- sany vnutri lyubogo bloka; my pogovorim ob etom v glave 4). Tochka s zapyatoj nikogda ne stavitsya posle pervoj figurnoj skobki, kotoraya zavershaet blok. 3.2. IF - ELSE Operator IF - ELSE ispol'zuetsya pri neobhodimosti sde- lat' vybor. Formal'no sintaksis imeet vid IF (vyrazhenie) operator-1 ELSE operator-2, Gde chast' ELSE yavlyaetsya neobyazatel'noj. Snachala vychislya- etsya vyrazhenie; esli ono "istinno" /t.e. znachenie vyrazheniya otlichno ot nulya/, to vypolnyaetsya operator-1. Esli ono lozhno /znachenie vyrazheniya ravno nulyu/, i esli est' chast' s ELSE, to vmesto operatora-1 vypolnyaetsya operator-2. Tak kak IF prosto proveryaet chislennoe znachenie vyrazhe- niya, to vozmozhno nekotoroe sokrashchenie zapisi. Samoj ochevid- noj vozmozhnost'yu yavlyaetsya zapis' IF (vyrazhenie) vmesto IF (vyrazhenie !=0) inogda takaya zapis' yavlyaetsya yasnoj i estestvennoj, no vreme- nami ona stanovitsya zagadochnoj. To, chto chast' ELSE v konstrukcii IF - ELSE yavlyaetsya neo- byazatel'noj, privodit k dvusmyslennosti v sluchae, kogda ELSE opuskaetsya vo vlozhennoj posledovatel'nosti operatorov IF. |ta neodnoznachnost' razreshaetsya obychnym obrazom - ELSE svya- zyvaetsya s blizhajshim predydushchim IF, ne soderzhashchim ELSE. Naprimer, v IF ( N > 0 ) IF( A > B ) Z = A; ELSE Z = B; konstrukciya ELSE otnositsya k vnutrennemu IF, kak my i poka- zali, sdvinuv ELSE pod sootvetstvuyushchij IF. Esli eto ne to, chto vy hotite, to dlya polucheniya nuzhnogo sootvetstviya neobho- dimo ispol'zovat' figurnye skobki: IF (N > 0) { IF (A > B) Z = A; } ELSE Z = B; Takaya dvusmyslennost' osobenno pagubna v situaciyah tipa IF (N > 0) FOR (I = 0; I < N; I++) IF (S[I] > 0) { PRINTF("..."); RETURN(I); } ELSE /* WRONG */ PRINTF("ERROR - N IS ZERO\N"); Zapis' ELSE pod IF yasno pokazyvaet, chego vy hotite, no kom- pilyator ne poluchit sootvetstvuyushchego ukazaniya i svyazhet ELSE s vnutrennim IF. Oshibki takogo roda ochen' trudno obnaruzhivayut- sya. Mezhdu prochim, obratite vnimanie, chto v IF (A > B) Z = A; ELSE Z = B; posle Z=A stoit tochka s zapyatoj. Delo v tom, chto soglasno grammaticheskim pravilam za IF dolzhen sledovat' operator, a vyrazhenie tipa Z=A, yavlyayushcheesya operatorom, vsegda zakanchiva- etsya tochkoj s zapyatoj. 3.3. ELSE - IF Konstrukciya IF (vyrazhenie) operator ELSE IF (vyrazhenie) operator ELSE IF (vyrazhenie) operator ELSE operator vstrechaetsya nastol'ko chasto, chto zasluzhivaet otdel'nogo kratkogo rassmotreniya. Takaya posledovatel'nost' operatorov IF yavlyaetsya naibolee rasprostranennym sposobom programmiro- vaniya vybora iz neskol'kih vozmozhnyh variantov. vyrazheniya prosmatrivayutsya posledovatel'no; esli kakoe-to vyrazhenie okazyvaetsya istinnym,to vypolnyaetsya otnosyashchijsya k nemu ope- rator, i etim vsya cepochka zakanchivaetsya. Kazhdyj operator mo- zhet byt' libo otdel'nym operatorom, libo gruppoj operatorov v figurnyh skobkah. Poslednyaya chast' s ELSE imeet delo so sluchaem, kogda ni odno iz proveryaemyh uslovij ne vypolnyaetsya. Inogda pri etom ne nado predprinimat' nikakih yavnyh dejstvij; v etom sluchae hvost ELSE operator mozhet byt' opushchen, ili ego mozhno ispol'zovat' dlya kontrolya, chtoby zasech' "nevozmozhnoe" uslovie. Dlya illyustracii vybora iz treh vozmozhnyh variantov pri- vedem programmu funkcii, kotoraya metodom polovinnogo deleniya opredelyaet, nahoditsya li dannoe znachenie h v otsortirovannom massive V. |lementy massiva V dolzhny byt' raspolozheny v po- ryadke vozrastaniya. Funkciya vozvrashchaet nomer pozicii (chislo mezhdu 0 i N-1), v kotoroj znachenie h nahoditsya v V, i -1, esli h ne soderzhitsya v V. BINARY(X, V, N) /* FIND X IN V[0]...V[N-1] */ INT X, V[], N; { INT LOW, HIGH, MID; LOW = 0; HIGH = N - 1; WHILE (LOW <= HIGH) { MID = (LOW + HIGH) / 2; IF (X < V[MID]) HIGH = MID - 1; ELSE IF (X > V[MID]) LOW = MID + 1; ELSE /* FOUND MATCH */ RETURN(MID); } RETURN(-1); } Osnovnoj chast'yu kazhdogo shaga algoritma yavlyaetsya prover- ka, budet li h men'she, bol'she ili raven srednemu elementu V[MID]; ispol'zovanie konstrukcii ELSE - IF zdes' vpolne es- testvenno. 3.4. Pereklyuchatel' Operator SWITCH daet special'nyj sposob vybora odnogo iz mnogih variantov, kotoryj zaklyuchaetsya v proverke sovpadeniya znacheniya dannogo vyrazheniya s odnoj iz zadannyh konstant i sootvetstvuyushchem vetvlenii. V glave 1 my priveli programmu podscheta chisla vhozhdenij kazhdoj cifry, simvolov pustyh pro- mezhutkov i vseh ostal'nyh simvolov, ispol'zuyushchuyu posledova- tel'nost' IF...ELSE IF...ELSE. Vot ta zhe samaya programma s pereklyuchatelem. MAIN() /* COUNT DIGITS,WHITE SPACE, OTHERS */ { INT C, I, NWHITE, NOTHER, NDIGIT[10]; NWHITE = NOTHER = 0; FOR (I = 0; I < 10; I++) NDIGIT[I] = 0; WHILE ((C = GETCHAR()) != EOF) SWITCH (C) { CASE '0': CASE '1': CASE '2': CASE '3': CASE '4': CASE '5': CASE '6': CASE '7': CASE '8': CASE '9': NDIGIT[C-'0']++; BREAK; CASE ' ': CASE '\N': CASE '\T': NWHITE++; BREAK; DEFAULT : NOTHER++; BREAK; } PRINTF("DIGITS ="); FOR (I = 0; I < 10; I++) PRINTF(" %D", NDIGIT[I]); PRINTF("\NWHITE SPACE = %D, OTHER = %D\N", NWHITE, NOTHER); Pereklyuchatel' vychislyaet celoe vyrazhenie v kruglyh skob- kah (v dannoj programme - znachenie simvola s) i sravnivaet ego znachenie so vsemi sluchayami (CASE). Kazhdyj sluchaj dolzhen byt' pomechen libo celym, libo simvol'noj konstantoj, libo konstantnym vyrazheniem. Esli znachenie konstantnogo vyrazhe- niya, stoyashchego posle variantnogo prefiksa CASE, sovpadaet so znacheniem celogo vyrazheniya, to vypolnenie nachinaetsya s etogo sluchaya. Esli ni odin iz sluchaev ne podhodit, to vypolnyaetsya operator posle prefiksa DEFAULT. Prefiks DEFAULT yavlyaetsya neobyazatel'nym ,esli ego net, i ni odin iz sluchaev ne podho- dit, to voobshche nikakie dejstviya ne vypolnyayutsya. Sluchai i vy- bor po umolchaniyu mogut raspolagat'sya v lyubom poryadke. Vse sluchai dolzhny byt' razlichnymi. Operator BREAK privodit k nemedlennomu vyhodu iz perek- lyuchatelya. Poskol'ku sluchai sluzhat tol'ko v kachestve metok, to esli vy ne predprimite yavnyh dejstvij posle vypolneniya operatorov, sootvetstvuyushchih odnomu sluchayu, vy provalites' na sleduyushchij sluchaj. Operatory BREAK i RETURN yavlyayutsya samym obychnym sposobom vyhoda iz pereklyuchatelya. Kak my obsudim pozzhe v etoj glave, operator BREAk mozhno ispol'zovat' i dlya nemedlennogo vyhoda iz operatorov cikla WHILE, FOR i DO. Provalivanie skvoz' sluchai imeet kak svoi dostoinstva, tak i nedostatki. K polozhitel'nym kachestvam mozhno otnesti to, chto ono pozvolyaet svyazat' neskol'ko sluchaev s odnim dej- stviem, kak bylo s probelom, tabulyaciej i novoj strokoj v nashem primere. No v to zhe vremya ono obychno privodit k neob- hodimosti zakanchivat' kazhdyj sluchaj operatorom BREAK, chtoby izbezhat' perehoda k sleduyushchemu sluchayu. Provalivanie s odnogo sluchaya na drugoj obychno byvaet neustojchivym, tak kak ono sklonno k rasshchepleniyu pri modifikacii programmy. Za isklyuche- niem, kogda odnomu vychisleniyu sootvetstvuyut neskol'ko metok, provalivanie sleduet ispol'zovat' umerenno. Zavedite privychku stavit' operator BREAK posle posledne- go sluchaya (v dannom primere posle DEFAULT), dazhe esli eto ne yavlyaetsya logicheski neobhodimym. V odin prekrasnyj den', kog- da vy dobavite v konec eshche odin sluchaj, eta malen'kaya mera predostorozhnosti izbavit vas ot nepriyatnostej. Uprazhnenie 3-1 -------------- Napishite programmu dlya funkcii EXPAND(S, T), kotoraya ko- piruet stroku S v t, zamenyaya pri etom simvoly tabulyacii i novoj stroki na vidimye uslovnye posledovatel'nosti, kak \N i \t. ispol'zujte pereklyuchatel'. 3.5. Cikly - WHILE i FOR My uzhe stalkivalis' s operatorami cikla WHILE i FOR. V konstrukcii WHILE (vyrazhenie) operator vychislyaetsya vyrazhenie. Esli ego znachenie otlichno ot nulya, to vypolnyaetsya operator i vyrazhenie vychislyaetsya snova. |tot cikl prodolzhaetsya do teh por, poka znachenie vyrazheniya ne stanet nulem, posle chego vypolnenie programmy prodolzhaetsya s mesta posle operatora. Operator FOR (vyrazhenie 1; vyrazhenie 2; vyrazhenie 3) operator ekvivalenten posledovatel'nosti vyrazhenie 1; WHILE (vyrazhenie 2) { operator vyrazhenie 3; } Grammaticheski vse tri komponenta v FOR yavlyayutsya vyrazheniyami. naibolee rasprostranennym yavlyaetsya sluchaj, kogda vyrazhenie 1 i vyrazhenie 3 yavlyayutsya prisvaivaniyami ili obrashcheniyami k fun- kciyam, a vyrazhenie 2 - uslovnym vyrazheniem. lyubaya iz treh chastej mozhet byt' opushchena, hotya tochki s zapyatoj pri etom dolzhny ostavat'sya. Esli otsutstvuet vyrazhenie 1 ili vyrazhe- nie 3, to ono prosto vypadaet iz rasshireniya. Esli zhe otsuts- tvuet proverka, vyrazhenie 2, to schitaetsya, kak budto ono vsegda istinno, tak chto FOR (;;) { ... } yavlyaetsya beskonechnym ciklom, o kotorom predpolagaetsya, chto on budet prervan drugimi sredstvami (takimi kak BREAK ili RETURN). Ispol'zovat' li WHILE ili FOR - eto, v osnovnom delo vkusa. Naprimer v WHILE ((C = GETCHAR()) == ' ' \!\! C == '\N' \!\! C == '\T') ; /* SKIP WHITE SPACE CHARACTERS */ net ni inicializacii, ni reinicializacii, tak chto cikl WHILe vyglyadit samym estestvennym. Cikl FOR, ochevidno, predpochtitel'nee tam, gde imeetsya prostaya inicializaciya i reinicializaciya, poskol'ku pri etom upravlyayushchie ciklom operatory naglyadnym obrazom okazyvayutsya vmeste v nachale cikla. |to naibolee ochevidno v konstrukcii FOR (I = 0; I < N; I++) kotoraya yavlyaetsya idiomoj yazyka "C" dlya obrabotki pervyh N elementov massiva, analogichnoj operatoru cikla DO v fortrane i PL/1. Analogiya, odnako, ne polnaya, tak kak granicy cikla mogut byt' izmeneny vnutri cikla, a upravlyayushchaya peremennaya sohranyaet svoe znachenie posle vyhoda iz cikla, kakova by ni byla prichina etogo vyhoda. Poskol'ku komponentami FOR mogut byt' proizvol'nye vyrazheniya, oni ne ogranichivayutsya tol'ko arifmeticheskimi progressiyami. Tem ne menee yavlyaetsya plohim stilem vklyuchat' v FOR vychisleniya, kotorye ne otnosyatsya k up- ravleniyu ciklom, luchshe pomestit' ih v upravlyaemye ciklom operatory. V kachestve bol'shego po razmeru primera privedem drugoj variant funkcii ATOI, preobrazuyushchej stroku v ee chislennyj ekvivalent. |tot variant yavlyaetsya bolee obshchim; on dopuskaet prisutstvie v nachale simvolov pustyh promezhutkov i znaka + ili -. (V glave 4 privedena funkciya ATOF, kotoraya vypolnyaet to zhe samoe preobrazovanie dlya chisel s plavayushchej tochkoj). Obshchaya shema programmy otrazhaet formu postupayushchih dannyh: - propustit' pustoj promezhutok, esli on imeetsya - izvlech' znak, esli on imeetsya - izvlech' celuyu chast' i preobrazovat' ee Kazhdyj shag vypolnyaet svoyu chast' raboty i ostavlyaet vse v podgotovlennom sostoyanii dlya sleduyushchej chasti. Ves' process zakanchivaetsya na pervom simvole, kotoryj ne mozhet byt' chast'yu chisla. ATOI(S) /* CONVERT S TO INTEGER */ CHAR S[]; { INT I, N, SIGN; FOR(I=0;S[I]==' ' \!\! S[I]=='\N' \!\! S[I]=='\T';I++) ; /* SKIP WHITE SPACE */ SIGN = 1; IF(S[I] == '+' \!\! S[I] == '-') /* SIGN */ SIGN = (S[I++]=='+') ? 1 : - 1; FOR( N = 0; S[I] >= '0' && S[I] <= '9'; I++) N = 10 * N + S[I] - '0'; RETURN(SIGN * N); } Preimushchestva centralizacii upravleniya ciklom stanovyatsya eshche bolee ochevidnymi, kogda imeetsya neskol'ko vlozhennyh cik- lov. Sleduyushchaya funkciya sortiruet massiv celyh chisel po meto- du shella. osnovnaya ideya sortirovki po shellu zaklyuchaetsya v tom, chto snachala sravnivayutsya udalennye elementy, a ne smezh- nye, kak v obychnom metode sortirovki. |to privodit k bystro- mu ustraneniyu bol'shoj chasti neuporyadochennosti i sokrashchaet posleduyushchuyu rabotu. Interval mezhdu elementami postepenno sokrashchaetsya do edinicy, kogda sortirovka fakticheski prevra- shchaetsya v metod perestanovki sosednih elementov. SHELL(V, N) /* SORT V[0]...V[N-1] INTO INCREASING ORDER */ INT V[], N; { INT GAP, I, J, TEMP; FOR (GAP = N/2; GAP > 0; GAP /= 2) FOR (I = GAP; I < N; I++) FOR (J=I-GAP; J>=0 && V[J]>V[J+GAP]; J-=GAP) { TEMP = V[J]; V[J] = V[J+GAP]; V[J+GAP] = TEMP; } } Zdes' imeyutsya tri vlozhennyh cikla. Samyj vneshnij cikl uprav- lyaet intervalom mezhdu sravnivaemymi elementami, umen'shaya ego ot N/2 vdvoe pri kazhdom prohode, poka on ne stanet ravnym nulyu. Srednij cikl sravnivaet kazhduyu paru elementov, razde- lennyh na velichinu intervala; samyj vnutrennij cikl peres- tavlyaet lyubuyu neuporyadochennuyu paru. Tak kak interval v konce koncov svoditsya k edinice, vse elementy v rezul'tate uporya- dochivayutsya pravil'no. Otmetim, chto v silu obshchnosti konstruk- cii FOR vneshnij cikl ukladyvaetsya v tu zhe samuyu formu, chto i ostal'nye, hotya on i ne yavlyaetsya arifmeticheskoj progressiej. Poslednej operaciej yazyka "C" yavlyaetsya zapyataya ",", ko- toraya chashche vsego ispol'zuetsya v operatore FOR. Dva vyrazhe- niya, razdelennye zapyatoj, vychislyayutsya sleva napravo, prichem tipom i znacheniem rezul'tata yavlyayutsya tip i znachenie pravogo operanda. Takim obrazom, v razlichnye chasti operatora FOR mozhno vklyuchit' neskol'ko vyrazhenij, naprimer, dlya parallel'- nogo izmeneniya dvuh indeksov. |to illyustriruetsya funkciej REVERSE(S), kotoraya raspolagaet stroku S v obratnom poryadke na tom zhe meste. REVERSE(S) /* REVERSE STRING S IN PLACE */ CHAR S[]; { INT C, I, J; FOR(I = 0, J = STRLEN(S) - 1; I < J; I++, J--) { C = S[I]; S[I] = S[J]; S[J] = C; } } Zapyatye, kotorye razdelyayut argumenty funkcij, peremennye v opisaniyah i t.d., ne imeyut otnosheniya k operacii zapyataya i ne obespechivayut vychislenij sleva napravo. Uprazhnenie 3-2 --------------- Sostav'te programmu dlya funkcii EXPAND(S1,S2), kotoraya rasshiryaet sokrashchennye oboznacheniya vida a-Z iz stroki S1 v ekvivalentnyj polnyj spisok avs...XYZ v S2. Dopuskayutsya sok- rashcheniya dlya strochnyh i propisnyh bukv i cifr. Bud'te gotovy imet' delo so sluchayami tipa a-v-s, a-Z0-9 i -a-Z. (Poleznoe soglashenie sostoit v tom, chto simvol -, stoyashchij v nachale ili konce, vosprinimaetsya bukval'no). 3.6. Cikl DO - WHILE Kak uzhe otmechalos' v glave 1, cikly WHILE i FOR obladayut tem priyatnym svojstvom, chto v nih proverka okonchaniya osushches- tvlyaetsya v nachale, a ne v konce cikla. Tretij operator cikla yazyka "C", DO-WHILE, proveryaet uslovie okonchaniya v konce, posle kazhdogo prohoda cherez telo cikla; telo cikla vsegda vypolnyaetsya po krajnej mere odin raz. Sintaksis etogo opera- tora imeet vid: DO operator WHILE (vyrazhenie) Snachala vypolnyaetsya operator, zatem vychislyaetsya vyrazhenie. Esli ono istinno, to operator vypolnyaetsya snova i t.d. Esli vyrazhenie stanovitsya lozhnym, cikl zakanchivaetsya. Kak i mozhno bylo ozhidat', cikl DO-WHILE ispol'zuetsya znachitel'no rezhe, chem WHILE i FOR, sostavlyaya primerno pyat' procentov ot vseh ciklov. Tem ne menee, inogda on okazyvaet- sya poleznym, kak, naprimer, v sleduyushchej funkcii ITOA, koto- raya preobrazuet chislo v simvol'nuyu stroku (obratnaya funkcii ATOI). |ta zadacha okazyvaetsya neskol'ko bolee slozhnoj, chem mozhet pokazat'sya snachala. Delo v tom, chto prostye metody vy- deleniya cifr generiruyut ih v nepravil'nom poryadke. My pred- pochli poluchit' stroku v obratnom poryadke, a zatem obratit' ee. ITOA(N,S) /*CONVERT N TO CHARACTERS IN S */ CHAR S[]; INT N; { INT I, SIGN; IF ((SIGN = N) < 0) /* RECORD SIGN */ N = -N; /* MAKE N POSITIVE */ I = 0; DO { /* GENERATE DIGITS IN REVERSE ORDER */ S[I++] = N % 10 + '0';/* GET NEXT DIGIT */ } WHILE ((N /=10) > 0); /* DELETE IT */ IF (SIGN < 0) S[I++] = '-' S[I] = '\0'; REVERSE(S); } Cikl DO-WHILE zdes' neobhodim, ili po krajnej mere udoben, poskol'ku, kakovo by ni bylo znachenie N, massiv S dolzhen so- derzhat' hotya by odin simvol. My zaklyuchili v figurnye skobki odin operator, sostavlyayushchij telo DO-WHILe, hotya eto i ne obyazatel'no, dlya togo, chtoby toroplivyj chitatel' ne prinyal chast' WHILE za nachalo operatora cikla WHILE. Uprazhnenie 3-3 -------------- Pri predstavlenii chisel v dvoichnom dopolnitel'nom kode nash variant ITOA ne spravlyaetsya s naibol'shim otricatel'nym chislom, t.e. So znacheniem N rAvnym -2 v stepeni m-1, gde m - razmer slova. ob座asnite pochemu. Izmenite programmu tak, chto- by ona pravil'no pechatala eto znachenie na lyuboj mashine. Uprazhnenie 3-4 -------------- Napishite analogichnuyu funkciyu ITOB(N,S), kotoraya preobra- zuet celoe bez znaka N v ego dvoichnoe simvol'noe predstavle- nie v S. Zaprogrammirujte funkciyu ITOH, kotoraya preobrazuet celoe v shestnadcaterichnoe predstavlenie. Uprazhnenie 3-5 --------------- Napishite variant Itoa, kotoryj imeet tri, a ne dva argu- menta. Tretij argument - minimal'naya shirina polya; preobrazo- vannoe chislo dolzhno, esli eto neobhodimo, dopolnyat'sya sleva probelami, tak chtoby ono imelo dostatochnuyu shirinu. 3.7. Operator BREAK Inogda byvaet udobnym imet' vozmozhnost' upravlyat' vyho- dom iz cikla inache, chem proverkoj usloviya v nachale ili v konce. Operator BReak pozvolyaet vyjti iz operatorov FOR, WHILE i DO do okonchaniya cikla tochno tak zhe, kak i iz perek- lyuchatelya. Operator BReak privodit k nemedlennomu vyhodu iz samogo vnutrennego ohvatyvayushchego ego cikla (ili pereklyuchate- lya). Sleduyushchaya programma udalyaet hvostovye probely i tabulya- cii iz konca kazhdoj stroki fajla vvoda. Ona ispol'zuet ope- rator BReak dlya vyhoda iz cikla, kogda najden krajnij pravyj otlichnyj ot probela i tabulyacii simvol. #DEFINE MAXLINE 1000 MAIN() /* REMOVE TRAILING BLANKS AND TABS */ { INT N; CHAR LINE[MAXLINE]; WHILE ((N = GETLINE(LINE,MAXLINE)) > 0) { WHILE (--N >= 0) IF (LINE[N] != ' ' && LINE[N] != '\T' && LINE[N] != '\N') BREAK; LINE[N+1] = '\0'; PRINTF("%S\N",LINE); } } Funkciya GETLINE vozvrashchaet dlinu stroki. Vnutrennij cikl nachinaetsya s poslednego simvola LINE (napomnim, chto --N umen'shaet N do ispol'zovaniya ego znacheniya) i dvizhetsya v ob- ratnom napravlenii v poiske pervogo simvola , kotoryj otli- chen ot probela, tabulyacii ili novoj stroki. Cikl preryvaet- sya, kogda libo najden takoj simvol, libo N stanovitsya otri- catel'nym (t.e., kogda prosmotrena vsya stroka). Sovetuem vam ubedit'sya, chto takoe povedenie pravil'no i v tom sluchae, kogda stroka sostoit tol'ko iz simvolov pustyh promezhutkov. V kachestve al'ternativy k BReak mozhno vvesti proverku v sam cikl: WHILE ((N = GETLINE(LINE,MAXLINE)) > 0) { WHILE (--N >= 0 && (LINE[N] == ' ' \!\! LINE[N] == '\T' \!\! LINE[N] == '\N')) ; ... } |to ustupaet predydushchemu variantu, tak kak proverka stano- vitsya trudnee dlya ponimaniya. Proverok, kotorye trebuyut pe- repleteniya &&, \!\!, ! I kruglyh skobok, po vozmozhnosti sle- duet izbegat'. 3.8. Operator CONTINUE Operator CONTINUE rodstvenen operatoru BReak, no ispol'- zuetsya rezhe; on privodit k nachalu sleduyushchej iteracii ohvaty- vayushchego cikla (FOR, WHILE, DO ). V ciklah WHILE i DO eto oz- nachaet neposredstvennyj perehod k vypolneniyu proverochnoj chasti; v cikle FOR upravlenie peredaetsya na shag reiniciali- zacii. (Operator CONTINUE primenyaetsya tol'ko v ciklah, no ne v pereklyuchatelyah. Operator CONTINUE vnutri pereklyuchatelya vnutri cikla vyzyvaet vypolnenie sleduyushchej iteracii cikla). V kachestve primera privedem fragment, kotoryj obrabaty- vaet tol'ko polozhitel'nye elementy massiva a; otricatel'nye znacheniya propuskayutsya. FOR (I = 0; I < N; I++) { IF (A[I] < 0) /* SKIP NEGATIVE ELEMENTS */ CONTINUE; ... /* DO POSITIVE ELEMENTS */ } Operator CONTINUE chasto ispol'zuetsya, kogda posleduyushchaya chast' cikla okazyvaetsya slishkom slozhnoj, tak chto rassmotre- nie usloviya, obratnogo proveryaemomu, privodit k slishkom glu- bokomu urovnyu vlozhennosti programmy. Uprazhnenie 3-6 -------------- Napishite programmu kopirovaniya vvoda na vyvod, s tem is- klyucheniem, chto iz kazhdoj gruppy posledovatel'nyh odinakovyh strok vyvoditsya tol'ko odna. (|to prostoj variant utility UNIQ sistem UNIX). 3.9. Operator GOTO i metki V yazyke "C" predusmotren i operator GOTO, kotorym besko- nechno zloupotreblyayut, i metki dlya vetvleniya. S formal'noj tochki zreniya operator GOTO nikogda ne yavlyaetsya neobhodimym, i na praktike pochti vsegda mozhno obojtis' bez nego. My ne ispol'zovali GOTO v etoj knige. Tem ne menee, my ukazhem neskol'ko situacij, gde operator GOTO mozhet najti svoe mesto. Naibolee harakternym yavlyaetsya ego ispol'zovanie togda, kogda nuzhno prervat' vypolnenie v nekotoroj gluboko vlozhennoj strukture, naprimer, vyjti srazu iz dvuh ciklov. Zdes' nel'zya neposredstvenno ispol'zovat' operator BReak, tak kak on preryvaet tol'ko samyj vnutrennij cikl. Poetomu: FOR ( ... ) FOR ( ... ) { ... IF (DISASTER) GOTO ERROR; } ... ERROR: CLEAN UP THE MESS Esli programma obrabotki oshibok netrivial'na i oshibki mogut voznikat' v neskol'kih mestah, to takaya organizaciya okazyva- etsya udobnoj. Metka imeet takuyu zhe formu, chto i imya peremen- noj, i za nej vsegda sleduet dvoetochie. Metka mozhet byt' pripisana k lyubomu operatoru toj zhe funkcii, v kotoroj naho- ditsya operator GOTO. V kachestve drugogo primera rassmotrim zadachu nahozhdeniya pervogo otricatel'nogo elementa v dvumernom massive. (Mnogo- mernye massivy rassmatrivayutsya v glave 5). Vot odna iz voz- mozhnostej: FOR (I = 0; I < N; I++) FOR (J = 0; J < M; J++) IF (V[I][J] < 0) GOTO FOUND; /* DIDN'T FIND */ ... FOUND: /* FOUND ONE AT POSITION I, J */ ... Programma, ispol'zuyushchaya operator GOTO, vsegda mozhet byt' napisana bez nego, hotya, vozmozhno, za schet povtoreniya neko- toryh proverok i vvedeniya dopolnitel'nyh peremennyh. Napri- mer, programma poiska v massive primet vid: FOUND = 0; FOR (I = 0; I < N && !FOUND; I++) FOR (J = 0; J < M && !FOUND; J++) FOUND = V[I][J] < 0; IF (FOUND) /* IT WAS AT I-1, J-1 */ ... ELSE /* NOT FOUND */ ... Hotya my ne yavlyaemsya v etom voprose dogmatikami, nam vse zhe kazhetsya, chto esli i nuzhno ispol'zovat' operator GOTO, to ves'ma umerenno.  * 4. Funkcii i struktura programm *  Funkcii razbivayut bol'shie vychislitel'nye zadachi na ma- len'kie podzadachi i pozvolyayut ispol'zovat' v rabote to, chto uzhe sdelano drugimi, a ne nachinat' kazhdyj raz s pustogo mes- ta. Sootvetstvuyushchie funkcii chasto mogut skryvat' v sebe de- tali provodimyh v raznyh chastyah programmy operacij, znat' kotorye net neobhodimosti, proyasnyaya tem samym vsyu programmu, kak celoe, i oblegchaya mucheniya pri vnesenii izmenenij. YAzyk "C" razrabatyvalsya so stremleniem sdelat' funkcii effektivnymi i udobnymi dlya ispol'zovaniya; "C"-programmy obychno sostoyat iz bol'shogo chisla malen'kih funkcij, a ne iz neskol'kih bol'shih. Programma mozhet razmeshchat'sya v odnom ili neskol'kih ishodnyh fajlah lyubym udobnym obrazom; ishodnye fajly mogut kompilirovat'sya otdel'no i zagruzhat'sya vmeste naryadu so skompilirovannymi ranee funkciyami iz bibliotek. My zdes' ne budem vdavat'sya v detali etogo processa, poskol'ku oni zavisyat ot ispol'zuemoj sistemy. Bol'shinstvo programmistov horosho znakomy s "bibliotechny- mi" funkciyami dlya vvoda i vyvoda /GETCHAR , PUTCHAR/ i dlya chislennyh raschetov /SIN, COS, SQRT/. V etoj glave my soobshchim bol'she o napisanii novyh funkcij. 4.1. Osnovnye svedeniya Dlya nachala davajte razrabotaem i sostavim programmu pe- chati kazhdoj stroki vvoda, kotoraya soderzhit opredelennuyu kom- binaciyu simvolov. /|to - special'nyj sluchaj utility GREP sistemy "UNIX"/. Naprimer, pri poiske kombinacii "THE" v na- bore strok NOW IS THE TIME FOR ALL GOOD MEN TO COME TO THE AID OF THEIR PARTY v kachestve vyhoda poluchim NOW IS THE TIME MEN TO COME TO THE AID OF THEIR PARTY osnovnaya shema vypolneniya zadaniya chetko razdelyaetsya na tri chasti: WHILE (imeetsya eshche stroka) IF (stroka soderzhit nuzhnuyu kombinaciyu) vyvod etoj stroki Konechno, vozmozhno zaprogrammirovat' vse dejstviya v vide odnoj osnovnoj procedury, no luchshe ispol'zovat' estestvennuyu strukturu zadachi i predstavit' kazhduyu chast' v vide otdel'noj funkcii. S tremya malen'kimi kuskami legche imet' delo, chem s odnim bol'shim, potomu chto otdel'nye ne otnosyashchiesya k sushchest- vu dela detali mozhno vklyuchit' v funkcii i umen'shit' vozmozh- nost' nezhelatel'nyh vzaimodejstvij. Krome togo, eti kuski mogut okazat'sya poleznymi sami po sebe. "Poka imeetsya eshche stroka" - eto GETLINE, funkciya, koto- ruyu my zaprogrammirovali v glave 1, a "vyvod etoj stroki" - eto funkciya PRINTF, kotoruyu uzhe kto-to podgotovil dlya nas. |to znachit, chto nam ostalos' tol'ko napisat' proceduru dlya opredeleniya, soderzhit li stroka dannuyu kombinaciyu simvolov ili net. My mozhem reshit' etu problemu, pozaimstvovav razra- botku iz PL/1: funkciya INDEX(S,t) vozvrashchaet poziciyu, ili indeks, stroki S, gde nachinaetsya stroka T, i -1, esli S ne soderzhit t . V kachestve nachal'noj pozicii my ispol'zuem 0, a ne 1, potomu chto v yazyke "C" massivy nachinayutsya s pozicii nul'. Kogda nam v dal'nejshem ponadobitsya proveryat' na sovpa- denie bolee slozhnye konstrukcii, nam pridetsya zamenit' tol'- ko funkciyu INDEX; ostal'naya chast' programmy ostanetsya toj zhe samoj. Posle togo, kak my potratili stol'ko usilij na razrabot- ku, napisanie programmy v detalyah ne predstavlyaet zatrudne- nij. nizhe privoditsya celikom vsya programma, tak chto vy mozhe- te videt', kak soedinyayutsya vmeste otdel'nye chasti. Kombina- ciya simvolov, po kotoroj proizvoditsya poisk, vystupaet poka v kachestve simvol'noj stroki v argumente funkcii INDEX, chto ne yavlyaetsya samym obshchim mehanizmom. My skoro vernemsya k ob- suzhdeniyu voprosa ob inicializacii simvol'nyh massivov i v glave 5 pokazhem, kak sdelat' kombinaciyu simvolov parametrom, kotoromu prisvaivaetsya znachenie v hode vypolneniya programmy. Programma takzhe soderzhit novyj variant funkcii GETLINE; vam mozhet okazat'sya poleznym sravnit' ego s variantom iz glavy 1. #DEFINE MAXLINE 1000 MAIN() /* FIND ALL LINES MATCHING A PATTERN */ { CHAR LINE[MAXLINE]; WHILE (GETLINE(LINE, MAXLINE) > 0) IF (INDEX(LINE, "THE") >= 0) PRINTF("%S", LINE); } GETLINE(S, LIM) /* GET LINE INTO S, RETURN LENGTH * CHAR S[]; INT LIM; { INT C, I; I = 0; WHILE(--LIM>0 && (C=GETCHAR()) != EOF && C != '\N') S[I++] = C; IF (C == '\N') S[I++] = C; S[I] = '\0'; RETURN(I); } INDEX(S,T) /* RETURN INDEX OF T IN S,-1 IF NONE */ CHAR S[], T[]; { INT I, J, K; FOR (I = 0; S[I] != '\0'; I++) { FOR(J=I, K=0; T[K] !='\0' && S[J] == T[K]; J++; K++) ; IF (T[K] == '\0') RETURN(I); } RETURN(-1); } Kazhdaya funkciya imeet vid imya (spisok argumentov, esli oni imeyutsya) opisaniya argumentov, esli oni imeyutsya { opisaniya i operatory , esli oni imeyutsya } Kak i ukazyvaetsya, nekotorye chasti mogut otsutstvo- vat'; minimal'noj funkciej yavlyaetsya DUMMY () { } kotoraya ne sovershaet nikakih dejstvij. /Takaya nichego ne delayushchaya funkciya inogda okazyvaetsya udobnoj dlya sohraneniya mesta dlya dal'nejshego razvitiya prog- rammy/. esli funkciya vozvrashchaet chto-libo otlichnoe ot celogo znacheniya, to pered ee imenem mozhet stoyat' ukazatel' tipa; etot vopros obsuzhdaetsya v sleduyushchem razdele. Programmoj yavlyaetsya prosto nabor opredelenij otdel'nyh funkcij. Svyaz' mezhdu funkciyami osushchestvlyaetsya cherez argumen- ty i vozvrashchaemye funkciyami znacheniya /v etom sluchae/; ee mozhno takzhe osushchestvlyat' cherez vneshnie peremennye. Funkcii mogut raspolagat'sya v ishodnom fajle v lyubom poryadke, a sama ishodnaya programma mozhet razmeshchat'sya na neskol'kih fajlah, no tak, chtoby ni odna funkciya ne rasshcheplyalas'. Operator RETURN sluzhit mehanizmom dlya vozvrashcheniya zna- cheniya iz vyzvannoj funkcii v funkciyu, kotoraya k nej obrati- las'. Za RETURN mozhet sledovat' lyuboe vyrazhenie: RETURN (vyrazhenie) Vyzyvayushchaya funkciya mozhet ignorirovat' vozvrashchaemoe znachenie, esli ona etogo pozhelaet. Bolee togo, posle RETURN mozhet ne byt' voobshche nikakogo vyrazheniya; v etom sluchae v vy- zyvayushchuyu programmu ne peredaetsya nikakogo znacheniya. Upravle- nie takzhe vozvrashchetsya v vyzyvayushchuyu programmu bez peredachi kakogo-libo znacheniya i v tom sluchae, kogda pri vypolnenii my "provalivaemsya" na konec funkcii, dostigaya zakryvayushchejsya pravoj figurnoj skobki. ESli funkciya vozvrashchaet znachenie iz odnogo mesta i ne vozvrashchaet nikakogo znacheniya iz drugogo mesta, eto ne yavlyaetsya nezakonnym, no mozhet byt' priznakom kakih-to nepriyatnostej. V lyubom sluchae "znacheniem" funkcii, kotoraya ne vozvrashchaet znacheniya, nesomnenno budet musor. Ot- ladochnaya programma LINT proveryaet takie oshibki. Mehanika kompilyacii i zagruzki "C"-programm, raspolo- zhennyh v neskol'kih ishodnyh fajlah, menyaetsya ot sistemy k sisteme. V sisteme "UNIX", naprimer, etu rabotu vypolnyaet komanda 'CC', upomyanutaya v glave 1. Predpolozhim, chto tri funkcii nahodyatsya v treh razlichnyh fajlah s imenami MAIN.s, GETLINE.C i INDEX.s . Togda komanda CC MAIN.C GETLINE.C INDEX.C kompiliruet eti tri fajla, pomeshchaet poluchennyj nastraivaemyj ob容ktnyj kod v fajly MAIN.O, GETLINE.O i INDEX.O i zagruzha- et ih vseh v vypolnyaemyj fajl, nazyvaemyj A.OUT . Esli imeetsya kakaya-to oshibka, skazhem v MAIN.C, to etot fajl mozhno perekompilirovat' otdel'no i zagruzit' vmeste s predydushchimi ob容ktnymi fajlami po komande CC MAIN.C GETLIN.O INDEX.O Komanda 'CC' ispol'zuet soglashenie o naimenovanii s ".s" i ".o" dlya togo, chtoby otlichit' ishodnye fajly ot ob容ktnyh. Uprazhnenie 4-1 ---------------- Sostav'te programmu dlya funkcii RINDEX(S,T), kotoraya vozvrashchaet poziciyu samogo pravogo vhozhdeniya t v S i -1, esli S ne soderzhit T. 4.2. Funkcii, vozvrashchayushchie necelye znacheniya Do sih por ni odna iz nashih programm ne soderzhala kako- go-libo opisaniya tipa funkcii. Delo v tom, chto po umolchaniyu funkciya neyavno opisyvaetsya svoim poyavleniem v vyrazhenii ili operatore, kak, naprimer, v WHILE (GETLINE(LINE, MAXLINE) > 0) Esli nekotoroe imya, kotoroe ne bylo opisano ranee, poyav- lyaetsya v vyrazhenii i za nim sleduet levaya kruglaya skobka, to ono po kontekstu schitaetsya imenem nekotoroj funkcii. Krome togo, po umolchaniyu predpolagaetsya, chto eta funkciya vozvrashcha- et znachenie tipa INT. Tak kak v vyrazheniyah CHAR preobrazuet- sya v INT, to net neobhodimosti opisyvat' funkcii, vozvrashchayu- shchie CHAR. |ti predpolozheniya pokryvayut bol'shinstvo sluchaev, vklyuchaya vse privedennye do sih por primery. No chto proishodit, esli funkciya dolzhna vozvratit' znache- nie kakogo-to drugogo tipa ? Mnogie chislennye funkcii, takie kak SQRT, SIN i COS vozvrashchayut DOUBLE; drugie special'nye funkcii vozvrashchayut znacheniya drugih tipov. CHtoby pokazat', kak postupat' v etom sluchae, davajte napishem i ispol'zuem funkciyu AToF(S), kotoraya preobrazuet stroku S v ekvivalent- noe ej plavayushchee chislo dvojnoj tochnosti. Funkciya AToF yavlya- etsya rasshireniem atoI, varianty kotoroj my napisali v glavah 2 i 3; ona obrabatyvaet neobyazatel'no znak i desyatichnuyu toch- ku, a takzhe celuyu i drobnuyu chast', kazhdaya iz kotoryh mozhet kak prisutstvovat', tak i otsutstvovat'./eta procedura pre- obrazovaniya vvoda ne ochen' vysokogo kachestva; inache ona by zanyala bol'she mesta, chem nam hotelos' by/. Vo-pervyh, sama AToF dolzhna opisyvat' tip vozvrashchaemogo eyu znacheniya, poskol'ku on otlichen ot INT. Tak kak v vyrazhe- niyah tip FLOAT preobrazuetsya v DOUBLE, to net nikakogo smys- la v tom, chtoby ATOF vozvrashchala FLOAT; my mozhem s ravnym us- pehom vospol'zovat'sya dopolnitel'noj tochnost'yu, tak chto my polagaem, chto vozvrashchaemoe znachenie tipa DOUBLE. Imya tipa dolzhno stoyat' pered imenem funkcii, kak pokazyvaetsya nizhe: DOUBLE ATOF(S) /* CONVERT STRING S TO DOUBLE */ CHAR S[]; { DOUBLE VAL, POWER; INT I, SIGN; FOR(I=0; S[I]==' ' \!\! S[I]=='\N' \!\! S[I]=='\T'; I++) ; /* SKIP WHITE SPACE */ SIGN = 1; IF (S[I] == '+' \!\! S[I] == '-') /* SIGN */ SIGN = (S[I++] == '+') ? 1 : -1; FOR (VAL = 0; S[I] >= '0' && S[I] <= '9'; I++) VAL = 10 * VAL + S[I] - '0'; IF (S[I] == '.') I++; FOR (POWER = 1; S[I] >= '0' && S[I] <= '9'; I++) { VAL = 10 * VAL + S[I] - '0'; POWER *= 10; } RETURN(SIGN * VAL / POWER); } Vtorym, no stol' zhe vazhnym, yavlyaetsya to, chto vyzyvayushchaya funkciya dolzhna ob座avit' o tom, chto ATOF vozvrashchaet znachenie, otlichnoe ot INT tipa. Takoe ob座avlenie demonstriruetsya na primere sleduyushchego primitivnogo nastol'nogo kal'kulyatora /edva prigodnogo dlya podvedeniya balansa v chekovoj knizhke/, kotoryj schityvaet po odnomu chislu na stroku, prichem eto chis- lo mozhet imet' znak, i skladyvaet vse chisla, pechataya summu posle kazhdogo vvoda. #DEFINE MAXLINE 100 MAIN() /* RUDIMENTARY DESK CALKULATOR */ { DOUBLE SUM, ATOF(); CHAR LINE[MAXLINE]; SUM = 0; WHILE (GETLINE(LINE, MAXLINE) > 0) PRINTF("\T%.2F\N",SUM+=ATOF(LINE)); Oisanie DOUBLE SUM, ATOF(); govorit, chto SUM yavlyaetsya peremennoj tipa DOUBLE , i chto ATOF yavlyaetsya funkciej, vozvrashchayushchej znachenie tipa DOUBLE . |ta mnemonika oznachaet, chto znacheniyami kak SUM, tak i ATOF(...) yavlyayutsya plavayushchie chisla dvojnoj tochnosti. Esli funkciya ATOF ne budet opisana yavno v oboih mestah, to v "C" predpolagaetsya, chto ona vozvrashchaet celoe znachenie, i vy poluchite bessmyslennyj otvet. Esli sama ATOF i obrashche- nie k nej v MAIN imeyut nesovmestimye tipy i nahodyatsya v od- nom i tom zhe fajle, to eto budet obnaruzheno kompilyatorom. No esli ATOF byla skompilirovana otdel'no /chto bolee veroyatno/, to eto nesootvetstvie ne budet zafiksirovano, tak chto ATOF budet vozvrashchat' znacheniya tipa DOUBLE, s kotorym MAIN budet obrashchat'sya, kak s INT , chto privedet k bessmyslennym rezul'- tatam. /Programma LINT vylavlivaet etu oshibku/. Imeya ATOF, my, v principe, mogli by s ee pomoshch'yu napi- sat' ATOI (preobrazovanie stroki v INT): ATOI(S) /* CONVERT STRING S TO INTEGER */ CHAR S[]; { DOUBLE ATOF(); RETURN(ATOF(S)); } Obratite vnimanie na strukturu opisanij i operator RETURN. Znachenie vyrazheniya v RETURN (vyrazhenie) vsegda preobrazuetsya k tipu funkcii pered vypolneniem samogo vozvrashcheniya. Poetomu pri poyavlenii v operatore RETURN znache- nie funkcii atoF, imeyushchee tip DOUBLE, avtomaticheski preobra- zuetsya v INT, poskol'ku funkciya ATOI vozvrashchaet INT. (Kak obsuzhdalos' v glave 2, preobrazovanie znacheniya s plavayushchej tochkoj k tipu INT osushchestvlyaetsya posredstvom otbrasyvaniya drobnoj chasti). Uprazhnenie 4-2 ---------------- Rasshir'te ATOF takim obrazom, chtoby ona mogla rabotat' s chislami vida 123.45e-6 gde za chislom s plavayushchej tochkoj mozhet sledovat' 'E' i poka- zatel' eksponenty, vozmozhno so znakom. 4.3. Eshche ob argumentah funkcij V glave 1 my uzhe obsuzhdali tot fakt , chto argumenty fun- kcij peredayutsya po znacheniyu, t.e. vyzvannaya funkciya poluchaet svoyu vremennuyu kopiyu kazhdogo argumenta, a ne ego adres. eto oznachaet, chto vyzvannaya funkciya ne mozhet vozdejstvovat' na ishodnyj argument v vyzyvayushchej funkcii. Vnutri funkcii kazh- dyj argument po sushchestvu yavlyaetsya lokal'noj peremennoj, ko- toraya inicializiruetsya tem znacheniem, s kotorym k etoj funk- cii obratilis'. Esli v kachestve argumenta funkcii vystupaet imya massiva, to peredaetsya adres nachala etogo massiva; sami elementy ne kopiruyutsya. Funkciya mozhet izmenyat' elementy massiva, ispol'- zuya indeksaciyu i adres nachala. Takim obrazom, massiv pereda- etsya po ssylke. V glave 5 my obsudim, kak ispol'zovanie uka- zatelej pozvolyaet funkciyam vozdejstvovat' na otlichnye ot massivov peremennye v vyzyvayushchih funkciyah. Mezhdu prochim, nesushchestvuet polnost'yu udovletvoritel'nogo sposoba napisaniya perenosimoj funkcii s peremennym chislom argumentov. Delo v tom, chto net perenosimogo sposoba, s po- moshch'yu kotorogo vyzvannaya funkciya mogla by opredelit', skol'- ko argumentov bylo fakticheski peredano ej v dannom obrashche- nii. Takim obrazom, vy, naprimer, ne mozhete napisat' dejst- vitel'no perenosimuyu funkciyu, kotoraya budet vychislyat' maksi- mum ot proizvol'nogo chisla argumentov, kak delayut vstroennye funkcii MAX v fortrane i PL/1. Obychno so sluchaem peremennogo chisla argumentov bezopasno imet' delo, esli vyzvannaya funkciya ne ispol'zuet argumentov, kotorye ej na samom dele ne byli peredany, i esli tipy sog- lasuyutsya. Samaya rasprostranennaya v yazyke "C" funkciya s pere- mennym chislom - PRINTF . Ona poluchaet iz pervogo argumenta informaciyu, pozvolyayushchuyu opredelit' kolichestvo ostal'nyh ar- gumentov i ih tipy. Funkciya PRINTF rabotaet sovershenno nep- ravil'no, esli vyzyvayushchaya funkciya peredaet ej nedostatochnoe kolichestvo argumentov, ili esli ih tipy ne soglasuyutsya s ti- pami, ukazannymi v pervom argumente. |ta funkciya ne yavlyaetsya perenosimoj i dolzhna modificirovat'sya pri ispol'zovanii v razlichnyh usloviyah. Esli zhe tipy argumentov izvestny, to konec spiska argu- mentov mozhno otmetit', ispol'zuya kakoe-to soglashenie; napri- mer, schitaya, chto nekotoroe special'noe znachenie argumenta (chasto nul') yavlyaetsya priznakom konca argumentov. 4.4. Vneshnie peremennye Programma na yazyke "C" sostoit iz nabora vneshnih ob容k- tov, kotorye yavlyayutsya libo peremennymi, libo funkciyami. Ter- min "vneshnij" ispol'zuetsya glavnym obrazom v protivopostav- lenie terminu "vnutrennij", kotorym opisyvayutsya argumenty i avtomaticheskie peremennye, opredelennye vnurti funkcij. Vneshnie peremennye opredeleny vne kakoj-libo funkcii i, ta- kim obrazom, potencial'no dostupny dlya mnogih funkcij. Sami funkcii vsegda yavlyayutsya vneshnimi, potomu chto pravila yazyka "C" ne razreshayut opredelyat' odni funkcii vnutri drugih. Po umolchaniyu vneshnie peremennye yavlyayu