C++11標准庫中cstdio頭文件新增的5個格式化I/O函數學習


剛開始學網絡編程,稍微擴展書上的簡單C/S程序時,發現以前太忽略標准I/O這一塊,查官網發現C++11新增了幾個格式化I/O函數。

  • snprintf    將格式化輸出寫入到有大小限制的緩存中
  • vfscanf     從流中讀取數據到可變參數列表中
  • vscanf      讀取格式化數據到可變參數列表中
  • vsnprintf  從可變參數列表中寫入數據到有大小限制的緩存中
  • vsscanf     從字符串中讀取格式化數據到可變參數列表中

主要談談snprintf,后面4個都是輔助可變參數列表的。

int snprintf ( char * s, size_t n, const char * format, ... );

百度一下會發現一些過時的文章寫到VC上是_snprintf而gcc上是snprintf,VC的_snprintf不會在復制完的字符串后面補上一個'\0'。

這是很多C新手會在Win平台會出現燙燙燙的原因,因為輸出字符數組時沒有遇到'\0'結尾,所以會一直輸出,甚至是未初始化的內存,默認為0xcccccccc,變成字符串就是“燙燙燙”。具體參考文章【考據】“燙燙燙”與“錕斤拷”的原理

但是新標准已經將snprintf標准化了函數簽名如下,所以也不用擔心那個問題。

snprintf和sprintf功能基本一致,但是更安全,參數多了一個size_t n,代表寫入緩存的數據大小。比如char buf[10]; 如果n超過10,就會在編譯期提醒錯誤,所以在VS中寫C++,如果使用fopen等函數會編譯不通過,提示你使用fopen_s(類似的還有其他_s后綴的函數)等安全(safe)函數(會在編譯期檢測錯誤)的原因。題外話,解決方案解決use -D_SCL_SECURE_NO_WARNINGS的問題 ,當然更簡單的做法就是每次新建C++項目時把默認SDL Check一欄的勾勾去掉。

回正題,也就是說,除了跟sprintf一樣,到格式化字符串結尾會停止寫入緩存外,當寫入字符數量到達n時也會停止寫入緩存,防止越界。這里的n有一點要注意,假如輸入是合法的n,實際寫入的字符數量是n-1,因為最后1個字符要留給'\0'。

返回值是如果緩存足夠大,所應能出現的字符數。(注意!這和sprintf不同!)如果格式化字符串有誤,返回負數。

#include <cstdio>

int main(int argc, char** argv)
{
	const int n = 10;
	char buf[n];
	int ret = snprintf(buf, n, "%d %s", 47, "is a good number");
	printf("%d:%s\n", ret, buf);
	return 0;
}

上述代碼返回19,即整個字符串(47 is a good number)的長度,因為只寫入了10個字符。返回值和n無關!是固定的!

所以當緩存大小BUFSIZE > retValue時(在這里即n>ret),字符串的n個字符被完全地寫入了緩存中,再加上'\0',占用了緩存的n+1個位置。

再以vfscanf為例

int vfscanf ( FILE * stream, const char * format, va_list arg );

和fscanf的區別在於,fscanf第三個參數是...,也就是C語言的可變參數列表,即<stdarg.h>的va_list、va_start、va_arg、va_end實現),對於va_list的描述,Objects of this type shall only be used as argument for the va_startva_argva_end and va_copy macros, or functions that use them, like the variable argument functions in <cstdio> (vprintfvscanfvsnprintfvsprintf and vsscanf).

當va_start初始化一個va_list后,在va_end之前調用v開頭的格式化I/O函數,將可變參數列表的每個參數都進行printf或scanf操作。以vfscanf為例

#include <cstdio>
#include <cstdarg>

int my_fscanf(FILE* const stream, const char* const format, ...)
{
	va_list ap;
	va_start(ap, format);
	int ret = vfscanf(stream, format, ap);
	va_end(ap);
	return ret;
}

int main(int argc, char** argv)
{
	int i;
	double d;
	char s[BUFSIZ];
	my_fscanf(stdin, "%d, %lf, %s", &i, &d, s);
	printf("i = %d, d = %lf, s = %s\n", i, d, s);
	return 0;
}

類似上面的代碼,相當於vxxx的函數都是用來配合那幾個va_list、va_start、va_end實現xxx函數,主要作用還是用來轉發可變參數列表,因為...無法作為參數傳遞,只能借助va_list的形式作為參數傳遞。因此通過vsnprintf能夠輕松實現通過格式化字符串生成std::string的功能,代碼如下:

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

std::string str_format(const char *fmt, ...)
{
	int old_size = strlen(fmt);
	std::unique_ptr<char[]> buf(new char[old_size]);
	va_list ap;
	
	va_start(ap, fmt);
	int new_size = vsnprintf(buf.get(), old_size, fmt, ap);
	va_end(ap);
	if (new_size < 0)
		return "";

	buf.reset(new char[new_size + 1]);
	va_start(ap, fmt);
	new_size = vsnprintf(buf.get(), new_size + 1, fmt, ap);
	va_end(ap);
	if (new_size < 0)
		return "";

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

int main()
{
	auto ret = str_format("%d %lf %s", 1, 3.14, "hello world");
	printf("%s\n", ret.c_str());  // 1 3.140000 hello world
	return 0;
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM