funkcii sravneniya strok strcmp(). Esli imya najdeno, to vozvrashchaetsya ukazatel' na soderzhashchuyu ego zapis', a v protivnom sluchae zavoditsya novaya zapis' s etim imenem. Dobavlenie novogo imeni oznachaet sozdanie novogo ob容kta name v svobodnoj pamyati s pomoshch'yu operacii new (sm. $$3.2.6), ego inicializaciyu i vklyuchenie v spisok imen. Poslednee vypolnyaetsya kak zanesenie novogo imeni v nachalo spiska, poskol'ku eto mozhno sdelat' dazhe bez proverki togo, est' li spisok voobshche. Simvol'naya stroka imeni takzhe razmeshchaetsya v svobodnoj pamyati. Funkciya strlen() ukazyvaet, skol'ko pamyati nuzhno dlya stroki, operaciya new otvodit nuzhnuyu pamyat', a funkciya strcpy() kopiruet v nee stroku. Vse strokovye funkcii opisany v <string.h>: extern int strlen(const char*); extern int strcmp(const char*, const char*); extern char* strcpy(char*, const char*); 3.1.4 Obrabotka oshibok Poskol'ku programma dostatochno prosta, ne nado osobo bespokoit'sya ob obrabotke oshibok. Funkciya error prosto podschityvaet chislo oshibok, vydaet soobshchenie o nih i vozvrashchaet upravlenie obratno: int no_of_errors; double error(const char* s) { cerr << "error: " << s << "\n"; no_of_errors++; return 1; } Nebuferizovannyj vyhodnoj potok cerr obychno ispol'zuetsya imenno dlya vydachi soobshchenij ob oshibkah. Upravlenie vozvrashchaetsya iz error() potomu, chto oshibki, kak pravilo, vstrechayutsya posredi vychisleniya vyrazheniya. Znachit nado libo polnost'yu prekrashchat' vychisleniya, libo vozvrashchat' znachenie, kotoroe ne dolzhno vyzvat' posleduyushchih oshibok. Dlya prostogo kal'kulyatora bol'she podhodit poslednee. Esli by funkciya get_token() otslezhivala nomera strok, to funkciya error() mogla by ukazyvat' pol'zovatelyu priblizitel'noe mesto oshibki. |to bylo by polezno pri neinteraktivnoj rabote s kal'kulyatorom. CHasto posle poyavleniya oshibki programma dolzhna zavershit'sya, poskol'ku ne udalos' predlozhit' razumnyj variant ee dal'nejshego vypolneniya. Zavershit' ee mozhno s pomoshch'yu vyzova funkcii exit(), kotoraya zakanchivaet rabotu s vyhodnymi potokami ($$10.5.1) i zavershaet programmu, vozvrashchaya svoj parametr v kachestve ee rezul'tata. Bolee radikal'nyj sposob zaversheniya programmy - eto vyzov funkcii abort(), kotoraya preryvaet vypolnenie programmy nemedlenno ili srazu zhe posle sohraneniya informacii dlya otladchika (sbros operativnoj pamyati). Podrobnosti vy mozhete najti v svoem spravochnom rukovodstve. Bolee tonkie priemy obrabotki oshibok mozhno predlozhit', esli orientirovat'sya na osobye situacii (sm.$$9), no predlozhennoe reshenie vpolne priemlemo dlya igrushechnogo kal'kulyatora v 200 strok. 3.1.5 Drajver Kogda vse chasti programmy opredeleny, nuzhen tol'ko drajver, chtoby inicializirovat' i zapustit' process. V nashem primere s etim spravitsya funkciya main(): int main() { // vstavit' predopredelennye imena: insert("pi")->value = 3.1415926535897932385; insert("e")->value = 2.7182818284590452354; while (cin) { get_token(); if (curr_tok == END) break; if (curr_tok == PRINT) continue; cout << expr() << '\n'; } return no_of_errors; } Prinyato, chto funkciya main() vozvrashchaet nul', esli programma zavershaetsya normal'no, i nenulevoe znachenie, esli proishodit inache. Nenulevoe znachenie vozvrashchaetsya kak chislo oshibok. Okazyvaetsya, vsya inicializaciya svoditsya k zaneseniyu predopredelennyh imen v tablicu. V cikle main chitayutsya vyrazheniya i vydayutsya rezul'taty. |to delaet odna stroka: cout << expr() << '\n'; Proverka cin pri kazhdom prohode cikla garantiruet zavershenie programmy, dazhe esli chto-to sluchitsya s vhodnym potokom, a proverka na leksemu END nuzhna dlya normal'nogo zaversheniya cikla, kogda funkciya get_token() obnaruzhit konec fajla. Operator break sluzhit dlya vyhoda iz blizhajshego ob容mlyushchego operatora switch ili cikla (t.e. operatora for, while ili do). Proverka na leksemu PRINT (t.e. na '\n' i ';') snimaet s funkcii expr() obyazannost' obrabatyvat' pustye vyrazheniya. Operator continue ekvivalenten perehodu na konec cikla, poetomu v nashem sluchae fragment: while (cin) { // ... if (curr_tok == PRINT) continue; cout << expr() << "\n"; } ekvivalenten fragmentu: while (cin) { // ... if (curr_tok == PRINT) goto end_of_loop; cout << expr() << "\n"; end_of_loop: ; } Bolee podrobno cikly opisyvayutsya v $$R.6 3.1.6 Parametry komandnoj stroki Kogda programma kal'kulyatora uzhe byla napisana i otlazhena, vyyasnilos', chto neudobno vnachale zapuskat' ee, vvodit' vyrazhenie, a zatem vyhodit' iz kal'kulyatora. Tem bolee, chto obychno nuzhno prosto vychislit' odno vyrazhenie. Esli eto vyrazhenie zadat' kak parametr komandnoj stroki zapuska kal'kulyatora, to mozhno sekonomit' neskol'ko nazhatij klavishi. Kak uzhe bylo skazano, vypolnenie programmy nachinaetsya vyzovom main(). Pri etom vyzove main() poluchaet dva parametra: chislo parametrov (obychno nazyvaemyj argc) i massiv strok parametrov (obychno nazyvaemyj argv). Parametry - eto simvol'nye stroki, poetomu argv imeet tip char*[argc+1]. Imya programmy (v tom vide, kak ono bylo zadano v komandnoj stroke) peredaetsya v argv[0], poetomu argc vsegda ne men'she edinicy. Naprimer, dlya komandnoj stroki dc 150/1.1934 parametry imeyut znacheniya: argc 2 argv[0] "dc" argv[1] "150/1.1934" argv[2] 0 Dobrat'sya do parametrov komandnoj stroki prosto; problema v tom, kak ispol'zovat' ih tak, chtoby ne menyat' samu programmu. V dannom sluchae eto okazyvaetsya sovsem prosto, poskol'ku vhodnoj potok mozhet byt' nastroen na simvol'nuyu stroku vmesto fajla ($$10.5.2). Naprimer, mozhno opredelit' cin tak, chtoby simvoly chitalis' iz stroki, a ne iz standartnogo vhodnogo potoka: int main(int argc, char* argv[]) { switch(argc) { case 1: // schityvat' iz standartnogo vhodnogo potoka break; case 2: // schityvat' iz stroki parametrov cin = *new istream(argv[1],strlen(argv[1])); break; default: error("slishkom mnogo parametrov"); return 1; } // dal'she prezhnij variant main } Pri etom istrstream - eto funkciya istream, kotoraya schityvaet simvoly iz stroki, yavlyayushchejsya ee pervym parametrom. CHtoby ispol'zovat' istrstream nuzhno vklyuchit' v programmu fajl <strstream.h>, a ne obychnyj <iostream.h>. V ostal'nom zhe programma ostalas' bez izmenenij, krome dobavleniya parametrov v funkciyu main() i ispol'zovaniya ih v operatore switch. Mozhno legko izmenit' funkciyu main() tak, chtoby ona mogla prinimat' neskol'ko parametrov iz komandnoj stroki. Odnako eto ne slishkom nuzhno, tem bolee, chto mozhno neskol'kih vyrazhenij peredat' kak odin parametr: dc "rate=1.1934;150/rate;19.75/rate;217/rate" Kavychki neobhodimy potomu, chto simvol ';' sluzhit v sisteme UNIX razdelitelem komand. V drugih sistemah mogut byt' svoi soglasheniya o parametrah komandnoj stroki. 3.2 Svodka operacij Polnoe i podrobnoe opisanie operacij yazyka S++ dano v $$R.7. Sovetuem prochitat' etot razdel. Zdes' zhe privoditsya kratkaya svodka operacij i neskol'ko primerov. Kazhdaya operaciya soprovozhdaetsya odnim ili neskol'kimi harakternymi dlya nee imenami i primerom ee ispol'zovaniya. V etih primerah class_name oboznachaet imya klassa, member - imya chlena, object - vyrazhenie, zadayushchee ob容kt klassa, pointer - vyrazhenie, zadayushchee ukazatel', expr - prosto vyrazhenie, a lvalue (adres) - vyrazhenie, oboznachayushchee ne yavlyayushchijsya konstantoj ob容kt. Oboznachenie (type) zadaet imya tipa v obshchem vide (s vozmozhnym dobavleniem *, () i t.d.). Esli ono ukazano bez skobok, sushchestvuyut ogranicheniya. Poryadok primeneniya unarnyh operacij i operacij prisvaivaniya "sprava nalevo", a vseh ostal'nyh operacij - "sleva napravo". To est', a=b=c oznachaet a=(b=c), a+b+c oznachaet (a+b)+c, i *p++ oznachaet *(p++), a ne (*p)++. ____________________________________________________________ Operacii S++ ============================================================ :: Razreshenie oblasti vidimosti class_name :: member :: Global'noe :: name ____________________________________________________________ . Vybor chlena object . member -> Vybor chlena pointer -> member [] Indeksirovanie pointer [ expr ] () Vyzov funkcii expr ( expr_list ) () Strukturnoe znachenie type ( expr_list ) sizeof Razmer ob容kta sizeof expr sizeof Razmer tipa sizeof ( type ) ____________________________________________________________ ++ Postfiksnyj inkrement lvalue ++ ++ Prefiksnyj inkrement ++ lvalue -- Postfiksnyj dekrement lvalue -- -- Prefiksnyj dekrement -- lvalue ~ Dopolnenie ~ expr ! Logicheskoe NE ! expr - Unarnyj minus - expr + Unarnyj plyus + expr & Vzyatie adresa & lvalue * Kosvennost' * expr new Sozdanie (razmeshchenie) new type delete Unichtozhenie (osvobozhdenie) delete pointer delete[] Unichtozhenie massiva delete[] pointer () Privedenie(preobrazovanie)tipa ( type ) expr ____________________________________________________________ . * Vybor chlena kosvennyj object . pointer-to-member ->* Vybor chlena kosvennyj pointer -> pointer-to-member ____________________________________________________________ * Umnozhenie expr * expr / Delenie expr / expr % Ostatok ot deleniya expr % expr ____________________________________________________________ + Slozhenie (plyus) expr + expr - Vychitanie (minus) expr - expr ____________________________________________________________ Vse operacii tablicy, nahodyashchiesya mezhdu dvumya blizhajshimi drug k drugu gorizontal'nymi chertami, imeyut odinakovyj prioritet. Prioritet operacij umen'shaetsya pri dvizhenii "sverhu vniz". Naprimer, a+b*c oznachaet a+(b*c), tak kak * imeet prioritet vyshe, chem +; a vyrazhenie a+b-c oznachaet (a+b)-c, poskol'ku + i - imeyut odinakovyj prioritet, i operacii + i - primenyayutsya "sleva napravo". | ____________________________________________________________ Operacii S++ (prodolzhenie) ============================================================ << Sdvig vlevo expr << expr >> Sdvig vpravo expr >> expr ____________________________________________________________ < Men'she expr < expr <= Men'she ili ravno expr <= expr > Bol'she expr > expr >= Bol'she ili ravno expr >= expr ____________________________________________________________ == Ravno expr == expr != Ne ravno expr != expr ____________________________________________________________ & Porazryadnoe I expr & expr ____________________________________________________________ ^ Porazryadnoe isklyuchayushchee ILI expr ^ expr ____________________________________________________________ | Porazryadnoe vklyuchayushchee ILI expr | expr ____________________________________________________________ && Logicheskoe I expr && expr ____________________________________________________________ || Logicheskoe ILI expr || expr ____________________________________________________________ ? : Operaciya usloviya expr? expr : expr ____________________________________________________________ = Prostoe prisvaivanie lvalue = expr *= Prisvaivanie s umnozheniem lvalue *= expr /= Prisvaivanie s deleniem lvalue /= expr %= Prisvaivanie s vzyatiem lvalue %= expr ostatka ot deleniya += Prisvaivanie so slozheniem lvalue += expr -= Prisvaivanie s vychitaniem lvalue -= expr <<= Prisvaivanie so sdvigom vlevo lvalue <<= expr >>= Prisvaivanie so sdvigom vpravo lvalue >>= expr &= Prisvaivanie s porazryadnym I lvalue &= expr |= Prisvaivanie s porazryadnym lvalue |= expr vklyuchayushchim ILI ^= Prisvaivanie s porazryadnym lvalue ^= expr isklyuchayushchim ILI ____________________________________________________________ Zapyataya (posledovatel'nost') expr , expr ____________________________________________________________ 3.2.1 Skobki Sintaksis yazyka S++ peregruzhen skobkami, i raznoobrazie ih primenenij sposobno sbit' s tolku. Oni vydelyayut fakticheskie parametry pri vyzove funkcij, imena tipov, zadayushchih funkcii, a takzhe sluzhat dlya razresheniya konfliktov mezhdu operaciyami s odinakovym prioritetom. K schast'yu, poslednee vstrechaetsya ne slishkom chasto, poskol'ku prioritety i poryadok primeneniya operacij opredeleny tak, chtoby vyrazheniya vychislyalis' "estestvennym obrazom" (t.e. naibolee rasprostranennym obrazom). Naprimer, vyrazhenie if (i<=0 || max<i) // ... oznachaet sleduyushchee: "Esli i men'she ili ravno nulyu, ili esli max men'she i". To est', ono ekvivalentno if ( (i<=0) || (max<i) ) // ... no ne ekvivalentno dopustimomu, hotya i bessmyslennomu vyrazheniyu if (i <= (0||max) < i) // ... Tem ne menee, esli programmist ne uveren v ukazannyh pravilah, sleduet ispol'zovat' skobki, prichem nekotorye predpochitayut dlya nadezhnosti pisat' bolee dlinnye i menee elegantnye vyrazheniya, kak: if ( (i<=0) || (max<i) ) // ... Pri uslozhnenii podvyrazhenij skobki ispol'zuyutsya chashche. Ne nado, odnako, zabyvat', chto slozhnye vyrazheniya yavlyayutsya istochnikom oshibok. Poetomu, esli u vas poyavitsya oshchushchenie, chto v etom vyrazhenii nuzhny skobki, luchshe razbejte ego na chasti i vvedite dopolnitel'nuyu peremennuyu. Byvayut sluchai, kogda prioritety operacij ne privodyat k "estestvennomu" poryadku vychislenij. Naprimer, v vyrazhenii if (i&mask == 0) // lovushka! & primenyaetsya posle == ne proishodit maskirovanie i (i&mask), a zatem proverka rezul'tata na 0. Poskol'ku u == prioritet vyshe, chem u &, eto vyrazhenie ekvivalentno i&(mask==0). V etom sluchae skobki igrayut vazhnuyu rol': if ((i&mask) == 0) // ... Imeet smysl privesti eshche odno vyrazhenie, kotoroe vychislyaetsya sovsem ne tak, kak mog by ozhidat' neiskushennyj pol'zovatel': if (0 <= a <= 99) // ... Ono dopustimo, no interpretiruetsya kak (0<=a)<=99, i rezul'tat pervogo sravneniya raven ili 0, ili 1, no ne znacheniyu a (esli, konechno, a ne est' 1). Proverit', popadaet li a v diapazon 0...99, mozhno tak: if (0<=a && a<=99) // ... Sredi novichkov rasprostranena oshibka, kogda v uslovii vmesto == (ravno) ispol'zuyut = (prisvoit'): if (a = 7) // oshibka: prisvaivanie konstanty v uslovii // ... Ona vpolne ob座asnima, poskol'ku v bol'shinstve yazykov "=" oznachaet "ravno". Dlya translyatora ne sostavit truda soobshchat' ob oshibkah podobnogo roda. 3.2.2 Poryadok vychislenij Poryadok vychisleniya podvyrazhenij, vhodyashchih v vyrazhenie, ne vsegda opredelen. Naprimer: int i = 1; v[i] = i++; Zdes' vyrazhenie mozhet vychislyat'sya ili kak v[1]=1, ili kak v[2]=1. Esli net ogranichenij na poryadok vychisleniya podvyrazhenij, to translyator poluchaet vozmozhnost' sozdavat' bolee optimal'nyj kod. Translyatoru sledovalo by preduprezhdat' o dvusmyslennyh vyrazheniyah, no k sozhaleniyu bol'shinstvo iz nih ne delaet etogo. Dlya operacij && || , garantiruetsya, chto ih levyj operand vychislyaetsya ran'she pravogo operanda. Naprimer, v vyrazhenii b=(a=2,a+1) b prisvoitsya znachenie 3. Primer operacii || byl dan v $$3.2.1, a primer operacii && est' v $$3.3.1. Otmetim, chto operaciya zapyataya otlichaetsya po smyslu ot toj zapyatoj, kotoraya ispol'zuetsya dlya razdeleniya parametrov pri vyzove funkcij. Pust' est' vyrazheniya: f1(v[i],i++); // dva parametra f2( (v[i],i++) ) // odin parametr Vyzov funkcii f1 proishodit s dvumya parametrami: v[i] i i++, no poryadok vychisleniya vyrazhenij parametrov neopredelen. Zavisimost' vychisleniya znachenij fakticheskih parametrov ot poryadka vychislenij - daleko ne luchshij stil' programmirovaniya. K tomu zhe programma stanovitsya neperenosimoj. Vyzov f2 proishodit s odnim parametrom, yavlyayushchimsya vyrazheniem, soderzhashchim operaciyu zapyataya: (v[i], i++). Ono ekvivalentno i++. Skobki mogut prinuditel'no zadat' poryadok vychisleniya. Naprimer, a*(b/c) mozhet vychislyat'sya kak (a*b)/c (esli tol'ko pol'zovatel' vidit v etom kakoe-to razlichie). Zametim, chto dlya znachenij s plavayushchej tochkoj rezul'taty vychisleniya vyrazhenij a*(b/c) i (a*b)/ mogut razlichat'sya ves'ma znachitel'no. 3.2.3 Inkrement i dekrement Operaciya ++ yavno zadaet inkrement v otlichie ot neyavnogo ego zadaniya s pomoshch'yu slozheniya i prisvaivaniya. Po opredeleniyu ++lvalue oznachaet lvalue+=1, chto, v svoyu ochered' oznachaet lvalue=lvalue+1 pri uslovii, chto soderzhimoe lvalue ne vyzyvaet pobochnyh effektov. Vyrazhenie, oboznachayushchee operand inkrementa, vychislyaetsya tol'ko odin raz. Analogichno oboznachaetsya operaciya dekrementa (--). Operacii ++ i -- mogut ispol'zovat'sya kak prefiksnye i postfiksnye operacii. Znacheniem ++x yavlyaetsya novoe (t. e. uvelichennoe na 1) znachenie x. Naprimer, y=++x ekvivalentno y=(x+=1). Naprotiv, znachenie x++ ravno prezhnemu znacheniyu x. Naprimer, y=x++ ekvivalentno y=(t=x,x+=1,t), gde t - peremennaya togo zhe tipa, chto i x. Napomnim, chto operacii inkrementa i dekrementa ukazatelya ekvivalentny slozheniyu 1 s ukazatelem ili vychitaniyu 1 iz ukazatelya, prichem vychislenie proishodit v elementah massiva, na kotoryj nastroen ukazatel'. Tak, rezul'tatom p++ budet ukazatel' na sleduyushchij element. Dlya ukazatelya p tipa T* sleduyushchee sootnoshenie verno po opredeleniyu: long(p+1) == long(p) + sizeof(T); CHashche vsego operacii inkrementa i dekrementa ispol'zuyutsya dlya izmeneniya peremennyh v cikle. Naprimer, kopirovanie stroki, okanchivayushchejsya nulevym simvolom, zadaetsya sleduyushchim obrazom: inline void cpy(char* p, const char* q) { while (*p++ = *q++) ; } YAzyk S++ (podobno S) imeet kak storonnikov, tak i protivnikov imenno iz-za takogo szhatogo, ispol'zuyushchego slozhnye vyrazheniya stilya programmirovaniya. Operator while (*p++ = *q++) ; veroyatnee vsego, pokazhetsya nevrazumitel'nym dlya neznakomyh s S. Imeet smysl povnimatel'nee posmotret' na takie konstrukcii, poskol'ku dlya C i C++ oni ne yavlyaetsya redkost'yu. Snachala rassmotrim bolee tradicionnyj sposob kopirovaniya massiva simvolov: int length = strlen(q) for (int i = 0; i<=length; i++) p[i] = q[i]; |to neeffektivnoe reshenie: stroka okanchivaetsya nulem; edinstvennyj sposob najti ee dlinu - eto prochitat' ee vsyu do nulevogo simvola; v rezul'tate stroka chitaetsya i dlya ustanovleniya ee dliny, i dlya kopirovaniya, to est' dvazhdy. Poetomu poprobuem takoj variant: for (int i = 0; q[i] !=0 ; i++) p[i] = q[i]; p[i] = 0; // zapis' nulevogo simvola Poskol'ku p i q - ukazateli, mozhno obojtis' bez peremennoj i, ispol'zuemoj dlya indeksacii: while (*q !=0) { *p = *q; p++; // ukazatel' na sleduyushchij simvol q++; // ukazatel' na sleduyushchij simvol } *p = 0; // zapis' nulevogo simvola Poskol'ku operaciya postfiksnogo inkrementa pozvolyaet snachala ispol'zovat' znachenie, a zatem uzhe uvelichit' ego, mozhno perepisat' cikl tak: while (*q != 0) { *p++ = *q++; } *p = 0; // zapis' nulevogo simvola Otmetim, chto rezul'tat vyrazheniya *p++ = *q++ raven *q. Sledovatel'no, mozhno perepisat' nash primer i tak: while ((*p++ = *q++) != 0) { } V etom variante uchityvaetsya, chto *q ravno nulyu tol'ko togda, kogda *q uzhe skopirovano v *p, poetomu mozhno isklyuchit' zavershayushchee prisvaivanie nulevogo simvola. Nakonec, mozhno eshche bolee sokratit' zapis' etogo primera, esli uchest', chto pustoj blok ne nuzhen, a operaciya "!= 0" izbytochna, t.k. rezul'tat uslovnogo vyrazheniya i tak vsegda sravnivaetsya s nulem. V rezul'tate my prihodim k pervonachal'nomu variantu, kotoryj vyzyval nedoumenie: while (*p++ = *q++) ; Neuzheli etot variant trudnee ponyat', chem privedennye vyshe? Tol'ko neopytnym programmistam na S++ ili S! Budet li poslednij variant naibolee effektivnym po zatratam vremeni i pamyati? Esli ne schitat' pervogo varianta s funkciej strlen(), to eto neochevidno. Kakoj iz variantov okazhetsya effektivnee, opredelyaetsya kak specifikoj sistemy komand, tak i vozmozhnostyami translyatora. Naibolee effektivnyj algoritm kopirovaniya dlya vashej mashiny mozhno najti v standartnoj funkcii kopirovaniya strok iz fajla <string.h>: int strcpy(char*, const char*); 3.2.4 Porazryadnye logicheskie operacii Porazryadnye logicheskie operacii & | ^ ~ >> << primenyayutsya k celym, to est' k ob容ktam tipa char, short, int, long i k ih bezznakovym analogam. Rezul'tat operacii takzhe budet celym. CHashche vsego porazryadnye logicheskie operacii ispol'zuyutsya dlya raboty s nebol'shim po velichine mnozhestvom dannyh (massivom razryadov). V etom sluchae kazhdyj razryad bezznakovogo celogo predstavlyaet odin element mnozhestva, i chislo elementov opredelyaetsya kolichestvom razryadov. Binarnaya operaciya & interpretiruetsya kak peresechenie mnozhestv, operaciya | kak ob容dinenie, a operaciya ^ kak raznost' mnozhestv. S pomoshch'yu perechisleniya mozhno zadat' imena elementam mnozhestva. Nizhe priveden primer, zaimstvovannyj iz <iostream.h>: class ios { public: enum io_state { goodbit=0, eofbit=1, failbit=2, badbit=4 }; // ... }; Sostoyanie potoka mozhno ustanovit' sleduyushchim prisvaivaniem: cout.state = ios::goodbit; Utochnenie imenem ios neobhodimo, potomu chto opredelenie io_state nahoditsya v klasse ios, a takzhe chtoby ne vozniklo kollizij, esli pol'zovatel' zavedet svoi imena napodobie goodbit. Proverku na korrektnost' potoka i uspeshnoe okonchanie operacii mozhno zadat' tak: if (cout.state&(ios::badbit|ios::failbit)) // oshibka v potoke Eshche odni skobki neobhodimy potomu, chto operaciya & imeet bolee vysokij prioritet, chem operaciya "|". Funkciya, obnaruzhivshaya konec vhodnogo potoka, mozhet soobshchat' ob etom tak: cin.state |= ios::eofbit; Operaciya |= ispol'zuetsya potomu, chto v potoke uzhe mogla byt' oshibka (t.e. state==ios::badbit), i prisvaivanie cin.state =ios::eofbit; moglo by zateret' ee priznak. Ustanovit' otlichiya v sostoyanii dvuh potokov mozhno sleduyushchim sposobom: ios::io_state diff = cin.state^cout.state; Dlya takih tipov, kak io_state, nahozhdenie razlichij ne slishkom poleznaya operaciya, no dlya drugih shodnyh tipov ona mozhet okazat'sya ves'ma poleznoj. Naprimer, polezno sravnenie dvuh razryadnyh massiva, odin iz kotoryh predstavlyaet nabor vseh vozmozhnyh obrabatyvaemyh preryvanij, a drugoj - nabor preryvanij, ozhidayushchih obrabotki. Otmetim, chto ispol'zovanie polej ($$R.9.6) mozhet sluzhit' udobnym i bolee lakonichnym sposobom raboty s chastyami slova, chem sdvigi i maskirovanie. S chastyami slova mozhno rabotat' i s pomoshch'yu porazryadnyh logicheskih operacij. Naprimer, mozhno vydelit' srednie 16 razryadov iz srediny 32-razryadnogo celogo: unsigned short middle(int a) { return (a>>8)&0xffff; } Tol'ko ne putajte porazryadnye logicheskie operacii s prosto logicheskimi operaciyami: && || ! Rezul'tatom poslednih mozhet byt' 0 ili 1, i oni v osnovnom ispol'zuyutsya v uslovnyh vyrazheniyah operatorov if, while ili for ($$3.3.1). Naprimer, !0 (ne nul') imeet znachenie 1, togda kak ~0 (dopolnenie nulya) predstavlyaet soboj nabor razryadov "vse edinicy", kotoryj obychno yavlyaetsya znacheniem -1 v dopolnitel'nom kode. 3.2.5 Preobrazovanie tipa Inogda byvaet neobhodimo yavno preobrazovat' znachenie odnogo tipa v znachenie drugogo. Rezul'tatom yavnogo preobrazovaniya budet znachenie ukazannogo tipa, poluchennoe iz znacheniya drugogo tipa. Naprimer: float r = float(1); Zdes' pered prisvaivaniem celoe znachenie 1 preobrazuetsya v znachenie s plavayushchej tochkoj 1.0f. Rezul'tat preobrazovaniya tipa ne yavlyaetsya adresom, poetomu emu prisvaivat' nel'zya (esli tol'ko tip ne yavlyaetsya ssylkoj). Sushchestvuyut dva vida zapisi yavnogo preobrazovaniya tipa: tradicionnaya zapis', kak operaciya privedeniya v S, naprimer, (double)a i funkcional'naya zapis', naprimer, double(a). Funkcional'nuyu zapis' nel'zya ispol'zovat' dlya tipov, kotorye ne imeyut prostogo imeni. Naprimer, chtoby preobrazovat' znachenie v tip ukazatelya, nado ili ispol'zovat' privedenie char* p = (char*)0777; ili opredelit' novoe imya tipa: typedef char* Pchar; char* p = Pchar(0777); Po mneniyu avtora, funkcional'naya zapis' v netrivial'nyh sluchayah predpochtitel'nee. Rassmotrim dva ekvivalentnyh primera: Pname n2 = Pbase(n1->tp)->b_name; // funkcional'naya zapis' Pname n3 = ((Pbase)n2->tp)->b_name; // zapis' s privedeniem Poskol'ku operaciya -> imeet bol'shij prioritet, chem operaciya privedeniya, poslednee vyrazhenie vypolnyaetsya tak: ((Pbase)(n2->tp))->b_name Ispol'zuya yavnoe preobrazovanie v tip ukazatelya mozhno vydat' dannyj ob容kt za ob容kt proizvol'nogo tipa. Naprimer, prisvaivanie any_type* p = (any_type*)&some_object; pozvolit obrashchat'sya k nekotoromu ob容ktu (some_object) cherez ukazatel' p kak k ob容ktu proizvol'nogo tipa (any_type). Tem ne menee, esli some_object v dejstvitel'nosti imeet tip ne any_type, mogut poluchit'sya strannye i nezhelatel'nye rezul'taty. Esli preobrazovanie tipa ne yavlyaetsya neobhodimym, ego voobshche sleduet izbegat'. Programmy, v kotoryh est' takie preobrazovaniya, obychno trudnee ponimat', chem programmy, ih ne imeyushchie. V to zhe vremya programmy s yavno zadannymi preobrazovaniyami tipa ponyatnee, chem programmy, kotorye obhodyatsya bez takih preobrazovanij, potomu chto ne vvodyat tipov dlya predstavleniya ponyatij bolee vysokogo urovnya. Tak, naprimer, postupayut programmy, upravlyayushchie registrom ustrojstva s pomoshch'yu sdviga i maskirovaniya celyh, vmesto togo, chtoby opredelit' podhodyashchuyu strukturu (struct) i rabotat' neposredstvenno s nej (sm. $$2.6.1). Korrektnost' yavnogo preobrazovaniya tipa chasto sushchestvenno zavisit ot togo, naskol'ko programmist ponimaet, kak yazyk rabotaet s ob容ktami razlichnyh tipov, i kakova specifika dannoj realizacii yazyka. Privedem primer: int i = 1; char* pc = "asdf"; int* pi = &i; i = (int)pc; pc = (char*)i; // ostorozhno: znachenie pc mozhet izmenit'sya. // Na nekotoryh mashinah sizeof(int) // men'she, chem sizeof(char*) pi = (int*)pc; pc = (char*)pi; // ostorozhno: pc mozhet izmenit'sya // Na nekotoryh mashinah char* imeet ne takoe // predstavlenie, kak int* Dlya mnogih mashin eti prisvaivaniya nichem ne grozyat, no dlya nekotoryh rezul'tat mozhet byt' plachevnym. V luchshem sluchae podobnaya programma budet perenosimoj. Obychno bez osobogo riska mozhno predpolozhit', chto ukazateli na razlichnye struktury imeyut odinakovoe predstavlenie. Dalee, proizvol'nyj ukazatel' mozhno prisvoit' (bez yavnogo preobrazovaniya tipa) ukazatelyu tipa void*, a void* mozhet byt' yavno preobrazovan obratno v ukazatel' proizvol'nogo tipa. V yazyke S++ yavnye preobrazovaniya tipa okazyvaetsya izlishnimi vo mnogih sluchayah, kogda v S (i drugih yazykah) oni trebuyutsya. Vo mnogih programmah mozhno voobshche obojtis' bez yavnyh preobrazovanij tipa, a vo mnogih drugih oni mogut byt' lokalizovany v neskol'kih podprogrammah. 3.2.6 Svobodnaya pamyat' Imenovannyj ob容kt yavlyaetsya libo staticheskim, libo avtomaticheskim (sm.$$2.1.3). Staticheskij ob容kt razmeshchaetsya v pamyati v moment zapuska programmy i sushchestvuet tam do ee zaversheniya. Avtomaticheskij ob容kt razmeshchaetsya v pamyati vsyakij raz, kogda upravlenie popadaet v blok, soderzhashchij opredelenie ob容kta, i sushchestvuet tol'ko do teh por, poka upravlenie ostaetsya v etom bloke. Tem ne menee, chasto byvaet udobno sozdat' novyj ob容kt, kotoryj sushchestvuet do teh por, poka on ne stanet nenuzhnym. V chastnosti, byvaet udobno sozdat' ob容kt, kotoryj mozhno ispol'zovat' posle vozvrata iz funkcii, gde on byl sozdan. Podobnye ob容kty sozdaet operaciya new, a operaciya delete ispol'zuetsya dlya ih unichtozheniya v dal'nejshem. Pro ob容kty, sozdannye operaciej new, govoryat, chto oni razmeshchayutsya v svobodnoj pamyati. Primerami takih ob容ktov yavlyayutsya uzly derev'ev ili elementy spiska, kotorye vhodyat v struktury dannyh, razmer kotoryh na etape translyacii neizvesten. Davajte rassmotrim v kachestve primera nabrosok translyatora, kotoryj stroitsya analogichno programme kal'kulyatora. Funkcii sintaksicheskogo analiza sozdayut iz predstavlenij vyrazhenij derevo, kotoroe budet v dal'nejshem ispol'zovat'sya dlya generacii koda. Naprimer: struct enode { token_value oper; enode* left; enode* right; }; enode* expr() { enode* left = term(); for(;;) switch(curr_tok) { case PLUS: case MINUS: get_token(); enode* n = new enode; n->oper = curr_tok; n->left = left; n->right = term(); left = n; break; default: return left; } } Generator koda mozhet ispol'zovat' derevo vyrazhenij, naprimer tak: void generate(enode* n) { switch (n->oper) { case PLUS: // sootvetstvuyushchaya generaciya delete n; } } Ob容kt, sozdannyj s pomoshch'yu operacii new, sushchestvuet, do teh por, poka on ne budet yavno unichtozhen operaciej delete. Posle etogo pamyat', kotoruyu on zanimal, vnov' mozhet ispol'zovat'sya new. Obychno net nikakogo "sborshchika musora", ishchushchego ob容kty, na kotorye nikto ne ssylaetsya, i predostavlyayushchego zanimaemuyu imi pamyat' operacii new dlya povtornogo ispol'zovaniya. Operandom delete mozhet byt' tol'ko ukazatel', kotoryj vozvrashchaet operaciya new, ili nul'. Primenenie delete k nulyu ne privodit ni k kakim dejstviyam. Operaciya new mozhet takzhe sozdavat' massivy ob容ktov, naprimer: char* save_string(const char* p) { char* s = new char[strlen(p)+1]; strcpy(s,p); return s; } Otmetim, chto dlya pereraspredeleniya pamyati, otvedennoj operaciej new, operaciya delete dolzhna umet' opredelyat' razmer razmeshchennogo ob容kta. Naprimer: int main(int argc, char* argv[]) { if (argc < 2) exit(1); char* p = save_string(arg[1]); delete[] p; } CHtoby dobit'sya etogo, prihoditsya pod ob容kt, razmeshchaemyj standartnoj operaciej new, otvodit' nemnogo bol'she pamyati, chem pod staticheskij (obychno, bol'she na odno slovo). Prostoj operator delete unichtozhaet otdel'nye ob容kty, a operaciya delete[] ispol'zuetsya dlya unichtozheniya massivov. Operacii so svobodnoj pamyat'yu realizuyutsya funkciyami ($$R.5.3.3-4): void* operator new(size_t); void operator delete(void*); Zdes' size_t - bezznakovyj celochislennyj tip, opredelennyj v <stddef.h>. Standartnaya realizaciya funkcii operator new() ne inicializiruet predostavlyaemuyu pamyat'. CHto sluchitsya, kogda operaciya new ne smozhet bol'she najti svobodnoj pamyati dlya razmeshcheniya? Poskol'ku dazhe virtual'naya pamyat' nebeskonechna, takoe vremya ot vremeni proishodit. Tak, zapros vida: char* p = new char [100000000]; obychno ne prohodit normal'no. Kogda operaciya new ne mozhet vypolnit' zapros, ona vyzyvaet funkciyu, kotoraya byla zadana kak parametr pri obrashchenii k funkcii set_new_handler() iz <new.h>. Naprimer, v sleduyushchej programme: #include <iostream.h> #include <new.h> #include <stdlib.h> void out_of_store() { cerr << "operator new failed: out of store\n"; exit(1); } int main() { set_new_handler(&out_of_store); char* p = new char[100000000]; cout << "done, p = " << long(p) << '\n'; } skoree vsego, budet napechatano ne "done", a soobshchenie: operator new failed: out of store // operaciya new ne proshla: net pamyati S pomoshch'yu funkcii new_handler mozhno sdelat' nechto bolee slozhnoe, chem prosto zavershit' programmu. Esli izvesten algoritm operacij new i delete (naprimer, potomu, chto pol'zovatel' opredelil svoi funkcii operator new i operator delete), to obrabotchik new_handler mozhet popytat'sya najti svobodnuyu pamyat' dlya new. Drugimi slovami, pol'zovatel' mozhet napisat' svoj "sborshchik musora", tem samym sdelav vyzov operacii delete neobyazatel'nym. Odnako takaya zadacha, bezuslovno, ne pod silu novichku. Po tradicii operaciya new prosto vozvrashchaet ukazatel' 0, esli ne udalos' najti dostatochno svobodnoj pamyati. Reakciya zhe na eto new_handler ne byla ustanovlena. Naprimer, sleduyushchaya programma: #include <stream.h> main() { char* p = new char[100000000]; cout << "done, p = " << long(p) << '\n'; } vydast done, p = 0 Pamyat' ne vydelena, i vam sdelano preduprezhdenie! Otmetim, chto, zadav reakciyu na takuyu situaciyu v funkcii new_handler, pol'zovatel' beret na sebya proverku: ischerpana li svobodnaya pamyat'. Ona dolzhna vypolnyat'sya pri kazhdom obrashchenii v programme k new (esli tol'ko pol'zovatel' ne opredelil sobstvennye funkcii dlya razmeshcheniya ob容ktov pol'zovatel'skih tipov; sm.$$R.5.5.6). 3.3 Svodka operatorov Polnoe i posledovatel'noe opisanie operatorov S++ soderzhitsya v $$R.6. Sovetuem oznakomit'sya s etim razdelom. Zdes' zhe daetsya svodka operatorov i neskol'ko primerov. ------------------------------------------------------------------ Sintaksis operatorov ------------------------------------------------------------------ operator: opisanie { spisok-operatorov opt } vyrazhenie opt ; if ( vyrazhenie ) operator if ( vyrazhenie ) operator else operator switch ( vyrazhenie ) operator while ( vyrazhenie ) operator do operator while ( vyrazhenie ) for (nachal'nyj-operator-for vyrazhenie opt; vyrazhenie opt) operator case vyrazhenie-konstanta : operator default : operator break ; continue ; return vyrazhenie opt ; goto identifikator ; identifikator : operator spisok-operatorov: operator spisok-operatorov operator nachal'nyj-operator-for: opisanie vyrazhenie opt ; ---------------------------------------------------------------------- Obratite vnimanie, chto opisanie yavlyaetsya operatorom, no net operatorov prisvaivaniya ili vyzova funkcii (oni otnosyatsya k vyrazheniyam). 3.3.1 Vybirayushchie operatory Znachenie mozhno proverit' s pomoshch'yu operatorov if ili switch: if ( vyrazhenie ) operator if ( vyrazhenie ) operator else operator switch ( vyrazhenie ) operator V yazyke S++ sredi osnovnyh tipov net otdel'nogo bulevskogo (tip so znacheniyami istina, lozh'). Vse operacii otnoshenij: == != < > <= >= dayut v rezul'tate celoe 1, esli otnoshenie vypolnyaetsya, i 0 v protivnom sluchae. Obychno opredelyayut konstanty TRUE kak 1 i FALSE kak 0. V operatore if, esli vyrazhenie imeet nenulevoe znachenie, vypolnyaetsya pervyj operator, a inache vypolnyaetsya vtoroj (esli on ukazan). Takim obrazom, v kachestve usloviya dopuskaetsya lyuboe vyrazhenie tipa celoe ili ukazatel'. Pust' a celoe, togda if (a) // ... ekvivalentno if (a != 0) ... Logicheskie operacii && || ! obychno ispol'zuyutsya v usloviyah. V operaciyah && i || vtoroj operand ne vychislyaetsya, esli rezul'tat opredelyaetsya znacheniem pervogo operanda. Naprimer, v vyrazhenii if (p && l<p->count) // ... snachala proveryaetsya znachenie p, i tol'ko esli ono ne ravno nulyu, to proveryaetsya otnoshenie l<p->count. Nekotorye prostye operatory if udobno zamenyat' vyrazheniyami usloviya. Naprimer, vmesto operatora if (a <= b) max = b; else max = a; luchshe ispol'zovat' vyrazhenie max = (a<=b) ? b : a; Uslovie v vyrazhenii usloviya ne obyazatel'no okruzhat' skobkami, no esli ih ispol'zovat', to vyrazhenie stanovitsya ponyatnee. Prostoj pereklyuchatel' (switch) mozhno zapisat' s pomoshch'yu serii operatorov if. Naprimer, switch (val) { case 1: f(); break; case 2: g(); break; default: h(); break; } mozhno ekvivalentno zadat' tak: if (val == 1) f(); else if (val == 2) g(); else h(); Smysl obeih konstrukcij sovpadaet, no vse zhe pervaya predpochtitel'nee, poskol'ku v nej naglyadnee pokazana sut' operacii: proverka na sovpadenie znacheniya val so znacheniem iz mnozhestva konstant. Poetomu v netrivial'nyh sluchayah zapis', ispol'zuyushchaya pereklyuchatel', ponyatnee. Nuzhno pozabotit'sya o kakom-to zavershenii operatora, ukazannogo v variante pereklyuchatelya, esli tol'ko vy ne hotite, chtoby stali vypolnyat'sya operatory iz sleduyushchego varianta. Naprimer, pereklyuchatel' switch (val) { // vozmozhna oshibka case 1: cout << "case 1\n"; case 2: cout << "case 2\n"; default: cout << "default: case not found\n"; } pri val==1 napechataet k bol'shomu udivleniyu neposvyashchennyh: case 1 case 2 default: case not found Imeet smysl otmetit' v kommentariyah te redkie sluchai, kogda standartnyj perehod na sleduyushchij variant ostavlen namerenno. Togda etot perehod vo vseh ostal'nyh sluchayah mozhno smelo schitat' oshibkoj. Dlya zaversheniya operatora v variante chashche vsego ispol'zuetsya break, no inogda ispol'zuyutsya return i dazhe goto. Privedem primer: switch (val) { // vozmozhna oshibka case 0: cout << "case 0\n"; case1: case 1: cout << "case 1\n"; return; case 2: cout << "case 2\n"; goto case1; default: cout << "default: case not found\n"; return; } Zdes' pri znachenii val ravnom 2 my poluchim: case 2 case 1 Otmetim, chto metku varianta nel'zya ispol'zovat' v operatore goto: goto case 2; // sintaksicheskaya oshibka 3.3.2 Operator goto Preziraemyj operator goto vse-taki est' v S++: goto identifikator; identifikator: operator Voobshche govorya, on malo ispol'zuetsya v yazykah vysokogo urovnya, no mozhet byt' ochen' polezen, esli tekst na S++ sozdaetsya ne chelovekom, a avtomaticheski, t.e. s pomoshch'yu programmy. Naprimer, operatory goto ispol'zuyutsya pri sozdanii analizatora po zadannoj grammatike yazyka s pomoshch'yu programmnyh sredstv. Krome togo, operatory goto mogut prigodit'sya v teh sluchayah, kogda na pervyj plan vyhodit skorost' raboty programmy. Odin iz nih - kogda v real'nom vremeni proishodyat kakie-to vychisleniya vo vnutrennem cikle programmy. Est' nemnogie situacii i v obychnyh programmah, kogda primenenie goto opravdano. Odna iz nih - vyhod iz vlozhennogo cikla ili pereklyuchatelya. Delo v tom, chto operator break vo vlozhennyh ciklah ili pereklyuchatelyah pozvolyaet perejti tol'ko na odin uroven' vyshe. Privedem primer: void f() { int i; int j; for ( i = 0; i < n; i++) for (j = 0; j<m; j++) if (nm[i][j] == a) goto found; // zdes' a ne najdeno // ... found: // nm[i][j] == a } Est' eshche operator continue, kotoryj pozvolyaet perejti na konec cikla. CHto eto znachit, ob座asneno v $$3.1.5. 3.4 Kommentarii i raspolozhenie teksta Programmu gorazdo legche chitat', i ona stanovitsya namnogo ponyatnee, esli razumno ispol'zovat' kommentarii i