// ... public: int open(const char*, const char*); }; int my_file::jpen(const char* name, const char* spec) { // ... if (::open(name,flag)) { // ispol'zuetsya open() iz UNIX(2) // ... } // ... } 5.4.3 Vlozhennye klassy Opisanie klassa mozhet byt' vlozhennym. Naprimer: class set { struct setmem { int mem; setmem* next; setmem(int m, setmem* n) { mem=m; next=n; } }; setmem* first; public: set() { first=0; } insert(int m) { first = new setmem(m,first); } // ... }; Dostupnost' vlozhennogo klassa ogranichivaetsya oblast'yu vidimosti leksicheski ob容mlyushchego klassa: setmem m1(1,0); // oshibka: setmem ne nahoditsya // v global'noj oblasti vidimosti Esli tol'ko opisanie vlozhennogo klassa ne yavlyaetsya sovsem prostym, to luchshe opisyvat' etot klass otdel'no, poskol'ku vlozhennye opisaniya mogut stat' ochen' zaputannymi: class setmem { friend class set; // dostupno tol'ko dlya chlenov set int mem; setmem* next; setmem(int m, setmem* n) { mem=m; next=n; } // mnogo drugih poleznyh chlenov }; class set { setmem* first; public: set() { first=0; } insert(int m) { first = new setmem(m,first); } // ... }; Poleznoe svojstvo vlozhennosti - eto sokrashchenie chisla global'nyh imen, a nedostatok ego v tom, chto ono narushaet svobodu ispol'zovaniya vlozhennyh tipov (sm. $$12.3). Imya klassa-chlena (vlozhennogo klassa) mozhno ispol'zovat' vne opisaniya ob容mlyushchego ego klassa tak zhe, kak imya lyubogo drugogo chlena: class X { struct M1 { int m; }; public: struct M2 { int m; }; M1 f(M2); }; void f() { M1 a; // oshibka: imya `M1' vne oblasti vidimosti M2 b; // oshibka: imya `M1' vne oblasti vidimosti X::M1 c; // oshibka: X::M1 chastnyj chlen X::M2 d; // normal'no } Otmetim, chto kontrol' dostupa proishodit i dlya imen vlozhennyh klassov. V funkcii-chlene oblast' vidimosti klassa nachinaetsya posle utochneniya X:: i prostiraetsya do konca opisaniya funkcii. Naprimer: M1 X::f(M2 a) // oshibka: imya `M1' vne oblasti vidimosti { /* ... */ } X::M1 X::f(M2 a) // normal'no { /* ... */ } X::M1 X::f(X::M2 a) // normal'no, no tret'e utochnenie X:: izlishne { /* ... */ } 5.4.4 Staticheskie chleny Klass - eto tip, a ne nekotoroe dannoe, i dlya kazhdogo ob容kta klassa sozdaetsya svoya kopiya chlenov, predstavlyayushchih dannye. Odnako, naibolee udachnaya realizaciya nekotoryh tipov trebuet, chtoby vse ob容kty etogo tipa imeli nekotorye obshchie dannye. Luchshe, esli eti dannye mozhno opisat' kak chast' klassa. Naprimer, v operacionnyh sistemah ili pri modelirovanii upravleniya zadachami chasto nuzhen spisok zadach: class task { // ... static task* chain; // ... }; Opisav chlen chain kak staticheskij, my poluchaem garantiyu, chto on budet sozdan v edinstvennom chisle, t.e. ne budet sozdavat'sya dlya kazhdogo ob容kta task. No on nahoditsya v oblasti vidimosti klassa task, i mozhet byt' dostupen vne etoj oblasti, esli tol'ko opisan v obshchej chasti. V etom sluchae imya chlena dolzhno utochnyat'sya imenem klassa: if (task::chain == 0) // kakie-to operatory V funkcii-chlene ego mozhno oboznachat' prosto chain. Ispol'zovanie staticheskih chlenov klassa mozhet zametno sokratit' potrebnost' v global'nyh peremennyh. Opisyvaya chlen kak staticheskij, my ogranichivaem ego oblast' vidimosti i delaem ego nezavisimym ot otdel'nyh ob容ktov ego klassa. |to svojstvo polezno kak dlya funkcij-chlenov, tak i dlya chlenov, predstavlyayushchih dannye: class task { // ... static task* task_chain; static void shedule(int); // ... }; No opisanie staticheskogo chlena - eto tol'ko opisanie, i gde-to v programme dolzhno byt' edinstvennoe opredelenie dlya opisyvaemogo ob容kta ili funkcii, naprimer, takoe: task* task::task_chain = 0; void task::shedule(int p) { /* ... */ } Estestvenno, chto i chastnye chleny mogut opredelyat'sya podobnym obrazom. Otmetim, chto sluzhebnoe slovo static ne nuzhno i dazhe nel'zya ispol'zovat' v opredelenii staticheskogo chlena klassa. Esli by ono prisutstvovalo, voznikla by neodnoznachnost': ukazyvaet li ono na to, chto chlen klassa yavlyaetsya staticheskim, ili ispol'zuetsya dlya opisaniya global'nogo ob容kta ili funkcii? Slovo static odno iz samyh peregruzhennyh sluzhebnyh slov v S i S++. K staticheskomu chlenu, predstavlyayushchemu dannye, otnosyatsya oba osnovnyh ego znacheniya: "staticheski razmeshchaemyj" , t.e. protivopolozhnyj ob容ktam, razmeshchaemym v steke ili svobodnoj pamyati, i "staticheskij" v smysle s ogranichennoj oblast'yu vidimosti, t.e. protivopolozhnyj ob容ktam, podlezhashchim vneshnemu svyazyvaniyu. K funkciyam-chlenam otnositsya tol'ko vtoroe znachenie static. 5.4.5 Ukazateli na chleny Mozhno brat' adres chlena klassa. Operaciya vzyatiya adresa funkcii-chlena chasto okazyvaetsya poleznoj, poskol'ku celi i sposoby primeneniya ukazatelej na funkcii, o kotoryh my govorili v $$4.6.9, v ravnoj stepeni otnosyatsya i k takim funkciyam. Ukazatel' na chlen mozhno poluchit', primeniv operaciyu vzyatiya adresa & k polnost'yu utochnennomu imeni chlena klassa, naprimer, &class_name::member_name. CHtoby opisat' peremennuyu tipa "ukazatel' na chlen klassa X", nado ispol'zovat' opisatel' vida X::*. Naprimer: #include <iostream.h> struct cl { char* val; void print(int x) { cout << val << x << '\n'; } cl(char* v) { val = v; } }; Ukazatel' na chlen mozhno opisat' i ispol'zovat' tak: typedef void (cl::*PMFI)(int); int main() { cl z1("z1 "); cl z2("z2 "); cl* p = &z2; PMFI pf = &cl::print; z1.print(1); (z1.*pf)(2); z2.print(3); (p->*pf)(4); } Ispol'zovanie typedef dlya zameny trudno vosprinimaemogo opisatelya v S dostatochno tipichnyj sluchaj. Operacii .* i ->* nastraivayut ukazatel' na konkretnyj ob容kt, vydavaya v rezul'tate funkciyu, kotoruyu mozhno vyzyvat'. Prioritet operacii () vyshe, chem u operacij .* i ->*, poetomu nuzhny skobki. Vo mnogih sluchayah virtual'nye funkcii ($$6.2.5) uspeshno zamenyayut ukazateli na funkcii. 5.4.6 Struktury i ob容dineniya Po opredeleniyu struktura - eto klass, vse chleny kotorogo obshchie, t.e. opisanie struct s { ... eto prosto kratkaya forma opisaniya class s { public: ... Poimenovannoe ob容dinenie opredelyaetsya kak struktura, vse chleny kotoroj imeyut odin i tot zhe adres ($$R.9.5). Esli izvestno, chto v kazhdyj moment vremeni ispol'zuetsya znachenie tol'ko odnogo chlena struktury, to ob座aviv ee ob容dineniem, mozhno sekonomit' pamyat'. Naprimer, mozhno ispol'zovat' ob容dinenie dlya hraneniya leksem translyatora S: union tok_val { char* p; // stroka char v[8]; // identifikator (ne bolee 8 simvolov) long i; // znacheniya celyh double d; // znacheniya chisel s plavayushchej tochkoj }; Problema s ob容dineniyami v tom, chto translyator v obshchem sluchae ne znaet, kakoj chlen ispol'zuetsya v dannyj moment, i poetomu kontrol' tipa nevozmozhen. Naprimer: void strange(int i) { tok_val x; if (i) x.p = "2"; else x.d = 2; sqrt(x.d); // oshibka, esli i != 0 } Krome togo, opredelennoe takim obrazom ob容dinenie nel'zya inicializirovat' takim kazhushchimsya vpolne estestvennym sposobom: tok_val val1 = 12; // oshibka: int prisvaivaetsya tok_val tok_val val2 = "12"; // oshibka: char* prisvaivaetsya tok_val Dlya pravil'noj inicializacii nado ispol'zovat' konstruktory: union tok_val { char* p; // stroka char v[8]; // identifikator (ne bolee 8 simvolov) long i; // znacheniya celyh double d; // znacheniya chisel s plavayushchej tochkoj tok_val(const char*); // nuzhno vybirat' mezhdu p i v tok_val(int ii) { i = ii; } tok_val(double dd) { d = dd; } }; |ti opisaniya pozvolyayut razreshit' s pomoshch'yu tipa chlenov neodnoznachnost' pri peregruzke imeni funkcii (sm. $$4.6.6 i $$7.3). Naprimer: void f() { tok_val a = 10; // a.i = 10 tok_val b = 10.0; // b.d = 10.0 } Esli eto nevozmozhno (naprimer, dlya tipov char* i char[8] ili int i char i t.d.), to opredelit', kakoj chlen inicializiruetsya, mozhno, izuchiv inicializator pri vypolnenii programmy, ili vvedya dopolnitel'nyj parametr. Naprimer: tok_val::tok_val(const char* pp) { if (strlen(pp) <= 8) strncpy(v,pp,8); // korotkaya stroka else p = pp; // dlinnaya stroka } No luchshe podobnoj neodnoznachnosti izbegat'. Standartnaya funkciya strncpy() podobno strcpy() kopiruet stroki, no u nee est' dopolnitel'nyj parametr, zadayushchij maksimal'noe chislo kopiruemyh simvolov. To, chto dlya inicializacii ob容dineniya ispol'zuyutsya konstruktory, eshche ne garantiruet ot sluchajnyh oshibok pri rabote s ob容dineniem, kogda prisvaivaetsya znachenie odnogo tipa, a vybiraetsya znachenie drugogo tipa. Takuyu garantiyu mozhno poluchit', esli zaklyuchit' ob容dinenie v klass, v kotorom budet otslezhivat'sya tip zanosimogo znacheniya : class tok_val { public: enum Tag { I, D, S, N }; private: union { const char* p; char v[8]; long i; double d; }; Tag tag; void check(Tag t) { if (tag != t) error(); } public: Tag get_tag() { return tag; } tok_val(const char* pp); tok_val(long ii) { i = ii; tag = I; } tok_val(double dd) { d = dd; tag = D; } long& ival() { check(I); return i; } double& fval() { check(D); return d; } const char*& sval() { check(S); return p; } char* id() { check(N); return v; } }; tok_val::tok_val(const char* pp) { if (strlen(pp) <= 8) { // korotkaya stroka tag = N; strncpy(v,pp,8); } else { // dlinnaya stroka tag = S; p = pp; // zapisyvaetsya tol'ko ukazatel' } } Ispol'zovat' klass tok_val mozhno tak: void f() { tok_val t1("korotkaya"); // prisvaivaetsya v tok_val t2("dlinnaya stroka"); // prisvaivaetsya p char s[8]; strncpy(s,t1.id(),8); // normal'no strncpy(s,t2.id(),8); // check() vydast oshibku } Opisav tip Tag i funkciyu get_tag() v obshchej chasti, my garantiruem, chto tip tok_val mozhno ispol'zovat' kak tip parametra. Takim obrazom, poyavlyaetsya nadezhnaya v smysle tipov al'ternativa opisaniyu parametrov s ellipsisom. Vot, naprimer, opisanie funkcii obrabotki oshibok, kotoraya mozhet imet' odin, dva, ili tri parametra s tipami char*, int ili double: extern tok_val no_arg; void error( const char* format, tok_val a1 = no_arg, tok_val a2 = no_arg, tok_val a3 = no_arg); 5.5 Konstruktory i destruktory Esli u klassa est' konstruktor, on vyzyvaetsya vsyakij raz pri sozdanii ob容kta etogo klassa. Esli u klassa est' destruktor, on vyzyvaetsya vsyakij raz, kogda unichtozhaetsya ob容kt etogo klassa. Ob容kt mozhet sozdavat'sya kak: [1] avtomaticheskij, kotoryj sozdaetsya kazhdyj raz, kogda ego opisanie vstrechaetsya pri vypolnenii programmy, i unichtozhaetsya po vyhode iz bloka, v kotorom on opisan; [2] staticheskij, kotoryj sozdaetsya odin raz pri zapuske programmy i unichtozhaetsya pri ee zavershenii; [3] ob容kt v svobodnoj pamyati, kotoryj sozdaetsya operaciej new i unichtozhaetsya operaciej delete; [4] ob容kt-chlen, kotoryj sozdaetsya v processe sozdaniya drugogo klassa ili pri sozdanii massiva, elementom kotorogo on yavlyaetsya. Krome etogo ob容kt mozhet sozdavat'sya, esli v vyrazhenii yavno ispol'zuetsya ego konstruktor ($$7.3) ili kak vremennyj ob容kt ($$R.12.2). V oboih sluchayah takoj ob容kt ne imeet imeni. V sleduyushchih podrazdelah predpolagaetsya, chto ob容kty otnosyatsya k klassu s konstruktorom i destruktorom. V kachestve primera ispol'zuetsya klass table iz $$5.3.1. 5.5.1 Lokal'nye peremennye Konstruktor lokal'noj peremennoj vyzyvaetsya kazhdyj raz, kogda pri vypolnenii programmy vstrechaetsya ee opisanie. Destruktor lokal'noj peremennoj vyzyvaetsya vsyakij raz po vyhode iz bloka, gde ona byla opisana. Destruktory dlya lokal'nyh peremennyh vyzyvayutsya v poryadke, obratnom vyzovu konstruktorov pri ih sozdanii: void f(int i) { table aa; table bb; if (i>0) { table cc; // ... } // ... } Zdes' aa i bb sozdayutsya (imenno v takom poryadke) pri kazhdom vyzove f(), a unichtozhayutsya oni pri vozvrate iz f() v obratnom poryadke - bb, zatem aa. Esli v tekushchem vyzove f() i bol'she nulya, to cc sozdaetsya posle bb i unichtozhaetsya prezhde nego. Poskol'ku aa i bb - ob容kty klassa table, prisvaivanie aa=bb oznachaet kopirovanie po chlenam bb v aa (sm. $$2.3.8). Takaya interpretaciya prisvaivaniya mozhet privesti k neozhidannomu (i obychno nezhelatel'nomu) rezul'tatu, esli prisvaivayutsya ob容kty klassa, v kotorom opredelen konstruktor: void h() { table t1(100); table t2 = t1; // nepriyatnost' table t3(200); t3 = t2; // nepriyatnost' } V etom primere konstruktor table vyzyvaetsya dvazhdy: dlya t1 i t3. On ne vyzyvaetsya dlya t2, poskol'ku etot ob容kt inicializiruetsya prisvaivaniem. Tem ne menee, destruktor dlya table vyzyvaetsya tri raza: dlya t1, t2 i t3! Dalee, standartnaya interpretaciya prisvaivaniya - eto kopirovanie po chlenam, poetomu pered vyhodom iz h() t1, t2 i t3 budut soderzhat' ukazatel' na massiv imen, pamyat' dlya kotorogo byla vydelena v svobodnoj pamyati pri sozdanii t1. Ukazatel' na pamyat', vydelennuyu dlya massiva imen pri sozdanii t3, budet poteryan. |tih nepriyatnostej mozhno izbezhat' (sm. $$1.4.2 i $$7.6). 5.5.2 Staticheskaya pamyat' Rassmotrim takoj primer: table tbl(100); void f(int i) { static table tbl2(i); } int main() { f(200); // ... } Zdes' konstruktor, opredelennyj v $$5.3.1, budet vyzyvat'sya dvazhdy: odin raz dlya tbl i odin raz dlya tbl2. Destruktor table::~table() takzhe budet vyzvan dvazhdy: dlya unichtozheniya tbl i tbl2 po vyhode iz main(). Konstruktory global'nyh staticheskih ob容ktov v fajle vyzyvayutsya v tom zhe poryadke, v kakom vstrechayutsya v fajle opisaniya ob容ktov, a destruktory dlya nih vyzyvayutsya v obratnom poryadke. Konstruktor lokal'nogo staticheskogo ob容kta vyzyvaetsya, kogda pri vypolnenii programmy pervyj raz vstrechaetsya opredelenie ob容kta. Tradicionno vypolnenie main() rassmatrivalos' kak vypolnenie vsej programmy. Na samom dele, eto ne tak dazhe dlya S. Uzhe razmeshchenie staticheskogo ob容kta klassa s konstruktorom i (ili) destruktorom pozvolyaet programmistu zadat' dejstviya, kotorye budut vypolnyat'sya do vyzova main() i (ili) po vyhode iz main(). Vyzov konstruktorov i destruktorov dlya staticheskih ob容ktov igraet v S++ chrezvychajno vazhnuyu rol'. S ih pomoshch'yu mozhno obespechit' sootvetstvuyushchuyu inicializaciyu i udalenie struktur dannyh, ispol'zuemyh v bibliotekah. Rassmotrim <iostream.h>. Otkuda berutsya cin, cout i cerr? Kogda oni inicializiruyutsya? Bolee sushchestvennyj vopros: poskol'ku dlya vyhodnyh potokov ispol'zuyutsya vnutrennie bufera simvolov, to proishodit vytalkivanie etih buferov, no kogda? Est' prostoj i ochevidnyj otvet: vse dejstviya vypolnyayutsya sootvetstvuyushchimi konstruktorami i destruktorami do zapuska main() i po vyhode iz nee (sm. $$10.5.1). Sushchestvuyut al'ternativy ispol'zovaniyu konstruktorov i destruktorov dlya inicializacii i unichtozheniya bibliotechnyh struktur dannyh, no vse oni ili ochen' specializirovany, ili neuklyuzhi, ili i to i drugoe vmeste. Esli programma zavershaetsya obrashchenie k funkcii exit(), to vyzyvayutsya destruktory dlya vseh postroennyh staticheskih ob容ktov. Odnako, esli programma zavershaetsya obrashcheniem k abort(), etogo ne proishodit. Zametim, chto exit() ne zavershaet programmu nemedlenno. Vyzov exit() v destruktore mozhet privesti k beskonechnoj rekursii. Esli nuzhna garantiya, chto budut unichtozheny kak staticheskie, tak i avtomaticheskie ob容kty, mozhno vospol'zovat'sya osobymi situaciyami ($$9). Inogda pri razrabotke biblioteki byvaet neobhodimo ili prosto udobno sozdat' tip s konstruktorom i destruktorom tol'ko dlya odnoj celi: inicializacii i unichtozheniya ob容ktov. Takoj tip ispol'zuetsya tol'ko odin raz dlya razmeshcheniya staticheskogo ob容kta, chtoby vyzvat' konstruktory i destruktory. 5.5.3 Svobodnaya pamyat' Rassmotrim primer: main() { table* p = new table(100); table* q = new table(200); delete p; delete p; // veroyatno, vyzovet oshibku pri vypolnenii } Konstruktor table::table() budet vyzyvat'sya dvazhdy, kak i destruktor table::~table(). No eto nichego ne znachit, t.k. v S++ ne garantiruetsya, chto destruktor budet vyzyvat'sya tol'ko dlya ob容kta, sozdannogo operaciej new. V etom primere q ne unichtozhaetsya voobshche, zato p unichtozhaetsya dvazhdy! V zavisimosti ot tipa p i q programmist mozhet schitat' ili ne schitat' eto oshibkoj. To, chto ob容kt ne udalyaetsya, obychno byvaet ne oshibkoj, a prosto poterej pamyati. V to zhe vremya povtornoe udalenie p - ser'eznaya oshibka. Povtornoe primenenie delete k tomu zhe samomu ukazatelyu mozhet privesti k beskonechnomu ciklu v podprogramme, upravlyayushchej svobodnoj pamyat'yu. No v yazyke rezul'tat povtornogo udaleniya ne opredelen, i on zavisit ot realizacii. Pol'zovatel' mozhet opredelit' svoyu realizaciyu operacij new i delete (sm. $$3.2.6 i $$6.7). Krome togo, mozhno ustanovit' vzaimodejstvie konstruktora ili destruktora s operaciyami new i delete (sm. $$5.5.6 i $$6.7.2). Razmeshchenie massivov v svobodnoj pamyati obsuzhdaetsya v $$5.5.5. 5.5.4 Ob容kty klassa kak chleny Rassmotrim primer: class classdef { table members; int no_of_members; // ... classdef(int size); ~classdef(); }; Cel' etogo opredeleniya, ochevidno, v tom, chtoby classdef soderzhal chlen, yavlyayushchijsya tablicej razmerom size, no est' slozhnost': nado obespechit' vyzov konstruktora table::table() s parametrom size. |to mozhno sdelat', naprimer, tak: classdef::classdef(int size) :members(size) { no_of_members = size; // ... } Parametr dlya konstruktora chlena (t.e. dlya table::table()) ukazyvaetsya v opredelenii (no ne v opisanii) konstruktora klassa, soderzhashchego chlen (t.e. v opredelenii classdef::classdef()). Konstruktor dlya chlena budet vyzyvat'sya do vypolneniya tela togo konstruktora, kotoryj zadaet dlya nego spisok parametrov. Analogichno mozhno zadat' parametry dlya konstruktorov drugih chlenov (esli est' eshche drugie chleny): class classdef { table members; table friends; int no_of_members; // ... classdef(int size); ~classdef(); }; Spiski parametrov dlya chlenov otdelyayutsya drug ot druga zapyatymi (a ne dvoetochiyami), a spisok inicializatorov dlya chlenov mozhno zadavat' v proizvol'nom poryadke: classdef::classdef(int size) : friends(size), members(size), no_of_members(size) { // ... } Konstruktory vyzyvayutsya v tom poryadke, v kotorom oni zadany v opisanii klassa. Podobnye opisaniya konstruktorov sushchestvenny dlya tipov, inicializaciya i prisvaivanie kotoryh otlichny drug ot druga, inymi slovami, dlya ob容ktov, yavlyayushchihsya chlenami klassa s konstruktorom, dlya postoyannyh chlenov ili dlya chlenov tipa ssylki. Odnako, kak pokazyvaet chlen no_of_members iz privedennogo primera, takie opisaniya konstruktorov mozhno ispol'zovat' dlya chlenov lyubogo tipa. Esli konstruktoru chlena ne trebuetsya parametrov, to i ne nuzhno zadavat' nikakih spiskov parametrov. Tak, poskol'ku konstruktor table::table() byl opredelen so standartnym znacheniem parametra, ravnym 15, dostatochno takogo opredeleniya: classdef::classdef(int size) : members(size), no_of_members(size) { // ... } Togda razmer tablicy friends budet raven 15. Esli unichtozhaetsya ob容kt klassa, kotoryj sam soderzhit ob容kty klassa (naprimer, classdef), to vnachale vypolnyaetsya telo destruktora ob容mlyushchego klassa, a zatem destruktory chlenov v poryadke, obratnom ih opisaniyu. Rassmotrim vmesto vhozhdeniya ob容ktov klassa v kachestve chlenov tradicionnoe al'ternativnoe emu reshenie: imet' v klasse ukazateli na chleny i inicializirovat' chleny v konstruktore: class classdef { table* members; table* friends; int no_of_members; // ... }; classdef::classdef(int size) { members = new table(size); friends = new table; // ispol'zuetsya standartnyj // razmer table no_of_members = size; // ... } Poskol'ku tablicy sozdavalis' s pomoshch'yu operacii new, oni dolzhny unichtozhat'sya operaciej delete: classdef::~classdef() { // ... delete members; delete friends; } Takie otdel'no sozdavaemye ob容kty mogut okazat'sya poleznymi, no uchtite, chto members i friends ukazyvayut na nezavisimye ot nih ob容kty, kazhdyj iz kotoryh nado yavno razmeshchat' i udalyat'. Krome togo, ukazatel' i ob容kt v svobodnoj pamyati summarno zanimayut bol'she mesta, chem ob容kt-chlen. 5.5.5 Massivy ob容ktov klassa CHtoby mozhno bylo opisat' massiv ob容ktov klassa s konstruktorom, etot klass dolzhen imet' standartnyj konstruktor, t.e. konstruktor, vyzyvaemyj bez parametrov. Naprimer, v sootvetstvii s opredeleniem table tbl[10]; budet sozdan massiv iz 10 tablic, kazhdaya iz kotoryh inicializiruetsya vyzovom table::table(15), poskol'ku vyzov table::table() budet proishodit' s fakticheskim parametrom 15. V opisanii massiva ob容ktov ne predusmotreno vozmozhnosti ukazat' parametry dlya konstruktora. Esli chleny massiva obyazatel'no nado inicializirovat' raznymi znacheniyami, to nachinayutsya tryuki s global'nymi ili staticheskimi chlenami. Kogda unichtozhaetsya massiv, destruktor dolzhen vyzyvat'sya dlya kazhdogo elementa massiva. Dlya massivov, kotorye razmeshchayutsya ne s pomoshch'yu new, eto delaetsya neyavno. Odnako dlya razmeshchennyh v svobodnoj pamyati massivov neyavno vyzyvat' destruktor nel'zya, poskol'ku translyator ne otlichit ukazatel' na otdel'nyj ob容kt massiva ot ukazatelya na nachalo massiva, naprimer: void f() { table* t1 = new table; table* t2 = new table[10]; delete t1; // udalyaetsya odna tablica delete t2; // nepriyatnost': // na samom dele udalyaetsya 10 tablic } V dannom sluchae programmist dolzhen ukazat', chto t2 - ukazatel' na massiv: void g(int sz) { table* t1 = new table; table* t2 = new table[sz]; delete t1; delete[] t2; } Funkciya razmeshcheniya hranit chislo elementov dlya kazhdogo razmeshchaemogo massiva. Trebovanie ispol'zovat' dlya udaleniya massivov tol'ko operaciyu delete[] osvobozhdaet funkciyu razmeshcheniya ot obyazannosti hranit' schetchiki chisla elementov dlya kazhdogo massiva. Ispolnenie takoj obyazannosti v realizaciyah S++ vyzyvalo by sushchestvennye poteri vremeni i pamyati i narushilo sovmestimost' s S. 5.5.6 Nebol'shie ob容kty Esli v vashej programme mnogo nebol'shih ob容ktov, razmeshchaemyh v svobodnoj pamyati, to mozhet okazat'sya, chto mnogo vremeni tratitsya na razmeshchenie i udalenie takih ob容ktov. Dlya vyhoda iz etoj situacii mozhno opredelit' bolee optimal'nyj raspredelitel' pamyati obshchego naznacheniya, a mozhno peredat' obyazannost' raspredeleniya svobodnoj pamyati sozdatelyu klassa, kotoryj dolzhen budet opredelit' sootvetstvuyushchie funkcii razmeshcheniya i udaleniya. Vernemsya k klassu name, kotoryj ispol'zovalsya v primerah s table. On mog by opredelyat'sya tak: struct name { char* string; name* next; double value; name(char*, double, name*); ~name(); void* operator new(size_t); void operator delete(void*, size_t); private: enum { NALL = 128 }; static name* nfree; }; Funkcii name::operator new() i name::operator delete() budut ispol'zovat'sya (neyavno) vmesto global'nyh funkcij operator new() i operator delete(). Programmist mozhet dlya konkretnogo tipa napisat' bolee effektivnye po vremeni i pamyati funkcii razmeshcheniya i udaleniya, chem universal'nye funkcii operator new() i operator delete(). Mozhno, naprimer, razmestit' zaranee "kuski" pamyati, dostatochnoj dlya ob容ktov tipa name, i svyazat' ih v spisok; togda operacii razmeshcheniya i udaleniya svodyatsya k prostym operaciyam so spiskom. Peremennaya nfree ispol'zuetsya kak nachalo spiska neispol'zovannyh kuskov pamyati: void* name::operator new(size_t) { register name* p = nfree; // snachala vydelit' if (p) nfree = p->next; else { // vydelit' i svyazat' v spisok name* q = (name*) new char[NALL*sizeof(name) ]; for (p=nfree=&q[NALL-1]; q<p; p--) p->next = p-1; (p+1)->next = 0; } return p; } Raspredelitel' pamyati, vyzyvaemyj new, hranit vmeste s ob容ktom ego razmer, chtoby operaciya delete vypolnyalas' pravil'no. |togo dopolnitel'nogo rashoda pamyati mozhno legko izbezhat', esli ispol'zovat' raspredelitel', rasschitannyj na konkretnyj tip. Tak, na mashine avtora funkciya name::operator new() dlya hraneniya ob容kta name ispol'zuet 16 bajtov, togda kak standartnaya global'naya funkciya operator new() ispol'zuet 20 bajtov. Otmetim, chto v samoj funkcii name::operator new() pamyat' nel'zya vydelyat' takim prostym sposobom: name* q= new name[NALL]; |to vyzovet beskonechnuyu rekursiyu, t.k. new budet vyzyvat' name::name(). Osvobozhdenie pamyati obychno trivial'no: void name::operator delete(void* p, size_t) { ((name*)p)->next = nfree; nfree = (name*) p; } Privedenie parametra tipa void* k tipu name* neobhodimo, poskol'ku funkciya osvobozhdeniya vyzyvaetsya posle unichtozheniya ob容kta, tak chto bol'she net real'nogo ob容kta tipa name, a est' tol'ko kusok pamyati razmerom sizeof(name). Parametry tipa size_t v privedennyh funkciyah name::operator new() i name::operator delete() ne ispol'zovalis'. Kak mozhno ih ispol'zovat', budet pokazano v $$6.7. Otmetim, chto nashi funkcii razmeshcheniya i udaleniya ispol'zuyutsya tol'ko dlya ob容ktov tipa name, no ne dlya massivov names. 5.6 Uprazhneniya 1. (*1) Izmenite programmu kal'kulyatora iz glavy 3 tak, chtoby mozhno bylo vospol'zovat'sya klassom table. 2. (*1) Opredelite tnode ($$R.9) kak klass s konstruktorami i destruktorami i t.p., opredelite derevo iz ob容ktov tipa tnode kak klass s konstruktorami i destruktorami i t.p. 3. (*1) Opredelite klass intset ($$5.3.2) kak mnozhestvo strok. 4. (*1) Opredelite klass intset kak mnozhestvo uzlov tipa tnode. Strukturu tnode pridumajte sami. 5. (*3) Opredelite klass dlya razbora, hraneniya, vychisleniya i pechati prostyh arifmeticheskih vyrazhenij, sostoyashchih iz celyh konstant i operacij +, -, * i /. Obshchij interfejs klassa dolzhen vyglyadet' primerno tak: class expr { // ... public: expr(char*); int eval(); void print(); }; Konstruktor expr::expr() imeet parametr-stroku, zadayushchuyu vyrazhenie. Funkciya expr::eval() vozvrashchaet znachenie vyrazheniya, a expr::print() vydaet predstavlenie vyrazheniya v cout. Ispol'zovat' eti funkcii mozhno tak: expr("123/4+123*4-3"); cout << "x = " << x.eval() << "\n"; x.print(); Dajte dva opredeleniya klassa expr: pust' v pervom dlya predstavleniya ispol'zuetsya svyazannyj spisok uzlov, a vo vtorom - stroka simvolov. Poeksperimentirujte s raznymi formatami pechati vyrazheniya, a imenno: s polnost'yu rasstavlennymi skobkami, v postfiksnoj zapisi, v assemblernom kode i t.d. 6. (*1) Opredelite klass char_queue (ochered' simvolov) tak, chtoby ego obshchij interfejs ne zavisel ot predstavleniya. Realizujte klass kak: (1) svyazannyj spisok i (2) vektor. O parallel'nosti ne dumajte. 7. (*2) Opredelite klass histogram (gistogramma), v kotorom vedetsya podschet chisel v opredelennyh intervalah, zadavaemyh v vide parametrov konstruktoru etogo klassa. Opredelite funkciyu vydachi gistogrammy. Sdelajte obrabotku znachenij, vyhodyashchih za interval. Podskazka: obratites' k <task.h>. 8. (*2) Opredelite neskol'ko klassov, porozhdayushchih sluchajnye chisla s opredelennymi raspredeleniyami. Kazhdyj klass dolzhen imet' konstruktor, zadayushchij parametry raspredeleniya i funkciyu draw, vozvrashchayushchuyu "sleduyushchee" znachenie. Podskazka: obratites' k <task.h> i klassu intset. 9. (*2) Perepishite primery date ($$5.2.2 i $$5.2.4), char_stack ($$5.2.5) i intset ($$5.3.2), ne ispol'zuya nikakih funkcij-chlenov (dazhe konstruktorov i destruktorov). Ispol'zujte tol'ko class i friend. Prover'te kazhduyu iz novyh versij i sravnite ih s versiyami, v kotoryh ispol'zuyutsya funkcii-chleny. 10.(*3) Dlya nekotorogo yazyka sostav'te opredeleniya klassa dlya tablicy imen i klassa, predstavlyayushchego zapis' v etoj tablice. Issledujte translyator dlya etogo yazyka, chtoby uznat', kakoj dolzhna byt' nastoyashchaya tablica imen. 11.(*2) Izmenite klass expr iz uprazhneniya 5 tak, chtoby v vyrazhenii mozhno bylo ispol'zovat' peremennye i operaciyu prisvaivaniya =. Ispol'zujte klass dlya tablicy imen iz uprazhneniya 10. 12.(*1) Pust' est' programma: #include <iostream.h> main() { cout << "Vsem privet\n"; } Izmenite ee tak, chtoby ona vydavala: Inicializaciya Vsem privet Udalenie Samu funkciyu main() menyat' nel'zya.  * GLAVA 6 Ne plodi ob容kty bez nuzhdy. - V. Okkam |ta glava posvyashchena ponyatiyu proizvodnogo klassa. Proizvodnye klassy - eto prostoe, gibkoe i effektivnoe sredstvo opredeleniya klassa. Novye vozmozhnosti dobavlyayutsya k uzhe sushchestvuyushchemu klassu, ne trebuya ego pereprogrammirovaniya ili peretranslyacii. S pomoshch'yu proizvodnyh klassov mozhno organizovat' obshchij interfejs s neskol'kimi razlichnymi klassami tak, chto v drugih chastyah programmy mozhno budet edinoobrazno rabotat' s ob容ktami etih klassov. Vvoditsya ponyatie virtual'noj funkcii, kotoroe pozvolyaet ispol'zovat' ob容kty nadlezhashchim obrazom dazhe v teh sluchayah, kogda ih tip na stadii translyacii neizvesten. Osnovnoe naznachenie proizvodnyh klassov - uprostit' programmistu zadachu vyrazheniya obshchnosti klassov. 6.1 Vvedenie i kratkij obzor Lyuboe ponyatie ne sushchestvuet izolirovanno, ono sushchestvuet vo vzaimosvyazi s drugimi ponyatiyami, i moshchnost' dannogo ponyatiya vo mnogom opredelyaetsya nalichiem takih svyazej. Raz klass sluzhit dlya predstavleniya ponyatij, vstaet vopros, kak predstavit' vzaimosvyaz' ponyatij. Ponyatie proizvodnogo klassa i podderzhivayushchie ego yazykovye sredstva sluzhat dlya predstavleniya ierarhicheskih svyazej, inymi slovami, dlya vyrazheniya obshchnosti mezhdu klassami. Naprimer, ponyatiya okruzhnosti i treugol'nika svyazany mezhdu soboj, tak kak oba oni predstavlyayut eshche ponyatie figury, t.e. soderzhat bolee obshchee ponyatie. CHtoby predstavlyat' v programme okruzhnosti i treugol'niki i pri etom ne upuskat' iz vida, chto oni yavlyayutsya figurami, nado yavno opredelyat' klassy okruzhnost' i treugol'nik tak, chtoby bylo vidno, chto u nih est' obshchij klass - figura. V glave issleduetsya, chto vytekaet iz etoj prostoj idei, kotoraya po suti yavlyaetsya osnovoj togo, chto obychno nazyvaetsya ob容ktno-orientirovannym programmirovaniem. Glava sostoit iz shesti razdelov: $$6.2 s pomoshch'yu serii nebol'shih primerov vvoditsya ponyatie proizvodnogo klassa, ierarhii klassov i virtual'nyh funkcij. $$6.3 vvoditsya ponyatie chisto virtual'nyh funkcij i abstraktnyh klassov, dany nebol'shie primery ih ispol'zovaniya. $$6.4 proizvodnye klassy pokazany na zakonchennom primere $$6.5 vvoditsya ponyatie mnozhestvennogo nasledovaniya kak vozmozhnost' imet' dlya klassa bolee odnogo pryamogo bazovogo klassa, opisyvayutsya sposoby razresheniya kollizij imen, voznikayushchih pri mnozhestvennom nasledovanii. $$6.6 obsuzhdaetsya mehanizm kontrolya dostupa. $$6.7 privodyatsya nekotorye priemy upravleniya svobodnoj pamyat'yu dlya proizvodnyh klassov. V posleduyushchih glavah takzhe budut privodit'sya primery, ispol'zuyushchie eti vozmozhnosti yazyka. 6.2 Proizvodnye klassy Obsudim, kak napisat' programmu ucheta sluzhashchih nekotoroj firmy. V nej mozhet ispol'zovat'sya, naprimer, takaya struktura dannyh: struct employee { // sluzhashchie char* name; // imya short age; // vozrast short department; // otdel int salary; // oklad employee* next; // ... }; Pole next nuzhno dlya svyazyvaniya v spisok zapisej o sluzhashchih odnogo otdela (employee). Teper' poprobuem opredelit' strukturu dannyh dlya upravlyayushchego (manager): struct manager { employee emp; // zapis' employee dlya upravlyayushchego employee* group; // podchinennyj kollektiv short level; // ... }; Upravlyayushchij takzhe yavlyaetsya sluzhashchim, poetomu zapis' employee hranitsya v chlene emp ob容kta manager. Dlya cheloveka eta obshchnost' ochevidna, no dlya translyatora chlen emp nichem ne otlichaetsya ot drugih chlenov klassa. Ukazatel' na strukturu manager (manager*) ne yavlyaetsya ukazatelem na employee (employee*), poetomu nel'zya svobodno ispol'zovat' odin vmesto drugogo. V chastnosti, bez special'nyh dejstvij nel'zya ob容kt manager vklyuchit' v spisok ob容ktov tipa employee. Pridetsya libo ispol'zovat' yavnoe privedenie tipa manager*, libo v spisok zapisej employee vklyuchit' adres chlena emp. Oba resheniya nekrasivy i mogut byt' dostatochno zaputannymi. Pravil'noe reshenie sostoit v tom, chtoby tip manager byl tipom employee s nekotoroj dopolnitel'noj informaciej: struct manager : employee { employee* group; short level; // ... }; Klass manager yavlyaetsya proizvodnym ot employee, i, naoborot, employee yavlyaetsya bazovym klassom dlya manager. Pomimo chlena group v klasse manager est' chleny klassa employee (name, age i t.d.). Graficheski otnoshenie nasledovaniya obychno izobrazhaetsya v vide strelki ot proizvodnyh klassov k bazovomu: employee ^ | manager Obychno govoryat, chto proizvodnyj klass nasleduet bazovyj klass, poetomu i otnoshenie mezhdu nimi nazyvaetsya nasledovaniem. Inogda bazovyj klass nazyvayut superklassom, a proizvodnyj - podchinennym klassom. No eti terminy mogut vyzyvat' nedoumenie, poskol'ku ob容kt proizvodnogo klassa soderzhit ob容kt svoego bazovogo klassa. Voobshche proizvodnyj klass bol'she svoego bazovogo v tom smysle, chto v nem soderzhitsya bol'she dannyh i opredeleno bol'she funkcij. Imeya opredeleniya employee i manager, mozhno sozdat' spisok sluzhashchih, chast' iz kotoryh yavlyaetsya i upravlyayushchimi: void f() { manager m1, m2; employee e1, e2; employee* elist; elist = &m1; // pomestit' m1 v elist m1.next = &e1; // pomestit' e1 v elist e1.next = &m2; // pomestit' m2 v elist m2.next = &e2; // pomestit' m2 v elist e2.next = 0; // konec spiska } Poskol'ku upravlyayushchij yavlyaetsya i sluzhashchim, ukazatel' manager* mozhno ispol'zovat' kak employee*. V to zhe vremya sluzhashchij ne obyazatel'no yavlyaetsya upravlyayushchim, i poetomu employee* nel'zya ispol'zovat' kak manager*. V obshchem sluchae, esli klass derived imeet obshchij bazovyj klass base, to ukazatel' na derived mozhno bez yavnyh preobrazovanij tipa prisvaivat' peremennoj, imeyushchej tip ukazatelya na base. Obratnoe preobrazovanie ot ukazatelya na base k ukazatelyu na derived mozhet byt' tol'ko yavnym: void g() { manager mm; employee* pe = &mm; // normal'no employee ee; manager* pm = &ee; // oshibka: // ne vsyakij sluzhashchij yavlyaetsya upravlyayushchim pm->level = 2; // katastrofa: pri razmeshchenii ee // pamyat' dlya chlena `level' ne vydelyalas' pm = (manager*) pe; // normal'no: na samom dele pe // ne nastroeno na ob容kt mm tipa manager pm->level = 2; // otlichno: pm ukazyvaet na ob容kt mm // tipa manager, a v nem pri razmeshchenii // vydelena pamyat' dlya chlena `level' } Inymi slovami, esli rabota s ob容ktom proizvodnogo klassa idet cherez ukazatel', to ego mozhno rassmatrivat' kak ob容kt bazovogo klassa. Obratnoe neverno. Otmetim, chto v obychnoj realizacii S++ ne predpolagaetsya dinamicheskogo kontrolya nad tem, chtoby posle preobrazovaniya tipa, podobnogo tomu, kotoroe ispol'zovalos' v prisvaivanii pe v pm, poluchivshijsya v rezul'tate ukazatel' dejstvitel'no byl nastroen na ob容kt trebuemogo tipa (sm. $$13.5). 6.2.1 Funkcii-chleny Prostye struktury dannyh vrode employee i manager sami po sebe ne slishkom interesny, a chasto i ne osobenno polezny. Poetomu dobavim k nim funkcii: class employee { char* name; // ... public: employee* next; // nahoditsya v obshchej chasti, chtoby // mozhno bylo rabotat' so spiskom void print() const; // ... }; class manager : public employee { // ... public: void print() const; // ... }; Nado otvetit' na nekotorye voprosy. Kakim obrazom funkciya-chlen proizvodnogo klassa manager mozhet ispol'zovat' chleny bazovogo klassa employee? Kakie chleny bazovogo klassa employee mogut ispol'zovat' funkcii-chleny proizvodnogo klassa manager? Kakie chleny bazovogo klassa employee mozhet ispol'zovat' funkciya, ne yavlyayushchayasya chlenom ob容kta tipa manager? Kakie otvety na eti voprosy dolzhna davat' realizaciya yazyka, chtoby oni maksimal'no sootvetstvovali zadache programmista? Rassmotrim primer: void manager::print() const { cout << " imya " << name << '\n'; } CHlen proizvodnogo klassa mozhet ispol'zovat' imya iz obshchej chasti svoego bazovogo klassa naravne so vsemi drugimi chlenami, t.e. bez ukazaniya imeni ob容kta. Predpolagaetsya, chto est' ob容kt, na kotoryj nastroen this, poetomu korrektnym obrashcheniem k name budet this->name. Odnako, pri translyacii funkcii manager::print() budet zafiksirovana oshibka: chlenu proizvodnogo klassa ne predostavleno pravo dostupa k chastnym chlenam ego bazovogo klassa, znachit name nedostupno v etoj funkcii. Vozmozhno mnogim eto pokazhetsya strannym, no davajte rassmotrim al'ternativnoe reshenie: funkciya-chlen proizvodnogo kl