功能:将格式化数据写入字符串
头文件:
stdio.h
函数原型:
int sprintf( char *buffer, const char *format, [ argument] … );
参数列表:
:char类型指针,指向要写入的字符串的地址。
:char指针,指向的内存存储格式字符串。
[]…:可选参数,可以是任意类型的数据。
返回值:字符串长度()
相关功能:
int sprintf_s(char *buffer,size_t sizeOfBuffer,const char *format, [argument] ... );
int _sprintf_s_l(char *buffer,size_t sizeOfBuffer,const char *format,locale_t locale ,[argument] ... );
int swprintf_s(wchar_t *buffer,size_t sizeOfBuffer,const wchar_t *format ,[argument]...);
int _swprintf_s_l(wchar_t *buffer,size_t sizeOfBuffer,const wchar_t *format,locale_t locale ,[argument]…);
template <size_t size>
int sprintf_s(char (&buffer)[size],const char *format, [argument] ... ); //仅存在于C++
template <size_t size>
int swprintf_s(wchar_t (&buffer)[size],const wchar_t *format ,[argument]...); //仅存在于C++
字符串格式化命令的主要作用是将格式化的数据写入字符串中。它是一个可变参数函数,使用时经常会出现问题,而每当出现问题时,通常是内存访问错误,会导致程序碰撞。
幸运的是,虽然误操作造成的问题很严重,但很容易发现。 无非有几种情况。 一般情况下,你用眼睛就能看到错误的代码,多看几遍就可以了。
参数说明及应用示例
格式的规格如下。 []中的部分是可选的。
%[指定参数][标识符][宽度][.精度]指示符
如果你想输出%'本身,请像这样处理%%'。
1.处理人物方向。 负号表示从后向前处理。
2. 填空。 如果为0,则表示空格处填0; 空白是默认值,这意味着空白将被保留。
3. 总字符宽度。 是最小宽度。
4. 准确性。 指小数点后浮点数的位数。
转换字符
%% 打印百分号而不进行转换。
%c 将整数转换为相应的 ASCII 字符。
%d 将整数转换为小数。
%f 乘以转换为浮点数的精度数。
%o 将整数转换为八进制。
%s 将整数转换为字符串。
%x 整数转换为小写十六进制。
%X 将整数转换为大写十六进制。
$money = 123.1
$formatted = sprintf (".2f", $money); // 此时变数 $ formatted 值为 "123.10"
$formatted = sprintf (".2f", $money); // 此时变数 $ formatted 值为 "00123.10"
$formatted = sprintf ("%-08.2f", $money); // 此时变数 $ formatted 值为 "123.1000"
$formatted = sprintf ("%.2f%%", 0.95 * 100); // 格式化为百分比
解释:
起始字符
0是“填空”字符,意思是如果长度不足,就用0来填充。
8格式化后总长度
2f小数位长度,即2位
第 3 行的值为“00123.10” 说明:
因为2f是(2位)+小数点符号(1位)+前面的123(3位)=6位,总长度是8位,所以前面用【补空符】0来表示,即是,00123.10
第 4 行的值为“123.1000” 说明:
-号是相反的操作,然后在末尾添加补空字符0。
在将各种类型的数据构建为字符串时,它的强大功能很少会让您失望。 由于它们的用途几乎相同,只是打印目的地不同。 前者打印为字符串,后者直接在命令行上输出。 这也带来了比有用更多的结果。
它是一个可变参数函数,定义如下:
int (char *, const char * [,] …);
除了前两个参数的固定类型外,还可以跟随任意数量的参数。 而其本质显然就在第二个参数上:
在格式字符串上。
两者都使用格式字符串来指定字符串的格式。 在格式字符串中,一些以“%”开头的格式说明符( )用于占据一个位置。 下面的变量参数列表中提供了相应的变量。 最终的函数是该说明符将被替换为相应位置的变量,产生调用者想要的字符串。
格式化数字字符串最常见的应用之一是将整数打印到字符串中,因此它在大多数情况下可以替代 itoa。
喜欢:
//把整数123 打印成一个字符串保存在s 中。
sprintf(s, "%d", 123); //产生"123"
可以指定宽度,不足的左边补空格:
sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567"
当然也可以左对齐:
sprintf(s, "%-8d%8d", 123, 4567); //产生:"123 4567"
也可以按照16 进制打印:
sprintf(s, "%8x", 4567); //小写16 进制,宽度占8 个位置,右对齐
sprintf(s, "%-8X", 4568); //大写16 进制,宽度占8 个位置,左对齐
这样就很容易得到一个整型的十六进制字符串了,但是当我们打印十六进制内容的时候,通常想要的是左边补0的等宽格式,那么该怎么办呢? 很简单,只要在表示宽度的数字前面加一个0就可以了。
(s,“X”,4567); //产生:“”
上面使用“%d”的十进制打印也可以使用这种左边补0的方法。
这里有一个符号扩展问题需要注意:比如我们要打印一个短整数(short)的内存十六进制表示-1,在Win32平台上,一个short类型占用2个字节,所以我们自然要使用4 将其打印为十六进制数:
短 si = -1;
(s,“X”,si);
生成了“”,这是怎么回事? 因为它是一个变量参数函数,所以除了前两个参数之外,后面的参数都不是类型安全的。 仅使用“%X”,函数无法知道在调用函数之前压入堆栈的参数。 是4字节整数还是2字节短整数? 因此,采用统一的4字节处理方式。 因此,参数在压入堆栈时会进行符号扩展并扩展为 32 位整数 -1。 打印时, 4 如果位置不够,则将32位整数-1的8位十六进制数字全部打印出来。
如果你想看到si的真面目,那么你应该要求编译器做0扩展而不是符号扩展(扩展时,二进制左填充为0而不是符号位):
(s,“X”,(短)si);
就是这样。 或者:
短 si = -1;
(s,“X”,si);
您还可以使用“%o”以八进制表示法打印整数字符串。 注意,八进制和十六进制都不会打印出负数,它们都是无符号的,这实际上是变量内部编码的直接十六进制或八进制表示。
控制浮点数打印格式
浮点数的打印和格式控制是另一个常用的功能。 浮点数使用格式字符“%f”控制,默认保留小数点后6位,例如:
(s, "%f", 3.); //产生“3”。
但有时我们想自己控制打印宽度和小数位数。 在这种情况下,我们应该使用“%m /nf”格式,其中m代表打印宽度,n代表小数位数。 例如:
(s,“.3f”,3.); //产生:“3.142”
(s,“%-10.3f”,3.); //产生:“3.142”
(s, "%.3f", 3.); //不指定总宽度,结果为:“3.142”
注意一个问题,你猜怎么着?
整数 i = 100;
(s,“%.2f”,i);
会出来什么? “100.00”? 这样对吗? 你自己试试就知道了,也试试这个:
(s,“%.2f”,()i);
第一个输入的结果肯定不是正确的。 原因和前面提到的一样。 当参数入栈时,调用者并不知道i对应的格式控制字符是“%f”。 当函数执行时,函数本身并不知道压入栈的是一个整数,所以存储整数i的可怜的4个字节被强行解释为浮点数格式,整个事情就乱了。 但是,如果有人对手动编码浮点数感兴趣,那么您可以使用此方法来检查您的手动编码结果是否正确。
字符/Ascii 码比较
我们知道,在C/C++语言中,char也是一种常见的类型。 除了字长之外,与short、int、long等类型没有本质区别。 它只是被每个人用来表示字符和字符串。 。 (或许当年这个类型应该叫“byte”,现在可以根据实际情况用byte或者short来定义char,更合适。)所以,用“%d”或者“%x”来打印一个字符,可以得到它的十进制或十六进制的ASCII码; 反之,使用“%c”打印一个整数,就可以看到其对应的ASCII字符。 下面的程序段将所有可见字符的ASCII码对照表打印到屏幕上(这里使用,注意“#”和“%X”一起使用时,会在十六进制数后面自动加上“0X”前缀) ):
for(int i = 32; i < 127; i++) {
("[ %c ]: %3d 0x%#04X\n", 我, 我, 我);
连接字符串
既然可以在格式控制字符串中插入各种东西,最后“连接成一个字符串”,那么自然就可以连接字符串,很多情况下都可以替换,但是可以一次连接多个字符串(自然也可以同时连接它们)在它们之间插入其他内容,非常灵活)。 例如:
char* who =“我”;
char* who = "CSDN";
(s,“%s 爱 %s。”,谁,谁); //产生:“我爱CSDN。”
只能连接字符串(以''结尾的字符数组或者称为字符缓冲区,null--),但有时我们有两个字符缓冲区,而且它们不以''结尾。 例如,许多从第三方库函数返回的字符数组以及从硬件或网络传输读取的字符流可能不一定在每个字符序列后面都以相应的 '' 结尾。 如果直接连接的话,肯定会导致非法的内存操作,而且至少要求第一个参数为空--,那怎么办呢? 我们自然会记得,前面提到过,打印整数和浮点数时我们可以指定宽度,字符串也是如此。 例如:
char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};
如果:
(s, “%s%s”, a1, a2); //不要这样做!
很可能会出现问题。 是否可以改为:
(s,“%7s%7s”,a1,a2);
也好不到哪里去,正确的应该是:
(s, "%.7s%.7s", a1, a2);//产生: ""
这可以与打印浮点数的“%m/nf”进行比较。 “%m.ns”中,m表示占用的宽度(字符串长度不足时添加空格,超过则按实际宽度打印),n表示对应的宽度。 字符串中可以使用的最大字符数。 通常打印字符串时,m用处不大,但句号后面的n用得较多。 当然,你也可以只取前后的部分字符:
(s, "%.6s%.5s", a1, a2);//产生: ""
很多时候,我们可能还希望这些格式控制字符中用来指定长度信息的数字是动态的而不是静态指定的,因为很多时候,程序并不会确切知道需要取出字符数组中的多少个字符直到它运行起来。 字符,在实现中也考虑了这种动态宽度/精度设置功能,使用“*”占据一个位置,否则需要一个常数来指定宽度或精度。 同样,可以像其他打印变量一样提供实际宽度或精度,因此上面的示例可以变为:
(s, “%.*s%.*s”, 7, a1, 7, a2);
或者:
(s, "%.*s%.s", (a1), a1, (a2), a2);
其实前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,如:
(s, "%-d", 4, 'A'); //产生“65”
(s, "%#0X", 8, 128); //产生“”,“#”产生0X
(s, "%.*f", 10, 2, 3.); //产生“3.14”
打印地址信息
有时在调试程序时,我们可能想检查一些变量或成员的地址。 由于地址或指针只是一个32位数字,因此可以使用“%u”打印无符号整数来将其打印出来:
(s, “%u”, &i);
然而,人们通常更喜欢使用十六进制而不是十进制来显示地址:
(s、“X”、&i);
然而,这些都是间接方法,对于地址打印,提供了专门的“%p”:
(s, “%p”, &i);
我认为它实际上相当于:
(s, “%0*x”, 2 * (void *), &i);
漏洞利用的返回值
较少人关注 / 函数的返回值,但有时它是有用的。 它返回在此函数调用中最终打印到字符缓冲区中的字符数。 也就是说,每次调用完成后,你就已经知道了结果字符串的长度,而无需再次调用。 喜欢:
int len = (s, “%d”, i);
对于正整数,len 等于整数 i 中的小数位数。
下面是一个完整的例子,生成[0, 100)之间的10个随机数,并打印到字符数组s中,
用逗号分隔。
#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(s);
return 0;
}
想象一下,当你从数据库中取出一条记录,然后想将它们的字段按照一定的规则连接成一个字符串时,就可以使用这种方法。 理论上应该比连续的效率更高,因为每次Every调用都需要先找到最后一个''的位置,而在上面给出的例子中,我们每次都是用返回值直接记录这个位置。
使用常见问题
它是一个可变参数函数,在使用时经常会出现问题,而每当出现问题时,通常都是内存访问,会导致程序崩溃。
询问错误,还好,误操作造成的问题虽然严重,但很容易发现。 无非有几种情况。
用你的眼睛看几次错误的代码就可以看到它。
()是()的安全版本,通过指定缓冲区长度避免了()的溢出风险。 如果在使用过程中使用了某个函数,编译器会发出警告:使用存在风险,建议使用。 这个安全版本的原型是:
int (char *, ,const char [,] … );
缓冲区溢出
第一个参数的长度太短。 没什么可说的。 给它一个更大的空间。当然,也可能是后续参数的问题。
对于这个问题,建议大家在对应可变参数的时候要小心,并且在打印字符串的时候,尽量使用“%.ns”的形式来指定最大字符数。
忘记第一个参数了
这是一个非常低级的问题。 它被使用得太频繁了。 //偶尔会犯这个错误。 :. (
变量参数对应问题
通常,您忘记提供与某个格式字符对应的可变参数,导致所有后续参数都放错位置。 检查一下。特别是
是否提供了所有与“”对应的参数?不要将整数与“%s”关联,编译器会认为你
欺负她太过分了(编译器是obj和exe之母,所以她应该是个女人)。
还有一个好表弟: ,专门用来格式化时间字符串。 它的用法和她表弟很相似,而且
格式控制字符很多,但毕竟小姑娘很细心,她也想让调用者指定缓冲区的最大长度,也许是为了
当问题出现时,你可以推卸责任。 这是一个例子:
t = 时间(0);
//生成“YYYY-MM-DD hh:mm:ss”格式的字符串。
字符s[32];
(s, (s), “%Y-%m-%d %H:%M:%S”, (&t));
他还可以在MFC中找到他的挚友:::,自然也有她在MFC中的同事:
CTime::,这一对得到了面向对象的支持,写出的代码更加优雅。