C++ 中的多态性

发布于 2024-11-04 20:01:44 字数 727 浏览 4 评论 0原文

AFAIK:

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

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

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

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

知道运行时多态性可以是通过虚拟函数实现 和静态多态性可以通过模板函数来实现,

但是对于另外两种

  • 即席多态性
  • ,参数多态性 网站说

广告-临时多态性:

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

参数多态性:

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

我几乎无法理解它们:(

如果可能的话,谁能用一个例子来解释它们呢? 我希望这个问题的答案能够对许多刚从大学毕业的学生有所帮助。

AFAIK:

C++ provides three different types of polymorphism.

  • Virtual functions
  • Function name overloading
  • Operator overloading

In addition to the above three types of polymorphism, there exist other kinds of polymorphism:

  • run-time
  • compile-time
  • ad-hoc polymorphism
  • parametric polymorphism

I know that runtime polymorphism can be achieved by virtual functions
and static polymorphism can be achieved by template functions

But for the other two

ad-hoc polymorphism:

If the range of actual types that can be used is finite and the combinations must be individually specified prior to use, this is called ad-hoc polymorphism.

parametric polymorphism:

If all code is written without mention of any specific type and thus can be used transparently with any number of new types it is called parametric polymorphism.

I can hardly understand them :(

can anyone explain them both if possible with an example?
I hope the answers to this questions would be helpful for many new passouts from their colleges.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

魂牵梦绕锁你心扉 2024-11-11 20:01:44

多态性的理解/要求

要理解多态性(计算科学中使用的术语),从简单的测试和定义开始会有所帮助。考虑:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

这里,f() 用于执行某些操作,并被赋予值 xy 作为输入。

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


C++ 多态性机制

显式程序员指定的多态性

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

  • 预处理:

    #define f(X) ((X) += 2)
    //(注意:在实际代码中,请为宏使用更长的大写名称!)
    
  • 重载:

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

    模板 <类型名称 T>
    void f(T&x) { x += 2; }
    
  • 虚拟调度:

    struct Base { 虚拟 Base&运算符+=(int) = 0; };
    
    结构X:基础
    {
        X(int n) : n_(n) { }
        X&运算符+=(int n) { n_ += n;返回*这个; }
        整数n_;
    };
    
    结构 Y :基础
    {
        Y(双 n) : n_(n) { }
        Y&运算符+=(int n) { n_ += n;返回*这个; }
        双n_;
    };
    
    void f(Base&x) { x += 2; } // 运行时多态调度
    

其他相关机制

编译器为内置类型提供的多态性、标准转换和强制转换/强制转换将在稍后讨论以保证完整性:

  • 无论如何,它们通常都可以直观地理解(保证“”哦,“反应”),
  • 它们影响了上述机制的需求阈值和使用的无缝性,并且
  • 解释会严重分散对更重要概念的注意力。

术语

进一步分类

考虑到上面的多态机制,我们可以通过多种方式对它们进行分类:

  • 什么时候选择多态类型特定的代码?

    • 运行时意味着编译器必须为程序在运行时可能处理的所有类型生成代码,并在运行时选择正确的代码(虚拟调度
    • 编译时意味着在编译期间选择特定于类型的代码。这样做的结果是:假设上面的程序仅使用 int 参数调用 f - 根据所使用的多态机制和内联选择,编译器可能会避免为 生成任何代码f(double),或者生成的代码可能在编译或链接的某个时刻被丢弃。 (除虚拟调度之外的所有机制
  • 支持哪些类型?

    • Ad-hoc意味着您提供显式代码来支持每种类型(例如重载、模板专门化);您明确添加支持“为此”(根据临时的含义)类型,其他一些“这个”,也许还有“那个”;-)。
    • 参数化意味着您可以尝试将该函数用于各种参数类型,而无需专门执行任何操作来启用其对它们的支持(例如模板、宏)。具有类似于模板/宏所期望的函数/运算符的对象1是模板/宏完成其工作所需的全部内容,具体类型无关紧要。 C++20 引入的“概念”表达并强制执行此类期望 - 请参阅 cppreference 页面位于此处

      • 参数多态性提供了鸭子类型 - 这一概念源自 James Whitcomb Riley,他显然说过“当我看到一只像鸭子一样行走、像鸭子一样游泳并嘎嘎叫的鸟时就像鸭子一样,我称那只鸟为鸭子。”.

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

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

“多态”

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* 处理的异构对象集合吐出,
  • 在运行时根据配置文件、命令行开关、UI 设置等选择实现,
  • 实现在运行时会有所不同,例如状态机模式。

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

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

支持多态性的其他机制

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

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

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

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

>编译器提供的隐式重载

从概念上讲,编译器重载了内置类型的许多运算符。它在概念上与用户指定的重载没有什么不同,但被列出是因为它很容易被忽视。例如,您可以使用相同的符号 x += 2 添加到 intdouble 中,编译器会生成:

  • 特定于类型的 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)生成类型适当的代码,以将 42 转换为 <代码>42.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 <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++ 中的典型情况一样,程序员有很大的自由来控制多态性的使用范围。

Understanding of / requirements for polymorphism

To understand polymorphism - as the term is used in Computing Science - it helps to start from a simple test for and definition of it. Consider:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Here, f() is to perform some operation and is being given values x and y as inputs.

To exhibit polymorphism, f() must be able to operate with values of at least two distinct types (e.g. int and double), finding and executing distinct type-appropriate code.


C++ mechanisms for polymorphism

Explicit programmer-specified polymorphism

You can write f() such that it can operate on multiple types in any of the following ways:

  • Preprocessing:

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

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

    template <typename T>
    void f(T& x) { x += 2; }
    
  • Virtual dispatch:

    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
    

Other related mechanisms

Compiler-provided polymorphism for builtin types, Standard conversions, and casting/coercion are discussed later for completeness as:

  • they're commonly intuitively understood anyway (warranting a "oh, that" reaction),
  • they impact the threshold in requiring, and seamlessness in using, the above mechanisms, and
  • explanation is a fiddly distraction from more important concepts.

Terminology

Further categorisation

Given the polymorphic mechanisms above, we can categorise them in various ways:

  • When is the polymorphic type-specific code selected?

    • Run time means the compiler must generate code for all the types the program might handle while running, and at run-time the correct code is selected (virtual dispatch)
    • Compile time means the choice of type-specific code is made during compilation. A consequence of this: say a program only called f above with int arguments - depending on the polymorphic mechanism used and inlining choices the compiler might avoid generating any code for f(double), or generated code might be thrown away at some point in compilation or linking. (all mechanisms above except virtual dispatch)
  • Which types are supported?

    • Ad-hoc meaning you provide explicit code to support each type (e.g. overloading, template specialisation); you explicitly add support "for this" (as per ad hoc's meaning) type, some other "this", and maybe "that" too ;-).
    • Parametric meaning you can just try to use the function for various parameter types without specifically doing anything to enable its support for them (e.g. templates, macros). An object with functions/operators that act like the template/macro expects1 is all that template/macro needs to do its job, with the exact type being irrelevant. The "concepts" introduced by C++20 express and enforce such expectations - see cppreference page here.

      • Parametric polymorphism provides duck typing - a concept attributed to James Whitcomb Riley who apparently said "When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.".

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • Subtype (aka inclusion) polymorphism allows you to work on new types without updating the algorithm/function, but they must be derived from the same base class (virtual dispatch)

1 - Templates are extremely flexible. SFINAE (see also std::enable_if) effectively allows several sets of expectations for parametric polymorphism. For example, you might encode that when the type of data you're processing has a .size() member you'll use one function, otherwise another function that doesn't need .size() (but presumably suffers in some way - e.g. using the slower strlen() or not printing as useful a message in the log). You can also specify ad-hoc behaviours when the template is instantiated with specific parameters, either leaving some parameters parametric (partial template specialisation) or not (full specialisation).

"Polymorphic"

Alf Steinbach comments that in the C++ Standard polymorphic only refers to run-time polymorphism using virtual dispatch. General Comp. Sci. meaning is more inclusive, as per C++ creator Bjarne Stroustrup's glossary (http://www.stroustrup.com/glossary.html):

polymorphism - providing a single interface to entities of different types. Virtual functions provide dynamic (run-time) polymorphism through an interface provided by a base class. Overloaded functions and templates provide static (compile-time) polymorphism. TC++PL 12.2.6, 13.6.1, D&E 2.9.

This answer - like the question - relates C++ features to the Comp. Sci. terminology.

Discussion

With the C++ Standard using a narrower definition of "polymorphism" than the Comp. Sci. community, to ensure mutual understanding for your audience consider...

  • using unambiguous terminology ("can we make this code reusable for other types?" or "can we use virtual dispatch?" rather than "can we make this code polymorphic?"), and/or
  • clearly defining your terminology.

Still, what's crucial to being a great C++ programmer is understanding what polymorphism's really doing for you...

    letting you write "algorithmic" code once and then apply it to many types of data

...and then be very aware of how different polymorphic mechanisms match your actual needs.

Run-time polymorphism suits:

  • input processed by factory methods and spat out as an heterogeneous object collection handled via Base*s,
  • implementation chosen at runtime based on config files, command line switches, UI settings etc.,
  • implementation varied at runtime, such as for a state machine pattern.

When there's not a clear driver for run-time polymorphism, compile-time options are often preferable. Consider:

  • the compile-what's-called aspect of templated classes is preferable to fat interfaces failing at runtime
  • SFINAE
  • CRTP
  • optimisations (many including inlining and dead code elimination, loop unrolling, static stack-based arrays vs heap)
  • __FILE__, __LINE__, string literal concatenation and other unique capabilities of macros (which remain evil ;-))
  • templates and macros test semantic usage is supported, but don't artificially restrict how that support is provided (as virtual dispatch tends to by requiring exactly matching member function overrides)

Other mechanisms supporting polymorphism

As promised, for completeness several peripheral topics are covered:

  • compiler-provided overloads
  • conversions
  • casts/coercion

This answer concludes with a discussion of how the above combine to empower and simplify polymorphic code - especially parametric polymorphism (templates and macros).

Mechanisms for mapping to type-specific operations

> Implicit compiler-provided overloads

Conceptually, the compiler overloads many operators for builtin types. It's not conceptually different from user-specified overloading, but is listed as it's easily overlooked. For example, you can add to ints and doubles using the same notation x += 2 and the compiler produces:

  • type-specific CPU instructions
  • a result of the same type.

Overloading then seamlessly extends to user-defined types:

std::string x;
int y = 0;

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

Compiler-provided overloads for basic types is common in high-level (3GL+) computer languages, and explicit discussion of polymorphism generally implies something more. (2GLs - assembly languages - often require the programmer to explicitly use different mnemonics for different types.)

> Standard conversions

The C++ Standard's fourth section describes Standard conversions.

The first point summarises nicely (from an old draft - hopefully still substantially correct):

-1- Standard conversions are implicit conversions defined for built-in types. Clause conv enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:

  • Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.

  • Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.

  • Zero or one qualification conversion.

[Note: a standard conversion sequence can be empty, i.e., it can consist of no conversions. ] A standard conversion sequence will be applied to an expression if necessary to convert it to a required destination type.

These conversions allow code such as:

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

a(3.14);
a(42);

Applying the earlier test:

To be polymorphic, [a()] must be able to operate with values of at least two distinct types (e.g. int and double), finding and executing type-appropriate code.

a() itself runs code specifically for double and is therefore not polymorphic.

But, in the second call to a() the compiler knows to generate type-appropriate code for a "floating point promotion" (Standard §4) to convert 42 to 42.0. That extra code is in the calling function. We'll discuss the significance of this in the conclusion.

> Coercion, casts, implicit constructors

These mechanisms allow user-defined classes to specify behaviours akin to builtin types' Standard conversions. Let's have a look:

int a, b;

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

Here, the object std::cin is evaluated in a boolean context, with the help of a conversion operator. This can be conceptually grouped with "integral promotions" et al from the Standard conversions in the topic above.

Implicit constructors effectively do the same thing, but are controlled by the cast-to type:

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

Implications of compiler-provided overloads, conversions and coercion

Consider:

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

If we want the amount x to be treated as a real number during the division (i.e. be 6.5 rather than rounded down to 6), we only need change to typedef double Amount.

That's nice, but it wouldn't have been too much work to make the code explicitly "type correct":

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

But, consider that we can transform the first version into a template:

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

It's due to those little "convenience features" that it can be so easily instantiated for either int or double and work as intended. Without these features, we'd need explicit casts, type traits and/or policy classes, some verbose, error-prone mess like:

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

So, compiler-provided operator overloading for builtin types, Standard conversions, casting / coercion / implicit constructors - they all contribute subtle support for polymorphism. From the definition at the top of this answer, they address "finding and executing type-appropriate code" by mapping:

  • "away" from parameter types

    • from the many data types polymorphic algorithmic code handles

    • to code written for a (potentially lesser) number of (the same or other) types.

  • "to" parametric types from values of constant type

They do not establish polymorphic contexts by themselves, but do help empower/simplify code inside such contexts.

You may feel cheated... it doesn't seem like much. The significance is that in parametric polymorphic contexts (i.e. inside templates or macros), we're trying to support an arbitrarily large range of types but often want to express operations on them in terms of other functions, literals and operations that were designed for a small set of types. It reduces the need to create near-identical functions or data on a per-type basis when the operation/value is logically the same. These features cooperate to add an attitude of "best effort", doing what's intuitively expected by using the limited available functions and data and only stopping with an error when there's real ambiguity.

This helps limit the need for polymorphic code supporting polymorphic code, drawing a tighter net around the use of polymorphism so localised use doesn't force widespread use, and making the benefits of polymorphism available as needed without imposing the costs of having to expose implementation at compile time, have multiple copies of the same logical function in the object code to support the used types, and in doing virtual dispatch as opposed to inlining or at least compile-time resolved calls. As is typical in C++, the programmer is given a lot of freedom to control the boundaries within which polymorphism is used.

无边思念无边月 2024-11-11 20:01:44

在 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 也是多态的 - 类型推断表明您指的是 a 类型的 3

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

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

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

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

In C++, the important distinction is run-time vs. compile-time binding. Ad-hoc vs. parametric doesn't really help, as I'll explain later.

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

Note - run-time polymorphism may still be resolved at compile-time, but that's just optimization. Needing to support run-time resolution efficiently, and trading off against other issues, is part of what led to virtual functions being what they are. And that's really key for all forms of polymorphism in C++ - each arises from different sets of trade-offs made in a different context.

Function overloading and operator overloading are the same thing in every way that matters. The names and the syntax for using them doesn't affect polymorphism.

Templates allow you to specify lots of function overloads at once.

There's another set of names for the same resolution-time idea...

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

These names are more associated with OOP, so it's a bit odd to say that a template or other non-member function uses early binding.

To better understand the relationship between virtual functions and function overloading, it's also useful to understand the difference between "single dispatch" and "multiple dispatch". The idea can be understood as a progression...

  • First, there are monomorphic functions. The implementation of the function is uniquely identified by the function name. None of the parameters is special.
  • Then, there is single dispatch. One of the parameters is considered special, and used (along with the name) to identify which implementation to use. In OOP, we tend to think of this parameter as "the object", list it before the function name etc.
  • Then, there is multiple dispatch. Any/all parameters contribute to identifying which implementation to use. Therefore, once again, none of the parameters needs to be special.

There's obviously more to OOP than an excuse to nominate one parameter as special, but that is one part of it. And relating back to what I said about trade-offs - single dispatch is quite easy to do efficiently (the usual implementation is called "virtual tables"). Multiple dispatch is more awkward, not just in terms of efficiency, but also for separate compilation. If you're curious, you might look up "the expression problem".

Just as it's a bit odd to use the term "early binding" for non-member functions, it's a bit odd to use the terms "single dispatch" and "multiple dispatch" where polymorphism is resolved at compile-time. Usually, C++ is considered not to have multiple dispatch, which is considered a particular kind of run-time resolution. However, function overloading can be seen as multiple-dispatch done at compile-time.

Getting back to parametric vs. ad-hoc polymorphism, these terms are more popular in functional programming, and they don't quite work in C++. Even so...

Parametric polymorphism means that you have types as parameters, and the exact same code is used irrespective of what type you use for those parameters.

Ad-hoc polymorphism is ad-hoc in the sense that you provide different code depending on the particular types.

Overloading and virtual functions are both examples of ad-hoc polymorphism.

Again, there's some synonyms...

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

Except these aren't quite synonyms, though they're commonly treated as though they were, and that's where confusion is likely to arise in C++.

The reasoning behind treating these as synonyms is that by constraining polymorphism to particular classes of types, it becomes possible to use operations specific to those classes of types. The word "classes" here can be interpreted in the OOP sense, but really just refers to (usually named) sets of types that share certain operations.

So parametric polymorphism is usually taken (at least by default) to imply unconstrained polymorphism. Because the same code is used irrespective of the type parameters, the only supportable operations are those that work for all types. By leaving the set of types unconstrained, you severely limit the set of operations you can apply to those types.

In e.g. Haskell, you can have...

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

The a here is an unconstrained polymorphic type. It could be anything, so there's not much we can do with values of that type.

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

Here, a is constrained to be a member of the Num class - types that act like numbers. That constraint allows you to do number-ish things with those values, such as add them. Even the 3 is polymorphic - type inference figures out that you mean the 3 of type a.

I think of this as constrained parametric polymorphism. There's only one implementation, but it can only be applied in constrained cases. The ad-hoc aspect is the choice of which + and 3 to use. Each "instance" of Num has it's own distinct implementation of these. So even in Haskell "parametric" and "unconstrained" aren't really synonyms - don't blame me, it's not my fault!

In C++, both overloading and virtual functions are ad-hoc polymorphism. The definition of ad-hoc polymorphism doesn't care whether the implementation is selected at run-time or compile-time.

C++ gets very close to parametric polymorphism with templates if every template parameter has type typename. There are type parameters, and there's a single implementation no matter which types are used. However, the "Substitution Failure Is Not An Error" rule means that implicit constraints arise as a result of using operations within the template. Additional complications include template specialization for providing alternative templates - different (ad-hoc) implementations.

So in a way C++ has parametric polymorphism, but it's implicitly constrained and could be overridden by ad-hoc alternatives - ie this classification doesn't really work for C++.

拔了角的鹿 2024-11-11 20:01:44

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

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

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

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

As to ad-hoc polymorphism, it means function overloading or operator overloading. Check out here:

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

As to parametric polymorphism, template functions can also be counted in because they don't necessarily take in parameters of FIXED types. For example, one function can sort array of integers and it can also sort array of strings, etc.

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

握住你手 2024-11-11 20:01:44

这可能没有任何帮助,但我这样做是为了向我的朋友介绍编程,通过给出定义的函数,例如主函数的 STARTEND,所以它是并不太令人畏惧(他们只使用了 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

main.h

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

This may not be of any help, but I made this to introduce my friends to programming by giving out defined functions, like START, and END for the main function so it was not too daunting (they only used the main.cpp file). It contains Polymorphic classes and structs, templates, vectors, arrays, preproccessor directives, friendship, operators and pointers (all of which you should probably know before attempting polymorphism):

Note: It is not finished, but you can get the idea

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

main.h

#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;
            }
        }
    }
};
初见 2024-11-11 20:01:44

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

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

Here is a basic example using Polymorphic classes

#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);
}
無處可尋 2024-11-11 20:01:44

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

Polymorphism means many forms as such it is used for an operator to act differently under different instances. Polymorphism is used to implement inheritance. For ex, we have defined a fn draw () for a class shape then the draw fn can be implemented for drawing circle, box, triangle and other shapes. ( which are objects of the class shape)

最近可好 2024-11-11 20:01:44

如果有人对这些人说“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 中的多态性(相同的名称,不同的行为)。

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

-答案-门/窗

想知道如何?

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

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

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

If anybody says CUT to these people

The Surgeon
The Hair Stylist
The Actor

What will happen?

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.

So above representation shows What is polymorphism (same name, different behavior) in OOP.

If you are going for an interview and interviewer asks you tell/show a live example for polymorphism in the same room we are sitting at, say-

Answer - Door / Windows

Wondering How?

Through Door / Window - a person can come, air can come, light can come, rain can come, etc.

i.e. One form different behavior(Polymorphism).

To understand it better and in a simple manner I used above example.. If you need reference for code follow above answers.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文