【C++】list的使用和基本迭代器框架的实现 vs和g++下string结构的说明

news2024/11/13 10:17:22

真正的成熟应该并不是追求完美,而是直面自己的缺憾,这才是生活的本质。

在这里插入图片描述

文章目录

  • 一、初见list
    • 1.list的迭代器失效和基本使用
    • 2.list的operations操作接口(看起来挺不错的接口,但可惜不怎么实用)
    • 3.vector和list的排序性能对比(list的sort接口不常用的原因:list的排序效率不高)
  • 二、list迭代器的基本框架(结构体指针无法满足需求,类封装+运算符重载让迭代器的行为像指针一样)
  • 三、vs和g++下string结构的说明
    • 1.vs下的string结构
    • 2.g++下的string结构



一、初见list

1.list的迭代器失效和基本使用

1.
list的底层是由带头双向循环链表实现的,与vector和string不同的是,list的遍历要通过迭代器来实现,就算我们不知道list迭代器的底层实现机制,但并不影响我们使用迭代器,这就是迭代器对于所有容器带来的好处,无论你是什么容器,都有统一的遍历方式,那就是迭代器。

2.
范围for的实现,本质就是通过迭代器,范围for可以遍历容器的迭代器,对迭代器进行解引用,然后依次拷贝给元素e,所以C++11的范围for没有什么新花样,本质上使用的还是迭代器实现的。在编译器编译代码的时候,会傻瓜式的将范围for替换为迭代器的代码,然后进行编译运行。

3.
数据结构初阶阶段所使用的尾插尾删,头插头删,对于list依旧可以正常使用。

void test_list1()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	list<int>::iterator it = lt.begin();//迭代器属于类的内嵌类型
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	lt.push_front(10);
	lt.push_front(20); 
	lt.push_front(30);
	lt.push_front(40);

	for (int e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.pop_back();
	lt.pop_back();
	lt.pop_front();
	lt.pop_front();

	for (int e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

4.
对于list来说,和vector一样,我们可以用迭代器区间配合find进行元素对应的某一个节点的查找,并返回该节点对应的迭代器位置。

5.
在list这个容器中,只要对某一个节点进行操作,就离不开迭代器,迭代器就是list的唯一,因为像链表这样的数据结构他是无法支持随机访问的,所以通过下标随机访问的方式是不可行的,那么我们就只能通过STL提供的迭代器来对某一节点进行操作。

6.
对于list来说,insert不会导致迭代器失效,vector存在迭代器失效是因为在扩容时reserve采取异地扩容的方式,这就导致原有迭代器指向了已经被释放的空间。
但list并不存在扩容这样的操作,list直接按需申请空间,你要插入多少个节点,那我就申请多少个节点,然后将所有的节点链接到头结点后面就好了,所以insert之后迭代器依旧可以继续使用,因为他对应的节点空间不会被销毁,依旧好好的存在着。

7.
而对于erase来说就不一样了,erase会释放迭代器对应节点的空间,自然erase之后迭代器就会失效,如果想要继续使用迭代器,则可以利用erase的返回值,erase会返回被删除节点的下一个节点的迭代器,我们可以用erase的返回值来更新迭代器。

void test_list2()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	list<int>::iterator pos = find(lt.begin(), lt.end(), 3);
	if (pos != lt.end())
	{
		lt.insert(pos, 30);//insert之后,pos迭代器肯定不会失效。
	}

	cout << *pos << endl;
	(*pos)++;

	for (int e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.erase(pos);//erase之后,迭代器会失效,因为节点空间被释放了
	cout << *pos << endl;
	for (int e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

2.list的operations操作接口(看起来挺不错的接口,但可惜不怎么实用)

1.
resize用于调整链表的空间,如果是调整大一些,那就是一个一个的申请节点,尾插到链表上面去。
如果是调整小一些,那也需要一个个的释放节点,相当于尾删节点。
但这个接口list不喜欢用。

2.
clear用于释放除头结点之外的所有节点,调用clear之后,链表的size大小也就变为了0,但需要和析构函数区分开来,析构函数会将头结点的空间也给释放掉,而clear仅仅只是将存储有效数据的所有节点释放掉。后面list模拟实现之后,就会有一个更深层次的理解。

在这里插入图片描述
在这里插入图片描述
3.
下面的operations的操作接口用的非常少,就是看起来确实挺有用的感觉挺不错,但在实际应用的时候就不怎么常用了,这也就是库的设计者在想的时候,想的很好,但是在程序员实际使用的时候并没有那么的实用。
这就是在做计划的时候,设计的很好,但等到实际使用的时候,发现没啥用,价值不大

4.
remove相当于find+erase,可用于链表中某个具体节点的删除,如果删除的数据不存在,则什么也不会发生,并不会报错。

5.
链表单独提供了一个排序接口sort,而没有用算法库里面的sort,这其实就涉及到迭代器的类型问题。
迭代器从功能上来说,可以分为三类:只能++的单向迭代器(单链表、哈希表),既能++也能 - - 的双向迭代器(list带头双向循环链表),既能++也能 - - 还能±某个具体的数的随机迭代器(string、vector)。

6.
算法库的sort底层用的是快速排序,为了key值选的合适,快排会进行三数取中,所以会进行迭代器的作差,而list的双向迭代器肯定不支持做差,所以调用算法库的sort就会报错。
如果想要排序链表,那就只能调用list类的成员函数sort来进行排序,list的sort底层用的是归并排序。

在这里插入图片描述
在这里插入图片描述

7.
unique可以对链表进行去重,但去重必须建立在排序的基础之上,如果不排序就去重,则去重的结果会发生问题。
挨着的相同数字会被去重,如果不挨着,unique调用后的结果就会出错,这一点有点像去除数组中重复元素那个题,快慢指针法进行去重,那个我记得也是建立在有序的情况下进行重复元素的删除的,这里的unique的道理和快慢指针相同。

8.
merge可以合并两个链表,reverse用于逆置链表,splice可以转移一个链表的节点或某一区间的节点或所有节点,到另一个链表上面去。

//operations操作接口:有用,但用处不大,和我们做的时间规划表一样,想的挺好,但在实际用的时候,并没有那么常用。
void test_list3()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(9);
	lt.push_back(5);
	lt.push_back(2);
	lt.push_back(5);
	lt.push_back(2);
	for (int e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	lt.remove(3);//remove=find+erase
	lt.remove(30);//如果删除的元素不存在,则什么也不会发生
	for (int e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.sort();//链表单独提供一个排序,没有用算法库里面的
	//sort(lt.begin(), lt.end());//这样进行链表排序是不行的
	//迭代器功能分类:
	//1.单向迭代器 ++ 单链表
	//2.双向迭代器 ++ -- list
	//3.随机迭代器 ++ -- + - vector&&string
	for (int e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	//必须先排序,再去重
	lt.unique();//去重算法是建立在有序的基础上。去重有点像快慢指针删除数组重复元素,所以如果重复数字不挨着,unique就会出现错误。
	for (int e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.reverse();逆置
	lt.merge();归并
	lt.splice();拼接转移
}

3.vector和list的排序性能对比(list的sort接口不常用的原因:list的排序效率不高)

1.
测试排序性能,建议在release版本下面进行测试,debug版本下会由于编译器版本的差异产生不同的现象,对于性能的测试release版本下面更加准确,debug底层会由于某些优化等等导致产生的结果不够精确。

2.
在数据量大概是十万左右的情况下,vector的排序性能大概是list排序性能的二倍左右,所以说list的排序性能很低了,相对于vector。

在这里插入图片描述
3.
有人做过比喻,如果你要排序list,倒不如先将list的数据拷贝到vector进行排序,等排完序再将数据拷贝回list里面去,就算这样的排序的性能都是要比直接用list进行排序的性能要高不少。从结果可以看到,vector的排序性能明显要高于list。
当然如果数据量很小的话,vector和list的差别就没有那么大了,那时候的排序时间就相差无几了。

4.
所以,如果在数据量很大的情况下,排序不会选择list的sort,其实主要是list的空间不连续,在访问不连续的空间时,消耗时间还是蛮大的。而连续的vector空间在访问时,消耗就比较小,CPU高速缓存的命中率也高,这也正是vector数据结构独有的优势。

在这里插入图片描述
5.
如果不头插头删,vector就比较好,如果频繁头插或中间插入删除等,list的结构优势就体现出来了,因为vector是连续的空间,list是一个一个的节点,一个需要挪动数据,一个不需要挪动数据。

//1.vector排序和链表排序的性能对比,所以如果你要排序,就不要将数据放到链表里面去,这也正是链表的sort接口不常用的原因。
//2.N个数据需要排序,vector+ 算法sort  list+ sort
void test_op()//优化这部分直接看release版本即可,debug版本对于不同的结构在底层优化达到的效果都不太一样。主要看release即可。
{
	srand(time(NULL));
	const int N = 100000;
	vector<int> v;
	v.reserve(N);

	list<int> lt1;
	list<int> lt2;
	for (int i = 0; i < N; ++i)
	{
		auto e = rand();
		v.push_back(e);
		lt1.push_back(e);
		//lt2.push_back(e);
	}

	// 拷贝到vector排序,排完以后再拷贝回来
	int begin1 = clock();
	//for (auto e : lt1)
	//{
	//	v.push_back(e);
	//}
	sort(v.begin(), v.end());//调用算法库的sort对vector进行排序
	//size_t i = 0;
	//for (auto& e : lt1)
	//{
	//	e = v[i++];
	//}
	int end1 = clock();

	int begin2 = clock();
	lt1.sort();
	int end2 = clock();

	printf("vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

二、list迭代器的基本框架(结构体指针无法满足需求,类封装+运算符重载让迭代器的行为像指针一样)

1.
C++为了能够支持泛型编程,搞出来内置类型的构造,实则编译器会在这里进行特殊处理,区分开泛型和内置类型,使用时,可以用类型的构造函数来进行初始化,内置类型一般初始化为0等值,自定义类型会调用该类的默认构造。

在这里插入图片描述

2.
迭代器是类的内嵌类型,行为像指针一样,可以解引用和++或 - - 。
vector和string的迭代器都是由原生指针实现的,那是因为他们的底层是一个动态的顺序表,内存是连续的,解引用迭代器就是解引用原生指针,那自然就可以拿到对应数组位置的内容,而list的迭代器对应的是一个结构体,是一个自定义类型,并非原生指针的内置类型,所以解引用迭代器我们拿到的是结构体对象,而并非是数据内容,这就不符合迭代器的特征,因为迭代器的本意就是要解引用拿到数据,而我们拿到的是一个结构体对象,这就有问题了。
所以这个时候我们就需要类封装和运算符重载来实现list的迭代器了,以便于他的迭代器能够解引用和++或 - - ,只要用运算符重载,当然就离不开类,解引用迭代器能够获得对应结构体数据,则迭代器就不简单是一个原生指针了,他应该是一个对象,这个对象的类成员函数可以实现解引用++ - - 等功能。

3.
这就好比年月日不支持++ - - 等操作,那我们就封装一个日期类,在日期类里面实现日期的++ - - 等操作。这里的迭代器也是这个意思,你普通的结构体指针node *不是不支持解引用拿到数据,++ - - 等操作吗?那我就封装一个类,在这个类里面利用运算符重载,让你的结构体指针node *支持迭代器操作,这不就好了吗?

4.
用一个结点的指针就可以作为list迭代器的成员变量了,迭代器本质就是一个对象,这个对象的成员变量是结构体指针,通过迭代器类和迭代器对象我们才能让list的迭代器实现解引用加加减减等操作。

5.
为了支持泛型,可以看到STL库在参数设计上采用模板的形式,在实现部分将内置类型也看作了自定义类型,C++让内置类型也支持构造,赋值,拷贝构造等成员函数,就是为了在泛型编程下,无论是自定义类型还是内置类型都能够统一用模板参数来处理,等到具体使用的时候在根据模板参数类型的不同实例化出不同的模板,这样在编程时可大大提升代码的可维护性,泛型编程可以省去很多不必要的代码。

在这里插入图片描述

namespace wyn
{
	template<class T>
	struct list_node
	{
		list_node* _next;//指向下一个结点的结构体指针
		list_node* _prev;//指向前一个结点的结构体指针
		T _data;//数据类型是泛型,可能是内置类型,也有可能是自定义类型

		list_node(const T& x)
		//new结点的时候会调用构造函数,但编译器默认生成的无参构造函数无法满足我们的要求
		//所以我们需要自己写一个带参数的构造函数,因为new结点时需要将数据作为参数传递,无参不符合要求。
			:_next(nullptr)
			,_prev(nullptr)
			,_data(x)
		{}
	};
	
	template<class T>
	struct __list_iterator
	{
		typedef list_node<T> node;
		
		node* _pnode;//迭代器类的成员就是一个结构体指针_pnode

		__list_iterator(node* p)
			:_pnode(p)
		{}

		T& operator*()//返回_data的引用,则解引用迭代器可以修改结点对应的数据
		{
			return _pnode->_data;
		}

		__list_iterator<T>& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}

		bool operator!=(const __list_iterator<T>& it)
		//比较两个迭代器是否相等,就是比较结点指针相不相等
		{
			return _pnode != it._pnode;
		}
	};
	
	template<class T>
	class list
	{
		typedef list_node<T> node;//将实例化后的类模板list_node<T>类型重定义为node
	public:
		typedef __list_iterator<T> iterator;
		//将实例化后的类模板__list_iterator<T>类型重定义为iterator
		
		iterator begin()
		{
			//iterator it(_head->_next);
			//return it;
			//上下这两种写法是等价的。
			return iterator(_head->_next);
			//返回迭代器类的匿名对象,参数传结构体指针,迭代器类的成员变量只有一个结构体指针。
			//匿名对象可以省下我们自己定义出对象然后再返回,这样比较麻烦。
		}

		iterator end()//迭代器对象出了作用域被析构掉,所以用传值返回,不能用传引用返回
		{
			return iterator(_head);
			//end()返回的是最后一个元素的下一个位置的迭代器,所以我们返回的是哨兵卫结点的迭代器对象。
		}
		void empty_initialize() 
		{
			_head = new node(T());//node实现的构造函数是带参数的,调用T类型的默认构造初始化
			//new一个结点,new会自动调用node类的带参构造函数,我们给构造函数传一个泛型的匿名对象,
			//保证结点存储的数据类型是泛型,既有可能是内置类型也有可能是自定义类型,所以传匿名对象。
			//如果是自定义类型,会调用其类的无参构造函数,如果是内置类型,基本是0或NULL等初始值,
			//我们可以认为内置类型也有构造函数,这样的写法实际是为了支持C++的泛型编程所搞出来的,
			//如果是内置类型,编译器会做特殊处理。
			_head->_next = _head;
			_head->_prev = _head;
		}
		list()
		{
			empty_initialize();
		}
	
		void push_back(const T& x)
		{
			node* newnode = new node(x);
			//这里所传x的类型是不确定的,他的类型取决于调用方给模板参数所传的值。
			//如果T是自定义类型,那x就是对象,如果T是内置类型,x就是变量。
			node* tail = _head->_prev;

			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;

			//insert(end(), x);
		}
		
	private:
		node* _head;
	};
}
	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		//内嵌类型 -- 迭代器需要能够1.解引用能够取到结点的数据 2.并且可以++或--进行移动
		//string和vector的iterator原生指针能够使用,是因为数组结构正好支持迭代器行为。
		//list如果用原生指针,它的数组结构无法支持迭代器行为,因为list的空间是不连续的。
		//为了支持list的迭代器,我们用类的封装和运算符重载进行支持。

		list<int>::iterator it = lt.begin();
		//由于迭代器对象的拷贝构造没有实现,所以用编译器默认生成的浅拷贝。
		while (it != lt.end())//vector和string可以用<来进行判断,但list这里只能用!=,这里会调用it对应类的运算符重载
		{
			//it.operator*(){} --- 转换为调用自定义类型对应类内的运算符重载函数
			//it.operator++(){}
			cout << *it << " ";//*it是自定义类型iterator的运算符重载,iterator是进行封装的类型
			++it;//++it也是自定义类型iterator的运算符重载。
			//(*it)++; --- it.operator*()函数的引用返回值进行自增,返回值可能是自定义类型或内置类型。
		}
		cout << endl;

		for (auto e : lt)//范围for就是傻瓜式的替换迭代器的代码,begin()end(),迭代器支持解引用++--等操作,范围for就能用
		{
			cout << e << " ";
		}
		cout << endl;
	}

三、vs和g++下string结构的说明

1.vs下的string结构

1.
下面所说的默认环境是32位平台,指针为4字节。从打印结果我们可以得到两个信息,一个是s1和s2的所占字节大小一样,另一个是两者所占字节大小为28字节。
首先,两者一样的原因是因为,对象的大小和存储的数据无关,因为数据是在堆区上动态开辟的,分析对象大小时,我们只看对象成员变量所占大小,所以这就能解释为什么对象s1和对象s2的大小是一样的了。

在这里插入图片描述
2.
至于为什么是28字节,而不是12字节,这就和vs下string的结构有关系了,我们实现的string有三个成员变量分别是_ptr、_size和_capacity按照内存对齐的原则应该是12字节。但我们实现的并不标准,仅仅能够完成string的大部分功能而已。
vsPJ版本的STL源码中string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:当字符串长度小于16时,使用内部固定的字符数组_buf来存放,当字符长度大于等于16时,从堆上开辟空间,不在使用_buf数组进行存放

在这里插入图片描述
3.
从调试窗口可以看到,当存储数据小于16时,数据内容被存放到了_Buf数组里面,而_Ptr并没有指向具有有效字符的空间。当存储数据大于等于16时,数据内容被存放到了_Ptr指向的动态开辟的空间里面,而_Buf里面什么都没有存。

4.
vs对于string的设计思想主要还是用空间换时间,增大string对象的大小,如果数据量比较小,那就用提前开好的_Buf数组进行存储,节省自己动态开辟空间的消耗。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.g++下的string结构

1.
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:共分为4个部分,空间总大小,字符串有效长度,引用计数,指向堆空间的指针,用来存储字符串

2.但是显示出来的string对象大小是8字节,因为默认使用的环境是64位,指针大小为8字节。

x86_64是64位平台,指针大小为8字节

在这里插入图片描述

在这里插入图片描述

3.
在没有向对象中指针指向空间发生数据的写入的时候,g++下面会先发生浅拷贝,先不开空间,下面代码的第三张图片中我们对空间进行了写入,这个时候就会发生写时拷贝,重新进行空间的深拷贝。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

Linux系统基础命令(二)

一、浏览和切换目录 ls命令&#xff1a;列出文件和目录&#xff0c;主要用于列出文件和目录 CentOS的终端默认是有颜色标注的。一般来说&#xff1a;蓝色--->目录&#xff1b;绿色-->可执行文件&#xff1b;红色--->压缩文件&#xff1b;浅蓝色--->链接文件&#…

Java并发类库提供的线程池有哪几种? 分别有什么特点?

第21讲 | Java并发类库提供的线程池有哪几种&#xff1f; 分别有什么特点&#xff1f; 我在专栏第 17 讲中介绍过线程是不能够重复启动的&#xff0c;创建或销毁线程存在一定的开销&#xff0c;所以利用线程池技术来提高系统资源利用效率&#xff0c;并简化线程管理&#xff0c…

计算机网络总结

第一章 互联网 互连网&#xff1a;局部范围互连起来的计算机网络&#xff0c;与网络相连的计算机常称为主机。 互联网&#xff1a;当今世界上最大的计算机网络&#xff0c;Internet 两个基本特点 连通性&#xff0c;资源共享(信息&#xff0c;软件&#xff0c;硬件) 计算…

Ubuntu18安装新版本PCL-1.13,并和ROS自带PCL-1.8共存

文章目录1.安装新版本PCL-1.132.在工程中使用新版本的PCL-1.133.pcl-1.13误装到/usr/local/下如何卸载1.安装新版本PCL-1.13 下载PCL-1.13代码&#xff1a; 修改CMakeLists.txt文件&#xff0c;不编译vtk相关的代码&#xff1a;vtk的问题比较难解决&#xff0c;但是一般我们安…

科技大势怎么看 2023怎么干?

2023年&#xff0c;科技的走向依旧是世界各国的关注重点&#xff0c;各国在纷纷设立自己的科技战略目标外&#xff0c;还在潜心研究不同技术领域的科技趋势&#xff0c;试图通过科技占据国际竞争的制高点。 随着我国深入实施创新驱动发展战略&#xff0c;推动产业结构优化升级&…

数据结构1——概念与程序复杂度

翻陈年老底翻到了之前的数据结构笔记&#xff0c;由于当时刚开始使用电脑记笔记&#xff0c;markdown语法用的还是不是很熟&#xff0c;以及技术上比较欠缺&#xff0c;望多多海涵。 来都来了&#xff0c;点个赞呗~ 数据结构大体框架 早期>>数值计算>>计算机元素…

配置主机名与ip的映射关系

本次进行简单的小实验 通过在windows上配置主机名与IP地址的映射关系&#xff0c;达到我们在xshell或其他远程连接设备上&#xff0c;不用IP地址登陆&#xff0c;只需要用主机名就能实现登陆的效果 配置 首先 需要查看自己虚拟机的IP地址&#xff0c;找到ens33或者ens160…

[项目] Boost搜索引擎

目录 1.项目相关背景 2.项目宏观原理 3.技术栈和项目环境 4.正排索引&&倒排索引 5.去标签与数据清洗 6.构建索引模块Index 6.1正排索引 6.2 建立倒排 jiebacpp使用 建立分词 7.搜索引擎模块Searcher Jsoncpp -- 通过jsoncpp进行序列化和反序列化 处理Cont…

Vscode中Vue文件保存格式化、 ElementUI、Font Awesome俩大插件使用

Vscode中Vue文件老一片红色出现格式错误&#xff1f;&#xff1f;如何运行别人的项目&#xff08;没有node_modules文件&#xff09;&#xff1f;&#xff1f;选用组件与图标&#xff1f;&#xff1f; 解决问题一 前提有&#xff1a;Prettier ESLint插件、ESLint插件 1.打开s…

Git图解-常用命令操作-可视化

目录 一、前言 二、初始化仓库 2.1 设置用户名与邮箱 2.2 初始化仓库 三、添加文件 四、查看文件状态 五、查看提交日志 六、查看差异 七、版本回退 八、删除文件 九、分支管理 9.1 创建分支 9.2 切换分支 9.3 查看分支 9.4 合并分支 十、文件冲突 十一、转视…

ROS从入门到精通系列(二十九)-- linux封装ROS 自启动脚本及可执行程序App

机器人分布式处理架构ROS分布式架构,HiBot架构主要使用C/S架构,其中HibotServer为服务器,Muqutte为消息服务器中间件,HiBotClient为运行在机器人上的客户端。主要实现了机器人任务的远程部署、监控、控制三大功能,机器人平台依赖于ROS。其架构如下图所示 Img Src: https:/…

第53篇-某商城sign参数分析-webpack【2023-03-07】

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析三、完整代码一、前言 今天再来试一个webpack的例子吧,网址: aHR0cHM6Ly9tLnlxYi5jb20vYmFuay9…

【NLP相关】XLNET原理以及案例

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

类成员的方法

初识对象 生活中或是程序中&#xff0c;我们都可以使用设计表格、生产表格、填写表格的形式组织数据进行对比&#xff0c;在程序中&#xff1a; 设计表格&#xff0c;称之为&#xff1a;设计类&#xff08;class&#xff09; 打印表格&#xff0c;称之为&#xff1a;创建对象 …

C语言--一维数组

数组概念 数组&#xff1a;是一种构造数据类型&#xff0c;用以处理批量的同种类型的数据。 主要特点&#xff1a;数据量大 &#xff0c;类型相同 一维数组的定义 语法&#xff1a; 类型说明符 数组名[整型常量表达式]&#xff1b; 注意&#xff1a; 方括号里面的内容用于指…

css3横向无限公告消息滚动功能

html部分 {{item}}css部分 .boxingeds{ display: flex; flex-wrap: wrap; width: 150%; position: relative; left: 1000rpx; padding: 30rpx 0; position: absolute; top: 23%; z-index: 2; -webkit-animation: myfirst 30s linear 2s infinite; .textname{ display: inlin…

数字三角形 购物单

题目&#xff1a; 题目描述 上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径&#xff0c;把路径上面的数加起来可以得到一个和&#xff0c;你的任务就是找到最大的和。 路径上的每一步只能从一个数走到下一层和它最近的左边的那个数或者右 边…

从0开始自制解释器——实现简单的加法计算器

为什么要学习编译器和解释器呢&#xff1f;文中的作者给出的答案有下面几个&#xff1a; 为了深入理解计算机是如何工作的&#xff1a;一个显而易见的道理就是&#xff0c;如果你不懂编译器和解释器是如何工作的那么你就不明白计算机是如何工作的编译器和解释器用到的一些原理…

InnoDB——详细说明索引中B+树的操作和原理

本内容针对Mysql5.x&#xff1b; 索引是应用程序设计和开发的一个重要方面。 若索引太多&#xff0c;应用程序的性能可能会收到影响。 而索引太少&#xff0c;对查询性能又会产生影响。 索引的注意事项&#xff1a; 如果知道数据的使用&#xff0c;从一开始就应该在需要处添加…

车企数据分类分级的实践指南出炉!“数据安全推进计划”发布,奇点云参编

日前&#xff0c;“数据安全推进计划”&#xff08;DSI&#xff09;正式发布《智能网联汽车数据分类分级实践指南》&#xff08;下文简称“指南”&#xff09;&#xff0c;旨在以合规为主要导向&#xff0c;明确智能网联汽车数据分类分级的方法论&#xff0c;为数据全生命周期的…