【模拟string函数的实现】

news2024/11/18 9:32:54

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

模拟string函数的实现

浅拷贝

深拷贝

vs和g++下string结构的说明

总结


前言

模拟string函数的实现

浅拷贝

深拷贝

总结


前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

模拟string函数的实现

string.h
#pragma once
#include <assert.h>
//string其实就是一个字符顺序表,唯一的区别就是在有效字符后面加了一个\0
namespace bit
{
	class string
	{
	public:

		typedef char* iterator;//把类型重命名成iterator,然后让类域隔开
		typedef const char* const_iterator;//const_iterator其实就是const char*

		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const//const修饰的是this
		{
			return _str + _size;
		}
		//函数的类型是无法支持函数的重载
		iterator begin()
		{
			return _str;//返回的是字符串第一个字符的下标
		}

		iterator end()
		{
			return _str + _size;//返回的是'\0'的下标
		}


		//无参的构造函数
		//string()
		//	:_str(nullptr)//不能给str空指针,怕返回空指针
		//	//如果给str赋值 nullptr,采用c语言的接口,返回的字符串是空指针,打印空指针会报错的
		//	,_size(0)
		//	,_capacity(0)
		//{}
		
		//c++兼容c,我们要用c语言的接口:返回字符串
		const char* c_str() const
		{
			return _str;
		}
		
		所以,我们要给str开一个空间,并赋值'\0',返回字符的地址,这是可以的
		//string()
		//	:_str(new char[1])
		//	, _size(0)
		//	, _capacity(0)
		//{
		//	_str[0] = '\0';
		//}

		//带参的构造函数
		/*string(const char* str)//strlen:遍历字符串,遇到 \0 就停止,不易多调用
			:_size(strlen(str))
			,_str(new char[strlen(str)+1])
			,_capacity(strlen(str))//capacity:不包含 \0 
		{
			strcpy(_str, str);
		}*/

		//以上的无参和带参的构造函数合二为一:全缺省的构造函数(即可传参,也可不传传参)
		//第一种情况:
		//string(const char* str = nullptr)
		//如果传的是无参,strlen(str):遍历字符串时,会对指针指向的内容解引用,str指向的是空指针
		//第二种情况:
		//string(const char* str = '\0')
        //也不能给str赋值 '\0',左右两边的类型要匹配;右边类型:char  左边类型:const char*
		//第三种情况:
		//string(const char* str = "\0")//字符串是"\0",但是结束时,还会再加一个 \0
		//第四种情况:
		string(const char* str = "")//缺省值给一个空字符串
			:_size(strlen(str))
		{
			_capacity = _size;//capacity:存有效的字符空间,有效的字符是0个,但是还开了一个空间,用来存\0 
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		size_t capacity() const
		{
			return _capacity;
		}

		//遍历
		size_t size() const
		{
			return _size;//返回有效字符串的个数
		}

		//函数的声明和定义在一块,本质就相当于是内联
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];//这些数据在堆区上,出来作用域也不会销毁,可以返回别名
		}
		//函数重载,上面的和下面的各用各的
		const char& operator[](size_t pos) const
		{
			//只能获取pos位置的字符,但是不能修改
			assert(pos < _size);
			return _str[pos];//这些数据在堆区上,出来作用域也不会销毁,可以返回别名
		}

		//s2(s1):深拷贝(拷贝构造函数)
		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		//s2(s1)
		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);
		}


		//赋值运算符重载(s1 = s3):也会出现浅拷贝的问题
		string& operator=(const string& s)
		{
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);
			delete[] _str;//把s1原来空间释放掉
			_str = tmp;
			_size = s._size;
			_capacity = s._capacity;

			return *this;
		}

		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		void resize(size_t n, char ch = '\0')//半缺省函数,有实参会替换缺省值
		{
			//保留前n个数据
			if (n <= _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				reserve(n);//n > capacity,就扩容
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_str[n] = '\0';
				_size = n;
			}
		}


		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				//手动扩容
				char* tmp = new char[n + 1];//开空间永远要多开一个,多开的一个是给'\0'准备的
				strcpy(tmp, _str);//拷贝数据
				delete[] _str;//释放旧空间
				_str = tmp;//指针指向新空间

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			// 扩容2倍
			/*if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';*/

			insert(_size, ch);//复用insert()函数
		}

		void append(const char* str)
		{
			// 扩容
			//size_t len = strlen(str);
			//if (_size + len > _capacity)
			//{
			//	//_size:当前字符串的长度;len:插入的字符串的长度;'\0'会单独开空间存放
			//	reserve(_size + len);
			//}

			insert(_size, str);//复用insert()函数
		}

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

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

		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			// 扩容2倍
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			}

			//挪动数据方法一:
			/*int end = _size;
			//while (end >= pos)//end:有符号;pos:无符号;有符号会向无符号提升
			while (end >= (int)pos)//这里的循环是把包括pos位置和end位置之间的数据往后挪动
			{
			    //如果一个运算符两边的操作数的类型不同的时候,会发生类型提升(范围小的向范围大的提升)
				_str[end + 1] = _str[end];
				--end;
			}*/
			/*_str[pos] = ch;
			++_size;*/

			//挪动数据方法二:
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			++_size;
		}

		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);//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;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);//不用删除\0

			//if (len == npos || len + pos >= _size)
			//假如len == npos-1,npos是-1,是无符号整型的最大值,再加pos,可能会存在溢出的风险
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

		void swap(string& s)
		{
			// 我们已经用 using namespace std;将std命名空间域给展开了,为什么还要加 std:: 呢?
			// 将 std 命名空间域展开(相当于:小王家开了一个通告,说你们可以拿我家的菜),
			// 但是顺序还是不变的:局部域----->全局域----->命名空间域
			// 加 std::是为了防止 swap()函数在局部域找swap()函数的定义出处时,发生事故;
			// 不加 std:: 的话,swap()函数会先找到 swap(string& s)的,但是参数的类型不一样,会发生报错 
			std::swap(_str, s._str);//调用库里的模板,这里是交换了堆区空间的地址
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//找字符
		size_t find(char ch, size_t pos = 0) const
		{
			assert(pos < _size);

			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
					return i;
			}

			return npos;
		}
		//找字符串
		size_t find(const char* sub, size_t pos = 0) const
		{
			assert(pos < _size);

			const char* p = strstr(_str + pos, sub);
			if (p)
			{
				return p - _str;//指针 - 指针 == 两个指针之间的元素个数(返回的是下标)
			}
			else
			{
				return npos;
			}
		}

		string substr(size_t pos = 0, size_t len = npos)
		{
			string sub;
			//if (len == npos || len >= _size-pos)
			if (len >= _size - pos)
			{
				for (size_t i = pos; i < _size; i++)
				{
					sub += _str[i];
				}
			}
			else
			{
				for (size_t i = pos; i < pos + len; i++)
				{
					sub += _str[i];
				}
			}

			return sub;
		}
		//只清理空间中的数据,并不会缩容
		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}


	private:
		//初始化列表初始化的顺序和声明的顺序一样
		char* _str;
		size_t _size;
		size_t _capacity;

	public:
		static const int npos;//npos:是一个公有的静态成员变量
	};

	const int string::npos = -1;

	//string中的非成员函数
	void swap(string& x, string& y)
	{
		x.swap(y);
	}
	//全局函数
	bool operator==(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret == 0;
	}

	bool operator<(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret < 0;
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}

	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}
	//流插入(必须是全局函数,没有访问类中的私有成员变量,所以不需要设置成友元函数)
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}

		return out;
	}
	//流提取(是一个覆盖)
	istream& operator>>(istream& in, string& s)//提取的字符放入string类型的对象中,所以不需要要用const来修饰
	{
		//s是s1和s2的别名,s对象中有s1或s2的字符串内容,流提取是一个覆盖;
		//此时流提取只会在字符串的尾部插入数据,所以我们要先把s对象中的数据清理掉
		s.clear();

		char ch;
		//in >> s[i];//不能这么写,对象s中还没有写入字符,还没有数据
		//in >> ch;
		//c++的cin和c语言的scanf是读元素的时候,是读不到空格和换行的
		//(他们认为空格或换行是多个元素之间的分割符,会自动把空格或换行符给忽略掉)
		//c语言应该用 getchar/getc;但是c++是不能用的
		//(因为c语言和c++的iostream流不是同一个,他们都有各自的缓存区)
		ch = in.get();//所以用它来取字符
		char buff[128];//1、在栈上开空间比在堆上开空间要快一些;2、出了函数作用域空间就销毁了,不会一直浪费空间
		size_t i = 0;
		//流插入和流提取遇到 空格或换行 就默认结束
		while (ch != ' ' && ch != '\n')
		{
			//buff:字符数组,一段一段往对象s中加
			buff[i++] = ch;
			// [0,126]
			if (i == 127)
			{
				buff[127] = '\0';
				s += buff;//把前127个字符加入对象s中
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;//把有效的数据个数加入对象s中
		}

		return in;
	}

	//流提取(是一个覆盖)
	//istream& operator>>(istream& in, string& s)//提取的字符放入string类型的对象中,所以不需要要用const来修饰
	//{
	//	//s是s1和s2的别名,s对象中有s1或s2的字符串内容,流提取是一个覆盖;
	//	//此时流提取只会在字符串的尾部插入数据,所以我们要先把s对象中的数据清理掉
	//	s.clear();
	//	char ch;
	//	//in >> s[i];//不能这么写,对象s中还没有写入字符,还没有数据
	//	//in >> ch;
	//	//c++的cin和c语言的scanf是读元素的时候,是读不到空格和换行的
	//	//(他们认为空格或换行是多个元素之间的分割符,会自动把空格或换行符给忽略掉)
	//	//c语言应该用 getchar/getc;但是c++是不能用的
	//	//(因为c语言和c++的iostream流不是同一个,他们都有各自的缓存区)
	//	ch = in.get();//所以用它来取字符
	//	s.reserve(128);
	//	//流插入和流提取遇到 空格或换行 就默认结束
	//	while (ch != '\n' && ch != ' ')
	//	{
	//		s += ch;
	//		ch = in.get();
	//	}

	//	return in;
	//}
	//获取一行
	istream& getline(istream& in, string& s)
	{
		s.clear();

		char ch;
		//in >> ch;
		ch = in.get();
		char buff[128];
		size_t i = 0;
		while (ch != '\n')
		{
			buff[i++] = ch;
			// [0,126]
			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("hello world");
		string s2;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		for (size_t i = 0; i < s1.size(); i++)
		{
			s1[i]++;
		}
		cout << endl;
		//遍历:下标+[]
		for (size_t i = 0; i < s1.size(); i++)
		{
			//s1:既能调用const的函数,也可以调用非const的函数
			//s1.operator[](i)
			cout << s1[i] << "";
		}
		cout << endl;

		const string s3("xxxx");//只能调用const char& operator[](size_t pos) const这个函数
		for (size_t i = 0; i < s3.size(); i++)
		{
			//s3[i]++;
			cout << s3[i] << " ";
		}
		cout << endl;


		数组的越界是很不好检查的:
		//int a[10];
		数组的读是检查不出来的
		//a[10];
		//a[11];
		数组的写不一定能检查出来。因为数组的越界检查是一种抽查
		//a[10] = 1;
	}

	void test_string2()
	{
		string s3("hello world");
		//范围for是一个替换机制(会自动替换成迭代器,这个地方是写死的,迭代器中必须要有iterator、begin、end):
		//自动取对象s3里面的数据赋值给ch,自动迭代,自动加加
		for (auto ch : s3)//s3是普通对象,范围for替换成普通迭代器
		{
			cout << ch << " ";
		}
		cout << endl;

		//迭代器(像指针,但不一定是指针)
		string::iterator it3 = s3.begin();
		while (it3 != s3.end())
		{
			*it3 -= 1;
			cout << *it3 << " ";
			++it3;
		}
		cout << endl;

		const string s4("xxxx");
		string::const_iterator it4 = s4.begin();
		while (it4 != s4.end())
		{
			//*it4 += 3;
			cout << *it4 << " ";
			++it4;
		}
		cout << endl;
		//s4是const对象,范围for替换成const迭代器(class类中必须声明iterator、begin、end)
		for (auto ch : s4)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void test_string3()
	{
		string s3("hello world");
		s3.push_back('1');
		s3.push_back('2');
		cout << s3.c_str() << endl;
		s3 += 'x';
		s3 += "yyyyyy";
		cout << s3.c_str() << endl;

		string s1("hello world");
		s1.insert(11, 'x');
		cout << s1.c_str() << endl;

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

	void test_string4()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.erase(6, 3);
		cout << s1.c_str() << endl;

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

		s1.erase(3);
		cout << s1.c_str() << endl;

		string s2("hello world");
		cout << s2.c_str() << endl;

		s2.resize(5);
		cout << s2.c_str() << endl;

		s2.resize(20, 'x');
		cout << s2.c_str() << endl;
	}

	void test_string5()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
		//此时,这里是 浅拷贝/值拷贝;s1和s2中的_str所指向的空间是同一块,析构函数释放数据会释放两次,
		//并且改动数据,对两个都有影响
		string s2(s1);
		cout << s2.c_str() << endl;

		s1[0] = 'x';

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

		string s3("xxxxx");
		s1 = s3;

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

	void test_string6()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.insert(6, "xxx");
		cout << s1.c_str() << endl;

		string s2("xxxxxxx");

		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		swap(s1, s2);//调用库里面的swap模板,代价:三次拷贝构造+一次析构(涉及到深拷贝,释放和申请空间的次数太多)
		s1.swap(s2);//高效的方法:交换两个堆区空间的地址,不需要多次释放和申请空间
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
	}

	void test_string7()
	{
		string url1("https://legacy.cplusplus.com/reference/string/string/substr/");
		string url2("http://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=%E5%90%8E%E7%BC%80%20%E8%8B%B1%E6%96%87&fenlei=256&rsv_pq=0xc17a6c03003ede72&rsv_t=7f6eqaxivkivsW9Zwc41K2mIRleeNXjmiMjOgoAC0UgwLzPyVm%2FtSOeppDv%2F&rqlang=en&rsv_dl=ib&rsv_enter=1&rsv_sug3=4&rsv_sug1=3&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=1588&rsv_sug4=6786");

		string protocol, domain, uri;
		size_t i1 = url1.find(':');
		if (i1 != string::npos)
		{
			protocol = url1.substr(0, i1 - 0);
			cout << protocol.c_str() << endl;
		}

		// strchar
		size_t i2 = url1.find('/', i1 + 3);
		if (i2 != string::npos)
		{
			domain = url1.substr(i1 + 3, i2 - (i1 + 3));
			cout << domain.c_str() << endl;

			uri = url1.substr(i2 + 1);
			cout << uri.c_str() << endl;
		}

		// strstr  
		size_t i3 = url1.find("baidu");
		cout << i3 << endl;
	}


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

		cout << (s1 == s2) << endl;

		cout << ("hello world" == s2) << endl;//左边是调用构造成员函数,类型是 const char*
		//左边不能是一个成员函数,他必须是一个对象,对象才能调用成员函数
		//(解释赋值运算符重载为什么是全局函数,如果是成员函数的话,第一个参数是 this,是对象的地址)
		cout << (s1 == "hello world") << endl;
		//单参数的构造函数可以支持隐式类型转换(const char*转换成string类型)

		cout << s1 << endl;
		cout << s2 << endl;
		//c++中的cout和cin的缓存区也不是同一个,所以cout出去的,不会影响cin进来的
		cin >> s1 >> s2;

		cout << s1 << endl;
		cout << s2 << endl;

		getline(cin, s1);
		cout << s1 << endl;
	}

	void test_string9()
	{
		string s1;

		cin >> s1;
		cout << s1.capacity() << endl;
	}

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

		cout << s1 << endl;
		cout << s2 << endl;
	}
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<string>
#include"string.h"


int main()
{

	bit::test_string1();
	return 0;
}

//内置类型为什么支持流插入和流提取呢?
//因为库里面直接就把内置类型重载了,直接掉库里面的函数;又可以自动识别类型,是因为这些函数有互相构成了函数重载

tmp要初始化为nullptr,否则当swap交换之后,tmp指向空,出了函数的作用域之后,会调用析构函数,析构函数会对tmp局部变量销毁,如果tmp是随机值会报错,而是nullptr的话,就不会有问题。传统写法和现代写法效率基本都一样。

拷贝构造是用一个存在的对象去构造另外一个要初始化的对象,那另外一个对象是没有空间的。

赋值是两个对象都已经存在了。

栈上开空间要比堆区上开空间要快,而且成本更低。

浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

说明:

上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构 造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块 空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

深拷贝

vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

  • vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字 符串的存储空间:

  • 当字符串长度小于16时,使用内部固定的字符数组来存放
  • 当字符串长度大于等于16时,从堆上开辟空间
union _Bxty
 {   // storage for small buffer or pointer to larger one
 value_type _Buf[_BUF_SIZE];
 pointer _Ptr;
 char _Alias[_BUF_SIZE]; // to permit aliasing
 } _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内 部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量

最后:还有一个指针做一些其他事情。

故总共占16+4+4+4=28个字节。

  • g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指 针将来指向一块堆空间,内部包含了如下字段:

  • 空间总大小
  • 字符串有效长度
  • 引用计数
struct _Rep_base
 {
    size_type               _M_length;
    size_type               _M_capacity;
    _Atomic_word            _M_refcount;
 };
  • 指向堆空间的指针,用来存储字符串。


总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

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

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

相关文章

某赛通电子文档安全管理系统 DecryptApplication 任意文件读取漏洞复现

0x01 产品简介 某赛通电子文档安全管理系统(简称:CDG)是一款电子文档安全加密软件,该系统利用驱动层透明加密技术,通过对电子文档的加密保护,防止内部员工泄密和外部人员非法窃取企业核心重要数据资产,对电子文档进行全生命周期防护,系统具有透明加密、主动加密、智能…

HarmonyOS NEXT应用开发—组件堆叠

介绍 本示例介绍运用Stack组件以构建多层次堆叠的视觉效果。通过绑定Scroll组件的onScroll滚动事件回调函数&#xff0c;精准捕获滚动动作的发生。当滚动时&#xff0c;实时地调节组件的透明度、高度等属性&#xff0c;从而成功实现了嵌套滚动效果、透明度动态变化以及平滑的组…

perfetto详解

1. perfettor基础 1.1 介绍 可以将Perfetto理解为systrace的升级版&#xff0c;用在更新的平台、新图表展示更多的信息。它可帮助开发者收集 Android 关键子系统&#xff08;如SurfaceFlinger/SystemServer/Input/Display 等 Framework 部分关键模块、服务&#xff0c;View系…

传输层/UDP/TCP协议

再谈端口号 TCP/IP协议用“源IP”&#xff0c;“源端口号”&#xff0c;“目的IP”&#xff0c;“目的端口号”&#xff0c;“协议号”&#xff0c;这样一个五元组来标识一个通信&#xff08;可以用netstat -n来查看&#xff09;。 端口号的划分和知名端口号 我们之前就说过&am…

Java学习笔记------常用API(四)

BigDecima 用于小数的精准计算 用来表示很大的小数 构造方法获取BigDecimal对象 public BigDecimal(double val)//有可能不精确&#xff0c;不建议使用 public BigDecimal(String val) 静态方法获取BigDecimal对象 public static BigDecimal valueOf(double val)//超出do…

居民健康监测小程序|基于微信小程序的居民健康监测小程序设计与实现(源码+数据库+文档)

居民健康监测小程序目录 目录 基于微信小程序的居民健康监测小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1、用户信息管理 2、健康科普管理 5.3公告类型管理 3、论坛信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推…

ros、c++基于类的编程基础

基于class的编程结构&#xff0c;中间穿插ros的话题发布机制。 首先建立功能包&#xff1a; catkin_create_pkg control geometry_msgs message_generation message_runtime nav_msgs roscpp rospy std_msgs以上依赖基本上是大多数的ros消息所需要的依赖了。 然后确定我们的…

如何通过堡垒机JumpServer使用VisualCode 连接服务器进行开发

前言&#xff1a;应用场景 我们经常会碰到需要远程登录到内网服务器进行开发的场景&#xff0c;一般的做法都是通过VPN登录回局域网&#xff0c;然后配置ftp或者ssh使用开发工具链接到服务器上进行开发。如果没有出现问题&#xff0c;那么一切都正常&#xff0c;但到了出现问题…

解决游戏程序一运行就退出的问题

正文&#xff1a; 在游戏开发过程中&#xff0c;我们可能会遇到程序一运行就立即退出的情况。这种情况通常是由于程序中的某些逻辑错误或初始化问题导致的。 下面我们将分析可能的原因&#xff0c;并提供一些解决方案。 目录 正文&#xff1a; 原因分析&#xff1a; 解决方案…

第二百零六回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"给geolocator插件提交问题的结果"相关的内容&#xff0c;本章回中将介绍自定义标题栏.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我…

多种智能搜索算法可视化还原 3D 魔方

一、写在前面 许久没有写图形化界面的程序了&#xff0c;最近学习了一些经典的盲目搜索算法与智能搜索算法&#xff0c;正好拿来还原三阶魔方&#xff01;试试手&#xff01; 提前声明 我不是专业搞人工智能的&#xff0c;理论或者实现过程有些许错误也很正常&#xff0c;评论…

spring源码分析-事务的底层源码-1

这里写自定义目录标题 spring事务的源码分析阅读spring事务源码的前置知识JDBC的事务spring当中和事务相关的对象spring应用程序编码spring事务的源码如何开始研究spring源码当中如何代理bean spring事务的源码分析 最近在研究seata&#xff1b;看了一下spring当中的事务有一点…

CASA模型在陆地生态系统碳循环研究中的应用探讨

植被&#xff0c;作为陆地生态系统的重要基石&#xff0c;对维护生态环境功能具有不可替代的作用。其中&#xff0c;植被净初级生产力&#xff08;NPP&#xff09;是衡量植被生态系统健康与功能的关键指标。它反映了单位面积上绿色植被通过光合作用生产的有机质总量在扣除自养呼…

ChatGPT编程—实现小工具软件(批量替换文本、批量处理图像文件)

ChatGPT编程—实现小工具软件(批量替换文本、批量处理图像文件) 今天借助[小蜜蜂AI][https://zglg.work]网站的ChatGPT编程实现一个功能&#xff1a;批量处理文件及其内容&#xff0c;例如批量替换文本、批量处理图像文件等。 环境&#xff1a;Pycharm 2021 系统&#xff1a…

西门子TIA中配置Anybus PROFINET IO Slave 模块

1、所需产品 Siemens S7 PLC CPU 315-2 PN/DP 6ES7 315-2EH-0AB0 Siemens PLC 编程电缆 n.a. n.a. PC ,并安装Siemens PLC编程软件 TIA Portal V11 X-gateway Slave 接口的GSDML文件 根据网关的软件版本而定 Anybus Communicator GSD文件 GSDML-V1.0-HMS-ABCPRT-20050317.xl…

腾讯云2核4g服务器能支持多少人访问?没搞错吧

腾讯云轻量2核4G5M带宽服务器支持多少人在线访问&#xff1f;5M带宽下载速度峰值可达640KB/秒&#xff0c;阿腾云以搭建网站为例&#xff0c;假设优化后平均大小为60KB&#xff0c;则5M带宽可支撑10个用户同时在1秒内打开网站&#xff0c;并发数为10&#xff0c;经阿腾云测试&a…

kkview远程控制: 内网远程桌面控制软件

内网远程桌面控制软件&#xff1a;高效、安全的远程管理方案 在信息技术日新月异的今天&#xff0c;内网远程桌面控制软件已成为许多企业和个人用户不可或缺的工具。这类软件允许用户通过内部网络&#xff0c;实现对其他计算机的远程访问和控制&#xff0c;从而大大提高工作效…

2024年高处安装、维护、拆除证模拟考试题库及高处安装、维护、拆除理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年高处安装、维护、拆除证模拟考试题库及高处安装、维护、拆除理论考试试题是由安全生产模拟考试一点通提供&#xff0c;高处安装、维护、拆除证模拟考试题库是根据高处安装、维护、拆除最新版教材&#xff0c;高…

Radware DDoS防护迎来重大升级,重拳出击在线游戏行业难题

日前&#xff0c;全球领先的网络安全和应用交付解决方案提供商Radware推出了多维DDoS检测和防护措施&#xff0c;以满足在线游戏行业独特复杂的需求。Radware开发了一系列新的算法来保护在线游戏免遭复杂攻击。 Radware首席运营官Gabi Malka表示&#xff1a;“在线游戏是价值数…

车联网应用:开箱即用的物联网卡管理平台

IoTLink是物联网卡管理平台&#xff0c;采用SpringBoot、Vue、Mybatis等技术&#xff0c;支持多渠道管理&#xff0c;提供物联卡数据采集和云端SaaS服务。功能包括物联卡、模组管理、业务告警、微信端操作等。工厂项目可利用该平台管理物联卡、监控数据&#xff0c;并通过定制功…