C++ 继承与多态
前言
面向对象的程序设计中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的类的定义。 而面向对象程序设计的真正力量不仅仅在于继承,还在于将派生类对象当基类对象一样处理的功能。支持这种功能的机制就是多态和动态绑定。提示:以下是本篇文章正文内容,下面案例可供参考
一、继承
概念:
派生类继承基类的全部数据成员和除了构造、析构函数之外的全部函数成员。(同时可以改造基类成员,并添加新的成员)
C++不同于Java ,可以多继承,即有多个父类
继承方式:控制从基类继承的成员的访问属性,只能向下改变访问控制 即public -> protected -> private
访问控制包括两个方面:
1、控制派生类中新增成员访问基类成员(以下简称类内部访问);
2、控制派生类的实例对象访问基类成员(以下简称对象访问)
注意:当分析访问控制时分以下两种情况;
派生类访问,考虑要访问的基类的成员在基类是什么身份
派生类的对象访问,考虑要访问的成员在派生类内是什么身份,自然包括继承的和新增的成员。
很容易理解无论什么继承方式,对于继承的私有成员,类和对象都不可直接访问,下面不再涉及。
以下实例说明三种继承方式:
公有继承,保护继承,私有继承
现有:
基类 classA
公有成员:property1
保护成员:property2
私有成员:property3
代码如下(示例):
class classA
{
public:
int property1;
void initclassA(int p1, int p2, int p3);
int getP1();
int getP2();
int getP3(); //classB1、classB2、classB3
protected:
int property2;
private:
int property3;
int a;
};
1、公有继承
(1) 概念
对继承的基类成员访问控制不发生改变,仍以在基类的中身份出现在派生类中。
(2) 测试
——对继承的基类的三类成员访问控制测试
代码如下(示例):
//public 继承 访问控制未改变
classB1 B1;
B1.initclassB(1,2,3);
cout<<"B1.properties:"<<endl;
cout<<B1.property1<<endl;
//外部可以直接访问继承的public成员
//cout<<B1.property2<<endl
//error: 'int classA::property2' is protected within this context
cout<<B1.getP2()<<endl<<B1.getP3()<<endl;
//对象B1直接访问不到protected、private成员,不同的是
//classB1类内部可以直接访问protected成员
//classB1类内部不能访问private成员,但可以间接使用classA的public或proteced的外部接口访问private成员。
(3) 结论
访问控制不发生改变,继承的protected成员,对象不可直接访问,类内部可以直接访问。
2、保护继承
(1) 概念
基类的公有成员和保护成员以保护成员的身份出现在派生类中
(2) 测试
——对继承的基类的三类成员访问控制测试
代码如下(示例):
//protected 继承 public变为protected
classB2 B2;
B2.initclassB(2,4,6);
cout<<"B1.properties"<<endl;
//cout<<B2.property1<<endl;
//error: 'int classA::property1' is inaccessible within this context
cout<<B2.getP1()<<endl<<B2.getP2()<<endl;
cout<<B2.getP3()<<endl;
(3) 结论
访问控制发生改变, 公有成员和保护成员在派生类以保护成员身份出现,类可以直接访问继承的公有成员和保护成员,对象不可以直接访问
3、私有继承
(1) 概念
基类的公有成员和保护成员以私有成员的身份出现在派生类中
(2) 测试
——对继承的基类的三类成员访问控制测试
代码如下(示例):
//private 继承 public和protected变为private
classB3 B3;
B3.initclassB(3,6,9);
cout<<"B1.properties"<<endl;
//cout<<B3.property1<<B3.property2<<B3.property3<<endl;
//error: 'int classA::property1' is inaccessible within this context
//error: 'int classA::property2' is protected within this context
cout<<B3.getP1()<<endl<<B3.getP2()<<endl;
cout<<B3.getP3()<<endl;
(3) 结论:
访问控制发生改变,继承的三类成员在派生类以私有成员身份出现,类内部可以访问直接继承的公有和保护成员,对象不可直接访问。类内部不可直接访问三类成员为错误理解。
4、友元关系不能继承
(1)友元关系
C是A的友元,则C的所有成员函数可以访问可以访问A的私有和保护成员
友元关系三点注意:
1.不能传递
2.是单向的
3.不能被继承
(2) 测试
设计类A含有私有变量a,在类A中设置类C是A的友元;
代码如下(示例):
class classA
{
public:
friend class classC;
void seta(int a)
{
this->a=a;
}
int geta()
{
return a;
}
private:
int a;
};
设计类C
代码如下(示例):
class classC
{
public:
int getAa(classA &a){
return a.a; //B的基类的私有变量a, C可以直接访问,C的派生类D不可直接访问,D要重写getAa
}
int getBb(classB1 &b){
//error: 'int classB1::b' is private within this context
//return b.b; //B的基类私有变量b,C及其派生类D都不能直接访问
return b.getb();
}
};
设计类B继承A,添加私有变量b;
代码如下(示例):
class classB1: public classA
{
public:
void setb(int b)
{
this->b=b;
}
int getb(){
return this->b;
}
private:
int b;
};
在类C中测试访问类B的成员变量a, b;
代码如下(示例):
classA A;
A.seta(10);
classB1 B; //继承A
B.setb(11);
B.seta(100);
classC C; //C是A的友元
cout<<"C-A.a,B.a,B.b"<<endl;
cout<<"A.a: "<<C.getAa(A)<<endl; //C是A的友元类,可以直接访问A的私有成员。
注意下面访问B继承的私有成员a,是直接访问。
cout<<"B.a: "<<C.getAa(B)<<endl; //C是B的基类A的友元类,B从基类A继承的私有成员a, C可以直接访问
cout<<"B.b: "<<C.getBb(B)<<endl; //C是B的基类A的友元类,但不是B的友元类,不可直接访问B的私有成员,需调用外部接口
设计类D继承C
代码如下(示例):
class classD: public classC
{
public:
int getAa(classA &a){
//return a.a; //友元关系不能被继承,D不能直接访问A的私有成员,调用外部接口重写基类的getAa()
return a.geta(); //同时D不能直接访问A的派生类的私有成员。
}
// int getBb(classB1 &b){ //已继承可以也不必重写
// //error: 'int classB1::b' is private within this context
// //return b.b; //C都不能直接访问B的私有变量,D更不可能
// return b.getb();
// }
};
在D的成员函数中测试访问类A的成员变量a,类B的成员变量a, b
代码如下(示例):
classD D; //继承C
cout<<"D-A.a,B.a,B.b"<<endl;
cout<<"A.a: "<<D.getAa(A)<<endl; //D的基类是A的友元类,但D不是A的友元类,不可直接访问B的私有成员,重写getAa
cout<<"B.a: "<<D.getAa(B)<<endl; //D不能直接访问B继承的私有成员a
cout<<"B.b: "<<D.getBb(B)<<endl; //D不能直接访问B的私有成员b
//总结 在友元关系中,友元关系不能被继承,C是A的友元,但C的派生类D不是A的友元
// B是A的派生类,C可以直接访问B继承的A的私有成员
总结 :
在友元关系中,友元关系不能被继承
A B C D四个类中,
C是A的友元,但C的派生类D不是A的友元
B是A的派生类,C可以直接访问B继承的A的私有成员
4、虚继承
当派生类的部分或全部直接基类都从一个共同基类派生而来时,在直接基类之间,直接基类继承来的成员用相同名称,在派生类对象中,同名数据成员就会有多个副本,同一个函数名就会有多个映射
可以通过作用域标识符来唯一标识并分别访问他们
也可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中只有一个,同一个函数也只有一个映射
语法形式:
class 派生类名: virtual 继承方式 基类名
如果虚基类声明有非默认形式的构造函数,并且没有声明默认形式的构造函数,这时直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化类表中列出对虚基类的初始化。
二、多态性
1、类型兼容规则
(1)概念:
在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。
替代包括以下情况:
1.派生类的对象可以隐含转换为基类的对象
2.派生类的对象可以初始化基类的引用
3.派生类的指针可以隐含转换为基类的指针
需要注意的是,派生类的对象的地址赋值基类的指针,通过这个指针只能访问到从基类继承的成员
通过下面实例1体会:
代码如下(示例):
#include <iostream>
using namespace std;
class base1{
public:
void display(){
cout<<"Base1:display()"<<endl;
}
};
class derived1 :public base1{
public:
void display(){
cout<<"derived1:display()"<<endl;
}
};
int main()
{
base1 base;
base1 &b = base;
derived1 derived;
base1 &d = derived;
b.display();
d.display();
return 0;
}
发现,结果为
Base1:display()
Base1:display()
2、一般多态性函数
一般多态性: 函数名称、参数、返回值完全相同
由于上面提到的派生类的对象的地址赋值基类的指针,通过这个指针只能访问到从基类继承的成员
解决方法:基类中有同名成员,再将同名函数声明为虚函数。
虚函数成员的声明语法时:
virtual 函数类型 函数名(形参表);
注意:虚函数声明只能出现在函数声明中,不能在函数实现时
虚函数是动态绑定的
可以总结运行中的多态的三个条件:
1.类之间满足赋值兼容规则
2.声明虚函数
3.由成员函数调用或者通过指针、引用来访问虚函数
使用虚函数,实例1的程序只需修改以下部分
代码如下(示例):
class base1{
public:
virtual void display(){
cout<<"Base1:display()"<<endl;
}
};
结果为
Base1:display()
derived1:display()
总结:
要重写与基类函数同名的函数,就应该在基类中将相应的函数声明为虚函数,而继承而来的非虚函数意味着不希望被派生类改编的行为。只有通过基类的指针或引用调用虚函数时,才会发生动态绑定。
3、特殊多态性函数
当函数的参数为子类或父类的引用或指针时。
在实例1已经设置虚函数的情况下添加以下全局函数
代码如下(示例):
void fun(base1 b){
b.display();
}
main()添加
代码如下(示例):
derived1 d2;
fun(d2);
结果
Base1:display()
输入派生类,调用的是基类的函数,这是因为,函数参数为基类,自动将输入的派生类保留基类成员,而丢掉派生类新增成员。
那么如何实现多态呢
参数改为基类的引用
代码如下(示例):
void fun(base1 &b){
b.display();
}
结果
derived1:display()
代码如下(示例):
4、析构函数的多态性
构造函数不能声明为虚函数,但可以声明虚析构函数
有什么作用呢?
如果有可能基类指针调用派生类对象的析构函数,就需要设置基类的析构函数为虚函数,否则派生类的析构函数不被调用,只调用基类的析构函数。
通过以下实例说明
代码如下(示例):
#include <iostream>
using namespace std;
class base1{
public:
~base1();
};
base1::~base1(){
cout<<"base1 destructor"<<endl;
}
class derived1 :public base1{
public:
derived1();
~derived1();
private:
int *p;
};
derived1::derived1(){
p = new int(0);
}
derived1::~derived1(){
cout<<"derived1 destructor"<<endl;
delete p;
}
void fun(base1 *b){
delete b;
}
int main()
{
base1 *d = new derived1();
fun(d);
return 0;
}
结果
base1 destructor
可以看到删除派生类时,调用的是基类的析构函数。
避免以上错误就是将基类的析构函数声明为虚函数
代码如下(示例):
class base1{
public:
virtual~base1();
};
三、多继承设计组合图形
博客二:多继承设计组合图形