对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函数吗?的更多相关文章

  1. C 中 关于printf 函数中度剖析

    题外话  这篇博文主要围绕printf函数分析的,主要讲解printf 使用C的可变参数机制, printf是否可重入(是否线程安全), printf函数的源码实现. 正文 1.C中可变参数机制 我们 ...

  2. 可变参数列表与printf()函数的实现

    问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...

  3. 三,对于printf函数和C语言编程的初步拓展

    前面说过了,任何程序都要有输出,所以printf函数是一个很重要的函数,所以有必要在学变量之前先拓展一下. 其实编程就是用计算机语言说话,一句一句地说,只要语法没错就能运行,至于能实现什么功能,就看编 ...

  4. 【stm32】实现STM32的串口数据发送和printf函数重定向

    在调试电机驱动程序的时候,是不能随便利用中断来进行一些寄存器或数据的查看的,不然你在运行的时候突然来一下,如果占空比大的话那可能直接就把MOS管给烧了,所以我们很多情况下只能使用USART(串口)来进 ...

  5. 考察printf函数返回值

    最近偶然间见了这样一道题:  #include<stdio.h> int main() { ; printf("%d\n",printf("%d", ...

  6. 初识C(2)---从printf函数开始

    继承[K&R]的传统,我们的第一个C语言程序也是“Hello, World.”. 书写C语言程序的大前提:C语言中的语法符号必须都是英文字符,即在中文输入法关闭状态下输入的字符. 例 1. H ...

  7. [转]printf 函数实现的深入剖析

    研究printf的实现,首先来看看printf函数的函数体 int printf(const char *fmt, ...) { int i; char buf[256];          va_l ...

  8. 【C语言】浅谈可变参数与printf函数

    一.何谓可变参数 int printf( const char* format, ...); 这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用& ...

  9. printf函数压栈解惑

    最近看到一些程序员的笔试题目,经常会考到printf函数的参数压栈问题,总体来讲就是参数从右向左依次压栈,再出栈,但是今天看到一个看似很简单的题目,却一直找不到头绪.题目如下: #include &l ...

随机推荐

  1. 夯实基础上篇-图解 JavaScript 执行机制

    讲基础不易,本文通过 9 个 demo.18 张 图.2.4k 文字串讲声明提升.JavaScript 编译和执行.执行上下文.调用栈的基础知识.

  2. 对比学习 ——simsiam 代码解析。

    ​  目录 1 : 事先准备 . 2 : 代码阅读. 2.1: 数据读取 2.2: 模型载入 3 训练过程: 4 测试过程: 5 :线性验证 6 : 用自己数据集进行对比学习. 第一:  改数据集 : ...

  3. element.insertAdjacentHTML

    一.概念 insertAdjacentHTML() 方法将指定的文本解析为 Element 元素,并将结果节点插入到DOM树中的指定位置.它不会重新解析它正在使用的元素,因此它不会破坏元素内的现有元素 ...

  4. Java基础语法Day_05(数组的概念)

    第14节 数组         day05_01_数组的概念 day05_02_数组的定义格式一_动态初始化 day05_03_数组的定义格式二_静态初始化 day05_04_数组的定义格式三_省略的 ...

  5. SQL注入绕过总结

    花括号绕过 select{x password}from{database.user} union select 1,{x 2},3 特征字符大小写绕过 UniOn SEleCt 1,2,3 MYSQ ...

  6. React 父组件调用子组件的方法

    父组件调用子组件的方法 React v16.3.0 及以后版本使用 import React, {Component} from 'react'; export default class Paren ...

  7. 论文解读(IGSD)《Iterative Graph Self-Distillation》

    论文信息 论文标题:Iterative Graph Self-Distillation论文作者:Hanlin Zhang, Shuai Lin, Weiyang Liu, Pan Zhou, Jian ...

  8. ucore lab6 调度管理机制 学习笔记

    这节虽叫调度管理机制,整篇下来主要就讲了几个调度算法.兴许是考虑到LAB5难,LAB6就仁慈了一把,难度大跳水.平常讲两节原理做一个实验,这次就上了一节原理.权当大战后的小憩吧. schedule函数 ...

  9. 批量安装Windows系统

    今天我们利用Windows server 2019自带的Windows部署服务通过网络批量安装Win 10 一.Windows服务 1)WDS WDS(Windows Deployment Servi ...

  10. 攻防世界web进阶题—bug

    攻防世界web进阶题-bug 1.打开题目看一下源码,没有问题 2.扫一下目录,没有问题 3.查一下网站的组成:php+Apache+Ubuntu 只有登录界面 这里可以可以想到:爆破.万能密码.进行 ...