首先,我是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
答案 0 :(得分:22)
执行此操作时:
A c = B();
您将B
值转换为A
。你不希望这样。
您应该创建一个B
对象并通过A
指针或引用访问它以获得多态行为:
B b;
A& c = b;
答案 1 :(得分:14)
在java中,你有值语义,类型为int
和float
,你有引用语义和其他一切。
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;
您的变量var
是whatever
返回的对象的引用。但是,在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;