std :: sprintf gibi dize biçimlendirme


454

Ben biçimine sahip std::stringolan sprintfve dosya akışı içine göndermek. Bunu nasıl yapabilirim?


6
uzun hikaye kısa kullanım boost::format(kennytm'in çözümü burada kullandığı için ). boost::formatzaten C ++ akış operatörlerini de destekliyor! Örnek: cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formaten az kod satırına sahiptir ... hakemli ve C ++ akışları ile güzel bir şekilde bütünleşir.
Trevor Boyd Smith

@Ockonal - Topluluk uğruna (temsilcim hakkında daha az umursamadım) seçiminizi değiştirmenizi öneririm. İlk snippet'te seçili olan, keyfi bir maksimum uzunluk kullanımında gerçekleşmesini bekleyen bir hata sunar. İkinci pasaj, sprintf gibi değişkenleri kullanma isteğinizi tamamen göz ardı eder. Burada temiz, güvenli, sadece C ++ standartlarına dayanan, test edilmiş ve iyi yorumlanmış SADECE cevabı seçmenizi öneririm. Benim olması önemli değil. Nesnel olarak doğrudur. Bkz. Stackoverflow.com/questions/2342162/… .
Douglas Daseeco

@TrevorBoydSmith a std::formatC ++ 20 BTW'ye eklendi: stackoverflow.com/a/57286312/895245 Harika!
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

1
@CiroSantilli i hakkında bir makale okudum C++20bu sadece dün ve ben testere C++20kopyalanan boostekleyerek (şimdi milyonuncu kez) std::formatiçin C++20spec! Çok mutluydum! Son 9 yılda yazdığım neredeyse her C ++ dosyası kullanıldı boost::format. C ++ 'daki akışlara resmi printf stili çıktı eklemek, tüm C ++ için uzun bir IMO yoluna gidecektir.
Trevor Boyd Smith

Yanıtlar:


334

Doğrudan yapamazsınız, çünkü temeldeki ara belleğe yazma erişiminiz yoktur (C ++ 11'e kadar; Dietrich Epp'in yorumuna bakın ). İlk önce bir c-string ile yapmanız ve ardından bir std :: string'e kopyalamanız gerekir:

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

Ama neden sadece bir string akışı kullanmıyorsunuz? Sadece bunu yapmak için değil, belirli nedenlerin olduğunu varsayıyorum:

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

17
Sihirli çerez char buf[100];bu çözümü çok sağlam değil. Ama asıl fikir orada.
John Dibling

18
John, akarsular yavaş değil. Akışların yavaş görünmesinin tek nedeni, iostreams'in varsayılan olarak C FILE çıktısıyla senkronize edilmesidir, böylece karıştırılmış cout ve printfs doğru şekilde çıkarılır. Bu bağlantıyı devre dışı bırakmak (cout.sync_with_stdio (false) çağrısıyla), c ++ 'ın akışlarının en azından MSVC10'dan itibaren stdio'dan daha iyi performans göstermesine neden olur.
Jimbo

72
Biçimleri kullanmanın nedeni, yerelleştiricinin cümlenin dilbilgisini zor kodlamak yerine yabancı diller için cümlenin yapısını yeniden oluşturmasına izin vermektir.
Martijn Courteaux

216
Bazı nedenlerden dolayı, diğer diller printf benzeri sözdizimini kullanır: Java, Python (yeni sözdizimi hala printf'e akışlardan daha yakındır). Masum insanlara sadece C ++ bu ayrıntılı iğrençliği verir.
quant_dev

9
Daha da iyisi, asprintfsonucu tutmak için yeterli alana sahip yeni bir dize ayıran kullanın . Sonra std::stringisterseniz bir kopyalayın freeve orijinali hatırlayın . Ayrıca, bunu bir makroya koymak mümkündür, böylece herhangi bir iyi derleyici formatı doğrulamanıza yardımcı olur - doublebir %sbeklendiği yere koymak istemezsiniz
Aaron McDaid

287

Modern C ++ bunu süper basitleştirir.

C ++ 20

C ++ 20std::format , tam olarak bunu yapmanızı sağlayan tanıtır . Bu benzer yedek alanlarını kullanan piton olanlar :

#include <iostream>
#include <format>

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

Tüm belgelere göz atın ! Büyük bir yaşam kalitesi iyileştirmesi.


C ++ 11

İle C ++ 11 s std::snprintf, bu zaten oldukça kolay ve güvenli bir görev haline geldi.

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

Yukarıdaki kod snippet'i CC0 1.0 altında lisanslanmıştır .

Satır satır açıklama:

Amaç: achar*tuşunu kullanarak yazınstd::snprintfve ardından a'ya dönüştürünstd::string.

İlk olarak, içinde özel bir koşul kullanarak char dizisinin istenen uzunluğunu belirleriz snprintf. Gönderen cppreference.com :

Geri dönüş değeri

[...] Sonuçta elde edilen dize buf_size sınırı nedeniyle kesilirse, işlev, sınır verilmemişse, yazılacak toplam karakter sayısını (sonlanan boş bayt dahil değil) döndürür.

Bu, istenen boyutun karakter sayısı artı bir olduğu anlamına gelir , böylece boş sonlandırıcı diğer tüm karakterlerin arkasına oturacak ve dize oluşturucu tarafından tekrar kesilebilecektir. Bu sorun yorumlarda @ alexk7 tarafından açıklanmıştır.

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

snprintfbir hata oluşursa negatif bir sayı döndürür, bu nedenle biçimlendirmenin istendiği gibi çalışıp çalışmadığını kontrol ederiz. Bunu yapmamak, yorumlarda @ead'in belirttiği gibi sessiz hatalara veya büyük bir arabellek tahsisine yol açabilir.

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

Ardından, yeni bir karakter dizisi atarız ve onu a'ya atarız std::unique_ptr. deleteBunu tekrar manuel olarak yapmak zorunda kalmayacağınız için genellikle tavsiye edilir .

Bunun, unique_ptryapıcı bir istisna atarsa ​​belleği ayıramayacağınız için kullanıcı tanımlı türlerle ayırmanın güvenli bir yolu olmadığını unutmayın !

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

Bundan sonra, elbette sadece snprintfamaçlanan kullanımı için kullanabilir ve biçimlendirilmiş dizeyi char[].

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

Son olarak, std::stringsonunda boş-sonlandırıcıyı atladığınızdan emin olarak yeni bir tane oluşturup iade ediyoruz .

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

Burada bir örnek görebilirsiniz .


std::stringArgüman listesinde de kullanmak istiyorsanız , bu özete bir göz atın .


Visual Studio kullanıcıları için ek bilgiler :

Açıklandığı gibi bu cevap Microsoft değiştirildi std::snprintfetmek _snprintf(evet, olmadan std::). MS ayrıca kullanımdan kaldırılmıştır ve _snprintf_sbunun yerine kullanılmasını önerir , ancak _snprintf_stamponun biçimlendirilmiş çıktıdan sıfır veya daha küçük olduğunu kabul etmez ve bu durumda çıktıların uzunluğunu hesaplamaz. Bu nedenle derleme sırasında kullanımdan kaldırma uyarılarından kurtulmak için dosyanın üst kısmına aşağıdakilerin kullanımını içeren aşağıdaki satırı ekleyebilirsiniz _snprintf:

#pragma warning(disable : 4996)

Son düşünceler

Bu soruya birçok cevap C ++ 11 zamanından önce yazılmıştır ve sabit tampon uzunlukları veya değişkenleri kullanılmıştır. C ++ 'ın eski sürümlerine bağlı kalmadıkça, bu çözümleri kullanmanızı tavsiye etmem. İdeal olarak, C ++ 20 yoluna gidin.

Bu yanıttaki C ++ 11 çözümü şablonlar kullandığından, çok kullanılırsa biraz kod üretebilir. Bununla birlikte, ikili dosyalar için çok sınırlı alana sahip bir ortam için geliştirmediğiniz sürece, bu bir sorun olmayacak ve hala hem açıklık hem de güvenlikteki diğer çözümlere göre büyük bir gelişme olacaktır.

Alan verimliliği çok önemliyse, değişkenler ve vsnprintf içeren bu iki çözüm yararlı olabilir. Sadece sorun isteyen sabit tampon uzunluklarına sahip herhangi bir çözüm KULLANMAYIN .


2
Lütfen Visual Studio kullanıcıları için yanıtınızda VS sürümünün en az 2013 olması gerektiğini vurgulayın. Bu makaleden yalnızca VS2013 sürümü ile çalıştığını görebilirsiniz: Arabellek boş bir işaretçi ise ve sayı sıfırsa, len şu şekilde döndürülür: sona erdirme null değeri dahil değil, çıktıyı biçimlendirmek için gereken karakter sayısı. Aynı bağımsız değişken ve yerel ayar parametreleriyle başarılı bir çağrı yapmak için en az len + 1 karakter içeren bir arabellek ayırın.
cha

3
@moooeeeep Birden fazla neden. İlk olarak, burada amaç bir c-string değil, bir std :: string döndürmektir return string(&buf[0], size);. İkincisi, böyle bir c-string döndürürseniz tanımsız davranışa neden olur çünkü işaret ettiğiniz değerleri tutan vektör dönüşte geçersiz kılınır. Üçüncüsü, C ++ öğrenmeye başladığımda, standart hangi öğelerin hangi sırayla depolanması gerektiğini tanımlamıyordu std::vector, bu nedenle depolama alanına bir işaretçi aracılığıyla erişmek tanımsız davranıştı. Şimdi işe yarayacaktı, ama bu şekilde yapmanın hiçbir yararı yok.
iFreilicht

2
@iFreilicht std::stringÖrtük olarak dönüştürülmüş vektörden ( kopya başlatma ) yeni bir yapı oluşturulur ve bu fonksiyonun imzasının belirttiği gibi bir kopya olarak döndürülür. Ayrıca a öğelerinin elemanları bitişikstd::vector olarak saklanır ve her zaman olması amaçlanmıştır . Ama bunu yapmanın faydası olmayabileceğine inanıyorum.
moooeeeep

4
Bu çözümü gerçekten çok seviyorum, ancak satırın sonunda boş karakterli bir dize almanız return string(buf.get(), buf.get() + size);gerektiğini düşünüyorum return string(buf.get(), buf.get() + size - 1);. Bunu gcc 4.9'da buldum.
Phil Williams

3
% S'ye bir std :: dizesini iletmek bir derleme hatasına neden olur ( hata: önemsiz olmayan 'std :: __ cxx11 :: basic_string <char>' nesnesini varyasyon işlevi ile geçiremez; çağrı çalışma zamanında iptal edilir [-Wnon-pod -varargs] ) clang 3.9.1'de , ancak CL 19'da para cezası derler ve bunun yerine çalışma zamanında çöker. Herhangi bir uyarı bayrağı cl derleme zamanında da öksürmek için açabilirsiniz?
Zitrax

241

vsnprintf()Dahili olarak kullanılan C ++ 11 çözümü :

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

Daha güvenli ve daha verimli (test ettim ve daha hızlı) bir yaklaşım:

#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());
}

fmt_strGereklerine uymak için değeri tarafından geçirilir va_start.

NOT: "Daha güvenli" ve "daha hızlı" sürüm bazı sistemlerde çalışmaz. Bu nedenle, her ikisi de hala listelenmiştir. Ayrıca, "daha hızlı" tamamen önceden konumlandırma adımının doğru olmasına bağlıdır, aksi takdirde strcpyyavaşlar.


3
yavaş. boyutu neden 1 arttırmalıyım? Ve bu cisim ne zaman -1 döndürür?
0xDEAD BEEF

27
Str.c_str () üzerine mi yazıyorsunuz? Bu tehlikeli değil mi?
kuantum

8
Bir başvuru bağımsız değişkeniyle va_start'ın MSVC'de sorunları var. Sessizce başarısız olur ve işaretçileri rastgele belleğe döndürür. Geçici bir çözüm olarak, std :: string & fmt yerine std :: string fmt kullanın veya bir sarıcı nesnesi yazın.
Steve Hanov

6
I + 1'd bu muhtemelen en std :: dizeleri nasıl uygulandığına dayalı çalışacağını biliyorum neden, ancak c_str gerçekten temel dize değiştirmek için bir yer olması amaçlanmamıştır. Salt okunur olması gerekiyordu.
Doug T.

6
Ve sonuçta elde edilen dize uzunluğunu önceden elde etmek için, bkz: stackoverflow.com/a/7825892/908336size İlk çağrı ile elde edebileceğiniz zaman, her bir yinelemede artan noktayı görmüyorum vsnprintf().
Massood Khaari

107

boost::format() istediğiniz işlevselliği sağlar:

Boost biçimi kitaplıkları özeti gelince:

Bir format nesnesi bir format-stringinden oluşturulur ve daha sonra% operatörüne tekrarlanan çağrılar yoluyla argümanlar verilir. Bu argümanların her biri daha sonra format-string'e göre bir dizeye birleştirilen dizelere dönüştürülür.

#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
ihtiyacınız olan kütüphaneleri de zenginleştirebilirsiniz. Birlikte verilen bir araç kullanma.
Hassan Syed

7
Yükseltme Biçimi sadece büyük değil, aynı zamanda çok yavaştır. Bkz. Zverovich.net/2013/09/07/… ve boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut

14
Projenizin herhangi bir yerine destek eklemek derleme sürelerini önemli ölçüde artırır. Büyük projeler için büyük olasılıkla önemli değil. Küçük projeler için, destek bir sürüklemedir.
quant_dev

2
@vitaut Alternatiflere kıyasla çok fazla kaynak tüketiyor olsa da . Dizeleri ne sıklıkla biçimlendirirsiniz? Sadece birkaç mikro saniye sürdüğünü ve çoğu projenin muhtemelen sadece birkaç düzine kez kullandığını düşünürsek, dize biçimlendirmesine yoğun bir şekilde odaklanmayan bir projede fark edilmez, değil mi?
AturSams

2
Ne yazık ki, boost :: formatı aynı şekilde çalışmaz: var_args öğesini kabul etmez. Bazı insanlar aynı / aynı deyimleri kullanan tek bir programla ilgili tüm kodlara sahip olmak ister.
xor007

88

C ++ 20 , API açısından std::formatbenzer sprintfolan ancak tam olarak güvenli olan, kullanıcı tanımlı türlerle çalışan ve Python benzeri biçim dizesi sözdizimini kullanan içerir. std::stringBunu bir akışa nasıl biçimlendirip yazabileceğiniz aşağıda açıklanmıştır:

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

veya

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

Alternatif olarak, bir dizeyi biçimlendirmek ve tek seferde bir dosya akışına veya bir dosya akışına yazmak için {fmt} kitaplığını kullanabilirsiniz stdout:

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

sprintfBuradaki diğer cevaplara veya çoğuna gelince , maalesef varargs kullanıyorlar ve formatsadece gerçek biçim dizeleriyle çalışan GCC özniteliği gibi bir şey kullanmazsanız doğal olarak güvenli değiller . Aşağıdaki örneklerde bu işlevlerin neden güvensiz olduğunu görebilirsiniz:

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

string_formatErik Aronesty'nin cevabından bir uygulama nerede . Bu kod derlenir, ancak çalıştırmaya çalıştığınızda büyük olasılıkla kilitlenir:

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

Feragatname : {fmt} ve C ++ 20'nin yazarıyım std::format.


IMHO sen özledim özledim error: 'fmt' has not been declared
Sérgio

Bu sadece bir pasaj, tam bir kod değil. Açıkçası <fmt / format.h> yazmanız ve kodu bir işleve koymanız gerekir.
vitaut

benim için çok açık değil, IMHO snippet'e eklemelisiniz, geri bildiriminiz için teşekkürler
Sérgio

1
Bir fmtuygulamaya gibi C ++ 20 eklendi! stackoverflow.com/a/57286312/895245 fmt şu anda bunun için destek talep ediyor. Mükemmel iş!
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

2
@vitaut Bu konudaki çalışmalarınız için teşekkür ederiz!
Curt Nichols


15

Kendi arabelleğimi oluşturmak yerine dize döndürür vsnprintf kullanarak kendi yazdı.

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

Böylece kullanabilirsiniz

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

Bu, verilerin tam bir kopyasını yapar, vsnprintfdoğrudan dizeye kullanmak mümkündür .
Mooing Duck

1
Elde edilen dize uzunluğunu önceden elde etmek için stackoverflow.com/a/7825892/908336 içindeki kodu kullanın . İstisnai olarak güvenli bir kod için akıllı işaretçiler kullanabilirsiniz:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

Bunun yedek durumda doğru olduğundan emin değilim; Bağımsız değişkenleri doğru görmek için ikinci vsnprintf () için vl va_copy yapmanız gerektiğini düşünüyorum. Örnek için bakınız: github.com/haberman/upb/blob/…
Josh Haberman

15

Biçimi için std::stringbir 'sprintf' şekilde, çağrı snprintf(bağımsız nullptrve 0gerekli tampon uzunluğunu elde etmek için). İşlevinizi aşağıdaki gibi C ++ 11 varyasyon şablonu kullanarak yazın:

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

C ++ 11 desteğiyle derleyin, örneğin GCC'de: g++ -std=c++11

Kullanımı:

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

std :: snprintf, VC ++ 12'de (Visual Studio 2013) kullanılamaz. Bunun yerine _snprintf ile değiştirin.
Shital Shah

neden char buf[length + 1];yerine kullanmıyorsun char* buf = new char[length + 1];?
Behrouz.M

Kullanarak arasındaki fark char[]ve char*yeni olan, önceki durumda tampon tamponundan yığında tahsis edilecek olmasıdır. Küçük tamponlar için sorun yok, ancak sonuçta elde edilen dizenin boyutunu garanti edemediğimizden, kullanımı biraz daha iyidir new. Örneğin, makinemde string_sprintf("value: %020000000d",5), 5 numaradan önce çok sayıda önde gelen sıfır, baskı yığınında diziyi kullanırken çekirdek dökümlerini yazdırın, ancak dinamik olarak ayrılmış dizi kullanılırken Tamam çalışıyornew char[length + 1]
user2622016 19:16

biçimlendirilmiş çıktı için gereken gerçek buff boyutunu elde etmek için çok akıllıca bir fikir
Chris

1
@ user2622016: Çözüm için teşekkürler! Lütfen bunun std::move gereksiz olduğunu unutmayın .
Mihai Todor

14

[değiştir: 20/05/25] daha da iyisi ...: Başlıkta
:

// `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; }

PRINTSTRING(r)Taşımasının avantajlı GUI veya terminal veya kullanan herhangi bir özel çıkış ihtiyaçları için hitap etmektir #ifdef _some_flag_, varsayılan:

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

[değiştir '17 / 8/31] Değişken şablonlu 'vtspf (..)' sürümü ekleniyor:

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

bu, bazen engellenen <<operatörlerin (bunun yerine) etkili bir şekilde virgülle sınırlandırılmış bir versiyonudur , şöyle kullanılır:

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


Erik Aronesty'nin cevabındaki (yukarıda) tekniği kullanmak için uyarlanmıştır:

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

[önceki cevap]
Çok geç bir cevap, ama benim gibi 'sprintf' yolunu sevenler için: Ben yazdım ve aşağıdaki işlevleri kullanıyorum. Eğer isterseniz, sprintf'lere daha yakından uyacak şekilde% -options öğesini genişletebilirsiniz; oradaki olanlar benim ihtiyaçlarım için yeterli. Sprintf ile aynı stringf () ve stringfappend () yöntemlerini kullanırsınız. Sadece ... için parametrelerin POD türleri olması gerektiğini unutmayın.

//=============================================================================
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;
}

@MooingDuck: Dan'ın Aronesty'nin cevabına yaptığı yorum uyarınca işlev parametresi değiştirildi. Sadece Linux / gcc kullanıyorum ve fmtreferans olarak iyi çalışıyor. (Ama sanırım insanlar oyuncaklarla oynamak isteyecekler, bu yüzden ...) Başka bir sözde 'böcek' varsa lütfen biraz ayrıntı verebilir misiniz?
slashmais

Kodunun bir kısmının nasıl çalıştığını yanlış anladım ve birçok yeniden boyutlandırma için yapıldığını düşündüm. Yeniden inceleme yanıldığımı gösteriyor. Kodunuz doğru.
Mooing Duck

Erik Aronesty'nin cevabını oluşturmak kırmızı bir ringa balığıdır. İlk kod örneği güvensiz, ikincisi verimsiz ve beceriksiz. Temiz uygulama, herhangi bir vprintf işlev ailesinin buf_sizesi sıfır ise, hiçbir şey yazılmaz ve arabellek boş bir işaretçi olabilir, ancak dönüş değeri (yazılmayacak bayt sayısı dahil değildir) null sonlandırıcı) hala hesaplanır ve döndürülür. Bir üretim kalitesi yanıtı burada: stackoverflow.com/questions/2342162/…
Douglas Daseeco

10

Google böyle yapar: StringPrintf(BSD Lisansı)
ve facebook bunu oldukça benzer bir şekilde yapar: StringPrintf(Apache Lisansı)
Her ikisi de uygun bir StringAppendFözellik sunar.


10

Bu çok popüler soru üzerine iki sentim.

Benzeri işlevlerin kılavuz sayfasınıprintf alıntılamak için :

Başarılı bir şekilde geri döndüğünde, bu işlevler yazdırılan karakter sayısını döndürür (çıktıyı dizelere sonlandırmak için kullanılan boş bayt hariç).

Snprintf () ve vsnprintf () işlevleri boyut baytlarından fazlasını yazmaz (sonlandırıcı boş bayt ('\ 0') dahil). Çıktı bu sınırdan dolayı kesilirse, dönüş değeri, yeterli alan varsa son dizeye yazılan karakter sayısıdır (sonlandırıcı boş bayt hariç). Böylece, boyut veya daha büyük bir dönüş değeri, çıktının kesildiği anlamına gelir.

Başka bir deyişle, aklı başında bir C ++ 11 uygulaması aşağıdaki gibi olmalıdır:

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

Oldukça iyi çalışıyor :)

Değişken şablonları yalnızca C ++ 11'de desteklenir. Piksel noktasından gelen cevap, eski programlama stillerini kullanarak benzer bir tekniği göstermektedir.

C ++ 'ın kutunun dışında böyle bir şey olmaması garip. Son zamanlarda to_string(), bence ileriye doğru büyük bir adım eklediler . Sonunda bir .formatoperatör ekleyip eklemeyeceklerini merak ediyorum std::string...

Düzenle

Alexk7'nin işaret ettiği gibi, bayt için yer +1olması gerektiğinden A'nın dönüş değerine std::snprintfihtiyaç vardır \0. Sezgisel olarak, eksik olan çoğu mimaride +1, requiredtamsayı a ile kısmen üzerine yazılacaktır 0. Bu, gerçek parametre olarak değerlendirildikten sonra gerçekleşecektir , bu nedenle etki görünmemelidir.requiredstd::snprintf

Ancak bu sorun, örneğin derleyici optimizasyonu ile değişebilir: derleyici requireddeğişken için bir kayıt kullanmaya karar verirse ne olur ? Bu, bazen güvenlik sorunlarına neden olan hatalardır.


1
snprintf her zaman sonlandırıcı bir boş bayt ekler, ancak karakter sayısını onsuz döndürür. Bu kod her zaman son karakteri atlamaz mı?
alexk7

@ alexk7, Güzel yakala! Cevabı güncelliyor. Kod son karakteri atlamaz, ancak bytesarabellek sonunun ötesinde , muhtemelen requiredtamsayı üzerine yazar (ki bu noktada neyse ki zaten değerlendirilir).
Dacav

1
Sadece küçük bir ipucu: 0 arabellek boyutu ile nullptr, arabellek bağımsız değişkeni olarak bir char b;kod geçirerek kodunuzdaki satırı ortadan kaldırabilirsiniz . ( Kaynak )
iFreilicht

@ iFreilicht, düzeltildi. Ayrıca +1
Dacav

2
"Char bytes [required]" kullanılması yığın yerine yığına ayrılacak, büyük format dizgileri için tehlikeli olabilir. Bunun yerine yeni bir kullanım kullanmayı düşünün. 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;
}

C99 snprintf ve C ++ 11 kullanma


9

Test Edilmiş, Üretim Kalitesi Cevabı

Bu cevap genel durumu standartlara uygun tekniklerle ele almaktadır. Aynı yaklaşım, sayfalarının alt kısmına yakın olan CppReference.com sitesinde de verilmiştir . Örneklerinden farklı olarak, bu kod sorunun gereksinimlerine uygundur ve robotik ve uydu uygulamalarında sahada test edilmiştir. Ayrıca yorumlarda da iyileşme oldu. Tasarım kalitesi aşağıda daha ayrıntılı tartışılmaktadır.

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

Öngörülebilir Doğrusal Verimlilik

İki geçiş soru özelliklerine göre güvenli, güvenilir ve öngörülebilir bir yeniden kullanılabilir fonksiyon için gerekliliklerdir. Yeniden kullanılabilir bir fonksiyonda değişken boyutlarının dağılımı hakkındaki varsayımlar kötü programlama stilidir ve kaçınılmalıdır. Bu durumda, değişkenlerin keyfi olarak büyük değişken uzunluk gösterimleri algoritma seçiminde anahtar bir faktördür.

Taşma üzerine yeniden denemek katlanarak verimsizdir, bu da C ++ 11 standart komitesi yazma tamponu boş olduğunda kuru bir çalışma sağlamak için yukarıdaki teklifi tartıştığında tartışılan başka bir nedendir.

Yukarıdaki üretime hazır uygulamada, ilk çalışma, tahsis boyutunu belirlemek için böyle bir kuru çalışmadır. Hiçbir ayırma gerçekleşmez. Printf direktiflerinin ayrıştırılması ve değişkenlerin okunması onlarca yıl boyunca son derece verimli hale getirilmiştir. Önemsiz vakalar için küçük bir verimsizliğin feda edilmesi gerekse bile yeniden kullanılabilir kod öngörülebilir olmalıdır.

Güvenlik ve Güvenilirlik

Andrew Koenig, bir Cambridge etkinliğindeki konferansından sonra küçük bir grubumuza, "Kullanıcı işlevleri, olağandışı işlevsellik başarısızlığından faydalanmamalı." Dedi. Her zamanki gibi, bilgeliği o zamandan beri kayıtta doğru olarak gösterildi. Düzeltilen ve kapatılan güvenlik hatası sorunları genellikle düzeltmeden önce kullanılan deliğin açıklamasındaki yeniden deneme saldırılarını gösterir.

Bu, sprintf Alternatifi, C9X Revizyon Teklifi , ISO IEC Belgesi WG14 N645 / X3J11 96-008'deki null tampon özelliği için resmi standartlar revizyon teklifinde belirtilmiştir . Dinamik bellek kullanılabilirliği kısıtlamaları dahilinde "% s" yazdırma yönergesi başına eklenen keyfi olarak uzun bir dize bir istisna değildir ve "Olağanüstü işlevsellik" üretmek için kullanılmamalıdır.

Teklifi, bu cevabın ilk paragrafında bağlantısı verilen C ++ Reference.org sayfasının alt kısmında verilen örnek kodu düşünün.

Ayrıca, başarısızlık vakalarının testi nadiren başarılı vakalar kadar sağlamdır.

taşınabilirlik

Tüm büyük işletim sistemi satıcıları, c ++ 11 standartlarının bir parçası olarak std :: vsnprintf'i tam olarak destekleyen derleyiciler sağlar. Artık dağıtımları sürdürmeyen satıcıların ürünlerini çalıştıran ana makineler, birçok nedenden dolayı g ++ veya clang ++ ile donatılmalıdır.

Yığın Kullanımı

Std :: vsnprintf'e yapılan ilk çağrıda yığın kullanımı, 2. çağrıdan daha az veya ona eşit olacak ve 2. çağrı başlamadan önce serbest bırakılacaktır. İlk çağrı yığın kullanılabilirliğini aşarsa, std :: fprintf de başarısız olur.


Kısa ve sağlam. Uygun olmayan vsnprintf'lere sahip HP-UX, IRIX, Tru64'te başarısız olabilir. EDIT: ayrıca, iki geçişin performansları nasıl etkileyebileceğini göz önünde bulundurarak, özellikle. En yaygın, küçük dizeler biçimlendirmesi için, ilk geçiş için yeterince büyük olabilecek bir tahmin düşündünüz mü?
Mühendis

FWIW, bahsettiğim tahmin, ilk çalışmanın gerçekleştiği yerde bir yığın tahsisli tampon kullanır. Uygunsa, ikinci bir çalışmanın maliyetini ve orada gerçekleşen dinamik ayırmayı kaydeder. Muhtemelen, küçük teller büyük tellerden daha sık kullanılır. Kaba ölçütümde bu strateji (neredeyse) küçük dizelerin çalışma süresini yarıya indiriyor ve yukarıdaki stratejinin birkaç yüzdesi (sabit genel gider?) İçinde. Kuru çalışma, vb. Kullanan C ++ 11 tasarımı üzerinde durur musunuz? Bunu okumak istiyorum.
Mühendis

@Mühendislik uzmanı, soruların cevabın gövdesinde, kodun üstünde ve altında ele alındı. Alt konuların bu şekilde okunması daha kolay hale getirilebilir.
Douglas Daseeco

6

C ++ 20 std::format

Geldi! Bu özellik http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html adresinde açıklanmıştır ve Python benzeri bir .format()sözdizimi kullanır .

Kullanımın şöyle olacağını düşünüyorum:

#include <format>
#include <string>

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

Destek GCC'ye geldiğinde bir deneyeceğim, GCC 9.1.0 g++-9 -std=c++2ahala desteklemiyor.

API yeni bir std::formatbaşlık ekleyecektir :

Önerilen biçimlendirme API'sı yeni başlıkta tanımlanmıştır <format>ve mevcut kod üzerinde hiçbir etkisi olmamalıdır.

Mevcut fmtkütüphane çoklu doldurmaya ihtiyacınız varsa uygulamayı uyguladığını iddia ediyor: https://github.com/fmtlib/fmt

C ++ 20'nin uygulanması std::format.

ve daha önce şu adreste bahsedilmişti: std :: sprintf gibi dize biçimlendirme


5

Erik Aronesty'nin verdiği cevaba dayanarak:

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();
}

Bu const, sonucun .c_str()orijinal cevapta olandan uzaklaşma ihtiyacını ortadan kaldırır .


1
Erik Aronesty'nin cevabını oluşturmak kırmızı bir ringa balığıdır. İlk kod örneği güvensiz ve ikincisi döngü ile verimsiz ve beceriksiz. Temiz uygulama, herhangi bir vprintf işlev ailesinin buf_sizesi sıfır ise, hiçbir şey yazılmaz ve arabellek boş bir işaretçi olabilir, ancak dönüş değeri (yazılmayacak bayt sayısı dahil değildir) null sonlandırıcı) hala hesaplanır ve döndürülür. Bir üretim kalitesi yanıtı burada: stackoverflow.com/questions/2342162/…
Douglas Daseeco

Erik Aronesty'nin cevabı, mayın eklendiğinden beri düzenlendi. Ben dizeleri inşa gibi saklamak için vektör <char> kullanma seçeneğini vurgulamak istedim. Bu işlevi sık sık C ++ kodundan C işlevleri çağırırken kullanın. Sorunun şimdi 34 cevabı olması ilginçtir.
ChetS

Vfprintf sayfasındaki cppreference.com örneği daha sonra eklendi. En iyi cevap şu anda kabul edilen cevap, bir printf varyantı yerine dize akışları kullanarak şeylerin C ++ yolu olduğuna inanıyorum. Ancak cevabım verildiğinde değer kattı; O zamanlar diğer cevaplardan aşamalı olarak daha iyiydi. Artık standardın string_view, parametre paketleri ve Variadic şablonu var, yeni bir cevap bu özellikleri içerebilir. Cevabım gelince, artık ek yukarı oyları hak etmese de, silinmeyi veya aşağı oyu hak etmiyor, bu yüzden onu bırakıyorum.
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
Akıllı fikir için +1, ancak ne _vscprintfolduğu çok açık değil . Bence bu cevabı detaylandırmalısınız.
Dacav

3

string ihtiyacınız olan şeye sahip değil, ancak std :: stringstream. Dizeyi oluşturmak için bir dize akışı kullanın ve sonra dizeyi çıkarın. İşte yapabileceğiniz şeylerin kapsamlı bir listesi. Örneğin:

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

bir çift veya float yazdırırken size 10 ondalık hassasiyet yeri verecektir.


8
Bu hala size kontrol baskı yakın bir şey vermez printf verir ... ama güzel.
Erik Aronesty

3

Bunu deneyebilirsiniz:

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

Asprintf (3) olan bir sistemdeyseniz , kolayca sarabilirsiniz:

#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
Ben formatargüman türlerini kontrol etmek ve -Wall ile iyi bir uyarı vermek gcc söyler gibi, daha önce bir açıklama olarak bu satırı eklemek istiyorum :std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid

2
Az önce bir arama ekledim va_end. "va_end, va_start veya va_copy işlevini çağıran bir işlevden önce çağrılmazsa, davranış tanımsızdır." - dokümanlar
Aaron McDaid

1
Vasprintf'in dönüş sonucunu, işaretçi değeri hata durumunda tanımsız olduğundan kontrol etmelisiniz. Yani, muhtemelen <new> ekleyin ve şunu ekleyin: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

İyi bir nokta, cevabını buna göre değiştirdim, throw std::bad_alloc();kod tabanımda C ++ istisnalarını kullanmıyorum ve bunu yapan insanlar için kolayca ekleyebilirler. kaynak yorum ve yorumunuzu burada bulabilirsiniz.
Thomas Perl

2

Bu benim programda bunu yapmak için kullandığım kod ... Bu fantezi bir şey, ama hile yapar ... Not, boyutunuzu uygulanabilir olarak ayarlamak zorunda kalacak. Benim için MAX_BUFFER 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
TextString öğesinin başlatılması, tüm arabelleği zaten sıfıra ayarlar.
Memset'e

Bu, verilerin tam bir kopyasını yapar, kullanmak mümkündür vsnprintf doğrudan dizeye .
Mooing Duck

2

Bu fikri Dacav ve pixelpoint'in cevabından aldı . Biraz oynadım ve anladım:

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

İle aklı başında programlama pratikte ancak ben hala hala yeterince basittir ve C ++ 11 gerektirmediğine daha güvenli alternatiflere açığım, kod yeterli olması gerektiğine inanıyoruz.


Ve burada vsnprintf(), ilk arabellek zaten yeterli olduğunda ikinci aramayı önlemek için bir ilk arabellek kullanan başka bir sürüm .

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);
}

(Bu sürümün sadece Piti Ongmongkolkul'un cevabına benzediği , sadece kullanmadığı newve delete[]oluştururken bir boyut belirttiği ortaya çıkıyor std::string.

Burada kullanılmaması newve delete[]yığının yığın üzerinde kullanımını ima etmesi fikri , tahsis ve yeniden yerleştirme işlevlerini çağırması gerekmediği için, ancak düzgün kullanılmazsa, bazı taşmalarda (belki de eski veya belki sadece savunmasız) sistemler. Bu bir endişe ise, newvedelete[] yerine . Buradaki tek endişenin vsnprintf()zaten limitlerle çağrıldığı gibi tahsislerle ilgili olduğunu unutmayın , bu nedenle ikinci tamponda tahsis edilen boyuta dayalı bir limit belirtmek de bunları önleyecektir.)


2

Genellikle bunu kullanıyorum:

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

Dezavantajı: tüm sistemler vazpini desteklemez


vasprintf güzel - ancak dönüş kodunu kontrol etmeniz gerekiyor. -1'de arabellek tanımsız bir değere sahip olacaktır. Gereksinim: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

2

@ İFreilicht cevabının biraz değiştirilmiş versiyonunun altında, C ++ 14 ( make_uniqueham beyan yerine fonksiyon kullanımı ) ve std::stringargümanlar için destek eklendi (Kenny Kerr makalesine dayanarak )

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

Çıktı:

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

İsterseniz bu cevabı orijinaliyle birleştirmekten çekinmeyin.



1

Cio çıktısını iomanip başlık dosyasını kullanarak cout'ta biçimlendirebilirsiniz. Setprecision, setfill vb. Yardımcı işlevlerden herhangi birini kullanmadan önce iomanip başlık dosyasını eklediğinizden emin olun.

Burada, geçmişte "birikmiş" olduğum vektörde ortalama bekleme süresini yazdırmak için kullandığım bir kod snippet'i var.

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

İşte C ++ akışlarını nasıl biçimlendirebileceğimize dair kısa bir açıklama. http://www.cprogramming.com/tutorial/iomanip.html


1

Arabellek dizgiyi yazdıracak kadar büyük değilse, sorunlar olabilir. Orada biçimlendirilmiş bir ileti yazdırmadan önce biçimlendirilmiş dizenin uzunluğunu belirlemelisiniz. Buna kendi yardımcısını yapıyorum (Windows ve Linux GCC'de test edildi ) ve kullanmayı deneyebilirsiniz.

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();

Satır ile ilgili olarak vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- Dizenin arabelleğinin biten bir boş karakter için yer olduğunu varsaymak güvenli mi? Boyut + 1 karakter ayırmayan uygulamalar var mı? Daha güvenli olur mudst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

Görünüşe göre önceki yorumuma cevap: Hayır boş bir karakter olduğunu varsaymak güvenli DEĞİLDİR. Özellikle C ++ 98 spesifikasyonu ile ilgili olarak: "data () + size () 'deki değere erişim tanımsız davranış üretir : null karakterin, bu fonksiyon tarafından döndürülen değerin işaret ettiği karakter sırasını sonlandırdığının garantisi yoktur . :: c_str, böyle bir garanti sağlayan bir işlev için. Bir program, bu dizideki karakterlerin hiçbirini değiştirmeyecektir. "Ancak, C ++ 11 özellikleri bunu belirtir datave c_streş anlamlılardır.
drwatsoncode

1
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();

1

Çok çok basit bir çözüm.

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

1

Bunun birçok kez cevaplandığını anlıyorum, ama bu daha özlü:

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());
}

misal:

#include <iostream>
#include <random>

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

Ayrıca bkz http://rextester.com/NJB14150


1

GÜNCELLEME 1 : eklenen fmt::formattestler

Burada tanıtılan yöntemler hakkında kendi araştırmamı yaptım ve burada belirtilenlere göre taban tabana zıt sonuçlar elde ediyorum.

4 yöntem üzerinde 4 işlev kullandım:

  • değişken işlev + vsnprintf+std::unique_ptr
  • değişken işlev + vsnprintf+std::string
  • değişken şablon işlevi + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfmtkütüphaneden fonksiyon

Test arka ucu için googletestkullanılmıştır.

#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();
}

for_eachUygulama buradan alınır: iterate başlığın üzerinde

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

}

Testler:

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);
    }
}

UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

unorted.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;
    }

}

SONUÇLAR :

[ 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)

Gördüğünüz gibi vsnprintf+ std::stringile uygulama eşittir fmt::format, ancak vsnprintf+ ile std::unique_ptrolandan daha hızlıdır std::ostringstream.

Testler derlendi Visual Studio 2015 Update 3ve adresinde gerçekleştirildi Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.