继承
在已有类的基础上创建新类的过程
一个 B 类继承A类,或称从类 A 派生类 B
类 A 称为基类(父类),类 B 称为派生类(子类)
类继承关系的语法形式
class 派生类名 : 基类名表
{
数据成员和成员函数声明
};
基类名表 构成
访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
无论哪种方式继承基类,派生类都不能直接使用基类的私有成员。
派生类生成的三个步骤:
- 吸收基类成员(全部吸收(构造、析构除外))
- 改造基类成员(通过在派生类中定义同名成员来屏蔽基类成员)
- 添加派生类新成员
重名成员:
派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽(hide)了基类的同名成员
在派生类中使用基类的同名成员,显式地使用类名限定符:
类名 :: 成员
派生类中访问静态成员
- 基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中的静态成员)
- 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
- 派生类中访问静态成员,用以下形式显式说明: 类名 :: 成员 或通过对象访问 对象名 . 成员
基类的初始化
在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
派生类构造函数声明为
派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 )
… 对象成员n ( 变元表 ) ;
构造函数执行顺序:1.基类 2.对象成员 3.派生类
派生类构造函数和析构函数的使用原则
- 基类的构造函数和析构函数不能被继承
- 如果基类没有定义构造函数或有无参的构造函数, 派生类也可以不用定义构造函数
- 如果基类无无参的构造函数,派生类必须定义构造函数
- 如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造
- 派生类是否定义析构函数与所属的基类无关
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
多继承:一个类有多个直接基类的继承关系称为多继承
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。
执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
多继承的构造函数
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)
{
// 派生类新增成员的初始化语句
}
**虚继承:**如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
- 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
- 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象。
- 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
- 虚继承声明使用关键字 virtual
例如:
class B { public : int b ;} ;
class B1 : virtual public B { private : int b1 ; } ;
class B2 : virtual public B { private : int b2 ; } ;
class C : public B1 , public B2 { private : float d ; } ;
/*由于类 C 的对象中只有一个 B 类子对象,名字 b 被约束到该子对象上,所以,当以不同路径使用名字 b 访问 B 类的子对象时,所访问的都是那个唯一的基类子对象。即
cc . B1 :: b 和 cc . B2 :: b 引用是同一个基类 B 的子对象*/
赋值兼容规则
赋值兼容规则指在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括以下的情况:
a 派生类的对象可以赋给基类对象
Base b;
Derived d;
b=d;
这样赋值的效果是,对象b中所有数据成员都将具有对象d中对应数据成员的值。
b 派生类的对象可以初始化基类的引用
Derived d;
Base &br=d;
c 派生类的对象的地址可以赋给基类类型的指针
Derived d;
Base *bptr=&d;
赋值兼容规则的特点:
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
虚函数和多态
多态性(Polymorphism)是指一个名字,多种语义;或界面相同,多种实现。
重载函数是多态性的一种简单形式。
虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编。
**多态的实现 **
多态性的实现和联编这一概念有关。所谓联编(Binding,绑定)就是把函数名与函数体的程序代码连接(联系)在一起的过程。
联编分成两大类:静态联编和动态联编。
静态联编优点:调用速度快,效率高,但缺乏灵活性;动态联编优点:运行效率低,但增强了程序灵活性。
C++为了兼容C语言仍然是编译型的,采用静态联编。为了实现多态性,利用虚函数机制,可部分地采用动态联编。
静态联编
联编是指一个程序模块、代码之间互相关联的过程。
静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。(重载函数使用静态联编。)
动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。 ( switch 语句和 if 语句是动态联编的例子。)
普通成员函数重载可表达为两种形式:
- 在一个类说明中重载
- 基类的成员函数在派生类重载。有 3 种编译区分方法:
(1)根据参数的特征加以区分
(2)使用“ :: ”加以区分
(3)根据类对象加以区分
基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
直接用基类指针引用基类对象;
直接用派生类指针引用派生类对象;
用基类指针引用一个派生类对象;
用派生类指针引用一个基类对象。
实现动态联编方式的前提:
- 先要声明虚函数
- 类之间满足赋值兼容规则
- 通过指针与引用来调用虚函数。
虚函数与动态联编
- 冠以关键字 virtual 的成员函数称为虚函数
- 实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本
注意:
一个虚函数,在派生类层界面相同的重载函数都保持虚特性
虚函数必须是类的成员函数
不能将友元说明为虚函数,但虚函数可以是另一个类的友元
析构函数可以是虚函数,但构造函数不能是虚函数
纯虚函数和抽象类
纯虚函数是一种特殊的虚函数,
在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。
这就是纯虚函数的作用。
纯虚函数是一个在基类中说明的虚函数,在基类中没有定义, 要求任何派生类都定义自己的版本
纯虚函数为各派生类提供一个公共界面
纯虚函数说明形式:
virtual 类型 函数名(参数表)= 0 ;
一个具有纯虚函数的基类称为抽象类。
class Employee
{ public:
Employee(const int,const string );
virtual ~Employee();
const string getName() const;
const int getNumber() const;
virtual double earnings() const=0;
virtual void print() const;
protected:
int number; // 编号
string name; // 姓名
};
class Manager : public Employee
{ public:
Manager(const int , const string, double =0.0);
~Manager() { }
void setMonthlySalary(double);
virtual double earnings() const;
virtual void print() const;
private:
double monthlySalary ;
};
class HourlyWorker : public Employee
{ public:
HourlyWorker(const long, const string, double=0.0, int =0 );
~HourlyWorker(){}
void setWage(double);
void setHours(int);
virtual double earnings() const;
virtual void print() const;
private:
double wage;
double hours;
};
class PieceWorker : public Employee
{ public:
PieceWorker(const long , const string, double =0.0, int =0 );
~PieceWorker() { }
void setWage ( double ) ;
void setQuantity ( int ) ;
virtual double earnings() const;
virtual void print() const;
private:
double wagePerPiece;
int quantity;
};