我们都知道,c++的三大特性为:封装,继承,多态。今天,我们来看看关于继承的一些问题!
is-a和has-a
is-a原则:(public继承实现)
一个子类就是一个父类,父类的所有内容都可用
注:想使用is-a,就要在设计父类的时候,检查父类的所有操作是否完全符合子类的要求。
has-a原则:(protected/private继承实现)
一个子类中有一个父类,但是部分父类的成员是不可用的。
赋值兼容规则(须为public继承):
class Person
{
public:
void Display()
{
cout<<age<<endl;
}
protected:
int age;
};
class Student:public Person
{
public:
int _num;
};
int main()
{
Person p;
Student s;
p = s;//子类对象可以赋值给父类对象
//s = p;//父类对象不可以赋值给子类对象
Person* p1=&s;
Person& r1=s;
return 0;
}
切片(切割):子类对象可以赋值给父类对象,但父类只会接收到子类继承它的部分也就是下图中红色方框中内容,其它部分无权访问。
通过上图监视窗口可知,父类的指针或引用可以指向子类对象,但是它并不能访问子类的所有成员,它只能访问到子类继承自己的成员。
子类的指针或引用不能指向父类对象,但可以通过强制类型转换完成。
class Person
{
public:
void Display()
{
cout<<age<<endl;
}
protected:
int age;
};
class Student:public Person
{
public:
int _num;
};
int main()
{
Person p;
Student s;
Student* p2=(Student*)&p;
Student& r2=(Student&)p;
p2->_num=1;
r2._num=2;
return 0;
}
强制类型转换在这里起了什么作用,让我们来一探究竟!
c++的的继承方式支持单继承和多继承:
单继承:一个子类只有一个直接父类。
class A
{
public:
int a;
};
class B:publicA //B是子类(派生类),公有继承父类(基类)
{
public:
int b;
};
class C:public B
{
public:
int c;
}
多继承:一个子类有两个或两个以上直接父类。
class D:public B,public C //子类D同时继承父类B和父类C
{
public:
int d;
};
菱形继承(钻石继承)
class A
{
public:
int a;
};
class B:public A
{
public:
int b;
};
class C:public A
{
public:
int c;
};
class D:public B,public C
{
public:
int d;
};
int main()
{
D d;
d.a = 1;
return 0;
}
菱形继承中各成员之间的关系:
实例化一个D类的对象来调取a,运行结果:
从成员关系图中可以看出来,因为我们的D类的对象中有两个a的成员变量,一个来自于B类,一个来自于A类,这个时候对a赋值的时候,编译器不知道该给哪个a赋值,由此引来菱形继承中二义性问题。
对于这个问题,我们可以通过间接访问来解决。
int main()
{
D d;
d.B::a = 1;//指定访问B类里面的成员变量a
return 0;
}
B类里面的成员变量和C类里面的成员变量是一样的,均来自于A类,但是,如果A类里面的成员变量非常多,这样间接存储会浪费空间,造成数据冗余。
为了解决菱形继承中二义性和数据冗余问题,我们引入虚继承:
class A
{
public:
int a;
};
class B:virtual public A
{
public:
int b;
};
class C:virtual public A
{
public:
int c;
};
class D:public B,public C
{
public:
int d;
};
int main()
{
D d;
d.a = 1;
d.b = 2;
d.c = 3;
d.d = 4;
return 0;
}
通过监视内存1可知,变量b和变量c上面存放一个类似于一个地址一样的数据,另外,变量a的值在变量d的下面。
通过监视内存2,对应地址存放的是虚基表指针,它指向的是一个虚基表。
变量b上面的地址存放的十六进制数是14的数,转化成十进制就是20,比较一下监视窗口1中,那个地址和a实际存放的位置的距离是20,这个时候我们就能够理解了,原来这里放置的是a这个变量的偏移量。
在这里,有人可能会提出疑惑:
(1)为什么在这里不直接存放a的地址,而是放置了一个相对位置的偏移量?因为我们如果要拿这个类去实例化多个对象,每个地址都存放类似的地址,会增加内存和地址的开销。
(2)还有一个问题是为什么内存1中的地址00eb580c对应于内存2中的位置是0而不是偏移量20?这是因为,这个0所在的位置是给接下来的虚函数准备的。
虚继承:
1,虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余和浪费空间的问题。
2,正如上述过程,虚继承体系看起来好复杂,在实际应用中我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系,因为使用虚继承解决数据冗余问题也带来了性能上的损耗。3,避免菱形继承,最好不要设计多继承。