Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » FAQ C / C++ (ANSI-Standard) » Die Sache mit den Rückgabewerten

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 <
000
07.04.2003, 22:58 Uhr
virtual
Sexiest Bit alive
(Operator)


Die Sache mit den Rückgabewerten
Dieser Artikel befasst sich mit einem der Grundprobleme in der C Programmierung, nämlich daß man eine tolle Routine geschrieben hat, die irgendein Ergebnis liefert und man nicht recht weiß, wie man es denn zurückgegeben soll. Um die ganze Sache nicht zu trocken zu machen, sollen hier veschiedene Lösungsansätze der folgenden Aufgabe diskutiert werden. Wir werden sehen, daß es Ansaätze gibt, die stets falsch sind (obwohl sie vielleicht auf den ersten Blick sinnvoll erscheinen) und andere, die unter Umständen richtig sind. Wir werden aber keine Lösung finden, die jeder Anforderung immer gerecht wird.
Die Aufgabe lautet:

Zitat:

Schreibe eine Routine, die in einem String alle Vokale verdoppelt, also aus dem String "Hallo Welt!" den String "Haalloo Weelt!" generiert. Die Routine ist so zu gestalten, daß sie möglichst universel einsetzbar ist und möglichst wenig Anforderungen an den Aufrufer stellt.


Zum Einstieg wollen wir ein Program schreiben, welches zunächst mal ganz ohne Routine auskommt, sondern lediglich die argumente, die wir dem Programm übergeben, mit verdoppelten Vokalen ausgibt. Dies erlaubt uns, den notwendigen Algorithmus zur Vokalverdopplung mal ganz ohne das ganze Geraffel zu betrachten, das später auf uns zukommen wird:

C++:
/* vokdop.c 0.9 */
#include <stdio.h>

/*
* Diese routine wird häufg gebraucht und wird in den folgenden Beispielen nicht
* nochmals explizit erwähnt: sie erwartet ein Zeichen als Parameter und gibt
* einen Wert !=0 zurück, wenn das zeichen ein Vokal ist; ansonsten wird eine
* 0 zurückgegeben. Das "inline" bedeutet soviel wie: wenn die Funktion "auf-
* gerufen" wird, dann füge in das Programm nicht den Aufruf, sondern das, was
* in der Funktion steht ein. So gesehen ist die Routine istVokal ein typsicheres
* Macro. Natürlich kann man das inline auch weg lassen, was die ganze Sache
* marginal inperformanter macht ;)
*/

inline int istVokal(char zeichen)
{
    return (zeichen=='a' || zeichen=='A' ||
            zeichen=='e' || zeichen=='E' ||
            zeichen=='i' || zeichen=='I' ||
            zeichen=='o' || zeichen=='O' ||
            zeichen=='u' || zeichen=='U');
}

/*
* Das eigentliche Hauptprogram. argc ist die Anzahl der übergebenen Parameter +1
* argv[0] enthält den namen des Programms, argv[1]..argv[argc-1] die Parameter.
* Zur Deklaration von main siehe auch: main - Aufzucht und Pflege
*/

int main(int argc, char**argv)
{
    int k; /* Zählvariable */
    int j; /* noch eine */

    /*
     * Prüfe argc, das muß mindestens 2 sein, wenn ein argument übergeben
     * wurde. Ohne diesen Test riskieren wir den Zugriff auf nicht existenten
     * Speicher.
     */

    if (argc<2)
    {
        fprintf(stderr, "%s: Zuwenig Argumente.\nBenutzung: %s Text...\n", argv[0], argv[0]);
        exit(1);
    }

    /*
     * Achleife für alle Argumente, also argv[1] .. argv[argc-1]
     */

    [b]for(k=1; k<argc; ++k)
    {
        /*
         * Der Einfacheinheit halber nennen wir den zu behandelnden Text einfach str.
         * in len speichern wir die Länge des eingabe textes.
         */

        char* str = argv[k];
        int len = strlen(str);

        /*
         * Zur Kontrolle geben wir den text so wir er ist aus:
         */

        printf("%s => ", str);

        /*
         * In der Schleife wird nun jedes Zeichen geprüft, ob es ein Vokal ist oder
         * nicht. Wenn es ein Vokal ist, gegeben wir es zweimal aus. Wenn nicht,
         * eben nur einmal.
         */

                for(j=0; j<len; ++j)
        {
            /*
             * Das Zeichen jedenfalls einmal ausgeben...
             */

            putchar(str[j]);

            /*
             * Aber wenn es ein Vokal ist, dann nochmal
             */

            if (istVokal(str[j]))
            {
                putchar(str[j]);
            }
        }

        /*
         * Der text ist abgearbeitet - fange neue Zeile an
         */

        putchar('\n');
    }[/b]
}


Das Programm ist was länglich, vor allem wegen der Kommentare. Wo wir hin wollen ist, daß der Teil, der fett gedruckt ist, ersetzt wird durch:

C++:
for(k=1; k<argc; ++k)
{
    [b]char* vokdop = vokal_doppler(argv[k]);[/b]
    printf("%s => %s\n", argv[k], vokdop);
}
char* vokdop = [i]vokal_doppler(str)[/i];
printf("%s", vokdop);


Mit anderen Worten: die Routine vokal_doppler soll das tun, was die innere for Schleife getan hat, mit einem wichtigen Unterschied: sie soll den Ergebnisstring nicht direkt auf dem Bildschirm ausgeben, sondern irgendwie Speichern; die Ausgabe soll dann in main geschehen (wie oben gesehen).
Okay, fangen wir an, uns nach Implementationsmöglichkeiten für vokal_doppler umzuschauen:
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)

Dieser Post wurde am 07.04.2003 um 23:14 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
07.04.2003, 23:00 Uhr
virtual
Sexiest Bit alive
(Operator)


1. "Lösungsansatz" (Es ist noch kein Meister von Himmel gefallen!)
Der hier vorgestellte Lösungsansatz passt wie angegossen auf unser Problem, aber leider hat er nicht nur einfach Nachteile, sondern ist auch grottenfalsch. Ich erwähne ihn, weil er ein typischer Anfängerfehler ist:

C++:
/* vokdop.c 1.0 */
#include <stdio.h>

inline int istVokal(char zeichen)
{
...
}

/*
* Zur Kritik der Routine siehe text unten.
*/

char* vokal_doppler(const char* str)
{
    /*
     * buf ist ein char Array, welches in den meisten Fällen ausreichen sollte,
     * den Resultat string aufzunehmen. mit ptr haben wir eine Zeiger auf das
     * erste Zeichen in buf. Wir werden ptr als Zähl- und Zuweisungsvariable
     * benutzen. (Siehe schleife unten).
     */

    char buf[1024];
    char* ptr = buf;

    /*
     * Da wir grundätzlich sichere Routinen schreiben (rotfl), prüfen wir,
     * ob str wirklich gültig ist und kehren sofort zurück, wenn str ein
     * NULL Pointer ist.
     */

    if (NULL == str)
    {
        return NULL;
    }

    /*
     * Die schleifen bedingung bedeutet soviel wie:
     * while(str[0]!=0).
     * Also: solange das Zeichen, wohin str zeigt, soll die Schleife
     * durchlaufen werden. In der Schleife wird str immer um ein zeichen
     * weiter verschoben.
     */

    while (*str)
    {
        /*
         * Kopiert das aktuelle Zeichen von str nach buffer. Der str
         * Pointer wird auch gleich um einen weiter gezählt. Etwas länglicher
         * ausgedruckt steht da unten:
         * ptr[0] = str[0]; str++;
         */

        *ptr = *str++;

        /*
         * Okay, in *ptr steht noch da zuletzt kopiert Zeichen. Wenn
         * dies ein Vokal ist, dann kopieren wir es direkt in das nächste
         * char im Buffer. Da unten Steht:
         * if (istVokalptr[0]) { ptr[1] = ptr[0]; ptr++; }
         */

        if (istVokal(*ptr))
        {
            *(ptr+1) = *ptr;
            ptr++;
        }

        /*
         * ptr zeigt noch immer auf das zuletzt koipierte Zeichen. Um
         * für den nächsten Schleifendurchgang präpariert zu sein, müssen
         * wir aber ptr auf das folgende Zeichen zeigen lassen.
         */

         ptr++;
    }

    /*
     * Sieht man von dem null terminierenden Byte des Strings ab, ist der
     * String nun komplett. Wir schreiben also noch ein 0 Byte und geben
     * dann den String (buf zeigt ja auf den Anfang) zurück:
     */

    *ptr = 0;
    return buf;
}

int main(int argc, char**argv)
{
    int k;
    int j;

    if (argc<2)
    {
        fprintf(stderr, "%s: Zuwenig Argumente.\nBenutzung: %s Text...\n", argv[0], argv[0]);
        exit(1);
    }

    for(k=1; k<argc; ++k)
    {
        char* vokdop = vokal_doppler(argv[k]);
        printf("%s => %s\n", argv[k], vokdop);
    }
}


Okay, die Routine passt wie gesagt wie Faust aufs Auge, weil wir in main nichts
anpassen müssen; sie entspricht also genau unseren Vorstelllungen. Nun aber zu den Problemen,
die die Routine mit sich bringen wird:
1. Da ist zunächst Mal das Problem, das in der Routine vokal_doppler der Buffer nur 1024 chars umfasst. Man kann da also einen String rein tun, der 1023 Zeichen hat (die terminierende Null will ja auch noch was Platz). Das ist schon recht wichtig für den Aufrufer, weil das bedeutet, daß der Eingabestring nicht länger als 1023 - der Anzahl der Vokale in diesem String sein darf. Normalerweise wird man dann wohl in die Dokumentation schreiben, daß der String nicht länger als 511 Zeichen sein darf, weil wenn jedes Zeichen ein Vokal wäre, würde ich der String glatt verdoppeln.
2. Das Hauptproblem ist aber, daß die Routine in 99.9% der fälle nicht funktioniert und tut sie es doch, dann ist es Zufall: die Variable buf ist eine lokale Variable. Dh, sobald die Funktion vokal_doppler verlassen wird, ist die Variable, ihr Speicher und damit der Resultatstring nicht mehr existent. Das mag auf manchen Systemen erstmal nicht auffallen, ist aber ein ernstzunehmendes Problem, welches im Zweifel zum Programmabsturz führt.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)

Dieser Post wurde am 09.04.2003 um 21:49 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
07.04.2003, 23:00 Uhr
virtual
Sexiest Bit alive
(Operator)


2. Lösungsansatz (unbequem und statisch - aber funktioniert!)
Um zumindest den zuletzt genannten Nachteil vom 1. Lösungsansatz zum umgehen (immerhin ist das ja ein KO-Kriterium!), kann man auf die Idee kommen, den Speicher, den man für das Resultat braucht, der Funktion einfach mitzugeben. Leider muß man dann auch die Größe dieses Speichers angeben, wodurch die Routine etwas komplizierter zu bedienen ist:

C++:
/* vokdop.c 2.0 */
#include <stdio.h>

inline int istVokal(char zeichen)
{
    return (zeichen=='a' || zeichen=='A' ||
            zeichen=='e' || zeichen=='E' ||
            zeichen=='i' || zeichen=='I' ||
            zeichen=='o' || zeichen=='O' ||
            zeichen=='u' || zeichen=='U');
}

/*
* Zur Kritik der Routine siehe text unten.
*/

char* vokal_doppler(const char* str, char* buf, int size)
{
    char* ptr = buf;
    int i;                 /* Variablen ... */
    int len;            /* ... für diverse ... */
    int vokal_anzahl;    /* ... Zwecke */

    /*
     * Unser NULL pointer check...
     */

    if (NULL == str)
    {
        return NULL;
    }

    /*
     * Wir müssen nun erstmal prüfen, ob unser Egebnisbuffer auch wirklich
     * groß genug ist, das Ergebnis auch zu fassen. Dazu zählen wir die
     * vokale...:
     */

    vokal_anzahl = 0;
    len = strlen(str);
    for(i=0; i<len; ++i)
    {
        if (istVokal(str[ i ])) ++vokal_anzahl;
    }

    /*
     * Wenn die Größe nicht ausreicht, müssen wir hier abbrechen. Wir
     * geben eine dicke fette NULL zuück!
     */

    if (size<=len+vokal_anzahl+1)
    {
        return NULL;
    }

    /*
     * Wie aus dem 1. Ansatz
     */

    while (*str)
    {
        *ptr = *str++;
        if (istVokal(*ptr))
        {
            *(ptr+1) = *ptr;
            ptr++;
        }
         ptr++;
    }
    *ptr = 0;
    return buf;
}

int main(int argc, char**argv)
{
    int k;
    int j;

    /*
     * Neuerdings brauchen wir eine Buffer, in dem wir das Ergebnis verwalten.
    */

    char buffer[1024];

    if (argc<2)
    {
        fprintf(stderr, "%s: Zuwenig Argumente.\nBenutzung: %s Text...\n", argv[0], argv[0]);
        exit(1);
    }

    for(k=1; k<argc; ++k)
    {
        char* vokdop = vokal_doppler(argv[k], buffer, sizeof(buffer));

        /*
         * Es mag den Fall geben, daß der Platz in [i]buffer[/i] nicht ausreicht,
         * dann gibt [i]vokal_doppler[/i] NULL zurück. Diesen Fall müssen wir
         * checken:
         */

        if (NULL == vokdop)
        {
            fprintf(stderr, "Mist: Buffer overflow!");
        }
        else
        {
            printf("%s => %s\n", argv[k], vokdop);
        }
    }
}


Wie in den nun folgenden Ansätzen auch, kann die Routine "schief" gehen, weil ja rein theoretisch (manchmal auch praktisch) der Speicher, den wir für das Resultat vorgesehen haben, einfach nicht ausreicht.
Damit ist auch klar, daß die Routine nach wie vr nicht mit beliebig langen Zeichenketten klarkommt, dh der Aufrufer muß die Größe des Resultatbuffers entweder so groß dimensionieren, daß auf jedenfall die Buffer ausreicht (eigentlich unmöglich), oder aber er muß auf den Benutzer vertrauen, daß er schon nichts zu großes eingibt (eigentlich untragbar).
Ein weiterer Nachteil ist, daß wir uns nun doch sehr weit von unserer ursprünglichen Vorstellung gelöst haben: die Routine hat plötzlich drei an Stelle von einem Parameter. Besonders schön ist das ja nicht.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)

Dieser Post wurde am 09.04.2003 um 21:50 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
07.04.2003, 23:02 Uhr
virtual
Sexiest Bit alive
(Operator)


3. Lösungsansatz (Statische Variablen, aber dynamisch!)
Das im 1. Lösungansatz genannte KO-Kriterium, nämlich daß der Speicher nach Verlassen der Routine einfach futsch ist, kann man im Prinzip einfach dadurch umgehen, daß man die Variable buf in der Routine vokal_doppler static deklariert:

C++:
...
char* vokal_doppler(const char* str)
{
    [b]static[/b] char buf[1024];
    char* ptr = buf;
    ...


Nun wollen wir aber auch direkt dem anderen Nachteil (der Längenbegrenzung aus dem 1. und 2. Lösungansatz) begegnen und den Speicher je nach Bedarf neu allozieren. Zunächst mal der Source:

C++:
/* vokdop.c 3.0 */
#include <stdio.h>
#include <stdlib.h>

inline int istVokal(char zeichen)
{
    ...
}

/*
* Zur Kritik der Routine siehe text unten.
*/

char* vokal_doppler(char* str)
{
    /*
     * buf is initial NULL, ihm wird aber im Verlauf der Routine mit realloc
     * Speicher zugewiesen. size Enthält die Länge des Speichers, auf den
     * buf zeigt. Zu beachten ist, daß beide Variablen static sind; dh die
     * Werte der Variablen gehen zwischen den Functionsaufrufen nicht verloren
     */

    static char* buf = NULL;
    static int size = 0;

    /*
     * Einige weitere "normale" Variablen für versch. Zwecke.
     */

    char* ptr;
    int str_laenge;


    /*
     * Wie bereits gesagt, behalten staic Variablen über Funktionsaufrufe
     * hinweg ihre Werte. Natürlich bleibt auch der Speicher belegt, den
     * wir weiter unten mittels realloc belegen. Um diesen Speicher auch    
     * freigeben zu können (in der Regel bei Programmende, "missbrauchen"
     * wir den Fall, wenn ein NULL Pointer für str übergeben wird: wir
     * geben den Speicher dann frei. Die Anwendung muß daher am Ende,
     * bevor sie terminiert, einmal [i]vokal_doppler(NULL)[/i] aufrufen,
     * um Memory leaks zu beseitigen. In professioneller software könnte
     * man einen entsprechenden atexit-Handler installieren, was wir aber
     * hier mal nicht machen.
     */

    if (NULL == str)
    {
        if (NULL != buf)
        {
            free(buf);
            buf = NULL;
            size = 0;
        }
        return NULL;
    }

    /*
     * Unsere Routine soll diesmal mit unbegrenzt langen Strings klar kommen.
     * Vom 1. Lösungsansatz her wissen wir, daß der Resulatstring als maximale
     * die doppelte Länge von [i]str[/i] hat. Wir prüfen im folgenden also
     * ob [i]buf[/i] bereits genug Speicher enthält, um den Ergebnisstring
     * aufzunehmen.
     */

    str_laenge = strlen(str);
    if (size<2*str_laenge+1) /* +1 wegen terminierender Null */
    {
        /*
          * Der bisher belegte Platz is nicht doppelt so groß wie der String,
         * folglich müssen wir ggf. etwas mehr Speicher belegen. Aber im Speicher
         * zu sparen, berechenen wir, wie lang der String tatsächlich werden
         * wird (wir zählen also die Vokale). Mit ein wenig Glück reicht
         * der bisher belegte Speicher ja doch... ;)
         */

        int i;
        int vokal_anzahl = 0;
        for(i=0; i<str_laenge; ++i)
        {
            if (istVokal(str[ i ])) ++vokal_anzahl;
        }

        /*
         * Wenn die vokalanzahl == 0 ist, brauchen wir eigentlich garnichts
         * zu tun und können [i]str[/i] zurückgeben.
         */

        if (0 == vokal_anzahl)
        {
            return str;
        }

        /*
         * Ansonsten ist sicherzustellen, daß der Speicher ausreicht:
         */

        if (size<str_laenge+vokal_anzahl+1)
        {
            /*
             * Oh - wir müssen den Speicher vergrößern. Das geht mit realloc:
             * Wir speichern zunächst in tmp den neuen Speicher. Bei realloc
             * ist es so, daß tmp==NULL ist, wenn kein Speicher da ist. Dann
             * Zeigt [i]buf[/i] aber immer noch auf den alten Speicher.
             * Funktioniert realloc hingegen, ist der Speicher, auf den [i]buf[/i]
             * zeigt ungültig, die neue Address steht dann in [i]tmp[/i].
             */

            char* tmp = realloc(buf, str_laenge+vokal_anzahl+1);
            if (NULL == tmp)
            {
                /* Mist: kein Speicher */
                return NULL;
            }
            else
            {
                /* Okay: neuen Speicher merken; auch dessen Länge */
                size = str_laenge+vokal_anzahl+1;
                buf = tmp;
            }

        }
    }

    ptr = buf;

    /*
     * Um es kurz zu machen, habe ich hier mal die Kommentare entfernt, es ist das gleiche
     * wie im ersten Lösungsansatz...
     */

    while (*str)
    {
        *ptr = *str++;
        if (istVokal(*ptr))
        {
             *(ptr+1) = *ptr;
            ptr++;
        }
        ptr++;
    }
    *ptr = 0;
    return buf;
}

int main(int argc, char**argv)
{
    int k;
    int j;

    if (argc<2)
    {
        fprintf(stderr, "%s: Zuwenig Argumente.\nBenutzung: %s Text...\n", argv[0], argv[0]);
        exit(1);
    }

    for(k=1; k<argc; ++k)
    {
        char* vokdop = vokal_doppler(argv[k]);
        /*
         * Wir arbeiten ja neuerdings mit dynamischen Speicher. Da kann einiges
         * schief gehen, zB kein Speicher mehr da. In diesem Fall gibt [i]vokal_doppler[/i]
         * einen NULL Pointer zurück. Dies müssen wir prüfen:
         */

        if (NULL == vokdop)
        {
            fprintf(stderr, "Mist: Kein Speicher!\n");
        }
        else
        {
            printf("%s => %s\n", argv[k], vokdop);
        }
    }

    /*
     * Wie bereits in der BEschreibung zu [i]vokal_doppler[/i] erwähnt, müssen
     * wir am ende den Speicher aus den static Vairablen freigeben:
     */

    vokal_doppler(NULL);
}


Die Lösung hat durchaus ihre Vorteile: Zum einen ist sie korrekt und kommt auch mit beliebig langen Strings klar. Und tatsächlich wird auch der Trick mit den static Variablen in manchen Systembibliotheken angewendet. Auch die Überprüfung, ob der Rückgabewert NULL ist (also ob die Funktion fehlschlug), ist nicht wirklich eine tragische Sache; damit hat man eigentlich immer zu tun, wenn man mit dynamischen Speicher zu tun hat. Ärgerlich ist sicherlich das vokal_doppler(NULL), welches wohl ganz gerne mal vergessen werden. Hinterlistiger sind da aber zwei Dinge die aus der Verwendung von static Variablen resultieren:
static Varaiblen sind hinsichtlich ihrere Eigenschaften globalen Varaiblen sehr ähnlich. Der einzige Unterschied besteht eigentlich nur darin, daß ihr Name nur innerhalb der umgebenden Funktion bekannt ist. Aber ihr Inhalt ist eben doch global. So kommt es, daß zwei aufeinanderfolgende Aufrufe von vokal_doppler die alten resultate überschreibt. Die Rückgabewerte bleiben also nur solange gültig, bis die Funktion erneut aufgerufen wird. Will man das Ergebnis länger aufbeahren, muß man eine Kopie anlegen.
Ein weiterer Nachteil ist, daß Funktionen, die static Variablen verwenden, ohne weitere Vorkehrungen generell nicht reentrant sind. Das beduetet konkret, daß man sie nicht in einer Multithreaded Application benutzen kann, weil zwei Threads sich gegenseitig den Speicher überschreiben.
Stören diese Beiden Minuspunkte nicht, ist die Lösung mit den static Variablen aber durachaus elegant und hat auch - bei guter Implementierung - performance Vorteiel gegenüber Versionen von vokal_doppler, die jedesmal neue Speicherbereiche anlegen.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)

Dieser Post wurde am 09.04.2003 um 21:53 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
07.04.2003, 23:03 Uhr
virtual
Sexiest Bit alive
(Operator)


[4. Lösungsansatz (Rundum dynamisch!)
Der 4. Lösungsansatz ist wohl der flexibelste von den reinen C Ansätzen. Hier kurz der Source:

C++:
/* vokdop.c 4.0 */
#include <stdio.h>
#include <stdlib.h>

inline int istVokal(char zeichen)
{
    ...
}

/*
* Zur Kritik der Routine siehe text unten.
*/

char* vokal_doppler(char* str)
{
    /*
     * In diesen Variablen merken wir uns größe und Lage des Resultats.
     * Wir vertrauen auf den Aufrufer, daß er doch bitte den Speicher
     * dann freigibt; wir schenkn ihn ihm.
     */

    char* buf = NULL;
    int size = 0;

    /*
     * Einige weitere Variablen
     */

    int len;
    int i;
    char* ptr;
    
    /*
     * Unser traditioneller NULL pointer check
     */

    if (NULL == str)
    {
        return NULL;
    }

    /*
     * Wir berechnen nun - wie schon in den Beispieln zuvor gesehen,
     * die länge des benötigten Speichers, danach belegen wir ihn
     */

    len = strlen(str);
    size = len+1;
    for(i=0; i<len; ++i)
    {
        if (istVokal(str[ i ])) ++size;
    }
    buf = malloc(size);
    if (NULL == buf)
    {
        return NULL;
    }

    ptr = buf;

    /*
     * Um es kurz zu machen, habe ich hier mal die Kommentare entfernt, es ist das gleiche
     * wie im ersten Lösungsansatz...
     */

    while (*str)
    {
        *ptr = *str++;
        if (istVokal(*ptr))
        {
             *(ptr+1) = *ptr;
            ptr++;
        }
        ptr++;
    }
    *ptr = 0;
    return buf;
}

int main(int argc, char**argv)
{
    int k;
    int j;

    if (argc<2)
    {
        fprintf(stderr, "%s: Zuwenig Argumente.\nBenutzung: %s Text...\n", argv[0], argv[0]);
        exit(1);
    }

    for(k=1; k<argc; ++k)
    {
        char* vokdop = vokal_doppler(argv[k]);
        /*
         * Wir belegen Speicher. das kann schief gehen. Deshalb wie üblich ein
         * test, ob es geklappt hat. Ansonsten müssen wir jedenfalls den Speicher
         * freigeben!
         */

        if (NULL == vokdop)
        {
            fprintf(stderr, "Mist: Kein Speicher!\n");
        }
        else
        {
            printf("%s => %s\n", argv[k], vokdop);
            free(vokdop);
        }
    }
}


Rein von der Funktionalität her läßt er keine Wünsche offen. Er mag zwar nicht ganz so performant sein, wie der 3. Ansatz, aber er ist multithreading fähig und kann auch mehrmals hinterander aufgerufen werden, ohne das das Ergebnis vorheriger Calls überschrieben wird. Allerdings ist das Memoryleak, welches man bekommt, wenn man das free() im main vergisst natürlcih größer - es wächst pro Funktionsaufruf. Aber da wir ja alle gute C Programmierer sein wollen, wollen wir es einfach nicht vergessen. . Auch wenn dies die flexibelste Lösung ist, ist sie unter Garantie die langsamste, weil immer neuer Speicher belegt wird und freigegeben wird. Speicheroperationen sind im Vergleich zu dem Zeitaufwand, den die eigentlich Routine benötigt, sehr hoch.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)

Dieser Post wurde am 09.04.2003 um 21:54 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
005
09.04.2003, 21:53 Uhr
virtual
Sexiest Bit alive
(Operator)


5. Lösungsansatz (OO)
Nich vorenthalten möchte ich die C++ Version, ohne weitere Kommentare. Der Destructor von std::string bewahrt und von Memoryleaks. Im Vergleich zu den anderen Routinen aber langsamer:

C++:
// vokdop.cpp 5.0 */
#include <iostream>
#include <string>


std::string vokal_doppler(const std::string& str)
{
    std::string res;
    std::string::size_type pos = 0;
    std::string::size_type lastpos = 0;

    while(std::string::npos != (pos=str.find_first_of("aeiouAEIOU", lastpos)))
    {
        res += str.substr(lastpos, pos-lastpos+1);
        res += str[pos++];
        lastpos = pos;
    }
    if (lastpos!=str.length()-1) res += str.substr(lastpos);
    return res;
}


int main(int argc, char**argv)
{
    int k;
    int j;

    if (argc<2)
    {
        std::cerr << argv[0]<<": Zuwenig Argumente.\nBenutzung:"<<argv[0]<<" Text..."<<std::endl;
        exit(1);
    }
    for(k=1; k<argc; ++k)
    {
        std::cout << argv[k] << " => " << vokal_doppler(argv[k]) << std::endl;
    }
}


--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 <     [ FAQ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: