【C++初阶】模拟实现vector

news2024/12/26 21:26:35

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、简单剖析vector的源码
  • 二、准备工作
  • 三、模拟实现vector常见操作
      • 3.1 无参的默认构造
      • 3.2 获取容量
      • 3.3 获取元素个数
      • 3.4 扩容 + memcpy的浅拷贝问题
      • 3.5 尾插
      • 3.6 迭代器
      • 3.7 析构函数
      • 3.8 operator[]
      • 3.9 插入insert
      • 3.10 删除某个位置的元素erase
      • 3.11 尾删pop_back
      • 3.12 增加/缩减有效数据resize
      • 3.13 拷贝构造 + memcpy浅拷贝
      • 3.14 交换
      • 3.15 赋值运算符重载
      • 3.16 用n个val构造
      • 3.17 用迭代器区间初始化
  • 四、模拟实现源码

一、简单剖析vector的源码

我们知道,vector是一个动态数组,它使用连续的内存存储元素。当我们向vector中插入元素时,如果导致当前内存不足以容纳所有元素,vector会重新分配更大的内存空间,并将所有元素复制到新的内存中。因此 ,vector的结构和string是一样的:

template<class T>

class vector
{
	T* _str;
	size_t _size;
	size_t _capacity;
};

然而vector的源码还是和以上有所差别的(详细源码在评论区):

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

如上所示,我们并不知道源码中的成员变量startfinishend_of_storage是什么,只能知道它们是指针。因此接下来我们得去看它的成员函数,想要快速了解应该重点看构造函数以及插入接口:

  vector() 
  :start(0), 
  finish(0), 
  end_of_storage(0) 
  {}
  
  vector(size_type n, const T& value) 
  { 
  		fill_initialize(n, value); 
  }
  
  vector(int n, const T& value) 
  { 
  		fill_initialize(n, value); 
  }
  
  vector(long n, const T& value) 
  { 	
  		fill_initialize(n, value); 
  }
  
  explicit vector(size_type n) 
  { 
  		fill_initialize(n, T()); 
  }

尴尬的是构造函数看不出什么所以然来,因此接下来可以看插入接口:

 // 尾插
 void push_back(const T* x)
 {
	if (finish != end_of_storage)
	{
		construct(finish, x);
		++finish;
	}
	else
	{
		insert_aux(end(), x);
	}
 }

有插入操作必定涉及到扩容:

 void reserve(size_type n) 
 {
    if (capacity() < n) 
    {
       const size_type old_size = size();
       iterator tmp = allocate_and_copy(n, start, finish);
       destroy(start, finish);
       deallocate();
       start = tmp;
       finish = tmp + old_size;
       end_of_storage = start + n;
    }
 }

从上我们就猜出:

  • start:从名字上来看可能是指向起始位置的指针
  • finish:从尾插可以看出,相当于指向数据的有效个数的指针
  • end_of_storage:从扩容接口可以看出,相当于指向当前容量的指针
    在这里插入图片描述

二、准备工作

为了方便管理代码,分两个文件来写:

  • Test.cpp - 测试代码逻辑
  • vector.h - 模拟实现vector
    在这里插入图片描述

三、模拟实现vector常见操作

3.1 无参的默认构造

namespace wj
{
	template<class T>

	class vector
	{
		typedef T* iterator;

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

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}
  • 为了防止与库的vector冲突,要重新写一个命名空间域wj
  • 要注意初始化列表的顺序,是按照成员变量的顺序来赋值的!

3.2 获取容量

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

指针 - 指针返回的是元素个数。
在这里插入图片描述

3.3 获取元素个数

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

3.4 扩容 + memcpy的浅拷贝问题

vector的扩容并不是原地扩容,而是会重新分配更大的内存空间,并将所有元素复制到新的内存中。并且一般都是只扩容,不缩容。

void reserve(size_t n)
{
	// n > 当前容量则需要扩容
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			// 如果原空间不为空,则拷贝数据
			memcpy(tmp, _start, sizeof(T) * sz);				
			// 释放原空间
			delete[] _start;
		}
		// 再指向新空间
		_start = tmp;
		_finish = _start + sz;
		// 不能像下面这么写
		// 原因是_start指向新空间后size就失效了(可以看看size的实现)
		// 所以应该提前记录size的大小
		/*_finish = _start + size();*/

		_end_of_storage = _start + n;
	}
}

以上代码看似没有问题,其实漏洞百出:

在这里插入图片描述

虽然打印出来了,但是程序还是存在bug

这是一个隐藏的深拷贝:

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

在这里插入图片描述

尾插"55555"就要进行扩容,memcpy拷贝数据到新空间;接下来delete旧空间,而delete的底层先是执行析构函数,完成对象(_str)中资源的清理,然后再释放对象的空间。

接着_start就会指向新拷贝的空间,然后tmp出了作用域会再次调用析构函数,导致_str所指向的内容被析构了两次,因此导致程序报错。

解决方法是:遍历赋值调用赋值重载,实现深拷贝。

void reserve(size_t n)
{
	// 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;
	}
}

3.5 尾插

void push_back(const T& x) 
{
	// 当插入一个数据之前要判断是否扩容
	if (_finish == _end_of_storage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	// 尾插
	*_finish = x; 
	++_finish;
}

3.6 迭代器

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

3.7 析构函数

// 7. 析构
~vector()
{
	if (_start)
	{
		delete[] _start;
		_start = _finish = _end_of_storage = nullptr;
	}
}

vector底层实际上自己实现了一个空间配置器(内存池),而内存池有点复杂,我们选择使用newdelete代替,因此要写析构函数
在这里插入图片描述

3.8 operator[]

// 可读可写
T& operator[](size_t pos)
{
	// 检查下标的有效性
	assert(pos < size());
	return _start[pos];
}

// 可读不可写
const T& operator[](size_t pos) const
{
	assert(pos < size());
	return _start[pos];
}

3.9 插入insert

实现insert在某个位置插入

在这里插入图片描述

特别要注意扩容部分:vector的扩容会导致迭代器失效

iterator insert(iterator pos, const T& x)
{
	// 判断pos的有效性
	assert(pos >= _start && pos <= _finish);
	
	// 可能存在扩容
	if (_finish == _end_of_storage)
	{
		// 防止迭代器失效
		// 提前记录pos的位置
		size_t len = pos - _start; 
		
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);

		// 防止迭代器失效
		// 还原新空间pos的位置
		pos = _start + len; 
	}
	
	// 挪动数据(从最后一个数据往后挪)
	// 1. 记录最后一个数据的位置
	iterator end = _finish - 1;
	// 2, 向后挪动
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	// 3. 插入数据
	*pos = x;
	++_finish;
	
	return pos;
}

【挪动数据】

在这里插入图片描述

3.10 删除某个位置的元素erase

在这里插入图片描述

iterator erase(iterator pos)
{
	// 检查下标的有效性
	assert(pos >= _start && pos < _finish);
	
	// 元素向前移
	iterator it = pos + 1;
	while (it != _finish)
	{
		*(it - 1) = *it;
		++it;
	}
	--_finish;

	return pos;
}

【删除数据】

在这里插入图片描述

3.11 尾删pop_back

void pop_back()
{
	if (_start)
	{
		--_finish;
	}
}

3.12 增加/缩减有效数据resize

void resize(size_t n, const T& val = T()) 
{
	// 相当于删除数据
	if (n < size())
	{
		// 改变_finish即可
		_finish = _start + n;
	}
	else // 否则增加数据
	{
		// 可能存在扩容
		reserve(n);
		while (_finish != _start + n)
		{
			// 填值
			*_finish = val;
			++_finish;
		}
	}
}
  • 为什么第二个参数是:const T& val = T()
    因为resize不给第二个参数默认是0,但缺省值不能直接给0,如果直接给0只有int是适用的,而如果Tstring就不对了。因此这里可以给一个 匿名对象。若Tstring就会调用它的默认构造。

但有人奇怪如果T是内置类型能编译的过吗?理论上是不行的,因为内置类型没有构造函数。但是有了模板以后,C++对内置类型进行了升级,内置类型也支持构造函数

在这里插入图片描述

3.13 拷贝构造 + memcpy浅拷贝

要手动写拷贝构造,默认不写的话是浅拷贝。成员变量会指向同一块空间,会导致析构两次的问题

// v1已知
// vector<int> v2(v1);

vector(const vector<T>& v) //v就是v1
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	// 1. 让v2开一块和v1一样大的空间
	_start = new T[v.capacity()];
	// 2. 拷贝数据
	memcpy(_start, v._start, sizeof(T) * v.size());
	// 3. 调整
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();	
}

reserve接口讲解到:使用memcpy导致string对象的浅拷贝,因此要改掉memcpy

vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	// 以下是传统写法
	_start = new T[v.capacity()];
	// 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.capacity();
}

还有一种写法可以避免以上的问题:

// v1已知
// vector<int> v2(v1);

vector(const vector<T>& v) // v1就是v
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	// 让v2开一块和v1一样大的空间
	reserve(v.capacity());
	for (auto x : v)
	{
		// 往v2插入数据
		push_back(x);
	}
}

3.14 交换

void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

3.15 赋值运算符重载

// v1已知,v2也已知
// v1 = v2

// 传参时,v2传值会调用拷贝构造(深拷贝)
vector<T>& operator=(vector<T> v) 
{
	// this就是v1
	// 直接交换即可
	this->swap(v);
	return *this;
}

3.16 用n个val构造

vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	resize(n, val);
}

可以复用resize,但之前要对成员变量初始化,因为内置类型的指针默认是随机值,不初始化会造成野指针问题。或者可以直接在成员变量给缺省值。

3.17 用迭代器区间初始化

vector(iterator first, iterator last)
{
	while (first != last)
	{
		push_back(*first); 
		++first;
	}
}

很多人会写出以上代码,但是这样写就写死了,因为这样写只能用vector的迭代器初始化

因此,我们在一个类中还可以再套模板

template<class InputIterator> //一个类模板还可以再套模板
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first); 
		++first;
	}
}

在这里插入图片描述

以上代码出现了非法的间接寻址,意思就是不是指针或者不是解引用的对象被解引用,因此以上测试代码调用了迭代器区间初始化的函数。

可是为什么会调用迭代器区间初始化的函数呢?

首先以上两个参数是101,编译器会认为是int类型的,本应该调用用n个val构造,但是其第一个参数是无符号的整型,而迭代器区间初始化的参数是是模板,因此编译器会调用更匹配的函数(迭代器区间初始化),而迭代器本应类似一个指针,这里却是一个普通的变量,解引用就导致了非法的间接寻址。

解决方案:函数重载(库里也是这样实现的)

 // 用n个val构造
vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	resize(n, val);
}
		
vector(int n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{		
		resize(n, val);	
}

四、模拟实现源码

#pragma once

#include<assert.h>

namespace wj
{
	template<class T>

	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		// 默认构造函数(
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

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

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

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

		// 迭代器
		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}


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

		// operator[]
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}

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

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start; 
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + len; 
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;

			return pos;
		}

		// 10. 删除erase
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);

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

			return pos;
		}

		// 11. 尾删
		void pop_back()
		{
			if (_start)
			{
				--_finish;
			}
		}

		// 12. 增加/缩减有效数据
		void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		//  赋值运算符重载
		vector<T>& operator=(vector<T> v) 
		{
			this->swap(v);
			return *this;
		}

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

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

		// 拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			_start = new T[v.capacity()];
			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.capacity();
		}

		// 第二种写法
		// vector<int> v2(v1) 
		/*vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(v.capacity());
			for (auto x : v)
			{
				push_back(x);
			}
		}*/

		// 用n个val构造
		vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			resize(n, val);
		}

		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			resize(n, val);
		}

		// 用迭代器区间初始化

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

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

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

相关文章

【测试】pywinauto的简单使用(安装、常用对象、元素控件、鼠标操作、键盘操作)

1.说明 pywinauto是一个用于自动化Python 模块&#xff0c;适合Windows系统的软件&#xff08;GUI&#xff09;&#xff0c;可以通过Pywinauto遍历窗口&#xff08;对话框&#xff09;和窗口里的控件&#xff0c;也可以控制鼠标和键盘输入&#xff0c;所以它能做的事情比之前介…

鲁图中大许少辉博士八一新书《乡村振兴战略下传统村落文化旅游设计》山东省图书馆典藏

鲁图中大许少辉博士八一新书《乡村振兴战略下传统村落文化旅游设计》山东省图书馆典藏

数据宽度、KEIL汇编和GNU汇编的区别

数据宽度 DCB data control byte(byte) DCW data control half word(short) DCD data control word(int) DCQ data control (long)KEIL汇编和GNU汇编的区别 IDA关于please position the cursor within a funtion的解决 我是在C/C++反编译中出现的问题,因为在动态调试的时候…

ctfshow-web11

0x00 前言 CTF 加解密合集CTF Web合集 0x01 题目 0x02 Write Up 这里要求输入的passwd和session必需要相当&#xff0c;那么我们只要让密码和session都为空即可。 以上

Vue的Ajax请求-axios、前后端分离练习

Vue的Ajax请求 axios简介 ​ Axios&#xff0c;是Web数据交互方式&#xff0c;是一个基于promise [5]的网络请求库&#xff0c;作用于node.js和浏览器中&#xff0c;它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生node.js http模块, 而在…

%f占位符

介绍&#xff1a; %f &#xff0c;用来输出实数&#xff08;包括单双精度&#xff09;&#xff0c;以小数形式输出。 通常情况下&#xff0c;当输入的数值或者打印的数值是float类型数据时&#xff0c;使用%f &#xff0c;当然在精度更高的double数据类型下&#xff0c;也可以…

行业追踪,2023-08-22

自动复盘 2023-08-22 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

Windows端口占用CMD关闭端口(8080被占用)

一、背景 作为一名开发&#xff0c;我们是不是经常遇到端口被占用了&#xff0c;比如80&#xff0c;8080等&#xff0c;但是我们却不知道是那个工程启动&#xff0c;对小白来说&#xff0c;估计会很苦恼&#xff0c;网上搜索也很麻烦处理&#xff0c;网上推荐也是如下步骤&…

springBoot防止重复提交

自定义注解AOPRedis 自定义注解 package com.wzw.config.anno;import java.lang.annotation.*;/*** 自定义注解防止表单重复提交*/ Target(ElementType.METHOD) // 注解只能用于方法 Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期 Documented public interface …

2024届校招:校招必须知道的三件事

校招提前知 提到校招&#xff0c;不少同学受到“金九银十”的影响&#xff0c;认为九、十月份是进行校招的时间段。但实际上&#xff0c;校招的时间越来越提前&#xff0c;上周陆续有央企、国企开启了24届提前批的招聘&#xff0c;打响了24届校招的第一枪。今天给大家整理了校…

Google Guava Cache的使用

1、前言 Google Guava Cache是Google Guava库中的一个缓存框架&#xff0c;用于缓存计算结果、数据或资源&#xff0c;提高程序访问效率和响应速度。Guava Cache具有以下特点&#xff1a; ①可配置性&#xff1a;Guava Cache支持多种缓存参数的配置&#xff0c;例如缓存大小、…

全流程R语言Meta分析核心技术教程

详情点击链接&#xff1a;全流程R语言Meta分析核心技术教程 一&#xff0c;Meta分析的选题与检索 1、Meta分析的选题与文献检索 1)什么是Meta分析&#xff1f; 2)Meta分析的选题策略 3)精确检索策略&#xff0c;如何检索全、检索准 4)文献的管理与清洗&#xff0c;如何制定文…

【Winform学习笔记(八)】通过委托实现跨窗体传值

通过委托实现跨窗体传值 前言正文1、委托及事件2、通过委托实现跨窗体传值的步骤1.在子窗体中定义委托2.在子窗体中声明一个委托类型的事件3.调用委托类型事件4.在实例化子窗体后&#xff0c;子窗体订阅事件接受方法5.实现具体的事件 3、具体示例4、完整代码5、实现效果 前言 …

cesium官网链接打不开

经常遇到cesium官网的某些链接打不开的情况 比如下面是一个cesium官网的blog链接 https://cesium.com/blog/2015/08/10/Introducing-3D-Tiles/ 可是打不开 解决办法&#xff1a; 把url中所有大写的字母都改成小写 大写的地方用黑体标出&#xff1a;Introducing-3D-Tiles …

SAP SPL(Special Ledger)之注释行项目-Noted Items

财务凭证过账里常见的SPL特殊总账标识根据业务主要有三种&#xff0c;BoE-billing of exchange: 汇票业务&#xff0c;包括商业汇票和银行汇票&#xff1b;Down Payment&#xff0c;预付款业务&#xff0c;包括供应商和客户预付款和申请&#xff1b;其它&#xff0c;一般是保证…

2022年12月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;鸡兔同笼 一个笼子里面关了鸡和兔子(鸡有2只脚&#xff0c;兔子有4只脚&#xff0c;没有例外)。已经知道了笼子里面脚的总数a&#xff0c;问笼子里面至少有多少只动物&#xff0c;至多有多少只动物。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 输入…

高压放大器在液晶弹性体中的应用研究

液晶弹性体是一种有机高分子材料&#xff0c;具有良好的可控变形性能和反应速度&#xff0c;因此在显示器、光学器件等领域得到了广泛的应用。高压放大器作为一种电子设备&#xff0c;可以将输入信号进行放大&#xff0c;从而为液晶弹性体的驱动提供足够的强度。下面安泰电子将…

安捷伦DSO9404A示波器

DSO9404A DSO9404A 是 Agilent 的 4 GHz、4 通道数字示波器。随时间测量电子电路或组件中的电压或电流信号&#xff0c;以显示幅度、频率和上升时间等。应用包括故障排除、生产测试和设计。 壹捌叁贰零玖壹捌陆伍叁 附加的功能&#xff1a; 带宽&#xff1a;4 GHz 4个频道 大…

连接校园网或需要认证的网,认证页面弹不出来解决方案

网络设置 在设置中&#xff0c;找到网络和Internet&#xff1b; 左侧选择状态&#xff0c;右侧选择更改适配器选项&#xff1b; 选择需要认证的那个适配器 右键打开属性 勾上ipv4 和 ipv6 (ipv6 不是必须的) 打开ipv4的属性 按照如下进行设置 关闭代理 发现认证…

MySQL详细安装与配置

免安装版的Mysql MySQL关是一种关系数据库管理系统&#xff0c;所使用的 SQL 语言是用于访问数据库的最常用的 标准化语言&#xff0c;其特点为体积小、速度快、总体拥有成本低&#xff0c;尤其是开放源码这一特点&#xff0c;在 Web 应用方面 MySQL 是最好的 RDBMS(Relation…