• 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不会在复制完的字符串后面补上一个''。

    这是很多C新手会在Win平台会出现烫烫烫的原因,因为输出字符数组时没有遇到''结尾,所以会一直输出,甚至是未初始化的内存,默认为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个字符要留给''。

    返回值是如果缓存足够大,所应能出现的字符数。(注意!这和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
    ", ret, buf);
    	return 0;
    }
    

    上述代码返回19,即整个字符串(47 is a good number)的长度,因为只写入了10个字符。返回值和n无关!是固定的!

    所以当缓存大小BUFSIZE > retValue时(在这里即n>ret),字符串的n个字符被完全地写入了缓存中,再加上'',占用了缓存的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
    ", 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
    ", ret.c_str());  // 1 3.140000 hello world
    	return 0;
    }
    

      

  • 相关阅读:
    前端性能优化成神之路--图片懒加载(lazyload image)
    前端性能优化成神之路-异步加载
    浮动布局详解
    布局:上下两个div高度固定,中间自适应
    盒子模型和弹性盒子模型的详解
    CSRF攻击详解
    使用Base64格式的图片制作ICON
    如何让父元素有透明度,但里面的子元素不透明
    父级元素以及子元素高度宽度未知如何实现水平垂直居中
    php file文件操作函数
  • 原文地址:https://www.cnblogs.com/Harley-Quinn/p/6337939.html
Copyright © 2020-2023  润新知