当前位置:网站首页>附录A printf、varargs与stdarg A.1 printf函数族
附录A printf、varargs与stdarg A.1 printf函数族
2022-08-01 21:00:00 【weixin_客子光阴】
C语言中经常被误解的3个常用工具:printf库函数族、varargs和stdarg工具。后两者主要用于编写那些随调用场合的不用,其参数的数目和类型也不同的函数。
printf函数族
#include <stdio.h>
main() {
printf("Hello world\n");
}
printf函数的第一个参数是关于输出格式的说明,它是一个描述了输出格式的字符串。这个字符串遵循通常的C语言惯例,以空字符(即\0)结尾。我们把这个字符串写成字符串常量的形式(即用双引号括起来),就能够自动保证它以空字符结尾。
printf函数是把数据写到标准输入,fprintf函数则可以把数据写到任何文件中。需要写入的特定文件,将作为fprintf函数的第一个参数,它必须是一个文件指针。因此
printf(stuff);
从意义上来说就等效于
fprintf(stdout, stuff);
sprintf函数的第1个参数是一个指向字符数组的指针,sprintf函数将把其输出数据写到这个字符数组中。编程人员应该确保这个数组足够大以容纳sprintf函数所生成的输出数据。sprintf函数其余的参数与printf函数的参数相同。sprintf函数生成的输出数据总是以空字符收尾,如果希望在输出数据中出现一个空字符,我们可以显示地使用%c格式项把它打印出来。
这3个函数的返回值都是已传送的字符数。对于sprintf的情形,作为输出数据结束标志的空字符并不计入总的字符数。如果printf或fprintf在试图写入时出现一个I/O错误,将返回一个负值。因为格式字符串决定了其余参数的类型,而且可以到运行时才建立格式字符串,所以要检查printf函数的参数类型是否正确是异常困难的。
printf("%d\n", 0.1);
printf("%g\n", 2);
最后得到的结果可能毫无意义,而且在程序实际运行之前,这些错误既有可能不会被编译器检测到,而成为“漏网之鱼”。
fprintf("error\n");
本意是使用fprintf函数输出一行出错信息到stderr,但是一时大意忘记写stderr,而fprintf函数会把格式字符串当做一个文件结构来处理,这种情况下就很可能出现内核转储的后果!
A.1.1 简单格式类型
每个格式项都是以格式码结束。
%d的含义是以十进制形式打印一个整数,例如
printf("2 + 2 = %d\n", 2 + 2);
%u要求打印无符号十进制整数。
printf("%u\n", -37);
char类型和short类型会被自动扩展为int类型,在把char类型视为有符号类型整数的机器上,这一点经常会引起令人吃惊的后果。 例如下面的例子:
char c;
c = -37;
printf("%u\n", c);
%o打印八进制整数
%x用于打印十六进制整数
八进制和十六进制总是作为无符号数处理。
%s格式项用于打印字符串:与之对应的参数应是一个字符指针,待输出的字符起始于所指向的地址,直到出现一个空字符('\0')才终止。
int n = 108;
printf("%d decimal = %o octal = %x hex\n", n, n, n);
printf("There %s %d item%s in the list.\n", n != 1 ? "are" : "is",
n, n != 1 ? "s" : "");
printf(s);
与
printf("%s", s);
两者的含义并不相同。第一个例子将把字符串s中的任何%字符视为一个格式项的标志,因而其后的字符会被视为格式码。如果除%%之外的任何格式码在字符串s中出现,而后面又没有对应的参数,将会带来麻烦。而第二个例子将会打印出任何以空字符结尾的字符串。
一个NULL指针并不指向任何实际的内存为止,它肯定不可能指向一个字符串。
printf("%s\n", NULL);
printf("%c", c);
等效于
putchar(c);
%g、%f、%e这3个格式项用于打印浮点值。%g格式项在打印那些不需要按列对齐的浮点数时特别有用。
printf("The decimal equivalent of '%c' is %d\n", '*', '*');
但是前者的适应性和灵活性更好,能够把字符c的值嵌入某个更大的上下文中。
它在打印出对应的数值时,会去掉该数值尾缀的零,保留6位有效数字。
printf("Pi = %g\n", 4 * atan(1.0));
printf("%g %g %g %g\n", 1/1.0, 1/2.0, 1/3.0, 1/4.0, 0.0);
注意,因为一个数中出现在前面的零对于精度没有贡献,输出的数值被四舍五入,而不是直接截断:
printf("%g\n", 2.0/3.0);
printf("%g\n", 123456789.0);
如果一个数的绝对值大于999999,%g会采用科学计数法来打印这样的数值。
当指数是-4时,这两种形式的长度就恰好相等。例如,0.000314159与3.14159e-04所占用的空间大小相同。对于比较小的数值,除非该数的指数小于或等于-5,%g格式项才会采用科学计数法来表示。因此,
printf("%g %g %g\n", 3.14159e-3, 3.14159e-4, 3.14159e-5);
%e要求一律显式地使用指数形式:%e格式项将打印出小数点后6位有效数字,而并非如%g格式项那样打印出的数是总共6位有效数字。
%f格式项则恰好相反,它强制禁止使用指数形式来表示浮点数。
printf("%f\n", 1e38);
printf("%%d prints a decimal value\n");
A.1.2 修饰符
整数有3种类型:short、long(l)和正常长度
%ld、%lo、%lx和%lu
long size;
...
printf("%d\n", size);
在某些机器上能工作,某些机器上不能工作。
宽度修饰符
如果一个数值太大而不能被它所在的栏所容纳,那么它就会挤占同一行右侧紧邻数值的位置。
int i;
for (i = 0; i <= 10; i++) {
printf("%2d %2d *\n", i, i * i);
}
宽度修饰符对所有的格式码都有效。
printf("%8%\n");
精度修饰符的作用是控制一个数值的表示中将要出现的数字位数,或者用于限制将要打印的字符串中应该出现的字符数。精度修饰符包括一个小数点和小数点后面的一串数字。精度修饰符出现在%符号和宽度修饰符之后,格式码与长度修饰符之前。精度修饰符的确切含义与格式码有关。
对于整数类型%d、%o、%x和%u,精度修饰符指定了打印数字的最少位数。如果待打印的数值不需要这么多位数来表示,就会在它的前面补上0.
printf("%.2d/%.2d/%.4d\n", 7, 14, 1789);
对于%e、%E和%f格式项,精度修饰符制定了小数点后应该出现的数字位数。除非标志(Flag,我们马上将讨论到)另有说明,否则仅当精度大于0时打印的数值中才会实际出现小数点。
double pi;
pi = 4 * atan(1.0);
printf("%.0f %.1f %.2f %.3f %.6f %.10f\n", pi, pi, pi, pi, pi, pi);
printf("%.0e %.1e %.2e %.10e\n", pi, pi, pi, pi, pi, pi);
对于%g和%G格式项,精度修饰符指定了打印数值中的有效数字位数。除非标准另有说明,否则非有效数字的0将被去掉。如果小数点后不跟点后不跟数字,则小数点也将被删除。
printf("%.1g %.2g %.4g %.8g\n", 10/3.0, 10/3.0, 10/3.0, 10/3.0);
char name[14];
...
printf("... %.14s ...", ..., name, ...);
对于%c和%%格式符,精度修饰符将被忽略。
A.1.3 标志:
可以在%符号和域宽修饰符之间插入标志字符,以微调格式项的效果。
在显示宽度大于被显示位数时,数据尾部都以显示区的右端对齐,左端则被填充空白字符。
*标志字符-的作用是,要求显示方式改为左端对齐,在右端填充空白字符。因此,仅当域宽
存在时,标志字符-才有意义(否则,填充空白字符就无从谈起)。
char name[14];
...
printf("... %-14s ...", ..., name, ...);
*标志字符+的作用是,规定每个待打印的数值在输出时都应该以它的符号(正好或符号)作为
第一个字符。因此,非负数打印出来后,应该在最前面有一个正号。标志字符+与标志字符-
之间不存在任何联系。
printf("%+d %+d %+d\n", -5, 0, 5);
*空白字符作为标志字符时,它的含义是:如果某数是一个非负数,就在它的前面插入一个空白
字符。如果我们希望让固定栏内的数值向左对齐,而又不想用标志字符+,这一点就特别有用。
如果标志字符+与空白字符同时出现在一个格式项中,最终的效果以标志字符+为准。例如:
int i;
for (i = -3; i <= 3; i++) {
print("% d\n", i);
}
如果我们希望在固定栏内按科学计数法打印数值,格式项% e和%+e要比正常的格式项%e有用的多。因为这时出现在非负数前面的正号(或者空白)保证了所有输出数值的小数点都会对齐。例如:
double x;
for (x = -3; x <= 3; x++) {
printf("% e %+e %e\n", x, x, x);
}
标志字符#的作用是对数值输出的格式进行微调,具体的方式与特定格式项有关。
%#o的效果是打印数字以0开头,%#x的效果是打印数字以0x或者0X开头。
0%o把数值0打印成00,%#o把数值0打印成0。
标志字符对浮点数格式的影响有两方面:其一,它要求小数点必须被打印出来,即使小数点后没有数字也是如此;其二,如果用于%g或%G格式项,打印出的数值尾缀的0将不会被去掉。例如:
printf("%.0f %#.0f %g %#g\n", 3.0, 3.0, 3.0, 3.0);
除了+和空白字符,其余的标志字符都是各自独立的。
A.1.4 可变域宽与精度
#define NAMESIZE 14
char name[NAMESIZE];
...
printf("... %.14s ....", ...., name, ...);
printf("... %.NAMESIZE ...", ..., name, ...); //no use,因为预处理器的作用范围不能到达字符串的内部。
printf函数因此允许间接指定域宽和精度。要做到这一点,我们只需用*替换域宽修饰符或精度修饰符其中之一,或者两者都替换。在这种情况下,printf函数首先从参数列表中取得将要使用的域宽或精度的实际数值,然后使用该数值来完成打印任务。
printf("... %.*s ...", ..., NAMESIZE, name, ...);
printf("%*.*s\n", 12, 5, str);
与下式完全等效
printf("%12.5s\n", str);
printf("%*%\n", n);
如果*用于替换域宽修饰符,而与其对应的参数的值为负数,那么效果相当把负号作为-标志字符来处理。因此,上例中如果n为负数,输出结果首先是一个%符号,后面再跟n-1个空格。
A.1.5 新增的格式码
%p用于以某种形式打印一个指针,具体的形式与特定的C语言实现有关(译注:一般是打印出该指针所指向的地址)
%n用于之处已经打印的字符数,这个数被存储在对应参数(一个整型指针)
int n;
printf("hello\n%n", &n);
A.1.6 废止的格式码
过去,要打印一个数值并在它前面填充0,唯一的办法就是使用标志字符0。标志字符0的作用是指定待打印的数值前应该填充0而不是空白字符。因此,
printf("%06d %06d\n", -37, 37);
即使用精度修饰符也能达到这种效果:
printf("%.6d %.6d\n", -37, 37);
在大多数场合,我们都可以用%.来替换%0,效果也非常接近。
边栏推荐
- Go Atomic
- WeChat applet cloud development | personal blog applet
- idea实用快捷键合集——持续更新
- SkiaSharp 之 WPF 自绘 五环弹动球(案例版)
- 模板特例化和常用用法
- 如何封装 cookie/localStorage/sessionStorage hook?
- Goroutine Leaks - The Forgotten Sender
- Convolutional Neural Network (CNN) mnist Digit Recognition - Tensorflow
- 】 【 nn. The Parameter () to generate and why do you want to initialize
- Pytorch框架学习记录12——完整的模型训练套路
猜你喜欢
SIPp installation and use
OSG Notes: Set DO_NOT_COMPUTE_NEAR_FAR to manually calculate far and near planes
New graduate students, great experience in reading English literature, worthy of your collection
【Kaggle】Classify Leaves
关于Request复用的那点破事儿。研究明白了,给你汇报一下。
[Multi-task learning] Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts KDD18
Wildcard SSL/TLS certificate
[Multi-task model] Progressive Layered Extraction: A Novel Multi-Task Learning Model for Personalized (RecSys'20)
MySQL 中出现的字符编码错误 Incorrect string value: ‘\x\x\x\x‘ for column ‘x‘
外骨骼机器人(七):标准步态数据库
随机推荐
扣减库存方案
Where should I prepare for the PMP exam in September?
[Energy Conservation Institute] Ankerui Food and Beverage Fume Monitoring Cloud Platform Helps Fight Air Pollution
Interview Blitz 70: What are sticky packs and half packs?How to deal with it?
Pytorch框架学习记录9——非线性激活
system collection
Protocol Buffer usage
Simple test of the use of iptables
Different operating with different locks, rounding
职场如象棋,测试/开发程序员如何突破成长瓶颈期?
Questions I don't know in database kernel interview(1)
Multithreaded producers and consumers
KDD2022 | Self-Supervised Hypergraph Transformer Recommendation System
记录第一次给开源项目提 PR
】 【 nn. The Parameter () to generate and why do you want to initialize
数据库内核面试中我不会的问题(1)
Telnet弱口令渗透测试
Godaddy domain name resolution is slow and how to use DNSPod resolution to solve it
Postman 批量测试接口详细教程
Remove 360's detection and modification of the default browser