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.编译器生成临时对象
返回对象与按值传递一样,都会生成临时副本,调用构造和析构函数,消耗了时间,所以比引用的时间长
但是如果函数不能返回函数中创建的临时对象的引用,因为在函数执行完,它就调用析构,不存在了。
注:析构和构造和赋值运算符都不能被继承