t' takoe pereopredelenie. Voznikayushchie iz-za etogo oshibki najti neprosto, vozmozhno potomu, chto oni dostatochno redki. Sledovatel'no, pereopredelenie imen sleduet svesti k minimumu. Esli vy oboznachaete global'nye peremennye ili lokal'nye peremennye v bol'shoj funkcii takimi imenami, kak i ili x, to sami naprashivaetes' na nepriyatnosti. Est' vozmozhnost' s pomoshch'yu operacii razresheniya oblasti vidimosti :: obratit'sya k skrytomu global'nomu imeni, naprimer: int x; void f2() { int x = 1; // skryvaet global'noe x ::x = 2; // prisvaivanie global'nomu x } Vozmozhnost' ispol'zovat' skrytoe lokal'noe imya otsutstvuet. Oblast' vidimosti imeni nachinaetsya v tochke ego opisaniya (po okonchanii opisatelya, no eshche do nachala inicializatora - sm. $$R.3.2). |to oznachaet, chto imya mozhno ispol'zovat' dazhe do togo, kak zadano ego nachal'noe znachenie. Naprimer: int x; void f3() { int x = x; // oshibochnoe prisvaivanie } Takoe prisvaivanie nedopustimo i lisheno smysla. Esli vy popytaetes' translirovat' etu programmu, to poluchite preduprezhdenie: "ispol'zovanie do zadaniya znacheniya". Vmeste s tem, ne primenyaya operatora ::, mozhno ispol'zovat' odno i to zhe imya dlya oboznacheniya dvuh razlichnyh ob容ktov bloka. Naprimer: int x = 11; void f4() // izvrashchennyj primer { int y = x; // global'noe x int x = 22; y = x; // lokal'noe x } Peremennaya y inicializiruetsya znacheniem global'nogo x, t.e. 11, a zatem ej prisvaivaetsya znachenie lokal'noj peremennoj x, t.e. 22. Imena formal'nyh parametrov funkcii schitayutsya opisannymi v samom bol'shom bloke funkcii, poetomu v opisanii nizhe est' oshibka: void f5(int x) { int x; // oshibka } Zdes' x opredeleno dvazhdy v odnoj i toj zhe oblasti vidimosti. |to hotya i ne slishkom redkaya, no dovol'no tonkaya oshibka. 2.1.2 Ob容kty i adresa Mozhno vydelyat' pamyat' dlya "peremennyh", ne imeyushchih imen, i ispol'zovat' eti peremennye. Vozmozhno dazhe prisvaivanie takim stranno vyglyadyashchim "peremennym", naprimer, *p[a+10]=7. Sledovatel'no, est' potrebnost' imenovat' "nechto hranyashcheesya v pamyati". Mozhno privesti podhodyashchuyu citatu iz spravochnogo rukovodstva: "Lyuboj ob容kt - eto nekotoraya oblast' pamyati, a adresom nazyvaetsya vyrazhenie, ssylayushcheesya na ob容kt ili funkciyu" ($$R.3.7). Slovu adres (lvalue - left value, t.e. velichina sleva) pervonachal'no pripisyvalsya smysl "nechto, chto mozhet v prisvaivanii stoyat' sleva". Adres mozhet ssylat'sya i na konstantu (sm. $$2.5). Adres, kotoryj ne byl opisan so specifikaciej const, nazyvaetsya izmenyaemym adresom. 2.1.3 Vremya zhizni ob容ktov Esli tol'ko programmist ne vmeshaetsya yavno, ob容kt budet sozdan pri poyavlenii ego opredeleniya i unichtozhen, kogda ischeznet iz oblasti vidimosti. Ob容kty s global'nymi imenami sozdayutsya, inicializiruyutsya (prichem tol'ko odin raz) i sushchestvuyut do konca programmy. Esli lokal'nye ob容kty opisany so sluzhebnym slovom static, to oni takzhe sushchestvuyut do konca programmy. Inicializaciya ih proishodit, kogda v pervyj raz upravlenie "prohodit cherez" opisanie etih ob容ktov, naprimer: int a = 1; void f() { int b = 1; // inicializiruetsya pri kazhdom vyzove f() static int c = a; // inicializiruetsya tol'ko odin raz cout << " a = " << a++ << " b = " << b++ << " c = " << c++ << '\n'; } int main() { while (a < 4) f(); } Zdes' programma vydast takoj rezul'tat: a = 1 b = 1 c = 1 a = 2 b = 1 c = 2 a = 3 b = 1 c = 3 ''Iz primerov etoj glavy dlya kratkosti izlozheniya isklyuchena makrokomanda #include <iostream>. Ona nuzhna lish' v teh iz nih, kotorye vydayut rezul'tat. Operaciya "++" yavlyaetsya inkrementom, t. e. a++ oznachaet: dobavit' 1 k peremennoj a. Global'naya peremennaya ili lokal'naya peremennaya static, kotoraya ne byla yavno inicializirovana, inicializiruetsya neyavno nulevym znacheniem (#2.4.5). Ispol'zuya operacii new i delete, programmist mozhet sozdavat' ob容kty, vremenem zhizni kotoryh on upravlyaet sam (sm. $$3.2.6). 2.2 IMENA Imya (identifikator) yavlyaetsya posledovatel'nost'yu bukv ili cifr. Pervyj simvol dolzhen byt' bukvoj. Bukvoj schitaetsya i simvol podcherkivaniya _. YAzyk S++ ne ogranichivaet chislo simvolov v imeni. No v realizaciyu vhodyat programmnye komponenty, kotorymi sozdatel' translyatora upravlyat' ne mozhet (naprimer, zagruzchik), a oni, k sozhaleniyu, mogut ustanavlivat' ogranicheniya. Krome togo, nekotorye sistemnye programmy, neobhodimye dlya vypolneniya programmy na S++, mogut rasshiryat' ili suzhat' mnozhestvo simvolov, dopustimyh v identifikatore. Rasshireniya (naprimer, ispol'zovanie $ v imeni) mogut narushit' perenosimost' programmy. Nel'zya ispol'zovat' v kachestve imen sluzhebnye slova S++ (sm. $$R.2.4), naprimer: hello this_is_a_most_unusially_long_name DEFINED foO bAr u_name HorseSense var0 var1 CLASS _class ___ Teper' privedem primery posledovatel'nostej simvolov, kotorye ne mogut ispol'zovat'sya kak identifikatory: 012 a fool $sys class 3var pay.due foo~bar .name if Zaglavnye i strochnye bukvy schitayutsya razlichnymi, poetomu Count i count - raznye imena. No vybirat' imena, pochti ne otlichayushchiesya drug ot druga, nerazumno. Vse imena, nachinayushchiesya s simvola podcherkivaniya, rezerviruyutsya dlya ispol'zovaniya v samoj realizacii ili v teh programmah, kotorye vypolnyayutsya sovmestno s rabochej, poetomu krajne legkomyslenno vstavlyat' takie imena v svoyu programmu. Pri razbore programmy translyator vsegda stremitsya vybrat' samuyu dlinnuyu posledovatel'nost' simvolov, obrazuyushchih imya, poetomu var10 - eto imya, a ne idushchie podryad imya var i chislo 10. Po toj zhe prichine elseif - odno imya (sluzhebnoe), a ne dva sluzhebnyh imeni else i if. 2.3 TIPY S kazhdym imenem (identifikatorom) v programme svyazan tip. On zadaet te operacii, kotorye mogut primenyat'sya k imeni (t.e. k ob容ktu, kotoryj oboznachaet imya), a takzhe interpretaciyu etih operacij. Privedem primery: int error_number; float real(complex* p); Poskol'ku peremennaya error_number opisana kak int (celoe), ej mozhno prisvaivat', a takzhe mozhno ispol'zovat' ee znacheniya v arifmeticheskih vyrazheniyah. Funkciyu real mozhno vyzyvat' s parametrom, soderzhashchim adres complex. Mozhno poluchat' adresa i peremennoj, i funkcii. Nekotorye imena, kak v nashem primere int i complex, yavlyayutsya imenami tipov. Obychno imya tipa nuzhno, chtoby zadat' v opisanii tipa nekotoroe drugoe imya. Krome togo, imya tipa mozhet ispol'zovat'sya v kachestve operanda v operaciyah sizeof (s ee pomoshch'yu opredelyayut razmer pamyati, neobhodimyj dlya ob容ktov etogo tipa) i new (s ee pomoshch'yu mozhno razmestit' v svobodnoj pamyati ob容kt etogo tipa). Naprimer: int main() { int* p = new int; cout << "sizeof(int) = " << sizeof(int) '\n'; } Eshche imya tipa mozhet ispol'zovat'sya v operacii yavnogo preobrazovaniya odnogo tipa k drugomu ($$3.2.5), naprimer: float f; char* p; //... long ll = long(p); // preobrazuet p v long int i = int(f); // preobrazuet f v int 2.3.1 Osnovnye tipy Osnovnye tipy S++ predstavlyayut samye rasprostranennye edinicy pamyati mashin i vse osnovnye sposoby raboty s nimi. |to: char short int int long int Perechislennye tipy ispol'zuyutsya dlya predstavleniya razlichnogo razmera celyh. CHisla s plavayushchej tochkoj predstavleny tipami: float double long double Sleduyushchie tipy mogut ispol'zovat'sya dlya predstavleniya bezznakovyh celyh, logicheskih znachenij, razryadnyh massivov i t.d.: unsigned char unsigned short int unsigned int unsigned long int Nizhe privedeny tipy, kotorye ispol'zuyutsya dlya yavnogo zadaniya znakovyh tipov: signed char signed short int signed int signed long int Poskol'ku po umolchaniyu znacheniya tipa int schitayutsya znakovymi, to sootvetstvuyushchie tipy s signed yavlyayutsya sinonimami tipov bez etogo sluzhebnogo slova. No tip signed char predstavlyaet osobyj interes: vse 3 tipa - unsigned char, signed char i prosto char schitayutsya razlichnymi (sm. takzhe $$R.3.6.1). Dlya kratkosti (i eto ne vlechet nikakih posledstvij) slovo int mozhno ne ukazyvat' v mnogoslovnyh tipah, t.e. long oznachaet long int, unsigned - unsigned int. Voobshche, esli v opisanii ne ukazan tip, to predpolagaetsya, chto eto int. Naprimer, nizhe dany dva opredeleniya ob容kta tipa int: const a = 1; // nebrezhno, tip ne ukazan static x; // tot zhe sluchaj Vse zhe obychno propusk tipa v opisanii v nadezhde, chto po umolchaniyu eto budet tip int, schitaetsya durnym stilem. On mozhet vyzvat' tonkij i nezhelatel'nyj effekt (sm. $$R.7.1). Dlya hraneniya simvolov i raboty s nimi naibolee podhodit tip char. Obychno on predstavlyaet bajt iz 8 razryadov. Razmery vseh ob容ktov v S++ kratny razmeru char, i po opredeleniyu znachenie sizeof(char) tozhdestvenno 1. V zavisimosti ot mashiny znachenie tipa char mozhet byt' znakovym ili bezznakovym celym. Konechno, znachenie tipa unsigned char vsegda bezznakovoe, i, zadavaya yavno etot tip, my uluchshaem perenosimost' programmy. Odnako, ispol'zovanie unsigned char vmesto char mozhet snizit' skorost' vypolneniya programmy. Estestvenno, znachenie tipa signed char vsegda znakovoe. V yazyk vvedeno neskol'ko celyh, neskol'ko bezznakovyh tipov i neskol'ko tipov s plavayushchej tochkoj, chtoby programmist mog polnee ispol'zovat' vozmozhnosti sistemy komand. U mnogih mashin znachitel'no razlichayutsya razmery vydelyaemoj pamyati, vremya dostupa i skorost' vychislenij dlya znachenij razlichnyh osnovnyh tipov. Kak pravilo, znaya osobennosti konkretnoj mashiny, legko vybrat' optimal'nyj osnovnoj tip (naprimer, odin iz tipov int) dlya dannoj peremennoj. Odnako, napisat' dejstvitel'no perenosimuyu programmu, ispol'zuyushchuyu takie vozmozhnosti nizkogo urovnya, neprosto. Dlya razmerov osnovnyh tipov vypolnyayutsya sleduyushchie sootnosheniya: 1==sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long) sizeof(float)<=sizeof(double)<=sizeof(long double) sizeof(I)==sizeof(signed I)==sizeof(unsigned I) Zdes' I mozhet byt' tipa char, short, int ili long. Pomimo etogo garantiruetsya, chto char predstavlen ne menee, chem 8 razryadami, short - ne menee, chem 16 razryadami i long - ne menee, chem 32 razryadami. Tip char dostatochen dlya predstavleniya lyubogo simvola iz nabora simvolov dannoj mashiny. No eto oznachaet tol'ko to, chto tip char mozhet predstavlyat' celye v diapazone 0..127. Predpolozhit' bol'shee - riskovanno. Tipy bezznakovyh celyh bol'she vsego podhodyat dlya takih programm, v kotoryh pamyat' rassmatrivaetsya kak massiv razryadov. No, kak pravilo, ispol'zovanie unsigned vmesto int, ne daet nichego horoshego, hotya takim obrazom rasschityvali vyigrat' eshche odin razryad dlya predstavleniya polozhitel'nyh celyh. Opisyvaya peremennuyu kak unsigned, nel'zya garantirovat', chto ona budet tol'ko polozhitel'noj, poskol'ku dopustimy neyavnye preobrazovaniya tipa, naprimer: unsigned surprise = -1; |to opredelenie dopustimo (hotya kompilyator mozhet vydat' preduprezhdenie o nem). 2.3.2 Neyavnoe preobrazovanie tipa V prisvaivanii i vyrazhenii osnovnye tipy mogut sovershenno svobodno ispol'zovat'sya sovmestno. Znacheniya preobrazovyvayutsya vsyudu, gde eto vozmozhno, takim obrazom, chtoby informaciya ne teryalas'. Tochnye pravila preobrazovanij dany v $$R.4 i $$R.5.4. Vse-taki est' situacii, kogda informaciya mozhet byt' poteryana ili dazhe iskazhena. Potencial'nym istochnikom takih situacij stanovyatsya prisvaivaniya, v kotoryh znachenie odnogo tipa prisvaivaetsya znacheniyu drugogo tipa, prichem v predstavlenii poslednego ispol'zuetsya men'she razryadov. Dopustim, chto sleduyushchie prisvaivaniya vypolnyayutsya na mashine, v kotoroj celye predstavlyayutsya v dopolnitel'nom kode, i simvol zanimaet 8 razryadov: int i1 = 256+255; char ch = i1 // ch == 255 int i2 = ch; // i2 == ? V prisvaivanii ch=i1 teryaetsya odin razryad (i samyj vazhnyj!), a kogda my prisvaivaem znachenie peremennoj i2, u peremennoj ch znachenie "vse edinicy", t.e. 8 edinichnyh razryadov. No kakoe znachenie primet i2? Na mashine DEC VAX, v kotoroj char predstavlyaet znakovye znacheniya, eto budet -1, a na mashine Motorola 68K, v kotoroj char - bezznakovyj, eto budet 255. V S++ net dinamicheskih sredstv kontrolya podobnyh situacij, a kontrol' na etape translyacii voobshche slishkom slozhen, poetomu nado byt' ostorozhnymi. 2.3.3 Proizvodnye tipy Ishodya iz osnovnyh (i opredelennyh pol'zovatelem) tipov, mozhno s pomoshch'yu sleduyushchih operacij opisaniya: * ukazatel' & ssylka [] massiv () funkciya a takzhe s pomoshch'yu opredeleniya struktur, zadat' drugie, proizvodnye tipy. Naprimer: int* a; float v[10]; char* p[20]; // massiv iz 20 simvol'nyh ukazatelej void f(int); struct str { short length; char* p; }; Pravila postroeniya tipov s pomoshch'yu etih operacij podrobno ob座asneny v $$R.8. Klyuchevaya ideya sostoit v tom, chto opisanie ob容kta proizvodnogo tipa dolzhno otrazhat' ego ispol'zovanie, naprimer: int v[10]; // opisanie vektora i = v[3]; // ispol'zovanie elementa vektora int* p; // opisanie ukazatelya i = *p; // ispol'zovanie ukazuemogo ob容kta Oboznacheniya, ispol'zuemye dlya proizvodnyh tipov, dostatochno trudny dlya ponimaniya lish' potomu, chto operacii * i & yavlyayutsya prefiksnymi, a [] i () - postfiksnymi. Poetomu v zadanii tipov, esli prioritety operacij ne otvechayut celi, nado stavit' skobki. Naprimer, prioritet operacii [] vyshe, chem u *, i my imeem: int* v[10]; // massiv ukazatelej int (*p)[10]; // ukazatel' massiva Bol'shinstvo lyudej prosto zapominaet, kak vyglyadyat naibolee chasto upotreblyaemye tipy. Mozhno opisat' srazu neskol'ko imen v odnom opisanii. Togda ono soderzhit vmesto odnogo imeni spisok otdelyaemyh drug ot druga zapyatymi imen. Naprimer, mozhno tak opisat' dve peremennye celogo tipa: int x, y; // int x; int y; Kogda my opisyvaem proizvodnye tipy, ne nado zabyvat', chto operacii opisanij primenyayutsya tol'ko k dannomu imeni (a vovse ne ko vsem ostal'nym imenam togo zhe opisaniya). Naprimer: int* p, y; // int* p; int y; NO NE int* y; int x, *p; // int x; int* p; int v[10], *p; // int v[10]; int* p; No takie opisaniya zaputyvayut programmu, i, vozmozhno, ih sleduet izbegat'. 2.3.4 Tip void Tip void sintaksicheski ekvivalenten osnovnym tipam, no ispol'zovat' ego mozhno tol'ko v proizvodnom tipe. Ob容ktov tipa void ne sushchestvuet. S ego pomoshch'yu zadayutsya ukazateli na ob容kty neizvestnogo tipa ili funkcii, nevozvrashchayushchie znachenie. void f(); // f ne vozvrashchaet znacheniya void* pv; // ukazatel' na ob容kt neizvestnogo tipa Ukazatel' proizvol'nogo tipa mozhno prisvaivat' peremennoj tipa void*. Na pervyj vzglyad etomu trudno najti primenenie, poskol'ku dlya void* nedopustimo kosvennoe obrashchenie (razymenovanie). Odnako, imenno na etom ogranichenii osnovyvaetsya ispol'zovanie tipa void*. On pripisyvaetsya parametram funkcij, kotorye ne dolzhny znat' istinnogo tipa etih parametrov. Tip void* imeyut takzhe bestipovye ob容kty, vozvrashchaemye funkciyami. Dlya ispol'zovaniya takih ob容ktov nuzhno vypolnit' yavnuyu operaciyu preobrazovaniya tipa. Takie funkcii obychno nahodyatsya na samyh nizhnih urovnyah sistemy, kotorye upravlyayut apparatnymi resursami. Privedem primer: void* malloc(unsigned size); void free(void*); void f() // raspredelenie pamyati v stile Si { int* pi = (int*)malloc(10*sizeof(int)); char* pc = (char*)malloc(10); //... free(pi); free(pc); } Oboznachenie: (tip) vyrazhenie - ispol'zuetsya dlya zadaniya operacii preobrazovaniya vyrazheniya k tipu, poetomu pered prisvaivaniem pi tip void*, vozvrashchaemyj v pervom vyzove malloc(), preobrazuetsya v tip int. Primer zapisan v arhaichnom stile; luchshij stil' upravleniya razmeshcheniem v svobodnoj pamyati pokazan v $$3.2.6. 2.3.5 Ukazateli Dlya bol'shinstva tipov T ukazatel' na T imeet tip T*. |to znachit, chto peremennaya tipa T* mozhet hranit' adres ob容kta tipa T. Ukazateli na massivy i funkcii, k sozhaleniyu, trebuyut bolee slozhnoj zapisi: int* pi; char** cpp; // ukazatel' na ukazatel' na char int (*vp)[10]; // ukazatel' na massiv iz 10 celyh int (*fp)(char, char*); // ukazatel' na funkciyu s parametrami // char i char*, vozvrashchayushchuyu int Glavnaya operaciya nad ukazatelyami - eto kosvennoe obrashchenie (razymenovanie), t.e. obrashchenie k ob容ktu, na kotoryj nastroen ukazatel'. |tu operaciyu obychno nazyvayut prosto kosvennost'yu. Operaciya kosvennosti * yavlyaetsya prefiksnoj unarnoj operaciej. Naprimer: char c1 = 'a'; char* p = &c1; // p soderzhit adres c1 char c2 = *p; // c2 = 'a' Peremennaya, na kotoruyu ukazyvaet p,- eto c1, a znachenie, kotoroe hranitsya v c1, ravno 'a'. Poetomu prisvaivaemoe c2 znachenie *p est' 'a'. Nad ukazatelyami mozhno vypolnyat' i nekotorye arifmeticheskie operacii. Nizhe v kachestve primera predstavlena funkciya, podschityvayushchaya chislo simvolov v stroke, zakanchivayushchejsya nulevym simvolom (kotoryj ne uchityvaetsya): int strlen(char* p) { int i = 0; while (*p++) i++; return i; } Mozhno opredelit' dlinu stroki po-drugomu: snachala najti ee konec, a zatem vychest' adres nachala stroki iz adresa ee konca. int strlen(char* p) { char* q = p; while (*q++) ; return q-p-1; } SHiroko ispol'zuyutsya ukazateli na funkcii; oni osobo obsuzhdayutsya v $$4.6.9 2.3.6 Massivy Dlya tipa T T[size] yavlyaetsya tipom "massiva iz size elementov tipa T". |lementy indeksiruyutsya ot 0 do size-1. Naprimer: float v[3]; // massiv iz treh chisel s plavayushchej tochkoj: // v[0], v[1], v[2] int a[2][5]; // dva massiva, iz pyati celyh kazhdyj char* vpc; // massiv iz 32 simvol'nyh ukazatelej Mozhno sleduyushchim obrazom zapisat' cikl, v kotorom pechatayutsya celye znacheniya propisnyh bukv: extern "C" int strlen(const char*); // iz <string.h> char alpha[] = "abcdefghijklmnopqrstuvwxyz"; main() { int sz = strlen(alpha); for (int i=0; i<sz; i++) { char ch = alpha[i]; cout << '\''<< ch << '\'' << " = " <<int(ch) << " = 0" << oct(ch) << " = 0x" << hex(ch) << '\n'; } } Zdes' funkcii oct() i hex() vydayut svoj parametr celogo tipa v vos'merichnom i shestnadcaterichnom vide sootvetstvenno. Obe funkcii opisany v <iostream.h>. Dlya podscheta chisla simvolov v alpha ispol'zuetsya funkciya strlen() iz <string.h>, no vmesto nee mozhno bylo ispol'zovat' razmer massiva alpha ($$2.4.4). Dlya mnozhestva simvolov ASCII rezul'tat budet takim: 'a' = 97 = 0141 = 0x61 'b' = 98 = 0142 = 0x62 'c' = 99 = 0143 = 0x63 ... Otmetim, chto ne nuzhno ukazyvat' razmer massiva alpha: translyator ustanovit ego, podschitav chislo simvolov v stroke, zadannoj v kachestve inicializatora. Zadanie massiva simvolov v vide stroki inicializatora - eto udobnyj, no k sozhaleniyu, edinstvennyj sposob podobnogo primeneniya strok. Prisvaivanie stroki massivu nedopustimo, poskol'ku v yazyke prisvaivanie massivam ne opredeleno, naprimer: char v[9]; v = "a string"; // oshibka Klassy pozvolyayut realizovat' predstavlenie strok s bol'shim naborom operacij (sm. $$7.10). Ochevidno, chto stroki prigodny tol'ko dlya inicializacii simvol'nyh massivov; dlya drugih tipov prihoditsya ispol'zovat' bolee slozhnuyu zapis'. Vprochem, ona mozhet ispol'zovat'sya i dlya simvol'nyh massivov. Naprimer: int v1[] = { 1, 2, 3, 4 }; int v2[] = { 'a', 'b', 'c', 'd' }; char v3[] = { 1, 2, 3, 4 }; char v4[] = { 'a', 'b', 'c', 'd' }; Zdes' v3 i v4 - massivy iz chetyreh (a ne pyati) simvolov; v4 ne okanchivaetsya nulevym simvolom, kak togo trebuyut soglashenie o strokah i bol'shinstvo bibliotechnyh funkcij. Ispol'zuya takoj massiv char my sami gotovim pochvu dlya budushchih oshibok. Mnogomernye massivy predstavleny kak massivy massivov. Odnako nel'zya pri zadanii granichnyh znachenij indeksov ispol'zovat', kak eto delaetsya v nekotoryh yazykah, zapyatuyu. Zapyataya - eto osobaya operaciya dlya perechisleniya vyrazhenij (sm. $$3.2.2). Mozhno poprobovat' zadat' takoe opisanie: int bad[5,2]; // oshibka ili takoe int v[5][2]; int bad = v[4,1]; // oshibka int good = v[4][1]; // pravil'no Nizhe opisyvaetsya massiv iz dvuh elementov, kazhdyj iz kotoryh yavlyaetsya, v svoyu ochered', massivom iz 5 elementov tipa char: char v[2][5]; V sleduyushchem primere pervyj massiv inicializiruetsya pyat'yu pervymi bukvami alfavita, a vtoroj - pyat'yu mladshimi ciframi. char v[2][5] = { { 'a', 'b', 'c', 'd', 'e' }, { '0', '1', '2', '3', '4' } }; main() { for (int i = 0; i<2; i++) { for (int j = 0; j<5; j++) cout << "v[" << i << "][" << j << "]=" << v[i][j] << " "; cout << '\n'; } } V rezul'tate poluchim: v[0][0]=a v[0][1]=b v[0][2]=c v[0][3]=d v[0][4]=e v[1][0]=0 v[1][1]=1 v[1][2]=2 v[1][3]=3 v[1][4]=4 2.3.7 Ukazateli i massivy Ukazateli i massivy v yazyke Si++ tesno svyazany. Imya massiva mozhno ispol'zovat' kak ukazatel' na ego pervyj element, poetomu primer s massivom alpha mozhno zapisat' tak: int main() { char alpha[] = "abcdefghijklmnopqrstuvwxyz"; char* p = alpha; char ch; while (ch = *p++) cout << ch << " = " << int (ch) << " = 0" << oct(ch) << '\n'; } Mozhno takzhe zadat' opisanie p sleduyushchim obrazom: char* p = &alpha[0]; |ta ekvivalentnost' shiroko ispol'zuetsya pri vyzovah funkcij s parametrom-massivom, kotoryj vsegda peredaetsya kak ukazatel' na ego pervyj element. Takim obrazom, v sleduyushchem primere v oboih vyzovah strlen peredaetsya odno i to zhe znachenie: void f() { extern "C" int strlen(const char*); // iz <string.h> char v[] = "Annemarie"; char* p = v; strlen(p); strlen(v); } No v tom i zagvoedka, chto obojti eto nel'zya: ne sushchestvuet sposoba tak opisat' funkciyu, chtoby pri ee vyzove massiv v kopirovalsya ($$4.6.3). Rezul'tat primeneniya k ukazatelyam arifmeticheskih operacij +, -, ++ ili -- zavisit ot tipa ukazuemyh ob容ktov. Esli takaya operaciya primenyaetsya k ukazatelyu p tipa T*, to schitaetsya, chto p ukazyvaet na massiv ob容ktov tipa T. Togda p+1 oboznachaet sleduyushchij element etogo massiva, a p-1 - predydushchij element. Otsyuda sleduet, chto znachenie (adres) p+1 budet na sizeof(T) bajtov bol'she, chem znachenie p. Poetomu v sleduyushchej programme main() { char cv[10]; int iv[10]; char* pc = cv; int* pi = iv; cout << "char* " << long(pc+1)-long(pc) << '\n'; cout << "int* " << long(pi+1)-long(pi) << '\n'; } s uchetom togo, chto na mashine avtora (Maccintosh) simvol zanimaet odin bajt, a celoe - chetyre bajta, poluchim: char* 1 int* 4 Pered vychitaniem ukazateli byli yavnoj operaciej preobrazovany k tipu long ($$3.2.5). On ispol'zovalsya dlya preobrazovaniya vmesto "ochevidnogo" tipa int, poskol'ku v nekotoryh realizaciyah yazyka S++ ukazatel' mozhet ne pomestit'sya v tip int (t.e. sizeof(int)<sizeof(char*)). Vychitanie ukazatelej opredeleno tol'ko v tom sluchae, kogda oni oba ukazyvayut na odin i tot zhe massiv (hotya v yazyke net vozmozhnostej garantirovat' etot fakt). Rezul'tat vychitaniya odnogo ukazatelya iz drugogo raven chislu (celoe) elementov massiva, nahodyashchihsya mezhdu etimi ukazatelyami. Mozhno skladyvat' s ukazatelem ili vychitat' iz nego znachenie celogo tipa; v oboih sluchayah rezul'tatom budet ukazatel'. Esli poluchitsya znachenie, ne yavlyayushcheesya ukazatelem na element togo zhe massiva, na kotoryj byl nastroen ishodnyj ukazatel' (ili ukazatelem na sleduyushchij za massivom element), to rezul'tat ispol'zovaniya takogo znacheniya neopredelen. Privedem primer: void f() { int v1[10]; int v2[10]; int i = &v1[5]-&v1[3]; // 2 i = &v1[5]-&v2[3]; // neopredelennyj rezul'tat int* p = v2+2; // p == &v2[2] p = v2-2; // *p neopredeleno } Kak pravilo, slozhnyh arifmeticheskih operacij s ukazatelyami ne trebuetsya i luchshe vsego ih izbegat'. Sleduet skazat', chto v bol'shinstve realizacij yazyka S++ net kontrolya nad granicami massivov. Opisanie massiva ne yavlyaetsya samodostatochnym, poskol'ku neobyazatel'no v nem budet hranit'sya chislo elementov massiva. Ponyatie massiva v S yavlyaetsya, po suti, ponyatiem yazyka nizkogo urovnya. Klassy pomogayut razvit' ego (sm. $$1.4.3). 2.3.8 Struktury Massiv predstavlyaet soboj sovokupnost' elementov odnogo tipa, a struktura yavlyaetsya sovokupnost'yu elementov proizvol'nyh (prakticheski) tipov. Naprimer: struct address { char* name; // imya "Jim Dandy" long number; // nomer doma 61 char* street; // ulica "South Street" char* town; // gorod "New Providence" char* state[2]; // shtat 'N' 'J' int zip; // indeks 7974 }; Zdes' opredelyaetsya novyj tip, nazyvaemyj address, kotoryj zadaet pochtovyj adres. Opredelenie ne yavlyaetsya dostatochno obshchim, chtoby uchest' vse sluchai adresov, no ono vpolne prigodno dlya primera. Obratite vnimanie na tochku s zapyatoj v konce opredeleniya: eto odin iz nemnogih v S++ sluchaev, kogda posle figurnoj skobki trebuetsya tochka s zapyatoj, poetomu pro nee chasto zabyvayut. Peremennye tipa address mozhno opisyvat' tochno tak zhe, kak i lyubye drugie peremennye, a s pomoshch'yu operacii . (tochka) mozhno obrashchat'sya k otdel'nym chlenam struktury. Naprimer: address jd; jd.name = "Jim Dandy"; jd.number = 61; Inicializirovat' peremennye tipa struct mozhno tak zhe, kak massivy. Naprimer: address jd = { "Jim Dandy", 61, "South Street", "New Providence", {'N','J'}, 7974 }; No luchshe dlya etih celej ispol'zovat' konstruktor ($$5.2.4). Otmetim, chto jd.state nel'zya inicializirovat' strokoj "NJ". Ved' stroki okanchivayutsya nulevym simvolom '\0', znachit v stroke "NJ" tri simvola, a eto na odin bol'she, chem pomeshchaetsya v jd.state. K strukturnym ob容ktam chasto obrashchayutsya c pomoshch'yu ukazatelej, ispol'zuya operaciyu ->. Naprimer: void print_addr(address* p) { cout << p->name << '\n' << p->number << ' ' << p->street << '\n' << p->town << '\n' << p->state[0] << p->state[1] << ' ' << p->zip << '\n'; } Ob容kty strukturnogo tipa mogut byt' prisvoeny, peredany kak fakticheskie parametry funkcij i vozvrashcheny funkciyami v kachestve rezul'tata. Naprimer: address current; address set_current(address next) { address prev = current; current = next; return prev; } Drugie dopustimye operacii, naprimer, takie, kak sravnenie (== i !=), neopredeleny. Odnako pol'zovatel' mozhet sam opredelit' eti operacii (sm. glavu 7). Razmer ob容kta strukturnogo tipa ne obyazatel'no raven summe razmerov vseh ego chlenov. |to proishodit po toj prichine, chto na mnogih mashinah trebuetsya razmeshchat' ob容kty opredelennyh tipov, tol'ko vyravnivaya ih po nekotoroj zavisyashchej ot sistemy adresacii granice (ili prosto potomu, chto rabota pri takom vyravnivanii budet bolee effektivnoj ). Tipichnyj primer - eto vyravnivanie celogo po slovnoj granice. V rezul'tate vyravnivaniya mogut poyavit'sya "dyrki" v strukture. Tak, na uzhe upominavshejsya mashine avtora sizeof(address) ravno 24, a ne 22, kak mozhno bylo ozhidat'. Sleduet takzhe upomyanut', chto tip mozhno ispol'zovat' srazu posle ego poyavleniya v opisanii, eshche do togo, kak budet zaversheno vse opisanie. Naprimer: struct link{ link* previous; link* successor; }; Odnako novye ob容kty tipa struktury nel'zya opisat' do teh por, poka ne poyavitsya ee polnoe opisanie. Poetomu opisanie struct no_good { no_good member; }; yavlyaetsya oshibochnym (translyator ne v sostoyanii ustanovit' razmer no_good). CHtoby pozvolit' dvum (ili bolee) strukturnym tipam ssylat'sya drug na druga, mozhno prosto opisat' imya odnogo iz nih kak imya nekotorogo strukturnogo tipa. Naprimer: struct list; // budet opredeleno pozdnee struct link { link* pre; link* suc; list* member_of; }; struct list { link* head; }; Esli by ne bylo pervogo opisaniya list, opisanie chlena link privelo by k sintaksicheskoj oshibke. Mozhno takzhe ispol'zovat' imya strukturnogo tipa eshche do togo, kak tip budet opredelen, esli tol'ko eto ispol'zovanie ne predpolagaet znaniya razmera struktury. Naprimer: class S; // 'S' - imya nekotorogo tipa extern S a; S f(); void g(S); No privedennye opisaniya mozhno ispol'zovat' lish' posle togo, kak tip S budet opredelen: void h() { S a; // oshibka: S - neopisano f(); // oshibka: S - neopisano g(a); // oshibka: S - neopisano } 2.3.9 |kvivalentnost' tipov Dva strukturnyh tipa schitayutsya razlichnymi dazhe togda, kogda oni imeyut odni i te zhe chleny. Naprimer, nizhe opredeleny razlichnye tipy: struct s1 { int a; }; struct s2 { int a; }; V rezul'tate imeem: s1 x; s2 y = x; // oshibka: nesootvetstvie tipov Krome togo, strukturnye tipy otlichayutsya ot osnovnyh tipov, poetomu poluchim: s1 x; int i = x; // oshibka: nesootvetstvie tipov Est', odnako, vozmozhnost', ne opredelyaya novyj tip, zadat' novoe imya dlya tipa. V opisanii, nachinayushchemsya sluzhebnym slovom typedef, opisyvaetsya ne peremennaya ukazannogo tipa, a vvoditsya novoe imya dlya tipa. Privedem primer: typedef char* Pchar; Pchar p1, p2; char* p3 = p1; |to prosto udobnoe sredstvo sokrashcheniya zapisi. 2.3.10 Ssylki Ssylku mozhno rassmatrivat' kak eshche odno imya ob容kta. V osnovnom ssylki ispol'zuyutsya dlya zadaniya parametrov i vozvrashchaemyh funkciyami znachenij , a takzhe dlya peregruzki operacij (sm.$$7). Zapis' X& oboznachaet ssylku na X. Naprimer: int i = 1; int& r = i; // r i i ssylayutsya na odno i to zhe celoe int x = r; // x = 1 r = 2; // i = 2; Ssylka dolzhna byt' inicializirovana, t.e. dolzhno byt' nechto, chto ona mozhet oboznachat'. Sleduet pomnit', chto inicializaciya ssylki sovershenno otlichaetsya ot operacii prisvaivaniya. Hotya mozhno ukazyvat' operacii nad ssylkoj, ni odna iz nih na samu ssylku ne dejstvuet, naprimer, int ii = 0; int& rr = ii; rr++; // ii uvelichivaetsya na 1 Zdes' operaciya ++ dopustima, no rr++ ne uvelichivaet samu ssylku rr; vmesto etogo ++ primenyaetsya k celomu, t.e. k peremennoj ii. Sledovatel'no, posle inicializacii znachenie ssylki ne mozhet byt' izmeneno: ona vsegda ukazyvaet na tot ob容kt, k kotoromu byla privyazana pri ee inicializacii. CHtoby poluchit' ukazatel' na ob容kt, oboznachaemyj ssylkoj rr, mozhno napisat' &rr. Ochevidnoj realizaciej ssylki mozhet sluzhit' postoyannyj ukazatel', kotoryj ispol'zuetsya tol'ko dlya kosvennogo obrashcheniya. Togda inicializaciya ssylki budet trivial'noj, esli v kachestve inicializatora ukazan adres (t.e. ob容kt, adres kotorogo mozhno poluchit'; sm. $$R.3.7). Inicializator dlya tipa T dolzhen byt' adresom. Odnako, inicializator dlya &T mozhet byt' i ne adresom, i dazhe ne tipom T. V takih sluchayah delaetsya sleduyushchee: [1] vo-pervyh, esli neobhodimo, primenyaetsya preobrazovanie tipa (sm.$$R.8.4.3); [2] zatem poluchivsheesya znachenie pomeshchaetsya vo vremennuyu peremennuyu; [3] nakonec, adres etoj peremennoj ispol'zuetsya v kachestve inicializatora ssylki. Pust' imeyutsya opisaniya: double& dr = 1; // oshibka: nuzhen adres const double& cdr = 1; // normal'no |to interpretiruetsya tak: double* cdrp; // ssylka, predstavlennaya kak ukazatel' double temp; temp = double(1); cdrp = &temp; Ssylki na peremennye i ssylki na konstanty razlichayutsya po sleduyushchej prichine: v pervom sluchae sozdanie vremennoj peremennoj chrevato oshibkami, poskol'ku prisvaivanie etoj peremennoj oznachaet prisvaivanie vremennoj peremennoj, kotoraya mogla k etomu momentu ischeznut'. Estestvenno, chto vo vtorom sluchae podobnyh problem ne sushchestvuet. i ssylki na konstanty chasto ispol'zuyutsya kak parametry funkcij (sm.$$R.6.3). Ssylka mozhet ispol'zovat'sya dlya funkcii, kotoraya izmenyaet znachenie svoego parametra. Naprimer: void incr(int& aa) { aa++; } void f() { int x = 1; incr(x); // x = 2 } Po opredeleniyu peredacha parametrov imeet tu zhe semantiku, chto i inicializaciya, poetomu pri vyzove funkcii incr ee parametr aa stanovitsya drugim imenem dlya x. Luchshe, odnako, izbegat' izmenyayushchih svoi parametry funkcij, chtoby ne zaputyvat' programmu. V bol'shinstve sluchaev predpochtitel'nee, chtoby funkciya vozvrashchala rezul'tat yavnym obrazom, ili chtoby ispol'zovalsya parametr tipa ukazatelya: int next(int p) { return p+1; } void inc(int* p) { (*p)++; } void g() { int x = 1; x = next(x); // x = 2 inc(&x); // x = 3 } Krome perechislennogo, s pomoshch'yu ssylok mozhno opredelit' funkcii, ispol'zuemye kak v pravoj, tak i v levoj chastyah prisvaivaniya. Naibolee interesnoe primenenie eto obychno nahodit pri opredelenii netrivial'nyh pol'zovatel'skih tipov. V kachestve primera opredelim prostoj associativnyj massiv. Nachnem s opredeleniya struktury pair: struct pair { char* name; // stroka int val; // celoe }; Ideya zaklyuchaetsya v tom, chto so strokoj svyazyvaetsya nekotoroe celoe znachenie. Netrudno napisat' funkciyu poiska find(), kotoraya rabotaet so strukturoj dannyh, predstavlyayushchej associativnyj massiv. V nem dlya kazhdoj otlichnoj ot drugih stroki soderzhitsya struktura pair (para: stroka i znachenie ). V dannom primere - eto prosto massiv. CHtoby sokratit' primer, ispol'zuetsya predel'no prostoj, hotya i neeffektivnyj algoritm: const int large = 1024; static pair vec[large+1]; pair* find(const char* p) /* // rabotaet so mnozhestvom par "pair": // ishchet p, esli nahodit, vozvrashchaet ego "pair", // v protivnom sluchae vozvrashchaet neispol'zovannuyu "pair" */ { for (int i=0; vec[i].name; i++) if (strcmp(p,vec[i].name)==0) return &vec[i]; if (i == large) return &vec[large-1]; return &vec[i]; } |tu funkciyu ispol'zuet funkciya value(), kotoraya realizuet massiv celyh, indeksiruemyj strokami (hotya privychnee stroki indeksirovat' celymi): int& value(const char* p) { pair* res = find(p); if (res->name == 0) { // do sih por stroka ne vstrechalas', // znachit nado inicializirovat' res->name = new char[strlen(p)+1]; strcpy(res->name,p); res->val = 0; // nachal'noe znachenie ravno 0 } return res->val; } Dlya zadannogo parametra (stroki) value() nahodit ob容kt, predstavlyayushchij celoe (a ne prosto znachenie sootvetstvuyushchego celogo) i vozvrashchaet ssylku na nego. |ti funkcii mozhno ispol'zovat', naprimer, tak: const int MAX = 256; // bol'she dliny samogo dlinnogo slova main() // podschityvaet chastotu slov vo vhodnom potoke { char buf[MAX]; while (cin>>buf) value(buf)++; for (int i=0; vec[i].name; i++) cout << vec[i].name << ": " << vec [i].val<< '\n'; } V cikle while iz standartnogo vhodnogo potoka cin chitaetsya po odnomu slovu i zapisyvaetsya v bufer buf (sm. glava 10), pri etom kazhdyj raz znachenie schetchika, svyazannogo so schityvaemoj strokoj, uvelichivaetsya. Schetchik otyskivaetsya v associativnom massive vec s pomoshch'yu funkcii find(). V cikle for pechataetsya poluchivshayasya tablica razlichnyh slov iz cin vmeste s ih chastotoj. Imeya vhodnoj potok aa bb bb aa aa bb aa aa programma vydaet: aa: 5 bb: 3 S pomoshch'yu shablonnogo klassa i peregruzhennoj operacii [] ($$8.8) dostatochno prosto dovesti massiv iz etogo primera do nastoyashchego associativnogo massiva. 2.4 LITERALY V S++ mozhno zadavat' znacheniya vseh osnovnyh tipov: simvol'nye konstanty, celye konstanty i konstanty s plavayushchej tochkoj. Krome togo, nul' (0) mozhno ispol'zovat' kak znachenie ukazatelya proizvol'nogo tipa, a simvol'nye stroki yavlyayutsya konstantami tipa char[]. Est' vozmozhnost' opredelit' simvolicheskie konstanty. Simvolicheskaya konstanta - eto imya, znachenie kotorogo v ego oblasti vidimosti izmenyat' nel'zya. V S++ simvolicheskie konstanty mozhno zadat' tremya sposobami: (1) dobaviv sluzhebnoe slovo const v opredelenii, mozhno svyazat' s imenem lyuboe znachenie proizvol'nogo tipa; (2) mnozhestvo celyh konstant mozhno opredelit' kak perechislenie; (3) konstantoj yavlyaetsya imya massiva ili funkcii. 2.4.1 Celye konstanty Celye konstanty mogut poyavlyat'sya v chetyreh oblich'yah: desyatichnye, vos'merichnye, shestnadcaterichnye i simvol'nye konstanty. Desyatichnye konstanty ispol'zuyutsya chashche vsego i vyglyadyat estestvenno: 0 1234 976 12345678901234567890 Desyatichnaya konstanta imeet tip int, esli ona umeshchaetsya v pamyat', otvodimuyu dlya int, v protivnom sluchae ee tip long. Translyator dolzhen preduprezhdat' o konstantah, velichina kotoryh prevyshaet vybrannyj format predstavleniya chisel. Konstanta, nachinayushchayasya s nulya, za kotorym sleduet x (0x), yavlyaetsya shestnadcaterichnym chislom (s osnovaniem 16), a konstanta, kotoraya nachinayushchayasya s nulya, za kotorym sleduet cifra, yavlyaetsya vos'merichnym chislom (s osnovaniem 8). Privedem primery vos'merichnyh konstant: 0 02 077 0123 Ih desyatichnye ekvivalenty ravny sootvetstvenno: 0, 2, 63, 83. V shestnadcaterichnoj zapisi eti konstanty vyglyadyat tak: 0x0 0x2 0x3f 0x53 Bukvy a, b, c, d, e i f ili ekvivalentnye im zaglavnye bukvy ispol'zuyutsya dlya predstavleniya chisel 10, 11, 12, 13, 14 i 15, sootvetstvenno. Vos'merichnaya i shestnadcaterichnaya formy zapisi naibolee podhodyat dlya zadaniya nabora razryadov, a ispol'zovanie ih dlya obychnyh chisel mozhet dat' neozhidannyj effekt. Naprimer, na mashine, v kotoroj int predstavlyaetsya kak 16-razryadnoe chislo v dopolnitel'nom kode, 0xffff est' otricatel'noe desyatichnoe chislo -1. Esli by dlya predstavleniya celogo ispol'zovalos' bol'shee chislo razryadov, to eto bylo by chislom 65535. Okonchanie U mozhet ispol'zovat'sya dlya yavnogo zadaniya konstant tipa unsigned. Analogichno, okonchanie L yavno zadaet konstantu tipa long. Naprimer: void f(int); void f(unsigned int); void f(long int); void g() { f(3); // vyzov f(int) f(3U); // vyzov f(unsigned int) f(3L); // vyzov f(long int) } 2.4.2 Konstanty s plavayushchej tochkoj Konstanty s plavayushchej tochkoj imeyut tip double. Translyator dolzhen preduprezhdat' o takih konstantah, znachenie kotoryh ne ukladyvaetsya v format, vybrannyj dlya predstavleniya chisel s plavayushchej tochkoj. Privedem primery konstant s plavayushchej tochkoj: 1.23 .23 0.23 1. 1.0 1.2e10 1.23e-15 Otmetim, chto vnutri konstanty s plavayushchej tochkoj ne dolzhno byt' probelov. Naprimer, 65.43 e-21 ne yavlyaetsya konstantoj s plavayushchej tochkoj, translyator raspoznaet eto kak chetyre otdel'nye leksemy: 65.43 e - 21 chto vyzovet sintaksicheskuyu oshibku. Esli nuzhna konstanta s plavayushchej tochkoj tipa float, to ee mozhno poluchit', ispol'zuya okonchanie f: 3.14159265f 2.0f 2.997925f 2.4.3 Simvol'nye konstanty Simvol'noj konstantoj yavlyaetsya simvol, zaklyuchennyj v odinochnye kavychki, naprimer, 'a' ili '0'. Simvol'nye konstanty mozhno schitat' konstantami, kotorye dayut imena celym znacheniyam simvolov iz nabora, prinyatogo na mashine, na kotoroj vypolnyaetsya programma. |to neobyazatel'no tot zhe nabor simvolov, kotoryj est' na mashine, gde programma translirovalas'. Takim obrazom, esli vy zapuskaete programmu na mashine, ispol'zuyushchej nabor simvolov ASCII, to znachenie '0' ravno 48, a esli mashina ispol'zuet kod EBCDIC, to ono budet ravno 240. Ispol'zovanie simvol'nyh konstant vmesto ih desyatichnogo celogo ekvivalenta povyshaet perenosimost' programm. Nekotorye special'nye kombinacii simvolov, nachinayushchiesya s obratnoj drobnoj cherty, imeyut standartnye nazvaniya: Konec stroki NL(LF) \n Gorizont