c++类的构建-构建本身的string类 深拷贝

成员变量带指针的string类构建

首先看测试案例:ios

#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;      
}

从以上测试案例中,咱们很容易知道要两个个构造函数重载:c++

//1.普通构造函数
String s1("hello"); 
//2 拷贝构造函数
String s3(s2);

还有两个操做符重载:数组

//1.=
 s3 = s1;
//2.<<
cout << s1 << endl;

如今来思考如何设计:函数

成员变量:String对象应该维护一个字符数组这里,采用char*类型测试

为何不采用 char[]呢?由于不知道有多少个字符,使用char*只须要在结尾加一个/0便可。this

private:
   char* m_data;

构造函数声明部分:spa

有可能直接传入字符串或者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);
}

思考:code

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指针。

相关文章
相关标签/搜索