Si të konfiguroni telefonat inteligjentë dhe PC. Portali informativ

Llojet dhe variablat e të dhënave. Llojet dhe operacionet e të dhënave C

Në këtë tutorial, do të mësoni Alfabeti C ++ dhe gjithashtu çfarë llojet e të dhënave mund të përpunohet nga programi në të. Ndoshta ky nuk është momenti më emocionues, por kjo njohuri është e nevojshme!Përveç kësaj, duke filluar të mësoni ndonjë gjuhë tjetër programimi, do të kaloni me më shumë besim në një fazë të ngjashme të të mësuarit. Një program C ++ mund të përmbajë karakteret e mëposhtme:

  • shkronjat latine të mëdha, të vogla A, B, C…, x, y, z dhe nënvizim;
  • Numrat arabë nga 0 deri në 9;
  • karaktere speciale: (), | , () + - /% *. \":?< > = ! & # ~ ; ^
  • hapësirë, skedë dhe karaktere të linjës së re.

Në testin e programit, mund të përdorni komentet... Nëse teksti me dy prerje përpara // dhe përfundon me një karakter të linjës së re ose është i mbyllur midis / * dhe * /, përpiluesi e shpërfill atë.

Të dhënat në C ++

Për të zgjidhur një problem në çdo program, çdo e dhënë përpunohet. Ato mund të jenë të llojeve të ndryshme: numra të plotë dhe realë, simbole, vargje, vargje. Është e zakonshme të përshkruhen të dhënat në C ++ në fillim të një funksioni. TE llojet bazë të të dhënave gjuha përfshin:

Për formimin e llojeve të tjera të të dhënave, themelore dhe të ashtuquajtura specifikuesit. C ++ përcakton katër specifikues të tipit të të dhënave:

  • i shkurtër - i shkurtër;
  • i gjatë - i gjatë;
  • nënshkruar - nënshkruar;
  • i panënshkruar - i panënshkruar.

Lloji i plotë

Variabla e llojit ndër në memorien e kompjuterit mund të marrë ose 2 ose 4 bajt. Varet nga bititeti i procesorit. Si parazgjedhje, të gjitha llojet e numrave të plotë konsiderohen të nënshkruar, domethënë specifikuesi nënshkruar mund të hiqet. Specifikimi e panënshkruar lejon që të paraqiten vetëm numra pozitivë. Më poshtë janë disa vargje të vlerave të numrave të plotë

Lloji i Gama Permasa
ndër -2147483648…2147483647 4 bajt
i panënshkruar ndër 0…4294967295 4 bajt
nënshkruar int -2147483648…2147483647 4 bajt
shkurt int -32768…32767 2 bajt
gjatë int -2147483648…2147483647 4 bajt
i panënshkruar i shkurtër int 0…65535 2 bajt

Lloji i vërtetë

Një numër me pikë lundruese përfaqësohet në formën mE + - p, ku m është mantisa (numër i plotë ose i pjesshëm me një pikë dhjetore), p është rendi (numër i plotë). Zakonisht vlerat si noton zënë 4 bajt, dhe dyfishtë 8 bajt. Tabela e diapazonit real:

noton 3.4E-38 ... 3.4E + 38 4 bajt
dyfishtë 1.7E-308 ... 1.7E + 308 8 bajt
dyfish i gjatë 3.4E-4932 ... 3.4E + 4932 8 bajt

Lloji Boolean

Variabla e llojit bool mund të marrë vetëm dy vlera e vertete ( e vërtetë ) ose fals ( Gënjeshtra ). Çdo vlerë tjetër përveç zeros interpretohet si e vërtetë. Kuptimi i rremë përfaqësohet në memorie si 0.

Lloji i zbrazët

Shumë vlera të këtij lloji janë bosh. Përdoret për të përcaktuar funksionet që nuk kthejnë një vlerë, për të specifikuar një listë të zbrazët të argumenteve të funksionit, si një lloj bazë për treguesit dhe në operacionet e transmetimit.

Konvertimi i llojit të të dhënave

C ++ bën dallimin midis dy llojeve të konvertimit të tipit të të dhënave: eksplicit dhe implicit.

  • Konvertimi i nënkuptuar ndodh automatikisht. Kjo bëhet gjatë krahasimit, caktimit ose vlerësimit të shprehjeve të llojeve të ndryshme. Për shembull, programi i mëposhtëm do të printojë në tastierë një vlerë të ngjashme noton.

#include "stdafx.h" #include duke përdorur hapësirën e emrave std; int main () (int i = 5; float f = 10.12; cout<> void "); ktheje 0;)

#include "stdafx.h"

#përfshi

duke përdorur hapësirën e emrave std;

int main ()

int i = 5; float f = 10,12;

cout<< i / f ;

sistemi ("pauzë >> void");

kthimi 0;

Lloji me humbjen më të vogël të informacionit i jepet përparësia më e lartë. Nuk duhet të abuzoni me konvertimin e nënkuptuar të tipit, pasi mund të lindin të gjitha llojet e situatave të papritura.

  • Konvertimi i qartë në krahasim me të bërë në mënyrë implicite nga programuesi. Ka disa mënyra për ta bërë këtë:
  1. Konvertimi në stile C: (noton) a
  2. Konvertimi në stile C ++: noton ()

Gjithashtu, konvertimet e tipit mund të kryhen duke përdorur operacionet e mëposhtme:

static_cast<>() const_cast<>() reinterpret_cast<>() dynamic_cast<> ()

static_cast<> ()

const_cast<> ()

reinterpret_cast<> ()

dinamik_cast<> ()

static_cas- kryen konvertimin e llojeve të të dhënave të lidhura. Ky operator hedh lloje sipas rregullave të zakonshme, të cilat mund të kërkohen kur përpiluesi nuk kryen konvertim automatik. Sintaksa do të duket si kjo:

Lloji static_cast<Тип>(nje objekt);

Duke përdorur static_cast, nuk mund të hiqni qëndrueshmërinë nga një ndryshore, por kjo është brenda fuqisë së deklaratës së mëposhtme. const_cast- përdoret vetëm kur është e nevojshme të hiqen konstantet nga objekti. Sintaksa do të duket si kjo:

Lloji iconst_cast< Lloji i> (nje objekt);

reinterpret_cast- përdoret për të kthyer lloje të ndryshme, numra të plotë në një tregues dhe anasjelltas. Nëse shihni fjalën e re "tregues" - mos u shqetësoni! ky është gjithashtu një lloj i të dhënave, por ne nuk do të punojmë me të së shpejti. Sintaksa këtu është e njëjtë si për operatorët e konsideruar më parë:

Lloji iriinterpretojnë_cast< Lloji i> (nje objekt);

dinamik_cast- përdoret për konvertimin e tipit dinamik, zbaton hedhjen e pointerëve ose lidhjeve. Sintaksë:

Lloji idinamike _cast< Lloji i> (nje objekt);

Personazhet e kontrollit

Ju tashmë jeni njohur me disa nga këto "karaktere kontrolli" (për shembull, me \ n). Ata të gjithë fillojnë me një vijë të prapme dhe janë gjithashtu të rrethuar nga thonjëza të dyfishta.

Imazhi

Kodi heksadecimal

Emri

Tingulli bipues

Backtrack

Përkthimi i faqes (format)

Përkthimi i linjës

Kthim ngarkese

Skeda horizontale

Skeda vertikale

Një ndryshim i rëndësishëm midis gjuhës C dhe gjuhëve të tjera (PL1, FORTRAN, etj.) është mungesa e një parimi të paracaktuar, i cili çon në nevojën për të deklaruar të gjitha variablat e përdorura në program në mënyrë eksplicite së bashku me treguesin e llojeve të tyre përkatëse. .

Deklaratat e ndryshueshme kanë formatin e mëposhtëm:

[memory-class-specifier] specifikues tip-specifier [= iniciator] [, specifikues [= iniciator]] ...

Një përshkrues është një identifikues i një ndryshoreje të thjeshtë ose një konstruksioni më kompleks me kllapa katrore, kllapa ose një yll (një grup ylli).

Një specifikues tipi është një ose më shumë fjalë kyçe që përcaktojnë llojin e ndryshores që deklarohet. Gjuha C ka një grup standard të llojeve të të dhënave që mund të përdoren për të ndërtuar lloje të reja (unike) të të dhënave.

Iniciator - specifikon një vlerë fillestare ose një listë të vlerave fillestare që (të cilat) i caktohen një ndryshoreje kur deklarohet.

Specifikimi i klasës së kujtesës përcaktohet nga një nga katër fjalët kyçe të gjuhës C: auto, extern, regjistër, statik dhe tregon se si do të ndahet memoria për variablin e deklaruar, nga njëra anë, dhe nga ana tjetër, shtrirja e kjo variabël, pra nga cilat pjesë të programit mund t'i referoheni.

1.2.1 Kategoritë e llojeve të të dhënave

Fjalë kyçe për përcaktimin e llojeve bazë të të dhënave

Llojet e numrave të plotë: Llojet lundruese: char float int dyfish i shkurtër i gjatë dyfish i gjatë i nënshkruar i panënshkruar

Një variabël i çdo lloji mund të deklarohet i pamodifikueshëm. Kjo arrihet duke shtuar fjalën kyçe const në specifikuesin e tipit. Objektet Const përfaqësojnë të dhëna vetëm për lexim, d.m.th. kësaj ndryshore nuk mund t'i caktohet një vlerë e re. Vini re se nëse nuk ka lloj-specifikues pas fjalës const, atëherë supozohet specifikuesi i tipit int. Nëse kryefjala const vjen para deklarimit të llojeve të përbëra (arresë, strukturë, përzierje, numërim), atëherë kjo çon në faktin se çdo element duhet të jetë gjithashtu i pamodifikueshëm, d.m.th. mund t'i caktohet një vlerë vetëm një herë.

Const dyfish A = 2,128E-2; konst B = 286; (nënkupton konstancën int B = 286)

Shembujt e deklarimit të të dhënave të përbëra do të diskutohen më poshtë.

1.2.2. Lloji i të dhënave me numër të plotë

Për të përcaktuar të dhënat e një lloji të plotë, përdoren fjalë kyçe të ndryshme që përcaktojnë gamën e vlerave dhe madhësinë e zonës së kujtesës të caktuar për variablat (Tabela 6).

Tabela 6

Vini re se fjalët kyçe të nënshkruara dhe të panënshkruara janë opsionale. Ato tregojnë se si interpretohet biti zero i variablës së deklaruar, pra nëse specifikohet fjala kyçe e panënshkruar, atëherë biti zero interpretohet si pjesë e numrit, përndryshe biti zero interpretohet si i nënshkruar. Nëse fjala kyçe e panënshkruar mungon, ndryshorja numër i plotë konsiderohet e nënshkruar. Në rast se specifikuesi i tipit përbëhet nga lloji i çelësit i nënshkruar ose i panënshkruar dhe më pas vijon identifikuesi i ndryshores, atëherë ai do të konsiderohet si një variabël i tipit int. Për shembull:

I panënshkruar int n; i panënshkruar int b; int c; (nënshkrimi int c nënkuptohet); e panënshkruar d; (nënkupton int d të panënshkruar); nënshkruar f; (nënkuptohet me nënshkrimin f).

Vini re se modifikuesi char përdoret për të përfaqësuar një karakter (nga një grup paraqitjesh karakteresh) ose për të deklaruar vargje literale. Vlera e një objekti të tipit char është një kod (1 bajt në madhësi) që korrespondon me karakterin që duhet të përfaqësohet. Për të përfaqësuar karakteret e alfabetit rus, modifikuesi i llojit të identifikuesit të të dhënave ka formën e shkronjave të panënshkruara, pasi kodet e shkronjave ruse tejkalojnë vlerën 127.

Duhet bërë vërejtja e mëposhtme: gjuha C nuk përcakton një paraqitje të memories dhe një gamë vlerash për identifikuesit me modifikues int dhe int të panënshkruar. Madhësia e kujtesës për një ndryshore me një modifikues int të nënshkruar përcaktohet nga gjatësia e fjalës së makinës, e cila ka madhësi të ndryshme në makina të ndryshme. Pra, në makinat 16-bit, madhësia e fjalës është e barabartë me 2 bajt, në makinat 32-bit, përkatësisht, me 4 bajt, d.m.th. lloji int është ekuivalent me llojet short int, ose long int, në varësi të arkitekturës së kompjuterit të përdorur. Kështu, i njëjti program mund të funksionojë siç duhet në një kompjuter dhe të mos funksionojë siç duhet në një tjetër. Për të përcaktuar gjatësinë e memories së zënë nga një variabël, mund të përdorni funksionin sizeof të gjuhës C, e cila kthen vlerën e gjatësisë së llojit të modifikuesit të specifikuar.

Për shembull:

A = madhësia e (int); b = madhësia (e gjatë int); c = madhësia (e gjatë e panënshkruar); d = madhësia (e shkurtër);

Vini re gjithashtu se konstantet oktale dhe heksadecimal mund të kenë gjithashtu modifikuesin e panënshkruar. Kjo arrihet duke specifikuar parashtesën u ose U pas konstantes; një konstante pa këtë parashtesë konsiderohet e nënshkruar.

Për shembull:

0xA8C (int nënshkruar); 01786l (e nënshkruar gjatë); 0xF7u (int e panënshkruar);

1.2.3. Të dhëna lundruese

Për variablat që përfaqësojnë një numër me pikë lundruese, përdoren modifikuesit e mëposhtëm të tipit: float, double, long double (në disa zbatime të gjuhës C, long double mungon).

Një vlerë float merr 4 bajt. Nga këto, 1 bajt është rezervuar për shenjën, 8 bit për eksponentin e tepërt dhe 23 bit për mantisa. Vini re se pjesa më e rëndësishme e mantisës është gjithmonë 1, kështu që nuk është e mbushur; prandaj, diapazoni i vlerave për variablin e pikës lundruese është afërsisht 3.14E-38 deri në 3.14E + 38.

Një dyshe zë 8 bit në memorie. Formati i tij është i ngjashëm me atë të float. Bitët e memories ndahen si më poshtë: 1 bit për shenjën, 11 bit për eksponentin dhe 52 bit për mantisa. Duke marrë parasysh pjesën e hequr të rendit të lartë të mantisës, diapazoni i vlerave është nga 1.7E-308 në 1.7E + 308.

Float f, a, b; dyfishi x, y;

1.2.4. Treguesit

Një tregues është adresa e memories së caktuar për vendosjen e identifikuesit (emri i një ndryshoreje, grupi, strukture ose vargu literal mund të përdoret si identifikues). Në rast se një ndryshore deklarohet si tregues, atëherë ajo përmban një adresë memorie në të cilën mund të gjendet një vlerë skalare e çdo lloji. Me rastin e deklarimit të një variabli të tipit të treguesit, është e nevojshme të përcaktohet lloji i objektit të të dhënave, adresa e të cilit do të përmbajë variablin dhe emrin e treguesit, të paraprirë nga një yll (ose një grup yjesh). Formati i deklaratës së treguesit:

tip-specifikues [modifikues] * specifikues.

Specifikuesi i tipit specifikon llojin e objektit dhe mund të jetë i çdo lloji bazë, lloj strukture, përzierje (kjo do të diskutohet më poshtë). Duke specifikuar fjalën kyçe void në vend të specifikuesit të tipit, është e mundur në një mënyrë të veçantë të shtyhet specifikimi i llojit të cilit i referohet treguesi. Një variabël i deklaruar si një tregues për të tipit void mund të përdoret për t'iu referuar një objekti të çdo lloji. Sidoqoftë, për të qenë në gjendje të kryeni veprime aritmetike dhe logjike në tregues ose në objektet në të cilat ata tregojnë, është e nevojshme të përcaktohet në mënyrë eksplicite lloji i objekteve gjatë kryerjes së secilit operacion. Përkufizime të tilla të tipit mund të kryhen duke përdorur një operacion cast.

Fjalët kyçe konst, afër, larg, i madh mund të përdoren si modifikues kur deklaroni një tregues. Fjala kyçe const tregon se treguesi nuk mund të ndryshohet në program. Madhësia e një variabli të deklaruar si tregues varet nga arkitektura e kompjuterit dhe nga modeli i memories së përdorur për të cilin do të kompilohet programi. Treguesit për lloje të ndryshme të dhënash nuk duhet të kenë të njëjtën gjatësi.

Fjalët kyçe afër, larg, i madh mund të përdoren për të modifikuar madhësinë e treguesit.

I panënshkruar int * a; / * ndryshorja a është një tregues për llojin int të panënshkruar (numra të plotë të panënshkruar) * / double * x; / * ndryshorja x tregon llojin e të dhënave me pikë lundruese me saktësi të dyfishtë * / char * fuffer; / * deklarohet një tregues me emrin fuffer i cili tregon një variabël të tipit char * / double nomer; void * adresat; adresat = & nomer; (dopio *) adresat ++; / * Ndryshorja adresa deklarohet si tregues për një objekt të çdo lloji. Prandaj, mund t'i caktohet adresa e çdo objekti (& është operacioni i llogaritjes së një adrese). Megjithatë, siç u përmend më lart, asnjë operacion aritmetik nuk mund të kryhet në një tregues derisa lloji i të dhënave që ai tregon të përcaktohet në mënyrë eksplicite. Kjo mund të bëhet duke përdorur një operacion cast (double *) për të kthyer adresat në një tregues për të dyfishuar dhe më pas për të rritur adresën. * / konst * dr; / * Ndryshorja dr deklarohet si tregues për një shprehje konstante, d.m.th. vlera e një treguesi mund të ndryshojë gjatë ekzekutimit të programit, por vlera në të cilën tregon jo. * / char i panënshkruar * const w = & obj. / * Ndryshorja w deklarohet si një tregues konstant për të dhënat e tipit char unsigned. Kjo do të thotë që gjatë gjithë programit do të tregojmë në të njëjtën zonë të memories. Përmbajtja e kësaj zone mund të ndryshohet. * /

1.2.5. Variablat e numëruar

Një variabël që mund të marrë një vlerë nga një listë vlerash quhet një ndryshore e numëruar ose një numërim.

Një deklaratë numërimi fillon me fjalën kyçe enum dhe ka dy formate prezantimi.

Formati 1. enum [enum-tag-emër] (numërimi-lista) përshkruesi [, përshkruesi ...];

Formati 2. përshkruesi enum enum-tag-emri [, përshkruesi ..];

Një deklaratë numërimi specifikon llojin e një ndryshoreje numërimi dhe përcakton një listë të konstantave të emërtuara të quajtura listë e numërimit. Vlera e çdo emri liste është një numër i plotë.

Një variabël i llojit të numërimit mund të marrë vlerat e njërës prej konstanteve të emërtuara në listë. Konstantet e listave të emërtuara janë të tipit int. Kështu, memoria që korrespondon me variablin e numërimit është memoria e nevojshme për të akomoduar një vlerë int.

Variablat e tipit enum mund të përdoren në shprehjet e indeksit dhe si operandë në operacionet aritmetike dhe relacionale.

Në formatin e parë 1, emrat dhe vlerat e numërimit specifikohen në listën e numërimit. Emri i etiketës opsionale enumeration është një identifikues që emërton etiketën e numërimit të përcaktuar nga lista e numërimit. Përshkruesi emërton një ndryshore numëruese. Më shumë se një ndryshore e llojit të numërimit mund të specifikohet në një deklaratë.

List-numërimi përmban një ose më shumë konstruksione të formës:

identifikues [= shprehje konstante]

Çdo identifikues emërton një element të numërimit. Të gjitha ID-të në listën e numërimit duhet të jenë unike. Në mungesë të një shprehjeje konstante, identifikuesi i parë korrespondon me vlerën 0, identifikuesi tjetër me vlerën 1, e kështu me radhë. Emri i një konstante numërimi është ekuivalent me vlerën e saj.

Identifikuesi i lidhur me një shprehje konstante merr vlerën e specifikuar nga ajo shprehje konstante. Një shprehje konstante duhet të jetë e tipit int dhe mund të jetë pozitive ose negative. Identifikuesit tjetër në listë i caktohet një vlerë shprehje konstante plus 1 nëse ai identifikues nuk ka një shprehje konstante. Përdorimi i anëtarëve të numërimit duhet t'u bindet rregullave të mëposhtme:

1. Një variabël mund të përmbajë vlera të dyfishta.

2. Identifikuesit në një listë numërimi duhet të jenë të dallueshëm nga të gjithë identifikuesit e tjerë në të njëjtin shtrirje, duke përfshirë emrat e ndryshoreve të zakonshme dhe identifikuesit nga listat e tjera të numërimit.

3. Emrat e llojeve të numërimit duhet të jenë të ndryshëm nga emrat e tjerë të llojeve, strukturave dhe përzierjeve të numërimit në të njëjtin shtrirje.

4. Vlera mund të ndjekë elementin e fundit të listës së numërimit.

Një javë (SUB = 0, / * 0 * / VOS = 0, / * 0 * / POND, / * 1 * / VTOR, / * 2 * / SRED, / * 3 * / HETV, / * 4 * / PJAT / * 5 * /) rab_ned;

Në këtë shembull, deklarohet etiketa e numëruar javë, me grupin përkatës të vlerave, dhe ndryshorja rab_ned deklarohet të jetë e tipit week.

Formati i dytë përdor emrin e etiketës së numërimit për t'iu referuar një lloji numërimi që është përcaktuar diku tjetër. Emri i etiketës së numërimit duhet t'i referohet një etikete numërimi të përcaktuar tashmë brenda fushëveprimit aktual. Për shkak se etiketa enum është deklaruar diku tjetër, numërimi nuk është i pranishëm në deklaratë.

Në deklarimin e një treguesi për një lloj të dhënash të numërimit dhe deklarimin e tipareve për llojet e numërimit, mund të përdorni emrin e një etikete numërimi përpara se të përcaktohet ai etiketë numërimi. Sidoqoftë, përkufizimi i një numërimi duhet t'i paraprijë çdo veprimi të treguesit të përdorur ndaj llojit të deklaratës typedef. Një deklaratë pa një listë të mëvonshme përshkruesish përshkruan një etiketë, ose, nëse mund të them kështu, një model numërimi.

1.2.6. Vargjeve

Vargjet janë një grup elementësh të të njëjtit lloj (double, float, int, etj.). Nga deklarimi i një vargu, përpiluesi duhet të marrë informacion për llojin e elementeve të grupit dhe numrin e tyre. Një deklaratë grupi ka dy formate:

përshkruesi i tipit specifikues [konstante - shprehje];

doreza e specifikuesit të tipit;

Një përshkrues është një identifikues i grupit.

Specifikuesi i tipit specifikon llojin e elementeve të grupit të deklaruar. Elementet e grupit nuk mund të jenë funksione dhe elemente të pavlefshme.

Shprehja konstante me kllapa specifikon numrin e elementeve në grup. Shprehja e konstitit mund të hiqet kur deklarohet një grup në rastet e mëposhtme:

Kur deklarohet, grupi inicializohet,

Vargu deklarohet si parametër formal i funksionit,

Në gjuhën C, përcaktohen vetëm vargje njëdimensionale, por meqenëse një element i grupit mund të jetë një grup, ju gjithashtu mund të përcaktoni vargje shumëdimensionale. Ato formalizohen nga një listë e shprehjeve konstante që ndjekin identifikuesin e grupit, me secilën shprehje konstante të mbyllur në kllapat e veta katrore.

Çdo shprehje konstante në kllapa katrore përcakton numrin e elementeve për një dimension të caktuar të vargut, kështu që një deklaratë dydimensionale e vargut përmban dy shprehje konstante, një tredimensionale, e kështu me radhë. Vini re se në gjuhën C, elementi i parë i një grupi ka një indeks të barabartë me 0.

Int a; / * e përfaqësuar si një matricë a a a a a a * / dyfish b; / * vektor i 10 elementeve të tipit të dyfishtë * / int w = ((2, 3, 4), (3, 4, 8), (1, 0, 9));

Shembulli i fundit deklaron një grup w. Listat e mbyllura në kllapa kaçurrelë korrespondojnë me vargjet e vargjeve; nëse nuk ka kllapa, inicializimi nuk do të kryhet si duhet.

Në gjuhën C, mund të përdorni feta grupesh, si në gjuhët e tjera të nivelit të lartë (PL1, etj.), Sidoqoftë, një sërë kufizimesh vendosen në përdorimin e fetave. Seksionet formohen duke hequr një ose më shumë palë kllapa katrore. Çiftet e kllapave katrore mund të hidhen vetëm nga e djathta në të majtë dhe në mënyrë strikte në mënyrë të njëpasnjëshme. Seksionet e vargjeve përdoren në organizimin e procesit llogaritës në funksionet e gjuhës C, të zhvilluara nga përdoruesi.

Nëse, gjatë thirrjes së një funksioni, shkruhet s, atëherë vargu zero i vargut s do të kalohet.

Kur hyni në grupin b, mund të shkruani, për shembull, b dhe do të kalohet një vektor me katër elementë, dhe qasja në b do të japë një grup dydimensional prej 3 me 4. Ju nuk mund të shkruani b, duke nënkuptuar se një vektor do të jetë kaloi, sepse kjo nuk përputhet me kufizimin e vendosur në seksionet e përdorimit të grupit.

Një shembull i deklarimit të një grupi karakteresh.

char str = "deklarimi i një grupi karakteresh";

Vini re se ka një element më shumë në një karakter literal, pasi elementi i fundit është sekuenca e arratisjes "\ 0".

1.2.7. Strukturat

Strukturat janë një objekt i përbërë që përmban elementë të çdo lloji, përveç funksioneve. Ndryshe nga një grup, i cili është një objekt homogjen, struktura mund të jetë heterogjene. Lloji i strukturës përcaktohet nga një procesverbal i formularit:

struct (lista e përkufizimeve)

Struktura duhet të përmbajë të paktën një komponent. Përkufizimi i strukturave është si më poshtë:

përshkrues i tipit të të dhënave;

ku tipi i të dhënave tregon llojin e strukturës për objektet e përcaktuara në përshkruesit. Në formën e tyre më të thjeshtë, përshkruesit janë identifikues ose vargje.

Struktura (x dyfishtë, y;) s1, s2, sm; struct (int viti; char mole, dita;) date1, date2;

Variablat s1, s2 përcaktohen si struktura, secila prej të cilave përbëhet nga dy komponentë x dhe y. Ndryshorja sm përkufizohet si një grup prej nëntë strukturash. Secila prej dy variablave date1, date2 përbëhet nga tre komponentë vit, molë, ditë. > p> Ekziston një mënyrë tjetër për të lidhur një emër me një lloj strukture, ajo bazohet në përdorimin e etiketës së strukturës. Një etiketë strukture është e ngjashme me një etiketë të numëruar. Etiketa e strukturës përcaktohet si më poshtë:

etiketa struct (lista e përshkrimeve;);

ku etiketa është një identifikues.

Në shembullin më poshtë, ID-ja e studentit përshkruhet si një etiketë strukture:

Nxënësi strukt (emri char; int id, mosha; char prp;);

Etiketa e strukturës përdoret për të deklaruar më pas strukturat e një lloji të caktuar në formën:

struct identifikuesit e listës së etiketave;

struct Studeut st1, st2;

Përdorimi i etiketave të strukturës është i nevojshëm për të përshkruar strukturat rekursive. Përdorimi i etiketave të strukturës rekursive është diskutuar më poshtë.

Nyja e strukturës (të dhënat int; nyja e strukturës * tjetër;) st1_nyja;

Etiketa e strukturës së nyjeve është me të vërtetë rekursive pasi përdoret në përshkrimin e vet, d.m.th. në formalizimin e treguesit të ardhshëm. Strukturat nuk mund të jenë drejtpërdrejt rekursive, d.m.th. një strukturë nyje nuk mund të përmbajë një komponent që është një strukturë nyje, por çdo strukturë mund të ketë një komponent që është një tregues për llojin e saj, siç është bërë në shembullin e mësipërm.

Komponentët e strukturës aksesohen duke specifikuar emrin e strukturës dhe në vijim, të ndarë me një pikë, emrin e komponentit të zgjedhur, për shembull:

St1.emri = "Ivanov"; st2.id = st1.id; st1_node.data = st1.moshë;

1.2.8. Shoqatat (përzierjet)

Një bashkim është i ngjashëm me një strukturë, megjithatë, në çdo kohë të caktuar, vetëm një nga elementët e bashkimit mund të përdoret (ose, me fjalë të tjera, të jetë i përgjegjshëm). Lloji i bashkimit mund të specifikohet si më poshtë:

Bashkimi (përshkrimi i elementit 1; ... përshkrimi i elementit n;);

Karakteristika kryesore e bashkimit është se e njëjta zonë memorie ndahet për secilin nga elementët e deklaruar, d.m.th. ato mbivendosen. Megjithëse qasja në këtë zonë të memories është e mundur duke përdorur cilindo element, elementi për këtë qëllim duhet të zgjidhet në mënyrë që rezultati të mos jetë i pakuptimtë.

Anëtarët e sindikatës aksesohen në të njëjtën mënyrë si strukturat. Etiketa union mund të zyrtarizohet në të njëjtën mënyrë si etiketa e strukturës.

Bashkimi përdoret për qëllimet e mëposhtme:

Inicializimi i objektit të memories së përdorur, nëse në një moment të caktuar vetëm një objekt nga shumë është aktiv;

Interpretoni paraqitjen bazë të një objekti të një lloji sikur objektit t'i ishte caktuar një lloj tjetër.

Kujtesa që korrespondon me një variabël të llojit të bashkimit përcaktohet nga sasia e nevojshme për të akomoduar anëtarin më të gjatë të bashkimit. Kur përdoret një element më i shkurtër, një variabël e tipit union mund të përmbajë memorie të papërdorur. Të gjithë elementët e bashkimit ruhen në të njëjtën zonë memorie, duke filluar nga e njëjta adresë.

Union (char fio; char adres; int vozrast; int telefon;) informoj; bashkim (int ax; char al;) ua;

Kur përdorni objektin infor të tipit union, mund të përpunoni vetëm elementin që ka marrë vlerën, d.m.th. pasi t'i caktohet një vlerë elementit inform.fio, nuk ka kuptim t'i referohemi elementeve të tjerë. Kombinimi i ua lejon akses të veçantë në bajtin e poshtëm ua.al dhe atë të sipërm ua.al të numrit ua.ax me dy bajtë.

1.2.9. Fushat bit

Anëtari i strukturës mund të jetë një fushë biti që ofron akses në pjesët individuale të memories. Fushat bit nuk mund të deklarohen jashtë strukturave. Ju gjithashtu nuk mund të organizoni grupe fushash bit dhe nuk mund të aplikoni operacionin e përcaktimit të adresës në fusha. Në përgjithësi, lloji i një strukture me një fushë bit specifikohet si më poshtë:

Struktura (identifikuesi i panënshkruar 1: gjatësia e fushës 1; identifikuesi i panënshkruar 2: gjatësia e fushës 2;)

gjatësia - fushat përcaktohen si shprehje me numër të plotë ose konstante. Kjo konstante përcakton numrin e biteve të caktuara në fushën përkatëse. Një fushë me gjatësi zero tregon shtrirjen me kufirin e fjalës tjetër.

Struct (i panënshkruar a1: 1; i panënshkruar a2: 2; i panënshkruar a3: 5; i panënshkruar a4: 2;) prim;

Strukturat Bitfield mund të përmbajnë gjithashtu komponentë karakteresh. Komponentë të tillë vendosen automatikisht në kufijtë e duhur të fjalëve dhe disa pjesë të fjalëve mund të mbeten të papërdorura.

1.2.10. Variabla me strukturë të ndryshueshme

Shumë shpesh, disa objekte programi i përkasin të njëjtës klasë, që ndryshojnë vetëm në disa detaje. Konsideroni, për shembull, paraqitjen e formave gjeometrike. Informacioni i përgjithshëm rreth formave mund të përfshijë elementë të tillë si zona, perimetri. Megjithatë, informacioni përkatës për dimensionet gjeometrike mund të ndryshojnë në varësi të formës së tyre.

Konsideroni një shembull në të cilin informacioni rreth formave gjeometrike paraqitet bazuar në përdorimin e kombinuar të strukturës dhe bashkimit.

Figura e strukturës (zona e dyfishtë, perimetri; / * komponentët e zakonshëm * / lloji int; / * atributi i komponentit * / bashkimi / * numërimi i përbërësve * / (rrezja e dyfishtë; / * rrethi * / dyfishi a; / * drejtkëndëshi * / dyfishi b ; / * trekëndësh * /) gjeom_fig;) fig1, fig2;

Në përgjithësi, çdo objekt i figurës do të përbëhet nga tre komponentë: zona, perimetri, lloji. Komponenti i tipit quhet etiketa e komponentit aktiv sepse përdoret për të treguar se cili komponent i bashkimit geom_fig është aktualisht aktiv. Një strukturë e tillë quhet strukturë e ndryshueshme sepse përbërësit e saj ndryshojnë në varësi të vlerës së etiketës së komponentit aktiv (vlera e tipit).

Vini re se në vend të komponentit tip të tipit int, do të ishte e këshillueshme të përdorni një tip të numëruar. Për shembull, të tilla

Njerëz figura_shah (RRETH, KUTI, TREKËNDËSH);

Konstantat CIRCLE, BOX, TRIANGLE do të marrin respektivisht vlerat 0, 1, 2. Variabla tip mund të deklarohet se ka një tip të numëruar:

shifër enum_lloji shahu;

Në këtë rast, përpiluesi C do të paralajmërojë programuesin për caktime të mundshme të gabuara, si p.sh.

figura.lloji = 40;

Në përgjithësi, një ndryshore e strukturës do të përbëhet nga tre pjesë: një grup përbërësish të zakonshëm, një etiketë përbërëse aktive dhe një pjesë me komponentë të ndryshueshëm. Forma e përgjithshme e një strukture të ndryshueshme është si më poshtë:

Struktura (komponentët e zakonshëm; etiketa e komponentit aktiv; bashkimi (përshkrimi i komponentit 1; përshkrimi i komponentit 2; ::: përshkrimi i komponentit n;) identifikuesi i bashkimit;) identifikuesi i strukturës;

Një shembull i përcaktimit të një ndryshoreje strukture të quajtur helth_record

Struktura (/ * informacion i përgjithshëm * / emri i karakterit; / * emri * / mosha int; / * mosha * / seksi i karakterit; / * gjinia * / / * etiketa e komponentit aktiv * / / * (statusi martesor) * / numër merital_status ; / * pjesë e ndryshueshme * / bashkim (/ * beqar * / / * nuk ka komponentë * / struct (/ * i martuar * / char marripge_date; char spouse_name; int no_children;) wedding_info; / * i divorcuar * / char data_divorced;) marital_info; ) rekordi_shëndetësor; enum marital_status (BASHKËR, / * beqar * / MARRIGO, / * i martuar * / I DIVORUR / * i divorcuar * /);

Ju mund t'i referoheni përbërësve të strukturës duke përdorur lidhjet:

Helth_record.neme, helth_record.ins, helth_record.marriage_info.marriage_date.

1.2.11. Përcaktimi i objekteve dhe llojeve

Siç u përmend më lart, të gjitha variablat e përdorur në programet C duhet të deklarohen. Lloji i ndryshores së deklaruar varet nga cila fjalë kyçe përdoret si specifikues i tipit dhe nëse specifikuesi është një identifikues i thjeshtë ose një kombinim i një identifikuesi me një modifikues tregues (yll), një grup (kllapa katrore), ose një funksion (kllapa ).

Kur deklaroni një ndryshore të thjeshtë, strukturë, përzierje ose bashkim, ose një numërim, një përshkrues është një identifikues i thjeshtë. Për të deklaruar një tregues, grup ose funksion, identifikuesi modifikohet në përputhje me rrethanat: me një yll në të majtë, katror ose kllapa në të djathtë.

Vini re një veçori të rëndësishme të gjuhës C, kur deklaroni më shumë se një modifikues mund të përdoret njëkohësisht, gjë që bën të mundur krijimin e shumë përshkruesve të ndryshëm të tipit kompleks.

Sidoqoftë, duhet të mbahet mend se disa kombinime të modifikuesve nuk lejohen:

Elementet e grupit nuk mund të jenë funksione,

Funksionet nuk mund të kthejnë vargje ose funksione.

Kur inicializohen përshkruesit kompleksë, kllapat katrore dhe kllapat (në të djathtë të identifikuesit) kanë përparësi ndaj yllit (në të majtë të identifikuesit). Kllapat ose kllapat kanë të njëjtën përparësi dhe zgjerohen nga e majta në të djathtë. Specifikuesi i tipit merret parasysh në hapin e fundit, kur specifikuesi tashmë është interpretuar plotësisht. Ju mund të përdorni kllapa për të ndryshuar rendin e interpretimit sipas nevojës.

Për interpretimin e përshkrimeve komplekse, propozohet një rregull i thjeshtë, i cili tingëllon si "nga brenda jashtë" dhe përbëhet nga katër hapa.

1. Filloni me një identifikues dhe shikoni në të djathtë për të parë nëse ka kllapa ose kllapa.

2. Nëse janë, atëherë interpretoni këtë pjesë të përshkruesit dhe më pas shikoni majtas për një yll.

3. Nëse në ndonjë fazë në të djathtë ka një kllapë mbyllëse, atëherë së pari është e nevojshme të zbatohen të gjitha këto rregulla brenda kllapave, dhe më pas të vazhdohet interpretimi.

4. Interpretoni specifikuesin e tipit.

Int * (* comp) (); 6 5 3 1 2 4

Në këtë shembull, ndryshorja comp (1) deklarohet si një grup prej dhjetë (2) treguesish (3) në funksionet (4) duke i kthyer treguesit (5) në vlerat e numrave të plotë (6).

Char * (* (* var) ()); 7 6 4 2 1 3 5

Ndryshorja var (1) deklarohet si një tregues (2) në një funksion (3) që kthen një tregues (4) në një grup (5) me 10 elementë, të cilët janë tregues (6) për vlerat char.

Përveç deklarimit të variablave të llojeve të ndryshme, është e mundur të deklarohen edhe lloje. Kjo mund të bëhet në dy mënyra. Mënyra e parë është të jepni një emër tag kur deklaroni një strukturë, bashkim ose numërim, dhe më pas përdorni atë emër në deklaratat e variablave dhe funksioneve si referencë për atë etiketë. E dyta është përdorimi i fjalës kyçe typedef për të deklaruar llojin.

Kur deklarohet me fjalën kyçe typedef, identifikuesi në vend të objektit që përshkruhet është emri i llojit të të dhënave që merret në konsideratë dhe më pas ky lloj mund të përdoret për të deklaruar variablat.

Vini re se çdo lloj mund të deklarohet duke përdorur fjalën kyçe typedef, duke përfshirë tipet e treguesve, funksioneve ose grupeve. Një emër me fjalën kyçe typedef për tipet e treguesit, strukturës, bashkimit mund të deklarohet përpara se të përcaktohen këto lloje, por brenda fushëveprimit të deklaruesit.

Typedef double (* MATH) (); / * MATH - emri i tipit të ri që përfaqëson një tregues në një funksion që kthen vlera të dyfishta * / MATH cos; / * cos është një tregues për një funksion që kthen vlerat e tipit double * / / * Mund të bëhet një deklaratë ekuivalente * / double (* cos) (); typedef char FIO / * FIO - grup prej dyzet karakteresh * / person FIO; / * Personi i ndryshueshëm është një grup prej dyzet karakteresh * / / * Kjo është e barabartë me deklarimin * / person char;

Gjatë deklarimit të variablave dhe llojeve, këtu janë përdorur emrat e tipave (MATH FIO). Për më tepër, emrat e tipave mund të përdoren në tre raste të tjera: në listën e parametrave formalë, në deklarimin e funksioneve, në operacionet e tipit casting dhe në operacionin sizeof (operacioni i tipit casting).

Emrat e llojeve për llojet bazë, llojet e numërimit, strukturat dhe përzierjet janë specifikuesit e tipit për ato lloje. Emrat e tipave për treguesit e grupeve dhe llojet e funksioneve janë specifikuar duke përdorur përshkruesit abstraktë si më poshtë:

tip-specifikues abstrakt-përshkrues;

Një abstrakt përshkruesi është një përshkrues pa një identifikues që përbëhet nga një ose më shumë tregues, vargje ose modifikues funksioni. Modifikuesi i treguesit (*) jepet gjithmonë përpara identifikuesit në përshkrues, dhe modifikuesit e vargut dhe funksionit () specifikohen gjithmonë pas tij. Kështu, për të interpretuar saktë një përshkrues abstrakt, duhet të filloni me një identifikues të nënkuptuar.

Përshkruesit abstraktë mund të jenë të ndërlikuar. Kllapat në përshkruesit kompleksë abstraktë përcaktojnë rendin e interpretimit, ashtu siç bënë kur interpretonin përshkruesit kompleksë në deklarata.

1.2.12. Inicializimi i të dhënave

Kur deklaroni një ndryshore, mund t'i caktoni një vlerë fillestare duke i bashkangjitur një iniciator përshkruesit. Iniciatori fillon me një shenjë "=" dhe ka format e mëposhtme.

Formati 1: = iniciator;

Formati 2: = (lista - iniciatorët);

Formati 1 përdoret kur inicializohen variabla të llojeve dhe treguesve bazë, dhe formati 2 përdoret kur inicializohen objektet e përbëra.

Variabla tol inicializohet me "N".

konst i gjatë megabute = (1024 * 1024);

Ndryshorja e pamodifikueshme megabute inicializohet me një shprehje konstante pas së cilës nuk mund të ndryshohet.

statike int b = (1,2,3,4);

Inicializohet një grup dy-dimensionale me numra të plotë b; elementëve të grupit u caktohen vlera nga lista. I njëjti inicializim mund të bëhet si më poshtë:

statike int b = ((1,2), (3,4));

Kur inicializoni një grup, mund të hiqni një ose më shumë dimensione

static int b type_ identifikues specifikues [, identifikues] ...

Modifikuesit - fjalë kyçe të nënshkruara, të panënshkruara, të shkurtra, të gjata.
Specifikuesi i tipit është një fjalë kyçe char ose int që përcakton llojin e ndryshores që deklarohet.
Identifikuesi është emri i ndryshores.

Char x; int a, b, c; i panënshkruar i gjatë i gjatë y;

Kur deklaroni një ndryshore, mund të inicializoni, domethënë t'i caktoni një vlerë fillestare.

Int x = 100;

Kur deklarohet, numri 100 do të shkruhet menjëherë në ndryshoren x. Është më mirë të deklarohen variablat e inicializuar në rreshta të veçantë.

Llojet e të dhënave. Një program në gjuhët procedurale, të cilit C i përket, është një përshkrim i operacioneve mbi vlera të llojeve të ndryshme. Një lloj përcakton grupin e vlerave që një vlerë mund të marrë dhe grupin e operacioneve në të cilat ajo mund të marrë pjesë.

Në C, llojet shoqërohen me emrat (identifikuesit) e vlerave, domethënë me variabla. Një qelizë memorie shoqërohet me një variabël në gjuhën C. Lloji i një ndryshoreje specifikon madhësinë e qelizës, metodën e kodimit të përmbajtjes së saj dhe transformimet e lejuara mbi vlerën e kësaj ndryshore. Të gjitha variablat duhet të deklarohen përpara përdorimit të tyre. Çdo variabël duhet të deklarohet vetëm një herë.

Përshkrimi përbëhet nga një specifikues tipi i ndjekur nga një listë variablash. Variablat në listë ndahen me presje. Në fund të përshkrimit vendoset një pikëpresje.

Shembuj përshkrimesh:

char a, b; / * Variablat a dhe b janë të tipit

char * / intх; / * Variabli x - i tipit int

* / char sym; / "Përshkruhen variablat sym të tipit char;

* / int count.num; / * numri dhe numri i tipit int * /

Variablave mund t'u caktohen vlera fillestare brenda deklaratave të tyre. Nëse emri i ndryshores ndiqet nga një shenjë e barabartë dhe një konstante, atëherë ajo konstante shërben si iniciator.

Shembuj: char backch = "\ 0";

Le të shqyrtojmë llojet kryesore në gjuhën C.

int - e tërë ("numër i plotë"). Vlerat e këtij lloji janë numra të plotë nga një gamë e kufizuar (zakonisht nga 32768 në 32767). Gama përcaktohet nga madhësia e qelizës për llojin dhe varet nga kompjuteri specifik. Përveç kësaj, ka fjalë të veçanta që mund të përdoren me llojin int: short int ("short integer"), unsigned int ("numër i plotë i panënshkruar" - "numër i plotë i panënshkruar"), long int ("numër i plotë i gjatë" ), të cilat shkurtojnë ose, anasjelltas, zgjeroni gamën e paraqitjes së numrave.

karakter- karakter ("karakter"). Vlera e vlefshme për këtë lloj është një karakter (të mos ngatërrohet me tekstin!). Personazhi është shkruar me apostrofa.

Shembuj:"x" 2 "?"

Në kujtesën e kompjuterit, një karakter zë një bajt. Në fakt, nuk ruhet një karakter, por një numër - një kod karakteri (nga 0 në 255). Të gjithë karakteret e vlefshme dhe kodet e tyre përkatëse tregohen në tabela të veçanta të kodimit.

Në gjuhën C, lejohet të përdoret lloji char si numerik, domethënë të kryeni veprime me kodin e karakterit, duke përdorur specifikuesin e llojit të plotë në kllapa - (int).

float - real (pikë lundruese). Vlerat e këtij lloji janë numra, por ndryshe nga char dhe int, ato nuk janë domosdoshmërisht numra të plotë.

12,87 -316,12 -3,345e5 12,345e-15

numra realë me saktësi të dyfishtë. Ky lloj është i ngjashëm me llojin float, por ka një gamë shumë më të gjerë vlerash (për shembull, për sistemin e programimit Borland-C nga 1.7E-308 në 1.7E + 308 në vend të diapazonit nga 3.4E-38 në 3.4E + 38 për tipin float). Sidoqoftë, një rritje në diapazonin dhe saktësinë e paraqitjes së numrave çon në një ulje të shpejtësisë së ekzekutimit të programit dhe përdorimin e kotë të RAM-it të kompjuterit.


Vini re mungesën e një lloji vargu në këtë listë. Nuk ka asnjë lloj të veçantë në C që mund të përdoret për të përshkruar vargjet. Në vend të kësaj, vargjet përfaqësohen si një grup elementesh char. Kjo do të thotë që karakteret në varg do të vendosen në vendndodhje të afërta të memories.

Duhet të theksohet se elementi i fundit i grupit është karakteri \ 0. Është një karakter null dhe përdoret në C për të shënuar fundin e një rreshti. Karakteri null nuk është shifra 0; nuk printohet dhe numërohet në tabelën ASCII me 0. Prania e karakterit null nënkupton që numri i qelizave në grup duhet të jetë. të paktën një më shumë se numri i karaktereve që do të ndahen në memorie.

Le të japim një shembull të përdorimit të vargjeve.

Programi 84

# përfshijnë kryesore ()

scanf ("% s", varg);

printf ("% s", varg);

Ky shembull përshkruan një grup prej 31 qelizave memorie, 30 prej të cilave mund të strehojnë një element të tipit char. Futet kur thirret funksioni scanf ("% s", varg); "&" mungon kur specifikoni një grup karakteresh.

Treguesit. Pointer - një paraqitje simbolike e adresës së memories së caktuar për variablin.

Për shembull, & emri është një tregues për emrin e ndryshores;

Këtu dhe është operacioni i marrjes së një adrese. Adresa aktuale është një numër, dhe përfaqësimi simbolik i & emrit është një konstante treguese.

Në gjuhën C ka edhe variabla të tipit pointer. Ashtu si vlera e një ndryshoreje të tipit char është një karakter, dhe vlera e një ndryshoreje të tipit int është një numër i plotë, vlera e një ndryshoreje të tipit pointer është adresa e një farë vlere.

Nëse i japim treguesit emrin ptr, mund të shkruajmë një operator si ky:

ptr = / * i cakton adresën e emrit ndryshores ptr * /

Themi në këtë rast se prt është një emër "tregues për". Dallimi midis ptr dhe & name është se prt është një ndryshore, ndërsa & name është një konstante. Nëse është e nevojshme, mund ta vendosni ndryshoren ptr në një objekt tjetër:

ptr= / * Ptr tregon bah, jo emrin * /

Tani vlera e ndryshores prt është adresa e ndryshores bah. Supozoni se e dimë se ndryshorja ptr përmban një referencë për ndryshoren bah. Më pas, për të hyrë në vlerën e kësaj ndryshoreje, mund të përdorni operacionin "adresim indirekt" *:

val = * ptr; / * përcaktoni vlerën e treguar nga ptr * / Dy operatorët e fundit, të marrë së bashku, janë ekuivalent me sa vijon:

Pra, kur pas shenjës & e ndjekur nga emri i ndryshores, rezultati i operacionit është adresa e ndryshores së specifikuar; & infermierja jep adresën e variablit infermiere; kur * pasohet nga një tregues drejt një ndryshoreje, rezultati i operacionit është vlera e vendosur në vendndodhjen e memories në adresën e specifikuar.

Shembull: infermiere = 22;

ptr = /* tregues për infermieren * /

Rezultati është caktimi i vlerës 22 në ndryshoren val.

Nuk mjafton të thuhet se një variabël është një tregues. Përveç kësaj, është e nevojshme të informohet se cilit lloj variabli i referohet ky tregues. Arsyeja është se variablat e llojeve të ndryshme zënë një numër të ndryshëm vendndodhjesh memorie, ndërsa disa operacione që përfshijnë tregues kërkojnë të dihet sasia e memories së alokuar.

Shembuj të përshkrimi i saktë i treguesve: int * pi; char * pc;

Specifikimi i tipit përcakton llojin e variablit të cilit i referohet treguesi, dhe karakteri * përcakton vetë variablin si tregues. Përshkrimi i formës int * pi; thotë se pi është një tregues dhe se * pi është një int.

Gjuha C ofron mundësinë për të përcaktuar emrat e llojeve të të dhënave. Ju mund t'i caktoni një emër çdo lloji të dhënash duke përdorur përkufizimin typedef dhe ta përdorni këtë emër në të ardhmen kur përshkruani objekte.

Formati: typedef<старый тип> <новый тип> Shembull: typedef i gjatë LARGE; / * përcakton të mëdha, e cila është e barabartë me të gjatë * /

Përkufizimi typedef nuk prezanton ndonjë lloj të ri, ai vetëm shton një emër të ri për llojin tashmë ekzistues. Variablat e përshkruar në këtë mënyrë kanë saktësisht të njëjtat veti si variablat e përshkruar në mënyrë eksplicite. Riemërtimi i tipit përdoret për të futur emra kuptimplotë ose të shkurtuar të tipeve për të përmirësuar kuptueshmërinë e programit dhe për të përmirësuar transportueshmërinë e programit (emrat e të njëjtit lloj të dhënash mund të ndryshojnë në kompjuterë të ndryshëm).

Operacionet. Gjuha C dallohet nga një shumëllojshmëri e gjerë operacionesh (më shumë se 40). Këtu do të shqyrtojmë vetëm ato kryesore, skedën. 3.3.

Veprimet aritmetike. Kjo perfshin

Shtesa (+),

Zbritja (binare) (-),

Shumëzimi (*),

Divizioni (/),

Pjesa e mbetur e pjesëtimit (%),

Zbritja (unare) (-).

Në gjuhën C, ekziston një rregull: nëse dividenti dhe pjesëtuesi janë të tipit int, atëherë ndarja kryhet tërësisht, domethënë, pjesa e pjesshme e rezultatit hidhet poshtë.

Si zakonisht, në shprehje, veprimet e shumëzimit, pjesëtimit dhe gjetjes së mbetjes kryhen para mbledhjes dhe zbritjes. Përdorni kllapa për të ndryshuar rendin e veprimeve.

Programi 85

#përfshi

5 = -3 + 4 * 5 - 6; printf ("% d \ n", s);

s = -3 + 4% 5 - 6; printf ("% d \ n", s);

s = -3 * 4% - 6/5; printf ("% d \ n", s);

s = (7 + 6)% 5/2; printf ("% d \ n", s);

Rezultati i ekzekutimit të programit: 11 1 0 1

Tabela 3.3 Vjetërsia dhe rendi i operacioneve

Bazat e gjuhës

Kodi i programit dhe të dhënat e manipuluara nga programi shkruhen në memorien e kompjuterit si një sekuencë bitash. Bit- Ky është elementi më i vogël i memories kompjuterike, i aftë për të ruajtur ose 0 ose 1. Në nivel fizik, kjo korrespondon me një tension elektrik, i cili, siç e dini, ekziston ose jo. Duke parë përmbajtjen e memories së kompjuterit, ne shohim diçka të tillë:
...

Është shumë e vështirë të kuptojmë këtë sekuencë, por ndonjëherë duhet të manipulojmë të dhëna të tilla të pastrukturuara (zakonisht kjo nevojitet kur programojmë drejtuesit e pajisjeve harduerike). C ++ ofron një sërë operacionesh për të punuar me të dhëna bit. (Ne do të flasim për këtë në Kapitullin 4.)
Si rregull, një strukturë imponohet në një sekuencë bitesh, duke i grupuar bitet në byte dhe fjalët... Një bajt përmban 8 bit dhe një fjalë përmban 4 bit, ose 32 bit. Sidoqoftë, përkufizimi i një fjale mund të jetë i ndryshëm në sisteme të ndryshme operative. Tani ka filluar kalimi në sistemet 64-bit dhe kohët e fundit sistemet me fjalë 16-bit ishin të zakonshme. Megjithëse madhësia e bajtit është e njëjtë në shumicën dërrmuese të sistemeve, ne ende do t'i referohemi këtyre vlerave si të varura nga makina.

Tani mund të flasim, për shembull, për bajtin me adresën 1040 ose fjalën me adresën 1024 dhe pohojmë se bajt me adresën 1032 nuk është i barabartë me bajtin me adresën 1040.
Megjithatë, ne nuk e dimë se çfarë është ndonjë bajt, çdo fjalë makinerie. Si të kuptoni kuptimin e 8 biteve të caktuara? Për të interpretuar në mënyrë të qartë kuptimin e këtij bajt (ose fjalë, ose grup tjetër bit), ne duhet të dimë llojin e të dhënave të përfaqësuara nga ky bajt.
C ++ ofron një grup llojesh të dhënash të integruara: karakter, numër i plotë, real - dhe një grup llojesh të përbëra dhe të zgjeruara: vargje, vargje, numra kompleksë. Përveç kësaj, ekziston një grup bazë operacionesh për operacionet me këto të dhëna: krahasimi, aritmetika dhe operacione të tjera. Ekzistojnë gjithashtu operatorë kërcimi, loop dhe të kushtëzuar. Këta elementë të gjuhës C ++ përbëjnë grupin e blloqeve ndërtuese nga të cilat mund të ndërtoni një sistem të çdo kompleksiteti. Hapi i parë në zotërimin e C ++ do të jetë studimi i elementeve bazë të listuara, të cilit i kushtohet Pjesa II e këtij libri.
Kapitulli 3 ofron një përmbledhje të llojeve të integruara dhe të zgjeruara, si dhe mekanizmat me anë të të cilave mund të krijohen lloje të reja. Në thelb, ky është, natyrisht, mekanizmi i klasës i prezantuar në seksionin 2.3. Kapitulli 4 diskuton shprehjet, operacionet e integruara dhe përparësinë e tyre dhe konvertimet e tipit. Kapitulli 5 flet për udhëzimet gjuhësore. Së fundi, Kapitulli 6 prezanton bibliotekën standarde C ++ dhe llojet e kontejnerëve - vargun vektorial dhe shoqërues.

3. Llojet e të dhënave C ++

Ky kapitull ofron një përmbledhje të ngulitura, ose elementare, llojet e të dhënave të gjuhës C ++. Fillon me një përkufizim fjalë për fjalë, të tilla si 3.14159 ose pi, dhe më pas koncepti e ndryshueshme, ose Objekt të jetë një nga llojet e të dhënave. Pjesa tjetër e këtij kapitulli i kushtohet një përshkrimi të detajuar të çdo lloji të integruar. Ai gjithashtu rendit llojet e të dhënave të derivuara për vargjet dhe vargjet e ofruara nga biblioteka standarde C ++. Edhe pse këto lloje nuk janë elementare, ato janë shumë të rëndësishme për të shkruar programe reale në C ++ dhe ne duam t'i prezantojmë sa më shpejt lexuesit. Ne do t'i quajmë lloje të tilla të dhënash zgjerimi Llojet bazë të C ++.

3.1. Literale

C ++ ka një grup të llojeve të të dhënave të integruara për përfaqësimin e numrave të plotë, numrave realë, simboleve dhe llojit të të dhënave "vargu i karaktereve", i cili përdoret për të ruajtur vargjet e karaktereve. Lloji char përdoret për të ruajtur karaktere të vetme dhe numra të plotë të vegjël. Ai zë një bajt makinerie. Llojet short, int dhe long janë krijuar për të përfaqësuar numra të plotë. Këto lloje ndryshojnë vetëm në gamën e vlerave që numrat mund të marrin, dhe madhësitë specifike të llojeve të listuara varen nga zbatimi. Zakonisht shkurt është gjysma e një fjale makine, int është një fjalë, e gjatë është një ose dy fjalë. Në sistemet 32-bit, int dhe long janë zakonisht të njëjtat madhësi.

Llojet float, double dhe long double janë të destinuara për numrat me pikë lundruese dhe ndryshojnë në saktësinë e paraqitjes (numri i shifrave domethënëse) dhe diapazoni. Në mënyrë tipike, float (përpikëri e vetme) merr një fjalë makinerie, dyfishi (përpikëri e dyfishtë) merr dy, dhe dyfishi i gjatë (përpikëri i zgjeruar) merr tre.
char, short, int dhe long së bashku bëjnë llojet e numrave të plotë e cila, nga ana tjetër, mund të jetë ikonike(nënshkruar) dhe e panënshkruar(i panënshkruar). Në llojet e nënshkruara, biti më i majtë përdoret për të ruajtur shenjën (0 - plus, 1 - minus), dhe bitet e mbetura përmbajnë vlerën. Në llojet e panënshkruara, të gjitha bitet përdoren për vlerën. Karakteri i nënshkruar i tipit 8-bit mund të përfaqësojë vlera nga -128 në 127, dhe char i panënshkruar mund të përfaqësojë vlera nga 0 në 255.

Kur një numër i caktuar shfaqet në program, për shembull 1, atëherë thirret ky numër fjalë për fjalë, ose konstante fjalë për fjalë... Një konstante, sepse nuk mund ta ndryshojmë vlerën e saj, dhe një literale, sepse vlera e saj shfaqet në tekstin e programit. Një literal është një sasi e paadresuar: megjithëse në realitet, natyrisht, ruhet në kujtesën e makinës, nuk ka asnjë mënyrë për të ditur adresën e saj. Çdo fjalë për fjalë ka një lloj specifik. Pra, 0 është e tipit int, 3.14159 është e tipit double.

Literalet e numrave të plotë mund të shkruhen në shënime dhjetore, oktale dhe heksadecimal. Ja si duket numri 20, i përfaqësuar nga fjalë për fjalë dhjetore, oktale dhe heksadecimale:

20 // dhjetore
024 // oktal
0x14 // gjashtëkëndor

Nëse literali fillon me 0, trajtohet si oktal, nëse fillon me 0x ose 0X, atëherë si heksadecimal. Shënimi i zakonshëm trajtohet si numër dhjetor.
Si parazgjedhje, të gjitha literalet e numrave të plotë nënshkruhen int. Ju mund të përcaktoni në mënyrë eksplicite një numër të plotë literal për aq kohë sa duke bashkangjitur shkronjën L në fund të numrit (përdoren të dyja shkronjat L të mëdha dhe l të vogla, por për lexueshmëri nuk duhet të përdorni shkronja të vogla: mund të ngatërrohet lehtësisht me

1). U (ose u) në fund e përkufizon fjalë për fjalë si int të panënshkruar, dhe dy shkronjat UL ose LU si të gjata të panënshkruara. Për shembull:

128u 1024UL 1L 8Lu

Literalet që përfaqësojnë numrat realë mund të shkruhen ose me presje dhjetore ose me shënim shkencor (eksponencial). Si parazgjedhje, ato janë të tipit double. Për të treguar në mënyrë eksplicite llojin float, duhet të përdorni prapashtesën F ose f, dhe për dyfishin e gjatë - L ose l, por vetëm në rastin e shkrimit me një pikë dhjetore. Për shembull:

3,14159F 0 / 1f 12,345L 0,0 3el 1,0E-3E 2,1,0L

Fjalët e vërtetë dhe false janë fjalë për fjalë të tipit bool.
Konstantet e përfaqësueshme të karaktereve letrare shkruhen si karaktere brenda thonjëzave të vetme. Për shembull:

"a" "2" "," "" (hapësirë)

Karakteret speciale (tab, kthimi i karrocës) shkruhen si sekuenca ikjeje. Sekuencat e tilla të mëposhtme janë përcaktuar (ato fillojnë me një karakter të kundërt):

Linja e re \ n skeda horizontale \ t hapësirë ​​e pasme \ b skeda vertikale \ v kthimi i karrocës \ r furnizim \ f thirrje \ një vijë e prapme \\ pyetje \? citim i vetëm \ "kuotim i dyfishtë\"

sekuenca e përgjithshme e arratisjes është \ ooo, ku ooo është një deri në tre shifra oktale. Ky numër është kodi i karakterit. Duke përdorur kodin ASCII, ne mund të shkruajmë fjalë për fjalë:

\ 7 (telefonatë) \ 14 (linja e re) \ 0 (null) \ 062 ("2")

Një karakter literal mund të parashtesohet me L (për shembull, L "a"), që do të thotë një lloj i veçantë wchar_t - një lloj karakteri me dy bajtë që përdoret për të ruajtur karaktere nga alfabetet kombëtare nëse ato nuk mund të përfaqësohen nga një lloj karakteri i zakonshëm , të tilla si shkronja kineze ose japoneze.
Një varg literal është një varg karakteresh i mbyllur në thonjëza të dyfishta. Një literal i tillë mund të përfshijë disa rreshta, në të cilin rast futet një vijë e prapme në fund të rreshtit. Personazhet speciale mund të përfaqësohen nga sekuencat e tyre të arratisjes. Këtu janë shembuj të literaleve të vargjeve:

"" (rresht bosh) "a" "\ nCC \ toptions \ tfile. \ n" "një fjalë për fjalë \ vargje me shumë rreshta sinjalizon vazhdimin e tij \ me një vijë të prapme"

Në fakt, një varg literal është një grup konstantesh karakteresh, ku, sipas konventës së C dhe C ++, elementi i fundit është gjithmonë karakteri special me kodin 0 (\ 0).
Fjalë për fjalë "A" specifikon një karakter të vetëm A, dhe vargu literal "A" është një grup prej dy elementësh: "A" dhe \ 0 (karakter bosh).
Meqenëse ekziston një lloj wchar_t, ka fjalë për fjalë të këtij lloji, të shënuara, si në rastin e karaktereve individuale, me parashtesën L:

L "një varg i gjerë fjalë për fjalë"

Një varg literal i tipit wchar_t është një grup me përfundim null i të njëjtit lloj.
Nëse dy ose më shumë literale vargjesh (të tilla si char ose wchar_t) shkojnë me radhë në një test programi, përpiluesi i bashkon ato në një varg. Për shembull, teksti i mëposhtëm

"dy" "disa"

do të gjenerojë një grup prej tetë karakteresh - dyshe dhe një karakter null fundor. Rezultati i bashkimit të vargjeve të llojeve të ndryshme është i papërcaktuar. Nëse shkruani:

// kjo nuk është një ide e mirë "dy" L "disa"

atëherë në ndonjë kompjuter rezultati do të jetë një linjë kuptimplotë, dhe në një tjetër mund të jetë diçka krejtësisht e ndryshme. Programet që përdorin veçoritë e zbatimit të një përpiluesi ose sistemi operativ të caktuar nuk janë të lëvizshëm. Ne dekurajojmë fuqimisht përdorimin e modeleve të tilla.

Ushtrimi 3.1

Shpjegoni ndryshimin në përkufizimet e fjalëve të mëposhtme:

(a) "a", L "a", "a", L "a" (b) 10, 10u, 10L, 10uL, 012, 0 * C (c) 3,14, 3,14f, 3,14L

Ushtrimi 3.2

Çfarë gabimesh janë bërë në shembujt e mëposhtëm?

(a) "Kush shkon me F \ 144rgus? \ 014" (b) 3.14e1L (c) "dy" L "disa" (d) 1024f (e) 3.14UL (f) "koment me shumë rreshta"

3.2. Variablat

Le të imagjinojmë se po zgjidhim problemin e ngritjes së 2 në fuqinë e 10. Ne shkruajmë:

#përfshi
int main () (
// një zgjidhje e parë
cout<< "2 raised to the power of 10: ";
cout<< 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
cout<< endl;
kthimi 0;
}

Problemi është zgjidhur, megjithëse na u desh të kontrollonim vazhdimisht nëse fjalë për fjalë 2 përsëritet në të vërtetë 10 herë. Ne nuk gabuam duke shkruar këtë sekuencë të gjatë dyshe, dhe programi ktheu rezultatin e saktë - 1024.
Por tani na u kërkua të ngrinim 2 në fuqinë e 17-të, dhe më pas në 23. Është jashtëzakonisht e papërshtatshme të modifikosh tekstin e programit çdo herë! Dhe, edhe më keq, është shumë e lehtë të bësh një gabim duke shkruar një dy shtesë ose duke e lënë jashtë… Por, çka nëse duhet të printosh një tabelë me dy fuqi nga 0 në 15? Përsëritni 16 herë dy rreshta që kanë një pamje të përgjithshme:

Cout<< "2 в степени X\t"; cout << 2 * ... * 2;

ku X është rritur në mënyrë sekuenciale me 1, dhe numri i kërkuar i literaleve zëvendësohet në vend të elipsës?

Po, ne e kemi përballuar detyrën. Klienti nuk ka gjasa të gërmojë në detaje, duke qenë i kënaqur me rezultatin. Në jetën reale, kjo qasje shpesh funksionon, për më tepër, është e justifikuar: problemi u zgjidh larg nga mënyra më elegante, por në kohën e dëshiruar. Kërkimi për një opsion më të mirë dhe më inteligjent mund të jetë një humbje jopraktike e kohës.

Në këtë rast, metoda brute-force jep përgjigjen e saktë, por sa e pakëndshme dhe e mërzitshme është të zgjidhësh një problem në këtë mënyrë! Ne e dimë saktësisht se çfarë hapash duhet të ndërmerren, por vetë hapat janë të thjeshtë dhe monoton.

Përfshirja e mekanizmave më kompleksë për të njëjtën detyrë, si rregull, rrit ndjeshëm kohën e fazës përgatitore. Përveç kësaj, sa më të sofistikuara të përdoren mekanizmat, aq më e madhe është gjasat e gabimeve. Por edhe përkundër gabimeve të pashmangshme dhe lëvizjeve të gabuara, përdorimi i “teknologjisë së lartë” mund të sjellë përfitime në shpejtësinë e zhvillimit, për të mos përmendur faktin se këto teknologji zgjerojnë ndjeshëm aftësitë tona. Dhe - çfarë është interesante! - vetë procesi i vendimmarrjes mund të bëhet tërheqës.
Le të kthehemi te shembulli ynë dhe të përpiqemi të "përmirësojmë teknologjikisht" zbatimin e tij. Ne mund të përdorim një objekt të emërtuar për të ruajtur vlerën e shkallës në të cilën duam të rrisim numrin tonë. Gjithashtu, në vend të një sekuence të përsëritur të literaleve, ne përdorim operatorin e ciklit. Kështu do të duket:

#përfshi
int main ()
{
// objekte të tipit int
vlera int = 2;
int pow = 10;
cout<< value << " в степени "
<< pow << ": \t";
int res = 1;
// operatori i ciklit:
// përsëris llogaritjen e res
// derisa cnt të jetë më i madh se pow
për (int cnt = 1; cnt<= pow; ++cnt)
res = res * vlera;
cout<< res << endl;
}

vlera, pow, res dhe cnt janë variabla që ju lejojnë të ruani, modifikoni dhe merrni vlera. Deklarata e ciklit for përsërit linjën e vlerësimit të kohëve të rezultatit.
Pa dyshim, ne kemi krijuar një program shumë më fleksibël. Megjithatë, ky ende nuk është një funksion. Për të marrë një funksion real që mund të përdoret në çdo program për të llogaritur fuqinë e një numri, duhet të zgjidhni pjesën e përgjithshme të llogaritjeve dhe të vendosni vlera specifike me parametra.

Int pow (int val, int exp) (për (int res = 1; exp> 0; --exp) res = res * val; return res;)

Tani marrja e ndonjë shkalle të numrit të dëshiruar nuk do të jetë e vështirë. Ja se si zbatohet detyra jonë e fundit - të printoni një tabelë me dy fuqi nga 0 në 15:

#përfshi extern int pow (int, int); int main () (int val = 2; int exp = 15;
cout<< "Степени 2\n";
për (int cnt = 0; cnt<= exp; ++cnt)
cout<< cnt << ": "
<< pow(val, cnt) << endl;
kthimi 0;
}

Sigurisht, funksioni ynë pow () nuk është ende mjaftueshëm i përgjithësuar dhe mjaft i fortë. Nuk mund të funksionojë me numra realë, gabimisht ngre numrat në një fuqi negative - gjithmonë kthen 1. Rezultati i rritjes së një numri të madh në një fuqi të madhe mund të mos përshtatet në një ndryshore të tipit int dhe më pas do të kthehet një vlerë e gabuar e rastësishme . A e shihni sa e vështirë rezulton të jenë funksionet e shkrimit të krijuara për përdorim të gjerë? Shumë më e vështirë sesa zbatimi i një algoritmi specifik që synon zgjidhjen e një problemi specifik.

3.2.1. Çfarë është një variabël

E ndryshueshme, ose nje objekt- kjo është një zonë e emërtuar e kujtesës në të cilën ne kemi akses nga programi; vlerat mund të vendosen atje dhe më pas të merren. Çdo variabël C ++ ka një lloj specifik që karakterizon madhësinë dhe vendndodhjen e asaj zone memorie, gamën e vlerave që mund të ruajë dhe grupin e operacioneve të zbatueshme për atë ndryshore. Këtu është një shembull i përcaktimit të pesë objekteve të llojeve të ndryshme:

Int student_count; paga e dyfishtë; bool on_loan; strins_rruga_adresa; kufizues char;

Një variabël, si një literal, ka një lloj specifik dhe ruan vlerën e tij në një zonë të caktuar të memories. Adresueshmëria- kjo është ajo që i mungon fjalë për fjalë. Ka dy sasi të lidhura me një ndryshore:

  • vlera aktuale, ose vlera r (nga vlera e lexuar - vlera për lexim), e cila ruhet në këtë zonë memorie dhe është e natyrshme si në ndryshoren ashtu edhe në atë literale;
  • vlera e adresës së zonës së memories së lidhur me variablin, ose l-vlera (nga vlera e vendndodhjes) - vendi ku ruhet vlera r; është e qenësishme vetëm në objekt.

Në shprehje

Ch = ch - "0";

ndryshorja ch ndodhet si në të majtë ashtu edhe në të djathtë të simbolit të caktimit. Në të djathtë është vlera për t'u lexuar (ch dhe karakteri fjalë për fjalë "0"): të dhënat e lidhura me variablin lexohen nga zona përkatëse e memories. Në të majtë është vlera e vendndodhjes: rezultati i zbritjes vendoset në zonën e kujtesës të lidhur me ndryshoren ch. Në përgjithësi, operandi i majtë i një caktimi duhet të jetë një vlerë l. Nuk mund të shkruajmë shprehjet e mëposhtme:

// gabimet e përpilimit: vlerat në të majtë nuk janë vlera l // gabim: fjalë për fjalë nuk është l-vlera 0 = 1; // gabim: shprehja aritmetike nuk është një rrogë me vlerë l + pagë * 0.10 = paga_re;

Deklarata e përkufizimit të ndryshores cakton memorie për të. Meqenëse një objekt ka vetëm një zonë memorie të lidhur me të, një deklaratë e tillë mund të ndodhë vetëm një herë në një program. Problemet lindin nëse një ndryshore e përcaktuar në një skedar burimor do të përdoret në një tjetër. Për shembull:

// file module0.C // përcakton një objekt Emri i skedarit vargu Emri i skedarit; // ... cakto një vlerë për emrin e skedarit
// skedar module1.C
// përdor objektin Emri i skedarit
// mjerisht, nuk përpilohet:
// Emri i skedarit nuk është përcaktuar në modulin1.C
ifstream input_file (emri i skedarit);

C ++ kërkon që një objekt të njihet përpara se t'i qaset fillimisht. Kjo është për shkak të nevojës për të siguruar që objekti të përdoret në mënyrë korrekte sipas llojit të tij. Në shembullin tonë, module1.C do të shkaktojë një gabim përpilimi sepse ndryshorja e skedarit Emri nuk është përcaktuar në të. Për të shmangur këtë gabim, ne duhet t'i tregojmë kompajlerit për variablin e përcaktuar tashmë fileName. Kjo bëhet me një deklaratë deklarimi të ndryshueshme:

// skedari module1.C // përdor objektin Emri i skedarit // Emri i skedarit është deklaruar, domethënë, programi merr
// informacion rreth këtij objekti pa përcaktimin e tij dytësor
emri i skedarit të vargut të jashtëm; ifstream input_file (emri i skedarit)

Deklarata e ndryshores i tregon kompajlerit se një objekt i emrit të dhënë, i llojit të caktuar, është përcaktuar diku në program. Asnjë memorie nuk ndahet për një variabël kur deklarohet. (Fjala kyçe e jashtme diskutohet në seksionin 8.2.)
Një program mund të përmbajë sa më shumë deklarata të së njëjtës variabël sipas dëshirës, ​​por ai mund të përcaktohet vetëm një herë. Është i përshtatshëm për të vendosur deklarata të tilla në skedarët e kokës, duke i përfshirë ato në ato module që e kërkojnë atë. Kështu që ne mund të ruajmë informacionin rreth objekteve në një vend dhe të sigurojmë lehtësinë e modifikimit të tij nëse është e nevojshme. (Ne do të flasim më shumë për skedarët e kokës në seksionin 8.2.)

3.2.2. Emri i ndryshores

Emri i ndryshores, ose identifikues, mund të përbëhet nga shkronja latine, numra dhe karakteri i nënvizuar. Shkronjat e mëdha dhe të vogla në emra janë të ndryshme. Gjuha C ++ nuk e kufizon gjatësinë e identifikuesit, por është e papërshtatshme të përdoren emra shumë të gjatë si gosh_this_is_an_impossibly_name_to_type.
Disa fjalë janë fjalë kyçe në C ++ dhe nuk mund të përdoren si identifikues; Tabela 3.1 ofron një listë të plotë të tyre.

Tabela 3.1. C ++ fjalë kyçe

asm auto bool thyej rast
kap karakter klasës konst const_cast
vazhdojnë default fshij bëj dyfishtë
dinamik_cast tjetër një numër eksplicite eksporti
e jashtme i rremë noton për mik
shkoj nëse ne rresht ndër gjatë
i ndryshueshëm hapësira e emrit i ri operatori private
të mbrojtura publike regjistroheni reinterpret_cast kthimi
i shkurtër nënshkruar madhësia e statike static_cast
struktura kaloni shabllon kjo hedhin
typedef e vërtetë provoni i tipizuar emri i tipit
bashkim voidunion duke përdorur Virtual i pavlefshëm

Për ta bërë më të lehtë të kuptueshëm tekstin e programit tuaj, ju rekomandojmë t'u përmbaheni konventave të pranuara përgjithësisht për emërtimin e objekteve:

  • emri i variablit zakonisht shkruhet me shkronja të vogla, për shembull indeksi (për krahasim: Index është një emër tipi, dhe INDEX është një konstante e përcaktuar duke përdorur direktivën e paraprocesorit #define);
  • identifikuesi duhet të ketë një kuptim, duke shpjeguar qëllimin e objektit në program, për shembull: data_lindja ose paga;

nëse një emër i tillë përbëhet nga disa fjalë, të tilla si, për shembull, data_lindja, atëherë është zakon që ose të ndahen fjalët me një nënvizim (data_lindja), ose të shkruhet çdo fjalë tjetër me një shkronjë të madhe (data e lindjes). Është vënë re se programuesit e mësuar me qasjen e orientuar drejt objekteve preferojnë të shkruhen me shkronja të mëdha, ndërsa ata që kanë shkruar shumë në C përdorin karakterin nënvizues. Cila nga të dyja është më e mirë është çështje shije.

3.2.3. Përkufizimi i objektit

Në rastin më të thjeshtë, deklarata e përkufizimit të objektit përbëhet nga specifikues i tipit dhe emri i objektit dhe përfundon me një pikëpresje. Për shembull:

Paga e dyfishtë; paga e dyfishtë; muaji int; ditë int; viti int; distanca e largët e panënshkruar;

Disa objekte të të njëjtit lloj mund të përcaktohen në një deklaratë. Në këtë rast, emrat e tyre renditen të ndarë me presje:

Paga e dyfishtë, paga; në muajin, ditën, vitin; distanca e largët e panënshkruar;

Përkufizimi i thjeshtë i një ndryshoreje nuk përcakton vlerën e saj fillestare. Nëse një objekt përkufizohet si global, specifikimi C ++ garanton që ai do të inicializohet në zero. Nëse ndryshorja është lokale ose e alokuar në mënyrë dinamike (duke përdorur operatorin e ri), vlera e saj fillestare është e padefinuar, domethënë mund të përmbajë një vlerë të rastësishme.
Përdorimi i variablave si këto është një gabim shumë i zakonshëm dhe i vështirë për t'u zbuluar. Rekomandohet që të specifikoni në mënyrë eksplicite vlerën fillestare të një objekti, të paktën në rastet kur nuk e dini nëse objekti mund të inicializohet. Mekanizmi i klasës prezanton konceptin e një konstruktori të paracaktuar, i cili përdoret për të caktuar vlerat e paracaktuara. (Ne kemi folur tashmë për këtë në seksionin 2.3. Ne do të vazhdojmë të flasim për konstruktorët e paracaktuar pak më vonë, në seksionet 3.11 dhe 3.15, ku do të zbërthejmë vargjet dhe klasat komplekse nga biblioteka standarde.)

Int main () (// objekt lokal i pa inicializuar int ival;
// objekti i tipit string është inicializuar
// konstruktori i paracaktuar
projekt string;
// ...
}

Vlera fillestare mund të specifikohet drejtpërdrejt në deklaratën e përkufizimit të ndryshores. Në C ++, lejohen dy forma të inicializimit të variablave - të qarta, duke përdorur operatorin e caktimit:

Int ival = 1024; string project = "Fantasia 2000";

dhe të nënkuptuar, me vlerën fillestare në kllapa:

Int ival (1024); projekt string ("Fantasia 2000");

Të dy opsionet janë ekuivalente dhe inicializojnë ivalin e plotë në 1024 dhe vargun e projektit në "Fantasia 2000".
Inicializimi i qartë mund të përdoret gjithashtu kur përcaktohen variablat me një listë:

Paga e dyfishtë = 9999,99, paga = paga + 0,01; muaji int = 08; dita = 07, viti = 1955;

Variabli bëhet i dukshëm (dhe i vlefshëm në program) menjëherë pas përcaktimit të tij, kështu që ne mund të inicializojmë variablin paga me shumën e variablit paga që sapo përcaktuam me një konstante. Pra, përkufizimi është:

// i saktë, por i pakuptimtë int bizarre = i çuditshëm;

është sintaksisht e vlefshme, ndonëse e pakuptimtë.
Llojet e integruara të të dhënave kanë një sintaksë të veçantë për të specifikuar një vlerë null:

// ival merr vlerën 0, dhe dval merr 0.0 int ival = int (); dyfish dval = dyfish ();

Në përkufizimin e mëposhtëm:

// int () zbatohet për secilin prej 10 elementeve vektoriale< int >ivec (10);

inicializimi me int () zbatohet për secilin nga dhjetë elementët e vektorit. (Ne folëm për klasën e vektorit në seksionin 2.8. Shih seksionin 3.10 dhe kapitullin 6 për më shumë rreth kësaj.)
Një variabël mund të inicializohet me një shprehje të çdo kompleksiteti, duke përfshirë thirrjet e funksioneve. Për shembull:

#përfshi #përfshi
çmimi dyfish = 109,99, zbritja = 0,16;
dyfishi_çmimi i shitjes (çmimi * zbritje);
kafshë shtëpiake me varg ("rrudhat"); extern int get_value (); int val = fit_vlera ();
abs_val i panënshkruar = abs (val);

abs () është një funksion standard që kthen vlerën absolute të një parametri.
get_value () është një funksion i personalizuar që kthen një vlerë të plotë.

Ushtrimi 3.3

Cili nga përkufizimet e variablave të mëposhtëm përmban gabime sintaksore?

(a) int car = 1024, auto = 2048; (b) int ival = ival; (c) int ival (int ()); (d) paga e dyfishtë = paga = 9999,99; (e) cin >> int_input_value;

Ushtrimi 3.4

Shpjegoni ndryshimin midis vlerës l dhe vlerës r. Jep shembuj.

Ushtrimi 3.5

Gjeni ndryshimet në përdorimin e variablave të emrit dhe studentit në rreshtin e parë dhe të dytë të secilit shembull:

(a) emri i vargut të jashtëm; emri i vargut ("ushtrimi 3.5a"); (b) vektor i jashtëm studentë; vektoriale studentë;

Ushtrimi 3.6

Cilët emra objektesh janë të pavlefshëm në C ++? Modifikojini ato në mënyrë që të jenë sintaksisht të sakta:

(a) int double = 3,14159; (b) vektor< int >_; (c) emri i vargut; (d) kapja e vargut-22; (e) char 1_ose_2 = "1"; (f) float Float = 3.14f;

Ushtrimi 3.7

Cili është ndryshimi midis përkufizimeve të variablave globale dhe lokale të mëposhtme?

String global_class; int global_int; int main () (
int local_int;
string local_class; //...
}

3.3. Treguesit

Treguesit dhe shpërndarja dinamike e memories u prezantuan shkurtimisht në seksionin 2.2. TreguesiËshtë një objekt që përmban adresën e një objekti tjetër dhe ju lejon të manipuloni në mënyrë indirekte atë objekt. Në mënyrë tipike, treguesit përdoren për të punuar me objekte të krijuara në mënyrë dinamike, për të ndërtuar struktura të lidhura të të dhënave, si lista të lidhura dhe pemë hierarkike, dhe për të kaluar objekte të mëdha - vargje dhe objekte të klasës - te funksionet si parametra.
Çdo tregues është i lidhur me një lloj të caktuar të dhënash, dhe përfaqësimi i tyre i brendshëm nuk varet nga lloji i brendshëm: si madhësia e kujtesës e zënë nga një objekt i llojit të treguesit, ashtu edhe diapazoni i vlerave janë të njëjta. Dallimi është se si përpiluesi interpreton objektin e referuar. Treguesit për lloje të ndryshme mund të kenë të njëjtin kuptim, por zona e kujtesës ku ndodhen llojet përkatëse mund të jetë e ndryshme:

  • një tregues në një int që përmban vlerën e adresës 1000 drejtohet në zonën e memories 1000-1003 (në një sistem 32-bit);
  • një tregues në një dyshe që përmban vlerën e adresës 1000 drejtohet në zonën e memories 1000-1007 (në një sistem 32-bit).

Ketu jane disa shembuj:

Int * ip1, * ip2; komplekse * cp; varg * pstring; vektoriale * pvec; dyfish * dp;

Indeksi tregohet me një yll përpara emrit. Në përcaktimin e variablave me një listë, një yll duhet të vijë përpara çdo treguesi (shih më lart: ip1 dhe ip2). Në shembullin më poshtë, lp është një tregues për një objekt të gjatë, dhe lp2 është një objekt i gjatë:

E gjatë * lp, lp2;

Në rastin e mëposhtëm, fp interpretohet si një objekt float, dhe fp2 është një tregues për të:

Float fp, * fp2;

Operatori i dereferencës (*) mund të ndahet me hapësira nga emri dhe madje edhe drejtpërdrejt ngjitur me fjalën kyçe të tipit. Prandaj, përkufizimet e mësipërme janë sintaksisht të sakta dhe plotësisht ekuivalente:

// vëmendje: ps2 nuk është një tregues për një varg! varg * ps, ps2;

Mund të supozohet se si ps ashtu edhe ps2 janë tregues, megjithëse treguesi është vetëm i pari prej tyre.
Nëse vlera e treguesit është 0, atëherë ai nuk përmban asnjë adresë objekti.
Le të jepet një ndryshore e tipit int:

Int ival = 1024;

Më poshtë janë shembuj të përcaktimit dhe përdorimit të treguesve për int pi dhe pi2:

// pi është inicializuar në zero int * pi = 0;
// pi2 inicializohet me adresën ival
int * pi2 =
// e saktë: pi dhe pi2 përmbajnë adresën ival
pi = pi2;
// pi2 përmban një adresë zero
pi2 = 0;

Një treguesi nuk mund t'i caktohet një vlerë që nuk është një adresë:

// gabim: pi nuk mund të jetë int pi = ival

Në mënyrë të ngjashme, nuk mund të caktoni një tregues të një lloji në një vlerë që është adresa e një objekti të një lloji tjetër. Nëse përcaktohen variablat e mëposhtëm:

dval i dyfishtë; dyfish * ps =

atëherë të dyja shprehjet e caktimit më poshtë do të shkaktojnë një gabim përpilimi:

// gabime në kompilim // caktim i pavlefshëm i llojeve të të dhënave: int *<== double* pi = pd pi = &dval;

Çështja nuk është se ndryshorja pi nuk mund të përmbajë adresat e objektit dval - adresat e objekteve të llojeve të ndryshme kanë të njëjtën gjatësi. Operacione të tilla të përzierjes së adresave janë të ndaluara qëllimisht, sepse interpretimi i objekteve nga përpiluesi varet nga lloji i treguesit për to.
Sigurisht, ka raste kur na intereson vlera e vetë adresës, dhe jo objekti në të cilin ajo tregon (le të themi se duam ta krahasojmë këtë adresë me ndonjë tjetër). Për të zgjidhur situata të tilla, u prezantua një tregues special i zbrazët, i cili mund të tregojë çdo lloj të dhënash dhe shprehjet e mëposhtme do të jenë të sakta:

// korrekt: void * mund të përmbajë // adresa të çdo lloji void * pv = pi; pv = pd;

Lloji i objektit të drejtuar nga void * është i panjohur dhe ne nuk mund ta manipulojmë këtë objekt. Gjithçka që mund të bëjmë me një tregues të tillë është t'i caktojmë vlerën e tij një treguesi tjetër ose ta krahasojmë atë me ndonjë vlerë adrese. (Ne do të flasim më shumë për treguesin e zbrazëtisë në seksionin 4.14.)
Për t'iu referuar një objekti që ka adresën e tij, duhet të përdorni operacionin e dereferencimit, ose adresimin indirekt, të shënuar me një yll (*). Duke pasur përkufizimet e variablave të mëposhtëm:

Int ival = 1024 ;, ival2 = 2048; int * pi =

// duke i caktuar në mënyrë indirekte vlerën ival2 ndryshores ival * pi = ival2;
// përdorimi i tërthortë i ival si rvalue dhe lvalue
* pi = abs (* pi); // ival = abs (ival);
* pi = * pi + 1; // ival = ival + 1;

Kur aplikojmë operacionin e marrjes së adresës (&) në një objekt të tipit int, marrim rezultatin e tipit int *
int * pi =
Nëse zbatojmë të njëjtin veprim për një objekt të tipit int * (pointer to int), marrim një pointer në pointer në int, d.m.th. int **. int ** është adresa e një objekti që përmban adresën e një objekti të tipit int. Duke mos referuar ppi, marrim një objekt int * që përmban adresën ival. Për të marrë vetë objektin ival, operacioni i dereferencimit ppi duhet të zbatohet dy herë.

Int ** ppi = π int * pi2 = * ppi;
cout<< "Значение ival\n" << "явное значение: " << ival << "\n"
<< "косвенная адресация: " << *pi << "\n"
<< "дважды косвенная адресация: " << **ppi << "\n"

Treguesit mund të përdoren në shprehjet aritmetike. Vini re shembullin e mëposhtëm, ku dy shprehje bëjnë gjëra krejtësisht të ndryshme:

Int i, j, k; int * pi = // i = i + 2
* pi = * pi + 2; // rrit adresën e përfshirë në pi me 2
pi = pi + 2;

Ju mund t'i shtoni një vlerë të plotë treguesit, gjithashtu mund të zbrisni prej tij. Shtimi i 1 në një tregues rrit vlerën që ai përmban nga madhësia e zonës së kujtesës të caktuar për një objekt të llojit përkatës. Nëse lloji char zë 1 bajt, int - 4 dhe double - 8, atëherë shtimi i 2 në treguesit në char, int dhe double do të rrisë vlerën e tyre me 2, 8 dhe 16. Si mund të interpretohet kjo? Nëse objektet e të njëjtit lloj ndodhen në memorie njëri pas tjetrit, atëherë rritja e treguesit me 1 do të bëjë që ai të tregojë tek objekti tjetër. Prandaj, operacionet aritmetike me tregues përdoren më shpesh gjatë përpunimit të vargjeve; në çdo rast tjetër, vështirë se justifikohen.
Kështu duket një shembull tipik i përdorimit të aritmetikës së adresave kur përsëritet mbi elementët e një grupi duke përdorur një përsëritës:

Int ia; int * iter = int * iter_fund =
ndërsa (iter! = iter_fund) (
bëj_diçka_me_vlerë (* iter);
++ iter;
}

Ushtrimi 3.8

Janë dhënë përkufizimet e variablave:

Int ival = 1024, ival2 = 2048; int * pi1 = & ival, * pi2 = & ival2, ** pi3 = 0;

Çfarë ndodh kur kryeni veprimet e mëposhtme të caktimit? A ka ndonjë gabim në këta shembuj?

(a) ival = * pi3; (e) pi1 = * pi3; (b) * pi2 = * pi3; (f) ival = * pi1; (c) ival = pi2; (g) pi1 = ival; (d) pi2 = * pi1; (h) pi3 =

Ushtrimi 3.9

Puna me tregues është një nga aspektet më të rëndësishme të C dhe C ++, por është e lehtë të bësh një gabim në të. Për shembull kodin

Pi = pi = pi + 1024;

pothuajse me siguri do të bëjë që pi të tregojë në një rajon të rastësishëm të memories. Çfarë bën ky operator caktimi dhe në cilin rast nuk do të rezultojë në një gabim?

Ushtrimi 3.10

Ky program përmban një gabim në lidhje me përdorimin e gabuar të treguesve:

Int foobar (int * pi) (* pi = 1024; kthim * pi;)
int main () (
int * pi2 = 0;
int ival = foobar (pi2);
kthimi 0;
}

Cili është gabimi? Si mund ta rregulloni?

Ushtrimi 3.11

Gabimet nga dy ushtrimet e mëparshme manifestohen dhe çojnë në pasoja fatale për shkak të mungesës së vërtetimit të vlerave të treguesit në C ++ gjatë ekzekutimit të programit. Pse mendoni se nuk u zbatua një kontroll i tillë? A mund të jepni disa udhëzime të përgjithshme për t'i bërë treguesit më të sigurt?

3.4. Llojet e vargjeve

C ++ mbështet dy lloje vargjesh - llojin e integruar të trashëguar nga C dhe klasën e vargjeve nga biblioteka standarde C ++. Klasa e vargjeve ofron shumë më tepër mundësi dhe për këtë arsye është më e përshtatshme për t'u përdorur, por në praktikë shpesh ka situata kur duhet të përdorni një lloj të integruar ose të keni një kuptim të mirë se si funksionon. (Një shembull do të ishte analizimi i parametrave të linjës së komandës të kaluar në main (). Ne do ta mbulojmë atë në Kapitullin 7.)

3.4.1. Lloji i vargut të integruar

Siç u përmend tashmë, lloji i vargut të integruar kaloi në C ++ me trashëgimi nga C. Një varg karakteresh ruhet në memorie si një grup dhe aksesohet duke përdorur një tregues char *. Biblioteka standarde C ofron një sërë funksionesh për manipulimin e vargjeve. Për shembull:

// kthen gjatësinë e vargut int strlen (const char *);
// krahason dy vargje
int strcmp (const char *, const char *);
// kopjon një rresht në tjetrin
char * strcpy (char *, const char *);

Biblioteka standarde C është pjesë e bibliotekës C ++. Për ta përdorur atë, ne duhet të përfshijmë një skedar kokë:

#përfshi

Treguesi për char, me të cilin i referohemi vargut, tregon grupin e karaktereve që i korrespondojnë vargut. Edhe kur shkruajmë një varg literal like

Const char * st = "Çmimi i një shishe vere \ n";

kompajleri i vendos të gjitha karakteret në varg në një varg dhe më pas i cakton st adresën e elementit të parë në grup. Si mund të punohet me një varg duke përdorur një tregues të tillë?
Në mënyrë tipike, aritmetika e adresës përdoret për të përsëritur karakteret në një varg. Meqenëse vargu përfundon gjithmonë me një karakter null, ju mund ta rritni treguesin me 1 derisa karakteri tjetër të bëhet null. Për shembull:

Ndërsa (* st ++) (...)

st është çreferencuar dhe vlera që rezulton kontrollohet për të vërtetën. Çdo vlerë jozero konsiderohet e vërtetë, dhe për këtë arsye cikli përfundon kur arrihet karakteri me kodin 0. Operatori i rritjes ++ shton 1 në treguesin st dhe kështu e zhvendos atë në karakterin tjetër.
Kështu mund të duket zbatimi i një funksioni që kthen gjatësinë e një vargu. Vini re se meqenëse një tregues mund të përmbajë një vlerë null (të mos tregojë asgjë), ai duhet të kontrollohet përpara se të çreferencohet:

Int string_length (const char * st) (int cnt = 0; if (st) while (* st ++) ++ cnt; return cnt;)

Një varg i tipit të integruar mund të konsiderohet bosh në dy raste: nëse treguesi i vargut ka një vlerë null (atëherë nuk kemi fare varg) ose tregon në një grup të përbërë nga një karakter null (d.m.th., në një varg që nuk përmban një karakter të vetëm domethënës).

// pc1 nuk adreson asnjë varg karakteresh char * pc1 = 0; // pc2 adreson një karakter null const char * pc2 = "";

Për një programues fillestar, përdorimi i vargjeve të integruara është i mbushur me gabime, sepse niveli i zbatimit është shumë i ulët dhe është e pamundur të bëhet pa aritmetikë adresash. Më poshtë do të tregojmë disa gabime tipike të bëra nga fillestarët. Detyra është e thjeshtë: llogaritni gjatësinë e vargut. Versioni i parë është i pasaktë. Korrigjoje.

#përfshi const char * st = "Çmimi i një shishe vere \ n"; int main () (
int len ​​= 0;
ndërsa (st ++) ++ len; cout<< len << ": " << st;
kthimi 0;
}

Në këtë version, treguesi st nuk është i çreferencuar. Prandaj, nuk është karakteri i treguar nga st ai që kontrollohet për barazi, por vetë treguesi. Meqenëse fillimisht ky tregues kishte një vlerë jo zero (adresa e vargut), ai kurrë nuk do të bëhet i barabartë me zero dhe cikli do të funksionojë pafundësisht.
Ky gabim është eliminuar në versionin e dytë të programit. Programi përfundon me sukses, por rezultati është i pasaktë. Ku e kemi gabim këtë herë?

#përfshi const char * st = "Çmimi i një shishe vere \ n"; int main ()
{
int len ​​= 0;
ndërsa (* st ++) ++ len; cout<< len << ": " << st << endl;
kthimi 0;
}

Gabimi është se pas përfundimit të ciklit, treguesi st nuk adreson karakterin origjinal literal, por karakterin e vendosur në memorie pas zeros përfundimtare të kësaj literale. Çdo gjë mund të jetë në këtë vend, dhe dalja e programit do të jetë një sekuencë e rastësishme karakteresh.
Mund të përpiqeni të rregulloni këtë gabim:

St = st - len; cout<< len << ": " << st;

Tani programi ynë prodhon diçka kuptimplote, por jo plotësisht. Përgjigja duket si kjo:

18: ena shishe vere

Ne harruam të merrnim parasysh se karakteri null pasues nuk ishte përfshirë në gjatësinë e llogaritur. st duhet të kompensohet me gjatësinë e vargut plus 1. Këtu është operatori i saktë më në fund:

St = st - len - 1;

dhe këtu është rezultati i saktë:

18: Çmimi i një shishe verë

Megjithatë, nuk mund të themi se programi ynë duket elegant. Operatori

St = st - len - 1;

shtohet për të korrigjuar një gabim të bërë në një fazë të hershme të hartimit të programit - rritja e drejtpërdrejtë e treguesit st. Ky operator nuk përshtatet në logjikën e programit dhe kodi tani është i vështirë për t'u kuptuar. Rregullimet e këtij lloji shpesh quhen arna - diçka e krijuar për të futur një vrimë në një program ekzistues. Një zgjidhje shumë më e mirë do të ishte rimendimi i logjikës. Një nga opsionet në rastin tonë do të ishte përcaktimi i një treguesi të dytë të inicializuar me vlerën st:

Const char * p = st;

Tani p mund të përdoret në një lak gjatësie, duke e lënë st të pandryshuar:

Ndërsa (* p ++)

3.4.2. Klasa e vargjeve

Siç e pamë sapo, përdorimi i llojit të vargut të integruar është i prirur ndaj gabimeve dhe i papërshtatshëm sepse zbatohet në një nivel shumë të ulët. Prandaj, është mjaft e zakonshme të zhvilloni klasën ose klasat tuaja për të përfaqësuar llojin e vargut - pothuajse çdo kompani, departament ose projekt individual kishte zbatimin e vet të vargut. Çfarë mund të them, ne kemi bërë të njëjtën gjë në dy botimet e mëparshme të këtij libri! Kjo shkaktoi probleme të përputhshmërisë dhe transportueshmërisë. Zbatimi i standardit të vargjeve nga biblioteka standarde C ++ kishte për qëllim t'i jepte fund kësaj shpikjeje të biçikletave.
Le të përpiqemi të specifikojmë grupin minimal të operacioneve që duhet të ketë klasa e vargut:

  • inicializimi me një grup karakteresh (një varg i tipit të integruar) ose një objekt tjetër të tipit varg. Lloji i integruar nuk ka opsionin e dytë;
  • kopjimi i një rreshti në tjetrin. Për një lloj të integruar, duhet të përdorni funksionin strcpy ();
  • qasje në karaktere individuale të vargut për lexim dhe shkrim. Në grupin e integruar, kjo bëhet duke përdorur funksionin e marrjes së indeksit ose adresimin indirekt;
  • krahasimi i dy vargjeve për barazi. Për një lloj të integruar, përdoret funksioni strcmp ();
  • bashkimi i dy vargjeve, duke marrë rezultatin ose si vargu i tretë, ose në vend të njërit prej atyre origjinale. Për një lloj të integruar, përdoret funksioni strcat (), por për të marrë rezultatin në një linjë të re, duhet të përdorni në mënyrë sekuenciale funksionet strcpy () dhe strcat ();
  • duke llogaritur gjatësinë e vargut. Ju mund të zbuloni gjatësinë e një vargu të një lloji të integruar duke përdorur funksionin strlen ();
  • aftësia për të gjetur nëse një varg është bosh. Për këtë qëllim, vargjet e integruara duhet të kontrollojnë dy kushte: char str = 0; // ... nëse (! str ||! * str) kthehen;

Klasa e vargut të Bibliotekës Standarde C ++ zbaton të gjitha këto operacione (dhe shumë më tepër, siç do të shohim në Kapitullin 6). Në këtë seksion, ne do të mësojmë se si të përdorim operacionet bazë të kësaj klase.
Për të përdorur objektet e klasës së vargut, duhet të përfshini skedarin përkatës të kokës:

#përfshi

Këtu është një shembull i një vargu nga seksioni i mëparshëm, i përfaqësuar nga një objekt vargu dhe një varg karakteresh i inicializuar:

#përfshi st string ("Çmimi i një shishe vere \ n");

Gjatësia e vargut kthehet nga funksioni anëtar i madhësisë () (gjatësia nuk përfshin karakterin null përfundimtar).

Cout<< "Длина " << st << ": " << st.size() << " символов, включая символ новой строки\n";

Forma e dytë e përkufizimit të vargut specifikon një varg bosh:

String st2; // rresht bosh

Si e dimë nëse një rresht është bosh? Sigurisht, ju mund ta krahasoni gjatësinë e tij me 0:

Nëse (! St.size ()) // e saktë: bosh

Sidoqoftë, ekziston gjithashtu një metodë speciale e zbrazët () që kthen true për një varg bosh dhe false për një varg jo bosh:

Nëse (st.zbrazët ()) // e saktë: bosh

Forma e tretë e konstruktorit inicializon një objekt të tipit string me një objekt tjetër të të njëjtit lloj:

String st3 (st);

Vargu st3 inicializohet me vargun st. Si mund të sigurohemi që këto rreshta të jenë të njëjta? Le të përdorim operatorin e krahasimit (==):

Nëse (st == st3) // inicializimi funksionoi

Si mund të kopjoj një rresht në tjetrin? Me operacionin e zakonshëm të caktimit:

St2 = st3; // kopjoni st3 në st2

Për të bashkuar vargjet, përdorni operacionet e mbledhjes (+) ose të mbledhjes dhe caktimit (+ =). Le të jepen dy rreshta:

Vargu s1 ("përshëndetje"); vargu s2 ("bota \ n");

Mund të marrim rreshtin e tretë, i përbërë nga lidhja e dy të parave, si kjo:

Vargu s3 = s1 + s2;

Nëse duam të shtojmë s2 në fund të s1, duhet të shkruajmë:

S1 + = s2;

Operacioni i mbledhjes mund të bashkojë objektet e klasës së vargut jo vetëm me njëri-tjetrin, por edhe me vargje të një lloji të integruar. Ju mund ta rishkruani shembullin e mësipërm në mënyrë që karakteret speciale dhe shenjat e pikësimit të përfaqësohen nga një lloj i integruar, dhe fjalët domethënëse të përfaqësohen nga objektet e klasës së vargut:

Const char * pc = ","; vargu s1 ("përshëndetje"); vargu s2 ("bota");
vargu s3 = s1 + pc + s2 + "\ n";

Shprehje si këto funksionojnë sepse përpiluesi di të konvertojë automatikisht objektet e tipit të integruar në objekte të klasës së vargut. Një caktim i thjeshtë i një vargu inline në një objekt vargu është gjithashtu i mundur:

Vargu s1; const char * pc = "një grup karakteresh"; s1 = pc; // djathtas

Megjithatë, konvertimi i kundërt nuk funksionon. Përpjekja për të bërë inicializimin e mëposhtëm të një vargu të tipit të integruar do të shkaktojë një gabim përpilimi:

Char * str = s1; // gabim përpilimi

Për të kryer këtë konvertim, duhet të thërrisni në mënyrë eksplicite një funksion anëtar me emrin disi të çuditshëm c_str ():

Char * str = s1.c_str (); // pothuajse e saktë

Funksioni c_str () kthen një tregues në një grup karakteresh që përmban vargun e objektit të vargut siç do të ishte në një lloj vargu të integruar.
Shembulli i mësipërm i inicializimit të një treguesi char * str ende nuk është plotësisht i saktë. c_str () kthen një tregues në një grup konstant për të parandaluar mundësinë e modifikimit të drejtpërdrejtë të përmbajtjes së një objekti përmes këtij treguesi të tipit

Konst char *

(Ne do të mbulojmë fjalën kyçe konst në seksionin tjetër.) Opsioni i saktë i inicializimit duket si ky:

Const char * str = s1.c_str (); // djathtas

Karakteret individuale të një objekti të tipit varg, si dhe një lloj i integruar, mund të aksesohen duke përdorur funksionin e marrjes së indeksit. Për shembull, këtu është një copë kodi që zëvendëson të gjitha pikat me nënvizat:

String str ("fa.disney.com"); madhësia int = str.madhësia (); për (int ix = 0; ix< size; ++ix) if (str[ ix ] == ".")
str [ix] = "_";

Kjo është gjithçka që donim të thonim për klasën e vargjeve tani. Në fakt, kjo klasë ka shumë veti dhe aftësi më interesante. Le të themi se shembulli i mëparshëm zbatohet gjithashtu duke thirrur një funksion të vetëm zëvendësues ():

Replace (rr.fillim (), str.fund (), ".", "_");

zëvendësimi () është një nga algoritmet gjenerike që pamë në seksionin 2.8 dhe do të trajtohet në detaje në kapitullin 12. Ky funksion funksionon nga fillimi () në fund (), i cili kthen treguesit në fillim dhe në fund të një vargu dhe zëvendëson elemente të barabarta me parametrin e tretë të tij, me të katërtin.

Ushtrimi 3.12

Kërkoni për gabime në deklaratat e mëposhtme:

(a) char ch = "Rruga e gjatë dhe gjarpëruese"; (b) int ival = (c) char * pc = (d) st st (& ch); (e) pc = 0; (i) pc = "0";
(f) st = pc; (j) st =
(g) ch = pc; (k) ch = * pc;
(h) pc = st; (l) * pc = ival;

Ushtrimi 3.13

Shpjegoni ndryshimin në sjelljen e deklaratave të ciklit të mëposhtëm:

Ndërsa (st ++) ++ cnt;
ndërsa (* st ++)
++ cnt;

Ushtrimi 3.14

Janë dhënë dy programe ekuivalente semantike. E para përdor llojin e vargut të integruar, e dyta përdor klasën e vargut:

// ***** Zbatimi duke përdorur vargjet C ***** #include #përfshi
int main ()
{
gabimet int = 0;
const char * pc = "një varg shumë i gjatë literal"; për (int ix = 0; ix< 1000000; ++ix)
{
int len ​​= strlen (pc);
char * pc2 = char i ri [len + 1];
strcpy (pc2, pc);
nëse (strcmp (pc2, pc))
++ gabime; fshij pc2;
}
cout<< "C-строки: "
<< errors << " ошибок.\n";
) // ***** Zbatimi duke përdorur klasën e vargut ***** #include
#përfshi
int main ()
{
gabimet int = 0;
string str ("një varg shumë i gjatë literal"); për (int ix = 0; ix< 1000000; ++ix)
{
int len ​​= rr.madhësi ();
varg str2 = str;
nëse (rr! = str2)
}
cout<< "класс string: "
<< errors << " ошибок.\n;
}

Çfarë po bëjnë këto programe?
Rezulton se zbatimi i dytë është dy herë më i shpejtë se i pari. E prisnit këtë rezultat? Si e shpjegoni?

Ushtrimi 3.15

A mund të përmirësoni ose plotësoni grupin e operacioneve të klasës së vargut të paraqitur në seksionin e fundit? Shpjegoni sugjerimet tuaja

3.5. Specifikimi konst

Le të marrim shembullin e kodit të mëposhtëm:

Për (indeks int = 0; indeks< 512; ++index) ... ;

Ka dy probleme me 512 literal. E para është lehtësia e perceptimit të tekstit të programit. Pse kufiri i sipërm i variablit të ciklit duhet të jetë saktësisht 512? Çfarë fshihet pas kësaj vlere? Ajo duket e rastësishme...
Problemi i dytë ka të bëjë me lehtësinë e modifikimit dhe mirëmbajtjes së kodit. Supozoni se programi juaj është i gjatë 10,000 rreshta, dhe fjalë për fjalë 512 shfaqet në 4% të tyre. Le të themi se në 80% të rasteve numri 512 duhet të ndryshohet në 1024. A mund ta imagjinoni kompleksitetin e një pune të tillë dhe numrin e gabimeve që mund të bëhen duke korrigjuar vlerën e gabuar?
Të dyja këto probleme zgjidhen në të njëjtën kohë: ju duhet të krijoni një objekt me vlerën 512. Duke i dhënë një emër kuptimplotë, për shembull bufSize, ne do ta bëjmë programin shumë më të kuptueshëm: është e qartë se çfarë është variabla loop duke u krahasuar me.

Indeksi< bufSize

Në këtë rast, ndryshimi i madhësisë së bufSize nuk kërkon shikimin e 400 rreshtave të kodit për të modifikuar 320 prej tyre. Sa më pak gabim është i mundur me koston e shtimit të vetëm një objekti! Tani vlera është 512 të lokalizuara.

Int bufSize = 512; // madhësia e bufferit të hyrjes // ... për (indeks int = 0; indeks< bufSize; ++index)
// ...

Mbetet një problem i vogël: ndryshorja bufSize këtu është një vlerë l që mund të ndryshohet aksidentalisht në program, duke çuar në një gabim të vështirë për t'u kapur. Një nga gabimet më të zakonshme është përdorimi i detyrës (=) në vend të krahasimit (==):

// duke ndryshuar në mënyrë të rastësishme vlerën e bufSize nëse (bufSize = 1) // ...

Si rezultat i ekzekutimit të këtij kodi, vlera bufSize do të bëhet e barabartë me 1, gjë që mund të çojë në sjellje plotësisht të paparashikueshme të programit. Gabimet e këtij lloji zakonisht janë shumë të vështira për t'u zbuluar, sepse ato thjesht nuk janë të dukshme.
Përdorimi i specifikuesit const e zgjidh këtë problem. Duke e deklaruar objektin si

Const int bufSize = 512; // madhësia e buferit të hyrjes

ne e kthejmë variablin në një konstante me një vlerë 512, vlera e së cilës nuk mund të ndryshohet: përpjekje të tilla pengohen nga përpiluesi: përdorimi i gabuar i operatorit të caktimit në vend të krahasimit, si në shembullin e mësipërm, do të shkaktojë një gabim përpilimi. .

// gabim: një përpjekje për të caktuar një vlerë në një konstante nëse (bufSize = 0) ...

Meqenëse një konstante nuk mund t'i caktohet një vlerë, ajo duhet të inicializohet aty ku është përcaktuar. Përcaktimi i një konstante pa e inicializuar atë gjithashtu hedh një gabim përpilimi:

Const dyfishtë pi; // gabim: konstante e pa inicializuar

Konsult dyfishi i pagës min = 9,60; // apo jo? gabim?
dyfish * ptr =

A duhet të lejojë kompajleri një detyrë të tillë? Meqenëse minWage është një konstante, nuk mund t'i caktohet një vlerë. Nga ana tjetër, asgjë nuk na pengon të shkruajmë:

* ptr + = 1,40; // ndryshoni objektin minWage!

Si rregull, përpiluesi nuk është në gjendje të mbrojë kundër përdorimit të treguesve dhe nuk do të jetë në gjendje të sinjalizojë një gabim nëse ato përdoren në këtë mënyrë. Kjo kërkon një analizë shumë të thellë të logjikës së programit. Prandaj, përpiluesi thjesht ndalon caktimin e adresave konstante për treguesit e zakonshëm.
Epo, ne jemi të privuar nga mundësia për të përdorur tregues për konstante? Nr. Për këtë, ka tregues të deklaruar me specifikuesin konst:

Const double * cptr;

ku cptr është një tregues për një objekt të dyfishtë konst. E hollësia qëndron në faktin se treguesi në vetvete nuk është një konstante, që do të thotë se ne mund të ndryshojmë vlerën e tij. Për shembull:

Const dyfish * pc = 0; konst dyfishtë MinPaga = 9,60; // e saktë: nuk mund të modifikohet minWage me pc
pc = dyfishtë dval = 3,14; // e saktë: nuk mund të modifikohet minWage me pc
// edhe pse dval nuk është konstante
pc = // dval i saktë = 3,14159; //djathtas
* pc = 3,14159; // gabim

Adresa e një objekti konstant i caktohet vetëm një treguesi në një konstante. Në të njëjtën kohë, një treguesi të tillë mund t'i caktohet adresa e një ndryshoreje të zakonshme:

Pc =

Një tregues konstant nuk lejon që objekti që ai adreson të ndryshohet duke përdorur adresimin indirekt. Megjithëse dval në shembullin e mësipërm nuk është një konstante, përpiluesi nuk do të lejojë që pc të modifikojë dval. (Përsëri, sepse nuk është në gjendje të përcaktojë adresën e cilës objekt mund të përmbajë treguesi në një moment arbitrar të ekzekutimit të programit.)
Në programet reale, treguesit në konstante përdoren më shpesh si parametra formalë të funksioneve. Përdorimi i tyre siguron që objekti i kaluar në funksion si një argument aktual nuk do të modifikohet nga ai funksion. Për shembull:

// Në programet reale, treguesit në konstante përdoren më shpesh // përdoren si parametra formalë të funksioneve int strcmp (const char * str1, const char * str2);

(Ne do të flasim më shumë rreth treguesve të konstantave në Kapitullin 7 kur flasim për funksionet.)
Ka edhe tregues të vazhdueshëm. (Vini re ndryshimin midis një treguesi konstant dhe një treguesi konstant!). Një tregues konstant mund të adresojë si një konstante ashtu edhe një ndryshore. Për shembull:

Int errNumb = 0; int * const currErr =

Këtu curErr është një tregues konstant për një objekt jo konstant. Kjo do të thotë që ne nuk mund t'i caktojmë adresën e një objekti tjetër, megjithëse vetë objekti mund të modifikohet. Kështu mund të përdoret treguesi curErr:

Bej dicka (); nëse (* curErr) (
error Handler ();
* curErr = 0; // korrekt: zero vlerën errNumb
}

Përpjekja për t'i caktuar një vlerë një treguesi konstant do të shkaktojë një gabim përpilimi:

CurErr = // gabim

Një tregues konstant për një konstante është bashkimi i dy rasteve të shqyrtuara.

Konst dyfishtë pi = 3,14159; const dyfish * const pi_ptr = π

As vlera e objektit të treguar nga pi_ptr dhe as vlera e vetë treguesit nuk mund të ndryshohet në program.

Ushtrimi 3.16

Shpjegoni kuptimin e pesë përkufizimeve të mëposhtme. A ka ndonjë të gabuar mes tyre?

(a) int i; (d) int * const cpi; (b) konst int ic; (e) const int * const cpic; (c) const int * pic;

Ushtrimi 3.17

Cili nga përkufizimet e mëposhtme është i saktë? Pse?

(a) int i = -1; (b) konst int ic = i; (c) const int * pic = (d) int * const cpi = (e) const int * const cpic =

Ushtrimi 3.18

Duke përdorur përkufizimet nga ushtrimi i mëparshëm, specifikoni operatorët e saktë të caktimit. Shpjegoni.

(a) i = ic; (d) pic = cpic; (b) pic = (i) cpic = (c) cpi = foto; (f) ic = * cpic;

3.6. Lloji i referencës

Një lloj referimi, i quajtur ndonjëherë një pseudonim, përdoret për t'i dhënë një objekti një emër alternativ. Një referencë ju lejon të manipuloni një objekt në mënyrë indirekte, ashtu si të përdorni një tregues. Megjithatë, ky manipulim indirekt nuk kërkon sintaksën e veçantë të kërkuar për treguesit. Referencat zakonisht përdoren si parametra formalë të funksioneve. Në këtë seksion, ne do të shikojmë përdorimin e pavarur të objekteve të tipit referencë.
Një lloj reference shënohet duke specifikuar operatorin e marrjes së adresës (&) përpara emrit të ndryshores. Lidhja duhet të inicializohet. Për shembull:

Int ival = 1024; // korrekt: refVal është një referencë për ival int & refVal = ival; // gabim: lidhja duhet të inicializohet int

Int ival = 1024; // gabim: refVal është i llojit int, jo int * int & refVal = int * pi = // i saktë: ptrVal është një referencë për një tregues int * & ptrVal2 = pi;

Pasi të përcaktoni një lidhje, nuk do të jeni më në gjendje ta ndryshoni atë për të punuar me një objekt tjetër (kjo është arsyeja pse lidhja duhet të inicializohet në vendin e përcaktimit të saj). Në shembullin e mëposhtëm, operatori i caktimit nuk e ndryshon vlerën e refVal, vlera e re i caktohet ndryshores ival - asaj që adreson refVal.

Int min_val = 0; // ival merr vlerën e min_val, // në vend të refVal, e ndryshon vlerën në min_val refVal = min_val;

RefVal + = 2; shton 2 te ival, variabli i referuar nga refVal. Po kështu int ii = refVal; cakton ii në vlerën aktuale të ival, int * pi = inicializon pi me adresën ival.

// dy objekte të tipit int përcaktohen int ival = 1024, ival2 = 2048; // një lidhje dhe një objekt i përcaktuar int & rval = ival, rval2 = ival2; // definohet një objekt, një tregues dhe një referencë
int inal3 = 1024, * pi = ival3, & ri = ival3; // dy referenca janë përcaktuar int & rval3 = ival3, & rval4 = ival2;

Një referencë konstante mund të inicializohet me një objekt të një lloji tjetër (nëse, sigurisht, është e mundur të konvertohet një lloj në një tjetër), si dhe me një vlerë të paadresuar, siç është një konstante literale. Për shembull:

Dyfishtë dval = 3,14159; // e vlefshme vetëm për referenca të vazhdueshme
const int & ir = 1024;
const int & ir2 = dval;
konst dyfish & dr = dval + 1.0;

Nëse nuk do të kishim dhënë specifikuesin konst, të tre përkufizimet e referencës do të kishin shkaktuar një gabim përpilimi. Megjithatë, arsyeja pse përpiluesi nuk i anashkalon përkufizimet e tilla është e paqartë. Le të përpiqemi ta kuptojmë.
Për fjalëpërfjalët, kjo është pak a shumë e qartë: ne nuk duhet të jemi në gjendje të ndryshojmë në mënyrë indirekte vlerën e një literale duke përdorur tregues ose referenca. Sa i përket objekteve të një lloji të ndryshëm, përpiluesi e konverton objektin origjinal në ndonjë ndihmës. Për shembull, nëse shkruajmë:

Dyfishtë dval = 1024; const int & ri = dval;

atëherë përpiluesi do ta konvertojë atë diçka si kjo:

Int temp = dval; const int & ri = temp;

Nëse do të mund t'i caktonim një vlerë të re ri, ne në fakt do të ndryshonim jo dval, por temp. Vlera dval do të mbetet e njëjtë, gjë që nuk është aspak e dukshme për programuesin. Prandaj, përpiluesi ndalon veprime të tilla dhe e vetmja mënyrë për të inicializuar një referencë me një objekt të një lloji tjetër është ta deklaroni atë si konst.
Këtu është një shembull tjetër i një lidhjeje që është e vështirë për t'u kuptuar herën e parë. Ne duam të përcaktojmë një referencë për adresën e një objekti konstant, por opsioni ynë i parë hedh një gabim përpilimi:

Const int ival = 1024; // gabim: nevojitet referencë konstante
int * & pi_ref =

Një përpjekje për të rregulluar çështjen duke shtuar specifikuesin konst gjithashtu dështon:

Const int ival = 1024; // ende një gabim const int * & pi_ref =

Cila eshte arsyeja? Nëse lexojmë me kujdes përkufizimin, do të shohim se pi_ref është një referencë për një tregues konstant në një objekt int. Dhe ne kemi nevojë për një tregues jo konstant për një objekt konstant, kështu që hyrja e mëposhtme do të jetë e saktë:

Const int ival = 1024; // djathtas
int * const & piref =

Ekzistojnë dy dallime kryesore midis një lidhjeje dhe një treguesi. Së pari, lidhja duhet të inicializohet në vendin e përcaktimit të saj. Së dyti, çdo ndryshim në lidhje nuk e transformon atë, por objektin të cilit i referohet. Le të shohim disa shembuj. Nëse shkruajmë:

Int * pi = 0;

ne e inicializojmë pi në zero, që do të thotë se pi nuk tregon ndonjë objekt. Në të njëjtën kohë regjistrimi

const int & ri = 0;
do të thotë diçka si kjo:
temp int = 0;
const int & ri = temp;

Sa i përket operacionit të caktimit, në shembullin e mëposhtëm:

Int ival = 1024, ival2 = 2048; int * pi = & ival, * pi2 = pi = pi2;

variabli ival i treguar nga pi mbetet i pandryshuar dhe pi merr vlerën e adresës së ndryshores ival2. Të dy pi dhe pi2 dhe tani tregojnë të njëjtin objekt ival2.
Nëse punojmë me lidhje:

Int & ri = ival, & ri2 = ival2; ri = ri2;

// shembull i përdorimit të lidhjeve // ​​Vlera kthehet në parametrin next_value
bool get_next_value (int & next_value); // operatori i mbingarkuar Operatori matricë + (const Matrix &, const Matrix &);

Int ival; ndërsa (get_next_value (ival)) ...

Int & Vlera e ardhshme = ival;

(Përdorimi i referencave si parametra formalë të funksioneve diskutohet më në detaje në Kapitullin 7.)

Ushtrimi 3.19

A ka ndonjë gabim në këto përkufizime? Shpjegoni. Si do t'i rregullonit ato?

(a) int ival = 1,01; (b) int & rval1 = 1,01; (c) int & rval2 = ival; (d) int & rval3 = (e) int * pi = (f) int & rval4 = pi; (g) int & rval5 = pi *; (h) int & * prval1 = pi; (i) konst int & ival2 = 1; (j) konst int & * prval2 =

Ushtrimi 3.20

A ka ndonjë caktim të gabuar midis këtyre (duke përdorur përkufizimet nga ushtrimi i mëparshëm)?

(a) rval1 = 3,14159; (b) prval1 = prval2; (c) prval2 = rval1; (d) * prval2 = ival2;

Ushtrimi 3.21

Gjeni gabimet në udhëzimet e dhëna:

(a) int ival = 0; const int * pi = 0; const int & ri = 0; (b) pi =
ri =
pi =

3.7. Lloji bool

Një objekt i tipit bool mund të marrë një nga dy vlerat: true dhe false. Për shembull:

// inicializoni vargun e vargut search_word = get_word (); // inicializoni variablin e gjetur
bool gjetur = fals; varg fjala tjetër; ndërsa (cin >> fjala tjetër)
nëse (fjala_tjetër == fjala_kërkimi)
gjetur = e vërtetë;
// ... // stenografi: nëse (gjetur == e vërtetë)
nëse (gjendet)
cout<< "ok, мы нашли слово\n";
tjetër cout<< "нет, наше слово не встретилось.\n";

Megjithëse bool është një nga llojet e numrave të plotë, ai nuk mund të deklarohet si i nënshkruar, i panënshkruar, i shkurtër ose i gjatë, kështu që përkufizimi i mësipërm është i gabuar:

// gabim short bool found = false;

Objektet e tipit bool konvertohen në mënyrë implicite në llojin int. E vërtetë bëhet 1, dhe e gabuar bëhet 0. Për shembull:

Bool gjetur = false; int dukuri_count = 0; ndërsa (/ * mërmëritje * /)
{
gjetur = kërkoj_për (/ * diçka * /); // gjetur është konvertuar në 0 ose 1
numërimi_dukuri + = u gjet; )

Në të njëjtën mënyrë, vlerat e llojeve të numrave të plotë dhe treguesve mund të konvertohen në vlera të tipit bool. Në këtë rast, 0 interpretohet si false, dhe çdo gjë tjetër si e vërtetë:

// kthen numrin e dukurive extern int find (const string &); bool gjetur = fals; nëse (gjetur = gjej ("buzë trëndafili")) // e saktë: e gjetur == e vërtetë // kthen një tregues te elementi
extern int * find (vlera int); nëse (gjetur = gjej (1024)) // e saktë: e gjetur == e vërtetë

3.8. Numërimet

Nuk është e pazakontë të përcaktohet një variabël që merr vlera nga një grup. Le të themi se një skedar hapet në cilindo nga tre mënyrat: për lexim, për shkrim, për shtim.
Natyrisht, tre konstante mund të përcaktohen për të treguar këto mënyra:

Const int input = 1; konst int output = 2; const int append = 3;

dhe përdorni këto konstante:

Bool open_file (string file_emri, int open_mode); //...
open_file ("Phoenix_and_the_Crane", shtoj);

Kjo zgjidhje është e pranueshme, por jo plotësisht e pranueshme, pasi nuk mund të garantojmë që argumenti i kaluar në funksionin open_file () është vetëm 1, 2 ose 3.
Përdorimi i një tipi të numëruar e zgjidh këtë problem. Kur shkruajmë:

Enum open_modes (hyrje = 1, dalje, shtoj);

ne jemi duke përcaktuar një lloj të ri open_modes. Vlerat e vlefshme për një objekt të këtij lloji janë të kufizuara në 1, 2 dhe 3, ku secila prej vlerave të specifikuara ka një emër kujtues. Ne mund të përdorim emrin e këtij lloji të ri për të përcaktuar si objektin e atij lloji ashtu edhe llojin e parametrave formalë të funksionit:

Skedari i hapur i pavlefshëm (emri i skedarit të vargut, mënyrat e hapura om);

hyrje, dalje dhe shtoj janë elementet e numërimit... Një grup anëtarësh numërimi specifikon një grup të vlefshëm vlerash për një objekt të një lloji të caktuar. Një variabël i tipit open_modes (në shembullin tonë) inicializohet në njërën prej këtyre vlerave, gjithashtu mund t'i caktohet ndonjë prej tyre. Për shembull:

Open_file ("Phoenix and the Crane", shtojce);

Një përpjekje për t'i caktuar një ndryshoreje të këtij lloji një vlerë të ndryshme nga një prej elementeve të numërimit (ose për t'ia kaluar atë si parametër një funksioni) do të shkaktojë një gabim përpilimi. Edhe nëse përpiqemi të kalojmë një vlerë të plotë që korrespondon me një nga elementët e numërimit, përsëri marrim një gabim:

// gabim: 1 nuk është anëtar i numeracionit open_modes open_file ("Jonah", 1);

Ekziston një mënyrë për të përcaktuar një variabël të tipit open_modes, për t'i caktuar vlerën e një prej elementeve të numërimit dhe për ta kaluar atë si parametër në funksion:

Mënyrat e_hapura om = hyrje; // ... om = shtoj; open_file ("TailTell", om);

Sidoqoftë, është e pamundur të merren emrat e elementëve të tillë. Nëse shkruajmë një deklaratë dalëse:

Cout<< input << " " << om << endl;

ne ende marrim:

Ky problem zgjidhet nëse përcaktoni një varg vargje në të cilin elementi me indeks të barabartë me vlerën e elementit të numërimit do të përmbajë emrin e tij. Me një grup të tillë, ne mund të shkruajmë:

Cout<< open_modes_table[ input ] << " " << open_modes_table[ om ] << endl Будет выведено: input append

Gjithashtu, nuk mund të përsërisni mbi të gjitha vlerat e numërimit:

// nuk mbështetet për (open_modes iter = hyrje; iter! = shtoj; ++ inter) // ...

Fjala kyçe enum përdoret për të përcaktuar një numërim dhe emrat e elementeve specifikohen në kllapa kaçurrelë, të ndara me presje. Si parazgjedhje, e para është 0, tjetra është 1, e kështu me radhë. Ky rregull mund të ndryshohet duke përdorur operatorin e caktimit. Për më tepër, çdo element tjetër pa një vlerë të specifikuar në mënyrë eksplicite do të jetë 1 më shumë se elementi që i paraprin në listë. Në shembullin tonë, ne specifikuam në mënyrë eksplicite vlerën 1 për hyrje, ndërsa dalja dhe shtesa do të ishin 2 dhe 3. Ja një shembull tjetër:

// trajta == 0, sfera == 1, cilindër == 2, shumëkëndësh == 3 një numër Forma (share, sperë, cilindër, shumëkëndësh);

Vlerat e plota që korrespondojnë me anëtarë të ndryshëm të të njëjtit numërim nuk duhet të jenë të ndryshme. Për shembull:

// pika2d == 2, pika2w == 3, pika 3d == 3, pika3w == 4 pikë numëro (pika 2d = 2, pika2w, pika 3d = 3, pika3w = 4);

Një objekt i numëruar mund të përcaktohet, përdoret në shprehje dhe i kalohet një funksioni si argument. Një objekt i tillë inicializohet vetëm me vlerën e njërit prej elementeve të numërimit, dhe vetëm një vlerë e tillë i caktohet - ose në mënyrë eksplicite ose si vlera e një objekti tjetër të të njëjtit lloj. Edhe vlerat e plota që korrespondojnë me elementët e vlefshëm të numërimit nuk mund t'i caktohen asaj:

Mumblem i zbrazët () (Pikat pt3d = pika3d; // e saktë: pt2d == 3 // gabim: pt3w është inicializuar për të shkruar int Pikat pt3w = 3; // gabim: shumëkëndëshi nuk përfshihet në numërimin e pikave pt3w = shumëkëndësh; / / e saktë: të dy objektet e tipit Points pt3w = pt3d;)

Sidoqoftë, në shprehjet aritmetike, një numërim mund të shndërrohet automatikisht në llojin int. Për shembull:

Const int array_size = 1024; // e saktë: pt2w konvertohet në int
int chunk_size = madhësia e grupit * pt2w;

3.9. Lloji i grupit

Ne kemi prekur tashmë vargjet në seksionin 2.1. Një grup është një grup elementësh të të njëjtit lloj, të cilët aksesohen nga indeksi - numri rendor i elementit në grup. Për shembull:

Int ival;

përcakton ival si një variabël të tipit int, dhe deklaratën

Int ia [10];

specifikon një grup prej dhjetë objektesh int. Për secilin prej këtyre objekteve, ose elementet e grupit, mund të arrihet duke përdorur funksionin e marrjes së indeksit:

Ival = ia [2];

i cakton variablës ival vlerën e elementit të grupit ia me indeks 2. Në mënyrë të ngjashme

Ia [7] = ival;

vendos elementin në indeksin 7 në ival.

Përkufizimi i grupit përbëhet nga një specifikues i llojit, një emër i grupit dhe një madhësi. Madhësia specifikon numrin e elementeve në grup (të paktën 1) dhe është i mbyllur në kllapa katrore. Madhësia e grupit duhet të dihet tashmë në kohën e kompilimit, dhe për këtë arsye, ajo duhet të jetë një shprehje konstante, megjithëse nuk është domosdoshmërisht e specifikuar nga një fjalë për fjalë. Këtu janë shembuj të përkufizimeve të sakta dhe të pasakta të vargjeve:

Extern int get_size (); // konstantet buf_size dhe max_files
const int buf_size = 512, max_files = 20;
int staf_size = 27; // e saktë: konstante char input_buffer [madhësia_buf]; // e saktë: shprehje konstante: 20 - 3 char * skedari Tabela [max_files-3]; // gabim: jo konstante paga të dyfishta [staff_size]; // gabim: shprehje jo konstante int test_scores [merr_madhësinë ()];

Objektet buf_size dhe max_files janë konstante, kështu që përkufizimet e grupeve input_buffer dhe fileTable janë të sakta. Por stafi_size është një variabël (edhe pse është inicializuar me një konstante prej 27), që do të thotë se pagat nuk lejohen. (Përpiluesi nuk është në gjendje të gjejë vlerën e variablit stafi_size kur përcaktohet grupi i pagave.)
Shprehja max_files-3 mund të vlerësohet në kohën e përpilimit, prandaj përkufizimi i grupit fileTable është sintaksisht i saktë.
Numërimi i elementeve fillon nga 0, kështu që për një grup prej 10 elementësh diapazoni i saktë i indekseve nuk është 1 - 10, por 0 - 9. Këtu është një shembull i përsëritjes mbi të gjithë elementët e grupit:

Int main () (const int array_size = 10; int ia [madhësia_array]; për (int ix = 0; ix< array_size; ++ ix)
ia [ix] = ix;
}

Kur përcaktoni një grup, mund ta inicializoni në mënyrë eksplicite duke renditur vlerat e elementeve të tij në kllapa kaçurrelë, të ndara me presje:

Const int array_size = 3; int ia [madhësia e grupit] = (0, 1, 2);

Nëse specifikojmë në mënyrë eksplicite një listë vlerash, atëherë mund të kapërcejmë specifikimin e madhësisë së grupit: përpiluesi do të llogarisë vetë numrin e elementeve:

// grup me madhësi 3 int ia = (0, 1, 2);

Kur të dyja madhësia dhe lista e vlerave janë të specifikuara në mënyrë eksplicite, tre opsione janë të mundshme. Kur madhësia dhe numri i vlerave përputhen, gjithçka është e qartë. Nëse lista e vlerave është më e shkurtër se madhësia e specifikuar, elementët e mbetur të grupit inicializohen në zero. Nëse ka më shumë vlera në listë, përpiluesi shfaq një mesazh gabimi:

// ia ==> (0, 1, 2, 0, 0) konst int madhësia e grupit = 5; int ia [madhësia e grupit] = (0, 1, 2);

Një grup karakteresh mund të inicializohet jo vetëm me një listë të vlerave të karaktereve në kllapa kaçurrelë, por edhe me një varg literal. Megjithatë, ka disa dallime midis këtyre metodave. Le të themi

Const char cal = ("C", "+", "+"); const char cal2 = "C ++";

Dimensioni i grupit ca1 është 3, i vargut ca2 - 4 (karakteri null përfundimtar merret parasysh në vargjet literale). Përkufizimi i mëposhtëm do të sjellë një gabim përpilimi:

// gabim: vargu "Daniel" përbëhet nga 7 elemente const char ch3 [6] = "Daniel";

Një grupi nuk mund t'i caktohet vlera e një grupi tjetër dhe inicializimi i një grupi me një tjetër është i pavlefshëm. Gjithashtu, nuk lejohet përdorimi i një grupi referencash. Këtu janë shembuj të përdorimit të saktë dhe të gabuar të vargjeve:

Const int array_size = 3; int ix, jx, kx; // korrekt: një grup treguesish të tipit int * int * iar = (& ix, & jx, & kx); // gabim: vargjet e referencave nuk lejohen int & iar = (ix, jx, kx); int main ()
{
int ia3 (madhësia_array]; // e saktë
// gabim: grupet e integruara nuk mund të kopjohen
ia3 = ia;
kthimi 0;
}

Për të kopjuar një grup në tjetrin, duhet ta bëni këtë për secilin element veç e veç:

Const int array_size = 7; int ia1 = (0, 1, 2, 3, 4, 5, 6); int main () (
int ia3 [madhësia e grupit]; për (int ix = 0; ix< array_size; ++ix)
ia2 [ix] = ia1 [ix]; kthimi 0;
}

Çdo shprehje që jep një rezultat të plotë mund të përdoret si një indeks grupi. Për shembull:

Int someVal, get_index (); ia2 [get_index ()] = someVal;

Le të theksojmë se gjuha C ++ nuk ofron kontroll mbi indekset e grupeve - as në kohën e kompilimit, as në kohën e ekzekutimit. Vetë programuesi duhet të sigurojë që indeksi të mos shkojë përtej kufijve të grupit. Gabimet e indeksit janë mjaft të zakonshme. Fatkeqësisht, nuk është aq e vështirë të hasësh shembuj të programeve që përpilojnë dhe madje funksionojnë, por megjithatë përmbajnë gabime fatale që herët a vonë çojnë në përplasje.

Ushtrimi 3.22

Cili nga përkufizimet e vargjeve të mëposhtme përmban gabime? Shpjegoni.

(a) int ia [madhësia_buf]; (d) int ia [2 * 7 - 14] (b) int ia [merr_madhësinë ()]; (e) char st [11] = "themelore"; (c) int ia [4 * 7 - 14];

Ushtrimi 3.23

Pjesa e mëposhtme e kodit duhet të inicializojë çdo element të grupit me një vlerë indeksi. Gjeni gabimet që keni bërë:

Int main () (konst int grupi_madhësia = 10; int ia [madhësia_grupi]; për (int ix = 1; ix<= array_size; ++ix)
ia [ia] = ix; //...
}

3.9.1. Vargjet shumëdimensionale

Në C ++ është e mundur të përdoren vargje shumëdimensionale, kur deklaroni të cilat duhet të specifikoni kufirin e duhur të secilit dimension në kllapa të veçanta katrore. Këtu është përkufizimi i një grupi dydimensional:

Int ia [4] [3];

Vlera e parë (4) specifikon numrin e rreshtave, e dyta (3) - numrin e kolonave. Objekti ia përkufizohet si një grup prej katër rreshtash me nga tre elementë secila. Vargjet shumëdimensionale mund të inicializohen gjithashtu:

Int ia [4] [3] = ((0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11));

Kllapat kaçurrelë të brendshëm që thyejnë listën e vlerave në vija janë opsionale dhe zakonisht përdoren për lexueshmëri. Inicializimi më poshtë është saktësisht i njëjtë me shembullin e mëparshëm, megjithëse më pak i qartë:

Int ia = (0,1,2,3,4,5,6,7,8,9,10,11);

Përkufizimi i mëposhtëm inicializon vetëm elementët e parë të çdo rreshti. Elementet e mbetur do të jenë zero:

Int ia [4] [3] = ((0), (3), (6), (9));

Nëse i lini mbajtëset e brendshme kaçurrela, rezultati është krejtësisht i ndryshëm. Të tre elementët e rreshtit të parë dhe elementi i parë i së dytës do të marrin vlerën e specifikuar, dhe pjesa tjetër do të inicializohet në mënyrë implicite në 0.

Int ia [4] [3] = (0, 3, 6, 9);

Kur u referoheni elementeve të një grupi shumëdimensional, duhet të përdorni indekse për çdo dimension (ato janë të mbyllura në kllapa katrore). Kështu duket inicializimi i një grupi dydimensional duke përdorur sythe të mbivendosur:

Int main () (konst int rowSize = 4; const int colSize = 3; int ia [Madhësia e rreshtit] [colSize]; për (int = 0; i< rowSize; ++i)
për (int j = 0; j< colSize; ++j)
ia [i] [j] = i + j j;
}

Dizajn

Ia [1, 2]

është e vlefshme nga pikëpamja e sintaksës C ++, por nuk do të thotë aspak atë që do të priste një programues i papërvojë. Ky nuk është një deklaratë e një grupi dydimensional 1-nga-2. Përmbledhja në kllapa katrore është një listë shprehjesh e ndarë me presje që do të kthejë vlerën e fundit prej 2 (shih operatorin me presje në seksionin 4.2). Prandaj, deklarata ia është ekuivalente me ia. Kjo është një mundësi tjetër për gabim.

3.9.2. Marrëdhënia e vargjeve dhe treguesve

Nëse kemi një përkufizim të vargut:

Int ia = (0, 1, 1, 2, 3, 5, 8, 13, 21);

atëherë çfarë do të thotë të tregosh thjesht emrin e tij në program?

Përdorimi i një identifikuesi të grupit në një program është i barabartë me specifikimin e adresës së elementit të tij të parë:

Në mënyrë të ngjashme, mund t'i referoheni vlerës së elementit të parë të grupit në dy mënyra:

// të dyja shprehjet kthejnë elementin e parë * ia; ia;

Për të marrë adresën e elementit të dytë të grupit, duhet të shkruajmë:

Siç e përmendëm më herët, shprehja

jep edhe adresën e elementit të dytë të grupit. Prandaj, kuptimi i tij na jepet në dy mënyrat e mëposhtme:

* (ia + 1); ia;

Vini re ndryshimin në shprehje:

* ia + 1 dhe * (ia + 1);

Operacioni i dereferencimit ka një më të lartë një prioritet sesa operacioni i shtimit (shih seksionin 4.13 për prioritetet e funksionimit). Prandaj, shprehja e parë së pari çreferencon variablin ia dhe merr elementin e parë të grupit dhe më pas i shton atij 1. Shprehja e dytë jep vlerën e elementit të dytë.

Ju mund të përsërisni mbi grup duke përdorur një indeks, siç bëmë në seksionin e mëparshëm, ose duke përdorur tregues. Për shembull:

#përfshi int main () (int ia = (0, 1, 1, 2, 3, 5, 8, 13, 21); int * pbegin = ia; int * pend = ia + 9; ndërsa (pbegin! = pend) ( cout<< *pbegin <<; ++pbegin; } }

Treguesi pbegin inicializohet me adresën e elementit të parë në grup. Çdo kalim nëpër lak e rrit këtë tregues me 1, që do të thotë se zhvendoset në elementin tjetër. Si e dini se ku të qëndroni? Në shembullin tonë, ne përcaktuam një tregues të dytë pendues dhe e inicializuam atë me adresën që ndjek elementin e fundit të grupit ia. Sapo pbegin është e barabartë me pend, ne e dimë se grupi ka përfunduar. Le ta rishkruajmë këtë program në mënyrë që fillimi dhe fundi i grupit t'i kalohen si parametra një funksioni të përgjithësuar që mund të printojë një grup të çdo madhësie:

#përfshi void ia_print (int * pbegin, int * pend) (
ndërsa (pfillim! = pend) (
cout<< *pbegin << " ";
++ pfillimi;
}
) int main ()
{
int ia = (0, 1, 1, 2, 3, 5, 8, 13, 21);
ia_print (ia, ia + 9);
}

Funksioni ynë është bërë më i gjithanshëm, megjithatë, ai mund të funksionojë vetëm me grupe të llojit int. Ekziston gjithashtu një mënyrë për të hequr këtë kufizim: transformoni këtë funksion në një shabllon (shabllonet u prezantuan shkurtimisht në seksionin 2.5):

#përfshi shabllon printim i pavlefshëm (elemType * pbegin, elemType * pend) (ndërsa (pbegin! = pend) (cout<< *pbegin << " "; ++pbegin; } }

Tani mund të thërrasim funksionin tonë print () për të printuar vargje të çdo lloji:

Int main () (int ia = (0, 1, 1, 2, 3, 5, 8, 13, 21); dyfishtë da = (3.14, 6.28, 12.56, 25.12); vargu sa = ("derrci", " eeyore "," pooh "); print (ia, ia + 9);
printim (da, da + 4);
print (sa, sa + 3);
}

Ne kemi shkruar të përgjithësuara funksionin. Biblioteka standarde ofron një grup algoritmesh gjenerike (e kemi përmendur këtë në seksionin 3.4), të zbatuara në mënyrë të ngjashme. Parametrat e funksioneve të tilla janë tregues në fillim dhe në fund të grupit, me të cilin ata kryejnë veprime të caktuara. Për shembull, ja se si duken thirrjet në algoritmin e renditjes së përgjithshme:

#përfshi int main () (int ia = (107, 28, 3, 47, 104, 76); string sa = ("derrkuq", "eeyore", "pooh"); renditje (ia, ia + 6);
rendit (sa, sa + 3);
};

(Ne do të ndalemi në algoritme gjenerike në detaje në Kapitullin 12; Shtojca ofron shembuj të përdorimit të tyre.)
Biblioteka standarde C ++ përmban një grup klasash që përmbledhin përdorimin e kontejnerëve dhe treguesve. (Kjo u diskutua në seksionin 2.8.) Në seksionin tjetër, ne do të trajtojmë vektorin standard të tipit të kontejnerit, i cili është një zbatim i orientuar nga objekti i një grupi.

3.10. Klasa vektoriale

Përdorimi i klasës vektoriale (shih seksionin 2.8) është një alternativë për përdorimin e vargjeve të integruara. Kjo klasë ofron shumë më tepër opsione, ndaj preferohet përdorimi i saj. Sidoqoftë, ka situata kur nuk mund të bëni pa vargje të integruara. Një nga këto situata është përpunimi i parametrave të linjës së komandës që i kalohen programit, të cilin do ta diskutojmë në seksionin 7.8. Klasa vektoriale, si klasa e vargut, është pjesë e Bibliotekës Standarde C ++.
Për të përdorur vektorin, duhet të përfshini skedarin e kokës:

#përfshi

Ekzistojnë dy qasje krejtësisht të ndryshme për përdorimin e një vektori, le t'i quajmë ato idioma e grupit dhe idioma STL. Në rastin e parë, një objekt i klasës vektoriale përdoret në të njëjtën mënyrë si një grup i një lloji të integruar. Përcaktohet një vektor i një dimensioni të caktuar:

Vektor< int >ivec (10);

e cila është analoge me përcaktimin e një grupi të tipit të integruar:

Int ia [10];

Për të hyrë në elementë individualë të vektorit, përdoret operacioni i marrjes së indeksit:

Void simp1e_examp1e () (konst int e1em_size = 10; vektor< int >ivec (e1em_size); int ia [e1em_size]; për (int ix = 0; ix< e1em_size; ++ix)
ia [ix] = ivec [ix]; //...
}

Ne mund të zbulojmë dimensionin e një vektori duke përdorur funksionin size () dhe të kontrollojmë nëse vektori është bosh duke përdorur funksionin bosh (). Për shembull:

Print_vektori i zbrazët (vektor ivec) (nëse (ivec.empty ()) kthehet; për (int ix = 0; ix< ivec.size(); ++ix)
cout<< ivec[ ix ] << " ";
}

Elementet vektoriale inicializohen me vlerat e paracaktuara. Për llojet numerike dhe treguesit, kjo vlerë është 0. Nëse objektet e klasës veprojnë si elementë, iniciatori për to vendoset nga konstruktori i paracaktuar (shih seksionin 2.3). Sidoqoftë, iniciatori gjithashtu mund të specifikohet në mënyrë eksplicite duke përdorur formularin:

Vektor< int >ivec (10, -1);

Të dhjetë elementët e vektorit do të jenë -1.
Një grup i një lloji të integruar mund të inicializohet në mënyrë eksplicite me një listë:

Int ia [6] = (-2, -1, O, 1, 2, 1024);

Për një objekt të vektorit të klasës, i njëjti veprim nuk është i mundur. Sidoqoftë, një objekt i tillë mund të inicializohet duke përdorur një grup të tipit të integruar:

// 6 artikuj ia kopjohen në vektorin ivec< int >ivec (ia, ia + 6);

Dy tregues i kalohen konstruktorit të vektorit ivec - një tregues në fillim të grupit ia dhe në elementin që ndjek të fundit. Si një listë e vlerave fillestare, lejohet të specifikoni jo të gjithë grupin, por disa nga diapazoni i tij:

// Kopjohen 3 elemente: ia, ia, vektori ia< int >ivec (& ia [2], & ia [5]);

Një tjetër ndryshim midis një vektori dhe një grupi të një lloji të integruar është aftësia për të inicializuar një objekt të tipit vektor në një tjetër dhe për të përdorur operacionin e caktimit për të kopjuar objektet. Për shembull:

Vektor< string >svec; void init_and_assign () (// një vektor inicializohet nga një vektor tjetër< string >emrat e përdoruesve (svec); // ... // një vektor kopjohet në një tjetër
svec = emrat e përdoruesve;
}

Kur flasim për idiomën STL, nënkuptojmë një qasje shumë të ndryshme për përdorimin e një vektori. Në vend që të specifikojmë menjëherë madhësinë e dëshiruar, ne përcaktojmë një vektor bosh:

Vektor< string >teksti;

Pastaj i shtojmë elemente duke përdorur funksione të ndryshme. Për shembull, funksioni push_back () fut një element në fund të vektorit. Këtu është një copë kodi që lexon një sekuencë rreshtash nga hyrja standarde dhe i shton ato në një vektor:

fjalë vargu; ndërsa (cin >> fjalë) (tekst.push_mbrapa (fjalë); // ...)

Edhe pse ne mund të përdorim operacionin e indeksit të marrjes për të përsëritur mbi elementët e vektorit:

Cout<< "считаны слова: \n"; for (int ix =0; ix < text.size(); ++ix) cout << text[ ix ] << " "; cout << endl;

më tipike brenda kësaj idiome do të ishte përdorimi i përsëritësve:

Cout<< "считаны слова: \n"; for (vector:: iterator it = teksti.filloj (); it! = teksti.fundi (); ++ it) cout<< *it << " "; cout << endl;

Një iterator është një klasë standarde e bibliotekës që në fakt është një tregues për një element të grupit.
Shprehje

çreferencon iteratorin dhe jep vetë elementin vektor. udhëzime

Zhvendos treguesin në artikullin tjetër. Nuk ka nevojë të përzihen këto dy qasje. Për të ndjekur idiomën STL kur përcaktoni një vektor bosh:

Vektor ivec;

Do të ishte gabim të shkruash:

Nuk kemi ende një element të vetëm vektor ivec; numri i elementeve zbulohet duke përdorur funksionin e madhësisë ().

Mund të bëhet edhe gabimi i kundërt. Nëse përcaktojmë një vektor të një madhësie të caktuar, për shembull:

Vektor ia (10);

Pastaj futja e elementeve rrit madhësinë e tij, duke shtuar elementë të rinj atyre ekzistues. Ndërsa kjo duket e qartë, një programues fillestar mund të shkruajë:

Const int size = 7; int ia [madhësia] = (0, 1, 1, 2, 3, 5, 8); vektoriale< int >ivec (madhësia); për (int ix = 0; ix< size; ++ix) ivec.push_back(ia[ ix ]);

Unë nënkuptova inicializimin e vektorit ivec me vlerat e elementeve ia, në vend të të cilit doli vektori ivec i madhësisë 14.
Duke ndjekur idiomën STL, jo vetëm që mund të shtoni, por edhe të hiqni elemente të një vektori. (Ne do t'i mbulojmë të gjitha këto në detaje dhe me shembuj në Kapitullin 6.)

Ushtrimi 3.24

A ka gabime në përkufizimet e mëposhtme?
int ia [7] = (0, 1, 1, 2, 3, 5, 8);

(a) vektor< vector< int >> ivec;
(b) vektor< int >ivec = (0, 1, 1, 2, 3, 5, 8);
(c) vektor< int >ivec (ia, ia + 7);
(d) vektor< string >svec = ivec;
(e) vektor< string >svec (10, varg ("null"));

Ushtrimi 3.25

Zbatoni funksionin e mëposhtëm:
bool është_e barabartë (konst int * ia, int ia_size,
vektor konst & ivec);
Funksioni is_equal () krahason dy kontejnerë element për element. Në rastin e kontejnerëve të madhësive të ndryshme, "bishti" i një më të gjatë nuk merret parasysh. Është e qartë se nëse të gjithë elementët e krahasuar janë të barabartë, funksioni kthen true, nëse të paktën njëri ndryshon - false. Përdorni një përsëritës për të përsëritur mbi artikujt. Shkruani një funksion main () që thërret is_equal ().

3.11. Klasa komplekse

Klasa e numrave kompleks është një klasë tjetër nga biblioteka standarde. Si zakonisht, për ta përdorur atë, duhet të përfshini një skedar kokë:

#përfshi

Një numër kompleks ka dy pjesë - reale dhe imagjinare. Pjesa imagjinare është rrënja katrore e një numri negativ. Është e zakonshme të shkruhet një numër kompleks në formë

ku 2 është pjesa reale dhe 3i është pjesa imagjinare. Këtu janë shembuj të përkufizimeve të objekteve të tipit kompleks:

// Numri i pastër imagjinar: 0 + 7-i kompleks< double >purei (0, 7); // pjesa imagjinare është 0: 3 + Oi kompleks< float >rea1_num (3); // si pjesët reale ashtu edhe ato imagjinare janë komplekse 0: 0 + 0-i< long double >zero; // inicializoni një numër kompleks me një kompleks tjetër< double >purei2 (purei);

Meqenëse kompleksi, si vektori, është një shabllon, ne mund ta instantojmë atë me float, double dhe long double, si në shembujt e mësipërm. Ju gjithashtu mund të përcaktoni një grup elementësh të tipit kompleks:

Kompleksi< double >konjuguar [2] = (kompleks< double >(2, 3), kompleks< double >(2, -3) };

Kompleksi< double >* ptr = kompleks< double >& ref = * ptr;

3.12. Direktiva Typedef

Direktiva typedef ju lejon të vendosni një sinonim për një lloj të dhënash të integruar ose të përcaktuar nga përdoruesi. Për shembull:

Typedef paga të dyfishta; vektor typedef vec_int; typedef vec_int test_rezultatet; typedef bool in_attendance; typedef int * Pint;

Emrat e përcaktuar me direktivën typedef mund të përdoren saktësisht në të njëjtën mënyrë si specifikuesit e tipit:

// dy herë në orë, javore; pagat për orë, javore; // vektor vecl (10);
vec_int vecl (10); // vektor test0 (c1ass_size); const int c1ass_size = 34; test_rezultatet test0 (c1ass_size); // vektor< bool >frekuentimi; vektoriale< in_attendance >frekuentimi (c1ass_size); // int * tabela [10]; Tabela pint [10];

Kjo direktivë fillon me fjalën kyçe typedef, e ndjekur nga një specifikues tipi dhe përfundon me një identifikues që bëhet sinonim për llojin e specifikuar.
Për çfarë përdoren emrat e përcaktuar me direktivën typedef? Duke përdorur emra kujtues për llojet e të dhënave, ju mund ta bëni programin tuaj më të lehtë për t'u lexuar. Për më tepër, është zakon të përdoren emra të tillë për lloje komplekse komplekse që përndryshe janë të vështira për t'u kuptuar (shih shembullin në seksionin 3.14), për të deklaruar tregues për funksionet dhe funksionet e anëtarëve të një klase (shih seksionin 13.6).
Më poshtë është një shembull i një pyetjeje për të cilën pothuajse të gjithë japin përgjigjen e gabuar. Gabimi është shkaktuar nga një keqkuptim i direktivës typedef si një zëvendësim i thjeshtë makro teksti. Përkufizimi është dhënë:

Typedef char * cstring;

Cili është lloji i ndryshores cstr në deklaratën e mëposhtme:

Extern const cstring cstr;

Përgjigja, e cila duket e qartë:

Const char * cstr

Megjithatë, kjo nuk është e vërtetë. Specifikimi const i referohet cstr, kështu që përgjigja e saktë është një tregues konstant për char:

Char * const cstr;

3.13. Përcaktues i paqëndrueshëm

Një objekt deklarohet si i paqëndrueshëm (i paqëndrueshëm, i ndryshueshëm në mënyrë asinkrone) nëse vlera e tij mund të ndryshohet pa u vënë re nga përpiluesi, për shembull, një ndryshore që përditësohet nga vlera e orës së sistemit. Ky specifikues i thotë përpiluesit që të mos optimizojë kodin për të punuar me objektin e dhënë.
Specifikimi i paqëndrueshëm përdoret si specifikuesi konst:

I paqëndrueshëm int disp1ay_register; volatile Detyra * curr_task; volatile int ixa [max_max]; bitmap_buf i paqëndrueshëm i ekranit;

display_register është një objekt int i paqëndrueshëm. curr_task është një tregues për një objekt të paqëndrueshëm Task. ixa është një grup i paqëndrueshëm i numrave të plotë, dhe çdo element i një grupi të tillë konsiderohet i paqëndrueshëm. bitmap_buf është një objekt i paqëndrueshëm i ekranit, secili prej anëtarëve të të dhënave të tij konsiderohet gjithashtu i paqëndrueshëm.
Qëllimi i vetëm i përdorimit të specifikuesit të paqëndrueshëm është t'i tregojë kompajlerit se ai nuk mund të përcaktojë se kush dhe si mund të ndryshojë vlerën e një objekti të caktuar. Prandaj, përpiluesi nuk duhet të kryejë optimizim në kodin që përdor këtë objekt.

3.14. Klasa në çift

Klasa e çiftit të Bibliotekës Standarde C ++ na lejon të përcaktojmë një palë vlerash me një objekt nëse ka ndonjë marrëdhënie semantike midis tyre. Këto vlera mund të jenë të njëjta ose lloje të ndryshme. Për të përdorur këtë klasë, duhet të përfshini skedarin e kokës:

#përfshi

Për shembull, udhëzimi

Çift< string, string >autor ("James", "Joyce");

krijon një objekt autor të tipit çift, i përbërë nga dy vlera vargu.
Pjesët individuale të një çifti mund të merren duke përdorur anëtarët e parë dhe të dytë:

String firstBook; nëse (Joyce.first == "James" &&
Joyce.second == "Joyce")
firstBook = "Stephen Hero";

Nëse keni nevojë të përcaktoni disa objekte të të njëjtit lloj të kësaj klase, është e përshtatshme të përdorni direktivën typedef:

Çifti Typedef< string, string >Autorët; Autorët proust ("marcel", "proust"); Autorët Joyce ("James", "Joyce"); Autorët musil ("robert", "musi1");

Këtu është një shembull tjetër i përdorimit të një çifti. Vlera e parë përmban emrin e një objekti, e dyta është një tregues për elementin e tabelës që korrespondon me këtë objekt.

Klasa EntrySlot; extern EntrySlot * 1ook_up (string); çift ​​typedef< string, EntrySlot* >Hyrja e simboleve; SymbolEntry aktuale_hyrja ("autor", 1ook_up ("autor"));
// ... nëse (EntrySlot * it = 1ook_up ("redaktori")) (
Current_entry.first = "redaktor";
aktuale_hyrja.e dyta = ajo;
}

(Ne do të rishikojmë klasën e çiftit në diskutimin tonë për llojet e kontejnerëve në Kapitullin 6 dhe algoritmet e përgjithshme në Kapitullin 12.)

3.15. Llojet e klasave

Mekanizmi i klasës ju lejon të krijoni lloje të reja të dhënash; ai prezanton llojet e vargut, vektorit, kompleksit dhe çiftit të diskutuar më sipër. Në kapitullin 2, ne prezantuam konceptet dhe mekanizmat që mbështesin qasjet e orientuara nga objekti dhe ato të orientuara nga objekti përmes zbatimit të klasës Array. Këtu, bazuar në një qasje objekti, ne do të krijojmë një klasë të thjeshtë String, zbatimi i së cilës do t'ju ndihmojë të kuptoni, në veçanti, mbingarkimin e operacionit - folëm për të në seksionin 2.3. (Klasat diskutohen në detaje në kapitujt 13, 14 dhe 15). Ne kemi dhënë një përshkrim të shkurtër të klasës për të dhënë shembuj më interesantë. Lexuesi i ri në C ++ mund ta kapërcejë këtë seksion dhe të presë për një përshkrim më sistematik të klasave në kapitujt në vijim.)
Klasa jonë String duhet të mbështesë inicializimin me një objekt String, një varg literal dhe një lloj vargu të integruar, si dhe funksionimin e caktimit të vlerave të këtyre llojeve në të. Ne përdorim konstruktorët e klasave dhe një operator të mbingarkuar caktimi për këtë. Qasja në karakteret individuale të vargut do të zbatohet si një operacion i mbingarkuar indeksi. Përveç kësaj, do të na duhet: funksioni i madhësisë () për të marrë informacion rreth gjatësisë së vargut; funksioni i krahasimit të objekteve të tipit String dhe një objekti String me një varg të tipit të integruar; si dhe operacionet hyrëse/dalëse të objektit tonë. Së fundi, ne do të zbatojmë aftësinë për të hyrë në paraqitjen e brendshme të vargut tonë si një lloj vargu i integruar.
Një përkufizim i klasës fillon me fjalën kyçe të klasës, e ndjekur nga një identifikues - emri i klasës ose lloji. Në përgjithësi, një klasë përbëhet nga seksione, të paraprira nga fjalët publike dhe private. Një seksion publik zakonisht përmban një grup operacionesh të mbështetur nga një klasë, të quajtur metoda ose funksione anëtare të klasës. Këto funksione anëtare përcaktojnë ndërfaqen publike të një klase, me fjalë të tjera, një grup veprimesh që mund të kryhen në objektet e një klase të caktuar. Një seksion privat zakonisht përfshin anëtarë të të dhënave që ofrojnë zbatim të brendshëm. Në rastin tonë, anëtarët e brendshëm janë _string - një tregues në char, si dhe _size i llojit int. _size do të mbajë informacion për gjatësinë e vargut, dhe _string do të jetë një grup karakteresh i ndarë në mënyrë dinamike. Kështu duket përkufizimi i klasës:

#përfshi klasë String; istream & operator >> (istream &, String &);
ostream & operator<<(ostream&, const String&); class String {
publike:
// grup konstruktorësh
// për inicializimin automatik
// String strl; // Vargu ()
// String str2 ("literal"); // String (const char *);
// Vargu str3 (str2); // String (const String &); String ();
String (const char *);
String (konst String &); // shkatërrues
~ String (); // operatorët e caktimit
// strl = str2
// str3 = "një varg literal" String & operator = (const String &);
String & operator = (const char *); // operatorët për kontrollin e barazisë
// strl == str2;
// str3 == "një varg literal"; operator bool == (const String &);
operator bool == (const char *); // mbingarkoni operatorin e aksesit sipas indeksit
// strl [0] = str2 [0]; char & operator (int); // akses te anëtarët e klasës
madhësia int () (kthimi _size;)
char * c_str () (return _string;) private:
int _size;
char * _string;
}

Klasa String ka tre konstruktorë. Siç u përmend në seksionin 2.3, mekanizmi i mbingarkesës ju lejon të përcaktoni disa zbatime të funksioneve me të njëjtin emër, nëse të gjithë ndryshojnë në numrin dhe / ose llojet e parametrave të tyre. Konstruktori i parë

Është konstruktori i paracaktuar sepse nuk kërkon një vlerë fillestare të qartë. Kur shkruajmë:

Një konstruktor i tillë thirret për str1.
Dy konstruktorët e mbetur kanë secili nga një parametër. Pra, për

String str2 ("varg karakteresh");

Konstruktori thirret

String (const char *);

Vargu str3 (str2);

Konstruktor

String (konst String &);

Lloji i konstruktorit të thirrur përcaktohet nga lloji i argumentit aktual. Konstruktori i fundit, String (const String &), quhet kopje sepse ai inicializon një objekt me një kopje të një objekti tjetër.
Nëse shkruani:

Vargu str4 (1024);

Kjo do të shkaktojë një gabim përpilimi, sepse nuk ka asnjë konstruktor të vetëm me një parametër int.
Një deklaratë operatori e mbingarkuar ka formatin e mëposhtëm:

Operatori Return_type op (lista_parametër);

Ku operatori është një fjalë kyçe dhe op është një nga operatorët e paracaktuar: +, =, ==, e kështu me radhë. (Shih Kapitullin 15 për përkufizimin e saktë të sintaksës.) Këtu është deklarimi i operatorit të mbingarkuar të marrjes së indeksit:

Char & operator (int);

Ky operator ka një parametër të vetëm të tipit int dhe kthen një referencë char. Vetë një operator i mbingarkuar mund të mbingarkohet nëse listat e parametrave të instancave individuale ndryshojnë. Për klasën tonë String, ne do të krijojmë dy operatorë të ndryshëm caktimi dhe barazie secili.
Për të thirrur një funksion anëtari, përdorni operatorët e aksesit të anëtarëve me pikë (.) ose shigjetë (->). Supozoni se kemi deklarata të objekteve të tipit String:

Objekti i vargut ("Danny");
String * ptr = String i ri ("Anna");
varg vargjesh;
Kështu duket thirrja e funksionit të madhësisë () për këto objekte:
vektoriale madhësi (3);

// aksesi i anëtarëve për objektet (.); // objektet janë 5 madhësi [0] = objekti.madhësia (); // aksesi i anëtarëve për treguesit (->)
// ptr është 4
madhësitë [1] = ptr-> madhësia (); // qasja e anëtarëve (.)
// grupi ka madhësinë 0
madhësitë [2] = grupi.madhësia ();

Ai kthen respektivisht 5, 4 dhe 0.
Operatorët e mbingarkuar aplikohen në një objekt në të njëjtën mënyrë si operatorët e zakonshëm:

Emri i vargut ("Yadie"); Emri i vargut 2 ("Yodie"); // operator bool == (konst String &)
nëse (emri == emri 2)
kthimi;
tjetër
// String & operator = (konst String &)
emri = emri2;

Një deklaratë e funksionit të anëtarit duhet të jetë brenda përkufizimit të një klase, dhe një përkufizim funksioni mund të qëndrojë si brenda përkufizimit të një klase ashtu edhe jashtë saj. (Të dyja madhësia () dhe c_str () përcaktohen brenda një klase.) Nëse një funksion përcaktohet jashtë një klase, atëherë duhet të specifikojmë, ndër të tjera, se cilës klasë i përket. Në këtë rast, përkufizimi i funksionit vendoset në skedarin burimor, për shembull, String.C, dhe përkufizimi i vetë klasës vendoset në skedarin e kokës (String.h në shembullin tonë), i cili duhet të përfshihet në burimi:

// përmbajtja e skedarit burimor: String.C // përfshijnë përkufizimin e klasës String
#include "String.h" // përfshijnë përkufizimin e funksionit strcmp ().
#përfshi
bool // kthim tip
String :: // klasë të cilës i përket funksioni
operatori == // emri i funksionit: operatori i barazisë
(konst String & rhs) // lista e parametrave
{
nëse (_size! = rhs._size)
kthej false;
ktheje strcmp (_strinq, rhs._string)?
e gabuar e sakte;
}

Kujtoni që strcmp () është një funksion standard i bibliotekës C. Ai krahason dy vargje të një lloji të integruar, duke kthyer 0 nëse vargjet janë të barabarta dhe jo zero nëse nuk janë të barabartë. Operatori i kushtëzuar (? :) teston vlerën para pikëpyetjes. Nëse është e vërtetë, vlera e shprehjes në të majtë të dy pikave kthehet, përndryshe, shprehja në të djathtë. Në shembullin tonë, vlera e shprehjes është false nëse strcmp () kthen një vlerë jozero, dhe e vërtetë nëse kthen zero. (Operatori i kushtëzuar diskutohet në seksionin 4.7.)
Operatori i krahasimit përdoret mjaft shpesh, funksioni që e zbaton doli të jetë i vogël, kështu që është e dobishme ta deklaroni këtë funksion në linjë. Përpiluesi zëvendëson tekstin e funksionit në vend që ta thërrasë, kështu që nuk harxhohet kohë për një thirrje të tillë. (Funksionet inline diskutohen në seksionin 7.6.) Një funksion anëtar i përcaktuar brenda një klase është inline si parazgjedhje. Nëse është përcaktuar jashtë klasës, për ta deklaruar atë inline, duhet të përdorni fjalën kyçe inline:

Inline bool String :: operator == (const String & rhs) (// njëjtë)

Përkufizimi i funksionit inline duhet të jetë në skedarin e kokës që përmban përkufizimin e klasës. Duke ripërcaktuar operatorin == si të integruar, duhet ta zhvendosim vetë tekstin e funksionit nga skedari String.C në skedarin String.h.
Më poshtë është zbatimi i operacionit të krahasimit midis një objekti String dhe një vargu të integruar:

Inline bool String :: operator == (const char * s) (ktheje strcmp (_string, s)? E gabuar: e vërtetë;)

Emri i konstruktorit është i njëjtë me emrin e klasës. Nuk konsiderohet të kthejë një vlerë, kështu që nuk keni nevojë të vendosni vlerën e kthimit as në përkufizimin e saj, as në trupin e saj. Mund të ketë disa konstruktorë. Si çdo funksion tjetër, ato mund të deklarohen në linjë.

#përfshi // String inline i konstruktorit të paracaktuar :: String ()
{
_madhësia = 0;
_string = 0;
) String inline :: String (const char * str) (if (! str) (_size = 0; _string = 0;) other (_size = str1en (str); strcpy (_string, str);) // konstruktori i kopjes
Vargu në linjë :: String (konst String & rhs)
{
madhësia = rhs._size;
nëse (! rhs._string)
_string = 0;
tjetër (
_string = karakter i ri [_madhësia + 1];
} }

Meqenëse ne kemi ndarë memorie në mënyrë dinamike duke përdorur operatorin e ri, duhet ta çlirojmë atë duke thirrur delete kur nuk kemi më nevojë për objektin String. Një tjetër funksion i veçantë i anëtarit i shërben këtij qëllimi - një destruktor, i cili thirret automatikisht në një objekt në momentin që ai objekt pushon së ekzistuari. (Shih Kapitullin 7 për jetëgjatësinë e një objekti.) Emri i destruktorit formohet nga karakteri tilde (~) dhe emri i klasës. Këtu është përkufizimi i destruktorit të klasës String. Këtu e quajmë operacionin e fshirjes për të liruar memorien e alokuar në konstruktor:

Vargu në linjë:: ~ String () (fshi _string;)

Të dy operatorët e caktimit të mbingarkuar përdorin fjalën kyçe speciale this.
Kur shkruajmë:

Emri i vargut ("orville"), emri2 ("vilbur");
emri = "Orville Wright";
ky është një tregues për objektin name1 brenda trupit të funksionit të caktimit.
kjo gjithmonë tregon për objektin e klasës përmes të cilit thirret funksioni. Nëse
ptr-> madhësia ();
obj [1024];

Pastaj madhësia e brendshme () kjo vlerë do të jetë adresa e ruajtur në ptr. Brenda operacionit të marrjes së indeksit, kjo përmban adresën e obj. Duke e çreferencuar këtë (duke përdorur * këtë), marrim vetë objektin. (Ky tregues është i detajuar në seksionin 13.4.)

Vargu dhe vargu në linjë :: operator = (konst char * s) (nëse (! S) (_size = 0; fshi _string; _string = 0;) tjetër (_size = str1en (s); fshi _string; _string = char [ _size + 1]; strcpy (_string, s);) kthe * këtë;)

Gjatë zbatimit të operacioneve të caktimit, një gabim bëhet mjaft shpesh: ata harrojnë të kontrollojnë nëse objekti që kopjohet nuk është i njëjti objekt në të cilin po kopjohet. Ne do ta kryejmë këtë kontroll duke përdorur të njëjtin tregues:

String & String Inline :: operator = (const String & rhs) (// në shprehjen // namel = * pointer_to_string // this is name1, // rhs - * pointer_to_string. Nëse (kjo! = & Rhs) (

Këtu është teksti i plotë i operacionit për caktimin e një objekti të të njëjtit lloj në një objekt String:

String & String Inline :: operator = (const String & rhs) (if (this! = & Rhs) (fshi _string; _size = rhs._size; if (! Rhs._string)
_string = 0;
tjetër (
_string = karakter i ri [_madhësia + 1];
strcpy (_string, rhs._string);
}
}
ktheje * këtë;
}

Operacioni i marrjes së një indeksi është pothuajse i njëjtë me zbatimin e tij për Array, të cilin e krijuam në seksionin 2.3:

#përfshi karaktere inline &
String :: operator (int elem)
{
pohoj (elem> = 0 && elem< _size);
kthej _string [elem];
}

Operatorët hyrës dhe dalës zbatohen si funksione të veçanta, jo anëtarë të klasës. (Ne do të diskutojmë pse në seksionin 15.2. Seksionet 20.4 dhe 20.5 flasin për mbingarkimin e deklaratave hyrëse dhe dalëse të bibliotekës iostream.) Deklarata jonë e hyrjes mund të lexojë jo më shumë se 4095 karaktere. setw () është një manipulues i paracaktuar, ai lexon një numër të caktuar karakteresh minus 1 nga rryma hyrëse, duke siguruar kështu që ne të mos e tejmbushim buferin tonë të brendshëm nëBuf. (Kapitulli 20 mbulon në detaje manipuluesin setw ().) Për të përdorur manipuluesit, duhet të përfshini skedarin e duhur të kokës:

#përfshi inline istream & operator >> (istream & io, String & s) (// kufizim artificial: 4096 karaktere const int 1imit_string_size = 4096; char inBuf [limit_string_size]; // setw () është përfshirë në bibliotekën iostream // kufizon madhësia e bllokut të lexuar në 1imit_string_size -l io >> setw (1imit_string_size) >> inBuf; s = mBuf; // String :: operator = (const char *); return io;)

Operatori i daljes ka nevojë për qasje në paraqitjen e brendshme të vargut. Që nga operatori<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода:

Ostream & operator inline<<(ostream& os, const String &s) { return os << s.c_str(); }

Më poshtë është një shembull i programit duke përdorur klasën String. Ky program merr fjalë nga rryma hyrëse dhe numëron totalin e tyre, si dhe numrin e fjalëve "the" dhe "it" dhe regjistron zanoret e hasura.

#përfshi #include "String.h" int main () (int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, theCnt = 0, itCnt = 0, wdCnt = 0, joVowel = 0; / / Fjalët "The" dhe "Ajo"
// kontrolloni me operatorin == (konst char *)
String but, the ("the"), it ("it"); // operatori >> (ostream &, String &)
ndërsa (cin >> buf) (
++ wdCnt; // operator<<(ostream&, const String&)
cout<< buf << " "; if (wdCnt % 12 == 0)
cout<< endl; // String::operator==(const String&) and
// String :: operator == (const char *);
nëse (buf == the | | buf == "The")
++ theCnt;
tjetër
nëse (buf == ajo || buf == "Ajo")
++ itCnt; // thërret vargun :: s-ize ()
për (int ix = 0; ix< buf.sizeO; ++ix)
{
// thirret String :: operator (int)
ndërprerës (buf [ix])
{
rasti "a": rasti "A": ++ aCnt; pushim;
rasti "e": rasti "E": ++ eCnt; pushim;
rasti "i": rasti "I": ++ iCnt; pushim;
rasti "o": rasti "0": ++ oCnt; pushim;
rasti "u": rasti "U": ++ uCnt; pushim;
default: ++ notVowe1; pushim;
}
}
) // operator<<(ostream&, const String&)
cout<< "\n\n"
<< "Слов: " << wdCnt << "\n\n"
<< "the/The: " << theCnt << "\n"
<< "it/It: " << itCnt << "\n\n"
<< "согласных: " < << "a: " << aCnt << "\n"
<< "e: " << eCnt << "\n"
<< "i: " << ICnt << "\n"
<< "o: " << oCnt << "\n"
<< "u: " << uCnt << endl;
}

Le ta testojmë programin duke i ofruar një paragraf nga një tregim për fëmijë i shkruar nga një prej autorëve të këtij libri (do ta shohim përsëri këtë histori në kapitullin 6). Këtu është rezultati i programit:

Alice Emma ka flokë të gjatë të kuq. Babi i saj thotë se kur era i fryn flokët, duket pothuajse e gjallë, si një zog i zjarrtë në fluturim. Një zog i bukur i zjarrtë, i thotë ai, magjik por i pazbutur. “Babi, shush, nuk ka një gjë të tillë”, i thotë ajo, njëkohësisht duke dashur që ai t’i tregojë më shumë. E turpshme, ajo pyet: "Dmth, babi, a është atje?" Fjalët: 65
/ Të: 2
ajo / Ajo: 1
bashkëtingëlloret: 190
një: 22
e: 30
unë: 24
rreth: 10
u: 7

Ushtrimi 3.26

Ka shumë përsëritje në implementimet tona të konstruktorëve dhe detyrave. Përpiquni ta zhvendosni kodin e kopjuar në një funksion të veçantë të anëtarit privat, siç bëtë në seksionin 2.3. Sigurohuni që opsioni i ri të jetë funksional.

Ushtrimi 3.27

Ndryshoni programin e testimit për të numëruar edhe bashkëtingëlloret b, d, f, s, t.

Ushtrimi 3.28

Shkruani një funksion anëtar që numëron numrin e shfaqjeve të një karakteri në një varg duke përdorur deklaratën e mëposhtme:

Klasa String (publike: // ... int count (char ch) const; // ...);

Ushtrimi 3.29

Zbatoni operatorin e lidhjes së vargut (+) në mënyrë që të bashkojë dy vargje dhe të kthejë rezultatin në një objekt të ri String. Këtu është deklarata e funksionit:

Klasa String (publik: // ... Operatori i vargut + (const String & rhs) const; // ...);

Artikujt kryesorë të lidhur