C++STL之list容器

概述:
list是一个双向链表,如果只在头尾插入删除,效率是非常高的,并且内存地址不连续可以动态改变内存。而vector是一个数组,在内存中地址连续,虽然也可以叫动态开辟内存,但是每次都是先创建新的内存,将上一次的内容拷贝到新内存,再释放原内存,这样的拷贝对于内存改变大的效率低下。
所以list主要用于头尾插入删除。vertor用于随机删除更加适当。

1 list的遍历

非常简单,不详解。这里注意一下,当list存string内容时,可以使用string的data成员函数去遍历取值。

void test01() {
	list<int> l;
	for (list<int>::iterator it = l.begin(); it != l.end(); it++){
		cout << *it << " ";
	}
	cout << endl;
}

2 list的插入

2.1 list的插入简单使用

void test02() {
	list<int> l;
	//头插
	l.push_front(1);
	l.push_front(2);
	//尾插
	l.push_back(7);
	l.push_back(8);

	for (auto it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

结果:
在这里插入图片描述

2.2 测试list的插入是否会造成迭代器失效
测试代码:

//测试链表的插入是否对迭代器失效
void test03() {
	list<int> l;
	//头插
	l.push_front(1);
	l.push_front(2);
	//尾插
	l.push_back(7);
	l.push_back(8);

	for (auto it = l.begin(); it != l.end(); it++) {
		if (*it == 1) {
			//l.push_front(3);
			//l.push_back(9);
			l.insert(it, 0);
		}
	}
	cout << endl;
}

2.2.1 先测试push_front,push_back
1)首先,先记录首次迭代器的首地址。
在这里插入图片描述

2)我们随便让它再某个位置插入后,再观察迭代器的首地址是否改变(即再使用该迭代器是否会报错),即可验证是否失效。
在这里插入图片描述
可以看到,即使插入后,迭代器的首地址是不会改变的,继续使用迭代器也不会报错。并且这里我们可以得出一个结论:C++迭代器遍历容器是使用it+n的方法去遍历,it始终指向首地址。而非it的指向不断改变。

2.2.2 测试list的insert
1)迭代器首地址。
在这里插入图片描述
2)调用insert后,可以看到,迭代器并未失效,继续使用迭代器也是正常的。
只不过需要注意的是list的insert是插入到迭代器的前面。使用这里的结果是2,0,1,7,8。
在这里插入图片描述

2.3 总结list的插入是否会造成迭代器失效

  • 1)list的插入不会对迭代器失效,原因在于list的首地址是固定的,即使内存不够也只是再链表中增加关系上的连接,并不会影响到首地址。而vector内存不足会导致整片内存更改,故首地址会变。
  • 2)所以list的插入不需要更新迭代器,vector需要。虽然list不需要更新迭代器,但是多线程下该加锁加锁。

3 list的删除

3.1 list的删除
list的删除主要用到pop_front和pop_back,erase极少用到,因为list的这种用法被更高效的vector代替(主要是遍历时使用erase嘛)。

//链表的删除
void test04() {
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(7);
	l.push_back(8);

	l.pop_front();
	l.pop_back();
	l.erase(l.begin());

	for (auto it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

结果:
在这里插入图片描述

3.2 测试list的删除是否会造成迭代器失效
测试代码。

//测试list的删除是否会造成迭代器失效
void test05() {
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(7);
	l.push_back(8);

	for (auto it = l.begin(); it != l.end(); it++) {
		if (*it == 2) {
			//l.pop_front();
			//l.pop_back();
			l.erase(it);
		}
	}
	cout << endl;
}

3.2.1 先测试pop_front
1)同样,先记录迭代器首地址。
在这里插入图片描述
2)运行完pop_front后,并且删除的是链表的首个元素,但是结果可以看到,迭代器首地址仍然不变,继续使用迭代器也正常,这说明pop_front的删除不会造成迭代器失效。所以我们这里可以猜想,list内部的pop_front删除操作只是对该元素进行删除,并且list.size减1,而实际上该内存并未释放。
在这里插入图片描述

3.2.2 测试pop_back
1)迭代器首地址。
在这里插入图片描述

2)调用pop_back后,地址仍一样,故迭代器没有失效。
在这里插入图片描述

3.2.3 测试erase
1)迭代器首地址。
在这里插入图片描述
2)调用erase函数后。可以看到,虽然迭代器仍指向该地址,但是指向的值已经被改变即地址被系统回收了,故调用erase会造成迭代器失效。
在这里插入图片描述
在这里插入图片描述

总结list的删除:

  • 1)pop_front,pop_back函数不会使迭代器失效,原因是它们只对容器的元素进行删除,而实际的内存并未被系统回收,仍然有效。
  • 2)而erase会使迭代器失效,因为该地址会被系统回收,后续不能再使用该迭代器即it++,所以必须更新迭代器。

4 list的其它成员函数

4.1 构造函数

//构造函数
void test06() {
	list<int> l1;
	list<int> l2(10, 5);//有参构造,创建10个5
	list<int> l3(l2);//拷贝构造
	list<int> l4(l2.begin(), l2.end());
	for (list<int>::iterator it = l4.begin(); it != l4.end(); it++){
		cout << *it << " ";
	}
	cout << endl;
}

4.2 resize函数

/*
	list.resize():
	若容器变长,则以elem值填充新位置。
	如果容器变短,则末尾超出容器长度的元素被删除。
*/
void test07() {

	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	l.push_back(6);
	l.resize(5, 0);//重置大小为5,默认以0填充
	for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}
	l.resize(10, 555);//重置大小为10,默认以555填充
	for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}
}

结果,可以看到6被删除,重置大小为10时默认以555填充。
在这里插入图片描述

4.3 assign函数

/*
	list.assign:重载函数。
	可以将另一个list容器的迭代器区间或者将多个值赋给本容器。
*/
void test08(){
	list<int> l;
	l.push_back(10);
	l.push_back(20);
	l.push_back(30);
	l.push_front(100);
	l.push_front(200);
	l.push_front(300);
	for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}

	list<int> l2;
	l2.assign(10, 100);
	for (list<int>::iterator it = l2.begin(); it != l2.end(); it++) {
		cout << *it << " ";
	}

	l2.assign(l.begin(), l.end());
	for (list<int>::iterator it = l2.begin(); it != l2.end(); it++) {
		cout << *it << " ";
	}
}

在这里插入图片描述

4.4 list.sort排序自定义对象类型
以该对象的年龄进行排序。
代码。

//3.5.3案例  给葫芦娃高级排序
class Person {
public:
	Person(string name, int age, int height) {
		m_name = name;
		m_age = age;
		m_height = height;
	}
	//list容器调用remove(p1)时用到了 "==",所以要重载类对象的==操作符,且参数必须是引用,因为它传过来的对象就是引用。必须要匹配
	bool operator==(const Person &p1) {
		if (m_name == p1.m_name && m_age == p1.m_age && m_height == p1.m_height) {
			return true;
		}
		return false;
	}

	string m_name;
	int m_age;
	int m_height;

};

//自定义排序的回调函数
bool MyCompare(Person &p1, Person &p2) {    //因为对Person类排序,所以两个参数是Person类
	//使用年龄做比较,相同则用身高,高的排前面(我下面用了身高矮的排前面)
	if (p1.m_age == p2.m_age) {
		return p1.m_height < p2.m_height;
	}
	return  p1.m_age < p2.m_age;
}

void test09() {
	list<Person> L;
	Person p1("大娃", 30, 170);
	Person p2("二娃", 28, 160);
	Person p3("三娃", 24, 150);
	Person p4("四娃", 24, 166);
	Person p5("五娃", 24, 158);
	Person p6("爷爷", 90, 200);
	Person p7("蛇精", 999, 999);
	L.push_back(p1);
	L.push_back(p2);
	L.push_back(p3);
	L.push_back(p4);
	L.push_back(p5);
	L.push_back(p6);
	L.push_back(p7);
	cout << "===原始数据===" << endl;
	for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
		cout << "姓名:" << it->m_name << ' ' << "年龄:" << it->m_age << ' ' << "身高:" << it->m_height << endl;
	}

	//使用list自带的sort()进行排序,又因为是自定义排序,所以必须写回调函数
	cout << "===排序后的数据===" << endl;
	L.sort(MyCompare);
	for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
		cout << "姓名:" << it->m_name << ' ' << "年龄:" << it->m_age << ' ' << "身高:" << it->m_height << endl;
	}

	//删除大娃后打印
	cout << "===删除大娃的数据===" << endl;
	L.remove(p1);
	for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
		cout << "姓名:" << it->m_name << ' ' << "年龄:" << it->m_age << ' ' << "身高:" << it->m_height << endl;
	}

}

结果:
在这里插入图片描述

5 多线程测试

下面我们测试多线程下,一个线程不断访问迭代器,另一个线程不断调用插入函数进行插入,看它是否仍满足上面单线程的结果。
这里不加上删除,是因为你在多线程下又写又读的,很容易出现问题,最好单独验证,并且我也不建议这种写法,因为list的读写不是原子操作,有风险。你想同时验证的话加锁是最安全的做法。

再次强调,再项目中不能多个线程不加锁同时对全局的list进行操作。这里只是为了演示,并且我这里的例子是一个负责写,一个负责读,并未同时写,例子本身只是用于测试迭代器是否失效。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include <list>
#include <algorithm>
#include <string>
#include <thread>

list<int> g_list;
void List_Foreach() {
	while (true){
		for (list<int>::iterator it = g_list.begin(); it != g_list.end(); it++) {
			cout << "Hello,i=" << *it << endl;
		}
		//this_thread::sleep_for(chrono::milliseconds(500));
	}

}

void List_Insert() {
	int i = 0;
	while (true){
		//this_thread::sleep_for(chrono::milliseconds(500));
		g_list.push_front(i);
		g_list.push_back(i);
		g_list.insert(g_list.begin(), i);
		i++;
	}
}

int main() {

	thread th1(List_Foreach);
	thread th2(List_Insert);
	th1.join();
	th2.join();

	return 0;
}

多次运行上面结果,可以看到一个线程不断插入,另一个线程不断循环使用迭代器是没有问题的。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值