C++知识点18——使用C++标准库(vector的增长与迭代器失效)

描述vector容器对象大小的方法有以下几个

1.size():返回vector容器对象中实际的元素个数

2.empty():当size()返回0时,该函数返回true,否则返回false,判断容器对象是否没有元素

如果想判断一个容器是否为空,直接使用empty函数,而不要写if(size()==0)

3.resize():resize函数用来扩大或者缩小容器中元素的实际个数,该函数是重载的

void resize (size_type n);//将vector中的元素的实际个数为重置为n,如果n小于原来vector的元素个数,那么将多出的元素删除

void resizefunc()
{
	vector<int> v1={1,2,3,4,5,6};
	v1.resize(10);//新添加4个int,值为0
	for (auto i:v1) {
		cout<<i<<endl;
	}
}

将第4行改为v1.resize(3),结果如下:

 

void resize (size_type n, const value_type& val);
/*将vector中的元素的实际个数为重置为n,
并初始化n个元素的值为val,如果n小于原来vector的元素个数,
那么将多出的元素删除,val对容器对象无影响*/

将第4行改为v1.resize(10,6),结果如下:

 

除了上述三个函数外,还有两个函数也描述vector的大小

4.capacity():vector容器在不重新分配内存的情况下,可以存储最多多少个元素

size_type capacity() const;

 

5.reserver(n):重新分配内存并给vector容器对象分配至少能容纳n个元素的内存

void reserve (size_type n);

 

6.shrink_to_fit():将capacity减少为与size函数相同大小

void shrink_to_fit();

示例

void vectordatafunc()
{
	vector<int> v1={1,2,3,4,5,6};
	v1.reserve(10);
	cout<<v1.capacity()<<','<<v1.size()<<endl;
	v1.shrink_to_fit();
	cout<<v1.capacity()<<','<<v1.size()<<endl;
}

v1里面实际的元素个数是6个,但是预先分配可容纳10个int的内存,所以capacity的返回值至少和reserve一致,最后,将预先分配的内存退回给系统,最后输出的capacity和size大小相同

 

vector如何增长

vector之所以有这么多和大小相关的方法,是因为vector容器的内存空间分配策略导致的

因为vector中的元素的连续存储的,所以,在容器无法容纳新元素时,添加新元素将导致将已有元素移动到新的内存中,并将旧内存释放,如果上述情况频繁发生,那么vector的性能会很慢

所以,为了提高vector的性能,vector对象在重新分配内存时,会分配比实际元素所占新空间更多的空间,通常是当前元素数目的2倍或者1.5倍(取决于实现),预留的这些空间用来保存更多的元素,就不用频繁分配内存了

所以,capacity表示vector在不重新分配内存的情况下可以最多容纳多少元素,而size只表示实际的元素数目,reverse表示预先分配的可容纳元素的内存空间

示例

void vectordatafunc()
{
	vector<int> v1={1,2,3,4,5,6};
	v1.reserve(10);
	cout<<v1.capacity()<<','<<v1.size()<<endl;
	v1.shrink_to_fit();
	cout<<v1.capacity()<<','<<v1.size()<<endl;

	v1.push_back(7);
	cout<<v1.capacity()<<','<<v1.size()<<endl;
}

当向容器中添加一个7时,v1的内存被重新分配,从输出结果看,Ubuntu18.04下vector重新分配内存时,预留空间是原来size的2倍

 

vector迭代器失效

因为vector重新分配内存时,会将把元素移入新的内存中,并将旧内存释放,所以这就会导致指向原来内存的指针,引用,迭代器失效

所以,在向vector中添加元素时,不要保存迭代器,而是要实时获取迭代器(在添加操作完成后,调用begin和end重新获取迭代器),因为在添加元素结束后很有可能迭代器就会失效

示例

#define _GLIBCXX_DEBUG//添加宏,使g++处于debug模式下,release模式对s解引用值为0
void iteratorfailed()
{
	vector<int> v1={1,2,3,4,5,6};
	v1.reserve(10);
	v1.shrink_to_fit();

	vector<int>::iterator s=v1.begin();
	cout<<*s<<endl;
	v1.push_back(7);
	cout<<v1.capacity()<<','<<v1.size()<<endl;
	cout<<*s<<endl;
}

在添加元素之后,迭代器失效,已经不指向元素1的内存,此时的迭代器变成了singular iterator,所以,不能被解引用

将最后一行改为cout<<*(v1.begin())<<endl; 输出OK

 

此外,即使vector不重新分配内存,insert操作也有可能使迭代器失效

示例1

#define _GLIBCXX_DEBUG
void iteratorfailed2()
{
	vector<int> v1={1,2,3,4,5,6};
	v1.reserve(10);
	vector<int>::iterator it = v1.begin();
	vector<int>::iterator fifth=v1.begin()+4;
	cout<<*fifth<<endl;
	v1.insert(v1.begin(), 66);
	cout<<__func__<<endl;
	cout<<*fifth<<endl;
}

可见,在debug模式下,如果插入元素前保存了插入位置之后的迭代器,那么,在插入操作结束后,该迭代器会失效,所以,在进行插入操作时,不要保存插入位置之后的迭代器

 

删除操作也是类似的,如果删除元素之后,那么原先在元素后边的迭代器就会失效

 

示例2

void erasefailed()
{
	vector<int> v1(10,3);
	vector<int>::iterator it8=v1.begin()+8;
	vector<int>::iterator it=v1.erase(v1.begin());
	cout<<*it8<<endl;
}

 

保存删除或插入位置后的迭代器会失效的原因就是vector容器中的数据保存在堆内存上,而堆内存是不连续的,所以会解引用一个singular 迭代器,导致迭代器失效

所以,不要保存删除或插入位置之后的迭代器,而是通过insert或erase的返回值更新迭代器

 

 

示例3

void iteratorfailed2()
{
	vector<int> v1={1,2,3,4,5,6};
	v1.reserve(10);
	vector<int>::iterator it = v1.begin();
	v1.insert(v1.begin(), *it);
	cout<<__func__<<endl;
	cout<<*it<<endl;
}

原因是在insert之后没有及时更新迭代器,而在insert之后,it失效,对it解引用,程序挂掉

解决办法就是更新it,使it继续有效(it=v1.insert(v1.begin(), *it);)

 

除了插入操作会引起迭代器失效,删除操作也会导致迭代器失效

示例1

void iteratorfailed3()
{
	vector<int> v1={1,2,3,4,5,6};
	vector<int>::iterator it = v1.begin();

    while (it != v1.end())
    {
        if (*it % 2 == 0)
        {
        	cout<<__func__<<endl;
            v1.erase(it);  
        }
        cout << *it << " ";
        ++it;
    }
}

上述代码在输出1之后,程序挂掉,原因就是在erase后,it本身已经失效,但是又对it进行解引用,所以程序挂掉,输出提示,访问了一个singular迭代器

 

示例2

void iteratorfailed3()
{
	vector<int> v1={1,2,3,4,5,6};
	vector<int>::iterator it = v1.begin();

    while (it != v1.end())
    {
        if (*it % 2 == 0)
        {
        	cout<<__func__<<endl;
            it=v1.erase(it);  
        }
        cout << *it << " ";
        ++it;
    }
}

输出提示访问一个尾后迭代器,该程序遇到奇数直接输出,然后把偶数删除,当遍历到元素5时,输出元素5并将迭代器++,所以迭代器指向的元素6,然后执行删除操作,删除操作结束后,但是问题出现了,v1.erase(it)的返回值使it指向了end(),之后在对end()解引用时,程序直接挂掉

 

解决办法:

1:既然是因为访问了end(),所以就可以在解引用之前加一个是否为end()的判断

void iteratorfailed3()
{
	vector<int> v1={1,2,3,4,5,6};
	vector<int>::iterator it = v1.begin();

    while (it != v1.end())
    {
        if (*it % 2 == 0)
        {
        	cout<<__func__<<endl;
            it=v1.erase(it); 
        }
        if (it !=v1.end()) {
        	cout << *it << " ";
        	++it;
        }
    }
    for (auto i:v1) {
		cout<<i<<endl;
	}
}

 

2.为了不解引用尾后迭代器,可以在删除6后直接退出while循环,因为删除6之后,it与end()相等,所以直接加上一个else分支

void iteratorfailed3()
{
	vector<int> v1={1,2,3,4,5,6};
	vector<int>::iterator it = v1.begin();

    while (it != v1.end())
    {
        if (*it % 2 == 0)
        {
        	cout<<__func__<<endl;
            it=v1.erase(it); 
        }
        else {
        	cout << *it << " ";
        	++it;
        }
    }
    for (auto i:v1) {
		cout<<i<<endl;
	}
}

输出结果同解决办法1

 

最后,assign也会导致迭代器失效,因为assign后,会将原来的内容擦除并释放内存,所以,如果在assign后解引用了assign之前保存的迭代器,也会导致程序出错

 

示例

void assignfailed()
{
	vector<int> v1(10,3);
	vector<int> v2(10,4);

	vector<int>::iterator s=v1.begin()+1;
	vector<int>::iterator e=v1.end();
	v1.assign(s,e);
	for (auto i:v1) {
		cout<<i<<endl;
	}
	cout<<*s<<endl;	
}

解决办法:就是注意在对vector进行assign操作时,传递给assign的迭代器不能指向调用assign的容器

 

 

所以,总结起来,vector迭代器失效的原因有以下几种:

1.vector重新分配内存导致迭代器失效(针对这一点,就是在添加元素后,要用begin和end更新获取迭代器,而不要保存begin和end)

2.保存了insert或erase位置之后的迭代器,并在insert或erase之后对该迭代器进行解引用(针对这一点,就是不要保存insert或erase位置之后的迭代器)

3.在erase或insert后,解引用了已经失效的迭代器(针对这一点,要根据erase或insert后,对迭代器进行及时更新)

4.在erase时,由于返回值时删除位置之后的迭代器,因此有可能访问到了尾后迭代器(end())(针对这一点,写代码是注意添加处理逻辑,不要解引用尾后迭代器)

5.对vector进行assign操作时,传递给assign的迭代器不能指向调用assign的容器

 

 

最后举一个简单例子:删除所有奇数,复制所有偶数

void example()
{
	vector<int> v1={1,2,3,4,5,6};
	v1.reserve(100);//reserve大一些,保证vector不会重新分配内存

	vector<int>::iterator it=v1.begin();
	while(it!=v1.end()) {
		if (*it % 2) {
			it=v1.erase(it);//删除奇数并更新迭代器it
		}
		else {
			it=v1.insert(it, *it);//添加结束后,更新迭代器指向添加的位置
			it+=2;//因为复制了偶数,所以要跳两步访问下一个奇数元素
		}
	}

	for (auto i:v1) {
		cout<<i<<endl;
	}
}

 

参考

《C++ Primer》

https://blog.csdn.net/sdoyuxuan/article/details/81673236

https://stackoverflow.com/questions/5441893/what-is-singular-and-non-singular-values-in-the-context-of-stl-iterators

https://stackoverflow.com/questions/15252002/what-is-the-past-the-end-iterator-in-stl-c

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值