C++ 中的多态性:调用重写的方法
Polymorphism in C++: Calling an overridden method
首先,我是 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
会被打印出来,但事实并非如此。为什么?我们覆盖了 class B
中的方法 foo
,我认为应该根据对象的运行时类型来决定应该调用哪个方法,在我的例子中是 B
,但不是静态类型(A
在我的例子中)。
实例是there
当你这样做时:
A c = B();
您正在将 B
值转换为A
。你不想要那个。
您应该创建一个 B
对象并通过 A
指针或引用 访问它以获得多态行为:
B b;
A& c = b;
orlp 说的正是。你也应该学习使用指针(它们很有趣)
A* c = new B();
c->foo(); // overriden foo
delete c;
在 java 中,您有 值语义 ,类型如 int
和 float
,并且您有 reference语义 与其他一切。
C++
情况并非如此:类型系统是统一的,您可以请求.[=34= 获得任何值或引用语义。 ]
使用您编写的代码
A c = B()
您已告诉编译器创建一个 B
类型的新值,然后 将其转换 为 A
类型的值,存储c
中的 值 。在这种情况下,转换意味着从您创建的新 B
实例中取出 A
数据,并将其 复制 到新的 A
实例中存储在 c
.
您可以这样做:
B b;
A &c = b;
这仍然会创建 值 b
,但现在 c
是对 [=18 的 引用 =],这意味着 c
现在将引用您创建的 B
实例,而不是其 A
部分的副本。
现在,这仍然会创建 b
作为局部变量,一旦 b
超出范围,存储在 b
中的对象就会被销毁。如果你想要更持久的东西,你需要使用 pointers;例如像
shared_ptr<A> c = make_shared<B>();
c->foo();
你可以做更多'raw'比如
A *b = new B();
但这是一个'dumb'指针; shared_ptr
更聪明,当没有其他对象引用它时,您的对象将被销毁。如果你做后者,你必须在适当的时候自己进行破坏(把它搞砸是错误的常见来源)
感兴趣的行是这个(改为使用统一初始化语法):
A c = B{};
重要的是要注意,当以这种方式声明时,c
的行为就像一个值。
您的代码从 B
的实例中构造了一个名为 c
的本地 A
。这称为切片:B
中不属于 A
的任何部分都已被 "sliced" 移除,只留下 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
注意c
指向的中间体B
的生命周期自动延长到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
您的困惑源于 Java 和 C++ 之间的关键区别。
在Java如果你写
MyClass var = whatever;
您的变量 var
是对 whatever
返回对象的 引用 。但是,在 C++ 中,此语法表示“通过将表达式 whatever
的结果传递给适当的构造函数来创建 类型 MyClass
的新对象,并复制结果对象放入变量 var
.
特别是,您的代码创建了一个类型为 A
的新对象,名为 c
,并将类型为 B
的临时默认构造对象传递给它的复制构造函数(因为那是唯一适合的构造函数)。由于新建的对象是A
类型,不是B
类型,显然调用了A
的方法foo
。
如果你想引用一个对象,你必须在 C++ 中通过向类型添加 &
来明确请求。但是,对非常量对象的引用不能绑定到临时对象。因此,您还需要显式声明绑定到的对象(或者,使用对 const 对象的引用,并将 foo
成员函数固定为 const
,因为它们不会更改对象反正)。因此,执行您想要的代码的最简单版本将是:
// 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
但是更好的版本应该是 const-correct,然后可以使用您原来的构造:
#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;
,因为这是一件坏事(而且您的代码无论如何都使用显式 std::
,所以它只是多余的)。
但是请注意,C++ 引用仍然不同于 Java 引用,因为它们无法重新分配;任何赋值都会转到底层对象。例如:
#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"
}
如果要更改引用的对象,则必须使用指针:
// definitions of A, B and C as above
int main()
{
A* prt = &b; // pointer ptr points to b
prt->foo(); // outputs "I'm a B"
prt = &c; // reassign ptr to point to c
prt->foo(); // outputs "I'm a C"
}
首先,我是 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
会被打印出来,但事实并非如此。为什么?我们覆盖了 class B
中的方法 foo
,我认为应该根据对象的运行时类型来决定应该调用哪个方法,在我的例子中是 B
,但不是静态类型(A
在我的例子中)。
实例是there
当你这样做时:
A c = B();
您正在将 B
值转换为A
。你不想要那个。
您应该创建一个 B
对象并通过 A
指针或引用 访问它以获得多态行为:
B b;
A& c = b;
orlp 说的正是。你也应该学习使用指针(它们很有趣)
A* c = new B();
c->foo(); // overriden foo
delete c;
在 java 中,您有 值语义 ,类型如 int
和 float
,并且您有 reference语义 与其他一切。
C++
情况并非如此:类型系统是统一的,您可以请求.[=34= 获得任何值或引用语义。 ]
使用您编写的代码
A c = B()
您已告诉编译器创建一个 B
类型的新值,然后 将其转换 为 A
类型的值,存储c
中的 值 。在这种情况下,转换意味着从您创建的新 B
实例中取出 A
数据,并将其 复制 到新的 A
实例中存储在 c
.
您可以这样做:
B b;
A &c = b;
这仍然会创建 值 b
,但现在 c
是对 [=18 的 引用 =],这意味着 c
现在将引用您创建的 B
实例,而不是其 A
部分的副本。
现在,这仍然会创建 b
作为局部变量,一旦 b
超出范围,存储在 b
中的对象就会被销毁。如果你想要更持久的东西,你需要使用 pointers;例如像
shared_ptr<A> c = make_shared<B>();
c->foo();
你可以做更多'raw'比如
A *b = new B();
但这是一个'dumb'指针; shared_ptr
更聪明,当没有其他对象引用它时,您的对象将被销毁。如果你做后者,你必须在适当的时候自己进行破坏(把它搞砸是错误的常见来源)
感兴趣的行是这个(改为使用统一初始化语法):
A c = B{};
重要的是要注意,当以这种方式声明时,c
的行为就像一个值。
您的代码从 B
的实例中构造了一个名为 c
的本地 A
。这称为切片:B
中不属于 A
的任何部分都已被 "sliced" 移除,只留下 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
注意c
指向的中间体B
的生命周期自动延长到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
您的困惑源于 Java 和 C++ 之间的关键区别。
在Java如果你写
MyClass var = whatever;
您的变量 var
是对 whatever
返回对象的 引用 。但是,在 C++ 中,此语法表示“通过将表达式 whatever
的结果传递给适当的构造函数来创建 类型 MyClass
的新对象,并复制结果对象放入变量 var
.
特别是,您的代码创建了一个类型为 A
的新对象,名为 c
,并将类型为 B
的临时默认构造对象传递给它的复制构造函数(因为那是唯一适合的构造函数)。由于新建的对象是A
类型,不是B
类型,显然调用了A
的方法foo
。
如果你想引用一个对象,你必须在 C++ 中通过向类型添加 &
来明确请求。但是,对非常量对象的引用不能绑定到临时对象。因此,您还需要显式声明绑定到的对象(或者,使用对 const 对象的引用,并将 foo
成员函数固定为 const
,因为它们不会更改对象反正)。因此,执行您想要的代码的最简单版本将是:
// 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
但是更好的版本应该是 const-correct,然后可以使用您原来的构造:
#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;
,因为这是一件坏事(而且您的代码无论如何都使用显式 std::
,所以它只是多余的)。
但是请注意,C++ 引用仍然不同于 Java 引用,因为它们无法重新分配;任何赋值都会转到底层对象。例如:
#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"
}
如果要更改引用的对象,则必须使用指针:
// definitions of A, B and C as above
int main()
{
A* prt = &b; // pointer ptr points to b
prt->foo(); // outputs "I'm a B"
prt = &c; // reassign ptr to point to c
prt->foo(); // outputs "I'm a C"
}