C + + 中的多态性

AFAIK:

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

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

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

  • 运行时间
  • 编译时
  • 自组织多态性
  • 参数多态

我知道 运行时多态性可以通过 < em > 虚函数来实现 静态多态性静态多态性可以通过 < em > 模板函数来实现

但是对于另外两个

Ad-hoc 多态性:

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

参数多态

如果所有代码都没有提到任何特定的类型,因此可以透明地与任意数量的新类型一起使用,那么它就被称为参数多态。

我几乎不能理解他们:

如果可能的话,有人能举个例子解释一下吗? 我希望这些问题的答案能对他们学校的许多新学生有所帮助。

57266 次浏览

至于 ad-hoc 多态性,它的意思是函数重载或运算符重载:

Http://en.wikipedia.org/wiki/ad-hoc_polymorphism

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

Http://en.wikipedia.org/wiki/parametric_polymorphism

理解/需求多态性

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

    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!)
    
  • 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';

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

> 标准换算

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

第一点很好地总结了(来自一个旧的草案——希望仍然基本正确) :

-1-标准转换是为内置类型定义的隐式转换。子句 conv 枚举此类转换的完整集。标准转换序列是按以下顺序的标准转换序列:

  • 从以下集合进行零或一次转换: lvalue 到 rvalue 转换、数组到指针转换和函数到指针转换。

  • 从以下集合进行零或一次转换: 整数提升、浮点提升、整数转换、浮点转换、浮点-整数转换、指针转换、指向成员转换和布尔转换。

  • 零或一个资格转换。

[注意: 一个标准的转换序列可以是空的,也就是说,它可以包含没有转换。]如果需要将表达式转换为所需的目标类型,则将对表达式应用标准转换序列。

这些转换允许下列代码:

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。这可以在概念上与上述主题中的标准转换中的“整合促销”等分组。

隐式构造函数有效地执行同样的操作,但是由 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;
}

由于这些小小的“便利特性”,它可以很容易地为 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 + + 中的典型情况一样,程序员可以自由地控制使用多态性的边界。

在 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 + + 中并不能很好地工作。即便如此。

参数多态意味着你使用类型作为参数,不管你使用什么类型作为参数,都使用完全相同的代码。

Ad-hoc 多态性是 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类类型的成员。该约束允许您对这些值进行数字化处理,例如添加它们。甚至 3是多态类型推断,你指的是 a类型的 3

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

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

如果每个模板参数的类型都是 typename,那么 c + + 就非常接近模板的参数多态了。有类型参数,并且不管使用哪种类型,都有一个单一的实现。但是,“替换失败不是错误”规则意味着隐式约束是在模板中使用操作的结果。其他复杂性包括模板专门化,用于提供替代模板——不同的(特别的)实现。

所以在某种程度上,c + + 有自己的参数多态,但是它受到隐式约束,并且可以被特定的替代方案所覆盖——即这种分类方法并不适用于 c + + 。

下面是一个使用多态类的基本示例

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

这可能没有任何帮助,但我这样做是为了向我的朋友介绍编程,给出定义函数,如 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

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

如果有人对这些人说卡

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

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

应答-门/窗

想知道怎么做?

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

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

为了更好地理解它,我用了上面的例子。.如果代码需要参考,请遵循以上答案。

多态性意味着操作符可以在不同的实例下以不同的方式进行操作。多态性用于实现继承。对于 ex,我们已经为一个类形状定义了一个 fn 绘制() ,然后绘制 fn 可以实现为绘制圆形,盒子,三角形和其他形状。(这是类形状的对象)