l' sozdaniya S++ byla v tom, chtoby pol'zovatel' mog opredelit' novye tipy dannyh, rabota s kotorymi byla by stol' zhe udobna i effektivna kak i so vstroennymi tipami. Takim obrazom, kazhetsya razumnym potrebovat', chtoby sredstva vvoda-vyvoda dlya S++ programmirovalis' s ispol'zovaniem vozmozhnostej S++, dostupnyh kazhdomu. Predstavlennye zdes' potokovye sredstva vvoda-vyvoda poyavilis' v rezul'tate popytki udovletvorit' etim trebovaniyam. Osnovnaya zadacha potokovyh sredstv vvoda-vyvoda - eto process preobrazovaniya ob容ktov opredelennogo tipa v posledovatel'nost' simvolov i naoborot. Sushchestvuyut i drugie shemy vvoda-vyvoda, no ukazannaya yavlyaetsya osnovnoj, i esli schitat' simvol prosto naborom bitov, ignoriruya ego estestvennuyu svyaz' s alfavitom, to mnogie shemy dvoichnogo vvoda-vyvoda mozhno svesti k nej. Poetomu programmistskaya sut' zadachi svoditsya k opisaniyu svyazi mezhdu ob容ktom opredelennogo tipa i bestipovoj (chto sushchestvenno) strokoj. Posleduyushchie razdely opisyvayut osnovnye chasti potokovoj biblioteki S++: 10.2 Vyvod: To, chto dlya prikladnoj programmy predstavlyaetsya vyvodom, na samom dele yavlyaetsya preobrazovaniem takih ob容ktov kak int, char *, complex ili Employee_record v posledovatel'nost' simvolov. Opisyvayutsya sredstva dlya zapisi ob容ktov vstroennyh i pol'zovatel'skih tipov dannyh. 10.3 Vvod: Opisany funkcii dlya vvoda simvolov, strok i znachenij vstroennyh i pol'zovatel'skih tipov dannyh. 10.4 Formatirovanie: CHasto sushchestvuyut opredelennye trebovaniya k vidu vyvoda, naprimer, int dolzhno pechatat'sya desyatichnymi ciframi, ukazateli v shestnadcaterichnoj zapisi, a veshchestvennye chisla dolzhny byt' s yavno zadannoj tochnost'yu fiksirovannogo razmera. Obsuzhdayutsya funkcii formatirovaniya i opredelennye programmistskie priemy ih sozdaniya, v chastnosti, manipulyatory. 10.5 Fajly i potoki: Kazhdaya programma na S++ mozhet ispol'zovat' po umolchaniyu tri potoka - standartnyj vyvod (cout), standartnyj vvod (cin) i standartnyj potok oshibok (cerr). CHtoby rabotat' s kakimi- libo ustrojstvami ili fajlami nado sozdat' potoki i privyazat' ih k etim ustrojstvam ili fajlam. Opisyvaetsya mehanizm otkrytiya i zakrytiya fajlov i svyazyvaniya fajlov s potokami. 10.6 Vvod-vyvod dlya S: obsuzhdaetsya funkciya printf iz fajla <stdio.h> dlya S a takzhe svyaz' mezhdu bibliotekoj dlya S i <iostream.h> dlya S++. Ukazhem, chto sushchestvuet mnogo nezavisimyh realizacij potokovoj biblioteki vvoda-vyvoda i nabor sredstv, opisannyh zdes', budet tol'ko podmnozhestvom sredstv, imeyushchihsya v vashej biblioteke. Govoryat, chto vnutri lyuboj bol'shoj programmy est' malen'kaya programma, kotoraya stremitsya vyrvat'sya naruzhu. V etoj glave predprinyata popytka opisat' kak raz malen'kuyu potokovuyu biblioteku vvoda-vyvoda, kotoraya pozvolit ocenit' osnovnye koncepcii potokovogo vvoda-vyvoda i poznakomit' s naibolee poleznymi sredstvami. Ispol'zuya tol'ko sredstva, opisannye zdes', mozhno napisat' mnogo programm; esli vozniknet neobhodimost' v bolee slozhnyh sredstvah, obratites' za detalyami k vashemu rukovodstvu po S++. Zagolovochnyj fajl <iostream.h> opredelyaet interfejs potokovoj biblioteki. V rannih versiyah potokovoj biblioteki ispol'zovalsya fajl <stream.h>. Esli sushchestvuyut oba fajla, <iostream.h> opredelyaet polnyj nabor sredstv, a <stream.h> opredelyaet podmnozhestvo, kotoroe sovmestimo s rannimi, menee bogatymi potokovymi bibliotekami. Estestvenno, dlya pol'zovaniya potokovoj bibliotekoj vovse ne nuzhno znanie tehniki ee realizacii, tem bolee, chto tehnika mozhet byt' razlichnoj dlya razlichnyh realizacij. Odnako, realizaciya vvoda-vyvoda yavlyaetsya zadachej, diktuyushchej opredelennye usloviya, znachit priemy, najdennye v processe ee resheniya, mozhno primenit' i dlya drugih zadach, a samo eto reshenie dostojno izucheniya. 10.2 VYVOD Stroguyu tipovuyu i edinoobraznuyu rabotu kak so vstroennymi, tak i s pol'zovatel'skimi tipami mozhno obespechit', esli ispol'zovat' edinstvennoe peregruzhennoe imya funkcii dlya razlichnyh operacij vyvoda. Naprimer: put(cerr,"x = "); // cerr - vyhodnoj potok oshibok put(cerr,x); put(cerr,'\n'); Tip argumenta opredelyaet kakuyu funkciyu nado vyzyvat' v kazhdom sluchae. Takoj podhod primenyaetsya v neskol'kih yazykah, odnako, eto slishkom dlinnaya zapis'. Za schet peregruzki operacii << , chtoby ona oznachala "vyvesti" ("put to"), mozhno poluchit' bolee prostuyu zapis' i razreshit' programmistu vyvodit' v odnom operatore posledovatel'nost' ob容ktov, naprimer tak: cerr << "x = " << x << '\n'; Zdes' cerr oboznachaet standartnyj potok oshibok. Tak, esli h tipa int so znacheniem 123, to privedennyj operator vydast x = 123 i eshche simvol konca stroki v standartnyj potok oshibok. Analogichno, esli h imeet pol'zovatel'skij tip complex so znacheniem (1,2.4), to ukazannyj operator vydast x = (1,2.4) v potok cerr. Takoj podhod legko ispol'zovat' poka x takogo tipa, dlya kotorogo opredelena operaciya <<, a pol'zovatel' mozhet prosto doopredelit' << dlya novyh tipov. My ispol'zovali operaciyu vyvoda, chtoby izbezhat' mnogoslovnosti, neizbezhnoj, esli primenyat' funkciyu vyvoda. No pochemu imenno simvol << ? Nevozmozhno izobresti novuyu leksemu (sm. 7.2). Kandidatom dlya vvoda i vyvoda byla operaciya prisvaivaniya, no bol'shinstvo lyudej predpochitaet, chtoby operacii vvoda i vyvoda byli razlichny. Bolee togo, poryadok vypolneniya operacii = nepodhodyashchij, tak cout=a=b oznachaet cout=(a=b). Probovali ispol'zovat' operacii < i >, no k nim tak krepko privyazano ponyatie "men'she chem" i "bol'she chem", chto operacii vvoda-vyvoda s nimi vo vseh prakticheski sluchayah ne poddavalis' prochteniyu. Operacii << i >> pohozhe ne sozdayut takih problem. Oni asimetrichny, chto pozvolyaet pripisyvat' im smysl "v" i "iz". Oni ne otnosyatsya k chislu naibolee chasto ispol'zuemyh operacij nad vstroennymi tipami, a prioritet << dostatochno nizkij, chtoby pisat' arifmeticheskie vyrazheniya v kachestve operanda bez skobok: cout << "a*b+c=" << a*b+c << '\n'; Skobki nuzhny, esli vyrazhenie soderzhit operacii s bolee nizkim prioritetom: cout << "a^b|c=" << (a^b|c) << '\n'; Operaciyu sdviga vlevo mozhno ispol'zovat' v operacii vyvoda, no, konechno, ona dolzhna byt' v skobkah: cout << "a<<b=" << (a<<b) << '\n'; 10.2.1 Vyvod vstroennyh tipov Dlya upravleniya vyvodom vstroennyh tipov opredelyaetsya klass ostream s operaciej << (vyvesti): class ostream : public virtual ios { // ... public: ostream& operator<<(const char*); //stroki ostream& operator<<(char); ostream& operator<<(short i) { return *this << int(i); } ostream& operator<<(int); ostream& operator<<(long); ostream& operator<<(double); ostream& operator<<(const void*); // ukazateli // ... }; Estestvenno, v klasse ostream dolzhen byt' nabor funkcij operator<<() dlya raboty s bezznakovymi tipami. Funkciya operator<< vozvrashchaet ssylku na klass ostream, iz kotorogo ona vyzyvalas', chtoby k nej mozhno bylo primenit' eshche raz operator<<. Tak, esli h tipa int, to cerr << "x = " << x; ponimaetsya kak (cerr.operator<<("x = ")).operator<<(x); V chastnosti, eto oznachaet, chto esli neskol'ko ob容ktov vyvodyatsya s pomoshch'yu odnogo operatora vyvoda, to oni budut vydavat'sya v estestvennom poryadke: sleva - napravo. Funkciya ostream::operator<<(int) vyvodit celye znacheniya, a funkciya ostream::operator<<(char) - simvol'nye. Poetomu funkciya void val(char c) { cout << "int('"<< c <<"') = " << int(c) << '\n'; } pechataet celye znacheniya simvolov i s pomoshch'yu programmy main() { val('A'); val('Z'); } budet napechatano int('A') = 65 int('Z') = 90 Zdes' predpolagaetsya kodirovka simvolov ASCII, na vashej mashine mozhet byt' inoj rezul'tat. Obratite vnimanie, chto simvol'naya konstanta imeet tip char, poetomu cout<<'Z' napechataet bukvu Z, a vovse ne celoe 90. Funkciya ostream::operator<<(const void*) napechataet znachenie ukazatelya v takoj zapisi, kotoraya bolee podhodit dlya ispol'zuemoj sistemy adresacii. Programma main() { int i = 0; int* p = new int(1); cout << "local " << &i << ", free store " << p << '\n'; } vydast na mashine, ispol'zuemoj avtorom, local 0x7fffead0, free store 0x500c Dlya drugih sistem adresacii mogut byt' inye soglasheniya ob izobrazhenii znachenij ukazatelej. Obsuzhdenie bazovogo klassa ios otlozhim do 10.4.1. 10.2.2 Vyvod pol'zovatel'skih tipov Rassmotrim pol'zovatel'skij tip dannyh: class complex { double re, im; public: complex(double r = 0, double i = 0) { re=r; im=i; } friend double real(complex& a) { return a.re; } friend double imag(complex& a) { return a.im; } friend complex operator+(complex, complex); friend complex operator-(complex, complex); friend complex operator*(complex, complex); friend complex operator/(complex, complex); //... }; Dlya novogo tipa complex operaciyu << mozhno opredelit' tak: ostream& operator<<(ostream&s, complex z) { return s << '(' real(z) << ',' << imag(z) << ')'; }; i ispol'zovat' kak operator<< dlya vstroennyh tipov. Naprimer, main() { complex x(1,2); cout << "x = " << x << '\n'; } vydast x = (1,2) Dlya opredeleniya operacii vyvoda nad pol'zovatel'skimi tipami dannyh ne nuzhno modificirovat' opisanie klassa ostream, ne trebuetsya i dostup k strukturam dannyh, skrytym v opisanii klassa. Poslednee ochen' kstati, poskol'ku opisanie klassa ostream nahoditsya sredi standartnyh zagolovochnyh fajlov, dostup po zapisi k kotorym zakryt dlya bol'shinstva pol'zovatelej, i izmenyat' kotorye oni vryad li zahotyat, dazhe esli by mogli. |to vazhno i po toj prichine, chto daet zashchitu ot sluchajnoj porchi etih struktur dannyh. Krome togo imeetsya vozmozhnost' izmenit' realizaciyu ostream, ne zatragivaya pol'zovatel'skih programm. 10.3 VVOD Vvod vo mnogom shoden s vyvodom. Est' klass istream, kotoryj realizuet operaciyu vvoda >> ("vvesti iz" - "input from") dlya nebol'shogo nabora standartnyh tipov. Dlya pol'zovatel'skih tipov mozhno opredelit' funkciyu operator>>. 10.3.1 Vvod vstroennyh tipov Klass istream opredelyaetsya sleduyushchim obrazom: class istream : public virtual ios { //... public: istream& operator>>(char*); // stroka istream& operator>>(char&); // simvol istream& operator>>(short&); istream& operator>>(int&); istream& operator>>(long&); istream& operator>>(float&); istream& operator>>(double&); //... }; Funkcii vvoda operator>> opredelyayutsya tak: istream& istream::operator>>(T& tvar) { // propuskaem obobshchennye probely // kakim-to obrazom chitaem T v`tvar' return *this; } Teper' mozhno vvesti v VECTOR posledovatel'nost' celyh, razdelyaemyh probelami, s pomoshch'yu funkcii: int readints(Vector<int>& v) // vozvrashchaem chislo prochitannyh celyh { for (int i = 0; i<v.size(); i++) { if (cin>>v[i]) continue; return i; } // slishkom mnogo celyh dlya razmera Vector // nuzhna sootvetstvuyushchaya obrabotka oshibki } Poyavlenie znacheniya s tipom, otlichnym ot int, privodit k prekrashcheniyu operacii vvoda, i cikl vvoda zavershaetsya. Tak, esli my vvodim 1 2 3 4 5. 6 7 8. to funkciya readints() prochitaet pyat' celyh chisel 1 2 3 4 5 Simvol tochka ostanetsya pervym simvolom, podlezhashchim vvodu. Pod probelom, kak opredeleno v standarte S, ponimaetsya obobshchennyj probel, t.e. probel, tabulyaciya, konec stroki, perevod stroki ili vozvrat karetki. Proverka na obobshchennyj probel vozmozhna s pomoshch'yu funkcii isspace() iz fajla <ctype.h>. V kachestve al'ternativy mozhno ispol'zovat' funkcii get(): class istream : public virtual ios { //... istream& get(char& c); // simvol istream& get(char* p, int n, char ='n'); // stroka }; V nih obobshchennyj probel rassmatrivaetsya kak lyuboj drugoj simvol i oni prednaznacheny dlya takih operacij vvoda, kogda ne delaetsya nikakih predpolozhenij o vvodimyh simvolah. Funkciya istream::get(char&) vvodit odin simvol v svoj parametr. Poetomu programmu posimvol'nogo kopirovaniya mozhno napisat' tak: main() { char c; while (cin.get(c)) cout << c; } Takaya zapis' vyglyadit nesimmetrichno, i u operacii >> dlya vyvoda simvolov est' dvojnik pod imenem put(), tak chto mozhno pisat' i tak: main() { char c; while (cin.get(c)) cout.put(c); } Funkciya s tremya parametrami istream::get() vvodit v simvol'nyj vektor ne menee n simvolov, nachinaya s adresa p. Pri vsyakom obrashchenii k get() vse simvoly, pomeshchennye v bufer (esli oni byli), zavershayutsya 0, poetomu esli vtoroj parametr raven n, to vvedeno ne bolee n-1 simvolov. Tretij parametr opredelyaet simvol, zavershayushchij vvod. Tipichnoe ispol'zovanie funkcii get() s tremya parametrami svoditsya k chteniyu stroki v bufer zadannogo razmera dlya ee dal'nejshego razbora, naprimer tak: void f() { char buf[100]; cin >> buf; // podozritel'no cin.get(buf,100,'\n'); // nadezhno //... } Operaciya cin>>buf podozritel'na, poskol'ku stroka iz bolee chem 99 simvolov perepolnit bufer. Esli obnaruzhen zavershayushchij simvol, to on ostaetsya v potoke pervym simvolom podlezhashchim vvodu. |to pozvolyaet proveryat' bufer na perepolnenie: void f() { char buf[100]; cin.get(buf,100,'\n'); // nadezhno char c; if (cin.get(c) && c!='\n') { // vhodnaya stroka bol'she, chem ozhidalos' } //... } Estestvenno, sushchestvuet versiya get() dlya tipa unsigned char. V standartnom zagolovochnom fajle <ctype.h> opredeleny neskol'ko funkcij, poleznyh dlya obrabotki pri vvode: int isalpha(char) // 'a'..'z' 'A'..'Z' int isupper(char) // 'A'..'Z' int islower(char) // 'a'..'z' int isdigit(char) // '0'..'9' int isxdigit(char) // '0'..'9' 'a'..'f' 'A'..'F' int isspace(char) // ' ' '\t' vozvrashchaet konec stroki // i perevod formata int iscntrl(char) // upravlyayushchij simvol v diapazone // (ASCII 0..31 i 127) int ispunct(char) // znak punktuacii, otlichen ot privedennyh vyshe int isalnum(char) // isalpha() | isdigit() int isprint(char) // vidimyj: ascii ' '..'~' int isgraph(char) // isalpha() | isdigit() | ispunct() int isascii(char c) { return 0<=c && c<=127; } Vse oni, krome isascii(), rabotayut s pomoshch'yu prostogo prosmotra, ispol'zuya simvol kak indeks v tablice atributov simvolov. Poetomu vmesto vyrazheniya tipa (('a'<=c && c<='z') || ('A'<=c && c<='Z')) // bukva kotoroe ne tol'ko utomitel'no pisat', no ono mozhet byt' i oshibochnym (na mashine s kodirovkoj EBCDIC ono zadaet ne tol'ko bukvy), luchshe ispol'zovat' vyzov standartnoj funkcii isalpha(), kotoryj k tomu zhe bolee effektiven. V kachestve primera privedem funkciyu eatwhite(), kotoraya chitaet iz potoka obobshchennye probely: istream& eatwhite(istream& is) { char c; while (is.get(c)) { if (isspace(c)==0) { is.putback(c); break; } } return is; } V nej ispol'zuetsya funkciya putback(), kotoraya vozvrashchaet simvol v potok, i on stanovitsya pervym podlezhashchim chteniyu. 10.3.2 Sostoyaniya potoka S kazhdym potokom (istream ili ostream) svyazano opredelennoe sostoyanie. Nestandartnye situacii i oshibki obrabatyvayutsya s pomoshch'yu proverki i ustanovki sostoyaniya podhodyashchim obrazom. Uznat' sostoyanie potoka mozhno s pomoshch'yu operacij nad klassom ios: class ios { //ios yavlyaetsya bazovym dlya ostream i istream //... public: int eof() const; // doshli do konca fajla int fail() const; // sleduyushchaya operaciya budet neudachna int bad() const; // potok isporchen int good() const; // sleduyushchaya operaciya budet uspeshnoj //... }; Poslednyaya operaciya vvoda schitaetsya uspeshnoj, esli sostoyanie zadaetsya good() ili eof(). Esli sostoyanie zadaetsya good(), to posleduyushchaya operaciya vvoda mozhet byt' uspeshnoj, v protivnom sluchae ona budet neudachnoj. Primenenie operacii vvoda k potoku v sostoyanii, zadavaemom ne good(), schitaetsya pustoj operaciej. Esli proizoshla neudacha pri popytke chteniya v peremennuyu v, to znachenie v ne izmenilos' (ono ne izmenitsya, esli v imeet tip, upravlyaemyj funkciyami chlena iz istream ili ostream). Razlichie mezhdu sostoyaniyami, zadavaemymi kak fail() ili kak bad() ulovit' trudno, i ono imeet smysl tol'ko dlya razrabotchikov operacij vvoda. Esli sostoyanie est' fail(), to schitaetsya, chto potok ne povrezhden, i nikakie simvoly ne propali; o sostoyanii bad() nichego skazat' nel'zya. Znacheniya, oboznachayushchie eti sostoyaniya, opredeleny v klasse ios: class ios { //... public: enum io_state { goodbit=0, eofbit=1, filebit=2, badbit=4, }; //... }; Istinnye znacheniya sostoyanij zavisyat ot realizacii, i ukazannye znacheniya privedeny tol'ko, chtoby izbezhat' sintaksicheski nepravil'nyh konstrukcij. Proveryat' sostoyanie potoka mozhno sleduyushchim obrazom: switch (cin.rdstate()) { case ios::goodbit: // poslednyaya operaciya s cin byla uspeshnoj break; case ios::eofbit: // v konce fajla break; case ios::filebit: // nekotoryj analiz oshibki // vozmozhno neplohoj break; case ios::badbit: // cin vozmozhno isporchen break; } V bolee rannih realizaciyah dlya znachenij sostoyanij ispol'zovalis' global'nye imena. |to privodilo k nezhelatel'nomu zasoreniyu prostranstva imenovaniya, poetomu novye imena dostupny tol'ko v predelah klassa ios. Esli vam neobhodimo ispol'zovat' starye imena v sochetanii s novoj bibliotekoj, mozhno vospol'zovat'sya sleduyushchimi opredeleniyami: const int _good = ios::goodbit; const int _bad = ios::badbit; const int _file = ios::filebit; const int _eof = ios::eofbit; typedef ios::io_state state_value ; Razrabotchiki bibliotek dolzhny zabotitsya o tom, chtoby ne dobavlyat' novyh imen k global'nomu prostranstvu imenovaniya. Esli elementy perechisleniya vhodyat v obshchij interfejs biblioteki, oni vsegda dolzhny ispol'zovat'sya v klasse s prefiksami, naprimer, kak ios::goodbit i ios::io_state. Dlya peremennoj lyubogo tipa, dlya kotorogo opredeleny operacii << i >>, cikl kopirovaniya zapisyvaetsya sleduyushchim obrazom: while (cin>>z) cout << z << '\n'; Esli potok poyavlyaetsya v uslovii, to proveryaetsya sostoyanie potoka, i uslovie vypolnyaetsya (t.e. rezul'tat ego ne 0) tol'ko dlya sostoyaniya good(). Kak raz v privedennom vyshe cikle proveryaetsya sostoyanie potoka istream, chto yavlyaetsya rezul'tatom operacii cin>>z. CHtoby uznat', pochemu proizoshla neudacha v cikle ili uslovii, nado proverit' sostoyanie. Takaya proverka dlya potoka realizuetsya s pomoshch'yu operacii privedeniya (7.3.2). Tak, esli z yavlyaetsya simvol'nym vektorom, to v privedennom cikle chitaetsya standartnyj vvod i vydaetsya dlya kazhdoj stroki standartnogo vyvoda po odnomu slovu (t.e. posledovatel'nosti simvolov, ne yavlyayushchihsya obobshchennymi probelami). Esli z imeet tip complex, to v etom cikle s pomoshch'yu operacij, opredelennyh v 10.2.2 i 10.2.3, budut kopirovat'sya kompleksnye chisla. SHablonnuyu funkciyu kopirovaniya dlya potokov so znacheniyami proizvol'nogo tipa mozhno napisat' sleduyushchim obrazom: complex z; iocopy(z,cin,cout); // kopirovanie complex double d; iocopy(d,cin,cout); // kopirovanie double char c; iocopy(c,cin,cout); // kopirovanie char Poskol'ku nadoedaet proveryat' na korrektnost' kazhduyu operaciyu vvoda- vyvoda, to rasprostranennym istochnikom oshibok yavlyayutsya imenno te mesta v programme, gde takoj kontrol' sushchestvenen. Obychno operacii vyvoda ne proveryayut, no inogda oni mogut zavershit'sya neudachno. Potokovyj vvod- vyvod razrabatyvalsya iz togo principa, chtoby sdelat' isklyuchitel'nye situacii legkodostupnymi, i tem samym uprostit' obrabotku oshibok v processe vvoda-vyvoda. 10.3.3 Vvod pol'zovatel'skih tipov Operaciyu vvoda dlya pol'zovatel'skogo tipa mozhno opredelit' v tochnosti tak zhe, kak i operaciyu vyvoda, no dlya operacii vvoda sushchestvenno, chtoby vtoroj parametr imel tip ssylki, naprimer: istream& operator>>(istream& s, complex& a) /* format input rasschitan na complex; "f" oboznachaet float: f ( f ) ( f , f ) */ { double re = 0, im = 0; char c = 0; s >> c; if (c == '(') { s >> re >> c; if (c == ',') s >> im >> c; if (c != ')') s.clear(ios::badbit); // ustanovim sostoyanie } else { s.putback(c); s >> re; } if (s) a = complex(re,im); return s; } Nesmotrya na szhatost' koda, obrabatyvayushchego oshibki, na samom dele uchityvaetsya bol'shaya chast' oshibok. Inicializaciya lokal'noj peremennoj s nuzhna dlya togo, chtoby v nee ne popalo sluchajnoe znachenie, naprimer '(', v sluchae neudachnoj operacii. Poslednyaya proverka sostoyaniya potoka garantiruet, chto parametr a poluchit znachenie tol'ko pri uspeshnom vvode. Operaciya, ustanavlivayushchaya sostoyanie potoka, nazvana clear() (zdes' clear - yasnyj, pravil'nyj), poskol'ku chashche vsego ona ispol'zuetsya dlya vosstanovleniya sostoyaniya potoka kak good(); znacheniem po umolchaniyu dlya parametra ios::clear() yavlyaetsya ios::goodbit. 10.4 Formatirovanie Vse primery iz 10.2 soderzhali neformatirovannyj vyvod, kotoryj yavlyalsya preobrazovaniem ob容kta v posledovatel'nost' simvolov, zadavaemuyu standartnymi pravilami, dlina kotoroj takzhe opredelyaetsya etimi pravilami. CHasto programmistam trebuyutsya bolee razvitye vozmozhnosti. Tak, voznikaet potrebnost' kontrolirovat' razmer pamyati, neobhodimoj dlya operacii vyvoda, i format, ispol'zuemyj dlya vydachi chisel. Tochno tak zhe dopustimo upravlenie nekotorymi aspektami vvoda. 10.4.1 Klass ios Bol'shinstvo sredstv upravleniya vvodom-vyvodom sosredotocheny v klasse ios, kotoryj yavlyaetsya bazovym dlya ostream i istream. Po suti zdes' nahoditsya upravlenie svyaz'yu mezhdu istream ili ostream i buferom, ispol'zuemym dlya operacij vvoda-vyvoda. Imenno klass ios kontroliruet: kak simvoly popadayut v bufer i kak oni vybirayutsya ottuda. Tak, v klasse ios est' chlen, soderzhashchij informaciyu ob ispol'zuemoj pri chtenii ili zapisi celyh chisel sistemy schisleniya (desyatichnaya, vos'merichnaya ili shestnadcaterichnaya), o tochnosti veshchestvennyh chisel i t.p., a takzhe funkcii dlya proverki i ustanovki znachenij peremennyh, upravlyayushchih potokom. class ios { //... public: ostream* tie(ostream* s); // svyazat' input i output ostream* tie(); // vozvratit' "tie" int width(int w); // ustanovit' pole width int width() const; char fill(char); // ustanovit' simvol zapolneniya char fill() const; // vernut' simvol zapolneniya long flags(long f); long flags() const; long setf(long setbits, long field); long setf(long); long unsetf(long); int precision(int); // ustanovit' tochnost' dlya float int precision() const; int rdstate(); const; // sostoyaniya potokov, sm. $$10.3.2 int eof() const; int fail() const; int bad() const; int good() const; void clear(int i=0); //... }; V 10.3.2 opisany funkcii, rabotayushchie s sostoyaniem potoka, ostal'nye privedeny nizhe. 10.4.1.1 Svyazyvanie potokov Funkciya tie() mozhet ustanovit' i razorvat' svyaz' mezhdu ostream i istream. Rassmotrim primer: main() { String s; cout << "Password: "; cin >> s; // ... } Kak mozhno garantirovat', chto priglashenie Password: poyavitsya na ekrane prezhde, chem vypolnit'sya operaciya chteniya? Vyvod v cout i vvod iz cin buferizuyutsya, prichem nezavisimo, poetomu Password: poyavitsya tol'ko po zavershenii programmy, kogda zakroetsya bufer vyvoda. Reshenie sostoit v tom, chtoby svyazat' cout i cin s pomoshch'yu operacii cin.tie(cout). Esli ostream svyazan s potokom istream, to bufer vyvoda vydaetsya pri kazhdoj operacii vvoda nad istream. Togda operacii cout << "Password: "; cin >> s; ekvivalentny cout << "Password: "; cout.flush(); cin >> s; Obrashchenie is.tie(0) razryvaet svyaz' mezhdu potokom is i potokom, s kotorym on byl svyazan, esli takoj byl. Podobno drugim potokovym funkciyam, ustanavlivayushchim opredelennoe znachenie, tie(s) vozvrashchaet predydushchee znachenie, t.e. znachenie svyazannogo potoka pered obrashcheniem ili 0. Vyzov bez parametra tie() vozvrashchaet tekushchee znachenie. 10.4.1.2 Polya vyvoda Funkciya width() ustanavlivaet minimal'noe chislo simvolov, ispol'zuyushcheesya v posleduyushchej operacii vyvoda chisla ili stroki. Tak v rezul'tate sleduyushchih operacij cout.width(4); cout << '(' << 12 << ')'; poluchim chislo 12 v pole razmerom 4 simvola, t.e. ( 12) Zapolnenie polya zadannymi simvolami ili vyravnivanie mozhno ustanovit' s pomoshch'yu funkcii fill(), naprimer: cout.width(4); cout.fill('#'); cout << '(' << "ab" << ')'; napechataet (##ab) Po umolchaniyu pole zapolnyaetsya probelami, a razmer polya po umolchaniyu est' 0, chto oznachaet "stol'ko simvolov, skol'ko nuzhno". Vernut' razmeru polya standartnoe znachenie mozhno s pomoshch'yu vyzova cout.width(0); // ``stol'ko simvolov, skol'ko nado'' Funkciya width() zadaet minimal'noe chislo simvolov. Esli poyavitsya bol'she simvolov, oni budut napechatany vse, poetomu cout.width(4); cout << '(' << "121212" << ")\n"; napechataet (121212) Prichina, po kotoroj razresheno perepolnenie polya, a ne usechenie vyvoda, v tom, chtoby izbezhat' zavisaniya pri vyvode. Luchshe poluchit' pravil'nuyu vydachu, vyglyadyashchuyu nekrasivo, chem krasivuyu vydachu, yavlyayushchuyusya nepravil'noj. Vyzov width() vliyaet tol'ko na odnu sleduyushchuyu za nim operaciyu vyvoda, poetomu cout.width(4); cout.fill('#'); cout << '(' << 12 << "),(" << '(' <<12 << ")\n"; napechataet (##12),(12) a ne (##12),(##12) kak mozhno bylo by ozhidat'. Odnako, zamet'te, chto esli by vliyanie rasprostranyalos' na vse operacii vyvoda chisel i strok, poluchilsya by eshche bolee neozhidannyj rezul'tat: (##12#),(##12# ) S pomoshch'yu standartnogo manipulyatora, pokazannogo v 10.4.2.1, mozhno bolee elegantno zadavat' razmera polya vyvoda. 10.4.1.3 Sostoyanie formata V klasse ios soderzhitsya sostoyanie formata, kotoroe upravlyaetsya funkciyami flags() i setf(). Po suti eti funkcii nuzhny, chtoby ustanovit' ili otmenit' sleduyushchie flagi: class ios { public: // upravlyayushchie formatom flagi: enum { skipws=01, // propusk obobshchennyh probelov dlya input // pole vyravnivaniya: left=02, // dobavlenie pered znacheniem right=04, // dobavlenie posle znacheniya internal=010, // dobavlenie mezhdu znakom i znacheniem // osnovanie celogo: dec=020, // vos'merichnoe oct=040, // desyatichnoe hex=0100, // shestnadcaterichnoe showbase=0200, // pokazat' osnovanie celogo showpoint=0400, // vydat' nuli v konce uppercase=01000, // 'E', 'X' , a ne 'e', 'x' showpos=02000, // '+' dlya polozhitel'nyh chisel // zapis' chisla tipa float: scientific=04000, // .dddddd Edd fixed=010000, // dddd.dd // sbros v vyhodnoj potok: unitbuf=020000, // posle kazhdoj operacii stdio=040000 // posle kazhdogo simvola }; //... }; Smysl flagov budet raz座asnen v posleduyushchih razdelah. Konkretnye znacheniya flagov zavisyat ot realizacii i dany zdes' tol'ko dlya togo, chtoby izbezhat' sintaksicheski nevernyh konstrukcij. Opredelenie interfejsa kak nabora flagov i operacij dlya ih ustanovki ili otmeny - eto ocenennyj vremenem, hotya i neskol'ko ustarevshij priem. Osnovnoe ego dostoinstvo v tom, chto pol'zovatel' mozhet sobrat' voedino nabor flagov, naprimer, tak: const int my_io_options = ios::left|ios::oct|ios::showpoint|ios::fixed; Takoe mnozhestvo flagov mozhno zadavat' kak parametr odnoj operacii cout.flags(my_io_options); a takzhe prosto peredavat' mezhdu funkciyami odnoj programmy: void your_function(int ios_options); void my_function() { // ... your_function(my_io_options); // ... } Mnozhestvo flagov mozhno ustanovit' s pomoshch'yu funkcii flags(), naprimer: void your_function(int ios_options) { int old_options = cout.flags(ios_options); // ... cout.flags(old_options); // reset options } Funkciya flags() vozvrashchaet staroe znachenie mnozhestva flagov. |to pozvolyaet pereustanovit' znacheniya vseh flagov, kak pokazano vyshe, a takzhe zadat' znachenie otdel'nomu flagu. Naprimer vyzov myostream.flags(myostream.flags()|ios::showpos); zastavlyaet klass myostream vydavat' polozhitel'nye chisla so znakom + i, v to zhe vremya, ne menyaet znacheniya drugih flagov. Poluchaetsya staroe znachenie mnozhestva flagov, k kotoromu dobavlyaetsya s pomoshch'yu operacii | flag showpos. Funkciya setf() delaet to zhe samoe, poetomu ekvivalentnaya zapis' imeet vid myostream.setf(ios::showpos); Posle ustanovki flag sohranyaet znachenie do yavnoj otmeny. Vse-taki upravlenie vvodom-vyvodom s pomoshch'yu ustanovki i otmeny flagov - gruboe i vedushchee k oshibkam reshenie. Esli tol'ko vy tshchatel'no ne izuchite svoe spravochnoe rukovodstvo i ne budete primenyat' flagi tol'ko v prostyh sluchayah, kak eto delaetsya v posleduyushchih razdelah, to luchshe ispol'zovat' manipulyatory (opisannye v 10.4.2.1). Priemy raboty s sostoyaniem potoka luchshe izuchit' na primere realizacii klassa, chem izuchaya interfejs klassa. 10.4.1.4 Vyvod celyh Priem zadaniya novogo znacheniya mnozhestva flagov s pomoshch'yu operacii | i funkcij flags() i setf() rabotaet tol'ko togda, kogda odin bit opredelyaet znachenie flaga. Ne takaya situaciya pri zadanii sistemy schisleniya celyh ili vida vydachi veshchestvennyh. Zdes' znachenie, opredelyayushchee vid vydachi, nel'zya zadat' odnim bitom ili kombinaciej otdel'nyh bitov. Reshenie, prinyatoe v <iostream.h>, svoditsya k ispol'zovaniyu versii funkcii setf(), rabotayushchej so vtorym "psevdoparametrom", kotoryj pokazyvaet kakoj imenno flag my hotim dobavit' k novomu znacheniyu. Poetomu obrashcheniya cout.setf(ios::oct,ios::basefield); // vos'merichnoe cout.setf(ios::dec,ios::basefield); // desyatichnoe cout.setf(ios::hex,ios::basefield); // shestnadcaterichnoe ustanovyat sistemu schisleniya, ne zatragivaya drugih komponentov sostoyaniya potoka. Esli sistema schisleniya ustanovlena, ona ispol'zuetsya do yavnoj pereustanovki, poetomu cout << 1234 << ' '; // desyatichnoe po umolchaniyu cout << 1234 << ' '; cout.setf(ios::oct,ios::basefield); // vos'merichnoe cout << 1234 << ' '; cout << 1234 << ' '; cout.setf(ios::hex,ios::basefield); // shestnadcaterichnoe cout << 1234 << ' '; cout << 1234 << ' '; napechataet 1234 1234 2322 2322 4d2 4d2 Esli poyavitsya neobhodimost' ukazyvat' sistemu schisleniya dlya kazhdogo vydavaemogo chisla, sleduet ustanovit' flag showbase. Poetomu, dobaviv pered privedennymi vyshe obrashcheniyami cout.setf(ios::showbase); my poluchim 1234 1234 02322 02322 0x4d2 0x4d2 Standartnye manipulyatory, privedennye v $$10.4.2.1, predlagayut bolee elegantnyj sposob opredeleniya sistemy schisleniya pri vyvode celyh. 10.4.1.5 Vyravnivanie polej S pomoshch'yu obrashchenij k setf() mozhno upravlyat' raspolozheniem simvolov v predelah polya: cout.setf(ios::left,ios::adjustfield); // vlevo cout.setf(ios::right,ios::adjustfield); // vpravo cout.setf(ios::internal,ios::adjustfield); // vnutrennee Budet ustanovleno vyravnivanie v pole vyvoda, opredelyaemom funkciej ios::width(), prichem ne zatragivaya drugih komponentov sostoyaniya potoka. Vyravnivanie mozhno zadat' sleduyushchim obrazom: cout.width(4); cout << '(' << -12 << ")\n"; cout.width(4); cout.setf(ios::left,ios::adjustfield); cout << '(' << -12 << ")\n"; cout.width(4); cout.setf(ios::internal,ios::adjustfield); cout << '(' << -12 << "\n"; chto vydast ( -12) (-12 ) (- 12) Esli ustanovlen flag vyravnivaniya internal (vnutrennij), to simvoly dobavlyayutsya mezhdu znakom i velichinoj. Kak vidno, standartnym yavlyaetsya vyravnivanie vpravo. 10.4.1.6 Vyvod plavayushchih chisel. Vyvod veshchestvennyh velichin takzhe upravlyaetsya s pomoshch'yu funkcij, rabotayushchih s sostoyaniem potoka. V chastnosti, obrashcheniya: cout.setf(ios::scientific,ios::floatfield); cout.setf(ios::fixed,ios::floatfield); cout.setf(0,ios::floatfield); // vernut'sya k standartnomu ustanovyat vid pechati veshchestvennyh chisel bez izmeneniya drugih komponentov sostoyaniya potoka. Naprimer: cout << 1234.56789 << '\n'; cout.setf(ios::scientific,ios::floatfield); cout << 1234.56789 << '\n'; cout.setf(ios::fixed,ios::floatfield); cout << 1234.56789 << '\n'; napechataet 1234.57 1.234568e+03 1234.567890 Posle tochki pechataetsya n cifr, kak zadaetsya v obrashchenii cout.precision(n) Po umolchaniyu n ravno 6. Vyzov funkcii precision vliyaet na vse operacii vvoda-vyvoda s veshchestvennymi do sleduyushchego obrashcheniya k precision, poetomu cout.precision(8); cout << 1234.56789 << '\n'; cout << 1234.56789 << '\n'; cout.precision(4); cout << 1234.56789 << '\n'; cout << 1234.56789 << '\n'; vydast 1234.5679 1234.5679 1235 1235 Zamet'te, chto proishodit okruglenie, a ne otbrasyvanie drobnoj chasti. Standartnye manipulyatory, vvedennye v $$10.4.2.1, predlagayut bolee elegantnyj sposob zadaniya formata vyvoda veshchestvennyh. 10.4.2 Manipulyatory K nim otnosyatsya raznoobraznye operacii, kotorye prihoditsya primenyat' srazu pered ili srazu posle operacii vvoda-vyvoda. Naprimer: cout << x; cout.flush(); cout << y; cin.eatwhite(); cin >> x; Esli pisat' otdel'nye operatory kak vyshe, to logicheskaya svyaz' mezhdu operatorami neochevidna, a esli uteryana logicheskaya svyaz', programmu trudnee ponyat'. Ideya manipulyatorov pozvolyaet takie operacii kak flush() ili eatwhite() pryamo vstavlyat' v spisok operacij vvoda-vyvoda. Rassmotrim operaciyu flush(). Mozhno opredelit' klass s operaciej operator<<(), v kotorom vyzyvaetsya flush(): class Flushtype { }; ostream& operator<<(ostream& os, Flushtype) { return flush(os); } opredelit' ob容kt takogo tipa Flushtype FLUSH; i dobit'sya vydachi bufera, vklyuchiv FLUSH v spisok ob容ktov, podlezhashchih vyvodu: cout << x << FLUSH << y << FLUSH ; Teper' ustanovlena yavnaya svyaz' mezhdu operaciyami vyvoda i sbrasyvaniya bufera. Odnako, dovol'no bystro nadoest opredelyat' klass i ob容kt dlya kazhdoj operacii, kotoruyu my hotim primenit' k potochnoj operacii vyvoda. K schast'yu, mozhno postupit' luchshe. Rassmotrim takuyu funkciyu: typedef ostream& (*Omanip) (ostream&); ostream& operator<<(ostream& os, Omanip f) { return f(os); } Zdes' operaciya vyvoda ispol'zuet parametry tipa "ukazatel' na funkciyu, imeyushchuyu argument ostream& i vozvrashchayushchuyu ostream&". Otmetiv, chto flush() est' funkciya tipa "funkciya s argumentom ostream& i vozvrashchayushchaya ostream&", my mozhem pisat' cout << x << flush << y << flush; poluchiv vyzov funkcii flush(). Na samom dele v fajle <iostream.h> funkciya flush() opisana kak ostream& flush(ostream&); a v klasse est' operaciya operator<<, kotoraya ispol'zuet ukazatel' na funkciyu, kak ukazano vyshe: class ostream : public virtual ios { // ... public: ostream& operator<<(ostream& ostream& (*)(ostream&)); // ... }; V privedennoj nizhe stroke bufer vytalkivaetsya v potok cout dvazhdy v podhodyashchee vremya: cout << x << flush << y << flush; Pohozhie opredeleniya sushchestvuyut i dlya klassa istream: istream& ws(istream& is ) { return is.eatwhite(); } class istream : public virtual ios { // ... public: istream& operator>>(istream&, istream& (*) (istream&)); // ... }; poetomu v stroke cin >> ws >> x; dejstvitel'no obobshchennye probely budut ubrany do popytki chteniya v x. Odnako, poskol'ku po umolchaniyu dlya operacii >> probely "s容dayutsya" i tak, dannoe primenenie ws() izbytochno. Nahodyat primenenie i manipulyatory s parametrami. Naprimer, mozhet poyavit'sya zhelanie s pomoshch'yu cout << setprecision(4) << angle; napechatat' znachenie veshchestvennoj peremennoj angle s tochnost'yu do chetyreh znakov posle tochki. Dlya etogo nuzhno umet' vyzyvat' funkciyu, kotoraya ustanovit znachenie peremennoj, upravlyayushchej v potoke tochnost'yu veshchestvennyh. |to dostigaetsya, esli opredelit' setprecision(4) kak ob容kt, kotoryj mozhno "vyvodit'" s pomoshch'yu operator<<(): class Omanip_int { int i; ostream& (*f) (ostream&,int); public: Omanip_int(ostream& (*ff) (ostream&,int), int ii) : f(ff), i(ii) { } friend ostream& operator<<(ostream& os, Omanip& m) { return m.f(os,m.i); } }; Konstruktor Omanip_int hranit svoi argumenty v i i f, a s pomoshch'yu operator<< vyzyvaetsya f() s parametrom i. CHasto ob容kty takih klassov nazyvayut ob容kt-funkciya. CHtoby rezul'tat stroki cout << setprecision(4) << angle byl takim, kak my hoteli, neobhodimo chtoby obrashchenie setprecision(4) sozdavalo bezymyannyj ob容kt klassa Omanip_int, soderzhashchij znachenie 4 i ukazatel' na funkciyu, kotoraya ustanavlivaet v potoke ostream znachenie peremennoj, zadayushchej tochnost' veshchestvennyh: ostream& _set_precision(ostream&,int); Omanip_int setprecision(int i) { return Omanip_int(&_set_precision,i); } Uchityvaya sdelannye opredeleniya, operator<<() privedet k vyzovu precision(i). Utomitel'no opredelyat' klassy napodobie Omanip_int dlya vseh tipov argumentov, poetomu opredelim shablon tipa: template<class T> class OMANIP { T i; ostream& (*f) (ostream&,T); public: OMANIP(ostream (*ff) (ostream&,T), T ii) : f(ff), i(ii) { } friend ostream& operator<<(ostream& os, OMANIP& m) { return m.f(os,m.i) } }; S pomoshch'yu OMANIP primer s ustanovkoj tochnosti mozhno sokratit' tak: ostream& precision(ostream& os,int) { os.precision(i); return os; } OMANIP<int> setprecision(int i) { return OMANIP<int>(&precision,i); } V fajle <iomanip.h> mozhno najti shablon tipa OMANIP, ego dvojnik dlya istream - shablon tipa SMANIP, a SMANIP - dvojnik dlya ioss. Nekotorye iz standartnyh manipulyatorov, predlagaemyh potochnoj bibliotekoj, opisany nizhe. Otmetim,chto programmist mozhet opredelit' novye neobhodimye emu manipulyatory, ne zatragivaya opredelenij istream, ostream, OMANIP ili SMANIP. Ideyu manipulyatorov predlozhil A. Kenig. Ego vdohnovili procedury razmetki (layout ) sistemy vvoda-vyvoda Algola68. Takaya tehnika imeet mnogo interesnyh prilozhenij pomimo vvoda-vyvoda. Sut' ee v