C++——模拟实现vector

news2024/11/24 2:46:36

1.查看vector的源代码

2.模拟实现迭代器

#pragma once

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:
		typedef T* iterator;//typedef会受到访问限定符的限制
		typedef const T* const_iterator;
		//const迭代器是指向的对象不能修改,否则岂不是遍历时it都不能++了

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(2);
		v.push_back(3);
		v.push_back(3);

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

		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;

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

}

3.成员函数

3.1构造和析构

#pragma once

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

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

		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

3.2拷贝构造

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

		//vector的拷贝构造
//注意:vector中拷贝数据,数据是内置类型才可以使用memcpy
//否则比如是string,又会出现深层次的浅拷贝问题
		
        vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)//这里不初始化的话,去reserve时会是随机值
		{
			reserve(v.capacity());//capacity函数也要加上const
			for (auto& e : v)
			{
				push_back(e);
			}
		}
		//注意使用引用,T是小对象还好,如果是大对象,代价就大了

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector8()
	{
		//不写拷贝构造,编译器默认是浅拷贝
		//所以不写拷贝构造时,程序会崩溃
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		vector<int> v2(v1);
		//vector<int>、vector<string>不是同一个类型

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

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

	}
}

3.3赋值重载

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

		void swap(vector<T>& tmp)
		{
			std::swap(_start, tmp._start);//不加std找不到库中的swap
			std::swap(_finish, tmp._finish);
			std::swap(_end_of_storage, tmp._end_of_storage);

		}

		//赋值重载
		vector<T>& operator=(vector<T> tmp)//调用赋值要先传参,v3传给tmp是拷贝构造
		{
			swap(tmp);
			return *this;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector8()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		for (auto e : v1)
		{
			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;
	}
}

3.4.补充:类模板的一个奇怪用法

我们可以发现,库中的拷贝构造x的类型写的是vector,而不是vector<T>

已知类名不是类型,然而事实上,在类里面,要使用类型的话,使用vector也可以,但不建议这么使用

3.5使用迭代器区间去构造

namespace jxy
{

	template <class T>
	class vector
	{
	public:

		//注意:在一个类模板里面还可以正常写模板函数

		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		//VS2019的编译器对内置类型进行了处理,初始化成了0,所以这里没有问题
		//但内置类型编译器不一定会处理,所以需要自己写初始化列表
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

	private:
		iterator _start = nullptr;//这里把缺省值给上就不需要写初始化列表了
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	void test_vector9()
	{
		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;
	}
}

为什么要写成模板函数?

因为如果这里使用iterator而不使用模板的话,那么vector使用该构造时就只能使用vector类型的迭代器区间来初始化。而如果写成模板的话,就可以使用各种类型的迭代器来初始化了,只要数据类型匹配就可以。

3.6使用n个val去构造

namespace jxy
{

	template <class T>
	class vector
	{
	public:

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

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	void test_vector9()
	{
		vector<int> v0(10, 0);//但这里会报错
		vector<string> v1(10, "xxxx");//这里正常

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

所以我们可以给int类型单独写一个重载函数来解决问题

//有更匹配的之后,就不会去实例化模板了
vector(int n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

4. Capacity

4.1 size和capacity

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

		size_t size()
		{
			return _finish - _start;
		}

		size_t capacity()
		{
			return _end_of_storage - _start;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

4.2reserve

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
                //库里面这里是内存池来的
				if (_start)//旧空间有可能为空
				{
					memcpy(tmp, _start, sizeof(T)*size());
					delete[] _start;
				}

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

4.3resize

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

		//void resize(size_t n,T value=T())
		void resize(size_t n, const T& value = T())
		{ 
			if (n <= size())
			{
				_finish = _start + n;

			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = value;
					_finish++;
				}
			}
		}

	};

	void test_vector3()
	{
		vector<string> v1;
		v1.resize(10);
		//空的string不会打印出来东西

		//v1.resize(10, string("xxx"));
		//可以填写匿名对象

		//v1.resize(10, "xxx");
		//甚至还可以这样写,因为单参数构造函数支持隐式类型转换

		vector<int*> v2;
		v2.resize(5);

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

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

	}

}

关于resize的参数

void resize(size_t n,T value=T())
这里缺省值不能给0,因为T的类型不能确定就是int
它也可以是double、string等等,所以这里是不能给固定值的
T类型的匿名对象,本质是调用默认构造,然后调用拷贝构造,优化为直接构造

void resize(size_t n, const T& value = T())
这样写就是创建一个匿名对象,然后去引用它
const引用会延长匿名对象的生命周期,延长到val不使用了,就结束了
匿名对象、临时对象具有常性,必须加const
            
这里缺省值是调用默认构造生成的值
那么问题又来了,如果T是自定义类型,那么匿名对象是有构造函数的
那如果T是内置类型呢?
比如是int,它又没有构造函数,怎么办?

事实上,模板设计出来以后,C++语法进行了升级
内置类型也可以认为有构造函数,否则泛型编程就无法使用

void test_vector2()
    {//这样写是可以的
        int a = 0;
        int b(1);
        int c = int(2);

        //在面向对象编程中,可以说一切都是对象
    }

5. Element access

5.1 operator[]

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

		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;
		iterator _finish;
		iterator _end_of_storage;
	};

}

6. Modifiers

6.1 push_back

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)//判断容量是否已满
			{
				size_t sz = size();
				size_t cap = capacity() == 0 ? 4 : capacity() * 2;
				//容量为0就开空间,否则就2倍扩容,然后拷贝数据,释放旧空间
				T* tmp = new T[cap];
				if (_start != nullptr)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;

				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + cap;
			}

		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

}

还可以去复用reserve来实现

void push_back(const T& x)
{

	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}

	*_finish = x;
	_finish++;

}

6.2 insert

错误示范:

namespace jxy
{
	//模板尽量不要分离编译
	template <class T>
	class vector
	{
	public:

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

			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity()*2);

			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}

			*pos = val;
			_finish++;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector4()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(2);
		v.push_back(3);
		v.push_back(3);

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

		v.insert(v.begin() + 2, 100);

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

		//头插
		//这里的头插就不需要单独处理了,因为pos不可能是0
		v.insert(v.begin(), 100);

		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(2);
		v.push_back(3);
		v.push_back(3);
		v.push_back(4);
		v.push_back(4);

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

        //第一次插入数据
		v.insert(v.begin() + 2, 100);

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

        //第二次插入数据
		v.insert(v.begin(), 100);

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

        //此时push_back如果去复用insert,程序会崩溃
	}

6.2.1迭代器失效:野指针

此时就涉及到一个问题,迭代器失效。

insert的正确写法:

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

	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity()*2);
		pos = _start + len;
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
	    *(end + 1) = *end;
		end--;
	}

	*pos = val;
	_finish++;
}

6.3 erase

namespace jxy
{

	template <class T>
	class vector
	{
	public:

		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator it = pos + 1;
			while (it < _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			_finish--;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector5()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(2);
		v.push_back(3);
		v.push_back(3);
		v.push_back(4);
		v.push_back(4);

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

		vector<int>::iterator pos = v.begin();

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

}

6.3.1迭代器失效

何为迭代器失效?

迭代器失效就是不能再使用这个迭代器了,如果使用了,那么结果就是未定义的。

迭代器失效就不能使用它来访问了。

void test_vector6()
{
	//想要删除所有偶数
	std::vector<int> v;

	//1.
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//2.
	//v.push_back(1);
	//v.push_back(2);
	//v.push_back(3);
	//v.push_back(4);
	//v.push_back(5);
	//v.push_back(6);
	//此时多插了个数据就会出问题

	//3.
	//v.push_back(2);
	//v.push_back(2);
	//v.push_back(3);
	//v.push_back(4);
	//v.push_back(5);
	//此时程序可以正常运行,但是结果不对,偶数没有删除完

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

	auto it = v.begin();
	while (it != v.end())
	{
//VS2019会进行强制检查
//erase以后认为it就失效了,不允许访问,访问就会报错
		if ((*it%2) == 0)
		{
			v.erase(it);
		}
		it++;
	}

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

6.3.2解决方案

库里erase的实现:

所以要对测试代码进行改进

void test_vector6()
{
	//想要删除所有偶数
	std::vector<int> v;

	v.push_back(1);

	v.push_back(2);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(8);
	v.push_back(8);

	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;
}

此时库中的erase也可以正常使用了。

再来修改下我们模拟实现的erase

namespace jxy
{

	template <class T>
	class vector
	{
	public:

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator it = pos + 1;
			while (it < _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			_finish--;
			return pos;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector6()
	{
		vector<int> v;

		v.push_back(1);
		v.push_back(2);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.push_back(8);
		v.push_back(8);

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

		auto it = v.begin();
		while (it != v.end())
		{
			if ((*it%2) == 0)
			{
				it=v.erase(it);
//或者erase不去更改,让它的返回值类型依旧是void,这里写作 v.erase(it);  也可以
			}

			else
			{
				it++;//不删除时才去++
			}
			
		}

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

}

6.4总结

使用insert和erase后,我们都认为迭代器失效,不能再访问,使用的话结果是未定义的。

如果想继续使用,可以使用返回值来控制

7.关于深层次的浅拷贝问题

7.1引入

namespace jxy
{

	template <class T>
	class vector
	{
	public:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)//旧空间有可能为空
				{
					memcpy(tmp, _start, sizeof(T)*size());
					delete[] _start;
				}

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

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector7()
	{
		//深拷贝问题

		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;

	}

}

这是为什么?

7.2解决方案

经过分析可知,主要的问题就是出在memcpy上,那么应该如何改写代码,使得string能够进行深拷贝呢?

实际上只要把memcpy改写成for循环,调用string的赋值就可以解决问题了。

namespace jxy
{

	template <class T>
	class vector
	{
	public:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)//旧空间有可能为空
				{
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
						//string的赋值重载就是深拷贝
					}
					delete[] _start;
				}

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

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector7()
	{
		//深拷贝问题

		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;

	}

}

进一步改进:

这里直接写深拷贝,还是有些浪费,拷贝数据,还要释放旧的空间

如果string使用引用计数的浅拷贝,就非常有价值了。

8.练习题

17. 电话号码的字母组合 - 力扣(LeetCode)

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

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

相关文章

UE5学习笔记23-给角色添加血量,添加收到伤害的功能

零、一些游戏框架知识 1.UE5中包含游戏模式类(Game Mode)、游戏状态类(Game State)、玩家状态类(Player State)、玩家控制器类(Player Controller)、所有的可以被控制的实体或角色类(Pawn)、窗口类(HUD/Widget) Game Mode&#xff1a;存在在服务器上&#xff0c;当我们在客户端…

扣绩效工资,违反劳动法吗?

将工资拆分成绩效工资和岗位工资是很多公司管理员工的一种常见方式。 其中绩效工资跟KPI强挂钩&#xff0c;如果当月没有达到公司绩效标准&#xff0c;那么公司就会扣绩效工资。 那扣绩效工资违反劳动法吗&#xff1f;HR应该如何进行绩效薪酬考核和发放&#xff1f; 扣绩效工…

【网络安全】Cookie与ID未强绑定导致账户接管

未经许可,不得转载。 文章目录 前言正文前言 DigiLocker 是一项在线服务,旨在为公民提供一个安全的数字平台,用于存储和访问重要的文档,如 Aadhaar 卡、PAN 卡和成绩单等。DigiLocker 通过多因素身份验证(MFA)来保护用户账户安全,通常包括 6 位数的安全 PIN 和一次性密…

大论文记录

基础知识回顾 1.强化学习&#xff08;Agent、Environment) 在 RL 中&#xff0c;代理通过不断与环境交互、以试错的方式进行学习&#xff0c;在不确定性下做出顺序决策&#xff0c;并在探索&#xff08;新领域&#xff09;和开发&#xff08;使用从经验中学到的知识&#xff…

五、Java 注释

一、Java 注释 在计算机语言中&#xff0c;注释是计算机语言的一个重要组成部分&#xff0c;用于在源代码中解释代码的作用&#xff0c;可以增强程序的可读性&#xff0c;可维护性。Java 注释是一种在 Java 程序中用于提供代码功能说明的文本。注释不会被编译器包含在最终的可…

数据清洗第3篇章 - 数据异常处理

数据清洗是数据分析过程中至关重要的一步&#xff0c;它确保数据的准确性、一致性和完整性。这不仅有助于提高分析结果的可靠性和有效性&#xff0c;还能为算法建模决策提供高质量的数据基础。在进行数据分析和建模的过程中&#xff0c;大量的时间花在数据准备上&#xff1a;加…

WebRTC Connection Negotiate解决

最近有个项目 &#xff0c;部署之后一直显示&#xff0c;查了一些资料还是没有解决&#xff0c;无奈只有自己研究解决&#xff1f; 什么是内网穿透&#xff1f; 我们访问我们自己的官网产品页面&#xff0c;我们的服务器是一个单独的个体&#xff0c;有独立的公网ip&#xf…

【Canvas与徽章】金圈蓝底国庆75周年徽章

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>金边黑盾75周年</title><style type"text/css"&g…

关于深度学习torch的环境配置问题

已经下好了torch在虚拟环境中&#xff0c;结果在ipynb文件中无法运行 后来在终端直接用python语句编译 发现没有问题 在编辑测试py文件 发现runcode有问题 原来是插件默认base环境 具体操作参考VS Code插件Code Runner使用python虚拟环境_coderunner怎么在虚拟环境中使用-CSD…

“2024光明多多垂直农业挑战赛”决赛启动成功举办

由光明食品集团所属上花集团的光明花博邨基地&#xff0c;与拼多多携手&#xff0c;联合中国农业大学、浙江大学等共同举办的“2024光明多多垂直农业挑战赛暨第四届多多农研科技大赛”于9月20-21日正式启动决赛。来自上海交大、中国农大、上海农科院、国家农业智能装备工程技术…

资源《Arduino 扩展板4-单游戏摇杆》说明。

资源链接&#xff1a; Arduino 扩展板4-单游戏摇杆 1.文件明细&#xff1a; 2.文件内容说明 包含&#xff1a;AD工程、原理图、PCB。 3.内容展示 4.简述 该文件为PCB工程&#xff0c;采用AD做的。 该文件打板后配合Arduino使用&#xff0c;属于Arduino的扩展板。 该文件…

STM32 GPIO - 笔记

输出: - 推挽 - 输出高低电平都由芯片决定 - 开漏 - 输出低电平有芯片输出,输出高电平由外部电路决定 输入: - 浮空 - 输入电平不确定 - 上拉 - 输入电平拉高 - 下拉 - 输入电平拉低

【网路通信基础与实践番外二】TCP协议的流量控制和拥塞控制以及二者区别和例题

TCP协议是端对端的协议&#xff0c;因此在数据进行传输的过程受发送方&#xff0c;数据通道&#xff0c;接收方三方状态的影响。我们用水龙头来比喻数据发送方&#xff0c;水管来比喻数据通道&#xff0c;水桶来表示数据接收方。 图(a)表示水桶太小&#xff0c;来不及接受注入…

SpringBoot使用@Slf4j注解实现日志输出

Slf4j 是 Lombok 库中的一个注解&#xff0c;它极大地简化了日志记录的代码。通过使用这个注解&#xff0c;Lombok 会自动在你的类中注入一个静态的日志对象。通过在类上添加 Slf4j 注解后&#xff0c;可以直接在方法中使用 log.info() 等方法进行日志打印。 1、安装相关依赖 …

基于RBAC的通用权限管理系统的详细分析与实现(理念篇——权限对象、权限项、功能权限、数据权限、权限组、权限设计)

一、权限&#xff08;Permission&#xff09; 在与人沟通的过程中&#xff0c;我们很多次提到了权限&#xff0c;但是权限具体的含义每个人理解的含义都不明确&#xff0c;这样很容易造成双方信息不对称&#xff0c;有的人就只是把权限理解成某个页面的是否可访问&#xff0c;…

超级干货:Air780EP AT开发之FTP应用

是时候讲一讲Air780EP模组FTP应用的多个AT命令示例&#xff0c;因为很多小伙伴已经提出要求了。 Air780EP是低功耗4G模组之一&#xff0c;支持全系列的AT指令以及LuatOS脚本二次开发。 一、准备工作 1.1 硬件准备 合宙EVB_Air780EP开发板一套&#xff0c;包括天线、SIM卡&am…

TXT文本文档内容整理归档,一键批量操作管理避免出错

在当今的信息化时代&#xff0c;文档管理成为了企业日常运营中不可或缺的一部分。其内容的合并管理更是显得尤为重要。避免在合并过程中出现格式混乱或内容丢失的情况&#xff0c;本文将探讨如何实现TXT文档内容的合并管理。 1.运行软件切换到“文本批量操作”功能版块上 2.在…

基于单片机汽车尾灯控制系统

**单片机设计介绍&#xff0c;基于单片机汽车尾灯控制系统设计 文章目录 前言概要设计思路 软件设计效果图 程序文章目录 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、…

超轻巧modbus调试助手使用说明

一、使用说明 1.1 数据格式 和其他的modbus采集工具一样&#xff0c;本组件也支持各种数据格式&#xff0c;其实就是高字节低字节的顺序。一般是2字节表示一个数据&#xff0c;后面又有4字节表示一个数据&#xff0c;目前好像还有8字节表示一个数据的设备。不同厂家的设备对应…

[Linux]Shell基本

入门 变量 运算符 语句 循环 控制台输入 函数