C++中的继承
C++中的继承
继承是使代码复用的重要手段,允许在保持原有类特性的基础上进行扩展
增加功能,产生的新类成为派生类,父类也称为基类
//基类
class Person
{
public:
void Print(){
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "mike";
int _age = 18;
};
//派生类1
class Student:public Person//注意格式,:public表示继承方式,如果不加public的话,class默认是private,struct默认是public
{
protected:
int _sutID;
};
//派生类2
class Teacher:public Person
{
protected:
string _job;
};
继承后父类的成员(成员函数+成员变量)都会变成子类的一部分两个派生类复用了父类的成员
总结:
1.基类private成员在派生类中不论以何种继承方式都是不可见的,
不可见是指基类的私有成员还是继承到了派生类中,但是语法限制
派生类对象无论在类内还是类外都不能访问它
2.基类private成员在派生类中不能被访问,如果积累成员不想被在类外被访问
但是需要在派生类中可以被访问,就定义为protected,所以保护成员限定符是因为继承而出现的
3.访问方式:public>protected>private,实际上一般都是用public继承,其他两个很少用
基类和派生类对象赋值转换
//基类和派生类对象赋值转换
class Person
{
public:
void Print(){
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "mike";
int _age = 18;
};
class Student :public Person//注意格式,:public表示继承方式,如果不加public的话,class默认是private,struct默认是public
{
public:
int _sutID;
};
void Test(){
//1.子类对象可以赋值给基类对象/指针/引用
Student sobj;
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
//sobj = pobj;
//3.基类的指针可以通过前置类型转换赋值给派生类的指针
pp = &sobj;
Student* ps1 = (Student*)pp;//转换为派生类的指针
ps1->_sutID = 10;
}
int main(){
void Test();
}
派生类对象可以赋值给基类的对象/基类的指针/基类的引用,
但是基类对象不能赋值给派生类对象
基类的指针可以通过强制类型转换赋值给派生类的指针,
但是必须是基类的指针是指向派生类对象是才是安全的
继承中的作用域
1.在继承体系中基类和派生类都有独立的作用域
2.子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问
这叫做函数隐藏,也叫重定义,(在子类成员函数中,可以使用 基类::基类成员 显示访问)
只要函数名相同就构成隐藏,实际使用最好不要定义同名函数
派生类的六个默认成员函数
//基类
class Base
{
public:
Base(int b)//基类构造函数
:_b(b)
{
cout << "Base(int)" << endl;
}
Base(Base& b)//基类拷贝构造函数
:_b(b._b)
{
cout << "Base(Base)" << endl;
}
Base& operator=(const Base& b){
if (this!= &b)
{
_b = b._b;//用基类对象给当前对象赋值
}
return *this;
}
~Base(){
cout << "Base()" << endl;
}
protected:
int _b;
};
//派生类
class Derived:public Base
{
public:
Derived(int b,int d)
:Base(b)//显示将基类构造函数调用
,_d(d)
{
cout << "derived(int)" << endl;
}
Derived(Derived& d)
:Base(d)//显示调用基类拷贝构造函数
,_d(d._d)
{
cout << "Derived(Derived&)" << endl;
}
Derived& operator-(const Derived& d){
if (this!=&d)
{
Base::operator=(d);//用基类的赋值初始化基类的部分
_d = d._d;
}
return *this;
}
~Derived(){
cout << "Derived()" << endl;
//自动调用基类的析构函数
//call Base::~Base();
}
protected:
int _d;
};
int main(){
Derived d1(10,20);
Derived d2(d1);
Derived d3(30,40);
d1 = d3;
return 0;
}
派生类的构造函数必须调用基类的构造函数来初始化属于基类的那一部分成员
如果基类没有默认的的构造函数,则必须在派生类狗凹函数的初始化列表显式调用
同样,派生类的拷贝构造函数和赋值运算符的重载也都要调用基类的来完成基类那一部分成员的操作
派生类的析构函数会在被调用完成之后自动调用基类的析构函数,这样才能保证先清理派生类再清理基类
调用顺序
派生类对象初始化先调用基类构造再调用派生类构造
派生类对象析构先调用基类的析构再调用基类析构,因为需要先初始化基类,再调用派生类
继承和有友元
//友元关系不能继承,也就是说基类友元不能访问子类的私有和保护成员
class Base
{
friend void Print();//基类的友元函数
public:
Base(int b=0)//基类构造函数
:_b(b)
{ }
protected:
int _b;
};
class Derived :public Base
{
public:
Derived(int b, int d)
:Base(b)
, _d(d)
{}
private:
int _d;
};
void Print(){
Base b;
b._b = 10;
Derived d(1, 2);
//d._d = 10; //友元关系不能继承,不能访问派生类
}
int main(){
void Print();
}
继承与静态成员
public:
static int _count;
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员,无论生出多少个子类,都只有一个static成员实例
复杂的菱形继承及菱形虚拟继承
菱形继承的问题
菱形继承有数据冗余和二义性的问题,在类D中会有两份来自A的成员
解决方法:
1.显式指定访问哪个父类的成员可以解决二义性,但是数据冗余无法解决
void Test(){
D a;
a._name = "mike";//二义性导致不知道访问哪个
//指定访问哪个父类成员,访问函数同样
a.B::_name = "abc";
a.C::_name = "def";
}
2.虚拟继承
在类B和类C继承基类A时加入virtual,这样保证了在子对象创建时,只保存了基类A的一份拷贝,解决了数据冗余和二义性问题…
C++使用虚拟继承,解决了从不同路径继承来的相同基类的数据成员在内存中有不同的拷贝造成数据不一致的问题,将共同基类设置为虚基类,这时从不同路径继承的虚基类在内存就只有一个映射
class B:virtual public A
{
protected:
string _name;
};
class C :virtual public A
{
protected:
string _name;
};
虚拟继承解决数据冗余和二义性的原理
特点:
1.虚拟继承比普通继承多了四个字节
2.派生类的对象模型 派生类对象模型倒立:基类在下,派生部分在上
3.编译器我派生类对象生成了默认的构造函数(如果不显式定义构造函数的话),并在构造函数中将对象的前四个字节初始化好
4.虚拟继承中派生类对象访问部分基类成员与访问自身成员的方式不同
菱形虚拟继承的内存对象模型:
下面的A同时隶属于B和C,这里通过B和C的两个指针,指向一张表,叫做虚基表.
虚基表中存放的是偏移量,通过偏移量找到下面的A
继承中的相关面试题
1.什么是虚拟继承,怎么实现
答:虚拟继承是一种机制,类通过虚拟继承指出它希望共享虚基类的状态。对给定的虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象,共享基类子对象称为虚基类。虚基类用virtual声明继承关系
2.什么是菱形继承?菱形继承的问题是什么?
答:菱形继承是一种特殊的多继承,假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C,导致了D中含有两份A类中的成员,产生代码冗余和二义性问题
3.菱形虚拟继承是如何解决冗余和二义性的?
答:通过两个虚基表指针指向的两个虚基表,虚基表中存放的是相对于自己和相对于基类的偏移量,通过相对于基类的偏移量来寻找
4.继承和组合的区别以及优缺点
类继承与对象组合是实现类复用的两种最常用的技术
继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。
继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。
继承的缺点:
1:父类的内部细节对子类是可见的。
2:子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
3:子类与父类是一种高耦合,违背了面向对象思想。
4 :继承关系最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合,子类缺乏独立性,从而影响了子类的可维护性。
5:不支持动态继承。在运行时,子类无法选择不同的父类。
组合的优点
1:不破坏封装,整体类与局部类之间松耦合,彼此相对独立。
2:具有较好的可扩展性。
3:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象。
组合是has a的关系
继承是is a的关系
总结
1.除非考虑使用多态,否则优先使用组合。
2.要实现类似”多重继承“的设计的时候,使用组合。
3.要考虑多态又要考虑实现“多重继承”的时候,使用组合+接口。
转自 https://blog.csdn.net/gvinaxu/article/details/51731202
上一篇: 【Java】Hibernate(八)关联映射之多对多
下一篇: 嵌入式基础知识之大小端字节序
推荐阅读
-
C++中的继承
-
.net实现oracle数据库中获取新插入数据的id的方法
-
关于.NET Framework中的设计模式--应用策略模式为List排序
-
在dropDownList中实现既能输入一个新值又能实现下拉选的代码
-
Java中的Vector和ArrayList区别及比较
-
Android UI设计系列之自定义SwitchButton开关实现类似IOS中UISwitch的动画效果(2)
-
详解maven的setting配置文件中mirror和repository的区别
-
Java中==与equals的区别小结
-
thinkPHP中钩子的两种配置调用方法详解
-
ThinkPHP5实现作业管理系统中处理学生未交作业与已交作业信息的方法