531-C++迭代器失效问题及解决方法

2023-10-27

初步在自定义vector类中实现迭代器

迭代器示意图:
在这里插入图片描述
为什么方式都是一样? 因为迭代器遍历完当前元素跳到下一个元素,底层数据结构的具体的遍历方式都封装在这个迭代器的++运算符函数了。
所以,作为使用方,我们不需要知道底层的数据结构原理。
我们只知道底层数据元素的遍历都封装在++运算符重载函数里面。

在这里插入图片描述

迭代器一般实现成容器的嵌套类型
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
只有容器底层数据结构内存是连续的,才提供[]运算符的重载
在这里插入图片描述
在这里插入图片描述
对于vector来说,我们可以通过[]运算符重载函数遍历访问容器,也可以定义迭代器访问容器,也可以使用for_each访问容器

迭代器失效问题

场景1
如果只删除容器中第一个偶数
在这里插入图片描述
在这里插入图片描述
进程返回为0,没有问题。

如果是把容器中所有的偶数都删除
在这里插入图片描述
在这里插入图片描述
进程运行出现了意外的中止,不可预期的错误。
进程不是正常结束运行

在这里插入图片描述
迭代器失效了,再进行++it就有问题了,是非法操作

场景2
如果只增加元素1次
在这里插入图片描述

在这里插入图片描述
进程是正常的结束。

如果把break去掉。需要添加元素多次。
在这里插入图片描述
在这里插入图片描述
进程意外中止
在这里插入图片描述
对失效的迭代器加加,就是非法的操作了。

我们分析一下
当我们使用迭代器从首元素开始遍历的时候,遇到一个偶数88,要进行删除操作。
在这里插入图片描述
把it指向的元素删除掉,当我们把88删除以后,删除点到容器末尾的位置的所有生成的迭代器就都失效了,也就是删除元素的迭代器本身,和后边有生成的迭代器都失效了,再使用就都出现错误了。
在这里插入图片描述
增加元素的情况也是一样的。
删除点或者增加点之前的迭代器都是好的,删除点或者增加点的迭代器及之后的迭代器都是失效的。
在这里插入图片描述
增加元素还有一种情况:扩容了,就是在其他地方重新开辟内存,相当于原来的容器底层上保持的迭代器就全部都失效了,因为vector底层是数组,vector的迭代器就是指针,原来的迭代器指向的是原来的数组内存空间,肯定完全失效了。

迭代器为什么会失效?

a:当容器调用erase方法后,当前位置到容器末尾元素的所有的迭代器全部失效了
b:当容器调用insert方法后,当前位置到容器末尾元素的所有的迭代器全部失效了
在这里插入图片描述

c:对于insert插入来说,如果引起容器内存扩容,那么原来容器的所有的迭代器就全部失效了
在这里插入图片描述
d:不同容器的迭代器是不能进行比较运算的

迭代器失效了以后,问题该怎么解决?

对插入/删除点的迭代器进行更新操作

我们发现:erase和insert都会返回一个新的迭代器。给当前位置插入元素或者删除元素,当前元素的后边元素都会发生移动,指向当前元素的迭代器就失效了。
当我们增加或者删除操作,会把操作完的当前位置的新的迭代器返回。
(生成当前位置的合法的迭代器并返回)
我们用it去接收返回的更新的迭代器。
我们删除了元素以后,后边的元素都会前移,所以it就不要++了。

//把vec容器中所有的偶数全部删除
auto it = vec.begin();
while (it != vec.end())
{
	if (*it % 2 == 0)
	{
		it = vec.erase(it);//更新删除当前元素的位置的迭代器
	}
	else
	{
		++it;
	}
}

我们现在来解决插入元素的问题
插入一个元素,就把原当前位置的元素都后移了。
所以我们的it还要再多执行1次++了。

//给vec容器中所有的偶数前面添加一个小于偶数值1的数字
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
	if (*it % 2 == 0)
	{
		it = vec.insert(it, *it - 1);
		++it;
	}
}

总笔记代码

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

int main()
{
	vector<int> vec;
	for (int i = 0; i < 20; ++i)
	{
		vec.push_back(rand() % 100 + 1);
	}

	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;

#if 0
	//给vec容器中所有的偶数前面添加一个小于偶数值1的数字
	auto it = vec.begin();
	for (; it != vec.end(); ++it)
	{
		if (*it % 2 == 0)
		{
			it = vec.insert(it, *it-1);
			++it;
		}
	}
#endif

#if 0
	//把vec容器中所有的偶数全部删除
	auto it = vec.begin();
	while (it != vec.end())
	{
		if (*it % 2 == 0)
		{
			//迭代器失效的问题,第一次调用erase以后,迭代器it就失效了
			it = vec.erase(it); // insert(it, val)   erase(it)
			//break;  迭代器失效了,再进行++it就有问题了,是非法操作 
		}
		else
		{
			++it;
		}
	}
#endif

	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;

	return 0;
}
#endif

完善vector类的迭代器代码

迭代器的构造函数:
在这里插入图片描述
构造完生成1个节点
在这里插入图片描述
构造函数的内容相当于头插法

在这里插入图片描述
在这里插入图片描述
形成链表,记录用户从容器中获取的是哪个元素的迭代器
迭代器的成员:
在这里插入图片描述

#include <iostream>
using namespace std;


//定义容器的空间配置器,和C++标准库的allocator实现一样
template<typename T>
struct Allocator
{
	T* allocate(size_t size)//负责内存开辟
	{
		return (T*)malloc(sizeof(T) * size);
	}
	void deallocate(void *p)//负责内存释放
	{
		free(p);
	}
	void construct(T *p, const T &val)//负责对象构造
	{
		new (p) T(val);//定位new
	}
	void destroy(T *p)//负责对象析构
	{
		p->~T();// ~T()代表了T类型的析构函数
	}
};

/*
容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间配置器来实现
*/
template<typename T, typename Alloc = Allocator<T>>
class vector
{
public:
	vector(int size = 10)//构造函数 
	{
		//需要把内存开辟和对象构造分开处理
		//_first = new T[size];
		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;
	}
	~vector()//析构函数 
	{
		//析构容器有效的元素,然后释放_first指针指向的堆内存
		//delete[]_first;
		for (T *p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作
		}
		_allocator.deallocate(_first);//释放堆上的数组内存
		_first = _last = _end = nullptr;
	}
	vector(const vector<T> &rhs)//拷贝构造函数 
	{
		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			//_first[i] = rhs._first[i];
			_allocator.construct(_first + i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
	}
	vector<T>& operator=(const vector<T> &rhs)//重载赋值函数 
	{
		if (this == &rhs)
			return *this;

		//delete[]_first;
		for (T *p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作
		}
		_allocator.deallocate(_first);

		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			//_first[i] = rhs._first[i];
			_allocator.construct(_first + i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
		return *this;
	}
	void push_back(const T &val)//向容器末尾添加元素
	{
		if (full())
			expand();
		//*_last++ = val;   _last指针指向的内存构造一个值为val的对象
		_allocator.construct(_last, val);
		_last++;
	}
	void pop_back() // 从容器末尾删除元素
	{
		if (empty())
			return;
		//erase(it);  verify(it._ptr, _last);
		//insert(it, val); verify(it._ptr, _last);
		verify(_last - 1, _last);//检查指定范围的迭代器 
		//--_last; //不仅要把_last指针--,还需要析构删除的元素
		--_last;
		_allocator.destroy(_last);
	}
	T back()const//返回容器末尾的元素的值
	{
		return *(_last - 1);
	}
	bool full()const { return _last == _end; }
	bool empty()const { return _first == _last; }
	int size()const { return _last - _first; }
	T& operator[](int index) // vec[2]
	{ 
		if (index < 0 || index >= size())
		{
			throw "OutOfRangeException";
		}
		return _first[index]; 
	}

	//#1迭代器一般实现成容器的嵌套类型
	class iterator
	{
	public:
		friend class vector<T, Alloc>;
		iterator(vector<T, Alloc> *pvec=nullptr
			, T *ptr = nullptr)
			:_ptr(ptr), _pVec(pvec)
		{
			Iterator_Base *itb = 
				new Iterator_Base(this, _pVec->_head._next);
			_pVec->_head._next = itb;
		}
		bool operator!=(const iterator &it)const
		{
			//检查迭代器的有效性
			if (_pVec == nullptr || _pVec != it._pVec)//不同容器的迭代器不能进行比较 
			{
				throw "iterator incompatable!";
			}
			return _ptr != it._ptr;
		}
		void operator++()
		{
			//检查迭代器的有效性
			if (_pVec == nullptr)//创建的时候是不为空的 
			{
				throw "iterator invalid!";
			}
			_ptr++;
		}
		T& operator*() 
		{ 
			//检查迭代器的有效性
			if (_pVec == nullptr)
			{
				throw "iterator invalid!";
			}
			return *_ptr; 
		} 
		const T& operator*()const 
		{ 
			//检查迭代器的有效性
			if (_pVec == nullptr)
			{
				throw "iterator invalid!";
			}
			return *_ptr; 
		}
	private:
		T *_ptr;
		//当前迭代器迭代的是哪个容器对象
		vector<T, Alloc> *_pVec;
	};
	//需要给容器提供begin和end方法
	iterator begin() { return iterator(this, _first); }
	iterator end() { return iterator(this, _last); }

	//检查迭代器失效
	void verify(T *first, T *last)
	{
		Iterator_Base *pre = &this->_head;
		Iterator_Base *it = this->_head._next;
		while (it != nullptr)//遍历存储迭代器的链表 
		{
			if (it->_cur->_ptr > first && it->_cur->_ptr <= last)//在指定检查的范围检查,让这个范围里的迭代器失效 
			{
				//迭代器失效,把iterator持有的容器指针置nullptr
				it->_cur->_pVec = nullptr;
				//删除当前迭代器节点,继续判断后面的迭代器节点是否失效
				pre->_next = it->_next;
				delete it;
				it = pre->_next;
			}
			else
			{
				pre = it;
				it = it->_next;
			}
		}
	}

	//自定义vector容器insert方法的实现
	iterator insert(iterator it, const T &val)
	{
		/* 
		我们: 
		1.不考虑扩容 verify(_first - 1, _last);
		2.不考虑it._ptr的指针合法性
		*/
		verify(it._ptr - 1, _last);//检查这个范围的迭代器,让其失效 
		T *p = _last;//最后一个元素的后继位置 
		while (p > it._ptr)
		{
			_allocator.construct(p, *(p-1));//在当前位置p上构造*(p-1)的对象 
			_allocator.destroy(p - 1);//把p-1位置的对象析构掉 
			p--;//从后向前走 
		}
		_allocator.construct(p, val);
		_last++;
		return iterator(this, p);//返回p位置生成的新的迭代器 
	}

	//自定义vector容器erase方法的实现 
	iterator erase(iterator it)
	{
		verify(it._ptr - 1, _last);
		//检查这个范围的迭代器,让其失效 -1是为了让当前传入的这个迭代器也失效 
		T *p = it._ptr;
		while (p < _last-1)//删除元素,元素的后边位置都要前移 
		{
			_allocator.destroy(p);//析构当前p位置的元素 
			_allocator.construct(p, *(p + 1));//在当前p位置上构建后一个位置上的元素 
			p++;//后移 
		}
		_allocator.destroy(p);
		_last--;
		return iterator(this, it._ptr);
	}

private:
	T *_first;//指向数组起始的位置
	T *_last;//指向数组中有效元素的后继位置
	T *_end;//指向数组空间的后继位置
	Alloc _allocator; // 定义容器的空间配置器对象

	//容器迭代器失效增加代码
	struct Iterator_Base
	{
		Iterator_Base(iterator *c=nullptr, Iterator_Base *n=nullptr)
			:_cur(c), _next(n) {}
		iterator *_cur;//指向某个迭代器的指针 
		Iterator_Base *_next;//next指针,保存地址 
	};
	Iterator_Base _head;//头结点,形成链表,记录用户从容器中获取的是哪个元素的迭代器 

	void expand()//容器的二倍扩容
	{
		int size = _end - _first;
		//T *ptmp = new T[2 * size];
		T *ptmp = _allocator.allocate(2 * size);
		for (int i = 0; i < size; ++i)
		{
			//ptmp[i] = _first[i];
			_allocator.construct(ptmp + i, _first[i]);
		}
		//delete[]_first;
		for (T *p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};

int main()
{
	vector<int> vec(200);
	for (int i = 0; i < 20 ; ++i)
	{
		vec.push_back(rand() % 100 + 1);
	}

	auto it = vec.begin();
	while (it != vec.end())
	{
		if (*it % 2 == 0)
		{
			//迭代器失效的问题,第一次调用erase以后,迭代器it就失效了
			it = vec.erase(it);//insert(it, val)   erase(it)
		}
		else
		{
			++it;
		}
	}

	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;



#if 0
	auto it1 = vec.end();
	vec.pop_back();//verify(_last-1, _last)
	auto it2 = vec.end();
	cout << (it1 != it2) << endl;


	int size = vec.size();
	for (int i = 0; i < size; ++i)
	{
		cout << vec[i] << " ";
	}
	cout << endl;

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

	//foreach
	for (int val : vec)//其底层原理,就是通过容器的迭代器来实现容器遍历的
	{
		cout << val << " ";
	}
	cout << endl;
#endif
	return 0;
}
#endif
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

531-C++迭代器失效问题及解决方法 的相关文章

  • 如何使用 C# 中的参数将用户重定向到 paypal

    如果我有像下面这样的简单表格 我可以用它来将用户重定向到 PayPal 以完成付款
  • 我如何才能等待多个事情

    我正在使用 C 11 和 stl 线程编写一个线程安全队列 WaitAndPop 方法当前如下所示 我希望能够将一些内容传递给 WaitAndPop 来指示调用线程是否已被要求停止 如果 WaitAndPop 等待并返回队列的元素 则应返回
  • 在哪里可以找到列出 SSE 内在函数操作的官方参考资料?

    是否有官方参考列出了 GCC 的 SSE 内部函数的操作 即 头文件中的函数 除了 Intel 的 vol 2 PDF 手册外 还有一个在线内在指南 https www intel com content www us en docs in
  • 为什么当实例化新的游戏对象时,它没有向它们添加标签? [复制]

    这个问题在这里已经有答案了 using System Collections using System Collections Generic using UnityEngine public class Test MonoBehaviou
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • 带动态元素的 WPF 启动屏幕。如何?

    我是 WPF 新手 我需要一些帮助 我有一个加载缓慢的 WPF 应用程序 因此我显示启动屏幕作为权宜之计 但是 我希望能够在每次运行时更改屏幕 并在文本区域中显示不同的引言 这是一个生产力应用程序 所以我将使用非愚蠢但激励性的引言 当然 如
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • 对现有视频添加水印

    我正在寻找一种用 C 在视频上加水印的方法 就像在上面写文字一样 图片或文字标签 我该怎么做 谢谢 您可以使用 Nreco 视频转换器 代码看起来像 NReco VideoConverter FFMpegConverter wrap new
  • 为什么编译时浮点计算可能不会得到与运行时计算相同的结果?

    In the speaker mentioned Compile time floating point calculations might not have the same results as runtime calculation
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • 混合 ExecutionContext.SuppressFlow 和任务时 AsyncLocal.Value 出现意外值

    在应用程序中 由于 AsyncLocal 的错误 意外值 我遇到了奇怪的行为 尽管我抑制了执行上下文的流程 但 AsyncLocal Value 属性有时不会在新生成的任务的执行范围内重置 下面我创建了一个最小的可重现示例来演示该问题 pr
  • IEnumreable 动态和 lambda

    我想在 a 上使用 lambda 表达式IEnumerable
  • 如何将服务器服务连接到 Dynamics Online

    我正在修改内部管理应用程序以连接到我们的在线托管 Dynamics 2016 实例 根据一些在线教程 我一直在使用OrganizationServiceProxy out of Microsoft Xrm Sdk Client来自 SDK
  • C# - OutOfMemoryException 在 JSON 文件上保存列表

    我正在尝试保存压力图的流数据 基本上我有一个压力矩阵定义为 double pressureMatrix new double e Data GetLength 0 e Data GetLength 1 基本上 我得到了其中之一pressur
  • 如何在文本框中插入图像

    有没有办法在文本框中插入图像 我正在开发一个聊天应用程序 我想用图标图像更改值 等 但我找不到如何在文本框中插入图像 Thanks 如果您使用 RichTextBox 进行聊天 请查看Paste http msdn microsoft co
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写

随机推荐

  • cuda 编译报错:Unresolved extern function 'cuda_tran_addr'

    出现这种问题的原因是在一个 cu文件中调用了另外一个 cu文件中的带有 device 修饰符的函数 在visual studio中需要做如下修改 如果是linux环境下需要加 dc编译选项
  • Android 10.0 系统服务之ActivityMnagerService-AMS启动流程-[Android取经之路]

    Android取经之路 的源码都基于Android Q 10 0 进行分析 Android取经之路 系列文章 系统启动篇 Android系统架构Android是怎么启动的Android 10 0系统启动之init进程Android10 0系
  • 总线周期

    3 8086执行了一个总线周期 是指8086做了哪些可能的操作 基本总线周期如何组成 在一个典型的读存储器总线周期中 地址信号 ALE信号 RD 信号 数据信号分别在何时产生 答 1 是指8086对片外的存储器或I O接口进行了一次访问 读
  • opnet 路由表

    数据结构IpT Rte Module Data是一个ip dispatch和其子进程所共有的一个数据结构 下面列出的和路由表相关的 struct IpT Rte Module Data IpT Cmn Rte Table ip route
  • 升降压电路的工作原理

    1 升压电路也叫自举电路 是利用自举升压二极管 自举升压电容等电子元件 使电容放电电压和电源电压叠加 从而使电压升高 有的电路升高的电压能达到数倍电源电压 开关直流升压电路 即所谓的boost或者step up电路 原理 the boost
  • Windows10下使用Caffe训练神经网络步骤一

    一 生成Caffe数据库 1 准备数据 参考链接中的作者提供了一些图片 共有500张图片 分为大巴车 恐龙 大象 鲜花和马五个类 每个类100张 编号分别以3 4 5 6 7开头 各为一类 其中每类选出20张用作测试 其余80张用作训练 因
  • 程序员思维

    程序员思维 起因 首先简单说一下 为什么我会想到这个话题 主要有这么几方面的原因 当我试图回过头去总结大学在计算机专业所学习的一些理论和知识的时候 发现 在学校里面学习的一些东西 走了两个极端 一个极端是偏向了细节 比如我们学习的那些 程序
  • 如何看mysql版本_如何查看mysql版本的四种方法,MySQL版本查看

    1 在终端下 mysql V 以下是代码片段 shengting login mysql V mysql Ver 14 7 Distrib 4 1 10a for redhat linux gnu i686 2 在mysql中 mysql
  • 什么是UKey?Ukey在密评中的应用 双因素身份认证 安当加密

    Ukey是什么及用途 UKey 又叫智能密码钥匙 是一种通过USB 通用串行总线接口 直接与计算机相连 具有密码验证功能 可靠高速的小型存储设备 ukey 是对现行的网络安全体系的一个极为有力的补充 基于可信计算机及智能卡技术把易用性 便携
  • ovs+dpdk 三级流表(microflow/megaflow/openflow)

    本文介绍在ovs dpdk下 三级流表的原理及其源码实现 普通模式ovs的第一和二级流表原理和ovs dpdk下的大同小异 三级流表完全一样 基本概念 microflow 最开始openflow流表是在kernel中实现的 但是因为在ker
  • uni-app发请求放在哪个生命周期函数好

    1 onLoad 页面加载了才发请求 在onLoad中发送请求是比较科学的 2 onShow 每次渲染页面就会发请求 会多次触发 会重复触发 页面隐藏显示也会触发 所以在这里发送请求不科学 3 onReady 页面初次渲染完成了 但是渲染完
  • 解决引入新的项目右边栏没有maven的问题

    解决IntejIdea引入新的项目时右侧边栏不显示maven项目的问题 导入新的idea项目的时候需要导入maven依赖 但是很多时候导入项目发现找不到maven 如图 应该怎么办呢 在help下找到find action 然后输入mave
  • <通信接口篇> I2C介绍

    目录 01 I2C总线介绍 总线物理连接 通信模式 I2C协议整体时序 02 I2C读写时序介绍 I2C写时序 主机 I2C读时序 主机 03 文章总结 大家好 这里是程序员杰克 一名平平无奇的嵌入式软件工程师 作为嵌入式开发人员 无论是硬
  • Java计算下一次提醒时间的简单算法

    Java计算下一次提醒时间的简单算法 需求分析 算法分析 代码清单 核心算法 计数器对象 工具类 代码下载地址 需求分析 在生产实践过程中 我们接到了一个这样的需求 客户接到系统做工作单后 按照要求客服人员要定时进行回访 回访提醒必须在工作
  • 2.2.2新版Banner轮播图实现

    随着Android弃用了jcenter库以后 Banner的使用也大大的和以前不同 下面就来介绍一下2 2 2版本banner的使用和Demo 文章目录 一 改进内容 二 Demo效果图 二 步骤 1 引入库 依赖banner 2 xml文
  • OSS对象存储

    文章目录 OSS 1 存储分类 2 对象存储OSS 2 1 概念组成 2 2 存储优势 2 3 与自建服务器的优势 2 4 Bucket存储空间 2 5 存储类型 2 6 对象 2 6 1 命名规范 2 6 2 存储空间与对象关系 2 7
  • Android编译系统-envsetup和lunch代码篇

    Android系统启动篇 1 android系统启动流程简介 2 android init进程启动流程 3 android zygote进程启动流程 4 Android SystemServer进程启动流程 5 android launch
  • mysql组成部分

    一 mysql组成部分 组成部分 1 连接池组件 2 管理服务和工具组件 3 SQL接口组件 4 查询分析器组件 5 优化器组件 6 缓存 cache 组件 7 插件式存储引擎 8 物理文件 1 mysql存储引擎 1 1 InnoDB存储
  • KMP算法原理详解_论文解读版

    1 KMP算法 KMP算法是一种保证线性时间的字符串查找算法 由Knuth Morris和Pratt三位大神发明 而算法取自这三人名字的首字母 因而得名KMP算法 那发明这样的字符串查找算法又有什么用 在当时计算机本身非常昂贵 计算资源更是
  • 531-C++迭代器失效问题及解决方法

    初步在自定义vector类中实现迭代器 迭代器示意图 为什么方式都是一样 因为迭代器遍历完当前元素跳到下一个元素 底层数据结构的具体的遍历方式都封装在这个迭代器的 运算符函数了 所以 作为使用方 我们不需要知道底层的数据结构原理 我们只知道