1. 格式化字符串
int sprintf( char *buffer, const char *format [, argument] … );
//注意一下返回值 为 int 是格式化好后的字符串的长度
注意,第一个参数不能传递 string类型的,若是结果非要string类,则可以
char c[size];
sprintf(c, "格式化形式",要格式化的内容);
string s = c;
2. 连接字符串
sprintf的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连接字符串,从而在许多场合可以替代 strcat,但 sprintf 能够一次连接多个字符串(自然也可以同时在它们中间插入别的内容,总之非常灵活)。比如:
1 2 3 4 |
|
strcat 只能连接字符串(一段以 '\0' 结尾的字符数组或叫做字符缓冲,null-terminated-string),但有时我们有两段字符缓冲区,他们并不是以 '\0' 结尾。比如许多从第三方库函数中返回的字符数组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的 '\0' 来结尾。如果直接连接,不管是 sprintf 还是 strcat 肯定会导致非法内存操作,而 strncat 也至少要求第一个参数是个 null-terminated-string,那该怎么办呢?我们 自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串也一样的。比如:
1 2 |
|
如果:
1 |
|
十有八九要出问题了。是否可以改成:
1 |
|
也没好到哪儿去,正确的应该是:
1 |
|
这可以类比打印浮点数的“%m.nf”,在“%m.ns”中,m表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),n才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m没什么大用,还是点号后面的n用的多。自然,也可以前后都只取部分字符:
1 |
|
在许多时候,我们或许还希望这些格式控制符中用以指定 长度信息的数字是动态的,而不是静态指定的,因为许多时候,程序要到运行时才会清楚到底需要取字符数组中的几个字符,这种动态的宽度/精度设置功能在 sprintf 的实现中也被考虑到了,sprintf 采用“*”来占用一个本来需要一个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以 和其它被打印的变量一样被提供出来,于是,上面的例子可以变成:
1 |
|
或者:
1 |
|
实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:
1 2 3 |
|
3、打印地址信息
有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32位的数,你完全可以使用打印无符号整数的“%u”把他们打印出来:
1 |
|
不过通常人们还是喜欢使用16进制而不是10进制来显示一个地址:
1 |
|
然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的“%p”:
1 |
|
我觉得它实际上就相当于:
1 |
|
4、利用sprintf的返回值
较少有人注意 printf/sprintf 函数的返回值,但有时它却是有用的,spritnf 返回了本次函数调用最终打印到字符缓冲区中的字符数目。也就是说每当一次 sprinf 调用结束以后,你无须再调用一次strlen 便已经知道了结果字符串的长度。如:
1 |
|
对于正整数来说,len便等于整数i的10进制位数。
下面的是个完整的例子,产生10个[0, 100)之间的随机数,并将他们打印到一个字符数组s中,以逗号分隔开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
设想当你从数据库中取出一条记录,然后希望把他们的各个字段按照某种规则连接成一个字符串时,就可以使用这种方法,从理论上讲,他应该比不断的 strcat 效率高,因为 strcat 每次调用都需要先找到最后 的那个 '\0' 的位置,而在上面给出的例子中,我们每次都利用 sprintf 返回值把这个位置直接记下来了。
6、使用 sprintf 的常见问题
sprintf是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误,但好在由sprintf误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通常用眼睛再把出错的代码多看几眼就看出来了。
1. 缓冲区溢出
第一个参数的长度太短了,没的说,给个大点的地方吧。当然也可能是后面的参数的问题,建议变参对应一定要细心,而打印字符串时,尽量使用“%.ns”的形式指定最大字符数。
2.忘记了第一个参数
低级得不能再低级问题,用 printf 用得太惯了。// 偶就常犯。
3.变参对应出问题
通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤其是对应“*”的那些参数,都提供了吗?不要把一个整数对应一个“%s”,编译器会觉得你欺她太甚了。
二、snprintf() 详解
函数原型:
int snprintf(char *restrict buf, size_t n, const char * restrict format, ...);
函数说明:最多从源串中拷贝 n-1 个字符到目标串中,然后再在后面加一个 '\0'。所以如果目标串的大小为 n 的话,将不会溢出。
函数返回值:若成功则返回欲写入的字符串长度,若出错则返回负值。
1、推荐的用法
1 2 3 4 5 6 7 8 9 10 |
|
输出:
str=012345678
2、不推荐的用法
1 2 3 4 5 6 7 8 9 10 |
|
输出:
str=01234567890123456
3、snprintf() 函数返回值的测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
输出:
aaabbbccc length=9
str1=abc,ret1=3
str2=aaa,ret2=9
特别注意:
snprintf() 的返回值是欲写入的字符串长度,而不是实际写入的字符串度。如:
1 2 3 4 5 6 7 8 9 10 |
|
输出:
10 | 1234