目录
目录
1.深拷贝(string)
深浅拷贝:
浅拷贝:在拷贝构造过程中由于对自定义类型中如果存在需要在堆上开空间(new)的情况,那么在拷贝构造的时候新的对象也会指向同一个堆上的空间地址。那么在调用析构函数的时候此时就会造成对堆上new出来的对象释放两次,且指向这个空间任意一个对象进行对此空间修改的时候都会造成另一个对象的内容也随之发生改变。这个就是浅拷贝带来的问题。--解决的方法就是深拷贝
深拷贝:为了解决浅拷贝的问题,这里我们要对针对浅拷贝new的一个空间进行处理-我们只需要当拷贝构造的时候对新的对象也开辟一个同等大小的内存空间在将数据拷贝过去这样就解决了两个对象指向同一个内存空间。但这里我们应该注意在拷贝的时候我们应该要使用&传参这样可以大大减少拷贝构造。--
深拷贝的实现()
//深拷贝的实现方式1
string(const string& s)
:_str(new char [strlen(s._str)+1])// 创造一个同等大小的空间
{
strcpy(_str, s._str);
}
// 对于"="的赋值重载 为了避免空间两个大小不一致我们就先开辟一个需要存放拷贝内容大小的空间
// 然后释放之前的空间 再将需要拷贝的内容存放到新开辟空间内。
string& operator=(const string &s)
{
if(this!=s)
{
string * tmp=new string[strlen(s._str)+1]; //这里+1是为了存放\0
strcpy(tmp,s._str);
delete []_str;
_str=tmp;
}
return *this;
}
//深拷贝的另一种做法
//改进深拷贝--想法我们借用一个中间变量去完成这些事情之后将内容给到this即可
// string(string& s)
// :_str(nullptr)
// {
// string tmp(s._str);// 这里是去调用构造函数来创造空间并将s._str的内容拷贝给tmp
//
// swap(_str, tmp._str);// 将_str和tmp所指向的空间进行交换
// // 因为tmp是一个临时变量出了作用域就要调用析构函数,但是此时tmp所指向的内容已经为
// //_str所指向内容,如果没有对_str初始化那么交换后tmp指向的也是一个随机值
// //我们不能对指向随机值的空间进行析构 所以我们应先将_str赋予初始值nullptr;
// //tmp._str = nullptr;或者这样也可也
// }
string (string&s)
_str(nullptr)
{
string tmp(_s.str);
swap(_str,tmp._str);
}
// 或者
string (string&s)
{
string tmp(s._str);
swap(_str,tmp._str);
tmp._str=nullptr;
}
对于‘=’的重载
string& operator=(string s) //这里没有用&
//这里是用了拷贝函数去完成了开辟空间 在创造s的时候就回去调用
//拷贝构造这样就可以s拥有了一个新的空间且有了str的内容
{
swap(_str, s._str);
return *this;
}
2.迭代器失效(vector--insert,earse)
迭代器失效分为两种:1.由于新开辟了空间将原来的内容拷贝到新的空间上造成指向原来地址的指针成为野指针。2.没有开辟新的空间但是由于数组内容的移动,那么迭代器原来指向的内容发生了改变,那么也属于迭代器失效,对于vs下无论是那种情况对原来的迭代器解引用的时候都会报错。其中一般在vector中的earse 以及insert中出现。 为了避免出现迭代器失效的问题C++利用返回值来解决此问题,insert()返回的是插入的数值前的位置的迭代器,earse()返回的是消除的位置的后一个迭代器。
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto begin = v.begin();
while (begin != v.end())
{
if (*begin % 2 == 0)
{
begin = v.erase(begin);// 所以为了避免迭代器失效带来的问题 所以会指向被函数调用擦除的最后一个元素之后的元素的新位置的迭代器。
}
else
{
begin++;
}
}
// vector 中的earse和insert实现
iterator insert(iterator pos,const T& val)
{
assert(pos >= _start && pos <= _finish);
int len = pos - _start;
if (_finish == _endofstorage)
{
size_t newcapactity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapactity);
}
pos = _start + len;
auto end = _finish+1;
while(end>pos)
{
*(end) = *(end - 1);
end--;
}
*(pos) = val;
_finish++;
return pos;
}
iterator earse(iterator pos)
{
assert(pos >= _start && pos <_finish);
auto tmp = pos;
while (tmp != _finish)
{
*(tmp) = *(tmp + 1);
tmp++;
}
_finish--;
return pos;
}
3. 头文件展开问题:
//假设 vector.h中使用了cout。
#include<iostream>
#include"vector.h"
//那么此时编译就会出现问题,原因是头文件在编译阶段会展开,且无论是成员变量还是成员函数都是遵守的向上
//查找 那么此时vector被展开,且vector中使用了std的成员函数那么此时向上查找就无法找到std的,那么此时就会报错。
using namespace std;
// 正确的方式
#include<iostream>
using namespace std;
#include"vector.h"
4.vector 的深浅拷贝
对于vector而言也存在深浅拷贝问题(浅拷贝的问题:1.当拷贝多个对象的时候任意一个对象的修改都会造成其他对象也发生改变。2.当如果存在自定义类型的时候且含有在堆上new的空间的时候在调用析构函数的时候就会造成对一个空间多次析构)。
解决浅拷贝的问题:
深拷贝的传统想法 1.开辟同等大小的空间 然后将原对象的内容复制即可。
// V1(V);
vector(const vector<T>& v)
{
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T) * v.size());
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
void reserve(size_t n)
{
if (n > capacity())
{
int sz = size();
T* tmp = new T[n];
memcpy(tmp, _start, sizeof(T) * sz);
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
//第二种 思路:将自己的数据先置为空 然后将v的数据依次插入
vector(const vector<T>&v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(v.capacity());
for(const T&e:v)
{
push_back(v);
}
}
深拷贝的现代写法
现代的写法创造一个临时变量通过去调用构造函数然后与其进行交换,这里于string不同,由于string可以同过自己成员变量一次拿到字符且由于string自己含有'\0'就可以完整的通过构造函数拿到所有的字符,但是vector无法拿到,所以在就要通过迭代器来一次获取数据。
//由于不能获取到原数据,这里采用了迭代器来获取,这里使用模板的好处就是可以在构造过程的时候可以
//使用任何类型(不仅是内置类型,自定义类型也支持)只要和T的类型对应即可
template <class InputIterator>
vector (InputIterator first, InputIterator end)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first!= end)
{
push_back(*first);
first++;
}
}
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
void swap(vector<T>& v)
{
std::swap (_start, v._start);
std::swap (_finish, v._finish);
std::swap (_endofstorage, v._endofstorage);
}
void test()
{
string s("hello");
vector<char>t(s);// 这里由于使用的是模板的迭代器所以可以支持不同类型的拷贝
}
vector ("="的赋值重载)
对于“=”的赋值重载也是分为两种传统写法和现代写法。
传统写法的想法:将原对象的数据先清空,然后在新对象的内容一次赋值给原对象。
vector <T>& operator =(const vector<T>& v)
{
if (this != v)
{
delete[]_start;
_start = _finish = _endofstorage = nullptr;
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
return *this;
}
现代写法
现代写法通过一个临时变量来过度,将内容先拷贝到临时变量内,然后通过交换可完成。
vector <T>& operator =(const vector<T> v) //这里是巧妙的利用了深拷贝的拷贝构造将v对象的内容//全部拷贝给了临时对象v;
{
swap(v);
return *this;
}