// ... } Kak vidno iz etogo primera, parametr ne ispol'zuetsya, esli ne zadano ego imya. Podobnye funkcii poyavlyayutsya pri uproshchenii programmy ili esli rasschityvayut na ee dal'nejshee rasshirenie. V oboih sluchayah rezervirovanie mesta v opredelenii funkcii dlya neispol'zuemogo parametra garantiruet, chto drugie funkcii, soderzhashchie vyzov dannoj, ne pridetsya menyat'. Uzhe govorilos', chto funkciyu mozhno opredelit' kak podstanovku (inline). Naprimer: inline fac(int i) { return i<2 ? 1 : n*fac(n-1); } Specifikaciya inline sluzhit podskazkoj translyatoru, chto vyzov funkcii fac mozhno realizovat' podstanovkoj ee tela, a ne s pomoshch'yu obychnogo mehanizma vyzova funkcij ($$R.7.1.2). Horoshij optimiziruyushchij translyator vmesto generacii vyzova fac(6) mozhet prosto ispol'zovat' konstantu 720. Iz-za nalichiya vzaimorekursivnyh vyzovov funkcij-podstanovok, a takzhe funkcij-podstanovok, rekursivnost' kotoryh zavisit ot vhodnyh dannyh, nel'zya utverzhdat', chto kazhdyj vyzov funkcii-podstanovki dejstvitel'no realizuetsya podstanovkoj ee tela. Stepen' optimizacii, provodimoj translyatorom, nel'zya formalizovat', poetomu odni translyatory sozdadut komandy 6*5*4*3*2*1, drugie - 6*fac(5), a nekotorye ogranichatsya neoptimizirovannym vyzovom fac(6). CHtoby realizaciya vyzova podstanovkoj stala vozmozhna dazhe dlya ne slishkom razvityh sistem programmirovaniya, nuzhno, chtoby ne tol'ko opredelenie, no i opisanie funkcii-podstanovki nahodilos' v tekushchej oblasti vidimosti. V ostal'nom specifikaciya inline ne vliyaet na semantiku vyzova. 4.6.3 Peredacha parametrov Pri vyzove funkcii vydelyaetsya pamyat' dlya ee formal'nyh parametrov, i kazhdyj formal'nyj parametr inicializiruetsya znacheniem sootvetstvuyushchego fakticheskogo parametra. Semantika peredachi parametrov tozhdestvenna semantike inicializacii. V chastnosti, sveryayutsya tipy formal'nogo i sootvetstvuyushchego emu fakticheskogo parametra, i vypolnyayutsya vse standartnye i pol'zovatel'skie preobrazovaniya tipa. Sushchestvuyut special'nye pravila peredachi massivov ($$4.6.5). Est' vozmozhnost' peredat' parametr, minuya kontrol' tipa ($$4.6.8), i vozmozhnost' zadat' standartnoe znachenie parametra ($$4.6.7). Rassmotrim funkciyu: void f(int val, int& ref) { val++; ref++; } Pri vyzove f() v vyrazhenii val++ uvelichivaetsya lokal'naya kopiya pervogo fakticheskogo parametra, togda kak v ref++ - sam vtoroj fakticheskij parametr uvelichivaetsya sam. Poetomu v funkcii void g() { int i = 1; int j = 1; f(i,j); } uvelichitsya znachenie j, no ne i. Pervyj parametr i peredaetsya po znacheniyu, a vtoroj parametr j peredaetsya po ssylke. V $$2.3.10 my govorili, chto funkcii, kotorye izmenyayut svoj peredavaemyj po ssylke parametr, trudnee ponyat', i chto poetomu luchshe ih izbegat' (sm. takzhe $$10.2.2). No bol'shie ob容kty, ochevidno, gorazdo effektivnee peredavat' po ssylke, chem po znacheniyu. Pravda mozhno opisat' parametr so specifikaciej const, chtoby garantirovat', chto peredacha po ssylke ispol'zuetsya tol'ko dlya effektivnosti, i vyzyvaemaya funkciya ne mozhet izmenit' znachenie ob容kta: void f(const large& arg) { // znachenie "arg" nel'zya izmenit' bez yavnyh // operacij preobrazovaniya tipa } Esli v opisanii parametra ssylki const ne ukazano, to eto rassmatrivaetsya kak namerenie izmenyat' peredavaemyj ob容kt: void g(large& arg); // schitaetsya, chto v g() arg budet menyat'sya Otsyuda moral': ispol'zujte const vsyudu, gde vozmozhno. Tochno tak zhe, opisanie parametra, yavlyayushchegosya ukazatelem, so specifikaciej const govorit o tom, chto ukazuemyj ob容kt ne budet izmenyat'sya v vyzyvaemoj funkcii. Naprimer: extern int strlen(const char*); // iz <string.h> extern char* strcpy(char* to, const char* from); extern int strcmp(const char*, const char*); Znachenie takogo priema rastet vmeste s rostom programmy. Otmetim, chto semantika peredachi parametrov otlichaetsya ot semantiki prisvaivaniya. |to razlichie sushchestvenno dlya parametrov, yavlyayushchihsya const ili ssylkoj, a takzhe dlya parametrov s tipom, opredelennym pol'zovatelem ($1.4.2). Literal, konstantu i parametr, trebuyushchij preobrazovaniya, mozhno peredavat' kak parametr tipa const&, no bez specifikacii const peredavat' nel'zya. Dopuskaya preobrazovaniya dlya parametra tipa const T&, my garantiruem, chto on mozhet prinimat' znacheniya iz togo zhe mnozhestva, chto i parametr tipa T, znachenie kotorogo peredaetsya pri neobhodimosti s pomoshch'yu vremennoj peremennoj. float fsqrt(const float&); // funkciya sqrt v stile Fortrana void g(double d) { float r; r = fsqrt(2.0f); // peredacha ssylki na vremennuyu // peremennuyu, soderzhashchuyu 2.0f r = fsqrt(r); // peredacha ssylki na r r = fsqrt(d); // peredacha ssylki na vremennuyu // peremennuyu, soderzhashchuyu float(d) } Zapret na preobrazovaniya tipa dlya parametrov-ssylok bez specifikacii const vveden dlya togo, chtoby izbezhat' nelepyh oshibok, svyazannyh s ispol'zovaniem pri peredache parametrov vremennyh peremennyh: void update(float& i); void g(double d) { float r; update(2.0f); // oshibka: parametr-konstanta update(r); // normal'no: peredaetsya ssylka na r update(d); // oshibka: zdes' nuzhno preobrazovyvat' tip } 4.6.4 Vozvrashchaemoe znachenie Esli funkciya ne opisana kak void, ona dolzhna vozvrashchat' znachenie. Naprimer: int f() { } // oshibka void g() { } // normal'no Vozvrashchaemoe znachenie ukazyvaetsya v operatore return v tele funkcii. Naprimer: int fac(int n) { return (n>1) ? n*fac(n-1) : 1; } V tele funkcii mozhet byt' neskol'ko operatorov return: int fac(int n) { if (n > 1) return n*fac(n-1); else return 1; } Podobno peredache parametrov, operaciya vozvrashcheniya znacheniya funkcii ekvivalentna inicializacii. Schitaetsya, chto operator return inicializiruet peremennuyu, imeyushchuyu tip vozvrashchaemogo znacheniya. Tip vyrazheniya v operatore return sveryaetsya s tipom funkcii, i proizvodyatsya vse standartnye i pol'zovatel'skie preobrazovaniya tipa. Naprimer: double f() { // ... return 1; // neyavno preobrazuetsya v double(1) } Pri kazhdom vyzove funkcii sozdaetsya novaya kopiya ee formal'nyh parametrov i avtomaticheskih peremennyh. Zanyataya imi pamyat' posle vyhoda iz funkcii budet snova ispol'zovat'sya, poetomu nerazumno vozvrashchat' ukazatel' na lokal'nuyu peremennuyu. Soderzhimoe pamyati, na kotoruyu nastroen takoj ukazatel', mozhet izmenit'sya nepredskazuemym obrazom: int* f() { int local = 1; // ... return &local; // oshibka } |ta oshibka ne stol' tipichna, kak shodnaya oshibka, kogda tip funkcii - ssylka: int& f() { int local = 1; // ... return local; // oshibka } K schast'yu, translyator preduprezhdaet o tom, chto vozvrashchaetsya ssylka na lokal'nuyu peremennuyu. Vot drugoj primer: int& f() { return 1; } // oshibka 4.6.5 Parametr-massiv Esli v kachestve parametra funkcii ukazan massiv, to peredaetsya ukazatel' na ego pervyj element. Naprimer: int strlen(const char*); void f() { char v[] = "massiv"; strlen(v); strlen("Nikolaj"); } |to oznachaet, chto fakticheskij parametr tipa T[] preobrazuetsya k tipu T*, i zatem peredaetsya. Poetomu prisvaivanie elementu formal'nogo parametra-massiva izmenyaet etot element. Inymi slovami, massivy otlichayutsya ot drugih tipov tem, chto oni ne peredayutsya i ne mogut peredavat'sya po znacheniyu. V vyzyvaemoj funkcii razmer peredavaemogo massiva neizvesten. |to nepriyatno, no est' neskol'ko sposobov obojti dannuyu trudnost'. Prezhde vsego, vse stroki okanchivayutsya nulevym simvolom, i znachit ih razmer legko vychislit'. Mozhno peredavat' eshche odin parametr, zadayushchij razmer massiva. Drugoj sposob: opredelit' strukturu, soderzhashchuyu ukazatel' na massiv i razmer massiva, i peredavat' ee kak parametr (sm. takzhe $$1.2.5). Naprimer: void compute1(int* vec_ptr, int vec_size); // 1-yj sposob struct vec { // 2-oj sposob int* ptr; int size; }; void compute2(vec v); Slozhnee s mnogomernymi massivami, no chasto vmesto nih mozhno ispol'zovat' massiv ukazatelej, svedya eti sluchai k odnomernym massivam. Naprimer: char* day[] = { "mon", "tue", "wed", "thu", "fri", "sat", "sun" }; Teper' rassmotrim funkciyu, rabotayushchuyu s dvumernym massivom - matricej. Esli razmery oboih indeksov izvestny na etape translyacii, to problem net: void print_m34(int m[3][4]) { for (int i = 0; i<3; i++) { for (int j = 0; j<4; J++) cout << ' ' << m[i][j]; cout << '\n'; } } Konechno, matrica po-prezhnemu peredaetsya kak ukazatel', a razmernosti privedeny prosto dlya polnoty opisaniya. Pervaya razmernost' dlya vychisleniya adresa elementa nevazhna ($$R.8.2.4), poetomu ee mozhno peredavat' kak parametr: void print_mi4(int m[][4], int dim1) { for ( int i = 0; i<dim1; i++) { for ( int j = 0; j<4; j++) cout << ' ' << m[i][j]; cout << '\n'; } } Samyj slozhnyj sluchaj - kogda nado peredavat' obe razmernosti. Zdes' "ochevidnoe" reshenie prosto neprigodno: void print_mij(int m[][], int dim1, int dim2) // oshibka { for ( int i = 0; i<dim1; i++) { for ( int j = 0; j<dim2; j++) cout << ' ' << m[i][j]; cout << '\n'; } } Vo-pervyh, opisanie parametra m[][] nedopustimo, poskol'ku dlya vychisleniya adresa elementa mnogomernogo massiva nuzhno znat' vtoruyu razmernost'. Vo-vtoryh, vyrazhenie m[i][j] vychislyaetsya kak *(*(m+i)+j), a eto, po vsej vidimosti, ne to, chto imel v vidu programmist. Privedem pravil'noe reshenie: void print_mij(int** m, int dim1, int dim2) { for (int i = 0; i< dim1; i++) { for (int j = 0; j<dim2; j++) cout << ' ' << ((int*)m)[i*dim2+j]; // zaputano cout << '\n'; } } Vyrazhenie, ispol'zuemoe dlya vybora elementa matricy, ekvivalentno tomu, kotoroe sozdaet dlya etoj zhe celi translyator, kogda izvestna poslednyaya razmernost'. Mozhno vvesti dopolnitel'nuyu peremennuyu, chtoby eto vyrazhenie stalo ponyatnee: int* v = (int*)m; // ... v[i*dim2+j] Luchshe takie dostatochno zaputannye mesta v programme upryatyvat'. Mozhno opredelit' tip mnogomernogo massiva s sootvetstvuyushchej operaciej indeksirovaniya. Togda pol'zovatel' mozhet i ne znat', kak razmeshchayutsya dannye v massive (sm. uprazhnenie 18 v $$7.13). 4.6.6 Peregruzka imeni funkcii Obychno imeet smysl davat' raznym funkciyam raznye imena. Esli zhe neskol'ko funkcij vypolnyaet odno i to zhe dejstvie nad ob容ktami raznyh tipov, to udobnee dat' odinakovye imena vsem etim funkciyam. Peregruzkoj imeni nazyvaetsya ego ispol'zovanie dlya oboznacheniya raznyh operacij nad raznymi tipami. Sobstvenno uzhe dlya osnovnyh operacij S++ primenyaetsya peregruzka. Dejstvitel'no: dlya operacij slozheniya est' tol'ko odno imya +, no ono ispol'zuetsya dlya slozheniya i celyh chisel, i chisel s plavayushchej tochkoj, i ukazatelej. Takoj podhod legko mozhno rasprostranit' na operacii, opredelennye pol'zovatelem, t.e. na funkcii. Naprimer: void print(int); // pechat' celogo void print(const char*) // pechat' stroki simvolov Dlya translyatora v takih peregruzhennyh funkciyah obshchee tol'ko odno - imya. Ochevidno, po smyslu takie funkcii shodny, no yazyk ne sposobstvuet i ne prepyatstvuet vydeleniyu peregruzhennyh funkcij. Takim obrazom, opredelenie peregruzhennyh funkcij sluzhit, prezhde vsego, dlya udobstva zapisi. No dlya funkcij s takimi tradicionnymi imenami, kak sqrt, print ili open, nel'zya etim udobstvom prenebregat'. Esli samo imya igraet vazhnuyu semanticheskuyu rol', naprimer, v takih operaciyah, kak + , * i << ($$7.2), ili dlya konstruktora klassa ($$5.2.4 i $$7.3.1), to takoe udobstvo stanovitsya sushchestvennym faktorom. Pri vyzove funkcii s imenem f translyator dolzhen razobrat'sya, kakuyu imenno funkciyu f sleduet vyzyvat'. Dlya etogo sravnivayutsya tipy fakticheskih parametrov, ukazannye v vyzove, s tipami formal'nyh parametrov vseh opisanij funkcij s imenem f. V rezul'tate vyzyvaetsya ta funkciya, u kotoroj formal'nye parametry nailuchshim obrazom sopostavilis' s parametrami vyzova, ili vydaetsya oshibka esli takoj funkcii ne nashlos'. Naprimer: void print(double); void print(long); void f() { print(1L); // print(long) print(1.0); // print(double) print(1); // oshibka, neodnoznachnost': chto vyzyvat' // print(long(1)) ili print(double(1)) ? } Podrobno pravila sopostavleniya parametrov opisany v $$R.13.2. Zdes' dostatochno privesti ih sut'. Pravila primenyayutsya v sleduyushchem poryadke po ubyvaniyu ih prioriteta: [1] Tochnoe sopostavlenie: sopostavlenie proizoshlo bez vsyakih preobrazovanij tipa ili tol'ko s neizbezhnymi preobrazovaniyami (naprimer, imeni massiva v ukazatel', imeni funkcii v ukazatel' na funkciyu i tipa T v const T). [2] Sopostavlenie s ispol'zovaniem standartnyh celochislennyh preobrazovanij, opredelennyh v $$R.4.1 (t.e. char v int, short v int i ih bezznakovyh dvojnikov v int), a takzhe preobrazovanij float v double. [3] Sopostavlenie s ispol'zovaniem standartnyh preobrazovanij, opredelennyh v $$R.4 (naprimer, int v double, derived* v base*, unsigned v int). [4] Sopostavlenie s ispol'zovaniem pol'zovatel'skih preobrazovanij ($$R.12.3). [5] Sopostavlenie s ispol'zovaniem ellipsisa ... v opisanii funkcii. Esli najdeny dva sopostavleniya po samomu prioritetnomu pravilu, to vyzov schitaetsya neodnoznachnym, a znachit oshibochnym. |ti pravila sopostavleniya parametrov rabotayut s uchetom pravil preobrazovanij chislovyh tipov dlya S i S++. Pust' imeyutsya takie opisaniya funkcii print: void print(int); void print(const char*); void print(double); void print(long); void print(char); Togda rezul'taty sleduyushchih vyzovov print() budut takimi: void h(char c, int i, short s, float f) { print(c); // tochnoe sopostavlenie: vyzyvaetsya print(char) print(i); // tochnoe sopostavlenie: vyzyvaetsya print(int) print(s); // standartnoe celochislennoe preobrazovanie: // vyzyvaetsya print(int) print(f); // standartnoe preobrazovanie: // vyzyvaetsya print(double) print('a'); // tochnoe sopostavlenie: vyzyvaetsya print(char) print(49); // tochnoe sopostavlenie: vyzyvaetsya print(int) print(0); // tochnoe sopostavlenie: vyzyvaetsya print(int) print("a"); // tochnoe sopostavlenie: // vyzyvaetsya print(const char*) } Obrashchenie print(0) privodit k vyzovu print(int), ved' 0 imeet tip int. Obrashchenie print('a') privodit k vyzovu print(char), t.k. 'a' - tipa char ($$R.2.5.2). Otmetim, chto na razreshenie neopredelennosti pri peregruzke ne vliyaet poryadok opisanij rassmatrivaemyh funkcij, a tipy vozvrashchaemyh funkciyami znachenij voobshche ne uchityvayutsya. Ishodya iz etih pravil mozhno garantirovat', chto esli effektivnost' ili tochnost' vychislenij znachitel'no razlichayutsya dlya rassmatrivaemyh tipov, to vyzyvaetsya funkciya, realizuyushchaya samyj prostoj algoritm. Naprimer: int pow(int, int); double pow(double, double); // iz <math.h> complex pow(double, complex); // iz <complex.h> complex pow(complex, int); complex pow(complex, double); complex pow(complex, complex); void k(complex z) { int i = pow(2,2); // vyzyvaetsya pow(int,int) double d = pow(2.0,2); // vyzyvaetsya pow(double,double) complex z2 = pow(2,z); // vyzyvaetsya pow(double,complex) complex z3 = pow(z,2); // vyzyvaetsya pow(complex,int) complex z4 = pow(z,z); // vyzyvaetsya pow(complex,complex) } 4.6.7 Standartnye znacheniya parametrov V obshchem sluchae u funkcii mozhet byt' bol'she parametrov, chem v samyh prostyh i naibolee chasto ispol'zuemyh sluchayah. V chastnosti, eto svojstvenno funkciyam, stroyashchim ob容kty (naprimer, konstruktoram, sm. $$5.2.4). Dlya bolee gibkogo ispol'zovaniya etih funkcij inogda primenyayutsya neobyazatel'nye parametry. Rassmotrim v kachestve primera funkciyu pechati celogo chisla. Vpolne razumno primenit' v kachestve neobyazatel'nogo parametra osnovanie schisleniya pechataemogo chisla, hotya v bol'shinstve sluchaev chisla budut pechatat'sya kak desyatichnye celye znacheniya. Sleduyushchaya funkciya void print (int value, int base =10); void F() { print(31); print(31,10); print(31,16); print(31,2); } napechataet takie chisla: 31 31 1f 11111 Vmesto standartnogo znacheniya parametra mozhno bylo by ispol'zovat' peregruzku funkcii print: void print(int value, int base); inline void print(int value) { print(value,10); } Odnako v poslednem variante tekst programmy ne stol' yavno demonstriruet zhelanie imet' odnu funkciyu print, no pri etom obespechit' udobnuyu i kratkuyu formu zapisi. Tip standartnogo parametra sveryaetsya s tipom ukazannogo znacheniya pri translyacii opisaniya funkcii, a znachenie etogo parametra vychislyaetsya v moment vyzova funkcii. Zadavat' standartnoe znachenie mozhno tol'ko dlya zavershayushchih podryad idushchih parametrov: int f(int, int =0, char* =0); // normal'no int g(int =0, int =0, char*); // oshibka int h(int =0, int, char* =0); // oshibka Otmetim, chto v dannom kontekste nalichie probela mezhdu simvolami * i = ves'ma sushchestvenno, poskol'ku *= yavlyaetsya operaciej prisvaivaniya: int nasty(char*=0); // sintaksicheskaya oshibka 4.6.8 Neopredelennoe chislo parametrov Sushchestvuyut funkcii, v opisanii kotoryh nevozmozhno ukazat' chislo i tipy vseh dopustimyh parametrov. Togda spisok formal'nyh parametrov zavershaetsya ellipsisom (...), chto oznachaet: "i, vozmozhno, eshche neskol'ko argumentov". Naprimer: int printf(const char* ...); Pri vyzove printf obyazatel'no dolzhen byt' ukazan parametr tipa char*, odnako mogut byt' (a mogut i ne byt') eshche drugie parametry. Naprimer: printf("Hello, world\n"); printf("My name is %s %s\n", first_name, second_name); printf("%d + %d = %d\n", 2,3,5); Takie funkcii pol'zuyutsya dlya raspoznavaniya svoih fakticheskih parametrov nedostupnoj translyatoru informaciej. V sluchae funkcii printf pervyj parametr yavlyaetsya strokoj, specificiruyushchej format vyvoda. Ona mozhet soderzhat' special'nye simvoly, kotorye pozvolyayut pravil'no vosprinyat' posleduyushchie parametry. Naprimer, %s oznachaet -"budet fakticheskij parametr tipa char*", %d oznachaet -"budet fakticheskij parametr tipa int" (sm. $$10.6). No translyator etogo ne znaet, i poetomu on ne mozhet ubedit'sya, chto ob座avlennye parametry dejstvitel'no prisutstvuyut v vyzove i imeyut sootvetstvuyushchie tipy. Naprimer, sleduyushchij vyzov printf("My name is %s %s\n",2); normal'no transliruetsya, no privedet (v luchshem sluchae) k neozhidannoj vydache. Mozhete proverit' sami. Ochevidno, chto raz parametr neopisan, to translyator ne imeet svedenij dlya kontrolya i standartnyh preobrazovanij tipa etogo parametra. Poetomu char ili short peredayutsya kak int, a float kak double, hotya pol'zovatel', vozmozhno, imel v vidu drugoe. V horosho produmannoj programme mozhet potrebovat'sya, v vide isklyucheniya, lish' neskol'ko funkcij, v kotoryh ukazany ne vse tipy parametrov. CHtoby obojti kontrol' tipov parametrov, luchshe ispol'zovat' peregruzku funkcij ili standartnye znacheniya parametrov, chem parametry, tipy kotoryh ne byli opisany. |llipsis stanovitsya neobhodimym tol'ko togda, kogda mogut menyat'sya ne tol'ko tipy, no i chislo parametrov. CHashche vsego ellipsis ispol'zuetsya dlya opredeleniya interfejsa s bibliotekoj standartnyh funkcij na S, esli etim funkciyam net zameny: extern "C" int fprintf(FILE*, const char* ...); extern "C" int execl(const char* ...); Est' standartnyj nabor makroopredelenij, nahodyashchijsya v <stdarg.h>, dlya vybora nezadannyh parametrov etih funkcij. Rassmotrim funkciyu reakcii na oshibku, pervyj parametr kotoroj pokazyvaet stepen' tyazhesti oshibki. Za nim mozhet sledovat' proizvol'noe chislo strok. Nuzhno sostavit' soobshchenie ob oshibke s uchetom, chto kazhdoe slovo iz nego peredaetsya kak otdel'naya stroka: extern void error(int ...) extern char* itoa(int); main(int argc, char* argv[]) { switch (argc) { case 1: error(0,argv[0],(char*)0); break; case 2: error(0,argv[0],argv[1],(char*)0); break; default: error(1,argv[0], "With",itoa(argc-1),"arguments",(char*)0); } // ... } Funkciya itoa vozvrashchaet stroku simvolov, predstavlyayushchuyu ee celyj parametr. Funkciyu reakcii na oshibku mozhno opredelit' tak: #include <stdarg.h> void error(int severity ...) /* za "severity" (stepen' tyazhesti oshibki) sleduet spisok strok, zavershayushchijsya nulem */ { va_list ap; va_start(ap,severity); // nachalo parametrov for (;;) { char* p = va_arg(ap,char*); if (p == 0) break; cerr << p << ' '; } va_end(ap); // ochistka parametrov cerr << '\n'; if (severity) exit(severity); } Vnachale pri vyzove va_start() opredelyaetsya i inicializiruetsya va_list. Parametrami makroopredeleniya va_start yavlyayutsya imya tipa va_list i poslednij formal'nyj parametr. Dlya vyborki po poryadku neopisannyh parametrov ispol'zuetsya makroopredelenie va_arg(). V kazhdom obrashchenii k va_arg nuzhno zadavat' tip ozhidaemogo fakticheskogo parametra. V va_arg() predpolagaetsya, chto parametr takogo tipa prisutstvuet v vyzove, no obychno net vozmozhnosti proverit' eto. Pered vyhodom iz funkcii, v kotoroj bylo obrashchenie k va_start, neobhodimo vyzvat' va_end. Prichina v tom, chto v va_start() mogut byt' takie operacii so stekom, iz-za kotoryh korrektnyj vozvrat iz funkcii stanovitsya nevozmozhnym. V va_end() ustranyayutsya vse nezhelatel'nye izmeneniya steka. Privedenie 0 k (char*)0 neobhodimo potomu, chto sizeof(int) ne obyazano sovpadat' s sizeof(char*). |tot primer demonstriruet vse te slozhnosti, s kotorymi prihoditsya stalkivat'sya programmistu, esli on reshil obojti kontrol' tipov, ispol'zuya ellipsis. 4.6.9 Ukazatel' na funkciyu Vozmozhny tol'ko dve operacii s funkciyami: vyzov i vzyatie adresa. Ukazatel', poluchennyj s pomoshch'yu poslednej operacii, mozhno vposledstvii ispol'zovat' dlya vyzova funkcii. Naprimer: void error(char* p) { /* ... */ } void (*efct)(char*); // ukazatel' na funkciyu void f() { efct = &error; // efct nastroen na funkciyu error (*efct)("error"); // vyzov error cherez ukazatel' efct } Dlya vyzova funkcii s pomoshch'yu ukazatelya (efct v nashem primere) nado vnachale primenit' operaciyu kosvennosti k ukazatelyu - *efct. Poskol'ku prioritet operacii vyzova () vyshe, chem prioritet kosvennosti *, nel'zya pisat' prosto *efct("error"). |to budet oznachat' *(efct("error")), chto yavlyaetsya oshibkoj. Po toj zhe prichine skobki nuzhny i pri opisanii ukazatelya na funkciyu. Odnako, pisat' prosto efct("error") mozhno, t.k. translyator ponimaet, chto efct yavlyaetsya ukazatelem na funkciyu, i sozdaet komandy, delayushchie vyzov nuzhnoj funkcii. Otmetim, chto formal'nye parametry v ukazatelyah na funkciyu opisyvayutsya tak zhe, kak i v obychnyh funkciyah. Pri prisvaivanii ukazatelyu na funkciyu trebuetsya tochnoe sootvetstvie tipa funkcii i tipa prisvaivaemogo znacheniya. Naprimer: void (*pf)(char*); // ukazatel' na void(char*) void f1(char*); // void(char*); int f2(char*); // int(char*); void f3(int*); // void(int*); void f() { pf = &f1; // normal'no pf = &f2; // oshibka: ne tot tip vozvrashchaemogo // znacheniya pf = &f3; // oshibka: ne tot tip parametra (*pf)("asdf"); // normal'no (*pf)(1); // oshibka: ne tot tip parametra int i = (*pf)("qwer"); // oshibka: void prisvaivaetsya int } Pravila peredachi parametrov odinakovy i dlya obychnogo vyzova, i dlya vyzova s pomoshch'yu ukazatelya. CHasto byvaet udobnee oboznachit' tip ukazatelya na funkciyu imenem, chem vse vremya ispol'zovat' dostatochno slozhnuyu zapis'. Naprimer: typedef int (*SIG_TYP)(int); // iz <signal.h> typedef void (SIG_ARG_TYP)(int); SIG_TYP signal(int, SIG_ARG_TYP); Takzhe chasto byvaet polezen massiv ukazatelej na funkcii. Naprimer, mozhno realizovat' sistemu menyu dlya redaktora s vvodom, upravlyaemym mysh'yu, ispol'zuya massiv ukazatelej na funkcii, realizuyushchie komandy. Zdes' net vozmozhnosti podrobno opisat' takoj redaktor, no dadim samyj obshchij ego nabrosok: typedef void (*PF)(); PF edit_ops[] = { // komandy redaktora &cut, &paste, &snarf, &search }; PF file_ops[] = { // upravlenie fajlom &open, &reshape, &close, &write }; Dalee nado opredelit' i inicializirovat' ukazateli, s pomoshch'yu kotoryh budut zapuskat'sya funkcii, realizuyushchie vybrannye iz menyu komandy. Vybor proishodit nazhatiem klavishi myshi: PF* button2 = edit_ops; PF* button3 = file_ops; Dlya nastoyashchej programmy redaktora nado opredelit' bol'shee chislo ob容ktov, chtoby opisat' kazhduyu poziciyu v menyu. Naprimer, neobhodimo gde-to hranit' stroku, zadayushchuyu tekst, kotoryj budet vydavat'sya dlya kazhdoj pozicii. Pri rabote s sistemoj menyu naznachenie klavish myshi budet postoyanno menyat'sya. CHastichno eti izmeneniya mozhno predstavit' kak izmeneniya znachenij ukazatelya, svyazannogo s dannoj klavishej. Esli pol'zovatel' vybral poziciyu menyu, kotoraya opredelyaetsya, naprimer, kak poziciya 3 dlya klavishi 2, to sootvetstvuyushchaya komanda realizuetsya vyzovom: (*button2[3])(); CHtoby polnost'yu ocenit' moshchnost' konstrukcii ukazatel' na funkciyu, stoit popytat'sya napisat' programmu bez nee. Menyu mozhno izmenyat' v dinamike, esli dobavlyat' novye funkcii v tablicu komand. Dovol'no prosto sozdavat' v dinamike i novye menyu. Ukazateli na funkcii pomogayut realizovat' polimorficheskie podprogrammy, t.e. takie podprogrammy, kotorye mozhno primenyat' k ob容ktam razlichnyh tipov: typedef int (*CFT)(void*,void*); void sort(void* base, unsigned n, unsigned int sz, CFT cmp) /* Sortirovka vektora "base" iz n elementov v vozrastayushchem poryadke; ispol'zuetsya funkciya sravneniya, na kotoruyu ukazyvaet cmp. Razmer elementov raven "sz". Algoritm ochen' neeffektivnyj: sortirovka puzyr'kovym metodom */ { for (int i=0; i<n-1; i++) for (int j=n-1; i<j; j--) { char* pj = (char*)base+j*sz; // b[j] char* pj1 = pj - sz; // b[j-1] if ((*cmp)(pj,pj1) < 0) { // pomenyat' mestami b[j] i b[j-1] for (int k = 0; k<sz; k++) { char temp = pj[k]; pj[k] = pj1[k]; pj1[k] = temp; } } } } V podprogramme sort neizvesten tip sortiruemyh ob容ktov; izvestno tol'ko ih chislo (razmer massiva), razmer kazhdogo elementa i funkciya, kotoraya mozhet sravnivat' ob容kty. My vybrali dlya funkcii sort() takoj zhe zagolovok, kak u qsort() - standartnoj funkcii sortirovki iz biblioteki S. |tu funkciyu ispol'zuyut nastoyashchie programmy. Pokazhem, kak s pomoshch'yu sort() mozhno otsortirovat' tablicu s takoj strukturoj: struct user { char* name; // imya char* id; // parol' int dept; // otdel }; typedef user* Puser; user heads[] = { "Ritchie D.M.", "dmr", 11271, "Sethi R.", "ravi", 11272, "SZYmanski T.G.", "tgs", 11273, "Schryer N.L.", "nls", 11274, "Schryer N.L.", "nls", 11275 "Kernighan B.W.", "bwk", 11276 }; void print_id(Puser v, int n) { for (int i=0; i<n; i++) cout << v[i].name << '\t' << v[i].id << '\t' << v[i].dept << '\n'; } CHtoby imet' vozmozhnost' sortirovat', nuzhno vnachale opredelit' podhodyashchie funkcii sravneniya. Funkciya sravneniya dolzhna vozvrashchat' otricatel'noe chislo, esli ee pervyj parametr men'she vtorogo, nul', esli oni ravny, i polozhitel'noe chislo v protivnom sluchae: int cmp1(const void* p, const void* q) // sravnenie strok, soderzhashchih imena { return strcmp(Puser(p)->name, Puser(q)->name); } int cmp2(const void* p, const void* q) // sravnenie nomerov razdelov { return Puser(p)->dept - Puser(q)->dept; } Sleduyushchaya programma sortiruet i pechataet rezul'tat: int main() { sort(heads,6,sizeof(user), cmp1); print_id(heads,6); // v alfavitnom poryadke cout << "\n"; sort(heads,6,sizeof(user),cmp2); print_id(heads,6); // po nomeram otdelov } Dopustima operaciya vzyatiya adresa i dlya funkcii-podstanovki, i dlya peregruzhennoj funkcii ($$R.13.3). Otmetim, chto neyavnoe preobrazovanie ukazatelya na chto-to v ukazatel' tipa void* ne vypolnyaetsya dlya parametra funkcii, vyzyvaemoj cherez ukazatel' na nee. Poetomu funkciyu int cmp3(const mytype*, const mytype*); nel'zya ispol'zovat' v kachestve parametra dlya sort(). Postupiv inache, my narushaem zadannoe v opisanii uslovie, chto cmp3() dolzhna vyzyvat'sya s parametrami tipa mytype*. Esli vy special'no hotite narushit' eto uslovie, to dolzhny ispol'zovat' yavnoe preobrazovanie tipa. 4.7 Makrosredstva Makrosredstva yazyka opredelyayutsya v $$R.16. V S++ oni igrayut gorazdo men'shuyu rol', chem v S. Mozhno dazhe dat' takoj sovet: ispol'zujte makroopredeleniya tol'ko togda, kogda ne mozhete bez nih obojtis'. Voobshche govorya, schitaetsya, chto prakticheski kazhdoe poyavlenie makroimeni yavlyaetsya svidetel'stvom nekotoryh nedostatkov yazyka, programmy ili programmista. Makrosredstva sozdayut opredelennye trudnosti dlya raboty sluzhebnyh sistemnyh programm, poskol'ku oni pererabatyvayut programmnyj tekst eshche do translyacii. Poetomu, esli vasha programma ispol'zuet makrosredstva, to servis, predostavlyaemyj takimi programmami, kak otladchik, profilirovshchik, programma perekrestnyh ssylok, budet dlya nee nepolnym. Esli vse-taki vy reshite ispol'zovat' makrokomandy, to vnachale tshchatel'no izuchite opisanie preprocessora S++ v vashem spravochnom rukovodstve i ne starajtes' byt' slishkom umnym. Prostoe makroopredelenie imeet vid: #define imya ostatok-stroki V tekste programmy leksema imya zamenyaetsya na ostatok-stroki. Naprimer, ob容kt = imya budet zameneno na ob容kt = ostatok-stroki Makroopredelenie mozhet imet' parametry. Naprimer: #define mac(a,b) argument1: a argument2: b V makrovyzove mac dolzhny byt' zadany dve stroki, predstavlyayushchie parametry. Pri podstanovke oni zamenyat a i b v makroopredelenii mac(). Poetomu stroka expanded = mac(foo bar, yuk yuk) pri podstanovke preobrazuetsya v expanded = argument1: foo bar argument2: yuk yuk Makroimena nel'zya peregruzhat'. Rekursivnye makrovyzovy stavyat pered preprocessorom slishkom slozhnuyu zadachu: // oshibka: #define print(a,b) cout<<(a)<<(b) #define print(a,b,c) cout<<(a)<<(b)<<(c) // slishkom slozhno: #define fac(n) (n>1) ?n*fac(n-1) :1 Preprocessor rabotaet so strokami i prakticheski nichego ne znaet o sintaksise C++, tipah yazyka i oblastyah vidimosti. Translyator imeet delo tol'ko s uzhe raskrytym makroopredeleniem, poetomu oshibka v nem mozhet diagnostirovat'sya uzhe posle podstanovki, a ne pri opredelenii makroimeni. V rezul'tate poyavlyayutsya dovol'no putannye soobshcheniya ob oshibkah. Dopustimy takie makroopredeleniya: #define Case break;case #define forever for(;;) A vot sovershenno izlishnie makroopredeleniya: #define PI 3.141593 #define BEGIN { #define END } Sleduyushchie makroopredeleniya mogut privesti k oshibkam: #define SQUARE(a) a*a #define INCR_xx (xx)++ #define DISP = 4 CHtoby ubedit'sya v etom, dostatochno poprobovat' sdelat' podstanovku v takom primere: int xx = 0; // global'nyj schetchik void f() { int xx = 0; // lokal'naya peremennaya xx = SQUARE(xx+2); // xx = xx +2*xx+2; INCR_xx; // uvelichivaetsya lokal'naya peremennaya xx if (a-DISP==b) { // a-=4==b // ... } } Pri ssylke na global'nye imena v makroopredelenii ispol'zujte operaciyu razresheniya oblasti vidimosti ($$2.1.1), i vsyudu, gde eto vozmozhno, zaklyuchajte imya parametra makroopredeleniya v skobki. Naprimer: #define MIN(a,b) (((a)<(b))?(a):(b)) Esli makroopredelenie dostatochno slozhnoe, i trebuetsya kommentarij k nemu, to razumnee napisat' kommentarij vida /* */, poskol'ku v realizacii S++ mozhet ispol'zovat'sya preprocessor S, kotoryj ne raspoznaet kommentarii vida //. Naprimer: #define m2(a) something(a) /* glubokomyslennyj kommentarij */ S pomoshch'yu makrosredstv mozhno sozdat' svoj sobstvennyj yazyk, pravda, skoree vsego, on budet neponyaten drugim. Krome togo, preprocessor S predostavlyaet dovol'no slabye makrosredstva. Esli vasha zadacha netrivial'na, vy, skoree vsego, obnaruzhite, chto reshit' ee s pomoshch'yu etih sredstv libo nevozmozhno, libo chrezvychajno trudno. V kachestve al'ternativy tradicionnomu ispol'zovaniyu makrosredstv v yazyk vvedeny konstrukcii const, inline i shablony tipov. Naprimer: const int answer = 42; template<class T> inline T min(T a, T b) { return (a<b)?a:b; } 4.8 Uprazhneniya 1. (*1) Sostav'te sleduyushchie opisaniya: funkciya s parametrami tipa ukazatel' na simvol i ssylka na celoe, nevozvrashchayushchaya znacheniya; ukazatel' na takuyu funkciyu; funkciya s parametrom, imeyushchim tip takogo ukazatelya; funkciya, vozvrashchayushchaya takoj ukazatel'. Napishite opredelenie funkcii, u kotoroj parametr i vozvrashchaemoe znachenie imeyut tip takogo ukazatelya. Podskazka: ispol'zujte typedef. 2. (*1) Kak ponimat' sleduyushchee opisanie? Gde ono mozhet prigodit'sya? typedef int (rifii&) (int, int); 3. (*1.5) Napishite programmu, podobnuyu toj, chto vydaet "Hello, world". Ona poluchaet imya (name) kak parametr komandnoj stroki i vydaet "Hello, name". Izmenite programmu tak, chtoby ona poluchala proizvol'noe chislo imen i vsem im vydavala svoe privetstvie: "Hello, ...". 4. (1.5) Napishite programmu, kotoraya, berya iz komandnoj stroki proizvol'noe chislo imen fajlov, vse eti fajly perepisyvaet odin za drugim v cout. Poskol'ku v programme proishodit konkatenaciya fajlov, vy mozhete nazvat' ee cat ot slova concatenation - konkatenaciya). 5. (*2) Perevedite nebol'shuyu programmu s yazyka S na S++. Izmenite zagolovochnye fajly tak, chtoby oni soderzhali opisanie vseh vyzyvaemyh funkcij i opisanie tipov vseh parametrov. Po vozmozhnosti vse komandy #define zamenite konstrukciyami enum, const ili inline. Udalite iz fajlov .c vse opisaniya vneshnih, a opredeleniya funkcij privedite k vidu, sootvetstvuyushchemu S++. Vyzovy malloc() i free() zamenite operaciyami new i delete. Udalite nenuzhnye operacii privedeniya. 6. (*2) Napishite funkciyu sort() ($$4.6.9), ispol'zuyushchuyu bolee effektivnyj algoritm sortirovki. 7. (*2) Posmotrite na opredelenie struktury tnode v $$R.9.3. Napishite funkciyu, zanosyashchuyu novye slova v derevo uzlov tnode. Napishite funkciyu dlya vyvoda uzlov dereva tnode. Napishite funkciyu, kotoraya proizvodit takoj vyvod v alfavitnom poryadke. Izmenite strukturu tnode tak, chtoby v nej soderzhalsya tol'ko ukazatel' na slovo proizvol'noj dliny, kotoroe razmeshchaetsya s pomoshch'yu new v svobodnoj pamyati. Izmenite funkciyu tak, chtoby ona rabotala s novoj strukturoj tnode. 8. (*1) Napishite funkciyu itoa(), kotoraya ispol'zovalas' v primere iz $$4.6.8. 9. (*2) Uznajte, kakie standartnye zagolovochnye fajly est' v vashej sisteme. Porojtes' v katalogah /usr/include ili /usr/include/CC (ili v teh katalogah, gde hranyatsya standartnye zagolovochnye fajly vashej sistemy). Prochitajte lyuboj pokazavshijsya interesnym fajl. 10. (*2) Napishite funkciyu, kotoraya budet perevorachivat' dvumernyj massiv. (Pervyj element massiva stanet poslednim). 11. (*2) Napishite shifruyushchuyu programmu, kotoraya chitaet simvoly iz cin i pishet ih v cout v zashifrovannom vide. Mozhno ispol'zovat' sleduyushchij prostoj metod shifracii: dlya simvola s zashifrovannoe predstavlenie poluchaetsya v rezul'tate operacii s^key[i], gde key - massiv simvolov, peredavaemyj v komandnoj stroke. Simvoly iz massiva key ispol'zuyutsya v ciklicheskom poryadke, poka ne budet prochitan ves' vhodnoj potok. Pervonachal'nyj tekst poluchaetsya povtornym primeneniem toj zhe operacii s temi zhe elementami key. Esli massiv key ne zadan (ili zadana pustaya stroka), shifraciya ne proishodit. 12. (*3) Napishite programmu, kotoraya pomogaet deshifrirovat' tekst, zashifrovannyj opisannym vyshe sposobom, kogda klyuch (t.e. massiv key) neizvesten. Podskazka: sm. D Kahn "The Codebreakers", Macmillan, 1967, New York, str. 207-213. 13. (*3) Napishite funkciyu obrabotki oshibok, pervyj parametr kotoryj podoben formatiruyushchej stroke-parametru printf() i soderzhit formaty %s, %c i %d. Za nim mozhet sledovat' proizvol'noe kolichestvo chislovyh parametrov. Funkciyu printf() ne ispol'zujte. Esli smysl formata %s i drugih formatov vam neizvesten, obratites' k $$10.6. Ispol'zujte <stdarg.h>. 14. (*1) Kakoe imya vy vybrali by dlya tipov ukazatelej na funkcii, kotorye opredelyayutsya s pomoshch'yu typedef? 15. (*2) Issledujte raznye programmy, chtoby poluchit' predstavlenie o raznyh ispol'zuemyh na praktike stilyah imenovaniya. Kak ispol'zuyutsya zaglavnye bukvy? Kak ispol'zuetsya podcherk? V kakih sluchayah ispol'zuyutsya takie imena, kak i ili x? 16. (*1) Kakie oshibki soderzhatsya v sleduyushchih makroopredeleniyah? #define PI = 3.141593; #define MAX(a,b) a>b?a:b #define fac(a) (a)*fac((a)-1) 17. (*3) Napishite makroprocessor s prostymi vozmozhnostyami, kak u preprocessora S. Tekst chitajte iz cin, a rezul'tat zapisyvajte v cout. Vnachale realizujte makroopredeleniya bez parametrov. Podskazka: v programme kal'kulyatora est' tablica imen i sintaksicheskij analizator, kotorymi mozhno vospol'zovat'sya. 18. (*2) Napishite programmu, izvlekayushchuyu kvadratnyj koren' iz dvuh (2) s pomoshch'yu standartnoj funkcii sqrt(), no ne vklyuchajte v programmu <math.h>. Sdelajte eto uprazhnenie s pomoshch'yu funkcii sqrt() na Fortrane. 19. (*2) Realizujte funkciyu print() iz $$4.6.7.  * GLAVA 5. KLASSY "|ti tipy ne abstraktnye, oni stol' zhe real'ny, kak int i float" - Dag Makilroj V etoj glave opisyvayutsya vozmozhnosti opredeleniya novyh tipov, dlya kotoryh dostup k dannym ogranichen zadannym mnozhestvom funkcij, osushchestvlyayushchih ego. Ob座asnyaetsya, kak mozhno ispol'zovat' chleny struktury dannyh, kak ee zashchishchat', inicializirovat' i, nakonec, unichtozhat'. V primerah privedeny prostye klassy dlya upravleniya tablicej imen, raboty so stekom, mnozhestvom i realiza