mirror of
https://gitee.com/Lamdonn/varch.git
synced 2025-12-06 16:56:42 +08:00
875 lines
26 KiB
Markdown
875 lines
26 KiB
Markdown
## 介绍
|
||
|
||
字符串实际是一串字符的集合,在C语言中,字符串是为char类型的数组,在结尾加上结束符`\0`则为字符串,没有结束符则为字符数组。
|
||
在C语言中,没有专门的字符串变量,而varch中的str则专门针对数组字符串进行了封装,封装成不需要关注底层存储结构的字符串变量。str实际还是通过连续地址的空间进行存储字符集,但是这些存储的工作不需要额外去关注,str提供的方法已经实现存储的工作,而且str也提供符合字符串操作的方法,方便字符串的灵活操作。方便说明,后面的传统的C语言字符串称为数组字符串,varch提供的成为str字符串。
|
||
|
||
## 接口
|
||
|
||
### 创建和删除str字符串对象
|
||
```c
|
||
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对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。
|
||
```c
|
||
void test(void)
|
||
{
|
||
str_t ss = str("Hello str!"); // 用数组字符串进行赋值构造
|
||
str_t copy = str(ss); // 用str字符串进行拷贝构造
|
||
_str(ss); // 删除str
|
||
_str(copy); // 删除str
|
||
}
|
||
```
|
||
|
||
### str字符串的数组字符串
|
||
```c
|
||
#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()`则是简短版的。
|
||
```c
|
||
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赋值
|
||
```c
|
||
str_t str_assign(str_t str, void *string);
|
||
```
|
||
str抽象字符串为一个类了,给str进行赋值操作,类似于=号的赋值操作。如同前面所说的`void *string`可以支持数组字符串和str字符串。
|
||
```c
|
||
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拼接
|
||
```c
|
||
#define str_append(str, ...)
|
||
```
|
||
str除了有类似`=`号的赋值操作,还有类似`+=`号的拼接操作。
|
||
形参后面不定参数,**可以拼接至少一个任意数量的字符串**。同样,可以是数组字符串也可以是str字符串。
|
||
`str_append`方法的原型是`str_t str_append_series(str_t str, ...);`,原型函数不定参数需要接`NULL`结尾,不建议使用原型方法。
|
||
```c
|
||
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插入
|
||
```c
|
||
str_t str_insert(str_t str, int pos, void *string);
|
||
```
|
||
在str中插入一个字符串,并返回自己。`pos`是插入的位置,`string`是插入的源字符串。
|
||
```c
|
||
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移除
|
||
```c
|
||
str_t str_erase(str_t str, int pos, int len);
|
||
```
|
||
从str中移除一段字符,并返回自己。`pos`是移除的位置,`len`是移除的长度。
|
||
```c
|
||
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尾部推入和弹出
|
||
```c
|
||
int str_push_back(str_t str, char c);
|
||
char str_pop_back(str_t str);
|
||
```
|
||
这两个方法分别从str字符串尾部推入和弹出一个字符,推入方法的返回值1成功0失败,弹出方法返回弹出的字符。
|
||
`str`存储结构和`vector`类似,在尾部操作具有比较高的效率。
|
||
```c
|
||
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字符串比较
|
||
```c
|
||
int str_compare(str_t str, void *string);
|
||
```
|
||
比较str与另外一个字符串,0为相等,-1为str小于string,1为str大于string。
|
||
```c
|
||
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创建子串
|
||
```c
|
||
str_t str_substr(str_t str, int pos, int len);
|
||
```
|
||
这个方法是从str中创建一个新的子串,**切记,在结束使用后,调用`_str(str)`方法来删除**。。
|
||
`pos`传入起始位置,`len`传入创建的长度。
|
||
```c
|
||
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字符串长度
|
||
```c
|
||
int str_length(str_t str);
|
||
```
|
||
str标准的获取长度的方法是这个,当然也可以用`strlen`方法去量,但是对于`str`对象没必要,因为这个标准的方法是不用耗费时间去量的,返回记录好了的长度。
|
||
```c
|
||
void test(void)
|
||
{
|
||
str_t ss = str("0123456789");
|
||
printf("length: %d\r\n", str_length(ss));
|
||
_str(ss);
|
||
}
|
||
```
|
||
结果:
|
||
```
|
||
length: 10
|
||
```
|
||
|
||
### str字符读写
|
||
```c
|
||
#define str_at(str, i)
|
||
```
|
||
str提供了针对其中的字符进行读写操作,当作数组的元素使用即可。
|
||
但切记,不要把字符修改成`\0`了。
|
||
```c
|
||
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字符串查找
|
||
```c
|
||
int str_find(str_t str, void *string, int pos);
|
||
int str_rfind(str_t str, void *string, int pos);
|
||
```
|
||
这两个方法分别从正向和反向查找字符子串,`string`传入字符串,`pos`传入开始查找的起始索引,返回则是返回子串所在的位置索引,没找到就返回`str_npos`。
|
||
```c
|
||
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查找匹配字符集
|
||
```c
|
||
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`。这四个函数分别找到**“第一个包含”**、**“第一个不包含”**、**“最后一个包含”**、**“最后一个不包含”**
|
||
例子:实际应用获取一个文件路径的文件和所在盘符
|
||
```c
|
||
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字符串翻转
|
||
```c
|
||
str_t str_reverse(str_t str, int begin, int end);
|
||
```
|
||
这个方法将str指定区间的字符串整体翻转。`begin`为区间起始位置,`end`为区间结束位置。
|
||
```c
|
||
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字符替换
|
||
```c
|
||
str_t str_replace(str_t str, int pos, int len, void *string);
|
||
```
|
||
这个方法把指定位置的字符按长度替换成指定的字符串。
|
||
```c
|
||
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字符串交换
|
||
```c
|
||
void str_swap(str_t str, str_t swap);
|
||
```
|
||
将两个字符串进行交换。
|
||
```c
|
||
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字符串复制到数组
|
||
```c
|
||
int str_copy(str_t str, int pos, int len, char *buf);
|
||
```
|
||
将str字符串指定位置的字符复制到指定的数组。其中,`pos`复制的起始位置。`len`复制的最大长度。`buf`就是接收复制数据的数组。函数返回实际复制的长度。
|
||
```c
|
||
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字符串格式化
|
||
```c
|
||
str_t str_format(str_t str, char *format, ...);
|
||
```
|
||
str字符串格式化操作,套用`sprintf`函数进行了重写,与其用法基本一致。**增加了%s对str类型的支持**,`str_format`对格式化进行分段的动态扩容,在使用上不需要像`sprintf`那样关注buf长度是否超出的问题,使用起来更加的简便。
|
||
```c
|
||
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
|
||
输入一个字符串,判断是否为回文字符串,即从左到右和从右到左完全一致。
|
||
```c
|
||
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
|
||
输入一个字符串,删除字符串中重复出现的字符。
|
||
```c
|
||
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类型声明
|
||
```c
|
||
typedef struct STR *str_t;
|
||
```
|
||
使用时候,只是用`str_t`即可。
|
||
|
||
```c
|
||
/* 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字符串的标志,那是怎么区分的呢?
|
||
看下区分的代码:
|
||
```c
|
||
/* 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()`到底是什么?为什么其可以区分。
|
||
```c
|
||
#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`赋值函数为例:
|
||
```c
|
||
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`函数。
|
||
```c
|
||
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获取实际存储数据
|
||
```c
|
||
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字符串替换原理
|
||
```c
|
||
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;
|
||
}
|
||
```
|
||
不管是插入还是移除的方法,都是基于替换的方法进行实现的。
|
||
```c
|
||
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拼接原理
|
||
```c
|
||
#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格式化源码
|
||
|
||
```c
|
||
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`分成一段段进行动态扩容、赋值。
|