五、继承与派生
1、继承与派生的概念
基类也可以叫做父类, 派生类可以叫做子类
继承、 派生 这两个词都是指的是传递关系, 只不过继承是站在子类角度说的, 派生是站在父类角度说的
为什么要使用继承,设计一个新类的时候可以继承父类的成员,减少设计类的冗余开发, 从而较好的解决了代码重用的问题
最高层(基类) : 是最普遍、 最一般的
底层(派生类) : 比它的上一层更具体, 并且含有高层的特性(继承) , 同时也与高层有细微不同
2、派生的方式
一般格式:
class 派生类名:派生方式 基类名
{
//派生类新增的数据成员和成员函数
};
class employee:public person{
private:
char department[20];
float salary;
public:
//...
};
employee 类继承 person 类,也可以说 person 类派生出了 employee 类。
在声明派生类时, 根据”派生方式”所写的关键字不同可以分为三种派生方式
a. 用 public 关键字的为 公有派生方式
b. 用 private 关键字的为 私有派生方式
c. 用 protected 关键字的为 保护派生方式
从继承源上分
1.单继承:每个派生类只直接继承了一个基类的特征
2. 多继承:多个基类派生出一个派生类的继承关系, 多继承的派生类直接继承了不止一个基类的特征。
class employee:public person,public animal
{
char department[20];
float salary;
public:
//...
};
3、公有派生(public)
class employee:public person{
char department[20];
float salary;
public:
//...
};
基类中的私有成员在派生类中不可访问,不可见;基类中的共有成员在派生类中仍是公有的,可以访问
派生后,可以通过基类中的公有成员函数访问基类中的私有成员
定义派生类构造函数时,要使用类似于对象成员的定义方式,参数列表写基类的名称
class Base{
private:
int x;
public:
Base(int x);
void setX(int x);
int getX(void);
~Base();
};
class Derive:public Base{//公有派生
private:
int y;
public:
Derive(int x,int y);//构造函数, 一定要有参数列表,只能写基类的类名称
void setY(int y);
int getY(void);
~Derive();//析构函数
};
Derive::Derive(int x,int y):Base(x)//在定义派生类构造函数时, 参数列表, 只能写基类的名称
{
this->y=y;
cout<<"Derive 中的 构造函数"<<endl;
}
构造的时候先构造基类再构造派生类;析构的时候先析构派生类再析构基类
.实例化对象的时候, 先给基类开辟空间, 再给派生类扩展部分开辟空间
4、私有派生
class employee:private person{
char department[20];
float salary;
public:
//...
};
基类中的私有成员派生类中不能访问,基类中的公有成员在派生类中是私有成员
私有派生和公有派生的构造函数是一样的
5、保护成员的作用
在不涉及派生的时候保护成员和私有成员是一样的;在涉及到派生的时候, 基类中私有成员, 无论是什么样的派生方式, 在派生类中都是不可见;
有时候咱们想让基类中的私有成员在派生类的成员函数中可以用, 私有成员是不可以的, 那咱们可以将基类中的私有成员声明成保护成员。 基类中的保护成员在派生中是可见的是可以用的。
在公有派生时,基类中的保护成员在派生类中仍是保护的;基类中的私有成员, 在派生类中是不能访问的
在私有派生时,基类中的保护成员在派生类中是私有的
6、保护派生
class employee:protected person
{
//…
};
基类中的私有成员在派生类中是不可见的不可访问;基类中的公有成员在派生类中是保护的
基类中的保护成员在保护派生之后还是保护的
7、派生后的访问权限总结
派生方式
公有派生public
私有派生private
保护派生protected
基类属性
私有 不能访问 不能访问 不能访问
保护 保护 私有 保护
公有 公有 私有 保护
8、多继承
多继承可以看做是单继承的扩展。 所谓多继承是指派生类具有多个基类, 派生类与每个基类之间的关系仍可看做是一个单继承
多继承下派生类的定义格式如下:
class 派生类名:继承方式 1 类名 1,继承方式 2 类名 2,....
{//派生类体
};
其中继承方式为public、private和protected之一
#include <iostream>
using namespace std;
class Point{
public:
void setXY(int a, int b)
{
x = a;
y = b;
}
int getX()
{return x;}
int getY()
{return y;}
private:
int x;
int y;
};
class Line{
public:
void setL(int a)
{
l = a;
}
int getL()
{return l;}
private:
int l;
};
class Face:public Point,public Line{ //以公有方式派生出 Face
public:
void setM(int a)
{m = a;}
int getM()
{return m;}
private:
int m;
};
int main()
{
Point cc;
cc.setXY(2,3);
cout<<cc.getX()<<","<<cc.getY()<<endl;
cout<<"------------------\n";
Face f;//定义 Face 对象
f.setXY(4,5);
f.setL(6);
f.setM(7);
cout<<f.getX()<<","<<f.getY()<<","<<f.getL()<<","<<f.getM()<<endl;
return 0;
}
在使用多继承是要注意避免发生二义性,两个父类中有可能有名字相同的数据成员或者成员函数
解决方法是:
c1.A::print();//调用 A 的 print
或者 c1.B::print();//调用 B 类中的 print
在 C 公有继承 A 和 B, 当 A 和 B 都公有继承 D 时, 这使得 A 和 B 都有 D 的公有部分, 在 C 中使用 D 的公有部分时, 编译器就会不确定是调用 A 的还是调用 B 的 而出现错误。因此要使用作用域预算符
#include<iostream>
using namespace std;
class D{
public:
int d;
};
class A:public D{
public :
int a;
};
class B:public D{
public :
int b;
};
class C:public A,public B{
public :
int c;
};
int main()
{
C ob;
ob.c=100;
cout<<"ob.c= "<<ob.c<<endl;
ob.A::d=200;
cout<<"ob.d= "<<ob.A::d<<endl;
return 0;
}
9、派生类的构造函数和析构函数
基类都有构造函数和析构函数, 或是显式定义、 或是隐式定义
派生类构造和析构函数的执行顺序:先构造基类, 再构造派生类。 先析构派生类, 再析构基类
当基类的构造函数没有参数(或全部默认值) 或没有显式的定义构造函数时, 那么派生类可以不向基类传递参数, 可以不定义构造函数
当基类含有带参数的构造函数时, 派生类必须定义构造函数, 以提供把参数传递给基类构造函数的途径
派生类构造函数的一般格式
派生类构造函数名(参数表 0):基类构造函数名(参数表 1)
{//......
}
参数表 1 是参数表 0 的子集
当派生类中含有对象成员时, 其构造函数的一般形式为
派生类构造函数名(参数表 0):基类构造函数名(参数表 1),
对象成员名 1(参数表 2),....,
对象成员名 n(参数表 n+1)
{//......
}
在定义派生类对象时构造函数的执行顺序是:基类->对象成员->派生类
析构函数的执行顺序是:派生类->对象成员->基类
#include <iostream>
using namespace std;
class Base{
private:
int x;
public:
Base(int i){
x = i;
cout<<"构造 base 类, x="<<x<<endl;
}
~Base(){
cout<<"析构 base 类, x="<<x<<endl;
}
void show(){
cout<<"x="<<x<<endl;
}
};
class Derive:public Base{
private:
int y;
Base d;
public:
Derive(int i,int j,int k):Base(i),d(j){
y = k;
cout<<"构造 derived 类, y="<<y<<endl;
}
~Derive(){
cout<<"析构 derived 类, y="<<y<<endl;
}
};
int main()
{
Derive obj(1,2,3);
obj.show();
return 0;
}
如果派生类的基类也是派生类,则每个派生类只需要负责其直接基类的构造,即只需要对父类负责
构造的时候先构造第一代再第二代最后第三代,析构的时候刚好相反
由于析构函数是不带参数的, 在派生类中是否需要定义析构函数与它所属的基类无关, 故基类的析构函数不会因为派生类没有定义析构函数而得不到执行, 它们各自是独立的