tom, chto sozdaetsya ob容kt, kotoryj mozhno peredavat' kuda ugodno i kotoryj ispol'zuetsya kak funkciya. Peredacha ob容kta yavlyaetsya bolee gibkim resheniem, poskol'ku detali vypolneniya chastichno opredelyayutsya sozdatelem ob容kta, a chastichno tem, kto k nemu obrashchaetsya. 10.4.2.1 Standartnye manipulyatory vvoda-vyvoda |to sleduyushchie manipulyatory: // Simple manipulators: ios& oct(ios&); // v vos'merichnoj zapisi ios& dec(ios&); // v desyatichnoj zapisi ios& hex(ios&); // v shestnadcaterichnoj zapisi ostream& endl(ostream&); // dobavit' '\n' i vyvesti ostream& ends(ostream&); // dobavit' '\0' i vyvesti ostream& flush(ostream&); // vydat' potok istream& ws(istream&); // udalit' obobshchennye probely // Manipulyatory imeyut parametry: SMANIP<int> setbase(int b); SMANIP<int> setfill(int f); SMANIP<int> setprecision(int p); SMANIP<int> setw(int w); SMANIP<long> resetiosflags(long b); SMANIP<long> setiosflags(long b); Naprimer, cout << 1234 << ' ' << hex << 1234 << ' ' << oct << 1234 << endl; napechataet 1234 4d2 2322 i cout << setw(4) << setfill('#') << '(' << 12 << ")\n"; cout << '(' << 12 << ")\n"; napechataet (##12) (12) Ne zabud'te vklyuchit' fajl <iomanip.h>, esli ispol'zuete manipulyatory s parametrami. 10.4.3 CHleny ostream V klasse ostream est' lish' neskol'ko funkcij dlya upravleniya vyvodom, bol'shaya chast' takih funkcij nahoditsya v klasse ios. class ostream : public virtual ios { //... public: ostream& flush(); ostream& seekp(streampos); ostream& seekp(streamoff, seek_dir); streampos tellp(); //... }; Kak my uzhe govorili, funkciya flush() opustoshaet bufer v vyhodnoj potok. Ostal'nye funkcii ispol'zuyutsya dlya pozicionirovaniya v ostream pri zapisi. Okonchanie na bukvu p ukazyvaet, chto imenno poziciya ispol'zuetsya pri vydache simvolov v zadannyj potok. Konechno eti funkcii imeyut smysl, tol'ko esli potok prisoedinen k chemu-libo, chto dopuskaet pozicionirovanie, naprimer fajl. Tip streampos predstavlyaet poziciyu simvola v fajle, a tip streamoff predstavlyaet smeshchenie otnositel'no pozicii, zadannoj seek_dir. Vse oni opredeleny v klasse ios: class ios { //... enum seek_dir { beg=0, // ot nachala fajla cur=1, // ot tekushchej pozicii v fajle end=2 // ot konca fajla }; //... }; Pozicii v potoke otschityvayutsya ot 0, kak esli by fajl byl massivom iz n simvolov: char file[n-1]; i esli fout prisoedineno k file, to fout.seek(10); fout<<'#'; pomestit # v file[10]. 10.4.4 CHleny istream Kak i dlya ostream, bol'shinstvo funkcij formatirovaniya i upravleniya vvodom nahoditsya ne v klasse iostream, a v bazovom klasse ios. class istream : public virtual ios { //... public: int peek() istream& putback(char c); istream& seekg(streampos); istream& seekg(streamoff, seek_dir); streampos tellg(); //... }; Funkcii pozicionirovaniya rabotayut kak i ih dvojniki iz ostream. Okonchanie na bukvu g pokazyvaet, chto imenno poziciya ispol'zuetsya pri vvode simvolov iz zadannogo potoka. Bukvy p i g nuzhny, poskol'ku my mozhem sozdat' proizvodnyj klass iostreams iz klassov ostream i istream, i v nem neobhodimo sledit' za poziciyami vvoda i vyvoda. S pomoshch'yu funkcii peek() programma mozhet uznat' sleduyushchij simvol, podlezhashchij vvodu, ne zatragivaya rezul'tata posleduyushchego chteniya. S pomoshch'yu funkcii putback(), kak pokazano v $$10.3.3, mozhno vernut' nenuzhnyj simvol nazad v potok, chtoby on byl prochitan v drugoe vremya. 10.5 Fajly i potoki Nizhe privedena programma kopirovaniya odnogo fajla v drugoj. Imena fajlov berutsya iz komandnoj stroki programmy: #include <fstream.h> #include <libc.h> void error(char* s, char* s2 ="") { cerr << s << ' ' << s2 << '\n'; exit(1); } int main(int argc, char* argv[]) { if (argc != 3) error("wrong number of arguments"); ifstream from(argv[1]); if (!from) error("cannot open input file",argv[1]); ostream to(argv[2]); if (!to) error("cannot open output file",argv[2]); char ch; while (from.get(ch)) to.put(ch); if (!from.eof() || to.bad()) error("something strange happened"); return 0; } Dlya otkrytiya vyhodnogo fajla sozdaetsya ob容kt klassa ofstream - vyhodnoj potok fajla, ispol'zuyushchij v kachestve argumenta imya fajla. Analogichno, dlya otkrytiya vhodnogo fajla sozdaetsya ob容kt klassa ifstream - vhodnoj fajlovyj potok, takzhe ispol'zuyushchij v kachestve argumenta imya fajla. V oboih sluchayah sleduet proverit' sostoyanie sozdannogo ob容kta, chtoby ubedit'sya v uspeshnom otkrytii fajla, a esli eto ne tak, operacii zavershatsya ne uspeshno, no korrektno. Po umolchaniyu ifstream vsegda otkryvaetsya na chtenie, a ofstream otkryvaetsya na zapis'. V ostream i v istream mozhno ispol'zovat' neobyazatel'nyj vtoroj argument, ukazyvayushchij inye rezhimy otkrytiya: class ios { public: //... enum open_mode { in=1, // otkryt' na chtenie out=2, // otkryt' kak vyhodnoj ate=4, // otkryt' i peremestit'sya v konec fajla app=010, // dobavit' trunc=020, // sokratit' fajl do nulevoj dliny nocreate=040, // neudacha, esli fajl ne sushchestvuet noreplace=0100 // neudacha, esli fajl sushchestvuet }; //... }; Nastoyashchie znacheniya dlya open_mode i ih smysl veroyatno budut zaviset' ot realizacii. Bud'te dobry, za detalyami obratites' k rukovodstvu po vashej biblioteke ili eksperimentirujte. Privedennye kommentarii mogut proyasnit' ih naznachenie. Naprimer, mozhno otkryt' fajl s usloviem, chto operaciya otkrytiya ne vypolnitsya, esli fajl uzhe ne sushchestvuet: void f() { ofstream mystream(name,ios::out|ios::nocreate); if (ofstream.bad()) { //... } //... } Takzhe mozhno otkryt' fajl srazu na chtenie i zapis': fstream dictionary("concordance", ios::in|ios::out); Vse operacii, dopustimye dlya ostream i ostream, mozhno primenyat' k fstream. Na samom dele, klass fstream yavlyaetsya proizvodnym ot iostream, kotoryj yavlyaetsya, v svoyu ochered', proizvodnym ot istream i ostream. Prichina, po kotoroj informaciya po buferizacii i formatirovaniyu dlya ostream i istream nahoditsya v virtual'nom bazovom klasse ios, v tom, chtoby zastavit' dejstvovat' vsyu etu posledovatel'nost' proizvodnyh klassov. Po etoj zhe prichine operacii pozicionirovaniya v istream i ostream imeyut raznye imena - seekp() i seekg(). V iostream est' otdel'nye pozicii dlya chteniya i zapisi. 10.5.1 Zakrytie potokov Fajl mozhet byt' zakryt yavno, esli vyzvat' close() dlya ego potoka: mystream.close(); No eto neyavno delaet destruktor potoka, tak chto yavnyj vyzov close() mozhet ponadobit'sya, esli tol'ko fajl nuzhno zakryt' do dostizheniya konca oblasti opredelennosti potoka. Zdes' voznikaet vopros, kak realizaciya mozhet obespechit' sozdanie predopredelennyh potokov cout, cin i cerr do ih pervogo ispol'zovaniya i zakrytie ih tol'ko posle poslednego ispol'zovaniya. Konechno, raznye realizacii biblioteki potokov iz <iostream.h> mogut po-raznomu reshat' etu zadachu. V konce koncov, reshenie - eto prerogativa realizacii, i ono dolzhno byt' skryto ot pol'zovatelya. Zdes' privoditsya tol'ko odin sposob, primenennyj tol'ko v odnoj realizacii, no on dostatochno obshchij, chtoby garantirovat' pravil'nyj poryadok sozdaniya i unichtozheniya global'nyh ob容ktov razlichnyh tipov. Osnovnaya ideya v tom, chtoby opredelit' vspomogatel'nyj klass, kotoryj po suti sluzhit schetchikom, sledyashchim za tem, skol'ko raz <iostream.h> byl vklyuchen v razdel'no kompilirovavshiesya programmnye fajly: class Io_init { static int count; //... public: Io_init(); ^Io_init(); }; static Io_init io_init ; Dlya kazhdogo programmnogo fajla opredelen svoj ob容kt s imenem io_init. Konstruktor dlya ob容ktov io_init ispol'zuet Io_init::count kak pervyj priznak togo, chto dejstvitel'naya inicializaciya global'nyh ob容ktov potokovoj biblioteki vvoda-vyvoda sdelana v tochnosti odin raz: Io_init::Io_init() { if (count++ == 0) { // inicializirovat' cout // inicializirovat' cerr // inicializirovat' cin // i t.d. } } Obratno, destruktor dlya ob容ktov io_init ispol'zuet Io_count, kak poslednee ukazanie na to, chto vse potoki zakryty: Io_init::^Io_init() { if (--count == 0) { // ochistit' cout (sbros, i t.d.) // ochistit' cerr (sbros, i t.d.) // ochistit' cin // i t.d. } } |to obshchij priem raboty s bibliotekami, trebuyushchimi inicializacii i udaleniya global'nyh ob容ktov. Vpervye v S++ ego primenil D. SHvarc. V sistemah, gde pri vypolnenii vse programmy razmeshchayutsya v osnovnoj pamyati, dlya etogo priema net pomeh. Esli eto ne tak, to nakladnye rashody, svyazannye s vyzovom v pamyat' kazhdogo programmnogo fajla dlya vypolneniya funkcij inicializacii, budut zametny. Kak vsegda, luchshe, po vozmozhnosti, izbegat' global'nyh ob容ktov. Dlya klassov, v kotoryh kazhdaya operaciya znachitel'na po ob容mu vypolnyaemoj raboty, chtoby garantirovat' inicializaciyu, bylo by razumno proveryat' takie pervye priznaki (napodobie Io_init::count) pri kazhdoj operacii. Odnako, dlya potokov takoj podhod byl by izlishne rastochitel'nym. 10.5.2 Strokovye potoki Kak bylo pokazano, potok mozhet byt' privyazan k fajlu, t.e. massivu simvolov, hranyashchemusya ne v osnovnoj pamyati, a, naprimer, na diske. Tochno tak zhe potok mozhno privyazat' k massivu simvolov v osnovnoj pamyati. Naprimer, mozhno vospol'zovat'sya vyhodnym strokovym potokom ostrstream dlya formatirovaniya soobshchenij, ne podlezhashchih nemedlennoj pechati: char* p = new char[message_size]; ostrstream ost(p,message_size); do_something(arguments,ost); display(p); S pomoshch'yu standartnyh operacij vyvoda funkciya do_something mozhet pisat' v potok ost, peredavat' ost podchinyayushchimsya ej funkciyam i t.p. Kontrol' perepolneniya ne nuzhen, poskol'ku ost znaet svoj razmer i pri zapolnenii perejdet v sostoyanie, opredelyaemoe fail(). Zatem funkciya display mozhet poslat' soobshchenie v "nastoyashchij" vyhodnoj potok. Takoj priem naibolee podhodit v teh sluchayah, kogda okonchatel'naya operaciya vyvoda prednaznachena dlya zapisi na bolee slozhnoe ustrojstvo, chem tradicionnoe, orientirovannoe na posledovatel'nost' strok, vyvodnoe ustrojstvo. Naprimer, tekst iz ost mozhet byt' pomeshchen v fiksirovannuyu oblast' na ekrane. Analogichno, istrstream yavlyaetsya vvodnym strokovym potokom, chitayushchim iz posledovatel'nosti simvolov, zakanchivayushchejsya nulem: void word_per_line(char v[], int sz) /* pechatat' "v" razmerom "sz" po odnomu slovu v stroke */ { istrstream ist(v,sz); // sozdat' istream dlya v char b2[MAX]; // dlinnee samogo dlinnogo slova while (ist>>b2) cout <<b2 << "\n"; } Zavershayushchij nul' schitaetsya koncom fajla. Strokovye potoki opisany v fajle <strstream.h>. 10.5.3 Buferizaciya Vse operacii vvoda-vyvoda byli opredeleny bez vsyakoj svyazi s tipom fajla, no nel'zya odinakovo rabotat' so vsemi ustrojstvami bez ucheta algoritma buferizacii. Ochevidno, chto potoku ostream, privyazannomu k stroke simvolov, nuzhen ne takoj bufer, kak ostream, privyazannomu k fajlu. Takie voprosy reshayutsya sozdaniem vo vremya inicializacii raznyh buferov dlya potokov raznyh tipov. No sushchestvuet tol'ko odin nabor operacij nad etimi tipami buferov, poetomu v ostream net funkcij, kod kotoryh uchityvaet razlichie buferov. Odnako, funkcii, sledyashchie za perepolneniem i obrashcheniem k pustomu buferu, yavlyayutsya virtual'nymi. |to horoshij primer primeneniya virtual'nyh funkcij dlya edinoobraznoj raboty s ekvivalentnymi logicheski, no razlichno realizovannymi strukturami, i oni vpolne spravlyayutsya s trebuemymi algoritmami buferizacii. Opisanie bufera potoka v fajle <iostream.h> mozhet vyglyadet' sleduyushchim obrazom: class streambuf { // upravlenie buferom potoka protected: char* base; // nachalo bufera char* pptr; // sleduyushchij svobodnyj bajt char* gptr; // sleduyushchij zapolnennyj bajt char* eptr; // odin iz ukazatelej na konec bufera char alloc; // bufer, razmeshchennyj s pomoshch'yu "new" //... // Opustoshit' bufer: // Vernut' EOF pri oshibke, 0 - udacha virtual int overflow(int c = EOF); // Zapolnit' bufer: // Vernut' EOF v sluchae oshibki ili konca vhodnogo potoka, // inache vernut' ocherednoj simvol virtual int underflow(); //... public: streambuf(); streambuf(char* p, int l); virtual ~streambuf(); int snextc() // poluchit' ocherednoj simvol { return (++gptr==pptr) ? underflow() : *gptr&0377; } int allocate(); // otvesti pamyat' pod bufer //... }; Podrobnosti realizacii klassa streambuf privedeny zdes' tol'ko dlya polnoty predstavleniya. Ne predpolagaetsya, chto est' obshchedostupnye realizacii, ispol'zuyushchie imenno eti imena. Obratite vnimanie na opredelennye zdes' ukazateli, upravlyayushchie buferom; s ih pomoshch'yu prostye posimvol'nye operacii s potokom mozhno opredelit' maksimal'no effektivno (i prichem odnokratno) kak funkcii-podstanovki. Tol'ko funkcii overflow() i underflow() trebuet svoej realizacii dlya kazhdogo algoritma buferizacii, naprimer: class filebuf : public streambuf { protected: int fd; // deskriptor fajla char opened; // priznak otkrytiya fajla public: filebuf() { opened = 0; } filebuf(int nfd, char* p, int l) : streambuf(p,l) { /* ... */ } ~filebuf() { close(); } int overflow(int c=EOF); int underflow(); filebuf* open(char *name, ios::open_mode om); int close() { /* ... */ } //... }; int filebuf::underflow() // zapolnit' bufer iz "fd" { if (!opened || allocate()==EOF) return EOF; int count = read(fd, base, eptr-base); if (count < 1) return EOF; gptr = base; pptr = base + count; return *gptr & 0377; // &0377 predotvrashchaet razmnozhenie znaka } Za dal'nejshimi podrobnostyami obratites' k rukovodstvu po realizacii klassa streambuf. 10.6 Vvod-vyvod v S Poskol'ku tekst programm na S i na S++ chasto putayut, to putayut inogda i potokovyj vvod-vyvod S++ i funkcii vvoda-vyvoda semejstva printf dlya yazyka S. Dalee, t.k. S-funkcii mozhno vyzyvat' iz programmy na S++, to mnogie predpochitayut ispol'zovat' bolee znakomye funkcii vvoda-vyvoda S. Po etoj prichine zdes' budet dana osnova funkcij vvoda-vyvoda S. Obychno operacii vvoda-vyvoda na S i na S++ mogut idti po ocheredi na urovne strok. Peremeshivanie ih na urovne posimvol'nogo vvoda-vyvoda vozmozhno dlya nekotoryh realizacij, no takaya programma mozhet byt' neperenosimoj. Nekotorye realizacii potokovoj biblioteki S++ pri dopushchenii vvoda-vyvoda na S trebuyut vyzova staticheskoj funkcii-chlena ios::sync_with_stdio(). V obshchem, potokovye funkcii vyvoda imeyut pered standartnoj funkciej S printf() to preimushchestvo, chto potokovye funkcii obladayut opredelennoj tipovoj nadezhnost'yu i edinoobrazno opredelyayut vyvod ob容ktov predopredelennogo i pol'zovatel'skogo tipov. Osnovnaya funkciya vyvoda S est' int printf(const char* format, ...) i ona vyvodit proizvol'nuyu posledovatel'nost' parametrov v formate, zadavaemom strokoj formatirovaniya format. Stroka formatirovaniya sostoit iz ob容ktov dvuh tipov: prostye simvoly, kotorye prosto kopiruyutsya v vyhodnoj potok, i specifikacii preobrazovanij, kazhdaya iz kotoryh preobrazuet i pechataet ocherednoj parametr. Kazhdaya specifikaciya preobrazovaniya nachinaetsya s simvola %, naprimer printf("there were %d members present.",no_of_members); Zdes' %d ukazyvaet, chto no_of_members sleduet schitat' celym i pechatat' kak sootvetstvuyushchuyu posledovatel'nost' desyatichnyh cifr. Esli no_of_members==127, to budet napechatano there were 127 members present. Nabor specifikacij preobrazovanij dostatochno bol'shoj i obespechivaet bol'shuyu gibkost' pechati. Za simvolom % mozhet sledovat': - neobyazatel'nyj znak minus, zadayushchij vyravnivanie vlevo v ukazannom pole dlya preobrazovannogo znacheniya; d neobyazatel'naya stroka cifr, zadayushchaya shirinu polya; esli v preobrazovannom znachenii men'she simvolov, chem shirina stroki, to ono dopolnitsya do shiriny polya probelami sleva (ili sprava, esli dana specifikaciya vyravnivaniya vlevo); esli stroka shiriny polya nachinaetsya s nulya, to dopolnenie budet provoditsya nulyami, a ne probelami; . neobyazatel'nyj simvol tochka sluzhit dlya otdeleniya shiriny polya ot posleduyushchej stroki cifr; d neobyazatel'naya stroka cifr, zadayushchaya tochnost', kotoraya opredelyaet chislo cifr posle desyatichnoj tochki dlya znachenij v specifikaciyah e ili f, ili zhe zadaet maksimal'noe chislo pechataemyh simvolov stroki; * dlya zadaniya shiriny polya ili tochnosti mozhet ispol'zovat'sya * vmesto stroki cifr. V etom sluchae dolzhen byt' parametr celogo tipa, kotoryj soderzhit znachenie shiriny polya ili tochnosti; h neobyazatel'nyj simvol h ukazyvaet, chto posleduyushchaya specifikaciya d, o, x ili u otnositsya k parametru tipa korotkoe celoe; l neobyazatel'nyj simvol l ukazyvaet, chto posleduyushchaya specifikaciya d, o, x ili u otnositsya k parametru tipa dlinnoe celoe; % oboznachaet, chto nuzhno napechatat' sam simvol %; parametr ne nuzhen; c simvol, ukazyvayushchij tip trebuemogo preobrazovaniya. Simvoly preobrazovaniya i ih smysl sleduyushchie: d Celyj parametr vydaetsya v desyatichnoj zapisi; o Celyj parametr vydaetsya v vos'merichnoj zapisi; x Celyj parametr vydaetsya v shestnadcaterichnoj zapisi; f Veshchestvennyj ili s dvojnoj tochnost'yu parametr vydaetsya v desyatichnoj zapisi vida [-]ddd.ddd, gde chislo cifr posle tochki ravno specifikacii tochnosti dlya parametra. Esli tochnost' ne zadana, pechataetsya shest' cifr; esli yavno zadana tochnost' 0, tochka i cifry posle nee ne pechatayutsya; e Veshchestvennyj ili s dvojnoj tochnost'yu parametr vydaetsya v desyatichnoj zapisi vida [-]d.ddde+dd; zdes' odna cifra pered tochkoj, a chislo cifr posle tochki ravno specifikacii tochnosti dlya parametra; esli ona ne zadana pechataetsya shest' cifr; g Veshchestvennyj ili s dvojnoj tochnost'yu parametr pechataetsya po toj specifikacii d, f ili e, kotoraya daet bol'shuyu tochnost' pri men'shej shirine polya; c Simvol'nyj parametr pechataetsya. Nulevye simvoly ignoriruyutsya; s Parametr schitaetsya strokoj (simvol'nyj ukazatel'), i pechatayutsya simvoly iz stroki do nulevogo simvola ili do dostizheniya chisla simvolov, ravnogo specifikacii tochnosti; no, esli tochnost' ravna 0 ili ne ukazana, pechatayutsya vse simvoly do nulevogo; p Parametr schitaetsya ukazatelem i ego vid na pechati zavisit ot realizacii; u Bezznakovyj celyj parametr pechataetsya v desyatichnoj zapisi. Nesushchestvuyushchee pole ili pole s shirinoj, men'shej real'noj, privedet k usecheniyu polya. Dopolnenie probelami proishodit, esli tol'ko specifikaciya shiriny polya bol'she real'noj shiriny. Nizhe priveden bolee slozhnyj primer: char* src_file_name; int line; char* line_format = "\n#line %d \"%s\"\n"; main() { line = 13; src_file_name = "C++/main.c"; printf("int a;\n"); printf(line_format,line,src_file_name); printf("int b;\n"); } v kotorom pechataetsya int a; #line 13 "C++/main.c" int b; Ispol'zovanie printf() nenadezhno v tom smysle, chto net nikakogo kontrolya tipov. Tak, nizhe priveden izvestnyj sposob polucheniya neozhidannogo rezul'tata - pechati musornogo znacheniya ili chego pohuzhe: char x; // ... printf("bad input char: %s",x); Odnako, eti funkcii obespechivayut bol'shuyu gibkost' i znakomy programmiruyushchim na S. Kak obychno, getchar() pozvolyaet znakomym sposobom chitat' simvoly iz vhodnogo potoka: int i;: while ((i=getchar())!=EOF) { // simvol'nyj vvod C // ispol'zuem i } Obratite vnimanie: chtoby bylo zakonnym sravnenie s velichinoj EOF tipa int pri proverke na konec fajla, rezul'tat getchar() nado pomeshchat' v peremennuyu tipa int, a ne char. Za podrobnostyami o vvode-vyvode na S otsylaem k vashemu rukovodstvu po S ili knige Kernigana i Ritchi "YAzyk programmirovaniya S". 10.7 Uprazhneniya 1. (*1.5) CHitaya fajl veshchestvennyh chisel, sostavlyat' iz par prochitannyh chisel kompleksnye chisla, zapisat' kompleksnye chisla. 2. (*1.5) Opredelit' tip name_and_address (tip_i_adres). Opredelit' dlya nego << i >>. Napisat' programmu kopirovaniya ob容ktov potoka name_and_address. 3. (*2) Razrabotat' neskol'ko funkcij dlya zaprosa i chteniya dannyh raznyh tipov. Predlozheniya: celoe, veshchestvennoe chislo, imya fajla, pochtovyj adres, data, lichnaya informaciya, i t.p. Popytajtes' sdelat' ih ustojchivymi k oshibkam. 4. (*1.5) Napishite programmu, kotoraya pechataet: (1) strochnye bukvy, (2) vse bukvy, (3) vse bukvy i cifry, (4) vse simvoly, vhodyashchie v identifikator v vashej versii S++, (5) vse znaki punktuacii, (6) celye znacheniya vseh upravlyayushchih simvolov, (7) vse obobshchennye probely, (8) celye znacheniya vseh obobshchennyh probelov, i, nakonec, (9) vse izobrazhaemye simvoly. 5. (*4) Realizujte standartnuyu biblioteku vvoda-vyvoda S (<stdio.h>) s pomoshch'yu standartnoj biblioteki vvoda-vyvoda S++ (<iostream.h>). 6. (*4) Realizujte standartnuyu biblioteku vvoda-vyvoda S++ (<iostream.h>) s pomoshch'yu standartnoj biblioteki vvoda-vyvoda S (<stdio.h>). 7. (*4) Realizujte biblioteki S i S++ tak, chtoby ih mozhno bylo ispol'zovat' odnovremenno. 8. (*2) Realizujte klass, dlya kotorogo operaciya [] peregruzhena tak, chtoby obespechit' proizvol'noe chtenie simvolov iz fajla. 9. (*3) Povtorite uprazhnenie 8, no dobejtes', chtoby operaciya [] byla primenima dlya chteniya i dlya zapisi. Podskazka: pust' [] vozvrashchaet ob容kt "deskriptor tipa", dlya kotorogo prisvaivanie oznachaet: prisvoit' cherez deskriptor fajlu, a neyavnoe privedenie k tipu char oznachaet chtenie fajla po deskriptoru. 10.(*2) Povtorite uprazhnenie 9, pozvolyaya operacii [] indeksirovat' ob容kty proizvol'nyh tipov, a ne tol'ko simvoly. 11.(*3.5) Produmajte i realizujte operaciyu formatnogo vvoda. Ispol'zujte dlya zadaniya formata stroku specifikacij kak v printf(). Dolzhna byt' vozmozhnost' popytok primeneniya neskol'kih specifikacij dlya odnogo vvoda, chtoby najti trebuemyj format. Klass formatnogo vvoda dolzhen byt' proizvodnym klassa istream. 12.(*4) Pridumajte (i realizujte) luchshie formaty vvoda. 13.(**2) Opredelite dlya vyvoda manipulyator based s dvumya parametrami: sistema schisleniya i celoe znachenie, i pechatajte celoe v predstavlenii, opredelyaemom sistemoj schisleniya. Naprimer, based(2,9) napechataet 1001. 14.(**2) Napishite "miniatyurnuyu" sistemu vvoda-vyvoda, kotoraya realizuet klassy istream, ostream, ifstream, ofstream i predostavlyaet funkcii, takie kak operator<<() i operator>>() dlya celyh, i operacii, takie kak open() i close() dlya fajlov. Ispol'zujte isklyuchitel'nye situacii, a ne peremennye sostoyaniya, dlya soobshcheniya ob oshibkah. 15.(**2) Napishite manipulyator, kotoryj vklyuchaet i otklyuchaet eho simvola.  * PROEKTIROVANIE I RAZVITIE "Serebryanoj puli ne sushchestvuet." - F. Bruks V etoj glave obsuzhdayutsya podhody k razrabotke programmnogo obespecheniya. Obsuzhdenie zatragivaet kak tehnicheskie, tak i sociologicheskie aspekty processa razvitiya programmnogo obespecheniya. Programma rassmatrivaetsya kak model' real'nosti, v kotoroj kazhdyj klass predstavlyaet opredelennoe ponyatie. Klyuchevaya zadacha proektirovaniya sostoit v opredelenii dostupnoj i zashchishchennoj chastej interfejsa klassa, ishodya iz kotoryh opredelyayutsya razlichnye chasti programmy. Opredelenie etih interfejsov est' iterativnyj process, obychno trebuyushchij eksperimentirovaniya. Upor delaetsya na vazhnoj roli proektirovaniya i organizacionnyh faktorov v processe razvitiya programmnogo obespecheniya. 11.1 Vvedenie Sozdanie lyuboj netrivial'noj programmnoj sistemy - slozhnaya i chasto vymatyvayushchaya zadacha. Dazhe dlya otdel'nogo programmista sobstvenno zapis' operatorov programmy est' tol'ko chast' vsej raboty. Obychno analiz vsej zadachi, proektirovanie programmy v celom, dokumentaciya, testirovanie, soprovozhdenie i upravlenie vsem etim zatmevaet zadachu napisaniya i otladki otdel'nyh chastej programmy. Konechno, mozhno vse eti vidy deyatel'nosti oboznachit' kak "programmirovanie" i zatem vpolne obosnovanno utverzhdat': "YA ne proektiruyu, ya tol'ko programmiruyu". No kak by ne nazyvalis' otdel'nye vidy deyatel'nosti, byvaet inogda vazhno sosredotochit'sya na nih po otdel'nosti, tak zhe kak inogda byvaet vazhno rassmotret' ves' process v celom. Stremyas' poskoree dovesti sistemu do postavki, nel'zya upuskat' iz vida ni detali, ni kartinu v celom, hotya dovol'no chasto proishodit imenno eto. |ta glava sosredotochena na teh chastyah processa razvitiya programmy, kotorye ne svyazany s napisaniem i otladkoj otdel'nyh programmnyh fragmentov. Obsuzhdenie zdes' menee tochnoe i detal'noe, chem vo vseh ostal'nyh chastyah knigi, gde rassmatrivayutsya konkretnye cherty yazyka ili opredelennye priemy programmirovaniya. |to neizbezhno, poskol'ku net gotovyh receptov sozdaniya horoshih programm. Detal'nye recepty "kak" mogut sushchestvovat' tol'ko dlya opredelennyh, horosho razrabotannyh oblastej primeneniya, no ne dlya dostatochno shirokih oblastej prilozheniya. V programmirovanii ne sushchestvuet zamenitelej razuma, opyta i vkusa. Sledovatel'no, v etoj glave vy najdete tol'ko obshchie rekomendacii, al'ternativnye podhody i ostorozhnye vyvody. Slozhnost' dannoj tematiki svyazana s abstraktnoj prirodoj programm i tem faktom, chto priemy, primenimye dlya nebol'shih proektov (skazhem, programma v 10000 strok, sozdannaya odnim ili dvumya lyud'mi), ne rasprostranyayutsya na srednie ili bol'shie proekty. Po etoj prichine inogda my privodim primery iz menee abstraktnyh inzhenernyh disciplin, a ne tol'ko iz programmirovaniya. Ne preminem napomnit', chto "dokazatel'stvo po analogii" yavlyaetsya moshennichestvom, i analogii sluzhat zdes' tol'ko v kachestve primera. Ponyatiya proektirovaniya, formuliruemye s pomoshch'yu opredelennyh konstrukcij S++, i poyasnyaemye primerami, my budem obsuzhdat' v glavah 12 i 13. Predlozhennye v etoj glave rekomendacii, otrazhayutsya kak v samom yazyke S++, tak i v reshenii konkretnyh programmnyh zadach po vsej knige. Snova napomnim, chto v silu chrezvychajnogo raznoobraziya oblastej primeneniya, programmistov i sredy, v kotoroj razvivaetsya programmnaya sistema, nel'zya ozhidat', chto kazhdyj vyvod, sdelannyj zdes', budet pryamo primenim dlya vashej zadachi. |ti vyvody primenimy vo mnogih samyh raznyh sluchayah, no ih nel'zya schitat' universal'nymi zakonami. Smotrite na nih so zdorovoj dolej skepticizma. YAzyk S++ mozhno prosto ispol'zovat' kak luchshij variant S. Odnako, postupaya tak, my ne ispol'zuem naibolee moshchnye vozmozhnosti S++ i opredelennye priemy programmirovaniya na nem, tak chto realizuem lish' maluyu dolyu potencial'nyh dostoinstv S++. V etoj glave izlagaetsya takoj podhod k proektirovaniyu, kotoryj pozvolyaet polnost'yu ispol'zovat' vozmozhnosti abstraktnyh dannyh i sredstva ob容ktnogo programmirovaniya S++. Takoj podhod obychno nazyvayut ob容ktno-orientirovannym proektirovaniem. V glave 12 obsuzhdayutsya osnovnye priemy programmirovaniya na S++, tam zhe soderzhitsya predosterezhenie ot somnitel'nyh idej, chto est' tol'ko odin "pravil'nyj" sposob ispol'zovaniya S++, i chto dlya polucheniya maksimal'nogo vyigrysha sleduet vsyakoe sredstvo S++ primenyat' v lyuboj programme ($$12.1). Ukazhem nekotorye osnovnye principy, rassmatrivaemye v etoj glave: - iz vseh voprosov, svyazannyh s processom razvitiya programmnogo obespecheniya, samyj vazhnyj - chetko soznavat', chto sobstvenno vy pytaetes' sozdat'. - Uspeshnyj process razvitiya programmnogo obespecheniya - eto dli- tel'nyj process. - Sistemy, kotorye my sozdaem, stremyatsya k predelu slozhnosti po otnosheniyu kak k samim sozdatelyam, tak i ispol'zuemym sredstvam. - |ksperiment yavlyaetsya neobhodimoj chast'yu proekta dlya razrabotki vseh netrivial'nyh programmnyh sistem. - Proektirovanie i programmirovanie - eto iterativnye processy. - Razlichnye stadii proekta programmnogo obespecheniya, takie kak: proektirovanie, programmirovanie i testirovanie - nevozmozhno strogo razdelit'. - Proektirovanie i programmirovanie nel'zya rassmatrivat' v otryve ot voprosov upravleniya etimi vidami deyatel'nosti. Nedoocenit' lyuboj iz etih principov ochen' legko, no obychno nakladno. V to zhe vremya trudno voplotit' eti abstraktnye idei na praktike. Zdes' neobhodim opredelennyj opyt. Podobno postroeniyu lodki, ezde na velosipede ili programmirovaniyu proektirovanie - eto iskusstvo, kotorym nel'zya ovladet' tol'ko s pomoshch'yu teoreticheskih zanyatij. Mozhet byt' vse eti emkie principy mozhno szhat' v odin: proektirovanie i programmirovanie - vidy chelovecheskoj deyatel'nosti; zabud' pro eto - i vse propalo. Slishkom chasto my zabyvaem pro eto i rassmatrivaem process razvitiya programmnogo obespecheniya prosto kak "posledovatel'nost' horosho opredelennyh shagov, na kazhdom iz kotoryh po zadannym pravilam proizvodyatsya nekotorye dejstviya nad vhodnymi dannymi, chtoby poluchit' trebuemyj rezul'tat". Sam stil' predydushchego predlozheniya vydaet prisutstvie chelovecheskoj prirody! |ta glava otnositsya k proektam, kotorye mozhno schitat' chestolyubivymi, esli uchityvat' resursy i opyt lyudej, sozdayushchih sistemu. Pohozhe, eto v prirode kak individuuma, tak i organizacii - brat'sya za proekty na predele svoih vozmozhnostej. Esli zadacha ne soderzhit opredelennyj vyzov, net smysla udelyat' osoboe vnimanie ee proektirovaniyu. Takie zadachi reshayutsya v ramkah uzhe ustoyavshejsya struktury, kotoruyu ne sleduet razrushat'. Tol'ko esli zamahivayutsya na chto-to ambicioznoe, poyavlyaetsya potrebnost' v novyh, bolee moshchnyh sredstvah i priemah. Krome togo, sushchestvuet tendenciya u teh, kto "znaet kak delat'", pereporuchat' proekt novichkam, kotorye ne imeyut takih znanij. Ne sushchestvuet "edinstvennogo pravil'nogo sposoba" dlya proektirovaniya i sozdaniya vsej sistemy. YA by schital veru v "edinstvennyj pravil'nyj sposob" detskoj bolezn'yu, esli by etoj bolezn'yu slishkom chasto ne zabolevali i opytnye programmisty. Napomnim eshche raz: tol'ko po toj prichine, chto priem uspeshno ispol'zovalsya v techenie goda dlya odnogo proekta, ne sleduet, chto on bez vsyakih izmenenij okazhetsya stol' zhe polezen dlya drugogo cheloveka ili drugoj zadachi. Vsegda vazhno ne imet' predubezhdenij. Ubezhdenie v tom, chto net edinstvenno vernogo resheniya, pronizyvaet ves' proekt yazyka S++, i, v osnovnom, po etoj prichine v pervom izdanii knigi ne bylo razdela, posvyashchennogo proektirovaniyu: ya ne hotel, chtoby ego rassmatrivali kak "manifest" moih lichnyh simpatij. Po etoj zhe prichine zdes', kak i v glavah 12 i 13, net chetko opredelennogo vzglyada na process razvitiya programmnogo obespecheniya, skoree zdes' prosto daetsya obsuzhdenie opredelennogo kruga, chasto voznikayushchih, voprosov i predlagayutsya nekotorye resheniya, okazavshiesya poleznymi v opredelennyh usloviyah. Za etim vvedeniem sleduet kratkoe obsuzhdenie celej i sredstv razvitiya programmnogo obespecheniya v $$11.2, a dal'she glava raspadaetsya na dve osnovnyh chasti: - $$11.3 soderzhit opisanie processa razvitiya programmnogo obespecheniya. - $$11.4 soderzhit nekotorye prakticheskie rekomendacii po organizacii etogo processa. Vzaimosvyaz' mezhdu proektirovaniem i yazykom programmirovaniya obsuzhdaetsya v glave 12, a glava 13 posvyashchena voprosam proektirovaniya bibliotek dlya S++. Ochevidno, bol'shaya chast' rassuzhdenij otnositsya k programmnym proektam bol'shogo ob容ma. CHitateli, kotorye ne uchastvuyut v takih razrabotkah, mogut sidet' spokojno i radovat'sya, chto vse eti uzhasy ih minovali, ili zhe oni mogut vybrat' voprosy, kasayushchiesya tol'ko ih interesov. Net nizhnej granicy razmera programmy, nachinaya s kotoroj imeet smysl zanyat'sya proektirovaniem prezhde, chem nachat' pisat' programmu. Odnako vse-taki est' nizhnyaya granica, nachinaya s kotoroj mozhno ispol'zovat' kakie-libo metody proektirovaniya. Voprosy, svyazannye s razmerom, obsuzhdayutsya v $$11.4.2. Trudnee vsego v programmnyh proektah borot'sya s ih slozhnost'yu. Est' tol'ko odin obshchij sposob bor'by so slozhnost'yu: razdelyaj i vlastvuj. Esli zadachu udalos' razdelit' na dve podzadachi, kotorye mozhno reshat' v otdel'nosti, to mozhno schitat' ee reshennoj za schet razdeleniya bolee, chem napolovinu. |tot prostoj princip primenim dlya udivitel'no bol'shogo chisla situacij. V chastnosti, ispol'zovanie modulej ili klassov pri razrabotke programmnyh sistem pozvolyaet razbit' programmu na dve chasti: chast' realizacii i chast', otkrytuyu pol'zovatelyu - kotorye svyazany mezhdu soboj (v ideale) vpolne opredelennym interfejsom. |to osnovnoj, vnutrenne prisushchij programmirovaniyu, princip bor'by so slozhnost'yu. Podobno etomu i process proektirovaniya programmy mozhno razbit' na otdel'nye vidy deyatel'nosti s chetko opredelennym (v ideale) vzaimodejstviem mezhdu lyud'mi, uchastvuyushchimi v nih. |to osnovnoj, vnutrenne prisushchij proektirovaniyu, princip bor'by so slozhnost'yu i podhod k upravleniyu lyud'mi,zanyatymi v proekte. V oboih sluchayah vydelenie chastej i opredelenie interfejsa mezhdu chastyami - eto to mesto, gde trebuetsya maksimum opyta i chut'ya. Takoe vydelenie ne yavlyaetsya chisto mehanicheskim processom, obychno ono trebuet pronicatel'nosti, kotoraya mozhet poyavit'sya tol'ko v rezul'tate dosko- nal'nogo ponimaniya sistemy na razlichnyh urovnyah abstrakcii (sm. $$11.3.3, $$12.2.1 i $$13.3). Blizorukij vzglyad na programmu ili na process razrabotki programmnogo obespecheniya chasto privodit k defektnoj sisteme. Otmetim, chto kak programmy, tak i programmistov razdelit' prosto. Trudnee dostignut' effektivnogo vzaimodejstviya mezhdu uchastnikami po obe storony granicy, ne narushaya ee i ne delaya vzaimodejstvie slishkom zhestkim. Zdes' predlozhen opredelennyj podhod k proektirovaniyu, a ne polnoe formal'noe opisanie metoda proektirovaniya. Takoe opisanie vyhodit za predmetnuyu oblast' knigi. Podhod, predlozhennyj zdes', mozhno primenyat' s razlichnoj stepen'yu formalizacii, i on mozhet sluzhit' bazoj dlya razlichnyh formal'nyh specifikacij. V tozhe vremya nel'zya schitat' etu glavu referatom, i zdes' ne delaetsya popytka rassmotret' kazhduyu temu, otnosyashchuyusya k processu razrabotki programm ili izlozhit' kazhduyu tochku zreniya. |to tozhe vyhodit za predmetnuyu oblast' knigi. Referat po etoj tematike mozhno najti v [2]. V etoj knige ispol'zuetsya dostatochno obshchaya i tradicionnaya terminologiya. Samye "interesnye" terminy, kak: proektirovanie, prototip, programmist - imeyut v literature neskol'ko opredelenij, chasto protivorechashchih drug drugu, poetomu predosteregaem vas ot togo, chtoby, ishodya iz prinyatyh v vashem okruzhenii opredelenij terminov, vy ne vynesli iz knigi to, na chto avtor sovershenno ne rasschityval. 11.2 Celi i sredstva Cel' programmirovaniya - sozdat' produkt, udovletvoryayushchij pol'zovatelya. Vazhnejshim sredstvom dlya dostizhenii etoj celi yavlyaetsya sozdanie programmy s yasnoj vnutrennej strukturoj i vospitanie kollektiva programmistov i razrabotchikov, imeyushchih dostatochnyj opyt i motivaciyu, chtoby bystro i effektivno reagirovat' na vse izmeneniya. Pochemu eto tak? Ved' vnutrennya struktura programmy i process, s pomoshch'yu kotorogo ona poluchena, v ideale nikak ne kasayutsya konechnogo pol'zovatelya. Bolee togo, esli konechnyj pol'zovatel' pochemu-to interesuetsya tem, kak napisana programma, to chto-to s etoj programmoj ne tak. Pochemu, nesmotrya na eto, tak vazhny struktura programmy i lyudi, ee sozdavshie? V konce koncov konechnyj pol'zovatel' nichego ob etom ne dolzhen znat'. YAsnaya vnutrennyaya struktura programmy oblegchaet: - testirovanie, - perenosimost', - soprovozhdenie, - rasshirenie, - reorganizaciyu i - ponimanie. Glavnoe zdes' v tom, chto lyubaya udachnaya bol'shaya programma imeet dolguyu zhizn', v techenie kotoroj nad nej rabotayut pokoleniya programmistov i razrabotchikov, ona perenositsya na novuyu mashinu, prisposablivaetsya k nepredusmotrennym trebovaniyam i neskol'ko raz perestraivaetsya. Vo vse vremya zhizni neobhodimo v priemlemoe vremya i s dopustimym chislom oshibok vydavat' versii programmy. Ne planirovat' vse eto - vse ravno, chto zaplanirovat' neudachu. Otmetim, chto, hotya v ideal'nom sluchae sluchae pol'zovateli ne dolzhny znat' vnutrennyuyu strukturu sistemy, na praktike oni obychno hotyat ee znat'. Naprimer, pol'zovatel' mozhet zhelat' poznakomit'sya v detalyah s razrabotkoj sistemy s cel'yu nauchit'sya kontrolirovat' vozmozhnosti i nadezhnost' sistemy na sluchaj peredelok i rasshirenij. Esli rassmatrivaemyj programmnyj produkt est' ne polnaya sistema, a nabor bibliotek dlya polucheniya programmnyh sistem, to pol'zovatel' zahochet uznat' pobol'she "detalej", chtoby oni sluzhili istochnikom idej i pomogali luchshe ispol'zovat' biblioteku. Nuzhno umet' ochen' tochno opredelit' ob容m proektirovaniya programmy. Nedostatochnyj ob容m privodit k beskonechnomu srezaniyu ostryh uglov ("pobystree peredadim sistemu, a oshibku ustranim v sleduyushchej versii"). Izbytochnyj ob容m privodit k uslozhnennomu opisaniyu sistemy, v kotorom sushchestvennoe teryaetsya v formal'nostyah, v rezul'tate chego pri reorganizacii programmy poluchenie rabotayushchej versii zatyagivaetsya ("novaya struktura namnogo luchshe staroj, pol'zovatel' soglasen zhdat' radi nee"). K tomu zhe voznikayut takie potrebnosti v resursah, kotorye nepozvolitel'ny dlya bol'shinstva potencial'nyh pol'zovatelej. Vybor ob容ma proektirovaniya - samyj trudnyj moment v razrabotke, imenno zdes' proyavlyaetsya talant i opyt. Vybor trudno sdelat' i dlya odnogo programmista ili razrabotchika, no on eshche trudnee dlya bol'shih zadach, gde zanyato mnogo lyudej raznogo urovnya kvalifikacii. Organizaciya dolzhna sozdavat' programmnyj produkt i soprovozhdat' ego, nesmotrya na izmeneniya v shtate, v napravlenii raboty ili v upravlyayushchej strukture. Rasprostranennyj sposob resheniya etih problem zaklyuchalsya v popytke svedeniya processa sozdaniya sistemy k neskol'kim otnositel'no prostym zadacham, ukladyvayushchimsya v zhestkuyu strukturu. Naprimer, sozdat' gruppu legko obuchaemyh (deshevyh) i vzaimozamenyaemyh programmistov nizkogo urovnya ("kodirovshchikov") i gruppu ne takih deshevyh, no vzaimozamenyaemyh (a znachit takzhe ne unikal'nyh) razrabotchikov. Schitaetsya, chto kodirovshchiki ne prinimayut reshenij po proektirovaniyu, a razrabotchiki ne utruzhdayut sebya "gryaznymi" podrobnostyami kodirovaniya. Obychno takoj podhod privodit k neudache, a gde on srabatyvaet, poluchaetsya slishkom gromozdkaya sistema s plohimi harakteristikami. Nedostatki takogo podhoda sostoyat v sleduyushchem: - slaboe vzaimodejstvie mezhdu programmistami i razrabotchikami privodit k neeffektivnosti, promedleniyu, upushchennym vozmozhnostyam i povtoreniyu oshibok iz-za plohogo ucheta i otsutstviya obmena opytom; - suzhenie oblasti tvorchestva razrabotchikov privodit k slabomu professional'nomu rostu, bezyniciativnosti, nebrezhnosti i bol'shoj tekuchesti kadrov. Po suti, podobnye sistemy - eto bespoleznaya trata redkih chelovecheskih talantov. Sozdanie struktury, v ramkah kotoroj lyudi mogut najti primenenie raznym talantam, ovladet' novym rodom deyatel'nosti i uchastvovat' v tvorcheskoj rabote - eto ne tol'ko blagorodnoe delo, no i praktichnoe, kommercheski vygodnoe predpriyatie. S drugoj storony, nel'zya sozdat' sistemu, predstavit' dokumentaciyu po nej i beskonechno ee soprovozhdat' bez nekotoroj zhestkoj organizacionnoj struktury. Dlya chisto novatorskogo proekta horosho nachat' s togo, chto prosto najti luchshih specialistov i pozvolit' im reshat' zadachu v sootvetstvii s ih ideyami. No po mere razvitiya proekta trebuetsya vse bol'she planirovaniya, specializacii i strogo opredelennogo vzaimodejstviya mezhdu zanyatymi v nem lyud'mi. Pod strogo opredelennym ponimaetsya ne matematicheskaya ili avtomaticheski verificiruemaya zapis' (hotya eto bezuslovno horosho tam, gde vozmozhno i primenimo), a skoree nabor ukazanij po zapisi, imenovaniyu, dokumentacii, testirovaniyu i t.p. No i zdes' neobhodimo chuvstvo mery. Slishkom zhestkaya struktura mozhet meshat' rostu i zatrudnyat' sovershenstvovanie. Zdes' podvergaetsya proverke talant i opyt menedzhera. Dlya otdel'nogo rabotnika analogichnaya problema svoditsya k opredeleniyu, gde nuzhno proyavit' smekalku, a gde dejstvovat' po receptam. Mozhno rekomendovat' planirovat' ne na period do vydachi sleduyushchej versii sistemy, a na bolee dolgij srok. Stroit' plany tol'ko do vypuska ocherednoj versii - znachit planiro