有一个函数,是我们从学习c语言就开始的第一天就接触的,那就是printf函数,可是这个家族的函数,带给我们的便利却不是一点半点,所以写一篇用法总结。

1.printf函数

格式化输出,可以输出八进制,十进制,十六进制,可以输出字符串,%p输出地址。基本的东西就不在赘述了。

printf是有返回值的,只是一般我们用不到。printf()函数也有一个返回值,它返回所打印的字符的数目。如果有输出错误,那么printf()会返回一个负数(printf( ) 的一些老版本会有不同的返回值)。

*号符,在printf函数中有着很强的格式化作用,如同linux中一样,* 代表任意匹配。

再看一个格式化输出十六进制的例子:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h> int main(void)
{
unsigned int a=0x1;
printf("%#010x",a);
return ;
}

在编程中经常会打印地址,而一般要求打印8个字节,而字节前导我们不希望使用空格而是使用前导0来填充,就可以使用上面的写法。

# 号:使用格式说明的可选形式,#o 打印的则以0(零)开始,八进制,#x或者#X,则以0x或者0X开始,十六进制。

0(零):对于所有数字格式,用前导零填充而不是空格。如果出现 - 标志或者指定了精度(对于整数)则忽略该标志。

那么%#010x:就表示:以十六进制输出,输出长度一共为10,前导用0填充而不是空格。由于#x的作用占用了两个位置,因为要输出0x,所以还剩下8个位置,这样就指定输出了十六进制8个字节长度的数据。

如果这样觉得不好阅读,可以不使用#,直接手动书写0x前缀,指定宽度为8,前导0即可:如:

这是printf函数的常用方法,但是正真重要的,还是在于可变参数及其家族的变种函数。

2.fprintf函数

专注于文件操作的file printf。文件输出,其实是往文件中写内容,如同printf函数一样,虽然说它是输出函数,但实质上往输出流写数据。fprintf聚集了printf家族的传统,可以格式化写文件:

#include<stdio.h>
#include<stdlib.h> int main(void)
{
float imag[];
float j=;
FILE *fp;
for(int i=;i<;i++,j=j+0.2)
{
imag[i]=1.0+j; } if((fp=fopen("test.txt","w+"))==NULL)
{
printf("err\n");
fflush(stdout);
getchar();
exit();
}
fprintf(fp,"%s","imag[]={");
for(int i=;i<;i++)
fprintf(fp," %.4f,",imag[i]);
fprintf(fp,"%c",'}');
fclose(fp);
return ;
}

输出文件:

fopen一个函数,使用w+的方式表示,打开一个文件,可以进行更新(读取和写入),如果该文件存在,就将其长度变为0,如果不存在则先创建之。

int fprintf( FILE *stream, const char * restrict format , ...);

按照format格式输出到stream。其实,在linux学习中,我们知道一切皆文件,这个FILE指针,是可以映射到标准输出的,即:printf(...)=fprintf(stdout,...).

3.sprintf

这个函数用得非常多,也非常重要,务必掌握。

作为printf家族成员,自然printf自带的格式化操作它都具有。sprintf主要用于把格式化输出写到指定字符数组中。

先看基础用法:

通过下面的例子了解sprintf的原理:

可以看到,sprintf把十进制数108放在字符数组buf中,但是存放的原理是,将十进制数的每一位变成字符存放在buf数组中,49对应ASCII字符1,48对应0,56对应8.这样之后,我们就可以预估计buf数组给多大空间合适。

sprintf可以很方便的格式化类型放在数组中,我们再看一个例子:

现在试想一个问题,我们经常编程的时候想要实现中文和英文的同时保存,但是,我们怎么以除了直接初始化的方式来执行英文和中文的链接呢?举例说明一下这个问题,我们想得到一个字符串,包含中文和英文,例如上面的hi,你好。我们可以这样初始化:char str[40]="hi,你好";但是,如果我们的中文,需要由其他地方给出呢?我们怎么做到拼接字符串?我们应该知道,中文所占字节一定是大于一个字节的,根据文本编码,对应2-4个字节(这个要注意哦)。比如现在给你两个字符串,一个是英文的,一个是中文的,你怎么把它拼接到一起?当然,库函数提供 了这样拼接功能的函数,可是,我们的sprintf也可以做到,而又因为sprintf支持各种格式,所以它使用是最频繁的,如上面例子呈现的那样。

返回值

返回写入buffer 的字符数,出错则返回负数。
sprintf 返回以format为格式argument为内容组成的结果被写入buffer 的字节数,结束字符‘\0’不计入内。即,如果“Hello”被写入空间足够大的buffer后,函数sprintf 返回5

sprintf函数等同于fprintf,除了输出被写入一个数组(由参数s指定),而不是一个流。 空字符(\0)写在写入的字符的末尾; 它不计入返回值的一部分。 如果复制发生在重叠的对象之间,行为是未定义的。

那么我们试试违规操作呢?

可以看到,sprintf对溢出是没有保护的,上面例子,str至少应该4个字节,就因为这样,才有了下面的snprintf函数。

3.1 snprintf

这个函数就是在sprintf的基础上增加了一点内容。

函数snprintf()和vsnprintf()不写入超出字节(包括终止空字节('\ 0'))。 如果由于此限制导致输出被截断,那么返回值是如果有足够的空间可用,则该字符数将被写入最终字符串(不包括终止空字节)。 因此,返回值大小比指定的n更大或者相等,意味着输出被截断。

还是上代码来看,因为sprintf会因为使用不当造成内存溢出,而snprintf则不会:

#include <stdio.h>
#include <string.h> int main()
{
char str[];
int ret = snprintf(str, , "%s", "abcdefg");
printf("%d\n", ret);
printf("%s\n", str);
return ;
}

linux下运行:

vs2017运行:

gcc for Windows:

看来和编译器有点关系呀,不过我们学习以c标准的为基准。

snprintf函数等同于fprintf,除了输出被写入一个数组(由参数s指定)而不是一个流。 如果n为零,则不写任何内容,
并且s可能是空指针。 否则超出n-1的输出字符丢弃而不是写入数组(这就是比sprintf安全的地方了),并且空字符写入实际写入数组的字符的末尾。 如果复制发生在重叠的对象之间,行为是未定义的。

snprintf函数返回已写入的字符数,n已经足够大,不计算终止空字符,如果发生编码错误返回负值。 因此,\0终止的输出已经被当且仅当返回的值为非负数且小于n时才完全写入。
见了c标准之后,可以知道,我们的gcc for Windows是和vc6.0一样的输入,这样其实是没有依照c99标准的,故不再分析这样编译器的输出。

那么在现在的基础上来分析上面的代码:

指定n=3,输出最多n-1个,因为snprintf需要给我们一个结尾\0,故只有ab被写入,返回值,是不包含结尾\0的应该输入的大小,这里是7。这证明了我们的字符有7个(不包含\0)需要输出,所以要想全部输出,我们应该指定n=8.

snprintf比sprintf安全,有了溢出保护,建议使用。

总结一下就是:snprintf有溢出保护,就算你给超出空间的内容也不会出现溢出错误,会被snprintf截断。输入的n,代表的是你想要格式化输出到 s指针指向的空间的内容加上1,通俗一点的意思就是,你想格式化输出7个字节到 s 指针指向的空间,那么你的 n就应该指定为8,因为snprintf只会传递n-1个字符过去,它要保留一个结尾\0.它的返回值,如果空间足够,没有发生截断的情况下,返回写入字符的个数,不包含结尾\0,如果发生截断,返回的是应该写入的长度,不包含结尾\0,所以不管截断不截断,返回值的数学值都是相同的,只是代表的意义不同,如果写入发生错误,返回负数。

4.vsprintf

#include<stdio.h>
#include <stdio.h>
#include <stdarg.h>
/*
在LCD_printf中应用
※※※ */ char buffer[];
int vspfunc(char *format, ...)
{
va_list aptr;
int ret; va_start(aptr, format);
ret = vsprintf(buffer, format, aptr);
va_end(aptr); return(ret);
} int main()
{
int i = ;
float f = 27.0;
char str[] = "world"; vspfunc("hello %d %f %s", i, f, str);
printf("%s", buffer);
return();
}

4.1 vsnprintf

和snprintf的功能对于sprintf一样,vsnprintf也是处于这样的原因成为升级版的vsprintf

printf家族探秘的更多相关文章

  1. sscanf和sprintf是scanf和printf家族用法 (转)

    sscanf和sprintf是scanf和printf家族用法 sscanf和sprintf是scanf和printf家族的一对成员,用于处理和分析字符串非常强大得两个函数头文件 stdio.h原型i ...

  2. scanf与printf用法详解

    一.scanf家族 1.scanf家族的原型 int scanf(char const *format,...); int fscanf(FILE *stream,char const *format ...

  3. [C和指针]第四部分

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  4. golang使用vet进行语法检查

    go tool vet是你的好朋友,不要忽视它. vet是一个优雅的工具,每个Go开发者都要知道并会使用它.它会做代码静态检查发现可能的bug或者可疑的构造.vet是Go tool套件的一部分,我们会 ...

  5. C-I/O操作函数详解

    EOF: End Of File, 文字流结尾, 这里的文字流可以是文件(file), 也可以是标准输入(stdin), 它的值在任何可能出现的字符之外(-1) 先列出三种基本类型操作函数 这里面返回 ...

  6. I-O流概念认知升级

    在文件操作基础入门中,我们提到了流的 概念,这篇我们将更多的介绍流这个东西,以及C的I/O相关知识 现在,我们从C程序员最熟悉的printf函数开始学习I/O流. 我们对printf函数一直是很喜爱的 ...

  7. 系统编程--标准IO

    1.流和FILE对象 对于国际字符集,一个字符可以由一个以上的字节来表示.标准I/O文件流可以用来操作单字节和多字节(宽,wide)字符集.一个流的方向(orientation)决定了字符是以单字节还 ...

  8. glib-2.40编译安装

    1 安装glib库所需要的依赖库: libffi-.tar.gz glib-.tar.xz 安装依赖库libffi: tar xf libffi-.tar.gz cd libffi- ./config ...

  9. 从 C++ 到 Objective-C 的快速指南

    简介 当我开始为iOS写代码的时候,我意识到,作为一个C++开发者,我必须花费更多的时间来弄清楚Objective-C中怪异的东西.这就是一个帮助C++专家的快速指南,能够使他们快速的掌握Apple的 ...

随机推荐

  1. webservice系统学习笔记6-使用soap的header传递消息

    1.显示的使用soap的header传递消息(不推荐使用,会破坏正常的代码结构,推荐使用handler处理) @WebResult(name="deleteResult") pub ...

  2. redis编译

    简介: Redis是Nosql中比较出名的,分布式数据库缓存,提升相应的速度,降低对数据库的访问! Redis是一种高级key-value数据库.它跟memcached类似,不过数据可以持久化,(永久 ...

  3. More Effective C++:通过引用捕获异常

    当你写一个catch子句时,必须确定让异常通过何种方式传递到catch子句里.你可以有三个选择:与你给函数传递参数一样,通过指针(by pointer),通过传值(by value)或通过引用(by ...

  4. 用户研究Q&A(1)

    近来,不少同事开始认同用户研究的价值,希望通过接触,理解和研究用户来获取提升产品的有效信息.这绝对是件好事,因为我一直抱持的理念是,研究并不是藏在实验室或者握在少部分人手中的稀罕货,更重要是一种理念和 ...

  5. supervisor介绍与安装

    前言 今天同事让我帮忙安装一个叫supervisor的软件,但自己确实没接触过这个软件 自己做一下学习的记录 我首先是查询了一下supervisor的官网,初步认识一下这个软件 Supervisor是 ...

  6. 【LeetCode】52. N-Queens II

    N-Queens II Follow up for N-Queens problem. Now, instead outputting board configurations, return the ...

  7. php 类和对象

    ⾯向对象是⼀种编程范式,它将对象作为程序的基本单元,将程序和数据封装起来, 以此来提⾼程序的重⽤性.灵活性和可扩展性. ⽬前很多语⾔都⽀持⾯向对象编程,既然对象对象是⼀种范式,其实这就和具体的编程语⾔ ...

  8. C# MediaPlayer的详细用法

    AxWindowsMediaPlayer的详细用法 作者:龙昊雪 AxWindowsMediaPlayer的详细用法收藏 function StorePage(){d=document;t=d.sel ...

  9. Linux命令-下载文件的工具:wget

    Linux系统中的wget是一个下载文件的工具,它用在命令行下.对于Linux用户是必不可少的工具,我们经常要下载一些软件或从远程服务器恢复备份到本地服务器.wget支持HTTP,HTTPS和FTP协 ...

  10. Oracle 11g的Deferred Segment Creation

    本篇主要介绍Oracle 11g中推出的“延迟段创建”(Deferred Segment Creation)特性,以及当我们使用这种特性时,需要注意的问题. 1)Deferred Segment Cr ...