26 KiB
介绍
字符串实际是一串字符的集合,在C语言中,字符串是为char类型的数组,在结尾加上结束符\0则为字符串,没有结束符则为字符数组。
在C语言中,没有专门的字符串变量,而varch中的str则专门针对数组字符串进行了封装,封装成不需要关注底层存储结构的字符串变量。str实际还是通过连续地址的空间进行存储字符集,但是这些存储的工作不需要额外去关注,str提供的方法已经实现存储的工作,而且str也提供符合字符串操作的方法,方便字符串的灵活操作。方便说明,后面的传统的C语言字符串称为数组字符串,varch提供的成为str字符串。
接口
创建和删除str字符串对象
str_t str_create(void *string);
void str_delete(str_t str);
#define str(str) // 为了更简便的使用,对str_create套一层宏定义
#define _str(str) // 对str_delete套一层宏定义,并在str删除后置为空
其中str_t为str的结构体,创建方法则会返回一个str对象,创建失败则返回NULL,其中string传入初始化的字符串,这个string定义为了void *的类型,所以可以支持数组字符串和str字符串的两种传参初始化(后续介绍中的void *string均是可以支持这两种字符串)。删除方法则是删除传入的str对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。
void test(void)
{
str_t ss = str("Hello str!"); // 用数组字符串进行赋值构造
str_t copy = str(ss); // 用str字符串进行拷贝构造
_str(ss); // 删除str
_str(copy); // 删除str
}
str字符串的数组字符串
#define str_c_str(str)
#define _S(str)
str存储字符串也是通过连续地址空间进行存储,获取此地址即可访问到实际存储的字符串。但在使用中,建议不要通过此方法把字符串当成数组字符串来修改。有同样作用的方法还有char* str_data(str_t str, int pos);,str_c_str()也是套用str_data实现的,此方法不建议替代str_c_str()来用。而_S()则是简短版的。
void test(void)
{
str_t ss = str("Hello str!"); // 用数组字符串进行赋值构造
str_t copy = str(ss); // 用str字符串进行拷贝构造
printf("ss: %s\r\n", _S(ss));
printf("copy: %s\r\n", _S(copy));
_str(ss); // 删除str
_str(copy); // 删除str
}
结果:
ss: Hello str!
copy: Hello str!
str赋值
str_t str_assign(str_t str, void *string);
str抽象字符串为一个类了,给str进行赋值操作,类似于=号的赋值操作。如同前面所说的void *string可以支持数组字符串和str字符串。
void test(void)
{
str_t ss = str(""); // 初始化为空字符串
printf("ss: %s\r\n", _S(ss));
str_assign(ss, "Hello str!"); // 赋值为 Hello str!
printf("ss: %s\r\n", _S(ss));
str_assign(ss, "This is the assignment method!"); // 再赋值为 This is the assignment method!
printf("ss: %s\r\n", _S(ss));
_str(ss);
}
结果:
ss:
ss: Hello str!
ss: This is the assignment method!
从中可以看到str不需要像数组字符串那样去关注定义的空间的大小问题,而由str内部去进行适应。
str拼接
#define str_append(str, ...)
str除了有类似=号的赋值操作,还有类似+=号的拼接操作。
形参后面不定参数,可以拼接至少一个任意数量的字符串。同样,可以是数组字符串也可以是str字符串。
str_append方法的原型是str_t str_append_series(str_t str, ...);,原型函数不定参数需要接NULL结尾,不建议使用原型方法。
void test(void)
{
str_t name = str("123456789");
str_t ident = str("qq");
str_t email = str("");
str_append(email, name, "@", ident, ".com"); // 拼接若干个字符串
printf("%s\r\n", str_c_str(email));
_str(name);
_str(ident);
_str(email);
}
结果:
123456789@qq.com
str插入
str_t str_insert(str_t str, int pos, void *string);
在str中插入一个字符串,并返回自己。pos是插入的位置,string是插入的源字符串。
void test(void)
{
str_t ss = str("0123456");
str_insert(ss, 3, "|insert|"); // 在位置3插入字符串
printf("ss: %s\r\n", str_c_str(ss));
_str(ss);
}
结果:
ss: 012|insert|3456
str移除
str_t str_erase(str_t str, int pos, int len);
从str中移除一段字符,并返回自己。pos是移除的位置,len是移除的长度。
void test(void)
{
str_t ss = str("0123456789");
str_erase(ss, 3, 3); // 移除位置3后面三个字符,也就是移除 345
printf("ss: %s\r\n", str_c_str(ss));
_str(ss);
}
结果:
ss: 0126789
str尾部推入和弹出
int str_push_back(str_t str, char c);
char str_pop_back(str_t str);
这两个方法分别从str字符串尾部推入和弹出一个字符,推入方法的返回值1成功0失败,弹出方法返回弹出的字符。
str存储结构和vector类似,在尾部操作具有比较高的效率。
void test(void)
{
str_t ss = str("0123456789");
str_push_back(ss, 'A');
printf("ss: %s\r\n", str_c_str(ss));
printf("pop %c\r\n", str_pop_back(ss));
printf("ss: %s\r\n", str_c_str(ss));
_str(ss);
}
结果:
ss: 0123456789A
pop A
ss: 0123456789
str字符串比较
int str_compare(str_t str, void *string);
比较str与另外一个字符串,0为相等,-1为str小于string,1为str大于string。
void test(void)
{
str_t ss = str("01234");
str_t copy = str(ss);
printf("compare: %d\r\n", str_compare(ss, copy));
printf("compare: %d\r\n", str_compare(ss, "01233"));
printf("compare: %d\r\n", str_compare(ss, "012345"));
_str(ss);
_str(copy);
}
结果:
compare: 0
compare: 1
compare: -1
str创建子串
str_t str_substr(str_t str, int pos, int len);
这个方法是从str中创建一个新的子串,切记,在结束使用后,调用_str(str)方法来删除。。
pos传入起始位置,len传入创建的长度。
void test(void)
{
str_t ss = str("0123456789");
str_t sub = str_substr(ss, 4, 2);
printf("ss: %s\r\n", str_c_str(ss));
printf("sub: %s\r\n", str_c_str(sub));
_str(ss);
_str(sub);
}
结果:
ss: 0123456789
sub: 45
str字符串长度
int str_length(str_t str);
str标准的获取长度的方法是这个,当然也可以用strlen方法去量,但是对于str对象没必要,因为这个标准的方法是不用耗费时间去量的,返回记录好了的长度。
void test(void)
{
str_t ss = str("0123456789");
printf("length: %d\r\n", str_length(ss));
_str(ss);
}
结果:
length: 10
str字符读写
#define str_at(str, i)
str提供了针对其中的字符进行读写操作,当作数组的元素使用即可。
但切记,不要把字符修改成\0了。
void test(void)
{
str_t ss = str("0123456789");
printf("ss[3] = %c\r\n", str_at(ss, 3));
str_at(ss, 3) = 'A';
printf("ss: %s\r\n", _S(ss));
_str(ss);
}
结果:
ss[3] = 3
ss: 012A456789
str字符串查找
int str_find(str_t str, void *string, int pos);
int str_rfind(str_t str, void *string, int pos);
这两个方法分别从正向和反向查找字符子串,string传入字符串,pos传入开始查找的起始索引,返回则是返回子串所在的位置索引,没找到就返回str_npos。
void test(void)
{
str_t ss = str("0123456789");
printf("find = %d\r\n", str_find(ss, "3456", 0)); // 从0位置开始找
printf("rfind = %d\r\n", str_rfind(ss, "23", str_length(ss) - 1)); // 从尾端开始往回找
printf("find = %d\r\n", str_find(ss, "0000", 0)); // 找一个不存在的字符串
_str(ss);
}
结果:
find = 3
rfind = 2
find = 2147483647
str查找匹配字符集
int str_find_first_of(str_t str, void *string, int pos);
int str_find_first_not_of(str_t str, void *string, int pos);
int str_find_last_of(str_t str, void *string, int pos);
int str_find_last_not_of(str_t str, void *string, int pos);
这四个方法和str_find、str_rfind方法不一样,find和rfind需要匹配全部字符,而这四个方法只要匹配上字符集的任意之一就匹配成功。
同样,string传入字符串(匹配字符集),pos传入开始查找的起始索引,返回则是返回子串所在的位置索引,没找到就返回str_npos。这四个函数分别找到**“第一个包含”、“第一个不包含”、“最后一个包含”、“最后一个不包含”**
例子:实际应用获取一个文件路径的文件和所在盘符
void test(void)
{
str_t ss = str("C:/workspace/project/C/00 - vlib/Project/main.c"); // 先给定一个文件路径
str_t pan = NULL; // 初始化为NULL,不构建
str_t filename = NULL; // 初始化为NULL,不构建
pan = str_substr(ss, 0, str_find_first_of(ss, ":", 0)); // 先找到第一个 :
filename = str_substr(ss, str_find_last_of(ss, "\\/", str_length(ss)-1) + 1, str_length(ss)); // 找到最后一个 \ 或者 / 路径分隔符
printf("pan: %s\r\n", str_c_str(pan));
printf("filename: %s\r\n", str_c_str(filename));
_str(ss);
_str(pan);
_str(filename);
}
结果:
pan: C
filename: main.c
str字符串翻转
str_t str_reverse(str_t str, int begin, int end);
这个方法将str指定区间的字符串整体翻转。begin为区间起始位置,end为区间结束位置。
void test(void)
{
str_t ss = str("0123456789");
str_reverse(ss, 2, 8);
printf("ss = %s\r\n", str_c_str(ss));
_str(ss);
}
结果:
ss = 0187654329
str字符替换
str_t str_replace(str_t str, int pos, int len, void *string);
这个方法把指定位置的字符按长度替换成指定的字符串。
void test(void)
{
str_t ss = str("My name is ZhangSan!");
printf("ss = %s\r\n", str_c_str(ss));
str_replace(ss, 11, 8, "Lisi"); // 把ZhangSan替换成Lisi
printf("ss = %s\r\n", str_c_str(ss));
_str(ss);
}
结果:
ss = My name is ZhangSan!
ss = My name is Lisi!
str字符串交换
void str_swap(str_t str, str_t swap);
将两个字符串进行交换。
void test(void)
{
str_t s1 = str("This s1!");
str_t s2 = str("This s2!");
str_swap(s1, s2);
printf("s1 = %s\r\n", str_c_str(s1));
printf("s2 = %s\r\n", str_c_str(s2));
_str(s1);
_str(s2);
}
结果:
s1 = This s2!
s2 = This s1!
str字符串复制到数组
int str_copy(str_t str, int pos, int len, char *buf);
将str字符串指定位置的字符复制到指定的数组。其中,pos复制的起始位置。len复制的最大长度。buf就是接收复制数据的数组。函数返回实际复制的长度。
void test(void)
{
str_t ss = str("0123456789");
char array[32] = { 0 };
int length = 0;
length = str_copy(ss, 5, sizeof(array), array); // 从位置开始复制,复制最大长度为数组的长度
array[length] = '\0'; // 在后面添加结束符,方便打印
printf("length = %d, copy: %s\r\n", length, array);
_str(ss);
}
结果:
length = 5, copy: 56789
str字符串格式化
str_t str_format(str_t str, char *format, ...);
str字符串格式化操作,套用sprintf函数进行了重写,与其用法基本一致。增加了%s对str类型的支持,str_format对格式化进行分段的动态扩容,在使用上不需要像sprintf那样关注buf长度是否超出的问题,使用起来更加的简便。
void test(void)
{
str_t ss = str("");
str_t pp = str("format");
str_format(ss, "Hello str! %s %s! int:%d, float:%.2f, char:%c", pp, "function", 18, 175.5, 'A');
printf("ss: %s\r\n", str_c_str(ss));
_str(ss);
_str(pp);
}
结果:
ss: Hello str! format function! int:18, float:175.50, char:A
从这个例子看,str_format不用去关注空间的问题,%s除了对char*支持也可以对str_t对象支持,做到了两种字符串的兼容。
参考例子
案例1
输入一个字符串,判断是否为回文字符串,即从左到右和从右到左完全一致。
void test(void)
{
str_t s = str("");
str_t r = str("");
char buf[64] = {0};
while (1)
{
scanf("%s", buf); // 输入一个字符串
str_assign(s, buf); // 将s赋值为输入的字符串
str_assign(r, s); // 将r赋值为s
str_reverse(r, 0, str_length(r) - 1); // 将r进行翻转
if (str_compare(s, r) == 0) // 比较s和r是否一致
{
printf("这是一个回文数字符串!\r\n");
}
else
{
printf("这不是一个回文数字符串!\r\n");
}
}
_str(s);
_str(r);
}
结果:
qwewq
这是一个回文数字符串!
qwerrewq
这是一个回文数字符串!
zxcvfd
这不是一个回文数字符串!
案例2
输入一个字符串,删除字符串中重复出现的字符。
void test(void)
{
str_t s = str("");
str_t r = str("");
char buf[64] = {0};
while (1)
{
scanf("%s", buf); // 输入一个字符串
str_assign(s, buf); // 将s赋值为输入的字符串
str_assign(r, ""); // 将r设为空
for (int i = 0; i < str_length(s); i++) // 遍历s
{
char t[2] = {str_at(s, i),0}; // 取出s的字符,组成一个短的数组字符串
if (str_find(r, t, 0) == str_npos) // 判断这个数组字符串是否在r当中了,也就是判断s的字符是否在r里面
{
str_append(r, t); // 不在里面则追加到里面
}
}
printf("%s\r\n", _S(r));
}
_str(s);
_str(r);
}
结果:
16783679816488742135468794
167839425
asdfgsaf
asdfg
hijief145ahya21
hijef145ay2
源码解析
str结构体
str容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致str存储结构的破坏。所以str解析器只留了唯一一个str的声明在头文件,然后结构体的定义都在源文件。只能使用str容器提供的方法对str对象进行操作。
str类型声明
typedef struct STR *str_t;
使用时候,只是用str_t即可。
/* str define */
typedef struct STR
{
char ident; /* ident of str */
char* base; /* base address of string */
int length; /* length of str */
int capacity; /* capacity of str */
} STR;
STR结构体中包含了4个成员,ident(识别标志,区分是数组字符串还是str字符串的依据),base(真正存储字符串的基地址),length(str的大小,也就是str的长度),capacity(分配的空间的容量,用来实际存储字符串,一般情况下不用去关注容量的事情)。
区分数组字符串和str字符串
在STR结构体当中,ident成员就是用于区分数组字符串和str字符串的标志,那是怎么区分的呢?
看下区分的代码:
/* str info define */
typedef struct
{
char *base;
int length;
} str_info_t;
static str_info_t string_info(void *string)
{
str_info_t info;
if (((char *)string)[0] == 0) /* empty array string */
{
info.base = (char *)string;
info.length = 0;
}
else if (((str_t)string)->ident == ident()) /* str type */
{
info.base = ((str_t)string)->base;
info.length = ((str_t)string)->length;
}
else /* array string type */
{
info.base = (char *)string;
info.length = strlen((char *)string);
}
return info;
}
首先,ident定义在结构体的首位,也就是结构体地址首位存储的内容就是ident的内容,所以当传入void *string,先判断一下这个地址首个字节的内容是什么。当为\0时候,就表示是一个空的数组字符串,当为ident()的时候,就代表是str字符串了,其他的值则为数组字符串了。
那这个ident()到底是什么?为什么其可以区分。
#define ident() (-1)
其实就是-1,那就是只要传入的地址的首个字节内容为-1就是str字符串,否则就是数组字符串。为什么-1可以用来区分。
首先先看ASCII编码的规则,字符只在0 ~ 127之间,用不上-1,那-1是可以用来区分的,在ASCII编码中区分没问题。
再看Unicode编码的,Unicode编码范围在0x0000 ~ 0x10FFFF,-1对应的无符号值是0xFF,在Unicode编码范围内没有以0xFF开头的,所以-1是可以用来区分的,在Unicode编码中区分没问题。
然后在UTF-8编码中,UTF-8编码是将Unicode编码调整为可变长的,在1~4个字节不等。在为ASCII字符的时候,采用一个字节编码保持和ASCII一致,超过了ASCII编码范围就变成了多字节,多字节是以第一个字节作为起始,标志这个字符需要多少字节,然后后面的字节归属这个字符。比如2字节的为110xxxxx 10xxxxxx,第一个字节前面多少个1就代表多少字节编码,以0分隔,后面的字节以10开头,其余x部分就是编码数据部分,如此,3字节的为1110xxxx 10xxxxxx 10xxxxxx,4字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。说明UTF-8的编码规则,也就是说明在UTF-8编码中也是没有以0xFF开头的,所以-1是可以用来区分的,在UTF-8编码中区分没问题。
选择-1作为区分,是因为在常用的编码里面,都不会用到,所以用-1来作为区分。
回到str_info_t string_info(void *string)函数,在str的api中,涉及到需要区分字符串类型的,都调用了这个函数进行区分,返回的str_info_t就包含字符串的基地址个长度。
str自适应长度
在str的所有api当中,使用时候都没有涉及需要自己去分配或者定义多少空间,因为在str内部会自适应字符串长度来动态调整存储空间的大小。
以str_assign赋值函数为例:
str_t str_assign(str_t str, void *string)
{
str_info_t info;
if (!str) return NULL;
if (!string) return NULL;;
info = string_info(string); // 获取字符串的信息,基地址和长度
if (!str_alter_capacity(str, info.length)) return NULL; // 根据长度进行容量调整
strcpy(str->base, info.base); // 将字符串复制到str空间
str->length = info.length; // 更新长度
return str;
}
其代码长度也就是这几行。其中调整容量str_alter_capacity函数。
static int str_alter_capacity(str_t str, int length)
{
int capacity = 0;
char *base = NULL;
/* check whether the capacity changes and need to adjust the capacity space */
capacity = gradient_capacity(length);
if (str->capacity != 0 && str->capacity == capacity) return 1;
if (!str->base) /* allocate new space */
{
str->base = (char *)malloc(capacity + 1);
if (!str->base) return 0;
}
else /* reallocate space */
{
base = (char *)realloc(str->base, capacity + 1);
if (!base) return 0;
str->base = base;
}
str->capacity = capacity;
return 1;
}
str获取实际存储数据
char* str_data(str_t str, int pos)
{
if (!str || pos < 0 || pos >= str->length)
{
error = 0; /* reset error area */
return &error;
}
return &str->base[pos];
}
str_at、str_c_str、_S都是基于str_data实现的,str_data实现的内容其实也很简单,就是将结构体中base指定索引的地址进行返回。在其中加入了error机制,也就是当传入的添加不满足的时候,会返回error的地址,error为模块定义的一个静态变量。防止返回NULL,对空地址进行操作时候导致的程序崩溃。
str字符串替换原理
str_t str_replace(str_t str, int pos, int len, void *string)
{
str_info_t info;
char *overlap = NULL;
if (!str) return NULL;
if (!string) return NULL;
if (pos < 0 || pos > str->length) return NULL;
if (len > str->length - pos) len = str->length - pos;
info = string_info(string);
/* check if addresses overlap
这一步是检查地址有没有重叠,就是string信息的地址有没有和原来的str的地址重叠
重叠的处理机制是,分配一块空间出来,把要替换的内容复制一份
到后面再把复制出来的替换进去
*/
if (str->base <= info.base && info.base <= str->base + str->length && pos < str->length)
{
overlap = (char *)malloc(info.length + 1);
if (!overlap) return NULL;
strcpy(overlap, info.base);
info.base = overlap;
}
/*
这一步,判断被替换的字符串与替换字符串的长度关系,也就是判断需不需要调整容量大小
*/
if (info.length > len) // 替换的字符串长了,需要扩容
{
/* 先扩容,扩大的容量为这个长度差值 */
if (str_alter_capacity(str, str->length + (info.length - len)) == 0)
{
if (overlap) free(overlap);
return NULL;
}
/* 把后部分数据往后移动,腾出点空间来存放替换进来较长的字符串 */
memmove(&str->base[pos + info.length], &str->base[pos + len], str->length - (pos + len));
/* 腾出空间后,把替换的字符串复制进来 */
memcpy(&str->base[pos], info.base, info.length);
}
else if (info.length < len) /* 长度变小 */
{
/* 把后面的数据往前面移,留info的长度给替换进来的字符串即可 */
memmove(&str->base[pos + info.length], &str->base[pos + len], str->length - (pos + len));
/* 把替换的字符串复制进来 */
memcpy(&str->base[pos], info.base, info.length);
/* 缩容的,后面调整容量 */
str_alter_capacity(str, str->length + (info.length - len));
}
else /* 长度没发生变化 */
{
/* 直接覆盖即可 */
memcpy(&str->base[pos], info.base, info.length);
}
str->length += (info.length - len);
str->base[str->length] = 0;
if (overlap) free(overlap);
return str;
}
不管是插入还是移除的方法,都是基于替换的方法进行实现的。
str_t str_insert(str_t str, int pos, void *string)
{
return str_replace(str, pos, 0, string);
}
str_t str_erase(str_t str, int pos, int len)
{
return str_replace(str, pos, len, "");
}
str拼接原理
#define str_append(str, ...) str_append_series((str), ##__VA_ARGS__, NULL)
str_t str_append_series(str_t str, ...)
{
va_list args;
void* s = NULL;
if (!str) return NULL;
va_start(args, str);
s = va_arg(args, void*);
while (s)
{
/* insert at the end */
if (!str_insert(str, str->length, s))
{
va_end(args);
return NULL;
}
s = va_arg(args, void*);
}
va_end(args);
return str;
}
实际就是调用str_append_series函数进行传参,在不定参数后面传入个NULL作为结尾。
而在str_append_series函数内部,处理不定参数,依次将一段段字符串通过str_insert插到尾端,完成拼接。
str格式化源码
str_t str_format(str_t str, char *format, ...)
{
va_list args;
int i = 0;
int len = 0; /* length of sub string */
char *s = NULL; /* temp string */
str_info_t info; /* str info */
double dbl = 0.0; /* double precision floating point */
char *begin = NULL; /* begin of format */
char qualifier = 0; /* conversion qualifier for integer as 'h','l','L' */
int width = 0; /* transition field width */
int precision = 0; /* minimum digits of integers and maximum digits of strings */
char tfmt[16] = "%"; /* temporary format */
if (!str) return NULL;
str_assign(str, "");
va_start(args, format);
for (; *format; format++)
{
/* 为普通字符时 */
if (*format != '%')
{
if (!begin) begin = format; // 记录下普通字符段的起始位置
continue; // 不再执行这个循环跳到下一个
}
/* 记录了普通字符的起始位置,也就表示当前位置的前段部分为普通的字符 */
if (begin)
{
len = format - begin; // 记录下长度给后面扩容
if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY;
while (len--) str->base[str->length++] = *begin++; // 复制普通的字符进去
begin = NULL;
}
/* 从这里开始处理格式化的数据段 */
begin = format;
while (1) // 记录前置符号标志
{
/* skips the first '%' also */
format++;
if ((*format != '0') &&
(*format != ' ') &&
(*format != '+') &&
(*format != '-') &&
(*format != '#')) break;
}
/* get field width */
width = -1;
if (is_digit(*format))
{
format += get_digit(format, &width);
}
else if (*format == '*')
{
format++;
width = va_arg(args, int);
if (width < 0) width = -width;
}
/* get the precision */
precision = -1;
if (*format == '.')
{
format++;
if (is_digit(*format)) format += get_digit(format, &precision);
else if (*format == '*')
{
format++;
precision = va_arg(args, int);
}
if (precision < 0) precision = 0;
}
/* get the conversion qualifier */
qualifier = 0;
if (*format == 'h' || *format == 'l' || *format == 'L')
{
qualifier = *format;
format++;
if (qualifier == 'l' && *format == 'l')
{
qualifier = 'L';
format++;
}
}
/* format distribution */
switch (*format)
{
case 'c':
// 预计需要多长空间
// 调用sprintf尾插一个字符
...
case 's':
// 预计需要多长空间
// 调用sprintf尾插一段字符串
...
case 'p':
// 预计需要多长空间
// 调用sprintf尾插一个指针
...
case 'a':
case 'A':
case 'e':
case 'E':
case 'g':
case 'G':
case 'f':
// 预计需要多长空间
// 调用sprintf尾插一个浮点数
...
case 'o':
case 'X':
case 'x':
case 'd':
case 'i':
case 'u':
// 预计需要多长空间
// 调用sprintf尾插一个整形数
...
case '%':
// 推入 %
...
default:
// 推入 %
...
}
}
/* copy tail string to str */
if (begin)
{
len = format - begin;
if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY;
while (len--) str->base[str->length++] = *begin++;
}
if (!str_alter_capacity(str, str->length)) goto FAIL_CAPACITY;
str->base[str->length] = '\0';
va_end(args);
return str;
FAIL_CAPACITY:
str->base[str->length] = '\0';
va_end(args);
return NULL;
}
源码比较长,大体的流程是,遍历format,在遇到%符号时进行具体的格式操作,以%为分隔符,将整段format分成一段段进行动态扩容、赋值。