首先看測試案例: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
假設類中有指針變量,就意味着要動態分配內存 ,由於不動態分配內存,執行完構造以後,棧上的函數內存就會被清空,指針將會指向一個不合法的地址。
假設類中有指針變量,則必須重寫析構函數去釋放它。既然使用了動態內存,那就意味堆中的內存被分配出去了。若採用默認的析構函數,消滅指針所佔內存(&x),可是並無消滅指針所指向地址所佔內存(*x),所以,必須在析構函數中delete指針。