9. C++STL详解vector的使用以及模拟实现

news2025/4/13 5:57:38

文章目录

  • 一、vector的使用介绍
    • 1.1 vector的定义
    • 1.2 vector iterator 的使用
    • 1.3 vector 增删查改
    • 二、vector 迭代器失效问题
        • 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
        • 指定位置元素的删除操作-->erase
        • Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。
  • 三、vector的接口使用
  • 四、OJ练习
    • 4.1 只出现一次的数字 I
    • 4.2 杨辉三角
    • 4.3 删除有序数组中的重复项
    • 4.4 只出现一次的数字||
    • 4.5 只出现一次的数字|||
    • 4.6 数组中出现次数超过一半的数字
  • 五、vector深度剖析及模拟实现
  • 六、使用memcpy拷贝问题
  • 七、std::vector的核心框架接口的模拟实现vector

一、vector的使用介绍

vector文档

1.1 vector的定义

1.2 vector iterator 的使用

vector 空间增长问题

capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。

resize在开空间的同时还会进行初始化,影响size。

void TestVectorExpand()
{
	size_t sz;
	std::vector<int> v;
	sz = v.capacity();
	cout << "making v grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

vs:运行结果:

vs下使用的STL基本是按照1.5倍方式扩容

g++运行结果:

linux下使用的STL基本是按照2倍方式扩容

1.3 vector 增删查改

二、vector 迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

对于vector可能会导致其迭代器失效的操作有:

会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
void TestVector3() {
	std::vector<int> v{ 1,2,3,4,5,6 };
	auto it = v.begin();

	// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
	v.resize(100, 8);
	// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
	v.reserve(100);
	// 插入元素期间,可能会引起扩容,而导致原空间被释放
	v.insert(v.begin(), 0);
	v.push_back(8);
	// 给vector重新赋值,可能会引起底层容量改变
	v.assign(100, 8);
	
	/*
	出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释
	放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块
	已经被释放的空间,而引起代码运行时崩溃。
	解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给
	it重新赋值即可。
	*/
	
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

指定位置元素的删除操作–>erase
void TestVector4() {

	int a[] = { 1, 2, 3, 4 };
	std::vector<int> v(a, a + sizeof(a) / sizeof(int));
	// 使用find查找3所在位置的iterator
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	// 删除pos位置的数据,导致pos迭代器失效。
	v.erase(pos);
	cout << *pos << endl; // 此处会导致非法访问
}

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

void TestVector5() {
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}
}

解决方法:需要下面这样写

void TestVector6() {
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			it = v.erase(it);
		else
			++it;
	}
}
Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。
  1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	for (size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;
	auto it = v.begin();
	cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
	// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效
	v.reserve(100);
	cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
	// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
	// 虽然可能运行,但是输出的结果是不对的
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

VS下执行:

Linux下执行:

  1. erase删除任意位置代码后,linux下迭代器并没有失效,因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	vector<int>::iterator it = find(v.begin(), v.end(), 3);
	v.erase(it);
	cout << *it << endl;
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

VS下执行:

Linux下执行:

  1. erase删除的迭代器如果是最后一个元素,删除之后it已经超过end,此时迭代器是无效的,++it导致程序崩溃
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	// vector<int> v{1,2,3,4,5,6};
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}
	for (auto e : v)
		cout << e << " ";
	cout << endl;
	return 0;
}

VS下执行:

使用第一组数据:

使用第二组数据:

Linux下执行:

使用第一组数据:

使用第二组数据:

从上述三个例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。

  1. 与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效
int main() {
	string s("hello");
	auto it = s.begin();
	// 放开之后代码会崩溃,因为resize到20会string会进行扩容
	// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
	// 后序打印时,再访问it指向的空间程序就会崩溃
	// s.resize(20, '!');
	while (it != s.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	it = s.begin();
	while (it != s.end())
	{
		it = s.erase(it);
		// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后
		// it位置的迭代器就失效了
		// s.erase(it);
		++it;
	}
}

迭代器失效解决办法:在使用前,对迭代器重新赋值即可。

三、vector的接口使用

#include <iostream>
#include <vector>
using namespace std;


//    vector的构造

int TestVector1()
{
	// constructors used in the same order as described above:
	vector<int> first;                                // empty vector of ints
	vector<int> second(4, 100);                       // four ints with value 100
	vector<int> third(second.begin(), second.end());  // iterating through second
	vector<int> fourth(third);                       // a copy of third

	// 下面涉及迭代器初始化的部分,我们学习完迭代器再来看这部分
	// the iterator constructor can also be used to construct from arrays:
	int myints[] = { 16,2,77,29 };
	vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));

	cout << "The contents of fifth are:";
	for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
		cout << ' ' << *it;
	cout << '\n';

	return 0;
}



//  vector的迭代器

void PrintVector(const vector<int>& v)
{
	// const对象使用const迭代器进行遍历打印
	vector<int>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

void TestVector2()
{
	// 使用push_back插入4个数据
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	// 使用迭代器进行遍历打印
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	// 使用迭代器进行修改
	it = v.begin();
	while (it != v.end())
	{
		*it *= 2;
		++it;
	}

	// 使用反向迭代器进行遍历再打印
	// vector<int>::reverse_iterator rit = v.rbegin();
	auto rit = v.rbegin();
	while (rit != v.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	PrintVector(v);
}


//  vector的resize 和 reserve

// reisze(size_t n, const T& data = T())
// 将有效元素个数设置为n个,如果时增多时,增多的元素使用data进行填充
// 注意:resize在增多元素个数时可能会扩容
void TestVector3()
{
	vector<int> v;

	// set some initial content:
	for (int i = 1; i < 10; i++)
		v.push_back(i);

	v.resize(5);
	v.resize(8, 100);
	v.resize(12);

	cout << "v contains:";
	for (size_t i = 0; i < v.size(); i++)
		cout << ' ' << v[i];
	cout << '\n';
}

// 测试vector的默认扩容机制
// vs:按照1.5倍方式扩容
// linux:按照2倍方式扩容
void TestVectorExpand()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "making v grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

// 往vecotr中插入元素时,如果大概已经知道要存放多少个元素
// 可以通过reserve方法提前将容量设置好,避免边插入边扩容效率低
void TestVectorExpandOP()
{
	vector<int> v;
	size_t sz = v.capacity();
	v.reserve(100);   // 提前将容量设置好,可以避免一遍插入一遍扩容
	cout << "making bar grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}


//  vector的增删改查

// 尾插和尾删:push_back/pop_back
void TestVector4()
{
	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())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	v.pop_back();
	v.pop_back();

	it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

// 任意位置插入:insert和erase,以及查找find
// 注意find不是vector自身提供的方法,是STL提供的算法
void TestVector5()
{
	// 使用列表方式初始化,C++11新语法
	vector<int> v{ 1, 2, 3, 4 };

	// 在指定位置前插入值为val的元素,比如:3之前插入30,如果没有则不插入
	// 1. 先使用find查找3所在位置
	// 注意:vector没有提供find方法,如果要查找只能使用STL提供的全局find
	auto pos = find(v.begin(), v.end(), 3);
	if (pos != v.end())
	{
		// 2. 在pos位置之前插入30
		v.insert(pos, 30);
	}

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

	pos = find(v.begin(), v.end(), 3);
	// 删除pos位置的数据
	v.erase(pos);

	it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

// operator[]+index 和 C++11中vector的新式for+auto的遍历
// vector使用这两种遍历方式是比较便捷的。
void TestVector6()
{
	vector<int> v{ 1, 2, 3, 4 };

	// 通过[]读写第0个位置。
	v[0] = 10;
	cout << v[0] << endl;

	// 1. 使用for+[]小标方式遍历
	for (size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;

	vector<int> swapv;
	swapv.swap(v);

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

	// 2. 使用迭代器遍历
	cout << "swapv data:";
	auto it = swapv.begin();
	while (it != swapv.end())
	{
		cout << *it << " ";
		++it;
	}

	// 3. 使用范围for遍历
	for (auto x : v)
		cout << x << " ";
	cout << endl;
}

四、OJ练习

4.1 只出现一次的数字 I

136. 只出现一次的数字 - 力扣(LeetCode)

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int x = 0;
        for (int i = 0; i < nums.size(); i++) {
            x ^= nums[i];
        }
        return x;
    }
};

4.2 杨辉三角

118. 杨辉三角 - 力扣(LeetCode)

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> vv(numRows);
        for (int i = 0; i < numRows; i++) {
            vv[i].resize(i + 1, 1);
        }
        for (int i = 2; i < numRows; i++) {
            for (int j = 1; j < i; j++) {
                vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
            }
        }
        return vv;
    }
};

4.3 删除有序数组中的重复项

26. 删除有序数组中的重复项 - 力扣(LeetCode)

int removeDuplicates(int* nums, int numsSize) {
    int src = 0, dest = src + 1;
    while (dest < numsSize) {
        if (nums[src] != nums[dest]) {
            nums[++src] = nums[dest++];
        } else
            dest++;
    }
    return src + 1;
}

4.4 只出现一次的数字||

137. 只出现一次的数字 II - 力扣(LeetCode)

class Solution {
public:
    int singleNumber(vector<int> &nums) {
        int ans = 0;
        for (int i = 0; i < 32; i++) {
            int cnt1 = 0;
            for (int x: nums) {
                cnt1 += x >> i & 1;
            }
            ans |= cnt1 % 3 << i;
        }
        return ans;
    }
};

4.5 只出现一次的数字|||

260. 只出现一次的数字 III - 力扣(LeetCode)

class Solution {
public:
    vector<int> singleNumber(vector<int> &nums) {
        unsigned int xor_all = 0;
        for (int x: nums) {
            xor_all ^= x;
        }
        int lowbit = xor_all & -xor_all;
        vector<int> ans(2);
        for (int x: nums) {
            ans[(x & lowbit) != 0] ^= x; // 分组异或
        }
        return ans;
    }
};

4.6 数组中出现次数超过一半的数字

数组中出现次数超过一半的数字_牛客题霸_牛客网

class Solution {
  public:
    int MoreThanHalfNum_Solution(vector<int>& numbers) {
        sort(numbers.begin(), numbers.end());
        return numbers[numbers.size() / 2];
    }
};

五、vector深度剖析及模拟实现

六、使用memcpy拷贝问题

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

void TestVector7() {
	lsl::vector<string> v;
	v.push_back("1111");
	v.push_back("2222");
	v.push_back("3333");
	v.push_back("4444");
	v.push_back("5555");
}

其中reserve的实现:

void reserve(size_t n)
{
    // 检查容量是否满
    if (_finish == _end_of_storage) {

        size_t oldSize = size(); // 保存旧的数据
        T* tmp = new T[n];
        // 注意这里
        if(_start)
            memcpy(tmp, _start, sizeof(T) * oldSize);

        // 释放原来的空间
        delete[] _start;

        // 改变指针
        _start = tmp;
        _finish = _start + oldSize;
        _end_of_storage = _start + n;
    }
}

问题分析:

  1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
  2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

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

正确修改:

void reserve(size_t n)
{
	// 检查容量是否满
	if (_finish == _end_of_storage) {

		size_t oldSize = size(); // 保存旧的数据
		T* tmp = new T[n];
		// 不能使用memcpy,会造成浅拷贝
		//if(_start)
		//	memcpy(tmp, _start, sizeof(T) * oldSize);
		
		if (_start)
			for (size_t i = 0; i < oldSize; i++)
				tmp[i] = _start[i]; // 内置类型直接赋值,自定义类型调用它的赋值重载

		// 释放原来的空间
		delete[] _start;

		// 改变指针
		_start = tmp;
		_finish = _start + oldSize;
		_end_of_storage = _start + n;
	}
}

七、std::vector的核心框架接口的模拟实现vector

#pragma once

#include <iostream>
#include <stdlib.h>
#include <assert.h>

using namespace std;

namespace lsl
{
	template<class T>
	class vector {
		// vector 的迭代器是原生指针
		typedef T* iterator;
		typedef const T* const_iterator;
	public:
		/// <summary>
		/// 迭代器
		/// </summary>
		iterator begin() {
			return _start;
		}
		iterator end() {
			return _finish;
		}
		const_iterator begin() const {
			return _start;
		}
		const_iterator end() const {
			return _finish;
		}

		/// <summary>
		/// 构造与析构
		/// </summary>
		vector()
		{}
		vector(size_t n, const T& val = T()) {
			resize(n, val);
		}

		/*
		* 理论上提供了vector(size_t n, const T& value = T())之后
		* vector(int n, const T& value = T())就不需要提供了,但是对于:
		* vector<int> v(10, 5);
		* 编译器在编译时,认为T已经被实例化为int,而10和5编译器会默认其为int类型
		* 就不会走vector(size_t n, const T& value = T())这个构造方法,
		* 最终选择的是:vector(InputIterator first, InputIterator last)
		* 因为编译器觉得区间构造两个参数类型一致,因此编译器就会将InputIterator实例化为int
		* 但是10和5根本不是一个区间,编译时就报错了
		* 故需要增加该构造方法
		*/
		vector(int n, const T& val = T()) {
			resize(n, val);
		}

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

		vector(initializer_list<T> il) {
			reserve(il.size());
			for (auto& e : il)
				push_back(e);
		}

		vector(const vector<T>& v) {
			// 先开空间
			reserve(v.capacity());
			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(_start, v._start);
		}

		vector<T>& operator=(const vector<T> v) {
			Swap(v);
			return *this;
		}

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

		/// <summary>
		/// 容量相关
		/// </summary>
		size_t size() const {
			return _finish - _start;
		}

		size_t capacity() const {
			return _end_of_storage - _start;
		}
		
		bool empty() const
		{
			return _start == _finish;
		}

		void reserve(size_t n)
		{
			// 检查容量是否满
			if (_finish == _end_of_storage) {

				size_t oldSize = size(); // 保存旧的数据
				T* tmp = new T[n];
				// 不能使用memcpy,会造成浅拷贝
				//if(_start)
				//	memcpy(tmp, _start, sizeof(T) * oldSize);
				
				if (_start)
					for (size_t i = 0; i < oldSize; i++)
						tmp[i] = _start[i]; // 内置类型直接赋值,自定义类型调用它的赋值重载

				// 释放原来的空间
				delete[] _start;

				// 改变指针
				_start = tmp;
				_finish = _start + oldSize;
				_end_of_storage = _start + n;
			}
		}
		
		void resize(size_t n, const T& val = T()) {
			// 如果比size()小直接改变_finish
			if (n < size())
				_finish = _start + n;
			else {
				// 扩容数据
				reserve(n);
				// 拷贝n个val
				while (_finish != _start + n)
					*_finish++ = val;
			}
		}

		/// <summary>
		/// 增删查改
		/// </summary>
		T& operator[](size_t i) {
			assert(i < size());
			return _start[i];
		}
		
		const T& operator[](size_t i) const {
			assert(i < size());
			return _start[i];
		}
		T& front()
		{
			return *_start;
		}

		const T& front()const
		{
			return *_start;
		}

		T& back()
		{
			return *(_finish - 1);
		}

		const T& back()const
		{
			return *(_finish - 1);
		}

		iterator insert(iterator pos, const T& x) {
			assert(pos >= _start);
			assert(pos <= _finish);

			// 发生了扩容,会导致迭代器失效,需要重新更新一下pos
			if (_finish == _end_of_storage) {
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}

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

			*pos = x;
			++_finish;
			return pos;
		}

		// 返回删除数据的下一个数据,解决:一边遍历一边删除的迭代器失效问题
		iterator erase(iterator pos) {
			assert(pos >= _start);
			assert(pos < _finish);
			iterator it = pos + 1;
			while (it < _finish)
			{
				*(it - 1) = *it;
				++it;
			}
			--_finish;
			return pos;
		}

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

		void pop_back() {
			erase(end() - 1);
		}

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

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

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

相关文章

C/C++调用Python程序代码实现混合编程笔记教程

0、引言 Python‌在基础开发、数据科学、人工智能、Web框架开发等领域具有广泛的支持工具和开发教程&#xff0c;极大的缩短了产品原型开发周期、降低了开发难度。 有许多的功能&#xff0c;通过C/C实现&#xff0c;非常的复杂并且不方便&#xff0c;但是Python可能就是几行代码…

LeetCode hot 100—子集

题目 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[1],[2…

Linux网络编程——数据链路层详解,以太网、MAC地址、MTU、ARP、DNS、NAT、代理服务器......

目录 一、前言 二、以太网 二、以太网帧格式 三、 MAC地址 四、MTU 1、数据链路层的数据分片 2、MTU对UDP协议的影响 3、MTU对TCP协议的影响 五、ARP协议 1、什么是ARP 2、ARP的作用 3、ARP协议的工作流程 4、ARP缓存表 5、ARP请求报文 6、中间人 六、DNS&…

基于springboot+vue的秦皇岛旅游景点管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系统展示 用户登录 旅游路…

Linux网络编程——TCP通信的四次挥手

一、前言 上篇文章讲到了TCP通信建立连接的“三次握手”的一些细节&#xff0c;本文再对TCP通信断开连接的“四次挥手”的过程做一些分析了解。 二、TCP断开连接的“四次挥手” 我们知道TCP在建立连接的时需要“三次握手”&#xff0c;三次握手完后就可以进行通信了。而在通…

计算机视觉算法实现——SAM实例分割:原理、实现与应用全景

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​ ​​​​​​​​​ ​​ 1. 实例分割领域概述 实例分割(Instance Segmentation)是计算机视觉领域最具挑战性的任务之一&#xff0c…

基于SpringBoot的宠物健康咨询系统(源码+数据库+万字文档)

502基于SpringBoot的宠物健康咨询系统&#xff0c;系统包含三种角色&#xff1a;管理员、用户&#xff0c;顾问主要功能如下。 【用户功能】 1. 首页&#xff1a;查看系统主要信息和最新动态。 2. 公告&#xff1a;浏览系统发布的公告信息。 3. 顾问&#xff1a;浏览可提供咨询…

vue2 el-element中el-select选中值,数据已经改变但选择框中不显示值,需要其他输入框输入值才显示这个选择框才会显示刚才选中的值

项目场景&#xff1a; <el-table-column label"税率" prop"TaxRate" width"180" align"center" show-overflow-tooltip><template slot-scope"{row, $index}"><el-form-item :prop"InquiryItemList. …

CCF CSP 第35次(2024.09)(2_字符串变换_C++)(哈希表+getline)

CCF CSP 第35次&#xff08;2024.09&#xff09;&#xff08;2_字符串变换_C&#xff09; 解题思路&#xff1a;思路一&#xff08;哈希表getline&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;哈希表getline&#xff09;&#xff09;&#xff1a; …

Docker--利用dockerfile搭建mysql主从集群和redis集群

Docker镜像制作的命令 链接 Docker 镜像制作的注意事项 链接 搭建mysql主从集群 mysql主从同步的原理 MySQL主从同步&#xff08;Replication&#xff09;是一种实现数据冗余和高可用性的技术&#xff0c;通过将主数据库&#xff08;Master&#xff09;的变更操作同步到一个…

蓝桥杯嵌入式考前模块总结

一.RTC 使用RTC直接再cubeMX中配置启动时钟和日历 如第六届省赛 想要让RTC的秒每隔一秒递增1需要在时钟树界面观察RTC的主频 由于RTC时钟主频为32KHZ将异步预分频计数器的值设为31&#xff0c;将同步预分频计数器的值设为999这样就可以将RTC的时钟信号分频为1HZ达到1秒自增的…

关于举办“2025年第五届全国大学生技术创新创业大赛“的通知

赛事含金量 大赛获奖即可有机会为你的大学里的“创新创业”加分&#xff01;这是每个大学要求必须修满的学分&#xff01; 中国“互联网&#xff0b;”大学生创新创业大赛磨刀赛&#xff01;“挑战杯”中国大学生创业计划大赛必参赛&#xff01; 国赛获奖&#xff0c;“互联…

Ingress蓝绿发布

Ingress蓝绿发布 Ingress常用注解说明yaml资源清单绿色版本yml资源清单蓝色版本yaml资源清单 主Ingress金丝雀Ingress基于客户端请求头的流量切分结果验证 基于客户端来源IP的流量切分结果验证 基于服务权重的流量切分结果验证 基于IP来源区域来切分IP---方案未验证基于User-Ag…

基于AOP+Log4Net+AutoFac日志框架

1.项目概述 这是一个基于 C# 的 WPF 项目 WpfApp12log4net&#xff0c;它综合运用了依赖注入、日志记录和接口实现等多种技术&#xff0c;同时使用了 Autofac、Castle.Core 和 log4net 等第三方库。 2.配置log4net 新建一个Log4Net.config&#xff0c;配置需要记录的日志信息…

python推箱子游戏

,--^----------,--------,-----,-------^--,-------- 作者 yty---------------------------^----------_,-------, _________________________XXXXXX XXXXXX XXXXXX ______(XXXXXXXXXXXX(________(------ 0 [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,0,0,0,0,0,0,0,0,0,0,0,…

华为hcie证书的有效期怎么判断?

在ICT行业&#xff0c;华为HCIE证书堪称含金量极高的“敲门砖”&#xff0c;拥有它往往意味着在职场上更上一层楼。然而&#xff0c;很多人在辛苦考取HCIE证书后&#xff0c;却对其有效期相关事宜一知半解。今天&#xff0c;咱们就来好好唠唠华为HCIE证书的有效期怎么判断这个关…

PowerBI 条形图显示数值和百分比

数据表: 三个度量值 销售额 SUM(销量表[销售量])//注意, 因为Y轴显示的产品&#xff0c;会被筛选&#xff0c;所以用ALLSELECTED来获取当前筛选条件下&#xff0c;Y轴显示的产品 百分比 FORMAT(DIVIDE([销售额],CALCULATE([销售额],ALLSELECTED(销量表[产品编码]))),"0…

基于YOLOv8的火车轨道检测识别系统:技术实现与应用前景

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​ ​​​​​​​​​ ​​ 1. 引言&#xff1a;火车轨道检测领域概述 铁路运输作为国民经济的大动脉&#xff0c;其安全运行至关重要…

css使用mix-blend-mode的值difference实现内容和父节点反色

1. 使用场景 往往开发过程中&#xff0c;经常遇到产品说你这个背景图和文字颜色太接近了&#xff0c;能不能适配下背景图&#xff0c;让用户能够看清具体内容是啥。 这么说吧&#xff0c;这种需求场景非常合理&#xff0c;因为你做开发就是要给用户一个交代&#xff0c;给他们…

Pytest多环境切换实战:测试框架配置的最佳实践!

你是否也遇到过这种情况&#xff1a;本地测试通过&#xff0c;一到测试环境就翻车&#xff1f;环境变量错乱、接口地址混乱、数据源配置丢失……这些「环境切换」问题简直像定时炸弹&#xff0c;随时引爆你的测试流程&#xff01; 测试人员每天都跟不同的环境打交道&#xff0…