C ++中的多态


129

据我所知:

C ++提供了三种不同类型的多态性。

  • 虚拟功能
  • 函数名称重载
  • 运算符重载

除了以上三种类型的多态性之外,还存在其他类型的多态性:

  • 运行
  • 编译时间
  • 临时多态性
  • 参数多态性

我知道,运行时多态性可以通过以下方式实现虚函数静态多态性可以通过以下方式实现模板功能

但是对于其他两个

临时多态性:

如果可以使用的实际类型范围是有限的,并且必须在使用前单独指定组合,则这称为临时多态性。

参数多态性:

如果编写所有代码时都没有提及任何特定类型,因此可以与任何数量的新类型透明地使用,则称为参数多态。

我很难理解他们:(

任何人都可以通过示例解释它们吗?我希望这些问题的答案对他们所在大学的许多新学员有帮助。


30
实际上,C ++具有四种多态性:参数化(通过C ++中的模板进行通用性),包含(通过C ++中的虚拟方法进行子类型化),重载和强制(隐式转换)。从概念上讲,函数重载和运算符重载之间几乎没有区别。
fredoverflow

所以看来我提到的网站误导了许多人。我正确吗?
维杰

@zombie:该网站涉及许多好的概念,但是在使用术语方面并不精确且不一致(例如,一旦它开始谈论虚拟调度/运行时多态性,就会发表很多关于多态性错误的陈述一般而言,但对于虚拟派遣则适用。如果您已经了解的话题,你可以涉及到什么是说和精神上插入必要的警告,但很难通过阅读网站获得有....
托尼德尔罗伊

一些术语是近义词,或者比其他术语更相关但更受限制。例如,根据我的经验,“临时多态性”一词在Haskell中最常用,但是“虚拟功能”却非常相关。较小的区别是“虚拟功能”是一个面向对象的术语,是指具有“后期绑定”的成员功能。“多重调度”也是一种特殊的多态性。正如FredOverflow所说,运算符和函数重载基本上是同一件事。
Steve314,2011年

我为您修复了格式。请阅读编辑窗格右侧的可用帮助。有人> 200个问题且> 3k的人应该知道这一基本知识。另外,您可能想购买新的键盘。该Shift键似乎间歇性失败。哦,还有:C ++中没有“模板函数”之类的东西。但是,有功能模板
2011年

Answers:


219

对多态性的了解/要求

要理解多态性(在计算机科学中使用该术语),它有助于从对其进行简单的测试和定义开始。考虑:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

在这里,f()是执行一些操作并被赋予值xy作为输入。

为了表现出多态性,f()必须能够使用至少两种不同类型(例如intdouble)的值进行操作,找到并执行不同类型适合的代码。


C ++多态性机制

程序员指定的明确多态性

您可以f()这样编写,使其可以通过以下任何一种方式对多种类型进行操作:

  • 预处理:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
    
  • 重载:

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
    
  • 范本:

    template <typename T>
    void f(T& x) { x += 2; }
    
  • 虚拟派遣:

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch
    

其他相关机制

稍后将讨论编译器为内置类型,标准转换和强制转换/强制提供的多态性,其完整性如下:

  • 无论如何,他们通常都会被直观地理解(保证有“ 哦,那个 ”反应),
  • 它们影响了上述机制的要求和无缝使用的门槛,并且
  • 解释只是对更重要的概念的干扰。

术语

进一步分类

鉴于以上所述的多态机制,我们可以通过多种方式对它们进行分类:

  • 何时选择特定于多态类型的代码?

    • 运行时意味着编译器必须为程序在运行时可能处理的所有类型生成代码,并且在运行时选择了正确的代码(虚拟调度
    • 编译时间是指在编译过程中选择特定于类型的代码。结果的后果:说一个仅f在上面带有int参数调用的程序-取决于所使用的多态机制和内联选择,编译器可能会避免为生成任何代码f(double),或者所生成的代码可能在编译或链接的某个时刻被丢弃。(除虚拟调度外,以上所有机制

  • 支持哪些类型?

    • 即席意味着您提供支持每种类型的显式代码(例如,重载,模板专门化);您显式添加了“为此”(按照临时意思)的支持类型,其他一些“此”,也可能是“那个” ;-)。
    • 参数化意味着您可以尝试将函数用于各种参数类型,而无需专门进行任何操作以使其支持它们(例如,模板,宏)。函数/运算符的行为类似于模板/宏的对象期望1 就是模板/宏完成其工作所需的全部内容,而确切的类型无关紧要。C ++ 20引入的“概念”表达并实施了这种期望-请参见cppreference页面

      • 参数多态性提供了鸭子的输入方式 -归因于James Whitcomb Riley的一个概念,他显然说过:“当我看到一只鸟像鸭子走路,像鸭子一样游泳,像鸭子一样duck时,我称那只鸟为鸭子。”

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • 子类型(又名包含)多态允许您在不更新算法/功能的情况下处理新类型,但是它们必须派生自相同的基类(虚拟调度)

1-模板非常灵活。 SFINAE(另请参见std::enable_if)有效地实现了对参数多态性的几套期望。例如,您可能会编码为:当您正在处理的数据类型具有.size()成员时,您将使用一个函数,否则将不需要另一个函数.size()(但可能会受到某种影响-例如,使用较慢strlen()或不打印在日志中有用的消息)。您还可以在使用特定参数实例化模板时指定临时行为,使某些参数保持参数化(部分模板特殊化)或不保留参数(完全特殊化)。

“多态的”

Alf Steinbach评论说,在C ++ Standard中,多态只涉及使用虚拟调度的运行时多态。通用电脑 科学 根据C ++创建者Bjarne Stroustrup的词汇表(http://www.stroustrup.com/glossary.html),其含义更具包容性:

多态性-为不同类型的实体提供单一接口。虚函数通过基类提供的接口提供动态(运行时)多态性。重载的函数和模板提供了静态(编译时)多态性。TC ++ PL 12.2.6、13.6.1,D&E 2.9。

这个答案-像问题一样-将C ++功能与Comp相关。科学 术语。

讨论区

在C ++标准中,使用的“多态性”定义比Comp窄。科学 社区,以确保您的受众的相互理解,考虑...

  • 使用明确的术语(“我们可以使该代码可用于其他类型吗?”或“我们可以使用虚拟分派吗?”而不是“我们可以使该代码多态吗?”),和/或
  • 明确定义您的术语。

尽管如此,对于成为一名出色的C ++程序员来说,至关重要的是要了解多态真正为您服务的事情。

    让您一次编写“算法”代码,然后将其应用于多种类型的数据

...然后非常了解不同的多态机制如何满足您的实际需求。

运行时多态适合:

  • 由工厂方法处理的输入,并通过Base*s 处理为异构对象集合,
  • 在运行时根据配置文件,命令行开关,UI设置等选择的实现,
  • 实现在运行时会有所不同,例如针对状态机模式。

如果没有明确的运行时多态性驱动程序,则通常首选编译时选项。考虑:

  • 模板类的所谓编译方面胜于在运行时失败的胖接口
  • FINEA
  • CRTP
  • 优化(许多内容包括内联和无效代码消除,循环展开,基于静态堆栈的数组与堆)
  • __FILE__、、__LINE__字符串文字串联和宏的其他独特功能(仍然是邪恶的;-))
  • 模板和宏测试是否支持语义用法,但不要人为地限制提供支持的方式(因为虚拟分派往往要求完全匹配的成员函数覆盖)

其他支持多态的机制

如所承诺的,为了完整起见,涵盖了几个外围主题:

  • 编译器提供的重载
  • 转换
  • 演员/强制

该答案以对以上内容如何结合以赋能和简化多态代码(尤其是参数多态(模板和宏))的讨论结束。

映射到特定于类型的操作的机制

>隐式编译器提供的重载

从概念上讲,编译器会为内置类型重载许多运算符。从概念上讲,它与用户指定的重载没有什么区别,但由于容易被忽略而被列出。例如,您可以使用相同的符号添加到ints和doubles x += 2,编译器将生成:

  • 类型专用的CPU指令
  • 相同类型的结果。

然后,重载无缝扩展到用户定义的类型:

std::string x;
int y = 0;

x += 'c';
y += 'c';

编译器为基本类型提供的重载在高级(3GL +)计算机语言中很常见,对多态性的明确讨论通常意味着更多。(2GL(汇编语言)通常要求程序员针对不同的类型显式使用不同的助记符。)

>标准转换

C ++标准的第四部分介绍了标准转换。

第一点很好地总结了(从一个旧的草稿中-希望仍然是正确的):

-1-标准转换是为内置类型定义的隐式转换。条款conv列举了此类转换的全部内容。标准转换顺序是按以下顺序进行的标准转换的顺序:

  • 以下集合中的零或一转换:左值到右值转换,数组到指针转换以及函数到指针转换。

  • 下列集合中的零个或一个转换:积分提升,浮点提升,积分转换,浮点转换,浮点积分转换,指针转换,成员转换指针和布尔转换。

  • 零或一资格转换。

[注意:标准转换顺序可以为空,即,可以不包含任何转换。]如果有必要将标准转换序列应用于表达式,以将其转换为所需的目标类型。

这些转换允许使用以下代码:

double a(double x) { return x + 2; }

a(3.14);
a(42);

应用先前的测试:

要实现多态,[ a()]必须能够使用至少两种不同类型(例如intdouble)的值进行操作,查找并执行适合类型的代码

a()本身专门运行代码double,因此不是多态的。

但是,在第二次调用中a(),编译器知道为“浮点升级”(标准§4)生成适合类型的代码以转换4242.0。额外的代码在调用函数中。我们将在结论中讨论其重要性。

>强制,强制转换,隐式构造函数

这些机制允许用户定义的类指定类似于内置类型的标准转换的行为。我们来看一下:

int a, b;

if (std::cin >> a >> b)
    f(a, b);

在此,std::cin借助转换运算符在布尔上下文中评估对象。可以从上述主题中的“标准转化”中将其与“整体促销”等概念地分组。

隐式构造函数可以有效地执行相同的操作,但是受强制转换类型控制:

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

编译器提供的重载,转换和强制的含义

考虑:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

如果我们希望x在除法过程中将金额视为实数(即6.5,而不是四舍五入到6),则只需将更改为typedef double Amount

很好,但是要使代码显式地“正确键入” 并不需要太多的工作:

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

但是,请考虑我们可以将第一个版本转换为template

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

由于这些小的“便利功能”,因此可以很容易地为intdouble和实例化实例化它。没有这些功能,我们将需要显式强制转换,类型特征和/或策略类,以及一些冗长且易于出错的混乱,例如:

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

因此,编译器为内置类型,标准转换,强制转换/强制/隐式构造函数提供的运算符重载-它们都对多态性提供了微妙的支持。在此答案顶部的定义中,它们通过映射解决“查找和执行适合类型的代码”:

  • 远离参数类型

    • 来自许多数据类型的多态算法代码句柄

    • 为(相同或其他)类型的(潜在较小)数目编写的代码。

  • 从常量类型的值“到”参数类型

它们不会自行建立多态上下文,但会帮助授权/简化此类上下文中的代码。

您可能会觉得受骗……看起来并不多。重要性在于,在参数多态上下文中(即在模板或宏内部),我们试图支持任意大范围的类型,但经常希望针对其他功能,字面量和为操作设计的操作来表达对它们的操作。小套的类型。当操作/值在逻辑上相同时,它减少了按类型创建几乎相同的函数或数据的需求。这些功能相互配合,以增强“尽力而为”的态度,通过使用有限的可用功能和数据来实现直观上的预期,并且只有在真正含糊不清的情况下才出现错误。

这有助于限制对支持多态代码的多态代码的需求,在多态使用周围画一个更紧密的网,因此局部使用不会强制广泛使用,并根据需要提供多态的好处,而不必承担必须公开实现的成本。编译时,在目标代码中具有相同逻辑功能的多个副本以支持使用的类型,并且在进行虚拟分派时与内联或至少在编译时解析调用相反。正如C ++中的典型做法一样,程序员拥有很大的自由度来控制使用多态性的边界。


1
-1除术语讨论外,其他答案很好。C ++标准在§1.8/ 1中定义了“多态”一词,此处引用了有关虚函数的10.3节。因此,这里没有摆动的空间,没有讨论的空间,也没有个人意见的空间:在标准C ++中,该术语是一劳永逸的。它确实发挥了实践作用。例如,第5.2.6节dynamic_cast“ 关于” 要求“指向多态类型的指针或左值”。干杯&心连心,
欢呼和心连心。

@Alf:很好的参考-尽管我认为您的观点太狭窄。从列表过载,临时和参数多态性等问题中可以很清楚地看出,答案应该将C ++的功能与通用Comp相联系。科学 术语的含义。实际上,Stroustrup的词汇表说:“多态性-为不同类型的实体提供单个接口。虚拟函数通过基类提供的接口提供动态(运行时)多态性。重载的函数和模板提供静态(编译时)多态性。 TC ++ PL 12.2.6、13.6.1,D&E 2.9。”
Tony Delroy

@Tony:这不是您回答问题的主旨所在。很好,很好。只是那个WRT。您会倒退该术语:正式的学术术语是《圣经》国际标准定义的狭义术语,而非正式的粗略术语则是人们在此问题和答案中可能会使用的略有不同的含义。干杯&心连心,
欢呼和心连心。-Alf

@Alf:我希望答案是伟大的-“其他机制”需要在第5行中重写,并且我正在考虑/草拟与多态机制形成对比的更具体的功能和含义。无论如何,我的理解是,仅以C ++为中心的正式学术意义可能较窄,但正式学术一般比较。科学 正如Stroustrup的词汇表所证明的那样,含义不是。我们需要确定性的东西-例如,克努斯(Knuth)的定义-还没有运气。我感谢您是C ++专家,但是您能具体指出与此相关的证据吗?
Tony Delroy

1
@Alf:其次,我相信多态性是在任何体面的一般Comp中正式定义的。科学 以与我的用法(和Stroustrup的用法)兼容的(永恒,稳定的)方式进行预订。Wikipedia文章链接了一些以这种方式定义的学术出版物:“多态函数是其操作数(实际参数)可以具有多个类型的函数。多态类型是其运算适用于多个类型的值的类型。” (摘自lucacardelli.name/Papers/OnUnderstanding.A4.pdf)。因此,问题是“谁代表科学奖”……?
Tony Delroy

15

在C ++中,重要的区别是运行时绑定与编译时绑定。临时与参数化并没有真正的帮助,我将在后面解释。

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

注意-运行时多态可能仍会在编译时解决,但这只是优化。需要有效支持运行时解析并在其他问题之间进行权衡是导致虚拟功能成为其本质的部分原因。这对于C ++中所有形式的多态性而言确实是关键-每种形式都源于在不同上下文中进行的不同权衡。

函数重载和运算符重载在所有重要方面都是相同的。名称和使用它们的语法不会影响多态。

模板允许您一次指定很多函数重载。

对于相同的解决时间概念,还有另一组名称...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

这些名称与OOP更加相关,因此说模板或其他非成员函数使用早期绑定有点奇怪。

为了更好地了解虚函数和函数重载之间的关系,了解“单调度”和“多调度”之间的区别也很有用。这个想法可以理解为一种进步。

  • 首先,存在单态函数。该功能的实现由功能名称唯一标识。这些参数都不是特殊的。
  • 然后,只有一个调度。其中一个参数被认为是特殊的,并与名称一起用于标识要使用的实现。在OOP中,我们倾向于将此参数视为“对象”,在功能名称等之前列出它。
  • 然后,有多个调度。任何/所有参数都有助于识别要使用的实现。因此,再次,这些参数都不需要是特殊的。

显然,OOP不仅仅是将一个参数指定为特殊参数的借口,但这只是其中的一部分。回到我所说的权衡问题上,高效地进行单次调度非常容易(通常的实现称为“虚拟表”)。多次调度不仅在效率方面而且在单独编译方面都比较尴尬。如果您好奇,可以查找“表达问题”。

对于非成员函数使用术语“早期绑定”有点奇怪,而在编译时解析多态性的术语“单调度”和“多调度”也有点奇怪。通常,C ++被认为没有多重调度,这被认为是一种特殊的运行时解析。但是,函数重载可以看作是在编译时完成的多次调度。

回到参数化与即席多态性方面,这些术语在函数式编程中更为流行,并且在C ++中不起作用。即使是这样...

参数多态性意味着您将类型作为参数,并且无论使用哪种类型的参数,都使用完全相同的代码。

临时多态性是临时的,即您根据特定类型提供不同的代码。

重载和虚函数都是临时多态性的示例。

再次,有一些同义词...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

除了它们不是完全的同义词以外,尽管它们通常被视作它们的同义词,并且在C ++中可能会引起混淆。

将它们视为同义词的背后原因是,通过将多态性约束到特定类型的类型,可以使用特定于那些类型的类型的操作。此处的“类”一词可以从OOP的角度进行解释,但实际上只是指(通常命名的)共享某些操作的类型集。

因此,通常将参数多态性(至少默认情况下)用于表示不受约束的多态性。因为使用相同的代码而不管类型参数如何,所以唯一可支持的操作是适用于所有类型的操作。通过使类型集不受约束,您可以严格限制可应用于这些类型的操作集。

在例如Haskell中,您可以拥有...

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

a是不受约束的多态类型。它可以是任何东西,因此我们对于该类型的值无能为力。

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

在这里,a被限制为Num类的成员-类型像数字一样起作用。该约束允许您使用这些值来做一些事,例如添加它们。即使3是多态类型推断也会指出您的意思3是类型a

我认为这是受约束的参数多态性。只有一种实现,但是只能在受限的情况下应用。临时方面是选择+3使用哪个方面。每个“实例” Num都有它们自己的独特实现。因此,即使在Haskell中,“参数”和“不受约束”也不是真正的同义词-不要怪我,这不是我的错!

在C ++中,重载和虚函数都是即席多态性。临时多态性的定义并不关心在运行时还是编译时选择实现。

如果每个模板参数都具有type,则C ++将非常接近模板的参数多态性typename。有类型参数,无论使用哪种类型,都只有一个实现。但是,“替换失败不是错误”规则意味着,在模板中使用操作会导致隐式约束。其他复杂性包括用于提供替代模板的模板专业化-不同的(临时)实现。

因此,C ++在某种程度上具有参数多态性,但是它受到隐式约束,并且可能被临时替代方法覆盖-即,这种分类对C ++并不真正有效。


+1很多有趣的观点和见解。我只花了几个小时来阅读有关Haskell的信息,所以“ a这是一个不受约束的多态类型[...],因此我们对该类型的值无能为力。” 很有意思-在C ++ sans Concepts中,您不限于仅对指定为模板参数的类型的参数尝试一组特定的操作...像boost concept这样的库以另一种方式工作-确保类型支持操作您指定,而不是防止意外使用其他操作。
Tony Delroy

@Tony-概念是一种显式限制模板多态性的方法。隐式约束显然不会因为兼容性而消失,但是显式约束肯定会显着改善性能。我很确定过去的一些概念计划与Haskell类型类有关,尽管我没有深入研究它们,并且当我上次“浅”看时,我对Haskell并不了解。
Steve314

“隐式约束显然不会因为兼容性而消失”-从内存来看,C ++ 0x Concepts确实(承诺:-/)防止了“隐式约束”-您只能以Concepts承诺的方式使用类型。
托尼·德罗伊

2

对于临时多态性,它意味着函数重载或运算符重载。在这里查看:

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

至于参数多态性,模板函数也可以算在内,因为它们不一定要接受FIXED类型的参数。例如,一个函数可以对整数数组进行排序,还可以对字符串数组进行排序等。

http://en.wikipedia.org/wiki/Parametric_polymorphism


1
不幸的是,尽管正确,但这是令人误解的。由于SFINAE规则,模板函数可以获得隐式约束-使用模板中的操作隐式约束多态性-并且模板专业化可以提供临时替代模板,这些模板可替代更通用的模板。因此,模板(默认情况下)提供不受约束的参数多态性,但是并没有强制执行-至少有两种方法可以使其成为受限或临时的。
Steve314

实际上,您的示例-排序-表示约束。排序仅适用于有序的类型(即提供<和类似的运算符)。在Haskell中,您将使用class明确表达该要求Ord。您会<根据特定类型(由的实例提供Ord)而有所不同的事实被认为是即席多态性。
Steve314

2

这可能无济于事,但是我这样做是为了向我的朋友介绍编程,方法是给出已定义的函数(例如START和)和ENDmain函数,因此它并不太令人生畏(它们仅使用main.cpp文件)。它包含多态类和结构,模板,向量,数组,预处理程序指令,友谊,运算符和指针(在尝试多态之前,您可能应该了解所有这些信息):

注意:尚未完成,但您可以理解

main.cpp

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

主文件

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};

1

这是使用多态类的基本示例

#include <iostream>

class Animal{
public:
   Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
    virtual void Speak(){
        std::cout << "I am an animal called " << name_ << std::endl;
    }
    const char* name_;
};

class Dog : public Animal{
public:
    Dog(const char* Name) : Animal(Name) {/*...*/}
    void Speak(){
        std::cout << "I am a dog called " << name_ << std::endl;
    }
};

int main(void){
    Animal Bob("Bob");
    Dog Steve("Steve");
    Bob.Speak();
    Steve.Speak();
    //return (0);
}

0

多态性意味着许多形式,因此它用于操作员在不同情况下采取不同的行动。多态性用于实现继承。例如,我们为类形状定义了一个fn draw(),然后可以为绘制圆形,矩形,三角形和其他形状实现绘制fn。(属于类形状的对象)


-3

如果有人对这些人说

The Surgeon
The Hair Stylist
The Actor

会发生什么?

The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.

因此,上面的表示显示了什么是OOP中的多态(同名,不同的行为)。

如果您要去面试,而面试官要求您在我们坐在的同一个房间里讲/展示多态性的实况示例,说-

答案-门/窗户

想知道如何?

通过门/窗户-一个人可以来,空气可以来,光可以来,雨可以来,等等。

即一种形式不同的行为(多态)。

为了更好地理解它并以一种简单的方式,我使用了上面的示例。如果您需要代码参考,请遵循上面的答案。


如前所述,为了更好地理解c ++中的多态性,我使用了上面的示例。这可能有助于新生在面试时实际理解和关联代码的含义或含义。谢谢!
桑契特2014年

op问“ C ++中的多态性”。您的答案太抽象了。
StahlRat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.