C++ 类的组合

news2025/1/21 12:23:26

解决复杂问题的有效方法就是将其层层分解为简单问题的组合,首先解决简单问题,复杂问题也就迎刃而解了。实际上,这种部件组装的生产方式广泛应用在工业生产中。例如,电视机的一个重要部件是显像管,但很多电视机厂自己并不生产显像管,而是向专门的显像管生产厂去购买。生产显像管的厂家会同时向多家电视机厂供货。这样专业化分工合作,极大提高了生产效率。
在面向对象程序设计中,可以对复杂对象进行分解、抽象,把一个复杂对象分解为简单对象的组合,由比较容易理解和实现的部件对象装配完成。

1.组合

下面创建一个圆类:

class Circle//定义类Cricle及其数据和方法
{
public:       //外部接口
	Circle(float r){}//构造函数
	float circumference();//计算圆的周长
	float area();//计算圆的面积
private://私有数据成员
	float r;//圆的半径
};

可以看到,类Circle中包含着float类型的数据,这样用C++的基本数据类型作为类的组成部件。实际上类的成员数据既可以是基本类型也可以是自定义类型,当然也可以是类的对象。由此便可以采用部件组装的方法,利用已有类的对象来构建新类,这些部件类比起整体类来说,更易于设计和实现。

类的组合描述的就是一个类内嵌其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。

**当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将首先被自动创建。**因为部件对象是复杂对象的一部分,因此,在创建对象时既要对本类的基本类型数据成员进行初始化,又要对内嵌对象成员进行初始化

理解所创建的对象和其内嵌对象的构造函数被调用的顺序

组合类构造函数定义的一般形式为:

类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),...
{类的初始化}

其中,“内嵌对象1(形参表),内嵌对象2(形参表),…”称为初始化列表,其作用是对内嵌对象进行初始化。
对基本类型的数据成员也可以这样初始化:

class Circle//定义类Cricle及其数据和方法
{
public:       //外部接口
	Circle(float r)radius(r){}//构造函数,成员初始化列表
	float circumference();//计算圆的周长
	float area();//计算圆的面积
private://私有数据成员
	float radius;//圆的半径
};

在创建一个组合类的对象时,不仅它自身的构造函数的函数体被执行,而且还调用其内嵌对象的构造函数。这时构造函数的调用顺序如下:
(1)调用内嵌对象的构造函数,调用顺序按照内嵌对象在组合类的定义中出现的顺序。注意,内嵌对象在构造函数的初始化列表中出现的顺序与内嵌对象构造函数的调用顺序无关。
(2)执行本类构造函数的函数体。
【提示】
如果有些内嵌对象没有出现在构造函数的初始化列表中,那么在第(1)步,该内嵌对象的默认构造函数将被执行。这样,如果一个类存在内嵌对象,尽管编译系统自动生成的隐含默认构造函数的函数体为空,但在执行默认构造函数时,如果声明组合类的对象没有指定对象的初始值,则默认形式(无形参)的构造函数被调用,这时内嵌对象的默认形式构造函数也会被调用。这时隐含的默认构造函数并非什么也不做。
【注意】
有些数据成员的初始化,必须在构造函数的初始化列表中进行。这些数据成员包括两类,一类是那些没有默认构造函数的内嵌对象——因为这类对象初始化时必须提供参数,另一类是引用类型的数据成员——因为引用型变量必须在初始化时绑定引用的对象。如果一个类包括着两类成员,那么编译器不能够为这个类提供隐含的默认构造函数,这时必须编写显式的构造函数,并且在每个构造函数的初始化列表中至少为这两类数据成员初始化。
**析构函数的调用执行顺序与构造函数相反。**析构函数的函数体被执行完毕后,内嵌对象的析构函数被一 一执,这些内嵌对象的析构函数调用顺序与它们在组合类的定义中出现的顺序相反。
【提示】
由于要调用内嵌函数的析构函数,所以隐含的析构函数并非什么也不做。
当存在组合类关系时,拷贝构造函数如何编写:
对于一个类,如果程序员没有编写拷贝构造函数,编译系统会在必要时自动生成一个隐含的拷贝构造函数,这个隐含的拷贝构造函数会自动调用内嵌对象的拷贝构造函数,为各个内嵌对象初始化。
如果要为组合类编写拷贝构造函数,则需要为内嵌成员对象的拷贝构造函数传递参数。
【例】假设C类中包含B类的对象b作为成员,C类的拷贝构造函数形式如下:

C::C(C&c1):b(c1.b){...}

【例题】我们使用一个类Line来描述线段,使用另一个类Point的对象来表示端点。用组合类来解决,使Line类包括Point类的两个对象p1和p2,作为其数据成员。Line类具有计算线段长度的功能,在构造函数中实现。

//类Point的定义
class Point
{
public:
	Point(int xx = 0, int yy = 0)
	{
		x = xx;
		y = yy;
	}
	Point(Point& p);
	int getX()
	{
		return x;
	}
	int getY()
	{
		return y;
	}
private:
	int x, y;
};

//Point拷贝构造函数的实现
Point::Point(Point& p)
{
	x = p.x;
	y = p.y;
	cout << "调用Point的拷贝构造函数" << endl;	
}

//Line类的定义
class Line
{
public:
	Line(Point xp1, Point xp2);
	Line(Line& l);
		double getLen()
	{
		return len;
	}
private:
	Point p1, p2;//Point类的对象p1,p2
	double len;
};

//组合类的构造函数Line的实现
Line::Line(Point xp1, Point xp2):p1(xp1),p2(xp2)
{
	cout << "调用Line的构造函数" << endl;
	double x = static_cast<double>(p1.getX() - p2.getX());
	double y = static_cast<double>(p1.getY() - p2.getY());
	len = sqrt(x * x + y * y);
}

//组合类的拷贝构造函数的实现
Line::Line(Line& l) :p1(l.p1), p2(l.p2)
{
	cout << "调用Line的拷贝构造函数" << endl;
	len = l.len;
}

//主函数
int main()
{
	Point myp1(1, 1), myp2(4, 5);//建立Point类的对象myp1,myp2
	Line line1(myp1, myp2);//建立Line类的对象line1
	Line line2(line1);  //利用拷贝构造函数用旧对象line1建立一个新对象line2
	cout << "两点之间的距离为:" ;
	cout << line1.getLen() << endl;
	cout << "两点之间的距离为:" ;
	cout << line2.getLen() << endl;
	return 0;
}

运行结果:
在这里插入图片描述
结果分析:
主程序在执行时,首先生成两个Point类的对象myp1和myp2,然后在构造Line类的对象line1,接着通过拷贝构造函数建立Line类的第二个对象line2,最后输出两点之间的距离。在整个运行过程中,Point类的拷贝构造函数被调用了6次,而且都是在Line类构造函数体运行之前进行的,它们分别是两个对象在Line类构造函数进行函数参数形实结合时,初始化内嵌对象时,以及拷贝构造line2时被调用的。两点的距离在Line类的构造函数中求得,存放在其私有数据成员len中,只能通过公有成员函数getLen()来访问。
在这里插入图片描述

2.前向引用声明

C++的类应当先定义,再使用,但是在处理复杂问题、考虑到组合时,很可能遇到两个类相互引用的情况,这种情况也称为循环依赖。例如:

class A   //A类的定义
{
public:   //外部接口
	void f(B b);  //以B类对象b为形参的成员函数
};
class B  //B类的定义
{
public:  //外部接口
	void g(A a);  //以A类对象a为形参的成员函数
};

这里类A的公有成员函数f的形参是B类的对象,同时B的公有成员函数g也以A类的对象为形参。由于在使用一个类之前,必须首先定义该类,因此无论将哪一个类的定义放在前面都会引起编译错误。解决这种问题的办法就是使用前向引用声明。前向引用声明,是在引用未定义的类之前,将该类的名字告诉编译器,使编译器知道那是一个类名。这样当程序中使用这个类名时,编译器就不会认为时错误的,而类的完整定义可以在程序的其他地方。
在上述程序中加上下面的前向引用声明问题就解决了:

class B;   //前向引用声明
class A   //A类的定义
{
public:   //外部接口
	void f(B b);  //以B类对象b为形参的成员函数
};
class B  //B类的定义
{
public:  //外部接口
	void g(A a);  //以A类对象a为形参的成员函数
};

使用前向引用声明虽然可以解决一些问题,但它并不是万能的。需要注意的是,尽管使用了前向引用声明,但是在提供一个完整的类定义之前,不能定义该类的对象,也不能在内联函数中使用该类的对象。如下:

class Fred;   //前向引用声明
class Barney
{
	Fred x;  //error  类Fred的定义尚不完善
};
class Fred
{
	Barney y;
};

对于这段程序,编译器指出错误。原因是对类名Fred的前向引用声明只能说明Fred是一个类名,而不能给出该类的完整定义,因此在类Barney中就不能定义Fred的数据成员。因此使两个类以彼此的对象为数据成员是不合法的。
再看下面这段程序:

class Fred;   //前向引用声明
class Barney
{
public:
	void method()
	{
		x.yabbaDabbaDo();//error Fred类的对象在定义前被使用
	}
private:
	Fred& x;  //正确 结果前向引用声明,可以声明Fred类的对象引用或指针
};
class Fred
{
public:
	void yabbaDabbaDo();
	Barney& y;
};

编译器在编译时会指出错误,因为在类Barney的内联函数中使用了由x所指向的Fred类的对象,而此时Fred类尚未被完整定义。解决这个问题的方法是,更改这两个类的定义顺序,或者将函数method()改为非内联形式,并且在类Fred的完整定义之后,再给出函数的定义。
【注意】使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/812990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ARM裸机-7

1、S5PV210的地址映射 1.1、什么是地址映射 S5PV210属于ARM Cortex-A8架构&#xff0c;32位CPU&#xff0c;CPU设计时就有32根地址线&32根数据线。32根地址线决定了CPU的地址空间为4G&#xff0c;那么这4G空间如何分配使用&#xff1f;这个问题就是地址映射问题。 1.2、S…

AnimateDiff论文解读-基于Stable Diffusion文生图模型生成动画

文章目录 1. 摘要2. 引言3. 算法3.1 Preliminaries3.2. Personalized Animation3.3 Motion Modeling Module 4. 实验5.限制6. 结论 论文&#xff1a; 《AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning》 github: https://g…

高级 IO

目录 前言 什么是IO&#xff1f; 有哪些IO的的方式呢&#xff1f; 五种IO模型 这五种模型在特性有什么差别呢&#xff1f; 其他高级IO 非阻塞IO fcntl 实现函数SetNonBlock I/O多路转接之select 初识select select函数 参数说明&#xff1a; 关于timeval结构 函数…

【解惑笔记】树莓派+OpenCV+YOLOv5目标检测(Pytorch框架)

【学习资料】 子豪兄的零基础树莓派教程https://github.com/TommyZihao/ZihaoTutorialOfRaspberryPi/blob/master/%E7%AC%AC2%E8%AE%B2%EF%BC%9A%E6%A0%91%E8%8E%93%E6%B4%BE%E6%96%B0%E6%89%8B%E6%97%A0%E7%97%9B%E5%BC%80%E6%9C%BA%E6%8C%87%E5%8D%97.md#%E7%83%A7%E5%BD%95…

【多线程中的线程安全问题】线程互斥

1 &#x1f351;线程间的互斥相关背景概念&#x1f351; 先来看看一些基本概念&#xff1a; 1️⃣临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源。2️⃣临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区。3️⃣互斥&…

python与深度学习(十一):CNN和猫狗大战

目录 1. 说明2. 猫狗大战2.1 导入相关库2.2 建立模型2.3 模型编译2.4 数据生成器2.5 模型训练2.6 模型保存2.7 模型训练结果的可视化 3. 猫狗大战的CNN模型可视化结果图4. 完整代码5. 猫狗大战的迁移学习 1. 说明 本篇文章是CNN的另外一个例子&#xff0c;猫狗大战&#xff0c…

建立动态数组,输入5个学生的,另外用一个函数检查其中有无低于60分的,输出不合格的成绩。

题为c程序设计&#xff08;第五版&#xff09;谭浩强 例8.30 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 这篇博客&#xff0c;让我们一起来学习内存的动态分配。 那么&#xff0c;什么是内存的动态分配呢&#xff1f;C语言允许建立…

RS485或RS232转ETHERCAT连接ethercat转换器

最近&#xff0c;生产管理设备中经常会遇到两种协议不相同的情况&#xff0c;这严重阻碍了设备之间的通讯&#xff0c;串口设备的数据不能直接传输给ETHERCAT。这可怎么办呢&#xff1f; 别担心&#xff0c;捷米JM-ECT-RS485/232来了&#xff01;这是一款自主研发的ETHERCAT从站…

FreeRTOS源码分析-7 消息队列

目录 1 消息队列的概念和作用 2 应用 2.1功能需求 2.2接口函数API 2.3 功能实现 3 消息队列源码分析 3.1消息队列控制块 3.2消息队列创建 3.3消息队列删除 3.4消息队列在任务中发送 3.5消息队列在中断中发送 3.6消息队列在任务中接收 3.7消息队列在中断中接收 1 消…

【RTT驱动框架分析03】- sfus flash 操作库的分析和基于STM32F103RCT6+CUBEMX的SFUS移植教程

sfus flash 操作库的分析 sfus 抽象 /*** serial flash device*/ typedef struct {char *name; /**< serial flash name */size_t index; /**< index of flash device information table see flash_…

filter shape、padding、strides三者之间的关系

filter shape 在深度学习中&#xff0c;“filter shape”&#xff08;滤波器形状&#xff09;指的是卷积神经网络中滤波器&#xff08;也称为卷积核&#xff09;的维度或大小。滤波器是用于在卷积层中提取特征的重要组件。 滤波器形状通常是一个四维张量&#xff0c;具体取决…

小研究 - 一种复杂微服务系统异常行为分析与定位算法(二)

针对极端学生化偏差&#xff08;&#xff25;&#xff58;&#xff54;&#xff52;&#xff45;&#xff4d;&#xff45; &#xff33;&#xff54;&#xff55;&#xff44;&#xff45;&#xff4e;&#xff54;&#xff49;&#xff5a;&#xff45;&#xff44; &#…

并发编程 - CompletableFuture

文章目录 Pre概述FutureFuture的缺陷类继承关系功能概述API提交任务的相关API结果转换的相关APIthenApplyhandlethenRunthenAcceptthenAcceptBoththenCombinethenCompose 回调方法的相关API异常处理的相关API获取结果的相关API DEMO实战注意事项 Pre 每日一博 - Java 异步编程…

DPN(Dual Path Network)网络结构详解

论文&#xff1a;Dual Path Networks 论文链接&#xff1a;https://arxiv.org/abs/1707.01629 代码&#xff1a;https://github.com/cypw/DPNs MXNet框架下可训练模型的DPN代码&#xff1a;https://github.com/miraclewkf/DPN 我们知道ResNet&#xff0c;ResNeXt&#xff0c;D…

面向对象【对象数组的使用与内存分析、方法重载、可变个数形参】

文章目录 对象数组实例对象内存分析 方法的重载重载方法调用打印方法的重载 可变个数形参的方法特点传递任意数量的参数与其他参数共存传递数组或多个参数 对象数组 存储对象引用的数组。它允许您在单个数组中存储多个对象&#xff0c;并通过索引访问和操作这些对象。 实例 创…

使用SpringBoot+SpringMVC+Mybatis+Redis实现个人博客管理平台

文章目录 前言1. 项目概述2. 项目需求2.1功能需求2.2 其他需求2.3 系统功能模块图 3. 开发环境4. 项目结构5. 部分功能介绍5.1 数据库密码密文存储5.2 统一数据格式返回5.3 登录拦截器 6. 项目展示 前言 在几个月前实现了一个servlet版本的博客系统&#xff0c;本项目则是在原…

JWT无状态理解

JSON Web Tokens (JWT) 被称为无状态&#xff0c;因为授权服务器不需要维护任何状态&#xff1b;令牌本身就是验证令牌持有者授权所需的全部内容。 JWTs都签订使用数字签名算法&#xff08;例如RSA&#xff09;不能被伪造。因此&#xff0c;任何信任签名者证书的人都可以放心地…

二维深度卷积网络模型下的轴承故障诊断

1.数据集 使用凯斯西储大学轴承数据集&#xff0c;一共有4种负载下采集的数据&#xff0c;每种负载下有10种 故障状态&#xff1a;三种不同尺寸下的内圈故障、三种不同尺寸下的外圈故障、三种不同尺寸下的滚动体故障和一种正常状态 2.模型&#xff08;二维CNN&#xff09; 使…

基于传统检测算法hog+svm实现图像多分类

直接上效果图&#xff1a; 代码仓库和视频演示b站视频005期&#xff1a; 到此一游7758258的个人空间-到此一游7758258个人主页-哔哩哔哩视频 代码展示&#xff1a; 数据集在datasets文件夹下 运行01train.py即可训练 训练结束后会保存模型在本地 运行02pyqt.py会有一个可视化…

iOS开发-自定义TabbarController与Tabbar按钮Badge角标

iOS开发-自定义Tabbar按钮Badge角标 Tabbar是放在APP底部的控件。UITabbarController是一个非常常见的一种展示模式了。比如微信、QQ都是使用tabbar来进行功能分类的管理。 一、实现自定义Tabbar 我这里Tabbar继承于系统的UITabBar&#xff0c;定义背景图、线条的颜色、tab…