2016-04-28 11 views
10

używam sprintf funkcji w C++ 11, w następujący sposób:Korzystanie sprintf z std :: string w C++

std::string toString() 
{ 
    std::string output; 
    uint32_t strSize=512; 
    do 
    { 
     output.reserve(strSize); 
     int ret = sprintf(output.c_str(), "Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u", 
      INDEX_RECORD_TYPE_SERIALIZATION_HEADER, 
      FORAMT_VERSION, 
      contentType, 
      contentFormatVersion, 
      magic, 
      segmentId); 

     strSize *= 2; 
    } while (ret < 0); 

    return output; 
} 

Czy istnieje lepszy sposób to zrobić, niż sprawdzać za każdym razem, gdy zastrzeżone przestrzeń wystarczy? Dla przyszłej możliwości dodawania więcej rzeczy.

+1

Czy korzystasz 'snprintf'? Ponieważ 'sprintf', jak pokazano w twoim kodzie, nie ma możliwości określenia rozmiaru bufora. 'snprintf' również zwróci wymagany rozmiar bufora, więc możesz użyć zwróconej wartości +1 jako nowego' strSize'. –

+1

Ten kod jest bardzo błędny. 'reserve' nie zmienia rozmiaru ciągu, a' sprintf' nie zwraca negatywu tylko dlatego, że napisałeś poza granicami. Musisz przydzielić potrzebną przestrzeń * przed * wypisaniem poza granice. –

+0

Powiązane pytanie: http://stackoverflow.com/questions/2342162/stdstring-formatowanie- like-sprintf –

Odpowiedz

13

Twój konstrukt - pisanie do bufora otrzymanych od c_str() - jest niezdefiniowane zachowanie, nawet jeśli sprawdzana zdolność napisu za wcześniej. (Wartością zwracaną jest wskaźnik do const char, a sama funkcja oznaczona const, z jakiegoś powodu.)

Nie mieszać C i C++, szczególnie nie do pisania do wewnętrznej reprezentacji obiektu. (To łamie bardzo podstawowe OOP.) Użyj C++, dla bezpieczeństwa typu i nie uruchamia się w specyfikatorze konwersji/niesparowanie parametrów, jeśli nic innego.

std::ostringstream s; 
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER 
    << " Version=" << FORMAT_VERSION 
    // ...and so on... 
    ; 
std::string output = s.str(); 

Alternatywa:

std::string output = "Type=" + std::to_string(INDEX_RECORD_TYPE_SERIALIZATION_HEADER) 
        + " Version=" + std::to_string(FORMAT_VERSION) 
        // ...and so on... 
        ; 
+0

Jak mogę symulować formatowanie ciągów w ostringstream, np. Magic =% 04x i tak dalej? –

+1

@danieltheman: ['setw'] (http://en.cppreference.com/w/cpp/io/manip/setw), [' setfill'] (http://en.cppreference.com/w/cpp/io/manip/setfill). – DevSolar

+0

Twój komentarz * 'Zwracana wartość jest wskaźnikiem ** const ** char, a sama funkcja oznaczona ** const **, z jakiegoś powodu' * oznacza, że ​​przedstawiony kod * nie powinien nawet kompilować * ze względu na 'const "niedopasowanie kwalifikatora w wyrażeniu' sprintf (output.c_str(), ... ', więc jest to niepoprawne OP * używa *' sprintf' w taki sposób, jak opisał, ponieważ prezentowany fragment nie jest działającym kodem ... – CiaPan

1

Twój kod jest nieprawidłowy. reserve przydziela pamięć dla ciągu znaków, ale nie zmienia jej rozmiaru. Zapis w buforze zwróconym przez c_str również nie zmienia swojego rozmiaru. Ciąg ciągle wierzy, że jego rozmiar wynosi 0, a ty właśnie napisałeś coś do nieużywanego miejsca w buforze ciągu. (Prawdopodobnie, technicznie, kod ma niezdefiniowane zachowanie, ponieważ pisanie do c_str jest niezdefiniowane, więc wszystko może się zdarzyć).

Co naprawdę chcesz zrobić, to zapomnieć sprintf i podobne funkcje w stylu C i użyć C++ sposób ciąg formatowania strumieni — wyrażenie:

std::ostringstream ss; 
ss << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER 
    << " Version=" << FORAMT_VERSION 
    << /* ... the rest ... */; 
return ss.str(); 
0

Tak, jest!

W C, lepiej jest skojarzyć plik o zerowej urządzenia i zrobić manekina printf żądanego wyjścia do tego, aby dowiedzieć się, ile miejsca zajmie, jeśli rzeczywiście wydrukowany. Następnie przydziel odpowiedni bufor i sprintf te same dane do niego.

W języku C++ można skojarzyć strumień wyjściowy z urządzeniem zerowym i przetestować liczbę znaków wydrukowanych za pomocą std::ostream::tellp. Jednak korzystanie z ostringstream jest lepszym rozwiązaniem - zobacz odpowiedzi od DevSolar lub Angew.

+0

Całkowicie brakuje punktu, którego nie można zapisać do bufora zwróconego przez 'c_str()' i niepotrzebnie skomplikowanego nawet wtedy, gdy istnieje 'snprintf()'/'snprintf_s'. – DevSolar

+0

'snprintf' jest lepszy od hackowania urządzenia zerowego. Ale musisz użyć tego hacka dla 'swprintf'. –

6

C++ wzory pokazane w innych odpowiedzi są ładniejsze, ale pod względem kompletności, tutaj jest poprawny sposób z sprintf:

auto format = "your %x format %d string %s"; 
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/); 
std::string output(size + 1, '\0'); 
std::sprintf(&output[0], format, /* Arguments go here*/); 

Zwróć uwagę na

  • Musisz resize swój ciąg. reserve nie zmienia rozmiaru bufora. W moim przykładzie bezpośrednio skonstruowałem poprawnie rozmiar łańcucha.
  • c_str() zwraca wartość const char*. Nie możesz przekazać go do sprintf.
  • std::string bufor nie miał ciągłości przed C++ 11 i to zależy od tej gwarancji. Jeśli potrzebujesz obsługi egzotycznych platform zgodnych z C++ 11, które używają implementacji linek dla std::string, to prawdopodobnie lepiej od razu sprintować do std::vector<char> najpierw, a następnie skopiować wektor do łańcucha.
  • Działa to tylko wtedy, gdy argumenty nie są modyfikowane między obliczaniem rozmiaru a formatowaniem; użyj lokalnych kopii zmiennych lub prymitywów synchronizacji wątków dla wielowątkowego kodu.
+0

Interesujące; Nie jestem do końca pewien, czy konstrukcja (zapis do '& output [0]') jest zgodna z literą standardu. To prawdopodobnie będzie działać obok wszystkich implementacji 'std :: string', ale jeśli chodzi o definicję, mam wątpliwości. – DevSolar

+0

@DevSolar Jeśli dobrze pamiętam, ciągłość bufora 'std :: string' jest gwarantowana od C++ 11. Wcześniej nie było to gwarantowane.O ile mi wiadomo, nie ma innych powodów, aby to nie zadziałało. – user2079303

+0

@ user2079303 konstruktor ciągu, który dostaje numer w nim? nie sądzę, że jest coś takiego w C++, czy mógłbyś to rozwinąć? mówię o tym: std :: string output (rozmiar + 1); –

1

Możemy mieszać kod tutaj https://stackoverflow.com/a/36909699/2667451 i tutaj https://stackoverflow.com/a/7257307 i wynik będzie być tak:

template <typename ...Args> 
std::string stringWithFormat(const std::string& format, Args && ...args) 
{ 
    auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...); 
    std::string output(size + 1, '\0'); 
    std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...); 
    return output; 
} 
Powiązane problemy