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 (tiny → short
→ int → long), tak zhe, kak i vse plavayushchie (float → double
→ quad) i vse simvol'nye (char → w_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 ne
inicializirovana yavno, ona inicializiruetsya znacheniem nil, blagodarya chemu
ukazateli vsegda soderzhat nekoe osmyslennoe znachenie. |to pravilo, konechno,
rasprostranyaetsya i na massivy iz ukazatelej.
Dalee, sistema tipov yazyka nadezhno obespechivaet tipobezopasnost'
ukazatelej. V otlichie ot C, ne sushchestvuet nikakoj operacii, pozvolyayushchej
privodit' ukazatel' na odin tip k ukazatelyu na drugoj (krome mehanizma qual,
obespechivayushchego bezopasnoe preobrazovanie ukazatelej na rodstvennye
ob®ektnye tipy, kotoryj my rassmotrim pozzhe).
Pomimo tipizacionnogo kontrolya, vsegda dejstvuet i kontrol'
aktual'nosti ukazatelej. |tot mehanizm perioda kompilyacii ne pozvolyaet
prisvoit' ssylku na peremennuyu ukazatelyu, imeyushchemu bolee shirokuyu oblast'
sushchestvovaniya, preduprezhdaya takim obrazom opasnost' poyavleniya "visyachih"
ssylok.
int iv1, ^ip1;
{
int iv2, ^ip2;
ip1 = iv1@; !! zakonno
ip2 = iv2@; !! zakonno
ip1 = iv2@; !! oshibka!
ip2 = iv1@; !! zakonno
ip1 = ip2; !! oshibka!
ip2 = ip1 !! zakonno
}
Predusmotren takzhe kontrol' konstantnosti, svyazannyj s ponyatiem
konstantnyh ukazatelej. Ukazatel', deklarirovannyj kak konstantnyj (const),
mozhet ukazyvat' tol'ko na konstantnye znacheniya. Rezul'tat imenovaniya
konstanty porozhdaet konstantnyj ukazatel', a rezul'tat razymenovaniya
konstantnogo ukazatelya -- konstantnoe znachenie. Esli prisvaivanie obychnogo
ukazatelya konstantnomu dopustimo, to obratnoe zapreshchaetsya. Takim obrazom,
obojti konstantnost' znacheniya nel'zya, dazhe pribegaya k ukazatelyam.
Nakonec, nemalovazhnuyu rol' igraet otsutstvie potencial'no opasnyh
operacij nad ukazatelyami. Tak, v protivopolozhnost' C, dlya ukazatelej ne
opredeleny inkrement, dekrement, additivnye operacii i dazhe sravneniya na
uporyadochennost'. Pomimo imenovaniya i razymenovaniya dlya ukazatelej dostupny
tol'ko inicializaciya, prisvaivanie, i sravnenie na ravenstvo/neravenstvo. V
obshchem sluchae dlya prisvaivaniya i/ili sravneniya ukazatelej trebuetsya tochnoe
sovpadenie vseh promezhutochnyh tipov (za otdel'nymi melkimi poslableniyami, na
kotoryh my podrobno ostanavlivat'sya ne budem).
Ukazateli osobenno vazhny kak sredstvo dlya raboty s dinamicheskimi
peremennymi, sozdavaemymi vo vremya vypolneniya programmy. Dlya sozdaniya
podobnoj peremennoj ispol'zuetsya special'nyj term opisaniya -- allokator,
effekt vypolneniya kotorogo sostoit v sozdanii dinamicheskoj peremennoj s
nemedlennym sohraneniem ukazatelya na nee. Privedem primer:
!! sperva nado deklarirovat' ukazateli ...
int ^ip, [4] ^ivp;
!! teper' sozdadim ob®ekty, na kotorye oni budut ukazyvat' ...
int alloc (ip) = 5, [4] alloc (ivp) = { 0, 10, 20, 30 };
!! ... posle chego ih mozhno ispol'zovat':
ip^; !! 5 (int)
ivp^#; !! 4 (u_int)
ivp^ [3]; !! 30 (int)
Ispol'zuemyj sintaksis mozhet pokazat'sya neprivychnym. Esli by v Kserione
byl C++ podobnyj operator new, eti dejstviya zapisyvalis' by primerno tak:
ip = new int;
ip^ = 5;
ivp = new int [4];
ivp^ = { 0, 10, 20, 30 }
Sintaksicheski konstrukciya alloc (PTR) yavlyaetsya termom opisaniya, t.e.
ona mozhet byt' ispol'zovana vezde, gde dopustimo opisanie obychnoj peremennoj
ili konstanty. Esli tip konteksta opisaniya TYPE, to operand allokatora PTR
-- proizvol'noe L-vyrazhenie tipa TYPE ^, igrayushchee rol' "priemnika" dlya
ukazatelya na sozdannuyu dinamicheskuyu peremennuyu. Pri etom allokator -- chisto
ispolnyaemaya konstrukciya, ne imeyushchaya nikakogo deklarativnogo effekta.
Blagodarya tomu, chto ona pomeshchena v kontekst opisaniya, k dinamicheskoj
peremennoj mozhno primenyat' inicializatory, imeyushchie privychnyj sintaksis.
Sozdannaya dinamicheskaya peremennaya iznachal'no dostupna tol'ko cherez
ukazatel' PTR. Operacii, obratnoj alloc, ne sushchestvuet i ne trebuetsya,
poskol'ku upravlenie pamyat'yu v yazyke osushchestvlyaetsya dinamicheski. Ispolnyayushchaya
sistema podderzhivaet schetchik aktual'nyh ssylok na dinamicheskie peremennye.
Kogda poslednyaya ssylka teryaet aktual'nost', peremennaya avtomaticheski
unichtozhaetsya.
Sushchestvuyut ogranichennye ukazateli, pri opisanii kotoryh zadavalsya
atribut limited. Oni sposobny ukazyvat' tol'ko na ob®ekty s lokal'nym ili
staticheskim razmeshcheniem, no ne na dinamicheskie. Vvedenie v yazyk takih
"nepolnocennyh" ukazatelej prodiktovano soobrazheniyami effektivnosti: oni
trebuyut men'she mesta (32 bita vmesto 64) i bol'shinstvo operacij nad nimi
vypolnyaetsya nemnogo bystree. Prisvaivanie ogranichennyh ukazatelej obychnym
vsegda dopustimo, no obratnoe prisvaivanie mozhet vyzvat' isklyuchenie: esli
pri vypolnenii programmy proishodit popytka prisvoit' ogranichennomu
ukazatelyu ssylku na dinamicheskuyu peremennuyu, vozbuzhdaetsya isklyuchenie
PointerDomainException.
Sushchestvuet eshche odin tonkij aspekt ukazatelej, svyazannyj s ukazatelyami
na massivy. V kontekste ukazatel'nogo tipa massiv mozhet byt' "bezrazmernym"
(polnost'yu ili chastichno), t.e. kakie-to iz ego razmerov mogut byt' yavno ne
zadany:
float [] ^fv, [][] ^fvv
Zdes' fv i fvv -- ukazateli na odnomernyj i dvumernyj massivy iz
plavayushchih, imeyushchih proizvol'nye razmery. Nikakie proverki razmerov pri etom
ne otmenyayutsya -- prosto informaciya o nih budet hranit'sya vmeste s samimi
ukazatelyami. Esli fv prisvoit' ukazatel' na kakoj-nibud' massiv, informaciya
ob ego dline budet takzhe sohranena v otdel'nom pole fv, a pri razymenovanii
fv ona budet izvlechena ottuda dlya proverki. Takim obrazom, za
universal'nost' "bezrazmernyh" ukazatelej na massivy prihoditsya platit' tem,
chto kazhdoe "propushchennoe" izmerenie uvelichivaet razmer ukazatelya na 32 bita
(i nemnogo umen'shaet effektivnost' raboty s nim). Odnako, bez "bezrazmernyh"
ukazatelej sozdanie mnogih bibliotek funkcij i klassov obshchego naznacheniya
(skazhem, simvol'nyh strok) bylo by prosto nevozmozhnym.
V zavershenie neobhodimo upomyanut' o special'noj raznovidnosti
ukazatelej -- ssylkah. V obshchem-to ssylki otlichayutsya ot obychnyh ukazatelej v
dvuh aspektah: pri inicializacii ssylki k inicializatoru neyavno primenyaetsya
operaciya imenovaniya, a pri ispol'zovanii ssylki v lyubom kontekste ona neyavno
razymenovyvaetsya. Vo vseh ostal'nyh otnosheniyah ssylki analogichny ukazatelyam,
i mogut imet' te zhe svojstva i atributy. Pri opisanii ssylok vmesto prefiksa
'^' ispol'zuetsya prefiks '@'. Vot primer raboty s ssylkami:
char ch1 = ‘A', ch2 = ‘B'; !! simvol'nye peremennye
char ^pc = ch1@; !! pc: ukazatel' na ch1
pc^ = ‘C'; !! teper' ch1 -- ‘C'
char @rc = ch1; !! rc: ssylka na ch1
rc = ‘D'; !! teper' ch1 -- ‘D'
Ssylki Kseriona ves'ma pohozhi na analogichnyj mehanizm C++, no ne menee
vazhny i razlichiya. Esli v C++ ssylki -- special'nyj yazykovyj mehanizm (strogo
govorya, oni ne peremennye), to v Kserione im sootvetstvuyut obychnye
peremennye (ili konstanty), imeyushchie ssylochnyj tip. On mozhet ispol'zovat'sya
kak lyuboj drugoj proizvodnyj tip (dopustimy dazhe ssylki na ssylki i t.p.).
Nakonec, v otlichie ot C++, ssylka ne immutabel'na: esli ssylochnaya peremennaya
ne konstantna, ee mozhno izmenit' (t.e. zastavit' ssylat'sya na drugoj ob®ekt
podhodyashchego tipa), ispol'zuya tot fakt, chto operaciya imenovaniya dlya ssylki
vozvrashchaet L-vyrazhenie, podhodyashchee dlya prisvaivaniya:
rc@ = ch2@; !! teper' rc ssylaetsya na ch2
rc = ‘E'; !! teper' ch2 -- ‘E'
V Kserione ssylki i prostye ukazateli polnost'yu vzaimozamenyaemy. V
obshchem i celom, ssylki mozhno schitat' "arhitekturnym izlishestvom" -- odnako
oni, kak i v C++, predstavlyayut soboj sushchestvennoe notacionnoe udobstvo vo
mnogih sluchayah -- naprimer pri ispol'zovanii funkcij, ozhidayushchih parametr(y)
ukazatel'nyh tipov.
Funkcional'nye tipy i funkcii
Kak i v lyubom yazyke programmirovaniya, v Kserione imeetsya mehanizm
funkcij, i blizko svyazannoe s nimi ponyatie funkcional'nyh tipov dannyh
(funkcionalov). |to eshche odin mehanizm sozdaniya proizvodnyh tipov dannyh,
predstavlyayushchih fragmenty programmy, k kotorym mozhno obratit'sya (vyzvat' ih).
Vazhnejshimi atributami funkcional'nogo tipa yavlyayutsya spisok parametrov (s
opredelennymi imenami i tipami), peredavaemyh funkcionalu pri vyzove i
znachenie opredelennogo tipa, vozvrashchaemoe kak rezul'tat ego vypolneniya.
Funkcional'nyj tip vvoditsya kak proizvodnyj ot tipa vozvrashchaemogo
znacheniya s pomoshch'yu prefiksnogo opisatelya, imeyushchego vid ‘(' <spisok
parametrov> ‘)':
!! int_op - funkcional s dvumya celymi
!! parametrami (a, b), vozvrashchayushchij int
int (int a, b) int_op;
!! f_func -- funkcional s tremya parametrami raznyh tipov,
!! vozvrashchayushchij float
float (float [] ^farray; char ch1, ch2; bool flag) f_func;
Spisok parametrov -- eto posledovatel'nost' standartnyh opisanij,
razdelennaya tochkami s zapyatoj. Vse peremennye i konstanty, opisannye v
deklaratore, priobretayut status parametrov funkcionala. Obratite vnimanie na
to, chto opisannye zdes' int_op i f_func -- peremennye funkcional'nyh tipov
(ne "prototipy funkcij", kak mogli by podumat' znakomye s S++). Konechno, v
sushchestvovanii funkcional'nyh peremennyh i konstant ne bylo by smysla, esli
by v yazyke ne bylo sobstvenno funkcij:
int (int a, b) op_add { return a + b }; !! summa parametrov
int (int a, b) op_sub { return a -- b } !! raznost' parametrov
Esli term opisaniya imeet vid <imya> ‘{‘ <spisok instrukcij>
‘}', on opisyvaet funkciyu <imya>, imeyushchuyu sootvetstvuyushchij tip (on
dolzhen byt' funkcional'nym) i vypolnyayushchuyu blok instrukcij. Kak legko videt',
funkcii op_add i op_sub vozvrashchayut summu i raznost' svoih parametrov (hotya
instrukciyu return my eshche "ne prohodili", smysl ee vpolne ocheviden). Eshche raz
podcherknem, chto opisanie funkcii -- chastnyj sluchaj terma opisaniya, t.e.
mozhet vstretit'sya vezde, gde dopustimo opisanie peremennoj, i mozhet
sochetat'sya s drugimi opisaniyami, osnovannymi na tom zhe tipe (no ne pytajtes'
opisat' "funkciyu" ne funkcional'nogo tipa -- eto, konechno, semanticheskaya
oshibka). Dopustimy i obychnye priemy, takie, kak faktorizaciya v opisanii:
!! mozhno dobavit' umnozhenie i delenie ...
int (int a, b) { op_mul { return a * b }, op_div { return a // b } }
Identifikator funkcii yavlyaetsya literalom sootvetstvuyushchego
funkcional'nogo tipa. Operacii, dostupnye dlya funkcionalov, pomimo vyzova,
vklyuchayut prisvaivanie, inicializaciyu i sravnenie (tol'ko na
ravenstvo/neravenstvo). Vot primery:
op_add (6, 5); !! 11
int_op = op_add; !! teper' int_op -- eto op_add
int_op (5, 4); !! 9
int_op -- op_add; !! true
int_op = op_mul; !! teper' int_op -- eto op_mul
int_op (10, 5); !! vozvrashchaet 50
int_op <> op_add; !! true
int_op -- op_mul !! true
op_sub = int_op !! oshibka! (op_sub -- literal, a ne peremennaya)
Obratite vnimanie: pri ispol'zovanii funkcional'nogo tipa ne nuzhno
kakih-libo yavnyh operacij imenovaniya/razymenovaniya. Konechno, tehnicheski
funkcional'nyj tip realizovan kak ukazatel' na nekij blok koda, odnako
programmist ne obyazan zadumyvat'sya nad etim. Koe-chto, bezuslovno, rodnit
funkcional'nye tipy s ukazatelyami i ssylkami. Tak, k nim takzhe primenimo
znachenie nil (otsutstvie ssylki) i, podobno ukazatelyam, vse funkcional'nye
peremennye i massivy neyavno inicializiruyutsya im. Konechno, popytka "vyzvat'"
nil vyzyvaet isklyuchenie pri vypolnenii programmy (NilInvokeException). Kak i
v sluchae ukazatelej, dlya prisvaivaniya i sravneniya funkcional'nyh tipov
trebuetsya ih polnaya tipizacionnaya sovmestimost': dva funkcionala sovmestimy,
esli sovmestimy vozvrashchaemye imi znacheniya, kolichestvo i tipy ih parametrov.
Imeetsya i analog "prototipov funkcij" v yazykah C i C++. Term opisaniya
vida ‘#'<imya> -- eto predeklarirovanie (predopisanie) funkcii
<imya>. Ono zadaet spisok parametrov i tip vozvrashchaemogo znacheniya,
predpolagaya, chto realizaciya dannoj funkcii budet vypolnena pozdnee. Vot
primer predopisaniya:
float (float x, y) #power; !! predeklariruem funkciyu power
Hotya funkciya power eshche ne realizovana, ee uzhe mozhno ispol'zovat':
float result = power (x, 0.5) !! kvadratnyj koren' iz x
V konce koncov, predeklarirovannuyu funkciyu neobhodimo realizovat' (v
toj zhe oblasti dejstviya, gde byla ee predeklaraciya) s pomoshch'yu konstrukcii
vida ‘#'<imya><telo funkcii>. Naprimer:
#power { return exp (y * log (x)) }
Obratite vnimanie na to, chto pri realizacii ne nado povtorno zadavat'
spisok parametrov i vozvrashchaemyj tip -- kompilyatoru oni uzhe izvestny. Bolee
togo, popytka polnost'yu opisat' uzhe predeklarirovannuyu funkciyu power byla by
oshibkoj, t.k. vosprinimalas' by kompilyatorom kak popytka pereopredelit' ee!
Zdes' soblyuden odin iz principov yazyka: kazhdyj ob®ekt dolzhen byt' opisan
tol'ko odnazhdy, a dublirovanie opisanij ne nuzhno i ne dopuskaetsya. V sluchae
predeklarirovannoj funkcii, strogo govorya, my imeem delo ne s dvumya
opisaniyami, a s edinym, razbitym na dve chasti: deklarativnuyu i
realizacionnuyu. V dannom sluchae yavnoj neobhodimosti ispol'zovat'
predeklarirovanie net, poskol'ku mozhno bylo by napisat' srazu:
float (float x, y) power { return exp (y * log (x)) }
No bez predeklarirovaniya nevozmozhno obojtis', kogda opisyvaetsya
semejstvo vzaimno-rekursivnyh funkcij, kazhdaya iz kotoryh vyzyvaet (pryamo ili
kosvennym obrazom) vse drugie.
Sintaksis i semantiku vyzova funkcionalov sleduet rassmotret'
podrobnee. Obychno vyzov yavlyaetsya N-arnoj operaciej, imeyushchej pervym operandom
vyzyvaemoe znachenie funkcional'nogo tipa. Dalee sleduet spisok argumentov,
kazhdyj iz kotoryh zadaet znachenie dlya odnogo iz parametrov funkcionala.
Tradicionno sootvetstvie mezhdu nimi ustanavlivaetsya po pozicionnomu
principu, t.e. poryadok argumentov vyzova sootvetstvuet poryadku parametrov v
deklaracii funkcional'nogo tipa:
void (float x, y; bool p, q) z_func;
z_func (0.5, 1.5, true, true)
!! (t.e. x ← 0.5, y ← 1.5, p ← true, q ← true)
Odnako, dopustim takzhe i imennoj princip, kogda imya parametra dlya
tekushchego argumenta zadaetsya yavno s pomoshch'yu prefiksa vida <parametr>
‘:'. Naprimer, kak zdes':
z_func (p: false, q: true, x: 0.0, y: 1.0)
!! (x ← 0.0, y ← 1.0, p ← false, q ← true)
Oba vida specifikacii mozhno kombinirovat' v odnom vyzove. Zadanie
argumenta bez prefiksa oznachaet, chto on otnositsya k sleduyushchemu po poryadku
parametru (k samomu pervomu, esli predshestvuyushchih ne bylo). Nakonec, element
spiska argumentov mozhet byt' pustym, chto oznachaet propusk sootvetstvuyushchego
parametra (kotoryj mozhet byt' zapolnen pozzhe):
z_func (3.14, , false, false, y: 8.9)
!! (x ← 3.14, y ← 8.9, p ← false, q ← false)
Pri neostorozhnom sochetanii vseh etih priemov vpolne mozhet okazat'sya
tak, chto pri vyzove funkcii parametr ostavlen bez znacheniya, ili zhe
inicializirovan dva (ili bolee) raza. Vtoroe yavlyaetsya bezuslovnoj oshibkoj, a
vot pervoe mozhet schitat'sya dopustimym. Delo v tom, chto k parametram funkcii,
kak i k lyubym peremennym, mozhet byt' primenena inicializaciya po umolchaniyu.
Lyuboj yavno zadannyj argument "vytesnyaet" neyavnoe znachenie parametra.
Analogichnaya vozmozhnost' imeetsya i v C++, no tam inicializaciya po umolchaniyu
mozhet otnosit'sya lish' k poslednim argumentam v spiske, a inicializatorami
obyazany byt' literal'nye znacheniya. V Kserione oba etih ogranicheniya
otsutstvuyut. Bolee togo, odin neochevidnyj (no ves'ma poleznyj) aspekt
opisanij sostoit v tom, chto inicializator dlya parametra mozhet soderzhat'
drugie parametry, opisaniya kotoryh predshestvuyut emu. Primenenie etogo metoda
luchshe pokazat' na primere:
!! Zamet'te, chto zdes' tri opisaniya nel'zya ob®edinit'
void (int a = 5; int b = a; int c = a + b) x_func;
x_func (11, 12, 13); !! vse argumenty zadano yavno
!! (a ← 11, b ← 12, c ← 13)
x_func (10, 20); !! a i b zadany, c po umolchaniyu
!! (a ← 10, b ← 20, c ← 30)
x_func (10); !! a zadano, b i c po umolchaniyu
!! (a ← 10, b ← 10, c ← 20)
x_func (); !! vse po umolchaniyu
!! (a ← 5, b ← 5, c ← 10)
Dazhe v kachestve razmerov parametrov-massivov mogut ispol'zovat'sya
vyrazheniya, soderzhashchie ranee deklarirovannye parametry. |to tozhe mozhet
okazat'sya poleznym:
!! matrichnoe proizvedenie: C = A (*) B
void (u_int L, M, N; double [L][M] @A, [M][N] @B, [L][N] @C) MatrixProduct {
! ... ! }
Semantika peredachi argumentov -- eto vsegda semantika inicializacii,
t.e. dopustimy ne tol'ko prostye vyrazheniya, no i lyubye inicializatory,
podhodyashchie po tipu. To zhe otnositsya k znacheniyu, vozvrashchaemomu instrukciej
return. Zametim, chto parametry-massivy (v otlichie ot C, C++ i Java) takzhe
peredayutsya (i vozvrashchayutsya) po znacheniyu, chto mozhet byt' ves'ma dorogim
udovol'stviem. Kak pravilo, massivy luchshe peredavat' cherez ukazatel' ili
ssylku, a peredachu po znacheniyu ispol'zovat' lish' v teh sluchayah, kogda eto
dejstvitel'no opravdano. Pomimo svoih parametrov, funkcii dostupna vsya
vneshnyaya sreda -- t.e. vse peremennye i konstanty (nezavisimo ot rezhima ih
razmeshcheniya) i prochie vidy opisanij, dostupnye v tochke, gde dano opisanie
funkcii.
V yazyke ne sushchestvuet peregruzhennyh (overloaded) funkcij, podobnyh
imeyushchimsya v C++. Imya kazhdoj funkcii v svoej oblasti dejstviya dolzhno byt'
unikal'no (kak i dlya lyubogo drugogo sub®ekta opisaniya).
V zaklyuchenie otmetim, chto funkcional'nyj tip dopuskaet otdel'nuyu formu
inicializatora, pryamo zadayushchego telo bezymyannoj funkcii. (Nekotorye yazyki
programmirovaniya nazyvayut podobnoe "lyambda-notaciej"). Neyavnyj inicializator
imeet vid ‘#' <telo funkcii>. Imena i tipy parametrov i vozvrashchaemogo
znacheniya yavno ne zadayutsya, a opredelyayutsya avtomaticheski, ishodya iz konteksta
inicializacii. Naprimer:
int (float a, b, c) t_func = #{ return :int (a * b * c) };
t_func (2, 3, 4) !! 24 (int)
Dopolnitel'nye raznovidnosti opisanij
CHtoby zavershit' razgovor ob opisaniyah, my rassmotrim nekotorye
special'nye deklarativnye konstrukcii. Vse oni imeyut skoree vspomogatel'noe,
chem principial'noe znachenie, no vse-taki oni polezny pri sozdanii real'nyh
programm.
Prezhde vsego, v Kserione imeetsya svoj analog opisaniya typedef v C,
pozvolyayushchij vvodit' novye tipy. Odnako, eto ne samostoyatel'naya konstrukciya,
a lish' eshche odin vid terma opisaniya (type <imya tipa>), kotoryj, kak
vsegda, mozhet sovmeshchat'sya s drugimi termami. Naprimer:
!! flt -- sinonim float,
!! pflt -- ukazatel' na float
!! ppflt -- ukazatel' na ukazatel' na float
float type flt, ^ type pflt, ^^ type ppflt
Klyuchevoe slovo type slishkom gromozdko, poetomu ego mozhno sokratit' do
simvola ‘%' (chto obychno na praktike i delaetsya). Dlya togo, chtoby
ispol'zovat' novoopredelennyj tip v kachestve kornya opisaniya, on tozhe dolzhen
predvaryat'sya slovom type (ili simvolom ‘%'):
%flt x, y, z; !! t.e. float x, y, z
%pflt p1, p2; !! t.e. float ^ {p1, p2}
%ppft pp1, pp2, pp3 !! t.e. float ^^ {pp1, pp2, pp3}
S tochki zreniya semantiki podobnaya zapis' -- ne bolee, chem sredstvo
sokratit' dlinnye opisaniya. V otlichie ot ob®ektnyh tipov, nikakimi
principial'no novymi svojstvami tip, vvedennyj cherez opisanie type, obladat'
ne budet.
Privedennye vyshe opisaniya -- eto chastnyj sluchaj bolee obshchego podhoda,
pozvolyayushchego ispol'zovat' v kachestve kornya opisaniya ne tol'ko opredelennyj
programmistom tip, no i proizvol'noe vyrazhenie, imeyushchee smysl. Vot neskol'ko
trivial'nyh primerov:
%(2 * 2) xx, yy, zz; !! t.e. u_int xx, yy, zz
%(10 < 20) pp, qq; !! t.e. bool pp, qq
%("text" []) cc !! t.e. char cc
Vyrazhenie v korne opisaniya (esli eto ne prosto identifikator, ono
dolzhno byt' zaklyucheno v skobki) vychislyaetsya, no ego znachenie ignoriruetsya, i
v kachestve bazy opisaniya ispol'zuetsya tol'ko ego tip. Nakonec, otmetim, chto
imena opredelennyh pol'zovatelem (no ne vstroennyh!) tipov -- eto takzhe
zakonnye (no neopredelennye) vyrazheniya. Vse eto otkryvaet vozmozhnosti dlya
mnogih poleznyh tryukov. Tak, ispol'zovanie imen proizvodnyh tipov v
vyrazheniyah (i vyrazhenij -- v kornyah opisanij) daet prostoj mehanizm
tipizacionnoj dekompozicii, t.e. perehoda ot proizvodnyh tipov k ih bazovym.
Vot primer togo, kak eto mozhno ispol'zovat' na praktike:
!! esli v_type -- vektornyj tip:
%(v_type []) %v_type_elem; !! v_type_elem -- eto tip elementov v_type
!! esli p_type -- ukazatel'nyj tip:
%(p_type ^) %p_type_ref; !! p_type_ref -- eto tip,
!!
poluchaemyj razymenovaniem p_type
!! esli f_type -- funkcional'nyj tip:
%(f_type ()) %f_type_result !! f_type_result -- eto tip znacheniya,
!!
vozvrashchaemogo f_type pri vyzove
Sushchestvuet eshche odna vazhnaya forma opisanij -- eto makroopredeleniya
(let-opredeleniya). V osnovnom, oni primenimy dlya teh zhe celej, chto i
opredeleniya #define v C/C++, t.e. kak makropodstanovki povtoryayushchihsya
fragmentov ishodnogo koda programmy. No ne menee vazhny i razlichiya. Esli
sredstva C-preprocessora -- eto nadstrojka nad yazykom, to let-opredeleniya --
eto chast' yazyka Kserion, a ob®ektom let-podstanovki mozhet byt' ne vsyakaya
stroka simvolov -- eto dolzhno byt' zakonnoe vyrazhenie yazyka. Obshchij sintaksis
makroopredeleniya imeet takoj vid:
let NAME1 ‘=' EXPR1 (‘,' NAME2 ‘=' EXPR2) ...
|to opredelenie delaet vse identifikatory NAME# sinonimami dlya
sootvetstvuyushchih vyrazhenij EXPR#. Kak i prochie vidy opredelenij,
makroopredeleniya lokal'ny dlya soderzhashchego ih bloka ili oblasti dejstviya.
Vazhno takzhe to, chto vyrazhenie EXPR dolzhno byt' korrektno ne tol'ko
sintaksicheski, no i semanticheski: v chastnosti, vse identifikatory,
upomyanutye v EXPR, dolzhny imet' smysl. V celom mehanizm makroopredelenij
obespechivaet ne tol'ko tekstual'nuyu, no i semanticheskuyu podstanovku: vse
imena budut imet' v tochke obrashcheniya k makro tot zhe smysl, kotoryj oni imeli
v tochke ego opredeleniya. Naprimer:
int value; !! celaya peremennaya
let v1 = value; !! v1 -- sinonim value
{ float value; !! pereopredelenie value v podbloke
value; !! (float value)
v1 !! (a eto -- int value)
}
Nakonec, esli EXPR yavlyaetsya L-vyrazheniem, to NAME -- takzhe L-vyrazhenie.
Mehanizm makroopredelenij yavlyaetsya dovol'no moshchnym sredstvom, ispol'zuemym
dlya samyh raznyh celej: ot opredeleniya simvolicheskih literalov (v otlichie ot
konstant-peremennyh, dlya nih ne trebuetsya dopolnitel'naya pamyat') do prostogo
sokrashcheniya slishkom dlinnyh identifikatorov peremennyh, funkcij, tipov i
klassov:
%err_no (%string FileName) #SystemOpenFile;
let SysOpen = SystemOpenFile !! sokrashchenie
V zavershenie rassmotrim opisanie conceal -- mehanizm "skrytiya" imen.
Esli identifikator, opredelennyj v nekoj vneshnej oblasti dejstviya (naprimer,
global'nyj) neobhodimo sdelat' nedostupnym v nekoj vnutrennej (i vseh
oblastyah, vlozhennyh v nee), etogo legko dobit'sya s pomoshch'yu special'nogo
opisatelya conceal:
conceal NAME (‘,' NAME1) ...
Opisatel' conceal delaet vse perechislennye v nem imena lokal'no
nedostupnymi (ot opisatelya do konca vnutrennej oblasti dejstviya, soderzhashchej
ego). V sushchnosti, opisanie conceal NAME rabotaet primerno kak let
NAME=<nothing>. V osnovnom, mehanizm conceal prednaznachen dlya raboty s
ob®ektami i ierarhiyami klassov (naprimer, skrytiya kakih-nibud' atributov
bazovogo klassa v proizvodnyh klassah), chto, konechno, ne oznachaet, chto ego
nel'zya ispol'zovat' dlya drugih celej.
Instrukcii i potok upravleniya
Sobstvenno programma sostoit v osnovnom iz operatorov ili instrukcij
yazyka (poslednij termin kazhetsya nam predpochtitel'nym, poetomu im my i budem
pol'zovat'sya). Prostejshie vidy instrukcij my uzhe rassmotreli. Tak, vse vidy
opisanij yavlyayutsya zakonnymi instrukciyami, dopustimymi v lyubom meste
programmy. Lyuboe vyrazhenie -- eto takzhe instrukciya (vozvrashchaemoe znachenie,
esli ono est', ignoriruetsya). V yazyke predusmotren takoj mehanizm
gruppirovki instrukcij, kak blok, t.e. posledovatel'nost' instrukcij,
razdelennyh tochkami s zapyatoj (‘;') i zaklyuchennaya v figurnye skobki ("{}").
Blok rassmatrivaetsya kak edinaya instrukciya i yavlyaetsya oblast'yu lokalizacii
dlya vseh soderzhashchihsya v nem opisanij. Zamet'te, chto v etom otnoshenii yazyk
sleduet tradiciyam Paskalya: toska s zapyatoj -- eto sintaksicheskij razdelitel'
instrukcij (no ni odna instrukciya ne zavershaetsya etim simvolom). Vo mnogih
sluchayah izbytochnaya tochka s zapyatoj ne schitaetsya oshibkoj, t.k. v yazyke
opredelena pustaya instrukciya, ne soderzhashchaya ni odnogo simvola (i, ochevidno,
ne vypolnyayushchaya nikakih dejstvij). Lyubaya instrukciya mozhet byt' pomechena
metkoj vida LABEL ‘:', chto pozvolyaet instrukciyam break, continue i goto na
nee ssylat'sya. Rassmotrim drugie vidy instrukcij.
Instrukciya utverzhdeniya (assert) imeet vid:
assert CND
Semantika ee prosta: vychislyaetsya CND (vyrazhenie tipa bool). Esli ono
istinno, nichego ne proishodit, v protivnom sluchae vozbuzhdaetsya
isklyuchitel'naya situaciya AssertException. |ta instrukciya nuzhna v osnovnom dlya
"otlova" logicheskih oshibok v processe otladki programmy.
Konechno zhe, imeetsya uslovnaya instrukciya (if/unless), imeyushchaya sleduyushchij
vid:
(if P_CND | unless N_CND) BLOCK
[else E_STMT]
Esli (dlya if-formy) vyrazhenie P_CND istinno ili (dlya unless-formy)
vyrazhenie N_CND lozhno, vypolnyaetsya blok BLOCK. V protivnom sluchae, esli
prisutstvuet neobyazatel'naya chast' else, budet vypolnena instrukciya E_STMT.
Zametim, chto telo uslovnoj instrukcii -- eto vsegda blok, ogranichennyj
figurnymi skobkami (chto snimaet problemu neodnoznachnosti "visyashchego else").
Odnako, kruglye skobki vokrug usloviya (kak v C) ne trebuyutsya (hotya, konechno,
nichemu i ne pomeshayut). V chasti else dopustima proizvol'naya instrukciya
(naprimer, drugoj if/unless). Ochevidno, chto formy if i unless polnost'yu
vzaimozamenyaemy, i kakuyu iz nih ispol'zovat' -- vopros konkretnogo sluchaya.
V otlichie ot bol'shinstva yazykov, v Kserione imeetsya tol'ko odna (zato
dovol'no moshchnaya) instrukciya cikla. Vot ee samyj obshchij sintaksis:
[for I_EXPR]
(while P_CND_PRE | until N_CND_PRE | loop)
[do R_EXPR]
BLOCK
[while P_CND_POST | until N_CND_POST]
Hotya ona vyglyadit dovol'no gromozdkoj, bol'shaya chast' ee komponent
neobyazatel'na. Neobyazatel'naya chast' for zadaet inicializator cikla --
vyrazhenie I_EXPR, kotoroe vsegda vychislyaetsya odin raz pered samym nachalom
raboty cikla. Dalee vsegda sleduet zagolovok cikla, zadayushchej ego
preduslovie, proveryaemoe pered kazhdoj iteraciej cikla. Esli (v forme while)
P_CND_PRE lozhno ili (v forme until) N_CND_PRE istinno, cikl zavershit svoyu
rabotu. Esli zhe zagolovok cikla svoditsya k loop, preduslovie otsutstvuet.
Telom cikla yavlyaetsya blok BLOCK, obychno vypolnyayushchij osnovnuyu rabotu.
Neobyazatel'naya chast' do zadaet postiteraciyu cikla: vyrazhenie R_STMT budet
vychislyat'sya na kazhdoj iteracii posle tela cikla. Nakonec, cikl mozhet imet' i
postuslovie: esli (v forme while) P_CND_POST lozhno ili (v forme until)
N_CND_POST istinno, cikl takzhe zavershitsya. Kakuyu iz dvuh form ispol'zovat'
dlya pred- i postusloviya -- eto, opyat'-taki, vopros predpochteniya. Preduslovie
i postuslovie mogut prisutstvovat' odnovremenno -- v etom sluchae, cikl
preryvaetsya, kogda perestaet soblyudat'sya hotya by odno iz nih. Nakonec
zametim, chto vmesto vyrazheniya I_EXPR mozhet byt' dano lyuboe opisanie, i pri
etom cikl stanovitsya oblast'yu lokalizacii dlya nego (t.e. kak by neyavno
zaklyuchaetsya v blok). |lementy for i do logicheski izbytochny -- oni nuzhny
tol'ko dlya togo, chtoby mozhno bylo radi naglyadnosti sobrat' v zagolovke vsyu
logiku upravleniya ciklom. Tak, esli nuzhen cikl s peremennoj i, menyayushchej
znachenie ot (vklyuchaya) 0 do (isklyuchaya) N; eto obychno zapisyvaetsya tak:
for u_int i = 0 while i < N do ++ i { !( telo cikla )! }
Neredko neobhodimo prervat' vypolnenie cikla gde-nibud' poseredine. Dlya
etogo udobno ispol'zovat' instrukciyu preryvaniya break:
break [LABEL]
Ona preryvaet vypolnenie soderzhashchego ee cikla, pomechennogo metkoj LABEL
(ravno kak i vseh vlozhennyh v nego ciklov, esli oni est'). Esli element
LABEL opushchen, preryvaetsya samyj vnutrennij iz ciklov, soderzhashchih instrukciyu
break. Instrukciya prodolzheniya continue:
continue [LABEL]
vyzovet preryvanie tekushchej iteracii cikla LABEL (ili, esli metka
opushchena, samogo vlozhennogo cikla) i perehod k ego sleduyushchej iteracii
(vklyuchaya vypolnenie postiteracii i proverku postusloviya, esli oni est').
V zavershenie upomyanem ob instrukcii perehoda goto:
goto [LABEL]
peredayushchej upravlenie instrukcii, pomechennoj metkoj LABEL. O vrednosti
podobnyh instrukcij klassiki strukturnogo programmirovaniya napisali stol'ko,
chto net smysla ih povtoryat'. Instrukciya goto v yazyke est', a ispol'zovat' li
ee v programme -- delo vashej sovesti i lichnyh predpochtenij.
Dlya zaversheniya raboty funkcii primenyaetsya uzhe znakomaya nam instrukciya
return:
return [EXPR]
Ona dopustima tol'ko v opredelenii funkcii i obespechivaet vyhod iz nee
s vozvratom znacheniya EXPR (podhodyashchego tipa). Vyrazhenie EXPR opuskaetsya,
esli tip funkcii -- void.
Nakonec, v yazyke imeetsya instrukciya with, tesno svyazannaya s ob®ektami i
potomu rassmotrennaya v sleduyushchem razdele.
Ob®ekty i klassy
Kserion -- eto ob®ektno-orientirovannyj yazyk. V nem prisutstvuet
koncepciya ob®ekta -- klyuchevogo mehanizma abstrakcii dannyh, obespechivayushchego
dlya nih inkapsulyaciyu, nasledovanie i polimorfizm.
Kazhdyj ob®ekt yazyka otnositsya k odnomu iz klassov, opredelyayushchih
specifichnye dlya nego svojstva i atributy. Samyj obshchij sintaksis opisaniya
klassa takov:
class CLASS_NAME [‘:' SUPERCLASS_NAME]
{
CLASS_DECLS
}
[instate INSTATE_LIST]
[destructor DESTRUCTOR_BODY]
Rassmotrim vse elementy opisaniya po poryadku. Prezhde vsego, kazhdyj klass
obyazan imet' unikal'noe v svoej oblasti dejstviya imya (CLASS_NAME). Klass
mozhet byt' libo kornevym, libo zhe proizvodnym ot uzhe opredelennogo
superklassa (klassa SUPERCLASS_NAME). Dalee sleduet zaklyuchennoe v figurnye
skobki telo opisaniya klassa, predstavlyayushchee soboj spisok CLASS_DECLS. Ego
elementami mogut byt' prakticheski vse vidy opisanij yazyka (vklyuchaya i
nekotorye drugie, rassmotrennye nizhe). V bol'shinstve sluchaev v opisanii
klassa prisutstvuyut peremennye, konstanty i funkcii.
Lyubaya peremennaya, opisanie kotoroj soderzhitsya v deklaracii klassa, po
umolchaniyu schitaetsya ego komponentoj. |to znachit, chto dlya kazhdogo ob®ekta
klassa sushchestvuet sobstvennaya kopiya etoj peremennoj. Esli zhe peremennaya
imeet yavno specificirovannyj rezhim razmeshcheniya static ili shared, ona
yavlyaetsya peremennoj klassa, t.e., v otlichie ot ego komponent, sushchestvuet v
edinstvennom ekzemplyare, vne zavisimosti ot togo, skol'ko ob®ektov dannogo
klassa bylo sozdano. Raznica mezhdu rezhimami static i shared sostoit v tom,
chto static-peremennye sushchestvuyut global'no (vremya ih sushchestvovaniya sovpadaet
so vremenem vypolneniya programmy), a dlya shared oblast' dejstviya, ravno kak
i vremya sushchestvovaniya, opredelyayutsya deklaraciej klassa.
V deklaracii klassa mogut prisutstvovat' vlozhennye bloki lichnyh
(private) i zashchishchennyh (protected) opisanij. Kak i v C++, imena vseh
ob®ektov, deklarirovannyh v private-bloke, dostupny tol'ko vnutri deklaracii
klassa, a v protected-bloke -- takzhe i vnutri deklaracij vseh ego
podklassov. Vse prochie deklaracii yavlyayutsya publichnymi, t.e. dostupnymi izvne
bez kakih-libo ogranichenij.
Sintaksicheski opisanie klassa igraet rol' kornya opisaniya. Zametim, chto
posle togo, kak klass deklarirovan, dlya ssylok na nego (kak i na vse prochie
proizvodnye tipy) ispol'zuetsya klyuchevoe slovo type ili ‘%' (a ne class).
V semantike ob®ektov unikal'nym (i ves'ma vazhnym) yavlyaetsya ponyatie
tekushchego ekzemplyara ob®ekta. Dlya kazhdogo klassa opredelen odin i tol'ko odin
tekushchij ekzemplyar. Ego mozhno rassmatrivat' kak neyavnuyu peremennuyu klassa s
tipom CLASS_NAME^ i rezhimom razmeshcheniya shared, inicializiruemuyu, kak i vse
ukazateli, znacheniem nil. V processe vypolneniya programmy tekushchij ekzemplyar
klassa mozhet vremenno menyat'sya. Obratit'sya k tekushchemu ekzemplyaru nekotorogo
klassa (skazhem, CLASS_NAME), mozhno ochen' prosto: po imeni etogo klassa. V
kontekste opisaniya lyubogo klassa vmesto ego imeni mozhno ispol'zovat'
klyuchevoj slovo this:
CLASS_NAME; !! tekushchij ekzemplyar klassa CLASS_NAME
this !! tekushchij ekzemplyar tekushchego klassa
Rassmotrim teper' binarnuyu operaciyu dostupa k klassu ‘.' (tochka).
Pervym operandom etoj operacii vsegda yavlyaetsya ob®ekt nekotorogo klassa, a
vtoroj operand (proizvol'noe vyrazhenie) -- eto rezul'tat operacii (ot nego
vyrazhenie takzhe zaimstvuet L-kontekstnost' i konstantnost'). Kak i v C++ i
Paskale, ona mozhet ispol'zovat'sya, naprimer, dlya dostupa k otdel'nym
komponentam ob®ekta, no v Kserione ee semantika znachitel'no shire. Formal'no
ona imeet dva nezavisimyh aspekta: deklarativnyj i procedurnyj.
Deklarativnyj aspekt operacii sostoit v tom, chto ee vtoroj operand
vychislyaetsya v kontekste prostranstva imen dannogo klassa (t.e. v nem
dostupny imena komponent, peremennyh, funkcij i inye atributy klassa).
Procedurnyj aspekt -- v tom, chto ona (na vremya vychisleniya svoego vtorogo
operanda) delaet svoj pervyj operand-ob®ekt tekushchim ekzemplyarom dlya svoego
klassa. Oba perechislennyh aspekta sochetayutsya estestvennym obrazom, kak vidno
iz primerov:
!! trivial'nyj vektor iz treh komponent
class VECTOR { float x, y, z };
%VECTOR vec1, vec2; !! para ob®ektov klassa VECTOR
vec1.x; !! x-komponenta vec1
vec2.(x + y + z); !! summa komponent vec2
vec1.(x*x + y*y + z*z) !! norma vektora vec1
Esli zhe pervyj operand -- eto ssylka na tekushchij ob®ekt (inymi slovami,
imya klassa), to deklarativnaya semantika ostaetsya neizmennoj, no procedurnaya
vyrozhdaetsya v pustuyu operaciyu (t.k. tekushchij ob®ekt uzhe yavlyaetsya takovym).
Takim obrazom, operaciya dostupa k klassu stanovitsya prakticheski tochnym
analogom operacii ‘::' (kvalifikacii) iz C++:
VECTOR.x !! x-komponenta tekushchego ekzemplyara VECTOR
this.x !! to zhe samoe v kontekste klassa VECTOR
V sisteme instrukcij yazyka imeetsya svoj analog operacii dostupa k
klassu -- instrukciya prisoedineniya with:
with OBJ_EXPR BLOCK
Ee semantika prakticheski ta zhe: vypolnit' blok instrukcij BLOCK v
kontekste klassa, opredelennogo OBJ_EXPR (deklarativnaya), i s OBJ_EXPR v
kachestve tekushchego ekzemplyara etogo klassa (procedurnaya). K primeru:
with vec1 { x = y = z = 0f }; !! obnulit' komponenty vec1
with VECTOR { x = y = z = 0f } !! to zhe s tekushchim ekzemplyarom VECTOR
V yazyke ne sushchestvuet special'nogo ponyatiya metoda klassa -- v osnovnom
potomu, chto oni i ne trebuyutsya. Metody klassov v C++ i Java harakterizuyutsya
tem, chto vmeste s drugimi argumentami oni neyavno poluchayut ukazatel' na
tekushchij ob®ekt klassa, s kotorym dolzhny rabotat'. Odnako, v Kserione ponyatie
tekushchego ob®ekta yavlyaetsya global'nym i ravno primenimym ko vsem funkciyam.
Funkcii, deklarirovannye vnutri klassa, otlichayutsya ot drugih tol'ko tem, chto
imeyut neposredstvennyj dostup ko vsem atributam klassa (vklyuchaya ego lichnuyu i
zashchishchennuyu chast'). Esli zhe poslednee ne trebuetsya, funkcii, rabotayushchie s
ob®ektami opredelennogo klassa, mogut byt' deklarirovany i za ego predelami.
Privedem primer dlya opisannogo nami klassa VECTOR:
!! Umnozhenie vektora na skalyar `a`
void (float a) scale_VECTOR
{ with VECTOR { x *= a; y *= a; z *= a } }
Opisannyj nami "psevdo-metod" scale_VECTOR ispol'zovat' na praktike tak
zhe prosto, kak i funkcii, deklarirovannye vmeste s samim klassom:
vec2.Scale_VECTOR (1.5) !! Umnozhit' vec2 na 1.5
with vec2 { Scale_VECTOR (1.5) } !! to zhe, chto i vyshe
Scale_VECTOR (2f) !! Umnozhit' tekushchij ekzemplyar VECTOR na 2
Pomimo etogo, dlya kazhdogo klassa avtomaticheski opredelyayutsya operacii
prisvaivaniya, inicializacii i sravneniya (na ravenstvo i neravenstvo).
Prisvaivanie ob®ektov sostoit v posledovatel'nom prisvaivanii vseh ih
komponent. Analogichnym obrazom opredelyaetsya ekzemplyarnaya inicializaciya:
ob®ekt vsegda mozhet byt' inicializirovan prisvaivaniem emu drugogo ob®ekta
togo zhe klassa. Operaciya sravneniya takzhe opredelena kak pokomponentnaya: esli
vse sootvetstvuyushchie komponenty ravny, dva ob®ekta schitayutsya ravnymi; v
protivnom sluchae oni razlichny. |ti operacii nad ob®ektami vsegda dostupny; v
otlichie ot C++ ih nevozmozhno pereopredelit' ili zhe "razopredelit'".
Konechno zhe, pomimo ekzemplyarnoj inicializacii predusmotreny i drugie
zakonnye sposoby inicializirovat' ob®ekt klassa. Dlya klassov vsegda
opredelena spiskovaya inicializaciya, a mozhet byt' dostupen i vyzov
konstruktora. Rassmotrim eti vozmozhnosti po poryadku.
Samyj trivial'nyj sposob inicializacii sozdavaemogo ob®ekta -- eto
inicializaciya ego spiskom komponent. V principe, etot sposob analogichen
spiskovoj inicializacii klassov i struktur v C i C++, no on dopuskaet bol'she
vozmozhnostej.
Obshchij sintaksis spiskovogo inicializatora ob®ekta imeet primerno takoj
vid:
‘#' ‘(' <COMP_LIST> ‘)'
gde COMP_LIST -- eto spisok inicializatorov dlya komponent ob®ekta. Ego
sintaksis my podrobno rassmatrivat' ne budem, poskol'ku on polnost'yu
identichen spisku argumentov funkcij. Edinstvennoe razlichie: spisok zdes'
primenyaetsya ne k parametram funkcionala, a k komponentam ob®ekta. V spiske
dopustimy i pozicionnye inicializatory, i imennye. Prakticheski nichem ne
otlichaetsya i semantika. Komponenty ob®ekta, kak i parametry funkcii, mogut
imet' inicializaciyu po umolchaniyu (v tom chisle, i s ispol'zovaniem ranee
opisannyh komponent), i yavnaya inicializaciya pereopredelyaet neyavnuyu. Nakonec,
zametim, chto pri inicializacii deklariruemoj peremennoj mozhet ispol'zovat'sya
sokrashchennaya forma: vmesto VAR = #( LIST ) mozhno napisat' prosto VAR ( LIST
). Privedem primery dlya klassa VECTOR:
%VECTOR null = #(0f, 0f, 0f); !! nulevoj vektor
%VECTOR null (0f, 0f, 0f) !! (to zhe, koroche)
%VECTOR null (x: 0f, y: 0f, z: 0f) !! (to zhe, ochen' razvernuto)
!! koordinatnye vektory-orty
%VECTOR PX (1f, 0f, 0f), PY (0f, 1f, 0f), PZ (0f, 0f, 1f)
%VECTOR NX (-1f, 0f, 0f), NY (0f, -1f, 0f), NZ (0f, 0f, -1f)
Dlya naibolee trivial'nyh klassov, podobnyh klassu VECTOR, spiskovaya
inicializaciya yavlyaetsya samym prostym i udobnym sposobom sozdaniya ob®ekta.
Odnako, chasto nuzhny i klassy-"chernye yashchiki", imeyushchie netrivial'nuyu
vnutrennyuyu strukturu, celostnost' kotoroj dolzhna vsegda byt' obespechena.
Spiskovaya inicializaciya dlya nih neudobna i nenadezhna, i luchshe ispol'zovat'
special'nye funkcii -- konstruktory.
Vse konstruktory deklariruyutsya vnutri sootvetstvuyushchego klassa.
Sintaksis opisaniya takoj zhe, kak i u funkcij, tol'ko v kachestve
vozvrashchaemogo tipa ispol'zuetsya fiktivnyj tip constructor (na samom dele,
konstruktory ne vozvrashchayut znacheniya voobshche). V otlichie ot C++ i Java, vse
konstruktory v Kserione -- imenovannye: klass mozhet imet' proizvol'noe
kolichestvo konstruktorov, no ih imena dolzhny razlichat'sya (i ni odno iz nih
ne sovpadaet s imenem klassa). Tak, k opisaniyu klassa VECTOR my mogli by
dobavit' konstruktor:
!! inicializaciya vektora polyarnymi koordinatami
!! (len -- modul', phi -- dolgota, theta -- shirota)
sonstructor (float len, phi, theta) polar
{ x = len * sin(phi) * cos(theta), y = len * cos(phi) * cos(theta), z = len
* sin(theta) }
Tot zhe konstruktor mozhet byt' bolee kompaktno zapisan tak:
sonstructor (float len, phi, theta) polar :
(len * sin(phi) * cos(theta), len * cos(phi) * cos(theta), len * sin(theta)
) {}
Konstrukciya v kruglyh skobkah posle dvoetochiya -- eto tot zhe spiskovyj
inicializator dlya ob®ekta, elementy kotorogo mogut obrashchat'sya k parametram
konstruktora. V dannom sluchae mozhno vybrat', kakuyu imenno formu
ispol'zovat', no esli kakie-to komponenty klassa trebuyut netrivial'noj
inicializacii (naprimer, sami yavlyayutsya ob®ektami), ispol'zovat'
spisok-inicializator v konstruktore -- eto edinstvennyj korrektnyj sposob
zadat' im nachal'noe znachenie. Nezavisimo ot togo, kak konstruktor polar
opredelen, ispol'zovat' ego mozhno tak:
%VECTOR anyvec = :polar (200f, PI/4f, PI/6f)
Obratite vnimanie na dvoetochie pered vyzovom konstruktora: ono yavno
ukazyvaet na to, chto pri inicializacii budet ispol'zovan konstruktor dlya
etogo klassa.
Kak i v C++, v Kserione sushchestvuyut vremennye ob®ekty. Vremennyj ob®ekt
sozdaetsya libo ukazaniem spiska komponent, libo obrashcheniem k konstruktoru
(obychno kvalificirovannomu s pomoshch'yu operacii ‘.'). Naprimer:
VECTOR (0.5, 0.3, -0.7) !! vremennyj vektor
VECTOR.polar (10.0, 2f*PI, PI/2f) !! drugoj variant
Sushchestvovanie vremennyh ob®ektov obychno dlitsya ne dol'she, chem
vypolnyaetsya instrukciya, v kotoroj oni byli sozdany.
Ne tol'ko inicializaciya, no i deinicializaciya ob®ekta mozhet potrebovat'
netrivial'nyh dejstvij, poetomu dlya klassa mozhet byt' zadan destruktor. |to
-- prosto blok koda, opredelyayushchij dejstviya, neyavno vypolnyaemye pri
zavershenii sushchestvovaniya lyubogo ob®ekta klassa. U klassa ne byvaet bolee
odnogo destruktora. Dazhe esli destruktor ne zadan yavno, kompilyator chasto
sozdaet neyavnyj destruktor v teh sluchayah, kogda eto neobhodimo. Dejstviya,
opisannye v yavnom destruktore, vsegda vypolnyayutsya do vyzova neyavnogo.
Sobstvenno, yavnye destruktory nuzhny redko: v osnovnom oni trebuyutsya lish' v
teh sluchayah, kogda ob®ekt zadejstvuet kakie-to vneshnie po otnosheniyu k
programme resursy (skazhem, otkryvaet fajly ili ustanavlivaet setevye
soedineniya), a takzhe dlya otladochnyh celej i statistiki.
Ochen' kratko rassmotrim aspekty yazyka, svyazannye s nasledovaniem. Kak
uzhe govorilos', klass mozhet imet' superklass, i v etom sluchae on nasleduet
vse atributy superklassa, v dopolnenie k tem, kotorye opredelyaet sam.
Oblast' vidimosti klassa vlozhena v oblast' vidimosti superklassa, poetomu
lyubye atributy superklassa mogut byt' pereopredeleny v proizvodnom klasse.
Podklassu dostupny vse publichnye i vse zashchishchennye (no ne privatnye!)
deklaracii superklassa. Mehanizmy let i conceal dayut gibkie vozmozhnosti
upravleniya vidimost'yu atributov superklassa, pozvolyaya skryvat' ih ili davat'
im al'ternativnye imena.
Lyubaya funkciya, deklarirovannaya v nekotorom klasse, mozhet imet'
specifikator virtual. On oznachaet, chto dannaya funkciya yavlyaetsya virtual'noj
funkciej dannogo klassa, t.e. mozhet imet' al'ternativnuyu realizaciyu v lyubom
iz ego podklassov. Mehanizm virtualizacii vyzovov funkcij obespechivaet t.n.
dinamicheskoe svyazyvanie: v otlichie ot obychnogo svyazyvaniya, osnovannogo na
informacii o tipah vremeni kompilyacii, dlya virtual'noj funkcii vsegda
vyzyvaetsya imenno ta versiya, kotoraya neobhodima, ishodya iz dinamicheskoj
informacii o real'nom tipe ob®ekta dannogo klassa, dostupnoj pri vypolnenii
programmy. Pereopredelit' virtual'nuyu funkciyu ochen' prosto. Dlya etogo ee imya
dolzhno byt' vklyucheno v spisok pereopredeleniya instate, obychno zavershayushchij
deklaraciyu podklassa. Parametry i tip funkcii povtorno zadavat' ne nuzhno:
oni zhestko opredelyayutsya virtual-deklaraciej superklassa. Neredko v spiske
instate daetsya i realizaciya novoj versii virtual'noj funkcii; v protivnom
sluchae realizaciya dolzhna byt' dana pozdnee.
Esli virtual'naya funkciya ne pereopredelena v podklasse, nasleduetsya ee
versiya iz superklassa. Fakticheski, imya virtual'noj funkcii -- eto interfejs,
za kotorym skryvaetsya mnozhestvo razlichnyh funkcij. Nakonec, kak i v C++,
podklass mozhet yavno vyzvat' versiyu iz kakogo-nibud' superklassa s pomoshch'yu
polnost'yu kvalificirovannogo imeni.
Nakonec, govorya o nasledovanii klassov, nel'zya ne upomyanut' ob
abstraktnyh klassah (ili prosto abstraktah). Abstraktnyj klass -- eto klass,
dlya kotorogo ne sushchestvuet ni odnogo ob®ekta (i, sootvetstvenno, ne
opredelen tekushchij ekzemplyar) i kotoryj mozhet ispol'zovat'sya tol'ko v
kachestve proizvoditelya klassov-potomkov. Pri opisanii abstraktnogo klassa
ispol'zuetsya klyuchevoe slovo abstract vmesto class. Abstraktnye superklassy
prednaznacheny dlya realizacii bazovyh koncepcij, kotorye lezhat v osnove nekoj
gruppy rodstvennyh ob®ektov, no sami ne mogut imet' nikakogo "real'nogo
voploshcheniya".
Kak obychno, my prodemonstriruem nasledovanie, polimorfizm i abstrakty
na bolee-menee realistichnom primere (rabota s prostejshimi geometricheskimi
ob®ektami).
!! Geometricheskaya figura (abstraktnyj klass)
abstract Figure {
!! figura obychno imeet...
!! -- nekij perimetr:
float () virtual perimeter;
!! -- nekuyu ploshchad':
float () virtual area;
};
!! Tochka
class Point : Figure {
} instate #perimeter { return 0f }, #area { return 0f };
!Otrezok (dliny L)
class Line : Figure {
float L !! dlina
} instate #perimeter { return L }, #area { return 0f };
!! Kvadrat (so storonoj S)
class Square : Figure {
float S !! storona
} instate #perimeter { return 4 * S }, #area { return S * S };
!! Pryamougol'nik (so storonami A, B)
class Rectangle : Figure {
float A, B
} instate #perimeter { return 2 * (A + B) }, #area { return A * B };
!! Krug (s radiusom R)
class Circle : Figure {
float R
} instate #perimeter { return 2 * PI * R }, #area { return PI * R * R };
Pri vsej primitivnosti opredelennoj nami ierarhii ob®ektov, s nej uzhe
mozhno delat' chto-to soderzhatel'noe. K primeru sleduyushchij fragment
podschityvaet summarnuyu ploshchad' figur v massive ssylok na figury fig_vec:
%Figure @ []@ fig_vec; !! ssylka na vektor ssylok na figury
float total_area = 0f; !! summarnaya ploshchad'
for u_int i = 0 while i <> fig_vec# do ++ i
{ total_area += fig_vec [i].area () }
Nakonec my otmetim, chto virtual'nye funkcii -- eto ne edinstvennyj
polimorfnyj mehanizm v yazyke. Pri neobhodimosti mozhno ispol'zovat'
special'nuyu operaciyu yavnogo privedeniya ukazatelya na superklass k ukazatelyu
na podklass. Binarnaya operaciya kvalifikacii:
CLASS qual OBJ_PTR_EXPR
predprinimaet popytku preobrazovat' OBJ_PTR_EXPR (ukazatel' na nekij
ob®ekt) k ukazatelyu na klass CLASS (kotoryj dolzhen byt' podklassom
OBJ_PTR_EXPR^). Operaciya vozvrashchaet vyrazhenie tipa CLASS^: esli ob®ekt, na
kotoryj ukazyvaet vtoroj operand, dejstvitel'no yavlyaetsya ekzemplyarom klassa
CLASS, vozvrashchaetsya ukazatel' na nego, v protivnom sluchae vozvrashchaetsya
znachenie nil. Vot pochemu vozvrashchaemoe znachenie vsegda dolzhno proveryat'sya
prezhde, chem s nim predprinimayutsya dal'nejshie vychisleniya.
%Figure ^fig_ptr; !! ukazyvaet na figuru
%Rectangle some_rect (10f, 20f); !! pryamougol'nik 10 * 20
%Circle some_circ (50f); !! okruzhnost' radiusa 50
fig_ptr = some_rect@; !! fig_ptr ukazyvaet na pryamougol'nik
Rectangle qual fig_ptr; !! vernet ukazatel' na some_rect
Circle qual fig_ptr; !! vernet nil
fig_ptr = some_circ@; !! fig_ptr ukazyvaet na okruzhnost'
Rectangle qual fig_ptr; !! vernet nil
Circle qual fig_ptr; !! vernet ukazatel' na some_circ
Kvalifikaciya s pomoshch'yu qual ochen' pohozha na dinamicheskoe privedenie
tipov dynamic_cast v poslednih versiyah yazyka C++.
Opredelenie operacij
Kak i v C++, v Kserione predusmotreny sredstva dlya pereopredeleniya
operacij. Srazu zhe zametim, chto na samom dele korrektnee govorit' ob ih
doopredelenii: ne sushchestvuet sposoba pereopredelit' operaciyu, uzhe imeyushchuyu
smysl (naprimer, opredelit' operaciyu ‘-‘ tak, chtoby ona skladyvala celye
chisla). Odnako, esli operaciya ne opredelena dlya nekotoroj kombinacii tipov
operandov, to v etom sluchae ej mozhet byt' pripisana nekotoraya semantika.
Operacii -- prakticheski edinstvennyj mehanizm yazyka, gde dopustima
peregruzka v zavisimosti ot tipov operandov, i yazyk pozvolyaet rasprostranit'
etot princip i na proizvodnye tipy. (Sintaksis, prioritet ili
associativnost' operacii pereopredelyat', konechno, nel'zya.)
Novaya semantika operacii zadaetsya s pomoshch'yu special'nogo opisatelya
opdef:
opdef OP_DEF1 ‘=' EXPR1 (‘,' OP_DEF2 ‘=' EXPR2) ...
Kak i vse prochie opisaniya, opredeleniya operacij imeyut lokal'nyj
harakter. Kazhdyj element OPDEF -- eto konstrukciya, imitiruyushchaya sintaksis
sootvetstvuyushchej operacii, no vmesto operandov-vyrazhenij v nej zadayutsya tipy
dannyh. (Garantirovanno mogut ispol'zovat'sya lyubye primitivnye tipy i imena
klassov, no vozmozhno, v budushchem mozhno budet ispol'zovat' lyubye proizvodnye
tipy).
Sootvetstvuyushchee vyrazhenie EXPR budet podstavlyat'sya vmesto kombinacii
OPDEF. Pri etom v EXPR dopustimo ispol'zovanie special'nyh termov vida
(<1>), (<2>)..., sootvetstvuyushchih pervomu operandu, vtoromu i
t.p. Primer:
opdef VECTOR + VECTOR = VECTOR.add (<1>, <2>)
Zdes' opredelyaetsya novaya semantika operacii ‘+' dlya dvuh ob®ektov
klassa VECTOR. Vmesto etoj operacii budet podstavlen vyzov funkcii add
(predpolozhitel'no opredelennoj v klasse VECTOR) s oboimi operandami v
kachestve argumentov.
Fakticheski opredelenie operacii -- eto raznovidnost' makroopredeleniya,
i v semantike makropodstanovki imeetsya ochen' mnogo obshchego s
let-opredeleniyami. Tak, podstanovka yavlyaetsya semanticheskoj, a ne
tekstual'noj. No opredelennaya operaciya -- eto ne vyzov funkcii: i dlya samogo
opredeleniya i dlya vseh ego operandov dejstvuet semantika podstanovki, a ne
vyzova. Gromozdkoe opredelenie vyzovet generaciyu bol'shogo kolichestva lishnego
koda, a esli v tele opdef-opredeleniya ssylka na parametr vstrechaetsya
mnogokratno, sootvetstvuyushchij ej operand takzhe budet podstavlen neskol'ko raz
(chto, voobshche-to, ves'ma nezhelatel'no).
Nakonec, otmetim, chto dlya togo, chtoby opredelenie operacii bylo
zadejstvovano, trebuetsya tochnoe sootvetstvie real'nyh tipov operandov tipam
v opdef-deklaracii. Privedeniya tipov ne dopuskayutsya. (V dal'nejshem, pravda,
eto ogranichenie mozhet byt' oslableno.)
Privedem soderzhatel'nyj primer opredeleniya operacij. Pust' u nas
imeetsya klass String, realizuyushchij simvol'nye stroki, grubaya model' kotorogo
dana nizhe:
class String {
!! (opredeleniya...)
!! dlina tekushchej stroki
u_int () #length;
!! konkatenaciya (sceplenie) strok head & tail
%String (%String head, tail) #concat;
!! replikaciya (povtorenie n raz) stroki str
%String (%String str; u_int n) #repl;
!! podstroka stroki str (ot from do to)
%String (%String str; u_int from, to) #substr;
!! ...
}
Teper' opredelim nabor operacij, pozvolyayushchih rabotat' so strokami
proshche.
!! dlya kompaktnosti ...
let Str = String;
!! ‘#' kak dlina stroki:
opdef Str# = (<1>).len ();
!! ‘+' kak konkatenaciya:
opdef Str + Str = Str.concat ((<1>), (<2>));
!! ‘*' kak replikaciya:
opdef Str * u_int = Str.repl ((<1>), (<2>));
opdef u_int * Str = Str.repl ((<2>), (<1>));
!! otrezok kak podstroka
opdef Str [u_int..u_int] = Str.substr (<1>, <2>, <3>);
Opredelennye tak operacii dovol'no udobno ispol'zovat':
Str("ABBA")#; !! 4
Str("Hello, ") + Str("world!"); !! Str("Hello, world!")
Str("A") * 5; !! Str("AAAAA")
3 * Str("Ha ") + Str("!"); !! Str("Ha Ha Ha !")
Str("Main program entry") [5..12]; !! Str("program")
Kak uzhe govorilos', imeyushchiesya v yazyke operacii vvoda i vyvoda
prednaznacheny isklyuchitel'no dlya pereopredeleniya. Dlya bol'shinstva primitivnyh
tipov (i dlya mnogih ob®ektnyh) eti operacii pereopredeleny v standartnyh
bibliotekah vvoda-vyvoda, chto delaet ih ispol'zovanie ochen' prostym. Ih
razumnoe opredelenie dlya pol'zovatel'skih klassov -- rekomenduemaya praktika.
Tak, dlya upomyanutogo klassa VECTOR my mozhem opredelit' operaciyu vyvoda
(OFile -- klass vyhodnyh potokov):
opdef OFile <: VECTOR =
(<1>) <: ‘(‘ <: (<2>).x <: ‘,' <: (<2>).y
<: ‘,' <: (<2>).z <: ‘)'
Zametim, chto poskol'ku operaciya vyvoda levo- associativna i vozvrashchaet
v kachestve znacheniya svoj levyj operand (potok vyvoda), opredelennaya nami
operaciya takzhe budet obladat' etim svojstvom, chto ochen' horosho. No u etogo
opredeleniya est' i nedostatok: pravyj operand vychislyaetsya tri raza, chto
neeffektivno i chrevato pobochnymi effektami. V dannom sluchae eto legko
popravit':
opdef OFile <: VECTOR =
(<2>).((<1>) <: ‘(‘ <: x <: ‘,' <: y <: ‘,' <:
z <: ‘)')
No, voobshche-to govorya, esli opredelennaya tak operaciya vyvoda budet
ispol'zovat'sya intensivno, eto privedet k zametnomu pereizbytku
sgenerirovannogo koda. Luchshim resheniem budet opredelit' funkciyu dlya vyvoda
ob®ektov VECTOR, a potom, uzhe cherez nee, operaciyu.
Import i eksport.
Pragmaty.
V zavershenie nashego obzora rassmotrim mehanizmy, obespechivayushchie
vzaimodejstvie mezhdu Kserion-programmoj i vneshnej sredoj. Ponyatno, chto ni
odna real'naya programma ne mozhet obojtis' bez nih: naprimer, standartnye
sredstva vvoda-vyvoda i vzaimodejstviya s OS, matematicheskie funkcii,
sredstva obrabotki isklyuchenij -- vse eto nahoditsya v standartnyh bibliotekah
yazyka.
Programma sostoit iz logicheski nezavisimyh, no vzaimodejstvuyushchih mezhdu
soboj strukturnyh edinic, nazyvaemyh modulyami. Obychno odin modul'
sootvetstvuet odnomu fajlu ishodnogo koda programmy. Kazhdyj iz modulej mozhet
vzaimodejstvovat' s drugimi s pomoshch'yu mehanizmov eksporta (pozvolyayushchego emu
predostavlyat' svoi resursy drugim modulyam) i importa (pozvolyayushchego emu
ispol'zovat' resursy, predostavlennye drugimi modulyami).
Lyubye vneshnie ob®ekty modulya (naprimer, global'nye peremennye, funkcii,
tipy dannyh i klassy) mogut byt' eksportirovany vo vneshnyuyu sredu. |to
delaetsya za schet pomeshcheniya ih v blok deklaracii eksporta, imeyushchej vid:
export { DECLARATION_LIST }
V module mozhet byt' mnogo deklaracij eksporta, no tol'ko na samom
verhnem (global'nom) urovne ierarhii opisanij. Vse vneshnie ob®ekty,
opredelennye v spiske opisanij DECLARATION_LIST, stanut dostupnymi drugim
modulyam. CHtoby poluchit' k nim dostup, modul' dolzhen vospol'zovat'sya
deklaraciej importa, imeyushchej vid:
import MODULE { STMT_LIST }
V otlichie ot deklaracii eksporta, deklaraciya importa mozhet byt'
lokal'noj: ona mozhet vstretit'sya v lyubom bloke ili, k primeru, v deklaracii
klassa. Zdes' MODULE -- eto tekstovaya stroka, zadayushchaya imya modulya. V bolee
obshchem sluchae, eto imya importiruemogo resursa, kotoryj mozhet byt' global'nym
(obshchesistemnym) ili dazhe setevym (sintaksis MODULE zavisit ot realizacii i
zdes' ne rassmotren). STMT_LIST -- proizvol'nyj spisok instrukcij, v kotorom
budet dostupno vse, eksportirovannoe resursom MODULE. V chastnosti, on mozhet
soderzhat' drugie deklaracii import, chto pozvolyaet importirovat' opisaniya iz
neskol'kih modulej.
Tochnaya semantika mehanizma importa/eksporta -- slishkom slozhnaya tema,
chtoby rassmatrivat' ee zdes' v detalyah. Esli kratko, to peredache cherez etot
mehanizm mogut podvergat'sya deklaracii peremennyh i funkcij, klassov, vse
opredelennye pol'zovatelem tipy, makroopredeleniya i operacii. Zametim, chto
kazhdyj modul' fakticheski sostoit iz vneshnej (deklarativnoj) i vnutrennej
(realizacionnoj) chastej. Dlya pravil'noj kompilyacii vseh importerov etogo
modulya trebuetsya lish' znanie pervoj iz nih; realizacionnaya chast' modulya (v
vide sgenerirovannogo koda) ostaetsya privatnoj.
Nakonec, sushchestvuet special'noe sluzhebnoe sredstvo dlya upravleniya
processom kompilyacii -- pragmaty:
pragma PRAGMA_STR
Literal'naya stroka PRAGMA_STR soderzhit direktivy kompilyatoru, nabor
kotoryh takzhe mozhet sil'no zaviset' ot realizacii i poka opredelen ochen'
priblizitel'no. Predpolagaetsya, chto pragmaty budut zadavat' opcii
kompilyatora, takie, kak rezhimy kodogeneracii, obrabotki preduprezhdenij i
oshibok, vyvoda listinga i t.p.
Perspektivy razvitiya i nerealizovannye vozmozhnosti yazyka
Kserion -- yazyk poka eshche ochen' molodoj i ves'ma dalekij ot
sovershenstva. V processe razrabotki yazyka u ego sozdatelej voznikali samye
raznye idei otnositel'no vozmozhnostej ego dal'nejshego razvitiya -- kak v
kratkosrochnoj, tak i v "strategicheskoj" perspektive. Na nekotoryh iz etih
idej stoit ostanovit'sya podrobnee.
Tak, prakticheski neizbezhnym predstavlyaetsya vklyuchenie v yazyk
let-makroopredelenij s parametrami. Funkcional'no oni budut pohozhi na
parametrizovannye #define C-preprocessora -- no, v otlichie ot poslednih, oni
budut, podobno opdef'am, imet' strogo tipizovannye parametry i analogichnuyu
semantiku podstanovki. Ne isklyucheno, chto parametrizovannye makroopredeleniya
budut dazhe dopuskat' peregruzku i vybor odnogo iz variantov na osnove tipov
argumentov.
V bolee otdalennoj perspektive, vozmozhno, poyavitsya i stol' moshchnyj
makro-mehanizm, kak shablony (template) dlya deklaracij klassov i funkcij,
podobnye analogichnym sredstvam v C++. Odnako, poka trudno uverenno skazat',
kakoj vid primet etot mehanizm v okonchatel'noj forme.
Sejchas v yazyke otsutstvuyut kakie-libo formy instrukcii vybora,
analogichnoj switch/case v C i C++, no ih otsutstvie ochen' chuvstvuetsya.
Skoree vsego, kogda analogichnyj mehanizm budet vklyuchen v yazyk, on budet
sushchestvenno bolee moshchnym. V chastnosti, on budet dopuskat' nelinejnuyu logiku
perebora i bolee slozhnye kriterii proverki "sluchaev".
Bezuslovno, bylo by ochen' poleznym takzhe vvedenie v yazyk mehanizma
perechislimyh tipov (enum), podobnogo imeyushchimsya i v Paskale, i v C.
Na povestke dnya stoyat i bolee slozhnye voprosy. Dolzhno li v Kserione
byt' realizovano mnozhestvennoe nasledovanie, kak v C++? |tot vopros yavlyaetsya
odnim iz samyh spornyh. Vozmozhny raznye varianty: polnyj zapret
mnozhestvennogo nasledovaniya (chto vryad li priemlimo), mnozhestvennoe
nasledovanie tol'ko ot special'nyh abstraktnyh klassov-interfejsov (takoj
podhod prinyat v Java), nasledovanie tol'ko ot nerodstvennyh
klassov-roditelej, i, nakonec, nasledovanie bez kakih-libo ogranichenij.
Est' dostatochno mnogo neyasnyh voprosov, svyazannyh s aspektami zashchity
soderzhimogo klassov. V nastoyashchej redakcii yazyka prinyat namnogo bolee
liberal'nyj podhod k etomu voprosu, chem v C++ i Java. YAzyk dopuskaet
raznoobraznye mehanizmy inicializacii ekzemplyara klassa (ekzemplyarom,
spiskom komponent, konstruktorom i, nakonec, vsegda dostupna avtomaticheskaya
neyavnaya inicializaciya). Kak pravilo, ob®ekty vsegda inicializiruyutsya nekim
"razumnym" obrazom, odnako mozhet vozniknut' potrebnost' i v klassah --
"chernyh yashchikah", inicializaciya kotoryh proishodit isklyuchitel'no cherez
posredstvo konstruktorov. S samoj semantikoj konstruktorov takzhe est'
nekotorye neyasnosti.
Nakonec, diskussionnym yavlyaetsya vopros o tom, kakie sredstva dolzhny
byt' vstroeny v yazyk, a kakie -- realizovany v standartnyh bibliotekah.
Naprimer, obrabotka isklyuchenij (a v budushchem, vozmozhno, i mnogopotochnost')
planirovalos' realizovat' kak vneshnie bibliotechnye sredstva -- no protiv
takogo podhoda takzhe est' ser'eznye vozrazheniya.
Vprochem, chto by ne planirovali razrabotchiki -- okonchatel'nyj vybor, kak
my nadeemsya, budet prinadlezhat' samim pol'zovatelyam yazyka.
Zaklyuchenie
V zaklyuchenie privedem nebol'shoj, no vpolne realistichnyj primer
zavershennogo Kserion-modulya, realizuyushchego prostejshie operacii nad
kompleksnymi chislami.
!!
!! Ishodnyj fajl: "complex.xrn"
!! Realizaciya klassa `complex`:
!! kompleksnye chisla (immutabel'nye)
!!
!! vneshnie funkcii (v real'noj programme importiruemye):
double (double x, y) #atan2; !! dvuhargumentnyj arktangens
double (double x, y) #hypot; !! gipotenuza
double (double x) #sqrt; !! kvadratnyj koren'
class complex {
!! komponenty klassa
double Re, Im; !! (real, imag)
!! [Unarnye operacii nad %complex]
%complex (%complex op1) %opUnary;
%opUnary #conj; !! Sopryazhenie
%opUnary #neg; !! Otricanie
%opUnary #sqrt; !! Kvadratnyj koren'
!! [Binarnye operacii nad %complex]
%complex (%complex op1, op2) %opBinary;
%opBinary #add; !! Slozhenie
%opBinary #sub; !! Vychitanie
%opBinary #mul; !! Umnozhenie
%opBinary #div; !! Delenie
!! Proverka na nul'
bool () is_zero { return Re -- 0f && Im -- 0f };
!! [Sravneniya dlya %complex]
bool (%complex op1, op2) %opCompare;
!! (na ravenstvo):
%opCompare eq { return op1.Re -- op2.Re && op1.Im -- op2.Im };
!! (na neravenstvo):
%opCompare ne { return op1.Re <> op2.Re || op1.Im <> op2.Im
};
!! Modul'
double (%complex op) mod { return hypot (op.Re, op.Im) };
!! Argument
double (%complex op) arg { return atan2 (op.Re, op.Im) };
};
!! Realizaciya predeklarirovannyh funkcij
!! Sopryazhennoe dlya op1
#complex.conj { return #(op1.Re, - op1.Im) };
!! Otricanie op1
#complex.neg { return #(- op1.Re, - op1.Im) };
!! Slozhenie op1 i op2
#complex.add { return #(op1.Re + op2.Re, op1.Im + op2.Im) };
!! Vychitanie op1 i op2
#complex.sub { return #(op1.Re - op2.Re, op1.Im - op2.Im) };
!! Proizvedenie op1 i op2
#complex.mul {
return #(op1.Re * op2.Re - op1.Im * op2.Im,
op1.Im * op2.Re + op1.Re * op2.Im)
};
!! CHastnoe op1 i op2
#complex.div {
!! (delitel' dolzhen byt' nenulevoj)
assert ~op2.is_zero ();
double denom = op2.Re * op2.Re + op2.Im * op2.Im;
return # ((op1.Re * op2.Re + op1.Im * op2.Im) / denom,
- (op1.Re * op2.Im + op2.Re * op1.Im) / denom)
};
let g_sqrt = sqrt; !! (global'naya funkciya `sqrt`)
!! Kvadratnyj koren' iz op1 (odno iz znachenij)
#complex.sqrt {
double norm = complex.mod (op1);
return #(g_sqrt ((norm + op1.Re) / 2f), g_sqrt ((norm - op1.Re) / 2f))
};
!!
!! Operacii dlya raboty s complex
!!
!! unarnyj '-' kak otricanie
opdef -complex = complex.neg ((<1>));
!! unarnyj '~' kak sopryazhenie
opdef ~complex = complex.conj ((<1>));
!! binarnyj '+' kak slozhenie
opdef complex + complex = complex.add ((<1>), (<2>));
!! binarnyj '-' kak vychitanie
opdef complex - complex = complex.sub ((<1>), (<2>));
!! binarnyj '*' kak umnozhenie
opdef complex * complex = complex.mul ((<1>), (<2>));
!! binarnyj '/' kak delenie
opdef complex / complex = complex.div ((<1>), (<2>));
Last-modified: Sun, 08 Sep 2002 05:41:28 GMT