第十三章 类继承
2020-02-24 10:40:44 11 举报
AI智能生成
C++ plus
作者其他创作
大纲/内容
前言
继承作用
在已有类的基础上添加功能
eg:对于数组类,可以添加数学运算
给类添加数据
修改类方法的行为
一个简单的基类
基类
代码
TabletennisPlayer
TabletennisPlayer
using std::string;
class TableTennisPlayer
{
private:
string name;
bool hasTable;
public:
TableTennisPlayer(const string & n="none",bool ht=false);
void Name() const;
bool HasTable() const { return hasTable; }
void ReasetTable(bool v){ hasTable=v;}
};
TableTennisPlayer::TableTennisPlayer (const string &n,bool ht)
:name(n),hasTable(ht)
{}
void TableTennisPlayer::Name() const
{
std::cout<<name<<std::endl;
}
class TableTennisPlayer
{
private:
string name;
bool hasTable;
public:
TableTennisPlayer(const string & n="none",bool ht=false);
void Name() const;
bool HasTable() const { return hasTable; }
void ReasetTable(bool v){ hasTable=v;}
};
TableTennisPlayer::TableTennisPlayer (const string &n,bool ht)
:name(n),hasTable(ht)
{}
void TableTennisPlayer::Name() const
{
std::cout<<name<<std::endl;
}
using std::cout;
TabletennisPlayer player1("Ben",true);
player1.Name();
if (olayer1.HasTable)
cout<<":has a table\n";
else
cout<<"hasn`t a table.\n";
TabletennisPlayer player1("Ben",true);
player1.Name();
if (olayer1.HasTable)
cout<<":has a table\n";
else
cout<<"hasn`t a table.\n";
说明
初始化列表
如果构造函数不用初始化列表会
调用默认构造函数(string的)
再用复制构造函数将name初始化为name
调用默认构造函数(string的)
再用复制构造函数将name初始化为name
TableTennisPlayer::TableTennisPlayer (const string &n,bool ht)
{
name=n;
hasTable=ht;
}
{
name=n;
hasTable=ht;
}
TabletennisPlayer player1("Ben",true);
C-风格字符串作为参数。
C-风格字符串作为参数。
但构造函数的形参类型为const string &,导致类型不匹配,但是string类
有一个将const char *作为参数的构造函数,使用C-风格字符串初始化string
对象时,将自动调用这个构造函数 。//??
有一个将const char *作为参数的构造函数,使用C-风格字符串初始化string
对象时,将自动调用这个构造函数 。//??
可以将string对象或C-风格字符串作为构造函数TableTennisPlayer的参数。
派生类
代码
//类声明
class RatePlayer:public TableTennisPlayer//public 是不是意味着还有private?
{
private:
unsigned int rating;//rate是成员的比分。
public:
RatedPlayer(unsigned int r=0,const string &n,bool ht=false);
RatedPlayer(unsigned int r=0,const TableTennisPlayer & tp);
unsigned int Rating() const {return rating;}
void RestRating(unsigned int r){rating = r;}
} ;
//类定义
RatePlayer::RatePlayer(unsigned int r,const string &n ,bool ht)
:TableTennisPlayer(n,ht)
{
rating =r;
}
RatePlayer::RatePlayer(unsigned int r,const TableTennisPlayer &tp)
:TableTennisPlayer(tp)
{
rating =r;
}
class RatePlayer:public TableTennisPlayer//public 是不是意味着还有private?
{
private:
unsigned int rating;//rate是成员的比分。
public:
RatedPlayer(unsigned int r=0,const string &n,bool ht=false);
RatedPlayer(unsigned int r=0,const TableTennisPlayer & tp);
unsigned int Rating() const {return rating;}
void RestRating(unsigned int r){rating = r;}
} ;
//类定义
RatePlayer::RatePlayer(unsigned int r,const string &n ,bool ht)
:TableTennisPlayer(n,ht)
{
rating =r;
}
RatePlayer::RatePlayer(unsigned int r,const TableTennisPlayer &tp)
:TableTennisPlayer(tp)
{
rating =r;
}
声明
格式
class RatedPlayer : public TableTennisPlayer
{
......
}
{
......
}
说明
表明TableTennisPlayer是一个公有基类。派生类对象包含基类对象。
公有派生:基类公有成员函数成为派生类的公有成员,基类的保护成员函数称为派生类的
保护成员函数。
私有部分称为派生类的一部分。(只能通过基类的公有保护方法访问)!!!
保护成员函数。
私有部分称为派生类的一部分。(只能通过基类的公有保护方法访问)!!!
派生类对象特征
派生类对象存储了基类的数据成员
可以使用基类的方法。
不能继承的函数
构造函数(包括复制构造函数)
析构函数
赋值运算符
在继承的特性上
需要
需要
派生类需要自己的构造函数
派生类需要自己的析构函数
子主题
赋值函数(其实可有可无)
//基类赋值函数不会继承
//基类赋值函数不会继承
系统默认的会把基类部分调用基类的赋值函数
可以根据需要添加额外的数据成员和成员函数
派生构造函数
调用原理
创建派生类对象时,先创建基类对象(先调用基类对象的构造函数),
再进入派生类的构造函数中。
再进入派生类的构造函数中。
一般做法
派生类的构造函数要给基类和派生类的新成员提供数据。
基类用初始化列表的方法赋初值
基类用初始化列表的方法赋初值
如果省略成员初始化列表。程序先给基类调用默认的构造函数。
(反正都要用到构造函数),派生类的构造函数总是调用一个基类
的构造函数
(反正都要用到构造函数),派生类的构造函数总是调用一个基类
的构造函数
对比类中含有其它类对象,也是这样子的!!!
格式
RatePlayer(unsigned int r ,const string & fn ,const string &ln , bool ht )
: TableTennisPlayer( fn , ln , ht )
{ .... }
: TableTennisPlayer( fn , ln , ht )
{ .... }
引(成员初始化列表)
法1
class Test2{
private:
int a ;
const int b;
}
private:
int a ;
const int b;
}
Test2::Test2( int aa ,int bb )
:a(aa),b(bb)
:a(aa),b(bb)
b是常量一定要这样做
法2
class Test1
{ ...... }
class Test2{
private:
Test1 t1;
.......
}
{ ...... }
class Test2{
private:
Test1 t1;
.......
}
Test2::Test2( Test1 & tt )
: t1( tt );
: t1( tt );
Test2::Test2( int s ,int t )
: t1( s , t );
: t1( s , t );
法3:如左//只能类继承吗?
好像是的
说明
TableTennisPlayer( fn , ln , ht ) 直接用构造函数
子主题
派生类对象过期时,先调用派生类对象的析构函数,再调用基类的析构函数。
除虚基类外,类只能将值传递回相邻的基类。//???
使用
TableTennisPlayer player1("Tara",false);
RatedPlayer rplayer1(1140,"Mallory",true);
//RatePlater 类可用TableTennisPlayer的类。
rplayer1.Name();
if(rplayer1.HasTable())
cout<<":has a table\n";
else
cout<<":hasn`t a table\n";
RatedPlayer rplayer2(112,player1)
cout<<"Name:";
rplayer2.Name();
cout<<":Rating:"<<rplayer2.Rating()<<endl;
RatedPlayer rplayer1(1140,"Mallory",true);
//RatePlater 类可用TableTennisPlayer的类。
rplayer1.Name();
if(rplayer1.HasTable())
cout<<":has a table\n";
else
cout<<":hasn`t a table\n";
RatedPlayer rplayer2(112,player1)
cout<<"Name:";
rplayer2.Name();
cout<<":Rating:"<<rplayer2.Rating()<<endl;
派生类和基类之间的特殊关系
关系
派生类可以使用基类的方法(除了私有的
//怎么使用?使用方法?
//怎么使用?使用方法?
class TableTennis{
public:
void f1();
void f2();
}
class RatePlayer:public TableTennis{
public:
void f1();
void ex();
}
//在派生类实现中
void ex()
{
f2();
TableTennis::f1();
}
public:
void f1();
void f2();
}
class RatePlayer:public TableTennis{
public:
void f1();
void ex();
}
//在派生类实现中
void ex()
{
f2();
TableTennis::f1();
}
可以不带对象直接调用。
如果基类与派生类有同名的函数,又需要用基类的函数。要有作用限定域。
没有同名的基类函数,可直接调用。
如果基类与派生类有同名的函数,又需要用基类的函数。要有作用限定域。
没有同名的基类函数,可直接调用。
基类指针可以(在不进行显式类型转换的情况下)指向派生类对象
基类引用可以(在不进行显式类型转换的情况下)引用派生类对象
例子
RatdPlayer rplayer1(1140,"Mallory","Duck",true);
TableTennisPlayer & rt = rplayer;
TableTennisPlayer * pt = &rplayer;
rt.Name();
pt->Name();
TableTennisPlayer & rt = rplayer;
TableTennisPlayer * pt = &rplayer;
rt.Name();
pt->Name();
特点
基类指针(引用)只能用于调用基类的方法。//在没有任何虚函数的情况下
注意派生类指针(引用)不能指向(引用)基类
应用!
基类引用定义的函数或者指针参数可
用于积累对象或者派生类对象
//??看不懂,直接看例子把
用于积累对象或者派生类对象
//??看不懂,直接看例子把
void Show( const TableTennisPlayer & rt)//注意是引用或者指针。
{
...........
}
TableTennisPlayer player1("Tara",true);
RatedPlayer rplayer1(1140,"Tom",true);
Show( player1 );
Show( rplayer1 );
{
...........
}
TableTennisPlayer player1("Tara",true);
RatedPlayer rplayer1(1140,"Tom",true);
Show( player1 );
Show( rplayer1 );
将基类对象初始化为派生类对象
例子
RatedPlayer player1( 1840,"Tara",true );
TableTennisPlayer player2( player1 );
TableTennisPlayer player2( player1 );
原理
赋值给player2时,调用复制构造函数 TableTennisPlayer(const TableTennisPlayer & )
引用可以接受基类和派生类
引用可以接受基类和派生类
将派生类对象赋给基类对象
例子
RatedPlayer player1( 1840,"Tara",true );
TableTennisPlayer player2;
player2 = player1;
TableTennisPlayer player2;
player2 = player1;
原理
ditto
继承is-a 关系
继承方式
公有继承
is -a 关系
保护继承
私有继承
is - a 关系含义
派生类是基类的一种类型
派生类 is a kind of 基类
派生类 is a kind of 基类
例子:
基类:fruit
香蕉类: banana
基类:fruit
香蕉类: banana
不能描述的关系
has a
派生类 has a 基类
午餐有fruit,但不能从fruit 中派生出来
is like a
is implemented as a( 作为....来实现 )
栈可以通过数组来实现,但是从数组中派生出栈不合适。
use a
计算机可以使用激光打印机,从Printer中派生Computer不合适
多态公有继承
含义
同一个方法在派生类和基类中的行为不同。取决于调用方法的对象。
方式(机制)
在派生类中重新定义基类的方法
使用虚方法
代码
class Brass{
private:
....
public:
...
virtual void Withdraw( double amt );
...
virtual void ViewAcct() const;
...
virtual ~Brass() {}
};
class BrassPlus{
private:
....
public:
...
virtual void Withdraw( double amt );
...
virtual void ViewAcct() const;
...
~BrassPlus();
};
private:
....
public:
...
virtual void Withdraw( double amt );
...
virtual void ViewAcct() const;
...
virtual ~Brass() {}
};
class BrassPlus{
private:
....
public:
...
virtual void Withdraw( double amt );
...
virtual void ViewAcct() const;
...
~BrassPlus();
};
格式
virtual void Withdraw( double amt );
说明
在基类中的类声明函数前缀中 + virtual
一般也在派生类声明中 + virtual 表明是虚函数
作用
一般成员函数
不加virtual
方法通过常规变量
调用
调用
Brass dom(....);
Brassplus dot(.....);
dom.ViewAcct(); //Brass 的ViewAcct;
dot.ViewAcct();// Brassplus 的ViewAcct;
Brassplus dot(.....);
dom.ViewAcct(); //Brass 的ViewAcct;
dot.ViewAcct();// Brassplus 的ViewAcct;
根据对象的类型来调用类相关的方法
方法通过指针/引用调用
Brass & b1_ref = dom;
Brass & b2_ref = dot;
b1_ref.ViewAcct() ; //Brass::ViewAcct;
b2_ref.ViewAcct() ; //Brass::ViewAcct;
Brass & b2_ref = dot;
b1_ref.ViewAcct() ; //Brass::ViewAcct;
b2_ref.ViewAcct() ; //Brass::ViewAcct;
根据指针/引用的类型来调用类相关的方法
Brass & b1_ref ;
Brass & b2_ref ;
b1_ref = new Brass;
b2_ref = new BrassPlusl;
b1_ref,ViewAcct(); //Bass::ViewAcct
b2_ref.ViewAcct();//Bass::ViewAcct
Brass & b2_ref ;
b1_ref = new Brass;
b2_ref = new BrassPlusl;
b1_ref,ViewAcct(); //Bass::ViewAcct
b2_ref.ViewAcct();//Bass::ViewAcct
加virtual
方法通过常规变量
调用
调用
Brass dom(....);
Brassplus dot(.....);
dom.ViewAcct(); //Brass 的ViewAcct;
dot.ViewAcct();// Brassplus 的ViewAcct;
Brassplus dot(.....);
dom.ViewAcct(); //Brass 的ViewAcct;
dot.ViewAcct();// Brassplus 的ViewAcct;
ditto
方法通过指针/引用调用
Brass & b1_ref = dom;
Brass & b2_ref = dot;
b1_ref.ViewAcct() ; //Brass::ViewAcct;
b2_ref.ViewAcct() ; //BrassPlus::ViewAcct;
Brass & b2_ref = dot;
b1_ref.ViewAcct() ; //Brass::ViewAcct;
b2_ref.ViewAcct() ; //BrassPlus::ViewAcct;
根据对象的类型来调用类相关的方法
析构函数
代码
virtual ~Brass();
~Brassplus();
~Brassplus();
如果
不是虚
不是虚
Brass * p1;
p1=new BrassPlus(.....);
delete p1;
p1=new BrassPlus(.....);
delete p1;
结果
只会调用brass 的析构函数,不会调用BrassPlus 的析构函数
注意
如果BrassPlus包含一个执行某些操作的构造函数,
则Brass必须有一个虚构函数,即使该析构函数不执行任何操作
则Brass必须有一个虚构函数,即使该析构函数不执行任何操作
使用
(配合使用)
(配合使用)
int main()
{
Brass * p_clients[CLIENTS];
if(...)
p_clients[i]= new Brass( temp , tempnum , tempbal );
else
p_clients[i]= new BrassPlus( temp,tempnum ,tempbal , tmax , trate);
for( int i =0 ; i< .. ;i++ )
{
p_clients[i].ViewAcct( ) ;
}
}
{
Brass * p_clients[CLIENTS];
if(...)
p_clients[i]= new Brass( temp , tempnum , tempbal );
else
p_clients[i]= new BrassPlus( temp,tempnum ,tempbal , tmax , trate);
for( int i =0 ; i< .. ;i++ )
{
p_clients[i].ViewAcct( ) ;
}
}
会根据p_clients 指向的类型来显示。
说明
p_clients[i].ViewAcct( ) ; 根据对象分配的类型来显示。new 分配的类型
静态联编和动态联编
相关概念
联编
将源代码中的函数调用解释为执行特定的函数代码块
动态联编
在编译过程中进行联编
C中的函数
C++中的重载函数虽然复杂了,但是也是静态联编
静态联编
编译器生成能够在程序运行时选择正确的虚方法代码
虚方法
指针和引用类型的兼容性
与动态联编
关系
关系
动态联编与通过指针和引用调用方法相关,
公有继承建立的is -a 关系的一种方法是如何处理指向对象的指针和引用
某种程度上,动态联编由继承控制。
公有继承建立的is -a 关系的一种方法是如何处理指向对象的指针和引用
某种程度上,动态联编由继承控制。
子主题
(一般)C++不允许将一种类型的地址赋给另一种类型的指针
允许将指向基类的引用和指针可以指向、引用派生类对象
向上强制转换
指向基类的引用和指针指向、引用派生类对象
不需要显式转换
例子
void fr( Brass & rb );
void fp( Brass * pb );
voud fv( Brass pv );
int main()
{
Brass b(....);
BrassPlus bp( .... );
fr(b);
fr(bp);
fp(b);
fp(bp);
fv(b);
fv(bp);
//valid;
}
void fp( Brass * pb );
voud fv( Brass pv );
int main()
{
Brass b(....);
BrassPlus bp( .... );
fr(b);
fr(bp);
fp(b);
fp(bp);
fv(b);
fv(bp);
//valid;
}
说明
按值传递只将 BrassPlus对象的Brass部分传递给函数 fv().
向下强制转换
指向派生类的引用和指针指向、引用基类对象
需要显式转换,也没有意义
Brass sally( ... );
BrassPlus *ptr = &sally //invalid
BrassPlus *ptr= ( BrassPluds *) sally; //valid
BrassPlus *ptr = &sally //invalid
BrassPlus *ptr= ( BrassPluds *) sally; //valid
虚成员函数和动态联编
关系
编译器对非虚函数——静态联编;
虚函数——动态联编
虚函数——动态联编
默认联编方式
静态联编
原因
效率
p503
概念模型
注意
仅将在派生类中重新定义基类的方法,设置为虚方法,否则,设置为非虚方法
虚函数
工作原理
工作原理
给每个对象添加一个(指向函数地址数组的指针) 的隐藏成员。
数组称为——虚函数表,存储为类对象进行声明的虚函数的地址。
数组称为——虚函数表,存储为类对象进行声明的虚函数的地址。
图片p204
子主题
调用虚函数时,程序查看存储在对象中的vtbl地址,
然后转向相应的函数地址表。
然后转向相应的函数地址表。
注意
如果在基类定义了虚函数,在派生类没有重新定义,虚函数表保存函数原始版本的地址
(使用虚函
数时的结果)
总结
数时的结果)
总结
每个对象都将增大,增大量为存储地址的空间
对于每个类,编译器都创建一个虚函数地址表(数组)
对于每个函数调用,都需要执行一项额外的操作
,即到表中查找地址
,即到表中查找地址
虚函数的注意事项
构造函数
要求
不能为虚函数,因为派生类不继承基类的构造函数
析构函数
要求
析构函数应该为虚函数,除非类不用做基类
不是虚类
问题
问题
Employee *pe = new Singer;
delete pe;
//只释放Employee 部分指向的内存,不释放Singer
组件指向的内存。
delete pe;
//只释放Employee 部分指向的内存,不释放Singer
组件指向的内存。
注意
即使基类的析构函数不做任何事情,
也需要提供一个虚类析构函数
也需要提供一个虚类析构函数
给类定义一个虚析构函数,即使类不作为基类也没有问题,
只是效率问题。
只是效率问题。
友元
要求
不能是虚函数,只有成员才能是虚类。
可以通过让友元函数使用虚成员函数解决问题。
可以通过让友元函数使用虚成员函数解决问题。
没有重新定义
使用该函数的基类版本,如果派生类位于派生链中,将使用最
新的虚函数版本。例外,基类版本是隐藏的。
新的虚函数版本。例外,基类版本是隐藏的。
重新定义将隐藏方法
代码
class Dwelling
{
public:
virtual void showpos(int a) const;
....
};
class Hovel:public Dwelling
{
public:
virtual void showpos() const;
...
};
Hovel trump;
trump.showperks(); //valid
trump.showperks(5); //invalid
{
public:
virtual void showpos(int a) const;
....
};
class Hovel:public Dwelling
{
public:
virtual void showpos() const;
...
};
Hovel trump;
trump.showperks(); //valid
trump.showperks(5); //invalid
子主题
重新定义不会生成函数的两个重载版本,
隐藏了接受int 参数的基本版本。
重新定义继承的方法不是重载,
如果同名,(即使)参数列表不一样,
会覆盖基类的声明
隐藏了接受int 参数的基本版本。
重新定义继承的方法不是重载,
如果同名,(即使)参数列表不一样,
会覆盖基类的声明
教训
1.
重新定义继承方法,确保与原来的原型完全相同,(参数、函数名、返回类型(小例外))
但如果返回类型是基类的引用或者指针,可以修改为指向派生类的引用或指针。
class Dwelling
{
public:
virtual Dwelling & bulid(int n);
....
};
class Hovel:public Dwelling
{
public:
virtual Hovel & bulid(int n);
...
};
{
public:
virtual Dwelling & bulid(int n);
....
};
class Hovel:public Dwelling
{
public:
virtual Hovel & bulid(int n);
...
};
2
如果 基类声明被重载了,则应在派生类中重新定义所有的基类版本
问题
如果不设置为虚函数,这样可以吗?
不可以,因为是同名
#include<iostream>
using namespace std;
class Dwelling
{
private:
int a,b;
public:
Dwelling(int aa,int bb);
void show();
};
class Hovel:public Dwelling
{
private:
int c;
public:
Hovel(int aa,int bb,int cc);
virtual Hovel & bulid(int n);
void show(int cc);
};
Dwelling::Dwelling(int aa,int bb)
{
a=aa;
b=bb;
}
void Dwelling::show()
{
cout<<a<<","<<b<<endl;
}
Hovel::Hovel(int aa,int bb,int cc)
:Dwelling(aa,bb),c(cc)
{}
void Hovel::show(int cc)
{
cout<<c<<endl;
}
int main()
{
Hovel h1(1,2,3);
h1.show(); //invalis
h1.show(3); //valid
}
using namespace std;
class Dwelling
{
private:
int a,b;
public:
Dwelling(int aa,int bb);
void show();
};
class Hovel:public Dwelling
{
private:
int c;
public:
Hovel(int aa,int bb,int cc);
virtual Hovel & bulid(int n);
void show(int cc);
};
Dwelling::Dwelling(int aa,int bb)
{
a=aa;
b=bb;
}
void Dwelling::show()
{
cout<<a<<","<<b<<endl;
}
Hovel::Hovel(int aa,int bb,int cc)
:Dwelling(aa,bb),c(cc)
{}
void Hovel::show(int cc)
{
cout<<c<<endl;
}
int main()
{
Hovel h1(1,2,3);
h1.show(); //invalis
h1.show(3); //valid
}
//可以
h1.Dwelling::show();
h1.show(3);
h1.Dwelling::show();
h1.show(3);
重载的话,要在同一个类中重载,不能一个在基类,一个在派生类
代码
class Dwelling
{
public:
....
virtual void showperks(int a) const;
virtual void showperks(double x) const;
virtual void showperks() const;
....
};
class Hovel:public Dwelling
{
public:
virtual void showperks(int a) const;
virtual void showperks(double x) const;
virtual void showperks() const;
};
{
public:
....
virtual void showperks(int a) const;
virtual void showperks(double x) const;
virtual void showperks() const;
....
};
class Hovel:public Dwelling
{
public:
virtual void showperks(int a) const;
virtual void showperks(double x) const;
virtual void showperks() const;
};
如果不需要修改,新定义可以
只调用基类的版本
只调用基类的版本
void Hovel::showperks() const { Dwelling::showperks() ;}
访问控制:protected
位置
作用
外部世界
保护成员的行为与私有成员相似
对于派生类
保护成员的行为与公有成员相似
应用
数据成员作为保护对象
最好不要,会用问题。??什么问题,
最好对类数据成员采用私有访问控制,不要使用保护访问控制;
同时通过基类方法使派生类能够访问类数据。
同时通过基类方法使派生类能够访问类数据。
成员函数成为保护对象
保护访问控制很有用,让派生类能够访问公众不能使用的内部函数
抽象基类(ABC)
原因
满足 is-a 关系,但是派生的类很笨拙,派生类不需要基类的一些数据,
eg:圆(circle)属于特殊的椭圆(Ellipse),但从椭圆中直接派生出圆显得笨拙。
eg:圆(circle)属于特殊的椭圆(Ellipse),但从椭圆中直接派生出圆显得笨拙。
两个有关系的类分别定义显示不了他们的共性——把他们的共同点抽
离出来,成为(圆、椭圆)的基类。
离出来,成为(圆、椭圆)的基类。
什么是抽象类?
声明了纯虚函数的类,称为抽象类。抽象类不能创建类对象。
纯虚函数
含义、作用
纯虚函数是在基类中只声明虚函数而不给出具体的函数定义体,将它的具体定义放在各派生类中,称此虚函数为纯虚函数.
通过该基类的指针或引用就可以调用所有派生类的虚函数,基类只是用于继承,仅作为一个接口,具体功能在派生类中实现.
通过该基类的指针或引用就可以调用所有派生类的虚函数,基类只是用于继承,仅作为一个接口,具体功能在派生类中实现.
声明
virtual 函数原型=0;
注意
声明为纯虚函数的函数体可以不定义,
也可以定义(主要作用为声明基类为抽象类)
也可以定义(主要作用为声明基类为抽象类)
抽象类特点
1.抽象类中可以有多个纯虚函数( 至少有一个 )
2.不能声明抽象类的对象,但可以声明指向抽象类的指针变量和引用变量
3.抽象类也可以定义其他非纯虚函数
4.如果派生类中没有重新定义基类中的纯虚函数,则在派生类中必须再将该虚函数声明为纯虚函数
5.从抽象类可以派生出具体或抽象类,但不能从具体类派生出抽象类
6.在一个复杂的类继承结构中,越上层的类抽象程度越高,有时甚至无法给出某些成员函数的实现,显然,抽象类是一种特殊的类,它一般处于类继承结构的较外层
7.引入抽象类的目的,主要是为了能将相关类组织在一个类继承结构中,并通过抽象类来为这些相关类提供统一的操作接口
2.不能声明抽象类的对象,但可以声明指向抽象类的指针变量和引用变量
3.抽象类也可以定义其他非纯虚函数
4.如果派生类中没有重新定义基类中的纯虚函数,则在派生类中必须再将该虚函数声明为纯虚函数
5.从抽象类可以派生出具体或抽象类,但不能从具体类派生出抽象类
6.在一个复杂的类继承结构中,越上层的类抽象程度越高,有时甚至无法给出某些成员函数的实现,显然,抽象类是一种特殊的类,它一般处于类继承结构的较外层
7.引入抽象类的目的,主要是为了能将相关类组织在一个类继承结构中,并通过抽象类来为这些相关类提供统一的操作接口
应用
抽象基类
类声明
代码
class AcctABC
{
private:
std::string fullName;
long acctNum;
double balance;
protected:
struct Formatting
{
std::ios_base::fmtflags flag;
std::streamsize pr;
};
const std::string & FullName() const {return fullName;} //返回私有成员数据 fullName
long AcctNum() const {return acctNum;} //返回私有成员数据 acctNum
Formatting SetFormat() const; //设置输出格式函数
void Restore(Formatting & f) const; //重设格式函数
public:
AcctABC(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0);
void Deposit(double amt) ;
virtual void Withdraw(double amt) = 0; // pure virtual function
double Balance() const {return balance;};
virtual void ViewAcct() const = 0; // pure virtual function
virtual ~AcctABC() {}
};
{
private:
std::string fullName;
long acctNum;
double balance;
protected:
struct Formatting
{
std::ios_base::fmtflags flag;
std::streamsize pr;
};
const std::string & FullName() const {return fullName;} //返回私有成员数据 fullName
long AcctNum() const {return acctNum;} //返回私有成员数据 acctNum
Formatting SetFormat() const; //设置输出格式函数
void Restore(Formatting & f) const; //重设格式函数
public:
AcctABC(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0);
void Deposit(double amt) ;
virtual void Withdraw(double amt) = 0; // pure virtual function
double Balance() const {return balance;};
virtual void ViewAcct() const = 0; // pure virtual function
virtual ~AcctABC() {}
};
说明
protect 中
把设置格式放在protected 中,派生类中可以使用,外部不可使用
把得到基类的私有数据放在protected 中,函数的方式得到。
类定义
代码
// Abstract Base Class
AcctABC::AcctABC(const string & s, long an, double bal)
{
fullName = s;
acctNum = an;
balance = bal;
}
void AcctABC::Deposit(double amt)
{
if (amt < 0)
cout << "Negative deposit not allowed; "
<< "deposit is cancelled.\n";
else
balance += amt;
}
void AcctABC::Withdraw(double amt)
{
balance -= amt;
}
// protected methods for formatting
AcctABC::Formatting AcctABC::SetFormat() const
{
// set up ###.## format
Formatting f;
f.flag =
cout.setf(ios_base::fixed, ios_base::floatfield);
f.pr = cout.precision(2);
return f;
}
void AcctABC::Restore(Formatting & f) const
{
cout.setf(f.flag, ios_base::floatfield);
cout.precision(f.pr);
}
AcctABC::AcctABC(const string & s, long an, double bal)
{
fullName = s;
acctNum = an;
balance = bal;
}
void AcctABC::Deposit(double amt)
{
if (amt < 0)
cout << "Negative deposit not allowed; "
<< "deposit is cancelled.\n";
else
balance += amt;
}
void AcctABC::Withdraw(double amt)
{
balance -= amt;
}
// protected methods for formatting
AcctABC::Formatting AcctABC::SetFormat() const
{
// set up ###.## format
Formatting f;
f.flag =
cout.setf(ios_base::fixed, ios_base::floatfield);
f.pr = cout.precision(2);
return f;
}
void AcctABC::Restore(Formatting & f) const
{
cout.setf(f.flag, ios_base::floatfield);
cout.precision(f.pr);
}
说明
virtual void Withdraw(double amt) = 0;
virtual void ViewAcct() const = 0;
virtual void ViewAcct() const = 0;
void AcctABC::Withdraw(double amt)
{
balance -= amt;
}
{
balance -= amt;
}
取款粗略计算
virtual void ViewAcct() const = 0;//没有定义,定义留在派生类中
派生类1
类声明
代码
class Brass :public AcctABC
{
public:
Brass(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0) : AcctABC(s, an, bal) { } //直接引用基类的构造函数
virtual void Withdraw(double amt); //重新定义
virtual void ViewAcct() const; //重新定义
virtual ~Brass() {}
};
{
public:
Brass(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0) : AcctABC(s, an, bal) { } //直接引用基类的构造函数
virtual void Withdraw(double amt); //重新定义
virtual void ViewAcct() const; //重新定义
virtual ~Brass() {}
};
说明
类定义
代码
void Brass::Withdraw(double amt)
{
if (amt < 0)
cout << "Withdrawal amount must be positive; "
<< "withdrawal canceled.\n";
else if (amt <= Balance())
AcctABC::Withdraw(amt); //引用基类的函数,因为是同名函数,所以有作用限定域
else
cout << "Withdrawal amount of $" << amt
<< " exceeds your balance.\n"
<< "Withdrawal canceled.\n";
}
void Brass::ViewAcct() const
{
Formatting f = SetFormat(); //引用基类函数,不是同名函数,所以不用,也不用通过对象去引用。
cout << "Brass Client: " << FullName() << endl;
cout << "Account Number: " << AcctNum() << endl;
cout << "Balance: $" << Balance() << endl;
Restore(f);
}
void Brass::Withdraw(double amt)
{
if (amt < 0)
cout << "Withdrawal amount must be positive; "
<< "withdrawal canceled.\n";
else if (amt <= Balance())
AcctABC::Withdraw(amt); //引用基类的函数,因为是同名函数,所以有作用限定域
else
cout << "Withdrawal amount of $" << amt
<< " exceeds your balance.\n"
<< "Withdrawal canceled.\n";
}
void Brass::ViewAcct() const
{
Formatting f = SetFormat(); //引用基类函数,不是同名函数,所以不用,也不用通过对象去引用。
cout << "Brass Client: " << FullName() << endl;
cout << "Account Number: " << AcctNum() << endl;
cout << "Balance: $" << Balance() << endl;
Restore(f);
}
说明
派生类2
类声明
代码
class BrassPlus : public AcctABC
{
private:
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0, double ml = 500,
double r = 0.10);
BrassPlus(const Brass & ba, double ml = 500, double r = 0.1);
virtual void ViewAcct()const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; };
void ResetOwes() { owesBank = 0; }
};
{
private:
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0, double ml = 500,
double r = 0.10);
BrassPlus(const Brass & ba, double ml = 500, double r = 0.1);
virtual void ViewAcct()const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; };
void ResetOwes() { owesBank = 0; }
};
说明
无
类定义
代码
BrassPlus::BrassPlus(const string & s, long an, double bal,
double ml, double r) : AcctABC(s, an, bal)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
BrassPlus::BrassPlus(const Brass & ba, double ml, double r)
: AcctABC(ba) // uses implicit copy constructor
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
void BrassPlus::ViewAcct() const
{
Formatting f = SetFormat();
cout << "BrassPlus Client: " << FullName() << endl;
cout << "Account Number: " << AcctNum() << endl;
cout << "Balance: $" << Balance() << endl;
cout << "Maximum loan: $" << maxLoan << endl;
cout << "Owed to bank: $" << owesBank << endl;
cout.precision(3);
cout << "Loan Rate: " << 100 * rate << "%\n";
Restore(f);
}
void BrassPlus::Withdraw(double amt)
{
Formatting f = SetFormat(); //formatting f 在基类的保护函数中,所以可以,这个f 与SetFormat()函数中的f 不同;
double bal = Balance();
if (amt <= bal)
AcctABC::Withdraw(amt);
else if ( amt <= bal + maxLoan - owesBank)
{
double advance = amt - bal;
owesBank += advance * (1.0 + rate);
cout << "Bank advance: $" << advance << endl;
cout << "Finance charge: $" << advance * rate << endl;
Deposit(advance);
AcctABC::Withdraw(amt);
}
else
cout << "Credit limit exceeded. Transaction cancelled.\n";
Restore(f);
}
double ml, double r) : AcctABC(s, an, bal)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
BrassPlus::BrassPlus(const Brass & ba, double ml, double r)
: AcctABC(ba) // uses implicit copy constructor
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
void BrassPlus::ViewAcct() const
{
Formatting f = SetFormat();
cout << "BrassPlus Client: " << FullName() << endl;
cout << "Account Number: " << AcctNum() << endl;
cout << "Balance: $" << Balance() << endl;
cout << "Maximum loan: $" << maxLoan << endl;
cout << "Owed to bank: $" << owesBank << endl;
cout.precision(3);
cout << "Loan Rate: " << 100 * rate << "%\n";
Restore(f);
}
void BrassPlus::Withdraw(double amt)
{
Formatting f = SetFormat(); //formatting f 在基类的保护函数中,所以可以,这个f 与SetFormat()函数中的f 不同;
double bal = Balance();
if (amt <= bal)
AcctABC::Withdraw(amt);
else if ( amt <= bal + maxLoan - owesBank)
{
double advance = amt - bal;
owesBank += advance * (1.0 + rate);
cout << "Bank advance: $" << advance << endl;
cout << "Finance charge: $" << advance * rate << endl;
Deposit(advance);
AcctABC::Withdraw(amt);
}
else
cout << "Credit limit exceeded. Transaction cancelled.\n";
Restore(f);
}
子主题
无
使用
代码。
还不与看电脑。
还不与看电脑。
// usebrass3.cpp -- polymorphic example
// compile with acctacb.cpp
#include <iostream>
#include <string>
#include "acctabc.h"
const int CLIENTS = 4;
int main()
{
using std::cin;
using std::cout;
using std::endl;
AcctABC * p_clients[CLIENTS]; //建立基类,brass,bassplus都可以存储在里面。
std::string temp;
long tempnum;
double tempbal;
char kind;
for (int i = 0; i < CLIENTS; i++)
{
cout << "Enter client's name: ";
getline(cin,temp);
cout << "Enter client's account number: ";
cin >> tempnum;
cout << "Enter opening balance: $";
cin >> tempbal;
cout << "Enter 1 for Brass Account or "
<< "2 for BrassPlus Account: ";
while (cin >> kind && (kind != '1' && kind != '2'))
cout <<"Enter either 1 or 2: ";
if (kind == '1')
p_clients[i] = new Brass(temp, tempnum, tempbal);//建立Brass 类型
else//建立BrassPlus类型
{
double tmax, trate;
cout << "Enter the overdraft limit: $";
cin >> tmax;
cout << "Enter the interest rate "
<< "as a decimal fraction: ";
cin >> trate;
p_clients[i] = new BrassPlus(temp, tempnum, tempbal,
tmax, trate);
}
while (cin.get() != '\n') //确保下一个客户的信息由下一个值输入!!!
continue;
}
cout << endl;
for (int i = 0; i < CLIENTS; i++)
{
p_clients[i]->ViewAcct();
cout << endl;
}
for (int i = 0; i < CLIENTS; i++)
{
delete p_clients[i]; // free memory
}
cout << "Done.\n";
// cin.get();
return 0;
}
// compile with acctacb.cpp
#include <iostream>
#include <string>
#include "acctabc.h"
const int CLIENTS = 4;
int main()
{
using std::cin;
using std::cout;
using std::endl;
AcctABC * p_clients[CLIENTS]; //建立基类,brass,bassplus都可以存储在里面。
std::string temp;
long tempnum;
double tempbal;
char kind;
for (int i = 0; i < CLIENTS; i++)
{
cout << "Enter client's name: ";
getline(cin,temp);
cout << "Enter client's account number: ";
cin >> tempnum;
cout << "Enter opening balance: $";
cin >> tempbal;
cout << "Enter 1 for Brass Account or "
<< "2 for BrassPlus Account: ";
while (cin >> kind && (kind != '1' && kind != '2'))
cout <<"Enter either 1 or 2: ";
if (kind == '1')
p_clients[i] = new Brass(temp, tempnum, tempbal);//建立Brass 类型
else//建立BrassPlus类型
{
double tmax, trate;
cout << "Enter the overdraft limit: $";
cin >> tmax;
cout << "Enter the interest rate "
<< "as a decimal fraction: ";
cin >> trate;
p_clients[i] = new BrassPlus(temp, tempnum, tempbal,
tmax, trate);
}
while (cin.get() != '\n') //确保下一个客户的信息由下一个值输入!!!
continue;
}
cout << endl;
for (int i = 0; i < CLIENTS; i++)
{
p_clients[i]->ViewAcct();
cout << endl;
}
for (int i = 0; i < CLIENTS; i++)
{
delete p_clients[i]; // free memory
}
cout << "Done.\n";
// cin.get();
return 0;
}
说明
暂无
虚基类
问题产生
一般情况
一般情况
多继承或直接间接继承中,特别是在如下的模型中。
类 A 派生出类 B 和类 C,
类 D 继承自类 B 和类 C
类 D 继承自类 B 和类 C
问题
代码
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: public A{
protected:
int m_b;
};
//直接基类C
class C: public A{
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
class A{
protected:
int m_a;
};
//直接基类B
class B: public A{
protected:
int m_b;
};
//直接基类C
class C: public A{
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
分析
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
在上述中,在D中m_a会产生两个副本,一个是来自B的m_a,一个是来自C的m_a
在D中m_a没有说明是来自B还是C,这样也会有冗余的情况
在D中m_a没有说明是来自B还是C,这样也会有冗余的情况
改正
void seta(int a){ m_a = a; } 改为:
void seta(int a){ B::m_a = a; / /C::m_a = a;}正确
void seta(int a){ B::m_a = a; / /C::m_a = a;}正确
解决
要使D类对象只产生一个m_a数据,将基类设置为虚基类,
使得在派生类中只保留一份间接基类的成员。
使得在派生类中只保留一份间接基类的成员。
void seta(int a){ m_a = a; } //可以
虚基类作用
见上解决
虚基类定义
class A
class B: virtual public A
class C: virtual public A
class D: public B, public C//B,C不用vritual
class B: virtual public A
class C: virtual public A
class D: public B, public C//B,C不用vritual
说明
观察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:
必须在虚派生的真实需求出现前就已经完成虚派生的操作。
如上,当定义 D 类时才出现了对虚派生的需求。
换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生
出来的类,它不会影响派生类本身。
必须在虚派生的真实需求出现前就已经完成虚派生的操作。
如上,当定义 D 类时才出现了对虚派生的需求。
换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生
出来的类,它不会影响派生类本身。
就是,D的多个继承中,若有其中两个类B,C继承同一 一个基类,
需要把B,C的共同直接类A(派生时)设置为虚基类。这样A的数据成员就
不会有多个副本。
需要把B,C的共同直接类A(派生时)设置为虚基类。这样A的数据成员就
不会有多个副本。
继承和动态分配
基类
类声明
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);
friend std::ostream & operator<<(std::ostream & os,
const baseDMA & rs); //友元
};
{
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);
friend std::ostream & operator<<(std::ostream & os,
const baseDMA & rs); //友元
};
类定义
baseDMA::baseDMA(const char * l, int r)
{
label = new char[std::strlen(l) + 1];
std::strcpy(label, l);
rating = r;
}
baseDMA::baseDMA(const baseDMA & rs)
{
label = new char[std::strlen(rs.label) + 1];
std::strcpy(label, rs.label);
rating = rs.rating;
}
baseDMA::~baseDMA()
{
delete [] label;
}
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;
}
std::ostream & operator<<(std::ostream & os, const baseDMA & rs)
{
os << "Label: " << rs.label << std::endl;
os << "Rating: " << rs.rating << std::endl;
return os;
}
{
label = new char[std::strlen(l) + 1];
std::strcpy(label, l);
rating = r;
}
baseDMA::baseDMA(const baseDMA & rs)
{
label = new char[std::strlen(rs.label) + 1];
std::strcpy(label, rs.label);
rating = rs.rating;
}
baseDMA::~baseDMA()
{
delete [] label;
}
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;
}
std::ostream & operator<<(std::ostream & os, const baseDMA & rs)
{
os << "Label: " << rs.label << std::endl;
os << "Rating: " << rs.rating << std::endl;
return os;
}
派生类不使用new
代码
lackDMA
lackDMA
类声明
class lacksDMA :public baseDMA
{
private:
enum { COL_LEN = 40};
char color[COL_LEN];
public:
lacksDMA(const char * c = "blank", const char * l = "null",
int r = 0);
lacksDMA(const char * c, const baseDMA & rs);
friend std::ostream & operator<<(std::ostream & os,
const lacksDMA & rs);
};
{
private:
enum { COL_LEN = 40};
char color[COL_LEN];
public:
lacksDMA(const char * c = "blank", const char * l = "null",
int r = 0);
lacksDMA(const char * c, const baseDMA & rs);
friend std::ostream & operator<<(std::ostream & os,
const lacksDMA & rs);
};
类定义
lacksDMA::lacksDMA(const char * c, const char * l, int r)
: baseDMA(l, r)
{
std::strncpy(color, c, 39);
color[39] = '\0';
}
lacksDMA::lacksDMA(const char * c, const baseDMA & rs)
: baseDMA(rs)// 调用base的复制构造函数。
{
std::strncpy(color, c, COL_LEN - 1);
color[COL_LEN - 1] = '\0';
}
std::ostream & operator<<(std::ostream & os, const lacksDMA & ls)
{
os << (const baseDMA &) ls;
os << "Color: " << ls.color << std::endl;
return os;
}
: baseDMA(l, r)
{
std::strncpy(color, c, 39);
color[39] = '\0';
}
lacksDMA::lacksDMA(const char * c, const baseDMA & rs)
: baseDMA(rs)// 调用base的复制构造函数。
{
std::strncpy(color, c, COL_LEN - 1);
color[COL_LEN - 1] = '\0';
}
std::ostream & operator<<(std::ostream & os, const lacksDMA & ls)
{
os << (const baseDMA &) ls;
os << "Color: " << ls.color << std::endl;
return os;
}
用户自定义
构造函数
调用的是基类的构造函数,所以可以
复制构造函数
调用的是基类的复制构造函数,所以可以
系统默认
默认复制构造函数
成员复制将根据数据类型采用相应的复制方式,
复制继承的类的组件时,使用该类的复制构造函数完成的
复制继承的类的组件时,使用该类的复制构造函数完成的
默认析构函数
执行自身的代码后调用基类的析构函数。(先派生类的析构函数,再基类的析构函数)
赋值函数
自动使用类的赋值运算符来对基类组件进行赋值。
综上结论
不需要显式使用New 分配动态空间给基类的指针成员,
如果不是需要,也不用显式定义构造函数,赋值函数。
如果不是需要,也不用显式定义构造函数,赋值函数。
派生类使用new
结论
需要显式定义复制构造函数,析构函数,赋值函数
只需处理派生类的动态分配成员即可。
构造函数,复制构造函数,赋值函数必需使用相应的基类方法来处理基类函数
析构函数,是自动完成的。
构造函数,复制构造函数,赋值函数必需使用相应的基类方法来处理基类函数
析构函数,是自动完成的。
代码
类声明
class hasDMA :public baseDMA
{
private:
char * style; //要使用new的成员
public:
hasDMA(const char * s = "none", const char * l = "null",
int r = 0);
hasDMA(const char * s, const baseDMA & rs);
hasDMA(const hasDMA & hs);
~hasDMA();
hasDMA & operator=(const hasDMA & rs);
friend std::ostream & operator<<(std::ostream & os,
const hasDMA & rs);
};
{
private:
char * style; //要使用new的成员
public:
hasDMA(const char * s = "none", const char * l = "null",
int r = 0);
hasDMA(const char * s, const baseDMA & rs);
hasDMA(const hasDMA & hs);
~hasDMA();
hasDMA & operator=(const hasDMA & rs);
friend std::ostream & operator<<(std::ostream & os,
const hasDMA & rs);
};
构造函数
hasDMA::hasDMA(const char * s, const char * l, int r)
: baseDMA(l, r)
{
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
: baseDMA(l, r)
{
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
hasDMA::hasDMA(const char * s, const baseDMA & rs)
: baseDMA(rs)
{
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
: baseDMA(rs)
{
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
复制构造函数
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(hs) // invoke base class copy constructor
{
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
}
说明
hasDMA::hasDMA(const hasDMA & hs)
: baseDMA(hs) // invoke base class copy constructor
//直接把hasDMA传递给baseDMA
: baseDMA(hs) // invoke base class copy constructor
//直接把hasDMA传递给baseDMA
析构函数
hasDMA::~hasDMA()
{
delete [] style;
}
{
delete [] style;
}
赋值函数
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;
}
{
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); // copy base portion
基类部分给基类的赋值构造函数处理即可.
需要作用域解析运算符。
基类部分给基类的赋值构造函数处理即可.
需要作用域解析运算符。
书中说相当于
*this = hs; 不懂??
*this = hs; 不懂??
其他(友元)
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
os << (const baseDMA &) hs;
os << "Style: " << hs.style << std::endl;
return os;
}
{
os << (const baseDMA &) hs;
os << "Style: " << hs.style << std::endl;
return os;
}
派生类如何使用友元函数
基类中
friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs);
std::ostream & operator<<(std::ostream & os, const baseDMA & rs)
{
os << "Label: " << rs.label << std::endl;
os << "Rating: " << rs.rating << std::endl;
return os;
}
friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs);
std::ostream & operator<<(std::ostream & os, const baseDMA & rs)
{
os << "Label: " << rs.label << std::endl;
os << "Rating: " << rs.rating << std::endl;
return os;
}
lackDMA
friend std::ostream & operator<<(std::ostream & os, const lacksDMA & rs);
std::ostream & operator<<(std::ostream & os, const lacksDMA & ls)
{
os << (const baseDMA &) ls;
os << "Color: " << ls.color << std::endl;
return os;
}
std::ostream & operator<<(std::ostream & os, const lacksDMA & ls)
{
os << (const baseDMA &) ls;
os << "Color: " << ls.color << std::endl;
return os;
}
hasDMA
friend std::ostream & operator<<(std::ostream & os, const hasDMA & rs);
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
os << (const baseDMA &) hs;
os << "Style: " << hs.style << std::endl;
return os;
}
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
os << (const baseDMA &) hs;
os << "Style: " << hs.style << std::endl;
return os;
}
问题
在派生类中,只能访问派生类的数据成员,怎么输出基类的数据成员?
调用基类的友元函数。
就是说,派生类也可以不通过对象调用基类的友元函数。
就是说,派生类也可以不通过对象调用基类的友元函数。
基类的友元函数不是成员函数,不能使用作用解析运算符指出使用哪个函数
使用强制类型转换法(如上),转换成基类的<<中接收的类型。
类设计回顾
析构函数
对于基类,即使它不需要析构函数,也应提供一个虚析构函数(基类的析构函数最好是虚函数)
赋值运算符
派生类赋值给基类
可以。
基类赋值给派生类
父节点
问题
Brass gp(“Griff”,21234,1200)
BrassPlus temp;
Temp=gp;// possible??
BrassPlus temp;
Temp=gp;// possible??
调用的是BrassPlus::operator= ( const BrassPlus & )函数
但派生类引用不能自动引用基类对象,因此代码不能执行。
但派生类引用不能自动引用基类对象,因此代码不能执行。
解决
方法1
定义一个转换构造函数
BrassPlus( const Brass &);
或者
BrassPlus(const Brass &ba,double m1=500,double r=0.1);
BrassPlus( const Brass &);
或者
BrassPlus(const Brass &ba,double m1=500,double r=0.1);
根据gp创建一个临时BrassPlus对象,然后用作赋值运算符参数。
方法2
定义一个用于将基类赋给派生类的赋值运算符
BrassPlus &BrassPlus::operator=( const Brass & ){ .... }
BrassPlus &BrassPlus::operator=( const Brass & ){ .... }
传递的参数类型
前提
Brass/Brass plus的ViewAcct()已经声明为虚函数
子主题
BrassPlus buzz(....)
show(buzz)
inadequate(buzz);
void show(const Brass & rha)//按引用传递对象
{
rba.ViewAcct();
cout<<endl;
}
void inadequate(Brass ba)//按值传递对象
{
ba.viewAcct();
cout<<endl;
}
show(buzz)
inadequate(buzz);
void show(const Brass & rha)//按引用传递对象
{
rba.ViewAcct();
cout<<endl;
}
void inadequate(Brass ba)//按值传递对象
{
ba.viewAcct();
cout<<endl;
}
结果
第一个函数,显示buzz的全部
第二个函数,只显示buzz的brass部分
第二个函数,只显示buzz的brass部分
0 条评论
下一页