当前位置: 首页 > article >正文

菱形继承的类对父类的初始化、组合、多态、多态的原理等的介绍

文章目录

  • 前言
  • 一、菱形继承的类对父类的初始化
  • 二、组合
  • 三、 多态
    • 1. 构成多态
    • 2. 虚函数
    • 3. 虚函数的重写
    • 4. 虚函数重写的两个例外
      • 1. 协变
      • 2. 析构函数的重写
    • 5. C++11 final 和 override
      • 1. final
      • 2. override
    • 6. 设计不想被继承的类
    • 7. 重载、覆盖(重写)、 隐藏(重定义)的对比
  • 四、多态的原理
  • 总结


前言

菱形继承的类对父类的初始化、组合、多态、多态的原理等的介绍


一、菱形继承的类对父类的初始化

#include<iostream>
#include <string>
using namespace std;

class A
{
public:
	A(const char* A)
		:_a(A)
	{
		cout << "class A" << endl;
	}
	string _a;
};

class B : virtual public A
{
public:
	B(const char* A, const  char* B)
		:A(A)
		,_b(B)
	{
		cout << "class B" << endl;
	}
	string _b;
};

class C : virtual public A
{
public:
	C(const char* A, const char* C)
		:A(A)
		,_c(C)
	{
		cout << "class C" << endl;
	}
	string _c;
};

class D : public B, public C
{
public:
	D(const char* A, const char* B, const char* C, const char* D)
		: A(A)
		, B(A, B)
		, C(A, C)
		, _d(D)
	{
		cout << "class D" << endl;
	}
	string _d;
};


int main()
{

	D d("class A", "class B", "class C", "class D");
	
	return 0;
}

结构为:
在这里插入图片描述

  • 因为D类有如上的结构,所以A类会在D类中调用构造函数初始化,并且只调用一次,在D类中初始化B类和C类时,传入的A类不调用A类的构造函数初始化。

在这里插入图片描述

二、组合

  • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

优先使用对象组合,而不是类继承。

#include <iostream>
using namespace std;

class A
{
protected:
	int _a;
};

class B : public A
{
protected:
	A _bb;
};

int main()
{

	return 0;
}

三、 多态

1. 构成多态

构成多态需要两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
// 构成多态
#include<iostream>
using namespace std;

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---半价" << endl;
	}
};

//void fun(Person& p)
//{
//	p.BuyTicket();
//}

void fun(Person* p)
{
	p->BuyTicket();
}

int main()
{
	Person p;
	fun(&p);

	Student s;
	fun(&s);

	return 0;
}

在这里插入图片描述

2. 虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---全价" << endl;
	}
};

3. 虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

在重写基类的虚函数的时候,虽然派生类的虚函数不写virtual也可以构成重写, 但是不建议这样使用

#include<iostream>
using namespace std;

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---半价" << endl;
	}
};

4. 虚函数重写的两个例外

1. 协变

基类与派生类虚函数返回值类型不同。

简单来讲就是 基类与派生类的虚函数的返回值构成父子类指针或引用关系。

#include <iostream>
using namespace std;

class A {};
class B :public A {};

class Person
{
public:
	virtual A* BuyTicket()
	{
		cout << "购票---全价" << endl;
		return new A;
	}
};

class Student : public Person
{
public:
	virtual B* BuyTicket()
	{
		cout << "购票---半价" << endl;
		return new B;
	}
};

void fun(Person& p)
{
	p.BuyTicket();
}


int main()
{
	Person p;
	fun(p);
	Student s;
	fun(s);
	return 0;
}
  • 基类和派生类构成父子类指针的关系

在这里插入图片描述

2. 析构函数的重写

虽然基类和派生类的析构函数的函数名不同,但是编译器会将析构函数的函数名,都处理成destructor,因此可以构成重写。

一般情况下,应该将派生类的析构函数与基类析构函数构成函数重写,使下面的情况delete可以实现多态,保证指向的对象正确调用析构函数。

析构函数可以构成虚函数重写吗, 为什么要构成虚函数重写?

  1. 析构函数加virtual会构成析构函数,因为编译器会将析构函数的名字统一命名为destructor
  2. 构成虚函数重写是因为,我们new一个派生类对象的空间,但是用基类的类型指针接收
  3. 在析构这个对象时,只会进行普通调用,普通调用会按照当前类型, 则只会调用基类的析构函数
  4. 这种情况,我们希望是一个多态调用,按照指向的类型调用析构函数,就需要构成虚函数的重写。
#include <iostream>
using namespace std;


class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---全价" << endl;
	}

	 virtual ~Person()
	{
		cout << " ~Person() " << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---半价" << endl;
	}

	virtual ~Student()
	{
		cout << " ~Student() " << endl;
	}
};

int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;

	delete p1;
	delete p2; // p2->destuctor() + operator delete(p)

	// 这里我们期望是一个多态调用,而不是普通调用

	p1 = nullptr;
	p2 = nullptr;

	return 0;
}

在这里插入图片描述

5. C++11 final 和 override

1. final

final 修饰的虚函数不能被重写

在这里插入图片描述

final修饰的类,不能被当做基类, 不能被继承

在这里插入图片描述

2. override

检查派生类中的虚函数与基类中虚函数是否构成重写,若不构成重写则报错。

#include <iostream>
using namespace std;


class Person
{
public:
	virtual void BuyTicket()
	{}

};

class Student : public Person
{
public:
	// 检查派生类中的虚函数与基类中的虚函数是否构成重写
	virtual void BuyTicket()override
	{
		cout << "购票---半价" << endl;
	}
};


int main()
{
	Person p;

	return 0;
}

在这里插入图片描述

6. 设计不想被继承的类

将构造函数私有或者将析构函数私有

将构造函数私有

#include <iostream>
using namespace std;

class A
{
public:
	static A CreateObj()
	{
		return A();
	}
private:
	A() {}

};

int main()
{
	A::CreateObj();
	return 0;
}

在这里插入图片描述

7. 重载、覆盖(重写)、 隐藏(重定义)的对比

在这里插入图片描述

四、多态的原理

普通调用在编译时地址就确定了
多态调用在程序运行时,到指向对象的虚函数表中找函数的地址

#include <iostream>
using namespace std;

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---全价" << endl;
	}

	int _a = 0;

};

class Student: public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "购票---半价" << endl;
	}

	int _b = 1;
};

// 普通调用
//void fun(Person p)
//{
//	p.BuyTicket();
//}

// 多态调用
void fun(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person p;

	Student s;


	return 0;
}

在这里插入图片描述


总结

菱形继承的类对父类的初始化、组合、多态、多态的原理等的介绍


http://www.kler.cn/news/323955.html

相关文章:

  • C#基础:掌握控制流语句,构建灵活的程序逻辑
  • Python中的“属性与方法”:解锁面向对象编程的秘密
  • 2024年9月25日,Intel发布至强6900P系列:128核心504MB缓存,终于追上AMD!
  • 跨多场景帧重建DENSER:使用小波估计进行城市动态场景重构
  • 机器学习:探索未知边界,解锁智能潜力
  • 华为-单臂路由
  • 服务运营 | 运营前沿:生成式AI改变医疗保健的运作方式
  • SignApp签名工具/美淘iOS在线签名工具/后端PHP
  • MATLAB与Docker Compose:实现微服务API文档的自动化部署与Vue.js集成
  • 算法分类自动驾驶主要算法介绍
  • 三分钟让你掌握PDF转音频:PDF2Audio背后的秘密
  • 2016年国赛高教杯数学建模C题电池剩余放电时间预测解题全过程文档及程序
  • easyexcel常见问题分析
  • html怎么让字体变颜色
  • Android (rust) vulkan (JNI) 画一个三角形: VulkanSurfaceView 初始化
  • ceph rgw 桶分片之reshard
  • 华为GaussDB数据库之Yukon安装与使用
  • 自動獲取IP地址和寬頻撥號上網的詳細指南
  • 828华为云征文|部署个人知识管理系统 SiyuanNote
  • Linux下C开发使用小技巧
  • _RET_IP_ 和_THIS_IP_ 作用
  • cesium的学习过程和使用案例
  • 闲盒支持的组网方式和注意事项
  • gitlab使用小结
  • 原宝,四周年快乐!
  • leetcode621. 任务调度器
  • Linux系统使用iptables配置入站端口
  • 教师工作量|基于springBoot的教师工作量管理系统设计与实现(附项目源码+论文+数据库)
  • shell脚本定时任务通知到钉钉
  • 使用yum为centos系统安装软件以及使用(包含阿里云yum源配置)