printf系列函數,包括fprintf、sprintf函數等,其功能是將C語言的所有基本數據類型按用戶要求進行格式化輸出。
printf函數幾乎是所有學習C語言的人接觸到的第一個函數,是C語言標准中使用頻率最高的函數。
printf函數是C語言標准函數中最著名的可變參數函數,看見printf這個函數名,就想起了C語言的說法一點也不過分,因此,可以說是C語言標准函數中的最具標志性的函數。
printf系列函數。在DOS環境下,這一系列輸出函數涵蓋了PC機所能用到的所有輸出設備,所以printf系列函數也是C語言中最復雜的函數。
當然,隨着DOS時代的結束,不僅printf系列函數的作用減弱了,就連C語言本身也被壓縮到了最小的應用領域。
本文寫的sprintfA函數,也是應一個小友要求寫的幾個函數之一,包括我昨天發布的《自己動手寫C語言浮點數轉換字符串函數》中的FloatToStr函數,是用來學習用的。之所以取名為sprintfA,不僅是區別系統本身的sprintf函數,同時也因為在Windows下,A表示的是傳統的ANSI函數。因為在Windows下,printf系列函數也“與時俱進”了,如wprintf等就是在寬字符環境下的輸出函數。由於我在sprintfA函數中使用了Windows的寬字符轉換函數,因此該函數只適用於Windows環境。
由於sprintfA函數代碼比較長,將分為多篇文章發布,《自己動手寫C語言浮點數轉換字符串函數》一文中的代碼也應算作一篇:
一、數據定義:
- typedef struct
- {
- INT type; // 數據長度類型
- INT width; // 數據最小寬度
- INT precision; // 數據精度
- BOOL left; // 是否居左
- BOOL zero; // 是否前導零
- INT decimals; // 浮點數: 1強制小數位; 16進制: -1: 0x, 1: 0X
- INT negative; // 符號:-1: '-'; 1: '+'
- LPSTR param; // 參數指針
- }FormatRec;
- typedef long long LLONG, *PLLONG;
- typedef unsigned long long ULLONG, *PULLONG;
- #define TYPE_CHAR 0
- #define TYPE_SHORT 1
- #define TYPE_GENERAL 2
- #define TYPE_LONG 3
- #define TYPE_LLONG 4
- #define PTR_SIZE sizeof(VOID*)
- #define TypeSize(size) (((size + PTR_SIZE - 1) / PTR_SIZE) * PTR_SIZE)
- #define TS_PTR PTR_SIZE
- #define TS_CHAR TypeSize(sizeof(CHAR))
- #define TS_WCHAR TypeSize(sizeof(WCHAR))
- #define TS_SHORT TypeSize(sizeof(SHORT))
- #define TS_INT TypeSize(sizeof(INT))
- #define TS_LONG TypeSize(sizeof(LONG))
- #define TS_LLONG TypeSize(sizeof(LLONG))
- #define TS_FLOAT TypeSize(sizeof(FLOAT))
- #define TS_DOUBLE TypeSize(sizeof(double))
- #define TS_EXTENDED TypeSize(sizeof(EXTENDED))
- #define CHAR_SPACE ' '
- #define CHAR_ZERO '0'
- #define CHAR_POS '+'
- #define CHAR_NEG '-'
- #define HEX_PREFIX_U "0X"
- #define HEX_PREFIX_L "0x"
- #define MAX_DIGITS_SIZE 40
FormatRec是一個數據格式化結構,它包含了sprintfA格式化各種數據所需的基本信息。
TYPE_XXXX是數據類型標記,對應於FormatRec.type字段。
TS_XXXX是各種數據類型在sprintfA可變參數傳遞時所占的棧字節長度。除指針類型和INT類型長度直接用sizeof關鍵字確定棧字節長度外,其它數據類型所占棧長度則用TypeSize宏配合計算取得,這樣就使得這些數據所占棧字節長度在各種環境下都是正確的,比如字符型長度為1字節,TypeSizesizeof(CHAR)),在32位編譯環境時等於4,在64位編譯環境時則等於8。
對於帶任意類型可變參數的函數來說,實參數據類型的棧字節長度正確與否,完全取決於程序員。比如在sprintfA的格式參數中定義了%Ld,應該是個64位整數類型,而在對應的可變參數部分卻給了一個int類型,在32位編譯器環境下,就存在2個錯誤,一是數據類型不正確,二是棧字節長度不匹配,64位整數長度為8字節,而INT的長度卻只有4字節,其結果就是這個數據以及其后的所有數據都會出現錯誤的顯示結果,甚至還有可能造成程序崩潰。這也是一些C語言初學者在使用printf系列函數時最容易犯的錯誤,他們混淆了一般函數與帶可變參數函數調用的區別, 對於一般的C函數,形參的數據類型是固定的,在調用時,如果實參與形參數據類型不匹配,編譯器視情況會作出錯誤、警告或者轉換等處理,而對於不同精度的相同數據類型,編譯器大都會自動進行擴展或截斷;而調用帶可變參數函數時,由於函數原型的形參說明部分為“...”,編譯器就沒法將int擴展為_int64了。
另外,還有有關浮點數部分的數據定義在《自己動手寫C語言浮點數轉換字符串函數》。
二、函數主體。
- // 獲取字符串中的數字。參數:字符串,數字指針。返回字符串中最后一個數字位置
- static LPSTR GetControlNum(LPCSTR s, INT *value)
- {
- register LPCSTR p = s;
- register INT v;
- for (v = 0; *p >= '0' && *p <= '9'; p ++)
- v = v * 10 + (*p - '0');
- *value = v;
- return (LPSTR)(p - 1);
- }
- LPSTR _sprintfA(LPSTR buffer, LPCSTR format, ...)
- {
- FormatRec rec;
- BOOL flag;
- CHAR c;
- LPCSTR psave; // ?
- register LPCSTR pf = format;
- register LPSTR pb = buffer;
- va_list paramList;
- va_start(paramList, format);
- rec.param = (LPSTR)paramList;
- while (TRUE)
- {
- while (*pf && *pf != '%')
- *pb ++ = *pf ++;
- if (*pf == 0) break;
- if (*(pf + 1) == '%') // 處理%%
- {
- *pb ++ = '%';
- pf += 2;
- continue;
- }
- psave = pf; // ?
- rec.width = rec.decimals = rec.negative = 0;
- rec.left = rec.zero = FALSE;
- rec.type = TYPE_GENERAL;
- rec.precision = -1;
- // 解析前導符號
- flag = TRUE;
- while (flag)
- {
- pf ++;
- switch (*pf)
- {
- case '0':
- rec.zero = TRUE;
- flag = FALSE;
- break;
- case '-':
- rec.left = TRUE;
- break;
- case '+':
- rec.negative = 1;
- break;
- case '#':
- rec.decimals = 1;
- break;
- default:
- pf --;
- flag = FALSE;
- break;
- }
- }
- // 解析輸出寬度和精度
- flag = TRUE;
- while (flag)
- {
- pf ++;
- switch (*pf)
- {
- case '.': // 如小數點后為'*','0' - '9'繼續處理精度和寬度
- rec.precision = 0;
- c = *(pf + 1);
- flag = (c == '*' || (c >= '0' && c <= '9'));
- break;
- case '*': // 處理'*'表示的寬度參數和精度參數
- if (*(pf - 1) == '.')
- {
- rec.precision = *(PINT)rec.param;
- flag = FALSE;
- }
- else
- {
- rec.width = *(PINT)rec.param;
- flag = *(pf + 1) == '.';
- }
- rec.param += TS_PTR;
- break;
- default: // 處理格式串中數字表示的寬度和精度
- if (*(pf - 1) == '.')
- {
- pf = GetControlNum(pf, &rec.precision);
- flag = FALSE;
- }
- else
- {
- pf = GetControlNum(pf, &rec.width);
- flag = *(pf + 1) == '.';
- }
- }
- }
- // 解析數據類型精度
- flag = TRUE;
- while (flag)
- {
- pf ++;
- switch(*pf)
- {
- case 'L':
- rec.type = TYPE_LLONG;
- break;
- case 'l':
- if (rec.type < TYPE_LLONG)
- rec.type ++;
- break;
- case 'H':
- rec.type = TYPE_CHAR;
- break;
- case 'h':
- if (rec.type > TYPE_CHAR)
- rec.type --;
- break;
- default:
- flag = FALSE;
- }
- }
- // 解析數據類型,並格式化
- c = *pf ++;
- switch (c)
- {
- case 's':
- pb = FormatStrA(pb, &rec);
- break;
- case 'c':
- pb = FormatCharA(pb, &rec);
- break;
- case 'd':
- case 'i':
- case 'u':
- pb = FormatIntA(pb, &rec, c == 'u');
- break;
- case 'f':
- pb = FormatFloatFA(pb, &rec);
- break;
- case 'e':
- case 'E':
- pb = FormatFloatEA(pb, &rec, c);
- break;
- case 'g':
- case 'G':
- pb = FormatFloatGA(pb, &rec, c);
- break;
- case 'x':
- if (rec.decimals)
- rec.decimals = -1;
- case 'X':
- pb = FormatHexA(pb, &rec, c);
- break;
- case 'o':
- pb = FormatOctalA(pb, &rec);
- break;
- case 'p':
- pb = FormatPointerA(pb, &rec);
- break;
- case 'n':
- GetPosSizeA(pb, buffer, &rec);
- break;
- default: // 錯誤:拷貝format剩余字符,返回
- // pf = psave + 1; // ? 也可處理為忽略后繼續
- // break; // ?
- lstrcpyA(pb, psave);
- return buffer;
- }
- }
- va_end(paramList);
- *pb = 0;
- return buffer;
- }
sprintfA函數的主體部分就是一個簡單的解釋器,通過一個主循環,對字符串參數format逐字符的作如下解析:
1)如果不是數據格式前綴字符'%',直接拷貝到輸出緩沖區buffer;
2)如果'%'后接着一個'%'字符,則表示要輸出后面這個'%';
3)緊接着'%'后面的,應該是數據格式前導字符。共有4個前導字符:
1、'0':前導零標志。如果數據被格式化后的長度小於規定的格式化寬度,則在被格式化后的數據前補0;
2、'-':左對齊標記。
3、'+':正數符號輸出標記。正數在正常格式輸出時,其符號是省略了的,'+'則表示要輸出這個符號;
4、'#':對浮點數,這是強制小數點('.')輸出標記。無論這個數有沒有小數部分,都必須輸出這個小數位符號;對整數的十六進制輸出,則是十六進制前綴(0x或者0X)輸出標記。
前導字符不是必須的,也可有多個前導符同時出現在'%'后面,但'0'必須排在最后一個,其余順序可任意。
4)解析數據輸出寬度和精度。寬度是指數據輸出時必須達到的字節數,如果格式化后的數據長度小於寬度,應用空格或者零補齊;精度則是數據要求格式化的長度,視數據類型不同而有所區別,如浮點數是指小數部分的長度,而其它數據則是指全部數據格式化長度,大於精度的數據是保留還是截斷,小於精度是忽略還是補齊(零或空格),后面涉及具體數據類型時再說明。
寬度和精度一般以'.'為分隔符,左邊是寬度,右邊是精度,如果只有寬度則'.'可忽略。寬度和精度可用固定數字表示,如“10.6”,也可用可變形式“*.*”表示。可變形式的寬度和精度必須在sprintf的可變參數部分有其對應的整數實參。
寬度和精度部分也不是必須的。
5)分析數據類型精度字符。在C語言中,相同類型的基本數據可能有不同的精度,如整數有長短之分,浮點數有精度之分,而字符有ANSI和UNICODE之分等等。在sprintfA中,是靠分析類型精度字符來取得的。字符'l'和'h'分別表示長數據和短數據,在16位編譯器環境下,一個'l'或'h'就夠了,而32位及以上編譯器中,隨着數據精度的提高,必須靠多個類型精度字符才能表示完整,為此,也可用字符'L'和'H'分別表示數據類型的最大精度和最小精度。sprintfA的數據類型精度分析有較高的容錯處理,你可以輸入任意多個類型精度字符。
類型精度字符也不是必須的,缺省情況下,按一般類型精度處理。
6)解析數據類型字符。數據類型字符的作用有2個,一是確定將要輸出的數據類型,如x是整型數,e是浮點數等;二是確定要輸出的形式,x是以小寫十六進制輸出整型數,e則是以指數形式輸出浮點數。
數據類型字符是必須的。數據類型字符解析完畢,各種信息寫入FormatRec結構,接着就是具體的各種數據的格式化過程了,其代碼將在后面給出。
7)錯誤處理。如果在'%'字符后,出現上述各種字符以外的字符,或者上述各種字符排列順序錯誤,就需要進行錯誤處理。printf系列函數的錯誤處理在不同的編譯器中的處理方式是不一樣的,主要有2種處理方式:一是忽略本次數據分析,format指針退回到'%'之后,繼續循環('%'后的字符作一般字符處理);二是不再作分析,直接將'%'后的所有字符輸出到buffer后退出函數。本文sprintfA函數采用了后一種處理方式,前一種處理方式在函數主體中也能找到,就是被注釋了的語句。
如果沒有錯誤,則回到1),繼續下一數據分析。
三、格式化字符及字符串。
- // 寬字符串轉換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;
- }
// 寬字符串轉換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);
- }
// 格式化數字串。參數:緩沖區,格式記錄,數字串,數字串長度。返回緩沖區尾偏移 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);
- }
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);
- }
// 按十六進制格式化指針。參數:緩沖區,格式記錄 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;
- }
// 獲取緩沖區當前位置字節數。參數:緩沖區,緩沖區首地址,格式記錄 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)。
八、格式化浮點數(有關浮點數的數據定義和底層的數據轉換函數見《自己動手寫C語言浮點數轉換字符串函數》一文)。
- // 轉換浮點數信息到浮點數記錄fRec。參數:格式記錄,格式方式標記,浮點數記錄
- static void GetFloatRec(FormatRec *rec, INT flag, FloatRec *fRec)
- {
- EXTENDED value;
- if (rec->precision < 0)
- rec->precision = F_DEFDECIMALS;
- else if (rec->precision > F_MAXDECIMALS)
- rec->precision = F_MAXDECIMALS;
- if (rec->type == TYPE_LLONG)
- {
- value = *(PEXTENDED)rec->param;
- rec->param += TS_EXTENDED;
- }
- else
- {
- value = *(double*)rec->param;
- rec->param += TS_DOUBLE;
- }
- switch (flag)
- {
- case 0: // %f
- FloatResolve(&value, F_MAXPRECISION, rec->precision, fRec);
- break;
- case 1: // %e or %E
- FloatResolve(&value, rec->precision + 1, 9999, fRec);
- break;
- case 2: // %g or %G
- FloatResolve(&value, rec->precision, 9999, fRec);
- }
- if (fRec->negative)
- rec->negative = -1;
- }
- // 格式化小數字串。參數:緩沖區,格式記錄,數字串,數字串長度。返回緩沖區尾偏移
- static LPSTR FormatDecimalA(LPSTR buffer, FormatRec *rec, LPCSTR str, INT strLen)
- {
- LPSTR p = buffer;
- INT spaces = rec->width - strLen;
- 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)
- *p ++ = (rec->negative == -1? CHAR_NEG : CHAR_POS);
- }
- memcpy(p, str, strLen);
- p += strLen;
- if (rec->left && spaces > 0)
- {
- memset(p, CHAR_SPACE, spaces);
- p += spaces;
- }
- return p;
- }
- #define F_MAXEXPONENT 45
- #define F_MINEXPONENT -45
- // 輸出指數字符串到buffer,返回指數字符串長度
- INT PutExponent(LPSTR buffer, CONST FloatRec *rec)
- {
- LPSTR p = buffer;
- INT e, exp = rec->digits[0]? rec->exponent - 1 : 0;
- *p ++ = rec->negative & 0x80? 'E' : 'e';
- if (exp < 0)
- {
- exp = -exp;
- *p ++ = '-';
- }
- else *p ++ = '+';
- if ((e = (exp / 1000)) != 0)
- {
- *p ++ = e + 0x30;
- exp %= 1000;
- }
- *p ++ = exp / 100 + 0x30;
- exp %= 100;
- *(PUSHORT)p = (((exp % 10) << 8) | (exp / 10)) + 0x3030;
- return (INT)(p - buffer + 2);
- }
- // 按浮點數記錄信息轉換為指數格式數字串。
- // 參數:緩沖區,浮點數記錄,轉換精度,是否強制小數位
- static INT FloatExponentA(LPSTR buffer, CONST FloatRec *rec, INT precision, BOOL decPoint)
- {
- LPSTR p = buffer;
- LPCSTR digits = rec->digits;
- if (*digits)
- *p ++ = *digits ++;
- else
- *p ++ = '0';
- if (precision > 0 || decPoint)
- {
- for (*p ++ = '.'; precision > 0 && *digits; *p ++ = *digits ++, precision --);
- for (; precision > 0; *p ++ = '0', precision --);
- }
- p += PutExponent(p, rec);
- return (INT)(p - buffer);
- }
- // 按浮點數記錄信息轉換為小數格式數字串。
- // 參數:緩沖區,浮點數記錄,轉換精度,是否強制小數位
- static INT FloatDecimalA(LPSTR buffer, CONST FloatRec *rec, INT precision, BOOL decPoint)
- {
- LPSTR p;
- LPCSTR digits;
- INT exp = rec->exponent;
- if (exp > F_MAXEXPONENT || exp < F_MINEXPONENT)
- return FloatExponentA(buffer, rec, precision, decPoint);
- p = buffer;
- digits = rec->digits;
- if (exp > 0)
- {
- for (; exp > 0 && *digits; *p ++ = *digits ++, exp --);
- for (; exp > 0; *p ++ = '0', exp --);
- if (decPoint || precision > 0)
- *p ++ = '.';
- }
- else
- {
- exp = -exp;
- precision -= exp;
- if (precision < 0)
- {
- exp += precision;
- precision = 0;
- }
- *p ++ = '0';
- if (exp > 0 || decPoint || precision > 0)
- {
- *p ++ = '.';
- for (; exp > 0; *p ++ = '0', exp --);
- }
- }
- for (; precision > 0 && *digits; *p ++ = *digits ++, precision --);
- for (; precision > 0; *p ++ = '0', precision --);
- return (INT)(p - buffer);
- }
- // 浮點數格式化為小數串。參數:緩沖區,格式記錄。返回緩沖區尾偏移
- static LPSTR FormatFloatFA(LPSTR buffer, FormatRec *rec)
- {
- FloatRec fRec;
- INT len;
- CHAR tmp[F_MAXDECIMALS+48];
- GetFloatRec(rec, 0, &fRec);
- if (fRec.digits[0] > '9') // nan or inf
- return FormatDecimalA(buffer, rec, fRec.digits, 3);
- len = FloatDecimalA(tmp, &fRec, rec->precision, rec->decimals);
- return FormatDecimalA(buffer, rec, tmp, len);
- }
- // 浮點數格式化為指數串。參數:緩沖區,格式記錄。返回緩沖區尾偏移
- static LPSTR FormatFloatEA(LPSTR buffer, FormatRec *rec, CHAR expChar)
- {
- FloatRec fRec;
- INT len;
- CHAR tmp[F_MAXDECIMALS+8];
- GetFloatRec(rec, 1, &fRec);
- if (fRec.digits[0] > '9') // nan or inf
- return FormatDecimalA(buffer, rec, fRec.digits, 3);
- if (expChar == 'E')
- fRec.negative |= 0x80; // 高位置1,大寫
- len = FloatExponentA(tmp, &fRec, rec->precision, rec->decimals);
- return FormatDecimalA(buffer, rec, tmp, len);
- }
- // 浮點數格式化為小數串或者指數串。參數:緩沖區,格式記錄。返回緩沖區尾偏移
- static LPSTR FormatFloatGA(LPSTR buffer, FormatRec *rec, CHAR expChar)
- {
- FloatRec fRec;
- INT len, precision;
- CHAR tmp[F_MAXDECIMALS+48];
- GetFloatRec(rec, 2, &fRec);
- if (fRec.digits[0] > '9') // nan or inf
- return FormatDecimalA(buffer, rec, fRec.digits, 3);
- if (expChar == 'G')
- fRec.negative |= 0x80; // 高位置1,大寫
- if (fRec.exponent > rec->precision || fRec.exponent < -3)
- {
- precision = rec->decimals? rec->precision - 1 : lstrlenA(fRec.digits) - 1;
- len = FloatExponentA(tmp, &fRec, precision, rec->decimals);
- }
- else
- {
- precision = rec->decimals? rec->precision - fRec.exponent : lstrlenA(fRec.digits) - fRec.exponent;
- if (precision < 0) precision = 0;
- len = FloatDecimalA(tmp, &fRec, precision, rec->decimals);
- }
- return FormatDecimalA(buffer, rec, tmp, len);
- }
在sprintfA函數中,浮點數的格式化是最復雜的。浮點數有2種表現形式,即小數形式和指數形式,分別用"%f"和"%e"格式表示,另外還有個"%g"格式,這是個自動格式,即sprintfA通過分析后,自行決定采用哪種形式。
在以小數形式的格式化中,對數據的格式化有個極限長度,不然,在擴展精度浮點數下,有些浮點數長度可達到近5000位,即使是雙精度浮點數,最高長度也達300多。在printf系列函數中,這個極限長度隨編譯器不同而不同,有的將這個值定為100,有的定為單精度浮點數的最大表現形式,即38等,我在這里把它定為了正負45位,當數據超過這個極限長度,就自動采用指數形式來格式化數據了。
在介紹sprintfA數據定義時就說過,由於sprintfA的可變參數部分沒有參數原型供編譯器對照,所以在輸入浮點數參數時要注意與格式字符串中對應的浮點數精度匹配,32位編譯器的浮點數缺省精度是64位雙精度數,即使你給的參數變量是個單精度數,也會擴展為雙精度數,如果參數變量是long double,而你使用的編譯器支持80位擴展精度浮點數時,傳遞的是80位擴展精度數,否則也是雙精度數,如果你給出一個整數,編譯器是不會自動轉換的。如果你在參數位置輸入的是常數就更應該注意了,123,123L,123.0f,123.0,123.0L這幾種常數形式是不同的(L也可是小寫),分別是整數,長整數,單精度浮點數,雙精度浮點數,擴展精度浮點數(如果編譯器不支持,也是雙精度數)。所以,在32位及以上編譯器中格式%f和%lf是等同的,自然,在不支持擴展精度浮點數的編譯器中,%llf(%Lf)也等同於%f。
本文格式化浮點數時用到的FloatResolve函數以及有關數據定義見《自己動手寫C語言浮點數轉換字符串函數》。
有關sprintfA函數的介紹就全部完畢。文章代碼沒進行嚴格的測試。