单继承

一、继承概念及定义

  • 继承概念

        继承就是代码复用、功能扩展的一种方式。简单的来说就是,在C++类中,每一个类都有自己独立的成员变量和成员函数,但也有时两个类中一部分成员是相同的或者几乎一样,例如声明两个类,其中一个学生类包含学号、姓名、性别。另外一个类是学校某一个部门除了包含学号、姓名、性别,还包含一些其他变量,如年龄、成绩等。这样我们就可以想到既然在一个学生类中声明了学号、姓名、性别,那我们可不可以在学生类的基础上声明某一个部门类,这样还可以减少重复的工作量。所以,使用继承就可以实现代码的复用。

  • 继承作用
  1. 代码复用、功能扩展
  2. 实现多态

继承最主要作用是实现多态。继承还有代码复用等功能,但代码复用破坏了类的封装性,受保护的成员,可以在不同的作用域内访问,所以破坏了类的封装性。

        在C++中“继承”就是在一个已从在的类的基础上建立一个新的类。一个类从已经存在的类那里获得已有的特性,这种现象称为类的继承。在保持原有类(基类/父类)的基础上进行拓展,增加功能,产生一个新的类,称为派生类(子类)

  • 继承定义

总结:

  1. 一个基类private成员,不管什么方式继承到派生类都是不可见的。这里的不可见是指虽然private成员已经继承到了派生类,但是在派生类中式不可以使用的(语法上限制不可以使用),所以在类外也是不可以使用的。
  2. 基类private成员在派生类中式不可见的。那如果我们想要在派生类内部可以使用基类变量,但是在类外不可以访问该基类变量,这时就定义为protected,所以保护成员限定符专门为了继承实现的。
  3. 从上表我们可以观察到以下结论:除了基类私有成员不管什么继承方式在派生类中都是不可见的。而其他基类的成员在派生类中访问权限 = min(继承方式,成员在基类中的访问限定符)。其中public > protected > private。
  4. 在使用关键字class时默认继承方式是private,使用struct时默认继承方式是public。
  5. 其实不管基类成员什么限定符,继承方式是什么,都是继承下来的,只不过继承下来的区别就是private在派生类里面是不可见的。

二、继承的赋值兼容规则

前提规则:public继承,因为public继承就是is-a的关系,也就是每个派生类对象就是一个基类对象

  1. 派生类对象可以赋值给基类的对象/基类的指针/基类的引用。
  2. 基类的对象不可以赋值给派生类
  3. 基类的指针可以强制类型转化赋值给派生类指针,但是前提是基类的指针指向派生类对象才是安全的。
class Base
{
public:
	int _b;
};

class Derived :public Base
{
public:
	int _d;
};

int main()
{
	Derived d;
	d._d = 1;
	d._b = 2;
	Base b;
	b._b = 3;
    b = d;//派生类对象给基类对象赋值
	Base *pb = &d;//基类指针指向派生类对象

	Base &pa = d;//派生类对象赋值给基类引用

	return 0;
}

所以,可以得到单继承派生类的对象模型:

三、继承中同名隐藏

继承中,基类和派生类是属于不同的作用域,所以不可以形成重载。

同名隐藏:基类和派生类有相同的成员(成员函数/成员变量),派生类成员将屏蔽掉基类中同名成员的访问,这种情况称为同名隐藏。可以看出函数只要是同名,不管参数类型就可以形成同名隐藏。

通过显示调用基类成员才可以访问被隐藏的函数。

class Base
{
public:
	void SetValue()
	{
		cout << "Base:void SetValue()" << endl;
	}
	int _b;
};

class Derived :public Base
{
public:
	void SetValue(int d)
	{
		cout << "Derived:void SetValue()" << endl;
	}
	int _d;
};

int main()
{
	Derived d;
	//d.SetValue();//这里不接收无参调用,可见隐藏了基类同名函数
	d.SetValue(10);
	//那么我们怎么访问基类中同名隐藏了的函数呢?
	//显示调用
	d.Base::SetValue();
	d.Derived::SetValue(10);
	return 0;
}

四、派生类中的默认成员函数

  • 语法上,派生类构造函数必须要调用基类的构造函数。如果基类没有默认的构造函数,那么必须要在派生类构造函数初始化列表显示调用。
class Base
{
public:
	Base(int b)
		:_b(b)
	{
		cout << "Base(int b)" << endl;
	}
public:
	int _b;
};

class Derived :public Base
{
public:
	//1、用户没有定义基类和派生类都没有构造函数,那么编译器也不会生成构造函数
	//2、用户定义基类构造函数缺省,派生类默认生成构造函数调用基类构造函数
	//3、用户定义基类构造函数非缺省,那么派生类构造函数需要我们显示给出并且显示调用基类构造函数
	Derived(int d)
		:Base(d)
		, _d(d)
	{
		cout << "Deived(int d)"<< endl;
	}
public:
	int _d;
};
  • 派生类的拷贝构造函数必须要调用基类的拷贝构造函数,完成基类部分拷贝初始化
  • 派生类的operator=必须要调用基类的operator=完成基类的赋值
  • 派生类的析构函数会在被调用完成之后,最后一条语句完成之后自动调用基类的析构函数。
  • 派生类初始化先调用派生类构造函数,在跳转到基类构造函数,完成基类的构造,之后返回到派生类构造位置继续执行后续代码
  • 派生类析构顺序是先调用完成派生类析构函数,然后在最后派生类对象最后一条语句之后调用基类的析构函数。。因为创建的哪个对象,就需要先调用哪个类,之后跳转。

面试题:实现一个不能被继承的类?

因为如果一个基类显示给出构造函数,那么派生类也必须要调用基类构造函数。所以如果把基类的构造函数给为私有的,那么就不能被继承。

//C++98
class NonInherit
{
	static NonInherit GetInstance()
	{
		return NonInherit();
	}
private:
	NonInherit(){}
};
//C++11
class NonInherit final
{};

五、继承与友元和静态成员的关系

友元关系不能被继承,也就是基类友元不能访问派生类私有成员和保护成员。

基类定义static成员,无论有多少个派生类,在整个继承体系里面都只有这样一个成员。

猜你喜欢

转载自blog.csdn.net/weixin_41318405/article/details/86438132