手写string类
1.题目:实现string类
包含构造函数、拷贝构造函数、析构函数、拷贝赋值
扩展:重载+、重载+=、重载==、重载[]、重载<<。
2.分析
(1)设计类
定义成员变量char *m_data;
,使用char型指针变量存储字符串的首地址。
除了重载<<外,其他函数都是成员函数,而输出流不能定义为成员函数,为了可以访问私有变量m_data,我们使用友元函数。
class myString
{
public:
myString(const char *str = 0); //构造
myString(const myString& str); //拷贝构造
myString& operator=(const myString& str); //重载=
myString operator+(const myString& str) const; //重载+
myString& operator+=(const myString& str) ; //重载+=
bool operator==(const myString& str) const; //重载==
char& operator[](int index); //重载[]
~myString(); //析构
//friend istream& operator >> (istream &is, myString &str);//输入
friend ostream& operator<<(ostream &os,const myString &str);//输出
private:
char *m_data;
};
注意到myString这个类是带指针的类,所以我们一定要自己写拷贝构造、拷贝赋值、析构函数防止内存泄漏。
(2)构造函数
首先思考什么时候调用构造函数:暂时能想到这两种情况myString s
,myString s1("hello")
我们可以在类中声明的构造函数为myString(const char *str = 0);
如果是第一种情况,传入默认参数NULL;第二种传入“hello”字符串,需要注意不能更改字符串,所以需要加const
。
总结下:
①构造函数没有返回值;
②使用const防止传入的参数被改变。
③声明中给定一个默认实参,但是在定义中不能带默认实参。
好了,下面开始写函数体,对于传入非空的参数,我们需要做的就是动态申请块内存,再把传入的字符串拷贝进去。
myString::myString(const char* pstr)
{
if (pstr)
{
m_data = new char[strlen(pstr) + 1];
strcpy(m_data, pstr);
}
else
{
m_data = new char[1];
*m_data = '\0';
}
}
这里稍微解释下字符串的存储,如下图字符串以'\0'
结尾,而我们需要得知传入的字符串有多长才能进行内存分配。strlen
这个函数用于计算字符串的长度,但是不包括末尾的'\0'
,所以我们把他加上1才是我们需要动态分配的内存空间。strcpy(m_data, pstr);
函数用于把pstr指针指向的字符串拷贝到m_data指向的字符串。
那么如果传入的参数是NULL呢,我们直接分配一个字节的空间,再把指针指向的值设为字符串结束符'\0'
即可。
(2)拷贝构造函数
之前我们说了带指针的类必须要自己写拷贝构造函数是因为直接使用默认拷贝构造会出现浅拷贝。
所谓浅拷贝就是只复制了字符串的地址给指针,使两个指针同时指向一块内存空间,如果删除分配的内存后,剩下的那么指针变为野指针。我们需要分配内存空间再拷贝实现深拷贝。
拷贝构造函数依旧是构造函数,没有返回值、加上const防止str被改变。函数体内部依旧是先分配空间、再拷贝。
myString::myString(const myString& str)
{
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data