2013-02-27 61 views
17

我正在學習C++。 coutstd::ostream類的一個實例。 如何使用它打印格式化的字符串?如何將C++ std :: ostream與printf-like格式一起使用?

我仍然可以使用printf,但我想了解更多C++風格的方法,它可以取得所有C++的好處。我認爲這應該可以與std::ostream,但我找不到正確的方法。

+1

這個頁面是不是一個全面的指南流格式化[** Output Formatting **](http://arachnoid.com/cpptutor/student3.html) – 2013-02-27 07:10:35

+1

您不應該' 「我的寶寶的年齡是」<< 3 << endl;'而不是'printf(「我的寶寶的年齡是%u \ n」,3);' – Daniel 2013-02-27 07:11:56

+3

' cout <<「我的寶寶的年齡是」<< 3 endl;'不是可本地化的;在非英語語言中,您可能會有不同的詞語順序。所以,這種語法不適用於多語言應用程序。強制使用這種語法的唯一方法是使'switch'取決於語言ID,這樣的解決方案很難看。 'printf'好得多,因爲翻譯者可以翻譯整個格式的字符串,並改變每個不常見語言的源代碼修改的詞序。 – Vitaliy 2015-12-29 11:43:42

回答

22

你可以用std::ostream唯一要做的是直接著名<< -syntax:

int i = 0; 
std::cout << "this is a number: " << i; 

而且有各種IO manipulators可以用來影響格式化,位數等整數,浮點數等。

但是,這與格式化的字符串printf不一樣。 C++ 11不包含任何允許您使用字符串格式的工具,與printf(除了printf本身除外,您可以在C++中使用時除外)相同。

在提供printf式的功能的庫而言,有boost::format,這使得代碼,如下所示(從簡介複製):

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

還要注意,有printf風格的proposal for inclusion在未來版本的標準中格式化。如果被接受,語法如下面可能會提供:

std::cout << std::putf("this is a number: %d\n",i); 
+4

+1爲boost :: format方式 – oDDsKooL 2013-02-27 07:22:13

+0

字符串生成如何?沒有辦法使字符串對象格式化? – Eonil 2013-02-27 07:29:15

+2

@Eonil是的。首先,您可以像上面的'std :: cout'一樣使用['std :: ostringstream'](http://en.cppreference.com/w/cpp/io/basic_stringstream)對象。當你用內容填充它時(使用'<< -operator),你可以使用它的'.str()'函數來獲得格式化的字符串。無論如何'boost :: format'都會返回一個字符串。我沒有在答案中包括這個,因爲你的問題是關於'std :: cout'的具體問題。 – jogojapan 2013-02-27 07:31:09

0

當我需要COUT兩者的安全性,並printf()的簡單變量的快速和容易的格式,我將兩者混合是這樣的。這是一個醜陋的修復,但是當我需要一些更復雜的實體一起得到的東西爲我做的輸出,比如像「2014年2月7日上午10:05」:

#include <stdio> 
#include <stdarg> 
#include <stdlib> 
#include <iostream> 

#pragma hdrstop 

using namespace std; 


char* print(char* fmt, ...) 
{ 
    static char buffer[80] = ""; 

    va_list argptr; 
    va_start(argptr,fmt); 

    vsprintf(buffer, fmt, argptr); 

    va_end(argptr); 

    return buffer; 
} 

#pragma argsused 
int main(int argc, char* argv[]) 
{ 

cout << print("\n%06d\n%6d\n%6d\n%010.3f",1,12,123,123.456); 

system("PAUSE>NUL"); 

return 0; 

} 
+0

至少使用'vsnprintf'來避免最明顯的錯誤:緩衝區溢出。 – 2016-03-20 19:03:33

+0

本地靜態你最終會遇到線程安全問題 – UVV 2016-07-25 13:18:21

-1

字段寬度

設置字段寬度非常簡單。對於每個變量,只需在它前面加上「setw(n)」即可。像這樣:

#include <iostream> 
#include <iomanip> 

using namespace std; 

int main() 
{ 
    const int max = 12; 
    const int width = 6; 
    for(int row = 1;row <= max;row++) { 
     for(int col = 1;col <= max;col++) { 
      cout << setw(width) << row * col; 
     } 
     cout << endl; 
    } 
    return 0; 
} 

通知如何「運輸及工務局局長(n)的」控制字段寬度,所以每個數是 打印在保持相同的寬度無論 寬度的數目本身的的領域內。

- 來自P.Lutus的「Programming/C++ tutorial」。

1

要實現的printf人們可以使用C++ 11個模板參數:

#include <iostream> 
#include <string> 

inline std::ostream & mprintf(std::ostream & ostr, const char * fstr) throw() 
{ 
    return ostr << fstr; 
} 

template<typename T, typename... Args> 
std::ostream & mprintf(std::ostream & ostr, 
     const char * fstr, const T & x) throw() 
{ 
    size_t i=0; 
    char c = fstr[0]; 

    while (c != '%') 
    { 
     if(c == 0) return ostr; // string is finished 
     ostr << c; 
     c = fstr[++i]; 
    }; 
    c = fstr[++i]; 
    ostr << x; 

    if(c==0) return ostr; // 

    // print the rest of the stirng 
    ostr << &fstr[++i]; 
    return ostr; 
} 


template<typename T, typename... Args> 
std::ostream & mprintf(std::ostream & ostr, 
     const char * fstr, const T & x, Args... args) throw() 
{ 
    size_t i=0; 
    char c = fstr[0]; 

    while (c != '%') 
    { 
     if(c == 0) return ostr; // string is finished 
     ostr << c; 
     c = fstr[++i]; 
    }; 
    c = fstr[++i]; 
    ostr << x; 

    if(c==0) return ostr; // string is finished 

    return mprintf(ostr, &fstr[++i], args...); 
} 

int main() 
{ 
    int c = 50*6; 
    double a = 34./67.; 
    std::string q = "Hello!"; 

    // put only two arguments 
    // the symbol after % does not matter at all 
    mprintf(std::cout, "%f + %f = %a \n", c, a); 

    // print string object: for real printf one should write q.c_str() 
    mprintf(std::cout, "message: \"%s\". \n", q); 

    // the last argument will be ignored 
    mprintf(std::cout, "%z + %f\n", (long)a, 12, 544); 

} 

輸出

300 + 2 = %a 
message: "Hello!". 
2 + 12 

這一個非常簡單的代碼並將它可以得到改善。

1)的優點是,它使用< <打印物體流,所以你可以把任意參數,可以輸出通過< <。

2)它忽略了格式的字符串中的參數的類型:後%可以站在任意的符號甚至空格。輸出流決定如何打印相應的對象。它也與printf兼容。

3)的缺點在於,它不能打印百分比符號「%」,一個需要稍微改進的代碼。

4)不能打印格式的數字,如%4.5F

5)如果參數的數目小於由格式化串預測,則該函數只打印字符串的其餘部分。

6)如果參數的數目大於由格式化串預測,則保持的參數被忽略

一種可以提高代碼,使2)-6)到完全模仿printf的行爲。 但是,如果你遵循printf的規則,那麼只有3)和4)基本上需要修復。

0

輸出示例:

2017-12-20T16:24:47,604144+01:00 Hello, World! 

代碼(在put_timestamp證明put_printf使用):

#include <assert.h> 
#include <chrono> 
#include <iomanip> 
#include <iostream> 

class put_printf { 
    static constexpr size_t failed = std::numeric_limits<size_t>::max(); // for any explicit error handling 
    size_t stream_size; // excluding '\0'; on error set to 0 or to "failed" 
    char buf_stack[2048+1]; // MAY be any size that fits on the stack (even 0), SHOULD be (just) large enough for most uses (including '\0') 
    std::unique_ptr<char[]> buf_heap; // only used if the output doesn't fit in buf_stack 
public: 
    explicit put_printf(const char *format, ...) 
      #if __GNUC__ 
      __attribute__ ((format (printf, 2, 3))) // most compelling reason for not using a variadic template; parameter 1 is implied "this" 
      #endif 
      { 
     va_list args; 
     va_start(args, format); 
     const int res = vsnprintf(buf_stack, sizeof(buf_stack), format, args); 
     va_end(args); 
     if (res < 0) { // easily provoked, e.g., with "%02147483646i\n", i.e., more than INT_MAX-1 significant characters (only observed, no guarantee seen) 
      stream_size = failed; 
     } else if (res < sizeof(buf_stack)) { // preferred path 
      stream_size = res; 
     } else { // not artificially constrained 
      try { 
       const size_t buf_size = static_cast<size_t>(res) + 1; // avoids relying on "res < INT_MAX" (only observed, no guarantee seen) 
       buf_heap.reset(new char[buf_size]); // observed to work even beyond INT_MAX=2^32-1 bytes 
       va_start(args, format); 
       if (vsnprintf(buf_heap.get(), buf_size, format, args) == res) stream_size = res; 
       else stream_size = failed; // can't happen 
       va_end(args); 
      } catch (const std::bad_alloc&) { // insufficient free heap space (or an environment-specific constraint?) 
       stream_size = failed; 
      } 
     } 
    } 
    friend std::ostream& operator<<(std::ostream& os, const put_printf& self) { 
     if (self.stream_size == failed) { 
      // (placeholder for any explicit error handling) 
      return os; 
     } else { 
      // using write() rather than operator<<() to avoid a separate scan for '\0' or unintentional truncation at any internal '\0' character 
      return os.write((self.buf_heap ? self.buf_heap.get() : self.buf_stack), self.stream_size); 
     } 
    } 
}; 

class put_timestamp { 
    const bool basic = false; 
    const bool local = true; 
public: 
    friend std::ostream& operator<<(std::ostream& os, const put_timestamp& self) { 
     const auto now = std::chrono::system_clock::now(); 
     const std::time_t now_time_t = std::chrono::system_clock::to_time_t(now); 
     struct tm tm; if ((self.local ? localtime_r(&now_time_t, &tm) : gmtime_r(&now_time_t, &tm)) == nullptr) return os; // TODO: explicit error handling? 
     static_assert(4 <= sizeof(int), ""); 
     const int microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch() % std::chrono::seconds(1)).count(); 
     assert(0 <= microseconds && microseconds < 1000000); // TODO: (how) do we know? 
     // TODO: doesn't "point" in "decimal_point()" imply "dot"/"full stop"/"period", unlike an obviously neutral term like "mark"/"separator"/"sign"? 
     const char decimal_sign = std::use_facet<std::numpunct<char>>(os.getloc()).decimal_point() == '.' ? '.' : ','; // full stop accepted, comma preferred 
     // TODO: all well and good for a locale-specific decimal sign, but couldn't the locale also upset microseconds formatting by grouping digits? 
     os << std::put_time(&tm, self.basic ? "%Y%m%dT%H%M%S" : "%FT%T") << put_printf("%c%06i", decimal_sign, microseconds); 
     if (! self.local) return os << "Z"; 
     const int tz_minutes = std::abs(static_cast<int>(tm.tm_gmtoff))/60; 
     return os << put_printf(self.basic ? "%c%02i%02i" : "%c%02i:%02i", 0 <= tm.tm_gmtoff ? '+' : '-', tz_minutes/60, tz_minutes % 60); 
    } 
}; 

int main() { 
    // testing decimal sign 
    ///std::cout.imbue(std::locale("en_GB")); 
    ///std::cout.imbue(std::locale("fr_FR")); 

    std::cout << put_timestamp() << " Hello, World!\n"; 
    #if 0 
    typedef put_printf pf; // just to demo local abbreviation 
    std::cout << "1: " << pf("%02147483646i\n" , 1 ) << std::endl; // res < 0 
    std::cout << "2: " << pf("%02147483643i%i\n", 1, 100) << std::endl; // res < 0 
    std::cout << "3: " << pf("%02147483643i%i\n", 1, 10) << std::endl; // works 
    std::cout << "4: " << pf("%02147483646i" , 1 ) << std::endl; // works 
    #endif 
    return 0; 
} 

評論關於put_printf:

// Reasons for the name "put_printf" (and not "putf" after all): 
// - put_printf is self-documenting, while using the naming pattern also seen in std::put_time; 
// - it is not clear whether the proposed std::putf would support exactly the same format syntax; 
// - it has a niche purpose, so a longer name is not an objection, and for frequent local uses 
//  it is easy enough to declare an even shorter "typedef put_printf pf;" or so. 
// Evaluation of delegating to vsnprintf() with intermediate buffer: 
// (+) identical result without implementation and/or maintenance issues, 
// (?) succeeds or fails as a whole, no output of successful prefix before point of failure 
// (-) (total output size limited to INT_MAX-1) 
// (-) overhead (TODO: optimal buf_stack size considering cache and VM page locality?) 
// Error handling (an STL design problem?): 
// - std::cout.setstate(std::ios_base::failbit) discards further std::cout output (stdout still works), 
//  so, to be aware of an error in business logic yet keep on trucking in diagnostics, 
//  should there be separate classes, or a possibility to plug in an error handler, or what? 
// - should the basic or default error handling print a diagnostic message? throw an exception? 
// TODO: could a function "int ostream_printf(std::ostream& os, const char *format, ...)" 
//   first try to write directly into os.rdbuf() before using buf_stack and buf_heap, 
//   and would that significantly improve performance or not? 
相關問題