此博客主要讲述类的继承与派生的相关内容,视频请参考
MOOC第九章
提示:以下是本篇文章正文内容,下面案例可供参考
继承——子类具有父类的性质,即在已存在的类上建立另一个新的类。(已存在的类叫做基类或父类,新建立的类叫做子类或派生类)
派生——子类拥有父类没有的性质。
派生类的功能
派生类只有一个直接基类
class 派生类名 : <继承方式> 基类名{…//新成员和修改的基类成员}
继承方式包括public,private,protected.
补充:保护成员特性:
基类 | 公有成员 | 私有成员 | 保护成员 |
---|---|---|---|
公有派生类 | 公有成员 | 不可访问成员 | 保护成员 |
私有派生类 | 私有成员 | 不可访问成员 | 私有成员 |
保护派生类 | 保护成员 | 不可访问成员 | 保护成员 |
即基类成员名与派生类成员名相同
同名访问规则:即如何区分该成员是基类还是派生类成员
在派生类中使用基类的同名成员:语法格式:基类名::成员
若使用派生类成员,则直接使用。派生类成员覆盖基类成员
对象使用基类的成员:对象名.基类名::成员名。
class Base
{
protected:
int v1;
public:
int v2;
Base(int a = 0,int b = 0) { v1 = a; v2 = b; }
};
class Devrid :public Base
{
int v2;
public:
int v3;
Devrid(int a = 0, int b = 0)
{
v2 = a; v3 = b;
}
void func() {
int sum1 = v1 + v2 + v3;//使用派生类成员v2
int sum2 = v1 + Base::v2 + v3;//使用基类成员v2
}
};
int main()
{
Devrid obj(5, 6);
obj.Base::v2 = 8;//基类成员的v2,可以访问
//obj.v2 = 7;//派生类成员的v2,不可访问
return 0;
}
提出:数据类型可以相互转化,但不同类型的类对象不可以相互转换,赋值兼容规则就是将子类对象转化为基类对象
内容:
在公有派生方式下:派生类对象可以作为基类对象类使用,具体方法如下:
只能将派生类对象赋值给基类对象
单继承派生类的构造函数
语法格式:
派生类构造函数(参数表):基类构造函数(参数表),对象成员名1(参数表), … 对象成员名n(参数表),
{
… //初始化自定义数据成员
}
如果基类使用缺省的构造函数或不带参的构造函数,则可以在初始化列表中省略,如果没有对象成员,也可以省略。
例子:
class Cirle
{
//Point center;
float radios;
public:
Cirle(float x, float y, float r) :center(x, y)//给point对象初始化
{
radios = r;
}
};
class ColorCirle :public Cirle
{
int color;
public:
ColorCirle(float x, float y, float r, int color) :Cirle(x, y, r)//先给基类对象初始化
{
this->color = color;
}
};
构造函数的调用顺序:
析构函数的调用顺序:
派生类只有多个直接基类
class 派生类名 : <继承方式1> 基类名1,
<继承方式2> 基类名2,
<继承方式3> 基类名3,
{…//新成员和修改的基类成员}
继承方式包括public,private,protected.
多继承派生类的构造函数
语法格式:
派生类构造函数(参数表):基类名1(参数表1),
基类名2(参数表2),
对象成员名1(参数表), … 对象成员名n(参数表),
{
… //派生类新添加的成员
}
派生类构造函数与析构函数执行顺序与单继承一致
基类对象的调用顺序与声明继承有关,对象成员的调用顺序按照类中的调用顺序。
可能出现的情况有:
访问不同基类的具有相同名字的成员时可能出现二义性
解决办法是用类名对成员加以限定,如c1.A::f()
访问共同基类的成员可能出现二义性
解决办法是使用虚基类
class A
{
public:
int a;
void g(){}
};
class B1 :virtual public A
{
int b1;
};
class B2 :virtual public A
{
int b2;
};
class C :public B1, public B2
{
int c;
public:
int f(){}
};
int main()
{
C obj;
obj.a = 8;//通过B1->A来的,因为先调用B1的基类构造函数,而调用B2的基类构造函数时,发现这个基类是虚基类,因此直接引用
obj.g();
return 0;
}
虚基类调用构造函数的次序
多态性的分类:
运算符重载:重新定义运算符作用在类类型上的含义
例子:复数的运算符重载
#include
using namespace std;
class Complex
{
double re, im;
public:
Complex(double i=0.0,double r=0.0):re(r),im(i){}
friend Complex operator+(Complex c1, Complex c2);
friend ostream& operator<<(ostream& out, Complex& obj);
};
Complex operator+(Complex c1, Complex c2) {
Complex t;
t.re = c1.re + c2.re;
t.im = c1.im + c2.im;
return t;
}
ostream& operator<<(ostream& out, Complex& obj)
{
out << obj.re << "+" << obj.im << "i";
return out;
}
int main()
{
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2;
cout << c3;
return 0;
}
在该函数前加上virtual关键字,该函数即为虚函数
一般情况下,派生类拥有从基类继承的成员,因此相对于已经定义的基类对象,如果将此对象重新定义为派生类对象,派生类从基类继承的成员对这个对象是不可见的,也就是说不能使用。
为达到以上目的,应该使用虚函数。
虚函数的特性:可以在一个或多个派生类中被重新定义,但要求在重定义时虚函数的原型(包括返回值类型,函数名,参数列表)必须完全相同。
基类中的函数具有虚特性的条件:
多态如何实现
基类必须指出希望被派生类重定义的那些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的不能定义为虚函数
实现机制是通过函数指针来实现的
虚函数表和虚指针:
VRTR由构造函数初始化
对虚函数的要求:
期望将析构函数定义为虚函数,这样容易处理基类和派生类的空间,而构造函数不能定义为虚函数
基类中的公共接口只需要有说明而不需要有实现,即为纯虚函数。具体的实现由派生类定义。
语法形式:
virtual 函数类型 函数名(参数列表)=0
试例:
class Shap//抽象类
{
virtual float Perimeter() = 0;//纯虚函数定义
virtual float Area() = 0;
};
相关概念: