133

AFAIK:

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

  • 虚函数
  • 函数名重载
  • 运算符重载

除了以上三种类型的多态性,还有其他类型的多态性:

  • 运行
  • 编译时
  • 特设多态性
  • 参数多态性

我知道运行时多态可以通过虚函数实现 ,静态多态可以通过模板函数实现

但是对于另外两个

临时多态性:

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

参数多态性:

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

我几乎无法理解他们:(

如果可能的话,谁能用一个例子来解释它们?我希望这个问题的答案对他们大学的许多新学员有所帮助。

4

7 回答 7

224

对多态性的理解/要求

要理解多态性——正如计算科学中使用的术语——它有助于从一个简单的测试和定义开始。考虑:

    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),或者生成的代码可能会在编译或链接的某个时刻被丢弃。(上述所有机制,虚拟调度除外

  • 支持哪些类型?

    • 即席意味着您提供明确的代码来支持每种类型(例如重载、模板专业化);您明确添加了“for this”(根据ad hoc的含义)类型、其他一些“this”,也许还有“that”的支持;-)。
    • 参数化意味着您可以尝试将函数用于各种参数类型,而无需专门做任何事情来启用对它们的支持(例如模板、宏)。具有像模板/宏期望1的函数/运算符的对象 模板/宏完成其工作所需的全部,确切的类型无关紧要。C++20 引入的“概念”表达并强制执行此类期望 - 请参阅此处的cppreference页面

      • 参数多态性提供了鸭子类型——这个概念归因于 James Whitcomb Riley,他显然说过“当我看到一只鸟像鸭子一样走路、像鸭子一样游泳、像鸭子一样叫声时,我称那只鸟为鸭子。” .

        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++ 标准中,多态仅指使用虚拟分派的运行时多态。一般比较 科学。根据 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 设置等选择实现,
  • 实现在运行时会有所不同,例如状态机模式。

当运行时多态性没有明确的驱动程序时,编译时选项通常更可取。考虑:

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

支持多态性的其他机制

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

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

这个答案最后讨论了上述内容如何结合以增强和简化多态代码 - 特别是参数多态性(模板和宏)。

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

> 隐式编译器提供的重载

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

  • 特定类型的 CPU 指令
  • 相同类型的结果。

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

std::string x;
int y = 0;

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

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

> 标准转换

C++ 标准的第四部分描述了标准转换。

第一点总结得很好(来自旧草案 - 希望仍然基本正确):

-1- 标准转换是为内置类型定义的隐式转换。Clause 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转换运算符的帮助下,对象在布尔上下文中进行评估。这可以在概念上与上述主题中的标准转换中的“整体促销”等进行分组。

隐式构造函数有效地做同样的事情,但由 cast-to 类型控制:

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;
}

正是由于这些小小的“便利功能”,它可以很容易地为intor实例化double并按预期工作。如果没有这些特性,我们将需要显式转换、类型特征和/或策略类,一些冗长、容易出错的混乱,例如:

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++ 中的典型情况一样,程序员有很大的自由来控制使用多态性的边界。

于 2011-05-02T08:11:31.490 回答
15

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

|----------------------+--------------|
| 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++ 中不太适用。即使是这样...

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

Ad-hoc 多态性是 ad-hoc,因为您根据特定类型提供不同的代码。

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

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

|------------+---------------|
| 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类的成员 - 行为类似于数字的类型。该约束允许您使用这些值做一些与数字相关的事情,例如添加它们。即使3is 多态类型推断也表明您的意思是3of 类型a

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

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

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

因此,在某种程度上,C++ 具有参数多态性,但它受到隐式约束并且可以被临时替代方案覆盖——即这种分类实际上不适用于 C++。

于 2013-11-17T08:15:41.720 回答
2

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

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

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

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

于 2011-05-02T07:44:35.723 回答
2

这可能没有任何帮助,但我这样做是为了向我的朋友介绍编程,方法是给出定义的函数,比如START, 和ENDmain 函数,所以它并不太令人生畏(他们只使用了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;
            }
        }
    }
};
于 2013-12-27T18:45:40.120 回答
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);
}
于 2013-12-26T09:43:06.857 回答
0

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

于 2016-08-25T21:43:19.757 回答
-3

如果有人对这些人说 CUT

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 中的多态性(同名,不同的行为)。

如果你要去面试,面试官要求你在我们坐的同一个房间里讲述/展示一个多态性的活生生的例子,比如说 -

答案 - 门/窗

想知道如何?

通过门/窗——人可以进来,空气可以进来,光可以进来,雨可以进来,等等。

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

为了更好地理解它,我使用了上面的例子。如果您需要参考代码,请按照上面的答案。

于 2014-11-15T11:55:00.667 回答