有一个函数,是咱们从学习c语言就开始的第一天就接触的,那就是printf函数,但是这个家族的函数,带给咱们的便利却不是一点半点,因此写一篇用法总结。linux
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 0; }
在编程中常常会打印地址,而通常要求打印8个字节,而字节前导咱们不但愿使用空格而是使用前导0来填充,就可使用上面的写法。编码
# 号:使用格式说明的可选形式,#o 打印的则以0(零)开始,八进制,#x或者#X,则以0x或者0X开始,十六进制。spa
0(零):对于全部数字格式,用前导零填充而不是空格。若是出现 - 标志或者指定了精度(对于整数)则忽略该标志。3d
那么%#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[10]; float j=0; FILE *fp; for(int i=0;i<10;i++,j=j+0.2) { imag[i]=1.0+j; } if((fp=fopen("test.txt","w+"))==NULL) { printf("err\n"); fflush(stdout); getchar(); exit(1); } fprintf(fp,"%s","imag[]={"); for(int i=0;i<10;i++) fprintf(fp," %.4f,",imag[i]); fprintf(fp,"%c",'}'); fclose(fp); return 0; }
输出文件:
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支持各类格式,因此它使用是最频繁的,如上面例子呈现的那样。
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[5]; int ret = snprintf(str, 3, "%s", "abcdefg"); printf("%d\n", ret); printf("%s\n", str); return 0; }
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[30]; 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 = 1115; float f = 27.0; char str[10] = "world"; vspfunc("hello %d %f %s", i, f, str); printf("%s", buffer); return(0); }
4.1 vsnprintf
和snprintf的功能对于sprintf同样,vsnprintf也是处于这样的缘由成为升级版的vsprintf