C++生成格式化的標(biāo)準(zhǔn)字符串

兩種格式化字符串方法

眾所周知,C++的std::string功能殘缺,各種功能都沒有,比如格式化字符串功能。
在python3中,支持兩種格式化字符串的方法,一種是C風(fēng)格,格式化的部分用%開頭,%后面的對應(yīng)具體類型(比如%s對應(yīng)字符串%d對應(yīng)整型),另一種則是類型無關(guān)的風(fēng)格,{0}對應(yīng)第1個(gè)參數(shù),{1}對應(yīng)第2個(gè)參數(shù)。

>>> "{0}'s age is {1}".format("赤紅", 11)
"赤紅's age is 11"
>>> "%s's age is %d" % ("赤紅", 11)
"赤紅's age is 11"

而在C++中則只能借用C函數(shù),用snprintf來格式化一片緩沖區(qū)

#define BUFFSIZE 512
    char buf[BUFFSIZE];
    snprintf(buf, BUFFSIZE, "%s's age is %d\n", "赤紅", 11);

亦或者用類型無關(guān)的流運(yùn)算符

    std::ostringstream os;
    os << "赤紅" << "'s age is " << 11 << "\n";
    std::string s = os.str();

暫且不談效率問題,這種用<<拼接多個(gè)不同類型對象的做法代碼量較大,而且在控制具體輸出格式時(shí)更為麻煩,比如控制數(shù)字所占位數(shù),或者小數(shù)點(diǎn)后位數(shù)。至少繁雜得讓我總是記不起來,寧可使用C風(fēng)格snprintf來控制。比如

    double d = 3.1415926;
    snprintf(buf, BUFFSIZE, "圓周率: %-8.3lf是祖沖之發(fā)現(xiàn)的\n", d);
$ ./a.out 
圓周率: 3.142   是祖沖之發(fā)現(xiàn)的

通過%-8.3lf將lf(long float即double)類型的浮點(diǎn)數(shù)設(shè)置占位數(shù)為8,設(shè)置小數(shù)點(diǎn)后位數(shù)為3,負(fù)號(hào)表示左對齊,這種表示方法非常簡單緊湊。
至于用C++的iomanip頭文件實(shí)現(xiàn),我還花了點(diǎn)時(shí)間查文檔。

    double d = 3.1415926;
    os << "圓周率: " << std::setw(8) << std::fixed
       << std::setprecision(3) << std::left
       << d << "是祖沖之發(fā)現(xiàn)的\n";

除了代碼如此之長以及有可能漏掉std::fixed外,還有問題在于setprecision已經(jīng)改變了默認(rèn)設(shè)置,也就是說,如果再os <<傳入一個(gè)浮點(diǎn)數(shù),保留的小數(shù)點(diǎn)位數(shù)仍然是3位。
也許有人說,這種好處在于setprecision和setw接收的可以是一個(gè)變量而非常量。實(shí)際上snprintf一樣可以做到。

    double d = 3.1415926;
    int n1 = 8, n2 = 3;
    snprintf(buf, BUFFSIZE, "圓周率: %-*.*lf是祖沖之發(fā)現(xiàn)的\n", n1, n2, d);

C++包裝snprintf生成格式化的std::string對象

APUE UNP TLPI這幾本講Linux下C編程的書中,都自己寫了錯(cuò)誤處理庫來包裝snprintf產(chǎn)生格式化的輸出,以免每次重復(fù)定義緩沖區(qū)/調(diào)用snprintf等等。
這樣的做法有個(gè)缺陷就是緩沖區(qū)(字符數(shù)組)長度有限制,當(dāng)然一般而言buffer size定義得足夠大的話是足夠的,畢竟打印太長的格式化字符串不如多調(diào)用幾次函數(shù)。
另一方面,由于這些函數(shù)僅僅是打印信息,尤其是經(jīng)常打印信息后直接退出程序。所以不會(huì)返回錯(cuò)誤字符串。如果在C++中想要把錯(cuò)誤信息作為異常傳給上一層處理,這些函數(shù)是不夠的。因此需要簡單修改下。

inline std::string format_string(const char* format, va_list args) {
    constexpr size_t oldlen = BUFSIZ;
    char buffer[oldlen];  // 默認(rèn)棧上的緩沖區(qū)
    va_list argscopy;
    va_copy(argscopy, args);
    size_t newlen = vsnprintf(&buffer[0], oldlen, format, args) + 1;
    newlen++;  // 算上終止符'\0'
    if (newlen > oldlen) {  // 默認(rèn)緩沖區(qū)不夠大,從堆上分配
        std::vector<char> newbuffer(newlen);
        vsnprintf(newbuffer.data(), newlen, format, argscopy);
        return newbuffer.data();
    }
    return buffer;
}

inline std::string format_string(const char* format, ...) {
    va_list args;
    va_start(args, format);
    auto s = format_string(format, args);
    va_end(args);

    return s;
}

這是模仿UNP的實(shí)現(xiàn),定義形參為va_list和...的兩個(gè)版本,其中接受va_list的版本還可為其它函數(shù)所用。因?yàn)镃風(fēng)格的可變參數(shù)列表...不能作為參數(shù)傳遞。另一點(diǎn),va_list類型也不一定有拷貝構(gòu)造函數(shù),因此得用va_copy來拷貝一份va_list,以供第二次使用。
C++11新增了可變模板參數(shù)特性,使得上述代碼可以得到簡化

template <typename ...Args>
inline std::string format_string(const char* format, Args... args) {
    constexpr size_t oldlen = BUFSIZ;
    char buffer[oldlen];  // 默認(rèn)棧上的緩沖區(qū)

    size_t newlen = snprintf(&buffer[0], oldlen, format, args...);
    newlen++;  // 算上終止符'\0'

    if (newlen > oldlen) {  // 默認(rèn)緩沖區(qū)不夠大,從堆上分配
        std::vector<char> newbuffer(newlen);
        snprintf(newbuffer.data(), newlen, format, args...);
        return std::string(newbuffer.data());
    }

    return buffer;
}

而傳遞可變模板參數(shù)也變得十分容易(使用forward完美轉(zhuǎn)發(fā)),示例代碼如下

xyz@ubuntu:~/unp_practice/lib$ cat test.cc 
#include <string.h>
#include <unistd.h>
#include "format_string.h"

template <typename ...Args>
void errExit(const char* format, Args... args) {
    auto errmsg = format_string(format, std::forward<Args>(args)...);
    errmsg = errmsg + ": " + strerror(errno) + "\n";
    fputs(errmsg.c_str(), stderr);
    exit(1);
}

int main() {
    const char* s = "hello world!";
    int fd = -1;
    if (write(fd, s, strlen(s)) == -1)
        errExit("write \"%s\" to file descriptor(%d) failed", s, fd);
    return 0;
}
xyz@ubuntu:~/unp_practice/lib$ g++ test.cc -std=c++11
xyz@ubuntu:~/unp_practice/lib$ ./a.out 
write "hello world!" to file descriptor(-1) failed: Bad file descriptor
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 最先接觸編程的知識(shí)是在大學(xué)里面,大學(xué)里面學(xué)了一些基礎(chǔ)的知識(shí),c語言,java語言,單片機(jī)的匯編語言等;大學(xué)畢...
    oceanfive閱讀 3,403評(píng)論 0 7
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,691評(píng)論 0 4
  • 耀眼的花,撲鼻的香,從花間走過就像從夢里走過。我靠得很近,近處盡是花;我走得很遠(yuǎn),遠(yuǎn)處還是花。走一次,戀一...
    冰夫閱讀 233評(píng)論 0 0
  • 流浪到香格里拉,覺定待在這里等下雪??
    srit大香蕉閱讀 267評(píng)論 1 1
  • 知天之長,而我所歷之短;知地之大,而我所居之小。安身守命,不可爭。消極的等待,或是積極的守候。唯鑒古觀今,以待天時(shí)。
    field筆閱讀 410評(píng)論 0 0

友情鏈接更多精彩內(nèi)容