vector类实现
vector结构:
如上图,vector的结构中,包含3个成员变量:
_start:指向vector元素的起始位置
_finish:指向vector元素的结束位置
_end_of_storage:指向vector元素可用空间的位置
为了和库里面的vector区分开,使用命名空间delia将 vector类和库里vector隔离开
1. namespace delia 2. { 3. template<class T>//vector里面的元素可能是int,可能是double……所以使用类模板 4. class vector 5. { 6. private: 7. iterator _start; 8. iterator _finish; 9. iterator _end_of_storage; 10. } 11. }
实现以下vector相关内容:
1.vector类构造
vector类有3种常见的构造函数
(1)无参构造函数,构造空容器
1. vector()//将成员变量都初始化成空指针 2. :_start(nullptr) 3. , _finish(nullptr) 4. , _end_of_storage(nullptr) 5. {}
(2)范围构造函数。函数模板InputIterator是输入迭代器类型,并且类型不确定,用first到last之间的元素尾插到容器中
1. 2. template<class InputIterator> 3. vector(InputIterator first, InputIterator last) 4. :_start(nullptr) 5. ,_finish(nullptr) 6. ,_end_of_storage(nullptr) 7. { 8. while (first != last) 9. { 10. if (first) 11. { 12. push_back(*first); 13. first++; 14. } 15. 16. } 17. } 18. 19. //e.g: 20. //vector<int> v1; 21. //v1.push_back(0); 22. //v1.push_back(1); 23. //v1.push_back(2); 24. //v1.push_back(3); 25. 26. //vector<int> v2(v1.begin(),v1.begin()+2); 向v2插入v1的前2个元素
(3) 填充构造函数。向容器中插入n个值为val的元素
1. vector(size_t n, const T& val) 2. :_start(nullptr) 3. , _finish(nullptr) 4. , _end_of_storage(nullptr) 5. { 6. reserve(n); 7. while (n) 8. { 9. push_back(val); 10. n--; 11. } 12. } 13. //e.g: 14. //vector<int> v2(3,5);向v2中插入3个5
但是编译时会提示:
这是因为当使用两个参数进行构造时,编译器会优先匹配构造函数(2),对*first解引用会报错。所以需要再实现两个不同参数类型的(3)的构造函数重载:
1. vector(int n, const T val) 2. :_start(nullptr) 3. , _finish(nullptr) 4. , _end_of_storage(nullptr) 5. { 6. reserve(n); 7. while (n) 8. { 9. push_back(val); 10. n--; 11. } 12. } 13. 14. vector(long n, const T val) 15. :_start(nullptr) 16. , _finish(nullptr) 17. , _end_of_storage(nullptr) 18. { 19. reserve(n); 20. while (n) 21. { 22. push_back(val); 23. n--; 24. } 25. } 26. 27. //e.g: 28. //vector<int> v2(3,5);向v2中插入3个5
2.拷贝构造
假如不写vector类的拷贝构造函数,那么编译器自动生成的默认拷贝构造函数只能完成浅拷贝,vector类的3个成员变量的类型都是T*,如果T是内置类型,那么拷贝OK;但如果T是自定义类型,那么拷贝对象和被拷贝对象指向同一块空间,后定义的先析构,这块空间会被释放两次,程序就会崩掉。
(1)传统的拷贝构造
1. //v2(v1) 2. vector(const vector<T>& v) 3. :_start(nullptr) 4. ,_finish(nullptr) 5. ,_end_of_storage(nullptr) 6. { 7. //1.申请空间 8. _start = new T[v.capacity()]; 9. 10. //2.拷贝数据 11. for (size_t i = 0; i < v.size(); i++) 12. { 13. _start[i] = v._start[i]; 14. } 15. 16. //3.更新大小及容量 17. _finish = _start + v.size(); 18. _end_of_storage = _start + v.capacity(); 19. }
为什么2.拷贝数据时不使用memcpy呢?
memcpy(_start, v._start, sizeof(T) * v.size());
①如果T是内置类型,使用memcpy拷贝完全OK,析构时不需要清理资源;
②如果T是自定义类型,假如T的类型为Stack自定义类型,那么使用memcpy会把v的地址拷贝给this,对于v的每一个元素,对象st2的成员变量_a拷贝的是st1的成员变量_a指针,即把st1的_a指针的值,拷贝给了st2的_a,那么两个指针的值是一样的,st1的_a和st2的_a指向同一块空间:
1. typedef int STDataType; 2. class Stack 3. { 4. private: 5. STDataType* _a; 6. int _size; 7. int _capacity; 8. }; 9. 10. int main() 11. { 12. Stack st1; 13. Satck st2(st1); 14. }
拷贝stack对象
拷贝v中的所有元素:
在析构时,vector的每个元素析构两次,那么同一块空间会被释放两次,程序会崩,因此,使用以下代码直接将vector元素值拷贝过来即可
1. //2.拷贝数据 2. for (size_t i = 0; i < v.size(); i++) 3. { 4. _start[i] = v._start[i]; 5. }
(2)现代的拷贝构造:开空间+逐个尾插
使用现代的拷贝构造时必须初始化,否则_start、_finish、_end_of_storage都是随机值,拷贝数据时可能会导致越界。如果T是自定义类型,那么会调用T的拷贝构造函数进行深拷贝
1. vector(const vector<T>& v) 2. :_start(nullptr) 3. , _finish(nullptr) 4. , _end_of_storage(nullptr) 5. { 6. reserve(v.capacity());//开与v一样大小的空间 7. 8. //逐个尾插 9. for (auto& e: v) 10. { 11. push_back(e); 12. } 13. }