假设我有这个:
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:对不起我的英文
答案 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的东西,它表示子类中的函数必须在与基类相同的preconditions和postconditions下工作。在这种情况下,函数必须能够对类型A的任何对象进行操作。注意,由于继承关系,每个B都是-A A,但不是每个A都是-A B.所以要替换基本方法,派生类中的新函数可以削弱先决条件或强化后置条件,但不能强化先决条件或削弱后置条件。
你的覆盖尝试强化了前提条件,它接受Bs,而不是所有As。
请注意,返回类型允许covariance。如果您的基类返回A,那么它保证返回值为-A A.然后基类可以返回B,因为每个B都是-A。
但是对于输入参数,只有逆变量符合LSP的理论要求,而输入/输出参数是不变量。特别是在C ++中,出于重载的目的,所有参数类型都是不变的。
答案 2 :(得分:3)
首先,A不是代码中的抽象类。它必须至少有一个纯虚函数是抽象的。
不同的参数意味着完全不同的方法,即使名称相同。把它想象成一个不同的名字。这就是为什么它被称为“签名”。如果A是抽象类,则此代码根本不会编译。
将调用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需要访问他们的成员才能正常工作。