c++类继承

虚函数工作原理:

给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针(虚函数表)。
虚函数表中存储了为类对象进行声明的虚函数的地址
无论类中包含的虚函数是1个还是10个,都只需要在对象中添加1个地址成员,只是表的大小不同而已。
调用函数时,程序将查看存储在对象中的虚函数地址,然后转向相应的函数地址表。
使用虚函数,在内存和执行速度方面的成本:

每个对象都将增大,增大量为存储地址的空间;
对于每个类,编译器都将创建一个虚函数地址表(数组);
对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。
虽然非虚函数较为高效,但不具备动态联编功能。

虚函数要点:

在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚的;
如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法(即动态联编)。
如果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。

构造函数不能是虚的。创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,派生类不继承基类的构造函数。

析构函数应当是虚函数。通常给基类提供一个虚析构函数,即使它不需要。
友元函数不能是虚函数,因为友元不是类成员,只有类成员才能是虚函数。若派生类中没有重新定义函数,则将使用该函数的基类版本;若派生类位于派生链中,则将使用最新的虚函数版本,基类版本是隐藏的情况下例外。
重新定义将隐藏方法。应遵循(1)如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针(返回类型协变)(2)如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。
2.访问控制:protected

protected与private相似,在类外只能用公有类成员来访问protected部分中的类成员。
protected与private的区别: 派生类成员可以直接访问基类中的保护成员,但不能直接访问基类中的私有成员。
故对外部世界来说,保护成员的行为与私有成员的相似;对于派生类来说,保护成员的行为与公有成员相似。
使用:

对数据成员最后使用私有访问控制,不要使用保护访问控制,同时通过基类方法使派生类能够访问基类数据。
对于成员函数来说,保护访问控制很有用,它让派生类能够访问公众不能使用的内部函数。
抽象基类

若两个类具有很多共性,但又不是is-a的关系时,eg:圆和椭圆?
解决-抽取其共性,放在一个ABC中。
引入纯虚函数:声明的结尾处为=0。
当类声明中包含纯虚函数时,则不能创建该类的对象,包含纯虚函数的类只用作基类。要成为真正的ABC,必须至少包含一个纯虚函数。
ABC要求具体派生类覆盖其纯虚函数-迫使派生类遵循ABC设置的接口规则。这种模型在基于组件的编程模式中很常见。

继承和动态内存分配

继承怎样与动态内存分配(new和delete)互动?

(1)派生类不使用new:
不需要为派生类定义显式析构函数、复制构造函数、赋值运算符。
(2)派生类使用new:
必须为派生类定义显式析构函数、复制构造函数、赋值运算符,且必须使用相应的基类方法来处理基类元素。
析构函数:自动完成;

baseDMA::~baseDMA()
{
delete [] label;
}

hasDMA::~hasDMA()
{
delete [] style;
}

构造函数:通过在初始化列表中调用基类的复制构造函数完成,否则,会自动调用基类的默认构造函数;

baseDMA::baseDMA(const baseDMA & rs)
{
label = new char[std::strlen(rs.label) + 1];
std::strcpy(label, rs.label);
rating = rs.rating;
}

hasDMA::hasDMA(const hasDMA & hs)
: baseDMA(hs) // invoke base class copy constructor
{
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
}
赋值运算符:通过使用作用域解析符显式地调用基类的赋值运算符来完成。

baseDMA & baseDMA::operator=(const baseDMA & rs)
{
if (this == &rs)
return *this;
delete [] label;
label = new char[std::strlen(rs.label) + 1];
std::strcpy(label, rs.label);
rating = rs.rating;
return *this;
}

hasDMA & hasDMA::operator=(const hasDMA & hs)
{
if (this == &hs)
return *this;
baseDMA::operator=(hs); // copy base portion
delete [] style; // prepare for new style
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
return *this;
}

baseDMA::operator=(hs); 含义为:* this = hs;
使用后面的代码时,编译器将使用hasDMA::operator=( ),形成递归调用。使用函数表示法使得赋值运算符被正确调用。

小结:
1.编译器生成的成员函数
(1)默认构造函数

默认构造函数要么没有参数,要么所有的参数都有默认值;
如果没有定义任何构造函数,编译器将定义默认构造函数;让您能创建对象,另一功能是调用基类的默认构造函数以及调用本身是对象的成员所属的默认构造函数;
若派生类构造函数的成员初始化列表中没有显式调用基类构造函数,则编译器使用基类的默认构造函数来构造派生类对象的基类部分;
如果定义了某种构造函数,编译器将不提供默认构造函数,若需要,需自己提供;
提供构造函数的动机之一就是确保对象总能被正确地初始化,若类包含指针成员,则必须初始化这些成员。
因此,最好提供一个显式默认构造函数,将所有的数据成员都初始化为合理的值。

(2)复制构造函数
在下述情况下,将使用复制构造函数:

将新对象初始化为一个同类对象;
按值将对象传递给函数;
函数按值返回对象;
编译器生成临时对象。
若程序没有使用复制构造函数,编译器将提供原型,但不提供函数定义;否则,程序将执行成员初始化的复制构造函数,即新对象的每个成员都被初始化为原始对象对应的成员的值。如果成员为类对象,则初始化该成员时,将使用相应类的复制构造函数。

(3)赋值运算符
默认的赋值运算符用于处理同类对象之间的赋值。
原型:

Star & Star::operator=(const Star *) { … }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值