初识C++ · 模拟实现vector

news2025/2/6 6:01:06

目录

前言:

1 部分简单函数的实现

2 push_back和pop_back

3 reserve和resize

4 Print_vector

5 insert和erase

6 拷贝构造

7 构造

8 赋值

9 memcpy的问题

10 迭代器失效


前言:

继上文模拟实现了string之后,接着就模拟实现vector,因为有了使用string的基础之后,vector的使用就易如反掌了,所以这里直接就模拟实现了,那么实现之前,我们先看一下源代码和文档:

源码不多看,因为很容易绕晕进去,我们从这两个部分得到的信息就够了,首先,vector是一个类模板,即我们可以往里存放许多不同的数据,比如实现一个二维数组,我们可以用vector里面套一个vector,就实现了一个二维数组,为什么说二维数组是一维数组的集合,因为vector的每个空间的start指针指向的空间又是一块连续的空间,即这就是二维数组的构造。

在public的下面有一堆的typedef,这也是不要多看源码的一个原因,很容易绕进去的,比如vector的成员变量的类型是iterator,我们看到value_type*是重命名为iterator了,那么value_type又是T的重命名,即start的类型就是T*,现在我们搞懂了一个问题即模拟实现vector的时候成员变量的类型是模板指针。

那么现在就需要搞懂三个成员变量是什么?

这里就明说了,start是空间的起始位置,finish是空间的最后一个数据的下一个位置,end_of_storage是空间的最后一个位置,所以我们实现size(),capacity()的时候就两两相减就可以了。

基本类型我们了解了,现在就开始模拟实现吧。


1 部分简单函数的实现

这里实现以下函数,size(),capacity(),begin(),end()以及const版本,operator[]以及const版本,empty(),析构函数,注意的就是返回值返回类型即可:

iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator begin()const
{
	return _start;
}
const_iterator end()const
{
	return _finish;
}
size_t size()
{
	return _finish - _start;
}
size_t capacity()
{
	return _end_of_storage - _start;
}
T& operator[](size_t pos)
{
	assert(pos <= _finish);
	return *(_start + pos);
}
const T& operator[](size_t pos)const
{
	assert(pos <= _finish);
	return *(_start + pos);
}
bool empty()
{
	return _start == _finish;
}
~vector()
{
	delete[] _start;
	_start = _finish = _end_of_storage = nullptr;
}

因为是[]重载,返回的是可以修改的类型所以用引用,这里模拟实现的时候一般都返回size_t类型,这里文档里面的原型,最好保持一致。


2 push_back和pop_back

尾插的时候要注意空间的扩容,扩容的判断条件即是_finish = _end_of_storage的时候,扩容方式和前面实现顺序表链表的时候没有什么区别,使用2倍扩容,但是实际上vs下的vector扩容的时候是使用的1.5倍扩容,我们模拟实现的是linux下的vector,在string模拟实现的时候,我们拷贝数据使用的是strcpy,在这里因为是模板,所以不能使用字符串函数,使用内存拷贝,即使用memcpy,当然memset也可以,但是memcpy就可以了,因为不存在空间重叠的问题。

这里有个隐藏的坑,到后面插入string类的时候才会显式出来,这里先不说,目前插入内置类型是没有问题的:

	void push_back(const T& val)
	{
		//判断扩容
		if (_finish == _end_of_storage)
		{
			size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
			T* tmp = new T[newcapacity];
			memcpy(tmp, _start, sizeof(T) * size());
			delete[] _start;
			_start = tmp;
            _finish = _start + size();
			_end_of_storage = tmp + newcapacity;
		}
		*_finish = val;
		_finish++;
	}

好了,实现好了吗?当然没有,这里也有个坑,即size()的问题,因为实现size()的时候,实现方式是_finish - _start,但是扩容之后,_start的位置已经改变,所以此时size()的实现变成了原来的_finish - 新的_start了,所以会报错,解决方式就是把size()先存起来再使用:

	void push_back(const T& val)
	{
		//判断扩容
		if (_finish == _end_of_storage)
		{
			size_t old_size = size();
			size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
			T* tmp = new T[newcapacity];
			memcpy(tmp, _start, sizeof(T) * size());
			delete[] _start;
			_start = tmp;
			_finish = tmp + old_size;//容易出错
			_end_of_storage = tmp + newcapacity;
		}
		*_finish = val;
		_finish++;
	}

这里如果忘了memcpy的使用可以看看文档。

那么push_back就还有一个坑没有解决了,欲知后事如何,请看下去咯。

pop_back的实现相对来说就简单多了,尾删不代表真正的删除数据,指针指向的位置改变就是尾删了,当然,删除操作想要执行就vector就不能为空:

	void pop_back()
	{
		assert(!empty());
		_finish--;
	}

3 reserve和resize

reserve的实现我们刚才其实已经实现过了,reserve即是扩容,push_back的时候我们已经考虑了扩容,所以代码差不了多少:

void reserve(size_t n)
{
	if (capacity() < n)
	{
		size_t old_size = size();
		T* tmp = new T[n];
		memcpy(tmp, _start, sizeof(T) * size());
		delete[] _start;
		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}

当然,这里的坑和push_back是一样的,只有在插入自定义类型的时候才会出现,先不急,那么这里的扩容就是把newcapacity换成了n而已,其余的是没有差别的。

resize模拟实现的时候就要注意了,如果resize实现的是缩容,就直接移动_finish指针就好了,如果实现的是扩容,那么默认参数我们怎么给呢?

文档里面记录的缺省值是value_type(),而value_type是typedef之后的T,所以我们可以看成T val = T(),看着是有点奇怪?其实一点都不奇怪,比如:

int main()
{
	//Test7_vector();
	int a = int();
	cout << a;
	char ch = char();
	cout << ch;
	return 0;
}

这种代码运行是完全没有问题的,这里的a是0,这里的ch是'\0',这是一种初始化方式,所以我们传参的时候就不能写成0,因为不是所有的vector都是vevtor<int>类型的,所以这里我们采用文档的方式,使用这种缺省值,那么扩容的时候多余的空间就给上这个缺省值就好了:

void resize(size_t n, const T& val = T())
{
	if (size() < n)
	{
		//扩容
		reserve(n);
		while (_finish < _start + n)
		{
			*_finish = val;
			_finish++;
		}
	}
	else
	{
		//删减
		_finish = _start + n;
	}
}

实际上我们也可以一直尾插T(),但是我们既然知道了我们要多少空间,我们不如直接把空间开完,然后进行赋值就好了,赋值的循环条件是_finish指针到最后扩容的指针的位置,也是比较好理解的。


4 Print_vector

当插入和扩容操作都完成了,下一步就是打印了,当然会有人说初始化怎么办,不急,我们先构造一个空的vector就可以了:

vector()
{}

iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;

先使用上缺省值,方便测试即可。

Print_vector是用来打印vector的,这里其实变相的考了遍历方式,遍历就是三件套:

void Print_vector(const T& t)
{
	for (auto e : t)
	{
		cout << e << " ";
	}
	cout << endl;

	//typename 告诉编译器这是个类型
	typename vector<T>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}

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

这是三种遍历方式,下标访问和迭代器使用都是没有问题的,唯独需要注意的是typename那里,如果那里我们使用auto it = v.begin()是没有问题的,但是如果我们使用vector<T>::iterator it = v.begin()就会有问题了,这里是因为编译器不知道这里的vector<T>::iterator是类型还是变量了,编译器的原则是不会去类模板里面找东西,而这里使用了模板,那么从编译器的角度出发,它不知道这个究竟是个类型还是变量,所以使用typename。


5 insert和erase

push_back和pop_back是尾插尾删,想要任意位置插入就需要用到insert和erase,文档里面erase有删除一段区间的,这里我们就实现任意位置删除一个数据即可,因为删除一段区间的原理是一样的,因为是任意位置删除和插入,所以实现了之后在push_back和pop_back上也可以复用

insert实现的原理很简单,挪动数据添加数据即可,当然要注意是否要扩容:

void insert(iterator pos, const T& val)
{
	assert(pos <= _finish);
	assert(pos >= _start);

	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		pos = len + _start;//易错点
	}
	iterator it1 = _finish - 1;
	while (it1 >= pos)
	{
		*(it1 + 1) = *it1;
		it1--;
	}
	*pos = val;
	_finish++;
}

当然,为了位置的合法性,这里使用assert断言一下,然后就是判断扩容,但是这里和push_back的size是一个道理,一旦发生扩容,那么pos位置就会变化,while循环的判断就会出问题,所以我们扩容后要更新一下pos的位置,这样才能保证循环的进行。

这里相对string还有一个很好的地方就是我们不用担心end >= pos的那种隐式类型转换比较的发生。

erase的实现原理是一样的,把删除位置的数据往后移动即可。

iterator erase(iterator pos)
{
	assert(pos <= _finish);
	assert(pos >= _start);
	iterator it1 = pos + 1;
	while (it1 < _finish)
	{
		*(it1 - 1) = *it1;
		it1++;
	}
	_finish--;
	return pos;
}

那么这里的复用就是:

void push_back(const T& val)
{
	insert(end(), val);
}
void pop_back()
{
	assert(!empty());
	_finish--;
}

6 拷贝构造

这里的拷贝构造没有string拷贝那么复杂了,不用想深拷贝的问题,开空间一直插入就可以了:

vector(const vector<T>& v)
{
	reserve(capacity());
	for (auto& e : v)
	{
		push_back(e);
	}
}

拷贝构造的最基本要求不能忘记,参数必须是const 的引用类型,不然会发生无限递归的问题。


7 构造

构造是最终boss。

第一个构造,即构造一个空的顺序表,我们可以专门写个函数:

vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{}

当然,没有必要,我们给上三个缺省值就可以了,这是一个构造重载。

第二个重载是给n个element,也很好操作,一直尾插就好了:

vector(size_t n, const vector<T>& val = T())
{
	reserve(n);
	for (int i = 0;i < n;i++)
	{
		push_back(val);
	}
}

第二个也没多大问题,再来看第三个区间构造,实际上第三个重载的最后一个参数可以完全不用管的,没有多大意义,区间构造会再用到一个模板,即类模板的成员函数也可以是个模板:

	template<class InputIterator>
	vector(InputIterator first,InputIterator last)
	{
		while (first != last)
		{
			push_back(*first);
			first++;
		}
	}

以为结束了?还没有,看这段代码:

vector<int> v3 = { 1,2,3,4,5,6 };

这个构造是不是觉得有点奇怪,好像对不上上面三个构造中的任何一个,因为花括号里面的类型是initializer_list

这是一个类,它的成员变量也就几个迭代器和size,但是这是c++11引进的,即c++11准许如下的初始化方式:

void Test_vector()
{
	//有关initializer_list
	int a = 1;
	int b = { 1 };
	int c{ 1 };
	//上面写法均正确 但是不推荐后面两种
	vector<int> v1 = { 1,2,3,4,5,6 };//隐式类型转化
	vector<int> v2({ 1,2,3,4,5,6,7,8 });//直接构造
}

但是不推荐这种方式,代码可读性没那么高,但是我们看到这种初始化方式应该知道这是使用的initializer_list类型来初始化的,这里的初始化也是涉及到了隐式类型转换,先构造一个临时对象,再拷贝构造这个临时对象,那么编译器会优化为直接构造。

所以构造就是这样构造的:

vector(initializer_list<T> il)
{
	reserve(il.size());
	for (auto& e : il)
	{
		push_back(e);
	}
}

但是,这里会出问题了:

vector(initializer_list<T> il)
{
	reserve(il.size());
	for (auto& e : il)
	{
		push_back(e);
	}
}

vector(size_t n, const vector<T>& val = T())
{
	reserve(n);
	for (int i = 0;i < n;i++)
	{
		push_back(val);
	}
}

void Test()
{
    vector<int> v(10,1);
    vector<int> v(10u,1);
    vector<int> v(10,'a');
}

下面的两个构造就不会出问题,第二个的u是无符号的标志,即size_t,第一个会出问题,因为10和1默认的类型是int,那你说,调用上面的没问题吧?调用下面的,虽然第一个的参数类型是size_t,但是将就将就吧也可以,那你说,编译器调用哪个?

所以这里会报错,那么源代码里面解决的就很简单粗暴:

vector(int n, const vector<T>& val = T())
{
	reserve(n);
	for (int i = 0;i < n;i++)
	{
		push_back(val);
	}
}

类型转换一下~ 


8 赋值

这里的赋值我们可以直接采用现代写法,不用传统的先开空间,再进行数据拷贝,我们利用swap函数,可以达到很好的效果:

	void swap(const vector<T>& v)
	{
		std::swap(_start,v._start);
		std::swap(_finish,v._finish);
		std::swap(_end_of_storage,v._end_of_storage);
	}

这里需要注意到的是swap的调用需要用到域名访问限定符,不然编译器会调用离函数最近的函数,即它本身,而我们使用的是std库里面的交换函数,所以需要使用::。

有了交换,我们构造一个临时对象,再进行交换即可:

vector<int>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}

我们提及到过连续赋值的条件是返回值应该是赋值过后的值,所以我们需要返回赋值之后的对象。


9 memcpy的问题

先看这样一段代码:

void Test5_vector()
{
	vector<string> v;
	v.push_back("11111");	
	v.push_back("22222");
	v.push_back("33333");
	v.push_back("44444");
	v.push_back("55555");
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

如果我们只存入4个或者及以下的数据是没有问题的,但是存入5个数据之后,就会存在访问出错的问题,这一切都是因为memcpy,也就是在扩容,尾插的时候会涉及到的问题。

我们了解memcpy的底层之后,就知道memcpy拷贝的方式是逐字节的拷贝,所以当_start指向的空间是自定义类型的时候,经过扩容之后,就会导致_start指向的空间被销毁,但是拷贝过来的指针依旧是指向那块被销毁的空间的,我们再次访问自然就会报错。

所以这里我们最好的方法就是进行赋值:

void reserve(size_t n)
{
	if (capacity() < n)
	{
		size_t old_size = size();
		T* tmp = new T[n];
		//memcpy(tmp, _start, sizeof(T) * size());
		for (int i = 0; i < old_size; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;
		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}

本质就是拷贝构造一个临时对象,就不会导致越界访问的问题。


10 迭代器失效

void Test6_vector()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(6);
	v1.push_back(7);
	v1.push_back(8);
	vector<int>::iterator it = v1.begin() + 3;
	v1.insert(it, 40);
	Print_vector(v1);
	//it = v1.begin() + 3;
	cout << *it << endl;
}

当我们多次插入之后,伴随着扩容的发生,所以it的位置会发生改变,it指向的是原来的空间位置,所以此时我们打印it的值的时候就会打印出来错误的值,所以如果发生了空间的改变导致迭代器失效,如果我们还要使用迭代器,我们就需要重置迭代器-》即那段注释。

void Test7_vector()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(4);
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
		    v1.erase(it);
            it++;
		}
	}
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

且看这段删除偶数的代码,好像没有问题?有以下几种情况就会出错了,11252,1223。

因为删除之后会有移动数据的发生,比如1223,删除了第一个2,第二个2往前面移动,然后再pos位置的指针往后移动,就相当于it指针移动了两次,所以会错过,那么如果最后一个是偶数,即11252,就会完美错过end,那么循环条件就无法结束,就会死循环了。

解决方法也很简单,让it删除之后原地踏步就好了:

while (it != v1.end())
{
	if (*it % 2 == 0)
	{
		it = v1.erase(it);//解决方法
	}
	else
	{
		++it;
	}
}

这也是为什么erase的返回值是iterator的原因,整个代码都是前后呼应的。


感谢阅读!

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

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

相关文章

Java三种方法实现多线程,继承Thread类,实现Runnable接口,实现Callable接口

目录 线程&#xff1a; 继承Thread类&#xff1a; 实现Runnable类&#xff1a; 实现Callable接口&#xff1a; 验证多线程&#xff1a; 线程&#xff1a; 定义&#xff1a;进程可以同时执行多个任务&#xff0c;每个任务就是线程。举个例子&#xff1a;一个Java程序&#…

rfid资产管理系统如何帮助医院管理耗材的

RFID资产管理系统可以帮助医院管理耗材&#xff0c;提高耗材管理的效率和准确性。以下是它可以发挥作用的几个方面&#xff1a; 1. 实时跟踪和定位&#xff1a;使用RFID标签附加在耗材上&#xff0c;可以实时跟踪和定位耗材的位置。医院可以通过系统查询耗材的实时位置&#xf…

以梦为马,不负韶华(3)-AGI在企业服务的应用

AGI在企业服务中&#xff0c;各应⽤已覆盖企业全流程&#xff0c;包含⼈⼒、法务、财税、流程⾃动化、知识管理和软件开发各领域。 由于⼤语⾔模型对⽂本处理类场景有着天然且直接的适配性&#xff0c;⽂本总结、⽂本内容⽣成、服务指引等发展起步早且应⽤成熟度更⾼。 在数据…

景源畅信电商:做抖店的成本高吗?

在当今数字化时代&#xff0c;抖音不仅仅是一个分享短视频的平台&#xff0c;更是一个充满商机的市场。随着抖音用户量的激增&#xff0c;越来越多的商家和个人开始关注在抖音上开设店铺&#xff0c;即所谓的“抖店”。那么&#xff0c;做抖店的成本高吗?这个问题困扰着许多初…

南京“十元手冲咖啡” :流量怎么砸中你?

三包速溶咖啡、一个塑料热水壶&#xff0c;却意外打造出一款爆品。 南京“十元手冲咖啡”突然爆火&#xff0c;起初靠的是出人意料&#xff0c;你以为她要从罐子里擓粉了&#xff0c;她掏出来三条雀巢速溶&#xff1b;你以为她要用机器打水了&#xff0c;她拿出来一个上世纪的…

鸿蒙开发接口UI界面:【@ohos.mediaquery (媒体查询)】

媒体查询 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 &#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 导入模块 import mediaquery from ohos.media…

微信聊天记录删除了怎么恢复?5个数据恢复方法要收好!

“我删除了手机上的微信&#xff0c;然后我的聊天记录就全没了。我怎样才能恢复以前的微信聊天记录&#xff1f;”作为苹果手机和安卓手机上最流行的即时信息应用程序之一&#xff0c;微信凭借其令人惊叹的功能&#xff0c;目前已拥有超过10亿活跃用户进行交流。只要您的手机上…

堆结构知识点复习——玩转堆结构

前言:堆算是一种相对简单的数据结构&#xff0c; 本篇文章将详细的讲解堆中的知识点&#xff0c; 包括那些我们第一次学习堆的时候容易忽略的内容&#xff0c; 本篇文章会作为重点详细提到。 本篇内容适合已经学完C语言数组和函数部分的友友们观看。 目录 什么是堆 建堆算法…

【CTF Web】CTFShow web2 Writeup(SQL注入+PHP+UNION注入)

web2 1 管理员赶紧修补了漏洞&#xff0c;这下应该没问题了吧&#xff1f; 解法 注意到&#xff1a; <!-- flag in id 1000 -->但是 or 被拦截了。 if(preg_match("/or|\/i",$id)){die("id error");}使用UNION注入&#xff1a; ?id1 union sele…

【鱼眼镜头11】Kannala-Brandt模型和Scaramuzza多项式模型区别,哪个更好?

Kannala-Brandt模型和Scaramuzza多项式模型在描述鱼眼相机畸变时都有其特定的数学表示和应用&#xff0c;但它们之间存在一些区别。以下是对两者区别的分点表示和归纳&#xff1a; 数学表示&#xff1a; Kannala-Brandt模型&#xff1a;它假设图像光心到投影点的距离和角度的多…

Excel某列中有不连续的数据,怎么提取数据到新的列?

这里演示使用高级筛选的例子&#xff1a; 1.设置筛选条件 在D2单元格输入公式&#xff1a;COUNTA(A4)>0 这里有两个注意事项&#xff1a; *. 公式是设置在D2单元格&#xff0c;D1单元格保持为空&#xff0c; **. 为什么公式中选A4单元格&#xff0c;A列的第一个数据在A3…

OpenBayes 教程上新 |全球首个开源的文生视频 DiT 模型!对标 Sora,保姆级 Latte 文生视频使用指南

小朋友不爱背诗怎么办&#xff1f;《千秋诗颂》试试看。 2 月 26 日&#xff0c;中国首部文生视频 AI 系列动画《千秋诗颂》于 CCTV-1 频道正式播出&#xff0c;这部动画由上海人工智能实验室和「央妈」&#xff08;中央广播电视总台&#xff09;强强联手&#xff0c;借助「央视…

什么样的数据摆渡设备,可以满足不同网间数据的安全传输需求?

数据摆渡设备是用来在不同的网络环境间安全地传输数据的硬件或软件解决方案。它们通常用于确保在具有不同安全级别的网络&#xff08;如内网和外网&#xff09;之间进行数据交换时的安全性和合规性。以下是一些常见的数据摆渡设备和方法&#xff1a; 移动介质拷贝&#xff1a;使…

“不是我兄弟”!刘强东内部“狼性训话”流出!

今天&#xff0c;京东创始人刘强东5月24日的线上讲话流出。 在这次线上讲话中&#xff0c;刘强东首先宣布为全体采销员工涨薪20%—100%&#xff0c;随后进行了一番“狼性训话”。往期报道可戳&#xff1a;刘强东怒了&#xff1a;“不是我兄弟”&#xff01; 刘强东在讲话中指…

不同厂商SOC芯片在视频记录仪领域的应用

不同SoC公司芯片在不同产品上的应用信息&#xff1a; 大唐半导体 芯片型号: LC1860C (主控) LC1160 (PMU)产品应用: 红米2A (399元)大疆晓Spark技术规格: 28nm工艺&#xff0c;4个ARM Cortex-A7处理器&#xff0c;1.5GHz主频&#xff0c;2核MaliT628 GPU&#xff0c;1300万像…

初学C语言100题:经典例题节选(源码分享)

1.打印Hello World! #include <stdio.h>int main() {printf("hello world\n");//使用printf库函数 注意引用头文件return 0; } 2.输入半径 计算圆的面积 int main() {float r, s;//定义变量scanf("%f", &r);//输入半径s 3.14 * r * r;// 圆的…

YOLOv8+PyQt5面部表情检测系统完整资源集合(yolov8模型,从图像、视频和摄像头三种路径识别检测,包含登陆页面、注册页面和检测页面)

1.资源包含可视化的面部表情检测系统&#xff0c;基于最新的YOLOv8训练的面部表情检测模型&#xff0c;和基于PyQt5制作的可视化面部表情检测系统&#xff0c;包含登陆页面、注册页面和检测页面&#xff0c;该系统可自动检测和识别图片或视频当中出现的八类面部表情&#xff1a…

Android跨进程通信--Binder机制及AIDL是什么?

文章目录 Binder机制Binder是什么&#xff1f;Binder相对于其他几种跨进程通信方式&#xff0c;有什么区别&#xff1f;谈一下 Binder IPC 通信过程&#xff1a;具体的通讯过程是什么&#xff1f;Binder如何处理发送请求与接收请求?Binder是通过什么方式来进行内存映射的&…

[SWPUCTF 2021 新生赛]pop

常见的魔术方法 魔术方法__construct() 类的构造函数&#xff0c;在对象实例化时调用 __destruct() 类的析构函数&#xff0c;在对象被销毁时被调用 __call() 在对象中调用一个不可访问的对象时被调用&#xff0c;比如一个对象被调用时&#xff0c;里面没有程序想调用的属性 …

网络安全等级保护2.0(等保)是什么

等保的全称是信息安全等级保护&#xff0c;是《网络安全法》规定的必须强制执行的&#xff0c;保障公民、社会、国家利益的重要工作。 通俗来讲就是&#xff1a;公司或者单位因为要用互联网&#xff0c;但是网上有坏人&#xff0c;我们不仅要防御外部坏人&#xff0c;还要看看…