一、前言
C++提供了比修改代码更好的方法来扩展和修改类,这种方法叫做类继承,它能够从已有的类中派生出新的类,通过继承派生出的类通常比设计新类要容易的多,下面是可以通过 继承完成的一些工作:
- 可以在已有的类的基础上添加新的功能。
- 可以给类添加新的数据。
- 可以修改类方法的行为。
二、继承解释
继承是类的重要特性。A类继承B类,我称B类为“基类”,A为“子类”或者“派生类”。派生类继承了基类之后,派生类就具有了基类的部分成员,具体得到了那些成员,这得由两个方面决定:
- 继承方式
- 基类成员的访问权限
三、需要在派生类中 添加什么呢?
- 派生类需要自己的构造函数。
- 派生类可以根据需要添加额外的数据成员和成员函数。
class RatedPlayer : public TableTennisPlayer
{
private:
unsigned int rating; //新增的成员
private:
RatedPlayer(unsigned int r = 0; const string &fn = "none", const string &ln = "none", bool ht = false);
RatedPlayer(unsigned r, const TableTennisPlayer &tp);
};
构造函数必须给新成员(如果有的话)和继承的成员提供数据,在第一个构造函数中,每一个成员对应一个形参,第二个构造函数中使用一个TableTennisPlayer参数,该参数包括firstname,lastname和hasTable。
四、构造函数:访问权限
派生类不能直接访问基类的私有成员,而必须通过基类的方法进行访问,具体的说,派生类构造函数必须使用基类构造函数。
下面是第一个构造函数的代码:
//TableTennisPlayer是基类,RatedPlayer是派生类
RatedPlayer::RatedPlayer(unsigned int r, const string &fn, const string &ln, bool ht) : TableTennisPlayer(fn, ln, ht)
{
rating = r; //rating是派生类的新成员
}
其中:TableTinnesPlayer(fn, ln,ht)是成员初始化列表,它是可执行的代码,调用TableTinnesPlayer的构造函数,例如,假设程序包含如下声明:
RetedPlayer rplayer1(1140, “Mallory”, “Duck”, true);
则ReatePlayer构造函数把实参“Mallory”,“Duck”和true赋给形参fn,ln和ht,然后将这些参数作为实参传递给TableTennisPlayer构造函数,后者将创建一个嵌套TableTannisPlayer对象,并将数据 “Mallory”,“Duck”和true存储在该对象中,然后,程序进入REtedPlarly构造函数体,完成RetedPlarly对象的创建,并将参数r的值赋值给成员rating。
如果不调用基类构造函数,程序讲使用默认的基类构造函数,除非要使用默认构造函数,否则应显式调用正确的基类构造函数
下面是第二个构造函数的代码:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer &tp) : TableTnnisPlayer(tp)
{
rating = r;
}
也可以写成:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer &tp) : TableTnnisPlayer(tp), rating(r)
{
}
派生类构造函数要点如下:
- 首先创造基类对象
- 派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数
- 派生类构造函数应初始化派生类新增的数据成员
五、类成员函数继承
- 如果对于父类函数(virtual/非virtual),如果子类没有同名函数,则正常继承
- 对于父类函数(virtual、非virutal),子类有同名函数,无同型函数,则不能调用父类函数
- 对于父类函数(virtual、非virtual),如果有同型函数:
非virtual函数由指针类型决定调用哪个
virtual函数由指针指向的对象决定调用哪个(运行时决定)
代码示例:
class BaseT{
public:
void func(){
printf("BaseT: func\n");
}
void func1(){
printf("BaseT: func1\n");
}
virtual void func2(){
printf("BaseT: func2\n");
}
};
class SubTest : public BaseT{ //注意public要有
public:
void func1(){
printf("SubTest: func1\n");
}
void func2(){
printf("SubTest: func2\n");
}
};
int main()
{
SubTest *mSubTest = new SubTest();
mSubTest->func(); //非virtual , 且无同型 调用父类函数
mSubTest->func1(); //非virtual , 有同型 指针类型SubTest, 调用SubTest函数
mSubTest->func2(); //非virtual , 有同型 指针类型SubTest, 调用SubTest函数
delete mSubTest;
printf("=============\n");
BaseT *mSubTest1 = new SubTest();
mSubTest1->func(); // 非virtual , 且无同型 调用父类函数
mSubTest1->func1(); // 非virtual , 有同型 指针类型BaseT, 调用BaseT函数
mSubTest1->func2(); // virtual , 有同型 对象类型SubTest, 调用SubTest函数
}
运行结果
BaseT: func
SubTest: func1
SubTest: func2
=============
BaseT: func
BaseT: func1
SubTest: func2
五、使用虚函数的一些限制
(1):只有类成员函数才能声明为虚函数,这是因为虚函数只适用于有继承关系的类对象中。
(2):静态成员函数不能说明为虚函数,因为静态成员函数不受限与某个对象,整个内存中只有一个,所以不会出现混淆的情况
(3):内联函数不可以被继承,因为内联函数是不能子啊运行中动态的确认其位置的。
(4):构造函数不可以被继承。
(5):析构函数可以被继承,而且通常声明为虚函数。
六、纯虚函数
(1):解释
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载
纯虚函数的声明有着特殊的语法格式:virtual 返回值类型成员函数名(参数表)=0;
(2):必要性:
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。
(3):抽象类的解释
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
在C++中,抽象类只能用于被继承而不能直接创建对象的类(Abstract Class)。
#include<iostream>
#include<cmath>
using namespace std;
class A
{
public :
virtual void fun() = 0;
};
class B :public A
{
public :
virtual void fun()
{
cout << "B: " << endl;
}
};
int main()
{
B b;
b.fun();
return 0;
}