继承
继承的概念
继承机制是面向对象程序设计使代码可以复用的最重要的手段,他允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生的新类称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
void Print()
{
cout << _name << endl;
cout << _age << endl;
}
protected:
int _age=12;
string _name = "peter";
};
class student :public Person
{
protected:
int _stuid;
};
class teacher :public Person
{
int _jobid;
};
int main()
{
student s;
teacher t;
s.Print();
t.Print();
return 0;
}
总结:
1.基类的private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到派生类对象当中,但语法上限制派生类对象不管在类内还是类外都不能访问他。
2.基类的private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出protected限定符是因为继承才出现的。
3.基类的私有成员在子类都是不可见的。基类的其他成员在子类中的访问方式为:基类名::基类成员。
4.使用class时默认的继承方式时private,使用struct时的默认继承方式时public,不过最好显示的写出继承方式。
5.在实际中一般使用的都是public继承,几乎很少使用protected、private继承,也不提倡使用protected,private继承,因为protected,private继承下来的成员只能在派生类的类里使用,在实际中扩展维护性不强。
基类和派生类对象赋值转换
1.派生类对象可以赋值给基类的对象,基类的指针,基类的引用。这里有个形象的说法叫切片或者切割。寓意是把派生类中父类那部分切来赋值过去。
2.基类对象不能赋值给派生类。
3.基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针指向派生类对象时才是安全的。这里的基类如果是多态类型,可以使用RTTI的dynamic cast来进行识别后进行安全转换。
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
void Print()
{
cout << _name << endl;
cout << _age << endl;
}
protected:
int _age=12;
string _name = "peter";
};
class student :public Person
{
protected:
int _stuid;
};
int main()
{
student s;
//派生类对象可以赋值给基类的对象,基类的指针,基类的引用
Person pobj = s;
Person* p = &s;
Person& rp = s;
//基类对象不能赋值给派生类
//s = pobj;
//基类的指针可以通过强制类型转换赋值给派生类的指针
student* ps1 = (student*)p;
ps1->Print();
return 0;
}
派生类的默认成员函数
1.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
2.派生类的拷贝构造函数必须要调用基类的拷贝构造函数完成基类的拷贝初始化
3.派生类的oprator=必须要调用基类的operator=完成基类的复制
4.派生类的析构函数会在被调用完成后自动调用基类的析构函数来清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
5.派生类对象初始化先调用基类构造再调用派生类构造
6.派生类对象析构清理先调用派生类析构再调用基类析
继承与友元
友元关系不能继承,也就是说基类的友元不能访问子类私有和保护成员。
继承与静态成员
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
static int count;
void Print()
{
cout << _name << endl;
cout << _age << endl;
}
protected:
int _age=12;
string _name = "peter";
};
int Person::count = 0;
class student :public Person
{
protected:
int _stuid;
};
class Badstudent :public student
{
protected:
int bid;
};
int main()
{
student s1;
Badstudent s2;
cout << Person::count << endl;
student::count = 9;
cout << Person::count << endl;
}
复杂的菱形继承以及菱形虚拟继承
单继承:一个字类只有一个直接父类时称这个继承为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:是多继承的一种特殊情况
菱形继承的问题:数据冗余性和二义性问题
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
void Print()
{
cout << _age << endl;
}
int _age;
};
class student :public Person
{
protected:
int _stuid;
};
class teacher:public student
{
protected:
int _tid;
};
class man :public student, public teacher
{
protected:
int _id;
};
int main()
{
man m;
m._age;//二义性
//冗余性
m.student::_age;
m.teacher::_age;
}
虚拟继承可以解决二义性和冗余性。
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
void Print()
{
cout << _age << endl;
}
int _age;
};
class student :virtual public Person
{
protected:
int _stuid;
};
class teacher:virtual public Person
{
protected:
int _tid;
};
class man:public student, public teacher
{
protected:
int _id;
};
int main()
{
man m;
m._age;//二义性
//冗余性
m.student::_age;
m.teacher::_age;
}
虚拟继承的原理
Person同时属于student和teacher,那么student和teacher如何找到公共的Person?这里是通过student和teacher的两个指针,指向同一张表。这两个指针叫做虚基表指针,这两个表叫做虚基表。虚基表中存放着偏移量。通过偏移量可以找到Person。
总结
1.很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有了虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承,否则在复杂度和性能上都有问题。
2.多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,比如Java。
3.继承和组合
public继承是一种is-a的关系。也就是说派生类都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,则每个B对象都有个A对象。
优先使用组合。