继承
类的继承,是新的类从已有的类那里得到已有的特性,或从已有的类产生新类的过程。原有类称为基类或父类,产生的新类成为派生类或子类
关系定性:
is-a:属于关系,例如狗属于一种动物,车属于一种交通工具,在面向对象中表现为继承关系。可以设计一个Animal类,Dog类作为Animal类的子类(派生类)。
is-a关系可以完成代码复用,是继承。
has-a:包含关系,例如车包含方向盘,轮胎,发动机,但不能说方向盘是一种车。
has-a关系把一个复杂的类处理为一个个相对简单的类,是聚合。比如创建方向盘类,轮胎类,发动机类,最后创建车类。车类调用四个轮胎实例和一个方向盘类实例和一个发动机类实例。
每个类本身很简单,通过聚合组成一个复杂的类,为聚合。
派生类声明:
Class 派生类名:[继承方式] 基类名
{
派生类成员声明;
}
一个派生类可以同时有多个基类,成为多重继承,派生类只有一个基类,成为单继承。
继承方式:继承方式规定了如何访问基类成员。有:public,private,protected。
继承方式不影响派生类的访问权限,影响了从基类继承来的成员的访问权限,包括派生类内的访问权想和派生对象。
pubilc(公有继承):基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类的私有成员。
private(私有继承):基类的公有成员和保护成员在派生类中成了私有成员,其私有成员仍为基类的私有成员。
protected(保护继承):基类的公有成员和保护成员在派生类中成了保护成员,其私有成员仍为基类的私有成员。

派生类成员包含两大部分,一部分是从基类继承过来的(体现共性),一部分是自己增加的(体现个性)。
派生类构造:
派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成的,然后派生类中新增的成员在派生类的构造函数中初始化。
派生类构造函数语法:

1.构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化。
2.如果基类中没有默认构造函数(无参),那么在派生类的构造函数中必须显式调用基类构造函数,以初始化基类成员。
3.派生类构造函数执行次序:
基类->成员->子类
构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。
原则如下:
1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。
3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。
4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。
6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式。
在子类的构造函数后,加一个冒号(:),然后加上父类的带参数的构造函数。这样,在子类的构造函数被调用时,系统就会去调用父类的带参数的构造函数去构造对象。
类中普通的成员变量也可以采取此种方式进行初始化。
派生类拷贝构造:

派生类中默认拷贝构造函数自动调用基类拷贝构造函数,只要派生类中实现了,就必须显式的调用父类拷贝构造函数。
派生类赋值运算符重载:
运算符不是构造器,可以继承。

派生类友元函数:
友元函数并非类成员,不能被继承,有时候可能希望派生类的友元能够使用基类中的而悠远函数。为此可以通过强制类型转换,将派生类的指针或是引用转为其类的引用或是指针,然后使用转换后的引用或是指针来调用基类中的友元函数。
派生类析构函数:
析构顺序与构造函数相反:子类,成员,基类。
多继承:
语法:

二义性问题:
多个父类中重名,集成到子类中后,为避免冲突,要携带父类的作用域信息,访问时需带上作用域,否则会产生二义性。
虚继承:
多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承

类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径
在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突
假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B-->D 这条路径,还是来自 A-->C-->D 这条路径
为了解决多继承时的命名冲突和冗余数据问题,C++提出了虚继承
在继承方式前面加上 virtual 关键字就是虚继承
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: virtual public A{ //虚继承
protected:
int m_b;
};
//直接基类C
class C: virtual public A{ //虚继承
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //正确
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}

虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class)
本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员
虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身
浙公网安备 33010602011771号