mirror of
https://gitee.com/Lamdonn/varch.git
synced 2025-12-08 01:36:42 +08:00
354 lines
11 KiB
Markdown
354 lines
11 KiB
Markdown
## 介绍
|
||
|
||
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(每个数据的大小),size(vector的大小,也就是vector的长度),capacity(vector的容量)。
|
||
|
||
### 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、16,256)分级梯度作为梯度往上增,到后面最大一次增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;
|
||
}
|
||
```
|