varch/doc/vector.md
2024-07-21 19:02:13 +08:00

354 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 介绍
vector容器与数组非常类似封装了数组常用的方法大部分场合可以用vector来代替普通的数组使用更加的安全。数组是静态的空间定义好了就不能改变大小而vector是动态的在使用的过程中可以动态的调整大小。
同时varch的vector也可以像数组一样随机直接访问。
## 接口
### 创建和删除vector对象
```c
vector_t vector_create(int dsize, int size);
void vector_delete(vector_t vector);
#define vector(type, size) // 为了更简便的使用对vector_create套一层宏定义
#define _vector(vector) // 对vector_delete套一层宏定义并在vector删除后置为空
```
其中**vector_t**为vector的结构体创建方法则会返回一个vector对象创建失败则返回NULL其中`dsize`传入数据的大小,`size`传入vector数组的大小数据的数量。删除方法则是删除传入的vector对象。创建方法和删除应该成对使用创建出来在结束使用应该删除掉。
```c
void test(void)
{
vector_t vt = vector(int, 16); // 创建长度为16的int型vector
int array[16];
_vector(vt); // 删除vt
}
```
### vector数据的读写
```c
void* vector_data(vector_t vector, int index);
#define vector_at(vector, type, i)
#define v2a(vector, type)
```
`vector_data`方法就是根据索引来获取数据的地址返回的则是指定的数据的地址NULL则是失败。而`vector_at`则是在`vector_data`的基础上加多类型,`v2a`是将vector当作普通数组使用通过`[]`下标进行访问。
```c
void test(void)
{
vector_t vt = vector(int, 8);; // 定义并创建长度为8的int型vector
int i = 0;
for (i = 0; i < 8; i++)
{
vector_at(vt, int, i) = i; // 使用at方法访问
}
for (i = 0; i < 8; i++)
{
printf("vt[%d] = %d\r\n", i, v2a(vt, int)[i]); // 使用下标访问
}
_vector(vt); // 用完即删除
}
```
结果:
```
vt[0] = 0
vt[1] = 1
vt[2] = 2
vt[3] = 3
vt[4] = 4
vt[5] = 5
vt[6] = 6
vt[7] = 7
```
### vector的大小和容量
```c
int vector_size(vector_t vector);
int vector_capacity(vector_t vector);
```
vector的大小很好理解也就是像数组那样的大小但是容量是什么呢容量是用来存储vector大小的空间因为是动态的数组为了更好的扩容一般是会预留一些空间给到后续的扩展。如此容量一定是大于或等于大小的。**在实际使用过程中,不必多关注容量,关注大小就足够了**
```c
void test(void)
{
vector_t vt = vector(int, 10);
printf("size=%d, capacity=%d\r\n", vector_size(vt), vector_capacity(vt));
_vector(vt);
}
```
结果:
```
size=10, capacity=12
```
### vector调整大小
```c
int vector_resize(vector_t vector, int size);
#define vector_clear(vector)
```
此方法是重新调整vector容器的方法可以扩容也可以缩容扩容就是在原有的基础上在尾部追加空间追加的空间不赋值也就是不一定为0。缩容则会将尾部多出来的部分舍弃掉。`vector_clear`则套用`vector_resize`调整大小为0。
```c
void test(void)
{
vector_t vt = vector(int, 10);
printf("size=%d\r\n", vector_size(vt));
vector_resize(vt, 32);
printf("size=%d\r\n", vector_size(vt));
vector_resize(vt, 14);
printf("size=%d\r\n", vector_size(vt));
_vector(vt);
}
```
结果:
```
size=10
size=32
size=14
```
### vector的插入和移除
```c
int vector_insert(vector_t vector, int index, void* data, int num);
int vector_erase(vector_t vector, int index, int num);
```
插入的方法是在指定索引的位置插入指定地址的num个数据而移除则是移除指定索引的num个数据。
```c
void test(void)
{
vector_t vt = vector(int, 0); // 创建0长度的vector容器
int array[10] = {0,1,2,3,4,5,6,7,8,9};
int i = 0;
printf("insert:\r\n");
vector_insert(vt, 0, array, 10); // 把array插入到vt当中相当于用array给vt赋值
for (i = 0; i < vector_size(vt); i++)
{
printf("vt[%d]=%d\r\n", i, v2a(vt, int)[i]);
}
printf("erase:\r\n");
vector_erase(vt, 5, 2); // 移除索引5后面的两个数据也就是索引5和索引6
for (i = 0; i < vector_size(vt); i++)
{
printf("vt[%d]=%d\r\n", i, v2a(vt, int)[i]);
}
_vector(vt);
}
```
结果:
```
insert:
vt[0]=0
vt[1]=1
vt[2]=2
vt[3]=3
vt[4]=4
vt[5]=5
vt[6]=6
vt[7]=7
vt[8]=8
vt[9]=9
erase:
vt[0]=0
vt[1]=1
vt[2]=2
vt[3]=3
vt[4]=4
vt[5]=7
vt[6]=8
vt[7]=9
```
通过插入和移除方法还延伸出了,以下的宏定义方法
```c
#define vector_push_front(vector, data)
#define vector_push_back(vector, data)
#define vector_pop_front(vector)
#define vector_pop_back(vector)
```
## 参考例子
```c
void test(void)
{
vector_t vt_vector = vector(vector_t, 3); // 类型为vector_t的vector
int i = 0;
char *name[3] = { // 将这三个名字作为数据源生成新的vector
"ZhangSan",
"LiSi",
"WangWu"
};
// 遍历vt_vector
for (i = 0; i < vector_size(vt_vector); i++)
{
v2a(vt_vector, vector_t)[i] = vector(char, 0); // 给vt_vector每一项新建一个char型的vector
vector_insert(v2a(vt_vector, vector_t)[i], 0, name[i], strlen(name[i]) + 1); // 将名字插入到vector当中
}
for (i = 0; i < vector_size(vt_vector); i++)
{
printf("vt_vector[%d]: %s\r\n", i, &vector_at(v2a(vt_vector, vector_t)[i], char, 0)); // 打印vt_vector记录下的名字
_vector(v2a(vt_vector, vector_t)[i]); // 删除vt_vector下的vector
}
_vector(vt_vector);
}
```
结果:
```
vt_vector[0]: ZhangSan
vt_vector[1]: LiSi
vt_vector[2]: WangWu
```
例子里面使用函数很多没有对返回值进行判断,实际应用需对返回值进行判断。
## 源码解析
### vector结构体
vector容器的所有结构体都是隐式的也就是不能直接访问到结构体成员的这样子的方式保证了模块的独立与安全防止外部调用修改结构体的成员导致vector存储结构的破坏。所以vector解析器只留了唯一一个vector的声明在头文件然后结构体的定义都在源文件。只能使用vector容器提供的方法对vector对象进行操作。
vector类型声明
```c
typedef struct VECTOR *vector_t;
```
使用时候,只是用`vector_t`即可。
```c
/* type of vector */
typedef struct VECTOR
{
void* base; /* base address for storing data */
int dsize; /* size of item */
int size; /* size of vector */
int capacity; /* capacity of vector */
} VECTOR;
```
VECTOR结构体中包含了4个成员base实际存储数据的基地址dsize每个数据的大小sizevector的大小也就是vector的长度capacityvector的容量
### vector动态大小
从结构体成员看来其实vector实现的逻辑并不难实现动态调整大小并不复杂。把要存的数据计算好大小调用`malloc`动态分配指定空间即可,而当要调整的时候,则使用`realloc`重新分配一下空间。
看下源码:
```c
int vector_resize(vector_t vector, int size)
{
void* base = NULL;
int capacity;
if (!vector) return 0;
if (size < 0) return 0;
capacity = gradient_capacity(size); // 计算新的空间需要多大的容量
if (capacity != vector->capacity) // 如果算出来的容量和当前的容量不一样了,则重新分配空间
{
base = realloc(vector->base, capacity * vector->dsize);
if (!base) return 0;
vector->base = base;
vector->capacity = capacity;
}
vector->size = size; // 更新新的大小
return 1;
}
```
如上代码,实现动态大小就是这么简单。
但是,这个计算容量的大小是怎么计算的呢?
```c
#define up_multiple(x, mul) ((x)+((mul)-((x)-1)%(mul))-1) /* get the smallest 'mul' multiple larger than 'x' */
static int gradient_capacity(int size)
{
int capacity = 1;
if (size <= 1) return 1;
while (capacity < size) capacity <<= 1; // 比size大的最小的2的次方
capacity >>= 1; // 比size小的最大的2的次方
if (capacity < 4) capacity = capacity << 1;
else if (capacity < 16) capacity = up_multiple(size, capacity >> 1);
else if (capacity < 256) capacity = up_multiple(size, capacity >> 2);
else capacity = up_multiple(size, 64);
return capacity;
}
```
这个函数是将size按梯度进行划分在这个梯度范围的size就会得到这个梯度的capacity。开始找到比size小的最大的2的次方然后以4、16256分级梯度作为梯度往上增到后面最大一次增64。
```c
int size = 0, capacity = 0;
for (size = 1; size < 1024; )
{
capacity = gradient_capacity(size);
printf("+%d\t%d\r\n", capacity - size + 1, capacity);
size = capacity + 1;
}
```
用这段代码可以测试到这个梯度算法区分出来的梯度为:
```
+1 1
+1 2
+2 4
+2 6
+2 8
+4 12
+4 16
+4 20
+4 24
+4 28
+4 32
+8 40
+8 48
+8 56
+8 64
+16 80
+16 96
+16 112
+16 128
+32 160
+32 192
+32 224
+32 256
+64 320
+64 384
+64 448
+64 512
+64 576
+64 640
+64 704
+64 768
+64 832
+64 896
+64 960
+64 1024
```
到后面最多只能增加64了。
### vector的插入和移除原理
插入的原理就是将指定位置后面的数据整体往后挪,给插入的数据留出空位,再把要插入的数据复制进来;同理,移除数据则是将后面的数据整体往前移,覆盖要移除的数据段。
先看插入的源码:
```c
int vector_insert(vector_t vector, int index, void* data, int num)
{
int i = 0;
int size;
if (!vector) return 0;
if (index < 0 || index > vector->size) return 0;
if (num == 0) return 0;
size = vector->size; // 先记录下原来的大小后面移数据需要用到扩容之后vector->size就变了找不到原来的尾部索引了
if (!vector_resize(vector, vector->size + num)) return 0; // 扩容,给插入的数据预留空间
if (index < size) memmove(at(index + num), at(index), vector->dsize * (size - index)); // 将数据整体往后移
if (data) memcpy(at(index + i), data, vector->dsize * num); // 把新的数据复制进去
return 1;
}
```
从中可以看出,插入的位置越靠前,后面需要移动的数据就越多,插入的效率就越低。
再看移除的源码:
```c
int vector_erase(vector_t vector, int index, int num)
{
unsigned char *op_ptr = NULL;
if (!vector) return 0;
if (vector->size == 0) return 0;
if (index < 0 || index >= vector->size) return 0;
if (num <= 0) return 0;
if (num > vector->size - index) num = vector->size - index; // num超过尾端的数据数量了就移除掉尾部全部即可
memmove(at(index), at(index + num), vector->dsize * (vector->size - (index + num))); // 将数据往前移
vector_resize(vector, vector->size - num); // 缩容去掉后半部分
return 1;
}
```