繁体   English   中英

虚函数是在C ++中实现运行时多态的唯一方法吗?

[英]Are virtual functions the only way to achieve Runtime Polymorphism in C++?

我的一个朋友问我“如何在C ++中实现运行时多态性?” 我回答“通过继承”

他说:“不,只能使用虚拟功能才能实现”。

所以我给了他一个以下代码的例子: -

#include<iostream>
using namespace std;

class A
{
public:
    int i;
    A(){i=100;}
};

class B : public A
{
public:
    int j;
    B(){i = -1; j = 99;}
};

void func(A& myA)
{
    cout<<myA.i << endl;
}

int main()
{
    B b;
    A* a = new B();
    func(*a);
    func(b);
    delete a;
    return 0;
}

这里,函数func()接受A的引用,但是我们传递B的对象,我们可以打印公共成员“i”的值。 他说这是编译时多态。

我的问题是: -

1)运行时多态性是否仅通过虚函数实现?

2)上面的例子是运行时多态还是编译时间?

3)如果我有以下代码: -

void func2(A& myA)
{
    cout << myA.i << endl;
    // dynamic/static cast myA to myB
    cout<<myB.j << endl;
}

它是什么样的多态性? 或者甚至是多态?

该示例未显示动态多态性。 要调用的方法在编译时是已知的。 关于应该调用哪个方法,没有运行时决定( 基于实际对象类型 )。 不同类型没有不同的行为。

例如,动态多态性的例子。
您需要在Base类中提供virtual成员函数,并在派生类中覆盖它。 要调用的实际方法由Base类指针指向的对象的实际类型决定。

在线样本

#include<iostream>
using namespace std;

class A
{
public:
    virtual void doSomething()
    {
        std::cout<<"\nIn A::doSomething()";
    }
};

class B : public A
{
public:
    virtual void doSomething()
    {
        std::cout<<"\nIn B::doSomething()";
    }
};



int main()
{
    B b;
    A obj;
    A* a = &b;
    a->doSomething();

    a = &obj;
    a->doSomething();

    return 0;
}

输出:

In B::doSomething()
In A::doSomething()

运行时多态性是仅通过虚拟函数实现的吗?

不,但virtual功能是最常见和最正确的方法。
多态性可以通过函数指针实现。 请考虑以下代码示例 ,实际调用方法取决于用户输入在运行时决定。 它是一种多态性,不是严格意义上的C ++意义,它规定了不同类型的不同行为。

#include <iostream>

typedef void (*someFunction)(int, char*);

void FirstsomeFunction(int i, char *c)
{
    std::cout<<"\n In FirstsomeFunction";
}

void SecondsomeFunction(int i, char *c)
{
    std::cout<<"\n In SecondsomeFunction";
}

int main()
{
    someFunction arr[1];
    int x = 0;
    std::cin >> x;

    if(x ==0)
        arr[0] = &FirstsomeFunction;
    else
        arr[0] = &SecondsomeFunction;

    (arr[0])(10,"Hello");

    return 0;
}

以上示例是否具有运行时多态性或编译时间?

没有任何类型的多态性。 在所有情况下都会调用相同的方法。 对于不同类型没有不同的行为,因此它不会被分类为任何类型的多态。

C语言的fprintf是一个多态函数。

您可以传递各种句柄,它可以打印到文件,标准输出,打印机, 套接字 ,系统可以表示为流的任何内容。

FILE* file = fopen("output.txt", "w");                    // a file
FILE* file = stdout;                                      // standard output
FILE* file = fopen("/dev/usb/lp0", "w");                  // a usb printer
FILE* file = popen("/usr/bin/lpr -P PDF", "w");           // a PDF file
FILE* file = fdopen(socket(AF_INET,SOCK_STREAM,0), "r+"); // network socket

fprintf(file, "Hello World.\n");

你写的不是多态。

这是你在C ++中做多态的方法:

#include<iostream>
using namespace std;

class A
{
public:
    virtual void func(){
        cout << "printing A" << endl;
    }

    virtual ~A(){}
};

class B : public A
{
public:
    void func(){
        cout << "printing B" << endl;
    }
};

int main()
{
    A* a = new A();
    A* b = new B();

    a->func(); // "printing A"
    b->func(); // "printing B"

    delete a;
    delete b;

    return 0;
}

如果要删除虚拟关键字,则会调用A的方法func两次。

我的一个朋友问我“如何在C ++中实现运行时多态性?” 我回答“通过继承”他说“不,它只能使用虚函数来实现”。

首先,术语多态性是模糊的:在通用计算科学意义上,它指的是隐式调用特定于类型的代码的能力,无论是在编译时还是在运行时。 在C ++标准中,定义非常狭窄的是虚拟调度(这是标准的普遍性)。 显然,你的朋友的问题是有意义的,因为他在询问它是如何在C ++中实现的,他的观点必须来自C ++之外 - 在计算科学术语的更大范围内。

当然,虚拟功能/调度是一个答案,但它们是唯一的答案......?

为了尝试回答这个问题,有必要清楚地了解哪些行为符合运行时多态性。 考虑:

void f(X& x)
{
    // the only situation where C++ doesn't know the run-time type of a variable is
    // where it's an instance of a class/struct accepted by reference or pointer

    ...some operation involving "x"...
}

任何可能导致调用涉及“x”的操作的机器代码的机制,其原因与运行时类型“x”特别相关,正变得非常接近运行时多态,但最后一个问题是:该分支是由语言隐式决定的,还是程序员明确安排的?

在虚拟分派的情况下,编译器隐式地知道创建分支到类型适当代码的虚拟分派表和查找。

但是,假设我们有一个函数指针,它先前设置为解决类型相应的代码,或者是一个特定于类型的数字或枚举,用于控制switch到类型特定的case 这些功能实现与运行时虚拟分派相同的行为,但设置必须由开发人员明确完成,并且没有编译器强制执行以确保完全基于运行时类型进行确定。 他们是否有资格是有争议的 因为C ++在虚拟调度中具有完全隐式的机制,并且因为在C ++标准中,多态性具有与虚拟调度相关的缩小定义,所以我猜大多数C ++程序员会说“不”。

但是在C世界中,描述说qsortbsearch (两个标准的libC函数,它们通过函数指针参数使用运行时调度来处理任意类型)作为运行时多态可能有助于快速理解......说它们更为正常虽然是通用实现。

尽管如此,毫无疑问,数百种计算科学教科书都有运行时多态性的功能定义,我敢打赌,使用函数指针或其他程序员初始化的元数据的调度满足了它们的很大比例。 所以,坚持认为只有一个明确的答案是毫无意义的。

我的问题是: -

1)运行时多态性是否仅通过虚函数实现?

如上所述,我在C ++的背景下倾向于“是”,但它(无休止地)可论证。

2)上面的例子是运行时多态还是编译时间?

两者都没有...在类型的基础上甚至没有两个函数可供选择 - 你总是为func()运行相同的机器代码:由编译器选择,期望类型为A

3)如果我有以下代码: -

void func2(A& myA)
{
    cout << myA.i << endl;
    // dynamic/static cast myA to myB
    cout<<myB.j << endl;
}

它是什么样的多态性? 或者甚至是多态?

根本不具有多态性,因为您没有基于类型的分支。 动态强制转换可以参考myA的运行时类型中的编译器填充类型元数据,如果你使用它只是有条件地调用对myB.j的访问 - 这将是未定义的行为,除非myAB - 然后您将回到手动,显式开发人员协调的特定于类型的行为,以及是否有资格作为“多态”,以上讨论。

使用虚函数实现多态性。 但是要产生任何效果,即根据类型不同的行为,你也需要继承

struct A {
    virtual void f() = 0;
};

struct B : public A {
    void f() {
        // do B things
        std::cout << "B::f() called\n";
    }
};

struct C : public A {
    void f() {
        // do C things
        std::cout << "C::f() called\n";
    }
};

现在,您可以使用不同的行为来指向或引用A ,具体取决于它是B还是C

[C ++]

多态性被定义为控制对一般动作类的访问的一个接口。 有两种类型的多态性,一种是编译时多态,另一种是运行时多态。 编译时多态是函数和运算符重载。 运行时多态性使用继承和虚函数完成。

多态性意味着函数在不同的时间呈现不同的形式。 在编译时,它被称为函数重载。 例如,程序可以包含两个函数,其中一个可以执行整数加法,另一个可以执行浮点数的添加,但函数的名称可以相同,例如add。 函数add()被认为是重载的。 两个或多个函数可以具有相同的名称,但它们的参数列表在参数或其数据类型方面应该不同。 仅在返回类型方面不同的函数不能重载。 编译器将根据传递的参数类型选择正确的函数。 在类的情况下,构造函数可能会被重载,因为可以有初始化和未初始化的对象。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM