欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++ 的 format 和 vformat 函數(shù)示例詳解

 更新時間:2025年02月10日 09:36:53   作者:王曉華-吹泡泡的小貓  
傳統(tǒng)C庫的printf系列函數(shù)存在安全問題,而C++推薦的基于流格式化輸入輸出雖然解決了安全性問題,但在易用性方面仍顯不足,C++11引入了新的C風格字符串格式化函數(shù),但類型安全問題依舊存在,下面通過本文介紹C++ 的 format 和 vformat 函數(shù)示例,感興趣的朋友一起看看吧

1 C++ 字符串格式化的困境

1.1 “冗長的裹腳布”

? 傳統(tǒng) C 庫的 printf() 系列函數(shù)的優(yōu)點就是函數(shù)調(diào)用更自然,并且格式化信息與參數(shù)分離,代碼結(jié)構(gòu)清晰,但是因為安全性問題一直被詬病。C++ 更推薦基于流的格式化輸入輸出代替?zhèn)鹘y(tǒng) C 庫的 printf() 系列函數(shù),雖然解決了 printf() 系列函數(shù)的安全性問題,但是在易用性方面也是有點反人類。先不說輸入和輸出,單就說字符串的格式化,對 C++ 的使用者來說簡直是“一把辛酸淚”。舉個簡單的例子,把浮點數(shù)格式化字符串,小數(shù)點后保留 3 位小數(shù),用 sprintf() 實現(xiàn)非常簡單:

char buf[64];
sprintf(buf, "%.3f", 1.0/3.0);  //buf 內(nèi)是 0.333

如果用 C++,就是這樣一番風格:

std::stringstream ss;
ss << std::fixed << std::setw(5) << std::setprecision(3) << 1.0/3.0;
std::string str = ss.str();  // str 是 0.333

? 不得不說,C++ 的這個風格真的很“學院派”,各種控制 IO 流的操作符如果用來出題考試,定能讓學渣們生不如死。這個例子只是格式化一個浮點數(shù),如果要將多個不同類型的數(shù)據(jù)格式化到一個字符串中,需要多少個控制符拼接?像裹腳布,冗長且不直觀。大多數(shù) C++ 程序員對于格式化字符串不得不繼續(xù)用 sprintf(),但是 sprintf() 除了安全性問題,還存在類型支持不足的問題。它只支持幾種內(nèi)置類型,不支持標準庫中的各種容器,更不用說用戶自定義的類型了。

1.2 C++ 11 的小革新

? C++ 11 提供了一個新的 C 風格字符串格式化函數(shù):

int snprintf(char* buffer, std::size_t buf_size, const char* format, ...);

除了 buf_size 參數(shù)有助于防止 buffer 溢出的好處之外,這個函數(shù)還可以計算對指定的參數(shù)進行文本格式化后需要的存儲空間:

const char *fmt = "sqrt(2) = %f";
int sz = std::snprintf(nullptr, 0, fmt, std::sqrt(2));
std::vector<char> buf(sz + 1); // note +1 for null terminator
std::snprintf(&buf[0], buf.size(), fmt, std::sqrt(2));

借助于 C++ 11 的函數(shù)參數(shù)包(關于參數(shù)包可參考《C++ 的 “…”與可變參數(shù)列表》一篇),可以實現(xiàn)一個具有 C++ 風格的文本格式化函數(shù)。在 stackoverflow 網(wǎng)站,有人給出了這樣一個解決方案:

template<typename ... Args>
std::string string_format(const std::string& format, Args ... args) {
    int size_s = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra space for '\0'
    if (size_s <= 0) { 
        throw std::runtime_error("Error during formatting."); 
    }
    auto size = static_cast<size_t>(size_s);
    std::unique_ptr<char[]> buf(new char[size]);
    std::snprintf(buf.get(), size, format.c_str(), args ...);
    return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}
//using string_format()
std::string s = string_format("%s is %d years old.", "Emma", 5);

雖然曲折,但是在你的編譯器升級到 C++ 20 之前,還可一用。不過需要注意,雖然這個函數(shù)解決了溢出問題,但是類型安全問題仍然存在。

2 C++ 20 的 format 函數(shù)

2.1 format 函數(shù)

? 雖然 boost 中的 format 庫存在很長時間了,但是不知道是這個庫的效率問題還是其他原因,一直沒有入 C++ 標準委員會的法眼。很多 C++ 程序員對 Python 的 format() 函數(shù)“垂涎三尺”,fmtlib 庫的出現(xiàn)終于緩解了這種渴望。更好的消息是 fmtlib 的一部分已經(jīng)進入了 C++ 20 標準,比如 format() 函數(shù)和 vformat() 函數(shù)。來看幾個 format() 函數(shù)的使用例子:

std::string name("Bob");
auto result = std::format("Hello {}!", name);  // Hello Bob!
//03:15:30
result = std::format("Full Time Format: {:%H:%M:%S}\n", 3h + 15min + 30s);
//***3.14159
std::print("{:*>10.5f}", std::numbers::pi);

是不是很像 Python?

2.2 fmt 標準格式

? fmt 參數(shù)表示字符串格式化的格式,類型是 std::format_string(提案中最初版本是 std::string_view,后來是 std::format_string,與 C++ 23 補充的 std::print() 函數(shù)一致)。顯然,這個“格式化字符串”是有格式的,一般來說,除了 “{” 和 “}” 兩個特殊字符外,其他的字符都會原樣復制到輸出結(jié)果中。“{” 和 “}” 是表示格式的特殊符號,如果確實需要輸出 “{” 和 “}”,就需要用轉(zhuǎn)義序列 “{{” 和 “}}”代替。實際上一對 “{” 和 “}” 符號組成了一個占位符,這個占位符的語法描述是:

其中 arg-id 和 format-spec 都是可選的,也就是說,一對空的大括號 “{}” 也是合法的格式字符串:

auto result = std::format("{} is {} years old.", "Kitty", 5);  //Kitty is 5 years old.

在這種情況下,每一對大括號與 args 表示的參數(shù)列表中的參數(shù)按順序一一對應。如果 args 參數(shù)列表中的參數(shù)個數(shù)比 fmt 中的格式化占位符多,則不匹配的參數(shù)會被忽略,但不會報錯。反過來,如果 args 參數(shù)列表中的參數(shù)個數(shù)比 fmt 中的格式化占位符少,則需要注意編譯器的行為。資料 [9] 的 P2216 提案已經(jīng)成為 C++ 23 的內(nèi)容,所以 C++ 23 版本的編譯器,會報編譯錯誤。對于 C++ 20 版本的編譯器,則要看它對這個提案的支持情況。還不支持 P2216 的編譯器不會報編譯錯誤,但是代碼會在運行時拋出 format_error 異常。

auto result = std::format("{} is {} years old.", "Kitty", 5, 43.67);  //運行正常,43.67 被忽略
auto result = std::format("{} is {} years old.", "Kitty");  //取決于編譯器

2.2.1 實參(占位符)索引(arg-id)

? 如果格式化字符串中需要強調(diào)占位符與參數(shù)的位置關系,則需要指定 arg-id 參數(shù)。arg-id 用于指定占位符代表的格式化值在參數(shù)列表 args 中的下標。比如:

std::string dogs{ "dogs" };
std::string emma{ "Emma" };
auto result = std::format("{0} loves {1}, but {1} don't love {0}.", emma, dogs);
//Emma loves dogs, but dogs don't love Emma.

需要注意,實參索引要么都不使用,要么就全部指定,格式化字符串不支持部分使用索引的情況,比如這樣的代碼是錯誤的:

auto s = std::format("{1} is a good {}, but Dos is {0} !\n", 6.22, apple); //error

2.2.2 格式說明(format-spec)

? 格式說明位于冒號的右邊,它的語法形式是:

fill-and-align(optional) sign(optional) #(optional) 0(optional) width(optional) precision(optional) L(optional) type(optional) 

看起來稍顯復雜,但是無非就是填充、對齊、符號、域?qū)挕⒕鹊葍?nèi)容。首先看看填充和對齊(fill-and-align),填充字符可以是除了 “{” 和 “}” 之外的任意字符,緊跟在后面的就是對齊標志。對齊標志也是一個字符,用 “<” 表示強制左對齊,用 “>” 表示強制右對齊,對齊標志有三種,用 “^” 表示居中對齊,會在值的前面插入 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor ⌊2n?⌋ 個填充字符,在值的后面插入 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil ⌈2n?⌉ 個填充字符(注意取整方向)。

auto str = std::format("{:*<7}", 42); // str 的值為 "42*****"
auto str = std::format("{:*>7}", 42); // str 的值為 "*****42"
auto str = std::format("{:*^7}", 42); // str 的值為 "**42***"

如果不指定填充和對齊控制字符,則用默認的填充和對齊控制。默認的填充字符是空格,對于數(shù)字類型,默認的對齊控制是右對齊,對于字符串,默認的對齊控制是左對齊。

? sign、#0 用于數(shù)字的格式表達(都是可選),其中 sign 表示數(shù)字的符號,用 “+” 表示在非負數(shù)前面添加一個+號,用 “-” 表示在一個負數(shù)前添加一個負號,空格 “ ” 表示在非負數(shù)前面添加一個空格字符,在負數(shù)前面添加一個負號。關于符號需要注意兩點:首先添加負號對于負數(shù)是默認行為,也就是說,即使不指定 sign 標志,輸出負數(shù)時也會加一個負號。其次,如果數(shù)值是非負值,即使指定了 “-” 符號標志,也會被忽略,同樣,對于負數(shù),即使指定了 “+” 符號標志,輸出時也會用負號代替。

auto s0 = std::format("{0:},{0:+},{0:-},{0: }", 1);   //"1,+1,1, 1"
auto s1 = std::format("{0:},{0:+},{0:-},{0: }", -1);  //"-1,-1,-1,-1"

? # 用于轉(zhuǎn)換輸出數(shù)據(jù)的可替換形式,對于整數(shù)類型,除了默認的十進制形式,還可以用二進制、八進制和十六進制的形式表示數(shù)值??梢灾付?type 控制符與 # 配合的方式指定數(shù)值的表示形式,比如 type 控制字符 “d” 表示十進制,這也是整數(shù)輸出的默認形式,type 控制字符 “b” 表示二進制,會在數(shù)值前插入“0b” 兩個字符。type 控制“o” 表示八進制,會在數(shù)值前插入一個字符 “0”。type 控制“x” 表示十六進制,會在數(shù)值前插入“0x” 兩個字符,如果用大寫的 “X” 字符,則插入 “0X” 兩個字符。如果格式描述中指定了輸出域?qū)?,則表示替換形式的字符要跟在域?qū)挃?shù)值后面,比如:

auto s = std::format("{0:#},{0:#6d},{0:#6b},{0:#6o},{0:#6x}", 10); //10,    10,0b1010,   012,   0xa

? 對于浮點數(shù)類型,如果是有限數(shù)字,并且小數(shù)點后面沒有有效數(shù)字的情況,比如 6.0,默認情況下是不輸出小數(shù)點的。如果希望強制輸出小數(shù)點,則需要用到 # 控制符,還可以配合 “g” 和 “G” 轉(zhuǎn)換符在小數(shù)點后面補足 0,比如下面的代碼:

auto s = std::format("{0:},{0:#},{0:#6g},{0:#6G}", 6.0); //6,6.,6.00000,6.00000

? 0 表示在數(shù)字前填充前導 0,對于無窮大和無效值,則忽略這個符號,不填充前導 0。如果 0 字符和對齊選項一起出現(xiàn),則忽略 0 字符。看幾個例子:

auto s = std::format("{:+06d}", 12);   // s 的值為 "+00012"
auto s = std::format("{:#06x}", 10); // s 的值為 "0x000a"
auto s = std::format("{:<06}", -42);  // s 的值為 "-42   " (因 < 對齊忽略 0 )

? widthprecision 用于表示數(shù)字的域?qū)捄途?。width 就是一個正的十進制數(shù)字,常用于配合對齊和填充控制符使用,也用于配合 0 控制符使用,前面已經(jīng)用展示了相關的例子。precision 的形式就是小數(shù)點后面跟一個十進制數(shù)字或者跟一個嵌套的替換占位符表示。precision 只能用于浮點數(shù)或字符串,對于浮點數(shù)表示輸出的格式化精度,對于字符串則表示使用字符串中多少個字符。type 控制字符 “f” 常用來配合精度控制使用,來看幾個例子:

float pi = 3.14f;
auto s = std::format("{:10f}", pi);           // s = "  3.140000" (width = 10)
auto s = std::format("{:.5f}", pi);           // s = "3.14000" (precision = 5)
auto s = std::format("{:10.5f}", pi);         // s = "   3.14000"
auto s = std::format("{:>10.5}", "Kitty loves cats!");         // s = "     Kitty"

如果你覺得精度和寬度需要寫死到格式化字符串中,會給使用帶來不便,那你就太小看 C++ 標準委員會的專家了。格式說明部分支持嵌套格式化占位符的形式,動態(tài)指定相關的格式化參數(shù),比如這樣:

auto s = std::format("{:{}f}", pi, 10);       // s = "  3.140000" (width = 10)
auto s = std::format("{:.{}f}", pi, 5);       // s = "3.14000" (precision = 5)
auto s = std::format("{:{}.{}f}", pi, 10, 5); // s = "   3.14000" (width = 10, precision = 5)

在上面幾行代碼中,參數(shù)列表中表示精度的 10 和 5 可以直接指定,也可以是通過其他形式計算得到的動態(tài)結(jié)果,形式上非常靈活。但是使用嵌套定位符的形式,需要確保對應的參數(shù)是正整數(shù)類型,否則 std::format() 函數(shù)會拋出異常(C++ 23 會報編譯錯誤,請參考資料 [9] 和 4.1 節(jié)的內(nèi)容)。

? L 控制符用于在格式化時引入地域化語言環(huán)境,這個控制符只用于算數(shù)類型,比如整數(shù)、浮點數(shù)和布爾類型數(shù)值的文本表示。對于整數(shù),可按照本地語言環(huán)境插入適當?shù)臄?shù)位組分隔符。對于浮點數(shù),也是按照本地語言環(huán)境插入適當?shù)臄?shù)位組和底分隔符。對于布爾類型的文本表示,與使用 std::numpunct::truename 和 std::numpunct::falsename 得到的結(jié)果一致。西方表達數(shù)字的習慣是用 “,” 做數(shù)字分隔符,中文環(huán)境則沒有這個習慣,比如下面的代碼,將地域化環(huán)境切換為西方英語環(huán)境,則數(shù)字的格式就有差別了:

std::locale::global(std::locale("en_US"));  //將語言環(huán)境切換為西方英語環(huán)境
auto s = std::format("{0:12},{0:12L}", 432198409L); // s =    432198409, 432,198,409

? type 控制符用于確定數(shù)值以何種方式展現(xiàn),前面介紹 # 控制符的時候已經(jīng)介紹了 “o”、“b”、“d”、“x”、“X” 幾個控制符。其實還有很多控制字符,比如 “B”,作用和 “b” 一樣,只是數(shù)字前綴用 “0B” 兩個字符。“e” 和 “E” 是用指數(shù)形式展示浮點數(shù),“s” 用于輸出字符串,“a” 和 “A” 是用 16 進制展示浮點數(shù)(用字母 p 表示指數(shù))等等。

2.3 自定義格式

? format 庫的強大之處還在于對用戶自定義類型擴展的支持,用戶可以通過提供 std::formatter<> 模板的特化實現(xiàn)對自定義類型的支持。實際上,C++ 對標準庫中的類型的支持,也是通過提供相應的 std::formatter<> 模板特化實現(xiàn)的,比如 char 類型的特化版本就是:

template<> struct formatter<char, char>;

如果要實現(xiàn)自定義數(shù)據(jù)類型的格式化規(guī)則,需要針對自定義數(shù)據(jù)類型實現(xiàn) std::formatter<> 的特化版本。具體的實現(xiàn)方法請參考《C++ 的 format 函數(shù)支持自定義類型》。

3 std::vformat 和 std::format_args

? std::format() 與 std::vformat() 的關系就如同 sprintf() 和 vsprintf() 的關系一樣,主要用于用戶自定的帶格式化參數(shù)的函數(shù)與 format 庫配合使用的場景,而 std::format_args 則用來配合進行參數(shù)傳遞??梢岳斫?,std::vformat() 就是 std::format() 的一個類型擦除版本,為了配合 std::format() 的實現(xiàn)而存在,避免代碼都在 std::format() 中造成的模板膨脹問題。一般不建議直接使用 std::vformat() 函數(shù),4.2 節(jié)也介紹了在 C++ 26 之前直接使用 std::vformat() 可能導致的問題。但是在某些情況下,std::vformat() 函數(shù)還是有用武之地的,比如下面的例子。

? 在 C++ 提供函數(shù)參數(shù)包之前,可變參數(shù)函數(shù)如果要配合 sprintf() 將用戶輸出的變長參數(shù)進行格式化,就需要用 va_list 配合 vsnprintf() 函數(shù)實現(xiàn),比如這個記錄日志的函數(shù)就是典型的用法:

void __cdecl DebugTracing(int nLevel, const char* fmt, ... ) {
	if(nLevel >= g_Level) { //控制日志記錄的級別
		va_list args;
		va_start(args, fmt);
		int nBuf;
		char szBuffer[512];
		nBuf = _vsnprintf(szBuffer, sizeof(szBuffer)/sizeof(char), fmt, args);
		ASSERT(nBuf < sizeof(szBuffer)); 
		LogMessage(szBuffer); //日志寫入系統(tǒng)
		va_end(args);
	}
}

因為 … 不是具體的參數(shù),所以無法直接調(diào)用 sprinf() 函數(shù)。只能用 va_start() 宏解析出 args 參數(shù),然后調(diào)用 vsprintf() 函數(shù)。這個函數(shù)中 szBuffer[] 的使用其實是讓人心驚膽戰(zhàn)的,512 個字節(jié)的數(shù)組放在棧中是個不妥的設計,函數(shù)調(diào)用鏈層級比較深的話可能爆棧,此外,512 字節(jié)有時候可能還不夠,動態(tài)申請內(nèi)存顯然很麻煩。

? 對于現(xiàn)代 C++ 而言,可以 std::format_args 參數(shù)配合 std::vformat() 函數(shù)安全地實現(xiàn)這個功能:

void DebugTracing(int nLevel, const std::string_view& fmt, std::format_args&& args) {
    if (nLevel >= g_Level) { //控制日志記錄的級別
        std::string msg = std::vformat(fmt, args);
        LogMessage(msg); //日志寫入系統(tǒng)
    }
}

使用時可以借助 std::make_format_args() 函數(shù)幫忙構(gòu)造 args 參數(shù),比如:

DebugTracing(5, "{0:<d}{1:<x}", std::make_format_args(34, 42));

實際上,DebugTracing() 函數(shù)的使用可以借助函數(shù)參數(shù)包語法,將 args 替換成函數(shù)參數(shù)包,從而進一步簡化使用:

template <typename... Args>
void DebugTracing(int nLevel, const std::string_view& fmt, Args&&... args) {
    if (nLevel >= g_Level) { //控制日志記錄的級別
        std::string msg = std::vformat(fmt, std::make_format_args(args...));
        LogMessage(msg); //日志寫入系統(tǒng)
    }
}

這樣使用的時候就不需要 std::make_format_args() 了:

DebugTracing(5, "{0:<d}{1:<x}", 34, 42);

4 C++ 23 和 26 的持續(xù)優(yōu)化

4.1 C++ 23 的改進

? C++ 23 對 format 庫的主要改進是支持更多的標準庫類型,比如 Ranges[5]、thread::id、std::stacktrace 等等。資料[6] 討論并明確了常見容器類型的格式化輸出形式,比如:

  • map 類型: {k1: v1, k2: v2}
  • set 類型: {v1, v2}
  • 一般序列容器類型: [v1, v2]

在 C++ 20 沒趕上火車的 std::print() 函數(shù)[4]也在 C++ 23 上車了,雖然還只支持標準輸出流和文件流,但是已經(jīng)具備了代替標準輸入輸出流的潛質(zhì)。還解決了時間庫的格式化在本地化處理時與 format 不兼容的問題,這個在《C++ 的時間庫之八:format 與格式化》一篇已經(jīng)介紹過了。另外,當使用 L 格式指定本地化輸出的時候,format 的結(jié)果采用何種編碼格式的問題,也由資料[8] 明確了,就是采用 unicode 編碼格式,而不是系統(tǒng)默認的格式。這很重要,以中文為例,Windows 系統(tǒng)默認的格式是 GB2312、GB18030、GBK 等代表的擴展 ASCII 編碼,而 Linux 采用的是 UTF-8 編碼,如果庫的規(guī)范在這個地方模糊,將給程序之間的數(shù)據(jù)交換帶來隱患。

? 資料 [9] 主要是對 LEWG 提出的一些問題進行了改進,比如將格式化字符串的類型從 basic_string_view 字符串類型改為 basic_format_string<charT, Args…> 類模板,改進的好處可以用這個例子說明一下:

auto s = std::format("{:d}", "I am not a number");

這行代碼在 C++ 20 版本中會在運行時拋出一個 std::format_error 異常,但是改進后,就可以在編譯期檢查出參數(shù)類型的不匹配,因為 basic_format_string 類包含參數(shù)類型信息,可以檢查格式化字符串的錯誤,這大大提升了 format() 函數(shù)的安全性。

? 資料 [11] 的改進值允許 format 支持非常量可格式化類型。改進的原因是在 C++ 20 中 format() 函數(shù)聲明大概是這個樣子:

template<class... Args>
string format(string_view fmt, const Args&... args);
//改進后大概這樣:
template <class... Args>
string format(const format_string<_Types...> fmt, Args&&... args);

注意它對參數(shù)的要求是常量引用,也就是說,參數(shù)要么是帶有 const,要么是可拷貝類型,這就限制了一些使用場景,比如非常量可迭代的 view,此時會產(chǎn)生臨時對象,這種隱含的臨時對象拷貝會產(chǎn)生不易覺察的開銷。所以提案改進的結(jié)果就是改用轉(zhuǎn)發(fā)引用,同時利用 format_string<> 對參數(shù)的生命周期進行檢查。

? 資料 [12] 和 [13] 主要是對填充字符的容差和長度估算問題給出了明確的解決方案,這幾個問題分別是 LWG issue 3576、LWG issue 3639 和 LWG issue 3780,感興趣的讀者可通過 資料 [12] 和 [13] 的問題鏈接自行了解相關的情況。

4.2 C++ 26 的改進

? C++ 26 的改進也不少,資料 [14] 解決了 to_string() 函數(shù)和 format() 函數(shù)輸出數(shù)字的格式不一致問題,調(diào)整后的 to_string() 函數(shù)輸出格式與 format() 函數(shù)的默認格式一致。資料 [15] 引入了一個很有意思的問題,來看這行代碼:

format("{:>{}}", "hello", "10")

按說 C++ 23 已經(jīng)支持對格式化字符串的檢查,以上錯誤可以在編譯期報錯的,但是實際上不是,上述代碼只是在運行期產(chǎn)生了一個 format_error 異常。我們在 2.2 節(jié)介紹過,format() 函數(shù)的格式化說明支持嵌套的形式使用動態(tài)格式參數(shù),這樣代碼就是指定了一個動態(tài)寬度,按照順序,下一個參數(shù),也就是 “10” 就是這個動態(tài)寬度,但是顯然,“10” 不是我們需要的整數(shù)類型。實際上根據(jù)第一個參數(shù)是字符串類型,編譯器應該知道它的寬度是整數(shù)類型,所以這里應該能夠檢查出動態(tài)寬度部分對應的類型不正確問題。因此,P2757 提案就要求對格式化參數(shù)也進行類型檢查。

? 從 C++ 20 到 26,那么多數(shù)據(jù)類型都被支持,怎么能少了指針呢?本質(zhì)上,指針作為整數(shù)類型,可以有多種輸出格式,按照 16 進制格式輸出就行了。但是,指針作為這么明確的一種數(shù)據(jù)類型,每次都要被 cast 成整數(shù)類型總是不爽。資料 [16] 就允許直接將指針格式化成地址形式:

//假設 uintptr_t 是預定義的指針類型
int i = 0;
format("{:#018x}", reinterpret_cast<uintptr_t>(&i)); // P2510 之前
format("{:018}", &i);// P2510 之后

能少敲幾次鍵盤也是極好的,對吧?

? 前面介紹過,資料 [9] 提出了很多編譯期檢查的改進,但是對于字符串來說,由于資源限制,資料 [9] 沒有提供一個好的 API 來使用格式字符串在編譯期未知的格式化函數(shù)。作為一種變通方法,可以使用類型擦除(type-erased) API(std::vformat() 函數(shù)),但是這嚴重破壞了運行時的安全性。資料 [18] 建議引入 fmtlib 庫現(xiàn)成的 runtime_format 提供運行期的檢查,以避免不安全的代碼破化系統(tǒng)安全性。資料 [9] 引入編譯期檢查的另一個問題就是要求格式化字符串必須是編譯期可以求值的常量或立即函數(shù)的返回值,否則的話就會導致編譯錯誤,比如:

std::string strfmt = translate("The answer is {}.");
auto s = std::format(strfmt, 42); // error

translate() 不是立即函數(shù)或常量函數(shù),所以 strfmt 不是編譯期常量,使用 format() 函數(shù)將導致編譯錯誤。于是大家就想到了 vformat() 函數(shù),但是這個類型擦除的 API 是為了避免模板膨脹而設計的,是給庫或格式化函數(shù)開發(fā)者使用的。然而阿貓阿狗程序員朋友們被逼的沒辦法,也只能硬著頭皮用了。但是,用的不合適,錯誤就來了,比如這個資料 [17] 上的例子:

std::string str = "{}";
std::filesystem::path path = "path/etic/experience";
auto args = std::make_format_args(path.string());
std::string msg = std::vformat(str, args);

這段看似“人畜無害”的代碼隱含著 UB,因為格式化參數(shù)保存的是一個已經(jīng)銷毀的對象的引用。所以 [17] 建議將 make_format_args() 函數(shù)的參數(shù)從轉(zhuǎn)發(fā)引用改成左值引用,從而避免錯誤使用右值的問題。

? 資料 [19] 主要是解決 char 被當成整數(shù)類型處理的問題,當 char 遇到 d 或 x 格式化描述符時,會被當成整數(shù)處理,但是 char 的符號性卻是由編譯器實現(xiàn)決定的,嗯,問題就出來了。標準 ASCII 都是正值,但是遇到 unicode 編碼的時候,就會遇到負值,這樣的情況不同的編譯器就會產(chǎn)生不一樣的輸出。所以資料 [19] 建議此時將 char 統(tǒng)一轉(zhuǎn)型為 unsigned char,從而避免不一致問題。

? 資料 [20] 引入了許多標準庫類型的格式化支持,其中包括對文件庫(filesystem)的 path 對象的格式化支持。但是因為帶空格的字符串會有 quoting 字符的問題,那就是雙引號,要不要轉(zhuǎn)義?還有就是本地化面臨的編碼問題和格式問題,一度被 SG 16 要求移除對 path 的支持。資料 [21] 針對這些問題提出了一種改進的方案,總算解決了這個危機。

參考資料

1.https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf

[2] P0645R10: Text Formatting

[3] P2372R3: Fixing locale handling in chrono formatters

[4] P2093R14: Formatted output

[5] P2286R8: Formatting Ranges

[6] P2585R1: Improve default container formatting

[7] P2693R1: Formatting thread::id and stacktrace

[8] P2419R2: Clarify handling of encodings in localized formatting of chrono types

[9] P2216R3: std::format improvements

[10] P2508R1: Expose std::basic-format-string<charT, Args…>

[11] P2418R2: Add support for std::generator-like types to std::format

[12] P2572R1: std::format() fill character allowances

[13] P2675R1: format’s width estimation is too approximate and not forward compatible

[14] P2587R3: to_string or not to_string

[15] P2757R3: Type-checking format args

[16] P2510R3: Formatting pointers

[17] P2905R2: Runtime format strings

[18] P2918R2: Runtime format strings II

[19] P2909R4: Fix formatting of code units as integers (Dude, where’s my char?)

[20] P1636R2: Formatters for librarytypes

[21] P2845R8: Formatting of std::filesystem::path

到此這篇關于C++ 的 format 和 vformat 函數(shù)的文章就介紹到這了,更多相關C++ format 和 vformat 函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C語言中實現(xiàn)自定義數(shù)據(jù)類型的輸入輸出的方法和技巧

    C語言中實現(xiàn)自定義數(shù)據(jù)類型的輸入輸出的方法和技巧

    在 C 語言中,除了基本的數(shù)據(jù)類型(如整型、浮點型、字符型等),我們還經(jīng)常需要自定義數(shù)據(jù)類型來滿足特定的編程需求,所以本文給大家介紹了C語言中實現(xiàn)自定義數(shù)據(jù)類型的輸入輸出的方法和技巧,需要的朋友可以參考下
    2024-07-07
  • C語言之字符串模糊查詢方法的實現(xiàn)

    C語言之字符串模糊查詢方法的實現(xiàn)

    本篇文章主要為大家介紹字符串模糊查詢的C語言程序編寫方法,有需要的朋友可以參考下
    2015-07-07
  • vscode C++開發(fā)環(huán)境配置步驟詳解(教你如何用vscode編寫寫C++)

    vscode C++開發(fā)環(huán)境配置步驟詳解(教你如何用vscode編寫寫C++)

    這篇文章主要介紹了vscode C++開發(fā)環(huán)境配置詳細教程(教你如何用vscode編寫寫C++),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • Linux?C/C++?timeout命令實現(xiàn)運行具有時間限制功能

    Linux?C/C++?timeout命令實現(xiàn)運行具有時間限制功能

    inux?timeout命令的一個屬性是時間限制??梢詾槿魏蚊钤O置時間限制。如果時間到期,命令將停止執(zhí)行,這篇文章主要介紹了Linux?C/C++?timeout命令實現(xiàn)(運行具有時間限制),需要的朋友可以參考下
    2023-02-02
  • 線段樹詳解以及C++實現(xiàn)代碼

    線段樹詳解以及C++實現(xiàn)代碼

    線段樹在一些acm題目中經(jīng)常見到,這種數(shù)據(jù)結(jié)構(gòu)主要應用在計算幾何和地理信息系統(tǒng)中,這篇文章主要給大家介紹了關于線段樹以及C++實現(xiàn)的相關資料,需要的朋友可以參考下
    2021-07-07
  • C/C++從零開始的cmake教程

    C/C++從零開始的cmake教程

    今天小編就為大家分享一篇關于C/C++從零開始的cmake教程,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • C++無法打開源文件bits/stdc++.h的問題

    C++無法打開源文件bits/stdc++.h的問題

    這篇文章主要介紹了C++無法打開源文件bits/stdc++.h的問題以及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • C語言實現(xiàn)校園導游系統(tǒng)

    C語言實現(xiàn)校園導游系統(tǒng)

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)校園導游系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • C++中4種強制類型轉(zhuǎn)換的區(qū)別總結(jié)

    C++中4種強制類型轉(zhuǎn)換的區(qū)別總結(jié)

    C++風格的類型轉(zhuǎn)換提供了4種類型轉(zhuǎn)換操作符來應對不同場合的應用。下面這篇文章主要給大家介紹了C++中4種強制類型轉(zhuǎn)換的區(qū)別,有需要的朋友們可以參考借鑒,下面來一起看看吧。
    2016-12-12
  • C++對數(shù)組的引用實例分析

    C++對數(shù)組的引用實例分析

    這篇文章主要介紹了C++對數(shù)組的引用實例分析,需要的朋友可以參考下
    2014-08-08

最新評論