如有疑问,欢迎讨论,QQ:1140004920
一、继承的概念
1.原有的类为基类,又称父类,对基类进行扩展产生的新类称为派生类,又称子类,继承可以使代码复用,并实现多态。
(1)继承的定义格式:class 派生类名:继承类型 基类名。共有三种继承类型,公有继承、保护继承、私有继承,如果不写继承类型,class默认为私有继承。struct默认为公有。最好显示写出继承方式。
(2)共有三种类成员访问限定符:公有,保护,私有,其中使用限定符保护或者私有,其成员无法在类外进行访问。
(3)定义一个简单的公有继承:
class B
{
public:
int _a;
protected:
int _b;
private:
int _c;
};
class D :public B
{
public:
int _d;
};
2.如果想用派生类去访问基类成员,是否能访问?让我们一起来看一下
![](https://img-blog.csdn.net/20171014151755374)
![](https://img-blog.csdn.net/20171014161838834)
![](https://img-blog.csdn.net/20171014161900668)
根据测试,说明三种继承中,在派生类中去访问基类成员时,基类私有成员都无法访问,公有和保护都可以访问。派生类中基类的私有成员存在,但不可见。
(1)公有继承中,基类的非私有成员在派生类中的访问属性不变。
(2)保护继承中,基类的非私有成员变为派生类的保护成员。
(3)私有继承中,基类的非私有成员(公有、私有)都变为派生类中的私有成员。也就是说在类外,无法使用派生类对基类的非私有成员进行访问。
注:三种继承,不影响基类对象对成员的访问。友元关系不能继承,因为友元函数不属于类的成员函数,也就是说基类的友元不能访问子类的私有和保护成员。
二、派生类的六个默认成员函数
![](https://img-blog.csdn.net/20171014175418680)
三、继承关系
在对派生类的对象进行初始化的时候,构造函数的调用顺序是怎样的呢?
class B
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
private:
int _a;
};
class D :private B
{
public:
D()
{
cout << "D()" << endl;
}
~D()
{
cout << "~D()" << endl;
}
private:
int _d;
};
void FunTest()
{
D d1;
}
其运行结果为
![](https://img-blog.csdn.net/20171016212637684)
(1)可以看出在构造派生类对象的时候,先调用基类的构造函数,但事实如此吗?经过进一步调试。发现在构造派生类对象的时候,先调用的是派生类的构造函数,然后到达派生类参数列表处,在调用基类的构造函数,完成对基类对象的构造,然后进行派生类对象的构造函数,执行类构造函数体。
(2)根据分析,析构函数的调用过程是倒着的,先析构派生类的,然后析构基类的。如果有派生列对象d1、d2,那么先释放d2,在释放d1。
![](https://img-blog.csdn.net/20171017172815009?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3Vvcm9uZzUyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
(3)如上图,子类和父类有同名成员,它将优先访问派生类的对象。如果想访问基类的那个同名成员,可以使用(基类::基类成员)。注意在实际继承中,最好不要定义同名成员。
同名隐藏与类型变量无关,与返回值无关。同名变量如此,同名函数也如此。
(4)在书写基类和派生类的时候也要注意一下几点问题,第一基类没有定义有参数的构造函数,子类也不用定义。第二基类定义了有参数的构造函数,派生类就一定得定义构造函数(派生类的默认构造函数没有参数,不能调用有参的基类)
![](https://img-blog.csdn.net/20171017185137774?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3Vvcm9uZzUyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
(5)如上图,基类没有缺省构造函数(带参数列表),派生类必须在初始化列表处显示给出基类名和参数列表。没有合适的默认参数可用,因为派生类刚才调用了一次基类的构造函数,这里默认的是如果基类没有缺省的构造函数,就不会默认合成基类的构造函数,所以在构造派生类的时候初始化列表加上基类的参数。
四、继承--赋值兼容规则(public继承)
![](https://img-blog.csdn.net/20171017191947581?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3Vvcm9uZzUyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20171017192829879)
(1)根据上图,说明子类的对象可以给父类对象赋值,但是父类的对象不能给子类赋值。因为父类给子类的对象赋值时,子类只能访问到b1,去访问d1的时候会发生错误
![](https://img-blog.csdn.net/20171017194201428)
(2)根据上图,说明父类的指针可以指向子类的对象,但是子类的指针不能指向父类的对象。因为子类的指针指向父类的对象时,它访问不到d1。反之父类的指针指向基类的对象时,父类只会访问自己的成员,不会访问出错。可以使用强制类型转换,使子类的指针指向父类的对象,但这样不安全,不推荐使用。
五、继承与静态成员
基类定义了一个静态成员,需要在类外进行声明,在整个继承体系中,只有这样一个成员。
![](https://img-blog.csdn.net/20171017195432973)
六、继承的三种类型
(1)单继承;一个子类只有一个父类
(2)多继承:一个子类有两个或以上的父类
(3)菱形继承:由单继承和多继承构成
![](https://img-blog.csdn.net/20171017200336055)
![](https://img-blog.csdn.net/20171017201124676)
当创建一个子类d的对象,去访问基类_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 d1;
d1._a = 1;
d1._b = 2;
d1._c = 3;
d1._d = 4;
cout << sizeof(d1) << endl;
getchar();
return 0;
}
根据运行图,发现菱形继承多了4个字节,这四个字节是什么呢?
![](https://img-blog.csdn.net/20171017204724692)
![](https://img-blog.csdn.net/20171017204752822)
![](https://img-blog.csdn.net/20171017204855136)
原来里面存放的是偏移量表格的地址,偏移量表格存放的是相对于自己的偏移量和相对于基类的偏移量,这样c和d指向同一个基类,很好地解决了二义性问题。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)