c++类的构建-构建自己的string类 深拷贝
成员变量带指针的string类构建
首先看测试案例:
#include "string.h"
#include <iostream>
using namespace std;
int main()
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
}
从以上测试案例中,我们很容易知道要两个个构造函数重载:
//1.普通构造函数
String s1("hello");
//2 拷贝构造函数
String s3(s2);
还有两个操作符重载:
//1.=
s3 = s1;
//2.<<
cout << s1 << endl;
现在来思考如何设计:
成员变量:String对象应该维护一个字符数组这里,采用char*类型
为什么不采用 char[]呢?因为不知道有多少个字符,使用char*只需要在结尾加一个/0即可。
private:
char* m_data;
构造函数声明部分:
有可能直接传入字符串或者string对象,所以要构造函数重载。且原值应该是不变的,所以加上const。
String(const char* cstr=0);
String(const String& str);
构造实现
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
思考:
1)为什么在String(const String& str)中,不直接对指针进行复制? 这里要采用深拷贝,也是为什么不用编译器自动提供的拷贝构造的原因。
2)这里使用了动态内存,那析构函数是不是也要重写?不重写的话char指针没有被释放,会不会造成内存泄漏?结论是,应该重写析构函数:
inline
String::~String()
{
delete[] m_data;
}
操作符重载部分:
1.=号的设计:
我们用=号的时候是这样一个场景,s3=s1,所以实际上是直接将s1的数据拷贝到s3上。
返回值应该是&类型,因为返回的是一个对象,引用更快。
参数是一个const string& 类型,const的原因是不能改变s1的值,&是为了传输更快
String& operator=(const String& str);
实现:
inline
String& String::operator=(const String& str)
{
//自我检测,是不是 s1=s1
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
思考:
为什么要delete[] m_data;?
因为原本可能左值就已经保存了一个字符串的地址了,将其释放才能重新赋值,不然就会造成内存泄漏。
为什么必须要自我检测?不仅仅是更快。 假设是 s1 = s1;这种情况 delete[] m_data;将会将左右值维护得字符串全部清空,那这样strcpy就无法正确复制内存了。
2.<<的设计
我们经常使用cout<< string 这样的模式
所以需要两个参数,第一个是string类型,第二个是cout对象,也就是ostream类型;
思考:string类型是完全不用变的,所以直接加const,而ostream里的缓冲区是时刻在变的,所以不能加const。
返回值类型应该是什么?
首先想到的应该是void,因为只需要将string里的字符数组输入到ostream流中即可。
但是还有另一种情况,即: cout << s1 << s2.这样的连续输出
因此,我们返回的应该是ostream. 这样进行流的套接。
最后:且因为这个符号左边是输出流对象,右边是字符串对象,所以不能写在类内部,应该直接设置成全局函数。写在类内部的话,左值固定为this。这样就成了 s1<<cout。不符合人的使用逻辑。
因此函数原型为:
ostream& operator<<(ostream& os, const String& str)
下面是全部代码:
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
从string里得到的经验
-
假设类中有指针变量,就意味着要动态分配内存 ,因为不动态分配内存,执行完构造之后,栈上的函数内存就会被清空,指针将会指向一个不合法的地址。
-
假设类中有指针变量,则必须重写析构函数去释放它。既然使用了动态内存,那就意味堆中的内存被分配出去了。若采用默认的析构函数,消灭指针所占内存(&x),但是并没有消灭指针所指向地址所占内存(*x),因此,必须在析构函数中delete指针。