STL?vector!!!

STL?vector!!!

一、前言

        之前我们借助手撕string加深了类和对象相关知识,今天我们将一起手撕一个vector,继续深化类和对象、动态内存管理、模板的相关知识

二、vector相关的前置知识

        1、什么是vector?


        vector是一个STL库中提供的类模板,它是存储元素对象的顺序表,其中提供了一些有关增删查改的接口,它的特点是可以通过下标的方式在表中的任意位置进行读、写

        2、vector中的相关接口

        在本文接下来的部分会介绍vector的常用接口,事实上借助这些接口就可以解决平常所能遇到的大部分问题,如果还需要了解vector提供的更多接口及使用方法的话,可以跳转到一下网页:
        vector - C++ Referencehttps://legacy.cplusplus.com/reference/vector/vector/?kw=vector

三、手撕一个vector类

        1、成员变量与整体框架

        注意:之前的顺序表我们都是通过记录指针、元素个数和空间大小来完成的,顺序表的实现自然也可以这样,但是STL库中不是这样实现的,所以今天我们学习一下STL库中的实现方式

        

        上面的_start、_finish、_endofstorge分别代表着指向有效元素起始位置、终止位置的下一个位置和空间终止位置的迭代器,所以在上面我们已经定义了迭代器,事实上就是指针类型,这是由于顺序表的一大特点就是在内存中连续存储,所以可以直接使用未封装的指针;另一方面,我们在声明成员变量的同时都给了一个缺省值,这是由于它们都是指针类型,在初始化时我们都要先初始化成空指针再进行下一步操作,所以我们可以直接在这里给一个缺省值,这样避免了在初始化列表多次的显示初始化成空指针

        2、构造函数

        库中的函数头:
        

        以上的三个构造函数我们都会一一实现,在这里补充一点:在上面的构造函数中出现了"clloc",事实上,这是一个内存池,而后面所给的缺省值是STL库中提供的一个默认的内存池,它可以有效的提高开辟空间的时间消耗,但是比较复杂,所以在这里我们直接new空间,在之后,我们会专门的进行讲解

                (1).空构造:
                

                在这里实现的空构造是非常简单的,这是由于我们在声明时已经给了缺省值,但是该空构造是一定要写的,这是因为我们还要实现别的构造函数,这时候编译器就不在自动生成默认构造函数了

                (2).用n个元素对象进行初始化:
                

                在使用n个元素对象进行初始化时,我们在参数部分给了一个缺省值T(),很明显,这是一个匿名对象,同时调用了对应的默认构造函数,这是没有问题的,但是如果T是int、float等内置类型呢?事实上,这点不用担心,这是由于C++将这些内置类型都进行了升级,使它们可以象自定义类型一样调用默认构造函数,举个例子:如果T是int类型,那么此时T()就会返沪一个0进行初始化

                (3).使用一个迭代器区间进行初始化:
                

                在上面的构造函数中,我们再次使用了模板,但这里是函数模板,这是由于我们不仅要支持顺序表的迭代器类型初始化,还要支持其它类型的迭代器初始化

        3、返回顺序表一些性质的函数

                (1).size函数:返回此时vector中的元素个数

                库中的函数头:
                

                实现:
                

                (2).capacity函数:返回此时vector中的空间大小

                库中的函数头:
                

                实现:
                

        4、拷贝构造函数

        库中的函数头:

        

        实现:
        

        这里需要注意:我们不可以使用memcpy,这是由于memcpy会按字节将x的内容拷贝到_start,但如果T类型是动态开辟内存,也就是说是深拷贝的话,那么虽然vector整体进行了深拷贝,但是vector中的内容却只完成了浅拷贝,会出现深浅拷贝的问题,此时选择上面的方式就万无一失了,这要求元素对象重载了赋值运算符,这一点是必须的

        5、迭代器相关函数

                (1).begin函数:


                库中的函数头:
                

                begin函数进行了两个重载,分别是普通begin和const begin它们分别可以返回一个可读可写的迭代器和一个只读不写的迭代器,都指向vector的首元素位置:
                

                (2).end函数:


                库中的函数头:
                

                end函数仍然进行了两个重载,返回的迭代器都指向末尾元素的下一个位置:
                

        6、[]操作符重载

        库中的函数头:
        

        库中对[]操作符进行了两个重载,与上面的begin类似:
        

        库中对于很多类型进行了封装,事实上上面我们实现的与库中的本质是一样的

        7、交换函数

        库中的函数头:
        

        这里的交换一定是要涉及深拷贝的,在这里我们借用一下算法库中的swap函数,因为它恰好就是深拷贝:
        

        8、赋值运算符重载

        库中的函数头:
        

        在这里我们对该函数实现进行一点小改动,但是对于用户的使用来说是相同的:
        

        在上面的实现中,我们采用了传值传参,此时形参是实参的拷贝,拷贝会调用我们之前实现过的拷贝构造函数进行深拷贝,再调用swap函数,它也会进行深拷贝之下的交换,最终*this就变成了x的拷贝,最后x这一拷贝出作用域销毁,调用析构函数(后面会实现),进行了空间的释放

        9、开空间相关函数

                (1).reserve函数:会开辟指定大小的空间,如果原来有元素,会进行拷贝,否则不进行任何处理

                库中的函数头:
                

                实现:
                

                在上面我们又遇到了象拷贝构造函数同样的问题,所以再次使用了赋值运算符重载的方式进行处理

                (2).resize函数:会开辟指定大小的空间,用val进行初始化,缺省值为T()

                库中的函数头:
                

                实现:
                

                这里我们直接对之前的函数进行复用即可

        10、插入相关函数

                (1).insert函数:在迭代器指定的位置直接插入一个值,返回最后该位置的迭代器

                库中的函数头:
                

                实现:
                

                从上面的代码可以看出:在insert函数内部可能开辟空间,这时候pos就不是原来的pos了,所以在函数外部,传给过insert函数的迭代器是不能再次使用的,因为使用过的迭代器可能已经失效,这时候我们通过反回值的方式解决了这个问题,所以如果想再次使用的话,要接受函数的返回值

                (2).push_back函数:在vector尾部插入一个对象

                库中的函数头:
                

                实现:
                

                事实上在这个位置我们直接复用刚才写过的insert就非常方便

        11、删除相关函数

                (1).erase函数:删除迭代器指定位置的元素,返回该位置的迭代器

                库中的函数头:
                

                实现:
                

                erase所删除的位置有可能是最后一个,此时删除之后传入的迭代器·就失效了,所以要接收返回值并判断

                (2).pop_back函数

                库中的函数头:

                

                实现:
                

                直接复用刚才写过的erase函数即可

        12、析构函数

        由于析构函数的特殊性,这里就不提供库中的函数头了:

        

四、vector

        下面就是我们今天一起完成的vector了:
        

#include <iostream> #include <cassert> #include <algorithm> using namespace std; namespace bea { template<class T> class vector { typedef T* iterator; typedef const T* const_iterator; public: //空构造 vector() {} //用n个元素对象进行初始化 vector(size_t n, const T& val = T()) { _start = new T[n + 5]; for (size_t i = 0; i < n; i++) { _start[i] = val; } _finish = _start + n; _endofstorge = _start + n + 4; } //使用一个迭代器区间进行初始化 template<class InputIterator> vector(InputIterator first, InputIterator last) { size_t n = last - first; _start = new T[n + 5]; InputIterator it1 = first, it2 = _start; while (it1 < last) { *it2 = *it1; it1++, it2++; } _finish = _start + n; _endofstorge = _start + n + 4; } //拷贝构造函数 vector(const vector& x) { size_t sz = x.size(); _start = new T[sz + 5]; for (size_t i = 0; i < sz; i++) { _start[i] = x._start[i]; } _finish = _start + sz; _endofstorge = _start + sz + 4; } //size函数 size_t size() const { return _finish - _start; } //capacity函数 size_t capacity() const { return _endofstorge - _start + 1; } //begin函数 iterator begin() { return _start; } const_iterator begin() const { return _start; } //end函数 iterator end() { return _finish; } const_iterator end() const { return _finish; } //[]操作符重载 T& operator[](size_t pos) { assert(pos < size()); return _start[pos]; } const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; } //交换函数 void swap(vector& x) { std::swap(x._start, _start); std::swap(x._finish, _finish); std::swap(x._endofstorge, _endofstorge); } //赋值运算符重载 vector& operator=(const vector x) { swap(x); return *this; } //开空间相关函数 void reserve(size_t n) { size_t sz = size(); if (n <= size()) return; iterator tmp = new T[n + 5]; if (_start) { for (size_t i = 0; i < sz; i++) { tmp[i] = _start[i]; } delete[] _start; } _finish = tmp + sz; _start = tmp; _endofstorge = tmp + n + 4; } //insert函数 iterator insert(iterator pos, const T& val) { assert(pos <= _finish); int n = pos - _start; if (capacity() < size() + 1) { reserve(size() + 5); } pos = _start + n; iterator end = _finish - 1, next = _finish; while (next > pos) { *next = *end; end--, next--; } *pos = val; _finish++; return pos; } void push_back(const T& val) { insert(_finish, val); } void resize(size_t n, T& val = T()) { vector(n, val); } //erase函数 iterator erase(iterator pos) { assert(pos < _finish); iterator end = pos + 1, front = pos; while (end < _finish) { *front = *end; front++, end++; } _finish--; if (pos == _finish) return nullptr; else return pos; } //pop_back函数 void pop_back() { erase(_finish - 1); } //析构函数 ~vector() { delete[] _start; _start = nullptr; _finish = nullptr; _endofstorge = nullptr; } private: iterator _start = nullptr; iterator _finish = nullptr; iterator _endofstorge = nullptr; }; } 

五、结语

        这就是我们所实现的vector的全部内容了,我们的目的仍然是了解vector的用法、加深类和对象和模板等知识点的理解,感谢大家的阅读,欢迎各位于晏、亦菲和我一起交流、学习、进步!

                

Read more

【C++】告别“类型转换”踩坑,从基础到四种核心强制转换方式

【C++】告别“类型转换”踩坑,从基础到四种核心强制转换方式

各位大佬好,我是落羽!一个坚持不断学习进步的学生。 如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步! 也欢迎关注我的blog主页:落羽的落羽 文章目录 * 一、回顾C语言的类型转换 * 二、C++中的类型转换 * 1. 内置类型转为自定义类型 * 2. 自定义类型转为内置类型 * 3. 自定义类型之间的转换 * 4. 类型安全与C++的四种强制类型转换方式 * 4.1 什么是类型安全? * 4.2 static_cast * 4.3 reinterpret_cast * 4.4 const_cast * 4.5 dynamic_cast 一、回顾C语言的类型转换 C语言的类型转换主要是隐式类型转换和强制类型转换: * 隐式类型转换,是编译器在特定情况下自动进行的类型转换,通常发生在不同类型的表达式运算中。主要是整型之间、整型与浮点型之间、

By Ne0inhk
【C++ Qt】多元素控件(ListWidget、TableWidget、TreeWidget)

【C++ Qt】多元素控件(ListWidget、TableWidget、TreeWidget)

每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论 : 本章将通过代码示例详细介绍了Qt中QListWidget、QTableWidget和QTreeWidget三种多元素控件的使用方法与核心功能,涵盖列表的增删操作、表格的行列管理以及树形结构的节点处理。 ———————— 早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。 多元素控件 Qt 中提供的多元素控件有: 1. QListWidget 2. QTableWidget 3. QTreeWidget QTreeView(前两个表示树形结构) QTableView(前两个表示表格结构) QListView(前两个表示列表结构) 不难发现两种结构中有:xxWidget 和 xxView 之间的区别 1. xxView 是更底层的实现 2. xxWidget 是基于 xxView 封装起来的 例如: * QTableView 是基于 MVC

By Ne0inhk
C++ —— 哈希详解 - 开散列与闭散列

C++ —— 哈希详解 - 开散列与闭散列

江河入海,知识涌动,这是我参与江海计划的第6篇。 目录 1. 哈希的概念 1.1 直接定址法 1.2 哈希冲突  1.3 负载因子 1.4 哈希函数  1.4.1 除法散列法/除留余数法   1.4.2 乘法散列法  1.4.3 全域散列法 1.5 处理哈希冲突 1.5.1 开放定址法(闭散列) 1. 线性探测(挨着查找) 2. 二次探测(跳跃着查找) 3. 双重散列 2. 闭散列实现哈希表 2.

By Ne0inhk