NIZOVI ZNAKOVA (STRINGOVI) Nizovi znakova (ili stringovi) služe za predstavljanje znakovnih podataka kao što su npr. riječi ili rečenice u memoriji. U programskom jeziku C i C++, osnova za predstavljanje nizova znakova je znakovni tip char. Nizovi znakova se predstavljaju upotrebom polja sa char elementima.
ZNAKOVNI TIP Znakovni tip char služi za pohranjivanje jednog znaka u radnu memoriju. Pod znakom se najčešće podrazumijevaju slova i brojevi, iako postoje i razni drugi znakovi. Znakovne varijable mogu se upotrebljavati na gotovo isti način kao i druge varijable osnovnog tipa: main() { char unos; cout << “Unesite jedan znak: “; cin >> unos; cout << “Unijeli ste: “ << unos << endl; }
return 0;
Znakovne konstante se pišu tako da se konkretan znak (samo jedan) navede unutar jednostrukih navodnika. Slijedeći primjer ilustrira tipični način upotrebe: main() { char ch; cout << “Unesite N za nastavak: “; cin >> ch;
}
if (ch == ‘N’) cout << “Nastavak\n“; else cout << “Kraj!\n”; return 0;
Važno je napomenuti da postoji razlika između velikih i malih slova. U prethodnom primjeru, program će “nastaviti” s radom (tj. ispisati će se poruka “Nastavak”) samo ukoliko korisnik unese veliko slovo N. Ukoliko ne želimo da naš program pravi razliku između velikih i malih slova, to možemo jednostavno napraviti u if uvjetu: if (ch == ‘N’ || ch == ‘n’) cout << “Nastavak\n”;
ASCII TABLICA U osnovi, računalo ne prepoznaje znakove. Svi podaci zapisani u računalu su zapisani u obliku brojeva. Ovo također vrijedi i za znakovni tip. Načelno, znakovi su prikazani u računalu tako da je svakom znaku pridružen jedinstveni broj. Kada se ispisuje znak (recimo na zaslon), računalo “zna” o kojem znaku je riječ pomoću njegovog broja. Tablica koja određuje kojim znakovima su pridruženi brojevi, zove se ASCII tablica. Odgovarajući broj za neki znak se
zove ASCII kod. ASCII tablica sadrži 256 znakova (brojevi tj. ASCII kodovi se kreću od 0 do 255). Tako npr. veliko slovo A (tj. znak ‘A’) ima ASCII kod 65. Znakovnim varijablama može se pridružiti brojčana vrijednost. Time se zapravo u varijablu sprema znak sa odgovarajućim ASCII kodom. Ovo je ilustrirano slijedećim naredbama: char ch; ch = 65; // isto kao i ch = ‘A’ cout << ch << endl; // ispis cin >> ch; if (ch == 65) // isto kao i if (ch == ‘A’) cout << “Unijeli ste znak A\n”; Važno je napomenuti da brojevni znakovi nemaju iste ASCII kodove kao i brojevi koje predstavljaju. Npr. znak ‘0’ nema ASCII kod 0. Ukoliko želimo ispitati je li korisnik unio određeni brojevni znak (ne broj), to možemo napraviti na slijedeći način: main() { char ch; while (1) { cout << “1. Unos\n”; cout << “2. Ispis\n”; cout << “X. Izlaz\n”; cin >> ch;
}
if (ch == ‘1’) // a ne if (ch == 1) // unos else if (ch == ‘2’) // ispis else if (ch == ‘X’ || ch == ‘x’) return 0; else cout << “Pogrešan odabir!\n”;
} Gornji program prikazuje kostur izbornika, gdje korisnik pri odabiru opcije može unijeti broj ili znak. Stoga se za učitavanje upotrebljava znakovna varijabla. Važno je primjetiti da se pri ispitivanju je li korisnik unio broj 1 ili 2, broj stavlja unutar jednostrukih navodnika. Da je varijabla ch bila tipa int, jednostruke navodnike bi trebalo ispustiti.
POSEBNI ZNAKOVI Posebni znakovi služe za prikaz znakova koji se ne mogu jednostavno staviti unutar jednostrukih navodnika. Najčešće upotrebljavani su newline (prelazak u novi red), te tabulator. Znak newline se u programu prikazuje kao ‘\n’, dok se tabulator prikazuje kao ‘\t’. Općenito, svi posebni znakovi započinju sa znakom \. Zbog toga je i za znak \ potreban posebni prikaz: ‘\\’. Slijedeći primjer prikazuje osnovnu upotrebu: main() { char ch; ch = ‘\n’;
cout << ch; ch = ‘\t’; cout << ch ch = ‘\\’; cout << ch; }
return 0;
NIZOVI ZNAKOVA (STRING) U programskim jezicima C i C++ ne postoji posebno ugrađeni tip za nizove znakova. Umjesto toga, u tu svrhu se koristi polje čiji su elementi tipa char. U svaki element polja se pohranjuje jedan znak riječi ili rečenice koja je predstavljena tim poljem. Najosnovnija upotreba (učitavanje i ispisivanje) se izvodi na isti način kao i kod drugih osnovnih tipova, dok se ostale osnovne operacije (pridruživanje, usporedba te spajanje dva niza znakova) izvode na malo drugačiji način.
OSNOVNA UPOTREBA DEKLARACIJA, UČITAVANJE I ISPIS Slijedeći primjer prikazuje najosnovniju upotrebu niza znakova: main() { char niz[20]; // niz znakova cout << “Unesite jednu rijec: “; cin >> niz; // ucitavanje cout << niz << endl; // ispis return 0; } U prvoj liniji funkcije main, deklariran je niz znakova. Odgovarajuća varijabla u ovom primjeru zove se niz, a deklarirana je kao polje od dvadeset elemenata tipa char. Važna činjenica je da se u ovako deklarirano polje se može smjestiti niz od najviše 19 znakova (uzrok će biti objašnjen nešto kasnije). Pri učitavanju i ispisivanju niza znakova se navodi samo naziv polja (bez uglatih zagrada). Za “obična” polja učitavanje i ispis pomoću cin i cout nije podržano, no za polje znakova je to posebno podržano. Kao posljedica učitavanja, riječ koju korisnik upiše se sprema u polje niz. Ista stvar vrijedi i za ispis, te će se tako niz znakova ispisati na zaslon.
PRIKAZ NIZA ZNAKOVA U MEMORIJI Pretpostavimo da je korisnik prethodnog programa unio riječ Ivo. Naredba ispisa bi tada trebala ispisati tu riječ na zaslon. Međutim, postavlja se pitanje, kako će naredba ispisa “znati” da treba ispisati samo prva tri učitana znaka? Razlog tome je što se u polje sprema posebni znak koji označava koji dio polja zapravo predstavlja riječ. Očito je naime da u prethodnom primjeru ne moraju se riječi biti maksimalne dužine. Taj poseban znak se zove “null
terminator” i označava kraj stringa. Drugim riječima, naredba ispisa će ispisati sve znakove u polju koji se nalaze prije null terminatora. Znakovna oznaka za null terminator je ‘\0’. Slijedeća slika ilustrira kako je u prethodnom primjeru popunjeno polje kada korisnik unese riječ Ivo: ‘I’
‘v’
‘o’
‘\0’
Prvi znak (tj. prvo slovo učitane riječi) se nalazi na nultoj poziciji u polju, slijedeći znak je na prvoj poziciji itd. Odmah nakon svih učitanih znakova slijedi znak ‘\0’ koji označava kraj stringa. Sve operacije koje na neki način analiziraju niz znakova (npr. naredba ispisa, usporedba, pridruživanje i sl.) uzet će u obzir samo one znakove koji se nalaze prije ‘\0’ znaka. Iz tog razloga u prethodnom primjeru se smije unijeti riječ od najviše 19 znakova. Naime, u prethodno polje moguće je spremiti najviše 20 podataka tipa char. Svaki niz znakova mora završavati sa ‘\0’ te je jedno mjesto unaprijed “rezervirano”. Preostaje još 19 slobodnih mjesta za znakove od kojih se sastoji sam podatak (riječ, rečenica). Općenito rečeno ako je niz znakova deklariran na slijedeći način: char niz[MAX]; tada se u taj niz može pohraniti najviše MAX – 1 znakova.
KONSTANTE NIZA ZNAKOVA (STRING KONSTANTE) String konstante su već često upotrebljavane. U programu se navode tako da se podatak (riječ ili rečenica) stavi unutar dvostrukih navodnika. Npr. “Ivo”
“Mama ide u dućan”
su primjeri string konstanti. Važno je razlikovati string konstante od znakovnih konstanti. Znakovna konstanta predstavlja jedan znak, dok string konstanta predstavlja jedan ili više znakova nakon kojih se nalazi null terminator. Drugim riječima konstante “A” i ‘A’ su različite. Prva predstavlja niz od jednog znaka (prevodilac skriveno dodaje null terminator), dok druga predstavlja jedan znak.
PRIDRUŽIVANJE Već je ranije spomenuto da su jedine operacije nad stringovima posebno podržane u C i C++ jezicima učitavanje i ispis. Drugim riječima, slijedeće naredbe nisu dopuštene: char niz1[10], niz2[10]; niz1 = “Ivo”; // nije dopušteno niz2 = niz1; // nije dopušteno Pridruživanje nizova znakova izvodi se pomoću funkcije strcpy (naziv je skraćen od string copy). Funkcija prima dva argumenta. Prvi argument je odredišni niz znakova (varijabla u koju se sprema podatak), dok je drugi podatak izvorišni niz znakova (tj. sadržaj koji se sprema). Drugi podatak može biti neko drugo polje znakova, ali može biti i string konstanta. Prethodna pridruživanja se mogu izvesti pomoću slijedećih naredbi: char niz1[10], niz2[10]; strcpy(niz1, “Ivo”); // niz1 = “Ivo” strcpy(niz2, niz1); // niz2 = niz1
Da bi se funkcija strcpy (i slične funkcije opisane u nastavku) mogla upotrebljavati potrebno je uključiti na početku programa datoteku STRING.H. Slijedeći primjer je prikazuje cjeloviti program koji se može prevesti: #include #include <string.h>
// potrebno zbog upotrebe strcpy
main() { char niz[20]; strcpy(niz,”Dobar dan!”); // pridruživanje cout << niz; }
return 0;
SPAJANJE STRINGOVA Pod spajanjem stringova misli se na pridruživanje jednog stringa na kraj drugog. Spajanje se obavlja pozivom funkcije strcat za čiju upotrebu je također potrebno uključiti datoteku STRING.H. Funkcija prima dva argumenta. Slično kao i kod strcpy funkcije, prvi argument je odredište, dok je drugi izvorište. Razlika je u tome što se izvorišni string pridodaje na kraj odredišnog stringa. Nakon Slijedećeg niz naredbi u stringu niz će se naći sadržaj “IvoIvic”. char niz[20]; strcpy(niz,”Ivo”); // pridruživanje strcat(niz,”Ivic”); // spajanje Ovaj primjer ilustrira i jednu bitnu karakteristiku funkcije strcpy. Izvorišni niz se pridodaje neposredno na kraj odredišnog. Ukoliko pri spajanju želimo da se ubaci i npr. znak razmaka, moramo to “ručno” napraviti. Slijedeći primjer učitava odvojeno ime i prezime i zatim i spaja u jedinstveni “čitljiv” niz znakova: #include #include <string.h> main() { char ime[10],prezime[10]; cout << “Unesite ime: “; cin >> ime; cout << “Unesite prezime: “; cin >> prezime; char puno_ime[20]; strcpy(puno_ime, ime); // pridruzi ime u puno_ime strcat(puno_ime, ” “); // dodaj razmak na kraj strcat(puno_ime, prezime); // dodaj prezime na kraj cout << puno_ime << endl; return 0; }
USPOREDBA DVA STRINGA Uspoređivanje dva stringa se obavlja pozivom funkcije strcmp ili stricmp. Prva funkcija razlikuje velika i mala slova, dok druga funkcija ne pravi tu razliku. Obje funkcije imaju istu sintaksu poziva. Funkcije primaju dva argumenta – dva niza koja se međusobno uspoređuju. Funkcije kao rezultat vraćaju cijeli broj koji se tumači na slijedeći način: rezultat funkcije 0 broj veći od nule broj manji od nule
značenje rezultata stringovi su identični prvi string je “veći” od drugog prvi string je “manji” od drugog
Kada se kaže da je prvi string veći od nekog drugog, može se reći da po abecedi (engleskoj), prvi string dolazi poslije drugog. Slično, prvi string je manji od drugog ukoliko po abecedi dolazi prije drugog stringa. Tako npr. vrijede slijedeći odnosi: Iva > Ana Iva < Maja Iva < Ivana
(tj. strcmp(“Iva”,”Ana”) je veće od nule) (tj. strcmp(“Iva”,”Maja”) je manje od nule) (tj. strcmp(“Iva”,”Ivana”) je manje od nule)
Pojmovi prvi i drugi string, se odnose na prvi tj. drugi argument funkcije strcmp ili stricmp. Slijedeći primjer prikazuje osnovnu upotrebu: #include #include <string.h> main() { char unos[20]; cout << “Unesite XXX za nastavak: “; cin >> unos; int rez if (rez cout else cout
= strcmp(unos,”XXX”); // usporedi stringove == 0) // ako su stringovi identični << “Nastavak!\n”; << “Kraj!\n”;
return 0; } U ovom programu, korisnik mora unijeti XXX za nastavak. Usporedba se obavlja pozivom funkcije strcmp te se zapisuje u varijablu rez. Ukoliko su dva stringa isti (tj. korisnik je unio XXX), tada će rezultat usporedbe biti nula. U suprotnom će rezultat biti različit od nule. Važno je primjetiti da pri unosu sva tri slova X moraju biti velika slova. Ukoliko se korisniku želi omogućiti da ne mora paziti na raspored velikih/malih slova, tada je potrebno pozvati funkciju stricmp umjesto funkcije strcmp. U tom slučaju će rezultat usporedbe biti 0 (smatrati će se da je korisnik unio tri slova X) uvijek kada se unesu tri slova X bez obzira na to je li koje od unesenih slova veliko ili malo.
UČITAVANJE VIŠE OD JEDNE RIJEČI U STRING Učitavanje stringa sa tipkovnice upotrebom cin objekta (i operatora <<) ima jedan veliki nedostatak, a to je da se u varijablu zapisuje samo prva riječ. Česte su situacije kada je potrebno učitati više od jedne riječi u string (npr. naslov ili cijelu rečenicu). Taj problem se može jednostavno riješiti na dva načina.
Prvi način je upotrebom get metode cin objekta. Ta metoda prima dva argumenta, pri čemu je prvi argument niz znakova u koji se učitava, dok je drugi argument maksimalni broj znakova koji će se spremiti u niz (umanjen za jedan). Slijedeći primjer ilustrira primjenu get metode cin objekta: #include main() { char niz[100]; cout << “Unesite recenicu: ”; cin.get(niz,100); cout << niz << endl; }
return 0;
U varijablu niz će se spremiti svi znakovi koji se unesu do pritiska na tipku enter. Ukoliko korisnik programa unese više od 99 znakova, u stringu će se naći samo prvih 99 znakova. Slično je moguće napraviti i pozivom funkcije gets, koja prima samo jedan argument: naziv stringa. Za upotrebu te funkcije potrebno je uključiti datoteku STDIO.H. Slijedeći primjer pokazuje upotrebu ove funkcije: #include <stdio.h> #include main() { char niz[100]; cout << “Unesite recenicu: ”; gets(niz); cout << niz << endl; }
return 0;
ANALIZA ZNAKOVA U STRINGU Dosadašnji primjeri pokazali su kako je moguće izvesti tipične operacije nad stringovima upotrebom postojećih funkcija. U praksi je ponekad potrebno napraviti određene operacije nad stringom koje nisu posebno podržane odgovarajućom funkcijom. Tada se radi slično kao i sa poljima. Potrebno je napraviti for petlju koja prolazi kroz string znak po znak i operaciju izvesti nad svakim znakom posebno. Prije pisanja petlje koja prolazi kroz cijeli string, potrebno je upoznati funkciju strlen koja vraća dužinu stringa (tj. broj znakova u stringu). Funkcija strlen prima jedan argument, naziv stringa, a vraća broj znakova u tom stringu (tj. broj znakova koji se nalaze prije null terminatora). Za upotrebu funkcije potrebno je također uključiti datoteku STRING.H. Općenito, petlja koja prolazi kroz sve znakove u stringu može se napisati na slijedeći način: char string[100]; // ovdje je potrebno ucitati string int n = strlen(string); // broj ucitanih znakova for (int i = 0;i < n;i++) // za svaki znak u stringu // obaviti operaciju nad znakom string[i]
Ova petlja funkcionira kao i svaka tipična petlja koja prolazi kroz sve elemente polja. Potrebno je samo imati na umu da je jedan element stringa zapravo varijabla tipa char (tj. element stringa je jedan znak). Slijedeći program traži od korisnika da unese jednu rečenicu te broji koliko riječi postoji u toj rečenici. To radi tako da broji koliko puta u rečenici se pojavljuje znak ‘ ‘ (tj. razmak): #include #include <string.h> #include <stdio.h> main() { char rec[100]; cout << “Unesite recenicu: “; gets(rec); int br_raz = 0; // broj razmaka int n = strlen(rec); for (int i = 0;i < n;i++) // za sve znakove if (rec[i] == ‘ ‘) // ako je znak razmak br_raz++; cout << “Broj rijeci iznosi: “ << br_raz << endl; }
return 0;
STRINGOVI I FUNKCIJE Važno je cijelo vrijeme imati na umu da je string zapravo polje čiji su elementi tipa char. Stoga, kada funkcija prima string argument, isto kao i kod “običnih” polja, funkcija prima pokazivač na tip char. Sama funkcija se ne mora brinuti oko toga što je riječ o pokazivaču, nego ka jednostavno koristi kao da je riječ o klasičnom stringu (što je zapravo i istina). Za razliku od dosadašnjih funkcija koje su primale polje kao argument, nije potrebno posebno poslati broj elemenata u polju. Naime to se može pribaviti funkcijom strlen. Funkcija strlen “zna” koliko je znakova u stringu jednostavno zato što ona “zna” da string završava sa znakom ‘\0’. Slijedeća funkcija broji koliko puta se određeni znak javlja u nekom stringu: int broj_pojave(char *string, char znak) { int rez = 0; int n = strlen(string); for (int i = 0;i < n;i++) if (string[i] == znak) // ako je i-ti znak jednak // trazenom znaku rez++; return rez; }
Upotreba u main funkciji se može napraviti na slijedeći način: char unos[100]; cin >> unos; cout << broj_pojave(unos,’a’); // koliko puta se u stringu // javlja malo slovo a Ovako definirana funkcija može primati samo string varijablu. Ako želimo da funkcija može primati i string konstantu (tj, niz znakova unutar navodnika), tada je potrebno pri deklaraciji funkcije odgovarajući argument deklarirati tako da bude tipa const char * umjesto char *. Konkretno na prethodnom primjeru se mijenja samo deklaracija funkcije: int broj_pojave(const char *string, char znak) Sada je u main funkciji moguće funkciju upotrijebiti i na ovaj način: cout << broj_pojave(“Mama”,’a’); Općenito, ako funkcija želi promijeniti original, tada se riječ const ispušta pri deklaraciji argumenta. Međutim, tada se pri pozivu funkcije mora navesti naziv varijable. S druge strane, ako funkcija ne mijenja argument, tada ga je pametno deklarirati sa modifikatorom const ispred, jer je onda moguće poslati i string konstantu. Slijedeća funkcija prima kao argument tri stringa te u prvi string upisuje spojene drugi i treći. Očito je da prvi string mora biti deklariran bez const modifikatora, dok je poželjno da druga dva stringa imaju const modifikator. void spoji(char *dest, const char *s1, const char *s2) { strcpy(dest, s1); // dest = s1 strcat(dest, s2); // dodaj na kraj stringa dest string s2 } U main funkciji moguće je funkciju spoji upotrijebiti na slijedeći način: char izlaz[20]; spoji(izlaz,”Ivo ”,”Ivic”); cout << izlaz << endl;
STRINGOVI U KLASI String varijabla može biti član klase (tj. pripadnog objekta). Tipične operacije, kao što su pristupne metode (set i get), se izvode na sličan način kao i kod prijašnjih tipova varijabli. U slijedećem primjeru dana je klasa radnik koja posjeduje jedan string član – ime radnika: #include #include <string.h> class Radnik { private: char ime[20]; public: char *get_ime() const; void set_ime(const char *novo_ime); };
char *Radnik::get_ime() const { return ime; } void Radnik::set_ime(const char *novo_ime) { strcpy(ime,novo_ime); } main() { Radnik Ivo; char unos[20]; cout << “Unesite ime radnika: “; cin >> unos; // poziv set metode Ivo.set_ime(unos); // poziv get metode char ime[20]; strcpy(ime,Ivo.get_ime()); cout << “Ime radnika: “ << ime << endl; return 0; } U ovom primjeru se u klasi nalazi jedan string podatkovni član te klasične set i get metode. Bitno je uočiti da metoda get_ime vraća pokazivač na tip char. Naime, važno je podsjetiti da je string zapravo polje znakova, a polje je isto što i pokazivač. Iz istih razloga, metoda set_ime prima novu vrijednost koja se sprema u podatkovni član ime u obliku pokazivača na tip char. U glavnoj funkciji, povratna vrijednost metode get_ime se koristi kao drugi argument funkcije strcpy. Budući da je drugi argument funkcije strcpy zapravo izvorišni string, time se postiže da se sadržaj člana ime objekta Ivo iskopira u lokalnu varijablu ime funkcije main.
POLJE STRINGOVA Budući da je string zapravo polje sa elementima tipa char, tada je polje stringova zapravo dvodimenzionalna matrica u kojoj su elementi tipa char. Općenito, polje stringova se može deklarirati na slijedeći način: char strings[MAX][LEN]; Pri čemu je MAX cjelobrojna konstanta koju treba ranije definirati (ili umjesto nje navesti neki broj). Ta konstanta označava koliko stringova se nalazi u polju. Slično vrijedi i za konstantu LEN koja označava najveći broj znakova (uvećan za jedan zbog ‘\0’ znaka) koji se može spremiti u jedan string u takvom polju. Npr. char imena[50][20]; Ovom deklaracijom se deklarira polje stringova u koje je moguće spremiti najviše 20 stringova. U svaki string stane najviše 19 znakova. Pristup elementima se odvija slično kao i kod običnih polja. Drugim riječima, ako se unutar programa navede imena[5] time se zapravo misli na 6. string u polju. Ako se pak navede imena[5][0], to označava 1. znak u 6. stringu polja stringova.