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 kla