11 KiB
介绍
vector容器与数组非常类似,封装了数组常用的方法,大部分场合可以用vector来代替普通的数组使用,更加的安全。数组是静态的空间,定义好了就不能改变大小,而vector是动态的,在使用的过程中可以动态的调整大小。
同时varch的vector也可以像数组一样随机直接访问。
接口
创建和删除vector对象
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对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。
void test(void)
{
vector_t vt = vector(int, 16); // 创建长度为16的int型vector
int array[16];
_vector(vt); // 删除vt
}
vector数据的读写
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当作普通数组使用,通过[]下标进行访问。
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的大小和容量
int vector_size(vector_t vector);
int vector_capacity(vector_t vector);
vector的大小很好理解,也就是像数组那样的大小,但是容量是什么呢?容量是用来存储vector大小的空间,因为是动态的数组,为了更好的扩容,一般是会预留一些空间给到后续的扩展。如此,容量一定是大于或等于大小的。在实际使用过程中,不必多关注容量,关注大小就足够了
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调整大小
int vector_resize(vector_t vector, int size);
#define vector_clear(vector)
此方法是重新调整vector容器的方法,可以扩容也可以缩容,扩容就是在原有的基础上,在尾部追加空间,追加的空间不赋值(也就是不一定为0)。缩容则会将尾部多出来的部分舍弃掉。vector_clear则套用vector_resize调整大小为0。
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的插入和移除
int vector_insert(vector_t vector, int index, void* data, int num);
int vector_erase(vector_t vector, int index, int num);
插入的方法是在指定索引的位置插入指定地址的num个数据,而移除则是移除指定索引的num个数据。
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
通过插入和移除方法还延伸出了,以下的宏定义方法
#define vector_push_front(vector, data)
#define vector_push_back(vector, data)
#define vector_pop_front(vector)
#define vector_pop_back(vector)
参考例子
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类型声明
typedef struct VECTOR *vector_t;
使用时候,只是用vector_t即可。
/* 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重新分配一下空间。
看下源码:
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;
}
如上代码,实现动态大小就是这么简单。
但是,这个计算容量的大小是怎么计算的呢?
#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。
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的插入和移除原理
插入的原理就是将指定位置后面的数据整体往后挪,给插入的数据留出空位,再把要插入的数据复制进来;同理,移除数据则是将后面的数据整体往前移,覆盖要移除的数据段。
先看插入的源码:
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;
}
从中可以看出,插入的位置越靠前,后面需要移动的数据就越多,插入的效率就越低。
再看移除的源码:
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;
}