Dynamisches Array kann nicht in einer Funktion genutzt werden?



  • OK, also das ist etwas zu hoch für mich...
    Ich habe früher Vector benutzt, hat super funktioniert, habe aber anders als du, die Methode vector::push_back(char chr) oder so ähnlich.



  • RungAaz schrieb:

    OK, also das ist etwas zu hoch für mich...
    Ich habe früher Vector benutzt, hat super funktioniert, habe aber anders als du, die Methode vector::push_back(char chr) oder so ähnlich.

    Da du mit einer fixen Länge hantierst, habe ich die Größe im jeweiligen Konstruktor angegeben. Und anschließend habe ich die Iteratoren (eines der Themen die man von der STL kennen sollte) zum durchlaufen benutzt. Wirklich komplizierter als dein Code ist es auch nicht (nur noch ungewohnt für dich). Du kannst natürlich auch mit dem Indexoperator arbeiten...



  • eines der Themen die man von der STL kennen sollte
    

    Ich glaube, das sollte ich mir mal demnächst anschauen.
    Danke.



  • RungAaz schrieb:

    eines der Themen die man von der STL kennen sollte
    

    Ich glaube, das sollte ich mir mal demnächst anschauen.
    Danke.

    An sich ist das Konzept recht einfach. Einen Iterator kannst du dir wie einen Zeiger vorstellen, der immer eine Position weiter gesetzt wird (Bei linear aufgebauten Datencontainern kann dies auch tatsächlich ein Zeiger sein, aber spätestens bei dynamischen Listen ist es eine Klasse).

    Jeder STL-Container (std::string, std::vector, std::list, std::map...) hat die Typdefinitionen iterator und const_iterator (letztere wenn du keine Änderungen am Element durchführen willst, habe ich aber hier ignoriert, die zweite Schleife hätte ein solchen verwenden können).

    Das heißt std::vector<char>::iterator ist beispielsweise eine Typdefinition des Iterators der im std::vector<char> verwendet wird.

    begin() liefert den Iterator auf das erste, end() den auf das Element HINTER dem letzten (Sprich: ein ungültiges!). Die Abbruchbedingung ist also, das man, wenn dieses erreicht wird, nichts mehr damit anstellt 😉

    #include <iostream>
    #include <vector>
    
    int main()
    {
        int anzahl;
        std::cin >> anzahl;
        std::vector<char> array(anzahl); // Konstruktor mit "anzahl" Elementen.
        read_chars(array);
    }
    
    void read_chars(std::vector<char> & array) // Als Referenz, um es direkt zu
    {                                          // manipulieren
        // Zur Verkürzung habe ich ein typdef verwendet.
        typedef std::vector<char>::iterator TIterator;
    
        // Stell dir den TIterator erst einmal als Zeiger vor. "pos" wird auf
        // den ersten Eintrag, end auf den Eintrag HINTER dem letzten gelegt.
        //
        // Dieser Iterator wird mit jedem Durchlauf um ein Element weiter gesetzt,
        // und es wird abgebrochen wenn pos==end ist.
        for(TIterator pos = array.begin(), end = array.end(); pos != end; ++pos)
            std::cin >> *pos; // *<iterator> liefert den Wert.
    
        std::cout << "\n\n";
        for(TIterator pos = array.begin(), end = array.end(); pos != end; ++pos)
            std::cout << *pos;
    }
    

    Der Vorteil an Iterator ist, das sie auch auf Klassen funktionieren, deren Speicher nicht linear aufgebaut ist, und es gibt sogar Iteratoren für das einlesen aus Streams etc.



  • Danke, jetzt peil ich besser durch.

    Was aber, wenn ich zwei Pointer erstellen würde:

    std::vector<char>* pPos = NULL, pEnd = NULL;
    

    Die dann jeweils auf den Anfang und das Element hinter dem letzten Element zeigen:

    pPos = array.begin();
    pEnd = array.end();
    

    Und dann in folgende Schleife einfügt:

    int i (0);
    while (i != pEnd)
    {
        std::cin >> *(pPos+i);
        i++;
    }
    

    Würde das auf funktionieren?
    Oder funktioniert das nicht, weil es ganz normale Pointer sind?


  • Mod

    Du kannst im Falle von std::vector Pointer äquivalent zu Iteratoren benutzen, ja. Der Vorteil der Iteratoren ist, dass du später einfach mal den Container wechseln kannst, ohne irgendwo im Programm etwas zu ändern.

    Dein kleines Beispiel wird jedoch nicht funktionieren, da du nicht einen Iterator einem Pointer zuweisen kannst, du musst schon die Adressen der Objekte nehmen auf die sich die Iteratoren beziehen.

    P.S.: Und was soll denn das geNULLe bei den Pointern? Das deutet irgendwie darauf hin, dass du Pointer nicht richtig verstanden hast und da lieber erstmal die Finger von lassen solltest. Mit Iteratoren fährst du wesentlich fehlerrobuster und anfängerfreundlich.



  • RungAaz schrieb:

    Was aber, wenn ich zwei Pointer erstellen würde:

    std::vector<char>* pPos = NULL, pEnd = NULL;
    

    Warum nimmst du nicht die beschriebenen Iteratoren, und versuchst dich krampfhaft an den Zeigern aufzuhängen?



  • P.S.: Und was soll denn das geNULLe bei den Pointern? Das deutet irgendwie darauf hin, dass du Pointer nicht richtig verstanden hast und da lieber erstmal die Finger von lassen solltest. Mit Iteratoren fährst du wesentlich fehlerrobuster und anfängerfreundlich.

    Laut einem Tutorial sollte man Pointer, sofern man sie nicht gleich zuweist immer auf NULL setzten, weil die sonst irgendwo hindeuten - oder nicht?

    Warum nimmst du nicht die beschriebenen Iteratoren, und versuchst dich krampfhaft an den Zeigern aufzuhängen?

    Keine Angst, ich nehme schon die Iteratoren. Mich hat es lediglich interessiert, ob es auch auf eine andere Weise geht, da du ja schon vorher erwähnt hast, dass diese Teile einem Pointer sehr ähnlich sind.


  • Mod

    RungAaz schrieb:

    P.S.: Und was soll denn das geNULLe bei den Pointern? Das deutet irgendwie darauf hin, dass du Pointer nicht richtig verstanden hast und da lieber erstmal die Finger von lassen solltest. Mit Iteratoren fährst du wesentlich fehlerrobuster und anfängerfreundlich.

    Laut einem Tutorial sollte man Pointer, sofern man sie nicht gleich zuweist immer auf NULL setzten, weil die sonst irgendwo hindeuten - oder nicht?

    Schlechtes Tutorial. Du als Programmierer musst bei solcher Art von Zeigerbenutzung jederzeit selber wissen ob dein Zeiger auf ein gültiges Objekt zeigt. Das geNULLe ist Cargo Cult von Leuten die das mal bei gewissen Datenstrukturen wie verketteten Listen gesehen haben, aber nicht verstanden haben, warum das dort Sinn macht.

    Die Technik mit dem NULLen von ungültigen Zeigern bewährt dich zwar vor sofortigem Programmabsturz im Fehlerfall, aber dennoch ist in diesem Fall ein Fehler in der Programmlogik aufgetreten. Und das schlimmste ist: Nun bekommst du es noch nicht einmal mit und dein Programm bleibt fehlerhaft!



  • Ok, das stimmt auch wieder.
    Also ist und bleibt die sinnvollste Verwendung von einem NULL-Wert, die Parameterübergabe bei Funktionen. Falls in der Parameterliste ein Pointer existiert, den man beim Aufruf grade nicht braucht - oder?


  • Mod

    Es gibt viele sinnvolle Anwendungen von Nullpointern und ich kenne sicherlich nicht alle. Ich wollte bloß klarstellen, dass dies hier keine ist.



  • Achso, ok, dann werde ich mir das mal merken.



  • RungAaz schrieb:

    Nun, ich hatte das ganze früher mit dem Vector-Template (wie von dir schon erwähnt) gemacht, aber warum eine zusätzliche Header-Datei einbinden wenn es bei C++ schon standartmäßig den new-Operator gibt?

    😮
    new[] und delete[] sind "low level" Operationen. Ein vektor ist ein "high level" Konzept. Da gibt es schon genug Unterschiede, die für vektor sprechen, zB:
    - Verwaltung des reservierten Speichers übernimmt der Vektor
    - C++ Implementierungen bieten oft einen "STL Debug-Modus" mit "sicheren Iteratoren" und zusätzlichen Bereichsüberprüfungen an.

    RungAaz schrieb:

    std::string habe ich nicht verwendet, da ich die Buchstaben einzeln brauche.
    Wobei ich die Zeichen, die ein String enthält auch einzeln Manipulieren kann - oder?
    Naja, warum einfach, wenn es auch kompliziert geht 😃

    Genau. Das ist die Frage. Warum new[]/delete[] benutzen, wenn Du auch vector oder string hättest nehmen können? Dass Du einen zusätzlichen Header einbinden musst ist kein guter Grund.

    kk



  • krümelkacker schrieb:

    new[] und delete[] sind "low level" Operationen. Ein vektor ist ein "high level" Konzept.

    low und high levels sollten nie ein Entscheidungskriterium sein, weil wie hier bei Vorliegen weiterer Gründe völlig irrelevant, und bei Nichtvorliegen stets zu Fehlschlüssen führend.



  • Also, auch wenn ich mir das Leben mit new und delete schwerer mache als es ist, bleibt die Erfahrung, die ich damit mache, und daruaf kommt es doch hauptsächlich an - oder nicht?

    Beispielsweise habe ich dadurch erfahren, dass ein Grund für das Verrecken des Programms während der Runtime das Nicht- bzw. Reinitialisieren eines Pointers sein kann.



  • RungAaz schrieb:

    Also, auch wenn ich mir das Leben mit new und delete schwerer mache als es ist, bleibt die Erfahrung, die ich damit mache, und daruaf kommt es doch hauptsächlich an - oder nicht?

    Beispielsweise habe ich dadurch erfahren, dass ein Grund für das Verrecken des Programms während der Runtime das Nicht- bzw. Reinitialisieren eines Pointers sein kann.

    Lass dir nichts einreden. Natürlich sind STL-Container normalerweise einfacher, weniger fehleranfällig und daher sinnvoller. Das heißt aber nicht, dass hier ständig Anfängern geraten werden muss, bloß nie - und sei es auch in Übungsprogrammen - new/delete/new[]/delete[] zu benutzen. Egal, mit wievielen 😮 - Smileys das hier immer unterstrichen wird, diesen Weg sollte man schon kennenlernen.



  • Wichtig ist, dass du zwischen Herumexperimentieren/Lernen/Erfahrung sammeln und produktivem Einsatz unterscheidest. Der erste Teil darf keineswegs zu kurz kommen. Doch wenn es für eine konkrete Problemstellung darum geht, mehrere Objekte gemeinsam zu verwalten, sind die STL-Container im Allgemeinen die bessere Wahl, da sie genau diese Anforderung erfüllen und dich von hintergründigen Aufgaben wie Speicherverwaltung entlasten.


Anmelden zum Antworten