zl程序教程

您现在的位置是:首页 >  其他

当前栏目

stl中String类的实现

2023-03-14 22:51:25 时间

代码中写了详细的注释,这里就不展开对每个函数做说明解释了 string.h

#pragma once
#include<cstdlib>
#include<iostream>
using namespace std;
class String
{
private:
	char* str;//指向动态数组的指针
	int size;
	void Error(const char* c)const;//错误信息报告
public:
	//构造函数和析构函数
	String(const char* c = "");//转换构造函数同时也是默认无参构造函数
	String(const String& s);//复制构造函数
	~String();
	//访问方法:
	//赋值运算符重载
	String& operator=(const char* c);//转换赋值:类串=c串
	String& operator=(const String& s);//转换赋值:类串=类串
	//指定位置插入
	String& Insert(int id, const String& s);//子串插入,插入到id下标之前
	//指定位置,删除指定长度的子串
	String& Erase(int id, int num);
	//获取指定位置,指定长度的子串
	String SubStr(int id, int num)const;
	//串连接
	String operator+(const String& s)const;//串连接:类串=类串+类串
	String operator+(const char* c)const;//串连接:类串=类串+c串
	friend String operator+(const char* c,const String& s);//串连接:类串=c串+类串
	//串比较
	bool operator==(const String& s);//串比较: 类串==类串
	bool operator==(const char* c);//串比较:类串==c串
	friend bool operator==(const char* c, const String& s);//串比较:c串==类串
	//成员转换:特殊的operator类型转换函数
	operator char* ()const; //将当前String对象转换为char*类型的对象,然后返回,即返回一个转换后的char* 对象
	//下标运算符重载
	char& operator[](int id); //非常量型下标运算符重载
	const char& operator[](int id)const;//常量型下标运算符重载,注意这里的后置const不能少,因为只有后置const才能作为重载条件
	//求串长
	int Size(void)const { return size; }
	//字符查找
	int Find_First_Of(char ch, int id)const;
	//子串查找
	int Find_First_Of(const String& s, int id)const;
	//输入输出运算符重载---做友元函数,因为左值要是输入输出符,而不是当前类对象
	friend istream& operator>>(istream& istr, String& s);
	friend ostream& operator<<(ostream& ostr, const String& s);
	//串读取
	int ReadString(istream& istr=cin, char delimiter='
');
};

string.cpp

#define _CRT_SECURE_NO_WARNINGS //如果关于strcpy等等函数报安全隐患,就加上这行代码,默认其为安全,注意:必须要置顶
#include"string.h"
#include<string.h>
//1.默认构造函数的实现--同时也是转换构造函数
//注意:如果声明的时候写了默认实参,那么实现的时候就不能再次写一遍,不然会报错
String::String(const char* c)
{
	size = strlen(c);//strlen不包含长度,sizeof包含
	str = new char[size + 1];//str在动态创建时的长度为size+1,是因为要包含一个
	if (!str)
		Error("String: overflow!");
	strcpy(str, c);
}
//2.错误信息报告
void String::Error(const char* c)const
{
	cout << c << endl;
}
//3.赋值构造函数
String::String(const String& s)
{
	size = s.size;
	str = new char[size + 1];
	if (!str)
		Error("String: overflow!");
	strcpy(str, s.str);
}
//4.成员赋值运算符重载
String& String::operator=(const char* c)
{
	int len = strlen(c);
	char* buf = str;
	//如果长度一样,就不用重新开辟空间存储了
	if (size != len)
	{
		str = new char[len + 1];
		if(!str)
			Error("String: overflow!");
		delete[] buf;
		size = len;
	}
	strcpy(str, c);
	return *this;
}
String& String::operator=(const String& s)
{
	if (size != s.size)
	{
		str = new char[s.size + 1];
		if (!str)
			Error("String: overflow!");
		size = s.size;
	}
	strcpy(str, s);
	return *this;
}
//5.成员转换函数---把当前string对象,转换为char*对象,并返回
String::operator char* ()const
{
	char* c = new char[size + 1];
	if(c==NULL)
		Error("String: overflow!");
	strcpy(c, str);
	return c;
}
//6.串连接
//(1)类串与类串连接
String String::operator+(const String& s)const
{
	String w;
	int len = size + s.size;
	delete[] w.str;
	w.str = new char[len + 1];
	if(!w.str)
		Error("String: overflow!");
	//一开始w是空的用拷贝,后来用连接字符串函数
	strcpy(w.str, s.str);
	strcat(w.str, str);
	w.size = len;
	return w;
}
//(2)类串与c串连接
String String::operator+(const char* c)const
{
	String w;
	int len = size + strlen(c);
	delete[] w.str;
	w.str = new char[len + 1];
	if(!w.str)
		Error("String: overflow!");
	strcpy(w.str, c);
	strcat(w.str, str);
	w.size = len;
	return w;
}
//(3)c串与类串连接
//注意:friend只能出现在友元函数的声明中,而不能出现在友元函数的实现中
String operator+(const char* c, const String& s)
{
	String w;
	int len = strlen(c) + s.size;
	delete[] w.str;
	if(!w.str)
		s.Error("String: overflow!");//这里可以用Error私有函数是因为这里是友元函数
	strcpy(w.str, c);
	strcat(w.str, s.str);
	w.size = len;
	return w;
}
//7.关系运算符重载
bool String::operator==(const String& s)
{
	return strcmp(str, s.str) == 0;
}
bool String::operator==(const char* c)
{
	return strcmp(str, c) == 0;
}
bool operator==(const char* c, const String& s)
{
	return strcmp(c, s.str) == 0;
}
//8.求子串--从下表id开始读取num个字符组成新的字符串,作为返回值
//步骤:
//(1):创建新的字符串s,并修改新串的字符串空间为num+1
//(2):将原串中的子串字符逐个赋给新串
String String::SubStr(int id, int num)const
{
	int len = size;
	int left = len - id,i;
	if(id<0||id>len-1)
		Error("id id illegal!");
	if (num <= 0 || num > left)
		Error("num is illegal!");
	//步骤(1)
	String s;
	delete[] s.str;
	s.str = new char[num + 1];
	if (!s.str)
		Error("overflow!");
	s.size = num;
	//步骤(2)
	char* p = str + id;
	char* p1 = s.str;
	for (i = 1; i <= num; i++)
		*p1++ = *p++;
	*p1 = '';
	return s;
}
//9.子串插入---在原串位置id处插入串s
//步骤:
//(1):扩展原串的字符串空间
//(2):将原串的结束符到下标id的字符依次后移
//(3):在下标id出插入串s
String& String::Insert(int id, const String& s)
{
	char* p, * p1, * buf;
	int len = size;//原串长度
	int len1 = s.size;//插入串的长度
	int left = len - id;//需要移动的字符个数
	if (id<0 || id>len)
		Error("id is illegal!");
//步骤(1)
	     buf = str;//先保存原串的字符串
	     str = new char[len + len1+1];//扩展原串的字符串空间
		if (str == NULL)
			Error("overflow!");
		//将原串拷贝过来,并释放原串的内存
		strcpy(str, buf);
		delete[] buf;
//步骤(2)
		p = str + len;//p指向原串的结束符
		p1 = p + len1;//指向移动的终点,移动距离是插入串s的长度
		for (int i = 1; i <= left+1; i++)
			*p1-- = *p--;     //从后往前挨个元素往后移动一格
 //步骤(3)
		p = str + id;
		p1 = s.str;
		while (*p1 != '')
			*p++ = *p1++;
//更新size大小
		size = len + len1;
		return *this;

}
//10.子串的删除---从原串下标id起,开始连续删除num个字符
//算法思想:原串分为前,中,后三段,中间是待删除的子串,前后连接
//步骤:
//(1)在原串中删除子串
//(2)暂存删除后的原串的字符串
//(3)重新分配原串的字符串空间,将暂存的字符串复制到原串,并释放原字符串空间
String& String::Erase(int id, int num)
{
	char* p, * q, * buf;
	int len = size;
	int left = len - id;//最多删除的字符个数
	if (id<0 || id>len - 1)
		Error("id is illegal!");
	if (num <= 0 || num > left)
		Error("num is illegal!");
	//步骤(1)
	p = str + id;//删除的子串的下标起始位置
	q = str + id + num;//删除子串的最后一个字符下标位置
	//这里用后端字符覆盖中段字符
	while (*q != '')
		*p++ = *q++;
	*p = '';//更新字符串结尾标志
	//步骤(2)
	buf = str;
	//步骤(3)
	len = strlen(buf);
	str = new char[len + 1];
	if (str == NULL)
		Error("overflow! ");
	strcpy(str, buf);
	size = len;
	delete[] buf;
	return *this;
}
//11.下标运算符重载
char& String::operator[](int id)
{
	if (id<0 || id>size - 1)
		Error("operator[]:id is illigeal");
	return *(str + id);
}
const char& String::operator[](int id)const
{
	if(id<0||id>size-1)
		Error("operator[]:id is illigeal");
	return *(str + id);
}
//12.字符查找---从某一位置开始寻找某一个字符首次出现的位置,如果找到,则返回其下标,否则返回-1
//从id开始查找字符首次出现的位置
int String::Find_First_Of(char ch, int id)const
{
	int start = id;
	char* p;
	if (start<0 || start>size - 1)
		Error("Start is illegal!");
	p = str + start;
	while (*p!= '')
	{
		if (*p == ch)
			break;
		else
		{
			++start;
			p++;
		}
	}
	return *p == '' ? -1 : start;
}
//13.重载输入输出运算符
istream& operator>>(istream& istr, String& s)
{
	char buf[256];//256是输入缓冲区长度
	istr >> buf;
	s = buf;//调用转换赋值运算符
	return istr;
}
ostream& operator<<(ostream& ostr, const String& s)
{
	ostr << s.str;
	return ostr;
}
//14.readstring读取的字符串可以含空格,以dilemiter表示的字符为读取结束符号,默认读取结束符号是换行符
int String::ReadString(istream& istr, char delimiter)
{
	char buf[256];
	int token = -1;
	if (istr.getline(buf, 256, delimiter))
	{
		*this = buf;//调用转换赋值运算符重载
		token = size;
	}
	return token;
}
//下面是全篇最难点:模式匹配
//从一类串的某一个下标位置开始查找一个子串,称为模式匹配,前者称为原串,后者称为模式串
//算法思想:
//把模式串分为首字符,尾字符和首位字符中间的子串。
//首先在原串中查找模式串的首字符,然后在原串中查找与模式串尾字符位置对应的字符进行比较
//如果相等,就比较他们的中间子串。
//如果中间子串相等,就结束算法.
int String::Find_First_Of(const String& s, int id)const
{
	int len = s.size;//模式串长度
	int end = size - 1;//原串的尾字符
	//步骤如下:
	//1.取模式串的首字符和尾字符分别存储在变量first和last中
	char first = s[0];    char last = s[len - 1];
	int firstid, lastid;
	String mid, cs;
	//2.从id位置开始,在原串中查找模式串的首字符,用firstid表示这个字符的下标,
	//然后在原串中计算与模式串尾字符位置对应的字符的下标lastid
	if (len > 2)
	{
		mid = s.SubStr(1, len - 2);
	}
	firstid = Find_First_Of(first, id);//获取模式串第一个字符在原串中首次出现的位置的下标
	lastid = firstid + len - 1;
	//3.因为下标lastid没有超过原串的上界,所以取该下标的字符与模式串尾字符比较.
	//如果相等,指向步骤4,否则从firstid+1开始,再执行步骤2
	while (firstid != -1 && lastid <= end)
	{
		if (str[lastid] = last)
		{
			//如果模式串包含字符小于2个,并且还是在存在两个字符匹配成功的情况下,说明找到了模式串在原串中的位置
			if (len <= 2)
			{
				return firstid;
			}
			//4.取他们的中间串进行比较。因为中间串相等,所以找到,返回firstid
			cs = SubStr(firstid + 1, len - 2);//再次获取中间串
			if (cs == mid)
			{
				return firstid;
			}
		}
		id = firstid + 1;
		firstid = Find_First_Of(first, id);//再次获取模式串首字符在原串中首次出现位置,从id位置开始往后
		lastid = firstid + len - 1;
	}
	return -1;
}

//16.析构函数
String::~String()
{
	if (str != NULL)
	{
		delete[] str;
		str = NULL;
	}
	size = 0;
}

main.cpp

#include<iostream>
using namespace std;
#include"string.h"
void test()
{
	String as("wework"), bs("hard"), cs;
	cs = as; 
	cs = as + bs;
	as = "study";
	cs.Insert(6, as);
	as = cs.SubStr(2, 4);
	cout << as << endl;

	String a("happy!");
	cout<<"a[0]= "<<a[0] << endl;
	cout << "p在a串中第一次出现的位置:" << a.Find_First_Of('p', 0) << endl;

	String bb("we are the god!");
	cout << "the在bb串中第一次出现的位置: " << bb.Find_First_Of("the", 0) << endl;
}
int main()
{
	test();
	return 0;
}
  • 这里函数只是列举了常见的一部分,实际的stl中的string的函数代码实现比这多的多
  • 这里的模式匹配用的是最简单的朴素模式匹配算法,高级一点的可以用KMP算法,还可以把KMP算法next数组优化为nextural数组