Fehlersuche: Strings nach Trennzeichen aufteilen



  • Hallo Forum! 🙂

    Ich habe hier schon öfter mitgelesen und mich jetzt mal angemeldet, da ich einen mir unbekannten Fehler habe und diesen seit mehreren Tagen nicht gelöst bekomme.

    Kurz zu mir: Ich bin Student aus NRW und wechsel zum kommenden WS die Uni und den Studiengang. Ab dem WS studiere ich an Informatik und freue mich schon. Da ich nicht ganz planlos anfangen möchte, habe ich mir vorab schon mal einiges an Materialien besorgt und nutze die Zeit bis zum Start für die Vorbereitung. Als HiWi wurde ich auf C++ und die Informatik erst so richtig aufmerksam und habe mich dann für einen Wechsel entschieden.

    So nun aber zum eigentlichen Problem: Ich schreibe ein Programm welches einen String und ein Trennzeichen vom Benutzer einliest und dann den String an besagter Stelle in 2 Strings aufteilt. Das Trennzeichen wird dabei verworfen. Leider bekomme ich in Zeile " erster_teil.at(i) = eingabe.at(i);" der "teilen.cpp" immer eine Fehlermeldung:

    "Ausnahme ausgelöst bei 0x7492DDC2 in Offline-06-01.exe: Microsoft C++-Ausnahme: std::out_of_range bei Speicherort 0x010BF49C.
    Unbehandelte Ausnahme bei 0x7492DDC2 in Offline-06-01.exe: Microsoft C++-Ausnahme: std::out_of_range bei Speicherort 0x010BF49C."

    Ich würde mich sehr freuen, wenn jemand vlt einen Tipp hat wo der Fehler liegen könnte. 🙂

    Main:

    #pragma once
    
    #include <iostream>
    #include <string>
    #include "teilen.h"
    
    using namespace std;
    
    int main()
    {
        char zeichen;
        string eingabe;
        string erster_teil;
        string zweiter_teil;
    
        cout << "Bitte geben Sie die einzeilige Zeichenkette ein: ? ";
        getline(cin, eingabe);
        cout << "Bitte geben Sie das Zeichen ein: ? ";
        cin >> zeichen;
    
        teilen(zeichen, eingabe, erster_teil, zweiter_teil);
    
        cout << "Der erste Teil der Zeichenkette lautet: " << erster_teil << endl;
        cout << "Der zweite Teil der Zeichenkette lautet: " << zweiter_teil << endl;
    
        system("pause");
        return 0;
    }
    

    teilen.cpp:

    #pragma once
    
    #include <string>
    
    
    void teilen(char zeichen, std::string eingabe, std::string &erster_teil, std::string &zweiter_teil)
    {
        int pos_ende = eingabe.length();
        int g = 0;
    
        for (unsigned int i = 0; i <= eingabe.length(); i++) // string eingabe auf trennzeichen durchsuchen
        {
    
            if (eingabe.at(i) == zeichen)
            {
                pos_ende = i;
                break;
            }
    
        }
        
        for (unsigned int i = 0; i <= pos_ende; i++) // erster teil abtrennen
        {
            
            erster_teil.at(i) = eingabe.at(i);
        }
    
        erster_teil.at(pos_ende) = '\0';
    
        for (unsigned int i = pos_ende + 1; i <= eingabe.length(); i++)
        {
            zweiter_teil.at(g) = eingabe.at(i);
            g++;
        }
    
        zweiter_teil.at(eingabe.length() + 1) = '\0';
    }
    

    teilen.h:

    #pragma once
    #include <string>
    
    void teilen(char zeichen, std::string eingabe, std::string& erster_teil, std::string& zweiter_teil);
    

    Liebe Grüße und vielen Dank 🙂



  • Du kannst nicht auf das n-te Zeichen eines Strings zugreifen, wenn dieser String leer ist. Also entweder die Zielstrings vorher auf die richtige Größe bringen (std::string::resize()) oder die Zeichen "anhängen" mit dem +=-Operator.



  • @duffy sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    erster_teil.at(pos_ende) = '\0';

    std::string muss man nicht mit '\0' terminieren.



  • In deiner teilen()-Funktion erfindest du das Rad neu. Im Übrigen möchtest du auf kleiner als (<) statt auf kleiner-gleich-als (<=) überprüfen, da du sonst einmal zu viel indizierst.

    Besser du greifst auf die Funktionen std::string::find und std::string::substr zurück, damit bist du hundert Mal schneller unterwegs.



  • @swordfish sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    Du kannst nicht auf das n-te Zeichen eines Strings zugreifen, wenn dieser String leer ist. Also entweder die Zielstrings vorher auf die richtige Größe bringen (std::string::resize()) oder die Zeichen "anhängen" mit dem +=-Operator.

    Das es daran eventuell liegen könnte kam mir auch in den Sinn (habs dann jedoch scheinbar fälschlicherweise verworfen)... Ich werde es mal mit dem += Operator ausprobieren und das Ergebnis dann mitteilen! Vielen Dank 🙂

    @manni66 sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    @duffy sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    erster_teil.at(pos_ende) = '\0';

    std::string muss man nicht mit '\0' terminieren.

    Danke für den Tipp. 🙂
    Dachte ich mache das vorsichtshalber... aber ist unnötig hast du recht!

    @spiri sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    In deiner teilen()-Funktion erfindest du das Rad neu. Im Übrigen möchtest du auf kleiner als (<) statt auf kleiner-gleich-als (<=) überprüfen, da du sonst einmal zu viel indizierst.

    Besser du greifst auf die Funktionen std::string::find und std::string::substr zurück, damit bist du hundert Mal schneller unterwegs.

    Es ist reine Übungsaufgabe aus den Uni-Materialien. Die wollen leider dass wir das Rad neu erfinden. 😉 😃
    Ich wollte eigentlich bewusst einmal mehr indizieren weil ich das Trennzeichen mit übernehmen wollte, um diesen dann in '\0' zu überschreiben. Dies hat sich aber mit dem neuen Gedankengang erübrigt und ich werde es wieder ändern.



  • Hallöchen,

    danke nochmal für eure ganzen Tipps 🙂 Ich habe die teilen.cpp folgendermaßen angepasst:

    #pragma once
    
    #include <string>
    
    
    void teilen(char zeichen, std::string eingabe, std::string &erster_teil, std::string &zweiter_teil)
    {
    	int pos_ende = eingabe.length();
    
    	for (int i = 0; i < eingabe.length(); i++) // string eingabe auf trennzeichen durchsuchen
    	{
    		if (eingabe.at(i) == zeichen)
    		{
    			pos_ende = i;
    			break;
    		}
    	}
    
    	for (int i = 0; i < pos_ende; i++) // erster teil abtrennen
    	{
    
    		erster_teil += eingabe.at(i);
    	}
    
    	for (int i = pos_ende + 1; i < eingabe.length(); i++)
    	{
    		zweiter_teil += eingabe.at(i);
    	}
    
    }
    

    Nun lässt sich das Programm jedoch leider nicht mehr kompilieren. 😞
    Ich bekomme folgende Fehlermeldung:

    [...]teilen.cpp(10): warning C4018: "<": Konflikt zwischen "signed" und "unsigned"
    [...]teilen.cpp(27): warning C4018: "<": Konflikt zwischen "signed" und "unsigned"

    Das bekomm ich unabhängig davon, ob ich jetzt in den Zeilen <= oder < schreibe bzw. unabhängig davon ob ich einen signed oder unsigned int habe. 🤔



  • pos_ende und die diversen is sollten vom Typ std::size_t sein. Das ist auch, was zB std::string::length() zurückgibt.



  • Das sind Warnungen, keine Fehlermeldungen. Das Programm sollte also übersetzt sein. Verwende std::size_tstatt ìnt für i und pos_ende, dann sollte es ohne Warnung übersetzen.
    Wenn man ganz korrekt sein will, müsste es std::string::size_type sein, das aber in der Regel wieder nur std::size_tist.



  • @duffy sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    "Ausnahme ausgelöst bei 0x7492DDC2 in Offline-06-01.exe: Microsoft C++-Ausnahme: std::out_of_range bei Speicherort 0x010BF49C.
    Unbehandelte Ausnahme bei 0x7492DDC2 in Offline-06-01.exe: Microsoft C++-Ausnahme: std::out_of_range bei Speicherort 0x010BF49C."

    Ist ja eigentlich ganz simpel. Du hast eine Ausnahme bei 0x7492DDC2 in dem Programm Offline-06-01.exe ausgelöst und zwar einen out_of_range Fehler am Speicherort 0x010BF49C. Den hast du dann auch nicht behandelt. Was an diesen Stellen im Speicher genau steht, kannst du sehr simpel mit einem Debugger nachschauen. Dann dürfte dir eigentlich sofort klar sein, was da schief geht.



  • @swordfish sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    pos_ende und die diversen is sollten vom Typ std::size_t sein. Das ist auch, was zB std::string::length() zurückgibt.

    Geht da nicht auch einfach unsigned int? Laut Internet ist size_t doch auch ein Typ von unsigned int...

    @manni66 sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    Das sind Warnungen, keine Fehlermeldungen. Das Programm sollte also übersetzt sein. Verwende std::size_tstatt ìnt für i und pos_ende, dann sollte es ohne Warnung übersetzen.
    Wenn man ganz korrekt sein will, müsste es std::string::size_type sein, das aber in der Regel wieder nur std::size_tist.

    Gleiches wie oben.


    Hat sich bereits erledigt. Das Programm hat nicht kompiliert weil ich eine andersnamige .cpp Datei im Quellordner hatte welche die gleiche Funktion definiert hatte aber mit anderen Inhalten. Diese war zwar nicht eingebunden aber hat wohl irgendwie Probleme verursacht...

    Das Programm läuft jetzt auch mit den int-Werten Problemlos aber ich werd mich aufjedenfall mal mit den Alternativen auseinander setzen...

    Vielen Dank an alle für die tolle Hilfe! 🙂
    War bestimmt nicht mein letztes mal. :face_savouring_delicious_food:



  • @tggc sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    @duffy sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    "Ausnahme ausgelöst bei 0x7492DDC2 in Offline-06-01.exe: Microsoft C++-Ausnahme: std::out_of_range bei Speicherort 0x010BF49C.
    Unbehandelte Ausnahme bei 0x7492DDC2 in Offline-06-01.exe: Microsoft C++-Ausnahme: std::out_of_range bei Speicherort 0x010BF49C."

    Ist ja eigentlich ganz simpel. Du hast eine Ausnahme bei 0x7492DDC2 in dem Programm Offline-06-01.exe ausgelöst und zwar einen out_of_range Fehler am Speicherort 0x010BF49C. Den hast du dann auch nicht behandelt. Was an diesen Stellen im Speicher genau steht, kannst du sehr simpel mit einem Debugger nachschauen. Dann dürfte dir eigentlich sofort klar sein, was da schief geht.

    Habe ich im Debugger durchlaufen lassen. Diese Fehlermeldung kam dann an exakt dieser Stelle ohne weitere Meldungen. Ich weiß ja mittlerweile, dass ich nicht auf die Positionen im String zugreifen konnte weil der String leer war. 😉
    Ärgere mich, dass ich da nicht selber draugekommen bin... manchmal sieht man den Wald vor lauter Bäumen nicht und mir fehlt da wohl noch die Routine.



  • @duffy sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    Geht da nicht auch einfach unsigned int? Laut Internet ist size_t doch auch ein Typ von unsigned int...

    Wenn irgendwo steht, std::size_t sei ein unsigned int dann ist das falsch. Ich gehe aber davon aus, daß Du unsigned integer gelesen hast und Dein Kopf daraus automagisch unsigned int gemacht hat. Gemeint ist irgendein vorzeichenloser, ganzzahliger Typ, der groß genug für alle Objekte ist, die im Speicher rumliegen können. Oft wird es wohl ein std::uint64_t sein.

    §21.2.4 (3):

    The type size_t is an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object.

    Ähnlich wie von @spiri vorgeschlagen wäre meine Antwort auf die Aufgabenstellung aber

    bool teilen(char trennzeichen, std::string const & eingabe, std::string & erster_teil, std::string & zweiter_teil)
    {
    	auto trennzeichen_position{ eingabe.find(trennzeichen) };
    	
    	if (trennzeichen_position == eingabe.npos)
    		return false;
    
    	erster_teil = std::string{ eingabe.begin(), eingabe.begin() + trennzeichen_position };
    	zweiter_teil = std::string{ eingabe.begin() + trennzeichen_position + 1, eingabe.end() };
    	return true;
    }
    


  • @swordfish Erwischt^^
    Ich mach mich da nochmal schlau... Danke!



  • Ich finde die Sonderfallbehandlung bei @swordfish nicht intuitiv. Wenn kein Trennzeichen vorhanden, ändere die Ausgabestrings nicht? Wie soll dann der Aufrufer ermitteln, ob die beiden Ausgabestrings korrekt gesetzt sind oder unverändert geblieben sind? Daher würde ich eher so vorgehen, dass ich in diesem Fall ersterTeil=eingabe, zweiterTeil="" setzen würde.

    Wobei allgemein die Frage auch ist, ob man nicht eher einfach ein pair<string(_view), string(_view)> zurückgeben sollte. Und natürlich den eingabestring als const-ref nehmen! 4 Pflicht-Parameter sind schon ganz schön viel! Zu _sv: bin mir nicht so sicher, sollte man das tun? Was ist, wenn jemand die Funktion mit einem Temporary aufruft?



  • @wob sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    Ich finde die Sonderfallbehandlung bei @swordfish nicht intuitiv. Wenn kein Trennzeichen vorhanden, ändere die Ausgabestrings nicht? Wie soll dann der Aufrufer ermitteln, ob die beiden Ausgabestrings korrekt gesetzt sind oder unverändert geblieben sind? Daher würde ich eher so vorgehen, dass ich in diesem Fall ersterTeil=eingabe, zweiterTeil="" setzen würde.

    Dann müsste der Aufrufer ersterTeil == eingabe prüfen um herauszufinden, ob das Trennzeichen in der eingabe enthalten ist, oder nicht. Ich würde einfach einen bool zurückgeben. Edited.

    @wob sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    Wobei allgemein die Frage auch ist, ob man nicht eher einfach ein pair<string(_view), string(_view)> zurückgeben sollte. [...] Zu string_view: bin mir nicht so sicher, sollte man das tun? Was ist, wenn jemand die Funktion mit einem Temporary aufruft?

    Dann ist der string_view schneller kaput als du "hopp" sagen kannst 😉



  • @swordfish

    Es war leider explizit in der Aufgabenstellung aus den Unterlagen gefordert, dass die Funktion folgende Form hat:

    void spalte_ab_erstem(char zeichen, string eingabe, string& erster_teil, string& zweiter_teil)
    

    Der Code wird später noch für 1-2 aufbauende Aufgabenstellung verwendet, weswegen ich mich an die Vorgaben halten wollte (wer weiß was da noch kommt o.O).

    Dennoch ist es nett zu sehen wie du es gelöst hättest mit bool... Danke hierfür!
    Kann nie schaden auch mal andere herangehensweisen zu betrachten.😎



  • Dann poste ich auch mal meine Lösung.
    Ganz einfach gehalten:

    #include <string>
    #include <utility>
    
    std::pair<std::string, std::string> teilen(const std::string& s, char delim){
        std::size_t pos = s.find(delim);
    
        if(pos == std::string::npos)
            return {s, ""};
    
        return {s.substr(0, pos), s.substr(pos + 1)};
    }
    


  • @spiri sagte in Fehlersuche: Strings nach Trennzeichen aufteilen:

    Ganz einfach gehalten:

    // ...
    

    Noch einfacher gehalten:

    #include <string>
    #include <utility>
    
    std::pair<std::string, std::string> teilen(std::string const & s, char delim)
    {
    	auto pos{ s.find(delim) };
    	return { s.substr(0, pos), s.substr(pos + 1) };
    }
    


  • Klar, damit erzeugst du nen Überlauf.



  • substr() wirft out_­of_­range nur if pos[1]> size(). Im Falle pos == npos == -1 und pos + 1 ist das Ergebnis 0, was nie > size() sein kann. --> wirft nicht.
    Produziert halt auch in .second vom pair den Eingabestring.

    [1] pos ... erster Parameter von substr().