Kako podesiti pametne telefone i računare. Informativni portal

Tipovi podataka i varijable. Tipovi podataka i operacije u jeziku C

U ovoj lekciji ćete naučiti Abeceda jezika C++, kao i šta tipovi podataka može upravljati programom na njemu. Ovo možda nije najuzbudljiviji trenutak, ali ovo znanje je neophodno! Osim toga, počevši učiti bilo koji drugi programski jezik, samopouzdanije ćete proći kroz sličnu fazu učenja. C++ program može sadržavati sljedeće znakove:

  • velika, mala latinična slova A, B, C…, x, y, z i donja crta;
  • arapski brojevi od 0 do 9;
  • specijalni znakovi: ( ) , | , () + - / % * . \' : ?< > = ! & # ~ ; ^
  • razmak, tabulator i novi red znakova.

U testu programa možete koristiti komentari. Ako tekst sa dva znaka "kosa crta" // i završava znakom za novi red ili je zatvoren između znakova /* i */, onda ga kompajler ignoriše.

Podaci u C++

Da bi se riješio problem u bilo kojem programu, neki podaci se obrađuju. Mogu biti različitih tipova: cijeli brojevi i realni brojevi, simboli, nizovi, nizovi. Podaci u C++ obično se opisuju na početku funkcije. TO osnovni tipovi podataka jezici uključuju:

Za formiranje drugih vrsta podataka, osnovnih i tzv specifikacije. C++ definira četiri specificatora tipa podataka:

  • kratko - kratko;
  • dugo - dugo;
  • potpisan - potpisan;
  • nepotpisan - nepotpisan.

cjelobrojni tip

varijabla tipa int u memoriji računara može zauzeti 2 ili 4 bajta. Zavisi od brzine procesora. Prema zadanim postavkama, svi cjelobrojni tipovi se smatraju potpisanim, odnosno specifikacijama potpisan ne može biti specificirano. specificer nepotpisan dozvoljava samo pozitivne brojeve da budu predstavljeni. Ispod su neki rasponi vrijednosti cjelobrojnog tipa

Tip Domet Veličina
int -2147483648…2147483647 4 bajta
unsigned int 0…4294967295 4 bajta
potpisan int -2147483648…2147483647 4 bajta
kratki int -32768…32767 2 bajta
long int -2147483648…2147483647 4 bajta
unsigned short int 0…65535 2 bajta

Pravi tip

Broj s pokretnim zarezom je predstavljen u obliku mE +- p, gdje je m mantisa (cijeli ili razlomak sa decimalnim zarezom), p je eksponent (cijeli broj). Obično vrijednosti poput float uzeti 4 bajta i duplo 8 bajtova. Tabela raspona stvarnih vrijednosti:

float 3.4E-38…3.4E+38 4 bajta
duplo 1.7E-308…1.7E+308 8 bajtova
dugačak dupli 3.4E-4932…3.4E+4932 8 bajtova

boolean tip

varijabla tipa bool može uzeti samo dvije vrijednosti tačno( tačno ) ili fasle ( Netačno ). Bilo koja vrijednost različita od nule se tumači kao tačno. Značenje false predstavljeno u memoriji kao 0.

tip praznine

Skup vrijednosti ovog tipa je prazan. Koristi se za definiranje funkcija koje ne vraćaju vrijednost, za specificiranje prazne liste argumenata funkcije, kao osnovnog tipa za pokazivače i u operacijama cast.

Konverzija tipa podataka

C++ razlikuje dva tipa konverzije tipa podataka: eksplicitnu i implicitnu.

  • Implicitna konverzija se dešava automatski. To se radi tokom poređenja, dodjeljivanja ili evaluacije izraza različitih tipova. Na primjer, sljedeći program će ispisati na konzolu vrijednost poput float.

#include "stdafx.h" #include korištenje imenskog prostora std; int main() ( int i=5; float f=10,12; cout<>void"); vrati 0; )

#include "stdafx.h"

#include

korištenje imenskog prostora std ;

int main()

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

cout<< i / f ;

system("pause>>void" );

return 0 ;

Tip sa najmanjim gubitkom informacija dobija najveći prioritet. Ne biste trebali zloupotrebljavati implicitnu konverziju tipa, jer se mogu pojaviti sve vrste nepredviđenih situacija.

  • Eksplicitna konverzija za razliku od implicitnog, izvodi programer. Postoji nekoliko načina da izvršite ovu konverziju:
  1. Pretvaranje u stilove C: (plutati) a
  2. Pretvaranje u stilove C++: float()

Izlijevanje tipa se također može izvršiti pomoću sljedećih operacija:

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

static_cast<> ()

const_cast<> ()

reinterpret_cast<> ()

dynamic_cast<> ()

static_cas- pretvara povezane tipove podataka. Ovaj operator izbacuje tipove prema uobičajenim pravilima, što može biti neophodno kada kompajler ne izvrši automatsku konverziju. Sintaksa će izgledati ovako:

static_cast type<Тип>(objekat);

Koristeći static_cast, ne možete ukloniti konstantnost iz varijable, ali to je unutar moći sljedeće izjave. const_cast- koristi se samo kada trebate ukloniti konstantnost iz objekta. Sintaksa će izgledati ovako:

Tipconst_cast< Tip> (objekat);

reinterpret_cast- koristi se za pretvaranje različitih tipova, cijeli broj u pokazivač i obrnuto. Ako vidite novu riječ "pokazivač" - nemojte se uplašiti! ovo je također tip podataka, ali nećemo uskoro raditi s njim. Ovdje je sintaksa ista kao i za prethodno razmatrane operatore:

Tipreinterpretirati_cast< Tip> (objekat);

dynamic_cast- koristi se za dinamičku konverziju tipova, implementira kasting pokazivača ili reference. sintaksa:

Tipdinamičan _cast< Tip> (objekat);

Kontrolni znakovi

Već ste upoznati sa nekim od ovih „kontrolnih znakova“ (na primjer, sa \n). Svi počinju sa obrnutom kosom crtom i također su okruženi dvostrukim navodnicima.

Slika

Hex kod

Ime

Zvuk bipera

Korak nazad

Prijevod stranice (format)

Prevođenje linija

Povrat kočije

Horizontalna kartica

Vertikalna kartica

Važna razlika između jezika C i drugih jezika (PL1, FORTRAN, itd.) je odsustvo zadanog principa, što dovodi do potrebe da se sve varijable koje se koriste u programu eksplicitno deklarišu zajedno sa naznakom njihovih odgovarajućih tipova .

Deklaracija varijable ima sljedeći format:

[specifier-klase-pohrane] deskriptor-specifikator tipa [= pokretač] [, deklarator [= pokretač] ]...

Deskriptor je jednostavan identifikator varijable ili složenija konstrukcija sa uglastim zagradama, zagradama ili zvjezdicom (skup zvjezdica).

Specifikator tipa je jedna ili više ključnih riječi koje definiraju tip varijable koja se deklarira. Jezik C ima standardni skup tipova podataka koji se mogu koristiti za konstruisanje novih (jedinstvenih) tipova podataka.

Inicijator - specificira početnu vrijednost ili listu početnih vrijednosti koje (koje) se dodjeljuju varijabli kada je deklarirana.

Specifikator memorijske klase je definiran jednom od četiri ključne riječi SI jezika: auto, extern, register, static i pokazuje kako će memorija biti alocirana za deklariranu varijablu, s jedne strane, a s druge, opseg ove varijable, odnosno iz kojih dijelova programa joj možete pristupiti.

1.2.1 Kategorije tipova podataka

Ključne riječi za definiranje osnovnih tipova podataka

Tipovi cjelobrojnih: Plutajući tipovi: char float int duplo kratko dugo dvostruko dugo bez predznaka

Varijabla bilo kojeg tipa može biti deklarirana kao nepromjenjiva. Ovo se postiže dodavanjem ključne riječi const u specificator tipa. Objekti tipa const predstavljaju podatke samo za čitanje, tj. ovoj varijabli se ne može dodijeliti nova vrijednost. Imajte na umu da ako ne postoji specificator tipa iza riječi const, onda se podrazumijeva specificator tipa int. Ako ključna riječ const dolazi ispred deklaracije kompozitnih tipova (niz, struktura, mješavina, nabrajanje), onda to dovodi do činjenice da svaki element mora biti i nepromjenjiv, tj. može mu se dodijeliti vrijednost samo jednom.

Const dupli A=2.128E-2; const B=286; (pretpostavljajući const int B=286)

Primjeri deklariranja složenih podataka bit će razmotreni u nastavku.

1.2.2. Cjelobrojni tip podataka

Za definiranje podataka cjelobrojnog tipa koriste se različite ključne riječi koje određuju raspon vrijednosti i veličinu memorijskog područja dodijeljenog varijablama (tablica 6).

Tabela 6

Imajte na umu da su potpisane i nepotpisane ključne riječi opcione. Oni određuju kako se tumači nulti bit deklarirane varijable, tj. ako je specificirana neoznačena ključna riječ, onda se nulti bit tumači kao dio broja, u suprotnom se nulti bit tumači kao znak. U nedostatku ključne riječi unsigned, integer varijabla se smatra potpisanom. Ako se specifikacija tipa sastoji od potpisanog ili nepotpisanog tipa ključa praćenog identifikatorom varijable, tada će se tretirati kao varijabla tipa int. Na primjer:

unsigned intn; unsigned int b; int c; (pod pretpostavkom da je potpisan int c); unsigned d; (pretpostavljajući unsigned int d); potpisan f; (pod pretpostavkom da je potpisan int f).

Imajte na umu da se modifikator tipa char koristi za predstavljanje karaktera (iz prikaza niza znakova) ili za deklarisanje literala niza. Vrijednost char objekta je kod (veličine 1 bajt) koji odgovara predstavljenom znaku. Za predstavljanje znakova ruskog alfabeta, modifikator tipa identifikatora podataka je unsigned char, budući da kodovi ruskih slova prelaze vrijednost 127.

Treba imati na umu sljedeću napomenu: jezik C ne definira memorijski prikaz i raspon vrijednosti za identifikatore sa modifikatorima tipa int i unsigned int. Veličina memorije za modifikator tipa int s predznakom određena je dužinom strojne riječi, koja ima različitu veličinu na različitim strojevima. Dakle, na 16-bitnim mašinama veličina riječi je 2 bajta, na 32-bitnim mašinama, respektivno, 4 bajta, tj. tip int je ekvivalentan tipovima short int ili long int u zavisnosti od arhitekture korišćenog računara. Dakle, isti program može raditi ispravno na jednom računaru, a neispravno na drugom. Da biste odredili dužinu memorije koju zauzima varijabla, možete koristiti operaciju sizeof jezika C, koja vraća vrijednost dužine specificiranog modifikatora tipa.

Na primjer:

A = sizeof(int); b = sizeof(long int); c = sizeof(unsigned long); d = sizeof(short);

Imajte na umu da oktalne i heksadecimalne konstante također mogu imati modifikator bez predznaka. To se postiže navođenjem prefiksa u ili U nakon konstante, a konstanta bez ovog prefiksa se smatra potpisanom.

Na primjer:

0xA8C (int signed); 01786l (dugo potpisan); 0xF7u (int unsigned);

1.2.3. float podaci

Za varijable koje predstavljaju broj s pomičnim zarezom, koriste se sljedeći modifikatori tipa: float, double, long double (nema SI u nekim implementacijama long double jezika).

Vrijednost sa modifikatorom tipa float zauzima 4 bajta. Od toga, 1 bajt je dodijeljen za predznak, 8 bita za eksponent viška i 23 bita za mantisu. Imajte na umu da je najznačajniji bit mantise uvijek 1, tako da nije dopunjen, tako da je raspon varijable s pomičnim zarezom otprilike 3,14E-38 do 3,14E+38.

Dvostruka vrijednost zauzima 8 bita u memoriji. Njegov je format sličan float formatu. Memorijski bitovi se dodjeljuju na sljedeći način: 1 bit za predznak, 11 bita za eksponent i 52 bita za mantisu. S obzirom na izostavljeni visoki dio mantise, raspon vrijednosti je od 1,7E-308 do 1,7E+308.

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

1.2.4. Pointers

Pointer je adresa memorije koja je dodijeljena za postavljanje identifikatora (identifikator može biti ime varijable, niza, strukture, string literala). Ako je varijabla deklarirana kao pokazivač, tada sadrži memorijsku adresu na kojoj se može pronaći skalarna vrijednost bilo kojeg tipa. Prilikom deklarisanja varijable tipa pokazivača, potrebno je definisati tip objekta podataka čiju će adresu varijabla sadržavati, te naziv pokazivača kojem prethodi zvjezdica (ili grupa zvjezdica). Format deklaracije pokazivača:

specificator tipa [ modifikator ] * deskriptor.

Specifikator tipa specificira tip objekta i može biti bilo kog osnovnog tipa, tipa strukture, mješavine (o tome će biti riječi u nastavku). Navođenjem void umjesto specificatora tipa, možete nekako odgoditi specifikaciju tipa na koji upućuje pokazivač. Varijabla deklarirana kao pokazivač na tip void može se koristiti za upućivanje na objekt bilo kojeg tipa. Međutim, da bi se mogle izvoditi aritmetičke i logičke operacije nad pokazivačima ili objektima na koje ukazuju, potrebno je eksplicitno definirati tip objekata prilikom izvođenja svake operacije. Takva određivanja tipa mogu se izvesti korištenjem operacije cast.

Ključne riječi const, near, far, huge mogu djelovati kao modifikatori prilikom deklariranja pokazivača. Ključna riječ const označava da se pokazivač ne može promijeniti u programu. Veličina varijable deklarirane kao pokazivač ovisi o arhitekturi računala i o korištenom memorijskom modelu za koji će se program kompajlirati. Pokazivači na različite tipove podataka ne moraju biti iste dužine.

Blizu, daleku, veliku ključne riječi mogu se koristiti za promjenu veličine pokazivača.

unsigned int * a; /* varijabla a je pokazivač za tip unsigned int (neoznačeni cijeli brojevi) */ double * x; /* varijabla x ukazuje na tip podataka s pomičnim zarezom dvostruke preciznosti */ char * fuffer ; /* deklarisati pokazivač pod nazivom fuffer koji ukazuje na varijablu tipa char */ double numberer; void *adrese; adresa = &broj; (dvostruko *) adresa ++; /* Varijabla adresa je deklarirana kao pokazivač na objekt bilo kojeg tipa. Stoga mu se može dodijeliti adresa bilo kojeg objekta (& je operacija izračunavanja adrese). Međutim, kao što je gore navedeno, nijedna aritmetička operacija se ne može izvesti na pokazivaču sve dok tip podataka na koji ukazuje nije eksplicitno definiran. Ovo se može učiniti korištenjem operacije cast (double *) za pretvaranje adrese u pokazivač u dvostruku i zatim povećanje adrese. */ const *dr; /* Varijabla dr je deklarirana kao pokazivač na konstantni izraz, tj. vrijednost pokazivača se može promijeniti tokom izvršavanja programa, ali vrijednost na koju pokazuje ne. */ unsigned char * const w = &obj. /* Varijabla w je deklarirana kao konstantni pokazivač na char neoznačene podatke. To znači da će w pokazivati ​​na isto područje memorije kroz cijeli program. Sadržaj ove oblasti je podložan promjenama. */

1.2.5. Nabrojane varijable

Varijabla koja može preuzeti vrijednost s neke liste vrijednosti naziva se varijabla nabrojanog tipa ili nabrajanje.

Deklaracija nabrajanja počinje ključnom riječi enum i ima dva formata predstavljanja.

Format 1. enum [enum-tag-name] (enum-list) deskriptor[, deskriptor...];

Format 2. enum enum-tag-name deskriptor [, deskriptor..];

Enum deklaracija specificira tip enum varijable i definira listu imenovanih konstanti koja se zove enum-list. Vrijednost svakog imena liste je cijeli broj.

Varijabla tipa enum može uzeti vrijednosti jedne od imenovanih konstanti liste. Konstante imenovane liste su tipa int. Dakle, memorija koja odgovara varijabli nabrajanja je memorija potrebna za pohranjivanje int vrijednosti.

Varijabla tipa enum može se koristiti u indeksnim izrazima i kao operandi u aritmetičkim i relacijskim operacijama.

U prvom formatu 1, imena i vrijednosti enuma su specificirani u listi enuma. Opcioni enum-tag-name je identifikator koji imenuje enum tag definisan enum-listom. Deskriptor imenuje varijablu nabrajanja. Više od jedne varijable tipa enum može biti definirano u deklaraciji.

Enum-lista sadrži jednu ili više konstrukcija oblika:

identifikator [= konstantni izraz]

Svaki identifikator imenuje enum element. Svi identifikatori u enum listi moraju biti jedinstveni. U nedostatku konstantnog izraza, vrijednost 0 odgovara prvom identifikatoru, vrijednost 1 odgovara sljedećem identifikatoru i tako dalje. Ime konstante nabrajanja je ekvivalentno njenoj vrijednosti.

Identifikator povezan s konstantnim izrazom poprima vrijednost specificiranu tim konstantnim izrazom. Konstantni izraz mora biti tipa int i može biti pozitivan ili negativan. Sljedećem identifikatoru na listi dodjeljuje se vrijednost jednaka konstantnom izrazu plus 1 ako taj identifikator nema svoj vlastiti konstantni izraz. Upotreba elemenata nabrajanja mora se pridržavati sljedećih pravila:

1. Varijabla može sadržavati duple vrijednosti.

2. Identifikatori u popisnoj listi moraju se razlikovati od svih ostalih identifikatora u istom opsegu, uključujući obična imena varijabli i identifikatore iz drugih popisnih lista.

3. Imena enum tipova moraju se razlikovati od drugih imena enum tipova, struktura i mješavina u istom opsegu.

4. Vrijednost može pratiti posljednji element popisne liste.

Enum sedmica ( SUB = 0, /* 0 */ VOS = 0, /* 0 */ POND, /* 1 */ VTOR, /* 2 */ SRED, /* 3 */ HETV, /* 4 */ PJAT /* 5 */ ) slave_ned ;

U ovom primjeru, deklarirana je nabrojana oznaka sedmica, s odgovarajućim skupom vrijednosti, a deklarirana je varijabla rab_ned, koja ima tip sedmice.

Drugi format koristi ime enum oznake za upućivanje na tip enuma definiran na drugom mjestu. Ime enum oznake mora se odnositi na već definiranu enum oznaku unutar trenutnog opsega. Pošto je enum tag deklarisan negdje drugdje, lista enum nije prisutna u deklaraciji.

U deklariranju pokazivača na enum tip podataka iu typedef deklaracijama za enum tipove, možete koristiti ime enum oznake prije nego što se definira enum oznaka. Međutim, definicija nabrajanja mora prethoditi bilo kojoj akciji korištenog pokazivača na tip deklaracije typedef. Deklaracija bez naknadne liste deskriptora opisuje oznaku ili, ako mogu tako reći, šablon za nabrajanje.

1.2.6. Nizovi

Nizovi su grupa elemenata istog tipa (double, float, int, itd.). Iz deklaracije niza, prevodilac mora dobiti informacije o tipu elemenata niza i njihovom broju. Deklaracija niza ima dva formata:

deskriptor specificatora tipa [const - izraz];

deskriptor specificatora tipa;

Deskriptor je identifikator niza.

Specifikator tipa specificira tip elemenata deklariranog niza. Elementi niza ne mogu biti funkcije ili void elementi.

Konstantni izraz u uglastim zagradama specificira broj elemenata u nizu. Konstantni izraz može biti izostavljen iz deklaracije niza u sljedećim slučajevima:

Kada je deklarisan, niz se inicijalizira,

Niz je deklarisan kao parametar formalne funkcije,

C definira samo jednodimenzionalne nizove, ali pošto element niza može biti niz, možete definirati i višedimenzionalne nizove. Oni su formalizovani kao lista konstantnih izraza koji prate identifikator niza, pri čemu je svaki konstantni izraz zatvoren u sopstvene uglaste zagrade.

Svaki konstantni izraz u uglastim zagradama definira broj elemenata u datoj dimenziji niza, tako da deklaracija dvodimenzionalnog niza sadrži dva konstantna izraza, trodimenzionalna deklaracija niza sadrži tri, i tako dalje. Imajte na umu da u jeziku C prvi element niza ima indeks 0.

int a; /* predstavljen kao matrica a a a a a a */ double b; /* vektor od 10 dvostrukih elemenata */ int w = ( ( 2, 3, 4 ), ( 3, 4, 8 ), ( 1, 0, 9 ) );

U posljednjem primjeru, deklarisan je niz w. Liste zatvorene u vitičaste zagrade odgovaraju nizovima niza; ako nema zagrada, inicijalizacija će biti izvršena pogrešno.

Odjeljci niza mogu se koristiti u C, kao iu drugim jezicima visokog nivoa (PL1, itd.), ali postoje brojna ograničenja u korištenju sekcija. Sekcije se formiraju izostavljanjem jednog ili više parova uglastih zagrada. Parovi uglastih zagrada mogu se ispuštati samo s desna na lijevo i to striktno uzastopno. Sekcije nizova se koriste u organizaciji računskog procesa u funkcijama SI jezika koje je razvio korisnik.

Ako upišete s kada pozivate funkciju, tada će biti proslijeđen nulti niz niza s.

Kada se pozivate na niz b, možete napisati, na primjer, b, i vektor od četiri elementa će biti proslijeđen, a upućivanje na b će dati dvodimenzionalni niz veličine 3 puta 4. Ne možete napisati b, što implicira da bit će proslijeđen vektor, jer to nije u skladu sa ograničenjem nametnutim sekcijama niza korištenja.

Primjer deklariranja niza znakova.

char str = "deklaracija niza znakova";

Imajte na umu da postoji još jedan element u literalu znakova, budući da je posljednji element "\0" izlazni niz.

1.2.7. strukture

Strukture su složeni objekt koji uključuje elemente bilo kojeg tipa, s izuzetkom funkcija. Za razliku od niza, koji je homogeni objekat, struktura može biti heterogena. Tip strukture utvrđuje se zapisom obrasca:

struktura ( lista definicija )

Struktura mora sadržavati najmanje jednu komponentu. Definicija strukture je sljedeća:

deskriptor tipa podataka;

gdje tip podataka specificira tip strukture za objekte definirane u deskriptorima. U svom najjednostavnijem obliku, deskriptori su identifikatori ili nizovi.

Struktura (dvostruki x,y; ) s1, s2, sm; struktura (int godina; char mol, dan; ) datum1, datum2;

Varijable s1, s2 su definirane kao strukture, od kojih se svaka sastoji od dvije komponente x i y. Varijabla sm je definirana kao niz od devet struktura. Svaka od dvije varijable date1, date2 sastoji se od tri komponente godina, mjesec, dan. >p>Postoji još jedan način povezivanja imena sa tipom strukture, baziran je na korištenju oznake strukture. Oznaka strukture je slična oznaci nabrojanog tipa. Oznaka strukture je definirana na sljedeći način:

struct tag ( lista opisa; );

gdje je oznaka identifikator.

U primjeru ispod, identifikator student je opisan kao struct tag:

Strukturirajte učenik (char name; int id, age; char prp; );

Oznaka strukture se koristi za naknadno deklarisanje struktura ove vrste u obliku:

struct tag id-list;

struct student st1,st2;

Upotreba oznaka strukture je neophodna za opisivanje rekurzivnih struktura. U nastavku je opisana upotreba oznaka rekurzivne strukture.

Čvor strukture ( int podaci; struct čvor * sljedeći; ) st1_node;

Oznaka strukture čvora je zaista rekurzivna, jer se koristi u svom opisu, tj. u formalizaciji sljedećeg pokazivača. Strukture ne mogu biti direktno rekurzivne, tj. čvor strukture ne može sadržavati komponentu koja je čvor strukture, ali svaka struktura može imati komponentu koja je pokazivač na njen tip, kao što je učinjeno u gornjem primjeru.

Komponentama strukture se pristupa navođenjem imena strukture nakon čega slijedi naziv odabrane komponente odvojen točkom, na primjer:

St1.name="Ivanov"; st2.id=st1.id; st1_node.data=st1.age;

1.2.8. Asocijacije (mješavine)

Sindikat je sličan strukturi, međutim, samo jedan od elemenata unije se može koristiti (ili, drugim riječima, odgovoriti) u bilo kojem trenutku. Tip sindikata se može specificirati na sljedeći način:

Unija (opis elementa 1; ... opis elementa n; );

Glavna karakteristika unije je da se ista memorijska oblast dodjeljuje za svaki od deklariranih elemenata, tj. oni se preklapaju. Iako je pristup ovom području memorije moguć korištenjem bilo kojeg od elemenata, element za tu svrhu mora biti odabran tako da dobijeni rezultat ne bude besmislen.

Članovima sindikata se pristupa na isti način kao i strukturama. Oznaka unije se može formalizirati na potpuno isti način kao i oznaka strukture.

Udruženje se koristi u sljedeće svrhe:

Inicijalizacija korištenog memorijskog objekta ako je u bilo kojem trenutku aktivan samo jedan objekt od mnogih;

Interpretacije osnovnog prikaza objekta jednog tipa kao da je objektu dodijeljen drugi tip.

Pohrana koja odgovara varijabli tipa sindikata određena je količinom koja je potrebna za smještaj najdužeg člana sindikata. Kada se koristi element manje dužine, varijabla tipa unije može sadržavati neiskorištenu memoriju. Svi elementi ujedinjenja pohranjeni su u istom memorijskom području, počevši od iste adrese.

Union (char fio; char adres; int vozrast; int telefon; ) inform; unija ( int ax; char al; ) ua;

Kada koristite infor objekat tipa unije, možete obraditi samo element koji je primio vrijednost, tj. nakon dodjeljivanja vrijednosti elementu inform.fio, nema smisla upućivati ​​se na druge elemente. Ua konkatenacija vam omogućava da zasebno pristupite malim ua.al i visokim ua.al bajtovima dvobajtnog broja ua.ax.

1.2.9. Bit polja

Element strukture može biti polje bitova koje omogućava pristup pojedinačnim bitovima memorije. Bitova polja se ne mogu deklarisati izvan struktura. Također ne možete organizirati nizove bitnih polja i ne možete primijeniti operaciju traženja adrese na polja. U opštem slučaju, tip strukture sa bitnim poljem je dat u sledećem obliku:

Struktura (nepotpisani identifikator 1: dužina polja 1; nepotpisani identifikator 2: dužina polja 2; )

dužina - polje je specificirano cjelobrojnim izrazom ili konstantom. Ova konstanta specificira broj bitova dodijeljenih odgovarajućem polju. Polje nulte dužine označava poravnanje prema sljedećoj granici riječi.

Struktura ( unsigned a1: 1; unsigned a2: 2; unsigned a3: 5; unsigned a4: 2; ) prim;

Strukture bitfielda također mogu sadržavati potpisane komponente. Takve komponente se automatski postavljaju na odgovarajuće granice riječi, a neki dijelovi riječi ostaju neiskorišteni.

1.2.10. Varijable sa promjenjivom strukturom

Vrlo često neki programski objekti pripadaju istoj klasi, razlikuju se samo u nekim detaljima. Razmotrimo, na primjer, reprezentaciju geometrijskih oblika. Opće informacije o oblicima mogu uključivati ​​elemente kao što su površina, perimetar. Međutim, odgovarajuće informacije o geometrijskim dimenzijama mogu se razlikovati ovisno o njihovom obliku.

Razmotrimo primjer u kojem su informacije o geometrijskim oblicima predstavljene na temelju kombinirane upotrebe strukture i sjedinjenja.

Strukturna figura (dvostruka površina,perimetar; /* uobičajene komponente */ tip int; /* atribut komponente */ unija /* nabrajanje komponente */ (dvostruki radijus; /* krug */ duplo a; /* pravougaonik */ dupli b; /* trougao */ ) geom_fig; ) fig1, fig2 ;

Općenito, svaki objekat tipa figure sastojat će se od tri komponente: površine, perimetra, tipa. Komponenta tipa naziva se oznaka aktivne komponente jer se koristi za označavanje koja komponenta geom_fig unije je trenutno aktivna. Takva struktura se naziva promjenjiva struktura jer se njene komponente mijenjaju ovisno o vrijednosti oznake aktivne komponente (vrijednosti tipa).

Imajte na umu da bi umjesto komponente tipa tipa int bilo preporučljivo koristiti nabrojani tip. Na primjer, takav

Enum figure_chess ( CIRCLE, BOX, TROANGLE ) ;

Konstante CIRCLE, BOX, TROANGLE će dobiti vrijednosti jednake 0, 1, 2, respektivno. Tip varijable se može deklarirati kao da ima nabrojani tip:

enum tip figure_chess;

U ovom slučaju, C prevodilac će upozoriti programera na potencijalno pogrešne zadatke, kao što je

figura.tip = 40;

Generalno, varijabla strukture će se sastojati od tri dijela: skupa zajedničkih komponenti, oznaka aktivne komponente i dijelova sa promjenjivim komponentama. Opšti oblik strukturne varijable je sljedeći:

Struktura ( zajedničke komponente; oznaka aktivne komponente; unija ( deklaracija komponente 1 ; deklaracija komponente 2 ; ::: deklaracija komponente n ; ) identifikator unije; ) struct-identifier;

Primjer definiranja varijable strukture pod nazivom helth_record

Struktura ( /* opće informacije */ char ime ; /* ime */ int starost; /* starost */ char spol; /* spol */ /* oznaka aktivne komponente */ /* (bračni status) */ enum merital_status ins ; /* varijabilni dio */ unija ( /* neoženjen */ /* bez člana */ struct ( /* vjenčani */ char marripge_date; char supružnika; int no_children; ) brak_info; /* razveden */ char date_divorced; ) marital_info; ) zdravstveni_dosije; enum marital_status ( SINGLE, /* neoženjen */ MARRIGO, /* oženjen */ RAZVOD /* razveden */ ) ;

Komponentama strukture može se pristupiti putem linkova:

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

1.2.11. Definiranje objekata i tipova

Kao što je gore spomenuto, sve varijable koje se koriste u C programima moraju biti deklarirane. Tip varijable koja se deklarira ovisi o tome koja se ključna riječ koristi kao specifikacija tipa i da li je deklarator jednostavan identifikator ili kombinacija identifikatora sa pokazivačem (zvjezdica), nizom (uglatim zagradama) ili modifikatorom funkcije (zagrade). .

Prilikom deklarisanja jednostavne varijable, strukture, mješavine ili unije, ili nabrajanja, deskriptor je jednostavan identifikator. Da biste deklarirali pokazivač, niz ili funkciju, identifikator se mijenja u skladu s tim: sa zvjezdicom na lijevoj strani, kvadratom ili zagradama na desnoj strani.

Primjećujemo važnu osobinu jezika C, kada deklarirate, možete koristiti više od jednog modifikatora u isto vrijeme, što omogućava stvaranje mnogo različitih složenih deskriptora tipa.

Međutim, moramo imati na umu da neke kombinacije modifikatora nisu dozvoljene:

Elementi niza ne mogu biti funkcije,

Funkcije ne mogu vratiti nizove ili funkcije.

Prilikom inicijalizacije složenih deskriptora, kvadrat i zagrade (desno od identifikatora) imaju prednost nad zvjezdicom (lijevo od identifikatora). Kvadrat ili zagrade imaju isti prioritet i proširuju se s lijeva na desno. Specifikator tipa se razmatra u zadnjem koraku, kada je deskriptor već u potpunosti interpretiran. Možete koristiti zagrade za promjenu redoslijeda tumačenja po potrebi.

Za tumačenje složenih opisa predlaže se jednostavno pravilo koje zvuči kao "iznutra prema van", a sastoji se od četiri koraka.

1. Počnite od ID-a i pogledajte desno za uglaste zagrade ili zagrade.

2. Ako postoje, onda protumačite taj dio deskriptora i zatim pogledajte lijevo za zvjezdicu.

3. Ako u bilo kojoj fazi na desnoj strani postoji završna zagrada, tada morate prvo primijeniti sva ova pravila unutar zagrada, a zatim nastaviti s tumačenjem.

4. Interpretirajte specifikaciju tipa.

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

U ovom primjeru, varijabla comp (1) je deklarirana kao niz od deset (2) pokazivača (3) na funkcije (4) koje vraćaju pokazivače (5) na cjelobrojne vrijednosti (6).

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

Varijabla var (1) je deklarirana kao pokazivač (2) na funkciju (3) koja vraća pokazivač (4) na niz (5) od 10 elemenata, koji su pokazivači (6) na vrijednosti char.

Pored deklaracija varijabli različitih tipova, moguće je deklarirati i tipove. Ovo se može uraditi na dva načina. Prvi način je da navedete ime oznake kada deklarirate strukturu, uniju ili enum, a zatim koristite to ime u deklaracijama varijable i funkcije kao referencu na tu oznaku. Drugi je korištenje ključne riječi typedef za deklariranje tipa.

Kada je deklarisan sa ključnom reči typedef, identifikator na mestu objekta koji se opisuje je ime tipa podataka koji se razmatra, a ovaj tip se onda može koristiti za deklarisanje varijabli.

Imajte na umu da se bilo koji tip može deklarirati pomoću ključne riječi typedef, uključujući tipove pokazivača, funkcije ili niza. Ime sa ključnom riječi typedef za tipove pokazivača, strukture, unije može se deklarirati prije nego što se ovi tipovi definiraju, ali unutar opsega deklaratora.

typedef double(*MATH)(); /* MATH - ime novog tipa koje predstavlja pokazivač na funkciju koja vraća vrijednosti tipa double */ MATH cos; /* cos pokazivač na funkciju koja vraća vrijednosti tipa double */ /* Može se napraviti ekvivalentna deklaracija */ double (* cos)(); typedef char FIO /* FIO je niz od četrdeset znakova */ FIO osoba; /* Promenljiva person je niz od četrdeset karaktera */ /* Ovo je ekvivalentno deklaraciji */ char person;

Prilikom deklarisanja varijabli i tipova, ovdje su korištena imena tipova (MATH FIO). Pored toga, imena tipova se mogu koristiti u tri druga slučaja: u listi formalnih parametara, u deklaraciji funkcija, u operacijama konverzije tipa i u operaciji sizeof (operacija konverzije tipa).

Imena tipova za osnovne, nabrajanje, strukturu i tipove mješavine su specifikatori tipa za te tipove. Imena tipova za niz i tipove pokazivača funkcije specificiraju se pomoću apstraktnih deskriptora kako slijedi:

apstraktni-deklarator specificatora tipa;

Apstraktni deskriptor je deskriptor bez identifikatora, koji se sastoji od jednog ili više pokazivača, niza ili modifikatora funkcije. Modifikator pokazivača (*) je uvijek specificiran prije identifikatora u deskriptoru, a niz i modifikator funkcije () su uvijek navedeni iza njega. Dakle, da bi se pravilno protumačio apstraktni deskriptor, mora se početi sa impliciranim identifikatorom.

Apstraktni deskriptori mogu biti složeni. Zagrade u složenim apstraktnim specifikacijama određuju redosled interpretacije, baš kao što su to činili kada se tumače složeni specifikacije u deklaracijama.

1.2.12. Inicijalizacija podataka

Kada je varijabla deklarirana, može joj se dati početna vrijednost pričvršćivanjem inicijatora na ručku. Inicijator počinje znakom "=" i ima sljedeće oblike.

Format 1: = pokretač;

Format 2: = ( lista - inicijatori );

Format 1 se koristi prilikom inicijalizacije varijabli osnovnih tipova i pokazivača, a format 2 kada se inicijaliziraju složeni objekti.

Varijabla tol se inicijalizira simbolom "N".

const long megabute = (1024 * 1024);

Nepromjenjiva varijabla megabute se inicijalizira u konstantni izraz nakon čega se ne može promijeniti.

statički int b = (1,2,3,4);

Inicijalizira se dvodimenzionalni niz od b cijelih brojeva; elementima niza se dodjeljuju vrijednosti sa liste. Ista inicijalizacija se može uraditi ovako:

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

Možete izostaviti jednu ili više dimenzija prilikom inicijalizacije niza

static int b typespecifier id [, id] ...

Modifikatori su ključne riječi signed, unsigned, short, long.
Specifikator tipa je ključna riječ char ili int koja specificira tip varijable koja se deklarira.
Identifikator je ime varijable.

Charx; int a, b, c; unsigned long long y;

Kada deklarišete varijablu, možete je inicijalizirati, odnosno dodijeliti joj početnu vrijednost.

int x = 100;

Broj 100 će odmah biti upisan u varijablu x nakon deklaracije. Bolje je deklarirati inicijalizirane varijable u posebnim redovima.

Tipovi podataka. Program u proceduralnim jezicima, kojem C pripada, je opis operacija nad vrijednostima različitih tipova. Tip definira skup vrijednosti koje vrijednost može preuzeti i skup operacija u kojima može sudjelovati.

U jeziku C, tipovi su povezani sa imenima (identifikatorima) vrednosti, odnosno sa varijablama. Varijabla u C je povezana s memorijskom lokacijom. Tip varijable specificira veličinu ćelije, način na koji je njen sadržaj kodiran i dozvoljene transformacije preko vrijednosti ove varijable. Sve varijable moraju biti deklarirane prije nego što se koriste. Svaka varijabla mora biti deklarirana samo jednom.

Opis se sastoji od specificatora tipa iza kojeg slijedi lista varijabli. Varijable na listi su odvojene zarezima. Tačka i zarez se stavlja na kraj opisa.

Primjeri opisa:

char a,b; /* Varijable a i b imaju tip

char */intx; /* Varijabla x - tip int

*/ char simbol; /" Opisane su varijable sym tipa char;

*/ int count.num; /* broj i broj tipa int */

Varijablama se mogu dodijeliti početne vrijednosti unutar njihovih deklaracija. Ako ime varijable prati znak jednakosti i konstanta, ta konstanta služi kao inicijalizator.

primjeri: char backch = "\0";

Razmotrite osnovne tipove u jeziku C.

int - cijeli broj ("cijeli broj"). Vrijednosti ovog tipa su cijeli brojevi iz nekog ograničenog raspona (obično - 32768 do 32767). Opseg je određen veličinom ćelije za tip i specifičan je za mašinu. Osim toga, postoje pomoćne riječi koje se mogu koristiti sa tipom int: short int ("kratki cijeli broj" - "kratki cijeli broj"), unsigned int ("unsigned integer" - "unsigned integer"), long int ("dugi cijeli broj" "), koji smanjuju ili, obrnuto, proširuju raspon predstavljanja brojeva.

char- karakter ("karakter"). Važeća vrijednost za ovaj tip je jedan znak (ne smije se brkati s tekstom!). Simbol je napisan apostrofima.

primjeri:"x" 2 "?"

Jedan znak zauzima jedan bajt u memoriji računara. U stvari, ne pohranjuje se znak, već broj - kod karaktera (od 0 do 255). U posebnim tablicama kodiranja su naznačeni svi važeći znakovi i njihovi odgovarajući kodovi.

U jeziku C dozvoljeno je korištenje tipa char kao numeričkog tipa, odnosno izvođenje operacija sa kodom znakova, uz korištenje specificatora cjelobrojnog tipa u zagradama - (int).

float - real (pokretni zarez). Vrijednosti ovog tipa su brojevi, ali, za razliku od char i int, nisu nužno cijeli brojevi.

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

realni brojevi dvostruke - dvostruke preciznosti. Ovaj tip je sličan tipu float, ali ima mnogo veći raspon vrijednosti (na primjer, za programski sistem Borland-C od 1.7E-308 do 1.7E+308 umjesto raspona od 3.4E-38 do 3.4E+38 za plutajući tip). Međutim, povećanje opsega i tačnosti predstavljanja brojeva dovodi do smanjenja brzine izvršavanja programa i rasipničkog korišćenja računarske RAM memorije.


Obratite pažnju na nedostatak tipa stringa na ovoj listi. Ne postoji poseban tip u C-u koji se može koristiti za opisivanje stringova. Umjesto toga, stringovi su predstavljeni kao niz char elemenata. To znači da će se znakovi u nizu nalaziti u susjednim memorijskim ćelijama.

Treba napomenuti da je posljednji element niza karakter \0. Ovo je "nul karakter" i koristi se u C za označavanje kraja niza. Null karakter nije cifra 0; nije ispisan i ima broj 0 u tablici ASCII kodova. Prisustvo nul znaka znači da bi broj ćelija u nizu trebao biti. najmanje jedan više od broja znakova koji će biti pohranjeni u memoriji.

Dajemo primjer korištenja stringova.

Program 84

#include main()

scanf("%s",string) ;

printf("%s",string);

Ovaj primjer opisuje niz od 31 memorijske lokacije, od kojih 30 može sadržavati jedan char element. Unosi se kada se pozove funkcija scanf("%s",string);. "&" nedostaje kada se navodi niz znakova.

Pointers. Pointer - neki simbolički prikaz adrese memorijske ćelije dodijeljene varijabli.

Na primjer, &name je pokazivač na varijablu imena;

Ovdje & je operacija dobivanja adrese. Stvarna adresa je broj, a simbolički prikaz adrese i imena je konstanta pokazivača.

C jezik takođe ima varijable pokazivača. Kao što je vrijednost char varijable znak, a vrijednost int varijable cijeli broj, vrijednost varijable pokazivača je adresa neke vrijednosti.

Ako pokazivaču damo ime ptr, možemo napisati sljedeću izjavu:

ptr = /* dodjeljuje adresu imena ptr */

U ovom slučaju kažemo da je prt "pokazivač na" ime. Razlika između dvije notacije ptr i &name je u tome što je prt varijabla dok je &name konstanta. Ako je potrebno, možete učiniti da varijabla ptr pokazuje na neki drugi objekt:

ptr= /* ptr ukazuje na bah, a ne na ime */

Sada je vrijednost varijable prt adresa varijable bah. Pretpostavimo da znamo da varijabla ptr sadrži referencu na varijablu bah. Zatim, da biste pristupili vrijednosti ove varijable, možete koristiti operaciju “indirektnog adresiranja” * :

val = *ptr; /* određuje vrijednost na koju ukazuje ptr */ Posljednje dvije izjave zajedno su ekvivalentne sljedećem:

Dakle, kada je znak & ime varijable slijedi, rezultat operacije je adresa navedene varijable; &nurse daje adresu varijable nurse; kada nakon * slijedi pokazivač na varijablu, rezultat operacije je vrijednost smještena u memorijsku lokaciju na navedenoj adresi.

primjer: medicinska sestra = 22;

ptr= /* pokazivač na medicinsku sestru */

Rezultat je dodjela vrijednosti 22 varijabli val.

Nije dovoljno reći da je neka varijabla pokazivač. Osim toga, morate reći na koji tip varijable se ovaj pokazivač odnosi. Razlog je taj što različite vrste varijabli zauzimaju različit broj memorijskih lokacija, dok neke operacije pokazivača zahtijevaju da znate količinu dodijeljene memorije.

Primjeri ispravan opis pokazivača: int *pi; char*pc;

Specifikacija tipa specificira tip varijable na koju upućuje pokazivač, a simbol * definira samu varijablu kao pokazivač. Opis tipa int *pi; kaže da je pi pokazivač i da je *pi int vrijednost.

C jezik pruža mogućnost definiranja imena tipova podataka. Bilo kojem tipu podataka može se dati ime korištenjem definicije typedef i ovo ime se može koristiti kasnije pri opisivanju objekata.

Format: typedef<старый тип> <новый тип> primjer: typedef long LARGE; /* veliki tip je definiran, što je ekvivalentno dugom */

Typedef ne uvodi nikakve nove tipove, on samo dodaje novo ime za već postojeći tip. Varijable deklarirane na ovaj način imaju potpuno ista svojstva kao i eksplicitno deklarirane varijable. Preimenovanje tipa se koristi za uvođenje smislenih ili skraćenih naziva tipova, što poboljšava razumljivost programa i za poboljšanje prenosivosti programa (nazivi istog tipa podataka mogu se razlikovati na različitim računarima).

Operacije. Jezik C odlikuje se širokim spektrom operacija (više od 40). Ovdje razmatramo samo glavne, tabelu. 3.3.

Aritmetičke operacije. Oni uključuju

Dodatak(+),

Oduzimanje (binarno) (-),

množenje (*),

divizija (/),

Ostatak od cjelobrojnog dijeljenja (%),

Oduzimanje (unarno) (-) .

U jeziku C je prihvaćeno pravilo: ako su dividenda i djelitelj tipa int, tada se podjela vrši u potpunosti, odnosno razlomak rezultata se odbacuje.

Kao i obično, u izrazima se operacije množenja, dijeljenja i ostatka izvode prije sabiranja i oduzimanja. Zagrade se koriste za promjenu redoslijeda akcija.

Program 85

#include

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);

Rezultat izvršenja programa: 11 1 0 1

Tabela 3.3 Starost i redoslijed izvođenja operacija

Osnove jezika

Kod programa i podaci kojima program manipuliše upisuju se u memoriju računara kao niz bitova. Bit- ovo je najmanji element kompjuterske memorije, sposoban da pohrani ili 0 ili 1. Na fizičkom nivou, to odgovara električnom naponu, koji, kao što znate, ili postoji ili nema. Gledajući sadržaj memorije računara, videćemo nešto poput:
...

Vrlo je teško shvatiti takav niz, ali ponekad moramo manipulirati takvim nestrukturiranim podacima (obično se potreba za tim javlja prilikom programiranja drajvera hardverskih uređaja). C++ pruža skup operacija za rad sa bitnim podacima. (O tome ćemo govoriti u 4. poglavlju.)
Po pravilu, neka struktura se nameće nizu bitova, grupirajući bitove u bajtova I riječi. Bajt sadrži 8 bita, a riječ 4 bajta ili 32 bita. Međutim, definicija riječi može biti različita na različitim operativnim sistemima. Prelazak na 64-bitne sisteme sada počinje, a sistemi sa 16-bitnim riječima su nedavno bili uobičajeni. Iako je veličina bajta ista u velikoj većini sistema, i dalje ćemo ove vrijednosti nazivati ​​specifičnim za stroj.

Sada možemo govoriti o, na primjer, bajtu na adresi 1040 ili riječi na adresi 1024 i reći da bajt na adresi 1032 nije jednak bajtu na adresi 1040.
Međutim, ne znamo šta predstavlja bilo koji bajt, bilo koja mašinska riječ. Kako razumjeti značenje određenih 8 bitova? Da bismo nedvosmisleno protumačili značenje ovog bajta (ili riječi, ili drugog skupa bitova), moramo znati vrstu podataka predstavljenih ovim bajtom.
C++ pruža skup ugrađenih tipova podataka: karakterni, cijeli broj, realni i skup kompozitnih i proširenih tipova: nizovi, nizovi, kompleksni brojevi. Osim toga, za radnje s ovim podacima postoji osnovni skup operacija: poređenje, aritmetičke i druge operacije. Tu su i prijelazi, petlje, uvjetni iskazi. Ovi elementi jezika C++ čine skup gradivnih blokova od kojih možete izgraditi sistem bilo koje složenosti. Prvi korak u savladavanju C++-a je učenje ovih osnovnih elemenata, čemu je i posvećen Drugi dio ove knjige.
Poglavlje 3 pruža pregled ugrađenih i proširenih tipova i mehanizama pomoću kojih možete kreirati nove tipove. U osnovi, to je, naravno, mehanizam klasa uveden u Odjeljku 2.3. Poglavlje 4 bavi se izrazima, ugrađenim operatorima i njihovim prioritetom, te konverzijama tipova. Poglavlje 5 govori o jezičkim uputstvima. Konačno, Poglavlje 6 predstavlja standardnu ​​biblioteku C++ i tipove kontejnera, vektor i asocijativni niz.

3. C++ tipovi podataka

Ovo poglavlje daje pregled ugrađen, ili osnovno, tipovi podataka jezika C++. Počinje sa definicijom literals, kao što je 3,14159 ili pi, a zatim i pojam varijabla, ili objekt, koji mora biti jedan od tipova podataka. Ostatak poglavlja posvećen je detaljnom opisu svakog ugrađenog tipa. Takođe navodi izvedene tipove podataka za nizove i nizove koje obezbeđuje standardna biblioteka C++. Iako ovi tipovi nisu elementarni, veoma su važni za pisanje pravih C++ programa i želimo da čitaoca upoznamo sa njima što je ranije moguće. Nazvat ćemo takve tipove podataka proširenje C++ osnovni tipovi.

3.1. Literali

C++ ima skup ugrađenih tipova podataka za predstavljanje cijelih i realnih brojeva, znakova, kao i tip podataka „niz znakova“ koji se koristi za pohranjivanje nizova znakova. Tip char se koristi za pohranjivanje pojedinačnih znakova i malih cijelih brojeva. Zauzima jedan bajt mašine. Tipovi short, int i long su dizajnirani da predstavljaju cijele brojeve. Ovi tipovi se razlikuju samo u rasponu vrijednosti koje brojevi mogu uzeti, a specifične veličine navedenih tipova zavise od implementacije. Obično kratko je pola mašinske riječi, int je jedna riječ, dugo je jedna ili dvije riječi. Na 32-bitnim sistemima, int i long su generalno iste veličine.

Tipovi float, double i long double su za brojeve s pomičnim zarezom i razlikuju se po preciznosti reprezentacije (broj značajnih cifara) i rasponu. Tipično, float (jednostruka preciznost) uzima jednu mašinsku riječ, dvostruka (dvostruka preciznost) dvije, a long double (proširena preciznost) tri.
char, short, int i long zajedno čine cjelobrojni tipovi, što zauzvrat može biti iconic(potpisano) i nepotpisan(nepotpisano). Kod tipova sa znakom, krajnji lijevi bit se koristi za pohranjivanje predznaka (0 je plus, 1 je minus), a preostali bitovi sadrže vrijednost. U neoznačenim tipovima, svi bitovi se koriste za vrijednost. 8-bitni znakovni znak može predstavljati vrijednosti od -128 do 127, a neoznačeni znak može predstavljati vrijednosti od 0 do 255.

Kada se u programu naiđe na broj, kao što je 1, ovaj broj se poziva doslovno, ili doslovna konstanta. Konstanta, jer ne možemo promijeniti njenu vrijednost, i literal, jer se njegova vrijednost pojavljuje u tekstu programa. Literal je vrijednost koja se ne može adresirati: iako je, naravno, zapravo pohranjena u memoriji mašine, ne postoji način da se zna njegova adresa. Svaki literal ima specifičan tip. Dakle, 0 je tipa int, 3.14159 je tipa double.

Literali cjelobrojnog tipa mogu se pisati u decimalnom, oktalnom i heksadecimalnom obliku. Evo kako broj 20 izgleda kao decimalni, oktalni i heksadecimalni literal:

20 // decimalni
024 // oktal
0x14 // hex

Ako literal počinje sa 0, tretira se kao oktalno, ako počinje sa 0x ili 0X, tretira se kao heksadecimalno. Uobičajena notacija se tretira kao decimalni broj.
Prema zadanim postavkama, svi literali s cijelim brojevima su potpisani int. Možete eksplicitno definirati cjelobrojni literal da je tipa long dodavanjem slova L na kraj broja (koriste se i velika slova L i mala slova l, ali zbog čitljivosti, mala slova se ne bi trebala koristiti: lako je pomiješati s

jedan). U (ili u) na kraju definiše literal kao unsigned int, a dva slova UL ili LU kao neoznačeno dugo. Na primjer:

128u 1024UL 1L 8Lu

Literali koji predstavljaju realne brojeve mogu se pisati ili sa decimalnim zarezom ili u naučnoj (eksponencijalnoj) notaciji. Po defaultu su tipa double. Da biste eksplicitno odredili tip float, morate koristiti sufiks F ili f, a za duge duple - L ili l, ali samo u slučaju zapisa sa decimalnim zarezom. Na primjer:

3.14159F 0/1f 12.345L 0.0 3el 1.0E-3E 2.1.0L

Riječi true i false su literali tipa bool.
Reprezentabilne literalne karakterne konstante se pišu kao jednostruki navodnici. Na primjer:

"a" "2" "," " " (razmak)

Specijalni znakovi (tabulator, vraćanje nosioca) se zapisuju kao izlazne sekvence. Definirane su sljedeće sekvence (počinju sa obrnutom kosom crtom):

Novi red \n horizontalni jezičak \t backspace \b vertikalni jezičak \v povratak nosača \r ubacivanje lista \f zvono \a obrnuta kosa crta \\ pitanje \? jednostruki navodnik \"dvostruki navodnik \"

Opšti izlazni niz je oblika \ooo, gdje je ooo jedna do tri oktalne cifre. Ovaj broj je kod karaktera. Koristeći ASCII kod, možemo napisati sljedeće literale:

\7 (poziv) \14 (novi red) \0 (null) \062("2")

Literal znakova može imati prefiks L (na primjer, L"a"), što znači poseban tip wchar_t, dvobajtni tip znakova koji se koristi za pohranjivanje znakova nacionalnih abeceda ako se ne mogu predstaviti uobičajenim tipom char , kao što su kineska ili japanska slova.
Literal stringa je niz znakova zatvoren u dvostruke navodnike. Takav literal može se prostirati na nekoliko redova, u kom slučaju se na kraju reda stavlja obrnuta kosa crta. Posebni znakovi mogu biti predstavljeni njihovim vlastitim izlaznim sekvencama. Evo primjera string literala:

"" (prazan niz) "a" "\nCC\options\tfile.\n" "višeredni \ string literal signalizira njegov \ nastavak sa obrnutom kosom crtom"

U stvari, string literal je niz znakovnih konstanti, gdje je, prema konvenciji jezika C i C++, posljednji element uvijek poseban znak sa kodom 0 (\0).
Literal "A" specificira jedan znak A, a string literal "A" specificira niz od dva elementa: "A" i \0 (prazan znak).
Pošto postoji tip wchar_t, postoje literali ovog tipa, označeni, kao iu slučaju pojedinačnih znakova, prefiksom L:

L "široki literalni niz"

Literal stringa tipa wchar_t je niz znakova istog tipa koji se završava nulom.
Ako se dva ili više string literala (tipa char ili wchar_t) pojavljuju u redu u testu programa, kompajler ih spaja u jedan string. Na primjer, sljedeći tekst

"dva" "neki"

će proizvesti niz od osam znakova - dva i završni null karakter. Rezultat spajanja nizova različitih tipova je nedefiniran. ako napišete:

// ovo nije dobra ideja "dva" L"neki"

onda će na nekom računaru rezultat biti neki smisleni niz, a na drugom može ispasti nešto sasvim drugo. Programi koji koriste karakteristike implementacije određenog kompajlera ili operativnog sistema nisu prenosivi. Snažno obeshrabrujemo upotrebu ovakvih struktura.

Vježba 3.1

Objasnite razliku u definicijama sljedećih literala:

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

Vježba 3.2

Koje su greške u primjerima u nastavku?

(a) "Ko ide s F\144rgusom?\014" (b) 3.14e1L (c) "dva" L"neki" (d) 1024f (e) 3.14UL (f) "komentar u više redaka"

3.2. Varijable

Zamislite da rješavamo problem dizanja 2 na stepen 10. Pišemo:

#include
int main() (
// prvo rješenje
cout<< "2 raised to the power of 10: ";
cout<< 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
cout<< endl;
return 0;
}

Problem je riješen, iako smo morali više puta provjeriti da li se literal 2 zaista ponavlja 10 puta. Nismo pogriješili u pisanju ovog dugačkog niza dvojki, a program je vratio tačan rezultat - 1024.
Ali sada je zatraženo da podignemo 2 na stepen 17, a zatim na 23. Izuzetno je nezgodno svaki put mijenjati tekst programa! I, što je još gore, vrlo je lako pogriješiti ako napišete dodatna dva ili ga preskočite... Ali šta ako treba da odštampate tabelu stepena dvojke od 0 do 15? Ponovite 16 puta dva reda koja imaju zajednički oblik:

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

gdje se X uzastopno povećava za 1, a traženi broj literala zamjenjuje se umjesto tačke?

Da, obavili smo posao. Malo je vjerovatno da će kupac ulaziti u detalje, jer je zadovoljan rezultatom. U stvarnom životu ovaj pristup često funkcionira, štoviše, opravdan je: problem se rješava daleko ne na najelegantniji način, već u željenom vremenskom okviru. Traženje ljepše i kompetentnije opcije može biti nepraktičan gubitak vremena.

U ovom slučaju “metoda grube sile” daje tačan odgovor, ali kako je neugodno i dosadno rješavati problem na ovaj način! Znamo tačno koje korake treba preduzeti, ali sami koraci su jednostavni i monotoni.

Uključivanje složenijih mehanizama za isti zadatak, po pravilu, značajno produžava vrijeme pripremne faze. Osim toga, što se koriste složeniji mehanizmi, veća je vjerovatnoća grešaka. Ali i pored neizbježnih grešaka i pogrešnih poteza, upotreba „visoke tehnologije“ može donijeti prednosti u brzini razvoja, a da ne govorimo o činjenici da ove tehnologije uvelike proširuju naše mogućnosti. I – šta je zanimljivo! Sam proces odlučivanja može postati privlačan.
Vratimo se našem primjeru i pokušajmo "tehnološki poboljšati" njegovu implementaciju. Možemo koristiti imenovani objekat da pohranimo vrijednost snage do koje želimo povećati svoj broj. Također, umjesto ponavljajućeg niza literala, možemo koristiti operator petlje. Evo kako će to izgledati:

#include
int main()
{
// objekti tipa int
int vrijednost = 2;
int pow = 10;
cout<< value << " в степени "
<< pow << ": \t";
int res = 1;
// operator petlje:
// ponoviti izračun rez
// dok cnt ne bude veći od pow
za (int cnt=1; cnt<= pow; ++cnt)
res = res * vrijednost;
cout<< res << endl;
}

value, pow, res i cnt su varijable koje vam omogućavaju pohranjivanje, modificiranje i preuzimanje vrijednosti. Naredba petlje for ponavlja vrijeme pow niza rezultata izračunavanja.
Bez sumnje, napravili smo mnogo fleksibilniji program. Međutim, to još uvijek nije funkcija. Da biste dobili stvarnu funkciju koja se može koristiti u bilo kojem programu za izračunavanje stepena broja, potrebno je istaknuti opći dio proračuna i postaviti određene vrijednosti kao parametre.

int pow(int val, int exp) (za (int res = 1; exp > 0; --exp) res = res * val; vrati res; )

Sada neće biti teško dobiti bilo koji stepen željenog broja. Evo kako se implementira naš posljednji zadatak - ispisati tablicu potencija dva od 0 do 15:

#include eksterni int pow(int,int); int main() (int val = 2; int exp = 15;
cout<< "Степени 2\n";
za (int cnt=0; cnt<= exp; ++cnt)
cout<< cnt << ": "
<< pow(val, cnt) << endl;
return 0;
}

Naravno, naša pow() funkcija još uvijek nije dovoljno opća i nije dovoljno pouzdana. Ne može da radi na realnim brojevima, pogrešno podiže brojeve na negativan stepen - uvek vraća 1. Rezultat podizanja velikog broja na veliki stepen možda neće stati u int promenljivu, i tada će se vratiti neka nasumična pogrešna vrednost. Vidite koliko teško može biti pisati funkcije koje su namijenjene širokoj upotrebi? Mnogo je teže nego implementirati određeni algoritam koji ima za cilj rješavanje određenog problema.

3.2.1. Šta je varijabla

Varijabilna, ili objekat- ovo je imenovana oblast memorije kojoj imamo pristup iz programa; možete staviti vrijednosti tamo i zatim ih dohvatiti. Svaka C++ varijabla ima specifičan tip koji karakterizira veličinu i lokaciju te memorijske oblasti, raspon vrijednosti koje može pohraniti i skup operacija primjenjivih na tu varijablu. Evo primjera definiranja pet objekata različitih tipova:

int student_count; dupla plata; bool on_loan; strins street_address; char delimiter;

Varijabla, poput literala, ima određeni tip i pohranjuje svoju vrijednost u nekom području memorije. Adresabilnost- to je ono što doslovno nedostaje. Postoje dvije vrijednosti povezane s varijablom:

  • sama vrijednost, ili r-vrijednost (od pročitana vrijednost - vrijednost za čitanje), koja je pohranjena u ovoj memorijskoj oblasti i inherentna je i promjenljivoj i literalnoj;
  • vrijednost adrese memorijskog područja povezanog s promjenljivom, ili l-vrijednost (od vrijednosti lokacije - vrijednost lokacije) - mjesto gdje je pohranjena r-vrijednost; pripada samo objektu.

U izrazu

Ch = ch - "0";

varijabla ch je i lijevo i desno od simbola dodjele. Na desnoj strani je pročitana vrijednost (ch i literal znakova "0"): podaci povezani s promjenljivom čitaju se iz odgovarajuće memorijske oblasti. Na lijevoj strani je vrijednost lokacije: rezultat oduzimanja se stavlja u memorijsko područje povezano s promjenljivom ch. Općenito, lijevi operand operatora dodjeljivanja mora biti l-vrijednost. Ne možemo napisati sljedeće izraze:

// greške pri kompilaciji: vrijednosti na lijevoj strani nisu l-vrijednosti // greška: literal nije l-vrijednost 0 = 1; // greška: aritmetički izraz nije l-vrijednost plata + plata * 0,10 = nova_plata;

Operator definicije varijable dodjeljuje memoriju za to. Budući da objekt ima samo jedno memorijsko područje povezano s njim, takav operator se može pojaviti samo jednom u programu. Ako se varijabla definirana u jednoj izvornoj datoteci mora koristiti u drugoj, nastaju problemi. Na primjer:

// file module0.C // definira fileName objekt string fileName; // ... postavi fileName na vrijednost
// module1.C datoteka
// koristi fileName objekt
// nažalost, ne kompajlira:
// Ime datoteke nije definirano u modulu1.C
ifstream ulazna_datoteka(ime datoteke);

C++ zahtijeva da objekt bude poznat prije nego mu se prvi put pristupi. To je zbog potrebe da se osigura da se objekt pravilno koristi prema njegovom tipu. U našem primjeru, module1.C će uzrokovati grešku kompilacije jer ime datoteke nije definirano u njemu. Da bismo izbjegli ovu grešku, moramo reći kompajleru o već definiranoj varijabli fileName. Ovo se radi pomoću izjave deklaracije varijable:

// file module1.C // koristi objekt fileName // fileName je deklariran, tj. program prima
// informacije o ovom objektu bez njegove sekundarne definicije
eksterni string fileName; ifstream input_file(ime datoteke)

Deklaracija varijable govori kompajleru da je objekat sa datim imenom i datim tipom definisan negdje u programu. Memorija se ne dodjeljuje za varijablu kada je deklarirana. (Ključna riječ extern je razmatrana u Odjeljku 8.2.)
Program može sadržavati bilo koji broj deklaracija iste varijable, ali se može definirati samo jednom. Takve deklaracije se zgodno stavljaju u datoteke zaglavlja, uključujući ih u module koji ih zahtijevaju. Tako možemo pohraniti informacije o objektima na jednom mjestu i pružiti pogodnost modifikacije ako je potrebno. (Više ćemo o datotekama zaglavlja govoriti u odjeljku 8.2.)

3.2.2. Ime varijable

ime varijable, ili identifikator, može se sastojati od latiničnih slova, cifara i znaka donje crte. Velika i mala slova u nazivima se razlikuju. Jezik C++ ne ograničava dužinu identifikatora, ali upotreba predugačkih imena kao što je gosh_this_is_an_impossibly_name_to_type je nezgodna.
Neke riječi su ključne riječi u C++ i ne mogu se koristiti kao identifikatori; Tabela 3.1 daje potpunu njihovu listu.

Tabela 3.1. Ključne riječi C++

asm auto bool break slučaj
uhvatiti char klasa konst const_cast
nastaviti default izbrisati uradi duplo
dynamic_cast ostalo enum eksplicitno izvoz
vanjski false float za prijatelju
idi ako U redu int dugo
promjenjiv imenskog prostora novo operater privatni
zaštićeno javnosti registar reinterpret_cast povratak
kratko potpisan sizeof statički static_cast
struct prekidač šablon ovo bacanje
typedef tačno probaj typeid typename
sindikat nevažeći sindikat koristeći virtuelno void

Kako biste tekst vašeg programa učinili razumljivijim, preporučujemo da slijedite općeprihvaćene konvencije za imenovanje objekata:

  • ime varijable se obično piše malim slovima, na primjer indeks (za poređenje: Indeks je ime tipa, a INDEX je konstanta definirana pomoću direktive preprocesora #define);
  • identifikator mora imati neko značenje, objašnjavajući svrhu objekta u programu, na primjer: datum_rođenja ili plata;

ako se takvo ime sastoji od nekoliko riječi, kao što je datum_rođenja, tada je uobičajeno da se riječi odvoje podvlakom (datum_rođenja) ili svaka slijedeća riječ piše velikim slovima (datum rođenja). Primećeno je da programeri koji su navikli na objektno orijentisani pristup preferiraju pisanje reči velikim slovom, dok oni_koji_su_napisali_C često koriste znak donje crte. Koja je od ove dvije metode bolja, stvar je ukusa.

3.2.3. Definicija objekta

U svom najjednostavnijem slučaju, operator definicije objekta sastoji se od specificator tipa I naziv objekta i završava se tačkom i zarezom. Na primjer:

dupla plata; dupla nadnica; int mjesec; int dan; int godina; nepotpisana duga udaljenost;

Možete definirati više objekata istog tipa u jednom iskazu. U ovom slučaju, njihova imena su navedena odvojena zarezima:

Dvostruka plata, nadnica; int mjesec, dan, godina; nepotpisana duga udaljenost;

Jednostavno definiranje varijable ne postavlja njenu početnu vrijednost. Ako je objekt definiran kao globalni, C++ specifikacija jamči da će biti inicijaliziran na null. Ako je varijabla lokalna ili dinamički alocirana (pomoću novog operatora), njena početna vrijednost nije definirana, odnosno može sadržavati neku nasumične vrijednosti.
Korišćenje ovakvih varijabli je vrlo česta greška, a takođe ju je teško uočiti. Preporučuje se eksplicitno navesti početnu vrijednost objekta, barem u slučajevima kada nije poznato može li se objekt inicijalizirati. Mehanizam klase uvodi koncept zadanog konstruktora, koji se koristi za dodeljivanje podrazumevanih vrednosti. (Već smo govorili o ovome u odjeljku 2.3. Nastavit ćemo govoriti o default konstruktorima malo kasnije u odjeljcima 3.11 i 3.15, gdje ćemo raspravljati o stringovima i složenim klasama iz standardne biblioteke.)

int main() ( // neinicijalizirani lokalni objekt int ival;
// objekt tipa string je inicijaliziran
// zadani konstruktor
string project;
// ...
}

Početna vrijednost se može specificirati direktno u izrazu definicije varijable. U C++-u su dozvoljena dva oblika inicijalizacije varijable - eksplicitna, koristeći operator dodjeljivanja:

Intival = 1024; string project = "Fantasia 2000";

i implicitno, sa početnom vrijednošću navedenom u zagradama:

Intival(1024); string project("Fantasia 2000");

Obje opcije su ekvivalentne i postavljaju početne vrijednosti za cjelobrojnu varijablu ival na 1024 i za niz projekta da bude "Fantasia 2000".
Eksplicitna inicijalizacija se također može koristiti kada se varijable definiraju kao lista:

Dvostruka plata = 9999,99, plata = plata + 0,01; int mjesec = 08; dan=07, godina=1955;

Varijabla postaje vidljiva (i važeća u programu) čim je definirana, tako da možemo inicijalizirati varijablu plaće sa zbirom novodefinirane varijable plaće sa nekom konstantom. Dakle, definicija je:

// ispravno, ali besmisleno int bizarre = bizarre;

sintaktički je validan, iako besmislen.
Ugrađeni tipovi podataka imaju posebnu sintaksu za postavljanje null vrijednosti:

// ival je postavljen na 0, a dval na 0.0 int ival = int(); double dval = double();

U sljedećoj definiciji:

// int() se primjenjuje na svaki od 10 elemenata vektora< int >ivec(10);

svaki od deset elemenata vektora je inicijaliziran sa int(). (Već smo govorili o vektorskoj klasi u Odjeljku 2.8. Vidi odjeljak 3.10 i Poglavlje 6 za više o tome.)
Varijabla se može inicijalizirati izrazom bilo koje složenosti, uključujući pozive funkcija. Na primjer:

#include #include
dupla cijena = 109,99, popust = 0,16;
dvostruka prodajna_cijena(cijena * popust);
string pet("bore"); extern int get_value(); intval = get_value();
unsigned abs_val = abs(val);

abs() je standardna funkcija koja vraća apsolutnu vrijednost parametra.
get_value() je neka korisnički definirana funkcija koja vraća cjelobrojnu vrijednost.

Vježba 3.3

Koja od sljedećih definicija varijable sadrži sintaksičke greške?

(a) int auto = 1024, auto = 2048; (b) int ival = ival; (c) int ival(int()); (d) dupla plata = plata = 9999,99; (e) cin >> int input_value;

Vježba 3.4

Objasnite razliku između l-vrijednosti i r-vrijednosti. Navedite primjere.

Vježba 3.5

Pronađite razlike u korištenju varijabli name i student u prvom i drugom redu svakog primjera:

(a) ime vanjskog stringa; stringname("vježba 3.5a"); (b) eksternvektor studenti; vektor studenti;

Vježba 3.6

Koja imena objekata nisu dozvoljena u C++? Promijenite ih tako da budu sintaktički ispravni:

(a) int double = 3,14159; (b) vektor< int >_; (c) string namespace; (d) hvatanje žice-22; (e) char 1_or_2 = "1"; (f) float Float = 3,14f;

Vježba 3.7

Koja je razlika između sljedećih definicija globalnih i lokalnih varijabli?

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

3.3. Pointers

Pokazivači i dinamička alokacija memorije su ukratko predstavljeni u odjeljku 2.2. Pointer je objekat koji sadrži adresu drugog objekta i omogućava vam da manipulišete tim objektom indirektno. Obično se pokazivači koriste za rad sa dinamički kreiranim objektima, za konstruisanje povezanih struktura podataka kao što su povezane liste i hijerarhijska stabla, i za prosleđivanje velikih objekata – nizova i objekata klasa – kao parametara funkcijama.
Svaki pokazivač je povezan s nekom vrstom podataka, a njihova interna reprezentacija ne ovisi o internom tipu: i veličina memorije koju zauzima objekt tipa pokazivača i raspon vrijednosti su isti za njih. Razlika je u tome kako kompajler tretira adresirani objekt. Pokazivači na različite tipove mogu imati istu vrijednost, ali memorijska lokacija na kojoj se nalaze odgovarajući tipovi može biti različita:

  • pokazivač na int koji sadrži adresnu vrijednost 1000 usmjerava se na memorijsku oblast 1000-1003 (na 32-bitnom sistemu);
  • pokazivač na double koji sadrži adresnu vrijednost 1000 usmjerava se na memorijsku oblast 1000-1007 (na 32-bitnom sistemu).

Evo nekoliko primjera:

int *ip1, *ip2; kompleks *cp; string*pstring; vektor *pvec; duplo *dp;

Pokazivač je označen zvjezdicom ispred imena. Prilikom definisanja varijabli liste, zvjezdica mora prethoditi svakom pokazivaču (vidi gore: ip1 i ip2). U primjeru ispod, lp je pokazivač na objekt tipa long, a lp2 je objekt tipa long:

Dugi *lp, lp2;

U sljedećem slučaju, fp se tumači kao float objekat, a fp2 je pokazivač na njega:

Float fp, *fp2;

Operator dereferenciranja (*) može biti odvojen razmacima od imena, pa čak i direktno pored ključne riječi tipa. Stoga su sljedeće definicije sintaktički ispravne i potpuno ekvivalentne:

//upozorenje: ps2 nije pokazivač na string! string* ps, ps2;

Može se pretpostaviti da su i ps i ps2 pokazivači, iako je pokazivač samo prvi od njih.
Ako je vrijednost pokazivača 0, onda ne sadrži nijednu adresu objekta.
Neka je data varijabla tipa int:

Intival = 1024;

Slijede primjeri definiranja i korištenja pokazivača na int pi i pi2:

//pi inicijaliziran na nultu adresu int *pi = 0;
// pi2 inicijaliziran adresom ival
int *pi2 =
// ispravno: pi i pi2 sadrže adresu ival
pi = pi2;
// pi2 sadrži adresu nula
pi2 = 0;

Pokazivaču se ne može dodijeliti vrijednost koja nije adresa:

// greška: pi ne može biti int pi = ival

Slično, ne možete dodijeliti pokazivač jednog tipa vrijednosti koja je adresa objekta drugog tipa. Ako su definirane sljedeće varijable:

double dval; duplo *ps =

tada će oba izraza dodjeljivanja dolje uzrokovati grešku kompilacije:

// greške pri kompilaciji // nevažeća dodjela tipa podataka: int*<== double* pi = pd pi = &dval;

Nije da varijabla pi ne može sadržavati adresu dval objekta - adrese objekata različitih tipova imaju istu dužinu. Takve operacije miješanja adresa su namjerno zabranjene, jer kompajlerova interpretacija objekata zavisi od tipa pokazivača na njih.
Naravno, ponekad nas zanima vrijednost same adrese, a ne objekta na koji ona ukazuje (recimo da želimo da uporedimo ovu adresu sa nekom drugom). Za rješavanje ovakvih situacija uvodi se poseban void pokazivač koji može ukazivati ​​na bilo koji tip podataka, a sljedeći izrazi će biti ispravni:

// ispravno: void* može sadržavati // adrese bilo kojeg tipa void *pv = pi; pv=pd;

Tip objekta na koji ukazuje void* je nepoznat i ne možemo manipulirati ovim objektom. Sve što možemo učiniti s takvim pokazivačem je dodijeliti njegovu vrijednost drugom pokazivaču ili ga uporediti s nekom vrijednošću adrese. (Više ćemo govoriti o pokazivaču void u odjeljku 4.14.)
Da biste upućivali na objekat koji ima svoju adresu, morate primijeniti operaciju dereferenciranja, ili indirektno adresiranje, označenu zvjezdicom (*). Imaju sljedeće definicije varijabli:

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

// indirektna dodjela ival ival2 *pi = ival2;
// indirektna upotreba ival kao rvalue i lvalue
*pi = abs(*pi); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;

Kada primijenimo operaciju adrese (&) na objekt tipa int, dobijamo rezultat tipa int*
int*pi =
Ako se ista operacija primeni na objekat tipa int* (pokazivač na int), dobijamo pokazivač na pokazivač na int, tj. int**. int** je adresa objekta koji sadrži adresu objekta tipa int. Dereferenciranjem ppi, dobijamo objekat tipa int* koji sadrži adresu ival. Da biste dobili sam objekat ival, operacija dereferenciranja ppi mora se primijeniti dvaput.

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

Pokazivači se mogu koristiti u aritmetičkim izrazima. Obratite pažnju na sljedeći primjer, gdje dva izraza izvode potpuno različite radnje:

Int i, j, k; int *pi = // i = i + 2
*pi = *pi + 2; // povećaj adresu sadržanu u pi za 2
pi = pi + 2;

Pointeru možete dodati cjelobrojnu vrijednost, a možete i oduzeti od nje. Dodavanje 1 pokazivaču povećava njegovu vrijednost za veličinu memorijskog područja dodijeljenog objektu odgovarajućeg tipa. Ako char zauzima 1 bajt, int 4, a double 8, tada će dodavanje 2 pokazivačima na char, int i double povećati njihovu vrijednost za 2, 8, odnosno 16. Kako se ovo može protumačiti? Ako se objekti istog tipa nalaze u memoriji jedan za drugim, povećanje pokazivača za 1 će uzrokovati da on pokazuje na sljedeći objekt. Stoga se aritmetika pokazivača najčešće koristi u obradi niza; u drugim slučajevima teško da su opravdani.
Evo kako izgleda tipičan primjer korištenja adresne aritmetike kada se iterira preko elemenata niza pomoću iteratora:

intia; int *iter = int *iter_end =
dok (iter != iter_end) (
do_something_with_value(*iter);
++iter;
}

Vježba 3.8

Date su definicije varijabli:

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

Šta se događa kada se izvrše sljedeće operacije dodjele? Ima li grešaka u ovim primjerima?

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

Vježba 3.9

Manipulacija pokazivačem je jedan od najvažnijih aspekata C i C++, ali je lako napraviti greške. Na primjer, kod

Pi = pi = pi + 1024;

će gotovo sigurno uzrokovati da pi pokazuje na nasumično područje memorije. Šta radi ovaj operator dodjeljivanja i u kojem slučaju neće rezultirati greškom?

Vježba 3.10

Ovaj program sadrži grešku koja se odnosi na nepravilnu upotrebu pokazivača:

int foobar(int *pi) ( *pi = 1024; povratak *pi; )
int main() (
int*pi2 = 0;
int ival = foobar(pi2);
return 0;
}

u čemu je greška? Kako to mogu popraviti?

Vježba 3.11

Pojavljuju se greške iz prethodne dvije vježbe i dovode do fatalnih posljedica zbog C++-ovog nedostatka validacije vrijednosti pokazivača u vremenu izvođenja. Zašto mislite da takva provjera nije provedena? Možete li ponuditi neke opće smjernice za sigurnije rukovanje pokazivačem?

3.4. Vrste nizova

C++ podržava dva tipa stringova, ugrađeni tip naslijeđen od C-a i string klasu iz standardne biblioteke C++. String klasa pruža mnogo više mogućnosti i stoga je praktičnija za korištenje, ali u praksi često postoje situacije kada trebate koristiti ugrađeni tip ili dobro razumjeti kako on funkcionira. (Jedan primjer bi bio raščlanjivanje opcija komandne linije proslijeđenih funkciji main(). Ovo ćemo pokriti u 7. poglavlju.)

3.4.1. ugrađeni tip stringa

Kao što je već spomenuto, ugrađeni tip stringa proslijeđen je u C++ naslijeđen od C. Niz znakova je pohranjen u memoriji kao niz, a pristupa mu se pomoću pokazivača tipa char*. C standardna biblioteka pruža skup funkcija za manipulaciju stringovima. Na primjer:

// vraća dužinu stringa int strlen(const char*);
// uspoređuje dva niza
int strcmp(const char*, const char*);
// kopira jedan niz u drugi
char* strcpy(char*, const char*);

C standardna biblioteka je dio C++ biblioteke. Da bismo ga koristili, moramo uključiti datoteku zaglavlja:

#include

Pointer na char, pomoću kojeg pristupamo nizu, pokazuje na niz znakova koji odgovaraju stringu. Čak i kada napišemo string literal kao

Const char *st = "Cijena boce vina\n";

kompajler stavlja sve znakove u nizu u niz, a zatim dodjeljuje st adresu prvog elementa u nizu. Kako se može raditi sa stringom koristeći takav pokazivač?
Obično se aritmetika adresa koristi za nabrajanje znakova niza. Budući da se niz uvijek završava nultim karakterom, možete povećati pokazivač za 1 dok sljedeći znak ne bude nul. Na primjer:

Dok (*st++) ( ... )

st se dereferencira i rezultirajuća vrijednost se provjerava da li je istinita. Bilo koja vrijednost različita od nule smatra se istinitom, i stoga se petlja završava kada se dostigne znak sa kodom 0. Operacija povećanja ++ dodaje 1 pokazivaču st i tako ga prebacuje na sljedeći znak.
Evo kako bi mogla izgledati implementacija funkcije koja vraća dužinu niza. Imajte na umu da budući da pokazivač može sadržavati null vrijednost (ne pokazivati ​​ni na što), treba ga provjeriti prije operacije dereferenciranja:

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

Niz ugrađenog tipa može se smatrati praznim u dva slučaja: ako je pokazivač na niz null (onda uopće nemamo ni jedan niz) ili ako ukazuje na niz koji se sastoji od jednog null karaktera (tj. string koji ne sadrži nijedan značajan karakter).

// pc1 ne adresira nijedan niz znakova char *pc1 = 0; // pc2 adresira null karakter const char *pc2 = "";

Za programera početnika, korištenje nizova ugrađenog tipa prepuno je grešaka zbog preniskog nivoa implementacije i nemogućnosti bez aritmetike adresa. U nastavku ćemo pokazati neke tipične greške početnika. Zadatak je jednostavan: izračunajte dužinu niza. Prva verzija je netačna. Popravi je.

#include const char *st = "Cijena boce vina\n"; int main() (
intlen = 0;
dok (st++) ++len; cout<< len << ": " << st;
return 0;
}

U ovoj verziji, st pokazivač nije dereferenciran. Stoga se ne provjerava jednakost sa 0 karakteru na koji ukazuje st, već sam pokazivač. Pošto je ovaj pokazivač prvobitno imao vrijednost različitu od nule (adresa reda), nikada neće postati nula, a petlja će se izvoditi neograničeno.
U drugoj verziji programa ova greška je eliminisana. Program se uspješno završava, ali rezultat nije tačan. Gdje griješimo ovaj put?

#include const char *st = "Cijena boce vina\n"; int main()
{
intlen = 0;
dok (*st++) ++len; cout<< len << ": " << st << endl;
return 0;
}

Greška je u tome što nakon završetka petlje, pokazivač st ne adresira originalni karakterni literal, već karakter koji se nalazi u memoriji nakon završne nule ovog literala. Bilo šta može biti na ovom mjestu, a izlaz programa će biti nasumični niz znakova.
Možete pokušati popraviti ovu grešku:

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

Sada naš program proizvodi nešto značajno, ali ne u potpunosti. Odgovor izgleda ovako:

18: ena boca vina

Zaboravili smo da uzmemo u obzir da završni nul karakter nije uključen u izračunatu dužinu. st bi trebao biti pomjeren za dužinu niza plus 1. Evo konačno ispravne izjave:

St \u003d st - len - 1;

i evo tačnog rezultata:

18: Cijena boce vina

Međutim, ne možemo reći da naš program izgleda elegantno. Operater

St \u003d st - len - 1;

dodano da ispravi grešku napravljenu u ranoj fazi dizajna programa - direktno povećanje st pokazivača. Ova izjava se ne uklapa u logiku programa, a kod je sada teško razumjeti. Popravke ove vrste se često nazivaju zakrpama—nešto dizajnirano da začepi rupu u postojećem programu. Mnogo bolje rješenje bi bilo ponovno promišljanje logike. Jedna opcija u našem slučaju bi bila da definiramo drugi pokazivač inicijaliziran vrijednošću st:

Const char *p = st;

Sada se p može koristiti u petlji dužine, ostavljajući vrijednost st nepromijenjenom:

dok (*p++)

3.4.2. string class

Kao što smo upravo vidjeli, korištenje ugrađenog tipa stringa je sklono greškama i nije baš zgodno zbog činjenice da je implementirano na preniskom nivou. Zbog toga je prilično uobičajeno da se razvije sopstvena klasa ili klase za predstavljanje tipa stringa - skoro svaka kompanija, odeljenje ili pojedinačni projekat imali su sopstvenu implementaciju niza. Šta reći, isto smo radili u prethodna dva izdanja ove knjige! To je dovelo do problema kompatibilnosti i prenosivosti programa. Implementacija standardne string klase od strane standardne biblioteke C++ imala je za cilj da stavi tačku na ovaj izum bicikala.
Pokušajmo odrediti minimalni skup operacija koje klasa string treba da ima:

  • inicijalizacija nizom znakova (string ugrađenog tipa) ili drugim objektom tipa string. Ugrađeni tip nema drugu mogućnost;
  • kopiranje jednog reda u drugi. Za ugrađeni tip, morate koristiti funkciju strcpy();
  • pristup pojedinačnim znakovima niza za čitanje i pisanje. U ugrađenom nizu, ovo se radi pomoću operacije subscript ili indirektnog adresiranja;
  • poređenje dva niza za jednakost. Za ugrađeni tip koristite funkciju strcmp();
  • spajanje dva niza, dobivajući rezultat ili kao treći niz ili umjesto jednog od originalnih. Za ugrađeni tip koristi se funkcija strcat(), ali da biste dobili rezultat u novom redu, morate koristiti funkcije strcpy() i strcat() u nizu;
  • izračunavanje dužine niza. Možete saznati dužinu niza ugrađenog tipa koristeći strlen() funkciju;
  • mogućnost saznanja da li je niz prazan. Za ugrađene stringove, dva uslova moraju biti provjerena u tu svrhu: char str = 0; //... if (! str || ! *str) return;

Klasa stringova standardne biblioteke C++ implementira sve ove operacije (i mnogo više, kao što ćemo vidjeti u poglavlju 6). U ovom dijelu ćemo naučiti kako koristiti osnovne operacije ove klase.
Da biste koristili objekte klase string, morate uključiti odgovarajući fajl zaglavlja:

#include

Evo primjera niza iz prethodnog odjeljka, predstavljenog objektom tipa string i inicijaliziranog nizom znakova:

#include string st("Cijena boce vina\n");

Duljinu stringa vraća funkcija člana size() (dužina ne uključuje završni null karakter).

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

Drugi oblik definicije stringa specificira prazan niz:

String st2; // prazan red

Kako znamo da li je niz prazan? Naravno, možete uporediti njegovu dužinu sa 0:

if (! st.size()) // ispravno: prazno

Međutim, postoji i posebna metoda empty() koja vraća true za prazan niz i false za neprazan:

if (st.empty()) // ispravno: prazno

Treći oblik konstruktora inicijalizira objekat tipa string sa drugim objektom istog tipa:

String st3(st);

String st3 je inicijaliziran stringom st. Kako možemo osigurati da se ovi nizovi podudaraju? Koristimo operator poređenja (==):

If (st == st3) // inicijalizacija je uspjela

Kako kopirati jednu liniju u drugu? Sa uobičajenim operatorom dodjele:

st2 = st3; // kopiraj st3 u st2

Za spajanje nizova koristite operator sabiranja (+) ili operator zbrajanja-dodjela (+=). Neka su data dva reda:

String s1("zdravo, "); string s2("svijet\n");

Možemo dobiti treći niz koji se sastoji od konkatenacije prva dva, ovako:

String s3 = s1 + s2;

Ako želimo dodati s2 na kraj s1, moramo napisati:

S1 += s2;

Operacija sabiranja može konkatenirati objekte string klase ne samo među sobom, već i sa nizovima ugrađenog tipa. Možete prepisati gornji primjer tako da posebni znakovi i interpunkcija budu predstavljeni ugrađenim tipom, a značajne riječi su objekti stringa klase:

Const char *pc = ", "; string s1("zdravo"); string s2("svijet");
string s3 = s1 + pc + s2 + "\n";

Takvi izrazi funkcioniraju jer kompajler zna kako automatski pretvoriti objekte ugrađenog tipa u string objekte. Također je moguće jednostavno dodijeliti ugrađeni string objektu stringa:

String s1; const char *pc = "niz znakova"; s1=kom; // desno

Obrnuta transformacija, međutim, ne funkcionira. Pokušaj da se izvrši sljedeća inicijalizacija niza ugrađenog tipa će uzrokovati grešku kompilacije:

Char*str = s1; // greška kompilacije

Da biste izvršili ovu konverziju, morate eksplicitno pozvati funkciju člana sa pomalo čudnim imenom c_str():

Char *str = s1.c_str(); // skoro tačno

Funkcija c_str() vraća pokazivač na niz znakova koji sadrži string objekta string, kao što bi bio u ugrađenom tipu stringa.
Gornji primjer inicijalizacije char *str pokazivača još uvijek nije sasvim ispravan. c_str() vraća pokazivač na const niz kako bi spriječio da se sadržaj objekta direktno modificira preko ovog pokazivača, koji ima tip

Konstantni znak *

(O ključnoj riječi const ćemo govoriti u sljedećem odjeljku.) Ispravna inicijalizacija izgleda ovako:

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

Pojedinačnim znakovima string objekta, poput ugrađenog tipa, može se pristupiti korištenjem operacije indeksa. Na primjer, evo isječka koda koji zamjenjuje sve tačke podvlakama:

Stringstr("fa.disney.com"); int size = str.size(); za (int ix = 0; ix< size; ++ix) if (str[ ix ] == ".")
str[ix] = "_";

To je sve što smo sada htjeli reći o string klasi. U stvari, ova klasa ima mnogo više zanimljivih svojstava i mogućnosti. Recimo da je prethodni primjer također implementiran pozivanjem jedne funkcije replace():

Zamijeni(str.begin(), str.end(), ".", "_");

replace() je jedan od generičkih algoritama koje smo uveli u Odjeljku 2.8 i biće detaljno obrađen u Poglavlju 12. Ova funkcija iterira u rasponu od begin() do end(), koji vraća pokazivače na početak i kraj string, i zamjenjuje elemente , jednake njegovom trećem parametru, četvrtim.

Vježba 3.12

Potražite greške u sljedećim izjavama:

(a) char ch = "Dugačak i krivudavi put"; (b) int ival = (c) char *pc = (d) string 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;

Vježba 3.13

Objasnite razliku u ponašanju sljedećih naredbi petlje:

Dok (st++) ++cnt;
dok (*st++)
++cnt;

Vježba 3.14

Daju se dva semantički ekvivalentna programa. Prvi koristi ugrađeni tip stringa, drugi koristi string klasu:

// ***** Implementacija korištenjem C stringova ***** #include #include
int main()
{
int greške = 0;
const char *pc = "veoma dugačak literalni niz"; za (int ix = 0; ix< 1000000; ++ix)
{
int len ​​= strlen(pc);
char *pc2 = novi char[ len + 1 ];
strcpy(pc2, pc);
if (strcmp(pc2, pc))
++greške; delete pc2;
}
cout<< "C-строки: "
<< errors << " ошибок.\n";
) // ***** Implementacija korištenjem string klase ***** #include
#include
int main()
{
int greške = 0;
string str("veoma dugačak literalni niz"); za (int ix = 0; ix< 1000000; ++ix)
{
int len ​​= str.size();
string str2 = str;
ako (str != str2)
}
cout<< "класс string: "
<< errors << " ошибок.\n;
}

Šta rade ovi programi?
Ispostavilo se da je druga implementacija duplo brža od prve. Jeste li očekivali ovakav rezultat? Kako to objašnjavate?

Vježba 3.15

Postoji li nešto što biste mogli poboljšati ili dodati skupu operacija na string klasi u posljednjem odjeljku? Objasnite svoje prijedloge

3.5. const specificer

Uzmimo sljedeći primjer koda:

For(int indeks = 0; indeks< 512; ++index) ... ;

Postoje dva problema s korištenjem literala 512. Prvi je lakoća percepcije teksta programa. Zašto bi gornja granica varijable petlje bila tačno 512? Šta se krije iza ove vrijednosti? Deluje nasumično...
Drugi problem se tiče lakoće modifikacije i održavanja koda. Pretpostavimo da se program sastoji od 10.000 linija, a literalnih 512 se pojavljuje u 4% njih. Recimo da 80% vremena broj 512 treba promijeniti u 1024. Možete li zamisliti količinu posla i broj grešaka koje se mogu napraviti ispravljanjem pogrešne vrijednosti?
Oba ova problema se rešavaju u isto vreme: treba da kreiramo objekat sa vrednošću 512. Dajući mu smisleno ime, kao što je bufSize, učinićemo program mnogo razumljivijim: jasno je šta je tačno petlja. varijabla se upoređuje sa.

Indeks< bufSize

U ovom slučaju, promjena veličine bufSize ne zahtijeva gledanje 400 linija koda da bi se modificiralo 320 od njih. Koliko se smanjuje vjerovatnoća greške po cijenu dodavanja samo jednog objekta! Sada je vrijednost 512 lokalizovan.

bufSize = 512; // veličina ulaznog bafera // ... za (int indeks = 0; index< bufSize; ++index)
// ...

Ostaje jedan mali problem: varijabla bufSize ovdje je l-vrijednost koja se može slučajno promijeniti u programu, što će dovesti do greške koju je teško pronaći. Evo jedne od najčešćih grešaka - korištenje operatora dodjeljivanja (=) umjesto poređenja (==):

// nasumična promjena bufSize vrijednosti if (bufSize = 1) // ...

Kao rezultat izvršavanja ovog koda, vrijednost bufSize će postati jednaka 1, što može dovesti do potpuno nepredvidivog ponašanja programa. Greške ove vrste je obično vrlo teško otkriti jer jednostavno nisu vidljive.
Upotreba specifikacije const rješava ovaj problem. Deklarisanjem objekta kao

Const int bufSize = 512; // veličina ulaznog bafera

varijablu pretvaramo u konstantu sa vrijednošću 512, čija vrijednost se ne može mijenjati: takve pokušaje kompajler zaustavlja: nepravilna upotreba operatora dodjele umjesto poređenja, kao u gornjem primjeru, dovešće do greške u kompilaciji.

// greška: pokušaj dodijeliti vrijednost konstanti if (bufSize = 0) ...

Pošto se konstanti ne može dodijeliti vrijednost, ona mora biti inicijalizirana na mjestu svoje definicije. Definiranje konstante bez inicijalizacije također uzrokuje grešku kompilacije:

Const dupli pi; // greška: neinicijalizirana konstanta

Konstantna dupla minimalna plata = 9,60; // zar ne? greška?
duplo *ptr =

Treba li kompajler dozvoliti ovu dodjelu? Pošto je minWage konstanta, ne može joj se dodijeliti vrijednost. S druge strane, ništa nam ne brani da napišemo:

*ptr += 1,40; // promijenite minWage objekt!

Po pravilu, kompajler nije u mogućnosti da zaštiti od upotrebe pokazivača i neće moći signalizirati grešku ako se koriste na ovaj način. Ovo zahtijeva previše analize logike programa. Stoga kompajler jednostavno zabranjuje dodeljivanje adresa konstanti običnim pokazivačima.
Pa, lišeni smo mogućnosti da koristimo pokazivače na konstante? br. Da biste to učinili, postoje pokazivači deklarirani sa const specifikacijom:

Const double *cptr;

gdje je cptr pokazivač na objekt tipa const double. Suptilnost leži u činjenici da sam pokazivač nije konstanta, što znači da možemo promijeniti njegovu vrijednost. Na primjer:

Const double *pc = 0; const dupla min.plata = 9,60; // ispravno: ne mogu promijeniti minWage sa računarom
kom = duplo dval = 3,14; // ispravno: ne mogu promijeniti minWage sa računarom
// iako dval nije konstanta
pc = // ispravan dval = 3,14159; //desno
*kom = 3,14159; // greška

Adresa const objekta se dodeljuje samo pokazivaču na konstantu. Međutim, takvom pokazivaču se može dodijeliti i adresa obične varijable:

PC =

Konstantni pokazivač ne dozvoljava da se objekt kojem se adresira modificira indirektnim adresiranjem. Iako dval nije konstanta u gornjem primeru, kompajler neće dozvoliti da se dval promeni preko računara. (Opet, zato što nije u mogućnosti da odredi koju adresu pokazivač može imati u bilo kom trenutku u izvršavanju programa.)
U stvarnim programima, pokazivači na konstante se najčešće koriste kao formalni parametri funkcija. Njihova upotreba jamči da objekt proslijeđen funkciji kao stvarni argument neće biti modificiran od strane te funkcije. Na primjer:

// U stvarnim programima, pokazivači na konstante se najčešće // koriste kao formalni parametri funkcija int strcmp(const char *str1, const char *str2);

(O pokazivačima na konstante ćemo više govoriti u 7. poglavlju kada budemo govorili o funkcijama.)
Tu su i stalni pokazivači. (Obratite pažnju na razliku između konstantnog pokazivača i pokazivača na konstantu!). Konstantni pokazivač može adresirati i konstantu i varijablu. Na primjer:

Inter errNumb = 0; int *const currErr =

Ovdje je curErr konstantni pokazivač na nekonstantni objekt. To znači da mu ne možemo dodijeliti adresu drugog objekta, iako se sam objekt može mijenjati. Evo kako se curErr pokazivač može koristiti:

Radi nešto(); if (*curErr) (
errorHandler();
*curErr = 0; // ispravno: resetirajte vrijednost errNumb
}

Pokušaj dodjeljivanja vrijednosti konstantnom pokazivaču će uzrokovati grešku kompilacije:

CurErr = // greška

Konstantni pokazivač na konstantu je unija dva razmatrana slučaja.

Const dupli pi = 3,14159; const double *const pi_ptr = π

Ni vrijednost objekta na koji ukazuje pi_ptr niti vrijednost samog pokazivača ne mogu se promijeniti u programu.

Vježba 3.16

Objasnite značenje sljedećih pet definicija. Da li neko od njih nije u pravu?

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

Vježba 3.17

Koje su od sljedećih definicija tačne? Zašto?

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

Vježba 3.18

Koristeći definicije iz prethodne vježbe, identificirajte ispravne operatore dodjeljivanja. Objasni.

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

3.6. Referentni tip

Referentni tip, koji se ponekad naziva pseudonim, koristi se da se objektu da dodatno ime. Referenca vam omogućava da manipulišete objektom indirektno, baš kao što to radi pokazivač. Međutim, ova indirektna manipulacija ne zahtijeva posebnu sintaksu potrebnu za pokazivače. Obično se reference koriste kao formalni parametri funkcija. U ovom odeljku ćemo se osvrnuti na upotrebu objekata referentnog tipa samostalno.
Tip reference se označava specificiranjem operatora adrese (&) ispred imena varijable. Veza mora biti inicijalizirana. Na primjer:

Intival = 1024; // ispravno: refVal - referenca na ival int &refVal = ival; // greška: referenca mora biti inicijalizirana na int

Intival = 1024; // greška: refVal je tipa int, a ne int* int &refVal = int *pi = // ispravno: ptrVal je referenca na int pokazivač *&ptrVal2 = pi;

Jednom kada definirate referencu, ne možete je promijeniti da radi s drugim objektom (zbog čega referenca mora biti inicijalizirana na mjestu svoje definicije). U sljedećem primjeru, naredba za dodjelu ne mijenja vrijednost refVal, nova vrijednost se dodjeljuje varijabli ival, onoj koju refVal adresira.

int min_val = 0; // ival dobiva vrijednost min_val, // not refVal mijenja vrijednost u min_val refVal = min_val;

RefVal += 2; dodaje 2 ivalu, varijablu na koju upućuje refVal. Slično int ii = refVal; postavlja ii na trenutnu vrijednost ival, int *pi = inicijalizira pi sa adresom ival.

// dva objekta tipa int su definirana int ival = 1024, ival2 = 2048; // jedna referenca i jedan objekt definirani int &rval = ival, rval2 = ival2; // definiran jedan objekt, jedan pokazivač i jedna referenca
int inal3 = 1024, *pi = ival3, &ri = ival3; // dvije definirane reference int &rval3 = ival3, &rval4 = ival2;

Referenca konstante može se inicijalizirati objektom drugog tipa (ako, naravno, postoji mogućnost pretvaranja jednog tipa u drugi), kao i neadresiranom vrijednošću, kao što je literalna konstanta. Na primjer:

duplo dval = 3,14159; // istina samo za const reference
const int &ir = 1024;
const int &ir2 = dval;
const double &dr = dval + 1.0;

Ako nismo specificirali const specifikaciju, sve tri referentne definicije bi uzrokovale grešku kompilacije. Međutim, razlog zašto kompajler ne preskače takve definicije nije jasan. Pokušajmo to shvatiti.
Za literale, ovo je manje-više jasno: ne bismo trebali moći indirektno promijeniti vrijednost literala koristeći pokazivače ili reference. Što se tiče objekata drugog tipa, kompajler pretvara originalni objekat u neki pomoćni. Na primjer, ako napišemo:

Dvostruki dval = 1024; const int &ri = dval;

onda ga prevodilac konvertuje u nešto ovako:

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

Kada bismo mogli dodijeliti novu vrijednost ri referenci, zapravo bismo promijenili ne dval, već temp. Vrijednost dval bi ostala ista, što je programeru potpuno neočigledno. Stoga, kompajler zabranjuje takve radnje, a jedini način da se inicijalizira referenca sa objektom drugog tipa je da se deklarira kao const.
Evo još jednog primjera veze koju je teško razumjeti prvi put. Želimo definirati referencu na adresu const objekta, ali naša prva opcija uzrokuje grešku kompilacije:

Const int ival = 1024; // greška: potrebna je konstantna referenca
int *&pi_ref =

Pokušaj rješavanja problema dodavanjem specifikacije const također ne uspijeva:

Const int ival = 1024; // i dalje greška const int *&pi_ref =

Šta je razlog? Pažljivo čitajući definiciju, vidjet ćemo da je pi_ref referenca na konstantni pokazivač na objekt tipa int. I potreban nam je non-const pokazivač na const objekat, tako da će sljedeći unos biti ispravan:

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

Postoje dvije glavne razlike između reference i pokazivača. Prvo, referenca mora biti inicijalizirana na mjestu njene definicije. Drugo, svaka promjena u referenci ne transformira nju, već objekt na koji se odnosi. Pogledajmo primjere. Ako napišemo:

int*pi = 0;

inicijaliziramo pokazivač pi na nulu, što znači da pi ne pokazuje ni na jedan objekt. Istovremeno, rekord

const int &ri = 0;
znači nešto ovako:
int temp = 0;
const int &ri = temp;

Što se tiče operacije dodjeljivanja, u sljedećem primjeru:

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

varijabla ival na koju ukazuje pi ostaje nepromijenjena, a pi dobiva vrijednost adrese ival2. I pi i pi2 i dalje upućuju na isti objekat ival2.
Ako radimo sa linkovima:

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

// primjer korištenja referenci // Vrijednost se vraća u parametru next_value
bool get_next_value(int &next_value); // preopterećen Matrix operator+(const Matrix&, const Matrix&);

Intival; dok (get_next_value(ival)) ...

int &next_value = ival;

(Upotreba referenci kao formalnih parametara funkcija detaljnije je razmotrena u 7. poglavlju.)

Vježba 3.19

Ima li grešaka u ovim definicijama? Objasni. Kako biste ih popravili?

(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) const int &ival2 = 1; (j) const int &*prval2 =

Vježba 3.20

Da li je neki od sljedećih zadataka pogrešan (koristeći definicije iz prethodne vježbe)?

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

Vježba 3.21

Potražite greške u sljedećim uputama:

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

3.7. tip bool

Objekt tipa bool može uzeti jednu od dvije vrijednosti: true i false. Na primjer:

// string inicijalizacija string search_word = get_word(); // inicijalizacija pronađene varijable
bool pronađen = lažno; string next_word; dok (cin >> next_word)
ako (sljedeća_riječ == riječ za pretraživanje)
pronađeno = istina;
// ... // skraćenica: if (pronađeno == istina)
ako (pronađeno)
cout<< "ok, мы нашли слово\n";
else cout<< "нет, наше слово не встретилось.\n";

Iako je bool jedan od cjelobrojnih tipova, ne može se proglasiti potpisanim, nepotpisanim, kratkim ili dugim, tako da je gornja definicija pogrešna:

// greška short bool found = false;

Objekti tipa bool se implicitno pretvaraju u tip int. Vrijednost true postaje 1, a false postaje 0. Na primjer:

bool pronađen = lažno; int Broj pojavljivanja = 0; dok (/* mrmljam */)
{
found = look_for(/* nešto */); // pronađena vrijednost se pretvara u 0 ili 1
Broj pojavljivanja += pronađeno; )

Na isti način, vrijednosti cjelobrojnih tipova i pokazivača mogu se pretvoriti u bool vrijednosti. U ovom slučaju, 0 se tumači kao netačno, a sve ostalo kao istinito:

// vraća broj pojavljivanja extern int find(const string&); bool pronađen = lažno; if (found = find("rosebud")) // ispravno: pronađeno == true // vraća pokazivač na element
extern int* find(int value); if (found = find(1024)) // ispravno: pronađeno == istina

3.8. Enumerations

Često je potrebno definirati varijablu koja uzima vrijednosti iz određenog skupa. Recimo da se datoteka otvara na bilo koji od tri načina: za čitanje, za pisanje, za dodavanje.
Naravno, tri konstante se mogu definirati za označavanje ovih modova:

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

i koristite ove konstante:

bool open_file(string file_name, int open_mode); // ...
open_file("Phoenix_and_the_Crane", dodaj);

Ovo rješenje je prihvatljivo, ali nije sasvim prihvatljivo, jer ne možemo garantirati da je argument proslijeđen funkciji open_file() samo 1, 2 ili 3.
Upotreba nabrojanog tipa rješava ovaj problem. Kada pišemo:

Enum open_modes( input = 1, output, append );

definiramo novi tip open_modes. Važeće vrijednosti za objekt ovog tipa ograničene su na 1, 2 i 3, pri čemu svaka od navedenih vrijednosti ima mnemoničko ime. Možemo koristiti ime ovog novog tipa da definiramo i objekt tog tipa i tip formalnih parametara funkcije:

Void open_file(string file_name, open_modes om);

ulaz, izlaz i dodavanje su elementi nabrajanja. Skup elemenata nabrajanja specificira važeći skup vrijednosti za objekt datog tipa. Varijabla tipa open_modes (u našem primjeru) je inicijalizirana jednom od ovih vrijednosti, može joj se dodijeliti i bilo koja od njih. Na primjer:

Open_file("Feniks i ždral", dodatak);

Pokušaj dodjeljivanja vrijednosti varijabli ovog tipa koja se razlikuje od jednog od elemenata nabrajanja (ili je proslijediti kao parametar funkciji) će uzrokovati grešku kompilacije. Čak i ako pokušamo proslijediti cjelobrojnu vrijednost koja odgovara jednom od elemenata nabrajanja, i dalje ćemo dobiti grešku:

// greška: 1 nije član enuma open_modes open_file("Jonah", 1);

Postoji način da se definira varijabla tipa open_modes, dodijeli joj vrijednost jednog od elemenata nabrajanja i prenese je kao parametar funkciji:

open_modes om=input; // ...om = append; open_file("TailTell", om);

Međutim, nije moguće dobiti nazive takvih elemenata. Ako napišemo izlaznu naredbu:

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

još uvijek dobijamo:

Ovaj problem se rješava definiranjem niza stringova u kojem će element sa indeksom jednakim vrijednosti elementa enum sadržavati svoje ime. Sa takvim nizom možemo napisati:

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

Također, ne možete iterirati preko svih enum vrijednosti:

// nije podržan za (open_modes iter = input; iter != append; ++inter) // ...

Ključna riječ enum se koristi za definiranje nabrajanja, a nazivi elemenata su navedeni u vitičastim zagradama, odvojeni zarezima. Podrazumevano, prvi je 0, sljedeći je 1 i tako dalje. Operator dodjeljivanja može promijeniti ovo pravilo. U ovom slučaju, svaki sljedeći element bez eksplicitno specificirane vrijednosti bit će 1 veći od elementa prije njega na listi. U našem primjeru, eksplicitno smo specificirali vrijednost 1 za ulaz, a izlaz i dodavanje će biti 2 i 3. Evo još jednog primjera:

// oblik == 0, sfera == 1, cilindar == 2, poligon == 3 enum Forms( share, spere, cylinder, poligon );

Cjelobrojne vrijednosti koje odgovaraju različitim elementima iste enumeracije ne moraju biti različite. Na primjer:

// point2d == 2, point2w == 3, point3d == 3, point3w == 4 enum Points (point2d=2, point2w, point3d=3, point3w=4);

Objekt čiji je tip enum može se definirati, koristiti u izrazima i proslijediti funkciji kao argument. Takav objekat se samo inicijalizira vrijednošću jednog od elemenata nabrajanja i samo mu se ta vrijednost dodjeljuje, bilo eksplicitno ili kao vrijednost drugog objekta istog tipa. Ne mogu mu se dodijeliti čak ni cjelobrojne vrijednosti koje odgovaraju važećim elementima nabrajanja:

Void mumble() ( Points pt3d = point3d; // ispravno: pt2d == 3 // greška: pt3w je inicijaliziran da upiše int Points pt3w = 3; // greška: poligon nije u Points enum pt3w = poligon; // ispravno : oba objekta tipa Points pt3w = pt3d; )

Međutim, u aritmetičkim izrazima, enum se može automatski pretvoriti u tip int. Na primjer:

const int array_size = 1024; // ispravno: pt2w se pretvara u int
int chunk_size = array_size * pt2w;

3.9. Upišite "niz"

Već smo se dotakli nizova u Odjeljku 2.1. Niz je skup elemenata istog tipa, kojima se pristupa preko indeksa - rednog broja elementa u nizu. Na primjer:

Intival;

definira ival kao varijablu tipa int i instrukciju

int ia[ 10 ];

specificira niz od deset int objekata. Za svaki od ovih objekata, ili elementi niza, može se pristupiti pomoću operacije preuzimanja indeksa:

Ival = ia[2];

dodeljuje promenljivoj ival vrednost elementa niza ia sa indeksom 2. Slično

Ia[ 7 ] = ival;

postavlja element sa indeksom 7 na ival.

Definicija niza se sastoji od specifikacije tipa, imena niza i veličine. Veličina određuje broj elemenata niza (najmanje 1) i zatvorena je u uglaste zagrade. Veličina niza mora biti poznata već u vrijeme kompajliranja i stoga mora biti konstantan izraz, iako ne nužno literal. Evo primjera ispravnih i netačnih definicija niza:

extern int get_size(); // konstante buf_size i max_files
const int buf_size = 512, max_files = 20;
intstaff_size = 27; // ispravno: konstantni char input_buffer[ buf_size]; // ispravno: konstantni izraz: 20 - 3 char *fileTable[ max_files-3 ]; // greška: nisu konstantne duple plate[ staff_size ]; // greška: nije konstantan izraz int test_scores[ get_size() ];

Objekti buf_size i max_files su konstante, tako da su definicije polja input_buffer i fileTable ispravne. Ali staff_size je varijabla (iako je inicijalizovana na konstantu 27), tako da su plate nezakonite. (Kompajler ne može pronaći vrijednost varijable staff_size u vrijeme kada je niz plata definiran.)
Izraz max_files-3 može se procijeniti u vrijeme kompajliranja, tako da je definicija polja fileTable sintaktički ispravna.
Numerisanje elemenata počinje od 0, tako da za niz od 10 elemenata, ispravan raspon indeksa nije 1 - 10, već 0 - 9. Evo primjera iteracije preko svih elemenata niza:

int main() ( const int array_size = 10; int ia[ array_size ]; for (int ix = 0; ix< array_size; ++ ix)
ia[ ix ] = ix;
}

Kada definirate niz, možete ga eksplicitno inicijalizirati navođenjem vrijednosti njegovih elemenata u vitičastim zagradama, odvojenih zarezima:

const int array_size = 3; int ia[veličina_niza] = (0, 1, 2);

Ako eksplicitno navedemo listu vrijednosti, onda ne možemo odrediti veličinu niza: kompajler će sam izračunati broj elemenata:

// niz veličine 3 int ia = ( 0, 1, 2 );

Kada su i veličina i lista vrijednosti eksplicitno specificirane, postoje tri opcije. Kada se veličina i broj vrijednosti poklapaju, sve je očigledno. Ako je lista vrijednosti kraća od navedene veličine, preostali elementi niza se inicijaliziraju na nulu. Ako ima više vrijednosti na listi, kompajler prikazuje poruku o grešci:

// ia ==> ( 0, 1, 2, 0, 0 ) const int array_size = 5; int ia[veličina_niza] = (0, 1, 2);

Niz znakova se može inicijalizirati ne samo listom vrijednosti znakova u vitičastim zagradama, već i literalom niza. Međutim, postoje neke razlike između ovih metoda. Recimo

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

Dimenzija niza ca1 je 3, dimenzija niza ca2 je 4 (završni null karakter se uzima u obzir u literalima stringova). Sljedeća definicija će uzrokovati grešku kompilacije:

// greška: string "Daniel" se sastoji od 7 elemenata const char ch3[ 6 ] = "Daniel";

Nizu se ne može dodijeliti vrijednost drugog niza, a inicijalizacija jednog niza drugim je također nezakonita. Također, nije dozvoljeno koristiti niz referenci. Evo primjera ispravne i netačne upotrebe nizova:

const int array_size = 3; int ix, jx, kx; // ispravno: niz pokazivača tipa int* int *iar = ( &ix, &jx, &kx); // greška: referentni nizovi su nevažeći int &iar = ( ix, jx, kx); int main()
{
int ia3( array_size ]; // ispravno
// greška: ugrađeni nizovi se ne mogu kopirati
ia3 = ia;
return 0;
}

Da biste kopirali jedan niz u drugi, ovo morate učiniti za svaki element pojedinačno:

const int array_size = 7; int ia1 = ( 0, 1, 2, 3, 4, 5, 6 ); int main() (
int ia3[veličina_niza]; za (int ix = 0; ix< array_size; ++ix)
ia2[ ix ] = ia1[ ix ]; return 0;
}

Indeks niza može biti bilo koji izraz koji daje rezultat cjelobrojnog tipa. Na primjer:

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

Naglašavamo da jezik C++ ne pruža kontrolu nad indeksima niza, ni u vrijeme kompajliranja ni u vrijeme izvođenja. Sam programer mora osigurati da indeks ne prelazi granice niza. Greške u indeksu su prilično česte. Nažalost, nije tako teško pronaći primjere programa koji se kompajliraju i čak rade, ali ipak sadrže fatalne greške koje prije ili kasnije dovedu do pada.

Vježba 3.22

Koje od sljedećih definicija niza sadrže greške? Objasni.

(a) int ia[buf_size]; (d) int ia[ 2 * 7 - 14 ] (b) int ia[ get_size() ]; (e) char st[ 11 ] = "osnovno"; (c) int ia[ 4 * 7 - 14 ];

Vježba 3.23

Sljedeći isječak koda bi trebao inicijalizirati svaki element niza vrijednošću indeksa. Pronađite greške koje ste napravili:

int main() ( const int array_size = 10; int ia[ array_size ]; for (int ix = 1; ix<= array_size; ++ix)
ia[ia] = ix; // ...
}

3.9.1. Višedimenzionalni nizovi

U C++ je moguće koristiti višedimenzionalne nizove, koji moraju biti deklarisani sa desnom granicom svake dimenzije u posebnim uglastim zagradama. Evo definicije dvodimenzionalnog niza:

Int ia[ 4 ][ 3 ];

Prva vrijednost (4) specificira broj redova, druga (3) broj kolona. Ia objekat je definisan kao niz od četiri niza sa po tri elementa. Višedimenzionalni nizovi se također mogu inicijalizirati:

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

Unutrašnje vitičaste zagrade koje razlažu listu vrijednosti u retke su opcione i koriste se u pravilu za čitljivost koda. Sljedeća inicijalizacija je potpuno ista kao i prethodni primjer, iako manje jasna:

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

Sljedeća definicija inicijalizira samo prve elemente svakog reda. Preostali elementi će biti nula:

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

Ako izostavite unutarnje vitičaste zagrade, rezultat je potpuno drugačiji. Sva tri elementa prvog reda i prvi element drugog će dobiti navedenu vrijednost, a ostali će biti implicitno inicijalizirani na 0.

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

Kada pristupate elementima višedimenzionalnog niza, morate koristiti indekse za svaku dimenziju (oni su zatvoreni u uglastim zagradama). Ovako izgleda inicijalizacija dvodimenzionalnog niza pomoću ugniježđenih petlji:

int main() ( const int rowSize = 4; const int colSize = 3; int ia[ rowSize ][ colSize ]; for (int = 0; i< rowSize; ++i)
za (int j = 0; j< colSize; ++j)
ia[ i ][ j ] = i + j j;
}

Dizajn

Ia[ 1, 2 ]

važi u smislu C++ sintakse, ali ne znači ono što bi neiskusni programer očekivao. Ovo nikako nije deklaracija dvodimenzionalnog niza 1 po 2. Agregat u uglastim zagradama je lista izraza razdvojenih zarezima koja će rezultirati konačnom vrijednošću 2 (pogledajte operator “zarez” u odjeljku 4.2 ). Prema tome, deklaracija ia je ekvivalentna ia. Ovo je još jedna prilika da napravite grešku.

3.9.2. Odnos između nizova i pokazivača

Ako imamo definiciju niza:

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

šta onda znači jednostavna indikacija njegovog imena u programu?

Upotreba identifikatora niza u programu je ekvivalentna specificiranju adrese njegovog prvog elementa:

Slično, postoje dva načina za pristup vrijednosti prvog elementa niza:

// oba izraza vraćaju prvi element *ia; ia;

Da uzmemo adresu drugog elementa niza, napisali bismo:

Kao što smo ranije spomenuli, izraz

također daje adresu drugog elementa niza. Shodno tome, njegovu vrijednost nam daju sljedeće dvije metode:

*(ia+1); ia;

Obratite pažnju na razliku u izrazima:

*ia+1 i *(ia+1);

Operacija dereferenciranja ima višu prioritet nego operacija sabiranja (pogledajte odjeljak 4.13 za više informacija o prioritetu operatora). Stoga, prvi izraz prvo dereferencira varijablu ia i dobija prvi element niza, a zatim mu dodaje 1. Drugi izraz isporučuje vrijednost drugog elementa.

Možete iterirati niz pomoću indeksa, kao što smo radili u prethodnom odeljku, ili pomoću pokazivača. Na primjer:

#include int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); int *pbegin = ia; int *pend = ia + 9; dok (pbegin != pend) ( cout<< *pbegin <<; ++pbegin; } }

Pbegin pokazivač je inicijaliziran adresom prvog elementa niza. Svaka iteracija kroz petlju povećava ovaj pokazivač za 1, što znači da se pomiče na sljedeći element. Kako znati gdje odsjesti? U našem primjeru definirali smo drugi pokazivač čekanja i inicijalizirali ga adresom koja slijedi nakon posljednjeg elementa ia niza. Čim vrijednost pbegin postane jednaka pend, znamo da je niz gotov. Prepišimo ovaj program tako da početak i kraj niza budu proslijeđeni kao parametri nekoj generičkoj funkciji koja može ispisati niz bilo koje veličine:

#include void ia_print(int *pbegin, int *pend) (
dok (pbegin != pend) (
cout<< *pbegin << " ";
++pbegin;
}
) int main()
{
int ia = (0, 1, 1, 2, 3, 5, 8, 13, 21);
ia_print(ia, ia + 9);
}

Naša funkcija je postala univerzalnija, međutim, može raditi samo sa int nizovima. Postoji način da se ukloni i ovo ograničenje: pretvorite datu funkciju u šablon (šabloni su ukratko predstavljeni u odjeljku 2.5):

#include šablon void print(elemType *pbegin, elemType *pend) ( while (pbegin != pend) ( cout<< *pbegin << " "; ++pbegin; } }

Sada možemo pozvati našu funkciju print() da ispišemo nizove bilo koje vrste:

int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); double da = ( 3.14, 6.28, 12.56, 25.12 ); string sa = ( "prasić", " eyore", "pooh" ); print(ia, ia+9);
print(da,da+4);
print(sa, sa+3);
}

Pisali smo generalizovano funkcija. Standardna biblioteka pruža skup generičkih algoritama (ovo smo već spomenuli u Odjeljku 3.4) implementiranih na ovaj način. Parametri takvih funkcija su pokazivači na početak i kraj niza s kojima izvode određene radnje. Na primjer, evo kako izgledaju pozivi na generalizirani algoritam sortiranja:

#include int main() ( int ia = (107, 28, 3, 47, 104, 76); string sa = ( "prase", "eeyore", "pu"); sort(ia, ia+6);
sortiraj(sa, sa+3);
};

(O generalizovanim algoritmima ćemo detaljno raspravljati u Poglavlju 12; primjeri njihove upotrebe će biti dati u Dodatku.)
C++ standardna biblioteka sadrži skup klasa koje obuhvataju upotrebu kontejnera i pokazivača. (O tome je bilo reči u Odeljku 2.8.) U sledećem odeljku ćemo pogledati standardni vektor tipa kontejnera, koji je objektno orijentisana implementacija niza.

3.10. vektorska klasa

Upotreba vektorske klase (pogledajte odjeljak 2.8) je alternativa korištenju ugrađenih nizova. Ova klasa pruža mnogo više funkcija, pa je njena upotreba poželjnija. Međutim, postoje situacije kada se nizovi ugrađenog tipa ne mogu izostaviti. Jedna od ovih situacija je obrada parametara komandne linije prosleđenih programu, o čemu ćemo raspravljati u odeljku 7.8. Vektorska klasa, kao i klasa string, dio je C++ standardne biblioteke.
Da biste koristili vektor, morate uključiti datoteku zaglavlja:

#include

Postoje dva potpuno različita pristupa korištenju vektora, nazovimo ih idiom niza i STL idiom. U prvom slučaju, objekt klase vektor se koristi na isti način kao niz ugrađenog tipa. Vektor date dimenzije je definiran:

Vector< int >ivec(10);

što je isto kao i definiranje niza ugrađenog tipa:

int ia[ 10 ];

Za pristup pojedinačnim elementima vektora koristi se operacija uzimanja indeksa:

Void simp1e_examp1e() ( const int e1em_size = 10; vektor< int >ivec(e1em_size); int ia[ e1em_size]; za (int ix = 0; ix< e1em_size; ++ix)
ia[ ix ] = ivec[ ix ]; // ...
}

Veličinu vektora možemo saznati pomoću funkcije size() i provjeriti je li vektor prazan pomoću funkcije empty(). Na primjer:

Void print_vector(vektor ivec) (if (ivec.empty()) povratak; for (int ix=0; ix< ivec.size(); ++ix)
cout<< ivec[ ix ] << " ";
}

Vektorski elementi se inicijaliziraju sa zadanim vrijednostima. Za numeričke tipove i pokazivače, ova vrijednost je 0. Ako objekti klase djeluju kao elementi, tada je inicijator za njih specificiran od strane defaultnog konstruktora (pogledajte odjeljak 2.3). Međutim, inicijator se također može eksplicitno specificirati koristeći formu:

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

Svih deset elemenata vektora će biti jednako -1.
Niz ugrađenog tipa može se eksplicitno inicijalizirati listom:

int ia[ 6 ] = ( -2, -1, 0, 1, 2, 1024);

Za objekat vektorske klase slična akcija nije moguća. Međutim, takav objekat se može inicijalizirati nizom ugrađenog tipa:

// 6 elemenata ia se kopira u ivec vektor< int >ivec(ia, ia+6);

Dva pokazivača se prosljeđuju konstruktoru vektora ivec - pokazivač na početak niza ia i na element koji slijedi nakon posljednjeg. Kao listu početnih vrijednosti, dozvoljeno je navesti ne cijeli niz, već dio njegovog raspona:

// 3 elementa se kopiraju: ia, ia, ia vektor< int >ivec(&ia[2], &ia[5]);

Druga razlika između vektora i niza ugrađenog tipa je mogućnost inicijalizacije jednog objekta tipa vektor sa drugim i korištenje operatora dodjeljivanja za kopiranje objekata. Na primjer:

Vector< string >svec; void init_and_assign() ( // jedan vektor je inicijaliziran drugim vektorom< string >korisnička imena(svec); // ... // jedan vektor se kopira u drugi
svec = korisnička imena;
}

Govoreći o STL idiomu, mislimo na sasvim drugačiji pristup korištenju vektora. Umjesto da odmah postavimo pravu veličinu, definiramo prazan vektor:

Vector< string >tekst;

Zatim mu dodajemo elemente koristeći različite funkcije. Na primjer, funkcija push_back() umeće element na kraj vektora. Evo dijela koda koji čita niz linija iz standardnog unosa i dodaje ih vektoru:

stringword; while (cin >> riječ) (text.push_back(word); // ... )

Iako možemo koristiti operaciju indeksa preuzimanja za ponavljanje elemenata vektora:

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

tipičnije unutar ovog idioma bi bilo korištenje iteratora:

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

Iterator je standardna bibliotečka klasa koja je zapravo pokazivač na element niza.
Izraz

dereferencira iterator i daje sam vektorski element. Uputstvo

Prebacuje pokazivač na sljedeći element. Nema potrebe miješati ova dva pristupa. Slijedeći STL idiom kada definirate prazan vektor:

Vector ivec;

Bila bi greška napisati:

Još uvijek nemamo niti jedan element ivec vektora; broj elemenata se saznaje pomoću funkcije size().

Možete napraviti i suprotnu grešku. Ako smo definirali vektor neke veličine, na primjer:

Vector ia(10);

Zatim umetanje elemenata povećava njegovu veličinu dodavanjem novih elemenata postojećim. Iako se ovo čini očiglednim, ipak bi programer početnik mogao napisati:

Const int veličina = 7; int ia[ size] = (0, 1, 1, 2, 3, 5, 8); vektor< int >ivec(veličina); za (int ix = 0; ix< size; ++ix) ivec.push_back(ia[ ix ]);

To je značilo inicijalizaciju ivec vektora sa vrijednostima ia elemenata, umjesto čega smo dobili ivec vektor veličine 14.
Prateći STL idiom, ne možete samo dodati, već i ukloniti elemente vektora. (Sve ćemo ovo detaljno i sa primjerima obraditi u 6. poglavlju.)

Vježba 3.24

Ima li grešaka u sljedećim definicijama?
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, string("null"));

Vježba 3.25

Implementirajte sljedeću funkciju:
bool is_equal(const int*ia, int ia_size,
const vektor &ivec);
Is_equal() funkcija uspoređuje dva kontejnera element po element. U slučaju različitih veličina kontejnera, „rep“ dužeg se ne uzima u obzir. Jasno je da ako su svi upoređeni elementi jednaki, funkcija vraća true, a ako se barem jedan razlikuje, netočno. Koristite iterator za ponavljanje elemenata. Napišite main() funkciju koja poziva is_equal().

3.11. složena klasa

Klasa kompleksnih brojeva je još jedna klasa iz standardne biblioteke. Kao i obično, da biste ga koristili, morate uključiti datoteku zaglavlja:

#include

Kompleksni broj ima dva dijela - realan i imaginarni. Imaginarni dio je kvadratni korijen negativnog broja. Kompleksni broj se obično piše kao

gdje je 2 pravi dio, a 3i imaginarni dio. Evo primjera složenih definicija objekata:

// čisti imaginarni broj: 0 + 7-i kompleks< double >purei(0, 7); // imaginarni dio je 0:3 + Oi kompleks< float >rea1_num(3); // i realni i imaginarni dijelovi su 0:0 + 0-i kompleks< long double >nula; // inicijalizacija jednog kompleksnog broja drugim kompleksom< double >purei2(purei);

Budući da je kompleks, poput vektora, predložak, možemo ga instancirati pomoću float, double i long double, kao u primjerima iznad. Također možete definirati niz složenih elemenata:

Kompleks< double >konjugirati [ 2 ] = ( kompleks< double >(2, 3), kompleks< double >(2, -3) };

Kompleks< double >*ptr = složeno< double >&ref = *ptr;

3.12. typedef direktiva

Direktiva typedef vam omogućava da navedete sinonim za ugrađeni ili korisnički definiran tip podataka. Na primjer:

Typedef dvostruke plaće; typedef vektor vec_int; typedef vec_int test_scores; typedef bool in_attendance; typedef int *Pint;

Imena definisana direktivom typedef mogu se koristiti na isti način kao i specificatori tipa:

// duplo po satu, sedmično; plate po satu, sedmično; // vektor vecl(10);
vec_int vecl(10); // vektor test0(c1ass_size); const int c1ass_size = 34; test_rezultati test0(c1ass_size); // vektor< bool >prisustvo; vektor< in_attendance >prisustvo (c1ass_size); // int *tablica[ 10 ]; Pint stol [ 10 ];

Ova direktiva počinje ključnom riječi typedef koju slijedi specificator tipa i završava identifikatorom koji postaje pseudonim za navedeni tip.
Za koja su imena definirana s typedef direktivom? Koristeći mnemonička imena za tipove podataka, možete učiniti svoj program lakšim za razumijevanje. Također je uobičajeno koristiti takva imena za složene, inače teško razumljive složene tipove (pogledajte primjer u odjeljku 3.14), da se deklariraju pokazivači na funkcije i funkcije člana klase (pogledajte odjeljak 13.6).
Slijedi primjer pitanja na koje gotovo svi daju pogrešan odgovor. Greška je uzrokovana nerazumijevanjem typedef direktive kao jednostavne tekstualne makro zamjene. Definicija je data:

Typedef char *cstring;

Koji je tip cstr varijable u sljedećoj deklaraciji:

extern const cstring cstr;

Odgovor koji se čini očiglednim je:

Konstantni znak *cstr

Međutim, to nije tačno. Specifikator const se odnosi na cstr, tako da je tačan odgovor konst pokazivač na char:

Char *const cstr;

3.13. volatile specificer

Objekt se deklarira kao promjenjiv (nestabilan, asinhrono promjenjiv) ako se njegova vrijednost može promijeniti a da kompajler to ne primijeti, kao što je varijabla koju ažurira sistemski sat. Ovaj specificator govori kompajleru da ne optimizira kod za rad sa datim objektom.
Specifikator volatile se koristi kao const specificer:

Volatile int disp1ay_register; volatile Task *curr_task; volatile int ixa[ max_size ]; volatile Screen bitmap_buf;

display_register je nestabilan objekat tipa int. curr_task je pokazivač na nestabilni Task objekat. ixa je nestabilan niz cijelih brojeva, a svaki element takvog niza se smatra nestabilnim. bitmap_buf je promjenjiv objekat klase Screen, svaki od njegovih članova podataka se također smatra promjenjivim.
Jedina svrha upotrebe volatile specifikacija je da kaže kompajleru da ne može odrediti ko i kako može promijeniti vrijednost datog objekta. Stoga, kompajler ne bi trebao optimizirati kod koji koristi ovaj objekt.

3.14. klasa u paru

Klasa para standardne biblioteke C++ omogućava nam da definišemo par vrednosti sa jednim objektom ako postoji bilo kakav semantički odnos između njih. Ove vrijednosti mogu biti iste ili različite vrste. Da biste koristili ovu klasu, morate uključiti datoteku zaglavlja:

#include

Na primjer, instrukcija

Par< string, string >author("James", "Joyce");

kreira autorski objekat tipa par, koji se sastoji od dvije vrijednosti niza.
Pojedinačni dijelovi para mogu se dohvatiti pomoću prvog i drugog člana:

String firstBook; if (Joyce.first == "James" &&
Joyce.second == "Joyce")
prva knjiga = "Stephen Hero";

Ako trebate definirati nekoliko objekata istog tipa ove klase, zgodno je koristiti typedef direktivu:

Typedef par< string, string >Autori; Autori proust("marcel", "proust"); Autori joyce("James", "Joyce"); Autori musil("robert", "musi1");

Evo još jednog primjera korištenja para. Prva vrijednost sadrži ime nekog objekta, druga je pokazivač na element tablice koji odgovara ovom objektu.

Class EntrySlot; extern EntrySlot* 1ook_up(string); typedef par< string, EntrySlot* >SymbolEntry; SymbolEntry current_entry("autor", 1ook_up("autor"));
// ... if (EntrySlot *it = 1ook_up("editor")) (
current_entry.first = "urednik";
current_entry.second = to;
}

(Vratit ćemo se na raspravu o klasi pair u poglavlju 6 o tipovima kontejnera i generičkim algoritmima u poglavlju 12.)

3.15. Tipovi klasa

Mehanizam klase vam omogućava da kreirate nove tipove podataka; uveo je nizove, vektore, komplekse i parove o kojima se govorilo gore. U poglavlju 2 govorili smo o konceptima i mehanizmima koji podržavaju objektno i objektno orijentisani pristup, koristeći implementaciju klase Array kao primjer. Ovdje ćemo, na osnovu objektnog pristupa, kreirati jednostavnu String klasu, čija implementacija će pomoći da se razumije, posebno, preopterećenje operatora - o tome smo govorili u odjeljku 2.3. (Nastava je detaljno razmotrena u poglavljima 13, 14 i 15). Dali smo kratak opis klase kako bismo dali zanimljivije primjere. Čitalac koji tek počinje da uči C++ može preskočiti ovaj odeljak i sačekati sistematičniji opis klasa u kasnijim poglavljima.)
Naša String klasa mora podržavati inicijalizaciju sa objektom klase String, string literalom i ugrađenim tipom stringa, kao i operaciju dodjeljivanja vrijednosti ovih tipova. Za ovo koristimo konstruktore klasa i preopterećeni operator dodjeljivanja. Pristup pojedinačnim znakovima stringa će biti implementiran kao preopterećeni operator preuzimanja indeksa. Osim toga, potrebna nam je: funkcija size() da dobijemo informacije o dužini stringa; operacija poređenja objekata tipa String i String objekta sa nizom ugrađenog tipa; kao i I/O operacije našeg objekta. Konačno, implementiramo mogućnost pristupa internom predstavljanju našeg stringa kao stringa ugrađenog tipa.
Definicija klase počinje ključnom riječi class nakon koje slijedi identifikator, ime klase ili tipa. Uopšteno govoreći, klasa se sastoji od sekcija kojima prethode riječi public (otvoreno) i privatno (zatvoreno). Otvoreni odjeljak obično sadrži skup operacija koje klasa podržava, a nazivaju se metode ili funkcije člana klase. Ove funkcije člana definiraju javno sučelje klase, drugim riječima, skup akcija koje se mogu izvesti na objektima te klase. Privatni odjeljak obično uključuje članove podataka koji pružaju internu implementaciju. U našem slučaju, interni članovi uključuju _string - pokazivač na char, kao i _size tipa int. _size će pohraniti informacije o dužini stringa, a _string je dinamički dodijeljeni niz znakova. Evo kako izgleda definicija klase:

#include class String; istream& operator>>(istream&, String&);
stream & operator<<(ostream&, const String&); class String {
javno:
// skup konstruktora
// za automatsku inicijalizaciju
// Stringstrl; // string()
// String str2("literal"); // String(const char*);
// String str3(str2); // String(const String&); string();
string(const char*);
String(const String&); // destructor
~string(); // operatori dodjele
// strl = str2
// str3 = "string literal" String& operator=(const String&);
String& operator=(const char*); // operatori jednakosti
// strl == str2;
// str3 == "literal stringa"; bool operator==(const String&);
bool operator==(const char*); // preopterećenje operatora pristupa indeksom
// strl[ 0 ] = str2[ 0 ]; char& operator(int); // pristup članovima klase
int size() (vrati _size;)
char* c_str() (vrati _string;) privatno:
int_size;
char*_string;
}

String klasa ima tri konstruktora. Kao što je objašnjeno u Odjeljku 2.3, mehanizam preopterećenja dozvoljava definiranje više implementacija funkcija s istim imenom, sve dok se sve razlikuju po broju i/ili tipovima svojih parametara. Prvi konstruktor

To je zadani konstruktor jer ne zahtijeva eksplicitnu početnu vrijednost. Kada pišemo:

Za str1 takav konstruktor se zove.
Dva preostala konstruktora imaju po jedan parametar. Da, za

String str2("niz znakova");

Poziva se konstruktor

string(const char*);

string str3(str2);

Konstruktor

String(const String&);

Tip pozvanog konstruktora određen je tipom stvarnog argumenta. Posljednji konstruktor, String(const String&), naziva se konstruktor kopiranja jer inicijalizira objekt kopijom drugog objekta.
ako napišete:

stringstr4(1024);

Ovo će uzrokovati grešku kompilacije jer ne postoji konstruktor s parametrom tipa int.
Preopterećena deklaracija operatora ima sljedeći format:

return_type operator op(parameter_list);

Gdje je operator ključna riječ, a op je jedan od unaprijed definiranih operatora: +, =, ==, itd. (Za preciznu definiciju sintakse, pogledajte Poglavlje 15.) Evo deklaracije preopterećenog subscript operatora:

Char& operator(int);

Ovaj operator ima jedan parametar tipa int i vraća referencu na char. Preopterećeni operator može sam biti preopterećen ako se liste parametara pojedinačnih instancija razlikuju. Za našu klasu String, kreirat ćemo dva različita operatora dodjele i jednakosti svaki.
Da biste pozvali funkciju člana, koristite operatore pristupa članu tačka (.) ili strelica (->). Recimo da imamo deklaracije objekata tipa String:

String object("Danny");
String *ptr = novi String("Anna");
Stringarray;
Evo kako izgleda poziv funkcije size() za ove objekte:
vektor veličine(3);

// pristup članu za objekte (.); // objekti su 5 veličina[ 0 ] = object.size(); // članski pristup za pokazivače (->)
// ptr ima veličinu 4
sizes[ 1 ] = ptr->size(); // pristup članu (.)
// niz ima veličinu 0
sizes[ 2 ] = array.size();

Vraća 5, 4 i 0 redom.
Preopterećeni operatori se primjenjuju na objekt na isti način kao i normalni operatori:

Ime niza("Yadie"); Stringname2("Yodie"); // bool operator==(const String&)
if (ime == ime2)
povratak;
ostalo
// String& operator=(const String&)
name=name2;

Deklaracija funkcije člana mora biti unutar definicije klase, a definicija funkcije može biti unutar ili izvan definicije klase. (I funkcije size() i c_str() su definirane unutar klase.) Ako je funkcija definirana izvan klase, tada moramo odrediti, između ostalog, kojoj klasi pripada. U ovom slučaju, definicija funkcije je smještena u izvorni fajl, recimo String.C, a definicija same klase je smještena u datoteku zaglavlja (String.h u našem primjeru), koja bi trebala biti uključena u izvornu datoteku:

// sadržaj izvorne datoteke: String.C // uključujući definiciju String klase
#include "String.h" // uključuje definiciju funkcije strcmp().
#include
bool // tip povratka
String:: // klasa kojoj funkcija pripada
operator== // ime funkcije: operator jednakosti
(const String &rhs) // lista parametara
{
if (_size != rhs._size)
return false;
vratiti strcmp(_stringq, rhs._string) ?
false: true;
}

Podsjetimo da je strcmp() funkcija standardne biblioteke C. Ona upoređuje dva ugrađena niza tipa, vraćajući 0 ako su stringovi jednaki i različiti od nule ako nisu. Uslovni operator (?:) testira vrijednost prije upitnika. Ako je istina, vraća se vrijednost izraza lijevo od dvotočka, u suprotnom je desno od dvotočka. U našem primjeru, vrijednost izraza je lažna ako je strcmp() vratila vrijednost koja nije nulta, a tačna ako je null. (Uslovni operator je pokriven u Odjeljku 4.7.)
Operacija poređenja se koristi prilično često, funkcija koja je implementira pokazala se malom, pa je korisno ovu funkciju proglasiti ugrađenom (inline). Kompajler zamjenjuje tekst funkcije umjesto da je poziva, tako da se ne troši vrijeme na takav poziv. (Ugrađene funkcije su razmatrane u Odjeljku 7.6.) Funkcija člana definirana unutar klase je ugrađena po defaultu. Ako je definiran izvan klase, da biste ga deklarirali inline, trebate koristiti ključnu riječ inline:

Inline bool String::operator==(const String &rhs) ( // isto)

Definicija inline funkcije mora biti u datoteci zaglavlja koja sadrži definiciju klase. Redefiniranjem operatora == kao ugrađenog operatora, moramo premjestiti sam tekst funkcije iz datoteke String.C u datoteku String.h.
Slijedi implementacija operacije poređenja String objekta sa ugrađenim nizom tipa:

Inline bool String::operator==(const char *s) (vrati strcmp(_string, s) ? false: true; )

Ime konstruktora je isto kao i ime klase. Ne smatra se da vraća vrijednost, tako da ne morate specificirati povratnu vrijednost ni u definiciji ni u njenom tijelu. Može postojati više konstruktora. Kao i svaka druga funkcija, mogu se deklarisati kao inline.

#include // zadani konstruktor inline String::String()
{
_size=0;
_string=0;
) inline String::String(const char *str) ( if (! str) ( _size = 0; _string = 0; ) else ( _size = str1en(str); strcpy(_string, str); ) // konstruktor kopiranja
inline String::String(const String &rhs)
{
veličina = rhs._size;
if(!rhs._string)
_string=0;
drugo(
_string = novi znak[_veličina + 1];
} }

Pošto smo dinamički dodijelili memoriju s novim operatorom, moramo je osloboditi pozivom za brisanje kada nam više ne treba String objekt. U tu svrhu služi još jedna posebna funkcija člana, destruktor, koji se automatski poziva za objekt u trenutku kada taj objekt prestane postojati. (Pogledajte Poglavlje 7 o životnom vijeku objekta.) Ime destruktora se formira od znaka tilde (~) i imena klase. Evo definicije destruktora klase String. Ovo je mjesto gdje pozivamo operaciju brisanja da oslobodimo memoriju dodijeljenu u konstruktoru:

Inline string: :~String() ( izbriši _string; )

Oba preopterećena operatora dodjeljivanja koriste posebnu ključnu riječ this.
Kada pišemo:

String name("orville"), name2("wilbur");
name = "Orville Wright";
ovo je pokazivač koji se obraća objektu name1 unutar tijela funkcije operacije dodjeljivanja.
ovo uvijek ukazuje na objekt klase kroz koji se poziva funkcija. Ako
ptr->size();
obj[1024];

Tada će unutar size() vrijednost ovog biti adresa pohranjena u ptr. Unutar operacije get index, ovo sadrži adresu obj. Dereferenciranjem ovoga (koristeći *this), dobijamo sam objekat. (Pokazivač this je detaljno opisan u odjeljku 13.4.)

Inline String& String::operator=(const char *s) ( if (! s) ( _size = 0; izbriši _string; _string = 0; ) else ( _size = str1en(s); izbriši _string; _string = novi char[ _size + 1 ]; strcpy(_string, s); ) vrati *ovo; )

Prilikom implementacije operacije dodjeljivanja, vrlo često se napravi jedna greška: zaborave provjeriti da li objekt koji se kopira nije isti onaj u koji se kopira. Ovu provjeru ćemo izvršiti koristeći isti pokazivač this:

Inline String& String::operator=(const String &rhs) ( // u izrazu // ime = *pointer_to_string // ovo je ime1, // rhs - *pokazivač na_niz. if (ovo != &rhs) (

Evo punog teksta operacije za dodjelu objekta istog tipa String objektu:

Inline String& String::operator=(const String &rhs) (if (ovaj != &rhs) (izbriši _string; _size = rhs._size; if (! rhs._string)
_string=0;
drugo(
_string = novi znak[_veličina + 1];
strcpy(_string, rhs._string);
}
}
vrati *ovo;
}

Operacija indeksa je skoro ista kao implementacija za niz koji smo kreirali u odjeljku 2.3:

#include inline karakter&
String::operator (int element)
{
tvrditi (elem >= 0 && elem< _size);
povratak _string[elem];
}

Ulazni i izlazni operatori su implementirani kao zasebne funkcije, a ne članovi klase. (Razlozi za to ćemo razmotriti u Odjeljku 15.2. Odjeljci 20.4 i 20.5 raspravljaju o preopterećenju ulaznih i izlaznih operatora iostream biblioteke.) Naš ulazni operator ne može čitati više od 4095 znakova. setw() je unapred definisani manipulator, on čita zadati broj znakova minus 1 iz ulaznog toka, čime se osigurava da ne prelijemo naš interni inBuf bafer. (Poglavlje 20 detaljno govori o setw() manipulatoru.) Da biste koristili manipulatore, morate uključiti odgovarajući fajl zaglavlja:

#include inline istream& operator>>(istream &io, String &s) ( // umjetno ograničenje: 4096 znakova const int 1imit_string_size = 4096; char inBuf[ limit_string_size ]; // setw() je dio iostream biblioteke // ograničava blok čitanja veličina na 1imit_string_size -l io >> setw(1imit_string_size) >> inBuf; s = mBuf; // String::operator=(const char*); return io; )

Izlaznoj naredbi je potreban pristup internoj reprezentaciji stringa. Od operatera<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода:

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

Slijedi primjer programa koji koristi klasu String. Ovaj program uzima riječi iz ulaznog toka i broji njihov ukupan broj, kao i broj riječi "the" i "it", te registruje naiđene samoglasnike.

#include #include "String.h" int main() ( int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, theCnt = 0, itCnt = 0, wdCnt = 0, notVowel = 0; / / Riječi "The" i "It"
// provjerit ćemo s operatorom==(const char*)
String ali, the("the"), it("it"); // operator>>(ostream&, String&)
dok (cin >> buf) (
++wdCnt; // operator<<(ostream&, const String&)
cout<< buf << " "; if (wdCnt % 12 == 0)
cout<< endl; // String::operator==(const String&) and
// String::operator==(const char*);
if (buf == the | | buf == "The")
++theCnt;
ostalo
if (buf == to || buf == "To")
++itCnt; // poziva String::s-ize()
za (int ix =0; ix< buf.sizeO; ++ix)
{
// poziva String:: operator(int)
prekidač (buf[ix])
{
slučaj "a": slučaj "A": ++aCnt; break;
slučaj "e": slučaj "E": ++eCnt; break;
case "i": case "I": ++iCnt; break;
slučaj "o": slučaj "0": ++oCnt; break;
case "u": case "U": ++uCnt; break;
default: ++notVowe1; break;
}
}
) // 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;
}

Testirajmo program: ponudićemo mu odlomak iz dječije priče koju je napisao jedan od autora ove knjige (s ovom pričom ćemo se susresti u 6. poglavlju). Evo izlaza programa:

Alice Emma ima dugu raspuštenu crvenu kosu. Njen tata kaže da kada joj vetar duva kroz kosu, ona izgleda skoro živa, kao vatrena ptica u letu. Prekrasna vatrena ptica, kaže joj, magična, ali neukroćena. "Tata, šuti, nema toga", kaže mu ona, istovremeno želeći da joj kaže više. Stidljivo, ona pita: "Mislim, tata, ima li?" Riječi: 65
the/The: 2
to/to: 1
suglasnici: 190
a:22
e:30
i:24
oko: 10
u:7

Vježba 3.26

Mnogo se ponavlja u našim implementacijama konstruktora i operatora dodjeljivanja. Razmislite o premještanju koda koji se ponavlja u zasebnu funkciju privatnog člana, kao što smo učinili u odjeljku 2.3. Provjerite funkcionira li nova opcija.

Vježba 3.27

Izmijenite testni program tako da broji i suglasnike b, d, f, s, t.

Vježba 3.28

Napišite funkciju člana koja broji broj pojavljivanja znaka u nizu koristeći sljedeću deklaraciju:

String klase ( public: // ... int count(char ch) const; // ... );

Vježba 3.29

Implementirajte operator konkatenacije nizova (+) tako da spaja dva niza i vraća rezultat u novom String objektu. Evo deklaracije funkcije:

Klasa String ( public: // ... String operator+(const String &rhs) const; // ... );

Top Related Articles