Vector
本节内容
- vector的介绍及使用
- vector深度剖析及模拟实现
string和vector
- vector char和string非常的类似,既然vector中也可以存放字符,那么为什么STL还要单独给出string呢?
vector的介绍及使用
vector的介绍
- vector是表示可变大小数组的序列容器。
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
- 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
- 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
- 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好
vector的使用
vector的定义
#include<iostream>
#include<vector>
#include<string>
using namespace std;
void TestVector1()
{
vector<int> v1;
vector<int> v2(10, 5); //给vector中放上10个值为5的元素
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
int array[] = { 1,2,3,4,5, };
//注意:STL中的所有区间都是左闭右开的
vector<int> v3(array, array + sizeof(array) / sizeof(array[0]));
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
//那么既然是左闭右开的,所以v4里面的元素就只有1,2,3
vector<int> v4(array, array + 3);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
string s("hello");
vector<char> v5(s.begin(), s.end()); //begin和end也是左闭右开的区间
for (auto e : v5)
{
cout << e << " ";
}
cout << endl;
//利用v3来拷贝构造v6
vector<int> v6(v3);
for (auto e : v6)
{
cout << e << " ";
}
cout << endl;
//C++新增了列表的初始化
vector<int> v7{ 1,2,3,4,5,6 };
for (auto e : v7)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestVector1();
return 0;
}
- 运行结果如下所示:
- vector中的迭代器
- 也可以这么写
#include<iostream>
#include<vector>
using namespace std;
void TestVector2()
{
vector<int> v{ 1,2,3,4,5 };
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.resize(10, 6);
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";;
}
cout << endl;
v.resize(3, 6);
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";;
}
cout << endl;
v.reserve(20); //第二个参数如果没有传递,那么使用0来进行填充
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.reserve(8); //第二个参数如果没有传递,那么使用0来进行填充
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestVector2();
return 0;
}
- 代码运行的结果如下所示:
- resize
#include<iostream>
#include<vector>
using namespace std;
class Date
{
public:
Date(int year=1900, int month=1, int day=1)
:_year(year),
_month(month),
_day(day)
{
}
private:
int _year;
int _month;
int _day;
};
void TestVector2()
{
vector<int> v{ 1,2,3,4,5 };
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.resize(10, 6);
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";;
}
cout << endl;
v.resize(3, 6);
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";;
}
cout << endl;
v.reserve(20); //第二个参数如果没有传递,那么使用0来进行填充
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.reserve(8); //第二个参数如果没有传递,那么使用0来进行填充
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
Date d(2020, 12, 12);
vector<Date> vd;
vd.resize(10, d);
vd.resize(20); //如果第二个参数没有传递的话,编译器会调用无参的构造函数
//进行填充,但是如果类中已经定义了构造函数,编译器将不会再去生成一个默认
//的构造函数,但是如果在上方的构造函数处将其变成全缺省的构造函数的话,这个代码
//就不会再出现问题了
}
int main()
{
TestVector2();
return 0;
}
- reserve
- 下面的这个代码其实实惠导致崩溃的,至于为什么崩溃,原因在于,reserve只是去改变了容量的大小,通过监视可以看到,底层的size的大小其实还是0,所以会造成代码崩溃的问题出现
#include<iostream>
#include<vector>
using namespace std;
void TestVector3()
{
vector<int> v1;
v1.reserve(10);
//注意:改行代码会崩溃
//原因:reserve(10)只是将容量的大小扩大到10,并不会增加有效元素的个数
//现在v1中有效元素的个数仍然是0,那么你要去访问0号位置的代码是肯定会崩溃的
cout << v1[0] << endl;
vector<int> v2;
v2.resize(10);
//下面的这个代码是没有什么问题的
cout << v2[0] << endl;
}
int main()
{
TestVector3();
return 0;
}
- 下面的代码可以得出的结论是:
- push_back在插入期间可能会进行扩容
- vector中扩容的机制是按照1.5倍的方式进行扩容在,在linux下是按照2倍的机制进行扩容的
- 扩容的具体操作是申请空间,拷贝元素,释放旧的空间,随着元素的不断增多,每次扩容的成本都很高,因此在插入期间,尽量避免扩容,那么,其实提前给好空间的大小就可以了,再插入的期间就不会去进行扩容的操作了
- 如果是自定义类型的对象,在push_back插入的是一份临时的拷贝,会去调用拷贝构造函数,所以说push_back的时候,其实我们是构造了两个对象的,效率有点低,也就是说每一次push_back期间都要进行拷贝构造,所以C++11提供就地构造
- C++11提供就地构造,emplace_back会在vector底层空间直接调用构造函数来构造对象,是不会产生对象的拷贝的,效率就会高一些,就不会再去调用拷贝构造函数了,只是去调用了构造函数,对象就完成了构造
#include<iostream>
#include<vector>
using namespace std;
void TestVector4()
{
vector<int> v;
size_t sz = v.capacity();
for (size_t i = 0; i < 100; i++)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << sz << endl;
}
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestVector4();
return 0;
}
- 关于insert的代码操作
#include<iostream>
#include<vector>
using namespace std;
void TestVector5()
{
vector<int> v{ 1,2,3,4,5 };
//需求,在3所在的位置插入0
//vector中本身是没有提供find的方法的,那么我们只能使用标准库中的find方法了
auto pos = find(v.begin(),v.end(),3);
if (pos != v.end())
{
v.insert(pos, 0);
}
v.insert(v.begin(), 5, 0);
//当然,也可以插入一段连续的空间
vector<int> vv{ 11,22,33 };
v.insert(v.end(), vv.begin(), vv.end());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestVector5();
return 0;
}
- 同时,也支持列表方式任意位置的插入
//支持列表方式任意位置的插入
v.insert(v.end(), { 100, 200, 300 });
- 既然有任意位置的插入,那么就存在有任意位置的删除
#include<iostream>
#include<vector>
using namespace std;
void TestVector6()
{
vector<int> v{ 1,2,3,4,5, };
v.erase(v.begin());
//相当于是从v.clear()
v.erase(v.begin(), v.end());
}
int main()
{
TestVector6();
return 0;
}
- 就地构造
- 容量相关问题
vector 迭代器失效问题
- 迭代器的本质就是一个指针或者说是对指针的封装
- vector的迭代器就是原生态的指针
- 迭代器失效指的就是指针不能正常使用,那么在什么情况下,指针是不可以正常使用的呢,我们知道,当指针指向的是一块非法的空间的时候,这个时候,指针是不可以正常使用的,也就是说这个指针现在是一个野指针,此时,他就是会失效的
- 对下面的这个代码进行运行的操作,我们可以发现,代码就发生了崩溃的现象,在一开始的时候,vector中只有1,2,3,4,5这5个元素,一开始it在最开始的位置,然后这个时候,我们需要去进行扩容的操作,然后,就会有一块新的空间,但是这个时候it指向的还是原来的那个旧的空间,在继续往下走,如果你想让两个迭代器进行比较的操作的话,代码就会发生崩溃的现象,it是不知道他原来所指向的空间已经发生了扩容的操作的,那么,迭代器就失效了
#include<iostream>
#include<vector>
using namespace std;
void TestVector7()
{
vector<int> v{ 1,2,3,4,5 };
auto it = v.begin();
v.push_back(6);
v.push_back(7);
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
int main()
{
TestVector7();
return 0;
}
- 哪些方式会导致迭代器的失效:
- assign
#include<iostream>
#include<vector>
using namespace std;
void TestVector7()
{
vector<int> v{ 1,2,3,4,5 };
auto it = v.begin();
//v.push_back(6);
//v.push_back(7);
v.assign(10,5);
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
int main()
{
TestVector7();
return 0;
}
- erase,删除操作时位于他后面的元素要整体向前进行搬移的操作
- 将2删除掉之后,it来到了3的位置处,此时如果在向下走一步的话,代码就会崩溃了,原因在于—假如说,现在vector里面只有一个元素,it现在就处于10的位置,然后现在你要去使用erase方法,那么此时需要将后面的元素往前般,但是现在,其实来说,后面是并没有元素的了,然后,这个时候end还需要向前挪动一个位置,此时,如果你对it进行解引用的操作的话,那就相当于是对end的位置进行解引用操作了,但是现在end位置是没有元素的,所以会使得迭代器失效
- eraseh会使所有的迭代器失效,只要erase了一次,迭代器就无法再使用了
- erase之后pos的迭代器就会失效了
#include<iostream>
#include<vector>
using namespace std;
void TestVector7()
{
vector<int> v{ 1,2,3,4,5,6,7,8,9,0 };
//现在我们需要将vector中所有的偶数删除掉
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it); //如果他是偶数的话,我们就使用erase将其删除掉
}
else
{
++it;
}
}
}
int main()
{
TestVector7();
return 0;
}
- 那么既然erase会使得迭代器失效,那么erase方法还能都和迭代器配合起来使用,其实时可以的,通过erase的返回值可以看出,erase的返回值是会返回要删除元素的下一个位置的元素返回回去,所以我们只需要用it接收一下就可以了
- 像下面这样写的话,代码其实就是不会崩溃的
#include<iostream>
#include<vector>
using namespace std;
void TestVector7()
{
vector<int> v{ 1,2,3,4,5,6,7,8,9,0 };
//现在我们需要将vector中所有的偶数删除掉
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it); //如果他是偶数的话,我们就使用erase将其删除掉
}
else
{
++it;
}
}
}
int main()
{
TestVector7();
return 0;
}
迭代器失效的解决方式
- 在进行如上使迭代器可能失效的操作之后,在下一次使用迭代器之前,必须给迭代器重新赋值就可以了,就不会导致迭代器失效了
- 重新进行赋值,迭代器就不会失效了,但是push_back也不是每一次都会导致迭代器失效,是否会导致迭代器的失效,主要看的是在使用push_back的时候会不会进行扩容的操作
vector创建二维数组
#include<iostream>
#include<vector>
using namespace std;
void TestVector8()
{
//矩阵:每行的元素个数都是相同的
//比如:5行6列的二维数组
vector<vector<int>> v1;
//二维数组总共有5行,现在每行还没有空间
v1.resize(5);
cout << v1[0].size() << endl;
cout << v1[0].capacity() << endl;
//经典的误用,如果像下面这样写代码的话,其实是会造成代码的崩溃的
//原因在于,v1[0]该vector还没有有效元素,所以无法访问0号位置的元素
//v1[0][0] = 10;
for (size_t i = 0; i < v1.size(); i++)
{
v1[i].resize(6);
for (size_t j = 0; j < v1[i].size(); ++j)
{
v1[i][j] = j + 1;
}
}
}
int main()
{
TestVector8();
return 0;
}
vector的模拟实现
- vector底层其实就是相当于一个动态数组,那么按照常理来说的话,其实是应该有三个字段的,一个字段用于指向存储元素的空间,一个字段用来存储元素的个数,一个字段用来存储容量的大小,但是vector底层的实现,并不是给出了像这样的三个字段,他给出的其实是,vector底层的实现其实是给出了三个迭代器,vector中的迭代器其实是typedef T* 起了一个别名,成为iterator,迭代器的类型其实就是原生态的指针
#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#pragma once
namespace bite
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
public:
vector()
//把三个变量一开始都给成空
:start(nullptr)
, finish(nullptr)
, end_of_storage(nullptr)
{
//我们在这个位置给出一个空的构造的的方法
//包括底层的空间我们也是没有给出的
}
vector(int n, const T& data = T())
{
//这个方法其实就是相当于提供了给出n个元素的构造的方法
//但是这里需要主义的一个点就是,如果这个T是内置类型的
//所有的元素,如果用户没有给出的话,那么他的值,其实就相当于是0
//如果是自定义类型的话,那么我们这个类必须要有无参的构造函数或者
//说是全缺省的构造函数,如果没有提供的话,那么代码肯定就是会发生报错的
start = new T[n];
for (int i = 0; i < n; i++)
{
start[i] = data;
}
finish = start + n;
end_of_storage = finish;
}
template<class Iterator>
vector(Iterator first, Iterator last)
{
//这个方法其实就是相当于我们给出叾一段区间的构造
//也就是说,用我们所给出的这段区间去对vector进行构造的操作
//参数一般给的是迭代器,那么真正的迭代器类型到底是什么,我们其实是不知道的
//所以在这个方法之前我们给出了模板参数列表
//给出的迭代器
//首先需要去计算一个这个区间中有多少元素,在计算出来之后,方便我去开辟空间
size_t size = 0;
auto it = first;
while (it++ != last)
{
++size; //然后元素个数就算是算出来了
}
//先去进行空间的开辟
//然后一个一个去进行赋值的操作
start = new T[size];
finish = start;
while (first != last)
{
*finish++ = *first++;
}
end_of_storage = finish;
}
//给出拷贝构造
vector(const vector<T>& v)
{
//先去开辟空间
//这里的拷贝构造的方式其实就是按照深拷贝的方式所给出来的
//因为我给新的对象重新去new空间了,所以相当于是深拷贝的方式
start = new T[v.size()];
finish = start;
auto it = v.begin();
while (it != v.end())
{
//空间开辟好了之后,我只需要把v里面的元素一个一个放进去其实就好了
*finish++ = *it++;
}
end_of_storage = finish;
}
//给出赋值运算符的重载
//vector<T>& operator=(const vector<T>& v);
//析构方法
~vector()
{
//进来之后先看一下start有没有指向空间
if (start)
delete[] start;
start = finish = end_of_storage = nullptr;
}
//给出迭代器的操作
iterator begin()
{
return start;
}
iterator end()
{
return finish;
}
const_iterator begin()const
{
return start;
}
const_iterator end()const
{
return finish;
}
//容量相关的操作
size_t size()const
{
return finish - start;
}
size_t capacity()const
{
return end_of_storage - start;
}
bool empty()const
{
//去看start和finish在不在一个位置,如果在一个位置的话,那么就表示他是空的了
return start == finish;
}
void resize(size_t newsize, const T& data = T())
{
//第二个参数的意思就是如果用户没有提供值去初始化的话
//那么就利用我们所给出的默认值去进行初始化的操作
//现在先去求一下oldsize,也就是去看一下旧的元素的个数有多少个
size_t oldsize = size();
if (newsize <= oldsize)
{
//将元素减少到newsize就可以了
finish = start + newsize;
//然后就将元素的个数变成newsize个了
}
else
{
//将元素个数增加到newsize个,那么既然是增加元素的话
//那么,很可能就会去进行扩容的操作
if (newsize > capacity())
{
reserve(newsize); //扩容到newsize个大小就可以了
}
//然后接下来去填充元素
for (size_t i = oldsize; i < newsize; i++)
{
*finish++ = data;
}
}
}
void reserve(size_t newcapacity)
{
size_t oldCapacity = capacity();
//是用来进行扩容的操作
if (newcapacity > oldCapacity)
{
//申请空间
//一般来说我们再去更改空间的代下的时候,linux下stl是按照两倍的
//方式去进行扩容的,vs下是按照1.5的方式去进行扩容的操作的
//但是那种情况是属于在边插入元素的同时边去进行扩容的操作
//现在的情况属于newsize的大小已经给出来了,就扩容到newsize
//容量的大小就可以了
T* temp = new T[newcapacity];
size_t oldsize = size();
//拷贝元素
//当start不为空的时候,再去进行拷贝的工作
if (start)
{
//这里是千万不可以使用memcpy的,因为使用memcpy的话,
//其实是相当于在进行浅拷贝的操作
//那么在使用string的时候其实就会因为同一个空间对多次释放从而导致代码崩溃的问题
//memcpy(temp, start, sizeof(T) * oldsize);
for (size_t i = 0; i < oldsize; i++)
{
temp[i] = start[i]; //在这个位置会调用赋值运算府重载
//赋值运算符重载其实就是使用了深拷贝的方式
//所以就避免了浅拷贝所带来的弊端
}
//释放旧的空间
delete[] start;
}
//使用新的空间
start = temp;
//start发生改变了,你再去调用之前的size就没有任何的意义了
//没有更新finish就去调用size方法,那么肯定是会出错的
finish = start + oldsize;
end_of_storage = start + newcapacity;
}
}
//元素访问的相关操作
//下标运算符
T& operator[](size_t index)
{
assert(index < size());
return start[index];
}
const T& operator[](size_t index)const
{
assert(index < size());
return start[index];
}
T& front()
{
return *start;
}
const T& front()const
{
return *start;
}
T& back()
{
return *(finish - 1);
}
const T& back()const
{
return *(finish - 1);
}
//modify
//修改操作
void push_back(const T& data)
{
//进行尾插的操作
if (finish == end_of_storage)
{
reserve(2 * capacity() + 3);
}
*finish++ = data;
}
void pop_back()
{
//进行尾删的操作
erase(end() - 1);
}
//返回值返回的是新插入的元素的位置
iterator insert(iterator pos, const T& data)
{
//如果要进行insert的话,那么我们首先需要去确保pos的有效性
if (pos<start || pos>end())
{
return end(); // 代表插入失败了
}
//同时,当你在进行元素插入的时候,你还需要去底层检测一下
//底层有没有空间,或者说,底层的空间够不够使用
if (finish == end_of_storage) //表明没有容量了
reserve(2 * capacity() + 3);
//需要将元素整体向后进行搬移,这个时候才可以将我们需要插入进去的元素成功的插入序列中
auto cur = finish - 1;
auto next = finish;
while (cur >= pos)
{
*next-- = *cur--;
}
*next = data;
//插入元素完成之后,finish还要向后走一步
finish++;
return pos;
}
//erase方法的返回值时返回待删除元素下一个元素的位置
iterator erase(iterator pos)
{
//任意位置的删除,其实就是将元素进行向前搬移的操作
if (pos < start || pos >= finish)
return finish;
auto prev = pos;
auto cur = prev + 1; //cur是需要搬移的第一个元素的位置
//然后我们可以开始进行搬移的操作了
while (cur < finish)
{
*prev++ = *cur++;
}
//因为元素的个数减少了一个
--finish;
return pos;
}
void clear()
{
finish = start;
}
private:
iterator start; //空间的起始位置
iterator finish; //最后一个元素的下一个位置
iterator end_of_storage; //空间的末尾
};
}
- insert操作
- erase操作
- 一开始出错的原因
- 浅拷贝的问题
- 将空间原封不动的搬过去
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;
#include"Vector.h"
#if 0
void TestVector1()
{
bite::vector<int> v1;
bite::vector<int> v2(10,5);
for (size_t i = 0; i < v2.size(); ++i)
{
cout << v2[i] << " ";
}
cout << endl;
int array[] = { 1,2,3,4,5 };
bite::vector<int> v3(array,array+sizeof(array)/sizeof(array[0]));
//当然,也可以去访问任意位置的元素
cout << v3.size() << endl;
cout << v3.capacity() << endl;
cout << v3[3] << endl;
cout << v3.front() << endl;
cout << v3.back() << endl;
//C++98中定义迭代器的方式,但是在这样写的话其实就太繁琐了
//bite::vector<int>::iterator it = v3.begin();
//所以我可以更改成一下的方式
auto it = v3.begin();
while (it != v3.end())
{
cout << *it << " ";
++it;
}
cout << endl;
bite::vector<int> v4(v2);
for (auto e : v4)
{
cout << e << " ";
}
cout<<endl;
}
void TestVector()
{
int array[] = { 1,2,3,4,5 };
bite::vector<int> v3(array, array + sizeof(array) / sizeof(array[0]));
//那么如何去验证范围for呢
//把拷贝构造函数屏蔽掉,把析构函数之后的所有的函数也全部都屏蔽掉
//那么可以看到,begin的方法和end的方法也被屏蔽掉了
//所以代码会出错,出错的原因就是没有begin方法和end方法
//得出的结论其实就是
//对于自定义类型,范围for:最终其实是转化为迭代器的方式来进行打印的
//如果用户所写的类要使用范围for来进行打印,那么必须要实现begin和end迭代器的接口
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
}
void TestVector2()
{
bite::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
cout << v.size();
cout << endl;
v.resize(7, 10); //将有效元素的个数设置成为7个,多出来的元素用10来进行填充
cout << v.size() << endl;
cout << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.resize(20);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
v.resize(10);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
v.pop_back();
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.insert(v.begin(), 0);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.erase(v.begin());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
#include"Vector.h"
int main()
{
//TestVector1();
TestVector2();
return 0;
}
#endif
class String
{
public:
String(const char* s = "")
{
if (s == nullptr)
s = "";
_str = new char[strlen(s) + 1];
strcpy(_str, s);
}
String(const String& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* temp = new char[strlen(s._str) + 1];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
void TestVectorStirng()
{
bite::vector<String> v;
v.push_back("1111");
v.push_back("2222");
v.push_back("3333");
v.push_back("4444");
}
void Test2Array()
{
//矩阵:每行元素的个数都一样
//定义了一个5行6列的数组
//每个元素我们使用10来进行填充
std::vector<std::vector<int>> v(5, vector<int>(6,10));
}
int main()
{
TestVectorStirng();
return 0;
}
回顾
- 全部指向空的字符串
- 调用了memcpy之后的场景如下所示,因为memcpy是字节拷贝,所以存在有浅拷贝的问题
- 那么存在的问题其实有两个,原先空串的空间因为没有被释放的原因,全部都丢失了;而且旧的空间中的string类对象和新空间中的string类的对象他们在底层用的是同一块内存空间,那么既然用的是同一块空间的话,那么在释放空间的时候,先将旧的空间给他释放掉,然后将每个string类的对象的资源给他清理掉(连续的空间在进行清理的时候是从最后一个开始往前进行清理的),那么就是先释放3333,然会释放2222,然后释放1111,那么就将stirng类的对象给他全部释完毕了,那么现在就需要去释放start所指向的空间,然后start所指向的空间就被释放掉了,那么当你把旧的空间释放掉的时候,新的空间就会出现问题,因为新空间中的每一个位置都是指向旧的空间的,现在旧的空间已经释放掉了,那么当你要去释放新的空间的时候,就会造成同一块空间被多次释放然后造成代码崩溃的问题出现了
有关二维数组的创建
- 每个vv的vector对象内部都包含了三个部分,这三个部分分别是start,finish,endofstorage
- 如果用n个值为val的元素来进行构造的话,如果第二个参数用户自己没有给出的话,编译器也是会为我们去给出的,编译器会调用vector的无参的构造函数给我们把第二个参数加上,使用vector<vector> v(n,vector());去进行构造,构造完成之后每一个对象中三个元素的指向全部都是空
- 如果使用for循环的话,那么其实就是vv中有几个元素就会循环几次
- 开辟好的空间就类似于下面这个样子
- 先把保存每一行的空间献给出来,然后再对每一行去开辟空间,使用完成之后记得进行释放的操作