【C++初阶】STL详解(二)string类的模拟实现

news2024/10/6 10:34:19

本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。

💓博主csdn个人主页:小小unicorn
⏩专栏分类:C++
🚚代码仓库:小小unicorn的代码仓库🚚
🌹🌹🌹关注我带你学习编程知识

STL详解(二)

  • string各函数接口总览
  • 默认成员函数:
    • 构造函数
    • 拷贝构造函数
      • 1.传统写法:
      • 2.现代写法
    • 析构函数
    • 赋值运算符重载函数
      • 1.传统写法
      • 1.现代写法
  • 迭代器相关函数
    • begin与end
  • 容量相关:
    • size和capacity
    • reserve与resize
    • empty
  • 访问字符串相关函数
    • operator[ ]
    • find和rfind
  • 修改字符串相关函数
    • push_back
    • append
    • operator+=
    • insert
    • erase
    • swap
    • c_str
  • 关系运算符重载函数
  • >>和<<运算符的重载以及getline函数
    • >>运算符的重载
    • <<运算符的重载
    • getline
  • 测试相关接口函数:
    • 测试1.对元素的访问:迭代器.c_str.与运算符重载[]
    • 测试2.修改字符串
    • 测试3.insert
    • 测试4.关系运算符与流插入流提取
    • 测试5.erase
    • 测试6.resize
    • 测试7.find
    • 测试8:拷贝构造
    • 测试9.容量与大小
  • string模拟实现源码
    • string.h
    • test.c

string各函数接口总览

namespace NIC
{
	//模拟实现string类
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		string(const char* str = "");         //构造函数
		string(const string& s);              //拷贝构造函数
		string& operator=(const string& s);   //赋值运算符重载函数
		~string();                            //析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;

		//容量和大小相关函数
		size_t size();
		size_t capacity();
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		bool empty()const;

		//修改字符串相关函数
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& erase(size_t pos, size_t len);
		void clear();
		void swap(string& s);
		const char* c_str()const;

		//访问字符串相关函数
		char& operator[](size_t i);
		const char& operator[](size_t i)const;
		size_t find(char ch, size_t pos = 0)const;
		size_t find(const char* str, size_t pos = 0)const;
		size_t rfind(char ch, size_t pos = npos)const;
		size_t rfind(const char* str, size_t pos = 0)const;

		//关系运算符重载函数
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
		bool operator<(const string& s)const;
		bool operator<=(const string& s)const;
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;

	private:
		char* _str;       //存储字符串
		size_t _size;     //记录字符串当前的有效长度
		size_t _capacity; //记录字符串当前的容量
		static const size_t npos; //静态成员变量(整型最大值)
	};
	const size_t string::npos = -1;

	//<<和>>运算符重载函数
	istream& operator>>(istream& in, string& s);
	ostream& operator<<(ostream& out, const string& s);
	istream& getline(istream& in, string& s);
}

注:为了防止与标准库当中的string类产生命名冲突,模拟实现时需放在自己的命名空间当中。

默认成员函数:

构造函数

构造函数设置为缺省参数,若不传入参数,则默认构造为空字符串。字符串的初始大小和容量均设置为传入C字符串的长度(不包括’\0’)

//构造函数
string(const char* str = "")
	:_size(strlen(str))//初始时,字符串大小设置为字符串长度
	, _capacity(_size)///初始时,字符串容量设置为字符串长度
{
	_str = new char[_capacity + 1];//为存储字符开辟空间(多开一个用于存放'\0')
	strcpy(_str, str);//将字符串拷贝到已经开好的空间
}

拷贝构造函数

在模拟实现拷贝之前,先了解一下深浅拷贝问题:

浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。

深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响。

很明显,我们并不希望拷贝出来的两个对象之间存在相互影响,因此,我们这里需要用到深拷贝。下面提供深拷贝的两种写法:

1.传统写法:

传统写法的思想:先开辟一块足以容纳源对象字符串的空间,然后将源对象的字符串拷贝过去,接着把源对象的其他成员变量也赋值过去即可。因为拷贝对象的_str与源对象的_str指向的并不是同一块空间,所以拷贝出来的对象与源对象是互相独立的。

// 传统写法
// s2(s1)
string(const string& s)
{
	_str = new char[s._capacity+1];
	strcpy(_str, s._str);//将s._str拷贝一份到_str
	_size = s._size;//_size赋值
	_capacity = s._capacity;//_capacity赋值
}

2.现代写法

在这里插入图片描述

现代写法与传统写法的思想不同:先根据源字符串的C字符串调用构造函数构造一个tmp对象,然后再将tmp对象与拷贝对象的数据交换即可。拷贝对象的_str与源对象的_str指向的也不是同一块空间,是互相独立的。

//现代写法
// s2(s1)
string(const string& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str);//调用构造函数
	swap(tmp);//交换这两个对象
}

注:swap成员函数的模拟实现在文章的后面

析构函数

string类的析构函数需要我们进行编写,因为每个string对象中的成员_str都指向堆区的一块空间,当对象销毁时堆区对应的空间并不会自动销毁,为了避免内存泄漏,我们需要使用delete手动释放堆区的空间。

//析构函数
~string()
{
	delete[] _str;  //释放_str指向的空间
	_str = nullptr; //及时置空,防止非法访问
	_size = 0;      //大小置0
	_capacity = 0;  //容量置0
}

赋值运算符重载函数

与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,我们同样需要采用深拷贝。下面也提供深拷贝的两种写法:

1.传统写法

赋值运算符重载函数的传统写法与拷贝构造函数的传统写法几乎相同,只是左值的_str在开辟新空间之前需要先将原来的空间释放掉,并且在进行操作之前还需判断是否是自己给自己赋值,若是自己给自己赋值,则无需进行任何操作。

//传统写法
string& operator=(const string& s)
{
	if (this != &s) //防止自己给自己赋值
	{
		delete[] _str; //将原来_str指向的空间释放
		_str = new char[s._capacity + 1]; //重新申请一块空间
		strcpy(_str, s._str);    //将s._str拷贝一份到_str
		_size = s._size;         //_size赋值
		_capacity = s._capacity; //_capacity赋值
	}
	return *this; //返回左值(支持连续赋值)
}

1.现代写法

赋值运算符重载函数的现代写法与拷贝构造函数的现代写法也是非常类似,但拷贝构造函数的现代写法是通过代码语句调用构造函数构造出一个对象,然后将该对象与拷贝对象交换;而赋值运算符重载函数的现代写法是通过采用“值传递”接收右值的方法,让编译器自动调用拷贝构造函数,然后我们再将拷贝出来的对象与左值进行交换即可。

//现代写法1
string& operator=(string s) //编译器接收右值的时候自动调用拷贝构造函数
{
	swap(s); //交换这两个对象
	return *this; //返回左值(支持连续赋值)
}

但这种写法无法避免自己给自己赋值,就算是自己给自己赋值这些操作也会进行,虽然操作之后对象中_str指向的字符串的内容不变,但是字符串存储的地址发生了改变,为了避免这种操作我们可以采用下面这种写法:

//现代写法2
string& operator=(const string& s)
{
	if (this != &s) //防止自己给自己赋值
	{
		string tmp(s); //用s拷贝构造出对象tmp
		swap(tmp); //交换这两个对象
	}
	return *this; //返回左值(支持连续赋值)
}

但实际中很少出现自己给自己赋值的情况,所以采用“现代写法1”就行了。

迭代器相关函数

string类中的迭代器可以认为是就是字符指针(原生指针),只是给字符指针起了一个别名叫iterator而已。但并不是说所有迭代器都是指针。

typedef char* iterator;
typedef const char* const_iterator;

begin与end

string类中的begin和end函数的实现简单,begin函数的作用就是返回字符串中第一个字符的地址:

iterator begin()
{
	return _str; //返回字符串中第一个字符的地址
}
const_iterator begin()const
{
	return _str; //返回字符串中第一个字符的const地址
}

end函数的作用就是返回字符串中最后一个字符的后一个字符的地址(即’\0’的地址):

iterator end()
{
	return _str + _size; //返回字符串中最后一个字符的后一个字符的地址
}
const_iterator end()const
{
	return _str + _size; //返回字符串中最后一个字符的后一个字符的const地址
}

在明白了string类中迭代器的底层实现,再来看看我们用迭代器遍历string的代码,其实就是用指针在遍历字符串而已。

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

在string介绍中我们还说到,可以用范围for来遍历string,可能很多初学者都会觉得范围for是个很神奇的东西,只需要一点点代码就能实现string的遍历。

实际上范围for并不神奇,因为在代码编译的时候,编译器会自动将范围for替换为迭代器的形式,也就是说范围for是由迭代器支持的,现在我们已经实现了string类的迭代器,自然也能用范围for对string进行遍历:

string s("hello world!!!");
//编译器将其替换为迭代器形式
for (auto e : s)
{
	cout << e << " ";
}
cout << endl;

容量相关:

size和capacity

因为string类的成员变量是私有的,我们并不能直接对其进行访问,所以string类设置了size和capacity这两个成员函数,用于获取string对象的大小和容量。

size函数用于获取字符串当前的有效长度(不包括’\0’)

//大小
size_t size()const
{
	return _size; //返回字符串当前的有效长度
}

capacity函数用于获取字符串当前的容量。

//容量
size_t capacity()const
{
	return _capacity; //返回字符串当前的容量
}

reserve与resize

reserve和resize这两个函数的执行规则一定要区分清楚。

reserve规则:
 1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
 2、当n小于对象当前的capacity时,什么也不做。

//改变容量,大小不变
void reserve(size_t n)
{
	if (n > _capacity) //当n大于对象当前容量时才需执行操作
	{
		char* tmp = new char[n + 1]; //多开一个空间用于存放'\0'
		strncpy(tmp, _str, _size + 1); //将对象原本的C字符串拷贝过来(包括'\0')
		delete[] _str; //释放对象原本的空间
		_str = tmp; //将新开辟的空间交给_str
		_capacity = n; //容量跟着改变
	}
}

注意:代码中使用strncpy进行拷贝对象C字符串而不是strcpy,是为了防止对象的C字符串中含有有效字符’\0’而无法拷贝(strcpy拷贝到第一个’\0’就结束拷贝了)。
在这里插入图片描述
resize规则:
 1、当n大于当前的size时,将size扩大到n,扩大的字符为ch,若ch未给出,则默认为’\0’。
 2、当n小于当前的size时,将size缩小到n。

//改变大小
void resize(size_t n, char ch = '\0')
{
	if (n <= _size) //n小于当前size
	{
		_size = n; //将size调整为n
		_str[_size] = '\0'; //在size个字符后放上'\0'
	}
	else //n大于当前的size
	{
		if (n > _capacity) //判断是否需要扩容
		{
			reserve(n); //扩容
		}
		for (size_t i = _size; i < n; i++) //将size扩大到n,扩大的字符为ch
		{
			_str[i] = ch;
		}
		_size = n; //size更新
		_str[_size] = '\0'; //字符串后面放上'\0'
	}
}

empty

empty是string的判空函数,我们可以调用strcmp函数来实现,strcmp函数是用于比较两个字符串大小的函数,当两个字符串相等时返回0。

//判空
bool empty()
{
	return strcmp(_str, "") == 0;
}

注意:两个字符串相比较千万不能用 == 。

访问字符串相关函数

operator[ ]

[ ]运算符的重载是为了让string对象能像C字符串一样,通过[ ] +下标的方式获取字符串对应位置的字符。

在C字符串中我们通过[ ] +下标的方式可以获取字符串对应位置的字符,并可以对其进行修改,实现[ ] 运算符的重载时只需返回对象C字符串对应位置字符的引用即可,这样便能实现对该位置的字符进行读取和修改操作了,但需要注意在此之前检测所给下标的合法性。

//[]运算符重载(可读可写)
char& operator[](size_t i)
{
	assert(i < _size); //检测下标的合法性
	return _str[i]; //返回对应字符
}

在某些场景下,我们可能只能用[ ] +下标的方式读取字符而不能对其进行修改。

例如,对一个const的string类对象进行[ ] +下标的操作,我们只能读取所得到的字符,而不能对其进行修改。所以我们需要再重载一个[ ] 运算符,用于只读操作。

//[]运算符重载(只读)
const char& operator[](size_t i)const
{
	assert(i < _size); //检测下标的合法性
	return _str[i]; //返回对应字符
}

find和rfind

find函数和rfind函数都是用于在字符串中查找一个字符或是字符串,find函数和rfind函数分别用于正向查找和反向查找,即从字符串开头开始向后查找和从字符串末尾开始向前查找。

find函数:
1、正向查找第一个匹配的字符。

首先判断所给pos的合法性,然后通过遍历的方式从pos位置开始向后寻找目标字符,若找到,则返回其下标;若没有找到,则返回npos。(npos是string类的一个静态成员变量,其值为整型最大值)

//正向查找第一个匹配的字符
size_t find(char ch, size_t pos = 0)
{
	assert(pos < _size); //检测下标的合法性
	for (size_t i = pos; i < _size; i++) //从pos位置开始向后寻找目标字符
	{
		if (_str[i] == ch)
		{
			return i; //找到目标字符,返回其下标
		}
	}
	return npos; //没有找到目标字符,返回npos
}

2、正向查找第一个匹配的字符串。

首先也是先判断所给pos的合法性,然后我们可以通过调用strstr函数进行查找。strstr函数若是找到了目标字符串会返回字符串的起始位置,若是没有找到会返回一个空指针。若是找到了目标字符串,我们可以通过计算目标字符串的起始位置和对象C字符串的起始位置的差值,进而得到目标字符串起始位置的下标。

//正向查找第一个匹配的字符串
size_t find(const char* str, size_t pos = 0)
{
	assert(pos < _size); //检测下标的合法性
	const char* ret = strstr(_str + pos, str); //调用strstr进行查找
	if (ret) //ret不为空指针,说明找到了
	{
		return ret - _str; //返回字符串第一个字符的下标
	}
	else //没有找到
	{
		return npos; //返回npos
	}
}

rfind函数:

实现rfind函数时,我们可以考虑复用已经写好了的两个find函数,但rfind函数是从后先前找,所以我们需要将对象的C字符串逆置一下,若是查找字符串,还需将待查找的字符串逆置一下,然后调用find函数进行查找,但注意传入find函数的pos以及从find函数接收到的pos都需要镜像对称一下。

1、反向查找第一个匹配的字符。
首先我们需要用对象拷贝构造一个临时对象tmp,因为我们并不希望调用rfind函数后对象的C字符串就被逆置了。我们将tmp对象的C字符串逆置,然后将所给pos镜像对称一下再调用find函数,再将从find函数接收到的返回值镜像对称一下作为rfind函数的返回值返回即可。

//反向查找第一个匹配的字符
size_t rfind(char ch, size_t pos = npos)
{
	string tmp(*this); //拷贝构造对象tmp
	reverse(tmp.begin(), tmp.end()); //调用reverse逆置对象tmp的C字符串
	if (pos >= _size) //所给pos大于字符串有效长度
	{
		pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
	}
	pos = _size - 1 - pos; //将pos改为镜像对称后的位置
	size_t ret = tmp.find(ch, pos); //复用find函数
	if (ret != npos)
		return _size - 1 - ret; //找到了,返回ret镜像对称后的位置
	else
		return npos; //没找到,返回npos
}

注:rfind函数规定,当所给的pos大于等于字符串的有效长度时,看作所给pos为字符串最后一个字符的下标。

2、反向查找第一个匹配的字符串。

首先我们还是需要用对象拷贝构造一个临时对象tmp,然后将tmp对象的C字符串逆置,同时我们还需要拷贝一份待查找的字符串,也将其逆置。然后将所给pos镜像对称一下再调用find函数。注意:此时我们将从find函数接收到的值镜面对称后,得到的是待查找字符串的最后一个字符在对象C字符串中的位置,而我们需要返回的是待查找字符串在对象C字符串中的第一个字符的位置,所以还需做进一步调整后才能作为rfind函数的返回值返回。
在这里插入图片描述

//反向查找第一个匹配的字符串
size_t rfind(const char* str, size_t pos = npos)
{
	string tmp(*this); //拷贝构造对象tmp
	reverse(tmp.begin(), tmp.end()); //调用reverse逆置对象tmp的C字符串
	size_t len = strlen(str); //待查找的字符串的长度
	char* arr = new char[len + 1]; //开辟arr字符串(用于拷贝str字符串)
	strcpy(arr, str); //拷贝str给arr
	size_t left = 0, right = len - 1; //设置左右指针
	//逆置字符串arr
	while (left < right)
	{
		::swap(arr[left], arr[right]);
		left++;
		right--;
	}
	if (pos >= _size) //所给pos大于字符串有效长度
	{
		pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
	}
	pos = _size - 1 - pos; //将pos改为镜像对称后的位置
	size_t ret = tmp.find(arr, pos); //复用find函数
	delete[] arr; //销毁arr指向的空间,避免内存泄漏
	if (ret != npos)
		return _size - ret - len; //找到了,返回ret镜像对称后再调整的位置
	else
		return npos; //没找到,返回npos
}

修改字符串相关函数

push_back

push_back函数的作用就是在当前字符串的后面尾插上一个字符,尾插之前首先需要判断是否需要增容,若需要,则调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在该字符的后方设置上’\0’,否则打印字符串的时候会出现非法访问,因为尾插的字符后方不一定就是’\0’

//尾插字符
void push_back(char ch)
{
	if (_size == _capacity) //判断是否需要增容
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2); //将容量扩大为原来的两倍
	}
	_str[_size] = ch; //将字符尾插到字符串
	_str[_size + 1] = '\0'; //字符串后面放上'\0'
	_size++; //字符串的大小加一
}

实现push_back还可以直接复用下面即将实现的insert函数。

//尾插字符
void push_back(char ch)
{
	insert(_size, ch); //在字符串末尾插入字符ch
}

append

append函数的作用是在当前字符串的后面尾插一个字符串,尾插前需要判断当前字符串的空间能否容纳下尾插后的字符串,若不能,则需要先进行增容,然后再将待尾插的字符串尾插到对象的后方,因为待尾插的字符串后方自身带有’\0’,所以我们无需再在后方设置’\0’。

//尾插字符串
void append(const char* str)
{
	size_t len = _size + strlen(str); //尾插str后字符串的大小(不包括'\0')
	if (len > _capacity) //判断是否需要增容
	{
		reserve(len); //增容
	}
	strcpy(_str + _size, str); //将str尾插到字符串后面
	_size = len; //字符串大小改变
}

实现append函数也可以直接复用下面即将实现的insert函数。

//尾插字符串
void append(const char* str)
{
	insert(_size, str); //在字符串末尾插入字符串str
}

operator+=

+=运算符的重载是为了实现字符串与字符、字符串与字符串之间能够直接使用+=运算符进行尾插。
+=运算符实现字符串与字符之间的尾插直接调用push_back函数即可。

//+=运算符重载
string& operator+=(char ch)
{
	push_back(ch); //尾插字符串
	return *this; //返回左值(支持连续+=)
}

+=运算符实现字符串与字符串之间的尾插直接调用append函数即可。

//+=运算符重载
string& operator+=(const char* str)
{
	append(str); //尾插字符串
	return *this; //返回左值(支持连续+=)
}

insert

insert函数的作用是在字符串的任意位置插入字符或是字符串。

insert函数用于插入字符时,首先需要判断pos的合法性,若不合法则无法进行操作,紧接着还需判断当前对象能否容纳插入字符后的字符串,若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的,先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后将字符插入字符串即可。

//在pos位置插入字符
string& insert(size_t pos, char ch)
{
	assert(pos <= _size); //检测下标的合法性
	if (_size == _capacity) //判断是否需要增容
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2); //将容量扩大为原来的两倍
	}
	char* end = _str + _size;
	//将pos位置及其之后的字符向后挪动一位
	while (end >= _str + pos)
	{
		*(end + 1) = *(end);
		end--;
	}
	_str[pos] = ch; //pos位置放上指定字符
	_size++; //size更新
	return *this;
}

insert函数用于插入字符串时,首先也是判断pos的合法性,若不合法则无法进行操作,再判断当前对象能否容纳插入该字符串后的字符串,若不能则还需调用reserve函数进行扩容。插入字符串时,先将pos位置及其后面的字符统一向后挪动len位(len为待插入字符串的长度),给待插入的字符串留出位置,然后将其插入字符串即可。

//在pos位置插入字符串
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size); //检测下标的合法性
	size_t len = strlen(str); //计算需要插入的字符串的长度(不含'\0')
	if (len + _size > _capacity) //判断是否需要增容
	{
		reserve(len + _size); //增容
	}
	char* end = _str + _size;
	//将pos位置及其之后的字符向后挪动len位
	while (end >= _str + pos)
	{
		*(end + len) = *(end);
		end--;
	}
	strncpy(_str + pos, str, len); //pos位置开始放上指定字符串
	_size += len; //size更新
	return *this;
}

注意:插入字符串的时候使用strncpy,不能使用strcpy,否则会将待插入的字符串后面的’\0’也插入到字符串中。

erase

erase函数的作用是删除字符串任意位置开始的n个字符。删除字符前也需要判断pos的合法性,进行删除操作的时候分两种情况:

1、pos位置及其之后的有效字符都需要被删除。
这时我们只需在pos位置放上’\0’,然后将对象的size更新即可。

2、pos位置及其之后的有效字符只需删除一部分。
这时我们可以用后方需要保留的有效字符覆盖前方需要删除的有效字符,此时不用在字符串后方加’\0’,因为在此之前字符串末尾就有’\0’了。

//删除pos位置开始的len个字符
string& erase(size_t pos, size_t len = npos)
{
	assert(pos < _size); //检测下标的合法性
	size_t n = _size - pos; //pos位置及其后面的有效字符总数
	if (len >= n) //说明pos位置及其后面的字符都被删除
	{
		_size = pos; //size更新
		_str[_size] = '\0'; //字符串后面放上'\0'
	}
	else //说明pos位置及其后方的有效字符需要保留一部分
	{
		strcpy(_str + pos, _str + pos + len); //用需要保留的有效字符覆盖需要删除的有效字符
		_size -= len; //size更新
	}
	return *this;
}

clear函数用于将对象中存储的字符串置空,实现时直接将对象的_size置空,然后在字符串后面放上’\0’即可。

//清空字符串
void clear()
{
	_size = 0; //size置空
	_str[_size] = '\0'; //字符串后面放上'\0'
}

swap

swap函数用于交换两个对象的数据,直接调用库里的swap模板函数将对象的各个成员变量进行交换即可。

但我们若是想在这里调用库里的swap模板函数,需要在swap函数之前加上“::”(作用域限定符),告诉编译器优先在全局范围寻找swap函数,否则编译器编译时会认为你调用的是正在实现的swap函数(就近原则)。

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

c_str

c_str函数用于获取对象C类型的字符串,实现时直接返回对象的成员变量_str即可。

//返回C类型的字符串
const char* c_str()const
{
	return _str;
}

关系运算符重载函数

关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于C++中任意一个类的关系运算符重载,我们均只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现。

例如,对于string类,我们可以选择只重载 > 和 == 这两个关系运算符

//>运算符重载
bool operator>(const string& s)const
{
	return strcmp(_str, s._str) > 0;
}
//==运算符重载
bool operator==(const string& s)const
{
	return strcmp(_str, s._str) == 0;
}

剩下的四个关系运算符的重载,就可以通过复用这两个已经重载好了的关系运算符来实现了。

//>=运算符重载
bool operator>=(const string& s)const
{
	return (*this > s) || (*this == s);
}
//<运算符重载
bool operator<(const string& s)const
{
	return !(*this >= s);
}
//<=运算符重载
bool operator<=(const string& s)const
{
	return !(*this > s);
}
//!=运算符重载
bool operator!=(const string& s)const
{
	return !(*this == s);
}

>>和<<运算符的重载以及getline函数

>>运算符的重载

重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入。输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取。

//>>运算符的重载
istream& operator>>(istream& in, string& s)
{
	s.clear(); //清空字符串
	char ch = in.get(); //读取一个字符
	while (ch != ' '&&ch != '\n') //当读取到的字符不是空格或'\n'的时候继续读取
	{
		s += ch; //将读取到的字符尾插到字符串后面
		ch = in.get(); //继续读取字符
	}
	return in; //支持连续输入
}

<<运算符的重载

重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印。实现时我们可以直接使用范围for对对象进行遍历即可。

//<<运算符的重载
ostream& operator<<(ostream& out, const string& s)
{
	//使用范围for遍历字符串并输出
	for (auto e : s)
	{
		cout << e;
	}
	return out; //支持连续输出
}

getline

getline函数用于读取一行含有空格的字符串。实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符。

//读取一行含有空格的字符串
istream& getline(istream& in, string& s)
{
	s.clear(); //清空字符串
	char ch = in.get(); //读取一个字符
	while (ch != '\n') //当读取到的字符不是'\n'的时候继续读取
	{
		s += ch; //将读取到的字符尾插到字符串后面
		ch = in.get(); //继续读取字符
	}
	return in;
}

测试相关接口函数:

测试1.对元素的访问:迭代器.c_str.与运算符重载[]

示例:
将字符串s1按照[]访问,按照迭代器访问,按照范围for访问。

void test_string1()
{

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

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

	//访问字符串
	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] ;
	}
	cout << endl;

	//迭代器
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		//写
		(*it)++;
		//读
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//范围for
	for (auto& ch : s1)
	{
		ch++;
		cout << ch << " ";
	}
	cout << endl;
	cout << s1.c_str() << endl;
}

测试结果:
在这里插入图片描述

测试2.修改字符串

示例:
给s1字符串插入其他字符串,用push_back与+=

void test_string2()
{
	string s1("hello world");
	cout << s1.c_str() << endl;
	s1.push_back(' ');
	s1.append("hello bit hello bit");

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

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

	string s2;
	s2 += '#';
	s2 += "*********************";
	cout << s2.c_str() << endl;
}

测试结果:
在这里插入图片描述

测试3.insert

示例:
测试insert

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

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

	s1.insert(s1.size(), '%');
	cout << s1.c_str() << endl;

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

测试结果:
在这里插入图片描述

测试4.关系运算符与流插入流提取

示例:
比较s1与s2,输入字符串并打印

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

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

	s1[0] = 'z';
	cout << (s1 >= s2) << endl;

	cout << s1 << endl;
	cin >> s1;
	cout << s1 << endl;
}

测试结果:
在这里插入图片描述

测试5.erase

示例:
测试rease

void test_string5()
{
	string s1("hello world");
	s1.insert(5, "abc");
	cout << s1 << endl;

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

	s1.erase(0, 3);
	cout << s1 << endl;

	s1.erase(5, 100);
	cout << s1 << endl;

	s1.erase(2);
	cout << s1 << endl;
}

测试结果:
在这里插入图片描述

测试6.resize

示例:
测试resize

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

	s1.resize(5);
	cout << s1 << endl;

	s1.resize(25, 'x');
	cout << s1 << endl;
}

测试结果:
在这里插入图片描述

测试7.find

示例:
将网址按照协议,域名,资源名三部分找出来。

void test_string7()
{
	string s1("test.cpp.tar.zip");
	//size_t i = s1.find('.');
	//size_t i = s1.rfind('.');

	//string s2 = s1.substr(i);
	//cout << s2 << endl;

	string s3("https://legacy.cplusplus.com/reference/string/string/rfind/");
	//string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg");
	// 协议
	// 域名
	// 资源名

	string sub1, sub2, sub3;
	size_t i1 = s3.find(':');
	if (i1 != string::npos)
		sub1 = s3.substr(0, i1);
	else
		cout << "没有找到i1" << endl;

	size_t i2 = s3.find('/', i1 + 3);
	if (i2 != string::npos)
		sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));
	else
		cout << "没有找到i2" << endl;

	sub3 = s3.substr(i2 + 1);

	//域名
	cout << "协议" << endl;
	cout << sub1 << endl;
	cout << "域名" << endl;
	cout << sub2 << endl;
	cout << "资源名" << endl;
	cout << sub3 << endl;
}

测试结果:
在这里插入图片描述

测试8:拷贝构造

示例:
将s3拷贝给s2

void test_string8()
{
	string s1("hello world");
	string s2 = s1;
	cout << s1 << endl;
	cout << s2 << endl;

	string s3("xxxxxxxxxxxxxxxxxxx");
	s2 = s3;

	cout << s2 << endl;
	cout << s3 << endl;
}

结果:
在这里插入图片描述

测试9.容量与大小

示例:
计算字符串“hello world”的长度与容量

void test_string9()
{
	string s1("hello world");

	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
}

结果:

在这里插入图片描述

string模拟实现源码

模拟实现中,我们没有进行对其定义与声明分离,将测试函数写成成员函数包装在命名空间里。

string.h

#include<assert.h>

namespace NIC
{
	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地址
		}

		const_iterator end() const
		{
			return _str + _size; //返回字符串中最后一个字符的后一个字符的地址
		}

		/*string()
			:_str(new char[1]{'\0'})
			,_size(0)
			,_capacity(0)
		{}*/
        
		//构造函数
		string(const char* str = "")
			:_size(strlen(str))//初始时,字符串大小设置为字符串长度
			, _capacity(_size)///初始时,字符串容量设置为字符串长度
		{
			_str = new char[_capacity + 1];//为存储字符开辟空间(多开一个用于存放'\0')
			strcpy(_str, str);//将字符串拷贝到已经开好的空间
		}


		 传统写法
		 s2(s1)
		//string(const string& s)
		//{
		//	_str = new char[s._capacity+1];
		//	strcpy(_str, s._str);//将s._str拷贝一份到_str
		//	_size = s._size;//_size赋值
		//	_capacity = s._capacity;//_capacity赋值
		//}


		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)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);//调用构造函数
			swap(tmp);//交换这两个对象
		}


		//赋值运算符重载函数
		 s2 = s3
		// 写法一:
		//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;
		//}


		//写法二:
		// s2 = s3
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		string tmp(s);
		//		//this->swap(tmp);
		//		swap(tmp);
		//	}

		//	return *this;
		//}

		//写法一:
		// s2 = s3
		string& operator=(string tmp)
		{
			swap(tmp);

			return *this;
		}

		//析构函数
		~string()
		{
			delete[] _str;//释放_str指向的空间
			_str = nullptr;//及时置空,防止非法访问
			_size = _capacity = 0;
		}

		//容量相关
		//大小
		size_t size() const
		{
			return _size;
		}

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

				_capacity = n;
			}
		}
		
		//resize
		void resize(size_t n, char ch = '\0')
		{
			if (n <= _size)//n小于当前size
			{
				_str[n] = '\0';//在size个字符后放上'\0'
				_size = n;//将size调整为n
			}
			else//n大于当前的size
			{
				reserve(n);
				while (_size < n)
				{
					_str[_size] = ch;
					++_size;
				}

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


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

			return _str[pos];
		}

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

			return _str[pos];
		}

		

		
		const char* c_str() const
		{
			return _str;
		}


		

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

		string substr(size_t pos, size_t len = npos)
		{
			string s;
			size_t end = pos + len;
			if (len == npos || pos + len >= _size) // 有多少取多少
			{
				len = _size - pos;
				end = _size;
			}

			s.reserve(len);
			for (size_t i = pos; i < end; i++)
			{
				s += _str[i];
			}

			return s;
		}

		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _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);
			_size += len;
		}

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

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

		// insert(0, 'x')
		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			// 17:17
			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);

			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			// 挪动数据
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + len] = _str[end];
				--end;
			}

			strncpy(_str + pos, str, len);
			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

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

		bool operator<(const string& s) const
		{
			return strcmp(_str, s._str) < 0;
		}

		bool operator==(const string& s) const
		{
			return strcmp(_str, s._str) == 0;
		}

		bool operator<=(const string& s) const
		{
			return *this < s || *this == s;
		}

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

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

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

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

		//const static size_t npos = -1;  // 特例
		//const static double npos = 1.1;  // 不支持
	public:
		const static size_t npos;
	};

	const size_t string::npos = -1;

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

	istream& operator>>(istream& in, string& s)
	{

		s.clear();
		//s.reserve(128);

		char buff[129];
		size_t i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 128)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}

			//s += ch;

			ch = in.get();
		}

		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

	void test_string1()
	{

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

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

		//访问字符串
		for (size_t i = 0; i < s1.size(); i++)
		{
			cout << s1[i] ;
		}
		cout << endl;

		//迭代器
		string::iterator it = s1.begin();
		while (it != s1.end())
		{
			//写
			(*it)++;
			//读
			cout << *it << " ";
			++it;
		}
		cout << endl;
		//范围for
		for (auto& ch : s1)
		{
			ch++;
			cout << ch << " ";
		}
		cout << endl;

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

	void test_string2()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
		s1.push_back(' ');
		s1.append("hello bit hello bit");

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

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

		string s2;
		s2 += '#';
		s2 += "*********************";
		cout << s2.c_str() << endl;
	}

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

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

		s1.insert(s1.size(), '%');
		cout << s1.c_str() << endl;

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

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

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

		s1[0] = 'z';
		cout << (s1 >= s2) << endl;

		cout << s1 << endl;
		cin >> s1;
		cout << s1 << endl;

		/*char ch1, ch2;
		cin >> ch1 >> ch2;*/
	}

	void test_string5()
	{
		string s1("hello world");
		s1.insert(5, "abc");
		cout << s1 << endl;

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

		s1.erase(0, 3);
		cout << s1 << endl;

		s1.erase(5, 100);
		cout << s1 << endl;

		s1.erase(2);
		cout << s1 << endl;
	}

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

		s1.resize(5);
		cout << s1 << endl;

		s1.resize(25, 'x');
		cout << s1 << endl;
	}

	void test_string7()
	{
		string s1("test.cpp.tar.zip");
		//size_t i = s1.find('.');
		//size_t i = s1.rfind('.');

		//string s2 = s1.substr(i);
		//cout << s2 << endl;

		string s3("https://legacy.cplusplus.com/reference/string/string/rfind/");
		//string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg");
		// 协议
		// 域名
		// 资源名

		string sub1, sub2, sub3;
		size_t i1 = s3.find(':');
		if (i1 != string::npos)
			sub1 = s3.substr(0, i1);
		else
			cout << "没有找到i1" << endl;

		size_t i2 = s3.find('/', i1 + 3);
		if (i2 != string::npos)
			sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));
		else
			cout << "没有找到i2" << endl;

		sub3 = s3.substr(i2 + 1);

		//域名
		cout << "协议" << endl;
		cout << sub1 << endl;
		cout << "域名" << endl;
		cout << sub2 << endl;
		cout << "资源名" << endl;
		cout << sub3 << endl;
	}

	void test_string8()
	{
		string s1("hello world");
		string s2 = s1;
		cout << s1 << endl;
		cout << s2 << endl;

		string s3("xxxxxxxxxxxxxxxxxxx");
		s2 = s3;

		cout << s2 << endl;
		cout << s3 << endl;
	}

	void test_string9()
	{
		string s1("hello world");
		
		cout << s1 << endl;
		cout << s1.size() << endl;
		cout << s1.capacity() << endl;
	}
}

test.c

test.c用于测试接口函数:

#include<string>
using namespace std;
#include"string.h"
int main()
{
	NIC::test_string9();
	return 0;
}

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

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

相关文章

Redis链表

前言 链表作为一种常见的数据结构&#xff0c;一般都会内置在很多高级语言中。由于Redis使用的是C语言并没有内置这种数据结构&#xff0c;所以Redis构建了自己的链表实现。 链表在Redis中应用广泛&#xff0c;比如列表建的底层实现之一就是链表。当一个列表键包含了数量比较多…

nodejs+vue面向中小学课堂教学辅助软件系统的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计

主要功能有&#xff0c;管理员通过后台会对此教学辅助进行审核&#xff0c;管理员在还可以进行首页、个人中心、学生管理、教师管理、班级信息管理、科目名称管理、课程信息管理、教学资料管理、作业信息管理、作业提交管理、作业成绩管理、在线考试管理、试题管理、考试管理、…

[PHP]关联和操作MySQL数据库然后将数据库部署到ECS

在Mac电脑上使用VS Code进行PHP开发并关联操作MySQL数据库&#xff0c;然后将数据库部署到ECS。 1.安装PHP和MySQL 确保你的Mac上已经安装了PHP和MySQL。你可以使用Homebrew来安装它们&#xff1a; $ brew install php $ brew install mysql 安装mysql完成后记住这一句: …

Notepad++ 通过HexEditor插件查看.hprof文件、heap dump文件的堆转储数据

文章目录 需求场景插件安装查看notepad的版本&#xff0c;看看是32位的还是64位的下载对应的版本解压导入插件打开notepad插件文件夹&#xff1a;Notepad安装目录新建一个HexEditor文件夹选中插件文件导入 重启notepad使用 需求场景 想要查看app内存的某个域的数据。 利用Andr…

2.项目疑问

Day01 1.前后端分离项目的全局异常处理怎么做 使用ControllerAdviceExceptionHandler&#xff08;类.class&#xff09;来实现异常处理 ControllerAdvice: Controller增强器。将异常处理器应用到所有的控制器 ExceptionHandler&#xff1a;异常处理器&#xff0c;只要发生异…

2023年(第六届)电力机器人应用与创新发展论坛-核心PPT资料下载

一、峰会简介 大会以“聚焦电力机器人创新、助力行业数字化转型、促进产业链协同发展”为主题&#xff0c;展示电力机器人产业全景创新技术&#xff0c;探讨数字化战略下电力机器人应用前景和发展趋势。为加快推进电力机器人应用拓新&#xff0c;助力电网数字化转型升级&#…

编译中的 CMP0148 警告

原因&#xff1a;CMake 3.12后&#xff0c; FindPythonInterp 及 FindPythonLibs被废弃 解决方法&#xff1a;使用 FindPython3、FindPython2、FindPython替换 实例&#xff1a;CMakeLists.txt中修改即可&#xff0c;如下图

【FPGA】Verilog:升降计数器 | 波纹计数器 | 约翰逊计数器 | 实现 4-bit 升降计数器的 UP/DOWN

目录 Ⅰ. 理论部分 0x00 升降计数器&#xff08;UP DOWN Counter&#xff09; 0x01 波纹计数器&#xff08;Ripple Counter&#xff09; 0x02 约翰逊计数器&#xff08;Johnson Counter&#xff09; Ⅱ. 实践部分 0x00 实现&#xff1a;升降计数器&#xff08;4-bit&…

基于java web的中小型人力资源管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

计算机毕业论文内容参考|基于深度学习的交通标识智能识别系统的设计与维护

文章目录 导文摘要前言绪论1课题背景2国内外现状与趋势3课题内容相关技术与方法介绍系统分析总结与展望导文 基于深度学习的交通标识智能识别系统是一种利用深度学习模型对交通标识进行识别和解析的系统。它可以帮助驾驶员更好地理解交通规则和安全提示,同时也可以提高道路交通…

JAVA生成图片缩略图、JAVA截取图片局部内容

JAVA生成图片缩略图、JAVA截取图片局部内容 目前&#xff0c;google已经有了更好的处理JAVA图片的工具&#xff0c;请搜索&#xff1a;Thumbnailator JAVA生成图片缩略图 package com.ares.image.test;import java.awt.Color; import java.awt.Graphics; import java.awt.Im…

Java中利用OpenCV进行人脸识别

OpenCV 概述 ​ OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源计算机视觉库&#xff0c;它提供了丰富的工具和算法&#xff0c;用于处理图像和视频数据。该库由一系列高效的计算机视觉算法组成&#xff0c;涵盖了许多领域&#xff0c;包括目…

K8S1.23.5部署(此前1.17版本步骤囊括)及问题记录

应版本需求&#xff0c;升级容器版本为1.23.5 kubernetes组件 一个kubernetes集群主要由控制节点&#xff08;master&#xff09;与工作节点&#xff08;node&#xff09;组成&#xff0c;每个节点上需要安装不同的组件。 master控制节点&#xff1a;负责整个集群的管理。 …

Pytorch torch.dot、torch.mv、torch.mm、torch.norm的用法详解

torch.dot的用法&#xff1a; 使用numpy求点积&#xff0c;对于二维的且一个二维的维数为1 torch.mv的用法&#xff1a; torch.mm的用法 torch.norm 名词解释&#xff1a;L2范数也就是向量的模&#xff0c;L1范数就是各个元素的绝对值之和例如&#xff1a;

RMI协议详解

前言特点应用示例存在的问题应用场景拓展 前言 RMI&#xff08;Remote Method Invocation&#xff0c;远程方法调用&#xff09;是Java中的一种远程通信协议&#xff0c;用于实现跨网络的对象方法调用。RMI协议基于Java的分布式计算&#xff0c;可以让客户端程序调用远程服务器…

MIB 6.1810实验Xv6 and Unix utilities(5)find

难度:moderate Write a simple version of the UNIX find program for xv6: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c. 题目要求&#xff1a;实现find &#xff0c;即在某个路径中&#xff0c;找出某…

Babyk勒索病毒数据集恢复,计算机服务器中了babyk勒索病毒怎么办?

计算机网络技术的不断应用&#xff0c;为企业的生产运营提供了极大便利&#xff0c;网络技术的不断发展也带来了许多网络安全隐患&#xff0c;近期&#xff0c;云天数据恢复中心陆续接到许多企业的求助&#xff0c;企业的计算机服务器遭到了babyk勒索病毒的攻击&#xff0c;导致…

nodejs+vue杰和牧场管理系统的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计

系统涉及的对象是奶牛。 系统使用员工有管理员和普通员工。 管理员有修改的权限&#xff0c;普通员工没有。系统包含新闻功能&#xff0c;最好是有个后台管理&#xff0c;在后台输入新闻标题和内容&#xff0c;插入图片&#xff0c;在网页上就可以展示。最好再有个轮播图。 新闻…

我的 2023 秋招总结,拿到了大厂offer

2023秋招小结 前言 & 介绍 作为2024年毕业的学生&#xff0c;在2023年也就是今年秋招。 现在秋招快结束了&#xff0c;人生可能没有几次秋招的机会&#xff08;应该就一次&#xff0c;最多两次吧哈哈&#xff09;&#xff0c;也有一点感悟&#xff0c;所以小小总结一下。…

基于SSM的项目管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…