你真的很了解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 ...
随机推荐
- js刷新页面window.location.reload()
window.location.reload()刷新当前页面 window.parent.location.reload()刷新父亲对象(用于框架) opener.location.reload()刷 ...
- Sqlalchemy异步操作不完全指北
异步SQLAlchemy SQLAlchemy作为一款通用的Python Orm工具,在最近的版本也支持了异步操作.但网上很多资料都不是很齐全,API也不是很好查询的情况下,我便有了整理一份基础文档的 ...
- 如何利用PowerShell完成的Windows服务器系统安全加固实践和基线检测
0x00 前言简述 最近单位在做等保测评,由本人从事安全运维方面的工作(PS:曾经做过等保等方面的安全服务),所以自然而然的与信安的测评人员一起对接相关业务系统的检查,在做主机系统测评检查时发现了系统 ...
- python中一些元组知识
元组 Python 的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号 ( ),列表使用方括号 [ ]. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可. 实例(Pytho ...
- JavaWeb和WebGIS学习笔记(五)——使用OpenLayers显示地图
系列链接: Java web与web gis学习笔记(一)--Tomcat环境搭建 Java web与web gis学习笔记(二)--百度地图API调用 JavaWeb和WebGIS学习笔记(三)-- ...
- CRUSE: Convolutional Recurrent U-net for Speech Enhancement
CRUSE: Convolutional Recurrent U-net for Speech Enhancement 本文是关于TOWARDS EFFICIENT MODELS FOR REAL-T ...
- CSS 字体超出 省略 ... 展示
效果 /* 标题 */ .title_t{ color: #000000; font-size: 130%; display: inline-block; line-height: 30px; wid ...
- XCTF练习题---WEB---robots
XCTF练习题---WEB---robots flag:cyberpeace{6c4b08933075fc620d16d1157ee07a7e} 解题步骤: 1.观察题目,打开场景 2.打开实验场景, ...
- CSS预编译器
零.CSS预编译器 CSS预处理器是指对生成CSS前的某一语法的处理.CSS预处理器用一种专门的编程语言,进行Web页面样式设计,然后再编译成正常的CSS文件,供项目使用 CSS预处理器为CSS增加一 ...
- springcloud + nacos实现共用基础服务(灰度版本)
背景: 当我们使用微服务时,若想在本地联调就需要启动多个服务,为了避免本地启动过多服务,现将注册中心等基础服务共用.当我们在服务A开发时,都是注册到同一个nacos,这样本地和开发环境的服务A就会同时 ...