sistematicheski vydelyat' tekst programmy probelami. Est' neskol'ko sposobov raspolozheniya teksta programmy, no net prichin schitat', chto odin iz nih - nailuchshij. Hotya u kazhdogo svoj vkus. To zhe mozhno skazat' i o kommentariyah. Odnako mozhno zapolnit' programmu takimi kommentariyami, chto chitat' i ponimat' ee budet tol'ko trudnee. Translyator ne v silah ponyat' kommentarij, poetomu on ne mozhet ubedit'sya v tom, chto kommentarij: [1] osmyslennyj, [2] dejstvitel'no opisyvaet programmu, [3] ne ustarel. Vo mnogih programmah popadayutsya nepostizhimye, dvusmyslennye i prosto nevernye kommentarii. Luchshe voobshche obhodit'sya bez nih, chem davat' takie kommentarii. Esli nekij fakt mozhno pryamo vyrazit' v yazyke, to tak i sleduet delat', i ne nado schitat', chto dostatochno upomyanut' ego v kommentarii. Poslednee zamechanie otnositsya k kommentariyam, podobnym privedennym nizhe: // peremennuyu "v" neobhodimo inicializirovat'. // peremennaya "v" mozhet ispol'zovat'sya tol'ko v funkcii "f()". // do vyzova lyuboj funkcii iz etogo fajla // neobhodimo vyzvat' funkciyu "init()". // v konce svoej programmy vyzovite funkciyu "cleanup()". // ne ispol'zujte funkciyu "weird()". // funkciya "f()" imeet dva parametra. Pri pravil'nom programmirovanii na S++ takie kommentarii obychno okazyvayutsya izlishnimi. CHtoby imenno eti kommentarii stali nenuzhnymi, mozhno vospol'zovat'sya pravilami svyazyvaniya ($$4.2) i oblastej vidimosti, a takzhe pravilami inicializacii i unichtozheniya ob容ktov klassa ($$5.5). Esli nekotoroe utverzhdenie vyrazhaetsya samoj programmoj, ne nuzhno povtoryat' ego v kommentarii. Naprimer: a = b + c; // a prinimaet znachenie b+c count++; // uvelichim schetchik count Takie kommentarii huzhe, chem izbytochnye. Oni razduvayut ob容m teksta, zatumanivayut programmu i mogut byt' dazhe lozhnymi. V to zhe vremya kommentarii imenno takogo roda ispol'zuyut dlya primerov v uchebnikah po yazykam programmirovaniya, podobnyh etoj knige. |to odna iz mnogih prichin, po kotoroj uchebnaya programma otlichaetsya ot nastoyashchej. Mozhno rekomendovat' takoj stil' vvedeniya kommentariev v programmu: [1] nachinat' s kommentariya kazhdyj fajl programmy: ukazat' v obshchih chertah, chto v nej opredelyaetsya, dat' ssylki na spravochnye rukovodstva, obshchie idei po soprovozhdeniyu programmy i t.d.; [2] snabzhat' kommentariem kazhdoe opredelenie klassa ili shablona tipa; [3] kommentirovat' kazhduyu netrivial'nuyu funkciyu, ukazav: ee naznachenie, ispol'zuemyj algoritm (esli tol'ko on neocheviden) i, vozmozhno, predpolozheniya ob okruzhenii, v kotorom rabotaet funkciya; [4] kommentirovat' opredelenie kazhdoj global'noj peremennoj; [5] davat' nekotoroe chislo kommentariev v teh mestah, gde algoritm neocheviden ili neperenosim; [6] bol'she prakticheski nichego. Privedem primer: // tbl.c: Realizaciya tablicy imen. /* Ispol'zovan metod Gaussa sm. Ral'ston "Nachal'nyj kurs po ..." str. 411. */ // v swap() predpolagaetsya, chto stek AT&T nachinaetsya s 3B20. /************************************ Avtorskie prava (c) 1991 AT&T, Inc Vse prava sohraneny **************************************/ Pravil'no podobrannye i horosho sostavlennye kommentarii igrayut v programme vazhnuyu rol'. Napisat' horoshie kommentarii ne menee trudno, chem samu programmu, i eto - iskusstvo, v kotorom stoit sovershenstvovat'sya. Zametim, chto esli v funkcii ispol'zuyutsya tol'ko kommentarii vida //, to lyubuyu ee chast' mozhno sdelat' kommentariem s pomoshch'yu /* */, i naoborot. 3.5 Uprazhneniya 1. (*1) Sleduyushchij cikl for perepishite s pomoshch'yu operatora while: for (i=0; i<max_length; i++) if (input_line[i] == '?') quest_count++; Zapishite cikl, ispol'zuya v kachestve ego upravlyayushchej peremennoj ukazatel' tak, chtoby uslovie imelo vid *p=='?'. 2. (*1) Ukazhite poryadok vychisleniya sleduyushchih vyrazhenij, zadav polnuyu skobochnuyu strukturu: a = b + c * d << 2 & 8 a & 077 != 3 a == b || a == c && c < 5 c = x != 0 0 <= i < 7 f(1,2) + 3 a = - 1 + + b -- - 5 a = b == c ++ a = b = c = 0 a[4][2] *= * b ? c : * d * 2 a-b, c=d 3. (*2) Ukazhite 5 razlichnyh konstrukcij na S++, znachenie kotoryh neopredeleno. 4. (*2) Privedite 10 raznyh primerov neperenosimyh konstrukcij na S++. 5. (*1) CHto proizojdet pri delenii na nul' v vashej programme na S++? CHto budet v sluchae perepolneniya ili poteri znachimosti? 6. (*1) Ukazhite poryadok vychisleniya sleduyushchih vyrazhenij, zadav ih polnuyu skobochnuyu strukturu: *p++ *--p ++a-- (int*)p->m *p.m *a[i] 7. (*2) Napishite takie funkcii: strlen() - podschet dliny stroki, strcpy() - kopirovanie strok i strcmp() - sravnenie strok. Kakimi dolzhny byt' tipy parametrov i rezul'tatov funkcij? Sravnite ih so standartnymi versiyami, imeyushchimisya v <string.h> i v vashem rukovodstve. 8. (*1) Vyyasnite, kak vash translyator otreagiruet na takie oshibki: void f(int a, int b) { if (a = 3) // ... if (a&077 == 0) // ... a := b+1; } Posmotrite, kakova budet reakciya na bolee prostye oshibki. 9. (*2) Napishite funkciyu cat(), kotoraya poluchaet dva parametra-stroki i vozvrashchaet stroku, yavlyayushchuyusya ih konkatenaciej. Dlya rezul'tiruyushchej stroki ispol'zujte pamyat', otvedennuyu s pomoshch'yu new. Napishite funkciyu rev() dlya perevertyvaniya stroki, peredannoj ej v kachestve parametra. |to oznachaet, chto posle vyzova rev(p) poslednij simvol p stanet pervym i t.d. 10. (*2) CHto delaet sleduyushchaya funkciya? void send(register* to, register* from, register count) // Psevdoustrojstvo. Vse kommentarii soznatel'no udaleny { register n=(count+7)/8; switch (count%8) { case 0: do { *to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; } while (--n>0); } } Kakov mozhet byt' smysl etoj funkcii? 11. (*2) Napishite funkciyu atoi(), kotoraya imeet parametr - stroku cifr i vozvrashchaet sootvetstvuyushchee ej celoe. Naprimer, atoi("123") ravno 123. Izmenite funkciyu atoi() tak, chtoby ona mogla perevodit' v chislo posledovatel'nost' cifr ne tol'ko v desyatichnoj, no i v vos'merichnoj i shestnadcaterichnoj zapisi, prinyatoj v S++. Dobav'te vozmozhnost' perevoda simvol'nyh konstant S++. Napishite funkciyu itoa() dlya perevoda celogo znacheniya v strokovoe predstavlenie. 12. (*2) Perepishite funkciyu get_token() ($$3.12) tak, chtoby ona chitala celuyu stroku v bufer, a zatem vydavala leksemy, chitaya po simvolu iz bufera. 13. (*2) Vvedite v programmu kal'kulyatora iz $$3.1 takie funkcii, kak sqrt(), log() i sin(). Podskazka: zadajte predopredelennye imena i vyzyvajte funkcii s pomoshch'yu massiva ukazatelej na nih. Ne zabyvajte proveryat' parametry, peredavaemye etim funkciyam. 14. (*3) Vvedite v kal'kulyator vozmozhnost' opredelyat' pol'zovatel'skie funkcii. Podskazka: opredelite funkciyu kak posledovatel'nost' operatorov, budto by zadannuyu samim pol'zovatelem. |tu posledovatel'nost' mozhno hranit' ili kak stroku simvolov, ili kak spisok leksem. Kogda vyzyvaetsya funkciya, nado vybirat' i vypolnyat' operacii. Esli pol'zovatel'skie funkcii mogut imet' parametry, to pridetsya pridumat' formu zapisi i dlya nih. 15. (*1.5) Peredelajte programmu kal'kulyatora, ispol'zuya strukturu symbol vmesto staticheskih peremennyh name_string i number_value: struct symbol { token_value tok; union { double number_value; char* name_string; }; }; 16.(*2.5) Napishite programmu, kotoraya udalyaet vse kommentarii iz programmy na S++. |to znachit, nado chitat' simvoly iz cin i udalyat' kommentarii dvuh vidov: // i /* */. Poluchivshijsya tekst zapishite v cout. Ne zabot'tes' o krasivom vide poluchivshegosya teksta (eto uzhe drugaya, bolee slozhnaya zadacha). Korrektnost' programm nevazhna. Nuzhno uchityvat' vozmozhnost' poyavleniya simvolov //, /* i */ v kommentariyah, strokah i simvol'nyh konstantah. 17. (*2) Issledujte razlichnye programmy i vyyasnite, kakie sposoby vydeleniya teksta probelami i kakie kommentarii ispol'zuyutsya.  * GLAVA 4 Iteraciya prisushcha cheloveku, a rekursiya - bogu. - L. Dojch Vse netrivial'nye programmy sostoyat iz neskol'kih razdel'no transliruemyh edinic, po tradicii nazyvaemyh fajlami. V etoj glave opisano, kak razdel'no transliruemye funkcii mogut vyzyvat' drug druga, kakim obrazom oni mogut imet' obshchie dannye, i kak dobit'sya neprotivorechivosti tipov, ispol'zuemyh v raznyh fajlah programmy. Podrobno obsuzhdayutsya funkcii, v tom chisle: peredacha parametrov, peregruzka imeni funkcii, standartnye znacheniya parametrov, ukazateli na funkcii i, estestvenno, opisaniya i opredeleniya funkcij. V konce glavy obsuzhdayutsya makrovozmozhnosti yazyka. 4.1 Vvedenie Rol' fajla v yazyke S++ svoditsya k tomu, chto on opredelyaet fajlovuyu oblast' vidimosti ($$R.3.2). |to oblast' vidimosti global'nyh funkcij (kak staticheskih, tak i podstanovok), a takzhe global'nyh peremennyh (kak staticheskih, tak i so specifikaciej const). Krome togo, fajl yavlyaetsya tradicionnoj edinicej hraneniya v sisteme, a takzhe edinicej translyacii. Obychno sistemy hranyat, transliruyut i predstavlyayut pol'zovatelyu programmu na S++ kak mnozhestvo fajlov, hotya sushchestvuyut sistemy, ustroennye inache. V etoj glave budet obsuzhdat'sya v osnovnom tradicionnoe ispol'zovanie fajlov. Vsyu programmu pomestit' v odin fajl, kak pravilo, nevozmozhno, poskol'ku programmy standartnyh funkcij i programmy operacionnoj sistemy nel'zya vklyuchit' v tekstovom vide v programmu pol'zovatelya. Voobshche, pomeshchat' vsyu programmu pol'zovatelya v odin fajl obychno neudobno i nepraktichno. Razbieniya programmy na fajly mozhet oblegchit' ponimanie obshchej struktury programmy i daet translyatoru vozmozhnost' podderzhivat' etu strukturu. Esli edinicej translyacii yavlyaetsya fajl, to dazhe pri nebol'shom izmenenii v nem sleduet ego peretranslirovat'. Dazhe dlya programm ne slishkom bol'shogo razmera vremya na peretranslyaciyu mozhno znachitel'no sokratit', esli ee razbit' na fajly podhodyashchego razmera. Vernemsya k primeru s kal'kulyatorom. Reshenie bylo dano v vide odnogo fajla. Kogda vy popytaetes' ego translirovat', neizbezhno vozniknut nekotorye problemy s poryadkom opisanij. Po krajnej mere odno "nenastoyashchee" opisanie pridetsya dobavit' k tekstu, chtoby translyator mog razobrat'sya v ispol'zuyushchih drug druga funkciyah expr(), term() i prim(). Po tekstu programmy vidno, chto ona sostoit iz chetyreh chastej: leksicheskij analizator (skaner), sobstvenno analizator, tablica imen i drajver. Odnako, etot fakt nikak ne otrazhen v samoj programme. Na samom dele kal'kulyator ne byl zaprogrammirovan imenno tak. Tak ne sleduet pisat' programmu. Dazhe esli ne uchityvat' vse rekomendacii po programmirovaniyu, soprovozhdeniyu i optimizacii dlya takoj "zryashnoj" programmy, vse ravno ee sleduet sozdavat' iz neskol'kih fajlov hotya by dlya udobstva. CHtoby razdel'naya translyaciya stala vozmozhnoj, programmist dolzhen predusmotret' opisaniya, iz kotoryh translyator poluchit dostatochno svedenij o tipah dlya translyacii fajla, sostavlyayushchego tol'ko chast' programmy. Trebovanie neprotivorechivosti ispol'zovaniya vseh imen i tipov dlya programmy, sostoyashchej iz neskol'kih razdel'no transliruemyh chastej, tak zhe spravedlivo, kak i dlya programmy, sostoyashchej iz odnogo fajla. |to vozmozhno tol'ko v tom sluchae, kogda opisaniya, nahodyashchiesya v raznyh edinicah translyacii, budut soglasovany. V vashej sisteme programmirovaniya imeyutsya sredstva, kotorye sposobny ustanovit', vypolnyaetsya li eto. V chastnosti, mnogie protivorechiya obnaruzhivaet redaktor svyazej. Redaktor svyazej - eto programma, kotoraya svyazyvaet po imenam razdel'no transliruemye chasti programmy. Inogda ego po oshibke nazyvayut zagruzchikom. 4.2 Svyazyvanie Esli yavno ne opredeleno inache, to imya, ne yavlyayushcheesya lokal'nym dlya nekotoroj funkcii ili klassa, dolzhno oboznachat' odin i tot zhe tip, znachenie, funkciyu ili ob容kt vo vseh edinicah translyacii dannoj programmy. Inymi slovami, v programme mozhet byt' tol'ko odin nelokal'nyj tip, znachenie, funkciya ili ob容kt s dannym imenem. Rassmotrim dlya primera dva fajla: // file1.c int a = 1; int f() { /* kakie-to operatory */ } // file2.c extern int a; int f(); void g() { a = f(); } V funkcii g() ispol'zuyutsya te samye a i f(), kotorye opredeleny v fajle file1.c. Sluzhebnoe slovo extern pokazyvaet, chto opisanie a v fajle file2.c yavlyaetsya tol'ko opisaniem, no ne opredeleniem. Esli by prisutstvovala inicializaciya a, to extern prosto proignorirovalos' by, poskol'ku opisanie s inicializaciej vsegda schitaetsya opredeleniem. Lyuboj ob容kt v programme mozhet opredelyat'sya tol'ko odin raz. Opisyvat'sya zhe on mozhet neodnokratno, no vse opisaniya dolzhny byt' soglasovany po tipu. Naprimer: // file1.c: int a = 1; int b = 1; extern int c; // file2.c: int a; extern double b; extern int c; Zdes' soderzhitsya tri oshibki: peremennaya a opredelena dvazhdy ("int a;" - eto opredelenie, oznachayushchee "int a=0;"); b opisano dvazhdy, prichem s raznymi tipami; c opisano dvazhdy, no neopredeleno. Takie oshibki (oshibki svyazyvaniya) translyator, kotoryj obrabatyvaet fajly po otdel'nosti, obnaruzhit' ne mozhet, no bol'shaya ih chast' obnaruzhivaetsya redaktorom svyazej. Sleduyushchaya programma dopustima v S, no ne v S++: // file1.c: int a; int f() { return a; } // file2.c: int a; int g() { return f(); } Vo-pervyh, oshibkoj yavlyaetsya vyzov f() v file2.c, poskol'ku v etom fajle f() ne opisana. Vo-vtoryh, fajly programmy ne mogut byt' pravil'no svyazany, poskol'ku a opredeleno dvazhdy. Esli imya opisano kak static, ono stanovitsya lokal'nom v etom fajle. Naprimer: // file1.c: static int a = 6; static int f() { /* ... */ } // file2.c: static int a = 7; static int f() { /* ... */ } Privedennaya programma pravil'na, poskol'ku a i f opredeleny kak staticheskie. V kazhdom fajle svoya peremennaya a i funkciya f(). Esli peremennye i funkcii v dannoj chasti programmy opisany kak static, to v etoj chasti programmy proshche razobrat'sya, poskol'ku ne nuzhno zaglyadyvat' v drugie chasti. Opisyvat' funkcii kak staticheskie polezno eshche i po toj prichine, chto translyatoru predostavlyaetsya vozmozhnost' sozdat' bolee prostoj variant operacii vyzova funkcii. Esli imya ob容kta ili funkcii lokal'no v dannom fajle, to govoryat, chto ob容kt podlezhit vnutrennemu svyazyvaniyu. Obratno, esli imya ob容kta ili funkcii nelokal'no v dannom fajle, to on podlezhit vneshnemu svyazyvaniyu. Obychno govoryat, chto imena tipov, t.e. klassov i perechislenij, ne podlezhat svyazyvaniyu. Imena global'nyh klassov i perechislenij dolzhny byt' unikal'nymi vo vsej programme i imet' edinstvennoe opredelenie. Poetomu, esli est' dva dazhe identichnyh opredeleniya odnogo klassa, eto - vse ravno oshibka: // file1.c: struct S { int a; char b; }; extern void f(S*); // file2.c: struct S { int a; char b; }; void f(S* p) { /* ... */ } No bud'te ostorozhny: opoznat' identichnost' dvuh opisanij klassa ne v sostoyanii bol'shinstvo sistem programmirovaniya S++. Takoe dublirovanie mozhet vyzvat' dovol'no tonkie oshibki (ved' klassy v raznyh fajlah budut schitat'sya razlichnymi). Global'nye funkcii-podstanovki podlezhat vnutrennemu svyazyvaniyu, i to zhe po umolchaniyu spravedlivo dlya konstant. Sinonimy tipov, t.e. imena typedef, lokal'ny v svoem fajle, poetomu opisaniya v dvuh dannyh nizhe fajlah ne protivorechat drug drugu: // file1.c: typedef int T; const int a = 7; inline T f(int i) { return i+a; } // file2.c: typedef void T; const int a = 8; inline T f(double d) { cout<<d; } Konstanta mozhet poluchit' vneshnee svyazyvanie tol'ko s pomoshch'yu yavnogo opisaniya: // file3.c: extern const int a; const int a = 77; // file4.c: extern const int a; void g() { cout<<a; } V etom primere g() napechataet 77. 4.3 Zagolovochnye fajly Tipy odnogo ob容kta ili funkcii dolzhny byt' soglasovany vo vseh ih opisaniyah. Dolzhen byt' soglasovan po tipam i vhodnoj tekst, obrabatyvaemyj translyatorom, i svyazyvaemye chasti programmy. Est' prostoj, hotya i nesovershennyj, sposob dobit'sya soglasovannosti opisanij v razlichnyh fajlah. |to: vklyuchit' vo vhodnye fajly, soderzhashchie operatory i opredeleniya dannyh, zagolovochnye fajly, kotorye soderzhat interfejsnuyu informaciyu. Sredstvom vklyucheniya tekstov sluzhit makrokomanda #include, kotoraya pozvolyaet sobrat' v odin fajl (edinicu translyacii) neskol'ko ishodnyh fajlov programmy. Komanda #include "vklyuchaemyj-fajl" zamenyaet stroku, v kotoroj ona byla zadana, na soderzhimoe fajla vklyuchaemyj-fajl. Estestvenno, eto soderzhimoe dolzhno byt' tekstom na S++, poskol'ku ego budet chitat' translyator. Kak pravilo, operaciya vklyucheniya realizuetsya otdel'noj programmoj, nazyvaemoj preprocessorom S++. Ona vyzyvaetsya sistemoj programmirovaniya pered sobstvenno translyaciej dlya obrabotki takih komand vo vhodnom tekste. Vozmozhno i drugoe reshenie: chast' translyatora, neposredstvenno rabotayushchaya s vhodnym tekstom, obrabatyvaet komandy vklyucheniya fajlov po mere ih poyavleniya v tekste. V toj sisteme programmirovaniya, v kotoroj rabotaet avtor, chtoby uvidet' rezul'tat komand vklyucheniya fajlov, nuzhno zadat' komandu: CC -E file.c |ta komanda dlya obrabotki fajla file.c zapuskaet preprocessor (i tol'ko!), podobno tomu, kak komanda CC bez flaga -E zapuskaet sam translyator. Dlya vklyucheniya fajlov iz standartnyh katalogov (obychno katalogi s imenem INCLUDE) nado vmesto kavychek ispol'zovat' uglovye skobki < i >. Naprimer: #include <stream.h> // vklyuchenie iz standartnogo kataloga #include "myheader.h" // vklyuchenie iz tekushchego kataloga Vklyuchenie iz standartnyh katalogov imeet to preimushchestvo, chto imena etih katalogov nikak ne svyazany s konkretnoj programmoj (obychno vnachale vklyuchaemye fajly ishchutsya v kataloge /usr/include/CC, a zatem v /usr/include). K sozhaleniyu, v etoj komande probely sushchestvenny: #include < stream.h> // <stream.h> ne budet najden Bylo by nelepo, esli by kazhdyj raz pered vklyucheniem fajla trebovalas' ego peretranslyaciya. Obychno vklyuchaemye fajly soderzhat tol'ko opisaniya, a ne operatory i opredeleniya, trebuyushchie sushchestvennoj translyatornoj obrabotki. Krome togo, sistema programmirovaniya mozhet predvaritel'no ottranslirovat' zagolovochnye fajly, esli, konechno, ona nastol'ko razvita, chto sposobna sdelat' eto, ne izmenyaya semantiki programmy. Ukazhem, chto mozhet soderzhat' zagolovochnyj fajl: Opredeleniya tipov struct point { int x, y; }; SHablony tipov template<class T> class V { /* ... */ } Opisaniya funkcij extern int strlen(const char*); Opredeleniya inline char get() { return *p++; } funkcij-podstanovok Opisaniya dannyh extern int a; Opredeleniya konstant const float pi = 3.141593; Perechisleniya enum bool { false, true }; Opisaniya imen class Matrix; Komandy vklyucheniya fajlov #include <signal.h> Makroopredeleniya #define Case break;case Kommentarii /* proverka na konec fajla */ Perechislenie togo, chto stoit pomeshchat' v zagolovochnyj fajl, ne yavlyaetsya trebovaniem yazyka, eto prosto sovet po razumnomu ispol'zovaniyu vklyucheniya fajlov. S drugoj storony, v zagolovochnom fajle nikogda ne dolzhno byt': Opredelenij obychnyh funkcij char get() { return *p++; } Opredelenij dannyh int a; Opredelenij sostavnyh const tb[i] = { /* ... */ }; konstant Po tradicii zagolovochnye fajly imeyut rasshirenie .h, a fajly, soderzhashchie opredeleniya funkcij ili dannyh, rasshirenie .c. Inogda ih nazyvayut "h-fajly" ili "s-fajly" sootvetstvenno. Ispol'zuyut i drugie rasshireniya dlya etih fajlov: .C, cxx, .cpp i .cc. Prinyatoe rasshirenie vy najdete v svoem spravochnom rukovodstve. Makrosredstva opisyvayutsya v $$4.7. Otmetim tol'ko, chto v S++ oni ispol'zuyutsya ne stol' shiroko, kak v S, poskol'ku S++ imeet opredelennye vozmozhnosti v samom yazyke: opredeleniya konstant (const), funkcij-podstanovok (inline), dayushchie vozmozhnost' bolee prostoj operacii vyzova, i shablonov tipa, pozvolyayushchie porozhdat' semejstvo tipov i funkcij ($$8). Sovet pomeshchat' v zagolovochnyj fajl opredeleniya tol'ko prostyh, no ne sostavnyh, konstant ob座asnyaetsya vpolne pragmaticheskoj prichinoj. Prosto bol'shinstvo translyatorov ne nastol'ko razumno, chtoby predotvratit' sozdanie nenuzhnyh kopij sostavnoj konstanty. Voobshche govorya, bolee prostoj variant vsegda yavlyaetsya bolee obshchim, a znachit translyator dolzhen uchityvat' ego v pervuyu ochered', chtoby sozdat' horoshuyu programmu. 4.3.1 Edinstvennyj zagolovochnyj fajl Proshche vsego razbit' programmu na neskol'ko fajlov sleduyushchim obrazom: pomestit' opredeleniya vseh funkcij i dannyh v nekotoroe chislo vhodnyh fajlov, a vse tipy, neobhodimye dlya svyazi mezhdu nimi, opisat' v edinstvennom zagolovochnom fajle. Vse vhodnye fajly budut vklyuchat' zagolovochnyj fajl. Programmu kal'kulyatora mozhno razbit' na chetyre vhodnyh fajla .c: lex.c, syn.c, table.c i main.c. Zagolovochnyj fajl dc.h budet soderzhat' opisaniya kazhdogo imeni, kotoroe ispol'zuetsya bolee chem v odnom .c fajle: // dc.h: obshchee opisanie dlya kal'kulyatora #include <iostream.h> enum token_value { NAME, NUMBER, END, PLUS='+', MINUS='-', MUL='*', DIV='/', PRINT=';', ASSIGN='=', LP='(', RP=')' }; extern int no_of_errors; extern double error(const char* s); extern token_value get_token(); extern token_value curr_tok; extern double number_value; extern char name_string[256]; extern double expr(); extern double term(); extern double prim(); struct name { char* string; name* next; double value; }; extern name* look(const char* p, int ins = 0); inline name* insert(const char* s) { return look(s,1); } Esli ne privodit' sami operatory, lex.c dolzhen imet' takoj vid: // lex.c: vvod i leksicheskij analiz #include "dc.h" #include <ctype.h> token_value curr_tok; double number_value; char name_string[256]; token_value get_token() { /* ... */ } Ispol'zuya sostavlennyj zagolovochnyj fajl, my dob'emsya, chto opisanie kazhdogo ob容kta, vvedennogo pol'zovatelem, obyazatel'no okazhetsya v tom fajle, gde etot ob容kt opredelyaetsya. Dejstvitel'no, pri obrabotke fajla lex.c translyator stolknetsya s opisaniyami extern token_value get_token(); // ... token_value get_token() { /* ... */ } |to pozvolit translyatoru obnaruzhit' lyuboe rashozhdenie v tipah, ukazannyh pri opisanii dannogo imeni. Naprimer, esli by funkciya get_token() byla opisana s tipom token_value, no opredelena s tipom int, translyaciya fajla lex.c vyyavila by oshibku: nesootvetstvie tipa. Fajl syn.c mozhet imet' takoj vid: // syn.c: sintaksicheskij analiz i vychisleniya #include "dc.h" double prim() { /* ... */ } double term() { /* ... */ } double expr() { /* ... */ } Fajl table.c mozhet imet' takoj vid: // table.c: tablica imen i funkciya poiska #include "dc.h" extern char* strcmp(const char*, const char*); extern char* strcpy(char*, const char*); extern int strlen(const char*); const int TBLSZ = 23; name* table[TBLSZ]; name* look(char* p, int ins) { /* ... */ } Otmetim, chto raz strokovye funkcii opisany v samom fajle table.c, translyator ne mozhet proverit' soglasovannost' etih opisanij po tipam. Vsegda luchshe vklyuchit' sootvetstvuyushchij zagolovochnyj fajl, chem opisyvat' v fajle .c nekotoroe imya kak extern. |to mozhet privesti k vklyucheniyu "slishkom mnogogo", no takoe vklyuchenie nestrashno, poskol'ku ne vliyaet na skorost' vypolneniya programmy i ee razmer, a programmistu pozvolyaet sekonomit' vremya. Dopustim, funkciya strlen() snova opisyvaetsya v privedennom nizhe fajle main.c. |to tol'ko lishnij vvod simvolov i potencial'nyj istochnik oshibok, t.k. translyator ne smozhet obnaruzhit' rashozhdeniya v dvuh opisaniyah strlen() (vprochem, eto mozhet sdelat' redaktor svyazej). Takoj problemy ne vozniklo by, esli by v fajle dc.h soderzhalis' vse opisaniya extern, kak pervonachal'no i predpolagalos'. Podobnaya nebrezhnost' prisutstvuet v nashem primere, poskol'ku ona tipichna dlya programm na S. Ona ochen' estestvenna dlya programmista, no chasto privodit k oshibkam i takim programmam, kotorye trudno soprovozhdat'. Itak, preduprezhdenie sdelano! Nakonec, privedem fajl main.c: // main.c: inicializaciya, osnovnoj cikl, obrabotka oshibok #include "dc.h" double error(char* s) { /* ... */ } extern int strlen(const char*); int main(int argc, char* argv[]) { /* ... */ } V odnom vazhnom sluchae zagolovochnye fajly vyzyvayut bol'shoe neudobstvo. S pomoshch'yu serii zagolovochnyh fajlov i standartnoj biblioteki rasshiryayut vozmozhnosti yazyka, vvodya mnozhestvo tipov (kak obshchih, tak i rasschitannyh na konkretnye prilozheniya; sm. glavy 5-9). V takom sluchae tekst kazhdoj edinicy translyacii mozhet nachinat'sya tysyachami strok zagolovochnyh fajlov. Soderzhimoe zagolovochnyh fajlov biblioteki, kak pravilo, stabil'no i menyaetsya redko. Zdes' ochen' prigodilsya by pretranslyator, kotoryj obrabatyvaet ego. Po suti, nuzhen yazyk special'nogo naznacheniya so svoim translyatorom. No ustoyavshihsya metodov postroeniya takogo pretranslyatora poka net. 4.3.2 Mnozhestvennye zagolovochnye fajly Razbienie programmy v raschete na odin zagolovochnyj fajl bol'she podhodit dlya nebol'shih programm, otdel'nye chasti kotoryh ne imeyut samostoyatel'nogo naznacheniya. Dlya takih programm dopustimo, chto po zagolovochnomu fajlu nel'zya opredelit', ch'i opisaniya tam nahodyatsya i po kakoj prichine. Zdes' mogut pomoch' tol'ko kommentarii. Vozmozhno al'ternativnoe reshenie: pust' kazhdaya chast' programmy imeet svoj zagolovochnyj fajl, v kotorom opredelyayutsya sredstva, predostavlyaemye drugim chastyam. Teper' dlya kazhdogo fajla .c budet svoj fajl .h, opredelyayushchij, chto mozhet predostavit' pervyj. Kazhdyj fajl .c budet vklyuchat' kak svoj fajl .h, tak i nekotorye drugie fajly .h, ishodya iz svoih potrebnostej. Poprobuem ispol'zovat' takuyu organizaciyu programmy dlya kal'kulyatora. Zametim, chto funkciya error() nuzhna prakticheski vo vseh funkciyah programmy, a sama ispol'zuet tol'ko <iostream.h>. Takaya situaciya tipichna dlya funkcij, obrabatyvayushchih oshibki. Sleduet otdelit' ee ot fajla main.c: // error.h: obrabotka oshibok extern int no_of_errors; extern double error(const char* s); // error.c #include <iostream.h> #include "error.h" int no_of_errors; double error(const char* s) { /* ... */ } Pri takom podhode k razbieniyu programmy kazhduyu paru fajlov .c i .h mozhno rassmatrivat' kak modul', v kotorom fajl .h zadaet ego interfejs, a fajl .c opredelyaet ego realizaciyu. Tablica imen ne zavisit ni ot kakih chastej kal'kulyatora, krome chasti obrabotki oshibok. Teper' etot fakt mozhno vyrazit' yavno: // table.h: opisanie tablicy imen struct name { char* string; name* next; double value; }; extern name* look(const char* p, int ins = 0); inline name* insert(const char* s) { return look(s,1); } // table.h: opredelenie tablicy imen #include "error.h" #include <string.h> #include "table.h" const int TBLSZ = 23; name* table[TBLSZ]; name* look(const char* p, int ins) { /* ... */ } Zamet'te, chto teper' opisaniya strokovyh funkcij berutsya iz vklyuchaemogo fajla <string.h>. Tem samym udalen eshche odin istochnik oshibok. // lex.h: opisaniya dlya vvoda i leksicheskogo analiza enum token_value { NAME, NUMBER, END, PLUS='+', MINUS='-', MUL='*', PRINT=';', ASSIGN='=', LP='(', RP= ')' }; extern token_value curr_tok; extern double number_value; extern char name_string[256]; extern token_value get_token(); Interfejs s leksicheskim analizatorom dostatochno zaputannyj. Poskol'ku nedostatochno sootvetstvuyushchih tipov dlya leksem, pol'zovatelyu funkcii get_token() predostavlyayutsya te zhe bufery number_value i name_string, s kotorymi rabotaet sam leksicheskij analizator. // lex.c: opredeleniya dlya vvoda i leksicheskogo analiza #include <iostream.h> #include <ctype.h> #include "error.h" #include "lex.h" token_value curr_tok; double number_value; char name_string[256]; token_value get_token() { /* ... */ } Interfejs s sintaksicheskim analizatorom opredelen chetko: // syn.h: opisaniya dlya sintaksicheskogo analiza i vychislenij extern double expr(); extern double term(); extern double prim(); // syn.c: opredeleniya dlya sintaksicheskogo analiza i vychislenij #include "error.h" #include "lex.h" #include "syn.h" double prim() { /* ... */ } double term() { /* ... */ } double expr() { /* ... */ } Kak obychno, opredelenie osnovnoj programmy trivial'no: // main.c: osnovnaya programma #include <iostream.h> #include "error.h" #include "lex.h" #include "syn.h" #include "table.h" int main(int argc, char* argv[]) { /* ... */ } Kakoe chislo zagolovochnyh fajlov sleduet ispol'zovat' dlya dannoj programmy zavisit ot mnogih faktorov. Bol'shinstvo ih opredelyaetsya sposobom obrabotki fajlov imenno v vashej sisteme, a ne sobstvenno v S++. Naprimer, esli vash redaktor ne mozhet rabotat' odnovremenno s neskol'kimi fajlami, dialogovaya obrabotka neskol'kih zagolovochnyh fajlov zatrudnyaetsya. Drugoj primer: mozhet okazat'sya, chto otkrytie i chtenie 10 fajlov po 50 strok kazhdyj zanimaet sushchestvenno bol'she vremeni, chem otkrytie i chtenie odnogo fajla iz 500 strok. V rezul'tate pridetsya horoshen'ko podumat', prezhde chem razbivat' nebol'shuyu programmu, ispol'zuya mnozhestvennye zagolovochnye fajly. Predosterezhenie: obychno mozhno upravit'sya s mnozhestvom, sostoyashchim primerno iz 10 zagolovochnyh fajlov (plyus standartnye zagolovochnye fajly). Esli zhe vy budete razbivat' programmu na minimal'nye logicheskie edinicy s zagolovochnymi fajlami (naprimer, sozdavaya dlya kazhdoj struktury svoj zagolovochnyj fajl), to mozhete ochen' legko poluchit' neupravlyaemoe mnozhestvo iz soten zagolovochnyh fajlov. 4.4 Svyazyvanie s programmami na drugih yazykah Programmy na S++ chasto soderzhat chasti, napisannye na drugih yazykah, i naoborot, chasto fragment na S++ ispol'zuetsya v programmah, napisannyh na drugih yazykah. Sobrat' v odnu programmu fragmenty, napisannye na raznyh yazykah, ili, napisannye na odnom yazyke, no v sistemah programmirovaniya s raznymi soglasheniyami o svyazyvanii, dostatochno trudno. Naprimer, raznye yazyki ili raznye realizacii odnogo yazyka mogut razlichat'sya ispol'zovaniem registrov pri peredache parametrov, poryadkom razmeshcheniya parametrov v steke, upakovkoj takih vstroennyh tipov, kak celye ili stroki, formatom imen funkcij, kotorye translyator peredaet redaktoru svyazej, ob容mom kontrolya tipov, kotoryj trebuetsya ot redaktora svyazej. CHtoby uprostit' zadachu, mozhno v opisanii vneshnih ukazat' uslovie svyazyvaniya. Naprimer, sleduyushchee opisanie ob座avlyaet strcpy vneshnej funkciej i ukazyvaet, chto ona dolzhna svyazyvat'sya soglasno poryadku svyazyvaniya v S: extern "C" char* strcpy(char*, const char*); Rezul'tat etogo opisaniya otlichaetsya ot rezul'tata obychnogo opisaniya extern char* strcpy(char*, const char*); tol'ko poryadkom svyazyvaniya dlya vyzyvayushchih strcpy() funkcij. Sama semantika vyzova i, v chastnosti, kontrol' fakticheskih parametrov budut odinakovy v oboih sluchayah. Opisanie extern "C" imeet smysl ispol'zovat' eshche i potomu, chto yazyki S i S++, kak i ih realizacii, blizki drug drugu. Otmetim, chto v opisanii extern "C" upominanie S otnositsya k poryadku svyazyvaniya, a ne k yazyku, i chasto takoe opisanie ispol'zuyut dlya svyazi s Fortranom ili assemblerom. |ti yazyki v opredelennoj stepeni podchinyayutsya poryadku svyazyvaniya dlya S. Utomitel'no dobavlyat' "C" ko mnogim opisaniyam vneshnih, i est' vozmozhnost' ukazat' takuyu specifikaciyu srazu dlya gruppy opisanij. Naprimer: extern "C" { char* strcpy(char*, const char); int strcmp(const char*, const char*) int strlen(const char*) // ... } V takuyu konstrukciyu mozhno vklyuchit' ves' zagolovochnyj fajl S, chtoby ukazat', chto on podchinyaetsya svyazyvaniyu dlya S++, naprimer: extern "C" { #include <string.h> } Obychno s pomoshch'yu takogo priema iz standartnogo zagolovochnogo fajla dlya S poluchayut takoj fajl dlya S++. Vozmozhno inoe reshenie s pomoshch'yu uslovnoj translyacii: #ifdef __cplusplus extern "C" { #endif char* strcpy(char*, const char*); int strcmp(const char*, const char*); int strlen(const char*); // ... #ifdef __cplusplus } #endif Predopredelennoe makroopredelenie __cplusplus nuzhno, chtoby obojti konstrukciyu extern "C" { ...}, esli zagolovochnyj fajl ispol'zuetsya dlya S. Poskol'ku konstrukciya extern "C" { ... } vliyaet tol'ko na poryadok svyazyvaniya, v nej mozhet soderzhat'sya lyuboe opisanie, naprimer: extern "C" { // proizvol'nye opisaniya // naprimer: static int st; int glob; } Nikak ne menyaetsya klass pamyati i oblast' vidimosti opisyvaemyh ob容ktov, poetomu po-prezhnemu st podchinyaetsya vnutrennemu svyazyvaniyu, a glob ostaetsya global'noj peremennoj. Ukazhem eshche raz, chto opisanie extern "C" vliyaet tol'ko na poryadok svyazyvaniya i ne vliyaet na poryadok vyzova funkcii. V chastnosti, funkciya, opisannaya kak extern "C", vse ravno podchinyaetsya pravilam kontrolya tipov i preobrazovaniya fakticheskih parametrov, kotorye v C++ strozhe, chem v S. Naprimer: extern "C" int f(); int g() { return f(1); // oshibka: parametrov byt' ne dolzhno } 4.5 Kak sozdat' biblioteku Rasprostraneny takie oboroty (i v etoj knige tozhe): "pomestit' v biblioteku", "poiskat' v takoj-to biblioteke". CHto oni oznachayut dlya programm na S++? K sozhaleniyu, otvet zavisit ot ispol'zuemoj sistemy. V etom razdele govoritsya o tom, kak sozdat' i ispol'zovat' biblioteku dlya desyatoj versii sistemy YUNIKS. Drugie sistemy dolzhny predostavlyat' pohozhie vozmozhnosti. Biblioteka sostoit iz fajlov .o, kotorye poluchayutsya v rezul'tate translyacii fajlov .c. Obychno sushchestvuet odin ili neskol'ko fajlov .h, v kotoryh soderzhatsya neobhodimye dlya vyzova fajlov .o opisaniya. Rassmotrim v kachestve primera, kak dlya chetko ne ogovorennogo mnozhestva pol'zovatelej mozhno dostatochno udobno opredelit' nekotoroe mnozhestvo standartnyh matematicheskih funkcij. Zagolovochnyj fajl mozhet imet' takoj vid: extern "C" { // standartnye matematicheskie funkcii // kak pravilo napisany na S double sqrt(double); // podmnozhestvo <math.h> double sin(double); double cos(double); double exp(double); double log(double); // ... } Opredeleniya etih funkcij budut nahodit'sya v fajlah sqrt.c, sin.c, cos.c, exp.c i log.c, sootvetstvenno. Biblioteku s imenem math.a mozhno sozdat' s pomoshch'yu takih komand: $ CC -c sqrt.c sin.c cos.c exp.c log.c $ ar cr math.a sqrt.o sin.o cos.o exp.o log.o $ ranlib math.a Zdes' simvol $ yavlyaetsya priglasheniem sistemy. Vnachale transliruyutsya ishodnye teksty, i poluchayutsya moduli s temi zhe imenami. Komanda ar (arhivator) sozdaet arhiv pod imenem math.a. Nakonec, dlya bystrogo dostupa k funkciyam arhiv indeksiruetsya. Esli v vashej sisteme net komandy ranlib (vozmozhno ona i ne nuzhna), to, po krajnej mere, mozhno najti v spravochnom rukovodstve ssylku na imya ar. CHtoby ispol'zovat' biblioteku v svoej programme, nado zadat' rezhim translyacii sleduyushchim obrazom: $ CC myprog.c math.a Vstaet vopros: chto daet nam biblioteka math.a? Ved' mozhno bylo by neposredstvenno ispol'zovat' fajly .o, naprimer tak: $ CC myprog.c sqrt.o sin.o cos.o exp.o log.o Delo v tom, chto vo mnogih sluchayah trudno pravil'no ukazat', kakie fajly .o dejstvitel'no nuzhny. V privedennoj vyshe komande ispol'zovalis' vse iz nih. Esli zhe v myprog vyzyvayutsya tol'ko sqrt() i cos(), togda, vidimo, dostatochno zadat' takuyu komandu: $ CC myprog.c sqrt.o cos.o No eto budet neverno, t.k. funkciya cos() vyzyvaet sin(). Redaktor svyazej, kotoryj vyzyvaetsya komandoj CC dlya obrabotki fajlov .a (v nashem sluchae dlya fajla math.a), umeet iz mnozhestva fajlov, obrazuyushchih biblioteku, izvlekat' tol'ko nuzhnye fajly .o. Inymi slovami, svyazyvanie s bibliotekoj pozvolyaet vklyuchat' v programmy mnogo opredelenij odnogo imeni (v tom chisle opredeleniya funkcij i peremennyh, ispol'zuemyh tol'ko vnutrennimi funkciyami, o kotoryh pol'zovatel' nikogda ne uznaet). V to zhe vremya v rezul'tiruyushchuyu programmu vojdet tol'ko minimal'no neobhodimoe chislo opredelenij. 4.6 Funkcii Samyj rasprostranennyj sposob zadaniya v S++ kakih-to dejstvij - eto vyzov funkcii, kotoraya vypolnyaet takie dejstviya. Opredelenie funkcii est' opisanie togo, kak ih vypolnit'. Neopisannye funkcii vyzyvat' nel'zya. 4.6.1 Opisaniya funkcij Opisanie funkcii soderzhit ee imya, tip vozvrashchaemogo znacheniya (esli ono est') i chislo i tipy parametrov, kotorye dolzhny zadavat'sya pri vyzove funkcii. Naprimer: extern double sqrt(double); extern elem* next_elem(); extern char* strcpy(char* to, const char* from); extern void exit(int); Semantika peredachi parametrov tozhdestvenna semantike inicializacii: proveryayutsya tipy fakticheskih parametrov i, esli nuzhno, proishodyat neyavnye preobrazovaniya tipov. Tak, esli uchest' privedennye opisaniya, to v sleduyushchem opredelenii: double sr2 = sqrt(2); soderzhitsya pravil'nyj vyzov funkcii sqrt() so znacheniem s plavayushchej tochkoj 2.0. Kontrol' i preobrazovanie tipa fakticheskogo parametra imeet v S++ ogromnoe znachenie. V opisanii funkcii mozhno ukazyvat' imena parametrov. |to oblegchaet chtenie programmy, no translyator eti imena prosto ignoriruet. 4.6.2 Opredeleniya funkcij Kazhdaya vyzyvaemaya v programme funkciya dolzhna byt' gde-to v nej opredelena, prichem tol'ko odin raz. Opredelenie funkcii - eto ee opisanie, v kotorom soderzhitsya telo funkcii. Naprimer: extern void swap(int*, int*); // opisanie void swap(int* p, int* q) // opredelenie { int t = *p; *p = *q; *q = *t; } Ne tak redki sluchai, kogda v opredelenii funkcii ne ispol'zuyutsya nekotorye parametry: void search(table* t, const char* key, const char*) { // tretij parametr ne ispol'zuetsya