C++初阶—vector深度剖析及模拟实现

news2024/10/6 12:24:20

目录

➡️0. 前言

😊1.简易框架实现

🐔1. 无参构造

🐔2. 容量capacity — 长度size()

🐔3. 动态增长 — push_back—pop_back — reserve

🐔4. 迭代器的实现

🐔4.front和back的实现

😊2.迭代器失效问题

🐔1. insert和erase初步实现

🐔2. g++和msvc差异分析及测试:

🐔3. erase和insert更新

😊3.深浅拷贝问题

🐔1.实现迭代器区间构造

🐔2.实现拷贝构造—传统写法

🐔3.实现拷贝构造—现代写法

🐔4.更新reserve函数

😊4.其他函数的实现

🐔1.resize函数

😊5.完整代码


➡️0. 前言

vector 容器是 STL 中最常用的容器之一,它和 array 容器非常类似,都可以看做是对C++普通数组的“升级版”。不同之处在于,array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。

vector 常被称为向量容器,因为该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)

下面将对vector最主要部分以及最常使用部分进行模拟实现,以便更好的掌握STL容器vector

深度剖析vector迭代器在Linux—g++失效问题以及在Windows—MSVC失效问题,涉及深浅拷贝问题

参考文档:vector - C++ Reference (cplusplus.com) 以及 STL源码

😊1.简易框架实现

 此图参考书籍《STL源码剖析》侯杰老师

由上图便可得出vector成员变量,分别实现其构造函数,析构函数

	template<class T>
	class vector
	{
    	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

🐔1. 无参构造

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

🐔2. 容量capacity — 长度size()

		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		size_t size() const
		{
			return _finish - _start;
		}

🐔3. 动态增长 — push_back—pop_back — reserve

如果reserve空间大于capacity,便需要进行扩容,而push_back也需要检查容量大小,判断是否需要扩容,此时扩容一块新的空间,旧空间将会进行释放,同时将原空间数据拷贝至新空间,此时使用memcpy是否有问题?

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

        void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			++_finish;
		}

        void pop_back()
		{
			assert(_finish > _start);
			--_finish;
		}

🐔4. 迭代器的实现

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

测试案例1:

	void func(const vector<int>& v)
	{
		vector<int>::const_iterator it = v.begin();
		while (it != v.end())
		{
			std::cout << *it << " ";
			it++;
		}
	}
    void test_vector1()
	{
	/*  vector<std::string> v;
		v.push_back("xxxx");
		//发生隐式类型转换,产生临时变量,临时变量具有常性
		//因此push_back参数需要用const修饰,同时大对象传参用& */
		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)
		{
			std::cout << v[i] << " ";
		}
		std::cout << std::endl;
		v.pop_back();
		v.pop_back();
		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			std::cout << *it << " ";
			it++;
		}
		std::cout << std::endl;
		
		for (auto& e : v)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		func(v);
	}

🐔4.front和back的实现

		T& front()
		{
			assert(size() > 0);
			return _start[0];
		}
		T& back()
		{
			assert(size() > 0);
			return *(_finish - 1);
		}

😊2.迭代器失效问题

🐔1. insert和erase初步实现

首先根据以往方式实现简易指定位置插入删除操作

		void insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			if (_end_of_storage == _finish)
			{
				size_t len = pos - _start;
                //开辟新的空间pos指向位置被释放,此时pos成为野指针
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
                //规避野指针,根据偏移量,计算新的pos,进行插入
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
		}
		void erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator begin = pos + 1;
			while (begin <_finish)
			{
				*(begin-1) = *begin;
				++begin;
			}
			--_finish;
		}

测试案例1:

	void test_vector2()
	{
		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)
		{
			std::cout << v[i] << " ";
		}
		std::cout << std::endl;
		vector<int>::iterator pv = std::find(v.begin(),v.end(),4);
		if (pv != v.end())
		{
			//在p位置插入数据以后,不要访问p,因为p可能由于扩容而失效
			//迭代器失效——会造成野指针  
			v.insert(pv, 100);
			//v.insert(pv, 99);//err
		}
		v.insert(v.begin(), 1);

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

通过上测试案例,如果在pv位置进行插入,此时再次使用pv位置,便可能会导致野指针问题,野指针问题的来源正是由于扩容释放原空间而产生的原指针指向已失效空间。

测试案例2:

	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);
		for (size_t i = 0; i < v.size(); ++i)
		{
			std::cout << v[i] << " ";
		}
		std::cout << std::endl;
		vector<int>::iterator pv = std::find(v.begin(), v.end(), 3);
		v.erase(pv);
		for (auto& e : v)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		v.erase(pv);
		for (auto& e : v)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
	}

在不同编译器下,STL版本不同,容器删除操作的底层实现也不同,而删除操作如果进行了缩容(以时间换空间),便会产生野指针问题。

🐔2. g++和msvc差异分析及测试:

void test_std_vector3()
{
	std::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	for (size_t i = 0; i < v.size(); ++i)
	{
		std::cout << v[i] << " ";
	}
	std::cout << std::endl;
	std::vector<int>::iterator pv = std::find(v.begin(), v.end(), 3);
	v.erase(pv);
	for (auto& e : v)
	{
		std::cout << e << " ";
	}
	std::cout << std::endl;
	v.erase(pv);
	for (auto& e : v)
	{
		std::cout << e << " ";
	}
	std::cout << std::endl;
}

结论:insert/erase pos位置,不要直接访问pos(pos失效)一定要更新,在直接访问,否则可能会出各种出乎意料的结果,这就是所谓的迭代器失效。
STL只是一个规范,在不同编译器下,STL底层实现不同


vs——PJ版STL 
insert和erase在pos位置操作后,不允许在此位置再次操作,强制报错


g++——SGI版本STL
insert和erase在pos位置操作后,允许在此位置再次操作,但是insert扩容或者erase缩容后,会造成野指针问题,会强制报错

🐔3. erase和insert更新

		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			if (_end_of_storage == _finish)
			{
				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 = x;
			++_finish;
			return pos;
			//STL规定:返回一个迭代器,指向新插入元素的位置
			//避免因扩容造成的野指针问题,同时返回新插入元素的位置
			//只要更新pos,便可以解决在当前位置之前继续插入
		}
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator begin = pos + 1;
			while (begin <_finish)
			{
				*(begin-1) = *begin;
				++begin;
			}
			--_finish;
			//if (size() < capacity / 2)
			//{
			//	//缩容---以时间换空间
			//	//缩容就会导致迭代器的失效
			//}
			return pos;
			//STL规定了返回删除位置的下一个位置的迭代器
			//避免因缩容导致的野指针问题,同时返回删除位置的下一个位置的迭代器
			//只要更新pos,便可以进行连续的删除
		}

测试案例:

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

		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				it = v.insert(it, *it * 2);
				it++;
			}
			it++;
		}
		for (auto& e : v)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
	}

😊3.深浅拷贝问题

🐔1.实现迭代器区间构造

		//使用模板便可以使用其他容器的迭代器区间构造_双重模板
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

🐔2.实现拷贝构造—传统写法

		//传统写法
		vector(const vector<T>& v)
		{
			_start = new T[v.size()];
			//这里给size capacity都可以,各有各的优势
			std::memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start + v.size();
			_end_of_storage = _start + v.size();
			//如果使用开辟空间为capacity,这_end_of_storage不同
			//_end_of_storage = _start + v.capacity;
		}

🐔3.实现拷贝构造—现代写法

		/*
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(v.size());
			//vector 数据是T 有可能是容器,使用引用
			//不给引用还会造成深拷贝,不修改,给const
			for (const auto& e : v)
			{
				push_back(e);
			}
		}
		*/
		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(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

测试案列1:

	void test_vector6()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(2);
		v.push_back(3);
		vector<int> vv(v);//涉及深浅拷贝
		//指向同一块空间,会析构两次,一个对象修改会影响另一个对象
		for (auto& e : v)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		for (auto& e : vv)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;

		//迭代器区间构造vector
		std::string str("abcdef");
		vector<char> v1(str.begin(),str.end());
		for (auto& e : v1)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
	}

测试案例2:

	class Solution {
	public:
		vector<vector<int>> generate(int numRows)
		{
			vector<vector<int>> vv;
			vv.resize(numRows);
			for (size_t i = 0; i < vv.size(); ++i)
			{
				vv[i].resize(i + 1, 0);
				vv[i].front() = vv[i].back() = 1;
			}
			for (size_t i = 0; i < vv.size(); ++i)
			{
				for (size_t j = 0; j < vv[i].size(); ++j)
				{
					if (vv[i][j] == 0)
					{
						vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
					}
				}
			}
			return vv;
			//传值返回涉及深拷贝问题
			//vector<vector<int>> 里面浅拷贝,外面深拷贝,err
		}
	};
	void test_vector9()
	{
		vector<vector<int>> vv = Solution().generate(10);
		for (size_t i = 0; i < vv.size(); ++i)
		{
			for (size_t j = 0; j < vv[i].size(); ++j)
			{
				if (j == 0)
				{
					for (size_t m = vv.size() - vv[i].size(); m > 0; m--)
					{
						std::cout << " ";
					}
				}
				std::cout << vv[i][j] << " ";
			}
			std::cout << std::endl;
		}
	}

允许以上代码便会报错:

问题分析:

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

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

 

因此:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

🐔4.更新reserve函数

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//std::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;
				_end_of_storage = _start + n;
			}
		}

😊4.其他函数的实现

🐔1.resize函数

		void resize(size_t n,const T& val = T())
		{
			if (n > capacity())
			{
				reserve(n);
			}
			if (n > size())
			{
				while (_finish < _start+n)
				{
					*_finish = val;
					++_finish;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

测试案例:

	void test_vector8()
	{
		vector<int> v1;
		v1.resize(10, 0);
		for (auto& e : v1)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		std::cout << v1.capacity() << std::endl;

		vector<int> v2;
		v2.reserve(10);
		v2.push_back(1);
		v2.push_back(2);
		v2.push_back(3);
		v2.push_back(4);
		v2.push_back(5);
		v2.resize(8,8);
		for (auto& e : v2)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		std::cout <<v2.capacity()<< std::endl;


		vector<int> v3;
		v3.reserve(10);
		v3.push_back(1);
		v3.push_back(2);
		v3.push_back(3);
		v3.push_back(4);
		v3.push_back(5);
		v3.resize(3);
		for (auto& e : v3)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		std::cout << v3.capacity() << std::endl;

	}

😊5.完整代码

	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()
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{}

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

		vector(int n, const T& val = T())
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (n)
			{
				push_back(val);
				n--;
			}
		}
		vector(size_t n, const T& val = T())
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (n)
			{
				push_back(val);
				n--;
			}
		}
		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(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}
		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}
		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		size_t size() const
		{
			return _finish - _start;
		}
		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];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}
		void resize(size_t n,const T& val = T())
		{
			if (n > capacity())
			{
				reserve(n);
			}
			if (n > size())
			{
				while (_finish < _start+n)
				{
					*_finish = val;
					++_finish;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}
		void push_back(const T& x)
		{
			insert(end(), x);
		}
		void pop_back()
		{
			assert(_finish > _start);
			--_finish;
		}
		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			if (_end_of_storage == _finish)
			{
				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 = x;
			++_finish;
			return pos;
		}
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator begin = pos + 1;
			while (begin <_finish)
			{
				*(begin-1) = *begin;
				++begin;
			}
			--_finish;
			return pos;
		}
		T& front()
		{
			assert(size() > 0);
			return _start[0];
		}
		T& back()
		{
			assert(size() > 0);
			return *(_finish - 1);
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

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

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

相关文章

你知道C语言的typedef关键字吗?

本篇博客主要讲解C语言中的typedef关键字。typedef的作用是类型重定义&#xff0c;可以理解为给类型起一个别名。我主要从3个方面来讲解&#xff1a; typedef内置类型。typedef自定义类型。typedef和#define的区别。 1.typedef内置类型 typedef可以给一个类型起“别名”。比如…

服务器部署前后端分离项目

服务器部署前后端分离项目 目录服务器部署前后端分离项目一、安装环境安装jdk1、在/usr/local目录下创建jdk文件夹&#xff0c;并将jdk安装包放到/usr/local/jdk包下并解压1.1通过文件传输工具将jdk包上传到服务器上1.2输入解压命令1.3解压完成&#xff0c;生成下面的文件2、配…

学习周报4/9

文章目录前言文献阅读摘要简介方法结论时间序列预测总结前言 本周阅读文献《Improving LSTM hydrological modeling with spatiotemporal deep learning and multi-task learning: A case study of three mountainous areas on the Tibetan Plateau》&#xff0c;文章主要基于…

多种方法解决SLF4J: Defaulting to no-operation (NOP) logger implementation的错误

文章目录1. 复现错误2. 分析错误3. 解决错误4. 解决该错误的其他方法1. 复现错误 今天在编写使用Quartz执行定时任务的方法&#xff0c;如下代码所示&#xff1a; public class QuartzTest {public static void main(String[] args) throws SchedulerException {// 1、创建Sch…

大数据系列——Hive理论

概述 Hive是一个数据仓库管理工具&#xff0c;将结构化的数据文件映射为一张数据库表&#xff0c;并提供类SQL&#xff08;HQL&#xff09;查询功能。由Facebook实现并开源,最后捐赠给Apache发展为顶级项目。 以RDBMS数据库为元数据存储服务&#xff0c; 以Hadoop HDFS来存储…

44.节流与防抖

目录 1 防抖 1.1 概念 1.2 应用场景 1.3 lodash防抖 1.4 手写防抖 2 节流 2.1 概念 2.2 应用场景 2.3 lodash节流 2.4 手写节流 2.5 记录视频上一次的播放位置 1 防抖 1.1 概念 防抖就是让事件触发后延迟n秒后再执行回调函数&#xff0c;在这n秒内如…

014:Mapbox GL添加draw组件,绘制点、线、多边形、删除

第014个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中添加draw组件,绘制点、线、多边形,删除所选元素。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共78行)相关API参考:专栏目标示例效果 配置方…

用于平抑可再生能源功率波动的储能电站建模及评价(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

项目部署---手工部署项目

手工部署项目 在ideal中开发springboot项目并打成jar包 将jar包上传到Linux服务器 mkdir /usr/local/app 创建目录&#xff0c;将项目jar包放到此目录 ![](https://img-blog.csdnimg.cn/83cf26b151874637a2dfeda7dd05e4cf.jpeg) 启动SpringBoot程序 检查防火墙&#xff0c;…

电脑蓝屏问题排查

最近电脑安装了最新win10&#xff0c;更新最新的驱动以后&#xff0c;开机几分钟后&#xff0c;会蓝屏重启&#xff0c;报错为&#xff1a; DRIVER_POWER_STATE_FAILURE 下载蓝屏分析工具BlueScreenView 问题出在ntoskrnl.exe bing搜索给出了二种解决方案&#xff1a; 1&a…

软件测试应届生社招找工作面试会遇到哪些坑?(全网最全避坑指南)

目录 找工作的最佳时间 是否裸辞 我们要做哪些准备工作 准备一段自我介绍 准备一份pdf简历 社招找工作的渠道&#xff1a;内推 找工作的最佳时间 社招找工作的最佳时间是&#xff1a;金三银四。也就是春节后的三月份和四月份。 为什么是金三银四呢&#xff1f;因为每年的…

HTML5 Geolocation

文章目录HTML5 Geolocation定位用户的位置浏览器支持HTML5 - 使用地理定位处理错误和拒绝在地图中显示结果给定位置的信息getCurrentPosition() 方法 - 返回数据Geolocation 对象 - 其他有趣的方法HTML5 Geolocation HTML5 Geolocation&#xff08;地理定位&#xff09;用于定位…

【SQL Server】数据库开发指南(五)T-SQL 高级查询综合应用与实战

T-SQL 是 SQL Server 的专用版本&#xff0c;提供了一组强大的高级查询功能&#xff0c;包括聚合函数、子查询、连接、视图、窗口函数、共享表达式、递归查询等。这些功能使得 T-SQL 可以轻松处理大量数据&#xff0c;并支持各种复杂的查询和数据操作。本文将介绍 T-SQL 的一些…

Debian 10配置apt源常见问题

目录 一&#xff1a;配置本地apt源没有发现文件 解决方案 二&#xff1a;apt下载bind9报错E: Package bind9 has no installation candidate 方法一&#xff1a; 方法二&#xff1a;更新不报错但是安装依旧报错E: Package bind9 has no installation candidate 一&#xff…

密码加密——加盐算法(两种方式)

加盐算法 文章目录加盐算法手写一个加盐算法spring security密码安全是一件很重要的事情&#xff0c;所以一定要谨慎对待 常见的主要是3种方式 明文MD5加密加盐算法 首先明文肯定是不可取的&#xff0c;在数据库中明文存储密码风险实在是太大了 简单来说&#xff0c;使用MD…

DataGrip连接数据库设置(MySQL、Oracle、SQL Server)

一、DataGrip连接MySQL 1.1 配置信息 1.2 测试查询employees库中departments表信息 employees为测试库&#xff0c;具体来源&#xff0c;参考这篇文章 下载并导入MySQL示例数据库employees 。 1.3 测试查询employees库中employees表信息 二、DataGrip连接Oracle 将SID改为o…

「字节跳动实习期间」免费专栏发布+服务端知识架构

字节跳动实习期间专栏内容 主要放在字节跳动实习期间个人的一些笔记&#xff0c;通过整理放出&#xff0c;并保持不断迭代 同时这里并不会放涉及字节内部的文档和知识&#xff0c;只是博主整理的网上已有的知识 欢迎同学们关注专栏、互相学习、共同进步&#xff0c;我也会在…

vue-cli(vue脚手架方式搭建)

1.首先安装node前端环境,可以帮助我们去下载其他的组件 下载完成后,去自己的电脑找到node的文件路径,复制去配置环境变量,在path中配 环境搭配完成后,在cmd中进行测试 ,输入一下两个命令进行测试 2.在hbuilderX中创建一个vue-cli项目(标准的前段项目) 3.组件路由 (1)安装 v…

java基础学习-5

Java基础学习-5快乐算法二分查找小总结分块查找冒泡选择插入排序递归算法快速排序小总结ArraysLambda表达式小总结Lambda表达式的省略写法小练习集合进阶CollectionColection的遍历方式迭代器遍历小总结增强for循环Lambda表达式遍历小总结List集合List的遍历方式迭代器遍历增强…

【redis】集群

redis集群 集群有点难 大部分的实操命令没有记录 希望能二刷补上 18:46 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录redis集群前言一、集群是什么&#xff1f;二、集群能干嘛&#xff1f;三、集群算法-分片-槽…