string详解(2)— 模拟实现

news2024/11/13 15:46:28

1.经典的string类实现

最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。

(1) String.h

为了与库里的string进行区分我们使用String: 

// String.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<string.h>
#include<assert.h>
using namespace std;

namespace zyt
{
	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()// 无参
		//	:_str(new char[1] {'\0'})//开一个空间存储'\0',不能直接给nullptr
		//	// 如果对无参对象应用c_str,接口会持续对空指针解引用直到遇到'\0'为止
		//	, _size(0)
		//	, _capacity(0)
		//{}

		//string(const char* str) // 有参
		//{
		//	_size = strlen(str);
		//	_capacity = _size; // 容量不包含‘\0’
		//	_str = new char[_capacity + 1]; // 空间要多开一个
		//	strcpy(_str, str);
		//}

		// 短小频繁调用的函数可以直接定义在类里,默认是inline
		// 或者直接写一个全缺省的构造
		String(const char* str = "")//C语言规定常量字符串后面自带一个‘\0’
		{//单参数构造函数支持隐式类型转换
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// 深拷贝
		//显示拷贝构造,否则其他函数(substr)会默认调用浅拷贝
		String(const String& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		//显示赋值重载,否则也会默认调用浅拷贝,导致内存泄漏
		String& operator=(const String& s)
		{
			if (this != &s)
			{// 避免自己给自己赋值时,将原有空间释放
				delete[] _str;// 把原有的空间直接释放掉

				_str = new char[s._capacity + 1];
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}

		~String()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		const char* c_str()const
		{
			return _str;
		}
		// 清空所有数据但不销毁空间
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

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

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

			return _str[pos];
		}
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		String& operator+=(char ch);
		String& operator+=(const char* str);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);

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

		String substr(size_t pos = 0, size_t len = npos);

	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static const size_t npos;
		//static const size_t npos = -1;
		// static const 整形 的类型可以直接定义,其余static类型都要声明和定义分离
	};

	bool operator<(const String& s1, const String& s2);
	bool operator<=(const String& s1, const String& s2);
	bool operator>(const String& s1, const String& s2);
	bool operator>=(const String& s1, const String& s2);
	bool operator==(const String& s1, const String& s2);
	bool operator!=(const String& s1, const String& s2);

	ostream& operator<<(ostream& out, const String& s);
	istream& operator>>(istream& in, String& s);

	void test1();
	void test2();
	void test3();
	void test4();
}

(2) String .cpp 

// String.cpp

#include"String.h"

namespace zyt
{
	const size_t String::npos = -1;//声明和定义分离

	void String::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

	void String::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';// 一定要加终止符
	}
	String& String::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}

	void String::append(const char* str)
	{
		size_t len = strlen(str);
		if (len + _size > _capacity)
		{
			//大于2倍,需要多少开多少,小于2倍按2倍扩
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}
	String& String::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	void String::insert(size_t pos, char ch)
	{
		assert(pos < _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//挪动数据

		//int end = _size;
		// 当操作符两端数据类型不同会发生类型提升(提升成无符号),所以要强转
		//while (end >= (int)pos)
		//{
		//	_str[end + 1] = _str[end];
		//	--end;
		//}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		++_size;
	}
	void String::insert(size_t pos, const char* str)
	{
		assert(pos < _size);
		size_t len = strlen(str);
		if (len + _size > _capacity)
		{
			//大于2倍,需要多少开多少,小于2倍按2倍扩
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}

		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}

		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}
		_size += len;
	}
	void String::erase(size_t pos, size_t len)// 缺省参数只能声明的时候给
	{
		assert(pos < _size);
		if (len >= _size - pos)// pos后面全删
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			for (size_t i = pos + len; i < _size; i++)
			{
				_str[i - len] = _str[i];
			}
			_size -= len;
		}
	}

	size_t String::find(char ch, size_t pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}
	size_t String::find(const char* str, size_t pos)
	{
		assert(pos < _size);

		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;// 两个指针相减就是要返回的下标
		}
	}

	String String::substr(size_t pos, size_t len)
	{
		assert(pos < _size);

		if (len > _size - pos)
		{ // 更新len
			len = _size - pos;
		}
		String sub;
		sub.reserve(len);// 预留空间

		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}
		return sub;
	}

	bool operator<(const String& s1, const String& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 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 strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	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)
	{
		s.clear();//将s原有的数据清空

		const int N = 256;
		char buff[N];//空间为256的数组用来存储字符串,避免多次扩容和大量空间浪费的问题
		int i = 0;

		char ch;
		//流提取遇到' '和'\0'无法读取
		//in >> ch;
		ch = in.get();//只获取一个字符
		while (ch != ' ' && ch != '\0')
		{
			buff[i++] = ch;//先存储在数组里
			if (i == N - 1)// i = 255
			{
				buff[i] = '\0';//存满了
				s += buff;// +=会给s扩容

				i = 0;
			}
			//in >> ch;
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		
		return in;
	}
	void test1()
	{
		String s1;
		String s2("hello world");
		cout << s2.c_str() << endl;
		for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i] += 2;
		}
		cout << s2.c_str() << endl;

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

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

	void test2()
	{
		String s1("hello world");
		s1 += 'a';
		s1 += 'b';
		cout << s1.c_str() << endl;
		s1 += "123";
		cout << s1.c_str() << endl;
		s1.insert(5, '*');
		cout << s1.c_str() << endl;
		String s2("hello world");
		s2.insert(5, "%%%");
		cout << s2.c_str() << endl;
		s2.insert(0, "%%%");
		cout << s2.c_str() << endl;
		s2.erase(5, 3);
		cout << s2.c_str() << endl;
		s2.erase(5, 100);
		cout << s2.c_str() << endl;
	}

	void test3()
	{
		String s1("hello world");
		size_t pos = s1.find(' ');
		String last = s1.substr(pos);
		cout << last.c_str() << endl;
		String s2(s1);
		cout << s2.c_str() << endl;
		s1 = s2;
		cout << s1.c_str() << endl;
		s2 = s2;
		cout << s2.c_str() << endl;
	}

	void test4()
	{
		String s1("hello world");
		String s2("hello world");
		cout << (s1 < s2) << endl;
		cout << (s1 == s2) << endl;
		//单参数构造函数支持隐式类型转换,所以没有函数实现也支持字符串和string比较
		cout << ("hello world" < s2) << endl;
		cout << (s1 == "hello world") << endl;
		//运算符重载必须有个类类型的参数,所以下面的会被识别成指针的比较
		cout << ("hello world" == "hello world") << endl;
		cout << s1 << " " << s2 << endl;
		String s3;
		cin >> s3;
		cout << s3 << endl;
	}
}

(3)Test.cpp 

// Test,cpp

#include"String.h"

int main()
{
	zyt::test4();
	return 0;
}

(4)注意

1. static const 整形】的类型可以直接定义,其余static类型都要声明和定义分离。

static const size_t npos = -1;

2. 短小频繁调用的函数(如类的构造、拷贝构造、赋值运算符重载以及析构函数等)可以直接定义在类里,默认是inline

3.全缺省构造函数用的是:String(const char* str = "") 

C语言规定常量字符串后面自带一个‘\0’

//String(const char* str = "\0") 错误示范,因为常量字符串后面自带一个'\0',所以这里重复写了。
//String(const char* str = nullptr) 错误示范,如果对无参对象应用c_str,接口会持续对空指针解引用直到遇到'\0'为止。
4. String类如果没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认
的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是, s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃, 这种拷贝方式,称为浅拷贝。

5. 显示赋值重载,否则也会默认调用浅拷贝,导致内存泄漏 

6. 封装:屏蔽了底层实现细节,提供了统一的类似访问容器的方式,不需要关心容器底部结构和实现细节

7. 当操作符两端数据类型不同会发生类型提升(提升成范围较大的),int类型和size_t类型比较会发生整形提升,两边都是正数不会有影响,但当int类型的数是负数(-1)时,就会出问题。

8. 在实现流输入时,为避免多次扩容和预留空间浪费过多,可以开一个空间为256的数组用来先存储字符串。

9. 实现流输入中,流提取遇到' '空格会被自动跳过,直到遇到下一个非空白字符,遇到'\0'结束读取,所以用到 get()函数get() 函数是 istream 类的一个成员函数,用于从输入流中读取字符,get() 函数不会自动跳过任何空白字符,包括空格和制表符

10. 单参数构造函数支持隐式类型转换,所以没有函数实现也支持字符串和string比较:

String s1("hello world");

cout << (s1 == "hello world") << endl;

11. 运算符重载必须有个类类型的参数,所以下面的会被识别成指针的比较
cout << ("hello world" == "hello world") << endl;


 2.浅拷贝

浅拷贝:也称位拷贝, 编译器只是将对象中的值拷贝过来 。如果对象中管理资源,最后就会导致 多个对象共享同一份资源 ,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。或者修改一个对象资源,会影响另外一个对象。
采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享

具体介绍可以看类和对象(中)的拷贝构造函数部分中有介绍:http://t.csdnimg.cn/2xIJH

3.深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

每个String类对象都要用空间来存放字符串,而s2要用s1拷贝构造出来,因此

深拷贝:给每个对象独立分配资源,保证多个对象之间不会因共享资源造成多次释放导致程序崩溃问题

(1)传统版String的写法

class String
{
public:
    // 全缺省的构造
    String(const char* str = "")//C语言规定常量字符串后面自带一个‘\0’
    {//单参数构造函数支持隐式类型转换
	    _size = strlen(str);
	    _capacity = _size;
	    _str = new char[_capacity + 1];
    	strcpy(_str, str);
    }
    // 深拷贝传统写法
    //显示拷贝构造,否则其他函数(substr)会默认调用浅拷贝
    String(const String& s)
    {
	    _str = new char[s._capacity + 1];
	    strcpy(_str, s._str);
	    _size = s._size;
	    _capacity = s._capacity;
    }
    //传统写法
    String& operator=(const String& s)
    {
    	if (this != &s)
	    {// 避免自己给自己赋值时,将原有空间释放
	    	delete[] _str;// 把原有的空间直接释放掉

		    _str = new char[s._capacity + 1];
		    strcpy(_str, s._str);
		    _size = s._size;
		    _capacity = s._capacity;
	    }

	    return *this;
    }
   
	~String()
	{ //delete nullptr 也不会报错,直接返回
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
    //...
private:
    char* _str;
    size_t _size;
    size_t _capacity;
};

(2)现代版String的写法

class String
{
public:
    String(const char* str = "")
    {
        if (nullptr == str)
        {
            assert(false);
            return;
        }
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    //实现交换函数(库里面)
    void swap(String& s)
    {
    	std::swap(_str, s._str);
	    std::swap(_size, s._size);
	    std::swap(_capacity, s._capacity);
    }
    // 深拷贝现代写法 s2(s1)
    String(const String& s)
    {// 不能将随机值交换给tmp,释放时就释放成野指针了,
     // 内置类型在定义时初始化成nullptr
	    String tmp(s._str);
	    swap(tmp);// this 和 tmp交换(调的是命名空间的交换函数)
    }
    
   	//现代版赋值重载=  (s1 = s3)
	//String& operator=(const String& s)
	//{
	//	if (this != &s)
	//	{
	//		String tmp(s._str);
	//		swap(tmp);//tmp指向s3空间,出作用域tmp自动销毁
	//	}
	//	return *this;
	//}
		
	//s1 = s3 现代版赋值重载=(简化版)
	String& operator=(String tmp)//这里直接触发拷贝构造tmp(局部变量)
	{
		swap(tmp);
		return *this;
	}
   	~String()
	{ //delete nullptr 也不会报错,直接返回
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
    //...
private://内置类型未初始化(初始化列表)编译器默认是随机值
	char* _str = nullptr;
	size_t _size = 0;
	size_t _capacity = 0;
}

● 实现现代版拷贝构造: 

1. 内置类型未初始化(初始化列表)编译器默认是随机值,调用变现代拷贝构造使用swap函数,不能将随机值交换给tmp,释放局部变量tmp时,就释放成野指针!

2. 内置类型在定义时初始化,指针给缺省值nullptr,其余给缺省值0。 

3. 实现s2(s1):

用s1创造局部变量tmp,再将s2与局部变量tmp交换,tmp指向nullptr,s2指向tmp原有资源,tmp出作用域销毁。

● 实现现代版赋值重载=:

类似于拷贝构造用s1创造局部变量tmp,再将s3与局部变量tmp交换,tmp指向s3原有资源,s3指向tmp原有资源,函数返回所需要的s3指向资源,tmp出作用域销毁。

(3)思考(swap)

C++98,算法库里面的swap和我们String类里实现的swap有什么区别?

// swap函数模板,具体实现调用的是实例化版本
template <class T> void swap ( T& a, T& b )
{
  T c(a); a=b; b=c;
}

算法库里面的swap调用了拷贝构造,赋值;总共实现3次深拷贝,多次开空间和拷贝数据,效率低。

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

类里实现的swap只实现了1次深拷贝,内置类型的交换不用开空间,大大提高了效率。

(4)写实拷贝

  写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数 :用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源 如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有 其他对象在使用该资源。  

使用写实拷贝优点:

Func()函数返回值可以不用调用深拷贝,而是调用浅拷贝,让ret指向str的原有空间,出了作用域str销毁,引用计数-1,这种方式提高效率。

vs一般默认是深拷贝,有的的编译器可能用的是写实拷贝。

//如果用写实拷贝,要在每个修改对象的接口中都插入该函数
void copy_on_write()
{
	if (count > 1)
	{
		//深拷贝
	}
}	

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

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

(1)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个字节。

(2)g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节(32位)/ 8字节(64位),内部只包含了一个 指针,该指针将来指向一块堆空间,内部包含了如下字段:
● 空间总大小
● 字符串有效长度
● 引用计数
● 指向堆空间的指针,用来存储字符串。
struct _Rep_base
{
    size_type     _M_length;
    size_type     _M_capacity;
    _Atomic_word     _M_refcount;
};

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

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

相关文章

在linux上架设Web服务器Apache(Ubuntu)

欢迎诸位来阅读在下的博文~ 在这里&#xff0c;在下会不定期发表一些浅薄的知识和经验&#xff0c;望诸位能与在下多多交流&#xff0c;共同努力! 江山如画&#xff0c;客心如若&#xff0c;欢迎到访&#xff0c;一展风采 文章目录 背景1. 安装 Apache2. 启动和检查 Apache 服务…

计算机的错误计算(六十三)

摘要 计算机的错误计算&#xff08;五十六&#xff09;探讨了大数的正切函数值的错误计算。本节讨论大数的余切函数的计算精度问题。 例1. 已知 计算 不妨用 3种方法计算。 (1) 在 Python 中利用 直接贴图&#xff1a; (2) 在 Java 中利用 若运行下列代码 import ja…

【Python快速入门和实践016】Python常用脚本-对视频抽取指定帧数并保存

一、功能介绍 这段代码的功能是从一个视频文件中抽取指定数量的帧&#xff0c;并将这些帧保存为图像文件。步骤如下&#xff1a; 设置路径和参数&#xff1a; video_path&#xff1a;视频文件的路径。image_folder&#xff1a;保存抽取图像的目录。num_frames_to_extract&#…

FL Studio21.2.4最新中文版免费下载汉化包破解补丁

&#x1f389; FL Studio 21中文版新功能全解析&#xff01;让你的音乐制作更加高效&#xff01; 嘿&#xff0c;各位音乐制作的小伙伴儿们&#xff0c;今天我要安利一款你们绝对会爱上的神器——FL Studio 21中文版&#xff01;这款软件不仅功能强大&#xff0c;而且操作简便…

【ARM】解析MDK生成的C Compiler list文件的具体内容

1、 文档目标 用于解析MDK生成的C Compiler list文件的具体内容。 2、 问题场景 在MDK的options窗口中的Listing栏中有生成对应的源文件的编译列表&#xff08;如图2-1&#xff09;。但是&#xff0c;对于这个生成的列表中包含什么具体的信息就不太清楚了。 图2-1 3、软硬件…

智慧安防/一网统管/视频监控EasyCVR视频汇聚平台的视频轻量化特点及应用

在数字化时代&#xff0c;视频监控已成为保障公共安全、提升管理效率的重要手段。随着技术的不断进步&#xff0c;EasyCVR视频汇聚平台应运而生&#xff0c;平台以其独特的视频轻量化特点在安防监控领域展现出强大的应用潜力。本文将详细探讨EasyCVR视频汇聚平台的视频轻量化特…

空间变换其参数化二

目录 刚性变换的问题描述 最优平移向量求解 最优旋转矩阵求解 反射矩阵消除 基于SVD刚性变换矩阵计算流程总结 刚性变换的问题描述 令P{p_1,p_2,...,p_n}和Q{q_1,q_2,...,q_n}是R^d空间内的两组对应的点。希望找到一个刚性的变换&#xff0c;在最小二乘的意义上最优地对齐…

如何使用pholcus库进行多线程网页标题抓取以提高效率?

在当今信息爆炸的时代&#xff0c;数据抓取已成为获取信息的重要手段。Go语言因其高效的并发处理能力而成为编写爬虫的首选语言之一。pholcus库&#xff0c;作为一个强大的Go语言爬虫框架&#xff0c;提供了多线程抓取的能力&#xff0c;可以显著提高数据抓取的效率。本文将介绍…

搭建内网开发环境(四)|基于nexus搭建maven私服

引言 在前面一篇教程中&#xff0c;通过 docker-compose 快速搭建好了开发环境所需的应用&#xff0c;本文介绍基于 nexus 搭建 maven 私服&#xff1b;虽然本篇教程是基于内网的&#xff0c;但是本文会分别介绍在拥有外网的情况下配置私服和内网的情况下配置私服&#xff0c;…

【论文阅读03】用于海洋物体检测的多注意力路径聚合网络

来源&#xff1a;用于海洋物体检测的多注意力路径聚合网络 |应用智能 (springer.com) 一、背景&#xff1a; 水下图像存在偏色、对比度低、能见度低等问题&#xff0c;使得海洋物体难以被探测到。这些都增加了海上目标探测的难度。 目前流行的检测器方法是基于卷积神经网络&…

怎么将pdf转为ppt文件?pdf转ppt的8个方法

在诸多职场与学术交流的场合中&#xff0c;我们时常面临将详尽的PDF文件转化为生动且易于编辑的PPT演示文稿的需求。这一转换不仅是为了满足演示时的灵活性&#xff0c;更是为了提升信息传递的效率与观众的理解度。从简单的在线工具到功能全面的专业软件&#xff0c;我们拥有多…

使用balenaEtcher制作 macOS 系统启动u盘

第一步&#xff1a;首先准备一个U盘要求8G(macOS Catalina 10.15.x及以上要求16G)或以上的空U盘&#xff0c;有资料会被格式化 第二步&#xff1a;下载并安装etcher https://www.apple114.com/threads/83/ 第三步&#xff1a;下载macOS引导镜像 (通过序列号查询电脑确认电脑…

Hive3:常用查询语句整理

一、数据准备 建库 CREATE DATABASE itheima; USE itheima;订单表 CREATE TABLE itheima.orders (orderId bigint COMMENT 订单id,orderNo string COMMENT 订单编号,shopId bigint COMMENT 门店id,userId bigint COMMENT 用户id,orderStatus tinyint COMMENT 订单状态 -3:用…

记录前后端接口使用AES+RSA混合加解密

一、前言 由于项目需求&#xff0c;需要用到前后端数据的加解密操作。在网上查找了了相关资料&#xff0c;但在实际应用中遇到了一些问题&#xff0c;不能完全满足我的要求。 以此为基础&#xff08;前后端接口AESRSA混合加解密详解&#xff08;vueSpringBoot&#xff09;附完…

讲解 狼人杀中的买单双是什么意思

买单双这个概念通常出现在有第三方的板子中 比如 咒狐板子 丘比特板子 咒狐板子 第一天狼队只要推掉预言家 第二天就可以与咒狐协商绑票 推出其他好人 以及丘比特板子 如果拉出一个人狼链 那么如果孤独再连一个狼人 那么 狼队第一天就可以直接派人上去拿警徽&#xff0c;这样…

NDP(Neighbor Discovery Protocol)简介

定义 邻居发现协议NDP&#xff08;Neighbor Discovery Protocol&#xff09;是IPv6协议体系中一个重要的基础协议。邻居发现协议替代了IPv4的ARP&#xff08;Address Resolution Protocol&#xff09;和ICMP路由设备发现&#xff08;Router Discovery&#xff09;&#xff0c;…

萌啦数据插件使用情况分析,萌啦数据插件下载

在当今数字化时代&#xff0c;数据已成为企业决策与个人分析不可或缺的重要资源。随着数据分析工具的日益丰富&#xff0c;一款高效、易用的数据插件成为了众多用户的心头好。其中&#xff0c;“萌啦数据插件”凭借其独特的优势&#xff0c;在众多竞品中脱颖而出&#xff0c;成…

[数据集][图像分类]肾脏病变分类数据集识别囊肿肿瘤结石数据集11756张4类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;11756 分类类别数&#xff1a;4 类别名称:["cyst","normal&…

上海晋名室外危化品暂存柜助力燃料电池行业

近日又有一个SAVEST室外危化品暂存柜项目成功验收交付使用。 用户是一家致力于为燃料电池行业提供研发、创新解决方案和技术支持的科技型中小企业。 用户在日常经营活动中涉及到氢气实验过程中的安全问题&#xff0c; 4月初在网上看到上海晋名室外暂存柜系列很感兴趣&#xf…

[EC Final 2020] ICPC2020 东亚赛区决赛重现赛题解

比赛链接:EC Final 2020 和 cyx20110930 组的队&#xff0c;用他的号交的题。顺便帮助他橙名&#xff0c;好耶&#xff01;&#xff08;rk 25&#xff0c;我俩各写 2 道&#xff09; Problem B: 这道是 cyx20110930 写的&#xff0c;顺便安利(copy)一下他的题解。 题目意思…