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里得到的经验
  1. 假设类中有指针变量,就意味着要动态分配内存 ,因为不动态分配内存,执行完构造之后,栈上的函数内存就会被清空,指针将会指向一个不合法的地址。

  2. 假设类中有指针变量,则必须重写析构函数去释放它。既然使用了动态内存,那就意味堆中的内存被分配出去了。若采用默认的析构函数,消灭指针所占内存(&x),但是并没有消灭指针所指向地址所占内存(*x),因此,必须在析构函数中delete指针。

posted @ 2020-12-11 09:47  lsxkugou  阅读(213)  评论(0编辑  收藏  举报