m, esli PA ukazyvaet na A[0], to *(PA+1) ssylaetsya na soderzhimoe A[1], PA+I - adres A[I], a *(PA+I) - soderzhimoe A[I]. |ti zamechaniya spravedlivy nezavisimo ot tipa peremennyh v massive A. Sut' opredeleniya "dobavleniya 1 k ukazatelyu", a takzhe ego rasprostraneniya na vsyu arifmetiku ukazatelej, sos- toit v tom, chto prirashchenie masshtabiruetsya razmerom pamyati, zanimaemoj ob容ktom, na kotoryj ukazyvaet ukazatel'. Takim obrazom, I v PA+I pered pribavleniem umnozhaetsya na razmer ob容ktov, na kotorye ukazyvaet PA. Ochevidno sushchestvuet ochen' tesnoe sootvetstvie mezhdu in- deksaciej i arifmetikoj ukazatelej. v dejstvitel'nosti kom- pilyator preobrazuet ssylku na massiv v ukazatel' na nachalo massiva. V rezul'tate etogo imya massiva yavlyaetsya ukazatel'- nym vyrazheniem. Otsyuda vytekaet neskol'ko ves'ma poleznyh sledstvij. Tak kak imya massiva yavlyaetsya sinonimom mestopolo- zheniya ego nulevogo elementa, to prisvaivanie PA=&A[0] mozhno zapisat' kak PA = A Eshche bolee udivitel'nym, po krajnej mere na pervyj vzg- lyad, kazhetsya tot fakt, chto ssylku na A[I] mozhno zapisat' v vide *(A+I). Pri analizirovanii vyrazheniya A[I] v yazyke "C" ono nemedlenno preobrazuetsya k vidu *(A+I); eti dve formy sovershenno ekvivalentny. Esli primenit' operaciyu & k obeim chastyam takogo sootnosheniya ekvivalentnosti, to my poluchim, chto &A[I] i A+I tozhe identichny: A+I - adres I-go elementa ot nachala A. S drugoj storony, esli PA yavlyaetsya ukazatelem, to v vyrazheniyah ego mozhno ispol'zovat' s indeksom: PA[I] iden- tichno *(PA+I). Koroche, lyuboe vyrazhenie, vklyuchayushchee massivy i indeksy, mozhet byt' zapisano cherez ukazateli i smeshcheniya i naoborot, prichem dazhe v odnom i tom zhe utverzhdenii. Imeetsya odno razlichie mezhdu imenem massiva i ukazatelem, kotoroe neobhodimo imet' v vidu. ukazatel' yavlyaetsya peremen- noj, tak chto operacii PA=A i PA++ imeyut smysl. No imya massi- va yavlyaetsya konstantoj, a ne peremennoj: konstrukcii tipa A=PA ili A++,ili P=&A budut nezakonnymi. Kogda imya massiva peredaetsya funkcii, to na samom dele ej peredaetsya mestopolozhenie nachala etogo massiva. Vnutri vyzvannoj funkcii takoj argument yavlyaetsya tochno takoj zhe pe- remennoj, kak i lyubaya drugaya, tak chto imya massiva v kachestve argumenta dejstvitel'no yavlyaetsya ukazatelem, t.e. Peremen- noj, soderzhashchej adres. my mozhem ispol'zovat' eto obstoyatel'- stvo dlya napisaniya novogo varianta funkcii STRLEN, vychislyayu- shchej dlinu stroki. STRLEN(S) /* RETURN LENGTH OF STRING S */ CHAR *S; { INT N; FOR (N = 0; *S != '\0'; S++) N++; RETURN(N); } Operaciya uvelicheniya S sovershenno zakonna, poskol'ku eta peremennaya yavlyaetsya ukazatelem; S++ nikak ne vliyaet na sim- vol'nuyu stroku v obrativshejsya k STRLEN funkcii, a tol'ko uvelichivaet lokal'nuyu dlya funkcii STRLEN kopiyu adresa. Opi- saniya formal'nyh parametrov v opredelenii funkcii v vide CHAR S[]; CHAR *S; sovershenno ekvivalentny; kakoj vid opisaniya sleduet predpo- chest', opredelyaetsya v znachitel'noj stepeni tem, kakie vyra- zheniya budut ispol'zovany pri napisanii funkcii. Esli funkcii peredaetsya imya massiva, to v zavisimosti ot togo, chto udob- nee, mozhno polagat', chto funkciya operiruet libo s massivom, libo s ukazatelem, i dejstvovat' dalee sootvetvuyushchim obra- zom. Mozhno dazhe ispol'zovat' oba vida operacij, esli eto ka- zhetsya umestnym i yasnym. Mozhno peredat' funkcii chast' massiva, esli zadat' v ka- chestve argumenta ukazatel' nachala podmassiva. Naprimer, esli A - massiv, to kak F(&A[2]) kak i F(A+2) peredayut funkcii F adres elementa A[2], potomu chto i &A[2], i A+2 yavlyayutsya ukazatel'nymi vyrazheniyami, ssylayushchimisya na tretij element A. vnutri funkcii F opisaniya argumentov mogut prisutstvovat' v vide: F(ARR) INT ARR[]; { ... } ili F(ARR) INT *ARR; { ... } CHto kasaetsya funkcii F, to tot fakt, chto ee argument v dejs- tvitel'nosti ssylaetsya k chasti bol'shego massiva,ne imeet dlya nee nikakih posledstvij. 5.4. Adresnaya arifmetika Esli P yavlyaetsya ukazatelem, to kakov by ni byl sort ob容kta, na kotoryj on ukazyvaet, operaciya P++ uvelichivaet P tak, chto on ukazyvaet na sleduyushchij element nabora etih ob容ktov, a operaciya P +=I uvelichivaet P tak, chtoby on uka- zyval na element, otstoyashchij na I elementov ot tekushchego ele- menta.eti i analogichnye konstrukcii predstavlyayut soboj samye prostye i samye rasprostranennye formy arifmetiki ukazatelej ili adresnoj arifmetiki. YAzyk "C" posledovatelen i postoyanen v svoem podhode k adresnoj arifmetike; ob容dinenie v odno celoe ukazatelej, massivov i adresnoj arifmetiki yavlyaetsya odnoj iz naibolee sil'nyh storon yazyka. Davajte proillyustriruem nekotorye iz sootvetstvuyushchih vozmozhnostej yazyka na primere elementarnoj (no poleznoj, nesmotrya na svoyu prostotu) programmy rasprede- leniya pamyati. Imeyutsya dve funkcii: funkciya ALLOC(N) vozvra- shchaet v kachestve svoego znacheniya ukazatel' P, kotoryj ukazy- vaet na pervuyu iz N posledovatel'nyh simvol'nyh pozicij, ko- torye mogut byt' ispol'zovany vyzyvayushchej funkciyu ALLOC prog- rammoj dlya hraneniya simvolov; funkciya FREE(P) osvobozhdaet priobretennuyu takim obrazom pamyat', tak chto ee v dal'nejshem mozhno snova ispol'zovat'. programma yavlyaetsya "elementarnoj", potomu chto obrashcheniya k FREE dolzhny proizvodit'sya v poryadke, obratnom tomu, v kotorom proizvodilis' obrashcheniya k ALLOC. Takim obrazom, upravlyaemaya funkciyami ALLOC i FREE pamyat' yav- lyaetsya stekom ili spiskom, v kotorom poslednij vvodimyj ele- ment izvlekaetsya pervym. Standartnaya biblioteka yazyka "C" soderzhit analogichnye funkcii, ne imeyushchie takih ogranichenij, i, krome togo, v glave 8 my privedem uluchshennye varianty. Mezhdu tem, odnako, dlya mnogih prilozhenij nuzhna tol'ko trivi- al'naya funkciya ALLOC dlya raspredeleniya nebol'shih uchastkov pamyati neizvestnyh zaranee razmerov v nepredskazuemye momen- ty vremeni. Prostejshaya realizaciya sostoit v tom, chtoby funkciya raz- davala otrezki bol'shogo simvol'nogo massiva, kotoromu my prisvoili imya ALLOCBUF. |tot massiv yavlyaetsya sobstvennost'yu funkcij ALLOC i FREE. Tak kak oni rabotayut s ukazatelyami, a ne s indeksami massiva, nikakoj drugoj funkcii ne nuzhno znat' imya etogo massiva. On mozhet byt' opisan kak vneshnij staticheskij, t.e. On budet lokal'nym po otnosheniyu k ishodno- mu fajlu, soderzhashchemu ALLOC i FREE, i nevidimym za ego pre- delami. Pri prakticheskoj realizacii etot massiv mozhet dazhe ne imet' imeni; vmesto etogo on mozhet byt' poluchen v rezul'- tate zaprosa k operacionnoj sisteme na ukazatel' nekotorogo neimenovannogo bloka pamyati. Drugoj neobhodimoj informaciej yavlyaetsya to, kakaya chast' massiva ALLOCBUF uzhe ispol'zovana. My pol'zuemsya ukazatelem pervogo svobodnogo elementa, nazvannym ALLOCP. Kogda k funk- cii ALLOC obrashchayutsya za vydeleniem N simvolov, to ona prove- ryaet, dostatochno li ostalos' dlya etogo mesta v ALLOCBUF. Es- li dostatochno, to ALLOC vozvrashchaet tekushchee znachenie ALLOCP (t.e. Nachalo svobodnogo bloka), zatem uvelichivaet ego na N, s tem chtoby on ukazyval na sleduyushchuyu svobodnuyu oblast'. Fun- kciya FREE(P) prosto polagaet ALLOCP ravnym P pri uslovii, chto P ukazyvaet na poziciyu vnutri ALLOCBUF. DEFINE NULL 0 /* POINTER VALUE FOR ERROR REPORT */ DEFINE ALLOCSIZE 1000 /* SIZE OF AVAILABLE SPACE */ TATIC CHAR ALLOCBUF[ALLOCSIZE];/* STORAGE FOR ALLOC */ TATIC CHAR *ALLOCP = ALLOCBUF; /* NEXT FREE POSITION */ HAR *ALLOC(N) /* RETURN POINTER TO N CHARACTERS */ INT N; ( IF (ALLOCP + N <= ALLOCBUF + ALLOCSIZE) { ALLOCP += N; RETURN(ALLOCP - N); /* OLD P */ } ELSE /* NOT ENOUGH ROOM */ RETURN(NULL); ) REE(P) /* FREE STORAGE POINTED BY P */ HAR *P; ( IF (P >= ALLOCBUF && P < ALLOCBUF + ALLOCSIZE) ALLOCP = P; ) Dadim nekotorye poyasneniya. Voobshche govorya, ukazatel' mo- zhet byt' inicializirovan tochno tak zhe, kak i lyubaya drugaya peremennaya, hotya obychno edinstvennymi osmyslennymi znacheniya- mi yavlyayutsya NULL (eto obsuzhdaetsya nizhe) ili vyrazhenie, vklyu- chayushchee adresa ranee opredelennyh dannyh sootvetstvuyushchego ti- pa. Opisanie STATIC CHAR *ALLOCP = ALLOCBUF; opredelyaet ALLOCP kak ukazatel' na simvoly i inicializiruet ego tak, chtoby on ukazyval na ALLOCBUF, t.e. Na pervuyu svo- bodnuyu poziciyu pri nachale raboty programmy. Tak kak imya mas- siva yavlyaetsya adresom ego nulevogo elementa, to eto mozhno bylo by zapisat' v vide STATIC CHAR *ALLOCP = &ALLOCBUF[0]; ispol'zujte tu zapis', kotoraya vam kazhetsya bolee estestven- noj. S pomoshch'yu proverki IF (ALLOCP + N <= ALLOCBUF + ALLOCSIZE) vyyasnyaetsya, ostalos' li dostatochno mesta, chtoby udovletvo- rit' zapros na N simvolov. Esli dostatochno, to novoe znache- nie ALLOCP ne budet ukazyvat' dal'she, chem na poslednyuyu pozi- ciyu ALLOCBUF. Esli zapros mozhet byt' udovletvoren, to ALLOC vozvrashchaet obychnyj ukazatel' (obratite vnimanie na opisanie samoj funkcii). Esli zhe net, to ALLOC dolzhna vernut' nekoto- ryj priznak, govoryashchij o tom, chto bol'she mesta ne ostalos'. V yazyke "C" garantiruetsya, chto ni odin pravil'nyj ukazatel' dannyh ne mozhet imet' znachenie nul', tak chto vozvrashchenie nu- lya mozhet sluzhit' v kachestve signala o nenormal'nom sobytii, v dannom sluchae ob otsutstvii mesta. My, odnako, vmesto nulya pishem NULL, s tem chtoby bolee yasno pokazat', chto eto speci- al'noe znachenie ukazatelya. Voobshche govorya, celye ne mogut os- myslenno prisvaivat'sya ukazatelyam, a nul' - eto osobyj slu- chaj. Proverki vida IF (ALLOCP + N <= ALLOCBUF + ALOOCSIZE) i IF (P >= ALLOCBUF && P < ALLOCBUF + ALLOCSIZE) demonstriruyut neskol'ko vazhnyh aspektov arifmetiki ukazate- lej. Vo-pervyh , pri opredelennyh usloviyah ukazateli mozhno sravnivat'. Esli P i Q ukazyvayut na elementy odnogo i togo zhe massiva, to takie otnosheniya, kak <, >= i t.d., rabotayut nadlezhashchim obrazom. Naprimer, P < Q istinno, esli P ukazyvaet na bolee rannij element massiva, chem Q. Otnosheniya == i != tozhe rabotayut. Lyuboj ukazatel' mozh- no osmyslennym obrazom sravnit' na ravenstvo ili neravenstvo s NULL. No ni za chto nel'zya ruchat'sya, esli vy ispol'zuete sravneniya pri rabote s ukazatelyami, ukazyvayushchimi na raznye massivy. Esli vam povezet, to na vseh mashinah vy poluchite ochevidnuyu bessmyslicu. Esli zhe net, to vasha programma budet pravil'no rabotat' na odnoj mashine i davat' nepostizhimye re- zul'taty na drugoj. Vo-vtoryh, kak my uzhe videli, ukazatel' i celoe mozhno skladyvat' i vychitat'. Konstrukciya P + N podrazumevaet N-yj ob容kt za tem, na kotoryj P ukazyvaet v nastoyashchij moment. |to spravedlivo nezavisimo ot togo, na ka- koj vid ob容ktov P dolzhen ukazyvat'; kompilyator sam masshta- biruet N v sootvetstvii s opredelyaemym iz opisaniya P razme- rom ob容ktov, ukazyvaemyh s pomoshch'yu P. naprimer, na PDP-11 masshtabiruyushchij mnozhitel' raven 1 dlya CHAR, 2 dlya INT i SHORT, 4 dlya LONG i FLOAT i 8 dlya DOUBLE. Vychitanie ukazatelej tozhe vozmozhno: esli P i Q ukazyvayut na elementy odnogo i togo zhe massiva, to P-Q - kolichestvo elementov mezhdu P i Q. |tot fakt mozhno ispol'zovat' dlya na- pisaniya eshche odnogo varianta funkcii STRLEN: STRLEN(S) /* RETURN LENGTH OF STRING S */ CHAR *S; { CHAR *P = S; WHILE (*P != '\0') P++; RETURN(P-S); } Pri opisanii ukazatel' P v etoj funkcii inicializirovan posredstvom stroki S, v rezul'tate chego on ukazyvaet na per- vyj simvol stroki. V cikle WHILE po ocheredi proveryaetsya kazh- dyj simvol do teh por, poka ne poyavitsya simvol konca stroki \0. Tak kak znachenie \0 ravno nulyu, a WHILE tol'ko vyyasnyaet, imeet li vyrazhenie v nem znachenie 0, to v dannom sluchae yav- nuyu proverku mozhno opustit'. Takie cikly chasto zapisyvayut v vide WHILE (*P) P++; Tak kak P ukazyvaet na simvoly, to operator P++ peredvi- gaet P kazhdyj raz tak, chtoby on ukazyval na sleduyushchij sim- vol. V rezul'tate P-S daet chislo prosmotrennyh simvolov, t.e. Dlinu stroki. Arifmetika ukazatelej posledovatel'na: esli by my imeli delo s peremennymi tipa FLOAT, kotorye za- nimayut bol'she pamyati, chem peremennye tipa CHAR, i esli by P byl ukazatelem na FLOAT, to operator P++ peredvinul by P na sleduyushchee FLOAT. takim obrazom, my mogli by napisat' drugoj variant funkcii ALLOC, raspredelyayushchej pamyat' dlya FLOAT, vmesto CHAR, prosto zameniv vsyudu v ALLOC i FREE opisatel' CHAR na FLOAT. Vse dejstviya s ukazatelyami avtomaticheski uchi- tyvayut razmer ob容ktov, na kotorye oni ukazyvayut, tak chto bol'she nichego menyat' ne nado. Za isklyucheniem upomyanutyh vyshe operacij (slozhenie i vy- chitanie ukazatelya i celogo, vychitanie i sravnenie dvuh uka- zatelej), vsya ostal'naya arifmetika ukazatelej yavlyaetsya neza- konnoj. Zapreshcheno skladyvat' dva ukazatelya, umnozhat', de- lit', sdvigat' ili maskirovat' ih, a takzhe pribavlyat' k nim peremennye tipa FLOAT ili DOUBLE. 5.5. Ukazateli simvolov i funkcii Strochnaya konstanta, kak, naprimer, "I AM A STRING" yavlyaetsya massivom simvolov. Kompilyator zavershaet vnutrennee predstavlenie takogo massiva simvolom \0, tak chto programmy mogut nahodit' ego konec. Takim obrazom, dlina massiva v pa- myati okazyvaetsya na edinicu bol'she chisla simvolov mezhdu dvojnymi kavychkami. Po-vidimomu chashche vsego strochnye konstanty poyavlyayutsya v kachestve argumentov funkcij, kak, naprimer, v PRINTF ("HELLO, WORLD\N"); kogda simvol'naya stroka, podobnaya etoj, poyavlyaetsya v prog- ramme, to dostup k nej osushchestvlyaetsya s pomoshch'yu ukazatelya simvolov; funkciya PRINTF fakticheski poluchaet ukazatel' sim- vol'nogo massiva. Konechno, simvol'nye massivy ne obyazany byt' tol'ko argu- mentami funkcij. Esli opisat' MESSAGE kak CHAR *MESSAGE; to v rezul'tate operatora MESSAGE = "NOW IS THE TIME"; peremennaya MESSAGE stanet ukazatelem na fakticheskij massiv simvolov. |to ne kopirovanie stroki; zdes' uchastvuyut tol'ko ukazateli. v yazyke "C" ne predusmotreny kakie-libo operacii dlya obrabotki vsej stroki simvolov kak celogo. My proillyustriruem drugie aspekty ukazatelej i massivov, razbiraya dve poleznye funkcii iz standartnoj biblioteki vvo- da-vyvoda, kotoraya budet rassmotrena v glave 7. Pervaya funkciya - eto STRCPY(S,T), kotoraya kopiruet stro- ku t v stroku S. Argumenty napisany imenno v etom poryadke po analogii s operaciej prisvaivaniya, kogda dlya togo, chtoby prisvoit' T k S obychno pishut S = T snachala privedem versiyu s massivami: STRCPY(S, T) /* COPY T TO S */ CHAR S[], T[]; { INT I; I = 0; WHILE ((S[I] = T[I]) != '\0') I++; } Dlya sopostavleniya nizhe daetsya variant STRCPY s ukazate- lyami. STRCPY(S, T) /* COPY T TO S; POINTER VERSION 1 */ CHAR *S, *T; { WHILE ((*S = *T) != '\0') { S++; T++; } } Tak kak argumenty peredayutsya po znacheniyu, funkciya STRCPY mozhet ispol'zovat' S i T tak, kak ona pozhelaet. Zdes' oni s udobstvom polagayutsya ukazatelyami, kotorye peredvigayutsya vdol' massivov, po odnomu simvolu za shag, poka ne budet sko- pirovan v S zavershayushchij v T simvol \0. Na praktike funkciya STRCPY byla by zapisana ne tak, kak my pokazali vyshe. Vot vtoraya vozmozhnost': STRCPY(S, T) /* COPY T TO S; POINTER VERSION 2 */ CHAR *S, *T; { WHILE ((*S++ = *T++) != '\0') ; } Zdes' uvelichenie S i T vneseno v proverochnuyu chast'. Zna- cheniem *T++ yavlyaetsya simvol, na kotoryj ukazyval T do uveli- cheniya; postfiksnaya operaciya ++ ne izmenyaet T, poka etot sim- vol ne budet izvlechen. Tochno tak zhe etot simvol pomeshchaetsya v staruyu poziciyu S, do togo kak S budet uvelicheno. Konechnyj rezul'tat zaklyuchaetsya v tom, chto vse simvoly, vklyuchaya zaver- shayushchij \0, kopiruyutsya iz T v S. I kak poslednee sokrashchenie my opyat' otmetim, chto sravne- nie s \0 yavlyaetsya izlishnim, tak chto funkciyu mozhno zapisat' v vide STRCPY(S, T) /* COPY T TO S; POINTER VERSION 3 */ CHAR *S, *T; { WHILE (*S++ = *T++) ; } hotya s pervogo vzglyada eta zapis' mozhet pokazat'sya zagadoch- noj, ona daet znachitel'noe udobstvo. |toj idiomoj sleduet ovladet' uzhe hotya by potomu, chto vy s nej budete chasto vst- rechat'sya v "C"-programmah. Vtoraya funkciya - STRCMP(S, T), kotoraya sravnivaet sim- vol'nye stroki S i t, vozvrashchaya otricatel'noe, nulevoe ili polozhitel'noe znachenie v sootvetstvii s tem, men'she, ravno ili bol'she leksikograficheski S, chem T. Vozvrashchaemoe znachenie poluchaetsya v rezul'tate vychitaniya simvolov iz pervoj pozi- cii, v kotoroj S i T ne sovpadayut. STRCMP(S, T) /* RETURN <0 IF S<T, 0 IF S==T, >0 IF S>T */ CHAR S[], T[]; { INT I; I = 0; WHILE (S[I] == T[I]) IF (S[I++] == '\0') RETURN(0); RETURN(S[I]-T[I]); } Vot versiya STRCMP s ukazatelyami: STRCMP(S, T) /* RETURN <0 IF S<T, 0 IF S==T, >0 IF S>T */ CHAR *S, *T; { FOR ( ; *S == *T; S++, T++) IF (*S == '\0') RETURN(0); RETURN(*S-*T); } tak kak ++ i -- mogut byt' kak postfiksnymi, tak i prefiksnymi operaciyami, vstrechayutsya drugie kombinacii * i ++ i --, hotya i menee chasto. Naprimer *++P uvelichivaet P do izvlecheniya simvola, na kotoryj ukazyvaet P, a *--P snachala umen'shaet P. Uprazhnenie 5-2 --------------- Napishite variant s ukazatelyami funkcii STRCAT iz glavy 2: STRCAT(S, T) kopiruet stroku T v konec S. Uprazhnenie 5-3 --------------- Napishite makros dlya STRCPY. Uprazhnenie 5-4 -------------- Perepishite podhodyashchie programmy iz predydushchih glav i up- razhnenij, ispol'zuya ukazateli vmesto indeksacii massivov. Horoshie vozmozhnosti dlya etogo predostavlyayut funkcii GETLINE /glavy 1 i 4/, ATOI, ITOA i ih varianty /glavy 2, 3 i 4/, REVERSE /glava 3/, INDEX i GETOP /glava 4/. 5.6. Ukazateli - ne celye Vy, vozmozhno, obratili vnimanie v predydushchih "s"-prog- rammah na dovol'no neprinuzhdennoe otnoshenie k kopirovaniyu ukazatelej. V obshchem eto verno, chto na bol'shinstve mashin uka- zatel' mozhno prisvoit' celomu i peredat' ego obratno, ne iz- meniv ego; pri etom ne proishodit nikakogo masshtabirovaniya ili preobrazovaniya i ni odin bit ne teryaetsya. k sozhaleniyu, eto vedet k vol'nomu obrashcheniyu s funkciyami, vozvrashchayushchimi ukazateli, kotorye zatem prosto peredayutsya drugim funkciyam, - neobhodimye opisaniya ukazatelej chasto opuskayutsya. Rassmot- rim, naprimer, funkciyu STRSAVE(S), kotoraya kopiruet stroku S v nekotoroe mesto dlya hraneniya, vydelyaemoe posredstvom obra- shcheniya k funkcii ALLOC, i vozvrashchaet ukazatel' na eto mesto. Pravil'no ona dolzhna byt' zapisana tak: CHAR *STRSAVE(S) /* SAVE STRING S SOMEWHERE */ CHAR *S; { CHAR *P, *ALLOC(); IF ((P = ALLOC(STRLEN(S)+1)) != NULL) STRCPY(P, S); RETURN(P); } na praktike sushchestvuet sil'noe stremlenie opuskat' opisaniya: *STRSAVE(S) /* SAVE STRING S SOMEWHERE */ { CHAR *P; IF ((P = ALLOC(STRLEN(S)+1)) != NULL) STRCPY(P, S); RETURN(P); } |ta programma budet pravil'no rabotat' na mnogih mashi- nah, potomu chto po umolchaniyu funkcii i argumenty imeyut tip INT, a ukazatel' i celoe obychno mozhno bezopasno peresylat' tuda i obratno. Odnako takoj stil' programmirovaniya v svoem sushchestve yavlyaetsya riskovannym, poskol'ku zavisit ot detalej realizacii i arhitektury mashiny i mozhet privesti k nepra- vil'nym rezul'tatam na konkretnom ispol'zuemom vami kompilya- tore. Razumnee vsyudu ispol'zovat' polnye opisaniya. (Otladoch- naya programma LINT predupredit o takih konstrukciyah, esli oni po neostorozhnosti vse zhe poyavyatsya). 5.7. Mnogomernye massivy V yazyke "C" predusmotreny pryamougol'nye mnogomernye mas- sivy, hotya na praktike sushchestvuet tendenciya k ih znachitel'no bolee redkomu ispol'zovaniyu po sravneniyu s massivami ukaza- telej. V etom razdele my rassmotrim nekotorye ih svojstva. Rassmotrim zadachu preobrazovaniya dnya mesyaca v den' goda i naoborot. Naprimer, 1-oe marta yavlyaetsya 60-m dnem neviso- kosnogo goda i 61-m dnem visokosnogo goda. Davajte vvedem dve funkcii dlya vypolneniya etih preobrazovanij: DAY_OF_YEAR preobrazuet mesyac i den' v den' goda, a MONTH_DAY preobrazu- et den' goda v mesyac i den'. Tak kak eta poslednyaya funkciya vozvrashchaet dva znacheniya, to argumenty mesyaca i dnya dolzhny byt' ukazatelyami: MONTH_DAY(1977, 60, &M, &D) Polagaet M ravnym 3 i D ravnym 1 (1-oe marta). Obe eti funkcii nuzhdayutsya v odnoj i toj zhe informacion- noj tablice, ukazyvayushchej chislo dnej v kazhdom mesyace. Tak kak chislo dnej v mesyace v visokosnom i v nevisokosnom godu otli- chaetsya, to proshche predstavit' ih v vide dvuh strok dvumernogo massiva, chem pytat'sya proslezhivat' vo vremya vychislenij, chto imenno proishodit v fevrale. Vot etot massiv i vypolnyayushchie eti preobrazovaniya funkcii: STATIC INT DAY_TAB[2][13] = { (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) }; DAY_OF_YEAR(YEAR, MONTH, DAY) /* SET DAY OF YEAR */ INT YEAR, MONTH, DAY; /* FROM MONTH & DAY */ { INT I, LEAP; LEAP = YEAR%4 == 0 && YEAR%100 != 0 \!\! YEAR%400 == 0; FOR (I = 1; I < MONTH; I++) DAY += DAY_TAB[LEAP][I]; RETURN(DAY); { MONTH_DAY(YEAR, YEARDAY, PMONTH, PDAY) /*SET MONTH,DAY */ INT YEAR, YEARDAY, *PMONTH, *PDAY; /* FROM DAY OF YEAR */ { LEAP = YEAR%4 == 0 && YEAR%100 != 0 \!\! YEAR%400 == 0; FOR (I = 1; YEARDAY > DAY_TAB[LEAP][I]; I++) YEARDAY -= DAY_TAB[LEAP][I]; *PMONTH = I; *PDAY = YEARDAY; } Massiv DAY_TAB dolzhen byt' vneshnim kak dlya DAY_OF_YEAR, tak i dlya MONTH_DAY, poskol'ku on ispol'zuetsya obeimi etimi fun- kciyami. Massiv DAY_TAB yavlyaetsya pervym dvumernym massivom, s ko- torym my imeem delo. Po opredeleniyu v "C" dvumernyj massiv po sushchestvu yavlyaetsya odnomernym massivom, kazhdyj element ko- torogo yavlyaetsya massivom. Poetomu indeksy zapisyvayutsya kak DAY_TAB[I][J] a ne DAY_TAB [I, J] kak v bol'shinstve yazykov. V ostal'nom s dvumernymi massivami mozhno v osnovnom obrashchat'sya takim zhe obrazom, kak v drugih yazykah. |lementy hranyatsya po strokam, t.e. Pri obrashchenii k elementam v poryadke ih razmeshcheniya v pamyati bystree vsego iz- menyaetsya samyj pravyj indeks. Massiv inicializiruetsya s pomoshch'yu spiska nachal'nyh zna- chenij, zaklyuchennyh v figurnye skobki; kazhdaya stroka dvumer- nogo massiva inicializiruetsya sootvetstvuyushchim podspiskom. My pomestili v nachalo massiva DAY_TAB stolbec iz nulej dlya to- go, chtoby nomera mesyacev izmenyalis' estestvennym obrazom ot 1 do 12, a ne ot 0 do 11. Tak kak za ekonomiyu pamyati u nas poka ne nagrazhdayut, takoj sposob proshche, chem podgonka indek- sov. Esli dvumernyj massiv peredaetsya funkcii, to opisanie sootvetstvuyushchego argumenta funkcii dolzhno soderzhat' koliches- tvo stolbcov; kolichestvo strok nesushchestvenno, poskol'ku, kak i prezhde, fakticheski peredaetsya ukazatel'. V nashem konkret- nom sluchae eto ukazatel' ob容ktov, yavlyayushchihsya massivami iz 13 chisel tipa INT. Takim obrazom, esli by trebovalos' pere- dat' massiv DAY_TAB funkcii F, to opisanie v F imelo by vid: F(DAY_TAB) INT DAY_TAB[2][13]; { ... } Tak kak kolichestvo strok yavlyaetsya nesushchestvennym, to opisa- nie argumenta v F moglo by byt' takim: INT DAY_TAB[][13]; ili takim INT (*DAY_TAB)[13]; v kotorm govoritsya, chto argument yavlyaetsya ukazatelem massiva iz 13 celyh. Kruglye skobki zdes' neobhodimy, potomu chto kvadratnye skobki [] imeyut bolee vysokij uroven' starshinst- va, chem *; kak my uvidim v sleduyushchem razdele, bez kruglyh skobok INT *DAY_TAB[13]; yavlyaetsya opisaniem massiva iz 13 ukazatelej na celye. 5.8. Massivy ukazatelej; ukazateli ukazatelej Tak kak ukazateli sami yavlyayutsya peremennymi, to vy vpol- ne mogli by ozhidat' ispol'zovaniya massiva ukazatelej. |to dejstvitel'no tak. My proillyustriruem eto napisaniem prog- rammy sortirovki v alfavitnom poryadke nabora tekstovyh strok, predel'no uproshchennogo varianta utility SORT operaci- onnoj sistem UNIX. V glave 3 my priveli funkciyu sortirovki po shellu, koto- raya uporyadochivala massiv celyh. |tot zhe algoritm budet rabo- tat' i zdes', hotya teper' my budem imet' delo so strochkami teksta razlichnoj dliny, kotorye, v otlichie ot celyh, nel'zya sravnivat' ili peremeshchat' s pomoshch'yu odnoj operacii. My nuzh- daemsya v takom predstavlenii dannyh, kotoroe by pozvolyalo udobno i effektivno obrabatyvat' stroki teksta peremennoj dliny. Zdes' i voznikayut massivy ukazatelej. Esli podlezhashchie sortirovke sroki hranyatsya odna za drugoj v dlinnom simvol'- nom massive (upravlyaemom, naprimer, funkciej ALLOC), to k kazhdoj stroke mozhno obratit'sya s pomoshch'yu ukazatelya na ee pervyj simvol. Sami ukazateli mozhno hranit' v massive. dve stroki mozhno sravnit', peredav ih ukazateli funkcii STRCMP. Esli dve raspolozhennye v nepravil'nom poryadke stroki dolzhny byt' perestavleny, to fakticheski perestavlyayutsya ukazateli v massive ukazatelej, a ne sami teksty strok. |tim isklyuchayutsya srazu dve svyazannye problemy: slozhnogo upravleniya pamyat'yu i bol'shih dopolnitel'nyh zatrat na fakticheskuyu perestanovku strok. Process sortirovki vklyuchaet tri shaga: chtenie vseh strok vvoda ih sortirovka vyvod ih v pravil'nom poryadke Kak obychno, luchshe razdelit' programmu na neskol'ko funkcij v sootvetstvii s estestvennym deleniem zadachi i vydelit' vedu- shchuyu funkciyu, upravlyayushchuyu rabotoj vsej programmy. Davajte otlozhim na nekotoroe vremya rassmotrenie shaga sorti- rovki i sosredotochimsya na strukture dannyh i vvode-vyvode. Funkciya, osushchestvlyayushchaya vvod, dolzhna izvlech' simvoly kazhdoj stroki, zapomnit' ih i postroit' massiv ukazatelej strok. Ona dolzhna takzhe podschitat' chislo strok vo vvode, tak kak eta informaciya neobhodima pri sortirovke i vyvode. tak kak funkciya vvoda v sostoyanii spravit'sya tol'ko s konechnym chis- lom vvodimyh strok, v sluchae slishkom bol'shogo ih chisla ona mozhet vozvrashchat' nekotoroe chislo, otlichnoe ot vozmozhnogo chisla strok, naprimer -1. Funkciya osushchestvlyayushchaya vyvod, dol- zhna pechatat' stroki v tom poryadke, v kakom oni poyavlyayutsya v massive ukazatelej. #DEFINE NULL 0 #DEFINE LINES 100 /* MAX LINES TO BE SORTED */ MAIN() /* SORT INPUT LINES */ \( CHAR *LINEPTR[LINES]; /*POINTERS TO TEXT LINES */ INT NLINES; /* NUMBER OF INPUT LINES READ */ IF ((NLINES = READLINES(LINEPTR, LINES)) >= 0) \( SORT(LINEPTR, NLINES); WRITELINES(LINEPTR, NLINES); \) ELSE PRINTF("INPUT TOO BIG TO SORT\N"); \) #DEFINE MAXLEN 1000 READLINES(LINEPTR, MAXLINES) /* READ INPUT LINES */ CHAR *LINEPTR[]; /* FOR SORTING */ INT MAXLINES; \( INT LEN, NLINES; CHAR *P, *ALLOC(), LINE[MAXLEN]; NLINES = 0; WHILE ((LEN = GETLINE(LINE, MAXLEN)) > 0) IF (NLINES >= MAXLINES) RETURN(-1); ELSE IF ((P = ALLOC(LEN)) == NULL) RETURN (-1); ELSE \( LINE[LEN-1] = '\0'; /* ZAP NEWLINE */ STRCPY(P,LINE); LINEPTR[NLINES++] = P; \) RETURN(NLINES); \) Simvol novoj stroki v konce kazhdoj stroki udalyaetsya, tak chto on nikak ne budet vliyat' na poryadok, v kotorom sortiruyutsya stroki. WRITELINES(LINEPTR, NLINES) /* WRITE OUTPUT LINES */ CHAR *LINEPTR[]; INT NLINES; \( INT I; FOR (I = 0; I < NLINES; I++) PRINTF("%S\N", LINEPTR[I]); \) Sushchestvenno novym v etoj programme yavlyaetsya opisanie CHAR *LINEPTR[LINES]; kotoroe soobshchaet, chto LINEPTR yavlyaetsya massivom iz LINES elementov, kazhdyj iz kotoryh - ukazatel' na peremennye tipa CHAR. |to oznachaet, chto LINEPTR[I] - ukazatel' na simvoly, a *LINEPTR[I] izvlekaet simvol. Tak kak sam LINEPTR yavlyaetsya massivom, kotoryj peredaet- sya funkcii WRITELINES, s nim mozhno obrashchat'sya kak s ukazate- lem tochno takim zhe obrazom, kak v nashih bolee rannih prime- rah. Togda poslednyuyu funkciyu mozhno perepisat' v vide: WRITELINES(LINEPTR, NLINES) /* WRITE OUTPUT LINES */ CHAR *LINEPTR[]; INT NLINES; \( INT I; WHILE (--NLINES >= 0) PRINTF("%S\N", *LINEPTR++); \) zdes' *LINEPTR snachala ukazyvaet na pervuyu stroku; kazhdoe uvelichenie peredvigaet ukazatel' na sleduyushchuyu stroku, v to vremya kak NLINES ubyvaet do nulya. Spravivshis' s vvodom i vyvodom, my mozhem perejti k sor- tirovke. programma sortirovki po shellu iz glavy 3 trebuet ochen' nebol'shih izmenenij: dolzhny byt' modificirovany opisa- niya, a operaciya sravneniya vydelena v otdel'nuyu funkciyu. Os- novnoj algoritm ostaetsya tem zhe samym, i eto daet nam opre- delennuyu uverennost', chto on po-prezhnemu budet rabotat'. SORT(V, N) /* SORT STRINGS V[0] ... V[N-1] */ CHAR *V[]; /* INTO INCREASING ORDER */ INT N; \( INT GAP, I, J; CHAR *TEMP; FOR (GAP = N/2; GAP > 0; GAP /= 2) FOR (I = GAP; I < N; I++) FOR (J = I - GAP; J >= 0; J -= GAP) \( IF (STRCMP(V[J], V[J+GAP]) <= 0) BREAK; TEMP = V[J]; V[J] = V[J+GAP]; V[J+GAP] = TEMP; \) \) Tak kak kazhdyj otdel'nyj element massiva V (imya formal'nogo parametra, sootvetstvuyushchego LINEPTR) yavlyaetsya ukazatelem na simvoly, to i TEMP dolzhen byt' ukazatelem na simvoly, chtoby ih bylo mozhno kopirovat' drug v druga. My napisali etu programmu po vozmozhnosti bolee prosto s tem, chtoby pobystree poluchit' rabotayushchuyu programmu. Ona mog- la by rabotat' bystree, esli, naprimer, vvodit' stroki ne- posredstvenno v massiv, upravlyaemyj funkciej READLINES, a ne kopirovat' ih v LINE, a zatem v skrytoe mesto s pomoshch'yu fun- kcii ALLOC. no my schitaem, chto budet razumnee pervonachal'nyj variant sdelat' bolee prostym dlya ponimaniya, a ob "effektiv- nosti" pozabotit'sya pozdnee. Vse zhe, po-vidimomu, sposob, pozvolyayushchij dobit'sya zametnogo uskoreniya raboty programmy sostoit ne v isklyuchenii lishnego kopirovaniya vvodimyh strok. Bolee veroyatno, chto sushchestvennoj raznicy mozhno dostich' za schet zameny sortirovki po shellu na nechto luchshee, naprimer, na metod bystroj sortirovki. V glave 1 my otmechali, chto poskol'ku v ciklah WHILE i FOR proverka osushchestvlyaetsya do togo, kak telo cikla vypol- nitsya hotya by odin raz, eti cikly okazyvayutsya udobnymi dlya obespecheniya pravil'noj raboty programmy pri granichnyh znache- niyah, v chastnosti, kogda vvoda voobshche net. Ochen' polezno prosmotret' vse funkcii programmy sortirovki, razbirayas', chto proishodit, esli vvodimyj tekst otsutstvuet. Uprazhnenie 5-5 -------------- Perepishite funkciyu READLINES takim obrazom, chtoby ona pomeshchala stroki v massiv, predostavlyaemyj funkciej MAIN, a ne v pamyat', upravlyaemuyu obrashcheniyami k funkcii ALLOC. Na- skol'ko bystree stala programma? 5.9. Inicializaciya massivov ukazatelej Rassmotrim zadachu napisaniya funkcii MONTH_NAME(N), koto- raya vozvrashchaet ukazatel' na simvol'nuyu stroku, soderzhashchuyu imya N-go mesyaca. |to ideal'naya zadacha dlya primeneniya vnut- rennego staticheskogo massiva. Funkciya MONTH_NAME soderzhit lokal'nyj massiv simvol'nyh strok i pri obrashchenii k nej voz- vrashchaet ukazatel' nuzhnoj stroki. Tema nastoyashchego razdela - kak inicializirovat' etot massiv imen. CHAR *MONTH_NAME(N) /* RETURN NAME OF N-TH MONTH */ INT N; \( STATIC CHAR *NAME[] = \( "ILLEGAL MONTH", "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUN", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" \); RETURN ((N < 1 \!\! N > 12) ? NAME[0] : NAME[N]); \) Opisanie massiva ukazatelej na simvoly NAME tochno takoe zhe, kak analogichnoe opisanie LINEPTR v primere s sortirovkoj. Inicializatorom yavlyaetsya prosto spisok simvol'nyh strok; kazhdaya stroka prisvaivaetsya sootvetstvuyushchej pozicii v massi- ve. Bolee tochno, simvoly I-oj stroki pomeshchayutsya v kakoe-to inoe mesto, a ee ukazatel' hranitsya v NAME[I]. Poskol'ku razmer massiva NAME ne ukazan, kompilyator sam podschityvaet kolichestvo inicializatorov i sootvetstvenno ustanavlivaet pravil'noe chislo. 5.10. Ukazateli i mnogomernye massivy Nachinayushchie izuchat' yazyk "s" inogda stanovyatsya v tupik pered voprosom o razlichii mezhdu dvumernym massivom i massi- vom ukazatelej, takim kak NAME v privedennom vyshe primere. Esli imeyutsya opisaniya INT A[10][10]; INT *B[10]; to A i B mozhno ispol'zovat' shodnym obrazom v tom smysle, chto kak A[5][5], tak i B[5][5] yavlyayutsya zakonnymi ssylkami na otdel'noe chislo tipa INT. No A - nastoyashchij massiv: pod nego otvoditsya 100 yacheek pamyati i dlya nahozhdeniya lyubogo uka- zannogo elementa provodyatsya obychnye vychisleniya s pryamougol'- nymi indeksami. Dlya B, odnako, opisanie vydelyaet tol'ko 10 ukazatelej; kazhdyj ukazatel' dolzhen byt' ustanovlen tak, chtoby on ukazyval na massiv celyh. esli predpolozhit', chto kazhdyj iz nih ukazyvaet na massiv iz 10 elementov, to togda gde-to budet otvedeno 100 yacheek pamyati plyus eshche desyat' yacheek dlya ukazatelej. Takim obrazom, massiv ukazatelej ispol'zuet neskol'ko bol'shij ob容m pamyati i mozhet trebovat' nalichie yav- nogo shaga inicializacii. No pri etom voznikayut dva preimu- shchestva: dostup k elementu osushchestvlyaetsya kosvenno cherez uka- zatel', a ne posredstvom umnozheniya i slozheniya, i stroki mas- siva mogut imet' razlichnye dliny. |to oznachaet, chto kazhdyj element B ne dolzhen obyazatel'no ukazyvat' na vektor iz 10 elementov; nekotorye mogut ukazyvat' na vektor iz dvuh ele- mentov, drugie - iz dvadcati, a tret'i mogut voobshche ni na chto ne ukazyvat'. Hotya my veli eto obsuzhdenie v terminah celyh, nesomnen- no, chashche vsego massivy ukazatelej ispol'zuyutsya tak, kak my prodemonstrirovali na funkcii MONTH_NAME, - dlya hraneniya simvol'nyh strok razlichnoj dliny. Uprazhnenie 5-6 -------------- Perepishite funkcii DAY_OF_YEAR i MONTH_DAY, ispol'zuya vmesto indeksacii ukazateli. 5.11. Komandnaya stroka argumentov Sistemnye sredstva, na kotorye opiraetsya realizaciya yazy- ka "s", pozvolyayut peredavat' komandnuyu stroku argumentov ili parametrov nachinayushchej vypolnyat'sya programme. Kogda funkciya MAIN vyzyvaetsya k ispolneniyu, ona vyzyvaetsya s dvumya argu- mentami. Pervyj argument (uslovno nazyvaemyj ARGC) ukazyvaet chislo argumentov v komandnoj stroke, s kotorymi proishodit obrashchenie k programme; vtoroj argument (ARGV) yavlyaetsya uka- zatelem na massiv simvol'nyh strok, soderzhashchih eti argumen- ty, po odnomu v stroke. Rabota s takimi strokami - eto obych- noe ispol'zovanie mnogourovnevyh ukazatelej. Samuyu prostuyu illyustraciyu etoj vozmozhnosti i neobhodimyh pri etom opisanij daet programma ECHO, kotoraya prosto pecha- taet v odnu stroku argumenty komandnoj stroki, razdelyaya ih probelami. Takim obrazom, esli dana komanda ECHO HELLO, WORLD to vyhodom budet HELLO, WORLD po soglasheniyu ARGV[0] yavlyaetsya imenem, po kotoromu vyzyvaet- sya programma, tak chto ARGC po men'shej mere raven 1. V prive- dennom vyshe primere ARGC raven 3, a ARGV[0], ARGV[1] i ARGV[2] ravny sootvetstvenno "ECHO", "HELLO," i "WORLD". Pervym fakticheskim agumentom yavlyaetsya ARGV[1], a poslednim - ARGV[ARGC-1]. Esli ARGC raven 1, to za imenem programmy ne sleduet nikakoj komandnoj stroki argumentov. Vse eto pokaza- no v ECHO: MAIN(ARGC, ARGV) /* ECHO ARGUMENTS; 1ST VERSION */ INT ARGC; CHAR *ARGV[]; \( INT I; FOR (I = 1; I < ARGC; I++) PRINTF("%S%C", ARGV[I], (I<ARGC-1) ? ' ' : '\N'); \) Poskol'ku ARGV yavlyaetsya ukazatelem na massiv ukazatelej, to sushchestvuet neskol'ko sposobov napisaniya etoj programmy, is- pol'zuyushchih rabotu s ukazatelem, a ne s indeksaciej massiva. My prodemonstriruem dva varianta. MAIN(ARGC, ARGV) /* ECHO ARGUMENTS; 2ND VERSION */ INT ARGC; CHAR *ARGV[]; \( WHILE (--ARGC > 0) PRINTF("%S%C",*++ARGV, (ARGC > 1) ? ' ' : '\N'); \) Tak kak ARGV yavlyaetsya ukazatelem na nachalo massiva strok-ar- gumentov, to, uvelichiv ego na 1 (++ARGV), my vynuzhdaem ego ukazyvat' na podlinnyj argument ARGV[1], a ne na ARGV[0]. Kazhdoe posleduyushchee uvelichenie peredvigaet ego na sleduyushchij argument; pri etom *ARGV stanovitsya ukazatelem na etot argu- ment. odnovremenno velichina ARGC umen'shaetsya; kogda ona ob- ratitsya v nul', vse argumenty budut uzhe napechatany. Drugoj variant: MAIN(ARGC, ARGV) /* ECHO ARGUMENTS; 3RD VERSION */ INT ARGC; CHAR *ARGV[]; \( WHILE (--ARGC > 0) PRINTF((ARGC > 1) ? "%S" : "%S\N", *++ARGV); \) |ta versiya pokazyvaet, chto argument formata funkcii PRINTF mozhet byt' vyrazheniem, tochno tak zhe, kak i lyuboj drugoj. Ta- koe ispol'zovanie vstrechaetsya ne ochen' chasto, no ego vse zhe stoit zapomnit'. Kak vtoroj primer, davajte vnesem nekotorye usovershenst- vovaniya v programmu otyskaniya zadannoj kombinacii simvolov iz glavy 4. Esli vy pomnite, my pomestili iskomuyu kombinaciyu gluboko vnutr' programmy, chto ochevidno yavlyaetsya sovershenno neudovletvoritel'nym. Sleduya utilite GREP sistemy UNIX, da- vajte izmenim programmu tak, chtoby eta kombinaciya ukazyva- las' v kachestve pervogo argumenta stroki. #DEFINE MAXLINE 1000 MAIN(ARGC, ARGV) /* FIND PATTERN FROM FIRST ARGUMENT */ INT ARGC; CHAR *ARGV[]; \( CHAR LINE[MAXLINE]; IF (ARGC != 2) PRINTF ("USAGE: FIND PATTERN\N"); ELSE WHILE (GETLINE(LINE, MAXLINE) > 0) IF (INDEX(LINE, ARGV[1] >= 0) PRINTF("%S", LINE); \) Teper' mozhet byt' razvita osnovnaya model', illyustriruyu- shchaya dal'nejshee ispol'zovanie ukazatelej. Predpolozhim, chto nam nado predusmotret' dva neobyazatel'nyh argumenta. Odin utverzhdaet: "napechatat' vse stroki za isklyucheniem teh, koto- rye soderzhat dannuyu kombinaciyu", vtoroj glasit: "pered kazh- doj vyvodimoj strokoj dolzhen pechatat'sya ee nomer". Obshcheprinyatym soglasheniem v "s"-programmah yavlyaetsya to, chto argument, nachinayushchijsya so znaka minus, vvodit neobyaza- tel'nyj priznak ili parametr. Esli my, dlya togo, chtoby soob- shchit' ob inversii, vyberem -X, a dlya ukazaniya o numeracii nuzhnyh strok vyberem -N("nomer"), to komanda FIND -X -N THE pri vhodnyh dannyh NOW IS THE TIME FOR ALL GOOD MEN TO COME TO THE AID OF THEIR PARTY. Dolzhna vydat' 2:FOR ALL GOOD MEN Nuzhno, chtoby neobyazatel'nye argumenty mogli raspolagat'- sya v proizvol'nom poryadke, i chtoby ostal'naya chast' programmy ne zavisela ot kolichestva fakticheski prisutstvuyushchih argumen- tov. v chastnosti, vyzov funkcii INDEX ne dolzhen soderzhat' ssylku na ARGV[2], kogda prisutstvuet odin neobyazatel'nyj argument, i na ARGV[1], kogda ego net. Bolee togo, dlya pol'- zovatelej udobno, chtoby neobyazatel'nye argumenty mozhno bylo ob容dinit' v vide: FIND -NX THE vot sama programma: #DEFINE MAXLINE 1000 MAIN(ARGC, ARGV) /* FIND PATTERN FROM FIRST ARGUMENT */ INT ARGC; CHAR *ARGV[]; \( CHAR LINE[MAXLINE], *S; LONG LINENO = 0; INT EXCEPT = 0, NUMBER = 0; WHILE (--ARGC > 0 && (*++ARGV)[0] == '-') FOR (S = ARGV[0]+1; *S != '\0'; S++) SWITCH (*S) \( CASE 'X': EXCEPT = 1; BREAK; CASE 'N': NUMBER = 1; BREAK; DEFAULT: PRINTF("FIND: ILLEGAL OPTION %C\N", *S); ARGC = 0; BREAK; \) IF (ARGC != 1) PRINTF("USAGE: FIND -X -N PATTERN\N"); ELSE WHILE (GETLINe(LINE, MAXLINE) > 0) \( LINENO++; IF ((INDEX(LINE, *ARGV) >= 0) != EXCEPT) \ IF (NUMBER) PRINTF("%LD: ", LINENO); PRINTF("%S", LINE); \) \) \) Argument ARGV uvelichivaetsya pered kazhdym neobyazatel'nym argumentom, v to vremya kak argument ARGC umen'shaetsya. esli net oshibok, to v konce cikla velichina ARGC dolzhna ravnyat'sya 1, a *ARGV dolzhno ukazyvat' na zadannuyu kombinaciyu. Obratite vnimanie na to, chto *++ARGV yavlyaetsya ukazatelem argumentnoj stroki; (*++ARGV)[0] - ee pervyj simvol. Kruglye skobki zdes' neobhodimy, potomu chto bez nih vyrazhenie by prinyalo sovershenno otlichnyj (i nepravil'nyj) vid *++(ARGV[0]). Dru- goj pravil'noj formoj byla by **++ARGV. Uprazhnenie 5-7 -------------- Napishite programmu ADD, vychislyayushchuyu obratnoe pol'skoe vyrazhenie iz komandnoj stroki. Naprimer, ADD 2 3 4 + * vychislyaet 2*(3+4). Uprazhnenie 5-8 -------------- Modificirujte programmy ENTAB i DETAB (ukazannye v ka- chestve uprazhnenij v glave 1) tak, chtoby oni poluchali spisok tabulyacionnyh ostanovok v kachestve argumentov. Esli argumen- ty otsutstvuyut, ispol'zujte standartnuyu ustanovku tabulyacij. Uprazhnenie 5-9 -------------- Rasshir'te ENTAB i DETAB takim obrazom, chtoby oni vospri- nimali sokrashchennuyu notaciyu ENTAB M +N oznachayushchuyu tabulyacionnye ostanovki cherez kazhdye N stolbcov, nachinaya so stolbca M. Vyberite udobnoe (dlya pol'zovatelya) povedenie funkcii po umolchaniyu. Uprazhnenie 5-10 --------------- Napishite programmu dlya funkcii TAIL, pechatayushchej posled- nie N strok iz svoego fajla vvoda. Pust' po umolchaniyu N rav- no 10, no eto chislo mozhet byt' izmeneno s pomoshch'yu neobyaza- tel'nogo argumenta, tak chto TAIL -N pechataet poslednie N strok. programma dolzhna dejstvovat' ra- cional'no, kakimi by nerazumnymi ni byli by vvod ili znache- nie N. Sostav'te programmu tak, chtoby ona optimal'nym obra- zom ispol'zovala dostupnuyu pamyat': stroki dolzhny hranit'sya, kak v funkcii SORT, a ne v dvumernom massive fiksirovannogo razmera. 5.12. Ukazateli na funkcii V yazyke "s" sami funkcii ne yavlyayutsya peremennymi, no imeetsya vozmozhnost' opredelit' ukazatel' na funkciyu, kotoryj mozhno obrabatyvat', peredavat' drugim funkciyam, pomeshchat' v massivy i t.d. My proillyustriruem eto, provedya modifikaciyu napisannoj ranee programmy sortirovki tak, chtoby pri zadanii neobyazatel'nogo argumenta -N ona by sortirovala stroki vvoda chislenno, a ne leksikograficheski. Sortirovka chasto sostoit iz treh chastej - sravneniya, ko- toroe opredelyaet uporyadochivanie lyuboj pary ob容ktov, peres- tanovki, izmenyayushchej ih poryadok, i algoritma sortirovki, osu- shchestvlyayushchego sravneniya i perestanovki do teh por, poka ob容kty ne raspolozhatsya v nuzhnom poryadke. Algoritm sortirov- ki ne zavisit ot operacij sravneniya i perestanovki, tak chto, peredavaya v nego razlichnye fu