Nowoczesne C ++ sprawia, że jest to bardzo proste.
C ++ 20
C ++ 20 wprowadza std::format
, co pozwala dokładnie to zrobić. Używa zastępczych pól podobnych do tych w Pythonie :
#include <iostream>
#include <format>
int main() {
std::cout << std::format("Hello {}!\n", "world");
}
Sprawdź pełną dokumentację ! To ogromna poprawa jakości życia.
C ++ 11
Z c ++ 11 s std::snprintf
, to już stało się bardzo łatwe i bezpieczne zadanie.
#include <memory>
#include <string>
#include <stdexcept>
template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
std::unique_ptr<char[]> buf( new char[ size ] );
snprintf( buf.get(), size, format.c_str(), args ... );
return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}
Powyższy fragment kodu jest licencjonowany na podstawie CC0 1.0 .
Objaśnienie linia po linii:
Cel: Napisz dochar*
używając, std::snprintf
a następnie przekonwertuj to nastd::string
.
Najpierw określamy pożądaną długość tablicy znaków przy użyciu specjalnego warunku w snprintf
. Od cppreference.com :
Zwracana wartość
[...] Jeśli wynikowy łańcuch zostanie obcięty z powodu limitu buf_size, funkcja zwraca całkowitą liczbę znaków (nie wliczając kończącego znaku zerowego), które zostałyby zapisane, gdyby limit nie został narzucony.
Oznacza to, że pożądany rozmiar to liczba znaków plus jeden , tak więc terminator zerowy usiądzie za wszystkimi innymi znakami i może zostać ponownie odcięty przez konstruktor łańcucha. Ten problem został wyjaśniony przez @ alexk7 w komentarzach.
size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;
snprintf
zwróci liczbę ujemną, jeśli wystąpi błąd, więc sprawdzamy, czy formatowanie działało zgodnie z oczekiwaniami. Nieprzestrzeganie tego może prowadzić do cichych błędów lub przydzielenia ogromnego bufora, jak wskazał @ead w komentarzach.
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
Następnie przydzielamy nową tablicę znaków i przypisujemy ją do std::unique_ptr
. Jest to ogólnie zalecane, ponieważ nie trzeba ręczniedelete
ponownie tego .
Pamiętaj, że nie jest to bezpieczny sposób przydzielania unique_ptr
typów zdefiniowanych przez użytkownika, ponieważ nie można cofnąć przydziału pamięci, jeśli konstruktor zgłasza wyjątek!
std::unique_ptr<char[]> buf( new char[ size ] );
Następnie możemy oczywiście po prostu użyć snprintf
zgodnie z przeznaczeniem i zapisać sformatowany ciąg do char[]
.
snprintf( buf.get(), size, format.c_str(), args ... );
Na koniec tworzymy i zwracamy nowe std::string
, upewniając się, że na końcu pominięto terminator zerowy.
return std::string( buf.get(), buf.get() + size - 1 );
Można zobaczyć przykład w akcji tutaj .
Jeśli chcesz także użyć std::string
na liście argumentów, spójrz na tę treść .
Dodatkowe informacje o Visual Studio użytkowników :
Jak wyjaśniono w tej odpowiedzi , Microsoft zmienił nazwę std::snprintf
na _snprintf
(tak, bez std::
). MS dodatkowo ustawiło to jako przestarzałe i zaleca użycie _snprintf_s
zamiast tego, jednak _snprintf_s
nie zaakceptuje bufora jako zera lub mniejszego niż sformatowane wyjście i nie obliczy długości wyjść, jeśli to nastąpi. Aby więc pozbyć się ostrzeżeń o wycofaniu podczas kompilacji, możesz wstawić następujący wiersz na górze pliku, który zawiera użycie _snprintf
:
#pragma warning(disable : 4996)
Końcowe przemyślenia
Wiele odpowiedzi na to pytanie zostało napisanych przed C ++ 11 i używa stałych długości buforów lub vargs. Jeśli nie utkniesz ze starymi wersjami C ++, nie poleciłbym używania tych rozwiązań. Idealnie jest przejść na C ++ 20.
Ponieważ rozwiązanie C ++ 11 w tej odpowiedzi używa szablonów, może generować sporo kodu, jeśli jest często używane. Jednak jeśli nie tworzysz środowiska z bardzo ograniczoną przestrzenią na pliki binarne, nie będzie to stanowić problemu i nadal stanowi znaczną poprawę w stosunku do innych rozwiązań, zarówno pod względem przejrzystości, jak i bezpieczeństwa.
Jeśli wydajność przestrzeni jest bardzo ważna, te dwa rozwiązania z vargs i vsnprintf mogą być przydatne.
NIE UŻYWAJ żadnych rozwiązań o ustalonych długościach buforów, które wymagają jedynie problemów.
boost::format
(jak tutaj wykorzystuje rozwiązanie KennyTM ).boost::format
już obsługuje również operatorów strumieniowych C ++! Przykład:cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;
.boost::format
ma najmniej wierszy kodu ... jest recenzowany i ładnie integruje się ze strumieniami C ++.