shchego vida dlya bazovyh klassov (ponyatij) obychno opredelyaetsya s pomoshch'yu virtual'nyh funkcij ($$6.5). Interpretaciya etih operacij, po mere nadobnosti, mozhet utochnyat'sya dlya kazhdogo konkretnogo sluchaya, t.e. dlya kazhdogo proizvodnogo klassa. Estestvenno, est' ogranicheniya i pri takoj organizacii programmy. Inogda ispol'zuemye v programme ponyatiya ne udaetsya uporyadochit' dazhe s pomoshch'yu napravlennogo aciklichnogo grafa. Nekotorye ponyatiya okazyvayutsya po svoej prirode vzaimosvyazannymi. Ciklicheskie zavisimosti ne vyzovut problem, esli mnozhestvo vzaimosvyazannyh klassov nastol'ko malo, chto v nem legko razobrat'sya. Dlya predstavleniya na S++ mnozhestva vzaimozavisimyh klassov mozhno ispol'zovat' druzhestvennye klassy ($$5.4.1). Esli ponyatiya programmy nel'zya uporyadochit' v vide dereva ili napravlennogo aciklichnogo grafa, a mnozhestvo vzaimozavisimyh ponyatij ne poddaetsya lokalizacii, to, po vsej vidimosti, vy popali v takoe zatrudnitel'noe polozhenie, vyjti iz kotorogo ne smozhet pomoch' ni odin iz yazykov programmirovaniya. Esli vam ne udalos' dostatochno prosto sformulirovat' svyazi mezhdu osnovnymi ponyatiyami zadachi, to, skoree vsego, vam ne udastsya ee zaprogrammirovat'. Eshche odin sposob vyrazheniya obshchnosti ponyatij v yazyke predostavlyayut shablony tipa. SHablonnyj klass zadaet celoe semejstvo klassov. Naprimer, shablonnyj klass spisok zadaet klassy vida "spisok ob容ktov T", gde T mozhet byt' proizvol'nym tipom. Takim obrazom, shablonnyj tip ukazyvaet, kak poluchaetsya novyj tip iz zadannogo v kachestve parametra. Samye tipichnye shablonnye klassy - eto kontejnery, v chastnosti, spiski, massivy i associativnye massivy. Napomnim, chto mozhno legko i prosto zaprogrammirovat' mnogie zadachi, ispol'zuya tol'ko prostye tipy, struktury dannyh, obychnye funkcii i neskol'ko klassov iz standartnyh bibliotek. Ves' apparat postroeniya novyh tipov sleduet privlekat' tol'ko togda, kogda on dejstvitel'no neobhodim. Vopros "Kak napisat' horoshuyu programmu na S++?" ochen' pohozh na vopros "Kak pishetsya horoshaya anglijskaya proza?". Na nego est' dva otveta: "Nuzhno znat', chto vy, sobstvenno, hotite napisat'" i "Praktika i podrazhanie horoshemu stilyu". Oba soveta prigodny dlya S++ v toj zhe mere, chto i dlya anglijskogo yazyka, i oboim dostatochno trudno sledovat'. Neskol'ko poleznyh sovetov Nizhe predstavlen "svod pravil", kotoryj stoit uchityvat' pri izuchenii S++. Kogda vy stanete bolee opytnymi, to na baze etih pravil smozhete sformulirovat' svoi sobstvennye, kotorye budut bolee podhodit' dlya vashih zadach i bolee sootvetstvovat' vashemu stilyu programmirovaniya. Soznatel'no vybrany ochen' prostye pravila, i v nih opushcheny podrobnosti. Ne sleduet vosprinimat' ih slishkom bukval'no. Horoshaya programma trebuet i uma, i vkusa, i terpeniya. S pervogo raza obychno ona ne poluchaetsya, poetomu eksperimentirujte! Itak, svod pravil. [1] Kogda vy pishite programmu, to sozdaete konkretnye predstavleniya teh ponyatij, kotorye ispol'zovalis' v reshenii postavlennoj zadachi. Struktura programmy dolzhna otrazhat' eti ponyatiya nastol'ko yavno, naskol'ko eto vozmozhno. [a] Esli vy schitaete "nechto" otdel'nym ponyatiem, to sdelajte ego klassom. [b] Esli vy schitaete "nechto" sushchestvuyushchim nezavisimo, to sdelajte ego ob容ktom nekotorogo klassa. [c] Esli dva klassa imeyut nechto sushchestvennoe, i ono yavlyaetsya dlya nih obshchim, to vyrazite etu obshchnost' s pomoshch'yu bazovogo klassa. [d] Esli klass yavlyaetsya kontejnerom nekotoryh ob容ktov, sdelajte ego shablonnym klassom. [2] Esli opredelyaetsya klass, kotoryj ne realizuet matematicheskih ob容ktov vrode matric ili kompleksnyh chisel i ne yavlyaetsya tipom nizkogo urovnya napodobie svyazannogo spiska, to: [a] Ne ispol'zujte global'nyh dannyh. [b] Ne ispol'zujte global'nyh funkcij (ne chlenov). [c] Ne ispol'zujte obshchih dannyh-chlenov. [d] Ne ispol'zujte funkcii friend (no tol'ko dlya togo, chtoby izbezhat' [a], [b] ili [c]). [e] Ne obrashchajtes' k dannym-chlenam drugogo ob容kta neposredstvenno. [f] Ne zavodite v klasse "pole tipa"; ispol'zujte virtual'nye funkcii. [g] Ispol'zujte funkcii-podstanovki tol'ko kak sredstvo znachitel'noj optimizacii. Zamechanie dlya programmistov na S CHem luchshe programmist znaet S, tem trudnee budet dlya nego pri programmirovanii na S++ otojti ot stilya programmirovaniya na S. Tak on teryaet potencial'nye preimushchestva S++. Poetomu sovetuem prosmotret' razdel "Otlichiya ot S" v spravochnom rukovodstve ($$R.18). Zdes' my tol'ko ukazhem na te mesta, v kotoryh ispol'zovanie dopolnitel'nyh vozmozhnostej S++ privodit k luchshemu resheniyu, chem programmirovanie na chistom S. Makrokomandy prakticheski ne nuzhny v S++: ispol'zujte const ($$2.5) ili enum ($$2.5.1), chtoby opredelit' poimenovannye konstanty; ispol'zujte inline ($$4.6.2), chtoby izbezhat' rashodov resursov, svyazannyh s vyzovom funkcij; ispol'zujte shablony tipa ($$8), chtoby zadat' semejstvo funkcij i tipov. Ne opisyvajte peremennuyu, poka ona dejstvitel'no vam ne ponadobitsya, a togda ee mozhno srazu inicializirovat', ved' v S++ opisanie mozhet poyavlyat'sya v lyubom meste, gde dopustim operator. Ne ispol'zujte malloc(), etu operaciyu luchshe realizuet new ($$3.2.6). Ob容dineniya nuzhny ne stol' chasto, kak v S, poskol'ku al'ternativnost' v strukturah realizuetsya s pomoshch'yu proizvodnyh klassov. Starajtes' obojtis' bez ob容dinenij, no esli oni vse-taki nuzhny, ne vklyuchajte ih v osnovnye interfejsy; ispol'zujte bezymyannye ob容dineniya ($$2.6.2). Starajtes' ne ispol'zovat' ukazatelej tipa void*, arifmeticheskih operacij s ukazatelyami, massivov v stile S i operacij privedeniya. Esli vse-taki vy ispol'zuete eti konstrukcii, upryatyvajte ih dostatochno nadezhno v kakuyu-nibud' funkciyu ili klass. Ukazhem, chto svyazyvanie v stile S vozmozhno dlya funkcii na S++, esli ona opisana so specifikaciej extern "C" ($$4.4). No gorazdo vazhnee starat'sya dumat' o programme kak o mnozhestve vzaimosvyazannyh ponyatij, predstavlyaemyh klassami i ob容ktami, chem predstavlyat' ee kak summu struktur dannyh i funkcij, chto-to delayushchih s etimi dannymi. Spisok literatury V knige nemnogo neposredstvennyh ssylok na literaturu. Zdes' priveden spisok knig i statej, na kotorye est' pryamye ssylki, a takzhe teh, kotorye tol'ko upominayutsya. [1] A.V.Aho, J.E.Hopcroft, and J.D.Ulman: Data Structures and Algoritms. Addison-Wesley, Reading, Massachusetts. 1983. [2] O-J.Dahl, B.Myrhaug, and K.Nugaard: SIMULA Common Base Language. Norwegian Computing Ctnter S-22. Oslo, Norway. 1970 [3] O-J.Dahl and C.A.R.Hoare: Hierarhical Program Construction in Structured Programming. Academic Press, New York. 1972. pp. 174-220. [4] Margaret A.Ellis and Bjarne Stroustrup: The Annotated C++ Reference Manual. Addison-Wesley, Reading, Massachusetts. 1990. [5] A.Goldberg and D.Rodson: SMALLTALK-80 - The Language and Its Implementation. Addison-Wesley, Reading, Massachusetts. 1983. [6] R.E.Griswold et.al.: The Snobol14 Programming Language. Prentice-Hall, Englewood Cliffs, New Jersy, 1970. [7] R.E.Griswold and M.T.Griswold: The ICON Programming Language. Prentice-Hall, Englewood Cliffs, New Jersy. 1983. [8] Brian W.Kernighan and Dennis M.Ritchie: The C Programming Language. Prentice-Hall, Englewood Cliffs, New Jersy. 1978. Second edition 1988. [9] Andrew Koenig and Bjarne Stroustrup: C++: As Close to C as possible - but no closer. The C++ Report. Vol.1 No.7. July 1989. [10] Andrew Koenig and Bjarne Stroustrup: Exception Handling for C++ (revised). Proc USENIX C++ Conference, April 1990. Also, Journal of Object Oriented Programming, Vol.3 No.2, July/August 1990. pp.16-33. [11] Barbara Liskov et.al.: CLU Reference Manual. MIT/LCS/TR-225. [12] George Orwell: 1984. Secker and Warburg, London. 1949. [13] Martin Richards and Colin Whitby-Strevens: BCPL - The Language and Its Compiler. Cambridge University Press. 1980. [14] L.Rosler: The Evolution of C - Past and Future. AT&T Bell Laboratories Technical Journal. Vol.63 No.8 Part 2. October 1984. pp.1685-1700. [15] Ravi Sethi: Uniform Syntax for Type Expressions and Declarations. Software Practice & Experience, Vol.11. 1981. pp.623-628. [16] Bjarne Stroustrup: Adding Classes to C: An Exercise in Language Evolution. Software Practice & Experience, Vol.13. 1983. pp.139-61. [17] Bjarne Stroustrup: The C++ Programming Language. Addison-Wesley. 1986. [18] Bjarne Stroustrup: Multiple Inheritance for C++. Proc. EUUG Spring Conference, May 1987. Also USENIX Computer Systems, Vol.2 No 4, Fall 1989. [19] Bjarne Stroustrup and Jonathan Shopiro: A Set of C classes for Co-Routine Style Programming. Proc. USENIX C++ conference, Santa Fe. November 1987. pp.417-439. [20] Bjarne Stroustrup: Type-safe Linkage for C++. USENIX Computer Systems, Vol.1 No.4 Fall 1988. [21] Bjurne Stroustrup: Parameterized Type for C++. Proc. USENIX C++ Conference, Denver, October 1988. pp.1-18. Also, USENIX Computer Systems, Vol.2 No.1 Winter 1989. [22] Bjarne Stroustrup: Standardizing C++. The C++ Report. Vol.1 No.1. January 1989. [23] Bjarne Stroustrup: The Evolution of C++: 1985-1989. USENIX Computer Systems, Vol.2 No.3. Summer 1989. [24] P.M.Woodward and S.G.Bond: Algol 68-R Users Guide. Her Majesty's Stationery Office, London. 1974. [25] UNIX Time-Sharing System: Programmer's Manual. Research Version, Tenth Edition. AT&T Bell Laboratories, Murray Hill, New Jersy, February 1985. [26] Aake Wilkstroem: Functional Programming Using ML. Prentice-Hall, Englewood Cliffs, New Jersy. 1987. [27] X3 Secretariat: Standard - The C Language. X3J11/90-013. Computer and Business Equipment Manufactures Association, 311 First Street, NW, Suite 500, Washington, DC 20001, USA. Ssylki na istochniki po proektirovaniyu i razvitiyu bol'shih sistem programmnogo obespecheniya mozhno najti v konce glavy 11.  * KRATKIJ OBZOR S++ "Nachnem s togo, chto vzdernem vseh etih zakonnikov, yazykovedov". ("Korol' Genrih VI", dejstvie II) V etoj glave soderzhitsya kratkij obzor osnovnyh koncepcij i konstrukcij yazyka S++. On sluzhit dlya beglogo znakomstva s yazykom. Podrobnoe opisanie vozmozhnostej yazyka i metodov programmirovaniya na nem daetsya v sleduyushchih glavah. Razgovor vedetsya v osnovnom vokrug abstrakcii dannyh i ob容ktno-orientirovannogo programmirovaniya, no perechislyayutsya i osnovnye vozmozhnosti procedurnogo programmirovaniya. 1.1 VVEDENIE YAzyk programmirovaniya S++ zadumyvalsya kak yazyk, kotoryj budet: - luchshe yazyka S; - podderzhivat' abstrakciyu dannyh; - podderzhivat' ob容ktno-orientirovannoe programmirovanie. V etoj glave ob座asnyaetsya smysl etih fraz bez podrobnogo opisaniya konstrukcij yazyka. $$1.2 soderzhit neformal'noe opisanie razlichij "procedurnogo", "modul'nogo" i "ob容ktno-orientirovannogo" programmirovaniya. Privedeny konstrukcii yazyka, kotorye sushchestvenny dlya kazhdogo iz perechislennyh stilej programmirovaniya. Svojstvennyj S stil' programmirovaniya obsuzhdaetsya v razdelah "procedurnoe programmirovanie i "modul'noe programmirovanie". YAzyk S++ - "luchshij variant S". On luchshe podderzhivaet takoj stil' programmirovaniya, chem sam S, prichem eto delaetsya bez poteri kakoj-libo obshchnosti ili effektivnosti po sravneniyu s S. V to zhe vremya yazyk C yavlyaetsya podmnozhestvom S++. Abstrakciya dannyh i ob容ktno-orientirovannoe programmirovanie rassmatrivayutsya kak "podderzhka abstrakcii dannyh" i "podderzhka ob容ktno- orientirovannogo programmirovaniya". Pervaya baziruetsya na vozmozhnosti opredelyat' novye tipy i rabotat' s nimi, a vtoraya - na vozmozhnosti zadavat' ierarhiyu tipov. $$1.3 soderzhit opisanie osnovnyh konstrukcij dlya procedurnogo i modul'nogo programmirovaniya. V chastnosti, opredelyayutsya funkcii, ukazateli, cikly, vvod-vyvod i ponyatie programmy kak sovokupnosti razdel'no transliruemyh modulej. Podrobno eti vozmozhnosti opisany v glavah 2, 3 i 4. $$1.4 soderzhit opisanie sredstv, prednaznachennyh dlya effektivnoj realizacii abstrakcii dannyh. V chastnosti, opredelyayutsya klassy, prostejshij mehanizm kontrolya dostupa, konstruktory i destruktory, peregruzka operacij, preobrazovaniya pol'zovatel'skih tipov, obrabotka osobyh situacij i shablony tipov. Podrobno eti vozmozhnosti opisany v glavah 5, 7, 8 i 9. $$1.5 soderzhit opisanie sredstv podderzhki ob容ktno-orientirovannogo programmirovaniya. V chastnosti, opredelyayutsya proizvodnye klassy i virtual'nye funkcii, obsuzhdayutsya nekotorye voprosy realizacii. Vse eto podrobno izlozheno v glave 6. $$1.6 soderzhit opisanie opredelennyh ogranichenij na puti sovershenstvovaniya kak yazykov programmirovaniya obshchego naznacheniya voobshche, tak i S++ v chastnosti. |ti ogranicheniya svyazany s effektivnost'yu, s protivorechashchimi drug drugu trebovaniyami raznyh oblastej prilozheniya, problemami obucheniya i neobhodimost'yu translyacii i vypolneniya programm v staryh sistemah. Esli kakoj-to razdel okazhetsya dlya vas neponyatnym, nastoyatel'no sovetuem prochitat' sootvetstvuyushchie glavy, a zatem, oznakomivshis' s podrobnym opisaniem osnovnyh konstrukcij yazyka, vernut'sya k etoj glave. Ona nuzhna dlya togo, chtoby mozhno bylo sostavit' obshchee predstavlenie o yazyke. V nej nedostatochno svedenij, chtoby nemedlenno nachat' programmirovat'. 1.2 Paradigmy programmirovaniya Ob容ktno-orientirovannoe programmirovanie - eto metod programmirovaniya, sposob napisaniya "horoshih" programm dlya mnozhestva zadach. Esli etot termin imeet kakoj-to smysl, to on dolzhen podrazumevat': takoj yazyk programmirovaniya, kotoryj predostavlyaet horoshie vozmozhnosti dlya ob容ktno-orientirovannogo stilya programmirovaniya. Zdes' sleduet ukazat' na vazhnye razlichiya. Govoryat, chto yazyk podderzhivaet nekotoryj stil' programmirovaniya, esli v nem est' takie vozmozhnosti, kotorye delayut programmirovanie v etom stile udobnym (dostatochno prostym, nadezhnym i effektivnym). YAzyk ne podderzhivaet nekotoryj stil' programmirovaniya, esli trebuyutsya bol'shie usiliya ili dazhe iskusstvo, chtoby napisat' programmu v etom stile. Odnako eto ne oznachaet, chto yazyk zapreshchaet pisat' programmy v etom stile. Dejstvitel'no, mozhno pisat' strukturnye programmy na Fortrane i ob容ktno-orientirovannye programmy na S, no eto budet pustoj tratoj sil, poskol'ku dannye yazyki ne podderzhivayut ukazannyh stilej programmirovaniya. Podderzhka yazykom opredelennoj paradigmy (stilya) programmirovaniya yavno proyavlyaetsya v konkretnyh yazykovyh konstrukciyah, rasschitannyh na nee. No ona mozhet proyavlyat'sya v bolee tonkoj, skrytoj forme, kogda otklonenie ot paradigmy diagnostiruetsya na stadii translyacii ili vypolneniya programmy. Samyj ochevidnyj primer - eto kontrol' tipov. Krome togo, yazykovaya podderzhka paradigmy mozhet dopolnyat'sya proverkoj na odnoznachnost' i dinamicheskim kontrolem. Podderzhka mozhet predostavlyat'sya i pomimo samogo yazyka, naprimer, standartnymi bibliotekami ili sredoj programmirovaniya. Nel'zya skazat', chto odin yazyk luchshe drugogo tol'ko potomu, chto v nem est' vozmozhnosti, kotorye v drugom otsutstvuyut. CHasto byvaet kak raz naoborot. Zdes' bolee vazhno ne to, kakimi vozmozhnostyami obladaet yazyk, a to, naskol'ko imeyushchiesya v nem vozmozhnosti podderzhivayut izbrannyj stil' programmirovaniya dlya opredelennogo kruga zadach. Poetomu mozhno sformulirovat' sleduyushchie trebovaniya k yazyku: [1] Vse konstrukcii yazyka dolzhny estestvenno i elegantno opredelyat'sya v nem. [2] Dlya resheniya opredelennoj zadachi dolzhna byt' vozmozhnost' ispol'zovat' sochetaniya konstrukcij, chtoby izbezhat' neobhodimosti vvodit' dlya etoj celi novuyu konstrukciyu. [3] Dolzhno byt' minimal'noe chislo neochevidnyh konstrukcij special'nogo naznacheniya. [4] Konstrukciya dolzhna dopuskat' takuyu realizaciyu, chtoby v neispol'zuyushchej ee programme ne vozniklo dopolnitel'nyh rashodov. [5] Pol'zovatelyu dostatochno znat' tol'ko to mnozhestvo konstrukcij, kotoroe neposredstvenno ispol'zuetsya v ego programme. Pervoe trebovanie apelliruet k logike i esteticheskomu vkusu. Dva sleduyushchih vyrazhayut princip minimal'nosti. Dva poslednih mozhno inache sformulirovat' tak: "to, chego vy ne znaete, ne smozhet nanesti vam vreda". S uchetom ogranichenij, ukazannyh v etih pravilah, yazyk S++ proektirovalsya dlya podderzhki abstrakcii dannyh i ob容ktno-orientirovannogo programmirovaniya v dobavlenie k tradicionnomu stilyu S. Vprochem, eto ne znachit, chto yazyk trebuet kakogo-to odnogo stilya programmirovaniya ot vseh pol'zovatelej. Teper' perejdem k konkretnym stilyam programmirovaniya i posmotrim kakovy osnovnye konstrukcii yazyka, ih podderzhivayushchie. My ne sobiraemsya davat' polnoe opisanie etih konstrukcij. 1.2.1 Procedurnoe programmirovanie Pervonachal'noj (i, vozmozhno, naibolee ispol'zuemoj) paradigmoj programmirovaniya bylo: Opredelite, kakie procedury vam nuzhny; ispol'zujte luchshie iz izvestnyh vam algoritmov! Udarenie delalos' na obrabotku dannyh s pomoshch'yu algoritma, proizvodyashchego nuzhnye vychisleniya. Dlya podderzhki etoj paradigmy yazyki predostavlyali mehanizm peredachi parametrov i polucheniya rezul'tatov funkcij. Literatura, otrazhayushchaya takoj podhod, zapolnena rassuzhdeniyami o sposobah peredachi parametrov, o tom, kak razlichat' parametry raznyh tipov, o razlichnyh vidah funkcij (procedury, podprogrammy, makrokomandy, ...) i t.d. Pervym procedurnym yazykom byl Fortran, a Algol60, Algol68, Paskal' i S prodolzhili eto napravlenie. Tipichnym primerom horoshego stilya v takom ponimanii mozhet sluzhit' funkciya izvlecheniya kvadratnogo kornya. Dlya zadannogo parametra ona vydaet rezul'tat, kotoryj poluchaetsya s pomoshch'yu ponyatnyh matematicheskih operacij: double sqrt ( double arg ) { // programma dlya vychisleniya kvadratnogo kornya } voide some_function () { double root = sqrt ( 2 ); // .. } Dvojnaya naklonnaya cherta // nachinaet kommentarij, kotoryj prodolzhaetsya do konca stroki. Pri takoj organizacii programmy funkcii vnosyat opredelennyj poryadok v haos razlichnyh algoritmov. 1.2.2 Modul'noe programmirovanie So vremenem pri v proektirovanii programm akcent smestilsya s organizacii procedur na organizaciyu struktur dannyh. Pomimo vsego prochego eto vyzvano i rostom razmerov programm. Modulem obychno nazyvayut sovokupnost' svyazannyh procedur i teh dannyh, kotorymi oni upravlyayut. Paradigma programmirovaniya priobrela vid: Opredelite, kakie moduli nuzhny; podelite programmu tak, chtoby dannye byli skryty v etih modulyah |ta paradigma izvestna takzhe kak "princip sokrytiya dannyh". Esli v yazyke net vozmozhnosti sgruppirovat' svyazannye procedury vmeste s dannymi, to on ploho podderzhivaet modul'nyj stil' programmirovaniya. Teper' metod napisaniya "horoshih" procedur primenyaetsya dlya otdel'nyh procedur modulya. Tipichnyj primer modulya - opredelenie steka. Zdes' neobhodimo reshit' takie zadachi: [1] Predostavit' pol'zovatelyu interfejs dlya steka (naprimer, funkcii push () i pop ()). [2] Garantirovat', chto predstavlenie steka (naprimer, v vide massiva elementov) budet dostupno lish' cherez interfejs pol'zovatelya. [3] Obespechivat' inicializaciyu steka pered pervym ego ispol'zovaniem. YAzyk Modula-2 pryamo podderzhivaet etu paradigmu, togda kak S tol'ko dopuskaet takoj stil'. Nizhe predstavlen na S vozmozhnyj vneshnij interfejs modulya, realizuyushchego stek: // opisanie interfejsa dlya modulya, // realizuyushchego stek simvolov: void push ( char ); char pop (); const int stack_size = 100; Dopustim, chto opisanie interfejsa nahoditsya v fajle stack.h, togda realizaciyu steka mozhno opredelit' sleduyushchim obrazom: #include "stack.h" // ispol'zuem interfejs steka static char v [ stack_size ]; // ``static'' oznachaet lokal'nyj // v dannom fajle/module static char * p = v; // stek vnachale pust void push ( char c ) { //proverit' na perepolnenie i pomestit' v stek } char pop () { //proverit', ne pust li stek, i schitat' iz nego } Vpolne vozmozhno, chto realizaciya steka mozhet izmenit'sya, naprimer, esli ispol'zovat' dlya hraneniya svyazannyj spisok. Pol'zovatel' v lyubom sluchae ne imeet neposredstvennogo dostupa k realizacii: v i p - staticheskie peremennye, t.e. peremennye lokal'nye v tom module (fajle), v kotorom oni opisany. Ispol'zovat' stek mozhno tak: #include "stack.h" // ispol'zuem interfejs steka void some_function () { push ( 'c' ); char c = pop (); if ( c != 'c' ) error ( "nevozmozhno" ); } Poskol'ku dannye est' edinstvennaya veshch', kotoruyu hotyat skryvat', ponyatie upryatyvaniya dannyh trivial'no rasshiryaetsya do ponyatiya upryatyvaniya informacii, t.e. imen peremennyh, konstant, funkcij i tipov, kotorye tozhe mogut byt' lokal'nymi v module. Hotya S++ i ne prednaznachalsya special'no dlya podderzhki modul'nogo programmirovaniya, klassy podderzhivayut koncepciyu modul'nosti ($$5.4.3 i $$5.4.4). Pomimo etogo S++, estestvenno, imeet uzhe prodemonstrirovannye vozmozhnosti modul'nosti, kotorye est' v S, t.e. predstavlenie modulya kak otdel'noj edinicy translyacii. 1.2.3 Abstrakciya dannyh Modul'noe programmirovanie predpolagaet gruppirovku vseh dannyh odnogo tipa vokrug odnogo modulya, upravlyayushchego etim tipom. Esli potrebuyutsya steki dvuh raznyh vidov, mozhno opredelit' upravlyayushchij imi modul' s takim interfejsom: class stack_id { /* ... */ }; // stack_id tol'ko tip // nikakoj informacii o stekah // zdes' ne soderzhitsya stack_id create_stack ( int size ); // sozdat' stek i vozvratit' // ego identifikator void push ( stack_id, char ); char pop ( stack_id ); destroy_stack ( stack_id ); // unichtozhenie steka Konechno takoe reshenie namnogo luchshe, chem haos, svojstvennyj tradicionnym, nestrukturirovannym resheniyam, no modeliruemye takim sposobom tipy sovershenno ochevidno otlichayutsya ot "nastoyashchih", vstroennyh. Kazhdyj upravlyayushchij tipom modul' dolzhen opredelyat' svoj sobstvennyj algoritm sozdaniya "peremennyh" etogo tipa. Ne sushchestvuet universal'nyh pravil prisvaivaniya identifikatorov, oboznachayushchih ob容kty takogo tipa. U "peremennyh" takih tipov ne sushchestvuet imen, kotorye byli by izvestny translyatoru ili drugim sistemnym programmam, i eti "peremennye" ne podchinyayutsya obychnym pravilam oblastej vidimosti i peredachi parametrov. Tip, realizuemyj upravlyayushchim im modulem, po mnogim vazhnym aspektam sushchestvenno otlichaetsya ot vstroennyh tipov. Takie tipy ne poluchayut toj podderzhki so storony translyatora (raznogo vida kontrol'), kotoraya obespechivaetsya dlya vstroennyh tipov. Problema zdes' v tom, chto programma formuliruetsya v terminah nebol'shih (odno-dva slova) deskriptorov ob容ktov, a ne v terminah samih ob容ktov ( stack_id mozhet sluzhit' primerom takogo deskriptora). |to oznachaet, chto translyator ne smozhet otlovit' glupye, ochevidnye oshibki, vrode teh, chto dopushcheny v privedennoj nizhe funkcii: void f () { stack_id s1; stack_id s2; s1 = create_stack ( 200 ); // oshibka: zabyli sozdat' s2 push ( s1,'a' ); char c1 = pop ( s1 ); destroy_stack ( s2 ); // nepriyatnaya oshibka // oshibka: zabyli unichtozhit' s1 s1 = s2; // eto prisvaivanie yavlyaetsya po suti // prisvaivaniem ukazatelej, // no zdes' s2 ispol'zuetsya posle unichtozheniya } Inymi slovami, koncepciya modul'nosti, podderzhivayushchaya paradigmu upryatyvaniya dannyh, ne zapreshchaet takoj stil' programmirovaniya, no i ne sposobstvuet emu. V yazykah Ada, Clu, S++ i podobnyh im eta trudnost' preodolevaetsya blagodarya tomu, chto pol'zovatelyu razreshaetsya opredelyat' svoi tipy, kotorye traktuyutsya v yazyke prakticheski tak zhe, kak vstroennye. Takie tipy obychno nazyvayut abstraktnymi tipami dannyh, hotya luchshe, pozhaluj, ih nazyvat' prosto pol'zovatel'skimi. Bolee strogim opredeleniem abstraktnyh tipov dannyh bylo by ih matematicheskoe opredelenie. Esli by udalos' ego dat', to, chto my nazyvaem v programmirovanii tipami, bylo by konkretnym predstavleniem dejstvitel'no abstraktnyh sushchnostej. Kak opredelit' "bolee abstraktnye" tipy, pokazano v $$4.6. Paradigmu zhe programmirovaniya mozhno vyrazit' teper' tak: Opredelite, kakie tipy vam nuzhny; predostav'te polnyj nabor operacij dlya kazhdogo tipa. Esli net neobhodimosti v raznyh ob容ktah odnogo tipa, to stil' programmirovaniya, sut' kotorogo svoditsya k upryatyvaniyu dannyh, i sledovanie kotoromu obespechivaetsya s pomoshch'yu koncepcii modul'nosti, vpolne adekvaten etoj paradigme. Arifmeticheskie tipy, podobnye tipam racional'nyh i kompleksnyh chisel, yavlyayutsya tipichnymi primerami pol'zovatel'skih tipov: class complex { double re, im; public: complex(double r, double i) { re=r; im=i; } complex(double r) // preobrazovanie float->complex { re=r; im=0; } friend complex operator+(complex, complex); friend complex operator-(complex, complex); // vychitanie friend complex operator-(complex) // unarnyj minus friend complex operator*(complex, complex); friend complex operator/(complex, complex); // ... }; Opisanie klassa (t.e. opredelyaemogo pol'zovatelem tipa) complex zadaet predstavlenie kompleksnogo chisla i nabor operacij s kompleksnymi chislami. Predstavlenie yavlyaetsya chastnym (private): re i im dostupny tol'ko dlya funkcij, ukazannyh v opisanii klassa complex. Podobnye funkcii mogut byt' opredeleny tak: complex operator + ( complex a1, complex a2 ) { return complex ( a1.re + a2.re, a1.im + a2.im ); } i ispol'zovat'sya sleduyushchim obrazom: void f () { complex a = 2.3; complex b = 1 / a; complex c = a + b * complex ( 1, 2.3 ); // ... c = - ( a / b ) + 2; } Bol'shinstvo modulej (hotya i ne vse) luchshe opredelyat' kak pol'zovatel'skie tipy. 1.2.4 Predely abstrakcii dannyh Abstraktnyj tip dannyh opredelyaetsya kak nekij "chernyj yashchik". Posle svoego opredeleniya on po suti nikak ne vzaimodejstvuet s programmoj. Ego nikak nel'zya prisposobit' dlya novyh celej, ne menyaya opredeleniya. V etom smysle eto negibkoe reshenie. Pust', naprimer, nuzhno opredelit' dlya graficheskoj sistemy tip shape (figura). Poka schitaem, chto v sisteme mogut byt' takie figury: okruzhnost' (circle), treugol'nik (triangle) i kvadrat (square). Pust' uzhe est' opredeleniya tochki i cveta: class point { /* ... */ }; class color { /* ... */ }; Tip shape mozhno opredelit' sleduyushchim obrazom: enum kind { circle, triangle, square }; class shape { point center; color col; kind k; // predstavlenie figury public: point where () { return center; } void move ( point to ) { center = to; draw (); } void draw (); void rotate ( int ); // eshche nekotorye operacii }; "Pole tipa" k neobhodimo dlya togo, chtoby takie operacii, kak draw () i rotate (), mogli opredelyat', s kakoj figuroj oni imeyut delo (v yazykah vrode Paskalya mozhno ispol'zovat' dlya etogo zapis' s variantami, v kotoroj k yavlyaetsya polem-deskriminantom). Funkciyu draw () mozhno opredelit' tak: void shape :: draw () { switch ( k ) { case circle: // risovanie okruzhnosti break; case triangle: // risovanie treugol'nika break; case square: // risovanie kvadrata break; } } |to ne funkciya, a koshmar. V nej nuzhno uchest' vse vozmozhnye figury, kakie tol'ko est'. Poetomu ona dopolnyaetsya novymi operatorami, kak tol'ko v sisteme poyavlyaetsya novaya figura. Ploho to, chto posle opredeleniya novoj figury nuzhno proverit' i, vozmozhno, izmenit' vse starye operacii klassa. Poetomu, esli vam nedostupen ishodnyj tekst kazhdoj operacii klassa, vvesti novuyu figuru v sistemu prosto nevozmozhno. Poyavlenie lyuboj novoj figury privodit k manipulyaciyam s tekstom kazhdoj sushchestvennoj operacii klassa. Trebuetsya dostatochno vysokaya kvalifikaciya, chtoby spravit'sya s etoj zadachej, no vse ravno mogut poyavit'sya oshibki v uzhe otlazhennyh chastyah programmy, rabotayushchih so starymi figurami. Vozmozhnost' vybora predstavleniya dlya konkretnoj figury sil'no suzhaetsya, esli trebovat', chtoby vse ee predstavleniya ukladyvalis' v uzhe zadannyj format, specificirovannyj obshchim opredeleniem figury (t.e. opredeleniem tipa shape). 1.2.5 Ob容ktno-orientirovannoe programmirovanie Problema sostoit v tom, chto my ne razlichaem obshchie svojstva figur (naprimer, figura imeet cvet, ee mozhno narisovat' i t.d.) i svojstva konkretnoj figury (naprimer, okruzhnost' - eto takaya figura, kotoraya imeet radius, ona izobrazhaetsya s pomoshch'yu funkcii, risuyushchej dugi i t.d.). Sut' ob容ktno-orientirovannogo programmirovaniya v tom, chto ono pozvolyaet vyrazhat' eti razlichiya i ispol'zuet ih. YAzyk, kotoryj imeet konstrukcii dlya vyrazheniya i ispol'zovaniya podobnyh razlichij, podderzhivaet ob容ktno-orientirovannoe programmirovanie. Vse drugie yazyki ne podderzhivayut ego. Zdes' osnovnuyu rol' igraet mehanizm nasledovaniya, zaimstvovannyj iz yazyka Simula. Vnachale opredelim klass, zadayushchij obshchie svojstva vseh figur: class shape { point center; color col; // ... public: point where () { return center; } void move ( point to ) { center = to; draw(); } virtual void draw (); virtual void rotate ( int ); // ... }; Te funkcii, dlya kotoryh mozhno opredelit' zayavlennyj interfejs, no realizaciya kotoryh (t.e. telo s operatornoj chast'yu) vozmozhna tol'ko dlya konkretnyh figur, otmecheny sluzhebnym slovom virtual (virtual'nye). V Simule i S++ virtual'nost' funkcii oznachaet: "funkciya mozhet byt' opredelena pozdnee v klasse, proizvodnom ot dannogo". S uchetom takogo opredeleniya klassa mozhno napisat' obshchie funkcii, rabotayushchie s figurami: void rotate_all ( shape v [], int size, int angle ) // povernut' vse elementy massiva "v" razmera "size" // na ugol ravnyj "angle" { int i = 0; while ( i<size ) { v [ i ] . rotate ( angle ); i = i + 1; } } Dlya opredeleniya konkretnoj figury sleduet ukazat', prezhde vsego, chto eto - imenno figura i zadat' ee osobye svojstva (vklyuchaya i virtual'nye funkcii): class circle : public shape { int radius; public: void draw () { /* ... */ }; void rotate ( int ) {} // da, poka pustaya funkciya }; V yazyke S++ klass circle nazyvaetsya proizvodnym po otnosheniyu k klassu shape, a klass shape nazyvaetsya bazovym dlya klassa circle. Vozmozhna drugaya terminologiya, ispol'zuyushchaya nazvaniya "podklass" i "superklass" dlya klassov circle i shape sootvetstvenno. Teper' paradigma programmirovaniya formuliruetsya tak: Opredelite, kakoj klass vam neobhodim; predostav'te polnyj nabor operacij dlya kazhdogo klassa; obshchnost' klassov vyrazite yavno s pomoshch'yu nasledovaniya. Esli obshchnost' mezhdu klassami otsutstvuet, vpolne dostatochno abstrakcii dannyh. Naskol'ko primenimo ob容ktno-orientirovannoe programmirovanie dlya dannoj oblasti prilozheniya opredelyaetsya stepen'yu obshchnosti mezhdu raznymi tipami, kotoraya pozvolyaet ispol'zovat' nasledovanie i virtual'nye funkcii. V nekotoryh oblastyah, takih, naprimer, kak interaktivnaya grafika, est' shirokij prostor dlya ob容ktno-orientirovannogo programmirovaniya. V drugih oblastyah, v kotoryh ispol'zuyutsya tradicionnye arifmeticheskie tipy i vychisleniya nad nimi, trudno najti primenenie dlya bolee razvityh stilej programmirovaniya, chem abstrakciya dannyh. Zdes' sredstva, podderzhivayushchie ob容ktno-orientirovannoe programmirovanie, ochevidno, izbytochny. Nahozhdenie obshchnosti sredi otdel'nyh tipov sistemy predstavlyaet soboj netrivial'nyj process. Stepen' takoj obshchnosti zavisit ot sposoba proektirovaniya sistemy. V processe proektirovaniya vyyavlenie obshchnosti klassov dolzhno byt' postoyannoj cel'yu. Ona dostigaetsya dvumya sposobami: libo proektirovaniem special'nyh klassov, ispol'zuemyh kak "kirpichi" pri postroenii drugih, libo poiskom pohozhih klassov dlya vydeleniya ih obshchej chasti v odin bazovyj klass. S popytkami ob座asnit', chto takoe ob容ktno-orientirovannoe programmirovanie, ne ispol'zuya konkretnyh konstrukcij yazykov programmirovaniya, mozhno poznakomit'sya v rabotah [2] i [6], privedennyh v spiske literatury v glave 11. Itak, my ukazali, kakuyu minimal'nuyu podderzhku dolzhen obespechivat' yazyk programmirovaniya dlya procedurnogo programmirovaniya, dlya upryatyvaniya dannyh, abstrakcii dannyh i ob容ktno-orientirovannogo programmirovaniya. Teper' neskol'ko podrobnee opishem sredstva yazyka, hotya i ne samye sushchestvennye, no pozvolyayushchie bolee effektivno realizovat' abstrakciyu dannyh i ob容ktno-orientirovannoe programmirovanie. 1.3 "Uluchshennyj S" Minimal'naya podderzhka procedurnogo programmirovaniya vklyuchaet funkcii, arifmeticheskie operacii, vybirayushchie operatory i cikly. Pomimo etogo dolzhny byt' predostavleny operacii vvoda- vyvoda. Bazovye yazykovye sredstva S++ unasledoval ot S (vklyuchaya ukazateli), a operacii vvoda-vyvoda predostavlyayutsya bibliotekoj. Samaya zachatochnaya koncepciya modul'nosti realizuetsya s pomoshch'yu mehanizma razdel'noj translyacii. 1.3.1 Programma i standartnyj vyvod Samaya malen'kaya programma na S++ vyglyadit tak: main () { } V etoj programme opredelyaetsya funkciya, nazyvaemaya main, kotoraya ne imeet parametrov i nichego ne delaet. Figurnye skobki { i } ispol'zuyutsya v S++ dlya gruppirovaniya operatorov. V dannom sluchae oni oboznachayut nachalo i konec tela (pustogo) funkcii main. V kazhdoj programme na S++ dolzhna byt' svoya funkciya main(), i programma nachinaetsya s vypolneniya etoj funkcii. Obychno programma vydaet kakie-to rezul'taty. Vot programma, kotoraya vydaet privetstvie Hello, World! (Vsem privet!): #include <iostream.h> int main () { cout << "Hello, World!\n"; } Stroka #include <iostream.h> soobshchaet translyatoru, chto nado vklyuchit' v programmu opisaniya, neobhodimye dlya raboty standartnyh potokov vvoda- vyvoda, kotorye nahodyatsya v iostream.h. Bez etih opisanij vyrazhenie cout << "Hello, World!\n" ne imelo by smysla. Operaciya << ("vydat'") zapisyvaet svoj vtoroj parametr v pervyj parametr. V dannom sluchae stroka "Hello, World!\n" zapisyvaetsya v standartnyj vyhodnoj potok cout. Stroka - eto posledovatel'nost' simvolov, zaklyuchennaya v dvojnye kavychki. Dva simvola: obratnoj drobnoj cherty \ i neposredstvenno sleduyushchij za nim - oboznachayut nekotoryj special'nyj simvol. V dannom sluchae \n yavlyaetsya simvolom konca stroki (ili perevoda stroki), poetomu on vydaetsya posle simvolov Hello, world! Celoe znachenie, vozvrashchaemoe funkciej main(), esli tol'ko ono est', schitaetsya vozvrashchaemym sisteme znacheniem programmy. Esli nichego ne vozvrashchaetsya, sistema poluchit kakoe-to "musornoe" znachenie. Sredstva vvoda/vyvoda potokovoj biblioteki podrobno opisyvayutsya v glave 10. 1.3.2 Peremennye i arifmeticheskie operacii Kazhdoe imya i kazhdoe vyrazhenie obyazany imet' tip. Imenno tip opredelyaet operacii, kotorye mogut vypolnyat'sya nad nimi. Naprimer, v opisanii int inch; govoritsya, chto inch imeet tip int, t.e. inch yavlyaetsya celoj peremennoj. Opisanie - eto operator, kotoryj vvodit imya v programmu. V opisanii ukazyvaetsya tip imeni. Tip, v svoyu ochered', opredelyaet kak pravil'no ispol'zovat' imya ili vyrazhenie. Osnovnye tipy, naibolee priblizhennye k "apparatnoj real'nosti" mashiny, takovy: char short int long Oni predstavlyayut celye chisla. Sleduyushchie tipy: float double long double predstavlyayut chisla s plavayushchej tochkoj. Peremennaya tipa char imeet razmer, nuzhnyj dlya hraneniya odnogo simvola na dannoj mashine (obychno eto odin bajt). Peremennaya int imeet razmer, neobhodimyj dlya celoj arifmetiki na dannoj mashine (obychno eto odno slovo). Sleduyushchie arifmeticheskie operacii mozhno ispol'zovat' nad lyubym sochetaniem perechislennyh tipov: + (plyus, unarnyj i binarnyj) - (minus, unarnyj i binarnyj) * (umnozhenie) / (delenie) % (ostatok ot deleniya) To zhe verno dlya operacij otnosheniya: == (ravno) != (ne ravno) < (men'she chem) <= (men'she ili ravno) >= (bol'she ili ravno) Dlya operacij prisvaivaniya i arifmeticheskih operacij v S++ vypolnyayutsya vse osmyslennye preobrazovaniya osnovnyh tipov, chtoby ih mozhno bylo neogranichenno ispol'zovat' lyubye ih sochetaniya: double d; int i; short s; // ... d = d + i; i = s * i; Simvol = oboznachaet obychnoe prisvaivanie. 1.3.3 Ukazateli i massivy Massiv mozhno opisat' tak: char v [ 10 ]; // massiv iz 10 simvolov Opisanie ukazatelya imeet takoj vid: char * p; // ukazatel' na simvol Zdes' [] oznachaet "massiv iz", a simvol * oznachaet "ukazatel' na". Znachenie nizhnej granicy indeksa dlya vseh massivov ravno nulyu, poetomu v imeet 10 elementov: v [ 0 ] ... v [ 9 ]. Peremennaya tipa ukazatel' mozhet soderzhat' adres ob容kta sootvetstvuyushchego tipa: p = & v [ 3 ]; // p ukazyvaet na 4-j element massiva v Unarnaya operaciya & oznachaet vzyatie adresa. 1.3.4 Uslovnye operatory i cikly V S++ est' tradicionnyj nabor vybirayushchih operatorov i ciklov. Nizhe privodyatsya primery operatorov if, switch i while. V sleduyushchem primere pokazano preobrazovanie dyujma v santimetr i obratno. Predpolagaetsya, chto vo vhodnom potoke znachenie v santimetrah zavershaetsya simvolom i, a znachenie v dyujmah - simvolom c: #include <iostream.h> int main () { const float fac = 2.54; float x, in, cm; char ch = 0; cout << "enter length: "; cin >> x; // vvod chisla s plavayushchej tochkoj cin >> ch // vvod zavershayushchego simvola if ( ch == 'i' ) { // dyujm in = x; cm = x * fac; } else if ( ch == 'c' ) { // santimetry in = x / fac; cm = x; } else in = cm = 0; cout << in << " in = " << cm << " cm\n"; } Operaciya >> ("vvesti iz") ispol'zuetsya kak operator vvoda; cin yavlyaetsya standartnym vhodnym potokom. Tip operanda, raspolozhennogo sprava ot operacii >>, opredelyaet, kakoe znachenie vvoditsya; ono zapisyvaetsya v etot operand. Operator switch (pereklyuchatel') sravnivaet znachenie s naborom konstant. Proverku v predydushchem primere mozhno zapisat' tak: switch ( ch ) { case 'i': in = x; cm = x * fac; break; case 'c': in = x / fac; cm = x; break; default: in = cm = 0; break; } Operatory break ispol'zuyutsya dlya vyhoda iz pereklyuchatelya. Vse konstanty variantov dolzhny byt' razlichny. Esli sravnivaemoe znachenie ne sovpadaet ni s odnoj iz nih, vypolnyaetsya operator s metkoj default. Variant default mozhet i otsutstvovat'. Privedem zapis', zadayushchuyu kopirovanie 10 elementov odnogo massiva v drugoj: int v1 [ 10 ]; int v2 [ 10 ]; // ... for ( int i=0; i<10; i++ ) v1 [ i ] = v2 [ i ]; Slovami eto mozhno vyrazit' tak: "Nachat' s i ravnogo nulyu, i poka i men'she 10, kopirovat' i-tyj element i uvelichivat' i." Inkrement (++) peremennoj celogo tipa prosto svoditsya k uvelicheniyu na 1. 1.3.5 Funkcii Funkciya - eto poimenovannaya chast' programmy, kotoraya mozhet vyzyvat'sya iz drugih chastej programmy stol'ko raz, skol'ko neobhodimo. Privedem programmu, vydayushchuyu stepeni chisla dva: extern float pow ( float, int ); // pow () opredelena v drugom meste int main () { for ( int i=0; i<10; i++ ) cout << pow ( 2, i ) << '\n'; } Pervaya stroka yavlyaetsya opisaniem funkcii. Ona zadaet pow kak funkciyu s parametrami tipa float i int, vozvrashchayushchuyu znachenie tipa float. Opisanie funkcii neobhodimo dlya ee vyzova, ee opredelenie nahoditsya v drugom meste. Pri vyzove funkcii tip kazhdogo fakticheskogo parametra sveryaetsya s tipom, ukazann