printf函数:
int printf(const char *format, ...)
它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。
format 标签属性是 %[flags][width][.precision][length]specifier,如下:
格式字符 | 意义 |
---|
d | 以十进制形式输出带符号整数(正数不输出符号) |
o | 以八进制形式输出无符号整数(不输出前缀0) |
x,X | 以十六进制形式输出无符号整数(不输出前缀Ox) |
我们可以看到printf的format支持十进制、8进制、16进制,就是没有二进制,所以嵌入式C开发中经常会遇到位操作,debug时为了方便查看结果,需要二进制查看,我们看一下如何打印2进制。
首先我们看一下一个数据如何换算成二进制:
一个整数不断的除以2,每次得到的余数即构成了二进制数,所以这里首先想到使用递归来实现。
void print_binary(unsigned int number) {
if (number >> 1) {
print_binary(number >> 1);
}
putc((number & 1) ? '1' : '0', stdout);
}
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;
}
有这么一个函数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的实现,大家可以研究一下:
- stdio-common/_itoa.c
- sysdeps/generic/_itoa.h
我们看一下同济大学陈老师和他的精英学子们重构的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',后移一位添加结束符。
最后翻转字符串。
题外话:可以看到这里有部分代码使用了驼峰,和其他部分有风格混搭,我们在代码工程中需要坚持一种风格。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)