C++三大特性之一——继承

继承

如何理解继承:

现实生活中的概念具有特殊与一般的关系。例如,一般意义的“人”都有姓名、性别、年龄等属性和吃饭、行走、工作、学习等行为,但按照职业划分,人又分为学生、教师、工程师、医生等,每一类人又有各自的特殊属性与行为,例如学生具有专业、年级等特殊属性和升级、毕业等特殊行为,这些属性和行为是医生所不具有的。如何把特殊与一般的概念间的关系描述清楚,使得特殊概念之间既能共享一般的属性和行为,又能具有特殊的属性和行为呢?
继承,就是解决这个问题的。只有继承,才可以在一般概念基础上,派生出特殊概念,使得一般概念中的属性和行为可以被特殊概念共享,摆脱重复分析、重复开发的困境。 C++语言中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明。通过类的这种层次结构,可以很好地反映出特殊概念与一般概念的关系。

示例:

例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同。接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处。

普通实现:

//Java页面
class Java 
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP 
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();

}

int main() {

	test01();

	system("pause");

	return 0;
}

继承实现:

//Java页面
class Java 
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP 
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();

}

int main() {

	test01();

	system("pause");

	return 0;
}
 继承的好处:

可以减少重复的代码

class A : public B;

A 类称为子类 或 派生类

B 类称为父类 或 基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员。

从基类继承过过来的表现其共性,而新增的成员体现了其个性。

继承方式:
继承方式private继承protected继承public继承
基类的private成员不可见不可见不可见
基类的protected成员变为private成员仍为protected成员仍为protected成员
基类的public成员变为private成员变为protected成员仍为protected成员

示例:

class Base1
{
public: 
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

//公共继承
class Son1 :public Base1
{
public:
	void func()
	{
		m_A; //可访问 public权限
		m_B; //可访问 protected权限
		//m_C; //不可访问
	}
};

void myClass()
{
	Son1 s1;
	s1.m_A; //其他类只能访问到公共权限
}

//保护继承
class Base2
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son2:protected Base2
{
public:
	void func()
	{
		m_A; //可访问 protected权限
		m_B; //可访问 protected权限
		//m_C; //不可访问
	}
};
void myClass2()
{
	Son2 s;
	//s.m_A; //不可访问
}

//私有继承
class Base3
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son3:private Base3
{
public:
	void func()
	{
		m_A; //可访问 private权限
		m_B; //可访问 private权限
		//m_C; //不可访问
	}
};
class GrandSon3 :public Son3
{
public:
	void func()
	{
		//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
		//m_A;
		//m_B;
		//m_C;
	}
};

继承相关常考面经:

什么是类的继承?
  • 类与类之间的关系: has-A包含关系,用以描述一个类由多个部件类构成,实现has-A关系用类的成员属性表示,即一个类的成员属性是另一个已经定义好的类; use-A,一个类使用另一个类,通过类之间的成员函数相互联系,定义友元或者通过传递参数的方式来实现;is-A,继承关系,关系具有传递性;
  • 继承的相关概念 所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称 为子类或者派生类,被继承的类称为父类或者基类;
  • 继承的特点: 子类拥有父类的所有属性和方法,子类可以拥有父类没有的属性和方法,子类对象可以当做父类对象使 用;
  • 继承中的访问控制 publicprotectedprivate
  • 继承中的构造和析构函数
  • 继承中的兼容性原则
publicprotectedprivate访问和继承权限/public/protected/private区别?

public的变量和函数在类的内部外部都可以访问。

protected的变量和函数只能在类的内部和其派生类中访问。

private 修饰的元素只能在类内访问。
  1. 访问权限
        派生类可以继承基类中除了构造/ 析构、赋值运算符重载函数之外的成员,但是这些成员的访问属性在派生过程中也是可以调整的,三种派生方式的访问权限如下表所示:注意外部访问并不是真正的外部访问,而是在通过派生类的对象对基类成员的访问。
派生类对基类成员的访问形象有如下两种:
  • 内部访问:由派生类中新增的成员函数对从基类继承来的成员的访问
  • 外部访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问

     2. 继承权限

public 继承
        公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,都保持原有的状态,而基类的私有成员任然是私有的,不能被这个派生类的子类所访问。
protected 继承
        保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成 员函数或友元函数访问,基类的私有成员仍然是私有的。
private 继承
        私有继承的特点是基类的所有公有成员和保护成员都成为派生类的私有成员,并不被它的派生类的子类 所访问,基类的成员只能由自己派生类访问,无法再往下继承。

什么是虚拟继承 

虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。虚继承可以解决多种继承前面提到的两个问题。

由于C++支持多继承,除了publicprotectedprivate三种继承方式外,还支持虚拟(virtual)继承,举 个例子:

#include <iostream>
using namespace std;
class A{}
class B : virtual public A{};
class C : virtual public A{};
class D : public B, public C{};
int main()
{
 cout << "sizeof(A):" << sizeof A <<endl; // 1,空对象,只有一个占位
 cout << "sizeof(B):" << sizeof B <<endl; // 4,一个bptr指针,省去占位,不需要对齐
 cout << "sizeof(C):" << sizeof C <<endl; // 4,一个bptr指针,省去占位,不需要对齐
 cout << "sizeof(D):" << sizeof D <<endl; // 8,两个bptr,省去占位,不需要对齐
}
上述代码所体现的关系是, B C 虚拟继承 A D 又公有继承 B C ,这种方式是一种 菱形继承或者钻石继 ,可以用如下图来表示

虚拟继承的情况下,无论基类被继承多少次,只会存在一个实体。 虚拟继承基类的子类中,子类会增加某种形式的指针,或者指向虚基类子对象,或者指向一个相关的表格;表格中存放的不是虚基类子对象 的地址,就是其偏移量,此类指针被称为bptr ,如上图所示。如果既存在 vptr 又存在 bptr ,某些编译器会 将其优化,合并为一个指针。
示例:
#include<iostream>
using namespace std;
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;
};
//菱形继承和菱形虚继承的对象模型
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    cout << sizeof(D) << endl;
    return 0;
}

分别从菱形继承和虚继承来分析:菱形继承中A在B,C,D,中各有一份,虚继承中,A共享。上面的虚继承表实际上是一个指针数组。B、C实际上是虚基表指针,指向虚基表。虚基表:存放相对偏移量,用来找虚基类。

继承机制中对象之间如何转换?指针和引用之间如何转换?
1) 向上类型转换
        将派生类指针或引用转换为基类的指针或引用被称为向上类型转换,向上类型转换会自动进行,而且向上类型转换是安全的。
2) 向下类型转换
        将基类指针或引用转换为派生类指针或引用被称为向下类型转换,向下类型转换不会自动进行,因为一个基类对应几个派生类,所以向下类型转换时不知道对应哪个派生类,所以在向下类型转换时必须加动态类型识别技术。RTTI 技术,用 dynamic_cast 进行向下类型转换。

在C++中,有四种常见的类型转换方式,分别是静态转换(static_cast)、动态转换(dynamic_cast)、常量转换(const_cast)和重新解释转换(reinterpret_cast)。

静态转换(static_cast):用于非多态类型之间的转换,如基本数据类型之间的转换、具有继承关系的类型之间的转换。静态转换在编译时进行,不提供运行时的类型检查,需要程序员确保转换的安全性。

动态转换(dynamic_cast):用于多态类型之间的转换,主要用于基类和派生类之间的转换。动态转换在运行时进行类型检查,可以判断是否可以安全地将基类指针或引用转换为派生类指针或引用,如果转换不安全,则返回空指针或抛出异常。

常量转换(const_cast):用于添加或删除变量的const属性、volatile属性。常量转换主要用于去除变量的常量性,允许对常量变量进行修改。

重新解释转换(reinterpret_cast):用于不同类型之间的二进制数据转换,通常用于低层操作。重新解释转换允许将指针或引用转换为不相关类型的指针或引用,但不进行类型安全检查。

关于从派生类转换为基类,可以使用静态转换(static_cast)。这种转换在基类和派生类之间进行向上转换是安全的,但不能保证向下转换的安全性,因为静态转换没有运行时的类型检查。

dynamic_cast 主要用于在运行时进行安全的向下转型,可以检查指针是否可以安全地转换为派生类的指针。它通常用于处理多态类型之间的转换,比如在处理基类和派生类的关系时,可以使用 dynamic_cast 来进行安全的类型转换。如果转换不安全,dynamic_cast 会返回空指针或抛出异常,因此它是一种较为安全的类型转换方式。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值