一. 环境
Linux x86_64,g++ 8.5.0
二. 实现
自实现 string 之前一直想写来着,一直拖着,现在把它完稿。这个版本是比较简单的版本,有一些可能有不同的或者更好的实现方式,后面有机会会加到里面。
打算实现的接口如下
c++
class MyString
{
friend std::ostream & operator<<(std::ostream & co, const MyString &ms);
friend std::istream & operator>>(std::istream & ci, MyString &ms);
public:
MyString(const char * s = nullptr);
~MyString();
MyString(const MyString & another);
MyString & operator=(const MyString & another);
MyString operator+(const MyString & another);
MyString & operator+=(const MyString & another);
bool operator>(const MyString & another);
bool operator<(const MyString & another);
bool operator==(const MyString & another);
char & operator[](int n);
char & at(int n);
private:
char * m_str;
};
- 构造函数,参数使用默认参数,默认参数作标记位。不论是否传递实参,申请资源时均以数组形式申请。不传递实参时,申请一个 char 的数组,有传递实参时,以实际的为准。这样,在释放资源时,均可以
delete []m_str
形式释放。
c++
MyString::MyString(const char *str)
{
if (nullptr == str)
{
m_str = new char[1];
*m_str = '\0';
}
else
{
m_str = new char[strlen(str)+1];
strcpy(m_str, str);
}
}
- 拷贝赋值,使用了两种方式。
第一种是基础的写法,先 delete 堆上的空间,再申请新的空间,然后复制内容。需要注意的是,需判断是否是自赋值的情况。
第二种采用了 copy && swap 技术,相对完善一点,前一种方式,在 delete []m_str
后如果程序出现异常,此时其它地方有使用到 m_str
的话就尴尬了,而后一种方式就没有这个问题。
c++
// version1
/*
MyString & MyString::operator=(const MyString &another)
{
if (this == &another)
{
return *this;
}
delete []m_str;
int len = strlen(another.m_str);
m_str = new char[len+1];
strcpy(m_str, another.m_str);
return *this;
}
*/
// version2,采用 copy and swap 技术
MyString & MyString::operator=(const MyString &another)
{
if (this == &another)
{
return *this;
}
MyString ms(another);
std::swap(this->m_str, ms.m_str);
return *this;
}
- 重载
+
运算符,成员函数返回一个临时对象。在申请新的空间后,在使用strcat()
之前需要初始化,否则可能会出现问题。strcat()
是从末尾为 '\0' 的地方开始拼接的。
c++
MyString MyString::operator+(const MyString &another)
{
MyString ms;
int len = strlen(this->m_str) + strlen(another.m_str);
delete []ms.m_str;
ms.m_str = new char[len +1]{0}; // 注意初始化
strcat(strcat(ms.m_str, this->m_str), another.m_str);
return ms;
}
- 重载
+=
运算符,返回值类型是引用类型,这样可以连续使用+=
。
使用 realloc()
后,在使用 strcat()
连接两个字符串之前,需要将 m_str
后面一部分新扩充的空间进行初始化。
c++
MyString & MyString::operator+=(const MyString &another)
{
int lenOfSource = strlen(this->m_str);
int lenOfAnother = strlen(another.m_str);
this->m_str = (char *)realloc(this->m_str, lenOfSource+lenOfAnother+1);
memset(this->m_str+lenOfSource, 0, lenOfAnother+1);
strcat(this->m_str, another.m_str);
return *this;
}
- 重载
>
运算符
c++
bool MyString::operator>(const MyString &another)
{
return strcmp(this->m_str, another.m_str) > 0;
}
重载 <
和 ==
与上面类似,就不重复列举了。
- 重载 [] 运算符,这个没啥好说的了。
c++
char & MyString::operator[](int n)
{
return m_str[n];
}
- 成员函数 at()
c++
char & MyString::at(int n)
{
return m_str[n];
}
- 重载输出 << 和 输入 >> 运算符。
在测试成员函数前,可以早点写这两个函数,测试时就方便打印了,不然还需要单独添加一个成员函数返回 m_str
了。
重载运算符,目标形式是:
c++
Mystring ms;
cout << ms;
cin >> ms;
对于重载,一般会考虑到成员函数重载和全局重载,但是 ostream
类和 istream
类都是系统提供的类,我们不可能在 ostream
类和 istream
类中进行修改,因此只能放弃成员函数重载。此时,只能是全局重载,即全局函数重载了。
考虑到会连续输出(cout << a << b;
),因此返回类型是 ostream &
类型,它是经入参而来,入参类型也是 ostream &
。
c++
std::ostream & operator<<(std::ostream & co, const MyString &ms)
{
co << ms.m_str;
return co;
}
输入运算符与输出运算符类似,第二个入参不能是 const
类型,因为需要修改入参 ms
。这里处理的相对简单了,栈上申请了1024字节的字符数组用以存储输入的数据,实际上会有不够用的情况。
c++
std::istream & operator>>(std::istream & ci, MyString &ms)
{
// 简单处理,申请一块固定大小的内存
char ch[1024];
ci >> ch;
delete []ms.m_str;
ms.m_str = new char[strlen(ch)+1];
strcpy(ms.m_str, ch);
return ci;
}
三. 完整代码,可点击链接 mystring ,如有有问题或不到之处,请指出并交流,看到后我会修改。
四. 参考
C++基础与提高 王桂林