概念
复用代码的一种方式,子类可以根据不同的继承方式复用父类的成员,可以在复用的基础上进行扩展。
class Person
{
public:
Person(int age=18,string name="张三")
:_age(age)
,_name(name)
{}
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
int _age;
string _name;
};
class stu:public Person
{
protected:
int id=111111;
};
int main()
{
stu a;
a.print();
return 0;
}
对于继承方式比较重要的几个点
1.父类的private成员子类无论什么方式继承在子类中都是不可见的
2.其他的权限基本上是在成员和继承方式中取权限较小的那个
对象赋值转换
子类的成员总是不少于父类的,所以我们可以使用子类来对一个父类对象进行赋值、初始化,父类的指针和引用也可以指向子类。
反过来就不行,因为父类的成员没有子类多。
这种方式叫做切片,但是没有真的切
stu s;
stu* sp;
Person* pp = &s;//指针
Person& p = s;//引用
Person p1 = s;//对象
sp = (stu*)pp;//父类的指针可以通过强转赋值给子类的指针,有可能越界
关于继承中的作用域
父子类的作用域是相互独立的,如果父子类中有同名的成员,这个情况构成隐藏,也叫重定义,默认只会访问子类的成员,也可以通过指定父类作用域的方式访问父类的同名成员。
如果是函数重定义只要函数名相同就构成重定义。
子类的默认成员函数
一、构造函数
如果父类的构造函数被我们定义了,那在子类的构造函数中必须显式调用父类的构造函数,子类无法初始化父类的成员变量,但是可以改变父类成员变量的值。
二、拷贝构造和重载=
子类这两个函数都是要完成父类的拷贝构造和赋值
三、析构函数
子类的析构函数必须先执行析构子类的成员,再执行父类的析构函数,如果反过来的话有一些子类在执行的时候要使用父类的一些数据,但是这个时候父类的成员已经被释放了,会导致问题,编译器会强制最后执行父类的析构函数
继承中的友元
友元是无法继承的,父类的友元函数无法访问子类的保护和私有,没了
难点 菱形继承和菱形虚拟继承
如果我们代码中遇到这种情况,就会发生一些糟糕的事情
当b类和c类都继承了a类中的成员,
而号d类又同时继承了b类和c类,
这个时候d类中就会有两份a中的成员,
一份是b类继承的一份是c类继承的,
这个时候通过d类成员访问a类中的成员就会有二义性,
你要的是b里的还是c里的a
同时也会造成数据冗余
访问也可也正常访问,我们只需呀指定一下类域就行了
class a
{
public:
int _a = 1;
};
class b: public a
{
public:
int _b = 2;
};
class c: public a
{
public:
int _c = 3;
};
class d:public b,public c
{
public:
int _d = 4;
};
void main()
{
d dd;
cout << dd.b::_a << endl;
}
这种方法显然不怎么实用,c++也提供了另一种解决方案
在b和c继承方法的前面加上关键字 virtual
class a
{
public:
int _a = 1;
};
class b:virtual public a
{
public:
int _b = 2;
};
class c:virtual public a
{
public:
int _c = 3;
};
class d:public b,public c
{
public:
int _d = 4;
};
void main()
{
d dd;
cout << dd._a << endl;
}
添加virtual关键字之后这种方式叫做虚继承
虚继承之后d类对象中就只有一份a类对象中的成员。
我们来简单瞅一眼菱形虚拟继承后的d对象存储布局就能知道他是怎么做到的
这个玩意很复杂,不要去写菱形继承