ssy-"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>));