C++STL——vector类

目录

一.vector类的初步介绍:

二.vector类的成员函数介绍:

        2.1基础成员函数使用:

                2.1.1reserve():扩容函数 

                代码测验:

                2.1.2resize()调整类对象的数据存储函数

                代码实验: 

                 结果:

                 编辑​

                2.1.3缩容函数:shrink_to_fit

        2.2迭代器

        代码实验:

        运行结果: 

         2.3operator运算符重载、at()、back()、front()函数

        代码实验:

        at();函数的作用和operator[ ]作用一样,都是可以获取类对象数组中的某个元素。

        代码实验:

         运行结果:

        对于front()和back()函数来说,就是获取类对象数组中的头元素和尾元素。

        2.4assign()函数:

        用法2: 

        用法1:使用迭代器做形参:

运行结果: 

        2.5增添数据

        运行结果:

        2.6删除元素 

        代码实验:

        运行结果:

        2.7查找 

        代码实验:

        运行结果: 

        


       之前讲了关于String字符序列类的底层理解、成员函数及模拟实现,String类中只能用于存放字符或字符串,底层是通过使用创建堆区空间的数组实现的;而今天要讲的是STL中相当有含金量的一个类——vector,vector的英文是向量:

一.vector类的初步介绍:

        1. vector是表示可变大小数组的序列容器。

        2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。

        3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是:分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大 小。

        4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存 储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是 对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

        5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

        6. 与其它动态序列容器相比(deque, list and ), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list统一的迭代器和引用更好。

二.vector类的成员函数介绍:

        2.1基础成员函数使用:

#include<iostream>
#include<vector>
using namespace std;

void Test1() {
	vector<int> v;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	cout << v.empty() << endl;		//empty为空则显示1,不为空则显示0
    cout<<
	v.push_back(10);
	v.push_back(185.95);	//舍弃小数,取整,不存在过.5进一
	v.push_back('c');	//v是int类型,所以转换的是ASCII码值
	v.push_back(7999);
	v.push_back(0);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	cout << v.empty() << endl;
    cout << "遍历所得:";
	for (auto& ch : v) {
		cout << ch << " ";
	}
	cout << endl;
	cout << v.max_size() << endl;		
//获取的是可以存储的总个数,10亿多,最大值是42亿多字节,因为是int类型,占4个字节,所以总字节数/int类型
	cout << "----------------------" << endl;
}

int main(){
    Test1();
    return 0;
}

 结果:

        2.1.1reserve():扩容函数 

        reserve的形参只有一个size类型的数值n,该数值可以改变类对象的容量,只会使得容量增加(扩容),不会使容量减少。

        1.如果 n 大于当前向量容量,则该函数会导致容器重新分配其存储,将其容量增加到 n(或更大)。
        2.在所有其他情况下,函数调用不会导致重新分配,容量不受影响。

        此函数对类对象的size大小没有影响,并且不能更改其元素。

代码测验: 

        还是根据上面所创建的对象v的例子:

cout << "使用reserve前: " << endl;

	cout << "v.size(): " << v.size() << endl;
	cout << "v.capacity(): " << v.capacity() << endl;
	cout << "使用reserve(5)后: " << endl;	
	v.reserve(5);					//——只改变容量
	cout << "v.size(): "<<v.size() << endl;
	cout << "v.capacity(): "<<v.capacity() << endl;
	cout << "使用reserve(20)后: " << endl;
	v.reserve(20);					//——只改变容量
	cout << "v.size(): " << v.size() << endl;
	cout << "v.capacity(): " << v.capacity() << endl;
	cout << "使用reserve(2)后: " << endl;
	v.reserve(2);					//——只改变容量
	cout << "v.size(): " << v.size() << endl;
	cout << "v.capacity(): " << v.capacity() << endl;
	cout << "----------" << endl;

        在vector类中,每当数据存储数量达到容量临界值后,再增添数据就会触发扩容机制,一般情况下扩容都是扩大到原来的1.5倍——2倍左右,这是计算机科学家们经过无数次的测试得出来的结果,扩大1.5倍——2倍是很合适的,可以避免扩少了需要频繁扩容的问题,也避免了扩多了导致空间浪费的问题!

如上是VS编译器中的扩容机制,按照大约1.5倍的方式进行的。 


    

        2.1.2resize()调整类对象的数据存储函数

函数作用:调整容器的大小,使其包含 n 个元素。
        1.如果 n 小于当前容器大小,则内容将减少到其前 n 个元素,删除超出的元素(并销毁它们)。
        2.如果 n 大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到 n 的大小。如果指定了 val,则新元素将初始化为 val 的副本,否则,它们将被值初始化。
        3.如果 n 也大于当前容器容量,则会自动重新分配分配的存储空间。 

        value_type()是第二参数的缺省值,它表示类模板泛型T,会调用T模板的构造函数。在当初学习该函数的时候,我会想为什么不把它设置为0呢?

        经过长时间的学习,我找到了一个重要的原因:对于内置类型char/int/double/size_t等,可以设置为0,但是若是修改自定义类的resize呢?也能设置为0吗?自定义的类是未知的,不确定的,所以使用匿名对象更好,对于内置类型的resize,匿名对象也能够调用内置类型的构造函数——默认为0

​ 

代码实验: 
cout << "resize: " << endl;
	cout << "遍历所得:";
	for (auto& ch : v) {
		cout << ch << " ";
	}
	cout << endl;
	cout << "使用resize前: " << endl;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	//使用resize,让v的顺序表只留下3个值
	v.resize(3);
	cout << "v.resize(3):缩减_size,容量不会变" << endl;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	cout << "遍历所得:";
	for (auto& ch : v) {
		cout << ch << " ";
	}
	cout << endl;
	cout << "----------" << endl;

	cout << "resize(6)表示扩容,且剩余的几个数按a的ASCII码值填写:" << endl;
	v.resize(6, 'a');
	cout << "遍历所得:";
	for (auto& ch : v) {
		cout << ch << " ";
	}
	cout << endl;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	cout << "----------" << endl;

	//resize代表扩容15个数值,_size=15,‘
	v.resize(15);
	for (auto& ch : v) {
		cout << ch << " ";
	}
	cout << endl;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
 结果:

 resize(3); 类对象的数据由上知为5,实参设为3会删减类对象的存储数据量,但不会影响容量;

​ 

        resize(6,'a'); 类对象的数据量由3-->6,多出来的3个数据,会根据写入的第二参数决定,第二参数为字符'a',经过整型提升从char-->int,‘a'的ASCII码值是97

       resize(15); 由于没有设置第二参数,函数默认采用缺省值:value_type(),默认是0。


        2.1.3缩容函数:shrink_to_fit

        一般情况下,没人会使用shrink_to_fit()函数,除非你将类对象扩容扩的很大,并且也没有用上这么多的容量,可以使用该函数缩小容量。

        缩容与扩容原理相同,都是重新创建一块新的堆区空间,将数据拷贝到新空间,然后释放原来的旧空间即可。缩容与扩容都是以时间换取空间,是有代价的。

        这里就不演示代码了。


        2.2迭代器

        迭代器是每个STL容器都有的核心,有了迭代器,我们便可以轻松对其遍历,方便查看内容、做出修改。

        如上是迭代器中的接口函数,我们可以把它们看作是指针,例如:begin指向vector类对象内容的开头,end指向vector类对象内容的结尾。在迭代器中,共有三种类型的begin和end,即正向迭代、反向迭代、const反向迭代。这三种类型的接口都受vector类域的限制,所以定义这些begin、end时需要加上类域才行!

   使用迭代器,需要明白其格式:

std::vector:: iterator 迭代器对象名(自己取名字)=vector类对象.begin(); 

代码实验:
#include<iostream>
#include<vector>
using namespace std;

void Test1() {
	vector <int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);

	//迭代器
	cout << "正向迭代器的遍历:";
	vector<int>::iterator vit = v.begin();
	while (vit != v.end()) {
		cout << *vit << " ";
		++vit;
	}
	cout << endl;


	cout << "反向迭代器的遍历:";
	vector<int>::reverse_iterator rvit = v.rbegin();
	while (rvit != v.rend()) {
		cout << ++(*rvit) << " ";
		++rvit;
	}
	cout << endl;


	cout << "const正向迭代器的遍历:";
	vector<int>::const_iterator cvit = v.cbegin();
	while (cvit != v.cend()) {
		//cout << --(*cvit) << " ";	报错
		cout << *(cvit) << " ";
		++cvit;
	}
	cout << endl;

	cout << "const反向迭代器的遍历:";
	vector<int>::const_reverse_iterator crvit = v.crbegin();
	while (crvit != v.crend()) {
		//cout << --(*crvit) << " ";	报错
		cout << *(crvit) << " ";
		++crvit;
	}
	cout << endl;

}

int main(){
    Test1();
    return 0;
}
运行结果: 

        通过结果可知:包含const的迭代器不能修改类对象的数据、reverse的迭代器相比较于正向迭代器来说,它的begin/end和正向的begin/end是位置相反的两个指针。


         2.3operator运算符重载、at()、back()、front()函数

代码实验:
void Test2() {
	vector <int> v;
	for (int i = 0; i < 10; ++i) {
		v.push_back(i * 10);
	}
	
	//operator[]访问
	cout << "operator[]遍历:";
	for (int i = 0; i < v.size(); ++i) {
		//读操作
		cout << v[i] << " ";
	}
	cout << endl;

}

[ ],这对方括号可以方便我们对其遍历,正好vector的底层是数组的原因,使用[ ]重载很方便!

at();函数的作用和operator[ ]作用一样,都是可以获取类对象数组中的某个元素。

代码实验:
//at与operator[]几乎一样的作用
	cout << "at函数遍历:";
	for (int i = 0; i < v.size(); ++i) {
		//读操作
		cout << v.at(i) << " ";
	}
	cout << endl;

	//但at函数操作不当越界时会报异常
	vector<double> v2;

	for (int i = 0; i < 10; ++i) {
		cout << v2.at(i) << " ";
	}
	cout << endl;
	cout << "------------------------------------" << endl;
 运行结果:

at和operator[ ]的区别在于:

        但at函数操作不当越界时会报异常;——异常导致系统崩溃挂掉

        operator[]越界会报asserion fail 错误;——错误会导致当前程序被迫终止运行。

对于front()和back()函数来说,就是获取类对象数组中的头元素和尾元素。
#include<vector>
#include<iostream>
using namespce std;

void Test3(){
vector<int> v3;
	vector <int> v;
	for (int i = 0; i < 10; ++i) {
		v.push_back(i * 10);
		cout << v[i] << " ";
	}
	cout << endl;

	cout << v.front() << endl;
	cout << v.back() << endl;
}

int main(){
    Test3();
    return 0;
}


        2.4assign()函数:

assign函数作用:分配新内容,替换其当前内容,并相应地修改其大小。

assign有两种用法,一种是使用迭代器的方式;一种是输入n和val

        在第二种方法中:assgin会将对象v原有的值全都删掉,只留下n(第一参数)个元素,且重新赋值为val(第二参数)

用法2: 
    int main(){
	vector<int> v3;
    int i=0;
    while(i<10){
        v3.push_back(i);
    }	
    //此时v3的size变为10

    //下面代码表示:将v3的_size变为5,且全都重新赋值为3,即 3 3 3 3 3 
	v3.assign(5,3);	
	for (auto& r : v3) {
		cout << r << " ";
	}
	cout << endl;
	cout << "-------------------------" << endl;
    return 0;
    }

用法1:使用迭代器做形参:
    string s1 = "hello world";
	v3.assign(s1.begin(),s1.end());		
    //该代码表示:v3[0]='h'的ASCII码值,v3[1]='e'的ASCII码值,v3[2]='l'的ASCII码值......

	for (auto& r : v3) {
		cout << r << " ";
	}	//此时v3共有11个元素,每个元素值分别为s1各个字符的ASCII码值
	cout << endl;
	cout << v3.size() << endl;
	cout << "-------------------------" << endl;


	v3.assign(++s1.begin(), --s1.end());
    //该代码表示:去掉了string s1的头元素和尾元素后将各个字符的ASCII码值赋值给v3
	for (auto& r : v3) {
		cout << r << " ";
	}	//此时v3共有9个元素,每个元素值分别为s1除去头尾后各个字符的ASCII码值
	cout << endl;
	cout << v3.size() << endl;
运行结果: 


        2.5增添数据

  方式:push_back()、insert()

注:push_back为尾插函数,所以只需要输入想要添加的值即可;

     而对于inert来说,可以头插、中间插、尾插,但是插入的方式仅限于用迭代器的方式,没有指定下标pos位置的插入了。 

void Test5() {
	vector<int> v5;
	v5.push_back(9);
	v5.push_back(8);
	v5.push_back(7);

	v5.insert(v5.begin(), 100);//表示在v5容器的首元素位置插入值为100的元素——简称头插
	for (auto& ch : v5) {
		cout << ch << " ";
	}
	cout << endl;

	v5.insert(v5.end(), 666);	//表示在v5容器的末尾元素插入值为666的元素——简称尾插

	for (auto& ch : v5) {
		cout << ch << " ";
	}
	cout << endl;

	//若想在中间某个位置插入则,
	v5.insert(v5.begin()+2, 145);	//在vector数组的第3个位置插入
	v5.insert(v5.end() -3, 467);	//在vector数组的倒数第四个位置插入
	for (auto& ch : v5) {
		cout << ch << " ";
	}
	cout << endl;
}

int main(){
    Test5();
    return 0;
}
运行结果:


        2.6删除元素 

方式也有两种:pop_back()、erase()

 注:pop_back为尾删函数;

        而对于erase来说,可以头删、中间删、尾删,并且删除的方式也仅限于用迭代器的方式,没有指定下标pos位置的删除。 

 代码实验:
void Test7() {
	vector<int> v7;
	v7.push_back(100);
	v7.push_back(200);
	v7.push_back(300);
	v7.push_back(400);
	v7.push_back(500);
	v7.push_back(600);
	cout << "删除前:";
	for (auto& ch : v7) {
		cout << ch << " ";
	}
	cout << endl;

	//删除

	//方法1:pop删
	cout << "pop删:";
	v7.pop_back();	//尾删
	//遍历
	for (auto& ch : v7) {
		cout << ch << " ";
	}
	cout << endl;

	//方法2:erase指定某一位置删除
	cout << "erase头删:";
	v7.erase(v7.begin());			//v7.begin()头删
	for (auto& ch : v7) {
		cout << ch << " ";
	}
	cout << endl;

	v7.erase(v7.end()-1);
	cout << "erase尾删:";
	for (auto& ch : v7) {
		cout << ch << " ";
	}
	cout << endl;

	cout << "erase中间删:";
	v7.erase(v7.begin()+1);			//v7.begin()头删
	for (auto& ch : v7) {
		cout << ch << " ";
	}
	cout << endl;
}

int main(){
    Test7();
    return 0;
}
运行结果:


        2.7查找 

其实,查找函数find()并不是vector的成员函数,它使用的std库中的find函数

 

        对于库中的find来说,其形参也是按照迭代器指针去指定元素查找

代码实验:
void Test6() {
	vector<int> v6;
	v6.push_back(100);
	v6.push_back(200);
	v6.push_back(300);
	v6.push_back(400);
	v6.push_back(500);
	v6.push_back(600);


	//find不是成员函数,所以find的参数是以迭代器为准,300是vector数组的元素值,不可填下标位置
	vector<int> ::iterator it1 = find(v6.begin(),v6.end(),300);
	if(it1 != v6.end()) {
		v6.insert(it1, 90);	//在it1位置处插入数值90
	}

	//遍历
	for (auto& ch : v6) {
		cout << ch << " ";
	}
	cout << endl;
}
运行结果: 

猜你喜欢

转载自blog.csdn.net/weixin_69283129/article/details/131899708