【C++指南】告别C字符串陷阱:如何实现封装string?

news2025/4/26 2:29:09

 

🌟 各位看官好,我是egoist2023

🌍 种一棵树最好是十年前,其次是现在!

💬 注意:本章节只详讲string中常用接口及实现,有其他需求查阅文档介绍。

🚀 今天通过了解string接口,从而实现封装自己的string类达到类似功能。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!

引入

C 语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便, C 标准库中提供了一些 str 系列
的库函数,但是这些库函数与字符串是分离开的,不太符合 OOP 的思想,而且底层空间需要用户
自己管理,稍不留神可能还会越界访问。因此在C++中string用封装的方式解决了这一问题。

string类的文档介绍 --> 如有需要自行查阅文档中接口实现。

auto和范围for

auto关键字(自动推导类型

  • 在早期C/C++auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得
  • auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加&。
  • 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
  • auto不能作为函数的参数,可以做返回值,但谨慎使用。
  • auto不能直接用来声明数组。

范围for(底层就是迭代器

  • 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
  • 范围for可以作用到数组和容器对象上进行遍历
  • 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

了解string常用接口

 1.常见构造

(constructor) 函数名称
功能说明
string() (重点)
构造空的 string 类对象,即空字符串
string(const char* s) (重点)
C-string 来构造 string 类对象
string(size_t n, char c)
string 类对象中包含 n 个字符c
string(const string&s) (重点)
拷贝构造函数

2.容量操作

函数名称
功能说明

size(重点)

返回字符串有效字符长度
length
返回字符串有效字符长度
capacity
返回空间总大小
empty
检测字符串释放为空串,是返回 true ,否则返回 false
clear
清空有效字符(不改变底层空间大小)
reserve
为字符串预留空间
resize
将有效字符的个数该成 n 个,多出的空间用字符 c 填充
注意:
1. size() length() 方法底层实现原理完全相同,引入 size() 的原因是保持与其他接口容器一致,而length函数是由于历史原因遗留的。
2. resize(size_t n) resize(size_t n, char c) 都是将字符串中有效字符个数改变到 n 个,不
同的是当字符个数增多时: resize(n) 0 来填充多出的元素空间, resize(size_t n, char
c) 用字符 c 来填充多出的元素空间。注意: resize 在改变元素个数时,如果是将元素个数
增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
3. reserve(size_t res_arg=0) :为 string 预留空间,不改变有效元素个数,当 reserve 的参
数小于 string 的底层空间总大小时, reserver 不会改变容量大小。

 3.迭代器访问

函数名称
功能说明
operator[] (重点)
返回 pos 位置的字符, const string 类对象调用
begin + end
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位
置的迭代器
rbegin + rend
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位
置的迭代器
范围 for
\

4.修改操作

函数名称
功能说明
push_back
在字符串后尾插字符 c
append
在字符串后追加一个字符串
operator+= ( 重点 )
在字符串后追加字符串 str
c_str ( 重点 )
返回 C 格式字符串
find + npos (
)
从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的 位置
rfind
从字符串 pos 位置开始往后找字符c,返回该字符在字符串中的位置
substr
str 中从 pos 位置开始,截取 n 个字符,然后将其返回

5.非成员函数

函数
功能说明
operator+
尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点)
输入运算符重载
operator<< (重点)
输出运算符重载
getline (重点)
获取一行字符串
relational operators (重点)
大小比较

string类模拟实现

底层结构

class string
{
public:
    //...

private:
	char* _str = nullptr;
	int _size = 0;
	int _capacity = 0;

	const static size_t npos;
};

 在上面定义的结构当中,其常量npos表示字符串末尾之前的所有字符,在substr接口中有使用。

const size_t string::npos = -1; //-1的无符号整数即表示最大值

1.常见构造 

我们知道无论如何字符串当中末尾总会存' \0 ' ,作为标记。因此在构造字符串string时,一定要多开一个空间存 ' \0 ' 。那如果new空间失败呢?采用抛异常的方式,在外进行捕获异常(之后会讲)。

在如下一段程序中,将字符串str拷贝到string当中,但是这样会导致多次析构一块空间导致程序崩溃的问题。

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

浅/深拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来 。如果 对象中管理资源 ,最后就会 导致
多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该
资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规
如下图当中, s1 s2 共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃 ,这种拷贝方式,称为浅拷贝。

 深拷贝:不单单是把数据拷贝过去,还需要开一块内存空间,防止指向同一块空间。


	string::string(const char* str)
		:_size(strlen(str))
	{
		_str = new char[_size + 1];//如果失败需要捕获异常
		_capacity = _size;
		strcpy(_str, str);
	}

	string::string(size_t n, char ch)
		:_str(new char[n + 1])
		, _size(n)
		, _capacity(n)
	{
		for (size_t i = 0;i < n;i++)
		{
			_str[i] = ch;
		}
		_str[_size] = '\0';
	}

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

​

拷贝构造、赋值运算法重载(重点)

拷贝构造:

 目标是将s中的数据拷贝到_str中,那我们直接调用strcpy函数将s数据拷过来即可? 

	string::string(const string& s)
	{
		strcpy(_str, s._str);
	}

 但是这样会导致析构时多次析构一块空间,从而报错(依然是浅拷贝的问题)。

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

赋值运算符重载:

特殊情况下可能自己给自己赋值,为了不再拷贝一次做判断。

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

		return *this;
	}
现代写法

实际上,上面的两段代码显得过于笨拙且冗杂,都是老老实实自己手写申请空间。而在如下一段程序当中,借用构造函数来完成拷贝及其赋值。而这种方法,也是实践当中最常用到的现代写法。

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

	//拷贝构造简洁化  --> 现代写法
	string::string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}

在如上一段程序当中,通过构造函数构造tmp。s这里是引用传参,即出了作用域不会销毁 ;而tmp是属于这个栈内的空间,出了作用域就会销毁。此时我们借助swap的特性,将_str指向的指针进行交换,此时就是*this指向了新申请的空间,再将个数和空间交换即可。

这样看,和平日写的拷贝构造是差不多的。别着急,我们再来看看赋值运算符重载的简化实现。

  1. 方法一:仍然采用上面思想写赋值重载;
  2. 方法二:实际上,当我们写完了拷贝构造后,我们甚至还能再借助拷贝构造的特性来完成赋值重载。此时,我们不再使用引用传参,而是借助拷贝构造出s,而s出了作用域就会销毁,此时我们再借助swap来进行交换。这样来看,这种现代写法是不是既简洁又充满着妙处。

	string& string::operator=(string s)
	{
		//s即是拷贝构造过来的
		swap(s); //出了作用域就会析构
		return *this;
	}

2.容量操作

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

3. 迭代器访问

什么是迭代器?

迭代器的作用是用来访问容器(用来保存元素的数据结构)中的元素,所以使用迭代器,我们就可以访问容器中里面的元素。那迭代器不就相当于指针一个一个访问容器中的元素吗?并不是,迭代器是像指针一样的行为,但是并不等同于指针,且功能更加丰富,这点需在之后慢慢体会。(本章节体现并不是很明显)

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

4. 修改操作

push_back插入逻辑:

  1. 当插入元素大于容器容量时,需进行扩容操作;
  2. _size的位置是' \0 ',但直接将插入元素覆盖即可,_size++,重新加上' \0 ' 。
void string::push_back(char x)
{
	if (_size + 1 > _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	_str[_size++] = x;
	_str[_size] = '\0';
}

 append插入逻辑:

  1. 计算需要插入字符串的长度len,若string的个数+len大于容量则需扩容;
  2. 若个数+len长度大于2倍扩容时,则应扩容到个数+len容量;
  3. 往string末尾插入字符串。
void string::append(const char* str)
{
	size_t len = strlen(str);
	if (len + _size > _capacity)
	{
		int NewCapacity = 2 * _capacity;
		if (len + _size > 2 * _capacity)
		{
			NewCapacity = len + _size;
		}
		reserve(NewCapacity);
	}
	strcpy(_str + _size, str);
	_size += len;
}

 +=运算符重载逻辑:

  1. 如果插入的是字符串,则采用append函数的逻辑;
  2. 如果插入的是字符,则采用push_back函数的逻辑;
  3. 无论哪种情况,实现方式都和以上两种代码实现方式是相同的,因此我们可以以复用的方式,更容易维护我们的代码。
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}
string& string::operator+=(char x)
{
	push_back(x);
	return *this;
}

 insert函数实现逻辑:

  1. 扩容逻辑与其上是类似的,区别在于插入元素后的数据是从后往前还是从前往后挪动;
  2. 如果是从前往后挪动,那么会发生覆盖数据的现象,而从后往前就不会,这点在之前也有强调过;
	void string::insert(size_t pos, size_t n, char ch)
	{
		assert(pos <= _size);
		//扩容
		if (_size + n > _capacity)
		{
			// 
			size_t newCapacity = 2 * _capacity;
			if (_size + n > 2 * _capacity)
			{
				newCapacity = _size + n;
			}

			reserve(newCapacity);
		}

		//int end = _size;
		//while (end >= (int)pos)//这里不强转会有err
		//{
		//	_str[end + n] = _str[end];
		//	--end;
		//}

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


		for (size_t i = 0;i < n;i++)
		{
			_str[pos + i] = ch;
		}
		_size += n;
	}
  1.  扩容逻辑与其上对应重载函数是一样的;
  2. 一样是需要将pos后的位置进行挪动后,思路是类似的,那能否复用上面的实现函数呢?

如果复用上面的函数,那么该往这位置插入的字符串都是相同的一个字符,这样想似乎不能复用。

但是没关系,这些位置刚好是为要插入字符串预留的,那么我们只要将这些位置覆盖一遍即可。

	void string::insert(size_t pos, const char* str)
	{
		size_t n = strlen(str);
		insert(pos, n, 'x');

		for (size_t i = 0;i < n;i++)
		{
			_str[i + pos] = str[i];
		}
	}

复用 :通过牺牲空间方法。


		string tmp(n, ch);
		insert(pos, tmp.c_str());

5. 非成员函数

vs string 的结构
string 总共占 28 个字节 ,内部结构稍微复杂一点,先是 有一个联合体,联合体用来定义
string 中字符串的存储空间
  • 当字符串长度小于16时,使用内部固定的字符数组来存放
  • 当字符串长度大于等于16时,从堆上开辟空间

union _Bxty
{         // storage for small buffer or pointer to larger one
        value_type _Buf [ _BUF_SIZE ];
        pointer _Ptr ;
        char _Alias [ _BUF_SIZE ]; // to permit aliasing
} _Bx ;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于 16 ,那 string 对象创建
好之后,内部已经有了 16 个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有 一个 size_t 字段保存字符串长度,一个 size_t 字段保存从堆上开辟空间总的
容量
最后:还 有一个指针 做一些其他事情。
故总共占 16+4+4+4=28 个字节。

流提取

vs下额外定义了个buff数组以减少扩容,提高效率。我们同样采用这种思想造类似的轮子。

//cin>>s
istream& operator>>(istream& in, string& s)
{
	s.clear();

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

	//为了减少频繁的扩容,定义一个数组
	char buff[1024];
	char ch = in.get();
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 1023)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

 流插入

//cout<<s
ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}

	return out;
}

getline函数(难点)

实现逻辑:

  1. 每次输入都往buff数组中填入数据;
  2. 当数据超过buff数组容量时,将数组里的数据加到string当中,buff数组从0开始继续填入数据;
  3. 如果ch==delim时,不再填入数据,将buff数组里剩下的数据加到string当中。
istream& getline(istream& is, string& s, char delim)
{
	char buff[1024];
	char ch = is.get();
	size_t i = 0;
	while (ch != delim)
	{
		buff[i++] = ch;
		if (i == 1023)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
		ch = is.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return is;
}

代码实现

string.h

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

namespace egoist
{
	class string
	{
	public:
		//迭代器
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

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

		//计算串的size和capacity
		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

		//构造函数
		string(const char* str = "");
		string(size_t n, char ch);
		
		//交换
		void swap(string& s);

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

		const char* c_str() const
		{
			return _str;
		}
		void reserve(size_t n);
		void push_back(char x);
		void append(const char* str);

		=重载运算符
		//string& operator=(const string& s);
		//现代简洁化
		string& operator=(string s);

		string& operator+=(const char* str);
		string& operator+=(char x);

		//比较大小
		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;

		//[]运算符重载
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			assert(pos >= 0);
			return _str[pos];
		}

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

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

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

		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		string substr(size_t pos, size_t len = npos);

		//析构
		~string();

	private:
		char* _str = nullptr;
		int _size = 0;
		int _capacity = 0;

		const static size_t npos;
	};

	//cout<<s
	ostream& operator<<(ostream& out, const string& s);

	//cin>>s
	istream& operator>>(istream& in, string& s);

	istream& getline(istream& is, string& s, char delim = '\n');

}

string.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"

namespace egoist
{
	const size_t string::npos = -1;

	string::string(const char* str)
		:_size(strlen(str))
	{
		_str = new char[_size + 1];//如果失败需要捕获异常
		_capacity = _size;
		strcpy(_str, str);
	}

	string::string(size_t n, char ch)
		:_str(new char[n + 1])
		, _size(n)
		, _capacity(n)
	{
		for (size_t i = 0;i < n;i++)
		{
			_str[i] = ch;
		}
		_str[_size] = '\0';
	}
	
	拷贝构造
	//string::string(const string& s)
	//{
	//	_str = new char[s._capacity + 1];
	//	strcpy(_str, s._str);
	//	_size = s._size;
	//	_capacity = s._capacity;
	//}

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

	//拷贝构造简洁化  --> 现代写法
	string::string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}

	void string::reserve(size_t n)
	{
			//需要增容 --> 为了和new配套使用,不用realloc
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
	}

	void string::push_back(char x)
	{
		if (_size + 1 > _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		_str[_size++] = x;
		_str[_size] = '\0';
	}

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (len + _size > _capacity)
		{
			int NewCapacity = 2 * _capacity;
			if (len + _size > 2 * _capacity)
			{
				NewCapacity = len + _size;
			}
			reserve(NewCapacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

	//=运算符重载
	//string& string::operator=(const string& s)
	//{
	//	if (this != &s)
	//	{
	//		delete _str;
	//		_str = new char[s._capacity + 1];
	//		strcpy(_str, s._str);
	//		_size = s._size;
	//		_capacity = s._capacity;
	//	}

	//	return *this;
	//}

	//现代简洁化 --> 通过调用拷贝构造
	string& string::operator=(string s)
	{
		//s即是拷贝构造过来的
		swap(s); //出了作用域就会析构
		return *this;
	}

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

	//比较大小
	bool string::operator==(const string& s) const
	{
		return strcmp(_str, s._str) == 0;
	}

	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) || (*this == s);
	}

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

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

	void string::insert(size_t pos, size_t n, char ch)
	{
		assert(pos <= _size);
		//扩容
		if (_size + n > _capacity)
		{
			// 
			size_t newCapacity = 2 * _capacity;
			if (_size + n > 2 * _capacity)
			{
				newCapacity = _size + n;
			}

			reserve(newCapacity);
		}

		//int end = _size;
		//while (end >= (int)pos)//这里不强转会有err
		//{
		//	_str[end + n] = _str[end];
		//	--end;
		//}

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


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

	void string::insert(size_t pos, const char* str)
	{
		由于高度相似,可采用复用
		//assert(pos <= _size);
		//size_t n = strlen(str);
		扩容
		//if (_size + n > _capacity)
		//{
		//	// 
		//	size_t newCapacity = 2 * _capacity;
		//	if (_size + n > 2 * _capacity)
		//	{
		//		newCapacity = _size + n;
		//	}

		//	reserve(newCapacity);
		//}

		//size_t end = _size + n;
		//while (end > pos + n - 1)
		//{
		//	_str[end] = _str[end - n];
		//	--end;
		//}
		size_t n = strlen(str);
		insert(pos, n, 'x');

		for (size_t i = 0;i < n;i++)
		{
			_str[i + pos] = str[i];
		}
		
		//通过牺牲空间方法复用
		/*string tmp(n, ch);
		insert(pos, tmp.c_str());*/
	}


	void string::erase(size_t pos, size_t len)
	{
		assert(pos >= 0);
		if (len > _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else {
			for (size_t i = pos;i <= _size;i++)
			{
				_str[i] = _str[i + len];
			}
			_size -= len;
		}
	}

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

	size_t string::find(const char* str, size_t pos)
	{
		const char* p = strstr(_str + pos, str);
		if (p == nullptr)
		{
			return npos;
		}
		else
		{
			return p - _str;
		}
	}

	string string::substr(size_t pos, size_t len)
	{
		size_t leftlen = _size - pos;
		if (len > leftlen)
			len = leftlen;

		string tmp;
		tmp.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			tmp += _str[pos + i];
		}

		return tmp;
	}

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

	//cout<<s
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}

		return out;
	}

	//cin>>s
	istream& operator>>(istream& in, string& s)
	{
		s.clear();

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

		//为了减少频繁的扩容,定义一个数组
		char buff[1024];
		char ch = in.get();
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 1023)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

	istream& getline(istream& is, string& s, char delim)
	{
		char buff[1024];
		char ch = is.get();
		size_t i = 0;
		while (ch != delim)
		{
			buff[i++] = ch;
			if (i == 1023)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = is.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return is;
	}
}

扩展 --> 引用计数的写时拷贝

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


 

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

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

相关文章

国内ip地址怎么改?详细教程

在中国&#xff0c;更改IP地址需要遵守规则&#xff0c;并确保所有操作合规。在特定情况下&#xff0c;可能需要修改IP地址以满足不同需求或解决特定问题。以下是一些常见且合法的IP地址变更方法及注意事项&#xff1a; 一、理解IP地址 IP地址是设备在网络中的唯一标识&#x…

模式设计简介

设计模式简介 设计模式是软件开发中经过验证的最佳实践解决方案,它是针对特定问题的通用解决方案,能够帮助开发者提升代码的可维护性、可扩展性和复用性。设计模式并非具体的代码实现,而是一种解决问题的思路和方法论,它源于大量的实践经验总结,旨在解决软件开发过程中反…

众趣科技X世界读书日丨数字孪生技术赋能图书馆空间智慧化运营

4月23日&#xff0c;是第30个“世界读书日”&#xff0c;不仅是庆祝阅读的日子&#xff0c;更是思考知识传播未来的契机。 图书馆作为主要传播图书的场所&#xff0c;在科技的发展中&#xff0c;图书馆正面临前所未有的挑战&#xff0c;联合国数据显示&#xff0c;全球近30%的…

MySQL 事务(详细版)

目录 一、事务简介 1、事务的概念 2、事务执行的案例 3、对于事务的理解 二、事务操作 &#xff08;一&#xff09;未控制事务 &#xff08;二&#xff09;控制事务一 &#xff08;三&#xff09;控制事务二 三、事务四大特性 四、并发事务问题 五、事务隔离…

c++之网络编程

网络编程&#xff1a;使得计算机程序能够在网络中发送和接受数据&#xff0c;从而实现分布式系统和网络服务的功能。 作用&#xff1a;使应用程序能够通过网络协议与其他计算机程序进行数据交换 基本概念 套接字&#xff08;socket&#xff09;&#xff1a; 套接字是网络通信…

MySQL8的安装方法

概述&#xff1a; MySQL对于开发人员来说&#xff0c;并不陌生。但是很多朋友提起安装MySQL就很头疼&#xff0c;如果一不小心安装失败&#xff0c;再现安装第二遍就变得更加头疼。今天给大家分享一个比较非常简单好安装的方法&#xff0c;并且删除或者卸载也都非常容易 下载…

CF每日4题

1500左右的做到还是有点吃力 2093E 1500 二分答案 题意&#xff1a;给定一个长度为 n 的数组&#xff0c;现在要把它切成 k 份&#xff0c;求每一份最小的MEX中的最大值。 就是找最大值&#xff0c;但是这个值是所有段最小的值采用二分答案&#xff0c;二分这个值&#xff0…

基于 Spring Boot 瑞吉外卖系统开发(七)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;七&#xff09; 新增菜品页面 菜品管理页面提供了一个“新增菜品”按钮&#xff0c;单击该按钮时&#xff0c;会打开新增菜品页面。 菜品分类列表 首先要获取分类列表数据。 请求路径/category/list&#xff0c;请求方法GE…

二项式分布html实验

二项式分布html实验 本文将带你一步步搭建一个纯前端的二项分布 Monte-Carlo 模拟器。 只要一个 HTML 文件&#xff0c;打开就能运行&#xff1a; 动态输入试验次数 n、成功概率 p 与重复次数 m点击按钮立刻得到「模拟频数 vs 理论频数」柱状图随着 m 增大&#xff0c;两组柱状…

大模型如何作为reranker?

大模型如何作为reranker&#xff1f; 作者&#xff1a;爱工作的小小酥 原文地址&#xff1a;https://zhuanlan.zhihu.com/p/31805674335 只为了感动自己而去做一些事情纯属浪费时间。 ————爱工作的小小酥 引言 用于检索的模型中&#xff0c;我们最熟悉的就是单塔和双塔了&…

发放优惠券

文章目录 概要整体架构流程技术细节小结 概要 发放优惠券 处于暂停状态&#xff0c;或者待发放状态的优惠券&#xff0c;在优惠券列表中才会出现发放按钮&#xff0c;可以被发放&#xff1a; 需求分析以及接口设计 需要我们选择发放方式&#xff0c;使用期限。 发放方式分…

试完5个AI海报工具后,我投了秒出设计一票!

随着AI技术的不断发展&#xff0c;越来越多的AI生成工具进入了设计领域&#xff0c;海报生成工具成为了其中的重要一员。今天&#xff0c;我们将为大家介绍三款热门的AI海报生成工具&#xff0c;并进行对比分析&#xff0c;帮助大家选择最适合的工具。 1. 秒出设计&#xff1a;…

PH热榜 | 2025-04-25

1. LambdaTest Accessibility Testing Suite 标语&#xff1a;轻松点击&#xff0c;确保网站的包容性和合规性。 介绍&#xff1a;LambdaTest 的可访问性测试工具可以自动识别你的网站和网络应用中是否符合 WCAG&#xff08;网页内容无障碍指南&#xff09;标准。你可以设置定…

模方ModelFun是什么?如何安装?

摘要&#xff1a;本文主要介绍模方ModelFun的软件简介、特性、安装环境配置、插件及软件安装。 1.软件简介 模方是一款实景三维模型的场景修饰与单体化建模工具&#xff0c;是建模的后处理软件&#xff0c;包括网格模型编辑和单体化建模两大模块。 场景修饰模块可以对 OBJ、OSG…

[AI Workflow] 基于多语种知识库的 Dify Workflow 构建与优化实践

在实际应用中,基于用户提供的资料快速构建高质量的知识库,并以此背景精准回答专业问题,是提升人工智能系统实用性的重要方向。然而,在跨语种环境下(如中、日、英混合资料与提问),传统的知识检索和回答生成流程往往面临匹配不准确、信息检索不全面的问题。 本文将介绍一种…

Pycharm(十六)面向对象进阶

一、继承 概述&#xff1a; 实际开发中&#xff0c;我们发现很多类中的步分内容是相似的&#xff0c;或者相同的&#xff0c;每次写很麻烦&#xff0c;针对这种情况&#xff0c; 我们可以把这些相似&#xff08;相同的&#xff09;部分抽取出来&#xff0c;单独地放到1个类中&…

WebGL图形编程实战【4】:光影交织 × 逐片元光照与渲染技巧

现实世界中的物体被光线照射时&#xff0c;会反射一部分光。只有当反射光线进人你的眼睛时&#xff0c;你才能够看到物体并辩认出它的颜色。 光源类型 平行光&#xff08;Directional Light&#xff09;&#xff1a;光线是相互平行的&#xff0c;平行光具有方向。平行光可以看…

Java高频面试之并发编程-07

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;线程之间有哪些通信方式&#xff1f; 在 Java 多线程编程中&#xff0c;线程间通信&#xff08;Inter-Thread Communica…

.NET代码保护混淆和软件许可系统——Eziriz .NET Reactor 7

.NET代码保护混淆和软件许可系统——Eziriz .NET Reactor 7 1、简介2、功能特点3、知识产权保护功能4、强大的许可系统5、软件开发工具包6、部署方式7、下载 1、简介 .NET Reactor是用于为.NET Framework编写的软件的功能强大的代码保护和软件许可系统&#xff0c;并且支持生成…