Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 6

Posto su moji studenti imali problema prosli put da savladaju stringove napisao

sam malo detaljnije upute za 10. tutorijal. Nadam se da ce vam biti od pomoci i
nemojte zamjeriti pravopisne i razne druge greske, jer sam jedva gledao dok sam
ovo kucao. smile emoticon
U programskim jezicima viseg nivoa najcesce postoji tip podataka koji se naziva
string i sluzi za obradu teksta. U C-u ne postoji takav tip podataka, ali se moz
e simulirati koristeci nizove znakova (char array).
Deklaracija takvog C stringa bi bila npr.:
char ime[100];
Ovim smo dobili niz od 100 elemenata tipa char koji su, kao i svaki drugi niz ko
ji na ovaj nacin deklariramo, nasumice inicijalizirani (ne znamo koja vrijednost
se nalazi u kojem elementu niza).
Ukoliko zelimo inicijalizirati C string tokom deklaracije mozemo koristiti sljed
ece konstrukcije:
char ime[100] = "Roland";
Ovim smo inicijalizirali prvih 7 elemenata niza na znakove
ime[0] -> 'R'
ime[1] -> 'o'
ime[2] -> 'l'
ime[3] -> 'a'
ime[4] -> 'n'
ime[5] -> 'd'
ime[6] -> '\0'
Ostali elementi su ponovo nasumice inicijalizirani. Konvencija kod stringova je
da znak '\0' koji se naziva jos null terminator i ima ASCII vrijdnost 0 oznacava
kraj stringa. Tu konvenciju postuju sve C-ovske funkcije koje primaju string ka
o argument, sto cemo vidjeti u nastavku.
Ukoliko zelimo ispisati string mozemo koristiti printf. na sljedeci nacin:
printf("%s", ime); // Ovo ispisuje Roland
Posto je formater %s funkcija printf kao argument ocekuje pokazivac na char. Var
ijabla ime predstavlja pokazivac na prvi element niza ime (&niz[0]), sto znaci d
a je prethodni poziv ispravan. Posto je konvencija da stringovi zavrsavaju sa nu
ll terminatorom, onda funkcija printf iterira kroz clanove niza ime sve dok ne n
aidje na znak '\0'.
Da smo primjera radi dodali jos znakove u niz:
ime[7] = '1';
ime[8] = '2';
ime[9] = '3';
ime[10] = '\0';
I ponovo pozvali:
printf("%s", ime);
Opet bismo dobili Roland, obzirom da printf ispisuje do prvog null terminatora s
tring! Ovaj trick se generalno moze iskoristiti za skracivanje stringova.
Pored se moze koristiti i pokazivacka aritmetika da se preskacu neka slova prili
kom ispisa npr.
printf("%s", ime + 2);
Ce ispisati land, jer radi argumenta ime + 2 funkcija printf misli da ime pocinj
e od 3. slova!
Stringove mozemo unijeti koristeci funkciju scanf kao npr.
scanf("%s", ime);
Primjetno je da ne koristimo adresni operator & nad varijablom ime kao sto smo r
adili sa drugim prostim tipovima podataka poput int, float, double, chard, itd.
Razlog tome je sto ime predstavlja vec pokazivac na prvi element niza (&niz[0]),
pa je adresni operator nepotreban. Nemojte slucajno greskom stavljati &niz, jer
to moze imati nepredivljive posljedice (http://stackoverflow.com/ /5406 /reading-astring-with-scanf).
Ukoliko sada unesemo na tastaturi Roland dobit cemo ponovo sljedece znakove u ni
zu:
ime[0] -> 'R'
ime[1] -> 'o'

ime[2] -> 'l'


ime[3] -> 'a'
ime[4] -> 'n'
ime[5] -> 'd'
ime[6] -> '\0'
Ali ukoliko pokusamo unijeti Roland of Gilead nazalost necemo dobiti ono sto oce
kujemo. Naime, scanf uzima sve do prvog white spacea (razmak, tab ili novi red).
Sto znaci da ce se Roland smjestiti u varijablu ime, a of Gilead ce ostati u bu
fferu.
Da bismo mogli unijeti stringove koji sadrze razmake sa tastature potrebno je na
praviti dodatnu funkciju (preuzeta sa OR LV10):
void unos(char* string, int duzina)
{
int i = 0;
char c;
do
{
c = getchar();
string[i] = c;
i++;
} while(c != '\n' && i < duzina);
string[i - 1] = '\0';
}
Prvi argument predstavlja adresu prvog clana stringa kojeg unosimo, a drugi argu
ment njegovu duzinu, odnosno maksimalan broj znakova koje mozemo unijeti u strin
g.
Da imam stringove:
char prvi[20];
char drugi[30];
char treci[40];
Ispravni pozivi bi bili
unos_stringa(prvi, 20);
unos_stringa(drugi, 30);
unos_stringa(treci, 40);
Ako se vratimo na nasu staru varijablu ime poziv bi bio
unos_stringa(ime, 100);
i ako unesemo Roland of Gilead onda se u bufferu nalazi
Roland of Gilead\n
Funkcija getchar() uzima prvi znak iz buffera, sto bi u prvoj iteraciji petlje b
ilo 'R' i smjesta ga u string[0] (sto je niz[0] u nasem pozivu). U bufferu je os
talo:
oland of Gilead\n
Sad se uzima slovo 'o' iz buffera u smjesta u string[1] (sto je niz[1]) i ovaj p
roces se nastavlja sve dok se ne uzima znak '\n' (koji mora biti u bufferom obzi
rom da mi unos sa tastature zavrsavamo tipkom enter).
Poslije petlje u nasem stringu se nalazi:
ime[0] -> 'R'
ime[1] -> 'o'
ime[2] -> 'l'
ime[3] -> 'a'
ime[4] -> 'n'
ime[5] -> 'd'
ime[6] -> ' '
ime[7] -> 'o'
ime[8] -> 'f'
ime[9] -> ' '
ime[10] -> 'G'
ime[12] -> 'i'
ime[13] -> 'l'
ime[14] -> 'e'

ime[15] -> 'a'


ime[16] -> 'd'
ime[17] -> '\n'
Obzirom da stringovi trebaju zavrsavati sa null terminatorom, to se poslije petl
je ime[17] prepisuje sa '\0'.
Ukoliko unesemo string koji je duzi od maksimalne duzine sto je za niz ime 100 z
nakova onda ce ih funkcija unos_stringa skratiti na prvih duzina - 1 znakova, st
o je u slucaju niza ime 99 znakova i na posljednje mjesto ce postaviti null term
inator. Tako da je ova funkcija otporna na potencijalne malverizacije korisnika.
Kada predamo stringove funkcijama kao argument (osim kod ove funkcije za unos st
ringa) ne moramo navoditi duzinu kao drugi argument, jer se zna da stringovi zav
rsavaju null terminatorom. Npr. ukoliko zelimo napisati funkciju koja ispisuje s
vaki znak stringa u novom redu mozemo to uraditi na sljedeci nacin:
void ispis(char* string)
{
while (*string != '\0')
{
printf("%c\n", *string);
string++;
}
}
Ako je u nasoj varijabli ime bilo Roland\0 onda ce ispis(ime) ispisati:
R
o
l
a
n
d
Jer string u prvoj iteraciji predstavlja pokazivac na 'R', pa *string ispisuje '
R'. Zbog pokazivacke aritmetike nakon string++ varijabla string pokazuje na 'o',
pa *string ispisuje 'o'. Ovo se ponavlja sve dok ne dodjemo do pokazivacem stri
ng na '\0' i petlja se zavrsava. Alternativno smo mogli koristit uslog while (*s
tring != 0) obzirom da '\0' ima ASCII vrijednost 0.
Medjutim, ne moramo nuzno koristiti pokazivacku aritmetiku za ispis. Prethodnu f
unkciju smo mogli i ovako implementirati:
void ispis(char* string)
{
int i = 0;
while (string[i] != '\0')
{
printf("%c\n", string[i]);
i++;
}
}
Ili preko for petlje:
void ispis(char* string)
{
int i = 0;
for (i = 0; string[i] != '\0'; i++)
{
printf("%c\n", string[i]);
i++;
}
}
Ili da lici iteriranje preko klasicnih nizova:
void ispis(char* string)
{
int i = 0;
for (i = 0; i < strlen(string); i++)
{

printf("%c\n", string[i]);
i++;
}
}
Funkcija strlen iz biblioteke string.h vraca duzinu stringa (ne ukljucujuci null
terminator). Tako za nas string Roland\0 u stringu ime vraca 6 (bez obzira sto
u ovaj niz mozemo staviti 100 charova!).
Na OR LV10 je trebalo implementirati funkciju koja vraca duzinu stringa (u princ
ipu funkcija koja oponasa strlen iz string.h). Nju je lako implementirati ako ko
ristimo bilo koju od prethodnih implementacija funkcije ispis, npr.
int duzina(char* string)
{
int duzina = 0;
while (*string != '\0')
{
duzina++;
string++;
}
return duzina;
}
Funkcija strlen je prakticno jednako implementirana kao prethodna funkcija za du
zinu, sto znaci da je posljednja implementacija za ispis najmanje efikasna, jer
dva puta prolazimo kroz niz - jednom da odredimo duzinu i jednom da ispisemo sve
znakove.
Posto su pojedinacni elementi stringa obicni znakovi, to znaci da mozemo samogla
snike izbrojati ako prodjemo kroz string i u svakoj iteraciji provjerimo da li j
e trenutni znak 'a' ili 'A' ili'e' ili 'E' ili 'i' ili 'I' ili 'o' ili 'O' ili '
u' ili 'U'. Medjutim, ono sto je lijepo uraditi je tu provjeru izdvojiti u poseb
nu funkciju kao npr.:
int samoglasnik(char c)
{
// Moze i preko if-a
switch (c)
{
case 'a':
case 'A':
case 'e':
case 'E':
case 'i':
case 'I':
case 'o':
case 'O':
case 'u':
case 'U':
return 1;
}
return 0;
}
Ili npr.:
int samoglasnik(char c)
{
char samoglasnici[10] = "aAeEiIoOuU";
int i;
for (i = 0; i < 10; i++)
{
if (c == samoglasnici[i])
return 1;
}
return 0;
}

Da bismo pretvorili mala slova u velika ponovo trebamo iterirati preko niza i za
svaki znak provjeriti da li je malo slovo, ukoliko jeste pretvorimo ga u veliko
. Za detalje je korisno pogledati ASCII tabelu:
http://www.asciitable.com/index/asciifull.gif
U ASCII tabeli su velika slova prije malih slova i slova su abecedno poredana, s
to znaci da mozemo praviti provjere tipa
if ((string[i] >= 'a') && (string[i] <= 'z'))
Ili
if ((*string >= 'a') && (*string <= 'z))
Da bismo zakljucili da je tekuci znak malo slovo.
Razlika izmedju velikih i malih slova je 32, npr. slovo 'A' ima vrijednost 65, a
slovo 'a' ima vrijednost 97, isto tako slovo 'B' ima vrijednost 66, a slovo 'b'
ima vrijednost 98, itd. Tako da malo slovo mozemo pretvoriti u veliko
if ((string[i] >= 'a') && (string[i] <= 'z'))
{
string[i] -= 32;
}
ili
if ((*string >= 'a') && (*string <= 'z))
{
*string -= 32;
}
Medjutim, razliku od 32 nije potrebno pamtiti, jer se moze dobiti kao 'a' - 'A'
ili 'b' - 'B', itd.
Tako da bi neka implementacija mogla ovako izgledati:
void samoglasnici(char* string)
{
int razlika = 'a' - 'A';
while (*string != 0)
{
if ((*string >= 'a') && (*string <= 'z))
{
*string -= razlika;
}
string++;
}
}
Prethodna funkcija ce sva mala slova iz stringa pretvoriti u velika slova. Medju
tim, u postavci tutorijala prototip treba da izgleda ovako:
char* samoglasnici(char* string);
Zasto bi povratni tip trebao biti char*? To je stvar konvencije u C-u, sve funkc
ije koje modificiraju niz vracaju kao rezultat pokazivac na prvi element niza da
su moguci lancani pozivi poput:
printf("%s", velika(ime));
Ukoliko smo unijeli string "Roland" u varijablu ime prvo ce se izvrsiti funkcija
velika i pretvoriti to u "ROLAND", zatim ce kao rezultat vratiti pokazivac na p
rvi element stringa ime da bi funkcija printf odmah mogla ispisati novu vrijedno
st (ovo se inace naziva lancani poziv funkcija). Tako da cemo implementaciju pre
praviti na sljedeci nacin:
char* samoglasnici(char* string)
{
char* pocetak = string;
int razlika = 'a' - 'A';
while (*string != 0)
{
if ((*string >= 'a') && (*string <= 'z))
{
*string -= razlika;
}
string++;

}
return pocetak;
}
Morali smo uvesti novu varijablu obzirom da iteriranjem po petlji pomjeramo poka
zivac desno, tako da smo npr. vratili string printf ne bi ispisao nista jer stri
ng pokazuje na null terminator.
Ovaj zadatak sa brojanjem rijeci podrazumijeva da su recenice gramaticki praviln
o napisane tako da se ovo svodi na brojanje razmaka. Tako tesni string "Danas je
lijep dan." ima 3 razmaka, odnosno 4 rijeci. Implementacije takve funkcije je d
osta trivijalna.
Ovaj zadnji zadatak zahtjeva da implementirate svoju funkciju koja oponasa funkc
iju strcmp iz biblioteke string.h. Ja bih preporucio da joj date drugo ime da bi
ste mogli porediti rezultate sa pravom strcmp funkcijom. Ideja je da u isto vrij
eme iterirate kroz dva stringa - znaci u prvoj iteraciji gledate prvi znak oba s
tringa, u drugoj iteraciji drugi znak oba stringa, itd. Prilikom dizajna ASCII t
abele je vec vodjeno racuna o tome, radi toga su velika slova prije malih slova,
a isto tako su pojedina slova poredana abecedno.
if (*prvi < *drugi) // prvi je abecedno prije drugog
if (*drugi < *prvi) // drugi je abecedno prije prvog
Ako se zavrsi petlja ova petlja bez returna onda provjeravate
if (duzina(prvi) < duzina(drugi)) // prvi je abecedno prije drugog
if (duzina(drugi) < duzina(prvi)) // drugi je abecedno prije prvog
Ako ni ovi if-ovi nisu zadovoljeni vratiti nulu, jer su stringovi jednaki. Ovo j
e sasvim okay implementacija, mada se ovo moze dodatno skratiti ako se uslov u p
etlji fino postavi.

You might also like