std :: pemformatan string seperti sprintf


454

Saya harus ke format std::stringdengan sprintfdan mengirimkannya ke file streaming. Bagaimana saya bisa melakukan ini?


6
long story short use boost::format(seperti solusi kennytm gunakan di sini ). boost::formatsudah mendukung operator stream C ++ juga! Contoh: cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formatmemiliki paling sedikit baris kode ... ditinjau oleh rekan dan terintegrasi dengan baik dengan aliran C ++.
Trevor Boyd Smith

@Ockonal - Demi komunitas (Saya tidak peduli tentang perwakilan saya) Saya sarankan Anda mengubah pilihan Anda. Yang saat ini dipilih, dalam cuplikan pertama, menyajikan bug yang menunggu untuk terjadi dalam penggunaan panjang maks sewenang-wenang. Cuplikan kedua sepenuhnya mengabaikan keinginan Anda untuk menggunakan vargs seperti sprintf. Saya sarankan Anda memilih jawaban HANYA di sini yang bersih, aman, hanya bergantung pada standar C ++, diuji, dan dikomentari dengan baik. Bahwa itu milikku tidak relevan. Itu benar secara objektif. Lihat stackoverflow.com/questions/2342162/… .
Douglas Daseeco

@TrevorBoydSmith a std::formattelah ditambahkan ke C ++ 20 BTW: stackoverflow.com/a/57286312/895245 Keren!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli saya membaca sebuah artikel tentang C++20baru kemarin dan saya melihat bahwa C++20disalin boost(untuk kesejuta kali sekarang) dengan menambahkan std::formatke C++20spesifikasi! Saya sangat sangat senang! Hampir setiap file C ++ yang saya tulis dalam 9 tahun terakhir telah digunakan boost::format. menambahkan keluaran gaya printf resmi ke stream di C ++ akan sangat membantu IMO untuk semua C ++.
Trevor Boyd Smith

Jawaban:


333

Anda tidak dapat melakukannya secara langsung, karena Anda tidak memiliki akses tulis ke buffer yang mendasarinya (sampai C ++ 11; lihat komentar Dietrich Epp ). Anda harus melakukannya terlebih dahulu dalam c-string, lalu salin ke std :: string:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

Tapi saya tidak yakin mengapa Anda tidak hanya menggunakan aliran string? Saya berasumsi Anda memiliki alasan khusus untuk tidak hanya melakukan ini:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();

17
Cookie ajaib di char buf[100];membuat solusi ini tidak terlalu kuat. Tetapi ide penting ada di sana.
John Dibling

18
John, stream tidak lambat. Satu-satunya alasan aliran tampak lambat adalah bahwa secara default iostreams menyinkronkan dengan output C FILE sehingga cout dan printfs yang tercampur di-output dengan benar. Menonaktifkan tautan ini (dengan panggilan ke cout.sync_with_stdio (false)) menyebabkan aliran c ++ mengungguli stdio, setidaknya pada MSVC10.
Jimbo

72
Alasan untuk menggunakan format adalah untuk memungkinkan localizer membangun kembali struktur kalimat untuk bahasa asing, alih-alih mengkode tata bahasa kalimat dengan keras.
Martijn Courteaux

216
Untuk beberapa alasan, bahasa lain menggunakan sintaks seperti printf: Java, Python (sintaks baru masih lebih dekat ke printf daripada ke stream). Hanya C ++ yang menimbulkan kekejian verbal ini pada manusia yang tidak bersalah.
quant_dev

9
Bahkan lebih baik, gunakan asprintf, yang mengalokasikan string baru dengan ruang yang cukup untuk menampung hasilnya. Kemudian salin ke std::stringjika Anda suka, dan ingat ke freeaslinya. Juga, dimungkinkan untuk meletakkan ini dalam makro sehingga setiap kompiler yang baik akan membantu memvalidasi format untuk Anda - Anda tidak ingin meletakkan di doublemana a %sdiharapkan
Aaron McDaid

286

C ++ modern membuat ini sangat sederhana.

C ++ 20

C ++ 20 memperkenalkan std::format, yang memungkinkan Anda untuk melakukan hal itu. Ini menggunakan bidang pengganti yang mirip dengan yang ada di python :

#include <iostream>
#include <format>

int main() {
    std::cout << std::format("Hello {}!\n", "world");
}

Lihat dokumentasi lengkapnya ! Ini merupakan peningkatan kualitas hidup yang luar biasa.


C ++ 11

Dengan C ++ 11 s std::snprintf, ini sudah menjadi tugas yang cukup mudah dan aman.

#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
}

Cuplikan kode di atas dilisensikan di bawah CC0 1.0 .

Penjelasan baris demi baris:

Tujuan: Menulis ke achar*dengan menggunakan std::snprintflalu mengonversinya menjadi astd::string.

Pertama, kami menentukan panjang array char yang diinginkan menggunakan kondisi khusus di snprintf. Dari cppreference.com :

Nilai pengembalian

[...] Jika string yang dihasilkan terpotong karena batas buf_size, fungsi mengembalikan jumlah karakter (tidak termasuk terminasi null-byte) yang seharusnya ditulis, jika batas itu tidak dikenakan.

Ini berarti bahwa ukuran yang diinginkan adalah jumlah karakter ditambah satu , sehingga null-terminator akan duduk setelah semua karakter lain dan dapat dipotong oleh konstruktor string lagi. Masalah ini dijelaskan oleh @ alexk7 di komentar.

size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintfakan mengembalikan angka negatif jika terjadi kesalahan, jadi kami kemudian memeriksa apakah pemformatan berfungsi sesuai keinginan. Tidak melakukan ini dapat menyebabkan kesalahan diam atau alokasi buffer besar, seperti yang ditunjukkan oleh @ead di komentar.

if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

Selanjutnya, kami mengalokasikan array karakter baru dan menetapkannya ke a std::unique_ptr. Ini umumnya disarankan, karena Anda tidak perlu melakukannya deletelagi secara manual .

Perhatikan bahwa ini bukan cara yang aman untuk mengalokasikan unique_ptrtipe yang ditentukan pengguna karena Anda tidak dapat membatalkan alokasi memori jika konstruktor melempar pengecualian!

std::unique_ptr<char[]> buf( new char[ size ] );

Setelah itu, kita tentu saja bisa menggunakan snprintfuntuk tujuan penggunaannya dan menulis string yang diformat ke char[].

snprintf( buf.get(), size, format.c_str(), args ... );

Akhirnya, kami membuat dan mengembalikan yang baru std::stringdari itu, memastikan untuk menghilangkan null-terminator di akhir.

return std::string( buf.get(), buf.get() + size - 1 );

Anda dapat melihat contoh beraksi di sini .


Jika Anda juga ingin menggunakan std::stringdalam daftar argumen, lihat intisari ini .


Informasi tambahan untuk pengguna Visual Studio :

Sebagaimana dijelaskan dalam jawaban ini , Microsoft berganti nama std::snprintfmenjadi _snprintf(ya, tanpa std::). MS selanjutnya menetapkannya sebagai usang dan menyarankan untuk menggunakannya _snprintf_ssebagai gantinya, namun _snprintf_stidak akan menerima buffer menjadi nol atau lebih kecil dari output yang diformat dan tidak akan menghitung panjang output jika itu terjadi. Jadi untuk menghilangkan peringatan penghentian selama kompilasi, Anda dapat memasukkan baris berikut di bagian atas file yang berisi penggunaan _snprintf:

#pragma warning(disable : 4996)

Pikiran terakhir

Banyak jawaban untuk pertanyaan ini ditulis sebelum waktu C ++ 11 dan menggunakan panjang buffer tetap atau vargs. Kecuali Anda terjebak dengan versi lama C ++, saya tidak akan merekomendasikan menggunakan solusi tersebut. Idealnya, gunakan cara C ++ 20.

Karena solusi C ++ 11 dalam jawaban ini menggunakan templat, ini dapat menghasilkan sedikit kode jika banyak digunakan. Namun, kecuali Anda mengembangkan untuk lingkungan dengan ruang biner yang sangat terbatas, ini tidak akan menjadi masalah dan masih merupakan peningkatan besar atas solusi lain dalam kejelasan dan keamanan.

Jika efisiensi ruang sangat penting, dua solusi dengan vargs dan vsnprintf ini dapat bermanfaat. JANGAN GUNAKAN solusi apa pun dengan panjang buffer tetap, yang hanya meminta masalah.


2
Harap tekankan dalam jawaban Anda untuk pengguna Visual Studio bahwa versi VS harus setidaknya 2013. Dari artikel ini Anda dapat melihat bahwa itu hanya bekerja dengan versi VS2013: Jika buffer adalah pointer nol dan hitung adalah nol, len dikembalikan sebagai hitungan karakter yang diperlukan untuk memformat output, tidak termasuk null terminating. Untuk membuat panggilan yang berhasil dengan argumen dan parameter lokal yang sama, alokasikan buffer yang menahan setidaknya len + 1 karakter.
cha

3
@ooooeeeeep Berbagai alasan. Pertama, tujuannya adalah mengembalikan string std ::, bukan c-string, jadi Anda mungkin bermaksud return string(&buf[0], size);atau sesuatu yang serupa. Kedua, jika Anda mengembalikan c-string seperti itu, itu akan menyebabkan perilaku yang tidak terdefinisi karena vektor yang memegang nilai yang Anda tunjuk akan tidak valid saat dikembalikan. Ketiga, ketika saya mulai belajar C ++, standar tidak menentukan dalam elemen urutan apa yang harus disimpan di dalam std::vector, jadi mengakses penyimpanannya melalui pointer adalah perilaku yang tidak ditentukan. Sekarang ini akan berhasil, tetapi saya melihat tidak ada manfaatnya melakukannya dengan cara itu.
iFreilicht

2
@ iFreilicht A baru std::stringakan dibangun dari vektor yang dikonversi secara implisit ( salin inisialisasi ), yang kemudian dikembalikan sebagai salinan, seperti yang disarankan oleh tanda tangan fungsi. Juga, unsur-unsur a std::vector, dan selalu dimaksudkan untuk, disimpan secara bersebelahan . Tetapi saya berpendapat bahwa mungkin tidak ada manfaatnya untuk melakukannya.
moooeeeep

4
Saya sangat suka solusi ini, namun saya pikir garisnya return string(buf.get(), buf.get() + size);harus return string(buf.get(), buf.get() + size - 1);lain Anda mendapatkan string dengan karakter nol di akhir. Saya menemukan ini menjadi kasus pada gcc 4.9.
Phil Williams

3
Melewati std :: string ke% s menyebabkan kesalahan kompilasi ( error: tidak dapat melewatkan objek tipe non-sepele 'std :: __ cxx11 :: basic_string <char>' melalui fungsi variadic; panggilan akan dibatalkan saat runtime [-Wnon-pod -varargs] ) di clang 3.9.1, tetapi di CL 19 ia mengkompilasi dengan baik dan crash saat runtime. Adakah bendera peringatan yang dapat saya nyalakan agar dapat membeli itu pada waktu kompilasi juga di cl?
Zitrax

241

Solusi C ++ 11 yang menggunakan vsnprintf()internal:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

Pendekatan yang lebih aman dan efisien (saya mengujinya, dan lebih cepat):

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

Nilai fmt_strini diteruskan untuk menyesuaikan dengan persyaratan va_start.

CATATAN: Versi "lebih aman" dan "lebih cepat" tidak berfungsi pada beberapa sistem. Karenanya keduanya masih terdaftar. Juga, "lebih cepat" tergantung sepenuhnya pada langkah preallokasi menjadi benar, jika tidak strcpymembuat itu lebih lambat.


3
lambat. mengapa menambah ukuran dengan 1? Dan kapan fungsi ini mengembalikan -1?
0xDEAD BEEF

27
Anda menimpa str.c_str ()? Bukankah itu berbahaya?
kuantum

8
va_start dengan argumen referensi memiliki masalah pada MSVC. Gagal diam-diam dan mengembalikan pointer ke memori acak. Sebagai solusinya, gunakan std :: string fmt bukannya std :: string & fmt, atau tulis objek pembungkus.
Steve Hanov

6
Saya memberi +1 karena saya tahu ini mungkin akan bekerja berdasarkan bagaimana kebanyakan std :: string diimplementasikan, namun c_str tidak benar-benar dimaksudkan sebagai tempat untuk memodifikasi string yang mendasarinya. Seharusnya hanya baca-saja.
Doug T.

6
Dan untuk mendapatkan panjang string yang dihasilkan sebelumnya, lihat: stackoverflow.com/a/7825892/908336 Saya tidak melihat titik peningkatan sizedalam setiap iterasi, ketika Anda bisa mendapatkannya dengan panggilan pertama vsnprintf().
Massood Khaari

107

boost::format() menyediakan fungsionalitas yang Anda inginkan:

Seperti dari Boost format perpustakaan sinopsis:

Objek format dibangun dari string format, dan kemudian diberikan argumen melalui panggilan berulang ke operator%. Masing-masing argumen tersebut kemudian dikonversi menjadi string, yang pada gilirannya digabungkan menjadi satu string, sesuai dengan format-string.

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"

5
Anda dapat memangkas pustaka yang Anda butuhkan tanpa dorongan juga. Menggunakan alat bantu.
Hassan Syed

7
Format Boost tidak hanya besar, tetapi juga sangat lambat. Lihat zverovich.net/2013/09/07/… dan boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut

14
Termasuk peningkatan di mana saja di proyek Anda segera meningkatkan waktu kompilasi secara signifikan. Untuk proyek besar, kemungkinan besar tidak masalah. Untuk proyek-proyek kecil, peningkatan merupakan hambatan.
quant_dev

2
@vitaut Meskipun sangat menghabiskan sumber daya jika dibandingkan dengan alternatif. Seberapa sering Anda memformat string? Mengingat itu hanya membutuhkan beberapa detik mikro dan sebagian besar proyek mungkin hanya menggunakannya beberapa lusin kali, itu tidak terlihat dalam proyek yang tidak terlalu fokus pada pemformatan string, bukan?
AturSams

2
Sayangnya, boost :: format tidak bekerja dengan cara yang sama: tidak menerima var_args. Beberapa orang suka memiliki semua kode yang terkait dengan satu program yang terlihat sama / menggunakan idiom yang sama.
xor007

88

C ++ 20 akan termasuk std::formatyang menyerupai sprintfdalam hal API tetapi sepenuhnya tipe-aman, bekerja dengan tipe yang ditentukan pengguna, dan menggunakan sintaks string format Python-like. Inilah cara Anda dapat memformat std::stringdan menulisnya ke aliran:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

atau

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());

Atau, Anda bisa menggunakan pustaka {fmt} untuk memformat string dan menulisnya ke stdoutatau aliran file dalam sekali jalan:

fmt::print(f, "Look, a string: {}", s); // where f is a file stream

Adapun sprintfatau sebagian besar jawaban lain di sini, sayangnya mereka menggunakan varargs dan secara inheren tidak aman kecuali Anda menggunakan sesuatu seperti formatatribut GCC yang hanya berfungsi dengan string format literal. Anda dapat melihat mengapa fungsi-fungsi ini tidak aman pada contoh berikut:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

di mana string_formatimplementasi dari jawaban Erik Aronesty. Kode ini dikompilasi, tetapi kemungkinan besar akan macet ketika Anda mencoba menjalankannya:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

Penafian : Saya penulis {fmt} dan C ++ 20 std::format.


IMHO Anda ketinggalan termasuk error: 'fmt' has not been declared
Sérgio

Ini hanya cuplikan, bukan kode lengkap. Jelas Anda harus memasukkan <fmt / format.h> dan meletakkan kode dalam suatu fungsi.
vitaut

bagi saya tidak begitu jelas, IMHO Anda harus memasukkannya dalam cuplikan, terima kasih atas umpan baliknya
Sérgio

1
Sebuah fmtseperti pelaksanaan ditambahkan ke C ++ 20! stackoverflow.com/a/57286312/895245 fmt saat ini mengklaim dukungan untuk itu. Kerja luar biasa!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

2
@vutut Terima kasih atas pekerjaan Anda dalam hal ini!
Curt Nichols


15

Saya menulis saya sendiri menggunakan vsnprintf sehingga mengembalikan string daripada harus membuat buffer saya sendiri.

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

Jadi Anda bisa menggunakannya seperti

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

Ini membuat salinan data lengkap penuh, dimungkinkan untuk digunakan vsnprintflangsung ke dalam string.
Mooing Duck

1
Gunakan kode di stackoverflow.com/a/7825892/908336 untuk mendapatkan panjang string yang dihasilkan sebelumnya. Dan Anda dapat menggunakan pointer pintar untuk kode pengecualian-aman:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

Saya tidak yakin ini benar dalam kasus fallback; Saya pikir Anda perlu melakukan va_copy vl untuk vsnprintf kedua () untuk melihat argumen dengan benar. Sebagai contoh, lihat: github.com/haberman/upb/blob/…
Josh Haberman

15

Untuk memformat std::stringdengan cara 'sprintf', panggil snprintf(argumen nullptrdan 0) untuk mendapatkan panjang buffer yang diperlukan. Tulis fungsi Anda menggunakan template variadic C ++ 11 seperti ini:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return str;
}

Kompilasi dengan dukungan C ++ 11, misalnya dalam GCC: g++ -std=c++11

Pemakaian:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);

std :: snprintf tidak tersedia di VC ++ 12 (Visual Studio 2013). Ganti dengan _snprintf sebagai gantinya.
Shital Shah

mengapa Anda tidak menggunakan char buf[length + 1];bukan char* buf = new char[length + 1];?
Behrouz.M

Perbedaan antara menggunakan char[]dan char*dengan yang baru, adalah bahwa dalam kasus sebelumnya buf akan dialokasikan pada stack. Tidak apa-apa untuk buffer kecil, tetapi karena kami tidak dapat menjamin ukuran string yang dihasilkan, itu sedikit lebih baik untuk digunakan new. Sebagai contoh pada mesin saya string_sprintf("value: %020000000d",5), cetak angka nol terkemuka sebelum angka 5, core dump ketika menggunakan array pada stack, tetapi berfungsi dengan baik ketika menggunakan array yang dialokasikan secara dinamisnew char[length + 1]
user2622016

ide yang sangat pintar untuk mendapatkan ukuran buff aktual yang dibutuhkan untuk output yang diformat
Chris

1
@ user2622016: Terima kasih atas solusinya! Harap dicatat bahwa std::move itu berlebihan .
Mihai Todor

14

[edit: 20/05/25] lebih baik lagi ...:
Di tajuk:

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

The PRINTSTRING(r)-fungsi adalah untuk cater untuk GUI atau terminal atau output yang khusus kebutuhan menggunakan #ifdef _some_flag_, defaultnya adalah:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[edit '17 / 8/31] Menambahkan versi templated variadic 'vtspf (..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

yang secara efektif adalah versi yang dibatasi koma (sebagai gantinya) dari <<-operator yang terkadang menghalangi , digunakan seperti ini:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


[Sunting] Diadaptasi untuk menggunakan teknik dalam jawaban Erik Aronesty (di atas):

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

[jawaban sebelumnya] Jawaban yang
sangat terlambat, tetapi bagi mereka yang, seperti saya, menyukai cara 'sprintf': Saya telah menulis dan menggunakan fungsi-fungsi berikut. Jika Anda suka, Anda dapat memperluas% -pilihan untuk lebih cocok dengan yang sprintf; yang ada di sana saat ini sudah cukup untuk kebutuhan saya. Anda menggunakan stringf () dan stringfappend () sama seperti Anda akan sprintf. Ingat saja bahwa parameter untuk ... harus tipe POD.

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}

@ MoooDuck: Mengubah parameter fungsi sesuai komentar Dan pada jawaban Aronesty. Saya hanya menggunakan Linux / gcc, dan dengan fmtreferensi itu berfungsi dengan baik. (Tapi saya kira orang-orang akan ingin bermain dengan mainan, jadi ...) Jika ada 'bug' lain yang bisa Anda jelaskan?
slashmais

Saya salah mengerti bagaimana bagian dari kodenya bekerja dan berpikir itu dilakukan untuk banyak ukuran. Memeriksa ulang menunjukkan bahwa saya salah. Kode Anda benar.
Mooing Duck

Membangun dari jawaban Erik Aronesty adalah herring merah. Sampel kode pertamanya tidak aman dan yang kedua tidak efisien dan canggung. Implementasi yang bersih secara jelas ditunjukkan oleh fakta bahwa, jika buf_siz dari salah satu fungsi keluarga vprintf adalah nol, tidak ada yang ditulis dan buffer dapat berupa null pointer, namun nilai kembalinya (jumlah byte yang akan ditulis tidak termasuk null terminator) masih dihitung dan dikembalikan. Jawaban kualitas produksi ada di sini: stackoverflow.com/questions/2342162/…
Douglas Daseeco

10

Beginilah cara Google melakukannya: StringPrintf(Lisensi BSD)
dan facebook melakukannya dengan cara yang sangat mirip: StringPrintf(Lisensi Apache)
Keduanya memberikan kenyamanan StringAppendFjuga.


10

Dua sen saya untuk pertanyaan yang sangat populer ini.

Mengutip halaman manual printffungsi-like :

Setelah berhasil kembali, fungsi-fungsi ini mengembalikan jumlah karakter yang dicetak (tidak termasuk byte nol yang digunakan untuk mengakhiri output ke string).

Fungsi snprintf () dan vsnprintf () tidak menulis lebih dari ukuran byte (termasuk terminasi null byte ('\ 0')). Jika output terpotong karena batas ini maka nilai kembali adalah jumlah karakter (tidak termasuk terminasi null byte) yang akan ditulis ke string akhir jika ruang yang cukup telah tersedia. Dengan demikian, nilai ukuran kembali atau lebih berarti bahwa output terpotong.

Dengan kata lain, implementasi C ++ 11 yang waras harus sebagai berikut:

#include <string>
#include <cstdio>

template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
        // See comments: the +1 is necessary, while the first parameter
        //               can also be set to nullptr

    char bytes[required];
    std::snprintf(bytes, required, fmt.c_str(), vs...);

    return std::string(bytes);
}

Ini bekerja dengan sangat baik :)

Templat variadic hanya didukung di C ++ 11. Jawaban dari pixelpoint menunjukkan teknik serupa menggunakan gaya pemrograman yang lebih lama.

Sungguh aneh bahwa C ++ tidak memiliki hal seperti itu di luar kotak. Mereka baru-baru ini menambahkan to_string(), yang menurut saya merupakan langkah maju yang bagus. Saya ingin tahu apakah mereka akan menambahkan .formatoperator kestd::string ...

Edit

Seperti yang ditunjukkan alexk7, A +1diperlukan pada nilai pengembalian std::snprintf, karena kita perlu memiliki ruang untuk \0byte. Secara intuitif, pada sebagian besar arsitektur yang hilang +1akan menyebabkan requiredbilangan bulat ditimpa sebagian dengan a 0. Ini akan terjadi setelah evaluasi requiredsebagai parameter aktual untukstd::snprintf , sehingga efeknya tidak akan terlihat.

Namun masalah ini dapat berubah, misalnya dengan optimisasi kompiler: bagaimana jika kompiler memutuskan untuk menggunakan register untuk requiredvariabel? Ini adalah jenis kesalahan yang terkadang mengakibatkan masalah keamanan.


1
snprintf selalu menambahkan terminasi null-byte tetapi mengembalikan jumlah karakter tanpa itu. Bukankah kode ini selalu melewati karakter terakhir?
alexk7

@ alexk7, Tangkapan bagus! Saya memperbarui jawabannya. Kode tidak melewatkan karakter terakhir, tetapi menulis di luar akhir bytesbuffer, mungkin di atas requiredbilangan bulat (yang untungnya pada saat itu sudah dievaluasi).
Dacav

1
Hanya sebuah petunjuk kecil: Dengan ukuran buffer 0, Anda bisa meneruskan nullptrargumen buffer, menghilangkan char b;baris dalam kode Anda. ( Sumber )
iFreilicht

@ iFreilicht, diperbaiki. Juga +1
Dacav

2
Menggunakan "char bytes [diperlukan]" akan dialokasikan pada tumpukan alih-alih tumpukan, itu bisa berbahaya pada string format besar. Pertimbangkan untuk menggunakan yang baru. Yann
Yannuth

9
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}

Menggunakan C99 snprintf dan C ++ 11


9

Diuji, Jawaban Kualitas Produksi

Jawaban ini menangani kasus umum dengan teknik yang sesuai standar. Pendekatan yang sama diberikan sebagai contoh di CppReference.com di dekat bagian bawah halaman mereka. Tidak seperti contoh mereka, kode ini cocok dengan persyaratan pertanyaan dan diuji lapangan dalam aplikasi robotika dan satelit. Ini juga telah meningkatkan komentar. Kualitas desain dibahas lebih lanjut di bawah ini.

#include <string>
#include <cstdarg>
#include <vector>

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }

Efisiensi Linier yang Dapat Diprediksi

Dua lintasan adalah kebutuhan untuk fungsi yang dapat digunakan kembali yang aman, andal, dan dapat diprediksi sesuai spesifikasi pertanyaan. Anggapan tentang distribusi ukuran muatan dalam fungsi yang dapat digunakan kembali adalah gaya pemrograman yang buruk dan harus dihindari. Dalam hal ini, representasi panjang variabel yang besar dan sewenang-wenang merupakan faktor kunci dalam pemilihan algoritma.

Mencoba ulang pada overflow secara eksponensial tidak efisien, yang merupakan alasan lain yang dibahas ketika komite standar C ++ 11 membahas proposal di atas untuk memberikan proses yang kering ketika buffer tulis nol.

Dalam implementasi siap produksi di atas, proses pertama adalah proses kering untuk menentukan ukuran alokasi. Tidak ada alokasi yang terjadi. Mengurai arahan printf dan membaca Vargs telah dibuat sangat efisien selama beberapa dekade. Kode yang dapat digunakan kembali harus dapat diprediksi, meskipun inefisiensi kecil untuk kasus sepele harus dikorbankan.

Keamanan dan Keandalan

Andrew Koenig berkata kepada sekelompok kecil dari kami setelah ceramahnya di sebuah acara di Cambridge, "Fungsi pengguna tidak seharusnya bergantung pada eksploitasi kegagalan fungsi fungsional yang tidak biasa." Seperti biasa, kebijaksanaannya telah terbukti benar dalam catatan sejak itu. Masalah bug keamanan tetap dan tertutup sering menunjukkan retry hacks dalam deskripsi lubang yang dieksploitasi sebelum perbaikan.

Ini disebutkan dalam proposal revisi standar formal untuk fitur buffer nol di Alternatif ke sprintf, Proposal Revisi C9X , Dokumen ISO IEC WG14 N645 / X3J11 96-008 . String panjang sewenang-wenang yang dimasukkan per arahan cetak, "% s," dalam batasan ketersediaan memori dinamis, tidak terkecuali, dan tidak boleh dieksploitasi untuk menghasilkan, "Fungsionalitas luar biasa."

Pertimbangkan proposal di samping kode contoh yang diberikan di bagian bawah halaman C ++ Reference.org yang ditautkan pada paragraf pertama dari jawaban ini.

Juga, pengujian kasus kegagalan jarang sama kuatnya dengan kasus sukses.

Portabilitas

Semua vendor OS utama menyediakan kompiler yang sepenuhnya mendukung std :: vsnprintf sebagai bagian dari standar c ++ 11. Host yang menjalankan produk vendor yang tidak lagi memelihara distribusi harus dilengkapi dengan g ++ atau clang ++ karena berbagai alasan.

Penggunaan Stack

Penggunaan stack pada panggilan ke-1 ke std :: vsnprintf akan kurang dari atau sama dengan panggilan ke-2, dan akan dibebaskan sebelum panggilan ke-2 dimulai. Jika panggilan pertama melebihi ketersediaan tumpukan, maka std :: fprintf akan gagal juga.


Singkat dan kuat. Ini bisa gagal pada HP-UX, IRIX, Tru64 yang memiliki vsnprintf-s yang tidak sesuai. EDIT: juga, mengingat bagaimana dua lintasan dapat mempengaruhi kinerja, esp. untuk pemformatan string kecil yang paling umum, apakah Anda menganggap tebakan awal, yang mungkin cukup besar?
Engineerist

FWIW, dugaan yang saya maksudkan adalah menggunakan buffer-dialokasikan buffer di mana menjalankan pertama kali terjadi. Jika cocok itu menghemat biaya menjalankan kedua dan alokasi dinamis yang terjadi di sana. Agaknya, string kecil lebih sering digunakan daripada string besar. Dalam tolok ukur kasar saya bahwa strategi (hampir) membagi dua waktu berjalan untuk string kecil dan berada dalam beberapa persen (overhead tetap mungkin?) Dari strategi di atas. Tolong uraikan desain C ++ 11 yang menggunakan dry run, dll.? Saya ingin membacanya.
Engineerist

@ Engineer, pertanyaan Anda telah dialamatkan di badan jawaban, di atas dan di bawah kode. Subtopik dapat dibuat lebih mudah untuk dibaca dengan cara itu.
Douglas Daseeco

6

C ++ 20 std::format

Itu telah datang! Fitur ini dijelaskan di: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html dan menggunakan .format()sintaks mirip-Python .

Saya berharap penggunaannya akan seperti:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

Saya akan mencobanya ketika dukungan datang ke GCC, GCC 9.1.0 dengan g++-9 -std=c++2amasih tidak mendukungnya.

API akan menambahkan std::formatheader baru :

API pemformatan yang diusulkan didefinisikan di header baru <format>dan seharusnya tidak berdampak pada kode yang ada.

fmtPustaka yang ada mengklaim untuk mengimplementasikannya jika Anda memerlukan polyfill: https://github.com/fmtlib/fmt

Implementasi C ++ 20 std::format.

dan sebelumnya disebutkan di: std :: pemformatan string seperti sprintf


5

Berdasarkan jawaban yang diberikan oleh Erik Aronesty:

std::string string_format(const std::string &fmt, ...) {
    std::vector<char> str(100,'\0');
    va_list ap;
    while (1) {
        va_start(ap, fmt);
        auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
        va_end(ap);
        if ((n > -1) && (size_t(n) < str.size())) {
            return str.data();
        }
        if (n > -1)
            str.resize( n + 1 );
        else
            str.resize( str.size() * 2);
    }
    return str.data();
}

Ini menghindari kebutuhan untuk membuang constdari hasil .c_str()yang ada dalam jawaban asli.


1
Membangun dari jawaban Erik Aronesty adalah herring merah. Sampel kode pertamanya tidak aman dan yang kedua, dengan loop tidak efisien dan canggung. Implementasi yang bersih secara jelas ditunjukkan oleh fakta bahwa, jika buf_siz dari salah satu fungsi keluarga vprintf adalah nol, tidak ada yang ditulis dan buffer dapat berupa null pointer, namun nilai kembalinya (jumlah byte yang akan ditulis tidak termasuk null terminator) masih dihitung dan dikembalikan. Jawaban kualitas produksi ada di sini: stackoverflow.com/questions/2342162/…
Douglas Daseeco

Jawaban Erik Aronesty telah diedit sejak saya ditambahkan. Saya ingin menyorot opsi menggunakan vektor <char> untuk menyimpan string saat dibuat. Saya sering menggunakan teknik ini saat memanggil fungsi C dari kode C ++. Sangat menarik bahwa pertanyaan itu sekarang memiliki 34 jawaban.
ChetS

Contoh cppreference.com pada halaman vfprintf ditambahkan kemudian. Saya percaya jawaban terbaik adalah jawaban yang saat ini diterima, menggunakan string stream bukan varian printf adalah cara C ++. Namun jawaban saya memang menambah nilai ketika diberikan; Itu lebih baik daripada jawaban lain pada saat itu. Sekarang standar memiliki string_view, paket parameter dan templat Variadic jawaban baru dapat mencakup fitur-fitur tersebut. Adapun jawaban saya, meskipun mungkin tidak lagi layak mendapatkan suara tambahan tambahan, itu tidak layak untuk dihapus atau turun suara, jadi saya membiarkannya begitu saja.
ChetS

5
inline void format(string& a_string, const char* fmt, ...)
{
    va_list vl;
    va_start(vl, fmt);
    int size = _vscprintf( fmt, vl );
    a_string.resize( ++size );
    vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
    va_end(vl);
}

1
Memberi +1 untuk gagasan cerdas, tetapi tidak terlalu jelas apa _vscprintfitu. Saya pikir Anda harus menguraikan jawaban ini.
Dacav

3

string tidak memiliki apa yang Anda butuhkan, tetapi std :: stringstream tidak. Gunakan stringstream untuk membuat string dan kemudian mengekstrak string. Berikut adalah daftar lengkap tentang hal-hal yang dapat Anda lakukan. Sebagai contoh:

cout.setprecision(10); //stringstream is a stream like cout

akan memberi Anda 10 tempat desimal presisi saat mencetak double atau float.


8
yang masih tidak memberi Anda apa pun di dekat printf kontrol memberi Anda ... tapi bagus.
Erik Aronesty

3

Anda bisa mencoba ini:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );

3

Jika Anda berada di sistem yang memiliki asprintf (3) , Anda dapat dengan mudah membungkusnya:

#include <iostream>
#include <cstdarg>
#include <cstdio>

std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

std::string format(const char *fmt, ...)
{
    std::string result;

    va_list ap;
    va_start(ap, fmt);

    char *tmp = 0;
    int res = vasprintf(&tmp, fmt, ap);
    va_end(ap);

    if (res != -1) {
        result = tmp;
        free(tmp);
    } else {
        // The vasprintf call failed, either do nothing and
        // fall through (will return empty string) or
        // throw an exception, if your code uses those
    }

    return result;
}

int main(int argc, char *argv[]) {
    std::string username = "you";
    std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
    return 0;
}

2
Saya akan menambahkan baris ini sebagai deklarasi sebelumnya format, karena memberitahu gcc untuk memeriksa jenis argumen dan memberikan peringatan yang layak dengan -Dinding:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid

2
Saya baru saja menambahkan panggilan ke va_end. "Jika va_end tidak dipanggil sebelum fungsi yang memanggil va_start atau va_copy kembali, perilaku tidak terdefinisi." - docs
Aaron McDaid

1
Anda harus memeriksa hasil pengembalian vasprintf karena nilai pointer tidak ditentukan pada kegagalan. Jadi, mungkin sertakan <new> dan tambahkan: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

Poin baiknya, saya telah memodifikasi jawaban sesuai, saya telah memutuskan untuk hanya memberikan komentar di sana daripada melakukan throw std::bad_alloc();, karena saya tidak menggunakan pengecualian C ++ di basis kode saya, dan untuk orang yang melakukannya, mereka dapat dengan mudah menambahkannya berdasarkan pada sumber komentar dan komentar Anda di sini.
Thomas Perl

2

Ini adalah kode yang saya gunakan untuk melakukan ini di program saya ... Ini bukan sesuatu yang mewah, tetapi itu triknya ... Catatan, Anda harus menyesuaikan ukuran Anda sebagaimana berlaku. MAX_BUFFER bagi saya adalah 1024.

std::string Format ( const char *fmt, ... )
{
    char textString[MAX_BUFFER*5] = {'\0'};

    // -- Empty the buffer properly to ensure no leaks.
    memset(textString, '\0', sizeof(textString));

    va_list args;
    va_start ( args, fmt );
    vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
    va_end ( args );
    std::string retStr = textString;
    return retStr;
}

4
Inisialisasi textString sudah menetapkan seluruh buffer menjadi nol. Tidak perlu memset ...
EricSchaefer

Ini membuat salinan data lengkap penuh, dimungkinkan untuk digunakan vsnprintflangsung ke dalam string.
Mooing Duck

2

Mengambil ide dari jawaban Dacav dan pixelpoint . Saya bermain-main sedikit dan mendapatkan ini:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

Dengan praktik pemrograman waras saya percaya kode harus cukup, namun saya masih terbuka untuk alternatif yang lebih aman yang masih cukup sederhana dan tidak memerlukan C ++ 11.


Dan inilah versi lain yang menggunakan buffer awal untuk mencegah panggilan kedua vsnprintf()ketika buffer awal sudah cukup.

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(Ternyata versi ini mirip dengan jawaban Piti Ongmongkolkul , hanya saja tidak digunakan newdan delete[], dan juga menentukan ukuran saat membuatstd::string .

Gagasan di sini untuk tidak menggunakan newdan delete[]menyiratkan penggunaan tumpukan di atas tumpukan karena itu tidak perlu memanggil fungsi alokasi dan deallokasi, namun jika tidak digunakan dengan benar, bisa berbahaya untuk buffer overflow di beberapa (mungkin lama, atau mungkin hanya sistem yang rentan). Jika ini merupakan masalah, saya sangat menyarankan menggunakan newdandelete[] sebagai gantinya. Perhatikan bahwa satu-satunya masalah di sini adalah tentang alokasi seperti vsnprintf()yang sudah disebut dengan batas, jadi menentukan batas berdasarkan ukuran yang dialokasikan pada buffer kedua juga akan mencegahnya.)


2

Saya biasanya menggunakan ini:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

Kerugian: tidak semua sistem mendukung vasprint


vasprintf bagus - namun Anda perlu memeriksa kode kembali. Pada -1 buffer akan memiliki nilai yang tidak ditentukan. Perlu: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

2

Versi @iFreilicht jawaban yang sedikit dimodifikasi, diperbarui ke C ++ 14 (penggunaan make_uniquefungsi alih-alih deklarasi mentah) dan menambahkan dukungan untuk std::stringargumen (berdasarkan artikel Kenny Kerr )

#include <iostream>
#include <memory>
#include <string>
#include <cstdio>

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

Keluaran:

i = 3, f = 5.000000, s = hello world

Silakan menggabungkan jawaban ini dengan yang asli jika diinginkan.



1

Anda dapat memformat output C ++ dalam cout menggunakan file header iomanip. Pastikan Anda menyertakan file header iomanip sebelum Anda menggunakan salah satu fungsi pembantu seperti setprecision, setfill dll.

Berikut ini cuplikan kode yang saya gunakan di masa lalu untuk mencetak waktu tunggu rata-rata dalam vektor, yang telah saya "kumpulkan".

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

Berikut ini adalah deskripsi singkat tentang bagaimana kami dapat memformat aliran C ++. http://www.cprogramming.com/tutorial/iomanip.html


1

Mungkin ada masalah, jika buffer tidak cukup besar untuk mencetak string. Anda harus menentukan panjang string yang diformat sebelum mencetak pesan yang diformat di sana. Saya membuat pembantu sendiri untuk ini (diuji pada Windows dan Linux GCC ), dan Anda dapat mencoba menggunakannya.

String.cpp: http://pastebin.com/DnfvzyKP
String.h: http://pastebin.com/7U6iCUMa

String.cpp:

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

Berkenaan dengan garis vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- Apakah aman untuk mengasumsikan buffer string memiliki ruang untuk mengakhiri karakter null? Apakah ada implementasi yang tidak mengalokasikan ukuran + 1 karakter. Akankah lebih aman untuk melakukannyadst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

Rupanya jawaban untuk komentar saya sebelumnya adalah: Tidak, itu TIDAK aman untuk menganggap ada karakter nol. Khusus berkaitan dengan spesifikasi C ++ 98: "Mengakses nilai pada data () + ukuran () menghasilkan perilaku yang tidak terdefinisi : Tidak ada jaminan bahwa karakter nol akan mengakhiri urutan karakter yang ditunjukkan oleh nilai yang dikembalikan oleh fungsi ini. Lihat string :: c_str untuk fungsi yang memberikan jaminan seperti itu. Program tidak boleh mengubah karakter apa pun dalam urutan ini. "Namun, spesifikasi C ++ 11 menunjukkan itu datadan c_strsinonim.
drwatsoncode


1

Solusi yang sangat-sangat sederhana.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

1

Saya menyadari ini telah dijawab berkali-kali, tetapi ini lebih ringkas:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

contoh:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

Lihat juga http://rextester.com/NJB14150


1

UPDATE 1 : fmt::formattes ditambahkan

Saya telah mengambil investigasi saya sendiri di sekitar metode yang telah diperkenalkan di sini dan mendapatkan hasil yang berlawanan secara berlawanan dengan yang disebutkan di sini.

Saya telah menggunakan 4 fungsi lebih dari 4 metode:

  • fungsi variadic + vsnprintf+std::unique_ptr
  • fungsi variadic + vsnprintf+std::string
  • fungsi templat variadic + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfungsi dari fmtperpustakaan

Untuk tes backend yang googletesttelah digunakan.

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

The for_eachpelaksanaan diambil dari sini: iterate lebih tuple

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

Tes:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

The UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

unsued.hpp :

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

unused.cpp :

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

HASIL :

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

Seperti yang Anda lihat implementasi melalui vsnprintf+ std::stringsama dengan fmt::format, tetapi lebih cepat daripada melalui vsnprintf+ std::unique_ptr, yang lebih cepat daripada melalui std::ostringstream.

Tes dikompilasi Visual Studio 2015 Update 3dan dijalankan pada Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.