C中的多态性

据我所知:

C提供三种不同类型的多态性.

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

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

>运行时
>编译时
> ad-hoc多态性
>参数多态性

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

但对于另外两个

> ad-hoc多态性
>参数多态性
website says,

ad-hoc多态性:

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

参数多态性:

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

我几乎无法理解他们:(

任何人都可以用一个例子解释他们两个吗?
我希望这些问题的答案对他们大学的许多新的消息有所帮助.

解决方法:

了解/多态性的要求

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

    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.

多态性的C机制

显式程序员指定的多态性

您可以编写f(),以便它可以通过以下任何方式在多种类型上运行:

>预处理:

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

>重载:

void f(int& x)    { x += 2; }

void f(double& x) { x += 2; }

>模板:

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

>虚拟发货:

struct Base { virtual Base& operator+=(int) = 0; };

struct X : Base
{
    X(int n) : n_(n) { }
    X& operator+=(int n) { n_ += n; return *this; }
    int n_;
};

struct Y : Base
{
    Y(double n) : n_(n) { }
    Y& operator+=(int n) { n_ += n; return *this; }
    double n_;
};

void f(Base& x) { x += 2; } // run-time polymorphic dispatch

其他相关机制

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

>无论如何,他们通常都会被直观地理解(保证“哦,那个”反应),
>它们影响了要求的门槛,以及使用的无缝性,上述机制,以及
>解释是对更重要概念的一种分散注意力.

术语

进一步分类

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

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

>运行时意味着编译器必须为程序在运行时可能处理的所有类型生成代码,并且在运行时选择正确的代码(虚拟调度)
>编译时间表示在编译期间选择特定于类型的代码.这样做的结果是:假设一个程序只用int参数调用上面的f – 取决于所使用的多态机制和内联选择,编译器可能会避免为f(double)生成任何代码,或者生成的代码可能会在编译中的某些时候被丢弃或链接. (除虚拟调度外的所有上述机制)

>支持哪些类型?

> Ad-hoc意味着您提供支持每种类型的显式代码(例如重载,模板专门化);你明确地添加支持“为此”(根据ad hoc的意思)类型,其他一些“这个”,也许“那个”也是;-).
>参数含义您可以尝试将该函数用于各种参数类型,而无需专门执行任何操作来启用其对它们的支持(例如模板,宏).具有与模板/宏期望1相同的函数/运算符的对象是模板/宏需要完成其工作的所有对象,其确切类型无关紧要.从C 11中删除的“概念”有助于表达和执行这些期望 – 让我们希望它们成为后来的标准.

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

template <typename Duck>
void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }

do_ducky_stuff(Vilified_Cygnet());

>子类型(又名包含)多态性允许您在不更新算法/函数的情况下处理新类型,但它们必须从相同的基类派生(虚拟调度)

1 – 模板非常灵活. SFINAE(参见std::enable_if)有效地允许参数多态的几组期望.例如,您可能编码,当您正在处理的数据类型具有.size()成员时,您将使用一个函数,否则另一个函数不需要.size()(但可能会以某种方式受到影响 – 例如,使用较慢的strlen()或不在日志中打印有用的消息).您还可以在使用特定参数实例化模板时指定临时行为,或者保留一些参数参数(partial template specialisation)或不参数(full specialisation).

“多态”

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.

这个答案 – 就像问题一样 – 将C功能与Comp相关联.科学.术语.

讨论

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

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

但是,成为一名出色的C程序员至关重要的是了解多态性真正为你做的事情……

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

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

运行时多态性适合:

>输入由工厂方法处理并吐出作为通过Base * s处理的异构对象集合,
>基于配置文件,命令行开关,UI设置等在运行时选择的实现,
>实现在运行时变化,例如状态机模式.

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

>模板化类的compile-what-c​​alled方面比运行时失败的fat接口更可取
> SFINAE
> CRTP
> optimisations(许多包括内联和死代码消除,循环展开,基于静态堆栈的数组vs堆)
> __FILE __,_ _ ____,字符串文字串联和宏的其他独特功能(仍然是邪恶的;-))
>支持模板和宏测试语义使用,但不要人为地限制提供支持的方式(因为虚拟调度往往需要完全匹配的成员函数覆盖)

支持多态性的其他机制

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

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

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

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

&GT隐式编译器提供的重载

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

>特定于类型的CPU指令
>相同类型的结果.

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

std::string x;
int y = 0;

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

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

&GT标准转化次数

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:

>来自以下集合的零或一次转换:左值到右值的转换,数组到指针的转换以及函数到指针的转换.
>来自以下集合的零或一次转换:整数促销,浮点促销,积分转换,浮点转换,浮点积分转换,指针转换,成员转换指针和布尔转换.
>零或一个资格转换.

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

这些转换允许以下代码:

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.

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

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

&GT强制,演员,隐含的构造者

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

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

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

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

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

>从参数类型“离开”

>从多种数据类型的多态算法代码处理
>为(可能较少)(相同或其他)类型编写的代码.

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

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

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

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

上一篇:带有子类参数的Java getMethod


下一篇:c-多态不能与相同数据类型(基类和继承的类)的函数返回值一起使用