printf如何按二进制格式打印

本文介绍了在C语言中打印二进制数的三种方法:递归实现、宏定义打印和itoa函数转换。重点讨论了itoa函数的实现原理,包括在不同库中的实现方式,并强调了代码风格的一致性在工程中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

printf函数:
int printf(const char *format, ...)
  • format -- 这是字符串,包含了要被写入到标准输出 stdout 的文本。

                      它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。

                      format 标签属性是 %[flags][width][.precision][length]specifier,如下:

格式字符意义
d以十进制形式输出带符号整数(正数不输出符号)
o以八进制形式输出无符号整数(不输出前缀0)
x,X以十六进制形式输出无符号整数(不输出前缀Ox)

我们可以看到printf的format支持十进制、8进制、16进制,就是没有二进制,所以嵌入式C开发中经常会遇到位操作,debug时为了方便查看结果,需要二进制查看,我们看一下如何打印2进制。

首先我们看一下一个数据如何换算成二进制:

 

一个整数不断的除以2,每次得到的余数即构成了二进制数,所以这里首先想到使用递归来实现。

  • 方法1: 递归实现
void print_binary(unsigned int number) {
    if (number >> 1) {
        print_binary(number >> 1);
    }
    putc((number & 1) ? '1' : '0', stdout);
}
  • 方法2:利用宏定义打印

PRINTF_BYTE_TO_BINARY_INT8直接依次从高到低位取出数据变成字符使用%c打印,如果16位,则调用2次PRINTF_BYTE_TO_BINARY_INT8,高位右移8位按照PRINTF_BYTE_TO_BINARY_INT8打印,低位直接打印,32位和64位依次类推。

 

这个算法思路简单清晰,相比上面的递归,效率很高,值得推荐。

 

/* --- PRINTF_BYTE_TO_BINARY macro's --- */
#define PRINTF_BINARY_PATTERN_INT8 "%c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i)    \
    (((i) & 0x80ll) ? '1' : '0'), \
    (((i) & 0x40ll) ? '1' : '0'), \
    (((i) & 0x20ll) ? '1' : '0'), \
    (((i) & 0x10ll) ? '1' : '0'), \
    (((i) & 0x08ll) ? '1' : '0'), \
    (((i) & 0x04ll) ? '1' : '0'), \
    (((i) & 0x02ll) ? '1' : '0'), \
    (((i) & 0x01ll) ? '1' : '0')

#define PRINTF_BINARY_PATTERN_INT16 \
    PRINTF_BINARY_PATTERN_INT8              PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) \
    PRINTF_BYTE_TO_BINARY_INT8((i) >> 8),   PRINTF_BYTE_TO_BINARY_INT8(i)
#define PRINTF_BINARY_PATTERN_INT32 \
    PRINTF_BINARY_PATTERN_INT16             PRINTF_BINARY_PATTERN_INT16
#define PRINTF_BYTE_TO_BINARY_INT32(i) \
    PRINTF_BYTE_TO_BINARY_INT16((i) >> 16), PRINTF_BYTE_TO_BINARY_INT16(i)
#define PRINTF_BINARY_PATTERN_INT64    \
    PRINTF_BINARY_PATTERN_INT32             PRINTF_BINARY_PATTERN_INT32
#define PRINTF_BYTE_TO_BINARY_INT64(i) \
    PRINTF_BYTE_TO_BINARY_INT32((i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)
/* --- end macros --- */

#include <stdio.h>
int main() {
    long long int flag = 1648646756487983144ll;
    printf("My Flag "
           PRINTF_BINARY_PATTERN_INT64 "\n",
           PRINTF_BYTE_TO_BINARY_INT64(flag));
    return 0;
}

 

  • 方法3: itoa实现:

有这么一个函数itoa, 它将整数转化为字符串,它不是标准的C库函数,最早出现在Kernighan 和Ritchie《The C Programming Language》这本书中:

/* reverse:  reverse string s in place */
void reverse(char s[])
{
    int c, i, j;

    for (i = 0, j = strlen(s)-1; i < j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}

/* itoa:  convert n to characters in s */
void itoa(int n, char s[])
{
    int i, sign;

    sign = n;
    i = 0;
    do {        /* generate digits in reverse order */
        s[i++] = abs(n % 10) + '0';     /* get next digit */
    } while (n /= 10);                  /* delete it */
    if (sign < 0)
        s[i++] = '-';
    s[i] = '\0';
    reverse(s);
}

它只支持了十进制,它的思路很简单,就是对n求余,得到尾数,然后n舍弃尾数再求余又得到一个尾数,依次类推,每个尾数使用ASCII值转换为字符,即直接使用基数'0' 加上偏移即可。这样得到的字符串是一个逆序的,需要翻转一下。翻转函数就很简单了,就是一个循环,2个变量i,j 一个从头部递增,一个从尾部递减,交换值即可。

 

我们来看一下几个库对该函数的实现:

Windows的 c runtime Library (CRT)的stdlib库中实现了itoa函数,

char *  itoa ( int value, char * str, int base );

但遗憾的是并没有找到微软的这个库的源代码。

不过,在glibc 2.28版本中找到了itoa的实现,大家可以研究一下:

我们看一下同济大学陈老师和他的精英学子们重构的UNIX V6PP 中如何实现的:

char* _itoa( unsigned long value, int neg_sign, char* buffer, int radix)
{
	char* bret = buffer;
	unsigned int num = value;
	char* bufferStart = 0;
	
	if ( !buffer || radix <= 0 || radix > 16 ) 
		return 0;
	
	if ( neg_sign )
	{
		*buffer++ = '-';
	}
	
	bufferStart = buffer;
	*buffer = '0'; /* if num == 0 then ...*/
	while ( num ) 
	{
		char ch = num % radix;
		*buffer++ =  ch + ( ch < 10 ? '0' : 'a' - 10 );
		num /= radix;
	}
	if ( value ) *buffer = 0;
	else *++buffer = 0;

	buffer--;
	while ( bufferStart < buffer ) /* reserve the string */
	{
		char tch = *bufferStart;
		*bufferStart = *buffer;
		*buffer = tch;
		bufferStart++;
		buffer--;
	}
	return bret;	
}

char* itoa( long value, char* buffer, int radix )
{
	int s =  value < 0 ? 1 : 0;
	if ( s ) value = -value;
	return _itoa( value, s, buffer, radix );
}
char* uitoa( unsigned long value, char* buffer, int radix )
{
	return _itoa( value, 0, buffer, radix );
}

我们可以看到他们移植重构的这段代码,思路和《The C Programming Language》这本书中的思路是一致的,不过他们支持了多进制,算法简单,逻辑清晰。

我们可以看到代码中使用了一个bufferStart指针指向buffer,首先给了一个初始值,保证num为0时能够正确输出;

然后在while循环中利用进制转换方式,即对该进制取余得到尾部数据,然后该进制的基数字符加上得到的余数作为偏移,就可以得到ASCII的字符;

这里16进制以上超出了10,ASCII表中0-9和a-f并不连续,所以转换为16进制时如果得到的尾数>=10的需要以'a' - 10作为基数;

同理不断移除尾部数据得到尾部字符,直到数据为0;

如果要转换的数据大于0,则直接添加结束符,如果数据等于0,保留初始值'0',后移一位添加结束符。

最后翻转字符串。

题外话:可以看到这里有部分代码使用了驼峰,和其他部分有风格混搭,我们在代码工程中需要坚持一种风格。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值