在 C++中代码的可重用性(software reusability)是通过继承(inheritance)这一机制来实现的。如果没有掌握继承性, 就没有掌握类与对象的精华。
类的继承, 是新的类从已有类那里得到已有的特性。 或从已有类产生新类的过程就是类的派生。 原有的类称为基类或父类, 产生的新类称为派生类或子类。派生与继承, 是同一种意义两种称谓。
如果 A is a B, 则 B 是 A 的基类, A 是 B 的派生类。 为继承关系。 如果 A 包含 B,则 B 是 A 的组成部分。 为聚合关系, 可以由组成部分聚合成为一个类。 所以只有是is a 的关系,才能够继承。
默认的继承方式是 private 私有继承。
一个派生类可以同时有多个基类, 这种情况称为多重继承, 派生类只有一个基类, 称为单继承。
单继承的语法:
class 派生类名: [继承方式] 基类名
{
派生类成员声明;
};
1、继承方式
public继承时:父类中的protected是只有被继承的子类和本类的成员函数可以访问。父类中的private成员,子类也无法访问。
继承方式 | 语义 |
---|---|
公有继承 public | 基类的公有成员和保护成员在派生类中保持原有访问属性, 其私有成员仍为基类的私有成员,子类不可以访问 |
私有继承 private | 基类的公有成员和保护成员在派生类中成了私有成员, 其私有成员仍为基类的私有成员。 |
保护继承 protected | 基类的公有成员和保护成员在派生类中成了保护成员, 其私有成员仍为基类的私有成员。 |
基本上都是用public来继承,Google的C++规范明确指出只能用public,不允许用别的方式。
继承的规则:
1 全盘接收, 除了构造器与析构器。 基类有可能会造成派生类的成员冗余, 所以说基类是需设计的。
2 派生类有了自己的个性, 使派生类有了意义。
派生类中的成员, 包含两大部分, 一类是从基类继承过来的, 一类是自己增加的成员。从基类继承过过来的表现其共性, 而新增的成员体现了其个性。 在内存中是紧挨着的。
2、构造顺序
由于子类中, 包含了两部分内容, 一部分来自, 父类, 一部分来自, 子类。 父类的部分, 要由调用父类的构造器来完成, 子类的部分, 在子类的构造器中来完成始化。 子类中,有内嵌的子对象(内嵌子对象就是类中的数据成员是其他类的一个对象)也需要构造。
子类会调用父类构造器,只有在父类含有标配的情况下才会调用,否则只能在子类中显示的调用。(标配就是不需要提供参数的构造器,也就是无参构造器或参数列表都有默认参数的情况),内嵌子对象也一样。
显示调用如下:
注意:基类的构造器和内嵌子对象只能放在参数列表中,而且参数列表中的是内嵌子对象,是放的对象,不是类,而且基类和内嵌子对象顺序只能是下面这样
派生类名::派生类名(总参列表)
:基类名(参数表),内嵌子对象(参数表)
{
派生类新增成员的初始化语句; //也可出现地参数列表中
}
3、派生类的拷贝构造
因为拷贝构造也是一种构造器,构造器都没有被继承下来,所以拷贝构造器也没有被继承下来。
当子类, 不自实现时,默认调用父类的拷贝构造。
若自实现,不显示的调用父类的拷贝构造, 此时只会调用父类的构造器,此时失去了拷贝构造的意义
子类对象赋给父类的引用,赋值兼容
当内嵌子对象,子类不自实现时拷贝构造, 默认调用内嵌子对象的拷贝构造。
若自实现,不作显示的调用内嵌子对象的拷贝构造, 此时只会调用内嵌子对象的构造器, 此时失去了拷贝构造的意义
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"A()"<<endl;
}
A(const A & anthter)
{
cout<<"A(const A & anthter)"<<endl;
}
};
class C
{
public:
C()
{
cout<<"C()"<<endl;
}
C(const C & another)
{
cout<<"C(const c & another)"<<endl;
}
};
class B:public A
{
public:
B()
{
cout<<"B()"<<endl;
}
B(const B & another)
:A(another),_c(another._c) //初始化列表调用父类和内嵌子对象的拷贝构造器,要用对象名,不是类名
{ }
C _c;
};
int main()
{
B b;
cout<<"+++++++++++++++"<<endl;
B bb(b);
return 0;
}
4、派生类的赋值重载
子类中若未实现赋值重载, 调用父类赋值重载(深或浅)同时调用内嵌子对象赋值重载。
子类中 实现赋值重载, 若无显示的调用父类的赋值重载, 父类成员的保持己有构造。 此时将失去赋值的意义, 通常要显示的调用父类的赋值重载。若无显示的调用内嵌子对象赋值重载,则内嵌子对象保持己有构造。 此时将失去赋值的意义, 通常要显示的调用内嵌子对象赋值重载。
显示的调用父类的赋值重载, 需要加父类和域作用域运算符, 避免死递归。
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"A"<<endl;
}
A& operator=(const A &)
{
cout<<" A & operator=(const A &)"<<endl;
}
};
class C
{
public:
C()
{
cout<<"C()"<<endl;
}
C& operator=(const C &)
{
cout<<" C & operator=(const C &)"<<endl;
}
};
class B:public A
{
public:
B & operator=(const B &another)
{
_c = another._c;//实现内嵌子对象的赋值重载,调用子对象的=
A::operator=(another);//实现父类的赋值重载,调用子对象的=
cout<<" B & operator=(const B &)"<<endl;
}
C _c; //内嵌子对象
};
int main()
{
B b1,b2;
cout<<"++++++++++"<<endl;
b1 = b2;
return 0;
}
5、派生类的友元函数
由于友元函数并非类成员, 因引不能被继承, 在某种需求下, 可能希望派生类的友元函数能够使用基类中的友元函数。 为此可以通过强制类型转换, 将派生类的指针或是引用强转为其父类的引用或是指针, 然后使用转换后的引用或是指针来调用基类中的友元函数。
#include <iostream>
using namespace std;
class Student
{
friend ostream &operator<<(ostream & out, Student & stu);//父类友元函数
public:
Student(int i,int j)
:a(i),b(j){}
private:
int a;
int b;
};
ostream &operator<<(ostream & out, Student & stu) //全局友元函数
{
out<<stu.a<<endl;
out<<stu.b<<endl;
}
class Graduate:public Student
{
friend ostream &operator<<(ostream & out, Graduate & gra) //子类友元函数
{
cout<<(Student&)gra; //要强制类型转化为(Student&)
cout<<gra.c<<endl;
}
public:
Graduate(int i,int j,int k)
:Student(i,j),c(k){}
private:
int c;
};
int main()
{
Graduate g(1,2,3);
cout<<g<<endl;
return 0;
}
6、派生类析构函数
析构函数的执行顺序与构造函数相反。
派生类的析构函数的功能, 保证层级内与其对应的构造函数, 完成清理工作。 无需指明析构关系。
7、多继承
从继承类别上分,继承可分为单继承和多继承,多继承,即父类不止一个。
多继承,就像生活中的沙发床,它既是沙发,又是床,所以可以继承两个父类,一个是沙发类,一个是床类,就成了沙发床。
继承语法
派生类名:public 基类名 1, public 基类名 2, ...,protected 基类名 n
构造器格式
派生类名::派生类名(总参列表)
: 基类名 1(参数表 1), 基类名(参数名 2)....基类名 n(参数名 n),
内嵌子对象 1(参数表 1),内嵌子对象 2(参数表 2)...内嵌子对象 n(参数表 n)
{
派生类新增成员的初始化语句;
}
此时只要正常的多继承就可以了,但是如果两个父类中数据成员重复,比如后面写的,那么就是接下来的虚继承问题。
三角问题
多个父类中重名的成员, 继承到子类中后, 为了避免冲突, 携带了各父类的作用域信息, 子类中要访问继承下来的重名成员, 则会产生二义性, 为了避免冲突, 访问时需要提供父类的作用域信息。
比如子类继承了父类X和父类Y,子类和父类中都有_data这个数据成员,那么我们在子类Z中的访问应该是这样的
void dis()
{
cout<<X::_data<<endl;
cout<<Y::_data<<endl;
}
虚继承——四角问题
三角关系中需要解决的问题有两类:
- 数据冗余问题,
- 访问不方便的问题。
解决方案, 是三角转四角的问题。 具体操作:
- 提取公共成员构成祖父类, 即虚基类,
- 各父类虚继承虚基类
在多继承中, 保存共同基类的多份同名成员, 虽然有时是必要的, 可以在不同的数据成员中分别存放不同的数据, 但在大多数情况下, 是我们不希望出现的。因为保留多份数据成员的拷贝, 不仅占有较多的存储空间, 还增加了访问的困难。 为此, C++提供了, 虚基类和虚继承机制, 实现了在多继承中只保留一份共同成员。
否则保留了多份成员,这就是虚继承的意义
比如类D继承B和C,然后B和C继承A,如果不采用虚继承的方式,那么就是这样存储的。
采用虚继承就是这样的:
虚基类
经提取, 存有公共元素的, 被虚继承的祖父类, 称为虚基类。 虚基类, 需要设计和抽象, 虚继承, 是一种继承的扩展。
语法:
class 派生类名:virtual 继承方式 虚基类
初始化顺序
先祖父类, 父类(从左向右), 子类, 仅最后一次初始化有效。
比如:
class SofaBed:public Sofa, public Bed
这里就是先初始化虚基类(祖父类),然后初始化Sofa类,因为上边代码中Sofa写在Bed前面,最后初始化Bed类,最后一次才是最有效的。
版权属于:孟超
本文链接:https://mengchao.xyz/index.php/archives/390/
转载时须注明出处及本声明