虚函数和多态

时间:2010-03-13 22:27:05

标签: c++ polymorphism virtual

假设我有这个:

class A
{
    public:
    virtual int hello(A a);
};

class B : public A
{
   public:
   int hello(B b){ bla bla };
};

所以,A它是一个抽象类。

1)在B类中,我正在定义一个假设覆盖A类的方法。但它的参数略有不同。我不确定这个,这是对的吗?也许是因为多态性,这是可以的,但它相当令人困惑。 2)如果我这样做:A a =新B;,然后是a.hello(lol);如果“lol”它不是B类,那么它会给出编译错误?,如果是来自另一个C类的C类(C类:公共A),会发生什么?

我对覆盖和虚拟事物感到困惑..我发现的所有例子都使用没有参数的方法。

任何答案,链接或任何欣赏的内容。

感谢

pd:对不起我的英文

6 个答案:

答案 0 :(得分:7)

你的B类没有覆盖 A中的成员函数,它重载它。或者无论如何都要尝试,稍后再看一下隐藏的内容。

覆盖是指派生类从基类定义自己的虚拟成员函数版本。重载是指定义具有相同名称的不同函数。

当对具有基类类型的指针或引用进行虚拟调用时,它只会“考虑”派生类中的覆盖,而不是重载。这是必不可少的 - 对于一个B实例来说,它可以被调用者处理,好像它可以完成A所能做的一切(这是动态多态和虚函数的重点),它的hello函数需要能够接受任何类型A的对象。hello函数只接受类型B的对象,而不是任何A,它的限制性更强。它不能扮演A hello函数的角色,因此它不是覆盖。

如果您在A和B上调用hello进行实验,传递A或B类型的对象,您应该能够看到差异。 A有一个功能A(你没有定义,所以如果你调用它,那么你的程序将无法链接,但你可以修复它)。 B有一个带B的函数。它们碰巧有相同的名字,当然因为B来自A,你可以将B传递给带有A的函数。但是B的函数不能作为虚拟调用中的覆盖

可以在B对象上调用A函数,但只能通过引用或指向A的指针.C ++的一个特性是B中hello的定义隐藏了A中的定义。如果重载是你想要什么,可以通过在类B中添加using A::hello;来取消隐藏基类功能。如果你需要覆盖,你必须定义一个采用相同参数的函数。例如:

#include <iostream>

class A
{
    public:
    virtual int hello(A a) {std::cout << "A\n"; }
    virtual int foo(int i) { std::cout << "A::Foo " << i << "\n"; }
};

class B : public A
{
   public:
   using A::hello;
   // here's an overload
   int hello(B b){ std::cout << "B\n"; };
   // here's an override:
   virtual int foo(int i) { std::cout << "B::Foo " << i << "\n"; }
};

int main() {
    A a;
    B b;
    a.hello(a);  // calls the function exactly as defined in A
    a.hello(b);  // B "is an" A, so this is allowed and slices the parameter
    b.hello(a);  // OK, but only because of `using`
    b.hello(b);  // calls the function exactly as defined in B
    A &ab = b;   // a reference to a B object, but as an A
    ab.hello(a); // calls the function in A
    ab.hello(b); // *also* calls the function in A, proving B has not overridden it
    a.foo(1);    // calls the function in A
    b.foo(2);    // calls the function in B
    ab.foo(3);   // calls the function in B, because it is overridden
}

输出:

A
A
A
B
A
A
A::Foo 1
B::Foo 2
B::Foo 3

如果从B中删除using A::hello;行,则调用b.hello(a);无法编译:

error: no matching function for call to `B::hello(A&)'
note: candidates are: int B::hello(B)

答案 1 :(得分:4)

一堆好的答案告诉你发生了什么,我以为我会为什么跳进来。

有一个名为Liskov Substitution Principle的东西,它表示子类中的函数必须在与基类相同的preconditionspostconditions下工作。在这种情况下,函数必须能够对类型A的任何对象进行操作。注意,由于继承关系,每个B都是-A A,但不是每个A都是-A B.所以要替换基本方法,派生类中的新函数可以削弱先决条件或强化后置条件,但不能强化先决条件或削弱后置条件。

你的覆盖尝试强化了前提条件,它接受Bs,而不是所有As。

请注意,返回类型允许covariance。如果您的基类返回A,那么它保证返回值为-A A.然后基类可以返回B,因为每个B都是-A。

但是对于输入参数,只有逆变量符合LSP的理论要求,而输入/输出参数是不变量。特别是在C ++中,出于重载的目的,所有参数类型都是不变的。

答案 2 :(得分:3)

首先,A不是代码中的抽象类。它必须至少有一个纯虚函数是抽象的。

  1. 不同的参数意味着完全不同的方法,即使名称相同。把它想象成一个不同的名字。这就是为什么它被称为“签名”。如果A是抽象类,则此代码根本不会编译。

  2. 将调用A :: hello()。没问题,参数必须是A类,好像没有继承。

答案 3 :(得分:1)

当您覆盖方法时,它会重新定义方法的作用。您只能覆盖已定义的虚拟成员(使用其参数集)。如果类型为A,则将调用A上的方法。如果类型是B,则即使变量是A类型但包含类型B的实例,也会调用B上的方法。

您无法更改已覆盖方法的参数定义,否则它将不再是覆盖。

答案 4 :(得分:1)

你正在做的是重载没有覆盖,即它就好像B类是:

class B
{
public:
  int hello(A a) {...}
  int hello(B b) {...}
};

你有两个相同名称的功能,但签名不同,这使得它们具有不同的功能(就像标准库具有abs(float)abs(double)等不同的功能一样。)

如果你想覆盖,那么你需要有相同的签名,即B类的hello需要采用A类参数。这样,​​当你在B类对象上调用hello时,它将使用B类hello而不是A类。

如果你真的希望B类的hello只接受B类型的对象,那么你所拥有的就好了,尽管你可能想让A类的hello非虚拟,因为你并不真的想要覆盖它 - - 您正在使用新参数和新行为定义新函数。

答案 5 :(得分:1)

Thansk的答案,但我必须澄清一些事情才能得到我的答案。

假设我的A类完全是我在原始问题中定义它的方式。我添加了另一种方法:

class A {
    ...
    int yeah();
}

然后我将B类定义如下:

class B : public A {
    int hello(A a);
};

另一个C类似于B。

我知道因为我是程序员,所以B和C的hello方法显然会有A类型的对象作为参数,但同一类的实例。 例如:

B b; 
b.hello(some_other_b_instance);

C c; 
c.hello(some_other_c_instance);

问题是在B和C类的每个hello函数中,我想用特定类B或C的属性做特定的事情。由于参数是A类,我不能使用它们。 / p>

我需要它是一种反多态,但它的错误是因为根据定义我可以将一个C实例发送到B hello类,但我知道它不会发生。

我希望你能够理解代码......一个分支是抽象的,真正的工作在特定的B和C中是有意义的,每个人都以他们特定的方式完成工作,使得功能正常工作。但是B和C需要访问他们的成员才能正常工作。