【C++】学习笔记——string_5

文章目录

  • 六、string类
    • 7. string类的模拟实现
    • 8. string类的模拟实现的完整代码
      • string.h头文件
      • test.c源文件
    • 9. string收尾
      • 写时拷贝
  • 未完待续


六、string类

7. string类的模拟实现

我们之前讲了实现 insert ,但是那个插入函数仅仅是在 pos 位置插入一个字符而且,我们并没有实现在 pos 位置插入一个字符串。所以我们现在将其补充上。

// 在 pos 位置插入一个字符串
void insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	// 空间不够需要扩容
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	size_t end = _size + len;
	while (end >= pos + len)
	{
		_str[end] = _str[end - len];
		--end;
	}
	strncpy(_str + pos, str, len);
}

当难以理解的时候,记得画图哦,我们来看看结果:

#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	s.insert(4, "AAAAAAA");
	std::cout << s.c_str() << std::endl;
	return 0;
}

在这里插入图片描述
没问题。其实我们发现,有了 insert 之后,前面的 push_back()append 好像可以复用这的代码,我们来调整一下。

void push_back(char ch)
{
	//if (_size == _capacity)
	//{
	//	// 扩容2倍
	//	reserve(_capacity == 0 ? 4 : 2 * _capacity);
	//}

	//_str[_size] = ch;
	//++_size;
	//_str[_size] = '\0';
	insert(_size, ch);
}

void append(const char* str)
{
	//size_t len = strlen(str);
	//if (_size + len > _capacity)
	//{
	//	reserve(_size + len);
	//}

	 从末尾开始,拷贝新字符串
	//strcpy(_str + _size, str);	
	//_size += len;
	insert(_size, str);
}

接下来我们再把 swap 给实现一下,有人会说哈,swap 在库里面就有,为啥还要我们手动实现呢?我给大家看看库里的 swap 是怎样的。
在这里插入图片描述
我们发现,库里的swap整整调用了3次拷贝和1次析构,虽然能用,但是它非常搓,所以我们要实现一个对我们来说更加好用的 swap

void swap(string& s)
{
	// 调用库里的swap,直接交换成员即可,不需要创建一个新的对象
	std::swap(_str, s._str);
	// 加上std:: 是让其直接去std域找,避免找到当前的成员函数swap
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	string s1("1234567890");
	
	s.swap(s1);
	std::cout << s.c_str() << std::endl << s1.c_str() << std::endl;
	return 0;
}

在这里插入图片描述
既然库里的 swap 对我们来说很挫,但是我们应该怎样才能防止别人使用库里的函数呢?
在这里插入图片描述
库里面有个 swap ,成员函数有个 swap ,这里怎么还有一个非成员函数的 swap
在这里插入图片描述
这里的 swap 原来直接调用的是成员函数 swap ,那么非成员函数的 swap 是全局的,库里的 swap 是全局的,为啥没发生冲突?为啥就会先用非成员函数的 swap ?我在模板那篇提到过,因为 库里的 swap 是模板当有现成的函数时,优先使用现成的。这下就完美解决了使用库里的swap了。

// string类外
void swap(string& x, string& y)
{
	x.swap(y);
}

接下来实现 find 函数。

// 查找一个字符
size_t find(char ch, size_t pos = 0) const
{
	for (size_t i = pos; i < _size; ++i)
	{
		if (_str[i] == ch)
			return i;
	}

	return npos;
}
// 查找一个字串
size_t find(const char* sub, size_t pos = 0) const
{
	assert(pos < _size);
	const char* p = strstr(_str + pos, sub);
	// 找到了
	if (p)
	{
		return p - _str;
	}
	// 没找到
	return npos;
}

接下来实现 substr 函数。
在这里插入图片描述

string substr(size_t pos = 0, size_t len = npos)
{
	string sub;
	if (len >= _size - pos)
	{
		for (size_t i = pos; i < _size; ++i)
		{
			sub += _str[i];
		}
	}
	else
	{
		for (size_t i = pos; i < pos + len; ++i)
		{
			sub += _str[i];
		}
	}
	return sub;
}

验证验证:

#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	
	size_t pos = s.find(',');
	std::cout << s.substr(pos, 3).c_str() << std::endl;
	return 0;
}

在这里插入图片描述
再就是重载比较。

// 重载成全局函数
bool operator==(const string& s1, const string& s2)
{
	// strcmp 若是相等则返回0
	int ret = strcmp(s1.c_str(), s2.c_str());
	return ret == 0;
}

bool operator<(const string& s1, const string& s2)
{
	int ret = strcmp(s1.c_str(), s2.c_str());
	return ret < 0;
}

bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}

bool operator>(const string& s1, const string& s2)
{
	return !(s1 <= s2);
}

bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}

bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

实现流插入和流提取:

using namespace std;

ostream& operator<<(ostream& out, const string& s)
{
	// 没有访问私有成员,不需要友元
	for (auto ch : s)
	{
		out << ch;
	}

	return out;
}

istream& operator>>(istream& in, string& s)
{
	// 需要将其内容清空
	s.clear();
	char ch;
	// cin 读取不到 ' ' 和 '\n'
	ch = in.get();
	// 减少扩容
	char buff[128];
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[127] = '\0';
			s += buff;
			i = 0;
		}

		ch = in.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}

	return in;
}

顺便实现 clear

// 成员函数
void clear()
{
	_size = 0;
	_str[_size] = '\0';
}

检验检验:

#include"string.h"
using namespace my;

int main()
{
	// 刚刚展开了std,这里避免冲突
	my::string s;
	cin >> s;
	cout << s;
	return 0;
}

在这里插入图片描述
很好,空格前的都被读取到了。
再来实现 getline 。getline就是读取一行嘛,相信实现了流提取运算符,实现一个 getline 肯定非常轻松。

istream& getline(istream& in, string& s)
{
	// 需要将其内容清空
	s.clear();
	char ch;
	// cin 读取不到 ' ' 和 '\n'
	ch = in.get();
	// 减少扩容
	char buff[128];
	size_t i = 0;
	while (ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[127] = '\0';
			s += buff;
			i = 0;
		}

		ch = in.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}

	return in;
}

再来检验:

#include"string.h"
using namespace my;

int main()
{
	my::string s;
	getline(cin, s);
	cout << s;
	return 0;
}

在这里插入图片描述

在实现拷贝构造函数时,我们写了一个非常传统的写法,这里再给大家实现一种新式写法:

// 传统写法
string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

// 新式写法
string(const string& s)
{
	string tmp(s._str);
	swap(tmp);
}

这里新式写法本质上就是调用构造函数,然后让 this指针 指向新的构造的 sring 类 。同样,赋值重载也能使用新式写法。

// 传统写法
string& operator=(const string& s)
{
	char* tmp = new char[s._capacity + 1];
	strcpy(tmp, s._str);
	
	delete[] _str;
	_str = tmp;
	_size = s._size;
	_capacity = s._capacity;

	return *this;
}

// 新式写法
string& operator=(const string& s)
{
	string tmp(s);
	swap(tmp);

	return *this;
}

这里赋值重载的新式写法还能优化(行数),既然在函数内部要调用拷贝构造,为什么不在传参的时候直接调用拷贝构造呢?

string& operator=(string s)
{
	swap(s);

	return *this;
}

8. string类的模拟实现的完整代码

string.h头文件

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

namespace my
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}

		// 默认是空串而不是空指针
		string(const char* str = "")
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		//string(const string& s)
		//{
		//	_str = new char[s._capacity + 1];
		//	strcpy(_str, s._str);
		//	_size = s._size;
		//	_capacity = s._capacity;
		//}

		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);
		}

		//string& operator=(const string& s)
		//{
		//	char* tmp = new char[s._capacity + 1];
		//	strcpy(tmp, s._str);
		//	
		//	delete[] _str;
		//	_str = tmp;
		//	_size = s._size;
		//	_capacity = s._capacity;

		//	return *this;
		//}

		string& operator=(string s)
		{
			swap(s);

			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		// 加 const 使其成为 const 成员函数,使 const 对象也能调用这个函数
		size_t size() const
		{
			return _size;
		}

		// 引用返回,可读可写
		inline char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		// 针对 const对象 的可读不可写,加 & 是为了减少拷贝
		inline const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

		size_t capacity() const
		{
			return _capacity;
		}

		void reserve(size_t n)
		{
			// 只有要扩容的大小比当前容量大才能扩容
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			//if (_size == _capacity)
			//{
			//	// 扩容2倍
			//	reserve(_capacity == 0 ? 4 : 2 * _capacity);
			//}

			//_str[_size] = ch;
			//++_size;
			//_str[_size] = '\0';

			insert(_size, ch);
		}

		void append(const char* str)
		{
			//size_t len = strlen(str);
			//if (_size + len > _capacity)
			//{
			//	reserve(_size + len);
			//}

			 从末尾开始,拷贝新字符串
			//strcpy(_str + _size, str);	
			//_size += len;

			insert(_size, str);
		}

		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		char* c_str() const
		{
			return _str;
		}

		// 在 pos 位置插入一个字符
		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				// 扩容2倍
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			}
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			++_size;
		}

		// 在 pos 位置插入一个字符串
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			// 空间不够需要扩容
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			size_t end = _size + len;
			while (end >= pos + len)
			{
				_str[end] = _str[end - len];
				--end;
			}
			strncpy(_str + pos, str, len);
		}

		// 从 pos 开始,删除 len 个字符,如果 len 是 npos ,则全删
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			// pos + len >= _size 可能会溢出
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			strcpy(_str + pos + len, _str + pos);
			_size -= len;
		}

		void resize(size_t n, char ch = '\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				reserve(n);
				for (size_t i = _size; i < n; ++i)
				{
					_str[i] = ch;
				}
				_str[n] = '\0';
				_size = n;
			}
		}

		void swap(string& s)
		{
			// 直接交换成员即可
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		size_t find(char ch, size_t pos = 0) const
		{
			for (size_t i = pos; i < _size; ++i)
			{
				if (_str[i] == ch)
					return i;
			}

			return npos;
		}

		size_t find(const char* sub, size_t pos = 0) const
		{
			assert(pos < _size);
			const char* p = strstr(_str + pos, sub);
			// 找到了
			if (p)
			{
				return p - _str;
			}
			// 没找到
			return npos;
		}

		string substr(size_t pos = 0, size_t len = npos)
		{
			string sub;
			if (len >= _size - pos)
			{
				for (size_t i = pos; i < _size; ++i)
				{
					sub += _str[i];
				}
			}
			else
			{
				for (size_t i = pos; i < pos + len; ++i)
				{
					sub += _str[i];
				}
			}
			return sub;
		}

		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		static const int npos;
	};

	// 静态成员变量在类外部定义
	const int string::npos = -1;

	// string类外
	void swap(string& x, string& y)
	{
		x.swap(y);
	}

	bool operator==(const string& s1, const string& s2)
	{
		// strcmp 若是相等则返回0
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret == 0;
	}

	bool operator<(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret < 0;
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}

	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

	ostream& operator<<(ostream& out, const string& s)
	{
		// 没有访问私有成员,不需要友元
		for (auto ch : s)
		{
			out << ch;
		}

		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		// 需要将其内容清空
		s.clear();
		char ch;
		// cin 读取不到 ' ' 和 '\n'
		ch = in.get();
		// 减少扩容
		char buff[128];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[127] = '\0';
				s += buff;
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

	istream& getline(istream& in, string& s)
	{
		// 需要将其内容清空
		s.clear();
		char ch;
		// cin 读取不到 ' ' 和 '\n'
		ch = in.get();
		// 减少扩容
		char buff[128];
		size_t i = 0;
		while (ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[127] = '\0';
				s += buff;
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}
}

test.c源文件

#include"string.h"
using namespace my;

int main()
{
	// 
	return 0;
}

9. string收尾

我们来看看下面一段程序:

#include"string.h"
using namespace my;

int main()
{
	std::string s("11111");
	cout << sizeof(s) << endl;
	return 0;
}

库里的 string 是多少空间呢?
在这里插入图片描述
嗯?如果是我们写的 string ,那么应该是 12 啊(32位下,64位是 24),为什么库里的 string 是 28呢?其实是因为编译器做了相关的优化,它在原有的基础上还新增了一个 buff[16] 数组,当数据较小的时候会存到 buff 里,比较大才会存到 _str 在堆上开辟的空间里。因为栈上开辟空间比在堆上开辟空间快。这仅仅是在 vs 编译器下是这样的,其他编译器不一定是这样。
在这里插入图片描述

写时拷贝

我们知道,拷贝构造不能是浅拷贝,假如是浅拷贝,那么会导致两个指针指向同一块空间,一个修改另一个也会发生变化,多次析构的问题。所以只能是深拷贝。但是库里的 string 类真的是这样的吗?库里的 string 类的构造函数其实是浅拷贝,但是它有个引用计数,代表有几个对象指向这块空间,如果有对象要修改(写),则重新深拷贝一个空间给其使用,析构的时候则判断引用计数是否为1,不为1则不析构。这样做其实就是赌有的对象不去修改内容,只要有这样的对象,那么就算是一种优化。


未完待续

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

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

相关文章

13.1 QQ邮箱

1. 邮箱发送 2. 准备工作 3. 整合SpringBoot 3.1 配置 依赖引入 <!-- 邮件服务--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency>application.…

发表博客之:transformer 架构 推理时候运算流程详细讲解,以及变长推理支持,小白都可以看得懂,AI推理工程师必备技能!

文章目录 [发表博客之&#xff1a;transformer 架构 推理时候运算流程详细讲解&#xff0c;以及变长推理支持&#xff0c;小白都可以看得懂&#xff0c;AI推理工程师必备技能&#xff01;](https://cyj666.blog.csdn.net/article/details/138439826)总结一下高性能变长推理 发表…

DevicData-P-XXXXXX勒索病毒有什么特点,如何恢复重要数据?

DevicData-P-XXXXXXXX这种新型勒索病毒有什么特点&#xff1f; DevicData-P-XXXXXXXX勒索病毒的特点主要包括以下几点&#xff1a; 文件加密&#xff1a;该病毒会搜索并加密用户计算机上的重要文件&#xff0c;如文档、图片、视频等&#xff0c;使其无法正常打开。这是通过强…

IoTDB 入门教程 问题篇②——RPC远程连接IoTDB服务器失败

文章目录 一、前文二、发现问题三、分析问题四、检查6667端口是否监听所有IP五、检查ECS云服务器的安全组是否允许六、检查Linux防火墙是否允许 一、前文 IoTDB入门教程——导读 二、发现问题 使用本地IP127.0.0.1可以连接IoTDB服务器使用远程IPxx.xx.xx.xx却连接不到。提示你…

抖音小店怎么运营操作,无货源正确做店方式,新手就看这一篇

大家好&#xff0c;我是电商笨笨熊 当前抖店还能做无货源模式吗&#xff1f; 当然可以。 近两年由于平台对于无货源的打击&#xff0c;很多人开始怀疑&#xff0c;无货源模式在抖店还能行得通吗&#xff1f; 抖店这个项目我们做了四年多的时间&#xff0c;无货源模式也是&a…

双fifo流水线操作——verilog练习与设计

文章目录 一、案例分析二、fifo_ctrl模块设计2.1 波形设计&#xff1a;2.2 代码实现2.2.1 fifo_ctrl2.2.2 顶层文件top_fifo_ctrl&#xff08;rx和tx模块省略&#xff09;2.2.3 仿真文件tb_fifo_ctrl 2.3波形仿真 一、案例分析 案例要求&#xff1a;写一个 fifo 控制器&#x…

vivado Aurora 8B/10B IP核(12)- Setp By Step搭建FPGA工程

Step1:任意创建一个新的空的工程&#xff08;创建工程的具体工程如果还不清楚的看我们教程第一季部分&#xff09;&#xff0c; 并且进入IP CORE列表 右击Customize ip Step2:配置 IP CORE-Core options Step3:配置 IP CORE-GT Selections Step4:配置 IP CORE-Shared Logic 为 …

深入解析Python中的`add_argument`用法

深入解析Python中的add_argument用法 在Python编程中&#xff0c;add_argument通常与命令行参数解析库argparse有关。这个库主要用于编写用户友好的命令行接口&#xff0c;其核心功能之一就是通过add_argument方法来指定程序可以接受哪些命令行参数。本篇博客将详细介绍argpar…

badKarma:一款功能强大的网络侦查GUI工具

关于badKarma badKarma是一款开源的网络侦查工具&#xff0c;该工具基于Python 3开发&#xff0c;提供了友好的图形化用户接口&#xff0c;可以帮助广大渗透测试人员在网络基础设施安全审计过程中执行网络侦查任务。 badKarma是一个模块化工具&#xff0c;基于python3 GTK套件…

(centos)yum安装mysql8.4

1.MySQL官方已经提供了适用于不同Linux发行版的预构建软件包&#xff0c;包括适用于CentOS的Yum Repository MySQL :: MySQL Community Downloads 2.在/usr/local文件夹下创建mysql文件夹&#xff0c;将下载的rpm文件放到目录下 3.执行安装命令 yum install mysql-community-…

算法打卡day41

今日任务&#xff1a; 1&#xff09;198.打家劫舍 2&#xff09;213.打家劫舍II 3&#xff09;337.打家劫舍III 4&#xff09;复习day16 198.打家劫舍 题目链接&#xff1a;198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 你是一个专业的小偷&#xff0c;计划偷窃沿街…

网安笔记(纯兴趣,随缘更新)

对于千锋教育的网安课程的笔记 (一)虚拟机环境搭建 01虚拟机概述 传统运行模式:一台计算机同时只能运行一个操作系统 虚拟机运行架构: 1.寄生架构 &#xff08;实验环境、测试环境&#xff09; • 虚拟机作为应用软件安装在操作系统上 • 可以在此应用软件上安装多个操作系统…

Docker部署nginx并且实现https访问

实验环境&#xff1a; 在已有的docker环境和nginx镜像的基础上进行操作 1、生成私钥 &#xff08;1&#xff09;openssl genrsa -out key.pem 2048 生成证书签名请求 (CSR) 并自签证书: &#xff08;2&#xff09;openssl req -new -x509 -key key.pem -out cert.pem -day…

招了个牛逼的DBA,问题少了一半,老油条慌了...

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、My…

带环链表和链表的复制,检验你链表的学习情况

前言&#xff1a;带环链表是链表中的经典问题&#xff0c;需要一定的数理思维&#xff0c;一定要掌握其来龙去脉&#xff0c;这样可以加深理解。本文主要讲解一下个人对带环链表的理解。 带环链关的OJ题 1.判断链表是否带环 题目&#xff1a; 141. 环形链表 给你一个链表的头…

并发-线程的 6 个状态(生命周期)

目录 状态解释 状态间的转化 状态解释 状态间的转化 根据Thread类中定义的枚举类型State值&#xff0c;可以看出有6种状态&#xff1a;可以通过 Thread.getState 方法获得线程的状态NEW&#xff08;新建&#xff09;New&#xff1a;新建了Thread类对象&#xff0c;但是没有启…

软设之进程资源图

进程资源图有两个要素&#xff0c;一个是P&#xff0c;也就是进程&#xff0c;一个是R&#xff0c;可以用R1或者R2等表示&#xff0c;表示资源。 R一般是一个矩形里面有几个圆圈&#xff0c;有几个圆圈就表示有几个资源 这里用R1表示资源&#xff0c;P表示进程 R1P 表示资源…

Tomcat启动闪退怎么解决(文末附终极解决方案)

AI是这么告诉我的 Tomcat启动时出现闪退问题可能由多种原因引起&#xff0c;以下是解决此类问题的一些通用方法&#xff1a; 检查环境变量&#xff1a; 确保已经正确设置了JAVA_HOME和JRE_HOME环境变量&#xff0c;并指向正确的Java安装路径。将Java的bin目录添加到系统的PATH…

频谱模拟器

频谱模拟器&#xff0c;特别是模拟频谱仪&#xff0c;是一种基于特定原理的频谱分析工具。以下是对其的详细介绍&#xff1a; 工作原理&#xff1a; 模拟频谱仪的工作原理主要基于频率转换原理&#xff0c;包括两个关键步骤&#xff1a;信号混频和滤波分析。 信号混频&#xf…

《Fundamentals of Power Electronics》——升压隔离型变换器、SEPIC隔离型变换器

以下是升压型隔离变换器的相关知识点&#xff1a; 升压型隔离变换器可以通过互换降压型隔离变换器的电源与负载的位置得到。升压型隔离变换器有许多种结构&#xff0c;此处简短的讨论两种情况。这些转换器主要使用在高压电源和低谐波整流器中。 图6.36所示是一种全桥型电路结…
最新文章