初步学习C++中的继承关系

 继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
#include<iostream>
using namespace std;
class Base
{
public:
	Base()
    {}
	~Base()
	{}
private:
	int _pri;
protected:
	int _pro;
public:
	int _pub;
};
class Derive : public Base
{
public:
	Derive()
	{}
	~Derive()
	{}
private:
	int _dPri;
protected:
	int _dPro;
public:
	int _dPub;
};
int main()
{
	cout << sizeof(Base) << endl;//12=4+4+4
	cout << sizeof(Derive) << endl;//24,派生类的大小包含了基类的大小和自己的大小
	return 0;
}

继承关系和访问限定符:

class Base
{
public:
	Base()
	{}
	~Base()
	{}
private:
	int _pri;
protected:
	int _pro;
public:
	int _pub;
};
class Derive :public Base
{
public:
	Derive()
	{}
	~Derive()
	{}
	void Display()
	{
		cout << "_pri=" << _pri << endl;//error基类的私有成员在派生类中不能被访问
		cout << "_pri=" << _pro << endl;
		cout << "_pri=" << _pub << endl;
		cout << "_dpri=" << _dpri << endl;
		cout << "_dpri=" << _dpri << endl;
		cout << "_dkpri=" << _dpri << endl;
	}
private:
	int _dpri;
protected:
	int _dpro;
public:
	int _dpub;
};
int main()
{
	Derive d;
	d._pri = 10;//error
	d._pro = 20;//error

	d._pub = 30;
	d._dpri = 20;//error
	d._dpro = 20;//error
	d._dpub = 20;
}
总结:
1.基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
2.public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
3.protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
4.不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
5.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
6.在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.

在派生类中如果没有显示定义类的六个成员函数,编译器系统则会默认合成这六个默认的成员函数

class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
	~Base()
	{
		cout << "~Base()" << endl;
	}
private:
	int _pri;
protected:
	int _pro;
public:
	int _pub;
};
class Derive :public Base
{
public:
	Derive()
	{
		cout << "Derive()" << endl;
	}
	~Derive()
	{
		cout << "~Derive()" << endl;
	}
private:
	int _dpri;
protected:
	int _dpro;
public:
	int _dpub;
};
int main()
{
	{
		Derive d;
	}
	getchar();
	return 0;
}

继承关系中构造函数调用的顺序:基类构造函数 =》派生类中对象构造函数 =》派生类构造函数体

继承关系中析构函数调用的顺序:派生类析构函数 =》派生类包含成员对象析构函数 =》基类析构函数

说明:  1、基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
        2、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
        3、基类定义了带有形参表构造函数,派生类就一定定义构造函数。

默认构造函数:指调用时无需提供参数的构造函数,因此:它可以是没有参数的,也可以是所有参数都有默认值的,两者取其一,否则有二义性。

编译器会合成默认(缺省)构造函数:1.当类中有类类型的成员,且类成员有默认的构造函数;

                                2.当基类有默认构造函数时,派生类没有构造函数;

                                3.当类中有虚函数或类派生链中有虚函数时;

                                4.当类采用虚继承方式时。


调用拷贝构造函数:1.用一个对象初始化另一个对象时,且是同类对象;(对象构建对象)

                  2.当函数的参数是对象,通过对象调用函数时,参数的拷贝会调用拷贝构造函数;(值传参)

                  3.当函数的返回值是对象,调用函数时,返回一个对象会调用拷贝构造函数。(值返回值)

编译器会合成拷贝构造函数1.当类中有类类型的成员,且类成员有拷贝构造函数;

                          2.当基类有拷贝构造函数时,派生类没有拷贝构造函数;

                          3.当类中有虚函数时;

                          4.当类采用虚继承方式时。


继承体系中的作用域:

1.在继承体系中基类和派生类是两个不同作用域。
2.子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用 基类::基类成员 访问)--隐藏 --重定义
3.注意在实际中在继承体系里面最好不要定义同名的成员。

继承与转换--赋值兼容规则--public继承:1.子类对象可以赋值给父类对象(切割/切片)
                                     2.父类对象不能赋值给子类对象
                                     3.父类的指针/引用可以指向子类对象
                                     4.子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)

class Base
{
public:
	Base(int data)
	{}
	~Base()
	{}
private:
	int _pri;
protected:
	int _pro;
public:
	int _pub;

	static int count;
};

int Base::count = 0;
class Derive : public Base
{
public:
	Derive()
		: Base(20)
	{}

	~Derive()
	{}
private:
	int _dPri;
protected:
	int _dPro;
public:
	int _dPub;
	int _pri;
};
int main()
{
Derive d;
Base b(0);
b = d;
d = b;//error,父类对象不能赋值给子类
Base* pBase = &d;
Derive *pD = (Derive*)&b;//通过强制类型转换可以赋值
return 0;
}


友元与继承:友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
继承与静态成员:基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。

单继承:一个子类只有一个直接父类。

多继承:一个子类至少有两个父类。

菱形继承:会产生二义性和数据冗余问题。

class Person
{
public:
	string _name;   // 姓名
};
class Student : public Person
{
protected:
	int _num;   //学号
};
class Teacher : public Person
{
protected:
	int _id;     // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse;     // 主修课程
};
void Test()
{
	// 显示指定访问哪个父类的成员
	Assistant a;
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}
为了解决菱形继承的二义性和数据冗余问题,使用虚继承
#include<iostream>
using namespace std;
class Base
{
public:
        virtual void FunTest1()  //虚函数
	{
		cout << "Bsae::FunTest1()" << endl;
	}
	virtual void FunTest2()  //虚函数
	{
		cout << "Base::FunTest2()" << endl;
	}
	int _data1=0x01;
};
class Derive :virtual public Base  //虚继承
{
public:
	virtual void FunTest3()  //虚函数
	{
		cout << "Derive::FunTest3()" << endl;
	}
	virtual void FunTest4()  //虚函数
	{
		cout << "Derive::FunTest4()" << endl;
	}
	int _data2=0x02;
};
int main()
{
	Base b1;
	Derive d1;
	return 0;
}
<img src="https://img-blog.csdn.net/20160413123747132?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
对象d1中存储了两个虚指针:分别是指向base的虚指针_vfptr和指向Derive的虚指针_vfptr。
<img src="https://img-blog.csdn.net/20160413123806697?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
0x00cfddf8表示的是Derive表自己的地址
0x00cfdbb0表示的是派生类独立成员相对于Base类成员偏移的地址
0x00cfdca8表示Derive中属于Base类的那部分的虚表地址
</pre><pre class="cpp" name="code" code_snippet_id="1640343" snippet_file_name="blog_20160413_18_7825472">在base的虚指针中存放的是base中虚表的地址,通过虚指针可以找到虚表的地址,即虚表中存放的是成员函数的调用地址:
<img src="https://img-blog.csdn.net/20160413115724037?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
</pre><pre class="cpp" name="code" code_snippet_id="1640343" snippet_file_name="blog_20160413_21_4368718">同样的在Derive中的虚指针中存放了Derive的虚表地址:
<img src="https://img-blog.csdn.net/20160413120037523?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
</pre><pre class="cpp" name="code" code_snippet_id="1640343" snippet_file_name="blog_20160413_24_9275761">总结:
<span style="color:#ff0000;">虚拟继承下,派生类对象组成:</span>
<span style="color:#ff0000;">1.派生类自己的虚表指针+派生类第一个成员相对于基类成员的偏移+派生类自己的数据部分</span>
<span style="color:#ff0000;">2.基类的虚表指针+基类的数据部分</span>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值