【C++】深入剖析list

news2025/1/12 12:05:06

本期我们来深入list的实现原理:

目录

一、STL中的list

二、list的模拟实现

2.1 搭建list的框架

2.2 list迭代器的实现

2.2.1 普通迭代器的实现

2.2.2 const类型迭代器的实现 

2.2.3 迭代器中->运算符重载实现

2.3 其他功能函数的实现

2.3.1 insert

2.3.2 erase

2.3.3 clean

2.3.4 析构函数

2.3.5 迭代器区间构造函数

2.3.6 拷贝构造函数

2.3.7 operator=

三、模拟实现list完整代码


一、STL中的list

我们先来看看在STL中是怎么定义list中的节点的:

template <class T>
struct __list_node {
  typedef void* void_pointer;
  void_pointer next;
  void_pointer prev;
  T data;
};

我们可以看到定义该节点时用的是struct关键字而不是class,这是因为在STL标准库想要开放节点中的指针供编译人员访问。

下面是对于list这个类的定义:

template <class T, class Alloc = alloc>
class list {
protected:
  typedef void* void_pointer;
  typedef __list_node<T> list_node;
  typedef simple_alloc<list_node, Alloc> list_node_allocator;
public:      
  typedef T value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef list_node* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

public:
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
}

剩下的源码我们就不过多解释,下面开始模拟实现:

二、list的模拟实现

2.1 搭建list的框架

我们先来简单写一写list的简单主要功能,搭建一个框架:

namespace lhs
{
	template<class T>
	struct _list_node
	{
		_list_node<T>* _prev;
		_list_node<T>* _next;
		T _data;

		//构造函数
		_list_node(const T& val = T())
			:_prev(nullptr),
			_next(nullptr),
			_data(val)
		{}
	};

	template<class T>
	class list
	{
	public:
		typedef _list_node<T> node;
		//构造函数
		list()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		//尾插
		void push_back(const T& val)
		{
			node* newnode = new node(val);
			newnode->_next = _head;
			newnode->_prev = _head->_prev;
			_head->_prev->_next = newnode;
			_head->_prev = newnode;
		}
		//头插
		void push_front(const T& val)
		{
			node* newnode = new node(val);
			newnode->_next = _head->_next;
			newnode->_prev = _head;
			_head->_next->_prev = newnode;
			_head->_next = newnode;
		}
	private:
		node* _head;
	};
}

上面对于可以熟练运用带头双向循环链表的我们来说就是小菜一碟~

2.2 list迭代器的实现

2.2.1 普通迭代器的实现

我们在string和vector中实现迭代器十分的容易,因为它们的存储空间物理上都是连续的,我们可以对重命名的迭代器++即可。但是在list中就不可能了,因为基于链表实现的list存储空间并非是连续的。所以想要实现list中的迭代器要自定义另一个类型进行运算符重载:

template<class T>
struct __list_iterator
{
	typedef _list_node<T> node;
	typedef __list_iterator<T> self;
	node* _node;

	//构造函数
	__list_iterator(node* t)
		:_node(t)
	{}

	//运算符重载
	T& operator*()
	{
		return _node->_data;
	}
	self& operator++()//前置++
	{
		_node = _node->_next;
		return *this;
	}
	self& operator++(int)//后置++
	{
		self tmp(*this);//保存++之前的数据
		_node = _node->_next;
		return tmp;
	}
	self& operator--()//前置--
	{
		_node = _node->_prev;
		return *this;
	}
	self& operator--(int)//后置--
	{
		self tmp(*this);//保存++之前的数据
		_node = _node->_prev;
		return tmp;
	}
	self& operator-(size_t n)
	{
		for (size_t i = 0; i < n; ++i)
		{
			_node = _node->_prev;
		}
		return *this;
	}
	self& operator+(size_t n)
	{
		for (size_t i = 0; i < n; ++i)
		{
			_node = _node->_next;
		}
		return *this;
	}

	bool operator!=(self n)
	{
		return _node != n._node;
	}
	bool operator==(self n)
	{
		return _node == n._node;
	}
};

有了这个自定义类,我们直接将list中的节点传入该类型让其进行运算符重载即可:

template<class T>
class list
{
public:
	typedef _list_node<T> node;
	//构造函数
	list()
	{
		_head = new node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	//迭代器
	typedef __list_iterator<T> iterator;

	iterator begin()
	{
		return iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
private:
	node* _head;
};

2.2.2 const类型迭代器的实现 

但是对于const成员呢?我们可以像下面代码一样构建一个const版本的迭代器:

但是这个const迭代器和普通迭代器的唯一区别只在于重载*运行时的返回值不同,如果这样子写会造成代码的冗余

下面我们可以在自定义实现的迭代器里多加入一个模版参数Ref,来判断list调用迭代器时传入的是否是const类型的节点:

template<class T, class Ref>//增加一个Ref模版参数来判断调用的是什么类型的迭代器
struct __list_iterator
{
	typedef _list_node<T> node;
	typedef __list_iterator<T, Ref> self;
	node* _node;

	//构造函数
	__list_iterator(node* t)
		:_node(t)
	{}

	//运算符重载
	Ref operator*()//通过第二个模版参数确定是不是const类型的返回值
	{
		return _node->_data;
	}
};

template<class T>
class list
{
public:
	typedef _list_node<T> node;
	//构造函数
	list()
	{
		_head = new node;
		_head->_next = _head;
		_head->_prev = _head;
	}

	//迭代器
	typedef __list_iterator<T, T&> iterator;//第二个模版参数传入T&调用普通迭代器
	typedef __list_iterator<T, const T&> const_iterator;//第二个模版参数传入const T&调用const迭代器

	iterator begin()
	{
		return iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator begin()const
	{
		return const_iterator(_head->_next);
	}
	const_iterator end()const
	{
		return const_iterator(_head);
	}

private:
	node* _head;
};

2.2.3 迭代器中->运算符重载实现

如果list中存的是自定义类型的数据我们难免会用到->来访问具体数据

例如:

struct A
{
	int _a1;
	int _a2;
};
void test2()
{
	list<A> l;
	A a = { 7,8 };
	l.push_back(a);
	l.push_back(a);
	l.push_back(a);
	list<A>::iterator it = l.begin();
	while (it != l.end())
	{
		std::cout << (*it)._a1 << "," << (*it)._a2 << std::endl;
		++it;
	}
	std::cout << std::endl;
}

在上述代码中,我们想要对list中的A类型自定义变量具体成员进行访问,需要对迭代器*操作之后再加上.

这样十分的奇怪,不方便我们具体操作,所以下面我们来重载->操作符:

template<class T,class Ref>
struct __list_iterator
{
	typedef _list_node<T> node;
	typedef __list_iterator<T, Ref> self;
	node* _node;

	//构造函数
	__list_iterator(node* t)
		:_node(t)
	{}

	//运算符重载
	T* operator->()
	{
		return &(_node->_data);
	}
}

下面我们就可以使用->来访自定义类型的内部成员了:

void test2()
{
	list<A> l;
	A a = { 7,8 };
	l.push_back(a);
	l.push_back(a);
	l.push_back(a);
	list<A>::iterator it = l.begin();
	while (it != l.end())
	{
		std::cout << it->_a1 << "," << it->_a2 << std::endl;
		++it;
	}
	std::cout << std::endl;
}

不过上面测试代码奇怪的是:it后面加上->构成运算符重载,该运算符返回的是T*类型的地址所以后面还要加上->才能指向所要访问的成员变量才对啊,所以不应该是it->->_a1才对吗?

这是由于编译器对代码的优化,让我们可以少写一个->就可以直接访问到成员变量了,看起来更直观。

但是下面又出现了一个问题:如果我们使用const类型的迭代器返回的应该是const T*类型才对,那我们可以模仿之前const类型迭代器的实现,再加一个模版参数Ptr来判断传入的类型是否为const:

template<class T, class Ref, class Ptr>//增加两个Ref和Ptr模版参数来判断传入数据的类型
struct __list_iterator
{
	typedef _list_node<T> node;
	typedef __list_iterator<T, Ref, Ptr> self;
	node* _node;

	//构造函数
	__list_iterator(node* t)
		:_node(t)
	{}

	//运算符重载
	Ref operator*()//通过第二个模版参数确定是不是const类型的返回值
	{
		return _node->_data;
	}
	Ptr operator->()//通过第三个模版参数确定是不是const类型的返回值
	{
		return &(_node->_data);
	}
};

template<class T>
class list
{
public:
	typedef _list_node<T> node;
	//构造函数
	list()
	{
		_head = new node;
		_head->_next = _head;
		_head->_prev = _head;
	}

	//迭代器
	typedef __list_iterator<T, T&, T*> iterator;//第二和第三个模版参数传入T&和T*调用普通迭代器
	typedef __list_iterator<T, const T&, const T*> const_iterator;//第二和第三个模版参数传入const T&和const T*调用const迭代器

	iterator begin()
	{
		return iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator begin()const
	{
		return const_iterator(_head->_next);
	}
	const_iterator end()const
	{
		return const_iterator(_head);
	}

private:
	node* _head;
};

 

2.3 其他功能函数的实现

我们有了迭代器就好实现其各种函数了:

2.3.1 insert

//任意位置前插入数据
void insert(iterator pos, const T& val)
{
	node* newnode = new node(val);
	newnode->_next = pos._node;
	newnode->_prev = pos._node->_prev;
	pos._node->_prev->_next = newnode;
	pos._node->_prev = newnode;
}

有了insert函数,我们刚开始搭建的push_back和push_front函数就可以直接复用了:

//尾插
void push_back(const T& val)
{
	insert(end(), val);
}
//头插
void push_front(const T& val)
{
	insert(begin(), val);
}

 

2.3.2 erase

void erase(iterator pos)
{
	assert(pos != end());//避免删除头节点
	pos._node->_prev->_next = pos._node->_next;
	pos._node->_next->_prev = pos._node->_prev;
	delete pos._node;
}

要注意了,使用erase函数后传入的迭代器会失效,因为其指向的空间会被释放。所以我们最好将erase这个函数设置一个返回值来进行修正:

iterator erase(iterator pos)
{
	assert(pos != end());
	node* next = pos._node->_next;
	pos._node->_prev->_next = pos._node->_next;
	pos._node->_next->_prev = pos._node->_prev;
	delete pos._node;
	return iterator(next);
}

有了erase函数,头删和尾删我们信手拈来:

//尾删
void pop_back()
{
	erase(--end());
}
//头删
void pop_front()
{
	erase(begin());
}

2.3.3 clean

//删除所有有效数据
void clean()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
}

2.3.4 析构函数

//析构
~list()
{
	clean();
	delete _head;
	_head = nullptr;
}

2.3.5 迭代器区间构造函数

template<class InputIterato>
list(InputIterato begin, InputIterato end)//迭代器区间构造
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
	while (begin != end)
	{
		push_back(*begin);
		++begin;
	}
}

2.3.6 拷贝构造函数

list(const list<T>& val)
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
	const_iterator it = val.begin();
	while (it != val.end())
	{
		push_back(*it);
		++it;
	}
}

上面代码是一个比较传统的写法,下面我们可以先复用迭代器区间构造函数创建一个临时对象,再构建一个swap函数来交换this的头节点和临时对象的头节点即可:

void swap(list<T>& l)//交换
{
	node* tmp = _head;
	_head = l._head;
	l._head = tmp;
}
list(const list<T>& val)
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
	list<T> tmp(val.begin(), val.end());//创建临时对象
	swap(tmp);//交换数据使this的头节点窃取临时对象的成果
}

2.3.7 operator=

list<T>& operator=(list<T> val)//这里传参没有使用&是为了防止对val数据的破坏
{
	swap(val);
	return *this;
}

注意上述代码传参使用&是为了让形参拷贝构造实参,防止swap函数将实参的数据进行交换

三、模拟实现list完整代码

#include<iostream>
#include<assert.h>
namespace lhs
{
	template<class T>
	struct _list_node
	{
		_list_node<T>* _prev;
		_list_node<T>* _next;
		T _data;

		//构造函数
		_list_node(const T& val = T())
			:_prev(nullptr),
			_next(nullptr),
			_data(val)
		{}
	};

	template<class T,class Ref,class Ptr>//增加两个Ref和Ptr模版参数来判断传入数据的类型
	struct __list_iterator
	{
		typedef _list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> self;
		node* _node;

		//构造函数
		__list_iterator(node* t)
			:_node(t) 
		{}

		//运算符重载
		Ref operator*()//通过第二个模版参数确定是不是const类型的返回值
		{
			return _node->_data;
		}
		Ptr operator->()//通过第三个模版参数确定是不是const类型的返回值
		{
			return &(_node->_data);
		}
		self& operator++()//前置++
		{
			_node = _node->_next;
			return *this;
		}
		self& operator++(int)//后置++
		{
			self tmp(*this);//保存++之前的数据
			_node = _node->_next;
			return tmp;
		}
		self& operator--()//前置--
		{
			_node = _node->_prev;
			return *this;
		}
		self& operator--(int)//后置--
		{
			self tmp(*this);//保存++之前的数据
			_node = _node->_prev;
			return tmp;
		}
		self& operator-(size_t n)
		{
			for (size_t i = 0; i < n; ++i)
			{
				_node = _node->_prev;
			}
			return *this;
		}
		self& operator+(size_t n)
		{
			for (size_t i = 0; i < n; ++i)
			{
				_node = _node->_next;
			}
			return *this;
		}
		bool operator!=(self n)
		{
			return _node != n._node;
		}
		bool operator==(self n)
		{
			return _node == n._node;
		}
	};

	template<class T>
	class list
	{
	public:
		typedef _list_node<T> node;
		//构造函数
		list()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}


		template<class InputIterato>
		list(InputIterato begin, InputIterato end)//迭代器区间构造
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
			while (begin != end)
			{
				push_back(*begin);
				++begin;
			}
		}
		void swap(list<T>& l)//交换
		{
			node* tmp = _head;
			_head = l._head;
			l._head = tmp;
		}
		list(const list<T>& val)
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
			list<T> tmp(val.begin(), val.end());//创建临时对象
			swap(tmp);//交换数据使this的头节点窃取临时对象的成果
		}

		//运算符重载
		list<T>& operator=(list<T> val)//这里传参没有使用&是为了防止对val数据的破坏
		{
			swap(val);
			return *this;
		}

		//尾插
		void push_back(const T& val)
		{
			insert(end(), val);
		}
		//头插
		void push_front(const T& val)
		{
			insert(begin(), val);
		}
		//迭代器
		typedef __list_iterator<T, T&, T*> iterator;//第二和第三个模版参数传入T&和T*调用普通迭代器
		typedef __list_iterator<T, const T&, const T*> const_iterator;//第二和第三个模版参数传入const T&和const T*调用const迭代器

		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator begin()const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end()const
		{
			return const_iterator(_head);
		}

		//任意位置前插入数据
		void insert(iterator pos, const T& val)
		{
			node* newnode = new node(val);
			newnode->_next = pos._node;
			newnode->_prev = pos._node->_prev;
			pos._node->_prev->_next = newnode;
			pos._node->_prev = newnode;
		}
		//删除任意位置的元素
		iterator erase(iterator pos)
		{
			assert(pos != end());
			node* next = pos._node->_next;
			pos._node->_prev->_next = pos._node->_next;
			pos._node->_next->_prev = pos._node->_prev;
			delete pos._node;
			return iterator(next);
		}
		//尾删
		void pop_back()
		{
			erase(--end());
		}
		//头删
		void pop_front()
		{
			erase(begin());
		}
		//删除所有有效数据
		void clean()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
		//析构
		~list()
		{
			clean();
			delete _head;
			_head = nullptr;
		}
	private:
		node* _head;
	};
}

本期到这里又要结束了,感谢各位的阅览~

我们下期见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/781979.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爬虫小白-如何辨别是否有cookie反爬案例

目录 一、Cookie介绍二、cookie生成来源区分查找三、如何判断是否有cookie反爬四、来自服务器生成的cookie反爬解决方法五、来自js生成的cookie反爬解决方法 一、Cookie介绍 先推荐该篇文章简单了解Cookie、Session、Token、JWT1、cookie的类型&#xff1a;会话cookie和持久co…

【LeetCode热题100】打卡第42天:滑动窗口最大值搜索二维矩阵II

文章目录 【LeetCode热题100】打卡第42天&#xff1a;滑动窗口最大值&搜索二维矩阵II⛅前言 滑动窗口最大值&#x1f512;题目&#x1f511;题解 搜索二维矩阵II&#x1f512;题目&#x1f511;题解 【LeetCode热题100】打卡第42天&#xff1a;滑动窗口最大值&搜索二维…

装箱问题(背包问题)

题目描述 有一个箱子容量为v(正整数&#xff0c;o≤v≤20000)&#xff0c;同时有n个物品(o≤n≤30)&#xff0c;每个物品有一个体积 (正整数)。要求从 n 个物品中&#xff0c;任取若干个装入箱内&#xff0c;使箱子的剩余空间为最小。 输入格式 第一行&#xff0c;一个整…

js函数增强

目录 函数的属性arguments将arguments转换成数组rest 纯函数柯里化函数自动实现函数柯里化组合函数自动实现组合化with与evalwitheval 严格模式严格模式的限制 函数的属性 函数其实也是一个对象 是对象就会有对应的方法与属性 以下是几个常用属性 name name属性主要用于访问给…

vue 富文本图片回显

富文本上传 <el-form-item label"服务费打款银行回单" prop"bankreceipt"> <!-- <ImageUpload--> <!-- :value"form.bankreceiptUrl"--> <!-- :fileType"fileType"--> <…

【解决方案】视频传输方案怎样选择适用的WiFi模块

工作环境中&#xff0c;我们接触的最多的是有线传输&#xff0c;但是这个会因为转换接口、传输距离等原因受到一些限制&#xff0c;而无线传输不会&#xff0c;不需要布线&#xff0c;不限制接口&#xff0c;传输距离也由于有线传输&#xff0c;这也是物联网广泛使用无线通信技…

了解下余弦相似度在文本处理中的应用

大家好啊&#xff0c;我是董董灿&#xff01; 昨天写了一篇介绍词向量的文章&#xff1a;5分钟搞懂什么是词嵌入&#xff0c;里面说到&#xff1a;通过把文本转换为词向量&#xff0c;就可以十分方便的计算两者之间的关系&#xff0c;看看哪两个单词更为相近。 比如有四个单词…

AtCoder Beginner Contest 288 F. Integer Division(递推+前缀和优化dp)

题目 给定一个n(2<n<2e5)位的数字串X&#xff0c; 可以将X划分成若干段&#xff0c;得分为每一段的乘积&#xff08;可以不分割&#xff0c;此时得分为X&#xff09; 求所有种分法的得分之和&#xff0c;答案对998244353取模 思路来源 洛谷题解 [ABC288F] Integer …

【PostgreSQL内核学习(十)—— 查询执行(可优化语句执行)】

可优化语句执行 概述物理代数与处理模型物理操作符的数据结构执行器的运行 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和学术成果&#xff0c;力求遵循合理使用原则&#xff0c;并在适用的情况下注明引用来源。 本文主要…

SAP客制化区域菜单和IMG配置清单

1. 自定义区域菜单 事务代码 SE43&#xff0c;操作如下 添加菜单对象 展示效果 输入区域菜单名称并回车&#xff0c;效果如下 2. 自定义IMG配置 事务代码 SIMGH IMG structure 示例-事务代码入口 示例-表格维护入口 示例-自定义代码控制对象 需要创建dummy表并设置表维护 页面设…

平头哥TH5120 BeagleV-Ahead开机系统软件使用体验

等了许久&#xff0c;Beagle 社区官网终于上线了BeagleV-Ahead 的主页 网址 https://beagleboard.org/beaglev-ahead &#xff0c;我们的系统软件评测将会以这个官方主页为出发点 &#xff0c;进行一系列的系统软件功能等操作演示&#xff0c;因官网没有中文页面&#xff0c;我…

【雕爷学编程】Arduino动手做(168)---ATTINY85迷你USB开发板2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

瑞吉外卖开发笔记 二

1、完善登录功能 问题分析 前面我们已经完成了后台系统的员工登录功能开发&#xff0c;但是还存在一个问题:用户如果不登录&#xff0c;直接访问系统首页面&#xff0c;照样可以正常访问。 这种设计并不合理&#xff0c;我们希望看到的效果应该是&#xff0c;只有登录成功后…

JVM运行时数据区——字符串常量池位置的调整

在JDK6及之前&#xff0c;使用永久代来实现方法区&#xff0c;字符串常量池(StringTable)是在永久代(方法区)中的&#xff0c;但是方法区的回收效率不高&#xff0c;在Full GC时才会回收。 在JDK7中&#xff0c;将字符串常量池转移到了堆中&#xff0c;分配在年轻代和老年代中。…

ROS——roslaunch传参注意

本文就总结自己最近开发项目中出现的一些roslaunch有关传参的注意事项说明一下&#xff0c;以免下次有犯错 roslaunch 文件传入到.cpp 这里我采用传参比较简单的方式&#xff0c;还有其他方式&#xff0c;不过这种最为简便 nh.param<xxx>("roslaunch中的名字,如A…

基于 H5 的画图工具

完整资料进入【数字空间】查看——baidu搜索"writebug" 第一章 绪论 1.1 研究的背景 计算机图形学是随着计算机及其外围设备而产生和发展起来的。它是近代计算机科学 与雷达电视及图象处理技术的发展汇合而产生的硕果。在造船、航空航天、汽车、电子、 机械、土建工…

Spring 容器

Spring 容器 两个核心接口&#xff1a;BeanFactory 和 ApplicationContext&#xff08;是BeanFactory的子接口&#xff09;&#xff0c;生成Bean实例并管理Bean的工厂 Bean 对象 Spring管理的基本单位&#xff0c;在基于Spring应用中&#xff0c;所有的组件都可以理解为是一…

【前端|CSS系列第4篇】CSS布局之网格布局

前言 最近在做的一个项目前台首页有一个展示词条的功能&#xff0c;每一个词条都以一个固定大小的词条卡片进行展示&#xff0c;要将所有的词条卡片展示出来&#xff0c;大概是下面这种布局 每一行的卡片数目会随着屏幕大小自动变化&#xff0c;并且希望整个卡片区域周围不要…

【CSDN竞赛】练习题(练手题)及解析(含代码+注释):小玉家的电费;饿龙咆哮-逃离城堡;收件邮箱;寻找宝藏山

订阅专栏,学习更多干货知识! 为想要参与CSDN竞赛的朋友提供几道练手题目及解决方案。含有题目描述以及不同编程语言的解决方案。 💿目录 🏮一、题目一:小玉家的电费📂1.1 题目描述📂1.2 示例代码🏮二、题目二:饿龙咆哮-逃离城堡📂2.1 题目描述📂2.2 示例代码…

基于PySceneDetect的视频场景变换侦测与处理

剪映中集成了一个智能镜头分割的功能,其实是基于python的三方库PySceneDetect来实现的,主要用于对视频进行分析,寻找场景切换或剪辑。 不过一个一个处理起来比较麻烦,这里介绍一个python的三方库实现自动化批量处理。 文章目录 PySceneDetect主要功能特征PySceneDetect的安…