C语言printf,格式化字符串,缓冲区
文章目录C语言printf,格式化字符串,缓冲区前言格式化字符串printf 常用的format标签:其他format指示符`%s``%p``%a``%n`子格式控制符相关库函数printf 缓冲区(缓存区)问题fprintf函数,stdout,stderr,缓冲区什么是缓冲区为什么需要缓冲区缓冲区类型缓冲区的大小缓冲区什么时候会被刷新(清空)getchar, getch, getche 的区别:其他%x、%X、%#x、%#X 的区别关于大写输出 (%X %G %E %A)关于`%I64d` 和 `%lld`如何输出 %d、\ 和双引号如果 printf %d 后不提供参数会怎么样?利用C11特性 _Generic 写的泛型print
前言
用惯了 Python 字符串的 format,感觉 C 的 printf 用起来好别扭,于是写这篇来帮忙记忆一些细节。
然后这是cpp官方文档 (C和C++的printf是一样的)
http://www.cplusplus.com/reference/cstdio/printf/
格式化字符串
printf 常用的format标签:
这里用了为了方便,用了宏函数(不过宏函数是不能滥用的)。
关于宏函数的一些特殊用法,可参考这篇博客:
https://blog..net/q2519008/article/details/80934815
顺带一提,我用 VSCODE 写C或C++代码时,宏函数也有高亮,很方便。
#include
#include
#include
// 下面这是一个用于输出结果的宏函数
// ... 表示任意数量参数,对应后面的 __VA_ARGS__
// #A 表示把A字符串化,这对__VA_ARGS__也一样
// ##A 表示把A与前面的进行拼接,但是##__VA_ARGS__行为不太一样
// ##__VA_ARGS__ 表示:当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错
// 关于如何把 FORMAT 参数嵌入字符串里,这了用上了字符串拼接的写法,即形如"xx""xx"的写法
#define PRINTF(FORMAT,...) printf("(%s) ---> ("FORMAT")\n",#__VA_ARGS__,##__VA_ARGS__);
int main(){
int intNum = 3;
long longNum = 3L;
long long LLNum = 3LL;
short ShortNum = 3;
char c='3';
unsigned short UShortNum = 3U;
unsigned int UIntNum = 3U;
unsigned long long ULLNum = 3ULL;
float FloatNum = 3.0f;
double DoubleNum = 3.0;
long double LDNum = 3.0;
//C99新类型
_Bool boolVar = true; // 注意,这里的 true 只是一个宏而已(int的1),和C++的true是不同的
long double _Complex LDComplexNum = 3.0+3.0I;// 或者 3+3*I,I是complex.h里的宏
PRINTF("%d",intNum); // 还有一种 %i 的写法,但我没怎么用过
PRINTF("%ld",longNum);
PRINTF("%lld",LLNum);
PRINTF("%hd",ShortNum);
PRINTF("%c",c);
PRINTF("%hu",UShortNum);
PRINTF("%u",UIntNum);
PRINTF("%llu",ULLNum);
PRINTF("%f",FloatNum);
PRINTF("%lf",DoubleNum);
PRINTF("%llf",LDNum);
PRINTF("%llf+%llfi",creall(LDComplexNum),cimagl(LDComplexNum));
PRINTF("%d",boolVar);
PRINTF("%lu",sizeof(_Bool));
return 0;
}
输出:
(intNum) ---> (3)
(longNum) ---> (3)
(LLNum) ---> (3)
(ShortNum) ---> (3)
(c) ---> (3)
(UShortNum) ---> (3)
(UIntNum) ---> (3)
(ULLNum) ---> (3)
(FloatNum) ---> (3.000000)
(DoubleNum) ---> (3.000000)
(LDNum) ---> (3.000000)
(creall(LDComplexNum),cimagl(LDComplexNum)) ---> (3.000000+3.000000i)
(boolVar) ---> (1)
(sizeof(_Bool)) ---> (1)
其他format指示符
%s
这个对应字符串,也很常用。
%p
输出指针指向的地址
其实是"%0[some number]X"
#include
int main(){
int a=3;
int *b=&a;
printf("%X\n",b);
printf("%p\n",b);
}
结果:
61FE44
000000000061FE44
%a
https://stackoverflow.com/questions/4826842/the-format-specifier-a-for-printf-in-c
The %a formatting specifier is new in C99. It prints the floating-point number in hexadecimal form. This is not something you would use to present numbers to users, but it’s very handy for under-the-hood/technical use cases.
As an example, this code:
printf("pi=%a\n", 3.14);
prints:
pi=0x1.91eb86p+1
The excellent article linked in the comments explains that this should be read “1.91EB86_16 * 2^1” (that is, the p is for power-of-two the floating-point number is raised to). In this case, “1.91EB86_16” is “1.5700000524520874_10”. Multiply this by the “2^1”, and you get "3.140000104904175_10".
Note that this also has the useful property of preserving all bits of precision, and presenting them in a robust way.
%n
这个相当特殊,它不会输出任何东西。
这个参数必须对应一个有符号整数的指针(不过我实验过unsigned,也行),它存储它出现之前打印的所有字符数。
类似的,有%hn(short),%hhn(byte)
下面的代码在linux下跑没问题
但是在windows下,似乎有问题:%n无效,而且其后一直到换行符的内容都没了(包括换行符)
我花了一段时间在网上查找该问题的原因(我猜一定是系统或者MinGW的问题)。
果然我找到了:
printf()中%n格式说明符
Debug Assertion when using %n in printf
printf Type Field Characters
其实原因很简单:就是微软认为%n不安全,已经不再直接支持了。
解决办法也在上面官网文档里给出来了(_set_printf_count_output),不过我用MinGW试了一下之后报链接错误,这应该是头文件里有定义,但找不到函数体。所以没有成功,我也没有深究。
(顺带一提,java printf的%n表示兼容型换行符;linux换行\n, macOS是\r, windows是\r\n; java为了兼容,就自己搞出来个 %n; https://stackoverflow.com/questions/1883345/whats-up-with-javas-n-in-printf)
//以下代码在deepin中正常运行 (基于Debian的一种linux发行版,是国产的)
# include
int main(void)
{
int i = 46;
printf("%d\n",i);
//%n的作用:计算%n之前字符数量
//如下面的xxxx,共4个
//将结果放到对应整型指针指向的空间
puts("(test 1)+++++++++++++++++++++++++++++++++");
//举个例子
printf("xxxx%n\n",&i);
printf("%d\n",i);
puts("(test 2)+++++++++++++++++++++++++++++++++");
//转义字符是看成一个字符
printf("x\\%%xxx%n\n",&i);
printf("%d\n",i);
puts("(test 3)+++++++++++++++++++++++++++++++++");
//对拼接起来的字符串当成一个整体来看待
printf("xxx" "xxx" "%n\n",&i);
printf("%d\n",i);
puts("(test 4)+++++++++++++++++++++++++++++++++");
//对非ASCII字符,结果与其编码方式有关
printf("你好%n\n",&i);
printf("%d\n",i);
puts("(test 5)+++++++++++++++++++++++++++++++++");
//如果前面有 format specifier,则计算将他们转化为字符串之后的总长度
printf("x%dxxx%n\n",100,&i);
printf("%d\n",i);
printf("x%dxxx%n\n",1000,&i);
printf("%d\n",i);
return 0;
}
结果:
46
(test 1)+++++++++++++++++++++++++++++++++
xxxx
4
(test 2)+++++++++++++++++++++++++++++++++
x\%xxx
6
(test 3)+++++++++++++++++++++++++++++++++
xxxxxx
6
(test 4)+++++++++++++++++++++++++++++++++
你好
6
(test 5)+++++++++++++++++++++++++++++++++
x100xxx
7
x1000xxx
8
子格式控制符
#include
#define PRINTF(FORMAT, ...) \
printf("format (\"%s\"): (%s) ---> (" FORMAT ")\n", FORMAT, #__VA_ARGS__, ##__VA_ARGS__);
#define __TEST__(N) printf("\n(__TEST__%3d)\n", (N));
int main()
{
/*
The format specifier can also contain sub-specifiers: flags, width, .precision
and modifiers
*/
int a = 11;
//负号表示左对齐(默认右对齐)
// 3表示要输出的字符的最小数目,不足补空格
__TEST__(1);
PRINTF("%-3d", a);
// 0表示用0而不是空格来补全,经测试这种写法只有右对齐时才有效
__TEST__(2);
PRINTF("[%03d] [%-03d]", a, a);
//*表示一个可以用整数替换的占位符,可以在运行时指定宽度
//经过试验 %0*2d 这样也能生效,参数为1时,变成 %012d
//不过不建议纠结于这种细节,会按照常规用法使用就行
__TEST__(3);
PRINTF("%0*d", 3, a);
//虽然下面这样能运行,但别这样用,可读性太差
//PRINTF("%0*2d", 1, a);
//对正数补加号
//不过对0也生效这点比较坑,估计是根据最高位是否为0判断正负的吧
__TEST__(4);
PRINTF("%+d", a);
PRINTF("[%+d] [%+d]", 0, -0);
PRINTF("[%-+3d] [%+-3d]", a, a);
//%[若干空格][不写或者number]d
//补足空格,但和 %[number]d 不同的是至少会保留一个空格
__TEST__(5);
PRINTF("% d", a);
PRINTF("% d", a);
PRINTF("% 4d",a);
PRINTF("% 2d",a);
//%#[某格式控制符]
// http://www.cplusplus.com/reference/cstdio/printf/
//(1) o,x,X
// the value is preceeded with 0, 0x or 0X respectively for values
// different than zero.
__TEST__(6);
PRINTF("%o", a);
PRINTF("%#o", a);
PRINTF("%x", a);
PRINTF("%#x", a);
//(2) a, A, e, E, f, F, g or G
// it forces the written output to contain a decimal point even if no more
// digits follow. By default, if no digits follow, no decimal point is
// written.
__TEST__(7);
PRINTF("%g", (float)(a));
PRINTF("%#g", (float)(a));
// (%.precision(精度))
//(这个可能是最麻烦的了)
//(1) d, i, o, u, x, X
// precision 指定了要写入的数字的最小位数。
// 如果写入的值短于该数,结果会用前导零来填充。
// 如果写入的值长于该数,结果不会被截断。
// 精度为 0 意味着不写入任何字符。
__TEST__(8);
//%03d 有点像;不同在于如果用%-.3d,左对齐无效
PRINTF("[%.3d] [%-.3d]", a, a);
//(2) e,E,f
// 要在小数点后输出的小数位数,规则是“四舍五入”
// %f 默认情况下其实是%.6f
__TEST__(9);
PRINTF("%.3f", 0.123456);
PRINTF("%.3f", 0.666666);
PRINTF("%.3f", 0.555555);
PRINTF("%.3f", 0.444555);
PRINTF("%.3f", 0.444545);
PRINTF("%08.2f", 33.3333);
//(3) g,G
// 要输出的最大有效位数
__TEST__(10);
PRINTF("%.3g", 31.123456);
PRINTF("%.3g", 1231231.123456);
//(4) s
// 要输出的最大字符数。默认情况下,所有字符都会被输出,直到遇到末尾的空字符。
__TEST__(11);
PRINTF("%.3s", "hahahahahahah");
//(5) c [无影响]
__TEST__(12)
PRINTF("%.123c", 'a');
//(6) If the period is specified without an explicit value for precision, 0 is assumed.
//*******************************
// 关于这点,网上搜到的基本上是下面这句:
// """当未指定任何精度时,默认为 1。如果指定时不带有一个显式值,则假定为 0。"""
// 其实这一句我之前没理解,实验的时候,%.s 那就是默认0啊。
// 直到我到 http://www.cplusplus.com/reference/cstdio/printf/ 看了英文原版。。。
// 前面那条 “默认为1” 根本就没有。。。。。。。。。。。
// 果然还是英文资料比较好啊。。。
//*******************************
__TEST__(13)
PRINTF("%.f", 33.3333);
PRINTF("%.s", "hahahahahahah");
//(6) %.*
// 其实这个和%*d那个用法一样,就不多说了
__TEST__(14);
PRINTF("%.*f", 4, 0.123456);
}
结果:
(__TEST__ 1)
format ("%-3d"): (a) ---> (11 )
(__TEST__ 2)
format ("[%03d] [%-03d]"): (a, a) ---> ([011] [11 ])
(__TEST__ 3)
format ("%0*d"): (3, a) ---> (011)
(__TEST__ 4)
format ("%+d"): (a) ---> (+11)
format ("[%+d] [%+d]"): (0, -0) ---> ([+0] [+0])
format ("[%-+3d] [%+-3d]"): (a, a) ---> ([+11] [+11])
(__TEST__ 5)
format ("% d"): (a) ---> ( 11)
format ("% d"): (a) ---> ( 11)
format ("% 4d"): (a) ---> ( 11)
format ("% 2d"): (a) ---> ( 11)
(__TEST__ 6)
format ("%o"): (a) ---> (13)
format ("%#o"): (a) ---> (013)
format ("%x"): (a) ---> (b)
format ("%#x"): (a) ---> (0xb)
(__TEST__ 7)
format ("%g"): ((float)(a)) ---> (11)
format ("%#g"): ((float)(a)) ---> (11.0000)
(__TEST__ 8)
format ("[%.3d] [%-.3d]"): (a, a) ---> ([011] [011])
(__TEST__ 9)
format ("%.3f"): (0.123456) ---> (0.123)
format ("%.3f"): (0.666666) ---> (0.667)
format ("%.3f"): (0.555555) ---> (0.556)
format ("%.3f"): (0.444555) ---> (0.445)
format ("%.3f"): (0.444545) ---> (0.445)
format ("%08.2f"): (33.3333) ---> (00033.33)
(__TEST__ 10)
format ("%.3g"): (31.123456) ---> (31.1)
format ("%.3g"): (1231231.123456) ---> (1.23e+006)
(__TEST__ 11)
format ("%.3s"): ("hahahahahahah") ---> (hah)
(__TEST__ 12)
format ("%.123c"): ('a') ---> (a)
(__TEST__ 13)
format ("%.f"): (33.3333) ---> (33)
format ("%.s"): ("hahahahahahah") ---> ()
(__TEST__ 14)
format ("%.*f"): (4, 0.123456) ---> (0.1235)
相关库函数
printf // 用于打印信息
scanf // 用于输入(不过用起来和printf的有所区别,我还没深究)
//上两个函数的safe版本,这是c11标准的
//如果用 VS2015 写代码时使用 scanf 可能会提示换成 scanf_s
//如果有理由不能换,则可加 #pragma warning(disable:4996)
printf_s
scanf_s
sprintf // 格式化字符串
fprintf // 写到文件
// https://en.cppreference.com/w/c/io/fprintf
Defined in header
int printf( const char *format, ... );
(until C99)
int printf( const char *restrict format, ... );
(since C99)
int fprintf( FILE *stream, const char *format, ... );
(until C99)
int fprintf( FILE *restrict stream, const char *restrict format, ... );
(since C99)
int sprintf( char *buffer, const char *format, ... );
(until C99)
int sprintf( char *restrict buffer, const char *restrict format, ... );
(since C99)
int snprintf( char *restrict buffer, size_t bufsz,
const char *restrict format, ... );
(since C99)
int printf_s(const char *restrict format, ...);
(since C11)
int fprintf_s(FILE *restrict stream, const char *restrict format, ...);
(since C11)
int sprintf_s(char *restrict buffer, rsize_t bufsz,
const char *restrict format, ...);
(since C11)
int snprintf_s(char *restrict buffer, rsize_t bufsz,
const char *restrict format, ...);
(since C11)
printf 缓冲区(缓存区)问题
fprintf函数,stdout,stderr,缓冲区
stdout 是标准输出,stderr 是标准错误
stdout 是有缓冲区的,stderr 没有缓冲区(为了立即输出错误)
printf(…) 其实等价于 fprintf(stdout,…),所以是有缓冲区的
如果缓冲区不被刷新,那么printf是不会将缓冲区里暂存