纯手动的C语言格式化输出函数.doc_第1页
纯手动的C语言格式化输出函数.doc_第2页
纯手动的C语言格式化输出函数.doc_第3页
纯手动的C语言格式化输出函数.doc_第4页
纯手动的C语言格式化输出函数.doc_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

纯手动的C语言格式化输出函数分类:C/C+作者:maozefa摘自:CSDN 技术Blog链接地址:/maozefaprintf系列函数,包括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语言浮点数转换字符串函数一文中的代码也应算作一篇: 一、数据定义:view plainprint?1. typedefstruct2. 3. INTtype;/数据长度类型4. INTwidth;/数据最小宽度5. INTprecision;/数据精度6. BOOLleft;/是否居左7. BOOLzero;/是否前导零8. INTdecimals;/浮点数:1强制小数位;16进制:-1:0x,1:0X9. INTnegative;/符号:-1:-;1:+10. LPSTRparam;/参数指针11. FormatRec;12. 13. typedeflonglongLLONG,*PLLONG;14. typedefunsignedlonglongULLONG,*PULLONG;15. 16. #defineTYPE_CHAR017. #defineTYPE_SHORT118. #defineTYPE_GENERAL219. #defineTYPE_LONG320. #defineTYPE_LLONG421. 22. #definePTR_SIZEsizeof(VOID*)23. #defineTypeSize(size)(size+PTR_SIZE-1)/PTR_SIZE)*PTR_SIZE)24. 25. #defineTS_PTRPTR_SIZE26. #defineTS_CHARTypeSize(sizeof(CHAR)27. #defineTS_WCHARTypeSize(sizeof(WCHAR)28. #defineTS_SHORTTypeSize(sizeof(SHORT)29. #defineTS_INTTypeSize(sizeof(INT)30. #defineTS_LONGTypeSize(sizeof(LONG)31. #defineTS_LLONGTypeSize(sizeof(LLONG)32. #defineTS_FLOATTypeSize(sizeof(FLOAT)33. #defineTS_DOUBLETypeSize(sizeof(double)34. #defineTS_EXTENDEDTypeSize(sizeof(EXTENDED)35. 36. #defineCHAR_SPACE37. #defineCHAR_ZERO038. #defineCHAR_POS+39. #defineCHAR_NEG-40. 41. #defineHEX_PREFIX_U0X42. #defineHEX_PREFIX_L0x43. 44. #defineMAX_DIGITS_SIZE40 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语言浮点数转换字符串函数。 二、函数主体。view plainprint?1. /获取字符串中的数字。参数:字符串,数字指针。返回字符串中最后一个数字位置2. staticLPSTRGetControlNum(LPCSTRs,INT*value)3. 4. registerLPCSTRp=s;5. registerINTv;6. for(v=0;*p=0&*p=0&c=9);66. break;67. case*:/处理*表示的宽度参数和精度参数68. if(*(pf-1)=.)69. 70. rec.precision=*(PINT)rec.param;71. flag=FALSE;72. 73. else74. 75. rec.width=*(PINT)rec.param;76. flag=*(pf+1)=.;77. 78. rec.param+=TS_PTR;79. break;80. default:/处理格式串中数字表示的宽度和精度81. if(*(pf-1)=.)82. 83. pf=GetControlNum(pf,&rec.precision);84. flag=FALSE;85. 86. else87. 88. pf=GetControlNum(pf,&rec.width);89. flag=*(pf+1)=.;90. 91. 92. 93. /解析数据类型精度94. flag=TRUE;95. while(flag)96. 97. pf+;98. switch(*pf)99. 100. caseL:101. rec.type=TYPE_LLONG;102. break;103. casel:104. if(rec.typeTYPE_CHAR)112. rec.type-;113. break;114. default:115. flag=FALSE;116. 117. 118. /解析数据类型,并格式化119. c=*pf+;120. switch(c)121. 122. cases:123. pb=FormatStrA(pb,&rec);124. break;125. casec:126. pb=FormatCharA(pb,&rec);127. break;128. cased:129. casei:130. caseu:131. pb=FormatIntA(pb,&rec,c=u);132. break;133. casef:134. pb=FormatFloatFA(pb,&rec);135. break;136. casee:137. caseE:138. pb=FormatFloatEA(pb,&rec,c);139. break;140. caseg:141. caseG:142. pb=FormatFloatGA(pb,&rec,c);143. break;144. casex:145. if(rec.decimals)146. rec.decimals=-1;147. caseX:148. pb=FormatHexA(pb,&rec,c);149. break;150. caseo:151. pb=FormatOctalA(pb,&rec);152. break;153. casep:154. pb=FormatPointerA(pb,&rec);155. break;156. casen:157. GetPosSizeA(pb,buffer,&rec);158. break;159. default:/错误:拷贝format剩余字符,返回160. /pf=psave+1;/?也可处理为忽略后继续161. /break;/?162. lstrcpyA(pb,psave);163. returnbuffer;164. 165. 166. va_end(paramList);167. *pb=0;168. returnbuffer;169. 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),继续下一数据分析。三、格式化字符及字符串。1/宽字符串转换ANSI字符串。参数:ANSI字符串,宽字符串,转换字符数(0不转换)。2/返回实际转换字符个数3staticINTWStrToStr(LPSTRdst,LPCWSTRsrc,INTcount)45returnWideCharToMultiByte(CP_THREAD_ACP,0,src,-1,6dst,count0?count+1:0,NULL,NULL)-1;789/格式化字符。参数:缓冲区,格式记录。返回缓冲区尾偏移10staticLPSTRFormatCharA(LPSTRbuffer,FormatRec*rec)1112INTlen,spaces;13LPSTRp=buffer;1415if(rec-type=TYPE_LONG)1617len=WStrToStr(NULL,(LPCWSTR)rec-param,0);18if(len=0)len=sizeof(CHAR);1920elselen=sizeof(CHAR);21spaces=rec-width-len;22if(rec-left=FALSE&spaces0)2324memset(p,CHAR_SPACE,spaces);25p+=spaces;2627if(rec-type=TYPE_LONG)2829WStrToStr(p,(LPCWSTR)rec-param,len);30p+=len;3132else*p+=*(LPCSTR)rec-param;33if(rec-left=TRUE&spaces0)3435memset(p,CHAR_SPACE,spaces);36p+=spaces;3738rec-param+=rec-type=TYPE_LONG?TS_WCHAR:TS_CHAR;39returnp;404142/格式化字符串。参数:缓冲区,格式记录。返回缓冲区尾偏移43staticLPSTRFormatStrA(LPSTRbuffer,FormatRec*rec)4445INTlen,spaces;46LPSTRp=buffer;4748if(rec-type=TYPE_LONG)49len=WStrToStr(NULL,*(LPCWSTR*)rec-param,0);50else51len=lstrlenA(*(LPCSTR*)rec-param);52if(rec-precision=0&lenrec-precision)53len=rec-precision;54spaces=rec-width-len;55if(rec-left=FALSE&spaces0)5657memset(p,CHAR_SPACE,spaces);58p+=spaces;5960if(rec-type=TYPE_LONG)61WStrToStr(p,*(LPCWSTR*)rec-param,len);62else63memcpy(p,*(LPCSTR*)rec-param,len);64p+=len;65if(rec-left=TRUE&spaces0)6667memset(p,CHAR_SPACE,spaces);68p+=spaces;6970rec-param+=TS_PTR;71returnp;72 如果不涉及宽字符,格式化字符和字符串是很简单的。 对于字符和字符串,%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。 四、格式化整型数。1/格式化数字串。参数:缓冲区,格式记录,数字串,数字串长度。返回缓冲区尾偏移2staticLPSTRFormatDigitsA(LPSTRbuffer,FormatRec*rec,LPCSTRdigits,INTlen)34LPSTRp=buffer;5INTspaces;67if(rec-precision=0)8rec-zero=FALSE;9rec-precision-=len;10if(rec-precisionprecision=0;12spaces=rec-width-len-rec-precision;13if(rec-negative)1415spaces-;16if(rec-left|rec-zero)17*p+=(rec-negative=-1?CHAR_NEG:CHAR_POS);1819if(rec-left=FALSE)2021if(spaces0)2223memset(p,rec-zero?CHAR_ZERO:CHAR_SPACE,spaces);24p+=spaces;2526if(rec-negative&!rec-zero&!rec-decimals)27*p+=(rec-negative=-1?CHAR_NEG:CHAR_POS);2829if(rec-precision!=0)3031memset(p,CHAR_ZERO,rec-precision);32p+=rec-precision;3334memcpy(p,digits,len);35p+=len;36if(rec-left=TRUE&spaces0)3738memset(p,CHAR_SPACE,spaces);39p+=spaces;4041returnp;424344/整型数转换为数字串。参数:数字串,整型数,是否无符号整数4546staticINTIntToDigits(LPSTRdigits,LONGsrc,BOOL*isUnsigned)4748ULONGv;49LPSTRp=digits+MAX_DIGITS_SIZE;5051if(*isUnsigned=FALSE&src0)src=-src;52else*isUnsigned=TRUE;53v=(ULONG)src;54do5556*(-p)=(CHAR)(v%10+0);57v/=10;58while(v);59return(INT)(MAX_DIGITS_SIZE-(p-digits);606162staticINTLLongToDigits(LPSTRdigits,LLONGsrc,BOOL*isUnsigned)6364ULLONGv;65LPSTRp=digits+MAX_DIGITS_SIZE;6667if(*isUnsigned=FALSE&srcnegative=0;88if(numSizesrec-typeparam;91if(isUnsigned)92value&=(ULONG)(-1)(TS_PTR-numSizesrec-type)param,&isUnsigned);97if(!isUnsigned)rec-negative=-1;98rec-param+=TypeSize(numSizesrec-type);99rec-decimals=0;100returnFormatDigitsA(buffer,rec,&digitsMAX_DIGITS_SIZE-len,len);101 在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)精度。 五、整型数格式化为十六进制和八进制数字串。1staticCHARhexDigitsU=0123456789ABCDEF;2staticCHARhexDigitsL=0123456789abcdef;34/整型数转换为十六进制串。参数:十六进制串,整型数,字节长度,转换精度,是否大写5staticINTNumberToHexA(LPSTRhex,LPCVOIDlpNumber,INTbytes,INTprecision,BOOLupper)67LPSTRph=hex;8LPBYTEpn=(LPBYTE)lpNumber;9LPSTRhexDigits;10INTlen;1112for(bytes-;bytes0&pnbytes=0;bytes-);13pn+=bytes;14bytes+;15len=bytes*2;16if(*pn&0xf0)=0)len-;17if(hex=NULL)18returnprecisionlen?precision:len;19for(precision-=len;precision0;*ph+=0,precision-);20hexDigits=upper?hexDigitsU:hexDigitsL;21if(*pn&0xf0)=0)2223*ph+=hexDigits*pn-&0x0f;24bytes-;2526for(;bytes0;bytes-,pn-)2728*ph+=he

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论