【C++】vector的模拟实现不会怎么办?看过来

news2025/2/23 21:14:57

🌈欢迎来到C++专栏~~vector的模拟实现


  • (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort🎓
  • 🌍博客主页:张小姐的猫~江湖背景
  • 快上车🚘,握好方向盘跟我有一起打天下嘞!
  • 送给自己的一句鸡汤🤔:
  • 🔥真正的大师永远怀着一颗学徒的心
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
  • 🎉🎉欢迎持续关注!
    在这里插入图片描述

请添加图片描述

vector的模拟实现

  • 🌈欢迎来到C++专栏~~vector的模拟实现
    • 一. 构造和析构
    • 二. 一系列基本接口
      • 🌈size & capacity
      • 🌈`[]`
      • 🌈iterator迭代器
    • 三、尾插尾删
      • reserve & resize
      • push_back & pop_back
    • 四、构造 & 拷贝构造 & 赋值重载
    • 五、迭代器失效问题
      • 🎨insert
      • 🎨erase
      • 🎨小总结
    • 💢memcpy拷贝问题
    • 💢动态二维数组理解
    • 附源码
      • `vector.h`
      • `test.c`
  • 📢写在最后

请添加图片描述

本文章按照逻辑链来完成vector的模拟实现,重点介绍迭代器失效问题,文末赋上源码.h

开始前,翻阅源码得知:

template<class T, class Alloc = alloc>
class vector 
{
public:
    typedef T* iterator;
private:
    iterator start;
    iterator finish;
    iterator end_of_storage;
};

其实类似于我们之前的_a_size_capacity ~

在这里插入图片描述

_size = _finish - _start
_capacity = _end_of_storage - _start

一. 构造和析构

  • 无参构造
  • 迭代器区间构造(后面实现)

类似顺序表,初始化都给空,不然push_back就会崩

//无参构造
vector()
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{}

🧡析构函数

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

二. 一系列基本接口

🌈size & capacity

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

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

🌈[]

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

🌈iterator迭代器

普通迭代器 & const迭代器,老生常谈的问题

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

迭代器支持, 范围for同样也能实现

三、尾插尾删

插入数据要考虑扩容问题

reserve & resize

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

注意:

  • 匿名对象具有常性,不可更改,所以加const
  • resize默认填充的是T类型的缺省值,int类型就是0;指针类型就是空指针;若是自定义类型,比如vector,那么就是调用的vector的构造函数,构造vector的匿名对象
  • C++为了迎合模板需要,内置类型int等被认为有构造函数
    在这里插入图片描述

🌊reserve
同样需要深拷贝,mencpy是浅拷贝

    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);
                //delete[] _start;
                
                for (size_t i = 0; i < sz; i++)
                {
                    tmp[i] = _start[i];
                }
                delete[] _start;
            }
            _start = tmp;
            _finish = _start + sz;
            _endofstorage = _start + n;
        }
    }

注意:

  • 更新_finish的时候,要提前把sz保存起来
// 错误示范,请勿模仿
_start = tmp;
_finish = _start + size();
_endofstorage = _start + n;
  • 因为这样的话,size()是_finish - _start(0),况且_start已经更新了,减完有可能就是负数了

在这里插入图片描述

拷贝数据不能简单地memcpy ,这样虽然vector是深拷贝的,比如vector<vector<int>>,其中的vector<int>仍然是浅拷贝的。对于int没有问题,但是对于自定义类型就出问题了。因此我们需要更改 ——

  • 若T是int,一个一个拷贝,没问题;
  • 如果是自定义类型,一个个的拷贝,就要调用深拷贝赋值了

push_back & pop_back

⚡push_back

    //1、&引用防止深拷贝  2、+const可以右值引用,防止不能引用是常性的临时变量
    void push_back(const T& x)
    {
        if (_finish == _end_of_storage)
        {
            reserve(capacity() == 0 ? 4 :capacity()*2);
        }
        *_finish = x;
        _finish++;
    }

⚡pop_back

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

四、构造 & 拷贝构造 & 赋值重载

💦迭代器区间构造

	//写成模板 :可以用其他迭代器区间进行构造 
	template <class InputIterator>
	vector(InputIterator first, InputIterator last)
		:_start(nullptr)
		,_finish(nullptr)
		,_end_of_storage(nullptr)
	{
		while(first != last)
		{
			push_back(*first);
			++first;
		}
	}

💢注意

  1. 一个类模板的成员函数又可以是一个函数模板,这样既可以用自身类型的迭代器,可以用其他任意迭代器区间进行构造 ,只要这个迭代器解引用的数据能和T匹配。
  2. 一定要记住初始化,没有初始化就是随机值,出现野指针。扩容时会拷贝数据释放空间,就崩溃了
  3. 关于InputIterator的含义:函数模板的模板参数传迭代器区间是有命名规范
    迭代器分为四类,来访问容器——
    在这里插入图片描述

这个后面会讲解到

💦拷贝构造——多种写法

在这里插入图片描述

	void swap(const T& v)
	{
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_end_of_storage, v._end_of_storage);
	}
		
	//现代写法2 : v2(v1)   资本家思维
	vector(const vector<T>& v)
		:_start(nullptr)
		, _finish(nullptr)
		, _end_of_storage(nullptr)
	{
		vector<T> tmp(v.begin(), v.end());//打工人
		swap(tmp);//交换
	}
  • 初始化的时候必须置nullptr,不然free的就是一块随机值的空间了

💦赋值重载——现代写法

  • 复用拷贝构造
  • v是局部变量,出作用域也会析构,相当于你叫外卖小哥帮你丢垃圾
		//赋值重载    v1 = v2
		vector<T> operator= (vector<T> v)
		{
			swap(v);//
			return *this;
		}

这里是传值调用,v是v2拷贝构造来的。到这里我都忘了为什么拷贝构造不能传值必须传引用传送门🎉🎉:类和对象,因为拷贝构造要传参,传参完又是拷贝构造,会无限递归

五、迭代器失效问题

🎨insert

🐋检查空间 —— 挪动数据 ——插入数据。 测试代码,发现insert插入第5个数据(即扩容时),出现了随机值

在这里插入图片描述

        //错误示范
		void insert(iterator pos, const T& x)
		{
			assert(pos > _start);
			assert(pos <= finish);//=就是尾插

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

			//挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *(end);
				--end; 
			}
			*pos = x;
			++_finish;
		}

测试代码 ——

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

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

		auto p = find(v.begin(), v.end(), 3);
		if (p != v.end())
		{
			v.insert(p, 30);
		}

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

	}

出现随机值
在这里插入图片描述

因为有一个隐藏的bug:如果insert时发生了扩容,会导致it指向的空间被释放,此时it还指向着已被释放的空间是野指针,这种问题,我们就叫做迭代器失效

在这里插入图片描述
💗于是,我们通过增容后,提前算出相对位置,更新pos来解决。此时外边的p仍然是失效的,则通过传返回值(An iterator that points to the first of the newly inserted elements.),若之后还需要使用p,再根据需要接收返回值(不能传引用,传引用会引发其他问题:返回常量)

🎉最终版:

		void insert(iterator pos, const T& x)
		{
			assert(pos > _start);
			assert(pos <= _finish);//=就是尾插

			if (_finish == _end_of_storage)
			{
			    //扩容会导致迭代器失效,需要提前计算更新pos
				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;
		}

同样测试得:

	//测试insert - 迭代器失效问题
	void testVector4()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		vector<int>::iterator it = find(v.begin(), v.end(), 2);
		if (p != v.end())
		{
		    // 在it位置插入数据后,不要在访问p,因为p可能失效了
			p = v.insert(it, 20);
			// 若insert时发生了扩容,会导致it指向的空间被释放
			// it本质上是野指针,这种问题,我们就叫做迭代器失效
		}
		v.insert(p, 10);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

在这里插入图片描述

🎨erase

常规思路:挪动数据覆盖。此处没有新开辟空间,也就没有野指针数据

        // 错误示范,请勿模仿
		void erase(iterator pos);
		{
			assert(pos >= _start);
			assert(pos < _finish);

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

erase缩容也可能到时pos失效(具体看编译器实现)

现在,要求实现删除v中所有偶数,测试代码

	//测试erase - 迭代器失效问题2
	void test_vector4()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		//要求删除所有的偶数
		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				v.erase(it);
			}
			++it;
		}

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

通过一系列测试: 发现

1 2 3 4 5 -> 正常(误打误撞) 
1 2 4 5	  -> 没删干净 		  
1 2 3 4	  -> 崩溃 		  

在这里插入图片描述

  • 以1245为例,偶数相连的情况;删除完2后,++it,导致后一个偶数没判断,跳过了4
  • 再分析1234,当erase最后一个偶数时,++it,此时的it_finish擦边而过了,*it就不断越界访问了,肯定崩溃

总结,erase(it)后,it指向的位置意义已经变了,直接++,会导致一些意料之外的结果,这也是迭代器失效的问题。

💦我们翻阅文档得知:函数调用后会返回刚删除位置的下一个位置,即这个意义已经改变的pos(An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.)(我网易翻译的应该没错)

💗终极版erase

		//stl规定erase返回删除位置的下一个位置
		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}
			--_finish;
			return pos;//此时移动完pos就是被删的后一个数据
		}

继续测试代码~

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

		//要求删除所有的偶数
		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;
	}

结果正确

在这里插入图片描述

🎨小总结

  1. 迭代器失效主要发生在inserterase中,用了迭代器并改变了底层的数据结构迭代器失效了,就不要再去访问pos位置;一定要更新,若还需要访问,可先接收返回值更新

💢memcpy拷贝问题

假设模拟实现的vector中的reserve接口中,使用memcpy进行的拷贝,以下代码会发生什么问题?

int main()
{
   bite::vector<bite::string> v;
   v.push_back("1111");
   v.push_back("2222");
   v.push_back("3333");
   return 0; 
 }

在这里插入图片描述
在这里插入图片描述
对于内置类型可以用mencpy,对于自定义类型就不能用mencpy了,memcpy是浅拷贝,指向同一块空间,要是析构or一个对象修改数据,就出问题了,就需要一个一个对象进行深拷贝赋值

💢动态二维数组理解

调用函数
在这里插入图片描述

附源码

vector.h

#pragma once
#include<algorithm>
#include <iostream>
#include <vector>
using namespace std;
#include<assert.h>


namespace ljj
{
	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;
			}
		}

		//拷贝构造 ~ 传统写法
		//v1(v)
		//vector(const vector<int>& v)
		//{
		//	_start = new T[v.size()];
		//	//memcpy(_start, v._start, sizeof(T) * v.size());//有坑
		//	for (size_t i = 0; i < v.size(); ++i)
		//	{
		//		_start[i] = v.start[i];
		//	}
		//	_finish = _start + v.size();
		//	_end_of_storage = _start + v.size();
		//}

		//现代写法1 : v2(v1)
		//vector(const vector<T>& v)
		//	:_start(nullptr)
		//	,_finish(nullptr)
		//	,_end_of_storage(nullptr)
		//{
		//	reserve(v.size());
		//	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);
		}
		
		//现代写法2 : v2(v1)   资本家思维
		vector(const vector<T> & v)
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());//打工人
			swap(tmp);//交换
		}

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

		//赋值重载    v1 = v2
		vector<T> operator= (vector<T> v)
		{
			swap(v);//
			return *this;
		}
		T& front()
		{
			assert(size() > 0);
			return *_start;
		}

		T& back()
		{
			assert(size() > 0);
			return *(finish - 1);
		}

		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
		size_t capacity() const
		{
			return _end_of_storage - _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];
		}

		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 reserve(size_t n)
		{
			//大于才扩容
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				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;
				_end_of_storage = _start + n;
			}

		}

		void push_back(const T& x)//1、&引用防止深拷贝  2、+const可以右值引用,防止不能引用常性的临时变量
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			++_finish;
		} 

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

		iterator insert(iterator pos, const T& x)
		{
			assert(pos > _start);
			assert(pos <= _finish);//=就是尾插

			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 = x;
			++_finish;

			return pos;
		}

		//stl规定erase返回删除位置的下一个位置
		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

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

			//if (size() < capacity() / 2)
			//{
			//	//缩容 --以时间换空间

			//}
		}



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

		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 ch : v)//傻瓜式的替换 begin ——》Begin都会报错
		{
			cout << ch << " ";
		}
		cout << endl;


	}
	//测试insert - 迭代器失效问题
	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(200);

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

		auto p = find(v.begin(), v.end(), 3);
		if (p != v.end())
		{
			v.insert(p, 30);
		}

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

	}

	//测试erase - 迭代器失效问题
	void test_vector3()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

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

		auto p = find(v.begin(), v.end(), 3);
		if (p != v.end())
		{
			v.erase(p);
		}

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

	//测试erase - 迭代器失效问题2
	void test_vector4()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(4);
		v.push_back(5);

		//要求删除所有的偶数
		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_vector5()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(4);
		v.push_back(5);

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

		vector<int> v1(v);

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

	//迭代器区间构造
	void test_vector6()
	{
		string s("hello world");
		vector<int> v(s.begin(), s.end());

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

	void test_vector7()
	{

		vector<int*> v1(10);

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


		//int  int 
		//vector<int> v2(10,10);//编译报错:非法的间接寻址  语法问题

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



		// char   int
		vector<char> v3(10, 'a');

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

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

		vector<int> v2;
		v2.reserve(10);
		v2.push_back(1);
		v2.push_back(2);
		v2.push_back(3);
		v2.push_back(4);

		v2.resize(8,8);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

		v2.resize(20,20);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

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

test.c

#include"vector.h"

int main()
{
	//ljj::test_vector1();
	//ljj::test_vector2();
	//ljj::test_vector3();
	//ljj::test_vector4();
	//ljj::test_vector5();
	//ljj::test_vector6();
	//ljj::test_vector7();
	ljj::test_vector8();

	 return 0;
}

📢写在最后

在这里插入图片描述

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

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

相关文章

MySQL是如何保证主从一致的

一&#xff1a;什么是binlog Binary log(二进制日志)&#xff0c;简称Binlog。 Binlog是记录所以数据表结构变更以及表数据修改的二进制日志&#xff0c;不会记录select和show这类操作。Binlog是以事件形式记录&#xff0c;还包括语句所执行的消耗时间。Binlog是MySql Server自…

0082 时间复杂度,冒泡排序

/* * 排序也称排序算法&#xff08;Sort Algorithm&#xff09; * 排序是将一组数据&#xff0c;依指定的顺序进行排列的过程。 * * 排序分类 * 1.内部排序&#xff1a;将需要处理的所有数据都加载到内存存储器中进行排序&#xff08;使用内存&#xff09; * 插…

Keil MDK的sct分散加载文件详解

sct 分散加载文件简介 MDK 生成一个以工程名命名的后缀为 *.sct 的分散加载文件 (Linker Control File&#xff0c;scatter loading)&#xff0c;链接器根据该文件的配置分配各个节区地址&#xff0c;生成分散加载代码&#xff0c;因此我们通过修改该文件可以定制具体节区的存…

Spring源码:Spring源码阅读环境搭建

本篇内容包括&#xff1a;Mac 环境下 gradle 的安装和配置、源码克隆、新建测试类&#xff0c;测试Spring源码 等内容&#xff01; 第一步&#xff1a;Mac 环境下 gradle 的安装和配置 1、下载安装包 # 到 GitHub 的 Spring 仓库选定 Spring 版本&#xff0c;查看对应版本 Sp…

Linux项目自动化构建工具make/makefile

1.背景 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计其数&#xff0c;其按类型&#xff0c;功能&#xff0c;模块分别放在若干目录中&#xff0c;makefile定义了一系列的规则来制定&#xff0c;那些文件需要先编译&a…

C艹笔记--面向对象程序设计

文章目录类与对象简介类与结构的区别定义成员函数继承继承小总结[C中::和:&#xff0c; .和->的作用和区别](https://zhuanlan.zhihu.com/p/165992745)符号::和&#xff1a;的作用和区别:::一般用来表示继承符号.和->的作用和区别#include#include""和#include…

STM32入门——基本 GPIO 的输出控制

文章目录1 什么是 GPIO &#xff1f;1.1 GPIO 简介1.2 GPIO 硬件解析1.2.1 保护二极管1.2.2 P-MOS、N-MOS 管1.2.3 数据输入输出寄存器1.2.4 复用功能输出1.2.5 模拟输入输出1.3 GPIO 的工作模式1.3.1 输入模式 (模拟/浮空/上拉/下拉)1.3.2 输出模式 (推挽/开漏)1.3.3 复用功能…

基于Nodejs+vue开发实现酒店管理系统

作者简介&#xff1a;Java、前端、Pythone开发多年&#xff0c;做过高程&#xff0c;项目经理&#xff0c;架构师 主要内容&#xff1a;Java项目开发、毕业设计开发、面试技术整理、最新技术分享 项目编号&#xff1a;BS-QD-KS-002 一&#xff0c;项目简介 本项目使用纯前端技…

mysql约束

文章目录mysql约束非空约束唯一性约束主键约束使用自增列&#xff1a;AUTO_INCREMENTFOREIGN KEY约束CHECK约束mysql约束 为什么需要约束&#xff1f;为了保证数据的完整性什么叫约束&#xff1f;对表中字段的限制约束的分类&#xff1a; 角度1&#xff1a;约束的字段个数&…

吴峰光杀进 Linux 内核

【编者按】吴峰光&#xff0c;Linux 内核守护者&#xff0c;学生时代被同学戏称为“老神仙”&#xff0c;两耳不闻窗外事&#xff0c;一心只搞 Linux。吴峰光的 Linux 内核之路&#xff0c;是天赋、兴趣、耐心、坚持的综合&#xff0c;这从一个补丁前后迭代了 16 个版本后还进行…

【初识Netty使用Netty实现简单的客户端与服务端的通信操作Netty框架中一些重要的类以及方法的解析】

一.Netty是什么&#xff1f; Netty 由 Trustin Lee(韩国&#xff0c;Line 公司)2004 年开发 本质&#xff1a;网络应用程序框架 实现&#xff1a;异步、事件驱动 特性&#xff1a;高性能、可维护、快速开发 用途&#xff1a;开发服务器和客户端 Netty的性能很高&#xff0…

字符串匹配算法(BF、KMP)

目录 1、暴力匹配&#xff08;BF&#xff09;算法 2、KMP算法 1、暴力匹配&#xff08;BF&#xff09;算法 BF算法&#xff0c;即暴力(Brute Force)算法&#xff0c;是普通的模式匹配算法&#xff0c;BF算法的思想就是将目标串S的第一个字符与模式串T 的第一个字符进行匹配&a…

【树莓派不吃灰】配置samba,文件夹目录配置在闲置U盘,实现局域网文件共享

目录1. 前言2. 安装 Samba2.1 安装samba 和 samba-common-bin2.2 配置/etc/samba/smb.conf文件2.3 配置登录账号和密码2.4 重启 samba 服务2.5 回到windows&#xff0c;就可以在网络当中发现共享的文件夹3. 在Windows上挂载smb的共享目录3.1 打开windows的smb功能3.2 添加网络映…

Java --- springMVC实现RESTFul案例

一、使用springMVC实现RESTFul小案例 1.1、项目目录图&#xff1a; 1.2、代码实现&#xff1a; pom.xml文件&#xff1a; <packaging>war</packaging><!--添加依赖--><dependencies><!--SpringMVC--><dependency><groupId>org.spr…

黑马C++ 03 提高4 —— STL常用容器_string容器/vector容器/deque容器

文章目录一、string容器1. string基本概念2. string构造函数3. string赋值操作4. string字符串拼接5. string查找和替换6. string字符串比较7. string字符存取8. string字符串的插入和删除9. string子串二、vector容器(尾插尾删)1. vector基本概念2. vector构造函数3. vector赋…

【目标检测】基于yolov3的血细胞检测(无bug教程+附代码+数据集)

多的不说,少的不唠,先看检测效果图: 共检测三类:红细胞RBC、白细胞WBC、血小板Platelets Hello,大家好,我是augustqi。今天给大家带来的保姆级教程是:基于yolov3的血细胞检测(无bug教程+附代码+数据集) 1.项目背景 在上一期的教程中,我们基于yolov3训练了一个红细…

韩顺平linux(1-11小节)

运维工程师 服务器的规划、调试优化、日常监控、故障处理 物联网linux Linux主要指的是内核 ubuntu&#xff08;python偏爱&#xff09;&#xff0c;centos 发行版本 内核进行包装 1.4服务器领域 linux在服务器领域的应用是最强的。 linux免费、稳定、高效等特点在这里得到了很…

2019 Sichuan Province Programming Contest J. Jump on Axis

题目链接&#xff1a;https://codeforces.com/gym/102821/problem/J 题意&#xff1a;给你一个坐标k&#xff0c;每次从0开始走 每次有三个选择&#xff1a;选择1走一步&#xff0c;选择2走两步&#xff0c;选择3走三步 每次选第i个选择的时候&#xff0c;如果他没有被选过&…

MySQL是如何保证数据不丢失的

一.什么是两阶段提交 1.SQL语句&#xff08;update user set name‘李四’ where id3&#xff09;的执行流程是怎样的呢&#xff1f; 1.执行器先找引擎取 ID3这一行。ID 是主键&#xff0c;引擎直接用树搜索找到这一行。 2.如果 ID3 这一行所在的数据页本来就在内存中&#x…

力扣算法入门刷题

1、回文数 判断输入的整数是否是回文 我的一般思路&#xff1a; 将输入的整数转成字符串&#xff0c;再将这个字符串转成字符数组c&#xff0c;对字符数组进行遍历&#xff0c;如果第i个元素与第 c.length - i - 1 元素不相等&#xff0c;也就是通过比较首尾元素是否相同来判断…