继承与派生

class TablePlayers
{
private:
	string firstname;
	string lastname;
	bool hasTable;
public:
	TablePlayers(const string &fn = "none", const string &ln = "none", bool ht = false);
	void name()const;
	bool HashTable() const { return hasTable; };
	void RestTable(bool v) { hasTable = v; };
};

定义一个基类TablePlayers

继承与派生:使用公有派生,基类的公有成员将成为派生类的公有成员,基类的私有成员也将成为派生类的一部分,但是只能通过基类的公有和保护方法访问。

class RatePlayer : public TablePlayers
{

};

定义RatePlayer是TablePlayers的派生类

RatePlayer将具有以下的特征:

1.派生类对象存储了基类的数据成员(派生类继承了基类的实现);

2.派生类对象可以使用基类的方法(派生类继承了基类的接口)。

 

那么派生类需要在继承特性中添加什么呢?

1.派生类需要自己的构造函数

2.可以根据需要添加额外的数据成员和成员函数

class RatePlayer : public TablePlayers
{
private:
	unsigned int rating;
public:
	RatePlayer(unsigned int r = 0, const string &fn = "none", const string &ln = "none", bool ht = false);
	RatePlayer(unsigned int r, const TablePlayers &tp);
	unsigned int Rating()const { return rating; }//add a method
	void ResetRating(unsigned int r) { rating = r; } // add a method

};

第一个RatedPlayer构造函数中,每个成员对应一个形参。

而第二个RatedPlayr构造函数中使用一个TablePlayers参数,参数包括了私有数据成员。

 

派生类的构造函数必须给新成员(如果有的话)和基类的成员提供数据

派生类不能直接访问基类的私有成员,而必须通过基类的方法进行访问。

派生类的构造必须使用基类的构造

 

第一个构造函数的代码及使用:

RatePlayer::RatePlayer(unsigned int r, const string &fn, const string &ln, bool ht) : TablePlayers(fn, ln, ht)
{
	rating = r;
}

RatePlayer rplayer1(1140, "a", "b", true);

 

有关派生类构造的要点:

1.首先创建基类的对象

2.派生类构造函数应通过成员初始化列表讲基类信息传递给基类构造

3.派生类构造应初始化派生类新增的数据成员

注:在创建派生类对象时,先调用基类构造,再调用派生类构造。

       派生类过期时,先调用派生类析构,再调用基类析构。

 

派生类与基类关系:

1.派生类可以调用基类的方法,但方法不能是私有的

2.基类指针可以在不进行显示类型转换的情况下指向派生类对象

3.基类引用可以在不进行显示类型转换的情况下引用派生类对象

 

不可将基类的对象和地址赋给派生类的引用和指针    基类指针只能调用基类的方法

这个是正确的:

    A a(1, 2);
	B b(3,2,1);

	A &rt = a;
	A *pt = &a;
	rt.show_a();
	pt->show_b();

这个是错误的,无意义的:

B &rt = a;
B *pt = &a;

两种方法实现多态公有继承:

1.在派生类中重新定义基类方法

2.使用虚方法

 

 

如果方法是通过引用或指针而不是对象调用的,它将确定使用哪一种方法

如果没有virtual,程序将根据引用类型或指针类型选择方法

有virtual,程序将根据引用或指针指向的类型选择方法

 

方法在基类中被声明为虚的后,他在派生类中将自动生成虚方法。

在基类中声明一个虚析构,为了确保释放派生对象时,按照正确顺序调用析构。

 

派生类不能直接访问类的私有数据,而必须使用基类的共有方法才能访问这些数据。

派生诶构造函数在初始化基类私有数据时,采用的是成员初始化列表语法。

 

虚析构:保证正确的析构函数序列别调用

如果不是虚析构,则调用基类的析构,否则先调用子类的析构,再调用基类的析构。

 

虚函数通过动态联编,编译器对非虚方法使用静态联编

动态联编比静态联编更加耗时间(需要定义一些方法来跟踪基类指针和引用指向的对象模型),所以默认情况下是静态。

 

虚函数的工作原理:

编译器处理虚函数的方法:给每个对象添加了一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称作虚函数表。虚函数表中存储了为类对象进行声明的虚函数的地址。

例如:基类对象包含了一个指针,该指针指向基类中所有虚函数的地址表。

派生类对象中将包含一个指向独立地址表的指针。如果派生类提供了虚函数的定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到vbtl中。

 

在使用虚函数时,在内存和执行速度方面有一定的成本,包括:

1.每个对象都将增大,增大量为存储地址的空间;

2.对于每个类,编译器都创建一个虚函数地址表(数组)

3.对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。

 

注:

1.构造不能为虚函数。因为创建派生类时,调用派生类的构造,而不是基类的。然后,派生类的构造将使用基类的一个构造。所以,派生类不继承基类的构造,所以为虚没有意义。

2.析构可以为虚

3.友元不能为虚函数,因为友元不是类成员。而只有成员才能为虚函数。

4.重新定义将隐藏方法

class A
{
public:
        virtual void s(int a) const;
};
class B : public A
{
public:
        virtual void s() const;
};

例如这样,重新定义不会生成函数的两个重载版本,而是隐藏了接受一个yin参数的基类版本。

 

Protected: 访问控制

protected与private的区别只有在基类派生的类中才能表现出来。

派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员

对于外部世界来说,保护成员与私有成员行为相似。

对于派生类来说,保护成员与公有成员类似。

 

抽象基类(ABC)

ABC只能作为基类,必须含有一个纯虚函数

例如:我们如果要实现圆或椭圆,我们知道椭圆是圆的特殊结构,我们可以用圆类派生出椭圆类,但是这样会有一些问题,这时候,我们可以把圆和椭圆的一些特性抽出来,组成一个基类(ABC),然后派生出圆和椭圆。

C++通过纯虚函数来提供未实现的函数

virtual double Area() const = 0;

我们通过=0的方式来声明纯虚函数。

 

默认构造函数:

默认构造函数要么没有参数,要么所有参数都有默认值。

复制构造函数:

在以下几种情况,使用复制构造函数:

1.将新对象初始化为一个同类对象

2.按值将对象传递给函数

3.函数按值返回对象

4.编译器生成临时对象

返回对象与按值传递一样,都会生成临时副本,调用构造和析构函数,消耗了时间,所以比引用的时间长

但是如果函数不能返回函数中创建的临时对象的引用,因为在函数执行完,它就调用析构,不存在了。

注:析构和构造和赋值运算符都不能被继承

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值