当前位置:网站首页 > 技术博客 > 正文

sprintf函数用法详解



sprintf是个变参函数,定义如下:

除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数:格式化字符串上。

printf 和sprintf都使用格式化字符串来指 定串的格式,在格式串内部使用一些以“%”开头的格式说明符(format specifications)来占据一个位置,在后边的变参列表中提供相应的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要 的字符串。


1. 格式化数字字符串

sprintf最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf在大多数场合可以替代itoa。如:


//把整数123打印成一个字符串保存在s中。


可以指定宽度,不足的左边补空格:


这样,一个整数的16进制字符串就很容易得到,但我们在打印16进制内容时,通常想要一种左边补0的等宽格式,那该怎么做呢?很简单,在表示宽度的数字前面加个0就可以了。


sprintf(s, “%08X”, 4567); //产生:“000011D7″


上面以”%d”进行的10进制打印同样也可以使用这种左边补0的方式。


这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1的内存16进制表示形式,在Win32平台上,一个 short型占2个字节,所以我们自然希望用4个16进制数字来打印它:



产生“FFFFFFFF”,怎么回事?因为 spritnf是个变参函数,除了前面两个参数之外,后面的参数都不是类型安全的,函数更没有办法仅仅通过一个“%X”就能得知当初函数调用前参数压栈时 被压进来的到底是个4字节的整数还是个2字节的短整数,所以采取了统一4字节的处理方式,导致参数压栈时做了符号扩展,扩展成了32位的整数-1,打印时 4个位置不够了,就把32位整数-1的8位16进制都打印出来了。如果你想看si的本来面目,那么就应该让编译器做0扩展而不是符号扩展(扩展时二进制左 边补0而不是补符号位):



sprintf和printf还可以按8进制打印整数字符串,使用”%o”。注意8进制和16进制都不会打印出负数,都是无符号的,实际上也就是变量的内部编码的直接的16进制或8进制表示。


2. 控制浮点数打印格式

浮点数的打印和格式控制是sprintf的又一大常用功能,浮点数使用格式符”%f”控制,默认保留小数点后6位数字,比如:



注意一个问题,你猜


第一个打出来的肯定不是正确结果,原因跟前面提到的一样,参数压栈时调用者并不知道跟i相对应的格式控制符是个”%f”。而函数执行时函数本身则并不知道当年被压入栈里的是个整数,于是可怜的保存整数i的那4个字节就被不由分说地强行作为浮点数格式来解释了,整个乱套了。


不过,如果有人有兴趣使用手工编码一个浮点数,那么倒可以使用这种方法来检验一下你手工编排的结果是否正确。J


字符/Ascii码对照


我们知道,在C/C++语言中,char也是一种普通 的scalable类型,除了字长之外,它与short,int,long这些类型没有本质区别,只不过被大家习惯用来表示字符和字符串而已。(或许当年 该把这个类型叫做“byte”,然后现在就可以根据实际情况,使用byte或short来把char通过typedef定义出来,这样更合适些)


于是,使用”%d”或者”%x”打印一个字符,便能得 出它的10进制或16进制的ASCII码;反过来,使用”%c”打印一个整数,便可以看到它所对应的ASCII字符。以下程序段把所有可见字符的 ASCII码对照表打印到屏幕上(这里采用printf,注意”#”与”%X”合用时自动为16进制数增加”0X”前缀):



3. 连接字符串

sprintf的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连接字符串,从而在许多场合可以替代strcat,但sprintf能够一次连接多个字符串(自然也可以同时在它们中间插入别的内容,总之非常灵活)。比如:


如果:



4. 打印地址信息

有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32位的数,你完全可以使用打印无符号整数的”打印出来:


sprintf(s, “卿amp;i);


不过通常人们还是喜欢使用16进制而不是10进制来显示一个地址:


sprintf(s, “%08X”, &i);


然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的”%p”:


sprintf(s, “%p”, &i);


我觉得它实际上就相当于:


sprintf(s, “%0*x”, 2 * sizeof(void *), &i);


5. 利用sprintf的返回值

较少有人注意printf/sprintf函数的返回值,但有时它却是有用的,spritnf返回了本次函数调用最终打印到字符缓冲区中的字符数目。也就是说每当一次sprinf调用结束以后,你无须再调用一次strlen便已经知道了结果字符串的长度。如:


int len = sprintf(s, “%d”, i);


对于正整数来说,len便等于整数i的10进制位数。


下面的是个完整的例子,产生10个[0, 100)之间的随机数,并将他们打印到一个字符数组s中,以逗号分隔开。



设想当你从数据库中取出一条记录,然后希望把他们的各 个字段按照某种规则连接成一个字符串时,就可以使用这种方法,从理论上讲,他应该比不断的strcat效率高,因为strcat每次调用都需要先找到最后 的那个'/0'的位置,而在上面给出的例子中,我们每次都利用sprintf返回值把这个位置直接记下来了。


6. 使用sprintf的常见问题

sprintf是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误,但好在由sprintf误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通常用眼睛再把出错的代码多看几眼就看出来了。


?? 缓冲区溢出


第一个参数的长度太短了,没的说,给个大点的地方吧。当然也可能是后面的参数的问题,建议变参对应一定要细心,而打印字符串时,尽量使用”%.ns”的形式指定最大字符数。


?? 忘记了第一个参数


低级得不能再低级问题,用printf用得太惯了。//偶就常犯。:。(


?? 变参对应出问题


通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤其是对应”*”的那些参数,都提供了吗?不要把一个整数对应一个”%s”,编译器会觉得你欺她太甚了(编译器是obj和exe的妈妈,应该是个女的,:P)。


7. strftime

sprintf还有个不错的表妹:strftime,专门用于格式化时间字符串的,用法跟她表哥很像,也是一大堆格式控制符,只是毕竟小姑娘家心细,她还要调用者指定缓冲区的最大长度,可能是为了在出现问题时可以推卸责任吧。这里举个例子:


sprintf在MFC中也能找到他的知音:CString::Format,strftime在MFC中自然也有她的同道:CTime::Format,这一对由于从面向对象哪里得到了赞助,用以写出的代码更觉优雅。


8. 后记

本文介绍的所有这些功能,在MSDN中都可以很容易地查到,笔者只是根据自己的使用经验,结合一些例子,把一些常用的,有用的,而可能为许多初学者所不知的用法介绍了一点,希望大家不要笑话,也希望大家批评指正。


有人认为这种带变参的函数会引起各种问题,因而不提倡使用。但笔者本人每每还是抵挡不了它们强大功能的诱惑,在实际工作中一直在使用。实际上,C#.NET 从开始就支持变参,刚发布不久的Java5.0也支持变参了。


①获取System时间: void GetSystemTime(LPSYSTEMTIME lpSystemTime); 下面是例子:


  • 上一篇: css按钮样式代码
  • 下一篇: 树莓派3b+图片
  • 版权声明


    相关文章:

  • css按钮样式代码2025-05-16 23:30:05
  • python3教程下载2025-05-16 23:30:05
  • dns域名后缀中的cn表示什么2025-05-16 23:30:05
  • 密码学内容2025-05-16 23:30:05
  • 命令模式的缺点2025-05-16 23:30:05
  • 树莓派3b+图片2025-05-16 23:30:05
  • mysql触发器 sql2025-05-16 23:30:05
  • vuex-i18n2025-05-16 23:30:05
  • f什么什么st2025-05-16 23:30:05
  • java高并发编程详解多线程与架构设计2025-05-16 23:30:05