你所不知道的printf函数
#include <stdio.h>
int main(void)
{
int a = 4;
int b = 3;
int c = a / b;
float d = *(float *)(&c);
long long e = 0xffffffffffffffff;
printf("a/b:%f,a:%dn", a / b, a, b); //打印0
printf("(float)a/b:%fn", ((float)a) / b); //打印1
printf("(double)a/b:%lfn", ((double)a) / b); //打印2
printf("d:%fn", d); //打印3
printf("%.*fn", 20, (double)a / b); //打印4
printf("e:%d,a:%dn", e, a); //打印5
printf("a:%d,++a:%d,a++:%dn", a, ++a, a++); //打印6
float aa = 1.33333333;
char *pa = "hello";
printf("%.*fn",9,aa);
printf("%*sn",10,pa);
return 0;
return 0;
}
可变参数中的类型提升
printf是接受变长参数的函数,传入printf中的参数个数可以不定。而我们在变长参数探究中说到: 调用者会对每个参数执行“默认实际参数提升",提升规则如下: ——float将提升到double ——char、short和相应的signed、unsigned类型将提升到int
也就是说printf实际上只会接受到double,int,long int等类型的参数。而从来不会实际接受到float,char,short等类型参数。 我们可以通过一个示例程序来检验
//bad code
#include<stdio.h>
int main(void)
{
char *p = NULL;
printf("%d,%f,%cn",p,p,p);
return 0;
}
编译报错如下:
printf.c: In function ‘main’:
printf.c:5:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘char *’ [-Wformat=]
printf("%d,%f,%cn",p,p,p);
^
printf.c:5:12: warning: format ‘%f’ expects argument of type ‘double’, but argument 3 has type ‘char *’ [-Wformat=]
printf.c:5:12: warning: format ‘%c’ expects argument of type ‘int’, but argument 4 has type ‘char *’ [-Wformat=]
我们可以从报错信息中看到:
- %d 期望的是 int 类型参数
- %f 期望的是 double 类型参数
- %c 期望的也是 int 类型参数
而编译之所以有警告是因为,char *类型无法通过默认实际参数提升,将其提升为int或double。
参数入栈顺序以及计算顺序
在C语言中,参数入栈顺序是确定的,从右往左。而参数的计算顺序却是没有规定的。也就是说,编译器可以实现从右往左计算,也可以实现从左往右计算。
浮点数的有效位
对于double类型,其有效位为15~~16位(参考:对浮点数的一些理解)。
可变域宽和精度
printf中,*的使用可实现可变域宽和精度,使用时只需要用*替换域宽修饰符和精度修饰符即可。在这样的情况下,printf会从参数列表中取用实际值作为域宽或者精度。示例程序如下:
#include<stdio.h>
int main(void)
{
float a = 1.33333333;
char *p = "hello";
printf("%.*fn",6,a);
printf("%*sn",8,p);
return 0;
}
运行结果:
1.333333
hello
而这里的6或者8完全可以是一个宏定义或者变量,从而做到了动态地格式控制。
格式控制符是如何处理参数的
printf有很多格式控制符,例如%d,它在处理输入时,会从堆栈中取其对应大小,即4个字节作为对应的参数值。也就是说,当你传入参数和格式控制符匹配或者在经过类型提升后和格式控制符匹配的时候,参数处理是没有任何问题的。但是不匹配时,可能会出现未定义行为(有两种情况例外,我们后面再说)。例如,%f期望一个double(8字节)类型,但是传入的参数是int(4字节),那么在处理这个int参数值,可能会多处理4个字节,并且也会造成处理数据错误。
有了前面这些内容的铺垫,我们再来解答开始的疑问:
- 对于问题0,a/b的结果显然为4字节的int类型1,而%f期望的是8字节的double,而计算结果只有4个字节,因此会继续格式化后面4个字节的a,而整型1和后面a组合成的8字节数据,按照浮点数的方式解释时,它的值就是0.000000了。由于前面已经读取解释了a的内容,因此第二个%d只能继续读取4个字节,也就是b的值3,最终就会出现打印a的值是3,而不是4。
- 对于问题1,实际上在printf中,是不需要%lf的,%f期望的就是double类型,在编译最开始的示例程序其实就可以发现这个事实。当然了在scanf函数中,这两者是有区别的。
- 对于问题2,也很简单,2的二进制存储形式按照浮点数方式解释读取时,就是该值。
- 对于问题3,double的有效位为15~16位,也就是之外的位数都是不可靠的。printf中的*可用于实现可变域宽和精度,前面已经解释过了。
- 对于问题4,这里不给出,留给读者思考,欢迎大家可留言区给出原因。
- 对于问题5,虽然参数计算顺序没有规定,但是实际上至少对于gcc来说,它是从右往左计算的。也就是说,先计算a++,而a++是先用在加,即压入a=4,其后,a的值变为5;再计算++a,先加再用,即压入a=5+1=6;最后a=6,压入栈。最终从左往右压入栈的值就分别为6,6,4。也就是最终的打印结果。但是实际情况中,这样的代码绝对不该出现!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- linux下分割和合并压缩包
- 编译.net .net Core程序 代码,仅做备份
- js删除数组对象中符合条件的数据
- .net core webapi jwt 更为清爽的认证 ,续期很简单(2)
- 手把手教你写一个windows服务 【基于.net】 附实用小工具{注册服务/开启服务/停止服务/删除服务}
- 一网打尽枚举操作 .net core
- Jenkins 发布.net core 程序,服务端无法下载nuget包的解决方法 error NU1102: 找不到版本为 (>= 3.1.6) 的包
- NET Core Kestrel部署HTTPS 一个服务器绑一个证书 一个服务器绑多个证书
- .net core webapi jwt 更为清爽的认证 ,续期很简单(1)
- 用flask来在线管理你的iptables
- Linux Shell命令速查表
- Windows10实现滑动锁屏
- Vue&uni-app在微信浏览器隐藏titleNView的一个方法
- 使用OData服务将SAP C4C自定义BO的TextCollection暴露给外部消费者
- 如何在SAP C4C AdvancedListPane上批量执行若干BO实例的action