Denis Gaev. Kserion: yazyk i tehnologiya programmirovaniya Versiya ot: 03.05.2002 Napisat' avtoru: mailto:dgaev@mail.ru Annotaciya Nastoyashchij dokument predstavlyaet soboj kratkij neformal'nyj obzor osnovnyh vozmozhnostej yazyka programmirovaniya Kserion (po sostoyaniyu na vesnu 2002 g.). On ne pretenduet na formal'nuyu strogost' i polnotu izlozheniya i ostavlyaet za predelami rassmotreniya mnogie tonkosti i "temnye mesta" yazyka. Ne rassmotreny standartnye yazykovye biblioteki, sovershenno ne zatronuty realizacionnye aspekty Kserion-sistemy i mnogoe drugoe. Tem ne menee, on predstavlyaetsya avtoram vpolne udovletvoritel'nym v kachestve nachal'nogo oznakomitel'nogo kursa. Predpolagaetsya, chto chitatel' imeet predstavlenie o bazovyh principah ob®ektno-orientirovannogo programmirovaniya, i v minimal'noj stepeni znakom s kakim-libo iz sovremennyh OO-yazykov (predpochtitel'no, C++ ili Java). Soderzhanie o Vvedenie o Leksicheskij sostav yazyka o Primitivnye tipy i operacii nad nimi o Massivy, ili vektornye tipy o Ukazateli i ssylki o Funkcionaly i funkcii o Drugie raznovidnosti opisanij o Instrukcii i potok upravleniya o Ob®ekty i klassy o Opredelenie operacij o Import i eksport. Pragmaty o Perspektivnye vozmozhnosti yazyka o Zaklyuchenie Kserion: yazyk i tehnologiya programmirovaniya Esli kserion brosit' v rasplavlennuyu med', poluchitsya serebro. Esli v serebro, to -- zoloto. Esli v nikel', to -- palladij. Esli v palladij, to -- platina... A. Lazarchuk, M. Uspenskij "Posmotri v glaza chudovishch" Vvedenie Kserion -- eto sovremennyj, polnofunkcional'nyj ob®ektno-orientirovannyj yazyk programmirovaniya. Pri razrabotke yazyka osnovnymi istochnikami idej posluzhili: C i C++, Paskal' (vklyuchaya ego ob®ektno-orientirovannye dialekty, takie kak Delphi) i Java. Pomimo perechislennyh, v opredelennoj stepeni na yazyk takzhe okazali vliyanie Algol-68, Simula, BCPL, CLU, Eiffel i nekotorye drugie menee izvestnye yazyki. Kserion yavlyaetsya gibridnym (ili procedurno-ob®ektnym) yazykom programmirovaniya, napominaya v etom otnoshenii C++ i (v men'shej stepeni) Java. On ne yavlyaetsya "chistym" ob®ektno-orientirovannym yazykom, podobnym SmallTalk ili Actor: v yazyke ne sushchestvuet ponyatij "metaklassa", "metodov klassov" i mehanizmov dlya dinamicheskogo sozdaniya klassov vo vremya vypolneniya programmy. Bol'shaya chast' atributov dlya ob®ektov klassov zhestko zadaetsya vo vremya kompilyacii i ne mozhet byt' izmenena vo vremya vypolneniya programmy. Kserion -- strogo tipizovannyj yazyk. |to oznachaet, chto bol'shaya chast' proverok tipov osushchestvlyaetsya vo vremya kompilyacii, i lish' otdel'nye specificheskie atributy ob®ektnoj tipizacii mogut proveryat'sya pri vypolnenii. Sistema tipizacii yazyka osnovana na chetkom razdelenii, provodimym mezhdu primitivnymi (prostymi), proizvodnymi i ob®ektnymi tipami dannyh. Kserion ne yavlyaetsya yazykom "sverhvysokogo urovnya", t.e. ne soderzhit v yavnom vide takih vysokourovnevyh struktur dannyh, kak spiski, kortezhi, mnozhestva, associativnye massivy i t.p.. Odnako, vse perechislennye mehanizmy mogut byt' effektivno realizovany sredstvami samogo yazyka (i ih realizaciya, bezuslovno, budut predostavlyat'sya standartnymi bibliotekami). Kserion obladaet ryadom vazhnyh osobennostej, specifichnyh dlya etogo yazyka ili realizovannyh v nem luchshe, chem vo mnogih drugih yazykah programmirovaniya. Dlya yazyka v celom harakterny: -- moshchnost' i gibkost'. V yazyke prisutstvuyut prakticheski vse vozmozhnosti, harakternye dlya tradicionnyh procedurnyh yazykov, takih kak C++ ili Paskal', bez proizvol'nyh ogranichenij na ih ispol'zovanie, harakternyh, naprimer, dlya Java. Pri etom mnogie iz etih vozmozhnostej stanovyatsya namnogo moshchnee i potencial'no cennee. Naprimer, v Kserione dopustimo dinamicheskoe opredelenie razmerov massivov, proizvol'nye inicializatory dlya massivov, argumentov funkcij i komponent klassov i mnogoe drugoe. -- ierarhicheskomu podhodu k razrabotke i realizacii programmy vo mnogom sodejstvuet prinyatyj v yazyke princip lokal'nosti. Lyubuyu Kserion-programmu mozhno rassmatrivat' kak ierarhiyu vlozhennyh drug v druga oblastej dejstviya, a lyuboe opisanie (deklaraciya) imeet lokal'nyj harakter, t.e. dejstvuet tol'ko v predelah samoj vnutrennej iz soderzhashchih ee oblastej dejstviya. Samoj vneshnej vsegda yavlyaetsya global'naya oblast' dejstviya, no ee ispol'zovanie luchshe maksimal'no ogranichit'. Pravil'nyj podhod k ispol'zovaniyu principa lokal'nosti, predpolagayushchij opisanie peremennyh, funkcij, tipov dannyh, klassov i t.p. tol'ko tam, gde oni nuzhny, yavlyaetsya vazhnym faktorom uluchsheniya kak nadezhnosti, tak i effektivnosti programmy. -- voprosam effektivnosti pri razrabotke yazyka udelyalos' osoboe vnimanie. Tak, kontrol' nad takimi principial'nymi dlya effektivnosti momentami, kak raspredelenie pamyati, nahoditsya v rukah razrabotchika. Nekotorye sredstva yazyka, takie, kak specificheskie atributy ukazatel'nyh tipov static i strict, dayut programmistu yavnuyu vozmozhnost' uluchshat' effektivnost' programmnogo koda za schet ego obshchnosti. Ne menee vazhno nalichie agregatnyh operacij prisvaivaniya i sravneniya dlya vektornyh i ob®ektnyh tipov. -- nadezhnost'. YAzyk yavlyaetsya nadezhnym v tom smysle, chto na nem nevozmozhno napisat' "sbojnyj" kod. Naprimer, obespechivaetsya polnaya proverka diapazona pri obrashcheniyah k massivam, proverka validnosti ispol'zuemyh ukazatelej na ob®ekty dannyh i ssylok na funkcii, predusmotren nadezhnyj mehanizm preobrazovaniya mezhdu rodstvennymi ob®ektnymi tipami i t.p. Vse oshibki podobnogo roda, vyyavlennye pri vypolnenii programmy, vozbuzhdayut isklyuchitel'nye situacii, kotorye mogut byt' perehvacheny i korrektno obrabotany. -- posledovatel'nost' i yasnost'. Predpolozhitel'no, mnogie konstrukcii yazyka imeyut bolee posledovatel'nyj i kompaktnyj sintaksis, chem ih analogi v Paskale, Java i C++. Mozhno skazat', chto dlya sintaksisa yazyka harakterna bol'shaya "ortogonal'nost'", chem dlya mnogih drugih yazykov. Esli nekaya konstrukciya yazyka sintaksicheski pravil'na, ona pochti vsegda imeet kakuyu-to razumnuyu semantiku i mozhet byt' polezna v opredelennoj situacii. Krome togo, yazyk minimiziruet ili polnost'yu isklyuchaet neobhodimost' v dubliruyushchih opisaniyah: kazhdyj ob®ekt yazyka dolzhen byt' opisan tol'ko odin raz. Mnogie chasto upotreblyaemye yazykovye konstrukcii mogut byt' zapisany bolee kompaktno. Aktivnoe ispol'zovanie makroopredelenij let takzhe mozhet znachitel'no "uplotnit'" programmu (vozmozhno, v ushcherb ee ponyatnosti). -- perenosimost' realizacii -- odin iz samyh vazhnyh aspektov yazyka. Rezul'tatom raboty kompilyatora yavlyaetsya vnutrennij kod Kserion-sistemy (zdes' ne opisannyj). |tot kod mozhet byt' vypolnen v rezhime interpretacii s dostatochno vysokoj effektivnost'yu na lyuboj 32-bitovoj platforme, no orientirovan v osnovnom na translyaciyu v mashinnyj kod celevogo processora. Leksicheskij sostav yazyka Na samom bazovom urovne lyubaya Kserion-programma mozhet rassmatrivat'sya prosto kak potok leksem. Poslednie podrazdelyayutsya na: ogranichiteli, razdeliteli i znaki operacij, klyuchevye slova, identifikatory, literaly i kommentarii. V promezhutkah mezhdu leksemami mogut prisutstvovat' probel'nye simvoly (probely, koncy strok i bol'shinstvo upravlyayushchih simvolov), ignoriruemye pri kompilyacii. V tom meste, gde dopustim probel'nyj simvol, vsegda mozhet vstretit'sya i kommentarij. Kommentarii opredelyayutsya dvumya sposobami: · kak (nepustaya) posledovatel'nost' simvolov, ogranichennaya dvumya simvolami ‘!'; · kak posledovatel'nost' ot dvuh simvolov ‘!' do konca tekushchej stroki. V sluchae, kogda primenyayutsya kommentarii pervogo tipa, rekomenduetsya ispol'zovat' vnutri nih paru dopolnitel'nyh skobok ("()","[]","{}","<>" ili chto-nibud' v etom rode), chtoby nachalo i konec kommentariya legko razlichalis'. Vot primery: !! dopustimyj kommentarij ! i eto tozhe ... ! !{ no takaya forma predpochtitel'nej }! Identifikatory -- eto simvolicheskie imena, kotorye imeyut vse ob®ekty (v shirokom smysle) yazyka: peremennye, konstanty, tipy dannyh, funkcii, klassy, makroopredeleniya, metki i pr. K identifikatoram pred®yavlyayutsya prakticheski te zhe trebovaniya, chto i v C: eto posledovatel'nosti latinskih bukv, desyatichnyh cifr i znakov podcherkivaniya, nachinayushchiesya ne s cifry. Kak minimum pervye 127 simvolov identifikatora yavlyayutsya znachashchimi; zaglavnye i strochnye bukvy razlichayutsya. Pomimo etogo, identifikator dolzhen otlichat'sya ot klyuchevogo slova. Sleduyushchie identifikatory yavlyayutsya klyuchevymi slovami: abstract, alloc, assert bool, break case, char, class, conceal, const, constructor, continue destructor, do, double else, enum, export false, float, for goto if, import, inline, instate, int, interface keyword label, let, limited, long, loop mediator native, nil opdef package, pragma, private, protected qual, quad realloc, return shared, short, static, strict, switch tiny, this, true, type u_int, u_long, u_short, u_tiny, unless, until virtual, void w_char, with, while Pomimo etogo, zakonnym identifikatorom yavlyaetsya posledovatel'nost' simvolov, zaklyuchennaya v obratnye kavychki. Vnutri kavychek dopustimy proizvol'nye simvoly, v t.ch. nacional'nye, upravlyayushchie i probel'nye i t.p. Sami kavychki -- ogranichiteli, a ne chast' identifikatora (alpha i `alpha` -- eto odin i tot zhe identifikator). !! primery identifikatorov: x; ABACUS: file_12; ActiveApplet; `Predel'nyj dopusk` Dlya predstavleniya znachenij bol'shinstva primitivnyh tipov v yazyke est' literaly. Celye literaly po umolchaniyu predstavlyayut soboj posledovatel'nosti desyatichnyh cifr. Literaly mogut byt' ne tol'ko desyatichnymi: prefiks ‘$o' ukazyvaet na vos'merichnyj literal, ‘$h' (ili ‘$x') -- na shestnadcaterichnyj, ‘$b' -- na dvoichnyj. Vse celye literaly mogut takzhe imet' suffiks, yavno zadayushchij ih tip (sm. sleduyushchij razdel): 't' (dlya u_tiny), 's' (dlya u_short), 'i', (dlya u_int, po umolchaniyu), 'l' (dlya u_long). Literaly s plavayushchej tochkoj opredeleny kak v C/C++, no takzhe mogut imet' yavnyj suffiks tipa: 'f' (dlya float, po umolchaniyu), 'd' (dlya double), 'q' (dlya quad). Simvol'nye literaly (tip char) ogranicheny prostymi kavychkami. Strokovye literaly (tip char [], massiv iz simvolov) ogranicheny dvojnymi kavychkami. V otlichie ot C/C++, oni mogut soderzhat' fizicheskie upravlyayushchie simvoly (takie, kak perevod stroki) i ne zavershayutsya avtomaticheski nulevym bajtom (poslednee ne trebuetsya, t.k. bibliotechnye sredstva yazyka opredelyayut dlinu massivov po drugomu). Vot primery literalov (v skobkah dany ih tipy): 37t; !! 37 (u_tiny) 37s; !! 37 (u_short) $b100101; !! 37 (u_int) $o45; !! to zhe, chto i vyshe $x25; !! to zhe, chto i vyshe 3.14159; !! 3.14159 (float) 3.14159d; !! 3.14159 (double) true; !! istina (bool) false; !! lozh' (bool) '@'; !! simvol ‘@' (char) "|to stroka"; !! stroka (char []) "|to -- eshche odin primer stroki, kotoraya zajmet neskol'ko strok pri vyvode" !! eshche odna stroka (char []) Primitivnye tipy i operacii nad nimi Primitivnye tipy dannyh igrayut fundamental'nuyu rol' v sisteme tipov yazyka, poskol'ku oni yavlyayutsya temi prostejshimi "kirpichikami", iz kotoryh stroitsya vse ostal'noe. Ih mozhno razdelit' na chislovye, simvol'nye, logicheskij i pustoj. V svoyu ochered', chislovye tipy predstavleny vosem'yu celochislennymi i tremya "plavayushchimi" tipami. Celochislennye tipy dannyh -- eto chetyre vida znachenij, imeyushchih znak (tiny, short, int, long) i ih bezznakovye analogi (u_tiny, u_short, u_int, u_long). "Znakovye" znacheniya predstavlyayut celye chisla v dopolnitel'nom kode i razlichayutsya razryadnost'yu: tip tiny obespechivaet tol'ko 8 dvoichnyh razryadov, short -- 16, int -- 32 i long -- 64. Sootvetstvuyushchie im tipy bez znaka imeyut tu zhe razryadnost', no predstavlyayut tol'ko neotricatel'nye chisla. Tri tipa predstavlyayut znacheniya s plavayushchej tochkoj (v sootvetstvii so standartom IEEE-754): float -- plavayushchee so standartnoj tochnost'yu, double -- s dvojnoj tochnost'yu i quad (zarezervirovan na budushchee, v nastoyashchee vremya s tochki zreniya realizacii neotlichim ot double). Dva prostyh tipa prednaznacheny dlya raboty s simvolami: tip char predstavlyaet 8-bitovye simvoly nabora ASCII/ISO, a tip w_char -- 16-bitovye nabora Unicode. Logicheskij (bulevskij) tip bool predstavlyaet lish' dva logicheskih znacheniya: istinu (true) i lozh' (false). V zavershenie upomyanem tip void (pustoj), voobshche ne imeyushchij znachenij i prednaznachennyj, v osnovnom, dlya opisaniya funkcij-procedur, ne vozvrashchayushchih kakogo-libo rezul'tata. Lyubaya peremennaya v yazyke dolzhna byt' opisana (deklarirovana) pered ispol'zovaniem. Dlya prostejshih tipov sintaksis deklaracij prost (i, v osnovnom, C-podoben): !! I, J, K -- bezznakovye celye peremennye u_int I = 1, J = 2, K = 3; !! X, Y, Z -- plavayushchie peremennye standartnoj tochnosti float X, Y, Z = 0.001; !! DONE -- logicheskaya peremennaya bool DONE = false Iz etih primerov takzhe vidno, chto opisanie peremennoj mozhet soprovozhdat'sya ee inicializaciej (i eto rekomenduemaya praktika). Esli peremennaya primitivnogo tipa ne inicializirovana yavno, ona budet soderzhat' neopredelennoe znachenie (musor). Naryadu s obychnymi peremennymi, v yazyke prisutstvuyut konstanty -- peremennye, znachenie kotoryh posle opisaniya ne mozhet byt' izmeneno. Opisanie konstanty predvaryaetsya klyuchevym slovom const. Ponyatno, chto konstanta nepremenno dolzhna byt' inicializirovana: !! space -- simvol'naya konstanta char const space = ‘ ‘; !! factor -- plavayushchaya konstanta float const factor = 2 * PI * PI; !! median -- celaya konstanta int const median = (low + high) // 2 Naryadu s konstantnost'yu, vazhnym atributom peremennoj yavlyaetsya rezhim razmeshcheniya, ukazyvayushchij kakim imenno obrazom peremennaya budet sozdana, i skol'ko vremeni ona prosushchestvuet. Po umolchaniyu, rezhim razmeshcheniya opredelyaet kontekst opisaniya: v zavisimosti ot togo mesta, gde vstretilos' opisanie, peremennaya mozhet byt' ob®yavlena global'noj, lokal'noj (v funkcii) ili komponentnoj (v klasse). Odnako, lyubaya peremennaya ili konstanta mozhet byt' yavno opisana kak staticheskaya (static), t.e. imeyushchaya vremya zhizni, sovpadayushchee so vremenem vypolneniya programmy: u_int i, j, static counter = 0 Zamet'te, chto klyuchevoe slovo static -- atribut opisyvaemoj peremennoj (v dannom sluchae, counter), a ne opisaniya v celom, kak prinyato v C. Dlya primitivnyh tipov dannyh opredeleno mnozhestvo operacij. Podrobno rassmatrivat' sistemu operacij my ne budem, tak kak ona vo mnogom pozaimstvovana iz C. Otmetim lish' naibolee sushchestvennye razlichiya. Tak, v otlichie ot C, v Kserione razlichayutsya operacii plavayushchego ('/') i celochislennogo ('//') deleniya (a vzyatie ostatka ot deleniya vypolnyaetsya operaciej '-/'). Naryadu s privychnymi dlya C-programmista operaciyami bitovyh sdvigov vpravo i vlevo ('<<' i '>>'), sushchestvuyut takzhe binarnye operacii bitovogo vrashcheniya ('<.<' i '>.>'), i unarnaya operaciya transpozicii ('>.<'). (Poslednyuyu operaciyu mozhno opisat' kak "perestanovku polovinok": dlya znacheniya tipa u_tiny ona menyaet mestami starshie i mladshie 4 bita, dlya u_short - starshie i mladshie 8 bitov i t.d.) Razumeetsya, predusmotreny privychnye dlya C-programmista operacii inkrementa ('++') i dekrementa ('-- ') v prefiksnoj i postfiksnoj forme. Vse operacii sravneniya vozvrashchayut znachenie tipa bool. Dlya vseh tipov opredeleny operacii sravneniya na ravenstvo ('--') i neravenstvo ('<>'), a dlya mnogih tipov dannyh opredeleny takzhe sravneniya na uporyadochennost' ('<', '<=', '>', '>='). V chastnosti, vse primitivnye tipy yavlyayutsya uporyadochennymi. (Dlya chislovyh tipov eto samoochevidno, simvol'nye tipy uporyadocheny v sootvetstvii s vnutrennej kodirovkoj, a dlya logicheskih znachenij prinyato false < true). Krome togo, dlya vseh primitivnyh tipov opredeleny binarnye operacii "maksimum" ('?>') i "minimum" ('?<'), vozvrashchayushchie, sootvetstvenno, bol'shij i men'shij iz svoih operandov. Obychnye binarnye arifmetiko-logicheskie operacii "i" ('&'), "ili" ('|') i "isklyuchayushchee ili" ('~') primenimy kak k logicheskim, tak i k celym znacheniyam (v poslednem sluchae oni vypolnyayutsya pobitno). |to zhe spravedlivo i dlya unarnoj operacii "ne" ('~'). Tol'ko dlya tipa bool opredeleny uslovno-logicheskie operacii "i" i "ili" ('&&' i '||'), kotorye, kak i v C, po vozmozhnosti izbegayut vychisleniya vtorogo operanda. Est' i C-podobnaya ternarnaya operaciya vybora: X ? Y : Z ponimaetsya kak "esli X (vyrazhenie tipa bool), to Y, inache Z". Nakonec, operaciya prisvaivaniya ('=') kak i v C vozvrashchaet prisvoennoe znachenie v kachestve rezul'tata (ona opredelena ne tol'ko dlya primitivnyh tipov, no ob etom pozzhe). Est' takzhe operacii prisvaivaniya, sovmeshchennogo s bol'shinstvom binarnyh operacij, takie kak '+=', '-=', '*=', '/=' i t.p. V otlichie ot C i Java, v yazyke otsutstvuet binarnaya operaciya ',' (posledovatel'nost'). No vmesto nee imeetsya bolee moshchnoe sredstvo: vyrazhenie mozhet dopolnyat'sya vstroennym blokom koda, vypolnyayushchimsya do ili posle ego vychisleniya: !! "vstroennyj blok", prefiksnaya forma ({ STMT_LIST } EXPR); !! "vstroennyj blok", postfiksnaya forma (EXPR { STMT_LIST }) V obeih formah eto vyrazhenie vozvrashchaet znachenie vyrazheniya EXPR. Odnako, pri etom eshche i vypolnyayutsya instrukcii iz STMT_LIST -- do vychisleniya EXPR (v prefiksnoj forme) ili posle nego (v postfiksnoj). K sozhaleniyu, blok instrukcij ne mozhet napryamuyu vernut' znachenie, kotoroe mozhno bylo by ispol'zovat' v vyrazhenii. Sistema operacij yazyka vsem perechislennym ne ogranichivaetsya, no operacii, opredelennye dlya proizvodnyh i ob®ektnyh tipov my rassmotrim chut' pozzhe. Nakonec, v yazyke imeyutsya binarnye operacii vvoda ('<:') i vyvoda (':>'), kotorye neobychny tem, chto voobshche ne imeyut nikakoj predopredelennoj semantiki, i prednaznacheny isklyuchitel'no dlya pereopredeleniya (naprimer, dlya operacij vvoda-vyvoda v sistemnyh bibliotekah). Sistema prioritetov neskol'ko otlichaetsya ot prinyatoj v C. Skazhem, operacii sdvigov i vrashcheniya schitayutsya mul'tiplikativnymi, t.e. imeyut tot zhe prioritetnyj uroven', chto i umnozhenie i delenie. Prioritet logicheskih i uslovno-logicheskih operacij odinakov (i bolee nizok, chem u sravnenij). Vse binarnye operacii, krome operacij prisvaivaniya, imeyut levuyu associativnost'. Znacheniya primitivnyh tipov mogut neyavno preobrazovyvat'sya drug v druga, no pravila etih preobrazovanij prinyaty bolee zhestkie, nezheli v C. Dopustimy lish' te preobrazovaniya, kotorye ne privodyat k potere informacii. Tak, mladshie celochislennye tipy mogut obobshchat'sya do starshih (tinyshortintlong), tak zhe, kak i vse plavayushchie (floatdoublequad) i vse simvol'nye (charw_char), a celochislennye znacheniya neyavno obobshchayutsya do plavayushchih. Drugie neyavnye preobrazovaniya zapreshcheny: v chastnosti, nel'zya neyavno ispol'zovat' simvol'nye i logicheskie znacheniya v kachestve celyh (i naoborot). Bol'shinstvo operacij takzhe ne pozvolyayut smeshivat' celye operandy so znakom i bez znaka: oni dolzhny byt' privedeny k edinoj znakovosti vo izbezhanie vozmozhnoj neodnoznachnosti. Kogda neyavnye preobrazovaniya ne rabotayut, mozhno pribegnut' k operacii yavnogo privedeniya tipov, imeyushchej takoj vid: :TYPE EXPR !! preobrazovat' vyrazhenie EXPR k tipu TYPE Semantika podobnogo preobrazovaniya takzhe ne tait v sebe osobyh syurprizov: plavayushchie znacheniya preobrazuyutsya v celye putem otbrasyvaniya drobnoj chasti, simvol'nye v chislovye -- v sootvetstvii so svoej kodirovkoj, a logicheskie znacheniya false i true schitayutsya ekvivalentnymi 0 i -1. Ochen' vazhno zametit', chto eta operaciya preobrazovaniya opredelena tol'ko dlya primitivnyh tipov, i k proizvodnym, v otlichie ot C, ona neprimenima. Massivy (vektornye tipy) Massivy -- odnorodnye nabory znachenij edinogo tipa, obespechivayushchie proizvol'nyj dostup k lyubomu iz etih znachenij (elementov) po celochislennomu indeksu -- eto odno iz principial'no vazhnyh sredstv yazyka. V otlichie ot Java i mnogih drugih yazykov, massivy ne yavlyayutsya ob®ektami v smysle OOP. Oni mogut imet' te zhe svojstva i atributy (rezhim razmeshcheniya, konstantnost' i pr.), chto i peremennye primitivnyh tipov. Vot primery opisanij massivov: !! intvec -- massiv iz LENGTH celyh int [LENGTH] intvec; !! text -- matrica simvolov, (HEIGHT strok) * (WIDTH stolbcov) char [WIDTH][HEIGHT] text Obratite vnimanie na to, chto sintaksis opisaniya massivov -- prefiksnyj: konstrukciya vida [SIZE] nazyvaetsya prefiksom opisaniya (deklaratorom) massiva. Ona oznachaet, chto tip deklariruemyh dalee ob®ektov menyaetsya s TYPE na TYPE [SIZE] (massiv iz SIZE elementov tipa TYPE). Vkladyvaya vektornye deklaratory drug v druga, mozhno opisyvat' dvuh- i bolee mernye massivy. Strogo govorya, ponyatie "mnogomernyj massiv" v yazyke otsutstvuet -- ih s uspehom zamenyayut massivy, sostoyashchie iz massivov, i tak dalee. Imenno eto my budem podrazumevat', govorya ob n-mernyh massivah (pri etom chislo n my budem nazyvat' razmernost'yu, ili rangom massiva). Odnako, nikakimi special'nymi svojstvami mnogomernye massivy ne obladayut (t.e. semantika vseh operacij nad nimi vyvoditsya iz semantiki operacij nad odnomernymi massivami). Zametim, chto prefiksnyj sintaksis v opisaniyah massivov -- eto ne isklyuchenie. Vse proizvodnye tipy yazyka vvodyatsya s pomoshch'yu analogichnyh prefiksnyh konstrukcij, blagodarya chemu dazhe samye slozhnye i zaputannye opisaniya chitayutsya dostatochno legko i edinoobrazno -- sprava nalevo (ot peremennoj ili drugogo opisyvaemogo ob®ekta k "kornyu" opisaniya). Kak i v C, prefiks(y) opisanij imeyut bolee vysokij prioritet, chem zapyataya, razdelyayushchaya deklaracii v spiske: int [10] aa, bb !! aa -- massiv iz 10 celyh, bb -- celoe Odnako, chasto neobhodimo opisat' neskol'ko massivov odinakovoj razmernosti. Togda prefiks massiva (kak i lyuboj obshchij prefiks proizvodnogo tipa) mozhno "vynesti za skobki" (figurnye). |tot priem, nazyvaemyj faktorizaciej, ochen' uproshchaet slozhnye opisaniya: int [10] { aa, bb } !! aa i bb -- massivy iz 10 celyh Faktorizaciyu mozhno primenyat' i rekursivno: int i, [10] { v, [20] { vv, [30] vvv } }; !! bolee gromozdkaya forma predydushchego opisaniya: int i, [10] v, [10][20] vv, [10][20][30] vvv V kachestve razmera massiva trebuetsya nekoe vyrazhenie tipa u_int. Na nego ne nakladyvaetsya drugih ogranichenij -- v chastnosti, ne trebuetsya, chtoby ono bylo vychislyaemym vo vremya kompilyacii. V obshchem sluchae razmer massiva opredelyaetsya tol'ko pri vypolnenii programmy. Odnako, on fiksirovan v tom smysle, chto vychislyaetsya odin raz, posle chego uzhe ne mozhet izmenit'sya (v yazyke net nastoyashchih gibkih massivov, razmery kotoryh mozhno menyat' "na letu"). V zhestkoj sisteme tipov yazyka razmery massivov rassmatrivayutsya kak osobyj sluchaj: vse, chto svyazano s nimi, obychno proveryaetsya tol'ko pri vypolnenii programmy. Massiv mozhet dazhe okazat'sya pustym, t.k. nulevoj razmer ne schitaetsya oshibkoj. Dlya massivov opredelen ryad operacij. Tak, poskol'ku razmer massiva vsegda izvesten kompilyatoru i ispolnyayushchej sisteme yazyka, ego netrudno uznat' s pomoshch'yu unarnoj postfiksnoj operacii '#'. Dlya peremennyh, opisannyh vyshe: intvec#; !! vozvrashchaet znachenie LENGTH (u_int) text#; !! vozvrashchaet znachenie HEIGHT (u_int) vvv# !! vozvrashchaet 30 (u_int) CHasto rabota s massivom osushchestvlyaetsya poelementno. Binarnaya operaciya indeksirovaniya pozvolyaet v lyuboj moment poluchit' dostup k lyubomu elementu massiva. Tak zhe, kak i v C, otschet indeksov vedetsya s nulya: !! pervyj element massiva intvec (int) intvec [0]; !! poslednij element massiva intvec (int) intvec [LENGTH - 1]; !! "verhnyaya" stroka matricy text (char [WIDTH]) text [0]; !! "nizhnyaya" stroka matricy text (char [WIDTH]) text [HEIGHT - 1]; !! "levyj verhnij" simvol matricy text (char) text [0][0]; !! "pravyj nizhnij" simvol matricy text (char) text [HEIGHT - 1][WIDTH - 1] Operaciya indeksirovaniya vsegda proveryaet korrektnost' indeksa, ne pozvolyaya obratit'sya k nesushchestvuyushchemu elementu. Esli pri vychislenii A [I] ne soblyudaetsya uslovie I < A#, normal'noe vypolnenie programmy prervetsya i budet vozbuzhdena isklyuchitel'naya situaciya (ArraySizeException). Zametim takzhe, chto hotya pri opisanii massiva my ispol'zovali prefiksnyj sintaksis, dlya dostupa k elementu ispol'zuetsya privychnaya postfiksnaya notaciya. (Izvestnyj po yazyku C princip "deklaraciya imitiruet ispol'zovanie" v Kserione veren "s tochnost'yu do naoborot": opisateli dlya massivov, ukazatelej i funkcionalov ispol'zuyut prefiksnyj sintaksis, no sootvetstvuyushchie operacii nad etimi tipami (indeksirovanie, razymenovanie, vyzov funkcii) -- tol'ko postfiksnyj). Vozmozhen ne tol'ko poelementnyj dostup k massivam: v yazyke opredelen ryad agregatnyh operacij, pozvolyayushchih rabotat' s massivami, kak s edinym celym. No prezhde zametim, chto tam, gde mozhno rabotat' s massivom, dopuskaetsya rabota i s lyubym ego nepreryvnym fragmentom (otrezkom). Ternarnaya operaciya vzyatiya otrezka -- A [FROM..TO] -- vozvrashchaet otrezok massiva A ot (vklyuchitel'no) elementa s indeksom FROM do (ne vklyuchaya) elementa s indeksom TO (t.e. spravedlivo tozhdestvo: A [FROM..TO]# -- TO -- FROM). Razumeetsya, korrektnost' indeksov proveryaetsya (esli narusheno uslovie FROM <= TO && TO <= A#, vozbuzhdaetsya znakomoe nam isklyuchenie ArraySizeException). Vprochem, otrezok nulevoj dliny dopustim, takzhe kak i massiv. V [0 .. N] !! otrezok: nachal'nye N elementov massiva V V [V#-N .. V#] !! otrezok: konechnye N elementov massiva V V otlichie ot indeksirovaniya, operaciya polucheniya otrezka nikogda ne ponizhaet rang massiva: rezul'tat vsegda imeet tu zhe razmernost', chto i operand. Otrezok dlinoj 1 -- eto massiv dliny 1, a ne odin element. Vsledstvie etogo, pri rabote s mnogomernym massivom mozhno poluchit' otrezok tol'ko po samomu vneshnemu izmereniyu, t.k. vse vnutrennie dlya etoj operacii nedostupny. Nakonec, otmetim, chto operacii vzyatiya indeksa i otrezka sohranyayut takie osobennosti svoego operanda, kak konstantnost' i L-kontekst (t.e. esli massiv konstanten, to lyuboj ego element takzhe yavlyaetsya konstantoj i t.p.). Zavershaya razgovor ob indeksirovanii massivov, sleduet upomyanut' osobuyu operaciyu "pustoj indeks". Ona polezna v osnovnom dlya polucheniya vnutrennih razmerov mnogomernyh massivov: text [0]# !! vozvrashchaet WIDTH text []# !! to zhe samoe Vtoraya zapis' nemnogo koroche, a glavnoe -- yavno podcherkivaet, chto operaciya indeksirovaniya zdes' nosit fiktivnyj harakter, t.k. nam nuzhen ne opredelennyj element massiva text, a lish' dostup k obshchemu tipu ego elementov. Rezul'tat, vydavaemyj operaciej [] -- t.n. neopredelennoe vyrazhenie, imeyushchee tip, no ne znachenie. Podrobnee o semantike neopredelennyh vyrazhenij, i sluchayah, kogda oni mogut potrebovat'sya, my pogovorim pozzhe. Dlya massivov, kak i dlya primitivnyh tipov, dostupno prisvaivanie: float [25] { VA, VB }; VA = VB !! skopirovat' vse elementy iz massiva VA v massiv VB Dlya prisvaivaniya massivov trebuetsya, chtoby tipy ih elementov tochno sovpadali (t.k. neyavnye privedeniya, dostupnye dlya primitivnyh tipov, ne obobshchayutsya na massivy iz nih). Pomimo etogo, dolzhny sovpadat' i razmery prisvaivaemyh massivov (po vsem izmereniyam, esli oni mnogomernye). Zamet'te, chto v privedennom sluchae ih sovpadenie ochevidno, i potomu proverka perioda vypolneniya budet opushchena. Odnako, vot primer bolee obshchej situacii: char [SIZE1] str1, [SIZE2] str2; str1 = str2 Zdes' pered prisvaivaniem proizojdet proverka usloviya SIZE1 -- SIZE2, i, esli ono okazhetsya lozhnym, budet vozbuzhdeno vse to zhe isklyuchenie ArraySizeException. Ne menee vazhno to, chto massivu mozhno prisvoit' skalyar. V etom sluchae ego znachenie (vychislennoe odin raz) budet "razmnozheno" i prisvoeno vsem elementam massiva. |tot priem nazyvaetsya vektorizaciej i obobshchaetsya na mnogomernye massivy: massivu mozhet byt' prisvoen massiv men'shego ranga -- pri etom on "razmnozhaetsya" po odnomu ili bol'shemu chislu izmerenij. Kak i pri obychnom prisvaivanii, trebuetsya identichnost' bazovyh tipov massivov, a vse "vnutrennie" razmery obyazatel'no budut provereny na ravenstvo: !{ Prisvaivaet str1 vsem HEIGHT strokam matricy text (predvaritel'no ubedivshis', chto text []# -- str1#, t.e. WIDTH -- SIZE1) }! text = str1 Poryadok prisvaivaniya elementov v massive schitaetsya neopredelennym. CHasto eto dejstvitel'no ne principial'no, odnako pri prisvaivanii perekryvayushchihsya otrezkov odnogo i togo zhe massiva on okazyvaetsya sushchestvennym. Poetomu sushchestvuyut dve special'nye formy operacii prisvaivaniya: inkrementnaya ('=#') i dekrementnaya ('=#@') (oni opredeleny tol'ko dlya massivov): A [10..19] =# A [15..24]; !! inkrementnoe prisvaivanie A [10..19] =#@ A [15..24] !! dekrementnoe prisvaivanie Zdes' operandami yavlyayutsya dva perekryvayushchihsya otrezka massiva A. V pervom sluchae prisvaivanie budet osushchestvlyat'sya ot pervogo elementa k poslednemu, t.e. budet nerazrushayushchim i vse elementy "uceleyut" pri kopirovanii. Vo vtorom sluchae, kopirovanie proizojdet v obratnom poryadke, pri etom otrezok chastichno perezapishet sam sebya. |to ne obyazatel'no oshibka. Naprimer, esli neobhodimo "razmnozhit'" nebol'shoj otrezok na vsyu dlinu massiva, prisvaivanie s odnovremennoj "avtomaticheskoj" perezapis'yu yavlyaetsya vpolne dopustimym (i ves'ma effektivnym) tehnicheskim priemom. Kak i peremennye primitivnyh tipov, massivy mogut byt' (a konstantnye -- i dolzhny byt') inicializirovany. Konechno, vse, chto mozhet byt' prisvoeno massivu, yavlyaetsya i zakonnym inicializatorom dlya nego. Odnako, pomimo etogo, dopuskaetsya eshche odna forma inicializacii massiva -- spiskovaya. int [5] List1 = { 1, 2, 3, 4, 5 }; int [5] List2 = { 1, List1[2]*3, List1[0]*List1[4] + 2, 4, List1# } Kak legko videt' iz vtorogo primera, inicializatory -- lyubye vyrazheniya, sootvetstvuyushchie tipu elementov massiva. Oni vychislyayutsya tol'ko pri vypolnenii inicializacii. Stol' zhe gibkij podhod dopustim i pri inicializacii mnogomernyh massivov. Vot vpolne zakonnyj, hotya i neskol'ko nadumannyj primer: int [3][5] Matrix = { !! stroka #0: zadadim spiskom { 100, 200, 300 }, !! stroka #1: voz'mem iz List1 List1 [0..3], !! stroka #2: zadadim spiskom { List1[0]*List2[2], List1[1]*List2[1], List1[2]*List2[0] }, !! stroka #3: voz'mem iz List2 List2 [2..5], !! stroka #4: vektorizuem 100 na 3 elementa 100 } Spiskovye inicializatory massivov -- primer t.n. inicializiruyushchih vyrazhenij, opredelennyh i dlya nekotoryh drugih tipov. Ih mozhno ispol'zovat' tol'ko v kontekste inicializacii dlya peremennoj dannogo tipa, t.e. ispol'zovat' spisok elementov, skazhem, kak prisvaivaemoe znachenie, nel'zya: List1 = { 10, 20, 30, 40, 50 } !! oshibka! V yazyke imeetsya ne tol'ko agregatnoe prisvaivanie, no i agregatnoe sravnenie. Dlya togo, chtoby dva massiva byli sravnimymi, trebuetsya, kak i pri prisvaivanii, tochnoe sovpadenie ih bazovyh tipov. Odnako, razlichie v razmerah pri sravnenii ne schitaetsya fatal'noj oshibkoj. Proshche vsego opisat' semantiku sravnenij na ravenstvo/neravenstvo: dva massiva schitayutsya ravnymi, esli ravny ih razmery i sootvetstvuyushchie elementy poparno; v protivnom sluchae oni ne ravny: str1 -- str2; !! istinno, esli str1# -- str2# !! I str1 [I] -- str2 [I] dlya lyubogo I str1 <> str2 !! v protivnom sluchae Esli zhe bazovyj tip massivov uporyadochen (naprimer, yavlyaetsya primitivnym tipom), dopustimo takzhe sravnenie massivov na uporyadochennost'. Pri etom semantika sravneniya opredelena analogichno leksikograficheskomu ("slovarnomu") sravneniyu simvol'nyh strok. Vot strogoe opredelenie operacij "bol'she" i "men'she" dlya massivov: str1 < str2; !! istinno, esli sushchestvuet takoe N, chto !! 1) str1 [0..N] -- str2 [0..N] !! 2) str1# -- N && str2# > N !! ILI ZHE !! str1 [N] < str2 [N] str1 > str2 !! istinno, esli sushchestvuet takoe N, chto !! 1) str1 [0..N] -- str2 [0..N] !! 2) str1# > N && str2# -- N !! ILI ZHE !! str1 [N] > str2 [N] Drugimi slovami: massiv str1 men'she [bol'she] massiva str2, esli pervyj otlichayushchijsya element massiva str1 men'she [bol'she] sootvetstvuyushchego elementa massiva str2, ili zhe esli vse elementy str1 ravny elementam str2, a dlina str1 men'she dliny str2 [... vse elementy str2 ravny elementam str1, a dlina str2 men'she dliny str1]. Pravila sravneniya massivov rekursivno obobshchayutsya na massivy bolee vysokih razmernostej. Esli odin iz operandov sravneniya imeet men'shij rang, chem drugoj, on neyavno podvergaetsya vektorizacii po vsem "nedostayushchim" vneshnim izmereniyam. Prodemonstriruem vse eto na primerah: str1 -- ‘ ‘ !! istinno, esli vse simvoly str1 -- probely str1 <> ‘ ‘ !! istinno, esli hotya by odin simvol str1 otlichen ot probela str1 -- text !! istinno, esli str1# -- text []# !! I vse stroki text sovpadayut s str1 str1 <> text !! istinno, esli str1# <> text []# !! ILI hotya by odna stroka text otlichna ot str1 Vozmozhnost' sravneniya massivov, bezuslovno, cenna, no ne menee vazhno znat', v kakom imenno meste oni razlichayutsya. Dlya etogo predusmotreny operacii skaniruyushchego sravneniya (skanirovaniya). Dlya kazhdoj iz operacij prostogo sravneniya ('--', '<>', '<', '>' ...) imeetsya sootvetstvuyushchaya operaciya inkrementnogo ('--#', '<>#', '<#', '>#' ...) i dekrementnogo ('--#@', '<>#@', '<#@', '>#@' ...) skanirovaniya. Vo mnogom oni podobny sootvetstvuyushchim im operaciyam sravneniya, v chastnosti, oni pred®yavlyayut absolyutno te zhe trebovaniya k tipam operandov i vypolnyayutsya prakticheski takim zhe obrazom. Glavnoe otlichie -- vozvrashchaemoe imi znachenie imeet ne tip bool, a tip u_int -- i oznachaet ono, sootvetstvenno ne istinnost'/lozhnost' operacii sravneniya v celom, a chislo elementov massiva (nachal'nyh dlya inkrementnyh operacij, konechnyh -- dlya dekrementnyh), dlya kotoryh sootvetstvuyushchee uslovie udovletvoryaetsya. Tak, dlya skanirovaniya na ravenstvo: !! v inkrementnoj forme: VAL -- A --# B; !! oznachaet, chto: !! A [0..VAL] -- B [0..VAL] !! I !! A [VAL] <> B [VAL] !! (esli oni sushchestvuyut). !! v dekrementnoj forme: VAL -- A --#@ B; !! oznachaet, chto: !! A [A#-VAL..A#] -- B [B#-VAL..B#] !! I !! A [A#-VAL-1] <> B [B#-VAL-1] !! (esli oni sushchestvuyut). Kak i pri sravnenii, operandy skanirovaniya mogut podvergat'sya vektorizacii. Takim obrazom, skanirovanie mozhno ispol'zovat' i v kachestve operacii poiska elementa v massive: !! najti pervyj probel v massive str1: if (first_count = str1 <># ‘ ‘) -- str1# { !( probely ne najdeny ... )! } else { !( str1 [first_count] -- pervyj probel )! } !! najti poslednij probel v massive str1: if (last_count = str1 <>#@ ‘ ‘) -- str1# { !( probely ne najdeny ... )! } else { !( str1 [str# - last_count - 1] -- poslednij probel )! } Rezyumiruya zametim, chto sistema vektornyh operacij yazyka mozhet ponachalu pokazat'sya dovol'no slozhnoj. Tem ne menee, vozmozhnost' otnositel'no kompaktnoj zapisi dovol'no slozhnyh operacij nad massivami slishkom cenna, chtoby eyu prenebregat'. Krome togo, vse agregatnye operacii realizovany maksimal'no effektivno, i ih ispol'zovanie mozhet dat' ves'ma sushchestvennyj vyigrysh, osobenno v bibliotekah i drugih sistemno-znachimyh komponentah. Ukazatel'nye i ssylochnye tipy Realizaciya netrivial'nyh struktur dannyh, takih, kak linejnye i kol'cevye spiski, derev'ya, grafy i seti byla by prakticheski nereal'na bez ukazatelej. V tom ili inom vide takoj mehanizm predusmotren v lyubom yazyke. Dazhe v Java, gde deklarirovan otkaz ot ukazatelej, eta koncepciya neyavno prisutstvuet, t.k. vse massivy i ob®ekty dostupny tol'ko cherez ssylki. V Kserione podhod yavlyaetsya bolee tradicionnym: kak i v C i Paskale, dostupny ukazateli na peremennye lyubyh tipov. Pravda, v otlichie ot C, v ispol'zovanie ukazatelej vnesen ryad ogranichenij, prodiktovannyh soobrazheniyami bezopasnosti. Vse ukazatel'nye tipy dannyh vvodyatsya s pomoshch'yu prefiksnogo opisatelya '^'. Naprimer: int ^ip; !! ip - ukazatel' na celoe int ^^ipp !! ipp - ukazatel' na ukazatel' na celoe |ti dva opisaniya legko ob®edinit' s pomoshch'yu faktorizacii: int ^{ ip, ^ ipp } !! to zhe, chto i vyshe Prefiks '^' mozhet predvaryat'sya klyuchevymi slovami const, limited i strict, smysl kotoryh my rassmotrim chut' pozzhe. Dlya vseh ukazatel'nyh tipov opredelen edinstvennyj literal -- nil, oznachayushchij otsutstvie ssylochnogo znacheniya. S ukazatelyami pryamo svyazany dve operacii: imenovanie i razymenovanie. Tak, L-vyrazhenie lyubogo tipa legko prevratit' v ukazatel' na etot tip s pomoshch'yu operacii imenovaniya (postfiks '@'): int a; double b; a@; !! ukazatel' na peremennuyu a (int ^) b@ !! ukazatel' na peremennuyu b (float ^) Obratnaya operaciya -- razymenovanie (postfiks '^') -- pozvolyaet perejti ot ukazatelya k peremennoj (konstante), na kotoruyu on ukazyvaet (rezul'tat etoj operacii -- L-vyrazhenie). Ponyatno, chto popytka razymenovaniya znacheniya nil vyzovet oshibku perioda vypolneniya (NilDerefException). ip^; !! razymenovat' ip (int) ipp^; !! razymenovat' ipp (int ^) ipp^^ !! razymenovat' ipp dvazhdy (int) Tradicionno ukazateli schitayutsya dovol'no opasnym yazykovym mehanizmom. Po etoj prichine v Kserione imeetsya ryad ogranichenij na ih ispol'zovanie. Prezhde vsego, v otlichie ot primitivnyh tipov, dlya ukazatel'nyh tipov dejstvuet prinuditel'naya inicializaciya: esli ukazatel'naya peremennaya