1.基本概念
子类可以把父类的东西当作自己的东西来用(个人理解)
![](https://img-blog.csdnimg.cn/direct/4da60ae8fdaa4b64bd1a21285aed5f28.png)
#include<iostream>
using namespace std;
class Person//基类
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
class Student : public Person//子类
{
protected:
int _stuid; // 学号
};
class Teacher : public Person//子类
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
上述代码基类Person有姓名,年龄等属性, 这是派生类学生和老师共有的性质,所以可以统一写到基类当中,派生类老师和学生分别有自己的工号和学号,但也可以使用基类的姓名和年龄等属性。
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。
2.继承的方式
使用private继承,父类的protected和public属性在子类中变为private
使用protected继承,父类的protected和public属性在子类中变为protected
使用public继承,父类中的protected和public属性不发生改变
基类的private ,子类咋的死活都不能通
protected子类可以访问,但是除了在父类和子类的外面,不可访问
public随便访问
3.构造-析构执行顺序
class CSTepFather {
public:
int m_a;
CSTepFather() {
m_a = 10;
}
CSTepFather(int a) {
m_a = a;
}
};
class CSon {
public:
CSTepFather m_stepFa; //包含另一个类的对象
int m_son;
CSon()/*:m_stepFa()*/ { // 当包含另一个类对象,编译器会自动调用无参的构造进行初始化
m_son = 20;
}
CSon(int a):m_stepFa(a) { // 如果像调用带参数的构造函数进行初始化,需要手动显式指定
m_son = 20;
}
};
当一个类包含另一个类的对象时编译器会自动调用无参的构造函数
类比继承 派生类编译器默认自动调用继承的父类的无参构造
4.隐藏
函数重载条件:
- 同一作用域
- 函数名相同,函数参数不同
而隐藏是不同作用域下的(一个作用域是基类,另一个是子类)
在继承体系中基类和派生类都有独立的作用域。而如果子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员显示访问)
5.赋值转换
子类对象可以赋值给 父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切来赋值过去。并且这个过程中没有类型转换。
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;
}
6.设计一个无法被继承的类
方式一:将基类的构造函数设置成private权限
#include<iostream>
using namespace std;
class A
{
private:
A()
{
cout << "A()" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B()//无法继承了,会报错
{
cout << "B()" << endl;
}
void func()
{
cout << _b << endl;
cout << _a << endl;
}
protected:
int _b;
};
int main()
{
B b;
b.func();
return 0;
}
我们已经知道基类中的private成员在派生类中是不可见(不可访问)
上述代码中,B继承了A,那么在B的构造函数中是会去自动调用A的构造函数的,而A的构造函数时不可见(不可被B访问),所以是会报错的。这是一个很巧的办法。
方式2:加上关键词final
当我们在基类设计时,在后面加上final关键字,那么此类就无法被继承。
#include<iostream>
using namespace std;
class A final
{
public:
A()
{
cout << "A()" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B()
{
cout << "B()" << endl;
}
void func()
{
cout << _b << endl;
cout << _a << endl;
}
protected:
int _b;
};
int main()
{
B b;
b.func();
return 0;
}
7.菱形继承
菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承。
菱形继承问题:
-
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
-
草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
class Animal
{
public:
int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;
st.Tuo::m_Age = 200;
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
动物类为基类,羊继承了动物类,驼也继承了动物类,羊驼类继承了羊类和驼类,那么羊驼则继承了两份动物,也就是两份m_Age.
这样就形成了菱形继承,也就会产生数据冗余–m_Age有两份,二义性––m_Age同名,访问不明确
若是硬要访问需要加上类名作用域用来区分,到底访问的是哪个age:
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
解决办法是虚继承,加上virtual:
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
这样一来两份age就变成了唯一的一份,数据共享,最终age的值以最后一次修改的值为标准
底层原理:
原来羊和驼类继承了两份age,virtual之后,改为继承了vbptr指针,指针指向的就是虚基表,虚基表里面有虚基类的偏移量最终指向同一个age