计算机术语多态意思,C ++中的多态性

据我所知:

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

虚拟功能

函数名称重载

运算符重载

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

运行

编译时间

ad-hoc多态性

参数多态性

我知道运行时多态性可以通过虚函数来实现

静态多态性可以通过模板函数实现

但对于另外两个

ad-hoc多态性

参数多态性

该网站说,

ad-hoc多态性:

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

参数多态性:

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

我几乎无法理解他们:(

任何人都可以用一个例子解释他们两个吗?

我希望这些问题的答案对他们大学的许多新的消息有所帮助。

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

因此,我提到的网站似乎误导了许多......我是否正确?

@zombie:该网站涉及很多好的概念,但在使用术语方面并不精确和一致(例如,一旦开始谈论虚拟调度/运行时多态性,它就会产生很多关于多态性的陈述,这些陈述一般都是错误的但对于虚拟调度来说却是如此)。如果您已经理解了这个主题,您可以了解所说的内容和精神上插入必要的警告,但通过阅读网站很难到达那里....

有些术语是近似同义词,或者比其他术语更相关但更受限制。例如,根据我的经验,术语"ad-hoc多态"主要用于Haskell,但"虚函数"非常密切相关。细微差别在于"虚函数"是一个面向对象的术语,指的是具有"后期绑定"的成员函数。"多次发送"也是一种特殊的多态性。正如FredOverflow所说,运算符和函数重载基本上都是一样的。

我为你修改了你的格式。请阅读编辑窗格右侧的可用帮助。有> 200个问题且> 3k的人应该知道这个基本的东西。此外,您可能想购买新的键盘。这个转移关键似乎是间歇性的失败。哦,并且:在C ++中没有"模板函数"这样的东西。但是,有功能模板。

@ sbi,人们在采访中面对这个问题,"模板功能和功能模板之间有什么区别"?如果没有这样的话,他们为什么要问? comeaucomputing.com/techtalk/templates/#terms

@zombie:在那篇文章中,Greg解释说,通常称为"模板功能"的是"功能模板"。他说功能模板实例可以称为"模板功能"。你是否以这种方式使用这个词?我建议不要使用它。这是误导,因为它被错误地使用了。

好的我同意我刚才提到的网站,因为你的评论"没有"模板功能"

@zombie:我不知道Greg使用过它,这是我第一次听到它的使用方式,这就是为什么我认为不会有这样的野兽。

@sbi - 只是澄清 - 你是说"功能模板"是未实例化的模板,而"模板功能"是(最好避免使用的术语)实例化的功能?混乱似乎对我来说是可以理解的。通过英语语法,通常有一个"xy" - >"y的x"替换模式用于名词一起使用,所以"函数模板" - >"函数模板"是有意义的,但"模板函数" - >"模板的功能"似乎并没有真正暗示"实例化"给我。如果有的话,它使用"函数"的不同含义来暗示"模板的目的"。

@Steve:一个功能模板是一个模板,您可以从中生成功能(就像桌布是一块布在桌子上的布料,婴儿油是由婴儿制成的油:))。现在,Greg说,模板函数是一个从模板生成的函数。我说这是一个相当不错的术语,除了使用术语模板功能的其他人正在错误地使用它来引用功能模板。

@sbi - 从你的双关语中删除双关语,婴儿油是为婴儿制造的油。我从学校记得的x模式的x y - > y大概是一个模糊的概括,其中"of"通常应该用更具体的东西代替。你对歧义的主张对我来说似乎很强烈,但在双关语之外,我们不会说"婴儿油"含糊不清 - 有一个普遍接受的含义,所以多词这个术语本身就是一个单词。"模板功能"是否有普遍接受的含义?错误地使用"模板功能"的常见程度如何?

@Steve:我知道为什么它被称为婴儿油,这只是一个笑话,并没有使我所说的无效。"类模板"不含糊不清("类的模板"似乎是正确的),它是正确的术语。正如我写的那样,Gregs术语是我第一次遇到"模板类",它没有(错误地)引用类模板,我经常听到它称为"模板类"。

@sbi - 对不起,如果遇到错误的话。如果我只是评论"常见的"模板功能"错误使用?"它不清楚我得到了什么。我知道你在开玩笑,而且大多数情况下我并不打算让你说的话无效 - 我只是在提出我的问题和建议。关于但人类语言是由共同使用方面定义的,这只是我的一个痴迷的口号 - 一个反复出现的笑话(尽管有一点背后),你不应该亲自接受。

@Steve:现在我读了它,我发现我的回复可能比当时的意思更加严厉。我道歉。无论如何,它经常被错误地使用,所以经常使用错误而不是经常使用错误。但我仍然认为这是错误的。首先是出于迂腐的原因(每个C ++都是一个学者),但也出于教学原因:术语"模板类",当应用于类模板时,错误地暗示事物是一个类,而不是,通常会让新手混淆他们试图使用需要类的模板。

@sbi:仅仅是为了讨论 - 并且值得所有2c恕我直言 - 另一个可能的原因,有些人可能会因为"模板"而导致领先,在源代码中按顺序遇到关键字:(模板...函数)和(模板......类)。

了解/多态性的要求

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

Type1 x;

Type2 y;

f(x);

f(y);

这里,f()是执行某些操作,并且给出值x和y作为输入。

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.

Ok.

多态的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

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

其他相关机制

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

无论如何,它们通常被直观地理解(保证"哦,那个"反应),

它们影响了要求的门槛,以及使用上述机制的无缝性,以及

解释是对更重要概念的一种分散注意力。

术语

进一步分类

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

何时选择了多态类型特定代码?

运行时意味着编译器必须为程序在运行时可能处理的所有类型生成代码,并且在运行时选择正确的代码(虚拟调度)

编译时间意味着在编译期间选择特定于类型的代码。这样做的结果是:假设一个程序仅使用int参数调用上面的f - 取决于所使用的多态机制和内联选择,编译器可能会避免为f(double)生成任何代码,或者生成的代码可能会丢弃一些指向编译或链接。 (除虚拟调度外的所有上述机制)

支持哪些类型?

Ad-hoc意味着您提供显式代码来支持每种类型(例如重载,模板专门化);你明确地添加支持"为此"(根据ad hoc的意思)类型,其他一些"这个",也许"那个"也是;-)。

参数含义您可以尝试将该函数用于各种参数类型,而无需专门执行任何操作来启用它对它们的支持(例如模板,宏)。具有与模板/宏期望1相同的函数/运算符的对象是模板/宏需要完成其工作的所有对象,其确切类型无关紧要。从C ++ 11中删除的"概念"有助于表达和实施这些期望 - 让我们希望它们成为后来的标准。

参数多态提供了鸭子打字 - 这个概念归功于James Whitcomb Riley,他明显地说:"当我看到一只像鸭子一样行走的小鸟,像鸭子一样游泳,像鸭子一样呱呱叫,我称这只鸟为鸭子。"

template

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),意思更具包容性:

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.

Ok.

这个答案 - 就像问题一样 - 将C ++特性与Comp相关联。科学。术语。

讨论

使用C ++标准使用比"Comporp"更窄的"多态"定义。科学。社区,以确保您的受众的相互理解考虑...

使用明确的术语("我们可以使这些代码可以重用于其他类型吗?"或"我们可以使用虚拟调度吗?"而不是"我们可以使这段代码具有多态性吗?")和/或

明确定义您的术语。

但是,成为一名优秀的C ++程序员至关重要的是理解多态性真正为你做的事情......

让你编写一次"算法"代码,然后将其应用于许多类型的数据

...然后非常清楚不同的多态机制如何与您的实际需求相匹配。

运行时多态性适合:

输入由工厂方法处理并作为通过Base* s处理的异构对象集合吐出,

基于配置文件,命令行开关,UI设置等在运行时选择的实现,

实现在运行时变化,例如状态机模式。

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

模板化类的compile-what-c??alled方面比运行时失败的fat接口更可取

SFINAE

CRTP

优化(许多包括内联和死代码消除,循环展开,基于静态堆栈的数组vs堆)

__FILE__,__LINE__,字符串文字串联和宏的其他独特功能(仍然是邪恶的;-))

支持模板和宏测试语义使用,但不要人为地限制提供支持的方式(因为虚拟调度往往需要完全匹配的成员函数覆盖)

支持多态性的其他机制

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

编译器提供的重载

转换

铸就/胁迫

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

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

>隐式编译器提供的重载

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

特定于类型的CPU指令

相同类型的结果。

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

std::string x;

int y = 0;

x += 'c';

y += 'c';

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

>标准转换

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

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

-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:

Ok.

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

来自以下集合的零或一次转换:整数促销,浮点促销,积分转换,浮点转换,浮点积分转换,指针转换,成员转换指针和布尔转换。

零或一个资格转换。

[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.

Ok.

这些转换允许以下代码:

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

a(3.14);

a(42);

应用早期测试:

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.

Ok.

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:

template

void f()

{

Amount x = 13;

x /= 2;

std::cout << x * 1.1;

}

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

template

void f()

{

Amount x = Policy::thirteen;

x /= static_cast(2);

std::cout << traits::to_double(x) * 1.1;

}

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

从参数类型"离开"

来自众多数据类型的多态算法代码处理

为(可能较少)(相同或其他)类型编写的代码。

从常量类型的值"到"参数类型

它们本身并不建立多态上下文,但确实有助于在这种上下文中赋予/简化代码。

你可能会觉得被骗了......看起来并不多。重要的是,在参数化多态上下文(即内部模板或宏)中,我们试图支持任意大范围的类型,但通常希望根据为其设计的其他函数,文字和操作来表达对它们的操作。一小组类型。当操作/值在逻辑上相同时,它减少了在每种类型的基础上创建几乎相同的功能或数据的需要。这些功能相互配合,增加了"尽力而为"的态度,通过使用有限的可用功能和数据来做直觉预期的事情,并且只有在存在真正的模糊性时才会停止错误。

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

好。

-1除术语讨论外,答案很棒。 C ++标准在1.8 / 1中定义术语"多态",参考关于虚函数的第10.3节。所以没有摆动的空间,没有讨论的余地,没有个人观点的余地:在标准C ++的背景下,这个术语是一劳永逸地定义的。它确实发挥了实践作用。例如,5.2.7 / 6 about dynamic_cast需要"指向多态类型的指针或左值"。干杯&hth。,

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

www2.research.att.com/~bs/glossary.html

@Tony:这不是你答案的主旨是错误的。没问题,很棒。它只是那个。你得到它的术语:正式的学术术语是由神圣国际标准定义的狭义术语,而人们可能意味着稍微不同的东西的非正式粗略术语是主要用于这个问题和答案的术语。干杯&hth。,

@Alf:我希望答案很好 -"其他机制"需要在五分之一的行中重写,我正在考虑/起草一个更具体的特征 - 与多态机制形成对比的含义。无论如何,我的理解是,正式的学术专用 - 以C ++为重点的意义可能是狭隘的,但正式的学术一般的Comp。科学。意思不是,正如Stroustrups词汇表所证明的那样。我们需要一些明确的东西 - 例如Knuth的定义 - 没有运气,谷歌搜索。我很感谢你是一位C ++大师,但是你能指出相关的相关证据吗?

@Alf:不是它的确定性,而只是通过显示我并非完全孤独;-) en.wikipedia.org/wiki/Polymorphism_(computer_science)

@Tony:我已经指出:标准正式定义了1.8 / 1中的"多态"(当一个术语被内联定义时,它的设置用斜体表示)。对于计算机编程语言,您无法获得比其国际标准更具权威性或更高权威性。例如,如果Bjarne说出与标准(和他有)相矛盾的东西,那么它就是规则的标准,而Bjarne必须鞠躬 - 就像我们所有人一样。但总的来说,使用更一般的基于概念的术语。由于没有正式定义,它是非正式的。不要把学术与正式混为一谈。是吗?

@Alf:这里的第一个问题是问题的主要背景:一个人手持一些Comp。科学。询问C ++特性如何与这些术语相关的术语。他们不是在C ++标准的背景下询问这些术语的含义。这些术语在科学意义上不是绝对的,它们是符号的便利 - 请参阅stackoverflow.com/questions/5387412/作为示例。因此,如果这些含义不同,则值得注意但不是更多。

@Alf:其次,我相信多态性在任何体面的通用Comp中都是正式定义的。科学。以与我的使用(和Stroustrups)兼容的(永恒,稳定)方式预订。维基百科的文章链接了一些以这种方式定义它的学术出版物:"多态函数是其操作数(实际参数)可以有多种类型的函数。多态类型是其操作适用于多种类型的值的类型。" (来自lucacardelli.name/Papers/OnUnderstanding.A4.pdf)。所以,问题是"谁为Comp.Sci说话"......?

@Alf:我不认为Comp内部有任何重大争议。科学。对于那个问题,虽然肯定在较窄的上下文中,如讨论仅提供运行时多态性,C ++标准或OO编程的语言,但采用的是较窄的视图。我从来没有听说过维基百科所链接的大多数学者,所以看到像Knuth这样的人的正式定义会很好。但是,C ++标准基本上是一个工程合同......它以一种传达要求的方式使用术语,而不是用于科学准确性。它不是这样的权威。

Duck-typing是(隐式)约束多态 - 该函数仅适用于支持quacking的类型。 C ++中的SFINAE规则意味着模板以这种方式被隐式约束。"参数"和"不受约束"通常用作同义词WRT多态性。严格地说,即使在像Haskell这样的语言中,受约束的参数多态性也与ad-hoc多态性(ad-hoc在类实例中)是分开的,但趋势是将"无约束"和"参数"视为同义词,因为附带条件不同,在C ++中应用这些术语会让人感到困惑。

@ Steve314:抱歉,我正在努力追随。你是说鸭子打字与参数多态不同,因为它需要支持约束(如承诺的C ++ 0xs Concepts)或基于类型属性的重载分辨率ala SFINAE?在模板实例化或宏扩展获得致命的编译器错误之前,某些测试"支持quacking的类型"的关键区别是指导实现的选择或定制吗?我的google-foos没有出现鸭子打字这方面的先例 - 你知道关于这个的任何定义/文章吗?

@Tony D - 它只是一个观察 - 在阅读这个答案之前,我从未考虑相对于参数/特殊分类的鸭子打字。在Haskell中工作,如果需要约束类型(例如,支持

@Tony D -"ad-hoc"和"约束"倾向于被认为是Haskell中的同义词的原因是因为它们是通过相同语言特征的(不同方面)实现的 - 类型类。如果没有该功能,您所拥有的只是无约束的参数多态,因此它们也被视为同义词。严格地说,在Haskell中,参数多态通过指定类型类约束来约束,并且类型类的方法(单独)是临时的,因为每个类型都使用自己的实现定义自己的实例。

@Tony D - 如果那个精确的形式意义是人们真正意味着什么不是问题,但他们通常不会。无论如何,由于"约束多态"和"ad-hoc多态"经常被视为同义词,我认为重要的是要注意模板中的参数多态(和鸭子类型)是隐式约束的 - 你没有声明你的类型必须支持quacking但是,如果它不能嘎嘎叫它就像鸳鸯一样嘎嘎叫。在Haskell中,约束是显式的。在C ++中,通过SFINAE,它是一个隐式静态约束。在Python中它是一个运行时错误,但仍然是一个约束。

@Tony D - 另外在C ++中,您有模板专业化 - 您可以提供模板的临时替代实现,在特定情况下覆盖它。这是SFINAE"不是错误"方面的一个原因 - 另一个模板可能提供正确的替代方案。这是ad-hoc多态,因为有多个实现 - 多个模板。它受约束,因为每个模板都有约束 - 一些隐式(只有带有这些操作的类型,只有更专业的模板不存在的情况)和一些显式。

@Tony D - 我应该删除所有这些评论(和我的答案)并写一篇博文。

@ Steve314:"C ++在编译时通过SFINAE错误进行can-it-quack检查 - 模板不会实例化" - 不使用SFINAE的模板会在x.quack()产生编译时错误,而SFINAE允许编译时测试并通过另一个功能来避免错误,甚至不试图嘎嘎叫。如果需要更明确的致命检查,那么我们有库提供的概念,例如boost.org/doc/libs/1_54_0/libs/concept_check/concept_check.htm。但这些只是围绕参数多态性报告的编译时错误的方式/位置进行了转换。

@ Steve314:哦 - 没有意识到你已经发布了一个答案 - 我有一个阅读(如果它还在那里!);-)

@Tony D - 抱歉 -"通过SFINAE错误"是一个脑力计,我的意思是"通过SFINAE规则"。我不确定是否有不同意见 - 澄清我在说什么,当你在模板中调用T::quack ()时,隐含地将该模板限制为只为具有quack ()的T类型实例化。即使你为没有它的某个类型T实例化,quack也不是错误,但是如果没有其他模板覆盖那个案例,那就是错误。所以它不是一个在某些情况下有错误的通用模板,它是一个带有隐式约束的模板。

@ Steve314我怀疑是在谈论相同的知识,只是无法毫不含糊地沟通,知道同意;-)。整蛊! SFINAE在至少一个潜在匹配中往往非常明确:您必须在函数参数或返回类型中进行测试(即使您真的只想在函数体中使用属性),并且在实践中它通常使用std::enable_if或类似于方便和强调SFINAE功能匹配标准的性质。然而,并非所有候选人都会明确地与SFINAE相关的代码混乱。

@TonyD:非常好的答案,但我的问题是如何以及为什么strlen()函数更慢?

@PravasiMeet:每个std::string对象跟踪自己的.size(),因此您可以随时读取成员变量,但strlen(const char*)必须扫描并计算文本中的每个字符,直到找到ASCII NUL终结符 - 文本越长,strlen()越长。大多数编译器会在编译时评估strlen()的字符串文字(例如strlen("abc")) - 所以至少在有限的情况下它是无关紧要的......)

在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 ++被认为不具有多个分派,这被认为是一种特定的运行时分辨率。但是,函数重载可以看作是在编译时完成的多次调度。

回到参数化和ad-hoc多态性,这些术语在函数式编程中更受欢迎,并且它们在C ++中不太起作用。尽管如此...

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

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

重载和虚函数都是ad-hoc多态的例子。

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

|------------+---------------|

| parametric | unconstrained |

| ad-hoc     | constrained   |

|------------+---------------|

除了这些不是完全同义词,尽管它们通常被视为它们,并且这可能是C ++中可能产生混淆的地方。

将这些视为同义词的原因在于,通过将多态性约束到特定类型的类,可以使用特定于这些类类型的操作。这里的"类"一词可以在OOP意义上解释,但实际上只是指(共同命名)共享某些操作的类型集。

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

在例如哈斯克尔,你可以......

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方面是选择使用哪个+和3。 Num的每个"实例"都有自己独特的实现。因此即使在Haskell中,"参数化"和"不受约束"也不是真正的同义词 - 不要怪我,这不是我的错!

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

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

因此,在某种程度上,C ++具有参数多态性,但它是隐式约束的,并且可以被ad-hoc备选方案覆盖 - 即这种分类对C ++并不真正起作用。

好。

+1很多有趣的观点和见解。我只花了几个小时阅读Haskell,所以"a这里是一个不受约束的多态类型[...]因此我们对这种类型的值所做的事情并不多。"感兴趣 - 在C ++中没有概念你不仅限于在指定为模板参数的类型的参数上尝试一组特定的操作...像boost概念这样的库以另一种方式工作 - 确保类型支持你指定的操作而不是防止意外使用额外的操作。

@Tony - 概念是一种明确约束模板多态性的方法。由于兼容性,隐式约束显然不会消失,但显式约束肯定会显着改善事物。我很确定一些过去的概念计划与Haskell类型类有些相关,虽然我没有深入研究它们,当我最后看起来"浅薄"时我并不知道Haskell。

"由于兼容性,隐式约束显然不会消失" - 来自内存,C ++ 0x Concepts确实(承诺: - /)防止"隐式约束" - 你只能以概念承诺的方式使用类型。

这可能没有任何帮助,但我通过为主函数提供定义的函数(如START和END)来向我的朋友介绍编程,因此它并不太令人生畏(它们只使用了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 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

#include

#include

#include

#include

#include

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

#define GET_ENTER_EXIT std::cout <

class Book;

class Library;

typedef std::vector 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 bool isBook(){

return (typeid(C) == typeid(Book) || std::is_base_of());

}

template class DuplicatableClass{

public:

ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){

return new ClassToDuplicate(ToDuplicate);

}

};

class Book : private DuplicatableClass{

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 <

throw std::exception(errorMessage.str().c_str());

}

}

void EditName(const char* Name){

if(approve(Name)){

name_ = Name;

}

else{

std::ostringstream errorMessage;

errorMessage <

throw std::exception(errorMessage.str().c_str());

}

}

virtual void Summarize(){

std::cout <

<< pages_.size() << ((pages_.size() == 1) ?" page:" : ((pages_.size() > 0) ?" pages:" :" pages")) << std::endl;

if(pages_.size() > 0){

ListPages(std::cout);

}

}

private:

std::vector 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() <

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 <

throw std::exception(errorMessage.str().c_str());

}

private:

const char* name_;

Books Collection;

};

template struct FixedBookCollection : public BookCollection{

FixedBookCollection(const char* Name) : BookCollection(Name){

if(!isBook()){

std::ostringstream errorMessage;

errorMessage <

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() <

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 <

throw std::exception(errorMessage.str().c_str());

}

private:

std::vector Collection;

};

template struct FixedSizeBookCollection : private std::array{

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 <

throw std::exception(errorMessage.str().c_str());

}

this->at(currentPos++) = &Book;

}catch(const std::exception& e){

std::ostringstream errorMessage;

errorMessage << e.what() <

throw std::exception(errorMessage.str().c_str());

}

private:

const char* name_;

int currentPos;

};

class Library : private std::vector{

public:

void operator+=(const BookCollection& Collection){

for(int i = 0; i < size(); ++i){

if((*this)[i] == &Collection){

std::ostringstream errorMessage;

errorMessage <

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 <

throw std::exception(errorMessage.str().c_str());

}

Book* DuplicateBook(Book* Book)const{

return (Book->Duplicate(*Book));

}

void Summarize(){

std::cout < 0) ?" book collections:" :" book collections")) << std::endl;

if(size() > 0){

for(int i = 0; i < size(); ++i){

std::cout << (*this)[i]->name_ << std::endl;

}

}

}

};

至于ad-hoc多态,它意味着函数重载或运算符重载。点击这里:

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

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

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

不幸的是,虽然这是正确的,但这是误导性的。由于SFINAE规则,模板函数可以获得隐式约束 - 使用模板中的操作隐式地约束多态性 - 模板特化可以提供覆盖更通用模板的临时替代模板。因此,模板(默认情况下)提供无约束的参数多态,但没有强制执行 - 至少有两种方式可以变为约束或临时。

事实上,你的例子 - 排序 - 意味着一个约束。排序仅适用于有序类型(即提供

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

#include

class Animal{

public:

Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/

virtual void Speak(){

std::cout <

}

const char* name_;

};

class Dog : public Animal{

public:

Dog(const char* Name) : Animal(Name) {/*...*/}

void Speak(){

std::cout <

}

};

int main(void){

Animal Bob("Bob");

Dog Steve("Steve");

Bob.Speak();

Steve.Speak();

//return (0);

}

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

如果有人对这些人说过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中的多态(同名,不同行为)是什么。

如果您要参加面试,面试官要求您告诉/展示我们所在的同一房间的多态性的实例,比如说 -

答案 - 门/窗

想知道怎么样?

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

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

为了更好地理解它,并以简单的方式使用上面的例子..如果你需要参考代码,请按照上面的答案。

正如我上面提到的那样,为了更好地理解上面例子中使用的c ++中的多态性。这可能有助于更新鲜地实际理解和联系在面试时执行代码背后的意义或内容。谢谢!

op问"c ++中的多态"。你的答案太抽象了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值