你真的很了解printf函数吗?
对C语言中经常使用的printf这个库函数,你是否真的吃透了呢?
系统化的学习C语言程序设计,是不是看过一两本C语言方面的经典著作就足够了呢?答案是显而易见的:不够。通过这种典型的入门级的学习方式,是不可能真正吃透C语言的。如果C语言这么容易就被吃透了,那么C语言就不可能成为程序设计语言之母,也不可能被各种操作系统用来作为系统调用的API接口语言。学习任何一门技术,如果只是会使用,而不知道其背后的原理,那么就只是知其然不知其所以然。本文试图通过回答一个跟printf有关的“为什么”的问题,来讨论一下简单用法背后的C语言标准中的一个与这个问题相关的技术概念。
为什么printf能够使用%f这个格式化字符串来输出float和double类型的参数?
先看代码例子。
printf("%f %f", 1.234f, 1.234);
乍一看,很多人会觉得这个代码简直天经地义,C语言入门者就已经能够正确的写出这种“如此简单”的代码。这又有什么值得大惊小怪的呢?
现在的问题是为什么上面这个语句能够正常运作?你是否真的深入思考过这个问题呢?
在回答这个为什么的问题之前,先来看另外一个为什么的问题:为什么要思考这个问题呢?
请注意,参数中对1.234f和1.234的输出使用的格式化字符串都是%f。
printf函数的格式化字符串有两个作用:
作用1:指示输出格式。这个是printf格式化字符串不可或缺的作用。
作用2:指示后面的参数值的数据类型。printf函数内部必须能够精确的知道后面传入的参数的类型,否则无法通过va_arg取得实参的值。这个作用是至关重要的。
上面这个代码显然是没有任何问题的。至此,不由得思考一个问题,printf如何能够取得后面两个参数的数据类型呢?printf不可能根据同样一个%f,一会得出后面的参数是float类型的推测,一会得出后面的参数是double的推测。printf能利用的已知信息就只有%f这个格式化字符串。有人说,后面写1.234f就是float,写1.234就是double啊。这个信息仅仅对函数调用方有效,对于被调用函数printf是不可见的。即printf确实无法分辨出后面两个参数的数据类型是什么。
那么printf究竟怎么就知道了两个参数的数据类型的呢?printf肯定是已经知道了数据类型的,否则无法用va_arg取得实参的值。这其实不是printf自身的特异功能,完全是沾了C语言标准的光。这就是神奇的默认参数类型提升。正是有了默认参数类型提升这个概念,编译器在编译上面这个代码时,首先将1.234f这个float类型的参数转化为了double类型的参数。那么printf在处理%f这个格式化字符串时,直接认为对应的参数是double类型即可完成工作,根本不需要区分实际参数是float还是double的问题。
既然是C语言标准提出的这个概念,那么很多C语言编译器就支持了这个特性。包括gcc和vs2019。而且,我们自己写一个变长参数的函数时,也必须考虑这个特性,否则所写函数无法正常工作。当然,当使用float作为参数调用va_arg时,编译器会编译错误的,因此也不必过于担心这个问题带来的困扰。问题在于当我们不了解这个神奇的概念时,在出现类似编译错误时,我们就无法理解代码到底为什么会产生编译错误。
实际上对于printf的格式化字符串,还有整数的格式化输出的问题。比如:
printf("%d %d %d", (short)1, (char)2, 3);
这又是一个C语言入门者就已经“熟练掌握”的代码。这其中同样存在前面关于%f类似的问题。printf怎么知道后面的3个参数的实际类型呢?要回答这个问题,同样需要理解前面提到的默认参数类型提升的概念。默认参数类型提升不仅对于float类型有效,对于char和short类型,同样会提升,不同在于前者执行的是浮点数类型提升,后者执行的是整数类型提升。因此,char和short类型都会先转化为int类型,再传递给printf函数。这样printf只需要把%d这个格式化字符串对应的实参理解为是int类型即可,这就解决了所有的实参类型的识别的问题。
一个简单的printf函数,背后竟然藏着这么一个神奇的技术概念,而且这个概念还是在C语言标准中明确描述的。现在,你了解了上面那个printf语句为什么能够正常运作了吗?
现在,你还认为printf这个库函数很简单吗?你还自信满满的认为自己精通C语言吗?
你真的很了解printf函数吗?的更多相关文章
- C 中 关于printf 函数中度剖析
题外话 这篇博文主要围绕printf函数分析的,主要讲解printf 使用C的可变参数机制, printf是否可重入(是否线程安全), printf函数的源码实现. 正文 1.C中可变参数机制 我们 ...
- 可变参数列表与printf()函数的实现
问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...
- 三,对于printf函数和C语言编程的初步拓展
前面说过了,任何程序都要有输出,所以printf函数是一个很重要的函数,所以有必要在学变量之前先拓展一下. 其实编程就是用计算机语言说话,一句一句地说,只要语法没错就能运行,至于能实现什么功能,就看编 ...
- 【stm32】实现STM32的串口数据发送和printf函数重定向
在调试电机驱动程序的时候,是不能随便利用中断来进行一些寄存器或数据的查看的,不然你在运行的时候突然来一下,如果占空比大的话那可能直接就把MOS管给烧了,所以我们很多情况下只能使用USART(串口)来进 ...
- 考察printf函数返回值
最近偶然间见了这样一道题: #include<stdio.h> int main() { ; printf("%d\n",printf("%d", ...
- 初识C(2)---从printf函数开始
继承[K&R]的传统,我们的第一个C语言程序也是“Hello, World.”. 书写C语言程序的大前提:C语言中的语法符号必须都是英文字符,即在中文输入法关闭状态下输入的字符. 例 1. H ...
- [转]printf 函数实现的深入剖析
研究printf的实现,首先来看看printf函数的函数体 int printf(const char *fmt, ...) { int i; char buf[256]; va_l ...
- 【C语言】浅谈可变参数与printf函数
一.何谓可变参数 int printf( const char* format, ...); 这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用& ...
- printf函数压栈解惑
最近看到一些程序员的笔试题目,经常会考到printf函数的参数压栈问题,总体来讲就是参数从右向左依次压栈,再出栈,但是今天看到一个看似很简单的题目,却一直找不到头绪.题目如下: #include &l ...
随机推荐
- 深入剖析 RocketMQ 源码 - 负载均衡机制
RocketMQ作为一款流行的消息中间件在各大互联网应用广泛,本文主要分析RocketMq在消息生产和消费过程中的负载均衡机制,并创新提出消费端负载均衡策略的改写以实现固定IP消费的可能.
- echarts饼图禁止鼠标悬浮区块突出
禁止悬浮突出,在series内添加hoverAnimation:false即可 代码如下: option = { color:['#3498db','#EEEEEE'], series: [ { na ...
- [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇
[源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 目录 [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 1. ...
- ArcGIS使用技巧(七)——批量导出
新手,若有错误还请指正! 在ArcGIS中如何将栅格数据批量导出?用到"复制栅格这个工具",这里我用的例子是:将ArcGIS默认输出的DEM文件夹批量导出为tif格式.(如果是文件 ...
- python基础练习题(题目 画圈,学用circle画圆形。)
day37 --------------------------------------------------------------- 实例056:画圈 题目 画图,学用circle画圆形. 分析 ...
- Envoy熔断限流实践(一)基于Rainbond插件实现熔断
Envoy 可以作为 Sevice Mesh 微服务框架中的代理实现方案,Rainbond 内置的微服务框架同样基于 Envoy 实现.本文所描述的熔断实践基于 Rainbond 特有的插件机制实现. ...
- 同时将代码备份到Gitee和GitHub
同时将代码备份到Gitee和GitHub 如何将GitHub项目一步导入Gitee 如何保持Gitee和GitHub同步更新 如何将GitHub项目一步导入Gitee 方法一: 登陆 Gitee 账号 ...
- linux下can调试工具canutils安装与使用
0. 编译环境所需要的工具 libsocketcan-0.0.11.tar.bz2 canutils-4.0.6.tar.bz2 下载路径 https://public.pengutronix.de ...
- 编写引入svg
SVG是一种XML语言,类似XHTML,可以用来绘制矢量图形,例如右面展示的图形.SVG可以通过定义必要的线和形状来创建一个图形,也可以修改已有的位图,或者将这两种方式结合起来创建图形.图形和其组成部 ...
- APP应用前端开发
1.开发手机APP前端要重视meta标签的编写: 2.注意HTML5标签在前端开发中的使用: 3.前端制作要舍弃CSS float属性(可flex布局),用绝对定位不利于页面布局的扩展: 4.APP前 ...