Ocenite etot tekst:




                           YAZYK S


                       B.V. Kernigan,
                         D.M. Richi.




    YAzyk "C"(proiznositsya "si") - eto universal'nyj yazyk
programmirovaniya, dlya kotorogo harakterny ekonomichnost' vyra-
zheniya, sovremennyj potok upravleniya i struktury dannyh, boga-
tyj nabor operatorov. YAzyk "C" ne yavlyaetsya ni yazykom "ochen'
vysokogo urovnya", ni "bol'shim" yazykom, i ne prednaznachaetsya
dlya nekotoroj special'noj oblasti primeneniya. No otsutstvie
ogranichenij i obshchnost' yazyka delayut ego bolee udobnym i ef-
fektivnym dlya mnogih zadach, chem yazyki, predpolozhitel'no bolee
moshchnye.
     YAzyk "C", pervonachal'no prednaznachavshijsya dlya napisaniya
operacionnoj sistemy "UNIX" na |VM DEC PDP-11, byl razrabo-
tan i realizovan na etoj sisteme Dennisom Richi. Operacionnaya
sistema, kompilyator s yazyka "C" i po sushchestvu vse prikladnye
programmy sistemy "UNIX" (vklyuchaya vse programmnoe obespeche-
nie, ispol'zovannoe pri podgotovke etoj knigi) napisany na
"C". Kommercheskie kompilyatory s yazyka "C" sushchestvuyut takzhe
na nekotoryh drugih |VM, vklyuchaya IBM SYSTEM/370, HONEYWELL
6000, INTERDATA 8/32. YAzyk "C", odnako, ne svyazan s kaki-
mi-libo opredelennymi apparatnymi sredstvami ili sistemami,
i na nem legko pisat' programmy, kotorye mozhno propuskat'
bez izmenenij na lyuboj |VM, imeyushchej "C"-kompilyator.
     |ta kniga prednaznachena dlya togo, chtoby pomoch' chitatelyu
nauchit'sya programmirovat' na yazyke "C". Ona soderzhit uchebnoe
vvedenie, cel' kotorogo - pozvolit' novym pol'zovatelyam na-
chat' programmirovat' kak mozhno bystree, otdel'nye glavy po
vsem osnovnym osobennostyam yazyka i spravochnoe rukovodstvo.
Obuchenie postroeno v osnovnom na chtenii, napisanii i razbore
primerov, a ne goloj formulirovke pravil. Primery, privodi-
mye v knige, po bol'shej chasti yavlyayutsya zakonchennymi real'ny-
mi programmami, a ne otdel'nymi fragmentami. Vse primery by-
li provereny neposredstvenno s teksta knigi, gde oni napecha-
tany v vide, prigodnom dlya vvoda v mashinu. Krome ukazanij o
tom, kak sdelat' ispol'zovanie yazyka bolee effektivnym, my
takzhe pytalis', gde eto vozmozhno, proillyustrirovat' poleznye
algoritmy i principy horoshego stilya i razumnoj razrabotki.
     Nastoyashchaya kniga ne yavlyaetsya vvodnym kursom v programmi-
rovanie; ona predpolagaet opredelennoe znakomstvo s osnovny-
mi ponyatiyami programmirovaniya takimi kak peremennye, opera-
tory prisvaivaniya, cikly, funkcii. Tem ne menee i novichok v
programmirovanii dolzhen okazat'sya v sostoyanii chitat' podryad
i osvoit'sya s yazykom, hotya pri etom byla by poleznoj pomoshch'
bolee opytnogo kollegi.
     Po nashemu opytu , "C" pokazal sebya priyatnym, vyrazi-
tel'nym i raznostoronnim yazykom na shirokom mnozhestve razno-
obraznyh programm. Ego legko vyuchit' , i on ne teryaet svoih
kachestv s rostom opyta programmista. My nadeemsya , chto eta
kniga pomozhet vam horosho ego ispol'zovat'.



     Vdumchivaya kritika i predlozheniya mnogih nashih druzej i
kolleg ochen' mnogo dobavili kak dlya samoj knigi, tak i dlya
nashego udovol'stviya pri ee napisanii. V chastnosti, Majk Bi-
apsi, Dzhim Blyu, St'yu Fel'dman, Doug Mak-Ilroj, Bill Rum, Bob
Rozin i Larri Rosler tshchatel'no prochitali mnozhestvo varian-
tov. My takzhe obyazany |lyu Aho, Stivu Bornu, Devu Dvoraku,
CHaku Heleyu, Debbi Helej, Marionu Harrisu, Riku Holtu, Stivu
Dzhonsonu, Dzhonu Masheyu, Bobu Mitcu, Ral'fu M'yua, Piteru Nel'-
sonu, |lliotu Pinsonu, Billu Plageru, Dzherri Spivaku, Kenu
Tompsonu i Piteru Vejnbergeru za poleznye zamechaniya na raz-
lichnyh etapah i Majku Losku i Dzho Osanna za neocenimuyu po-
moshch' pri pechatanii knigi.
        Brajen V. Kernigan
           Dennis M. Richi




      Annotaciya ........................................1
0.1.  Vvedenie   .......................................7
1.    Uchebnoe vvedenie..................................
1.1.     Hachinaem.......................................
1.2.     Peremennye i arifmetika........................
1.3.     Operator FOR...................................
1.4.     Simvolicheskie konstanty........................
1.5.     Nabor poleznyh programm........................
1.5.1.     Vvod i vyvod simvolov........................
1.5.2.     Kopirovanie fajla............................
1.5.3.     Podschet simvolov.............................
1.5.4.     Podschet strok................................
1.5.5.     Podschet slov.................................
1.6.     Massivy........................................
1.7.     Funkcii........................................
1.8.     Argumenty - vyzov po znacheniyu..................
1.9.     Massivy simvolov...............................
1.10.    Oblast' dejstviya: vneshnie peremennye...........
1.11.    Rezyume.........................................
2.    Tipy, operacii i vyrazheniya........................
2.1.     Imena peremennyh...............................
2.2.     Tipy i razmery dannyh..........................
2.3.     Konstanty......................................
2.3.1.     Simvol'naya konstanta.........................
2.3.2.     Konstantnoe vyrazhenie........................
2.3.3.     Strochnaya konstanta...........................
2.4.     Opisaniya.......................................
2.5.     Arifmeticheskie operacii........................
2.6.     Operacii otnosheniya i logicheskie operacii.......
2.7.     Preobrazovanie tipov...........................
2.8.     Operacii uvelicheniya i umen'sheniya...............
2.9.     Pobitovye logicheskie operacii..................
2.10.    Operacii i vyrazheniya prisvaivaniya..............
2.11.    Uslovnye vyrazheniya.............................
2.12.    Starshinstvo i poryadok vychisleniya...............
3.    Potok upravleniya..................................
3.1.     Operatory i bloki..............................
3.2.     IF - ELSE......................................
3.3.     ELSE - IF......................................
3.4.     Pereklyuchatel'..................................
3.5.     Cikly - WHILE i FOR............................
3.6.     Cikl DO - WHILE................................
3.7.     Operator BREAK.................................
3.8.     Operator CONTINUE..............................
3.9.     Operator GOTO i metki..........................
4.    Funkcii i struktura programm......................
4.1.     Osnovnye svedeniya..............................
4.2.     Funkcii, vozvrashchayushchie necelye znacheniya.........
4.3.     Eshche ob argumentah funkcij......................
4.4.     Vneshnie peremennye.............................
4.5.     Pravila, opredelyayushchie oblast' dejstviya.........
4.5.1.     Oblast' dejstviya.............................
4.6.     Staticheskie peremennye.........................
4.7.     Registrovye peremennye.........................
4.8.     Blochnaya struktura..............................
4.9.     Inicializaciya..................................



4.10.    Rekursiya.......................................
4.11.    Preprocessor yazyka "C".........................
4.11.1.    Vklyuchenie fajlov.............................
4.11.2.    Makropodstanovka.............................
5.       Ukazateli i massivy............................
5.1.     Ukazateli i adresa.............................
5.2.     Ukazateli i argumenty funkcij..................
5.3.     ukazateli i massivy............................
5.4.     Adresnaya arifmetika............................
5.5.     ukazateli simvolov i funkcii...................
5.6.     Ukazateli - ne celye...........................
5.7.     Mnogomernye massivy............................
5.8.     Massivy ukazatelej; ukazateli ukazatelej.......
5.9.     Inicializaciya massivov ukazatelej..............
5.10.    Ukazateli i mnogomernye massivy................
5.11.    Komandnaya stroka argumentov....................
5.12.    Ukazateli na funkcii...........................
6.    Struktury.........................................
6.1.     Osnovnye svedeniya..............................
6.2.     Struktury i funkcii............................
6.3.     Massivy sruktur................................
6.4.     Ukazateli na struktury.........................
6.5.     Struktury, ssylayushchiesya na sebya.................
6.6.     Poisk v tablice................................
6.7.     Polya...........................................
6.8.     Ob容dineniya....................................
6.9.     Opredelenie tipa...............................
7.    Vvod i vyvod......................................
7.1.     Obrashchenie k standartnoj biblioteke.............
7.2.     Standartnyj vvod i vyvod - funkcii  GETCHAR
         i PUTCHAR......................................
7.3.     Formatnyj vyvod - funkciya PRINTF...............
7.4.     Formatnyj vvod - funkciya SCANF.................
7.5.     Formatnoe preobrazovanie v pamyati..............
7.6.     Dostup k fajlam................................
7.7.     Obrabotka oshibok - STDERR i EXIT...............
7.8.     Vvod i vyvod strok.............................
7.9.     Neskol'ko raznoobraznyh funkcij................
7.9.1.     Proverka vida simvolov i preobrazovaniya......
7.9.2.     Funkciya UNGETC...............................
7.9.3.     Obrashchenie k sisteme..........................
7.9.4.     Upravlenie pamyat'yu...........................
8.    Interfejs sistemy UNIX............................
8.1.     Deskriptory fajlov.............................
8.2.     Nizkourovnevyj vvod/vyvod - operatory  READ
         i WRITE........................................
8.3.     Otkrytie, sozdanie, zakrytie i  rasceplenie
         (UNLINK).......................................
8.4.     Proizvol'nyj dostup - SEEK i LSEEK.............
8.5.     Primer - realizaciya funkcij FOPEN i GETC.......
8.6.     Primer - raspechatka spravochnikov...............
8.7.     Primer - raspredelitel' pamyati.................


      Prilozhenie a: spravochnoe rukovodstvo po yazyku 'C'.
9.1.     Vvedenie.......................................
10.   Leksicheskie soglasheniya............................
10.1.    Kommentarii....................................
10.2.    Identifikatory (imena).........................
10.3.    Klyuchevye slova.................................
10.4.    Konstanty......................................
10.4.1.    Celye konstanty..............................
10.4.2.    YAvnye dlinnye konstanty......................
10.4.3.    Simvol'nye konstanty.........................
10.4.4.    Plavayushchie konstanty..........................
10.5.    Stroki.........................................
10.6.    Harakteristiki apparatnyh sredstv..............
11.   Sintaksicheskaya notaciya............................
12.   CHto v imene tebe moem?............................
13.   Ob容kty i L-znacheniya..............................
14.   Preobrazovaniya....................................
14.1.    Simvoly i celye................................
14.2.    Tipy FLOAT i DOUBLE............................
14.3.    Plavayushchie i celochislennye velichiny.............
14.4.    Ukazateli i celye..............................
14.5.    Celoe bez znaka................................
14.6.    Arifmeticheskie preobrazovaniya..................
15.   Vyrazheniya.........................................
15.1.    Pervichnye vyrazheniya............................
15.2.    Unarnye operacii...............................
15.3.    Mul'tiplikativnye operacii.....................
15.4.    Additivnye operacii............................
15.5.    Operacii sdviga................................
15.6.    Operacii otnosheniya.............................
15.7.    Operacii ravenstva.............................
15.8.    Pobitovaya operaciya 'i'.........................
15.9.    Pobitovaya operaciya isklyuchayushchego 'ili'..........
15.10.   Pobitovaya operaciya vklyuchayushchego 'ili'...........
15.11.   Logicheskaya operaciya 'i'........................
15.12.   Operaciya logicheskogo 'ili'.....................
15.13.   Uslovnaya operaciya..............................
15.14.   Operaciya prisvaivaniya..........................
15.15.   Operaciya zapyataya...............................
16.   Opisaniya..........................................
16.1.    Specifikatory klassa pamyati....................
16.2.    Specifikatory tipa.............................
16.3.    Opisateli......................................
16.4.    Smysl opisatelej...............................
16.5.    Opisanie struktur i ob容dinenij................
16.6.    Inicializaciya..................................
16.7.    Imena tipov....................................
16.8.    TYPEDEF........................................
17.   Operatory.........................................
17.1.    Pperatornoe vyrazhenie..........................
17.2.    Sostavnoj operator (ili blok)..................
17.3.    Uslovnye operatory.............................
17.4.    Operator WHILE.................................
17.5.    Operator DO....................................


17.6.    Operator FOR...................................
17.7.    Operator SWITCH................................
17.8.    Operator BREAK.................................
17.9.    Operator CONTINUE..............................
17.10.   Operator vozvrata..............................
17.11.   Operator GOTO..................................
17.12.   Oomechennyj operator............................
17.13.   Oustoj operator................................
18.   Vneshnie opredeleniya...............................
18.1.    Vneshnee opredelenie funkcii....................
18.2.    Vneshnie opredeleniya dannyh.....................
19.   Pravila, opredelyayushchie oblast' dejstviya............
19.1.    Leksicheskaya oblast' dejstviya...................
19.2.    Oblast' dejstviya vneshnih identifikatorov.......
20.   Stroki upravleniya kompilyatorom....................
20.1.    Zamena leksem..................................
20.2.    Vklyuchenie fajlov...............................
20.3.    Uslovnaya kompilyaciya............................
21.   Neyavnye opisaniya..................................
22.   Snova o tipah.....................................
22.1.    Struktury i ob容dineniya........................
22.2.    Funkcii........................................
22.3.    Massivy, ukazateli i indeksaciya................
22.4.    YAvnye preobrazovaniya ukazatelej................
23.   Konstantnye vyrazheniya.............................
24.   Soobrazheniya o perenosimosti.......................
25.   Anahronizmy.......................................
26.   Svodka sintaksicheskih pravil......................
26.1.    Vyrazheniya......................................
26.2.    Opisaniya.......................................
26.3.    Operatory......................................
26.4.    Vneshnie opredeleniya............................
26.5.    Preprocessor...................................
27.   Prisvaivanie struktury............................
28.   Tip perechisleniya..................................
29.   Tablica izobrazhenij nepechatnyh simvolov yazyka "C".





     YAzyk "C" yavlyaetsya universal'nym yazykom programmirova-
niya. On tesno svyazan s operacionnoj sistemoj "UNIX" , tak
kak byl razvit na etoj sisteme i tak kak "UNIX" i ee prog-
rammnoe obespechenie napisano na "C". Sam yazyk , odnako, ne
svyazan s kakoj-libo odnoj operacionnoj sistemoj ili mashinoj;
i hotya ego nazyvayut yazykom sistemnogo programmirovaniya, tak
kak on udoben dlya napisaniya operacionnyh sistem, on s ravnym
uspehom ispol'zovalsya pri napisanii bol'shih vychislitel'nyh
programm, programm dlya obrabotki tekstov i baz dannyh.
     YAzyk "C" - eto yazyk otnositel'no "nizkogo urovnya". V
takoj harakteristike net nichego oskorbitel'nogo; eto prosto
oznachaet, chto "C" imeet delo s ob容ktami togo zhe vida, chto i
bol'shinstvo |VM, a imenno, s simvolami, chislami i adresami.
Oni mogut ob容dinyat'sya i peresylat'sya posredstvom obychnyh
arifmeticheskih i logicheskih operacij, osushchestvlyaemyh real'-
nymi |VM.
     V yazyke "C" otsutstvuyut operacii, imeyushchie delo nepos-
redstvenno s sostavnymi ob容ktami, takimi kak stroki simvo-
lov, mnozhestva, spiski ili s massivami, rassmatrivaemymi kak
celoe. Zdes', naprimer, net nikakogo analoga operaciyam PL/1,
operiruyushchim s celymi massivami i strokami. YAzyk ne predos-
tavlyaet nikakih drugih vozmozhnostej raspredeleniya pamyati,
krome staticheskogo opredeleniya i mehanizma stekov, obespechi-
vaemogo lokal'nymi peremennyh funkcij; zdes' net ni
"kuch"(HEAP), ni "sborki musora", kak eto predusmatrivaetsya v
ALGOLE-68. Nakonec, sam po sebe "C" ne obespechivaet nikakih
vozmozhnostej vvoda-vyvoda: zdes' net operatorov READ ili
WRITE i nikakih vstroennyh metodov dostupa k fajlam. Vse eti
mehanizmy vysokogo urovnya dolzhny obespechivat'sya yavno vyzyva-
emymi funkciyami.
     Analogichno, yazyk "C" predlagaet tol'ko prostye, posle-
dovatel'nye konstrukcii potokov upravleniya: proverki, cikly,
gruppirovanie i podprogrammy, no ne mul'tiprogrammirovanie,
parallel'nye operacii, sinhronizaciyu ili soprogrammy.
     Hotya otsutstvie nekotoryh iz etih sredstv mozhet vyglya-
det' kak udruchayushchaya nepolnocennost' ("vyhodit, chto ya dolzhen
obrashchat'sya k funkcii, chtoby sravnit' dve stroki simvolov
?!"), no uderzhanie yazyka v skromnyh razmerah daet real'nye
preimushchestva. Tak kak "C" otnositel'no mal, on ne trebuet
mnogo mesta dlya svoego opisaniya i mozhet byt' bystro vyuchen.
Kompilyator s "C" mozhet byt' prostym i kompaktnym. Krome to-
go, kompilyatory legko pishutsya; pri ispol'zovanii sovremennoj
tehnologii mozhno ozhidat' napisaniya kompilyatora dlya novoj |VM
za paru mesyacev i pri etom okazhetsya, chto 80 procentov prog-
rammy novogo kompilyatora budet obshchej s programmoj dlya uzhe
sushchestvuyushchih kompilyatorov. |to obespechivaet vysokuyu stepen'
mobil'nosti yazyka. Poskol'ku tipy dannyh i stuktury upravle-
niya, imeyushchiesya v "C", neposredstvenno podderzhivayutsya bol'-
shinstvom sushchestvuyushchih |VM, biblioteka, neobhodimaya vo vremya
progona izolirovannyh programm, okazyvaetsya ochen' malen'koj.
Na PDP -11, naprimer, ona soderzhit tol'ko programmy dlya
32-bitovogo umnozheniya i deleniya i dlya vypolneniya programm
vvoda i vyvoda posledovatel'nostej. Konechno, kazhdaya realiza-
ciya obespechivaet ischerpyvayushchuyu, sovmestimuyu biblioteku funk-
cij dlya vypolneniya operacij vvoda-vyvoda, obrabotki strok i
raspredeleniya pamyati, no tak kak obrashchenie k nim osushchestvlya-
etsya tol'ko yavno, mozhno , esli neobhodimo, izbezhat' ih vyzo-
va; eti funkcii mogut byt' kompaktno napisany na samom "C".



     Opyat' zhe iz-za togo , chto yazyk "C" otrazhaet vozmozhnosti
sovremennyh komp'yuterov, programmy na "C" okazyvayutsya dosta-
tochno effektivnymi, tak chto ne voznikaet pobuzhdeniya pisat'
vmesto etogo programmy na yazyke assemblera. Naibolee ubedi-
tel'nym primerom etogo yavlyaetsya sama operacionnaya sistema
"UNIX", kotoraya pochti polnost'yu napisana na "C". Iz 13000
strok programmy sistemy tol'ko okolo 800 strok samogo nizko-
go urovnya napisany na assemblere. Krome togo, po sushchestvu
vse prikladnoe programmnoe obespechenie sistemy "UNIX" napi-
sano na "C"; podavlyayushchee bol'shinstvo pol'zovatelej sistemy
"UNIX"(vklyuchaya odnogo iz avtorov etoj knigi) dazhe ne znaet
yazyka assemblera PDP-11.
     Hotya "C" sootvetstvuet vozmozhnostyam mnogih |VM, on ne
zavisit ot kakoj-libo konkretnoj arhitektury mashiny i v silu
etogo bez osobyh usilij pozvolyaet pisat' "perenosimye" prog-
rammy, t.e. programmy, kotorye mozhno propuskat' bez izmene-
nij na razlichnyh apparatnyh sredstvah. V nashih krugah stal
uzhe tradiciej perenos programmnogo obespecheniya, razrabotan-
nogo na sisteme "UNIX", na sistemy |VM: HONEYWELL, IBM i
INTERDATA. Fakticheski kompilyatory s "C" i programmnoe obes-
pechenie vo vremya progona programm na etih chetyreh sistemah,
po-vidimomu, gorazdo bolee sovmestimy, chem standartnye ver-
sii fortrana amerikanskogo nacional'nogo instituta standar-
tov (ANSI). Sama operacionnaya sistema "UNIX" teper' rabotaet
kak na PDP-11, tak i na INTERDATA 8/32. Za isklyucheniem prog-
ramm, kotorye neizbezhno okazyvayutsya v nekotoroj stepeni ma-
shinno-zavisimymi, takih kak kompilyator, assembler i otlad-
chik. Napisannoe na yazyke "C" programmnoe obespechenie iden-
tichno na obeih mashinah. Vnutri samoj operacionnoj sistemy
7000 strok programmy, isklyuchaya matematicheskoe obespechenie
yazyka assemblera |VM i upravleniya operaciyami vvoda-vyvoda,
sovpadayut na 95 procentov.
     Programmistam, znakomym s drugimi yazykami, dlya sravne-
niya i protivopostavleniya mozhet okazat'sya poleznym upominanie
neskol'kih istoricheskih, tehnicheskih i filosofskih aspektov
"C".
     Mnogie iz naibolee vazhnyh idej "C" proishodyat ot goraz-
do bolee starogo, no vse eshche vpolne zhiznennogo yazyka BCPL ,
razrabotannogo Martinom Richardsom. Kosvenno yazyk BCPL okazal
vliyanie na "C" cherez yazyk "B", napisannyj Kenom Tompsonom v
1970 godu dlya pervoj operacionnoj sistemy "UNIX" na |VM
PDP-7.
     Hotya yazyk "C" imeet neskol'ko obshchih s BCPL harakternyh
osobennostej, on nikoim obrazom ne yavlyaetsya dialektom pos-
lednego. I BCPL i "B" - "beztipnye" yazyki; edinstvennym vi-
dom dannyh dlya nih yavlyayutsya mashinnoe slovo, a dostup k dru-
gim ob容ktam realizuetsya special'nymi operatorami ili obra-
shcheniem k funkciyam. V yazyke "C" ob容ktami osnovnyh tipov dan-
nyh yavlyayutsya simvoly, celye chisla neskol'kih razmerov i chis-
la s plavayushchej tochkoj. Krome togo, imeetsya ierarhiya proiz-
vodnyh tipov dannyh, sozdavaemyh ukazatelyami, massivami,
strukturami, ob容dineniyami i funkciyami.



     YAzyk "C" vklyuchaet osnovnye konstrukcii potoka upravle-
niya, trebuemye dlya horosho struktuirovannyh programm: gruppi-
rovanie operatorov, prinyatie reshenij (IF), cikly s proverkoj
zaversheniya v nachale (WHILE, FOR) ili v konce (DO) i vybor
odnogo iz mnozhestva vozmozhnyh variantov (SWITCH). (Vse eti
vozmozhnosti obespechivalis' i v BCPL, hotya i pri neskol'ko
otlichnom sintaksise; etot yazyk predchuvstvoval nastupivshuyu
cherez neskol'ko let modu na strukturnoe programmirovanie).
     V yazyke "C" imeyutsya ukazateli i vozmozhnost' adresnoj
arifmetiki. Argumenty peredayutsya funkciyam posredstvom kopi-
rovaniya znacheniya argumenta , i vyzvannaya funkciya ne mozhet
izmenit' fakticheskij argument v vyzyvayushchej programme. Esli
zhelatel'no dobit'sya "vyzova po ssylke", mozhno neyavno pere-
dat' ukazatel', i funkciya smozhet izmenit' ob容kt, na kotoryj
etot ukazatel' ukazyvaet. Imena massivov peredayutsya ukazani-
em nachala massivov, tak chto argumenty tipa massivov effek-
tivno vyzyvayutsya po ssylke.
     K lyuboj funkcii mozhno obrashchat'sya rekursivno, i ee lo-
kal'nye peremennye obychno "avtomaticheskie", t.e. Sozdayutsya
zanovo pri kazhdom obrashchenii. Opisanie odnoj funkcii ne mozhet
soderzhat'sya vnutri drugoj, no peremennye mogut opisyvat'sya v
sootvetstvii s obychnoj blochnoj strukturoj. Funkcii v "C" -
programme mogut translirovat'sya otdel'no. peremennye po ot-
nosheniyu k funkcii mogut byt' vnutrennimi, vneshnimi, no iz-
vestnymi tol'ko v predelah odnogo ishodnogo fajla, ili pol-
nost'yu global'nymi. Vnutrennie peremennye mogut byt' avtoma-
ticheskimi ili staticheskimi. Avtomaticheskie peremennye dlya
bol'shej effektivnosti mozhno pomeshchat' v registry, no ob座avle-
nie registra yavlyaetsya tol'ko ukazaniem dlya kompilyatora i ni-
kak ne svyazano s konkretnymi mashinnymi registrami.
     YAzyk "C" ne yavlyaetsya yazykom so strogimi tipami v smysle
paskalya ili algola 68. On sravnitel'no snishoditelen k pre-
obrazovaniyu dannyh, hotya i ne budet avtomaticheski preobrazo-
vyvat' tipy dannyh s bujnoj neprinuzhdennost'yu yazyka PL/1.
Sushchestvuyushchie kompilyatory ne predusmatrivayut nikakoj proverki
vo vremya vypolneniya programmy indeksov massivov, tipov argu-
mentov i t.d.
     V teh situaciyah, kogda zhelatel'na strogaya proverka ti-
pov, ispol'zuetsya special'naya versiya kompilyatora. |ta prog-
ramma nazyvaetsya LINT ochevidno potomu, ona vybiraet kusochki
puha iz vashej programmy. Programma LINT ne generiruet mashin-
nogo koda, a delaet ochen' stroguyu proverku vseh teh storon
programmy, kotorye mozhno prokontrolirovat' vo vremya kompilya-
cii i zagruzki. Ona opredelyaet nesootvetstvie tipov, nesov-
mestimost' argumentov, neispol'zovannye ili ochevidnym obra-
zom neinicializirovannye peremennye, potencial'nye trudnosti
perenosimosti i t.d. Dlya programm,kotorye blagopoluchno pro-
hodyat cherez LINT, garantiruetsya otsutstvie oshibok tipa pri-
merno s toj zhe polnotoj, kak i dlya programm, napisannyh,
naprimer, na ALGOLE-68. Drugie vozmozhnosti programmy LINT
budut otmecheny, kogda predstavitsya sootvetstvuyushchij sluchaj.



     Nakonec, yazyk "C", podobno lyubomu drugomu yazyku, imeet
svoi nedostatki. Nekotorye operacii imeyut neudachnoe starshin-
stvo; nekotorye razdely sintaksisa mogli by byt' luchshe; su-
shestvuet neskol'ko versij yazyka, otlichayushchihsya nebol'shimi de-
talyami. Tem ne menee yazyk "C" zarekomendoval sebya kak isklyu-
chitel'no effektivnyj i vyrazitel'nyj yazyk dlya shirokogo raz-
noobraziya primenenij programmirovaniya.
     Soderzhanie knigi organizovano sleduyushchim obrazom. Glava
1 yavlyaetsya uchebnym vvedeniem v central'nuyu chast' yazyka "C".
Cel' - pozvolit' chitatelyu startovat' tak bystro,kak tol'ko
vozmozhno, tak kak my tverdo ubezhdeny, chto edinstvennyj spo-
sob izuchit' novyj yazyk - pisat' na nem programmy. Pri etom ,
odnako, predpolagaetsya rabochee vladenie osnovnymi elementami
programmirovaniya; zdes' ne ob座asnyaetsya, chto takoe |VM ili
kompilyator, ne poyasnyaetsya smysl vyrazhenij tipa N=N+1. Hotya
my i pytalis', gde eto vozmozhno, prodemonstrirovat' poleznuyu
tehniku programmirovaniya. |ta kniga ne prednaznachaetsya byt'
spravochnym rukovodstvom po strukturam dannyh i algoritmam;
tam, gde my vynuzhdeny byli sdelat' vybor, my koncentrirova-
lis' na yazyke.
     V glavah so 2-j po 6-yu razlichnye aspekty "C" izlagayutsya
bolee detal'no i neskol'ko bolee formal'no, chem v glave 1,
hotya udarenie po-prezhnemu delaetsya na razbore primerov za-
konchennyh, poleznyh programm, a ne na otdel'nyh fragmentah.
     V glave 2 obsuzhdayutsya osnovnye tipy dannyh, operatory i
vyrazheniya. V glave 3 rassmatrivayutsya upravlyayushchie operatory:
IF-ELSE ,WHILE ,FOR i t.d. Glava 4 ohvatyvaet funkcii i
strukturu programmy - vneshnie peremennye, pravila opredelen-
nyh oblastej dejstviya opisaniya i t.d. V glave 5 obsuzhdayutsya
ukazateli i adresnaya arifmetika. Glava 6 soderzhit podrobnoe
opisanie struktur i ob容dinenij.
     V glave 7 opisyvaetsya standartnaya biblioteka vvoda-vy-
voda yazyka "C", kotoraya obespechivaet standartnyj interfejs s
operacionnoj sistemoj. |ta biblioteka vvoda-vyvoda podderzhi-
vaetsya na vseh mashinah, na kotoryh realizovan "C", tak chto
programmy, ispol'zuyushchie ee dlya vvoda, vyvoda i drugih sis-
temnyh funkcij, mogut perenosit'sya s odnoj sistemy na druguyu
po sushchestvu bez izmenenij.
     V glave 8 opisyvaetsya interfejs mezhdu "C" - programmami
i operacionnoj sistemoj "UNIX". Upor delaetsya na vvod-vyvod,
sistemu fajlov i perenosimost'. Hotya nekotorye chasti etoj
glavy specifichny dlya operacionnoj sistemy "UNIX", program-
misty, ne ispol'zuyushchie "UNIX", vse zhe dolzhny najti zdes' po-
leznyj material, v tom chisle nekotoroe predstavlenie o tom,
kak realizovana odna versiya standartnoj biblioteki i predlo-
zheniya dlya dostizheniya perenosimosti programmy.
     Prilozhenie A soderzhit spravochnoe rukovodstvo po yazyku
"C". Ono yavlyaetsya "oficial'nym" izlozheniem sintaksisa i se-
mantiki "C" i (isklyuchaya chej-libo sobstvennyj kompilyator)
okonchatel'nym arbitrom dlya vseh dvusmyslennostej i upushchenij
v predydushchih glavah.



     Tak kak "C" yavlyaetsya razvivayushchimsya yazykom, realizovan-
nym na mnozhestve sistem, chast' materila nastoyashchej knigi mo-
zhet ne sootvetstvovat' tekushchemu sostoyaniyu razrabotki na ka-
koj-to konkretnoj sisteme. My staralis' izbegat' takih prob-
lem i predosteregat' o vozmozhnyh trudnostyah. V somnitel'nyh
sluchayah, odnako, my obychno predpochitali opisyvat' situaciyu
dlya sistemy "UNIX" PDP-11 , tak kak ona yavlyaetsya sredoj dlya
bol'shinstva programmiruyushchih na yazyke "C". V prilozhenii a
takzhe opisany rashozhdeniya v realizaciyah yazyka "C" na osnov-
nyh sistemah.



     Davajte nachnem s bystrogo vvedeniya v yazyk "C". Nasha
cel' - prodemonstrirovat' sushchestvennye elementy yazyka na re-
al'nyh programmah, ne uvyazaya pri etom v detalyah, formal'nyh
pravilah i isklyucheniyah. V etoj glave my ne pytaemsya izlozhit'
yazyk polnost'yu ili hotya by strogo (razumeetsya, privodimye
primery budut korrektnymi). My hotim kak mozhno skoree doves-
ti vas do takogo urovnya, na kotorom vy byli by v sostoyanii
pisat' poleznye programmy, i chtoby dobit'sya etogo, my sosre-
dotachivaemsya na osnovnom: peremennyh i konstantah, arifmeti-
ke, operatorah peredachi upravleniya, funkciyah i elementarnyh
svedeniyah o vvode i vyvode. My sovershenno namerenno ostavlya-
em za predelami etoj glavy mnogie elementy yazyka "C", koto-
rye imeyut pervostepennoe znachenie pri napisanii bol'shih
programm, v tom chisle ukazateli, srtuktury, bol'shuyu chast' iz
bogatogo nabora operatorov yazyka "C", neskol'ko operatorov
peredachi upravleniya i nesmetnoe kolichestvo detalej.
     Takoj podhod imeet, konechno, svoi nedostatki. Samym su-
shchestvennym yavlyaetsya to, chto polnoe opisanie lyubogo konkret-
nogo elementa yazyka ne izlagaetsya v odnom meste, a poyasne-
niya, v silu kratkosti, mogut privesti k nepravil'nomu istol-
kovaniyu. Krome togo, iz-za nevozmozhnosti ispol'zovat' vsyu
moshch' yazyka, primery okazyvayutsya ne stol' kratkimi i elegant-
nymi, kak oni mogli by byt'. I hotya my staralis' svesti eti
nedostatki k minimumu, vse zhe imejte ih vvidu.
     Drugoj nedostatok sostoit v tom, chto posleduyushchie glavy
budut neizbezhno povtoryat' nekotorye chasti etoj glavy. My na-
deemsya, chto takoe povtorenie budet skoree pomogat', chem raz-
drazhat'.
     Vo vsyakom sluchae, opytnye programmisty dolzhny okazat'sya
v sostoyanii proekstrapolirovat' material dannoj glavy na
svoi sobstvennye programmistskie nuzhdy. Nachinayushchie zhe dolzhny
v dopolnenie pisat' analogichnye malen'kie samostoyatel'nye
programmy. I te, i drugie mogut ispol'zovat' etu glavu kak
karkas, na kotoryj budut naveshivat'sya bolee podrobnye opisa-
niya, nachinayushchiesya s glavy 2.



 Edinstvennyj sposob osvoit' novyj yazyk
programmirovaniya - pisat' na nem programmy. Pervaya program-
ma, kotoraya dolzhna byt' napisana, - odna dlya vseh yazykov:
napechatat' slova : HELLO, WORLD.
     |to - samyj sushchestvennyj bar'er; chtoby preodolet' ego,
vy dolzhny sumet' zavesti gde-to tekst programmy, uspeshno ego
skompilirovat', zagruzit', prognat' i najti, gde okazalas'
vasha vydacha. Esli vy nauchilis' spravlyat'sya s etimi tehniches-
kimi detalyami, vse ostal'noe sravnitel'no prosto.



     Programma pechati "HELLO, WORLD" na yazyke "C" imeet vid:

   MAIN ()
   {
           PRINTF("HELLO, WORLD\N");
   }
     Kak propustit' etu programmu - zavisit ot ispol'zuemoj
vami sistemy. V chastnosti, na operacionnoj sisteme "UNIX" vy
dolzhny zavesti ishodnuyu programmu v fajle, imya kotorogo
okanchivaetsya na ".C" , naprimer, HELLO.C , i zatem skompili-
rovat' ee po komande

           CC HELLO.C

     Esli vy ne dopustili kakoj-libo nebrezhnosti , takoj kak
propusk simvola ili nepravil'noe napisanie, kompilyaciya proj-
det bez soobshchenij i budet sozdan ispolnyaemyj fajl s imenem
a.OUT . Progon ego po komande

              A.OUT

privedet k vyvodu

          HELLO, WORLD

     Na drugih sistemah eti pravila budut inymi; prokonsul'-
tirujtes' s mestnym avtoritetom.

    Uprazhnenie 1-1
     ---------------
     Propustite etu programmu na vashej sisteme. Poprobujte
ne vklyuchat' razlichnye chasti programmy i posmotrite kakie so-
obshcheniya ob oshibkah vy pri etom poluchite.
     Teper' nekotorye poyasneniya k samoj programme. Lyubaya
"C"-programma, kakov by ni byl ee razmer, sostoit iz odnoj
ili bolee "funkcij", ukazyvayushchih fakticheskie operacii
komp'yutera, kotorye dolzhny byt' vypolneny. Funkcii v yazyke
"C" podobny funkciyam i podprogrammam fortrana i proceduram
PL/1, paskalya i t.d. V nashem primere takoj funkciej yavlyaetsya
MAIN. Obychno vy mozhete davat' funkciyam lyubye imena po vashemu
usmotreniyu, no MAIN - eto osoboe imya; vypolnenie vashej prog-
rammy nachinaetsya snachala s funkcii MAIN. |to oznachaet, chto
kazhdaya programma dolzhna v kakom-to meste soderzhat' funkciyu s
imenem MAIN. Dlya vypolneniya opredelennyh dejstvij funkciya
MAIN obychno obrashchaetsya k drugim funkciyam, chast' iz kotoryh
nahoditsya v toj zhe samoj programme, a chast' - v bibliotekah,
soderzhashchih ranee napisannye funkcii.



     Odnim sposobom obmena dannymi mezhdu funkciyami yavlyaetsya
peredacha posredstvom argumentov. Kruglye skobki, sleduyushchie
za imenem funkcii, zaklyuchayut v sebe spisok argumentov; zdes'
maIN - funkciya bez argumentov, chto ukazyvaetsya kak (). Ope-
ratory, sostavlyayushchie funkciyu, zaklyuchayutsya v figurnye skobki
{ i }, kotorye analogichny DO-END v PL/1 ili BEGIN-END v al-
gole, paskale i t.d. Obrashchenie k funkcii osushchestvlyaetsya uka-
zaniem ee imeni, za kotorym sleduet zaklyuchennyj v kruglye
skobki spisok argumentov. zdes' net nikakih operatorov CALL,
kak v fortrane ili PL/1. Kruglye skobki dolzhny prisutstvo-
vat' i v tom sluchae, kogda funkciya ne imeet argumentov.
Stroka

     PRINTF("HELLO, WORLD\N");

yavlyaetsya obrashcheniem k funkcii, kotoroe vyzyvaet funkciyu
s imenem PRINTF i argumetom "HELLO, WORLD\N". Funkciya PRINTF
yavlyaetsya bibliotechnoj funkciej, kotoraya vydaet vyhodnye dan-
nye na terminal (esli tol'ko ne ukazano kakoe-to drugoe mes-
to naznacheniya). V dannom sluchae pechataetsya stroka simvolov,
yavlyayushchayasya argumentom funkcii.
     Posledovatel'nost' iz lyubogo kolichestva simvolov, zak-
lyuchennyh v udvoennye kavychki "...", nazyvaetsya 'simvol'noj
strokoj' ili 'strochnoj konstantoj'. Poka my budem ispol'zo-
vat' simvol'nye stroki tol'ko v kachestve argumentov dlya
PRINTF i drugih funkcij.
     Posledovatel'nost' \N v privedennoj stroke yavlyaetsya
oboznacheniem na yazyke "C" dlya 'simvola novoj stroki', koto-
ryj sluzhit ukazaniem dlya perehoda na terminale k levomu krayu
sleduyushchej stroki. Esli vy ne vklyuchite \N (poleznyj eksperi-
ment), to obnaruzhite, chto vasha vydacha ne zakonchitsya pereho-
dom terminala na novuyu stroku. Ispol'zovanie posledovatel'-
nosti \N - edinstvennyj sposob vvedeniya simvola novoj stroki
v argument funkcii PRINTF; esli vy poprobuete chto-nibud'
vrode

         PRINTF("HELLO, WORLD
         ");

to "C"-kompilyator  budet pechatat' zloradnye diagnosticheskie
soobshcheniya o nedostayushchih kavychkah.
     Funkciya PRINTF ne obespechivaet avtomaticheskogo perehoda
na novuyu stroku, tak chto mnogokratnoe obrashchenie k nej mozhno
ispol'zovat' dlya poetapnoj sborki vyhodnoj stroki. Nasha per-
vaya programma, pechatayushchaya identichnuyu vydachu, s tochno takim
zhe uspehom mogla by byt' napisana v vide

    MAIN()
    {
            PRINTF("HELLO, ");
            PRINTF("WORLD");
            PRINTF("\N");
    }
     Podcherknem, chto \N predstavlyaet tol'ko odin simvol. Us-
lovnye 'posledovatel'nosti', podobnye \N , dayut obshchij i do-
puskayushchij rasshirenie mehanizm dlya predstavleniya trudnyh dlya
pechati ili nevidimyh simvolov. Sredi prochih simvolov v yazyke
"C" predusmotreny sleduyushchie: \t - dlya tabulyacii, \B - dlya
vozvrata na odnu poziciyu, \" - dlya dvojnoj kavychki i \\ dlya
samoj obratnoj kosoj cherty.

    Uprazhnenie 1-2
     ---------------
     Provedite eksperimenty dlya togo, chtoby uznat' chto proi-
zojdet, esli v stroke, yavlyayushchejsya argumentom funkcii PRINTF
budet soderzhat'sya \X, gde X - nekotoryj simvol, ne vhodyashchij
v vysheprivedennyj spisok.



     Sleduyushchaya programma pechataet privedennuyu nizhe tablicu
temperatur po Farengejtu i ih ekvivalentov po stogradusnoj
shkale Cel'siya, ispol'zuya dlya perevoda formulu

         C = (5/9)*(F-32).
    0       -17.8
   20        -6.7
   40         4.4
   60        15.6
  ...         ...
  260       126.7
  280       137.8
  300       140.9

      Teper' sama programma:

   /* PRINT FAHRENHEIT-CELSIUS TABLE
   FOR F = 0, 20, ..., 300 */
   MAIN()
   {
   INT LOWER, UPPER, STEP;
   FLOAT FAHR, CELSIUS;
   LOWER = 0;  /* LOWER LIMIT OF TEMPERATURE
   TABLE */
   UPPER =300; /* UPPER LIMIT */
   STEP  = 20; /* STEP SIZE */
   FAHR = LOWER;
   WHILE (FAHR <= UPPER) {
     CELSIUS = (5.0/9.0) * (FAHR -32.0);
     PRINTF("%4.0F %6.1F\N", FAHR, CELSIUS);
     FAHR = FAHR + STEP;
   }
   }



Pervye dve stroki

     /* PRINT FAHRENHEIT-CELSIUS TABLE
        FOR  F = 0, 20, ..., 300 */

yavlyayutsya kommentariem, kotoryj v dannom sluchae kratko poyas-
nyaet, chto delaet programma. Lyubye simvoly mezhdu /* i */ ig-
noriruyutsya kompilyatorom; mozhno svobodno pol'zovat'sya kommen-
tariyami dlya oblegcheniya ponimaniya programmy. Kommentarii mo-
gut poyavlyat'sya v lyubom meste, gde vozmozhen probel ili pere-
hod na novuyu stroku.
    V yazyke "C" vse peremennye dolzhny byt' opisany do ih is-
pol'zovaniya, obychno eto delaetsya v nachale funkcii do pervogo
vypolnyaemogo operatora. Esli vy zabudete vstavit' opisanie,
to poluchite diagnosticheskoe soobshchenie ot kompilyatora. Opisa-
nie sostoit iz tipa i spiska peremennyh, imeyushchih etot tip,
kak v

    INT LOWER, UPPER, STEP;
    FLOAT FAHR, CELSIUS;

    Tip INT oznachaet, chto vse peremennye spiska celye; tip
FLOAT prednaznachen dlya chisel s plavayushchej tochkoj, t.e. dlya
chisel, kotorye mogut imet' drobnuyu chast'. Tochnost' kak INT ,
TAK i FLOAT zavisit ot konkretnoj mashiny, na kotoroj vy ra-
botaete. Na PDP-11, naprimer, tip INT sootvetstvuet 16-bito-
vomu chislu so znakom, t.e. chislu, lezhashchemu mezhdu -32768 i
+32767. CHislo tipa FLOAT - eto 32-bitovoe chislo, imeyushchee
okolo semi znachashchih cifr i lezhashchee v diapazone ot 10e-38 do
10e+38. V glave 2 privoditsya spisok razmerov dlya drugih ma-
shin.
    V yazyke "C" predusmotreno neskol'ko drugih osnovnyh ti-
pov dannyh, krome INT i FLOAT:
CHAR    simvol - odin bajt
SHORT   korotkoe celoe
LONG    dlinnoe celoe
DOUBLE  plavayushchee s dvojnoj tochnost'yu
    Razmery etih ob容ktov tozhe mashinno-nezavisimy; detali
privedeny v glave 2. Imeyutsya takzhe massivy, struktury i ob-
容dineniya etih osnovnyh tipov, ukazateli na nih i funk-
cii,kotorye ih vozvrashchayut; so vsemi nimi my vstretimsya v
svoe vremya.
    Fakticheski vychisleniya v programme perevoda temperatur
nachinayutsya s operatorov prisvaivaniya
LOWER = 0;
UPPER =300;
STEP = 20;
FAHR =LOWER;
kotorye pridayut peremennym ih nachal'nye znacheniya. kazhdyj ot-
del'nyj operator zakanchivaetsya tochkoj s zapyatoj.
    Kazhdaya stroka tablicy vychislyaetsya odinakovym obrazom,
tak chto my ispol'zuem cikl, povtoryayushchijsya odin raz na stro-
ku. V etom naznachenie operatora WHILE:

WHILE (FAHR <= UPPER) {
....
}


proveryaetsya uslovie v kruglyh skobkah. Esli ono istinno
(FAHR men'she ili ravno UPPER), to vypolnyaetsya telo cikla
(vse operatory, zaklyuchennye v figurnye skobki { i } ). Zatem
vnov' proveryaetsya eto uslovie i, esli ono istinno, opyat' vy-
polnyaetsya telo cikla. Esli zhe uslovie ne vypolnyaetsya ( FAHR
prevoshodit UPPER ), cikl zakanchivaetsya i proishodit perehod
k vypolneniyu operatora, sleduyushchego za operatorom cikla. Tak
kak v nastoyashchej programme net nikakih posleduyushchih operato-
rov, to vypolnenie programmy zavershaetsya.
    Telo operatora WHILE mozhet sostoyat' iz odnogo ili bolee
operatorov, zaklyuchennyh v figurnye skobki, kak v programme
perevoda temperatur, ili iz odnogo operatora bez skobok,
kak, naprimer, v

    WHILE (I < J)
          I = 2 * I;

    V oboih sluchayah operatory, upravlyaemye operatorom WHILE,
sdvinuty na odnu tabulyaciyu, chtoby vy mogli s pervogo vzglyada
videt', kakie operatory nahodyatsya vnutri cikla. Takoj sdvig
podcherkivaet logicheskuyu strukturu programmy. Hotya v yazyke
"C" dopuskaetsya sovershenno proizvol'noe raspolozhenie opera-
torov v stroke, podhodyashchij sdvig i ispol'zovanie probelov
znachitel'no oblegchayut chtenie programm. My rekomenduem pisat'
tol'ko odin operator na stroke i (obychno) ostavlyat' probely
vokrug operatorov. Raspolozhenie figurnyh skobok menee sushches-
tvenno; my vybrali odin iz neskol'kih populyarnyh stilej. Vy-
berite podhodyashchij dlya vas stil' i zatem ispol'zujte ego pos-
ledovatel'no.
    Osnovnaya chast' raboty vypolnyaetsya v tele cikla. Tempera-
tura po Cel'siyu vychislyaetsya i prisvaivaetsya peremennoj
CELAIUS operatorom

     CELSIUS = (5.0/9.0) * (FAHR-32.0);

prichina ispol'zovaniya vyrazheniya 5.0/9.0 vmesto vyglyadyashchego
proshche 5/9 zaklyuchaetsya v tom, chto v yazyke "C", kak i vo mno-
gih drugih yazykah, pri delenii celyh proishodit usechenie,
sostoyashchee v otbrasyvanii drobnoj chasti rezul'tata. Takim ob-
razom, rezul'tat operacii 5/9 raven nulyu, i, konechno, v etom
sluchae vse temperatury okazalis' by ravnymi nulyu. Desyatichnaya
tochka v konstante ukazyvaet, chto ona imeet tip s plavayushchej
tochkoj, tak chto, kak my i hoteli, 5.0/9.0 ravno 0.5555... .
    My takzhe pisali 32.0 vmesto 32 , nesmotrya na to, chto tak
kak peremennaya FAHR imeet tip FLOAT , celoe 32 avtomaticheski
by preobrazovalos' k tipu FLOAT ( v 32.0) pered vychitaniem.
S tochki zreniya stilya razumno pisat' plavayushchie konstanty s
yavnoj desyatichnoj tochkoj dazhe togda, kogda oni imeyut celye
znacheniya; eto podcherkivaet ih plavayushchuyu prirodu dlya prosmat-
rivayushchego programmu i obespechivaet to, chto kompilyator budet
smotret' na veshchi tak zhe, kak i Vy.



    Podrobnye pravila o tom, v kakom sluchae celye preobrazu-
yutsya k tipu s plavayushej tochkoj, privedeny v glave 2. Sejchas
zhe otmetim, chto prisvaivanie

           FAHR = LOWER;

proverka

           WHILE (FAHR <= UPPER)

rabotayut, kak ozhidaetsya, - pered vypolneniem operacij celye
preobrazuyutsya v plavayushchuyu formu.
    |tot zhe primer soobshchaet chut' bol'she o tom, kak rabotaet
PRINTF. Funkciya PRINTF fakticheski yavlyaetsya universal'noj
funkciej formatnyh preobrazovanij, kotoraya budet polnost'yu
opisana v glave 7. Ee pervym argumentom yavlyaetsya stroka sim-
volov, kotoraya dolzhna byt' napechatana, prichem kazhdyj znak %
ukazyvaet, kuda dolzhen podstavlyat'sya kazhdyj iz ostal'nyh ar-
gumentov /vtoroj, tretij, .../ i v kakoj forme on dolzhen pe-
chatat'sya. Naprimer, v operatore

  PRINTF("%4.0F %6.1F\N", FAHR, CELSIUS);

specifikaciya preobrazovaniya %4.0F govorit, chto chislo s pla-
vayushchej tochkoj dolzhno byt' napechatano v pole shirinoj po kraj-
nej mere v chetyre simvola bez cifr posle desyatichnoj tochki.
specifikaciya %6.1F opisyvaet drugoe chislo, kotoroe dolzhno
zanimat' po krajnej mere shest' pozicij s odnoj cifroj posle
desyatichnoj tochki, analogichno specifikaciyam F6.1 v fortrane
ili F(6,1) v PL/1. Razlichnye chasti specifikacii mogut byt'
opushcheny: specifikaciya %6F govorit, chto chislo budet shirinoj
po krajnej mere v shest' simvolov; specifikaciya %2 trebuet
dvuh pozicij posle desyatichnoj tochki, no shirina pri etom ne
ogranichivaetsya; specifikaciya %F govorit tol'ko o tom, chto
nuzhno napechatat' chislo s plavayushchej tochkoj. Funkciya PRINTF
takzhe raspoznaet sleduyushchie specifikacii: %D - dlya desyatichno-
go celogo, %o - dlya vos'merichnogo chisla, %h - dlya shestnadca-
tirichnogo, %s - dlya simvola, %S - dlya simvol'noj stroki i %%
- dlya samogo simvola %.
    Kazhdaya konstrukciya s simvolom % v pervom argumente funk-
cii PRINTF sochetaetsya s sootvetstvuyushchim vtorym, tret'im, i
t.d. Argumentami; oni dolzhny soglasovyvat'sya po chislu i ti-
pu; v protivnom sluchae vy poluchite bessmyslennye rezul'taty.
    Mezhdu prochim, funkciya PRINTF ne yavlyaetsya chast'yu yazyka
"C"; v samom yazyke "C" ne opredeleny operacii vvoda-vyvoda.
Net nichego tainstvennogo i v funkcii PRINTF ; eto - prosto
poleznaya funkciya, yavlyayushchayasya chast'yu standartnoj biblioteki
podprogramm, kotoraya obychno dostupna "C"-programmam. CHtoby
sosredotochit'sya na samom yazyke, my ne budem podrobno osta-
navlivat'sya na operaciyah vvoda-vyvoda do glavy 7. V chastnos-
ti, my do teh por otlozhim formatnyj vvod. Esli vam nado
vvesti chisla - prochitajte opisanie funkcii SCANF v glave 7,
razdel 7.4. Funkciya SCANF vo mnogom shodna s PRINTF , no ona
schityvaet vhodnye dannye, a ne pechataet vyhodnye.




    Uprazhnenie  1-3
     ----------------
    Preobrazujte programmu perevoda temperatur takim obra-
zom, chtoby ona pechatala zagolovok k tablice.

    Uprazhnenie  1-4
     ----------------
    Napishite programmy pechati sootvetstvuyushchej tablicy pere-
hoda ot gradusov cel'siya k gradusam farengejta.




    Kak i mozhno bylo ozhidat', imeetsya mnozhestvo razlichnyh
sposobov napisaniya kazhdoj programmy. Davajte rassmotrim ta-
koj variant programmy perevoda temperatur:

MAIN()  /* FAHRENHEIT-CELSIUS TABLE */
{
   INT FAHR;
   FOR (FAHR = 0; FAHR <= 300; FAHR = FAHR + 20)
 PRINTF("%4D %6.1F\N", FAHR, (5.0/9.0)*(FAHR-32.0));
}

    |ta programma vydaet te zhe samye rezul'taty, no vyglyadit
bezuslovno po-drugomu. Glavnoe izmenenie - isklyuchenie bol'-
shinstva peremennyh; ostalas' tol'ko peremennaya FAHR , prichem
tipa INT (eto sdelano dlya togo, chtoby prodemonstrirovat'
preobrazovanie %D v funkcii PRINTF). Nizhnyaya i verhnyaya grani-
cy i razmer shchaga poyavlyayutsya tol'ko kak konstanty v operatore
FOR , kotoryj sam yavlyaetsya novoj konstrukciej, a vyrazhenie,
vychislyayushchee temperaturu po cel'siyu, vhodit teper' v vide
tret'ego argumenta funkcii PRINTF , a ne v vide otdel'nogo
operatora prisvaivaniya.
    Poslednee izmenenie yavlyaetsya primerom vpolne obshchego pra-
vila yazyka "C" - v lyubom kontekste, v kotorom dopuskaetsya
ispol'zovanie znacheniya peremennoj nekotorogo tipa, vy mozhete
ispol'zovat' vyrazhenie etogo tipa. Tak kak tretij argument
funkcii PRINTF dolzhen imet' znachenie s plavayushchej tochkoj,
chtoby sootvetstvovat' specifikacii %6.1F, to v etom meste
mozhet vstretit'sya lyuboe vyrazhenie plavayushchego tipa.
    Sam operator FOR - eto operator cikla, obobshchayushchij opera-
tor WHILE. Ego funkcionirovanie dolzhno stat' yasnym, esli vy
sravnite ego s ranee opisannym operatorom WHILE . Operator
FOR soderzhit tri chasti, razdelyaemye tochkoj s zapyatoj. Pervaya
chast'

FAHR = 0

vypolnyaetsya odin raz pered vhodom v sam cikl. Vtoraya chast' -
proverka, ili uslovie, kotoroe upravlyaet ciklom:

FAHR <= 300

eto uslovie proveryaetsya i, esli ono istinno, to vypolnyaetsya
telo cikla (v dannom sluchae tol'ko funkciya PRINTF ). Zatem
vypolnyaetsya shag reinicializacii
FAHR =FAHR + 20

i uslovie proveryaetsya snova. cikl zavershaetsya, kogda eto us-
lovie stanovitsya lozhnym. Tak zhe, kak i v sluchae operatora
WHILE , telo cikla mozhet sostoyat' iz odnogo operatora ili iz
gruppy operatorov, zaklyuchennyh v figurnye skobki. Iniciali-
ziruyushchaya i reinicializiruyushchaya chasti mogut byt' lyubymi ot-
del'nymi vyrazheniyami.
    Vybor mezhdu operatorami WHILE i FOR proizvolen i osnovy-
vaetsya na tom , chto vyglyadit yasnee. Operator FOR obychno udo-
ben dlya ciklov, v kotoryh inicializaciya i reinicializaciya
logicheski svyazany i kazhdaya zadaetsya odnim operatorom, tak
kak v etom sluchae zapis' bolee kompaktna, chem pri ispol'zo-
vanii operatora WHILE , a operatory upravleniya ciklom sosre-
dotachivayutsya vmeste v odnom meste.

    Uprazhnenie  1-5
    ---------------
    Modificirujte programmu perevoda temperatur takim obra-
zom, chtoby ona pechatala tablicu v obratnom poryadke, t.e. Ot
300 gradusov do 0.



    Poslednee zamechanie, prezhde chem my navsegda ostavim
programmu perevoda temperatur. Pryatat' "magicheskie chisla",
takie kak 300 i 20, vnutr' programmy - eto neudachnaya prakti-
ka; oni dayut malo informacii tem, kto, vozmozhno, dolzhen bu-
det razbirat'sya v etoj programme pozdnee, i ih trudno izme-
nyat' sistematicheskim obrazom. K schast'yu v yazyke "C" predus-
motren sposob, pozvolyayushchij izbezhat' takih "magicheskih chi-
sel". Ispol'zuya konstrukciyu #DEFINE , vy mozhete v nachale
programmy opredelit' simvolicheskoe imya ili simvolicheskuyu
konstantu, kotoraya budet konkretnoj strokoj simvolov. Vpos-
ledstvii kompilyator zamenit vse ne zaklyuchennye v kavychki po-
yavleniya etogo imeni na sootvetstvuyushchuyu stroku. Fakticheski
eto imya mozhet byt' zameneno absolyutno proizvol'nym tekstom,
ne obyazatel'no ciframi

 #DEFINE  LOWER 0/* LOWER LIMIT OF TABLE */
 #DEFINE  UPPER 300  /* UPPER LIMIT */
 #DEFINE  STEP  20  /* STEP SIZE */
 MAIN () /* FAHRENHEIT-CELSIUS TABLE */
 {
 INT FAHR;
FOR (FAHR =LOWER; FAHR <= UPPER; FAHR =FAHR + STEP)
  PRINTF("%4D %6.1F\N", FAHR, (5.0/9.0)*(FAHR-32));
 }


    velichiny LOWER, UPPER i STEP yavlyayutsya konstantami i poe-
tomu oni ne ukazyvayutsya v opisaniyah. Simvolicheskie imena
obychno pishut propisnymi bukvami, chtoby ih bylo legko otli-
chit' ot napisannyh strochnymi bukvami imen peremennyh. otme-
tim, chto v konce opredeleniya ne stavitsya tochka s zapyatoj.
Tak kak podstavlyaetsya vsya stroka, sleduyushchaya za opredelennym
imenem, to eto privelo by k slishkom bol'shomu chislu tochek s
zapyatoj v operatore FOR .







    Teper' my sobiraemsya rassmotret' semejstvo rodstvennyh
programm, prednaznachennyh dlya vypolneniya prostyh operacij
nad simvol'nymi dannymi. V dal'nejshem vy obnaruzhite, chto
mnogie programmy yavlyayutsya prosto rasshirennymi versiyami teh
prototipov, kotorye my zdes' obsuzhdaem.





    Standartnaya biblioteka vklyuchaet funkcii dlya chteniya i za-
pisi po odnomu simvolu za odin raz. funkciya GETCHAR() izvle-
kaet sleduyushchij vvodimyj simvol kazhdyj raz, kak k nej obrashcha-
yutsya, i vozvrashchaet etot simvol v kachestve svoego znacheniya.
|to znachit, chto posle

        C = GETCHAR()

peremennaya 'C' soderzhit sleduyushchij simvol iz vhodnyh dannyh.
Simvoly obychno postupayut s terminala, no eto ne dolzhno nas
kasat'sya do glavy 7.
    Funkciya PUTCHAR(C) yavlyaetsya dopolneniem k GETCHAR : v
rezul'tate obrashcheniya

        PUTCHAR (C)

soderzhimoe peremennoj 'C' vydaetsya na nekotoryj vyhodnoj no-
sitel', obychno opyat' na terminal. Obrashchenie k funkciyam
PUTCHAR i PRINTF mogut peremezhat'sya; vydacha budet poyavlyat'sya
v tom poryadke, v kotorom proishodyat obrashcheniya.
    Kak i funkciya PRINTF , funkcii GETCHAR i PUTCHAR ne so-
derzhat nichego ekstraordinarnogo. Oni ne vhodyat v sostav yazy-
ka "C", no k nim vsegda mozhno obratit'sya.





    Imeya v svoem rasporyazhenii tol'ko funkcii GETCHAR i
PUTCHAR vy mozhete, ne znaya nichego bolee ob operaciyah vvo-
da-vyvoda, napisat' udivitel'noe kolichestvo poleznyh prog-
ramm. Prostejshim primerom mozhet sluzhit' programma posimvol'-
nogo kopirovaniya vvodnogo fajla v vyvodnoj. Obshchaya shema ime-
et vid:
vvesti simvol
WHILE (simvol ne yavlyaetsya priznakom konca fajla)
  vyvesti tol'ko chto prochitannyj simvol
  vvesti novyj simvol

programma, napisannaya na yazyke "C", vyglyadit sleduyushchim obra-
zom:

 MAIN()  /* COPY INPUT TO OUTPUT; 1ST VERSION */
 {
    INT C;

    C = GETCHAR();
    WHILE (C != EOF) {
           PUTCHAR (C);
           C = GETCHAR();
    }
 }



operator otnosheniya != oznachaet "ne ravno".
     Osnovnaya problema zaklyuchaetsya v tom, chtoby zafiksiro-
vat' konec fajla vvoda. Obychno, kogda funkciya GETCHAR natal-
kivaetsya na konec fajla vvoda, ona vozvrashchaet znachenie , ne
yavlyayushcheesya dejstvitel'nym simvolom; takim obrazom, programma
mozhet ustanovit', chto fajl vvoda ischerpan. Edinstvennoe os-
lozhnenie, yavlyayushcheesya znachitel'nym neudobstvom, zaklyuchaetsya v
sushchestvovanii dvuh obshcheupotrebitel'nyh soglashenij o tom, ka-
koe znachenie fakticheski yavlyaetsya priznakom konca fajla. My
otsrochim reshenie etogo voprosa, ispol'zovav simvolicheskoe
imya EOF dlya etogo znacheniya, kakim by ono ni bylo. Na prakti-
ke EOF budet libo -1, libo 0, tak chto dlya pravil'noj raboty
pered programmoj dolzhno stoyat' sobstvenno libo

 #DEFINE  EOF  -1

libo

 #DEFINE  EOF   0

     Ispol'zovav simvolicheskuyu konstantu EOF dlya predstavle-
niya znacheniya, vozvrashchaemogo funkciej GETCHAR pri vyhode na
konec fajla, my obespechili, chto tol'ko odna velichina v prog-
ramme zavisit ot konkretnogo chislennogo znacheniya.
     My takzhe opisali peremennuyu 'C' kak INT , a ne CHAR , s
tem chtoby ona mogla hranit' znachenie, vozvrashchaemoe GETCHAR .
kak my uvidim v glave 2, eta velichina dejstvitel'no INT, tak
kak ona dolzhna byt' v sostoyanii v dopolnenie ko vsem vozmozh-
nym simvolam predstavlyat' i EOF.
     Programmistom, imeyushchim opyt raboty na "C", programma
kopirovaniya byla by napisana bolee szhato. V yazyke "C" lyuboe
prisvaivanie, takoe kak

 C = GETCHAR()

mozhet byt' ispol'zovano v vyrazhenii; ego znachenie - prosto
znachenie, prisvaivaemoe levoj chasti. Esli prisvaivanie sim-
vola peremennoj 'C' pomestit' vnutr' proverochnoj chasti ope-
ratora WHILE , to programma kopirovaniya fajla zapishetsya v
vide:

MAIN()  /* COPY INPUT TO OUTPUT; 2ND VERSION */
{
INT C;

WHILE ((C = GETCHAR()) != EOF)
PUTCHAR(C);
}

     Programma izvlekaet simvol , prisvaivaet ego peremennoj
'C' i zatem proveryaet, ne yavlyaetsya li etot simvol priznakom
konca fajla. Esli net - vypolnyaetsya telo operatora WHILE,
vyvodyashchee etot simvol. Zatem cikl WHILE povtoryaetsya. kogda,
nakonec, budet dostignut konec fajla vvoda, operator WHILE
zavershaetsya, a vmeste s nim zakanchivaetsya vypolnenie i funk-
cii MAIN .



     V etoj versii centralizuetsya vvod - v programme tol'ko
odno obrashchenie k funkcii GETCHAR - i uzhimaetsya programma.
Vlozhenie prisvaivaniya v proveryaemoe uslovie - eto odno iz
teh mest yazyka "C", kotoroe privodit k znachitel'nomu sokra-
shcheniyu programm. Odnako, na etom puti mozhno uvlech'sya i nachat'
pisat' nedostupnye dlya ponimaniya programmy. |tu tendenciyu my
budem pytat'sya sderzhivat'.
     Vazhno ponyat' , chto kruglye skobki vokrug prisvaivaniya v
uslovnom vyrazhenii dejstvitel'no neobhodimy. Starshinstvo
operacii != vyshe, chem operacii prisvaivaniya =, a eto oznacha-
et, chto v otsutstvie kruglyh skobok proverka usloviya != bu-
det vypolnena do prisvaivaniya =. Takim obrazom, operator

C = GETCHAR()  != EOF

ekvivalenten operatoru

C = (GETCHAR() != EOF)

     |to, vopreki nashemu zhelaniyu, privedet k tomu, chto 'C'
budet prinimat' znachenie 0 ili 1 v zavisimosti ot togo, na-
tolknetsya ili net GETCHAR na priznak konca fajla. Podrobnee
ob etom budet skazano v glave 2/.





     Sleduyushchaya programma podschityvaet chislo simvolov; ona
predstavlyaet soboj nebol'shoe razvitie programmy kopirovaniya.

 MAIN()  /* COUNT CHARACTERS IN INPUT */
 {
     LONG NC;

     NC = 0;
     WHILE (GETCHAR() != EOF )
    ++NC;
     PRINTF("%1D\N", NC);
 }

 Operator

    ++NC;

demonstriruet novuyu operaciyu, ++, kotoraya oznachaet uveliche-
nie na edinicu. Vy mogli by napisat' NC = NC + 1 , no ++NC
bolee kratko i zachastuyu bolee effektivno. Imeetsya sootvetst-
vuyushchaya operaciya -- umen'shenie na edinicu. Operacii ++ i --
mogut byt' libo prefiksnymi (++NC), libo postfiksnymi
(NC++); eti dve formy, kak budet pokazano v glave 2, imeyut v
vyrazheniyah razlichnye znacheniya, no kak ++NC, tak i NC++ uve-
lichivayut NC. Poka my budem priderzhivat'sya prefiksnyh opera-
cij.



     Programma podscheta simvolov nakaplivaet ih kolichestvo v
peremennoj tipa LONG, a ne INT . Na PDP-11 maksimal'noe zna-
chenie ravno 32767, i esli opisat' schetchik kak INT , to on
budet perepolnyat'sya dazhe pri sravnitel'no malom fajle vvoda;
na yazyke "C" dlya HONEYWELL i IBM tipy LONG i INT yavlyayutsya
sinonimami i imeyut znachitel'no bol'shij razmer. Specifikaciya
preobrazovaniya %1D ukazyvaet PRINTF , chto sootvetstvuyushchij
argument yavlyaetsya celym tipa LONG .
     CHtoby spravit'sya s eshche bol'shimi chislami, vy mozhete is-
pol'zovat' tip DOUBLE / FLOAT dvojnoj dliny/. my takzhe is-
pol'zuem operator FOR vmesto WHILE s tem, chtoby proillyustri-
rovat' drugoj sposob zapisi cikla.

 MAIN()  /* COUNT CHARACTERS IN INPUT */
 {
     DOUBLE  NC;

     FOR (NC = 0; GETCHAR() != EOF; ++NC)
    ;
     PRINTF("%.0F\N", NC);
 }

     Funkciya PRINTF ispol'zuet specifikaciyu %F kak dlya FLOAT
, tak i dlya DOUBLE ; specifikaciya %.0F podavlyaet pechat' ne-
sushchestvuyushchej drobnoj chasti.
     Telo operatora cikla FOR zdes' pusto, tak kak vsya rabo-
ta vypolnyaetsya v proverochnoj i reinicializacionnoj chastyah.
No grammaticheskie pravila yazyka "C" trebuyut, chtoby operator
FOR imel telo. Izolirovannaya tochka s zapyatoj, sootvetstvuyu-
shaya pustomu operatoru, poyavlyaetsya zdes', chtoby udovletvorit'
etomu trebovaniyu. My vydelili ee na otdel'nuyu stroku, chtoby
sdelat' ee bolee zametnoj.
     Prezhde chem my rasprostimsya s programmoj podscheta simvo-
lov, otmetim, chto esli fajl vvoda ne soderzhit nikakih simvo-
lov, to uslovie v WHILE ili FOR ne vypolnitsya pri samom per-
vom obrashchenii k GETCHAR , i, sledovatel'no , programma vy-
dast nul', t.e. Pravil'nyj otvet. eto vazhnoe zamechanie. od-
nim iz priyatnyh svojstv operatorov WHILE i FOR yavlyaetsya to,
chto oni proveryayut uslovie v nachale cikla, t.e. Do vypolneniya
tela. Esli delat' nichego ne nado, to nichego ne budet sdela-
no, dazhe esli eto oznachaet, chto telo cikla nikogda ne budet
vypolnyat'sya. programmy dolzhny dejstvovat' razumno, kogda oni
obrashchayutsya s fajlami tipa "nikakih simvolov". Operatory
WHILE i FOR pomogayut obespechit' pravil'noe povedenie prog-
ramm pri granichnyh znacheniyah proveryaemyh uslovij.





     Sleduyushchaya programma podschityvaet kolichestvo strok v
fajle vvoda. Predpolagaetsya, chto stroki vvoda zakanchivayutsya
simvolom novoj stroki \N, skrupulezno dobavlennym k kazhdoj
vypisannoj stroke.
MAIN()  /* COUNT LINES IN INPUT */
{
    INT C,NL;

    NL = 0;
    WHILE ((C = GETCHAR()) != EOF)
   IF (C =='\N')
       ++NL;
    PRINTF("%D\N", NL);
}

     Telo WHILE teper' soderzhit operator IF , kotoryj v svoyu
ochered' upravlyaet operatorom uvelicheniya ++NL. Operator IF
proveryaet zaklyuchennoe v kruglye skobki uslovie i, esli ono
istinno, vypolnyaet sleduyushchij za nim operator /ili gruppu
operatorov, zaklyuchennyh v figurnye skobki/. My opyat' ispol'-
zovali sdvig vpravo, chtoby pokazat', chto chem upravlyaet.
     Udvoennyj znak ravenstva == yavlyaetsya oboznacheniem v
yazyke "C" dlya "ravno" /analogichno .EQ. V fortrane/. |tot
simvol vveden dlya togo, chtoby otlichat' proverku na ravenstvo
ot odinochnogo =, ispol'zuemogo pri prisvaivanii. Poskol'ku v
tipichnyh "C" - programmah znak prisvaivaniya vstrechaetsya pri-
merno v dva raza chashche, chem proverka na ravenstvo, to estest-
venno, chtoby znak operatora byl vpolovinu koroche.
     Lyuboj otdel'nyj simvol mozhet byt' zapisan vnutri odi-
nochnyh kavychek, i pri etom emu sootvetstvuet znachenie, rav-
noe chislennomu znacheniyu etogo simvola v mashinnom nabore sim-
volov; eto nazyvaetsya simvol'noj konstantoj. Tak, naprimer,
'A' - simvol'naya konstanta; ee znachenie v nabore simvolov
ASCII /amerikanskij standartnyj kod dlya obmena informaciej/
ravno 65, vnutrennemu predstavleniyu simvola a. Konechno, 'A'
predpochtitel'nee, chem 65: ego smysl ocheviden i on ne zavisit
ot konkretnogo mashinnogo nabora simvolov.
     Uslovnye posledovatel'nosti, ispol'zuemye v simvol'nyh
strokah, takzhe zanimayut zakonnoe mesto sredi simvol'nyh kon-
stant. Tak v proverkah i arifmeticheskih vyrazheniyah '\N'
predstavlyaet znachenie simvola novoj stroki. Vy dolzhny tverdo
uyasnit', chto '\N' - otdel'nyj simvol, kotoryj v vyrazheniyah
ekvivalenten odinochnomu celomu; s drugoj storony "\N" - eto
simvol'naya stroka, kotoraya soderzhit tol'ko odin simvol. Vop-
ros o sopostavlenii strok i simvolov obsuzhdaetsya v glave 2.

    Uprazhnenie  1-6
    ----------------
     Napishite programmu dlya podscheta probelov, tabulyacij i
novyh strok.

    Uprazhnenie  1-7
    ----------------
     Napishite programmu, kotoraya kopiruet vvod na vyvod, za-
menyaya pri etom kazhduyu posledovatel'nost' iz odnogo ili bolee
probelov na odin probel.





     CHetvertaya programma iz nashej serii poleznyh programm
podschityvaet kolichestvo strok, slov i simvolov, ispol'zuya
pri etom ves'ma shirokoe opredelenie, chto slovom yavlyaetsya lyu-
baya posledovatel'nost' simvolov, ne soderzhashchaya probelov, ta-
bulyacij ili novyh strok. /|to - uproshchennaya versiya utility
'WC' sistemy 'UNIX'/



#DEFINE    YES  1
#DEFINE    NO   0

MAIN()  /* COUNT LINES, WORDS, CHARS IN INPUT */
{
   INT C, NL, NW, INWORD;

   INWORD = NO;
   NL = NW = NC = 0;
   WHILE((C = GETCHAR()) != EOF)  {
       ++NC;
       IF (C == '\N')
            ++NL;
       IF (C==' ' \!\! C=='\N' \!\! C=='\T')
            INWORD = NO;
       ELSE IF (INWORD == NO)  {
            INWORD = YES;
            ++NW;
       }
   }
   PRINTF("%D %D %D\N", NL, NW, NC);
}

     Kazhdyj raz, kogda programma vstrechaet pervyj simvol
slova, ona uvelichivaet schetchik chisla slov na edinicu. Pere-
mennaya INWORD sledit za tem, nahoditsya li programma v nasto-
yashchij moment vnutri slova ili net; snachala etoj peremennoj
prisvaivaetsya " ne v slove", chemu sootvetstvuet znachenie NO.
My predpochitaem simvolicheskie konstanty YES i NO liternym
znacheniyam 1 i 0, potomu chto oni delayut programmu bolee udob-
noj dlya chteniya. Konechno, v takoj kroshechnoj programme, kak
eta, eto ne privodit k zametnoj raznice, no v bol'shih prog-
rammah uvelichenie yasnosti vpolne stoit teh skromnyh dopolni-
tel'nyh usilij, kotoryh trebuet sledovanie etomu principu s
samogo nachala. Vy takzhe obnaruzhite, chto sushchestvennye izmene-
niya gorazdo legche vnosit' v te programmy, gde chisla figuri-
ruyut tol'ko v kachestve simvol'nyh konstant.
    Stroka

      NL = NW = NC = 0;

polagaet vse tri peremennye  ravnymi  nulyu.  |to  ne
osobyj sluchaj, a sledstvie togo obstoyatel'stva, chto operato-
ru prisvaivaniya sootvetstvuet nekotoroe znachenie i prisvai-
vaniya provodyatsya posledovatel'no sprava nalevo. Takim obra-
zom, delo obstoit tak, kak esli by my napisali

       NC = (NL = (NW = 0));

operaciya \!\! Oznachaet   OR  , tak chto stroka

       IF( C==' ' \!\! C=='\N' \!\! C=='\T')

govorit "esli s - probel, ili s - simvol novoj stroki, ili s
-tabulyaciya ..."./uslovnaya posledovatel'nost' \T yavlyaetsya
izobrazheniem simvola tabulyacii/.



   Imeetsya sootvetstvuyushchaya operaciya && dlya AND. Vyrazheniya,
 svyazannye operaciyami && ili \!\! , Rassmatrivayutsya sleva na
  pravo, i pri etom garantiruetsya, chto ocenivanie vyrazhenij
  budet prekrashcheno, kak tol'ko stanet yasno, yavlyaetsya li vse
  vyrazhenie istinnym ili lozhnym. Tak, esli 'C' okazyvaetsya
 probelom, to net nikakoj neobhodimosti proveryat', yavlyaetsya
li 'C' simvolom novoj stroki ili tabulyacii, i takie proverki
   dejstvitel'no ne delayutsya. V dannom sluchae eto ne imeet
 principial'nogo znacheniya, no, kak my skoro uvidim, v bolee
 slozhnyh situaciyah eta osobennost' yazyka ves'ma sushchestvenna.
  |tot primer takzhe demonstriruet operator ELSE yazyka "C",
 kotoryj ukazyvaet to dejstvie, kotoroe dolzhno vypolnyat'sya,
 esli uslovie, soderzhashcheesya v operatore IF, okazhetsya lozhnym.
                     Obshchaya forma takova:

 IF (vyrazhenie)
 operator-1
 ELSE operator-2

    Vypolnyaetsya odin i tol'ko odin iz dvuh operatorov, svya-
zannyh s konstrukciej IF-ELSE. Esli vyrazhenie istinno, vy-
polnyaetsya operator-1; esli net - vypolnyaetsya operator-2.
Fakticheski kazhdyj operator mozhet byt' dovol'no slozhnym. V
programme podscheta slov operator, sleduyushchij za ELSE , yavlya-
etsya opertorom IF , kotoryj upravlyaet dvumya operatorami v
figurnyh skobkah.

    Uprazhnenie  1-9
    ----------------
    Kak by vy stali proveryat' programmu podscheta slov ?
Kakie imeyutsya ogranicheniya ?

    Uprazhnenie  1-10
    -----------------
    Napishite programmu, kotoraya budet pechatat' slova iz faj-
la vvoda, prichem po odnomu na stroku.

    Uprazhnenie  1-11
    ----------------
    Peredelajte programmu podscheta slov, ispol'zuya luchshee
predelenie "slova"; schitajte, naprimer slovom posledovatel'-
nost' bukv, cifr i apostrofov, rachinayushchuyusya s bukvy.





    Davajte napishem programmu podscheta chisla poyavlenij kazh-
doj cifry, simvolov pustyh promezhutkov/probel, tabulyacii,
novaya stroka/ i vseh ostal'nyh simvolov. Konechno, takaya za-
dacha neskol'ko iskusstvenna, no ona pozvolit nam proillyust-
rirovat' v odnoj programme srazu neskol'ko aspektov yazyka
"C".
    My razbili vvodimye simvoly na dvenadcat' kategorij, i
nam udobnee ispol'zovat' massiv dlya hraneniya chisla poyavlenij
kazhdoj cifry, a ne desyat' otdel'nyh peremennyh. Vot odin iz
variantov programmy:
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;

    WHILE ((C = GETCHAR()) != EOF)
 IF (C >= '0' && C <= '9')
     ++NDIGIT[C-'0'];
 ELSE IF(C== ' ' \!\! C== '\N' \!\! C== '\T')
     ++NWHITE;
 ELSE
     ++NOTHER;

    PRINTF("DIGITS =");
    FOR (I = 0; I < 10; ++I)
    PRINTF(" %D", NDIGIT[I]);
    PRINTF("\NWHITE SPACE = %D, OTHER = %D\N",
     NWHITE, NOTHER);
}

Opisanie

   INT  NDIGIT[10];

ob座avlyaet, chto NDIGIT yavlyaetsya massivom iz desyati celyh. v
yazyke "C" indeksy massiva vsegda nachinayutsya s nulya /a ne s
1, kak v fortrane ili PL/1/, tak chto elementami massiva yav-
lyayutsya NDIGIT[0], NDIGIT[1],..., NDIGIT[9]. eta osobennost'
otrazhena v ciklah FOR , kotorye inicializiruyut i pechatayut
massiv.
     Indeks mozhet byt' lyubym celym vyrazheniem, kotoroe, ko-
nechno, mozhet vklyuchat' celye peremennye, takie kak I , i ce-
lye konstanty.
     |ta konkretnaya programma sil'no opiraetsya na svojstva
simvol'nogo predstavleniya cifr. Tak, naprimer, v programme
proverka

   IF( C >= '0' && C <= '9')...

opredelyaet, yavlyaetsya li simvol v 'C' cifroj, i esli eto tak,
to chislennoe znachenie etoj cifry opredelyaetsya po formule / C
- '0'/. Takoj sposob rabotaet tol'ko v tom sluchae, esli zna-
cheniya simvol'nyh konstant '0', '1' i t.d. Polozhitel'ny, ras-
polozheny v poryadke vozrastaniya i net nichego, krome cifr,
mezhdu konstantami '0' i '9'. K schast'yu, eto verno dlya vseh
obshcheprinyatyh naborov simvolov.



    Po opredeleniyu pered provedeniem arifmeticheskih opera-
cij, vovlekayushchih peremennye tipa CHAR i INT, vse oni preob-
razuyutsya k tipu INT, TAK chto v arifmeticheskih vyrazheniyah pe-
remennye tipa CHAR po sushchestvu identichny peremennym tipa
INT. |to vpolne estestvenno i udobno; naprimer, C -'0'- eto
celoe vyrazhenie so znacheniem mezhdu 0 i 9 v sootvetstvii s
tem, kakoj simvol ot '0' do '9' hranitsya v 'C', i, sledova-
tel'no, ono yavlyaetsya podhodyashchim indeksom dlya massiva NDIGIT.
    Vyyasnenie voprosa, yavlyaetsya li dannyj simvol cifroj,
simvolom pustogo promezhutka ili chem-libo eshche, osushchestvlyaetsya
posledovatel'nost'yu operatorov

 IF (C >= '0' && C <= '9')
 ++NDIGIT[C-'0'];
 ELSE IF(C == ' ' \!\! C == '\N' \!\! C == '\T')
 ++NWHITE;
 ELSE
 ++NOTHER;

Konstrukciya
    IF  (uslovie)
            operator
    ELSE IF  (uslovie)
            operator
    ELSE
            operator

chasto vstrechayutsya v programmah kak sredstvo vyrazheniya situa-
cij, v kotoryh osushchestvlyaetsya vybor odnogo iz neskol'kih
vozmozhnyh reshenij.
    Programma prosto dvizhetsya sverhu vniz do teh por, poka
ne udovletvoritsya kakoe-nibud' uslovie; togda vypolnyaetsya
sootvetstvuyushchij 'operator', i vsya konstrukciya zavershaetsya.
/Konechno, 'operator' mozhet sostoyat' iz neskol'kih operato-
rov, zaklyuchennyh v figurnye skobki/. Esli ni odno iz uslovij
ne udovletvoryaetsya, to vypolnyaetsya 'operator', stoyashchij posle
zaklyuchitel'nogo ELSE, esli ono prisutstvuet. Esli posledneE
ELSE i sootvetstvuyushchij 'operator' opushcheny (kak v programme
podscheta slov), to nikakih dejstvij ne proizvoditsya. Mezhdu
nachal'nym IF i konechnym ELSE mozhet pomeshchat'sya proizvol'noe
kolichestvo grupp

ELSE IF (uslovie)
   operator

    S tochki zreniya stilya celesoobrazno zapisyvat' etu konst-
rukciyu tak, kak my pokazali, s tem chtoby dlinnye vyrazheniya
ne zalezali za pravyj kraj stranicy.
    Operator SWITCH (pereklyuchatel'), kotoryj rassmatrivaetsya
v glave 3, predstavlyaet druguyu vozmozhnost' dlya zapisi raz-
vetvleniya na neskol'ko variantov. etot operator osobenno
udoben, kogda proveryaemoe vyrazhenie yavlyaetsya libo prosto ne-
kotorym celym, libo simvol'nym vyrazheniem, sovpadayushchim s od-
noj iz nekotorogo nabora konstant. Versiya etoj programmy,
ispol'zuyushchaya operator SWITCH, budet dlya sravneniya privedena
v glave 3.




    Uprazhnenie  1-12
    ----------------

    Napishite programmu, pechatayushchuyu gistogrammu dlin slov iz
fajla vvoda. Samoe legkoe - nachertit' gistogrammu gorizon-
tal'no; vertikal'naya orientaciya trebuet bol'shih usilij.





    V yazyke "C" funkcii ekvivalentny podprogrammam ili funk-
ciyam v fortrane ili proceduram v PL/1, paskale i t.d. Funk-
cii dayut udobnyj sposob zaklyucheniya nekotoroj chasti vychisle-
nij v chernyj yashchik, kotoryj v dal'nejshem mozhno ispol'zovat',
ne interesuyas' ego vnutrennim soderzhaniem. Ispol'zovanie
funkcij yavlyaetsya fakticheski edinstvennym sposobom spravit'sya
s potencial'noj slozhnost'yu bol'shih programm. Esli funkcii
organizovany dolzhnym obrazom, to mozhno ignorirovat' to, kak
delaetsya rabota; dostatochno znanie togo, chto delaetsya. YAzyk
"C" razrabotan takim obrazom, chtoby sdelat' ispol'zovanie
funkcij legkim, udobnym i effektivnym. Vam budut chasto vst-
rechat'sya funkcii dlinoj vsego v neskol'ko strochek, vyzyvae-
mye tol'ko odin raz, i oni ispol'zuyutsya tol'ko potomu, chto
eto proyasnyaet nekotoruyu chast' programmy.
    Do sih por my ispol'zovali tol'ko predostavlennye nam
funkcii tipa PRINTF, GETCHAR i PUTCHAR; teper' pora napisat'
neskol'ko nashih sobstvennyh. tak kak v "C" net operacii voz-
vedeniya v stepen', podobnoj operacii ** v fortrane ili PL/1,
davajte proillyustriruem mehaniku opredeleniya funkcii na pri-
mere funkcii POWER(M,N), vozvodyashchej celoe m v celuyu polozhi-
tel'nuyu stepen' N. Tak znachenie POWER(2,5) ravno 32. Konech-
no, eta funkciya ne vypolnyaet vsej raboty operacii **, pos-
kol'ku ona dejstvuet tol'ko s polozhitel'nymi stepenyami ne-
bol'shih chisel, no luchshe ne sozdavat' dopolnitel'nyh zatrud-
nenij, smeshivaya neskol'ko razlichnyh voprosov.
    Nizhe privoditsya funkciya POWER i ispol'zuyushchaya ee osnovnaya
programma, tak chto vy mozhete videt' celikom vsyu strukturu.

 MAIN()  /* TEST POWER FUNCTION */
 {
    INT I;

    FOR(I = 0; I < 10; ++I)
     PRINTF("%D %D %D\N",I,POWER(2,I),POWER(-3,I));
 }

 POWER(X,N)  /* RAISE  X  N-TH POWER; N > 0  */
 INT X,N;
 {
    INT I, P;
    P = 1;
    FOR (I =1; I <= N; ++I)
    P = P * X;
    RETURN (P);
 }
    Vse funkcii imeyut odinakovyj vid:
 imya (spisok argumentov, esli oni imeyutsya)
 opisanie argumentov, esli oni imeyutsya
 {
 opisaniya
    operatory
 }


    |ti funkcii mogut byt' zapisany v lyubom poryadke i naho-
dit'sya v odnom ili dvuh ishodnyh fajlah. Konechno, esli is-
hodnaya programma razmeshchaetsya v dvuh fajlah, vam pridetsya
dat' bol'she ukazanij pri kompilyacii i zagruzke, chem esli by
ona nahodilas' v odnom, no eto delo operacionnoj sistemy, a
ne atribut yazyka. V dannyj moment, dlya togo chtoby vse polu-
chennye svedeniya o progone "C"- programm, ne izmenilis' v
dal'nejshem, my budem predpolagat', chto obe funkcii nahodyatsya
v odnom i tom zhe fajle.
    Funkciya POWER vyzyvaetsya dvazhdy v stroke

PRINTF("%D %D %D\N",I,POWER(2,I),POWER(-3,I));

pri kazhdom obrashchenii funkciya POWER, poluchiv dva argumenta,
vazvrashchaet celoe znachenie, kotoroe pechataetsya v zadannom
formate. V vyrazheniyah POWER(2,I) yavlyaetsya tochno takim zhe ce-
lym, kak 2 i I. /Ne vse funkcii vydayut celoe znachenie; my
zajmemsya etim voprosom v glave 4/.
    Argumenty funkcii POWER dolzhny byt' opisany sootvetstvu-
yushchim obrazom, tak kak ih tipy izvestny. |to sdelano v stroke

INT X,N;

kotoraya sleduet za imenem funkcii.
    Opisaniya argumentov pomeshchayutsya mezhdu spiskom argumentov
i otkryvayushchejsya levoj figurnoj skobkoj; kazhdoe opisanie za-
kanchivaetsya tochkoj s zapyatoj. Imena, ispol'zovannye dlya ar-
gumentov funkcii POWER, yavlyayutsya chisto lokal'nymi i nedos-
tupny nikakim drugim funkciyam: drugie procedury mogut is-
pol'zovat' te zhe samye imena bez vozniknoveniya konflikta.
|to verno i dlya peremennyh I i P; I v funkcii POWER nikak ne
svyazano s I v funkcii MAIN.
    Znachenie, vychislennoe funkciej POWER, peredayutsya v MAIN
s pomoshch'yu operatora RETURN, tochno takogo zhe, kak v PL/1.
vnutri kruglyh skobok mozhno napisat' lyuboe vyrazhenie. Funk-
ciya ne obyazana vozvrashchat' kakoe-libo znachenie; operator
RETURN, ne soderzhashchij nikakogo vyrazheniya, privodit k takoj
zhe peredache upravleniya, kak "svalivanie na konec" funkcii
pri dostizhenii konechnoj pravoj figurnoj skobki, no pri etom
v vyzyvayushchuyu funkciyu ne vozvrashchaetsya nikakogo poleznogo zna-
cheniya.

    Uprazhnenie  1-13
    ----------------
    Apishite programmu preobrazovaniya propisnyh bukv iz ajla
vvoda v strochnye, ispol'zuya pri etom funkciyu OWER(C), koto-
raya vozvrashchaet znachenie 'C', esli C'- ne bukva, i znachenie
sootvetstvuyushchej strochnoj ukvy, esli 'C'-bukva.







    Odin aspekt v "C" mozhet okazat'sya neprivychnym dlya prog-
rammistov, kotorye ispol'zovali drugie yazyki, v chastnosti,
fortran i PL/1. v yazyke "C" vse argumenty funkcij peredayutsya
"po znacheniyu". eto oznachaet, chto vyzvannaya funkciya poluchaet
znacheniya svoih argumentov s pomoshch'yu vremennyh peremennyh
/fakticheski cherez stek/, a ne ih adresa. |to privodit k ne-
kotorym osobennostyam, otlichnym ot teh, s kotorymi my stalki-
valis' v yazykah tipa fortrana i PL/1, ispol'zuyushchih "vyzov po
ssylke ", gde vyzvannaya procedura rabotaet s adresom argu-
menta, a ne s ego znacheniem.
    Glavnoe otlichie sostoit v tom, chto v "C" vyzvannaya funk-
ciya ne mozhet izmenit' peremennuyu iz vyzyvayushchej funkcii; ona
mozhet menyat' tol'ko svoyu sobstvennuyu vremennuyu kopiyu.
    Vyzov po znacheniyu, odnako, ne pomeha, a ves'ma cennoe
kachestvo. Ono obychno privodit k bolee kompaktnym programmam,
soderzhashchim men'she ne otnosyashchihsya k delu peremennyh, potomu
chto s argumentami mozhno obrashchat'sya kak s udobno inicializi-
rovannymi lokal'nymi peremnnymi vyzvannoj procedury. Vot,
naprimer, variant funkcii POWER ispol'zuyushchej eto obstoyatel'-
stvo

 POWER(X,N)  /* RAISE  X  N-TH POWER; N > 0;
           VERSION 2 */
 INT X,N;
 {
 INT P;

 FOR (P = 1; N > 0; --N)
      P = P * X;
 RETURN (P);
 }


    Argument N ispol'zuetsya kak vremennaya peremennaya; iz ne-
go vychitaetsya edinica do teh por, poka on ne stanet nulem.
Peremennaya I zdes' bol'she ne nuzhna. chtoby ni proishodilo s N
vnutri POWER eto nikak ne vliyaet na argument, s kotorym per-
vonachal'no obratilis' k funkcii POWER.
    Pri neobhodimosti vse zhe mozhno dobit'sya, chtoby funkciya
izmenila peremennuyu iz vyzyvayushchej programmy. |ta programma
dolzhna obespechit' ustanovlenie adresa peremennoj /tehniches-
ki, cherez ukazatel' na peremennuyu/, a v vyzyvaemoj funkcii
nado opisat' sootvetstvuyushchij argument kak ukazatel' i ssy-
lat'sya k fakticheskoj peremennoj kosvenno cherez nego. My ras-
smotrim eto podrobno v glave 5.
    Kogda v kachestve argumenta vystupaet imya massiva, to
fakticheskim znacheniem, peredavaemym funkcii, yavlyaetsya adres
nachala massiva. /Zdes' net nikakogo kopirovaniya elementov
massiva/. S pomoshch'yu indeksacii i adresa nachala funkciya mozhet
najti i izmenit' lyuboj element massiva. |to - tema sleduyushche-
go razdela.







    Po-vidimomu samym obshchim tipom massiva v "C" yavlyaetsya
massiv simvolov. CHtoby proillyustrirovat' ispol'zovanie mas-
sivov simvolov i obrabatyvayushchih ih funkcij, davajte napishem
programmu, kotoraya chitaet nabor strok i pechataet samuyu dlin-
nuyu iz nih. Osnovnaya shema programmy dostatochno prosta:

WHILE (imeetsya eshche stroka)
  IF (eta stroka dlinnee samoj dlinnoj iz
  predydushchih)
     zapomnit' etu stroku i ee dlinu
napechatat' samuyu dlinnuyu stroku
    Po etoj sheme yasno, chto programma estestvennym obrazom
raspadaetsya na neskol'ko chastej. Odna chast' chitaet novuyu
stroku, drugaya proveryaet ee, tret'ya zapominaet, a ostal'nye
chasti programmy upravlyayut etim processom.
    Poskol'ku vse tak prekrasno delitsya, bylo by horosho i
napisat' programmu sootvetsvuyushchim obrazom. Davajte snachala
napishem otdel'nuyu funkciyu GETLINE, kotoraya budet izvlekat'
sleduyushchuyu stroku iz fajla vvoda; eto - obobshchenie funkcii
GETCHAR. my popytaemsya sdelat' etu funkciyu po vozmozhnosti
bolee gibkoj, chtoby ona byla poleznoj i v drugih situaciyah.
Kak minimum GETLINE dolzhna peredavat' signal o vozmozhnom po-
yavlenii konca fajla; bolee obshchij poleznyj variant mog by pe-
redavat' dlinu stroki ili nul', esli vstretitsya konec fajla.
nul' ne mozhet byt' dlinoj stroki, tak kak kazhdaya stroka so-
derzhit po krajnej mere odin simvol; dazhe stroka, soderzhashchaya
tol'ko simvol novoj stroki, imeet dlinu 1.
    Kogda my nahodim stroku, kotoraya dlinnee samoj dlinnoj
iz predydushchih, to ee nado gde-to zapomnit'. |to navodit na
mysl' o drugoj funkcii, COPY , kotoraya budet kopirovat' no-
vuyu stroku v mesto hraneniya.
    Nakonec, nam nuzhna osnovnaya programma dlya upravleniya
funkciyami GETLINE i COPY . Vot rezul'tat :

#DEFINE  MAXLINE  1000 /* MAXIMUM INPUT
       LINE SIZE */
 MAIN()  /* FIND LONGEST LINE */
 {
 INT LEN; /* CURRENT LINE LENGTH */
 INT MAX; /* MAXIMUM LENGTH SEEN SO FAR */
 CHAR LINE[MAXLINE]; /* CURRENT INPUT LINE */
 CHAR SAVE[MAXLINE]; /* LONGEST LINE, SAVED */

 MAX = 0;
 WHILE ((LEN = GETLINE(LINE, MAXLINE)) > 0)
     IF (LEN > MAX) {
    MAX = LEN;
    COPY(LINE, SAVE);
     }
     IF (MAX > 0)   /* THERE WAS A LINE */
    PRINTF("%S", SAVE);
 }
 GETLINE(S,LIM) /* GET LINE INTO S,RETURN LENGTH */
 CHAR S[];
 INT LIM;
 {
    INT C, I;

    FOR(I=0;I<LIM-1 && (C=GETCHAR())!=EOF && C!='\N';++I)
    S[I] = C;
    IF (C == '\N')  {
  S[I] = C;
  ++I;
    }
    S[I] = '\0';
    RETURN(I);
 }

 COPY(S1, S2)    /* COPY S1 TO S2;
               ASSUME S2 BIG ENOUGH */
 CHAR S1[], S2[];
 {
 INT I;

 I = 0;
 WHILE ((S2[I] = S1[I] != '\0')
    ++I;
 }

    Funkciya MAIN i GETLINE obshchayutsya kak cherez paru argumen-
tov, tak i cherez vozvrashchaemoe znachenie. argumenty GETLINE
opisany v strokah

    CHAR S[];
    INT LIM;

kotorye ukazyvayut, chto pervyj argument yavlyaetsya massivom, a
vtoroj - celym.
    Dlina massiva S ne ukazana, tak kak ona opredelena v
MAIN . funkciya GETLINE ispol'zuet operator RETURN dlya pere-
dachi znacheniya nazad v vyzyvayushchuyu programmu tochno tak zhe, kak
eto delala funkciya POWER. Odni funkcii vozvrashchayut nekotoroe
nuzhnoe znachenie; drugie, podobno COPY, ispol'zuyutsya iz-za ih
dejstviya i ne vozvrashchayut nikakogo znacheniya.
    CHtoby pometit' konec stroki simvolov, funkciya GETLINE
pomeshchaet v konec sozdavaemogo ej massiva simvol \0 /nulevoj
simvol, znachenie kotorogo ravno nulyu/. |to soglashenie is-
pol'zuetsya takzhe kompilyatorom s yazyka "C": kogda v "C" -
programme vstrechaetsya strochnaya konstanta tipa

   "HELLO\N"



to kompilyator sozdaet massiv simvolov, soderzhashchij simvoly
etoj stroki, i zakanchivaet ego simvolom \0, s tem chtoby fun-
kcii, podobnye PRINTF, mogli zafiksirovat' konec massiva:

-------------------------------------------
!  H !  E  !  L  !  L  !  O  ! \N  ! \0 !
-------------------------------------------

Specifikaciya formata %S ukazyvaet, chto PRINTF ozhidaet stro-
ku, predstavlennuyu v takoj forme. Proanalizirovav funkciyu
COPY, vy obnaruzhite, chto i ona opiraetsya na tot fakt, chto ee
vhodnoj argument okanchivaetsya simvolom \0, i kopiruet etot
simvol v vyhodnoj argument S2. /Vse eto podrazumevaet, chto
simvol \0 ne yavlyaetsya chast'yu normal'nogo teksta/.
    Mezhdu prochim, stoit otmetit', chto dazhe v takoj malen'koj
programme, kak eta, voznikaet neskol'ko nepriyatnyh organiza-
cionnyh problem. Naprimer, chto dolzhna delat' MAIN, esli ona
vstretit stroku, prevyshayushchuyu ee maksimal'no vozmozhnyj raz-
mer? Funkciya GETLINE postupaet razumno: pri zapolnenii mas-
siva ona prekrashchaet dal'nejshee izvlechenie simvolov, dazhe es-
li ne vstrechaet simvola novoj stroki. Proveriv poluchennuyu
dlinu i poslednij simvol, funkciya MAIN mozhet ustanovit', ne
byla li eta stroka slishkom dlinnoj, i postupit' zatem, kak
ona sochtet nuzhnym. Radi kratkosti my opustili etu problemu.
    Pol'zovatel' funkcii GETLINE nikak ne mozhet zaranee uz-
nat', naskol'ko dlinnoj okazhetsya vvodimaya stroka. Poetomu v
GETLINE vklyuchen kontrol' perepolneniya. v to zhe vremya pol'zo-
vatel' funkcii COPY uzhe znaet /ili mozhet uznat'/, kakov raz-
mer strok, tak chto my predpochli ne vklyuchat' v etu funkciyu
dopolnitel'nyj kontrol'.

    Uprazhnenie  1-14
    -----------------
    Peredelajte vedushchuyu chast' programmy poiska samoj dlinnoj
stroki takim obrazom, chtoby ona pravil'no pechatala dliny
skol' ugodno dlinnyh vvodimyh strok i vozmozhno bol'shij
tekst.

    Uprazhnenie   1-15
    -----------------
    Napishite programmu pechati vseh strok dlinnee 80 simvo-
lov.

    Uprazhnenie  1-16
    -----------------
    Napishite programmu, kotoraya budet udalyat' iz kazhdoj
stroki stoyashchie v konce probely i tabulyacii, a takzhe stroki,
celikom sostoyashchie iz probelov.

    Uprazhnenie  1-17
    -----------------
    Napishite funkciyu REVERSE(S), kotoraya raspologaet sim-
vol'nuyu stroku S v obratnom poryadke. S ee pomoshch'yu napishite
programmu, kotoraya obratit kazhduyu stroku iz fajla vvoda.







    Peremennye v MAIN(LINE, SAVE i t.d.) yavlyayutsya vnutrenni-
mi ili lokal'nymi po otnosheniyu k funkcii MAIN, potomu chto
oni opisany vnutri MAIN i nikakaya drugaya funkciya ne imeet k
nim pryamogo dostupa. |to zhe verno i otnositel'no peremennyh
v drugih funkciyah; naprimer, peremennaya I v funkcii GETLINE
nikak ne svyazana s I v COPY. Kazhdaya lokal'naya peremennaya su-
shchestvuet tol'ko togda, kogda proizoshlo obrashchenie k sootvets-
tvuyushchej funkcii, i ischezaet, kak tol'ko zakonchitsya vypolne-
nie etoj funkcii. Po etoj prichine takie peremennye, sleduya
terminologii drugih yazykov, obychno nazyvayut avtomaticheskimi.
My vpred' budem ispol'zovat' termin avtomaticheskie pri ssyl-
ke na eti dinamicheskie lokal'nye peremennye. /v glave 4 ob-
suzhdaetsya klass staticheskoj pamyati, kogda lokal'nye peremen-
nye vse zhe okazyvayutsya v sostoyanii sohranit' svoi znacheniya
mezhdu obrashcheniyami k funkciyam/.
    Poskol'ku avtomaticheskie peremennye poyavlyayutsya i ischeza-
yut vmeste s obrashcheniem k funkcii, oni ne sohranyayut svoih
znachenij v promezhutke ot odnogo vyzova do drugogo, v silu
chego im pri kazhdom vhode nuzhno yavno prisvaivat' znacheniya.
Esli etogo ne sdelat', to oni budut soderzhat' musor.
    V kachestve al'ternativy k avtomaticheskim peremennym mozh-
no opredelit' peremennye, kotorye budut vneshnimi dlya vseh
funkcij, t.e. Global'nymi peremennymi, k kotorym mozhet obra-
tit'sya po imeni lyubaya funkciya, kotoraya pozhelaet eto sdelat'.
(etot mehanizm ves'ma shoden s "COMMON" v fortrane i
"EXTERNAL" v PL/1). Tak kak vneshnie peremennye dostupny vsyu-
du, ih mozhno ispol'zovat' vmesto spiska argumentov dlya pere-
dachi dannyh mezhdu funkciyami. Krome togo, poskol'ku vneshnie
peremennye sushchestvuyut postoyanno, a ne poyavlyayutsya i ischezayut
vmeste s vyzyvaemymi funkciyami, oni sohranyayut svoi znacheniya
i posle togo, kak funkcii, prisvoivshie im eti znacheniya, za-
vershat svoyu rabotu.
    Vneshnyaya peremennaya dolzhna byt' opredelena vne vseh funk-
cij; pri etom ej vydelyaetsya fakticheskoe mesto v pamyati. Ta-
kaya peremennaya dolzhna byt' takzhe opisana v kazhdoj funkcii,
kotoraya sobiraetsya ee ispol'zovat'; eto mozhno sdelat' libo
yavnym opisaniem EXTERN, libo neyavnym po kontekstu. CHtoby
sdelat' obsuzhdenie bolee konkretnym, davajte perepishem prog-
rammu poiska samoj dlinnoj stroki, sdelav LINE, SAVE i MAX
vneshnimi peremennymi. |to potrebuet izmeneniya opisanij i tel
vseh treh funkcij, a takzhe obrashchenij k nim.

#DEFINE MAXLINE 1000   /* MAX. INPUT LINE SIZE*/

CHAR   LINE[MAXLINE];  /* INPUT LINE */
CHAR   SAVE[MAXLINE];/* LONGEST LINE SAVED HERE*/
INT   MAX;/*LENGTH OF LONGEST LINE SEEN SO FAR*/
MAIN()  /*FIND LONGEST LINE; SPECIALIZED VERSION*/
{
   INT     LEN;
   EXTERN  INT   MAX;
   EXTERN  CHAR   SAVE[];
   MAX = 0;

WHILE ( (LEN = GETLINE()) > 0 )
   IF ( LEN > MAX )  {
           MAX = LEN;
           COPY();
   }
IF ( MAX > 0 )  /* THERE WAS A LINE */
   PRINTF( "%S", SAVE );
}

GETLINE()       /* SPECIALIZED VERSION */
{
   INT C, I;
   EXTERN CHAR LINE[];

   FOR (I = 0; I < MAXLINE-1


       && (C=GETCHAR()) !=EOF && C!='\N'; ++I)
            LINE[I] = C;
            ++I;
    }
    LINE[I] = '\0'
    RETURN(I)
 }
 COPY()  /* SPECIALIZED VERSION */
 {
    INT I;
    EXTERN CHAR LINE[], SAVE[];

    I = 0;
    WHILE ((SAVE[I] = LINE[I]) !='\0')
            ++I;
 }

    Vneshnie peremennye dlya funkcij MAIN, GETLINE i COPY op-
redeleny v pervyh strochkah privedennogo vyshe primera, koto-
rymi ukazyvaetsya ih tip i vyzyvaetsya otvedenie dlya nih pamya-
ti. sintaksicheski vneshnie opisaniya tochno takie zhe, kak opi-
saniya, kotorye my ispol'zovali ranee, no tak kak oni raspo-
lozheny vne funkcij, sootvetstvuyushchie peremennye yavlyayutsya
vneshnimi. CHtoby funkciya mogla ispol'zovat' vneshnyuyu pereme-
nuyu, ej nado soobshchit' ee imya. Odin sposob sdelat' eto -
vklyuchit' v funkciyu opisanie EXTERN; eto opisanie otlichaetsya
ot predydushchih tol'ko dobavleniem klyuchevogo slova EXTERN.



    V opredelennyh situaciyah opisanie EXTERN mozhet byt' opu-
shcheno: esli vneshnee opredelenie peremennoj nahoditsya v tom zhe
ishodnom fajle, ran'she ee ispol'zovaniya v nekotoroj konkret-
noj funkcii, to ne obyazatel'no vklyuchat' opisanie EXTERN dlya
etoj peremennoj v samu funkciyu. Opisaniya EXTERN v funkciyah
MAIN, GETLINE i COPY yavlyayutsya, takim obrazom, izlishnimi.
Fakticheski, obychnaya praktika zaklyuchaetsya v pomeshchenii oprede-
lenij vseh vneshnih peremennyh v nachale ishodnogo fajla i
posleduyushchem opuskanii vseh opisanij EXTERN.
    Esli programma nahoditsya v neskol'kih ishodnyh fajlah, i
nekotoraya peremennaya opredelena, skazhem v fajle 1, a ispol'-
zuetsya v fajle 2, to chtoby svyazat' eti dva vhozhdeniya pere-
mennoj, neobhodimo v fajle 2 ispol'zovat' opisanie EXTERN.
|tot vopros podrobno obsuzhdaetsya v glave 4.
    Vy dolzhno byt' zametili, chto my v etom razdele pri ssyl-
ke na vneshnie peremennye ochen' akkuratno ispol'zuem slova
opisanie i opredelenie. "Opredelenie" otnositsya k tomu mes-
tu, gde peremennaya fakticheski zavoditsya i ej vydelyaetsya pa-
myat'; "opisanie" otnositsya k tem mestam, gde ukazyvaetsya
priroda peremennoj, no nikakoj pamyati ne otvoditsya.
    Mezhdu prochim, sushchestvuet tendenciya ob座avlyat' vse, chto ni
popadetsya, vneshnimi peremennymi, poskol'ku kazhetsya, chto eto
uproshchaet svyazi, - spiski argumentov stanovyatsya koroche i pe-
remennye vsegda prisutstvuyut, kogda by vam oni ni ponadobi-
lis'. No vneshnie peremennye prisutstvuyut i togda, kogda vy v
nih ne nuzhdaetes'. Takoj stil' programmirovaniya chrevat opas-
nost'yu, tak kak on privodit k programmam, svyazi dannyh vnut-
ri kotoryh ne vpolne ochevidny. Peremennye pri etom mogut iz-
menyat'sya neozhidannym i dazhe neumyshlennym obrazom, a program-
my stanovitsya trudno modificirovat', kogda voznikaet takaya
neobhodimost'. Vtoraya versiya programmy poiska samoj dlinnoj
stroki ustupaet pervoj otchasti po etim prichinam, a otchasti
potomu, chto ona lishila universal'nosti dve ves'ma poleznye
funkcii, vvedya v nih imena peremennyh, s kotorymi oni budut
manipulirovat'.

    Uprazhnenie 1-18
    ---------------
    Proverka v operatore FOR funkcii GETLINE dovol'no neuk-
lyuzha. Perepishite programmu takim obrazom, chtoby sdelat' etu
proverku bolee yasnoj, no sohranite pri etom to zhe samoe po-
vedenie v konce fajla i pri perepolnenii bufera. YAvlyaetsya li
eto povedenie samym razumnym?





    Na dannom etape my obsudili to, chto mozhno by nazvat'
tradicionnym yadrom yazyka "C". Imeya etu gorst' stroitel'nyh
blokov, mozhno pisat' poleznye programmy ves'ma znachitel'nogo
razmera, i bylo by veroyatno neplohoj ideej, esli by vy za-
derzhalis' zdes' na kakoe-to vremya i postupili takim obrazom:
sleduyushchie nizhe uprazhneniya predlagayut vam ryad programm nes-
kol'ko bol'shej slozhnosti, chem te, kotorye byli privedeny v
etoj glave.



    Posle togo kak vy ovladeete etoj chast'yu "C", pristupajte
k chteniyu sleduyushchih neskol'kih glav. Usiliya, kotorye vy pri
etom zatratite, polnost'yu okupyatsya, potomu chto v etih glavah
obsuzhdayutsya imenno te storony "C", gde moshch' i vyrazitel'-
nost' yazyka nachinaet stanovit'sya ochevidnoj.

    Uprazhnenie 1-19
   ---------------
    Napishite programmu DETAB, kotoraya zamenyaet tabulyacii vo
vvode na nuzhnoe chislo probelov tak, chtoby promezhutok dosti-
gal sleduyushchej tabulyacionnoj ostanovki. Predpolozhite fiksiro-
vannyj nabor tabulyacionnyh ostanovok, naprimer, cherez kazhdye
N pozicij.

    Uprazhnenie 1-20
    ----------------
    Napishite programmu ENTAB, kotoraya zamenyaet stroki probe-
lov minimal'nym chislom tabulyacij i probelov, dostigaya pri
etom teh zhe samyh promezhutkov. Ispol'zujte te zhe tabulyacion-
nye ostanovki, kak i v DETAB.

    Uprazhnenie 1-21
    ----------------
    Napishite programmu dlya "sgibaniya" dlinnyh vvodimyh strok
posle poslednego otlichnogo ot probela simvola, stoyashchego do
stolbca N vvoda, gde N - parametr. ubedites', chto vasha prog-
ramma delaet chto-to razumnoe s ochen' dlinnymi strokami i v
sluchae, kogda pered ukazannym stolbcom net ni tabulyacij, ni
probelov.

    Uprazhnenie 1-22
    ----------------
    Napishite programmu udaleniya iz "C"-programmy vseh kom-
mentariev. Ne zabyvajte akkuratno obrashchat'sya s "zakavychenny-
mi" strokami i simvol'nymi konstantami.

    Uprazhnenie 1-23
    ----------------
    Napishite programmu proverki "C"-programmy na elementar-
nye sintaksicheskie oshibki, takie kak nesootvetstvie kruglyh,
kvadratnyh i figurnyh skobok. Ne zabud'te o kavychkah, kak
odinochnyh, tak i dvojnyh, i o kommentariyah. (|ta programma
ves'ma slozhna, esli vy budete pisat' ee dlya samogo obshchego
sluchaya).







    Peremennye i konstanty yavlyayutsya osnovnymi ob容ktami, s
kotorymi operiruet programma. Opisaniya perechislyayut peremen-
nye, kotorye budut ispol'zovat'sya, ukazyvayut ih tip i, voz-
mozhno, ih nachal'nye znacheniya. Operacii opredelyayut, chto s ni-
mi budet sdelano. vyrazheniya ob容dinyayut peremennye i konstan-
ty dlya polucheniya novyh znachenij. Vse eto - temy nastoyashchej
glavy.





    Hotya my etogo srazu pryamo ne skazali, sushchestvuyut nekoto-
rye ogranicheniya na imena peremennyh i simvolicheskih kons-
tant. Imena sostavlyayutsya iz bukv i cifr; pervyj simvol dol-
zhen byt' bukvoj. Podcherkivanie "_" tozhe schitaetsya bukvoj;
eto polezno dlya udobochitaemosti dlinnyh imen peremennyh.
Propisnye i strochnye bukvy razlichayutsya; tradicionnaya prakti-
ka v "s" - ispol'zovat' strochnye bukvy dlya imen peremennyh,
a propisnye - dlya simvolicheskih konstant.
    Igrayut rol' tol'ko pervye vosem' simvolov vnutrennego
imeni, hotya ispol'zovat' mozhno i bol'she. Dlya vneshnih imen,
takih kak imena funkcij i vneshnih peremennyh, eto chislo mo-
zhet okazat'sya men'she vos'mi, tak kak vneshnie imena ispol'zu-
yutsya razlichnymi assemblerami i zagruzchikami. Detali privo-
dyatsya v prilozhenii a. Krome togo, takie klyuchevye slova kak
IF, ELSE, INT, FLOAT i t.d., zarezervirovany: vy ne mozhete
ispol'zovat' ih v kachestve imen peremennyh. (Oni pishutsya
strochnymi bukvami).
    Konechno, razumno vybirat' imena peremennyh takim obra-
zom, chtoby oni oznachali nechto, otnosyashcheesya k naznacheniyu pe-
remennyh, i chtoby bylo menee veroyatno sputat' ih pri napisa-
nii.





    YAzyke "C" imeetsya tol'ko neskol'ko osnovnyh tipov dan-
nyh:
CHAR odin bajt, v kotorom mozhet nahodit'sya odin simvol iz
vnutrennego nabora simvolov.
INT Celoe, obychno sootvetstvuyushchee estestvennomu razmeru ce-
lyh v ispol'zuemoj mashine.
FLOAT S plavayushchej tochkoj odinarnoj tochnosti.
DOUBLE S plavayushchej tochkoj dvojnoj tochnosti.
    Krome togo imeetsya ryad kvalifikatorov, kotorye mozhno is-
pol'zovat' s tipom INT: SHORT (korotkoe), LONG (dlinnoe) i
UNSIGNED (bez znaka). Kvalifikatory SHORT i LONG ukazyvayut
na razlichnye razmery celyh. CHisla bez znaka podchinyayutsya za-
konam arifmetiki po modulyu 2 v stepeni N, gde N - chislo bi-
tov v INT; chisla bez znakov vsegda polozhitel'ny. Opisaniya s
kvalifikatorami imeyut vid:

   SHORT INT X;
   LONG INT Y;
   UNSIGNED INT Z;



    Clovo INT v takih situaciyah mozhet byt' opushcheno, chto
obychno i delaetsya.
    Kolichestvo bitov, otvodimyh pod eti ob容kty zavisit ot
imeyushchejsya mashiny; v tablice nizhe privedeny nekotorye harak-
ternye znacheniya.

           Tablica 1
  ---------------------------------------------------------
              !
   DEC PDP-11   HONEYWELL    IBM 370     INTERDATA  !
              6000          8/32     !
              !
       ASCII       ASCII       EBCDIC      ASCII    !
              !
    CHAR     8-BITS      9-BITS      8-BITS      8-BITS   !
    INT     16          36       32         32       !
    SHORT    16          36       16         16       !
    LONG     32          36       32         32       !
    FLOAT    32          36       32         32       !
    DOUBLE   64          72       64         64       !
              !
  ---------------------------------------------------------

    Cel' sostoit v tom, chtoby SHORT i LONG davali vozmozh-
nost' v zavisimosti ot prakticheskih nuzhd ispol'zovat' raz-
lichnye dliny celyh; tip INT otrazhaet naibolee "estestvennyj"
razmer konkretnoj mashiny. Kak vy vidite, kazhdyj kompilyator
svobodno interpretiruet SHORT i LONG v sootvetstvii so svoi-
mi apparatnymi sredstvami. Vse, na chto vy mozhete tverdo po-
lagat'sya, eto to, chto SHORT ne dlinnee, chem LONG.





    Konstanty tipa INT i FLOAT my uzhe rassmotreli. Otmetim
eshche tol'ko, chto kak obychnaya

 123.456e-7,

tak i "nauchnaya" zapis'

 0.12e3

dlya FLOAT yavlyaetsya zakonnoj.
    Kazhdaya konstanta s plavayushchej tochkoj schitaetsya imeyushchej
tip DOUBLE, tak chto oboznachenie "E" sluzhit kak dlya FLOAT,
tak i dlya DOUBLE.
    Dlinnye konstanty zapisyvayutsya v vide 123L. Obychnaya ce-
laya konstanta, kotoraya slishkom dlinna dlya tipa INT, rassmat-
rivaetsya kak LONG.



    Sushchestvuet sistema oboznachenij dlya vos'merichnyh i shest-
nadcaterichnyh konstant: lidiruyushchij 0(nul') v konstante tipa
INT ukazyvaet na vos'merichnuyu konstantu, a stoyashchie vperedi
0X sootvetstvuyut shestnadcaterichnoj konstante. Naprimer, de-
syatichnoe chislo 31 mozhno zapisat' kak 037 v vos'merichnoj for-
me i kak 0X1F v shestnadcaterichnoj. SHestnadcaterichnye i vos'-
merichnye konstanty mogut takzhe zakanchivat'sya bukvoj L, chto
delaet ih otnosyashchimisya k tipu LONG.





    Simvol'naya konstanta - eto odin simvol, zaklyuchennyj v
odinarnye kavychki, kak, naprimer, 'h'. Znacheniem simvol'noj
konstanty yavlyaetsya chislennoe znachenie etogo simvola vo vnut-
rennem mashinnom nabore simvolov. Naprimer, v nabore simvolov
ASCII simvol'nyj nul', ili '0', imeet znachenie 48, a v kode
EBCDIC - 240, i oba eti znacheniya sovershenno otlichny ot chisla
0. Napisanie '0' vmesto chislennogo znacheniya, takogo kak 48
ili 240, delaet programmu ne zavisyashchej ot konkretnogo chis-
lennogo predstavleniya etogo simvola v dannoj mashine. Sim-
vol'nye konstanty tochno tak zhe uchastvuyut v chislennyh opera-
ciyah, kak i lyubye drugie chisla, hotya naibolee chasto oni is-
pol'zuyutsya v sravnenii s drugimi simvolami. Pravila preobra-
zovaniya budut izlozheny pozdnee.
    Nekotorye negraficheskie simvoly mogut byt' predstavleny
kak simvol'nye konstanty s pomoshch'yu uslovnyh posledovatel'-
nostej, kak, naprimer, \N (novaya stroka), \T (tabulyaciya), \0
(nulevoj simvol), \\ (obratnaya kosaya cherta), \' (odinarnaya
kavychka) i t.d. Hotya oni vyglyadyat kak dva simvola, na samom
dele yavlyayutsya odnim. Krome togo, mozhno sgenerirovat' proiz-
vol'nuyu posledovatel'nost' dvoichnyh znakov razmerom v bajt,
esli napisat'

   '\DDD'

gde DDD - ot odnoj do treh vos'merichnyh cifr, kak v

   #DEFINE  FORMFEED  '\014'  /* FORM FEED */

    Simvol'naya konstanta '\0', izobrazhayushchaya simvol so znache-
niem 0, chasto zapisyvaetsya vmesto celoj konstanty 0 , chtoby
podcherknut' simvol'nuyu prirodu nekotorogo vyrazheniya.





    Konstantnoe vyrazhenie - eto vyrazhenie, sostoyashchee iz od-
nih konstant. Takie vyrazheniya obrabatyvayutsya vo vremya kompi-
lyacii, a ne pri progone programmy, i sootvetstvenno mogut
byt' ispol'zovany v lyubom meste, gde mozhno ispol'zovat' kon-
stantu, kak, naprimer v
  #DEFINE MAXLINE 1000
  CHAR LINE[MAXLINE+1];

ili

 SECONDS = 60 * 60 * HOURS;





    Strochnaya konstanta - eto posledovatel'nost', sostoyashchaya
iz nulya ili bolee simvolov, zaklyuchennyh v dvojnye kavychki,
kak, naprimer,

  "I AM A STRING"    /* ya - stroka */
ili
  ""    /* NULL STRING */     /* nul'-stroka */

    Kavychki ne yavlyayutsya chast'yu stroki, a sluzhat tol'ko dlya
ee ogranicheniya. te zhe samye uslovnye posledovatel'nosti, ko-
torye ispol'zovalis' v simvol'nyh konstantah, primenyayutsya i
v strokah; simvol dvojnoj kavychki izobrazhaetsya kak \".
    S tehnicheskoj tochki zreniya stroka predstavlyaet soboj
massiv, elementami kotorogo yavlyayutsya otdel'nye simvoly. CHto-
by programmam bylo udobno opredelyat' konec stroki, kompilya-
tor avtomaticheski pomeshchaet v konec kazhdoj stroki nul'-simvol
\0. Takoe predstavlenie oznachaet, chto ne nakladyvaetsya konk-
retnogo ogranicheniya na to, kakuyu dlinu mozhet imet' stroka, i
chtoby opredelit' etu dlinu, programmy dolzhny prosmatrivat'
stroku polnost'yu. Pri etom dlya fizicheskogo hraneniya stroki
trebuetsya na odnu yachejku pamyati bol'she, chem chislo zaklyuchen-
nyh v kavychki simvolov. Sleduyushchaya funkciya STRLEN(S) vychislya-
et dlinu simvol'noj stroki S ne schitaya konechnyj simvol \0.

 STRLEN(S)       /* RETURN LENGTH OF S */
 CHAR  S[];
 {
    INT I;

    I = 0;
    WHILE (S[I] != '\0')
            ++I;
    RETURN(I);
 }

    Bud'te vnimatel'ny i ne putajte simvol'nuyu konstantu so
strokoj, soderzhashchej odin simvol: 'X' - eto ne to zhe samoe,
chto "X". Pervoe - eto otdel'nyj simvol, ispol'zovannyj s
cel'yu polucheniya chislennogo znacheniya, sootvetstvuyushchego bukve
h v mashinnom nabore simvolov. Vtoroe - simvol'naya stroka,
sostoyashchaya iz odnogo simvola (bukva h) i \0.






    Vse peremennye dolzhny byt' opisany do ih ispol'zovaniya,
hotya nekotorye opisaniya delayutsya neyavno, po kontekstu. Opi-
sanie sostoit iz specifikatora tipa i sleduyushchego za nim
spiska peremennyh, imeyushchih etot tip, kak, naprimer,

INT LOWER, UPPER, STEP;
CHAR C, LINE[1000];

    Peremennye mozhno raspredelyat' po opisaniyam lyubym obra-
zom; privedennye vyshe spiski mozhno s tem zhe uspehom zapisat'
v vide

INT LOWER;
INT UPPER;
INT STEP;
CHAR C;
CHAR LINE[1000];

    Takaya forma zanimaet bol'she mesta, no ona udobna dlya do-
bavleniya kommentariya k kazhdomu opisaniyu i dlya posleduyushchih
modifikacij.
    Peremennym mogut byt' prisvoeny nachal'nye znacheniya vnut-
ri ih opisaniya, hotya zdes' imeyutsya nekotorye ogranicheniya.
Esli za imenem peremennoj sleduyut znak ravenstva i konstan-
ta, to eta konstanta sluzhit v kachestve inicializatora, kak,
naprimer, v

CHAR BACKSLASH = '\\';
INT I = 0;
FLOAT EPS = 1.0E-5;

    Esli rassmatrivaemaya peremennaya yavlyaetsya vneshnej ili
staticheskoj, to inicializaciya provoditsya tol'ko odin raz,
soglasno koncepcii do nachala vypolneniya programmy. Iniciali-
ziruemym yavno avtomaticheskim peremennym nachal'nye znacheniya
prisvaivayutsya pri kazhdom obrashchenii k funkcii, v kotoroj oni
opisany. Avtomaticheskie peremennye, ne inicializiruemye yav-
no, imeyut neopredelennye znacheniya, (t.e. musor). Vneshnie i
staticheskie peremennye po umolchaniyu inicializiruyutsya nulem,
no, tem ne menee, ih yavnaya inicializaciya yavlyaetsya priznakom
horoshego stilya.
    My prodolzhim obsuzhdenie voprosov inicializacii, kogda
budem opisyvat' novye tipy dannyh.





    Binarnymi arifmeticheskimi operaciyami yavlyayutsya +, -, *, /
i operaciya deleniya po modulyu %. Imeetsya unarnaya operaciya -,
no ne sushchestvuet unarnoj operacii +.



    Pri delenii celyh drobnaya chast' otbrasyvaetsya. Vyrazhenie

 X % Y

daet ostatok ot deleniya X na Y i, sledovatel'no, ravno nulyu,
kogda h delitsya na Y tochno. Naprimer, god yavlyaetsya visokos-
nym, esli on delitsya na 4, no ne delitsya na 100, isklyuchaya
to, chto delyashchiesya na 400 gody tozhe yavlyayutsya visokosnymi. Po-
etomu

IF(YEAR % 4 == 0 && YEAR % 100 != 0 \!\! YEAR % 400 == 0)
    god visokosnyj
ELSE
    god nevisokosnyj


    Operaciyu % nel'zya ispol'zovat' s tipami FLOAT ili
DOUBLE.
    Operacii + i - imeyut odinakovoe starshinstvo, kotoroe
mladshe odinakovogo urovnya starshinstva operacij *, / i %, ko-
torye v svoyu ochered' mladshe unarnogo minusa. Arifmeticheskie
operacii gruppiruyutsya sleva napravo. (Svedeniya o starshinstve
i associativnosti vseh operacij sobrany v tablice v konce
etoj glavy). Poryadok vypolneniya associativnyh i kommutativ-
nyh operacij tipa + i - ne fiksiruetsya; kompilyator mozhet pe-
regruppirovyvat' dazhe zaklyuchennye v kruglye skobki vyrazhe-
niya, svyazannye takimi operaciyami. takim obrazom, a+(B+C) mo-
zhet byt' vychisleno kak (A+B)+C. |to redko privodit k kako-
mu-libo rashozhdeniyu, no esli neobhodimo obespechit' strogo
opredelennyj poryadok, to nuzhno ispol'zovat' yavnye promezhu-
tochnye peremennye.
    Dejstviya, predprinimaemye pri perepolnenii i antipere-
polnenii (t.e. Pri poluchenii slishkom malen'kogo po absolyut-
noj velichine chisla), zavisyat ot ispol'zuemoj mashiny.





    Operaciyami otnosheniya yavlyayutsya

 =>   >       =<   <

vse oni imeyut odinakovoe starshinstvo. Neposredstvenno za ni-
mi po urovnyu starshinstva sleduyut operacii ravenstva i nera-
venstva:

 ==   !=

kotorye tozhe imeyut odinakovoe starshinstvo. operacii otnoshe-
niya mladshe arifmeticheskih operacij, tak chto vyrazheniya tipa
I<LIM-1 ponimayutsya kak I<(LIM-1), kak i predpolagaetsya.
    Logicheskie svyazki && i \!\! bolee interesny. Vyrazheniya,
svyazannye operaciyami && i \!\!, vychislyayutsya sleva napravo,
prichem ih rassmotrenie prekrashchaetsya srazu zhe kak tol'ko sta-
novitsya yasno, budet li rezul'tat istinoj ili lozh'yu. uchet
etih svojstv ochen' sushchestvenen dlya napisaniya pravil'no rabo-
tayushchih programm. Rassmotrim, naprimer, operator cikla v schi-
tyvayushchej stroku funkcii GETLINE, kotoruyu my napisali v glave
1.
FOR(I=0;I<LIM-1 && (C=GETCHAR()) != '\N' && C != EOF; ++I)
 S[I]=C;

    YAsno, chto pered schityvaniem novogo simvola neobhodimo
proverit', imeetsya li eshche mesto v massive S, tak chto uslovie
I<LIM-1 dolzhno proveryat'sya pervym. I esli eto uslovie ne vy-
polnyaetsya, my ne dolzhny schityvat' sleduyushchij simvol.
    Tak zhe neudachnym bylo by sravnenie 'C' s EOF do obrashche-
niya k funkcii GETCHAR : prezhde chem proveryat' simvol, ego
nuzhno schitat'.
    Starshinstvo operacii && vyshe, chem u \!\!, i obe oni
mladshe operacij otnosheniya i ravenstva. Poetomu takie vyrazhe-
niya, kak

I<LIM-1 && (C = GETCHAR()) != '\N' && C != EOF

ne nuzhdayutsya v dopolnitel'nyh kruglyh skobkah. No tak kak
operaciya != starshe operacii prisvaivaniya, to dlya dostizheniya
pravil'nogo rezul'tata v vyrazhenii

 (C = GETCHAR()) != '\N'

kobki neobhodimy.

    Unarnaya operaciya otricaniya ! Preobrazuet nenulevoj ili
istinnyj operand v 0, a nulevoj ili lozhnyj operand v 1.
Obychnoe ispol'zovanie operacii ! Zaklyuchaetsya v zapisi

  IF( ! INWORD )

Vmesto

  IF( INWORD == 0 )

Trudno skazat', kakaya forma luchshe. Konstrukcii tipa ! INWORD
CHitayutsya dovol'no udobno ("esli ne v slove"). No v bolee
slozhnyh sluchayah oni mogut okazat'sya trudnymi dlya ponimaniya.

    Uprazhnenie 2-1
    ---------------
    Napishite operator cikla, ekvivalentnyj privedennomu vyshe
operatoru FOR, ne ispol'zuya operacii &&.





    Esli v vyrazheniyah vstrechayutsya operandy razlichnyh tipov,
to oni preobrazuyutsya k obshchemu tipu v sootvetstvii s nebol'-
shim naborom pravil. V obshchem, avtomaticheski proizvodyatsya
tol'ko preobrazovaniya, imeyushchie smysl, takie kak, naprimer,
preobrazovanie celogo v plavayushchee v vyrazheniyah tipa F+I. Vy-
razheniya zhe, lishennye smysla, takie kak ispol'zovanie pere-
mennoj tipa FLOAT v kachestve indeksa, zapreshcheny.
    Vo-pervyh, tipy CHAR i INT mogut svobodno smeshivat'sya v
arifmeticheskih vyrazheniyah: kazhdaya peremennaya tipa CHAR avto-
maticheski preobrazuetsya v INT. |to obespechivaet znachitel'nuyu
gibkost' pri provedenii opredelennyh preobrazovanij simvo-
lov. Primerom mozhet sluzhit' funkciya ATOI, kotoraya stavit v
sootvetstvie stroke cifr ee chislennyj ekvivalent.
 ATOI(S)         /* CONVERT S TO INTEGER */
 CHAR S[];
 {
    INT I, N;

    N = 0;
    FOR ( I = 0; S[I]>='0' && S[I]<='9'; ++I)
            N = 10 * N + S[I] - '0';
    RETURN(N);
 }

    KAK Uzhe obsuzhdalos' v glave 1, vyrazhenie

 S[I] - '0'

imeet chislennoe znachenie nahodyashchegosya v S[I] simvola, potomu
chto znachenie simvolov '0', '1' i t.d. obrazuyut vozrastayushchuyu
posledovatel'nost' raspolozhennyh podryad celyh polozhitel'nyh
chisel.
    Drugoj primer preobrazovaniya CHAR v INT daet funkciya
LOWER, preobrazuyushchaya dannuyu propisnuyu bukvu v strochnuyu. Esli
vystupayushchij v kachestve argumenta simvol ne yavlyaetsya propis-
noj bukvoj, to LOWER vozvrashchaet ego neizmennym. Privodimaya
nizhe programma spravedliva tol'ko dlya nabora simvolov ASCII.

LOWER(C) /* CONVERT C TO LOWER CASE; ASCII ONLY */
INT C;
{
   IF ( C >= 'A' && C <= 'Z' )
           RETURN( C + '@' - 'A');
   ELSE   /*@ Zapisano vmesto 'A' strochnogo*/
           RETURN(C);
}

|ta funkciya pravil'no rabotaet pri kode ASCII, potomu chto
chislennye znacheniya, sootvetstvuyushchie v etom kode propisnym i
strochnym bukvam, otlichayutsya na postoyannuyu velichinu, a kazhdyj
alfavit yavlyaetsya sploshnym - mezhdu a i Z net nichego, krome
bukv. |to poslednee zamechanie dlya nabora simvolov EBCDIC
sistem IBM 360/370 okazyvaetsya nespravedlivym, v silu chego
eta programma na takih sistemah rabotaet nepravil'no - ona
preobrazuet ne tol'ko bukvy.
    Pri preobrazovanii simvol'nyh peremennyh v celye vozni-
kaet odin tonkij moment. Delo v tom, chto sam yazyk ne ukazy-
vaet, dolzhny li peremennym tipa CHAR sootvetstvovat' chislen-
nye znacheniya so znakom ili bez znaka. Mozhet li pri preobra-
zovanii CHAR v INT poluchit'sya otricatel'noe celoe? K sozhale-
niyu, otvet na etot vopros menyaetsya ot mashiny k mashine, otra-
zhaya rashozhdeniya v ih arhitekture. Na nekotoryh mashinah
(PDP-11, naprimer) peremennaya tipa CHAR, krajnij levyj bit
kotoroj soderzhit 1, preobrazuetsya v otricatel'noe celoe
("znakovoe rasshirenie"). Na drugih mashinah takoe preobrazo-
vanie soprovozhdaetsya dobavleniem nulej s levogo kraya, v re-
zul'tate chego vsegda poluchaetsya polozhitel'noe chislo.



    Opredelenie yazyka "C" garantiruet, chto lyuboj simvol iz
standartnogo nabora simvolov mashiny nikogda ne dast otrica-
tel'nogo chisla, tak chto eti simvoly mozhno svobodno ispol'zo-
vat' v vyrazheniyah kak polozhitel'nye velichiny. No proizvol'-
nye kombinacii dvoichnyh znakov, hranyashchiesya kak simvol'nye
peremennye na nekotoryh mashinah, mogut dat' otricatel'nye
znacheniya, a na drugih polozhitel'nye.
    Naibolee tipichnym primerom vozniknoveniya takoj situacii
yavlyaetsya suchaj, kogda znachenie 1 ispol'zuetsya v kachestve
EOF. Rassmotrim programmu

 CHAR C;
 C = GETCHAR();
 IF ( C == EOF )
    ...

    Na mashine, kotoraya ne osushchestvlyaet znakovogo rasshireniya,
peremennaya 's' vsegda polozhitel'na, poskol'ku ona opisana
kak CHAR, a tak kak EOF otricatel'no, to uslovie nikogda ne
vypolnyaetsya. CHtoby izbezhat' takoj situacii, my vsegda pre-
dusmotritel'no ispol'zovali INT vmesto CHAR dlya lyuboj pere-
mennoj, poluchayushchej znachenie ot GETCHAR.
    Osnovnaya zhe prichina ispol'zovaniya INT vmesto CHAR ne
svyazana s kakim-libo voprosom o vozmozhnom znakovom rasshire-
nii. prosto funkciya GETCHAR dolzhna peredavat' vse vozmozhnye
simvoly (chtoby ee mozhno bylo ispol'zovat' dlya proizvol'nogo
vvoda) i, krome togo, otlichayushcheesya znachenie EOF. Sledova-
tel'no znachenie EOF ne mozhet byt' predstavleno kak CHAR, a
dolzhno hranit'sya kak INT.
    Drugoj poleznoj formoj avtomaticheskogo preobrazovaniya
tipov yavlyaetsya to, chto vyrazheniya otnosheniya, podobnye I>J, i
logicheskie vyrazheniya, svyazannye operaciyami && i \!\!, po op-
redeleniyu imeyut znachenie 1, esli oni istinny, i 0, esli oni
lozhny. Takim obrazom, prisvaivanie

 ISDIGIT = C >= '0' && C <= '9';

polagaet ISDIGIT ravnym 1, esli s - cifra, i ravnym 0 v pro-
tivnom sluchae. (V proverochnoj chasti operatorov IF, WHILE,
FOR i t.d. "Istinno" prosto oznachaet "ne nul'").
     Neyavnye arifmeticheskie preobrazovaniya rabotayut v osnov-
nom, kak i ozhidaetsya. V obshchih chertah, esli operaciya tipa +
ili *, kotoraya svyazyvaet dva operanda (binarnaya operaciya),
imeet operandy raznyh tipov, to pered vypolneniem operacii
"nizshij" tip preobrazuetsya k "vysshemu" i poluchaetsya rezul'-
tat "vysshego" tipa. Bolee tochno, k kazhdoj arifmeticheskoj
operacii primenyaetsya sleduyushchaya posledovatel'nost' pravil
preobrazovaniya.
     - Tipy CHAR i SHORT preobrazuyutsya v INT, a FLOAT v
DOUBLE.



     - Zatem, esli odin iz operandov imeet tip DOUBLE, to
drugoj preobrazuetsya v DOUBLE, i rezul'tat imeet tip DOUBLE.
     - V protivnom sluchae, esli odin iz operandov imeet tip
LONG, to drugoj preobrazuetsya v LONG, i rezul'tat imeet tip
LONG.
     - V protivnom sluchae, esli odin iz operandov imeet tip
UNSIGNED, to drugoj preobrazuetsya v UNSIGNED i rezul'tat
imeet tip UNSIGNED.
     - V protivnom sluchae operandy dolzhny byt' tipa INT, i
rezul'tat imeet tip INT.
Podcherknem, chto vse peremennye tipa FLOAT v vyrazheniyah pre-
obrazuyutsya v DOUBLE; v "C" vsya plavayushchaya arifmetika vypolnya-
etsya s dvojnoj tochnost'yu.
    Preobrazovaniya voznikayut i pri prisvaivaniyah; znachenie
pravoj chasti preobrazuetsya k tipu levoj, kotoryj i yavlyaetsya
tipom rezul'tata. Simvol'nye peremennye preobrazuyutsya v ce-
lye libo so znakovym rasshireniem ,libo bez nego, kak opisano
vyshe. Obratnoe preobrazovanie INT v CHAR vedet sebya horosho -
lishnie bity vysokogo poryadka prosto otbrasyvayutsya. Takim ob-
razom

INT I;
CHAR C;

I = C;
C = I;

znachenie 's' ne izmenyaetsya. |to verno nezavisimo ot togo,
vovlekaetsya li znakovoe rasshirenie ili net.
    Esli h tipa FLOAT, a I tipa INT, to kak

 h = I;
tak i
 I = h;

privodyat k preobrazovaniyam; pri etom FLOAT preobrazuetsya v
INT otbrasyvaniem drobnoj chasti. Tip DOUBLE preobrazuetsya vo
FLOAT okrugleniem. Dlinnye celye preobrazuyutsya v bolee ko-
rotkie celye i v peremennye tipa CHAR posredstvom otbrasyva-
niya lishnih bitov vysokogo poryadka.
    Tak kak argument funkcii yavlyaetsya vyrazheniem, to pri pe-
redache funkciyam argumentov takzhe proishodit preobrazovanie
tipov: v chastnosti, CHAR i SHORT stanovyatsya INT, a FLOAT
stanovitsya DOUBLE. Imenno poetomu my opisyvali argumenty
funkcij kak INT i DOUBLE dazhe togda, kogda obrashchalis' k nim
s peremennymi tipa CHAR i FLOAT.
    Nakonec, v lyubom vyrazhenii mozhet byt' osushchestvleno
("prinuzhdeno") yavnoe preobrazovanie tipa s pomoshch'yu konstruk-
cii, nazyvaemoj perevod (CAST). V etoj konstrukcii, imeyushchej
vid

 (imya tipa) vyrazhenie
    Vyrazhenie preobrazuetsya k ukazannomu tipu po pravilam
preobrazovaniya, izlozhennym vyshe. Fakticheski tochnyj smysl
operacii perevoda mozhno opisat' sleduyushchim obrazom: vyrazhenie
kak by prisvaivaetsya nekotoroj peremennoj ukazannogo tipa,
kotoraya zatem ispol'zuetsya vmesto vsej konstrukcii. Napri-
mer, bibliotechnaya procedura SQRT ozhidaet argumenta tipa
DOUBLE i vydast bessmyslennyj otvet, esli k nej po nebrezh-
nosti obratyatsya s chem-nibud' inym. takim obrazom, esli N -
celoe, to vyrazhenie

 SQRT((DOUBLE) N)

    do peredachi argumenta funkcii SQRT preobrazuet N k tipu
DOUBLE. (Otmetim, chto operaciya perevod preobrazuet znachenie
N v nadlezhashchij tip; fakticheskoe soderzhanie peremennoj N pri
etom ne izmenyaetsya). Operaciya perevoda imraciya perevoda ime-
et tot zhe uroven' starshinstva, chto i drugie unarnye opera-
cii, kak ukazyvaetsya v tablice v konce etoj glavy.

    Uprazhnenie 2-2
   ---------------
    Sostav'te programmu dlya funkcii HTOI(S), kotoraya preob-
razuet stroku shestnadcaterichnyh cifr v ekvivalentnoe ej ce-
loe znachenie. Pri etom dopustimymi ciframi yavlyayutsya cifry ot
1 do 9 i bukvy ot a do F.





    V yazyke "C" predusmotreny dve neobychnye operacii dlya
uvelicheniya i umen'sheniya znachenij peremennyh. Operaciya uveli-
cheniya ++ dobavlyaet 1 k svoemu operandu, a operaciya umen'she-
niya -- vychitaet 1. My chasto ispol'zovali operaciyu ++ dlya
uvelicheniya peremennyh, kak, naprimer, v

 IF(C == '\N')
    ++I;

    Neobychnyj aspekt zaklyuchaetsya v tom, chto ++ i -- mozhno
ispol'zovat' libo kak prefiksnye operacii (pered peremennoj,
kak v ++N), libo kak postfiksnye (posle peremennoj: N++).
|ffekt v oboih sluchayah sostoit v uvelichenii N. No vyrazhenie
++N uvelichivaet peremennuyu N do ispol'zovaniya ee znacheniya, v
to vremya kak N++ uvelichivaet peremennuyu N posle togo, kak ee
znachenie bylo ispol'zovano. |to oznachaet, chto v kontekste,
gde ispol'zuetsya znachenie peremennoj, a ne tol'ko effekt
uvelicheniya, ispol'zovanie ++N i N++ privodit k raznym re-
zul'tatam. Esli N = 5, to

 h = N++;

ustanavlivaet h ravnym 5, a

 h = ++N;



polagaet h ravnym 6. V oboih sluchayah N stanovitsya ravnym 6.
Operacii uvelicheniya i umen'sheniya mozhno primenyat' tol'ko k
peremennym; vyrazheniya tipa h=(I+J)++ yavlyayutsya nezakonnymi.
    V sluchayah, gde nuzhen tol'ko effekt uvelicheniya, a samo
znachenie ne ispol'zuetsya, kak, naprimer, v

IF ( C == '\N' )
   NL++;

vybor prefiksnoj ili postfiksnoj operacii yavlyaetsya delom
vkusa. no vstrechayutsya situacii, gde nuzhno ispol'zovat' imen-
no tu ili druguyu operaciyu. Rassmotrim, naprimer, funkciyu
SQUEEZE(S,C), kotoraya udalyaet simvol 's' iz stroki S, kazhdyj
raz, kak on vstrechaetsya.

SQUEEZE(S,C)    /* DELETE ALL C FROM S */
CHAR S[];
INT C;
{
   INT I, J;

   FOR ( I = J = 0; S[I] != '\0'; I++)
           IF ( S[I] != C )
    S[J++] = S[I];
   S[J] = '\0';
}

Kazhdyj raz, kak vstechaetsya simvol, otlichnyj ot 's', on kopi-
ruetsya v tekushchuyu poziciyu J, i tol'ko posle etogo J uvelichi-
vaetsya na 1, chtoby byt' gotovym dlya postupleniya sleduyushchego
simvola. |to v tochnosti ekvivalentno zapisi

   IF ( S[I] != C ) {
           S[J] = S[I];
           J++;
   }

    Drugoj primer podobnoj konstrukcii daet funkciya GETLINE,
kotoruyu my zaprogrammirovali v glave 1, gde mozhno zamenit'

IF ( C == '\N' )  {
   S[I] = C;
   ++I;
}

bolee kompaktnoj zapis'yu

IF ( C == '\N' )
   S[I++] = C;
    V kachestve tret'ego primera rassmotrim funkciyu
STRCAT(S,T), kotoraya pripisyvaet stroku t v konec stroki S,
obrazuya konkatenaciyu strok S i t. Pri etom predpolagaetsya,
chto v S dostatochno mesta dlya hraneniya poluchennoj kombinacii.

 STRCAT(S,T)     /* CONCATENATE T TO END OF S */
 CHAR S[], T[];  /* S MUST BE BIG ENOUGH */
 {
    INT I, J;

    I = J = 0;
    WHILE (S[I] != '\0') / *FIND END OF S */
            I++;
   WHILE((S[I++] = T[J++]) != '\0') /*COPY T*/
            ;
 }

Tak kak iz T v S kopiruetsya kazhdyj simvol, to dlya podgotovki
k sleduyushchemu prohozhdeniyu cikla postfiksnaya operaciya ++ pri-
menyaetsya k obeim peremennym I i J.



    Uprazhnenie 2-3
    ---------------
    Napishite drugoj variant funkcii SQUEEZE(S1,S2), kotoryj
udalyaet iz stroki S1 kazhdyj simvol, sovpadayushchij s kakim-libo
simvolom stroki S2.

    Uprazhnenie 2-4
    ---------------
    Napishite programmu dlya funkcii ANY(S1,S2), kotoraya naho-
dit mesto pervogo poyavleniya v stroke S1 kakogo-libo simvola
iz stroki S2 i, esli stroka S1 ne soderzhit simvolov stroki
S2, vozvrashchaet znachenie -1.





    V yazyke predusmotren ryad operacij dlya raboty s bitami;
eti operacii nel'zya primenyat' k peremennym tipa FLOAT ili
DOUBLE.

 &    Pobitovoe AND
 \!    Pobitovoe vklyuchayushchee OR
 ^    pobitovoe isklyuchayushchee OR
 <<    sdvig vlevo
 >>    sdvig vpravo
 \^    dopolnenie (unarnaya operaciya)

"\" immitiruet vertikal'nuyu chertu.

Pobitovaya operaciya AND chasto ispol'zuetsya dlya maskirovaniya
nekotorogo mnozhestva bitov; naprimer, operator

   C = N & 0177
peredaet v 's' sem' mladshih bitov N , polagaya ostal'nye rav-
nymi nulyu. Operaciya 'e' pobitovogo OR ispol'zuetsya dlya vklyu-
cheniya bitov:

   C = X e MASK

ustanavlivaet na edinicu te bity v h , kotorye ravny edinice
v MASK.
    Sleduet byt' vnimatel'nym i otlichat' pobitovye operacii
& i 'e' ot logicheskih svyazok && i \!\! , Kotorye podrazume-
vayut vychislenie znacheniya istinnosti sleva napravo. Naprimer,
esli h=1, a Y=2, to znachenie h&Y ravno nulyu , v to vremya kak
znachenie X&&Y ravno edinice./pochemu?/
    Operacii sdviga << i >> osushchestvlyayut sootvetstvenno
sdvig vlevo i vpravo svoego levogo operanda na chislo bitovyh
pozicij, zadavaemyh pravym operandom. Takim obrazom , h<<2
sdvigaet h vlevo na dve pozicii, zapolnyaya osvobozhdayushchiesya
bity nulyami, chto ekvivalentno umnozheniyu na 4. Sdvig vpravo
velichiny bez znaka zapolnyaet osvobozhdayushchiesya bity na nekoto-
ryh mashinah, takih kak PDP-11, zapolnyayutsya soderzhaniem zna-
kovogo bita /"arifmeticheskij sdvig"/, a na drugih - nulem
/"logicheskij sdvig"/.
    Unarnaya operaciya \^ daet dopolnenie k celomu; eto ozna-
chaet , chto kazhdyj bit so znacheniem 1 poluchaet znachenie 0 i
naoborot. |ta operaciya obychno okazyvaetsya poleznoj v vyrazhe-
niyah tipa

   X & \^077

gde poslednie shest' bitov h maskiruyutsya nulem. Podcherknem,
chto vyrazhenie X&\^077 ne zavisit ot dliny slova i poetomu
predpochtitel'nee, chem, naprimer, X&0177700, gde predpolaga-
etsya, chto h zanimaet 16 bitov. Takaya perenosimaya forma ne
trebuet nikakih dopolnitel'nyh zatrat, poskol'ku \^077 yavlya-
etsya konstantnym vyrazheniem i, sledovatel'no, obrabatyvaetsya
vo vremya kompilyacii.
    CHtoby proillyustrirovat' ispol'zovanie nekotoryh operacij
s bitami, rassmotrim funkciyu GETBITS(X,P,N), kotoraya vozvra-
shchaet /sdvinutymi k pravomu krayu/ nachinayushchiesya s pozicii r
pole peremennoj h dlinoj N bitov. my predpolagaem , chto
krajnij pravyj bit imeet nomer 0, i chto N i r - razumno za-
dannye polozhitel'nye chisla. naprimer, GETBITS(h,4,3) vozvra-
shchaet sdvinutymi k pravomu krayu bity, zanimayushchie pozicii 4,3
i 2.

 GETBITS(X,P,N)  /* GET N BITS FROM POSITION P */
 UNSIGNED X, P, N;
  {
    RETURN((X >> (P+1-N)) & \^(\^0 << N));
  }



Operaciya X >> (P+1-N) sdvigaet zhelaemoe pole v pravyj konec
slova. Opisanie argumenta X kak UNSIGNED garantiruet, chto
pri sdvige vpravo osvobozhdayushchiesya bity budut zapolnyat'sya nu-
lyami, a ne soderzhimym znakovogo bita, nezavisimo ot togo, na
kakoj mashine propuskaetsya programma. Vse bity konstantnogo
vyrazheniya \^0 ravny 1; sdvig ego na N pozicij vlevo s po-
moshch'yu operacii \^0<<N sozdaet masku s nulyami v N krajnih
pravyh bitah i edinicami v ostal'nyh; dopolnenie \^ sozdaet
masku s edinicami v N krajnih pravyh bitah.

    Uprazhnenie 2-5
    ---------------
    Peredelajte GETBITS takim obrazom, chtoby bity otschityva-
lis' sleva napravo.

    Uprazhnenie 2-6
    ---------------
    Napishite programmu dlya funkcii WORDLENGTH(), vychislyayushchej
dlinu slova ispol'zuemoj mashiny, t.e. CHislo bitov v peremen-
noj tipa INT. Funkciya dolzhna byt' perenosimoj, t.e. Odna i
ta zhe ishodnaya programma dolzhna pravil'no rabotat' na lyuboj
mashine.

    Uprazhnenie 2-7
    ---------------
    Napishite programmu dlya funkcii RIGHTROT(N,B), sdvigayushchej
ciklicheski celoe N vpravo na B bitovyh pozicij.

    Uprazhnenie 2-8
    ---------------
    Napishite programmu dlya funkcii INVERT(X,P,N), kotoraya
invertiruet (t.e. Zamenyaet 1 na 0 i naoborot) N bitov X, na-
chinayushchihsya s pozicii P, ostavlyaya drugie bity neizmenennymi.





    Takie vyrazheniya, kak

I = I + 2

v kotoryh levaya chast' povtoryaetsya v pravoj chasti mogut byt'
zapisany v szhatoj forme

I += 2

ispol'zuya operaciyu prisvaivaniya vida +=.
    Bol'shinstvu binarnyh operacij (operacij podobnyh +, ko-
torye imeyut levyj i pravyj operand) sootvetstvuet operaciya
prisvaivaniya vida op=, gde op - odna iz operacij

+  - *  /  %  <<  >>  &  \^  \!

Esli e1 i e2 - vyrazheniya, to
e1 op= e2

ekvivalentno

e1 = (e1) op (e2)

za isklyucheniem togo, chto vyrazhenie e1 vychislyaetsya tol'ko
odin raz. Obratite vnimanie na kruglye skobki vokrug e2:

X *= Y + 1

to

X = X * (Y + 1)

ne

X = X * Y + 1

    V kachestve primera privedem funkciyu BITCOUNT, kotoraya
podschityvaet chislo ravnyh 1 bitov u celogo argumenta.


BITCOUNT(N)   /* COUNT 1 BITS IN N */
UNSIGNED N;
(
INT B;
FOR (B = 0; N != 0; N >>= 1)
   IF (N & 01)
           B++;
RETURN(B);
)

    Ne govorya uzhe o kratkosti, takie operatory privaivaniya
imeyut to preimushchestvo, chto oni luchshe sootvetstvuyut obrazu
chelovecheskogo myshleniya. My govorim: "pribavit' 2 k I" ili
"uvelichit' I na 2", no ne "vzyat' I, pribavit' 2 i pomestit'
rezul'tat opyat' v I". Itak, I += 2. Krome togo, v gromozdkih
vyrazheniyah, podobnyh

 YYVAL[YYPV[P3+P4] + YYPV[P1+P2]] += 2

Takaya operaciya prisvaivaniya oblegchaet ponimanie programmy,
tak kak chitatel' ne dolzhen skrupulezno proveryat', yavlyayutsya
li dva dlinnyh vyrazheniya dejstvitel'no odinakovymi, ili za-
dumyvat'sya, pochemu oni ne sovpadayut. Takaya operaciya prisvai-
vaniya mozhet dazhe pomoch' kompilyatoru poluchit' bolee effektiv-
nuyu programmu.
    My uzhe ispol'zovali tot fakt, chto operaciya prisvaivaniya
imeet nekotoroe znachenie i mozhet vhodit' v vyrazheniya; samyj
tipichnyj primer
 WHILE ((C = GETCHAR()) != EOF)

prisvaivaniya, ispol'zuyushchie drugie operacii prisvaivaniya (+=,
-= i t.d.) takzhe mogut vhodit' v vyrazheniya, hotya eto slucha-
etsya rezhe.
     Tipom vyrazheniya prisvaivaniya yavlyaetsya tip ego levogo
operanda.

    Uprazhnenie 2-9
    ---------------
     V dvoichnoj sisteme schisleniya operaciya X&(X-1) obnulyaet
samyj pravyj ravnyj 1 bit peremennoj X.(pochemu?) ispol'zujte
eto zamechanie dlya napisaniya bolee bystroj versii funkcii
BITCOUNT.





     Operatory

 IF (A > B)
    Z = A;
 ELSE
    Z = B;

konechno vychislyayut v Z maksimum iz a i v. Uslovnoe vyrazhenie,
zapisannoe s pomoshch'yu ternarnoj operacii "?:", predostavlyaet
druguyu vozmozhnost' dlya zapisi etoj i analogichnyh konstruk-
cij. V vyrazhenii

e1 ? E2 : e3

snachala vychislyaetsya vyrazhenie e1. Esli ono otlichno ot nulya
(istinno), to vychislyaetsya vyrazhenie e2, kotoroe i stanovitsya
znacheniem uslovnogo vyrazheniya. V protivnom sluchae vychislyaet-
sya e3, i ono stanovitsya znacheniem uslovnogo vyrazheniya. Kazh-
dyj raz vychislyaetsya tol'ko odno iz vyrazheniya e2 i e3. Takim
obrazom, chtoby polozhit' Z ravnym maksimumu iz a i v, mozhno
napisat'

Z = (A > B) ? A : B;   /* Z = MAX(A,B) */

    Sleduet podcherknut', chto uslovnoe vyrazhenie dejstvitel'-
no yavlyaetsya vyrazheniem i mozhet ispol'zovat'sya tochno tak zhe,
kak lyuboe drugoe vyrazhenie. Esli e2 i e3 imeyut raznye tipy,
to tip rezul'tata opredelyaetsya po pravilam preobrazovaniya,
rassmotrennym ranee v etoj glave. naprimer, esli F imeet tip
FLOAT, a N - tip INT, to vyrazhenie

(N > 0) ? F : N

Imeet tip DOUBLE nezavisimo ot togo, polozhitel'no li N ili
net.



    Tak kak uroven' starshinstva operacii ?: ochen' nizok,
pryamo nad prisvaivaniem, to pervoe vyrazhenie v uslovnom vy-
razhenii mozhno ne zaklyuchat' v kruglye skobki. Odnako, my vse
zhe rekomenduem eto delat', tak kak skobki delayut uslovnuyu
chast' vyrazheniya bolee zametnoj.
    Ispol'zovanie uslovnyh vyrazhenij chasto privodit k korot-
kim programmam. Naprimer, sleduyushchij nizhe operator cikla pe-
chataet N elementov massiva, po 10 v stroke, razdelyaya kazhdyj
stolbec odnim probelom i zakanchivaya kazhduyu stroku (vklyuchaya
poslednyuyu) odnim simvolom perevoda stroki.

OR (I = 0; I < N; I++)
 PRINTF("%6D%C",A[I],(I%10==9 \!\! I==N-1) ? '\N' : ' ')

Simvol perevoda stroki zapisyvaetsya posle kazhdogo desyatogo
elementa i posle N-go elementa. Za vsemi ostal'nymi elemen-
tami sleduet odin probel. Hotya, vozmozhno, eto vyglyadit mud-
renym, bylo by pouchitel'nym popytat'sya zapisat' eto, ne is-
pol'zuya uslovnogo vyrazheniya.

    Uprazhnenie 2-10
    ---------------
Perepishite programmu dlya funkcii LOWER, kotoraya perevodit
propisnye bukvy v strochnye, ispol'zuya vmesto konstrukcii
IF-ELSE uslovnoe vyrazhenie.





V privodimoj nizhe tablice svedeny pravila starshinstva i as-
sociativnosti vseh operacij, vklyuchaya i te, kotorye my eshche ne
obsuzhdali. Operacii, raspolozhennye v odnoj stroke, imeyut
odin i tot zhe uroven' starshinstva; stroki raspolozheny v po-
ryadke ubyvaniya starshinstva. Tak, naprimer, operacii *, / i %
imeyut odinakovyj uroven' starshinstva, kotoryj vyshe, chem uro-
ven' operacij + i -.

  OPERATOR        ASSOCIATIVITY

  () [] -> .        LEFT TO RIGHT

  !  \^ ++  --  -  (TYPE)  * &  SIZEOF      RIGHT TO LEFT

  *  /  %        LEFT TO RIGHT

  +  -         LEFT TO RIGHT

  <<  >>         LEFT TO RIGHT

  <  <= >  >=        LEFT TO RIGHT
  ==  !=         LEFT TO RIGHT

  &         LEFT TO RIGHT

  ^         LEFT TO RIGHT

  \!         LEFT TO RIGHT

  &&         LEFT TO RIGHT

  \!\!         LEFT TO RIGHT

  ?:         RIGHT TO LEFT

  =  += -=  ETC.       RIGHT TO LEFT

  ,  (CHAPTER 3)       LEFT TO RIGHT


Operacii -> i . Ispol'zuyutsya dlya dostupa k elementam struk-
tur; oni budut opisany v glave 6 vmeste s SIZEOF (razmer
ob容kta). V glave 5 obsuzhdayutsya operacii * (kosvennaya adre-
saciya) i & (adres).
Otmetim, chto uroven' starshinstva pobitovyh logicheskih opera-
cij &, ^ i e nizhe urovnya operacij == i !=. |to privodit k
tomu, chto osushchestvlyayushchie pobitovuyu proverku vyrazheniya, po-
dobnye

IF ((X & MASK) == 0) ...

Dlya polucheniya pravil'nyh rezul'tatov dolzhny zaklyuchat'sya v
kruglye skobki.
    Kak 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.







     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.





     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.





    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.





     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.





    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'.





    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).





    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.




    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'.





    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).





    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.





    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.





    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.







    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.





    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.





    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 yavlyayutsya 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/.





    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 ?





    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.





    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.





    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'.





    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.





    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).







    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.





    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.





    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.







    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).






    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'.





    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.





    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?





    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  obrazom,  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.





    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.





    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/.





    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).





    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.




    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?




    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.




    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.







    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.




    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 funkcii sravneniya i perestanovki,
my mozhem organizovat' sortirovku po razlichnym kriteriyam.
Imenno takoj podhod ispol'zuetsya v nashej novoj programme
sortirovki.
    Kak i prezhde, leksikograficheskoe sravnenie dvuh strok
osushchestvlyaetsya funkciej STRCMP, a perestanovka funkciej
SWAP; nam nuzhna eshche funkciya NUMCMP, sravnivayushchaya dve stroki
na osnove chislennogo znacheniya i vozvrashchayushchaya uslovnoe ukaza-
nie togo zhe vida, chto i STRCMP. |ti tri funkcii opisyvayutsya
v MAIN i ukazateli na nih peredayutsya v SORT. V svoyu ochered'
funkciya SORT obrashchaetsya k etim funkciyam cherez ih ukazateli.
my urezali obrabotku oshibok v argumentah s tem, chtoby sosre-
dotochit'sya na glavnyh voprosah.



 #DEFINE LINES 100 /* MAX NUMBER OF LINES
        TO BE SORTED */

 MAIN(ARGC, ARGV) /* SORT INPUT LINES */
 INT ARGC;
 CHAR *ARGV[];
 \(
  CHAR *LINEPTR[LINES]; /* POINTERS TO TEXT LINES */
  INT NLINES; /* NUMBER OF INPUT LINES READ */
  INT STRCMP(), NUMCMP(); /* COMPARSION FUNCTIONS */
  INT SWAP(); /* EXCHANGE FUNCTION */
  INT NUMERIC = 0; /* 1 IF NUMERIC SORT */

  IF(ARGC>1 && ARGV[1][0] == '-' && ARGV[1][1]=='N')
     NUMERIC = 1;
  IF(NLINES = READLINES(LINEPTR, LINES)) >= 0) \(
     IF (NUMERIC)
           SORT(LINEPTR, NLINES, NUMCMP, SWAP);
     ELSE
           SORT(LINEPTR, NLINES, STRCMP, SWAP);
     WRITELINES(LINEPTR, NLINES);
  \) ELSE
     PRINTF("INPUT TOO BIG TO SORT\N");
 \)


Zdes' STRCMP, NIMCMP i SWAP - adresa funkcij; tak kak izves-
tno, chto eto funkcii, operaciya & zdes' ne nuzhna sovershenno
analogichno tomu, kak ona ne nuzhna i pered imenem massiva.
Peredacha adresov funkcij organizuetsya kompilyatorom.
    Vtoroj shag sostoit v modifikacii SORT:

 SORT(V, N, COMP, EXCH) /* SORT STRINGS V[0] ... V[N-1] */
 CHAR *V[];           /* INTO INCREASING ORDER */
 INT N;
 INT (*COMP)(), (*EXCH)();
 \(
  INT GAP, I, J;

  FOR(GAP = N/2; GAP > 0; GAP /= 2)
      FOR(I = GAP; I < N; I++)
     FOR(J = I-GAP; J >= 0; J -= GAP) \(
         IF((*COMP)(V[J], V[J+GAP]) <= 0)
             BREAK;
         (*EXCH)(&V[J], &V[J+GAP]);
     \)
 \)



    Zdes' sleduet obratit' opredelennoe vnimanie na opisa-
niya. Opisanie

 INT (*COMP)()

govorit, chto COMP yavlyaetsya ukazatelem na funkciyu, kotoraya
vozvrashchaet znachenie tipa INT. Pervye kruglye skobki zdes'
neobhodimy; bez nih opisanie

 INT *COMP()

govorilo by, chto COMP yavlyaetsya funkciej, vozvrashchayushchej ukaza-
tel' na celye, chto, konechno, sovershenno drugaya veshch'.
    Ispol'zovanie COMP v stroke

 IF (*COMP)(V[J], V[J+GAP]) <= 0)

polnost'yu soglasuetsya s opisaniem: COMP - ukazatel' na funk-
ciyu, *COMP - sama funkciya, a

  (*COMP)(V[J], V[J+GAP])

- obrashchenie k nej. Kruglye skobki neobhodimy dlya pravil'nogo
ob容dineniya komponentov.
    My uzhe privodili funkciyu STRCMP, sravnivayushchuyu dve stroki
po pervomu chislennomu znacheniyu:

NUMCMP(S1, S2) /* COMPARE S1 AND S2 NUMERICALLY */
CHAR *S1, *S2;
\(
 DOUBLE ATOF(), V1, V2;

 V1 = ATOF(S1);
 V2 = ATOF(S2);
 IF(V1 < V2)
    RETURN(-1);
 ELSE IF(V1 > V2)
    RETURN(1);
 ELSE
    RETURN (0);
\)


    Zaklyuchitel'nyj shag sostoit v dobavlenii funkcii SWAP,
perestavlyayushchej dva ukazatelya. |to legko sdelat', neposredst-
venno ispol'zuya to, chto my izlozhili ranee v etoj glave.



SWAP(PX, PY) /* INTERCHANGE *PX AND *PY */
CHAR *PX[], *PY[];
\(
 CHAR *TEMP;

 TEMP = *PX;
 *PX = *PY;
 *PY = TEMP;
\)
    Imeetsya mnozhestvo drugih neobyazyatel'nyh argumentov, ko-
torye mogut byt' vklyucheny v programmu sortirovki: nekotorye
iz nih sostavlyayut interesnye uprazhneniya.

    Uprazhnenie 5-11
    ---------------
    Modificirujte SORT takim obrazom, chtoby ona rabotala s
metkoj -R, ukazyvayushchej na sortirovku v obratnom (ubyvayushchem)
poryadke. Konechno, -R dolzhna rabotat' s -N.

    Uprazhnenie 5-12
    ---------------
    Dobav'te neobyazatel'nyj argument -F, ob容dinyayushchij vmeste
propisnye i strochnye bukvy, tak chtoby razlichie registrov ne
uchityvalos' vo vremya sortirovki: dannye iz verhnego i nizhne-
go registrov sortiruyutsya vmeste, tak chto bukva 'a' propisnoe
i 'a' strochnoe okazyvayutsya sosednimi , a ne razdelennymi ce-
lym alfavitom.

    Uprazhnenie 5-13
    ---------------
    Dobav'te neobyazatel'nyj argument -D ("slovarnoe uporyado-
chivanie"), pri nalichii kotorogo sravnivayutsya tol'ko bukvy,
chisla i probely. Pozabot'tes' o tom, chtoby eta funkciya rabo-
tala i vmeste s -F.

    Uprazhnenie 5-14
    ---------------
    Dobav'te vozmozhnost' obrabotki polej, tak chtoby mozhno
bylo sortirovat' polya vnutri strok. Kazhdoe pole dolzhno sor-
tirovat'sya v sootvetstvii s nezavisimym naborom neobyazatel'-
nyh argumentov. (predmetnyj ukazatel' etoj knigi sortiroval-
sya s pomoshch'yu argumentov -DF dlya kategorii ukazatelya i s -N
dlya nomerov stranic).





    Struktura - eto nabor iz odnoj ili bolee peremennyh,
vozmozhno razlichnyh tipov, sgruppirovannyh pod odnim imenem
dlya udobstva obrabotki. (V nekotoryh yazykah, samyj izvestnyj
iz kotoryh paskal', struktury nazyvayutsya "zapisyami").
    Tradicionnym primerom struktury yavlyaetsya uchetnaya kartoch-
ka rabotayushchego: "sluzhashchij" opisyvaetsya naborom atributov ta-
kih, kak familiya, imya, otchestvo (f.i.o.), adres, kod soci-
al'nogo obespecheniya, zarplata i t.d. Nekotorye iz etih atri-
butov sami mogut okazat'sya strukturami: f.i.o. Imeet nes-
kol'ko komponent, kak i adres, i dazhe zarplata.
    Struktury okazyvayutsya poleznymi pri organizacii slozhnyh
dannyh osobenno v bol'shih programmah, poskol'ku vo mnogih
situaciyah oni pozvolyayut sgruppirovat' svyazannye dannye takim
obrazom, chto s nimi mozhno obrashchat'sya, kak s odnim celym, a
ne kak s otdel'nymi ob容ktami. V etoj glave my postaraemsya
prodemonstrirovat' to, kak ispol'zuyutsya struktury. Program-
my, kotorye my dlya etogo budem ispol'zovat', bol'she, chem
mnogie drugie v etoj knige, no vse zhe dostatochno umerennyh
razmerov.




    Davajte snova obratimsya k proceduram preobrazovaniya daty
iz glavy 5. Data sostoit iz neskol'kih chastej takih, kak
den', mesyac, i god, i, vozmozhno, den' goda i imya mesyaca. |ti
pyat' peremennyh mozhno ob容denit' v odnu strukturu vida:

 STRUCT DATE \(
 INT  DAY;
 INT  MONTH;
 INT  YEAR;
 INT  YEARDAY;
 CHAR MON_NAME[4];
 \);

    Opisanie struktury, sostoyashchee iz zaklyuchennogo v figurnye
skobki spiska opisanij, nachinaetsya s klyuchevogo slova STRUCT.
Za slovom STRUCT mozhet sledovat' neobyazatel'noe imya, nazyva-
emoe yarlykom struktury (zdes' eto DATe). Takoj yarlyk imenuet
struktury etogo vida i mozhet ispol'zovat'sya v dal'nejshem kak
sokrashchennaya zapis' podrobnogo opisaniya.
    |lementy ili peremennye, upomyanutye v strukture, nazyva-
yutsya chlenami. YArlyki i chleny struktur mogut imet' takie zhe
imena, chto i obychnye peremennye (t.e. Ne yavlyayushchiesya chlenami
struktur), poskol'ku ih imena vsegda mozhno razlichit' po kon-
tekstu. Konechno, obychno odinakovye imena prisvaivayut tol'ko
tesno svyazannym ob容ktam.



    Tochno tak zhe, kak v sluchae lyubogo drugogo bazisnogo ti-
pa, za pravoj figurnoj skobkoj, zakryvayushchej spisok chlenov,
mozhet sledovat' spisok peremennyh.
Operator

   STRUCT   \( ...\) X,Y,Z;

sintaksicheski analogichen

   INT X,Y,Z;

v tom smysle, chto kazhdyj iz operatorov opisyvaet X , Y i Z v
kachestve peremennyh sootvestvuyushchih tipov i privodit k vyde-
leniyu dlya nih pamyati.
    Opisanie struktury, za kotorym ne sleduet spiska pere-
mennyh, ne privodit k vydeleniyu kakoj-libo pamyati; ono tol'-
ko opredelyaet shablon ili formu struktury. Odnako, esli takoe
opisanie snabzheno yarlykom, to etot yarlyk mozhet byt' ispol'-
zovan pozdnee pri opredelenii fakticheskih ekzemplyarov struk-
tur. Naprimer, esli dano privedennoe vyshe opisanie DATE, to

   STRUCT  DATE D;

opredelyaet peremennuyu D v kachestve struktury tipa DATE.
Vneshnyuyu ili staticheskuyu strukturu mozhno inicializirovat',
pomestiv vsled za ee opredeleniem spisok inicializatorov dlya
ee komponent:

 STRUCT DATE D=\( 4, 7, 1776, 186, "JUL"\);

    CHlen opredelennoj struktury mozhet byt' ukazan v vyrazhe-
nii s pomoshch'yu konstrukcii vida

  imya struktury . CHlen
  --------------------
Operaciya ukazaniya chlena struktury "." svyazyvaet imya struktu-
ry i imya chlena. V kachestve primera opredelim LEAP (priznak
visokosnosti goda) na osnove daty, nahodyashchejsya v strukture
D,

LEAP = D.YEAR % 4 == 0 && D.YEAR % 100 != 0
   \!\! D.YEAR % 400 == 0;

ili proverim imya mesyaca

 IF (STRCMP(D.MON_NAME, "AUG") == 0) ...

Ili preobrazuem pervyj simvol imeni mesyaca tak, chtoby ono
nachinalos' so strochnoj bukvy

 D.MON_NAME[0] = LOWER(D.MON_NAME[0]);



    Struktury mogut byt' vlozhennymi; uchetnaya kartochka sluzha-
shchego mozhet fakticheski vyglyadet' tak:

 STRUCT  PERSON  \(
    CHAR NAME[NAMESIZE];
    CHAR ADDRESS[ADRSIZE];
    LONG ZIPCODE;   /* pochtovyj indeks */
    LONG SS_NUMBER; /* kod soc. Obespecheniya */
    DOUBLE SALARY;  /* zarplata */
    STRUCT DATE BIRTHDATE; /* data rozhdeniya */
    STRUCT DATE HIREDATE; /* data postupleniya
     na rabotu */
 \);


Struktura PERSON soderzhit dve struktury tipa DATE . Esli my
opredelim EMP kak

 STRUCT PERSON EMP;

to

 EMP.BIRTHDATE.MONTH

budet ssylat'sya na mesyac rozhdeniya. Operaciya ukazaniya chlena
struktury "." associiruetsya sleva napravo.




    V yazyke "C" sushchestvuet ryad ogranichenij na ispol'zovanie
struktur. Obyazatel'nye pravila zaklyuchayutsya v tom, chto edins-
tvennye operacii, kotorye vy mozhete provodit' so struktura-
mi, sostoyat v opredelenii ee adresa s pomoshch'yu operacii & i
dostupe k odnomu iz ee chlenov. |to vlechet za soboj to, chto
struktury nel'zya prisvaivat' ili kopirovat' kak celoe, i chto
oni ne mogut byt' peredany funkciyam ili vozvrashcheny imi. (V
posleduyushchih versiyah eti ogranicheniya budut snyaty). Na ukaza-
teli struktur eti ogranicheniya odnako ne nakladyvayutsya, tak
chto struktury i funkcii vse zhe mogut s udobstvom rabotat'
sovmestno. I nakonec, avtomaticheskie struktury, kak i avto-
maticheskie massivy, ne mogut byt' inicializirovany; inicia-
lizaciya vozmozhna tol'ko v sluchae vneshnih ili staticheskih
struktur.
    Davajte razberem nekotorye iz etih voprosov, perepisav s
etoj cel'yu funkcii perobrazovaniya daty iz predydushchej glavy
tak, chtoby oni ispol'zovali struktury. Tak kak pravila zap-
reshchayut neposredstvennuyu peredachu struktury funkcii, to my
dolzhny libo peredavat' otdel'no komponenty, libo peredat'
ukazatel' vsej struktury. Pervaya vozmozhnost' demonstriruetsya
na primere funkcii DAY_OF_YEAR, kak my ee napisali v glave
5:

 D.YEARDAY = DAY_OF_YEAR(D.YEAR, D.MONTH, D.DAY);



drugoj sposob sostoit v peredache ukazatelya. esli my opishem
HIREDATE kak

 STRUCT  DATE HIREDATE;

i perepishem DAY_OF_YEAR nuzhnym obrazom, my smozhem togda na-
pisat'

 HIREDATE YEARDAY = DAY_OF_YEAR(&HIREDATE);

peredavaya ukazatel' na HIREDATE funkcii DAY_OF_YEAR . Funk-
ciya dolzhna byt' modificirovana, potomu chto ee argument te-
per' yavlyaetsya ukazatelem, a ne spiskom peremennyh.

   DAY_OF_YEAR(PD) /* SET DAY OF YEAR FROM MONTH, DAY */
   STRUCT DATE *PD;
   \(
INT I, DAY, LEAP;

DAY = PD->DAY;
LEAP = PD->YEAR % 4 == 0 && PD->YEAR % 100 != 0
   \!\! PD->YEAR % 400 == 0;
FOR (I =1;  I < PD->MONTH; I++)
   DAY += DAY_TAB[LEAP][I];
RETURN(DAY);
    \)


Opisanie

STRUCT DATE *PD;

govorit, chto PD yavlyaetsya ukazatelem struktury tipa DATE.
Zapis', pokazannaya na primere

PD->YEAR

yavlyaetsya novoj. Esli P - ukazatel' na strukturu, to
               P-> chlen struktury
               ------------------
obrashchaetsya k konkretnomu chlenu. (Operaciya -> - eto znak mi-
nus, za kotorym sleduet znak ">".)
    Tak kak PD ukazyvaet na strukturu, to k chlenu YEAR mozhno
obratit'sya i sleduyushchim obrazom

 (*PD).YEAR

no ukazateli struktur ispol'zuyutsya nastol'ko chasto, chto za-
pis' -> okazyvaetsya udobnym sokrashcheniem. Kruglye skobki v
(*PD).YEAR neobhodimy, potomu chto operaciya ukazaniya chlena



stuktury starshe , chem * . Obe operacii, "->" i ".", associi-
ruyutsya sleva napravo, tak chto konstrukcii sleva i sprava
zkvivalentny

 P->Q->MEMB    (P->Q)->MEMB
 EMP.BIRTHDATE.MONTH    (EMP.BIRTHDATE).MONTH

Dlya polnoty nizhe privoditsya drugaya funkciya, MONTH_DAY, pere-
pisannaya s ispol'zovaniem struktur.

   MONTH_DAY(PD) /* SET MONTH AND DAY FROM DAY OF YEAR */
   STRUCT DATE *PD;
   \(
 INT I, LEAP;

 LEAP = PD->YEAR % 4 == 0 && PD->YEAR % 100 != 0
    \!\! PD->YEAR % 400 == 0;
 PD->DAY = PD->YEARDAY;
 FOR (I = 1; PD->DAY > DAY_TAB[LEAP][I]; I++)
    PD->DAY -= DAY_TAB[LEAP][I];
 PD->MONTH = I;
    \)

    Operacii raboty so strukturami "->" i "." naryadu so ()
dlya spiska argumentov i [] dlya indeksov nahodyatsya na samom
verhu ierarhii strashinstva operacij i, sledovatel'no, svyazy-
vayutsya ochen' krepko. Esli, naprimer, imeetsya opisanie

 STRUCT \(
    INT X;
    INT *Y;
 \) *P;

to vyrazhenie

 ++P->X

uvelichivaet h, a ne r, tak kak ono ekvivalentno vyrazheniyu
++(P->h). Dlya izmeneniya poryadka vypolneniya operacij mozhno
ispol'zovat' kruglye skobki: (++P)->h uvelichivaet P do dos-
tupa k h, a (P++)->X uvelichivaet P posle. (kruglye skobki v
poslednem sluchae neobyazatel'ny. Pochemu ?)
    Sovershenno analogichno *P->Y izvlekaet to, na chto ukazy-
vaet Y; *P->Y++ uvelichivaet Y posle obrabotki togo, na chto
on ukazyvaet (tochno tak zhe, kak i *S++); (*P->Y)++ uvelichi-
vaet to, na chto ukazyvaet Y; *P++->Y uvelichivaet P posle vy-
borki togo, na chto ukazyvaet Y.






    Struktury osobenno podhodyat dlya upravleniya massivami
svyazannyh peremennyh. Rassmotrim, naprimer, programmu pods-
cheta chisla vhozhdenij kazhdogo klyuchevogo slova yazyka "C". Nam
nuzhen massiv simvol'nyh strok dlya hraneniya imen i massiv ce-
lyh dlya podscheta. odna iz vozmozhnostej sostoit v ispol'zova-
nii dvuh parallel'nyh massivov KEYWORD i KEYCOUNT:

CHAR *KEYWORD [NKEYS];
INT  KEYCOUNT [NKEYS];

No sam fakt, chto massivy parallel'ny, ukazyvaet na vozmozh-
nost' drugoj organizacii. Kazhdoe klyuchevoe slovo zdes' po su-
shchestvu yavlyaetsya paroj:

CHAR *KEYWORD;
INT  KEYCOUNT;

i, sledovatel'no, imeetsya massiv par. Opisanie struktury

STRUCT KEY  \(
   CHAR *KEYWORD;
   INT  KEYCOUNT;
\) KEYTAB [NKEYS];

operdelyaet massiv KEYTAB struktur takogo tipa i otvodit dlya
nih pamyat'. Kazhdyj element massiva yavlyaetsya strukturoj. |to
mozhno bylo by zapisat' i tak:

STRUCT KEY  \(
   CHAR *KEYWORD;
   INT  KEYCOUNT;
\);
STRUCT KEY KEYTAB [NKEYS];

    Tak kak struktura KEYTAB fakticheski soderzhit postoyannyj
nabor imen, to legche vsego inicializirovat' ee odin raz i
dlya vseh chlenov pri opredelenii. Inicializaciya struktur
vpolne analogichna predydushchim inicializaciyam - za opredeleni-
em sleduet zaklyuchennyj v figurnye skobki spisok inicializa-
torov:

 STRUCT KEY  \(
    CHAR *KEYWORD;
    INT  KEYCOUNT;
 \) KEYTAB[] =\(
    "BREAK", 0,
    "CASE", 0,
    "CHAR", 0,
    "CONTINUE", 0,
    "DEFAULT", 0,
    /* ... */
    "UNSIGNED", 0,
    "WHILE", 0
 \);

Inicializatory perechislyayutsya parami sootvetstvenno chlenam
struktury. Bylo by bolee tochno zaklyuchat' v figurnye skobki
inicializatory dlya kazhdoj "stroki" ili struktury sleduyushchim
obrazom:

 \( "BREAK", 0 \),
 \( "CASE", 0 \),
 . . .



No kogda inicializatory yavlyayutsya prostymi peremennymi ili
simvol'nymi strokami i vse oni prisutstvuyut, to vo vnutren-
nih figurnyh skobkah net neobhodimosti. Kak obychno, kompilya-
tor sam vychislit chislo elementov massiva KEYTAB, esli inici-
alizatory prisutstvuyut, a skobki [] ostavleny pustymi.
    Programma podscheta klyuchevyh slov nachinaetsya s opredele-
niya massiva KEYTAB. vedushchaya programma chitaet svoj fajl vvo-
da, posledovatel'no obrashchayas' k funkcii GETWORD, kotoraya iz-
vlekaet iz vvoda po odnomu slovu za obrashchenie. Kazhdoe slovo
ishchetsya v massive KEYTAB s pomoshch'yu varianta funkcii binarnogo
poiska, napisannoj nami v glave 3. (Konechno, chtoby eta funk-
ciya rabotala, spisok klyuchevyh slov dolzhen byt' raspolozhen v
poryadke vozrastaniya).

 #DEFINE    MAXWORD   20

 MAIN()   /* COUNT "C" KEYWORDS */
 \(
 INT  N, T;
 CHAR WORD[MAXWORD];

 WHILE ((T = GETWORD(WORD,MAXWORD)) != EOF)
    IF (T == LETTER)
      IF((N = BINARY(WORD,KEYTAB,NKEYS)) >= 0)
         KEYTAB[N].KEYCOUNT++;
 FOR (N =0; N < NKEYS; N++)
    IF (KEYTAB[N].KEYCOUNT > 0)
     PRINTF("%4D %S\N",
       KEYTAB[N].KEYCOUNT, KEYTAB[N].KEYWORD);
 \)
 BINARY(WORD, TAB, N) /* FIND WORD IN TAB[0]...TAB[N-1] */
 CHAR *WORD;
 STRUCT KEY TAB[];
 INT N;
 \(
  INT LOW, HIGH, MID, COND;

  LOW = 0;
  HIGH = N - 1;
  WHILE (LOW <= HIGH) \(
    MID = (LOW+HIGH) / 2;
    IF((COND = STRCMP(WORD, TAB[MID].KEYWORD)) < 0)
     HIGH = MID - 1;
    ELSE IF (COND > 0)
     LOW = MID + 1;
    ELSE
     RETURN (MID);
  \)
  RETURN(-1);
 \)
My vskore privedem funkciyu GETWORD; poka dostatochno skazat',
chto ona vozvrashchaet LETTER kazhdyj raz, kak ona nahodit slovo,
i kopiruet eto slovo v svoj pervyj argument.



    Velichina NKEYS - eto kolichestvo klyuchevyh slov v massive
KEYTAB . Hotya my mozhem soschitat' eto chislo vruchnuyu, gorazdo
legche i nadezhnee poruchit' eto mashine, osobenno v tom sluchae,
esli spisok klyuchevyh slov podverzhen izmeneniyam. Odnoj iz
vozmozhnostej bylo by zakonchit' spisok inicializatorov ukaza-
niem na nul' i zatem projti v cikle skvoz' massiv KEYTAB,
poka ne najdetsya konec.
    No, poskol'ku razmer etogo massiva polnost'yu opredelen k
momentu kompilyacii, zdes' imeetsya bolee prostaya vozmozhnost'.
CHislo elementov prosto est'

SIZE OF KEYTAB / SIZE OF STRUCT KEY

delo v tom, chto v yazyke "C" predusmotrena unarnaya operaciya
SIZEOF, vypolnyaemaya vo vremya kompilyacii, kotoraya pozvolyaet
vychislit' razmer lyubogo ob容kta. Vyrazhenie

SIZEOF(OBJECT)

vydaet celoe, ravnoe razmeru ukazannogo ob容kta. (Razmer op-
redelyaetsya v nespecificirovannyh edinicah, nazyvaemyh "baj-
tami", kotorye imeyut tot zhe razmer, chto i peremennye tipa
CHAR). Ob容kt mozhet byt' fakticheskoj peremennoj, massivom i
strukturoj, ili imenem osnovnogo tipa, kak INT ili DOUBLE,
ili imenem proizvodnogo tipa, kak struktura. V nashem sluchae
chislo klyuchevyh slov ravno razmeru massiva, delennomu na raz-
mer odnogo elementa massiva. |to vychislenie ispol'zuetsya v
utverzhdenii #DEFINE dlya ustanovleniya znacheniya NKEYS:

#DEFINE NKEYS (SIZEOF(KEYTAB) / SIZEOF(STRUCT KEY))

    Teper' perejdem k funkcii GETWORD. My fakticheski napisa-
li bolee obshchij variant funkcii GETWORD, chem neobhodimo dlya
etoj programmy, no on ne na mnogo bolee slozhen. Funkciya
GETWORD vozvrashchaet sleduyushchee "slovo" iz vvoda, gde slovom
schitaetsya libo stroka bukv i cifr, nachinayushchihsya s bukvy, li-
bo otdel'nyj simvol. Tip ob容kta vozvrashchaetsya v kachetve zna-
cheniya funkcii; eto - LETTER, esli najdeno slovo, EOF dlya
konca fajla i sam simvol, esli on ne bukvennyj.

 GETWORD(W, LIM)   /* GET NEXT WORD FROM INPUT */
 CHAR *W;
 INT LIM;
 \(
  INT C, T;
  IF (TYPE(C=*W++=GETCH()) !=LETTER) \(
       *W='\0';
       RETURN(C);
  \)



 WHILE (--LIM > 0)  \(
  T = TYPE(C = *W++ = GETCH());
  IF (T ! = LETTER && T ! = DIGIT) \(
       UNGETCH(C);
       BREAK;
  \)
 \)
 *(W-1) - '\0';
 RETURN(LETTER);
 \)

Funkciya GETWORD ispol'zuet funkcii GETCH i UNGETCH, kotorye
my napisali v glave 4: kogda nabor alfavitnyh simvolov pre-
ryvaetsya, funkciya GETWORD poluchaet odin lishnij simvol. V re-
zul'tate vyzova UNGETCH etot simvol pomeshchaetsya nazad vo vvod
dlya sleduyushchego obrashcheniya.
    Funkciya GETWORD obrashchaetsya k funkcii TYPE dlya opredele-
niya tipa kazhdogo otdel'nogo simvola iz fajla vvoda. Vot va-
riant, spravedlivyj tol'ko dlya alfavita ASCII.

  TYPE(C)  /* RETURN TYPE OF ASCII CHARACTER */
  INT C;
   \(
  IF (C>= 'A' && C<= 'Z' \!\! C>= 'A' && C<= 'Z')
       RETURN(LETTER);
  ELSE IF (C>= '0' && C<= '9')
       RETURN(DIGIT);
  ELSE
       RETURN(C);
  \)


Simvolicheskie konstanty LETTER i DIGIT mogut imet' lyubye
znacheniya, lish' by oni ne vstupali v konflikt s simvolami,
otlichnymi ot bukvenno-cifrovyh, i s EOF; ochevidno vozmozhen
sleduyushchij vybor

     #DEFINE   LETTER   'A'
     #DEFINE   DIGIT   '0'

funkciya GETWORD mogla by rabotat' bystree, esli by obrashcheniya
k funkcii TYPE byli zameneny obrashcheniyami k sootvetstvuyushchemu
massivu TYPE[ ]. V standartnoj biblioteke yazyka "C" predus-
motreny makrosy ISALPHA i ISDIGIT, dejstvuyushchie neobhodimym
obrazom.

    Uprazhnenie 6-1
    --------------
    Sdelajte takuyu modifikaciyu funkcii GETWORD i ocenite,
kak izmenitsya skorost' raboty programmy.

    Uprazhnenie 6-2
    --------------
    Napishite variant funkcii TYPE, ne zavisyashchij ot konkret-
nogo naborasimvolov.




    Uprazhnenie 6-3
    --------------
    Napishite variant programmy podscheta klyuchevyh slov, koto-
ryj by ne uchityval poyavleniya etih slov v zaklyuchennyh v ka-
vychki strokah.




    CHtoby proillyustrirovat' nekotorye soobrazheniya, svyazannye
s ispol'zovaniem ukazatelej i massivov struktur, davajte
snova sostavim programmu podscheta klyuchevyh strok, ispol'zuya
na etot raz ukazateli, a ne indeksy massivov.
    Vneshnee opisanie massiva KEYTAB ne nuzhno izmenyat', no
funkcii MAIN i BINARY trebuyut modifikacii.

    MAIN()   /* COUNT C KEYWORD; POINTER VERSION */
    \(
   INT  T;
   CHAR WORD[MAXWORD];
   STRUCT KEY *BINARY(), *P;
   WHILE ((T = GETWORD(WORD, MAXWORD;) !=EOF)
     IF (T==LETTER)
     IF ((P=BINARY(WORD,KEYTAB,NKEYS)) !=NULL)
             P->KEYCOUNT++;
   FOR (P=KEYTAB; P>KEYTAB + NKEYS; P++)
     IF (P->KEYCOUNT > 0)
  PRINTF("%4D %S/N", P->KEYCOUNT, P->KEYWORD);
    \)
     STRUCT KEY *BINARY(WORD, TAB, N) /* FIND WORD */
    CHAR *WORD   /* IN TAB[0]...TAB[N-1] */
    STRUCT KEY TAB [];
    INT N;
    \(
   INT  COND;
   STRUCT KEY *LOW = &TAB[0];
   STRUCT KEY *HIGH = &TAB[N-1];
   STRUCT KEY *MID;
   WHILE (LOW <= HIGH) \(
  MID = LOW + (HIGH-LOW) / 2;
  IF ((COND = STRCMP(WORD, MID->KEYWORD)) < 0)
        HIGH = MID - 1;
  ELSE IF (COND > 0)
        LOW = MID + 1;
  ELSE
        RETURN(MID);
   \)
   RETURN(NULL);
    \)

    Zdes' imeetsya neskol'ko momentov, kotorye stoit otme-
tit'. Vo-pervyh, opisanie funkcii BINARI dolzhno ukazyvat',
chto ona vozvrashchaet ukazatel' na strukturu tipa KEY, a ne na
celoe; eto ob座avlyaetsya kak v funkcii MAIN, tak i v BINARY.
Esli funkciya BINARI nahodit slovo, to ona vozvrashchaet ukaza-
tel' na nego; esli zhe net, ona vozvrashchaet NULL.



    Vo-vtoryh, vse obrashcheniya k elementam massiva KEYTAB osu-
shchestvlyayutsya cherez ukazateli. |to vlechet za soboj odno sushches-
tvennoe izmenenie v funkcii BINARY: srednij element bol'she
nel'zya vychislyat' prosto po formule

  MID = (LOW + HIGH) / 2

potomu chto slozhenie dvuh ukazatelej ne daet kakogo-nibud'
poleznogo rezul'tata (dazhe posle deleniya na 2) i v dejstvi-
tel'nosti yavlyaetsya nezakonnym. etu formulu nado zamenit' na

  MID = LOW + (HIGH-LOW) / 2

v rezul'tate kotoroj MID stanovitsya ukazatelem na element,
raspolozhennyj poseredine mezhdu LOW i HIGH.
    Vam takzhe sleduet razobrat'sya v inicializacii LOW i
HIGH. ukazatel' mozhno inicializirovat' adresom ranee oprede-
lennogo ob容kta; imenno kak my zdes' i postupili.
    V funkcii MAIN my napisali

  FOR (P=KEYTAB; P < KEYTAB + NKEYS; P++)

Esli P yavlyaetsya ukazatelem struktury, to lyubaya arifmetika s
P uchityvaet fakticheskij razmer dannoj struktury, tak chto P++
uvelichivaet P na nuzhnuyu velichinu, v rezul'tate chego P ukazy-
vaet na sleduyushchij element massiva struktur. No ne schitajte,
chto razmer struktury raven summe razmerov ee chlenov, - iz-za
trebovanij vyravnivaniya dlya razlichnyh ob容ktov v strukture
mogut voznikat' "dyry".
    I, nakonec, neskol'ko vtorostepennyj vopros o forme za-
pisi programmy. Esli vozvrashchaemaya funkciej velichina imeet
tip, kak, naprimer, v

     STRUCT KEY *BINARY(WORD, TAB, N)

To mozhet okazat'sya, chto imya funkcii trudno vydelit' sredi
teksta. V svyazi s etim inogda ispol'zuetsya drugoj stil' za-
pisi:

          STRUCT KEY *
      BINARY(WORD, TAB, N)

|to glavnym obrazom delo vkusa; vyberite tu formu, kotoraya
vam nravitsya, i priderzhivajtes' ee.




    Predpolozhim, chto nam nado spravit'sya s bolee obshchej zada-
chej, sostoyashchej v podschete chisla poyavlenij vseh slov v neko-
torom fajle vvoda. Tak kak spisok slov zaranee ne izvesten,
my ne mozhem ih uporyadochit' udobnym obrazom i ispol'zovat'
binarnyj poisk. My dazhe ne mozhem osushchestvlyat' posledovatel'-
nyj prosmotr pri postuplenii kazhdogo slova, s tem chtoby us-
tanovit', ne vstrechalos' li ono ranee; takaya programma budet
rabotat' vechno. (Bolee tochno, ozhidaemoe vremya raboty rastet
kak kvadrat chisla vvodimyh slov). Kak zhe nam organizovat'
programmu, chtoby spravit'sya so spiskom proizvol'nyh slov?



    Odno iz reshenij sostoit v tom, chtoby vse vremya hranit'
massiv postupayushchih do sih por slov v uporyadochennom vide, po-
meshchaya kazhdoe slovo v nuzhnoe mesto po mere ih postupleniya.
ODnako eto ne sleduet delat', peremeshchaya slova v linejnom
massive, - eto takzhe potrebuet slishkom mnogo vremeni. Vmesto
etogo my ispol'zuem strukturu dannyh, nazyvaemuyu doichnym de-
revom.
    Kazhdomu novomu slovu sootvetstvuet odin "uzel" dereva;
kazhdyj uzel soderzhit:
ukazatel' teksta slova
----------------------
schetchik chisla poyavlenij
-----------------------
ukazatel' uzla levogo potomka
-----------------------------
ukazatel' uzla pravogo potomka
------------------------------
Nikakoj uzel ne mozhet imet' bolee dvuh detej; vozmozhno ot-
sutsvie detej ili nalichie tol'ko odnogo potomka.
    Uzly sozdayutsya takim obrazom, chto levoe podderevo kazhdo-
go uzla soderzhit tol'ko te slova, kotorye men'she slova v
etom uzle, a pravoe podderevo tol'ko te slova, kotorye bol'-
she. CHtoby opredelit', nahoditsya li novoe slovo uzhe v dereve,
nachinayut s kornya i sravnivayut novoe slovo so slovom, hranya-
shchimsya v etom uzle. Esli slova sovpadayut, to vopros reshaetsya
utverditel'no. Esli novoe slovo men'she slova v dereve, to
perehodyat k rassmotreniyu levogo potomka; v protivnom sluchae
issleduetsya pravyj potomok. Esli v nuzhnom napravlenii poto-
mok otsutstvuet, to znachit novoe slovo ne nahoditsya v dereve
i mesto etogo nedostayushchego potomka kak raz i yavlyaetsya mes-
tom, kuda sleduet pomestit' novoe slovo. Poskol'ku poisk iz
lyubogo uzla privodit k poisku odnogo iz ego potomkov, to sam
process poiska po sushchestvu yavlyaetsya rekursivnym. V sootvets-
tvii s etim naibolee estestvenno ispol'zovat' rekursivnye
procedury vvoda i vyvoda.
    Vozvrashchayas' nazad k opisaniyu uzla, yasno, chto eto budet
struktura s chetyr'mya komponentami:

STRUCT TNODE \( /* THE BASIC NODE */
   CHAR *WORD; /* POINTS TO THE TEXT */
   INT   COUNT; /* NUMBER OF OCCURRENCES */
   STRUCT TNODE *LEFT; /* LEFT CHILD */
   STRUCT TNODE *RIGHT; /* RIGHT CHILD */
\);

|to "rekursivnoe" opisanie uzla mozhet pokazat'sya riskovan-
nym, no na samom dele ono vpolne korrektno. Struktura ne
imeet prava soderzhat' ssylku na samu sebya, no

STRUCT TNODE *LEFT;

opisyvaet LEFT kak ukazatel' na uzel, a ne kak sam uzel.



    Tekst samoj programmy okazyvaetsya udivitel'no malen'kim,
esli, konechno, imet' v rasporyazhenii nabor napisannyh nami
ranee procedur, obespechivayushchih nuzhnye dejstviya. My imeem v
vidu funkciyu GETWORD dlya izvlecheniya kazhdogo slova iz fajla
vvoda i funkciyu ALLOC dlya vydeleniya mesta dlya hraneniya slov.
    Vedushchaya programma prosto schityvaet slova s pomoshch'yu funk-
cii GETWORD i pomeshchaet ih v derevo, ispol'zuya funkciyu TREE.

#DEFINE   MAXWORD   20
MAIN()    /* WORD FREGUENCY COUNT */
\(
    STRUCT TNODE *ROOT, *TREE();
    CHAR WORD[MAXWORD];
    INT   T;
    ROOT = NULL;
    WHILE ((T = GETWORD(WORD, MAXWORD)) \! = EOF)
       IF (T == LETTER)
            ROOT = TREE(ROOT, WORD);
    TREEPRINT(ROOT);
\)

    Funkciya TREE sama po sebe prosta. Slovo peredaetsya funk-
ciej MAIN k verhnemu urovnyu (kornyu) dereva. Na kazhdom etape
eto slovo sravnivaetsya so slovom, uzhe hranyashchimsya v etom uz-
le, i s pomoshch'yu rekursivnogo obrashcheniya k TREE prosachivaetsya
vniz libo k levomu, libo k pravomu podderevu. V konce koncov
eto slovo libo sovpadaet s kakim-to slovom, uzhe nahodyashchimsya
v dereve (v etom sluchae schetchik uvelichivaetsya na edinicu),
libo programma natolknetsya na nulevoj ukazatel', svidetel'-
stvuyushchij o neobhodimosti sozdaniya i dobavleniya k derevu no-
vogo uzla. V sluchae sozdaniya novogo uzla funkciya TREE vozv-
rashchaet ukazatel' etogo uzla, kotoryj pomeshchaetsya v roditel'-
skij uzel.

 STRUCT TNODE *TREE(P, W)
        /* INSTALL W AT OR BELOW P */
 STRUCT TNODE *P;
 CHAR *W;
 \(
    STRUCT TNODE *TALLOC();
    CHAR *STRSAVE();
    INT COND;
    IF (P == NULL) \( /* A NEW WORD
       HAS ARRIVED */
         P == TALLOC(); /* MAKE A NEW NODE */
         P->WORD = STRSAVE(W);
         P->COUNT = 1;
         P->LEFT = P->RIGHT = NULL;
 \) ELSE IF ((COND = STRCMP(W, P->WORD)) == 0)
         P->COUNT++;     /* REPEATED WORD */
     ELSE IF (COND < 0)/* LOWER GOES INTO LEFT SUBTREE */
         P->LEFT = TREE(P->LEFT, W);
 ELSE            /* GREATER INTO RIGHT SUBTREE */
         P->RIGHT = TREE(P->RIGHT, W);
 RETURN(P);
 \)


    Pamyat' dlya novogo uzla vydelyaetsya funkciej TALLOC, yavlya-
yushchejsya adaptaciej dlya dannogo sluchaya funkcii ALLOC, napisan-
noj nami ranee. Ona vozvrashchaet ukazatel' svobodnogo prost-
ranstva, prigodnogo dlya hraneniya novogo uzla dereva. (My
vskore obsudim eto podrobnee). Novoe slovo kopiruetsya funk-
ciej STRSAVE v skrytoe mesto, schetchik inicializiruetsya edi-
nicej, i ukazateli oboih potomkov polagayutsya ravnymi nulyu.
|ta chast' programmy vypolnyaetsya tol'ko pri dobavlenii novogo
uzla k rebru dereva. My zdes' opustili proverku na oshibki
vozvrashchaemyh funkcij STRSAVE i TALLOC znachenij (chto nerazum-
no dlya prakticheski rabotayushchej programmy).
    Funkciya TREEPRINT pechataet derevo, nachinaya s levogo pod-
dereva; v kazhdom uzle snachala pechataetsya levoe podderevo
(vse slova, kotorye mladshe etogo slova), zatem samo slovo, a
zatem pravoe podderevo (vse slova, kotorye starshe). Esli vy
neuverenno operiruete s rekursiej, narisujte derevo sami i
napechatajte ego s pomoshch'yu funkcii TREEPRINT ; eto odna iz
naibolee yasnyh rekursivnyh procedur, kotoruyu mozhno najti.

 TREEPRINT (P) /* PRINT TREE  P  RECURSIVELY */
 STRUCT TNODE *P;
 \(
    IF (P != NULL)    \(
       TREEPRINT (P->LEFT);
       PRINTF("%4D %S\N", P->COUNT, P->WORD);
       TREEPRINT (P->RIGHT);
    \)
\)

    Prakticheskoe zamechanie: esli derevo stanovitsya "nesba-
lansirovannym" iz-za togo, chto slova postupayut ne v sluchaj-
nom poryadke, to vremya raboty programmy mozhet rasti slishkom
bystro. V hudshem sluchae, kogda postupayushchie slova uzhe uporya-
docheny, nastoyashchaya programma osushchestvlyaet dorogostoyashchuyu imi-
taciyu linejnogo poiska. Sushchestvuyut razlichnye obobshcheniya dvo-
ichnogo dereva, osobenno 2-3 derev'ya i AVL derev'ya, kotorye
ne vedut sebya tak "v hudshih sluchayah", no my ne budem zdes'
na nih ostanavlivat'sya.
    Prezhde chem rasstat'sya s etim primerom, umestno sdelat'
nebol'shoe otstuplenie v svyazi s voprosom o raspredelenii pa-
myati. YAsno, chto v programme zhelatel'no imet' tol'ko odin
raspredelitel' pamyati, dazhe esli emu prihoditsya razmeshchat'
razlichnye vidy ob容ktov. No esli my hotim ispol'zovat' odin
raspredelitel' pamyati dlya obrabotki zaprosov na vydelenie
pamyati dlya ukazatelej na peremennye tipa CHAR i dlya ukazate-
lej na STRUCT TNODE, to pri etom voznikayut dva voprosa. Per-
vyj: kak vypolnit' to sushchestvuyushchee na bol'shinstve real'nyh
mashin ogranichenie, chto ob容kty opredelennyh tipov dolzhny
udovletvoryat' trebovaniyam vyravnivaniya (naprimer, chasto ce-
lye dolzhny razmeshchat'sya v chetnyh adresah)? Vtoroj: kak orga-
nizovat' opisaniya, chtoby spravit'sya s tem, chto funkciya ALLOC
dolzhna vozvrashchat' razlichnye vidy ukazatelej ?
    Voobshche govorya, trebovaniya vyravnivaniya legko vypolnit'
za schet vydeleniya nekotorogo lishnego prostranstva, prosto
obespechiv to, chtoby raspredelitel' pamyati vsegda vozvrashchal
ukazatel', udovletvoryayushchij vsem ogranicheniyam vyravnivaniya.
Naprimer, na PDP-11 dostatochno, chtoby funkciya ALLOC vsegda
vozvrashchala chetnyj ukazatel', poskol'ku v chetnyj adres mozhno
pomestit' lyuboj tip ob容kta. edinstvennyj rashod pri etom -
lishnij simvol pri zaprose na nechetnuyu dlinu. Analogichnye
dejstviya predprinimayutsya na drugih mashinah. Takim obrazom,
realizaciya ALLOC mozhet ne okazat'sya perenosimoj, no ee is-
pol'zovanie budet perenosimym. Funkciya ALLOC iz glavy 5 ne
predusmatrivaet nikakogo opredelennogo vyravnivaniya; v glave
8 my prodemonstriruem, kak pravil'no vypolnit' etu zadachu.
    Vopros opisaniya tipa funkcii ALLOC yavlyaetsya muchitel'nym
dlya lyubogo yazyka, kotoryj ser'ezno otnositsya k proverke ti-
pov. Luchshij sposob v yazyke "C" - ob座avit', chto ALLOC vozvra-
shchaet ukazatel' na peremennuyu tipa CHAR, a zatem yavno preob-
razovat' etot ukazatel' k zhelaemomu tipu s pomoshch'yu operacii
perevoda tipov. Takim obrazom, esli opisat' P v vide

    CHAR *P;
to
    (STRUCT TNODE *) P

preobrazuet ego v vyrazheniyah v ukazatel' na strukturu tipa
TNODE . Sledovatel'no, funkciyu TALLOC mozhno zapisat' v vide:

STRUCT TNODE *TALLOC()
\(
   CHAR *ALLOC();

   RETURN ((STRUCT TNODE *) ALLOC(SIZEOF(STRUCT TNODE)));
\)


eto bolee chem dostatochno dlya rabotayushchih v nastoyashchee vremya
kompilyatorov, no eto i samyj bezopasnyj put' s uchetom buduyu-
shchego.

    Uprazhnenie 6-4
    ----------------
    Napishite programmu, kotoraya chitaet "C"-programmu i pecha-
taet v alfavitnom poryadke kazhduyu gruppu imen peremennyh, ko-
torye sovpadayut v pervyh semi simvolah, no otlichayutsya gde-to
dal'she. (Sdelajte tak, chtoby 7 bylo parametrom).

    Uprazhnenie 6-5
    ----------------
    Napishite programmu vydachi perekrestnyh ssylok, t.e.
Programmu, kotoraya pechataet spisok vseh slov dokumenta i dlya
kazhdogo iz etih slov pechataet spisok nomerov strok, v koto-
rye eto slovo vhodit.

    Uprazhnenie 6-6
    ----------------
    Napishite programmu, kotoraya pechataet slova iz svoego
fajla vvoda, raspolozhennye v poryadke ubyvaniya chastoty ih po-
yavleniya. Pered kazhdym slovom napechatajte chislo ego poyavle-
nij.





    Dlya illyustracii dal'nejshih aspektov ispol'zovaniya struk-
tur v etom razdele my napishem programmu, predstavlyayushchuyu so-
boj soderzhimoe paketa poiska v tablice. |ta programma yavlya-
etsya tipichnym predstavitelem podprogramm upravleniya simvol'-
nymi tablicami makroprocessora ili kompilyatora. Rassmotrim,
naprimer, operator #DEFINE yazyka "C". Kogda vstrechaetsya
stroka vida

#DEFINE YES    1

to imya YES i zamenyayushchij tekst 1 pomeshchayutsya v tablicu. Pozd-
nee, kogda imya YES poyavlyaetsya v operatore vida

INWORD = YES;

Ono dolzhno byt' zameshcheno na 1.
    Imeyutsya dve osnovnye procedury, kotorye upravlyayut imena-
mi i zamenyayushchimi ih tekstami. Funkciya INSTALL(S,T) zapisyva-
et imya S i zamenyayushchij tekst T v tablicu; zdes' S i T prosto
simvol'nye stroki. Funkciya LOOKUP(S) ishchet imya S v tablice i
vozvrashchaet libo ukazatel' togo mesta, gde eto imya najdeno,
libo NULL, esli etogo imeni v tablice ne okazalos'.
    Pri etom ispol'zuetsya poisk po algoritmu heshirovaniya -
postupayushchee imya preobrazuetsya v malen'koe polozhitel'noe chis-
lo, kotoroe zatem ispol'zuetsya dlya indeksacii massiva ukaza-
telej. |lement massiva ukazyvaet na nachalo cepochnyh blokov,
opisyvayushchih imena, kotorye imeyut eto znachenie heshirovaniya.
Esli nikakie imena pri heshirovanii ne poluchayut etogo znache-
niya, to elementom massiva budet NULL.
    Blokom cepi yavlyaetsya struktura, soderzhashchaya ukazateli na
sootvetstvuyushchee imya, na zamenyayushchij tekst i na sleduyushchij blok
v cepi. Nulevoj ukazatel' sleduyushchego bloka sluzhit priznakom
konca dannoj cepi.

STRUCT NLIST  \(  /* BASIC TABLE ENTRY */
     CHAR *NAME;
     CHAR *DEF;
     STRUCT NLIST *NEXT; /* NEXT ENTRY IN CHAIN */
\);

Massiv ukazatelej eto prosto

 DEFINE    HASHSIZE     100
 TATIC STRUCT NLIST *HASHTAB[HASHSIZE] /* POINTER TABLE */

    Znachenie funkcii heshirovaniya, ispol'zuemoj obeimi funk-
ciyami LOOKUP i INSTALL , poluchaetsya prosto kak ostatok ot
deleniya summy simvol'nyh znachenij stroki na razmer massiva.
(|to ne samyj luchshij vozmozhnyj algoritm, no ego dostoinstvo
sostoit v isklyuchitel'noj prostote).



 HASH(S)   /* FORM HASH VALUE FOR STRING */
 CHAR *S;
 \(
  INT HASHVAL;

  FOR (HASHVAL = 0; *S != '\0'; )
      HASHVAL += *S++;
  RETURN(HASHVAL % HASHSIZE);
 \)

    V rezul'tate processa heshirovaniya vydaetsya nachal'nyj in-
deks v massive HASHTAB ; esli dannaya stroka mozhet byt'
gde-to najdena, to imenno v cepi blokov, nachalo kotoroj uka-
zano tam. Poisk osushchestvlyaetsya funkciej LOOKUP. Esli funkciya
LOOKUP nahodit, chto dannyj element uzhe prisutstvuet, to ona
vozvrashchaet ukazatel' na nego; esli net, to ona vozvrashchaet
NULL.

STRUCT NLIST *LOOKUP(S) /* LOOK FOR S IN HASHTAB */
CHAR *S;
\(
STRUCT NLIST *NP;

FOR (NP = HASHTAB[HASH(S)]; NP != NULL;NP=NP->NEXT)
    IF (STRCMP(S, NP->NAME) == 0)
  RETURN(NP);  /* FOUND IT */
RETURN(NULL);    /* NOT FOUND */


    Funkciya INSTALL ispol'zuet funkciyu LOOKUP dlya opredele-
niya, ne prisutstvuet li uzhe vvodimoe v dannyj moment imya;
esli eto tak, to novoe opredelenie dolzhno vytesnit' staroe.
V protivnom sluchae sozdaetsya sovershenno novyj element. Esli
po kakoj-libo prichine dlya novogo elementa bol'she net mesta,
to funkciya INSTALL vozvrashchaet NULL.

   STRUCT NLIST *INSTALL(NAME, DEF) /* PUT (NAME, DEF) */
   CHAR *NAME, *DEF;
   \(
 STRUCT NLIST *NP, *LOOKUP();
 CHAR *STRSAVE(), *ALLOC();
 INT HASHVAL;

 IF((NP = LOOKUP(NAME)) == NULL) \( /* NOT FOUND */
  NP = (STRUCT NLIST *) ALLOC(SIZEOF(*NP));
  IF (NP == NULL)
     RETURN(NULL);
  IF ((NP->NAME = STRSAVE(NAME)) == NULL)
     RETURN(NULL);
  HASHVAL = HASH(NP->NAME);
  NP->NEXT = HASHTAB[HASHVAL];
  HASHTAB[HASHVAL] = NP;
 \) ELSE        /* ALREADY THERE */
      FREE((NP->DEF);/* FREE PREVIOUS DEFINITION */
 IF ((NP->DEF = STRSAVE(DEF)) == NULL)
      RETURN (NULL);
 RETURN(NP);
   \)



    Funkciya STRSAVE prosto kopiruet stroku, ukazannuyu v ka-
chestve argumenta, v mesto hraneniya, poluchennoe v rezul'tate
obrashcheniya k funkcii ALLOC. My uzhe priveli etu funkciyu v gla-
ve 5. Tak kak obrashchenie k funkcii ALLOC i FREE mogut prois-
hodit' v lyubom poryadke i v svyazi s problemoj vyravnivaniya,
prostoj variant funkcii ALLOC iz glavy 5 nam bol'she ne pod-
hodit; smotrite glavy 7 i 8.

    Uprazhnenie 6-7
    ---------------
    Napishite proceduru, kotoraya budet udalyat' imya i oprede-
lenie iz tablicy, upravlyaemoj funkciyami LOOKUP i INSTALL.

    Uprazhnenie 6-8
    ---------------
    Razrabotajte prostuyu, osnovannuyu na funkciyah etogo raz-
dela, versiyu processora dlya obrabotki konstrukcij #DEFINE ,
prigodnuyu dlya ispol'zovaniya s "C"-programmami. Vam mogut
takzhe okazat'sya poleznymi funkcii GETCHAR i UNGETCH.




    Kogda vopros ekonomii pamyati stanovitsya ochen' sushchestven-
nym, to mozhet okazat'sya neobhodimym pomeshchat' v odno mashinnoe
slovo neskol'ko razlichnyh ob容ktov; odno iz osobenno rasp-
rosranennyh upotreblenij - nabor odnobitovyh priznakov v
primeneniyah, podobnyh simvol'nym tablicam kompilyatora. vnesh-
ne obuslovlennye formaty dannyh, takie kak interfejsy appa-
ratnyh sredstv takzhe zachastuyu predpolagayut vozmozhnost' polu-
cheniya slova po chastyam.
    Predstav'te sebe fragment kompilyatora, kotoryj rabotaet
s simvol'noj tablicej. S kazhdym identifikatorom programmy
svyazana opredelennaya informaciya, naprimer, yavlyaetsya on ili
net klyuchevym slovom, yavlyaetsya li on ili net vneshnim i/ili
staticheskim i t.d. Samyj kompaktnyj sposob zakodirovat' ta-
kuyu informaciyu - pomestit' nabor odnobitovyh priznakov v ot-
del'nuyu peremennuyu tipa CHAR ili INT.
    Obychnyj sposob, kotorym eto delaetsya, sostoit v oprede-
lenii nabora "masok", otvechayushchih sootvetstvushchim bitovym po-
ziciyam, kak v

  #DEFINE   KEYWORD   01
  #DEFINE   EXTERNAL  02
  #DEFINE   STATIC    04

(chisla dolzhny byt' stepenyami dvojki). Togda obrabotka bitov
svedetsya k "zhonglirovaniyu bitami" s pomoshch'yu operacij sdviga,
maskirovaniya i dopolneniya, opisannyh nami v glave 2.
    Nekotorye chasto vstrechayushchiesya idiomy:

  FLAGS \!= EXTERNAL  \! STATIC;

vklyuchaet bity EXTERNAL i STATIC v FLAGS, v to vremya kak

  FLAGS &= \^(eXTERNAL \! STATIC);



ih vyklyuchaet, a

  IF ((FLAGS & (EXTERNAL \! STATIC)) == 0) ...

istinno, esli oba bita vyklyucheny.
    Hotya etimi idiomami legko ovladet', yazyk "C" v kachestve
al'ternativy predlagaet vozmozhnost' opredeleniya i obrabotki
polej vnutri slova neposredstvenno, a ne posredstvom pobito-
vyh logicheskih operacij. Pole - eto nabor smezhnyh bitov
vnutri odnoj peremennoj tipa INT. Sintaksis opredeleniya i
obrabotki polej osnovyvaetsya na strukturah. Naprimer, sim-
vol'nuyu tablicu konstrukcij #DEFINE, privedennuyu vyshe, mozhno
by bylo zamenit' opredeleniem treh polej:

    STRUCT  \(
  UNSIGNED IS_KEYWORD : 1;
  UNSIGNED IS_EXTERN  : 1;
  UNSIGNED IS_STATIC  : 1;
    \)    FLAGS;

Zdes' opredelyaetsya peremennaya s imenem FLAGS, kotoraya soder-
zhit tri 1-bitovyh polya. Sleduyushchee za dvoetochiem chislo zadaet
shirinu polya v bitah. Polya opisany kak UNSIGNED, chtoby pod-
cherknut', chto oni dejstvitel'no budut velichinami bez znaka.
    Na otdel'nye polya mozhno ssylat'sya, kak FLAGS.IS_STATIE,
FLAGS. IS_EXTERN, FLAGS.IS_KEYWORD I t.d., to est' tochno tak
zhe, kak na drugie chleny struktury. Polya vedut sebya podobno
nebol'shim celym bez znaka i mogut uchastvovat' v arifmetiches-
kih vyrazheniyah tochno tak zhe, kak i drugie celye. Takim obra-
zom, predydushchie primery bolee estestvenno perepisat' tak:

    FLAGS.IS_EXTERN = FLAGS.IS_STATIC = 1;

dlya vklyucheniya bitov;

    FLAGS.IS_EXTERN = FLAGS.IS_STATIC = 0;

dlya vyklyucheniya bitov;

  IF (FLAGS.IS_EXTERN == 0 &&FLAGS.IS_STATIC == 0)...

dlya ih proverki.
    Pole ne mozhet perekryvat' granicu INT; esli ukazannaya
shirina takova, chto eto dolzhno sluchit'sya, to pole vyravniva-
etsya po granice sleduyushchego INT. Polyam mozhno ne prisvaivat'
imena; neimenovannye polya (tol'ko dvoetochie i shirina) is-
pol'zuyutsya dlya zapolneniya svobodnogo mesta. CHtoby vynudit'
vyravnivanie na granicu sleduyushchego INT, mozhno ispol'zovat'
special'nuyu shirinu 0.



    Pri rabote s polyami imeetsya ryad momentov, na kotorye
sleduet obratit' vnimanie. Po-vidimomu naibolee sushchestvennym
yavlyaetsya to, chto otrazhaya prirodu razlichnyh apparatnyh sred-
stv, raspredelenie polej na nekotoryh mashinah osushchestvlyaetsya
sleva napravo, a na nekotoryh sprava nalevo. |to oznachaet,
chto hotya polya ochen' polezny dlya raboty s vnutrenne oprede-
lennymi strukturami dannyh, pri razdelenii vneshne opredelyae-
myh dannyh sleduet tshchatel'no rassmatrivat' vopros o tom, ka-
koj konec postupaet pervym.
    Drugie ogranicheniya, kotorye sleduet imet' v vidu: polya
ne imeyut znaka; oni mogut hranit'sya tol'ko v peremennyh tipa
INT (ili, chto ekvivalentno, tipa UNSIGNED); oni ne yavlyayutsya
massivami; oni ne imeyut adresov, tak chto k nim ne primenima
operaciya &.




    Ob容dineniya - eto peremennaya, kotoraya v razlichnye momen-
ty vremeni mozhet soderzhat' ob容kty raznyh tipov i razmerov,
prichem kompilyator beret na sebya otslezhivanie razmera i tre-
bovanij vyravnivaniya. Ob容dineniya predstavlyayut vozmozhnost'
rabotat' s razlichnymi vidami dannyh v odnoj oblasti pamyati,
ne vvodya v programmu nikakoj mashinno-zavisimoj informacii.
    V kachestve primera, snova iz simvol'noj tablicy kompilya-
tora, predpolozhim, chto konstanty mogut byt' tipa INT , FLOAT
ili byt' ukazatelyami na simvoly. znachenie kazhdoj konkretnoj
konstanty dolzhno hranit'sya v peremennoj sootvestvuyushchego ti-
pa, no vse zhe dlya upravleniya tablicej samym udobnym bylo by,
esli eto znachenie zanimalo by odin i tot zhe ob容m pamyati i
hranilos' v tom zhe samom meste nezavisimo ot ego tipa. eto i
yavlyaetsya naznacheniem ob容dineniya - vydelit' otdel'nuyu pere-
mennuyu, v kotoroj mozhno zakonno hranit' lyubuyu odnu iz pere-
mennyh neskol'kih tipov. Kak i v sluchae polej, sintaksis os-
novyvaetsya na strukturah.

  UNION U_TAG \(
  INT IVAL;
  FLOAT FVAL;
  CHAR *PVAL;
  \) UVAL;

Peremennaya UVAL budet imet' dostatochno bol'shoj razmer,chtoby
hranit' naibol'shij iz treh tipov, nezavisimo ot mashiny, na
kotoroj osushchestvlyaetsya kompilyaciya, - programma ne budet za-
visit' ot harakteristik apparatnyh sredstv. Lyuboj iz etih
treh tipov mozhet byt' prisvoen UVAR i zatem ispol'zovan v
vyrazheniyah, poka takoe ispol'zovanie sovmestimo: izvlekaemyj
tip dolzhen sovpadat' s poslednim pomeshchennym tipom. Delo
programmista - sledit' za tem, kakoj tip hranitsya v ob容di-
nenii v dannyj moment; esli chto-libo hranitsya kak odin tip,
a izvlekaetsya kak drugoj, to rezul'taty budut zaviset' ot
ispol'zuemoj mashiny.



    Sintaksicheski dostup k chlenam ob容dineniya osushchestvlyaetsya
sleduyushchim obrazom:

  imya ob容dineniya.chlen
  --------------------
ili
  ukazatel' ob容dineniya ->chlen
  ----------------------------

to est' tochno tak zhe, kak i v sluchae struktur. esli dlya ots-
lezhivaniya tipa, hranimogo v dannyj moment v UVAL, ispol'zu-
etsya peremennaya UTYPE, to mozhno vstretit' takoj uchastok
programmy:

  IF (UTYPE == INT)
  PRINTF("%D\N", UVAL.IVAL);
  ELSE IF (UTYPE == FLOAT)
  PRINTF("%F\N", UVAL.FVAL);
  ELSE IF (UTYPE == STRING)
  PRINTF("%S\N", UVAL.PVAL);
  ELSE
  PRINTF("BAD TYPE %D IN UTYPE\N", UTYPE);

    Ob容dineniya mogut poyavlyat'sya vnutri struktur i massivov
i naoborot. Zapis' dlya obrashcheniya k chlenu ob容dineniya v
strukture (ili naoborot) sovershenno identichna toj, kotoraya
ispol'zuetsya vo vlozhennyh strukturah. naprimer, v massive
struktur, opredelennym sleduyushchim obrazom

 STRUCT \(
 CHAR *NAME;
 INT FLAGS;
 INT UTYPE;
 UNION \(
 INT IVAL;
 FLOAT FVAL;
 CHAR *PVAL;
 \) UVAL;
  \) SYMTAB[NSYM];

na peremennuyu IVAL mozhno soslat'sya kak

  SYMTAB[I].UVAL.IVAL

a na pervyj simvol stroki PVAL kak

  *SYMTAB[I].UVAL.PVAL

   V sushchnosti ob容dinenie yavlyaetsya strukturoj, v kotoroj vse
chleny imeyut nulevoe smeshchenie. Sama struktura dostatochno ve-
lika, chtoby hranit' "samyj shirokij" chlen, i vyravnivanie
prigodno dlya vseh tipov, vhodyashchih v ob容dinenie. Kak i v
sluchae struktur, edinstvennymi operaciyami, kotorye v nastoya-
shchee vremya mozhno provodit' s ob容dineniyami, yavlyayutsya dostup k



chlenu i izvlechenie adresa; ob容dineniya ne mogut byt' prisvo-
eny, peredany funkciyam ili vozvrashcheny imi. ukazateli ob容di-
nenij mozhno ispol'zovat' v tochno takoj zhe manere, kak i uka-
zateli struktur.
   Programma raspredeleniya pamyati, privodimaya v glave 8 ,
pokazyvaet, kak mozhno ispol'zovat' ob容dinenie, chtoby sde-
lat' nekotoruyu peremennuyu vyrovnennoj po opredelennomu vidu
granicy pamyati.




   V yazyke "C" predusmotrena vozmozhnost', nazyvaemaya TYPEDEF
dlya vvedeniya novyh imen dlya tipov dannyh. Naprimer, opisanie

TYPEDEF INT LENGTH;

delaet imya LENGTH sinonimom dlya INT. "Tip" LENGTH mozhet byt'
ispol'zovan v opisaniyah, perevodov tipov i t.d. Tochno takim
zhe obrazom, kak i tip INT:

   LENGTH   LEN, MAXLEN;
   LENGTH   *LENGTHS[];

Analogichno opisaniyu

    TYPEDEF CHAR *STRING;

delaet STRING sinonimom dlya CHAR*, to est' dlya ukazatelya na
simvoly, chto zatem mozhno ispol'zovat' v opisaniyah vida

    STRING P, LINEPTR[LINES], ALLOC();

    Obratite vnimanie, chto ob座avlyaemyj v konstrukcii TYPEDEF
tip poyavlyaetsya v pozicii imeni peremennoj, a ne srazu za
slovom TYPEDEF. Sintaksicheski konstrukciya TYPEDEF podobna
opisaniyam klassa pamyati EXTERN, STATIC i t. D. my takzhe is-
pol'zovali propisnye bukvy, chtoby yasnee vydelit' imena.
    V kachestve bolee slozhnogo primera my ispol'zuem konst-
rukciyu TYPEDEF dlya opisaniya uzlov dereva, rassmotrennyh ra-
nee v etoj glave:

 TYPEDEF STRUCT TNODE \(     /* THE BASIC NODE */
 CHAR *WORD; /* POINTS TO THE TEXT */
 INT COUNT; /* NUMBER OF OCCURRENCES */
 STRUCT TNODE *LEFT;     /* LEFT CHILD */
 STRUCT TNODE *RIGHT;    /* RIGHT CHILD */
 \) TREENODE, *TREEPTR;

V rezul'tate poluchaem dva novyh klyuchevyh slova: TREENODE
(struktura) i TREEPTR (ukazatel' na strukturu). Togda funk-
ciyu TALLOC mozhno zapisat' v vide


 TREEPTR TALLOC()
 \(
    CHAR *ALLOC();
    RETURN((TREEPTR) ALLOC(SIZEOF(TREENODE)));
 \)

    Neobhodimo podcherknut', chto opisanie TYPEDEF ne privodit
k sozdaniyu novogo v kakom-libo smysle tipa; ono tol'ko do-
bavlyaet novoe imya dlya nekotorogo sushchestvuyushchego tipa. pri
etom ne voznikaet i nikakoj novoj semantiki: opisannye takim
sposobom peremennye obladayut tochno temi zhe svojstvami, chto i
peremennye, opisannye yavnym obrazom. Po sushchestvu konstrukciya
TYPEDEF shodna s #DEFINE za isklyucheniem togo, chto ona inter-
pretiruetsya kompilyatorom i potomu mozhet osushchestvlyat' podsta-
novki teksta, kotorye vyhodyat za predely vozmozhnostej mak-
roprocessora yazyka "C". Naprimer,

 TYPEDEF INT (*PFI) ();

sozdaet tip PFI dlya "ukazatelya funkcii, vozvrashchayushchej znache-
nie tipa INT", kotoryj zatem mozhno bylo by ispol'zovat' v
programme sortirovki iz glavy 5 v kontekste vida

PFI STRCMP, NUMCMP, SWAP;


    Imeyutsya dve osnovnye prichiny primeneniya opisanij
TYPEDEF. Pervaya prichina svyazana s parametrizaciej programmy,
chtoby oblegchit' reshenie problemy perenosimosti. Esli dlya ti-
pov dannyh, kotorye mogut byt' mashinno-zavisimymi, ispol'zo-
vat' opisanie TYPEDEF, to pri perenose programmy na druguyu
mashinu pridetsya izmenit' tol'ko eti opisaniya. Odna iz tipich-
nyh situacij sostoit v ispol'zovanii opredelyaemyh s pomoshch'yu
TYPEDEF imen dlya razlichnyh celyh velichin i v posleduyushchem
podhodyashchem vybore tipov SHORT, INT i LONG dlya kazhdoj imeyu-
shchejsya mashiny.
Vtoroe naznachenie TYPEDEF sostoit v obespechenii luchshej doku-
mentacii dlya programmy - tip s imenem TREEPTR mozhet okazat'-
sya bolee udobnym dlya vospriyatiya, chem tip, kotoryj opisan
tol'ko kak ukazatel' slozhnoj struktury.
I nakonec, vsegda sushchestvuet veroyatnost', chto v budushchem kom-
pilyator ili nekotoraya drugaya programma, takaya kak LINT, smo-
zhet ispol'zovat' soderzhashchuyusya v opisaniyah TYPEDEF informaciyu
dlya provedeniya nekotoroj dopolnitel'noj proverki programmy.





    Sredstva vvoda/vyvoda ne yavlyayutsya sostavnoj chast'yu yazyka
"s", tak chto my ne vydelyali ih v nashem predydushchem izlozhenii.
Odnako real'nye programmy vzaimodejstvuyut so svoej okruzhayu-
shchej sredoj gorazdo bolee slozhnym obrazom, chem my videli do
sih por. V etoj glave budet opisana "standartnaya biblioteka
vvoda/vyvoda", to est' nabor funkcij, razrabotannyh dlya
obespecheniya standartnoj sistemy vvoda/vyvoda dlya "s"- prog-
ramm. |ti funkcii prednaznacheny dlya udobstva programmnogo
interfejsa, i vse zhe otrazhayut tol'ko te operacii, kotorye
mogut byt' obespecheny na bol'shinstve sovremennyh operacion-
nyh sistem. Procedury dostatochno effektivny dlya togo, chtoby
pol'zovateli redko chuvstvovali neobhodimost' obojti ih "radi
effektivnosti", kak by ni byla vazhna konkretnaya zadacha. I,
nakonec, eti procedury zadumany byt' "perenosimymi" v tom
smysle, chto oni dolzhny sushchestvovat' v sovmestimom vide na
lyuboj sisteme, gde imeetsya yazyk "s", i chto programmy, koto-
rye ogranichivayut svoi vzaimodejstviya s sistemoj vozmozhnostya-
mi, predostavlyaemymi standartnoj bibliotekoj, mozhno budet
perenosit' s odnoj sistemy na druguyu po sushchestvu bez izmene-
nij.
    My zdes' ne budem pytat'sya opisat' vsyu biblioteku vvo-
da/vyvoda; my bolee zainteresovany v tom, chtoby prodemonst-
rirovat' sushchnost' napisaniya "s"-programm, kotorye vzaimodej-
stvuyut so svoej operacionnoj sredoj.




    Kazhdyj ishodnyj fajl, kotoryj obrashchaetsya k funkcii iz
standartnoj biblioteki, dolzhen vblizi nachala soderzhat' stro-
ku

 #INCLUDE <STDIO.H>

v fajle STDIO.H opredelyayutsya nekotorye makrosy i peremennye,
ispol'zuemye bibliotekoj vvoda/vyvoda. Ispol'zovanie uglovyh
skobok vmesto obychnyh dvojnyh kavychek - ukazanie kompilyatoru
iskat' etot fajl v spravochnike, soderzhashchem zagolovki stan-
dartnoj informacii (na sisteme UNIX obychno LUSRLINELUDE).
    Krome togo, pri zagruzke programmy mozhet okazat'sya neob-
hodimym ukazat' biblioteku yavno; na sisteme PDP-11 UNIX,
naprimer, komanda kompilyacii programmy imela by vid:

CC   ishodnye fajly i t.d. -LS

gde -LS ukazyvaet na zagruzku iz standartnoj biblioteki.





    Samyj prostoj mehanizm vvoda zaklyuchaetsya v chtenii po od-
nomu simvolu za raz iz "standartnogo vvoda", obychno s termi-
nala pol'zovatelya, s pomoshch'yu funkcii GETCHAR. Funkciya
GETCHAR() pri kazhdom k nej obrashchenii vozvrashchaet sleduyushchij



vvodimyj simvol. V bol'shinstve sred, kotorye podderzhivayut
yazyk "s", terminal mozhet byt' zamenen nekotorym fajlom s po-
moshch'yu oboznacheniya < : esli nekotoraya programma PROG ispol'-
zuet funkciyu GETCHAR to komandnaya stroka

PROG<INFILE

privedet k tomu, chto PROG budet chitat' iz fajla INFILE, a ne
s terminala. Pereklyuchenie vvoda delaetsya takim obrazom, chto
sama programma PROG ne zamechaet izmeneniya; v chastnosti stro-
ka"<INFILE" ne vklyuchaetsya v komandnuyu stroku argumentov v
ARGV. Pereklyuchenie vvoda okazyvaetsya nezametnym i v tom slu-
chae, kogda vyvod postupaet iz drugoj programmy posredstvom
potochnogo (PIPE) mehanizma; komandnaya stroka

OTHERPROG \! PROG

progonyaet dve programmy, OTHERPROG i PROG, i organizuet tak,
chto standartnym vvodom dlya PROG sluzhit standartnyj vyvod
OTHERPROG.
    Funkciya GETCHAR vozvrashchaet znachenie EOF, kogda ona popa-
daet na konec fajla, kakoj by vvod ona pri etom ne schityva-
la. Standartnaya biblioteka polagaet simvolicheskuyu konstantu
EOF ravnoj -1 (posredstvom #DEFINE v fajle STDIO.H), no pro-
verki sleduet pisat' v terminah EOF, a ne -1, chtoby izbezhat'
zavisimosti ot konkretnogo znacheniya.
    Vyvod mozhno osushchestvlyat' s pomoshch'yu funkcii PUTCHAR(C),
pomeshchayushchej simvol 's' v "standartnyj vvod", kotoryj po umol-
chaniyu yavlyaetsya terminalom. Vyvod mozhno napravit' v nekotoryj
fajl s pomoshch'yu oboznacheniya > : esli PROG ispol'zuet PUTCHAR,
to komandnaya stroka

PROG>OUTFILE

privedet k zapisi standartnogo vyvoda v fajl OUTFILE, a ne
na terminal. Na sisteme UNIX mozhno takzhe ispol'zovat' potoch-
nyj mehanizm. Stroka

 PROG \! ANOTHERPROG

pomeshchaet standartnyj vyvod PROG v standartnyj vvod
ANOTHERPROG. I opyat' PROG ne budet osvedomlena ob izmenenii
napravleniya.
    Vyvod, osushchestvlyaemyj funkciej PRINTF, takzhe postupaet v
standartnyj vyvod, i obrashcheniya k PUTCHAR i PRINTF mogut pe-
remezhat'sya.
    Porazitel'noe kolichestvo programm chitaet tol'ko iz odno-
go vhodnogo potoka i pishet tol'ko v odin vyhodnoj potok; dlya
takih programm vvod i vyvod s pomoshch'yu funkcij GETCHAR,
PUTCHAR i PRINTF mozhet okazat'sya vpolne adekvatnym i dlya na-
chala opredelenno dostatochnym. |to osobenno spravedlivo tog-



da, kogda imeetsya vozmozhnost' ukazaniya fajlov dlya vvoda i
vyvoda i potochnyj mehanizm dlya svyazi vyvoda odnoj programmy
s vvodom drugoj. Rassmotrim, naprimer, programmu LOWER, ko-
toraya preobrazuet propisnye bukvy iz svoego vvoda v stroch-
nye:

#INCLUDE <STDIO.H>

MAIN() /* CONVERT INPUT TO LOWER CASE */
\(
 INT C;

 WHILE ((C = GETCHAR()) != EOF)
    PUTCHAR(ISUPPER(C) ? TOLOWER(C) : C);
\)

"Funkcii" ISUPPER i TOLOWER na samom dele yavlyayutsya makrosa-
mi, opredelennymi v STDIO.H . Makros ISUPPER proveryaet, yav-
lyaetsya li ego argument bukvoj iz verhnego registra, i vozv-
rashchaet nenulevoe znachenie, esli eto tak, i nul' v protivnom
sluchae. Makros TOLOWER preobrazuet bukvu iz verhnego regist-
ra v tu zhe bukvu nizhnego registra. Nezavisimo ot togo, kak
eti funkcii realizovany na konkretnoj mashine, ih vneshnee po-
vedenie sovershenno odinakovo, tak chto ispol'zuyushchie ih prog-
rammy izbavleny ot znaniya simvol'nogo nabora.
    Esli trebuetsya preobrazovat' neskol'ko fajlov, to mozhno
sobrat' eti fajly s pomoshch'yu programmy, podobnoj utilite CAT
sistemy UNIX,

 CAT FILE1 FILE2 ... \! LOWER>OUTPUT

i izbezhat' tem samym voprosa o tom, kak obratit'sya k etim
fajlam iz programmy. (Programma CAT privoditsya pozzhe v etoj
glave).
    Krome togo otmetim, chto v standartnoj biblioteke vvo-
da/vyvoda "funkcii" GETCHAR i PUTCHAR na samom dele mogut
byt' makrosami. |to pozvolyaet izbezhat' nakladnyh rashodov na
obrashchenie k funkcii dlya obrabotki kazhdogo simvola. V glave 8
my prodemonstriruem, kak eto delaetsya.





    Dve funkcii: PRINTF dlya vyvoda i SCANF dlya vvoda (sledu-
yushchij razdel) pozvolyayut preobrazovyvat' chislennye velichiny v
simvol'noe predstavlEnie i obratno. Oni takzhe pozvolyayut ge-
nerirovat' i interpretirovat' formatnye stroki. My uzhe vsyudu
v predydushchih glavah neformal'no ispol'zovali funkciyu PRINTF;
zdes' privoditsya bolee polnoe i tochnoe opisanie. Funkciya

PRINTF(CONTROL, ARG1, ARG2, ...)



preobrazuet, opredelyaet format i pechataet svoi argumenty v
standartnyj vyvod pod upravleniem stroki CONTROL. Upravlyayu-
shchaya stroka soderzhit dva tipa ob容ktov: obychnye simvoly, ko-
torye prosto kopiruyutsya v vyhodnoj potok, i specifikacii
preobrazovanij, kazhdaya iz kotoryh vyzyvaet preobrazovanie i
pechat' ocherednogo argumenta PRINTF.
    Kazhdaya specifikaciya preobrazovaniya nachinaetsya s simvola
% i zakanchivaetsya simvolom preobrazovaniya. Mezhdu % i simvo-
lom preobrazovaniya mogut nahodit'sya:
- znak minus, kotoryj ukazyvaet o vyravnivanii preobrazovan-
  nogo argumenta po levomu krayu ego polya.
- Stroka cifr, zadayushchaya minimal'nuyu shirinu polya. Preobrazo-
  vannoe chislo budet napechatano v pole po krajnej mere etoj
  shiriny, a esli neobhodimo, to i v bolee shirokom. Esli pre-
  obrazovannyj argument imeet men'she simvolov, chem ukazannaya
  shirina polya, to on budet dopolnen sleva (ili sprava, esli
  bylo ukazano vyravnivanie po levomu krayu)zapolnyayushchimi sim-
  volami do etoj shiriny. Zapolnyayushchim simvolom obychno yavlyaet-
  sya probel, a esli shirina polya ukazyvaetsya s lidiruyushchim nu-
  lem, to etim simvolom budet nul' (lidiruyushchij nul' v dannom
  sluchae ne oznachaet vos'merichnoj shiriny polya).
- Tochka, kotoraya otdelyaet shirinu polya ot sleduyushchej stroki
  cifr.
- Stroka cifr (tochnost'), kotoraya ukazyvaet maksimal'noe
  chislo simvolov stroki, kotorye dolzhny byt' napechatany, ili
  chislo pechataemyh sprava ot desyatichnoj tochki cifr dlya pere-
  mennyh tipa FLOAT ili DOUBLE.
- Modifikator dliny L, kotoryj ukazyvaet, chto sootvetstvuyu-
  shchij element dannyh imeet tip LONG, a ne INT.
     Nizhe privodyatsya simvoly preobrazovaniya i ih smysl:

D - argument preobrazuetsya k desyatichnomu vidu.
O - Argument preobrazuetsya v bezznakovuyu vos'merichnuyu formu
  (bez lidiruyushchego nulya).
X - Argument preobrazuetsya v bezznakovuyu shestnadcaterichnuyu
  formu (bez lidiruyushchih 0X).
U - Argument preobrazuetsya v bezznakovuyu desyatichnuyu formu.
C - Argument rassmatrivaetsya kak otdel'nyj simvol.
S - Argument yavlyaetsya strokoj: simvoly stroki pechatayutsya do
  teh por, poka ne budet dostignut nulevoj simvol ili ne bu-
  det napechatano kolichestvo simvolov, ukazannoe v specifika-
  cii tochnosti.
E - Argument, rassmatrivaemyj kak peremennaya tipa FLOAT ili
  DOUBLE, preobrazuetsya v desyatichnuyu formu v vide
  [-]M.NNNNNNE[+-]XX, gde dlina stroki iz N opredelyaetsya
  ukazannoj tochnost'yu. Tochnost' po umolchaniyu ravna 6.
F - Argument, rassmatrivaemyj kak peremennaya tipa FLOAT ili
  DOUBLE, preobrazuetsya v desyatichnuyu formu v vide
  [-]MMM.NNNNN, gde dlina stroki iz N opredelyaetsya ukazannoj
  tochnost'yu. Tochnost' po umolchaniyu ravna 6. otmetim, chto eta
  tochnost' ne opredelyaet kolichestvo pechataemyh v formate F
  znachashchih cifr.



G - Ispol'zuetsya ili format %E ili %F, kakoj koroche; nezna-
  chashchie nuli ne pechatayutsya.
Esli idushchij za % simvol ne yavlyaetsya simvolom preobrazovaniya,
to pechataetsya sam etot simvol; sledovatel'no,simvol % mozhno
napechatat', ukazav %%.
    Bol'shinstvo iz formatnyh preobrazovanij ochevidno i bylo
proillyustrirovano v predydushchih glavah. Edinstvennym isklyuche-
niem yavlyaetsya to, kak tochnost' vzaimodejstvuet so strokami.
Sleduyushchaya tablica demonstriruet vliyanie zadaniya razlichnyh
specifikacij na pechat' "HELLO, WORLD" (12 simvolov). My po-
mestili dvoetochiya vokrug kazhdogo polya dlya togo, chtoby vy
mogli videt' ego protyazhennost'.

:%10S:          :HELLO, WORLD:
:%10-S:         :HELLO, WORLD:
:%20S:          :    HELLO, WORLD:
:%-20S:         :HELLO, WORLD      :
:%20.10S:       :      HELLO, WOR:
:%-20.10S:      :HELLO, WOR      :
:%.10S:         :HELLO, WOR:

    Predosterezhenie: PRINTF ispol'zuet svoj pervyj argument
dlya opredeleniya chisla posleduyushchih argumentov i ih tipov. Es-
li kolichestvo argumentov okazhetsya nedostatochnym ili oni bu-
dut imet' nesootvetstvennye tipy, to vozniknet putanica i vy
poluchite bessmyslennye rezul'taty.

    Uprazhnenie 7-1
    --------------

    Napishite programmu, kotoraya budet pechatat' razumnym ob-
razom proizvol'nyj vvod. Kak minimum ona dolzhna pechatat'
negraficheskie simvoly v vos'merichnom ili shestnadcaterichnom
vide (v sootvetstvii s prinyatymi u vas obychayami) i sklady-
vat' dlinnye stroki.




    Osushchestvlyayushchaya vvod funkciya SCANF yavlyaetsya analogom
PRINTF i pozvolyaet provodit' v obratnom napravlenii mnogie
iz teh zhe samyh preobrazovanij. Funkciya

SCANF(CONTROL, ARG1, ARG2, ...)

chitaet simvoly iz standartnogo vvoda, interpretiruet ih v
sootvetstvii s formatom, ukazannom v argumente CONTROL, i
pomeshchaet rezul'taty v ostal'nye argumenty. Upravlyayushchij argu-
ment opisyvaetsya nizhe; drugie argumenty, kazhdyj iz kotoryh
dolzhen byt' ukazatelem, opredelyayut, kuda sleduet pomestit'
sootvetstvuyushchim obrazom preobrazovannyj vvod.
    Upravlyayushchaya stroka obychno soderzhit specifikacii preobra-
zovaniya, kotorye ispol'zuyutsya dlya neposredstvennoj interpre-
tacii vhodnyh posledovatel'nostej. Upravlyayushchaya stroka mozhet
soderzhat':
- probely, tabulyacii ili simvoly novoj stroki ("simvoly pus-
  tyh promezhutkov"), kotorye ignoriruyutsya.



- Obychnye simvoly (ne %), kotorye predpolagayutsya sovpadayushchi-
  mi so sleduyushchimi otlichnymi ot simvolov pustyh promezhutkov
  simvolami vhodnogo potoka.
- Specifikacii preobrazovaniya, sostoyashchie iz simvola %, neo-
  byazatel'nogo simvola podavleniya prisvaivaniya *, neobyaza-
  tel'nogo chisla, zadayushchego maksimal'nuyu shirinu polya i sim-
  vola preobrazovaniya.
    Specifikaciya preobrazovaniya upravlyaet preobrazovaniem
sleduyushchego polya vvoda. normal'no rezul'tat pomeshchaetsya v pe-
remennuyu, kotoraya ukazyvaetsya sootvetstvuyushchim argumentom.
Esli, odnako , s pomoshch'yu simvola * ukazano podavlenie pris-
vaivaniya, to eto pole vvoda prosto propuskaetsya i nikakogo
prisvaivaniya ne proizvoditsya. Pole vvoda opredelyaetsya kak
stroka simvolov, kotorye otlichny ot simvolov prostyh prome-
zhutkov; ono prodolzhaetsya libo do sleduyushchego simvola pustogo
promezhutka, libo poka ne budet ischerpana shirina polya, esli
ona ukazana. Otsyuda sleduet, chto pri poiske nuzhnogo ej vvo-
da, funkciya SCANF budet peresekat' granicy strok, poskol'ku
simvol novoj stroki vhodit v chislo pustyh promezhutkov.
    Simvol preobrazovaniya opredelyaet interpretaciyu polya vvo-
da; soglasno trebovaniyam osnovannoj na vyzove po znacheniyu
semantiki yazyka "s" sootvetstvuyushchij argument dolzhen byt'
ukazatelem. Dopuskayutsya sleduyushchie simvoly preobrazovaniya:
D - na vvode ozhidaetsya desyatichnoe celoe; sootvetstvuyushchij ar-
   gument dolzhen byt' ukazatelem na celoe.
O - Na vvode ozhidaetsya vos'merichnoe celoe (s lidiruyushchim nu-
   lem ili bez nego); sootvetstvuyushchij argument dolzhen byt'
   ukazatelem na celoe.
X - Na vvode ozhidaetsya shestnadcaterichnoe celoe (s lidiruyushchi-
   mi 0X ili bez nih); sootvetstvuyushchij argument dolzhen byt'
   ukazatelem na celoe.
H - Na vvode ozhidaetsya celoe tipa SHORT; sootvetsvuyushchij ar-
   gument dolzhen byt' ukazatelem na celoe tipa SHORT.
C - Ozhidaetsya otdel'nyj simvol; sootvetstvuyushchij argument
   dolzhen byt' ukazatelem na simvoly; sleduyushchij vvodimyj
   simvol pomeshchaetsya v ukazannoe mesto. Obychnyj propusk sim-
   volov pustyh promezhutkov v etom sluchae podavlyaetsya; dlya
   chteniya sleduyushchego simvola, kotoryj ne yavlyaetsya simvolom
   pustogo promezhutka, pol'zujtes' specifikaciej preobrazo-
   vaniya %1S.
S - Ozhidaetsya simvol'naya stroka; sootvetstvuyushchij argument
   dolzhen byt' ukazatelem simvolov, kotoryj ukazyvaet na
   massiv simvolov, kotoryj dostatochno velik dlya prinyatiya
   stroki i dobavlyaemogo v konce simvola \0.
F - Ozhidaetsya chislo s plavayushchej tochkoj; sootvetstvuyushchij ar-
   gument dolzhen byt' ukazatelem na peremennuyu tipa FLOAT.
E - simvol preobrazovaniya E yavlyaetsya sinonimom dlya F. Format
   vvoda peremennoj tipa FLOAT vklyuchaet neobyazatel'nyj znak,
   stroku cifr, vozmozhno soderzhashchuyu desyatichnuyu tochku i neo-
   byazatel'noe pole eksponenty, sostoyashchee iz bukvy E, za ko-
   toroj sleduet celoe, vozmozhno imeyushchee znak.



    Pered simvolami preobrazovaniya D, O i X mozhet stoyat' L,
kotoraya oznachaet , chto v spiske argumentov dolzhen nahodit'sya
ukazatel' na peremennuyu tipa LONG, a ne tipa INT. Analogich-
no, bukva L mozhet stoyat' pered simvolami preobrazovaniya E
ili F, govorya o tom, chto v spiske argumentov dolzhen naho-
dit'sya ukazatel' na peremennuyu tipa DOUBLE, a ne tipa FLOAT.
    Naprimer, obrashchenie
INT I;
FLOAT X;
CHAR NAME[50];
SCANF("&D %F %S", &I, &X, NAME);

so strokoj na vvode

25  54.32E-1   THOMPSON

privodit k prisvaivaniyu I znacheniya 25,X - znacheniya 5.432 i
NAME - stroki "THOMPSON", nadlezhashchim obrazom zakonchennoj
simvolom \ 0. eti tri polya vvoda mozhno razdelit' stol'kimi
probelami, tabulyaciyami i simvolami novyh strok, skol'ko vy
pozhelaete. Obrashchenie

 INT  I;
 FLOAT X;
 CHAR NAME[50];
 SCANF("%2D %F %*D %2S", &I, &X, NAME);

s vvodom

 56789 0123 45A72

prisvoit I znachenie 56, X - 789.0, propustit 0123 i pomestit
v NAME stroku "45". pri sleduyushchem obrashchenii k lyuboj procedu-
re vvoda rassmotrenie nachnetsya s bukvy A. V etih dvuh prime-
rah NAME yavlyaetsya ukazatelem i, sledovatel'no, pered nim ne
nuzhno pomeshchat' znak &.
    V kachestve drugogo primera perepishem teper' elementarnyj
kal'kulyator iz glavy 4, ispol'zuya dlya preobrazovaniya vvoda
funkciyu SCANF:

  #INCLUDE  <STDIO.H>
  MAIN()    /* RUDIMENTARY DESK CALCULATOR */
  \(
  DOUBLE SUM, V;
  SUM =0;
  WHILE (SCANF("%LF", &V) !=EOF)
       PRINTF("\T%.2F\N", SUM += V);
  \)

vypolnenie funkcii SCANF zakanchivaetsya libo togda, kogda ona
ischerpyvaet svoyu upravlyayushchuyu stroku, libo kogda nekotoryj
element vvoda ne sovpadaet s upravlyayushchej specifikaciej. V
kachestve svoego znacheniya ona vozvrashchaet chislo pravil'no sov-
padayushchih i prisvoennyh elementov vvoda. |to chislo mozhet byt'



ispol'zovano dlya opredeleniya kolichestva najdennyh elementov
vvoda. pri vyhode na konec fajla vozvrashchaetsya EOF; podcherk-
nem, chto eto znachenie otlichno ot 0, chto sleduyushchij vvodimyj
simvol ne udovletvoryaet pervoj specifikacii v upravlyayushchej
stroke. Pri sleduyushchem obrashchenii k SCANF poisk vozobnovlyaetsya
neposredstvenno za poslednim vvedennym simvolom.
    Zaklyuchitel'noe predosterezhenie: argumenty funkcii SCANF
dolzhny byt' ukazatelyami. Nesomnenno naibolee rasprostranen-
naya oshibka sostoit v napisanii

 SCANF("%D", N);

vmesto

 SCANF("%D", &N);







    Ot funkcii SCANF i PRINTF proishodyat funkcii SSCANF i
SPRINTF, kotorye osushchestvlyayut analogichnye preobrazovaniya, no
operiruyut so strokoj, a ne s fajlom. Obrashcheniya k etim funk-
ciyam imeyut vid:

 SPRINTF(STRING, CONTROL, ARG1, ARG2, ...)
 SSCANF(STRING, CONTROL, ARG1, ARG2, ...)

Kak i ran'she , funkciya SPRINTF preobrazuet svoi argumenty
ARG1, ARG2 i t.d. V sootvetstvii s formatom, ukazannym v
CONTROL, no pomeshchaet rezul'taty v STRING, a ne v standartnyj
vyvod. KOnechno, stroka STRING dolzhna byt' dostatochno velika,
chtoby prinyat' rezul'tat. Naprimer, esli NAME - eto simvol'-
nyj massiv, a N - celoe, to

SPRINTF(NAME, "TEMP%D", N);

sozdaet v NAME stroku vida TEMPNNN, gde NNN - znachenie N.
    Funkciya SSCANF vypolnyaet obratnye preobrazovaniya - ona
prosmatrivaet stroku STRING v sootvetstvii s formatom v ar-
gumente CONTROL i pomeshchaet rezul'tiruyushchie znacheniya v argu-
menty ARG1, ARG2 i t.d.eti argumenty dolzhny byt' ukazatelya-
mi. V rezul'tate obrashcheniya

SSCANF(NAME, "TEMP%D", &N);

peremennaya N poluchaet znachenie stroki cifr, sleduyushchih za
TEMP v NAME.

    Uprazhnenie 7-2
    --------------
    Perepishite nastol'nyj kal'kulyator iz glavy 4, ispol'zuya
dlya vvoda i preobrazovaniya chisel SCANF i/ili SSCANF.






    Vse do sih por napisannye programmy chitali iz standart-
nogo vvoda i pisali v standartnyj vyvod, otnositel'no koto-
ryh my predpolagali, chto oni magicheskim obrazom predostavle-
ny programme mestnoj operacionnoj sistemoj.
    Sleduyushchim shagom v voprose vvoda-vyvoda yavlyaetsya napisa-
nie programmy, rabotayushchej s fajlom, kotoryj ne svyazan zara-
nee s programmoj. odnoj iz programm, kotoraya yavno demonstri-
ruet potrebnost' v takih operaciyah, yavlyaetsya CAT, kotoraya
ob容dinyaet nabor iz neskol'kih imenovannyh fajlov v standar-
tnyj vyvod. Programma CAT ispol'zuetsya dlya vyvoda fajlov na
terminal i v kachestve universal'nogo sborshchika vvoda dlya
programm, kotorye ne imeyut vozmozhnosti obrashchat'sya k fajlam
po imeni. Naprimer, komanda

CAT X.C.Y.C

pechataet soderzhimoe fajlov X.C i Y.C v standartnyj vyvod.
    Vopros sostoit v tom, kak organizovat' chtenie iz imeno-
vannyh fajlov, t.e., kak svyazat' vneshnie imena, kotorymi
myslit pol'zovatel', s fakticheski chitayushchimi dannye operato-
rami.

    |ti pravila prosty. Prezhde chem mozhno schityvat' iz neko-
torogo fajla ili zapisyvat' v nego, etot fajl dolzhen byt'
otkryt s pomoshch'yu funkcii FOPEN iz standartnoj biblioteki.
funkciya FOPEN beret vneshnee imya (podobnoe X.C ili Y.C), pro-
vodit nekotorye obsluzhivayushchie dejstviya i peregovory s opera-
cionnoj sistemoj (detali kotoryh ne dolzhny nas kasat'sya) i
vozvrashchaet vnutrennee imya, kotoroe dolzhno ispol'zovat'sya pri
posleduyushchih chteniyah iz fajla ili zapisyah v nego.
    |to vnutrennee imya, nazyvaemoe "ukazatelem fajla", fak-
ticheski yavlyaetsya ukazatelem struktury, kotoraya soderzhit in-
formaciyu o fajle, takuyu kak mesto razmeshcheniya bufera, tekushchaya
poziciya simvola v bufere, proishodit li chtenie iz fajla ili
zapis' v nego i tomu podobnoe. Pol'zovateli ne obyazany znat'
eti detali, potomu chto sredi opredelenij dlya standartnogo
vvoda-vyvoda, poluchaemyh iz fajla STDIO.H, soderzhitsya opre-
delenie struktury s imenem FILE. Edinstvennoe neobhodimoe
dlya ukazatelya fajla opisanie demonstriruetsya primerom:

        FILE *FOPEN(), *FP;

    Zdes' govoritsya, chto FP yavlyaetsya ukazatelem na FILE i
FOPEN vozvrashchaet ukazatel' na FILE. Obratite vnimanie, chto
FILE yavlyaetsya imenem tipa, podobnym INT, a ne yarlyku struk-
tury; eto realizovano kak TYPEDEF. (Podrobnosti togo, kak
vse eto rabotaet na sisteme UNIX, privedeny v glave 8).
    Fakticheskoe obrashchenie k funkcii FOPEN v programme imeet
vid:
       FP=FOPEN(NAME,MODE);



  Pervym argumentom funkcii FOPEN yavlyaetsya "imya" fajla, koto-
roe zadaetsya v vide simvol'noj stroki. Vtoroj argument MODE
("rezhim") takzhe yavlyaetsya simvol'noj strokoj, kotoraya ukazy-
vaet, kak etot fajl budet ispol'zovat'sya. Dopustimymi rezhi-
mami yavlyayutsya: chtenie ("R"), zapis' ("W") i dobavlenie
("A").
 Esli vy otkroete fajl, kotoryj eshche ne sushchetvuet, dlya za-
 pisi ili dobavleniya, to takoj fajl budet sozdan (esli eto
vozmozhno). Otkrytie sushchestvuyushchego fajla na zapis' privodit k
otbrasyvaniyu ego starogo soderzhimogo. Popytka chteniya nesu-
shchestvuyushchego fajla yavlyaetsya oshchibkoj. Oshibki mogut byt' obus-
 lovleny i drugimi prichinami (naprimer, popytkoj chteniya iz
 fajla, ne imeya na to razresheniya). Pri nalichii kakoj-libo
 oshibki funkciya vozvrashchaet nulevoe znachenie ukazatelya NULL
(kotoroe dlya udobstva takzhe opredelyaetsya v fajle STDIO.H).
  Drugoj neobhodimoj veshch'yu yavlyaetsya sposob chteniya ili za-
pisi, esli fajl uzhe otkryt. Zdes' imeetsya neskol'ko vozmozh-
nostej, iz kotoryh GETC i PUTC yavlyayutsya prostejshimi.funkciya
GETC vozvrashchaet sleduyushchij simvol iz fajla; ej neobhodim uka-
zatel' fajla, chtoby znat', iz kakogo fajla chitat'. Takim ob-
                          razom,

            C=GETC(FP)

pomeshchaet v "C" sleduyushchij simvol iz fajla, ukazannogo posred-
stvom FP, i EOF, esli dostignut konec fajla.
    Funkciya PUTC, yavlyayushchayasya obrashcheniem k funkcii GETC,

           PUTC(C,FP)

pomeshchaet simvol "C" v fajl FP i vozvrashchaet "C". Podobno fun-
kciyam GETCHAR i PUTCHAR, GETC i PUTC mogut byt' makrosami, a
ne funkciyami.
    Pri zapuske programmy avtomaticheski otkryvayutsya tri faj-
la, kotorye snabzheny opredelennymi ukazatelyami fajlov. |timi
fajlami yavlyayutsya standartnyj vvod, standartnyj vyvod i stan-
dartnyj vyvod oshibok; sootvetstvuyushchie ukazateli fajlov nazy-
vayutsya STDIN, STDOUT i STDERR. Obychno vse eti ukazateli svya-
zany s terminalom, no STDIN i STDOUT mogut byt' perenaprav-
leny na fajly ili v potok (PIPE), kak opisyvalos' v razdele
7.2.
    Funkcii GETCHAR i PUTCHAR mogut byt' opredeleny v termi-
nalah GETC, PUTC, STDIN i STDOUT sleduyushchim obrazom:
#DEFINE GETCHAR() GETC(STDIN) #DEFINE PUTCHAR(C)  PUTC(C,
STDOUT)
Pri rabote s fajlami dlya formatnogo vvoda i vyvoda mozhno is-
pol'zovat' funkcii FSCANF i FPRINTF. Oni identichny funkciyam
SCANF i PRINTF, za isklyucheniem togo, chto pervym argumentom
yavlyaetsya ukazatel' fajla, opredelyayushchij tot fajl, kotoryj bu-
det chitat'sya ili kuda budet vestis' zapis'; upravlyayushchaya
stroka budet vtorym argumentom.



    Pokonchiv s predvaritel'nymi zamechaniyami, my teper' v
sostoyanii napisat' programmu CAT dlya konkatenacii fajlov.
Ispol'zuemaya zdes' osnovnaya shema okazyvaetsya udobnoj vo
mnogih programmah: esli imeyutsya argumenty v komandnoj stro-
ke, to oni obrabatyvayutsya posledovatel'no. Esli takie argu-
menty otsutstvuyut, to obrabatyvaetsya standartnyj vvod. |to
pozvolyaet ispol'zovat' programmu kak samostoyatel'no, tak i
kak chast' bol'shej zadachi.

  #INCLUDE <STDIO.H>
  MAIN(ARGC, ARGV)   /*CAT: CONCATENATE FILES*/
  INT ARGC;
  CHAR *ARGV[];
  \(
 FILE *FP, *FOPEN();
 IF(ARGC==1) /*NO ARGS; COPY STANDARD INPUT*/
 FILECOPY(STDIN);
 ELSE
 WHILE (--ARGC > 0)
      IF ((FP=FOPEN(*++ARGV,"R"))==NULL) \(
         PRINTF("CAT:CAN'T OPEN %\N",*ARGV);
         BREAK;
      \) ELSE \(
         FILECOPY(FP);
         FCLOSE(FP);
      \)
  \)
   FILECOPY(FP)  /*COPY FILE FP TO STANDARD OUTPUT*/
   FILE *FP;
   \(
  INT C;
  WHILE ((C=GETC(FP)) !=EOF)
  PUTC(C, STDOUT);
   \)

Ukazateli fajlov STDIN i STDOUT zaranee opredeleny v biblio-
teke vvoda-vyvoda kak standartnyj vvod i standartnyj vyvod;
oni mogut byt' ispol'zovany v lyubom meste, gde mozhno ispol'-
zovat' ob容kt tipa FILE*.oni odnako yavlyayutsya konstantami, a
ne peremennymi, tak chto ne pytajtes' im chto-libo prisvai-
vat'.
    Funkciya FCLOSE yavlyaetsya obratnoj po otnosheniyu k FOPEN;
ona razryvaet svyaz' mezhdu ukazatelem fajla i vneshnim imenem,
ustanovlennuyu funkciej FOPEN, i vysvobozhdaet ukazatel' fajla
dlya drugogo fajla.bol'shinstvo operacionnyh sistem imeyut ne-
kotorye ogranicheniya na chislo odnovremenno otkrytyh fajlov,
kotorymi mozhet rasporyazhat'sya programma. Poetomu, to kak my
postupili v CAT, osvobodiv ne nuzhnye nam bolee ob容kty, yav-
lyaetsya horoshej ideej. Imeetsya i drugaya prichina dlya primene-
niya funkcii FCLOSE k vyhodnomu fajlu - ona vyzyvaet vydachu
informacii iz bufera, v kotorom PUTC sobiraet vyvod. (Pri
normal'nom zavershenii raboty programmy funkciya FCLOSE vyzy-
vaetsya avtomaticheski dlya kazhdogo otkrytogo fajla).






    Obrabotka oshibok v CAT neideal'na. Neudobstvo zaklyuchaet-
sya v tom, chto esli odin iz fajlov po nekotoroj prichine oka-
zyvaetsya nedostupnym, diagnosticheskoe soobshchenie ob etom pe-
chataetsya v konce ob容dinennogo vyvoda. |to priemlemo, esli
vyvod postupaet na terminal, no ne goditsya, esli vyvod pos-
tupaet v nekotoryj fajl ili cherez potochnyj (PIPELINE) meha-
nizm v druguyu programmu.
    CHtoby luchshe obrabatyvat' takuyu situaciyu, k programme
tochno takim zhe obrazom, kak STDIN i STDOUT, prisoedinyaetsya
vtoroj vyhodnoj fajl, nazyvaemyj STDERR. Esli eto voobshche
vozmozhno, vyvod, zapisannyj v fajle STDERR, poyavlyaetsya na
terminale pol'zovatelya, dazhe esli standartnyj vyvod naprav-
lyaetsya v drugoe mesto.
    Davajte peredelaem programmu CAT takim obrazom, chtoby
soobshcheniya ob oshibkah pisalis' v standartnyj fajl oshibok.

   "INCLUDE  <STDIO.H>
   MAIN(ARGC,ARGV)  /*CAT: CONCATENATE FILES*/
   INT ARGC;
   CHAR *ARGV[];
   \(
  FILE *FP, *FOPEN();
  IF(ARGC==1)  /*NO ARGS; COPY STANDARD INPUT*/
  FILECOPY(STDIN);
  ELSE
  WHILE (--ARGC > 0)
     IF((FP=FOPEN(*++ARGV,"R#))==NULL) \(
     PRINTF(STDERR,
       "CAT: CAN'T OPEN,%S\N", ARGV);
     EXIT(1);
  \) ELSE \(
     FILECOPY(FP);
  \)
  EXIT(0);
   \)

Programma soobshchaet ob oshibkah dvumya sposobami. Diagnostiches-
koe soobshchenie, vydavaemoe funkciej FPRINTF, postupaet v
STDERR i, takim obrazom, okazyvaetsya na terminale pol'zova-
telya, a ne ischezaet v potoke (PIPELINE) ili v vyhodnom faj-
le.
    Programma takzhe ispol'zuet funkciyu EXIT iz standartnoj
biblioteki, obrashchenie k kotoroj vyzyvaet zavershenie vypolne-
niya programmy. Argument funkcii EXIT dostupen lyuboj program-
me, obrashchayushchejsya k dannoj funkcii, tak chto uspeshnoe ili neu-
dachnoe zavershenie dannoj programmy mozhet byt' provereno dru-
goj programmoj, ispol'zuyushchej etu v kachestve podzadachi. Po
soglasheniyu velichina 0 v kachetsve vozvrashchaemogo znacheniya svi-
detel'stvuet o tom, chto vse v poryadke, a razlichnye nenulevye
znacheniya yavlyayutsya priznakami normal'nyh situacij.



    Funkciya EXIT vyzyvaet funkciyu FCLOSE dlya kazhdogo otkry-
togo vyhodnogo fajla, s tem chtoby vyvesti vsyu pomeshchennuyu v
bufery vyhodnuyu informaciyu, a zatem vyzyvaet funkciyu _EXIT.
Funkciya _EXIT privodit k nemedlennomu zaversheniyu bez ochistki
kakih-libo buferov; konechno, pri zhelanii k etoj funkcii mozh-
no obratit'sya neposredstvenno.




    Standartnaya biblioteka soderzhit funkciyu FGETS, sovershen-
no analogichnuyu funkcii GETLINE, kotoruyu my ispol'zovali na
vsem protyazhenii knigi. V rezul'tate obrashcheniya

FGETS(LINE, MAXLINE, FP)

sleduyushchaya stroka vvoda (vklyuchaya simvol novoj stroki) schity-
vaetsya iz fajla FP v simvol'nyj massiv LINE; samoe bol'shoe
MAXLINE_1 simvol budet prochitan. Rezul'tiruyushchaya stroka za-
kanchivaetsya simvolom \ 0. Normal'no funkciya FGETS vozvrashchaet
LINE; v konce fajla ona vozvrashchaet NULL. (Nasha funkciya
GETLINE vozvrashchaet dlinu stroki, a pri vyhode na konec fajla
- nul').
    Prednaznachennaya dlya vyvoda funkciya FPUTS zapisyvaet
stroku (kotoraya ne obyazana soderzhat' simvol novoj stroki) v
fajl:

 FPUTS(LINE, FP)

    CHtoby pokazat', chto v funkciyah tipa FGETS i FPUTS net
nichego tainstvennogo, my privodim ih nizhe, skopirovannymi
neposredstvenno iz standartnoj biblioteki vvoda-vyvoda:

   #INCLUDE  <STDIO.H>
   CHAR *FGETS(S,N,IOP) /*GET AT MOST N CHARS FROM IOP*/
   CHAR *S;
   INT N;
   REGISTER FILE *IOP;
   \(
  REGISTER INT C;
  REGISTER CHAR *CS;
  CS = S;
  WHILE(--N>0&&(C=GETC(IOP)) !=EOF)
  IF ((*CS++ = C)=='\N')
       BREAK;
  *CS = '\0';
  RETURN((C==EOF && CS==S) 7 NULL : S);
   \)
   FPUTS(S,IOP) /*PUT STRING S ON FILS IOP*/
   REGISTER CHAR *S;
   REGISTER FILE *IOP;
   \(
  REGISTER INT C;
  WHILE (C = *S++)
  PUTC(C,IOP);
   \)




    Uprazhnenie 7-3
    ---------------
    Napishite programmu sravneniya dvuh fajlov, kotoraya budet
pechatat' pervuyu stroku i poziciyu simvola, gde oni razlichayut-
sya.

    Uprazhnenie 7-4
    ---------------
    Peredelajte programmu poiska zadannoj kombinacii simvo-
lov iz glavy 5 takim obrazom, chtoby v kachestve vvoda ispol'-
zovalsya nabor imenovannyh fajlov ili, esli nikakie fajly ne
ukazany kak argumenty, standartnyj vvod. Sleduet li pechatat'
imya fajla pri nahozhdenii podhodyashchej stroki?

    Uprazhnenie 7-5
    --------------
    Napishite programmu pechati nabora fajlov, kotoraya nachina-
et kazhdyj novyj fajl s novoj stranicy i pechataet dlya kazhdogo
fajla zagolovok i schetchik tekushchih stranic.




    Standartnaya biblioteka predostavlyaet mnozhestvo raznoob-
raznyh funkcij, nekotorye iz kotoryh okazyvayutsya osobenno
poleznymi. My uzhe upominali funkcii dlya raboty so strokami:
STRLEN, STRCPY, STRCAT i STRCMP. Vot nekotorye drugie.




    Nekotorye makrosy vypolnyayut proverku simvolov i preobra-
zovaniya:


 SALPHA(C) ne 0, esli "C" alfavitnyj simvol,
     0 - esli net.
 SUPPER(C) Ne 0, esli "C" bukva verhnego registra,
     0 - esli net.
 SLOWER(C) Ne 0, esli "C" bukva nizhnego registra,
     0 - esli net.
 SDIGIT(C) Ne 0, esli "C" cifra,
     0 - esli net.
 SSPACL(C) Ne 0, esli "C" probel, tabulyaciya
     ili novaya stroka, 0 - esli net.
 OUPPER(C) Preobrazuet "C" v bukvu verhnego registra.
 OLOWER(C) Preobrazuet "C" v bukvu nizhnego registra.




    Standartnaya biblioteka soderzhit dovol'no ogranichennuyu
versiyu funkcii UNGETCH, napisannoj nami v glave 4; ona nazy-
vaetsya UNGETC. V rezul'tate obrashcheniya

 UNGETC(C,FP)

simvol "C" vozvrashchaetsya v fajl FP. Pozvolyaetsya vozvrashchat' v
kazhdyj fajl tol'ko odin simvol. Funkciya UNGETC mozhet byt'
ispol'zovana v lyuboj iz funkcij vvoda i s makrosami tipa
SCANF, GETC ili GETCHAR.






    Funkciya SYSTEM(S) vypolnyaet komandu, soderzhashchuyusya v sim-
vol'noj stroke S, i zatem vozobnovlyaet vypolnenie tekushchej
programmy. Soderzhimoe S sil'no zavisit ot ispol'zuemoj ope-
racionnoj sistemy. V kachestve trivial'nogo primera, ukazhem,
chto na sisteme UNIX stroka

  SYSTEM("DATE");

privodit k vypolneniyu programmy DATE, kotoraya pechataet datu
i vremya dnya.




    Funkciya CALLOC ves'ma shodna s funkciej ALLOC, ispol'zo-
vannoj nami v predydushchih glavah. V rezul'tate obrashcheniya

  CALLOC(N, SIZEOF(OBJCCT))

vozvrashchaetsya libo ukazatel' prostranstva, dostatochnogo dlya
razmeshcheniya N ob容ktov ukazannogo razmera, libo NULL, esli
zapros ne mozhet byt' udvoletvoren. Otvodimaya pamyat' inicia-
liziruetsya nulevymi znacheniyami.
    Ukazatel' obladaet nuzhnym dlya rassmatrivaemyh ob容ktov
vyravnivaniem, no emu sleduet pripisyvat' sootvetstvuyushchij
tip, kak v

  CHAR *CALLOC();
  INT *IP;
  IP=(INT*) CALLOC(N,SIZEOF(INT));

    Funkciya CFREE(P) osvobozhdaet prostranstvo, na kotoroe
ukazyvaet "P", prichem ukazatel' "P" pevonachal'no dolzhen byt'
poluchen v rezul'tate obrashcheniya k CALLOC. Zdes' net nikakih
ogranichenij na poryadok osvobozhdeniya prostranstva, no budet
nepriyatnejshej oshibkoj osvobodit' chto-nibud', chto ne bylo po-
lucheno obrashcheniem k CALLOC.
    Realizaciya programmy raspredeleniya pamyati, podobnoj
CALLOC, v kotoroj razmeshchennye bloki mogut osvobozhdat'sya v
proizvol'nom poryadke, prodemonstrirovana v glave 8.





    Material etoj glavy otnositsya k interfejsu mezhdu s-prog-
rammami i operacionnoj sistemoj UNIX. Tak kak bol'shinstvo
pol'zovatelej yazyka "C" rabotayut na sisteme UNIX, eta glava
okazhetsya poleznoj dlya bol'shinstva chitatelej. dazhe esli vy
ispol'zuete s-kompilyator na drugoj mashine, izuchenie privodi-
myh zdes' primerov dolzhno pomoch' vam glubzhe proniknut' v me-
tody programmirovaniya na yazyke "C".
    |ta glava delitsya na tri osnovnye chasti: vvod/vyvod,
sistema fajlov i raspredelenie pamyati. Pervye dve chasti
predpolagayut nebol'shoe znakomstvo s vneshnimi harakteristika-
mi sistemy UNIX.
    V glave 7 my imeli delo s sistemnym interfejsom, kotoryj
odinakov dlya vsego mnogoobraziya operacionnyh sistem. Na kazh-
doj konkretnoj sisteme funkcii standartnoj biblioteki dolzhny
byt' napisany v terminah vvoda-vyvoda, dostupnyh na dannoj
mashine. V sleduyushchih neskol'kih razdelah my opishem osnovnuyu
sistemu svyazannyh s vvodom i vyvodom tochek vhoda operacion-
noj sistemy UNIX i proillyustriruem, kak s ih pomoshch'yu mogut
byt' realizovany razlichnye chasti standartnoj biblioteki.




    V operacionnoj sisteme UNIX ves' vvod i vyvod osushchestv-
lyaetsya posredstvom chteniya fajlov ili ih zapisi, potomu chto
vse periferijnye ustrojstva, vklyuchaya dazhe terminal pol'zova-
telya, yavlyayutsya fajlami opredelennoj fajlovoj sistemy. |to
oznachaet, chto odin odnorodnyj interfejs upravlyaet vsemi svya-
zyami mezhdu programmoj i periferijnymi ustrojstvami.
    V naibolee obshchem sluchae pered chteniem iz fajla ili za-
pis'yu v fajl neobhodimo soobshchit' sisteme o vashem namerenii;
etot process nazyvaetsya "otkrytiem" fajla. Sistema vyyasnya-
et,imeete li vy pravo postupat' takim obrazom (sushchestvuet li
etot fajl? imeetsya li u vas razreshenie na obrashchenie k ne-
mu?), i esli vse v poryadke, vozvrashchaet v programmu nebol'shoe
polozhitel'noe celoe chislo, nazyvaemoe deskriptorom fajla.
vsyakij raz, kogda etot fajl ispol'zuetsya dlya vvoda ili vyvo-
da, dlya identifikacii fajla upotreblyaetsya deskriptor fajla,
a ne ego imya. (Zdes' sushchestvuet primernaya analogiya s ispol'-
zovaniem READ (5,...) i WRITE (6,...) v fortrane). Vsya in-
formaciya ob otkrytom fajle soderzhitsya v sisteme; programma
pol'zovatelya obrashchaetsya k fajlu tol'ko cherez deskriptor faj-
la.
    Dlya udobstva vypolneniya obychnyh operacij vvoda i vyvoda
s pomoshch'yu terminala pol'zovatelya sushchestvuyut special'nye sog-
lasheniya. Kogda interpretator komand ("SHELL") progonyaet
programmu, on otkryvaet tri fajla, nazyvaemye standartnym
vvodom, standartnym vyvodom i standartnym vyvodom oshibok,
kotorye imeyut sootvetstvenno chisla 0, 1 i 2 v kachestve desk-
riptorov etih fajlov. V normal'nom sostoyanii vse oni svyazany
s terminalom, tak chto esli programma chitaet s deskriptorom
fajla 0 i pishet s deskriptorami fajlov 1 i 2, to ona mozhet
osushchestvlyat' vvod i vyvod s pomoshch'yu terminala, ne zabotyas'
ob otkrytii sootvetstvuyushchih fajlov.



    Pol'zovatel' programmy mozhet perenapravlyat' vvod i vyvod
na fajly, ispol'zuya operacii komandnogo interpretatora SHELL
"<" i ">" :

  PROG <INFILE>OUTFILE

V etom sluchae interpretator komand SHELL izmenit prisvaiva-
nie po umolchaniyu deskriptorov fajlov 0 i 1 s terminala na
ukazannye fajly. Normal'no deskriptor fajla 2 ostaetsya svya-
zannym s terminalom, tak chto soobshcheniya ob oshibkah mogut pos-
tupat' tuda. Podobnye zamechaniya spravedlivy i togda, kogda
vvod i vyvod svyazan s kanalom. Sleduet otmetit', chto vo vseh
sluchayah prikrepleniya fajlov izmenyayutsya interpretatorom
SHELL, a ne programmoj. Sama programma, poka ona ispol'zuet
fajl 0 dlya vvoda i fajly 1 i 2 dlya vyvoda, ne znaet ni otku-
da prihodit ee vvod, ni kuda postupaet ee vydacha.




    Samyj nizkij uroven' vvoda/vyvoda v sisteme UNIX ne pre-
dusmatrivaet ni kakoj-libo buferizacii, ni kakogo-libo dru-
gogo servisa; on po sushchestvu yavlyaetsya neposredstvennym vho-
dom v operacionnuyu sistemu. Ves' vvod i vyvod osushchestvlyaetsya
dvumya funkciyami: READ i WRITE. Pervym argumentom obeih funk-
cij yavlyaetsya deskriptor fajla. Vtorym argumentom yavlyaetsya
bufer v vashej programme, otkuda ili kuda dolzhny postupat'
dannye. Tretij argument - eto chislo podlezhashchih peresylke
bajtov. Obrashcheniya k etim funkciyam imeyut vid:

N_READ=READ(FD,BUF,N);
N_WRITTEN=WRITE(FD,BUF,N);

Pri kazhdom obrashchenii vozvrashchaetsya schetchik bajtov, ukazyvayu-
shchij fakticheskoe chislo peredannyh bajtov. Pri chtenii vozvra-
shchennoe chislo bajtov mozhet okazat'sya men'she, chem zaproshennoe
chislo. Vozvrashchennoe nulevoe chislo bajtov oznachaet konec faj-
la, a "-1" ukazyvaet na nalichie kakoj-libo oshibki. Pri zapi-
si vozvrashchennoe znachenie ravno chislu fakticheski zapisannyh
bajtov; nesovpadenie etogo chisla s chislom bajtov, kotoroe
predpolagalos' zapisat', obychno svidetel'stvuet ob oshibke.
    Kolichestvo bajtov, podlezhashchih chteniyu ili zapisi, mozhet
byt' sovershenno proizvol'nym. Dvumya samymi rasprostranennymi
velichinami yavlyayutsya "1", kotoraya oznachaet peredachu odnogo
simvola za obrashchenie (t.e. Bez ispol'zovaniya bufera), i
"512", kotoraya sootvetstvuet fizicheskomu razmeru bloka na
mnogih periferijnyh ustrojstvah. |tot poslednij razmer budet
naibolee effektivnym, no dazhe vvod ili vyvod po odnomu sim-
volu za obrashchenie ne budet neobyknovenno dorogim.
    Ob容diniv vse eti fakty, my napisali prostuyu programmu
dlya kopirovaniya vvoda na vyvod, ekvivalentnuyu programme ko-
pirovki fajlov, napisannoj v glave 1. Na sisteme UNIX eta
programma budet kopirovat' chto ugodno kuda ugodno, potomu
chto vvod i vyvod mogut byt' perenapravleny na lyuboj fajl ili
ustrojstvo.
   #DEFINE BUFSIZE 512 /*BEST SIZE FOR PDP-11 UNIX*/
   MAIN() /*COPY INPUT TO OUTPUT*/
   \(
  CHAR BUF[BUFSIZE];
  INT N;
  WHILE((N=READ(0,BUF,BUFSIZE))>0)
  WRITE(1,BUF,N);
   \)

Esli razmer fajla ne budet kraten BUFSIZE, to pri nekotorom
obrashchenii k READ budet vozvrashcheno men'shee chislo bajtov, ko-
torye zatem zapisyvayutsya s pomoshch'yu WRITE; pri sleduyushchem pos-
le etogo obrashchenii k READ budet vozvrashchen nul'.
    Pouchitel'no razobrat'sya, kak mozhno ispol'zovat' funkcii
READ i WRITE dlya postroeniya procedur bolee vysokogo urovnya,
takih kak GETCHAR, PUTCHAR i t.d. Vot, naprimer, variant
funkcii GETCHAR, osushchestvlyayushchij vvod bez ispol'zovaniya bufe-
ra.

   #DEFINE CMASK 0377 /*FOR MAKING CHAR'S > 0*/
   GETCHAR() /*UNBUFFERED SINGLE CHARACTER INPUT*/
   \(
  CHAR C;
  RETURN((READ(0,&C,1)>0 7 & CMASK : EOF);
   \)

Peremennaya "C" dolzhna byt' opisana kak CHAR, potomu chto fun-
kciya READ prinimaet ukazatel' na simvoly. Vozvrashchaemyj sim-
vol dolzhen byt' maskirovan chislom 0377 dlya garantii ego po-
lozhitel'nosti; v protivnom sluchae znakovyj razryad mozhet sde-
lat' ego znachenie otricatel'nym. (Konstanta 0377 podhodit
dlya evm PDP-11, no ne obyazatel'no dlya drugih mashin).
    Vtoroj variant funkcii GETCHAR osushchestvlyaet vvod bol'shi-
mi porciyami, a vydaet simvoly po odnomu za obrashchenie.

  #DEFINE CMASK 0377 /*FOR MAKING CHAR'S>0*/
  #DEFINE BUFSIZE 512
  GETCHAR() /*BUFFERED VERSION*/
  \(
 STATIC CHAR  BUF[BUFSIZE];
 STATIC CHAR  *BUFP = BUF;
 STATIC INT   N = 0;
 IF (N==0) \( /*BUFFER IS EMPTY*/
 N=READ(0,BUF,BUFSIZE);
 BUFP = BUF;
 \)
 RETURN((--N>=0) ? *BUFP++ & CMASK : EOF);
  \)




    Krome sluchaya, kogda po umolchaniyu opredeleny standartnye
fajly vvoda, vyvoda i oshibok, vy dolzhny yavno otkryvat' faj-
ly, chtoby zatem chitat' iz nih ili pisat' v nih. Dlya etoj ce-
li sushchestvuyut dve tochki vhoda: OPEN i CREAT.



    Funkciya OPEN ves'ma shodna s funkciej FOPEN, rassmotren-
noj v glave 7, za isklyucheniem togo, chto vmesto vozvrashcheniya
ukazatelya fajla ona vozvrashchaet deskriptor fajla, kotoryj yav-
lyaetsya prosto celym tipa INT.

INT FD;
FD=OPEN(NAME,RWMODE);

Kak i v sluchae FOPEN, argument NAME yavlyaetsya simvol'noj
strokoj, sootvetstvuyushchej vneshnemu imeni fajla. Odnako argu-
ment, opredelyayushchij rezhim dostupa, otlichen: RWMODE ravno: 0 -
dlya chteniya, 1 - dlya zapisi, 2 - dlya chteniya i zapisi. Esli
proishodit kakaya-to oshibka, funkciya OPEN vozvrashchaet "-1"; v
protivnom sluchae ona vozvrashchaet dejstvitel'nyj deskriptor
fajla.
    Popytka otkryt' fajl, kotoryj ne sushchestvuet, yavlyaetsya
oshibkoj. Tochka vhoda CREAT predostavlyaet vozmozhnost' sozda-
niya novyh fajlov ili perezapisi staryh. V rezul'tate obrashche-
niya

FD=CREAT(NAME,PMODE);

vozvrashchaet deskriptor fajla, esli okazalos' vozmozhnym soz-
dat' fajl s imenem NAME, i "-1" v protivnom sluchae. Esli
fajl s takim imenem uzhe sushchestvuet, CREAT usechet ego do nu-
levoj dliny; sozdanie fajla, kotoryj uzhe sushchestvuet, ne yav-
lyaetsya oshibkoj.
    Esli fajl yavlyaetsya sovershenno novym, to CREAT sozdaet
ego s opredelennym rezhimom zashchity, specificiruemym argumen-
tom PMODE. V sisteme fajlov na UNIX s fajlom svyazyvayutsya de-
vyat' bitov zashchity informacii, kotorye upravlyayut razresheniem
na chtenie, zapis' i vypolnenie dlya vladel'ca fajla, dlya
gruppy vladel'cev i dlya vseh ostal'nyh pol'zovatelej. Takim
obrazom, trehznachnoe vos'merichnoe chislo naibolee udobno dlya
specifikacii razreshenij. Naprimer, chislo 0755 svidetel'stvu-
et o razreshenii na chtenie, zapis' i vypolnenie dlya vladel'ca
i o razreshenii na chtenie i vypolnenie dlya gruppy i vseh os-
tal'nyh.
    Dlya illyustracii nizhe privoditsya programma kopirovaniya
odnogo fajla v drugoj, yavlyayushchayasya uproshchennym variantom uti-
lity CP sistemy UNIX. (Osnovnoe uproshchenie zaklyuchaetsya v tom,
chto nash variant kopiruet tol'ko odin fajl i chto vtoroj argu-
ment ne dolzhen byt' spravochnikom).

   #DEFINE NULL 0
   #DEFINE BUFSIZE 512
   #DEFINE PMODE 0644/*RW FOR OWNER,R FOR GROUP,OTHERS*/
   MAIN(ARGC,ARGV) /*CP: COPY F1 TO F2*/
   INT ARGC;
   CHAR *ARGV[];
   \(
  INT F1, F2, N;
  CHAR BUF[BUFSIZE];



  IF (ARGC ! = 3)
  ERROR("USAGE:CP FROM TO", NULL);
  IF ((F1=OPEN(ARGV[1],0))== -1)
  ERROR("CP:CAN'T OPEN %S", ARGV[1]);
  IF ((F2=CREAT(ARGV[2],PMODE))== -1)
  ERROR("CP: CAN'T CREATE %S", ARGV[2]);
  WHILE ((N=READ(F1,BUF,BUFSIZE))>0)
  IF (WRITE(F2,BUF,N) !=N)
       ERROR("CP: WRITE ERROR", NULL);
  EXIT(0);
   \)
   ERROR(S1,S2) /*PRINT ERROR MESSAGE AND DIE*/
   CHAR *S1, S2;
   \(
  PRINTF(S1,S2);
  PRINTF("\N");
  EXIT(1);
   \)

    Sushchestvuet ogranichenie (obychno 15 - 25) na kolichestvo
fajlov, kotorye programma mozhet imet' otkrytymi odnovremen-
no. V sootvetstvii s etim lyubaya programma, sobirayushchayasya ra-
botat' so mnogimi fajlami, dolzhna byt' podgotovlena k pov-
tornomu ispol'zovaniyu deskriptorov fajlov. Procedura CLOSE
preryvaet svyaz' mezhdu deskriptorom fajla i otkrytym fajlom i
osvobozhdaet deskriptor fajla dlya ispol'zovaniya s nekotorym
drugim fajlom. Zavershenie vypolneniya programmy cherez EXIT
ili v rezul'tate vozvrata iz vedushchej programmy privodit k
zakrytiyu vseh otkrytyh fajlov.
    Funkciya rascepleniya UNLINK (FILENAME) udalyaet iz sistemy
fajlov fajl s imenem FILENAME ( iz dannogo spravochnogo faj-
la. Fajl mozhet byt' sceplen s drugim spravochnikom, vozmozhno,
pod drugim imenem - primech.perevodchika).

    Uprazhnenie 8-1
    --------------
    Perepishite programmu CAT iz glavy 7, ispol'zuya funkcii
READ, WRITE, OPEN i CLOSE vmesto ih ekvivalentov iz standar-
tnoj biblioteki. Provedite eksperimenty dlya opredeleniya ot-
nositel'noj skorosti raboty etih dvuh variantov.




    Normal'no pri rabote s fajlami vvod i vyvod osushchestvlya-
etsya posledovatel'no: pri kazhdom obrashchenii k funkciyam READ i
WRITE chtenie ili zapis' nachinayutsya s pozicii, neposredstven-
no sleduyushchej za predydushchej obrabotannoj. No pri neobhodimos-
ti fajl mozhet chitat'sya ili zapisyvat'sya v lyubom proizvol'nom
poryadke. Obrashchenie k sisteme s pomoshch'yu funkcii LSEEK pozvo-
lyaet peredvigat'sya po fajlu, ne proizvodya fakticheskogo chte-
niya ili zapisi. V rezul'tate obrashcheniya

 LSEEK(FD,OFFSET,ORIGIN);



tekushchaya poziciya v fajle s deskriptorom FD peredvigaetsya na
poziciyu OFFSET (smeshchenie), kotoraya otschityvaetsya ot mesta,
ukazyvaemogo argumentom ORIGIN (nachalo otscheta). Posleduyushchee
chtenie ili zapis' budut teper' nachinat'sya s etoj pozicii.
Argument OFFSET imeet tip LONG; FD i ORIGIN imeyut tip INT.
Argument ORIGIN mozhet prinimat' znacheniya 0,1 ili 2, ukazyvaya
na to, chto velichina OFFSET dolzhna otschityvat'sya sootvetst-
venno ot nachala fajla, ot tekushchej pozicii ili ot konca faj-
la. Naprimer, chtoby dopolnit' fajl, sleduet pered zapis'yu
najti ego konec:

 LSEEK(FD,0L,2);

chtoby vernut'sya k nachalu ("peremotat' obratno"), mozhno napi-
sat':

 LSEEK(FD,0L,0);

obratite vnimanie na argument 0L; ego mozhno bylo by zapisat'
i v vide (LONG) 0.
    Funkciya LSEEK pozvolyaet obrashchat'sya s fajlami primerno
tak zhe, kak s bol'shimi massivami, pravda cenoj bolee medlen-
nogo dostupa. sleduyushchaya prostaya funkciya, naprimer, schityvaet
lyuboe kolichestvo bajtov, nachinaya s proizvol'nogo mesta v
fajle.

   GET(FD,POS,BUF,N) /*READ N BYTES FROM POSITION POS*/
   INT FD, N;
   LONG POS;
   CHAR *BUF;
   \(
  LSEEK(FD,POS,0); /*GET TO POS*/
  RETURN(READ(FD,BUF,N));
   \)

    V bolee rannih redakciyah, chem redakciya 7 sistemy UNIX,
osnovnaya tochka vhoda v sistemu vvoda-vyvoda nazyvaetsya SEEK.
Funkciya SEEK identichna funkcii LSEEK, za isklyucheniem togo,
chto argument OFFSET imeet tip INT, a ne LONG. v sootvetstvii
s etim, poskol'ku na PDP-11 celye imeyut tol'ko 16 bitov, ar-
gument OFFSET, ukazyvaemyj funkcii SEEK, ogranichen velichinoj
65535; po etoj prichine argument ORIGIN mozhet imet' znacheniya
3, 4, 5, kotorye zastavlyayut funkciyu SEEK umnozhit' zadannoe
znachenie OFFSET na 512 (kolichestvo bajtov v odnom fizicheskom
bloke) i zatem interpretirovat' ORIGIN, kak esli eto 0, 1
ili 2 sootvetstvenno. Sledovatel'no, chtoby dostich' proiz-
vol'nogo mesta v bol'shom fajle, nuzhno dva obrashcheniya k SEEK:
snachala odno, kotoroe vydelyaet nuzhnyj blok, a zatem vtoroe,
gde ORIGIN imeet znachenie 1 i kotoroe osushchestvlyaet peredvi-
zhenie na zhelaemyj bajt vnutri bloka.

    Uprazhnenie 8-2
    ---------------
    Ochevidno, chto SEEK mozhet byt' napisana v terminalah
LSEEK i naoborot. napishite kazhduyu funkciyu cherez druguyu.






    Davajte teper' na primere realizacii funkcij FOPEN i
GETC iz standartnoj biblioteki podprogramm prodemonstriruem,
kak nekotorye iz opisannyh elementov ob容dinyayutsya vmeste.
    Napomnim, chto v standartnoj biblioteke fajly opisyvatsya
posredstvom ukazatelej fajlov, a ne deskriptorov. Ukazatel'
fajla yavlyaetsya ukazatelem na strukturu, kotoraya soderzhit
neskol'ko elementov informacii o fajle: ukazatel' bufera,
chtoby fajl mog chitat'sya bol'shimi porciyami; schetchik chisla
simvolov, ostavshihsya v bufere; ukazatel' sleduyushchej pozicii
simvola v bufere; nekotorye priznaki, ukazyvayushchie rezhim chte-
niya ili zapisi i t.d.; deskriptor fajla.
    Opisyvayushchaya fajl struktura dannyh soderzhitsya v fajle
STDIO.H, kotoryj dolzhen vklyuchat'sya (posredstvom #INCLUDE) v
lyuboj ishodnyj fajl, v kotorom ispol'zuyutsya funkcii iz stan-
dartnoj biblioteki. On takzhe vklyuchaetsya funkciyami etoj bib-
lioteki. V privodimoj nizhe vyderzhke iz fajla STDIO.H imena,
prednaznachaemye tol'ko dlya ispol'zovaniya funkciyami bibliote-
ki, nachinayutsya s podcherkivaniya, s tem chtoby umen'shit' vero-
yatnost' sovpadeniya s imenami v programme pol'zovatelya.

 DEFINE _BUFSIZE 512
 DEFINE _NFILE   20 /*FILES THAT CAN BE HANDLED*/
  TYPEDEF STRUCT _IOBUF \(
    CHAR *_PTR;   /*NEXT CHARACTER POSITION*/
    INT  _CNT;    /*NUMBER OF CHARACTERS LEFT*/
    CHAR *_BASE;  /*LOCATION OF BUFFER*/
    INT  _FLAG;   /*MODE OF FILE ACCESS*/
    INT  _FD;     /*FILE DESCRIPTOR*/
 ) FILE;
 XTERN FILE _IOB[_NFILE];

 DEFINE   STDIN         (&_IOB[0])
 DEFINE   STDOUT        (&_IOB[1])
 DEFINE   STDERR        (&_IOB[2])

 DEFINE   _READ   01  /* FILE OPEN FOR READING */
 DEFINE   _WRITE  02  /* FILE OPEN FOR WRITING */
 DEFINE   _UNBUF  04  /* FILE IS UNBUFFERED */
 DEFINE   _BIGBUF 010 /* BIG BUFFER ALLOCATED */
 DEFINE   _EOF 020 /* EOF HAS OCCURRED ON THIS FILE */
 DEFINE   _ERR 040 /* ERROR HAS OCCURRED ON THIS FILE */
 DEFINE   NULL 0
 DEFINE   EOF  (-1)

 DEFINE   GETC(P) (--(P)->_CNT >= 0 \
   ? *(P)->_PTR++ & 0377 : _FILEBUF(P))
 DEFINE   GETCHAR() GETC(STDIN)

 DEFINE   PUTC(X,P) (--(P)->_CNT >= 0 \
   ? *(P)->_PTR++ = (X) : _FLUSHBUF((X),P))
 DEFINE   PUTCHAR(X)       PUTC(X,STDOUT)



    V normal'nom sostoyanii makros GETC prosto umen'shaet
schetchik, peredvigaet ukazatel' i vozvrashchaet simvol. (Esli
opredelenie #DEFINE slishkom dlinnoe, to ono prodolzhaetsya s
pomoshch'yu obratnoj kosoj cherty). Esli odnako schetchik stanovit-
sya otricatel'nym, to GETC vyzyvaet funkciyu _FILEBUF, kotoraya
snova zapolnyaet bufer, reinicializiruet soderzhimoe struktury
i vozvrashchaet simvol. Funkciya mozhet predostavlyat' perenosimyj
interfejs i v to zhe vremya soderzhat' neperenosimye konstruk-
cii: GETC maskiruet simvol chislom 0377, kotoroe podavlyaet
znakovoe rasshirenie, osushchestvlyaemoe na PDP-11, i tem samym
garantiruet polozhitel'nost' vseh simvolov.
    Hotya my ne sobiraemsya obsuzhdat' kakie-libo detali, my
vse zhe vklyuchili syuda opredelenie makrosa PUTC, dlya togo chto-
by pokazat', chto ona rabotaet v osnovnom tochno takzhe, kak i
GETC, obrashchayas' pri zapolnenii bufera k funkcii _FLUSHBUF.
    Teper' mozhet byt' napisana funkciya FOPEN. Bol'shaya chast'
programmy funkcii FOPEN svyazana s otkryvaniem fajla i raspo-
lozheniem ego v nuzhnom meste, a takzhe s ustanovleniem bitov
priznakov takim obrazom, chtoby oni ukazyvali nuzhnoe sostoya-
nie. Funkciya FOPEN ne vydelyaet kakoj-libo bufernoj pamyati;
eto delaetsya funkciej _FILEBUF pri pervom chtenii iz fajla.

 #INCLUDE <STDIO.H>
 #DEFINE  PMODE  0644 /*R/W FOR OWNER;R FOR OTHERS*/
 FILE *FOPEN(NAME,MODE) /*OPEN FILE,RETURN FILE PTR*/
 REGISTER CHAR *NAME, *MODE;
 \(
     REGISTER INT FD;
     REGISTER FILE *FP;
 IF(*MODE !='R'&&*MODE !='W'&&*MODE !='A') \(
     FPRINTF(STDERR,"ILLEGAL MODE %S OPENING %S\N",
    MODE,NAME);
     EXIT(1);
 \)
 FOR (FP=_IOB;FP<_IOB+_NFILE;FP++)
     IF((FP->_FLAG & (_READ \! _WRITE))==0)
    BREAK; /*FOUND FREE SLOT*/
 IF(FP>=_IOB+_NFILE) /*NO FREE SLOTS*/
     RETURN(NULL);
 IF(*MODE=='W') /*ACCESS FILE*/
     FD=CREAT(NAME,PMODE);
 ELSE IF(*MODE=='A') \(
     IF((FD=OPEN(NAME,1))==-1)
    FD=CREAT(NAME,PMODE);
     LSEEK(FD,OL,2);
 \) ELSE
     FD=OPEN(NAME,0);
 IF(FD==-1) /*COULDN'T ACCESS NAME*/
     RETURN(NULL);
 FP->_FD=FD;
 FP->_CNT=0;
 FP->_BASE=NULL;
 FP->_FLAG &=(_READ \! _WRITE);
 FP->_FLAG \!=(*MODE=='R') ? _READ : _WRITE;
 RETURN(FP);
 \)


    Funkciya _FILEBUF neskol'ko bolee slozhnaya. Osnovnaya trud-
nost' zaklyuchaetsya v tom, chto _FILEBUF stremitsya razreshit'
dostup k fajlu i v tom sluchae, kogda mozhet ne okazat'sya dos-
tatochno mesta v pamyati dlya buferizacii vvoda ili vyvoda. es-
li prostranstvo dlya novogo bufera mozhet byt' polucheno obra-
shcheniem k funkcii CALLOC, to vse otlichno; esli zhe net, to
_FILEBUF osushchestvlyaet nebuferizovannyj vvod/ vyvod, ispol'-
zuya otdel'nyj simvol, pomeshchennyj v lokal'nom massive.

  #INCLUDE  <STDIO.H>
  _FILLBUF(FP) /*ALLOCATE AND FILL INPUT BUFFER*/
  REGISTER FILE *FP;
   (
  STATIC CHAR SMALLBUF(NFILE);/*FOR UNBUFFERED 1/0*/
   CHAR *CALLOC();
 IF((FR->_FLAG&_READ)==0\!\!(FP->_FLAG&(EOF\!_ERR))\!=0
    RETURN(EOF);
 WHILE(FP->_BASE==NULL) /*FIND BUFFER SPACE*/
    IF(FP->_FLAG & _UNBUF) /*UNBUFFERED*/
   FP->_BASE=&SMALLBUF[FP->_FD];
    ELSE IF((FP->_BASE=CALLOC(_BUFSIZE,1))==NULL)
    FP->_FLAG \!=_UNBUF; /*CAN'T GET BIG BUF*/
    ELSE
    FP->_FLAG \!=_BIGBUF; /*GOT BIG ONE*/
 FP->_PTR=FP->_BASE;
 FP->_CNT=READ(FP->_FD, FP->_PTR,
    FP->_FLAG & _UNBUF ? 1 : _BUFSIZE);
 FF(--FP->_CNT<0) \(
    IF(FP->_CNT== -1)
    FP->_FLAG \! = _EOF;
    ELSE
    FP->_FLAG \! = _ ERR;
    FP->_CNT = 0;
    RETURN(EOF);
     \)
     RETURN(*FP->_PTR++ & 0377); /*MAKE CHAR POSITIVE*/
  )

Pri pervom obrashchenii k GETC dlya konkretnogo fajla schetchik
okazyvaetsya ravnym nulyu, chto privodit k obrashcheniyu k
_FILEBUF. Esli funkciya _FILEBUF najdet, chto etot fajl ne ot-
kryt dlya chteniya, ona nemedlenno vozvrashchaet EOF. V protivnom
sluchae ona pytaetsya vydelit' bol'shoj bufer, a esli ej eto ne
udaetsya, to bufer iz odnogo simvola. Pri etom ona zanosit v
_FLAG sootvetstvuyushchuyu informaciyu o buferizacii.
    Raz bufer uzhe sozdan, funkciya _FILEBUF prosto vyzyvaet
funkciyu READ dlya ego zapolneniya, ustanavlivaet schetchik i
ukazateli i vozvrashchaet simvol iz nachala bufera.
    Edinstvennyj ostavshijsya nevyyasnennym vopros sostoit v
tom, kak vse nachinaetsya. Massiv _IOB dolzhen byt' opredelen i
inicializirovan dlya STDIN, STDOUT i STDERR:



  FILE _IOB[NFILE] = \(
 (NULL,0,_READ,0), /*STDIN*/
 (NULL,0,NULL,1),  /*STDOUT*/
 (NULL,0,NULL,_WRITE \! _UNBUF,2) /*STDERR*/
);

Iz inicializacii chasti _FLAG etogo massiva struktur vidno,
chto fajl STDIN prednaznachen dlya chteniya, fajl STDOUT - dlya
zapisi i fajl STDERR - dlya zapisi bez ispol'zovaniya bufera.

    Uprazhnenie 8-3
    --------------
    Perepishite funkcii FOPEN i _FILEBUF, ispol'zuya polya
vmesto yavnyh pobitovyh operacij.

    Uprazhnenie 8-4
    ---------------
    Razrabotajte i napishite funkcii _FLUSHBUF i FCLOSE.

    Uprazhnenie 8-5
    ---------------
    Standartnaya biblioteka soderzhit funkciyu

  FSEEK(FP, OFFSET, ORIGIN)

kotoraya identichna funkcii LSEEK, isklyuchaya to, chto FP yavlyaet-
sya ukazatelem fajla, a ne deskriptorom fajla. Napishite
FSEEK. Ubedites', chto vasha FSEEK pravil'no soglasuetsya s bu-
ferizaciej, sdelannoj dlya drugih funkcij biblioteki.




    Inogda trebuetsya drugoj vid vzaimodejstviya s sistemoj
fajlov - opredelenie informacii o fajle, a ne togo, chto v
nem soderzhitsya. Primerom mozhet sluzhit' komanda LS ("spisok
spravochnika") sistemy UNIX. Po etoj komande raspechatyvayutsya
imena fajlov iz spravochnika i, neobyazatel'no, drugaya infor-
maciya, takaya kak razmery, razresheniya i t.d.
    Poskol'ku, po krajnej mere, na sisteme UNIX spravochnik
yavlyaetsya prosto fajlom, to v takoj komande, kak LS net niche-
go osobennogo; ona chitaet fajl i vydelyaet nuzhnye chasti iz
nahodyashchejsya tam informacii. Odnako format informacii oprede-
lyaetsya sistemoj, tak chto LS dolzhna znat', v kakom vide vse
predstavlyaetsya v sisteme.
    My eto chastichno proillyustriruem pri napisanii programmy
FSIZE. Programma FSIZE predstavlyaet soboj special'nuyu formu
LS, kotoraya pechataet razmery vseh fajlov, ukazannyh v spiske
ee argumentov. Esli odin iz fajlov yavlyaetsya spravochnikom, to
dlya obrabotki etogo spravochnika programma FSIZE obrashchaetsya
sama k sebe rekursivno. esli zhe argumenty voobshche otsutstvu-
yut, to obrabatyvaetsya tekushchij spravochnik.
    Dlya nachala dadim kratkij obzor struktury sistemy fajlov.
Spravochnik - eto fajl, kotoryj soderzhit spisok imen fajlov i
nekotoroe ukazanie o tom, gde oni razmeshchayutsya. Fakticheski
eto ukazanie yavlyaetsya indeksom dlya drugoj tablicy, kotoruyu
nazyvayut "I - uzlovoj tablicej". Dlya fajla I-uzel - eto to,



gde soderzhitsya vsya informaciya o fajle, za isklyucheniem ego
imeni. Zapis' v spravochnike sostoit tol'ko iz dvuh elemen-
tov: nomera I-uzla i imeni fajla. Tochnaya specifikaciya postu-
paet pri vklyuchenii fajla SYS/DIR.H, kotoryj soderzhit

  #DEFINE DIRSIZ 14 /*MAX LENGTH OF FILE NAME*/
  STRUCT DIRECT /*STRUCTURE OF DIRECTORY ENTRY*/
  \(
 INO_T&_INO; /*INODE NUMBER*/
 CHAR &_NAME[DIRSIZ]; /*FILE NAME*/
  \);

    "Tip" INO_T - eto opredelyaemyj posredstvom TYPEDEF tip,
kotoryj opisyvaet indeks I-uzlovoj tablicy. Na PDP-11 UNIX
etim tipom okazyvaetsya UNSIGNED, no eto ne tot sort informa-
cii, kotoryj pomeshchayut vnutr' programmy: na raznyh sistemah
etot tip mozhet byt' razlichnym. Poetomu i sleduet ispol'zo-
vat' TYPEDEF. Polnyj nabor "sistemnyh" tipov nahoditsya v
fajle SYS/TUPES.H.
    Funkciya STAT beret imya fajla i vozvrashchaet vsyu soderzhashchu-
yusya v I-om uzle informaciyu ob etom fajle (ili -1, esli ime-
etsya oshibka). Takim obrazom, v rezul'tate

  STRUCT STAT STBUF;
  CHAR *NAME;
  STAT(NAME,&STBUF);

struktura STBUF napolnyaetsya informaciej iz I-go uzla o fajle
s imenem NAME. Struktura, opisyvayushchaya vozvrashchaemuyu funkciej
STAT informaciyu, nahoditsya v fajle SYS/STAT.H i vyglyadit
sleduyushchim obrazom:

  STRUCT STAT /*STRUCTURE RETURNED BY STAT*/
  \(
    DEV_T ST_DEV;   /* DEVICE OF INODE */
    INO_T ST_INO;   /* INODE NUMBER */
    SHORT ST_MODE   /* MODE BITS */
    SHORT ST_NLINK; / *NUMBER OF LINKS TO FILE */
    SHORT ST_UID;   /* OWNER'S USER ID */
    SHORT ST_GID;   /* OWNER'S GROUP ID */
    DEV_T ST_RDEV;  /* FOR SPECIAL FILES */
    OFF_T ST_SIZE;  /* FILE SIZE IN CHARACTERS */
    TIME_T ST_ATIME; /* TIME LAST ACCESSED */
    TIME_T ST_MTIME; /* TIME LAST MODIFIED */
    TIME_T ST_CTIME; /* TIME ORIGINALLY CREATED */
  \)

Bol'shaya chast' etoj informacii ob座asnyaetsya v kommentariyah.
|lement ST.MODE soderzhit nabor flagov, opisyvayushchih fajl; dlya
udobstva opredeleniya flagov takzhe nahodyatsya v fajle
SYS/STAT.H.
 #DEFINE S_IFMT    0160000 /* TYPE OF FILE */
 #DEFINE S_IFDIR   0040000 /* DIRECTORY */
 #DEFINE S_IFCHR   0020000 /* CHARACTER SPECIAL */
 #DEFINE S_IFBLK   0060000 /* BLOCK SPECIAL */
 #DEFINE S_IFREG   0100000 /* REGULAR */
 #DEFINE S_ISUID   04000   /* SET USER ID ON EXECUTION */
 #DEFINE S_ISGID   02000   /* SET GROUP ID ON EXECUTION */
 #DEFINE S_ISVTX   01000   /*SAVE SWAPPED TEXT AFTER USE*/
 #DEFINE S_IREAD   0400    /* READ PERMISSION */
 #DEFINE S_IWRITE  0200    /* WRITE PERMISSION */
 #DEFINE S_IEXEC   0100    /* EXECUTE PERMISSION */

    Teper' my v sostoyanii napisat' programmu FSIZE. Esli po-
luchennyj ot funkcii STAT rezhim ukazyvaet, chto fajl ne yavlya-
etsya spravochnikom, to ego razmer uzhe pod rukoj i mozhet byt'
napechatan neposredstvenno. Esli zhe on okazyvaetsya spravochni-
kom, to my dolzhny obrabatyvat' etot spravochnik otdel'no dlya
kazhdogo fajla; tak kak spravochnik mozhet v svoyu ochered' so-
derzhat' podspravochniki, etot process obrabotki yavlyaetsya re-
kursivnym.
    Kak obychno, vedushchaya programma glavnym obrazom imeet delo
s komandnoj strokoj argumentov; ona peredaet kazhdyj argument
funkcii FSIZE v bol'shoj bufer.

#INCLUDE <STDIO.H.>
#INCLUDE <SYS/TYPES.H> /*TYPEDEFS*/
#INCLUDE <SYS/DIR.H>   /*DIRECTORY ENTRY STRUCTURE*/
#INCLUDE <SYS/STAT.H>  /*STRUCTURE RETURNED BY STAT*/
#DEFINE BUFSIZE  256
MAIN(ARGC,ARGV) /*FSIZE:PRINT FILE SIZES*/
CHAR *ARGV[];
\(
  CHAR BUF[BUFSIZE];
  IF(ARGC==1) \( /*DEFAULT:CURRENT DIRECTORY*/
 ATRCPY(BUF,".");
 FSIZE(BUF);
  \) ELSE
 WHILE(--ARGC>0) \(
    STRCPY(BUF,*++ARGV);
    FSIZE(BUF);
 \)
\)

    Funkciya FSIZE pechataet razmer fajla. Esli odnako fajl
okazyvaetsya spravochnikom, to FSIZE snachala vyzyvaet funkciyu
DIRECTORY dlya obrabotki vseh ukazannyh v nem fajlov. Obrati-
te vnimanie na ispol'zovanie imen flagov S_IFMT i _IFDIR iz
fajla STAT.H.



 FSIZE(NAME) /*PRINT SIZE FOR NAME*/
 CHAR *NAME;
 \(
     STRUCT STAT STBUF;
     IF(STAT(NAME,&STBUF)== -1) \(
 FPRINTF(STDERR,"FSIZE:CAN'T FIND %S\N",NAME);
 RETURN;
 \)
 IF((STBUF.ST_MODE & S_IFMT)==S_IFDIR)
       DIRECTORY(NAME);
 PRINTF("%8LD %S\N",STBUF.ST_SIZE,NAME);
\)
    Funkciya DIRECTORY yavlyaetsya samoj slozhnoj. Odnako znachi-
tel'naya ee chast' svyazana s sozdaniem dlya obrabatyvaemogo v
dannyj moment fajla ego polnogo imeni, po kotoromu mozhno
vosstanovit' put' v dereve.

 DIRECTORY(NAME)    /*FSIZE FOR ALL FILES IN NAME*/
 CHAR *NAME;
  (
    STRUCT DIRECT DIRBUF;
    CHAR *NBP, *NEP;
    INT I, FD;
    NBP=NAME+STRLEN(NAME);
    *NBP++='/'; /*ADD SLASH TO DIRECTORY NAME*/
    IF(NBP+DIRSIZ+2>=NAME+BUFSIZE) /*NAME TOO LONG*/
   RETURN;
    IF((FD=OPEN(NAME,0))== -1)
   RETURN;
    WHILE(READ(FD,(CHAR *)&DIRBUF,SIZEOF(DIRBUF))>0) \(
   IF(DIRBUF.D_INO==0) /*SLOT NOT IN USE*/
   CONTINUE;
   IF(STRCMP (DIRBUF.D_NAME,".")==0
   \!\! STRCMP(DIRBUF.D_NAME,"..")==0
   CONTINUE; /*SKIP SELF AND PARENT*/
   FOR (I=0,NEP=NBP;I<DIRSIZ;I++)
   *NEP++=DIRBUF.D_NAME[I];
   *NEP++='\0';
   FSIZE(NAME);
    \)
    CLOSE(FD);
    *--NBP='\0'; /*RESTORE NAME*/
  )

    Esli nekotoraya dyra v spravochnike v nastoyashchee vremya ne
ispol'zuetsya (potomu chto fajl byl udalen), to v sootvetstvu-
yushchee I-uzlovoe chislo ravno nulyu, i eta poziciya propuskaetsya.
Kazhdyj spravochnik takzhe soderzhit zapis' v samom sebe, nazy-
vaemuyu ".", i o svoem roditele, ".."; oni, ochevidno, takzhe
dolzhny byt' propushcheny, a to programma budet rabotat' ves'ma
i ves'ma dolgo.



    Hotya programma FSIZE dovol'no specializirovanna, ona vse
zhe demonstriruet paru vazhnyh idej. vo-pervyh, mnogie prog-
rammy ne yavlyayutsya "sistemnymi programmami"; oni tol'ko is-
pol'zuyut informaciyu, forma ili soderzhanie kotoroj opredelya-
etsya operacionnoj sistemoj. Vo-vtoryh, dlya takih programm
sushchestvenno, chto predstavlenie etoj informacii vhodit tol'ko
v standartnye "zagolovochnye fajly", takie kak STAT.H i
DIR.H, i chto programmy vklyuchayut eti fajly, a ne pomeshchayut
fakticheskie opisaniya vnutr' samih programm.




    V glave 5 my napisali beshitrostnyj variant funkcii
ALLOC. Variant, kotoryj my napishem teper', ne soderzhit ogra-
nichenij: obrashcheniya k funkciyam ALLOC i FREE mogut peremezhat'-
sya v lyubom poryadke; kogda eto neobhodimo, funkciya ALLOC ob-
rashchaetsya k operacionnoj sisteme za dopolnitel'noj pamyat'yu.
Krome togo, chto eti procedury polezny sami po sebe, oni tak-
zhe illyustriruyut nekotorye soobrazheniya, svyazannye s napisani-
em mashinno-zavisimyh programm otnositel'no mashinno-nezavisi-
mym obrazom, i pokazyvayut prakticheskoe primenenie struktur,
ob容dinenij i konstrukcij TYPEDEF.
    Vmesto togo, chtoby vydelyat' pamyat' iz skompilirovannogo
vnutri massiva fiksirovannogo razmera, funkciya ALLOC budet
po mere neobhodimosti obrashchat'sya za pamyat'yu k operacionnoj
sisteme. Poskol'ku razlichnye sobytiya v programme mogut tre-
bovat' asinhronnogo vydeleniya pamyati, to pamyat', upravlyaemaya
ALLOC, ne mozhet byt' nepreryvnoj. V silu etogo svobodnaya pa-
myat' hranitsya v vide cepochki svobodnyh blokov. Kazhdyj blok
vklyuchaet razmer, ukazatel' sleduyushchego bloka i samu svobodnuyu
pamyat'. Bloki uporyadochivayutsya v poryadke vozrastaniya adresov
pamyati, prichem poslednij blok (s naibol'shim adresom) ukazy-
vaet na pervyj, tak chto cepochka fakticheski okazyvaetsya kol'-
com.
    Pri postuplenii zaprosa spisok svobodnyh blokov prosmat-
rivaetsya do teh por, poka ne budet najden dostatochno bol'shoj
blok. Esli etot blok imeet v tochnosti trebuemyj razmer, to
on otceplyaetsya ot spiska i peredaetsya pol'zovatelyu. Esli zhe
etot blok slishkom velik, to on razdelyaetsya, nuzhnoe kolichest-
vo peredaetsya pol'zovatelyu, a ostatok vozvrashchaetsya v svobod-
nyj spisok. Esli dostatochno bol'shogo bloka najti ne udaetsya,
to operacionnoj sistemoj vydelyaetsya novyj blok, kotoryj
vklyuchaetsya v spisok svobodnyh blokov; zatem poisk vozobnov-
lyaetsya.
    Osvobozhdenie pamyati takzhe vlechet za soboj prosmotr svo-
bodnogo spiska v poiske podhodyashchego mesta dlya vvedeniya osvo-
bozhdennogo bloka. Esli etot osvobodivshijsya blok s kakoj-libo
storony primykaet k bloku iz spiska svobodnyh blokov, to oni
ob容dinyayutsya v odin blok bol'shego razmera, tak chto pamyat' ne
stanovitsya slishkom razdroblennoj. Obnaruzhit' smezhnye bloki
prosto, potomu chto svobodnyj spisok soderzhitsya v poryadke
vozrastaniya adresov.



    Odna iz problem, o kotoroj my upominali v glave 5, zak-
lyuchaetsya v obespechenii togo, chtoby vozvrashchaemaya funkciej
ALLOC pamyat' byla vyrovnena podhodyashchim obrazom dlya teh
ob容ktov, kotorye budut v nej hranit'sya. Hotya mashiny i raz-
lichayutsya, dlya kazhdoj mashiny sushchestvuet tip, trebuyushchij nai-
bol'shih ogranichenij po razmeshcheniyu pamyati, esli dannye samogo
ogranichitel'nogo tipa mozhno pomestit' v nekotoryj opredelen-
nyj adres, to eto zhe vozmozhno i dlya vseh ostal'nyh tipov.
Naprimer, na IBM 360/370,HONEYWELL 6000 i mnogih drugih ma-
shinah lyuboj ob容kt mozhet hranit'sya v granicah, sootvetstvuyu-
shchim peremennym tipa DOUBLE; na PDP-11 budut dostatochny pere-
mennye tipa INT.
    Svobodnyj blok soderzhit ukazatel' sleduyushchego bloka v ce-
pochke, zapis' o razmere bloka i samo svobodnoe prostranstvo;
upravlyayushchaya informaciya v nachale nazyvaetsya zagolovkom. Dlya
uproshcheniya vyravnivaniya vse bloki kratny razmeru zagolovka, a
sam zagolovok vyrovnen nadlezhashchim obrazom. |to dostigaetsya s
pomoshch'yu ob容dineniya, kotoroe soderzhit zhelaemuyu strukturu za-
golovka i obrazec naibolee ogranichitel'nogo po vyravnivaniyu
tipa:

TYPEDEF INT ALIGN; /*FORCES ALIGNMENT ON PDP-11*/
UNION HEADER \( /*FREE BLOCK HEADER*/
   STRUCT \(
   UNION HEADER *PTR; /*NEXT FREE BLOCK*/
   UNSIGNED SIZE; /*SIZE OF THIS FREE BLOCK*/
   \) S;
   ALIGN  X; /*FORCE ALIGNMENT OF BLOCKS*/
\);
TYPEDEF UNION HEADER HEADER;

    Funkciya ALLOC okruglyaet trebuemyj razmer v simvolah do
nuzhnogo chisla edinic razmera zagolovka; fakticheskij blok,
kotoryj budet vydelen, soderzhit na odnu edinicu bol'she,
prednaznachaemuyu dlya samogo zagolovka, i eto i est' znachenie,
kotoroe zapisyvaetsya v pole SIZE zagolovka. Ukazatel', vozv-
rashchaemyj funkciej ALLOC, ukazyvaet na svobodnoe prostranst-
vo, a ne na sam zagolovok.

STATIC HEADER BASE; /*EMPTY LIST TO GET STARTED*/
STATIC HEADER *ALLOCP=NULL; /*LAST ALLOCATED BLOCK*/
CHAR *ALLOC(NBYTES)/*GENERAL-PURPOSE STORAGE ALLOCATOR*/
UNSIGNED NBYTES;
\(
   HEADER *MORECORE();
   REGISTER HEADER *P, *G;
   REGISTER INT NUNITS;
   NUNITS=1+(NBYTES+SIZEOF(HEADER)-1)/SIZEOF(HEADER);
   IF ((G=ALLOCP)==NULL) \( /*NO FREE LIST YET*/
BASE.S PTR=ALLOCP=G=&BASE;
BASE.S.SIZE=0;
   \)



   FOR (P=G>S.PTR; ; G=P, P=P->S.PTR) \(
IF (P->S.SIZE>=NUNITS) \( /*BIG ENOUGH*/
    IF (P->S.SIZE==NUNITS) /*EXACTLY*/
   G->S.PTR=P->S.PTR;
    ELSE \( /*ALLOCATE TAIL END*/
   P->S.SIZE-=NUNITS;
   P+=P->S.SIZE;
   P->S.SIZE=NUNITS;
    \)
    ALLOCP=G;
    RETURN((CHAR *)(P+1));
 \)
 IF(P==ALLOCP) /*WRAPPED AROUND FREE LIST*/
     IF((P=MORECORE(NUNITS))==NULL)
    RETURN(NULL); /*NONE LEFT*/
 \)
    \)

    Peremennaya BASE ispol'zuetsya dlya nachala raboty. Esli
ALLOCP imeet znachenie NULL, kak v sluchae pervogo obrashcheniya k
ALLOC, to sozdaetsya vyrozhdennyj svobodnyj spisok: on sostoit
iz svobodnogo bloka razmera nul' i ukazatelya na samogo sebya.
V lyubom sluchae zatem issleduetsya svobodnyj spisok. Poisk
svobodnogo bloka podhodyashchego razmera nachinaetsya s togo mesta
(ALLOCP), gde byl najden poslednij blok; takaya strategiya po-
mogaet sohranit' odnorodnost' diska. Esli najden slishkom
bol'shoj blok, to pol'zovatelyu predlagaetsya ego hvostovaya
chast'; eto privodit k tomu, chto v zagolovke ishodnogo bloka
nuzhno izmenit' tol'ko ego razmer. Vo vseh sluchayah vozvrashchae-
myj pol'zovatelyu ukazatel' ukazyvaet na dejstvitel'no svo-
bodnuyu oblast', lezhashchuyu na edinicu dal'she zagolovka. Obrati-
te vnimanie na to, chto funkciya ALLOC pered vozvrashcheniem "P"
preobrazuet ego v ukazatel' na simvoly.
    Funkciya MORECORE poluchaet pamyat' ot operacionnoj siste-
my. Detali togo, kak eto osushchestvlyaetsya, menyayutsya, konechno,
ot sistemy k sisteme. Na sisteme UNIX tochka vhoda SBRK(N)
vozvrashchaet ukazatel' na "N" dopolnitel'nyh bajtov pamya-
ti.(ukazatel' udvoletvoryaet vsem ogranicheniyam na vyravniva-
nie). Tak kak zapros k sisteme na vydelenie pamyati yavlyaetsya
sravnitel'no dorogoj operaciej, my ne hotim delat' eto pri
kazhdom obrashchenii k funkcii ALLOC. Poetomu funkciya MORECORE
okruglyaet zatrebovannoe chislo edinic do bol'shego znacheniya;
etot bol'shij blok budet zatem razdelen tak, kak neobhodimo.
Masshtabiruyushchaya velichina yavlyaetsya parametrom, kotoryj mozhet
byt' podobran v sootvetstvii s neobhodimost'yu.



 #DEFINE NALLOC 128 /*#UNITS TO ALLOCATE AT ONCE*/
 STATIC HEADER *MORECORE(NU) /*ASK SYSTEM FOR MEMORY*/
 UNSIGNED NU;
 \(
    CHAR *SBRK();
    REGISTER CHAR *CP;
    REGISTER HEADER *UP;
    REGISTER INT RNU;
    RNU=NALLOC*((NU+NALLOC-1)/NALLOC);
    CP=SBRK(RNU*SIZEOF(HEADER));
    IF ((INT)CP==-1) /*NO SPACE AT ALL*/
 RETURN(NULL);
    UP=(HEADER *)CP;
    UP->S.SIZE=RNU;
    FREE((CHAR *)(UP+1));
    RETURN(ALLOCP);
 \)

    Esli bol'she ne ostalos' svobodnogo prostranstva, to fun-
kciya SBRK vozvrashchaet "-1", hotya NULL byl by luchshim vyborom.
Dlya nadezhnosti sravneniya "-1" dolzhna byt' preobrazovana k
tipu INT. Snova prihoditsya mnogokratno ispol'zovat' yavnye
preobrazovaniya (perevod) tipov, chtoby obespechit' opredelen-
nuyu nezavisimost' funkcij ot detalej predstavleniya ukazate-
lej na razlichnyh mashinah.
    I poslednee - sama funkciya FREE. Nachinaya s ALLOCP, ona
prosto prosmatrivaet svobodnyj spisok v poiske mesta dlya
vvedeniya svobodnogo bloka. |to mesto nahoditsya libo mezhdu
dvumya sushchestvuyushchimi blokami, libo v odnom iz koncov spiska.
V lyubom sluchae, esli osvobodivshijsya blok primykaet k odnomu
iz sosednih, smezhnye bloki ob容dinyayutsya. Sledit' nuzhno tol'-
ko zatem, chtoby ukazateli ukazyvali na to, chto nuzhno, i chto-
by razmery byli ustanovleny pravil'no.

FREE(AP) /*PUT BLOCKE AP IN FREE LIST*/
CHAR *AP;
\(
   REGISTER HEADER *P, *G;
   P=(HEADER*)AP-1; /*POINT TO HEADER*/
   FOR (G=ALLOCP; !(P>G && P>G->S.PTR);G=G->S.PTR)
IF (G>=G->S.PTR && (P>G \!\! P<G->S.PTR))
    BREAK; /*AT ONE END OR OTHER*/
IF (P+P->S.SIZE==G->S.PTR)\(/*JOIN TO UPPER NBR*/
    P->S.SIZE += G->S.PTR->S.SIZE;
    P->S.PTR = G->S.PTR->S.PTR;
\) ELSE
    P->S.PTR = G->S.PTR;
IF (G+G->S.SIZE==P) \( /*JOIN TO LOWER NBR*/
    G->S.SIZE+=P->S.SIZE;
    G->S.PTR=P->S.PTR;
\) ELSE
    G->S.PTR=P;
ALLOCP = G;
   \)


    Hotya raspredelenie pamyati po svoej suti zavisit ot is-
pol'zuemoj mashiny, privedennaya vyshe programma pokazyvaet,
kak etu zavisimost' mozhno regulirovat' i ogranichit' ves'ma
nebol'shoj chast'yu programmy. Ispol'zovanie TYPEDEF i UNION
pozvolyaet spravit'sya s vyravnivaniem (pri uslovii, chto funk-
ciya SBRK obespechivaet podhodyashchij ukazatel'). Perevody tipov
organizuyut vypolnenie yavnogo preobrazovaniya tipov i dazhe
spravlyayutsya s neudachno razrabotannym sistemnym interfejsom.
I hotya rassmotrennye zdes' podrobnosti svyazany s raspredele-
niem pamyati, obshchij podhod ravnym obrazom primenim i k drugim
situaciyam.

    Uprazhnenie 8-6
    --------------
    Funkciya iz standartnoj biblioteki CALLOC(N,SIZE) vozvra-
shchaet ukazatel' na "N" ob容ktov razmera SIZE, prichem sootvet-
stvuyushchaya pamyat' inicializiruetsya na nul'. napishite programmu
dlya CALLOC, ispol'zuya funkciyu ALLOC libo v kachestve obrazca,
libo kak funkciyu, k kotoroj proishodit obrashchenie.

    Uprazhnenie 8-7
    ---------------
    Funkciya ALLOC prinimaet zatrebovannyj razmer, ne prove-
ryaya ego pravdopodobnosti; funkciya FREE polagaet, chto tot
blok, kotoryj ona dolzhna osvobodit', soderzhit pravil'noe
znachenie v pole razmera. Usovershenstvujte eti procedury,
zatrativ bol'she usilij na proverku oshibok.

    Uprazhnenie 8-8
    ---------------
    Napishite funkciyu BFREE(P,N), kotoraya vklyuchaet proizvol'-
nyj blok "P" iz "N" simvolov v spisok svobodnyh blokov, up-
ravlyaemyj funkciyami ALLOC i FREE. S pomoshch'yu funkcii BFREE
pol'zovatel' mozhet v lyuboe vremya dobavlyat' v svobodnyj spi-
sok staticheskij ili vneshnij massiv.











    |to rukovodstvo opisyvaet yazyk 's' dlya komp'yuterov DEC
PDP-11, HONEYWELL 6000, IBM sistema/370 i INTERDATA 8/32.
tam, gde est' rashozhdeniya, my sosredotachivaemsya na versii
dlya PDP-11, stremyas' v to zhe vremya ukazat' detali, kotorye
zavisyat ot realizacii. Za malym isklyucheniem, eti rashozhdeniya
neposredstvenno obuslovleny osnovnymi svojstvami ispol'zue-
mogo apparatnogo oborudovaniya; razlichnye kompilyatory obychno
vpolne sovmestimy.





    Imeetsya shest' klassov leksem: identifikatory, klyuchevye
slova, konstanty, stroki, operacii i drugie razdeliteli.
Probely, tabulyacii , novye stroki i kommentarii (sovmestno,
"pustye promezhutki"), kak opisano nizhe, ignoriruyutsya, za is-
klyucheniem teh sluchaev, kogda oni sluzhat razdelitelyami lek-
sem. Neobhodim kakoj-to pustoj promezhutok dlya razdeleniya
identifikatorov, klyuchevyh slov i konstant, kotorye v protiv-
nom sluchae sol'yutsya.
    Esli sdelan razbor vhodnogo potoka na leksemy vplot' do
dannogo simvola, to v kachestve sleduyushchej leksemy beretsya sa-
maya dlinnaya stroka simvolov, kotoraya eshche mozhet predstavlyat'
soboj leksemu.




    Kommentarij otkryvaetsya simvolami /* i zakanchivaetsya
simvolami /*. Kommentarii ne vkladyvayutsya drug v druga.




    Identifikator - eto posledovatel'nost' bukv i cifr; per-
vyj simvol dolzhen byt' bukvoj. Podcherkivanie _ schitaetsya
bukvoj. Bukvy nizhnego i verhnego registrov razlichayutsya. zna-
chashchimi yavlyayutsya ne bolee, chem pervye vosem' simvolov, hotya
mozhno ispol'zovat' i bol'she. Na vneshnie identifikatory, ko-
torye ispol'zuyutsya razlichnymi assemblerami i zagruzchikami,
nakladyvatsya bolee zhestkie ogranicheniya:

    DEC PDP-11             7 simvolov, 2 registra
    HONEYWELL 6000         6 simvolov, 1 registr
    IBM 360/370            7 simvolov, 1 registr
    INTERDATA 8/32         8 simvolov, 2 registra







    Sleduyushchie identifikatory zarezervirovany dlya ispol'zova-
niya v kachestve klyuchevyh slov i ne mogut ispol'zovat'sya inym
obrazom:

     INT      EXTERN      ELSE
     CHAR      REGISTER      FOR
     FLOAT      TYPEDEF      DO
     DOUBLE      STATIC      WHILE
     STRUCT      GOTO      SWITCH
     UNION      RETURN      CASE
     LONG      SIZEOF      DEFAULT
     SHORT      BREAK      ENTRY
     UNSIGNED    CONTINUE
    *AUTO      IF

Klyuchevoe slovo ENTRY v nastoyashchee vremya ne ispol'zuetsya ka-
kim-libo kompilyatorom; ono zarezervirovano dlya ispol'zovaniya
v budushchem. V nekotoryh realizaciyah rezerviruetsya takzhe slova
FORTRAN i ASM



Imeetsya neskol'ko vidov konstant, kotorye perechisleny nizhe.
V punkte 10.6 rezyumiruyutsya harakteristiki apparatnyh sred-
stv, kotorye vliyayut na razmery.






    Celaya konstanta, sostoyashchaya iz posledovatel'nosti cifr,
schitaetsya vos'merichnoj, esli ona nachinaetsya s 0 (cifra
nul'), i desyatichnoj v protivnom sluchae. Cifry 8 i 9 imeyut
vos'merichnye znacheniya 10 i 11 sootvetstvenno. Posledovatel'-
nost' cifr, kotoroj predshestvuyut simvoly 0h (nul', h-malen'-
koe) ili 0h (nul' h-bol'shoe), rassmatrivaetsya kak shestnadca-
tirichnoe celoe. SHestnadcatirichnye cifry vklyuchayut bukvy ot a
(malen'koe) ili a (bol'shoe) do F (malen'koe) ili F (bol'shoe)
so znacheniyami ot 10 do 15. Desyatichnaya konstanta, velichina
kotoroj prevyshaet naibol'shee mashinnoe celoe so znakom, schi-
taetsya dlinnoj; vosmerichnaya ili shestnadcatirichnaya konstanta,
kotoroe prevyshaet naibol'shee mashinnoe celoe bez znaka, takzhe
schitaetsya dlinnoj.



    Desyatichnaya, vosmerichnaya ili shestnadcatirichnaya konstanta,
za kotoroj neposredstvenno sleduet L (el'-malen'koe) ili L
(el'-bol'shoe), yavlyaetsya dlinnoj konstantoj. Kak obsuzhdaetsya
nizhe, na nekotoryh mashinah celye i dlinnye znacheniya mogut
rassmatrivat'sya kak identichnye.



    Simvol'naya konstanta - eto simvol, zaklyuchennyj v odinoch-
nye kavychki, kak, naprimer, 'X'. Znacheniem simvol'noj kons-
tanty yavlyaetsya chislennoe znachenie etogo simvola v mashinnom
predstavlenii nabora simvolov.



    Nekotorye negraficheskie simvoly, odinochnaya kavychka ' i
obratnaya kosaya cherta \ mogut byt' predstavleny v sootvetst-
vii so sleduyushchej tablicej uslovnyh posledovatel'nostej:

  novaya stroka                      NL/LF/    \N
  gorizontal'naya tabulyaciya          HT        \T
  simvol vozvrata na odnu poziciyu   BS        \B
  vozvrat karetki                   CR        \R
  perehod na novuyu stranicu         FF        \F
  obratnaya kosaya cherta              \         \\
  odinochnaya kavychka                 '         \'
  kombinaciya bitov                  DDD       \DDD

    Uslovnaya posledovatel'nost' \DDD sostoit iz obratnoj ko-
soj cherty, za kotoroj sleduyut 1,2 ili 3 vosmerichnyh cifry,
kotorye rassmativayutsya kak zadayushchie znachenie zhelaemogo sim-
vola. Special'nym sluchaem etoj konstrukcii yavlyaetsya posledo-
vatel'nost' \0 (za nulem ne sleduet cifra), kotoraya oprede-
lyaet simvol NUL. esli sleduyushchij za obratnoj kosoj chertoj
simvol ne sovpadaet s odnim iz ukazannyh, to obratnaya kosaya
cherta ignoriruetsya.



    Plavayushchaya konstanta sostoit iz celoj chasti, desyatichnoj
tochki, drobnoj chasti, bukvy E (malen'kaya) ili E (bol'shaya) i
celoj eksponenty s neobyazatel'nym znakom. Kak celaya, tak i
drobnaya chast' yavlyayutsya posledovatel'nost'yu cifr. Libo celaya,
libo drobnaya chast' (no ne obe) mozhet otsutstvovat'; libo de-
syatichnaya tochka, libo e (malen'kaya) i eksponenta (no ne to i
drugoe odnovremenno) mozhet otsutstvovat'. Kazhdaya plavayushchaya
konstanta schitaetsya imeyushchej dvojnuyu tochnost'.



    Stroka - eto posledovatel'nost' simvolov, zaklyuchennaya v
dvojnye kavychki, kak, napriimer,"...". Stroka imeet tip
"massiv massivov" i klass pamyati STATIC (sm. Punkt 4 nizhe).
Stroka inicializirovana ukazannymi v nej simvolami. Vse
stroki, dazhe identichno zapisannye, schitayutsya razlichnymi.
Kompilyator pomeshchaet v konec kazhdoj stroki nulevoj bajt \0, s
tem chtoby prosmatrivayushchaya stroku programma mogla opredelit'
ee konec. Pered stoyashchim vnutri stroki simvolom dvojnoj ka-
vychki " dolzhen byt' postavlen simvol obratnoj kosoj cherty \;
krome togo, mogut ispol'zovat'sya te zhe usloviya posledova-
tel'nosti, chto i v simvol'nyh konstantah. I poslednee, ob-
ratnaya kosaya cherta \, za kotoroj neposredstvenno sleduet
simvol novoj stroki, ignoriruetsya.






    Sleduyushchaya nizhe tablica summiruet nekotorye svojstva ap-
paratnogo oborudovaniya, kotorye menyayutsya ot mashiny k mashine.
Hotya oni i vliyayut na perenosimost' programm, na praktike oni
predstavlyayut malen'kuyu problemu, chem eto mozhet kazat'sya za-
ranee.
          Tablica 1
 -------------------------------------------------------
   DEC PDP-11   HONEYWELL      IBM 370    INTERDATA 8/32
   ASCII        ASCII          EBCDIC     ASCII
 CHAR   8 BITS  9 BITS         8 BITS     8 BITS
 INT    16      36             32         32
 SHORT  16      36             16         16
 LONG   32      36             32         32
 FLOAT  32      36             32         32
 DOUBLE 64      72             64         64
 RANGE -38/+38 -38/+38         -76/+76    -76/+76
--------------------------------------------------------




    V ispol'zuemoj v etom rukovodstve sintaksicheskoj notacii
sintaksicheskie kategorii vydelyayutsya kursivom (prim. perev.:
v nastoyashchee vremya sinteksicheskie kategorii vmesto kursivom
vydelyayutsya podcherkivaniem), a liternye slova i simvoly -
zhirnym shriftom. Al'ternativnye kategorii perechislyayutsya na
otdel'nyh strochkah. Neobyazatel'nyj simvol, terminal'nyj ili
neterminal'nyj, ukazyvaetsya indeksom "neob", tak chto

   \( vyrazhenie
    --------- neob \)

ukazyvaet na neobyazatel'noe vyrazhenie, zaklyuchennoe v figur-
nyh skobkah. Sintaksis summiruetsya v punkte 18.




    YAzyk "C" osnovyvaet interpretaciyu identifikatora na dvuh
priznakah identifikatora: ego klasse pamyati i ego tipe.
Klass pamyati opredelyaet mesto i vremya hraneniya pamyati, svya-
zannoj s identifikatorom; tip opredelyaet smysl velichin, na-
hodyashchihsya v pamyati, opredelennoj pod identifikatorom.
    Imeyutsya chetyre klassa pamyati: avtomaticheskaya, statiches-
kaya, vneshnyaya i registrovaya. Avtomaticheskie peremennye yavlya-
yutsya lokal'nymi dlya kazhdogo vyzova bloka i ischezayut pri vy-
hode iz etogo bloka. Staticheskie peremennye yavlyayutsya lokal'-
nymi, no sohranyayut svoi znacheniya dlya sleduyushchego vhoda v blok
dazhe posle togo, kak upravlenie peredaetsya za predely bloka.
Vneshnie peremennye sushchestvuyut i sohranyayut svoi znacheniya v
techenie vypolneniya vsej programmy i mogut ispol'zovat'sya dlya
svyazi mezhdu funkciyami, v tom chisle i mezhdu nezavisimo skom-
pilirovannymi funkciyami. Registrovye peremennye hranyatsya
(eli eto vozmozhno) v bystryh registrah mashiny; podobno avto-
maticheskim peremennym oni yavlyayutsya lokal'nymi dlya kazhdogo
bloka i ischezayut pri vyhode iz etogo bloka.



    V yazyke "C" predusmotreno neskol'ko osnovnyh tipov
ob容ktov:
    ob容kty, napisannye kak simvoly (CHAR), dostatochno veli-
ki, chtoby hranit' lyuboj chlen iz sootvetstvuyushchego dannoj rea-
lizacii vnutrennego nabora simvolov, i esli dejstvitel'nyj
simvol iz etogo nabora simvolov hranitsya v simvol'noj pere-
mennoj, to ee znachenie ekvivalentno celomu kodu etogo simvo-
la. V simvol'nyh peremennyh mozhno hranit' i drugie velichiny,
no realizaciya budet mashinno-zavisimoj.
    Mozhno ispol'zovat' do treh razmerov celyh, opisyvaemyh
kak SHORT INT, INT i LONG INT. Dlinnye celye zanimayut ne
men'she pamyati, chem korotkie, no v konkretnoj realizacii mo-
zhet okazat'sya, chto libo korotkie celye, libo dlinnye celye,
libo te i drugie budut ekvivalentny prostym celym. "Prostye"
celye imeyut estestvennyj razmer, predusmatrivaemyj arhiitek-
turoj ispol'zuemoj mashiny; drugie razmery vvodyatsya dlya udvo-
letvoreniya special'nyh potrebnostej.
    Celye bez znaka, opisyvaemye kak UNSIGNED, podchinyayutsya
zakonam arifmetiki po modulyu 2**N, gde N - chislo bitov v ih
predstavlenii. (Na PDP-11 dlinnye velichiny bez znaka ne pre-
dusmotreny).
    Plavayushchie odinarnoj tochnosti (FLOAT) i plavayushchie dvojnoj
tochnosti (DOUBLE) v nekotoryh realizaciyah mogut byt' sinoni-
mami.
    Poskol'ku ob容kty upomyanutyh vyshe tipov mogut byt' ra-
zumno interpretirovany kak chisla, eti tipy budut nazyvat'sya
arifmeticheskimi. tipy CHAR i INT vseh razmerov sovmestno bu-
dut nazyvat'sya celochislennymi. Tipy FLOAT i DOUBLE sovmestno
budut nazyvat'sya plavayushchimi tipami.
    Krome osnovnyh arifmeticheskih tipov sushchestvuet konceptu-
al'no beskonechnyj klass proizvodnyh tipov, kotorye obrazuyut-
sya iz osnovnyh tipov sleduyushchim obrazom:
     massivy ob容ktov bol'shinstva tipov;
     funkcii, kotorye vozvrashchayut ob容kty zadannogo tipa;
     ukazateli na ob容kty dannogo tipa;
     struktury, soderzhashchie posledovatel'nost' ob容ktov
     razlichnyh tipov;
     ob容dineniya, sposobnye soderzhat' odin iz neskol'kih
       ob容ktov razlichnyh tipov.
    Voobshche govorya, eti metody postroeniya ob容ktov mogut pri-
menyat'sya rekursivno.




    Ob容kt yavlyaetsya dostupnym obrabotke uchastkom pamyati;
L-znachenie - eto vyrazhenie, ssylayushcheesya na ob容kt. Ochevidnym
primerom vyrazheniya L-znacheniya yavlyaetsya identifikator. Sushches-
tvuyut operacii, rezul'tatom kotoryh yavlyayutsya L-znacheniya; es-
li, naprimer, E - vyrazhenie ukazannogo tipa, to *E yavlyaetsya
vyrazheniem L-znacheniya, ssylayushchimsya na ob容kt E. Nazvanie
"L-znachenie" proishodit ot vyrazheniya prisvaivaniya E1=E2, v
kotorom levaya chast' dolzhna byt' vyrazheniem L-znacheniya. Pri
posleduyushchem obsuzhdenii kazhdoj operacii budet ukazyvat'sya,
ozhidaet li ona operandov L-znacheniya i vydaet li ona L-znache-
nie.






    Ryad operacij mozhet v zavisimosti ot svoih operandov vy-
zyvat' preobrazovanie znachenie operanda iz odnogo tipa v
drugoj. V etom razdele ob座asnyayutsya rezul'taty, kotorye sle-
duet ozhidat' ot takih preobrazovanij. V p. 14.6 Podvodyatsya
itogi preobrazovanij, trebuemye bol'shinstvom obychnyh opera-
cij; eti svedeniya dopolnyayutsya neobhodimym obrazom pri obsuzh-
denii kazhdoj operacii.




    Simvol ili korotkoe celoe mozhno ispol'zovat' vsyudu, gde
mozhno ispol'zovat' celoe. Vo vseh sluchayah znachenie preobra-
zuetsya k celomu. Preobrazovanie bolee korotkogo celogo k bo-
lee dlinnomu vsegda soprovozhdaetsya znakovym rasshireniem; ce-
lye yavlyayutsya velichinami so znakom. Osushchestvlyaetsya ili net
znakovoe rasshirenie dlya simvolov, zavisit ot ispol'zuemoj
mashiny, no garantiruetsya, chto chlen standartnogo nabora sim-
volov neotricatelen. iz vseh mashin, rassmatrivaemyh v etom
rukovodstve, tol'ko PDP-11 osushchestvlyaet znakovoe rasshirenie.
oblast' znachenij simvol'nyh peremennyh na PDP-11 menyaetsya ot
-128 do 127; simvoly iz nabora ASC11 imeyut polozhitel'nye
znacheniya. Simvol'naya konstanta, zadannaya s pomoshch'yu vos'me-
richnoj uslovnoj posledovatel'nosti, podvergaetsya znakovomu
rasshireniyu i mozhet okazat'sya otricatel'noj; naprimer, '\377'
imeet znachenie -1.
    Kogda bolee dlinnoe celoe preobrazuetsya v bolee korotkoe
ili v CHAR, ono obrezaetsya sleva; lishnie bity prosto otbra-
syvayutsya.




    Vsya plavayushchaya arifmetika v "C" vypolnyaetsya s dvojnoj
tochnost'yu kazhdyj raz, kogda ob容kt tipa FLOAT poyavlyaetsya v
vyrazhenii, on udlinyaetsya do DOUBLE posredstvom dobavleniya
nulej v ego drobnuyu chast'. kogda ob容kt tipa DOUBLE dolzhen
byt' preobrazovan k tipu FLOAT, naprimer, pri prisvaivanii,
pered usecheniem DOUBLE okruglyaetsya do dliny FLOAT.



    Preobrazovanie plavayushchih znachenij k celochislennomu tipu
imeet tendenciyu byt' do nekotoroj stepeni mashinno-zavisimym;
v chastnosti napravlenie usecheniya otricatel'nyh chisel menyaet-
sya ot mashine k mashine. Rezul'tat ne opredelen, esli znachenie
ne pomeshchaetsya v predostavlyaemoe prostranstvo.
    Preobrazovanie celochislennyh znachenij v plavayushchie vypol-
nyaetsya bez oslozhnenij. Mozhet proizojti nekotoraya poterya toch-
nosti, esli dlya rezul'tata ne soderzhitsya dostatochnogo koli-
chestva bitov.



    Celoe ili dlinnoe celoe mozhet byt' pribavleno k ukazate-
lyu ili vychteno iz nego; v etom sluchae pervaya velichina preob-
razuetsya tak, kak ukazyvaetsya v razdele opisaniya operacii
slozheniya.



    Dva ukazatelya na ob容kty odinakovogo tipa mogut byt'
vychteny; v etom sluchae rezul'tat preobrazuetsya k celomu, kak
ukazyvaetsya v razdele opisaniya operacii vychitaniya.



    Vsyakij raz, kogda celoe bez znaka ob容dinyaetsya s prostym
celym, prostoe celoe preobrazuetsya v celoe bez znaka i re-
zul'tat okazyvaetsya celym bez znaka. Znacheniem yavlyaetsya nai-
men'shee celoe bez znaka, sootvetstvuyushchee celomu so znakom
(po modulyu 2**razmer slova). V dvoichnom dopolnitel'nom pred-
stavlenii eto preobrazovanie yavlyaetsya chisto umozritel'nym i
ne izmenyaet fakticheskuyu kombinaciyu bitov.
    Kogda celoe bez znaka preobrazuetsya k tipu LONG, znache-
nie rezul'tata sovpadaet so znacheniem celogo bez znaka. Ta-
kim obrazom, eto preobrazovanie svoditsya k dobavleniyu nulej
sleva.



    Podavlyayushchee bol'shinstvo operacij vyzyvaet preobrazovanie
i opredelyaet tipy rezul'tata analogichnym obrazom. Privodimaya
nizhe shema v dal'nejshem budet nazyvat'sya "obychnymi arifmeti-
cheskimi preobrazovaniyami".
Snachala lyubye operandy tipa CHAR ili SHORT preobrazuyutsya v
  INT, a lyubye operandy tipa FLOAT preobrazuyutsya v DOUBLE.
Zatem, esli kakoj-libo operand imeet tip DOUBLE, to drugoj
  preobrazuetsya k tipu DOUBLE, i eto budet tipom rezul'tata.
V protivnom sluchae, esli kakoj-libo operand imeet tip LONG,
  to drugoj operand preobrazuetsya k tipu LONG, i eto i budet
  tipom rezul'tata.
V protivnom sluchae, esli kakoj-libo operand imeet tip
  UNSIGNED, to drugoj operand preobrazuetsya k tipu UNSIGNED,
  i eto budet tipom rezul'tata.
V protivnom sluchae oba operanda budut imet' tip INT, i eto
  budet tipom rezul'tata.




    Starshinstvo operacij v vyrazheniyah sovpadaet s poryadkom
sledovaniya osnovnyh podrazdelov nastoyashchego razdela, nachinaya
s samogo vysokogo urovnya starshinstva. Tak, naprimer, vyrazhe-
niyami, ukazyvaemymi v kachestve operandov operacii +
(p.15.4), YAvlyayutsya vyrazheniya, opredelennye v p.p.15.1-15.3.
Vnutri kazhdogo podrazdela operacii imeet odinakovoe starshin-
stvo. V kazhdom podrazdele dlya opisyvaemyh tam operacij uka-
zyvaetsya ih associativnost' sleva ili sprava. Starshinstvo i
associativnost' vseh operacij v vyrazheniyah rezyumiruyutsya v
grammaticheskoj svodke v p.18.
    V protivnom sluchae poryadok vychislenij vyrazhenij ne opre-
delen. V chastnosti, kompilyator schitaet sebya v prave vychis-
lyat' podvyrazheniya v tom poryadke, kotoryj on nahodit naibolee
effektivnym, dazhe esli eti podvyrazheniya privodyat k pobochnym
effektam. Poryadok, v kotorom proishodyat pobochnye effekty, ne
specificiruetsya. Vyrazheniya, vklyuchayushchie kommutativnye i asso-
ciativnye operacii ( *,+,&,!,^ ), mogut byt' pereuporyadocheny
proizvol'nym obrazom dazhe pri nalichii kruglyh skobok; chtoby
vynudit' opredelennyj poryadok vychislenij, v etom sluchae ne-
obhodimo ispol'zovat' yavnye promezhutochnye peremennye.



    Pri vychislenii vyrazhenij obrabotka perepolneniya i pro-
verka pri delenii yavlyayutsya mashinno-zavisimymi. Vse sushchestvu-
yushchie realizacii yazyka "C" ignoriruyut perepolnenie celyh; ob-
rabotka situacij pri delenii na 0 i pri vseh osobyh sluchayah
s plavayushchimi chislami menyaetsya ot mashiny k mashine i obychno
vypolnyaetsya s pomoshch'yu bibliotechnoj funkcii.



    Pervichnye vyrazheniya, vklyuchayushchie ., ->, indeksaciyu i ob-
rashcheniya k funkciyam, gruppiruyutsya sleva napravo.

  Pervichnoe vyrazhenie:
  identifikator
  konstanta
  stroka
  (vyrazhenie)
  pervichnoe-vyrazhenie  [vyrazhenie]
  pervichnoe-vyrazhenie  (spisok-vyrazhenij   neo
  pervichnoe-L-znachenie . Identifikator
  pervichnoe-vyrazhenie -> identifikator
  spisok-vyrazhenij:
  vyrazhenie
  spisok-vyrazhenij, vyrazhenie

Identifikator yavlyaetsya pervichnym vyrazheniem pri uslovii, chto
on opisan podhodyashchim obrazom, kak eto obsuzhdaetsya nizhe. tip
identifikatora opredelyaetsya ego opisaniem. Esli, odnako, ti-
pom identifikatora yavlyaetsya "massiv ...", to znacheniem vyra-
zheniya, sostoyashchego iz etogo identifikatora , yavlyaetsya ukaza-
tel' na pervyj ob容kt v etom massive, a tipom vyrazheniya bu-
det "ukazatel' na ...". Bolee togo, identifikator massiva ne
yavlyaetsya vyrazheniem L-znacheniya. podobnym obrazom identifika-
tor, kotoryj opisan kak "funkciya, vozvrashchayushchaya ...", za isk-
lyucheniem togo sluchaya, kogda on ispol'zuetsya v pozicii imeni
funkcii pri obrashchenii, preobrazuetsya v "ukazatel' na funk-
ciyu, kotoraya vozvrashchaet ...".
    Konstanta yavlyaetsya pervichnym vyrazheniem. V zavisimosti
ot ee formy tipom konstanty mozhet byt' INT, LONG ili DOUBLE.
    Stroka yavlyaetsya pervichnym vyrazheniem. Ishodnym ee tipom
yavlyaetsya "massiv simvolov"; no sleduya tem zhe samym pravilam,
kotorye privedeny vyshe dlya identifikatorov, on modificiruet-
sya v "ukazatel' na simvoly", i rezul'tatom yavlyaetsya ukaza-
tel' na pervyj simvol stroki. (imeetsya isklyuchenie v nekoto-
ryh inicializatorah; sm. P. 16.6.)
    Vyrazhenie v kruglyh skobkah yavlyaetsya pervichnym vyrazheni-
em, tip i znachenie kotorogo identichny tipu i znacheniyu etogo
vyrazheniya bez skobok. Nalichie kruglyh skobok ne vliyaet na
to, yavlyaetsya li vyrazhenie L-znacheniem ili net.



    Pervichnoe vyrazhenie, za kotorym sleduet vyrazhenie v
kvadratnyh skobkah, yavlyaetsya pervichnym vyrazheniem. Intuitiv-
no yasno, chto eto vyrazhenie s indeksom. Obychno pervichnoe vy-
razhenie imeet tip "ukazatel' na ...", indeksnoe vyrazhenie
imeet tip INT, a tipom rezul'tata yavlyaetsya "...". Vyrazhenie
E1[E2] po opredeleniyu identichno vyrazheniyu * ((E1) + (E2)).
Vse, chto neobhodimo dlya ponimaniya etoj zapisi, soderzhitsya v
etom razdele; voprosy, svyazannye s ponyatiem identifikatorov
i operacij * i + rassmatrivayutsya v p.p. 15.1, 15.2 I 15.4
sootvetstvenno; vyvody summiruyutsya nizhe v p. 22.3.
    Obrashchenie k funkcii yavlyaetsya pervichnym vyrazheniem, za
kotorym sleduet zaklyuchennyj v kruglye skobki vozmozhno pustoj
spisok vyrazhenij, razdelennyh zapyatymi, kotorye i predstav-
lyayut soboj fakticheskie argumenty funkcii. Pervichnoe vyrazhe-
nie dolzhno byt' tipa "funkciya, vozvrashchayushchaya ...", a rezul'-
tat obrashcheniya k funkcii imeet tip "...". Kak ukazyvaetsya ni-
zhe, ranee ne vstrechavshchijsya identifikator, za kotorym nepos-
redstvenno sleduet levaya kruglaya skobka, schitaetsya opisannym
po kontekstu, kak predstavlyayushchij funkciyu, vozvrashchayushchuyu ce-
loe; sledovatel'no chashche vsego vstrechayushchijsya sluchaj funkcii,
vozvrashchayushchej celoe znachenie, ne nuzhdaetsya v opisanii.
    Pered obrashcheniem lyubye fakticheskie argumenty tipa FLOAT
preobrazuyutsya k tipu DOUBLE, lyubye argumenty tipa CHAR ili
SHORT preobrazuyutsya k tipu INT, i, kak obychno, imena massi-
vov preobrazuyutsya v ukazateli. Nikakie drugie preobrazovaniya
ne vypolnyayutsya avtomaticheski; v chastnosti, ne sravnivaet ti-
py fakticheskih argumentov s tipami formal'nyh argumentov.
Esli preobrazovanie neobhodimo, ispol'zujte yavnyj perevod
tipa (CAST); sm. P.p. 15.2, 16.7.
    Pri podgotovke k vyzovu funkcii delaetsya kopiya kazhdogo
fakticheskogo parametra; takim obrazom, vse peredachi argumen-
tov v yazyke "C" osushchestvlyayutsya strogo po znacheniyu. funkciya
mozhet izmenyat' znacheniya svoih formal'nyh parametrov, no eti
izmeneniya ne vliyayut na znacheniya fakticheskih parametrov. S
drugoj strony imeetsya vozmozhnost' peredavat' ukazatel' pri
takom uslovii, chto funkciya mozhet izmenyat' znachenie ob容kta,
na kotoryj etot ukazatel' ukazyvaet. Poryadok vychisleniya ar-
gumentov v yazyke ne opredelen; obratite vnimanie na to, chto
razlichnye kompilyatory vychislyayut po raznomu.
    Dopuskayutsya rekursivnye obrashcheniya k lyuboj funkcii.
    Pervichnoe vyrazhenie, za kotorym sleduet tochka i identi-
fikator, yavlyaetsya vyrazheniem. Pervoe vyrazhenie dolzhno byt'
L-znacheniem, imenuyushchim strukturu ili ob容dinenie, a identi-
fikator dolzhen byt' imenem chlena struktury ili ob容dineniya.
Rezul'tatom yavlyaetsya L-znachenie, ssylayushcheesya na poimenovan-
nyj chlen struktury ili ob容dineniya.
    Pervichnoe vyrazhenie, za kotorym sleduet strelka (sostav-
lennaya iz znakov - i >) i identifikator, yavlyaetsya vyrazheni-
em. pervoe vyrazhenie dolzhno byt' ukazatelem na strukturu ili
ob容dinenie, a identifikator dolzhen imenovat' chlen etoj
struktury ili ob容dineniya. Rezul'tatom yavlyaetsya L-znachenie,
ssylayushcheesya na poimenovannyj chlen struktury ili ob容dineniya,
na kotoryj ukazyvaet ukazatel'noe vyrazhenie.
    Sledovatel'no, vyrazhenie E1->MOS yavlyaetsya tem zhe samym,
chto i vyrazhenie (*E1).MOS. Struktury i ob容dineniya rassmat-
rivayutsya v p. 16.5. Privedennye zdes' pravila ispol'zovaniya
struktur i ob容dinenij ne navyazyvayutsya strogo, dlya togo chto-
by imet' vozmozhnost' obojti mehanizm tipov. Sm. P. 22.1.






    Vyrazhenie s unarnymi operaciyami gruppiruetsya spravo na-
levo.
 Unarnoe-vyrazhenie:
      *  vyrazhenie
      &  L-znachenie
      -  vyrazhenie
      !  Vyrazhenie
      \^ vyrazhenie
      ++ L-znachenie
      -- L-znachenie
      L-znachenie ++
      L-znachenie --
      (imya-tipa) vyrazhenie
      SIZEOF vyrazhenie
      SIZEOF imya-tipa

    Unarnaya operaciya * oznachaet kosvennuyu adresaciyu: vyrazhe-
nie dolzhno byt' ukazatelem, a rezul'tatom yavlyaetsya L-znache-
nie, ssylayushcheesya na tot ob容kt, na kotoryj ukazyvaet vyrazhe-
nie. Esli tipom vyrazheniya yavlyaetsya "ukazatel' na...", to ti-
pom rezul'tata budet "...".
    Rezul'tatom unarnoj operacii & yavlyaetsya ukazatel' na
ob容kt, k kotoromu ssylaetsya L-znachenie. Esli L-znachenie
imeet tip "...", to tipom rezul'tata budet "ukazatel' na
...".
    Rezul'tatom unarnoj operacii - (minus) yavlyaetsya ee ope-
rand, vzyatyj s protivopolozhnym znakom. Dlya velichiny tipa
UNSIGNED rezul'tat poluchaetsya vychitaniem ee znacheniya iz 2**N
(dva v stepeni N), gde N-chislo bitov v INT. Unarnoj operacii
+ (plyus) ne sushchestvuet.
    Rezul'tatom operacii logicheskogo otricaniya ! YAvlyaetsya 1,
esli znachenie ee operanda ravno 0, i 0, esli znachenie ee
operanda otlichno ot nulya. Rezul'tat imeet tip INT. |ta ope-
raciya primenima k lyubomu arifmeticheskomu tipu ili ukazate-
lyam.
    Operaciya \^ daet obratnyj kod, ili dopolnenie do edini-
cy, svoego operanda. Vypolnyayutsya obychnye arifmeticheskie pre-
obrazovaniya. Operand dolzhen byt' celochislennogo tipa.
    Ob容kt, na kotoryj ssylaetsya operand L-znacheniya prefiks-
noj operacii ++, uvelichivaetsya. znacheniem yavlyaetsya novoe
znachenie operanda, no eto ne L-znachenie. Vyrazhenie ++h ekvi-
valentno h+=1. Informaciyu o preobrazovaniyah smotri v razbore
operacii slozheniya (p. 15.4) i operacii prisvaivaniya (p.
15.14).
    Prefiksnaya operaciya -- analogichna prefiksnoj operacii
++, no privodit k umen'sheniyu svoego operanda L-znacheniya.
    Pri primenenii postfiksnoj operacii ++ k L-znacheniyu re-
zul'tatom yavlyaetsya znachenie ob容kta, na kotoryj ssylaetsya
L-znachenie. Posle togo, kak rezul'tat prinyat k svedeniyu,
ob容kt uvelichivaetsya tochno takim zhe obrazom, kak i v sluchae
prefiksnoj operacii ++. Rezul'tat imeet tot zhe tip, chto i
vyrazhenie L-znacheniya.



    Pri primenenii postfiksnoj operacii -- k L-znacheniyu re-
zul'tatom yavlyaetsya znachenie ob容kta, na kotoryj ssylaetsya
L-znachenie. Posle togo, kak rezul'tat prinyat k svedeniyu,
ob容kt umen'shaetsya tochno takim zhe obrazom, kak i v sluchae
prefiksnoj operacii --. Rezul'tat imeet tot zhe tip, chto i
vyrazhenie L-znacheniya.
    Zaklyuchennoe v kruglye skobki imya tipa dannyh,stoyashchee pe-
red vyrazheniem , vyzyvaet preobrazovanie znacheniya etogo vy-
razheniya k ukazannomu tipu. |ta konstrukciya nazyvaetsya pere-
vod (CAST). Imena tipov opisyvayutsya v p. 16.7.
    Operaciya SIZEOF vydaet razmer svoego operanda v bajtah.
(Ponyatie bajt v yazyke ne opredeleno, razve tol'ko kak znache-
nie operacii SIZEOF. Odnako vo vseh sushchestvuyushchih realizaciyah
bajtom yavlyaetsya prostranstvo, neobhodimoe dlya hraneniya
ob容kta tipa CHAR). Pri primenenii k massivu rezul'tatom yav-
lyaetsya polnoe chislo bajtov v massive. Razmer opredelyaetsya iz
opisanij ob容ktov v vyrazhenii. |to vyrazhenie semanticheski
yavlyaetsya celoj konstantoj i mozhet byt' ispol'zovano v lyubom
meste, gde trebuetsya konstanta. Osnovnoe primenenie eta ope-
raciya nahodit pri svyazyah s procedurami, podobnym raspredeli-
telyam pamyati, i v sistemah vvoda- vyvoda.
    Operaciya SIZEOF mozhet byt' takzhe primenena i k zaklyuchen-
nomu v kruglye skobki imeni tipa. V etom sluchae ona vydaet
razmer v bajtah ob容kta ukazannogo tipa.
    Konstrukciya SIZEOF (tip) rassmatrivaetsya kak celoe, tak
chto vyrazhenie SIZEOF (tip) - 2 ekvivalentno vyrazheniyu
(SIZEOF (tip)9 - 2.



    Mul'tiplikativnye operacii *, /, i % gruppiruyutsya sleva
napravo. Vypolnyayutsya obychnye arifmeticheskie preobrazovaniya.
  Mul'tiplikativnoe-vyrazhenie:
  vyrazhenie * vyrazhenie
  vyrazhenie / vyrazhenie
  vyrazhenie % vyrazhenie
    Binarnaya operaciya * oznachaet umnozhenie. Operaciya * asso-
ciativna, i vyrazheniya s neskol'kimi umnozheniyami na odnom i
tom zhe urovne mogut byt' peregruppirovany kompilyatorom.
    Binarnaya operaciya / oznachaet delenie. Pri delenii polo-
zhitel'nyh celyh osushchestvlyaetsya usechenie po napravleniyu k nu-
lyu, no esli odin iz operandov otricatelen, to forma usecheniya
zavisit ot ispol'zuemoj mashiny. Na vseh mashinah, ohvatyvae-
myh nastoyashchim rukovodstvom, ostatok imeet tot zhe znak , chto
i delimoe. Vsegda spravedlivo, chto (A/B)*B+A%B ravno A (esli
B ne ravno 0).
    Binarnaya operaciya % vydaet ostatok ot deleniya pervogo
vyrazheniya na vtoroe. Vypolnyayutsya obychnye arifmeticheskie pre-
obrazovaniya. Operandy ne dolzhny byt' tipa FLOAT.



    Additivnye operacii + i - gruppiruyutsya sleva napravo.
vypolnyayutsya obychnye arifmeticheskie preobrazovaniya. Dlya kazh-
doj operacii imeyutsya nekotorye dopolnitel'nye vozmozhnosti,
svyazannye s tipami operandov.



 Additivnoe-vyrazhenie:
 vyrazhenie + vyrazhenie
 vyrazhenie - vyrazhenie

Rezul'tatom operacii + yavlyaetsya summa operandov. Mozhno skla-
dyvat' ukazatel' na ob容kt v massive i znachenie lyubogo celo-
chislennogo tipa. vo vseh sluchayah poslednee preobrazuetsya v
adresnoe smeshchenie posredstvom umnozheniya ego na dlinu ob容k-
ta, na kotoryj ukazyvaet etot ukazatel'. Rezul'tatom yavlyaet-
sya ukazatel' togo zhe samogo tipa, chto i ishodnyj ukazatel',
kotoryj ukazyvaet na drugoj ob容kt v tom zhe massive, smeshchen-
nyj sootvetstvuyushchim obrazom otnositel'no pervonachal'nogo
ob容kta. Takim obrazom, esli P yavlyaetsya ukazatelem ob容kta v
massive, to vyrazhenie P+1 yavlyaetsya ukazatelem na sleduyushchij
ob容kt v etom massive.
    Nikakie drugie kombinacii tipov dlya ukazatelej ne razre-
shayutsya.
    Operaciya + associativna, i vyrazhenie s neskol'kimi slo-
zheniyami na tom zhe samom urovne mogut byt' pereuporyadocheny
kompilyatorom.
    Rezul'tatom operacii - yavlyaetsya raznost' operandov. Vy-
polnyayutsya obychnye arifmeticheskie preobrazovaniya. Krome togo,
iz ukazatelya mozhet byt' vychteno znachenie lyubogo celochislen-
nogo tipa, prichem, provodyatsya te zhe samye preobrazovaniya,
chto i pri operacii slozheniya.
    Esli vychitayutsya dva ukazatelya na ob容kty odinakovogo ti-
pa, to rezul'tat preobrazuetsya (deleniem na dlinu ob容kta) k
tipu INT, predstavlyaya soboj chislo ob容ktov, razdelyayushchih uka-
zyvaemye ob容kty. Esli eti ukazateli ne na ob容kty iz odnogo
i togo zhe massiva, to takoe preobrazovanie, voobshche govorya,
dast neozhidannye rezul'taty, potomu chto dazhe ukazateli na
ob容kty odinakovogo tipa ne obyazany otlichat'sya na velichinu,
kratnuyu dline ob容kta.



    Operacii sdviga << i >> gruppiruyutsya sleva napravo. Dlya
obeih operacij provodyatsya obychnye arifmeticheskie preobrazo-
vaniya ih operandov, kazhdyj iz kotoryh dolzhen byt' celochis-
lennogo tipa. Zatem pravyj operand preobrazuetsya k tipu INT;
rezul'tat imeet tip levogo operanda. Rezul'tat ne opredelen,
esli pravyj operand otricatelen ili bol'she ili raven, chem
dlina ob容kta v bitah.
  Vyrazhenie-sdviga:
  vyrazhenie << vyrazhenie
  vyrazhenie >> vyrazhenie

Znacheniem vyrazheniya E1<<E2 yavlyaetsya E1 (interpretiruemoe kak
kombinaciya bitov), sdvinutoe vlevo na E2 bitov; osvobozhdayu-
shchiesya bity zapolnyayutsya nulem. znacheniem vyrazheniya E1>>E2 yav-
lyaetsya E1, sdvinutoe vpravo na E2 bitovyh pozicij. Esli E1
imeet tip UNSIGNE, to sdvig vpravo garantirovanno budet lo-
gicheskim (zapolnenie nulem); v protivnom sluchae sdvig mozhet
byt' (i tak i est' na PDP-11) arifmeticheskim (osvobozhdayushchie-
sya bity zapolnyayutsya kopiej znakovogo bita).






    Operacii otnosheniya gruppiruyutsya sleva napravo, no etot
fakt ne ochen' polezen; vyrazhenie A<B<C ne oznachaet togo, chto
ono kazalos' by dolzhno oznachat'.
  Vyrazhenie-otnosheniya:
  vyrazhenie < vyrazhenie
  vyrazhenie > vyrazhenie
  vyrazhenie <= vyrazhenie
  vyrazhenie >= vyrazhenie

Operacii < (men'she), > (bol'she), <= (men'she ili ravno) i >=
(bol'she ili ravno) vse dayut 0, esli ukazannoe otnoshenie lozh-
no, i 1, esli ono istinno. Rezul'tat imeet tip ITN. Vypolnya-
yutsya obychnye arifmeticheskie preobrazovaniya. Mogut sravni-
vat'sya dva ukazatelya; rezul'tat zavisit ot otnositel'nogo
raspolozheniya ukazyvaemyh ob容ktov v adresnom prostranstve.
Sravnenie ukazatelej perenosimo tol'ko v tom sluchae, esli
ukazateli ukazyvayut na ob容kty iz odnogo i togo zhe massiva.



    Vyrazhenie-ravenstva:
 vyrazhenie == vyrazhenie
 vyrazhenie != vyrazhenie

Operacii == (ravno) i != (ne ravno) v tochnosti analogichny
operaciyam otnosheniya, za isklyucheniem togo, chto oni imeyut bo-
lee nizkij uroven' starshinstva. (Poetomu znachenie vyrazheniya
A<B==C<D ravno 1 vsyakij raz, kogda vyrazhenie A<B i C<D imeyut
odinakovoe znachenie istinnosti).
    Ukazatel' mozhno sravnivat' s celym, no rezul'tat budet
mashinno- nezavisimym tol'ko v tom sluchae, esli celym yavlyaet-
sya konstanta 0. Garantiruetsya, chto ukazatel', kotoromu pris-
voeno znachenie 0, ne ukazyvaet ni na kakoj ob容kt i na samom
dele okazyvaetsya ravnym 0; obshcheprinyato schitat' takoj ukaza-
tel' nulem.



    Vyrazhenie-i:
 vyrazhenie & vyrazhenie

Operaciya & yavlyaetsya associativnoj, i vklyuchayushchie & vyrazheniya
mogut byt' pereuporyadocheny. Vypolnyayutsya obychnye arifmetiches-
kie preobrazovaniya; rezul'tatom yavlyaetsya pobitovaya funkciya
'i' operandov. |ta operaciya primenima tol'ko k operandam ce-
lochislennogo tipa.



    Vyrazhenie-isklyuchayushchego-ili:
  vyrazhenie ^ vyrazhenie

Operaciya ^ yavlyaetsya associativnoj, i vklyuchayushchie ^ vyrazheniya
mogut byt' pereuporyadocheny. vypolnyayutsya obychnye arifmetiches-
kie preobrazovaniya; rezul'tatom yavlyaetsya pobitovaya funkciya
isklyuchayushchego 'ili' operandov. Operaciya primenima tol'ko k
operandam celochislennogo tipa.






    Vyrazhenie-vklyuchayushchego-ili:
  vyrazhenie \! Vyrazhenie

Operaciya \! YAvlyaetsya associativnoj, i soderzhashchie \! Vyrazhe-
niya mogut byt' pereuporyadocheny. vypolnyayutsya obychnye arifme-
ticheskie preobrazovaniya; rezul'tatom yavlyaetsya pobitovaya fun-
kciya vklyuchayushchego 'ili' operandov. Operaciya primenima tol'ko
k operandam celochislennogo tipa.



    Vyrazhenie-logicheskogo-i:
  vyrazhenie && vyrazhenie

Operaciya && gruppiruetsya sleva napravo. Ona vozvrashchaet 1,
esli oba ee operanda otlichny ot nulya, i 0 v protivnom slu-
chae. V otlichie ot & operaciya && garantiruet vychislenie sleva
napravo; bolee togo, esli pervyj operand raven 0, to znache-
nie vtorogo operanda voobshche ne vychislyaetsya.
    Operandy ne obyazany byt' odinakovogo tipa, no kazhdyj iz
nih dolzhen byt' libo odnogo iz osnovnyh tipov, libo ukazate-
lem. rezul'tat vsegda imeet tip ITN.



    Vyrazhenie-logicheskogo-ili:
 vyrazhenie \!\! vyrazhenie

Operaciya \!\! Gruppiruetsya sleva napravo. Ona vozvrashchaet 1,
esli odin iz operandov otlichen ot nulya, i 0 v protivnom slu-
chae. V otlichie ot operacii \! Operaciya \!\! Garantiruet vy-
chislenie sleva napravo; bolee togo, esli pervyj operand ot-
lichen ot nulya, to znachenie vtorogo operanda voobshche ne vychis-
lyaetsya.
    Operandy ne obyazany byt' odinakovogo tipa, no kazhdyj iz
nih dolzhen byt' libo odnogo iz osnovnyh tipov, libo ukazate-
lem. Rezul'tat vsegda imeet tip INT.



    Uslovnoe-vyrazhenie:
 vyrazhenie ? vyrazhenie : vyrazhenie

Uslovnye vyrazheniya gruppiruyutsya slevo napravo. Vychislyaetsya
znachenie pervogo vyrazheniya, i esli ono otlichno ot nulya, to
rezul'tatom budet znachenie vtorogo vyrazheniya; v protivnom
sluchae rezul'tatom budet znachenie tret'ego vyrazheniya. Esli
eto vozmozhno, provodyatsya obychnye arifmeticheskie preobrazova-
niya, s tem, chtoby privesti vtoroe i tret'e vyrazheniya k obshche-
mu tipu; v protivnom sluchae, esli oba vyrazheniya yavlyayutsya
ukazatelyami odinakovogo tipa, to rezul'tat imeet tot zhe tip;
v protivnom sluchae odno vyrazhenie dolzhno byt' ukazatelem, a
drugoe - konstantoj 0, i rezul'tat budet imet' tip ukazate-
lya. Vychislyaetsya tol'ko odno iz vtorogo i tret'ego vyrazhenij.






    Imeetsya ryad operacij prisvaivaniya, kazhdaya iz kotoryh
gruppiruetsya sleva napravo. Vse operacii trebuyut v kachestve
svoego levogo operanda L-znachenie, a tipom vyrazheniya prisva-
ivaniya yavlyaetsya tip ego levogo operanda. Znacheniem vyrazheniya
prisvaivaniya yavlyaetsya znachenie, hranimoe v levom operande
posle togo, kak prisvaivanie uzhe budet proizvedeno. Dve chas-
ti sostavnoj operacii prisvaivaniya yavlyayutsya otdel'nymi lek-
semami.
    Vyrazhenie-prisvaivaniya:
  L-znachenie = vyrazhenie
  L-znachenie += vyrazhenie
  L-znachenie -= vyrazhenie
  L-znachenie *= vyrazhenie
  L-znachenie /= vyrazhenie
  L-znachenie %= vyrazhenie
  L-znachenie >>= vyrazhenie
  L-znachenie <<= vyrazhenie
  L-znachenie &= vyrazhenie
  L-znachenie ^= vyrazhenie
  L-znachenie \!= vyrazhenie

    Kogda proizvoditsya prostoe prisvaivanie C'=', znachenie
vyrazheniya zamenyaet znachenie ob容kta, na kotoroe ssylaetsya
L-znachenie. Esli oba operanda imeyut arifmeticheskij tip, to
pered prisvaivaniem pravyj operand preobrazuetsya k tipu le-
vogo operanda.
    O svojstvah vyrazheniya vida E1 op = E2, gde Op - odna iz
perechislennyh vyshe operacij, mozhno sdelat' vyvod, esli
uchest', chto ono ekvivalentno vyrazheniyu E1 = E1 op (E2); od-
nako vyrazhenie E1 vychislyaetsya tol'ko odin raz. V sluchae ope-
racij += i -= levyj operand mozhet byt' ukazatelem, prichem
pri etom (celochislennyj) pravyj operand preobrazuetsya takim
obrazom, kak ob座asneno v p. 15.4; vse pravye operandy i vse
otlichnye ot ukazatelej levye operandy dolzhny imet' arifmeti-
cheskij tip.
    Ispol'zuemye v nastoyashchee vremya kompilyatory dopuskayut
prisvaivanie ukazatelya celomu, celogo ukazatelyu i ukazatelya
ukazatelyu drugogo tipa. takoe prisvaivanie yavlyaetsya chistym
kopirovaniem bez kakih-libo preobrazovanij. Takoe upotreble-
nie operacij prisvaivaniya yavlyaetsya neperenosimym i mozhet
privodit' k ukazatelyam, kotorye pri ispol'zovanii vyzyvayut
oshibki adresacii. Tem ne menee garantiruetsya, chto prisvaiva-
nie ukazatelyu konstanty 0 daet nulevoj ukazatel', kotoryj
mozhno otlichat' ot ukazatelya na lyuboj ob容kt.



    Vyrazhenie-s-zapyatoj:
  vyrazhenie , vyrazhenie



Para vyrazhenij, razdelennyh zapyatoj, vychislyaetsya sleva nap-
ravo i znachenie levogo vyrazheniya otbrasyvaetsya. Tipom i zna-
cheniem rezul'tata yavlyaetsya tip i znachenie pravogo operanda.
|ta operaciya gruppiruetsya sleva napravo. V kontekste, gde
zapyataya imeet special'noe znachenie, kak, naprimer, v spiske
fakticheskih argumentov funkcij (p. 15.1) Ili v spiskah ini-
cializatorov (p. 16.6), Operaciya zapyataya, opisyvaemaya v etom
razdele, mozhet poyavlyat'sya tol'ko v kruglyh skobkah; napri-
mer, funkciya

  F(A,(T=3,T+2),C)

imeet tri argumenta, vtoroj iz kotoryh imeet znachenie 5.



    Opisaniya ispol'zuyutsya dlya ukazaniya interpretacii, koto-
ruyu yazyk "C" budet davat' kazhdomu identifikatoru; oni ne
obyazatel'no rezerviruyut pamyat', sootvetstvuyushchuyu identifika-
toru. Opisaniya imeyut formu
 Opisanie:
 specifikatory-opisaniya spisok-opisatelej
            neob;

Opisateli v spiske opisatelej soderzhat opisyvaemye identifi-
katory. Specifikatory opisaniya predstavlyayut soboj posledova-
tel'nost' specifikatorov tipa i specifikatorov klassa pamya-
ti.

Specifikatory-opisaniya:
  specifikator-tipa specifikatory-opisaniya
       neob
  specifikator-klassa-pamyati specifikator-opisaniya
      neob

spisok dolzhen byt' samosoglasovannym v smysle, opisyvaemom
nizhe.



    Nizhe perechislyayutsya specifikatory klassa pamyati:
 Specifikator-klassa-pamyati:
      AUTO
      STATIC
      EXTERN
      REGISTER
      TYPEDEF

    Specifikator TYPEDEF ne realizuet pamyati i nazyvaetsya
"specifikatorom klassa pamyati" tol'ko po sintaksicheskim so-
obrazheniyam; eto obsuzhdaetsya v p. 16.8. Smysl razlichnyh klas-
sov pamyati byl obsuzhden v p. 12.
    Opisaniya AUTO, STATIC i REGISTER sluzhat takzhe v kachestve
opredelenij v tom smysle, chto oni vyzyvayut rezervirovanie
nuzhnogo kolichestva pamyati. V sluchae EXTERN dolzhno prisutst-
vovat' vneshnee opredelenie (p. 18) Ukazyvaemyh identifikato-
rov gde-to vne funkcii, v kotoroj oni opisany.



    Opisanie REGISTER luchshe vsego predstavlyat' sebe kak opi-
sanie AUTO vmeste s namekom kompilyatoru, chto opisannye takim
obrazom peremennye budut chasto ispol'zovat'sya. |ffektivny
tol'ko neskol'ko pervyh takih opisanij. Krome togo, v regis-
trah mogut hranit'sya tol'ko peremennye opredelennyh tipov;
na PDP-11 eto INT, CHAR ili ukazatel'. Sushchestvuet i drugoe
ogranichenie na ispol'zovanie registrovyh peremennyh: k nim
nel'zya primenyat' operaciyu vzyatiya adresa &. Pri razumnom is-
pol'zovanii registrovyh opisanij mozhno ozhidat' polucheniya
men'shih po razmeru i bolee bystryh programm, no uluchshenie v
budushchem generirovaniya kodov mozhet sdelat' ih nenuzhnymi.
    Opisanie mozhet soderzhat' ne bolee odnogo specifikatora
klassa pamyati. Esli opisanie ne soderzhit specifikatora klas-
sa pamyati, to schitaetsya, chto on imeet znachenie AUTO, esli
opisanie nahoditsya vnutri nekotoroj funkcii, i EXTERN v pro-
tivnom sluchae. isklyuchenie: funkcii nikogda ne byvaet avtoma-
ticheskimi.



    Nizhe perechislyayutsya specifikatory tipa.

  Specifikator-tipa:
  CHAR
  SHORT
  INT
  LONG
  UNSIGNED
  FLOAT
  DOUBLE
  specifikator-struktury-ili-ob容dineniya
  opredelyayushchee-tip-imya

    Slova LONG, SHORT i USIGNED mozhno rassmatrivat' kak pri-
lagatel'nye; dopustimy sleduyushchie kombinacii:

  SHORT INT
  LONG INT
  USIGNED INT
  LONG FLOAT

Poslednyaya kombinaciya oznachaet to zhe, chto i DOUBLE. V ostal'-
nom opisanie mozhet soderzhat' ne bolee odnogo specifikatora
tipa. Esli opisanie ne soderzhit specifikatora tipa, to schi-
taetsya, chto on imeet znachenie INT.
    Specifikatory struktur i ob容dinenij obsuzhdayutsya v p.
16.5; Opisaniya s opredelyayushchimi tip imenami TYPEDEF obsuzhda-
yutsya v p. 16.8.






    Vhodyashchij v opisanie spisok opisatelej predstavlyaet soboj
posledovatel'nost' razdelennyh zapyatymi opisatelej, kazhdyj
iz kotoryh mozhet imet' inicializator.
  Spisok-opisatelej:
 inicializiruemyj-opisatel'
 inicializiruemyj-opisatel', spisok-opisatelej
  inicializiruemyj-opisatel':
 opisatel'-inicializator
    neob

Inicializatory opisyvayutsya v p. 16.6. Specifikatory i opisa-
niya ukazyvayut tip i klass pamyati ob容ktov, na kotorye ssyla-
yutsya opisateli. Opisateli imeyut sleduyushchij sintaksis:

 opisatel':
 identifikator
 ( opisatel' )
 * opisatel'
 opisatel' ()
 opisatel' [konstantnoe-vyrazhenie
         neob]

Gruppirovanie takoe zhe kak i v vyrazheniyah.



    Kazhdyj opisatel' rassmatrivaetsya kak utverzhdenie togo,
chto kogda konstrukciya toj zhe samoj formy, chto i opisatel',
poyavlyaetsya v vyrazhenii, to ona vydaet ob容kt ukazannogo tipa
i ukazannogo klassa pamyati. Kazhdyj opisatel' soderzhit rovno
odin identifikator; eto imenno tot identifikator, kotoryj i
opisyvaetsya.
    Esli v kachestve opisatelya poyavlyaetsya prosto identifika-
tor, to on imeet tip, ukazyvaemyj v specificiruyushchem zagolov-
ke opisaniya.
    Opisatel' v kruglyh skobkah identichen opisatelyu bez
kruglyh skobok, no kruglye skobki mogut izmenyat' svyazi v
sostavnyh opisatelyah. Primery smotri nizhe.
    Predstavim sebe opisanie

   T     DI

gde T - specifikator tipa (podobnyj INT i t.d.), a DI - opi-
satel'. Predpolozhim, chto eto opisanie privodit k tomu, chto
sootvetstvuyushchij identifikator imeet tip "...T", gde "..."
pusto, esli DI prosto otdel'nyj identifikator (tak chto tip X
v "INT X" prosto INT). Togda , esli DI imeet formu

    *D

to soderzhashchijsya identifikator budet imet' tip "... Ukazatel'
na T".



    Esli DI imeet formu

    D()

to soderzhashchijsya identifikator imeet tip "... Funkciya, vozv-
rashchayushchaya T".
    Esli DI imeet formu

 D[konstantnoe-vyrazhenie]

ili

 D[ ]

to soderzhashchijsya identifikator imeet tip "...massiv T". V
pervom sluchae konstantnym vyrazheniem yavlyaetsya vyrazhenie,
znachenie kotorogo mozhno opredelit' vo vremya kompilyacii i ko-
toroe imeet tip INT. (Tochnoe opredelenie konstantnogo vyra-
zheniya dano v p. 23). Kogda neskol'ko specifikacij vida "mas-
siv iz" okazyvayutsya primykayushchimi, to sozdaetsya mnogomernyj
massiv; konstantnoe vyrazhenie, zadayushchee granicy massivov,
mozhet otsutstvovat' tol'ko u pervogo chlena etoj posledova-
tel'nosti. Takoe opuskanie polezno, kogda massiv yavlyaetsya
vneshnim i ego fakticheskoe opredelenie, kotoroe vydelyaet pa-
myat', privoditsya v drugom meste. Pervoe konstantnoe vyrazhe-
nie mozhet byt' opushcheno takzhe togda, kogda za opisatelem sle-
duet inicializaciya. V etom sluchae razmer opredelyaetsya po
chislu privedennyh inicializiruemyh elementov.
    Massiv mozhet byt' obrazovan iz elementov odnogo iz os-
novnyh tipov, iz ukazatelej, iz struktur ili ob容dinenij ili
iz drugih massivov (chtoby obrazovat' mnogomernyj massiv).
    Ne vse vozmozhnosti, kotorye razresheny s tochki zreniya
ukazannogo vyshe sintaksisa, fakticheski dopustimy. Imeyutsya
sleduyushchie ogranicheniya: funkcii ne mogut vozvrashchat' massivy,
struktury, ob容dineniya ili funkcii, hotya oni mogut vozvra-
shchat' ukazateli na takie veshchi; ne sushchestvuet massivov funk-
cij, hotya mogut byt' massivy ukazatelej na funkcii. Analo-
gichno, struktury ili ob容dineniya ne mogut soderzhat' funkciyu,
no oni mogut soderzhat' ukazatel' na funkciyu.
    V kachestve primera rassmotrim opisanie

 INT I, *IP, F(), *FIP(), (*PFI)();

v kotorom opisyvaetsya celoe I, ukazatel' IP na celoe, funk-
ciya F, vozvrashchayushchaya celoe, funkciya FIP, vozvrashchayushchaya ukaza-
tel' na celoe, i ukazatel' PFI na funkciyu, kotoraya vozvrashcha-
et celoe. Osobenno polezno sravnit' dva poslednih opisatelya.
Svyaz' v *FIP() mozhno predstavit' v vide *(FIP()), tak chto
opisaniem predpolagaetsya, a takoj zhe konstrukciej v vyrazhe-
nii trebuetsya obrashchenie k funkcii FIP i posleduyushchee ispol'-
zovanie kosvennoj adresacii dlya vydachi s pomoshch'yu poluchennogo
rezul'tata (ukazatelya) celogo. V opisatele (*PFI)() dopolni-



tel'nye skobki neobhodimy, poskol'ku oni tochno tak zhe, kak i
v vyrazhenii, ukazyvayut, chto kosvennaya adresaciya cherez ukaza-
tel' na funkciyu vydaet funkciyu, kotoraya zatem vyzyvaetsya;
eta vyzvannaya funkciya vozvrashchaet celoe.
    V kachestve drugogo primera privedem opisanie

   FLOAT FA[17], *AFP[17];

v kotorom opisyvaetsya massiv chisel tipa FLOAT i massiv uka-
zatelej na chisla tipa FLOAT. Nakonec,

  STATIC INT X3D[3][5][7];

opisyvaet staticheskij trehmernyj massiv celyh razmerom
3*5*7. bolee podrobno, X3D yavlyaetsya massivom iz treh elemen-
tov; kazhdyj element yavlyaetsya massivom pyati massivov; kazhdyj
poslednij massiv yavlyaetsya massivom iz semi celyh. Kazhdoe iz
vyrazhenij X3D, X3D[I], X3D[I][J] i X3D[I][J][K] mozhet razum-
nym obrazom poyavlyat'sya v vyrazheniyah. Pervye tri imeyut tip
"massiv", poslednee imeet tip INT.



    Struktura - eto ob容kt, sostoyashchij iz posledovatel'nosti
imenovannyh chlenov. kazhdyj chlen mozhet byt' proizvol'nogo ti-
pa. Ob容dinenie - eto ob容kt, kotoryj v dannyj moment mozhet
soderzhat' lyuboj iz neskol'kih chlenov. Specifikatory i
ob容dineniya imeyut odinakovuyu formu.
Specifikator-struktury-ili-ob容dineniya

struktura-ili-ob容dinenie \( spisok-opisanij-struktury\)

identifikator    struktury-ili-ob容dineniya
\(spisok-opisanij-struktury\)
identifikator struktury-ili-ob容dineniya

Struktura-ili-ob容dinenie:

   STRUCT
   UNION

Spisok-opisanij-struktury yavlyaetsya posledovatel'nost'yu opi-
sanij chlenov struktury ili ob容dineniya:

 Spisok-opisanij-struktury:
opisanie-struktury
opisanie-struktury spisok-opisanij-struktury
 opisanie-struktury:
specifikator-tipa spisok-opisatelej-struktury
 spisok-opisatelej-struktury:
opisatel'-struktury
opisatel'-struktury, spisok-opisatelej-struktury

V obychnom sluchae opisatel' struktury yavlyaetsya prosto opisa-
telem chlena struktury ili ob容dineniya. CHlen struktury mozhet
takzhe sostoyat' iz specificirovannogo chisla bitov. Takoj chlen
nazyvaetsya takzhe polem; ego dlina otdelyaetsya ot imeni polya
dvoetochiem.



  Opisatel'-struktury:
  opisatel'
  opisatel': konstantnoe vyrazhenie
  : konstantnoe vyrazhenie

Vnutri struktury opisannye v nej ob容kty imeyut adresa, koto-
rye uvelichivayutsya v sootvetstvii s chteniem ih opisanij sleva
napravo. Kazhdyj chlen struktury, kotoryj ne yavlyaetsya polem,
nachinaetsya s adresnoj granicy, sootvetstvuyushchej ego tipu;
sledovatel'no v strukture mogut okazat'sya neimenovannye dy-
ry. CHleny, yavlyayushchiesya polyami, pomeshchayutsya v mashinnye celye;
oni ne perekryvayut granicy slova. Pole, kotoroe ne umeshchaetsya
v ostavshemsya v dannom slove prostranstve, pomeshchaetsya v sle-
duyushchee slovo. Polya vydelyayutsya sprava nalevo na PDP-11 i sle-
va napravo na drugih mashinah.
    Opisatel' struktury, kotoryj ne soderzhit opisatelya, a
tol'ko dvoetochie i shirinu, ukazyvaet neimenovannoe pole, po-
leznoe dlya zapolneniya svobodnogo prostranstva s cel'yu soot-
vetstviya zadavaemyh izvne shemam. Special'nyj sluchaj neime-
novannogo polya s shirinoj 0 ispol'zuetsya dlya ukazaniya o vy-
ravnivanii sleduyushchego polya na granicu slova. Pri etom pred-
polagaetsya, chto "sleduyushchee pole" dejstvietl'no yavlyaetsya po-
lem, a ne obychnym chlenom struktury, poskol'ku v poslednem
sluchae vyravnivanie osushchestvlyaetsya avtomaticheski.
    Sam yazyk ne nakladyvaet ogranichenij na tipy ob容ktov,
opisannyh kak polya, no ot realizacij ne trebuetsya obespechi-
vat' chto-libo otlichnoe ot celyh polej. Bolee togo, dazhe polya
tipa INT mogut rassmatrivat'sya kak neimeyushchie znaka. Na
PDP-11 polya ne imeyut znaka i mogut prinimat' tol'ko celye
znacheniya. Vo vseh realizaciyah otsutstvuyut massivy polej i k
polyam ne primenima operaciya vzyatiya adresa &, tak chto ne su-
shchestvuet i ukazatelej na polya.
    Ob容dinenie mozhno predstavit' sebe kak strukturu, vse
chleny kotoroj nachinayutsya so smeshcheniya 0 i razmer kotoroj dos-
tatochen, chtoby soderzhat' lyuboj iz ee chlenov. V kazhdyj moment
ob容dinenie mozhet soderzhat' ne bolee odnogo iz svoih chlenov.
    Specifikator struktury ili ob容dineniya vo vtoroj forme,
t.e. Odin iz

STRUCT identifikator \(spisok-opisanij-struktury\)

UNION identifikator \(spisok-opisanij-struktury\)

opisyvaet identifikator v kachestve yarlyka struktury (ili yar-
lyka ob容dineniya) struktury, specificirovannoj etim spiskom.
Posleduyushchee opisanie mozhet zatem ispol'zovat' tret'yu formu
specifikatora, odin iz

STRUCT identifikator

UNION identifikator

YArlyki struktur dayut vozmozhnost' opredeleniya struktur, koto-
rye ssylayutsya na samih sebya; oni takzhe pozvolyayut neodnokrat-
no ispol'zovat' privedennuyu tol'ko odin raz dlinnuyu chast'
opisaniya. Zapreshchaetsya opisyvat' strukturu ili ob容dinenie,
kotorye soderzhat obrazec samogo sebya, no struktura ili
ob容dinenie mogut soderzhat' ukazatel' na strukturu ili
ob容dinenie takogo zhe vida, kak oni sami.
    Imena chlenov i yarlykov mogut sovpadat' s imenami obychnyh
peremennyh. Odnako imena yarlykov i chlenov dolzhny byt' vzaim-
no razlichnymi.
    Dve struktury mogut imet' obshchuyu nachal'nuyu posledovatel'-
nost' chlenov; eto oznachaet, chto tot zhe samyj chlen mozhet poya-
vit'sya v dvuh razlichnyh strukturah, esli on imeet odinakovyj
tip v obeih strukturah i esli vse predydushchie chleny obeih
struktur odinakovy. (Fakticheski kompilyator tol'ko proveryaet,
chto imya v dvuh razlichnyh strukturah imeet odinakovyj tip i
odinakovoe smeshchenie, no esli predshestvuyushchie chleny otlichayut-
sya, to konstrukciya okazyvaetsya neperenosimoj).
    Vot prostoj primer opisaniya struktury:

 STRUCT TNODE \(
 CHAR TWORD[20];
 INT COUNT;
 STRUCT TNODE *LEFT;
 STRUCT TNODE *RIGHT;
 \);
Takaya struktura soderzhit massiv iz 20 simvolov, celoe i dva
ukazatelya na podobnye struktury. Kak tol'ko privedeno takoe
opisanie, opisanie

   STRUCT TNODE S, *SP;

govorit o tom, chto S yavlyaetsya strukturoj ukazannogo vida, a
SP yavlyaetsya ukazatelem na strukturu ukazannogo vida. Pri na-
lichii etih opisanij vyrazhenie

  SP->COUNT

ssylaetsya k polyu COUNT struktury, na kotoruyu ukazyvaet SP;
vyrazhenie

  S.LEFT

ssylaetsya na ukazatel' levogo poddereva v strukture S, a vy-
razhenie

  S.RIGHT->TWORD[0]

ssylaetsya na pervyj simvol chlena TWORD pravogo poddereva iz
S.





    Opisatel' mozhet ukazyvat' nachal'noe znachenie opisyvaemo-
go identifikatora. Inicializator sostoit iz vyrazheniya ili
zaklyuchennogo v figurnye skobki spiska znachenij, pered koto-
rymi stavitsya znak =.

  Inicializator:
  = vyrazhenie
  = \(spisok-inicializatora\)
  = \(spisok-inicializatora,\)
  spisok-inicializatora:
  vyrazhenie
  spisok-inicializatora,spisok-inicializatora
  \(spisok-inicializatora\)

    Vse vyrazheniya, vhodyashchie v inicializator staticheskoj ili
vneshnej peremennoj, dolzhny byt' libo konstantnymi vyrazheniya-
mi, opisyvaemymi v p. 23, Libo vyrazheniyami, kotorye svodyatsya
k adresu ranee opisannoj peremennoj, vozmozhno smeshchennomu na
konstantnoe vyrazhenie. Avtomaticheskie i registrovye peremen-
nye mogut byt' inicializirovany proizvol'nymi vyrazheniyami,
vklyuchayushchimi konstanty i ranee opisannye peremennye i funk-
cii.
    Garantiruetsya, chto neinicializirovannye staticheskie i
vneshnie peremennye poluchayut v kachestve nachal'nyh znachenij
0;neinicializirovannye avtomaticheskie i registrovye peremen-
nye v kachestve nachal'nyh znachenij soderzhat musor.
    Kogda inicializator primenyaetsya k skalyaru (ukazatelyu ili
ob容ktu arifmeticheskogo tipa), to on sostoit iz odnogo vyra-
zheniya, vozmozhno zaklyuchennogo v figurnye skobki. Nachal'noe
znachenie ob容kta nahoditsya iz vyrazheniya; vypolnyayutsya te zhe
samye preobrazovaniya, chto i pri prisvaivanii.
    Kogda opisyvaemaya peremennaya yavlyaetsya agregatom (struk-
turoj ili massivom ), to inicializator sostoit iz zaklyuchen-
nogo v figurnye skobki i razdelennogo zapyatymi spiska inici-
alizatorov dlya chlenov agregata. |tot spisok sostavlyaetsya v
poryadke vozrastaniya indeksa ili v sootvetstvii s poryadkom
chlenov. Esli agregat soderzhit podagregaty, to eto pravilo
primenyaetsya rekursivno k chlenam agregata. Esli kolichestvo
inicializatorov v spiske okazyvaetsya men'she chisla chlenov ag-
regata, to ostavshiesya chleny agregata zapolnyayutsya nulyami.
Zapreshchaetsya inicializirovat' ob容dineniya ili avtomaticheskie
agregaty.
    Figurnye skobki mogut byt' opushcheny sleduyushchim obrazom.
Esli inicializator nachinaetsya s levoj figurnoj skobki, to
posleduyushchij razdelennyj zapyatymi spisok inicializatorov ini-
cializiruet chleny agregata; budet oshibkoj, esli v spiske
okazhetsya bol'she inicializatorov, chem chlenov agregata. Esli
odnako inicializator ne nachinaetsya s levoj figurnoj skobki,
to iz spiska beretsya tol'ko nuzhnoe dlya chlenov dannogo agre-
gata chislo elementov; ostavshiesya elementy ispol'zuyutsya dlya
inicializacii sleduyushchego chlena agregata, chast'yu kotorogo yav-
lyaetsya nastoyashchij agregat.



    Poslednee sokrashchenie dopuskaet vozmozhnost' inicializacii
massiva tipa CHAR s pomoshch'yu stroki. V etom sluchae chleny mas-
siva posledovatel'no inicializiruyutsya simvolami stroki.
    Naprimer,

    INT X[] = \(1,3,5\);

opisyvaet i inicializiruet X kak odnomernyj massiv; poskol'-
ku razmer massiva ne specificirovan, a spisok inicializitora
soderzhit tri elementa, schitaetsya, chto massiv sostoit iz treh
chlenov.
    Vot primer inicializacii s polnym ispol'zovaniem figur-
nyh skobok:

    FLOAT *Y[4][3] = \(
     ( 1, 3, 5 ),
     ( 2, 4, 6 ),
     ( 3, 5, 7 ),
    \);

Zdes' 1, 3 i 5 inicializiruyut pervuyu stroku massiva Y[0], a
imenno Y[0][0], Y[0][1] i Y[0][2]. Analogichnym obrazom sle-
duyushchie dve strochki inicializiruyut Y[1] i Y[2]. Inicializator
zakanchivaetsya prezhdevremenno, i, sledovatel'no massiv Y[3]
inicializiruetsya nulyami. V tochnosti takogo zhe effekta mozhno
bylo by dostich', napisav

     FLOAT Y[4][3] = \(
      1, 3, 5, 2, 4, 6, 3, 5, 7
     \);

Inicializator dlya Y nachinaetsya s levoj figurnoj skobki, no
inicializatora dlya Y[0] net. Poetomu ispol'zuetsya 3 elementa
iz spiska. Analogichno sleduyushchie tri elementa ispol'zuyutsya
posledovatel'no dlya Y[1] i Y[2]. sleduyushchee opisanie

     FLOAT Y[4][3] = \(
      (1), (2), (3), (4)
     \);

inicializiruet pervyj stolbec Y (esli ego rassmatrivat' kak
dvumernyj massiv), a ostal'nye elementy zapolnyayutsya nulyami.
    I nakonec, opisanie

    CHAR MSG[] = "SYNTAX ERROR ON LINE %S\N";

demonstriruet inicializaciyu elementov simvol'nogo massiva s
pomoshch'yu stroki.



    V dvuh sluchayah (dlya yavnogo ukazaniya tipa preobrazovaniya
v konstrukcii perevoda i dlya argumentov operacii SIZEOF) zhe-
latel'no imet' vozmozhnost' zadavat' imya tipa dannyh. |to
osushchestvlyaetsya s pomoshch'yu "imeni tipa", kotoroe po sushchestvu
yavlyaetsya opisaniem ob容kta takogo tipa , v kotorom opushcheno
imya samogo ob容kta.



   Imya tipa:
 specifikator-tipa abstraktnyj-opisatel'
   abstraktnyj-opisatel':
 pusto
 (abstraktnyj-opisatel')
 * abstraktnyj opisatel'
 abstraktnyj-opisatel' ()
 abstraktnyj-opisatel' [konstantnoe vyrazhenie
       neob]

Vo izbezhanii dvusmyslennosti v konstrukcii

     (abstraktnyj opisatel')

trebuetsya, chtoby abstraktnyj-opisatel' byl nepust. Pri etom
ogranichenii vozmozhno odnoznacheno opredelit' to mesto v abst-
raktnom-opisatele, gde by poyavilsya identifikator, esli by
eta konstrukciya byla opisatelem v opisanii. Imenovannyj tip
sovpadaet togda s tipom gipoteticheskogo identifikatora. Nap-
rimer, imena tipov

 INT
 INT *
 INT *[3]
 INT (*)[3]
 INT *()
 INT (*)()

imenuyut sootvetstvenno tipy "celyj", "ukazatel' na celoe",
"massiv iz treh ukazatelej na celoe", "ukazatel' na massiv
iz treh celyh", " funkciya, vozvrashchayushchaya ukazatel' na celoe"
i "ukazatel' na funkciyu, vozvrashchayushchuyu celoe".



    Opisaniya, v kotoryh "klass pamyati"specificirovan kak
TYPEDEF, ne vyzyvayut vydeleniya pamyati. vmesto etogo oni op-
redelyayut identifikatory ,kotorye pozdnee mozhno ispol'zovat'
tak, slovno oni yavlyayutsya klyuchevymi slovami, imeyushchimi osnov-
nye ili proizvodnye tipy.
 Opredelyayushchee-tip-imya
 identifikator

V predelah oblasti dejstviya opisaniya so specifikatorom
TYPEDEF kazhdyj identifikator, yavlyayushchijsya chast'yu lyubogo opi-
satelya v etom opisanii, stanovitsya sintaksicheski ekvivalent-
nym klyuchevomu slovu, imeyushchemu tot tip , kotoryj associiruet
s identifikatorom v opisannom v p. 16.4 Smysle. Naprimer,
posle opisanij

 TYPEDEF INT MILES, >KLICKSP;
 TYPEDEF STRUCT ( DOUBLE RE, IM; ) COMPLEX;

konstrukcii

 MILES DISTANCE;
 EXTERN KLICKSP METRICP;
 COMPLEX Z, *ZP;



stanovyatsya zakonnymi opisaniyami; pri etom tipom DISTANCE yav-
lyaetsya INT, tipom METRICP - "ukazatel' na INT", tipom Z -
specificirovannaya struktura i tipom ZP - ukazatel' na takuyu
strukturu.
    Specifikator TYPEDEF ne vvodit kakih-libo sovershenno no-
vyh tipov, a tol'ko opredelyaet sinonimy dlya tipov, kotorye
mozhno bylo by specificirovat' i drugim sposobom. Tak v pri-
vedennom vyshe primere peremennaya DISTANCE schitaetsya imeyushchej
tochno takoj zhe tip, chto i lyuboj drugoj ob容kt, opisannyj v
INT.



    Za isklyucheniem osobo ogovarivaemyh sluchaev, operatory
vypolnyayutsya posledovatel'no.



    Bol'shinstvo operatorov yavlyayutsya operatornymi vyrazheniya-
mi, kotorye imeyut formu

     vyrazhenie;

obychno operatornye vyrazheniya yavlyayutsya prisvaivaniyami ili ob-
rashcheniyami k funkciyam.



    S tem chtoby dopustit' vozmozhnost' ispol'zovaniya neskol'-
kih operatorov tam, gde ozhidaetsya prisutstvie tol'ko odnogo,
predusmatrivaetsya sostavnoj operator (kotoryj takzhe i ekvi-
valentno nazyvayut "blokom"):

    sostavnoj operator:
   \(spisok-opisanij    spisok-operatorov
               neob  neob\)
    spisok-opisanij:
   opisanie
   opisanie spisok-opisanij
    spisok-operatorov:
   operator
   operator spisok-operatorov

Esli kakoj-libo identifikator iz spiska-opisanij byl opisan
ranee, to vo vremya vypolneniya bloka vneshnee opisanie podav-
lyaetsya i snova vstupaet v silu posle vyhoda iz bloka.
    Lyubaya inicializaciya avtomaticheskih i registracionnyh pe-
remennyh provoditsya pri kazhdom vhode v blok cherez ego nacha-
lo. V nastoyashchee vremya razreshaetsya (no eto plohaya praktika)
peredavat' upravlenie vnutr' bloka; v takom sluchae eti ini-
cializacii ne vypolnyayutsya. Inicializacii staticheskih pere-
mennyh provodyatsya tol'ko odin raz, kogda nachinaetsya vypolne-
nie programmy.
    Nahodyashchiesya  vnutri  bloka   vneshnie   opisaniya   ne
rezerviruyut pamyati,  tak   chto   ih inicializaciya   ne
razreshaetsya.






    Imeyutsya dve formy uslovnyh operatorov:

   IF (vyrazhenie) operator
   IF (vyrazhenie) operator ELSE operator

V oboih sluchayah vychaslyaetsya vyrazhenie i, esli ono otlichno ot
nulya, to vypolnyaetsya pervyj podoperator. Vo vtorom sluchae,
esli vyrazhenie ravno nulyu, vypalnyaetsya vtoroj podoperator.
Kak obychno, dvusmyslennost' "ELSE" razreshaetsya svyazyvaeniem
ELSE s poslednim vstrechayushchimsya IF, u kotorogo net ELSE.



    Operator WHILE imeet formu

     WHILE (vyrazhenie) operator

Podoperator vypolnyaetsya povtorno do teh por, poka znachenie
vyrazheniya ostaetsya otlichnym ot nulya. proverka proizvoditsya
pered kazhdym vypolneniem operatora.



    Operator DO imeet formu

DO operator WHILE (vyrazheniya)

    Operator vypolnyaetsya povtorno do teh por, poka znachenie
vyrazheniya ne stanet ravnym nulyu. Proverka proizvoditsya posle
kazhdogo vypolneniya operatora.



    Operator FOR imeet formu

(vyrazhenie-1  ; vyrazhenie-2 ; vyrazhenie-3  )operator
     neob          neob          neob

    Operator FOR ekvivalenten sleduyushchemu

 vyrazhenie-1;
    WHILE   (vyrazhenie-2) \(
       operator
       vyrazhenie-3
 \)

Takim obrazom, pervoe vyrazhenie opredelyaet inicializaciyu
cikla; vtoroe specifiuiruet proverku, vypolnyaemuyu pered kazh-
doj iteraciej, tak chto vyhod iz cikla proishodit togda, kog-
da znachenie vyrazheniya stanovitsya nulem; tret'e vyrazhenie
chasto zadaet prirashchenie parametra, kotoroe provoditsya posle
kazhdoj iteracii.
    Lyuboe vyrazhenie ili dazhe vse oni mogut byt' opushcheny. Es-
li otsutstvuet vtoroe vyrazhenie, to predlozhenie s WHILE schi-
taetsya ekvivalentnym WHILE(1); drugie otsutstvuyushchie vyrazhe-
niya prosto opuskayutsya iz privedennogo vyshe rasshireniya.






    Operator SWITCH (pereklyuchatel'), vyzyvaet peredachu up-
ravleniya k odnomu iz neskol'kih operatorov, v zavisimosti ot
znacheniya vyrazheniya. Operator imeet formu

 SWITCH (vyrazhenie) operator

V vyrazhenii provodyatsya obychnye arifmeticheskie preobrazova-
niya, no rezul'tat dolzhen imet' tip INT. Operator obychno yav-
lyaetsya sostavnym. Lyuboj operator vnutri etogo operatora mo-
zhet byt' pomechen odnim ili bolee variantnym prefiksom CASE,
imeyushchim formu:

 CASE konstanstnoe vyrazhenie:

gde konstantnoe vyrazhenie dolzhno imet' tip INT. Nikakie dve
variantnye konstanty v odnom i tom zhe pereklyuchatele ne mogut
imet' odinakovoe znachenie. tochnoe opredelenie konstantnogo
vyrazheniya privoditsya v p. 23.
    Krome togo, mozhet prisutstvovat' samoe bol'shee odin ope-
ratornyj prefiks vida

 DEFAULT:

    Pri vypolnenii operatora SWITCH vychislyaetsya vhodyashchee v
nego vyrazhenie i sravnivaetsya s kazhdoj variantnoj konstan-
toj. Esli odna iz variantnyh konstant okazyvaetsya ravnoj
znacheniyu etogo vyrazheniya, to upravlenie peredaetsya operato-
ru, kotoryj sleduet za sovpadayushchim variantnym prefiksom. Es-
li ni odna iz variantnyh konstant ne sovpadaet so znacheniem
vyrazheniya i esli pri etom prisutstvuet prefiks DEFAULT, to
upravlenie peredaetsya operatoru, pomechennomu etim prefiksom.
esli ni odin iz variantov ne podhodit i prefiks DEFAULT ot-
sutstvuet, to ni odin iz operatorov v pereklyuchatele ne vy-
polnyaetsya.
    Sami po sebe prefiksy CASE i DEFAULT ne izmenyayut potok
upravleniya, kotoroe besprepyatsvenno prohodit cherez takie
prefiksy. Dlya vyhoda iz pereklyuchatelya smotrite operator
BREAK, p. 17.8.
    Obychno operator, kotoryj vhodit v pereklyuchatel', yavlyaet-
sya sostavnym. Opisaniya mogut poyavlyat'sya v nachale etogo ope-
ratora, no inicializacii avtomaticheskih i registrovyh pere-
mennyh budut neeffektivnymi.



    Operator

BREAK;

vyzyvaet zavershenie vypolneniya naimen'shego ohvatyvayushchego
etot operator operatora WHILE, DO, FOR ili SWITCH; upravle-
nie peredaetsya operatoru, sleduyushchemu za zavershennym operato-
rom.









    Operator

 CONTINUE;

privodit k peredache upravleniya na prodolzhayushchuyu cikl chast'
naimen'shego ohvatyvayushchego etot operator operatora WHILE, DO
ili FOR; to est' na konec cikla. Bolee tochno, v kazhdom iz
operatorov

  WHILE(...) \(       DO \(      FOR(...) \(
     ...            ...         ...
  CONTIN: ;           CONTIN: ;     CONTIN: ;
  \)            \) WHILE(...);     \)

Operator CONTINUE ekvivalenten operatoru GOTO CONTIN. (Za
CONTIN: sleduet pustoj operator; sm. P. 17.13.).



    Vozvrashchenie iz funkcii v vyzyvayushchuyu programmu osushchestv-
lyaetsya s pomoshch'yu operatora RETURN, kotoryj imeet odnu iz
sleduyushchih form

  RETURN;
   RETURN vyrazhenie;

V pervom sluchae vozvrashchaemoe znachenie neopredeleno. Vo vto-
rom sluchae v vyzyvayushchuyu funkciyu vozvrashchaetsya znachenie vyra-
zheniya. Esli trebuetsya, vyrazhenie preobrazuetsya k tipu funk-
cii, v kotoroj ono poyavlyaetsya, kak v sluchae prisvaivaniya.
Popadanie na konec funkcii ekvivalentno vozvratu bez vozvra-
shchaemogo znacheniya.



    Upravlenie mozhno peredavat' bezuslovno s pomoshch'yu opera-
tora

    GOTO identifikator1

identifikator dolzhen byt' metkoj (p. 9.12), Lokalizovannoj v
dannoj funkcii.



    Pered lyubym operatorom mozhet stoyat' pomechennyj prefiks
vida
    identifikator:

kotoryj sluzhit dlya opisaniya identifikatora v kachestve metki.
Metki ispol'zuyutsya tol'ko dlya ukazaniya mesta, kuda peredaet-
sya upravlenie operatorom GOTO. Oblast'yu dejstviya metki yavlya-
etsya dannaya funkciya, za isklyucheniem lyubyh podblokov, v koto-
ryh tot zhe identifikator opisan snova. Smotri p. 19.






    Pustoj operator imeet formu:

  ;

Pustoj operator okazyvaetsya poleznym, tak kak on pozvolyaet
postavit' metku pered zakryvayushchej skobkoj \) sostavnogo ope-
ratora ili ukazat' pustoe telo v operatorah cikla, takih kak
WHILE.



    C-programma predstavlyaet soboj posledovatel'nost' vnesh-
nih opredelenij. Vneshnee opredelenie opisyvaet identifikator
kak imeyushchij klass pamyati EXTERN (po umolchaniyu), ili vozmozhno
STATIC, i specificirovannyj tip. Specifikator tipa (p. 16.2)
Takzhe mozhet byt' pustym; v etom sluchae schitaetsya, chto tip
yavlyaetsya tipom INT. Oblast' dejstviya vneshnih opredelenij
rasprostranyaetsya do konca fajla, v kotorom oni privedeny,
tochno tak zhe , kak vliyanie opisanij prostiraetsya do konca
bloka. Sintaksis vneshnih opredelenij ne otlichaetsya ot sin-
taksisa opisanij, za isklyucheniem togo, chto tol'ko na etom
urovne mozhno privodit' tekst funkcij.



    Opredelenie funkcii imeet formu

    opredelenie-funkcii:

    specifikatory-opisaniya   opisatel'-funkcii
telo-funkcii
         neob

Edinstvennymi specifikatorami klassa pamyati, dopuskaemymi v
kachestve specifikatorov-opisaniya, yavlyayutsya EXTERN ili
STATIC; o razlichii mezhdu nimi smotri p. 19.2. Opisatel' fun-
kcii podoben opisatelyu dlya "funkcii, vozvrashchayushchej...", za
isklyucheniem togo, chto on perechislyaet formal'nye parametry
opredelyaemoj funkcii.

  Oisatel'-funkcii:
 opisatel' (spisok-parametrov
         neob)
  spisok parametrov:
  identifikator
  identifikator, spisok-parametrov

telo-funkcii imeet formu

   telo-funkcii:
  spisok-opisanij sostavnoj-operator



    Identifikatory iz spiska parametrov i tol'ko oni mogut
byt' opisany v spiske opisanij. Lyuboj identifikator, tip ko-
torogo ne ukazan, schitaetsya imeyushchim tip INT. Edinstvennym
dopustimym zdes' specifikatorom klassa pamyati yavlyaetsya
REGISTER; esli takoj klass pamyati specificirovan, to v nacha-
le vypolneniya funkcii sootvetstvuyushchij fakticheskij parametr
kopiruetsya, esli eto vozmozhno, v registr.
    Vot prostoj primer polnogo opredeleniya funkcii:

  INT MAX(A, B, C)
  INT A, B, C;
  \(
 INT M;
 M = (A>B) ? A:B;
 RETURN((M>C) ? M:C);
  \)

Zdes' INT - specifikator-tipa, MAX(A,B,C) - opisatel'-funk-
cii, INT A,B,C; - spisok-opisanij formal'nyh parametrov, \(
... \) - Blok, soderzhashchij tekst operatora.
    V yazyke "C" vse fakticheskie parametry tipa FLOAT preob-
razuyutsya k tipu DOUBLE, tak chto opisaniya formal'nyh paramet-
rov, ob座avlennyh kak FLOAT, prisposobleny prochest' parametry
tipa DOUBLE. Analogichno, poskol'ku ssylka na massiv v lyubom
kontekste (v chastnosti v fakticheskom parametre) rassmatriva-
etsya kak ukazatel' na pervyj element massiva, opisaniya for-
mal'nyh parametrov vila "massiv ..." prisposobleny prochest'
: "ukazatel' na ...". I nakonec, poskol'ku struktury,
ob容dineniya i funkcii ne mogut byt' peredany funkcii, bess-
myslenno opisyvat' formal'nyj parametr kak strukturu,
ob容dinenie ili funkciyu (ukazateli na takie ob容kty, konech-
no, dopuskayutsya).




    Vneshnee opredelenie dannyh imeet formu

 opredelenie-dannyh:
   opisanie

Klassom pamyati takih dannyh mozhet byt' EXTERN (v chastnosti,
po umolchaniyu) ili STATIC, no ne AUTO ili REGISTER.



    Vsya C-programma neobyazatel'no kompiliruetsya odnovremen-
no; ishodnyj tekst programmy mozhet hranit'sya v neskol'kih
fajlah i ranee skompilirovannye procedury mogut zagruzhat'sya
iz bibliotek. Svyaz' mezhdu funkciyami mozhet osushchestvlyat'sya kak
cherez yavnye obrashcheniya, tak i v rezul'tate manipulirovaniya s
vneshnimi dannymi.
    Poetomu sleduet rassmotret' dva vida oblastej dejstviya:
vo-pervyh, tu, kotoraya mozhet byt' nazvana leksicheskoj ob-
last'yu dejstviya identifikatora i kotoraya po sushchestvu yavlyaet-
sya toj oblast'yu v programme, gde etot identifikator mozhno
ispol'zovat', ne vyzyvaya diagnosticheskogo soobshcheniya "neopre-
delennyj identifikator"; i vo-vtoryh, oblast' dejstviya, ko-
toraya svyazana s vneshnimi identifikatorami i kotoraya harakte-
rizuetsya pravilom, chto ssylki na odin i tot zhe vneshnij iden-
tifikator yavlyayutsya ssylkami na odin i tot zhe ob容kt.






    Leksicheskaya oblast' dejstviya identifikatorov, opisannyh
vo vneshnih opredeleniyah, prostiraetsya ot opredeleniya do kon-
ca ishodnogo fajla, v kotorom on nahoditsya. Leksicheskaya ob-
last' dejstviya identifikatorov, yavlyayushchihsya formal'nymi para-
metrami, rasprostranyaetsya na tu funkciyu, k kotoroj oni otno-
syatsya. Leksicheskaya oblast' dejstviya identifikatorov, opisan-
nyh v nachale bloka, prostiraetsya do konca etogo bloka. Lek-
sicheskoj oblast'yu dejstviya metok yavlyaetsya ta funkciya, v ko-
toroj oni nahodyatsya.
    Poskol'ku vse obrashcheniya na odin i tot zhe vneshnij identi-
fikator obrashchayutsya k odnomu i tomu zhe ob容ktu (sm. P. 19.2),
Kompilyator proveryaet vse opisaniya odnogo i togo zhe vneshnego
identifikatora na sovmestimost'; v dejstvitel'nosti ih ob-
last' dejstviya rasprostranyaetsya na ves' fajl, v kotorom oni
nahodyatsya.
    Vo vseh sluchayah, odnako, est' nekotoryj identifikator,
yavnym obrazom opisan v nachale bloka, vklyuchaya i blok, kotoryj
obrazuet funkciyu, to dejstvie lyubogo opisaniya etogo identi-
fikatora vne bloka priostanavlivaetsya do konca etogo bloka.
    Napomnim takzhe (p. 16.5), CHto identifikatory, sootvetst-
vuyushchie obychnym peremennym, s odnoj storony, i identifikato-
ry, sootvetstvuyushchie chlenam i yarlykam struktur i ob容dinenij,
s drugoj storony, formiruyut dva neperesekayushchihsya klassa, ko-
torye ne vstupayut v protivorechie. CHleny i yarlyki podchinyayutsya
tem zhe samym pravilam opredeleniya oblastej dejstviya, kak i
drugie identifikatory. Imena, specificiruemye s pomoshch'yu
TYPEDEF, vhodyat v tot zhe klass, chto i obychnye identifikato-
ry. Oni mogut byt' pereopredeleny vo vnutrennih blokah, no
vo vnutrennem opisanii tip dolzhen byt' ukazan yavno:

 TYPEDEF FLOAT DISTANCE;
 ...
 \(
    AUTO INT DISTANCE;
    ...

Vo vtorom opisanii specifikator tipa INT dolzhen prisutstvo-
vat', tak kak v protivnom sluchae eto opisanie budet prinyato
za opisanie bez opisatelej s tipom DISTANCE (prim. Avtora:
soglasites', chto led zdes' tonok.).



    Esli funkciya ssylaetsya na identifikator, opisannyj kak
EXTERN, to gde-to sredi fajlov ili bibliotek, obrazuyushchih
polnuyu programmu, dolzhno soderzhat'sya vneshnee opredelenie
etogo identifikatora. Vse funkcii dannoj programmy, kotorye
ssylayutsya na odin i tot zhe vneshnij identifikator, ssylayutsya
na odin i tot zhe ob容kt, tak chto sleduet pozabotit'sya, chtoby
specificirovannye v etom opredelenii tip i razmer byli sov-
mestimy s tipom i razmerom, ukazyvaemymi v kazhdoj funkcii,
kotoraya ssylaetsya na eti dannye.



    Poyavlenie klyuchevogo slova EBTERN vo vneshnem opredelenii
ukazyvaet na to, chto pamyat' dlya opisannyh v nem identifika-
torov budet vydelena v drugom fajle. Sledovatel'no, v sosto-
yashchej iz mnogih fajlov programme vneshnee opredelenie identi-
fikatora, ne soderzhashchee specifikatora EXTERN, dolzhno poyav-
lyat'sya rovno v odnom iz etih fajlov. lyubye drugie fajly, ko-
torye zhelayut dat' vneshnee opredelenie etogo identifikatora,
dolzhny vklyuchat' v eto opredelenie slovo EXTERN. Identifika-
tor mozhet byt' inicializirovan tol'ko v tom opisanii, koto-
roe privodit k vydeleniyu pamyati.
    Identifikatory, vneshnee opredelenie kotoryh nachinaetsya
so slova STATIC, nedostupny iz drugih fajlov. Funkcii mogut
byt' opisany kak STATIC.



    Kompilyator yazyka "C" soderzhit preprocessor, kotoryj poz-
volyaet osushchestvlyat' makropodstanovki, uslovnuyu kompilyaciyu i
vklyuchenie imenovannyh fajlov. Stroki, nachinayushchiesya s #, ob-
shchayutsya s etim preprocessorom. Sintaksis etih strok ne svyazan
s ostal'nym yazykom; oni mogut poyavlyat'sya v lyubom meste i ih
vliyanie rasprostranyaetsya (nezavisimo ot oblasti dejstviya) do
konca ishodnogo programmnogo fajla.



    Upravlyayushchaya kompilyatorom stroka vida

#DEFINE identifikator stroka-leksem

    (Obratite vnimanie na otsutstvie v konce tochki s zapya-
toj) privodit k tomu, chto preprocessor zamenyaet posleduyushchie
vhozhdeniya etogo identifikatora na ukazannuyu stroku leksem.
Stroka vida

#DEFINE        identifikator
(identifikator,...,identifikator)stroka leksem

gde mezhdu pervym identifikatorom i otkryvayushchejsya skobkoj (
net probela, predstavlyaet soboj makroopredelenie s argumen-
tami. Posleduyushchee vhozhdenie pervogo identifikatora, za koto-
rym sleduet otkryvayushchaya skobka '(', posledovatel'nost' raz-
delennyh zapyatymi leksem i zakryvayushchaya skobka ')', zamenyayut-
sya strokoj leksem iz opredeleniya. kazhdoe vhozhdenie identifi-
katora, upomyanutogo v spiske formal'nyh parametrov v oprede-
lenii , zamenyaetsya sootvetstvuyushchej strokoj leksem iz obrashche-
niya. Fakticheskimi argumentami v obrashchenii yavlyayutsya stroki
leksem, razdelennye zapyatymi; odnako zapyatye, vhodyashchie v za-
kavychennye stroki ili zaklyuchennye v kruglye skobki, ne raz-
delyayut argumentov. Kolichestvo formal'nyh i fakticheskih para-
metrov dolzhno sovpadat'. Tekst vnutri stroki ili simvol'noj
konstanty ne podlezhit zamene.
    V oboih sluchayah zamenennaya stroka prosmatrivaetsya snova
s cel'yu obnaruzheniya drugih opredelennyh identifikatorov. V
oboih sluchayah slishkom dlinnaya stroka opredeleniya mozhet byt'
prodolzhena na drugoj stroke, esli pomestit' v konce prodol-
zhaemoj stroki obratnuyu kosuyu chertu \ .



    Opisyvaemaya vozmozhnost' osobenno polezna dlya opredeleniya
"ob座avlyaemyh konstant", kak, naprimer,

 #DEFINE TABSIZE 100
 INT TABLE[TABSIZE];

Upravlyayushchaya stroka vida

 #UNDEF identifikator

privodit k otmene preprocessornogo opredeleniya dannogo iden-
tifikatora.



    Stroka upravleniya kompilyatorom vida

 #INCLUDE "FILENAME"

privodit k zamene etoj stroki na vse soderzhimoe fajla s ime-
nem FILENAME. Fajl s etim imenem snachala ishchetsya v spravochni-
ke nachal'nogo ishodnogo fajla, a zatem v posledovatel'nosti
standartnyh mest. V otlichie ot etogo upravlyayushchaya stroka vida

 #INCLUDE <FILENAME>

ishchet fajl tol'ko v standartnyh mestah i ne prosmatrivaet
spravochnik ishodnogo fajla.
    Stroki #INCLUDE mogut byt' vlozhennymi.



    Stroka upravleniya kompilyatorom vida

#IF konstantnoe vyrazhenie

proveryaet, otlichno li ot nulya znachenie konstantnogo vyrazhe-
niya (sm. P. 15). Upravlyayushchaya stroka vida

#IF DEF identifikator

proveryaet, opredelen li etot identifikator v nastoyashchij mo-
ment v preprocessore, t.e. Opredelen li etot identifikator s
pomoshch'yu upravlyayushchej stroki #DEFINE.



    Ne vsegda yavlyaetsya neobhodimym specificirovat' i klass
pamyati i tip identifikatora v opisanii. Vo vneshnih opredele-
niyah i opisaniyah formal'nyh parametrov i chlenov struktur
klass pamyati opredelyaetsya po kontekstu. Esli v nahodyashchemsya
vnutri funkcii opisanii ne ukazan tip, a tol'ko klass pamya-
ti, to predpolagaetsya, chto identifikator imeet tip INT; esli
ne ukazan klass pamyati, a tol'ko tip, to identifikator pred-
polagaetsya opisannym kak AUTO. Isklyuchenie iz poslednego pra-
vila daetsya dlya funkcij, potomu chto specifikator AUTO dlya
funkcij yavlyaetsya bessmyslennym (yazyk "C" ne v sostoyanii kom-
pilirovat' programmu v stek); esli identifikator imeet tip
"funkciya, vozvrashchayushchaya ...", to on predpolagaetsya neyavno
opisannym kak EXTERN.



    Vhodyashchij v vyrazhenie i neopisannyj ranee identifikator,
za kotorym sleduet skobka ( , schitaetsya opisannym po kontek-
stu kak "funkciya, vozvrashchayushchaya INT".



    V etom razdele obobshchayutsya svedeniya ob operaciyah, kotorye
mozhno primenyat' tol'ko k ob容ktam opredelennyh tipov.



    Tol'ko dve veshchi mozhno sdelat' so strukturoj ili ob容di-
neniem: nazvat' odin iz ih chlenov (s pomoshch'yu operacii) ili
izvlech' ih adres ( s pomoshch'yu unarnoj operacii &). Drugie
operacii, takie kak prisvaivanie im ili iz nih i peredacha ih
v kachestve parametrov, privodyat k soobshcheniyu ob oshibke. V bu-
dushchem ozhidaetsya, chto eti operacii, no ne obyazatel'no ka-
kie-libo drugie, budut razresheny.
    V p. 15.1 Govoritsya, chto pri pryamoj ili kosvennoj ssylke
na strukturu (s pomoshch'yu . Ili ->) imya sprava dolzhno byt'
chlenom struktury, nazvannoj ili ukazannoj vyrazheniem sleva.
|to ogranichenie ne navyazyvaetsya strogo kompilyatorom, chtoby
dat' vozmozhnost' obojti pravila tipov. V dejstvitel'nosti
pered '.' dopuskaetsya lyuboe L-znachenie i zatem predpolagaet-
sya, chto eto L-znachenie imeet formu struktury, dlya kotoroj
stoyashchee sprava imya yavlyaetsya chlenom. Takim zhe obrazom, ot vy-
razheniya, stoyashchego pered '->', trebuetsya tol'ko byt' ukazate-
lem ili celym. V sluchae ukazatelya predpolagaetsya, chto on
ukazyvaet na strukturu, dlya kotoroj stoyashchee sprava imya yavlya-
etsya chlenom. V sluchae celogo ono rassmatrivaetsya kak abso-
lyutnyj adres sootvetstvuyushchej struktury, zadannyj v edinicah
mashinnoj pamyati.
    Takie struktury ne yavlyayutsya perenosimymi.



    Tol'ko dve veshchi mozhno sdelat' s funkciej: vyzvat' ee ili
izvlech' ee adres. Esli imya funkcii vhodit v vyrazhenie ne v
pozicii imeni funkcii, sootvetstvuyushchej obrashcheniyu k nej, to
generiruetsya ukazatel' na etu funkciyu. Sledovatel'no, chtoby
peredat' odnu funkciyu drugoj, mozhno napisat'

 INT F();
 ...
 G(F);

Togda opredelenie funkcii G moglo by vyglyadet' tak:

G(FUNCP)
INT(*FUNCP)();
\(
   ...
   (*FUNCP)();
   ...
\)

Obratite vnimanie, chto v vyzyvayushchej procedure funkciya F dol-
zhna byt' opisana yavno, potomu chto za ee poyavleniem v G(F) ne
sleduet skobka ( .






    Kazhdyj raz, kogda identifikator, imeyushchij tip massiva,
poyavlyaetsya v vyrazhenii, on preobrazuetsya v ukazatel' na per-
vyj chlen etogo massiva. Iz-za etogo preobrazovaniya massivy
ne yavlyayutsya L-znacheniyami. Po opredeleniyu operaciya indeksaciya
[] interpretiruetsya takim obrazom, chto E1[E2] schitaetsya
identichnym vyrazheniyu *((e1)+(e2)). Soglasno pravilam preob-
razovanij, primenyaemym pri operacii +, esli E1 - massiv, a
e2 - celoe, to e1[e2] ssylaetsya na e2-j chlen massiva e1. Po-
etomu nesmotrya na nesimmetrichnyj vid operaciya indeksacii yav-
lyaetsya kommutativnoj.
    V sluchae mnogomernyh massivov primenyaetsya posledovatel'-
noe pravilo. Esli e yavlyaetsya N-mernym massivom razmera
I*J*...*K, to pri poyavlenii v vyrazhenii e preobrazuetsya v
ukazatel' na (N-1)-mernyj massiv razmera J*...*K. Esli ope-
raciya * libo yavno, libo neyavno, kak rezul'tat indeksacii,
primenyaetsya k etomu ukazatelyu, to rezul'tatom operacii budet
ukazannyj (N-1)-mernyj massiv, kotoryj sam nemedlenno preob-
razuetsya v ukazatel'.
    Rassmotrim, naprimer, opisanie

INT X[3][5];

Zdes' X massiv celyh razmera 3*5. Pri poyavlenii v vyrazhenii
X preobrazuetsya v ukazatel' na pervyj iz treh massivov iz 5
celyh. V vyrazhenii X[I], kotoroe ekvivalentno *(X+I), snacha-
la X preobrazuetsya v ukazatel' tak, kak opisano vyshe; zatem
I preobrazuetsya k tipu X, chto vyzyvaet umnozhenie I na dlinu
ob容kta, na kotoryj ukazyvaet ukazatel', a imenno na 5 celyh
ob容ktov. Rezul'taty skladyvayutsya, i primenenie kosvennoj
adresacii daet massiv (iz 5 celyh), kotoryj v svoyu ochered'
preobrazuetsya v ukazatel' na pervoe iz etih celyh. Esli v
vyrazhenie vhodit i drugoj indeks, to tazhe samaya argumentaciya
primenyaetsya snova; rezul'tatom na etot raz budet celoe.
    Iz vsego etogo sleduet, chto massivy v yazyke "C" hranyatsya
postrochno ( poslednij indeks izmenyaetsya bystree vsego) i chto
pervyj indeks v opisanii pomogaet opredelit' obshchee kolichest-
vo pamyati, trebuemoe dlya hraneniya massiva, no ne igraet ni-
kakoj drugoj roli v vychisleniyah, svyazannyh s indeksaciej.



    Razreshayutsya opredelennye preobrazovaniya, s ispol'zovani-
em ukazatelej , no oni imeyut nekotorye zavisyashchie ot konkret-
noj realizacii aspekty. Vse eti preobrazovaniya zadayutsya s
pomoshch'yu operacii yavnogo preobrazovaniya tipa; sm. P. 15.2 i
16.7.
    Ukazatel' mozhet byt' preobrazovan v lyuboj iz celochislen-
nyh tipov, dostatochno bol'shoj dlya ego hraneniya. Trebuetsya li
pri etom INT ili LONG, zavisit ot konkretnoj mashiny. Preob-
razuyushchaya funkciya takzhe yavlyaetsya mashinno-zavisimoj, no ona
budet vpolne estestvennoj dlya teh, kto znaet strukturu adre-
sacii v mashine. Detali dlya nekotoryh konkretnyh mashin privo-
dyatsya nizhe.
    Ob容kt celochislennogo tipa mozhet byt' yavnym obrazom pre-
obrazovan v ukazatel'. takoe preobrazovanie vsegda perevodit
preobrazovannoe iz ukazatelya celoe v tot zhe samyj ukazatel',
no v drugih sluchayah ono budet mashinno-zavisimym.



    Ukazatel' na odin tip mozhet byt' preobrazovan v ukaza-
tel' na drugoj tip. Esli preobrazuemyj ukazatel' ne ukazyva-
et na ob容kty, kotorye podhodyashchim obrazom vyravneny v pamya-
ti, to rezul'tiruyushchij ukazatel' mozhet pri ispol'zovanii vy-
zyvat' oshibki adresacii. Garantiruetsya, chto ukazatel' na
ob容kt zadannogo razmera mozhet byt' preobrazovan v ukazatel'
na ob容kt men'shego razmera i snova obratno, ne preterpev pri
etom izmeneniya.
    Naprimer, procedura raspredeleniya pamyati mogla by prini-
mat' zapros na razmer vydelyaemogo ob容kta v bajtah, a vozv-
rashchat' ukazatel' na simvoly; eto mozhno bylo by ispol'zovat'
sleduyushchim obrazom.

 EXTERN CHAR *ALLOC();
 DOUBLE *DP;
 DP=(DOUBLE*) ALLOC(SIZEOF(DOUBLE));
 *DP=22.0/7.0;

Funkciya ALLOC dolzhna obespechivat' (mashinno-zavisimym sposo-
bom), chto vozvrashchaemoe eyu znachenie budet podhodyashchim dlya pre-
obrazovaniya v ukazatel' na DOUBLE; v takom sluchae ispol'zo-
vanie etoj funkcii budet perenosimym.
    Predstavlenie ukazatelya na PDP-11 sootvetstvuet 16-bito-
vomu celomu i izmeryaetsya v bajtah. Ob容kty tipa CHAR ne ime-
yut nikakih ogranichenij na vyravnivanie; vse ostal'nye ob容k-
ty dolzhny imet' chetnye adresa.
    Na HONEYWELL 6000 ukazatel' sootvetstvuet 36-bitovomu
celomu; slovu sootvetstvuet 18 levyh bitov i dva neposredst-
venno primykayushchih k nim sprava bita, kotorye vydelyayut simvol
v slove. Takim obrazom, ukazateli na simvoly izmeryayutsya v
edinicah 2 v stepeni 16 bajtov; vse ostal'noe izmeryaetsya v
edinicah 2 v stepeni 18 mashinnyh slov. Velichiny tipa DOUBLE
i soderzhashchie ih agregaty dolzhny vyravnivat'sya po chetnym ad-
resam slov (0 po modulyu 2 v stepeni 19). |vm IBM 370 i
INTERDATA 8/32 shodny mezhdu soboj. Na obeih mashinah adresa
izmeryayutsya v bajtah; elementarnye ob容kty dolzhny byt' vyrov-
neny po granice, ravnoj ih dline, tak chto ukazateli na SHORT
dolzhny byt' kratny dvum, na INT i FLOAT - chetyrem i na
DOUBLE - vos'mi. Agregaty vyravnivayutsya po samoj strogoj
granice, trebuemoj kakim-libo iz ih elementov.



    V neskol'kih mestah v yazyke "C" trebuyutsya vyrazheniya, ko-
torye posle vychisleniya stanovyatsya konstantami: posle varian-
tnogo prefiksa CASE, v kachestve granic massivov i v inicia-
lizatorah. V pervyh dvuh sluchayah vyrazhenie mozhet soderzhat'
tol'ko celye konstanty, simvol'nye konstanty i vyrazheniya
SIZEOF, vozmozhno svyazannye libo binarnymi operaciyami

 + - * / . % & \! CH << >> == 1= <> <= >=

libo unarnymi operaciyami

- \^

libo ternarnoj operaciej ?:



Kruglye skobki mogut ispol'zovat'sya dlya gruppirovki, no ne
dlya obrashcheniya k funkciyam.

    V sluchae inicializatorov dopuskaetsya bol'shaya (udarenie
na bukvu o) svoboda; krome perechislennyh vyshe konstantnyh
vyrazhenij mozhno takzhe primenyat' unarnuyu operaciyu & k vneshnim
ili staticheskim ob容ktam i k vneshnim ili staticheskim massi-
vam, imeyushchim v kachestve indeksov konstantnoe vyrazhenie.
Unarnaya operaciya & mozhet byt' takzhe primenena neyavno, v re-
zul'tate poyavleniya neindeksirovannyh massivov i funkcij. Os-
novnoe pravilo zaklyuchaetsya v tom, chto posle vychisleniya ini-
cializator dolzhen stanovitsya libo konstantoj, libo adresom
ranee opisannogo vneshnego ili staticheskogo ob容kta plyus ili
minus konstanta.



    Nekotorye chasti yazyka "C" po svoej suti mashinno-zavisi-
my. Sleduyushchie nizhe perechislenie potencial'nyh trudnostej ho-
tya i ne yavlyayutsya vseob容mlyushchimi, no vydelyaet osnovnye iz
nih.
    Kak pokazala praktika, voprosy, celikom svyazannye s ap-
paratnym oborudovaniem, takie kak razmer slova, svojstva
plavayushchej arifmetiki i celogo deleniya, ne predstavlyayut oso-
bennyh zatrudnenij. Drugie aspekty apparatnyh sredstv naho-
dyat svoe otrazhenie v razlichnyh realizaciyah. Nekotorye iz
nih, v chastnosti, znakovoe rasshirenie (preobrazuyushchee otrica-
tel'nyj simvol v otricatel'noe celoe) i poryadok, v kotorom
pomeshchayutsya bajty v slove, predstavlyayut soboj nepriyatnost',
kotoraya dolzhna tshchatel'no otslezhivat'sya. Bol'shinstvo iz os-
tal'nyh problem etogo tipa ne vyzyvaet skol'ko-nibud' znachi-
tel'nyh zatrudnenij.
    CHislo peremennyh tipa REGISTER, kotoroe fakticheski mozhet
byt' pomeshcheno v registry, menyaetsya ot mashiny k mashine, takzhe
kak i nabor dopustimyh dlya nih tipov. Tem ne menee vse kom-
pilyatory na svoih mashinah rabotayut nadlezhashchim obrazom; lish-
nie ili nedopustimye registrovye opisaniya ignoriruyutsya.
    Nekotorye trudnosti voznikayut tol'ko pri ispol'zovanii
somnitel'noj praktiki programmirovaniya. Pisat' programmy,
kotorye zavisyat ot kakih- libo etih svojstv, yavlyaetsya chrez-
vychajno nerazumnym.
    YAzykom ne ukazyvaetsya poryadok vychisleniya argumentov fun-
kcij; oni vychislyayutsya sprava nalevo na PDP-11 i VAX-11 i
sleva napravo na ostal'nyh mashinah. poryadok, v kotorom pro-
ishodyat pobochnye effekty, takzhe ne specificiruetsya.
    Tak kak simvol'nye konstanty v dejstvitel'nosti yavlyayutsya
ob容ktami tipa INT, dopuskaetsya ispol'zovanie simvol'nyh
konstant, sostoyashchih iz neskol'kih simvolov. Odnako, poskol'-
ku poryadok, v kotorom simvoly pripisyvayutsya k slovu, menyaet-
sya ot mashiny k mashine, konkretnaya realizaciya okazyvaetsya
ves'ma mashinno-zavisimoj.
    Prisvaivanie polej k slovam i simvolov k celym osushchestv-
lyaetsya spravo nalevo na PDP-11 i VAX-11 i sleva napravo na
drugih mashinah. eti razlichiya nezametny dlya izolirovannyh
programm, v kotoryh ne razresheno smeshivat' tipy (preobrazuya,
naprimer, ukazatel' na INT v ukazatel' na CHAR i zatem pro-
veryaya ukazyvaemuyu pamyat'), no dolzhny uchityvat'sya pri sogla-
sovanii s nakladyvaemymi izvne shemami pamyati.



    YAzyk, prinyatyj na razlichnyh kompilyatorah, otlichaetsya
tol'ko neznachitel'nymi detalyami. Samoe zametnoe otlichie sos-
toit v tom, chto ispol'zuemyj v nastoyashchee vremya kompilyator na
PDP-11 ne inicializiruet struktury, kotorye soderzhat polya
bitov, i ne dopuskaet nekotorye operacii prisvaivaniya v op-
redelennyh kontekstah, svyazannyh s ispol'zovaniem znacheniya
prisvaivaniya.



    Tak kak yazyk "C" yavlyaetsya razvivayushchimsya yazykom, v staryh
programmah mozhno vstretit' nekotorye ustarevshie konstrukcii.
Hotya bol'shinstvo versij kompilyatora podderzhivaet takie anah-
ronizmy, oni v konce koncov ischeznut, ostaviv za soboj tol'-
ko problemy perenosimosti.
    V rannih versiyah "C" dlya problem prisvaivaniya ispol'zo-
valas' forma =ON, a ne ON=, privodya k dvusmyslennostyam, ti-
pichnym primerom kotoryh yavlyaetsya

X = -1

gde X fakticheski umen'shaetsya, poskol'ku operacii = i - pri-
mykayut drug k drugu, no chto vpolne moglo rassmatrivat'sya i
kak prisvaivanie -1 k X.
    Sintaksis inicializatorov izmenilsya: ran'she znak ravens-
tva, s kotorogo nachinaetsya inicializator, otsutstvoval, tak
chto vmesto

INT X = 1;

ispol'zovalos'

INT X 1;

izmenenie bylo vneseno iz-za inicializacii

INT F (1+2)

kotoraya dostatochno sil'no napominaet opredelenie funkcii,
chtoby smutit' kompilyatory.



    |ta svodka sintaksisa yazyka "C" prednaznachena skoree dlya
oblegcheniya ponimaniya i ne yavlyaetsya tochnoj formulirovkoj yazy-
ka.



    Osnovnymi vyrazheniyami yavlyayutsya sleduyushchie:

   vyrazhenie:
    pervichnoe-vyrazhenie
  * vyrazhenie
  & vyrazhenie
  - vyrazhenie
  ! Vyrazhenie
 \^ vyrazhenie
 ++ L-znachenie
 -- L-znachenie
  L-znachenie ++
  L-znachenie --



  SIZEOF vyrazhenie
  (imya tipa) vyrazhenie
  vyrazhenie binarnaya-operaciya vyrazhenie
  vyrazhenie ? Vyrazhenie : vyrazhenie
  L-znachenie operaciya-prisvaivaniya vyrazhenie
  vyrazhenie , vyrazhenie
   pervichnoe vyrazhenie:
  identifikator
  konstanta
  stroka
  (vyrazhenie)
  pervichnoe-vyrazhenie (spisok vyrazhenij
          neob)
  pervichnoe-vyrazhenie [vyrazhenie]
  L-znachenie . Identifikator
  pervichnoe vyrazhenie -> identifikator
   L-znachenie:
  identifikator
  pervichnoe-vyrazhenie [vyrazhenie]
  L-znachenie . Identifikator
  pervichnoe-vyrazhenie -> identifikator
  * vyrazhenie
  (L-znachenie)

Operacii pervichnyh vyrazhenij

  () []  .  ->

imeyut  samyj  vysokij  prioritet  i  gruppiruyutsya   sleva
napravo. Unarnye operacii

  *  &  -  !  \^  ++  --  SIZEOF(Imya tipa)

imeyut bolee nizkij prioritet, chem operacii pervichnyh vyrazhe-
nij, no bolee vysokij, chem prioritet lyuboj binarnoj opera-
cii. |ti operacii gruppiruyutsya sprava nalevo. Vse binarnye
operacii i uslovnaya operaciya (prim. Perevod.: uslovnaya ope-
raciya gruppiruetsya sprava nalevo; eto izmenenie vneseno v
yazyk v 1978 g.) gruppiruyutsya sleva napravo i ih prioritet
ubyvaet v sleduyushchem poryadke:

  Binarnye operacii:
      *   /   %
      +   -
      >>  <<
      <   >  <=   >=
      ==  !=
      &
      \^
      \!
      &&
    \!\!
      ?:



Vse operacii prisvaivaniya imeyut odinakovyj prioritet i grup-
piruyutsya sprava nalevo.
Operacii prisvaivaniya:
  =  +=  -=  *=  ?=  %=  >>=  <<=  &=  \^=  \!=

Operaciya zapyataya imeet samyj nizkij prioritet i gruppiruetsya
sleva napravo.



Opisanie:
specifikatory-opisaniya spisok-inicializiruemyh-opisatelej
        neob;
---------------------------------------------------------

specifikatory-opisaniya:

  specifikator-tipa specifikatory-opisaniya
       neob
  specifikator-klassa-pamyati specifikatory-opisaniya
       neob
specifikator-klassa-pamyati:
 AUTO
 STATIC
 EXTERN
 REGISTER
 TYPEDEF
specifikator-tipa:
  CHAR
  SHORT
  INT
  LONG
  UNSIGNED
  FLOAT
  DOUBLE
   specifikator-struktury-ili-ob容dineniya
   opredelyayushchee-tip-imya
spisok-inicializiruemyh-opisatelej:
   inicializiruemyj-opisatel'
   inicializiruemyj-opisatel',
   spisok-inicializiruemyh-opisatelej
inicializiruemyj-opisatel'
  opisatel'-inicializator
    neob
opisatel':
   identifikator
   (opisatel')
   * opisatel'
   opisatel' ()
   opisatel' [konstantnoe vyrazhenie
         neob]



specifikator-struktury-ili-ob容dineniya:
   STRUCT spisok-opisatelej-struktury
   STRUCT identifikator \(spisok-opisanij-struktury\)
   STRUCT identifikator
   UNION \(spisok-opisanij-struktury\)
   UNION identifikator \(spisok-opisanij-struktury\)
   UNION identifikator
spisok-opisanij-struktcry:
   opisanie-struktury
   opisanie-struktury spisok-opisanij-struktury
opisanie struktury:
   specifikator-tipa spisok-opisatelej-struktury:
spisok-opisatelej-struktury
   opisatel'-struktury
   opisatel'-struktury,spisok-opisatelej-struktury
opisatel'-struktury:
   opisatel'
   opisatel': konstantnoe vyrazhenie
   :konstantnoe-vyrazhenie
inicializator:
   = vyrazhenie
   = \(spisok-inicializatora\)
   = \(spisok-inicializatora\)
spisok inicializatora:
   vyrazhenie
   spisok-inicializatora,spisok-inicializatora
   \(spisok-inicializatora\)
imya-tipa:
   specifikator-tipa abstraktnyj-opisatel'
abstraktnyj-opisatel':
   pusto
   \(abstraktnyj-opisatel'\)
   * abstraktnyj-opisatel'
   abstraktnyj-opisatel' ()
   abstraktnyj-opisatel' [konstantnoe-vyrazhenie
            neob]
opredelyayushchee-tip-imya:
  identifikator



sostavnoj-operator:
  \(spisok-opisanij      spisok-operatorov
        neob      neob\)
spisok-opisanij:
  opisanie
  opisanie spisok-opisanij
spisok-operatorov:
  operator
  operator spisok-operatorov
operator:
  sostavnoj operator
  vyrazhenie;



  IF (vyrazhenie) operator
  IF (vyrazhenie) operator ELSE operator
  WHILE (vyrazhenie) operator
  DO operator WHILE (vyrazhenie);
  FOR(vyrazhenie-1    ;vyrazhenie-2    ;vyrazhenie-3    )
      neob      neob   neob
 operator
  SWITCH (vyrazhenie) operator
  CASE konstantnoe-vyrazhenie : operator
  DEFAULT: operator
  BREAK;
  CONTINUE;
  RETURN;
  RETURN vyrazhenie;
  GOTO identifikator;
  identifikator : operator
  ;



Programma:
  vneshnee-opredelenie
  vneshnee-opredelenie programma
vneshnee-opredelenie:
  opredelenie-funkcii
  opredelenie-dannyh
opredelenie-funkcii:
  specifikator-tipa     opisatel'-funkcii telo-funkcii
        neob
opisatel'-funkcii:
  opisatel' (spisok-parametrov    )
    neob
spisok-parametrov:
   idetifikator
   identifikator , spisok-parametrov
telo-funkcii:
   spisok-opisanij-tipa operator-funkcii
operator-funkcii:
   \(spisok opisanij     spisok-operatorov\)
         neob
opredelenie dannyh:
   EXTERN  specifikator tipa    spisok
   neob   neob
  inicializiruemyh opisatelej    ;
     neob
   STATIC  specifikator tipa     spisok
   neob   neob
  inicializiruemyh opisatelej
     neob;



#DEFINE identifikator stroka-leksem
#DEFINE
#DEFINE identifikator(identifikator,...,identifikator)str
#UNDEF identifikator
#INCLUDE "imya-fajla"
#INCLUDE <imya-fajla>
#IF konstantnoe-vyrazhenie
#IFDEF identifikator
#IFNDEF identifikator
#ELSE
#ENDIF
#LINE konstanta identifikator

     Poslednie izmeneniya yazyka "C" (15 noyabrya 1978 g.)

    27. Prisvaivanie struktury
    Struktury mogut byt' prisvoeny, peredany funkciyam v ka-
chestve argumentov i vozvrashcheny funkciyam. Tipy uchastvuyushchih
operandov dolzhny ostavat'sya temi zhe samymi. Drugie pravdopo-
dobnye operatory, takie kak sravnenie na ravenstvo, ne byli
realizovany.
    V realizacii vozvrashcheniya struktur funkciyami na PDP-11
imeetsya kovarnyj defekt: esli vo vremya vozvrata proishodit
preryvanie i ta zhe samaya funkciya peenterabel'no vyzyvaetsya
vo vremya etogo preryvaniya, to znachenie vozvrashchaemoe iz per-
vogo vyzova, mozhet byt' isporcheno. |ta trudnost' mozhet voz-
niknut' tol'ko pri nalichii istinnogo preryvaniya, kak iz ope-
racionnoj sistemy, tak i iz programmy pol'zovatelya, preryva-
niya, kotoroe sushchestvenno dlya ispol'zovaniya signalov; obychnye
rekursivnye vyzovy sovershenno bezopasny.
    28. Tip perechisleniya
    Vveden novyj tip dannyh,analogichnyj skalyarnym tipam yazy-
ka paskal'. K specifikatoru-tipa v ego sintaksicheskom opisa-
nii v razdele 8.2. Prilozheniya a sleduet dobavit'

 specifikator-perechisleniya
 -------------------------

s sintaksisom


pecifikator-perechisleniya:
-------------------------
ENUM spisok-perechisleniya
     -------------------
ENUM identifikator  spisok-perechisleniya
     -------------  -------------------
ENUM identifikator
     -------------
cpisok-perechisleniya:
-------------------
perechislyaemoe
-------------
spisok-perechisleniya, perechislyaemoe
-------------------  -------------
perechislyaemoe:
--------------
  identifikator
 -------------
 identifikator = konstantnoe vyrazhenie
 -------------   ---------------------

    Rol' identifikatora v specifikatore-perechisleniya pol-
nost'yu analogichna roli yarlyka struktury v specifikato-
re-struktury; identifikator oboznachaet opredelennoe perechis-
lenie. Naprimer, opisanie

 ENUM COLOR \(RED, WHITE, BLACK, BLUE \);
 . . .
 ENUM COLOR *CP, COL;


Ob座avlyaet identifikator COLOR yarlykom perechisleniya tipa,
opisyvayushchego razlichnye cveta i zatem ob座avlyaet CP ukazatelem
na ob容kt etogo tipa, a COL - ob容ktom etogo tipa.
    Identifikatory v spiske-perechisleniya opisyvayutsya kak
konstanty i mogut poyavit'sya tam, gde trebuyutsya (po konteks-
tu) konstanty. Esli ne ispol'zuetsya vtoraya forma perechislyae-
mogo (s ravestvom =), to velichiny konstant nachinayutsya s 0 i
vozrastayut na 1 v sootvetstvii s prochteniem ih opisaniya sle-
va na pravo. Perechislyaemoe s prisvoeniem = pridaet sootvets-
tvuyushchemu identifikatoru ukazannuyu velichinu; posleduyushchie
identifikatory prodolzhayut progressiyu ot pripisannoj velichi-
ny.
    Vse yarlyki perechisleniya i konstanty mogut byt' razlichny-
mi i nepohozhimi na yarlyki i chleny struktur dazhe pri uslovii
ispol'zovaniya odnogo i togo zhe mnozhestva identifikatorov.
    Ob容kty dannogo tipa perechisleniya rassmatrivayutsya kak
ob容kty, imeyushchie tip, otlichnyj ot lyubyh tipov i kontroliruyu-
shchaya programma LINT soobshchaet ob oshibkah nesootvetstviya tipov.
V realizacii na PDP-11 so vsemi perechislyaemymi peremennymi
operiruyut tak, kak esli by oni imeli tip INT.
    29. Tablica izobrazhenij nepechatnyh simvolov yazyka "C".
    V dannoj tablice privedeny izobrazheniya nekotoryh simvo-
lov (figurnye skobki i t.d.) yazyka "C", kotoryh mozhet ne
okazat'sya v znakovom nabore displeya ili pechatayushchego ustrojs-
tva.
-------------------------------------------------
!    Znachenie          !   Izobrazhenie   **     !
!                      !    V tekste            !
-------------------------------------------------
!  Figurnaya otkryvayushchayasya  !                    !
!    Skobka                !        \(          !
!                          !                    !
-------------------------------------------------
!  Figurnaya zakryvayushchayasya  !                    !
!    Skobka                !        \)          !
!                          !                    !
-------------------------------------------------
! Vertikal'naya             !                    !
!    CHerta                 !        \!          !
!                          !                    !
-------------------------------------------------
!                          !                    !
!   Apostorof              !        \'          !
!                          !                    !
-------------------------------------------------
!    Volnistaya             !                    !
!      CHerta               !        \^          !
!                          !                    !
-------------------------------------------------

    ** P_r_i_m_e_ch_a_n_i_e:
    Izobrazheniya privedeny dlya operacionoj sistemy UNIX. Pri
rabote kompilyatora "C" pod upravleniem lyuboj drugoj operaci-
onnoj sistemy, neobhodimo vospol'zovat'sya sootvetstvuyushchim
rukovodstvom dlya dannoj sistemy.

Last-modified: Sun, 31 Aug 2003 05:43:18 GMT
Ocenite etot tekst: