Vector

构造函数

int main()
{
	vector<int> v;//无参构造(其实是全缺省)
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	vector<int> v1(v);//拷贝构造
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}


void test_vector()
{
	vector<int> v1(10, 1);//填充构造
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	vector<int> v2(v1.begin(), v1.end());//迭代器构造
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;
	string s1("hello wjj");
	vector<char> v3(s1.begin()+3, --s1.end());
	for (auto e : v3)
	{
		cout << e << " ";//lo wj
	}
	cout << endl;
    vector<char> v4(s1.begin(), s1.end());//只要迭代器的指向可以传到容器的数据类型上去,就可以构造。char类型也可以用整型接收。cout的话就是输出对应的ASCII码值
}

迭代器

所有迭代器的区间是左闭右开的

对于vector和string这种类型或许迭代器并不是很好用,因为有更好的范围for(缺点是不支持倒序)和[]+下标。但是以后的容器比如说list,虽然也可以重载[],但是由于它的底层结构并不是顺序表,所以实现的时间复杂度太高了,需要遍历才可以从第一个开始找,这个时候需要依赖迭代器。

resize函数

void resize (size_type n, value_type val = value_type())

void test_vector4()
{
	vector<int> v1;
	v1.resize(10, 0);//resize的时候直接给缺省值
	vector<int> v2(10, 1);
	for (auto ch : v1)
	{
		cout << ch;
	}
	cout << endl;
	for (auto ch : v2)
	{
		cout << ch;
	}
}

erase函数和insert函数

iterator erase (iterator position);//删除一个

iterator erase (iterator first, iterator last);//删除连续个

iterator insert (iterator position, const value_type& val);//single element 

void insert (iterator position, size_type n, const value_type& val);//fill

template <class InputIterator>  void insert (iterator position, InputIterator first, InputIterator last);//range

erase\insert采用的都是迭代器,但是vector并没有find函数,而是采用的标准库里面的std::find,之所以在vector中不提供find的函数的原因是因为标准库里面的find的函数是适配vector以及其他的容器的。但是由于vector的底层也是数组,所以并不建议过多的使用这两个函数。

template <class InputIterator, class T>   

InputIterator find (InputIterator first, InputIterator last, const T& val);

函数返回值是:If no elements match, the function returns last。而我们提供的迭代器的区间是左闭右开的,所以last我们是取不到的,这里通过返回last来告诉我们并没有匹配的选项。

void test_vector()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	vector<int>::iterator pos = find(v.begin(), v.end(), 2);//采用标准库的find函数
	if (pos != v.end())
	{
		v.insert(pos, 20);
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	pos = find(v.begin(), v.end(), 2);//想要得到pos必须要再继续find一遍,因为pos存在迭代器失效的问题
	if (pos != v.end())
	{
		v.erase(pos);
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	v.erase(v.begin());
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

vector<char>

首先string类里面结尾会带'\0',但是vector<char>是没有的。其二vector<char>比较大小的方法和string并不是同一个比较逻辑。最后string还有许多自己特殊的要求,虽然二者再结构以及用法有许多相似的地方。

vector模拟实现

对于编译器,空间并不是直接new出来的,new出来的空间优势是已经初始化了,已经初始化的意思是比如new出来的class T的空间返回值是ptr,ptr所指向的位置一定是一个已存在的以初始化的对象,所以可以赋值,我们可以直接通过*ptr=T(xxx),但是这种方式的缺点是需要空间就去new,new需要去堆区开辟空间。而编译器一般是先去堆区申请一大块空间到内存池这个地方,但这一大块空间是没有初始化的,以后需要空间就直接去内存池去拿,但是要通过定位new对该空间进行初始化赋值。

内置类型不管是否初始化都可以赋值,但是自定义类型对于一个内存要初始化了才可以进行赋值,自定义类型new出来的空间是初始化的,这里初始化了也可以理解为就相当于是一个实例化的对象了,所以可以对自定义类型赋值。

构造函数

默认构造和fill构造

vector()
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{

}
vector(size_t n, const T& val = T())//第二个参数是const T& val = T()中T()是匿名对象,匿名对象周期就在这一行,按道理会导致val在下面的代码中无法继续使用,因为val所代表的数据已经销毁了。但是const(普通医用不行是因为匿名对象具有常性)引用会延长匿名对象的生命周期到引用对象的作用域结束,这是因为以后用val就是用匿名对象,就好像匿名对象重新有了一个名字。
//必须要写初始化列表,否则的话用vector<int> v1(10,5)来进行实例化对象的时候,v1的成员变量都是随机值,此时开始进入reserve(n)函数,这个函数由于_start非空,会进入到delete[]_start、memcpy函数,导致后面一些列错误。所以必须初始化。
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

 fill构造函数有一个参数是const T& val = T(),对于自定义类型是存在构造函数的,那内置类型呢?c++由于函数模板的出现,不得不要求内置类型也必须要有构造函数,为了兼容函数模板的概念。也是这个原因,我们一般希望自定义类型最好要有一个默认构造函数,可以是编译器自己生成的,也可以是无参的或者是全缺省的。

int main()
{
	int i = int();
	int j = int(2);
	return 0;
}

迭代器构造

类模板的成员函数又是一个模板,这里采用模板而非iterator的原因是因为如果采用iterator就将这个迭代器构造的函数写死了,只可以用vector的迭代器来进行构造。但事实并不是这样,只要类型符合,任意类型的迭代器都可以用。也就是只要存储的数据类型符合,比如说两方都是int、char或者是可以隐式类型转换如一个是int一个是char。

//采用迭代器进行构造
template <class InputIterator>
vector(InputIterator first, InputIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
	{
		while (first != last)
		{
			push_back(*first);
			first++;
		}
	}
//当有了迭代器构造和fill构造函数之后,会遇到下面的问题
void testvector3()
{
	vector<int>v(10, 5);//该行代码会调用哪个构造函数,其实会调用迭代器构造函数,然后报错非常间接寻址,因为他会对10进行解引用。因为他会把class InputIterator看成int类型,而vector(size_t n, const T& val = T())第一个参数是无符号整型,第二个是整型的引用,调用fill构造函数的时候第一个需要进行类型转换,相比迭代器构造,没有那么的匹配。
}
//解决方法1
void testvector3()
{
	vector<int>v(10u, 5);//让编译器将第一个参数认为是无符号整型
}
解决方法2,再重载一个参数类型不同的fill构造函数
vector(int n, const T& val = T())
{
	reserve(n);
	for (int i = 0; i < n; ++i)
	{
		push_back(val);
	}
}
//迭代构造函数的使用
void testvector3()
	{
		vector<int>v1(5u, 5);
		v1.insert(v1.begin(), 2);
		v1.insert(v1.begin(), 4);
		print(v1);
		vector<int> v2(v1.begin(), v1.end());
		print(v2);
		vector<int> v3(v1.begin()+1, v1.end()-1);//vector<int> v3(v1.begin()++, v1.end()--)是会报错的,因为begin()、end()是传值返回,返回的是一个拷贝,这个拷贝其实是一个临时对象,这个临时对象具有常性,无法自己++或者--
		print(v3);
		std::string s1("hello wjj");
		vector<int> v4(s1.begin(), s1.end());
		print(v4);
		int arr[] = { 1,2,3,4,5,6 };
		vector<int> v5(arr, arr + 6);//此处采用的是原生指针,因为原生指针可以当成天然的迭代器,但要求原生指针指向数组。迭代器的区间是左闭右开,所以要注意第二个参数其实是指向最后一个有效数据arr+5的下一个位置arr+6
		print(v5);
	}

拷贝构造

//如果采用系统默认的拷贝构造,也就是浅拷贝(值拷贝)
void testvector5()
{
	vector<int> v1;
	v1.push_back(5);
	v1.push_back(2);
	v1.push_back(7);
	v1.push_back(3);
	v1.push_back(6);
	v1.push_back(8);
	vector<int> v2(v1);
}//后果就是v2和v1的_start是指向堆区的同一个位置,会析构两次


//拷贝构造,深拷贝通过复用实现
vector(const vector<T>& v)
{
	reserve(v.capacity());
	for (auto ch : v)
	{
		push_back(ch);
	}
}
拷贝构造,深拷贝通过传统方法实现
vector(const vector<T>& v)
{
	_start = new T[v.capacity()];
	memcpy(_start, v._start, sizeof(T)*)v.size()];
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

//解决memcpy是浅拷贝的问题
vector(const vector<T>& v)
{
	_start = new T[v.capacity()];
	for (size_t i = 0; i < v.size(); ++i)
	{
		_start[i] = v._start[i];//通过赋值来解决memcpy浅拷贝的问题,比如说string的赋值,回去开一个一样大的空间,并且将数据拷贝过来。
	    _finish = _start + v.size();
	    _end_of_storage = _start + v.capacity();
    }
}
//拷贝构造一种比较现代的写法
vector(const vector<T>& v)
		{
			vector<T> tmp(v.begin(), v.end());//迭代器构造是通过push_back迭代器v.begin()来实现的,而push_back是通过*finish=*v.begin()来实现的,赋值运算符重载采用深拷贝自己写
			swap(tmp);
		}
//由上述问题牵扯出来的reserve函数也存在第二层是浅拷贝的问题
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			memcpy(tmp, _start, sizeof(T) * sz);//如果T是string的这种自定义类型,扩容之后vector中的string元素的_str成员变量和扩容之前的string元素的_str是指向同一个空间,但是下一行代码马上就对这些旧空间进行了析构
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;//不要写成了_finish=_start+size()=_start+_finish-_start,否则_finish还是老样子,没有存在更新
		_end_of_storage = _start + n;
	}
}
//reserve函数的优化
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * sz);c次数是浅拷贝
			for (size_t i = 0; i < size(); i++)
			{
				tmp[i] = _start[i];//但是产生了新的问题,如果此处T是vector<int>类型,需要用到赋值运算符重载,这里如果我们不写的话,就采用编译器默认生成的运算符重载,而这里是浅拷贝。所以我们要自己写一下赋值运算符重载,以实现深拷贝。
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;//不要写成了_finish=_start+size()=_start+_finish-_start,否则_finish还是老样子,没有存在更新
		_end_of_storage = _start + n;
	}
}

 赋值运算符重载

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> v)//比如说v1=v2;此时v是v2的拷贝构造,此时v是v1想要的,而v1本身的可以丢弃了
{
	swap(v);
	return *this;
}

 

以上的几个构造函数都重复写了初始化列表,由于都是内置类型,我们可以不写初始化列表,全部在类的成员变量声明的地方给缺省值,缺省值是给初始化列表用的,构造函数有初始化列表,如果不屑初始化列表,会自动拿缺省值进行初始化。

迭代器的指针类型

int main()
{
	std::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	std::vector<int>::iterator it = v.begin();
	cout << typeid(it).name() << endl;//该函数可以用来输出对象的数据类型
	return 0;
}

在gcc编译器当中,其实vector的迭代器是原生指针类型,我们这边模拟实现的方式也是按照linux的版本实现的。但是在vs当中迭代器并不是原生指针类型,可以理解为原生指针的封装。

typedef T* iterator;
typedef const T* const_iterator;

reserve函数(再拷贝构造中还要进行修改)

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			memcpy(tmp, _start, sizeof(T) * sz);
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;//不要写成了_finish=_start+size()=_start+_finish-_start,否则_finish还是老样子,没有存在更新
		_end_of_storage = _start + n;
	}
}

 resize函数

void resize(size_t n, T val = T())
{
	if (n < size())
	{
		_finish = _start + n;//缩小size就是将_finish减减
	}
	else
	{
		if (n > capacity())
		{
			reserve(n);
		}
		while (_finish != _start + n)//_finish指向的最后一个有效数据,而_end_of_storage指向的位置是整个容器的最后一位
		{
			*_finish = val;
			++_finish;
		}
	}
}

push_back函数

void push_back(const T& x)
{
	if (capacity() == size())
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	*_finish = x;
	++_finish;
}

insert函数

iterator insert(iterator pos, const T& val)//防止迭代器失效的方法之一是insert之后我们默认迭代器pos失效了,就不再使用了。
{
	assert(pos <= _finish);
	assert(pos >= _start);
	if (capacity() == size())
	{
		size_t len = pos - _start; 
		reserve(capacity() == 0 ? 4 : 2 * capacity());//这里的扩容基本上是异地扩容,所以pos的位置在扩容之后应当发生调整。	pos = _start + len;这里是用来更新扩容之后的pos,解决pos这个迭代器失效的问题。然后再往pos位置插入val。
	    //但是问题是出了这个函数,pos还是扩容之前的pos,因为形参的改变并不能影响实参。这里的解决办法是将pos做为函数的返回值。stl库里面返回的也是这个变量值。
	    // 还有一个解决方法将第一个形参改为引用iterator& pos,但是会有问题,因为v.insert(v.begin(),0)是会报错的。这是因为所有的传值返回返回的都是拷贝,这种拷贝称为临时对象,这种临时对象具有常性,是不能直接传给引用的,可以产给const itreator& pos,但是这样的话会导致pos没办法更新。
	    //但我们也要知道,如果没有扩容这个步骤,是不会有上述这些问题的
	}
	iterator end = _finish;
	while (end > pos)
	{
		*end = *(end - 1);
		end--;
	}
	*pos = val;
	++_finish;
	return pos;
}

erase函数

iterator erase(iterator pos)//erase之后的,我们依旧认为它失效了,最好不要去访问了。因为当你erase的是vector中的末尾位置的有效数据,其实此时你再去访问pos位置就显的不是很合理了。
{
	assert(pos >= _start);
	assert(pos < _finish);
	iterator front = pos;
	while (front < _finish - 1)
	{
		*(front) = *(front + 1);
		front++;
	}
	_finish--;
	return pos;
}

迭代器失效的问题

insert迭代器会失效是因为insert有可能导致扩容,扩容之后pos的位置是发生变化了的,如果不对pos进行更新的话,pos就属于野指针。erase我们也认为迭代器失效了,因为当pos的位置是_finish-1,erase之后再去访问pos就不是合法的了,因为此时pos的位置和_finish的位置是相同的,而_finish并不是一个合法的位置。对于迭代器的比较,推荐使用!=,虽然现在vector和string由于底层是顺序表,可以使用大于小于进行比较,但是一旦到了list链表阶段,大小与就不好使了。

对于迭代器失效的问题,erase和insert都是通过返回值来进行解决的,insert返回pos,erase返回的是pos的下一个数据的位置,其实如果采用我自己实现的代码,返回的还是pos。

string也有迭代器失效,但是insert和erase喜欢用下标,所以很少出现迭代器失效的问题。

sort函数

void testvector4()
{
	vector<int> v1;
	v1.push_back(5);
	v1.push_back(2);
	v1.push_back(7);
	v1.push_back(3);
	v1.push_back(6);
	v1.push_back(8);
	print(v1);
	std::sort(v1.begin(), v1.end());
	print(v1);
	int arr[] = { 1,9,7,6,8,3 };
	std::sort(arr, arr + 6);
	for (auto ch : arr)
	{
		std::cout << ch;
	}
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值