Sztring megfordítása

Deklaráljon és írjon C függvényt, amelynek bemenete egy sztring és kimenete a bemeneti sztring fordítottja! A kimenetet ne visszatérési értékként adja vissza, válasszon más megoldást! A helyfoglalás a hívó dolga. A feladat megoldásához nem használhatja a string.h állomány függvényeit.

A megoldásért kattints ide!

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.






2007.08.29. 20:51:47 |  Permalink  |  Hozzászólások száma: 0  |  Tárgyszavak: Sztringek


Írja meg Ön is véleményét!


Hozzászólásokat csak regisztrált, bejelentkezett felhasználóktól tudunk elfogadni!

Hozzászólások