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

相關文章
相關標籤/搜索