继承—菱形继承及菱形虚拟继承

一、继承
(一)概念:使代码复用的手段
(二)继承权限:
1.类中的访问权限(public、protected、private)
2.继承列表中的继承权限(public、protected、private)
注意:保护访问权限和私有访问权限在派生类中体现出来,即protected可以在派生类访问,在类外不能访问,private不可以在派生类中访问,更不能在类外访问
(三)继承方式:
1.公有的继承方式(public)
(1)基类中成员:已经被继承到派生类
(2)基类中不同访问权限的成员在派生类中的访问权限没有改变
注意:基类中的私有成员不能在派生类中访问

#include <iostream>
using namespace std;
class Base
{
public:
        void SetBase(int pri, int pro, int pub)
        {
               _priB = pri;
               _proB = pro;
               _pubB = pub;
        }
        void Print()
        {
               cout << "_priB = " << _priB << endl;
               cout << "_proB = " << _proB << endl;
               cout << "_pubB = " << _pubB << endl;
        }
        //三个不同类的成员变量 12个字节
private:
        int _priB;
protected:
        int _proB;
public:
        int _pubB;
};
//子类或者派生类
class Dertived : public Base
{
        //测试成员变量和成员函数
public:
        void SetDerived(int pri, int pro, int pub)
        {
               _pubB = pub;
               _proB = pro;
        }
private:
        int _priD;
public:
        int _pubD;
protected:
        int _proD;
};
class C : public Dertived
{
public:
        void TestFunc()
        {
               _proB = 10;
        }
};
int main()
{
        cout << sizeof(Dertived) << endl;//输出为12,证明是继承
        Dertived d;
        d.SetBase(10, 20, 30);
        d.Print();
        getchar();
        return 0;
}

2.保护的继承方式
(1)基类中成员:已经被继承到派生类
(2)基类中public的成员变量在子类中的访问权限变为protected
(3)基类中protected的成员变量在子类中仍旧是protected
注意:基类中私有的成员变量不能在派生类中访问
3.私有的继承方式
(1)基类中成员:已经被继承到派生类
(2)基类中的public的成员变量在子类中的访问权限是private
(3)基类中的protected的成员变量在子类中的访问权限是private
注意:基类中的私有成员变量不能在派生类中访问,class默认的继承访问是private
4.is-a
(1)public的继承方式:子类和基类是is-a,可以将子类对象看成是一个基类对象,所有用到基类对象的位置,都可以用子类进行替换访问。
5.基类和派生类赋值转换
(1)将派生类对象可以赋值给基类对象/指针/引用,即可以用基类的指针去指向派生类的对象,用基类的引用去引用派生类的对象(不能用派生类的指针指向基类的对象

class Person
{
protected:
	string _name;
	int _num;
};

class student :public Person
{
public:
	int _num;
};

void test()
{
	student sobj;
	Person pobj = sobj;//子类对象可以赋值给基类对象/指针/引用
	Person* pp = &sobj;
	Person& rp = sobj;

	//sobj = pobj;  //会出错:因为基类对象不能赋值给子类对象

	pp = &sobj;
	student* ps1 = (student*)pp;
	ps1->_num = 10;
	//这三行代码是基类的指针通过强制类型转换赋值给子类的指针,前提是基类的指针指向的是子类的对象。如果基类的指针指向的是基类的对象,就存在越界访问
	pp = &pobj;
	student* ps2 = (student*)pp;
	ps2->_num = 10;
}

(四)继承中的作用域(相同名字的成员被继承下来,但是调用的时候被隐藏了)
1.在继承体系中,基类和子类都有独立的作用域。
2.子类和基类有同名成员,子类成员将屏蔽基类对同名成员的直接访问,这种情况交隐藏,也叫重定义。
3.需要注意的是如果是成员函数的隐藏,只需要函数名字相同就能构成隐藏。(避免不要这样写)
(五)类的六个默认成员函数
1.A类(基类)有缺省的构造函数,B类(子类)没有显式定义任何构造函数,包含了A类(基类)的对象_a,那么B类(子类)子类必须在初始化列表阶段显式调用A类(基类)的构造函数。
2.子类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝初始化。
3.子类的operator=必须要调用基类的operator=完成基类的赋值。
4.子类的析构函数在被调用完成之后自动调用基类的析构函数清理基类成员。只有这样做才能保证子类的对象先清理子类成员再清理基类成员的顺序。
5.子类对象初始化先调用基类构造,再调用子类构造。

class Person
{
public:
	Person(const char* name = "Peter")
		:_name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person& operator=(const Person& p)" << endl;
		if (this != &p)
		{
			_name = p._name;
		}

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}

protected:
	string _name;
};

class Student :public Person
{
public:
	Student(const char* name, int num)
		:Person(name)//1.这里必须显式调用
		, _num(num)
	{
		cout << "Student()" << endl;
	}

	Student(const Student& s)
		:Person(s)
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s);//先调用基类的构造函数完成对基类的初始化
			_num = s._num;
		}

		return *this;
	}

	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num;
};

void test()
{
	Student s1("Jack", 18);
	Student s2(s1);
	Student s3("rose", 18);
	s1 = s3;
}

(六)final关键字
1.在类的后面加final关键字,则该类不能被继承。

class MonInherit final
{}

(七)继承与友元
友元关系不能继承。就是基类友元不能访问子类私有和保护成员。

class Student;

class Person
{
public:
	friend void Display(const Person& p, const Student& s);

protected:
	string _name;
};

class Student:public Person
{
protected:
	int _stuNum;
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	//cout << s._stuNum << endl;//不能访问其子类的成员
}

(八)继承与静态成员
1.基类定义了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 test()
{
	Student s1;
	cout << "人数" << Person::_count << endl;
	Student s2;
	cout << "人数" << Person::_count << endl;
	Student s3;
	cout << "人数" << Person::_count << endl;
	Graduate s4;
	cout << "人数" << Person::_count << endl;
	Student::_count = 0;
	cout << "人数" << Person::_count << endl;
}

运行结果如下:
在这里插入图片描述
(九)复杂的菱形继承及菱形虚拟继承
1.单继承:一个子类只有一个直接父类。
在这里插入图片描述
2.多继承:一个子类有两个或两个以上直接父类。
在这里插入图片描述
3.菱形继承:菱形继承是多继承的一种特殊情况。
在这里插入图片描述

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

void test()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
}


B、C中的_a都是来自于A,其实两个_a是同一个,但是因为B、C各自继承了一次,就产生了两份,造成了数据冗余,由于D继承于B、C,B、C继承于A,所以D中的_a不知道来自B还是来自C,于是产生了二义性
4.虚拟继承的引入:
(1)虚拟继承是解决C++多重继承的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝,这将存在两个问题,第一,浪费存储空间,第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式上面也有提到,我们在这里重温一遍,是将子类的对象赋值给基类的对象/指针/引用,但是多重继承就可能存在一个基类的多份拷贝,这就出现了二义性。
我们可以将上面的代码改写为:

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

void test()
{
	D d;
}

就上面的代码进行简单的对比分析:
加了virtual之后,_a会单独存放,B和C共享_a,于是解决了数据冗余及二义性。
5.菱形虚拟继承
在这里插入图片描述
同时我们也可以发现在虚拟继承之后,类的大小也发生变化,首先_b和_c对象的内存空间分别多了一个存储一个地址的空间,而把_a变量放在了成员变量的最底下,使_a成为一个公共的变量,所以我们可以得到,加了virtual之后的继承比普通继承多了4个字节。在内存中看到在加了virtual的类中多了一个地址,分析如下:
第一类分析:
在这里插入图片描述
第二类分析:
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值