字符数组
-
在内存(栈、静态全局区)中开辟了一段空间存放字符串,存放的字符串的内容可以修改,但是字符数组的名字是个常量。
例如:
char str[]="I love C!"; //(后面自带了\0)
str = "hello world!"; //错误,字符数组名是个常量
示例:
char str[] = "I love C!";
printf("%s\n", str);
str[3] = 'O'; // 正确,字符数组的元素可以修改
printf("%s\n", str);
输出:
I love C!
I lOve C!
字符串指针
-
在文字常量区开辟了一段空间存放字符串,将字符串的首地址赋给str。文字常量区的内容不可以修改。
char *str="I love C!"
strcpy(str, "hello world!"); //错误,文字常量区的内容不可以修改
str = "hello world!"; //正确,str指向另一个字符串
堆
- 使用malloc函数在堆区申请空间,将字符串拷贝到堆区。堆区的内容可以修改。不能初始化,只能使用strcpy和scanf赋值。
const修饰符
- const char *str
str指向的内存的内容不能通过str来修改,但是str的指向是可以改变的
- char const *str
str是只读变量,str不能指向别的地方
- const char const *str
str是只读变量,str不能指向别的地方,同时str指向的内存的内容也不能通过str来修改
1、测字符串长度函数
头文件: #include <string.h>
定义: size_t strlen(const char *s);
功能:测字符指针s指向的字符串中字符的个数,不包括'\0'.
例:
char str1[20]="hello";
char *str2="hello";
printf("%d\n", sizeof(str1)); //20 数组的容量
printf("%d\n", sizeof(str2)); //4 指针变量的长度
printf("%d\n", strlen(str1)); //5 数组中存放字符串的长度,即数组中存放内容的长度
printf("%d\n", strlen(str2)); //5 字符串常量指针指向的内容的长度
所以,其实在看待str1和str2时,还是应该有区别的,并不是两者都是指针这么简单。str1是个数组,只不过str1这个名字可以用来作为数组的首地址,表现得像个指针,其实str2才是个(常量)指针。
sizeof测量的是变量所能容纳的最大空间大小,比如str1,它能容纳的最大空间是20个字节,即使当前只是放了“hello”这5个字节;而str2是个指针变量,它最大可容纳的空间就是只有4个字节,即一个指针的大小。strlen测量的是变量中实际存放的内容长度,不包括'\0'.
2、字符串拷贝函数
头文件: #include <string.h>
定义: char *strcpy(char *dest, const char *src);
功能:'\0'也会拷贝. 返回目的内存地址。
注意:必须保证dest指向的内存空间足够大,否则出现内存污染;同时保证dest指向的空间可写。
定义: char *strncpy(char *dest, const char *src, size_t n);
功能:返回目的内存地址。
注意:不拷贝'\0';
如果n大于src指向的字符串中的字符个数,则在dest后面填充n-strlen(src)个'\0'.
3、字符串追加函数
头文件: #include <string.h>
定义: char *strcat(char *dest, const char *src);
功能:追加src字符串到dest指向的字符串的后面,会追加'\0'. 返回目的内存地址。
注意:必须保证dest指向的内存空间足够大,否则出现内存污染;同时保证dest指向的空间可写。
定义: char *strncat(char *dest, const char *src, size_t n);
功能:追加src字符串的前n个字符到dest指向的字符串的后面,返回目的内存地址。如果n大于src指向的字符串中的字符个数,则会追加'\0'.
注意:必须保证dest指向的内存空间足够大,否则出现内存污染;同时保证dest指向的空间可写。
4、字符查找函数
头文件: #include <string.h>
定义: char *strchr(const char *s, int c);
功能:在字符指针s指向的字符串中,找ascii码为c的字符
注意:是首次匹配,如果说s指向的字符串中有多个ascii为c的字符,则找的是第一个字符。
返回值:找到了返回找到的字符的地址,找不到返回NULL。
定义: char *strrchr(const char *s, int c);
功能:在字符指针s指向的字符串中,找最后一次出现的ascii码为c的字符。
5、字符串匹配函数
头文件: #include <string.h>
定义: char *strstr(const char *src, const char *need);
功能:在字符指针src指向的字符串中,查找need指向的字符串,首次匹配
返回值:找到了返回找到的字符串的地址,找不到返回NULL。
6、字符串切割函数
头文件: #include <string.h>
定义: char *strtok(char *str, const char *delim);
功能:按照delim指向的字符串中的字符,切割str指向的字符串。其实就是在str指向的字符串中发现了delim字符串中的字符,将其变成'\0',调用一次strtok只切割一次,切割一次之后,再去切割的时候strtok的第一个参数传NULL,意思是接着上次切割的位置继续切割。
返回值:该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。
例如:
#define LEN 9
char str[100] = "lucy:yuxin,hanbing,xiaoyang:xueyang;zhanghu; :,yongheng";
char* p[LEN];
int i = 0, j;
printf("str: %s\n", str);
p[i] = strtok(str, ",:;");
printf("p[%d]: %s\n", i, p[i]);
while (p[i] != NULL) {
for (j = 0; j <= i; j++) {
printf("--- %d --- p[%d] addr %p: %s\n", i, j, (unsigned int )p[j], p[j]);
}
i++;
p[i] = strtok(NULL, ",:;");
printf("---------- round end, i = %d addr %p ----------\n", i, (unsigned int)p[i]);
}
for (j = 0; j < LEN; j++) {
printf("p[%d] addr %p: %s\n", j, (unsigned int)p[j], p[j]);
}
printf("str: %s\n", str);
输出:
str: lucy:yuxin,hanbing,xiaoyang:xueyang;zhanghu; :,yongheng
p[0]: lucy
--- 0 --- p[0] addr 001DF8E4: lucy
---------- round end, i = 1 addr 001DF8E9 ----------
--- 1 --- p[0] addr 001DF8E4: lucy
--- 1 --- p[1] addr 001DF8E9: yuxin
---------- round end, i = 2 addr 001DF8EF ----------
--- 2 --- p[0] addr 001DF8E4: lucy
--- 2 --- p[1] addr 001DF8E9: yuxin
--- 2 --- p[2] addr 001DF8EF: hanbing
---------- round end, i = 3 addr 001DF8F7 ----------
--- 3 --- p[0] addr 001DF8E4: lucy
--- 3 --- p[1] addr 001DF8E9: yuxin
--- 3 --- p[2] addr 001DF8EF: hanbing
--- 3 --- p[3] addr 001DF8F7: xiaoyang
---------- round end, i = 4 addr 001DF900 ----------
--- 4 --- p[0] addr 001DF8E4: lucy
--- 4 --- p[1] addr 001DF8E9: yuxin
--- 4 --- p[2] addr 001DF8EF: hanbing
--- 4 --- p[3] addr 001DF8F7: xiaoyang
--- 4 --- p[4] addr 001DF900: xueyang
---------- round end, i = 5 addr 001DF908 ----------
--- 5 --- p[0] addr 001DF8E4: lucy
--- 5 --- p[1] addr 001DF8E9: yuxin
--- 5 --- p[2] addr 001DF8EF: hanbing
--- 5 --- p[3] addr 001DF8F7: xiaoyang
--- 5 --- p[4] addr 001DF900: xueyang
--- 5 --- p[5] addr 001DF908: zhanghu
---------- round end, i = 6 addr 001DF910 ----------
--- 6 --- p[0] addr 001DF8E4: lucy
--- 6 --- p[1] addr 001DF8E9: yuxin
--- 6 --- p[2] addr 001DF8EF: hanbing
--- 6 --- p[3] addr 001DF8F7: xiaoyang
--- 6 --- p[4] addr 001DF900: xueyang
--- 6 --- p[5] addr 001DF908: zhanghu
--- 6 --- p[6] addr 001DF910:
---------- round end, i = 7 addr 001DF913 ----------
--- 7 --- p[0] addr 001DF8E4: lucy
--- 7 --- p[1] addr 001DF8E9: yuxin
--- 7 --- p[2] addr 001DF8EF: hanbing
--- 7 --- p[3] addr 001DF8F7: xiaoyang
--- 7 --- p[4] addr 001DF900: xueyang
--- 7 --- p[5] addr 001DF908: zhanghu
--- 7 --- p[6] addr 001DF910:
--- 7 --- p[7] addr 001DF913: yongheng
---------- round end, i = 8 addr 00000000 ----------
p[0] addr 001DF8E4: lucy
p[1] addr 001DF8E9: yuxin
p[2] addr 001DF8EF: hanbing
p[3] addr 001DF8F7: xiaoyang
p[4] addr 001DF900: xueyang
p[5] addr 001DF908: zhanghu
p[6] addr 001DF910:
p[7] addr 001DF913: yongheng
p[8] addr 00000000: (null)
str: lucy
从上面的输出可以看到,p[0]、p[1]...其实分别指向了str中相应的位置,如p[1]是p[0]后移了5个字节(lucy 4个字节和一个':'),空格也被保存了下来,最后一次切割返回的是NULL。需要注意的是,最后str的值变化了,因为lucy后面的':'变成了'\0'。
(1)把LEN改成8,看看打印是怎么样的:
输出:
str: lucy:yuxin,hanbing,xiaoyang:xueyang;zhanghu; :,yongheng
p[0]: lucy
--- 0 --- p[0] addr 0014FB8C: lucy
---------- round end, i = 1 addr 0014FB91 ----------
--- 1 --- p[0] addr 0014FB8C: lucy
--- 1 --- p[1] addr 0014FB91: yuxin
---------- round end, i = 2 addr 0014FB97 ----------
--- 2 --- p[0] addr 0014FB8C: lucy
--- 2 --- p[1] addr 0014FB91: yuxin
--- 2 --- p[2] addr 0014FB97: hanbing
---------- round end, i = 3 addr 0014FB9F ----------
--- 3 --- p[0] addr 0014FB8C: lucy
--- 3 --- p[1] addr 0014FB91: yuxin
--- 3 --- p[2] addr 0014FB97: hanbing
--- 3 --- p[3] addr 0014FB9F: xiaoyang
---------- round end, i = 4 addr 0014FBA8 ----------
--- 4 --- p[0] addr 0014FB8C: lucy
--- 4 --- p[1] addr 0014FB91: yuxin
--- 4 --- p[2] addr 0014FB97: hanbing
--- 4 --- p[3] addr 0014FB9F: xiaoyang
--- 4 --- p[4] addr 0014FBA8: xueyang
---------- round end, i = 5 addr 0014FBB0 ----------
--- 5 --- p[0] addr 0014FB8C: lucy
--- 5 --- p[1] addr 0014FB91: yuxin
--- 5 --- p[2] addr 0014FB97: hanbing
--- 5 --- p[3] addr 0014FB9F: xiaoyang
--- 5 --- p[4] addr 0014FBA8: xueyang
--- 5 --- p[5] addr 0014FBB0: zhanghu
---------- round end, i = 6 addr 0014FBB8 ----------
--- 6 --- p[0] addr 0014FB8C: lucy
--- 6 --- p[1] addr 0014FB91: yuxin
--- 6 --- p[2] addr 0014FB97: hanbing
--- 6 --- p[3] addr 0014FB9F: xiaoyang
--- 6 --- p[4] addr 0014FBA8: xueyang
--- 6 --- p[5] addr 0014FBB0: zhanghu
--- 6 --- p[6] addr 0014FBB8:
---------- round end, i = 7 addr 0014FBBB ----------
--- 7 --- p[0] addr 0014FB8C: lucy
--- 7 --- p[1] addr 0014FB91: yuxin
--- 7 --- p[2] addr 0014FB97: hanbing
--- 7 --- p[3] addr 0014FB9F: xiaoyang
--- 7 --- p[4] addr 0014FBA8: xueyang
--- 7 --- p[5] addr 0014FBB0: zhanghu
--- 7 --- p[6] addr 0014FBB8:
--- 7 --- p[7] addr 0014FBBB: yongheng
---------- round end, i = 8 addr 00000000 ----------
p[0] addr 0014FB8C:
p[1] addr 0014FB91: yuxin
p[2] addr 0014FB97: hanbing
p[3] addr 0014FB9F: xiaoyang
p[4] addr 0014FBA8: xueyang
p[5] addr 0014FBB0: zhanghu
p[6] addr 0014FBB8:
p[7] addr 0014FBBB: yongheng
str:
可以看到,前面的输出与LEN是9时是一致的,但是到最后的输出发生了变化,p[0]中的字符串变成了空,str也变成了空,且最后切割时返回的NULL也没有保存下来(其实最后切割返回的NULL保存到了p[8]中,发生了溢出),所以在用这个切割函数时,务必要把最后的NULL也考虑进去,多分配一个保存NULL指针的空间,否则会出错。
(2)把LEN改回9,同时在str的前面和后面加几个分隔符,看看打印是怎么样的:
char str[100] = "::,lucy:yuxin,hanbing,xiaoyang:xueyang;zhanghu; :,yongheng;;";
输出:
str: ::,lucy:yuxin,hanbing,xiaoyang:xueyang;zhanghu; :,yongheng;;
p[0]: lucy
--- 0 --- p[0] addr 0024FBDF: lucy
---------- round end, i = 1 addr 0024FBE4 ----------
--- 1 --- p[0] addr 0024FBDF: lucy
--- 1 --- p[1] addr 0024FBE4: yuxin
---------- round end, i = 2 addr 0024FBEA ----------
--- 2 --- p[0] addr 0024FBDF: lucy
--- 2 --- p[1] addr 0024FBE4: yuxin
--- 2 --- p[2] addr 0024FBEA: hanbing
---------- round end, i = 3 addr 0024FBF2 ----------
--- 3 --- p[0] addr 0024FBDF: lucy
--- 3 --- p[1] addr 0024FBE4: yuxin
--- 3 --- p[2] addr 0024FBEA: hanbing
--- 3 --- p[3] addr 0024FBF2: xiaoyang
---------- round end, i = 4 addr 0024FBFB ----------
--- 4 --- p[0] addr 0024FBDF: lucy
--- 4 --- p[1] addr 0024FBE4: yuxin
--- 4 --- p[2] addr 0024FBEA: hanbing
--- 4 --- p[3] addr 0024FBF2: xiaoyang
--- 4 --- p[4] addr 0024FBFB: xueyang
---------- round end, i = 5 addr 0024FC03 ----------
--- 5 --- p[0] addr 0024FBDF: lucy
--- 5 --- p[1] addr 0024FBE4: yuxin
--- 5 --- p[2] addr 0024FBEA: hanbing
--- 5 --- p[3] addr 0024FBF2: xiaoyang
--- 5 --- p[4] addr 0024FBFB: xueyang
--- 5 --- p[5] addr 0024FC03: zhanghu
---------- round end, i = 6 addr 0024FC0B ----------
--- 6 --- p[0] addr 0024FBDF: lucy
--- 6 --- p[1] addr 0024FBE4: yuxin
--- 6 --- p[2] addr 0024FBEA: hanbing
--- 6 --- p[3] addr 0024FBF2: xiaoyang
--- 6 --- p[4] addr 0024FBFB: xueyang
--- 6 --- p[5] addr 0024FC03: zhanghu
--- 6 --- p[6] addr 0024FC0B:
---------- round end, i = 7 addr 0024FC0E ----------
--- 7 --- p[0] addr 0024FBDF: lucy
--- 7 --- p[1] addr 0024FBE4: yuxin
--- 7 --- p[2] addr 0024FBEA: hanbing
--- 7 --- p[3] addr 0024FBF2: xiaoyang
--- 7 --- p[4] addr 0024FBFB: xueyang
--- 7 --- p[5] addr 0024FC03: zhanghu
--- 7 --- p[6] addr 0024FC0B:
--- 7 --- p[7] addr 0024FC0E: yongheng
---------- round end, i = 8 addr 00000000 ----------
p[0] addr 0024FBDF: lucy
p[1] addr 0024FBE4: yuxin
p[2] addr 0024FBEA: hanbing
p[3] addr 0024FBF2: xiaoyang
p[4] addr 0024FBFB: xueyang
p[5] addr 0024FC03: zhanghu
p[6] addr 0024FC0B:
p[7] addr 0024FC0E: yongheng
p[8] addr 00000000: (null)
str: ::,lucy
这时,p[0]p[1]...p[8]还是正常的,但是最后str的前面还是有分隔符存在(注意p[0]中没有分隔符)。
7、sscanf函数
作用:从一个字符串中读进与指定格式相符的数据
原型:Int sscanf( string str, string fmt, mixed var1, mixed var2 ... );
说明:sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源。
其中的format可以是一个或多个 {%[*] [width] [{h | l | I64 | L}]type | ' ' | '\t' | '\n' | 非%符号}
注:
1、 * 亦可用于格式中, (即 %*d 和 %*s) 加了星号 (*) 表示跳过此数据不读入. (也就是不把此数据读入参数中)
2、{a|b|c}表示a,b,c中选一,[d],表示可以有d也可以没有d。
3、width表示读取宽度。
4、{h | l | I64 | L}:参数的size,通常h表示单字节size,I表示2字节 size,L表示4字节size(double例外),l64表示8字节size。
5、type :这就很多了,就是%s,%d之类。
6、特别的:%*[width] [{h | l | I64 | L}]type 表示满足该条件的被过滤掉,不会向目标参数中写入值
支持集合操作:
%[a-z] 表示匹配a到z中任意字符,贪婪性(尽可能多的匹配)
%[aB'] 匹配a、B、'中一员,贪婪性%[^a] 匹配非a的任意字符,贪婪性
1).跳过数据:%*s或%*d
sscanf("1234 5678", "%*d%s", buf);
printf("-%s\n", buf); //输出-5678\. 跳过1234,然后跳过空格获取字符串
sscanf("1234 00 5678", "%*d%s", buf);
printf("-%s\n", buf); //输出-00
2).读指定宽度数据:%[width]s
sscanf("12345678", "%4s", buf); //从字符串中获取字符串,只要4个字节,存放在buf中
printf("%s\n", buf); //输出1234
sscanf("12345678", "%3d", &num);
printf("%d\n", num); //输出123
sscanf("12345678", "%*3d%3d", &num);
printf("%d\n", num); //输出456
sscanf("1234 00 5678", "%*d%6s", buf);
printf("-%s\n", buf); //输出-00(自带空格隔断功能!?)
sscanf("1234 00 5678", "%8s", buf); //buf定义:char buf[50];
printf("-%s\n", buf); //输出-1234
sscanf("1234 00 5678", "%8d", &aa); //aa是一个int型变量
printf("-%d\n", aa); //输出-1234
3).支持集合操作:只支持获取字符串
sscanf("agcd32DajfDdFF", "%[a-z]", buf);
printf("%s\n", buf); //输出agcd
sscanf("agcd32DajfDdFF", "%*[a-z]%5s", buf);
printf("%s\n", buf); //输出32Daj
sscanf("H43AF0agcd32DajfDdFF", "%[^a-z]", buf);
printf("%s\n", buf); //输出H43AF0
sscanf("H43AF0agcd32DajfDdFF", "%[4Hjc3A2F]", buf);
printf("%s\n", buf); //输出H43AF,即遇到不在“4Hjc3A2F”范围中的字符就停止
4).注意下面两个的区别
sscanf("agcd32DajfDdFF", "%s", buf);
printf("%s\n", buf); //输出agcd32DajfDdFF
sscanf("agcd32DajfDdFF", "%1s", buf);
printf("%s\n", buf); //输出a
8、sprintf()函数
作用:用于将输出存到字符缓冲中。
原型:sprintf(char *buffer, const char *format, [argument]);
int a=1,b=2;
char s[10];
sprintf(s,"a=%d,b=%d",1,2);
puts(s);
输出:
a=1,b=2
9、vsnprintf函数
在讲vsnprintf函数之前,先来看看va_start、va_arg和va_end这三个宏。
C中变长实参头文件stdarg.h提供了一个数据类型va_list和三个宏(va_start、va_arg和va_end),用它们在被调用函数不知道参数个数和类型时对可变参数表进行测试,从而为访问可变参数提供了方便且有效的方法。va_list是一个char类型的指针,当被调用函数使用一个可变参数时,它声明一个类型为va_list的变量,该变量用来指向va_arg和va_end所需信息的位置。
示例:
int sum(int n, ...) //sum函数就是一个求和函数,固定参数n表示需要求和的参数个数,...表示可变参数
{
printf("n = %d\n",n);
int i, j, sum = 0;
va_list vap;
va_start(vap, n); // 指向可变参数表中的第一个参数
printf("vap = %d\n", *vap);
for (i = 0; i < n; ++i) {
j = va_arg(vap, int); //取出可变参数表中的参数,并修改参数指针vap使其增加以指向表中下一个参数
printf("va_arg(vap, int) = %d\n", j);
sum += j;
}
va_end(vap); //把指针vap赋值为0
return sum;
}
int main(void)
{
int m = sum(3, 40, 80, 70);
cout << m << endl;
return 0;
}
输出:
n = 3
vap = 40
va_arg(vap, int) = 40
va_arg(vap, int) = 80
va_arg(vap, int) = 70
190
注意:sum不能定义成“int sum(...)”,因为这是ANSI C要求至少得定义一个固定参数。
下面回到vsnprintf函数。
函数原型:int vsnprintf(char *str, size_t size, const char *format, va_list ap);
头文件: #include <stdarg.h>
函数功能:将可变参数格式化输出到一个字符数组
参数说明:
char *str [out] - 把生成的格式化的字符串存放在这里.
size_t size [in] - str可接受的最大字符数(非字节数,UNICODE一个字符两个字节),防止产生数组越界.
const char *format [in] - 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序。
va_list ap [in] - va_list变量(va:variable-argument:可变参数)
返回值: 执行成功,返回最终生成字符串的长度,若生成字符串的长度大于size,则将字符串的前size个字符复制到str,同时将原串的长度返回(不包含终止符);执行失败,返回负值,并置errno.
备注: linux环境下是:vsnprintf,VC6环境下是:_vsnprintf
示例:
#include <stdio.h>
#include <stdarg.h>
#define MAXLEN 10
char* test_end = "HERE.";
int mon_log(char* format, ...)
{
char str_tmp[MAXLEN];
int i = 0, j = 0;
va_list vArgList;
va_start(vArgList, format);
i = vsnprintf(str_tmp, MAXLEN, format, vArgList);
va_end(vArgList);
printf("%s", str_tmp);
printf("%s\n", test_end);
return i;
}
void main()
{
int i = mon_log("%s,%d,%d", "abc", 2, 3); //"%s,%d,%d"就对应固定参数format,"abc","2","3"就对应可变参数...
printf("i = %d\n", i);
}
输出:
abc,2,3HERE.
i = 7
"abc,2,3"是str_tmp的内容,总共7个字符。"HERE"是结尾测试字符串,用于标记str_tmp的结束。 vsnprintf会自动在写入字符的后面加上停止符\0。
对上面的例子做少许改动,如下:
#include <stdio.h>
#include <stdarg.h>
#define MAXLEN 10
char* test_end = "HERE.";
int mon_log(char* format, ...)
{
char str_tmp[MAXLEN];
int i = 0, j = 0;
va_list vArgList;
va_start(vArgList, format);
i = vsnprintf(str_tmp, MAXLEN, format, vArgList);
va_end(vArgList);
printf("%s", str_tmp);
printf("%s\n", test_end);
for (j = 0; j < MAXLEN; j++)
{
printf("%d ", str_tmp[j]);
}
printf("\n");
return i;
}
void main()
{
int i = mon_log("%s,%d,%d,%c", "abc", 2, 3, '\n');
printf("i = %d\n", i);
}
输出:
abc,2,3,
HERE.
97 98 99 44 50 44 51 44 10 0 // 这里前面9个字符就是写到str_tmp中的9个字符,最后一个0就是添加的停止符\0.
i = 9
如上,str_tmp最大10个字符,在写入9个字符后,自动添加了\0(输出中第三行最后一个ASCII码为0的字符)。由于前9个字符的最后一个是换行符(第三行倒数第二个ASCII码为10的字符),所以代码"printf("%s", str_tmp)"并没有增加换行符打印,但是实际还是有换行效果。
将main函数中的代码改成下面,即增加一个字符d。
int i=mon_log(“%s,%d,%d,%c”,”abcd”,2,3, ‘\n’);
输出:
abcd,2,3,HERE.
97 98 99 100 44 50 44 51 44 0
i = 10
此时换行符没有被写到str_tmp,因此代码"printf("%s", str_tmp)"没有换行效果。但是停止符还是会写到str_tmp中。继续测试下面的情况:
int i=mon_log(“%s,%d,%d,%c”,”abcde”,2,3, ‘\n’);
输出;
abcde,2,3HERE.
97 98 99 100 101 44 50 44 51 0
i = 11
停止符还是会写到str_tmp中,但是观察到返回值i在依次增加(从9到11)。故而对于返回值,等于size或者大于size,表示输出到buffer的字符被截断,如果输出过程中遇到错误,则返回一个负数。这里的size为10,所以上面两种方式都已经发生截断,截断后的剩余字符外加一个停止符会被写到数组str_tmp中,保证数组不越界。也就是说,数组中有一个位置是固定要留给停止符的,实际能拷贝到数组里面的字符个数是size-1。
与vsnprintf函数类似的还有vsprintf函数,这里不再细讲了,有兴趣自己研究吧。