Permalänk
Medlem

C++ string to int

Håller på att knåpa ihop ett enkelt litet program till jobbet som ska underlätta jobbet för min kollega och har valt att göra detta i C++ då jag återupptagit intresset för det.

I mitt fall när programmet körs så öppnar programmet en "settings-fil" låt oss kalla den settings.txt.
I denna fil finns det lite enklare inställningar som kan göras enligt formatet:

Setting1: 34 Setting2: 12 etc...

Mitt huvudprogram lyckas öppna filen och hämta värdet för varje inställning men jag vill ju ha en liten validering på att värdet verkligen är en korrekt int.

Som jag gör nu så löser jag det enligt följande metodik:

std::string parsedSetting; // Denna sträng innehåller värdet för inställningen efter parsning, exempelvis 34. int parsedSettingAsInt; try { parsedSettingAsInt = stoi(parsedSetting); return 1; } catch (...) { std::cout << "Tyvärr, det sket sig att göra str->int" ; return 0; }

Så om jag i min inställning skriver in inställning "34" så fungerar det.
Skriver jag in "test34" så får jag error enligt catch.
Skriver jag däremot in "34 sträng" så får jag fortfarande ok på konverteringen och värdet på int-variabeln blir då 34.

Har förstått att detta är beteendet av stoi() och det är säkert ok egentligen men det gnager mig ändå att man kan skriva in ett ogiltigt värde men ändå få det "korrekt".

Går det att fånga felet i mitt 3:e exempel ändå på något smidigt sätt eller ska man leva med det?

Permalänk
Medlem

Lättaste är att först ta input från stream till en buffer, sedan ha någon parsing på buffern som faktiskt kollar vad som är i den innan den skickar det den ska till variabeln.

Permalänk
Medlem

du kan ju pars:a strängen o kolla att den består av enbart siffror....t.ex. med RegEx eller genom char(ascii) kontroll

mvh Lazze

Permalänk
Medlem

Eller skicka med en index-variabel som andra parameter till stoi, och kontrollera om den sätts till strängens längd (eller kolla om det finns något annat än whitespace efter indexet, om du vill vara riktigt noggrann).

Permalänk
Medlem
Skrivet av Tea42BBS:

du kan ju pars:a strängen o kolla att den består av enbart siffror....t.ex. med RegEx eller genom char(ascii) kontroll

mvh Lazze

Så när jag har parsat ut mitt värde t.ex "34 sträng" så loopar jag igenom den strängen för att se att varje char i den har ett giltigt värde enligt ASCII-tabellen? Borde ju vara hyffsat enkelt då 0-9 ligger i följd i ASCII-tabellen.
Eller tolkar jag dig fel kanske, men det låter ju rimligt

Permalänk
Medlem
Skrivet av bardbard:

Så när jag har parsat ut mitt värde t.ex "34 sträng" så loopar jag igenom den strängen för att se att varje char i den har ett giltigt värde enligt ASCII-tabellen? Borde ju vara hyffsat enkelt då 0-9 ligger i följd i ASCII-tabellen.
Eller tolkar jag dig fel kanske, men det låter ju rimligt

Så kan du göra, och det funkar så länge det faktiskt är ASCII som används.
Men varför begränsa dig till ASCII?

Anropa istället isdigit() för varje tecken för att se om det är en siffra eller inte.
Då skall det fungera oavsett vilken teckenuppsättning som används.

Permalänk
Medlem

Eller så kan du konvertera tillbaka ditt int-värde till en sträng, och jämföra med originalsträngen. Ser de olika ut, vet du att du fick in och ignorerade skräp vid inmatningen.

Permalänk
Medlem
Skrivet av Erik_T:

Så kan du göra, och det funkar så länge det faktiskt är ASCII som används.
Men varför begränsa dig till ASCII?

Anropa istället isdigit() för varje tecken för att se om det är en siffra eller inte.
Då skall det fungera oavsett vilken teckenuppsättning som används.

Hmmm visste inte att den fanns men det låter ju helt klart smidigast!

Skrivet av MvonSchantz:

Eller så kan du konvertera tillbaka ditt int-värde till en sträng, och jämföra med originalsträngen. Ser de olika ut, vet du att du fick in och ignorerade skräp vid inmatningen.

Har tänkt på den lösningen faktiskt men vet inte, känns lite b Men det lär ju fungera!
Är väl bara att jämföra längderna på strängarna före och efter så ser man ju om de är olika.

Permalänk
Medlem
Skrivet av bardbard:

Har tänkt på den lösningen faktiskt men vet inte, känns lite b

Det är helt klart en B-lösning. Men allt beror ju på vad du har för krav på dig. Nu har du en verktygslåda med olika lösningar, och kan välja den som passar just din situation bäst.

Permalänk
Medlem
Skrivet av grimpe:

Eller skicka med en index-variabel som andra parameter till stoi, och kontrollera om den sätts till strängens längd (eller kolla om det finns något annat än whitespace efter indexet, om du vill vara riktigt noggrann).

Håller helt med.

Till TS. Här är ett exempel på hur man använder den:
https://www.cplusplus.com/reference/string/stoi/

Permalänk
Medlem
Skrivet av MvonSchantz:

Eller så kan du konvertera tillbaka ditt int-värde till en sträng, och jämföra med originalsträngen. Ser de olika ut, vet du att du fick in och ignorerade skräp vid inmatningen.

Jag skulle inte rekommendera den lösningen.
För heltal fungerar det nog, men skulle man byta till flyttal kan det bli helt fel.
Dessutom ganska ineffektiv metod.

Permalänk
Medlem

Får tacka för många och snabba svar!
Har ju en bra verktygslåda nu att lösa problemet med så vi får väl se vad det blir i slutändan^^

Permalänk
Medlem

Det moderna sättet att konvertera strängar till tal är annars std::from_chars, den är snabbare än stoi och oberoende av locale.

Permalänk
Medlem

Använda "pos" out-parametern för att kolla att du använt alla tecken i strängen om du vill ha det beteendet. https://en.cppreference.com/w/cpp/string/basic_string/stol

Permalänk
Medlem
Skrivet av Erik_T:

Jag skulle inte rekommendera den lösningen.
För heltal fungerar det nog, men skulle man byta till flyttal kan det bli helt fel.
Dessutom ganska ineffektiv metod.

Ja... och nej.

Jag vurmar starkt att man ska anpassa sin lösning till kraven man har på sig. I det här fallet var kravet heltal och kontexten "ett litet program... som ska underlätta jobbet", så, inget som ska ut till kund, och med fokus på att lösningen inte ska växa sig för stor.

Jag håller helt med om att den akademiskt snyggaste lösningen är att använda regex. Den i praktiken kanske bäst avvägda lösningen är nog med isdigit. Men ibland är quick-and-dirty rätt lösning också, när man bara snabbt behöver något som fungerar i den situation man är.

Det finns absolut en poäng ibland att framtidssäkra kod för andra format (flyttal) än de man just nu använder, men det medför en kostnad och det tycker jag att man ska väga mot hur troligt det är att formatet faktiskt komma att bytas i framtiden. Att lägga tid på det nu, bara för att det är det akademiskt korrekta sättet att jobba på, är jag tveksam till i kommersiell verksamhet, även om det absolut har sin plats under upplärning.

Gällande effektiviteten skulle jag bedöma att den billigaste lösningen är isdigit, att jämföra den återkonverterade stränglängden ligger i mitten, och regex kommer att kosta mest. Men jag skulle också vilja säga att i det här fallet är det nog irrelevant om det är effektivt eller inte, då det handlar om att läsa några inställningar från en fil. Något som utförs en handfull gånger behöver antagligen inte vara effektivt. Det är bättre att lägga den tiden på att optimera det som faktiskt tar tid i programmet.

Så sammanfattningsvis, bedöm kraven och anpassa lösningen efter dem. Ibland är quick-and-dirty rätt. Ibland är akademiskt korrekt rätt. Ibland är det rätt att lägga sig någonstans mitt emellan.

Permalänk
Medlem

Har lyckats knåpa ihop något som fungerar nu enligt önskemål:

bool handleSettings::getSettingInt(std::string search, int& setting) { std::string parsedSetting; if (searchForSetting(search, parsedSetting)) { if (isValidInt(parsedSetting, setting)) { return true; } else { std::cout << "Could not convert str to int for setting '" << search << "' with value '" << parsedSetting << "'" << std::endl; std::cout << "Stopping program..." << std::endl; return false; } } else { return false; } } bool isValidInt(std::string& parsedSetting, int& setting) { std::size_t idx = 0; int result; try { result = std::stoi(parsedSetting, &idx); } catch (...) { return false; } if (idx == parsedSetting.size()) { setting = result; return true; } else { return false; } }

Är i isValidInt som jag fixat.
Jag var tvungen att köra try/catch på den för om jag annars skickade in en sträng typ "s34" så fick jag exception.
Men nu verkar if (idx == parsedSetting.size()) lösa biffen med om man har text efter siffrorna t.ex "34 hejhej".

Känns som att isdigit() är enklare egentligen?

Permalänk
Medlem

@bardbard:

#include <charconv> #include <string> #include <iostream> int main() { const std::string str = "12345"; int value = 0; auto res = std::from_chars(str.data(), str.data() + str.size(), value); if (res.ptr != str.data() + str.size()) { std::cout << "whole string is not a number!" << std::endl; } else if (res.ec == std::errc::invalid_argument) { std::cout << "string does not start with a number!" << std::endl; } else if (res.ec == std::errc::result_out_of_range) { std::cout << "out of range!" << std::endl; } else { std::cout << "value = " << value << std::endl; } }

Permalänk
Medlem
Skrivet av MvonSchantz:

Det finns absolut en poäng ibland att framtidssäkra kod för andra format (flyttal) än de man just nu använder, men det medför en kostnad och det tycker jag att man ska väga mot hur troligt det är att formatet faktiskt komma att bytas i framtiden. Att lägga tid på det nu, bara för att det är det akademiskt korrekta sättet att jobba på, är jag tveksam till i kommersiell verksamhet, även om det absolut har sin plats under upplärning.

Inte så mycket framtidssäkring i det här fallet, som för att inte skaffa sig dåliga vanor. Det är helt enkelt inte ett bra sätt att lösa problemet, och är inte tillräckligt mycket enklare att implementera jämfört med de bra sätten för att kunna rättfärdigas på den punkten.

Ibland kan det absolut vara så att en quick-and-dirty lösning faktiskt är att föredra framför en mer korrekt metod, men i det här fallet är den mer dirty än quick.

Citat:

Gällande effektiviteten skulle jag bedöma att den billigaste lösningen är isdigit, att jämföra den återkonverterade stränglängden ligger i mitten, och regex kommer att kosta mest. Men jag skulle också vilja säga att i det här fallet är det nog irrelevant om det är effektivt eller inte, då det handlar om att läsa några inställningar från en fil. Något som utförs en handfull gånger behöver antagligen inte vara effektivt. Det är bättre att lägga den tiden på att optimera det som faktiskt tar tid i programmet.

Den effektivaste, och på många sätt enklaste lösningen är att, som någon annan föreslog tidigare i tråden, skicka med en pekare till en indexvariabel till stoi() och sedan utgå från den för att se om det finns icke-blank steg efter.
Alternativt, om man inte vill tillåta blanksteg, att loopa igenom strängen för att se efter att den inte innehåller annat än siffror.

Använda en regex i det här fallet är bara att krångla till det, och man vinner egentligen inget på att använda en sådan jämfört med övriga lösningar.

Permalänk
Entusiast
Skrivet av bardbard:

bool isValidInt(std::string& parsedSetting, int& setting) { std::size_t idx = 0; int result; try { result = std::stoi(parsedSetting, &idx); } catch (...) { return false; } if (idx == parsedSetting.size()) { setting = result; return true; } else { return false; } }

Jag känner mig förd bakom ljuset av den här funktionen. Dess namn signalerar starkt att den är en ren funktion som berättar om en viss sträng är ett giltigt heltal, varken mer eller mindre. Men i själva verket modifierar den helt vilt ett värde utanför sig själv (setting). Att värdet som modifieras är en explicit parameter är en förmildrande omständighet, men jag tycker inte det räcker för att motivera diskrepansen mellan funktionens namn och dess beteende.

En bra tumregel för programmering är att försöka skriva det man menar™. För mig är isValidInt ovan ett skolboksexempel på hur det blir när man inte gör det.

Tror för övrigt att isValidInt borde vara static, men kan inte C++ särskilt bra så kan ha fel.

Permalänk
Medlem
Skrivet av Alling:

Tror för övrigt att isValidInt borde vara static, men kan inte C++ särskilt bra så kan ha fel.

isValidInt är inte en klass-metod utan en vanlig funktion, så det finns ingen större anledning att göra den static.

Permalänk
Medlem
Skrivet av Alling:

Jag känner mig förd bakom ljuset av den här funktionen. Dess namn signalerar starkt att den är en ren funktion som berättar om en viss sträng är ett giltigt heltal, varken mer eller mindre. Men i själva verket modifierar den helt vilt ett värde utanför sig själv (setting). Att värdet som modifieras är en explicit parameter är en förmildrande omständighet, men jag tycker inte det räcker för att motivera diskrepansen mellan funktionens namn och dess beteende.

En bra tumregel för programmering är att försöka skriva det man menar™. För mig är isValidInt ovan ett skolboksexempel på hur det blir när man inte gör det.

Tror för övrigt att isValidInt borde vara static, men kan inte C++ särskilt bra så kan ha fel.

Förstår precis vad du menar och håller med dig till fullo. Har insett lite själv nu en del tokigheter överlag.
Problemet är att jag hade 1 "stor" fil förut med ca 500 rader kod. Började då flytta funktioner till en egen fil för att få min "main.cpp" mer överskådlig.
Sen kom jag på att äh, vafan det här borde ju vara 3 olika klasser istället som gör sin grej o.s.v.
Kontentan är att den här "utbrytningen" har fört med sig en del konstigheter gällande metoder, syntax etc.. som jag nu tänker städa upp.
Ett klassiskt fall av att göra först och tänka sen
Exemplet ovan är mer kod på en "lösning" som fungerar för ursprunglig frågeställning.
Men som det ser ut just nu så agerar funktionen enbart som en ren bool utan att modifiera värden utanför.

Kommer bli bra sen när jag har städat färdigt hela programmet^^

I övrigt så är jag ren hobby-knackare så finns ingen risk att mitt skräp letar sig ut på "kritiska" ställen

Men mycket bra input även om det är för hobby-nivå, man kan ju försöka att göra rätt iaf!

Permalänk
Entusiast
Skrivet av perost:

isValidInt är inte en klass-metod utan en vanlig funktion, så det finns ingen större anledning att göra den static.

Menar du att den inte ligger i en klass, utan "naken" i filens toppscope? I så fall håller jag med. Annars förstår jag nog inte vad du menar.

Permalänk
Medlem
Skrivet av Alling:

Menar du att den inte ligger i en klass, utan "naken" i filens toppscope? I så fall håller jag med. Annars förstår jag nog inte vad du menar.

Den är inte en del av en klass, i så fall skulle det stå t.ex. handleSettings::isValidInt istället för bara isValidInt. Det kan hända att den faktiskt är en del av klassen i den verkliga koden, men om funktionen bara används internt i klassen så kan den lika gärna vara en s.k. "free function" som det heter i C++-termer.

Permalänk
Medlem

För en amatör som mig, vad innebär static?

Kan visa min header-fil för min "hantera settingsfilen" här. "Måste" man ha lite statics?

#pragma once #include <string> #include <vector> #include <fstream> class handleSettings { private: std::ifstream m_file; std::string m_fileName; std::string m_inputFileName; std::string m_outputFileName; int m_attributeIndex; std::vector<int> m_nodePairs; bool m_allOk = true; void generateFile(); void readAllSettings(); std::string searchForSetting(std::string); bool isValidStringValue(std::string&); bool isValidIntValue(std::string&); bool createIntVector(std::string&); public: handleSettings(std::string); std::string getInputFileName() { return m_inputFileName; } std::string getOutputFileName() { return m_outputFileName; } int getAttributeIndex() { return m_attributeIndex; } int getNodePairSize() { return m_nodePairs.size(); } bool didWeMakeIt() { return m_allOk; } ~handleSettings(); };

Vad jag gör här är att skapa ett objekt från min main.cpp och när konstruktorn anropas så sker alla private-funktioner, d.v.s den läser in alla värden från min settings-fil och lagrar dessa i objektet.
Tänkte detta kan vara läckert då jag kan hämta dessa värden enkelt från min main.cpp och känns hyffsat lätt att bygga på med en inställning till eller tre.

Men hur och varför vill jag lägga in statics här? Mer av nyfikenhet vad det innebär.

Permalänk
Medlem
Skrivet av bardbard:

För en amatör som mig, vad innebär static?

static har många olika innebörder i C++ beroende på var det används, men för en metod så innebär det att den då kan användas som en vanlig funktion utan att man behöver ha en instans av klassen. Se t.ex. Learn C++ för mer detaljer.

Men att ha en privat static-metod är oftast onödigt, i så fall kan du oftast bara implementera den som en fri funktion i .cpp-filen och strunta i att ha den som en del av klassen. Du kan då undvika att dra in onödiga beroenden i header-filen för en funktion som ingen annan än klassen själv ändå kan använda. I det här fallet spelar det ingen roll, men det är bra att vara konsekvent.

Permalänk
Medlem
Skrivet av bardbard:

För en amatör som mig, vad innebär static?

Kan visa min header-fil för min "hantera settingsfilen" här. "Måste" man ha lite statics?

#pragma once #include <string> #include <vector> #include <fstream> class handleSettings { private: std::ifstream m_file; std::string m_fileName; std::string m_inputFileName; std::string m_outputFileName; int m_attributeIndex; std::vector<int> m_nodePairs; bool m_allOk = true; void generateFile(); void readAllSettings(); std::string searchForSetting(std::string); bool isValidStringValue(std::string&); bool isValidIntValue(std::string&); bool createIntVector(std::string&); public: handleSettings(std::string); std::string getInputFileName() { return m_inputFileName; } std::string getOutputFileName() { return m_outputFileName; } int getAttributeIndex() { return m_attributeIndex; } int getNodePairSize() { return m_nodePairs.size(); } bool didWeMakeIt() { return m_allOk; } ~handleSettings(); };

Läs på om const både för in-parametrar som är referenser och const på metoder. Och var konsekvent hur du hanterar std::string in-parametrar. T.ex. så kan metoden didWeMakeIt markeras som const (du modifierar inte objektet med den metoden).