第13章 类继承
13.1.2.构造函数:访问权限的考虑
(1)派生类不能直接访问基类的私有成员,而必须通过基类方法,派生类的构造函数必须使用基类构造函数
RatedPlayer::RatePlayer(unsigned int r, const string &fn,const string &ln,bool ht):TableTennisPlayer(fn,ln,ht)
{
rating = r;
}
(1) 如果不调用基类构造函数,程序将使用默认的基类构造函数
RatedPlayer::RatePlayer(unsigned int r, const string &fn,const string &ln,bool ht):TableTennisPlayer()
{
rating = r;
}
(3)
RatedPlayer::RatePlayer(unsigned int r,cosnt TableTennisPlayer &tp/*调用复制构造函数*/):TableTennisPlayer(tp)
{
rating = r;
}
(4)创建派生类对象是,程序先调用基类构造函数,然后才调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。
(5)派生类的构造函数总是要调用基类的构造函数。可以用初始化列表的语法要指明要使用基类的构造函数,否则将使用默认的基类构造函数
13.2继承:is-a关系(is-a-kind-of)
Has-a Fruit 对象作为Lunch类的数据成员
Is-like-a
Is-implemented-as-a(作为……..来实现)例如:可以用数组实现栈,但从Array类派生出Stack类是不合适的,因为栈不是数组。例如:数组的索引不是栈的属性,正确:
通过让栈包涵体格Array对象成员来隐藏数组实现
Uses-a:例如:可以使用友元函数或类处理Printer对象和Computer对象之间的通信。
13.3多态公有继承
(1)如果没有virtual,程序将根据引用类型或指针类型选择方法;如果使用virtual程序将根据引用和指针指向的对象的类型来选择方法。
(2)为什么要调用虚析构函数?
如果析构函数不是虚的,则将只调用对应于指针类型的析构函数。
如果是虚的,则将调用相应对象类型的析构函数,然后在调用基类的析构函数。
作用:使用虚析构函数可以确保正确的析构函数序列被调用
13.4静态联编和动态联编
(1)编译器对非虚方法使用静态联编
编译器对虚方法使用动态联编
(2) 为什么默认为静态联编?P515
[1]效率:静态联编效率高
[2]概念:可能包含一些不在派生类重新定义的成员函数,不将这些函数设置为虚函数
13.4.2
(1)虚函数的工作原理:
给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表,虚函数表中储存了为类进行声明的虚函数地址。例如:基类对象中包含了一个指针,改指针指向基类中所有虚函数的地址表
调用虚函数时,程序将查看储存在对象vtbl地址,然后转向相应的虚数地址表。如果使用类声明中定义了的第一个虚函数,则程序将使用数组中的第一个虚数地址,并执行具有改地址的函数。
缺点:[1]每个对象都将增大,增大量为储存地址的空间
[2]对于每个类,编译器都创建一个虚函数地址表(数组)
[3]对于每个函数调用,都需要执行一项额外的操作,机到表中查找地址
13.4.3
(1)构造函数:不能是虚函数。创建派生类对象,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将使用基类的一个构造函数-------派生类要使用基类的构造函数
(2)析构函数:
通常应该给基类提供一个虚析构函数,即使它并不需要析构函数
(3)友元:友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数
(4)重新定义将隐藏方法
Class Dwelling
{
public :
virtual void showperks(int a)const;
};
Class Hovel:public Dwelling
{
Public:
virtual void showperks()const ;
};
重新定义不会生成函数的两个重载版本,而是隐藏了接收int参数的基类版本,隐藏同名函数的基类方法,不管参数特征标如何。
两条经验:
[1]如果重新定义继承方法,应确保与原来的原型完全相同,返回类型协变,因为返回类型随类类型的变化而变化
class Dwelling
{
public :
virtual Dwelling &bulid(int n);
};
class Hovel:public Dwelling
{
Public:
virtual Hovel &build(int n)
};这种例外只是适用于返回值,不适用于参数
[2]如果基类被重载了,则应该在派生类中重新定义所有的基类版本。
class Dwelling
{
Public :
Virtual void showperks(int a) const;
Virtual void showperks(double x)const;
Virtual void showperks() cosnt;
};
Class Hovel:public Dwelling
{
Public :
Virtual void showperks(int a) const;
Virtual void showperks(double x)const;
Virtual void showperks() cosnt;
};
如果只重新定义一个版本,则另外两个版本将被隐藏,派生类将无法使用它们。
注意:如果不需要修改它们,则重新定义只需要基类版本
Void Hovel::showperks() const
{
Dwelling::showperks();
}
13.7继承和动态内存分配
#include<iostream>
#include<string.h>
using namespace std;
class baseDMA
{
private:
char *label;
int rating;
public :
baseDMA(const char *l="null",int r = 0 );
baseDMA(const baseDMA &rs);
virtual ~baseDMA();
baseDMA &operator =(const baseDMA &rs);
};
baseDMA::baseDMA(const char *l,int r )
{
label = new char[strlen(l)+1];
strcpy(label,l);
rating = r;
cout<<" baseDMA::baseDMA(const char *l,int r )"<<endl;
}
baseDMA::baseDMA (const baseDMA &rs)
{
label = new char [strlen(rs.label)+1];
strcpy(label,rs.label);
rating = rs.rating;
cout<<"baseDMA::baseDMA (const baseDMA &rs)"<<endl;
}
baseDMA::~baseDMA()
{
delete []label;
cout<<"baseDMA::~baseDMA()"<<endl;
}
baseDMA &baseDMA::operator =(const baseDMA &rs)
{
if(this==&rs)
{
cout<<"baseDMA &baseDMA::operator =(const baseDMA &rs)"<<endl;
return *this;
}
delete []label;
label = new char [strlen(rs.label)+1];
strcpy(label,rs.label);
rating = rs.rating;
cout<<"baseDMA &baseDMA::operator =(const baseDMA &rs)"<<endl;
return *this;
}
/******************************************************************************/
class lackDMA:public baseDMA
{
private:
char *color;
public:
lackDMA(const char *color)
{
color = new char [strlen(color)+1];
strcpy(this->color,color);
cout<<"lackDMA(const char color[40])"<<endl;
}
lackDMA(const lackDMA &hs):baseDMA(hs)/*!!!!!!!!!!!!!!*/
{
color = new char [strlen(hs.color)+1];
strcpy(color,hs.color);
cout<<"lackDMA(const hasDMA &hs):baseDMA(hs)"<<endl;
}
lackDMA & operator =(const lackDMA &hs)
{
if(this == &hs)
{
return *this;
}
baseDMA::operator=(hs);/*!!!!!!!!!!!!!!*/
//*this = hs;
delete []color;
color = new char[strlen(hs.color)+1];
strcpy(color,hs.color);
return *this;
}
virtual ~lackDMA()
{
delete []color;
cout<<"virtual ~lackDMA()"<<endl;
}
};
int main()
{
{
lackDMA lack1("hello world");
cout<<"-----------------------"<<endl;
lackDMA lack2("hello yuanyuan");
lack2=lack1;
cout<<"-----------------------"<<endl;
}
return 0;
}
13.8.2
(1)按值传递对象与传递引用
[1]提高效率,按值传递对象涉及到生成临时拷贝,即调用复制构造函数,然后调用析构函数。如果函数不修改对象,应将参数声明为const。
[2]在继承使用虚函数时,被定义为接收基类引用参数的函数可以接收派生类
(2)返回对象和返回引用
测试:
#include<iostream>
using namespace std;
class A
{
public :
int a;
public :
A(int a)
{
this->a = a;
cout<<"A(int a)"<<endl;
}
A(const A &aa)
{
this->a=aa.a;
cout<<"A(const A &aa)"<<endl;
}
A &operator =(const A &aa)
{
this->a = aa.a;
cout<<"A &operator =(const A &aa)"<<endl;
}
~A()
{
cout<<"~A"<<endl;
}
};
A play(const A &a)
{
return a;
}
int main()
{
{
//A a(100);
//A b(200);
//b=play(a);
A temp=play(5);
}
return 0;
}
(3)使用const
[1]确保方法不修改参数
[2]不修改调用它的对象
[3]可以使用const来确保引用和指针返回的值不能用于修改对象中的数据
[4]P539
13.8.3
什么不能被继承?
(1) 构造函数:创建派生类对象时,必须调用派生类的构造函数
(2) 析构函数:在释放对象时,程序将首先调用派生类的析构函数,然后调用基类的析构函数。如果基类有默认析构函数,编译器将为派生类生成默认析构函数
(3) 赋值运算符:因为它包含一个类型为其所属类的形参。
可以通过强制类型转换将,派生类的引用或指针转换为基类引用或指针,然后使用转换后的指针或引用调用基类的友元函数:
ostream &operator<<(operator &os,const hasDMA &hs)
{
os<< (const baseDMA&)hs;
os<<”Style: “<<hs.style<<endl;
return os;
}
第13章 类继承
最新推荐文章于 2023-05-10 19:35:01 发布