2010-12-28 5 views
13

Chciałbym użyć obiektów dostarczonych przez stringstream, aby wyodrębnić wartości ze stałego formatu string jako bezpieczną dla rodzaju alternatywę dla sscanf. Jak mogę to zrobić?Używanie strumienia łańcuchów zamiast `sscanf` do parsowania łańcucha o stałym formacie

Rozważmy następujący konkretny przypadek użycia. Mam std::string w następującym ustalonym formacie:

YYYYMMDDHHMMSSmmm

Gdzie:

YYYY = 4 digits representing the year 
MM = 2 digits representing the month ('0' padded to 2 characters) 
DD = 2 digits representing the day ('0' padded to 2 characters) 
HH = 2 digits representing the hour ('0' padded to 2 characters) 
MM = 2 digits representing the minute ('0' padded to 2 characters) 
SS = 2 digits representing the second ('0' padded to 2 characters) 
mmm = 3 digits representing the milliseconds ('0' padded to 3 characters) 

Wcześniej robiłem coś wzdłuż tych linii:

string s = "20101220110651184"; 
unsigned year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, milli = 0;  
sscanf(s.c_str(), "%4u%2u%2u%2u%2u%2u%3u", &year, &month, &day, &hour, &minute, &second, &milli); 

Wartości szerokości są magiczne numery , i to jest w porządku. Chciałbym użyć strumieni, aby wyodrębnić te wartości i przekonwertować je na unsigned sw interesie bezpieczeństwa typu. Ale gdy próbuję to:

stringstream ss; 
ss << "20101220110651184"; 
ss >> setw(4) >> year; 

year zachowuje wartość 0. Powinno to być 2010.

Jak zrobić to, co próbuję zrobić? Nie mogę używać Boost ani żadnej innej biblioteki 3rd party, ani używać C++ 0x.

+0

* Bill nie może czekać na jego kopii "Standard C++ iostreams i locales" ... +1 –

+0

'setw() 'służy do zapisu danych wyjściowych. To nie działa w przypadku czytania. – marcog

+1

Może powinieneś wrócić do początku strumienia przed wyodrębnieniem pierwszego pola. –

Odpowiedz

6

One nie jest szczególnie wydajny rozwiązaniem byłoby skonstruować jakieś tymczasowe ciągi i użyć leksykalny Obsada:

std::string s("20101220110651184"); 
int year = lexical_cast<int>(s.substr(0, 4)); 
// etc. 

lexical_cast mogą być realizowane w ciągu zaledwie kilku linii kodu; Herb Sutter przedstawił absolutne minimum w swoim artykule: "The String Formatters of Manor Farm."

Nie jest to dokładnie to, czego szukasz, ale jest to bezpieczny sposób na wyodrębnienie pól o stałej szerokości z ciągu znaków.

+0

Nie mogę użyć 'lexical_cast', ponieważ jest to część Boost. –

+0

Chociaż mogłem ponownie użyć strumieni lub niektórych rzeczy typu "atoi". Miałem nadzieję, że uda mi się to osiągnąć w bardziej naturalny sposób. –

+0

@John: Możesz napisać własną dość łatwo. Połączyłem się z jednym z artykułów Herb Sutter, w którym przedstawiono bardzo podstawową implementację (siedem ładnie sformatowanych linii kodu). Lub napisałem bardzo prostą wersję w [mój pierwszy post przepełnienia stosu] (http://stackoverflow.com/questions/1528374/how-can-i-extend-a-lexical-cast-to-support-enumerated-types); ten jest dwoma liniami kodu. –

4

używam następujących, to może być przydatne dla Ciebie:

template<typename T> T stringTo(const std::string& s) 
    { 
     std::istringstream iss(s); 
     T x; 
     iss >> x; 
     return x; 
    }; 

template<typename T> inline std::string toString(const T& x) 
    { 
     std::ostringstream o; 
     o << x; 
     return o.str(); 
    } 

Szablony te wymagają:

#include <sstream> 

Wykorzystanie

long date; 
date = stringTo<long>(std::cin); 

YMMV

+0

W funkcji 'stringTo' bardzo ważne jest sprawdzenie stanu' iss' po ekstrakcji, aby upewnić się, że udało się poprawnie obsłużyć błędy (wyślij wyjątek, zwróć kod błędu, przerwij aplikację, cokolwiek). –

+0

+1 to, w gruncie rzeczy, zasadniczo to, co sugeruje powyższy @James. Miałem nadzieję, że skorzystam z czegoś już dostarczonego przez StdLib, ale być może będę musiał napisać to sam –

1

Od here, może się on przydać:

template<typename T, typename charT, typename traits> 
std::basic_istream<charT, traits>& 
    fixedread(std::basic_istream<charT, traits>& in, T& x) 
{ 
    if (in.width() == 0) 
    // Not fixed size, so read normally. 
    in >> x; 
    else { 
    std::string field; 
    in >> field; 
    std::basic_istringstream<charT, traits> stream(field); 
    if (! (stream >> x)) 
     in.setstate(std::ios_base::failbit); 
    } 
    return in; 
} 

setw() odnosi się tylko do czytania w stringów cstrings. Powyższa funkcja wykorzystuje ten fakt, odczytując ciąg znaków, a następnie przesyłając go do wymaganego typu. Możesz go używać w połączeniu z setw() lub ss.width(w), aby czytać w dowolnym polu o stałej szerokości.

+0

+1 To znowu jest to, co zasugerował także @James. Czuję tu trend ... :) –

4

Erm, jeśli jest w ustalonym formacie, dlaczego tego nie zrobisz?

std::string sd("20101220110651184"); 
    // insert spaces from the back 
    sd.insert(14, 1, ' '); 
    sd.insert(12, 1, ' '); 
    sd.insert(10, 1, ' '); 
    sd.insert(8, 1, ' '); 
    sd.insert(6, 1, ' '); 
    sd.insert(4, 1, ' '); 
    int year, month, day, hour, min, sec, ms; 
    std::istringstream str(sd); 
    str >> year >> month >> day >> hour >> min >> sec >> ms; 
+0

+1 od jove, to może zadziałać! –

+0

Tworzysz zasadniczo nowy łańcuch rozdzielany spacjami, który operator >> może analizować, ponieważ zawiera spacje ... Niezbyt wydajne. – BHS

0
template<typename T> 
struct FixedRead { 
    T& content; 
    int size; 
    FixedRead(T& content, int size) : 
      content(content), size(size) { 
     assert(size != 0); 
    } 
    template<typename charT, typename traits> 
    friend std::basic_istream<charT, traits>& 
    operator >>(std::basic_istream<charT, traits>& in, FixedRead<T> x) { 
     int orig_w = in.width(); 
     std::basic_string<charT, traits> o; 
     in >> setw(x.size) >> o; 
     std::basic_stringstream<charT, traits> os(o); 
     if (!(os >> x.content)) 
      in.setstate(std::ios_base::failbit); 
     in.width(orig_w); 
     return in; 
    } 
}; 

template<typename T> 
FixedRead<T> fixed_read(T& content, int size) { 
    return FixedRead<T>(content, size); 
} 

void test4() { 
    stringstream ss("20101220110651184"); 
    int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, ms = 0; 
    ss >> fixed_read(year, 4) >> fixed_read(month, 2) >> fixed_read(day, 2) 
      >> fixed_read(hour, 2) >> fixed_read(min, 2) >> fixed_read(sec, 2) 
      >> fixed_read(ms, 4); 
    cout << "year:" << year << "," << "month:" << month << "," << "day:" << day 
      << "," << "hour:" << hour << "," << "min:" << min << "," << "sec:" 
      << sec << "," << "ms:" << ms << endl; 
} 
0

Rozwiązanie ps5mh jest naprawdę ładne, ale nie działa na stałym rozmiarze parsowania łańcuchów, które zawierają spacji. Następujące poprawki tego rozwiązania:

template<typename T, typename T2> 
struct FixedRead 
{ 
    T& content; 
    T2& number; 
    int size; 
    FixedRead(T& content, int size, T2 & number) : 
     content(content), number(number), size(size) 
    { 
     assert (size != 0); 
    } 
    template<typename charT, typename traits> 
    friend std::basic_istream<charT, traits>& 
    operator >>(std::basic_istream<charT, traits>& in, FixedRead<T,T2> x) 
    { 
     if (!in.eof() && in.good()) 
     { 
      std::vector<char> buffer(x.size+1); 
      in.read(buffer.data(), x.size); 
      int num_read = in.gcount(); 
      buffer[num_read] = 0; // set null-termination of string 
      std::basic_stringstream<charT, traits> os(buffer.data()); 
      if (!(os >> x.content)) 
       in.setstate(std::ios_base::failbit); 
      else 
       ++x.number; 
     } 
     return in; 
    } 
}; 
template<typename T, typename T2> 
FixedRead<T,T2> fixedread(T& content, int size, T2 & number) { 
    return FixedRead<T,T2>(content, size, number); 
} 

ten może być stosowany jako:

std::string s = "90007127  19000715790007397"; 
std::vector<int> ints(5); 
int num_read = 0; 
std::istringstream in(s); 
in >> fixedread(ints[0], 8, num_read) 
    >> fixedread(ints[1], 8, num_read) 
    >> fixedread(ints[2], 8, num_read) 
    >> fixedread(ints[3], 8, num_read) 
    >> fixedread(ints[4], 8, num_read); 
// output: 
// num_read = 4 (like return value of sscanf) 
// ints = 90007127, 1, 90007157, 90007397 
// ints[4] is uninitialized 
Powiązane problemy