string模拟实现

news2024/9/22 21:34:30

文章目录

  • 1.回顾库函数
    • strcpy
    • memcpy
    • strcmp
    • strstr
  • 2.回顾类和对象
      • 哪些函数里会有this指针?
      • this指针调用方法
      • 结论:只要是不修改this指针指向的对象内容的成员函数,都可以加上const
      • 自己写了构造函数,编译器不会自动生成默认构造
      • 2.1构造和拷贝构造都可以使用初始化列表进行初始化
    • 2.2 C和C++区别:C中结构体里面没有函数方法
  • 3.string 模拟实现
    • 构造函数
      • 默认构造的错误写法
      • 默认构造的正确写法
    • 拷贝构造
    • 深浅拷贝
    • 赋值重载
      • 深浅拷贝
      • 传统写法
      • 现代写法
    • swap()
    • 析构函数
    • const char* c_str() const C的接口
    • size() 返回有效字符个数
    • []符号重载
    • reserve()
      • 模拟实现reserve
    • void push_back(char c)
    • string& append(const char* s)
    • void insert(size_t pos, size_t n, char c)
    • void insert(size_t pos, const char* s)
    • void erase(size_t pos, size_t len = npos)
    • size_t find(const char* str, size_t pos = 0)
    • string substr(size_t pos = 0, size_t len = npos)
    • iterator迭代器 左闭右开[ )
      • 定义
      • 使用

1.回顾库函数

strcpy

strcpy 都是char 类型 考完就算完成

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);

	char* ret = dest;			//先对src后置++ 在解引用  后置++优先级高于* 这个写法\0也被拷贝,表达式结果为\0就结束
	while ((*dest++ = *src++))//判断*dest的结果是不是'\0',如果是就结束,会将源字符串中的 '\0' 拷贝到目标空间
	{
		;
	}
	return ret;

}

memcpy

void * memcpy ( void * destination, const void * source, size_t num );
涉及多种类型,可能是char* int*
所以传入形参num ,多少字节需要被拷贝
num
Number of bytes to copy.
size_t is an unsigned integral type.
其中就需要利用char*指针 1个字节一个字节的拷贝

要用此函数拷贝,要保证源和目的地都至少有num字节个空间

思路如图所示:
在这里插入图片描述

void* my_memcpy(void* dest, const void* src, size_t num)
{//如果source和destination有任何的重叠,复制的结果都是未定义的。
	assert(dest && src);
	void* ret = dest;
	//前->后
	while (num--)//循环num次
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;//++(char*)dest;如果换成(char*)dest++,dest++报错(“void *”: 未知的大小),因为char*转换是临时的
		src = (char*)src + 1;//++(char*)src;
	}
	return ret;
}

strcmp

strcmp能比较不同长度的字符串吗?可以,并且其中字符串提前结束后最后一位\0参与了对比,这和string里面的<复写有所区别,string里面\0不应该参与对比

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while(*str1 == *str2)
	{
		if (*str1 == '\0')//abc和abc相等情况判断,如果*str1 == \0 那么*str2也等于\0
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;//不相等看*str1 - *str2谁大 返回>0 <0
}

strstr

思路:
BF
暴力求解,定义下标i,j开始从头遍历,i和j相同就一起+1,遇到不同的字符就让j返回到子串0,i回到开始位置+1

时间复杂度O(m*n)
在这里插入图片描述

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	if (*str2 == '\0')//传入空字符串  直接返回str1
	{
		return (char*)str1;
	}

	const char* s1 = NULL;
	const char* s2 = NULL;
	const char* cp = str1;//记录可能的起始位置

	while (*cp)
	{
		s1 = cp;
		s2 = str2;//重置要查找子串的开始位置
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return (char*)cp;
		}
		cp++;
	}
	return NULL;
}

2.回顾类和对象

哪些函数里会有this指针?

答:只要是类成员函数里面都有

this指针调用方法

this->push_back(); 等价于 (*this).push_back();

string& operator+=(char c)
		{
			//this->push_back(c);
			//(*this).push_back(c);
			push_back(c);
			return *this;
		}

结论:只要是不修改this指针指向的对象内容的成员函数,都可以加上const

好处是 非const和const的对象都可以调用,但对于要修改this指向内容的对象加上const就没法改了

自己写了构造函数,编译器不会自动生成默认构造

2.1构造和拷贝构造都可以使用初始化列表进行初始化

在这里插入图片描述

2.2 C和C++区别:C中结构体里面没有函数方法

3.string 模拟实现

要注意_size和_capacity的更新
也要注意 成员函数插入或删除的一些函数 pos位置的范围 assert(pos<size)…等

构造函数

默认构造的错误写法

这种写法导致了 cout<< nullptr << endl的错误,输出nullptr会报错

然而cout << NULL << endl;并不会报错,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量,默认编译器认为NULL是0,要搞成指针必须(void*)0

在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

   string()
   :_size(0)
   ,_capacity(0)
   ,_str(nullptr)
   {   }

默认构造的正确写法

/string()
:_size(0)
,_capacity(0)
,_str(new char[1])
{
_str[0] = ‘\0’;
}
/

即使stirng对象是空,string认为也是要有一个\0的
string类 capacity不计算\0
c的字符数组,以\o为终止算长度
string不看\0,以size为终止算长度

下面将默认构造和有参构造合并

此种实现的是最常用的构造 string (const char* s);
说人话就是用一个C字符串来构造string对象
开头罗列出了默认构造的几个错误缺省参数,原因如后面所示

这里并没有使用初始化列表进行初始化,因为都是内置类型,初始化列表也无法完成memcpy或其他可能的工作,所以干脆没用

根据_size+1也拷贝了\0,这里不用strcpy()而改用memcpy() 的原因是:遇到hello\0xxxxx这种string对象拷贝就不能用strcpy了

//string(const char* s = '\0')//类型不匹配 char* = char
		//string(const char* s = nullptr) //下面strlen会崩 
		//string(const char* s = "\0") //内存有2个 \0 \0 没必要,但是正确,下面会开辟一个空间,strcpy会把\0拷贝到_str
		string(const char* s = "")//空字符串,默认1个\0,下面会开辟一个空间,strcpy会把\0拷贝到_str
		{
			_size = strlen(s);
			_capacity = _size;//string类 capacity不计算\0
			_str = new char[_size + 1];//为\0留一个位置
			//strcpy(_str, s);
			memcpy(_str,s,_size+1);//遇到hello\0xxxxx这种string对象拷贝就不能用strcpy了
		}

拷贝构造

深浅拷贝

浅拷贝的问题
内存泄漏
析构两次

形参中const string& s 加const 让const对象也可以传参,并且防止了this对象和形参赋值顺序可能发生颠倒

_str = new char[s._capacity + 1];为什么不开辟 s._size+1大小的空间?
因为目的就是拷贝和源相同capacity大小的空间,如果开s._size+1大小 _capacity就可能变小

这里我写出了一个错误,类似于C初阶冒泡函数形参传递无法在函数中sizeof出整个数组大小,我试图sizeof(s._str)求 整个数组的大小,这是做不到的,只有在同一个函数栈帧中才能sizeof(数组名)求出整个数组的大小,s是作为形参传入,s._str就类似于冒泡函数中的形参sort(int a[]) 里面sizeof(a)是求不出整个数组大小的

string(const string& s)
{
	_str = new char[s._capacity + 1];
	//memcpy(_str, s._str, sizeof(s._str));//形参传入的是指针,参考C初阶冒泡的错误写法
	memcpy(_str, s._str, s._size+1);
	_size = s._size;
	_capacity = s._capacity;
}

赋值重载

深浅拷贝

问题
析构两次
一个对象的修改造成另一个对象一起改变

类似于拷贝构造,但是拷贝构造不需要释放原来的数据空间
赋值重载要做的事情是释放原有的空间,开辟一块新空间,赋值另一个string对象的内容
//原有空间可能小,要扩容
//可能大,多的浪费了
//也可能刚好够
//不管原来到底是够还是大或者小,反正都是要释放原空间,开辟新空间
//s1 = s2

传统写法

自己开新空间,赋值s2的数据,再释放原有空间

赋值代表拷贝,这里不能开s._size+1个,比源对象的容量少,和拷贝构造一样

string& operator=(const string& s)
		{
			if (this != &s)//this指向s1 ,s1 != s才赋值
			{
				char* tmp = new char[s._capacity + 1];//赋值代表拷贝,这里不能开s._size+1个,比源对象的容量少
				memcpy(tmp,s._str, s._size+1);
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}

现代写法

过渡版
s1 = s2
思路:打工人是拷贝构造,利用拷贝构造构造出的tmp,再让tmp和this交换,s1也就换好了

这里面不能直接交换两个对象,会造成栈溢出,原因是swap里面也是赋值又继续调用赋值重载又swap…

string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				
				std::swap(_str, tmp._str);
				std::swap(_size, tmp._size);
				std::swap(_capacity, tmp._capacity);

				//swap(tmp, *this);//对象直接交换,交换里面还是赋值重载,就会造成栈溢出
			}
			return *this;
		}

swap()

string里面的是交换成员变量,与算法库 的swap()里面不同的是:库里面的swap是深拷贝,对比string里的swap效率低一些

void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

现代写法最终版
思路:在形参中就调用拷贝构造,构造形参tmp,进行与s1的交换
注意形参不要写const,const导致形参无法被修改也就不能交换了

void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
string& operator=(string tmp)
		{
			//this->swap(tmp);
			swap(tmp);

			return *this;
		}

析构函数

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

const char* c_str() const C的接口

返回C字符串_str

const char* c_str() const
		{
			return _str;
		}

size() 返回有效字符个数

size_t size() const
		{
			return _size;
		}

[]符号重载

char& operator[](size_t pos)//无法加const,需要读写修改成员变量
		{
			assert(pos < _size);
			return _str[pos];
		}
	//const对象版本  只读
const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

reserve()

作用:修改capacity,单纯改变空间
不具约束力的请求,编译器没有缩容

测试发现reserve只扩容,不缩容
测试代码

int main()
{
	string s1("hello linux");
	s1.reserve(100);//单纯改变空间

	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	s1.reserve(5);//不具约束力的请求,编译器没有缩容
	cout << s1[9] << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	//开空间+填值初始化
	//比size大的部分用'\0'填充
	s1.resize(105);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;


	s1.resize(0);//即使是0 也没缩容
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	
	return 0;
}

模拟实现reserve

思路:如果需要的空间>capacity就扩容,开辟需要n个空间,将原有数据拷贝过去,释放原来的空间,更新成员变量

void reserve(size_t n)
		{//调整想要的有效字符的空间,可能缩容但不进行缩容
			if (n > _capacity)
			{
				cout << "reserve->" << n << endl;
				char* tmp = new char[n + 1];//n是有效个数,还需要加\0
				//strcpy(tmp, _str);
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
			
		}

void push_back(char c)

追加一个字符
思路:在字符串末尾追加一个字符,可以二倍扩容
注意事项:因为涉及2倍,原有空间如果是0就需要利用三目来规避此问题

void push_back(char c)
		{
			//二倍扩容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size++] = c;
			_str[_size] = '\0';
		}

string& append(const char* s)

追加字符串
思路:不能二倍扩容,当len很大,len + _size有可能大于二倍扩
至少要开到len+_size
注意事项:reserve(len + _size);//这里不需要len+_size+1 因为reserve里面已经+1

string& append(const char* s)
	{
	//不能二倍扩容,当len很大,len + _size有可能大于二倍扩
	//至少要开到len+_size
	size_t len = strlen(s);
	if (len + _size > _capacity)
	{
		reserve(len + _size);//这里不需要len+_size+1 因为reserve里面已经+1
	}
	//strcpy(_str + _size, s);
	memcpy(_str + _size, s, len + 1);//多拷贝一个\0
	_size += len;
	
	return *this;
		}

void insert(size_t pos, size_t n, char c)

插入n个字符
思路:n + _size <= _capacity就刚好够,不用扩容,不然就扩容到n+_size
之后就是挪动数据,把_size位置的数据往后挪动n个,才能在Pos(包括pos)往后的位置空出n个位置来插入
注意:挪动数据时,pos位置在中间都没事,如果Pos==0,那么因为size_t是无符号整形,end–导致size_t = 42亿几,判断条件就停不下来了
那么解决方法有三种
1.将end和pos都强转为int,pos如果不强转那么会导致int end算术转换为size_t,仍然会进入循环
2.设置npos = -1 这是设置静态变量npos那么如果end == -1就结束
3.把前一个数据挪动到后面去
思路图
while(end>pos+n)
_str[end] = _str[end-3]
end–
在这里插入图片描述

在这里插入图片描述

void insert(size_t pos, size_t n, char c)
		{
			assert(pos <= _size);
			//扩容
			if (n + _size > _capacity)
			{
				reserve(n + _size);
			}
			// 挪动数据
			/*int end = _size;
			while (end >= (int)pos)
			{
				_str[end + n] = _str[end];
				end--;
			}*/
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + n] = _str[end];
				end--;
			}
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = c;
			}
			_size += n;
	
		}

void insert(size_t pos, const char* s)

插入字符串
思路:和插入n个字符没啥区别,都是挪动然后插入n个字符

void insert(size_t pos, const char* s)
		{
			assert(pos <= _size);

			size_t len = strlen(s);
			//扩容// 至少扩容到_size + len
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}

			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + len] = _str[end];
				end--;
			}
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = s[i];
			}
			_size += len;

		}

void erase(size_t pos, size_t len = npos)

删除pos位置(包括Pos)后续的len个字符
思路图:
在这里插入图片描述
思路:从pos位置开始删除,如果pos+len >= _size or len == npos那么很简单,相当于直接从pos位置是\0截断,如果不是这种情况,那么就需要把end = pos + len 的位置移动到Pos位置,他们俩++即可。
注意:先判断len = = npos如果是这种情况,如果先判断pos + len 会溢出

void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (len == npos ||pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					/*_str[end - len] = _str[end];
					end++;*/
					_str[pos++] = _str[end++];
				}
			}
			_size -= len;
		}

size_t find(const char* str, size_t pos = 0)

查找字符串子串,返回子串在主串中的下标
注意:指针-指针,p2指向的位置不计算在内,而是p1和p2之间的元素个数,以下标版本的也一样
在这里插入图片描述

在这里插入图片描述

size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);

			const char* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return ptr - _str; 
			}
			else
			{
				return npos;
			}
		}

string substr(size_t pos = 0, size_t len = npos)

返回主串中的子串,返回从pos位置开始(包含pos)的len个字符
涉及小知识点:数组中下标也符合指针-指针,可以计算中间的元素个数,结合测试案例来看
小知识点思路图
在这里插入图片描述
测试代码:

void test_string4()
{
		ljh::string url1 = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";
		// 协议  域名  资源名
		size_t pos1 = url1.find("://");
		if (pos1 != ljh::string::npos)
		{
			ljh::string protocol = url1.substr(0, pos1);
			cout << protocol.c_str() << endl;
		}
		size_t pos2 = url1.find('/', pos1 + 3);
		if (pos2 != ljh::string::npos)
		{
			ljh::string domain = url1.substr(pos1 + 3, pos2 - (pos1 + 3));
			cout << domain.c_str() << endl;
			ljh::string uri = url1.substr(pos2 + 1);
			cout << uri.c_str() << endl;
		}
		//cout << "pos1 = " << pos1 << "pos2 = " << pos2 << endl;

}

模拟实现代码:
思路:如果要从pos位置开始提取的子串长度>=_size 那么就直接将n设置为_size-pos,也就是直接提取这一段,不然,就是不会超过_size,那么直接就开n个大小空间,利用循环并且控制好下标放到tmp里面就可以了

这里面pos + len 其实等价于 比如 pos +1 pos+1 - pos = 1这样其实算的是从pos位置开始计算的个数,也就是_size-pos,和上面的小知识点一样

string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			size_t n = len;
			if (len == npos || pos + len >= _size)
			{
				n = _size - pos;
			}
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;//返回值是自定义类型,必须调用拷贝构造进行深拷贝
		}

iterator迭代器 左闭右开[ )

在string类域里是一个char*指针类型重命名
typedef 这里的规律是 原本的类型放在中间,自定义名字放在右边,要加分号,宏才不加分号

定义

这里实现了两个版本,一个是读写 ,另一个是const对象的只读
end指针 return _str+_size 保证了end是开区间,指向最后一个的下一个

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

使用

细节:只要支持了迭代器,编译器自动支持范围for
并且使用迭代器时用auto自动推导(迭代器类型有点长)

ljh::string::iterator it = s1.begin();
	while (it < s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;

	const ljh::string s3("hello world");
	s3[0];
	//ljh::string::const_iterator it1 = s3.begin();
	auto it1 = s3.begin();

	while (it1 < s3.end())
	{
		cout << *it1;
		it1++;
	}
	cout << endl;

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

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

相关文章

代码随想录第21天 | 回溯理论基础 77. 组合

回溯理论基础 回溯法解决的问题都可以抽象为树形结构&#xff0c;是的&#xff0c;我指的是所有回溯法的问题都可以抽象为树形结构&#xff01; 因为回溯法解决的都是在集合中递归查找子集&#xff0c;集合的大小就构成了树的宽度&#xff0c;递归的深度&#xff0c;都构成的…

MySQL面试题总结(部分)

一.介绍MySQL为什么在面试中会提及 1.为什么要在面试时MySQL会被提及&#xff1f; 在面试中问MySQL问题有几个主要原因&#xff1a; 1. 数据库管理系统的重要性&#xff1a;MySQL作为一种常用的关系型数据库管理系统(RDBMS)&#xff0c;在互联网和企业应用中得到广泛使用。对数…

Conda安装及使用方法(常用命令)

系列文章目录 文章目录 系列文章目录前言一、Conda下载安装1.下载2.安装3.配置国内源 二、Conda安装Python环境1.创建虚拟环境2.激活虚拟环境3.虚拟环境安装Python库 三、Conda环境环境执行脚本四、PyCharm配置Conda环境五、Conda迁移环境1.方式一&#xff1a;拷贝环境2.方式二…

Modbus通信从入门到精通_1_Modbus通信基础

关于Modbus通信的相关知识比较零碎&#xff0c;此处对查找到的知识点从理论&#xff0c;通信协议、使用方法方面进行整理。 值得学习的博文&#xff1a;Modbus及调试用软件介绍&#xff1b;Modbus协议和上位机应用开发介绍 文章目录 1. Modbus通信理论1.1 Modbus通信特点1.2 存…

多线程(1): 线程的创建、回收、分离

1. 多线程概述 多线程在项目开发过程中使用频率非常高&#xff0c;因为使用多线程可以提高程序的并发性。提高程序并发性有两种方式:(1)多线程 (2)多进程。但是多线程对系统资源的消耗会更加少一些&#xff0c;并且线程和进程执行效率差不多。 在执行系统应用程序时&#xff…

2023/7/8总结

Tomcat 启动&#xff1a;双击bin目录下的startup.bat文件停止&#xff1a;双击bin目录下的shutdown.bat 文件访问 &#xff1a;http://localhost:8080&#xff08;默认是8080&#xff0c;可以修改&#xff09; git的使用 打开git bash git config --global user.name "名…

Vue3---什么是路由缓存问题

使用带有参数的路由时需要注意的是&#xff0c;当用户从 /users/johnny 导航到 /users/jolyne 时&#xff0c;相同的组件实例将被重复使用。因为两个路由都渲染同个组件&#xff0c;比起销毁再创建&#xff0c;复用则显得更加高效。不过&#xff0c;这也意味着组件的生命周期钩…

500万PV的网站需要多少台服务器?

1. 衡量业务量的指标 衡量业务量的指标项有很多&#xff0c;比如&#xff0c;常见Web类应用中的PV、UV、IP。而比较贴近业务的指标项就是大家通常所说的业务用户数。但这个用户数比较笼统&#xff0c;其实和真实访问量有比较大的差距&#xff0c;所以为了更贴近实际业务量及压力…

什么是提示工程?

原文链接&#xff1a;芝士AI吃鱼 理解大规模人工智能模型为何如此行事是一门艺术。即使是最有成就的技术专家也会对大型语言模型 (LLM) 的意想不到的能力感到困惑&#xff0c;大型语言模型是ChatGPT等人工智能聊天机器人的基本构建模块。 因此&#xff0c;提示工程成为生成式 …

特征选择算法 | Matlab 基于最大互信息系数特征选择算法(MIC)的分类数据特征选择

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 特征选择算法 | Matlab 基于最大互信息系数特征选择算法(MIC)的分类数据特征选择 部分源码 %--------------------

python 常用数据结构-列表

list 列表 列表定义与使用列表常用方法列表嵌套列表推导式 列表定义 列表是有序的可变元素的集合&#xff0c;使用中括号[]包围&#xff0c;元素之间用逗号分隔 列表是动态的&#xff0c;可以随时扩展和收缩 列表是异构的&#xff0c;可以同时存放不同类型的对象 列表中允…

阶乘后的零(力扣)数学 JAVA

给定一个整数 n &#xff0c;返回 n! 结果中尾随零的数量。 提示 n! n * (n - 1) * (n - 2) * … * 3 * 2 * 1 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;0 解释&#xff1a;3! 6 &#xff0c;不含尾随 0 示例 2&#xff1a; 输入&#xff1a;n 5 输出&…

WSL2 及 docker开发环境搭建

WSL2 及 docker开发环境搭建 1.使能WSL 控制面板->程序->程序和功能->启动或关闭Windows功能->勾选红框中选项->确认后重启电脑 &#xfeff; 2.下载Linux Kernel Update安装包 下载地址如下&#xff0c; 附件已将下载的安装包作为附件形式上传&#xff0c;…

ITIL 4服务连续性管理实践

一、目的和描述 关键信息 服务连续性管理实践的目的是确保灾难发生时&#xff0c;服务的可用性和性能能够保持在足够的水平。本实践提供了一个框架机制&#xff0c;利用产生有效响应的能力来构建组织的弹性&#xff0c;以保障关键利益相关者的利益&#xff0c;还有组织的声誉…

element 封装dialog弹窗组件鼠标移动到弹窗出现title

问题&#xff1a; element 封装dialog弹窗组件鼠标移动到弹窗出现title 封装的组件 <template><el-dialog title"111"v-bind"$attrs" v-on"$listeners" :visible.sync"show" ></el-dialog> </template><s…

02-webpack的热更新是如何做的,以及原理

一、是什么 HMR 可以理解为模块热替换&#xff0c;指在应用程序运行过程中&#xff0c;替换、添加、删除模块&#xff0c;而无需重新刷新整个应用. 如&#xff0c;我们在应用运行过程中修改了某个模块&#xff0c;通过自动刷新会导致整个应用的整体刷新&#xff0c;那页面中的…

pygame伪3d 实现地面效果

教程来自What is Mode 7? Let’s code it! 油管镜像 import cv2 import pygame import sys from pygame import gfxdraw import numpy as np(width, height) (800, 600) pygame.init() screen pygame.display.set_mode((width, height)) image pygame.image.load("11…

ElasticSearch基础学习(SpringBoot集成ES)

一、概述 什么是ElasticSearch&#xff1f; ElasticSearch&#xff0c;简称为ES&#xff0c; ES是一个开源的高扩展的分布式全文搜索引擎。 它可以近乎实时的存储、检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;处理PB级别的数据。 ES也…

什么是API接口?主要作用是什么?

API英文全称为&#xff1a;Application Programming Interface&#xff0c;中文意思是应用程序编程接口&#xff0c;它是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力。 主要作用&#xff1a; API之主要目的是提供应用程…

【笔记】Pycharm配置Node.js运行js代码

最近在学习关于Js逆向的知识&#xff0c;需要在PyCharm中运行Js程序&#xff0c;记录一下配置过程。 安装Node.js Node.js中文网 选择自己电脑对应的安装包下载暗转即可 安装好软件后&#xff0c;配置node.js环境变量。 完成安装和环境配置后&#xff0c;打开cmd测试是否安…