继承
基类/父类 – base class
继承是为了完成复用。
父类并不能用子类的成员,可以说子类把父类当做自己的成员。
//派生类 继承方式 基类
class student : public Person
// 可以不写,默认的是private
// strcut默认是公用继承
1、继承定义
1.1继承基类的访问方式
//private: 在子类中不可见(完全不能用) 只能防外人
//protected: 在子类中是可见的。既能防外人,也能防儿子
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。比如说父亲的隐私,就算是他儿子,也不能去访问。protected是防外人,privateb不仅是防外人,而且防儿子并不能直接使用,但能通过基类的函数去调用(需要经过父亲的同意)。
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强
2、基类和派生对象赋值转换
- 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫 切片或者切割*。寓意把派生类中父类那部分切来赋值过去。将父类的找过来,拷贝到子类
student s;
Preson p = s; //纯天然的,并没有隐式类型转换。认为子类就是一个特殊的父类
double d = 1.1;
int i = d;//隐式类型转换
const int& ri = d;
Preson& rp = s;
Preson* ptrp = &s;
- 基类对象不能赋值给派生类对象。可以理解为基类的范围比派生类更大
3.继承中的作用域
函数重载是必须在一个作用域里面的
- 在继承体系中基类和派生类都有独立的作用域
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
- 注意在实际中在继承体系里面最好不要定义同名的成员
4.派生类的默认成员函数
假如父类没有默认构造函数就必须我们自己显示在初始化调用
- 子类要调用拷贝构造会调用父类的拷贝构造,要初始化谁的那部分就调谁的
- 子类的析构函数完成时,会自动去调用父类的析构,保证先析构子再析构父(和栈帧结构类似)
5.继承与友元
友元关系不能继承,也就是说基类友元不能被子类访问私有和保护成员
6.继承与静态成员
静态成员的继承是父类和派生类一起公享父类定义的静态成员变量,并不会各自存在一个。
如果静态成员是private的,则派生类无权访问
7.菱形继承
菱形继承的问题:菱形继承有数据冗余和二义性的问题
菱形继承(Diamond Inheritance)是一种多继承情况下常见的问题,因为它会导致同一个基类在继承链中出现多次,形成类似于菱形的继承结构,从而造成一些不易察觉的问题,如数据冗余和命名冲突。
菱形继承问题通常在以下情况下发生:一个类 A 继承自两个不同的类 B 和 C,而这两个类又同时继承自同一个基类 D,这样就会导致类 A 中包含了来自基类 D 的两份数据和方法。
解决菱形继承问题的一种常见方法是使用虚继承(virtual inheritance),它能够让公共基类在继承链中只出现一次,从而避免了数据冗余和命名冲突。在虚继承中,继承该基类的派生类需要声明虚继承关系。在 C++ 中,可以使用关键字 virtual 来声明虚继承关系,
例如
class D {
public:
int x;
};
class B : virtual public D {
public:
int y;
};
class C : virtual public D {
public:
int z;
};
class A : public B, public C {
public:
int w;
};
在上面的代码中,类 B 和类 C 均通过 virtual 继承方式继承了基类 D,而类 A 则同时继承自类 B 和类 C,避免了菱形继承问题的发生。
虚继承 — 尽量不要使用
指的是派生类类型的对象、指针、引用访问基类和派生类都有的同名函数时,隐藏规则的底层原因其实是C++的名字解析过程。在继承机制下,派生类的类域被嵌套在基类的类域中,隐藏针对的是父类,用子类去调用,必须指定类名进行调用,
virtual 虚继承的标志
class B : virtual public D
{
//如果是菱形继承,就需要指定类访问,不然会出现二义性,数据冗余
}
#define _CRT_SECURE_NO_WARNINGS 1
class D {
public:
int x;
};
class B : public D {
//class B : virtual public D {
public:
int y;
};
class C : public D{
//class C : virtual public D {
public:
int z;
};
class A : public B, public C {
public:
int w;
};
int main()
{
A a;
a.B::x = 1;
a.C::x = 2;
a.y = 3;
a.z = 4;
a.w = 5;
return 0;
}
不使用virtual
使用virtual
虚继承的B和C里放了指针,指向的地址下一个位置是一个偏移量,使原本应该存储A内容的指针偏移就可以找到D。有多少个类虚继承了D,就有多少个指针!即指向存放偏移量的指针
多个对象指向的偏移量是一样的!!!
B b = a;
B* ptrb = &a;
C* ptrc = &a;
继承跟出现的顺序无关,只与声明的先后有关;基类的肯定先定义,然后再按声明的顺序进行继承
oo语言就是面向对象的语言
组合和继承总结
Is-a:
是a:A Is B:A是B(继承关系,继承)。
has-a:
有a:A has B:A有B(从属关系,组合)。
设计:高内聚,低耦合。
//继承
//耦合度高
//B可以直接用A的三个成员;A改动保护可能会影响B
//继承可以理解成白箱复用,你可以看见它的底层实现去针对
class A
{
public:
void func1();
protected:
int _a1;
int _a2;
};
class B : public A
{
};
//组合
//耦合度低
//D可以直接用C的一个成员,间接使用其他两个成员
//C改动保护和私有成员基本不影响D
//组合类似黑箱复用,你并不能看见它的底层实现,只能推测并进行测试
class C
{
public:
void func2();
protected:
int _c1;
int _c2;
};
class D
{
private :
C _cc;
};
注意:
尽量多去用组合,组合的耦合性低,代码的可维护性高;有些关系适合继承就用继承,适合组合就用组合;如果两个都合适就最好使用组合。