目录
在上两篇有关vector的模拟实现中,还有构造,拷贝构造,赋值拷贝以及析构函数没有实现,本篇主要实现四个函数。
vector析构函数模拟实现
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
如果start指向空间,那么就释放掉该空间,并给其余成员变量置为空指针nullptr。
vector赋值拷贝模拟实现
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
// v1 = v2
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
直接交换两个自定义类型变量里的成员变量即可。
vector拷贝构造模拟实现
// 强制编译器生成默认的
vector() = default;
// v2(v1)
vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}
例如要用v1拷贝构造v2,先开辟一个和v1大小相同的空间,再把v1里的元素依次尾插至v2。
vector构造函数模拟实现
类模板的成员函数
// 类模板的成员函数
// 函数模板 -- 目的支持任意容器的迭代器区间初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
类模板的成员函数也可以构造函数模板。使用方法如下图所示:
void test_vector6()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
vector<int> v2(v1.begin() + 1, v1.end());
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
}
它和拷贝构造的区别是可以使拷贝的空间可控,如果我们只要第二个元素开始进行拷贝,可以begin + 1。
此处的InputIterator意味着任意的迭代器都可以使用,例如下代码所示:
string s("hello");
vector<int> v3(s.begin(), s.end());
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
这里利用了string的迭代器,又因为是要int类型的v3,所以hello中五个字符按照ascii码转换成int类型进行拷贝v3。
n个val构造
vector(size_t n, const T& val = T()) //T()不能用0代替
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
此处T( )不能用0代替,此处为匿名对象,如果模板为string,list的时候,用0作为缺省值就会发生错误。此外,C++对内置类型进行了优化升级,也有自己的构造。
// C++内置类型进行了升序,也有构造
int i = 0;
int j(1);
int k = int();
int x = int(2);
就是为了兼容上述n个val构造的函数,是函数能实现更多的初始化。以下代码发生了错误:
vector<int> v3(10, 1);
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
单参数和多参数对象隐式类型转换
以A类型为例:
class A
{
public:
A(int a1 = 0)
:_a1(a1)
, _a2(0)
{}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
private:
int _a1;
int _a2;
};
// 单参数和多参数对象隐式类型转换
// 省略赋值符号
A aa1(1, 1); //一个参数
A aa2 = { 2, 2 }; //两个参数
A aa9{ 2, 2 }; // 不要
const A& aa8 = { 1,1 }; //引用的是中间产生的临时变量
A aa3(1);
A aa4 = 1;
那么vector也可以用容器{ } 来进行初始化吗?
vector<int> v1{ 1,2,3,4,5,6 };
这里的隐式类型转换,跟上面不一样,这里参数个数不固定。
void test_vector()
{
// 这里的隐式类型转换,跟上面不一样,这里参数个数不固定
vector<int> v1({ 1,2,3,4,5,6 }); //直接构造
vector<int> v2 = { 10, 20, 30 }; //隐式类型转换
auto il1 = { 1, 2, 3, 4, 5, 6 };
initializer_list<int> il2 = { 1, 2, 3 };
cout << typeid(il1).name() << endl; //class std::initializer_list<int>
cout << sizeof(il2) << endl; //内部为两个指针,指向它的开始和结束
for (auto e : il1)
{
cout << e << " ";
}
cout << endl;
vector<A> v3 = { 1, A(1), A(2,2), A{1}, A{2,2}, {1}, {2,2} };
}
这里我们要模拟实现一个initializer_list
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto e : il)
{
push_back(e);
}
}
使用memcpy拷贝问题
void test_vector9()
{
vector<string> v1;
v1.push_back("111111111111111111");
v1.push_back("111111111111111111");
v1.push_back("111111111111111111");
v1.push_back("111111111111111111");
v1.push_back("111111111111111111");
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
问题分析:
- memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
- 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且 自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
此处发生了错误,打印出了其他值,大概原因可以考虑为扩容出现问题了。
memcpy都是浅拷贝,现_str与原_str指向的同一个空间,delete空间释放后,tmp的_str也跟着小时,这就导致了现在的_str成为了野指针。解决办法如下所示:
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();
T* tmp = new T[n];
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * oldsize);
for (size_t i = 0; i < oldsize; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + oldsize;
_end_of_storage = _start + n;
}
}
这里调用string的赋值拷贝,开辟一个新的空间,将原string里的值搬到新开辟空间的string,并且两者指向不同的空间,这样delete原vector就没影响了。
结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。