C语言小知识---printf()函数转换符的意义

286 阅读6分钟

  小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

  本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

  printf()函数大家已经很熟悉了,它的转换符在打印数据的时候也会经常使用,比如%c,%d,%f等。那么为什么打印的时候一定需要转换符呢?系统难道不能自动识别吗?转换符存在的意义又是什么?

  下面就通过一个简单的例子来看一下,转换符存在的意义,在8位单片机上通过不同的转换符打印两个整数。

image.png

通过不同的转换符打印两个整数,一个正整数,一个负整数。核心代码如下:

short num1 = 666;
short num2 = -32700;

 printf( "num1: short --- %6hd, unsigned short --- %6hu \r\n", num1, num1 );
 printf( "num2: short --- %6hd, unsigned short --- %6hu \r\n", num2, num2 );

 printf( "num1: int   --- %6d, char --- %6c \r\n", num1, num1 );
 printf( "num2: int   --- %6d, char --- %6c \r\n\r\n\r\n", num2, num2 );

在打印结果之前,先看一下short类型的数据范围是多少。

image.png

在8位单片机中short数据类型的范围是 -32768 到 32768,程序中的正整数为666,负整数为-32700,说明程序中的数据都在有效数据范围内。

下面看程序打印的结果

image.png

通过short和unsigned short打印num1时,输出的结果是正确的。但是通过unsigned short打印num2时输出的结果是32836,。通过字符型打印两个数字的时候,都出现了一个字符。那么这些打印错误的结果是从哪里来的?为什么又是这样的结果呢?

直接在内存中看看,这两个数字是如何存储的。

image.png

通过直接查看单片机的内存可以发现,数字666存储的位置是在0x00000C位置开始的,数字-32700 存储的位置是从 0x00000E位置开始的,short类型是16位,占两个字节。所以内存中 02 和 9a 就代表的是数字666,80和44就代表数字-32700.

下面通过进制转换看看这两个数字对应的16进制数字是什么?

image.png 数字666对应的16进制数字的原码、反码、补码,都是029A。

image.png 数字-32700 对应的16进制补码是8044,难道负整数在内存中存储的是它的补码吗?

确实是这样的,对于有符号的整数,系统是通过二进制的补码来表示。数字 0 --- 32767 代表它们本身,而数字32768 --- 65535 则表示负数,其中65535表示-1,65534表示-2。所以-32700对应保存的数字就是 65536-32767=32836。

image.png 通过进制转换可以看出,32836对应的16进制刚好是8044。

  当通过转换符“%hu”告诉printf函数要打印一个无符号的十进制整数时,系统检测到内存中存贮的这个32836刚好在无符号整数范围内,所以就直接打印出来了。 而当通过转换符“%hd”告诉printf函数要打印一个有符号的十进制整数时,系统检测到内存中存储的这个32836大于有符号整数的最大值32767,那么系统就知道了,这个数字肯定是个负数,它是以反码存储的,那么它的正确值就应该是-(65536-32836)=-32700。所以通过“%hd”转换符打印出来的就是-32700,而通过“%hu”打印出来的就是32836。

  那么后面的通过“%c”打印出来的这个字符又是怎么来的呢? 在系统中当使用“%c”的时候,系统就会认为这是一个字符设备,那么它的值范围就是一个ASCII码表,也就是0 - 255,如果这个值超过了255,系统就会用这个数字对256取余,也就是在内存中只取两个字节的后一个字节。

image.png

这样系统在通过“%c”打印数字666的时候,就只取内存中的9a,打印-32700的时候,就只取内存中的44。这两个数字对应的ASCII码表是多少呢?

image.png

9a对应的字符是 š,44对应的字符是 D,但是9a对应的这个字符在串口工具上打印不出来,所以打印的是?,44对应的是字母D,所以打印出来的就是字母D。

  通过上面的例子可以看出,printf()函数在内存中读取数据的时候,是通过转换符来判断要读取几位数据,该从内存中哪个位置去取数据,如果转换符设置的不正确,那么printf()函数去内存中读取数据的时候就会读错。

下面再通过一个简单的例子测试一下,测试代码如下:

int n1=0x0102;
int n2=0x0304;
int n3=0x0506;
int n4=0x0708;

printf( "%ld,%ld,%ld,%ld \r\n\r\n\r\n", n1,n2,n3,n4 );

这个例子很简单,定义了四个整数,这四个整数各占两个字节,但是打印数据的时候,使用“%ld”,也就是一个字符占4个字节。输出结果如下:

image.png

打印的数字都非常大,那么这些数字是怎么来的?直接去内存中查看。

image.png

在内存中可以看出01到08依次在内存中按顺序存贮,如果使用“%ld”打印的时候,那么print()函数在读取n1的值时,就会直接在内存中取出4个位作为n1的值,也就是取出n1的值是 0x01020304,可以使用进制转换查看一下。 image.png

16909060转换成你的16进制果然是01020304

对应的n2取出来的值就是0x05060708 image.png

84281096转换成16进制果然是05060708

通过这个例子可以明显的看出,转换符对于printf()函数读取数据是非常重要的。转换符如果设置的不对,那么读取的数据肯定就会出现错误。