C++标准模板库--vector

vector

介绍

        vector(向量)是一种序列容器,表示为可以改变大小的数组。vector中的元素使用连续的存储位置,这意味着也可以使用指向其元素的常规指针+偏移量来访问任意元素,且与数组一样高效。但与数组不同的是,vector的大小可以动态变化,容器会自动处理他们的存储空间。在其内部,vector使用动态分配的数组来存储他们的元素。当插入新元素需要增大容量时,存储空间需要重新分配,这意味着需要分配一个新数组并将所有元素移动到其中。就处理时间而言,这是一个相对昂贵的任务,因此vector不会在每次想容器中添加元素时都重新分配。相反,vector容器可以分配一些额外的存储空间,以适应可能的增长,因此容器的实际容量肯呢个大于包含其元素严格所需的存储空间。因此与数组相比,向量消耗更多的内存,以换取更有效的方式管理存储和动态增长的能力。与其他动态序列容器(deque、列表、前向列表)相比,vector可以非常高效地访问其元素,并且可以相对高效地从其末端添加或删除元素。对于涉及在末尾以外的位置插入或删除元素的操作,他们的性能比其他操作差,并且迭代器引用的一致性不如列表和前向列表。

        以上是官方对于vector的介绍,从上面我们能够了解到vector本质上就是一种可动态增长的顺序表,其特性与数据结构中学习的顺序表相差无几,但vector的出现极大地简化了我们使用顺序表的工作量,且vector与其他容器有着较深的联系,因此学好vector有利于我们未来的学习。


vector的使用

         对于vector,在学习时我们一定要学会查看文档:vector文档,vector在实际中非常重要,我们要熟悉常见的接口,以应对未来的使用。

(constructor)构造函数

函数声明接口说明
vector();无参构造
vector(size_t n, const T& val = T());构造并初始化n个val
vector(const vector& x);拷贝构造
vector(inputiterator first, inputiterator last);使用迭代器进行区间初始化构造

代码演示:

	//构造5个int的空间
	vector<int> v1(5);
	//构造10个int空间,并初始化为1
	vector<int> v2(10, 1);
	//去掉v2的第一个和最后一个空间,使用剩余空间构造并用相同数值初始化
	vector<int> v3(++v2.begin(), --v2.end());
	//拷贝构造
	vector<int> v4 = v3;

iterator迭代器的使用

函数名称接口说明
begin+end获取第一个数据位置的iterator/const_iterator,获取最后一个数据的下一个位置的iterator/const_iterator.
rbegin+rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

 代码演示:

vector<int> v(10, 1);
// []下标形式
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;

//反向迭代器
vector<int>::reverse_iterator rit = v.rbegin();
while(rit != v.rend())
{
    cout << *rit << " ";
    rit++;
}
cout << endl;

//范围for
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;

空间管理函数

函数名称接口说明
size

获取数据个数

capacity获取容量大小
empty判断是否为空
resize改变vector的_size
reserve改变vector的capacity

注意:

  • capacity函数在vs和g++下的扩容机制略有不同,vs下capacity是按1.5倍增长的,g++下是按2倍增长的。在面试中经常会考察这个问题,不要固化地认为vector的扩容都是2倍,具体增长多少是根据具体的需求来定义的。vs是PJ版本STL,g++是SGI版本STL。
  • reserve函数只负责开空间,如果明确知道需要开多少空间,reserve就可以一次性开好,缓解了多次扩容的代价。
  • resize在开空间的同时还会进行初始化,影响_size。

扩容机制测试代码:

// 测试vector的默认扩容机制
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';
		}
	}
}

 内存管理函数代码演示:

    //reserve
	vector<int> v(10, 1);
	cout << "size:" << v.size() << " " << "capacity:" << v.capacity() << endl;
	v.reserve(20);
	cout << "size:" << v.size() << " " << "capacity:" << v.capacity() << endl;
	v.reserve(15);
	cout << "size:" << v.size() << " " << "capacity:" << v.capacity() << endl;
	v.reserve(5);
	cout << "size:" << v.size() << " " << "capacity:" << v.capacity() << endl;

    //resize
	vector<int> v(10, 1);
	cout <<"size:" << v.size() << " " << "capacity:" << v.capacity() << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	v.resize(15,2);
	cout << "size:" << v.size() << " " << "capacity:" << v.capacity() << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	v.resize(25, 3);
	cout << "size:" << v.size() << " " << "capacity:" << v.capacity() << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	v.resize(5);
	cout << "size:" << v.size() << " " << "capacity:" << v.capacity() << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

增删查改

函数名称接口说明
push_back尾插
pop_back尾删
find查找(不是vector的成员接口)
insert在pos之前插入val
erase

删除pos位置的数据

swap交换两个vector对象的数据空间
operator[]像数组一样访问

代码演示:

vector<int> v(5, 1);
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;
//尾删
v.pop_back();
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;
//尾插
v.push_back(5);
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;
//pos位置之前插入
v.insert(v.begin(), 4);
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;
//寻找第一个为1数据的位置
vector<int>::iterator pos = find(v.begin(),v.end(),1);
//删除pos位置的数据
v.erase(pos);
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;
// operator[]的调用
v[2] = 9;
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;
//swap交换函数
vector<int> v1(5, 2);
swap(v, v1);
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;

vector<vector<int>>

        vector<vector<int>>是C++中的动态二维数组结构,是一个vector的集合,其中每个元素都是一个vector<int>,用于存储整数。对比c语言中的二维数组,两者的存储方式与索引访问方法是一样的,不同指出在于:1、vector的动态性(vector<vector<int>>可以动态调整大小,支持在运行时添加或删除行),而二维数组的大小需要在编译时固定。2、内存管理方面,vector会自动管理内存,避免了内存泄漏,而二维数组需要手动管理(特别是在动态分配内存时)。3、便利性:vector中提供了许多遍历的成员函数(如size()、push_back()等),使用更加灵活。

例题:

        给定一个非负整数 numRows生成「杨辉三角」的前 numRows 行。在「杨辉三角」中,每个数是它左上方和右上方的数的和。

 C++的vector写法:

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

        在使用vector<vector<int>>做这道题时,我们可以发现每一列的数组是动态开辟的,这样极大的简化了代码量,且在遍历访问数组赋值时,由于resize初始化可以给1,我们在下面的遍历赋值中可以省去第一二行和每一行的首尾的赋值操作。

vector底层的模拟实现

框架:

template<class T>
class vector
{
public:

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

默认成员函数的实现:

//构造
/*vector()
{}*/
vector() = default;		// C++11 强制生成默认构造
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);
	}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}
//拷贝构造
vector(const vector<T>& v)
{
	reserve(v.size());
	for (auto& e : v)
	{
		push_back(e);
	}
}
//swap
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<T>& operator=(vector<T> tmp)
{
	swap(tmp);
	return *this;
}
//析构函数
~vector()
{
	delete[] _start;
	_start = _finish = _end_of_storage = nullptr;
}

        上面的代码中实现了一个简单的 vector 类的构造函数、拷贝构造、析构函数和一些成员函数。

  1. 无参默认构造函数:使用 C++11 的特性生成一个默认构造函数。这里也可以写一个空的默认构造函数,我们只要在变量声明时给了缺省值,就无需再给默认值初始化。
  2. 带参默认构造函数:vector(size_t n, const T& val = T())vector(int n, const T& val = T()):这两个构造函数用于创建一个包含 n 个元素、初始值为 valvector。使用 reserve(n) 预留空间,随后通过 push_back(val)vector 中添加元素。
  3. 迭代器区间构造函数:接受两个迭代器,复制 [first, last) 范围内的元素到 vector 中。
  4. 拷贝构造函数:通过 reserve 分配足够的空间,然后逐个使用 push_back 复制原 vector 的元素。
  5. 交换函数:通过交换内部指针 _start_finish_end_of_storage 实现高效的资源交换。
  6. 赋值运算符重载:使用拷贝并交换的技术实现赋值运算符,避免多次资源管理,确保安全性和效率。
  7. 析构函数:清理分配的内存,将指针置为空,防止悬挂指针。

迭代器的实现:

//迭代器
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 中的元素。

  1. iteratorconst_iterator 分别定义了指向元素的普通指针和常量指针类型,即普通迭代器和const迭代器。
  2. begin() 返回指向 vector 第一个元素的迭代器(_start)。
  3. end() 返回指向 vector 最后一个元素后一个位置的迭代器(_finish),用于表示迭代的结束。
  4. 常量版本的 begin()end() 允许在 const 对象上使用迭代器,保证不会修改 vector 中的元素。

空间管理函数:

//empty
bool empty()
{
	if (_start == _finish)
		return true;
	else
		return false;
}

// size和capacity
size_t size() const
{
	return _finish - _start;
}
size_t capacity() const
{
	return _end_of_storage - _start;
}
// reserve
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_size = size();
		T* tmp = new T[n];
		//memcpy(tmp, _start, old_size * sizeof(T));
		for (size_t i = 0; i < old_size; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;

		_start = tmp;
		_finish = _start + old_size;
		_end_of_storage = _start + n;
	}
}
//resize
void resize(size_t n, T val = T())
{
	if (n < _finish)
	{
		_finish = _start + n;
	}
	else
	{
		reserve(n);
		while (_finish < _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

        上面代码实现了一些常用的 vector 成员函数,包括检查是否为空、获取大小和容量、以及调整容量和大小的功能。

  1. empty:检查 vector 是否为空,判断 begin()end() 是否相同。如果相同,返回 true,表示没有元素;否则返回 false
  2. size() :返回当前 vector 中元素的数量(_finish_start 的差)。
  3. capacity(): 返回当前分配的空间大小(_end_of_storage_start 的差),表示在不重新分配内存的情况下,最多能容纳多少元素。
  4.  reserve(n):用于调整 vector 的容量,如果 n 大于当前容量,重新分配内存并复制原有元素到新内存中,释放旧内存。这里需要注意如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。
  5. resize(n, val): 调整 vector 的大小:如果新大小 n 小于当前大小,更新 _finish;如果 n 大于当前大小,首先调用 reserve(n) 确保有足够的容量,然后在新增加的元素位置填充 val

增删查改:

//尾插
void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	*_finish = x;
	++_finish;
}
//尾删
void pop_back()
{
	if (!empty());
	--_finish;
}
//insert
void 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 : 2 * capacity());
		pos = _start + len;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = x;
	++_finish;
}
//erase
void erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);
	
	iterator end = pos + 1;
	while (end < _finish)
	{
		*(end - 1) = *end;
		end++;
	}
	_finish--;
}

// operator[]
T& operator[](int i)
{
	return _start[i];
}
const T& operator[](int i) const
{
	return _start[i];
}

        上面代码实现了 vector 的一些基本操作,包括尾插、尾删、插入、删除元素,以及重载下标运算符。

  1. push_back(x):vector 的末尾添加一个元素 x。如果当前容量已满,调用 reserve() 扩大容量(初始为 4,之后每次扩展为当前容量的两倍),随后将新元素赋值到 _finish 指向的位置,然后递增 _finish
  2. pop_back() :从 vector 的末尾删除一个元素。先检查是否为空(条件判断有一个多余的分号,应去掉),然后递减 _finish即可。
  3. insert(pos, x): 在指定位置 pos 插入元素 x。先检查 pos 是否有效。如果当前容量已满,扩容并更新 pos 的指向。通过向后移动元素,为新元素留出空间,最后将 x 放入 pos 位置,并递增 _finish
  4. erase(pos): 删除指定位置的元素。先检查 pos 是否有效。将 pos 后面的元素向前移动,覆盖 pos 位置的元素,最后递减 _finish
  5. 提供对 vector 元素的随机访问。非常量版本返回可修改的元素引用,常量版本返回常量引用,保证元素不能被修改。

完整代码:

vector.h

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

// 模板不能把声明和定义分离,因此只定义了一个.h文件。

namespace zy
{
//vctor<vector<int>>
	template<class T>
	class vector
	{
	public:
		//构造
		/*vector()
		{}*/
		vector() = default;		// C++11 强制生成默认构造
		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);
			}
		}
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		//拷贝构造
		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}
		//swap
		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<T>& operator=(vector<T> tmp)
		{
			swap(tmp);
			return *this;
		}
		//析构函数
		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
		//迭代器
		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;
		}
		//empty
		bool empty()
		{
			if (_start == _finish)
				return true;
			else
				return false;
		}

		// size和capacity
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		// reserve
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = size();
				T* tmp = new T[n];
				//memcpy(tmp, _start, old_size * sizeof(T));
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;

				_start = tmp;
				_finish = _start + old_size;
				_end_of_storage = _start + n;
			}
		}
		//resize
		void resize(size_t n, T val = T())
		{
			if (n < _finish)
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		//尾插
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = x;
			++_finish;
		}
		//尾删
		void pop_back()
		{
			if (!empty());
			--_finish;
		}
		//insert
		void 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 : 2 * capacity());
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = x;
			++_finish;
		}
		//erase
		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			
			iterator end = pos + 1;
			while (end < _finish)
			{
				*(end - 1) = *end;
				end++;
			}
			_finish--;
		}

		// operator[]
		T& operator[](int i)
		{
			return _start[i];
		}
		const T& operator[](int i) const
		{
			return _start[i];
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	template<class T>
	void print_vector(const vector<T>& v)
	{
		// 规定,不能在没有实例化的类模板里面取东西,编译器不能区分这里const_iterator是类型还是静态成员变量
		// 想取东西需要在开头加typename,或者使用auto自动判断类型
		//typename vector<T>::const_iterator it = v.begin();
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

	void vector_test1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		//v.push_back(5);
		v.insert(v.begin() + 1, 6);
		print_vector(v);

		auto it = v.begin();
		while (it != v.end())
		{
			//使用insert和erase以后,就不能直接访问,需要先更新这个迭代器的值。
			//因为在vs的标准库中是会报错的,因为发生了迭代器失效
			if ((*it) % 2 == 0)
			{
				//it = v.erase(it);
				v.erase(it);
			}
			else {
				++it;
			}
		}
		print_vector(v);
	}
};

vector迭代器失效问题

        迭代器的主要作用就是让算法能够忽略底层数据结构的差异,可直接对容器进行遍历。其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T*。因此迭代器失效实际上就是迭代器底层对应的指针所指向的空间被销毁了或被修改了,导致迭代器指向的是一块已经被释放的空间或被修改后的空间,造成的后果是程序崩溃或取值不对。

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

  • 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
#include <iostream>
using namespace std;
#include <vector>
int main()
{
    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;
    return 0;
}
  •  指定位置元素的删除操作--erase
#include <iostream>
using namespace std;
#include <vector>
int main()
{
    int a[] = { 1, 2, 3, 4 };
    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; // 此处会导致非法访问
    return 0;
}

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

以本题为例:删除vector中所有的偶数。

#include <iostream>
using namespace std;
#include <vector>
int main()
{
    vector<int> v{ 1, 2, 3, 4 };
    auto it = v.begin();
    while (it != v.end())
    {
        if (*it % 2 == 0)
            v.erase(it);
        ++it;
    } 
    return 0;
}

        这样写就会导致迭代器失效的问题,在删除it位置的元素后,后面的元素会先前覆盖,也就是说此时 it 又指向了下一个元素,然后it++,导致跳过了一个元素,有可能导致数据删除的不干净。解决办法是每次删除元素后,返回一个迭代器给it,在删除元素时跳过++it,这样就避免了‘遍历时有元素被跳过。

#include <iostream>
using namespace std;
#include <vector>
int main()
{
    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;
    } 
    return 0;
}

        值得注意的是:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。且与vector类似,string在插入+扩容操作和erase之后,迭代器也会失效。

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


 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

React01 开发环境搭建

React 开发环境搭建 一、创建 React 项目二、项目精简 一、创建 React 项目 执行下述命令创建 react 项目 blu-react-basis npx create-react-app blu-react-basis项目目录结构如下&#xff1a; 执行下述命令启动项目 npm run start启动效果如下&#xff1a; 二、项目精简 …

51单片机的万年历【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块时钟模块按键蜂鸣器等模块构成。适用于电子万年历、数字时钟万年历等相似项目。 可实现功能: 1、LCD1602实时显示年月日星期和北京时间&#xff0c;具备闰年判断功能 2、按键可设置闹钟时间 3、按键可修改当前时…

案例-登录认证

案例-登录认证 在前面的课程中&#xff0c;我们已经实现了部门管理、员工管理的基本功能&#xff0c;但是大家会发现&#xff0c;我们并没有登录&#xff0c;就直接访问到了Tlias智能学习辅助系统的后台。 这是不安全的&#xff0c;所以我们今天的主题就是登录认证。 最终我们…

redo文件误删除后通过逻辑备份进行恢复

问题描述 开发同事让在一个服务器上查找下先前库的备份文件是否存在&#xff0c;如果存在进行下恢复。翻了服务器发现备份文件存在&#xff0c;多愁了一眼竟翻到了该备份文件于2024.6.17日恢复过的日志&#xff0c;赶紧和开发沟通说2024.6.17号已经恢复过了为啥还要恢复&#x…

空间大数据的数据变换与价值提炼

在数字化时代&#xff0c;空间大数据正成为推动社会经济发展的关键因素。空间大数据不仅体量巨大&#xff0c;而且具有高速流转、多样类型和真实性等特点&#xff0c;它们在获取、存储、管理、分析方面超出了传统数据库软件工具的能力范围。地理信息系统&#xff08;GIS&#x…

AWS账号与邮箱的关系解析

在当今数字化时代&#xff0c;云计算服务的普及使得越来越多的企业和个人用户开始使用亚马逊网络服务&#xff08;AWS&#xff09;。作为全球领先的云服务平台&#xff0c;AWS为用户提供了丰富的计算、存储和数据库服务。然而&#xff0c;对于许多新用户来说&#xff0c;关于AW…

openresty通过header_filter_by_lua记录特定的请求头和特定的响应头到日志文件

有时我们希望记录特定的请求头信息和特定的响应头信息,以便能够通过关联请求信息和响应头信息,来实现记录请求和响应的对应关系。这里通过逐步尝试和优化的方式进行尝试。具体包括将需要的请求头和响应头组织到一条日志记录,输出到单独的错误日志文件记录等的配置尝试。 1.…

C语言中的文件操作:从基础到深入底层原理

文件操作是几乎所有应用程序的重要组成部分&#xff0c;特别是在系统级编程中。C语言因其高效、灵活以及接近硬件的特点&#xff0c;成为了文件操作的理想选择。本文将全面深入地探讨C语言中的文件操作&#xff0c;从文件系统的概念到具体的文件操作函数&#xff0c;再到底层的…

c++的哈希表、哈希桶的介绍与实现

目录 前言 哈希概念 哈希冲突 哈希函数 哈希冲突解决 闭散列 —— 开放定址法 开散列 —— 链地址法&#xff08;拉链法、哈希桶&#xff09; 哈希表的闭散列实现 哈希表的结构 哈希表的仿函数 哈希表的插入 哈希表的查找 哈希表的删除 哈希表的开散列实现&#xff…

如何查看默认网关地址:详细步骤

在日常的网络配置与故障排查中&#xff0c;了解并正确查看默认网关地址是一项基础且至关重要的技能。默认网关是连接本地网络与外部网络&#xff08;如互联网&#xff09;的关键节点&#xff0c;它扮演着数据包转发的重要角色。无论是家庭网络、办公室网络还是更复杂的网络环境…

MySQL:基于Spring监听Binlog日志

binlog的三种模式 MySQL 的二进制日志&#xff08;binlog&#xff09;有三种不同的格式&#xff0c;通常被称为 binlog 模式。这三种模式分别是 Statement 模式、Row 模式和Mixed 模式。 Statement 模式&#xff1a; 在 Statement 模式下&#xff0c;MySQL 记录每个会更改数…

【编程语言】Kotlin快速入门 - 类与对象

类的组成 Kotlin中类的基本组成可写成以下的形式&#xff0c;Kotlin也是使用class关键字声明一个类的&#xff0c;我在此也同时加入了name和age两个字段。 class Student: Person() {var name ""var age 1fun eat() {println("$name is $age")} }Kotli…

WebGL编程指南 - 入门续

相关内容&#xff1a;在attribute变量传递参数的基础上&#xff0c;通过JavaScript获取鼠标事件的坐标&#xff0c;再经过坐标转换传递给attribute变量&#xff1b;Web颜色缓冲区每次绘制之后都会重置相关函数&#xff1a;JavaScript鼠标事件onmousedown/onmouseup/onclick htm…

0基础学java之Day09(下午完整版)

六、数组 概念&#xff1a; 1.数组是引用数据类型 2.数组中的数据叫做元素 3.元素都有标号叫做索引/下标 4.下标从0开始 5.数组一旦初始化成功&#xff0c;长度不可变&#xff08;意味着数组没有添加和删除&#xff09; 6.数组中的元素在内存中是挨在一起的 声明&#xff1a; 数…

数据结构与算法 - 树 #数的概念 #二叉树 #堆 - 堆的实现/堆排序/TOP-K问题

文章目录 前言 一、树 (一)、概念 1、树的定义 (二)、树的定义 1、树为什么是递归定义的&#xff1f; 2、如何定义树(如何表达一棵树) 解决方案一&#xff1a;假设我们得知该树的度 解决方案二&#xff1a;顺序表 解决方案三&#xff1a;左孩子右兄弟表示法 二、二叉…

人工智能是否会取代人类的工作吗? 就业方向该如何抉择

人工智能是否会取代人类的工作一直是备受关注的话题。目前来看&#xff0c;人工智能在某些方面确实展现出了强大的能力&#xff0c;但要说完全取代人类还不太可能。 一、人工智能的优势 高效处理大量数据 人工智能可以快速处理和分析海量数据&#xff0c;例如在金融领域进行风…

linux------缓冲区与C库的原理

前言 一、缓冲区 缓冲区的作用是提高效率&#xff0c;因为将数据写入到设备&#xff0c;是需要调用系统接口的&#xff0c;如果每次写入缓冲区的数据就调用一次系统调用&#xff0c;涉及到系统调用这时操作系统就会介入&#xff0c;用户态转为内核态&#xff0c;这个过程需要时…

解决PyCharm 2023 Python Packages列表为空

原因是因为没有设置镜像源 展开 > 之后&#xff0c;这里 点击齿轮 添加一个阿里云的源 最后还需要点击刷新 可以选择下面的任意一个国内镜像源&#xff1a; 清华&#xff1a;https://pypi.tuna.tsinghua.edu.cn/simple 阿里云&#xff1a;http://mirrors.aliyun.com/…

Vue3 集成Monaco Editor编辑器

Vue3 集成Monaco Editor编辑器 1. 安装依赖2. 使用3. 效果 Monaco Editor &#xff08;官方链接 https://microsoft.github.io/monaco-editor/&#xff09;是一个由微软开发的功能强大的在线代码编辑器&#xff0c;被广泛应用于各种 Web 开发场景中。以下是对 Monaco Editor 的…