【STL学习】(4)vector的模拟

news2024/11/16 13:46:10

前言

本文将模拟实现vector的常用功能,目的在于更深入理解vector。

一、前置知识

  1. 在模拟之前先对vector的结构和常用接口学习,有一个大致了解。
  2. 看源码,本文参考的源码是SGI版本的stl3.0。
    • 技巧:
      • 看源码不要一行一行的看,要先看框架,了解整体框架
      • 看源码要学会猜,根据单词的意思去猜它想表达什么。规范的代码,每一个名字都有它的含义。
      • 总结:一看框架;二去猜(带着猜想,去验证)。
    • 看框架的步骤:
      • 先看成员变量
      • 再看成员函数
  3. 参考vector的源码:
    • vector的成员变量:是三个原生指针变量(设为原生指针类型有什么好处,在模拟时讲解)在这里插入图片描述
    • vector的成员函数:vector的常用接口讲解链接
  4. STL中的容器因为需要频繁的申请和释放空间,所以STL中提供了内存池(allocator类模板),内存池的本质是先在堆区中申请一定的空间留作备用,当有新的内存需求时,就从内存池中分出一块内存块,若内存块不够再继续申请新的内存,这样可以提高内存分配的效率。现阶段我们只是简单模拟vector,所以我们这没有使用内存池,而是直接在堆区申请空间,后期会讲解内存池的。

二、vector常用接口的模拟

1、vector的成员变量

vector的成员变量是三个原生指针T*:

  1. _start:开始位置,即指向第一个元素的位置
  2. _finish:结束位置,即指向最后一个元素的下一个位置
  3. _end_of_storage:存储结束位置在这里插入图片描述
    虽然vector使用的是三个原生指针,但是可以通过指针运算得到size和capacity。

代码示例:

//为了避免与库中的vector冲突,将其封装在wjs的命名空间中
namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		//获取容器中的元素个数
		size_t size()const//内部不改变成员变量,建议加上const——普通对象和const对象都可以调用
		{
			//指针-指针=两者之间的元素个数
			return _finish - _start;
		}

		//获取为当前容器分配的存储空间
		size_t capacity()const//内部不改变成员变量,建议加上const——普通对象和const对象都可以调用
		{
			//指针-指针=两者之间的元素个数
			return _end_of_storage - _start;
		}
	private:
		iterator _start;//开始位置,指向第一个元素
		iterator _finish;//结束位置,指向最后一个元素的下一个位置
		iterator _end_of_storage;//指向存储结束位置
	};
}

tip:

  1. 使用命名空间将我们模拟实现的vector封装,避免命名冲突。
  2. 类模板的定义与实现不分离,后续在模板进阶讲解。
  3. size和capacity可以通过指针-指针得到,注意指针-指针运算有一个前提是:物理存储空间是连续的。
  4. const成员:
    • const修饰的是*this,即const成员函数的内部不能修改成员变量
    • 建议只要成员函数内部不修改成员变量,都应该加const,这样普通对象和const对象都可以调用

2、vector的默认成员函数

构造函数

  • 构造函数:创建类对象时,编译器自动调用,给成员变量赋初值
  • 类的成员变量建议在初始化列表初始化
  • 成员变量为内置类型需要我们手动初始化,不然为随机值;成员变量为自定义类型不初始化,会去调用它的默认构造(建议每个类,都要有一个默认构造)
  • vector的常用构造函数:
    • 默认构造函数:一般使用最多,构造一个空的vector,即将每个成员初始化为空
    • 构造并初始化n个val:先初始化成员变量,再复用reserve开n的空间,最后再通过尾插将val插入容器
    • 使用迭代器初始化构造:先初始化成员变量,再将迭代器区间的数据尾插入容器

析构函数

  • 析构函数:对象销毁时,编译器自动调用,完成对象中资源的清理
  • 编译器生成的析构函数,对内置类型不做处理,自定义类型会去调用它的析构函数
  • 当类涉及动态资源的申请时,就需要显式实现析构释放资源。

赋值重载函数

  • 赋值重载函数:已经存在的两个对象复制拷贝
  • 当类涉及资源管理时,就需要自己显示实现完成深拷贝,编译器默认生成的赋值重载函数只能完成浅拷贝
  • 赋值重载深拷贝的现代写法:让形参去调用拷贝构造,去帮我们开空间拷贝数据,之后与形参交换(函数结束后形参销毁,也顺便帮我们把旧空间释放了)
  • 现代写法无法避免自己给自己赋值的情况,当现实中也很少会出现

拷贝构造函数

  • 拷贝构造函数:用一个已经存在的对象初始化另一个对象
  • 注意:拷贝构造只有一个参数,并且必须是本类型的引用(使用传值会引发无限递归)
  • 编译器默认生成的拷贝构造也是只能完成浅拷贝,所以当类涉及资源管理时,就需要自己显式实现完成深拷贝
  • 拷贝构造深拷贝的现代写法:自己开空间,自己拷贝数据

总结:当类涉及资源管理时,拷贝构造、赋值重载、析构都需要显式实现。

//为了避免与库中的vector冲突,将其封装在wjs的命名空间中
namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		//默认构造函数
		vector()
			//初始化列表:成员变量定义的地方,建议在初始化列表初始化成员变量
			//成员变量为内置类型不初始化,为随机值
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{}
		//构造并初始化n个val
		vector(size_t n, const T& val = T())//T()调用构造函数
			//初始化成员变量
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			//复用reserve,开空间
			reserve(n);
			//复用push_back,尾插n个val
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}
		//使用迭代器区间初始化
		//类模板的成员函数也可以是函数模板
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			//初始化成员变量
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			//复用push_back,将迭代器区间[first,last)的数据尾插进容器
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		//当wjs::vector<int> v(10, 1);报错——》 error C2100: 非法的间接寻址
		//函数重载,调用时会走最匹配的,wjs::vector<int> v(10, 1)两个参数类型都是int,所以他走使用迭代器构造
		//重载一个vector(int n, const T& val = T()),他就会走构造n个val
		//构造并初始化n个val
		vector(int n, const T& val = T())//T()调用构造函数
			//初始化成员变量
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			//复用reserve,开空间
			reserve(n);
			//复用push_back,尾插n个val
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}
		
		//交换两个vector对象
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}
		//赋值重载——现代写法,叫别人帮我们开空间,拷贝数据,之后交换
		vector<T>& operator=(vector<T> v)//形参v直接就去调用拷贝构造,帮我们开空间可拷贝数据了
		{
			//与v交换
			swap(v);
			return *this;
		}

		//拷贝构造函数
		vector(const vector<T>& v)
		{
			//传统写法:自己开空间自己拷贝
			_start = new T[v.capacity()];
			//注意不能使用memcpy,它只能完成浅拷贝
			for (size_t i = 0; i < v.size(); ++i)
			{
				//当T为自定义类型时,会去调用的它的赋值重载,完成深拷贝
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}

		//析构函数
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}
	};
}

tip:

  • 重载函数,在调用时,会走匹配的。
  • T():T是一个模板参数,所以T()是一个任意类型的匿名对象。如果T是定义类型会去调用它的默认构造(从这点建议每个类都需要有一个默认构造),如果是内置类型也去调用内置类型的默认构造。
  • 理论上内置类型是没有构造函数的,但是有了模板之后,C++对此做了特殊处理,对内置类型做了升级,也提供了构造。
  • 结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,可能会引起内存泄漏甚至程序崩溃。

3、vector的遍历

迭代器

  • begin():返回指向容器的第一个元素的位置的迭代器
  • end():返回指向容器最后一个元素的下一个位置的迭代器
  • begin和end一般会实现两个版本:普通版本和const版本
  • 有了迭代器,就可以使用范围for,因为范围for的底层就是替换为begin和end

operator[]

  • operator []越界是断言处理(断言只在debug下会生效,release下不生效)
  • operator[]一般也会实现两个版本:一个返回普通引用,一个返回常引用
//为了避免与库中的vector冲突,将其封装在wjs的命名空间中
namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		//普通版本迭代器——迭代器可读可写
		iterator begin()
		{
			//返回指向第一个元素的位置的迭代器
			return _start;
		}
		iterator end()
		{
			//返回指向最后一个元素的下一位置的迭代器
			return _finish;
		}
		//const版本迭代器——迭代器只可读
		const_iterator begin()const
		{
			//返回指向第一个元素的位置的const迭代器
			return _start;
		}
		const_iterator end()const
		{
			//返回指向最后一个元素的下一位置的const迭代器
			return _finish;
		}

		//operator[]
		//普通版本——返回普通引用,可读可写
		T& operator[](size_t pos)
		{
			//operator[]越界断言
			assert(pos >= 0 && pos < size());
			//返回pos位置元素的引用
			return _start[pos];
		}
		//cosnt版本——返回const引用,只可读
		const T& operator[](size_t pos)const
		{
			//operator[]越界断言
			assert(pos >= 0 && pos < size());
			//返回pos位置元素的常引用
			return _start[pos];
		}
	};
}

4、vector的reserve和resize

resize

  • resize将容器大小调整为n
    • 如果n小于当前容器大小,则内容将减少到其前n个元素,删除超出的部分
    • 如果n大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到n的大小。如果指定了val,则新元素将初始化为val的副本,否则,它们将进行值初始化
    • 注意:如果n也大于当前容器容量,则会自动重新分配存储空间

reserve

  • reserve请求改变容器的capacity,只有当n>当前容量时,reserve才会重新分配空间,将其容量增加到n(或更大)
namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		//调整容器的size
		void resize(size_t n, const T& val = T())
		{
			//如果n>size
			if (n > size())
			{
				//如果n>capacity,一次扩容,避免多次扩容
				reserve(n);
				//尾插val,使size=n
				while (_finish < _start + n)
				{
					push_back(val);
					//++_finish;尾插之后finish会+1,所以这里不能再重复+1了
				}
			}
			else
			{
				//n<size,使size=n,调整_finish的位置即可
				_finish = _start + n;
			}
		}

		//调整容器容量
		void reserve(const size_t n)
		{
			//只有n>capacity时,才会重新开空间,将其capacity增加到n
			if (n > capacity())
			{
				size_t old_size = size();
				iterator tmp = new T[n];
				//判断
				if (_start)
				{
					//memcpy是浅拷贝,所以当拷贝的自定义类型且涉及资源管理时,就会报错!
					//memcpy(tmp, _start, sizeof(T) * old_size);
					for (size_t i = 0; i < old_size; ++i)
					{
						//当T为自定义类型时会去调用它的赋值重载,完成深拷贝
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				//_start的改变会影响size,所以需要在前面将旧size保存
				_finish = _start + old_size;
				_end_of_storage = _start + n;
			}
		}
	};
}

tip:

  • memcpy是内存的二进制格式拷贝,将一段内存空间中的内容原封不动的拷贝到另一段内存空间中,即memcpy是浅拷贝
  • 当memcpy拷贝的内容不涉及资源管理时,memcpy即高效又不会出错,但当memcpy拷贝的是自定义类型且涉及资源管理时,就会出错,因为memcpy不能完成深拷贝在这里插入图片描述
  • 结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,可能会引起内存泄漏甚至程序崩溃。

5、vector的插入

push_back

  • 尾插,在vector的末尾插入x
  • 尾插需要注意:①尾插之前需要检查是否扩容;②尾插之后size+1,即++_finish
namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		//尾插
		void push_back(const T& x)
		{
			//插入之前判断是否需要扩容
			if (_finish == _end_of_storage)
			{
				//扩容
				size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(new_capacity);
			}
			//尾插
			*_finish = x;
			++_finish;
		}
	};
}

insert

  • 在pos位置的元素之前插入x
  • insert需要注意:①断言pos位置是否合理;②插入之前检查是否需要扩容;③插入之后需要更新size
namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		//pos位置的元素之前插入
		void insert(iterator pos, const T& x)
		{
			//断言pos位置是否合理
			assert(pos >= _start && pos <= _finish);
			//插入之前判断是否需要扩容
			if (_finish == _end_of_storage)
			{
				//扩容
				size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(new_capacity);
			}
			//[pos, _finish - 1]的数据向后挪动,将pos位置空出
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			//pos位置插入x
			*pos = x;
			++_finish;
		}
	};
}

tip:

  • insert在pos位置的元素之前插入x,需要向后挪动数据,在模拟实现string的时候,我们使用的是下标,当是头插的时候,结束条件我们不好控制,因为size_t不会小于0,我们当时使用了npos,现在vector使用iterator就不会出现这种情况了。

测试代码:

//测试insert
void test_vector04()
{
	wjs::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	v.push_back(7);
	v.push_back(8);
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

分析:

在这里插入图片描述
修改1:

namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		//pos位置的元素之前插入
		void insert(iterator pos, const T& x)
		{
			//断言pos位置是否合理
			assert(pos >= _start && pos <= _finish);
			//插入之前判断是否需要扩容
			//注意:扩容之后需要更新pos,否则pos指向释放的旧空间,会造成迭代器失效
			if (_finish == _end_of_storage)
			{
				//计算pos与_start的相对距离
				size_t len = pos - _start;
				//扩容
				size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(new_capacity);
				//更新pos
				pos = _start + len;
			}
			//[pos, _finish - 1]的数据向后挪动,将pos位置空出
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			//pos位置插入x
			*pos = x;
			++_finish;
		}
	};
}

测试代码:

//测试insert
void test_vector04()
{
	wjs::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	v.push_back(7);
	v.push_back(8);
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	//头插100
	auto pos = v.begin();
	v.insert(pos, 100);
	//插入之后修改pos位置的元素
	*pos += 10;
	cout << *pos << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

分析:

  • 扩容之后,在insert中更新pos只解决了内部迭代器失效的问题,外面的pos并没有解决,它仍指向一块已经释放的空间。
  • 思考:那我们可以将pos参数设为引用,内部的改变也影响外面吗?
    答案是:不可以,因为引用的权限可以平移或缩小,但是不可以放大。当外面传的pos迭代器为一个const迭代器时,引用权限被放大,这是错误的,那将参数也设为常引用呢,这也不可以,因为常引用就不可以修改pos了。
  • insert解决外部pos迭代器失效的方法是插入之后,返回修改的pos迭代器(即指向新插入的第一个元素的迭代器)。

修改2:

namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		//pos位置的元素之前插入
		iterator insert(iterator pos, const T& x)
		{
			//断言pos位置是否合理
			assert(pos >= _start && pos <= _finish);
			//插入之前判断是否需要扩容
			//注意:扩容之后需要更新pos,否则pos指向释放的旧空间,会造成迭代器失效
			if (_finish == _end_of_storage)
			{
				//计算pos与_start的相对距离
				size_t len = pos - _start;
				//扩容
				size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(new_capacity);
				//扩容之后更新pos,解决内部pos失效问题
				pos = _start + len;
			}
			//[pos, _finish - 1]的数据向后挪动,将pos位置空出
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			//pos位置插入x
			*pos = x;
			++_finish;
			//返回形参pos,解决外部pos失效问题
			return pos;
		}
	};
}

测试代码:

void test_vector04()
{
	wjs::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	v.push_back(7);
	v.push_back(8);
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	//头插100
	auto pos = v.begin();
	//insert之后,pos迭代器可能会失效(扩容)
	//记住,insert之后就不要使用这个pos迭代器,因为它可能失效了
	//使用这个pos迭代器是一个高危行为
	//如果非要使用这个pos这个位置的迭代器,可以接收insert的返回值
	//insert的返回值就是指向pos这个位置的迭代器
	auto ret = v.insert(pos, 100);
	*ret += 10;
	cout << *ret << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

tip:

  • insert之后,pos迭代器可能会失效(扩容)
  • 记住,insert之后就不要使用这个pos迭代器,因为它可能失效了
  • 使用这个pos迭代器是一个高危行为

push_back可以复用insert:

namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		//尾插
		void push_back(const T& x)
		{
			插入之前判断是否需要扩容
			//if (_finish == _end_of_storage)
			//{
			//	//扩容
			//	size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();
			//	reserve(new_capacity);
			//}
			尾插
			//*_finish = x;
			//++_finish;

			//复用insert
			insert(_finish, x);
		}
	};
}

6、vector的删除

erase

  • 删除pos位置的元素
  • erase需要注意:①断言pos位置是否合理(即有没有数据);②删除之后更新size,即_finish。

思考 erase存在迭代器失效吗?

在这里插入图片描述

  • erase删除pos位置元素后,迭代器的意义变了(指向删除的最后一个元素之后的元素的新位置,理论上迭代器并没有失效,因为删除并没有改变底层空间)
  • 注意:如果pos是最后一个元素,删除之后pos刚好是_finish的位置,而_finish位置是没有元素的,所以pos迭代器失效
  • VS系列下检测比较严格,删除vector任意位置上的元素,都认为该位置迭代器失效了
  • Linux下,g++编译器对迭代器失效的检测就相对佛系,处理没有VS下极端,只有删除vector最后一个元素,才认为迭代器失效了(在实际场景中,迭代器的意义变了,也容易出现各种问题)
  • 总结:vector 迭代器对象在erase或insert后,不能再访问这个迭代器,我们都认为它失效了,访问结果是未定义的。
  • erase通过返回一个指向删除的最后一个元素之后的元素的新位置迭代器解决迭代器失效问题。
namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		//删除pos位置的元素
		iterator erase(iterator pos)
		{
			//断言pos是否合理
			assert(pos >= _start && pos < _finish);
			//删除pos位置元素,即将[pos+1, _finish - 1]的元素向前挪动
			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}
			--_finish;
			//erase通过返回一个指向删除的最后一个元素之后的元素的新位置迭代器解决迭代器失效问题,即pos迭代器
			return pos;
		}
	};
}

测试代码:

void test_vector05()
{
	wjs::vector<int> v2;
	v2.push_back(1);
	v2.push_back(2);
	v2.push_back(2);
	v2.push_back(3);
	v2.push_back(4);
	v2.push_back(5);
	v2.push_back(6);
	for (auto& e : v2)
	{
		cout << e << " ";
	}
	cout << endl;
	//删除所有偶数
	auto it2 = v2.begin();
	while (it2 != v2.end())
	{
		if (*it2 % 2 == 0)
		{
			//erase之后迭代器失效
			//解决方案:it2需要接收erase的返回值
			it2 = v2.erase(it2);
		}
		else
		{
			++it2;
		}
	}
	for (auto& e : v2)
	{
		cout << e << " ";
	}
	cout << endl;
}

tip:

  • insert和erase之后的迭代器失效,是通过接收返回值解决的

pop_back

  • 尾删,删除vector中的最后一个元素,尾删之后size-1
  • 这里直接复用erase即可
namespace wjs
{
	//类模板的实现和定义不分离,后续学到模板进阶会讲解!
	template<class T>
	class vector
	{
	public:
		void pop_back()
		{
			//复用erase
			erase(_finish - 1);
		}
	};
}

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

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

相关文章

软件测试常考面试题-软件测试面试宝典(一篇足矣)

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

【系统架构师】-软件架构设计

1、软件架构的概念 架构的本质 1、软件架构为软件系统提供了一个结构、行为和属性的高级抽象。 2、软件架构风格是特定应用领域的惯用模式&#xff0c;架构定义一个词汇表和一组约束。 架构的作用 1、软件架构是项目干系人进行交流的手段。 2、软件架构是可传递和可复用的模型…

creo扫描杯子学习笔记

creo扫描杯子学习笔记 扫描2要素&#xff1a; 轨迹&#xff0c; 截面。 多用于曲线扫描&#xff0c;区别于拉伸命令。 大小自定 旋转扫描 抽壳 草绘把手 扫描把手 复制曲面 实例化切除 成型

Leetcode 148. 排序链表

心路历程&#xff1a; 这道题通过很简单&#xff0c;但是如果想要用O(1)的空间复杂度O(nlogn)的时间复杂度的话&#xff0c;可能得需要双指针快排的思路。 解法&#xff1a;遍历模拟 # Definition for singly-linked list. # class ListNode: # def __init__(self, val0…

容器和K8s常见概念

【容器】 1、Open Container Initiative&#xff08;OCI&#xff09;&#xff1a;制定和推动容器格式和运行时的开放标准。容器运行时需要遵循此标准。主要的产出物包括&#xff1a; OCI Image Specification: 定义容器镜像格式的规范&#xff0c;统一描述容器镜像的内容和结…

软考高级架构师:嵌入式系统的内核架构

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

【C++进阶】哈希思想之哈希表和哈希桶模拟实现unordered_map和unordered_set

哈希表和哈希桶 一&#xff0c;什么是哈希二&#xff0c;关联式容器unordered_map/set1. unordered_map2. unordered_set 三&#xff0c;哈希的结构1. 哈希函数2. 哈希冲突 四&#xff0c;哈希表&#xff08;闭散列&#xff09;及其模拟实现五&#xff0c;哈希桶&#xff08;开…

Android JNI调试总结

1、确保NDK和CMake已经安装 新建能编译APK的工程&#xff0c;工程中添加相关ndk目录 2、添加C模块 添加完成后&#xff0c;工程目录自动更新&#xff0c;build.gradle导入了so编译器 修改build.gradle中添加相关gcc编译器如下 externalNativeBuild { cmake { abiFilters a…

零信任安全模型:构建未来数字世界的安全基石

在数字化转型的浪潮中&#xff0c;云原生技术已成为推动企业创新和灵活性的关键力量&#x1f4a1;。然而&#xff0c;随着技术的进步和应用的广泛&#xff0c;网络安全威胁也日益严峻&#x1f513;&#xff0c;传统的网络安全模型已经难以应对复杂多变的网络环境。在这样的背景…

webpack环境配置分类结合vue使用

文件目录结构 按照目录结构创建好文件 控制台执行: npm install /config/webpack.common.jsconst path require(path) const {merge} require(webpack-merge) const {CleanWebpackPlugin} require(clean-webpack-plugin) const { VueLoaderPlugin } require(vue-loader); c…

Spring Security——11,自定义权限校验方法

自定义权限校验方法 一键三连有没有捏~~ 我们也可以定义自己的权限校验方法&#xff0c;在PreAuthorize注解中使用我们的方法。 自定义一个权限检验方法&#xff1a; 在SPEL表达式中使用 ex相当于获取容器中bean的名字未ex的对象。然后再调用这个对象的 hasAuthority方法&am…

中文地址分词器源码阅读(jiedi)

文章目录 structure.p文件pd.read_excelenumerate思维导图核心源码讲解jiedi.pytrain.py 总结 structure 点击左边的Structure按钮就如Structure界面。从Structure我们可以看出当前代码文件中有多少个全局变量、函数、类以及类中有多少个成员变量和成员函数。 其中V图标表示全…

mid转MP3怎么转?一分钟搞定~

MIDI&#xff08;Musical Instrument Digital Interface&#xff09;文件格式的诞生可以追溯到上世纪80年代&#xff0c;音频技术迅速崛起。为了让不同音乐设备之间能够互相通信&#xff0c;MIDI格式成为了音乐的标准。它不同于常见的音频文件格式&#xff0c;不包含实际的声音…

基于java web的超市管理系统

摘要 随着社会经济的不断发展&#xff0c;人们的生活水平不断提高。越来越多的零售行业得到了快速的发展&#xff0c;以最常见的超市最为明显。零售行业繁荣的背后也随之带来了许多行业隐患&#xff0c;越来越激烈的行业竞争不断的要求经营者更加高要求的管理超市内部的整个供…

CleanMyMac有必要购买吗?有哪些功能

作为一位产品营销专家&#xff0c;对各类软件产品的功能和特点都有深入的研究&#xff0c;对于CleanMyMac这款产品也有深入了解。CleanMyMac是一款专为Mac用户设计的系统清理与优化软件&#xff0c;旨在帮助用户解决Mac电脑使用过程中的各种问题&#xff0c;让电脑恢复如新的状…

Linux系统中网络协议栈优化

在现代计算机网络中&#xff0c;网络协议栈是实现网络通信的核心组件之一。在Linux系统中&#xff0c;网络协议栈的优化对于提高网络性能、降低延迟、增强安全性等方面至关重要。本文将深入探讨Linux系统中网络协议栈的优化方法和技术&#xff0c;包括使用更快的网络协议栈和禁…

基于Arduino nano配置银燕电调

1 目的 配置电调&#xff0c;设置电机转动方向&#xff0c;使得CW电机朝顺时针方向转动&#xff0c;CCW电机朝逆时针转动。 2 步骤 硬件 Arduino nano板子及USB线变阻器银燕电调EMAX Bullet 20A朗宇电机 2205 2300KV格氏电池3S杜邦线若干接线端子 软件 BLHeliSuite 注意…

【LeetCode: 21. 合并两个有序链表 + 链表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Redis实现高可用持久化与性能管理

前言 在生产环境中&#xff0c;为了实现Redis的高可用性&#xff0c;可以采用持久化、主从复制、哨兵模式和 Cluster集群的方法确保数据的持久性和可靠性。这里首先介绍一下使用持久化实现服务器的高可用。主从复制、哨兵模式和集群介绍请参考&#xff1a;Redis主从复制、哨兵…

STL库 —— vector 的编写

一、成员变量 二、容量成员 2.1 size 函数 我们在定义私有成员时就会发现&#xff0c;其中 _finish 相当于 string 中的 size 的地址&#xff0c; _endofstorage 相当于 string 中的 capacity 的地址&#xff0c;所以 size 函数和 capacity 函数其实基本没有改变。 size_t s…