(一四二)继承和动态内存分配

来源:互联网 发布:会计网络课程 编辑:程序博客网 时间:2024/05/21 01:28

当基类、派生类用,或者不用动态内存时,共有四种情况:

(注意,前提是基类的动态内存分配相关函数符合常规使用动态内存的要求)

 

情况一:基类 使用 动态内存分配、派生类新增数据成员 不使用 动态内存分配

假如基类使用动态内存分配(new),其必然设定①构造函数、②复制构造函数、③赋值运算符、④析构函数。

又知,派生类的构造函数(包括默认构造函数),需要调用基类的构造函数。

那么基类的数据成员若使new,则已经没问题。

派生类新增的数据成员,由于不使用new,因此可以按值传递。

 

①对于派生类构造函数:直接将参数赋值对应的数据成员——ok

 

②对于派生类复制构造函数,使用默认的复制构造函数(因此对于派生类新增数据对象使用按值传递,对于基类的数据对象使用基类的复制构造函数)——ok

 

③对于赋值运算符来说,使用默认的赋值运算符,是按值传递(因此对派生类新增的数据成员,按值传递,对基类数据对象,使用基类的赋值运算符函数),如果有特殊需求,则显式调用基类的赋值运算符A::operator=(b);//显式调用基类赋值运算符函数),然后对派生类的数据成员进行处理(③可以参考情况三的赋值运算符处理)——ok

 

④对于析构函数来说,会先调用派生类的析构函数,然后调用基类的析构函数——ok

 

因此:假如派生类新增数据成员不使用动态内存的话,如无特别需求,可以无需特别设置

使用默认的构造函数、复制构造函数、赋值运算符、析构函数即可。

 

如代码:

class A//基类{char*name;public:A(const char*q)//构造函数{name = new char[strlen(q) + 1];strcpy_s(name, strlen(q) + 1, q); }A(const A&a)//复制构造函数{ name = new char[strlen(a.name) + 1];strcpy_s(name, strlen(a.name) + 1, a.name);}virtual ~A() { delete[]name; }//析构函数A&operator=(const A&a)//赋值运算符{if (this == &a)return *this;//为防止自己赋值自己delete[]name;name = new char[strlen(a.name) + 1];strcpy_s(name, strlen(a.name) + 1, a.name);return *this;}friend std::ostream& operator<<(std::ostream&os, const A&b){os << b.name;return os;}};class B :public A//基类的派生类{int id;public:B(const char*a, int q) :A(a)//构造函数{id = q;}B(const B&b) :A(b)//复制构造函数,可省略,使用默认复制构造函数{id = b.id;}B&operator=(const B&b)//赋值运算符,可省略,使用默认的赋值运算符{if (this == &b)return *this;//为防止自己赋值自己A::operator=(b);//显式调用基类赋值运算符函数id = b.id + 5;//为了区分而修改return *this;}friend std::ostream& operator<<(std::ostream&os, const B&b){os << A(b) << "," << b.id;return os;}};

情况二:基类 不使用 动态内存分配、派生类新增数据成员 不使用 动态内存分配

和情况一并没有什么区别。如无特殊需求,使用默认的复制构造函数、赋值运算符即可。

 

 

 

情况三:基类 使用 动态内存分配、派生类新增数据成员 使用 动态内存分配

由于派生类新增数据成员使用动态内存分配,那么显然,不能使用默认构造函数、默认的复制构造函数、默认的赋值运算符、默认的析构函数了(否则无法形成newdelete的对应)。

 

①构造函数:

首先,构造函数需要调用基类的构造函数,是毫无疑问的。因此,基类的动态内存分配ok

其次,对于派生类新增的数据成员,对于需要使用动态内存的,使用new运算符来分配内存。不使用动态内存的,常规处理,因此ok

 

②复制构造函数:

首先,复制构造函数需要调用基类的复制构造函数。将派生类对象作为参数传递给基类初始化列表,由于基类引用可以指向派生类对象,因此可以成功初始化派生类的基类部分。

然后,对派生类新增的部分,对于需要使用动态内存的,使用new运算符来分配内存。不使用动态内存的,常规处理,因此ok

 

③赋值运算符:

首先,需要显式的调用基类的赋值运算符(A::operator=(b);//显式调用基类赋值运算符函数),以使得基类的部分被成功赋值;

其次,对于派生类部分,按照正常方式处理使用动态内存的数据成员(delete后再new),和不使用动态内存的数据成员。

最后,注意在函数的开始部分,添加防止自己赋值给自己的代码。

 

④析构函数:

由于派生类的析构函数,会自动调用基类的析构函数,因此,只对派生类新增的数据成员进行delete释放内存处理。

 

如代码(只修改了派生类B):

class B :public A//基类的派生类{char* id;public:B(const char*a, char*q) :A(a)//构造函数{id=new char[strlen(q)+1];strcpy_s(id, strlen(q) + 1, q);}B(const B&b) :A(b)//复制构造函数{id = new char[strlen(b.id) + 1];strcpy_s(id, strlen(b.id) + 1, b.id);}~B()//析构函数,处理派生类新增数据成员{delete[]id;}B&operator=(const B&b)//赋值运算符{if (this == &b)return *this;//为防止自己赋值自己A::operator=(b);//显式调用基类赋值运算符函数delete[]id;//需要先deleteid = new char[strlen(b.id) + 1];strcpy_s(id, strlen(b.id) + 1, b.id);return *this;}friend std::ostream& operator<<(std::ostream&os, const B&b){os << A(b) << "," << b.id;return os;}};

情况四:基类 不使用 动态内存分配、派生类新增数据成员 使用 动态内存分配

和情况三的办法一样(因为情况三使用基类的各种方法来处理基类部分的数据)。

 

 

总结:

①我忘了给基类加析构函数;

②我加了析构函数,忘了给析构函数加关键字virtual变成虚函数。

 

 

对于友元函数:

如果想在派生类的友元函数中调用基类的友元函数,那么应该使用强制类型转换,即 基类名(派生类对象) ,即可使用,如A(b)

也可以这样:(const 基类名&)派生类对象 表示强制转换派生类对象为const基类类型。

 


0 0
原创粉丝点击