OOP笔记(八) 类的继承与派生
1.概念
类的继承:是新的类从已有类那里得到已有的特性。
类的派生:从已有的类产生一个新类的过程。
基类(父类):原有的类
派生类(子类):产生的新类。
派生类同样可以作为基类派生新的类,这样就形成了类的层次结构。类的派生实际是一种演化、发展过程,即通过扩展、更改和特殊化,从一个已知类出发建立一个新类。通过类的派生可以建立具有共同关键特征的对象家族,从而实现代码的重用,这种继承和派生的机制对于已有程序的发展和改进极为有利。
单继承与多继承
单继承:一个派生类只有一个直接基类的情况,称为单继承
多继承:一个派生类同时有多个基类,称为多继承
在派生过程中,派生出来的新类也同样可以作为基类再继续派生新的类,此外,一个基类可以同时派生出多个派生类。即:
一个基类从父类继承来的特征可以被其他新的子类所继承
一个父类的特征,可以同时被多个子类继承
这样,就形成了一个相互关联的类的家族,称为类族。
2.派生类的定义
派生类的生成过程
(1)吸收基类成员
在C++的类继承中,第一步是将基类的成员全盘接收,这样,派生类实际上就包含了它所有基类中除构造和析构函数之外的所有成员。
在派生过程中,构造函数和析构函数都不被继承。
(2)改造基类成员
对基类成员的改造包括两个方面:
A. 一个是基类成员的访问控制问题
B. 一个是对基类数据或函数成员的覆盖,就是在派生类中声明一个和基类数据或函数同名的成员。
同名覆盖:如果派生类声明了一个和基类成员同名的新成员(如果是成员函数,则函数名要相同,参数不同属于重载),派生的新成员就会覆盖外层同名的成员。
1)在派生类中通过派生类的对象直接使用成员名就只能访问到派生类中声明的同名成员(而访问不到基类中的同名成员),这称为同名覆盖。
(3)添加新的成员
派生类新成员的加入是继承与派生机制的核心,是保证派生类在功能上有所发展的关键。我们可以根据实际情况需要,给派生类添加适当的数据和函数成员,来实现必要的新增功能。
此外,由于基类的构造函数和析构函数不被继承,还需要在派生类中重新加入新的构造和析构函数。
//基类
class Employee
{
private:
string m_Name;
int m_Age;
int m_Year;
string m_Depart;
public:
Employee(){}//构造函数
~Employee(){}//析构函数
void insertInfo(string name,int age,int year,string department);//录入数据
int getAge()
{return m_Age;}
void printOn(); //输出信息
bool retire(); //判定退休
};
//派生类
class Manager:public Employee //(1)吸收基类成员:在类继承中,第一步是将基类成员全盘接收
{
private:
string m_Level; //(3)添加新的成员:级别
public:
Manager(){}//构造函数
~Manager(){}//析构函数
void insertInfo(string name,int age,int year,string department,string level); //(2)改造基类成员:同名覆盖
};
3.派生类的访问属性
基类的公有和保护成员的访问属性在派生类中保持不变,而基类的私有成员不可访问。
- 基类的公有成员和保护成员被继承到派生类中仍然作为派生类的公有成员和保护成员,派生类的其他成员可以直接访问它们。
- 其他外部使用者能通过派生类的对象访问继承来的公有成员
- 但不论是派生类的成员还是对象都无法访问基类的私有成员。
4.派生类的构造函数与析构函数
class B1
{
public:
B1(int i) //有参构造函数
{cout<<"构造B1 "<<i<<endl;}
};
class B2
{
public:
B2(int j) //有参构造函数
{cout <<"构造B2 "<<endl;}
};
class B3
{
public:
B3() //无参构造函数
{cout <<"构造B3 "<<endl;}
};
class C:public B1,public B3,public B2 //三个基类
{
public:
C(int a,int b,int c,int d) :B1(c),B2(b){} //必须初始化列表
};
int main()
{
C obj(1,2,3,4);
return 0;
}
调用基类构造函数的两种方式
• 显式方式:
派生类的构造函数中一定要为基类的构造函数提供参数
派生类::派生类(形参数列表):基类(基类参数列表)
• 隐式方式:
1、派生类的构造函数中, 省略基类构造函数时,派生类的构造函数, 自动调用基类的默认构造函数
2、派生类的析构函数被执行时, 执行完派生类的析构函数后, 自动调用基类的析构函数
class Base { //基类
public:
int n;
Base(int i):n(i)
{ cout << "基类 " << n << " 构造" << endl; }
~Base()
{ cout << "基类" << n << "析构" << endl; }
};
class Derived:public Base { //派生
public:
Derived(int i):Base(i)
{cout << "派生类构造" << endl; }
~Derived()
{cout << “派生类析构” << endl;}
};
int main()
{
Derived Obj(3); return 0; }
class A
{
public:
A (int i)
{ x = i;
cout<<"A构造"<<endl; }
~A() { cout<<"析构"<<endl; }
int getX()
{
return x; }
private:
int x;
};
class B
{
public:
B(int i) : a1(i+10)
{ y = i;
cout<<"B 构造"<<endl; }
~B() { cout<<"B 析构"<<endl; }
void printB()
{cout<<"B's d, x = "<<a1.getX()<<endl;}
private:
int y;
A a1;//对象成员
};
class D : public B
{
public:
D(int i) : B(i-2), a2(i+2)
{ z = i;
cout<<"D 构造"<<endl; }
~D() { cout<<"D 析构"<<endl; }
void dispD(){}
private:
int z;
A a2;//对象成员
};
int main()
{
D d(2);
return 1;
}
(1)创建派生类的对象时, 执行派生类的构造函数之前:
A、调用 基类 的构造函数,按照基类继承顺序调用构造函数
初始化派生类对象中从基类继承的成员
B、调用 成员对象类 的构造函数,按照成员对象的定义顺序
初始化派生类对象中成员对象
执行完 派生类的析构函数 后:
C、调用 成员对象类 的析构函数
D、调用 基类 的析构函数
(2) 析构函数的调用顺序与构造函数的调用顺序相反
4.多重继承
#include <iostream>
class B1
{
public:
int nv;
void fun() {cout<<"B1::B1"<<endl;}
void fun(int x)
{
cout<<x;
fun();}
};
class D1:public B1
{
public:
int nv;
void fun()
{cout<<" D1::D1 "<<endl;}
};
void main()
{
D1 d1;
d1.nv=1;
d1.fun(12); //编译出错,找不到对应的函数
d1.B1::nv=2;
d1.B1::fun(12);
cout<<d1.nv<<endl; //输出d1的nv
cout<<d1.B1::nv<<endl; //输出d1基类B1的nv
}
隐藏:如果派生类和基类都有一个同名的函数,而编译器最终选择了派生类的成员函数,阻止了编译器继续向上查找函数的定义。
在刚才的继承中,派生类中的fun()隐藏了基类的fun(int)。
,(这种情况下)派生类成员将覆盖所有基类的同名成员。
覆盖:是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);(2)函数名字相同;(3)参数相同;
5.继承中同名情况的访问
两种情况(均需要使用作用域标识符):
1)、多重继承,各基类间无继承关系,无共同基类
如果某派生类的多个基类拥有同名的成员,同时,派生类也新增了这样的同名成员,(这种情况下)派生类成员将覆盖所有基类的同名成员。则:
使用“对象名.成员名”可以唯一标识和访问派生类新增成员。
采用作用域分辨符(::)可以访问基类的同名成员
class A
{
private:
int a;
public:
void print() //
{ cout<<“A::a = "<<a<<endl; }
};
class B
{
private:
int a;
public:
void print()
{ cout<<“B:: a= "<<a<<endl; }
};
class C : public A, public B
{
private:
int a;
public:
void print()
{
A::print();
B::print();
cout<<“C::a = "<<a<<endl;
}
};
int main()
{
C obj;
obj.print();
obj.A::print();
obj.B::print();
return 0;
}
2)、各基类间有继承关系或有共同基类。
如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称。
则:
对于同名成员,也需要使用作用域分辨符来唯一标识,而且必须用直接基类来限定。
#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(int s = 0) { speed = s; }
void SetSpeed(int s)
{
cout<<"Set Speed"<<endl;
speed = s;
}
void print()
{
cout<<"I'm a Vehicle"<<endl; }
privated:
double speed;
};
class Car : public Vehicle //汽车
{
public:
Car(int s = 0, double km = 0):Vehicle(s) { Km = km; }
void print() {cout<<"I'm a car"<<endl; }
privated:
double Km;
};
class Boat : public Vehicle //船
{
public:
Boat(int s = 0, double kn = 0):Vehicle(s) { Knot = kn; }
void print() { cout<<"I'm a Boat"<<endl; }
privated:
double Knot;
};
class AmphibianCar : public Car, public Boat
{
public:
AmphibianCar(int s,int km, double kn) : Car(s, km), Boat(s, kn) { }
void print()
{ cout<<"I'm an AmphibianCar"<<endl; }
};
int main()
{
AmphibianCar a(10.0, 80.0, 40.5);
// a. SetSpeed(20); 不明确
a. Car::SetSpeed(20);
a.print();
return 0;
}
6.菱形继承-虚继承
虚基类:将共同的基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。在派生类的声明过程中声明。
语法形式:
class 派生类名:virtual 继承方式 基类名
说明:
作用是声明基类为派生类的虚基类。
多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。
声明了虚基类后,虚基类的成员在进一步派生过程中和派生类一起维护同一个内存数据拷贝。
#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(int s)
{ speed = s; } //带参数构造函数
void SetSpeed(int s)
{
cout<<"Set Speed"<<endl;
speed = s;
}
void print()
{ cout<<"I'm a Vehicle"<<endl; }
private:
double speed;
};
class Car : virtual public Vehicle //汽车
{
public:
Car(int s = 0, double km = 0):Vehicle(s) { Km = km; }
void print() {cout<<"I'm a car"<<endl; }
private:
double Km;
};
class Boat : virtual public Vehicle //船
{
public:
Boat(int s = 0, double kn = 0)
:Vehicle(s) { Knot = kn; }
void print() { cout<<"I'm a Boat"<<endl; }
private:
double Knot;
};
class AmphibianCar : public Car, public Boat
{
public:
AmphibianCar(int s,int km, double kn)
: Vehicle(s), Car(s, km), Boat(s, kn) { }
void print()
{ cout<<"I'm an AmphibianCar"<<endl; }
};
int main()
{
AmphibianCar a(10.0, 80.0, 40.5);
a. SetSpeed(20); // 正确
a. Car::SetSpeed(20);
a.print();
return 0;
}