Igen, ez egy
tipikus ZH példa. Azért tipikus, mert a sztringekkel nagyon sok mindent vissza
lehet kérni: tömbkezelést (a sztring egy tömb, aminek a végén '\0' van),
vezérlési szerkezeteket, algoritmizálási készséget és főként memóriafoglalást.
Nézzük előbb a
memóriafoglalással kapcsolatos kérdéseket! Hogyan deklaráljuk a függvényt?
Sokszor előbukkan az alábbi megoldás:
char* sztring_fordit(char* str)
{
...
}
A hívótól
megkaptunk egy sztringre mutató pointert, amelynek a hívó foglalt helyet (hogy
tömböt definiált, vagy mallocozott az
részletkérdés). Ez rendben van. A sztringre mutató pointer jött át, ezért ha
megváltoztatunk egy karaktert, az az átadott sztringben is megváltozik. Mi
ilyet nem szeretnénk csinálni, és ezt elegáns módon jelezhetjük is:
char* sztring_fordit(const char* str)
Ettől a pointert
még állíthatjuk (pl: str++), csak a
mutatott területet nem (pl. str[0] ++).
A sztringek és
tömbök között átjárás miatt a paramétert írhatnánk tömbös alakban:
char* sztring_fordit(const char str[])
A két str között nincs különbség. Amikor
hozzálátnánk a megíráshoz, rögtön egy problémába ütközünk: hova másoljuk a megfordított
sztringet?
A legszörnyűbb
megoldás erre a tömb deklarálása lokális változóként. Az első probléma az, hogy
nem ismerjük a tömb hosszát. A második jóval komolyabb. Tegyük fel, hogy 40
karakternél hosszabb sztringet (a lezárást is beleértve) nem kapunk sohasem.
char* sztring_fordit(const char str[])
{
char
fordstr[40];
/*
Megfordítás... */
return
fordstr;
}
Viszont a fordstr egy lokális változó, aminek a
címét nem szolgáltathatjuk ki a függvényen kívülre, mert akkorra a függvény már
visszatért, a lokális változóihoz tartozó memóriaterület már felszabadult, és
más van a helyén. Ez egy nagyon súlyos hiba, a sztring fordítottja
kriksz-krakszokat tartalmaz. Az azt felhasználó programrészek nevében csak
reménykedni tudunk, hogy a memóriában van valahol egy '\0', különben senki sem
tudja, hol landolnak a sztringet bejáró ciklusok.
Ha tényleg csak
40 karakterre szólna a feladat, megoldhatnánk statikus változóval, mert az nem
szabadul fel a függvényből való visszatéréskor.
char*
sztring_fordit(const char
str[])
{
static char fordstr[40];
/*
Megfordítás... */
return
fordstr;
}
Ez azért nem
elegáns, mert többszálú környezetben nem használható ez a megoldás. Ugyanis a
függvény következő hívása módosítja a memóriaterületet, amit az előző hívó még
használna. Továbbá, ha egymás után hívjuk a függvényt, akkor a második felülírja az előző eredményét, amire külön figyelmeztetni kell a felhasználót. Ezért ezt a megoldást sem nevezhetjük jónak.
Következő
megoldás a dinamikus memóriahasználat. Egy for
ciklussal megszámoljuk, hány karakter van a sztringben, és eggyel nagyobb
helyet foglalunk, hogy a lezárókarakternek is legyen helye.
char*
sztring_fordit(const char
str[])
{
int i;
char*
fordstr;
for(i=0;str[i]!='\0';i++);
fordstr = malloc(sizeof(char)*(i+1));
/*
Megfordítás... */
return
fordstr;
}
Ez a megoldás már
működik, a veszélye annyi, hogy a hívó függvény nem hívja meg a free-t, és ez memóriaszivárgást
eredményez. Lehetőleg kerüljük ezt. Ha mégis ilyen jellegű megoldást
alkalmaznánk, mindig hangsúlyozzuk, hogy a visszaadott pointerre free-t kell hívni!
A példa
jószándékú útmutatásként köti ki, hogy ezt a megoldást kerüljük, és a hívó
foglaljon helyet a megfordított sztringnek is. Egyébként a C könyvtár is ezt a
megoldát követi. Ekkor a nem visszatérési értékként adjuk vissza a sztringet,
hanem egye bemenő paraméterként megadott (statikusan vagy dinamikusan foglalt)
tömböt módosítunk:
sztring_fordit(char* fordstr,
const char*
str)
Egy lehetséges
megoldás az alábbi:
void
sztring_fordit(char* fordstr, const char* str)
{
int i,l;
/* Hossz '\0'
nélkül */
for(l=0;
str[l]!=0;l++);
/*
Megfordítás... */
for(i=0;
str[i]!=0;i++)
{
fordstr[i]=str[l-i-1];
}
fordstr[l]='\0';
}
ZH esetén mindig
javasoljuk a rajz készítését egy adott példával, hogy stimmelnek-e az indexek.
Természetesen a feladat megoldható egy pointerrel, két pointerrel, while ciklussal is.
Ezek után nézzük
a függvény meghívását. Pusztán azért, mert pointer van a paraméterlistában, nem
rohanunk mallocot hívni, mert tudjuk,
hogy annak paraméterátadási okai is lehetnek. Nézzünk egy példát:
int main()
{
char
fordstr[9];
sztring_fordit(fordstr, "12345678");
printf(fordstr);
return 0;
}
Itt az "12345678" egy
karakterkonstans. Ugyanúgy mint ahogy a 3, vagy a 3.24 int, illetve double
konstansok. Mint ahogy a 3-at sem változtatjuk pointeren keresztül, így a
sztringkonstansot sem. Megnyugtató, hogy a függvényünk pont ezt garantálja
azzal, hogy a második pointer konstans. Mivel tudjuk, hogy mórickapéldánkban a
megfordított sztring hossza nem lesz több, mint kilenc lezárásostul, ezért a
fordítóval foglaltatunk helyet: tömböt deklarálunk.
Az átadandó
sztringet is definiálhatnánk másképp. Tegyük fel, hogy később meg akarjuk
változtatni egy-két karakterét. Ekkor deklarálunk egy tömböt, amelyet a kívánt
sztringgel inicializálunk:
char str[] = "12345678";
Itt jegyezzük
meg, hogy az idézőjeles karaktersorozat itt nem sztringkonstans, hanem char tömböt az egyszerűség kedvéért így
is lehet inicializálni. A hatás teljesen ugyanaz, mint a
char str[]= {'1','2','3','4','5','6','7','8','\0'};
kifejezés, csak
egy kicsit rövidebb. Mivel ez egy tömb a fordító foglal neki helyet, azért nem
adtunk neki méretet, mert az inicializáló adatokból a fordító kiszámolja.
Írhatnánk ezt is:
char str[9] = "12345678";
char str[9]= {'1','2','3','4','5','6','7','8','\0'};
Csak éppen
felesleges, ha a számolást a fordító automatikusan megcsinálja. Ezek után
írhatjuk, hogy str[3]++.Ezzel a tömbös variációkat kimerítettük.
Nézzük a
pointert! Az alábbi programrészlet a legdurvább hiba ebben a versenyszámban:
int main()
{
char*
fordstr;
sztring_fordit(fordstr, "12345678");
printf(fordstr);
return 0;
}
A fordstr pointer véletlenszerűen mutat
valahova a memóriába, oda pedig nem illik írni, még a mi sztring_fordit függvényünkkel sem.
Ebben a
szituációban csak akkor dolgozunk pointerrel, ha dinamikus memóriakezelést
szeretnénk. Ekkor a mallocot és
ugyanaz hívja, mint akinek a free-t
kell, vagyis aki foglalt, az figyel a felszabadításra is.
int main()
{
char*
fordstr = malloc(sizeof(char)*9);
sztring_fordit(fordstr, "12345678");
printf(fordstr);
free(fordstr);
return 0;
}
Ebben a
szituációban ez egy helytelen megoldás, mert tudjuk a megfordított sztring
maximális méretét, el tudjuk kerülni a dinamikus memóriakezelést, amit pedig
úgy kerülünk, amennyire lehet. Viszont ha például felhasználói adatbevitel
miatt nem tudjuk a méretet, akkor nincs más választásunk, mint a dinamikus memóriakezelés.
s
Végül nézzük az
alábbi programot:
int main()
{
char
fordstr[9];
char* str
= "12345678";
sztring_fordit(fordstr, str);
printf(fordstr);
return 0;
}
Mit jelent a
char* str = "12345678";
kifejezés?
A char* str egy pointer, lefoglalódik neki
a 32 bit (32-bites architektúrán). A "12345678"
pedig sztringkonstans, amelynek címére rámutat a pointer. Vagyis a str[2]++ csúnya dolog, mert konstansot
akar megváltoztatni.