您的位置:首页 > 新闻 > 资讯 > C++初阶学习——探索STL奥秘——vector的模拟实现

C++初阶学习——探索STL奥秘——vector的模拟实现

2024/10/11 13:23:24 来源:https://blog.csdn.net/Joker10085/article/details/142284545  浏览:    关键词:C++初阶学习——探索STL奥秘——vector的模拟实现

vector的结构比较特殊,成员变量为三个指针

#pragma once
#include <iostream>
using std::cin;
using std::cout;
using std::endl;#include <string>
using std::string;namespace Yohifo
{template<class T>class vector{public:typedef T value_type;typedef value_type* pointer;	//指针typedef const value_type* const_pointer;typedef value_type* iterator;	//迭代器typedef const value_type* const_iterator;typedef value_type& reference;	//引用typedef const value_type& const_reference;private:iterator _start;	//指向起始位置iterator _finish;	//指向有效元素的下一个位置iterator _end_of_storage;	//指向可用空间的下一个位置};
}

 

1、默认成员函数

默认成员函数需要自己设计,因为涉及深浅拷贝问题

//默认构造
vector() :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}//带参构造
vector(size_t n, const T& value = T()):vector()
{reserve(n);	//扩容while (n--) push_back(value);	//逐个尾插
}
//额外版本,避免匹配上迭代器区间构造
vector(int n, const T& value = T()):vector()
{reserve(n);	//扩容while (n--) push_back(value);	//逐个尾插
}//迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last):vector()
{//考虑提前计算容量InputIterator cur = first;int len = 0;while (cur != last) ++len, ++cur;reserve(len);while (first != last) push_back(*first), ++first;
}

注意:

 在设计带参构造函数时,需要再额外提供一个vector(int b,const T& value=T())版本

以防使用vector<int>v(10,6)(构造对象,内容为10个6)优先匹配上迭代器构造,此时会造成非法简介寻址错误

此时多处用到了匿名对象作为缺省值

vector(size_t n, const T& value = T());
vector(int n, const T& value = T());

带参构造、拷贝构造、迭代器区间构造等函数创建新对象前,需要先初始化,有多种初始化方法:

1.在定义成员变量后设置缺省值

2.在创建新对象前手动进行初始化(初

始化列表)

3.d调用默认构造进行初始化

这里采用的是初始化列表调用默认构造函数初始化的方式

匿名对象调用默认构造就是需要写T(),如果匿名对象的无参构造需要写成T(),要是直接写成T,就会被当做是类型T,会出现语法报错

1.2拷贝构造

//拷贝构造-传统写法
vector(const vector<T>& v):vector()
{reserve(v.capacity());	//扩容size_t pos = 0;while (pos < v.size()) *(_start + pos) = *(v.begin() + pos), ++pos;_finish = begin() + v.size();
}
拷贝构造-现代写法
//vector(const vector<T>& v)
//	:vector()
//{
//	vector<T> tmp(v.begin(), v.end());	//构造临时对象
//	swap(tmp);	//直接交换
//}

拷贝构造对象前可以 先进行扩容,避免空间浪费; 采用逐个数据赋值拷贝的方式进行拷贝,因为有可能是自定义类型,逐个赋值可以避免浅拷贝问题


比如 T为 string 类型,实际调用时是这样的 this[pos]= v[pos](string 对象,调用对应的赋值
重载函数)


注意: vector 的拷贝构造函数必须自己写,默认生成的是浅拷贝


现代写法着重交换思想,利用选代器区间构造出临时对象,再将临时对象“交换”给当前对象即可
这种方式有点窃取劳动成果的感觉-

1.3赋值重载

//赋值重载-传统写法
vector<T>& operator=(const vector<T>& v)
{if (this != &v){reserve(v.capacity());	//扩容size_t pos = 0;while (pos < v.size())*(_start + pos) = *(v.begin() + pos), ++pos;_finish = begin() + v.size();}return *this;
}
赋值重载-现代写法
void swap(vector<T>& v)
{//交换三个内置类型,效率要高得多//std::swap(_start, v._start);//std::swap(_finish, v._finish);//std::swap(_end_of_storage, v._end_of_storage);
//}
//vector<T>& operator=(vector<T> tmp)
//{
//	swap(tmp);//	return *this;
//}

赋值重载的传统写法与拷贝构造基本一致,赋值重载中不需要新建对象,因为是“赋值”。注意: 赋值前,可以先判断两个对象是否为同一个,如果是,则不需要进行操作

1.4构析函数

//析构函数
~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}

start 指向已开辟空间的首位置,可以直接进行空间释放


注意:空间申请时,使用的是 new[],因此释放时需要使用 delete []


1.5经典问题:深度拷贝


众多构造函数都离不开空间调整函数 reserve ,所以这里提前进行学习,并且 reserve 在实现时会出现一个经典问题:深浅拷贝

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();	//需要先保存 _start 与 _finish 间的距离pointer tmp = new value_type[n];	//开辟新空间if (begin()){//memcpy(tmp, begin(), size() * sizeof(T));	//不能直接移动size_t pos = 0;while (pos < sz){//调用自定义类型的赋值重载函数,完成深拷贝*(tmp + pos) = *(begin() + pos);	//深拷贝pos++;}delete[] begin();	//释放原空间}_start = tmp;	//赋值新空间_finish = _start + sz;_end_of_storage = _start + n;}
}

函数执行步骤:


判断 n是否大于容量,大于才需要进行扩容


保存有效元素数(大小),后面有用


三步走:开辟新空间 ->移动元素至新空间 ->释放旧空间,更改指向


注意: 在将旧空间中的数据移动至新空间时,不能直接通过 memcpy/memmove 的方式进行数据移动,因为这些库函数都是浅拷贝,使用后会造成重复析构问题


举例:使用 memcpy 进行数据迁移

 

 

 

 实际上,拷贝构造、赋值重载、reserve 都需考虑深度拷贝的问题

一句话总结:对于自定义类型来说,在进行拷贝/赋值等操作时,调用对应的赋值重载函数即可


reserve 扩容时,发生了这些事情:

2.迭代器 

 vector的迭代器就是原生指针

typedef T value_type;
typedef value_type* iterator;	//迭代器
typedef const value_type* const_iterator;//=====迭代器设计=====
iterator begin() { return _start; }
iterator end() { return _finish; }const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }

3、容量

3.1查看容量

//=====容量相关=====
size_t size() const { return end() - begin();  }
size_t capacity() const { return _end_of_storage - begin();  }
bool empty() const { return begin() == end();  }

3.2、容量调整


可以调整容量( reserve ),也可以调整大小( resize)
reserve 前面已经介绍过了,这里来看看resize 

void resize(size_t n, const_reference val = value_type())
{if (n < size())erase(begin() + n, end());elseinsert(end(), n - size(), val);
}

操作步骤:


判断 n是否大于大小
如果小于,执行删除,区间为[begin()+n,end()]
如果小于或等于,就执行插入,区间为[end(),n-size(),val]


value_type 就是 T,缺省值为默认对象值

4、数据访问

4.1下标访问

有两种方式,分别是[]和at

//=====数据访问=====
reference operator[](size_t pos)
{assert(pos >= 0 && pos < size());return *(begin() + pos);
}
const_reference operator[](size_t pos) const
{assert(pos >= 0 && pos < size());return *(begin() + pos);
}reference at(size_t pos) { return (*this)[pos]; }
const_reference at(size_t pos) const { return (*this)[pos]; }

4.2队尾元素

reference front() { return (*this)[0]; }
const_reference front() const { return (*this)[0]; }
reference back() { return (*this)[size() - 1]; }
const_reference back() const { return (*this)[size() - 1]; }

5.修改 

5.1首尾删减

void push_back(value_type val)
{if (size() == capacity())reserve(capacity() == 0 ? 4 : capacity() * 2);	//考虑容量为0的情况*_finish++ = val;	//在最后一个位置插入
}void pop_back() 
{assert(!empty());--_finish;
}

关于尾插,还有一个类似的拼接函数 assign ,将某段区间或个 val 值,拼接至对象后面 

//=====数据修改=====
template <class InputIterator>
void assign(InputIterator first, InputIterator last)
{//迭代器区间拼接InputIterator cur = first;int len = 0;while (cur != last) ++len, ++cur;reserve(len);while (first != last) push_back(*first), ++first;
}
void assign(int n, const value_type& val)
{reserve(n);	//提前扩容while (n--) push_back(val);
}

5.2任意位置增删

iterator insert(iterator pos, const_reference val)
{//先记录当前迭代器的位置size_t sz = pos - begin();if (size() == capacity())reserve(capacity() == 0 ? 4 : capacity() * 2);	//考虑容量为0的情况pos = begin() + sz;	//更新迭代器iterator cur = end();while (cur != pos) *cur = *(cur - 1), --cur;*cur = val;	//插入数据++_finish;	//尾向后移动return pos;	//返回新迭代器位置
}
iterator insert(iterator pos, size_t n, const_reference val)
{while (n--) pos = insert(pos, val), pos++;	//正确写法return pos;
}iterator erase(iterator pos)
{assert(pos >= begin() && pos < end());iterator cur = pos;while (pos != end()) *pos = *(pos + 1), ++pos;--_finish;return cur;
}
iterator erase(iterator first, iterator last)
{//迭代器区间删除//两个结束条件:last == _finish//while (last != _finish) *first = *(last + 1), ++first, ++last;	//错误写法while (last != _finish) *first = *last, ++first, ++last;	//正确写法_finish = first;return _finish;
}

迭代器区间删除时,区间为左闭右开,在进行数据覆盖时,需要写成 *first = *last 而非 *first = *(last + 1),这样会导致删除出现问题

5.3迭代器失效

insert可能会导致迭代器失效

这是因为当插入数据需要扩容时,返回的pos位置还是原来的那块地址,但是扩容后插入的位置已经发生了变化,所以会导致迭代器失效。

为了解决这个问题,迭代器要返回插入后的位置

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com