当前位置: 首页 > news >正文

【C++初阶】--- string类模拟实现

1.基础函数

1.1成员函数

成员函数主要是_str、_size、_capacity这三个。npos是size_t 的最大值,用于当作后续成员函数的参数的缺省值。

class string
{
private:
		char* _str = nullptr;//指向字符串的指针
		size_t _size = 0;//字符串长度
		size_t _capacity = 0;//空间大小
		static const size_t npos;
};

1.2默认构造函数

默认构造我们给str一个缺省值"",即没有传参的时候字符串为空,只有一个’\0’,因为strcpy会把’\0’拷贝过来

//默认构造
string(const char* str = "")//如果没传值的话会拷贝'\0''
{
	_capacity = _size = strlen(str);
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

1.3拷贝构造

拷贝构造有两种写法,一种是普通写法,一种是现代写法

  1. 普通写法:
    我们先根据要拷贝的对象确认空间大小,然后new一块空间,new出空间后利用strcpy将字符串中的内容拷贝过来
//拷贝构造
string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}
  1. 现代写法:
    我们利用要拷贝的那个字符串构造一个string对象tmp,然后我们自己写一个swap函数用于交换两个string对象的内容,这样我们就可以将tmp中的内容交换到我们要拷贝构造的这个对象中。
    需要注意的是注意相关成员变量需要给缺省值,否则交换后tmp的_str可能是野指针。因为tmp是个局部对象,出了拷贝构造就要调用析构函数。
//交换两个字符串的值
void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
//现代写法,拷贝构造
string(const string& s)
{
	//利用s._str构造tmp,再使用tmp与this交换
	//注意相关成员变量需要给缺省值,否则交换后tmp的_str可能是野指针
	string tmp(s._str);
	swap(tmp);
}

1.4赋值运算符重载

赋值运算符重载也分为普通写法和现代写法

  1. 普通写法:
    在不是给自己赋值的情况下,我们先delete原对象字符串的的空间,然后new一个和赋值对象字符串一样大的空间,在利用strcpy拷贝内容,更新长度和空间大小。
//赋值运算符重载
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;
}
  1. 现代写法
    写法1我们可以利用对象的字符串来构造tmp,或者直接用对象拷贝构造tmp。构造出tmp后交换此对象与tmp对象,两者内容也就交换了。
    写法2更为简介,首先我们参数不再是引用,而是传值,则tmp只是一份拷贝。传值传参,对形参的的改变不会影响实参,我们直接用传过来的tmp进行交换。
//现代写法,赋值运算符重载
//写法1
string& operator=(const string& s)//不用引用,则tmp只是一份拷贝
{
	if (this != &s)
	{
		//string tmp(s._str);//用s._str构造tmp
		string tmp(s);//用s拷贝构造tmp
		swap(tmp);
	}
	return *this;
}

//写法2,优化
string& operator=(string tmp)//不用引用,则tmp只是一份拷贝
{
	//交换tmp不会对tmp原本的值造成影响
	swap(tmp);
	return *this;
}

1.5析构函数

因为我们有资源的申请,需要显示写析构函数将new的空间delete

//析构
~string()
{
	//不析构空指针
	if (_str)
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
}

2.可以写在类里的成员函数

短小的函数和频繁调用的函数可以直接写在类中,在类中写的成员函数函数默认是inline函数(内联函数)

2.1size()和capacity()

size()返回的是字符串的长度,capacity返回的是空间的大小

size_t size() const
{
	return _size;
}

size_t capacity() const
{
	return _capacity;
}

2.2empty()、clear()、c_str()

  1. empty()用于判断字符串是否为空,空返回true,非空返回false
  2. clear()用于清除字符串中的内容,一般不会清除空间
  3. c_str()用于获取等效的字符串
//判空
bool empty() const
{
	return _size == 0;
}

//清除字符串,不清除空间
void clear()
{
	_str[0] = '\0';
	_size = 0;
}

//获取等效的字符串
const char* c_str() const
{
	return _str;
}

2.3operator[]

char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

[]重载,可以获取字符串中对应下标的元素,前提确保不能越界,const重载返回的元素不能逆行修改。

char& operator[](size_t index)
{
	//确保有效位置
	assert(index < _size);
	return _str[index];
}
const char& operator[](size_t index) const
{
	assert(index < _size);
	return _str[index];
}

3.迭代器iterator

迭代器的底层我们暂且可以认为是指针

typedef char* iterator;//正向迭代器
typedef const char* const_iterator;//正向只读迭代器

3.1begin()和end()

  1. begin()会返回一个指向字符串的第一个字符的迭代器。
  2. end()会返回一个指向字符串的最后一个字符的下一个位置的迭代器,即’\0’位置
const_iterator begin() const
{
	return _str;
}

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

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

4.修改空间大小的函数

4.1resver()

void reserve (size_t n = 0);

resver()用于请求更改容量
• 如果 n 大于当前字符串容量,则该函数会导致容器将其容量增加到 n 个字符(或更大)。
• 如果n <= 当前字符串容量,容量是否变化取决于编译器。
• 此函数对字符串长度没有影响,也无法更改其内容。

void string::resver(size_t n)
{
	//n <= _capacity的话我们选择不改变容量
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

4.2resize()

resize()用于调整字符串大小
• 将字符串大小调整为 n 个字符的长度。
• 如果 n 小于当前字符串长度,则当前值将缩短为其前 n 个字符,并删除第 n个字符以外的字符。
• 如果 n 大于当前字符串长度,则通过在末尾插入所需数量的字符来扩展当前内容,以达到 n 的大小。如果指定了 c,则新元素将初始化为 c 的副本,否则,它们是值初始化字符(空字符)。

memset将 ptr 指向的内存块的前num字节数设置为指定值value

void * memset ( void * ptr, int value, size_t num );
//调整字符串大小
void string::resize(size_t newSize, char c)
{
	//n 大于当前字符串长度,则通过在末尾插入所需数量的字符来扩展当前内容
	if (newSize > _size)
	{
		//newSize比空间大,需要进行扩容
		if (newSize > _capacity)
		{
			resver(newSize);
			memset(_str + _size, c, newSize - _size);
		}
		else
		{
			memset(_str + _size, c, _capacity - _size);
		}
	}
	//n 小于当前字符串长度,则当前值将缩短为其前 n 个字符,并删除第 n个字符以外的字符
	else
	{
		_str[newSize] = '\0';
		_size = newSize;
	}
	_capacity = newSize;
	_str[newSize] = '\0';
}

5.用于修改字符串内容的函数

5.1push_back()

push_back()用于尾插一个字符

void push_back (char c);

插入字符前我们先考虑容量是否足够,不够的话我们就2倍扩容,不要忘了结尾加上’\0’

//尾插字符
void string::push_back(char c)
{
	//考虑扩容
	if (_size == _capacity)
	{
		resver(_capacity == 0 ? 4 : 2 * _capacity);
	}
	_str[_size++] = c;
	_str[_size] = '\0';
}

5.2operator+=()

operator+=()用于尾插字符、字符串、string对象,我们模拟实现尾插字符和字符串。

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

尾插字符我们可以直接复用push_back(),两者功能一样

//在末尾追加字符
string& string::operator+=(char c)
{
	//直接复用
	push_back(c);
	return *this;
}

在末尾追加字符串我们可以直接复用append(),这个等下讲,append()用于尾插字符串。

//在末尾追加字符串
string& string::operator+=(const char* str)
{
	//直接复用
	append(str);
	return *this;
}

5.3append()

依旧先考虑扩容问题,但因为我们是2倍扩容,如果_size + len比2倍还大,那空间依旧不够用,那就他要多少我们扩多少。扩容完我们strcpy将字符串中内容进行拷贝。

//在末尾追加字符串
void string::append(const char* str)
{
	//考虑扩容
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		//比2倍大就按需扩容,否则就直接2倍扩
		resver(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}
	strcpy(_str + _size, str);
	_size += len;
}

5.4insert()

insert()用于将其他字符插入字符串中 pos(或 p)指示的字符之前,并返回该字符的位置

string& insert (size_t pos, const char s);
string& insert (size_t pos, const char* s);

需要注意pos不能越界,然后考虑扩容,然后移动pos位置及之后的数据向后移动一位('\0’一起移动),移动完后在pos位置放入对应字符。

//在pos位置插入字符
string& string::insert(size_t pos, const char c)
{
	//pos可以在尾部,相当于尾插
	assert(pos <= _size);
	//考虑扩容
	if (_size == _capacity)
	{
		resver(_capacity == 0 ? 4 : 2 * _capacity);
	}

	//'\0'一起移
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}
	_str[pos] = c;
	_size++;

	return *this;
}

在pos位置插入字符串和插入字符操作差不多,不同的是扩容的操作和向后移动的距离。

//在pos位置插入字符串
string& string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		resver(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}

	size_t end = _size + len;
	while (end > pos + len - 1)
	{
		_str[end] = _str[end - len];
		end--;
	}
	for (size_t i = 0; i < len; i++)
	{
		_str[i + pos] = str[i];
	}
	_size += len;
	_str[_size] = '\0';
	return *this;
}

5.5erase()

erase()用于擦除字符串的一部分,减少其长度

const size_t string::npos = -1;
string& erase (size_t pos = 0, size_t len = npos);

先判断pos是否越界,如果要删除的长度超过字符剩余长度,说明pos位置及之后的字符都需要删除,我们直接将pos位置的数据修改成’\0’,即字符串的结尾。
如果要删除的长度小于字符剩余长度,则说明说明后面还剩余有效字符,需要将这些字符向前移动len长度。

//删除pos位置开始的len个元素
string& string::erase(size_t pos, size_t len)
{
	assert(pos < _size);

	//len超过剩余长度,则说明后面全删了
	if (len > _size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}

	//后面还剩几个字符没删
	else
	{
		for (size_t i = pos + len; i < _size; i++)
		{
			_str[i - len] = _str[i];
		}
		_size -= len;
	}
	return *this;
}

6.查找字符串中的内容

6.1find()

find()用于在字符串中搜索和参数(字符或者字符串)匹配的第一个匹配项,并返回其在字符串中的下标。

size_t find (const char* s, size_t pos = 0) const;
size_t find (char c, size_t pos = 0) const;

判断pos是否越界,然后从pos位置开始向后找,找到就返回下标,没找到则返回npos。

//从pos位置开始往后找字符c,返回下标
size_t string::find(char c, size_t pos) const
{
	//pos有效性
	assert(pos < _size);
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == c)
			return i;
	}
	//没有找到
	return npos;
}

strstr()函数可以返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回 null 指针。

char * strstr (char * str1, const char * str2 );

如果tmp不为nullptr,则说明子串存在,返回的则是str2第一次出现的指针,将tmp指针与字符串指针_str相减,两指针相减即地址相减,可以得出下标。

//返回字串在string中第一次出现的位置
size_t string::find(const char* str, size_t pos) const
{
	assert(pos < _size);
	const char* tmp = strstr(_str + pos, str);
	if (tmp == nullptr)
		return npos;
	else
		return tmp - _str;		
}

6.2substr()

substr()用于生成子字符串,可以生成一个从pos位置开始,长度为len的字符串,如果剩余长度小于len,则拷贝至末尾停止。
如果没有给定len,则按照缺省值拷贝,npos是个无符号整型,-1转换成无符号整型是INT_MAX,即从pos位置拷贝至末尾

const size_t string::npos = -1;
string substr (size_t pos = 0, size_t len = npos) const;

确定拷贝长度,然后为子字符串开空间,并拷贝字符。

//生成子字符串
string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);

	//len大于剩余长度,将len更新为剩余长度
	if (len > _size - pos)
	{
		len = _size - pos;
	}

	string sub;
	sub.resver(len);//开空间
	for (size_t i = 0; i < len; i++)
	{
		sub += _str[i + pos];
	}

	return sub;
}

7.运算符重载

7.1比较运算符重载

我们将以下运算符进行重载,就可以比较对象字符串的内容大小

bool operator<(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator!=(const string& s1,const string& s2);
bool operator<(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

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

bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}

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

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

bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

7.2流插入、流提取运算符重载

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

流插入我们使用范围for将字符串一个一个插入即可

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

流提取我们则要先清除对象字符串中的字符,buff的作用是为了避免插入字符过程中的频繁扩容,使用用buff先存储输入的字符,满了之后我们将buff字符串尾插到对象s的字符串中。
cin遇到空格或者换行就不读取了,我们模拟实现也是遇到空格或者换行即停止。
我们读取字符的时候使用ch = in.get();而不是cin>>ch是因为cin默认空格和换行是间隔符,不会进行读取,也就是说我们永远读取不到空格和换行,程序将陷入死循环,而get()可以读到空格和换行。
注意循环结束后如果i>0说明buff中还有字符,我们需要将其尾插至对象s的字符串中。

istream& operator>>(istream& in, string& s)
	{
		//先清除s中的字符
		s.clear();

		const int N = 256;
		char buff[N];

		int i = 0;
		//不能用in>>ch,因为读不到空格和换行,默认是间隔符
		char ch = in.get();

		while(ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			//满了,最后一个填'\0'
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			//继续读下一个字符
			ch = in.get();
		}

		//不足256个不会尾插,将剩下的尾插进去
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}
}

相关文章:

  • Springboot JPA ShardingSphere 根据年分表
  • CentOS 7无法上网问题解决
  • 由小到大的数列,寻找是否存在一个数的耗时最小的算法
  • Linux红帽:RHCSA认证知识讲解(十 二)调试 SELinux,如何管理 SELinux 的运行模式、安全策略、端口和上下文策略
  • WEB安全--内网渗透--捕获NET-NTLMv2 Hash
  • 【Android Audio】Parameter Framework - pfw
  • 解锁轨道交通 UI 界面设计的奥秘,打造出行新体验
  • Langfuse的使用带样例
  • 车辆视频检测器linux版对于密码中包含敏感字符的处理方法
  • `uia.WindowControl` 是什么:获取窗口文字是基于系统的 UI 自动化接口,而非 OCR 方式
  • 批量压缩 jpg/png 等格式照片|批量调整图片的宽高尺寸
  • LINUX 5 cat du head tail wc 计算机拓扑结构 计算机网络 服务器 计算机硬件
  • 计算机网络体系结构(一)
  • # 项目部署指南:Flask、Gradio与Docker实现流程
  • Three.js 系列专题 5:加载外部模型
  • STM32cubmax配置STM32407VET6,实现网络通信
  • Kotlin与HttpClient编写视频爬虫
  • SQL122 删除索引
  • MySQL8.0.40编译安装(Mysql8.0.40 Compilation and Installation)
  • 【Git “ls-tree“ 命令详解】
  • 白领兼职做网站/免费做网站怎么做网站吗
  • 庆阳市人大常委会网站建设/seo能干一辈子吗
  • 新浪云服务器做网站/软文是什么意思?
  • l兰州网站建设/网站seo策划方案实例
  • 电子商务网站建设与维护 试卷/万网域名注册官网
  • 网站用axure做的rp格式/网站推广平台搭建