C++学习记录4——继承和多态

5 篇文章 0 订阅

一、继承

继承可减少代码重复,提高利用率

1.继承的基本语法

语法:class 子类:继承方式 父类
子类也称派生类,父类也称基类
派生类中的成员,一部分从基类继承(共性),一部分自己新增(个性)

class Basepage
{
public:
	void header()
	{
		cout << "header" << endl;
	}
	void footer()
	{
		cout << "footer" << endl;
	}
	void left()
	{
		cout << "left" << endl;
	}
};
class A :public Basepage  //继承方式为public
{
public:
	void test1()
	{
		cout << "1" << endl;
	}
};
class B :public Basepage
{
public:
	void test2()
	{
		cout << "2" << endl;
	}
};
int main()
{
	A a;
	a.footer();
	a.test1();
	B b;
	b.header();
	b.test2();
	return 0;
}

2.继承方式

  1. 公共继承:父类的公共、保护对应在子类中仍为公共、保护权限
  2. 保护继承:父类的公共、保护在子类中都为保护权限
  3. 私有继承:父类的公共、保护在子类中都变为私有权限

父类中的私有属性,子类无法访问

3.继承中的对象模型

子类继承时,会将父类的全部的非静态成员属性都继承下来保留,父类中的私有属性被编译器隐藏,子类无法访问,但确实被继承了。

class Basepage
{
public:
	int m_a;
protected:
	int m_b;
private:
	int m_c;
};
class A :public Basepage
{
public:
	int m_d;
};
int main()
{
	A a;
	return 0;
}

验证:利用VS自带的开发人员命令提示符工具(在开始菜单中)
在这里插入图片描述
打开
在这里插入图片描述
打开文件所在的文件夹,复制文件路径,若文件不在默认的C盘,需跳转盘符(如输入F:跳转到F盘)
输入cd 文件路径,回车,跳转文件路径
输入dir,查看当前目录下的内容
在这里插入图片描述
查看命名cl /d1 reportSingleClassLayout类名 文件名
报告单个类的布局(reportSingleClassLayout)

如下图可看到类的占用空间大小和继承父类的、自身创建的属性情况
在这里插入图片描述

4.继承中的构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数
先构造父类,再构造子类,先析构子类,再析构父类

5.继承中同名成员的处理方式

对于继承的同名成员
访问子类中的同名成员,直接访问即可
访问父类中的同名成员,要加作用域

class Basepage
{
public:
	int m_a=10;
};
class A :public Basepage
{
public:
	int m_a=100;  //与父类对象同名
};
int main()
{
	A a;
	cout<<"A m_a"<<a.m_a<<endl;  //直接调用子类
	cout<<"Basepage m_a"<<a.Basepage::m_a<<endl;  //此处加作用域
	return 0;
}

如果子类中出现和父类同名的成员函数,子类的成员会隐藏掉父类所有的同名成员函数(包括重载的函数),想访问父类中被隐藏的同名成员函数,都要加作用域

class Basepage
{
public:
	void fun(){}
	void fun(int a){}  //重载的成员函数
};
class A :public Basepage
{
public:
	void fun(){} //与父类对象同名
};
int main()
{
	A a;
	a.fun();  //默认调用子类函数
	a.fun(10);  //错误
	a.Basepage::fun(10);  //调用重载的父类函数
	return 0;
}

6.继承同名静态成员的处理方式

与上相同,注意两种访问方式
访问子类中的同名成员,直接访问即可
访问父类中的同名成员,要加作用域
通过对象访问时完全相同
要注意静态成员可通过类名访问

A::m_a  //通过子类名访问子类中m_a
Basepage::m_a  //通过父类名访问父类中m_a
A::Basepage::m_a  //通过子类名访问父类作用域下父类中m_a

7.多继承语法

C++中一个类可继承多个类
class 子类:继承方式 父类1,继承方式 父类2
多继承可能引发父类中有同名函数出现,需要加作用域区分
实际开发中不建议使用多继承

8.菱形继承(钻石继承)

概念:
两个子类继承同一个父类,又有某个类同时继承了上述两个子类,这种继承称为菱形继承或钻石继承
在这里插入图片描述
PS:类比马、驴和骡子似乎更适合……

动物 骡子 动物的数据 动物的数据
  1. 两父类拥有相同数据,可以通过加作用域区分,但有两个数据,资源浪费且无意义
  2. 利用虚继承(继承之前加关键字virtual),则Animal类(最大)称为虚基类,可解决菱形继承问题
class Animal{};
class Sheep:virtual public Animal{};
class Tuo:virtual public Animal{};
class SheepTuo:public Sheep, public Tuo{};

在这里插入图片描述

虚基类指针(vbptr)指向虚基类表(vbtable),继承虚基类指针,通过虚基类表中的偏移量找到数据,数据只保留一份

二、多态

1.多态基本概念

多态分两类,多态一般指动态多态

  • 静态多态:函数重载和运算符重载,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定——编译阶段确定函数地址
  • 动态多态的函数地址晚绑定——运行阶段确定函数地址

编译阶段确定函数地址

class Animal
{
public:
	void speak();
};
class Cat:public Animal
{
public:
	void speak();
};
void dospeak(Animal &animal)  //允许Animal &animal = cat
{
	animal.speak();  //地址早绑定,会执行Animal中的speak()
}
int main()
{
	Cat cat;
	dospeak(cat);  //C++允许父子类之间类型自动转换
}

运行阶段确定函数地址

此时的speak()调用由传入的对象类型确定

class Animal
{
public:
//此处加virtual称为虚函数
	virtual void speak();  //可实现地址晚绑定
};
class Cat:public Animal
{
public:
	//此处重写speak()函数
	void speak();  //此句前virtual可加可不加
};
void dospeak(Animal &animal)  //用父类指针或引用执行子类对象
{
	animal.speak();  
}
int main()
{
	Cat cat;     //传入sheep1对象
	dospeak(cat);  //就执行Sheep中的speak()
}

动态多态要满足的条件:

  1. 有继承关系
  2. 子类重写父类的虚函数(重写是对完全相同的函数写入不同内容)

动态多态的使用:
用父类指针或引用指向子类对象

2.多态的底层原理

此时同空类,占1个字节空间

class Animal
{
public:
	void speak();
};

加virtual后占4个字节

class Animal
{
public:
	virtual void speak();  
};

占4个字节的其实是一个指针:
虚函数(表)指针(vfptr),指向虚函数表(vftable),表内记录虚函数地址
在这里插入图片描述
子类重写虚函数之前:
在这里插入图片描述
子类重写虚函数之后:
在这里插入图片描述

3.多态的优点与实例

多态的优点

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展和维护

实例:计算器

普通实现计算器,代码量相对较少,但想扩展新的功能要修改源码,在真实开发中提倡开闭原则:对扩展进行开放,对修改进行关闭。

//普通实现计算器操作
class Calculator
{
public:
	int getResult(string oper)
	{
		if (oper == "+")
		{
			return m_num1 + m_num2;
		}
		else if (oper == "-")
		{
			return m_num1 - m_num2;
		}
		else if (oper == "*")
		{
			return m_num1 * m_num2;
		}//想扩展新的功能要修改源码
		 //在真实开发中提倡开闭原则:对扩展进行开放,对修改进行关闭
	}
	int m_num1;
	int m_num2;

};

void test02()
{
	//创建计算器对象
	Calculator c;
	c.m_num1 = 10;
	c.m_num2 = 10;
	cout << c.m_num1 << "+" << c.m_num2 << "=" << c.getResult("+") << endl;
	cout << c.m_num1 << "-" << c.m_num2 << "=" << c.getResult("-") << endl;
	cout << c.m_num1 << "*" << c.m_num2 << "=" << c.getResult("*") << endl;
}
int main()
{
	test02();
	return 0;
}

多态实现计算器,代码量相对大,但体现多态的优点,提倡用多态设计程序架构。

//利用多态实现计算器操作
//实现计算器的抽象类
class AbstractCalculator
{
public:
	virtual int getResult()
	{
		return 0;
	}

	int m_Num1;
	int m_Num2;
};
//加法类
class Add : public AbstractCalculator
{
public:
	virtual int getResult()
	{
		return m_Num1 + m_Num2;
	}
};
//减法类
class Sub : public AbstractCalculator
{
public:
	virtual int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
//乘法类
class Mul : public AbstractCalculator
{
public:
	virtual int getResult()
	{
		return m_Num1 * m_Num2;
	}
};
//扩展只需继续向下添加即可

void test03()//此处三类代码类似,可另写成函数,传入参数(AbstractCalculator* abc,string fun) ,减少代码量
{
	//加法
	AbstractCalculator* abc = new Add;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
	//用完要释放
	delete abc;
	//减法
	abc = new Sub;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
	delete abc;
	//乘法
	abc = new Mul;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
	delete abc;
}
int main()
{
	test03();
	return 0;
}

4.纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可将虚函数改为纯虚函数
语法:virtual 返回值类型 函数名 (参数列表) = 0;

当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

目的:倒逼子类重写纯虚函数实现多态

5.虚析构和纯虚析构

多态使用时,父类指针在析构时,不会调用子类的析构函数。若子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或纯虚析构

虚析构和纯虚析构的共性

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构的区别

  • 如果是纯虚析构,该类就属于抽象类,无法实例化对象

虚析构语法:virtual ~类名(){}(析构函数前加virtual)
纯虚析构语法:
类内声明:virtual ~类名() = 0;
类外实现析构函数:类名::~类名(){析构代码}(第一个类名表明作用域,第二个类名即为析构函数名)

注意:此处的纯虚析构既需要在父类中声明,也需要有具体的实现,因为父类中也可能有属性开辟堆区,需要执行析构代码释放

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值