C++基础---继承(详细版)

继承是什么?

继承是 C++ 面向对象编程中一个强大的特性,他指的是一个类在原有类的基础上进行更详细更具体的定义过程,它允许一个类从另一个类获取成员,包括除了构造函数、拷贝构造函数、析构函数、友元和静态成员以外所有的数据成员和成员函数。

继承的特性

继承具有传递性和不对称性,我们举个例子,交通工具是一个大类,在他的基础上我们可以派生出火车、汽车、飞机、轮船等类,而这些类又可以再派生出其他类,比如汽车可以分为轿车、面包车、卡车等,轿车是交通工具,这就说明了继承的传递性;所有的轿车都是交通工具,但是不是所有的交通工具都是轿车,这说明了继承的不对称性。

继承的作用

通过继承,可以实现代码的复用和扩展避免重复编写相同或相似的代码例如,如果有一个基类  Animal  ,它包含了一些基本的属性和方法,如name、age和eat()方法。然后我们可以创建一个派生类Dog,它继承自Animal,那么他也拥有了name、age和eat()方法,并且还可以添加自己特有的属性和方法,比如bark() (吠叫)方法。

继承的分类

根据子类继承的父类数量可以将继承分为单继承和多继承:

单继承说的是一个子类只有一个直接父类,多继承是一个子类有多个直接父类。

子类就是新产生的类,也可以叫做派生类,父类就是原有的类,也可以叫做基类。

根据继承时的访问限定可以分为公有继承、私有继承和保护继承:

公有继承时,基类的公有成员在派生类中仍然是公有成员,可以被派生类对象以及派生类之外的代码访问。  基类的保护成员在派生类中仍然是保护成员,只能在派生类及其子类中访问。

保护继承时,基类的公有成员在派生类中变为保护成员。  基类的保护成员在派生类中仍然是保护成员。

私有继承时,基类的公有成员和保护成员在派生类中都变为私有成员,都只能在派生类内部访问。

无论哪种继承方式,基类的私有成员在派生类中都是不可直接访问的。如果想要在派生类内部来访问基类的私有成员,则基类需要提供相应的公共接口,比如公有或保护成员函数,使派生类可以间接操作基类的私有成员。

类型兼容性原则

说到公有继承,就可以联想到类型兼容性原则,就是说 任何需要基类对象的地方都可以用该基类的公有派生类代替。因为一个类的公有派生类继承了除构造析构拷贝构造,静态成员外的所有成员,同时基类成员的权限通过公有继承基本不变,这样就意味着可以用这个类的公有派生类对象来代替基类对象。  

他涉及到五种使用场景:

      ① 用派生类对象给基类对象赋值 

      ② 拿派生类对象初始化基类对象

      ③ 派生类对象可以拷贝构造基类对象

      ④ 基类指针可以指向派生类对象,要注意的是,在这种情况下,指针只能访问基类有的成员,而不能访问派生类独有的成员

      ⑤ 基类对象的引用可以引用派生类对象

调用顺序

在继承中,还涉及到构造函数和析构函数的调用顺序。当创建派生类的对象时,首先会调用基类的构造函数,对基类继承的成员进行内存分配和初始化,然后再调用派生类的构造函数如果是多继承的话,则按照继承时声明的顺序调用基类构造函数;在对象销毁时,析构函数的调用顺序则相反,先调用派生类的析构函数,然后再调用基类的析构函数。  

#include <iostream>
using namespace std;
class A1
{
public:
	A1() { cout << "A1 constructor" << endl; }
	~A1() { cout << "A1 destructor" << endl; }
};
class A2
{
public:
	A2() { cout << "A2 constructor" << endl; }
	~A2() { cout << "A2 destructor" << endl; }
};
class A3
{
public:
	A3() { cout << "A3 constructor" << endl; }
	~A3() { cout << "A3 destructor" << endl; }
};

class Demo :public A1, public A3, public A2
{
public:
	Demo() { cout << "Demo constructor" << endl; }
	~Demo() { cout << "Demo destructor" << endl; }
};
int main()
{
	Demo obj;
	return 0;
}

如上述代码运行结果如下图:

 

派生类构造函数的写法

类名::构造函数名(形参列表):基类名1(参数列表),基类名2(参数列表),…基类名n(参数列表),成员1(参数列表),成员2(参数列表),…成员n(参数列表)

{

       构造函数函数体;

}

关于继承中的同名问题:

① 成员函数的隐藏

派生类中含有和基类成员函数同名的函数时,在派生类对象调用时,默认只能看见派生类自己的同名函数。

规则:1)有继承关系

           2)基类和派生类函数名同名

② 成员函数的重定义

在隐藏规则的前提下,派生类的成员函数的函数原型和基类的函数原型一致。

规则:1)有继承关系

           2)基类和派生类函数名同名,返回值一致,参数一致

当子类的成员函数名与父类中成员函数同名且函数原型完全相同时,我们可以用virtual关键字来修饰父类中的同名成员函数,也就是说父类中的这个同名成员函数现在是一个虚函数,此时我们通过父类指针调用该函数时,实际上调用的就是子类中重写的函数了,这就构成了多态

例如,假设有一个基类Shape,其中有一个虚函数draw()  ,然后有派生类Circle 和 Rectangle(矩形)分别重写了 draw() 函数。此时我们创建一个Shape类型的指针ptr,让他等于new Circle(),就是开辟一块Circle大小的空间,把他存到指针ptr里,再让ptr指向draw()函数,我们就可以发现虽然指针类型是Shape类的,但是他调用的是Circle类的draw()函数。如果把Circle换成Rectangle也是一样的。 

#include<iostream>
using namespace std;
class Shape
{
public:
	virtual void draw()
	{
		cout << "画一个图形" << endl;
	}
};
class Circle :public Shape
{
public:
	void draw()
	{
		cout << "画一个圆形" << endl;
	}
};
class Rectangle :public Shape
{
public:
	void draw()
	{
		cout << "画一个矩形" << endl;
	}
};
int main()
{
	/*Shape* ptr = new Circle();
	ptr->draw();
	delete ptr;
	ptr = nullptr;*/
	Circle c;
	Rectangle r;
	Shape s;
	Shape* p[] = { &c,&r,&s };
	for (int i = 0; i < 3; i++)
	{
		p[i]->draw();
	}
	return 0;
}

运行结果如下图:

 

菱形继承与虚继承

继承中还有一个特殊的继承,菱形继承

当出现一个派生类从多个基类继承,而这些基类又有共同的基类时,就可能出现菱形继承。

这可能导致数据冗余和二义性问题。为了解决这个问题,我们可以使用虚继承技术。所谓虚继承实际上是在派生类中多了一个存储结构,叫虚基表,里面存放的是基类的成员函数地址,派生类实例化对象时,不同基类中的重复成员在派生类对象内存中只有一份地址,这使得重复的成员在派生类中只存在一份实例,避免了重复。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值