string类模拟实现(c++)(学习笔记)

news2024/11/16 23:47:09

string

  • 1.构造函数
    • 1.1 不带参构造
    • 1.2 带参数的构造函数
    • 1.3 合并两个构造函数。
  • 2. 析构函数
  • 3.拷贝构造函数
  • 4. 赋值运算符重载
  • 5. size()/capacity()
  • 6. 解引用[]
  • 8.iterator迭代器
  • 7.Print()
  • 8.> ==
  • 8. push_back()&append()
    • 8.1 reserve()
  • 9. +=
  • 10.insert()
    • 10.1 任意位置插入一个字符
    • 10.2 在任意位置插入字符串
  • 11. resize()
  • 12 erase()
  • 13. swap()
  • 14.find()
    • 14.1 查找字符
    • 14.2 查找字符串
  • 15 <<和>>
    • 15.1 流提取
    • 15.2 流插入

框架:

namespace abc
{
	class string
	{
	private:
		char* _str;  
		size_t _size;  //有效字符大小
		size_t _capacity; //总容量
	};

}

1.构造函数

因为string类构造函数有多种形式,这里只实现两个最常用的。不带参数的,带参数的。

1.1 不带参构造

示例1·:cout对空指针解引用报错

		string()
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{}
		char* c_str()
		{
			return _str;
		}
	void Test1()
	{
		abc::string s1;
		cout << s1.c_str() << endl;//该行报错
	}

报错原因:首先s1的字符串指向的为空指针。c_str会返回一个c格式的字符串,但是cout<<s1.c_str()会自动识别类型,识别为字符串类型,打印就会解引用。造成空指针访问报错。
改正:如果换成标准库里的string就不会报错,因为它赋的不是空指针,是空字符串。

改1:
在这里插入图片描述
如果这样直接赋值的化,成员变量_str为非const变量,会出现权限放大的错误。这样做也不可行。

改2:
申请一个字符的空间,然后函数体里面初始化。

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

修改成这样就可以了。这样后续既可以修改字符串内容,也可以打印空字符串。

1.2 带参数的构造函数

示例2:

		string(const char* str)   //加const是因为常量字符串必须用const接收,不然会在传参时出错
			:_str(str)   //     *******该行会报错
			,_size(strlen(str))
			,_capacity(strlen(str)+1)
		{}

	void Test1()
	{
	
		abc::string s2("hello world");
		cout << s2.c_str() << endl;
	}

报错原因:
因为str为const类型,而成员变量_str为非const类型,赋值产生权限放大。给_str加const不可取,会导致后续没法修改字符串内容。

改1:还是开空间,能存上常量字符串(”hello“),并且还能保证能修改内容,初始化列表初始化不方便,选择在函数体初始化。

		string(const char* str)
			: _size(strlen(str))
		{
			_capacity = _size;//容量就是能装有效字符的个数
			_str = new char[_capacity + 1]; //开的空间要多包含一个\0
			strcpy(_str, str);//拷贝字符串内容
		}

1.3 合并两个构造函数。

看起来是两个无参有参的构造函数,其实可以合并成一个,因为第一个无参的就是一个空字符串。

示例:

		string(const char* str = "")  //使用缺省函数来合并
			: _size(strlen(str))
		{
			_capacity = _size;//容量就是能装有效字符的个数
			_str = new char[_capacity + 1]; //开的空间要多包含一个\0
			strcpy(_str, str);//拷贝字符串内容
		}

2. 析构函数

构造函数写完对应写析构函数,只需要保证new和delete符号匹配即可。
示例:

		//析构函数
		~string()
		{
			delete[] _str;  //都用带括号的
			_str = nullptr;
			_size = _capacity = 0;
		}

3.拷贝构造函数

拷贝构造函数逻辑上不难。
示例:

		//拷贝构造函数
		string(const string& str)
			:_size(str._size)
			, _capacity(str._capacity)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str._str);
		}
	void Test1()
	{
		abc::string s1;
		abc::string s2("hello world");

		abc::string s3(s1);
		abc::string s4(s2);

		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		cout << s3.c_str() << endl;
		cout << s4.c_str() << endl;
	}

程序运行正确,没有报错。

4. 赋值运算符重载

示例:(经典标0)

		//赋值运算符重载
		string& operator=(const string& s)
		{	
			_size = s._size;
			_capacity = s._capacity;
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
			return *this;
		}

	void Test2()
	{
		abc::string s1;
		abc::string s2("hello world");
		abc::string s3("i love you peter");

		s2 = s3;

		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		cout << s3.c_str() << endl;
	}

问题:
1.首先s2的空间没有释放,导致内存泄露问题。
2. 没有考虑拷贝多少的问题,比如相等空间可以i直接赋值,大空间给小空间,小空间给大空间的问题。

优化1:首先要将s2的内存释放掉,然后申请一块新空间,赋给s2。(这样可以不用考虑原因2的三种情况,简化逻辑)

		string& operator=(const string& s)
		{
			delete[] _str;//释放空间
			_str = new char[s._capacity + 1]; //申请新空间
			strcpy(_str, s._str);   //拷贝
			_size = s._size;
			_capacity = s._capacity;
			return *this;
		}

问题:
3.如果内存申请失败,该版本会弄丢s2的原有值。

优化2:
使用临时变量开空间,开成功再赋回去。

		string& operator=(const string& s)
		{
			char* tem = new char[s._capacity + 1]; //先申请新空间
			strcpy(tem, s._str);   //拷贝
			//没有抛异常,往下执行
			delete[] _str;//释放空间
			_str = tem;   //赋回来

			_size = s._size; 
			_capacity = s._capacity;
			return *this;
		}

问题:如果自己给自己赋值,原地不动就好了。
优化3:

		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tem = new char[s._capacity + 1]; //先申请新空间
				strcpy(tem, s._str);   //拷贝
				//没有抛异常,往下执行
				delete[] _str;//释放空间
				_str = tem;   //赋回来

				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}

5. size()/capacity()

示例:

		size_t size()
		{
			return _size;
		}
		size_t capacity()
		{
			return _capacity;
		}
			void Test3()
	{
		const abc::string s1;
		const abc::string s2("hello world");
		abc::string s3("i love you peter");

		cout << s1.size() << endl;   //报错
		cout << s2.capacity() << endl;  //报错
	}

问题:调用的两个函数,都出现了权限放大的错误。
改正:给this加上const即可。

		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}

6. 解引用[]

示例:

		char& operator[](size_t pos)
		{
			assert(pos < _size);  //pos位置得合法
			return _str[pos];
		}
		void Test3()
		{
			abc::string s2("hello world");
	
			for (size_t i = 0; i < s2.size(); i++)
			{
				cout << (s2[i]) << " ";
			}
		}

问题:如果const对象解引用,会产生权限放大的错误,得把this加const,解决问题。但const对象返回值也得是const(防止对象被修改)。这就与非const对象产生矛盾。

改正:再次重载一个适合const对象的引用函数,即可解决问题。(运算符重载yyds)

		char& operator[](size_t pos)
		{
			assert(pos < _size);  //pos位置得合法
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);  //pos位置得合法
			return _str[pos];
		}

8.iterator迭代器

迭代器是一种比较方便的访问有序对象的一种通用方法。
示例:

	public:
		//迭代器
		typedef char* iterator;

	public:
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
//测试代码
	void Test4()
	{
		abc::string s2("hello world");

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


		// 范围for
		for (auto ch : s2)
		{
			cout << ch << " ";
		}

	}

解释:因为范围for的底层就是迭代器,如果迭代器底层实现好了的话,范围for也可以用。

问题:这个代码同样仅仅考虑了非const的问题,因此再写一组重载。

改:加一组const迭代器就可以了。

	public:
		//迭代器
		typedef const char* const_iterator;  //const型

	public:
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
	void Test4()
	{
		const abc::string s2("hello world");

		abc::string::const_iterator it = s2.begin();
		while (it != s2.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;

		// 范围for
		for (auto ch : s2)
		{
			cout << ch << " ";
		}

	}

7.Print()

该函数实现不难,仅仅需要注意区分const和非const即可。
示例:

		void Print()const
		{
			// 范围for
			for (auto ch : *this)
			{
				cout << ch << " ";
			}
		}
		void Test5()
		{
			const abc::string s2("hello world");
		
			s2.Print();
		
		}

因为前面已经实现好const和非const迭代器,这样Print函数,const对象调用const迭代器,非const对象调用非const迭代器。能成功运行。

8.> ==

string类的比大小遵循c语言的方式,使用strcmp复现。
示例:注意const和非const对象。

		bool operator>(const string& s)const
		{
			return strcmp(_str, s._str) > 0;
		}
		bool operator==(const string & s)const
		{
			return strcmp(_str, s._str) == 0;
		}
		bool operator>=(const string& s)const
		{
			return _str > s._str || _str == s._str;
		}
		bool operator<(const string& s)const
		{
			return !(_str>=s._str);
		}
		bool operator<=(const string& s)const
		{
			return _str < s._str || _str == s._str;
		}
		bool operator!=(const string& s)const
		{
			return !(_str==s._str);
		}

注意:仅仅写出来>+==就可以把其他都复用出来了。

8. push_back()&append()

该算法实现一个字符串增和一个字符增。
示例:


		void reserve(size_t n)
		{
			char* tem = new char[n + 1];//多开一个空间存\0
			strcpy(tem, _str);
			delete[] _str;
			_str = tem;

			_capacity = n;
		}
		void append(const char* str)
		{
			size_t len = strlen(str);
			//需要扩容
			if(_size + len > _capacity)
			{
				//防止new_capacity比len小
				size_t new_capacity = (2 * _capacity) > (_size + len) ? (2 * _capacity) : (_size + len);
				reserve(new_capacity);
			}
			strcpy(_str + _size, str);
			_size += len;
		}


		void push_back(char ch)
		{
			//容量不够需要扩容
			if (_size + 1 > _capacity)
			{
				reserve(2 * _capacity);
			}
			_str[_size++] = ch;  
			_str[_size] = '\0';  // 别忘了\0
		}
	void Test7()
	{
		abc::string s2("hello world");
		const char ch = 'a';
		
		s2.push_back(ch);
		s2.append("xxxxaaa");

	}

问题:但是当字符串为空的时候,如果添加字符的话,push_back会报错,因为capacity为0,导致扩容还是0。

改:为了简单起见,我们只需要保证capacity不为0即可,可以在构造函数中修改。如下:

		string(const char* str = "")
			: _size(strlen(str))
		{
			_capacity = (_size == 0 ? 3 : _size);//容量就是能装有效字符的个数
			_str = new char[_capacity + 1]; //开的空间要多包含一个\0
			strcpy(_str, str);//拷贝字符串内容
		}

这样即可解决问题。

8.1 reserve()

该函数的实现发方法仍然有一些问题,就是当要保留的空间小于原有的空间。函数不做改动。
改:加一个判断语句即可。

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tem = new char[n + 1];//多开一个空间存\0
				strcpy(tem, _str);
				delete[] _str;
				_str = tem;
				_capacity = n;
			}
		}

9. +=

使用上面的append和push,实现此函数轻而易举。
示例:

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

	void Test7()
	{
		abc::string s2("hello world");
		const char ch = 'a';
		
		//s2.push_back(ch);
		//s2.append("xxxxaaa");
		s2 += "hello world";
		s2 += 'a';
	}

10.insert()

10.1 任意位置插入一个字符

示例:
在pos位置插入一个字符ch。

		//在第pos位置插入
		void insert(size_t pos, char ch)
		{
			//位置合法
			assert(pos <= _size);

			if (_size + 1 > _capacity)
			{
				reserve(2 * _capacity);
			}
			size_t  end = _size - 1;
			while (end >= pos)
			{
				_str[end + 1] = _str[end];
				end--;
			}

			_str[pos] = ch;
			_size++;
		}

问题:
1.插入一个字符串后,结尾没加\0。
2.最好不要用end>=pos,因为都是无符号数,当pos和end同时等于0,end–,end变成了-1(但是是无符号数),因此会导致越界访问。

改正:
1.不能让end等于0,让end到1就结束,从前往后赋值。
2.最后加上\0。

		void insert(size_t pos, char ch)
		{
			//位置合法
			assert(pos <= _size);

			if (_size + 1 > _capacity)
			{
				reserve(2 * _capacity);
			}
			size_t  end = _size;
			while (end > pos)  
			{
				_str[end] = _str[end - 1];
				end--;
			}

			_str[pos] = ch;
			_size++;
			_str[_size] = '\0';  //没有写\0
		}

10.2 在任意位置插入字符串

示例:

		//插入字符串
		void insert(size_t pos, const char* str)
		{
			assert(str);
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				size_t new_capacity = (2 * _capacity) > (_size + len) ? (2 * _capacity) : (_size + len);
				reserve(new_capacity);   
			}
			//从后往前一个字符一个字符移动,记得最后不\0,到插入位置结束

			size_t end = _size - 1;
			while (end >= pos&&end!=-1)
			{
				_str[end + len] = _str[end];
				end--;
			}
			int i = 0;  //计数
			size_t count = len;
			while (count--)
			{
				_str[pos++] = str[i++];
			}

			_size += len;
			_str[_size] = '\0';
		}

问题:
1.同样是end和pos的关系,应该从前往后赋值,然后让后面的作为结束条件,这样就防止end越界了。

改正:
1.使用后面的下标作为结束条件,值得学习。
2.挪动完数据后,需要拷贝,可以使用strncpy进行拷贝。

				//插入字符串
		void insert(size_t pos, const char* str)
		{
			assert(str);
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				size_t new_capacity = (2 * _capacity) > (_size + len) ? (2 * _capacity) : (_size + len);
				reserve(new_capacity);
			}
			//从后往前一个字符一个字符移动,记得最后不\0,到插入位置结束
			size_t end = _size + len;
			while (end > pos+len-1)
			{
				_str[end] = _str[end - len];
				end--;
			}
			//按字节拷贝
			strncpy(_str + pos, str, len);
			_size += len;
		}

11. resize()

当容量小于size时,直接赋\0;当容量>size&&<capacity时,直接填充指定字符;当容量>capacity时,先扩容再填充指定字符。
示例:

	//比_size小,就直接阶段、比_capacity大就用\0填充
		void resize(size_t n, char ch = '\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
			}
			//需不需要异地扩容
			else
			{
				if (n <= _capacity)
				{
					memset(_str + _size, ch, n - _size);
					//最后再加上\0;
					_str[n] = '\0';
				}
				else
				{
					
					//char* tem = new char[n+1];  //要多申请一个空间
					//strcpy(tem, _str);
					//delete[] _str;
					//_str = tem;
					reserve(n);
						
					memset(_str + _size, ch, n - _size);
					_str[n] = '\0';
					_capacity = n;
				}
			}
			_size = n;
			
		}

优化:
此代码逻辑有些冗余,可以修改成如下代码,更加简洁。

				//比_size小,就直接阶段、比_capacity大就用\0填充
		void resize(size_t n, char ch = '\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
			}
			//需不需要异地扩容
			else
			{
				if (n > _capacity)
				{
					reserve(n);
					_capacity = n;
				}
				memset(_str + _size, ch, n - _size);
				_str[n] = '\0';
			}
			_size = n;

		}

12 erase()

擦除给定位置的n个字符。

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

		}

问题:else的代码有一些冗余,可以使用strcpy来简化

优化:

				// pos为位置
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos + 1;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}

		}

13. swap()

交换两个string对象的内容。

		//1.   swap(s1,s2)
		//2.   s1.swap(s2)
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

对于两种swap的方式,很明显第二种更高效,第一种需要拷贝构造,第二种自己实现的方式则不需要。

14.find()

14.1 查找字符

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

14.2 查找字符串

使用strstr()库函数,复现。

		size_t find(const char* str, size_t pos = 0)
		{
			assert(str);
			char* p = strstr(_str + pos, str);   //查找字符串的库函数
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
		}

15 <<和>>

15.1 流提取

因为this指针的原因,不能将其定义为成员函数。

	ostream& operator<< (ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	
	void Test8()
	{
		abc::string s1("hello world");
		s1 += '\0';
		s1 += "aaaaaaaaaa";

		cout << s1 << endl;  //hello worldaaaaaaaaaa
		cout << s1.c_str() << endl; //hello world
	}

注意:这里要简单提一下:为什么两个输出的函数不一样。
第一个s1是我们自己实现的函数,它是根据字符串的个数打印的。
而第二个s1.c_str返回的是指针,编译器会根据指针来打印,遇到\0就停止!

15.2 流插入

示例:

	istream& operator>> (istream& in, string& s)
	{
		char ch;
		in >> ch;
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			in >> ch;
		}
		return in;
	}

报错:输入“hello world”
该代码不能完成功能,因为in>>ch,读不进去空格和回车。所以根本跳不出循环。

改正:换一种方式获取缓冲区数据。使用get()函数。

	istream& operator>> (istream& in, string& s)
	{
		char ch = in.get();   //可以读到空格
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}

问题:1.频繁+=会导致频繁扩容。
2.连续两次读取数据不会清除第一次的数据。

优化:1.使用缓冲区的概念,一下+=一个缓冲区。
2.每次提取之前,要清空字符。


	void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
	istream& operator>> (istream& in, string& s)
	{

		s.clear();  //每次读取前,清空s
		char buf[128];  //申请一个缓冲区
		char ch = in.get();
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buf[i++] = ch;
			if (i == 127)  //剩一个位置给\0
			{
				buf[i] = '\0';
				s += buf;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)   //缓冲区有内容,再加上
		{
			buf[i] = '\0';
			s += buf;
		}
		return in;
	}

以上就是string类部分库函数实现。

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

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

相关文章

在线客服软件的市场需求及前景如何?

随着互联网的不断发展&#xff0c;越来越多的企业开始意识到在线客服软件在客户服务中的重要作用。现在&#xff0c;各种形态的在线客服软件涌现出来&#xff0c;如何选择适合自己公司的线上客服软件成为了企业面临的一个挑战。本文将从市场需求和前景方面分析在线客服软件行业…

【Loopback Detection 环回检测以及原理解读】

Loopback Detection简介 Loopback Detection&#xff08;环回检测&#xff09;通过周期性发送环回检测报文来检测设备下挂网络是否存在环路。 网络中的环路会导致设备对广播、组播以及未知单播等报文进行重复发送&#xff0c;造成网络资源浪费甚至网络瘫痪。为了能够及时发现…

MS4553S双向电平转换器可pin对pin兼容TXB0102/TXS0102

MS4553S是一款双向电平转换器&#xff0c;可以用作混合电压的数字信号系统中。其使用两个独立构架的电源供电&#xff0c;A端供电电压范围是1.65V到5.5V&#xff0c;B端供电电压范围是2.3V到5.5V。可用在电压为1.8V、2.5V、3.3V和5V的信号转换系统中。当OE端为低电平时&#xf…

大学生毕业嵌入式和JAVA哪条未来更有前景?

今日话题&#xff0c;大学生毕业后选择嵌入式和Java两个岗位哪个更具前景&#xff1f;答案因个人情况而异。通常来说&#xff0c;对于零基础转行的同学&#xff0c;学习Java可能会是一个更广泛选择的建议&#xff0c;因为Java岗位更多&#xff0c;且不需要涉及硬件知识。然而&a…

Live800:一个优秀的客服应具备哪些技能?

一个优秀的客服应该具备哪些技能&#xff1f;这是每个企业在招聘和培训客服人员时都需要考虑的问题。一名优秀的客服不仅需要善于沟通&#xff0c;还需要具备专业知识、灵活应变、耐心细致等多方面的能力。在这篇文章中&#xff0c;我们将从多个方面探讨一个优秀客服应该具备什…

MATLAB——极限学习机参考程序

欢迎关注“电击小子程高兴的MATLAB小屋” %% I. 清空环境变量 clear all clc %% II. 训练集/测试集产生 %% % 1. 导入数据 load iris_data.mat %% % 2. 随机产生训练集和测试集 P_train []; T_train []; P_test []; T_test []; for i 1:3 temp_input features((i-…

Bug小能手系列(python)_12: 使用mne库读取.set文件报错 TypeError: ‘int‘ object is not iterable

使用mne库读取.set文件报错 0 引言1. 报错原因2. 推荐解决方案3. 总结 0 引言 在使用mne库读取.set文件&#xff0c;然后对文件进行处理。在运行过程中出现报错&#xff1a;TypeError: int object is not iterable 其中&#xff0c;代码库包的版本这里主要介绍mne的版本&…

MYSQL 连接

高频 SQL 50 题&#xff08;基础版&#xff09; - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台 1378. 使用唯一标识码替换员工ID SELECT COALESCE(unique_id, NULL) AS unique_id,name FROM Employees LEFT JOIN EmployeeUNI ON Employees.…

7.MidBook项目经验之阿里OSS,微信支付(退款),定时任务,图表数据处理

1.阿里云实名认证 阿里云对象存储oss,标准高频率访问, 低频访问存储,归档存储(根本不经常访问) 冗余存储(备份) 读写权限(所有人还是自己访问) Component public class ConstantOssPropertiesUtils implements InitializingBean {不用注入,由spring创建bean使用流 MultipartFil…

SpringBoot整合RabbitMQ并实现消息发送与接收

系列文章目录 解析JSON格式参数 & 修改对象的key VUE整合Echarts实现简单的数据可视化 Java中运用BigDecimal对字符串的数值进行加减乘除等操作 List&#xff1c;HashMap&#xff1c;String,String&#xff1e;&#xff1e;实现自定义字符串排序&#xff08;key排序、Valu…

Node介绍(nvm安装和npm常用命令)

文章目录 Node 介绍为什么要学习 Node.jsNode.js 是什么Node能做什么nvm常用的 nvm 命令npm 快捷键npm 常用命令切换 npm 下包镜像源常用命令 Node 介绍 为什么要学习 Node.js 企业需求 具有服务端开发经验更改front-endback-end全栈开发工程师基本的网站开发能力 服务端前端…

TP4067带电池反接保护500MA线性锂电池充电芯片

概述 TP4067 是一款完整的单节锂电池充电器&#xff0c;带电池正负极反接保护输入电源正负极反接保护的单芯片&#xff0c;兼容大小3mA-600mA充电电流。采用涓流、恒流、恒压控制&#xff0c;SOT23-6封装与较少的外部元件数目使得TP4067成为便携式应用的理想选择.TP4067可以适…

ims-go项目搭建

通过集成开发工具Goland创建项目 整合Gin框架&#xff0c;在终端中输入如下命令&#xff1a; go get -u github.com/gin-gonic/gin 整合Gorm&#xff0c;安装命令如下&#xff1a; go get -u gorm.io/gorm 安装sqlserver驱动&#xff0c;安装命令如下&#xff1a; go get -u…

docker报错问题解决:Error Invalid or corrupt jarfile app.jar

文章目录 1.问题描述2.问题分析3.问题解决 1.问题描述 此时处在 /home/ubuntu/app 目录下&#xff0c;并且在该目录下有一个 jenkins-0.0.1-SNAPSHOT.jar。 我在 /home/ubuntu/app 目录下执行了 docker 容器运行命令&#xff1a; # 映射 8859 端口 # 容器名为 jenkins-demo #…

常用Python自动化测试框架有哪些?优缺点对比

随着技术的进步和自动化技术的出现&#xff0c;市面上出现了一些自动化测试框架。只需要进行一些适用性和效率参数的调整&#xff0c;这些自动化测试框架就能够开箱即用&#xff0c;大大节省了测试时间。而且由于这些框架被广泛使用&#xff0c;他们具有很好的健壮性&#xff0…

Unity DOTS System与SystemGroup概述

最近DOTS终于发布了正式的版本, 我们来分享以下DOTS里面System关键概念&#xff0c;方便大家上手学习掌握Unity DOTS开发。 对惹&#xff0c;这里有一个游戏开发交流小组&#xff0c;希望大家可以点击进来一起交流一下开发经验呀&#xff01; System是迭代计算与处理World中的…

面试总结分享:25道数据库测试题

1&#xff09;什么是数据库测试&#xff1f; 数据库测试也称为后端测试。数据库测试分为四个不同的类别。数据完整性测试 数据有效性测试 数据库相关的性能 测试功能&#xff0c;程序和触发器 2&#xff09;在数据库测试中&#xff0c;我们需要正常检查什么&#xff1f; 通常&a…

【UE5】 ListView使用DataTable数据的蓝图方法

【UE5】 ListView使用DataTable数据的蓝图方法 ListView 是虚幻引擎中的一种用户界面控件&#xff0c;用于显示可滚动的列表。它可以用于显示大量的数据&#xff0c;并提供了各种功能和自定义选项来满足不同的需求。 DataTable是虚幻引擎中的一种数据表格结构&#xff0c;用于存…

美创科技信创数据安全「利基者」!

近日&#xff0c;第一新声研究部正式发布《2023年中国信创网络安全产品竞争力象限》&#xff08;下称“象限报告“&#xff09;。 ◼︎ 象限报告综合考虑企业占有率、在技术/应用上的成熟度、在客户方面的交付完成度及口碑、产品在市场/营销/商业模式/行业拓展等战略上的领先性…

在ERP管理系统中,库存管理的基本流程是什么?

在ERP管理系统中&#xff0c;库存管理的基本流程是什么&#xff1f; 下面我就以我们公司正在用的简道云库存管理系统为例&#xff0c;为大家进行库存管理基本流程的演示 这个系统是我们公司自己搭建的&#xff0c;大家如果有需要可以自取&#xff0c;也可以在模板的基础上自行…