C++之String类模拟实现(下)

news2024/10/25 15:33:20

片头

哈喽~小伙伴们,在上一篇中,我们讲解了C++的string类的相关函数,这一章中,我们将继续深入学习string类函数,准备好了吗?咱们开始咯~


五、对内容进行修改

⑤insert函数

在指定位置插入字符或者字符串

函数声明

		//insert函数
		void insert(size_t pos, char ch);//插入字符

 函数定义

	//insert函数,插入字符
	void string::insert(size_t pos, char ch) {
		//严格控制pos的取值范围,避免越界
		assert(pos <= _size);

		//如果_capacity和_size相等,需要扩容
		if (_capacity == _size) {
			size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newCapacity);
		}

		//end从'\0'的位置开始
		size_t end = _size;
		while (end >= pos) {
			_str[end + 1] = _str[end];
			//最后一次: pos+1 = pos;
			--end;
			//每执行完一次,end向前挪动一位
		}
		_str[pos] = ch;//将pos位置放入ch字符
		++_size;	   //_size更新
	}

这里的end初始指向末尾_size,也就是'\0'的位置,将数据不断往后挪动,直到腾出pos位置。 

测试一下:

但是如果这时,我们往下标为0的位置头插'D',发现编译器崩溃,为啥呢?

因为pos此时为0, 如果pos大于等于0,则进入循环,若pos小于0,就结束。end是size_t类型,就不会小于0。如果end减到-1,再转成无符号类型size_t,无符号整型-1是一个很大很大的数,必然会越界。所以,我们将end的类型修改为int。

int end = _size;

 

但是此时的pos仍然是size_t类型,在一个操作符两边的操作数,如果它们类型不一样,它们就会发生隐式类型转换,当有符号类型遇到无符号类型,有符号类型就会隐式类型转换成无符号类型。

解决方法1:将pos强转成int类型

        int end = _size;                 //end从'\0'的位置开始
		while (end >= (int)pos)          //将pos强转成int,避免类型不同
        {       
			_str[end + 1] = _str[end];   //最后一次:_str[pos+1] = _str[pos]
			--end;
		}
		_str[pos] = ch;
		++_size;	  
	}

解决方法2:end仍然是size_t类型,但是起始位置是_size+1  

		size_t end = _size + 1;			//end的起始位置是_size的下一个位置
		while (end > pos)				//end>pos位置继续,end==pos就结束
		{
			_str[end] = _str[end - 1];	//最后一次: 下标为0的元素挪到下标为1的位置
			end--;
		}
		_str[pos] = ch;
		++_size;	

我们 初始指向'\0'的下一个位置,最终当end和pos值相等时跳出循环,完成插入与对成员变量的修改。


接下来,我们用insert函数实现插入字符串

函数声明

	void insert(size_t pos, const char* str);//插入字符串

函数定义

//方法1:	
    void string::insert(size_t pos, const char* str) {
        //严格控制pos的取值范围,避免越界
		assert(pos <= _size);

		int len = strlen(str);			//新字符串的长度(不包括'\0')
		if (_size + len > _capacity)	//检查是否需要扩容
		{
			reserve(_size + len);
		}
		int end = _size;				//end的起始位置_size
		while (end >= (int)pos)			//判断end是否满足循环条件
		{
			_str[end + len] = _str[end];
			end--;
		}
		memcpy(_str + pos, str, len);	//使用memcpy函数,拷贝len个字符,不包括'\0'
		_size += len;					//_size更新
	}

诶,为啥这里不能使用strcpy函数了呢?

因为strcpy函数会把插入字符串的'\0'一起拷贝过去,改变了原字符串的内容,这样就不行的。

中间的逻辑也可以这样进行修改,保证结果正确:

		int end = _size + len;				  //将end设定为_size+len
		while (end > pos + len - 1)	          //判断end是否满足循环条件
		{
			_str[end] = _str[end - len];
			end--;
		}
		memcpy(_str + pos, str, len);		 //使用memcpy函数,拷贝len个字符,不包括'\0'
		_size += len;						 //_size更新

 测试一下:

实现了insert函数后,我们就可以在push_back函数和append函数复用它们

//尾插一个字符
	void string::push_back(char ch) {
		insert(_size, ch);	//在_size位置,插入一个字符ch
	}

//尾插一个字符串
	void string::append(const char* str) {
		insert(_size, str);//在_size位置,插入一个字符串
	}

⑥erase函数

函数声明

        //erase函数
		void erase(size_t pos = 0 , size_t len = npos);

从pos位置开始,删除len个字符(pos默认为0,len默认为npos)

这里的pos不能等于_size,因为我们不能删去'\0',npos为整数-1,这里我们需要自己在类中定义。

public:
    static const int npos;

在外部进行初始化

const int string::npos = -1;

函数定义

//erase函数
	void string::erase(size_t pos, size_t len = npos) {
		assert(pos < _size);				 //严格控制pos的有效区间

		//len大于后面字符个数时,有多少删多少
		if (len == npos || len >= _size - pos) 
		{
			_str[pos] = '\0';				//将pos的位置改为'\0'
			_size = pos;					//有效元素的个数为pos个
		}
		else
		{
		//len小于后面的字符个数
			strcpy(_str + pos, _str + pos + len);//将后面的字符拷贝到pos位置
			_size -= len;						 //有效字符个数更新
		}
	}

运行一下:


⑦find查找字符或字符串

函数声明

		//find函数
		size_t find(char ch, size_t pos = 0);			//查找字符
		size_t find(const char* sub, size_t pos = 0);	//查找字符串

函数定义

//从pos位置开始,查找字符
	size_t string::find(char ch, size_t pos) {
		for (int i = pos; i < _size; i++) {
			if (_str[i] == ch) {
				return i;
			}
		}
		return npos;
	}

//从pos位置开始,查找字符串
	size_t string::find(const char* sub, size_t pos) {
		char* p = strstr(_str + pos, sub);
		return p - _str;//返回的就是下标
	}

(1)查找字符:我们给了缺省值npos,如果不赋值,默认从起始位置开始找,遍历数组;若找到目标字符,则返回下标;若找不到,则返回npos 

(2)查找目标字符串:我们调用字符串函数strstr,如果找到返回目标字符串起始位置的指针;若没找到,则返回空指针。我们再加一个判断条件,如果不为空,指针相减即为目标字符串起始位置的下标(第一个元素的下标);若为空,则找不到,返回npos。

因此,我们还可以把查找字符串函数再改进一下:

//从pos位置开始,查找字符串
	size_t string::find(const char* sub, size_t pos) {
		//p指针表示找到目标字符串的地址
		char* p = strstr(_str + pos, sub);

		//如果p指针存在,直接返回目标字符串的下标
		if (p) {
			return p - _str;
		}
		else {
		//如果p指针不存在,返回npos
			return npos;
		}
	}

运行一下


⑧substr函数

substr函数用于从字符串中提取一个子字符串

函数声明

		//substr函数
		string substr(size_t pos = 0, size_t len = npos);

函数定义

//substr函数

//定义方法1:
	string string::substr(size_t pos, size_t len) {
		string sub;
		if (len == npos || 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;
	}

我们直接在新的sub字符串尾插字符

还有另外一种定义方法:

//substr函数

//定义方法2:
	string string::substr(size_t pos, size_t len) {
		if (len == npos || len >= _size - pos) {
		string  sub(_str + pos);
			return sub;
		}
		else {
			string sub;
			sub.reserve(len);//至少开len个字符的空间
			for (int i = 0; i < len; i++) //执行拷贝的次数
			{
				sub += _str[pos + i];//从pos位置开始,拷贝len个字符
			}
			return sub;
		}
	}

⑨swap函数

 函数声明

		//swap函数
		void swap(string& s);

函数定义

//swap函数
	void string::swap(string& s) {
		//使用std库里面的swap函数
		std::swap(_str, s._str);		
		std::swap(_capacity, s._capacity);
		std::swap(_size, s._size);
	}

运行一下


 六、重载函数

①operator=赋值函数

函数声明

		//operator=赋值函数
		string& operator=(const string& s);

函数定义

//operator=赋值函数
	string& string::operator=(const string& s) {
		char* temp = new char[s._capacity + 1];		//多开一个空间,存放'\0'
		strcpy(temp, s._str);						//数据拷贝
		delete[] _str;								//释放原来的空间
			
		_str = temp;								//将指针指向新地址
		_size = s._size;							//_size更新
		_capacity = s._capacity;					//_capacity更新

		return *this;								//返回*this
	}

运行一下

但是如果是自己赋值给自己,那么就不需要释放原来的旧空间。因此,我们需要将代码改进

//operator=赋值函数
	string& string::operator=(const string& s) {
		if (this != &s) //如果不是自身赋值
		{
			char* temp = new char[s._capacity + 1];		//多开一个空间,存放'\0'
			strcpy(temp, s._str);						//数据拷贝
			delete[] _str;								//释放原来的空间

			_str = temp;								//将指针指向新地址
			_size = s._size;							//_size更新
			_capacity = s._capacity;					//_capacity更新
		}

		return *this;								//返回*this
	}
}

开辟一块新空间,将原内容拷贝到新空间中并释放原来的空间,然后更改指针指针指向和成员变量,最后返回*this 


②operator==等几个比较函数

函数声明

		//比较函数
		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;

函数定义

   //比较2个字符串元素的ASCII码值
     bool string::operator<(const string& s)const 
    {
		return strcmp(_str, s._str) < 0;
	}

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

我们可以先写出2个简单的函数,剩下的情况可以复用上面的函数

其余的函数如下:

//比较函数
	 bool string::operator<=(const string& s)const 
    {
		return *this < s || *this == s;
	}

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

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

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

③流插入和流提取

注意:流插入和流提取不能写在string类里面。比如:cout<<d1,这样只能传递2个参数,如果放到了成员函数,就会多一个this指针,这个参数就不方便传递。因此,我们把它写在类外面。

函数声明

	//流插入和流提取
	istream& operator>>(istream& is, string& str);
	ostream& operator<<(ostream& os, const string& str);

函数定义

//流提取
	ostream& operator<<(ostream& os, const string& str) {
		for (size_t i = 0; i < str.size(); i++) {
			os << str[i];
		}
		return os;
	}

我们不需要将上述函数设置为友元,因为我们没有访问成员变量,是通过迭代器来实现的

//流插入

//字符串清理
	void string::clear() {
		_str[0] = '\0';
		_size = 0;
	}

	istream& operator>>(istream& is, string& str) {
		str.clear();
		char ch;
		ch = is.get();
		char buff[128];	//缓冲数组
		size_t i = 0;
		while (ch != ' ' && ch != '\n') //读字符读不到空格
		{
			buff[i++] = ch;
			if (i == 127) {
				buff[127] = '\0';
				str += buff;
				i = 0;
			}
			//s += ch;
			ch = is.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}

输入是对内容的覆盖,所以我们首先实现一个clear函数来清空字符串,原理很简单:

//字符串清理
	void string::clear() {
		_str[0] = '\0';
		_size = 0;
	}

只需要修改_size并设置'\0'即可

注意,我们cin输入是读不到空格和换行符的。

其中,ch = is.get();从输入流中读取单个字符

我们这里创造了一个缓冲数组char buff[128];,来简单进行讲解一下:

这个数组功能就是存储字符的内容,如果数组满了,则直接进行+=将整个数组内容尾插到字符串中,并更新索引,其主要特点就是,避免了我们自己为字符串s不断扩容降低了效率,并且可能会造成空间的浪费

与cin不同的是,getline可以读取空格,所以我们只需要修改循环条件即可

istream& getline(istream& in, string& s) {
		s.clear();
		char ch;
		ch = in.get();
		char buff[128];
		size_t i = 0;
		while (ch != '\0') {
			buff[i++] = ch;
			if (i == 127) {
				buff[127] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			ch = in.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

七、完整代码 

string.h文件

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

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;//将char*重命名为iterator
		typedef const char* const_iterator;

		//非const版本的iterator
		iterator begin();	   //提供iterator begin()函数
		iterator end();		   //提供iterator end()函数

		//const版本的iterator
		const_iterator begin() const;	
		const_iterator end() const;

		//string();//无参构造
		string(const char* str = "");	//全缺省的构造函数
		~string();						//析构函数

		
		string(const string& s);		//拷贝构造函数

		const char* c_str() const;		//c_str函数
		size_t size() const;			//size()函数
		size_t capacity() const;		//capacity()函数

		//非const版本
		char& operator[](size_t pos);	//operator[]函数
		//const版本
		const char& operator[](size_t pos)const;

		//预留空间
		void reserve(size_t n);
		//尾插一个字符
		void push_back(char ch);
		//尾插一个字符串
		void append(const char* str);

		//operator+=函数可以构成重载,函数名相同,参数不同
		string& operator+=(char ch);
		string& operator +=(const char* str);

		//insert函数
		void insert(size_t pos, char ch);//插入字符
		void insert(size_t pos, const char* str);//插入字符串

		//erase函数
		void erase(size_t pos = 0, size_t len = npos);

		//find函数
		size_t find(char ch, size_t pos = 0);			//查找字符
		size_t find(const char* sub, size_t pos = 0);	//查找字符串

		//substr函数
		string substr(size_t pos = 0, size_t len = npos);

		//operator=赋值函数
		string& operator=(const string& s);

		//swap函数
		void swap(string& s1);

		//比较函数
		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;

		//字符串清理
		void clear();

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

		//静态成员变量npos在类里面声明
		const static size_t npos;
	};

	//流插入和流提取
	istream& operator>>(istream& is, string& str);
	ostream& operator<<(ostream& os, const string& str);

	//getline函数模拟流输入
	istream& getline(istream& in, string& s);
}

 string.cpp文件

#include"string.h"
namespace bit {
	//静态成员变量npos在类外定义
	const size_t string::npos = -1;


	//指定类域,在类里面声明,在类外面定义

	//begin()函数返回的是第一个元素
	string::iterator string::begin() {
		return _str;
	}
	//end()函数返回的是最后一个元素的下一个位置
	string::iterator string::end() {
		return _str + _size;
	}

	string::const_iterator string::begin() const {
		return _str;
	}
	string::const_iterator string::end() const {
		return _str + _size;
	}


	//无参构造
//string::string() {
//	//_str = new char('\0');
//	_str = new char[1]{'\0'};
//	_size = 0;
//	_capacity = 0;
//}	

	//构造函数
	string::string(const char* str)
		//将_size放在初始化列表里面
		:_size(strlen(str))
	{
		//_str和_capacity放到函数体里面
		_str = (new char[_size + 1]);
		_capacity = _size;
		strcpy(_str, str);
	}

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

	//拷贝构造函数
	string::string(const string& s) {
		_str = new char[s._capacity + 1];	//多开1个空间,存放'\0'
		strcpy(_str, s._str);				//拷贝数据
		_capacity = s._capacity;			//设置容量
		_size = s._size;					//设置有效数据的个数
	}


	//c_str函数
	const char* string::c_str() const {
		return _str;
	}

	//size()函数
	size_t string::size() const {
		return _size;
	}

	//capacity()函数
	size_t string::capacity() const {
		return _capacity;
	}

	//operator[]函数
	char& string::operator[](size_t pos) {
		assert(pos < _size);//严格控制pos的有效区间
		return _str[pos];
	}
	//const版本
	const char& string::operator[](size_t pos)const {
		assert(pos < _size);
		return _str[pos];
	}


	void string::reserve(size_t n) {
		//如果n大于当前的_capacity,需要扩容
		if (n > _capacity) {
			//开n+1个空间,多开1个空间预留给'\0'
			//'\0'是不包括在_capacity里面的
			char* temp = new char[n + 1];
			strcpy(temp, _str);				//拷贝数据
			delete[] _str;					//释放旧空间

			_str = temp;					//将新的地址赋给_str
			_capacity = n;					//_capacity为n,代表n个有效数据
		}
	}

	//尾插一个字符
	void string::push_back(char ch) {
		如果_capacity == _size,说明空间为0或者空间满了,需要扩容
		//if (_capacity == _size) {
		//	size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
		//	reserve(newCapacity);
		//}
		//_str[_size] = ch;			//新的字符ch插入到原来存放'\0'的位置
		//_str[_size + 1] = '\0';		//'\0'就存放到下一个位置
		//_size++;					//_size更新

		insert(_size, ch);	//在_size位置,插入一个字符ch
	}

	//尾插一个字符串
	void string::append(const char* str) {
		获取str新字符串的长度
		//size_t len = strlen(str);

		如果_size+len大于原有的capacity,扩容
		//if (_size + len > _capacity) {
		//	reserve(_size + len);
		//}
		strcat(_str, str);//在原来的基础上,尾插str新字符串
		//strcpy(_str+_size, str);//自定义起始位置,从'\0'的位置开始
		//_size += len;			//_size更新

		insert(_size, str);//在_size位置,插入一个字符串
	}

	string& string::operator+=(char ch) {
		push_back(ch);//调用push_back函数
		return *this;
	}
	string& string::operator +=(const char* str) {
		append(str);//调用append函数
		return *this;
	}

	//insert函数
	void string::insert(size_t pos, char ch) {
		//严格控制pos的取值范围,避免越界
		assert(pos <= _size);

		//如果_capacity和_size相等,需要扩容
		if (_capacity == _size) {
			size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newCapacity);
		}

		end从'\0'的位置开始
		//int end = _size;
		将pos强转成int,避免类型不同
		//while (end >= (int)pos) {
		//	_str[end + 1] = _str[end];
		//	//最后一次: pos+1 = pos;
		//	--end;
		//	//每执行完一次,end向前挪动一位
		//}

		size_t end = _size + 1;			//end的起始位置是_size的下一个位置
		while (end > pos)				//end>pos位置继续,end==pos就结束
		{
			_str[end] = _str[end - 1];	//最后一次: 下标为0的元素挪到下标为1的位置
			end--;
		}
		_str[pos] = ch;//将pos位置放入ch字符
		++_size;	   //_size更新
	}
	void string::insert(size_t pos, const char* str) {
		//严格控制pos的取值范围,避免越界
		assert(pos <= _size);

		int len = strlen(str);				//新字符串的长度(不包括'\0')
		if (_size + len > _capacity)		//检查是否需要扩容
		{
			reserve(_size + len);
		}
		//int end = _size;					//end的起始位置_size
		//while (end >= (int)pos)				//判断end是否满足循环条件
		//{
		//	_str[end + len] = _str[end];
		//	end--;
		//}

		int end = _size + len;				  //将end设定为_size+len
		while (end > pos + len - 1)	      //判断end是否满足循环条件
		{
			_str[end] = _str[end - len];
			end--;
		}

		memcpy(_str + pos, str, len);		//使用memcpy函数,拷贝len个字符,不包括'\0'
		_size += len;						//_size更新
	}

	//erase函数
	void string::erase(size_t pos, size_t len) {
		assert(pos < _size);				 //严格控制pos的有效区间

		//len大于后面字符个数时,有多少删多少
		if (len == npos || len >= _size - pos) 
		{
			_str[pos] = '\0';				//将pos的位置改为'\0'
			_size = pos;					//有效元素的个数为pos个
		}
		else
		{
		//len小于后面的字符个数
			strcpy(_str + pos, _str + pos + len);//将后面的字符拷贝到pos位置
			_size -= len;						 //有效字符个数更新
		}
	}

	//find函数

	//从pos位置开始,查找字符
	size_t string::find(char ch, size_t pos) {
		for (int i = pos; i < _size; i++) {
			if (_str[i] == ch) {
				return i;
			}
		}
		return npos;
	}

	//从pos位置开始,查找字符串
	size_t string::find(const char* sub, size_t pos) {
		//char* p = strstr(_str + pos, sub);
		//return p - _str;//返回的就是下标

		//p指针表示找到目标字符串的地址
		char* p = strstr(_str + pos, sub);

		//如果p指针存在,直接返回目标字符串的下标
		if (p) {
			return p - _str;
		}
		else {
		//如果p指针不存在,返回npos
			return npos;
		}
	}

	//substr函数
	string string::substr(size_t pos, size_t len) {
		string sub;
		if (len == npos || 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;
	}

	//substr函数
	//string string::substr(size_t pos, size_t len) {
	//	if (len == npos || len >= _size - pos) {
	//	string  sub(_str + pos);
	//		return sub;
	//	}
	//	else {
	//		string sub;
	//		sub.reserve(len);//至少开len个字符的空间
	//		for (int i = 0; i < len; i++) //执行拷贝的次数
	//		{
	//			sub += _str[pos + i];//从pos位置开始,拷贝len个字符
	//		}
	//		return sub;
	//	}
	//}

	//operator=赋值函数
	string& string::operator=(const string& s) {
		if (this != &s) //如果不是自身赋值
		{
			char* temp = new char[s._capacity + 1];		//多开一个空间,存放'\0'
			strcpy(temp, s._str);						//数据拷贝
			delete[] _str;								//释放原来的空间

			_str = temp;								//将指针指向新地址
			_size = s._size;							//_size更新
			_capacity = s._capacity;					//_capacity更新
		}

		return *this;								//返回*this
	}

	//swap函数
	void string::swap(string& s) {
		//使用std库里面的swap函数
		std::swap(_str, s._str);		
		std::swap(_capacity, s._capacity);
		std::swap(_size, s._size);
	}

	//比较函数
	bool string::operator<(const string& s)const {
		return strcmp(_str, s._str) < 0;
	}
	bool string::operator<=(const string& s)const {
		return *this < s || *this == s;
	}
	bool string::operator>(const string& s)const {
		return !(*this <= s);
	}
	bool string::operator>=(const string& s)const {
		return !(*this < s);
	}
	bool string::operator==(const string& s)const {
		return strcmp(_str, s._str) == 0;
	}
	bool string::operator!=(const string& s)const {
		return !(*this == s);
	}

	//字符串清理
	void string::clear() {
		_str[0] = '\0';
		_size = 0;
	}

	//流插入
	istream& operator>>(istream& is, string& str) {
		str.clear();
		char ch;
		ch = is.get();
		char buff[128];	//缓冲数组
		size_t i = 0;
		while (ch != ' ' && ch != '\n') //读字符读不到空格
		{
			buff[i++] = ch;
			if (i == 127) {
				buff[127] = '\0';
				str += buff;
				i = 0;
			}
			//s += ch;
			ch = is.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}

	//流提取
	ostream& operator<<(ostream& os, const string& str) {
		for (size_t i = 0; i < str.size(); i++) {
			os << str[i];
		}
		return os;
	}

	istream& getline(istream& in, string& s) {
		s.clear();
		char ch;
		ch = in.get();
		char buff[128];
		size_t i = 0;
		while (ch != '\0') {
			buff[i++] = ch;
			if (i == 127) {
				buff[127] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			ch = in.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

}

片尾

今天,在上一篇的基础上,我们深入学习了string类函数的模拟实现,希望看完这篇文章能对友友们有所帮助!!!

点赞收藏加关注!!!

谢谢大家!!!

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

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

相关文章

基于Raspberry Pi人脸识别自动门

人脸识别自动门 简介 在当今数字化时代&#xff0c;智能家居安全变得越来越重要。今天&#xff0c;我要向大家介绍一个结合了安全性与便利性的项目——人脸识别自动门。这个项目通过在门上实施基于面部识别的高级安全系统&#xff0c;使用摄像头验证房主的面部&#xff0c;自…

重学SpringBoot3-集成Spring Boot Actuator

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Spring Boot Actuator 1. 什么是 Spring Boot Actuator&#xff1f;2. Spring Boot Actuator 的核心功能3. Spring Boot 3 中集成 Actuator3.1 添加…

ElasticSearch是什么?

1.概述 Elasticsearch 是一个基于 Apache Lucene 构建的开源分布式搜索引擎和分析引擎。它专为云计算环境设计&#xff0c;提供了一个分布式的、高可用的实时分析和搜索平台。Elasticsearch 可以处理大量数据&#xff0c;并且具备横向扩展能力&#xff0c;能够通过增加更多的硬…

2014年国赛高教杯数学建模C题生猪养殖场的经营管理解题全过程文档及程序

2014年国赛高教杯数学建模 C题 生猪养殖场的经营管理 某养猪场最多能养10000头猪&#xff0c;该养猪场利用自己的种猪进行繁育。养猪的一般过程是&#xff1a;母猪配种后怀孕约114天产下乳猪&#xff0c;经过哺乳期后乳猪成为小猪。小猪的一部分将被选为种猪&#xff08;其中公…

20240727 影石 笔试

文章目录 1、选择题1.11.21.31.41.51.61.71.81.91.10 2、简答题2.12.22.32.42.52.62.72.8 3、编程题3.1 岗位&#xff1a;云台嵌入式工程师-2025校招 题型&#xff1a;10 道选择题&#xff0c;8 道简答题&#xff0c;1 道编程题 1、选择题 1.1 【多选】以下关于DMA的描述哪些…

Pytest中fixture含返回值时如何隐式自动应用?

在我们使用 Pytest 框架进行自动化测试时&#xff0c;强大的 fixture 夹具为框架的灵活应用提供了极大的便利。比如我们可以利用 fixture 的autouse属性&#xff0c;使它在测试方法的不同范围层级上自动生效。但如果要引用fixture的返回&#xff0c;我们通常还是要明确指定&…

FMEA 在智能制造中的应用与挑战_SunFMEA

【大家好&#xff0c;我是唐Sun&#xff0c;唐Sun的唐&#xff0c;唐Sun的Sun。一站式数智工厂解决方案服务商】 FMEA&#xff08;失效模式与影响分析&#xff09;在智能制造中具有重要的应用价值&#xff0c;但同时也面临着一系列的挑战。 应用&#xff1a; 产品设计优化 在…

react18+react-transition-group实现路由切换过度

效果如下 官网安装对应的插件 创建对应的样式 .fade-enter {opacity: 0; } .fade-exit {opacity: 1; } .fade-enter-active {opacity: 1; } .fade-exit-active {opacity: 0; } .fade-enter-active, .fade-exit-active {transition: opacity 500ms; }const location useLoca…

WSL(Windows Subsystem for Linux)——简单的双系统开发

文章目录 WSLWSL的作用WSL的使用WSL的安装挂载磁盘的作用安装linux发行版wsl下载mysql&#xff0c;mongodb&#xff0c;redis WSL 前言&#xff1a;本人由于在开发中需要linux环境&#xff0c;同时还想要直接在Windows下开发&#xff0c;来提升开发效率&#xff0c;随即简单学…

【问题分析】使用gperftools分析排查内存问题

背景 当程序长时间允许时(压测、服务器程序)&#xff0c;就会面临更大的挑战&#xff0c;其中内存泄漏就是一类典型的问题&#xff0c;内存泄漏往往不易发现&#xff0c;导致的现象更是千奇百怪&#xff0c;本文主要介绍如何借助gperftools分析一个模块的内存泄漏 案例代码 …

SpringBoot框架在高校竞赛管理中的创新应用

3系统分析 3.1可行性分析 通过对本高校学科竞赛平台实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本高校学科竞赛平台采用SSM框架&#xff0c;JAVA作为开发语…

编译/引导EDK2 树莓派4

格蠹的幽兰代码本(RK3588)支持UEFI启动&#xff0c;在阅读RK3588代码的时候发现EDK2也对树莓派系列进行了支持。经过一番尝试&#xff0c;借助幽兰&#xff0c;我也在树莓派上bringup EFI bios(只能引导到Bios setup界面&#xff0c;不知道如何安装OS)&#xff0c;在此记录SOP。…

1.Label Studio 介绍

Label Studio 介绍 文章目录 Label Studio 介绍前言一、安装介绍二、Run with Docker Compose1、WSL2安装2、Docker Desktop安装3、Label Studio安装&#xff08;第二种方法 Run with Docker Compose &#xff09; 三、Install for local development1.下载源码2.安装poetry3.安…

YOLO11改进 | 注意力机制 | 用于增强小目标感受野的RFEM

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 近年来&#xff0c;基于深度学习的人脸检…

【计算机网络】计算机网络相关术语

文章目录 NAT概述NAT的基本概念NAT的工作原理1. **基本NAT&#xff08;静态NAT&#xff09;**2. **动态NAT**3. **NAPT&#xff08;网络地址端口转换&#xff0c;也称为PAT&#xff09;** 底层实现原理1. **数据包处理**2. **转换表**3. **超时机制** NAT的优点NAT的缺点总结 P…

vue3 高德地图标注(飞线,呼吸点)效果

装下这两个 npm 忘了具体命令了&#xff0c;百度一下就行 “loca”: “^1.0.1”, “amap/amap-jsapi-loader”: “^1.0.1”, <template><div id"map" style"width: 100%;height: 100%;"></div> </template><script setup> …

linux 下 verilog 简明开发环境附简单实例

author: hjjdebug date: 2024年 10月 12日 星期六 10:34:13 CST descripton: linux 下 verilog 简明开发环境附简单实例 甲: 安装软件 1. sudo apt install iverilog 该包verilog 源代码的编译器iverilog&#xff0c;其输出是可执行的仿真文件格式vvp格式 它可以检查源代码中…

ubuntu20.4环境下gcc-aarch64交叉编译器的安装

交叉编译器&#xff08;Linux环境&#xff09;arm gcc 8.3一共有5个版本&#xff0c;常用的有4个版本&#xff08;另外一个为大端linux版本&#xff09;&#xff0c;分别是32bit裸机版本&#xff08;arm-eabi&#xff09;、64bit裸机版本&#xff08;aarch64-elf&#xff09;、…

4. 单例模式线程安全问题--是否加锁

单例模式线程安全问题--是否加锁 是否加锁问题指什么&#xff1f;解决多线程并发来带的问题继承MonoBehaviour的单例模式不继承MonoBehaviour的单例模式 总结 是否加锁问题指什么&#xff1f; 如果程序当中存在多线程&#xff0c;我们需要考虑当多个线程同时访问同一个内存空间…

【Java】面向UDP接口的网络编程

【Java】面向UDP接口的网络编程 一. 基本通信模型二. APIDatagramSocketDatagramPacket 三. 回显服务器/客户端示例服务器客户端总结 一. 基本通信模型 UDP协议是面向数据报的&#xff0c;因此此处要构建数据报(Datagram)在进行发送。 二. API DatagramSocket DatagramSocke…