【STL专题】模拟实现std::string,深入解析内部实现机制:从大小到运算符重载的探索之旅【万字详解】

 欢迎来到 CILMY23的博客

🏆本篇主题为:模拟实现std::string,深入解析内部实现机制:从大小到运算符重载的探索之旅【万字详解】

🏆个人主页:CILMY23-CSDN博客

🏆系列专栏:Python | C++ | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算法专题 | 代码训练营

🏆感谢观看,支持的可以给个一键三连,点赞收藏+评论。如果你觉得有帮助,还可以点点关注


目录

 模拟实现string

一、基本属性

二、string的构造函数和析构函数

💫 构造函数

💫 析构函数 

三、string的遍历

💫 for + []

💫 迭代器iterator和范围for 

四、增删查改

💫 增加

💦💦 push_back

💦💦 reserve

💦💦 append

💦💦 +=运算符重载

💫 插入

🍀🍀一个字符的插入 

🍃中间插入

🍃尾插

🍃头插

🍀🍀一个字符串的插入 

💫 删除

                 ☘️☘️对pos进行检查

☘️☘️ pos位置后的全删除

☘️☘️只删除一部分

💫 resize 

💫 swap 

💫 查找---find和substr 

☘️☘️ find

☘️☘️ substr 

5.运算符重载

💫 赋值运算符和拷贝构造函数 

🌍🌍 拷贝构造函数

🌍🌍赋值运算符重载

6.特殊接口

💫 C字符串-- c_str

7. 关系比较

8.流插入和流提取

💫 流插入

💫 流提取

9. 优化 

💫 getline

💫 添加const 


前言

在上一篇 【STL专题】深入探索C++之std::string:不止于字符串【万字详解】,当中我们了解了string的基本用法和一些基本属性,这些东西可以在我们的文档库查询string - C++ Reference (cplusplus.com)

 今天我们就来模拟实现一些string的成员函数,以及一些接口。

我们大致的思维导图如下:

接下来我们就仿照这个思维导图一步步实现我们的string,首先文件配置如下:

我们的这个类,被我们封装在命名空间中,这个命名空间可以自己设定名字,我的如下:

准备好这些后,我们就开始吧 


 模拟实现string

一、基本属性

这一块和顺序表类似,string容器我们要知道size,即有效字符的长度,_size 指向 '\0',我们要知道容量,即顺序表中的空间大小,以及一个开始的头指针,char * str。另外,我们还会用到npos这个静态成员变量。 如果有不懂的友友,可以再回顾一下上一篇中,我是如何介绍npos的,我们说它是一个size_t (无符号整数)的最大值。

代码如下所示,这样我们就完成了string的基本设置。 

namespace CILMY
{
	class string
	{
	public:

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
			static const int npos;
	};
	const int string::npos = -1;
}

二、string的构造函数和析构函数

如果有忘记的部分,可以点击以下链接,回顾一下构造函数和析构函数.

【C++】C++中的构造函数和析构函数详解icon-default.png?t=N7T8https://blog.csdn.net/sobercq/article/details/138132643

💫 构造函数

 在写构造函数的时候我们更喜欢写全缺省这种的构造函数,方便简洁。

当我们想初始化一个string的时候我们会遇到以下两种情况:

  1. 我们会用一个常量字符串传递
  2. 我们可能什么都不传递,就只写string s.

我们先拿第一种情况举例

假设传进来一个常量字符串,我们可以先获取长度,让空间也和长度相等。问题又来了,我们应该如何把str存进去呢,这时候我们就要开辟空间了,然后我们再利用strcpy给它拷贝进去。注意:开辟空间的时候要+1

 那如果是第二种情况呢?

那如果外界什么都不传递,那还怎么拷贝呢?

其实我们可以用缺省值来解决这个问题,那缺省值又设定什么呢?实际上,C语言中的字符串在末尾会默认认定一个"\0",所以我们可以考虑把这个给拷贝进来。

 问题又来了,当我们拷贝的时候,我们知道strcpy遇到"\0"就停止了,所以实际上我们什么都没拷贝,只是在新空间的开头默认放了一个"\0",这也是为什么我刚刚把capacity+1的原因。

 这样我们的构造函数就实现完成了。(感兴趣的大伙可以自己写测试代码测试一下,我会写些函数放在文章末尾。)

string(const char* str = "")
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

💫 析构函数 

 析构函数的实现比较容易,这里就不多讲解啦.(总共三步走,delete释放空间,指针置空,长度和空间都为0)

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

三、string的遍历

那初始化string后,我们还想看看我们的里面都存了啥,这时候我们就要遍历我们的string了,遍历一共有三种方式:

如果我们想遍历字符串,我们需要再写一个函数来获取长度。

//获取长度
size_t size() const
{
	return _size;
}

💫 for + []

 如果我们想实现string[]就必须重载运算符[].

注意一点:防止越界!我们可以用assert来检查是否越界.

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

for循环的遍历也很简单,主要是因为要重载运算符[],使用起来和数组一样,真的是方便快捷. 

for (int i = 0; i < s1.size(); i++)
{
	cout << s1[i] << " ";
}
cout << endl;

💫 迭代器iterator和范围for 

迭代器到底是什么?我们说它像指针,但不一定是指针,有可能是指针,今天我们就写string是指针的版本。注意:不是所有的迭代器都是指针,这里的迭代器可以用指针来实现。

将char* 用typedef重命名成iterator。 

typedef char* iterator;
iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

那范围for呢?

范围for语法解释很牛逼

自动取数据给ch,自动++,编译底层的时候,其实还是一个迭代器。

我们可以通过反汇编看: 

到这,我们的三种遍历方式就验证完了

四、增删查改

💫 增加

💦💦 push_back

 这一块和顺序表差不多,首先在添加之前我们肯定要扩容,这就不得不提及我们上一篇中提到的reserve了

💦💦 reserve

那我们如何实现它呢?

我们来看以下原理图:

我画的原理图有点小乱,我来解释解释

首先开辟一个新空间,接着把旧空间的字符拷贝进新空间,释放旧空间,让旧空间指针指向新空间。最后让_capacity = n 即可。

特别注意:

reserve扩容要给'\0'留一个位置哦

这一块大致和顺序表类似。只是我们用上了new和delete。

那什么时候要扩容?

就是当我们空间不够的时候要扩容,故当n > capacity的时候,我们就要扩容了,一般我们可以按照1.5倍扩容,或者2倍扩容。

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

尾插过程: 

 这部分比较简单,我就不多阐述了,上代码!

push_back 代码

//push_back
void push_back(char ch)
{
	//扩容
	//当空间为0的时候,我们开4个空间
	//空间不为0,按照2倍扩容
	//push_back 是尾插
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}

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

💦💦 append

  append 是 string 的一个成员函数,用于向字符串的末尾添加内容。

 我们拿个例子看看就知道了,就如我以下所示,我们要知道尾插进来字符串的长度,知道长度后,才好插入,在原来的空间给它留足位置,拷贝进去。

这样我们的append就写好了。 

//append
void append(const char* ch)
{
	size_t len = strlen(ch);
	if (_size + len >= _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	strcpy(_str + _size, ch);
	_size = _size + len;
}

💦💦 +=运算符重载

最经常使用的,肯定是+=了;

这里我们直接复用append和push_back即可。

//+= 一个字符
string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}
//+= 一个字符串
string& operator+=(const char* ch)
{
	append(ch);
	return *this;
}

💫 插入

 插入分两种情况,但都是在pos位置插入,要么插入一个字符,要么插入一个字符串。

首先我们来看插入一个字符的情况。

🍀🍀一个字符的插入 

我们要挪动数据,给pos位置留出一个空间出来。

🍃中间插入

🍃尾插

尾插的过程和中间插入差不多

 

到这我们的代码就差不多了。 

//insert 插入一个字符
void insert(size_t pos, char ch)
{
	//对pos进行检查
	assert(pos <= _size);

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

	//挪动数据
	size_t end = _size + 1;
	while (end - 1 >= pos)
	{

		_str[end] = _str[end - 1];
		end--;
	}

	_str[end] = ch;
	_size++;
}
🍃头插

 我们信誓旦旦的上测试后发现,头插崩溃了

 看了半天没看懂,直接上调试-->,我们发现它一直在挪动数据,问题来了

为什么一直在挪动数据?

其实,这个二进制码有关,我们知道无符号整数,它一直都是>=0的,(不明白的uu可以看链接) ,这就导致一个问题,这个循环永远都是>=pos的,所以我们要把类型都转换成int。那只变一个行不行呢?

答案是也不行

因为一个运算符两边的操作数如果类型不同,会发生一个现象,类型提升,一般是范围小的向范围大的提升。无符号整数的范围更大,因此都转变成了size_t

一个字符的插入代码 

//insert 插入一个字符
void insert(size_t pos, char ch)
{
	//对pos进行检查
	assert(pos <= _size);

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

	//挪动数据
	int end = _size + 1;
	while (end - 1 >= (int)pos)
	{

		_str[end] = _str[end - 1];
		end--;
	}

	_str[end] = ch;
	_size++;
}

这样就解决了,我们的三种情况,头插,尾插,中间插入。

🍀🍀一个字符串的插入 

大部分过程都和上述一致,我们可以把前面的代码扣下来进行改造。

我们要插入一个字符串,就要知道这个字符串的长度,预留足够的空间,我们看末尾位置,要预留足够的空间,直接留长度为len即可,然后我们开始挪动数据,我们只要保证end - len 不在pos位置左边即可。

扩容条件不在是_size == capacity 了,而是判断空间够不够我留下len长度的空间。 

 

数据如何拷贝?

这里我们不能再使用strcpy了,而是拷贝固定长度的strncpy,因为strcpy会多放一个'\0'在拷贝位置的末尾。

 一个字符串的插入代码

//insert 插入一个字符串
void insert(size_t pos, const char* ch)
{
	//对pos进行检查
	assert(pos <= _size);

	size_t len = strlen(ch);

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

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

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

对这段代码我们也可以优化一下:

如果我们不想要强制类型转换的话:

//insert 插入一个字符串
void insert(size_t pos, const char* ch)
{
	//对pos进行检查
	assert(pos <= _size);
	size_t len = strlen(ch);
	//扩容
	if (_size >= _capacity - len)
	{
		reserve(_size + len);
	}
	//挪动数据
	int end = _size + len;
	while (end > pos + len - 1)
	{
		_str[end] = _str[end - len];
		end--;
	}

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

 这里的扩容可以按照你想要的方式去设计,我是两倍扩容,当然你可以只扩容len扩容,就如我优化的一样。

其次是实现完insert,append和push_back,可以复用insert,push_back,就是在size这个位置插入一个字符,append就是在size这个位置插入一个字符串.

//push_back
void push_back(char ch)
{
	insert(_size, ch);
}

//append
void append(const char* ch)
{
	insert(_size, ch);
}

💫 删除

上期我们是这么说的: 

 erase还是挺好用的,erase 函数用来删除字符串中的一部分内容,是一个非常有用的成员函数,允许多种不同的用法以适应不同的需求。

erase的删除有两种情况

1.len == npos,len + pos >= _size意味着全部删除

2.指定位置删除。

☘️☘️对pos进行检查

 pos的位置要不要等于size?

答案:不能,因为size指向的是'\0'。

☘️☘️ pos位置后的全删除

这里的删除并非是全部删除,跟顺序表一样,我们只需要把'\0'放在pos位置即可.注意,全部删除的情况还包括,len 的长度加pos位置开始,超过有效字符size的大小。 

但是这里也存在溢出的情况,算是一个小bug,所以我们可以用len >= _size - pos 来表示. 

☘️☘️只删除一部分

 

我们只需要把pos+len位置之后的数据,用strcpy拷贝过来即可。

//erase删除
void erase(size_t pos, size_t len = npos)
{
	//检查
	assert(pos < _size);

	//删除
	if (len == npos || len >= _size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		//挪动数据
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}

}

💫 resize 

 我们可以先来回顾一下resize的用法:

resize重载了两个函数: 

resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字
符个数增多时;resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。

resize分两种情况,1.将元素减少 2.将元素增多

 将元素减少

如果要让元素减少,直接在这个n的位置加个'\0',就相当于减少了字符个数. 

 将元素增加

 那我们就要用c去填充这中间的空白部分,然后在最后n这个位置插入一个'\0';

resize代码: 


void resize(size_t n, char ch = '\0')
{
	if (n <= _size)
	{

		_str[n] = '\0';
		//空间保持不变,有效字符个数减少
		_size = n;
	}
	else
	{
		reserve(n);

		for (int i = _size; i < n; i++)
		{
			_str[i] = ch;
		}

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

💫 swap 

我们的库里面也有一个swap,swap主要就交换,但是下面这个swap是不是效率不高呢?

我们看它的代码是拷贝构造一个c出来,然后去调用拷贝构造函数,一共三次拷贝,然后调用析构函数,释放c。代价太大,所以我们可以自己写一个swap. 

 我们的思路也很简单,直接将对应的变量交换即可.没有必要调用拷贝构造函数去拷贝那么多。

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

💫 查找---find和substr 

☘️☘️ find

回顾上期 find 的用法: 

 如果我们想覆盖字符串,删除,替换,需要找到一个位置的时候,find()就显得极其重要。find 函数用于在字符串内查找子串或字符的第一个出现位置。如果找到了匹配项,它就返回匹配项的下标;如果没有找到,它则返回 npos,这是一个特别定义的常量,表示不存在的位置。

 第一眼是暴力循环,我们直接搞两层循环找就完了。那当然,我们实现底层不这么搞,所以我们先把单个字符的解决了再想找子串的吧。

⛵⛵find 一个字符

 看过程图就很容易理解了,假设我们找到了,我们就返回 i 这个下标,找不到就返回npos

代码: 

size_t find(char ch, size_t pos = 0)
{
    assert(pos < _size);
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return npos;
}

⛵⛵find 找一个子串

找一个子串就可以用我刚刚说的暴力结束,但是这样代码不简洁,我们可以使用 strstr (链接)来解决。 

 过程图如下:

strstr 返回的是一个指针,那指针之间的距离是一个整数,同时如果我们减去的起始位置的指针,那它就是下标位置。

size_t find(const char* sub, size_t pos = 0)
{
	//对pos位置检查
	assert(pos < _size);
	const char* p = strstr(_str + pos, sub);
	if (p)
	{
		return p - _str;
	}
	else
	{
		return npos;
	}
}

☘️☘️ substr 

 回顾上期:

substr 函数用于从字符串中提取一个子串。这个方法非常灵活,允许你指定开始位置和需要提取的子串的长度。如果不指定长度,则默认提取从开始位置到字符串末尾的所有字符。

这个函数会返回一个新的 string 对象,包含从 pos 开始、长度为 len 的子串。如果 pos 是字符串的长度或更大,函数会抛出out_of_range 异常。如果 pos 加上 len 超出了字符串的末尾,那么只会提取到字符串的末尾为止的子串。

 如果 pos 加上 len 超出了字符串的末尾,那么只会提取到字符串的末尾为止的子串。也就是说从pos位置开始后面的字符串都要了。

我们可以防止溢出,写成

 其余情况,就是pos+len < _size,我们就把这个区间的所有的字符串搞进去,直到小于pos + len。

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

5.运算符重载

💫 赋值运算符和拷贝构造函数 

🌍🌍 拷贝构造函数

拷贝构造函数,最经常使用的情景就是用另外一个字符串然后来拷贝一个新的字符串,这里涉及深拷贝,如果有不懂深浅拷贝的,也可以点击链接看看 

拷贝构造函数的大致过程如下:我画的比较简单,理解起来就是,我们要开辟一个空间和s一样的,然后把s当中的字符串拷贝到我们新开辟的空间当中,并且把'\0'加入进去. 

代码: 

//拷贝构造函数
string(const string& s)
{
	char* tmp = new char[s._capacity + 1];
	strcpy(tmp, s._str);

	_str = tmp;
	_size = s._size;
	_capacity = s._capacity;
}

优化:

这里也可以不写tmp.

 拷贝构造函数代码

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

🌍🌍赋值运算符重载

一般用到赋值运算符我们会考虑以下三种情况:

  1. 将已有的string 拷贝给新的 string
  2. 拷贝一个字符串
  3. 拷贝一个字符

 这一块比较简单就不多详解了,大家感兴趣可以自己分析试试看,如果你有什么更好的想法,或者意见,欢迎在评论区阐述。

//赋值运算符重载
//s2 = s1
string& operator=(const string& 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 = "cilmy"
string& operator=(const char* str)
{
	size_t len = strlen(str);

	char* tmp = new char[len + 1];
	strcpy(tmp, str);

	delete[] _str;
	_str = tmp;
	_size = _capacity = len;
	return *this;
}
//s2 = 'c'
string& operator=(char ch)
{
	char* tmp = new char[2];
	delete[] _str;
	_str = tmp;
	_str[0] = ch;
	_size = _capacity = 1;
	_str[1] = '\0';

	return *this;
}

6.特殊接口

💫 C字符串-- c_str

回顾一下:

 c_str() 返回一个指向正规C字符串的指针,常量,即以空字符结束的字符数组。这个方法用于获取一个C风格的字符串版本,通常是为了与需要传统C字符串的C语言API兼容。

所以其实我们只需要返回一个指针即可。

//c_str接口
char* c_str()
{
	return _str;
}

7. 关系比较

这一块的思路跟之前类似,我们可以直接写两个,然后其它复用即可。

有两种思路,

一种是每个字符都遍历比较过去,然后比较大小即可。

二、利用C语言库里的strcmp来比较两个字符串。

strcmp:

strcmp - C++ Reference (cplusplus.com)

首先我们实现 < 和 ==

 这些关系比较都是重载成全局的非成员函数

//关系比较
bool operator> (const string& s, const string& s1)
{
	int tmp = strcmp(s.c_str(), s1.c_str());
	return tmp > 0;
}

那这里会报错,是因为我们不支持const的c字符串

增加一个const的c字符串重载函数即可

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

那这里是,如果比较对了,返回1,比较不对,就返回0.

基本上实现两个关系就其他就可以复用了,比如实现大于和等于,小于和等于,或者小于不等于这样。那我这里是小于,大于,等于都实现了。大家可以按照喜欢的来实现。 

//关系比较
bool operator> (const string& s, const string& s1)
{
	int tmp = strcmp(s.c_str(), s1.c_str());
	return tmp > 0;
}

bool operator== (const string& s, const string& s1)
{
	int tmp = strcmp(s.c_str(), s1.c_str());
	return tmp == 0;
}

bool operator>= (const string& s, const string& s1)
{
	return (s > s1 || s == s1);
}
bool operator< (const string& s, const string& s1)
{
	int tmp = strcmp(s.c_str(), s1.c_str());
	return tmp < 0;

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

8.流插入和流提取

流提取 和 流插入,其实就是和我们使用cout << /(cin >>)在做输入操作/(输出)的时候一样,控制台会先去等待我们输入一个值/(通过去缓冲区中拿取数据,然后将其显示在控制台上) 

💫 流插入

 那流插入是输出到屏幕上。所以这里模拟实现cout

string的流插入比较简单,就是直接把字符遍历打印到屏幕上即可。

//流插入
ostream& operator<<(ostream& out, string& s)
{
	for (auto e : s)
	{
		out << e;
	}

	return out;
}

问题来了,我们之前在实现日期计算器的时候用到了友元,为什么这里不用?

实际上,我们实现日期计算器的时候讲过,如果类里提供函数,像java那样,我们也可以不使用友元,使用友元是为了方便访问私有成员变量,这里我们没有要访问私有成员变量,所以不用友元。

💫 流提取

 流提取的初步思路也是这样,我们直接提取出一个字符,然后给它增加到s当中即可

//流提取
istream& operator>> (istream& in, string& s)
{
	char ch;
	in >> ch;

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

	return in;
}

但是运行后发现,它卡在死循环这里了,这是为什么呢? 

 

这是因为,C++的cin,c语言的scanf取不到换行或者空格,这里就会没有结束,跳不出循环。

那C语言如何解决呢?

c语言用getchar,getc都可以读到

特别注意,c语言和c++的输入输出不混用,它们会各自读到自己的内存缓冲区,也就是C语言有C语言的流读取内存区块,C++也有自己独立的流读取内存区块。

C++中的istream其实是一个类。

istream - C++ Reference (cplusplus.com)

它提供的函数

可以取下任意一个字符。

因此我们的代码可以优化成以下情况:

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

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

		return in;
	}

我们的代码就正式运行成功了。 

这里我们用了一个clear来清空我们的字符,因为尾插是不对的,我们通常都是对一个空string进行流提取。

 

//清空
void clear()
{
	_size = 0;
	_str[_size] = '\0';
}
//这里复用resize(0)也是可以的

9. 优化 

💫 getline

 那我们想提取一行,而不是像上面一个一样只提取一个,我们就可以用getline来实现。

把上述代码抄下来,把条件改了,这个getline就是取一整行的情况了。 

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

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

	return in;
}

getline的另外一种写法,用字符数组做一个中转站来实现。 

//getline
istream& getline(istream& in, string& s)
{
	s.clear();

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

		ch = in.get();
	}

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

	return in;
}

💫 添加const 

1.对iterator我们可以增加静态的成员遍历,因为这受制于权限访问。

//迭代器
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;
}

2. find 我们可以写成const成员。


💡个人总结

1️⃣ 开空间的时候,有时候要多开一个,因为要给'\0'一个位置

2️⃣ 一个运算符两边的操作数如果类型不同,会发生一个现象,类型提升,一般是范围小的向范围大的提升。无符号整数的范围更大,因此都转变成了size_t

3️⃣ 迭代器,不一定是指针,它像指针,有可能不是指针,有可能是指针。vs实现下的string中的迭代器不是指针,但string的迭代器可以用指针简单实现

4️⃣中文字符能否输入?---缓冲区中没有中国两个字,但它是拼出来的,中是两个字符,国是两个字符。这个涉及编码

5️⃣ C++的cin,c语言的scanf取不到换行或者空格,c语言用getchar,getc都可以读到,注意,c语言和c++的输入输出不混用,它们会各自读到自己的内存缓冲区。


🛎️感谢各位同伴的支持,本期string专题就讲解到这啦,下期我们将开始接触新的容器---vector 如果你觉得写的不错的话,可以给个一键三连,点赞,收藏+评论,可以的话还希望点点关注,若有不足,欢迎各位在评论区讨论。    

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 PVE 自签 CA 证书签发新证书

前言 PVE 安装时会自动创建一个有效期 10 年的 CA 证书, 我们可以利用这个 CA 证书给虚拟机中的 Web 应用签发新的 TLS 证书用于提供 HTTPS 服务. 下面以 PVE 虚拟机中通过 Docker 跑的一个 雷池 应用为例进行演示. PVE 证书位置 官方文档: https://pve.proxmox.com/wiki/Pr…

python-多任务编程

2. 多任务编程 2.1 多任务概述 多任务 即操作系统中可以同时运行多个任务。比如我们可以同时挂着qq&#xff0c;听音乐&#xff0c;同时上网浏览网页。这是我们看得到的任务&#xff0c;在系统中还有很多系统任务在执行,现在的操作系统基本都是多任务操作系统&#xff0c;具备…

国产麒麟、UOS在线打开pdf加盖印章

PageOffice支持两种电子印章方案&#xff0c;可实现对Word、Excel、PDF文档加盖PageOffice自带印章或ZoomSeal电子印章&#xff08;全方位保护、防篡改、防伪造&#xff09;。Word和Excel的盖章功能请参考&#xff1a;Word和Excel加盖印章和签字功能 &#xff08;目前只支持win…

【Django+Vue3 线上教育平台项目实战】Celery赋能:优化订单超时处理与自动化定时任务调度

文章目录 前言⭐✨&#x1f4ab;&#x1f525;&#x1f4d6;一、Celery⭐1.基本概念及介绍:✨2.使用步骤&#x1f4ab; 二、订单超时 取消订单&#xff08;Celery&#xff09;&#x1f525;具体实现流程&#x1f4d6; 前言⭐✨&#x1f4ab;&#x1f525;&#x1f4d6; 在构建复…

Mac Electron 应用如何进行签名(signature)和公证(notarization)?

最近很多客户反映&#xff0c;从官网下载的Mac Electron应用打不开&#xff0c;直接报病毒&#xff0c;类似于这种&#xff1a; 这是因为在MacOS 10.14.5之后&#xff0c;如果应用没有在苹果官方平台进行公证notarization(我们可以理解为安装包需要审核&#xff0c;来判断是否存…

Typora 1.5.8 版本安装下载教程 (轻量级 Markdown 编辑器),图文步骤详解,免费领取(软件可激活使用)

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Typora是一款基于Markdown语法的轻量级文本编辑器&#xff0c;它的主要目标是为用户提供一个简洁、高效的写作环境。以下是Typora的一些主要特点和功能&#xff1a; 实时预览&#xff1a;Typora支持实时预览功能&#xff0…

【每日一练】python编写一个简易计算器

程序代码: #循环语句&#xff0c;条件为真所以循环执行 while True: #定义两个数的变量和运算符号 num1 float(input("第一个数:")) num2 float(input("第一个数:")) syminput("选择运算符 - * /&#xff1a;") #判断运算符号 …

知名在线市场 Etsy 允许在其平台上销售 AI 艺术品,但有条件限制|TodayAI

近日&#xff0c;以手工和复古商品著称的在线市场 Etsy 宣布&#xff0c;将允许在其平台上销售 AI 生成的艺术品。这一举措引发了广泛关注和争议。尽管 Etsy 正在接受 AI 艺术的潮流&#xff0c;但平台对这一类商品的销售设置了一些限制。 根据 Etsy 新发布的政策&#xff0c;…

小程序图片下载保存方法,图片源文件保存!

引言 现在很多时候我们在观看到小程序中的图片的时候&#xff0c;想保存图片的原文件格式的话&#xff0c;很多小程序是禁止保存的&#xff0c;即使是让保存的话&#xff0c;很多小程序也会限制不让保存原文件&#xff0c;只让保存一些分辨率很低的&#xff0c;非常模糊的图片…

【iOS】类对象的结构分析

目录 对象的分类object_getClass和class方法isa流程和继承链分析isa流程实例验证类的继承链实例验证 类的结构cache_t结构bits分析实例验证属性properties方法methods协议protocolsro类方法 类结构流程图解 对象的分类 OC中的对象主要可以分为3种&#xff1a;实例对象&#xf…

tinymce富文本支持word内容同时粘贴文字图片上传 vue2

效果图 先放文件 文件自取tinymce: tinymce富文本简单配置及word内容粘贴图片上传 封装tinymce 文件自取&#xff1a;tinymce: tinymce富文本简单配置及word内容粘贴图片上传 页面引用组件 <TinymceSimplify refTinymceSimplify v-model"knowledgeBlockItem.content…

exo 大模型算力共享;Llama3-70B是什么

目录 exo 大模型算力共享 exo框架的特点 如何使用exo框架 注意事项 结论 Llama3-70B是什么 一、基本信息 二、技术特点 三、性能与应用 四、未来发展 exo 大模型算力共享 exo框架的特点 异构支持:支持多种不同类型的设备,包括智能手机、平板电脑、笔记本电脑以及高…

仅两家!云原生向量数据库 PieCloudVector 全项通过信通院「可信数据库」评测

7月16日&#xff0c;2024 可信数据库发展大会在北京隆重举行。大会以“自主、创新、引领”为主题&#xff0c;近百位数据库领域的专家、学者齐聚一堂&#xff0c;带来高质量的数据库技术洞察与实战经验。 本次可信数据库发展大会中&#xff0c;中国信通院正式公布 2024 年上半年…

SpringMVC源码深度解析(中)

接上一遍博客《SpringMVC源码深度解析(上)》继续聊。最后聊到了SpringMVC的九大组建的初始化&#xff0c;以 HandlerMapping为例&#xff0c;SpringMVC提供了三个实现了&#xff0c;分别是&#xff1a;BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctio…

公司技术栈用到了RocketMQ,我对此块知识进行了回顾(初始RocketMQ)

前言 作为24届的校招生&#xff0c;不知道大伙儿们是否都已经到了工作岗位上。为了以后更方便的接触到公司的业务&#xff0c;我司为我们安排了将近一个月的实操。虽然不用敲代码&#xff0c;但是… 了解到我司使用到的技术栈&#xff0c;在空闲时间正好对RocketMQ这块技术做个…

[PM]产品运营

生命周期 运营阶段 主要工作 拉新 新用户的定义 冷启动 拉新方式 促活 用户活跃的原因 量化活跃度 运营社区化/内容化 留存 用户流失 培养用户习惯 用户挽回 变现 变现方式 付费模式 广告模式 数据变现 变现指标 传播 营销 认识营销 电商营销中心 拼团活动 1.需求整理 2.…

使用LVS+NGinx+Netty实现数据接入

数据接入 链接参考文档 LVSKeepalived项目 车辆数据上收&#xff0c;TBox通过TCP协议连接到TSP平台 建立连接后进行数据上传。也可借由该连接实现远程控制等操作。 通过搭建 LV—NGinx—Netty实现高并发数据接入 LVS&#xff1a;四层负载均衡&#xff08;位于内核层&#x…

【数据结构进阶】二叉搜索树

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; C || 数据结构 目录 &#x1f308;前言&#x1f525;二叉搜索树&#x1f525; 二叉搜索树的实现Insert&#xff08;插入&#xff09;find&#xff08;查找&#xff09;erase(删除)destro…

【中项】系统集成项目管理工程师-第2章 信息技术发展-2.1信息技术及其发展-2.1.1计算机软硬件与2.1.2计算机网络

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

车载音视频App框架设计

简介 统一播放器提供媒体播放一致性的交互和视觉体验&#xff0c;减少各个媒体应用和场景独自开发的重复工作量&#xff0c;实现媒体播放链路的一致性&#xff0c;减少碎片化的Bug。本文面向应用开发者介绍如何快速接入媒体播放器。 主要功能&#xff1a; 新设计的统一播放U…