1、继承是指函数之间相同性很高时,可以采取从基类(父类)继承相同的部分,形成派生类(子类)。
如图:
可以将三者相同部分写成一个函数然后分别继承
2、继承方式
三种:public,private,protected
3、赋值转换——切片
子类对象可以赋值给 父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切来赋值过去。并且这个过程中没有类型转换。没有类型转换也意味着不产生临时变量!,临时变量具有常性,所以不需要加上const。
下面,我们来看一看代码是怎么体现赋值过程没有类型转换的:
class Person
{
protected:
string _name;
string _sex;
public:
int _age;
};
class Student :public Person
{
public:
int _No;
};
int main()
{
Person p;
Student s;
//中间不存在类型转换
p = s;
//引用赋值
Person& rp = s;
rp._age = 1;
//指针赋值
Person* ptr = &s;
ptr->_age = 2;
return 0;
}
虽然是不同类型的,但是不会发生类型转换,而类型转换在中间会产生临时变量,而临时变量具有常性,必须加const,而对于父类和子类的赋值可以不加const!
赋值转换也称为向上转换,子类可以给父类的对象,指针、引用。而向下装换是不行的:父类对象不能赋值给子类对象,子类有的成员而父类没有,缺少的部分怎么去给
这里先简单了解一下:父类的指针可以通过强制类型转换赋值给子类的指针。但是必须是父类的指针是指向子类对象时才是安全的。这里父类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。
void Test()
{
Student sobj;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2。父类对象不能赋值给子类对象
//sobj = pobj;
// 3.父类的指针可以通过强制类型转换赋值给子类的指针
pp = &sobj;
Student * ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_No = 10;
}
4、继承的作用域——隐藏
在继承体系中基类和派生类都有独立的作用域。而如果子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员显示访问)
需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
注意在实际中在继承体系里面最好不要定义同名的成员。
class Person
{
protected:
string _name = "HWC";
int _num = 10086;
};
class Student : public Person
{
public:
void Print()
{
cout << " 姓名:" << _name << endl;
cout << " 身份证号:" << Person::_num << endl;
cout << " 学号:" << _num << endl;
}
protected:
int _num = 666;
};
int main()
{
Student s1;
s1.Print();
return 0;
}
_num默认访问就近原则,访问自己的,如果想访问父类的成员,就直接指定作用域Person::_num。
5、总结:
1、基类private成员在派生类中无论以什么方式继承均不可见(在类外与类内均不能直接访问);
2、基类的私有成员在子类均不可见,基类的其他成员在子类的访问方式取权限小的;public>protected>private
3、为使私有成员可以在类内访问,定义为protected(子类可直接手机用)
4、基类与派生类具有赋值兼容转换(特殊的语法规则,不是类型转换)
5、子父类同名函数关系为隐藏
6、子类默认生成构造
父类成员----->默认构造
子类成员(内置)----->默认构造
子类成员(自定义)----->默认构造
子类中的父类成员的构造,拷贝,析构均需要调用父类中的函数
7、友元关系无法继承
eg:你父亲的朋友不一定是你的朋友
8、如果父类中有静态成员,在继承中子类访问该成员时访问的是统一对象
先在类中定义静态变量,然后在外部声明 并进行初始化 , 该步骤的作用是 告诉编译器 , 为 静态变量 分配内存 , 否则不分配内存 编译会报错 ;声明并初始化静态变量,如果没有该操作 , 静态成员不可用之后 , 才能使用 类的 静态成员 , 如果没有在类外部初始化 , 该静态成员是无法使用的 ;
9、单继承与多继承(两个及以上的直接父类继承)
注:多继承看似合理但实际上是个大坑
如:蔬菜和水果同属于植物,西红柿既是蔬菜也是水果。这样西红柿就有了两遍的植物信息,造成数据冗余与二义性
例如助教既是学生也是老师但其身份证号只有一个,多继承使其储存两遍。
为解决这个问题可以用虚继承(菱形继承):
virtual必须在第一时间加
10、继承(b继承a)与组合(b含有a)
https://blog.csdn.net/weixin_60478154/article/details/128859481