C++中的继承

目录

继承的概念

继承方式

基类与派生类对象赋值转换

继承中的作用域

派生类的默认成员函数

基类的静态成员 

菱形继承与虚拟继承

继承与组合


继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

例如学校管理系统,学生、老师具有相同的属性,例如姓名,性别,年龄等属性。各自也具有不同的属性,例如学号、工号。

class Person {
public:
	void Print()
	{
		cout << "name: " << _name << endl;
		cout << "_age: " << _age << endl;
	}
protected:
    //共同属性,基类
	string _name = "Fancy";
	int _age = 18;
};

class Student :public Person {//继承

protected:
    //独特的属性,派生类
	string _stuid = "B06040217";
};
class Teacher :public Person {

protected:
	string _jobid = "";
};

int main()
{
	Student s;
	return 0;
}

继承方式

三种继承方式:

public继承、protected继承、private继承。

总结一下:

protected限定符是为了继承才产生的,当不在继承环境下使用时,效果和private一样,都是防止外部访问。

访问权限public > protected > private,基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式)(private成员除外)

基类的private成员,不管是哪种继承方式,在派生类中都不可见,即派生类无法访问这些成员。

基类与派生类对象赋值转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。

派生类赋值给基类时,只赋值基类中拥有的成员。

注意:

基类对象不能赋值给派生类对象。

但是,基类的指针可以通过强制类型转换赋值给派生类的指针。

 

继承中的作用域

class Person {
public:
	void Print()
	{
		cout << "name: " << _name << endl;
		cout << "_age: " << _age << endl;
	}

protected:
	string _name = "Fancy";
	int _age = 18;
};

class Student :public Person {
public:
	void test()
	{
		_age = 10;
	}
protected:
	string _stuid = "B06040217";
};
int main()
{
	Student s;
	s.Print();
	s.test();
	s.Print();
	return 0;
}

输出结果:

因为是继承过来的,相当于是自己的成员变量,所以可以修改。

如果在派生类中存在与基类同名的成员,则派生类将屏蔽基类同名的成员。这种情况叫做隐藏,也叫作重定义

也就是说,基类与派生类具有独立的作用域,访问过程就进原则

class Student :public Person {
public:
	void test()
	{
		_age = 10;
	}
protected:
    int _age;
	string _stuid = "B06040217";
};

修改之前的代码之后运行结果如下图所示

此时在修改_age的值就不会发生变化,此时调用基类Print会打印出来的是基类的_age。

派生类的默认成员函数

1、派生类构造函数会调用基类的构造函数来初始化基类的成员

注意:派生类无法自行初始化基类成员

class Person {
public:
	Person(const char* name = "Fancy")//构造函数
		:_name(name)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)//拷贝构造
		:_name(p._name)
	{
		cout << "Person(const Person& )" << endl;
	}
	Person& operator=(const Person& p)//operator =
	{
		cout << "Person& operator=(const Person& )" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}
	~Person()//析构函数
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
};

class Student :public Person {
public:
	
protected:
	string _stuid = "B06040217";
};
int main()
{
	Student s;
	return 0;
}

可以看到派生类自动调用基类的构造函数与析构函数

如果尝试手动初始化基类的成员变量,会出错。

 只能通过调用基类的构造函数才能初始化

同样的,拷贝构造函数是一样的道理

也需要手动的调用一下基类的拷贝构造函数,基类只截取他自己需要的参数。

operator = 也得显示调用,不然未完全拷贝,很危险。

注意:析构函数与前面不太一样,不需要显示的调用,他会自动的去调用,显示调用会析构多次。

基类的静态成员 

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员无论派生出多少个子类,都只有一个static成员实例 。这个很好理解。

菱形继承与虚拟继承

继承又分为,单继承、多继承、菱形继承。

单继承

 多继承

 菱形继承

菱形继承的问题:

二义性数据冗余

用上面的图来说,二义性的表现在Student和Teacher中都有Person的成员,当Assistant 对象访问该成员时,不知道是Student的成员还是Person的成员。

对于这种问题,就只能交代清楚是谁中的成员。

例如:

Assistant a;
a.Student::_name = "who";

数据冗余表现在

Assistant 中有两份Person 中的成员,这并不一定是我们想要的。

针对这种问题,我们可以采用虚拟继承的方法

virtual关键字。

如果不采用虚拟继承,Assistant 中有两份Person 中的成员

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.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	
	return 0;
}

可以看到其中有两份A的信息,且其中的_a值不相同。

如果采用虚继承,Assistant 类只会继承一份Person类的成员信息

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;
};

可以看到所有的_a值都是一样的

虚继承的原理:虚基表

虚基表中存的偏移量,来计算虚基类对象的位置。

注意:多继承是C++语言上的缺陷。

继承与组合

继承:

class A{……};

class B :public A{……};

组合:

class A{……};

class B

{A a;};

继承和组合都完成了类的复用,前者是 is a的逻辑(是一个什么),后者是has a的逻辑(有一个什么)

继承一定程度上破坏了基类的封装,基类一旦改变,对派生类有很大的影响。所以耦合度高。

组合类似于黑箱复用,耦合度低,组合类之间没有强的依赖关系. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值