【C++】:STL中的string类的增删查改的底层模拟实现

news2025/2/27 1:19:56

在这里插入图片描述
本篇博客仅仅实现存储字符(串)的string
同时由于C++string库设计的不合理,我仅实现一些最常见的增删查改接口
接下来给出的接口都是基于以下框架:

private:
		char* _str;//思考如何不用const
		size_t _size;
		size_t _capacity;
		//这样写可以
		const static size_t string::npos = -1;
		//下面这种写法也可以->和上述写法产生矛盾
		const static size_t string::npos;
	};
	//静态变量只能在类外面定义 上述两种定义容易产生矛盾

📚1.string默认构造 | 析构函数 | 拷贝构造 | 赋值重载

//无参默认构造
string()
	:_str(new char[1])
	, _size(0)
	, _capacity(0)
{
	_str[0] = '\0';
}
//有参默认构造
string(const char* str="\0")//不能写'\0'
	//->类型不匹配
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

C++string标准库中,无参构造并不是空间为0,直接置为空指针 而是开一个字节,并存放‘\0’
C++中支持无参构造一个对象后,直接在后面插入数据,也从侧面说明了这点
由于C++构造函数不管写不写都会走初始化列表的特性,所以这里也走初始化列表
string中,_capacity和_size都不包含空指针,所以带参构造要多开一个空间,用来存储’\0’

📚2.string类析构函数

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

📚3.拷贝构造

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

📚4.赋值重载

传统写法:

string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

现代写法:

//法二
void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
string& operator=(string tmp)
{
	swap(tmp);
	return *this;
}

📚5.iterator迭代器和基于范围的for循环

在C++中,范围for在底层是通过迭代器来实现的 所以只要实现了迭代器,就支持范围for
而迭代器类似于指针,迭代器可以被看作是指针的一种泛化,它提供了类似指针的功能,可以进行解引用操作、指针运算等
以下提供了const迭代器和非const迭代器:

typedef char* iterator;
const typedef 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;
}

📚6.运算符重载operator[ ]

这里我们和库中一样,提供以下两个版本

//可读可写
char operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}
//只读
const char operator[](size_t pos)const
{
	assert(pos < _size);
	return _str[pos];
}
char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}
bool operator>(const string& s)
{
	return strcpy(_str, s._str) == 0;
}
bool operator==(const string& s)
{
	return strcpy(_str, s._str) == 0;
}
bool operator>=(const string& s)
{
	return *this > s || *this == s;
}
bool operator<(const string& s)
{
	return !(*this >= s);
	//return strcpy(_str, s._str) == 0;
}
bool operator!=(const string& s)
{
	return !(*this == s);
}

这里我用了this指针指向的是private里面的数据 然后和s对象所对应的值作比较 能和strcpy函数联合起来用 能达到同样的效果 这是一个很不错的想法

📚7.容量相关:size | resize | capacity | reserve

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

在C++中,我们一般不缩容
所以实现reserve时容量调整到n首先判断目标容量n是否大于当前容量。如果小于就不做处理,否则先开辟n+1个内存空间(多出来的一个用于存储‘\0’),然后将原有数据拷贝到新空间strcpy会将’\0’一并拷贝过去然后释放就空间,并让_str指向新空间,同时更新_capacity

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

当n<_size时,只需将下标为n的地址处的数据改为’\0’
其他情况,我们直接统一处理。直接复用reserve()函数将_capacity扩到n 然后用将[_size, n)中的数据全部初始化为ch 这里博主给ch一个初始值’\0’,但ch不一定为’\0’,所以要将下标为n处的地址初始化为’\0’

void resize(size_t n, char ch='\0')
{
	if (n <= _size)
	{
		_str[n] = '\0';
		_size = n;
	}
	else
	{
		reserve(n);
		while (_size < n)
		{
			_str[_size] = ch;
			_size++;
		}
		_str[_size] = '\0';
	}
}

📚8.数据相关:push_back | append | operator+= | insert | erase

尾插:push_back
尾插首先检查扩容,在插入数据

void push_back(char ch)
{
	//扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//插入数据
	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';

}

append尾部插入字符串

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

operator+=()字符/字符串
operator+=()字符、字符串可以直接复用push_back和append

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

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

insert插入字符/字符串
下面给出各位初学者容易犯的错误:

void insert(size_t pos, char ch)
{
	assert(pos <= _size);
	//扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//挪动数据
	size_t end = _size;
	while (end >= pos)
	{
		_str[end+1] = _str[end];
		end--;
	}
	_str[pos] = ch;
	_size++
}

假设是在头插字符,end理论上和pos(即0)比较完后就减到-1,在下一次循环条件比较时失败,退出循环 但遗憾的是end是size_t类型,始终>=0, 会导致死循环
解决办法如下
从end从最后数据的后一位开始,每次将前一个数据移到当前位置。最后条件判断就转化为end>pos,不会出现死循环这种情况

void insert(size_t pos, char ch)
{
	assert(pos <= _size);
	//扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//挪动数据
	size_t end = _size+1;
	while (end > pos)
	{
		_str[end] = _str[end-1];
		end--;
	}
	//插入数据,更新_size
	_str[pos] = ch;
	_size++;
}

insert插入字符串

void insert(size_t pos, const char* str)
{
	int len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	
	size_t end = _size+1;
	while (end > pos)
	{
		_str[end + len-1] = _str[end-1];
		end--;
	}
	strncpy(_str + pos, str, len);
	_size += len;
}

erase
erase分两种情况:
从pos开始,要删的数据个数超过的字符串,即将pos后序所有数据全部情况 直接将pos处数据置为’\0’即可
从pos开始,要删的数据个数没有超出的字符串。所以只需要从pos+len位置后的所有数据向前移动从pos位置覆盖原数据即可

void erase(size_t pos, size_t len = npos)
{
	//len==nppos意思是字串全部删除
	if (len==npos || pos + len >= _size)
	{
		//有多少,删多少
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			begin++;
		}
		_size -= len;
	}
}

📚9.关系操作符重载:< | == | <= | > | >= | !=

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

📚10.find查找字符 | 字符串 | substr

size_t find(char ch, size_t pos = 0)
{
	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 char* p = strstr(_str + pos, sub);
	if (p)
	{
		return p - _str;
	}
	else
	{
		return npos;
	}
}

strsub()模拟实现
strsub目标长度可能越界string,也可能还有没有。但不管是那种情况,最后都需要拷贝数据。所以这里我们可以先将len真实长度计算出来,在拷贝数据

string substr(size_t pos, size_t len = npos)const
{
	string s;
	size_t end = pos + len;
	//目标字符越界string,更新len
	if (len == npos || end >= _size)
	{
		len = _size - pos;
		end = _size;
	}
	//拷贝数据
	s.reserve(len);
	for (size_t i = pos; i < end; i++)
	{
		s += _str[i];
	}
	return s;
}

📚11.流插入和流提取<< | >>

由于前面我们实现了迭代器,所以最简单的方式就是范围for

ostream& operator<<(ostream& out, const string& s)
{
	/*for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}*/
	for (auto ch : s)
		out << ch;
	return out;
}

流提取>>
流提取比较特殊。在流提取前需要将原有数据全部清空。同时由于>>无法获取空字符和换行符()(都是作为多个值之间的间隔),直接流提取到ostream对象中,没法结束类似于C语言中scanf, 换行符和空字符仅仅只是起到判断结束的作用,但scanf无法获取到它们
所以这里博主直接调用istream对象中的get()函数类似于C语言中的getchar()函数

class string
{
	void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
private:
	char* _str;
	size_t _capacity;
	size_t _size;
};

istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch;
		//in >> ch;
		ch = in.get();

		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			//in >> ch;
			ch = in.get();
		}
		return in;
	}

📚12.完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
#include<string>
#include<assert.h>
using namespace std;
namespace success
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		string()
			:_str(new char[1])
			, _size(0)
			, _capacity(0)
		{
			_str[0] = '\0';
		}
		string(const char* str="\0")//不能写'\0'
			//->类型不匹配
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		const char* c_str()
		{
			return _str;
		}
		string(const string& str)
			:_size(str._size)
		{
			_capacity = str._capacity;
			_str = new char[str._capacity + 1];
			strcpy(_str, str._str);
		}
		char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		size_t size()
		{
			return _size;
		}
		string& operator=(const string& str)
		{
			if (this != &str)
			{
				delete[] _str;
				_str = new char[str._capacity + 1];
				_size = str._size;
				_capacity = str._capacity;
				strcpy(_str, str._str);
			}
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
		bool operator>(const string& s)
		{
			return strcpy(_str, s._str) == 0;
		}
		bool operator==(const string& s)
		{
			return strcpy(_str, s._str) == 0;
		}
		bool operator>=(const string& s)
		{
			return *this > s || *this == s;
		}
		bool operator<(const string& s)
		{
			return !(*this >= s);
			//return strcpy(_str, s._str) == 0;
		}
		bool operator!=(const string& s)
		{
			return !(*this == s);
		}
		void resize(size_t n, char ch = '\0');
		void reserve(size_t n)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
		void push_back(char ch)
		{
			if (_size + 1 > _capacity)
			{			
				reserve(_capacity * 2);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			//strcat(_str, str);
			_size += len;
		}
		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 + 1] = _str[end];
				--end;
			}
			_str[pos] = ch;
			++_size;
		}
		void insert(size_t pos, const char* str)
		{
			int len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			
			size_t end = _size+1;
			while (end > pos)
			{
				_str[end + len-1] = _str[end-1];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
		}
		void erase(size_t pos, size_t len = npos)
		{
			//len==nppos意思是字串全部删除
			if (len==npos || pos + len >= _size)
			{
				//有多少,删多少
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t begin = pos + len;
				while (begin <= _size)
				{
					_str[begin - len] = _str[begin];
					begin++;
				}
				_size -= len;
			}
		}
	private:
		char* _str;//思考如何不用const
		size_t _size;
		size_t _capacity;
		//这样写可以
		const static size_t string::npos = -1;
		//下面这种写法也可以->和上述写法产生矛盾
		const static size_t string::npos;
	};
	//静态变量只能在类外面定义 上述两种定义容易产生矛盾
	void test_string1()
	{
		string s1("hello bit");
		string s2("hello bit.");
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		s2[0]++;
		cout << s2.c_str() << endl;
	}
	void test_string2()	
	{
		string s3 = ("hello bit");
		string s4("hello bit.");
		cout << s3.c_str() << endl;
		cout << s4.c_str() << endl;
		string s5(s3);
		cout << s5.c_str() << endl;
		s3[0]++;
		s4 = s3;
		cout << s3.c_str() << endl;
		cout << s5.c_str() << endl;
	}

	void test_string3()
	{
		string s1 = "hello bit";
		string::iterator it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}
#include"string.h"
#include<Windows.h>
#include<iostream>
using namespace std;
int main()
{
	try
	{
		success::test_string2();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	success::test_string1();
	success::test_string2();
	success::test_string3();
	//string a = "hello world";
	//string b = a;//在用一个表达式中 构造+拷贝构造
	//->编译器优化成一次构造
	//a.c_str() == b.c_str()比较的是存储字符串位置的地址
	//if (a.c_str() == b.c_str())
	//{
	//	cout << "true" << endl;
	//}
	//else cout << "false" << endl;//案1
	//string c = b;
	//c = "";
	//if (a.c_str() == b.c_str())	
	//{
	//	cout << "true" << endl;
	//}
	//else cout << "false" << endl;
	//a = "";
	//if (a.c_str() == b.c_str())
	//{
	//	cout << "true" << endl;
	//}
	//else cout << "false" << endl;、
	
	
	//string str("Hello Bit.");
	//str.reserve(111);
	//str.resize(5);
	//str.reserve(50);
	//cout << str.size() << ":" << str.capacity() << endl;
	//str.reserve(111); //调整容量为111
	//str.resize(5);   //调整元素个数为5
	//str.reserve(50);  //调整容量为50,由于调整的容量小于已有空间容量,故容量不会减小
	//所以size = 5 capacity = 111


	//string strText = "How are you?";
	//string strSeparator = " ";
	//string strResult;
	//int size_pos = 0;
	//int size_prev_pos = 0;
	//while ((size_pos = strText.find_first_of(strSeparator, size_pos)) != string::npos)
	//{
	//	strResult = strText.substr(size_prev_pos, size_pos - size_prev_pos);
	//	cout << strResult << " ";
	//	size_prev_pos = ++size_pos;
	//}
	//if (size_prev_pos != strText.size())
	//{
	//	strResult = strText.substr(size_prev_pos, size_pos - size_prev_pos);
	//	cout << strResult << " ";
	//}
	//cout << endl;
	system("pause");
	return 0;
}
// 为了和标准库区分,此处使用String
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 错误示范
//String(const char* str = nullptr) 错误示范
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 测试
void TestString()
{
String s1("hello bit!!!");
String s2(s1);
}

在这里插入图片描述
说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝
浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规
深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供
在这里插入图片描述
传统版写法的String类

class String
{
public:
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};

现代版写法的String类

class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(nullptr)
{
String strTmp(s._str);
swap(_str, strTmp._str);
}
// 对比下和上面的赋值那个实现比较好?
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
/*
String& operator=(const String& s)
{
if(this != &s)
{
String strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
*/
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};

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

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

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

相关文章

redis性能管理

redis的数据库是存放在内存当中&#xff0c;所以对内存的监控至关重要 redis内存监控和解析 1.如何查看redis内存使用情况 [rootlocalhost utils]# redis-cli -h 20.0.0.170 -p 6379 20.0.0.170:6379> info memory used_memory:853336 //redis中数据占用的内存 use…

Can‘t open the append-only file: Permission denied

redis rdb aof-CSDN博客 Cant open the append-only file: Permission denied E:\Document_Redis_Windows\redis-2.4.5-win32-win64\64bit E:\Document_Redis_Windows\redis-2.4.5-win32-win64\64bit\redis.conf 还是不行&#xff0c;就要修改权限了&#xff0c;windows【完全控…

Apache访问控制

服务器相关的访问控制 Options指令 Options指令是Apache服务器配置文件中的一个重要指令,它可以用于控制特定目录启用哪些服务器特性。Options指令可以在Apache服务器的核心配置、虚拟主机配置、特定目录配置以及.htaccess文件中使用。 以下是一些常用的服务器特性选项: N…

springboot前后端分离项目配置https接口(ssl证书)

文章目录 说明vue.js前端部署vue.js项目axios请求配置本地创建日志文件创建Dockerfile文件配置ssl证书nginx.confvue项目打包上传创建容器部署 后端springboot项目部署配置ssl证书打包部署 补充&#xff1a;jsk证书和pfx证书补充&#xff1a;两种证书的转化JKS转PFXPFX 转 JKS …

DLL文件的使用

DLL文件就是动态库文件&#xff0c;搞个例子就懂了&#xff0c;搞两个工程&#xff0c;一个工程生成DLL&#xff0c;一个工程调用DLL。 生成DLL工程 打开Visual Studio 2019&#xff0c;版本高了可能不行&#xff0c;创建新项目。 搜索DLL&#xff0c;动态链接库。 给项目起个…

万界星空科技SMT行业生产管理MES系统解决方案

一、SMT行业特点&#xff1a; SMT&#xff08;Surface Mounted Technology&#xff09;作为电子组装行业里首先的技术和工艺&#xff0c;选择合适的MES解决方案来保障SMT生产的成功至关重要。 电子行业涉及的范围非常广&#xff0c;包含了汽车、电脑、电视、手机等产品上&…

如何编辑WordPress配置文件wp-config.php

目录 wp-config.php文件全部内容&#xff1a; 修改wp-config.ph文件中的数据库设置&#xff1a; 设置wp-config.ph文件中的密钥部分 修改数据库表前缀 设置绝对路径 WordPress会把数据库的相关信息存在wp-config.php文件中。如果编辑有问题&#xff0c;则会出现建立数据库连…

Java—学生信息管理系统(简单、详细)

文章目录 一、主界面展示二、学生类三、系统功能方法3.1 main()方法3.2 添加学生信息3.3 删除学生信息3.4 修改学生信息3.5 查看所有学生信息 四、完整代码4.1 Student .Java4.2 StudentManger.Java 前言&#xff1a;本案例在实现时使用了Java语言中的ArrayList集合来储存数据。…

八、ffmpeg录制视频为yuv文件

前言 测试环境&#xff1a; ffmpeg的4.3.2自行编译版本windows环境qt5.12 图片的一些重要知识&#xff1a; RGB图片 位深度&#xff1a;每一个像素都会使用n个二进制位来存储颜色信息。每一个像素的颜色都是由红&#xff08;Red&#xff09;、绿&#xff08;Green&#xff0…

中职组网络安全 Server-Hun-1.img Server-Hun-2.img

一串密码 smbuser用户和密码登录ssh还是失败提示需要密钥&#xff0c;尝试ftp登录成功 发现密钥存放在.ssh/下&#xff0c;在kali上生成一个密钥&#xff0c;通过上传到.ssh/下&#xff0c;将其替换掉 使用kali生成密钥 登录成功,但是无法拿到root目录下的flag 获取root用户权限…

分类预测 | Matlab实现基于PSO-PNN粒子群算法优化概率神经网络的数据分类预测

分类预测 | Matlab实现基于PSO-PNN粒子群算法优化概率神经网络的数据分类预测 目录 分类预测 | Matlab实现基于PSO-PNN粒子群算法优化概率神经网络的数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现基于PSO-PNN粒子群算法优化概率神经网络的数据…

Scala如何写一个通用的游戏数据爬虫程序

以前想要获取一些网站数据的时候&#xff0c;都是通过人工手动复制粘贴&#xff0c;这样的效率及其低下。数据少无所谓&#xff0c;如果需要采集大量数据&#xff0c;手动就显得乏力了。半夜睡不着&#xff0c;爬起来写一段有关游戏商品数据的爬虫通用模板&#xff0c;希望能帮…

大数据预处理技术

文章目录 前言 大数据技术成为前沿专业 也是现在甚至未来的朝阳产业&#xff0c;大数据有分别是 数据预处理 数据存储 大数据处理和分析 数据可视化 部分组成 &#xff0c;大数据行业有数据则称王&#xff0c;大数据的核心是数据本身 怎么获取有价值的数据呢&#xff1f;本章讲…

【iOS】知乎日报

文章目录 前言一、首页1.网络的异步请求2.避免同一网络请求执行多次3.下拉刷新与上拉加载的实现下拉刷新上拉加载 二、网页1.webView的实现2.webView的滑动加载3.网页与首页内容的同步更新 三、评论区Masonory实现行高自适应 四、收藏中心通过FMDB实现数据持久化1.创建或打开数…

笔记本外接显示器的一些基本操作

1>&#xff0c;安装问题直接问客服&#xff0c;正常情况是将显示屏接上电源&#xff0c;然后用先将显示屏和笔记本的HDMI接口连接即可。 按下组合键 win p ,选择 “复制”。 2>&#xff0c;接上显示屏后&#xff0c;原笔记本无声音&#xff1f; 1、找到笔记本电脑右下…

【Rxjava详解】(二) 操作符的妙用

文章目录 接口变化操作符mapflatmapdebouncethrottleFirst()takeconcat RxJava 是一个基于 观察者模式的异步编程库&#xff0c;它提供了丰富的操作符来处理和转换数据流。 操作符是 RxJava 的核心组成部分&#xff0c;它们提供了一种灵活、可组合的方式来处理数据流&#xf…

5-1 Java 网络编程

第1关&#xff1a;URL类与InetAddress类 任务描述 本关任务&#xff1a;了解网络编程基础知识。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.URL&#xff1b;2.InetAddress。 URL 统一资源定位符&#xff08;Uniform Resource Locator&#xff0c;缩…

新的centos7.9安装jenkins—(一)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 因为是用java8&#xff0c;所以还是要最后java8版本的jenkins&#xff0c;版本号是2.346.3&#xff0c;后…

HTTP四大参数类型及请求参数的方式和如何接收

HTTP 请求中4大参数类型和接收方法。 1、请求头参数head 请求头参数顾名思义&#xff0c;是存放在请求头中发送给服务器的参数&#xff0c;服务器通过解析请求头获取参数内容。通常会存放本次请求的基本设置&#xff0c;以帮助服务器理解并解析本次请求的body体。 参数形式如…

大模型变身双面人:虚假新闻制造机VS假新闻鉴别大师!

大家是怎样看待大型语言模型生成信息的可靠性呢&#xff1f; 尽管大语言模型生成的内容“像模像样”&#xff0c;但这些模型偶尔的失误揭示了一个关键问题&#xff1a;它们生成的内容并不总是真实可靠的。 那么&#xff0c;这种“不保真”特性能否被用来制造虚假信息呢&#x…