nter++; /* poschitat' simvol */ return counter; /* skol'ko simvolov, otlichnyh ot '\0' */ } Tut nikakih ogranichenij net. Imenno etot podhod i byl izbran v yazyke Si, hotya v principe mozhno samomu pol'zovat'sya i drugimi. Na samom dele v yazyke est' takaya STANDARTNAYA funkciya strlen(s) (vam ne nado pisat' ee samomu, ee uzhe napisali za vas). --------------------------------------------------------------------- INICIALIZACIYA GLOBALXNOGO MASSIVA ================================= Massiv, zadannyj vne kakih-libo funkcij, mozhno proinicializirovat' konstantnymi nachal'nymi znacheniyami: int array[5] = { 12, 23, 34, 45, 56 }; char string[7] = { 'P', 'r', 'i', 'v', 'e', 't', '\0' }; Esli razmer massiva ukazan BOLXSHE, chem my perechislim elementov, to ostal'nye elementy zapolnyatsya nulyami (dlya int) ili '\0' dlya char. int array[5] = { 12, 23, 34 }; Esli my perechislim bol'she elementov, chem pozvolyaet razmer massiva - eto budet oshibkoj. int a[5] = { 177, 255, 133 }; Operaciya indeksacii massiva a[] daet: pri n znachenie vyrazheniya a[n] est' -------------------------------------------- -1 ne opredeleno (oshibka: "indeks za granicej massiva") 0 177 1 255 2 133 3 0 4 0 5 ne opredeleno (oshibka)  * 13_FUNCS.txt *  KAK PROISHODIT VYZOV FUNKCII ============================ Pust' u nas opisana funkciya, vozvrashchayushchaya celoe znachenie. /* OPREDELENIE FUNKCII func(). */ /* Gde func - ee imya. Nazvat' my ee mozhem kak nam ugodno. */ int func(int a, int b, int c){ int x, y; ... x = a + 7; ... b = b + 4; ... return(nekoe_znachenie); } Zdes' a, b, c - argumenty funkcii (parametry) x, y - lokal'nye peremennye Tochka vyzova - nahoditsya vnutri kakoj-to drugoj funkcii, naprimer funkcii main() main(){ int zz, var; ... var = 17; zz = func(33, 77, var + 3) + 44; ... } Kogda vypolnenie programmy dohodit do stroki zz = func(33, 77, var + 3) + 44; 1) Proishodit VYZOV FUNKCII func() (a) |tot punkt my uvidim nizhe. (b) Sozdayutsya peremennye s imenami a, b, c, x, y; (c) Peremennym-argumentam prisvaivayutsya nachal'nye znacheniya, kotorye berutsya iz tochki vyzova. V tochke vyzova perechislen spisok (cherez zapyatuyu) vyrazhenij (formul): func(vyrazhenie1, vyrazhenie2, vyrazhenie3) Vychislennye znacheniya etih vyrazhenij sootvetstvenno budut prisvoeny 1-omu, 2-omu i 3-emu argumentam (parametram) iz opredeleniya funkcii: int func(a, b, c){ /* a = nomer 1, b = 2, c = 3 */ Pervyj parametr: a = 33; Vtoroj parametr: b = 77; Tretij parametr: c = var + 3; to est', vychislyaya, c = 20; Lokal'nye peremennye x i y soderzhat neopredelennye znacheniya, to est' musor (my ne mozhem predskazat' ih znacheniya, poka ne prisvoim im yavnym obrazom kakoe-libo znachenie sami). 2) Vypolnyaetsya TELO funkcii, to est' vychisleniya, zapisannye vnutri { ... } v opredelenii funkcii. Naprimer: x = a + 7; I parametry, i lokal'nye peremennye - eto PEREMENNYE, to est' ih mozhno izmenyat'. b = b + 4; Pri etom nikakie peremennye VNE etoj funkcii ne izmenyayutsya. (Ob etom eshche raz pozzhe). 3) Proizvoditsya VOZVRAT iz funkcii. ... return(nekoe_znachenie); } Naprimer, eto mozhet byt' ... return(a + 2 * x); } Rassmotrim, chto pri etom proishodit v tochke vyzova: zz = func(33, 77, var + 3) + 44; (1) Vycherkivaem func(.....) zz = XXXXXXX + 44; (2) Vychislyaem znachenie "nekoe_znachenie" v operatore return, i berem KOPIYU etogo znacheniya. Pust' pri vychislenii tam poluchilos' 128. (3) Podstavlyaem eto znachenie na mesto vycherknutogo func(.....) U nas poluchaetsya zz = 128 + 44; (4) AVTOMATICHESKI UNICHTOZHAYUTSYA lokal'nye peremennye i argumenty funkcii: a - ubito b - ubito c - ubito x - ubito y - ubito Takih peremennyh (i ih znachenij) bol'she net v prirode. (5) Punkt, kotoryj my obsudim pozzhe. (6) Prodolzhaem vychislenie: zz = 128 + 44; Vychislyaetsya v zz = 172; /* operator prisvaivaniya */ ------------------------------------------------------------------------- int func1(int x){ printf("func1: x=%d\n", x); /* 1 */ x = 77; printf("func1: x=%d\n", x); /* 2 */ return x; } void main(){ int var, y; var = 111; y = func1(var); /* @ */ printf("main: var=%d\n", var); /* 3 */ } V dannom sluchae v tochke @ my peredaem v funkciyu func1() ZNACHENIE peremennoj var, ravnoe 111. |to znachit, chto pri vyzove funkcii budet sozdana peremennaya x i ej budet prisvoeno nachal'noe znachenie 111 x = 111; Poetomu pervyj operator printf() napechataet 111. Zatem my izmenyaem znachenie peremennoj x na 77. My menyaem peremennuyu x, no ne peremennuyu var !!! Ispol'zovav ZNACHENIE (ego kopiyu) iz peremennoj var dlya x, my o peremennoj var zabyli - ona nas ne kasaetsya (a my - ee). Poetomu vtoroj operator printf() napechataet 77. V peremennoj zhe var ostalos' znachenie 111, chto i podtverdit nam tretij operator printf, kotoryj napechataet 111. ------------------------------------------------------------------------- VREMENNOE SOKRYTIE PEREMENNYH ============================= int func1(int x){ /* f.1 */ printf("func1: x=%d\n", x); /* f.2 */ x = 77; /* f.3 */ printf("func1: x=%d\n", x); /* f.4 */ return x; /* f.5 */ } void main(){ int x, y; /* 1 */ x = 111; /* 2 */ y = func1(x); /* 3 */ printf("main: x=%d y=%d\n", x, y); /* 4 */ } A teper' my i peremennuyu vnutri main(), i argument funkcii func1() nazvali odnim i tem zhe imenem. CHto budet? Budet to zhe samoe, chto v predydushchem primere. V moment vyzova funkcii func1() budet sozdana NOVAYA peremennaya s imenem x, a staraya (prezhnyaya) peremennaya i ee znachenie budut VREMENNO SPRYATANY (skryty). Mozhno bylo by utochnit' eti peremennye imenami funkcij, v kotoryh oni opredeleny: main::x i func1::x (no eto uzhe konstrukcii iz yazyka Si++, a ne Si). Vypolnim programmu po operatoram: |/* 1 */ Otvodyatsya peremennye main::x i main::y dlya celyh chisel; |/* 2 */ main::x = 111; |/* 3 */ Vyzyvaetsya func1(111); | +-------+ . |/* f.1 */ Otvoditsya peremennaya func1::x so znacheniem 111; . |/* f.2 */ Pechataetsya 111 iz peremennoj func1::x; . | . |/* f.3 */ func1::x = 77; (eto ne main::x, a drugaya peremennaya, . | LOKALXNAYA dlya funkcii func1. . | Peremennuyu main::x my sejchas ne vidim - . | ona "zaslonena" imenem nashej lokal'noj . | peremennoj. . | Poetomu my ne mozhem ee izmenit'). . | . |/* f.4 */ Pechataet 77 iz func1::x; . |/* f.5 */ Vozvrashchaet znachenie func1::x , to est' 77. . | Peremennaya func1::x unichtozhaetsya. . | . | Teper' my snova vozvrashchaemsya v funkciyu main(), . | gde imya x oboznachaet peremennuyu main::x . | a ne func1::x +-------+ | |/* 3 */ y = 77; |/* 4 */ Pechataet znacheniya main::x i main::y, to est' | 111 i 77. |tot mehanizm sokrytiya imen pozvolyaet pisat' funkcii main() i func1() raznym programmistam, pozvolyaya im NE ZABOTITXSYA o tom, chtoby imena lokal'nyh peremennyh v funkciyah NE SOVPADALI. Pust' sovpadayut - huzhe ne budet, mehanizm upryatyvaniya imen razreshit konflikt. Zato programmist mozhet ispol'zovat' lyuboe ponravivsheesya emu imya v lyuboj funkcii - hotya by i x, ili i. ------------------------------------------------------------------------- To zhe samoe proishodit s lokal'nymi peremennymi, a ne s argumentami funkcii. int func1(int arg){ /* lokal'naya peremennaya-parametr func1::arg */ int x; /* lokal'naya peremennaya func1::x */ x = arg; printf("func1: x=%d\n", x); x = 77; printf("func1: x=%d\n", x); return x; } void main(){ int x, y; /* peremennye main::x i main::y */ x = 111; y = func1(x); printf("main: x=%d y=%d\n", x, y); } Dejstvuet tot zhe samyj mehanizm vremennogo sokrytiya imeni x. Voobshche zhe, argumenty funkcii i ee lokal'nye peremennye otlichayutsya tol'ko odnim: argumentam avtomaticheski prisvaivayutsya nachal'nye znacheniya, ravnye znacheniyam sootvetstvuyushchih vyrazhenij v spiske imya_funkcii(..., ..., ....) arg1 arg2 arg3 v meste vyzova funkcii. To est' OPISANIE FUNKCII: int f(int arg1, int arg2, int arg3){ int perem1, perem2; ... /* prodolzhenie */ } VYZOV: .... f(vyrazhenie1, vyrazhenie2, vyrazhenie3) ... TO V TELE FUNKCII VYPOLNITSYA (v moment ee vyzova): arg1 = vyrazhenie1; arg2 = vyrazhenie2; arg3 = vyrazhenie3; perem1 = MUSOR; perem2 = MUSOR; ... /* prodolzhenie */ ------------------------------------------------------------------------- GLOBALXNYE PEREMENNYE ===================== Nakonec, sushchestvuyut peremennye, kotorye ob®yavlyayutsya VNE VSEH FUNKCIJ, i sushchestvuyushchie vse vremya vypolneniya programmy (a ne tol'ko to vremya, kogda aktivna funkciya, v kotoroj oni sozdany). Lokal'nye peremennye i argumenty UNICHTOZHAYUTSYA pri vyhode iz funkcii. Global'nye peremennye - net. int x = 12; /* ::x - ej mozhno zaranee prisvoit' konstantu */ int globvar; /* ::globvar */ int f1(){ int x; /* f1::x */ x = 77; printf("x=%d\n", x); /* 4 */ return x; } int f2(){ printf("x=%d\n", x); /* 5 */ return 0; } void main(){ int x, y; /* main::x */ x = 111; /* 1 */ printf("x=%d\n", x); /* 2 */ printf("glob=%d\n", globvar); /* 3 */ y = f1(); y = f2(); } V dannom primere my vidim: - vo-pervyh my vidim FUNKCII BEZ PARAMETROV. |to normal'naya situaciya. - vo-vtoryh tut ispol'zuyutsya TRI peremennye s imenem "x". Kak vypolnyaetsya programma? /* 1 */ main::x = 111; |to lokal'nyj x, a ne global'nyj. Global'nyj x poprezhnemu soderzhit 12. /* 2 */ Napechataet znachenie peremennoj main::x, to est' 111. Vnutri funkcii main global'naya peremennaya ::x zaslonena svoej sobstvennoj peremennoj x. V dannom sluchae NET SPOSOBA dobrat'sya iz main k global'noj peremennoj x, eto vozmozhno tol'ko v yazyke Si++ po imeni ::x K peremennoj zhe globvar u nas dostup est'. /* 3 */ Pechataet ::globvar. My obnaruzhivaem, chto ee znachenie 0. V otlichie ot global'nyh peremennyh, kotorye iznachal'no soderzhat MUSOR, global'nye peremennye iznachal'no soderzhat znachenie 0. V ramochku, podcherknut'. /* 4 */ Pri vyzove f1() peremennaya f1::x zaslonyaet soboj kak main::x tak i ::x V dannom sluchae napechataetsya 77, no ni ::x ni main::x ne budut izmeneny operatorom x = 77. |to izmenyalas' f1::x /* 5 */ Pri vyzove f2() istoriya interesnee. Tut net svoej sobstvennoj peremennoj x. No kakaya peremennaya pechataetsya tut - ::x ili main::x ? Otvet: ::x to est' 12. Peremennye nazvany lokal'nymi eshche i potomu, chto oni NEVIDIMY V VYZYVAEMYH FUNKCIYAH. |to OPREDELENIE lokal'nyh peremennyh. (Poetomu ne sprashivajte "pochemu?" Po opredeleniyu) To est', esli my imeem funca(){ int vara; ... ...funcb();... /* vyzov */ ... } to iz funkcii funcb() my NE IMEEM DOSTUPA K PEREMENNOJ vara. funcb(){ int z; z = vara + 1; /* oshibka, vara neizvestna vnutri funcb() */ } Esli, v svoyu ochered', funcb() vyzyvaet funcc(), to i iz funcc() peremennaya vara nevidima. Ostanovites' i osoznajte. |to pravilo sluzhit vse toj zhe celi - raznye funkcii mogut byt' napisany raznymi programmistami, kotorye mogut ispol'zovat' odni i te zhe imena dlya RAZNYH peremennyh, ne boyas' ih vzaimoperesecheniya. Mnozhestva imen, ispol'zovannyh v raznyh funkciyah, nezavisimy drug ot druga. Imena iz odnoj funkcii NIKAK ne otnosyatsya k peremennym s temi zhe imenami IZ DRUGOJ funkcii. Vernemsya k paragrafu KAK PROISHODIT VYZOV FUNKCII i rassmotrim punkt (a). Teper' on mozhet byt' opisan kak (a) Lokal'nye peremennye i argumenty vyzyvayushchej funkcii delayutsya nevidimymi. ~~~~~~~~~~ A pri vozvrate iz funkcii: (5) Lokal'nye peremennye i argumenty vyzyvayushchej funkcii snova delayutsya vidimymi. ODNAKO global'nye peremennye vidimy iz LYUBOJ funkcii, isklyuchaya sluchaj, kogda global'naya peremennaya zaslonena odnoimennoj lokal'noj peremennoj dannoj funkcii. ------------------------------------------------------------------------- PROCEDURY ========= Byvayut funkcii, kotorye ne vozvrashchayut nikakogo znacheniya. Takie funkcii oboznachayutsya void ("pustyshka"). Takie funkcii nazyvayut eshche PROCEDURAMI. void func(){ printf("Privetik!\n"); return; /* vernut'sya v vyzyvayushchuyu funkciyu */ } Takie funkcii vyzyvayutsya radi "pobochnyh effektov", naprimer pechati strochki na ekran ili izmeneniya global'nyh (i tol'ko) peremennyh. int glob; void func(int a){ glob += a; } Operator return tut neobyazatelen, on avtomaticheski vypolnyaetsya pered poslednej skobkoj } Vyzov takih funkcij ne mozhet byt' ispol'zovan v operatore prisvaivaniya: main(){ int z; z = func(7); /* oshibka, a chto my prisvaivaem ??? */ } Korrektnyj vyzov takov: main(){ func(7); } Prosto vyzov i vse. ZACHEM FUNKCII? CHtoby vyzyvat' ih s raznymi argumentami! int res1, res2; ... res1 = func(12 * x * x + 177, 865, 'x'); res2 = func(432 * y + x, 123 * y - 12, 'z'); Kstati, vy zametili, chto spisok fakticheskih parametrov sleduet cherez zapyatuyu; i vyrazhenij rovno stol'ko, skol'ko parametrov u funkcii? Funkciya opisyvaet POSLEDOVATEXNOSTX DEJSTVIJ, kotoruyu mozhno vypolnit' mnogo raz, no s raznymi ishodnymi dannymi (argumentami). V zavisimosti ot dannyh ona budet vydavat' raznye rezul'taty, no vypolnyaya odni i te zhe dejstviya. V tom to i sostoit ee prelest': my ne dubliruem odin kusok programmy mnogo raz, a prosto "vyzyvaem" ego. Funkciya - abstrakciya ALGORITMA, to est' posledovatel'nosti dejstvij. Ee konkretizaciya - vyzov funkcii s uzhe opredelennymi parametrami. ------------------------------------------------------------------------- Operator return mozhet nahodit'sya ne tol'ko v konce funkcii, no i v ee seredine. On vyzyvaet nemedlennoe prekrashchenie tela funkcii i vozvrat znacheniya v tochku vyzova. int f(int x){ int y; y = x + 4; if(y > 10) return (x - 1); y *= 2; return (x + y); } REKURSIVNYE FUNKCII. STEK Rekursivnoj nazyvaetsya funkciya, vyzyvayushchaya sama sebya. int factorial(int arg){ if(arg == 1) return 1; /* a */ else return arg * factorial(arg - 1); /* b */ } |ta funkciya pri vyzove factorial(n) vychislit proizvedenie n * (n-1) * ... * 3 * 2 * 1 nazyvaemoe "faktorial chisla n". V dannoj funkcii peremennaya arg budet otvedena (a posle i unichtozhena) MNOGO RAZ. Tak chto peremennaya factorial::arg dolzhna poluchit' eshche i NOMER vyzova funkcii: factorial::arg[uroven'_vyzova] I na kazhdom novom urovne novaya peremennaya skryvaet vse predydushchie. Tak dlya factorial(4) budet +----------------------------------------+ | | factorial::arg[ 0_oj_raz ] est' 4 | | | factorial::arg[ 1_yj_raz ] est' 3 | | | factorial::arg[ 2_oj_raz ] est' 2 | | | factorial::arg[ 3_ij_raz ] est' 1 | V --------+ +--------- Zatem pojdut vozvraty iz funkcij: +----------------------------------------+ A | /* b */ return 4 * 6 = 24 | | | /* b */ return 3 * 2 = 6 | | | /* b */ return 2 * 1 = 2 | | | /* a */ return 1; | | --------+ +--------- Takaya konstrukciya nazyvaetsya STEK (stack). --------+ +------------ | | pustoj stek +---------------+ Polozhim v stek znachenie a | --------+ | +------------ | V | +---------------+ | a | <--- vershina steka +---------------+ Polozhim v stek znachenie b --------+ +------------ | | +---------------+ | b | <--- vershina steka +---------------+ | a | +---------------+ Polozhim v stek znachenie c --------+ +------------ | | +---------------+ | c | <--- vershina steka +---------------+ | b | +---------------+ | a | +---------------+ Analogichno, znacheniya "snimayutsya so steka" v obratnom poryadke: c, b, a. V kazhdyj moment vremeni u steka dostupno dlya chteniya (kopirovaniya) ili vybrasyvaniya tol'ko dannoe, nahodyashcheesya na VERSHINE steka. Tak i v nashej rekursivnoj funkcii peremennaya factorial::arg vedet sebya imenno kak stek (etot yashchik-stek imeet imya arg) - ona imeet ODNO imya, no raznye znacheniya v raznyh sluchayah. Peremennye, kotorye AVTOMATICHESKI vedut sebya kak stek, vstrechayutsya tol'ko v (rekursivnyh) funkciyah. Stek - eto chasto vstrechayushchayasya v programmirovanii konstrukciya. Esli podobnoe povedenie nuzhno programmistu, on dolzhen promodelirovat' stek pri pomoshchi massiva: int stack[10]; int in_stack = 0; /* skol'ko elementov v steke */ /* Zanesenie znacheniya v stek */ void push(int x){ stack[in_stack] = x; in_stack++; } /* Snyat' znachenie so steka */ int pop(){ if(in_stack == 0){ printf("Stek pust, oshibka.\n"); return (-1); } in_stack--; return stack[in_stack]; } Obratite v nimanie, chto net nuzhdy STIRATX (naprimer obnulyat') znacheniya elementov massiva, vykinutyh s vershiny steka. Oni prosto bol'she ne ispol'zuyutsya, libo zatirayutsya novym znacheniem pri pomeshchenii na stek novogo znacheniya. void main(){ push(1); push(2); push(3); while(in_stack > 0){ printf("top=%d\n", pop()); } } STEK I FUNKCII Budem rassmatrivat' kazhdyj VYZOV funkcii kak pomeshchenie v special'nyj stek bol'shogo "bloka informacii", vklyuchayushchego v chastnosti ARGUMENTY I LOKALXNYE PEREMENNYE vyzyvaemoj funkcii. Operator return iz vyzvannoj funkcii vytalkivaet so steka VESX takoj blok. V kachestve primera rassmotrim takuyu programmu: int x = 7; /* global'naya */ int v = 333; /* global'naya */ int factorial(int n){ int w; /* lishnyaya peremennaya, tol'ko dlya demonstracionnyh celej */ w = n; if(n == 1) return 1; /* #a */ else return n * factorial(n-1); /* #b */ } void func(){ int x; /* func::x */ x = 777; /* #c */ printf("Vyzvana funkciya func()\n"); } void main(){ int y = 12; /* main::y */ int z; /* A */ z = factorial(3); /* B */ printf("z=%d\n", z); func(); /* C */ } ------------------------------------------------------------------------- Vypolnenie programmy nachnetsya s vyzova funkcii main(). V tochke /* A */ | v z g l ya d | V V --------+ +-------- |=======================| | z = musor | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE V kazhdyj dannyj moment vidimy peremennye, kotorye nahodyatsya a) na vershine (i tol'ko) steka vyzovov funkcij. b) i nezaslonennye imi global'nye peremennye. V dannom sluchae my smotrim "sverhu" i vidim: main::z, main::y, ::x, ::v ------------------------------------------------------------------------- V tochke /* B */ my vyzyvaem factorial(3). --------+ +-------- |=======================| | w = musor | | n = 3 | |factorial(3) | |=======================| | +-|---> tekushchij operator z = factorial(3); | z = musor | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE Pri novom vzglyade vidimy: factorial(3)::w, factorial(3)::n, ::x, ::v Stali nevidimy: main::z, main::y Stroka "tekushchij operator ..." ukazyvaet mesto, s kotorogo nado vozobnovit' vypolnenie funkcii, kogda my vernemsya v nee. ------------------------------------------------------------------------- Kogda vypolnenie programmy v funkcii factorial(3) dojdet do tochki /* #b */ budet vypolnyat'sya return 3 * factorial(2). V itoge budet vyzvana funkciya factorial(2). --------+ +-------- |=======================| | w = musor | | n = 2 | |factorial(2) | |=======================| | +-|---> tekushchij operator return 3 * factorial(2); | w = 3 | | n = 3 | |factorial(3) | |=======================| | +-|---> tekushchij operator z = factorial(3); | z = musor | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE ------------------------------------------------------------------------- Kogda vypolnenie programmy v funkcii factorial(2) dojdet do tochki /* #b */ budet vypolnyat'sya return 2 * factorial(1). V itoge budet vyzvana funkciya factorial(1). --------+ +-------- |=======================| | w = musor | | n = 1 | |factorial(1) | |=======================| | +-|---> tekushchij operator return 2 * factorial(1); | w = 2 | | n = 2 | |factorial(2) | |=======================| | +-|---> tekushchij operator return 3 * factorial(2); | w = 3 | | n = 3 | |factorial(3) | |=======================| | +-|---> tekushchij operator z = factorial(3); | z = musor | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE ------------------------------------------------------------------------- Zatem v factorial(1) vypolnenie programmy dojdet do tochki /* #a */ i budet proizvodit'sya return 1. Pri return vycherkivaetsya ODIN blok informacii so steka vyzovov funkcij, i vozobnovlyaetsya vypolnenie "tekushchego operatora" v funkcii, stavshej NOVOJ vershinoj steka vyzovov. Zamet'te, chto v dannoj situacii vyzvannye funkcii factorial(m) eshche ne zavershilis'. V nih eshche ESTX chto sdelat': vychislit' vyrazhenie v return, i sobstvenno vypolnit' sam return. Vychisleniya budut prodolzheny. --------+ +-------- |=======================| | +-|---> tekushchij operator return 1; | w = 1 | | n = 1 | |factorial(1) | |=======================| | +-|---> tekushchij operator return 2 * factorial(1); | w = 2 | | n = 2 | |factorial(2) | |=======================| | +-|---> tekushchij operator return 3 * factorial(2); | w = 3 | | n = 3 | |factorial(3) | |=======================| | +-|---> tekushchij operator z = factorial(3); | z = musor | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE ------------------------------------------------------------------------- Nachinaetsya vytalkivanie funkcij so steka i vypolnenie operatorov return; --------+ +-------- |=======================| | +-|---> tekushchij operator return 2 * 1; | w = 2 | | n = 2 | |factorial(2) | |=======================| | +-|---> tekushchij operator return 3 * factorial(2); | w = 3 | | n = 3 | |factorial(3) | |=======================| | +-|---> tekushchij operator z = factorial(3); | z = musor | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE --------+ +-------- |=======================| | +-|---> tekushchij operator return 3 * 2; | w = 3 | | n = 3 | |factorial(3) | |=======================| | +-|---> tekushchij operator z = factorial(3); | z = musor | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE --------+ +-------- |=======================| | +-|---> tekushchij operator z = 6; | z = musor | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE --------+ +-------- |=======================| | z = 6 | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE ------------------------------------------------------------------------- Nakonec, v tochke /* C */ budet vyzvana funkciya func(). Rassmotrim tochku /* #c */ v nej. --------+ +-------- |=======================| | x = 777; | |func(); | |=======================| | +-|---> tekushchij operator func(); | z = 6 | | y = 12 | +------+---------+ |main() | |x = 7 | v = 333 | +-----------------------+-----------+------+---------+----- STEK VYZOVOV FUNKCIJ GLOBALXNYE PEREMENNYE V dannom meste nas interesuet - kakie peremennye vidimy? Vidimy: func::x = 777 ::v = 333 I vse. ::x zaslonen lokal'noj peremennoj. main::y, main::z nevidimy, tak kak nahodyatsya ne na vershine steka vyzovov funkcij ------------------------------------------------------------------------- Mnogie funkcii bolee estestvenno vyrazhayutsya cherez rekursiyu. Hotya, chasto eto privodit k izlishnim vychisleniyam po sravneniyu s iteraciyami (to est' ciklami). Vot primer - chisla Fibonachchi. int fibonacci(int n){ if(n==1 || n==2) return 1; /* else */ return fibonacci(n-1) + fibonacci(n-2); } void main(){ printf("20oe chislo Fibonachchi ravno %d\n", fibonacci(20)); } Poskol'ku tut otsutstvuet massiv dlya zapominaniya promezhutochnyh rezul'tatov, to etot massiv na samom dele neyavno modeliruetsya v vide lokal'nyh peremennyh vnutri steka vyzovov funkcij. Odnako etot sposob ploh - v nem slishkom mnogo povtoryayushchihsya dejstvij. Dobavim operator pechati - i poschitaem, skol'ko raz byla vyzvana fibonacci(1) ? int called = 0; int fibonacci(int n){ if(n==1){ called++; return 1; } else if(n==2) return 1; return fibonacci(n-1) + fibonacci(n-2); } void main(){ printf("20oe chislo Fibonachchi ravno %d\n", fibonacci(20)); printf("fibonacci(1) byla vyzvana %d raz\n", called); } Ona byla vyzvana... 2584 raza! 14.c /* Risuem hitruyu geometricheskuyu figuru */ #include <stdio.h> const int LINES = 15; void draw(int nspaces, int nstars, char symbol){ int i; for(i=0; i < nspaces; i++) putchar(' '); for(i=0; i < nstars; i++) putchar(symbol); } void main(){ int nline, nsym; char symbols[3]; /* Massiv iz treh bukv */ symbols[0] = '\\'; symbols[1] = 'o'; symbols[2] = '*'; for(nline=0; nline < LINES; nline++){ for(nsym = 0; nsym < 3; nsym++) draw(nline, nline, symbols[nsym]); /* Perehod na novuyu stroku vynesen iz funkcii v glavnyj cikl */ putchar('\n'); } } 15.c /* Zadacha: narisovat' tablicu vida kot kot kot koshka koshka kot kot kot koshka koshka kot ... Gde idet posledovatel'nost' kot, kot, kot, koshka, koshka... povtoryayushchayasya mnogo raz i raspolagaemaya po 6 zverej v stroku. */ #include <stdio.h> /* magicheskaya stroka */ /* Ob®yavlenie global'nyh peremennyh. V dannom sluchae - konstant. */ int TOMCATS = 3; /* tri kota */ int CATS = 2; /* dve koshki */ int ANIMALS_PER_LINE = 6; /* shest' zverej v kazhdoj stroke */ int LINES = 25; /* chislo vyvodimyh strok */ /* i nam ponadobitsya eshche odna peremennaya - obshchee chislo zverej. Ee my vychislim cherez uzhe zadannye, poetomu tut my ee ob®yavim... no vychislit' chto-libo mozhno tol'ko vnutri funkcii. V nashem sluchae - v funkcii main(). */ int ANIMALS; /* obshchee chislo zverej */ int nth_in_line = 0; /* nomer zverya v tekushchej stroke */ /* |ta peremennaya ne mozhet byt' lokal'noj v funkcii, tak kak * togda ona unichtozhitsya pri vyhode iz funkcii. Nam zhe neobhodimo, * chtoby ee znachenie sohranyalos'. Poetomu peremennaya - global'naya. */ /* Funkciya, kotoraya schitaet chislo zverej v odnoj stroke i libo perevodit stroku, libo perevodit pechat' v sleduyushchuyu kolonku (tabulyaciej). */ void checkIfWeHaveToBreakLine(){ nth_in_line++; /* tekushchij nomer zverya v stroke (s edinicy) */ if(nth_in_line == ANIMALS_PER_LINE){ /* Esli stroka zapolnena zver'mi... */ putchar('\n'); /* novaya stroka */ nth_in_line = 0; /* v novoj stroke net zverej */ } else { putchar('\t'); /* v sleduyushchuyu kolonku */ } } void main(){ int nanimal; /* nomer zverya */ int i; /* schetchik */ ANIMALS = ANIMALS_PER_LINE * LINES; nanimal = 0; while(nanimal < ANIMALS){ for(i=0; i < TOMCATS; i++){ /* Operator printf() vyvodit STROKU SIMVOLOV. STROKA zaklyuchaetsya v dvojnye kavychki (ne putat' s odinochnymi dlya putchar(). */ printf("kot"); nanimal++; /* poschitat' eshche odnogo zverya */ /* i proverit' - ne nado li perejti na novuyu stroku ? */ checkIfWeHaveToBreakLine(); } for(i=0; i < CATS; i++){ printf("koshka"); nanimal++; /* poschitat' eshche odnogo zverya */ /* i proverit' - ne nado li perejti na novuyu stroku ? */ checkIfWeHaveToBreakLine(); } } /* putchar('\n'); */ } 16.c /* Ta zhe zadacha, no eshche nado pechatat' nomer kazhdogo zverya. Ogranichimsya pyat'yu strokami. */ #include <stdio.h> /* magicheskaya stroka */ int TOMCATS = 3; /* tri kota */ int CATS = 2; /* dve koshki */ int ANIMALS_PER_LINE = 6; /* shest' zverej v kazhdoj stroke */ int LINES = 5; /* chislo vyvodimyh strok */ int ANIMALS; /* obshchee chislo zverej */ int nth_in_line = 0; /* nomer zverya v tekushchej stroke */ void checkIfWeHaveToBreakLine(){ nth_in_line++; if(nth_in_line == ANIMALS_PER_LINE){ putchar('\n'); nth_in_line = 0; } else printf("\t\t"); /* @ */ /* Odinokij operator mozhet obojtis' bez {...} vokrug nego */ } void main(){ int nanimal; int i; ANIMALS = ANIMALS_PER_LINE * LINES; nanimal = 0; while(nanimal < ANIMALS){ for(i=0; i < TOMCATS; i++){ /* Format %d vyvodit znachenie peremennoj tipa int v vide tekstovoj stroki. Sama peremennaya dolzhna byt' v spiske posle formata (spisok - eto perechislenie peremennyh cherez zapyatuyu). Peremennyh ILI vyrazhenij (formul). Davajte vyvodit' po DVE tabulyacii -- eto mesto otmecheno v funkcii checkIfWeHaveToBreakLine() kak @. Eshche raz vnimanie - odin simvol my vyvodim kak putchar('a'); Neskol'ko simvolov - kak printf("abcdef"); Odinochnye kavychki - dlya odnoj bukvy. Dvojnye kavychki - dlya neskol'kih. */ printf("kot%d", nanimal); nanimal++; checkIfWeHaveToBreakLine(); } for(i=0; i < CATS; i++){ printf("koshka%d", nanimal); nanimal++; checkIfWeHaveToBreakLine(); } } } 17.c /* Zadacha: napechatat' korni iz chisel ot 1 do 100. Novaya informaciya: Nam ponadobitsya novyj tip dannyh - DEJSTVITELXNYE CHISLA. |to chisla, imeyushchie drobnuyu chast' (posle tochki). Kak my uzhe znaem, celye - eto int. bukva - eto char. dejstvitel'noe chislo - eto double. (est' eshche slovo float, no im pol'zovat'sya ne rekomenduetsya). Dlya vychisleniya kornya ispol'zuetsya iteracionnyj algoritm Gerona. q = koren' iz x; q[0] := x; q[n+1] := 1/2 * ( q[n] + x/q[n] ); Glavnoe tut ne vpast' v oshibku, ne klyunut' na q[n] i ne zavesti massiv. Nam ne nuzhny rezul'taty kazhdoj iteracii, nam nuzhen tol'ko konechnyj otvet. Poetomu nam budet vpolne dostatochno ODNOJ, no izmenyayushchejsya v cikle, yachejki q. */ #include <stdio.h> /* Eshche odno novoe klyuchevoe slovo - const. Oboznachaet konstanty. V otlichie ot peremennyh, takie imena nel'zya izmenyat'. To est', esli gde-to potom poprobovat' napisat' epsilon = ... ; to eto budet oshibkoj. */ const double epsilon = 0.0000001; /* tochnost' vychislenij */ /* Funkciya vychisleniya modulya chisla */ double doubleabs(double x){ if(x < 0) return -x; else return x; } /* Funkciya vychisleniya kvadratnogo kornya */ double sqrt(double x){ double sq = x; /* Takaya konstrukciya est' prosto sklejka dvuh strok: double sq; sq = x; Nazyvaetsya eto "ob®yavlenie peremennoj s inicializaciej". */ while(doubleabs(sq*sq - x) >= epsilon){ sq = 0.5 * (sq + x/sq); } return sq; } void main() { int n; for(n=1; n <= 100; n++) printf("sqrt(%d)=%lf\n", n, sqrt((double) n) ); } /* Zdes' v operatore printf() my pechataem DVA vyrazheniya. FORMAT ZNACHENIE ------ -------- %d -- n %lf -- sqrt((double) n) Po formatu %d pechatayutsya znacheniya tipa int. Po formatu %c pechatayutsya znacheniya tipa char. Po formatu %lf (ili %g) pechatayutsya znacheniya tipa double. CHto znachit "napechatat' znachenie vyrazheniya sqrt(xxx)" ? |to znachit: - vyzvat' funkciyu sqrt() s argumentom, ravnym xxx; - vychislit' ee; - vozvrashchennoe eyu znachenie napechatat' po formatu %lf, to est' kak dejstvitel'noe chislo. Zamet'te, chto tut vozvrashchaemoe znachenie NE prisvaivaetsya nikakoj peremennoj, my ne sobiraemsya ego hranit'. Tochno tak zhe, kak v operatore x = 12 + 34; 12 i 34 ne hranyatsya ni v kakih peremennyh, a operator printf("%d\n", 12); pechataet CHISLO 12, a ne peremennuyu. Tochno tak zhe, kak mozhno pisat' double z; z = sqrt(12) + sqrt(23); gde znachenie, vychislennoe kazhdoj funkciej, NE hranitsya v svoej sobstvennoj peremennoj (takaya peremennaya na samom dele sushchestvuet v komp'yutere, no programmistu ona ne nuzhna i nedostupna). Ili z = sqrt( sqrt(81)); (koren' iz kornya iz 81 --> dast 3) Dalee, chto oznachaet konstrukciya (double) n ? Funkciya sqrt() trebuet argumenta tipa double. My zhe predlagaem ej celyj argument int n; Celye i dejstvitel'nye chisla predstavleny v pamyati mashiny PO-RAZNOMU, poetomu chisla 12 i 12.0 hranyatsya v pamyati PO-RAZNOMU. Mashina umeet preobrazovyvat' celye chisla v dejstvitel'nye i naoborot, nado tol'ko skazat' ej ob etom. Operator (double) x nazyvaetsya "privedenie tipa k double". Zametim, chto chasto preobrazovanie tipa vypolnyaetsya avtomaticheski. Tak, naprimer, pri slozhenii int i double int avtomaticheski privoditsya k double, i rezul'tat imeet tip double. int var1; double var2, var3; var1 = 2; var2 = 2.0; var3 = var1 + var2; chto oznachaet na samom dele var3 = (double) var1 + var2; var3 stanet ravno 4.0 Bolee togo, k primeru tip char - eto tozhe CELYE CHISLA iz intervala 0...255. Kazhdaya bukva imeet kod ot 0 do 255. */  * 18_POINTERS.txt *  UKAZATELI ========= void f(int x){ x = 7; } main(){ int y = 17; f(y); printf("y=%d\n", y); /* pechataet: y=17 */ } V argumente x pereda£tsya KOPIYA znacheniya y, poetomu x=7; ne izmenyaet znacheniya u. Kak vse zhe sdelat', chtoby vyzyvaemaya funkciya mogla izmenyat' znachenie peremennoj? Otbrosim dva sposoba: - ob®yavlenie y kak global'noj (mnogo global'nyh peremennyh - plohoj stil'), - y=f(y); (a chto esli nado izmenit' MNOGO peremennyh? return, k neschast'yu, mozhet vernut' lish' odno znachenie). Ispol'zuetsya novaya dlya nas konstrukciya: UKAZATELX. -------------------------------------------------- Primer (@) void f(int *ptr){ /* #2 */ *ptr = 7; /* #3 */ } main (){ int y=17; f(&y); /* #1 */ printf("y=%d\n", y); /* pechataet: y=7 */ } Nu kak, nashli tri otlichiya ot ishodnogo teksta? ---------------------------------------------------------------------- My vvodim dve novye konstrukcii: &y "ukazatel' na peremennuyu y" ili "adres peremennoj y" *ptr oznachaet "razymenovanie ukazatelya ptr" (podrobnee - pozzhe) int *ptr; oznachaet ob®yavlenie peremennoj ptr, kotoraya mozhet soderzhat' v sebe ukazatel' na peremennuyu, hranyashchuyu int-chislo. Dlya nachala opredelim, chto takoe ukazatel'. int var1, var2, z; /* celochislennye peremennye */ int *pointer; /* ukazatel' na celochislennuyu peremennuyu */ var1 = 12; var2 = 43; pointer = &var1; My budem izobrazhat' ukazatel' v vide STRELKI; eto horoshij priem i pri prakticheskom programmirovanii. ________ /pointer/ _/_______/_ | znachenie| Peremennaya, hranyashchaya ukazatel' | est' | (adres drugoj peremennoj) | | | &var1 | | | |_______|_| | |&var1 - sam ukazatel' na var1 - | "strelka, ukazyvayushchaya na peremennuyu var1". V Ona oboznachaetsya &var1 ________ / var1 / _/_______/_ | znachenie| | est' | | 12 | |_________| Takim obrazom, UKAZATELX - eto "strelka, ukazyvayushchaya na nekij yashchik-peremennuyu". Nachalo etoj strelki mozhno (v svoyu ochered') hranit' v kakoj-nibud' peremennoj. Pri etom, esli strelka ukazyvaet na peremennuyu tipa int, to tip peremennoj, hranyashchej nachalo strelki, est' int * Esli tipa char, to tip - char * ADRES (ukazatel' na) mozhno vzyat' tol'ko ot peremennoj ili elementa massiva, no ne ot vyrazheniya. int x; int arr[5]; Zakonno &x strelka na yashchik "x" & arr[3] strelka na yashchik "arr[3]" Nezakonno &(2+2) tut net imenovannogo "yashchika", na kotoryj ukazyvaet strelka, da i voobshche yashchika net. ISPOLXZOVANIE UKAZATELEJ Ukazateli neskol'ko razlichno vedut sebya SLEVA i SPRAVA ot operatora prisvaivaniya. Nas interesuet novaya operaciya, primenyaemaya tol'ko k ukazatelyam: *pointer -------------------------------------------------------------------- SPRAVA ot prisvaivanij i v formulah =================================== *pointer oznachaet "vzyat' znachenie peremennoj (lezhashchee v yashchike), na kotoruyu ukazyvaet ukazatel', hranyashchijsya v peremennoj pointer". V nashem primere - eto chislo 12. To est' *pointer oznachaet "projti po strelke i vzyat' ukazyvaemoe eyu ZNACHENIE". printf("%d\n", *pointer); Pechataet 12; z = *pointer; /* ravnocenno z = 12; */ z = *pointer + 66; /* ravnocenno z = 12 + 66; */ Zastavim teper' ukazatel' ukazyvat' na druguyu peremennuyu (inache govorya, "prisvoim ukazatelyu adres drugoj peremennoj") pointer = &var2; ________ /pointer/ _/_______/_ | | | &var2 | | | |_______|_| | |&var2 | V ________ / var2 / _/_______/_ | | | 43 | | | |_________| Posle etogo z = *pointer; oznachaet z = 43; -------------------------------------------------------------------- Takim obrazom, konstrukciya z = *pointer; oznachaet z = *(&var2); oznachaet z = var2; To est' * i & vzaimno STIRAYUTSYA. SLEVA ot prisvaivaniya... *pointer = 123; Oznachaet "polozhit' znachenie pravoj chasti (t.e. 123) v peremennuyu (yashchik), na kotoryj