7.9支持整数的格式化
printf()和sprintf()区别
printf()
函数有以下特点:
- 支持格式化字符串,可以按照一定的格式输出不同类型的数据;
- 输出结果直接显示在控制台上,不需要使用其他的输出语句;
- 输出结果无法保存到变量中,只能直接显示在控制台上。
例如:
#include <stdio.h>
int main() {
int num = 123;
float pi = 3.14159;
char str[] = "Hello, world!";
printf("The number is %d, the pi is %.2f, and the string is %s.\n", num, pi, str);
return 0;
}
sprintf()
函数有以下特点:
- 支持格式化字符串,可以按照一定的格式输出不同类型的数据;
- 输出结果保存到字符数组中,可以被用于输出或者存储;
- 输出结果不会直接显示在控制台上,需要使用其他的输出语句输出。
例如:
#include <stdio.h>
int main() {
int num = 123;
float pi = 3.14159;
char str[50];
sprintf(str, "The number is %d, the pi is %.2f.", num, pi);
printf("%s\n", str);
return 0;
}
从上面的例子可以看出,sprintf()
函数可以将格式化字符串的输出结果保存到字符数组中,并且可以通过其他的输出语句输出这个字符数组。
格式化字符串到缓存中
void kernel_sprintf(char * buffer, const char * fmt, ...) {
va_list args;
va_start(args, fmt);
kernel_vsprintf(buffer, fmt, args);
va_end(args);
}
对之前的kernel_vsprintf(char * buffer, const char * fmt, va_list args)函数添加代码,使其能够格式化4种进制的整数
void kernel_vsprintf(char * buffer, const char * fmt, va_list args) {
enum {NORMAL, READ_FMT} state = NORMAL;
char ch;
char * curr = buffer;
while ((ch = *fmt++)) {
switch (state) {
// 普通字符
case NORMAL:
if (ch == '%') {
state = READ_FMT;
} else {
*curr++ = ch;
}
break;
// 格式化控制字符,只支持部分
case READ_FMT:
if (ch == 'd') { //需要把10进制的数字格式化为字符串
int num = va_arg(args, int);
kernel_itoa(curr, num, 10);
curr += kernel_strlen(curr);
} else if (ch == 'x') {//需要把16进制的数字格式化为字符串
int num = va_arg(args, int);
kernel_itoa(curr, num, 16);
curr += kernel_strlen(curr);
} else if (ch == 'c') {
char c = va_arg(args, int);
*curr++ = c;
} else if (ch == 's') {
const char * str = va_arg(args, char *);
int len = kernel_strlen(str);
while (len--) {
*curr++ = *str++;
}
}
state = NORMAL;
break;
}
}
}
将int转化为字符串函数
void kernel_itoa(char * buf, int num, int base) {
// 转换字符索引[-15, -14, ...-1, 0, 1, ...., 14, 15]
static const char * num2ch = {"FEDCBA9876543210123456789ABCDEF"}; //因为可能有负数所以这样设计
char * p = buf;
int old_num = num;
// 仅支持部分进制
if ((base != 2) && (base != 8) && (base != 10) && (base != 16)) {
*p = '\0';
return;
} //只允许这4种进制,但是我们只写了10进制和16进制,但是底下代码都通用
// 只支持十进制负数
int signed_num = 0;//最后逆序,如果是负数(10进制的负数)则需要逆序,需要跳过那个符号
if ((num < 0) && (base == 10)) {
*p++ = '-';
signed_num = 1;
}
if (signed_num) {
do {
char ch = num2ch[num % base + 15];
*p++ = ch;
num /= base;
} while (num);
} else {
uint32_t u_num = (uint32_t)num;
do {
char ch = num2ch[u_num % base + 15];
*p++ = ch;
u_num /= base;
} while (u_num);
}
*p-- = '\0'; //最后加这个自己没有搞清楚
// 将转换结果逆序,生成最终的结果
char * start = (!signed_num) ? buf : buf + 1;
while (start < p) {
char ch = *start;
*start = *p;
*p-- = ch;
start++;
} //利用双指针逆序
}