ym v opisanii funkcii, tochno tak zhe, kak esli by inicializirovalas' peremennaya opisannogo tipa. |to garantiruet nadlezhashchuyu proverku i preobrazovaniya tipov. Naprimer, vyzov funkcii pow(12.3,"abcd") translyator sochtet oshibochnym, poskol'ku "abcd" yavlyaetsya strokoj, a ne parametrom tipa int. V vyzove pow(2,i) translyator preobrazuet celuyu konstantu (celoe 2) v chislo s plavayushchej tochkoj (float), kak togo trebuet funkciya. Funkciya pow mozhet byt' opredelena sleduyushchim obrazom: float pow ( float x, int n ) { if ( n < 0 ) error ( "oshibka: dlya pow () zadan otricatel'nyj pokazatel'"); switch ( n ) { case 0: return 1; case 1: return x; default: return x * pow ( x, n-1 ); } } Pervaya chast' opredeleniya funkcii zadaet ee imya, tip vozvrashchaemogo znacheniya (esli ono est'), a takzhe tipy i imena formal'nyh parametrov (esli oni sushchestvuyut). Znachenie vozvrashchaetsya iz funkcii s pomoshch'yu operatora return. Raznye funkcii obychno imeyut raznye imena, no funkciyam, vypolnyayushchim shodnye operacii nad ob容ktami raznyh tipov, luchshe dat' odno imya. Esli tipy parametrov takih funkcij razlichny, to translyator vsegda mozhet razobrat'sya, kakuyu funkciyu nuzhno vyzyvat'. Naprimer, mozhno imet' dve funkcii vozvedeniya v stepen': odnu - dlya celyh chisel, a druguyu - dlya chisel s plavayushchej tochkoj: int pow ( int, int ); double pow ( double, double ); //... x = pow ( 2,10 ); // vyzov pow ( int, int ) y = pow ( 2.0, 10.0 );// vyzov pow ( double, double ) Takoe mnogokratnoe ispol'zovanie imeni nazyvaetsya peregruzkoj imeni funkcii ili prosto peregruzkoj; peregruzka rassmatrivaetsya osobo v glave 7. Parametry funkcii mogut peredavat'sya libo "po znacheniyu", libo "po ssylke". Rassmotrim opredelenie funkcii, kotoraya osushchestvlyaet vzaimoobmen znachenij dvuh celyh peremennyh. Esli ispol'zuetsya standartnyj sposob peredachi parametrov po znacheniyu, to pridetsya peredavat' ukazateli: void swap ( int * p, int * q ) { int t = * p; * p = * q; * q = t; } Unarnaya operaciya * nazyvaetsya kosvennost'yu (ili operaciej razymenovaniya), ona vybiraet znachenie ob容kta, na kotoryj nastroen ukazatel'. Funkciyu mozhno vyzyvat' sleduyushchim obrazom: void f ( int i, int j ) { swap ( & i, & j ); } Esli ispol'zovat' peredachu parametra po ssylke, mozhno obojtis' bez yavnyh operacij s ukazatelem: void swap (int & r1, int & r2 ) { int t = r1; r1 = r2; r2 = t; } void g ( int i, int j ) { swap ( i, j ); } Dlya lyubogo tipa T zapis' T& oznachaet "ssylka na T". Ssylka sluzhit sinonimom toj peremennoj, kotoroj ona inicializirovalas'. Otmetim, chto peregruzka dopuskaet sosushchestvovanie dvuh funkcij swap v odnoj programme. 1.3.6 Moduli Programma S++ pochti vsegda sostoit iz neskol'kih razdel'no transliruemyh "modulej". Kazhdyj "modul'" obychno nazyvaetsya ishodnym fajlom, no inogda - edinicej translyacii. On sostoit iz posledovatel'nosti opisanij tipov, funkcij, peremennyh i konstant. Opisanie extern pozvolyaet iz odnogo ishodnogo fajla ssylat'sya na funkciyu ili ob容kt, opredelennye v drugom ishodnom fajle. Naprimer: extern "C" double sqrt ( double ); extern ostream cout; Samyj rasprostranennyj sposob obespechit' soglasovannost' opisanij vneshnih vo vseh ishodnyh fajlah - pomestit' takie opisaniya v special'nye fajly, nazyvaemye zagolovochnymi. Zagolovochnye fajly mozhno vklyuchat' vo vse ishodnye fajly, v kotoryh trebuyutsya opisaniya vneshnih. Naprimer, opisanie funkcii sqrt hranitsya v zagolovochnom fajle standartnyh matematicheskih funkcij s imenem math.h, poetomu, esli nuzhno izvlech' kvadratnyj koren' iz 4, mozhno napisat': #include <math.h> //... x = sqrt ( 4 ); Poskol'ku standartnye zagolovochnye fajly mogut vklyuchat'sya vo mnogie ishodnye fajly, v nih net opisanij, dublirovanie kotoryh moglo by vyzvat' oshibki. Tak, telo funkcii prisutstvuet v takih fajlah, esli tol'ko eto funkciya-podstanovka, a inicializatory ukazany tol'ko dlya konstant ($$4.3). Ne schitaya takih sluchaev, zagolovochnyj fajl obychno sluzhit hranilishchem dlya tipov, on predostavlyaet interfejs mezhdu razdel'no transliruemymi chastyami programmy. V komande vklyucheniya zaklyuchennoe v uglovye skobki imya fajla (v nashem primere - <math.h>) ssylaetsya na fajl, nahodyashchijsya v standartnom kataloge vklyuchaemyh fajlov. CHasto eto - katalog /usr/include/CC. Fajly, nahodyashchiesya v drugih katalogah, oboznachayutsya svoimi putevymi imenami, vzyatymi v kavychki. Poetomu v sleduyushchih komandah: #include "math1.h" #include "/usr/bs/math2.h" vklyuchayutsya fajl math1.h iz tekushchego kataloga pol'zovatelya i fajl math2.h iz kataloga /usr/bs. Privedem nebol'shoj zakonchennyj primer, v kotorom stroka opredelyaetsya v odnom fajle, a pechataetsya v drugom. V fajle header.h opredelyayutsya nuzhnye tipy: // header.h extern char * prog_name; extern void f (); Fajl main.c yavlyaetsya osnovnoj programmoj: // main.c #include "header.h" char * prog_name = "primitivnyj, no zakonchennyj primer"; int main () { f (); } a stroka pechataetsya funkciej iz fajla f.c: // f.c #include <stream.h> #include "header.h" void f () { cout << prog_name << '\n'; } Pri zapuske translyatora S++ i peredache emu neobhodimyh fajlov-parametrov v razlichnyh realizaciyah mogut ispol'zovat'sya raznye rasshireniya imen dlya programm na S++. Na mashine avtora translyaciya i zapusk programmy vyglyadit tak: $ CC main.c f.c -o silly $ silly primitivnyj, no zakonchennyj primer $ Krome razdel'noj translyacii koncepciyu modul'nosti v S++ podderzhivayut klassy ($$5.4). 1.4 Podderzhka abstrakcii dannyh Podderzhka programmirovaniya s abstrakciej dannyh v osnovnom svoditsya k vozmozhnosti opredelit' nabor operacij (funkcii i operacii) nad tipom. Vse obrashcheniya k ob容ktam etogo tipa ogranichivayutsya operaciyami iz zadannogo nabora. Odnako, imeya takie vozmozhnosti, programmist skoro obnaruzhivaet, chto dlya udobstva opredeleniya i ispol'zovaniya novyh tipov nuzhny eshche nekotorye rasshireniya yazyka. Horoshim primerom takogo rasshireniya yavlyaetsya peregruzka operacij. 1.4.1 Inicializaciya i udalenie Kogda predstavlenie tipa skryto, neobhodimo dat' pol'zovatelyu sredstva dlya inicializacii peremennyh etogo tipa. Prostejshee reshenie - do ispol'zovaniya peremennoj vyzyvat' nekotoruyu funkciyu dlya ee inicializacii. Naprimer: class vector { // ... public: void init ( init size ); // vyzov init () pered pervym // ispol'zovaniem ob容kta vector // ... }; void f () { vector v; // poka v nel'zya ispol'zovat' v.init ( 10 ); // teper' mozhno } No eto nekrasivoe i chrevatoe oshibkami reshenie. Budet luchshe, esli sozdatel' tipa opredelit dlya inicializacii peremennyh nekotoruyu special'nuyu funkciyu. Esli takaya funkciya est', to dve nezavisimye operacii razmeshcheniya i inicializacii peremennoj sovmeshchayutsya v odnoj (inogda ee nazyvayut installyaciej ili prosto postroeniem). Funkciya inicializacii nazyvaetsya konstruktorom. Konstruktor vydelyaetsya sredi vseh prochih funkcij dannogo klassa tem, chto imeet takoe zhe imya, kak i sam klass. Esli ob容kty nekotorogo tipa stroyatsya netrivial'no, to nuzhna eshche odna dopolnitel'naya operaciya dlya udaleniya ih posle poslednego ispol'zovaniya. Funkciya udaleniya v S++ nazyvaetsya destruktorom. Destruktor imeet to zhe imya, chto i ego klass, no pered nim stoit simvol ~ (v S++ etot simvol ispol'zuetsya dlya operacii dopolneniya). Privedem primer: class vector { int sz; // chislo elementov int * v; // ukazatel' na celye public: vector ( int ); // konstruktor ~vector (); // destruktor int& operator [] ( int index ); // operaciya indeksacii }; Konstruktor klassa vector mozhno ispol'zovat' dlya kontrolya nad oshibkami i vydeleniya pamyati: vector::vector ( int s ) { if ( s <= 0 ) error ( "nedopustimyj razmer vektora" ); sz = s; v = new int [ s ]; // razmestit' massiv iz s celyh } Destruktor klassa vector osvobozhdaet ispol'zovavshuyusya pamyat': vector::~vector () { delete [] v; // osvobodit' massiv, na kotoryj // nastroen ukazatel' v } Ot realizacii S++ ne trebuetsya osvobozhdeniya vydelennoj s pomoshch'yu new pamyati, esli na nee bol'she ne ssylaetsya ni odin ukazatel' (inymi slovami, ne trebuetsya avtomaticheskaya "sborka musora"). V zamen etogo mozhno bez vmeshatel'stva pol'zovatelya opredelit' v klasse sobstvennye funkcii upravleniya pamyat'yu. |to tipichnyj sposob primeneniya konstruktorov i destruktorov, hotya est' mnogo ne svyazannyh s upravleniem pamyat'yu primenenij etih funkcij (sm., naprimer, $$9.4). 1.4.2 Prisvaivanie i inicializaciya Dlya mnogih tipov zadacha upravleniya imi svoditsya k postroeniyu i unichtozheniyu svyazannyh s nimi ob容ktov, no est' tipy, dlya kotoryh etogo malo. Inogda neobhodimo upravlyat' vsemi operaciyami kopirovaniya. Vernemsya k klassu vector: void f () { vector v1 ( 100 ); vector v2 = v1; // postroenie novogo vektora v2, // inicializiruemogo v1 v1 = v2; // v2 prisvaivaetsya v1 // ... } Dolzhna byt' vozmozhnost' opredelit' interpretaciyu operacij inicializacii v2 i prisvaivaniya v1. Naprimer, v opisanii: class vector { int * v; int sz; public: // ... void operator = ( const vector & ); // prisvaivanie vector ( const vector & ); // inicializaciya }; ukazyvaetsya, chto prisvaivanie i inicializaciya ob容ktov tipa vector dolzhny vypolnyat'sya s pomoshch'yu opredelennyh pol'zovatelem operacij. Prisvaivanie mozhno opredelit' tak: void vector::operator = ( const vector & a ) // kontrol' razmera i kopirovanie elementov { if ( sz != a.sz ) error ( "nedopustimyj razmer vektora dlya =" ); for ( int i = 0; i < sz; i++ ) v [ i ] = a.v [ i ]; } Poskol'ku eta operaciya ispol'zuet dlya prisvaivaniya "staroe znachenie" vektora, operaciya inicializacii dolzhna zadavat'sya drugoj funkciej, naprimer, takoj: vector::vector ( const vector & a ) // inicializaciya vektora znacheniem drugogo vektora { sz = a.sz; // razmer tot zhe v = new int [ sz ]; // vydelit' pamyat' dlya massiva for ( int i = 0; i < sz; i++ ) //kopirovanie elementov v [ i ] = a.v [ i ]; } V yazyke S++ konstruktor vida T(const T&) nazyvaetsya konstruktorom kopirovaniya dlya tipa T. Lyubuyu inicializaciyu ob容ktov tipa T on vypolnyaet s pomoshch'yu znacheniya nekotorogo drugogo ob容kta tipa T. Pomimo yavnoj inicializacii konstruktory vida T(const T&) ispol'zuyutsya dlya peredachi parametrov po znacheniyu i polucheniya vozvrashchaemogo funkciej znacheniya. 1.4.3 SHablony tipa Zachem programmistu mozhet ponadobit'sya opredelit' takoj tip, kak vektor celyh chisel? Kak pravilo, emu nuzhen vektor iz elementov, tip kotoryh neizvesten sozdatelyu klassa Vector. Sledovatel'no, nado sumet' opredelit' tip vektora tak, chtoby tip elementov v etom opredelenii uchastvoval kak parametr, oboznachayushchij "real'nye" tipy elementov: template < class T > class Vector { // vektor elementov tipa T T * v; int sz; public: Vector ( int s ) { if ( s <= 0 ) error ( "nedopustimyj dlya Vector razmer" ); v = new T [ sz = s ]; // vydelit' pamyat' dlya massiva s tipa T } T & operator [] ( int i ); int size () { return sz; } // ... }; Takovo opredelenie shablona tipa. On zadaet sposob polucheniya semejstva shodnyh klassov. V nashem primere shablon tipa Vector pokazyvaet, kak mozhno poluchit' klass vektor dlya zadannogo tipa ego elementov. |to opisanie otlichaetsya ot obychnogo opisaniya klassa nalichiem nachal'noj konstrukcii template<class T>, kotoraya i pokazyvaet, chto opisyvaetsya ne klass, a shablon tipa s zadannym parametrom-tipom (zdes' on ispol'zuetsya kak tip elementov). Teper' mozhno opredelyat' i ispol'zovat' vektora raznyh tipov: void f () { Vector < int > v1 ( 100 ); // vektor iz 100 celyh Vector < complex > v2 ( 200 ); // vektor iz 200 // kompleksnyh chisel v2 [ i ] = complex ( v1 [ x ], v1 [ y ] ); // ... } Vozmozhnosti, kotorye realizuet shablon tipa, inogda nazyvayutsya parametricheskimi tipami ili genericheskimi ob容ktami. Ono shodno s vozmozhnostyami, imeyushchimisya v yazykah Clu i Ada. Ispol'zovanie shablona tipa ne vlechet za soboj kakih-libo dopolnitel'nyh rashodov vremeni po sravneniyu s ispol'zovaniem klassa, v kotorom vse tipy ukazany neposredstvenno. 1.4.4 Obrabotka osobyh situacij Po mere rosta programm, a osobenno pri aktivnom ispol'zovanii bibliotek poyavlyaetsya neobhodimost' standartnoj obrabotki oshibok (ili, v bolee shirokom smysle, "osobyh situacij"). YAzyki Ada, Algol-68 i Clu podderzhivayut standartnyj sposob obrabotki osobyh situacij. Snova vernemsya k klassu vector. CHto nuzhno delat', kogda operacii indeksacii peredano znachenie indeksa, vyhodyashchee za granicy massiva? Sozdatel' klassa vector ne znaet, na chto rasschityvaet pol'zovatel' v takom sluchae, a pol'zovatel' ne mozhet obnaruzhit' podobnuyu oshibku (esli by mog, to eta oshibka voobshche ne voznikla by). Vyhod takoj: sozdatel' klassa obnaruzhivaet oshibku vyhoda za granicu massiva, no tol'ko soobshchaet o nej neizvestnomu pol'zovatelyu. Pol'zovatel' sam prinimaet neobhodimye mery. Naprimer: class vector { // opredelenie tipa vozmozhnyh osobyh situacij class range { }; // ... }; Vmesto vyzova funkcii oshibki v funkcii vector::operator[]() mozhno perejti na tu chast' programmy, v kotoroj obrabatyvayutsya osobye situacii. |to nazyvaetsya "zapustit' osobuyu situaciyu" ("throw the exception"): int & vector::operator [] ( int i ) { if ( i < 0 || sz <= i ) throw range (); return v [ i ]; } V rezul'tate iz steka budet vybirat'sya informaciya, pomeshchaemaya tuda pri vyzovah funkcij, do teh por, poka ne budet obnaruzhen obrabotchik osoboj situacii s tipom range dlya klassa vektor (vector::range); on i budet vypolnyat'sya. Obrabotchik osobyh situacij mozhno opredelit' tol'ko dlya special'nogo bloka: void f ( int i ) { try { // v etom bloke obrabatyvayutsya osobye situacii // s pomoshch'yu opredelennogo nizhe obrabotchika vector v ( i ); // ... v [ i + 1 ] = 7; // privodit k osoboj situacii range // ... g (); // mozhet privesti k osoboj situacii range // na nekotoryh vektorah } catch ( vector::range ) { error ( "f (): vector range error" ); return; } } Ispol'zovanie osobyh situacij delaet obrabotku oshibok bolee uporyadochennoj i ponyatnoj. Obsuzhdenie i podrobnosti otlozhim do glavy 9. 1.4.5 Preobrazovaniya tipov Opredelyaemye pol'zovatelem preobrazovaniya tipa, naprimer, takie, kak preobrazovanie chisla s plavayushchej tochkoj v kompleksnoe, kotoroe neobhodimo dlya konstruktora complex(double), okazalis' ochen' poleznymi v S++. Programmist mozhet zadavat' eti preobrazovaniya yavno, a mozhet polagat'sya na translyator, kotoryj vypolnyaet ih neyavno v tom sluchae, kogda oni neobhodimy i odnoznachny: complex a = complex ( 1 ); complex b = 1; // neyavno: 1 -> complex ( 1 ) a = b + complex ( 2 ); a = b + 2; // neyavno: 2 -> complex ( 2) Preobrazovaniya tipov nuzhny v S++ potomu, chto arifmeticheskie operacii so smeshannymi tipami yavlyayutsya normoj dlya yazykov, ispol'zuemyh v chislovyh zadachah. Krome togo, bol'shaya chast' pol'zovatel'skih tipov, ispol'zuemyh dlya "vychislenij" (naprimer, matricy, stroki, mashinnye adresa) dopuskaet estestvennoe preobrazovanie v drugie tipy (ili iz drugih tipov). Preobrazovaniya tipov sposobstvuyut bolee estestvennoj zapisi programmy: complex a = 2; complex b = a + 2; // eto oznachaet: operator + ( a, complex ( 2 )) b = 2 + a; // eto oznachaet: operator + ( complex ( 2 ), a ) V oboih sluchayah dlya vypolneniya operacii "+" nuzhna tol'ko odna funkciya, a ee parametry edinoobrazno traktuyutsya sistemoj tipov yazyka. Bolee togo, klass complex opisyvaetsya tak, chto dlya estestvennogo i besprepyatstvennogo obobshcheniya ponyatiya chisla net neobhodimosti chto-to izmenyat' dlya celyh chisel. 1.4.6 Mnozhestvennye realizacii Osnovnye sredstva, podderzhivayushchie ob容ktno-orientirovannoe programmirovanie, a imenno: proizvodnye klassy i virtual'nye funkcii,- mozhno ispol'zovat' i dlya podderzhki abstrakcii dannyh, esli dopustit' neskol'ko realizacij odnogo tipa. Vernemsya k primeru so stekom: template < class T > class stack { public: virtual void push ( T ) = 0; // chistaya virtual'naya funkciya virtual T pop () = 0; // chistaya virtual'naya funkciya }; Oboznachenie =0 pokazyvaet, chto dlya virtual'noj funkcii ne trebuetsya nikakogo opredeleniya, a klass stack yavlyaetsya abstraktnym, t.e. on mozhet ispol'zovat'sya tol'ko kak bazovyj klass. Poetomu steki mozhno ispol'zovat', no ne sozdavat': class cat { /* ... */ }; stack < cat > s; // oshibka: stek - abstraktnyj klass void some_function ( stack <cat> & s, cat kitty ) // normal'no { s.push ( kitty ); cat c2 = s.pop (); // ... } Poskol'ku interfejs steka nichego ne soobshchaet o ego predstavlenii, ot pol'zovatelej steka polnost'yu skryty detali ego realizacii. Mozhno predlozhit' neskol'ko razlichnyh realizacij steka. Naprimer, stek mozhet byt' massivom: template < class T > class astack : public stack < T > { // istinnoe predstavlenie ob容kta tipa stek // v dannom sluchae - eto massiv // ... public: astack ( int size ); ~astack (); void push ( T ); T pop (); }; Mozhno realizovat' stek kak svyazannyj spisok: template < class T > class lstack : public stack < T > { // ... }; Teper' mozhno sozdavat' i ispol'zovat' steki: void g () { lstack < cat > s1 ( 100 ); astack < cat > s2 ( 100 ); cat Ginger; cat Snowball; some_function ( s1, Ginger ); some_function ( s2, Snowball ); } O tom, kak predstavlyat' steki raznyh vidov, dolzhen bespokoit'sya tol'ko tot, kto ih sozdaet (t.e. funkciya g()), a pol'zovatel' steka (t.e. avtor funkcii some_function()) polnost'yu ograzhden ot detalej ih realizacii. Platoj za podobnuyu gibkost' yavlyaetsya to, chto vse operacii nad stekami dolzhny byt' virtual'nymi funkciyami. 1.5 Podderzhka ob容ktno-orientirovannogo programmirovaniya Podderzhku ob容ktno-orientirovannogo programmirovaniya obespechivayut klassy vmeste s mehanizmom nasledovaniya, a takzhe mehanizm vyzova funkcij-chlenov v zavisimosti ot istinnogo tipa ob容kta (delo v tom, chto vozmozhny sluchai, kogda etot tip neizvesten na stadii translyacii). Osobenno vazhnuyu rol' igraet mehanizm vyzova funkcij-chlenov. Ne menee vazhny sredstva, podderzhivayushchie abstrakciyu dannyh (o nih my govorili ranee). Vse dovody v pol'zu abstrakcii dannyh i baziruyushchihsya na nej metodov, kotorye pozvolyayut estestvenno i krasivo rabotat' s tipami, dejstvuyut i dlya yazyka, podderzhivayushchego ob容ktno-orientirovannoe programmirovanie. Uspeh oboih metodov zavisit ot sposoba postroeniya tipov, ot togo, naskol'ko oni prosty, gibki i effektivny. Metod ob容ktno-orientirovannogo programmirovaniya pozvolyaet opredelyat' bolee obshchie i gibkie pol'zovatel'skie tipy po sravneniyu s temi, kotorye poluchayutsya, esli ispol'zovat' tol'ko abstrakciyu dannyh. 1.5.1 Mehanizm vyzova Osnovnoe sredstvo podderzhki ob容ktno-orientirovannogo programmirovaniya - eto mehanizm vyzova funkcii-chlena dlya dannogo ob容kta, kogda istinnyj tip ego na stadii translyacii neizvesten. Pust', naprimer, est' ukazatel' p. Kak proishodit vyzov p->rotate(45)? Poskol'ku S++ baziruetsya na staticheskom kontrole tipov, zadayushchee vyzov vyrazhenie imeet smysl tol'ko pri uslovii, chto funkciya rotate() uzhe byla opisana. Dalee, iz oboznacheniya p->rotate() my vidim, chto p yavlyaetsya ukazatelem na ob容kt nekotorogo klassa, a rotate dolzhna byt' chlenom etogo klassa. Kak i pri vsyakom staticheskom kontrole tipov proverka korrektnosti vyzova nuzhna dlya togo, chtoby ubedit'sya (naskol'ko eto vozmozhno na stadii translyacii), chto tipy v programme ispol'zuyutsya neprotivorechivym obrazom. Tem samym garantiruetsya, chto programma svobodna ot mnogih vidov oshibok. Itak, translyatoru dolzhno byt' izvestno opisanie klassa, analogichnoe tem, chto privodilis' v $$1.2.5: class shape { // ... public: // ... virtual void rotate ( int ); // ... }; a ukazatel' p dolzhen byt' opisan, naprimer, tak: T * p; gde T - klass shape ili proizvodnyj ot nego klass. Togda translyator vidit, chto klass ob容kta, na kotoryj nastroen ukazatel' p, dejstvitel'no imeet funkciyu rotate(), a funkciya imeet parametr tipa int. Znachit, p->rotate(45) korrektnoe vyrazhenie. Poskol'ku shape::rotate() byla opisana kak virtual'naya funkciya, nuzhno ispol'zovat' mehanizm vyzova virtual'noj funkcii. CHtoby uznat', kakuyu imenno iz funkcij rotate sleduet vyzvat', nuzhno do vyzova poluchit' iz ob容kta nekotoruyu sluzhebnuyu informaciyu, kotoraya byla pomeshchena tuda pri ego sozdanii. Kak tol'ko ustanovleno, kakuyu funkciyu nado vyzvat', dopustim circle::rotate, proishodit ee vyzov s uzhe upominavshimsya kontrolem tipa. Obychno v kachestve sluzhebnoj informacii ispol'zuetsya tablica adresov funkcij, a translyator preobrazuet imya rotate v indeks etoj tablicy. S uchetom etoj tablicy ob容kt tipa shape mozhno predstavit' tak: center vtbl: color &X::draw &Y::rotate ... ... Funkcii iz tablicy virtual'nyh funkcij vtbl pozvolyayut pravil'no rabotat' s ob容ktom dazhe v teh sluchayah, kogda v vyzyvayushchej funkcii neizvestny ni tablica vtbl, ni raspolozhenie dannyh v chasti ob容kta, oboznachennoj ... . Zdes' kak X i Y oboznacheny imena klassov, v kotorye vhodyat vyzyvaemye funkcii. Dlya ob容kta circle oba imeni X i Y est' circle. Vyzov virtual'noj funkcii mozhet byt' po suti stol' zhe effektiven, kak vyzov obychnoj funkcii. 1.5.2 Proverka tipa Neobhodimost' kontrolya tipa pri obrashcheniyah k virtual'nym funkciyam mozhet okazat'sya opredelennym ogranicheniem dlya razrabotchikov bibliotek. Naprimer, horosho by predostavit' pol'zovatelyu klass "stek chego-ugodno". Neposredstvenno v S++ eto sdelat' nel'zya. Odnako, ispol'zuya shablony tipa i nasledovanie, mozhno priblizit'sya k toj effektivnosti i prostote proektirovaniya i ispol'zovaniya bibliotek, kotorye svojstvenny yazykam s dinamicheskim kontrolem tipov. K takim yazykam otnositsya, naprimer, yazyk Smalltalk, na kotorom mozhno opisat' "stek chego-ugodno". Rassmotrim opredelenie steka s pomoshch'yu shablona tipa: template < class T > class stack { T * p; int sz; public: stack ( int ); ~stack (); void push ( T ); T & pop (); }; Ne oslablyaya staticheskogo kontrolya tipov, mozhno ispol'zovat' takoj stek dlya hraneniya ukazatelej na ob容kty tipa plane (samolet): stack < plane * > cs ( 200 ); void f () { cs.push ( new Saab900 ); // Oshibka pri translyacii : // trebuetsya plane*, a peredan car* cs.push ( new Saab37B ); // prekrasno: Saab 37B - na samom // dele samolet, t.e. tipa plane cs.pop () -> takeoff (); cs.pop () -> takeoff (); } Esli staticheskogo kontrolya tipov net, privedennaya vyshe oshibka obnaruzhitsya tol'ko pri vypolnenii programmy: // primer dinamicheskoe kontrolya tipa // vmesto staticheskogo; eto ne S++ Stack s; // stek mozhet hranit' ukazateli na ob容kty // proizvol'nogo tipa void f () { s.push ( new Saab900 ); s.push ( new Saab37B ); s.pop () -> takeoff (); // prekrasno: Saab 37B - samolet cs.pop () -> takeoff (); // dinamicheskaya oshibka: // mashina ne mozhet vzletet' } Dlya sposoba opredeleniya, dopustima li operaciya nad ob容ktom, obychno trebuetsya bol'she dopolnitel'nyh rashodov, chem dlya mehanizma vyzova virtual'nyh funkcij v S++. Rasschityvaya na staticheskij kontrol' tipov i vyzov virtual'nyh funkcij, my prihodim k inomu stilyu programmirovaniya, chem nadeyas' tol'ko na dinamicheskij kontrol' tipov. Klass v S++ zadaet strogo opredelennyj interfejs dlya mnozhestva ob容ktov etogo i lyubogo proizvodnogo klassa, togda kak v Smalltalk klass zadaet tol'ko minimal'no neobhodimoe chislo operacij, i pol'zovatel' vprave primenyat' nezadannye v klasse operacii. Inymi slovami, klass v S++ soderzhit tochnoe opisanie operacij, i pol'zovatelyu garantiruetsya, chto tol'ko eti operacii translyator sochtet dopustimymi. 1.5.3 Mnozhestvennoe nasledovanie Esli klass A yavlyaetsya bazovym klassom dlya B, to B nasleduet atributy A. t.e. B soderzhit A plyus eshche chto-to. S uchetom etogo stanovitsya ochevidno, chto horosho, kogda klass B mozhet nasledovat' iz dvuh bazovyh klassov A1 i A2. |to nazyvaetsya mnozhestvennym nasledovaniem. Privedem nekij tipichnyj primer mnozhestvennogo nasledovaniya. Pust' est' dva bibliotechnyh klassa displayed i task. Pervyj predstavlyaet zadachi, informaciya o kotoryh mozhet vydavat'sya na ekran s pomoshch'yu nekotorogo monitora, a vtoroj - zadachi, vypolnyaemye pod upravleniem nekotorogo dispetchera. Programmist mozhet sozdavat' sobstvennye klassy, naprimer, takie: class my_displayed_task: public displayed, public task { // tekst pol'zovatelya }; class my_task: public task { // eta zadacha ne izobrazhaetsya // na ekrane, t.k. ne soderzhit klass displayed // tekst pol'zovatelya }; class my_displayed: public displayed { // a eto ne zadacha // t.k. ne soderzhit klass task // tekst pol'zovatelya }; Esli nasledovat'sya mozhet tol'ko odin klass, to pol'zovatelyu dostupny tol'ko dva iz treh privedennyh klassov. V rezul'tate libo poluchaetsya dublirovanie chastej programmy, libo teryaetsya gibkost', a, kak pravilo, proishodit i to, i drugoe. Privedennyj primer prohodit v S++ bezo vsyakih dopolnitel'nyh rashodov vremeni i pamyati po sravneniyu s programmami, v kotoryh nasleduetsya ne bolee odnogo klassa. Staticheskij kontrol' tipov ot etogo tozhe ne stradaet. Vse neodnoznachnosti vyyavlyayutsya na stadii translyacii: class task { public: void trace (); // ... }; class displayed { public: void trace (); // ... }; class my_displayed_task:public displayed, public task { // v etom klasse trace () ne opredelyaetsya }; void g ( my_displayed_task * p ) { p -> trace (); // oshibka: neodnoznachnost' } V etom primere vidny otlichiya S++ ot ob容ktno-orientirovannyh dialektov yazyka Lisp, v kotoryh est' mnozhestvennoe nasledovanie. V etih dialektah neodnoznachnost' razreshaetsya tak: ili schitaetsya sushchestvennym poryadok opisaniya, ili schitayutsya identichnymi ob容kty s odnim i tem zhe imenem v raznyh bazovyh klassah, ili ispol'zuyutsya kombinirovannye sposoby, kogda sovpadenie ob容ktov dolya bazovyh klassov sochetaetsya s bolee slozhnym sposobom dlya proizvodnyh klassov. V S++ neodnoznachnost', kak pravilo, razreshaetsya vvedeniem eshche odnoj funkcii: class my_displayed_task:public displayed, public task { // ... public: void trace () { // tekst pol'zovatelya displayed::trace (); // vyzov trace () iz displayed task::trace (); // vyzov trace () iz task } // ... }; void g ( my_displayed_task * p ) { p -> trace (); // teper' normal'no } 1.5.4 Inkapsulyaciya Pust' chlenu klassa (nevazhno funkcii-chlenu ili chlenu, predstavlyayushchemu dannye) trebuetsya zashchita ot "nesankcionirovannogo dostupa". Kak razumno ogranichit' mnozhestvo funkcij, kotorym takoj chlen budet dostupen? Ochevidnyj otvet dlya yazykov, podderzhivayushchih ob容ktno-orientirovannoe programmirovanie, takov: dostup imeyut vse operacii, kotorye opredeleny dlya etogo ob容kta, inymi slovami, vse funkcii-chleny. Naprimer: class window { // ... protected: Rectangle inside; // ... }; class dumb_terminal : public window { // ... public: void prompt (); // ... }; Zdes' v bazovom klasse window chlen inside tipa Rectangle opisyvaetsya kak zashchishchennyj (protected), no funkcii-chleny proizvodnyh klassov, naprimer, dumb_terminal::prompt(), mogut obratit'sya k nemu i vyyasnit', s kakogo vida oknom oni rabotayut. Dlya vseh drugih funkcij chlen window::inside nedostupen. V takom podhode sochetaetsya vysokaya stepen' zashchishchennosti (dejstvitel'no, vryad li vy "sluchajno" opredelite proizvodnyj klass) s gibkost'yu, neobhodimoj dlya programm, kotorye sozdayut klassy i ispol'zuyut ih ierarhiyu (dejstvitel'no, "dlya sebya" vsegda mozhno v proizvodnyh klassah predusmotret' dostup k zashchishchennym chlenam). Neochevidnoe sledstvie iz etogo: nel'zya sostavit' polnyj i okonchatel'nyj spisok vseh funkcij, kotorym budet dostupen zashchishchennyj chlen, poskol'ku vsegda mozhno dobavit' eshche odnu, opredeliv ee kak funkciyu-chlen v novom proizvodnom klasse. Dlya metoda abstrakcii dannyh takoj podhod chasto byvaet malo priemlemym. Esli yazyk orientiruetsya na metod abstrakcii dannyh, to ochevidnoe dlya nego reshenie - eto trebovanie ukazyvat' v opisanii klassa spisok vseh funkcij, kotorym nuzhen dostup k chlenu. V S++ dlya etoj celi ispol'zuetsya opisanie chastnyh (private) chlenov. Ono ispol'zovalos' i v privodivshihsya opisaniyah klassov complex i shape. Vazhnost' inkapsulyacii, t.e. zaklyucheniya chlenov v zashchitnuyu obolochku, rezko vozrastaet s rostom razmerov programmy i uvelichivayushchimsya razbrosom oblastej prilozheniya. V $$6.6 bolee podrobno obsuzhdayutsya vozmozhnosti yazyka po inkapsulyacii. 1.6 Predely sovershenstva YAzyk S++ proektirovalsya kak "luchshij S", podderzhivayushchij abstrakciyu dannyh i ob容ktno-orientirovannoe programmirovanie. Pri etom on dolzhen byt' prigodnym dlya bol'shinstva osnovnyh zadach sistemnogo programmirovaniya. Osnovnaya trudnost' dlya yazyka, kotoryj sozdavalsya v raschete na metody upryatyvaniya dannyh, abstrakcii dannyh i ob容ktno-orientirovannogo programmirovaniya, v tom, chto dlya togo, chtoby byt' yazykom obshchego naznacheniya, on dolzhen: - idti na tradicionnyh mashinah; - sosushchestvovat' s tradicionnymi operacionnymi sistemami i yazykami; - sopernichat' s tradicionnymi yazykami programmirovaniya v effektivnosti vypolneniya programmy; - byt' prigodnym vo vseh osnovnyh oblastyah prilozheniya. |to znachit, chto dolzhny byt' vozmozhnosti dlya effektivnyh chislovyh operacij (arifmetika s plavayushchej tochkoj bez osobyh nakladnyh rashodov, inache pol'zovatel' predpochtet Fortran) i sredstva takogo dostupa k pamyati, kotoryj pozvolit pisat' na etom yazyke drajvery ustrojstv. Krome togo, nado umet' pisat' vyzovy funkcij v dostatochno neprivychnoj zapisi, prinyatoj dlya obrashchenij v tradicionnyh operacionnyh sistemah. Nakonec, dolzhna byt' vozmozhnost' iz yazyka, podderzhivayushchego ob容ktno-orientirovannoe programmirovanie, vyzyvat' funkcii, napisannye na drugih yazykah, a iz drugih yazykov vyzyvat' funkciyu na etom yazyke, podderzhivayushchem ob容ktno-orientirovannoe programmirovanie. Dalee, nel'zya rasschityvat' na shirokoe ispol'zovanie iskomogo yazyka programmirovaniya kak yazyka obshchego naznacheniya, esli realizaciya ego celikom polagaetsya na vozmozhnosti, kotorye otsutstvuyut v mashinah s tradicionnoj arhitekturoj. Esli ne vvodit' v yazyk vozmozhnosti nizkogo urovnya, to pridetsya dlya osnovnyh zadach bol'shinstva oblastej prilozheniya ispol'zovat' nekotorye yazyki nizkogo urovnya, naprimer S ili assembler. No S++ proektirovalsya s raschetom, chto v nem mozhno sdelat' vse, chto dopustimo na S, prichem bez uvelicheniya vremeni vypolneniya. Voobshche, S++ proektirovalsya, ishodya iz principa, chto ne dolzhno voznikat' nikakih dopolnitel'nyh zatrat vremeni i pamyati, esli tol'ko etogo yavno ne pozhelaet sam programmist. YAzyk proektirovalsya v raschete na sovremennye metody translyacii, kotorye obespechivayut proverku soglasovannosti programmy, ee effektivnost' i kompaktnost' predstavleniya. Osnovnym sredstvom bor'by so slozhnost'yu programm viditsya, prezhde vsego, strogij kontrol' tipov i inkapsulyaciya. Osobenno eto kasaetsya bol'shih programm, sozdavaemyh mnogimi lyud'mi. Pol'zovatel' mozhet ne yavlyat'sya odnim iz sozdatelej takih programm, i mozhet voobshche ne byt' programmistom. Poskol'ku nikakuyu nastoyashchuyu programmu nel'zya napisat' bez podderzhki bibliotek, sozdavaemyh drugimi programmistami, poslednee zamechanie mozhno otnesti prakticheski ko vsem programmam. S++ proektirovalsya dlya podderzhki togo principa, chto vsyakaya programma est' model' nekotoryh sushchestvuyushchih v real'nosti ponyatij, a klass yavlyaetsya konkretnym predstavleniem ponyatiya, vzyatogo iz oblasti prilozheniya ($$12.2). Poetomu klassy pronizyvayut vsyu programmu na S++, i nalagayutsya zhestkie trebovaniya na gibkost' ponyatiya klassa, kompaktnost' ob容ktov klassa i effektivnost' ih ispol'zovaniya. Esli rabotat' s klassami budet neudobno ili slishkom nakladno, to oni prosto ne budut ispol'zovat'sya, i programmy vyrodyatsya v programmy na "luchshem S". Znachit pol'zovatel' ne sumeet nasladit'sya temi vozmozhnostyami, radi kotoryh, sobstvenno, i sozdavalsya yazyk.  * GLAVA 2. OPISANIYA I KONSTANTY "Sovershenstvo dostizhimo tol'ko v moment kraha". (S.N. Parkinson) V dannoj glave opisany osnovnye tipy (char, int, float i t.d.) i sposoby postroeniya na ih osnove novyh tipov (funkcij, vektorov, ukazatelej i t.d.). Opisanie vvodit v programmu imya, ukazav ego tip i, vozmozhno, nachal'noe znachenie. V etoj glave vvodyatsya takie ponyatiya, kak opisanie i opredelenie, tipy, oblast' vidimosti imen, vremya zhizni ob容ktov. Dayutsya oboznacheniya literal'nyh konstant S++ i sposoby zadaniya simvolicheskih konstant. Privodyatsya primery, kotorye prosto demonstriruyut vozmozhnosti yazyka. Bolee osmyslennye primery, illyustriruyushchie vozmozhnosti vyrazhenij i operatorov yazyka S++, budut privedeny v sleduyushchej glave. V etoj glave lish' upominayutsya sredstva dlya opredeleniya pol'zovatel'skih tipov i operacij nad nimi. Oni obsuzhdayutsya v glavah 5 i 7. 2.1 OPISANIYA Imya (identifikator) sleduet opisat' prezhde, chem ono budet ispol'zovat'sya v programme na S++. |to oznachaet, chto nuzhno ukazat' ego tip, chtoby translyator znal, k kakogo vida ob容ktam otnositsya imya. Nizhe privedeny neskol'ko primerov, illyustriruyushchih vse raznoobrazie opisanij: char ch; int count = 1; char* name = "Njal"; struct complex { float re, im; }; complex cvar; extern complex sqrt(complex); extern int error_number; typedef complex point; float real(complex* p) { return p->re; }; const double pi = 3.1415926535897932385; struct user; template<class T> abs(T a) { return a<0 ? -a : a; } enum beer { Carlsberg, Tuborg, Thor }; Iz etih primerov vidno, chto rol' opisanij ne svoditsya lish' k privyazke tipa k imeni. Bol'shinstvo ukazannyh opisanij odnovremenno yavlyayutsya opredeleniyami, t.e. oni sozdayut ob容kt, na kotoryj ssylaetsya imya. Dlya ch, count, name i cvar takim ob容ktom yavlyaetsya element pamyati sootvetstvuyushchego razmera. |tot element budet ispol'zovat'sya kak peremennaya, i govoryat, chto dlya nego otvedena pamyat'. Dlya real podobnym ob容ktom budet zadannaya funkciya. Dlya konstanty pi ob容ktom budet chislo 3.1415926535897932385. Dlya complex ob容ktom budet novyj tip. Dlya point ob容ktom yavlyaetsya tip complex, poetomu point stanovitsya sinonimom complex. Sleduyushchie opisaniya uzhe ne yavlyayutsya opredeleniyami: extern complex sqrt(complex); extern int error_number; struct user; |to oznachaet, chto ob容kty, vvedennye imi, dolzhny byt' opredeleny gde-to v drugom meste programmy. Telo funkcii sqrt dolzhno byt' ukazano v kakom-to drugom opisanii. Pamyat' dlya peremennoj error_number tipa int dolzhna vydelyat'sya v rezul'tate drugogo opisaniya error_number. Dolzhno byt' i kakoe-to drugoe opisanie tipa user, iz kotorogo mozhno ponyat', chto eto za tip. V programme na yazyke S++ dolzhno byt' tol'ko odno opredelenie kazhdogo imeni, no opisanij mozhet byt' mnogo. Odnako vse opisaniya dolzhny byt' soglasovany po tipu vvodimogo v nih ob容kta. Poetomu v privedennom nizhe fragmente soderzhatsya dve oshibki: int count; int count; // oshibka: pereopredelenie extern int error_number; extern short error_number; // oshibka: nesootvetstvie tipov Zato v sleduyushchem fragmente net ni odnoj oshibki (ob ispol'zovanii extern sm. #4.2): extern int error_number; extern int error_number; V nekotoryh opisaniyah ukazyvayutsya "znacheniya" ob容ktov, kotorye oni opredelyayut: struct complex { float re, im; }; typedef complex point; float real(complex* p) { return p->re }; const double pi = 3.1415926535897932385; Dlya tipov, funkcij i konstant "znachenie" ostaetsya neizmennym; dlya dannyh, ne yavlyayushchihsya konstantami, nachal'noe znachenie mozhet vposledstvii izmenyat'sya: int count = 1; char* name = "Bjarne"; //... count = 2; name = "Marian"; Iz vseh opredelenij tol'ko sleduyushchee ne zadaet znacheniya: char ch; Vsyakoe opisanie, kotoroe zadaet znachenie, yavlyaetsya opredeleniem. 2.1.1 Oblast' vidimosti Opisaniem opredelyaetsya oblast' vidimosti imeni. |to znachit, chto imya mozhet ispol'zovat'sya tol'ko v opredelennoj chasti teksta programmy. Esli imya opisano v funkcii (obychno ego nazyvayut "lokal'nym imenem"), to oblast' vidimosti imeni prostiraetsya ot tochki opisaniya do konca bloka, v kotorom poyavilos' eto opisanie. Esli imya ne nahoditsya v opisanii funkcii ili klassa (ego obychno nazyvayut "global'nym imenem"), to oblast' vidimosti prostiraetsya ot tochki opisaniya do konca fajla, v kotorom poyavilos' eto opisanie. Opisanie imeni v bloke mozhet skryvat' opisanie v ob容mlyushchem bloke ili global'noe imya; t.e. imya mozhet byt' pereopredeleno tak, chto ono budet oboznachat' drugoj ob容kt vnutri bloka. Posle vyhoda iz bloka prezhnee znachenie imeni (esli ono bylo) vosstanavlivaetsya. Privedem primer: int x; // global'noe x void f() { int x; // lokal'noe x skryvaet global'noe x x = 1; // prisvoit' lokal'nomu x { int x; // skryvaet pervoe lokal'noe x x = 2; // prisvoit' vtoromu lokal'nomu x } x = 3; // prisvoit' pervomu lokal'nomu x } int* p = &x; // vzyat' adres global'nogo x V bol'shih programmah ne izbezhat' pereopredeleniya imen. K sozhaleniyu, chelovek legko mozhet proglyade