C++ stl容器string的底层模拟实现

目录

前言:

1.成员变量

2.构造函数与拷贝构造函数

3.析构函数

4.赋值重载

5.[]重载

6.比较关系重载

7.reserve

8.resize

9.push_back,append和重载+=

10.insert

11.erase

12.find

14.迭代器

15.流插入,流提取重载

16.swap

17.c_str

18.完整代码+测试

总结:


前言:
本篇模拟string类的底层实现,只会调一些重要的接口实现,结尾附上完整代码。

1.成员变量

	private:
		//const char* _str
		//不使用const修饰,string类的字符串是可以修改的,而且例如扩容的时候也需要修改指针的指向
		// 然后就是_str的初始化是最好new出来的,如果直接初始化为空指针,例如在流插入的时候需要解引用,就不行了。		
		char* _str;
		size_t _capacity;
		size_t _size;

		//static const size_t npos=-1;//静态成员变量要在类外声明初始化,不能给缺省值,但是如果加上const,又支持这样给缺省值的语法了
		static const size_t npos;

首先注意的就是_str,不能是const类型的,因为我们的string字符串是可以改的,而且如果是const类型的,在扩容等也需要改变指针的指向;而且我们在流插入的时候解引用,所以在初始化的时候最好是new出来的。

静态成员常量npos需要在类外声明初始化,注意的是,本来静态成员变量是不能给缺省值的,但是加上了const就又可以给缺省值了。

2.构造函数与拷贝构造函数

		string(const char* str="")//strlen遇到字符0就停了,这样传参数也正好,无参的size也是0,或者写成"\0",但是不能写成'\0'类型不匹配
			:_size(strlen(str))//strlen内部获取字符串长度是需要解引用的,所以最好初始化不要给空指针,
		{
			_capacity = _size == 0 ? 3 : _size;
			_str = new char[_capacity + 1];//+1给字符0准备,实际capacity没有字符0
			strcpy(_str, str);
		}

	    string(const string& st)
			:_size(st._size)
			,_capacity(st._capacity)
		{
			_str = new char[st._capacity + 1];//+1给字符0准备,实际capacity没有字符0
			strcpy(_str, st._str);
		}

构造的参数这样写是因为这样传参数相当于无参的构造只有一个字符0,正好符合strlen计算长度的规则,无参的时候_size就是0了。

_capacity需要判断是否为0,不然什么都没有直接二倍扩容会出错。

_str的容量加1是给字符0准备的,但是实际_size与_capacity是没有计算字符0的,而且在调试的时候也看不到,但是还是要加上。

构造的思路就是根据传参字符串的的容量开一块新的空间,进行拷贝完成初始化。

拷贝构造就是深拷贝了,所以就是根据要拷贝的对象的大小开一块空间,再让空间拷贝给给调用的对象,注意容量与大小也要变。

3.析构函数

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

注意配套使用delete就行。

4.赋值重载

		string& operator=(const string& st)
		{
			//这样写如果new出错了,原来的对象也被破坏了
			//delete[] _str;
			//_str = new char[st._capacity + 1];
			//strcpy(_str, st._str);
			//_capacity = st._capacity;
			//_size = st._size;


			if (this != &st)
			{
				char* tmp = new char[st._capacity + 1];
				strcpy(tmp,st._str);
				delete[] _str;
				_str = tmp;

				_capacity = st._capacity;
				_size = st._size;

				return *this;
			}
		}

赋值重载就要考虑空间的问题了,同时也是一个深拷贝。

假设s1=s2,如果s1容量多,直接将s2赋值过去没问题,但是会造成空间的浪费。

如果s1的空间少,此时再将s2赋值就会出现问题了,所以综合一下:先让原空间也就是被赋值的空间给释放掉,然后再根据赋值的内容开辟空间,让被赋值的指向这块新的空间,再进行拷贝,并完成大小和容量的更新。

上面的为注释的写法,但是还有一点问题,如果new失败了,那被赋值的空间早已被释放了,这就破坏了原来的空间,所以为了不破坏原有的空间,先根据赋值的内容开一块空间,再将赋值的内容拷贝给这块空间,此时再释放旧空间,再将旧空间指向新空间即可。

注意要判断自己不能给自己赋值。

5.[]重载

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

		char& operator[](size_t pos) 
		{
			assert(pos < _size);
			return _str[pos];
		}

注意断言,下标位置不能等于_size,_size指向最后一个字符的下一个也就是字符0,但是实际库中的string的字符0我们是可以访问到的,这里主要表示有效数据的访问。

要提供两个版本,方便const对象调用。 

6.比较关系重载

		 //string比较
		 bool operator>(const string& st) const
		 {
			 return strcmp(_str, st._str) > 0;
		 }

		 bool operator==(const string& st) const
		 {
			 return strcmp(_str, st._str) == 0;
		 }
		 
		 bool operator>=(const string& st) const
		 {
			 return *this > st || *this == st;//如果不用const修饰==的重载,这里调用就不能st==*this了,因为st是const类型的		 
		 }

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

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

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

这个没什么好说的了,就是写两个复用就完了。

注意没有修改成员变量的成员函数最好还是就是const,像>=复用==的逻辑的时候,如果==不是const的成员函数,就不能倒过来写了。 

7.reserve

		 void reserve(size_t n)
		 {
			 if (n > _capacity)//如果n小于这块空间,相当于缩容了,原字符串再拷贝给缩容的空间就越界了
			 {
				 char* tmp = new char[n + 1];//这里一样是放在原对象被破坏,+1用来存放字符0
				 strcpy(tmp, _str);
				 delete[] _str;
				 _str = tmp;

				 _capacity = n;//但是capacity实际还是没有字符0的
			 }

注意判断扩容的大小不能小于原有的容量,不然就是缩容了,缩容再拷贝数据就越界了。

8.resize

		 void resize(size_t n, char ch = '\0')
		 {
			 if (n <= _capacity)
			 {
				 //保留前n个数据
				 _size = n;
				 _str[_size] = '\0';
			 }
			 else
			 {
				 if (n > _capacity)
				 {
					 reserve(n);
				 }

				 size_t i = _size;//直接在后面补初始化的数据
				 while (i < n)
				 {
					 _str[i] = ch;
					 ++i;
				 }

				 _size = n;
				 _str[_size] = '\0';
			 }
		 }

如果扩容大小小于容量,那就保留前n个数据,末尾改成字符0。

如果大,那就扩容,原有数据不变,扩容后面的数据都默认初始化为字符0。如果传的有参数,就往末尾补数据,最后别忘了更新size,还要在最后补字符0。

9.push_back,append和重载+=

		 void push_back(char ch)
		 {
			 if (_size + 1 > _capacity)
			 {
				 reserve(_capacity * 2);
			 }

			 _str[_size] = ch;
			 ++_size;
			 _str[_size] = '\0';//插入完了,字符串后面的字符0就找不到了,所以size更新完后再补一个字符0

			 //复用insert
			 //insert(_size,ch)
		 }

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

			 strcpy(_str + _size, str);//正好覆盖了原字符串的字符0
			 _size += len;

			 //复用insert
			 //insert(_size,str)
		 }
		 string& operator+=(char ch)
		 {
			 push_back(ch);
			 return *this;
		 }

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

注意尾插一个字符时的字符0的补充以及扩容即可。 

10.insert

		 string& insert(size_t pos, char ch)
		 {
			 assert(pos <= _size);
			 if (_size + 1 > _capacity)
			 {
				 reserve(_capacity * 2);
			 }
			 size_t end = _size + 1;

			 while (end > pos)
			 {
				 _str[end] = _str[end - 1];
				 --end;
			 }

			 _str[pos] = ch;
			 ++_size;

			 return *this;
		 }

		 string& 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-1)
			 {
				 _str[end] = _str[end - len];
				 --end;
			 }
			 strncpy(_str + pos, str, len);
			 _size += len;
			 return *this;

		 }

这个建议画图理解,也一样先扩容。

注意断言,在等于size的时候插入就是尾插入,不用添加字符0,直接交换了,注意pos是下标。 

11.erase

		 string& erase(size_t pos, size_t len = npos)//删除pos位置(包括pos位置)后的len个字符,如果不够删,就删完
		 {
			 assert(pos < _size);
			 if (len == npos || pos + len >= _size)
			 {
				 _str[pos] = '\0';//pos也被删了
				 _size = pos;
			 }
			 else
			 {
				 strcpy(_str + pos, _str + pos + len);
				 _size -= len;
			 }

			 return *this;
		 }

当等于缺省值的时候或者要删的字符正好删到了最后一个有效数据,直接就是在pos的位置放字符0,更新size就行了(注意pos位置的值也被删了)。

如果没有删完,就将删除后右边的子串与原字符串连接起来,更新size。 

12.find

		 size_t find(char ch, size_t pos=0)//pos为0就是代表找这个字符出现的第一个位置,返回下标;为1就是返回出现第二次的位置的下标
		 {
			 assert(pos < _size);
			 for (size_t i = pos; i < _size; ++i)
			 {
				 if (_str[i] == ch)
				 {
					 return i;
				 }
			 }

			 return npos;
		 }

		 size_t find(const char* str, size_t pos = 0)
		 {
			 char* p = strstr(_str + pos, str);//找子串可以使用暴力的kmp,但是实际作用不大,还有BM算法;strstr使用的是暴力的算法
			 if (p == nullptr)
			 {
				 return npos;
			 }
			 else
			 {
				 return p - _str;
			 }
		 }

 查找字符就是遍历一遍就行了,查找字符串需要使用strstr或者算法,也就是查找子串,strstr返回的是子串的首元素位置。

14.迭代器

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

对指针的重命名,后面容器的实现更复杂,后面再说。

注意还有const迭代器,供const对象调用,这里分开划分const成员与非const成员函数主要是因为我们使用普通迭代器大部分会修改数据。 

15.流插入,流提取重载

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;//有些编译器会让字符0打印成空格
		}

		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char 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;
	}

注意不是友元,没有访问类的成员。 

16.swap

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

直接复用即可,如果直接写调用要写成s1.swap(s2),会有两个对象三个深拷贝。 

17.c_str

		const char* c_str()
		{
			return _str;
		}

直接返回首字符地址,注意是const类型的。 

18.完整代码+测试

#pragma once
#include <string>
#include <string.h>
#include <assert.h>
#include <iostream>

using namespace std;

namespace my_string
{
	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="")//strlen遇到字符0就停了,这样传参数也正好,无参的size也是0,或者写成"\0",但是不能写成'\0'类型不匹配
			:_size(strlen(str))//strlen内部获取字符串长度是需要解引用的,所以最好初始化不要给空指针,
		{
			_capacity = _size == 0 ? 3 : _size;
			_str = new char[_capacity + 1];//+1给字符0准备,实际capacity没有字符0
			strcpy(_str, str);
		}

	    string(const string& st)
			:_size(st._size)
			,_capacity(st._capacity)
		{
			_str = new char[st._capacity + 1];//+1给字符0准备,实际capacity没有字符0
			strcpy(_str, st._str);
		}

		string& operator=(const string& st)
		{
			//这样写如果new出错了,原来的对象也被破坏了
			//delete[] _str;
			//_str = new char[st._capacity + 1];
			//strcpy(_str, st._str);
			//_capacity = st._capacity;
			//_size = st._size;


			if (this != &st)
			{
				char* tmp = new char[st._capacity + 1];
				strcpy(tmp,st._str);
				delete[] _str;
				_str = tmp;

				_capacity = st._capacity;
				_size = st._size;

				return *this;
			}
		}

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

		const char* c_str()
		{
			return _str;
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

		char& operator[](size_t pos) 
		{
			assert(pos < _size);
			return _str[pos];
		}

		 size_t size() const//size和capacity实际都没有算字符0,但是在string里面实际是有这个字符0的,但是调试是看不到的
		 {
			 return _size;
		 }

		 size_t capacity() const
		 {
			 return _capacity;
		 }

		 //string比较
		 bool operator>(const string& st) const
		 {
			 return strcmp(_str, st._str) > 0;
		 }

		 bool operator==(const string& st) const
		 {
			 return strcmp(_str, st._str) == 0;
		 }
		 
		 bool operator>=(const string& st) const
		 {
			 return *this > st || *this == st;//如果不用const修饰==的重载,这里调用就不能st==*this了,因为st是const类型的		 
		 }

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

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

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

		 void reserve(size_t n)
		 {
			 if (n > _capacity)//如果n小于这块空间,相当于缩容了,原字符串再拷贝给缩容的空间就越界了
			 {
				 char* tmp = new char[n + 1];//这里一样是放在原对象被破坏,+1用来存放字符0
				 strcpy(tmp, _str);
				 delete[] _str;
				 _str = tmp;

				 _capacity = n;//但是capacity实际还是没有字符0的
			 }
		 }

		 void push_back(char ch)
		 {
			 if (_size + 1 > _capacity)
			 {
				 reserve(_capacity * 2);
			 }

			 _str[_size] = ch;
			 ++_size;
			 _str[_size] = '\0';//插入完了,字符串后面的字符0就找不到了,所以size更新完后再补一个字符0

			 //复用insert
			 //insert(_size,ch)
		 }

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

			 strcpy(_str + _size, str);//正好覆盖了原字符串的字符0
			 _size += len;

			 //复用insert
			 //insert(_size,str)
		 }

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

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

		 void resize(size_t n, char ch = '\0')
		 {
			 if (n <= _capacity)
			 {
				 //保留前n个数据
				 _size = n;
				 _str[_size] = '\0';
			 }
			 else
			 {
				 if (n > _capacity)
				 {
					 reserve(n);
				 }

				 size_t i = _size;//直接在后面补初始化的数据
				 while (i < n)
				 {
					 _str[i] = ch;
					 ++i;
				 }

				 _size = n;
				 _str[_size] = '\0';
			 }
		 }

		 string& insert(size_t pos, char ch)
		 {
			 assert(pos <= _size);
			 if (_size + 1 > _capacity)
			 {
				 reserve(_capacity * 2);
			 }
			 size_t end = _size + 1;

			 while (end > pos)
			 {
				 _str[end] = _str[end - 1];
				 --end;
			 }

			 _str[pos] = ch;
			 ++_size;

			 return *this;
		 }

		 string& 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-1)
			 {
				 _str[end] = _str[end - len];
				 --end;
			 }
			 strncpy(_str + pos, str, len);
			 _size += len;
			 return *this;

		 }

		 string& erase(size_t pos, size_t len = npos)//删除pos位置(包括pos位置)后的len个字符,如果不够删,就删完
		 {
			 assert(pos < _size);
			 if (len == npos || pos + len >= _size)
			 {
				 _str[pos] = '\0';//pos也被删了
				 _size = pos;
			 }
			 else
			 {
				 strcpy(_str + pos, _str + pos + len);
				 _size -= len;
			 }

			 return *this;
		 }

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

		 size_t find(char ch, size_t pos=0)//pos为0就是代表找这个字符出现的第一个位置,返回下标;为1就是返回出现第二次的位置的下标
		 {
			 assert(pos < _size);
			 for (size_t i = pos; i < _size; ++i)
			 {
				 if (_str[i] == ch)
				 {
					 return i;
				 }
			 }

			 return npos;
		 }

		 size_t find(const char* str, size_t pos = 0)
		 {
			 char* p = strstr(_str + pos, str);//找子串可以使用暴力的kmp,但是实际作用不大,还有BM算法;strstr使用的是暴力的算法
			 if (p == nullptr)
			 {
				 return npos;
			 }
			 else
			 {
				 return p - _str;
			 }
		 }
		 
		 void clear()
		 {
			 _str[0] = '\0';
			 _size = 0;
		 }
	private:
		//const char* _str
		//不使用const修饰,string类的字符串是可以修改的,而且例如扩容的时候也需要修改指针的指向
		// 然后就是_str的初始化是最好new出来的,如果直接初始化为空指针,例如在流插入的时候需要解引用,就不行了。		
		char* _str;
		size_t _capacity;
		size_t _size;

		//static const size_t npos=-1;//静态成员变量要在类外声明初始化,不能给缺省值,但是如果加上const,又支持这样给缺省值的语法了
		static const size_t npos;

	};

	const size_t string::npos = -1;

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;//有些编译器会让字符0打印成空格
		}

		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char 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;
	}

	void test_string1()
	{
		string s1;
		string s2("hello world");

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

		s2[0]++;
		cout << s2.c_str() << endl;
	}

	void test_string2()
	{
		string s1;
		string s2("hello world");
		string s3(s2);//经典的浅拷贝问题
		//对于自定义类型,去调用默认的拷贝构造,但由于空间是new出来的,所以s2和s3指向的空间是一样的
		//会造成两个问题:两次析构和修改的时候互相影响
		//如何深拷贝?先拷贝下来一块空间,再让值拷贝下来,再让被拷贝的对象指向这块空间
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		cout << s3.c_str() << endl;

		s1 = s3;//赋值和拷贝构造类似,默认生成的也是浅拷贝
		cout << s1.c_str() << endl;
		cout << s3.c_str() << endl;
	}

	void Print(const string& s)//传值传参会调拷贝构造,也要深拷贝;const对象,调用的函数也要是const
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			cout << s[i] << " ";
		}
		cout << endl;


		string::const_iterator it = s.begin();//再使用范围for就OK了
		while (it != s.end())
		{
			//cout << (*it)-- << " ";只能读,不能写
			++it;
		}
		cout << endl;

		/*for (auto ch : s)//这里就用不了了,因为范围for是替换的迭代器,这里用迭代器也不行,这里是const对象,而普通的迭代器会修改内容,权限被放大了
		{
			cout << ch << " ";
		}
		cout << endl;*/

	}

	void test_string3()
	{
		string s1("hello world");
		for (size_t i = 0; i < s1.size(); ++i)
		{
			s1[i]++;
		}
		cout << endl;
		Print(s1);

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

		it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto ch : s1)//实现了迭代器就能用范围for,因为它的底层就是宏,替换的迭代器,迭代器begin改成Begin就不能用范围for了
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void test_string4()
	{
		string s1("hello world");
		string s2("hello world");
		string s3("xx");

		cout << (s1 < s2) << endl;//比较ascii码
		cout << (s1 == s2) << endl;
		cout << (s1 >= s2) << endl;
	}

	void test_string5()
	{
		string s1("hello world");
		//s1.push_back(' ');
		//s1.append("xxxxxxxxxxxxxxxxxxxx");


		s1 += ' ';
		s1 += "xxxxxxxxxxxxxxxxx";
		cout << s1.c_str() << endl;

		s1.insert(5, 'x');
		cout << s1.c_str() << endl;
	}

	void test_string6()
	{
		string s1("hello world111111111111111111111111");
		cout << s1.capacity() << endl;
		s1.reserve(10);
		cout << s1.capacity() << endl;
	}

	void test_string7()
	{
		string s1;
		s1.resize(20, 'x');
		cout << s1.c_str() << endl;
		s1.resize(30, 'y');
		cout << s1.c_str() << endl;

		s1.resize(10);
		cout << s1.c_str() << endl;
	}

	void test_string8()
	{
		string s1("hello world");
		s1.insert(0, 'x');
		cout << s1.c_str() << endl;
		s1.insert(0, "yyy");
		cout << s1.c_str() << endl;

		s1.erase(5, 1);
		cout << s1.c_str() << endl;
		s1.erase(5, 30);
		cout << s1.c_str() << endl;

	}
	//流插入重载必须实现成友元函数?不对,没有访问类中的成员,不需要
	void test_string9()
	{
		string s1("hello world");
		s1 += '\0';
		s1 += "xxxxxxxxx";
		cout << s1 << endl;
		cout << s1.c_str() << endl;

		string s2;
		cin >> s2;
		cout << s2 << endl;
	}

}

总结:

细节很多,后面的容器实现大部分类似,但还需要复习。

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

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

相关文章

Testng测试框架(6)--@Factory动态地创建测试类的实例

工厂允许您动态地创建测试。例如&#xff0c;假设您想创建一个测试方法&#xff0c;该方法将多次访问网站上的某个页面&#xff0c;并且您希望使用不同的值来调用它。 public class TestWebServer {Test(parameters { "number-of-times" })public void accessPage(…

【C++题解】1605. 求一个两位数的个位和十位的和

问题&#xff1a;1605. 求一个两位数的个位和十位的和 类型&#xff1a;基本运算、拆位求解。 题目描述&#xff1a; 从键盘读入一个两位的整数 n &#xff0c;请求出这个两位整数个位和十位的和是多少&#xff1f; 输入&#xff1a; 一个两位的整数 n 。 输出&#xff1a…

hbase基础shell用法

HBase中用create命令创建表&#xff0c;具体如下&#xff1a; create student,Sname,Ssex,Sage,Sdept,course 此时&#xff0c;即创建了一个“student”表&#xff0c;属性有&#xff1a;Sname,Ssex,Sage,Sdept,course。因为HBase的表中会有一个系统默认的属性作为行键&#x…

【MySQL】事务篇

SueWakeup 个人主页&#xff1a;SueWakeup 系列专栏&#xff1a;学习技术栈 个性签名&#xff1a;保留赤子之心也许是种幸运吧 目录 本系列专栏 1. 什么是事务 2. 事务的特征 原子性&#xff08;Atomicity&#xff09; 一致性&#xff08;Consistency&#xff09; 隔离性&…

255Mesh 无线lora模块详细配置和测试

一、型号介绍 字符含义&#xff1a; E&#xff1a;终端 N&#xff1a;节点&#xff08;node&#xff09; G&#xff1a;网关 &#xff08;gateway&#xff09; 官网淘宝介绍 注意&#xff1a;组网必须配网关。 二、功能配置 软件界面 1.网络参数 网络参数包括网络 ID&a…

华为OD技术面试-有序数组第K最小值

背景 2024-03-15华为od 二面&#xff0c;记录结题过程 有序矩阵中第 K 小的元素 - 力扣&#xff08;LeetCode&#xff09; https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/submissions/512483717/ 题目 给你一个 n x n 矩阵 matrix &#xff0c;其…

FPGA基于VCU的H265视频压缩,HDMI2.0输入,支持4K60帧,提供工程源码+开发板+技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的视频图像编解码方案 3、详细设计方案设计框图FPGA开发板视频输入Video PHY ControllerHDMI 1.4/2.0 Receiver SubsystemVideo Processing SubsystemVideo Frame Buffer WriteZynq UltraScale VCUPetaLinux 系统制作VLC播放器工…

ubuntu安装irtualbox注意事项

下载官网7.0版本&#xff0c;ubuntu22.04,安装一直出错误&#xff0c;查到了下面兄弟的办法&#xff0c;仍然不幸&#xff0c;最后使用apt重装&#xff0c;请按第二部分流程安装&#xff0c;安装6.1,可以用 第一部分&#xff0c;反正我是没搞通&#xff0c;7.0反复的出现相同的…

在 Leetcode 上使用 Javascript 查找数组中的所有重复项(使用 JS 的 DSA)

在本篇博客文章中&#xff0c;我们将探讨如何在数组中找出所有重复的元素&#xff0c;这个问题源自LeetCode上的一个问题。 问题描述&#xff1a; 我们有一个包含n个整数的数组&#xff0c;所有整数都在范围[1, n]内。每个整数要么出现一次&#xff0c;要么出现两次。任务是找…

第8章 项目整合管理

子过程&#xff1a;为创建预定的产品、服务或成果而执行的一系列相互关联的行动和活动&#xff1b; 既属于某一过程组&#xff08;启动、规划、执行、监控、收尾&#xff09;又属于某一知识领域&#xff08;整合、范围、进度、成本、质量、资源、采购、沟通、干系人、风险&…

5G智慧水利数字孪生可视化平台,推进水利行业数字化转型

5G智慧水利数字孪生可视化平台&#xff0c;推进水利行业数字化转型。随着5G技术的快速发展&#xff0c;越来越多的行业开始探索数字化转型的道路。水利行业作为国民经济的重要支柱&#xff0c;也面临着数字化转型的迫切需求。5G智慧水利数字孪生可视化平台作为水利行业数字化转…

数据安全之路:Databend 用户与角色管理应用

Databend 目前支持基于角色的访问控制 (RBAC) 和 自主访问控制 (DAC) 模型&#xff0c;用于访问控制功能。 通过本指南&#xff0c;我们会了解权限和角色在 Databend 中的基本概念&#xff0c;以及如何管理角色、继承角色与建立层级、设置默认角色以及所有权的重要性。这些功能…

活动预告|NineData 创始人CEO叶正盛将参加QCon全球软件开发大会,共话AI大模型技术在数据库DevOps的实践

4月13日下午&#xff0c;NineData创始人&CEO叶正盛即将参加InfoQ中国主办的『QCon全球软件开发大会北京站』的技术大会。在本次技术峰会上&#xff0c;叶正盛将以《AI大模型技术在数据库DevOps的实践》为主题&#xff0c;深入剖析AI大模型技术在数据库DevOps领域的最新进展…

逆向案例二十一——遇到混淆怎么办

开始新的板块尝试&#xff0c;混淆了怎么办 网址&#xff1a;极简壁纸_海量电脑桌面壁纸美图_4K超高清_最潮壁纸网站 抓包抓到&#xff0c;好久没做解密了&#xff0c;奥里给干他&#xff01;&#xff1a; 搜索关键字&#xff0c;打上断点&#xff0c;点击第二页。 _0x10a345…

SOLIDOWRKS怎么将中间格式的模具装配体转化为装配体格式

模具是工业生产中用于制作成型物品的工具&#xff0c;它由各种零件构成&#xff0c;可以通过改变所成型材料的物理状态来实现物品外形的加工。如果工程师已经有其他格式的模具装配体&#xff0c;但是又想将其他格式的模具装配体导入solidworks里面&#xff0c;并且将一个个实体…

rust wasm入门

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d8;相关专栏Rust初阶教程、go语言基础系列、spring教程等&#xff0c;大家有兴趣的可以看一看 &#x1f4d9;Jav…

SpringBoot修改菜品模块开发

需求分析与设计 一&#xff1a;产品原型 在菜品管理列表页面点击修改按钮&#xff0c;跳转到修改菜品页面&#xff0c;在修改页面回显菜品相关信息并进行修改&#xff0c;最后点击保存按钮完成修改操作。 修改菜品原型&#xff1a; 二&#xff1a;接口设计 通过对上述原型图…

图像处理与视觉感知---期末复习重点(7)

文章目录 一、图像压缩1.1 三种冗余1.2 模型1.3 信息测量 二、无误差压缩2.1 哈夫曼编码2.1.1 步骤2.1.2 例题 2.2 算术编码 三、变换编码 一、图像压缩 1.1 三种冗余 1. 三种基本的是数据冗余为&#xff1a;编码冗余、像素间冗余、心理视觉冗余。 2. 编码冗余&#xff1a;如果…

在线拍卖系统|基于Springboot的在线拍卖系统设计与实现(源码+数据库+文档)

在线拍卖系统目录 基于Springboot的在线拍卖系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、前台&#xff1a; 2、后台 用户功能模块 5.2用户功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a…

个人博客系统项目(SpringBoot+Linux部署上线)

在学完SpringBoot框架、MyBatis后&#xff0c;直接开始做第一个项目&#xff1a;博客系统 首先&#xff0c;该博客系统包含核心功能有&#xff1a; 一、登录、注册、退出登录功能。 二、没有登陆前可以查看博客首页以及博客展示的分页处理&#xff0c;以及点击查看博客可以…