sprintf的深入

举报
ReCclay 发表于 2022/02/21 23:32:23 2022/02/21
【摘要】 韶光易逝,青春难在。 记得在STC15的学习中,经常见得例程中这个函数。人家也是用的6的一批,今天特此再来仔细学习下! 1、连接字符串 emmm,,其实我们知道strcat可以连接两个字符串,但...

韶光易逝,青春难在。


记得在STC15的学习中,经常见得例程中这个函数。人家也是用的6的一批,今天特此再来仔细学习下!

1、连接字符串

emmm,,其实我们知道strcat可以连接两个字符串,但是sprintf当然也是可以的,并且连接多个都没有问题。

先来看个小例子哈,,

#include <stdio.h>
int main()
{
    char buf[60]={0};
    char *who="I";
    char *whom="CSDN";
    char *whos="How about you?";
    sprintf(buf,"%s love %s. %s",who,whom,whos);
    printf("%s",buf);
}
//输出:I love CSDN. How about you?
//绝对没有收广告费!!!
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

当然,上面展示的是使用两个字符串咯,其实你随便想连接多少个都么有问题的,只要有需求。。

但是问题也来了,不管strcat还是sprintf针对的对象都是字符串,什么叫字符串呢?

emmm,,其实应该讲字符串的最重要特征是什么,没错!
就是以’\0’结尾!!!


那假如我们,使用的是不带’\0’的字符数组呢?又会是怎么样的光景?

#include <stdio.h>
int main()
{
    char s[400];
    char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
    char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};

    sprintf(s, "%s%s", a1, a2); 
    printf("%s", s);

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

输出

ABCDEFGAHIJKLMNABCDEFGABCDEFGAH
Process returned 0 (0x0)   execution time : 0.050 s
Press any key to continue.
  
 
  • 1
  • 2
  • 3

问题显而易见了,也许还会问:


“什么字符数组还有就是不带 ‘\0’的,您逗我呢?”

“看官,稍安勿躁。”

您忘了吗?,,,C语言是没有专门的字符串类型的,我们存储字符串都用的是字符数组,然后就存在一个问题了,什么时候会加 ‘\0’呢?,并不是所有的时候都加的。

当非单个字符输入,不管有无,个数指定都会自动加 ‘\0’;
譬如: str[] = “ReCclay” 或者 str[10]=”ReCclay”

当是单个字符输入,除非指定个数,否则不自动加 ‘\0’;
譬如: str[10]={ ‘R’, ‘e’, ‘C’, ‘c’, ‘l’, ‘a’, ‘y’};//自动加
str[]={ ‘R’, ‘e’, ‘C’, ‘c’, ‘l’, ‘a’, ‘y’}; //是不会加的

其实,戳这里,有详细的博文


问题处理

而且,这种末尾不加 ‘\0’的字符处理,在实际应用中更加常见!比如许多从第三方库函数中返回的字符数组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的’’来结尾。如果直接连接,不管是sprintf 还是strcat 肯定会导致非法内存操作,而strncat 也至少要求第一个参数是个null-terminated-string,那该怎么办呢?我们自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串也一样的。


第一感觉,也许是应该这样写代码:

sprintf(s, "%7s%7s", a1, a2); 
  
 
  • 1

但是结果呢,,,

ABCDEFGAHIJKLMNABCDEFGABCDEFGAH
Process returned 0 (0x0)   execution time : 0.040 s
Press any key to continue.

  
 
  • 1
  • 2
  • 3
  • 4

似乎吧,并不是那么好。。。


看这里,哈哈哈。。。

sprintf(s, "%.7s%.7s", a1, a2);//产生:"ABCDEFGHIJKLMN"
  
 
  • 1
ABCDEFGHIJKLMN
Process returned 0 (0x0)   execution time : 0.086 s
Press any key to continue.

  
 
  • 1
  • 2
  • 3
  • 4

是不是大不一样哈,,,

emm,,,有啥规律可寻呢?

这可以类比打印浮点数的”%m/nf”,在”%m.ns”中,m 表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印)。
然后在打印字符串的时候 n 才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m 没什么大用,还是点号后面的n 用的多。

2、动态指定长度


还有更骚的,
在许多时候,我们或许还希望这些格式控制符中用以指定长度信息的数字是动态的,而不是静态指定的,因为许多时候,程序要到运行时才会清楚到底需要取字符数组中的几个字符,这种动态的宽度/精度设置功能在sprintf 的实现中也被考虑到了,sprintf 采用”*”来占用一个本来需要一个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以和其它被打印的变量一样被提供出来,于是,上面的例子可以变成:

#include <stdio.h>
int main()
{
    char s[400];
    char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
    char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};

    sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2);
    printf("%s", s);

}
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
ABCDEFGHIJKLMN
Process returned 0 (0x0)   execution time : 0.017 s
Press any key to continue.

  
 
  • 1
  • 2
  • 3
  • 4

6的一批。。。。

3、地址的打印

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

sprintf(s,"%u",&i);
  
 
  • 1

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

sprintf(s,"%08X",&i);
  
 
  • 1

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

sprintf(s,"%p",&i);
  
 
  • 1

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

sprintf(s,"%0*x",2*sizeof(void*),&i);
  
 
  • 1

4、巧用其返回值

emmm,,,先看一个简单的小例子哈、、、

#include <stdio.h>
int main()
{
    char s[10];
    int len = sprintf(s, "%d", 20);
    printf("%d", len);
}

//答案是2,,,但是如果加个\n的话,注意是计入的!!!
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

有一点注意的时,它和strlen其实一样,都是靠’\0’来判断是否到达字符串吧末尾的!(注意不计入 ‘\0’)。。

但是有一点不太一样的就是,它分个数字并不是理所当然的把一个整数放在一个char上,而是分开放。上面的例子很好的说明了这一点。

然后再来看个更有意思一点的吧

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
    srand(time(0));
    char s[64];
    int offset=0;
    for(int i=0;i<10;i++)
    {
        offset+=sprintf(s+offset,"%d,",rand()%100);
    }
    s[offset-1]='\n';//将最后一个逗号换成换行符。
    printf("%d\r\n", offset);
    printf(s);

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
30
63,53,52,27,15,39,90,18,98,26

Process returned 0 (0x0)   execution time : 0.076 s
Press any key to continue.

//有一点注意,最后一个逗号仍然存在的,只不过转换成换行的形式存在了而已。
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

理论上讲,sprintf是不是比strcat的效率高嘞。。。

MSDN上的一个例子,也记录下:

#include<stdio.h>
int main(void)
{
    char buffer[200],s[]="computer",c='l';
    int i=35,j;
    float fp=1.7320534f;//Format and print various data:
    j=sprintf(buffer,"String:%s\n",s);//C4996
    j+=sprintf(buffer+j,"Character:%c\n",c);//C4996
    j+=sprintf(buffer+j,"Integer:%d\n",i);//C4996
    j+=sprintf(buffer+j,"Real:%f\n",fp);//C4996
//Note:sprintf is deprecated; consider using sprintf_s instead
    printf("Output:\n%s\ncharactercount=%d\n",buffer,j);
}
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

常见的问题

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

1、缓冲区溢出

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

2、忘记了第一个参数

低级得不能再低级问题,用printf 用得太惯了。
变参对应出问题
通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤其是对应”*”的那些参数,都提供了吗?

3、sprintf_s和snprintf

sprintf_s()是sprintf()的安全版本,通过指定缓冲区长度来避免sprintf()存在的溢出风险 。在使用VS2008时如果你使用了sprintf函数,那么编译器会发出警告:使用sprintf存在风险,建议使用sprintf_s。这个安全版本的原型是:
int sprintf_s(char *buffer,size_t sizeOfBuffer,const char *format [,argument] … );
不过sprintf_s()是微软私有的函数,考虑到跨平台移植,最好使用snprintf()。

两者的原型基本相同:
int _snprintf(char *buffer, size_t count, const char *format [,argument] … );

strftime

sprintf 还有个不错的表妹:strftime,专门用于格式化时间字符串的,用法跟她表哥很像,也是一大堆格式控制符,只是毕竟小姑娘家心细,她还要调用者指定缓冲区的最大长度,可能是为了在出现问题时可以推卸责任吧。这里举个例子:
time_t t = time(0);
//产生”YYYY-MM-DD hh:mm:ss”格式的字符串。
char s[32];
strftime(s, sizeof(s), “%Y-%m-%d %H:%M:%S”, localtime(&t));
sprintf 在MFC 中也能找到他的知音:CString::Format,strftime 在MFC 中自然也有她的同道:CTime::Format,这一对由于从面向对象那里得到了赞助,用以写出的代码更觉优雅。


参考:
https://baike.baidu.com/item/sprintf/9703430?fr=aladdin

文章来源: recclay.blog.csdn.net,作者:ReCclay,版权归原作者所有,如需转载,请联系作者。

原文链接:recclay.blog.csdn.net/article/details/78985029

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。