继承与派生目录
一.概念
1.基类和派生类
派生类,又称子类,由一个类继承到另一个类上所得,其除了拥有自身的对象,还拥有从父类那所继承的对象,可以通过一定的方式访问父类的成员对象;基类,又称父类,用于继承到子类,使子类拥有它本身和基类的对象。
简单地理解,就是爸爸和儿子的关系,爸爸拥有的特征——比如身高,肥胖程度,性格等(基类),都会继承到儿子身上(举个例子),但儿子除了拥有父亲的特征之外,还拥有自己的特征,这些便是派生类。
2.继承访问
①继承访问方式
我们来看这段代码,将a设置为基类,b设置为派生类,基类public、protected、private中各有一个打印函数,继承的b中则有一个调用基类打印函数的函数。我们将演示三种继承方式时的派生类类中和类外的访问权限。(不写继承方式默认为private)
class a
{
public:
void public_Print()
{
cout << "a::public_Print()" << endl;
}
protected:
void protected_Print()
{
cout << "a::protected_Print()" << endl;
}
private:
void private_Print()
{
cout << "a::private_Print()" << endl;
}
};
// 继承方式
class b : public a
// class b : protected a
// class b : private a
{
public:
// 派生类类内访问权限测试
void b_public_Print()
{
public_Print();
protected_Print();
private_Print();
}
};
int main()
{
b B;
// 派生类类外访问权限测试
B.public_Print();
B.protected_Print();
B.private_Print();
B.b_public_Print();
return 0;
}
public继承
使用public继承时,类内中可以访问基类的public、protected成员,类外仅可以访问public成员,如图:
protected继承
使用protected继承时,类内中可以访问基类的public、protected成员,此时继承基类的public对象存在于子类的protected中,类外成员都受到保护而不可访问。
private继承
使用private继承时,类内中可以访问基类的public、protected成员,此时继承基类的public和protected对象存在于子类的private中,类外成员都受到保护而不可访问。图示结果与上图一致。
基类所有的private对象不管在子类的类内还是类外都被隐藏保护了起来,不可访问。
②继承的作用域
在继承体系中,基类和派生类都有独立的作用域。
关于重定义
如果在基类和派生类中都有相同函数名的函数,那么他们会构成重定义,也叫做隐藏,即子类调用该函数会屏蔽对父类同名函数的直接访问,可以通过"基类::基类成员"的方式访问。
③派生类构造函数和析构函数的调用
在继承中,派生类是必须要调用基类的构造函数的,如果没有调用基类构造函数的话会直接报错(没写的话系统会自动生成默认构造函数)。
而关于调用顺序:调用构造函数时,会先调用基类的构造函数,然后再调用派生类的;而调用析构函数时,则恰恰相反。(系统默认派生类调用完析构函数后再自动调用基类的)
3.继承类型
继承中,大部分的数据类型都会被派生类继承,但不包括这一种数据:友元函数,基类的友元函数不会被继承,打个比方,好比你父亲的朋友不一定是你的朋友。还有一个特殊情况:static修饰的静态成员,虽然它可以被继承,但它属于整个类,在该继承体系中始终只存在一个。
二.定义
1.赋值兼容转换(切割、切片)
基类可以接受派生类的对象,并通过切片达到给基类赋值的目的。我们可以运行以下代码:
class Person
{
public:
Person(const string& name, const int& age)
:_name(name)
, _age(age)
{}
private:
string _name;
int _age;
};
class Student : public Person
{
public:
Student(const string& name, const int& age, const int& num)
:Person(name,age)
,_num(num)
{}
private:
int _num;
};
int main()
{
Student A("张三", 18, 20230724);
// 切片赋值给父类
Person B = A;
return 0;
}
通过监视窗口可以得到以下情况:
2.单继承
单继承的概念:一个子类只有一个直接父类时成为单继承。
3.多继承
多继承的概念:一个子类有两个及以上父类时称这个继承关系为多继承。
该继承体系中对基类的初始化按照从左到右的顺序进行,析构时则相反。
4.菱形继承
菱形继承的概念:是多继承的一种特殊情况,派生类继承多个基类时,这几个基类也作为派生类继承了同一个或多个相同的基类。
这种情况看似简答,其实比较复杂,会引发很多问题:每个基类对象又都包含了同一个基类对象——数据冗余;有两个相同的基类对象——数据的二义性。
举例:我们将运行以下代码。
class a
{
public:
a(const int& num)
:_a(num)
{}
private:
int _a;
};
class b : public a
{
public:
b(const int& num)
: _b(num)
, a(num + 10)
{}
private:
int _b;
};
class c : public a
{
public:
c(const int& num)
: _c(num)
, a(num + 10)
{}
private:
int _c;
};
class d : public b, public c
{
public:
d(const int& num1, const int& num2, const int& num3)
:b(num1)
,c(num2)
,_d(num3)
{}
private:
int _d;
};
int main()
{
d D(1, 2, 3);
return 0;
}
我们观察运行后的内存情况,可以得出以下图解,二义性和数据冗余可以直观地表达出来。
菱形虚拟继承
在菱形继承中作为中间节点的子类前加上virtual,可以做到菱形虚拟继承。
class a
{
public:
a(const int& num)
:_a(num)
{}
private:
int _a;
};
class b : virtual public a
{
public:
b(const int& num)
: _b(num)
, a(num + 10)
{}
private:
int _b;
};
class c : virtual public a
{
public:
c(const int& num)
: _c(num)
, a(num + 10)
{}
private:
int _c;
};
class d : public b, public c
{
public:
d(const int& num1, const int& num2, const int& num3, const int& num4)
:a(num1)
,b(num2)
,c(num3)
,_d(num4)
{}
private:
int _d;
};
int main()
{
d D(1, 2, 3, 4);
return 0;
}
用此方法得到将不再创建重复的_a,而是创建一个虚基表指针,虚基表里面存储着偏移量,系统通过虚基表里的偏移量找到_a所存储的地方,此处虚基表的大小为8个字节,因为不一定只会有一个需要用偏移量标记的数据。