C ++中的多态性:调用重写方法

时间:2015-03-15 11:11:34

标签: c++ polymorphism

首先,我是Java编码器,想要了解c ++中的多态性。我为了学习目的写了这个例子:

#include<iostream>

using namespace std;

class A
{
public:
    virtual void foo(){ std::cout << "foo" << std::endl; }
};

class B : public A
{
public:
    void foo(){ std::cout << "overriden foo" << std::endl; }
};

A c = B(); 

int main(){ c.foo(); } //prints foo, not overriden foo

我预计会打印overriden foo,但事实并非如此。为什么?我们覆盖了foo中的方法class B,我认为应该调用哪个方法的决定是从对象的运行时类型开始,在我的例子中是B,但不是一个静态类型(在我的例子中是A)。

实例是there

5 个答案:

答案 0 :(得分:22)

执行此操作时:

A c = B(); 

B转换为A。你不希望这样。

您应该创建一个B对象并通过A 指针或引用访问它以获得多态行为:

B b;
A& c = b;

答案 1 :(得分:14)

在java中,你有值语义,类型为intfloat,你有引用语义和其他一切。

C++中并非如此:类型系统是统一的,您可以获得要求的任何值或参考语义。

使用您编写的代码

A c = B()

您已告诉编译器创建类型为B的新值,然后转换为类型为A的值,并存储值<{1}}中的。在这种情况下,转换意味着从您创建它的新c实例中取出A数据,并复制到B实例中存储的A实例中。 {1}}。

你可以这样做:

c

这仍会创建 B b; A &c = b; ,但现在b引用c,这意味着{{1现在将引用您创建的A实例,而不是其c部分的副本。

现在,这仍然会创建B作为局部变量,只要A超出范围,b中存储的对象就会被销毁。如果你想要更持久的东西,你需要使用指针;例如

之类的东西
b

你可以做更像'原始'的事情,比如

b

但这是一个'哑'指针; shared_ptr<A> c = make_shared<B>(); c->foo(); 更聪明,当没有别的东西引用它时,你的对象就会被摧毁。如果你做了后者,你必须在适当的时候自己做破坏(并且弄乱它是一个常见的错误来源)

答案 2 :(得分:7)

您的困惑源于Java和C ++之间的重要区别。

在Java中如果你写

MyClass var = whatever;

您的变量varwhatever返回的对象的引用。但是,在C ++中,此语法意味着“通过将表达式MyClass的结果传递给适当的构造函数,创建一个类型为whatever新对象,并将生成的对象复制到变量var

特别是,您的代码创建了一个名为A的{​​{1}}类型的新对象,并将类型为c的临时默认构造对象传递给其复制构造函数(因为这是只适合的构造函数)。由于新创建的对象的类型为B,而不是A类型,因此显然会调用B的方法A

如果要引用对象,则必须在C ++中明确请求将foo添加到该类型中。但是,对非常量对象的引用不能绑定到临时对象。因此,您还需要显式声明绑定的对象(或者,使用对const对象的引用,并将&成员函数修复为foo,因为它们不会更改对象无论如何)。因此,最简单的代码版本可以实现您想要的内容:

const

然而,更好的版本将是const-correct,然后可以使用您的原始构造:

// your original definitions of A and B assumed here

B b; // The object of type B
A& c = b; // c is now a *reference* to b
int main() { c.foo(); } // calls B::foo() thanks to polymorphism

(请注意,我删除了#include <iostream> class A { public: virtual void foo() const // note the additional const here! { std::cout << "foo" << std::endl; } }; class B : public A { public: void foo() const // and also const here { std::cout << "overridden foo" << std::endl; } }; A const& c = B(); // Since we bind to a const reference, // the lifetime of the temporary is extended to the // lifetime of the reference int main() { c.foo(); } //prints overridden foo ,因为这样做很糟糕(而且您的代码无论如何都使用了显式using namespace std;,所以它只是多余的。)

但请注意,C ++引用仍然与Java引用不同,因为它们无法重新分配;任何赋值都转到底层对象。例如:

std::

如果要更改引用的对象,则必须使用指针:

#include <iostream>

class A { public: virtual void foo() const { std::cout << "I'm an A\n"; } };
class B: public A { public: void foo() const { std::cout << "I'm a B\n"; } };
class C: public A { public: void foo() const { std::cout << "I'm a C\n"; } };

B b;
C c;

int main()
{
   A& ref = b; // bind reference ref to object b
   ref.foo(); // outputs "I'm a B"
   ref = c; // does *not* re-bind the reference to c, but calls A::operator= (which in this case is a no-op)
   ref.foo(); // again outputs "I'm a B"
}

答案 3 :(得分:4)

感兴趣的是这一点(改为使用统一的初始化语法):

A c = B{};

重要的是要注意,当以这种方式声明时,c的行为就像一个值。

您的代码构造了A实例中名为c的本地B。这称为切片:B中不属于A的任何部分已被“切片”掉,仅留下A

在C ++中给出符号引用语义(称为间接)需要不同的表示法。

例如:

A &c = B{};
A d = B{}; // Casts the intermediate B instance to const A &,
           // then copy-constructs an A

c.foo(); // calls B::foo because c points to a B through an A interface

d.foo(); // calls A::foo because d is only an instance of an A

请注意,B点所指向的中间c的生命周期会自动扩展到c的范围。另一方面,B的构建完成后,第二个中间d被销毁。

在C ++中,引用是不可变的(初始化后不能更改它们)。在表达式中使用时,就好像它们指向的对象(值)一样:

A &c = B{};

c = A{}; // Calls the compiler-provided A::operator = (const A &)
         // (a virtual assignment operator with this signature
         // was not defined).
         // This DOES NOT change where c points.

另一方面,指针可以改变:

A a{};
B b{};

A *cptr = &b;

cptr->foo(); // calls B::foo

cptr = &a;

cptr->foo(); // calls A::foo

答案 4 :(得分:1)

究竟是什么orlp说的。你也应该学会使用指针(它们很有趣)

A* c = new B();
c->foo(); // overriden foo
delete c;