string类型是天天用的玩意,但是自己实现时,用顺序存储还是链式存储更合适呢?
《数据结构》中介绍了三种方式:
1,定长顺序存储
长度有限,貌似没大用处
3,块链存储
数组和链表的一种混用形式,原本直觉上认为这种方式较合理,但仔细思考,发现其便利性也仅体现在连接了。综合来说不如第2种。
2,堆分配存储
这是今天要实现的方式。
方法的选择上,理论上好歹也应该重载下加号运算符什么的,但运算符重载作为C++的专题留到以后再说。现在仅关心数据结构,实现部分教材中列出的方法。
串的长度是直接记录下来,结尾没有补0作结束符。
效率上当然没法和STL提供的相比,仅是意思下练练手罢。
一开始还有种想法,string这玩意太平常,了解下干脆别浪费时间自己实现了,结果粗大事了。事实证明不动手还是不行啊。
以下是一份错误的代码:
#include <iostream>
using std::cout;
using std::endl;
class MyString
{
public:
char *ch;
int length;
MyString();
~MyString();
//给串赋字符串常量值
void StrAssign(char *chars);
//按序比较,>T返回值>0,= =,< <
int StrCompare(MyString T);
//S = S + S2;
void Concat(MyString S2);
//返回从pos起len长度的子串
MyString SubString(int pos, int len);
void Show();
};
//初始化为空串
MyString::MyString()
{
ch = NULL;
length = 0;
}
MyString::~MyString()
{
delete ch;
ch = NULL;
}
void MyString::StrAssign(char *chars)
{
//不管三七二十一,先释放掉原有数据
if (ch)
{
delete ch;
ch = NULL;
}
//计算chars长度
int i=0;
while(chars[i] != NULL)
{
i++;
}
if (0 == i) //注意若所赋串为空,length需归0
{
ch = NULL;
length = 0;
}
else
{
ch = new char[i]();
for(int j=0; j<i; ++j)
{
ch[j] = chars[j];
}
length = i;
}
}
int MyString::StrCompare(MyString T)
{
for(int i=0; (i<length) && (i<T.length); ++i)
{
if (ch[i] > (T.ch)[i])
{
return 1;
}
else if (ch[i] < (T.ch)[i])
{
return -1;
}
}
return length - T.length;
}
void MyString::Concat(MyString S2)
{
//为空直接赋值
if (ch == NULL)
{
ch = S2.ch;
length = S2.length;
}
else
{
//备份原串
char *tmp = new char[length]();
for(int i=0; i<length; ++i)
{
tmp[i] = ch[i];
}
length = length + S2.length;
delete ch;
ch = new char[length]();
int i = 0;
//复制原串
for(; i<(length - S2.length); ++i)
{
ch[i] = tmp[i];
}
delete tmp;
tmp = NULL;
//复制S2
for(; i<length; ++i)
{
ch[i] = (S2.ch)[i - length + S2.length];
}
}
}
MyString MyString::SubString(int pos, int len)
{
MyString result;
if ((pos<=0)||(pos>length)||(len<0)||(len>length-pos+1))
{
cout<<"invalid param or empty String"<<endl;
result.ch = NULL;
result.length = 0;
return result;
}
if (0 == len)
{
result.ch = NULL;
result.length = 0;
}
else
{
result.ch = new char[len]();
for(int i=0; i<len; ++i)
{
result.ch[i] = ch[i+pos-1]; //注意此处要-1
}
result.length = len;
}
return result;
}
void MyString::Show()
{
if (NULL == ch)
{
cout<<"the string is empty"<<endl;
}
else
{
for(int i=0; i<length; ++i)
{
cout<<ch[i];
}
cout<<endl;
}
}
int main()
{
MyString s1; s1.Show();
s1.StrAssign("hello"); s1.Show();
MyString s2;
s2.StrAssign("abc"); s2.Show();
if (s1.StrCompare(s2)>0)
{
cout<<"s1>s2"<<endl;
}
else if(s1.StrCompare(s2)<0)
{
cout<<"s1<s2"<<endl;
}
s1.Concat(s2); s1.Show();
s2.SubString(4,2);
s2.SubString(2,2).Show();
}
然后运行结果如下:
the string is empty
hello
abc
s1>s2
我现在刻意的练习C朝C++风格的转换,抛弃struct用class,暴露出了一些漏洞。很好。
为什么出错呢?原因是这样的:
由于我潜意识里希望作为参数传递进各方法的MyString对象不要被修改,而又避免使用对我来说较生疏的const等关键字,所以我采用了直接传递对象的方法,甚至连指针都懒得用。结果就反而弄出问题了!
注意,假如此时将StrCompare()的测试语句注释掉,会发现后面的Concat()是正常的。那明明Compare只是读取了字符作了一些比较,根本没作修改动作,为何还会影响到后面的语句呢?
秘密在于,我将s2作为参数传递进Compare()时,s2是被复制了一份的,当然ch及length都被默认的复制构造函数一一复制进去。当然这些都在预料之中没什么问题。
但是要注意!复制的这份形参,退出函数时,是要被处理的!比如这里的MyString,退出Compare()时是要被析构的!而我之前为了避免内存泄露,专门为MyString的析构函数编写了释放指针的语句。所以导致,形参被析构时,把原本被复制的s2也连累了,s2的指针被delete,资源就不见了。
所以假如把析构函数的语句注释掉,什么都不做,输出的结果就会变正常,不过这种解决方法不好,留下内存泄露的问题。
所以该用const的地方还是得用,而即便仅仅采用引用传递的方式也许反而不会产生问题。
然后我将代码的所有值传递修改为引用传递,运行后,前面部分均正常,SubString()仍报错。
原因也很简单,我很2B的在SubString()中定义了局部变量result,然后返回这个result,但是result是要被析构的啊,于是返回值就没法用了。
修改代码如下后,一切正常了。
/*
2014.4.18
堆分配存储串
*/
#include <iostream>
using std::cout;
using std::endl;
class MyString
{
public:
char *ch;
int length;
MyString();
~MyString();
//给串赋字符串常量值
void StrAssign(char *chars);
//按序比较,>T返回值>0,= =,< <
int StrCompare(MyString *T);
//S = S + S2;
void Concat(MyString *S2);
//返回从pos起len长度的子串
void SubString(MyString *result, int pos, int len);
void Show();
};
//初始化为空串
MyString::MyString()
{
ch = NULL;
length = 0;
}
MyString::~MyString()
{
delete ch;
ch = NULL;
}
void MyString::StrAssign(char *chars)
{
//不管三七二十一,先释放掉原有数据
if (ch)
{
delete ch;
ch = NULL;
}
//计算chars长度
int i=0;
while(chars[i] != NULL)
{
i++;
}
if (0 == i) //注意若所赋串为空,length需归0
{
ch = NULL;
length = 0;
}
else
{
ch = new char[i]();
for(int j=0; j<i; ++j)
{
ch[j] = chars[j];
}
length = i;
}
}
int MyString::StrCompare(MyString *T)
{
for(int i=0; (i<length) && (i<T->length); ++i)
{
if (ch[i] > (T->ch)[i])
{
return 1;
}
else if (ch[i] < (T->ch)[i])
{
return -1;
}
}
return length - T->length;
}
void MyString::Concat(MyString *S2)
{
//为空直接赋值
if (ch == NULL)
{
ch = S2->ch;
length = S2->length;
}
else
{
//备份原串
char *tmp = new char[length]();
for(int i=0; i<length; ++i)
{
tmp[i] = ch[i];
}
length = length + S2->length;
delete ch;
ch = new char[length]();
int i = 0;
//复制原串
for(; i<(length - S2->length); ++i)
{
ch[i] = tmp[i];
}
delete tmp;
tmp = NULL;
//复制S2
for(; i<length; ++i)
{
ch[i] = (S2->ch)[i - length + S2->length];
}
}
}
void MyString::SubString(MyString *result, int pos, int len)
{
if ((pos<=0)||(pos>length)||(len<0)||(len>length-pos+1))
{
cout<<"invalid param or empty String"<<endl;
result->ch = NULL;
result->length = 0;
}
if (0 == len)
{
result->ch = NULL;
result->length = 0;
}
else
{
result->ch = new char[len]();
for(int i=0; i<len; ++i)
{
result->ch[i] = ch[i+pos-1]; //注意此处要-1
}
result->length = len;
}
}
void MyString::Show()
{
if (NULL == ch)
{
cout<<"the string is empty"<<endl;
}
else
{
for(int i=0; i<length; ++i)
{
cout<<ch[i];
}
cout<<endl;
}
}
int main()
{
MyString s1; s1.Show();
s1.StrAssign("hello"); s1.Show();
MyString s2;
s2.StrAssign("abc"); s2.Show();
if (s1.StrCompare(&s2)>0)
{
cout<<"s1>s2"<<endl;
}
else if(s1.StrCompare(&s2)<0)
{
cout<<"s1<s2"<<endl;
}
s1.Concat(&s2); s1.Show();
MyString result;
s2.SubString(&result, 4, 2);
s2.SubString(&result, 2, 2);
result.Show();
}
/*
运行结果:
the string is empty
hello
abc
s1>s2
helloabc
invalid param or empty String
bc
注意点:
1,字符串比较过程的逻辑关系一定要想清楚
*/
当然,要避免参数值被改动,仅靠个人思维推断是不规范的,最好还是加上const修饰符。
另外把属性改回了private,传指针改成了传引用,最终的代码如下:
/*
2014.4.18
堆分配存储串
*/
#include <iostream>
using std::cout;
using std::endl;
class MyString
{
private:
char *ch;
int length;
public:
MyString();
~MyString();
//给串赋字符串常量值
void StrAssign(const char *chars);
//按序比较,>T返回值>0,= =,< <
int StrCompare(const MyString &T);
//S = S + S2;
void Concat(const MyString &S2);
//返回从pos起len长度的子串
void SubString(MyString *result, int pos, int len) const;
void Show();
};
//初始化为空串
MyString::MyString()
{
ch = NULL;
length = 0;
}
MyString::~MyString()
{
delete ch;
ch = NULL;
}
void MyString::StrAssign(const char *chars)
{
//不管三七二十一,先释放掉原有数据
if (ch)
{
delete ch;
ch = NULL;
}
//计算chars长度
int i=0;
while(chars[i] != NULL)
{
i++;
}
if (0 == i) //注意若所赋串为空,length需归0
{
ch = NULL;
length = 0;
}
else
{
ch = new char[i]();
for(int j=0; j<i; ++j)
{
ch[j] = chars[j];
}
length = i;
}
}
int MyString::StrCompare(const MyString &T)
{
for(int i=0; (i<length) && (i<T.length); ++i)
{
if (ch[i] > (T.ch)[i])
{
return 1;
}
else if (ch[i] < (T.ch)[i])
{
return -1;
}
}
return length - T.length;
}
void MyString::Concat(const MyString &S2)
{
//为空直接赋值
if (ch == NULL)
{
ch = S2.ch;
length = S2.length;
}
else
{
//备份原串
char *tmp = new char[length]();
for(int i=0; i<length; ++i)
{
tmp[i] = ch[i];
}
length = length + S2.length;
delete ch;
ch = new char[length]();
int i = 0;
//复制原串
for(; i<(length - S2.length); ++i)
{
ch[i] = tmp[i];
}
delete tmp;
tmp = NULL;
//复制S2
for(; i<length; ++i)
{
ch[i] = (S2.ch)[i - length + S2.length];
}
}
}
void MyString::SubString(MyString *result, int pos, int len) const
{
if ((pos<=0)||(pos>length)||(len<0)||(len>length-pos+1))
{
cout<<"invalid param or empty String"<<endl;
result->ch = NULL;
result->length = 0;
}
if (0 == len)
{
result->ch = NULL;
result->length = 0;
}
else
{
result->ch = new char[len]();
for(int i=0; i<len; ++i)
{
result->ch[i] = ch[i+pos-1]; //注意此处要-1
}
result->length = len;
}
}
void MyString::Show()
{
if (NULL == ch)
{
cout<<"the string is empty"<<endl;
}
else
{
for(int i=0; i<length; ++i)
{
cout<<ch[i];
}
cout<<endl;
}
}
int main()
{
MyString s1; s1.Show();
s1.StrAssign("hello"); s1.Show();
MyString s2;
s2.StrAssign("abc"); s2.Show();
if (s1.StrCompare(s2)>0)
{
cout<<"s1>s2"<<endl;
}
else if(s1.StrCompare(s2)<0)
{
cout<<"s1<s2"<<endl;
}
s1.Concat(s2); s1.Show();
MyString result;
s2.SubString(&result, 4, 2);
s2.SubString(&result, 2, 2);
result.Show();
}