本节实现一个简单的string类,以方便后面的string系列能够顺利进行,设计string系列旨在学习和理解一些底层技术,比如COW、内存池等等,另一方面学习语言的基本功,同时也可以加深对string类的理解。
代码可能很长,但是学习的最好办法就是阅读代码。
类定义
这里定义了string类的一些基本函数
class MString{
public:
//构造函数
MString():m_len(0),m_str(0){}
MString(const MString& s);
MString(const char* s);
MString(size_t n);
~MString();
//赋值操作符,实现对象赋值
MString& operator=(const MString& s);
//赋值操作符,实现字符串赋值
MString& operator=(const char* s);
//转换成c类型字符串
const char* c_str(){ return m_str; }
//判断字符是否为空
bool empty(){ return m_len>0? true:false; }
//返回字符串长度
size_t length(){ return m_len; }
//返回字符串大小
size_t size(){return m_len;}
//删除pos开始的n个字符,不能为左值
const MString& erase(size_t pos, size_t n);
//对象末尾添加字符串s
MString& append(const MString& s);
//返回从pos开始的n个字符
MString substring(size_t pos,size_t n);
//从位置pos开始插入字符串s
MString& insert(size_t pos,const MString& s);
MString& insert(size_t pos,const char* s);
//把字符串s中从pos开始的n个字符返回赋给当前字符串
MString& assign(const MString& s, size_t,size_t);
MString& assign(const char* s, size_t,size_t);
private:
size_t m_len;
char* m_str;
};
实现
#include "mstring.h"
MString::MString(const MString& s)
{
m_len = s.m_len;
m_str = new char[m_len+1];
strcpy(m_str,s.m_str);
}
MString::MString(const char* s)
{
if (s == NULL)
{
return;
}
m_len = strlen(s);
m_str = new char[m_len+1];
strcpy(m_str,s);
}
MString::MString(size_t n)
{
m_len = n;
m_str = new char[n+1];
}
MString::~MString()
{
delete[] m_str;
m_len = -1;
}
//赋值操作符,深拷贝
MString& MString::operator=(const MString& s)
{
if (strcmp(this->m_str, s.m_str) == 0) //两个字符串相等,则直接返回。
{
return *this;
}
if (s.m_len > 0) //如果两个字符串不想等,且s中存在元素。
{
if (m_len > 0)
{
delete[] m_str;
}
this->m_len = s.m_len;
m_str = new char[m_len + 1];
strcpy(m_str, s.m_str);
m_str[m_len] = '\0';//strcpy已近复制了'\0'
}
//若s为空,直接返回本身,若不为空返回深拷贝后的自己
return *this;
}
//字符串赋值
MString& MString::operator=(const char* s)
{
if (s == NULL)
{
return *this;
}
if (this->m_len > 0)
{
delete[] m_str;
}
this->m_len = strlen(s);
this->m_str = new char[m_len+1];
strcpy(m_str,s);
m_str[m_len] = '\0'; //strcpy已近复制了'\0'
return *this;
}
//删除pos开始的n个字符,pos位置以0开开始
const MString& MString::erase(size_t pos, size_t n)
{
assert(pos < m_len && pos > 0 && n > 0 && n < m_len);
if (pos + n < m_len) //如果删除的长度在len范围内
{
int i;
int move_num = m_len - pos - n ;
for (i = 0;i < move_num;i++)
{
m_str[pos+i] = m_str[pos + n + i];
}
m_str[pos + i] = '\0';
}
else //删除的长度超过m_len
{
m_str[pos] = '\0';
}
return *this;
}
//对象末尾添加字符串s
MString& MString::append(const MString& s)
{
if (s.m_len < 1)
{
return *this;
}
m_len += s.m_len;
char* tmp_str = new char[m_len +1];
strcpy(tmp_str,m_str);
strcat(tmp_str,s.m_str);
tmp_str[m_len] = '\0';
delete[] m_str;
m_str = tmp_str;
return *this;
}
//返回从pos开始的n个字符
MString MString::substring(size_t pos,size_t n)
{
assert(pos < m_len && pos > 0 && n > 0 && n < m_len);
MString* new_string = new MString(n);
if (pos + n < m_len) //截取长度在字符串范围内
{
while(n--)
{
new_string->m_str[n] = this->m_str[pos + n];
}
new_string->m_str[new_string->m_len] = '\0';
}
else //截取长度在字符串之外
{
strcpy(new_string->m_str, &this->m_str[pos]);
new_string->m_str[this->m_len - pos]='\0';
}
return *new_string;
}
//从位置pos开始插入字符串s
MString& MString::insert(size_t pos,const MString& s)
{
assert(pos >= 0 && pos <= m_len);
m_len += s.m_len;
char* tmp_str = new char[m_len +1];
//赋值新的字符串中
strncpy(tmp_str,m_str,pos);
tmp_str[pos] = '\0';
strcat(tmp_str,s.m_str);
strcat(tmp_str,&m_str[pos]);
tmp_str[m_len] = '\0';
//释放原字符串
delete[] m_str;
m_str = tmp_str;
return *this;
}
//从位置pos开始插入字符串s
MString& MString::insert(size_t pos,const char* s)
{
assert(pos >= 0 && pos <= m_len);
m_len += strlen(s);
char* tmp_str = new char[m_len +1];
//赋值新的字符串中
strncpy(tmp_str,m_str,pos);
tmp_str[pos] = '\0'; //注意这里要加结束符,否则strcat会一直找到'\0'时才开始连接。
strcat(tmp_str,s);
strcat(tmp_str,&m_str[pos]);
tmp_str[m_len] = '\0';
//释放原字符串
if (m_str != NULL)
{
delete[] m_str;
}
m_str = tmp_str;
return *this;
}
//把字符串s中从pos开始的n个字符返回赋给当前字符串
MString& MString::assign(const MString& s, size_t pos,size_t n)
{
assert(s.m_len >= pos + n && pos >= 0 && n>0);
if (m_len == 0 && m_str == 0)
{
m_len = n;
m_str = new char[m_len +1];
strncpy(m_str, &s.m_str[pos],n);
m_str[m_len] = '\0';
}
else
{
m_len = n;
char* tmp_str = new char[m_len +1];
strncpy(tmp_str, &s.m_str[pos],n);
tmp_str[m_len] = '\0';
delete[] m_str;
m_str = tmp_str;
}
return *this;
}
MString& MString::assign(const char* s, size_t pos,size_t n)
{
size_t slen = strlen(s);
assert(slen >= pos + n && pos >= 0 && n>0);
if (m_len == 0 && m_str == 0)
{
m_len = n;
m_str = new char[m_len +1];
strncpy(m_str, &s[pos],n);
m_str[m_len] = '\0';
}
else
{
m_len = n;
char* tmp_str = new char[m_len +1];
strncpy(tmp_str, &s[pos],n);
tmp_str[m_len] = '\0';
delete[] m_str;
m_str = tmp_str;
}
return *this;
}
其中,某些方法是参考vector实现思想写的。下一节,操作符重载总结(http://blog.csdn.net/z702143700/article/details/47166437)
2015-09-17修改:看了剑指offer后发现,自己 写代码还是很低级的。拿operator=来说。
实现中,先delete 掉m_str,再申请一段内存的时候,如果内存不足,new抛出异常,此时m_str就变成了一个空指针。这样很容易导致程序崩溃。
要实现这种异常安全性,必须先new到内存后再去释放。剑指offer中给了一个很好的解法:先创建一个临时实例,再交换临时和原来实例。
MString& MString::operator=(const MString& s)
{
if (&s != this)
{
MString* tms = new MString(s);
char* ptmp = tms->m_str;
tms->m_str = this->m_str;
this->m_str = ptmp;
}
return *this;
}