C++_第十三章_成员初始化列表语法(十二章最后)_派生类_基类指针或引用可指向派生类对象_多态公有继承_静态联编和动态联编_复习类特殊成员函数_抽象类_继承和动态内存分配_公有成员、私有成员和保护成员的访问控制(可见性)
目录
成员初始化列表语法
01)问题的提出:
对于一个Queue类的声明(不存在问题,注意在类中声明一个常量):
class Queue
{
private:
int items;
const int qsize; //常量
pubilc:
Queue(int qs); //构造函数
...
};
Queue类构造函数的定义(存在问题,因为不能给常量qsize赋值):
Queue::Queue(int qs)
{
items = 0;
qsize = 0; //不合法!
}
02)解决的方法:使用成员初始化列表
Classy::Classy(int n,int m) : mem1(n), mem2(0), mem3(n*m+2)
{
...
}
以上将n的值赋给mem1,将0赋给mem2,将n*m+2的值赋给mem3。从概念上说,这些初始化构造是在对象创建时完成的,此时还未执行
大括号中的任何代码。请注意一下几点:
A 成员初始化列表只能用于构造函数
B 必须用这种格式来初始化非静态const数据成员
C 必须用这种格式来初始化引用数据成员
D 不能讲成员初始化列表用于构造函数之外的其他类方法
E 成员初始化列表的括号方式也可以用于常规舒适化,可以将下述代码:
int games = 162;
double talk = 2.71828;
替换为:
int games(162);
double talk(2.71828);
03)C++11的类内初始化:
class Classy
{
int mem1 = 10;
const int mem2 = 20;
};
与在构造函数中使用成员初始化列表等价:
Classy::Classy() : mem1(10), mem2(20) { ... }
也可以将初始值替换为其他的数值:
Classy::Classy(int n) : mem1(n) { ... } //此时mem1=n, mem2=20
派生类
01)加入有一个类TableTennisPlayer类,在这个类的基础上派生出一个类RatedPlayer,方法如下
class RatedPlayer : public TableTennisPlayer //公有继承方式(public) TableTennisPlayer为基类,RatedPlayer 为派生类
{
private:
...
public:
...
};
02)上述语句是写在h文件中的;冒号指出RatedPlayer是派生类,TableTennisPlayer是基类
派生类对象储存了基类的数据成员(派生类继承了基类的实现);
派生类对象可以使用基类的方法(派生类继承了基类的接口);
派生类需要自己的构造函数,可以根据需要添加额外的数据成员和成员函数;
03)派生类不可以直接访问基类的私有数据成员,而必须通过基类方法进行访问,派生类构造函数必须使用基类构造函数,
因为要肯定是要创建派生类对象的,那么在创建派生类对象的时候,也必须加上基类的私有数据,所以派生类构造函数必须使用基类构造函数
对基类中的数据成员进行赋值;
创建培生类对象时,必须首先创建基类对象。从概念上说,这意味着基类对象应该在程序进入派生类构造函数之前被创建,那么我们使用
成员初始化列表的语法来完成这项工作,例如:
RatedPlayer::RatedPlayer(unsiged int r,const string & fn,const string & ln,bool ht) : TableTennisPlayer(fn,ln,ht)
{
rating = r;
};//该语句是在对应的类cpp文件中
I 最后的TableTennisPlayer(fn,ln,ht)表示首先创建基类对象,并对该对象进行赋值,即将fn,ln,ht赋给基类对应的参数
II 在主函数文件中将可以执行如下代码:
RatedPlayer ratedPlayer(1140,"Mallory","Duck",true); //创建派生类对象ratedPlayer
RatedPlayer构造函数将把实参"Mallory","Duck",true赋给形参fn,ln,ht,然后将这些参数作为实参传递给TableTennisPlayer构造函数对象
并将数据"Mallory","Duck",true存储在该对象中。然后进入RatedPlayer构造函数体,完成ratedPlayer的创建
III 如果省略成员初始化列表,那么程序将会使用基类的默认构造函数,完成数据的赋值操作
04)来看第二个派生类构造函数代码:
RatedPlayer::RatedPlayer(unsiged int r,const TableTennisPlayer & tp) : TableTennisPlayer(tp)
{
rating = r;
}
由于tp的类型为指向TableTennisPlayer类对象的引用,因此将调用基类的复制构造函数。基类如果没有定义赋值构造函数,则编译器
将自动生成一个。由于该例程中没有使用动态内存分配(string成员确实是使用了动态内存分配,但是成员复制将使用string类的复制构造函数
来复制string成员),所以使用编译器自动生成的复制构造函数是可行的。
05)如果愿意,也可以使用成员初始化列表的方法对派生类成员进行初始化,在这时,应该使用成员名(rating),而不是类名,如下:
RatedPlayer::RatedPlayer(unsiged int r,const TableTennisPlayer & tp) : TableTennisPlayer(tp),rating(r)
{
}
06)有关派生类构造函数的要点如下:
I 首先要创建基类对象;
II 派生类构造函数应该通过成员初始化列表将基类信息传递给基类构造函数;
III 派生类构造函数应初始化派生类新增的数据成员;
IV 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类
构造函数负责初始化新增的数据成员。派生类构造函数总是调用一个基类构造函数,可以使用成员初始化列表的方法指明要使用的
基类构造函数,否则将使用默认的基类构造函数;
IIV 派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数;
IIIV 初始化成员列表只能用于构造函数。
1 /* 派生类 */ 2 /* 3 01)加入有一个类TableTennisPlayer类,在这个类的基础上派生出一个类RatedPlayer,方法如下 4 class RatedPlayer : TableTennisPlayer 5 { 6 private: 7 ... 8 public: 9 ... 10 }; 11 02)上述语句是写在h文件中的;冒号指出RatedPlayer是派生类,TableTennisPlayer是基类 12 派生类对象储存了基类的数据成员(派生类继承了基类的实现); 13 派生类对象可以使用基类的方法(派生类继承了基类的接口); 14 派生类需要自己的构造函数,可以根据需要添加额外的数据成员和成员函数; 15 03)派生类不可以直接访问基类的私有数据成员,而必须通过基类方法进行访问,派生类构造函数必须使用基类构造函数, 16 因为要肯定是要创建派生类对象的,那么在创建派生类对象的时候,也必须加上基类的私有数据,所以派生类构造函数必须使用基类构造函数 17 对基类中的数据成员进行赋值; 18 创建培生类对象时,必须首先创建基类对象。从概念上说,这意味着基类对象应该在程序进入派生类构造函数之前被创建,那么我们使用 19 成员初始化列表的语法来完成这项工作,例如: 20 RatedPlayer::RatedPlayer(unsiged int r,const string & fn,const string & ln,bool ht) : TableTennisPlayer(fn,ln,ht) 21 { 22 rating = r; 23 };//该语句是在对应的类cpp文件中 24 I 最后的TableTennisPlayer(fn,ln,ht)表示首先创建基类对象,并对该对象进行赋值,即将fn,ln,ht赋给基类对应的参数 25 II 在主函数文件中将可以执行如下代码: 26 RatedPlayer ratedPlayer(1140,"Mallory","Duck",true); //创建派生类对象ratedPlayer 27 RatedPlayer构造函数将把实参"Mallory","Duck",true赋给形参fn,ln,ht,然后将这些参数作为实参传递给TableTennisPlayer构造函数对象 28 并将数据"Mallory","Duck",true存储在该对象中。然后进入RatedPlayer构造函数体,完成ratedPlayer的创建 29 III 如果省略成员初始化列表,那么程序将会使用基类的默认构造函数,完成数据的赋值操作 30 04)来看第二个派生类构造函数代码: 31 RatedPlayer::RatedPlayer(unsiged int r,const TableTennisPlayer & tp) : TableTennisPlayer(tp) 32 { 33 rating = r; 34 } 35 由于tp的类型为指向TableTennisPlayer类对象的引用,因此将调用基类的复制构造函数。基类如果没有定义赋值构造函数,则编译器 36 将自动生成一个。由于该例程中没有使用动态内存分配(string成员确实是使用了动态内存分配,但是成员复制将使用string类的复制构造函数 37 来复制string成员),所以使用编译器自动生成的复制构造函数是可行的。 38 05)如果愿意,也可以使用成员初始化列表的方法对派生类成员进行初始化,在这时,应该使用成员名(rating),而不是类名,如下: 39 RatedPlayer::RatedPlayer(unsiged int r,const TableTennisPlayer & tp) : TableTennisPlayer(tp),rating(r) 40 { 41 42 } 43 06)有关派生类构造函数的要点如下: 44 I 首先要创建基类对象; 45 II 派生类构造函数应该通过成员初始化列表将基类信息传递给基类构造函数; 46 III 派生类构造函数应初始化派生类新增的数据成员; 47 IV 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类 48 构造函数负责初始化新增的数据成员。派生类构造函数总是调用一个基类构造函数,可以使用成员初始化列表的方法指明要使用的 49 基类构造函数,否则将使用默认的基类构造函数; 50 IIV 派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数; 51 IIIV 初始化成员列表只能用于构造函数。 52 */ 53 54 #ifndef TABTENN1_H_ 55 #define TABTENN1_H_ 56 57 #include <string> 58 59 using std::string; 60 61 /* 基类的声明 */ 62 class TableTennisPlayer 63 { 64 private: 65 string firstname; 66 string lastname; 67 bool hasTable; //是否有桌子 68 public: 69 TableTennisPlayer(const string & fn = "none",const string & ln = "none", bool ht = false); //带默认参数的构造函数 70 //由于"none"是const char类型的,故要fn的类型也要为const类型 71 void ShowName() const; //最后一个const表示不可以对对象中的数据进行修改 72 bool HasTable() const { return hasTable; } //返回私有数据,进而可以使对象可以访问私有数据 73 void ResetTable(bool v) { hasTable = v; } //对是否有球桌进行重新选择 74 }; 75 /* 派生类 */ 76 class RatedPlayer : public TableTennisPlayer //表明TableTennisPlayer是基类,RatedPlayer是派生类 77 { 78 private: 79 unsigned int rating; //派生类新增的数据成员 80 public: 81 RatedPlayer(unsigned int r = 0, const string & fn = "none", const string & ln = "none", bool ht = false); //RatedPlayer构造函数,以具体数据的方式将数据传递给基类构造函数 82 RatedPlayer(unsigned int r, const TableTennisPlayer & tp); //以指向基类对象的引用作为参数传递给基类构造函数 83 unsigned int Rating() const { return rating; } //返回私有数据,进而可以使对象可以访问私有数据 84 void ResetRating(unsigned int r) { rating = r; } //派生类新增的类方法 85 }; 86 87 88 #endif
1 //tabletenn1.cpp 2 3 #include "tabletenn1.h" 4 #include <iostream> 5 6 using std::cout; 7 using std::endl; 8 9 /* 基类构造函数(使用初始化成员列表方法初始化数据成员) 10 01)注意啦,构造函数前也是要加类限定符TableTennisPlayer::的好不啦!!! 11 02)注意在h文件中的默认参数是不可以带过来的啦!!! 12 */ 13 TableTennisPlayer::TableTennisPlayer(const string & fn, const string & ln, bool ht) 14 :firstname(fn), lastname(ln), hasTable(ht) {} 15 //将fn赋值给私有数据firstname,ln赋值给lastname,ht赋值给hasTable,函数体为空 16 17 void TableTennisPlayer::ShowName() const 18 { 19 cout << lastname << ", " << firstname; 20 } 21 22 /* 派生类构造函数 */ 23 RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht) 24 :TableTennisPlayer(fn,ln,ht) 25 { 26 rating = r; 27 } 28 //首先使用基类构造函数TableTennisPlayer(const string & fn = "none", const string & ln = "none", bool ht = false)将数据传入基类中 29 //然后创建派生类对象,最后执行rating = r; 30 RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp) 31 :TableTennisPlayer(tp),rating(r) 32 { 33 34 } 35 //首先使用基类构造函数TableTennisPlayer(const string & fn = "none", const string & ln = "none", bool ht = false)将数据传入基类中 36 //tp是指向基类对象的形参,实参赋值给形参,会调用基类的复制构造函数,如果没有使用new(在基类中可以不定义复制构造函数),将使用基类的默认复制构造函数也是可以的 37 //然后创建派生类对象,最后执行rating = r; 38 /* 39 也可以这样写: 40 RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp) 41 :TableTennisPlayer(tp) 42 { 43 rating = r; 44 } 45 */
1 #include <iostream> 2 #include "tabletenn1.h" 3 4 int main() 5 { 6 using std::cout; 7 using std::endl; 8 9 TableTennisPlayer player1("Tara", "Boomadea", false); //创建基类对象player1 10 RatedPlayer rplayer1(1140, "Mallory", "Duck", true); //创建派生类对象rplayer1 11 12 player1.ShowName(); //基类对象调用基类方法 13 if (player1.HasTable()) 14 cout << ": has a table" << endl; 15 else 16 cout << ": hasn't a table" << endl; 17 18 rplayer1.ShowName(); //派生类对象调用基类方法 19 if(rplayer1.HasTable()) 20 cout << ": has a table" << endl; 21 else 22 cout << ": hasn't a table" << endl; 23 24 cout << "Name: "; 25 rplayer1.ShowName(); 26 cout << "; Rating: " << rplayer1.Rating() << endl; 27 28 RatedPlayer rplayer2(1212, player1); //使用基类对象player1作为实参创建派生类对象rplayer2 29 cout << "Name: "; 30 rplayer2.ShowName(); 31 cout << "; Rating: " << rplayer2.Rating() << endl; 32 33 system("pause"); 34 return 0; 35 36 }
执行结果:
基类指针或引用可指向派生类对象
01)假如有基类TableTennisPlayer,派生类RatedPlayer
RatedPlayer rplayer(1140,"Mallory", "Duck", true); //创建派生类对象rplayer
TableTennisPlayer & rt = rplayer; //基类引用rt指向派生类对象rplayer
TableTennisPlayer * pt = &rplayer; //基类指针指向派生类对象rplayer
rt.name(); //调用基类方法name() 这里有隐藏的问题,全都是调用基类方法,如果想调用派生类方法怎么办?方法是使用虚函数
pt->name(); //调用基类方法name()
需要注意的是,基类指针或引用只能调用基类方法,而不能调用派生类中增加的方法
02)不可以将基类对象和地址赋给派生类引用和指针:
TableTennisPlayer player("Mallory", "Duck", true);
RatedPlayer & rr = player; //不合法
RatedPlayer * pr = &player; //不合法
03)可以将基类引用或者是指针作为形参,将基类或者是派生类对象作为实参:
假如有函数:show(const TableTennisPlayer & rt); //注意该函数的形参
TableTennisPlayer player("Mallory", "Duck", true);
RatedPlayer rplayer(1140,"Mallory", "Duck", true);
show(player); //合法
show(rplayer); //合法
04)同理指针:
假如有函数:show(const TableTennisPlayer * pt);
TableTennisPlayer player("Mallory", "Duck", true);
RatedPlayer rplayer(1140,"Mallory", "Duck", true);
show(&player); //合法
show(&rplayer); //合法
05)可以使用派生类对象创建基类对象:
RatedPlayer olaf1(1140,"Mallory", "Duck", true);
TableTennisPlayer olaf2(olaf1); //这将调用基类的复制构造函数,TableTennisPlayer(const TableTennisPlayer &),即使基类中没有定义该函数,
//使用默认的复制构造函数也是可以的
06)可以将派生类对象赋值给基类对象:
RatedPlayer olaf1(1140,"Mallory", "Duck", true);
TableTennisPlayer winner;
winner = olaf1; //将调用基类的重载赋值运算符TableTennisPlayer & operator=(const TableTennisPlayer &) const;
多态公有继承
01)正如上面01)中所说的:
假如有基类TableTennisPlayer,派生类RatedPlayer
RatedPlayer rplayer(1140,"Mallory", "Duck", true); //创建派生类对象rplayer
TableTennisPlayer & rt = rplayer; //基类引用rt指向派生类对象rplayer
TableTennisPlayer * pt = &rplayer; //基类指针指向派生类对象rplayer
rt.name(); //调用基类方法name() 这里都是调用的基类方法啊!!!
pt->name(); //调用基类方法name() 这里都是调用的基类方法啊!!!
那么问题来了,我想用基类指针或引用调用派生类方法,该怎么办?方法是使用虚函数
02)在函数声明前加上关键字virtual即可,在定义不用加关键字virtual,如下声明:
virtual void ViewAcct() const; //声明虚函数
03)那么下面介绍多态公有继承,假如ViewAcct()是在基类和派生类中分别定义的虚函数,则有如下示例:
RatedPlayer rplayer(1140,"Mallory", "Duck", true);
TableTennisPlayer player("Mallory", "Duck", true);
TableTennisPlayer & b1_ref = rplayer;
TableTennisPlayer & b2_ref = player;
b1_ref.ViewAcct(); //根据引用指向的对象选择要使用的函数,调用派生类中的ViewAcct()
b2_ref.ViewAcct(); //根据引用指向的对象选择要使用的函数,调用基类中的ViewAcct()
04)同理对于指针:
RatedPlayer rplayer(1140,"Mallory", "Duck", true);
TableTennisPlayer player("Mallory", "Duck", true);
TableTennisPlayer * b1_ref = &rplayer;
TableTennisPlayer * b2_ref = &player;
b1_ref->ViewAcct(); //根据引用指向的对象选择要使用的函数,调用派生类中的ViewAcct()
b2_ref->ViewAcct(); //根据引用指向的对象选择要使用的函数,调用基类中的ViewAcct()
总结:如果没有使用虚方法,那么调用基类还是派生类方法,要看指针的类型
如果使用了虚方法,那么调用基类还是派生类方法(的虚方法),要看指针指向的对象的类型,进而调用相应的虚方法
静态联编和动态联编 m9
01)如果在基类和派生类中没有将ViewAcct()声明为虚函数,则b1_ref->ViewAcct()将根据指针(b1_ref)类型调用对应的ViewAcct();
总之编译器对非虚方法使用静态联编;
02)如果如果在基类和派生类中将ViewAcct()声明为虚函数,则b1_ref->ViewAcct()将根据对象(指针b1_ref指向的对象)类型调用对应的ViewAcct();
总之编译器对虚方法使用动态联编。
复习类特殊成员函数
/* (01)复制构造函数 */
假如Star是一个类,类Star的复制构造函数如下;
Star(const Star &);
在下述情况下,将使用复制构造函数:
01)将新对象初始化为一个同类的对象;
02)按值传递给函数(当作实参);
03)函数按值返回对象(当作返回值);
04)编译器生成临时对象。
/* (02)赋值构造函数 */
01)不要将赋值和初始化混淆了:
Star sirius; //创建类对象sirius
Star alpha = sirius; //初始化,调用复制构造函数
Star dogstar;
dogstar = sirius; //赋值,调用赋值构造函数
02)Star类的赋值运算符(对=的重载)定义如下:
Star & Star::operator=(const Star &) {...} //返回一个Star类对象引用
03)如果希望将字符串赋值给类对象,方法之一是定义下面的运算符:
Star & Star::operator=(const char*) {...}
另一种方法是使用转换函数,但可能导致编译器出现混乱。‘
/* (03)构造函数 */
构造函数不同于其他方法,因为他创建对象,而其他类方法是被现有的对象调用,这是构造函数
不被继承的原因之一。继承以为着派生类对象可以调用基类方法。
/* (04)按值传递对象与传递引用 */
通常,编写使用对象做为参数的函数时,应按引用传递,而不是使用按值本身来传递。这样做的原因是
为了提高效率。按值传递对象涉及到生成临时拷贝,即调用复制构造函数生成对象副本,然后调用析构函数删除临时对象副本,
因此使用按值传递对象效率不高。
直接返回对象和按值传递对象类似:他们都生成对象的临时副本,从而效率不高;
返回引用和传递对象的引用类似;调用和被调用的函数都将对一个对象进行操作。
如果函数返回在函数中创建的临时对象,因为在函数结束时,临时对象会消失,因此应该返回对象(实际上返回的是该对象的副本),
而不是返回指向对象的引用。
如果是返回按照实参传递给该函数或者是调用该类方法的对象,则应该按引用返回对象,如:
const Stock & Stock::topval(const Stock & s)
{
if(s.total_val > total_val)
return s; //返回按照实参传递给该函数的对象
else
*this; //返回调用该类方法的对象
}
/* (04)使用const */
const Stock & Stock::topval(const Stock & s) const //最后一个const相当于const Stock* this
{
if(s.total_val > total_val)
return s; //返回按照实参传递给该函数的对象
else
*this; //返回调用该类方法的对象
}
由于返回值s和*this均为const类型,所以在函数头中第一个const也是必须要加上的
/* (05)将派生类对象赋值给基类对象: 允许 */
Brass blips; //创建基类对象
BrassPlus snips("Rafe Plosh", 91191, 3993.19, 600.0, 0.12);//创建派生类对象
blips = snips; //实际调用方法为blips.operator=(snips),因此该句将调用基类的赋值运算符
snips做为一个实参传入基类的赋值运算符,对应的形参为Brass & s ,由于允许基类引用指向派生类对象,所以
此处实参赋值给形参是合理的。最终的效果是基类的赋值运算符忽略派生类中额外添加的数据,将属于基类中的数据
赋值给基类对象
但是将基类对象赋值给派生类对象是不允许的,例如:
Brass gp("Rafe Plosh", 21234, 1200);
BrassPlus temp;
temp = gp; //不合法的,除非有转换构造函数BrassPlus(const Brass &);
此时实际调用方法为temp.operator=(gp),因此该句将调用派生类赋值运算符,在这时,gp作为实参,
对应的形参为BrassPlus & s,由于不可以将派生类引用指向基类对象,所以此时是不合法的。
抽象基类
01)抽象基类中至少要包含一个纯虚函数,纯虚函数是在虚函数(在函数前加上关键字virtual)的基础上,在声明的最后加上 =0 即可
例如:virtual double Area() const = 0; //声明一个纯虚函数
virtual Move(int x,y) = 0; //声明一个纯虚函数
纯虚函数可以没有定义,也可以有定义,如果有定义的话,在定义中是不需要加=0的
02)提出抽象基类目的是为了出相处两个或更多类的共性,将这些共性放到一个类(抽象类)中做为基类,然后再去派生类
03)当类声明中包含纯虚函数时,则不能创建该类的对象 *****
04)在抽象类中声明的纯虚函数,在派生类中对应的声明为虚函数即可
1 #ifndef ACCTABC_H_ 2 #define ACCTABC_H_ 3 #include <iostream> 4 #include <string> 5 6 /* 抽象类 */ 7 class AccABC 8 { 9 private: 10 std::string fullname; 11 long accNum; 12 double balance; 13 //声明若干保护成员,派生类中的方法可以直接访问保护成员,除派生类意外的类或者是函数,均不可访问保护成员 14 //且类方法是不可以直接在类外访问私有数据成员,但是在类内(比如类中方法的定义)类方法是可以访问私有数据的 15 protected: 16 struct Formatting //声明一个结构体 17 { 18 std::ios_base::fmtflags flag; 19 std::streamsize pr; 20 }; 21 const std::string & Fullname() const { return fullname; } //直接在h文件中定义类方法 22 long AccNum() const { return accNum; } //直接在h文件中定义类方法,以便类方法可以在类外,比如主函数中访问私有数据 23 Formatting SetFormat() const; //返回值为结构 24 void Restore(Formatting & f) const; 25 public: 26 AccABC(const std::string & s = "NullBody", long an = -1; double bal = 0);//声明抽象类的构造函数 27 void Deposit(double amt); 28 virtual void withdraw(double amt) = 0; //声明纯虚函数 29 virtual void ViewAcct() const = 0; //声明纯虚函数 30 double Balance() const { return balance; } //声明加定义 31 virtual ~AccABC() {} //声明加定义虚析构函数 32 }; 33 34 /* 派生类一 */ 35 class Brass : public AccABC 36 { 37 public: 38 Brass(const std::string & s = "NullBody", long an = -1, double bal = 0.0) : AccABC(s,an,bal) {} 39 //上句为构造函数的定义,必须使用成员初始化列表调用抽象类(基类)中的构造函数创建基类对象,该派生类构造函数的函数体为空 40 virtual void withdraw(double amt); //声明虚函数,注意在抽象类对应的是纯虚函数 41 virtual void ViewAcct() const; //声明虚函数 42 virtual ~Brass(); 43 }; 44 45 /* 派生类二 */ 46 class BrassPlus : public AccABC 47 { 48 private: 49 double maxLoan; 50 double rate; 51 double owesBank; 52 public: 53 BrassPlus(const std::string & s = "NullBody", long an = -1, double bal = 0.0, double m1 = 500, double r = 0.10); 54 //上句为构造函数的声明,不必使用成员初始化列表初始化基类数据,在定义中是必须使用成员初始化方法的 55 BrassPlus(const Brass & ba, double m1 = 500, double r = 0.10); 56 virtual void Withdraw(double amt); 57 virtual void ViewAcct() const; 58 void ResetMax(double m) {maxLoan = m;} //定义方法 59 void ResetRate(double r) { rate = r; } //定义方法 60 void ResetOwes() { owesBank = 0; } //定义方法 61 }; 62 63 #endif
1 //accABC.cpp 2 #include <iostream> 3 #include "accABC.h" 4 5 using std::cout; 6 using std::endl; 7 using std::ios_base; 8 using std::string; 9 10 /*----------抽象类中的方法定义----------*/ 11 AccABC::AccABC(const string & s,long an, double bal) //构造函数定义,实现数据传送 12 { 13 fullName = s; 14 accNum = an; 15 balance = bal; 16 } 17 void AccABC::Deposit(double amt) //实现存钱操作 18 { 19 if(amt<0) 20 cout<<"Negative deposit not allowed; "<<"Deposit is cancelled."<<endl; 21 else 22 balance += amt; 23 } 24 void AccABC::Withdraw(double amt) //实现取钱操作 25 { 26 balance -= amt; 27 } 28 //抽象类中的保护成员定义 29 AccABC::Formatting AccABC::SetFormat() const //第一个类限定符AccABC::表示Formatting是类AccABC中的数据结构,且该函数返回值为一个结构 30 { 31 Formatting f; 32 f.flag = cout.setf(ios_base::fixed, ios_base::floatfield);//set()设置输出格式,并返回此前的格式给f.flag 33 f.pr = cout.precision(2); //set()设置输出格式,并返回此前的格式给f.pr 34 return f; 35 } 36 //抽象类中的保护成员定义 37 void AccABC::Restore(Formatting & f) const 38 { 39 cout.setf(f.flag, ios_base::floatfield); 40 cout.precision(f.pr); 41 } 42 43 /*----------派生类一中的方法定义----------*/ 44 void Brass::Withdraw(double amt) 45 { 46 if(amt<0) 47 cout<<"Withdraw amount must be positive; "<<"withdraw is cancelled."<<endl; 48 else if(amt <= Banlance()) 49 AccABC::Withdraw(amt); //调用抽象类(基类)中的方法 50 else 51 cout<<"Withdraw amount of $ "<<amt<<" exceeds your balance.\n"<<"Withdraw is cancelled.\n"; 52 } 53 void Brass::ViewAcct() const 54 { 55 Formatting f = SetFormat(); //设置输出格式,并将以前的格式返回给结构f 56 cout<<"Brass Client: "<< Fullname << endl; 57 cout<<"Account Number: "<< AcctNum() << endl; 58 cout<< "Balance: $"<< Balance() <<endl; 59 Restore(f); 60 } 61 /*----------派生类二中的方法定义----------*/ 62 BrassPlus::BrassPlus(const string & s,long an, double bal, double m1, double r) : AccABC(s,an,bal); 63 { 64 maxLoan = m1; 65 owesBank = 0.0; 66 rate = r; 67 } 68 /* 69 对于下面的构造函数,需要注意的是,在AccABC抽象类中是没有类型为Brass引用的构造函数的,也不需要这样的构造函数 70 因为AccABC中复制构造函数中有一个AccABC引用的形参,而基类引用可以指向派生类类型 71 因此(AccABC复制构造函数)(将使用)(Brass参数的AccABC部分)构造新对象AccABC部分 72 */ 73 BrassPlus::BrassPlus(const Brass & ba, double m1, double r) : AccABC(ba) //解释见上面 74 { 75 maxLoan = m1; 76 owesBank = 0.0; 77 rate = r; 78 } 79 80 void BrassPlus::ViewAcct() const 81 { 82 Formatting f = SetFormat(); //设置输出格式,并将以前的格式返回给结构f 83 84 cout<< "Brass Client: "<< Fullname << endl; 85 cout<< "Account Number: "<< AcctNum() << endl; 86 cout<< "Balance: $"<< Balance() <<endl; 87 cout<< "Maximum loan: $" << maxLoan <<endl; 88 cout<< "Owe to bank: $" << owesBank <<endl; 89 cout.precision(3); //设置输出格式为输出小数点后3位小数 90 cout<< "Loan Rate: "<< 100*rate << "%"<<endl; 91 92 Restore(f); 93 } 94 void BrassPlus::Withdraw(double amt) 95 { 96 Formatting f = SetFormat(); 97 98 double bal = Balance(); 99 if(amt<=bal) 100 AccABC::Withdraw(amt); //调用抽象类(基类)中的方法 101 else if(amt <= bal + maxLoan - owesBank) //maxLoan是最多可借银行的钱数,owesBank是已经欠银行的钱数,bal是自己存的钱数 102 { 103 double advance = amt - bal; //advance是需要从银行借的钱数,借出来后就是自己的钱了,所以还需要Deposit(advance)将钱存进去 104 owesBank += advance*(1.0+rate); //更新欠银行的钱数 105 cout<< "Bank advance: $"<<advance <<endl; 106 cout<< "Finance charge: $"<<advance * rate <<endl; 107 Deposit(advance); //从银行借一定的钱数之后,就足够取得钱了 108 AccABC::Withdraw(amt); //此时再从自己的账户中去取钱,取完账户内余额就为0了 109 } 110 else 111 cout<<"Credit limit exceed. Transaction cancelled.\n"; 112 Restore(f); 113 }
1 //usebrass3.cpp 2 #include <iostream> 3 #include <string> 4 #include "acctabc.h" 5 6 const int CLIENTS = 4; 7 8 int main() 9 { 10 using std::cin; 11 using std::cout; 12 using std::endl; 13 14 AccABC* p_clients[CLIENTS]; //创建指向抽象类(基类)的指针数组,以便管理两个派生类的对象 15 std::string tempname; 16 long tempnum; 17 double tempbal; 18 char kind; 19 20 for(int i=0; i<CLIENTS; i++) 21 { 22 cout<<"Enter client's name: "; 23 getline(cin,tempname); 24 cout<<"Enter client's account number: "; 25 cin>>tempnum; 26 cout<<"Enter opening balance: $"; 27 cin>>tempbal; 28 cout<<"Enter 1 for Brass Account or 2 for BrassPlus Account: "; 29 while(cin>>kind && (kind != '1' && kind !='2')) 30 cout<<"Enter either 1 or 2"; 31 if (kind == '1') 32 p_clients[i] = new Brass(tempname, tempnum, tempbal); 33 else 34 { 35 double tmax, trate; 36 cout<<"Enter the overdraft limit: $"; 37 cin>>tmax; 38 cout<<"Enter the interest rate as a decimal fraction: "; 39 cin>>trate; 40 p_clients[i] = new BrassPlus(tempname,tempnum,tempbal,tmax,trate); 41 } 42 while(cin.get()!='\n') //一直接收换行符 43 continue; 44 } 45 cout<<endl; 46 for(int i=0; i<CLIENTS; i++) 47 { 48 delete p_clients[i]; 49 } 50 cout<<"Done"<<endl; 51 52 system("pause"); 53 return 0; 54 }
关键字protected的说明:
//声明若干保护成员,派生类中的方法可以直接访问保护成员,除派生类以外的类或者是函数,均不可访问保护成员
//且类方法是不可以直接在类外访问私有数据成员,但是在类内(比如类中方法的定义)类方法是可以访问私有数据的
继承和动态内存分配
01)在基类和两个派生类这都定义了友元函数,三个友元函数是互不干扰的,自己定义自己的友元函数就可以了
02)涉及到了派生类使用动态内存分配和不适用动态内存分配的情况
1 /*继承和动态内存分配*/ 2 /* 3 01)派生类不使用new 4 5 */ 6 /* 7 说明: 8 01)在基类和两个派生类这都定义了友元函数,三个友元函数是互不干扰的,自己定义自己的友元函数就可以了 9 02)涉及到了派生类使用动态内存分配和不适用动态内存分配的情况 10 */ 11 12 //dma.h 13 #ifndef DMA_H_ 14 #define DMA_H_ 15 16 #include <iostream> 17 //基类 18 class baseDMA 19 { 20 private: 21 char* label; 22 int rating; 23 public: 24 baseDMA(const char* m = "null", int r = 0); //声明构造函数 25 baseDMA(const baseDMA & rs); //声明复制构造函数 26 virtual ~baseDMA(); //声明虚析构函数 27 baseDMA & operator=(const baseDMA & rs); //声明赋值运算符,或者是对等号的重载 28 friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs); //声明友元函数,切对输出运算符<<进行重载 29 }; 30 31 //派生类一:不使用动态内存分配 32 class lacksDMA : public baseDMA 33 { 34 private: 35 enum{ COL_LEN = 40;} //使用枚举定义一个常量 36 char color[COL_LEN]; //定义一个char型数组 37 public: 38 lacksDMA(const char* c = "blank", const char* m = "null", int r = 0); //构造函数 39 lacksDMA(const char* c = "blank", const baseDMA & rs); //一个参数为基类引用的构造函数 40 friend std::ostream & operator<<(std::ostream & os, const lacksDMA & rs); //声明友元函数,切对输出运算符<<进行重载 41 }; 42 43 //派生类二:使用动态内存分配 44 class hasDMA : public baseDMA 45 { 46 private: 47 char* style; //这里在定义的时候,使用动态内存分配 48 public: 49 haseDMA(const char* s = "None", const char* m = "Null", int r = 0); //声明构造函数 50 hasDMA(const char* s, const baseDMA & rs); //声明一个参数为基类引用的构造函数 51 hasDMA(const hasDMA & hs); //声明派生类的复制构造函数 52 ~hasDMA(); //析构函数 53 hasDMA & operator=(const hasDMA & rs); //对等号的重载,返回值为指向派生类的引用 54 friend std::ostream & operator<<(std::ostream & os, const hasDMA & rs); 55 }; 56 57 #enfif
1 //usedma.cpp 2 #include <iostream> 3 #include "dma.h" 4 5 int main() 6 { 7 using std::cout; 8 using std::endl; 9 10 baseDMA shirt("Portabelly", 8); 11 lacksDMA ballon("red", "Blimpo", 4); 12 hasDMA map("Mercator", "Buffalo Keys", 5); 13 14 cout << "Displaying baseDMA object: " << endl; 15 cout << shirt << endl; 16 17 cout << "Displaying lacksDMA object: " << endl; 18 cout << ballon << endl; 19 20 cout << "Displaying hasDMA object: " << endl; 21 cout << map << endl; 22 23 lacksDMA ballon2(ballon); //调用派生类一的复制构造函数 24 cout << "Result of lacksDMA copy: " << endl; 25 cout << ballon2 << endl; 26 27 hasDMA map2; 28 map2 = map; //调用派生类一的对等号的重载函数(赋值运算符) 29 cout << "Result of hasDMA assignment: " << endl; 30 cout << map2 << endl; 31 32 system("pause"); 33 return 0; 34 }
1 //dma.cpp 2 #include "dma.h" 3 #include <cstring> 4 5 /*--------基类方法定义--------*/ 6 baseDMA::baseDMA(const char* m,int r) 7 { 8 label = new char[std::strlen(m) + 1]; //使用[]为label动态的分配内存,所以在delete的时候要加上[] 9 std::strcpy(label, m); //将m复制给label 10 rating = r; 11 } 12 baseDMA:;baseDMA(const baseDMA & rs); //定义复制构造函数 13 { 14 label = new char[std::strlen(rs.label) + 1]; //为label动态的分配内存 15 std::strcpy(label, rs.label); //将rs.label复制给label 16 rating = rs.rating; 17 } 18 ~baseDMA() //该析构函数在h文件中声明的是虚函数,但是在定义的时候不用加关键字virtual 19 { 20 delete [] label; 21 } 22 baseDMA & baseDMA::operator=(const baseDMA & rs); //定义赋值运算符,或者是对等号的重载 23 { 24 if(this == &rs) //判断被赋值(this)的地址和赋值(&rs)的地址是否相同 25 return *this; 26 delete [] label; //删除以前label中的内存 27 label = new char[std::strlen(rs.label) + 1]; 28 std::strcpy(label,rs.label); 29 rating = rs.rating; 30 return *this; 31 } 32 std::ostream & operator<<(std::ostream & os, const baseDMA & rs) //基类友元函数定义 33 { 34 os << "Label: " << rs.label << std::endl; 35 os << "Rating: " << rs.rating << std::endl; 36 return os; 37 } 38 39 /*--------派生类一方法定义:不使用动态内存分配--------*/ 40 lacksDMA::lacksDMA(const char* c, const char* m, int r) : baseDMA(m,r) //使用成员初始化列表方法调用基类构造函数 41 { 42 std::strncmp(color, c, COL_LEN-1); //注意这里使用的是strncmp()函数 43 color[COL_LEN-1] = '\0'; 44 } 45 lacksDMA::lacksDMA(const char* c, const baseDMA & rs) : baseDMA(rs) //使用成员初始化列表方法调用基类复制构造函数 46 { 47 std::strncmp(color, c, COL_LEN-1); //注意这里使用的是strncmp()函数 48 color[COL_LEN-1] = '\0'; 49 } 50 std::ostream & operator<<(std::ostream & os, const lacksDMA & rs) 51 { 52 os << (const baseDMA &) rs; //将rs强制转换为baseDMA类型,然后调用基类转换的友元函数输出。在派生类中输出基类中数据的方法***** 53 os << "Color: " << rs.color << std::endl; 54 return os; 55 } 56 57 /*--------派生类二方法定义:使用动态内存分配--------*/ 58 haseDMA::haseDMA(const char* s, const char* m, int r) : baseDMA(m,r) //使用成员初始化列表方法调用基类构造函数 59 { 60 style = new char[std::strlen(s) + 1]; //为style动态分配内存 61 std::strcpy(style,s); 62 } 63 //注意下面的这个构造函数的第二个参数是指向基类的引用 64 haseDMA::hasDMA(const char* s, const baseDMA & rs) : baseDMA(rs) //使用成员初始化列表方法调用基类复制构造函数 65 { 66 style = new char[std::strlen(s) + 1]; //为style动态分配内存 67 std::strcpy(style,s); 68 } 69 hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) //派生类二的复制构造函数,并调用基类的复制构造函数处理派生类转换属于基类的数据 70 { 71 style = new char[std::strlen(hs.style) + 1]; //为style动态分配内存 72 std::strcpy(style,hs.style); 73 } 74 hasDMA::~hasDMA() 75 { 76 delete [] style; 77 } 78 hasDMA & hasDMA::operator=(const hasDMA & rs) //对等号的重载,是两个对象可以进行赋值 79 { 80 if(this == &rs) 81 return *this; 82 baseDMA::operator=(rs); //调用基类中的对等号的重载函数,将派生类中属于基类的数据进行赋值 83 delete [] style; 84 style = new char[std::strlen(rs.style) + 1]; 85 std::strcpy(style, rs.style); 86 return *this; 87 } 88 std::ostream & operator<<(std::ostream & os, const hasDMA & rs) 89 { 90 os << (const baseDMA &) rs; //将rs强制转换为baseDMA类型,然后调用基类转换的友元函数输出。在派生类中输出基类中数据的方法***** 91 os << "Style: " << rs.style << std::endl; 92 return os; 93 } 94 95 //对dma.cpp的说明 96 /* 97 00)当派生类中使用动态内存分配时,必须为派生类定义显式析构函数、复制构造函数和赋值运算符(对等号的重载)***** 98 当派生类中没有使用动态内存分配时,就不需要在派生类中定义显式析构函数、复制构造函数和赋值运算符(对等号的重载) 99 01)当基类和派生类都使用了动态内存分配时,派生类的析构函数、复制构造函数、赋值运算符都必须使用想用的基类方法来处理基类元素 100 02)对于析构函数:派生类的析构函数会自动的调用基类的析构函数,因此只需要创建基类和派生类的析构函数即可; 101 02)对于复制构造函数:处理包含在派生类中的基类元素,是在初始化成员列表中完成的,例如: 102 haseDMA::hasDMA(const char* s, const baseDMA & rs) : baseDMA(rs) //使用成员初始化列表方法调用基类复制构造函数 103 { 104 style = new char[std::strlen(s) + 1]; //为style动态分配内存 105 std::strcpy(style,s); 106 } 107 03)对于赋值运算符(对等号的重载): 通过作用域解析运算符显式的调用基类的赋值运算符来完成的,例如: 108 hasDMA & hasDMA::operator=(const hasDMA & rs) //对等号的重载,是两个对象可以进行赋值 109 { 110 if(this == &rs) 111 return *this; 112 baseDMA::operator=(rs); //显式的调用基类的赋值运算符,将派生类中属于基类的数据进行赋值 113 delete [] style; 114 style = new char[std::strlen(rs.style) + 1]; 115 std::strcpy(style, rs.style); 116 return *this; 117 } 118 */
//对dma.cpp的说明
/*
00)当派生类中使用动态内存分配时,必须为派生类定义显式析构函数、复制构造函数和赋值运算符(对等号的重载)*****
当派生类中没有使用动态内存分配时,就不需要在派生类中定义显式析构函数、复制构造函数和赋值运算符(对等号的重载)
01)当基类和派生类都使用了动态内存分配时,派生类的析构函数、复制构造函数、赋值运算符都必须使用想用的基类方法来处理基类元素
02)对于析构函数:派生类的析构函数会自动的调用基类的析构函数,因此只需要创建基类和派生类的析构函数即可;
02)对于复制构造函数:处理包含在派生类中的基类元素,是在初始化成员列表中完成的,例如:
haseDMA::hasDMA(const char* s, const baseDMA & rs) : baseDMA(rs) //使用成员初始化列表方法调用基类复制构造函数
{
style = new char[std::strlen(s) + 1]; //为style动态分配内存
std::strcpy(style,s);
}
03)对于赋值运算符(对等号的重载): 通过作用域解析运算符显式的调用基类的赋值运算符来完成的,例如:
hasDMA & hasDMA::operator=(const hasDMA & rs) //对等号的重载,是两个对象可以进行赋值
{
if(this == &rs)
return *this;
baseDMA::operator=(rs); //显式的调用基类的赋值运算符,将派生类中属于基类的数据进行赋值
delete [] style;
style = new char[std::strlen(rs.style) + 1];
std::strcpy(style, rs.style);
return *this;
}
*/
公有成员、私有成员和保护成员的访问控制(可见性)