【C++】vector

目录

一、vector的介绍

二、vector的常用接口

1.构造函数和赋值重载

1.1构造函数

1.2赋值重载

2.析构函数

3.迭代器相关操作函数

3.1        begin()

3.2        end()

 3.3        rbegin()

3.4        rend()

4.容器元素个数和容量操作函数

4.1        size()

4.2        resize()

4.3        capacity()

4.4        reserve()

4.5        empty()

5.获取容器元素的函数

5.1        operator[]()

5.2        at()

6.修改容器的函数

6.1        insert()

6.2        erase()

6.3        push_back()

6.4        pop_back()

 6.5        swap()

 三、总结


一、vector的介绍

vector是STL的八大容器之一,是表示大小可变数组的序列容器,与数据结构中的动态顺序顺序表本质相同。但是vector是用C++的类模板来实现的,封装了很多常用的功能函数,如构造函数、begin()、end()等函数,在平常的使用中可以简化代码,方便我们更好的解决问题。

1. vector是表示可变大小数组的序列容器
2.vector也采用的连续存储空间来存储元素。可以采用下标对vector的元素进行访问,和数组一样高效。它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,如果容量不够,会异地扩容。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配(例如:vs和g++编译器的扩容策略就不相同)。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低(需要移动数组部分元素)。

二、vector的常用接口

1.构造函数和赋值重载

1.1构造函数

vector类重载了多个构造函数,结合上图进行说明:

注:上图函数参数类型中,const allocator_type& alloc = allocator_type()是内存池的缺省参数(内存池简单来说就是一块提前申请好的内存,在使用空间时不用再去内存申请空间,可以节省时间);size_type就是我们常用的size_t,即unsigned int类型;value_type是模板类型,value_type()是匿名对象,调用默认构造函数初始化。

(1)无参构造函数:构造一个容量为空的vector容器。

(2)半缺省构造函数:构造一个size等于n的vector容器,容器的每个元素初始化为val。

(3)构造一个元素个数等于[first,last)区间内元素个数,元素值等于[first,last)区间内对应元素值的vector容器。

(4)拷贝构造函数:将vector容器x拷贝一份,构造一个新vector容器。

具体调用如下:

void VectorTest1()
{
	vector<int> v1;    //(1)
	vector<int> v2(10, 2);     //(2)
	string s = "hello world!";    
	vector<int> v3(s.begin(), s.end());    //(3)
	vector<int> v4(v2);   //(4)
}

1.2赋值重载

赋值重载在结果上和拷贝构造相同,都是得到一个与参数对象相同的容器;区别在于:拷贝构造在容器定义时调用,相当于容器初始化;赋值重载是对已经定义好了的容器进行拷贝赋值,相当于容器赋值。

具体调用如下:

void VectorTest2()
{
	vector<int> v1(10, 2);
	vector<int> v2;    //默认构造
	v2 = v1;    //赋值重载
}

2.析构函数

 构造函数是构造类对象,析构函数则是释放对象资源(堆区上的资源)。

构造函数不需要显式调用,程序结束时编译器会自动调用。

3.迭代器相关操作函数

3.1        begin()

begin()函数对于STL容器来说含义几乎相同,都是返回指向首元素的迭代器。

3.2        end()

end()函数对于STL容器来说含义几乎相同,都是返回指向最后一个元素的下一个位置的迭代器。

begin()和end()的具体调用如下:

void VectorTest3()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(3);
	v1.push_back(5);
	v1.push_back(7);
	v1.push_back(9);

	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//范围for遍历容器数组
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

注:由于vector支持迭代器,所以也可以用范围for来实现容器数组的遍历(只能顺序遍历)。

 3.3        rbegin()

3.4        rend()

rbegin()和rend()本质与begin()和end()相似,不同的是它们是逆序的;rbegin()返回的是指向最后一个元素的迭代器,rend()返回的是指向首元素的前一个位置的迭代器,且返回的迭代器是反向迭代器;反向迭代器的方向与迭代器相反,即进行++操作时是向容器的头部(元素下标更小的方向)移动。

rbegin()和rend()的具体调用如下:

void VectorTest4()
{
	vector<int> v1;
	v1.push_back(2);
	v1.push_back(4);
	v1.push_back(6);
	v1.push_back(8);
	v1.push_back(10);

	vector<int>::reverse_iterator it = v1.rbegin();
	while (it != v1.rend())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//范围for遍历容器数组
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

注:因为第一次打印是反向迭代器的运用,第二次打印使用的范围for(只能顺序打印),所以打印的顺序是完全相反的。

4.容器元素个数和容量操作函数

4.1        size()

 返回vector容器的元素个数。

4.2        resize()

修改vector容器的元素个数为n,当元素个数相较于原本的更大时,增加的元素全部初始化为value_type(),即调用默认构造来进行初始化(此场景下,内置类型也有默认构造)。

4.3        capacity()

 返回vector容器的容量大小。

4.4        reserve()

修改容器的容量大小为n,当n大于原本的容量大小时,进行异地扩容的操作。因为在调用push_back()和insert()函数进行元素插入时,容器的容量可能不够,此时编译器会进行异地扩容的操作,导致时间消耗大。因此,当你知道你待完成任务要使用的容器的最大容量时,可以调用reserve()函数提前扩容,以避免多次扩容带来的消耗,来提高性能。

4.5        empty()

 判断容器是否为空(即容器的元素个数即size是否为0)。

上述函数的具体调用:

void VectorTest5()
{
	vector<int> v;
	cout << v.empty() << endl;//判空
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	v.resize(10);
	v.reserve(15);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	//范围for遍历容器数组
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	v.push_back(2);
	v.push_back(4);
	v.push_back(6);
	v.push_back(8);
	v.push_back(10);
	//范围for遍历容器数组
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << v.capacity() << endl;
	cout << v.empty() << endl;//判空
}

5.获取容器元素的函数

5.1        operator[]()

重载运算符[],因为vector容器是动态数组,是一块连续的存储空间,因此可以通过下标来访问元素。函数返回下标的对应容器元素(第一个可读可写,第二个可读不可写)。

具体调用如下:

void VectorTest6()
{
	vector<int> v;
	v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	v.push_back(7);
	v.push_back(8);
	v.push_back(9);
	v.push_back(10);

	for (size_t i = 0;i < v.size();i++)
    //for (size_t i = 0;i <= v.size();i++)  //程序中断
	{
		cout << v[i] << " ";
		v[i] *= 2;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

 当以上程序中第一个for循环中的控制部分改为i <=v. size()时,因为越界访问,程序运行失败。

5.2        at()

函数功能与上述[]运算符重载相同,也是通过下标获得容器元素。

具体调用如下:

void VectorTest7()
{
	vector<int> v;
	v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	v.push_back(7);
	v.push_back(8);
	v.push_back(9);
	v.push_back(10);

	for (size_t i = 0;i < v.size();i++)
	//for (size_t i = 0;i <= v.size();i++) //程序中断
	{
		cout << v.at(i) << " ";
		v.at(i) *= 2;
	}
	cout << endl;
    //范围for遍历数组
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	
}

 

当以上程序中第一个for循环中的控制部分改为i <=v. size()时,因为越界访问,程序运行失败。

综上,operator[]和at()都能通过下标对容器数组元素进行访问和修改(const修饰的函数不能修改数组元素),唯一不同的就是对越界访问时的处理方式不同(最终结果都是程序中断,但是operator[]的更果断直接)。

6.修改容器的函数

6.1        insert()

在positon指向的位置插入新元素(一个或多个),插入时如果由于容量不够而导致编译器调用reserve()异地扩容的话,positon会因为指向非法空间而导致迭代器失效;如果没扩容,由于position的含义已经发生改变,仍然默认迭代器失效故,不论insert函数是否在内部调用了reserve()函数进行扩容,只要调用了insert()函数进行元素插入,就一定会牵涉到迭代器失效的问题,(底层实现使得我们使用失效的迭代器时会直接报错,如下是使用失效迭代器时的调试信息)。

唯一的解决办法就是改变迭代器的值,即通过赋值操作赋予它新的含义。(底层实现是直接将其变为空指针,使我们无法使用)。

注意:插入一个元素时,insert()函数返回类型是迭代器,其他两个重载的成员函数的返回类型是void。

测试代码:

void VectorTest8()
{
	vector<int> v;
	v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	vector<int>::iterator it = v.begin() + 2;
	v.insert(it, 2);//该insert()函数可以接受返回值,也可以不接收;用it接收可以解决迭代器失效的问题
	//it = v.insert(it, 2);
	//以下两行对迭代器it的使用都会导致程序异常退出,迭代器失效后不能使用
	//(*it)++;
	//it++;
	
	//v.insert(it,10,2);//也会导致迭代器失效,且该insert()函数的返回值是void,不能通过接受返回值来更新迭代器it的值从而解决迭代器失效的问题
	//可以把迭代器失效的问题和指针联想成相似的东西:
	//迭代器失效相当于指针被置为nullptr,此时不能使用它进行诸如解引用之类的操作
	//解决办法就是赋予其一个新的有效的值(更新迭代器)
	
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

}

使用失效迭代器代码的调试结果如下:

 

6.2        erase()

erase()函数的不同重载版本的返回值都是迭代器,返回值是指向被删除元素的下一个位置。由于可能删除了容器中最后的所有元素,返回的迭代器指向end()指向的下一个位置,容易导致越界访问,需要特别注意。其次,第一个erase()重载函数,函数参数是一个迭代器形参,删除元素后,该迭代器失效,无法使用(可以通过赋新值来解决失效问题,同insert()函数)。

代码测试:

void VectorTest9()
{
	vector<int> v;
	v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	vector<int>::iterator it = v.begin() + 2;
	it = v.erase(it);//返回指向被删除元素的下一个元素,如果没有用it接收返回值,it是失效的迭代器
	//it = v.erase(v.begin() + 3, v.end());//it返回end()的下一个位置,下面的*it会越界访问,导致程序运行中断
	(*it)++;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

综上,迭代器失效的类型有:

a.由于插入元素导致元素整体后移(尾插一视同仁),从而导致指向原空间的迭代器不再有效(插入元素前进行扩容操作的话,迭代器指向的空间是非法的,也会导致迭代器失效)

b.由于删除元素导致元素整体前移(尾删一视同仁),迭代器不再指向想指向的元素,从而导致迭代器失效

迭代器在内存重新分配时将失效(它所指向的元素在该操作后会发生变化)。

(注意:以上是vs上调用的库是这么规定的,linux使用的不是同一个库,与迭代器相关的规定有所不同)

6.3        push_back()

尾插,相当于insert(v.end())。

6.4        pop_back()

尾删,相当于erase(v.end()-1)。尾删不会改变容器的容量capacity。

功能测试代码:

void VectorTest10()
{
	vector<int> v;
	v.reserve(5);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	v.pop_back();
	v.pop_back();
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

 6.5        swap()

交换两个容器的内容(包括容器的元素和容器个数以及容量大小),vector的接口函数swap()函数的效率比直接调用std::swap()函数来交换两个vector容器要高得多(前者是交换指针的指向,后者是调用拷贝构造+赋值重载来实现,还需要产生中间容器)。

模拟实现vector::swap():

void swap(vector<T>& v)
{
    std::swap(_start, v._start);//_start指向容器数组的首元素
	std::swap(_finish, v._finish);//_finish指向容器的最后一个容器的下一个位置
 	std::swap(_end_of_storage, v._end_of_storage);//_end_of_storage指向容器装满时最后一个元 
                                                    素的下一个位置
}

 6.6        clear()

清空容器,容量大小不变。

功能测试:

void VectorTest11()
{
	vector<int> v1;
	vector<int> v2;

	cout << "交换前:" << endl;

	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(100);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	v2.push_back(2);
	v2.push_back(4);
	v2.push_back(6);
	v2.push_back(8);
	v2.push_back(10);
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	v1.swap( v2);//vector的接口函数swap的形参只有一个,std::swap()有两个形参
	cout << "交换后:" << endl;
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	v1.clear();
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;
	v2.clear();
	cout << v2.size() << endl;
	cout << v2.capacity() << endl;

}

 三、总结

vector是STL的八大容器之一,内部数据结构是动态数组,支持元素的随机访问(通过下标访问)。相比用C语言实现顺序表而言,这里很好的体现了类封装的优势,可以在类内定义函数,对外提供函数接口,方便用户调用常用功能函数来进行平时的代码设计和实现,提高效率。

在本篇博客中,在测试各个常用函数接口的函数功能时都是用的vector<int>,即容器元素为int类型的vector容器,得益于模板,vector容器可以实例化成各种类型的vector容器(容器的元素可以是内置类型和自定义类型,自定义类型自然也包括各大容器本身,在解题时有时候会巧妙的运用到,主要是考察我们对容器的理解和运用)。

vector容器的模拟实现可以加深我们对其的理解,大家感兴趣的可以自己尝试,我就不再多做说明。如果本文有陈述不正确的地方,欢迎各位斧正!

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值