【C++】——继承方式
一、 继承的概念相关:
1、概念
在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生的新类称派生类。
2、格式:
3、作用:
1、)代码复用
2、)实现多态
二、代码分析继承
1、一段简单的代码展示继承
class Base
{
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived:public Base
{
private:
int _priD;
protected:
int _proD;
public:
int _pubD;
};
int main()
{
cout << sizeof(Derived) << endl;//24
return 0;
}
有没有继承下来,看派生类的大小即可
大小为24,即继承下来了,
(1)派生类继承下来基类的公有成员变量也是公有的;
(2)基类的私有成员在基类里可以访问,但在派生类里无法直接访问,是基类里的隐私
(3)将基类里保护的成员继承下来,要么是私有的,要么是保护的——问题?哪一个呢?下面我们就一起研究一下
2、继承的方式
猜测一下上面的继承问题
猜测:保护(基类)———private, protected(派生类)
假设:protected—派生一层
class Base
{
public:
void SetValue(int pri, int pro, int pub)
{
_pri = pri;
_pro = pro;
_pub = pub;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived:public Base
{
public:
void SetValueD(int pri, int pro, int pub)
{
}
private:
int _priD;
protected:
int _proD;
public:
int _pubD;
};
//验证公有的继承方式
class D:public Derived
{
public:
void FunTest()
{
_pro = 5;
_proD = 10;
}
};
int main()
{
cout << sizeof(Derived) << endl;
Derived d;
d.SetValue(10, 20, 30);
return 0;
}
(1)——验证保护的继承方式
class Base
{
public:
void SetValue(int pri, int pro, int pub)
{
_pri = pri;
_pro = pro;
_pub = pub;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived :protected Base
{
public:
void SetValueD(int pri, int pro, int pub)
{
SetValue(pri, pro, pub);//此函数继承下来是保护的(私有的调用不了)
}
private:
int _priD;
protected:
int _proD;
public:
int _pubD;
};
int main()
{
cout << sizeof(Derived) << endl;
Derived d;
d.SetValue(10, 20, 30);
return 0;
}
此函数在基类权限是公有的的,但通过保护继承,在类外不允许调用,说明派生类继承下来时该成员函数的权限发生了改变(要么是私有的,要么是保护的)。
下面来验证一下
验证方法1、
验证方法2、再派生一层,再最底层的派生类中调用
验证结果为基类公有的成员函数、成员变量,以保护的继承方式继承下来,派生类中变为保护的(基类中是保护的继承下来也是保护的)
(2)——私有的继承方式
基类中公有的成员被私有的继承方式继承下来,在派生类中变成了私有的
——验证方法:再派生一层,在派生类中已经变为私有的,在最底层派生类中不可访问
class Base
{
public:
void SetValue(int pri, int pro, int pub)
{
_pri = pri;
_pro = pro;
_pub = pub;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived :private Base
{
public:
void SetValueD(int pri, int pro, int pub)
{
}
private:
int _priD;
protected:
int _proD;
public:
int _pubD;
};
//验证私有的继承方式
class D :public Derived//再派生一层,在派生类中已经变为私有的,在最底层派生类中不可访问
{
public:
void FunTest()
{
_pub = 10;
}
};
综合以上的代码分析总结继承权限和访问限定符如下
(注意:不可见即不可访问)
总的来说一句话:public —> protected ——> private
如果超过了本身,则继承下来的降一级,否则保持不变。
总结:
(1)基类private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义protected,可以看出保护成员限定符是因继承才出现的
(2)public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象
(3)protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下
不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承意味着is-implemented-in-terms-of(是根据……实现的)。通常比组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的
(4)不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)
(5)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
(6)在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承
三、派生类对象的构造和析构
1、继承体系下派生类和基类构造函数的调用次序
创建哪个类的对象就调用哪个类的构造函数
——创建派生类对象
调用派生类构造函数——>在派生类构造函数初始化列表构造基类部分B()——>构造派生类自己特有的成员——>执行派生类构造函数的函数体
2、继承体系下派生类和基类析构函数的调用次序
——销毁派生类对象
调用派生类的析构函数——>销毁派生类自己管理的资源——>调用基类的析构函数——>销毁基类管理的资源
【说明】
(1)基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表
(2)基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数
(3)基类定义了带有形参表构造函数,派生类就一定定义构造函数
四、继承体系中的作用域以及赋值兼容规则
1、作用域
(1)在继承体系中基类和派生类是两个不同作用域
(2)子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问(在子类成员函数中,可以使用 基类::基类成员 访问)–隐藏 –重定义
(3)注意在实际中在继承体系里面最好不要定义同名的成员
——同名隐藏(类型无关)
(a)成员变量同名:
class B
{
public:
int _b;
};
class D :public B
{
public:
char _b;
};
int main()
{
D d;
d._b = 1000000;
return 0;
}
如果不指定作用域,会赋值给派生类自己的成员
最好在基类和派生类中不要出现相同的成员名,但也是有办法的
是不是就达到了给基类成员赋值的效果了呢
那么为什么没加作用域,会默认赋值给派生类的成员呢?(当然会优先访问自己的咯)
(b)成员函数同名
优先调用派生类的成员函数
class B
{
public:
void Test()
{}
public:
int _b;
};
class D :public B
{
public:
void Test(int a)
{}
public:
char _d;
};
int main()
{
D d;
d.Test(10);
return 0;
}
指明作用域之后呢,和预想结果是一样的
——函数重载和同名隐藏
(1)函数重载:同一作用域、相同函数名、参数列表不同(个数、类型、次序),和返回值是否相同没有关系。
(2)同名隐藏:在继承体系中,基类和派生类 有相同名称的成员(成员变量和函数,和类型没有关系),如果使用派生类对象调用同名称的类成员,优先调用派生类自己。
2、赋值兼容规则
在public继承权限下,子类和派生类对象之间有:
(1)子类对象可以赋值给父类对象(切割/切片)
(2)父类对象不能赋值给子类对象
(3)父类的指针/引用可以指向子类对象
(4)子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
3、友元与继承
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
静态成员可以继承
class B
{
friend void FunTest();//友元——能在函数内访问B类的私有成员
public:
int _b;
private:
int _b1;
};
class D :public B
{
public:
int _d;
private:
int _d1;
};
void FunTest()
{
B b;
b._b1 = 10;
D d;
d._d1 = 20;
}
四、继承体系下的对象模型
对象模型:对象中非静态成员变量在内存中的布局形式
1、 单继承:一个类只有一个基类(前面的就是单继承的例子,)
class B
{
public:
int _b;
};
class D: public B
{
public:
Int _d;
};
对象模型:
D:(基类中成员在上+派生类自己特有的成员在下)
_b—>B
_d—>派生类D自己的成员
2、 多继承:一个类有多个基类(先继承哪个类,哪个就在前面)
class B1
{
public:
int _b1;
};
class B2
{
public:
int _b2;
};
class D: public B1, public B2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
d._b1 = 0;
d._b2 = 1;
d._d = 2;
return 0;
}
文字描述对象模型:
D:
_b1—>B1
_b2—>B2
_d—>派生类D自己特有的
3、 菱形继承:单继承+多继承(二义性问题)
class B
{
public:
int _b;
};
class C1:public B
{
public:
int _c1;
};
class C2 :public B
{
public:
int _c2;
};
class D :public C1, public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;//20
D d;
d.C1::_b = 0;
d._c1 = 1;
d._c2 = 2;
d.C2::_b = 3;
d._d = 4;
return 0;
}
文字描述对象模型:
D:C1(B + c1)+ C2(B + c2)+ _d
C1:_b—>B
_c1—>C1自己特有的
C2:_b—>B
_c2—>C2自己特有的
_d—>派生类D自己的
菱形继承对象模型
4、 虚拟继承:(多了4字节)
class B
{
public:
int _b;
};
class D : virtual public B
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;//12
D d;
d._b = 1;
d._d = 2;
return 0;
}
派生类D的大小——》4+B+_d =====》12
派生类D的模型:
和普通继承的不同:基类的部分到后面去了
派生类虚拟继承时,编译器给它合成了一个构造函数
多了4字节—实际上是指向了偏移量表格的地址,偏移量表格里面有两部分内容:对象相对于自己的偏移量0+基类成员相对于对象起始位置的偏移量8
5、 菱形虚拟继承:
//即用虚拟继承解决菱形继承中存在的二义性
class B
{
public:
int _b;
};
class C1 : virtual public B
{
public:
int c1;
};
class C2 :virtual public B
{
public:
int c2;
};
class D : public C1,public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;//24
D d;
d._b = 1;
d.c1 = 2;
d.c2 = 3;
d._d = 4;
return 0;
}
派生类的对象模型:
关于多态的部分在后续会发出来