嘉明的C++学习之继承与多态
对应关系表
在基类私有数据和成员中,派生类无论是怎么继承都不可以访问基类的成员。
如果是私有继承,本来在基类中public属性的成员变量在派生类中都以private属性出现,protected也同样。
上面是共有派生
这里的int b1是基类的私有数据,所以派生类不可以访问。
int b2是保护数据,因此派生类访问时要以保护的形式访问。
以私有的形式派生,访问一切成员数据都要以私有的形式访问。且如果在派生类中再进行派生,其派生会终止,因为私有类型的数据派生类不可进行访问。
保护类型的派生,访问基类数据时,全部以那保护的形式进行访问。
#include<iostream>
#include<string>
using namespace std;
class Student{
public:
Student(int n,string na ,float s):num(n),name(na),score(s){}
void display(){
cout<<"num:" << num <<endl
<<"name:"<< name<<endl
<<"score:"<<score<<endl;
}
protected:
int num;
string name;
float score;
};
class Student1:protected Student
{
public:
Student1(int n,string na,float s,int a,string ad)://总参数列表,用冒号隔开
Student(n,na,s)//基类构造函数的参数列表
{
age = a;
add = ad;
}
void display1()
{
display();
cout<<"age:"<<age<<endl
<<"addr:"<<add<<endl;
}
void access_base(){
cout<<"num="<<num<<endl;
}
private:
int age;
string add;//新增地址和年龄
};
int main(){
Student1 stu(2021,"xiaoli",100,18,"NanJing");
stu.display1();
stu.access_base();
return 0;
}
析构函数与构造函数执行顺序相反,所以最后先执行派生类的析构函数再执行基类的析构函数。
多重继承
一个派生类从两个或多个基类派生而来。
例如沙发床就是一个多重继承的例子,他继承了床和沙发的特点。
基类与基类之间用逗号隔开,派生类与基类用冒号隔开。
#include<iostream>
using namespace std;
class B1//第一个基类
{
public:
int b1;
B1(int v1):b1(v1){;}
};
class B2//第二个基类
{
public:
int b2;
B2(int v2):b2(v2){}
};
class D:public B1,public B2 //多重继承,具有B1与B2的特性.
{
public:int d;
D(int v1,int v2,int v3):B1(v1),B2(v2),d(v3){}//构造函数的定义
void disp()
{
cout<<"b1 = "<<b1<<",b2 = "<<b2;
cout<<",d = "<<d<<endl;
}
};
int main(){
D demo(10,20,30);
demo.disp();
return 0;
}
当基类B1,B2有一个同名成员时,会产生二义性。
这时候就会有一问题,当两个基类的某一个成员同名时,派生类访问的是B1的成员还是B2的成员呢?
这就叫做同成员名二义性
D1、D2当从D1继承B的成员,又从D2继承B的成员时,也会产生二义性
那么要如何解决这个二义性带来的问题呢?
第一种:带域名直接访问
第二种:使用虚基类。
构造函数参数声明需要注意,要声明基类的同时也要声明基类的基类的参数。
这时A就为D的虚基类,B1、B2为D的直接基类,而不是D的虚基类
这里的disp1有两个,来自B1、B2,所以通过使用域名的方法解决这个二义性。(同名成员带来的二义性)
这里的成员a属于基类的基类A,而B1、B2都继承了A中的a,D又属于B1、B2的派生类,所以这样继承下来就会就会产生二义性,这时候就通过虚函数来解决这个问题(同一个基类被多次继承产生的二义性)
多重继承
多态就执行同一个指令时表现出不同的动作,例如,狗和猫都属于动物类,它们都执行叫这个动作时,
狗是汪汪汪叫
而
猫是喵喵喵叫
这就是所谓的多态的理解
C++中的多态就是一种函数的多种形态
编译时多态又称为静态联编,通过重载机制来实现(例如函数重载等等机制)。
运行时多态称为动态联编,通过虚函数和继承来实现。
那么什么是虚函数呢?
理解
(1)该用基类的地方可以用派生类代替,(参数、对象赋值等等)
(2)基类的指针可以指向派生类
这是没有虚函数的代码,但是固有的性质
bp是基类指针
最后的结果都是调用基类函数输出call base class,不过数值为11、12、13而已
而我们想输出的结果是带call deliver class 的
说明都是受限于基类,而不能调用派生类中的class deliver class
这就是不加虚函数的静态联编,不会出现所谓的动态联编。
受限:是因为私有派生,不可访问数据,只有共有派生才能实现。
这里定义的pb=&d,pb->show(),本来是我们的意思是调用派生类的show
但偏偏结果却是调用了基类的show()
这就说明了一个问题,这是静态联编
因此想要调用派生类的函数,必须要使用虚函数定义与派生类同名的函数(并不是虚基类),使基类的指针指向派生类时,并且可以使用它
。
虽然上面的语句都一致,但是所执行的功能却不同,这就是虚函数的作用,以及C++多态的体现。
注:最后一行是引用
形式 &x = y
指针传地址一般是 x =&y
#include<iostream>
using namespace std;
class Base
{
public:virtual void show(){
cout<<"Base class\n";}
};
class Derived:public Base
{
public:
void show()
{
cout<<"Derived class\n";
}
};
class Derived2:public Base
{
public: void show(){
cout<<"Derived2 class\n";
}
};
int main(){
Base bobj,*p;
Derived dobj1;
Derived2 dobj2;
p = &bobj;
p->show();
p = &dobj1;
p->show();
p = &dobj2;
p->show();
Base &bref = dobj2;
bref.show();
return 0;
}
虚函数的定义有点相似与虚基类解决二义性的问题
但是不能搞混。
重要的点:
纯虚函数与抽象类
例如上面的代码,只定义了一个图像类,而派生出圆形、三角形、正方形等等类,这个图像类就是抽象的东西,没什么具体意义。这时候就可以把它的某个函数定义为纯虚函数(无任何功能,不能调用),为派生类保留一个公共接口,方便多态运行。
只要有一个纯虚函数就是抽象类
其作用是提供一个公共的接口,提高派生类中继承中的层次性,纯虚函数功能的实现具体有派生类来完成
不能建立抽象类对象,但是可以建立指针来完成多态。
重要:
重要:
代码:
这里是S[0] = new Triangle(1,2)相当于把Triangle 的对象地址赋值给S[0],因为对象的定义有两种
1、类名 对象名
2、new 类名
new时,创建对象时自动调用构造
delete时,自动调用析构