继承与动态内存分配

当继承和动态内存分配的问题交织在一起的时候,我们考虑类实现的时候,就需要考虑更多的东西,先上代码:

 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] = '';//结束标志符
 45 }
 46 
 47 LackDMA::LackDMA(const char*c, const BaseDMA & rs) :BaseDMA(rs)
 48 {
 49     strncpy(color, c, 39);
 50     color[39] = '';
 51 }
 52 
 53 LackDMA::LackDMA(const LackDMA & rs) :BaseDMA(rs)
 54 {
 55     strncpy(color, rs.color, 39);
 56     color[39] = '';
 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呢???

思考从声明子类到销毁子类,整个程序的执行过程???!!!

原文地址:https://www.cnblogs.com/shaonianpi/p/10375561.html