## 介绍 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; } ```