c++个人学习笔记——类继承

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_38427857/article/details/88206810

类继承

面对对象过程编程的主要目的之一是提供可重用的代码块。当项目十分庞大时,对已通过测试的代码进行重用比重新编写代码更能提供效率,节省时间。
c++通过扩展和修改类来提高代码的重用性,这种方法叫类继承。从已有的类(基类)派生出新的类(派生类),派生类继承了基类的所有特性(成员变量、方法等),并且还可以在此基础上添加特有的特性完成对基类的拓展。这种继承的方式比重新设计一个与基类相似的类更方便,提高写代码的效率。

class animal
{
	public:
		animal(int Size,int Foot):size(Size),foot(Foot)
		{
			std::cout << "animal的构造函数被调用了" << std::endl;
		}
		void getsize()
		{
			std::cout << size << std::end;
		}
		virtual void cry()
		{
			std::cout << "" << std::end;
		}
		virtual ~animal()
		{
			std::cout << "animal被析构了" << std::endl;
		}
	private:
		int size;
		int foot;
}

这是一个名为animal的基类,然后需要写一个名为dog的类,而这个dog类拥有animal类的所有特性并且在此加上了成员变量tail以及对cry()方法进行修改。因此,可以通过继承animal来获得其特性。

class dog : public animal
{
	public:
		dog(int Size,int Foot,int Tail) : animal(Size,Foot)
		{
			Tail = tail;
			std::cout << "dog的构造函数被调用了" << std::endl;
		}
		virtual void cry()
		{
			std::cout << "woowoo" << std::end;
		}
		void run(){}
		virtual ~dog()
		{ 
			std::cout << "dog被析构了" << std::endl;
		}
	private:
		int tail; 
}

这里dog类对方法cry()进行了改写,并增加了方法run()和变量tail,其他方法和变量则完全继承于animal类。
这样只需要对修改的方法进行重新设计,并增添新的成员变量或方法,从而免去了对重复代码的编写。

animal(int Size,int Foot):size(Size),foot(Foot)
{
	std::cout << "animal的构造函数被调用了" << std::endl;
}
dog(int Size,int Foot,int Tail) : animal(Size,Foot)
{
	Tail = tail;
	std::cout << "dog的构造函数被调用了" << std::endl;
}

这里可以看出派生类的构造函数与基类的构造函数的有所不同。这是由于创建派生类对象时,程序需要首先创建基类对象。
下面运行一段测试用例

#include<iostream>
#include"animal.h"
int main()
{
	using std::cout;
	using std::endl;
	animal a(2, 2);
	dog b(4, 3, 1);
	return 0;
}

运行结果
在这里插入图片描述
从这里可以看出,animal的构造函数被调用了2次,然而测试用例中只对初始化了一个animal对象。程序在创建一个dog对象的时候也创建了一个animal对象。派生类所继承的成员变量的初始化是通过创建一个基类对象来进行的。
而在派生类的析构函数被调用后,也会调用一次基类的析构函数以将创建派生类对象的同时创建的基类对象析构掉。

is-a关系

由于派生类继承了基类的所有特性,因此派生类 is a 基类。如在程序中,dog继承了animal,那么dog就是一个animal。is-a关系是单向的,因此并不存在基类 is a 派生类的关系。基于这种is-a关系,可以用指向基类类型的指针或基类类型的引用来指向一个派生类对象。
如:

dog a(231);
animal * b = a; 

由此可以得出一种用一个指向基类的数组指针来存储基类和派生类对象的应用
如:

dog d1(231);
dog d2(231);
dog d3(231);
animal a1(23);
animal a2(23);
animal a3(23);
animal * a[6];
a[0] = &d1;
a[1] = &d2;
a[2] = &d3;
a[3] = &a1;
a[4] = &a2;
a[5] = &a3;

virtual关键字

virtual关键字声明了一个函数为虚函数。当使用一个基类指针或基类引用指向一个派生类对象时,通过这个指针或引用来调用重写函数如cry(),虚函数将指明调用的是dog类(派生类)的cry()而不是animal类(基类)的cry()。若派生类中没有对虚函数进行重定义,则调用基类的版本。

dog a(231);
animal * b = a; 
b->cry();            //调用dog::cry()

而如果cry()函数没有用virtual标记,则通过基类指针或基类应用来调用重写函数时,将被调用的是基类的函数。

dog a(231);
animal * b = a; 
b->cry();            //调用animal::cry()

如果是通过对象来调用重写函数则不会出现上述的情况。

dog a(231);
animal b(21); 
a->cry();            //调用dog::cry()
b->cry();            //调用animal::cry()

因此,一般都会用virtual关键字标记重写函数,以避免上述的第二种情况。
而析构函数一般都会标上virtual,因为如果析构函数中涉及到释放内存的操作时,没有标上virtual,可能会出现析构函数调用顺序错乱而引发内存释放不完全或者重复释放同一块内存的错误。

抽象基类ABC

拥有一个纯虚函数的基类就称为抽象基类。抽象基类与基类的最大不同在于,抽象基类不能创建对象,因为抽象基类的纯虚函数可以只提供接口而不实现。抽象基类就相当于一个基础模板,其派生类将继承他的所有特性并实现那些未实现的接口,按需要进行扩展,成为一个完整的类。
纯虚函数相比虚函数的不同在于,在函数声明的末尾加上了=0,以及可以只声明和不定义其实现。

virtual void cry()=0

protected控制访问

对于在类外,protected与private相似,只能通过public成员来对protected成员进行访问。
而在类内,派生类可以直接访问基类的protected成员

三种继承方式

public继承:所有成员访问权限不变
protected继承:将public成员变为protected成员,private成员不变
private继承:将所有成员访问权限改为private

展开阅读全文

没有更多推荐了,返回首页