继承
继承介绍
继承是类的重要特性。A类继承B类,我称B类为“基类”,A为“子类”。A类继承了B类之后,A类就具有了B类的部分成员,具体得到了那些成员,这得由两个方面决定:
- 继承方式
- 基类成员的访问权限
三种继承方式比较
-
公有继承方式(public)
- 基类的私有成员,子类不可以访问
- 基类的保护成员,子类可以继承为自己的保护成员,在派生类可以访问,在外部不可以访问。
- 基类的公有成员,子类可以继承为自己的公有成员。在派生类可以访问,在外部也可以访问。 -
保护继承(protected)
- 基类的私有成员,子类不可以访问
- 基类的保护成员,子类可以继承为自己的保护成员,在派生类可以访问,在外部不可以访问。
- 基类的公有成员,子类可以继承为自己的公有成员。在派生类可以访问,在外部也可以访问。 -
私有继承(private)
私有继承方式的,就是在继承时,把protected变成private,它需要注意的事项为:- 基类公有成员,子类中继承为自己的私有成员,在派生类可以访问,在外部不可以访问。
- 基类保护成员,子类中继承为自己的私有成员,在派生类可以访问,在外部不可以访问。
- 基类私有成员,子类一样不可以访问基类的私有成员,
三种继承方式比较
从上面的结果来看,私有继承和保护继承作用完全一样。仔细一想其实还是有区别,区别是如果派生类再一次去派生其它类时,对于刚才的私有继承来说,再派生的类将得不到任何成员。而对于刚才的保护继承,仍能够得到基类的公有和保护成员。
继承中的构造析构
构造函数定义格式
派生类名::派生类名(基类所需形参,派生类成员所需形参,对象成员形参):基类名(基类的参数), <对象成员名>(对象成员的参数)
{
派生类成员初始化赋值语句;
}
调用原则
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反
继承与组合混搭情况下,构造和析构调用原则
原则: 先构造父类,再构造成员变量、最后构造自己
先析构自己,在析构成员变量、最后析构父类
先构造的对象,后释放
同名变量处理方法
1、当子类成员变量与父类成员变量同名时,子类依然从父类继承同名成员
3、在子类中通过作用域分辨符"::"进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
4、同名成员存储在内存中的不同位置
总结:同名成员变量和成员函数通过作用域分辨符进行区分
派生类中的static关键字
继承和static关键字在一起会产生什么现象?
理论知识
Ø 基类定义的静态成员,将被所有派生类共享
Ø 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质 (遵守派生类的访问控制)
Ø 派生类中访问静态成员,用以下形式显式说明:
类名 :: 静态数据成员名
总结
1> static函数也遵守3个访问原则
2> static易犯错误(不但要初始化,更重要的显示的告诉编译器分配内存)
3> 构造函数默认为private
单继承
定义格式
class <派生类名> : <继承方式> <基类名>
{
<派生类新增加的数据成员>
<派生类新增加的成员函数>
};
构造
class A{
protected:
num1;
public:
A(int n1):num1(n1){}
};
class B:public A{
protected;
num2;
public://当父类的构造函数有参数时,在子类的初始化列表中显示调用
B(int n1,int n2):A(n1),num2(n2);
};
基类对象指针与派生类对象指针的关系
CPerson person, *p_person = &person; //person为基类对象
CStudent stud, *p_stud = &stud; //stud为派生类对象
则:p_person = p_stud; //ok,派生类对象指针赋给基类对象指针
p_stud = p_person; //error,基类对象不能直接赋派生类对象
p_person->display(); //error, 基类指针不能调用派生类成员函数
((CStudent *)p_person)->display(); //ok,基类指针转换后
//可调用派生类成员函数
具有私有继承和保护继承关系的两个类类对象指针间不可互相赋值
多重继承
在过去的学习中,我们始终接触的单个类的继承,但是在现实生活中,一些新事物往往会拥有两个或者两个以上事物的属性,为了解决这个问题,C++引入了多重继承的概念,C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。
定义格式
一般格式为:
class <派生类名>:<继承方式1> <基类名1>,<继承方式2>
<基类名2>,…
{
<派生类新增加的数据成员>
<派生类新增加的成员函数>
};
构造函数
定义形式(类似单继承)
当基类构造函数不带参数时,不必显式指明调用基类构造函数;
当基类构造函数带参数时,必须显式指明调用基类构造函数;并由派生类构造函数的形式参数为被调用的基类构造函数提供实参。
派生类名::派生类名(基类1形参,基类2形参,… 派生类形参):基类名1(参数), 基类名2(参数), ...基类名n(参数)
{
派生类成员初始化赋值语句;
};
虚拟继承
二义性问题
多重继承中,派生类的基类中有2个或2个以上基类含有相同名字的成员,在派生类中该名字产生了二义性,使编译程序无法判断派生类对象在调用该名字时应调用哪个基类中的版本。
解决名字冲突的办法有两个
- 使用作用域运算符
- 重定义有名字冲突的成员
举例:
class CBase1
{
public:
int base;
};
class CBase2
{
public:
int base;
};
class CDerived : public CBase1,public CBase2
{
private:
int derived;
};
下面的访问是二义性的:
CDerived d;
d.base
下面是正确的:
d.CBase1::base
d.CBase2::base
重复继承的二义性问题
重复继承:一个派生类2次或2次以上继承同一个基类
如例图
解决方法:
通过虚拟继承,使某个公共基类的成员在其派生类中只产生一个拷贝。
关键词virtual,虚基类
为了解决多继承时可能发生的对同一基类继承多次而产生的二义性问题,使某个公共基类的成员在其派生类中只产生一个拷贝,可在从基类派生新的类时将这个基类用virtual关键字说明为虚基类
例如:class CBase1 : virtual public Cbase
在前面加上virtual关键字就可以实现虚拟继承,使用虚拟继承后,当系统碰到多重继承的时候就会自动先加入一个Cbase的拷贝,当再次请求一个Cbase的拷贝的时候就会被忽略,保证继承类成员函数的唯一性。
例:
class CBase
{ public:
int base;
};
class CBase1 : virtual public CBase
{ private:
int base1;
};
class CBase2 : virtual public CBase
{ private:
int base2;
};
class CDerived : public CBase1,public CBase2
{ private:
int derived;
};
下面的访问不会出现二义性:
CDerived d;
d.base
d.CBase::base
虚基类的派生类的构造函数
-
先执行虚基类的构造函数,再执行不是虚基类的基类的构造函数,最后执行构造函数中新加入部分;
-
若有多个虚基类时,依派生类定义时,虚基类出现次序从左至右地执行;
-
当有多个非虚基类时,也依派生类定义时,基类出现次序,从左至右地执行。
注意
虽然说虚拟继承与虚函数有一定相似的地方,但务必要记住,他们之间是绝对没有任何联系的!
多态性
(1):解释多态性:
函数的多种不同的实现方式即为多态
(2):必要性
在继承中,有时候基类的一些函数在派生类中也是有用的,但是功能不够全或者两者的功能实现方式就是不一样的,这个时候就希望重载那个基类的函数,但是为了不再调用这个函数时,出现不知道调用基类的还是子类的情况出现,于是就提出了多态。如果语言不知多态,则不能称为面向对象的。
(3):多态性是如何实现的
多态是实现是依赖于虚函数来实现的,之所以虚函数可以分清楚当前调用的函数是基类的还是派生类的,主要在于基类和派生类分别有着自己的虚函数表,再调用虚函数时,它们是通过去虚函数表去对应的函数的。
虚函数
概念:
是指在基类中以关键字virtual说明,并在派生类中重新定义的一个非静态成员函数
基类的虚函数在派生类中仍然是虚函数
在派生类中重定义继承的成员虚函数时,即使没有保留字virtual,该函数仍然是虚函数(但为了更好地表达这些函数的实质,最好加上这一保留字virtual)
限制:
- 只有类成员函数才能声明为虚函数,这是因为虚函数只适用于有继承关系的类对象中。
- 静态成员函数不能说明为虚函数
- 内联函数不可以被继承
- 构造函数不可以被继承
- 析构函数可以被继承,而且通常声明为虚函数
定义
1. 在基类中说明虚函数的方法如下:
virtual <函数返回类型> <函数名>(<参数表>)
2. 基类中的虚函数,在派生类中依然是虚函数,在实现时无须用virtual关键字说明。
实现动态联编
- 只有采用指向基类对象的指针或引用来调用虚函数时,才会按动态联编的方式来调用。
- 基类中的虚函数必须具有public或protected访问权限,且派生类必须以公有继承方式从基类派生
例:
#include<iostream>
using namespace std;
class Animal{
public:
void virtual cry(){
cout<<"I am animal."<<endl;
}
};
class Dog:public Animal{
public:
void cry(){
cout<<"I am a dog."<<endl;
}
};
class Cat:public Animal{
public:
void cry(){
cout<<"I am a cat."<<endl;
}
};
int main()
{
Dog dog;
Cat cat;
//调Animal类的Cry方法
Animal animal;
animal=dog;
animal.cry();
//调Animal类的Cry方法
animal=cat;
animal.cry();
cout<<"----------"<<endl;
Animal *p_animal;
//调Dog类的Cry方法
p_animal=&dog;
p_animal->cry();
//调Cat类的Cry方法
p_animal=&cat;
p_animal->cry();
cout<<"----------"<<endl;
//调Dog类的Cry方法
Animal &d_animal=dog;
d_animal.cry();
//调Cat类的Cry方法
Animal &c_animal=cat;
cat.cry();
return 0;
}
输出:
I am animal.
I am animal.
----------
I am a dog.
I am a cat.
----------
I am a dog.
I am a cat.
纯虚函数
基类虚成员函数有时无法具体实现,可以将其在基类中用不包括任何代码的纯虚函数来定义。
定义:
virtual <函数返回类型> <函数名>(<参数表>) = 0;
抽象类
包含纯虚函数的类称为抽象类。
- 由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
- 抽象类不能用作变量类型、函数返回和显式转换的类型,但可定义指向抽象类的指针或引用
- 在C++中,抽象类只能用于被继承而不能直接创建对象的类