Dynamisches Array kann nicht in einer Funktion genutzt werden?



  • mh probier mal eine temporäre variable

    char c;
    while (...)
    cin >> c;
    pArray[i] = c;
    

    nach dem schema
    wenn das auch nicht klappt, weiss ich leider auch nichts...
    der code sollte eigentlich richtig sein
    (ausser das das delte [] pArray am ende fehlt)



  • Dein Problem ist, dass pArray nicht pArray ist. Du legst eine Globale an, mit der du arbeiten willst. In deiner main-Funktion legst du aber einen lokalen Zeiger mit gleichem Namen an, dem du dann auch brav Speicher zuweist. Das ändert dann aber leider nix am globalen Zeiger, der zeigt noch weiterhin ins Nirwana.

    💡 Ein Debugger hätte helfen können, den Sachverhalt gleich aufzuklären. 💡



  • Heilige Mutter Theresa! Bin ich Blöd. 😮

    Vielen vielen Dank, ich wars irgendwie so von den Funktionen gewöht. Die deklariere ich am Anfang immer (void foo();) und nach der main-Funktion definiere ich sie (void foo(){...}) und jetzt habe ich auch bei der Zuweisung (char* pArray = new char [anzahl]) mein char* noch dabei.
    Und der globale Pointer zeigt, wie shcon erwähnt, in Nirvana. Was warschienlich auch der Grund dafür ist, dass der Compiler dazu nichts gesagt hat.
    Den Pointer musste ich allerdings global deklarieren, um ihn auch in einer Funktion nutzen zu können - oder?

    Wie auch immer, vielen Dank!



  • RungAaz schrieb:

    Den Pointer musste ich allerdings global deklarieren, um ihn auch in einer Funktion nutzen zu können - oder?

    Nein, es gibt dazu Parameterlisten. Globale Variablen sind selten nötig (ich habe nur in ganz wenigen Projekten je eine verwendet).

    Die Frage bleibt aber weiterhin warum du nicht mit std::string (oder notfalls einem std::vector<char>) arbeitest.



  • Nein, es gibt dazu Parameterlisten. Globale Variablen sind selten nötig (ich habe nur in ganz wenigen Projekten je eine verwendet).

    Stimmt auch wieder.

    Die Frage bleibt aber weiterhin warum du nicht mit std::string (oder notfalls einem std::vector<char>) arbeitest.

    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?
    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 😃



  • Ungetestet (aber absichtlich noch relativ einfach gehalten):

    #include <iostream>
    #include <vector>
    
    int main()
    {
        int anzahl;
        std::cin >> anzahl;
        std::vector<char> array(anzahl);
        read_chars(array);
    }
    
    void read_chars(std::vector<char> & array)
    {
        typedef std::vector<char>::iterator TIterator;
        for(TIterator pos = array.begin(), end = array.end(); pos < end; ++pos)
            std::cin >> *pos;
        std::cout << "\n\n";
        for(TIterator pos = array.begin(), end = array.end(); pos < end; ++pos)
            std::cout << *pos;
    }
    
    #include <iostream>
    #include <string>
    
    int main()
    {
        int anzahl;
        std::cin >> anzahl;
        std::string array(anzahl, '\0');
        read_chars(array);
    }
    
    void read_chars(std::string & array)
    {
        for(std::string::iterator it pos = array.begin(), end = array.end(); pos < end; ++pos)
            std::cin >> *pos;
        std::cout << "\n\n";
        for(std::string::iterator it pos = array.begin(), end = array.end(); pos < end; ++pos)
            std::cout << *pos;
    }
    


  • 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.


Anmelden zum Antworten