C/C++编程:区分接口继承和实现继承

1059 篇文章 275 订阅

public继承有两部分组成:

  • 函数接口(function interface)
  • 函数实现(function implementation)

身为类的设计者,有时候你会希望派生类只继承成员函数的接口(也就是声明);有时候你会希望派生类同时继承函数的接口和实现,有时候你会希望能够覆写(override)它们所继承的实现;有时候你会希望生类同时继承函数的接口和实现,并且不允许覆写任何东西。

我们来看个例子:

class Shape{
public:
	virtual void draw() const = 0;
	virtual void error(const std::string& msg);
	int objectID() const;
	...
};

class Rectangel : public Shape {...}
class Ellipse : public Shape {...}

Shape是个抽象类:它的纯虚函数draw使它成为一个抽象类。所以客户不能够创建Shape类的实体,只能创建其派生类的实体。 尽管如此,Shape还是强烈影响了所有以public形式继承它的派生类,因为:成员函数的接口总是会被继承

  • public继承意味着is-a,所以对基类为真的任何事情一定对派生类也为真。因此如果某个函数可以施行于某个类身上,一定也可以作用域派生类身上
  • 对于纯虚函数virtual void draw() const = 0
    • 纯虚函数有两个最突出的特性:
      • 它们必须被任何继承了它们的具现类重新声明
      • 它们在抽象类中通常没有定义
    • 从这两个形式可以看出:声明一个纯虚函数的目的是为了让派生类只继承函数接口。Shaped::draw的声明相当于对具象派生类设计者说:”你必须提供一个draw函数,但我不干涉你怎么实现它
    • 纯虚函数在抽象类中通常没有定义,但是不是不能这样做:抽象类中可以实现纯虚函数,不过调用他们的唯一途径是明确支持其类的名称
Shape* ps = new Shape; // 错误,Shape是抽象的
Shape* ps1 = new Rectangle; //没问题
ps1->draw();   // 调用Rectangle::draw
ps1->Shape::draw(); // 调用Shape::draw

一般而言,这项性质用途有限,但是稍后你将看到,它可以实现一种机制,为简朴的impure virtual函数提供更平常更安全的缺省实现。

简朴的impure virtual函数和pure virtual函数有点不同。一如往常,派生类继承其函数接口,但非纯虚函数会提供一份实现代码,派生类可能覆写(overried)它,也就是说:

  • 声明impure virtual函数的目的,是让派生类继承该函数的接口和缺省实现

看个例子:

class Shape{
public:
	virtual void error(const std::string& msg);
	}

其error接口表示,每个类都必须支持一个”当遇上错误是可调用“的函数,但每个类可以自由处理错误,如果每个类不想针对错误做出任何特殊行为,它可以退回到Shape类提供的缺省错误处理行为。也就是说,Shape::error告诉派生类:”你可以一个error函数,用来处理错误,但如果你不想自己写一个,可以使用Shape class提供的缺省版本“。

但是,允许impure virtual函数同时指定函数声明和函数缺省行为,有可能造成危险。我们来看一个例子。

场景: 一家公司只有两种飞机,两者都以相同的方式飞行
实现:为了表现所有飞机都一定能飞,并阐明”不同型飞机原则上需要不同的fly实现“。Airplane::fly被声明为virtual。然而为了避免在ModelA和ModelB中编写相同代码,缺省行为由Airplane::fly提供,它同时被ModelA和ModelB继承。如下


class Airport{...}  // 机场
class Airplane{
public:
	virtual void fly(const Airport& destination);
};

void Airplane::fly(const Airport& destination)// 缺省代码,将飞机飞往指定目的地
};

class ModelA : public Airplane {}
class modelB :  public Airplane {}

新需求:加入这家公司又买了一种C型飞机,这种飞机的不是采用默认的飞行方式,但是不小心忘记了重新定义fly函数:

class ModelC :  public Airplane {}

这将造成重大灾难。

问题不在Airplane::fly有缺省行为,而在于ModelC在没有明白说出"我要"的情况下就缺省继承了该缺省行为。幸运的是我们可以轻易做到"提供缺省实现给派生类,但除非它们要求否则免谈"。技巧在于切断”虚函数“和"缺省实现"之间的连接。下面是一种做法。

class Airplane{
public:
	virtual void fly(const Ariport& destination) = 0;
protected: 
	void defaultFly(const Ariport& destination);
}

void Airplane::defaultFly(const Airport& destination)// 缺省代码,将飞机飞往指定目的地
};

现在Airplane::fly已经被改为一个纯虚函数,只提供飞行接口。其缺省行为以独立的defaultFly函数提供。如果派生类想要使用缺省实现,可以在其fly函数中对defaultFly做一个inline调用:


class ModelA : public Airplane {
public:
 	virtual void fly(const Airport& destination){
 		defaultFly(destination);
	}
}

class ModelB : public Airplane {
public:
 	virtual void fly(const Airport& destination){
 		defaultFly(destination);
	}
}

现在就不用担心ModelC类意外继承不正确的fly实现了,因为Airplane的纯虚函数强迫ModelC必须提供自己的fly版本。

另外,注意

  • Airplane::defaultFly是protected的,因为它是Airplane和其派生类的实现细目。乘客只需要关心飞机能不能飞,不需要关心怎么飞。
  • Airplane::defaultFly是一个non-virtual函数。因为没有任何派生类应该重新定义此函数

有人反对以不同的函数分别提供接口和缺省实现(fly,default),会应为过渡雷同的函数名称而引起类命名空间污染问题。但是他们也同意,接口和缺省应该分开。那这个矛盾怎能解决呢?我们可以利用纯虚函数必须在派生类中重新声明,但是它们也可以有自己的实现这一事实来解决:

class Airplane{
public:
	virtual void fly(const Ariport& destination) = 0;
};
void Airplane::fly(const Ariport& destination){
	// 缺省代码,将飞机飞往指定目的地
}

class ModelA : public Airplane {
public:
 	virtual void fly(const Airport& destination){
 		Airplane::fly(destination);
	}
}

class ModelB : public Airplane {
public:
 	virtual void fly(const Airport& destination){
 		Airplane::fly(destination);
	}
}

本质上,现在的fly被分成两个基本要素:其声明部分表现的是接口(派生类必须用的),其定义部分标注的是缺省行为(派生类可能要用的,但只有它们明确提出申请才是)。

最后,我们来看看Shape的non-virtual的objectID:

class Shape{
public:
	int objectID() const;
};

如果成员函数是个non-virtual函数,意味着它并不打算在派生类中有不同的行为。实际上,一个non-virtual成员函数所表现的不变性凌驾于其特异性,因为它表示不论派生类变得多么特异化,它的行为都不可以改变。就自身而言:

  • 声明non-virtual函数的目的是为了令派生类继承函数的接口以及一份强制性实现
  • 一个non-virtual成员函数所表现的不变性凌驾于其特异性,所以绝不该在派生类中被重新定义

Shape::objectID的声明相当于:”每个Shape对象都有一个用来产生对象识别码的函数,此识别码总是采用相同的计算方法,该方法由Shape::objectID的定义式决定,任何派生类都不应该尝试改变其行为“。

纯虚函数,impure virtual函数、non-virtual函数之间的差异,使得你得以精确指定你想要派生类继承的东西:只继承接口,或者是继承接口和一份缺省实现,或者是继承接口和一份强制实现。由于这些不同类型的声明意味着根本意义并不相同的事情,当你声明你的成员函数,必须谨慎选择。注意

  • 不要将所有的函数都声明为non-vritual,这使得派生类没有空间进行特化工作。non-vritual析构函数绝对会带来问题。事实上:

    • 如果你的类不会是基类,那么就可以将所有的成员函数都声明为non-virtual是正确的
    • 只要任何类打算被当作一个基类,都会拥有若干虚函数(只是析构函数是纯虚函数)
  • 不要将所有的成员函数都声明为virtual

    • 有些函数就是不该在派生类中被重新定义,这是它们应该是non-vritual
    • 只有interface class,才可以将所有的成员函数都声明为virtual

总结

  • 接口继承和实现继承不同。在public继承下,派生类总是继承基类的接口
  • pure virtual函数只具体指定接口继承
  • impure virtual函数具体指定接口接口和缺省实现继承
  • non-virtual函数具体指定接口继承以及强制性实现继承
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Effective C++(编程的50个细节) Effective C++(编程的50个细节)着重讲解了编写C++程序应该注意的50个细节问题,书中的每一条准则描述了一个编写出更好的C++的方式,每一个条款的背后都有具体范例支持,书中讲的都是C++编程技巧和注意事项,很多都是自己平时不太注意但又很重要的内容,绝对经典,作者Scott Meyers是全世界最知名的C++软件开发专家之一。 电子书PDF格式下载:http://www.yanyulin.info/pages/2013/11/effective.html 1、从C转向C++ 条款1:尽量用CONST和INLINE而不用#DEFINE 条款2:尽量用而不用 条款3:尽量用NEW和DELETE而不用MALLOC和FREE 条款4:尽量使用C++风格的注释 2、内存管理 条款5:对应的NEW和DELETE要采用相同的形式 条款6:析构函数里对指针成员调用DELETE 条款7:预先准备好内存不够的情况 条款8:写OPERATOR NEW与OPERATOR DELETE要遵循常规 条款9:避免隐藏标准形式的NEW 条款10:如果写了OPERATOR NEW就要同时写OPERATOR DELETE 条款11:为需要动态分配内存的类声明一个拷贝构造函数和一个赋值函数 条款12:尽量使用初始化而不要在构造函数里赋值 条款13:初始化列表中成员列出顺序和它们在类中的声明顺序相同 条款14:确定基类有虚析构函数 条款15:让OPERATOR=返回*THIS的引用 条款16:在OPERATOR=中对所有数据成员赋值 条款17:在OPERATOR=中检查给自已赋值的情况 3、类和函数:设计与声明 条款18:争取使类的接口完整并且最小 条款19:分清成员函数,非成员函数和友元函数 条款20:避免PUBLIC接口出现数据成员 条款21:尽可能使用CONST 条款22:尽量用传引用而不用传值 条款23:必须返回一个对象时不要试图返回一个引用 条款24:在函数重载与设定参数默认值间慎重选择 条款25:避免对指针与数字类型的重载 条款26:当心潜在的二义性 条款27:如果不想使用隐式生成的函数要显示的禁止它 条款28:划分全局名字空间 4、类和函数:实现 条款29:避免返回内部数据的句柄 条款30:避免这样的成员函数,其返回值是指向成员的非CONST指针或引用 条款31:千万不要返回局部对象的引用,也不要返回函数内部用NEW初始化的指针 条款32:尽可能推迟变量的定义 条款33:明智的使用INLINE 条款34:将文件间的编译依赖性阡至最低 5、继承与面向对象设计 条款35:使公有继承体现是一个的函义 条款36:区分接口继承实现继承 条款37:绝不要重新定义继承而来的非虚函数 条款38:绝不要重新定义继承而来的缺省参数值 条款39:避免向下转换继承层次 条款40:通过分层来体现有一个和用...来实现 条款41:区分继承和模板 条款42:明智的使用私有继承 条款43:明智的使用多继承 条款44:说你想说的,理解你说的 6、杂项 条款45:弄清C++在幕后为你所写、所调用的函数 条款46:宁可编译与链接时出错,也不要运行时出错 条款47:确保非局部静态对象在使用前被初始化 条款48:重视编译器警告 条款49:熟悉标准库 条款50:提高对C++的认识
内容简介: 有人说C++程序员可以分成两类,读过Effective C++的和没读过的。世界顶级C++大师Scott Meyers成名之作的第三版的确当得起这样的评价。当您读过《Effective C++中文版(第3版改善程序与设计的55个具体做法)》后,就获得了迅速提升自己C++功力的一个契机。   在国际上,本书所引起的反响,波及整个计算机技术出版领域,余音至今未绝。几乎在所有C++书籍的推荐名单上,本书都会位于前三名。作者高超的技术把握力、独特的视角﹑诙谐轻松的写作风格﹑独具匠心的内容组织,都受到极大的推崇和仿效。这种奇特的现象,只能解释为人们对这本书衷心的赞美和推崇。   《Effective C++中文版(第3版改善程序与设计的55个具体做法)》不是读完一遍就可以束之高阁的快餐读物,也不是用以解决手边问题的参考手册,而是需要您去反复阅读体会的,C++是真正程序员的语言,背后有着精深的思想与无与伦比的表达能力,这使得它具有类似宗教般的魅力。希望这本书能够帮助您跨越C++的重重险阻,领略高处才有的壮美风光,做一个成功而快乐的C++程序员。 Effective C++中文版(第3版改善程序与设计的55个具体做法)》一共组织55个准则,每一条准则描述一个编写出更好的C++的方式。每一个条款的背后都有具体范例支撑。第三版有一半以上的篇幅是崭新内容,包括讨论资源管理和模板(templates)运用的两个新章。为反映出现代设计考虑,对第二版论题做了广泛的修订,包括异常(exceptions)、设计模式(design patterns)和多线程(multithreading)。   《Effective C++中文版(第3版改善程序与设计的55个具体做法)》的重要特征包括:   ·高效的 classes、functions、templates 和inheritance hierarchies(继承体系)方面的专家级指导。   ·崭新的 "TR1" 标准程序库功能应用,以及与既有标准程序库组件的比较。   ·洞察 C++和其他语言(例如Java、C#、C)之间的不同。此举有助于那些来自其他语言阵营的开发人员消化吸收 C++ 式的各种解法。 目录: 译序 中英简繁术语对照 目录 序言 致谢 导读 1.让自己习惯C++ 条款01:视C++为一个语言联邦 条款02:尽量以const,enum,inline替换#define 条款03:尽可能使用const 条款04:确定对象被使用前已先被初始化 2.构造/析构/赋值运算 条款05:了解C++默默编写并调用哪些函数 条款06:若不想使用编译器自动成生的函数,就该明确拒绝 条款07:为多态基类声明Virtual析构函数 条款08:别让异常逃离析构函数 条款09:绝不在构造和析构过程中调用Virtual函数 条款10:令Operator=返回一个referenceto this 条款11:在Operator=中处理“自我赋值” 条款12:复制对象时勿忘其每一个成分 3.资源管理 条款13:以对象管理资源 条款14:在资源管理类中小心Coping行为 条款15:在资源管理类中提供对原始资源的访问 条款16:成对使用new和delete对象置入智能指针 条款17:以独立语句将newed对象置入智能指针 4.设计与声明 条款18:让接口容易被正确使用,不易被误用 条款19:设计class犹如设计type 条款20:宁以pass-by-reference-to-const替换Pass-by-value 条款21:必须返回对象时,别妄想返回其reference 条款22: 将成员变量声明为private 条款23: 宁以non-member、non-friend替换member函数 条款24:若有所参数皆需类型转换,请为此采用non-member函数 条款25:考虑写出一个不抛异常的swap函数 5.实现 条款26:尽可能延后变量定义式的出现时间 条款27:尽量少做转型动作 条款28:避免返回handles指向对象内部成分 条款29:为“异常安全”而努力是值得的 条款30:透彻了解inlining的里里外外 条款31:将文件间的编译依存关系降至最低 6.继承与面向对象设计 条款32:确定你的public继承塑模出is-a关系 条款33:避免遮掩继承而来的名称 条款34:区分接口继承实现继承 条款35:考虚virtual函数以外的其他选择 条款36:绝不重新定义继承而来的non-virtual函数 条款37:绝不重新定义继承而来的缺省参数值 条款38:通过复合塑模出has-a或“根据某物实现出” 条款39:明智而审慎地使用private继承 条款40:明智而审慎地使用private继承 7.模板与泛型编程 8.定制new和delete 9.杂项讨论 A 本书之外 B 新旧版条款对映 索引
2018年软件学院C++课程设计 课程设计目的: 1、熟悉利用面向对象的方法以及C++编程思想来完成系统的设计; 2、锻炼学生在设计的过程中,建立清晰的类层次,应用继承和多态等面向对象的编程思想; 3、通过本课程设计,加深对面向对象程序设计课程所学知识的理解,熟练掌握和巩固C++语言的基本知识和语法规范,深刻体会面向对象的编程思想,掌握使用面向对象程序设计语言C++,学会编写结构清晰、风格良好的C++语言程序,从而具备利用计算机编程分析解决综合性实际问题的初步能力。 课程设计题目:模拟即时通信系统实现 一、题目描述 基于社交的即时通信是腾*公司的主要业务,先后有QQ、微信、微博等服务,可能还将继续推出微商、微唱、微走、微笑等产品。这些软件既可以独立提供服务,又互相辉映关联。腾*公司希望对各系统进行整合形成统一的立体社交软件平台。现请完成该平台的设计并实现。要求如下: 1、用户基本信息: 号码ID,昵称,出生时间,T龄(号码申请时间)、所在地、好友列表、群列表。 微博与QQ共享ID,微信采用独立ID,但是可以与QQ号码绑定对应。其他微X产品也分为这两种情况。 2、好友管理 (1)实现各功能好友信息的添加、修改、删除、查询的功能。 (2)可以查询微X之间各自共同好友。如微信可以添加QQ推荐好友。 3、群管理 (1)设定每个微X功能已有1001、1002、1003、1004、1005、1006等群号。 (2)加入群、退出群、挨T、查询群成员等。 (3)不同微X之间群的理念不同,比如:QQ群可以申请加入,而微信群则只能推荐加入;QQ群允许设置临时讨论组(子群),微信群则不允许;QQ群有以群主为核心的管理员制度,而微信群仅有群主为特权账号。 4、开通管理 用户可以选择自己开通该平台的N个微X服务。 5、登录管理 各微X之间只要有一个服务登录,则其它服务简单确认后视为自动登录。 6、功能展示要求(main函数) (1)设计约定。开通服务情况、群成员信息和好友信息可以预先保存到文件中,在系统启动时将这些信息加载到内存中; (2)一个服务登录后,本人开通的其它所有服务均进入开通状态。 (3)服务之间可以依据本人开通的任意另外一个服务的好友添加好友。 (4)展示一个服务当前群的特色功能;在群成员数据不受伤害的前提下,动态变换为其他类型群的管理特色。 (5)实现QQ的点对点的TCP通信的收发功能。(选做)提示: a)需要加载ws2_32.lib静态库,打开头文件winsock.h。 b)百度IP地址、端口等概念; c)百度socket编程,关注bind、listen、accept、connect、send、receive等函数用法。 二、技术层次要求及说明 1、基本层次。 完成上述功能要求,所采用技术不限,比如采用纯面向过程思想实现; 2、支持对象层次。 正确完成了类的切割,利用对象技术实现。 (1)容器类主要包括:例如,微X成员管理。 (2)其它主要类包括:例如,微X信息、群信息、好友信息。 3、抽象、封装层次 采用了继承或者组合实现复用,对数据成员提供了必要的接口保护; (1)抽象出了基础类,并被其它功能复用; (2)如好友维护、群信息维护等操作均应该提供接口形式; 4、面向对象层次 支持多态功能,支持依据设计原则的优化。 好友管理、群管理等; 5、优化提高层次 (1)提供简便菜单,以1、2等数字区分几类功能,并允许返回菜单; (2)I/O操作支持。基本功能中,已有设定信息,在初始化时候可以固化在程序代码中,也可以存放在文件中,每次容器实例化时读入,析构时写回文件中,以实现断电保存。 (3)可扩展性支持,需要考虑群、好友等与主要服务之间的关系; (4)灵活性支持。群的管理模式动态可变; (5)程序有必要的注释; (6)可以采用UML工具画出简单类图 (7)为防止不诚信行为,要求类的设计均以独立文件存在,且所有的类名称后面应有自己的姓名缩写,如张三设计的QQ信息类名称:TencentZhS。 三、设计步骤(参考 ): 在清楚上述系统功能要处理是什么的基础上,考虑用如下方式来设计 1、确定所需的类及其相互间的关系。 (1)要从问题中归纳出一个概念或实体,从这些概念或实体出发建立相应的类。 (2)尽量使类小而简单,以使其看起来容易理解。 (3)充分利用封装以增加类的可靠性,以便使用时保证更加可靠。 (4)通过继承建立类族,以方便使用多态性。 2、确定每个类的实现。 (1)考虑类的对象应该如何构造和析构。 (2)考虑类的成员函数的建立。 (3)综合考虑各个类在命名和功能方面有哪些共性。 3、细化有关的类,描述他们之间的相互关系,即类关系和对象关系。 4、描述本系统的界面,通过分别定义成员的不同属性,为抽象和实现提供分离的接口。 四、设计工具 1、设计工具:建议使用.net 系列中的C++ 编译器,但不局限于此。 2、不提倡使用MFC和可视化开发技术。 五、设计报告 (报告的具体格式附后) 六、考核方式 1、在设计结束前的最后一天检查程序并接受质疑。 2、检查程序前须提交设计报告(按提交报告的先后顺序检查程序)。 七、考核标准: 参照5个技术层次划分。 八、课程设计后作业(不考核) 引入可视化设计,在本课程设计基础上实现可视化QQ即时通信功能,包括: 多人聊天;聊天记录查询。需要涉及知识如下: 1、网络通信编程; 2、可视化编程; 3、多线程编程; 4、数据库编程

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值