继承与动态内存分配
当继承和动态内存分配的问题交织在一起的时候,我们考虑类实现的时候,就需要考虑更多的东西,先上代码:
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呢???
思考从声明子类到销毁子类,整个程序的执行过程???!!!
stay foolish,stay hungry