printf如何按⼆进制格式打印
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进制。
⾸先我们看⼀下⼀个数据如何换算成⼆进制:
printf怎么输出字符⼀个整数不断的除以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',后移⼀位添加结束符。
最后翻转字符串。
题外话:可以看到这⾥有部分代码使⽤了驼峰,和其他部分有风格混搭,我们在代码⼯程中需要坚持⼀种风格。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论