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