繼承與動態內存分配
阿新 • • 發佈:2019-02-14
避免 析構 turn 其中 更多 變量 差異 動態內存分配 構造
當繼承和動態內存分配的問題交織在一起的時候,我們考慮類實現的時候,就需要考慮更多的東西,先上代碼:
1 #ifndef DMA_H 2 #define DMA_H 3 # include"iostream" 4 using namespace std; 5 class BaseDMA 6 { 7 //protected: //將父類私有成員變量private聲明為protected,則繼承類也可直接訪問成員變量,private表明除了自己的類,其余的都不可直接訪問(就算是繼承子類也不可以) 8 //char* label; 9 //int rating; 10 private: 11 char* label; 12 int rating; 13 public: 14 BaseDMA(const char*l = "null", int r = 0);//構造函數 15 BaseDMA(const BaseDMA & rs);//拷貝函數 16 virtual ~BaseDMA();//虛析構函數,思考虛析構函數的意義,虛析構函數是為了避免多態時釋放造成的內存泄露,因此,我們將基類的析構函數定義為虛函數是有好處的 17 BaseDMA & operator=(const BaseDMA & rs);//賦值運算符重載,返回 BaseDMA & 是為了連續賦值,那麽這又是為什麽呢???18 friend ostream& operator<<(ostream& os, const BaseDMA & rs);//實際上,這裏表明了ostream是一個類,返回ostream&是為了連續顯示,友元函數並不是類成員函數 19 }; 20 class LackDMA :public BaseDMA 21 { 22 23 private: 24 enum{COL_LEN=40};//采用這樣的定義類型更便於程序的管理 25 char color[COL_LEN]; 26 public: //思考要不要重新設置構造函數,析構函數,以及賦值拷貝函數,以及為什麽不需要修改27 LackDMA(const char*c="black" , const char*l = "null", int r =0); 28 LackDMA(const char*c , const BaseDMA & rs); 29 LackDMA(const LackDMA & rs); 30 friend ostream& operator<<(ostream& os, const LackDMA & rs); 31 }; 32 33 class HasDMA :public BaseDMA 34 { 35 private: 36 char *style; 37 public: 38 HasDMA(const char*s = "none", const char*l = "null", int r = 0); 39 HasDMA(const char*s, const BaseDMA & rs); 40 HasDMA(const HasDMA & rs); 41 ~HasDMA();//為何上述lackdma代碼段不需要重新定義析構函數,為何這裏需要顯示定義析構函數 42 HasDMA & operator=(const HasDMA & rs);//為何這裏的賦值拷貝函數也需要重新定義 43 friend ostream& operator<<(ostream& os, const HasDMA & rs); 44 }; 45 #endif
上述類聲明中,定義了一個基類BaseDMA,以及由該基類衍生的兩個子類:LackDMA,HasDMA;其中,LackDMA類不涉及動態內存分配,HasDMA涉及動態內存分配。關於所涉及的其他知識細節,暫時不進行描述。下面給出類實現部分代碼:
1 #include"dma.h" 2 # include "cstring" 3 using namespace std; 4 /////////BaseDMA類成員函數實現///////////////////////// 5 BaseDMA::BaseDMA(const char*l = "null", int r = 0) 6 { 7 label = new char[strlen(l) + 1]; 8 strcpy(label, l); 9 rating = r; 10 } 11 BaseDMA::BaseDMA(const BaseDMA & rs) 12 { 13 label = new char[strlen(rs.label) + 1]; 14 strcpy(label, rs.label); 15 rating = rs.rating; 16 } 17 18 BaseDMA::~BaseDMA()//在虛構函數實現的時候,並不需要virtual關鍵字 19 { 20 delete[] label; 21 } 22 //我們發現下面的賦值函數和采用類引用的拷貝函數基本功能一致,思考其內在聯系 23 BaseDMA & BaseDMA:: operator=(const BaseDMA & rs)// 註意返回類型要放在作用域的前面 24 { 25 if (this == &rs)//思考在什麽時候顯示的使用this 指針 26 return *this; 27 delete[] label;//這裏為何要使用delete[]刪除原來 28 label = new char[strlen(rs.label) + 1]; 29 strcpy(label, rs.label); 30 rating = rs.rating; 31 return *this; 32 } //仔細思考這一段代碼背後的機制 33 34 ostream& operator<<(ostream& os, const BaseDMA & rs)//需要註意的吧是。友元函數並不屬於類的成員函數,因此這裏並沒有用BaseDMA::進行類作用域約束 35 { 36 os << "Label: " << rs.label << endl; 37 os << "rating: " << rs.rating << endl; 38 return os;//返回os是為了形成對<<a<<b的連續顯示效果 39 } 40 ///////////////////////LackDMA類成員函數實現/////////////////////////// 41 LackDMA::LackDMA(const char*c = "black", const char*l = "null", int r = 0) :BaseDMA(l, r) 42 { 43 strncpy(color, c, 39);//註意這裏的拷貝函數不再是strcpy而是strncpy; 44 color[39] = ‘\0‘;//結束標誌符 45 } 46 47 LackDMA::LackDMA(const char*c, const BaseDMA & rs) :BaseDMA(rs) 48 { 49 strncpy(color, c, 39); 50 color[39] = ‘\0‘; 51 } 52 53 LackDMA::LackDMA(const LackDMA & rs) :BaseDMA(rs) 54 { 55 strncpy(color, rs.color, 39); 56 color[39] = ‘\0‘; 57 } 58 59 ostream& operator<<(ostream& os, const LackDMA & rs) 60 { 61 //os << "Label: " << rs.label << endl;//遇到這種子類無法訪問父類,該怎麽辦???除了可以使用將private聲明為protected之外 62 //os << "rating: " << rs.rating << endl;//我們當然可以使用將private成員聲明成protected成員,使得子類獲得訪問權限。 63 os << (const BaseDMA&)rs;//本質是通過基類的成員函數訪問基類的成員變量,註意,子類是無法訪問父類的私有成員變量的,必須通過父類的公有的成員函數進行訪問。 64 os << "color: " << rs.color << endl; 65 return os; 66 } 67 ////////////hasDMA類成員函數實現///////// 68 HasDMA::HasDMA(const char*s = "none", const char*l = "null", int r = 0) :BaseDMA(l, r) 69 { 70 style = new char[strlen(s) + 1]; 71 strcpy(style, s); 72 } 73 HasDMA::HasDMA(const char*s, const BaseDMA & rs) : BaseDMA(rs) 74 { 75 style = new char[strlen(s) + 1]; 76 strcpy(style, s); 77 } 78 HasDMA::HasDMA(const HasDMA & rs) : BaseDMA(rs) 79 { 80 style = new char[strlen(rs.style) + 1]; 81 strcpy(style,rs.style); 82 } 83 HasDMA::~HasDMA() 84 { 85 delete[] style; 86 } 87 HasDMA & HasDMA:: operator=(const HasDMA & rs) //賦值運算符代碼究竟該怎麽寫,寫成:BaseDMA(rs)為什麽是一種錯誤的寫法 88 { 89 if (this == &rs) 90 return *this; 91 BaseDMA:: operator=(rs);//無論是哪種初始化方式,對基類的賦值是必不可少的。但這裏為何要采用這種形式呢? 92 delete[] style; 93 style = new char[strlen(rs.style) + 1]; 94 strcpy(style, rs.style); 95 return *this;//返回對象自身 96 } 97 ostream& operator<<(ostream& os, const HasDMA & rs) 98 { 99 os << (const BaseDMA &)rs;//本質上是通過基類的方法(基類的運算符重載)訪問基類的成員變量,原理同LackDMA中描述的相同。 100 os << "style:" << rs.style << endl; 101 return os; 102 }
上述類成員實現的代碼中,涉及了很多的知識細節,我們尤其要關註的是:
1 子類不能直接訪問父類的私有成員,必須通過父類的共有成員函數對其進行訪問(如代碼91行和99行,但這兩處有所區別,思考其中的差異性),
2 將private聲明成protected,可以使得子類獲得原私有成員的訪問權限。(protected的作用就是為子類提供了一個訪問權限,但對外仍然和private相同)
3 友元函數並不是類成員函數,因此在函數實現的時候,並沒有對其約定作用域解析符。
4 對子類進行初始化的時候,一定是先對父類進行初始化。(無論是采用最原始的初始化,還是復制初始化,還是賦值初始化)
5 思考92行的代碼,為何進行delete[],那麽何時進行的new呢???
思考從聲明子類到銷毀子類,整個程序的執行過程???!!!
繼承與動態內存分配