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

news2024/11/27 10:20:03

目录

前言:

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

}

总结:

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

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

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

相关文章

【Linux】shell脚本实战-if单双分支条件语句详解

if单分支 在所有的编程语言里面&#xff0c;if条件语句几乎是最简单的语句格式&#xff0c;且用途最广。 当if后面的<条件表达式>成立&#xff08;真&#xff09;的时候&#xff0c;就会执行then后面的指令或语句&#xff0c;否则&#xff0c;就会忽略then后面的指令或…

鸿蒙开发学习笔记第一篇--TypeScript基础语法

目录 前言 一、ArkTS 二、基础语法 1.基础类型 1.布尔值 2.数字 3.字符串 4.数组 5.元组 6.枚举 7.unkown 8.void 9.null和undefined 10.联合类型 2.条件语句 1.if语句 1.最简单的if语句 2.if...else语句 3.if...else if....else 语句 2.switch语句 5.函数…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十 简单视频浮雕画效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十 简单视频浮雕画效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十 简单视频浮雕画效果 一、简单介绍 二、简单视频浮雕画效果实现原理 三、简单视频浮雕画效果…

基于微信小程序的短文写作竞赛管理系统

采用技术 基于微信小程序的短文写作竞赛管理系统的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringMVCMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 小程序端 登录 首页 竞赛信息界面 竞赛成果界面 学生…

2024个人动态线条导航HTML源码

源码介绍 2024个人导航HTML源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 源码下载 2024个人导航HTML源码

Django Rest Framework的序列化和反序列化

Django Rest Framework的序列化和反序列化 目录 Django Rest Framework的序列化和反序列化Django传统序列化Django传统反序列化安装DRF序列化器serializers序列化反序列化反序列化保存instance和data CBV和APIView执行流程源码解析CBV源码分析APIView源码分析 DRF的Request解析…

KVM部署

1、检查虚拟化支持 首先&#xff0c;确认你的系统处理器支持硬件虚拟化&#xff0c;在Linux终端中&#xff0c;使用以下命令&#xff1a; egrep -c (vmx|svm) /proc/cpuinfo2、安装KVM及其工具 yum update yum install qemu-kvm libvirt libvirt-python libguestfs-tools vi…

FPGA - 以太网UDP通信(二)

一&#xff0c;引言 前文链接&#xff1a;FPGA - 以太网UDP通信&#xff08;一&#xff09; 在上文章中介绍了以太网简介&#xff0c;以太网UDP通信硬件结构&#xff0c;以及PHY芯片RGMII接口-GMII接口转换逻辑&#xff0c;接下来介绍UDP通信结构框图以及数据链路层&#xff…

政安晨:【Keras机器学习实践要点】(二十六)—— 内卷神经网络

目录 简介 设置 卷积 演变 测试逆卷积层 图像分类 获取 CIFAR10 数据集 数据可视化 卷积神经网络 逆向传播神经网络 比较 损失图和准确率图 可视化卷积核 结论 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Ke…

从 SQLite 3.5.9 迁移到 3.6.0(二十一)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;从 SQLite 3.4.2 迁移到 3.5.0&#xff08;二十&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 ​SQLite 版本 3.6.0 &#xff08;2008-07-16&#xff09; 包含许多更改。按照惯例 SQLite项目&#xff…

FPGA在医疗的应用,以4K医疗内窥镜为例

前言 随着技术的发展&#xff0c;医学影像作为科学技术的主要成就之一&#xff0c;在无创诊断和治疗领域已经有了多种应用。其中一个应用是内窥镜&#xff0c;在20世纪90年代&#xff0c;当利用电荷耦合装置将图像传输到显示器上成为可能时&#xff0c;内窥镜变得更加广泛。为…

自动化测试框架 Selenium(3)

目录 1.前言 2.等待方式 2.1死等 2.2智能等待 3.游览器操作 3.1游览器最大化 3.2设置游览器的宽 高 3.3 游览器的前进和后退 3.4游览器滚动条 1.前言 本篇博客,我们将继续Selenium自动化测试的学习.在前面的章节中,俺介绍了Selenium是怎么回事,和键盘鼠标操作.还有url和…

数据应用OneID:ID-Mapping Spark GraphX实现

前言 说明 以用户实体为例&#xff0c;ID 类型包含 user_id 和 device_id。当然还有其他类型id。不同id可以获取到的阶段、生命周期均不相同。 device_id 生命周期通常指的是一个设备从首次被识别到不再活跃的整个时间段。 user_id是用户登录之后系统分配的唯一标识&#xff…

(2022级)成都工业学院数据库原理及应用实验三:数据定义语言DDL

唉&#xff0c;用爱发电连赞都没几个&#xff0c;博主感觉没有动力了 想要完整版的sql文件的同学们&#xff0c;点赞评论截图&#xff0c;发送到2923612607qq,com&#xff0c;我就会把sql文件以及如何导入sql文件到navicat的使用教程发给你的 基本上是无脑教程了&#xff0c;…

Vue ElementUI el-input-number 改变控制按钮 icon 箭头为三角形

el-input-number 属性 controls-position 值为 right 时&#xff1b; <el-input-number v-model"num" controls-position"right" :min"1" :max"10"></el-input-number>原生效果 修改后效果 CSS 修改 .el-input-number…

医院订餐平台:为患者提供贴心服务的创新解决方案

在现代医疗服务中&#xff0c;患者的就餐问题一直是一个备受关注的议题。传统的医院饮食服务往往面临着餐品单一、服务不及时等问题&#xff0c;无法满足患者的个性化需求。为了提高患者的就餐体验&#xff0c;医院订餐平台应运而生&#xff0c;通过数字化、个性化的服务&#…

Mac M2安装 Windows

由于需要在 Windows 上使用一些软件&#xff0c;今天在 Mac M2 上安装了 Windows 11。以前在 X86 Mac 上安装很容易&#xff0c;都是 X86 架构随便找个镜像安装上就可以用了。到了 M1/M2 Arm 架构就会麻烦一些&#xff0c;先在网上找到 Windows 10 Arm 架构的安装镜像&#xff…

LVGL9.1移植STM32F103C8T6花屏问题解决

这一次的话算是花了一下午差不多解决了一个问题&#xff0c;具体我是用 stm32f103c8t6(20k RAM, 128k Flash) 移植的LVGL库(屏幕是240x240的st7789, 因为RAM的buf不太够所以缩小了显示面积) 直接切入主题: 如果出现花屏问题&#xff0c; 这个问题出在你自定义编写的lv_set_flu…

搜维尔科技:【煤矿安全仿真】煤矿事故预防处置VR系统,矿山顶板灾害,冲击地压灾害等预防演练!

产品概述 煤矿事故预防处置VR系统 系统内容&#xff1a; 事故预防处置VR系统的内容包括&#xff1a;火灾的预防措施、火灾预兆、防灭火系统、火灾案例重现、顶板事故预兆、顶板事故原因、顶板事故案例重现、瓦斯概念及性质、瓦斯的涌出形式、瓦斯预兆、瓦斯爆炸条件及预防措…

文献速递:深度学习肝脏肿瘤诊断---动态对比增强 MRI 上的自动肝脏肿瘤分割使用 4D 信息:基于 3D 卷积和卷积 LSTM 的深度学习模型

Title 题目 Automatic Liver Tumor Segmentation on Dynamic Contrast Enhanced MRI Using 4D Information: Deep Learning Model Based on 3D Convolution and Convolutional LSTM 动态对比增强 MRI 上的自动肝脏肿瘤分割使用 4D 信息&#xff1a;基于 3D 卷积和卷积 LSTM …