C++11基于范围的for循环&vector容器扩容详解&迭代器失效

  • Post author:
  • Post category:其他



目录


C++11基于范围for循环


vector容器扩容详解


迭代器失效


总结


C++11基于范围for循环


对于一个有范围的集合



来说





在程序代码中指定


循环的范围


有时候


是多余的,还


可能


犯错误。


为此C++11中引入了基于范围的for循环。


语法:


语法:

for (迭代的变量 : 迭代的范围)

{

// 循环体。

}

对于一个vector<int>容器,我们一般会这样遍历:

但是基于范围的for循环是这样的:


它的原理就是从v01中取到一个内容赋值给变量val;

然后在循环体中操作val;

记住他的原理,很重要!


迭代的范围不仅仅可以使用容器,也可以使用数组或者初始化列表

,如下:

还有很重要的一点:



如果容器中的元素是结构体和类,迭代器变量应该申明为引用,加c


onst


约束表示只读。


我们演示一下类,代码如下:

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

class MyClass
{
public:
	string m_name;

	MyClass() { cout << "默认构造函数AA()。\n"; }

	MyClass(const string& name) : m_name(name) { cout << "构造函数,name=" << m_name << "。\n"; }

	MyClass(const MyClass& a) : m_name(a.m_name) { cout << "拷贝构造函数,name=" << m_name << "。\n"; }

	MyClass& operator=(const MyClass& a) { m_name = a.m_name;  cout << "赋值函数,name=" << m_name << "。\n";  return *this; }

	~MyClass() { cout << "析构函数,name=" << m_name<<"。\n"; }
};

int main()
{

    return 0;
}

首先我们需要先明白vector容器扩充的原理:

main函数代码如下:

int main()
{
    vector<MyClass> v;
	cout << "v.capacity()=" << v.capacity() << "----------------------------------\n";
	v.emplace_back("www");
	cout << "v.capacity()=" << v.capacity() << "----------------------------------\n";
	v.emplace_back("eee");
	cout << "v.capacity()=" << v.capacity() << "----------------------------------\n";
	v.emplace_back("rrr");
	cout << "v.capacity()=" << v.capacity() << "----------------------------------\n";

    return 0;
}

capacity指的是vector已使用的大小;

运行:

接下来我们一步一步分析;

vector容器扩容详解

首先我们使用了vector的无参构造,它已使用容量大小是0;

然后emplace_back()函数构造了一个m_name为”www”的对象,调用了一次MyClass的构造函数;

然后显示容量此时为1;

重点:

然后再次构造一个m_name为eee的对象,输出了三行日志,为什么呢?

首先vector容器原来的大小capacity()=1,1是无法满足两个元素的(“www”,”eee”),所以需要扩充,但是


vector容器的扩充分为四个步骤


1、申请一个新的地址,这个地址可以存放下”www”,和”eee”的对象,这一步没有日志;

2、构造”eee”存放到新的地址,显示构造函数,name=”eee”。

3、将原来地址中的”www”拷贝过来,显示拷贝构造,name=”www”。

4、释放原来的内存,显示析构函数,name=”www”。

知道了vector扩充的步骤,下面的日志大家可以自己分析了;

讲vector容器扩充的步骤,主要是为了分析日志,让大家不要对接下来的输出的日志感觉迷茫;

现在我们已知,该容器有三个元素,那么我们会怎么访问这三个元素呢?

我们首先想到的肯定是这样写:

for (auto a : v)
	cout << a.m_name << " ";
cout << endl;

auto可以自动推导出v的类型;

运行一下:

红框上面的我们已经介绍了,不会再迷茫了;

红框中的内容又输出了一堆,为什么呢?我们分析一下:

还记不记得上面讲的基于范围for循环原理?

从迭代的范围中取到一个内容赋值给迭代的变量


在这里就是从v中取到一个内容赋值给a;

v中都是什么内容呢?”www”、”eee”、”rrr”的对象,一共三个对象;

那么第一次循环,我们将”www”的对象赋值给a,会调用拷贝构造,

显示拷贝构造函数,name=www。

然后我们循环体中

显示www和一个空格(也就是第二行日志的开头)

然后因为我们是将”www”的对象拷贝给a,那么a使用完这个对象肯定要析构啊,所以调用了析构函数,

显示析构函数,name=www。

至此前两行的日志已经解释完毕;下面的原理都一样,不解释了;

所以下面这张图我们应该明白了:

for循环输出一个内容,就要拷贝一次,析构一次,这肯定不是我们想要的结果对吧?

那么怎么办呢?


因为我们a是拷贝过来的副本,所以a才有必要析构它,但是如果a使用的就是vector容器中的对象,不是拷贝过来的副本,就没有必要析构它,因为他不归a所管;

怎么让a是容器中的对象而不是拷贝的副本呢?

引用;

如下:

没有拷贝构造,因为我们使用的是vector容器中的对象本身,不会发生拷贝,既然没拷贝就不会有拷贝构造的日志,a也不会析构它;

这样还不是最完美的,为什么呢?


因为我们的a现在拿到的不是拷贝副本,而是vector容器中真正的数据,那么如果a操作了vector容器中的数据还是比较危险的,所以a应该加上const,不可修改vector容器中的数据;

如下:

所以这就是为什么:


如果容器中的元素是结构体和类,迭代器变量应该申明为引用,加c


onst


约束表示只读。



迭代器失效




最后基于范围的for循环还要注意迭代器失效的问题


,在vector容器中,使用

resize()、reserve()、assign()、push_back()


、p


op_back()





insert()、erase()等


函数会


引起


v


ector


容器的动态数组


发生变化


,可能导致


vector迭代器失效



演示一下,迭代器失效,代码如下:

上面是正常情况下的运行结果,下面取消注释:

只有1,正常显示了,这是为什么呢?就是迭代器失效了


如果不知道为什么迭代器失效,说明上面的vector容器扩充的四个步骤没有学好

我们分析一下:

首先第一次循环,先输出val和空格;此时val是第一个元素,所以输出了1;

然后v.push_back(2);这一行代码,由于原来的vector容器的数量不能满足再填充一个2进去,所以这行代码会触发vector容器的扩充;

扩充四个步骤:1、申请

新地址

;2、将新的内容填充到

新地址

;3、将原地址中的内容拷贝到新地址;4、


释放原地址



当我们的v.push_back(2)运行完了之后,vector容器元素的地址已经发生了改变,但是我们的val还是在原来的地址中取内容,因为这是已经编译好的地址,同时原来的地址已经被释放了,所以输出了四个垃圾值;

最后注意:

如果我们想验证容器扩充之后,地址发生了改变,那么假设我们的容器是vector<int> aa({1,2,3,4,5});那么我们想要输出aa元素的首地址,千万不能这样写:&aa;因为这样获得的是vector容器的地址,在栈区;

我们想要输出aa元素的首地址必须使用STL提供的函数,aa.data()这样就能获取aa元素的首地址;


总结


1、迭代的范围可以是数组名、容器名、初始化列表或者可迭代的对象(支持b


egin()


、e


nd()


、+


+


、=


=


)。


2、数组名传入函数后,已退化成指针,不能作为容器名。


3、注意迭代器失效的问题。


4、输出vector元素的地址,必须使用vector中的data()函数,不能取地址vector对象;


本篇文章内容至此结束!感谢观看!



版权声明:本文为qq_52572621原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。