首页 > 编程语言 >C++PrimerPlus:第十三章类和继承:抽象基类

C++PrimerPlus:第十三章类和继承:抽象基类

时间:2024-06-22 11:01:14浏览次数:20  
标签:PrimerPlus double void C++ ny 纯虚 基类 Circle

:第十三章类和继承:抽象基类

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如::第十三章类和继承:抽象基类


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

提示:这里可以添加本文要记录的大概内容:

例如::第十三章类和继承:抽象基类


提示:以下是本篇文章正文内容,下面案例可供参考

一、抽象基类

至此,介绍了简单继承和较复杂的多态继承。接下来更为复杂的是抽象基类(abstractbase class,ABC)。
我们来看一些可使用 ABC的编程情况。
有时候,使用is-a规则并不是看上去的那样简单。例如,假设您正在开发一个图形程序,该程序会显示圆和椭圆等。圆是椭圆的一个特殊情况–长轴和短轴等长的椭圆。因此,所有的圆都是椭圆,可以从Ellipse 类派生出Circle类。但涉及到细节时,将发现很多问题。首先考虑 Elipse 类包含的内容。数据成员可以包括椭圆中心的坐标、半长轴(长轴的一半)、短半轴(短轴的一半)以及方向角(水平坐标轴与长轴之间的角度)。另外,还可以包括一些移动圆、返回椭圆面积、旋转椭圆以及缩放长半轴和短半轴的方法:

class Ellipse
{
private:
	double x;//x-coordinate of the ellipse's center
	double y;//y-coordinate of the ellipse's center
	double a;//semimajor axis
	double b;//semiminor axis
	double angle;//orientation angle in degrees
public :
	void Move(intnx,ny){x=nx;y=ny};
	virtual double Area()const {return 3.14159*a* b};
	virtual void Rotate(double nang){angle += nang;}
	virtual void Scale(double sa,double sb){a*= sa;b *= sb;}
}

现在假设从 Ellipse 类派生出一个 Circle 类:

class Circle:public Ellipse
{

...
}

虽然圆是一种椭圆,但是这种派生是笨拙的。例如,圆只需要一个值(半径)就可以描述大小和形状,并不需要有长半轴(a)和短半轴(b)。Circle 构造函数可以通过将同一个值赋给成员a和b来照顾这种情况,但将导致信息冗余。angle 参数和 Rotate()方法对圆来说没有实际意义;而Scale()方法(顾名思义)会将两个轴作不同的缩放,将圆变成椭圆。可以使用一些技巧来修正这些问题,例如在 Circle 类中的私有部分包含重新定义的 Rotate()方法,使Rotate()不能以公有方式用于圆。但总的来说,不使用继承,直接定义 Circle 类更简单:

class Circle // no inheritance
{
	private:
		double x,//x-coordinate of the circle's center
		double y;//y-coordinate of the circle's center
		double r;radius
		...
	public:
	...
	void Move(int nx,ny){x = nx;y = ny;}
	double Area()constP{return3.14159*r*r;}
	void Scale(double sr){r*=sr;}
}

现在,类只包含所需的成员。但这种解决方法的效率也不高。Circle和Elipse 类有很多共同点,将它
们分别定义则忽略了这一事实。还有一种解决方法,即从Ellipse和 Circle类中抽象出它们的共性,将这些特性放到一个 ABC中。然后从该ABC派生出 Circle 和 Elipse 类。这样,便可以使用基类指针数组同时管理 Circle 和 Ellipse 对象,即可以使用多态方法)。在这个例子中,这两个类的共同点是中心坐标、Move()方法(对于这两个类是相同的)和 Area()方法(对于这两个类来说,是不同的)。确实,甚至不能在 ABC中实现 Area()方法,因为它没有包含必要的数据成员。C++通过使用纯虚函数(pure virtualfunction)提供未实现的函数。纯虚函数声明的结尾处为=0,参见Area()方法:

class BaseEllipse //abstract base class
{
private:
	double x;//x-coordinate of center
	double Y;//y-coordinate of center
public :
	BaseEllipse(doublex0=0,doubley0=0):x(x0)y(y0){}
	virtual ~BaseEllipse(){}
	void Move(int nx,ny){x=nx;y=ny;}
	virtual double Area()const =0;//a pure virtual function
}

当类声明中包含纯虚函数时,则不能创建该类的对象。这里的理念是,包含纯虚函数的类只用作基类要成为真正的 ABC,必须至少包含一个纯虚函数。原型中的=0使虚函数成为纯虚函数。这里的方法 Area()没有定义,但C++甚至允许纯虚函数有定义。例如,也许所有的基类方法都与Move()一样,可以在基类
中进行定义,但您仍需要将这个类声明为抽象的。在这种情况下,可以将原型声明为虚的:

void Move(int nx,ny)=0;

这将使基类成为抽象的,但您仍可以在实现文件中提供方法的定义:

void BaseEllipse::Move(intnx,ny){x=nx;y=ny;}

总之,在原型中使用=0指出类是一个抽象基类,在类中可以不定义该函数。

现在,可以从 BaseEllipse类派生出Elips类和 Circle 类,添加所需的成员来完成每个类。需要注意的一点是,Circle 类总是表示圆,而 Elipse 类总是表示椭圆–也可以是圆。然而,Elipse 类圆可被重新缩放为非圆,而 Ciecle 类圆必须始终为圆。

使用这些类的程序将能够创建 Elipse对象和Circle对象,但是不能创建 BaseEllipse对象。由于 Circle和 Elipse 对象的基类相同,因此可以用BaseEllipse 指针数组同时管理这两种对象。像 Circle 和 Ellipse 这样的类有时被称为具体(concrete)类,这表示可以创建这些类型的对象。

总之,ABC描述的是至少使用一个纯虚函数的接口,从ABC派生出的类将根据派生类的具体特征,使用常规虚函数来实现这种接口。


总结

提示:这里对文章进行总结:

暂无

标签:PrimerPlus,double,void,C++,ny,纯虚,基类,Circle
From: https://blog.csdn.net/zhyjhacker/article/details/139781756

相关文章

  • 从0开始C++(五):友元函数&运算符重载
    友元函数介绍C++中的友元函数是一种特殊的函数,它可以访问和操作类的私有成员和保护成员。友元函数可以在类的内部或外部声明和定义,但在其声明和定义中需要使用关键字friend来标识。友元函数可以是全局函数,也可以是其他类的成员函数。下面是友元函数的一些重要特点和用法:......
  • c++ 多重包含/定义 || 链接性 || 生命周期
     作用域&&生命周期C++中的作用域(scope)指的是变量、函数或其他标识符的可见和可访问的范围。生命周期(Lifetime)指的是变量或对象存在的时间段。它开始于变量或对象的创建(定义)时刻,结束于其被销毁的时刻。作用域:通过其声明的位置来确定。全局作用域:定义在(类/函数)外部......
  • c++ virtual || virtual =0
    虚函数&&纯虚抽象类:包含纯虚函数的类称为抽象类,继承层次结构的较上层。作用:将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。继承:子类继承基类的成员及成员函数,不可以删除,可以(修改)通过虚函数重写......
  • 【C++】list的使用方法和模拟实现
    ❤️欢迎来到我的博客❤️前言list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素list与forward_list非常相似:最......
  • 2024年华为OD机试真题-分披萨-(C++/Java/python)-OD统一考试(C卷D卷)
    题目描述"吃货"和"馋嘴"两人到披萨店点了一份铁盘(圆形)披萨,并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。但是粗心的服务员将披萨切成了每块大小都完全不同奇数块,且肉眼能分辨出大小。由于两人都想吃到最多的披萨,他们商量了一个他们认为公平的分法:从"吃货"开始,轮流......
  • c/c++ 数据结构 顺序栈
    本文是以c语言的风格编写的c++程序。栈的特点:先进后出,后进先出。顺序栈的结构定义:一个数组以及一个”指针“(不是真正的指针,而是位置变化的说明)#include<stdio.h>#include<malloc.h>#defineMaxsize20typedefstruct{ intdata[Maxsize]; inttop;}SqStack; 初......
  • 2020C++等级考试二级真题题解
     202012数组指定部分逆序重放c++ #include<iostream>usingnamespacestd;intmain(){  inta[110];  intn,k;  cin>>n>>k;  for(inti=0;i<n;i++){    cin>>a[i];  }  for(inti=0;i<k/2;i++){......
  • C++系统相关操作1 - 调用命令行并获取返回值
    1.关键词2.sysutil.h3.sysutil.cpp3.1.system_util_unix.cpp3.2.system_util_win.cpp4.测试代码5.运行结果6.源码地址1.关键词关键词:C++系统调用systempopen跨平台应用场景:希望直接调用操作系统的某些命令,并获取命令的返回值。2.sysutil.h#pragm......
  • C++系统相关操作2 - 获取系统环境变量
    1.关键词2.sysutil.h3.sysutil.cpp4.测试代码5.运行结果6.源码地址1.关键词C++系统调用环境变量getenv跨平台2.sysutil.h#pragmaonce#include<cstdint>#include<string>namespacecutl{/***@briefGetanenvironmentvariable.......
  • C++核心编程运算符的重载
    C++核心编程运算符的重载文章目录C++核心编程运算符的重载1.“+”运算符的重载1.1作为成员函数重载1.2作为全局函数重载2."<<"运算符重载2.1为什么需要重载左移运算符2.2如何重载左移运算符2.3注意事项3."++"运算符重载3.1前置递增运算符重载3.2后置递增运算符重载......