C++相关概念和易错语法(13)(string的模拟实现)

string由于存在字符串和单字符的概念,使得它的一些接口,实现要比vector多一些。本质上来看string的实现是在顺序表的基础上加入串相关的操作。下面我会分享如何模拟实现string,这可以进一步提高我们对string的熟练程度。

1.构造函数、拷贝构造和析构函数

string::string(const char* str)//构造函数
	:_size(strlen(str))
{
	if (_size <= 15)
		strcpy(_buff, str);
	else
	{
		delete[] _buff, _buff = nullptr;
		_capacity = _size;

		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}
}


string::string(const string& str)//拷贝构造
	:_size(str._size)
	,_capacity(str._capacity)
{
	if (_capacity <= 15)
		strcpy(_buff, str._buff);
	else
	{
		delete[] _buff, _buff = nullptr;

		_str = new char[_capacity + 1];
		strcpy(_str, str._str);
	}
}



string::~string()//析构函数
{
	if (_capacity <= 15)
		delete[] _buff, _buff = nullptr;
	else
		delete[] _str, _str = nullptr;

	_capacity = _size = 0;
}

这里我使用了_buff和_str,使他们分情况存储。当存储字符少于15个的时候(实际开了16个字节,预留一个给\0),就存到_buff里,多了就全部移到_str中。

在这里需要注意的是_str和_buff一定要控制好,当转移数据,从_str转移到_buff时,一定要在释放_str后让_str置空,否则很多地方会出现连续delete两次导致报错,我当时实现的时候这个bug找了很久,如果想像VS2022那样使用两个字符串来管理string的话一定要注意。

2.swap


	void string::swap(string& str)//交换
	{
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
		std::swap(_buff, str._buff);
	}

我们实现swap绝大多数情况是为了交换数据,不需要深拷贝,因此直接交换成员变量的所有值即可。swap在后续的复用特别好用,后面会讲到。

3.迭代器的模拟

迭代器是封装的进一步体现,即不需要了解底层数据类型也能正确的用指针的方式对串的内容进行访问。

在串中,我们使用char*和const char*就可以很好地模拟迭代器了


	string::iterator string::begin()//迭代器
	{
		return _capacity <= 15 ? _buff : _str;
	}

	string::iterator string::end()//迭代器
	{
		return _capacity <= 15 ? _buff + _size : _str + _size;
	}

	string::const_iterator string::begin() const//迭代器
	{
		return _capacity <= 15 ? _buff : _str;
	}

	string::const_iterator string::end() const//迭代器
	{
		return _capacity <= 15 ? _buff + _size : _str + _size;
	}

4.访问

使用运算符重载可以使我们像普通数组那样去访问数据,注意区分_buff和_str

char string::operator[](size_t pos)//下标取值
{
	assert(pos < _size);
	return _capacity <= 15 ? _buff[pos] : _str[pos];
}

const char string::operator[](size_t pos) const//下标取值
{
	assert(pos < _size);
	return _capacity <= 15 ? _buff[pos] : _str[pos];
}

5.赋值运算符=

这里就能很好的体现出swap的复用优势了,因为赋值意味着原来的数据可以被丢弃,我们借助自动调用析构的特性将可以丢弃的数据和有用的数据交换,使无用的数据存到临时变量(形参)中,在函数结束的时候自动调用析构帮我们销毁了。

	string& string::operator=(string str)//赋值
	{
		swap(str);
		return *this;
	}

利用复用,我们的代码可以非常简约,但是实际上执行的复杂度是没有改变的,因为复用是函数套用使得表层逻辑简单,底层实现的逻辑是没有变的。

6.insert

insert是string的核心,因为这个函数能够实现几乎所有情况的插入,而erase也可借助insert实现,push_back、append可以直接复用insert,所以这个函数的实现直接影响后续所有插入删除。


	string& string::insert(size_t pos, size_t n, char c)//连续插入n个字符
	{
		assert(pos <= _size);//防止越界

		_size += n;
		if (_size + n > _capacity)
			reserve((_capacity + n) * 2);

		for (size_t cur = _size + n; cur > pos + n - 1; cur--)
		{
			_capacity <= 15 ? _buff[cur] = _buff[cur - n] : _str[cur] = _str[cur - n];
		}

		for (size_t count = 0; count < n; count++)
		{
			_capacity <= 15 ? _buff[pos + count] = c : _str[pos + count] = c;
		}

		return *this;
	}

	string& string::insert(size_t pos, const string& str, size_t subpos, size_t len)//插入字符串
	{
		assert(pos <= _size);
		assert(subpos <= str._size);

		if (len > str._size - subpos)
			len = str._size - subpos;

		if (len)
		{
			if (_size + len > _capacity)
				reserve((_capacity + len) * 2);

			for (size_t cur = _size + len; cur > pos + len - 1; cur--)
			{
				_capacity <= 15 ? _buff[cur] = _buff[cur - len] : _str[cur] = _str[cur - len];
			}

			for (size_t count = 0; count < len; count++)
			{
				_capacity <= 15 ? _buff[pos + count] = (str._capacity <= 15 ? str._buff[subpos + count] : str._str[subpos + count]) : _str[pos + count] = (str._capacity <= 15 ? str._buff[subpos + count] : str._str[subpos + count]);
			}

			_size += len;
		}
		
		return *this;
	}

大部分的代码是很简单的,但是在数据挪动的时候下标是一个大难题。这需要我们总结一些技巧。

其中着重理解闭、开、个数之间的关系,可以很好帮我们判断下标问题

7.erase


	string& string::erase(size_t pos, size_t len)//删除字符串内容
	{
		assert(pos < _size);

		if (len > _size - pos)
			swap(string().insert(0, *this, 0, pos));
		else
		{
			string s1, s2;
			s1.insert(0, *this, 0, pos);
			s2.insert(0, *this, pos + len);

			*this = s1 + s2;
		}

		return *this;
	}

借助复用我们可以进一步实现erase,当从pos开始全部删除时用swap,其余情况分成两段insert,最后加起来,这里我提前用了operator+,operator+很好实现,只是我这里讲解的顺序不一样。

insert、erase附加操作这里就不展开了,最后我会分享全部代码。

8.比较

比较运算符重载我们其实只需要实现其中的一两个,其它全部用上层逻辑联系起来,可以很快实现


	bool string::operator>(const string& str) const
	{
		return strcmp(_capacity <= 15 ? _buff : _str, str._capacity <= 15 ? str._buff : str._str) > 0;
	}

	bool string::operator>=(const string& str) const
	{
		return strcmp(_capacity <= 15 ? _buff : _str, str._capacity <= 15 ? str._buff : str._str) >= 0;
	}

	bool string::operator<(const string& str) const
	{
		return !(*this >= str);
	}

	bool string::operator<=(const string& str) const
	{
		return !(*this > str);
	}

	bool string::operator!=(const string& str) const
	{
		return !(*this == str);
	}

	bool string::operator==(const string& str) const
	{
		return strcmp(_capacity <= 15 ? _buff : _str, str._capacity <= 15 ? str._buff : str._str) == 0;
	}

9.流插入和流提取


	ostream& operator<<(ostream& out, const string& str)//流提取
	{
		if (str.capacity() <= 15)
			out << "_buff: ";
		else
			out << "_str: ";

		for (auto e : str)
			out << e;

		out << "    _size:" << str.size() << "    _capacity:" << str.capacity() << endl;

		return out;
	}

	istream& operator>>(istream& in, string& str)
	{
		char ch = in.get();

		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			ch = in.get();
		}

		return in;
	}

唯一需要注意的是流插入要使用cin.get(),最好不要用scanf,因为C++和C的缓冲区不互通,也不要用cin>>,读的时候直接忽略了空格,根本停不下来。

全部代码汇总:

string.h



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

namespace my_string
{
	class string
	{
	public:

		static const int npos;

		typedef char* iterator;
		typedef const char* const_iterator;

		void swap(string& str);
		string(const char* str = "");
		string(const string& str);
		~string();

		size_t size() const;
		size_t capacity() const;
		const char* c_str() const;

		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		string& operator=(string str);
		char operator[](size_t pos);
		const char operator[](size_t pos) const;
		string& operator+=(const string& s);
		string& operator+=(char c);

		string& reserve(size_t newcapacity);
		string& insert(size_t pos, size_t n, char c);
		string& insert(size_t pos, const string& str, size_t subpos = 0, size_t len = npos);
		string& erase(size_t pos, size_t len = npos);
		string& push_back(char c);
		string& append(const string& str);
		string substr(size_t pos = 0, size_t len = npos) const;

		size_t find(char c, size_t pos = 0) const;
		size_t find(const string& str, size_t pos = 0) const;
		size_t find_first_of(const string& str, size_t pos = 0) const;
		size_t find_first_not_of(const string& str, size_t pos = 0) const;

		bool operator>(const string& str) const;
		bool operator>=(const string& str) const;
		bool operator<(const string& str) const;
		bool operator<=(const string& str) const;
		bool operator!=(const string& str) const;
		bool operator==(const string& str) const;

	private:
		char* _str = nullptr;
		size_t _size;
		size_t _capacity = 15;
		char* _buff = new char[16];
	};

	ostream& operator<<(ostream& out, const string& str);
	istream& operator>>(istream& in, string& str);
	string operator+(const string& s1, const string& s2);


}

string.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include "string.h"

namespace my_string
{
	const int string::npos = -1;

	void string::swap(string& str)//交换
	{
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
		std::swap(_buff, str._buff);
	}

	string::string(const char* str)//构造函数
		:_size(strlen(str))
	{
		if (_size <= 15)
			strcpy(_buff, str);
		else
		{
			delete[] _buff, _buff = nullptr;
			_capacity = _size;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
	}

	string::string(const string& str)//拷贝构造
		:_size(str._size)
		,_capacity(str._capacity)
	{
		if (_capacity <= 15)
			strcpy(_buff, str._buff);
		else
		{
			delete[] _buff, _buff = nullptr;

			_str = new char[_capacity + 1];
			strcpy(_str, str._str);
		}
	}


	string::~string()//析构函数
	{
		if (_capacity <= 15)
			delete[] _buff, _buff = nullptr;
		else
			delete[] _str, _str = nullptr;

		_capacity = _size = 0;
	}

	size_t string::size() const//获取size
	{
		return _size;
	}

	size_t string::capacity() const//获取capacity
	{
		return _capacity;
	}

	const char* string::c_str() const//取有效字符串地址
	{
		return _capacity <= 15 ? _buff : _str;
	}

	string::iterator string::begin()//迭代器
	{
		return _capacity <= 15 ? _buff : _str;
	}

	string::iterator string::end()//迭代器
	{
		return _capacity <= 15 ? _buff + _size : _str + _size;
	}

	string::const_iterator string::begin() const//迭代器
	{
		return _capacity <= 15 ? _buff : _str;
	}

	string::const_iterator string::end() const//迭代器
	{
		return _capacity <= 15 ? _buff + _size : _str + _size;
	}


	string& string::operator=(string str)//赋值
	{
		swap(str);
		return *this;
	}

	char string::operator[](size_t pos)//下标取值
	{
		assert(pos < _size);
		return _capacity <= 15 ? _buff[pos] : _str[pos];
	}

	const char string::operator[](size_t pos) const//下标取值
	{
		assert(pos < _size);
		return _capacity <= 15 ? _buff[pos] : _str[pos];
	}

	string& string::operator+=(const string& s)//字符串自增
	{
		insert(_size, s, 0);
		return *this;
	}

	string& string::operator+=(char c)//追加同种字符
	{
		insert(_size, 1, c);
		return *this;
	}

	string& string::reserve(size_t newcapacity)//扩容或合理缩容
	{
		if (newcapacity < _size)
			return *this;

		char* tmp = new char[_size + 1];
		strcpy(tmp, _capacity <= 15 ? _buff : _str);

		delete[] _buff, delete[] _str;
		_str = nullptr, _buff = nullptr;//置空防止后续报错

		if (newcapacity <= 15)
			_buff = new char[newcapacity + 1];
		else
			_str = new char[newcapacity + 1];

		strcpy(newcapacity <= 15 ? _buff : _str, tmp);

		_capacity = newcapacity;
		delete[] tmp;

		return *this;
	}

	string& string::insert(size_t pos, size_t n, char c)//连续插入n个字符
	{
		assert(pos <= _size);//防止越界

		_size += n;
		if (_size + n > _capacity)
			reserve((_capacity + n) * 2);

		for (size_t cur = _size + n; cur > pos + n - 1; cur--)
		{
			_capacity <= 15 ? _buff[cur] = _buff[cur - n] : _str[cur] = _str[cur - n];
		}

		for (size_t count = 0; count < n; count++)
		{
			_capacity <= 15 ? _buff[pos + count] = c : _str[pos + count] = c;
		}

		return *this;
	}

	string& string::insert(size_t pos, const string& str, size_t subpos, size_t len)//插入字符串
	{
		assert(pos <= _size);
		assert(subpos <= str._size);

		if (len > str._size - subpos)
			len = str._size - subpos;

		if (len)
		{
			if (_size + len > _capacity)
				reserve((_capacity + len) * 2);

			for (size_t cur = _size + len; cur > pos + len - 1; cur--)
			{
				_capacity <= 15 ? _buff[cur] = _buff[cur - len] : _str[cur] = _str[cur - len];
			}

			for (size_t count = 0; count < len; count++)
			{
				_capacity <= 15 ? _buff[pos + count] = (str._capacity <= 15 ? str._buff[subpos + count] : str._str[subpos + count]) : _str[pos + count] = (str._capacity <= 15 ? str._buff[subpos + count] : str._str[subpos + count]);
			}

			_size += len;
		}
		
		return *this;
	}


	string& string::erase(size_t pos, size_t len)//删除字符串内容
	{
		assert(pos < _size);

		if (len > _size - pos)
			swap(string().insert(0, *this, 0, pos));
		else
		{
			string s1, s2;
			s1.insert(0, *this, 0, pos);
			s2.insert(0, *this, pos + len);

			*this = s1 + s2;
		}

		return *this;
	}


	string& string::push_back(char c)//追加单个字符
	{
		(*this) += c;
		return *this;
	}

	string& string::append(const string& str)//追加字符串
	{
		(*this) += str;
		return *this;
	}

	string string::substr(size_t pos, size_t len) const//子字符串
	{
		string tmp = *this;

		tmp.swap(string().insert(0, *this, pos));

		return tmp;
	}

	size_t string::find(char c, size_t pos) const//找单个字符
	{
		for (size_t count = pos; count < _size; count++)
		{
			if ((*this)[count] == c)
				return count;
		}

		return npos;
	}

	size_t string::find(const string& str, size_t pos) const//找字符串
	{
		for (size_t count = pos; count < _size - str._size + 1; count++)
		{
			if ((*this)[count] == str[0])
			{
				size_t start = count;

				for (auto e : str)
				{
					if (e == (*this)[start++])
						continue;
					break;
				}

				if (start - count == str._size)
					return count;

			}

		}
		return npos;
	}

	size_t string::find_first_of(const string& str, size_t pos) const//找字符串第一次出现位置
	{
		bool stage = false;

		size_t* count = new size_t[str._size], min = 0, i = 0;

		for (auto e : str)
		{
			count[i++] = find(e, pos);
		}

		for (size_t k = 0; k < str._size; k++)
		{
			if (count[k] <= count[min])
			{
				min = k;
				stage = true;
			}
		}

		if (stage)
			return count[min];
		return npos;

	}

	size_t string::find_first_not_of(const string& str, size_t pos) const//找字符串第一次没出现的位置
	{
		size_t cur_pos = 0, next_pos = 0;

		if (cur_pos = find_first_of(str, pos) != 0)
			return cur_pos == npos ? 0 : cur_pos - 1;

		for (size_t count = 1; count < _size; count++)
		{
			next_pos = find_first_of(str, pos + count);
			if (cur_pos + 1 != next_pos)
				return cur_pos + 1;
			cur_pos = next_pos;
		}

		return npos;
	}

	bool string::operator>(const string& str) const
	{
		return strcmp(_capacity <= 15 ? _buff : _str, str._capacity <= 15 ? str._buff : str._str) > 0;
	}

	bool string::operator>=(const string& str) const
	{
		return strcmp(_capacity <= 15 ? _buff : _str, str._capacity <= 15 ? str._buff : str._str) >= 0;
	}

	bool string::operator<(const string& str) const
	{
		return !(*this >= str);
	}

	bool string::operator<=(const string& str) const
	{
		return !(*this > str);
	}

	bool string::operator!=(const string& str) const
	{
		return !(*this == str);
	}

	bool string::operator==(const string& str) const
	{
		return strcmp(_capacity <= 15 ? _buff : _str, str._capacity <= 15 ? str._buff : str._str) == 0;
	}

	ostream& operator<<(ostream& out, const string& str)//流提取
	{
		if (str.capacity() <= 15)
			out << "_buff: ";
		else
			out << "_str: ";

		for (auto e : str)
			out << e;

		out << "    _size:" << str.size() << "    _capacity:" << str.capacity() << endl;

		return out;
	}

	istream& operator>>(istream& in, string& str)
	{
		char ch = in.get();

		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			ch = in.get();
		}

		return in;
	}

	string operator+(const string& s1, const string& s2)//两个字符串相加
	{
		string tmp;
		tmp += s1, tmp += s2;
		return tmp;
	}

}

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

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

相关文章

Mysql与Navicat可视化命令大全 ----项目实战

软件准备&#xff1a;✍Mysql8.0下载地址&#xff08;推荐&#xff09;✍Navicat 16 下载地址&#xff08;推荐&#xff09; 注&#xff1a;不会安装看主页&#xff0c;关注我&#xff0c;免费指导&#xff0c;接计算机毕设☑ -----------------------------------------------…

交换机连接方式

一、级联方式 级联是将多个交换机或其他网络设备依次连接&#xff0c;形成一个层次结构&#xff0c;从而扩展网络的覆盖范围和端口数量。 在级联连接中&#xff0c;数据信号会从一个设备依次传递到下一个设备。每个设备都会接收并处理来自上级设备的数据&#xff0c;并将其转…

【MySQL精通之路】MySQL8.0新增功能-原子DDL语句支持

太长不看系列&#xff1a; 本文一句话总结&#xff0c;MySQL8.0支持多条DDL语句执行时的原子性了&#xff08;仅限Innodb&#xff09; 本文属于下面这篇博客的子博客&#xff1a; 【MySQL精通之路】MySQL8.0官方文档-新增功能 1.意义描述 MySQL 8.0支持原子数据定义语言&…

设置我们JavaScript设置的开发环境

你想设置一个用于编写Java脚本的开发环境,对吧?我们会在接下来的笔记中写一些JavaScript代码,所以我们需要一个开发环境。那么我们需要选择哪种开发环境呢? 通常情况下,对于像Java或C#这样的语言,你需要进行一些安装,对吧?你需要下载Java或某个运行时环境,并设置好路…

uniapp集成websocket不断线的处理-打牌记账

背景 近期在开发打牌记账微信小程序时&#xff0c;我们将房间这个业务场景做成了类似聊天室功能。 对房间内发生的动作&#xff0c;都能实时对其他人可见。 如:转账&#xff0c;离开&#xff0c;加入&#xff0c;结算等动作 其他人员都能实时接收到推送消息&#xff0c; 这个时…

Android模块化项目搭建和模块之间跳转传值(1)

一、背景 近段时间 由于工作没有这么繁忙&#xff0c;于是总结了一下项目中的模块化处理&#xff0c;并且这也是在众多面试中会问到的问题&#xff0c;希望能够帮助到在学习或者了解模块化的同学。 二、项目搭建 1、其实模块化就是将众多功能模块分成一个一个的模块进行开发…

<项目> 云备份

目录 一、简单认识 二、实现目标 三、服务端程序负责功能及功能模块划分 四、客户端程序负责功能及功能模块划分 五、环境搭建 &#xff08;一&#xff09;gcc 7.3 &#xff08;二&#xff09;安装jsoncpp库 &#xff08;三&#xff09;下载bundle数据压缩库 &#xf…

聊聊 JSON Web Token (JWT) 和 jwcrypto 的使用

哈喽大家好&#xff0c;我是咸鱼。 最近写的一个 Python 项目用到了 jwcrypto 这个库&#xff0c;这个库是专门用来处理 JWT 的&#xff0c;JWT 全称是 JSON Web Token &#xff0c;JSON 格式的 Token。 今天就来简单入门一下 JWT。 官方介绍&#xff1a;https://jwt.io/intr…

添加、修改和删除列表元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 添加、修改和删除列表元素也称为更新列表。在实际开发时&#xff0c;经常需要对列表进行更新。下面我们介绍如何实现列表元素的添加、修改和删除。 …

vs无法打开或包括文件”QTxxx“

vs创建项目时默认引入core、gui、和widgets等模块&#xff0c;在需要网络通讯或者图表等开发时需要添加相应模块。 点击扩展 -> QT VS Tools -> QT Project Setting->Qt Modules&#xff0c;添加相应模块即可

【Jenkins】Centos7安装Jenkins(环境:JDK11,tomcat9,maven3.8)

目录 Jenkins部署环境Maven安装1.上传安装包2.解压3.配置Maven环境变量4.使配置文件立即生效5.校验Maven安装6.Maven配置阿里云仓库7.Maven配置依赖下载位置 Git安装安装监测安装 JDK17安装1.查看旧版本JDK2.卸载旧版本JDK3.查看是否卸载干净4.创建java目录5.下载JDK11安装包6.…

kettle从入门到精通 第六十二课 ETL之kettle job中发送邮件(带多个附件),闭坑指南

1、今天群里一个朋友加我微信遇到问下向我求助。一顿测试下来发现原来是使用kettle姿势不对&#xff0c;对kettle没有完全驾驭导致的&#xff0c;今天和大家一起分享下这个问题。 2、先自我膨胀下&#xff0c;自从写kettle系列文章之后认识了很多朋友&#xff0c;同时文章也帮助…

设计模式6——单例模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 单例模式&#xff08;Singleto…

1-4月我国5G用户、流量占比均过半,呈现平稳增长态势!

1-4月份&#xff0c;通信行业整体运行平稳。电信业务量收平稳增长&#xff1b;5G、千兆光网等新型基础设施建设持续推进&#xff0c;网络连接用户规模不断扩大&#xff0c;移动互联网接入流量较快增长。 一、总体运行情况 电信业务收入稳步增长&#xff0c;电信业务总量增速保持…

vue3.0(十)双向数据绑定原理和v2.0对比

文章目录 MVVM框架1 理解ViewModel2 MVVM的优点 vue2.0 双向数据绑定原理1 实现双向数据绑定2 实现3 Vue2.0 缺点和解决办法 vue3.0 双向数据绑定原理vue2.0和vue3.0 的差异Vue2.0Vue3.0Object.defineProperty和Proxy的对比 MVVM框架 MVVM&#xff08;Model-View-ViewModel&am…

Kubectl 的使用——k8s陈述式资源管理

一、kebuctl简介: kubectl 是官方的CLI命令行工具&#xff0c;用于与 apiserver 进行通信&#xff0c;将用户在命令行输入的命令&#xff0c;组织并转化为 apiserver 能识别的信息&#xff0c;进而实现管理 k8s 各种资源的一种有效途径。 对资源的增、删、查操作比较方便&…

欢聚笔试题求助帖

事情是这样的&#xff0c;这段时间一直在求职投简历&#xff0c;期望在暑假之前接到一份大数据开发的实习工作。投了很多公司&#xff0c;然后就收到了欢聚的笔试邀约&#xff0c;HR说要我一天之内做出来&#xff0c;恰巧第二天还有组会要汇报&#xff0c;我就先放下了&#xf…

21.1zabbix低级自动发现-监控项详解

详解分析&#xff1a;低级自动发现&#xff1a;自动创建监控项&#xff08;红色部分字体是怎么创建得监控项&#xff1f;&#xff09; 点击对应得主机-监控项-Network interfaces应用集&#xff0c;键值有进4个&#xff0c;出4个。因为本机存在4块网卡 注释&#xff1a;本机存…

BGP(一)边界网关协议

BGP协议基础 路由分类 直连路由 非直连路由&#xff08;间接路由&#xff09; 静态路由动态路由 IGP&#xff1a;内网网关路由协议&#xff08;在企业内部或数据中心内部使用&#xff09; DV&#xff1a;距离矢量路由协议RIP&#xff08;v1/v2&#xff09;IGRP——网络直径&…

文本转语音软件-TTSMaker

一、TTSMaker介绍 TTSMaker&#xff08;马克配音&#xff09;是一款免费的文本转语音工具&#xff0c;提供语音合成服务&#xff0c;支持多种语言&#xff0c;包括中文、英语、日语、韩语、法语、德语、西班牙语、阿拉伯语等50多种语言&#xff0c;以及超过300种语音风格。 可…