自己动手写C语言格式化输出函数(二)

上接《自己动手写C语言格式化输出函数(一)》 。

三、格式化字符及字符串。

// 宽字符串转换ANSI字符串。参数:ANSI字符串,宽字符串,转换字符数(0不转换)。 // 返回实际转换字符个数 static INT WStrToStr(LPSTR dst, LPCWSTR src, INT count) { return WideCharToMultiByte(CP_THREAD_ACP, 0, src, -1, dst, count > 0? count + 1: 0, NULL, NULL) - 1; } // 格式化字符。参数:缓冲区,格式记录。返回缓冲区尾偏移 static LPSTR FormatCharA(LPSTR buffer, FormatRec *rec) { INT len, spaces; LPSTR p = buffer; if (rec->type == TYPE_LONG) { len = WStrToStr(NULL, (LPCWSTR)rec->param, 0); if (len == 0) len = sizeof(CHAR); } else len = sizeof(CHAR); spaces = rec->width - len; if (rec->left == FALSE && spaces > 0) { memset(p, CHAR_SPACE, spaces); p += spaces; } if (rec->type == TYPE_LONG) { WStrToStr(p, (LPCWSTR)rec->param, len); p += len; } else *p ++ = *(LPCSTR)rec->param; if (rec->left == TRUE && spaces > 0) { memset(p, CHAR_SPACE, spaces); p += spaces; } rec->param += rec->type == TYPE_LONG? TS_WCHAR : TS_CHAR; return p; } // 格式化字符串。参数:缓冲区,格式记录。返回缓冲区尾偏移 static LPSTR FormatStrA(LPSTR buffer, FormatRec *rec) { INT len, spaces; LPSTR p = buffer; if (rec->type == TYPE_LONG) len = WStrToStr(NULL, *(LPCWSTR*)rec->param, 0); else len = lstrlenA(*(LPCSTR*)rec->param); if (rec->precision >= 0 && len > rec->precision) len = rec->precision; spaces = rec->width - len; if (rec->left == FALSE && spaces > 0) { memset(p, CHAR_SPACE, spaces); p += spaces; } if (rec->type == TYPE_LONG) WStrToStr(p, *(LPCWSTR*)rec->param, len); else memcpy(p, *(LPCSTR*)rec->param, len); p += len; if (rec->left == TRUE && spaces > 0) { memset(p, CHAR_SPACE, spaces); p += spaces; } rec->param += TS_PTR; return p; }

如果不涉及宽字符,格式化字符和字符串是很简单的。

对于字符和字符串,"%lc"和"%ls"表示宽字符和宽字符串,其它类型精度全部视为默认值,即ANSI字符和ANSI字符串。

宽字符的转换是由WStrToStr函数来完成的,而WStrToStr又是调用的Windows API函数WideCharToMultiByte,

在格式化字符0时,C语言的printf和sprintf有所不同,前者是用空格替代的。例如:printf("%s%c456", "123", 0),显示出来是“123 456",而sprintf(s, "%s%c456", "123", 0)后,s="123",因此,sprintfA也就是s="123"。

四、格式化整型数。

// 格式化数字串。参数:缓冲区,格式记录,数字串,数字串长度。返回缓冲区尾偏移 static LPSTR FormatDigitsA(LPSTR buffer, FormatRec *rec, LPCSTR digits, INT len) { LPSTR p = buffer; INT spaces; if (rec->precision >= 0) rec->zero = FALSE; rec->precision -= len; if (rec->precision < 0) rec->precision = 0; spaces = rec->width - len - rec->precision; if (rec->negative) { spaces --; if (rec->left || rec->zero) *p ++ = (rec->negative == -1? CHAR_NEG : CHAR_POS); } if (rec->left == FALSE) { if (spaces > 0) { memset(p, rec->zero? CHAR_ZERO : CHAR_SPACE, spaces); p += spaces; } if (rec->negative && !rec->zero && !rec->decimals) *p ++ = (rec->negative == -1? CHAR_NEG : CHAR_POS); } if (rec->precision != 0) { memset(p, CHAR_ZERO, rec->precision); p += rec->precision; } memcpy(p, digits, len); p += len; if (rec->left == TRUE && spaces > 0) { memset(p, CHAR_SPACE, spaces); p += spaces; } return p; } // 整型数转换为数字串。参数:数字串,整型数,是否无符号整数 static INT IntToDigits(LPSTR digits, LONG src, BOOL *isUnsigned) { ULONG v; LPSTR p = digits + MAX_DIGITS_SIZE; if (*isUnsigned == FALSE && src < 0) src = -src; else *isUnsigned = TRUE; v = (ULONG)src; do { *(-- p) = (CHAR)(v % 10 + '0'); v /= 10; } while (v); return (INT)(MAX_DIGITS_SIZE - (p - digits)); } static INT LLongToDigits(LPSTR digits, LLONG src, BOOL *isUnsigned) { ULLONG v; LPSTR p = digits + MAX_DIGITS_SIZE; if (*isUnsigned == FALSE && src < 0) src = -src; else *isUnsigned = TRUE; v = (ULLONG)src; do { *(-- p) = (CHAR)(v % 10 + '0'); v /= 10; } while (v); return (INT)(MAX_DIGITS_SIZE - (p - digits)); } static INT numSizes[] = {sizeof(CHAR), sizeof(SHORT), sizeof(INT), sizeof(LONG), sizeof(LLONG)}; // 格式化整型数。参数:缓冲区,格式记录,是否无符号整数。返回缓冲区尾偏移 static LPSTR FormatIntA(LPSTR buffer, FormatRec *rec, BOOL isUnsigned) { ULONG value; INT len; CHAR digits[MAX_DIGITS_SIZE]; if (isUnsigned) rec->negative = 0; if (numSizes[rec->type] <= TS_PTR) { value = *(PULONG)rec->param; if (isUnsigned) value &= ((ULONG)(-1) >> ((TS_PTR - numSizes[rec->type]) << 3)); len = IntToDigits(digits, value, &isUnsigned); } else len = LLongToDigits(digits, *(PLLONG)rec->param, &isUnsigned); if (!isUnsigned) rec->negative = -1; rec->param += TypeSize(numSizes[rec->type]); rec->decimals = 0; return FormatDigitsA(buffer, rec, &digits[MAX_DIGITS_SIZE - len], len); }

在C的基本数据中,整型数的表达范围是最“与时俱进”的。16位编译器时,int是2字节,long为4字节;而32编译器下,int和long都变成了4字节,另外多了个8字节的_int64类型;64位编译器下,int仍然是4字节,long成了8字节,是否会有个16字节的_int128?我没用过64位编译器,不知道。代码中定义了一个LLONG类型,并写了2个整型数转换字符串函数,凡是小于或等于指针长度范围的整型数,使用IntToDigits函数,否则使用LLongToDigits函数。从表面看,这2个函数除数据类型不同外,语句是一样的,但编译后,前者的速度要快。如果是写商用的函数,建议还是使用插入汇编进行转换,因为汇编只作一个除法,就可的到商和余数,而高级语言需作2个除法。

有些C语言格式化输出函数在整型数转换时,是忽略hh(或者H)精度的,也就是说整型数转换的最小精度为sizeof(SHORT),而sprintfA的整型数的最小精度为sizeof(CHAR)。比如"%hhu", -123,前者输出是65413,而后者却是133。如果把代码中numSizes数组的第一个元素改为sizeof(SHORT),sprintfA也会忽略hh(或者H)精度。

五、整型数格式化为十六进制和八进制数字串。

static CHAR hexDigitsU[] = "0123456789ABCDEF"; static CHAR hexDigitsL[] = "0123456789abcdef"; // 整型数转换为十六进制串。参数:十六进制串,整型数,字节长度,转换精度,是否大写 static INT NumberToHexA(LPSTR hex, LPCVOID lpNumber, INT bytes, INT precision, BOOL upper) { LPSTR ph = hex; LPBYTE pn = (LPBYTE)lpNumber; LPSTR hexDigits; INT len; for (bytes --; bytes > 0 && pn[bytes] == 0; bytes --); pn += bytes; bytes ++; len = bytes * 2; if ((*pn & 0xf0) == 0) len --; if (hex == NULL) return precision > len? precision : len; for (precision -= len; precision > 0; *ph ++ = '0', precision --); hexDigits = upper? hexDigitsU : hexDigitsL; if ((*pn & 0xf0) == 0) { *ph ++ = hexDigits[*pn -- & 0x0f]; bytes --; } for (; bytes > 0; bytes --, pn --) { *ph ++ = hexDigits[*pn >> 4]; *ph ++ = hexDigits[*pn & 0x0f]; } return (INT)(ph - hex); } // 按十六进制格式化整型数。参数:缓冲区,格式记录,类型字符(x or X) static LPSTR FormatHexA(LPSTR buffer, FormatRec *rec, CHAR hexChar) { LPSTR p = buffer; INT spaces, len, pix; BOOL upper = hexChar == 'X'; if (rec->precision >= 0) rec->zero = FALSE; pix = rec->decimals? 2 : 0; rec->precision -= pix; len = NumberToHexA(NULL, rec->param, numSizes[rec->type], rec->precision, upper); spaces = rec->width - len - pix; if (rec->decimals && (rec->left || rec->zero)) { memcpy(p, rec->decimals > 0? HEX_PREFIX_U : HEX_PREFIX_L, 2); p += 2; } if (rec->left == FALSE) { if (spaces > 0) { memset(p, rec->zero? CHAR_ZERO : CHAR_SPACE, spaces); p += spaces; } if (rec->decimals && !rec->zero) { memcpy(p, rec->decimals > 0? HEX_PREFIX_U : HEX_PREFIX_L, 2); p += 2; } } p += NumberToHexA(p, rec->param, numSizes[rec->type], rec->precision, upper); if (rec->left == TRUE && spaces > 0) { memset(p, CHAR_SPACE, spaces); p += spaces; } rec->param += TypeSize(numSizes[rec->type]); return p; } // 整型数转换为八进制串。参数:八进制串,整型数,字节长度 static INT NumberToOtcalA(LPSTR otcal, LPCVOID lpNumber, INT bytes) { LPSTR p = otcal + MAX_DIGITS_SIZE; ULLONG v = 0; memcpy(&v, lpNumber, bytes); do { *(-- p) = (CHAR)((v & 7) + '0'); v >>= 3; } while (v); return (INT)(MAX_DIGITS_SIZE - (p - otcal)); } // 按八进制格式化整型数。参数:缓冲区,格式记录 static LPSTR FormatOctalA(LPSTR buffer, FormatRec *rec) { CHAR otcal[MAX_DIGITS_SIZE]; INT len = NumberToOtcalA(otcal, rec->param, numSizes[rec->type]); rec->param += TypeSize(numSizes[rec->type]); rec->negative = 0; return FormatDigitsA(buffer, rec, &otcal[MAX_DIGITS_SIZE - len], len); }

整型数转换为十六进制或者八进制数字串,除了进制不同,其它与前面整型数转换为10进制数是一样的。

六、格式化指针。

// 按十六进制格式化指针。参数:缓冲区,格式记录 static LPSTR FormatPointerA(LPSTR buffer, FormatRec *rec) { INT prec = PTR_SIZE << 1; CHAR tmp[PTR_SIZE * 2]; NumberToHexA(tmp, rec->param, TS_PTR, prec, TRUE); rec->precision = -1; // 忽略精度 return FormatDigitsA(buffer, rec, tmp, prec); }

因为指针地址同样也是个整型数,所以指针的格式化和整型数转换为十六进制数字串是一样的,只不过精度是固定的,32位编译器下为8位十六进制数,64位编译器下则为16位十六进制数。

七、获取缓冲区当前位置字节数。

// 获取缓冲区当前位置字节数。参数:缓冲区,缓冲区首地址,格式记录 static VOID GetPosSizeA(LPSTR buffer, LPSTR buffer0, FormatRec *rec) { LLONG size = buffer - buffer0; memcpy((LPVOID)*(PLONG*)rec->param, &size, numSizes[rec->type]); rec->param += TS_PTR; }

这是格式化输出函数中最特殊的输出,它不是把某个参数的值输出到缓冲区,而是把输出缓冲区当前位置的长度输出到某个参数,这个参数必须是指针形式的。

同整型数转换为数字串一样,sprintfA确认的最小数据精度为sizeof(CHAR),也可以改变为sizeof(SHORT)。

未完待续......

声明:本文代码主要供学习使用,如作其它用途,出问题慨不负责。

水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值