C++ 面向对象特性之一:继承

一、继承

继承(inheritance)机制是面向对象程序设计使代码可以复用,允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,叫派生类或者子类。而被继承的类叫做基类或者父类。

⑵ 继承的使用

class animal
{
private:
	int _a;
};

//通过 : + public / protected / private 方式继承父类
class dog : public animal
{
private:
	int _b;
};

⑶ 继承方式与访问限定符
1、public 继承方式:
(一) public成员函数或者成员变量继承下来的依然是public成员函数或成员变量。
(二) protected / private成员函数或者成员变量受访问限定符影响继承下来依然为protected / private成员函数或者成员变量。

protected继承方式:
(一) protected / private成员函数或者成员变量继承下来的依然是protected / private 成员函数或成员变量。
(二) public成员函数或者成员变量继承下来的会改变为protected成员函数或成员变量。

private继承方式:
(一) private成员函数或者成员变量继承下来的依然是private 成员函数或成员变量。
(二)public / protected成员函数或者成员变量继承下来的会改变为private成员函数或成员变量。
2、关键字class时默认的继承方式private,使用struct时默认的继承方式public

总结:继承方式与访问限定符谁更高级选谁 (private > protected > public)。但一般使用都是public继承,几乎很少使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展 / 维护性不强。


二、赋值转换(切片)

派生类是可以直接赋值给基类(指针、引用、实例化的对象),而基类是不能直接赋值给派生类,而是需要强转类型给派生类,这样会有越界的风险。
在多继承中,有多个基类的话,赋值转换后,是指向相应的基类的位置。下面用指针举例,引用和对象也是同样的道理

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };

int main()
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;
	
	return 0;
}

请添加图片描述


三、隐藏

在继承体系中基类和派生类都有独立的作用域,然后子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问),在实际当中,尽量不要定义同名函数构成隐藏。如果是相同作用域,符合重载条件构成重载


继承的默认成员函数

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员(初始化顺序为基类在到子类)。如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。若是多继承,则是按照继承顺序来依次初始化与初始化列表无关

  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化初始化顺序为基类在到子类)。

  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。

  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

  5. 派生类对象初始化先调用基类构造再调派生类构造

  6. 派生类对象析构清理先调用派生类析构再调基类的析构


四、继承和特殊成员关系

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

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
class Graduate : public Student
{
protected:
	string _seminarCourse; // 研究科目
};

void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
	Person::_count = 10;
	cout << " 人数 :" << Graduate::_count << endl;
}

在这里插入图片描述


五、菱形继承

(一)菱形继承是多继承(子类继承二个及以上的父类)的一种特殊情况。
请添加图片描述

(二)菱形继承存在的问题
Ⅰ 二义性 :数据出现重复时,调用重复的数据编译器无法确定该调用那个。如上图当father Jason调用age变量时,编译器无法确定该调用那个age,这时候需要指定父类去调用,如:jason.age = 30; 改为jason.actor::age = 30。

Ⅱ 数据冗余:要依靠虚拟继承解决,使用方法如下

class person
{
public:
	string name;
	int age;
};

//虚继承
class actor : virtual public person 
{};

class model: virtual public person 
{};

(三)虚继承原理
将原来数据位置替换成虚基表指针,在Jason对象中只保存一份person的数据通过虚基表中偏移量找到该数据,这样解决了二义性和数据冗余的问题。

请添加图片描述

(三)总结:有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,虽然能够解决菱形继承所带来的问题,但是底层实现就变得很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承!!!。否则在复杂度及性能上都有问题。

六、继承和组合

(一)public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高

(二)组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。组合类之间没有很强的依赖关系,耦合度低,代码维护性好

(三)继承和组合的选择:当继承和组合都合适的情况下,用组合来进行设计。当设计成继承时比设计组合更合适时用继承,当设计成组合时比设计成继承更合适时用组合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值