在C语言中不支持真正意义上的字符串,C中用字符数组加上'\0'配合起来作为字符串,再配合一组函数实现字符串的操作,如strcpy、strcmp等等。C语言不支持自定义类型,因此无法获得字符串类型。
从C到C++的进化过程中引入了自定义数据类型,所以在C++中就能通过类类型来完成字符串类型的定义。
在我们的模板库中为何需要实现字符串类?STL标准库中有cstring类,QT中有qstring,那是因为往往很多时候都是在处理字符串,所以在我们的可复用的模板库中是有必要提供字符串类的。
设计要求
模板编程。
继承自顶层父类Object。
保护成员中:char*指针m_str、记录字符串长度的m_length变量、初始化m_str和m_length的init函数、判断两个len长的字符串是否相等的函数equal。
公有成员中:构造函数、操作符重载函数、功能函数、析构函数等等。
注意事项:
1、考虑类对象和字符串之间能够无缝对接进行互操作。
2、考虑操作符重载函数是否需要实现const版本。
3、利用C中的字符串操作函数来实现string类中的成员函数。
string类声明
class String : public Object
{
protected:
char* m_str;
int m_length;
void init(const char* s);
bool equal(const char* l, const char* r, int len)const;
public:
String();
String(char c);
String(const char* s);
String(const String& s);
int length() const;
const char* Str() const;
char& operator [](int i);//返回值为引用表示可以作为左值,给非const对象使用
char operator [](int i)const;
//判断某个字符串是否以某个子串开始或结尾
bool StartWith(const char* s)const;//不需要改变成员变量
bool StartWith(const String& s) const;
bool EndOf(const char* s)const;
bool EndOf(const String& s)const;
//插入函数
String& insert(int i, const char* s);//链式操作,操作完成后返回的是字符串自己 `
String& insert(int i, const String& s);
//删除字符串前后的空白
String& trim();
bool operator == (const String& s)const;
bool operator == (const char* s)const;
bool operator != (const String& s)const;
bool operator != (const char* s)const;
bool operator > (const String& s)const;
bool operator > (const char* s)const;
bool operator < (const String& s)const;
bool operator < (const char* s)const;
bool operator >= (const String& s)const;
bool operator >= (const char* s)const;
bool operator <= (const String& s)const;
bool operator <= (const char* s)const;
String operator + (const String& s)const;
String operator + (const char* s)const;
String& operator += (const String& s);//使用引用的原因是可以出现在赋值符的左边
String& operator += (const char* s);
String& operator = (const String& s);
String& operator = (const char* s);
String& operator = (const char c);
~String();
};
操作符重载函数
init函数:
void String::init(const char* s)
{
m_str = strdup(s);//在堆中将s复制一份
if( m_str )
{
m_length = strlen((const char*)m_str);
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memeory to create String object...");
}
}
将需要初始化的字符串在堆空间中复制一份出来赋值给保护成员变量m_str,调用strdup函数。如果失败抛出异常。
获取初始化字符串长度。
构造函数
String::String()
{
init("");
}
String::String(char c)
{
char s[] = {c, '\0'};//手动构造字符串
init(s);
}
String::String(const char* s)
{
init( s ? s : "");//防止被空指针初始化
}
String::String(const String& s)
{
init(s.m_str);
}
通过调用init函数进行构造函数实现,体现了设计的可复用性。
Str函数
const char* String::Str()const
{
return this->m_str;
}
实现类对象和字符串之间的互操作。
操作符重载函数
每种类型的操作符重载函数都需要实现两个版本,函数参数为const char*的和const string& 类型的。因为需要满足char*和类对象
// ==比较操作符重载
bool String::operator == (const String& s)const
{
return strcmp(this->m_str,s.m_str) == 0;
}
bool String::operator == (const char* s)const
{
return strcmp(this->m_str,s ? s : "") == 0;
}
// != 比较操作符重载
bool String::operator != (const String& s)const
{
return !(this->m_str == s.m_str);
}
bool String::operator != (const char* s)const
{
return !(this->m_str == s ? s : "");
}
此处只实现部分比较操作符重载,其他的类似大于、小于等比较操作符重载原理一致。利用C语言中strcmp函数。
+、+=操作符重载函数
// + 运算操作符重载
String String::operator + (const char* s)const//const函数不能直接改变成员变量的值但是可以使用另一个对象来改变对象的成员变量值
{
String ret;
int len = m_length + strlen( s ? s : "");
char* str = reinterpret_cast(malloc(len + 1));
if(str)
{
strcpy(str,m_str);
strcat(str,s ? s : "");
free(m_str);
ret.m_str = str;
ret.m_length = len;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memeory to mlalloc...");
}
return ret;
}
String String::operator + (const String& s)const
{
return (*this + s.m_str);
}
// += 运算操作符重载
String& String::operator += (const String& s)
{
return (*this = *this + s);
}
String& String::operator += (const char* s)
{
return (*this = *this + s);
}
在+=操作符重载实现中,函数返回值为string&类型,符合操作符的原义,将+之后的结果保存起来,函数实现为调用+操作符的重载函数后并赋值给当前对象的this指针。
=操作符重载
既然前面用到了赋值操作符,那么我们就该实现赋值操作符的重载。
// = 赋值运算符重载
String& String::operator = (const String& s)
{
return (*this = s.m_str);
}
String& String::operator = (const char* s)
{
if(m_str != s)//防止自赋值
{
char* str = strdup(s ? s : "");//合法性检测并复制一份
if(str)
{
free(m_str);
m_str = str;
m_length = strlen(m_str);
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memeory to get in heap...");
}
}
return *this;
}
String& String::operator = (const char c)
{
char str[] = {c, '\0'};
return (*this = str);
}
数组访问操作符
// []操作符重载
char& String::operator [](int i)//为什么是引用,是引用可以出现在赋值符号左边
{
if( (i >= 0) && (i < m_length) )
{
return m_str[i];
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"para i Index Out to use []...");
}
}
char String::operator [](int i)const//调用非const版本,它不能被赋值
{
return (const_cast(*this)[i]);//技巧
}
功能函数
判断两个等长字符串是否相等:equal
bool String::equal(const char* l, const char* r, int len)const
{
bool ret = true;
for(int i = 0; i < len && ret; i++)//只要检测到一个不相等就直接退出循环
{
ret = ret && (l[i] == r[i]);
}
return ret;
}
是否以指定字符串开始:StartWith
bool String::StartWith(const char* s)const//不需要改变成员变量
{
bool ret = (s != NULL);
if( ret )
{
int len = strlen(s);
ret = (len <= m_length) && (equal(m_str, s, len));//判断是否相等
}
return ret;
}
bool String::StartWith(const String& s) const
{
return (StartWith(s.m_str));
}
是否以指定字符串结尾:EndOf
bool String::EndOf(const char* s)const
{
bool ret = (s != NULL);
if( ret )
{
int len = strlen(s);
char* start = m_str + (m_length - len);//定位到最后len长度的位置
ret = (len <= m_length) && (equal(start, s, len));
}
return ret;
}
bool String::EndOf(const String& s)const
{
return (EndOf(s.m_str));
}
两个版本,能够进行互操作。
插入函数:insert
String& String::insert(int i, const char* s)//链式操作,操作完成后返回的是字符串自己
{
if( (i >= 0) && (i <= m_length))
{
if( (s != NULL) && (s[0] != '\0') )
{
int len = strlen(s);
char* str = reinterpret_cast(malloc(m_length + len + 1));
if(str)
{
strncpy(str,m_str,i);
strncpy(str + i, s, len );
strncpy(str + i + len, m_str+i, m_length - i);
str[m_length + len] = '\0';
free(m_str);
m_str = str;
m_length = m_length + len;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"No memory to insert ...");
}
}
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"para i Index Out ...");
}
return *this;
}
String& String::insert(int i, const String& s)
{
return insert(i,s.m_str);
}
返回值为string&目的是可以实现链式操作,即(类对象).(成员函数1).(成员函数2).(...)
两个版本是为了能够实现互操作。
删除字符串首尾空白部分函数:trim
//删除首尾空白字符
String& String::trim()//返回引用表示能够实现链式操作
{
int begin = 0;
int end = m_length - 1;
while(m_str[begin] == ' ') begin++;//找到中间部分的首尾位置
while(m_str[end] == ' ') end--;
if(begin == 0)//如果前面没空格
{
m_str[end + 1] = '\0';
m_length = end + 1;
}
else
{
for(int i = 0, j = begin; j <= end; i++, j++ )//移动
{
m_str[i] = m_str[j];
}
m_str[end - begin + 1] = '\0';
m_length = end - begin + 1;
}
return *this;
}
在此感谢狄泰唐老师。