vector实现遇到的问题

news2025/1/13 7:47:33

        前言:vector是表示可变大小数组的序列容器。就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小,下面,我们就来介绍一下,实现vector时可能会遇到的一些问题和接决策略,最终奉上模拟实现代码。

还是老规矩,我们给出实现类的初始代码,以方便更好的阅读后续的代码,该部分的代码不再做过多赘述,有兴趣的读者可以评论:

#pragma once
#include<assert.h>

namespace my_std
{
	template<class T>//vector存储的元素需要用模板来表示
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}
		//默认构造
		vector()//声明中已给出缺省值,可以省略不写
		{}
        ~vector()
		{
			delete[] _start;//采用new/delete的形式存储
			_finish = _endofstorage = nullptr;
		}
		size_t capacity() const //空间容量
		{
			return _endofstorage - _start;
		}
		size_t size() const //数组长度
		{
			return _finish - _start;
		}
        T& operator[](size_t pos)//可读可写
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const//只读
		{
			assert(pos < size());

			return _start[pos];
		}
    private:
		//初始化加上缺省值
		iterator _start=nullptr;//开头
		iterator _finish = nullptr;//结尾加1
		iterator _endofstorage = nullptr;//空间容量
	};
}

目录

1.尾插元素和扩容函数

异地扩容导致原地址失效

2.匿名对象做缺省值的resize函数

3.memcpy-特殊类型的浅拷贝“隐藏杀手”

string类成员的浅拷贝

4.迭代器失效问题

insert函数带来的迭代器失效

erase函数带来的迭代器失效

5.拷贝构造和赋值问题

6.迭代器区间初始化构造

7.完整代码

vector.h

test.cpp


1.尾插元素和扩容函数

异地扩容导致原地址失效

       我们的vector实际上是一个数组容器,也是一个由头尾指针组成的一定长度的顺序表,我们在实际操作时,只能操作头指针或者尾指针中的一个来控制数组,另一个指针是随着操作的指针变化而变化的,我们只需要知道数组长度即可,但是,在当前空间不够用的情况下,又采取异地扩容,可能会导致头尾指针分离一段时间,如果不能察觉,就会导致数组出现错误:

void reserve(size_t n)//扩容函数
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t sz = size();

				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}

					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}
void push_back(const T& x)
		{
            //如果空间满了先扩容
			size_t n = _finish - _start;//提前算出保存,避免后序因异地扩容而导致原地址失效
			if (_finish == _endofstorage)
			{
				size_t len = capacity() == 0 ? 4 : capacity() * 2;
				T* temp = new T[len];
				if (_start)//如果原来有内容
				{
					memcpy(temp, _start, sizeof(T)*n);
					delete[] _start;
				}
				_start = temp;
				_finish = _start + n;
				_endofstorage = _start + len;
				//reserve(len);如果复用将从T* temp开始到_endofstorge=_start+len;注释即可
			}
			*_finish = x;
			++_finish;
			//insert(end(), x);
		}

2.匿名对象做缺省值的resize函数

     如果我们调用resize函数,其实就是重新给数组分配一定的空间,那么,这个空间内部的元素该如何初始化呢,这一般都由输入者自定义的类型来,这样一来,我们就不能将缺省值特别规定为int或者别的类型,而是使用模板,按照代码编辑者需要的类型自动生成对应的缺省值类型即可,并且,在C++中,我们的内置类型(int,double)等也有自己的构造函数,同时,我们可以将参数匿名对象设为const,这样一来可以延长匿名对象的使用周期到其使用结束后销毁。

void resize(size_t n,const T& val=T())//匿名对象表示填入的缺省值,其有可能是整形,字符串,甚至是vector等,所以采用匿名对象来调用默认构造,而const可以延长匿名对象的生命周期到不用之后再销毁
		{
			if (n <= size())//不大于尾部不用变
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);//在reserve函数内部判断是否需要扩容,并返回扩容后的空间,_finish指针不会改变相对于_start的距离
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

3.memcpy-特殊类型的浅拷贝“隐藏杀手”

memcpy函数的简介:

1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中

2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且 自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

string类成员的浅拷贝

       我们知道string类对象的字符串并不是存放在对象本身的空间里,而是存放在对象的数据成员所指向的堆中,所以在使用memcpy时,就会导致只是将string对象的数据成员原封不动的复制拷贝下来,相当于其指向的string对象的成员的数据还是保存在原来的堆空间中,并没有发生深拷贝,出现了两个指针指向同一处的情况,这就会在原拷贝对象在析构时产生重复析构的错误。

     解决办法也比较简单,那就是用我们的笨方法,采用循环的方式将原对象一一拷贝到新的空间里来就行了,当然,这种错误的出现只会发生在需要扩容的情况下,所以,我们只需要调整扩容函数即可。

void reserve(size_t n)//扩容函数
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t sz = size();

				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);//自定义对象造成异地扩容浅拷贝
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}

					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

4.迭代器失效问题

迭代器失效的特征:

1.如果迭代器失效了,就不能再使用这个迭代器;

2.如果使用了这个迭代器,其结果将会是未定义的。

insert函数带来的迭代器失效

        在vector中,我们使用的一般都是迭代器的位置来表示元素的位置,像在某个元素的位置后面插入一个元素的函数,其定义一般为void insert(iterator pos, const T& x);,那么就可能会发生如下的场景,

       在这样的场景下,插入元素势必就会导致出错,所以,其中一个方法就是,我们记住pos与_start的相对偏移量,在扩容后数组移动到新的空间之后再将pos也更新到新的位置即可。

void insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);//等于_finish相当于尾插
			//首先检查是否空间满了,满了就要先开空间
			if (_finish == _endofstorage)
			{
				size_t len1 = pos - _start;
				cout <<"扩容前待插入位置相对于begin的偏移量为"<< len1 << endl;
				reserve(capacity() == 0 ? 4 : capacity() * 2);//注意,扩容之后会导致迭代器发生变化,也将会导致原来传入的参数失效
				size_t len2 = pos - _start;
				cout <<"扩容后待插入位置相对于begin的偏移量为"<< len2 << endl;
				pos = _start + len1;
			}
			iterator end = _finish-1;
			_finish++;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
		}

上述的做法还有些许不妥之处,如果我们有如下的需求:

这样的话,返回的迭代器还能使用吗

       首先,我们知道insert函数在内部修改了传入的迭代器,但是这和实参并没有什么关系,因为我们的insert函数是传值传参,众所周知,传值传参不影响实参,所以,一旦调用了insert函数,迭代器就会出现诸多的可能性,也就是迭代器失效了。

为何不建议传引用传参

      针对上面的问题,有的人可能会想到传引用传参,但是,我们得形参是实参的一份拷贝,而实参又是一个常量,形参作为临时变量而具有了常性,所以不能被修改,逻辑上也就不对了,如果再将引用加上const,那我们在insert函数内部就不能对参数迭代器再进行修改了,所以,对于insert函数来说,正常的使用我们一般选择传值传参,但是其使用会导致迭代器失效的问题,这一点,我们需要注意。


erase函数带来的迭代器失效

     vector的删除函数比较好写,我们传入迭代器参数,因为不涉及扩容问题,自然也就不存在上面的insert函数造成的迭代器失效的类型的问题,我们直接在原来的空间上进行移动覆盖即可,这里详细的也不再赘述:

void erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);//注意不能等于_finish,因为_finish指向的是尾部元素的下一个元素,该处本来没有元素所以也不能删除
			iterator it = pos + 1;
			while (it != end())
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			
		}

那删除完之后的迭代器失效了吗?

       我们不妨来实测一下,我们取三组样例,分别对样例中的偶数元素进行删除,分析并查看其结果:

       为什么会有这样的结果呢,其实重点就在迭代器的位置,我们知道,当迭代器判断到一个元素符合条件时,会执行删除指令,这个指令也就是我们上面的erase函数,其本质上是向前挪动数据覆盖达到删除效果,而此时的迭代器在删除完元素后其实是指向了被删除元素的下一个元素,下一步我们就需要判断该元素,如果此时仍然让迭代器++的话,就会导致上一个被删除元素后面紧挨着的下一个数据被跳过了判断,也就出现了错误问题。

      解决办法其实也简单,就是我们在判断符合条件时不执行迭代器++操作,只有当该元素不符合删除条件时才执行迭代器++操作,这样就可以保证每个元素都能够被判断到,也就解决了错误。

      那,如果我想访问这个迭代器,就真没办法了吗?别急,还真有,我们来看官方文档对于迭代器失效问题的解决方案:

      官方的意思是,让erase函数返回一个迭代器,这个迭代器指向上一个已删除元素的下一个元素,其实还是原来的那个迭代器的位置。  

iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);//注意不能等于_finish,因为_finish指向的是尾部元素的下一个元素,该处本来没有元素所以也不能删除
			iterator it = pos + 1;
			while (it != end())
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;//删除元素的下一个位置,其实还是该迭代器的位置,因为向前挪动覆盖,原来迭代器的位置上删除后存储的就是下一个元素
		}
void test_vector5()
	{
		// 1 2 3 4 5
		// 1 2 3 4 5 6
		// 2 2 3 4 5
		std::vector<int> v;//调用库里的vector,默认原来的erase迭代器失效
		v.push_back(2);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		//v.push_back(6);

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		auto it = v.begin();
		while (it != v.end())
		{
			
			if (*it % 2 == 0)
			{
				it=v.erase(it);//让迭代器重新赋值,再次有效
			}
			else
			    ++it;
		}

5.拷贝构造和赋值问题

      对于自己写的vector,如果不单独写拷贝构造函数,那么将会使用默认的拷贝函数,也就会造成浅拷贝的情况,和赋值操作同理,所以,我们也是需要编写拷贝构造函数和赋值函数,这部分在string的部分已经详细展开,这里也不再赘述,

//拷贝构造函数
		vector(const vector<T> & x)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(x.capacity());//修改空间为x的空间
			for (auto& e : x)
			{
				push_back(e);//赋值即可
			}
		}
//赋值操作
		// v1 = v3
		void swap(vector<T>& v)//写成vector &x也可以
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		vector<T>& operator=(vector<T> tmp)
		{
			swap(tmp);//交换后tmp为临时变量后续自动销毁
			return *this;
		}


6.迭代器区间初始化构造

在官方的vector文档中,我们还可以看到vector构造函数的另外l两种初始化方案:

 在上面实现的功能的基础上,想实现这两个构造函数并不能,这里给出一种实现方案:

template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
				push_back(val);
		}

但是如果我们来整上一个测试样例:vector<int> v0(10, 0);显然我们的目的是想让编译器给我们初始化10个空间,并且全都初始化为0,但是,编译器真的会乖乖的听我们的吗?

         很显然出错了,编译器不听我们的,因为,编译器不知道该匹配哪个了?因为这两个数据参数都可以看做同一个类型,所以,它也符合模板的迭代器初始化的函数的参数列表,所以,编译器就会调用最匹配的那一个函数来构造:

       这种情况下,官方采用的是将 vector(size_t n, const T& val = T())再次重载,使其更加符合我们类型,比如重载为vector(int n, const T& val = T()),就可以解决我们上面的错误了,但不过说实话,这种方式的代码复用性比较差,有点面向样例编程的滋味,但是目前也没有好的解决办法了。

7.完整代码

vector.h

#pragma once
#include<assert.h>
#include <vector>

namespace my_std
{
	template<class T>
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}
		//默认构造
		vector()//声明中已给出缺省值,可以省略不写
		{}
		//拷贝构造函数
		vector(const vector<T> & x)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(x.capacity());//修改空间为x的空间
			for (auto& e : x)
			{
				push_back(e);//赋值即可
			}
		}
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
				push_back(val);
		}
		vector(int n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
				push_back(val);
		}
		//赋值操作
		// v1 = v3
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		vector<T>& operator=(vector<T> tmp)
		{
			swap(tmp);//交换后tmp为临时变量后续自动销毁
			return *this;
		}

		~vector()
		{
			delete[] _start;//采用new/delete的形式存储
			_finish = _endofstorage = nullptr;
		}
		size_t capacity() const //空间容量
		{
			return _endofstorage - _start;
		}
		size_t size() const //数组长度
		{
			return _finish - _start;
		}
		void reserve(size_t n)//扩容函数
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t sz = size();

				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);//自定义对象造成异地扩容浅拷贝
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}

					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}
		void resize(size_t n,const T& val=T())//匿名对象表示填入的缺省值,其有可能是整形,字符串,甚至是vector等,所以采用匿名对象来调用默认构造,而const可以延长匿名对象的生命周期到不用之后再销毁
		{
			if (n <= size())//不大于尾部不用变
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);//在reserve函数内部判断是否需要扩容,并返回扩容后的空间,_finish指针不会改变相对于_start的距离
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
		void push_back(const T& x)
		{
            //如果空间满了先扩容
			size_t n = _finish - _start;//提前算出保存,避免后序因异地扩容而导致原地址失效
			if (_finish == _endofstorage)
			{
				size_t len = capacity() == 0 ? 4 : capacity() * 2;
				//T* temp = new T[len];
				//if (_start)//如果原来有内容
				//{
				//	memcpy(temp, _start, sizeof(T)*n);
				//	delete[] _start;
				//}
				//_start = temp;
				//_finish = _start + n;
				//_endofstorage = _start + len;
				reserve(len);
			}
			*_finish = x;
			++_finish;
			//insert(end(), x);
		}
		void insert(iterator pos, const T& x)//而且此处不能加引用
		{
			assert(pos >= _start && pos <= _finish);//等于_finish相当于尾插
			//首先检查是否空间满了,满了就要先开空间
			if (_finish == _endofstorage)
			{
				size_t len1 = pos - _start;
				cout <<"扩容前待插入位置相对于begin的偏移量为"<< len1 << endl;
				reserve(capacity() == 0 ? 4 : capacity() * 2);//注意,扩容之后会导致迭代器发生变化,也将会导致原来传入的参数失效
				size_t len2 = pos - _start;
				cout <<"扩容后待插入位置相对于begin的偏移量为"<< len2 << endl;
				pos = _start + len1;
			}
			iterator end = _finish-1;
			_finish++;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
		}
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);//注意不能等于_finish,因为_finish指向的是尾部元素的下一个元素,该处本来没有元素所以也不能删除
			iterator it = pos + 1;
			while (it != end())
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;//删除元素的下一个位置,其实还是该迭代器的位置,因为向前挪动覆盖,原来迭代器的位置上删除后存储的就是下一个元素
		}
		T& operator[](size_t pos)//可读可写
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const//只读
		{
			assert(pos < size());

			return _start[pos];
		}
	private:
		//初始化加上缺省值
		iterator _start=nullptr;//开头
		iterator _finish = nullptr;//结尾加1
		iterator _endofstorage = nullptr;//空间容量
	};
	void test1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);


		for (size_t i = 0; i < v.size(); i++)
			cout << v[i] << " ";
		cout << endl;
		for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
		{
			*it *= 10;
			cout << *it << " ";
		}
		cout << endl;
		for (auto e : v)
			cout << e << " ";
		cout << endl;

	}
	void test_vector2()
	{
		int i = 0;
		int j(1);//可见内置类型在模板中是存在默认构造函数的
		int k = int(2);

		vector<int*> v1;
		v1.resize(10);

		vector<string> v2;
		//v2.resize(10, string("xxx"));
		v2.resize(10, "xxx");//单参数的构造函数支持隐式类型的转换

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_vector3()
	{
		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);

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int>::iterator it = v.begin() + 2;
		v.insert(it, 30);

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		//v.insert(v.begin(), 30);
		v.insert(v.begin() + 3, 30);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_vector4()
	{
		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);

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		auto pos = v.begin();
		v.erase(pos);

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		v.erase(v.begin() + 3);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_vector5()
	{
		// 1 2 3 4 5
		// 1 2 3 4 5 6
		// 2 2 3 4 5
		std::vector<int> v;//调用库里的vector,默认原来的erase迭代器失效
		v.push_back(2);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		//v.push_back(6);

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		auto it = v.begin();
		while (it != v.end())
		{
			
			if (*it % 2 == 0)
			{
				it=v.erase(it);//让迭代器重新赋值,再次有效
			}
			else
			    ++it;
		}

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_vector6()
	{
		vector<string> v;
		v.push_back("111111111111111111111");
		v.push_back("111111111111111111111");
		v.push_back("111111111111111111111");
		v.push_back("111111111111111111111");
		v.push_back("111111111111111111111");


		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_vector7()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(1);

		vector<int> v2(v1);

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v3;
		v3.push_back(10);
		v3.push_back(20);
		v3.push_back(30);
		v3.push_back(40);

		v1 = v3;

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_vector8()
	{
		vector<int> v0(10, 0);
		vector<string> v1(10, "xxxx");

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2;
		v2.push_back(10);
		v2.push_back(20);
		v2.push_back(30);
		v2.push_back(40);

		vector<int> v3(v2.begin(), v2.end());

		string str("hello world");
		vector<int> v4(str.begin(), str.end());
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;

		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

test.cpp

#include<iostream>
using namespace std;
#include "vector.h"
int main()
{
	//my_std::test1();
	my_std::test_vector8();



	return 0;
}

       

        每个人都有一段异常艰难的时光,生活的压力,工作的失意,学业的压力,爱的惶惶不可终日,如果此刻不太顺利的话,一定是有十倍百倍的运气在前方等着你!不要怀疑自我,大步往前走,以后会很好很好的。

    

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

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

相关文章

【C/C++】课程设计:通讯录管理系统源码,C语言链表实现

大家好呀&#xff0c;亲爱的小伙伴们&#xff01;你们今天有在编写代码吗&#xff1f; 如果有熟悉的小伙伴看到我&#xff0c;就会知道又到了学习源码项目的好时机了&#xff01;没错&#xff0c;今天要分享的同样是一个经典的管理系统项目&#xff1a;通信录管理系统&#xf…

小白学Linux都能学会

文章目录 1. 初识Linux1.1 操作系统1.2 Linux发展历程1.3 Linux简介1.3.1 什么是 Linux1.3.2 Linux的特点 1.4 Linux和Unix区别1.5 Linux和Windows区别1.6 Linux发行商和常见发行版1.7 Linux 应用领域**1.8 Linux之CentOS**1.9 总结 2. 系统与设置命令2.1 学习命令的原因2.2 Li…

7天GMV达220万美元!TikTok Shop爆品榜出炉。

7天GMV达220万美元&#xff01;TikTok Shop爆品榜出炉 8月28日消息&#xff0c;据跨境指南联合TikTok数据分析平台EchoTik发布的数据&#xff0c;监测了上周TikTok Shop印尼、马来西亚、泰国、美国市场GMV前10的商品。上周在印尼市场GMV排名前10的商品中&#xff1a;FREE ONGK…

Kotlin协程flow缓冲buffer

Kotlin协程flow缓冲buffer 先看一个普通的flow&#xff1a; import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import kotlin.system.measureTimeMillisfun main(args: Array<String>) {val delayTime 100Lru…

初试nacos服务注册中心

项目基本流程是想获取订单信息的同时获取用户信息&#xff0c;所以order服务提供nacos获取user服务提供的用户信息。 启动nacos服务 在bin目录下的cmd命令行下执行 startup.cmd -m standalone 单机部署 启动服务 服务注册到nacos Nacos是SpringCloudAlibaba的组件&#xff0c…

Yolov8小目标检测(12):动态稀疏注意力BiFormer | CVPR 2023

💡💡💡本文改进:动态稀疏注意力,cvpr2023。 BiFormer | 亲测在红外弱小目标检测涨点,map@0.5 从0.755提升至0.758 💡💡💡Yolo小目标检测,独家首发创新(原创),适用于Yolov5、Yolov7、Yolov8等各个Yolo系列,专栏文章提供每一步步骤和源码,带你轻松实现小…

【GPT,Flask】用Python Flask结合OpenAI的GPT API构建一个可自主搭建的内容生成应用网站

【背景】 自己构建模型并进行训练需要很高的知识,技能和资源门槛。如今,通过OpenAI提供的API,则可以快速通过GPT能力构建可以提供内容生成服务的在线网站。这套框架可以提供给用户,用户可以利用该框架在自己的环境(比如自己的公司内)构建内容生成服务。你也可以自己上线…

【C++入门】模版初阶(泛型编程)

目录 1.泛型编程2.函数模版2.1函数模版的概念2.2函数模版的使用2.3函数模版的原理2.4函数模版的实例化2.5 模板参数的匹配原则 3.类模版3.1类模版的定义格式3.2类模版的实例化 1.泛型编程 让我们思考一个小问题&#xff1a;如何实现一个通用的交换函数呢&#xff1f; 在解决这…

自动泊车的自动驾驶控制算法

1. 自动泊车系统 自动泊车系统(AutomatedParkingASSiSt,APA)利用车辆搭载的传感器感知车辆周边环境,扫描满足当前车辆停放的障碍物空间车位或线车位,并通过人机交互(HumanMachine Interface,HMI)获取驾驶员对目标车位的选择或自动确定目标车位,自动规划泊车路径,通过控制器向车…

【C++笔记】C++内存管理

【C笔记】C内存管理 一、C中动态内存申请的方式二、new和delete的实现原理2.1、operator new和operator delete函数 一、C中动态内存申请的方式 在C语言中我们需要动态申请空间的时候我们通常都是用malloc函数&#xff0c;但是malloc函数对自定义类型是没什么问题的&#xff0…

ATA-2161高压放大器的电子实验案例(案例合集)

ATA-2161是一款理想的可放大交直流信号的单通道高压放大器。最大差分输出1600Vp-p(800Vp)高压&#xff0c;可以驱动高压型负载。凭借其优异的指标参数受到不少电子工程师的喜欢&#xff0c;其在电子实验中的应用也非常频繁&#xff0c;下面为大家整理出ATA-2161高压放大器的应用…

无涯教程-Android - Intents/Filters

Android Intent 是要执行的操作的抽象描述。它可以与 startActivity 一起启动Activity&#xff0c;将 broadcastIntent 发送给任何BroadcastReceiver组件&#xff0c;并与 startService(Intent)或 bindService(Intent&#xff0c;ServiceConnection&#xff0c;int)与后台服务进…

jq——点击显示隐藏来回切换、图片来回切换

案例展示 案例代码 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>显示隐藏</title></head><script src"js/jquery.js"></script><style>.switch {width: 50px;height: 50px;…

antd table minHeight

网上常见设置antd table最小宽度的方案&#xff1a; 这种方法也不是不可以&#xff0c;但是若需要动态设置最小高度的话&#xff0c;这样写就不是很合适。 所以这边选择动态计算table高度的方法来实现&#xff1a; 第一步&#xff1a;计算table scroll height const getTab…

「CSS|前端开发|页面布局」03 开发网站所需要知道的CSS:如何实现你想要的页面布局

本文主要介绍如何分析页面布局&#xff0c;了解HTML标签元素的默认布局以及如何修改标签元素的布局方式&#xff0c;最终能够结合CSS框架实现任意我们看到或者想到的页面布局。 文章目录 本系列前文传送门一、场景说明二、页面布局设计逻辑三、CSS布局编写逻辑HTML元素的默认布…

【附安装包】CorelCAD2023安装教程

软件下载 软件&#xff1a;CorelCAD版本&#xff1a;2023语言&#xff1a;简体中文大小&#xff1a;534.17M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.bai…

证券型代币成为新焦点!交易上链,合规也要上链?

“数字化正在使传统行业的边界变得模糊&#xff0c;这是一场真正的金融革命。”麦肯锡早在2017年的《在没有边界的世界中竞争》报告中就以此形容了数字化浪潮。随着对虚拟资产的全球接受度增加&#xff0c;监管机构也开始对其潜力展开讨论。 当代币被视为金融工具时&#xff0c…

java对时间序列每x秒进行分组

问题&#xff1a;将一个时间序列每5秒分一组&#xff0c;返回嵌套的list&#xff1b; 原理&#xff1a;int除int会得到一个int&#xff08;也就是损失精度&#xff09; 输入&#xff1a;排序后的list&#xff0c;每几秒分组值 private static List<List<Long>> get…

Yolov8-pose关键点检测:模型轻量化创新 | PConv结合c2f | CVPR2023 FasterNet

💡💡💡本文解决什么问题:新的partial convolution(PConv),通过同时减少冗余计算和内存访问可以更有效地提取空间特征。 PConv| GFLOPs从9.6降低至8.5,参数量从6482kb降低至6134kb, mAP50从0.921提升至0.925 Yolov8-Pose关键点检测专栏介绍:https://blog.csdn.n…