C++进阶:继承

  • 继承的概念与定义
    1、概念:继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类或者子类,原有类就称为基类或者父类.
    定义:
    例如:
#include <iostream>
using namespace std;
#include <string>
class Person//基类
{
public:
	Person(const string& name, int age)
		:_name(name)
		, _age(age)
	{}
	void PrintPerson()
	{
		cout << _name << endl;
		cout << _age << endl;
	}
private:
	string _name;
	int _age;
};
class Student :Person//派生类
{};
int main()
{
	cout << sizeof(Student) << endl;//string类的字节大小是28,
	//大小为32,将基类中的成员变量继承到派生类中
	return 0;
}

其中Person类就是基类,而Student类就是派生类,将基类中的成员变量继承下来
2、继承权限:public、protected、private
访问权限:public、protected、private
1》当继承方式是public时
例如:

class Base
{
public:
	void SetBase(int pub, int pro, int pri)
	{
		_pub = pub;
		_pro = pro;
		_pri = pri;
	}
	void print()
	{
		cout << _pub << endl;
		cout << _pro << endl; 
		cout << _pri << endl;
	}
public:
	int _pub;
protected:
	int _pro;
private:
	int _pri;
};
class Derived :public Base//按照public继承方式继承
{
public:
	void SetDerived(int pub, int pro, int pri)
	{
		//从基类中继承的成员变量的赋值

		_pub = pub;//基类中访问权限是public的pub被继承了,并且可以访问
		_pro = pro;//基类中访问权限是protected的pro被继承了,并且可以访问,但是在类外不能访问
		//_pri = pri;//不可以,基类中的访问权限是private的成员变量被继承了,但是不能访问(不可见的)
		/*
		派生类的继承方式是public时:基类中private的成员在派生类中不能直接被访问
		但该成员已经被继承了
		*/

		//子类自己的成员变量赋值
		_pubD = pub;
		_proD = pro;
		_priD = pri;	
	}
public:
	int _pubD;
protected:
	int _proD;
private:
	int _priD;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._pub = 10;//可以直接被访问,基类中public的成员变量在子类中也是public的
	//d._pro = 10;//不能直接访问,不能在类外访问,但不知道它的访问权限是protected还是private
	return 0;
}

说明基类Base中的成员变量的确在子类Derived中被继承了,但是基类中访问权限是private的成员变量不能访问(不可见)的,基类中public和private的成员变量被继承后,它们在子类中的访问权限不变,public修饰的仍然可以在类外访问,private修饰的不能在类外访问,但是基类中protected修饰的成员变量在被继承后它的访问权限到底是protected的还是private的,可以采用这种方法

class Base
{
public:
	void SetBase(int pub, int pro, int pri)
	{
		_pub = pub;
		_pro = pro;
		_pri = pri;
	}
	void print()
	{
		cout << _pub << endl;
		cout << _pro << endl; 
		cout << _pri << endl;
	}
public:
	int _pub;
protected:
	int _pro;
private:
	int _pri;
};
class Derived :public Base//按照public继承方式继承
{
public:
	void SetDerived(int pub, int pro, int pri)
	{
		//从基类中继承的成员变量的赋值

		_pub = pub;//基类中访问权限是public的pub被继承了,并且可以访问
		_pro = pro;//基类中访问权限是protected的pro被继承了,并且可以访问,但是在类外不能访问
		//_pri = pri;//不可以,基类中的访问权限是private的成员变量被继承了,但是不能访问(不可见的)
		/*
		派生类的继承方式是public时:基类中private的成员在派生类中不能直接被访问
		但该成员已经被继承了
		*/

		//子类自己的成员变量赋值
		_pubD = pub;
		_proD = pro;
		_priD = pri;	
	}
public:
	int _pubD;
protected:
	int _proD;
private:
	int _priD;
};
class D :public Derived
{
public:
	void TestFunc()
	{
		_pro = 10;//可以使用,说明Derive的类中继承的_pro访问权限不是private的,而是protected的,说明没有改变
	}
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._pub = 10;//可以直接被访问,基类中public的成员变量在子类中也是public的
	//d._pro = 10;//不能直接访问,不能在类外访问,但不知道它的访问权限是protected还是private
	return 0;
}

对子类Derive的再进行一次继承方式为public的继承,得到子类D,发现访问权限是protected的成员变量可以在D类中使用,(如果访问权限变为了private,就不能在它的子类中使用,因为是不可见的),说明它的访问权限还是protected
2》当继承方式是protected时
例如:

class Base
{
public:
	void SetBase(int pub, int pro, int pri)
	{
		_pub = pub;
		_pro = pro;
		_pri = pri;
	}
	void print()
	{
		cout << _pub << endl;
		cout << _pro << endl;
		cout << _pri << endl;
	}
public:
	int _pub;
protected:
	int _pro;
private:
	int _pri;
};
class Derived :protected Base//按照protected继承方式继承
{
public:
	void SetDerived(int pub, int pro, int pri)
	{
		//从基类中继承的成员变量的赋值

		_pub = pub;//可以访问
		_pro = pro;//可以访问
		//_pri = pri;//已经被继承,不能访问(不可见)

		//子类自己的成员变量赋值
		_pubD = pub;
		_proD = pro;
		_priD = pri;
	}
public:
	int _pubD;
protected:
	int _proD;
private:
	int _priD;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._pub = 10;//不能在类外访问,说明_pub的访问权限public变成了protected或者private
	d._pro = 20;//不能在类外访问,它的访问权限有可能是protected或者private
	return 0;
}

当继承方式是protected时,基类中的成员变量都被继承了,访问权限是private的成员变量不能被访问(不可见),但是被继承的成员变量他们的访问权限却发生了变化,因此可以将子类再派生一次,生成D类,来看成员变量的访问权限到底发生了什么变化,例如:

class Base
{
public:
	void SetBase(int pub, int pro, int pri)
	{
		_pub = pub;
		_pro = pro;
		_pri = pri;
	}
	void print()
	{
		cout << _pub << endl;
		cout << _pro << endl;
		cout << _pri << endl;
	}
public:
	int _pub;
protected:
	int _pro;
private:
	int _pri;
};
class Derived :protected Base//按照protected继承方式继承
{
public:
	void SetDerived(int pub, int pro, int pri)
	{
		//从基类中继承的成员变量的赋值

		_pub = pub;//可以访问
		_pro = pro;//可以访问
		//_pri = pri;//已经被继承,不能访问(不可见)

		//子类自己的成员变量赋值
		_pubD = pub;
		_proD = pro;
		_priD = pri;
	}
public:
	int _pubD;
protected:
	int _proD;
private:
	int _priD;
};
class D :public Derived
{
public:
	void TestFunc()
	{
		_pub = 10;//它可以被访问,但是不能在类外访问,因此访问权限是protected
		_pro = 20;//可以被访问,因此访问权限还是protected	
	}
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	//d._pub = 10;//不能在类外访问,说明_pub的访问权限public变成了protected或者private
	//d._pro = 20;//不能在类外访问,它的访问权限有可能是protected或者private
	return 0;
}

说明继承方式是protected时,基类中访问权限是public的成员变量在子类中的访问权限变为protected,其他成员变量访问权限不变
3》继承方式是private时
例如:

class Base
{
public:
	void SetBase(int pub, int pro, int pri)
	{
		_pub = pub;
		_pro = pro;
		_pri = pri;
	}
	void print()
	{
		cout << _pub << endl;
		cout << _pro << endl;
		cout << _pri << endl;
	}
public:
	int _pub;
protected:
	int _pro;
private:
	int _pri;
};
class Derived :private Base//按照private继承方式继承
{
public:
	void SetDerived(int pub, int pro, int pri)
	{
		//从基类中继承的成员变量的赋值

		_pub = pub;//可以访问
		_pro = pro;//可以访问
		//_pri = pri;//不能访问

		//子类自己的成员变量赋值
		_pubD = pub;
		_proD = pro;
		_priD = pri;
	}
public:
	int _pubD;
protected:
	int _proD;
private:
	int _priD;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	//d._pub = 10;//不能在类外访问,说明它的访问权限是protected或者private
	//d._pro = 20;//不能在类外访问,说明它的访问权限是protected或者private
	return 0;
}

当继承方式是private时,基类中的成员变量在子类中都继承了,但是private修饰的成员变量仍然在子类中不可见,基类中public和protected修饰的成员变量在被继承后,在类外都不能访问他们的访问权限要么是protected要么是private,那么可以再进行子类的继承,来证明他们的访问权限到底是怎么改变的,例如:

class Base
{
public:
	void SetBase(int pub, int pro, int pri)
	{
		_pub = pub;
		_pro = pro;
		_pri = pri;
	}
	void print()
	{
		cout << _pub << endl;
		cout << _pro << endl;
		cout << _pri << endl;
	}
public:
	int _pub;
protected:
	int _pro;
private:
	int _pri;
};
class Derived :private Base//按照private继承方式继承
{
public:
	void SetDerived(int pub, int pro, int pri)
	{
		//从基类中继承的成员变量的赋值

		_pub = pub;//可以访问
		_pro = pro;//可以访问
		//_pri = pri;//不能访问

		//子类自己的成员变量赋值
		_pubD = pub;
		_proD = pro;
		_priD = pri;
	}
public:
	int _pubD;
protected:
	int _proD;
private:
	int _priD;
};
class D :public Derived
{
public:
	void TestFunc()
	{
		_pub = 10;
		_pro = 10;
		//这两个在Derived的子类中都不能使用,并且在类外也不能使用,说明
		//访问权限都是private
	}
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	//d._pub = 10;//不能在类外访问,说明它的访问权限是protected或者private
	//d._pro = 20;//不能在类外访问,说明它的访问权限是protected或者private
	return 0;
}

可以看出,在继承方式为private时,基类中的成员变量的访问权限都变成了private
总结
(1)不管什么继承方式,基类被派生后,成员都被继承了,但是基类中访问权限是private的成员在派生类中不能使用/访问(不可见)
(2)当继承方式是public时,基类中成员的访问权限不变;当继承方式是protected时,基类中访问权限是public的成员在派生类中的访问权限降为protected,其他的不变;当继承方式是private时,基类中访问权限是public和protected的成员在派生类中的访问权限降为private。也就是public>protected>private。
如图:
在这里插入图片描述
3、class默认的继承权限是private,struct默认的继承权限是public
4、因此就可以看出为什么C++要给出private访问权限同时也要给出protected访问权限,在继承中就有了明显的区别

  • 基类与派生类的对象赋值转换(赋值兼容规则)
    注意:一定是public继承方式,子类和基类是is-a关系,可以将子类看成一个基类的对象,在类外时,所有用到基类对象的位置都可以用一个子类对象来进行代替;
    (1)派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用,反之则不行。
    (2)可以让基类的指针或者引用指向子类的对象,反之则不行,虽然基类的指针可以通过强制类型转换赋值给派生类的指针,但可能会产生安全问题。
    例如:
class B
{
public:
	void SetB(int b)
	{
		_b = b;
	}
protected:
	int _b;
};
class D :public B
{
public:
	void SetD(int b, int d)
	{
		_b = b;
		_d = d;
	}
protected:
	int _d;

};
int main()
{
	B b;//基类
	b.SetB(10);
	D d;//派生类
	d.SetD(20, 30);
	b = d;//可以赋值
	//d = b;//报错,基类对象不能对子类对象赋值
	return 0;
}

那么为什么基类的对象不能给子类的对象赋值?
首先如果是public的继承方式,基类对象和子类对象就是is-a关系(可以将子类对象看成是一个基类对象),但是如果是这样的关系,那么基类对象是不是可以给子类对象赋值?答案是否定的。
从对象模型来看,对象模型就是对象中各个成员变量在内存中的布局形式,例如基类B定义的对象,它的内存中包含成员变量_b,而派生类D中已经按照public的继承方式将基类B的成员继承了下来,而派生类本身就有自己的成员变量_d,则派生类D的对象模型由两部分组成,上面是继承下来的成员_b,下面是自己的_d,如图
在这里插入图片描述
由派生类对象给基类对象肯定是没有问题的,因为可以将派生类对象看成一个基类对象,进行赋值当然可以,但是如果由基类对象给派生类对象进行赋值,如图:
在这里插入图片描述
基类中的_b可以对派生类的_b赋值,但是派生类中的_d就不知道怎么办了,如果给它赋值,代码就不安全了,当然,派生类的指针也可以赋值给基类的指针和引用,但是基类的指针不能赋值给派生类的指针和引用,但是如果要让基类的指针赋值给派生类的指针,基类的指针可以通过强制类型转换赋值给派生类的指针,但是必须是基类的指针是指向派生类对象时才是安全的。

  • 继承的作用域
    1、在继承体系中基类和派生类都有独立的作用域,所以如果基类和派生类中有着满足函数一些特性的函数,是不能看成函数重载的,因为不在一个作用域
    2、子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。
    同名隐藏(也属于赋值兼容规则):
    例如:
class B
{
public:
	void SetB(int b)
	{
		_b = b;
	}
protected:
	int _b;
};
class D:public B
{
public:
	char _b;
};
int main()
{
	D d;
	d._b = 10;//赋值给子类自己的_b
	return 0;
}

分析:D是B的子类,他继承了基类B的成员变量_b,子类D自己也有一个成员变量_b,那么当定义一个派生类的对象d时,通过对象d访问_b时,他访问的是子类的_b,不是基类的_b,这就是同名隐藏,_即在基类和子类中具有相同名称的成员(成员变量/成员函数),如果使用子类的对象访问继承体系中同名的成员,永远访问的都是子类的成员,基类中同名的成员永远访问不到,(相当于子类中同名成员隐藏了基类中的同名成员),与同名成员的类型无关,如果要通过子类对象来访问基类中的同名成员,需要在成员前面加上基类的类名称和作用域访问符,例如访问B类的成员,d.B::b
注意:. 如果是成员函数的隐藏,只需要函数名称相同就构成隐藏;在实际中最好不要使用同名的成员。

  • 派生类的默认成员函数
    1、派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员,如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。
    例如:
class Base
{
public:
	Base(int b)
		:_b(b)
	{}
	int _b;
};
class Derived : public Base
{
public:
	/*
	编译器默认生成的构造函数一定是
	Derived()
	  :Base()//只能调用无参的基类构造函数
	{}这样的
	*/
	Derived(int b, int d)//用户显式定义出派生类的构造函数,因为基类的构造函数是非缺省的,所以在初始化列表中调用基类的构造函数
		:Base(b)
		, _d(d)
	{}
	int _d;
};
int main()
{
	//Derived d;//报错,没有合适的默认构造函数
	Derived d(10, 20);//没有问题
	return 0;
}

当基类中没有显式定义构造函数,默认的构造函数是无参的,派生类中不用显式定义构造函数,编译器自动调用基类的默认构造函数,又或者基类中显式定义了非缺省构造函数,派生类构造函数必须给出构造函数并且在初始化列表位置调用基类构造函数

class Base
{
public:
	Base(int b=10)
		:_b(b)
	{}
	int _b;
};
class Derived : public Base
{
public:
	int _d;
};
int main()
{
	Derived d;
	return 0;
}

分析:又比如基类中显式定义了全缺省构造函数,派生类的构造函数可以不用实现
总结
(1)派生类构造函数一定要在初始化列表的位置调用基类的构造函数;
编译器自动调用基类默认的构造函数,或者是需要用户显式定义在派生类构造函数初始化列表中进行调用
(2)如果基类存在缺省的构造函数(全缺省或者无参的),派生类的构造函数可以实现也可以不实现(用户根据应用场景实现)
(3)如果基类显式定义了非缺省的构造函数,派生类必须在其初始化列表的位置调用基类的构造
2、 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
3、. 派生类的operator=必须要调用基类的operator=完成基类的复制
例如:

class Base
{
public:
	Base(int b)
		:_b(b)
	{}
	Base(Base& b)//拷贝构造函数
		:_b(b._b)
	{
		cout << "Base(Base&)" << endl;
	}
	int _b;
};
class Derived :public Base
{
public:
	Derived(int b, int d)
		:Base(b)
		, _d(d)
	{}
	Derived(Derived& d)//显式给出拷贝构造函数,在初始化列表出给出基类的拷贝构造函数
		:Base(d._b)
	{}
	Derived& operator=(const Derived& d)
	{
		Base::operator=(d);
		cout << "Derived::operator=(Derived&)" << endl;
		return *this;
	}
	int _d;
};
int main()
{
	Derived d1(10, 20);
	Derived d2(d1);//如果派生类没有显式定义拷贝构造函数,编译器自动生成
	d2 = d1;
	//派生类没有显式定义赋值运算符重载,编译器自动生成,
	//要调用基类的赋值运算符重载
	return 0;
}

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

class Base
{
public:
	Base(int b)
		:_b(b)
	{
		cout << "Base::Base()" << endl;
	}
	~Base()
	{
		cout << "Base::~Base()" << endl;
	}
	int _b;
};
class Derived :public Base
{
public:
	Derived(int b, int d)
		:Base(b)
		, _d(d)
	{
		cout << "Derived::Derived()" << endl;
	}
	~Derived()
	{
		cout << "Derived::~Derived()" << endl;
	}
	int _d;
};
void Testfunc()
{
	Derived d1(10, 20);
	Derived d2(d1);
}
int main()
{
	Testfunc();
	return 0;
}

最后的打印结果是如图:
在这里插入图片描述
函数调用顺序是先调用派生类的构造函数,在它的构造函数的初始化列表调用基类的构造函数,然后再进行执行语句,因此先打印的是基类构造函数的执行语句,后打印派生类的构造函数的执行语句,出了函数函数作用域后,先调用派生类的析构函数,在执行完派生类的析构函数的执行语句的最后一条,编译器加了一条自动调用基类的析构函数的指令,打印它的执行语句,因此出现了上图的打印顺序。
5派生类对象初始化先调用基类构造再调派生类构造
6派生类对象析构清理先调用派生类析构再调基类的析构
防止类被继承:

//防止类被继承(C++98),但是这种方法效率不够高,在C++11中可以使用final关键字来防止类被继承
class Base
{
public:
	static Base GetInstance(int b)
		//这个如果是普通的成员函数,则必须通过对象来调用,
		//但是这时无法用构造函数创建对象,因此可以将它变为静态成员函数
	{
		return Base(b);
	}
private:
	Base(int b)//将基类的构造函数给成私有的,但是虽然不能被继承了,但是如果想要在类外创建对象,又无法做到了,因此可以
		:_b(b)
	{}
	int _b;

};
class Derived :public Base
{
public:
	//Derived(int b, int d)
	//	:Base(b)//不能访问,因为基类中的构造函数为private的,不能被继承
	//	, _d(d)
	//{}
	int _d;
};
int main()
{
	Base b = Base::GetInstance(10);
	return 0;
}

但是这种方法比较麻烦,而且效率不高,在C++11中可以使用final关键字来防止类被继承,例如:

class Base final
{
public:
	Base(int b)
		:_b(b)
	{}
	int _b;

};
class Derived :public Base//不能继承
{
public:
	int _d;
};
int main()
{
	return 0;
}

只要在基类的类名后加上final关键字即可

  • 继承与友元
    友元关系不能继承,即基类友元不能访问子类私有和保护成员
    例如:
class Base
{
	friend void TestFunc();
public:
	Base(int b)
		:_b(b)
	{}
	~Base()
	{}
protected:
	int _b;

};
class Derived :public Base
{
public:
	Derived(int b, int d)
		:Base(b)
		, _d(d)
	{}
	~Derived()
	{}
protected:
	int _d;
};
void TestFunc()
{
	Base b(10);
	cout << b._b << endl;//无法直接访问,因为基类中的_b是protected的,可以使用友元
	Derived d(10, 20);
	//cout << d._d << endl;//无法打印,因此友元关系没有继承
}
int main()
{
	TestFunc();
	return 0;
}
  • 继承与静态成员
    基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
    例如:
class Base
{
public:
	Base(int b)
		:_b(b)
	{
		++count;
	}
protected:
	int _b;
public:
	static int count;
};
int Base::count = 0;
class Derived :public Base
{
public:
	Derived(int b, int d)
		:Base(b)
		, _d(d)
	{}
protected:
	int _d;
};
int main()
{
	Derived d(10,20);
	cout << &Base::count << endl;
	cout<<&Derived::count<< endl;
	return 0;
}

最终打印的结果是如图:
在这里插入图片描述
说明基类中静态成员也被继承了,且整个继承体系中只有这一个成员

  • 菱形继承与菱形虚拟继承
    单继承概念:一个子类只继承一个父类时称这个继承关系为单继承
    单继承对象模型:
    在这里插入图片描述
    多继承概念:一个子类有两个或以上直接父类时称这个继承关系为多继承
    例如:
//多继承
class B1
{
public:
	int _b1;
};
class B2
{
public:
	int _b2;
};
class D :public B1, public B2
{
public:
	int _d;
};
void TestFunc()
{
	cout << sizeof(D) << endl;//12
}
int main()
{
	TestFunc();
	return 0;
}

子类D有两个基类B1、B2,它的对象模型就是如图:
在这里插入图片描述
菱形继承概念:
是多继承的一种特殊情况,如图:
在这里插入图片描述
就是将单继承与多继承复合,(C1,B)、(C2,B)是两个单继承,C1继承自B,C2也继承自B,(D,C1)和(D,C2)是一个多继承,即D继承自C1,也继承自C2。看起来像一个菱形,所以就是菱形继承。
菱形继承的缺陷,怎么解决?
例如:

//菱形继承
class B
{
public:
	int _b;
};
class C1 :public B
{
public:
	int _c1;
};
class C2 :public B
{
public:
	int _c2;
};
class D :public C1, public C2
{
public:
	int _d;
};
int main()
{
	cout << sizeof(D) << endl;//20
	D d;
	d._b = 10;//错误
	return 0;
}

分析:C1从B中继承_b,C2从B中继承_b,D从C1中继承一个_b,D从C2中也继承一个_b,D中就有两份_b,它的派生类的对象模型如图:

如果直接通过派生类D的对象来访问_b,编译器就不知道访问C1和C2哪个里面继承的_b,编译时报错,因为访问不明确,这就是菱形继承的缺陷,也就是菱形继承的二义性
那么该如何解决呢?
(1)让访问变得明确,也就是在_b前增加基类(C1或者C2)名称,例如:

d.C1::_b = 10;
d.C2::_b = 20;//可以通过此类方法来具体的访问C1或者C2中继承的_b,让访问变得明确

但是这种方法在根本上并没有解决二义性问题,而且如果B类比较大,派生类D就会扩展的很大,不方便,
因此解决的第二种方法是:让最顶层基类成员(B)在D类中只保存一份,使用菱形虚拟继承方式
首先先介绍什么是虚拟继承:在单继承的继承方式前面加上virtual关键字,就把单继承变为了虚拟继承,例如:

class B
{
public:
	int _b;
};
class D :virtual public B
{
public:
	int _d;
};
int main()
{
	cout << sizeof(D) << endl;//12
	return 0;
}

可以看出将单继承变成虚拟继承后,它增加了4个字节,虚拟对象模型是倒立的,即基类在下,子类在上;编译器给派生类生成了一个默认的构造函数,并且传递实参1(作用大概就是确认到底要不要给对象的前4个字节赋值,即检测继承是否是虚拟继承),生成的默认构造函数是给对象的前4个字节赋值。
虚拟继承与普通继承的区别
(1)虚拟继承要加虚拟关键字virtual
(2)对象模型区别:虚拟继承要多四个字节空间,在构造函数中,将一个地址放在派生类对象的前4个字节中—>地址:指向一个表格(表格中存放的是偏移量,即派生类对象相对于自己的偏移量和派生类对象中基类部分相对于对象起始位置的偏移量),如图:
在这里插入图片描述
普通继承对象模型:基类部分在上,子类部分在下
虚拟继承对象模型:基类部分在最下面,子类部分在上,最上面是偏移量表格的地址
(3)派生类对基类成员的访问形式
普通继承:直接访问
虚拟继承:从对象前4个字节中取偏移量表格地址---->偏移量表格—>取基类部分相对于派生类对象起始位置的偏移量—>对象首地址+偏移量—>访问派生类对象中基类的成员
(4)构造函数不同
虚拟继承是:
1>派生类—>合成构造函数—>将偏移量表格地址放在对象前4个字节
2>多一个参数—>1(检测是否为虚拟继承)
虚拟继承的应用
(1)用来解决继承中的二义性
(2)菱形虚拟继承
例如:

//菱形虚拟继承
class B
{
public:
	void TestFunc()
	{}
	int _b;
};
class C1 :virtual public B
{
public:
	int _c1;
};
class C2 :virtual public B
{
public:
	int _c2;
};
class D :public C1, public C2
{
public:
	int _d;
};
int main()
{
	D d;
	d._b = 1;
	d._c1 = 2;
	d._c2 = 3;
	d._d = 4;
	return 0;
}

它在内存中的对象模型如图:
在这里插入图片描述
即可以看成
在这里插入图片描述
这就是菱形虚拟继承在内存中的对象模型

  • 继承和组合
    在继承方式为public时,子类和基类的关系就是is-a的关系,子类对象可以看成基类对象,在类外所有使用到基类对象的地方,都可以使用子类对象来替换;而继承方式是protected和private时,基类和子类的关系就是has-a的关系,它可以体现在2个方面:(1)protected/private继承关系,因为当继承关系是protected时,基类中访问权限是public的成员在派生类中的访问权限已经变为protected,在类外不能通过派生类对象直接访问基类的成员,该基类的成员函数可以在派生类成员函数中直接被访问,所以这两种继承方式类似于has-a关系(2)组合(聚合):一个对象中包含了另外一个对象,称为组合
    一般优先使用组合,而不是protected/private的继承方式来继承,因为继承破坏了类的封装性,而且一般很少用到,所以优先使用组合,组合的耦合度低,代码维护性好。
    例如:一个组合的例子
class A
{
public:
	void Testfunc()
	{

	}
private:
	int _a;
};
class B
{
public:
	void TestFunc()
	{}
private:
	A _a;
	int _b;
};
  • 笔试面试题
    1、什么是菱形继承?菱形继承的问题是什么?
    菱形继承就是一种单继承和多继承的复合,举上述B、C1、C2、D类的例子说明即可,菱形继承的问题就是二义性(举上述例子说明)
    2、什么是菱形虚拟继承?如何解决数据冗余和二义性的
    有两种方法解决数据冗余和二义性,一种是让访问变得明确,使用类名+作用域限定符来进行访问,一种就是利用菱形虚拟继承,就是让冗余的数据存一份即可
    3、多继承中指针偏移问题?
    例如:
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;
}

有 A. p1 == p2 == p3; B. p1 < p2 < p3; C. p1 == p3 != p2; D. p1 != p2 != p3四个选项,应该选C,因为先定义一个基类(Base1)指针p1指向派生类对象模型存放基类Base1成员的首地址,即派生类对象的首地址;定义一个基类(Base2)指针指向派生类对象模型中存放基类Base2成员空间的首地址;定义一个派生类指针p3指向派生类对象首地址,所以p1==p3!=p2,选C,如图:
在这里插入图片描述
源代码(github):
https://github.com/wangbiy/C-/tree/master/test_2019_9_23_1/test_2019_9_23_1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值